@event4u/agent-config 9.14.0 → 9.15.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 (276) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/CHANGELOG.md +110 -112
  3. package/README.md +8 -0
  4. package/dist/agent-src/commands/agents/audit.md +7 -7
  5. package/dist/agent-src/commands/agents/optimize.md +1 -1
  6. package/dist/agent-src/commands/analyze/reference-repo.md +1 -1
  7. package/dist/agent-src/commands/chat-history.md +1 -0
  8. package/dist/agent-src/commands/context.md +1 -0
  9. package/dist/agent-src/commands/council.md +1 -1
  10. package/dist/agent-src/commands/ghostwriter.md +1 -1
  11. package/dist/agent-src/commands/memory.md +2 -1
  12. package/dist/agent-src/commands/module.md +1 -0
  13. package/dist/agent-src/commands/optimize/agents-dir.md +1 -1
  14. package/dist/agent-src/commands/optimize/deep.md +5 -2
  15. package/dist/agent-src/commands/optimize/skills.md +5 -5
  16. package/dist/agent-src/commands/override.md +1 -0
  17. package/dist/agent-src/commands/prediction-pool.md +4 -4
  18. package/dist/agent-src/commands/research/deep.md +1 -1
  19. package/dist/agent-src/commands/roadmap.md +2 -1
  20. package/dist/agent-src/commands/rule-compliance-audit.md +1 -1
  21. package/dist/agent-src/commands/tdd.md +1 -0
  22. package/dist/agent-src/commands/tests.md +1 -0
  23. package/dist/agent-src/commands/upstream-contribute.md +5 -5
  24. package/dist/agent-src/commands/video/from-script.md +1 -1
  25. package/dist/agent-src/commands/video/from-song.md +1 -1
  26. package/dist/agent-src/commands/video/scene.md +1 -1
  27. package/dist/agent-src/commands/video/stitch.md +1 -1
  28. package/dist/agent-src/commands/video/storyboard.md +1 -1
  29. package/dist/agent-src/commands/worktree/create.md +2 -1
  30. package/dist/agent-src/contexts/augment-infrastructure.md +1 -1
  31. package/dist/agent-src/contexts/contracts/artifact-engagement-flow.md +5 -5
  32. package/dist/agent-src/contexts/contracts/command-suggestion-flow.md +3 -3
  33. package/dist/agent-src/contexts/contracts/emergency-triage-block.md +2 -2
  34. package/dist/agent-src/contexts/execution/roadmap-process-loop.md +4 -3
  35. package/dist/agent-src/contexts/execution/subagent-routing.md +19 -0
  36. package/dist/agent-src/contexts/override-system.md +1 -1
  37. package/dist/agent-src/ghostwriter/fictional-fixture-v1.md +1 -1
  38. package/dist/agent-src/rules/augment-edit-discipline.md +1 -1
  39. package/dist/agent-src/rules/autonomous-execution.md +2 -1
  40. package/dist/agent-src/rules/brand-consistency.md +1 -0
  41. package/dist/agent-src/rules/brand-source-of-truth.md +1 -0
  42. package/dist/agent-src/rules/code-comment-discipline.md +2 -0
  43. package/dist/agent-src/rules/communication-through-line.md +1 -0
  44. package/dist/agent-src/rules/content-quoting-floor.md +1 -0
  45. package/dist/agent-src/rules/cross-source-consistency.md +0 -1
  46. package/dist/agent-src/rules/downstream-changes.md +1 -0
  47. package/dist/agent-src/rules/external-code-graph-interop.md +1 -0
  48. package/dist/agent-src/rules/external-reference-deep-dive.md +1 -0
  49. package/dist/agent-src/rules/fast-path-marker-visibility.md +1 -0
  50. package/dist/agent-src/rules/finance-safety-floor.md +0 -1
  51. package/dist/agent-src/rules/icon-consistency.md +1 -0
  52. package/dist/agent-src/rules/image-likeness-and-rights.md +1 -0
  53. package/dist/agent-src/rules/improve-before-implement.md +1 -1
  54. package/dist/agent-src/rules/invite-challenge.md +1 -0
  55. package/dist/agent-src/rules/lethal-trifecta-guard.md +1 -0
  56. package/dist/agent-src/rules/low-impact-corpus-privacy-floor.md +1 -0
  57. package/dist/agent-src/rules/markdown-safe-codeblocks.md +1 -0
  58. package/dist/agent-src/rules/media-governance-routing.md +4 -3
  59. package/dist/agent-src/rules/media-sync-ground-truth.md +1 -0
  60. package/dist/agent-src/rules/no-attribution-footers.md +1 -0
  61. package/dist/agent-src/rules/no-decorative-emojis-in-git-surfaces.md +1 -0
  62. package/dist/agent-src/rules/no-pr-progress-comments.md +1 -0
  63. package/dist/agent-src/rules/onboarding-gate.md +1 -0
  64. package/dist/agent-src/rules/output-discipline.md +1 -0
  65. package/dist/agent-src/rules/prefer-enums-over-literals.md +1 -0
  66. package/dist/agent-src/rules/preservation-guard.md +1 -0
  67. package/dist/agent-src/rules/question-not-instruction.md +1 -0
  68. package/dist/agent-src/rules/roadmap-ci-steps-policy.md +1 -0
  69. package/dist/agent-src/rules/role-mode-adherence.md +1 -0
  70. package/dist/agent-src/rules/runtime-safety.md +1 -0
  71. package/dist/agent-src/rules/security-sensitive-stop.md +1 -0
  72. package/dist/agent-src/rules/session-canary.md +9 -2
  73. package/dist/agent-src/rules/source-confidentiality.md +1 -0
  74. package/dist/agent-src/rules/source-of-truth.md +1 -0
  75. package/dist/agent-src/rules/spreadsheet-source-quality.md +1 -0
  76. package/dist/agent-src/rules/think-before-action.md +1 -0
  77. package/dist/agent-src/rules/token-budget-discipline.md +1 -0
  78. package/dist/agent-src/rules/token-efficiency.md +1 -0
  79. package/dist/agent-src/rules/token-optimizer-maintenance.md +5 -5
  80. package/dist/agent-src/rules/tool-safety.md +1 -0
  81. package/dist/agent-src/rules/untrusted-input-defense.md +2 -0
  82. package/dist/agent-src/rules/user-interaction.md +1 -0
  83. package/dist/agent-src/rules/user-interrupt-priority.md +1 -0
  84. package/dist/agent-src/skills/adr-create/SKILL.md +1 -1
  85. package/dist/agent-src/skills/check-refs/SKILL.md +1 -1
  86. package/dist/agent-src/skills/competitive-positioning/SKILL.md +1 -1
  87. package/dist/agent-src/skills/condense-memory/SKILL.md +1 -1
  88. package/dist/agent-src/skills/context-authoring/SKILL.md +1 -1
  89. package/dist/agent-src/skills/copilot-agents-optimization/SKILL.md +1 -1
  90. package/dist/agent-src/skills/description-assist/SKILL.md +1 -1
  91. package/dist/agent-src/skills/github-ci/SKILL.md +1 -0
  92. package/dist/agent-src/skills/guideline-writing/SKILL.md +1 -1
  93. package/dist/agent-src/skills/lint-skills/SKILL.md +1 -1
  94. package/dist/agent-src/skills/readme-reviewer/SKILL.md +1 -1
  95. package/dist/agent-src/skills/roadmap-management/SKILL.md +8 -1
  96. package/dist/agent-src/skills/roadmap-writing/SKILL.md +1 -1
  97. package/dist/agent-src/skills/skill-improvement-pipeline/SKILL.md +1 -1
  98. package/dist/agent-src/skills/skill-writing/SKILL.md +4 -4
  99. package/dist/agent-src/skills/token-optimizer/SKILL.md +4 -4
  100. package/dist/agent-src/skills/upstream-contribute/SKILL.md +6 -6
  101. package/dist/agent-src/skills/using-git-worktrees/SKILL.md +25 -0
  102. package/dist/agent-src/templates/AGENTS.md +1 -1
  103. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  104. package/dist/agent-src/templates/command.md +1 -1
  105. package/dist/agent-src/templates/copilot-instructions.md +3 -3
  106. package/dist/agent-src/templates/rule.md +2 -2
  107. package/dist/agent-src/templates/scripts/work_engine/orchestration.ts +1 -1
  108. package/dist/agent-src/templates/skill-archive-note.md +5 -5
  109. package/dist/agent-src/templates/skill.md +1 -1
  110. package/dist/cli/agent-config.js +66 -19
  111. package/dist/cli/agent-config.js.map +1 -1
  112. package/dist/cli/main.js +9 -4
  113. package/dist/cli/main.js.map +1 -1
  114. package/dist/cli/python/workspace_drive.js +2 -0
  115. package/dist/cli/python/workspace_drive.js.map +1 -1
  116. package/dist/cli/python/workspace_drive_health.js +2 -0
  117. package/dist/cli/python/workspace_drive_health.js.map +1 -1
  118. package/dist/cli/python/workspace_hosts.js +2 -0
  119. package/dist/cli/python/workspace_hosts.js.map +1 -1
  120. package/dist/cli/python/workspace_sessions.js +2 -0
  121. package/dist/cli/python/workspace_sessions.js.map +1 -1
  122. package/dist/cli/registry.js +1 -0
  123. package/dist/cli/registry.js.map +1 -1
  124. package/dist/cli-delegate/{chunk-A3HJ6NTI.js → chunk-63WSP6YZ.js} +2 -1
  125. package/dist/cli-delegate/{chunk-EJYIGZ3T.js → chunk-BKO2AZ7Z.js} +1 -1
  126. package/dist/cli-delegate/{chunk-GCVFLBP2.js → chunk-H574KOUH.js} +338 -494
  127. package/dist/cli-delegate/chunk-NXFWFAYV.js +339 -0
  128. package/dist/cli-delegate/{chunk-TEGJU2JX.js → chunk-ZXXJLL5K.js} +2 -2
  129. package/dist/cli-delegate/cmd_conformance.js +29 -23
  130. package/dist/cli-delegate/cmd_doctor.js +3 -3
  131. package/dist/cli-delegate/cmd_export.js +4 -4
  132. package/dist/cli-delegate/cmd_preflight.js +2 -2
  133. package/dist/cli-delegate/cmd_prune.js +4 -4
  134. package/dist/cli-delegate/cmd_settings_migrate.js +4 -4
  135. package/dist/cli-delegate/cmd_sync.js +4 -4
  136. package/dist/cli-delegate/cmd_uninstall.js +4 -4
  137. package/dist/cli-delegate/cmd_validate.js +4 -4
  138. package/dist/discovery/deprecation-report.md +1 -1
  139. package/dist/discovery/discovery-manifest.json +127 -95
  140. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  141. package/dist/discovery/discovery-manifest.summary.md +1 -1
  142. package/dist/discovery/orphan-report.md +1 -1
  143. package/dist/discovery/packs.json +2 -2
  144. package/dist/discovery/trust-report.md +1 -1
  145. package/dist/discovery/workspaces.json +2 -2
  146. package/dist/hooks/dispatch.js +729 -430
  147. package/dist/install/install.mjs +52 -11
  148. package/dist/install/rule_scope.js +18 -2
  149. package/dist/install/rule_scope.js.map +1 -1
  150. package/dist/mcp/registry-manifest.json +2 -2
  151. package/dist/mcp/server.json +2 -2
  152. package/dist/mcp/server.mjs +518 -411
  153. package/dist/router.json +1 -1
  154. package/dist/scripts/_lib/claude_settings_hooks.js +30 -5
  155. package/dist/scripts/_lib/claude_settings_hooks.js.map +1 -1
  156. package/dist/scripts/_lib/scoped_projection.js +223 -0
  157. package/dist/scripts/_lib/scoped_projection.js.map +1 -0
  158. package/dist/scripts/_lib/surface_tiers.js +125 -0
  159. package/dist/scripts/_lib/surface_tiers.js.map +1 -0
  160. package/dist/scripts/_lib/token_count.js +166 -0
  161. package/dist/scripts/_lib/token_count.js.map +1 -0
  162. package/dist/scripts/_lib/trigger_eval_floors.js +8 -0
  163. package/dist/scripts/_lib/trigger_eval_floors.js.map +1 -1
  164. package/dist/scripts/condense.js +139 -9
  165. package/dist/scripts/condense.js.map +1 -1
  166. package/dist/scripts/project_thin_rules.js +450 -0
  167. package/dist/scripts/project_thin_rules.js.map +1 -0
  168. package/dist/server/routes/workspace.js +2 -0
  169. package/dist/server/routes/workspace.js.map +1 -1
  170. package/dist/server/schemas/settings.js +9 -3
  171. package/dist/server/schemas/settings.js.map +1 -1
  172. package/dist/ui/assets/index-DJY_vQVn.js +40 -0
  173. package/dist/ui/assets/index-DJY_vQVn.js.map +1 -0
  174. package/dist/ui/index.html +1 -1
  175. package/docs/guidelines/agent-infra/roadmap-progress-mechanics.md +1 -1
  176. package/package.json +11 -11
  177. package/src/agent-src/templates/AGENTS.md +1 -1
  178. package/src/agent-src/templates/scripts/work_engine/orchestration.ts +1 -1
  179. package/src/config/agent-settings.template.yml +59 -11
  180. package/src/config/ci-local-parity.yml +8 -0
  181. package/src/config/discovery/unassigned-artefacts.yml +23 -25
  182. package/src/config/evaluator-budgets.json +35 -5
  183. package/src/config/gate-coverage.yml +147 -0
  184. package/src/config/gate-violation-baselines.json +25 -0
  185. package/src/install/rule_scope.ts +27 -2
  186. package/src/scripts/_cli/cmd_conformance.ts +13 -24
  187. package/src/scripts/_dispatch.bash +10 -0
  188. package/src/scripts/_lib/claude_settings_hooks.ts +30 -4
  189. package/src/scripts/_lib/gate_baseline.ts +213 -0
  190. package/src/scripts/_lib/scoped_projection.ts +50 -0
  191. package/src/scripts/_lib/tier_budget_routing.ts +234 -0
  192. package/src/scripts/_lib/tool_adapter_registry.ts +8 -1
  193. package/src/scripts/_lib/trigger_eval_floors.ts +9 -0
  194. package/src/scripts/_lib/trigger_routers.ts +17 -5
  195. package/src/scripts/adr/regenerate_index.ts +4 -3
  196. package/src/scripts/ai-video/lib/parse-blueprint.sh +1 -1
  197. package/src/scripts/ai_council/compile_corpus.ts +1 -1
  198. package/src/scripts/audit_cloud_compatibility.ts +37 -2
  199. package/src/scripts/audit_initial_context.ts +53 -19
  200. package/src/scripts/audit_likelihood.ts +31 -2
  201. package/src/scripts/audit_user_type_axis.ts +58 -11
  202. package/src/scripts/bench_hook_latency.ts +172 -41
  203. package/src/scripts/build_mcp_catalog.ts +12 -1
  204. package/src/scripts/check_always_budget.ts +122 -11
  205. package/src/scripts/check_augment_description_cap.ts +24 -3
  206. package/src/scripts/check_ci_strict_superset.ts +150 -0
  207. package/src/scripts/check_cluster_patterns.ts +100 -0
  208. package/src/scripts/check_consumer_scope_flip.ts +26 -4
  209. package/src/scripts/check_evaluator_budgets.ts +198 -19
  210. package/src/scripts/check_gate_coverage.ts +337 -1
  211. package/src/scripts/check_generator_output_coverage.ts +2 -1
  212. package/src/scripts/check_iron_law_prominence.ts +1 -1
  213. package/src/scripts/check_kernel_rule_bundle.ts +1 -1
  214. package/src/scripts/check_no_new_legacy_path.ts +167 -3
  215. package/src/scripts/check_pack_size.ts +18 -4
  216. package/src/scripts/check_portability.ts +37 -7
  217. package/src/scripts/check_references.ts +19 -3
  218. package/src/scripts/check_release_pr_shape.ts +13 -0
  219. package/src/scripts/check_token_regression.ts +9 -2
  220. package/src/scripts/condense.ts +155 -12
  221. package/src/scripts/cost/budget.mjs +63 -2
  222. package/src/scripts/evaluator_umbrella.sh +34 -1
  223. package/src/scripts/generate_capability_matrix.ts +8 -0
  224. package/src/scripts/generate_router_coverage_from_matrix.ts +155 -0
  225. package/src/scripts/hook_manifest.yaml +17 -3
  226. package/src/scripts/hooks/block_config_weakening.ts +337 -0
  227. package/src/scripts/hooks/concern_registry.ts +2 -0
  228. package/src/scripts/hooks/probe_throttle.ts +75 -0
  229. package/src/scripts/install-hooks.sh +24 -2
  230. package/src/scripts/install.ts +2 -2
  231. package/src/scripts/lint_agents_md.ts +9 -3
  232. package/src/scripts/lint_archived_skills.ts +1 -1
  233. package/src/scripts/lint_artefact_frontmatter.ts +185 -37
  234. package/src/scripts/lint_command_routing.ts +21 -52
  235. package/src/scripts/lint_command_verbs.ts +47 -28
  236. package/src/scripts/lint_context_spine_usage.ts +38 -12
  237. package/src/scripts/lint_framework_leakage.ts +25 -6
  238. package/src/scripts/lint_handoffs.ts +49 -2
  239. package/src/scripts/lint_media_policy_linkage.ts +62 -30
  240. package/src/scripts/lint_namespace.ts +92 -7
  241. package/src/scripts/lint_new_skill_gate.ts +39 -31
  242. package/src/scripts/lint_originality_shingles.ts +7 -1
  243. package/src/scripts/lint_override_kernel_guard.ts +21 -3
  244. package/src/scripts/lint_pack_boundaries.ts +461 -91
  245. package/src/scripts/lint_pack_dependencies.ts +24 -57
  246. package/src/scripts/lint_persona_governance.ts +1 -1
  247. package/src/scripts/lint_role_experiences.ts +34 -5
  248. package/src/scripts/lint_trigger_precision.ts +142 -0
  249. package/src/scripts/mcp_server/consumer_tool_catalog.json +12 -201
  250. package/src/scripts/mcp_server/tools.ts +1 -1
  251. package/src/scripts/prepack-check.mjs +40 -1
  252. package/src/scripts/prepack_router_targets.d.mts +7 -0
  253. package/src/scripts/prepack_router_targets.mjs +93 -0
  254. package/src/scripts/print_required_checks.ts +72 -18
  255. package/src/scripts/profile_staleness_hook.ts +12 -0
  256. package/src/scripts/record_evaluator_measurements.mjs +105 -0
  257. package/src/scripts/recruit_preflight.sh +1 -1
  258. package/src/scripts/release.ts +105 -1
  259. package/src/scripts/router_target_paths.d.mts +4 -0
  260. package/src/scripts/router_target_paths.mjs +69 -0
  261. package/src/scripts/router_telemetry.ts +52 -1
  262. package/src/scripts/routing_doctor.ts +485 -0
  263. package/src/scripts/rule_trigger_eval.ts +403 -0
  264. package/src/scripts/schemas/rule.schema.json +5 -1
  265. package/src/scripts/session_canary_hook.ts +96 -5
  266. package/src/scripts/skill_linter.ts +9 -28
  267. package/src/scripts/smoke/skills.sh +1 -1
  268. package/src/scripts/sweep_dead_scan_roots.ts +356 -10
  269. package/src/scripts/trigger_coverage.ts +13 -2
  270. package/src/scripts/update_counts.ts +1 -1
  271. package/src/scripts/validate_telegraph_carveouts.ts +1 -1
  272. package/src/scripts/wrapper_freshness_hook.ts +14 -0
  273. package/src/server/schemas/settings.ts +19 -5
  274. package/dist/cli-delegate/chunk-TJIAM5FW.js +0 -133
  275. package/dist/ui/assets/index-BpSQt5vm.js +0 -40
  276. package/dist/ui/assets/index-BpSQt5vm.js.map +0 -1
