Skip to content

Commit c3e31aa

Browse files
committed
Deep audit pass complete — 97 tests, smoke, operator smoke, and production audit all green.
Per-project identity (fingerprint) project_fingerprint.py now collects industry-standard signals: verification_commands — inferred from Makefile (make verify), npm scripts, pytest/Jest/Go/Cargo runtime_versions — .nvmrc, .node-version, .python-version, .tool-versions dependency_automation — Renovate / Dependabot detection has_codeowners — GitHub CODEOWNERS presence Cache invalidation extended for these files These flow through steering_context, session_brief, and build_project_steering_digest. Bootstrap fill hardening _anti_goal() no longer returns the exact placeholder phrase — always produces project-specific anti-goals (including a library archetype) _workflow_hint() prefers verification_commands over generic script lists bootstrap_steering_bundle() always includes preview when requested, even when the skeleton is fully evidence-autofilled Checkpoint context guard — "apply autofill preview" no longer triggers disk writes (only explicit write contexts do) Agent ergonomics wiring merge_roadmap_hint_into_result() propagates project_steering_digest and agent_next_call to the top-level tool response format_agent_steering_line() shows Verify and Docs lines from fingerprint Checkpoint fallback removed generic "Edit ROADMAP.md per skill" — uses recommended_next_action → validate/checkpoint/autofill Production audit expansion scripts/roadmap_audit.py now verifies: Full BOOTSTRAP_PLACEHOLDER_PHRASES coverage (no unmapped or manual-only phrases) verification_commands from Makefile projects session_brief includes digest when bootstrap incomplete Native write hint merge propagates digest + agent_next_call Template brief expectations when skeleton is fully evidence-autofilled End-to-end flow (unchanged architecture, tighter execution) fingerprint (verify cmds, runtime, CODEOWNERS, …) → evidence → bootstrap_fill_plan → project_steering_digest → all surfaces (session / cockpit / checkpoint / write hints / doctor / progress) → apply_bootstrap_fill (preview/write) → validate → recommended_next_action Evidence-driven skeletons for projects with README + Makefile can now autofill all template phrases in one pass; remaining placeholders still route through apply_bootstrap_fill with per-project replacements from fingerprint, git, and code soup audit.
1 parent 3a2dcb1 commit c3e31aa

9 files changed

Lines changed: 262 additions & 19 deletions

lib/agent/roadmap/agent_steering.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,14 @@ def _project_context_lines(steering: dict[str, Any]) -> list[str]:
4040
if make_targets:
4141
lines.append(f"Makefile: {', '.join(make_targets[:4])}")
4242

43+
verify_cmds = steering.get("verification_commands") or []
44+
if verify_cmds:
45+
lines.append(f"Verify: {', '.join(verify_cmds[:3])}")
46+
47+
docs_roots = steering.get("docs_roots") or []
48+
if docs_roots:
49+
lines.append(f"Docs: {', '.join(docs_roots[:3])}")
50+
4351
if steering.get("has_backstage_catalog"):
4452
lines.append("Backstage: catalog-info.yaml present")
4553
catalog = steering.get("catalog_name")

