@event4u/agent-config 8.13.0 → 9.1.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 (316) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/CHANGELOG.md +183 -0
  3. package/MIGRATION.md +14 -0
  4. package/README.md +2 -2
  5. package/dist/agent-src/commands/agent-handoff.md +35 -0
  6. package/dist/agent-src/commands/agents/init.md +1 -0
  7. package/dist/agent-src/commands/agents/optimize.md +1 -0
  8. package/dist/agent-src/commands/agents/user/accept.md +3 -2
  9. package/dist/agent-src/commands/agents/user/init.md +5 -4
  10. package/dist/agent-src/commands/agents/user/update.md +3 -2
  11. package/dist/agent-src/commands/agents/user.md +1 -0
  12. package/dist/agent-src/commands/agents.md +1 -0
  13. package/dist/agent-src/commands/analytics/show.md +1 -0
  14. package/dist/agent-src/commands/analytics.md +1 -0
  15. package/dist/agent-src/commands/analyze/decision.md +1 -0
  16. package/dist/agent-src/commands/analyze/incident.md +1 -0
  17. package/dist/agent-src/commands/analyze/near-miss.md +1 -0
  18. package/dist/agent-src/commands/analyze/postmortem.md +1 -0
  19. package/dist/agent-src/commands/analyze/premortem.md +1 -0
  20. package/dist/agent-src/commands/analyze/reference-repo.md +1 -0
  21. package/dist/agent-src/commands/analyze.md +3 -2
  22. package/dist/agent-src/commands/brand/identity.md +6 -5
  23. package/dist/agent-src/commands/brand/review.md +6 -5
  24. package/dist/agent-src/commands/brand/strategy.md +6 -4
  25. package/dist/agent-src/commands/brand/tokens.md +7 -6
  26. package/dist/agent-src/commands/brand/voice.md +5 -4
  27. package/dist/agent-src/commands/brand.md +9 -7
  28. package/dist/agent-src/commands/bug/fix.md +1 -0
  29. package/dist/agent-src/commands/bug/investigate.md +1 -0
  30. package/dist/agent-src/commands/bug.md +2 -1
  31. package/dist/agent-src/commands/challenge-me/vision.md +1 -0
  32. package/dist/agent-src/commands/challenge-me/with-docs.md +1 -0
  33. package/dist/agent-src/commands/challenge-me.md +28 -6
  34. package/dist/agent-src/commands/chat-history.md +1 -0
  35. package/dist/agent-src/commands/check-current-md.md +1 -0
  36. package/dist/agent-src/commands/commit.md +1 -0
  37. package/dist/agent-src/commands/context/create.md +3 -2
  38. package/dist/agent-src/commands/context.md +1 -0
  39. package/dist/agent-src/commands/cost/profile.md +1 -0
  40. package/dist/agent-src/commands/cost.md +2 -1
  41. package/dist/agent-src/commands/council/analysis.md +1 -0
  42. package/dist/agent-src/commands/council/debate.md +1 -0
  43. package/dist/agent-src/commands/council/default.md +21 -20
  44. package/dist/agent-src/commands/council/design.md +1 -0
  45. package/dist/agent-src/commands/council/optimize.md +1 -0
  46. package/dist/agent-src/commands/council/pr.md +1 -0
  47. package/dist/agent-src/commands/council.md +1 -0
  48. package/dist/agent-src/commands/estimate-ticket.md +1 -0
  49. package/dist/agent-src/commands/explain-run.md +88 -0
  50. package/dist/agent-src/commands/feature/dev.md +1 -0
  51. package/dist/agent-src/commands/feature/explore.md +1 -0
  52. package/dist/agent-src/commands/feature/plan.md +1 -0
  53. package/dist/agent-src/commands/feature/refactor.md +1 -0
  54. package/dist/agent-src/commands/feature/roadmap.md +1 -0
  55. package/dist/agent-src/commands/feature.md +2 -1
  56. package/dist/agent-src/commands/fix/pr-comments.md +19 -0
  57. package/dist/agent-src/commands/fix.md +2 -1
  58. package/dist/agent-src/commands/ghostwriter/delete.md +1 -0
  59. package/dist/agent-src/commands/ghostwriter/fetch.md +1 -0
  60. package/dist/agent-src/commands/ghostwriter/show.md +1 -0
  61. package/dist/agent-src/commands/ghostwriter/write.md +1 -0
  62. package/dist/agent-src/commands/ghostwriter.md +1 -0
  63. package/dist/agent-src/commands/grill-me.md +17 -12
  64. package/dist/agent-src/commands/humanize.md +1 -0
  65. package/dist/agent-src/commands/image/analyse.md +2 -1
  66. package/dist/agent-src/commands/image/create.md +1 -0
  67. package/dist/agent-src/commands/image/verify.md +1 -0
  68. package/dist/agent-src/commands/image.md +16 -12
  69. package/dist/agent-src/commands/implement-ticket.md +1 -0
  70. package/dist/agent-src/commands/judge/on-diff.md +1 -0
  71. package/dist/agent-src/commands/judge/solo.md +1 -0
  72. package/dist/agent-src/commands/judge/steps.md +1 -0
  73. package/dist/agent-src/commands/judge.md +1 -0
  74. package/dist/agent-src/commands/knowledge/cross-repo.md +1 -0
  75. package/dist/agent-src/commands/knowledge/forget.md +1 -0
  76. package/dist/agent-src/commands/knowledge/ingest.md +1 -0
  77. package/dist/agent-src/commands/knowledge/list.md +10 -0
  78. package/dist/agent-src/commands/knowledge.md +1 -0
  79. package/dist/agent-src/commands/memory/learn-low-impact.md +1 -0
  80. package/dist/agent-src/commands/memory/mine-session.md +1 -0
  81. package/dist/agent-src/commands/memory/promote.md +3 -2
  82. package/dist/agent-src/commands/memory.md +1 -0
  83. package/dist/agent-src/commands/mission/upgrade.md +45 -24
  84. package/dist/agent-src/commands/mode.md +1 -0
  85. package/dist/agent-src/commands/module/create.md +1 -0
  86. package/dist/agent-src/commands/module/explore.md +1 -0
  87. package/dist/agent-src/commands/module.md +1 -0
  88. package/dist/agent-src/commands/optimize/agents-dir.md +1 -0
  89. package/dist/agent-src/commands/optimize/project.md +1 -0
  90. package/dist/agent-src/commands/optimize/prompt.md +1 -0
  91. package/dist/agent-src/commands/optimize.md +2 -1
  92. package/dist/agent-src/commands/orchestrate.md +1 -0
  93. package/dist/agent-src/commands/override.md +1 -0
  94. package/dist/agent-src/commands/package.md +2 -1
  95. package/dist/agent-src/commands/post-as/ghostwriter.md +1 -0
  96. package/dist/agent-src/commands/post-as/me.md +1 -0
  97. package/dist/agent-src/commands/post-as.md +1 -0
  98. package/dist/agent-src/commands/pr/create/description-only.md +139 -8
  99. package/dist/agent-src/commands/pr/create.md +35 -22
  100. package/dist/agent-src/commands/prediction-pool.md +7 -6
  101. package/dist/agent-src/commands/prepare-for-review.md +1 -0
  102. package/dist/agent-src/commands/profile/activate.md +1 -0
  103. package/dist/agent-src/commands/profile/deactivate.md +1 -0
  104. package/dist/agent-src/commands/profile.md +1 -0
  105. package/dist/agent-src/commands/project.md +2 -1
  106. package/dist/agent-src/commands/refine-ticket.md +7 -0
  107. package/dist/agent-src/commands/research/deep.md +1 -0
  108. package/dist/agent-src/commands/research.md +1 -0
  109. package/dist/agent-src/commands/review/routing.md +1 -0
  110. package/dist/agent-src/commands/review.md +2 -1
  111. package/dist/agent-src/commands/roadmap/ai-council.md +1 -0
  112. package/dist/agent-src/commands/roadmap/materialize.md +1 -0
  113. package/dist/agent-src/commands/roadmap/process-full.md +1 -0
  114. package/dist/agent-src/commands/roadmap/process-phase.md +1 -0
  115. package/dist/agent-src/commands/roadmap/process-step.md +1 -0
  116. package/dist/agent-src/commands/roadmap.md +1 -0
  117. package/dist/agent-src/commands/security-audit-config.md +2 -1
  118. package/dist/agent-src/commands/skill/preview.md +1 -0
  119. package/dist/agent-src/commands/skill.md +1 -0
  120. package/dist/agent-src/commands/skills/discover.md +1 -0
  121. package/dist/agent-src/commands/skills.md +1 -0
  122. package/dist/agent-src/commands/sync/agent-settings.md +1 -0
  123. package/dist/agent-src/commands/sync.md +2 -1
  124. package/dist/agent-src/commands/team/adversarial.md +3 -2
  125. package/dist/agent-src/commands/team/delegate.md +3 -2
  126. package/dist/agent-src/commands/team/review.md +3 -2
  127. package/dist/agent-src/commands/team/status.md +49 -18
  128. package/dist/agent-src/commands/team-knowledge.md +8 -6
  129. package/dist/agent-src/commands/team.md +21 -16
  130. package/dist/agent-src/commands/tests/e2e-heal.md +1 -0
  131. package/dist/agent-src/commands/tests/e2e-plan.md +1 -0
  132. package/dist/agent-src/commands/tests/execute.md +1 -0
  133. package/dist/agent-src/commands/tests.md +2 -1
  134. package/dist/agent-src/commands/update-form-request-messages.md +1 -0
  135. package/dist/agent-src/commands/upstream-contribute.md +1 -0
  136. package/dist/agent-src/commands/video/from-script.md +3 -2
  137. package/dist/agent-src/commands/video/from-song.md +22 -21
  138. package/dist/agent-src/commands/video/scene.md +1 -0
  139. package/dist/agent-src/commands/video/stitch.md +2 -1
  140. package/dist/agent-src/commands/video/storyboard.md +1 -0
  141. package/dist/agent-src/commands/video.md +1 -0
  142. package/dist/agent-src/commands/work.md +1 -0
  143. package/dist/agent-src/commands/worktree/cleanup.md +8 -7
  144. package/dist/agent-src/commands/worktree/create.md +1 -0
  145. package/dist/agent-src/commands/worktree/verify.md +1 -0
  146. package/dist/agent-src/commands/worktree.md +1 -0
  147. package/dist/agent-src/rules/active-remediation.md +12 -36
  148. package/dist/agent-src/rules/artifact-drafting-protocol.md +7 -64
  149. package/dist/agent-src/rules/context-hygiene.md +19 -113
  150. package/dist/agent-src/rules/design-fidelity.md +4 -60
  151. package/dist/agent-src/rules/domain-adoption-policy.md +10 -106
  152. package/dist/agent-src/rules/framework-neutrality-in-generic-skills.md +12 -60
  153. package/dist/agent-src/rules/improve-before-implement.md +4 -0
  154. package/dist/agent-src/rules/minimal-safe-diff.md +6 -86
  155. package/dist/agent-src/rules/size-enforcement.md +10 -0
  156. package/dist/agent-src/rules/tool-safety.md +17 -3
  157. package/dist/agent-src/skills/agent-security-review/SKILL.md +12 -1
  158. package/dist/agent-src/skills/ai-council/SKILL.md +11 -0
  159. package/dist/agent-src/skills/bug-analyzer/SKILL.md +24 -2
  160. package/dist/agent-src/skills/code-review/SKILL.md +105 -93
  161. package/dist/agent-src/skills/code-review/checklists/backend.md +50 -0
  162. package/dist/agent-src/skills/code-review/checklists/database.md +19 -0
  163. package/dist/agent-src/skills/code-review/checklists/dependency.md +18 -0
  164. package/dist/agent-src/skills/code-review/checklists/docs.md +14 -0
  165. package/dist/agent-src/skills/code-review/checklists/frontend.md +19 -0
  166. package/dist/agent-src/skills/code-review/checklists/infra.md +17 -0
  167. package/dist/agent-src/skills/code-review/checklists/producing-the-review.md +62 -0
  168. package/dist/agent-src/skills/code-review/checklists/test-only.md +13 -0
  169. package/dist/agent-src/skills/code-review/evals/evals.json +53 -15
  170. package/dist/agent-src/skills/condense-memory/SKILL.md +3 -2
  171. package/dist/agent-src/skills/conventional-commits-writing/evals/evals.json +15 -0
  172. package/dist/agent-src/skills/docker/SKILL.md +10 -0
  173. package/dist/agent-src/skills/docx-authoring/SKILL.md +102 -0
  174. package/dist/agent-src/skills/github-ci/evals/evals.json +25 -0
  175. package/dist/agent-src/skills/humanizer/SKILL.md +79 -3
  176. package/dist/agent-src/skills/humanizer/references/anti-aiisms.md +112 -0
  177. package/dist/agent-src/skills/humanizer/references/fixtures.md +70 -0
  178. package/dist/agent-src/skills/judge-bug-hunter/SKILL.md +7 -1
  179. package/dist/agent-src/skills/laravel/SKILL.md +10 -0
  180. package/dist/agent-src/skills/launch-readiness/SKILL.md +35 -0
  181. package/dist/agent-src/skills/llm-provider-knowledge/SKILL.md +146 -0
  182. package/dist/agent-src/skills/llm-provider-knowledge/evals/triggers.json +20 -0
  183. package/dist/agent-src/skills/merge-conflicts/SKILL.md +30 -0
  184. package/dist/agent-src/skills/pdf-tools/SKILL.md +101 -0
  185. package/dist/agent-src/skills/quality-tools/evals/evals.json +15 -0
  186. package/dist/agent-src/skills/security-audit/SKILL.md +29 -0
  187. package/dist/agent-src/skills/security-maturity-assessment/SKILL.md +90 -0
  188. package/dist/agent-src/skills/security-maturity-assessment/evals/triggers.json +52 -0
  189. package/dist/agent-src/skills/skill-writing/SKILL.md +79 -0
  190. package/dist/agent-src/skills/spreadsheet-authoring/SKILL.md +11 -3
  191. package/dist/agent-src/skills/subagent-orchestration/prompts/README.md +11 -0
  192. package/dist/agent-src/skills/subagent-orchestration/schemas/team-review-status.json +103 -0
  193. package/dist/agent-src/skills/terraform/SKILL.md +10 -0
  194. package/dist/agent-src/skills/test-driven-development/SKILL.md +31 -0
  195. package/dist/agent-src/skills/threat-modeling/SKILL.md +15 -0
  196. package/dist/agent-src/skills/token-optimizer/SKILL.md +10 -4
  197. package/dist/agent-src/templates/agent-settings.md +28 -0
  198. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  199. package/dist/agent-src/templates/scripts/telemetry/aggregator.ts +0 -0
  200. package/dist/agent-src/templates/scripts/telemetry/engagement.ts +30 -0
  201. package/dist/agent-src/templates/scripts/telemetry/report_renderer.ts +22 -9
  202. package/dist/agent-src/templates/scripts/telemetry_record.ts +5 -0
  203. package/dist/agent-src/templates/scripts/work_engine/_lib/agent_settings.ts +4 -1
  204. package/dist/discovery/deprecation-report.md +1 -1
  205. package/dist/discovery/discovery-manifest.json +314 -194
  206. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  207. package/dist/discovery/discovery-manifest.summary.md +5 -5
  208. package/dist/discovery/orphan-report.md +1 -1
  209. package/dist/discovery/packs.json +23 -18
  210. package/dist/discovery/trust-report.md +8 -8
  211. package/dist/discovery/workspaces.json +18 -13
  212. package/dist/install/install.mjs +48 -33
  213. package/dist/mcp/registry-manifest.json +3 -3
  214. package/dist/router.json +1 -1
  215. package/dist/scripts/_lib/agent_settings.js +4 -1
  216. package/dist/scripts/_lib/agent_settings.js.map +1 -1
  217. package/dist/scripts/condense.js +5 -0
  218. package/dist/scripts/condense.js.map +1 -1
  219. package/dist/server/schemas/settings.js +10 -1
  220. package/dist/server/schemas/settings.js.map +1 -1
  221. package/docs/CLAIMS.md +9 -2
  222. package/docs/MIGRATION.md +20 -0
  223. package/docs/SKILL_CENSUS.md +8 -0
  224. package/docs/architecture.md +3 -3
  225. package/docs/benchmark.md +67 -2
  226. package/docs/catalog.md +2 -2
  227. package/docs/command-flows.md +1 -1
  228. package/docs/contracts/agents-layout.md +1 -1
  229. package/docs/contracts/ai-council-config.md +9 -2
  230. package/docs/contracts/ai-team-config.md +37 -4
  231. package/docs/contracts/consumer-bridge.md +55 -63
  232. package/docs/contracts/install-layout.md +1 -1
  233. package/docs/contracts/migrate-command.md +1 -1
  234. package/docs/contracts/prelaunch-diagnostics.md +151 -0
  235. package/docs/contracts/release-pr-gating.md +1 -0
  236. package/docs/customization.md +22 -1
  237. package/docs/decisions/ADR-020-global-only-consumer-scope.md +34 -0
  238. package/docs/decisions/ADR-049-configuration-trust-boundary.md +9 -1
  239. package/docs/featured-skills.md +2 -2
  240. package/docs/getting-started-by-role.md +1 -1
  241. package/docs/getting-started.md +17 -6
  242. package/docs/governance-advantage.md +1 -1
  243. package/docs/guidelines/agent-infra/active-remediation-mechanics.md +45 -0
  244. package/docs/guidelines/agent-infra/agent-interaction-and-decision-quality.md +28 -0
  245. package/docs/guidelines/agent-infra/api-cost-levers.md +126 -0
  246. package/docs/guidelines/agent-infra/artifact-drafting-protocol-mechanics.md +76 -0
  247. package/docs/guidelines/agent-infra/context-hygiene-mechanics.md +90 -0
  248. package/docs/guidelines/agent-infra/domain-adoption-gates.md +112 -0
  249. package/docs/guidelines/agent-infra/framework-neutrality-patterns.md +66 -0
  250. package/docs/guidelines/agent-infra/minimal-safe-diff-mechanics.md +99 -0
  251. package/docs/guidelines/agent-infra/skill-quality-checklist.md +13 -0
  252. package/docs/guidelines/design-fidelity-mechanics.md +76 -0
  253. package/docs/installation.md +2 -1
  254. package/docs/maintainers/dev-mode.md +3 -2
  255. package/docs/parity/command-frontmatter.md +60 -0
  256. package/docs/proof.md +7 -6
  257. package/package.json +1 -1
  258. package/src/agent-src/templates/scripts/telemetry/aggregator.ts +0 -0
  259. package/src/agent-src/templates/scripts/telemetry/engagement.ts +30 -0
  260. package/src/agent-src/templates/scripts/telemetry/report_renderer.ts +22 -9
  261. package/src/agent-src/templates/scripts/telemetry_record.ts +5 -0
  262. package/src/agent-src/templates/scripts/work_engine/_lib/agent_settings.ts +4 -1
  263. package/src/config/agent-settings.template.yml +73 -10
  264. package/src/config/gitignore-block.txt +16 -6
  265. package/src/config/prelaunch-areas.yml +36 -0
  266. package/src/scripts/_cli/cmd_conformance.ts +2 -2
  267. package/src/scripts/_cli/cmd_doctor.ts +280 -39
  268. package/src/scripts/_cli/cmd_refresh.ts +35 -118
  269. package/src/scripts/_lib/agent_settings.ts +4 -1
  270. package/src/scripts/_lib/claude_plugin.ts +77 -2
  271. package/src/scripts/_lib/orchestration_record.ts +5 -0
  272. package/src/scripts/_lib/tool_adapter_registry.ts +120 -0
  273. package/src/scripts/ai_council/clients.ts +68 -6
  274. package/src/scripts/ai_council/pricing.ts +62 -0
  275. package/src/scripts/ai_team/config.ts +73 -1
  276. package/src/scripts/ai_team/review_gate.ts +636 -0
  277. package/src/scripts/ai_team/team_dispatch.ts +612 -0
  278. package/src/scripts/bench_ab_task_runner.ts +11 -1
  279. package/src/scripts/bench_parity_count.ts +279 -0
  280. package/src/scripts/build_linear_digest.ts +3 -1
  281. package/src/scripts/check_bridge_derivation.ts +172 -0
  282. package/src/scripts/check_consumer_scope_flip.ts +179 -0
  283. package/src/scripts/check_council_config_location.ts +115 -15
  284. package/src/scripts/check_host_loadability.ts +81 -0
  285. package/src/scripts/condense.ts +5 -0
  286. package/src/scripts/council_cli.ts +19 -3
  287. package/src/scripts/hook_manifest.yaml +12 -1
  288. package/src/scripts/install.ts +44 -45
  289. package/src/scripts/lint_agents_layout.ts +10 -4
  290. package/src/scripts/lint_breaking_changes_index.ts +303 -0
  291. package/src/scripts/lint_handoffs.ts +42 -0
  292. package/src/scripts/lint_skill_descriptions.ts +228 -0
  293. package/src/scripts/lint_skill_descriptions_allowlist.json +1 -0
  294. package/src/scripts/lint_skill_scripts_readonly.ts +145 -0
  295. package/src/scripts/lint_skill_scripts_readonly_allowlist.json +8 -0
  296. package/src/scripts/lint_supported_tools_matrix.ts +35 -0
  297. package/src/scripts/mcp_server/tools.ts +1 -3
  298. package/src/scripts/optimize_skill_description.ts +144 -0
  299. package/src/scripts/orchestration_record.ts +2 -1
  300. package/src/scripts/prelaunch_diagnostics.ts +248 -0
  301. package/src/scripts/run_skill_evals.ts +70 -4
  302. package/src/scripts/schemas/agent-settings.schema.json +17 -0
  303. package/src/scripts/schemas/command.schema.json +7 -0
  304. package/src/scripts/schemas/evals.schema.json +105 -14
  305. package/src/scripts/schemas/prelaunch-report.schema.json +107 -0
  306. package/src/scripts/schemas/skill.schema.json +18 -1
  307. package/src/scripts/schemas/subagent.schema.json +6 -0
  308. package/src/scripts/self_review_gate.test.ts +11 -0
  309. package/src/scripts/self_review_gate.ts +7 -1
  310. package/src/scripts/skill_eval_coverage.ts +4 -1
  311. package/src/scripts/skill_linter.ts +43 -3
  312. package/src/scripts/smoke_host_loadability.sh +73 -0
  313. package/src/scripts/team_review_gate_hook.ts +115 -0
  314. package/src/scripts/trigger_eval_grandfather.json +1 -2
  315. package/src/scripts/utilization_report.ts +225 -0
  316. package/src/server/schemas/settings.ts +24 -1
