PICurv 0.1.0
A Parallel Particle-In-Cell Solver for Curvilinear LES
 
Loading...
Searching...
No Matches
Functions | Variables
audit_agent_setup Namespace Reference

Functions

dict[str, str] frontmatter (Path path)
 Parse the simple YAML front matter used by a skill file.
 
dict[str, Path] skill_entries (Path root)
 Return skill directories directly below one discovery root.
 
set[str] entry_names (Path root)
 Return every direct child name under a skill discovery root.
 
None remove_entry (Path path)
 Remove one skill-tree entry without following a directory symlink.
 
None synchronize ()
 Materialize byte-identical Claude skill copies from the canonical tree.
 
subprocess.CompletedProcess[str] git (*str args)
 Run Git with machine-global excludes disabled.
 
list[str] audit ()
 Validate shared instructions, skill discovery copies, and local-settings hygiene.
 
argparse.Namespace parse_args (list[str] argv)
 Parse audit command-line arguments.
 
int main (list[str]|None argv=None)
 Synchronize when requested, then fail closed on portability drift.
 

Variables

 REPO_ROOT = Path(__file__).resolve().parents[2]
 
str CANONICAL_ROOT = REPO_ROOT / ".agents" / "skills"
 
str CLAUDE_ROOT = REPO_ROOT / ".claude" / "skills"
 
dict EXPECTED_SKILLS
 
str LOCAL_SETTINGS = ".claude/settings.local.json"
 
str LOCAL_SETTINGS_PATTERN = "/.claude/settings.local.json"
 

Detailed Description

Audit and synchronize the repository-portable agent instruction setup.

Function Documentation

◆ frontmatter()

dict[str, str] audit_agent_setup.frontmatter ( Path  path)

Parse the simple YAML front matter used by a skill file.

Parameters
[in]pathSkill file to inspect.
Returns
Parsed scalar front-matter fields.

Definition at line 26 of file audit_agent_setup.py.

26def frontmatter(path: Path) -> dict[str, str]:
27 """!
28 @brief Parse the simple YAML front matter used by a skill file.
29 @param[in] path Skill file to inspect.
30 @return Parsed scalar front-matter fields.
31 """
32
33 lines = path.read_text(encoding="utf-8").splitlines()
34 if not lines or lines[0] != "---":
35 return {}
36 fields: dict[str, str] = {}
37 for line in lines[1:]:
38 if line == "---":
39 return fields
40 if ":" in line:
41 key, value = line.split(":", 1)
42 fields[key.strip()] = value.strip()
43 return {}
44
45
Here is the caller graph for this function:

◆ skill_entries()

dict[str, Path] audit_agent_setup.skill_entries ( Path  root)

Return skill directories directly below one discovery root.

Parameters
[in]rootSkill discovery directory.
Returns
Mapping from directory name to path, including symlinked directories.

Definition at line 46 of file audit_agent_setup.py.

46def skill_entries(root: Path) -> dict[str, Path]:
47 """!
48 @brief Return skill directories directly below one discovery root.
49 @param[in] root Skill discovery directory.
50 @return Mapping from directory name to path, including symlinked directories.
51 """
52
53 if not root.is_dir():
54 return {}
55 return {
56 child.name: child
57 for child in root.iterdir()
58 if child.is_dir() or child.is_symlink()
59 }
60
61
Here is the caller graph for this function:

◆ entry_names()

set[str] audit_agent_setup.entry_names ( Path  root)

Return every direct child name under a skill discovery root.

Parameters
[in]rootSkill discovery directory.
Returns
Child-name set, including unexpected files and links.

Definition at line 62 of file audit_agent_setup.py.

62def entry_names(root: Path) -> set[str]:
63 """!
64 @brief Return every direct child name under a skill discovery root.
65 @param[in] root Skill discovery directory.
66 @return Child-name set, including unexpected files and links.
67 """
68
69 return {child.name for child in root.iterdir()} if root.is_dir() else set()
70
71
Here is the caller graph for this function:

◆ remove_entry()

None audit_agent_setup.remove_entry ( Path  path)

Remove one skill-tree entry without following a directory symlink.

Parameters
[in]pathExact materialized or symlinked skill path.
Returns
None.

Definition at line 72 of file audit_agent_setup.py.

72def remove_entry(path: Path) -> None:
73 """!
74 @brief Remove one skill-tree entry without following a directory symlink.
75 @param[in] path Exact materialized or symlinked skill path.
76 @return None.
77 """
78
79 if path.is_symlink() or path.is_file():
80 path.unlink()
81 elif path.is_dir():
82 shutil.rmtree(path)
83
84
Here is the caller graph for this function:

◆ synchronize()

None audit_agent_setup.synchronize ( )

Materialize byte-identical Claude skill copies from the canonical tree.

Returns
None.
Exceptions
RuntimeErrorwhen the canonical skill set is incomplete or unexpected.

Definition at line 85 of file audit_agent_setup.py.

