Skip to content

Commit c0d6ed0

Browse files
committed
Another deep audit and production-hardening pass is complete. 104 tests, smoke, operator smoke, and production audit all pass.
Always-on per-project digest The biggest change: project_steering_digest is now attached on every steering surface, not only when bootstrap placeholders remain. New attach_steering_digest_fields() builds a compact identity card from project_fingerprint whenever a workspace resolves. attach_bootstrap_steering_fields() now: Returns fill plan + preview + digest when bootstrap is incomplete Returns digest only when bootstrap is complete (no empty {}) Wired through: Surface Behavior enrich_payload_with_steering Digest on all tool payloads; fill plan when bootstrap incomplete session_brief Digest always; fill plan when needed build_progress_snapshot Digest always build_live_steering_brief Always via unified attach validate_roadmap Digest when schema passes and bootstrap complete explain_gate Digest always; bootstrap suffix only when placeholders remain Agents get per-project identity on guide, validate, progress, session start, and explain-gate — not just during bootstrap fill. Fingerprint — CI & hooks (industry standard) Signal Source ci_workflow_names .github/workflows/*.yml / *.yaml stems has_pre_commit .pre-commit-config.yaml Cache invalidation now includes workflow file mtimes so CI changes refresh the fingerprint. Digest and live steering lines include Workflows and Hooks: pre-commit configured when present. Production cleanup Removed “manual edits” language from autofill operator summary Progress telemetry (roadmap_hooks) records steering_brief, project_archetype, and verification_commands on tool events Audit expansion Verifies: validate always includes project_steering_digest progress and explain_gate always include digest with steering_brief GitHub workflow name detection (ci from .github/workflows/ci.yml) Architecture project_fingerprint (always) → project_steering_digest (always on every surface) → bootstrap_fill_plan (only when placeholders remain) → apply_bootstrap_fill → validate → recommended_next_action Per-project steering is now a persistent identity layer across the full roadmap lifecycle, with bootstrap autofill as a targeted phase rather than the only time project context appears.
1 parent 5cf0302 commit c0d6ed0

11 files changed

Lines changed: 154 additions & 11 deletions

lib/agent/roadmap/agent_steering.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,13 @@ def _project_context_lines(steering: dict[str, Any]) -> list[str]:
6060
if ci:
6161
lines.append(f"CI: {', '.join(ci[:2])}")
6262

63+
workflows = steering.get("ci_workflow_names") or []
64+
if workflows:
65+
lines.append(f"Workflows: {', '.join(workflows[:3])}")
66+
67+
if steering.get("has_pre_commit"):
68+
lines.append("Hooks: pre-commit configured")
69+
6370
git_remote = steering.get("git_remote")
6471
if git_remote:
6572
lines.append(f"Origin: {git_remote}")
@@ -185,8 +192,7 @@ def build_live_steering_brief(*, workspace: Optional[str] = None) -> dict[str, A
185192
"steering_line": format_agent_steering_line(workspace=steering.get("workspace") or workspace) or None,
186193
**steering,
187194
}
188-
if steering.get("bootstrap_complete") is False:
189-
from plugins.dietcode.lib.agent.roadmap.bootstrap_fill import attach_bootstrap_steering_fields
195+
from plugins.dietcode.lib.agent.roadmap.bootstrap_fill import attach_bootstrap_steering_fields
190196

191-
out.update(attach_bootstrap_steering_fields(steering, tier="light"))
197+
out.update(attach_bootstrap_steering_fields(steering, tier="light"))
192198
return out

lib/agent/roadmap/bootstrap_fill.py

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from __future__ import annotations
33

44
import re
5+
from pathlib import Path
56
from typing import Any, Optional
67

78
from plugins.dietcode.lib.agent.roadmap.schema import find_bootstrap_placeholders
@@ -493,7 +494,7 @@ def apply_bootstrap_fill_draft(
493494
"operator_summary": (
494495
f"Applied {len(applied)} evidence-backed replacement(s); {len(remaining)} template phrase(s) remain."
495496
if applied
496-
else f"No autofill applied — {len(remaining)} template phrase(s) remain — roadmap(action='apply_bootstrap_fill') or manual edits from bootstrap_fill_plan.tasks."
497+
else f"No autofill applied — {len(remaining)} template phrase(s) remain — roadmap(action='apply_bootstrap_fill') or bootstrap_fill_plan.tasks."
497498
),
498499
}
499500

@@ -526,6 +527,8 @@ def build_project_steering_digest(
526527
"dependency_automation": fingerprint.get("dependency_automation"),
527528
"has_backstage_catalog": fingerprint.get("has_backstage_catalog"),
528529
"catalog_name": fingerprint.get("catalog_name"),
530+
"ci_workflow_names": fingerprint.get("ci_workflow_names") or [],
531+
"has_pre_commit": fingerprint.get("has_pre_commit"),
529532
}
530533
if fill_plan:
531534
digest["bootstrap_remaining"] = fill_plan.get("remaining_count")
@@ -564,15 +567,50 @@ def bootstrap_steering_bundle(
564567
return out
565568

566569

570+
def attach_steering_digest_fields(steering: dict[str, Any]) -> dict[str, Any]:
571+
"""Attach compact per-project digest whenever a workspace is resolved."""
572+
root = steering.get("workspace")
573+
if not root:
574+
return {}
575+
fp: dict[str, Any] = {}
576+
try:
577+
from plugins.dietcode.lib.agent.roadmap.project_fingerprint import build_project_fingerprint
578+
579+
fp = build_project_fingerprint(root)
580+
except Exception:
581+
fp = {
582+
k: steering.get(k)
583+
for k in (
584+
"steering_brief",
585+
"steering_identity",
586+
"project_archetype",
587+
"stack_summary",
588+
"purpose_hint",
589+
"verification_commands",
590+
"entry_points",
591+
"makefile_targets",
592+
"agent_rules_files",
593+
"git_remote",
594+
"docs_roots",
595+
"governance_files",
596+
"workspace_packages",
597+
)
598+
if steering.get(k) is not None
599+
}
600+
if not fp:
601+
fp = {"steering_identity": Path(str(root)).name}
602+
return {"project_steering_digest": build_project_steering_digest(fp)}
603+
604+
567605
def attach_bootstrap_steering_fields(
568606
steering: dict[str, Any],
569607
*,
570608
tier: str = "light",
571609
include_preview: bool = False,
572610
) -> dict[str, Any]:
573-
"""Attach fill plan + digest when bootstrap placeholders remain (shared by session/progress/gate)."""
611+
"""Attach per-project digest; include fill plan when bootstrap placeholders remain."""
574612
if steering.get("bootstrap_complete") is not False:
575-
return {}
613+
return attach_steering_digest_fields(steering)
576614

577615
from pathlib import Path as PathCls
578616

lib/agent/roadmap/explain_gate.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,10 +64,11 @@ def build_explain_gate_payload(*, workspace: Optional[str] = None) -> dict[str,
6464
),
6565
}
6666
report = payload.get("report") or ""
67-
if inputs.get("bootstrap_complete") is False:
68-
from plugins.dietcode.lib.agent.roadmap.bootstrap_fill import attach_bootstrap_steering_fields
67+
from plugins.dietcode.lib.agent.roadmap.bootstrap_fill import attach_bootstrap_steering_fields
6968

70-
payload.update(attach_bootstrap_steering_fields(steering, tier="light"))
69+
attached = attach_bootstrap_steering_fields(steering, tier="light")
70+
payload.update(attached)
71+
if inputs.get("bootstrap_complete") is False:
7172
remaining = (payload.get("project_steering_digest") or {}).get("bootstrap_remaining")
7273
suffix = (
7374
f"\nBootstrap fill: {remaining} template phrase(s) remain — "

lib/agent/roadmap/progress.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,10 @@ def build_progress_snapshot(*, workspace: Optional[str] = None) -> dict[str, Any
190190
from plugins.dietcode.lib.agent.roadmap.bootstrap_fill import attach_bootstrap_steering_fields
191191

192192
payload.update(attach_bootstrap_steering_fields(steering, tier="light"))
193+
else:
194+
from plugins.dietcode.lib.agent.roadmap.bootstrap_fill import attach_steering_digest_fields
195+
196+
payload.update(attach_steering_digest_fields(steering))
193197
return payload
194198

195199

lib/agent/roadmap/project_fingerprint.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,13 +103,27 @@ def _fingerprint_cache_token(root: Path) -> float:
103103
"pnpm-workspace.yaml",
104104
"turbo.json",
105105
"SECURITY.md",
106+
"compose.yml",
107+
".pre-commit-config.yaml",
106108
):
107109
path = root / rel
108110
if path.is_file():
109111
try:
110112
token = max(token, path.stat().st_mtime)
111113
except OSError:
112114
continue
115+
wf_dir = root / ".github" / "workflows"
116+
if wf_dir.is_dir():
117+
for path in wf_dir.glob("*.yml"):
118+
try:
119+
token = max(token, path.stat().st_mtime)
120+
except OSError:
121+
continue
122+
for path in wf_dir.glob("*.yaml"):
123+
try:
124+
token = max(token, path.stat().st_mtime)
125+
except OSError:
126+
continue
113127
return token
114128

115129

@@ -460,6 +474,19 @@ def _compose_services(root: Path) -> list[str]:
460474
return []
461475

462476

477+
def _ci_workflow_names(root: Path) -> list[str]:
478+
wf_dir = root / ".github" / "workflows"
479+
if not wf_dir.is_dir():
480+
return []
481+
names: list[str] = []
482+
for path in sorted(list(wf_dir.glob("*.yml")) + list(wf_dir.glob("*.yaml"))):
483+
if path.stem not in names:
484+
names.append(path.stem)
485+
if len(names) >= 6:
486+
break
487+
return names
488+
489+
463490
def _governance_files(root: Path) -> list[str]:
464491
found: list[str] = []
465492
for rel in ("SECURITY.md", "CODE_OF_CONDUCT.md", ".editorconfig", "CHANGELOG.md"):
@@ -637,6 +664,8 @@ def _build_project_fingerprint(root: Path) -> dict[str, Any]:
637664
compose_services = _compose_services(root)
638665
governance_files = _governance_files(root)
639666
workspace_packages = _workspace_packages(root)
667+
ci_workflow_names = _ci_workflow_names(root)
668+
has_pre_commit = (root / ".pre-commit-config.yaml").is_file()
640669
has_codeowners = (root / ".github" / "CODEOWNERS").is_file() or (root / "CODEOWNERS").is_file()
641670
verification_commands = _verification_commands(
642671
root,
@@ -719,6 +748,8 @@ def _build_project_fingerprint(root: Path) -> dict[str, Any]:
719748
"compose_services": compose_services or None,
720749
"governance_files": governance_files or None,
721750
"workspace_packages": workspace_packages or None,
751+
"ci_workflow_names": ci_workflow_names or None,
752+
"has_pre_commit": has_pre_commit,
722753
"has_backstage_catalog": has_backstage,
723754
"catalog_name": catalog_meta.get("catalog_name"),
724755
"catalog_description": catalog_meta.get("catalog_description"),

lib/agent/roadmap/roadmap_checkpoint.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -431,6 +431,11 @@ def validate_roadmap(*, workspace: Optional[str] = None) -> dict[str, Any]:
431431
evidence=evidence,
432432
bootstrap_inc=True,
433433
)
434+
elif validation.valid:
435+
from plugins.dietcode.lib.agent.roadmap.bootstrap_fill import attach_steering_digest_fields
436+
from plugins.dietcode.lib.agent.roadmap.steering_context import build_steering_context
437+
438+
payload.update(attach_steering_digest_fields(build_steering_context(workspace=root)))
434439
return clarity_envelope(payload, phase_info=phase_info)
435440

436441

lib/agent/roadmap/session.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,10 @@ def session_brief(*, workspace: Optional[str] = None) -> dict[str, Any] | None:
8484
from plugins.dietcode.lib.agent.roadmap.bootstrap_fill import attach_bootstrap_steering_fields
8585

8686
result.update(attach_bootstrap_steering_fields(steering, tier="light"))
87+
else:
88+
from plugins.dietcode.lib.agent.roadmap.bootstrap_fill import attach_steering_digest_fields
89+
90+
result.update(attach_steering_digest_fields(steering))
8791
return result
8892
except Exception as exc:
8993
return {"enabled": True, "success": False, "error": str(exc)}

lib/agent/roadmap/steering_context.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,8 @@ def build_steering_context(*, workspace: Optional[str] = None) -> dict[str, Any]
173173
"compose_services",
174174
"governance_files",
175175
"workspace_packages",
176+
"ci_workflow_names",
177+
"has_pre_commit",
176178
"has_backstage_catalog",
177179
"catalog_name",
178180
"catalog_description",
@@ -199,4 +201,8 @@ def enrich_payload_with_steering(
199201
from plugins.dietcode.lib.agent.roadmap.bootstrap_fill import attach_bootstrap_steering_fields
200202

201203
out.update(attach_bootstrap_steering_fields(steering, tier="light"))
204+
elif "project_steering_digest" not in out:
205+
from plugins.dietcode.lib.agent.roadmap.bootstrap_fill import attach_steering_digest_fields
206+
207+
out.update(attach_steering_digest_fields(steering))
202208
return out

lib/runtime/roadmap_hooks.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,9 @@ def _post_tool_call(
215215
"phase": payload.get("phase"),
216216
"valid": payload.get("valid"),
217217
"stale": (parsed.get("checkpoint_freshness") or {}).get("stale"),
218+
"steering_brief": parsed.get("steering_brief"),
219+
"project_archetype": parsed.get("project_archetype"),
220+
"verification_commands": (parsed.get("project_steering_digest") or {}).get("verification_commands"),
218221
},
219222
success=success,
220223
)

scripts/roadmap_audit.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,15 @@ def main() -> int:
222222
fp_sec = build_project_fingerprint(root)
223223
if "SECURITY.md" not in (fp_sec.get("governance_files") or []):
224224
failures.append("fingerprint should detect SECURITY.md governance file")
225+
wf_dir = root / ".github" / "workflows"
226+
wf_dir.mkdir(parents=True, exist_ok=True)
227+
(wf_dir / "ci.yml").write_text("name: ci\n", encoding="utf-8")
228+
from plugins.dietcode.lib.agent.roadmap.project_fingerprint import invalidate_fingerprint_cache
229+
230+
invalidate_fingerprint_cache(root)
231+
fp_ci = build_project_fingerprint(root)
232+
if "ci" not in (fp_ci.get("ci_workflow_names") or []):
233+
failures.append("fingerprint should detect GitHub workflow names")
225234
if fp.get("project_archetype") not in {"project", "library", "application", "web-app", "cli-tool", "hermes-plugin", "monorepo"}:
226235
failures.append(f"unexpected project_archetype: {fp.get('project_archetype')}")
227236
roadmap_text = (root / "ROADMAP.md").read_text(encoding="utf-8")
@@ -359,6 +368,8 @@ def main() -> int:
359368
validated2 = validate_roadmap(workspace=str(root))
360369
if "bootstrap_fill_plan" not in validated2:
361370
failures.append("validate should include bootstrap_fill_plan when placeholders remain")
371+
if not validated2.get("project_steering_digest"):
372+
failures.append("validate missing project_steering_digest")
362373
if "recommended_next_action" not in validated2:
363374
failures.append("validate missing recommended_next_action")
364375
rec = validated2.get("recommended_next_action") or {}
@@ -446,8 +457,11 @@ def main() -> int:
446457
failures.append("explain_gate digest missing sample_fill_task")
447458

448459
prog = build_progress_snapshot(workspace=str(root))
449-
if "project_steering_digest" not in prog:
450-
failures.append("progress missing project_steering_digest when bootstrap incomplete")
460+
if not prog.get("project_steering_digest"):
461+
failures.append("progress missing project_steering_digest")
462+
463+
if not (explain.get("project_steering_digest") or {}).get("steering_brief"):
464+
failures.append("explain_gate missing project_steering_digest steering_brief")
451465

452466
auto_ckpt = checkpoint_brief(workspace=str(root), context="apply autofill write")
453467
if "bootstrap_autofill_applied" not in auto_ckpt:

0 commit comments

Comments
 (0)