@@ -0,0 +1,303 @@
1
+ #!/usr/bin/env tsx
2
+ /**
3
+ * BREAKING_CHANGES.md ↔ CHANGELOG sync linter (road-to-feedback-9.0 P1.4).
4
+ *
5
+ * `BREAKING_CHANGES.md` is the discoverable index of consumer-facing breaking
6
+ * changes, one per major (`X.0.0`). When a diff ships a NEW breaking entry in a
7
+ * RELEASED CHANGELOG section it must also touch the index in the same change,
8
+ * or the index silently rots behind the changelog.
9
+ *
10
+ * TRIGGER: a git diff (working tree vs `origin/main` — staged + unstaged) that
11
+ * ADDS a `### BREAKING` / `### BREAKING CHANGES` subheading under a RELEASED
12
+ * major section (`## [N.0.0]`) in `CHANGELOG.md`, where the same major had no
13
+ * such subheading at the base ref (a wholly-new released section counts). A
14
+ * breaking note under `## [Unreleased]` is WIP and never trips the gate.
15
+ *
16
+ * REQUIREMENT: when triggered, the same diff must also modify
17
+ * `BREAKING_CHANGES.md`.
18
+ *
19
+ * Escape hatch (mirrors `check_structural_breaking.ts`'s `ci-override` trailer):
20
+ * an own-line trailer `ci-override: breaking-index-override` (the bare token
21
+ * `breaking-index-override` on its own line is also accepted) in any commit
22
+ * body in the range, or the env var `BREAKING_INDEX_OVERRIDE=1`, clears the
23
+ * gate for an intentional index-deferred change.
24
+ *
25
+ * CLI: exit 0 clean/cleared, 1 violation (or selftest failure), 2 usage error.
26
+ * `--selftest` proves the rule on inline string fixtures (no git needed).
27
+ */
28
+ import { spawnSync } from 'node:child_process';
29
+ import * as fs from 'node:fs';
30
+ import * as path from 'node:path';
31
+ import { fileURLToPath, pathToFileURL } from 'node:url';
32
+
33
+ const _HERE = fileURLToPath(import.meta.url);
34
+ // src/scripts/lint_breaking_changes_index.ts → two levels up is the repo root.
35
+ const REPO_ROOT = path.resolve(path.dirname(_HERE), '..', '..');
36
+ const BASE_REF = 'origin/main';
37
+ const CHANGELOG_REL = 'CHANGELOG.md';
38
+ const INDEX_REL = 'BREAKING_CHANGES.md';
39
+
40
+ // A RELEASED major section heading: `## [N.0.0]…` (minor + patch both 0).
41
+ // `## [Unreleased]` never matches, so WIP breaking notes are out of scope.
42
+ const RELEASED_MAJOR_HEADING_RE = /^## \[(?<version>\d+\.0\.0)\]/;
43
+ // Any other level-1/2 heading closes the current section.
44
+ const SECTION_BOUNDARY_RE = /^##? /;
45
+ // A breaking subheading as emitted by release-please: `### BREAKING` or
46
+ // `### BREAKING CHANGES` (all-caps). Mixed-case `### Breaking changes` under
47
+ // Unreleased is intentionally NOT matched.
48
+ const BREAKING_SUBHEADING_RE = /^### BREAKING\b/;
49
+ // Override trailer — same shape as check_structural_breaking's `ci-override:`
50
+ // mechanism (own line, not an inline mention); the bare token is also accepted.
51
+ const OVERRIDE_RE = /^(?:ci-override:\s*)?breaking-index-override\s*$/m;
52
+
53
+ /**
54
+ * Return the set of RELEASED major versions whose CHANGELOG section contains a
55
+ * `### BREAKING` subheading.
56
+ */
57
+ export function released_breaking_majors(lines: readonly string[]): Set<string> {
58
+ const found = new Set<string>();
59
+ for (let i = 0; i < lines.length; i++) {
60
+ const m = RELEASED_MAJOR_HEADING_RE.exec(lines[i]!);
61
+ if (!m) {
62
+ continue;
63
+ }
64
+ const version = m.groups!['version']!;
65
+ // Section body: heading line down to (exclusive) the next boundary.
66
+ let end = lines.length;
67
+ for (let j = i + 1; j < lines.length; j++) {
68
+ if (SECTION_BOUNDARY_RE.test(lines[j]!)) {
69
+ end = j;
70
+ break;
71
+ }
72
+ }
73
+ if (lines.slice(i + 1, end).some((ln) => BREAKING_SUBHEADING_RE.test(ln))) {
74
+ found.add(version);
75
+ }
76
+ }
77
+ return found;
78
+ }
79
+
80
+ /**
81
+ * Core check. Returns the sorted list of RELEASED major versions that GAINED a
82
+ * `### BREAKING` subheading between `oldLines` and `newLines` — the versions
83
+ * that should be reflected in `BREAKING_CHANGES.md`.
84
+ */
85
+ export function added_released_breaking_majors(
86
+ oldLines: readonly string[],
87
+ newLines: readonly string[],
88
+ ): string[] {
89
+ const before = released_breaking_majors(oldLines);
90
+ const after = released_breaking_majors(newLines);
91
+ const added = [...after].filter((v) => !before.has(v));
92
+ added.sort();
93
+ return added;
94
+ }
95
+
96
+ export interface CheckResult {
97
+ readonly addedMajors: readonly string[];
98
+ readonly indexChanged: boolean;
99
+ readonly overridden: boolean;
100
+ readonly violation: boolean;
101
+ }
102
+
103
+ /** Pure decision: violation iff a released BREAKING was added, the index was not touched, and no override cleared it. */
104
+ export function evaluate(
105
+ oldLines: readonly string[],
106
+ newLines: readonly string[],
107
+ indexChanged: boolean,
108
+ overridden: boolean,
109
+ ): CheckResult {
110
+ const addedMajors = added_released_breaking_majors(oldLines, newLines);
111
+ const violation = addedMajors.length > 0 && !indexChanged && !overridden;
112
+ return { addedMajors, indexChanged, overridden, violation };
113
+ }
114
+
115
+ // ─── git helpers (CLI only) ──────────────────────────────────────────────────
116
+
117
+ function _git(...args: string[]): string {
118
+ const proc = spawnSync('git', args, { cwd: REPO_ROOT, encoding: 'utf-8' });
119
+ return (proc.stdout as string | null) ?? '';
120
+ }
121
+
122
+ function _base(): string {
123
+ const mb = _git('merge-base', BASE_REF, 'HEAD').trim();
124
+ return mb || BASE_REF;
125
+ }
126
+
127
+ function _splitlines(s: string): string[] {
128
+ return s.split(/\r\n|\r|\n/);
129
+ }
130
+
131
+ /** File contents at a ref, or '' when the path did not exist there. */
132
+ function _blob_at(ref: string, rel: string): string {
133
+ return _git('show', `${ref}:${rel}`);
134
+ }
135
+
136
+ // ─── selftest fixtures ─────────────────────────────────────────────────────────
137
+
138
+ function _changelog(sections: string): string[] {
139
+ return ('# Changelog\n\n## [Unreleased]\n\n' + sections).split('\n');
140
+ }
141
+
142
+ const _RELEASED_WITH_BREAKING =
143
+ '## [9.0.0](https://example/compare/8.13.0...9.0.0) (2026-07-13)\n\n' +
144
+ '### BREAKING CHANGES\n\n* installer path changed ([abc1234](https://example))\n\n';
145
+ const _RELEASED_NO_BREAKING =
146
+ '## [9.0.0](https://example/compare/8.13.0...9.0.0) (2026-07-13)\n\n' +
147
+ '### Features\n\n* a feature ([abc1234](https://example))\n\n';
148
+
149
+ function run_selftest(): number {
150
+ let failed = 0;
151
+ const assert = (label: string, ok: boolean): void => {
152
+ process.stdout.write(`${ok ? '✅' : '❌'} selftest: ${label}\n`);
153
+ if (!ok) failed += 1;
154
+ };
155
+
156
+ const oldNoBreaking = _changelog(_RELEASED_NO_BREAKING);
157
+ const newWithBreaking = _changelog(_RELEASED_WITH_BREAKING);
158
+
159
+ // 1. Released BREAKING added, index NOT changed, no override → VIOLATION.
160
+ const red = evaluate(oldNoBreaking, newWithBreaking, false, false);
161
+ assert(
162
+ 'released BREAKING added without index change FAILS',
163
+ red.violation && red.addedMajors.length === 1 && red.addedMajors[0] === '9.0.0',
164
+ );
165
+
166
+ // 2. Same, but the index WAS changed → passes.
167
+ const greenIdx = evaluate(oldNoBreaking, newWithBreaking, true, false);
168
+ assert('index change clears the gate', !greenIdx.violation && greenIdx.addedMajors.length === 1);
169
+
170
+ // 3. Same, index untouched, but override present → passes.
171
+ const greenOverride = evaluate(oldNoBreaking, newWithBreaking, false, true);
172
+ assert('override clears the gate', !greenOverride.violation);
173
+
174
+ // 4. BREAKING only under [Unreleased] (WIP) → never trips.
175
+ const oldEmpty = _changelog('');
176
+ const newUnreleasedBreaking = (
177
+ '# Changelog\n\n## [Unreleased]\n\n### BREAKING CHANGES\n\n* wip break ([abc1234](https://example))\n\n'
178
+ ).split('\n');
179
+ const wip = evaluate(oldEmpty, newUnreleasedBreaking, false, false);
180
+ assert('Unreleased BREAKING does not trip the gate', !wip.violation && wip.addedMajors.length === 0);
181
+
182
+ // 5. A released BREAKING already present at base (no new addition) → clean.
183
+ const noop = evaluate(newWithBreaking, newWithBreaking, false, false);
184
+ assert('pre-existing released BREAKING is not re-flagged', !noop.violation);
185
+
186
+ if (failed > 0) {
187
+ process.stderr.write(`❌ selftest: ${failed} fixture(s) failed.\n`);
188
+ return 1;
189
+ }
190
+ process.stdout.write('✅ selftest: all fixtures behave as specified.\n');
191
+ return 0;
192
+ }
193
+
194
+ // ─── CLI ───────────────────────────────────────────────────────────────────────
195
+
196
+ interface ParsedArgs {
197
+ selftest: boolean;
198
+ quiet: boolean;
199
+ }
200
+
201
+ function _argparse_error(message: string): never {
202
+ process.stderr.write(`lint_breaking_changes_index: error: ${message}\n`);
203
+ process.exit(2);
204
+ }
205
+
206
+ function parse_args(argv: readonly string[]): ParsedArgs {
207
+ const args: ParsedArgs = { selftest: false, quiet: false };
208
+ for (const arg of argv) {
209
+ if (arg === '--selftest') {
210
+ args.selftest = true;
211
+ } else if (arg === '--quiet') {
212
+ args.quiet = true;
213
+ } else if (arg === '-h' || arg === '--help') {
214
+ process.stdout.write('usage: lint_breaking_changes_index [-h] [--selftest] [--quiet]\n');
215
+ process.exit(0);
216
+ } else {
217
+ _argparse_error(`unrecognized arguments: ${arg}`);
218
+ }
219
+ }
220
+ return args;
221
+ }
222
+
223
+ function main(argv?: readonly string[]): number {
224
+ const args = parse_args(argv ?? process.argv.slice(2));
225
+ if (args.selftest) {
226
+ return run_selftest();
227
+ }
228
+
229
+ const base = _base();
230
+ if (!base) {
231
+ return 0; // no trunk to diff against (e.g. shallow CI) → no-op
232
+ }
233
+
234
+ const oldLines = _splitlines(_blob_at(base, CHANGELOG_REL));
235
+ const changelogPath = path.join(REPO_ROOT, CHANGELOG_REL);
236
+ const newLines = fs.existsSync(changelogPath)
237
+ ? _splitlines(fs.readFileSync(changelogPath, 'utf-8'))
238
+ : [];
239
+
240
+ // Was BREAKING_CHANGES.md touched (staged or unstaged) vs the base?
241
+ const indexChanged = _git('diff', '--name-only', base, '--', INDEX_REL).trim() !== '';
242
+
243
+ const log = _git('log', '--format=%B', `${base}..HEAD`);
244
+ const overridden = OVERRIDE_RE.test(log) || process.env['BREAKING_INDEX_OVERRIDE'] === '1';
245
+
246
+ const result = evaluate(oldLines, newLines, indexChanged, overridden);
247
+
248
+ if (!result.violation) {
249
+ if (!args.quiet) {
250
+ if (result.addedMajors.length > 0) {
251
+ const why = result.overridden ? 'override' : `${INDEX_REL} updated`;
252
+ process.stdout.write(
253
+ `✅ lint-breaking-changes-index: ${result.addedMajors.length} released ` +
254
+ `BREAKING addition(s), cleared by ${why}\n`,
255
+ );
256
+ } else {
257
+ process.stdout.write(
258
+ '✅ lint-breaking-changes-index: no new released BREAKING entries vs trunk\n',
259
+ );
260
+ }
261
+ }
262
+ return 0;
263
+ }
264
+
265
+ process.stderr.write(
266
+ `❌ lint-breaking-changes-index: released BREAKING entr(y/ies) added to ${CHANGELOG_REL} ` +
267
+ `without a matching ${INDEX_REL} change:\n`,
268
+ );
269
+ for (const v of result.addedMajors) {
270
+ process.stderr.write(` - ## [${v}] gained a ### BREAKING section\n`);
271
+ }
272
+ process.stderr.write(
273
+ `\nWhen a released major ships a breaking change, ${INDEX_REL} — the discoverable\n` +
274
+ `index (one entry per major) — must be updated in the same change. Resolve by:\n` +
275
+ ` • adding the matching entry to ${INDEX_REL}, or\n` +
276
+ ' • adding an own-line `ci-override: breaking-index-override` trailer to a commit\n' +
277
+ ' body (or setting BREAKING_INDEX_OVERRIDE=1) for an intentionally deferred index.\n',
278
+ );
279
+ return 1;
280
+ }
281
+
282
+ function _isCliEntry(): boolean {
283
+ if (process.argv[1] === undefined) {
284
+ return false;
285
+ }
286
+ const argvUrl = pathToFileURL(path.resolve(process.argv[1])).href;
287
+ if (import.meta.url === argvUrl) {
288
+ return true;
289
+ }
290
+ try {
291
+ const here = fs.realpathSync(fileURLToPath(import.meta.url));
292
+ const argv = fs.realpathSync(path.resolve(process.argv[1]));
293
+ return here === argv;
294
+ } catch {
295
+ return false;
296
+ }
297
+ }
298
+
299
+ if (_isCliEntry() || process.argv[1] === _HERE) {
300
+ process.exit(main());
301
+ }
302
+
303
+ export { main, run_selftest, RELEASED_MAJOR_HEADING_RE, BREAKING_SUBHEADING_RE, OVERRIDE_RE };
@@ -321,9 +321,51 @@ function _pyRepr(s: string): string {
321
321
  return `'${s.replace(/\\/g, '\\\\').replace(/'/g, "\\'")}'`;
322
322
  }
