@event4u/agent-config 5.6.1 → 5.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (225) hide show
  1. package/.agent-src/commands/agent-handoff.md +1 -1
  2. package/.agent-src/commands/agent-status.md +1 -1
  3. package/.agent-src/commands/agents/audit.md +1 -1
  4. package/.agent-src/commands/agents/init.md +1 -1
  5. package/.agent-src/commands/agents/user/accept.md +3 -3
  6. package/.agent-src/commands/agents/user/init.md +4 -4
  7. package/.agent-src/commands/agents/user/show.md +3 -3
  8. package/.agent-src/commands/agents/user/update.md +3 -3
  9. package/.agent-src/commands/agents/user.md +1 -1
  10. package/.agent-src/commands/agents.md +1 -1
  11. package/.agent-src/commands/analytics/prune.md +1 -1
  12. package/.agent-src/commands/analytics/show.md +1 -1
  13. package/.agent-src/commands/analytics.md +1 -1
  14. package/.agent-src/commands/bug-fix.md +1 -1
  15. package/.agent-src/commands/challenge-me.md +1 -1
  16. package/.agent-src/commands/chat-history/import.md +1 -1
  17. package/.agent-src/commands/chat-history/learn.md +1 -1
  18. package/.agent-src/commands/chat-history/show.md +1 -1
  19. package/.agent-src/commands/chat-history.md +1 -1
  20. package/.agent-src/commands/check-current-md.md +1 -1
  21. package/.agent-src/commands/condense.md +1 -1
  22. package/.agent-src/commands/context.md +1 -1
  23. package/.agent-src/commands/cost-report.md +13 -8
  24. package/.agent-src/commands/council.md +3 -3
  25. package/.agent-src/commands/create-pr/description-only.md +1 -1
  26. package/.agent-src/commands/create-pr.md +1 -1
  27. package/.agent-src/commands/e2e-heal.md +1 -1
  28. package/.agent-src/commands/e2e-plan.md +1 -1
  29. package/.agent-src/commands/feature.md +1 -1
  30. package/.agent-src/commands/fix/ci.md +1 -1
  31. package/.agent-src/commands/fix/portability.md +1 -1
  32. package/.agent-src/commands/fix/pr-bot-comments.md +1 -1
  33. package/.agent-src/commands/fix/pr-comments.md +1 -1
  34. package/.agent-src/commands/fix/pr-developer-comments.md +1 -1
  35. package/.agent-src/commands/fix/refs.md +1 -1
  36. package/.agent-src/commands/fix/seeder.md +1 -1
  37. package/.agent-src/commands/fix.md +1 -1
  38. package/.agent-src/commands/judge.md +1 -1
  39. package/.agent-src/commands/knowledge/cross-repo.md +1 -1
  40. package/.agent-src/commands/knowledge/forget.md +1 -1
  41. package/.agent-src/commands/knowledge/ingest.md +1 -1
  42. package/.agent-src/commands/knowledge/list.md +1 -1
  43. package/.agent-src/commands/knowledge.md +1 -1
  44. package/.agent-src/commands/memory/add.md +1 -1
  45. package/.agent-src/commands/memory/learn-low-impact.md +1 -1
  46. package/.agent-src/commands/memory/load.md +1 -1
  47. package/.agent-src/commands/memory/mine-session.md +1 -1
  48. package/.agent-src/commands/memory/promote.md +1 -1
  49. package/.agent-src/commands/memory/propose.md +1 -1
  50. package/.agent-src/commands/memory.md +1 -1
  51. package/.agent-src/commands/mode.md +1 -1
  52. package/.agent-src/commands/optimize/agents-dir.md +1 -1
  53. package/.agent-src/commands/optimize/augmentignore.md +1 -1
  54. package/.agent-src/commands/optimize/rtk.md +1 -1
  55. package/.agent-src/commands/optimize/skills.md +1 -1
  56. package/.agent-src/commands/optimize.md +1 -1
  57. package/.agent-src/commands/orchestrate.md +1 -1
  58. package/.agent-src/commands/override/create.md +1 -1
  59. package/.agent-src/commands/override/manage.md +1 -1
  60. package/.agent-src/commands/override.md +1 -1
  61. package/.agent-src/commands/package-reset.md +1 -1
  62. package/.agent-src/commands/prediction-pool.md +234 -0
  63. package/.agent-src/commands/profile/activate.md +81 -0
  64. package/.agent-src/commands/profile/deactivate.md +68 -0
  65. package/.agent-src/commands/profile/show.md +70 -0
  66. package/.agent-src/commands/profile.md +68 -0
  67. package/.agent-src/commands/project-health.md +1 -1
  68. package/.agent-src/commands/quality-fix.md +1 -1
  69. package/.agent-src/commands/roadmap/process-full.md +1 -1
  70. package/.agent-src/commands/roadmap/process-phase.md +1 -1
  71. package/.agent-src/commands/roadmap/process-step.md +1 -1
  72. package/.agent-src/commands/roadmap.md +1 -1
  73. package/.agent-src/commands/set-cost-profile.md +9 -9
  74. package/.agent-src/commands/skill/preview.md +3 -3
  75. package/.agent-src/commands/skill.md +1 -1
  76. package/.agent-src/commands/skills/discover.md +1 -1
  77. package/.agent-src/commands/skills.md +1 -1
  78. package/.agent-src/commands/sync-agent-settings.md +3 -3
  79. package/.agent-src/commands/sync-gitignore/fix.md +1 -1
  80. package/.agent-src/commands/sync-gitignore.md +1 -1
  81. package/.agent-src/commands/update-form-request-messages.md +1 -1
  82. package/.agent-src/presets/README.md +1 -1
  83. package/.agent-src/profiles/README.md +1 -1
  84. package/.agent-src/rules/non-destructive-by-default.md +2 -1
  85. package/.agent-src/skills/check-refs/SKILL.md +1 -1
  86. package/.agent-src/skills/finishing-a-development-branch/SKILL.md +1 -1
  87. package/.agent-src/skills/git-workflow/SKILL.md +1 -1
  88. package/.agent-src/skills/jira-integration/SKILL.md +1 -1
  89. package/.agent-src/skills/markitdown/SKILL.md +1 -1
  90. package/.agent-src/skills/prediction-pool-optimizer/SKILL.md +314 -0
  91. package/.agent-src/skills/prediction-pool-optimizer/evals/triggers.json +20 -0
  92. package/.agent-src/skills/prediction-pool-optimizer/reference/ev-fixtures.md +175 -0
  93. package/.agent-src/skills/prediction-pool-optimizer/reference/odds-and-bonus.md +109 -0
  94. package/.agent-src/skills/rtk-output-filtering/SKILL.md +1 -1
  95. package/.agent-src/skills/script-writing/SKILL.md +1 -1
  96. package/.agent-src/skills/token-optimizer/SKILL.md +1 -1
  97. package/.agent-src/skills/using-git-worktrees/SKILL.md +1 -1
  98. package/.agent-src/templates/agent-settings.md +7 -7
  99. package/.agent-src/templates/agents/agent-project-settings.example.yml +2 -2
  100. package/.agent-src/templates/scripts/work_engine/_lib/agent_settings.py +54 -6
  101. package/.agent-src/templates/scripts/work_engine/hook_bootstrap.py +1 -1
  102. package/.agent-src/templates/scripts/work_engine/hooks/builtin/memory_visibility.py +9 -7
  103. package/.agent-src/templates/scripts/work_engine/hooks/settings.py +9 -10
  104. package/.agent-src/templates/scripts/work_engine/scoring/memory_visibility.py +17 -4
  105. package/.claude-plugin/marketplace.json +370 -364
  106. package/CHANGELOG.md +108 -0
  107. package/README.md +2 -2
  108. package/config/agent-settings.template.yml +11 -2
  109. package/config/discovery/packs.yml +11 -0
  110. package/config/discovery/session-profiles.yml +37 -0
  111. package/config/discovery/workspaces.yml +1 -1
  112. package/config/profiles/balanced.ini +1 -1
  113. package/config/profiles/full.ini +1 -1
  114. package/config/profiles/minimal.ini +1 -1
  115. package/dist/discovery/deprecation-report.md +1 -1
  116. package/dist/discovery/discovery-manifest.json +254 -100
  117. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  118. package/dist/discovery/discovery-manifest.summary.md +4 -3
  119. package/dist/discovery/orphan-report.md +1 -1
  120. package/dist/discovery/packs.json +41 -6
  121. package/dist/discovery/trust-report.md +3 -3
  122. package/dist/discovery/workspaces.json +19 -6
  123. package/dist/mcp/registry-manifest.json +3 -3
  124. package/dist/server/io/substituteTemplate.js +3 -3
  125. package/dist/server/io/substituteTemplate.js.map +1 -1
  126. package/dist/server/routes/settings.js +2 -2
  127. package/dist/server/routes/settings.js.map +1 -1
  128. package/dist/server/schemas/settings.js +4 -2
  129. package/dist/server/schemas/settings.js.map +1 -1
  130. package/dist/ui/assets/{index-DVsyUMZe.js → index-5lFqAKL0.js} +2 -2
  131. package/dist/ui/assets/index-5lFqAKL0.js.map +1 -0
  132. package/dist/ui/index.html +1 -1
  133. package/docs/architecture/current-onboard-baseline.md +3 -3
  134. package/docs/architecture.md +2 -2
  135. package/docs/catalog.md +11 -5
  136. package/docs/contracts/adr-level-6-productization.md +1 -1
  137. package/docs/contracts/command-clusters.md +2 -0
  138. package/docs/contracts/config-presets.md +2 -2
  139. package/docs/contracts/cost-profile-defaults.md +5 -5
  140. package/docs/contracts/discovery-manifest.schema.json +1 -1
  141. package/docs/contracts/explain-trace.schema.json +3 -3
  142. package/docs/contracts/memory-visibility-v1.md +15 -7
  143. package/docs/contracts/profile-system.md +2 -2
  144. package/docs/contracts/session-profile-overlay.md +120 -0
  145. package/docs/contracts/settings-api.md +3 -3
  146. package/docs/contracts/value-report-schema.md +14 -1
  147. package/docs/customization.md +47 -5
  148. package/docs/decisions/ADR-010-profile-pack-preset-boundary.md +47 -11
  149. package/docs/decisions/ADR-013-discovery-frontmatter-contract.md +16 -2
  150. package/docs/decisions/ADR-034-per-skill-model-recommendation-transport.md +1 -1
  151. package/docs/decisions/ADR-036-global-install-browser-wizard-handoff.md +106 -0
  152. package/docs/decisions/ADR-037-cost-profile-untangle.md +117 -0
  153. package/docs/decisions/ADR-038-canonical-settings-path.md +66 -0
  154. package/docs/decisions/ADR-039-claude-skills-untracked.md +139 -0
  155. package/docs/decisions/ADR-rule-kernel-and-router.md +1 -1
  156. package/docs/decisions/INDEX.md +4 -0
  157. package/docs/development.md +12 -0
  158. package/docs/getting-started.md +2 -2
  159. package/docs/guidelines/agent-infra/layered-settings.md +10 -4
  160. package/docs/installation.md +3 -3
  161. package/docs/setup/mcp-client-config.md +1 -1
  162. package/docs/skills-catalog.md +5 -1
  163. package/docs/value.md +9 -7
  164. package/docs/wizard.md +1 -1
  165. package/llms.txt +4 -0
  166. package/package.json +1 -1
  167. package/scripts/__pycache__/validate_frontmatter.cpython-312.pyc +0 -0
  168. package/scripts/_cli/cmd_doctor.py +3 -2
  169. package/scripts/_cli/cmd_explain.py +1 -1
  170. package/scripts/_cli/cmd_versions.py +2 -2
  171. package/scripts/_cli/explain_last/inputs.py +11 -8
  172. package/scripts/_cli/explain_last/sections/inputs.py +1 -1
  173. package/scripts/_lib/__pycache__/__init__.cpython-312.pyc +0 -0
  174. package/scripts/_lib/__pycache__/agent_src.cpython-312.pyc +0 -0
  175. package/scripts/_lib/agent_settings.py +54 -6
  176. package/scripts/_lib/agent_src.py +30 -0
  177. package/scripts/_lib/value_ladder.py +99 -2
  178. package/scripts/_lib/value_report.py +30 -16
  179. package/scripts/ai_council/modes.py +1 -1
  180. package/scripts/ai_council/session.py +5 -1
  181. package/scripts/audit_command_surface.py +7 -1
  182. package/scripts/audit_initial_context.py +26 -2
  183. package/scripts/check_gate_paths.py +117 -0
  184. package/scripts/check_references.py +51 -2
  185. package/scripts/check_skill_requires.py +143 -0
  186. package/scripts/check_test_coverage_diff.py +180 -0
  187. package/scripts/compile_router.py +5 -1
  188. package/scripts/condense.py +92 -4
  189. package/scripts/config/session_profiles.py +492 -0
  190. package/scripts/council_cli.py +5 -1
  191. package/scripts/first-run.sh +11 -11
  192. package/scripts/hook_manifest.yaml +15 -7
  193. package/scripts/hooks/dispatch_hook.py +8 -0
  194. package/scripts/install +14 -1
  195. package/scripts/install-hooks.sh +2 -1
  196. package/scripts/install.py +203 -433
  197. package/scripts/install_anthropic_key.sh +1 -1
  198. package/scripts/install_openai_key.sh +1 -1
  199. package/scripts/inventory_abstraction_budget.py +6 -1
  200. package/scripts/lint_agents_md.py +11 -4
  201. package/scripts/lint_discovery_vocabulary.py +5 -5
  202. package/scripts/lint_hook_concern_budget.py +5 -1
  203. package/scripts/lint_marketplace.py +18 -7
  204. package/scripts/lint_roadmap_ci_steps.py +5 -1
  205. package/scripts/lint_roadmap_complexity.py +5 -1
  206. package/scripts/lint_value_dashboard.py +1 -1
  207. package/scripts/mcp_server/prompts.py +5 -1
  208. package/scripts/prediction-pool/adapters/_schema.md +42 -0
  209. package/scripts/prediction-pool/adapters/kicktipp.yml +23 -0
  210. package/scripts/prediction-pool/poisson_sim.py +167 -0
  211. package/scripts/prediction-pool/pool_winsim.py +236 -0
  212. package/scripts/prediction-pool/score_ev.py +188 -0
  213. package/scripts/profile_staleness_hook.py +69 -0
  214. package/scripts/render_value_md.py +1 -0
  215. package/scripts/roadmap_progress_hook.py +56 -6
  216. package/scripts/schemas/agent-settings.schema.json +77 -0
  217. package/scripts/schemas/skill.schema.json +7 -0
  218. package/scripts/smoke_quickstart.py +7 -6
  219. package/scripts/sync_agent_settings.py +12 -5
  220. package/scripts/validate_agent_settings.py +124 -0
  221. package/scripts/validate_decision_engine.py +5 -1
  222. package/templates/minimal/.agent-settings.yml +1 -1
  223. package/dist/ui/assets/index-DVsyUMZe.js.map +0 -1
  224. package/scripts/measure_roadmap_trajectory.py +0 -112
  225. package/scripts/verify_roadmap_closure.py +0 -327
