Skip to content

Commit e2213ea

Browse files
tbitcsoz-agent
andcommitted
feat: EXEC-001 no-python-c rule (#70) + supplementary rules audit (#71)
#70: Added EXEC-001 rule to agent system prompt — forbids python -c for non-trivial code. Agents must write to file then execute. Only exception: single-line import/version checks. #71: New check_supplementary_rules() in auditor — scans project tree for *_RULES.md files (e.g. YOCTO_BUILD_RULES.md) and warns if they're not referenced in AGENTS.md auto-load registry. Fixable=True. Closes #70, closes #71. Co-Authored-By: Oz <oz-agent@warp.dev>
1 parent a325348 commit e2213ea

2 files changed

Lines changed: 77 additions & 0 deletions

File tree

src/specsmith/agent/runner.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,16 @@ def build_system_prompt(
224224
"I couldn’t find [tool] on your PATH. Please tell me the full path or add it to PATH."
225225
5. NEVER loop between retrying and suggesting installation.
226226
227+
## EXEC-001 — NO INLINE PYTHON EXECUTION:
228+
NEVER run non-trivial Python code via `python -c "..."` or `python -c '...'`.
229+
Always write Python code to a file first, then execute the file:
230+
1. Use write_file to create the script (e.g. /tmp/task.py or .specsmith/scripts/task.py)
231+
2. Use run_command to execute: `python task.py`
232+
The ONLY exception: single-line import/version checks
233+
(e.g. `python -c "import sys; print(sys.version)"`).
234+
Reason: inline python -c cannot be interrupted, does not stream output,
235+
and fails silently on Windows.
236+
227237
## TOOL CALL FORMAT RULE:
228238
NEVER output raw JSON tool calls as text. Use ONLY the native tool_use mechanism.
229239
Do NOT write <tools>, <tool_call>, or JSON blocks in your response text.

src/specsmith/auditor.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -618,6 +618,72 @@ def check_phase_readiness(root: Path) -> list[AuditResult]:
618618
return results
619619

620620

621+
def check_supplementary_rules(root: Path) -> list[AuditResult]:
622+
"""Check that *_RULES.md files are referenced in AGENTS.md (#71).
623+
624+
Scans the project tree for supplementary rule files (e.g.
625+
YOCTO_BUILD_RULES.md, VIVADO_RULES.md) and warns if they are not
626+
listed in the AGENTS.md governance registry.
627+
"""
628+
results: list[AuditResult] = []
629+
agents_path = root / "AGENTS.md"
630+
if not agents_path.exists():
631+
return results
632+
633+
agents_text = agents_path.read_text(encoding="utf-8")
634+
635+
# Find all *_RULES.md or *_BUILD_RULES.md files outside docs/governance/
636+
rule_files: list[Path] = []
637+
skip_dirs = {".git", "node_modules", ".venv", "venv", "__pycache__", ".specsmith"}
638+
for p in root.rglob("*_RULES.md"):
639+
if any(part in skip_dirs for part in p.parts):
640+
continue
641+
# Skip the standard governance files
642+
rel = str(p.relative_to(root)).replace("\\\\", "/")
643+
if rel.startswith("docs/governance/"):
644+
continue
645+
rule_files.append(p)
646+
647+
if not rule_files:
648+
return results # No supplementary rules found
649+
650+
unreferenced: list[str] = []
651+
for rf in rule_files:
652+
rel = str(rf.relative_to(root)).replace("\\\\", "/")
653+
fname = rf.name
654+
# Check if the file is mentioned in AGENTS.md (by name or path)
655+
if fname not in agents_text and rel not in agents_text:
656+
unreferenced.append(rel)
657+
658+
if unreferenced:
659+
results.append(
660+
AuditResult(
661+
name="supplementary-rules",
662+
passed=False,
663+
message=(
664+
f"{len(unreferenced)} supplementary rule file(s) not in AGENTS.md: "
665+
+ ", ".join(unreferenced[:5])
666+
+ (
667+
". Add them to the auto-load registry."
668+
if len(unreferenced) <= 5
669+
else f" (+{len(unreferenced)-5} more)"
670+
)
671+
),
672+
fixable=True,
673+
)
674+
)
675+
else:
676+
results.append(
677+
AuditResult(
678+
name="supplementary-rules",
679+
passed=True,
680+
message=f"All {len(rule_files)} supplementary rule file(s) referenced in AGENTS.md",
681+
)
682+
)
683+
684+
return results
685+
686+
621687
def run_audit(root: Path) -> AuditReport:
622688
"""Run all audit checks and return a report."""
623689
report = AuditReport()
@@ -629,6 +695,7 @@ def run_audit(root: Path) -> AuditReport:
629695
report.results.extend(check_type_mismatch(root))
630696
report.results.extend(check_trace_chain_integrity(root))
631697
report.results.extend(check_phase_readiness(root))
698+
report.results.extend(check_supplementary_rules(root))
632699
return report
633700

634701

0 commit comments

Comments
 (0)