@event4u/agent-config 8.8.0 → 8.10.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 (215) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/CHANGELOG.md +139 -263
  3. package/README.md +5 -5
  4. package/dist/agent-src/commands/agent-handoff.md +39 -2
  5. package/dist/agent-src/commands/cost/report.md +13 -3
  6. package/dist/agent-src/commands/research/deep.md +8 -0
  7. package/dist/agent-src/commands/research/report.md +7 -0
  8. package/dist/agent-src/commands/roadmap/create.md +12 -0
  9. package/dist/agent-src/commands/roadmap/process-full.md +31 -5
  10. package/dist/agent-src/commands/tests/create.md +28 -6
  11. package/dist/agent-src/commands/work.md +33 -0
  12. package/dist/agent-src/commands/worktree/cleanup.md +78 -0
  13. package/dist/agent-src/commands/worktree/create.md +62 -0
  14. package/dist/agent-src/commands/worktree/status.md +63 -0
  15. package/dist/agent-src/commands/worktree/verify.md +70 -0
  16. package/dist/agent-src/commands/worktree.md +85 -0
  17. package/dist/agent-src/contexts/communication/rules-auto/think-before-action-mechanics.md +30 -1
  18. package/dist/agent-src/contexts/execution/orchestration-telemetry.md +60 -31
  19. package/dist/agent-src/contexts/execution/roadmap-process-loop.md +37 -0
  20. package/dist/agent-src/contexts/execution/subagent-topologies.md +34 -0
  21. package/dist/agent-src/contexts/execution/verification-mechanics.md +9 -9
  22. package/dist/agent-src/rules/agent-authority.md +1 -1
  23. package/dist/agent-src/rules/communication-through-line.md +1 -1
  24. package/dist/agent-src/rules/delegation-policy.md +5 -0
  25. package/dist/agent-src/rules/design-fidelity.md +40 -0
  26. package/dist/agent-src/rules/direct-answers.md +1 -1
  27. package/dist/agent-src/rules/downstream-changes.md +1 -0
  28. package/dist/agent-src/rules/external-code-graph-interop.md +65 -0
  29. package/dist/agent-src/rules/git-history-discipline.md +18 -0
  30. package/dist/agent-src/rules/lethal-trifecta-guard.md +6 -1
  31. package/dist/agent-src/rules/minimal-safe-diff.md +58 -10
  32. package/dist/agent-src/rules/non-destructive-by-default.md +3 -0
  33. package/dist/agent-src/rules/question-not-instruction.md +76 -0
  34. package/dist/agent-src/rules/senior-engineering-discipline.md +1 -1
  35. package/dist/agent-src/rules/spreadsheet-source-quality.md +61 -0
  36. package/dist/agent-src/rules/think-before-action.md +1 -0
  37. package/dist/agent-src/rules/tool-safety.md +8 -0
  38. package/dist/agent-src/rules/untrusted-input-defense.md +87 -22
  39. package/dist/agent-src/scripts/update_roadmap_progress.ts +2 -1
  40. package/dist/agent-src/skills/accessibility-auditor/SKILL.md +19 -0
  41. package/dist/agent-src/skills/accessibility-auditor/evals/evals.json +15 -0
  42. package/dist/agent-src/skills/ai-code-blindspots/SKILL.md +10 -3
  43. package/dist/agent-src/skills/analysis-skill-router/evals/evals.json +13 -0
  44. package/dist/agent-src/skills/api-testing/SKILL.md +15 -1
  45. package/dist/agent-src/skills/character-consistency/evals/evals.json +16 -0
  46. package/dist/agent-src/skills/code-review/SKILL.md +2 -0
  47. package/dist/agent-src/skills/command-routing/evals/evals.json +14 -0
  48. package/dist/agent-src/skills/command-writing/SKILL.md +53 -40
  49. package/dist/agent-src/skills/dashboard-design/evals/evals.json +16 -0
  50. package/dist/agent-src/skills/dcf-modeling/evals/domain-truth.json +29 -0
  51. package/dist/agent-src/skills/dcf-modeling/evals/evals.json +20 -0
  52. package/dist/agent-src/skills/decision-record/evals/evals.json +20 -0
  53. package/dist/agent-src/skills/deep-reading-analyst/SKILL.md +1 -0
  54. package/dist/agent-src/skills/design-intelligence/SKILL.md +49 -12
  55. package/dist/agent-src/skills/design-intelligence/evals/evals.json +14 -0
  56. package/dist/agent-src/skills/design-review/SKILL.md +35 -15
  57. package/dist/agent-src/skills/design-system-capture/SKILL.md +84 -24
  58. package/dist/agent-src/skills/design-system-capture/evals/evals.json +15 -0
  59. package/dist/agent-src/skills/developer-like-execution/evals/evals.json +16 -0
  60. package/dist/agent-src/skills/doc-coauthoring/SKILL.md +27 -0
  61. package/dist/agent-src/skills/doc-coauthoring/evals/evals.json +16 -0
  62. package/dist/agent-src/skills/editorial-calendar/evals/domain-truth.json +16 -0
  63. package/dist/agent-src/skills/editorial-calendar/evals/evals.json +16 -0
  64. package/dist/agent-src/skills/estimate-ticket/evals/evals.json +16 -0
  65. package/dist/agent-src/skills/existing-ui-audit/SKILL.md +30 -0
  66. package/dist/agent-src/skills/existing-ui-audit/evals/triggers.json +47 -0
  67. package/dist/agent-src/skills/fe-design/SKILL.md +36 -5
  68. package/dist/agent-src/skills/fe-design/evals/triggers.json +47 -0
  69. package/dist/agent-src/skills/forecasting/evals/domain-truth.json +29 -0
  70. package/dist/agent-src/skills/forecasting/evals/evals.json +20 -0
  71. package/dist/agent-src/skills/frontend-render-security/SKILL.md +2 -2
  72. package/dist/agent-src/skills/fundraising-narrative/evals/domain-truth.json +16 -0
  73. package/dist/agent-src/skills/fundraising-narrative/evals/evals.json +16 -0
  74. package/dist/agent-src/skills/html-deck/SKILL.md +27 -0
  75. package/dist/agent-src/skills/iconography/SKILL.md +24 -0
  76. package/dist/agent-src/skills/incident-commander/evals/domain-truth.json +16 -0
  77. package/dist/agent-src/skills/incident-commander/evals/evals.json +20 -0
  78. package/dist/agent-src/skills/launch-readiness/evals/evals.json +21 -0
  79. package/dist/agent-src/skills/logging-monitoring/evals/evals.json +16 -0
  80. package/dist/agent-src/skills/markitdown/SKILL.md +1 -0
  81. package/dist/agent-src/skills/mcp/SKILL.md +24 -0
  82. package/dist/agent-src/skills/mcp-builder/SKILL.md +9 -1
  83. package/dist/agent-src/skills/memory-consolidation/SKILL.md +55 -4
  84. package/dist/agent-src/skills/messaging-architecture/evals/domain-truth.json +16 -0
  85. package/dist/agent-src/skills/messaging-architecture/evals/evals.json +16 -0
  86. package/dist/agent-src/skills/perf-feedback-craft/evals/evals.json +16 -0
  87. package/dist/agent-src/skills/pest-testing/SKILL.md +1 -1
  88. package/dist/agent-src/skills/playwright-testing/SKILL.md +16 -0
  89. package/dist/agent-src/skills/prompt-engineering-patterns/evals/evals.json +16 -0
  90. package/dist/agent-src/skills/refine-prompt/evals/evals.json +16 -0
  91. package/dist/agent-src/skills/refine-ticket/evals/evals.json +16 -0
  92. package/dist/agent-src/skills/release-comms/evals/evals.json +16 -0
  93. package/dist/agent-src/skills/rice-prioritization/evals/evals.json +20 -0
  94. package/dist/agent-src/skills/roadmap-writing/SKILL.md +39 -5
  95. package/dist/agent-src/skills/runway-cognition/evals/domain-truth.json +29 -0
  96. package/dist/agent-src/skills/runway-cognition/evals/evals.json +20 -0
  97. package/dist/agent-src/skills/scenario-modeling/evals/domain-truth.json +29 -0
  98. package/dist/agent-src/skills/scenario-modeling/evals/evals.json +20 -0
  99. package/dist/agent-src/skills/secrets-management/SKILL.md +7 -0
  100. package/dist/agent-src/skills/skill-writing/SKILL.md +12 -3
  101. package/dist/agent-src/skills/spreadsheet-authoring/SKILL.md +98 -0
  102. package/dist/agent-src/skills/spreadsheet-authoring/evals/triggers.json +47 -0
  103. package/dist/agent-src/skills/subagent-orchestration/SKILL.md +27 -33
  104. package/dist/agent-src/skills/supply-chain-intake/SKILL.md +12 -0
  105. package/dist/agent-src/skills/systematic-debugging/evals/evals.json +20 -0
  106. package/dist/agent-src/skills/test-case-discovery/SKILL.md +118 -0
  107. package/dist/agent-src/skills/test-case-discovery/evals/triggers.json +52 -0
  108. package/dist/agent-src/skills/test-driven-development/SKILL.md +32 -6
  109. package/dist/agent-src/skills/test-driven-development/evals/evals.json +16 -0
  110. package/dist/agent-src/skills/testing-anti-patterns/SKILL.md +34 -4
  111. package/dist/agent-src/skills/threat-modeling/evals/evals.json +21 -0
  112. package/dist/agent-src/skills/typography-system/evals/evals.json +14 -0
  113. package/dist/agent-src/skills/ui-component-architect/SKILL.md +9 -0
  114. package/dist/agent-src/skills/unit-economics-modeling/evals/domain-truth.json +40 -0
  115. package/dist/agent-src/skills/unit-economics-modeling/evals/evals.json +24 -0
  116. package/dist/agent-src/skills/verify-completion-evidence/evals/evals.json +16 -0
  117. package/dist/agent-src/skills/vision-articulation/evals/evals.json +16 -0
  118. package/dist/agent-src/skills/voice-and-tone-design/evals/evals.json +16 -0
  119. package/dist/agent-src/skills/worktree-lifecycle/SKILL.md +180 -0
  120. package/dist/agent-src/skills/worktree-lifecycle/evals/triggers.json +52 -0
  121. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  122. package/dist/agent-src/templates/roadmaps.md +36 -0
  123. package/dist/cli/registry.js +3 -0
  124. package/dist/cli/registry.js.map +1 -1
  125. package/dist/discovery/deprecation-report.md +1 -1
  126. package/dist/discovery/discovery-manifest.json +326 -67
  127. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  128. package/dist/discovery/discovery-manifest.summary.md +6 -6
  129. package/dist/discovery/orphan-report.md +1 -1
  130. package/dist/discovery/packs.json +25 -14
  131. package/dist/discovery/trust-report.md +4 -4
  132. package/dist/discovery/workspaces.json +25 -12
  133. package/dist/install/install.mjs +136 -1
  134. package/dist/mcp/registry-manifest.json +3 -3
  135. package/dist/router.json +1 -1
  136. package/dist/scripts/_lib/claude_builtin_names.js +145 -0
  137. package/dist/scripts/_lib/claude_builtin_names.js.map +1 -0
  138. package/dist/scripts/condense.js +14 -0
  139. package/dist/scripts/condense.js.map +1 -1
  140. package/docs/CLAIMS.md +36 -8
  141. package/docs/architecture.md +4 -4
  142. package/docs/archive/CHANGELOG-pre-8.9.0.md +306 -0
  143. package/docs/benchmark.md +113 -0
  144. package/docs/catalog.md +32 -8
  145. package/docs/command-flows.md +6 -1
  146. package/docs/contracts/CHANGELOG-conventions.md +6 -0
  147. package/docs/contracts/command-clusters.md +18 -0
  148. package/docs/contracts/design-artifact-lifecycle.md +93 -0
  149. package/docs/contracts/design-artifact-verification.md +153 -0
  150. package/docs/contracts/injected-block-authenticity-model.md +66 -0
  151. package/docs/contracts/mcp-tool-inventory.md +28 -28
  152. package/docs/contracts/release-pr-gating.md +26 -0
  153. package/docs/contracts/release-sizing.md +85 -0
  154. package/docs/contracts/surface-agent-contracts.md +153 -0
  155. package/docs/decisions/ADR-061-corpus-grounding-layer.md +13 -0
  156. package/docs/decisions/ADR-115-command-cluster-consolidation-phase-4.md +1 -1
  157. package/docs/decisions/ADR-116-memory-tripwire-activation-path.md +9 -0
  158. package/docs/distribution/consumer-matrix.md +96 -0
  159. package/docs/featured-skills.md +2 -2
  160. package/docs/getting-started-by-role.md +1 -1
  161. package/docs/getting-started.md +2 -2
  162. package/docs/governance-advantage.md +1 -1
  163. package/docs/guidelines/agent-infra/asking-and-brevity-examples.md +27 -0
  164. package/docs/guidelines/agent-infra/emphasis-budget.md +45 -0
  165. package/docs/guidelines/agent-infra/simplicity-and-goal-demos.md +151 -0
  166. package/docs/guidelines/agent-infra/tool-description-as-policy.md +35 -0
  167. package/docs/guidelines/design-canon.md +1 -0
  168. package/docs/proof.md +38 -23
  169. package/docs/release-runbook.md +3 -0
  170. package/docs/second-brain-scope.md +69 -26
  171. package/docs/self-review-gate.md +51 -0
  172. package/package.json +1 -1
  173. package/src/agent-src/scripts/update_roadmap_progress.ts +2 -1
  174. package/src/scripts/_dispatch.bash +27 -0
  175. package/src/scripts/_lib/claude_builtin_names.ts +148 -0
  176. package/src/scripts/_lib/file_slicer.ts +117 -0
  177. package/src/scripts/_lib/lexical_index.ts +155 -0
  178. package/src/scripts/_lib/orchestration_record.ts +183 -0
  179. package/src/scripts/_lib/orchestration_savings.ts +114 -9
  180. package/src/scripts/_lib/retrieval_sanitize.ts +71 -0
  181. package/src/scripts/_lib/security_lint.ts +1 -0
  182. package/src/scripts/_lib/stat_index.ts +98 -0
  183. package/src/scripts/benchmark.ts +122 -0
  184. package/src/scripts/build_proof.ts +30 -17
  185. package/src/scripts/check_memory.ts +2 -0
  186. package/src/scripts/check_quality_regression.ts +51 -0
  187. package/src/scripts/check_release_adjacent_health.ts +121 -0
  188. package/src/scripts/check_roadmap_trackable.ts +3 -2
  189. package/src/scripts/check_rule_invariants.ts +275 -0
  190. package/src/scripts/condense.ts +14 -0
  191. package/src/scripts/consumer_matrix.ts +353 -0
  192. package/src/scripts/discovery_graph.ts +0 -0
  193. package/src/scripts/external_sources_denylist.json +4 -1
  194. package/src/scripts/generate_index.ts +6 -2
  195. package/src/scripts/install-hooks.sh +25 -0
  196. package/src/scripts/install.ts +35 -2
  197. package/src/scripts/learning_sidecar.ts +0 -0
  198. package/src/scripts/lint_agent_skill_names.ts +29 -0
  199. package/src/scripts/lint_changelog_rollback.ts +293 -0
  200. package/src/scripts/lint_knowledge_scale.ts +4 -3
  201. package/src/scripts/lint_roadmap_complexity.ts +124 -0
  202. package/src/scripts/lint_versioned_cache.ts +195 -0
  203. package/src/scripts/mcp_server/consumer_tool_catalog.json +2 -1
  204. package/src/scripts/mcp_server/tools.ts +24 -1
  205. package/src/scripts/measure_lexical_ranking.ts +173 -0
  206. package/src/scripts/memory_lookup.ts +0 -0
  207. package/src/scripts/orchestration_record.ts +156 -0
  208. package/src/scripts/orchestration_savings_report.ts +65 -8
  209. package/src/scripts/schemas/skill.schema.json +4 -0
  210. package/src/scripts/score_domain_truth.ts +192 -0
  211. package/src/scripts/second_brain_retrieval.ts +320 -0
  212. package/src/scripts/second_brain_run.ts +341 -0
  213. package/src/scripts/self_review_gate.test.ts +65 -0
  214. package/src/scripts/self_review_gate.ts +272 -0
  215. package/src/scripts/trigger_eval_grandfather.json +223 -224