85def synchronize() -> None:
86 """!
87 @brief Materialize byte-identical Claude skill copies from the canonical tree.
88 @return None.
89 @throws RuntimeError when the canonical skill set is incomplete or unexpected.
90 """
91
92 canonical = skill_entries(CANONICAL_ROOT)
93 canonical_names = entry_names(CANONICAL_ROOT)
94 if canonical_names != EXPECTED_SKILLS:
95 missing = sorted(EXPECTED_SKILLS - canonical_names)
96 extra = sorted(canonical_names - EXPECTED_SKILLS)
97 raise RuntimeError(f"canonical skill set differs: missing={missing}, extra={extra}")
98
99 CLAUDE_ROOT.mkdir(parents=True, exist_ok=True)
100 for path in list(CLAUDE_ROOT.iterdir()):
101 remove_entry(path)
102 for name in sorted(EXPECTED_SKILLS):
103 shutil.copytree(canonical[name], CLAUDE_ROOT / name, symlinks=False)
104
105
Head of a generic C-style linked list.
Definition variables.h:469
Here is the call graph for this function:
Here is the caller graph for this function:

◆ git()

subprocess.CompletedProcess[str] audit_agent_setup.git ( *str  args)

Run Git with machine-global excludes disabled.

Parameters
[in]argsArguments following git.
Returns
Completed process with captured text output.

Definition at line 106 of file audit_agent_setup.py.

106def git(*args: str) -> subprocess.CompletedProcess[str]:
107 """!
108 @brief Run Git with machine-global excludes disabled.
109 @param[in] args Arguments following `git`.
110 @return Completed process with captured text output.
111 """
112
113 return subprocess.run(
114 ["git", "-c", "core.excludesFile=/dev/null", "-C", str(REPO_ROOT), *args],
115 capture_output=True,
116 text=True,
117 check=False,
118 )
119
120
Here is the caller graph for this function:

◆ audit()

list[str] audit_agent_setup.audit ( )

Validate shared instructions, skill discovery copies, and local-settings hygiene.

Returns
Human-readable violations; an empty list means the audit passed.

Definition at line 121 of file audit_agent_setup.py.

121def audit() -> list[str]:
122 """!
123 @brief Validate shared instructions, skill discovery copies, and local-settings hygiene.
124 @return Human-readable violations; an empty list means the audit passed.
125 """
126
127 errors: list[str] = []
128 agents = REPO_ROOT / "AGENTS.md"
129 claude = REPO_ROOT / "CLAUDE.md"
130
131 if not agents.is_file() or agents.is_symlink():
132 errors.append("AGENTS.md must be a tracked regular file, not a symlink")
133 if not claude.is_file() or claude.is_symlink():
134 errors.append("CLAUDE.md must be a regular file, not a symlink")
135 elif "@AGENTS.md" not in {line.strip() for line in claude.read_text(encoding="utf-8").splitlines()}:
136 errors.append("CLAUDE.md must import the canonical @AGENTS.md instructions")
137
138 canonical = skill_entries(CANONICAL_ROOT)
139 copies = skill_entries(CLAUDE_ROOT)
140 canonical_names = entry_names(CANONICAL_ROOT)
141 copy_names = entry_names(CLAUDE_ROOT)
142 if canonical_names != EXPECTED_SKILLS:
143 errors.append(
144 "canonical skill set mismatch: "
145 f"missing={sorted(EXPECTED_SKILLS - canonical_names)}, "
146 f"extra={sorted(canonical_names - EXPECTED_SKILLS)}"
147 )
148 if copy_names != EXPECTED_SKILLS:
149 errors.append(
150 "Claude skill set mismatch: "
151 f"missing={sorted(EXPECTED_SKILLS - copy_names)}, "
152 f"extra={sorted(copy_names - EXPECTED_SKILLS)}; run make sync-agent-skills"
153 )
154
155 for name in sorted(EXPECTED_SKILLS & set(canonical)):
156 source_dir = canonical[name]
157 source = source_dir / "SKILL.md"
158 if source_dir.is_symlink() or not source.is_file() or source.is_symlink():
159 errors.append(f"canonical skill {name} must contain a regular SKILL.md")
160 continue
161 metadata = frontmatter(source)
162 if metadata.get("name") != name:
163 errors.append(f"{source.relative_to(REPO_ROOT)} front-matter name must be {name!r}")
164 if not metadata.get("description"):
165 errors.append(f"{source.relative_to(REPO_ROOT)} requires a non-empty description")
166
167 target_dir = copies.get(name)
168 target = target_dir / "SKILL.md" if target_dir else None
169 if target_dir is None:
170 continue
171 if target_dir.is_symlink() or target is None or not target.is_file() or target.is_symlink():
172 errors.append(f"Claude skill {name} must be a materialized regular-file copy")
173 continue
174 source_files = sorted(
175 path.relative_to(source_dir) for path in source_dir.rglob("*") if path.is_file()
176 )
177 target_files = sorted(
178 path.relative_to(target_dir) for path in target_dir.rglob("*") if path.is_file()
179 )
180 if source_files != target_files:
181 errors.append(f"Claude skill {name} file set differs; run make sync-agent-skills")
182 continue
183 for relative in source_files:
184 if (source_dir / relative).read_bytes() != (target_dir / relative).read_bytes():
185 errors.append(
186 f"Claude skill {name}/{relative} differs byte-for-byte; "
187 "run make sync-agent-skills"
188 )
189
190 tracked = git("ls-files", "--error-unmatch", "--", LOCAL_SETTINGS)
191 if tracked.returncode == 0:
192 errors.append(f"{LOCAL_SETTINGS} is machine-local and must not be tracked")
193 ignored = git("check-ignore", "-v", "--", LOCAL_SETTINGS)
194 if ignored.returncode != 0:
195 errors.append(f"{LOCAL_SETTINGS} must be ignored by the repository .gitignore")
196 else:
197 fields = ignored.stdout.rstrip().split("\t", 1)[0].rsplit(":", 2)
198 source = fields[0] if len(fields) == 3 else ""
199 pattern = fields[2] if len(fields) == 3 else ""
200 if Path(source).name != ".gitignore" or pattern != LOCAL_SETTINGS_PATTERN:
201 errors.append(
202 f"{LOCAL_SETTINGS} must use the exact repository ignore pattern "
203 f"{LOCAL_SETTINGS_PATTERN!r}, not a user-global or incidental rule"
204 )
205
206 return errors
207
208
Here is the call graph for this function:
Here is the caller graph for this function:

◆ parse_args()

argparse.Namespace audit_agent_setup.parse_args ( list[str]  argv)

Parse audit command-line arguments.

Parameters
[in]argvArguments excluding the executable name.
Returns
Parsed command-line namespace.

Definition at line 209 of file audit_agent_setup.py.

209def parse_args(argv: list[str]) -> argparse.Namespace:
210 """!
211 @brief Parse audit command-line arguments.
212 @param[in] argv Arguments excluding the executable name.
213 @return Parsed command-line namespace.
214 """
215
216 parser = argparse.ArgumentParser(
217 description="Audit or synchronize PICurv's portable shared agent setup."
218 )
219 parser.add_argument(
220 "--sync",
221 action="store_true",
222 help="replace Claude skill entries with materialized copies of canonical .agents skills",
223 )
224 return parser.parse_args(argv)
225
226
Here is the caller graph for this function:

◆ main()

int audit_agent_setup.main ( list[str] | None   argv = None)

Synchronize when requested, then fail closed on portability drift.

Parameters
[in]argvOptional arguments excluding the executable name.
Returns
Zero on success and one on any setup violation.

Definition at line 227 of file audit_agent_setup.py.

227def main(argv: list[str] | None = None) -> int:
228 """!
229 @brief Synchronize when requested, then fail closed on portability drift.
230 @param[in] argv Optional arguments excluding the executable name.
231 @return Zero on success and one on any setup violation.
232 """
233
234 args = parse_args(sys.argv[1:] if argv is None else argv)
235 try:
236 if args.sync:
237 synchronize()
238 except (OSError, RuntimeError) as error:
239 print(f"Agent setup synchronization failed: {error}", file=sys.stderr)
240 return 1
241
242 errors = audit()
243 if errors:
244 for error in errors:
245 print(f"ERROR: {error}", file=sys.stderr)
246 return 1
247 action = "synchronized and audited" if args.sync else "audited"
248 print(f"Agent setup {action}: shared instructions and {len(EXPECTED_SKILLS)} skills are portable.")
249 return 0
250
251
int main(int argc, char **argv)
Entry point for the postprocessor executable.
Here is the call graph for this function:
Here is the caller graph for this function:

Variable Documentation

◆ REPO_ROOT

audit_agent_setup.REPO_ROOT = Path(__file__).resolve().parents[2]

Definition at line 13 of file audit_agent_setup.py.

◆ CANONICAL_ROOT

str audit_agent_setup.CANONICAL_ROOT = REPO_ROOT / ".agents" / "skills"

Definition at line 14 of file audit_agent_setup.py.

◆ CLAUDE_ROOT

str audit_agent_setup.CLAUDE_ROOT = REPO_ROOT / ".claude" / "skills"

Definition at line 15 of file audit_agent_setup.py.

◆ EXPECTED_SKILLS

dict audit_agent_setup.EXPECTED_SKILLS
Initial value:
1= {
2 "picurv-capability-change",
3 "picurv-solver-debugging",
4 "picurv-spatial-kernel-change",
5 "picurv-technical-communication",
6}

Definition at line 16 of file audit_agent_setup.py.

◆ LOCAL_SETTINGS

str audit_agent_setup.LOCAL_SETTINGS = ".claude/settings.local.json"

Definition at line 22 of file audit_agent_setup.py.

◆ LOCAL_SETTINGS_PATTERN

str audit_agent_setup.LOCAL_SETTINGS_PATTERN = "/.claude/settings.local.json"

Definition at line 23 of file audit_agent_setup.py.