@@ -1,112 +0,0 @@
1
- #!/usr/bin/env python3
2
- """Phase 5.1 — Roadmap commitment-history measurement.
3
-
4
- Walks `agents/roadmaps/archive/` and computes per-roadmap checkbox
5
- completion ratio at archival time. Output: one-line trajectory metric
6
- per roadmap, plus an aggregate `agents/runtime/reports/roadmap-trajectory.json`.
7
-
8
- Checkbox grammar (mirrors `scripts/roadmap_progress_check.py`):
9
- - `[ ]` — open
10
- - `[x]` — done
11
- - `[~]` — in-progress
12
- - `[-]` — cancelled / dropped (counts neither toward open nor closed)
13
-
14
- Trajectory metric = closed / (open + closed + in-progress); cancelled
15
- items are excluded from the denominator so a cleanly archived "we
16
- decided not to do this" doesn't dilute the score.
17
- """
18
-
19
- from __future__ import annotations
20
-
21
- import argparse
22
- import json
23
- import re
24
- import sys
25
- from pathlib import Path
26
-
27
- ROOT = Path(__file__).resolve().parent.parent
28
- ARCHIVE = ROOT / "agents" / "roadmaps" / "archive"
29
- REPORT = ROOT / "agents" / "reports" / "roadmap-trajectory.json"
30
-
31
- CHECKBOX = re.compile(r"^\s*[-*]\s*\[(?P<state>[ x~\-])\]", re.MULTILINE)
32
-
33
-
34
- def measure(path: Path) -> dict:
35
- text = path.read_text(encoding="utf-8", errors="replace")
36
- counts = {"open": 0, "done": 0, "wip": 0, "cancelled": 0}
37
- for m in CHECKBOX.finditer(text):
38
- state = m.group("state")
39
- if state == " ":
40
- counts["open"] += 1
41
- elif state == "x":
42
- counts["done"] += 1
43
- elif state == "~":
44
- counts["wip"] += 1
45
- elif state == "-":
46
- counts["cancelled"] += 1
47
- denom = counts["open"] + counts["done"] + counts["wip"]
48
- ratio = (counts["done"] / denom) if denom else None
49
- return {
50
- "file": str(path.relative_to(ROOT)),
51
- "counts": counts,
52
- "completion_ratio": ratio,
53
- "total_actionable": denom,
54
- }
55
-
56
-
57
- def main() -> int:
58
- ap = argparse.ArgumentParser()
59
- ap.add_argument("--archive", default=str(ARCHIVE))
60
- ap.add_argument("--report", default=str(REPORT))
61
- ap.add_argument("--print-table", action="store_true")
62
- args = ap.parse_args()
63
-
64
- archive = Path(args.archive)
65
- if not archive.exists():
66
- print(f"❌ archive not found: {archive}", file=sys.stderr)
67
- return 2
68
-
69
- rows = [measure(p) for p in sorted(archive.glob("*.md"))]
70
-
71
- # Aggregate: mean, median, count above 80%, count zero-completion
72
- ratios = [r["completion_ratio"] for r in rows if r["completion_ratio"] is not None]
73
- aggregate = {
74
- "roadmaps": len(rows),
75
- "scored": len(ratios),
76
- "mean": (sum(ratios) / len(ratios)) if ratios else None,
77
- "median": sorted(ratios)[len(ratios) // 2] if ratios else None,
78
- "above_80pct": sum(1 for r in ratios if r >= 0.80),
79
- "below_50pct": sum(1 for r in ratios if r < 0.50),
80
- "zero_completion": sum(1 for r in ratios if r == 0.0),
81
- }
82
-
83
- out = Path(args.report)
84
- out.parent.mkdir(parents=True, exist_ok=True)
85
- out.write_text(
86
- json.dumps({"aggregate": aggregate, "rows": rows}, indent=2) + "\n",
87
- encoding="utf-8",
88
- )
89
-
90
- print(f"✅ Wrote {out.relative_to(ROOT)}")
91
- print(f" roadmaps={aggregate['roadmaps']} scored={aggregate['scored']}")
92
- if aggregate["mean"] is not None:
93
- print(
94
- f" mean={aggregate['mean']:.1%} median={aggregate['median']:.1%} "
95
- f"above_80%={aggregate['above_80pct']} below_50%={aggregate['below_50pct']} "
96
- f"zero={aggregate['zero_completion']}"
97
- )
98
- if args.print_table:
99
- print()
100
- print(f" {'file':70s} {'ratio':>7s} {'done':>5s} {'open':>5s} {'wip':>5s} {'cx':>5s}")
101
- for r in sorted(rows, key=lambda x: (x["completion_ratio"] is None, -(x["completion_ratio"] or 0))):
102
- ratio = "—" if r["completion_ratio"] is None else f"{r['completion_ratio']:.1%}"
103
- print(
104
- f" {Path(r['file']).name:70s} {ratio:>7s} "
105
- f"{r['counts']['done']:>5d} {r['counts']['open']:>5d} "
106
- f"{r['counts']['wip']:>5d} {r['counts']['cancelled']:>5d}"
107
- )
108
- return 0
109
-
110
-
111
- if __name__ == "__main__":
112
- sys.exit(main())
@@ -1,327 +0,0 @@
1
- #!/usr/bin/env python3
2
- """verify_roadmap_closure — scan archived roadmaps for phantom-shipping.
3
-
4
- For each `agents/roadmaps/archive/*.md` file:
5
-
6
- 1. Locate the closure-decision block (heuristic: `## Closure decision`,
7
- `## Sunset`, `maintainer override`).
8
- 2. Extract file-path-shaped tokens from the block (backtick paths +
9
- markdown link targets). Sibling-roadmap references are filtered out.
10
- 3. Verify each token: exists on disk? If not, was it ever in git history?
11
- 4. Classify the roadmap (verified / partial / phantom / no-claims /
12
- not-closure-marked) and emit a per-roadmap + aggregate report.
13
-
14
- Run: `python3 scripts/verify_roadmap_closure.py [--json out.json]`
15
- """
16
-
17
- from __future__ import annotations
18
-
19
- import argparse
20
- import json
21
- import re
22
- import subprocess
23
- import sys
24
- from dataclasses import dataclass, field
25
- from pathlib import Path
26
- from typing import Iterable
27
-
28
- REPO = Path(__file__).resolve().parent.parent
29
- ARCHIVE = REPO / "agents" / "roadmaps" / "archive"
30
-
31
- CLOSURE_HEADERS = re.compile(
32
- r"^##\s+(closure decision|sunset|cancellation|maintainer override)",
33
- re.IGNORECASE | re.MULTILINE,
34
- )
35
- NEXT_H2 = re.compile(r"^##\s+", re.MULTILINE)
36
-
37
- BACKTICK_TOKEN = re.compile(r"`([^`\n]+?)`")
38
- MD_LINK = re.compile(r"\]\(([^)\s]+?)\)")
39
- TASK_TARGET = re.compile(r"^task\s+([a-z][\w:-]*)$")
40
- SLASH_CMD = re.compile(r"^/([a-z][\w-]*(?::[a-z][\w-]*)?)$")
41
- HEADING_PAT = re.compile(r"^##+\s+(.+)$")
42
-
43
- PATH_HINT = re.compile(
44
- r"^(scripts/|docs/|agents/|templates/|"
45
- r"\.agent-src\.uncondensed/|\.agent-src/|\.augment/|\.claude/|\.cursor/|"
46
- r"taskfiles/|Taskfile)"
47
- )
48
- PATH_SHAPED = re.compile(r"^[\w.-]+/.+|\.[a-z]{1,5}$")
49
- CONCEPT_NAME = re.compile(r"^[a-z][\w-]{2,}$")
50
- PUNCT_ONLY = re.compile(r"^[^A-Za-z0-9]+$")
51
- SKIP_PREFIX = ("http://", "https://", "mailto:", "#")
52
- SKIP_SUFFIX_FRAGMENT = re.compile(r"#.*$")
53
-
54
-
55
- SHIPPED_MARKERS = re.compile(
56
- r"\b(shipped|landed|live|live in|delivered|completed|complete|exists?|in tree|"
57
- r"in place|wired|active|adopted|published|are live|partially shipped)\b",
58
- re.IGNORECASE,
59
- )
60
- DROPPED_MARKERS = re.compile(
61
- r"\b(sunset|sunsetted|dropped|drop\b|cancell?ed|deferred|retracted|phantom|"
62
- r"never materiali[sz]ed|not shipped|does not exist|doesn't exist|missing|"
63
- r"out of scope|deprioriti[sz]ed|out\-of\-scope|won't ship|will not ship)\b",
64
- re.IGNORECASE,
65
- )
66
- BULLET_SPLIT = re.compile(r"^[ \t]*[-*]\s+", re.MULTILINE)
67
-
68
-
69
- def bullet_sentiment(bullet_text: str) -> str:
70
- has_dropped = bool(DROPPED_MARKERS.search(bullet_text))
71
- has_shipped = bool(SHIPPED_MARKERS.search(bullet_text))
72
- if has_dropped and not has_shipped:
73
- return "dropped"
74
- if has_shipped and not has_dropped:
75
- return "shipped"
76
- if has_shipped and has_dropped:
77
- return "mixed"
78
- return "neutral"
79
-
80
-
81
- @dataclass
82
- class Claim:
83
- token: str
84
- kind: str # path | task | md-link | slash-cmd | heading | concept
85
- sentiment: str = "neutral" # shipped | dropped | mixed | neutral
86
- exists: bool = False
87
- ever_in_git: bool = False
88
-
89
-
90
- @dataclass
91
- class Verdict:
92
- roadmap: str
93
- has_closure: bool
94
- block: str = ""
95
- claims: list[Claim] = field(default_factory=list)
96
- classification: str = "no-claims"
97
- phantom_rate: float = 0.0
98
-
99
-
100
- def find_block(text: str) -> str:
101
- m = CLOSURE_HEADERS.search(text)
102
- if not m:
103
- return ""
104
- start = m.start()
105
- end_match = NEXT_H2.search(text, m.end())
106
- end = end_match.start() if end_match else len(text)
107
- return text[start:end]
108
-
109
-
110
- def is_self_roadmap_ref(token: str, roadmap_name: str) -> bool:
111
- base = token.rsplit("/", 1)[-1]
112
- return base.endswith(".md") and (
113
- base.startswith("step-") or base.startswith("road-to-") or base == roadmap_name
114
- )
115
-
116
-
117
- def classify_token(tok: str) -> tuple[str, str] | None:
118
- tok = SKIP_SUFFIX_FRAGMENT.sub("", tok).strip()
119
- if not tok or PUNCT_ONLY.match(tok) or any(tok.startswith(p) for p in SKIP_PREFIX):
120
- return None
121
- m = TASK_TARGET.match(tok)
122
- if m:
123
- return ("task", m.group(1))
124
- m = SLASH_CMD.match(tok)
125
- if m:
126
- return ("slash-cmd", m.group(1))
127
- m = HEADING_PAT.match(tok)
128
- if m:
129
- return ("heading", m.group(1).strip())
130
- if PATH_HINT.match(tok) or "/" in tok or tok.endswith((".md", ".py", ".sh", ".yml", ".json")):
131
- return ("path", tok)
132
- if CONCEPT_NAME.match(tok):
133
- return ("concept", tok)
134
- return None
135
-
136
-
137
- def split_bullets(block: str) -> list[str]:
138
- parts = BULLET_SPLIT.split(block)
139
- return [p.strip() for p in parts[1:] if p.strip()]
140
-
141
-
142
- def _ingest(seen: dict, kind: str, value: str, sentiment: str) -> None:
143
- key = (kind, value)
144
- if key in seen:
145
- existing = seen[key]
146
- if existing.sentiment == "neutral" and sentiment != "neutral":
147
- existing.sentiment = sentiment
148
- elif existing.sentiment != sentiment and sentiment != "neutral":
149
- existing.sentiment = "mixed"
150
- return
151
- seen[key] = Claim(value, kind, sentiment)
152
-
153
-
154
- def extract_claims(block: str, roadmap_name: str) -> list[Claim]:
155
- seen: dict[tuple[str, str], Claim] = {}
156
- bullets = split_bullets(block) or [block]
157
- for bullet in bullets:
158
- sent = bullet_sentiment(bullet)
159
- for m in BACKTICK_TOKEN.finditer(bullet):
160
- cls = classify_token(m.group(1))
161
- if not cls:
162
- continue
163
- kind, value = cls
164
- if kind == "path" and is_self_roadmap_ref(value, roadmap_name):
165
- continue
166
- _ingest(seen, kind, value, sent)
167
- for m in MD_LINK.finditer(bullet):
168
- cls = classify_token(m.group(1))
169
- if not cls:
170
- continue
171
- kind, value = cls
172
- if kind == "path" and is_self_roadmap_ref(value, roadmap_name):
173
- continue
174
- _ingest(seen, "md-link", value, sent)
175
- return list(seen.values())
176
-
177
-
178
- def verify_path(token: str) -> bool:
179
- return (REPO / token).exists()
180
-
181
-
182
- def verify_task(target: str) -> bool:
183
- for tf in [REPO / "Taskfile.yml", *((REPO / "taskfiles").glob("*.yml") if (REPO / "taskfiles").exists() else [])]:
184
- if not tf.exists():
185
- continue
186
- if re.search(rf"^\s+{re.escape(target)}:\s*$", tf.read_text(), re.MULTILINE):
187
- return True
188
- return False
189
-
190
-
191
- def verify_slash_cmd(name: str) -> bool:
192
- base = name.split(":")[0]
193
- candidates = [
194
- REPO / ".agent-src.uncondensed" / "commands" / f"{base}.md",
195
- REPO / ".agent-src.uncondensed" / "commands" / base,
196
- REPO / ".agent-src.uncondensed" / "skills" / base,
197
- REPO / ".claude" / "skills" / base,
198
- ]
199
- return any(c.exists() for c in candidates)
200
-
201
-
202
- def verify_heading(heading: str) -> bool:
203
- # Look for the heading text in skills/rules/contexts as evidence of pattern adoption
204
- pattern = re.compile(rf"^##+\s+{re.escape(heading)}\b", re.MULTILINE)
205
- for root in (REPO / ".agent-src.uncondensed", REPO / "agents", REPO / "docs"):
206
- if not root.exists():
207
- continue
208
- for f in root.rglob("*.md"):
209
- try:
210
- if pattern.search(f.read_text(errors="ignore")):
211
- return True
212
- except Exception:
213
- continue
214
- return False
215
-
216
-
217
- def verify_concept(name: str) -> bool:
218
- # grep across source-of-truth tree for any literal mention as evidence
219
- try:
220
- r = subprocess.run(
221
- ["git", "grep", "-l", "-w", name, "--",
222
- ".agent-src.uncondensed/", "docs/", "scripts/", "agents/settings/contexts/"],
223
- cwd=REPO, capture_output=True, text=True, timeout=15,
224
- )
225
- return bool(r.stdout.strip())
226
- except Exception:
227
- return False
228
-
229
-
230
- def git_history(token: str) -> bool:
231
- try:
232
- r = subprocess.run(
233
- ["git", "log", "--all", "--oneline", "-n", "1", "--", token],
234
- cwd=REPO, capture_output=True, text=True, timeout=10,
235
- )
236
- return bool(r.stdout.strip())
237
- except Exception:
238
- return False
239
-
240
-
241
- def classify(claims: list[Claim]) -> tuple[str, float]:
242
- # Phantom rate is computed only over claims the closure block *asserts as
243
- # shipped*. Claims explicitly marked as dropped/sunset are excluded —
244
- # missing them is consistent with the rationale, not a phantom.
245
- shipped = [c for c in claims if c.sentiment in ("shipped", "mixed")]
246
- if not shipped:
247
- if not claims:
248
- return "no-claims", 0.0
249
- return "no-shipped-claims", 0.0
250
- missing = [c for c in shipped if not c.exists]
251
- rate = len(missing) / len(shipped)
252
- if rate == 0:
253
- return "verified", 0.0
254
- if rate >= 0.5:
255
- return "phantom", rate
256
- return "partial-phantom", rate
257
-
258
-
259
- def audit(roadmap: Path) -> Verdict:
260
- text = roadmap.read_text()
261
- block = find_block(text)
262
- if not block:
263
- return Verdict(roadmap.name, has_closure=False)
264
- claims = extract_claims(block, roadmap.name)
265
- for c in claims:
266
- if c.kind == "task":
267
- c.exists = verify_task(c.token)
268
- elif c.kind == "slash-cmd":
269
- c.exists = verify_slash_cmd(c.token)
270
- elif c.kind == "heading":
271
- c.exists = verify_heading(c.token)
272
- elif c.kind == "concept":
273
- c.exists = verify_concept(c.token)
274
- else:
275
- c.exists = verify_path(c.token)
276
- if not c.exists and c.kind in ("path", "md-link"):
277
- c.ever_in_git = git_history(c.token)
278
- cls, rate = classify(claims)
279
- return Verdict(roadmap.name, True, block.strip()[:200], claims, cls, rate)
280
-
281
-
282
- def main(argv: Iterable[str]) -> int:
283
- ap = argparse.ArgumentParser()
284
- ap.add_argument("--json", type=Path)
285
- ap.add_argument("--only", help="filter by roadmap name substring")
286
- args = ap.parse_args(list(argv))
287
-
288
- verdicts = []
289
- for md in sorted(ARCHIVE.glob("*.md")):
290
- if args.only and args.only not in md.name:
291
- continue
292
- verdicts.append(audit(md))
293
-
294
- closure_set = [v for v in verdicts if v.has_closure]
295
- by_cls: dict[str, list[Verdict]] = {}
296
- for v in closure_set:
297
- by_cls.setdefault(v.classification, []).append(v)
298
-
299
- print(f"# Archive Closure-Verification Report\n")
300
- print(f"- archive total: {len(verdicts)}")
301
- print(f"- with closure block: {len(closure_set)}")
302
- for cls in ("phantom", "partial-phantom", "verified", "no-shipped-claims", "no-claims"):
303
- print(f"- {cls}: {len(by_cls.get(cls, []))}")
304
- print()
305
-
306
- for cls in ("phantom", "partial-phantom"):
307
- rows = by_cls.get(cls, [])
308
- if not rows:
309
- continue
310
- print(f"## {cls.upper()} ({len(rows)})\n")
311
- for v in sorted(rows, key=lambda x: -x.phantom_rate):
312
- print(f"### {v.roadmap} · phantom-rate {v.phantom_rate:.0%} (shipped-claim basis)")
313
- for c in v.claims:
314
- mark = "✅" if c.exists else "❌"
315
- git = " (git: ever-existed)" if (not c.exists and c.ever_in_git) else ""
316
- sentinel = {"shipped": "[SHIP]", "dropped": "[DROP]", "mixed": "[MIX]", "neutral": "[--]"}.get(c.sentiment, "[?]")
317
- print(f" {mark} {sentinel} [{c.kind}] `{c.token}`{git}")
318
- print()
319
-
320
- if args.json:
321
- args.json.write_text(json.dumps([v.__dict__ for v in verdicts], default=lambda o: o.__dict__, indent=2))
322
- print(f"\n→ JSON written to {args.json}", file=sys.stderr)
323
- return 0
324
-
325
-
326
- if __name__ == "__main__":
327
- sys.exit(main(sys.argv[1:]))