@@ -0,0 +1,293 @@
1
+ #!/usr/bin/env tsx
2
+ /**
3
+ * CHANGELOG `Rollback:` linter.
4
+ *
5
+ * Normative source: `docs/contracts/release-sizing.md § Rollback path`.
6
+ * Every NEW minor / major section in `CHANGELOG.md` — a heading matching
7
+ * the repo's `## [X.Y.0](compare-link) (date)` shape (patch component 0)
8
+ * — must contain at least one `Rollback:` line anywhere in its section,
9
+ * naming how a consumer undoes the subsystem the release introduced or
10
+ * substantially reworked.
11
+ *
12
+ * Cutoff — decidable, no retro-fail: the gate only fires for section
13
+ * versions **strictly greater** than the current `package.json` version
14
+ * at lint time. Historical sections are grandfathered by construction.
15
+ * `--cutoff X.Y.Z` overrides the cutoff (e.g. the release pipeline can
16
+ * pass the PREVIOUS released version so the section it is about to cut
17
+ * is itself in scope).
18
+ *
19
+ * Shares the heading / era regexes with the drift gate via
20
+ * `src/scripts/_lib/changelog_eras.ts` — no parallel copies.
21
+ *
22
+ * CLI: exit 0 clean, 1 violations (or selftest failure), 2 usage error.
23
+ * `--selftest` proves the rule on inline fixtures: a minor section
24
+ * without `Rollback:` fails; one with it passes.
25
+ */
26
+
27
+ import * as fs from 'node:fs';
28
+ import * as path from 'node:path';
29
+ import { fileURLToPath, pathToFileURL } from 'node:url';
30
+
31
+ import {
32
+ CHANGELOG,
33
+ REPO_ROOT,
34
+ VERSION_HEADING_RE,
35
+ } from './_lib/changelog_eras.js';
36
+
37
+ const _HERE = fileURLToPath(import.meta.url);
38
+
39
+ const ROLLBACK_LINE_RE = /\bRollback:/;
40
+ // Section ends at the next version heading, the next era header, or any
41
+ // other level-1/2 heading (`## [Unreleased]`, `# Era: …`). `###` sub-
42
+ // headings stay inside the section.
43
+ const SECTION_BOUNDARY_RE = /^##? /;
44
+
45
+ export interface Violation {
46
+ readonly version: string;
47
+ readonly line: number; // 1-based heading line
48
+ }
49
+
50
+ function parse_semver(v: string): [number, number, number] | null {
51
+ const m = /^(\d+)\.(\d+)\.(\d+)$/.exec(v.trim());
52
+ if (!m) {
53
+ return null;
54
+ }
55
+ return [Number(m[1]), Number(m[2]), Number(m[3])];
56
+ }
57
+
58
+ function semver_gt(a: [number, number, number], b: [number, number, number]): boolean {
59
+ if (a[0] !== b[0]) return a[0] > b[0];
60
+ if (a[1] !== b[1]) return a[1] > b[1];
61
+ return a[2] > b[2];
62
+ }
63
+
64
+ /** Read the cutoff version from package.json at the repo root. */
65
+ function package_json_version(): string {
66
+ const pkg_path = path.join(REPO_ROOT, 'package.json');
67
+ const pkg = JSON.parse(fs.readFileSync(pkg_path, 'utf-8')) as { version?: string };
68
+ if (typeof pkg.version !== 'string' || parse_semver(pkg.version) === null) {
69
+ throw new Error(`package.json version is not bare semver: '${String(pkg.version)}'`);
70
+ }
71
+ return pkg.version;
72
+ }
73
+
74
+ /**
75
+ * Core check: return one Violation per minor/major section (`## [X.Y.0]`)
76
+ * whose version is strictly greater than `cutoff` and whose body carries
77
+ * no `Rollback:` line.
78
+ */
79
+ export function check_rollback_lines(lines: readonly string[], cutoff: string): Violation[] {
80
+ const cut = parse_semver(cutoff);
81
+ if (cut === null) {
82
+ throw new Error(`cutoff is not bare semver (X.Y.Z): '${cutoff}'`);
83
+ }
84
+ const violations: Violation[] = [];
85
+ for (let i = 0; i < lines.length; i++) {
86
+ const m = VERSION_HEADING_RE.exec(lines[i]!);
87
+ if (!m) {
88
+ continue;
89
+ }
90
+ const version = m.groups!['version']!;
91
+ const parsed = parse_semver(version);
92
+ if (parsed === null || parsed[2] !== 0) {
93
+ continue; // patch release — not a minor/major section
94
+ }
95
+ if (!semver_gt(parsed, cut)) {
96
+ continue; // at or below the cutoff — grandfathered
97
+ }
98
+ // Section body: heading line down to (exclusive) the next boundary.
99
+ let end = lines.length;
100
+ for (let j = i + 1; j < lines.length; j++) {
101
+ if (SECTION_BOUNDARY_RE.test(lines[j]!)) {
102
+ end = j;
103
+ break;
104
+ }
105
+ }
106
+ const has_rollback = lines.slice(i, end).some((ln) => ROLLBACK_LINE_RE.test(ln));
107
+ if (!has_rollback) {
108
+ violations.push({ version, line: i + 1 });
109
+ }
110
+ }
111
+ return violations;
112
+ }
113
+
114
+ // ─── selftest fixtures ─────────────────────────────────────────────────────────
115
+
116
+ function _fixture(section_body: string): string[] {
117
+ return (
118
+ '# Changelog\n\n' +
119
+ '## [Unreleased]\n\n' +
120
+ '# Era: 9.1.x — current\n\n' +
121
+ '> Started at `9.1.0`.\n\n' +
122
+ section_body +
123
+ '# Era: pre-9.0.0 — archived\n'
124
+ ).split('\n');
125
+ }
126
+
127
+ function run_selftest(): number {
128
+ const cutoff = '9.0.0';
129
+ let failed = 0;
130
+ const assert = (label: string, ok: boolean): void => {
131
+ process.stdout.write(`${ok ? '✅' : '❌'} selftest: ${label}\n`);
132
+ if (!ok) failed += 1;
133
+ };
134
+
135
+ // 1. New minor section WITHOUT Rollback: → exactly one violation.
136
+ const red = check_rollback_lines(
137
+ _fixture(
138
+ '## [9.1.0](https://example/compare/9.0.0...9.1.0) (2026-07-10)\n\n' +
139
+ '### Features\n\n* **workspace:** new subsystem ([abc1234](https://example))\n\n',
140
+ ),
141
+ cutoff,
142
+ );
143
+ assert('minor section without Rollback: FAILS', red.length === 1 && red[0]!.version === '9.1.0');
144
+
145
+ // 2. Same section WITH a Rollback: line → clean.
146
+ const green = check_rollback_lines(
147
+ _fixture(
148
+ '## [9.1.0](https://example/compare/9.0.0...9.1.0) (2026-07-10)\n\n' +
149
+ '### Features\n\n* **workspace:** new subsystem ([abc1234](https://example))\n' +
150
+ ' Rollback: set `workspace.enabled: false` in `.agent-settings.yml`.\n\n',
151
+ ),
152
+ cutoff,
153
+ );
154
+ assert('minor section with Rollback: passes', green.length === 0);
155
+
156
+ // 3. Historical minor section at/below the cutoff → grandfathered.
157
+ const old = check_rollback_lines(
158
+ _fixture('## [9.0.0](https://example/compare/8.9.0...9.0.0) (2026-06-01)\n\n* old entry\n\n'),
159
+ cutoff,
160
+ );
161
+ assert('section at cutoff is grandfathered', old.length === 0);
162
+
163
+ // 4. New PATCH section without Rollback: → out of scope.
164
+ const patch = check_rollback_lines(
165
+ _fixture('## [9.1.1](https://example/compare/9.1.0...9.1.1) (2026-07-11)\n\n* a fix\n\n'),
166
+ cutoff,
167
+ );
168
+ assert('patch section is out of scope', patch.length === 0);
169
+
170
+ // 5. Rollback: in a FOLLOWING section must not satisfy the earlier one.
171
+ const bleed = check_rollback_lines(
172
+ _fixture(
173
+ '## [9.2.0](https://example/compare/9.1.0...9.2.0) (2026-08-01)\n\n* no rollback here\n\n' +
174
+ '## [9.1.0](https://example/compare/9.0.0...9.1.0) (2026-07-10)\n\n' +
175
+ 'Rollback: only covers 9.1.0.\n\n',
176
+ ),
177
+ cutoff,
178
+ );
179
+ assert('Rollback: does not bleed across sections', bleed.length === 1 && bleed[0]!.version === '9.2.0');
180
+
181
+ if (failed > 0) {
182
+ process.stderr.write(`❌ selftest: ${failed} fixture(s) failed.\n`);
183
+ return 1;
184
+ }
185
+ process.stdout.write('✅ selftest: all fixtures behave as specified.\n');
186
+ return 0;
187
+ }
188
+
189
+ // ─── CLI ───────────────────────────────────────────────────────────────────────
190
+
191
+ interface ParsedArgs {
192
+ selftest: boolean;
193
+ cutoff: string | null;
194
+ changelog: string | null;
195
+ quiet: boolean;
196
+ }
197
+
198
+ function _argparse_error(message: string): never {
199
+ process.stderr.write(`lint_changelog_rollback: error: ${message}\n`);
200
+ process.exit(2);
201
+ }
202
+
203
+ function parse_args(argv: readonly string[]): ParsedArgs {
204
+ const args: ParsedArgs = { selftest: false, cutoff: null, changelog: null, quiet: false };
205
+ for (let i = 0; i < argv.length; i++) {
206
+ const arg = argv[i]!;
207
+ if (arg === '--selftest') {
208
+ args.selftest = true;
209
+ } else if (arg === '--cutoff') {
210
+ const v = argv[++i];
211
+ if (v === undefined) _argparse_error('argument --cutoff: expected one argument');
212
+ args.cutoff = v;
213
+ } else if (arg.startsWith('--cutoff=')) {
214
+ args.cutoff = arg.slice('--cutoff='.length);
215
+ } else if (arg === '--changelog') {
216
+ const v = argv[++i];
217
+ if (v === undefined) _argparse_error('argument --changelog: expected one argument');
218
+ args.changelog = v;
219
+ } else if (arg.startsWith('--changelog=')) {
220
+ args.changelog = arg.slice('--changelog='.length);
221
+ } else if (arg === '--quiet') {
222
+ args.quiet = true;
223
+ } else if (arg === '-h' || arg === '--help') {
224
+ process.stdout.write(
225
+ 'usage: lint_changelog_rollback [-h] [--selftest] [--cutoff X.Y.Z] [--changelog PATH] [--quiet]\n',
226
+ );
227
+ process.exit(0);
228
+ } else {
229
+ _argparse_error(`unrecognized arguments: ${arg}`);
230
+ }
231
+ }
232
+ return args;
233
+ }
234
+
235
+ function main(argv?: readonly string[]): number {
236
+ const args = parse_args(argv ?? process.argv.slice(2));
237
+ if (args.selftest) {
238
+ return run_selftest();
239
+ }
240
+
241
+ const changelog_path = args.changelog ?? CHANGELOG;
242
+ const cutoff = args.cutoff ?? package_json_version();
243
+ const text = fs.readFileSync(changelog_path, 'utf-8');
244
+ const lines = text.split(/\r\n|\r|\n/);
245
+
246
+ const violations = check_rollback_lines(lines, cutoff);
247
+ if (violations.length > 0) {
248
+ process.stderr.write(
249
+ `❌ ${violations.length} minor/major CHANGELOG section(s) above ` +
250
+ `cutoff ${cutoff} without a Rollback: line:\n`,
251
+ );
252
+ for (const v of violations) {
253
+ process.stderr.write(` - ## [${v.version}] (line ${v.line})\n`);
254
+ }
255
+ process.stderr.write(
256
+ '\nEvery new minor/major section must name a rollback path for each ' +
257
+ 'introduced or substantially reworked subsystem via a `Rollback:` ' +
258
+ 'line — see docs/contracts/release-sizing.md § Rollback path.\n',
259
+ );
260
+ return 1;
261
+ }
262
+
263
+ if (!args.quiet) {
264
+ process.stdout.write(
265
+ `✅ CHANGELOG Rollback: gate clean (cutoff ${cutoff}, ` +
266
+ `${path.relative(REPO_ROOT, changelog_path)}).\n`,
267
+ );
268
+ }
269
+ return 0;
270
+ }
271
+
272
+ function _isCliEntry(): boolean {
273
+ if (process.argv[1] === undefined) {
274
+ return false;
275
+ }
276
+ const argvUrl = pathToFileURL(path.resolve(process.argv[1])).href;
277
+ if (import.meta.url === argvUrl) {
278
+ return true;
279
+ }
280
+ try {
281
+ const here = fs.realpathSync(fileURLToPath(import.meta.url));
282
+ const argv = fs.realpathSync(path.resolve(process.argv[1]));
283
+ return here === argv;
284
+ } catch {
285
+ return false;
286
+ }
287
+ }
288
+
289
+ if (_isCliEntry() || process.argv[1] === _HERE) {
290
+ process.exit(main());
291
+ }
292
+
293
+ export { main, run_selftest, parse_semver, semver_gt, package_json_version };
@@ -14,9 +14,10 @@
14
14
  * - sessions scale — `agents/knowledge/sessions/` > 50 pages