@@ -224,7 +224,19 @@ function _is_carve_out_pointer_line(line: string): boolean {
224
224
  const SHAPE_EXAMPLE_RE =
225
225
  /\((?:Laravel|Symfony|Django|Rails|Next\.js)[^)]*\b(?:shape|example)\b|\b(?:Laravel|Symfony|Django|Rails) (?:shape|example):/i;
226
226
 
227
- const FRONTMATTER_FRAMEWORK_RE = /^---\s*\n([\s\S]*?)\n---/m;
227
+ /**
228
+ * Real frontmatter only — the block that OPENS the file, not any `---` fence.
229
+ *
230
+ * This carried `/m` with no start-of-file guard, the only such parser in the
231
+ * suite (every sibling anchors at index 0). With `/m`, a file that has no
232
+ * leading frontmatter latched onto the first `---`-delimited span anywhere in
233
+ * its body — and since a non-null match makes the caller `continue`, the whole
234
+ * file was then exempted from leakage scanning. What collides with it is the
235
+ * house convention of quoting a `---`-fenced frontmatter EXAMPLE in prose: one
236
+ * such example carrying a `framework:` line silently disarms the gate for that
237
+ * file (road-to-gates-that-can-fail Phase 6.2, finding 4).
238
+ */
239
+ const FRONTMATTER_FRAMEWORK_RE = /^---[ \t]*\n([\s\S]*?)\n---/;
228
240
  const FRAMEWORK_KEY_RE = /^(?:framework|\s+framework)\s*:\s*(\S+)/m;
229
241
 
230
242
  export function is_carve_out(p: string): boolean {
@@ -246,11 +258,12 @@ export function is_inventory_file(p: string): boolean {
246
258
  if (parts.includes('contexts')) {
247
259
  return true;
248
260
  }
249
- if (
250
- path.basename(rel) === 'README.md' &&
251
- parts.length === 2 &&
252
- (parts[0] === '.agent-src.uncondensed' || parts[0] === 'dist/agent-src')
253
- ) {
261
+ // DEAD-ROOT REPAIR (road-to-renewal-foundation Phase 1). Both comparisons
262
+ // were unreachable: `.agent-src.uncondensed` was deleted by ADR-051, and
263
+ // `'dist/agent-src'` can never equal a single `/`-split segment. So the
264
+ // inventory-README exemption never fired for any file, and the real
265
+ // inventory file (`src/agent-src/README.md`) got no exemption at all.
266
+ if (path.basename(rel) === 'README.md' && parts.length === 3 && parts[0] === 'src') {
254
267
  return true;
255
268
  }
256
269
  return false;
@@ -504,8 +517,13 @@ export function main(argv?: readonly string[]): number {
504
517
  const file_hits: Array<[string, Hit[]]> = [];
505
518
  let total_hits = 0;
506
519
  let allowlisted_total = 0;
520
+ // gate-coverage contract (src/config/gate-coverage.yml): files actually
521
+ // inspected. NOTE the "N files" in the human summary is files WITH HITS —
522
+ // reading that as coverage is exactly how a blind gate looks healthy.
523
+ let scannedFiles = 0;
507
524
 
508
525
  for (const f of iter_md_files(args.paths)) {
526
+ scannedFiles += 1;
509
527
  if (is_carve_out(f)) {
510
528
  continue;
511
529
  }
@@ -580,6 +598,7 @@ export function main(argv?: readonly string[]): number {
580
598
  `\n${total_hits} hits across ${file_hits.length} files ` +
581
599
  `(${allowlisted_total} allowlisted)\n`,
582
600
  );
601
+ process.stderr.write(`scanned: ${String(scannedFiles)}\n`);
583
602
  return total_hits ? 1 : 0;
584
603
  }
585
604
 
@@ -16,9 +16,20 @@ import * as fs from 'node:fs';
16
16
  import * as path from 'node:path';
17
17
  import { fileURLToPath, pathToFileURL } from 'node:url';
18
18
 
19
+ import { SRC_SKILLS } from './_lib/agent_src.js';
20
+ import { checkRatchet } from './_lib/gate_baseline.js';
21
+ import { DeadScopeError, assertScanned } from './_lib/scan_scope.js';
22
+
19
23
  const _HERE = fileURLToPath(import.meta.url);
20
24
  const REPO = path.resolve(path.dirname(_HERE), '..', '..');
21
- const SKILLS_DIR = path.join(REPO, '.agent-src.uncondensed', 'skills');
25
+ /**
26
+ * ADR-051 moved skill authoring to `src/skills`. Until 2026-08-02 this default
27
+ * still named the retired pre-ADR-051 source container, so the CLI walked a
28
+ * missing directory and printed "no violations under <that root>" while
29
+ * reading zero files. Resolved through the shared resolver so the next root
30
+ * move updates every consumer at once.
31
+ */
32
+ const SKILLS_DIR = SRC_SKILLS();
22
33
 
23
34
  /** Mirror `QUIET = "--quiet" in sys.argv` (computed at import). */
24
35
  const QUIET = process.argv.slice(2).includes('--quiet');
@@ -369,11 +380,32 @@ export function main(argv?: readonly string[]): number {
369
380
  if (args.length > 0) {
370
381
  skills_dir = _resolve(args[0] as string);
371
382
  }
383
+ const isDefaultRoot = skills_dir === SKILLS_DIR;
384
+
385
+ // Scope assertion: zero skill files means the root moved, not that the
386
+ // corpus is clean. This is the state the gate shipped in until 2026-08-02.
387
+ const scanned = _rglobSkillMd(skills_dir).length;
388
+ try {
389
+ assertScanned({
390
+ gate: 'lint_handoffs',
391
+ scanned,
392
+ units: 'skill file(s)',
393
+ roots: [_relTo(skills_dir, REPO)],
394
+ });
395
+ } catch (exc) {
396
+ if (!(exc instanceof DeadScopeError)) {
397
+ throw exc;
398
+ }
399
+ process.stderr.write(`❌ ${exc.message}\n`);
400
+ return 2;
401
+ }
402
+
372
403
  const violations = lint(skills_dir);
373
404
  if (violations.length === 0) {
374
405
  if (!QUIET) {
375
406
  process.stdout.write(
376
- `✅ lint_handoffs: no violations under ${_relTo(skills_dir, REPO)}\n`,
407
+ `✅ lint_handoffs: no violations under ${_relTo(skills_dir, REPO)} ` +
408
+ `(${scanned} skill file(s) scanned)\n`,
377
409
  );
378
410
  }
379
411
  return 0;
@@ -381,6 +413,21 @@ export function main(argv?: readonly string[]): number {
381
413
  for (const v of violations) {
382
414
  process.stdout.write(_render(v, REPO) + '\n');
383
415
  }
416
+ // Ratchet applies to the repo corpus only — an explicit fixture root is
417
+ // judged on its own findings, never against the repo's recorded debt.
418
+ if (isDefaultRoot) {
419
+ const verdict = checkRatchet({
420
+ gate: 'lint_handoffs',
421
+ actual: violations.length,
422
+ repoRoot: REPO,
423
+ });
424
+ if (verdict.ok) {
425
+ process.stdout.write(`\n⚠️ ${verdict.message}\n`);
426
+ return 0;
427
+ }
428
+ process.stderr.write(`\n❌ ${verdict.message}\n`);
429
+ return 1;
430
+ }
384
431
  process.stderr.write(`\n❌ lint_handoffs: ${violations.length} violation(s)\n`);
385
432
  return 1;
386
433
  }
@@ -6,35 +6,57 @@
6
6
  * Phase 4 / Wave 4b). The CLI contract is pinned — `--quiet`
7
7
  * flag read from argv at module load, scan roots + policy ordering (policies
8
8
  * sorted; scan files via rglob), finding messages, output channel (all on
9
- * stdout except the final orphan summary on stderr), exit codes. No
10
- * behaviour changes — historical quirks preserved (consumers pin the exact behaviour). (note: `POLICY_DIR` /
11
- * `SCAN_ROOTS` point at `agents/policies/media`, matching the .py).
9
+ * stdout except the final orphan summary on stderr), exit codes.
12
10
  *
13
- * Every policy file under `agents/policies/media/` (except README) must be
14
- * linked from at least one of a skill SKILL.md, a routing rule, or a sibling
15
- * policy file. A policy that no surface references is a silent — failed — policy.
11
+ * Every policy file under `agents/settings/policies/media/` (except README)
12
+ * must be linked from at least one of a skill SKILL.md, a routing rule, or a
13
+ * sibling policy file. A policy that no surface references is a silent —
14
+ * failed — policy.
16
15
  *
17
- * Exit codes: 0 all policies linked, 1 one or more orphan policies.
16
+ * The inherited `POLICY_DIR` and `SCAN_ROOTS` were faithful ports of the .py's
17
+ * paths, and every one of them was stale: the policy root was a level too
18
+ * shallow and the referrer roots named the pre-ADR-051 container. Repaired
19
+ * 2026-08-02; see the constants below.
20
+ *
21
+ * Exit codes: 0 all policies linked, 1 one or more orphan policies, 2 dead scope.
18
22
  */
19
23
 
20
24
  import * as fs from 'node:fs';
21
25
  import * as path from 'node:path';
22
26
  import { fileURLToPath, pathToFileURL } from 'node:url';
23
27
 
28
+ import { DeadScopeError, assertScanned } from './_lib/scan_scope.js';
29
+
24
30
  const QUIET = process.argv.includes('--quiet');
25
31
 
26
32
  const _HERE = fileURLToPath(import.meta.url);
27
33
  const REPO = path.resolve(path.dirname(_HERE), '..', '..');
28
- const POLICY_DIR = path.join(REPO, 'agents', 'policies', 'media');
34
+ /**
35
+ * The policies whose reachability is being checked.
36
+ *
37
+ * This named `agents/policies/media` while the policies have always lived one
38
+ * level deeper, under `agents/settings/policies/media` — the gate's own
39
+ * "…/settings/policies/media/ missing" message named the right path while the
40
+ * constant named the wrong one. Result: `main()` took the missing-dir early
41
+ * return and exited 0 without checking a single policy.
42
+ */
43
+ const POLICY_DIR = path.join(REPO, 'agents', 'settings', 'policies', 'media');
29
44
  const EXEMPT_STEMS: ReadonlySet<string> = new Set(['README']);
30
45
 
31
- // Surfaces scanned for inbound references to policy files.
46
+ /**
47
+ * Surfaces scanned for inbound references to policy files.
48
+ *
49
+ * Three of these named the pre-ADR-051 source container and a fourth named
50
+ * `.claude/skills`, a generated projection absent from this repo — so the
51
+ * reference scan read nothing either. Now the real authoring roots (a command
52
+ * is `src/domains/<pack>/<subpath>/command.md`, hence the domains root) plus
53
+ * the policy directory itself, since a sibling policy is a valid referrer.
54
+ */
32
55
  const SCAN_ROOTS: readonly string[] = [
33
- path.join(REPO, '.agent-src.uncondensed', 'skills'),
34
- path.join(REPO, '.agent-src.uncondensed', 'rules'),
35
- path.join(REPO, '.agent-src.uncondensed', 'commands'),
36
- path.join(REPO, '.claude', 'skills'),
37
- path.join(REPO, 'agents', 'policies', 'media'),
56
+ path.join(REPO, 'src', 'skills'),
57
+ path.join(REPO, 'src', 'rules'),
58
+ path.join(REPO, 'src', 'domains'),
59
+ POLICY_DIR,
38
60
  ];
39
61
 
40
62
  function _exists(p: string): boolean {
@@ -165,24 +187,34 @@ function referrers_for(policy: string, scanFiles: readonly string[]): string[] {
165
187
  }
166
188
 
167
189
  function main(): number {
168
- if (!_exists(POLICY_DIR)) {
169
- emit(
170
- 'media-policy-linkage: agents/settings/policies/media/ missing — ' +
171
- 'nothing to lint.',
172
- );
173
- return 0;
174
- }
175
-
190
+ // Scope assertions replace the two silent early returns this gate used to
191
+ // take. Both are deliberately WITHOUT `allowEmpty`: "the policy directory
192
+ // is missing" was not a benign optional-surface case here, it was the bug —
193
+ // the constant pointed one level too shallow and the gate green-lit an
194
+ // unchecked policy set for as long as it has existed.
176
195
  const policies = collect_policies();
177
- if (policies.length === 0) {
178
- emit(
179
- 'media-policy-linkage: agents/settings/policies/media/ has no policy ' +
180
- 'files — nothing to lint.',
181
- );
182
- return 0;
196
+ const scanFiles = collect_scan_files();
197
+ try {
198
+ assertScanned({
199
+ gate: 'lint_media_policy_linkage',
200
+ scanned: policies.length,
201
+ units: 'policy file(s)',
202
+ roots: [_relToPosix(POLICY_DIR, REPO)],
203
+ });
204
+ assertScanned({
205
+ gate: 'lint_media_policy_linkage',
206
+ scanned: scanFiles.length,
207
+ units: 'referrer-candidate file(s)',
208
+ roots: SCAN_ROOTS.map((r) => _relToPosix(r, REPO)),
209
+ });
210
+ } catch (exc) {
211
+ if (!(exc instanceof DeadScopeError)) {
212
+ throw exc;
213
+ }
214
+ process.stderr.write(`❌ ${exc.message}\n`);
215
+ return 2;
183
216
  }
184
217
 
185
- const scanFiles = collect_scan_files();
186
218
  const orphans: string[] = [];
187
219
  for (const policy of policies) {
188
220
  const referrers = referrers_for(policy, scanFiles);
@@ -209,7 +241,7 @@ function main(): number {
209
241
 
210
242
  emit(
211
243
  `media-policy-linkage: ${policies.length} policy file(s) — all ` +
212
- `linked.`,
244
+ `linked (${scanFiles.length} file(s) scanned for referrers).`,
213
245
  );
214
246
  return 0;
215
247
  }
@@ -18,10 +18,18 @@ import * as fs from 'node:fs';
18
18
  import * as path from 'node:path';
19
19
  import { fileURLToPath, pathToFileURL } from 'node:url';
20
20
 
21
+ import {
22
+ SRC_AGENT,
23
+ SRC_RULES,
24
+ SRC_SKILLS,
25
+ iter_commands,
26
+ logical_relpath,
27
+ } from './_lib/agent_src.js';
28
+ import { DeadScopeError, assertScanned } from './_lib/scan_scope.js';
29
+
21
30
  const _HERE = fileURLToPath(import.meta.url);
22
31
 
23
32
  const ROOT = path.resolve(path.dirname(_HERE), '..', '..');
24
- const SRC = path.join(ROOT, '.agent-src.uncondensed');
25
33
 
26
34
  // Source-of-truth regex; mirrored in docs/contracts/namespace.md § 1.
27
35
  const NAME_RE = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/;
@@ -44,14 +52,65 @@ interface Target {
44
52
  subVerb: boolean;
45
53
  }
46
54
 
55
+ /**
56
+ * ADR-051 moved every one of these roots out of the pre-ADR-051 source
57
+ * container. Until 2026-08-02 the table still named it, so `scan()` skipped
58
+ * all four `_isDir` checks and reported `BASELINE: 0 issues · 0 name(s)
59
+ * checked` — a green verdict over an empty corpus.
60
+ *
61
+ * Commands are NOT in this table: they no longer live in one flat directory.
62
+ * `src/domains/<pack>/<subpath>/command.md` is walked by `_command_targets()`
63
+ * below, which reconstructs the same name/sub-verb semantics from the logical
64
+ * command path.
65
+ */
47
66
  const TARGETS: Target[] = [
48
- { kind: 'skill', root: path.join(SRC, 'skills'), glob: '*/SKILL.md', depth: 1, subVerb: false },
49
- { kind: 'rule', root: path.join(SRC, 'rules'), glob: '*.md', depth: 0, subVerb: false },
50
- { kind: 'command', root: path.join(SRC, 'commands'), glob: '*.md', depth: 0, subVerb: false },
51
- { kind: 'command', root: path.join(SRC, 'commands'), glob: '*/*.md', depth: 0, subVerb: true },
52
- { kind: 'persona', root: path.join(SRC, 'personas'), glob: '*.md', depth: 0, subVerb: false },
67
+ { kind: 'skill', root: SRC_SKILLS(), glob: '*/SKILL.md', depth: 1, subVerb: false },
68
+ { kind: 'rule', root: SRC_RULES(), glob: '*.md', depth: 0, subVerb: false },
69
+ {
70
+ kind: 'persona',
71
+ root: path.join(SRC_AGENT(), 'personas'),
72
+ glob: '*.md',
73
+ depth: 0,
74
+ subVerb: false,
75
+ },
53
76
  ];
54
77
 
78
+ /** One command name to check, plus the physical file that carries it. */
79
+ interface CommandTarget {
80
+ fullPath: string;
81
+ name: string;
82
+ subVerb: boolean;
83
+ }
84
+
85
+ /**
86
+ * Command names, reconstructed from the `src/domains` layout.
87
+ *
88
+ * The retired flat layout globbed `commands/*.md` (top-level verb, sub_verb
89
+ * false) and `commands/​*​/*.md` (sub-verb, sub_verb true — reserved words are
90
+ * legal there). The logical path of a domains command is the same
91
+ * `commands/<subpath>.md`, so the mapping is exact: the LAST segment is the
92
+ * name, and anything nested below the top level is a sub-verb.
93
+ */
94
+ function _command_targets(): CommandTarget[] {
95
+ const out: CommandTarget[] = [];
96
+ for (const physical of iter_commands()) {
97
+ let logical: string;
98
+ try {
99
+ logical = logical_relpath(physical);
100
+ } catch {
101
+ continue;
102
+ }
103
+ const segments = logical.slice('commands/'.length).replace(/\.md$/, '').split('/');
104
+ const name = segments[segments.length - 1] as string;
105
+ if (!name) {
106
+ continue;
107
+ }
108
+ out.push({ fullPath: physical, name, subVerb: segments.length > 1 });
109
+ }
110
+ out.sort((a, b) => (a.fullPath < b.fullPath ? -1 : a.fullPath > b.fullPath ? 1 : 0));
111
+ return out;
112
+ }
113
+
55
114
  function _isDir(p: string): boolean {
56
115
  try {
57
116
  return fs.statSync(p).isDirectory();
@@ -205,6 +264,14 @@ function scan(): [number, number] {
205
264
  }
206
265
  }
207
266
  }
267
+ for (const { fullPath, name, subVerb } of _command_targets()) {
268
+ checked += 1;
269
+ for (const e of _shape_errors(name, subVerb, 'command')) {
270
+ const rel2 = path.relative(ROOT, fullPath).split(path.sep).join('/');
271
+ process.stderr.write(`❌ ${rel2}: ${e}\n`);
272
+ issues += 1;
273
+ }
274
+ }
208
275
  return [checked, issues];
209
276
  }
210
277
 
@@ -261,6 +328,25 @@ function main(argv?: readonly string[]): number {
261
328
  return check_single(args.name);
262
329
  }
263
330
  const [checked, issues] = scan();
331
+ // Scope assertion: 0 names checked means every root moved, not that every
332
+ // name is valid. That was this gate's shipped state until 2026-08-02.
333
+ try {
334
+ assertScanned({
335
+ gate: 'lint_namespace',
336
+ scanned: checked,
337
+ units: 'name(s)',
338
+ roots: [
339
+ ...TARGETS.map((t) => path.relative(ROOT, t.root).split(path.sep).join('/')),
340
+ 'src/domains/**/command.md',
341
+ ],
342
+ });
343
+ } catch (exc) {
344
+ if (!(exc instanceof DeadScopeError)) {
345
+ throw exc;
346
+ }
347
+ process.stderr.write(`❌ ${exc.message}\n`);
348
+ return 2;
349
+ }
264
350
  if (issues) {
265
351
  process.stderr.write(`BASELINE: ${issues} issue(s) across ${checked} name(s)\n`);
266
352
  return 1;
@@ -299,7 +385,6 @@ if (_isCliEntry() || process.argv[1] === _HERE) {
299
385
 
300
386
  export {
301
387
  ROOT,
302
- SRC,
303
388
  NAME_RE,
304
389
  RESERVED,
305
390
  NON_ARTEFACTS,
@@ -33,6 +33,9 @@ import { fileURLToPath, pathToFileURL } from 'node:url';
33
33
 
34
34
  import YAML from 'yaml';
35
35
 
36
+ import { SRC_SKILLS } from './_lib/agent_src.js';
37
+ import { DeadScopeError, assertScanned } from './_lib/scan_scope.js';
38
+
36
39
  const _HERE = fileURLToPath(import.meta.url);
37
40
  // Path(__file__).resolve().parent.parent.parent — repo root (three dirs up).
38
41
  const ROOT = path.resolve(path.dirname(_HERE), '..', '..');
@@ -86,36 +89,19 @@ function _exists(p: string): boolean {
86
89
  }
87
90
  }
88
91
 
89
- /** Mirror `_skill_roots()`. */
92
+ /**
93
+ * Mirror `_skill_roots()`.
94
+ *
95
+ * ADR-051 moved skill authoring to `src/skills`; a later commit deleted the
96
+ * per-package source trees this used to enumerate FIRST. That preferred branch
97
+ * has resolved to [] on every checkout since, so the 2026-07-29 repair's
98
+ * `src/skills` fallback is the only one that has ever fired. The dead
99
+ * preference is removed rather than left as a trap that would silently take
100
+ * precedence again if a `packages/` directory reappeared for another reason.
101
+ */
90
102
  function _skill_roots(): string[] {
91
- const pkgs = path.join(ROOT, 'packages');
92
- let roots: string[] = [];
93
- if (_isDir(pkgs)) {
94
- let entries: string[];
95
- try {
96
- entries = fs.readdirSync(pkgs);
97
- } catch {
98
- entries = [];
99
- }
100
- entries.sort();
101
- for (const name of entries) {
102
- const cand = path.join(pkgs, name, '.agent-src.uncondensed', 'skills');
103
- if (_isDir(cand)) {
104
- roots.push(cand);
105
- }
106
- }
107
- }
108
- // ADR-051 moved skill authoring to `src/skills`; a later commit deleted
109
- // `packages/` entirely. Until 2026-07-29 the only fallback was the retired
110
- // container, so this resolved to [] and the overlap gate silently passed
111
- // every new skill.
112
- if (roots.length === 0) {
113
- const cand = path.join(ROOT, 'src', 'skills');
114
- if (_isDir(cand)) {
115
- roots = [cand];
116
- }
117
- }
118
- return roots;
103
+ const cand = SRC_SKILLS();
104
+ return _isDir(cand) ? [cand] : [];
119
105
  }
120
106
 
121
107
  /** Mirror `_keyword_vector(text)`. */
@@ -378,16 +364,38 @@ export function main(argv?: string[]): number {
378
364
  }
379
365
  const { baseline, quiet } = parsed;
380
366
 
367
+ // Scope assertion FIRST, before the no-new-skills early return: the dedupe
368
+ // half of this gate compares a new skill against the existing corpus, so an
369
+ // empty corpus does not mean "nothing to check", it means every new skill
370
+ // is waved through as unique. Asserting only on the diff would miss that.
371
+ const allSkills = collect();
372
+ try {
373
+ assertScanned({
374
+ gate: 'lint_new_skill_gate',
375
+ scanned: allSkills.length,
376
+ units: 'existing skill(s)',
377
+ roots: _skill_roots().map((r) => path.relative(ROOT, r).split(path.sep).join('/')),
378
+ });
379
+ } catch (exc) {
380
+ if (!(exc instanceof DeadScopeError)) {
381
+ throw exc;
382
+ }
383
+ process.stderr.write(`❌ ${exc.message}\n`);
384
+ return 2;
385
+ }
386
+
381
387
  const added = added_skill_files(baseline);
382
388
  if (added.length === 0) {
383
389
  if (!quiet) {
384
- process.stdout.write(`✅ No new skills added (baseline: ${baseline}).\n`);
390
+ process.stdout.write(
391
+ `✅ No new skills added (baseline: ${baseline}; ` +
392
+ `${allSkills.length} existing skill(s) scanned).\n`,
393
+ );
385
394
  }
386
395
  return 0;
387
396
  }
388
397
 
389
398
  const addedSet = new Set(added);
390
- const allSkills = collect();
391
399
  const existing = allSkills.filter((s) => !addedSet.has(s.relpath));
392
400
 
393
401
  const violations: string[] = [];
@@ -104,7 +104,13 @@ function _collect(): Doc[] {
104
104
  push(`skill:${d.name}`, path.join(skillsDir, d.name, 'SKILL.md'));
105
105
  }
106
106
  }
107
- const personasDir = path.join(REPO, 'src', 'personas');
107
+ // `src/agent-src/personas`, not `src/personas` — the latter has never existed
108
+ // since ADR-051 moved the container, so the `existsSync` guard below silently
109
+ // dropped all 30 personas from a comparison whose own header promises to cover
110
+ // them. Same dead-root class the rest of this roadmap repairs; it escaped the
111
+ // class-A sweep only because the literal does not carry a retired-container
112
+ // prefix (road-to-gates-that-can-fail Phase 1).
113
+ const personasDir = path.join(REPO, 'src', 'agent-src', 'personas');
108
114
  if (fs.existsSync(personasDir)) {
109
115
  const walk = (dir: string, prefix: string): void => {
110
116
  for (const d of fs.readdirSync(dir, { withFileTypes: true })) {
@@ -65,10 +65,28 @@ export interface OverrideRow {
65
65
  violations: string[];
66
66
  }
67
67
 
68
- /** `**Mode:** \`extend\`` — the header form the override contract specifies. */
68
+ /**
69
+ * `**Mode:** \`extend\`` — the header form the override contract specifies.
70
+ *
71
+ * Reads EVERY `**Mode:**` line, not the first. A non-global `exec` took
72
+ * whichever declaration appeared earliest, and the override contract doc itself
73
+ * carries two example `**Mode:**` lines — so a file whose real declaration is
74
+ * `replace` could be read as `extend` from an example sitting above it. On a
75
+ * safety-floor rule that is the difference between a blocked override and a
76
+ * silently permitted one, which makes first-match-wins a bypass rather than a
77
+ * parsing nit (road-to-gates-that-can-fail Phase 6.2, finding 5).
78
+ *
79
+ * Disagreeing declarations resolve to `unknown`, which the caller already
80
+ * treats as a violation — fail closed. A guard that cannot tell which mode a
81
+ * file declares must say so, never pick one.
82
+ */
69
83
  export function parse_mode(text: string): 'extend' | 'replace' | 'unknown' {
70
- const m = /^\s*\*\*Mode:\*\*\s*`?(extend|replace)`?\s*$/im.exec(text);
71
- return m ? (m[1] as 'extend' | 'replace') : 'unknown';
84
+ const found = new Set<string>();
85
+ const re = /^\s*\*\*Mode:\*\*\s*`?(extend|replace)`?\s*$/gim;
86
+ let m: RegExpExecArray | null;
87
+ while ((m = re.exec(text)) !== null) found.add(m[1] as string);
88
+ if (found.size !== 1) return 'unknown';
89
+ return [...found][0] as 'extend' | 'replace';
72
90
  }
73
91
 
74
92
  /** `> Overrides: <rule> §<section> — <reason>` — the citation obligation. */