323
323
 
324
+ // ── HANDOFF.md artifact validation (workflow-contracts Phase 2) ─────────
325
+ // SEPARATE concern from the cross-wing skill-handoff lint above: this
326
+ // validates the OPTIONAL standing workflow-resume artifact
327
+ // (agents/runtime/state/HANDOFF.md, written by /agent-handoff --file).
328
+ // Same file on purpose — one linter owns everything named "handoff";
329
+ // the naming reconciliation is this comment.
330
+
331
+ export const HANDOFF_ARTIFACT_REQUIRED = [
332
+ 'Mode',
333
+ 'Contract received',
334
+ 'Contract owed',
335
+ 'Decisions',
336
+ 'Open questions',
337
+ 'Next command',
338
+ ] as const;
339
+
340
+ /** Returns missing required section headings ([] = valid). */
341
+ export function validate_handoff_artifact(text: string): string[] {
342
+ const missing: string[] = [];
343
+ for (const heading of HANDOFF_ARTIFACT_REQUIRED) {
344
+ const re = new RegExp(`^##\\s+${heading}\\s*$`, 'mi');
345
+ if (!re.test(text)) {
346
+ missing.push(heading);
347
+ }
348
+ }
349
+ return missing;
350
+ }
351
+
324
352
  export function main(argv?: readonly string[]): number {
325
353
  let skills_dir = SKILLS_DIR;
326
354
  const args = argv ?? process.argv.slice(2);
355
+ if (args.length > 0 && String(args[0]).endsWith('HANDOFF.md')) {
356
+ // artifact mode — validate the workflow-resume file's required fields
357
+ const text = fs.readFileSync(_resolve(args[0] as string), 'utf-8');
358
+ const missing = validate_handoff_artifact(text);
359
+ if (missing.length === 0) {
360
+ if (!QUIET) process.stdout.write('✅ HANDOFF artifact: all required fields present\n');
361
+ return 0;
362
+ }
363
+ for (const m of missing) {
364
+ process.stdout.write(`${args[0]}:1:handoff-artifact-missing-field: missing required section \`## ${m}\`\n`);
365
+ }
366
+ process.stderr.write(`❌ ${missing.length} missing HANDOFF field(s)\n`);
367
+ return 1;
368
+ }
327
369
  if (args.length > 0) {
328
370
  skills_dir = _resolve(args[0] as string);
329
371
  }
@@ -0,0 +1,228 @@
1
+ #!/usr/bin/env -S node --import tsx
2
+ /**
3
+ * lint_skill_descriptions — description-quality gate (ecosystem-harvest
4
+ * skill-quality-gates, Phase 1).
5
+ *
6
+ * Description-driven routing is the suite's core asset: the agent picks a skill
7
+ * from its `description`. A circular or condition-free description ("Triggers on
8
+ * X, X" / a description that is just the skill name) makes routing a coin-flip.
9
+ * This linter rejects the four description defects that break routing:
10
+ *
11
+ * (a) desc-equals-name — normalized description ≡ normalized name
12
+ * (b) duplicated-trigger — the same multi-word trigger phrase repeated verbatim
13
+ * (c) triggers-are-name — every trigger phrase is a substring of the name
14
+ * (no new routing signal beyond the name)
15
+ * (d) no-routing-signal — the description gives the router nothing to
16
+ * disambiguate on: no condition marker (when / before /
17
+ * after / only / for …), no specifics enumeration
18
+ * (em-dash / colon / comma list), no quoted example,
19
+ * no imperative-verb lead. A bare topic restatement.
20
+ *
21
+ * (d) is signal-ABSENCE, not a phrase mandate: an imperative house-style
22
+ * description ("Writes Laravel PHP — Eloquent, Artisan …") carries routing
23
+ * signal and passes. The farm-generator's name-echo frontmatter is the
24
+ * must-fail specimen.
25
+ *
26
+ * Deterministic, static-config only. Allowlist mirrors lint_skill_originality:
27
+ * `lint_skill_descriptions_allowlist.json`, capped at 20 (autonomous-execution
28
+ * allowlist-growth antipattern — over-cap means the linter is wrong).
29
+ *
30
+ * Exit codes: 0 = clean (or only allowlisted), 1 = at least one non-allowlisted
31
+ * violation, 2 = allowlist over cap / usage error.
32
+ *
33
+ * Usage:
34
+ * ./scripts-run src/scripts/lint_skill_descriptions [--quiet] [--json <path>] [--root <dir>]
35
+ */
36
+
37
+ import * as fs from 'node:fs';
38
+ import * as path from 'node:path';
39
+ import process from 'node:process';
40
+ import { fileURLToPath } from 'node:url';
41
+
42
+ import { parse_frontmatter } from './skill_overlap.js';
43
+
44
+ const _HERE = fileURLToPath(import.meta.url);
45
+ const REPO = path.resolve(path.dirname(_HERE), '..', '..');
46
+ const DEFAULT_SKILLS = path.join(REPO, 'src', 'skills');
47
+ const ALLOWLIST = path.join(path.dirname(_HERE), 'lint_skill_descriptions_allowlist.json');
48
+ const ALLOWLIST_CAP = 20;
49
+
50
+ // A condition / trigger marker — one of the ways a description signals WHEN it routes.
51
+ const CONDITION_RE =
52
+ /\b(use when|use to|use for|use on|use during|when |before |after |only when|only for|triggers on|fires when|activ|for [a-z])/i;
53
+ // An imperative / gerund lead verb — the house-style "Writes X", "Convert X" form
54
+ // carries WHAT-it-does routing signal even without a "when" clause.
55
+ const IMPERATIVE_LEAD_RE =
56
+ /^["'`]*\s*(write|author|convert|review|run|add|build|generate|create|fix|wire|detect|analy[sz]e|optimi[sz]e|draft|extract|route|turn|produce|score|consolidate|rate|model|estimate|plan|check|audit|ship|emit|render|guide|poll|capture|explain|map|prepare|handle|manage|track|compare|validate|verify|enforce|choose|pick|enumerate|investigate|format|scaffold|design|define|describe|gate|block|refactor|migrate|transcribe|summari[sz]e|orchestrate|surface|propose|walk|stress-test|red-team|challenge|bootstrap|seed|install|deploy|package|split|merge|condense|harvest|evaluate|assess|diagnose|reproduce|trace|inspect|lint|test)/i;
57
+
58
+ interface Violation {
59
+ slug: string;
60
+ code: 'desc-equals-name' | 'duplicated-trigger' | 'triggers-are-name' | 'no-routing-signal';
61
+ detail: string;
62
+ }
63
+
64
+ /** A description carries routing signal if it names a condition, leads with an
65
+ * imperative verb, enumerates specifics (em-dash / colon / ≥2 comma segments),
66
+ * or embeds an example query (a quoted phrase). Absence of all four is the defect. */
67
+ function hasRoutingSignal(desc: string): boolean {
68
+ if (CONDITION_RE.test(desc)) return true;
69
+ if (IMPERATIVE_LEAD_RE.test(desc.trim())) return true;
70
+ if (/[—–:;→]/.test(desc)) return true; // specifics enumeration
71
+ if (/['"][^'"]{3,}['"]/.test(desc)) return true; // quoted example query
72
+ if (desc.split(',').filter((s) => s.trim().length >= 3).length >= 2) return true;
73
+ return false;
74
+ }
75
+
76
+ function normalize(s: string): string {
77
+ return s
78
+ .toLowerCase()
79
+ .replace(/[`"'*_.,:;!?()[\]{}]/g, ' ')
80
+ .replace(/[-\s]+/g, ' ')
81
+ .trim();
82
+ }
83
+
84
+ /** Split a description into candidate trigger phrases on sentence/list punctuation. */
85
+ function triggerPhrases(desc: string): string[] {
86
+ return desc
87
+ .split(/[.;·,|]|\bor\b|\band\b|\/|—|–/i)
88
+ .map((p) => normalize(p))
89
+ .filter((p) => p.length >= 3);
90
+ }
91
+
92
+ function analyseSkill(slug: string, fm: Record<string, string>): Violation[] {
93
+ const out: Violation[] = [];
94
+ const name = fm['name'] ?? slug;
95
+ const desc = (fm['description'] ?? '').trim();
96
+ if (desc === '') {
97
+ // Empty description is a frontmatter-schema failure, not this linter's job.
98
+ return out;
99
+ }
100
+ const nName = normalize(name);
101
+ const nDesc = normalize(desc);
102
+
103
+ // (a) description ≡ name
104
+ if (nDesc === nName) {
105
+ out.push({ slug, code: 'desc-equals-name', detail: `description normalizes to the name "${name}"` });
106
+ }
107
+
108
+ const phrases = triggerPhrases(desc);
109
+
110
+ // (b) duplicated MULTI-WORD trigger phrase repeated verbatim (the farm-specimen
111
+ // "Triggers on: X, X" defect) — a single recurring domain word is not a defect.
112
+ const multiWord = phrases.filter((p) => p.includes(' '));
113
+ const seen = new Set<string>();
114
+ for (const p of multiWord) {
115
+ if (seen.has(p)) {
116
+ out.push({ slug, code: 'duplicated-trigger', detail: `trigger phrase repeated verbatim: "${p}"` });
117
+ break;
118
+ }
119
+ seen.add(p);
120
+ }
121
+
122
+ // (c) every trigger phrase is a substring of the name (no signal beyond name)
123
+ if (phrases.length > 0 && phrases.every((p) => nName.includes(p) || p.includes(nName))) {
124
+ out.push({
125
+ slug,
126
+ code: 'triggers-are-name',
127
+ detail: 'every trigger phrase is contained in the skill name — no routing signal beyond the name',
128
+ });
129
+ }
130
+
131
+ // (d) no routing signal at all — bare topic restatement
132
+ if (!hasRoutingSignal(desc)) {
133
+ out.push({
134
+ slug,
135
+ code: 'no-routing-signal',
136
+ detail: 'no condition marker, imperative-verb lead, specifics enumeration, or example query — the router has nothing to disambiguate on',
137
+ });
138
+ }
139
+
140
+ return out;
141
+ }
142
+
143
+ function loadAllowlist(): Set<string> {
144
+ if (!fs.existsSync(ALLOWLIST)) {
145
+ return new Set();
146
+ }
147
+ const data = JSON.parse(fs.readFileSync(ALLOWLIST, 'utf-8')) as {
148
+ entries?: Array<{ slug: string; code: string }>;
149
+ };
150
+ const entries = Array.isArray(data.entries) ? data.entries : [];
151
+ if (entries.length > ALLOWLIST_CAP) {
152
+ process.stderr.write(
153
+ `❌ lint_skill_descriptions: allowlist has ${entries.length} entries (> ${ALLOWLIST_CAP}). ` +
154
+ `Per the autonomous-execution allowlist-growth antipattern, the linter is wrong, ` +
155
+ `not the content — tighten the heuristic, do not grow the allowlist.\n`,
156
+ );
157
+ process.exit(2);
158
+ }
159
+ return new Set(entries.map((e) => `${e.slug}::${e.code}`));
160
+ }
161
+
162
+ function* skillMds(root: string): Generator<string> {
163
+ if (!fs.existsSync(root)) return;
164
+ for (const entry of fs.readdirSync(root, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
165
+ if (!entry.isDirectory()) continue;
166
+ const md = path.join(root, entry.name, 'SKILL.md');
167
+ if (fs.existsSync(md)) yield md;
168
+ }
169
+ }
170
+
171
+ interface Args {
172
+ quiet: boolean;
173
+ json: string | null;
174
+ root: string;
175
+ }
176
+
177
+ function parseArgs(argv: string[]): Args {
178
+ const a: Args = { quiet: false, json: null, root: DEFAULT_SKILLS };
179
+ for (let i = 0; i < argv.length; i += 1) {
180
+ const arg = argv[i];
181
+ if (arg === '--quiet') a.quiet = true;
182
+ else if (arg === '--json') a.json = argv[(i += 1)] ?? '';
183
+ else if (arg === '--root') a.root = argv[(i += 1)] ?? DEFAULT_SKILLS;
184
+ else {
185
+ process.stderr.write(`lint_skill_descriptions: unrecognized argument: ${arg}\n`);
186
+ process.exit(2);
187
+ }
188
+ }
189
+ return a;
190
+ }
191
+
192
+ function main(argv: string[]): number {
193
+ const args = parseArgs(argv);
194
+ const allow = loadAllowlist();
195
+ const violations: Violation[] = [];
196
+ let scanned = 0;
197
+ for (const md of skillMds(args.root)) {
198
+ scanned += 1;
199
+ const slug = path.basename(path.dirname(md));
200
+ const [fm] = parse_frontmatter(fs.readFileSync(md, 'utf-8'));
201
+ for (const v of analyseSkill(slug, fm)) {
202
+ if (!allow.has(`${v.slug}::${v.code}`)) violations.push(v);
203
+ }
204
+ }
205
+
206
+ if (args.json) {
207
+ fs.mkdirSync(path.dirname(args.json), { recursive: true });
208
+ fs.writeFileSync(args.json, JSON.stringify({ scanned, violations }, null, 2) + '\n');
209
+ }
210
+
211
+ if (violations.length > 0) {
212
+ process.stderr.write(`❌ lint_skill_descriptions: ${violations.length} description defect(s):\n`);
213
+ for (const v of violations) {
214
+ process.stderr.write(` ${v.slug} [${v.code}] — ${v.detail}\n`);
215
+ }
216
+ return 1;
217
+ }
218
+ if (!args.quiet) {
219
+ process.stdout.write(`✅ lint_skill_descriptions: ${scanned} skill(s) scanned, no description defects.\n`);
220
+ }
221
+ return 0;
222
+ }
223
+
224
+ if (path.resolve(process.argv[1] ?? '') === path.resolve(_HERE)) {
225
+ process.exit(main(process.argv.slice(2)));
226
+ }
227
+
228
+ export { analyseSkill, normalize, triggerPhrases, main };
@@ -0,0 +1 @@
1
+ { "entries": [] }