15
15
  * → design fold-through-consolidate-gate for tracked pages
16
16
  * - type scale — any single memory/knowledge type > 200 files
17
- * → build the pre-decided file-first in-memory BM25 CLI
17
+ * → rank via `_lib/lexical_index.ts` (hand-rolled BM25 +
18
+ * trigram, no engine fork / no FTS5; ADR-061 honoured)
18
19
  * - corpus scale — > 500 files across all types
19
- * → same BM25 activation path
20
+ * → same lexical-index activation path
20
21
  * - hot-context size — `agents/runtime/state/hot-context.md` > 600 tokens
21
22
  * (estimated at 4 chars/token) → trim schema / fix the
22
23
  * deterministic writer
@@ -151,7 +152,7 @@ export function runChecks(root: string): Warning[] {
151
152
  metric: `${n}/${TYPE_FILES_MAX}`,
152
153
  message:
153
154
  `type '${type}' holds ${n} files (> ${TYPE_FILES_MAX}). ` +
154
- 'Activation path (pre-decided): build the file-first in-memory BM25 CLI (re-index at session start, no vectors, no service).',
155
+ 'Activation path (resolved, road-to-retrieval-substrate-hardening B2): rank via the hand-rolled BM25 + trigram index in `_lib/lexical_index.ts` (pure stdlib, NO engine fork / NO SQLite-FTS5, ADR-061 honoured). Measured lift: mean tie-set 3.333 → 1.0 (internal/bench/reports/lexical-ranking.json). Wire it lazily at first lookup + a stat-index, no vectors, no service.',
155
156
  });