lib/agent/roadmap/bootstrap_fill.py

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ def _suggest_replacement(
3737
ci = fingerprint.get("ci_systems") or []
3838
scripts = fingerprint.get("entry_points") or []
3939
make_targets = fingerprint.get("makefile_targets") or []
40+
verify_cmds = fingerprint.get("verification_commands") or []
4041
if make_targets:
4142
scripts = list(dict.fromkeys([*scripts, *make_targets[:3]]))
4243

@@ -74,7 +75,7 @@ def _suggest_replacement(
7475
"project_fingerprint.runtime_center_hint",
7576
),
7677
"A fragmented patch surface without a documented center of gravity.": (
77-
_anti_goal(archetype),
78+
_anti_goal(archetype, brief=brief),
7879
"project_fingerprint.project_archetype",
7980
),
8081
"Initial roadmap bootstrap.": (
@@ -131,7 +132,7 @@ def _suggest_replacement(
131132
"project_fingerprint.runtime_center_hint",
132133
),
133134
"List anti-goals that protect coherence.": (
134-
_anti_goal(archetype),
135+
_anti_goal(archetype, brief=brief),
135136
"project_fingerprint.project_archetype",
136137
),
137138
"Describe what the project is becoming using README, architecture docs, and recent commits.": (
@@ -281,10 +282,15 @@ def _workflow_hint(
281282
ci: list[str],
282283
) -> str:
283284
parts: list[str] = []
284-
if scripts:
285+
verify_cmds = fingerprint.get("verification_commands") or []
286+
if verify_cmds:
287+
parts.append(f"verify via {verify_cmds[0]}")
288+
elif scripts:
285289
parts.append(f"dev/build via {', '.join(scripts[:3])}")
286-
if tests:
290+
if tests and not verify_cmds:
287291
parts.append(f"verify with {tests[0]}")
292+
elif tests:
293+
parts.append(f"tests: {tests[0]}")
288294
if ci:
289295
parts.append(f"CI: {ci[0]}")
290296
archetype = fingerprint.get("project_archetype") or ""
@@ -298,14 +304,19 @@ def _workflow_hint(
298304
return "Document primary dev, deploy, and agent-assisted workflows from README and scripts."
299305

300306

301-
def _anti_goal(archetype: str) -> str:
307+
def _anti_goal(archetype: str, *, brief: str = "") -> str:
302308
goals = {
303309
"hermes-plugin": "A Hermes plugin that stores ROADMAP.md outside the project workspace or drifts from kernel hook conventions.",
304310
"monorepo": "A monorepo without documented package boundaries and shared center of gravity.",
305311
"web-app": "A web app whose UI, API, and deploy surfaces diverge without documented authority boundaries.",
306312
"cli-tool": "A CLI whose entrypoints multiply without a documented operational center.",
313+
"library": "A library whose public API surface drifts without documented stability boundaries.",
307314
}
308-
return goals.get(archetype, "A fragmented patch surface without a documented center of gravity.")
315+
if archetype in goals:
316+
return goals[archetype]
317+
if brief:
318+
return f"Uncontrolled scope changes to {brief} without a documented center of gravity."
319+
return "Uncontrolled scope sprawl without a documented center of gravity."
309320

310321

311322
def _primary_risk(evidence: dict[str, Any], fingerprint: dict[str, Any]) -> str:
@@ -482,10 +493,17 @@ def build_project_steering_digest(
482493
"has_ci": fingerprint.get("has_ci"),
483494
"has_tests": fingerprint.get("has_tests"),
484495
"entry_points": fingerprint.get("entry_points") or [],
496+
"verification_commands": fingerprint.get("verification_commands") or [],
485497
"git_remote": fingerprint.get("git_remote"),
486498
"agent_rules_files": fingerprint.get("agent_rules_files") or [],
487499
"makefile_targets": fingerprint.get("makefile_targets") or [],
500+
"docs_roots": fingerprint.get("docs_roots") or [],
501+
"license": fingerprint.get("license"),
502+
"runtime_versions": fingerprint.get("runtime_versions"),
503+
"has_codeowners": fingerprint.get("has_codeowners"),
504+
"dependency_automation": fingerprint.get("dependency_automation"),
488505
"has_backstage_catalog": fingerprint.get("has_backstage_catalog"),
506+
"catalog_name": fingerprint.get("catalog_name"),
489507
}
490508
if fill_plan:
491509
digest["bootstrap_remaining"] = fill_plan.get("remaining_count")
@@ -516,7 +534,7 @@ def bootstrap_steering_bundle(
516534
"bootstrap_fill_plan": fill_plan,
517535
"project_steering_digest": build_project_steering_digest(fp, fill_plan=fill_plan),
518536
}
519-
if include_preview and fill_plan.get("tasks"):
537+
if include_preview:
520538
out["bootstrap_autofill_preview"] = apply_bootstrap_fill_draft(roadmap_text, evidence)
521539
if fill_plan.get("tasks"):
522540
out["operator_summary"] = fill_plan.get("operator_summary")

lib/agent/roadmap/native_bridge.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,11 @@ def merge_roadmap_hint_into_result(result: Any, hint: dict[str, Any]) -> str:
209209
parsed = {"result": result}
210210

211211
parsed["_roadmap_write_hint"] = hint
212+
digest = hint.get("project_steering_digest")
213+
if digest:
214+
parsed["project_steering_digest"] = digest
215+
if hint.get("bootstrap_incomplete") and isinstance(digest, dict) and digest.get("agent_next_call"):
216+
parsed["agent_next_call"] = digest["agent_next_call"]
212217
try:
213218
from plugins.dietcode.lib.agent.roadmap.operator import build_agent_operator_hints
214219

lib/agent/roadmap/project_fingerprint.py

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,16 @@ def _fingerprint_cache_token(root: Path) -> float:
8989
"go.mod",
9090
"Makefile",
9191
"AGENTS.md",
92+
"CONTRIBUTING.md",
9293
"catalog-info.yaml",
94+
".nvmrc",
95+
".node-version",
96+
".python-version",
97+
".tool-versions",
98+
".github/CODEOWNERS",
99+
"CODEOWNERS",
100+
"renovate.json",
101+
".github/dependabot.yml",
93102
):
94103
path = root / rel
95104
if path.is_file():
@@ -439,6 +448,72 @@ def _catalog_metadata(root: Path) -> dict[str, Optional[str]]:
439448
return meta
440449

441450

451+
def _runtime_versions(root: Path) -> dict[str, str]:
452+
versions: dict[str, str] = {}
453+
for rel, label in (
454+
(".nvmrc", "node"),
455+
(".node-version", "node"),
456+
(".python-version", "python"),
457+
(".tool-versions", "asdf"),
458+
):
459+
path = root / rel
460+
if not path.is_file():
461+
continue
462+
line = _read_text(path, limit=64).strip().splitlines()
463+
if line:
464+
versions[label] = line[0][:32]
465+
return versions
466+
467+
468+
def _dependency_automation(root: Path) -> list[str]:
469+
found: list[str] = []
470+
if (root / "renovate.json").is_file() or (root / ".github" / "renovate.json").is_file():
471+
found.append("Renovate")
472+
if (root / ".github" / "dependabot.yml").is_file() or (root / ".github" / "dependabot.yaml").is_file():
473+
found.append("Dependabot")
474+
return found
475+
476+
477+
def _verification_commands(
478+
root: Path,
479+
*,
480+
entry_points: list[str],
481+
makefile_targets: list[str],
482+
test_frameworks: list[str],
483+
) -> list[str]:
484+
"""Industry-standard verify commands inferred from Makefile, package scripts, and test markers."""
485+
cmds: list[str] = []
486+
for target in ("verify", "test", "lint", "check", "ci"):
487+
if target in makefile_targets:
488+
cmds.append(f"make {target}")
489+
break
490+
491+
scripts = _package_json(root).get("scripts")
492+
if isinstance(scripts, dict):
493+
for name in ("verify", "test", "lint", "ci", "check"):
494+
if name in scripts:
495+
cmd = f"npm run {name}" if (root / "package.json").is_file() else name
496+
if cmd not in cmds:
497+
cmds.append(cmd)
498+
break
499+
500+
if not cmds:
501+
if "pytest" in test_frameworks:
502+
cmds.append("pytest")
503+
elif any(t in test_frameworks for t in ("Jest", "Vitest")):
504+
cmds.append("npm test")
505+
elif (root / "go.mod").is_file():
506+
cmds.append("go test ./...")
507+
elif (root / "Cargo.toml").is_file():
508+
cmds.append("cargo test")
509+
elif entry_points:
510+
first = entry_points[0]
511+
if first in ("test", "verify", "lint", "check"):
512+
cmds.append(f"npm run {first}" if (root / "package.json").is_file() else first)
513+
514+
return cmds[:4]
515+
516+
442517
def _makefile_targets(root: Path) -> list[str]:
443518
makefile = root / "Makefile"
444519
if not makefile.is_file():
@@ -491,6 +566,15 @@ def _build_project_fingerprint(root: Path) -> dict[str, Any]:
491566
docs_roots = _docs_roots(root)
492567
agent_rules = _agent_rules_files(root)
493568
makefile_targets = _makefile_targets(root)
569+
runtime_versions = _runtime_versions(root)
570+
dependency_automation = _dependency_automation(root)
571+
has_codeowners = (root / ".github" / "CODEOWNERS").is_file() or (root / "CODEOWNERS").is_file()
572+
verification_commands = _verification_commands(
573+
root,
574+
entry_points=entry_points,
575+
makefile_targets=makefile_targets,
576+
test_frameworks=test_frameworks,
577+
)
494578
has_backstage = (root / "catalog-info.yaml").is_file()
495579
catalog_meta = _catalog_metadata(root) if has_backstage else {}
496580

@@ -556,6 +640,10 @@ def _build_project_fingerprint(root: Path) -> dict[str, Any]:
556640
"docs_roots": docs_roots,
557641
"agent_rules_files": agent_rules,
558642
"makefile_targets": makefile_targets,
643+
"verification_commands": verification_commands,
644+
"runtime_versions": runtime_versions or None,
645+
"dependency_automation": dependency_automation or None,
646+
"has_codeowners": has_codeowners,
559647
"has_backstage_catalog": has_backstage,
560648
"catalog_name": catalog_meta.get("catalog_name"),
561649
"catalog_description": catalog_meta.get("catalog_description"),

lib/agent/roadmap/roadmap_checkpoint.py

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -276,7 +276,7 @@ def checkpoint_brief(
276276
if status.get("phase") == "bootstrap_fill"
277277
else "roadmap(action='checkpoint')"
278278
if not evidence.get("roadmap", {}).get("exists")
279-
else "Edit ROADMAP.md per skill, then roadmap(action='validate'), then return checkpoint summary."
279+
else "roadmap(action='validate')"
280280
)
281281
),
282282
}
@@ -305,10 +305,19 @@ def checkpoint_brief(
305305
bootstrap_inc=True,
306306
)
307307
ctx_lower = (context or "").lower()
308-
if any(
309-
phrase in ctx_lower
310-
for phrase in ("apply autofill", "apply bootstrap", "autofill write", "write autofill")
311-
):
308+
autofill_write = (
309+
any(
310+
phrase in ctx_lower
311+
for phrase in (
312+
"apply autofill write",
313+
"apply bootstrap write",
314+
"autofill write",
315+
"write autofill",
316+
)
317+
)
318+
or ctx_lower.strip() in ("apply autofill", "apply bootstrap", "autofill")
319+
) and "preview" not in ctx_lower
320+
if autofill_write:
312321
from plugins.dietcode.lib.agent.roadmap.bootstrap_fill import write_bootstrap_autofill
313322

314323
applied = write_bootstrap_autofill(workspace=root, dry_run=False)

lib/agent/roadmap/session.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ def session_brief(*, workspace: Optional[str] = None) -> dict[str, Any] | None:
5555
"project_archetype": steering.get("project_archetype"),
5656
"agent_rules_files": steering.get("agent_rules_files"),
5757
"makefile_targets": steering.get("makefile_targets"),
58+
"verification_commands": steering.get("verification_commands"),
5859
"has_backstage_catalog": steering.get("has_backstage_catalog"),
5960
"readme_tagline": steering.get("readme_tagline"),
6061
"center_of_gravity_excerpt": steering.get("center_of_gravity_excerpt"),

lib/agent/roadmap/steering_context.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,10 @@ def build_steering_context(*, workspace: Optional[str] = None) -> dict[str, Any]
166166
"docs_roots",
167167
"agent_rules_files",
168168
"makefile_targets",
169+
"verification_commands",
170+
"runtime_versions",
171+
"has_codeowners",
172+
"dependency_automation",
169173
"has_backstage_catalog",
170174
"catalog_name",
171175
"catalog_description",

scripts/roadmap_audit.py

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,8 @@ def main() -> int:
216216
failures.append("fingerprint should detect AGENTS.md")
217217
if not fp.get("makefile_targets"):
218218
failures.append("fingerprint should detect Makefile targets")
219+
if "make verify" not in (fp.get("verification_commands") or []):
220+
failures.append("fingerprint missing make verify from Makefile targets")
219221
if fp.get("project_archetype") not in {"project", "library", "application", "web-app", "cli-tool", "hermes-plugin", "monorepo"}:
220222
failures.append(f"unexpected project_archetype: {fp.get('project_archetype')}")
221223
roadmap_text = (root / "ROADMAP.md").read_text(encoding="utf-8")
@@ -270,6 +272,48 @@ def main() -> int:
270272
failures.append("session_brief missing _roadmap_operator_hints")
271273
if not (brief or {}).get("steering_line"):
272274
failures.append("session_brief missing steering_line")
275+
if not (brief or {}).get("project_steering_digest"):
276+
failures.append("session_brief missing project_steering_digest when bootstrap incomplete")
277+
278+
from plugins.dietcode.lib.agent.roadmap.schema import BOOTSTRAP_PLACEHOLDER_PHRASES
279+
280+
minimal_evidence = {
281+
"project_fingerprint": {
282+
"steering_brief": "Audit Project — test stack",
283+
"purpose_hint": "Purpose line.",
284+
"project_archetype": "library",
285+
},
286+
"git": {"recent_commits": [], "changed_files_recent": []},
287+
"code_soup_audit": {"overall_risk": "Low", "signals": []},
288+
}
289+
for phrase in BOOTSTRAP_PLACEHOLDER_PHRASES:
290+
plan = build_bootstrap_fill_plan(
291+
roadmap_text=f"# Test\n\n{phrase}\n",
292+
evidence=minimal_evidence,
293+
)
294+
tasks = plan.get("tasks") or []
295+
if not tasks:
296+
failures.append(f"bootstrap fill plan missing task for phrase: {phrase[:60]}")
297+
continue
298+
task = tasks[0]
299+
if (task.get("suggested_replacement") or "") == phrase:
300+
failures.append(f"bootstrap phrase unmapped (same text): {phrase[:60]}")
301+
if str(task.get("evidence_source") or "").startswith("manual"):
302+
failures.append(f"bootstrap phrase manual-only: {phrase[:60]}")
303+
304+
import json as _json
305+
from plugins.dietcode.lib.agent.roadmap.native_bridge import merge_roadmap_hint_into_result, roadmap_write_hint
306+
307+
write_hint = roadmap_write_hint(
308+
tool_name="write_file",
309+
args={"path": "ROADMAP.md"},
310+
workspace=str(root),
311+
)
312+
merged = _json.loads(merge_roadmap_hint_into_result({"ok": True}, write_hint))
313+
if not merged.get("project_steering_digest"):
314+
failures.append("merge_roadmap_hint missing top-level project_steering_digest")
315+
if write_hint.get("bootstrap_incomplete") and not merged.get("agent_next_call"):
316+
failures.append("merge_roadmap_hint missing agent_next_call when bootstrap incomplete")
273317

274318
from plugins.dietcode.lib.agent.roadmap.agent_steering import format_agent_steering_line
275319

@@ -334,8 +378,12 @@ def main() -> int:
334378
failures.append("template brief missing bootstrap_autofill_preview")
335379
if not (tmpl.get("project_steering_digest") or {}).get("steering_brief"):
336380
failures.append("template brief missing steering digest")
337-
if "apply_bootstrap_fill" not in str((tmpl.get("bootstrap_fill_plan") or {}).get("agent_next_call") or ""):
338-
failures.append("template brief fill plan should recommend apply_bootstrap_fill")
381+
fill_plan = tmpl.get("bootstrap_fill_plan") or {}
382+
if fill_plan.get("tasks"):
383+
if "apply_bootstrap_fill" not in str(fill_plan.get("agent_next_call") or ""):
384+
failures.append("template brief fill plan should recommend apply_bootstrap_fill")
385+
elif not fill_plan.get("bootstrap_complete"):
386+
failures.append("template brief should report bootstrap_complete when skeleton has no remaining phrases")
339387

340388
record_validation(str(root), valid=True, phase="checkpoint")
341389
record_file_mutation(str(root), tool="write_file", path="ROADMAP.md")

0 commit comments

Comments
 (0)