156
157
  }
157
158
  }
@@ -190,6 +190,9 @@ function lint_roadmap(
190
190
  _check_execution_mode(fm, problems);
191
191
  if (warnings) {
192
192
  _check_autonomous_authoring(text, fm, warnings);
193
+ _check_human_gate_steps(text, warnings);
194
+ _check_human_gate_phase_headings(text, warnings);
195
+ _check_human_gate_exit_criteria(text, warnings);
193
196
  }
194
197
  return problems;
195
198
  }
@@ -236,6 +239,127 @@ function _check_autonomous_authoring(text: string, fm: string, warnings: string[
236
239
  }
237
240
  }
238
241
 
242
+ // Human-gate step patterns (templates/roadmaps.md rule 22) — checkbox
243
+ // steps that park work on a human instead of the agent. Steps are
244
+ // agent-executable by default; genuinely user-clearable gates belong in
245
+ // a structured `## Blockers` entry, agent-checkable verifications become
246
+ // commands / targeted tests. Warnings never fail the lint; they fire for
247
+ // every execution mode (an interactive roadmap with sprinkled human-check
248
+ // steps is the same authoring bug, just slower to hurt).
249
+ const HUMAN_GATE_STEP_PATS: Array<[RegExp, string]> = [
250
+ [
251
+ /\b(user|maintainer|human|operator|stakeholder|product owner) (verifies|reviews|approves|confirms|signs?[- ]off(?: on)?|inspects|validates)\b/i,
252
+ "'<human> verifies / reviews / approves …' step",
253
+ ],
254
+ [
255
+ /\bmanually (verify|review|check|test|confirm|inspect|validate)\b/i,
256
+ "'manually verify / check …' step",
257
+ ],
258
+ [
259
+ /\bwait for (the )?(user|maintainer|stakeholders?|approval|confirmation|review|feedback)\b/i,
260
+ "'wait for approval / user' step",
261
+ ],
262
+ [/\bask the (user|maintainer)\b/i, "'ask the user' step"],
263
+ [
264
+ /\b(get|obtain|request|await|collect) ((the|an?|final) )?((user|maintainer|stakeholder|product owner|team) )?(approval|confirmation|sign[- ]?off)\b/i,
265
+ "'obtain approval / sign-off' step",
266
+ ],
267
+ [/\bsign[- ]?off (from|by)\b/i, "'sign-off from <human>' step"],
268
+ [/\bhuman review (required|needed|gate)\b/i, "'human review required' step"],
269
+ [
270
+ /\b(confirm|check|verify|review|clarify) with the (user|maintainer)\b/i,
271
+ "'confirm with the user' step",
272
+ ],
273
+ ];
274
+
275
+ // Human-gate phase headings (template rule 22) — a phase whose TITLE is
276
+ // the human checkpoint ("## Phase 5 — Review / Sign-off", "## User
277
+ // Acceptance"). Whole-heading match after an optional "Phase N" prefix,
278
+ // so working headings ("## Review existing skills") never fire.
279
+ const HUMAN_GATE_HEADING_PAT =
280
+ /^#{2,3}\s+(?:phase\s+\d+\s*(?:[—–:.-])?\s*)?(?:review(?:\s*(?:[/&+]|and)\s*sign[- ]?off)?|sign[- ]?off|approval|user\s+acceptance(?:\s+testing)?|manual\s+(?:verification|review|testing|qa)|human\s+(?:validation|review))\s*$/i;
281
+
282
+ // Human-approval exit / acceptance criteria (template rule 22) — criteria
283
+ // must be agent-decidable (exit code, file exists, test passes), never
284
+ // "user approves" / "looks good". Applied inside Exit-criteria /
285
+ // Acceptance blocks only.
286
+ const HUMAN_GATE_CRITERIA_PAT =
287
+ /\b(user|maintainer|human|operator|stakeholder|product owner)\b[^\n]{0,60}\b(approv\w*|confirm\w*|sign[- ]?off|reviews)\b|\b(approval|confirmation|sign[- ]?off)\s+(from|by|received)\b|\blooks good\b/i;
288
+
289
+ const EXIT_CRITERIA_OPENER_PAT = /^\s*(?:[-*]\s+)?\*{0,2}(exit criteria|acceptance(?: criteria)?)\*{0,2}\s*:?/i;
290
+
291
+ const RULE22_HINT =
292
+ 'templates/roadmaps.md rule 22: steps and criteria are agent-decidable; ' +
293
+ 'replace with an agent-verifiable check (command / targeted test) or, ' +
294
+ 'if only a human can decide/authorize, a structured ## Blockers entry';
295
+
296
+ function _check_human_gate_steps(text: string, warnings: string[]): void {
297
+ for (const rawLine of text.split('\n')) {
298
+ if (!/^\s*-\s*\[ \]/.test(rawLine)) {
299
+ continue;
300
+ }
301
+ for (const [re, label] of HUMAN_GATE_STEP_PATS) {
302
+ if (re.test(rawLine)) {
303
+ warnings.push(
304
+ `human-gate step (${label}): ` +
305
+ `"${rawLine.trim().slice(0, 80)}" — ${RULE22_HINT}`,
306
+ );
307
+ break;
308
+ }
309
+ }
310
+ }
311
+ }
312
+
313
+ function _check_human_gate_phase_headings(text: string, warnings: string[]): void {
314
+ for (const rawLine of text.split('\n')) {
315
+ if (!rawLine.startsWith('#')) {
316
+ continue;
317
+ }
318
+ if (HUMAN_GATE_HEADING_PAT.test(rawLine)) {
319
+ warnings.push(
320
+ `human-gate phase heading: "${rawLine.trim().slice(0, 80)}" — ` +
321
+ 'a phase whose work only a human can finish blocks autonomous ' +
322
+ `execution; ${RULE22_HINT}`,
323
+ );
324
+ }
325
+ }
326
+ }
327
+
328
+ function _check_human_gate_exit_criteria(text: string, warnings: string[]): void {
329
+ let inHeadingBlock = false;
330
+ let inInlineBlock = false;
331
+ for (const rawLine of text.split('\n')) {
332
+ const heading = /^#{1,6}\s+(.*)$/.exec(rawLine);
333
+ if (heading) {
334
+ inHeadingBlock = /\b(acceptance|exit criteria)\b/i.test(heading[1]!);
335
+ inInlineBlock = false;
336
+ continue;
337
+ }
338
+ if (rawLine.trim() === '') {
339
+ inInlineBlock = false;
340
+ continue;
341
+ }
342
+ const opener = EXIT_CRITERIA_OPENER_PAT.test(rawLine);
343
+ if (opener) {
344
+ inInlineBlock = true;
345
+ }
346
+ if (!inHeadingBlock && !inInlineBlock) {
347
+ continue;
348
+ }
349
+ // Open-checkbox lines are covered by _check_human_gate_steps;
350
+ // done/deferred/cancelled lines record history, not future gates.
351
+ if (/^\s*-\s*\[[ xX~-]\]/.test(rawLine)) {
352
+ continue;
353
+ }
354
+ if (HUMAN_GATE_CRITERIA_PAT.test(rawLine)) {
355
+ warnings.push(
356
+ `human-approval exit criterion: "${rawLine.trim().slice(0, 80)}" — ` +
357
+ `criteria must be agent-decidable; ${RULE22_HINT}`,
358
+ );
359
+ }
360
+ }
361
+ }
362
+
239
363
  function _check_execution_mode(fm: string, problems: string[]): void {
240
364
  if (!fm || !/^execution:/m.test(fm)) {
241
365
  return; // absent field = interactive default — valid
@@ -0,0 +1,195 @@
1
+ #!/usr/bin/env tsx
2
+ /**
3
+ * Versioned-cache lint (road-to-retrieval-substrate-hardening B5b).
4
+ *
5
+ * Every derived cache the suite writes MUST be safe to invalidate when its
6
+ * producer changes — otherwise a tool-version bump silently reads a
7
+ * stale-format cache and corrupts the read path. This lint makes that
8
+ * discipline structural: any cache-file path literal in `src/scripts/**`
9
+ * must EITHER carry a version namespace in the path (`v<N>`, a `${…version…}`
10
+ * interpolation, or a `_VERSION`/`schema_version` reference), OR carry an
11
+ * explicit invalidation justification comment (`cache-version:` /
12
+ * `cache-invalidation:`) within a few lines — an author's ack that the cache
13
+ * is invalidated by another mechanism (full overwrite, content hash, …).
14
+ *
15
+ * It is a FAILING gate (this roadmap introduces it), scoped tightly to
16
+ * cache-file SUFFIXES so it has near-zero false-positive surface: it fires on
17
+ * the derived caches later phases create (B2 index, B3 learning sidecar, B5a
18
+ * stat-index), not on directories or ordinary data files.
19
+ *
20
+ * Usage: lint_versioned_cache.ts [--dir <root>] [--format text|json] [--quiet]
21
+ * Exit codes: 0 = clean, 1 = usage error, 2 = violations found, 3 = internal.
22
+ */
23
+ import * as fs from 'node:fs';
24
+ import * as path from 'node:path';
25
+ import process from 'node:process';
26
+ import { fileURLToPath } from 'node:url';
27
+
28
+ const PROG = 'lint_versioned_cache.ts';
29
+
30
+ /** Window (lines) around a cache-path literal scanned for a justification. */
31
+ export const JUSTIFY_WINDOW = 6;
32
+
33
+ /**
34
+ * A path literal is a "derived cache file" when its basename matches one of
35
+ * these suffixes. Suffix-based (not dir-based) to keep the trigger precise —
36
+ * ordinary data/config JSON does not match.
37
+ */
38
+ export const CACHE_SUFFIX_RE =
39
+ /(?:^|[\/'"`\-.])(?:[a-z0-9_\-.]*)(?:-index|\.index|-cache|\.cache|\.stat-index|-stat-index|\.agent-learning)\.json(?:['"`]|$)/i;
40
+
41
+ /** A version namespace present in the path literal itself. */
42
+ const VERSION_IN_PATH_RE = /(?:[\/_\-.]v\d+|\$\{[^}]*[Vv]ersion[^}]*\}|\$\{[^}]*VERSION[^}]*\})/;
43
+
44
+ /** An explicit author justification comment near the literal. */
45
+ const JUSTIFY_RE = /(?:cache-version:|cache-invalidation:)/i;
46
+
47
+ /** A string / template literal containing a cache-file path. */
48
+ const LITERAL_RE = /(['"`])((?:\\.|(?!\1).)*?)\1/g;
49
+
50
+ export interface Violation {
51
+ file: string;
52
+ line: number; // 1-indexed
53
+ literal: string;
54
+ message: string;
55
+ }
56
+
57
+ function listTsFiles(root: string): string[] {
58
+ const out: string[] = [];
59
+ const skip = new Set([
60
+ 'node_modules',
61
+ '.git',
62
+ 'dist',
63
+ 'coverage',
64
+ '.turbo',
65
+ '.cache',
66
+ '__pycache__',
67
+ ]);
68
+ const walk = (dir: string): void => {
69
+ let entries: fs.Dirent[];
70
+ try {
71
+ entries = fs.readdirSync(dir, { withFileTypes: true });
72
+ } catch {
73
+ return;
74
+ }
75
+ for (const e of entries) {
76
+ if (e.isDirectory()) {
77
+ if (!skip.has(e.name)) walk(path.join(dir, e.name));
78
+ } else if (e.isFile() && e.name.endsWith('.ts') && !e.name.endsWith('.d.ts')) {
79
+ out.push(path.join(dir, e.name));
80
+ }
81
+ }
82
+ };
83
+ walk(root);
84
+ return out.sort();
85
+ }
86
+
87
+ function _isCachePathLiteral(value: string): boolean {
88
+ // Reset lastIndex — CACHE_SUFFIX_RE has no /g flag, so test() is safe.
89
+ return CACHE_SUFFIX_RE.test(value);
90
+ }
91
+
92
+ export function scanFile(file: string, source: string): Violation[] {
93
+ const lines = source.split('\n');
94
+ const violations: Violation[] = [];
95
+ for (let i = 0; i < lines.length; i++) {
96
+ const text = lines[i] as string;
97
+ LITERAL_RE.lastIndex = 0;
98
+ let m: RegExpExecArray | null;
99
+ while ((m = LITERAL_RE.exec(text)) !== null) {
100
+ const value = m[2] as string;
101
+ if (!_isCachePathLiteral(value)) continue;
102
+ if (VERSION_IN_PATH_RE.test(value)) continue; // versioned in the path itself
103
+
104
+ // Look for a justification comment in the window around the literal.
105
+ const from = Math.max(0, i - JUSTIFY_WINDOW);
106
+ const to = Math.min(lines.length - 1, i + JUSTIFY_WINDOW);
107
+ let justified = false;
108
+ for (let j = from; j <= to; j++) {
109
+ if (JUSTIFY_RE.test(lines[j] as string)) {
110
+ justified = true;
111
+ break;
112
+ }
113
+ }
114
+ if (justified) continue;
115
+
116
+ violations.push({
117
+ file,
118
+ line: i + 1,
119
+ literal: value,
120
+ message:
121
+ 'derived cache without a version namespace — add a `v<N>` (or ' +
122
+ '`${…version…}`) segment to the path, or an inline ' +
123
+ '`// cache-invalidation: <why no version is needed>` comment',
124
+ });
125
+ }
126
+ }
127
+ return violations;
128
+ }
129
+
130
+ export function runChecks(root: string): Violation[] {
131
+ const violations: Violation[] = [];
132
+ for (const file of listTsFiles(root)) {
133
+ // Never lint this file itself — its regexes contain the very suffixes
134
+ // it hunts for.
135
+ if (path.basename(file) === 'lint_versioned_cache.ts') continue;
136
+ let source: string;
137
+ try {
138
+ source = fs.readFileSync(file, 'utf-8');
139
+ } catch {
140
+ continue;
141
+ }
142
+ violations.push(...scanFile(file, source));
143
+ }
144
+ return violations;
145
+ }
146
+
147
+ export function main(argv: string[]): number {
148
+ let dir = 'src/scripts';
149
+ let format: 'text' | 'json' = 'text';
150
+ let quiet = false;
151
+ for (let i = 0; i < argv.length; i++) {
152
+ const a = argv[i] as string;
153
+ if (a === '--dir') {
154
+ dir = argv[++i] ?? dir;
155
+ } else if (a.startsWith('--dir=')) {
156
+ dir = a.slice('--dir='.length);
157
+ } else if (a === '--format') {
158
+ format = (argv[++i] as 'text' | 'json') ?? 'text';
159
+ } else if (a === '--quiet') {
160
+ quiet = true;
161
+ } else if (a === '-h' || a === '--help') {
162
+ process.stdout.write(`usage: ${PROG} [--dir <root>] [--format text|json] [--quiet]\n`);
163
+ return 0;
164
+ } else {
165
+ process.stderr.write(`${PROG}: error: unknown argument ${a}\n`);
166
+ return 1;
167
+ }
168
+ }
169
+
170
+ let violations: Violation[];
171
+ try {
172
+ violations = runChecks(path.resolve(dir));
173
+ } catch (exc) {
174
+ process.stderr.write(`${PROG}: internal error: ${String(exc)}\n`);
175
+ return 3;
176
+ }
177
+
178
+ if (format === 'json') {
179
+ process.stdout.write(JSON.stringify({ violations }, null, 2) + '\n');
180
+ return violations.length > 0 ? 2 : 0;
181
+ }
182
+ if (violations.length === 0) {
183
+ if (!quiet) process.stdout.write(`${PROG}: no unversioned derived caches\n`);
184
+ return 0;
185
+ }
186
+ for (const v of violations) {
187
+ process.stdout.write(`❌ ${v.file}:${v.line} — cache '${v.literal}': ${v.message}\n`);
188
+ }
189
+ process.stdout.write(`${PROG}: ${violations.length} unversioned derived cache(s)\n`);
190
+ return 2;
191
+ }
192
+
193
+ if (fileURLToPath(import.meta.url) === path.resolve(process.argv[1] ?? '')) {
194
+ process.exit(main(process.argv.slice(2)));
195
+ }
@@ -70,7 +70,8 @@
70
70
  "types": {"type": "array", "items": {"type": "string"}, "minItems": 1, "description": "Memory types to scan, e.g. `historical-patterns`, `incident-learnings`, `ownership`. At least one required."},
71
71
  "keys": {"type": "array", "items": {"type": "string"}, "description": "Optional anchor paths or globs to match entries against (e.g. a file you are about to edit)."},
72
72
  "limit": {"type": "integer", "minimum": 1, "default": 5, "description": "Maximum entries to return per type. Defaults to 5."},
73
- "detail": {"type": "string", "enum": ["index", "full"], "default": "full", "description": "'index' returns compact priced rows (id, title, tokens_estimate) instead of full bodies — call this first, then memory_get the ids you need. 'full' (default) returns complete entries."}
73
+ "detail": {"type": "string", "enum": ["index", "full"], "default": "full", "description": "'index' returns compact priced rows (id, title, tokens_estimate) instead of full bodies — call this first, then memory_get the ids you need. 'full' (default) returns complete entries."},
74
+ "token_budget": {"type": "integer", "minimum": 1, "description": "Optional token budget. When set, entries are rendered as one-line compact rows (id, type, confidence, `line`) and the row set is hard-cut at token_budget × 4 chars; omitted hits appear as a top-level `truncation` hint naming a concrete next step. Absent → the envelope is unchanged."}
74
75
  },
75
76
  "required": ["types"],
76
77
  "additionalProperties": false