@event4u/agent-config 11.0.0 → 12.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 (312) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/CHANGELOG.md +173 -229
  3. package/agents/templates/.ai-council.yml.example +46 -3
  4. package/dist/agent-src/commands/agent-handoff.md +0 -1
  5. package/dist/agent-src/commands/agent-status.md +0 -1
  6. package/dist/agent-src/commands/agents/audit.md +0 -1
  7. package/dist/agent-src/commands/agents/init.md +0 -1
  8. package/dist/agent-src/commands/agents/optimize.md +0 -1
  9. package/dist/agent-src/commands/agents/user/accept.md +0 -1
  10. package/dist/agent-src/commands/agents/user/delete.md +0 -1
  11. package/dist/agent-src/commands/agents/user/init.md +0 -1
  12. package/dist/agent-src/commands/agents/user/review.md +0 -1
  13. package/dist/agent-src/commands/agents/user/show.md +0 -1
  14. package/dist/agent-src/commands/agents/user/update.md +0 -1
  15. package/dist/agent-src/commands/agents/user.md +0 -1
  16. package/dist/agent-src/commands/agents.md +0 -1
  17. package/dist/agent-src/commands/analytics/prune.md +0 -1
  18. package/dist/agent-src/commands/analytics/show.md +0 -1
  19. package/dist/agent-src/commands/analytics.md +0 -1
  20. package/dist/agent-src/commands/analyze/conformance.md +0 -1
  21. package/dist/agent-src/commands/analyze/decision.md +0 -1
  22. package/dist/agent-src/commands/analyze/inbox.md +0 -1
  23. package/dist/agent-src/commands/analyze/incident.md +0 -1
  24. package/dist/agent-src/commands/analyze/near-miss.md +0 -1
  25. package/dist/agent-src/commands/analyze/postmortem.md +0 -1
  26. package/dist/agent-src/commands/analyze/premortem.md +0 -1
  27. package/dist/agent-src/commands/analyze/reference-repo.md +0 -1
  28. package/dist/agent-src/commands/analyze.md +0 -1
  29. package/dist/agent-src/commands/brand/identity.md +0 -1
  30. package/dist/agent-src/commands/brand/review.md +0 -1
  31. package/dist/agent-src/commands/brand/strategy.md +0 -1
  32. package/dist/agent-src/commands/brand/tokens.md +0 -1
  33. package/dist/agent-src/commands/brand/voice.md +0 -1
  34. package/dist/agent-src/commands/brand.md +0 -1
  35. package/dist/agent-src/commands/bug/fix.md +0 -1
  36. package/dist/agent-src/commands/bug/investigate.md +0 -1
  37. package/dist/agent-src/commands/bug.md +0 -1
  38. package/dist/agent-src/commands/challenge-me/vision.md +0 -1
  39. package/dist/agent-src/commands/challenge-me/with-docs.md +0 -1
  40. package/dist/agent-src/commands/challenge-me.md +0 -1
  41. package/dist/agent-src/commands/chat-history/import.md +0 -1
  42. package/dist/agent-src/commands/chat-history.md +0 -1
  43. package/dist/agent-src/commands/check-current-md.md +0 -1
  44. package/dist/agent-src/commands/commit/in-chunks.md +0 -1
  45. package/dist/agent-src/commands/commit.md +0 -1
  46. package/dist/agent-src/commands/condense.md +0 -1
  47. package/dist/agent-src/commands/context/create.md +0 -1
  48. package/dist/agent-src/commands/context/refactor.md +0 -1
  49. package/dist/agent-src/commands/context.md +0 -1
  50. package/dist/agent-src/commands/contribution-precheck.md +0 -1
  51. package/dist/agent-src/commands/cost/profile.md +0 -1
  52. package/dist/agent-src/commands/cost/report.md +0 -1
  53. package/dist/agent-src/commands/cost.md +0 -1
  54. package/dist/agent-src/commands/council/analysis.md +0 -1
  55. package/dist/agent-src/commands/council/debate.md +0 -1
  56. package/dist/agent-src/commands/council/default.md +0 -1
  57. package/dist/agent-src/commands/council/design.md +0 -1
  58. package/dist/agent-src/commands/council/optimize.md +0 -1
  59. package/dist/agent-src/commands/council/pr.md +0 -1
  60. package/dist/agent-src/commands/council.md +0 -1
  61. package/dist/agent-src/commands/design-system/capture.md +0 -1
  62. package/dist/agent-src/commands/design-system/generate.md +0 -1
  63. package/dist/agent-src/commands/design-system/import.md +0 -1
  64. package/dist/agent-src/commands/design-system.md +0 -1
  65. package/dist/agent-src/commands/estimate-ticket.md +0 -1
  66. package/dist/agent-src/commands/explain-run.md +0 -1
  67. package/dist/agent-src/commands/feature/dev.md +0 -1
  68. package/dist/agent-src/commands/feature/explore.md +0 -1
  69. package/dist/agent-src/commands/feature/plan.md +0 -1
  70. package/dist/agent-src/commands/feature/refactor.md +0 -1
  71. package/dist/agent-src/commands/feature/roadmap.md +0 -1
  72. package/dist/agent-src/commands/feature.md +0 -1
  73. package/dist/agent-src/commands/fix/ci.md +0 -1
  74. package/dist/agent-src/commands/fix/comments.md +0 -1
  75. package/dist/agent-src/commands/fix/portability.md +0 -1
  76. package/dist/agent-src/commands/fix/pr-comments-loop.md +0 -1
  77. package/dist/agent-src/commands/fix/pr-comments.md +0 -1
  78. package/dist/agent-src/commands/fix/quality.md +0 -1
  79. package/dist/agent-src/commands/fix/refs.md +0 -1
  80. package/dist/agent-src/commands/fix/route.md +0 -1
  81. package/dist/agent-src/commands/fix/seeder.md +0 -1
  82. package/dist/agent-src/commands/fix.md +0 -1
  83. package/dist/agent-src/commands/ghostwriter/delete.md +0 -1
  84. package/dist/agent-src/commands/ghostwriter/fetch.md +0 -1
  85. package/dist/agent-src/commands/ghostwriter/list.md +0 -1
  86. package/dist/agent-src/commands/ghostwriter/show.md +0 -1
  87. package/dist/agent-src/commands/ghostwriter/write.md +0 -1
  88. package/dist/agent-src/commands/ghostwriter.md +0 -1
  89. package/dist/agent-src/commands/grill-me.md +0 -1
  90. package/dist/agent-src/commands/humanize.md +0 -1
  91. package/dist/agent-src/commands/image/analyse.md +0 -1
  92. package/dist/agent-src/commands/image/create.md +0 -1
  93. package/dist/agent-src/commands/image/verify.md +0 -1
  94. package/dist/agent-src/commands/image.md +0 -1
  95. package/dist/agent-src/commands/implement-ticket.md +0 -1
  96. package/dist/agent-src/commands/jira-ticket.md +0 -1
  97. package/dist/agent-src/commands/judge/on-diff.md +0 -1
  98. package/dist/agent-src/commands/judge/solo.md +0 -1
  99. package/dist/agent-src/commands/judge/steps.md +0 -1
  100. package/dist/agent-src/commands/judge.md +0 -1
  101. package/dist/agent-src/commands/knowledge/cross-repo.md +0 -1
  102. package/dist/agent-src/commands/knowledge/forget.md +0 -1
  103. package/dist/agent-src/commands/knowledge/ingest.md +0 -1
  104. package/dist/agent-src/commands/knowledge/list.md +0 -1
  105. package/dist/agent-src/commands/knowledge.md +0 -1
  106. package/dist/agent-src/commands/memory/add.md +0 -1
  107. package/dist/agent-src/commands/memory/learn-low-impact.md +0 -1
  108. package/dist/agent-src/commands/memory/load.md +0 -1
  109. package/dist/agent-src/commands/memory/mine-session.md +0 -1
  110. package/dist/agent-src/commands/memory/promote.md +0 -1
  111. package/dist/agent-src/commands/memory/propose.md +0 -1
  112. package/dist/agent-src/commands/memory.md +0 -1
  113. package/dist/agent-src/commands/mission/upgrade.md +0 -1
  114. package/dist/agent-src/commands/mode.md +0 -1
  115. package/dist/agent-src/commands/module/create.md +0 -1
  116. package/dist/agent-src/commands/module/explore.md +0 -1
  117. package/dist/agent-src/commands/module.md +0 -1
  118. package/dist/agent-src/commands/optimize/agents-dir.md +0 -1
  119. package/dist/agent-src/commands/optimize/augmentignore.md +0 -1
  120. package/dist/agent-src/commands/optimize/deep.md +0 -1
  121. package/dist/agent-src/commands/optimize/project.md +0 -1
  122. package/dist/agent-src/commands/optimize/prompt.md +0 -1
  123. package/dist/agent-src/commands/optimize/rtk.md +0 -1
  124. package/dist/agent-src/commands/optimize/skills.md +0 -1
  125. package/dist/agent-src/commands/optimize.md +0 -1
  126. package/dist/agent-src/commands/orchestrate.md +0 -1
  127. package/dist/agent-src/commands/override/create.md +0 -1
  128. package/dist/agent-src/commands/override/manage.md +0 -1
  129. package/dist/agent-src/commands/override.md +0 -1
  130. package/dist/agent-src/commands/package/reset.md +0 -1
  131. package/dist/agent-src/commands/package/test.md +0 -1
  132. package/dist/agent-src/commands/package.md +0 -1
  133. package/dist/agent-src/commands/post-as/ghostwriter.md +0 -1
  134. package/dist/agent-src/commands/post-as/me.md +0 -1
  135. package/dist/agent-src/commands/post-as.md +0 -1
  136. package/dist/agent-src/commands/pr/create/description-only.md +0 -1
  137. package/dist/agent-src/commands/pr/create.md +23 -9
  138. package/dist/agent-src/commands/prediction-pool.md +0 -1
  139. package/dist/agent-src/commands/prepare-for-review.md +0 -1
  140. package/dist/agent-src/commands/profile/activate.md +0 -1
  141. package/dist/agent-src/commands/profile/deactivate.md +0 -1
  142. package/dist/agent-src/commands/profile/show.md +0 -1
  143. package/dist/agent-src/commands/profile.md +0 -1
  144. package/dist/agent-src/commands/project/analyze.md +0 -1
  145. package/dist/agent-src/commands/project/health.md +0 -1
  146. package/dist/agent-src/commands/project.md +0 -1
  147. package/dist/agent-src/commands/refine-ticket.md +0 -1
  148. package/dist/agent-src/commands/research/deep.md +0 -1
  149. package/dist/agent-src/commands/research/report.md +0 -1
  150. package/dist/agent-src/commands/research.md +0 -1
  151. package/dist/agent-src/commands/review/changes.md +0 -1
  152. package/dist/agent-src/commands/review/routing.md +0 -1
  153. package/dist/agent-src/commands/review.md +0 -1
  154. package/dist/agent-src/commands/roadmap/ai-council.md +0 -1
  155. package/dist/agent-src/commands/roadmap/create.md +0 -1
  156. package/dist/agent-src/commands/roadmap/materialize.md +0 -1
  157. package/dist/agent-src/commands/roadmap/next.md +0 -1
  158. package/dist/agent-src/commands/roadmap/process-full.md +0 -1
  159. package/dist/agent-src/commands/roadmap/process-phase.md +0 -1
  160. package/dist/agent-src/commands/roadmap/process-step.md +0 -1
  161. package/dist/agent-src/commands/roadmap.md +0 -1
  162. package/dist/agent-src/commands/rule-compliance-audit.md +0 -1
  163. package/dist/agent-src/commands/security-audit-config.md +0 -1
  164. package/dist/agent-src/commands/skill/preview.md +0 -1
  165. package/dist/agent-src/commands/skill.md +0 -1
  166. package/dist/agent-src/commands/skills/discover.md +0 -1
  167. package/dist/agent-src/commands/skills.md +0 -1
  168. package/dist/agent-src/commands/sync/agent-settings.md +0 -1
  169. package/dist/agent-src/commands/sync/gitignore/fix.md +0 -1
  170. package/dist/agent-src/commands/sync/gitignore.md +0 -1
  171. package/dist/agent-src/commands/sync.md +0 -1
  172. package/dist/agent-src/commands/tdd/green.md +0 -1
  173. package/dist/agent-src/commands/tdd/red.md +0 -1
  174. package/dist/agent-src/commands/tdd/refactor.md +0 -1
  175. package/dist/agent-src/commands/tdd.md +0 -1
  176. package/dist/agent-src/commands/team/adversarial.md +0 -1
  177. package/dist/agent-src/commands/team/delegate.md +0 -1
  178. package/dist/agent-src/commands/team/review.md +0 -1
  179. package/dist/agent-src/commands/team/status.md +0 -1
  180. package/dist/agent-src/commands/team-knowledge/bootstrap.md +0 -1
  181. package/dist/agent-src/commands/team-knowledge/consolidate.md +0 -1
  182. package/dist/agent-src/commands/team-knowledge.md +0 -1
  183. package/dist/agent-src/commands/team.md +0 -1
  184. package/dist/agent-src/commands/tests/create.md +0 -1
  185. package/dist/agent-src/commands/tests/e2e-heal.md +0 -1
  186. package/dist/agent-src/commands/tests/e2e-plan.md +0 -1
  187. package/dist/agent-src/commands/tests/execute.md +0 -1
  188. package/dist/agent-src/commands/tests.md +0 -1
  189. package/dist/agent-src/commands/threat-model.md +0 -1
  190. package/dist/agent-src/commands/update-form-request-messages.md +0 -1
  191. package/dist/agent-src/commands/upstream-contribute.md +0 -1
  192. package/dist/agent-src/commands/video/from-script.md +0 -1
  193. package/dist/agent-src/commands/video/from-song.md +0 -1
  194. package/dist/agent-src/commands/video/scene.md +0 -1
  195. package/dist/agent-src/commands/video/stitch.md +0 -1
  196. package/dist/agent-src/commands/video/storyboard.md +0 -1
  197. package/dist/agent-src/commands/video.md +0 -1
  198. package/dist/agent-src/commands/work.md +0 -1
  199. package/dist/agent-src/commands/worktree/cleanup.md +0 -1
  200. package/dist/agent-src/commands/worktree/create.md +0 -1
  201. package/dist/agent-src/commands/worktree/status.md +0 -1
  202. package/dist/agent-src/commands/worktree/verify.md +0 -1
  203. package/dist/agent-src/commands/worktree.md +0 -1
  204. package/dist/agent-src/contexts/model-recommendations.md +6 -4
  205. package/dist/agent-src/rules/low-impact-corpus-privacy-floor.md +1 -0
  206. package/dist/agent-src/scripts/roadmap_gates.ts +30 -2
  207. package/dist/agent-src/scripts/update_roadmap_progress.ts +32 -1
  208. package/dist/agent-src/skills/docx-authoring/SKILL.md +1 -0
  209. package/dist/agent-src/skills/git-workflow/SKILL.md +9 -1
  210. package/dist/agent-src/skills/pdf-tools/SKILL.md +1 -0
  211. package/dist/agent-src/skills/roadmap-writing/SKILL.md +7 -7
  212. package/dist/agent-src/skills/skill-writing/SKILL.md +35 -0
  213. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  214. package/dist/agent-src/templates/roadmaps.md +41 -3
  215. package/dist/cli/commands/commands.candidates.test.js +1 -1
  216. package/dist/cli/commands/commands.candidates.test.js.map +1 -1
  217. package/dist/cli/commands/commands.js +9 -15
  218. package/dist/cli/commands/commands.js.map +1 -1
  219. package/dist/cli/commands/profiles.test.js +7 -7
  220. package/dist/cli/commands/profiles.test.js.map +1 -1
  221. package/dist/cli/discovery/loadManifest.js.map +1 -1
  222. package/dist/cli/main.js +2 -2
  223. package/dist/cli/main.js.map +1 -1
  224. package/dist/cli/registry.js +1 -0
  225. package/dist/cli/registry.js.map +1 -1
  226. package/dist/cli-delegate/chunk-K34QTSD5.js +3895 -0
  227. package/dist/cli-delegate/{chunk-L565YFXH.js → chunk-LKZ5DOYW.js} +557 -297
  228. package/dist/cli-delegate/{chunk-NN4KW4HZ.js → chunk-O52B4F5S.js} +123 -2338
  229. package/dist/cli-delegate/cmd_conformance.js +18 -6
  230. package/dist/cli-delegate/cmd_doctor.js +2 -2
  231. package/dist/cli-delegate/cmd_export.js +1 -1
  232. package/dist/cli-delegate/cmd_handoff.js +1 -1
  233. package/dist/cli-delegate/cmd_prune.js +1 -1
  234. package/dist/cli-delegate/cmd_settings_migrate.js +1 -1
  235. package/dist/cli-delegate/cmd_sync.js +1 -1
  236. package/dist/cli-delegate/cmd_uninstall.js +1 -1
  237. package/dist/cli-delegate/cmd_validate.js +1 -1
  238. package/dist/discovery/deprecation-report.md +1 -1
  239. package/dist/discovery/discovery-manifest.json +213 -412
  240. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  241. package/dist/discovery/discovery-manifest.summary.md +2 -2
  242. package/dist/discovery/dormancy-report.md +1 -1
  243. package/dist/discovery/orphan-report.md +1 -1
  244. package/dist/discovery/packs.json +3 -3
  245. package/dist/discovery/trust-report.md +1 -1
  246. package/dist/discovery/workspaces.json +3 -3
  247. package/dist/hooks/dispatch.js +2588 -1082
  248. package/dist/install/install.mjs +744 -482
  249. package/dist/mcp/registry-manifest.json +3 -3
  250. package/dist/mcp/server.json +2 -2
  251. package/dist/mcp/server.mjs +159 -15
  252. package/dist/router.json +1 -1
  253. package/dist/scripts/_lib/model_tier.js +9 -0
  254. package/dist/scripts/_lib/model_tier.js.map +1 -1
  255. package/dist/scripts/condense.js +50 -0
  256. package/dist/scripts/condense.js.map +1 -1
  257. package/docs/MIGRATION.md +21 -1
  258. package/docs/contracts/settings-classes.md +41 -0
  259. package/package.json +1 -1
  260. package/src/agent-src/scripts/roadmap_gates.ts +30 -2
  261. package/src/agent-src/scripts/update_roadmap_progress.ts +32 -1
  262. package/src/config/agent-settings.template.yml +34 -0
  263. package/src/config/evaluator-budgets.json +4 -4
  264. package/src/config/gate-coverage.yml +39 -9
  265. package/src/config/gate-violation-baselines.json +9 -3
  266. package/src/config/hook-token-budget.json +7 -1
  267. package/src/config/preamble-payload-budget.json +28 -0
  268. package/src/scripts/_dispatch.bash +52 -5
  269. package/src/scripts/_lib/git_common_dir.ts +173 -0
  270. package/src/scripts/_lib/model_tier.ts +9 -0
  271. package/src/scripts/_lib/release_highlights.ts +108 -7
  272. package/src/scripts/_lib/skill_catalogue.ts +862 -0
  273. package/src/scripts/ai_council/clients.ts +182 -3
  274. package/src/scripts/ai_council/config.ts +16 -1
  275. package/src/scripts/ai_council/low_impact.ts +14 -1
  276. package/src/scripts/ai_council/orchestrator.ts +14 -1
  277. package/src/scripts/ai_council/transport_resolver.ts +32 -2
  278. package/src/scripts/audit_command_surface.ts +21 -15
  279. package/src/scripts/build_catalog_index.ts +1 -1
  280. package/src/scripts/build_discovery_manifest.ts +30 -34
  281. package/src/scripts/capture_skill_catalogue.ts +185 -328
  282. package/src/scripts/check_branch_freshness.ts +217 -62
  283. package/src/scripts/check_release_published.ts +6 -2
  284. package/src/scripts/check_release_trunk_sync.ts +18 -2
  285. package/src/scripts/condense.ts +50 -1
  286. package/src/scripts/dispatch_r2_reviewer.ts +15 -5
  287. package/src/scripts/evidence_report.ts +15 -4
  288. package/src/scripts/gen_discovery_baseline.ts +3 -3
  289. package/src/scripts/hook_manifest.yaml +67 -13
  290. package/src/scripts/hooks/concern_registry.ts +2 -0
  291. package/src/scripts/hooks/orchestration_record_hook.ts +21 -0
  292. package/src/scripts/hooks/subagent_ledger_hook.ts +17 -4
  293. package/src/scripts/hooks/tool_result_bytes_hook.ts +219 -0
  294. package/src/scripts/hot_context_hook.ts +13 -1
  295. package/src/scripts/install.ts +129 -0
  296. package/src/scripts/janitor.ts +77 -2
  297. package/src/scripts/lint_command_tiers.ts +29 -75
  298. package/src/scripts/lint_originality.ts +101 -14
  299. package/src/scripts/lint_plan_risk_register.ts +22 -9
  300. package/src/scripts/lint_roadmap_blockers.ts +109 -6
  301. package/src/scripts/lint_scheduled_deprecations.ts +738 -0
  302. package/src/scripts/migration_status.ts +17 -1
  303. package/src/scripts/probe_review_binding_drift.ts +731 -0
  304. package/src/scripts/release.ts +95 -1
  305. package/src/scripts/schemas/command.schema.json +6 -11
  306. package/src/scripts/schemas/skill.schema.json +12 -2
  307. package/src/scripts/schemas/subagent.schema.json +2 -2
  308. package/src/scripts/session_eol_report.ts +81 -0
  309. package/src/scripts/skill_linter.ts +82 -2
  310. package/src/scripts/workspace_doctor.ts +464 -0
  311. package/src/scripts/worktree_cleanup_check.ts +113 -8
  312. package/dist/cli-delegate/chunk-AYKQGIBG.js +0 -1594
@@ -536,7 +536,7 @@ function die(msg: string, code = 2): never {
536
536
  throw new SystemExitError(code);
537
537
  }
538
538
 
539
- interface RunResult {
539
+ export interface RunResult {
540
540
  returncode: number;
541
541
  stdout: string;
542
542
  stderr: string;
@@ -1623,6 +1623,93 @@ function set_template_pin(p: string, version: string): void {
1623
1623
  fs.writeFileSync(p, lines.join('\n'), 'utf-8');
1624
1624
  }
1625
1625
 
1626
+ /**
1627
+ * Refuse a MAJOR cut that carries a scheduled deprecation due at or before it.
1628
+ *
1629
+ * **The trigger is the shape of the TARGET, not a comparison with the current
1630
+ * version.** A major target is `X.0.0` — and that one predicate covers every
1631
+ * path to a major: the `--as major` flag, an explicit `--version 13.0.0`,
1632
+ * auto-detection from a `feat!:` commit, AND `--resume`, where
1633
+ * `_detect_in_flight_target()` returns the already-bumped `package.json`
1634
+ * version so `target === current` and any current-vs-target comparison
1635
+ * silently returns. Resume was the fourth path an earlier version of this
1636
+ * guard missed while its own comment claimed three paths converged.
1637
+ *
1638
+ * Refusing a resumed release can strand a partially-completed one, and that is
1639
+ * the deliberate trade: a stranded release is recoverable by fixing the table
1640
+ * and resuming again, whereas a major shipped over a missed commitment is the
1641
+ * failure this whole surface exists to prevent.
1642
+ *
1643
+ * The target version is PASSED to the gate. Without it the gate falls back to
1644
+ * `package.json`, which at the cut to N still reads N-1 — so a row committed
1645
+ * to N reads as one major early and passes, and only rows already a major late
1646
+ * could ever be refused. That is the lateness being prevented, so measuring
1647
+ * against the shipped version would have made the refusal fire exactly one
1648
+ * major too late, forever.
1649
+ *
1650
+ * Runs under `--dry-run` too, unlike the rest of preflight: this check is one
1651
+ * subprocess reading two files, so the "keep a preview fast" rationale that
1652
+ * excludes the ~15s test-trend collection does not apply — and a preview that
1653
+ * reports green for the single condition that will refuse the real run is the
1654
+ * case an operator runs a preview to discover. It REPORTS there rather than
1655
+ * dying: `--dry-run` exiting 0 before `execute()` and before `preflight()` is a
1656
+ * contract this file's own tests assert, and an earlier revision of this guard
1657
+ * broke it by sitting above the dry-run branch with no preview mode.
1658
+ *
1659
+ * @param runner Seam for the gate invocation. Production passes nothing and
1660
+ * gets the real `run`; tests inject a stub, because the alternative — reaching
1661
+ * this branch only by mutating `docs/MIGRATION.md` — would make the refusal
1662
+ * path testable exclusively through a tracked-file edit.
1663
+ */
1664
+ export function assert_scheduled_deprecations_clear(
1665
+ target: string,
1666
+ runner: (args: readonly string[]) => RunResult = (args) => run(args, { check: false, capture: true }),
1667
+ opts: { previewOnly?: boolean } = {},
1668
+ ): void {
1669
+ const [, minor, patch] = parse_version(target);
1670
+ if (minor !== 0 || patch !== 0) {
1671
+ return;
1672
+ }
1673
+ const res = runner([
1674
+ './scripts-run',
1675
+ 'src/scripts/lint_scheduled_deprecations',
1676
+ '--cutting',
1677
+ target,
1678
+ ]);
1679
+ if (res.returncode === 0) {
1680
+ return;
1681
+ }
1682
+ process.stderr.write(res.stdout);
1683
+ process.stderr.write(res.stderr);
1684
+ if (res.returncode !== 1) {
1685
+ // The gate reserves 1 for a finding and 2 for a usage/environment
1686
+ // failure. Diagnosing the latter as "the table has an overdue row"
1687
+ // sends the releaser to edit a file that is not the problem.
1688
+ die(
1689
+ `refusing the ${target} cut: the scheduled-deprecations check could not run ` +
1690
+ `(exit ${String(res.returncode)}). That is an environment or usage failure, not a ` +
1691
+ 'finding in docs/MIGRATION.md — fix the invocation or the checkout, then re-run.',
1692
+ );
1693
+ }
1694
+ if (opts.previewOnly === true) {
1695
+ // The preview's job is to SHOW what the real run will refuse. Dying
1696
+ // here would break the `--dry-run exits 0` contract asserted elsewhere
1697
+ // in this file's tests — trading a documented exit code for a message
1698
+ // that has already been printed above.
1699
+ process.stderr.write(
1700
+ `\n(dry-run) the ${target} cut WOULD BE REFUSED for the reason above. ` +
1701
+ 'Previewing only; exit code unchanged.\n',
1702
+ );
1703
+ return;
1704
+ }
1705
+ die(
1706
+ `refusing the ${target} cut: the scheduled-deprecations table in docs/MIGRATION.md ` +
1707
+ 'has a row due at or before this major, or one that cannot be resolved. Act on ' +
1708
+ "it — perform the removal in its own change, or revise the row's commitment and " +
1709
+ 'record why the surface stays — then re-run.',
1710
+ );
1711
+ }
1712
+
1626
1713
  // ─── preflight ────────────────────────────────────────────────────────────────
1627
1714
 
1628
1715
  /**
@@ -2578,6 +2665,13 @@ function main(argv: readonly string[] | null = null): number {
2578
2665
  }
2579
2666
  parse_version(target);
2580
2667
 
2668
+ // Runs in dry-run too — a preview that hides the one condition which will
2669
+ // refuse the real run is the case previews are for — but REPORTS there
2670
+ // instead of dying: `--dry-run returns 0` is a contract this file's tests
2671
+ // assert, and breaking it to surface a warning would trade a documented
2672
+ // exit code for a message.
2673
+ assert_scheduled_deprecations_clear(target, undefined, { previewOnly: args.dry_run });
2674
+
2581
2675
  if (!args.dry_run) {
2582
2676
  preflight(target, { resume: args.resume, ci: args.ci });
2583
2677
  }
@@ -14,15 +14,10 @@
14
14
  "maxLength": 64,
15
15
  "$comment": "Always the path-derived hyphen slug (`commit`, `council-default`) — identical to the `.claude/skills/` directory slug generated by condense.py. Agent-Skills-spec-compliant (lowercase letters, digits, hyphens; no leading/trailing/double hyphen; <=64 chars) so strict consumers like Zed accept projected SKILL.md entries. Colon names (`council:default`) are FORBIDDEN since the 2026-06 Zed fix; Claude Code's `/cluster:sub` invocation is path-derived from `commands/<cluster>/<sub>.md` and unaffected by this field."
16
16
  },
17
- "tier": {
18
- "type": "integer",
19
- "enum": [0, 1, 2],
20
- "description": "Command-surface tier per docs/contracts/command-surface-tiers.md. 0 = daily-driver (rendered in default `./agent-config --help`), 1 = power-user (rendered with `--tier=1`), 2 = maintenance/internal (rendered only with `--tier=all`). Default for new commands is 2 — promotion is gated by ADR criteria, never by author preference. BACK-COMPAT ALIAS since ADR-092: `visibility:` is the named source of truth (visible↔0, advanced↔1, internal↔2); `tier:` is retained as a derived integer alias and dual-emitted in the discovery manifest during the deprecation window. When both are present they MUST agree (enforced by lint_command_tiers.py)."
21
- },
22
17
  "visibility": {
23
18
  "type": "string",
24
19
  "enum": ["visible", "advanced", "internal"],
25
- "description": "Command-surface visibility (ADR-092) — the NAMED source of truth that supersedes the integer `tier:` proxy. `visible` = daily-driver (default `./agent-config --help`, ↔ tier 0), `advanced` = power-user (`--tier=1`, ↔ tier 1), `internal` = maintenance/hidden (`--tier=all` only, ↔ tier 2). Read by the surface classifier (commands ls/explain), the per-pack visible-command budget audit, and the discovery-manifest builder, each preferring `visibility:` and falling back to `tier:`. Default for new commands is `internal`; promotion is gated by ADR criteria per docs/contracts/command-surface-tiers.md."
20
+ "description": "Command-surface visibility per docs/contracts/command-surface-tiers.md — the sole command-surface classifier since the integer `tier:` alias was removed. `visible` = daily-driver (default `./agent-config --help`), `advanced` = power-user (`--tier=1`), `internal` = maintenance/hidden (`--tier=all` only). Read by the surface classifier (commands ls/explain), the per-pack visible-command budget audit, and the discovery-manifest builder. Default for new commands is `internal`; promotion is gated by ADR criteria. Presence and validity are enforced by lint_command_tiers.ts."
26
21
  },
27
22
  "description": {
28
23
  "type": "string",
@@ -71,7 +66,7 @@
71
66
  "type": "string",
72
67
  "minLength": 1,
73
68
  "maxLength": 200,
74
- "description": "6.0.0-C Phase 2 routing metadata. One-line existence justification: the single user intent this command serves (e.g. 'Implement a ticket end-to-end from its tracker id'). REQUIRED on every visible command (tier 0/1), enforced by scripts/lint_command_routing.py; optional on internal (tier 2) commands. The human-readable answer to 'why does this command exist?'"
69
+ "description": "6.0.0-C Phase 2 routing metadata. One-line existence justification: the single user intent this command serves (e.g. 'Implement a ticket end-to-end from its tracker id'). REQUIRED on every visible command (visibility `visible`/`advanced`), enforced by scripts/lint_command_routing.py; optional on `internal` commands. The human-readable answer to 'why does this command exist?'"
75
70
  },
76
71
  "routes_to": {
77
72
  "type": "array",
@@ -81,7 +76,7 @@
81
76
  "type": "string",
82
77
  "pattern": "^[a-z][a-z0-9]*(-[a-z0-9]+)*$"
83
78
  },
84
- "description": "6.0.0-C Phase 2 routing metadata. The data backbone the routing-eval lint checks against: the skill slug(s), cluster sub-command(s), or command(s) this command routes the intent to (e.g. ['refine-ticket', 'feature-planning'] or ['fix-ci']). Always hyphen slugs — colon identifiers forbidden since the 2026-06 Zed fix. REQUIRED on every visible command (tier 0/1), enforced by scripts/lint_command_routing.py; optional on internal commands."
79
+ "description": "6.0.0-C Phase 2 routing metadata. The data backbone the routing-eval lint checks against: the skill slug(s), cluster sub-command(s), or command(s) this command routes the intent to (e.g. ['refine-ticket', 'feature-planning'] or ['fix-ci']). Always hyphen slugs — colon identifiers forbidden since the 2026-06 Zed fix. REQUIRED on every visible command (visibility `visible`/`advanced`), enforced by scripts/lint_command_routing.py; optional on `internal` commands."
85
80
  },
86
81
  "category": {
87
82
  "type": "string",
@@ -96,7 +91,7 @@
96
91
  "pattern": "^[a-z][a-z0-9-]*(:[a-z][a-z0-9-]*)?$",
97
92
  "$comment": "Colon form stays ALLOWED here on purpose: `replaces` lists PRIOR names/aliases, and pre-2026-06 colon names (`commit:in-chunks`) are legitimate historical aliases."
98
93
  },
99
- "description": "6.0.0-C Phase 2 routing metadata. The alias / deprecation link: prior command name(s) or alias(es) this command absorbs or supersedes. Empty array `[]` when the command replaces nothing (the common case). REQUIRED-as-a-key on every visible command (tier 0/1) so the routing surface is explicit, enforced by scripts/lint_command_routing.py; optional on internal commands. Distinct from `superseded_by` (set on the OLD shim pointing forward); `replaces` is set on the NEW canonical command pointing back."
94
+ "description": "6.0.0-C Phase 2 routing metadata. The alias / deprecation link: prior command name(s) or alias(es) this command absorbs or supersedes. Empty array `[]` when the command replaces nothing (the common case). REQUIRED-as-a-key on every visible command (visibility `visible`/`advanced`) so the routing surface is explicit, enforced by scripts/lint_command_routing.py; optional on `internal` commands. Distinct from `superseded_by` (set on the OLD shim pointing forward); `replaces` is set on the NEW canonical command pointing back."
100
95
  },
101
96
  "type": {
102
97
  "type": "string",
@@ -163,8 +158,8 @@
163
158
  },
164
159
  "model_tier": {
165
160
  "type": "string",
166
- "enum": ["lite", "medium", "high", "inherit"],
167
- "description": "Vendor-neutral capability band the command needs (ADR-035). Commands project as Claude skills, so high/medium/lite produce a native Claude `model:` (high→opus, medium→sonnet, lite→haiku) when model.auto_switch=auto; the generator owns that mapping. inherit keeps the session model. Non-Claude agents are suggestion-only. No vendor model names here."
161
+ "enum": ["lite", "medium", "high", "frontier", "inherit"],
162
+ "description": "Vendor-neutral capability band the command needs (ADR-035, amended by ADR-232). Commands project as Claude skills, so frontier/high/medium/lite produce a native Claude `model:` when model.auto_switch=auto; the generator owns that mapping. inherit keeps the session model. `frontier` is the band above the strongest generally-recommended one - opt-in and expected to stay rare. Non-Claude agents are suggestion-only. No vendor model names here."
168
163
  },
169
164
  "context": {
170
165
  "type": "string",
@@ -31,7 +31,16 @@
31
31
  "compatibility": {
32
32
  "type": "string",
33
33
  "maxLength": 500,
34
- "description": "Agent-Skills spec field (optional): machine-readable declaration of environment requirements — system packages, network access, product/host constraints. Mirrors the public spec so media/doc skills can state requirements outside prose."
34
+ "deprecated": true,
35
+ "description": "DEPRECATED (announced 2026-08-14, removal maintainer-scheduled — see docs/MIGRATION.md § Scheduled deprecations). Agent-Skills spec field (optional): free-text declaration of environment requirements — system packages, network access, product/host constraints. Superseded by `harness_compat` (the machine-readable class) plus `runtime_requires` (the probeable detail). Still accepted and still emitted so the public-spec mirror keeps working during the window; new skills declare `harness_compat` instead."
36
+ },
37
+ "harness_compat": {
38
+ "type": "string",
39
+ "enum": [
40
+ "consumer-installed-deps"
41
+ ],
42
+ "description": "Structured replacement for the free-text `compatibility` class claim: WHAT KIND of harness constraint the skill carries, in a form `doctor`/`preflight` can branch on. Distinct from `runtime_requires`, which names WHICH bins/env/network to probe — a Python module such as `python-docx` is not a PATH binary, so it has no `bins` entry and would otherwise be invisible to any machine reader. Deliberately single-valued: the enum is derived from the only two skills that ever declared `compatibility`, and both attest exactly one class. A second value is added when a skill attests one, never pre-emptively — a speculative vocabulary is the pro-forma-field risk this schema already refuses for `runtime_requires`. `consumer-installed-deps` = the skill runs only where the consumer has itself installed named third-party libraries or engines; this package ships no runtime for it.",
43
+ "$comment": "road-to-skill-ecosystem-executable-payloads Phase 1, under the maintainer's 2026-08-14 resolution of blocker `compatibility-deprecation-is-a-consumer-visible-decision`: ADDITIVE — `harness_compat` lands beside `compatibility`, which is NOT removed here; its removal is approved in principle and scheduled through docs/MIGRATION.md § Scheduled deprecations, never dropped silently. Enum evidence (verbatim, the complete population of `compatibility` users): docx-authoring — \"Requires a consumer-installed OOXML library (python-docx …) … Ships zero runtime in this package.\"; pdf-tools — \"Requires consumer-installed PDF libraries: pypdf …, reportlab …; an OCR engine (e.g. tesseract) … Ships zero runtime in this package.\" Both state one class; the label generalises \"library\"/\"engine\" to \"deps\" and adds no claim neither text makes."
35
44
  },
36
45
  "runtime_requires": {
37
46
  "type": "object",
@@ -159,9 +168,10 @@
159
168
  "lite",
160
169
  "medium",
161
170
  "high",
171
+ "frontier",
162
172
  "inherit"
163
173
  ],
164
- "description": "Vendor-neutral capability band the skill needs (ADR-035). high/medium/lite project to a native Claude `model:` (high→opus, medium→sonnet, lite→haiku) when model.auto_switch=auto; the generator owns that mapping exclusively. inherit keeps the session model. Non-Claude agents are suggestion-only — the model-recommendation rule names the tier and the agent resolves its own model. No vendor model names here."
174
+ "description": "Vendor-neutral capability band the skill needs (ADR-035, amended by ADR-232). frontier/high/medium/lite project to a native Claude `model:` when model.auto_switch=auto; the generator owns that mapping exclusively. inherit keeps the session model. `frontier` means reasoning capability above the strongest generally-recommended band - opt-in and expected to stay rare, so work that genuinely needs it says so instead of inheriting it silently. Non-Claude agents are suggestion-only - the model-recommendation rule names the tier and the agent resolves its own model. No vendor model names here."
165
175
  },
166
176
  "context": {
167
177
  "type": "string",
@@ -34,8 +34,8 @@
34
34
  },
35
35
  "model_tier": {
36
36
  "type": "string",
37
- "enum": ["lite", "medium", "high", "inherit"],
38
- "description": "Vendor-neutral capability band (ADR-034/035). high/medium/lite project to a native Claude `model:` (high→opus, medium→sonnet, lite→haiku) via model_tier.ts; `inherit` is a documented passthrough sentinel (→ CC `model: inherit`), not a new mapping."
37
+ "enum": ["lite", "medium", "high", "frontier", "inherit"],
38
+ "description": "Vendor-neutral capability band (ADR-034/035, amended by ADR-232). frontier/high/medium/lite project to a native Claude `model:` via model_tier.ts; `inherit` is a documented passthrough sentinel (→ CC `model: inherit`), not a new mapping. `frontier` is the band above the strongest generally-recommended one - opt-in, expected to stay rare, and never the resolution of `inherit`."
39
39
  },
40
40
  "effort": {
41
41
  "type": "string",
@@ -61,6 +61,33 @@ export interface EolReport {
61
61
  compact_summary_records: number;
62
62
  marker_drift: boolean;
63
63
  };
64
+ /**
65
+ * State-destroyed vs state-captured, as ONE reading
66
+ * (road-to-inbox-harvest-2026-08-d-context-ledger Step 2.2).
67
+ *
68
+ * Before this, a compaction count and an advisory count sat in two places
69
+ * and nobody correlated them, so "how often was state destroyed with no
70
+ * capture in front of it" had no answer at all.
71
+ *
72
+ * HONEST LIMIT, stated in the shape rather than in prose only: the two
73
+ * sides come from different corpora — compactions are scanned out of
74
+ * transcripts, the advisory stamps live in per-session state files under a
75
+ * hashed key — and no field joins a transcript to its state file. So this
76
+ * is an AGGREGATE comparison over the same store, not a per-session join,
77
+ * and `join_basis` says so on every emitted report. A reader may compare
78
+ * the totals; a reader may NOT conclude that a specific compaction lacked
79
+ * a capture.
80
+ */
81
+ capture: {
82
+ /** Session-eol state files found. `null` ⇒ the directory is absent. */
83
+ state_files: number | null;
84
+ /** State files carrying an `advisory_fired_at` stamp. */
85
+ advisory_fired: number;
86
+ /** State files that additionally recorded "advised, but no envelope". */
87
+ advised_without_envelope: number;
88
+ /** Always `"aggregate"` today. See the doc comment. */
89
+ join_basis: 'aggregate';
90
+ };
64
91
  /** Pearson r of file bytes vs final context tokens over the readable set. */
65
92
  bytes_tokens_pearson_r: number | null;
66
93
  threshold_crossings: Record<string, number>;
@@ -130,6 +157,47 @@ export function scanSessionFile(file: string): SessionSummary | null {
130
157
  return { file, bytes, counters, last_ts };
131
158
  }
132
159
 
160
+ /** Where `session_eol_hook` persists its per-session state (gitignored). */
161
+ export const SESSION_EOL_STATE_REL = path.join('agents', 'runtime', 'state', 'session-eol');
162
+
163
+ /**
164
+ * The capture side of the Step-2.2 reading.
165
+ *
166
+ * `state_files: null` distinguishes "the directory is not there" from "it is
167
+ * there and empty" — an instrument that reports 0 for both cannot tell a quiet
168
+ * estate from one it cannot see, which is the failure the sibling ledger fixed
169
+ * for its own open-record set.
170
+ */
171
+ export function scanCaptureState(root: string): EolReport['capture'] {
172
+ const dir = path.join(root, SESSION_EOL_STATE_REL);
173
+ let names: string[];
174
+ try {
175
+ names = fs.readdirSync(dir).filter((n) => n.endsWith('.json'));
176
+ } catch {
177
+ return { state_files: null, advisory_fired: 0, advised_without_envelope: 0, join_basis: 'aggregate' };
178
+ }
179
+
180
+ let advisoryFired = 0;
181
+ let advisedWithoutEnvelope = 0;
182
+ for (const name of names) {
183
+ try {
184
+ const parsed: unknown = JSON.parse(fs.readFileSync(path.join(dir, name), 'utf-8'));
185
+ if (typeof parsed !== 'object' || parsed === null) continue;
186
+ const rec = parsed as Record<string, unknown>;
187
+ if (typeof rec['advisory_fired_at'] === 'string') advisoryFired++;
188
+ if (typeof rec['missing_envelope_warned_at'] === 'string') advisedWithoutEnvelope++;
189
+ } catch {
190
+ // A torn or hand-edited state file is data we do not have.
191
+ }
192
+ }
193
+ return {
194
+ state_files: names.length,
195
+ advisory_fired: advisoryFired,
196
+ advised_without_envelope: advisedWithoutEnvelope,
197
+ join_basis: 'aggregate',
198
+ };
199
+ }
200
+
133
201
  export function buildReport(root: string, now: Date = new Date()): EolReport {
134
202
  const files = listTranscriptFiles({ root });
135
203
  const sessions: SessionSummary[] = [];
@@ -176,6 +244,7 @@ export function buildReport(root: string, now: Date = new Date()): EolReport {
176
244
  compact_summary_records: summaryRecords,
177
245
  marker_drift: summaryRecords !== allEvents.length,
178
246
  },
247
+ capture: scanCaptureState(root),
179
248
  bytes_tokens_pearson_r: pearson(
180
249
  withUsage
181
250
  .filter((s) => (s.counters.final_context_tokens as number) > 1_000)
@@ -216,6 +285,18 @@ export function renderText(r: EolReport): string {
216
285
  ` trigger pre-tokens: min=${fmt(c.pre_tokens.min)} median=${fmt(c.pre_tokens.median)} max=${fmt(c.pre_tokens.max)}`,
217
286
  );
218
287
  }
288
+ // Step 2.2: the capture side, printed directly under the destruction side
289
+ // so the two are read together. `join_basis` is printed every time — a
290
+ // reader must not mistake an aggregate comparison for a per-session join.
291
+ const cap = r.capture;
292
+ if (cap.state_files === null) {
293
+ lines.push(` capture: no session-eol state directory — capture side UNOBSERVED (not zero)`);
294
+ } else {
295
+ lines.push(
296
+ ` capture: ${cap.advisory_fired} of ${cap.state_files} state file(s) advised, ` +
297
+ `${cap.advised_without_envelope} advised with no envelope [join: ${cap.join_basis}]`,
298
+ );
299
+ }
219
300
  if (c.post_tokens) {
220
301
  lines.push(` post-tokens median=${fmt(c.post_tokens.median)}`);
221
302
  }
@@ -254,6 +254,42 @@ const EXTERNAL_EXECUTION_HANDLERS = new Set(['shell', 'php', 'node']);
254
254
  * span lines and accept a bare key whose value sits somewhere below it.
255
255
  */
256
256
  const RUNTIME_REQUIRES_BLOCK_PATTERN = /^runtime_requires:[ \t]*(\{.*)?$/m;
257
+
258
+ /**
259
+ * The deprecated free-text `compatibility:` key and its structured successor
260
+ * `harness_compat:`, both top-level. Anchored at column 0 so a `compatibility:`
261
+ * nested inside another block (or the word inside a quoted description) is not
262
+ * read as the frontmatter key — the same reason the neighbouring patterns above
263
+ * use `^` rather than a bare substring test. A value is required after the colon
264
+ * so a bare key with the value on the next line is not counted as a declaration.
265
+ */
266
+ const COMPATIBILITY_KEY_PATTERN = /^compatibility:[ \t]*\S/m;
267
+ const HARNESS_COMPAT_KEY_PATTERN = /^harness_compat:[ \t]*\S/m;
268
+
269
+ /**
270
+ * `safety_mode: strict` is a claim made in the frontmatter about a skill whose
271
+ * execution path may mutate something. Nothing checked that the body honours it,
272
+ * so the claim could be pure metadata. These patterns recognise the two shapes
273
+ * the action-reference split allows (`skill-writing` § Action-reference split):
274
+ * an inline precondition that gates the write, or a deferral of the write-path
275
+ * steps into the skill's own `references/`.
276
+ *
277
+ * Deliberately a warning over a phrase list rather than an error over a required
278
+ * section: a skill that gates its write path some third way is doing the right
279
+ * thing, and a hard gate over a corpus of one strict skill would be a gate that
280
+ * mostly cannot fail — the defect class `road-to-skill-ecosystem-gate-integrity`
281
+ * exists to remove.
282
+ */
283
+ const STRICT_WRITE_GATE_PATTERNS = [
284
+ /--dry-run/i,
285
+ /\bnever silent[- ]run/i,
286
+ /\bpropose,? never\b/i,
287
+ /\bconfirms? before\b/i,
288
+ /\bexplicit (?:user )?confirmation\b/i,
289
+ /\basks? (?:the user )?(?:first|before)\b/i,
290
+ ];
291
+ const SKILL_REFERENCE_DEFERRAL_PATTERN = /\]\(\.{0,2}\/?references\//;
292
+
257
293
  const VALID_EXECUTION_FIELDS = new Set([
258
294
  'type',
259
295
  'handler',
@@ -1295,7 +1331,7 @@ function lint_wing4_boundaries(text: string): Issue[] {
1295
1331
  * for an optional parameter — a future caller that forgets it gets a confident
1296
1332
  * error about a declaration it never looked for.
1297
1333
  */
1298
- function lint_execution_metadata(execution: ExecutionBlock, frontmatter: string): Issue[] {
1334
+ function lint_execution_metadata(execution: ExecutionBlock, frontmatter: string, body = ''): Issue[] {
1299
1335
  const issues: Issue[] = [];
1300
1336
 
1301
1337
  const execType = execution.type;
@@ -1370,6 +1406,29 @@ function lint_execution_metadata(execution: ExecutionBlock, frontmatter: string)
1370
1406
  );
1371
1407
  }
1372
1408
 
1409
+ // road-to-skill-ecosystem-executable-payloads Phase 5 — the action-reference
1410
+ // split. A strict-mode skill either gates its write path inline, or says it
1411
+ // does not define the mutating workflow and defers those steps to its own
1412
+ // `references/` behind a precondition. Neither present → the strict claim is
1413
+ // frontmatter only, and the body reads like an ordinary skill.
1414
+ if (
1415
+ safetyMode === 'strict' &&
1416
+ body !== '' &&
1417
+ !STRICT_WRITE_GATE_PATTERNS.some((p) => p.test(body)) &&
1418
+ !SKILL_REFERENCE_DEFERRAL_PATTERN.test(body)
1419
+ ) {
1420
+ issues.push(
1421
+ new Issue(
1422
+ 'warning',
1423
+ 'strict_mode_missing_write_gate',
1424
+ "execution.safety_mode is 'strict' but the body names no precondition on the write " +
1425
+ 'path and defers no write-path steps to the skill\'s references/ — state the gate ' +
1426
+ 'inline (propose-then-confirm, --dry-run) or defer it, per skill-writing ' +
1427
+ '§ Action-reference split',
1428
+ ),
1429
+ );
1430
+ }
1431
+
1373
1432
  const timeout = execution.timeout_seconds;
1374
1433
  if (timeout !== undefined) {
1375
1434
  if (typeof timeout !== 'number' || !Number.isInteger(timeout) || timeout <= 0) {
@@ -1583,9 +1642,30 @@ export function lint_skill(p: string, text: string, absPath: string | null = nul
1583
1642
  }
1584
1643
  }
1585
1644
 
1645
+ // `compatibility:` is deprecated in favour of the structured
1646
+ // `harness_compat:` (schema, 2026-08-14). The field is NOT removed — the
1647
+ // schema still accepts it and both existing users still carry it — so this
1648
+ // is a warning, not an error: its job is to stop NEW adoption during the
1649
+ // window, which is the only thing a deprecation notice with no gate cannot
1650
+ // do. Regex, not a YAML parse, to match the file's existing convention.
1651
+ if (
1652
+ COMPATIBILITY_KEY_PATTERN.test(frontmatter) &&
1653
+ !HARNESS_COMPAT_KEY_PATTERN.test(frontmatter)
1654
+ ) {
1655
+ issues.push(
1656
+ new Issue(
1657
+ 'warning',
1658
+ 'deprecated_compatibility_field',
1659
+ "Frontmatter declares the deprecated free-text 'compatibility:' without the structured " +
1660
+ "'harness_compat:' that supersedes it — add harness_compat (see skill.schema.json and " +
1661
+ 'docs/MIGRATION.md § Scheduled deprecations); compatibility stays accepted until removal',
1662
+ ),
1663
+ );
1664
+ }
1665
+
1586
1666
  const execution = parseExecutionBlock(frontmatter);
1587
1667
  if (execution !== null) {
1588
- issues.push(...lint_execution_metadata(execution, frontmatter));
1668
+ issues.push(...lint_execution_metadata(execution, frontmatter, text.replace(FRONTMATTER_PATTERN, '')));
1589
1669
  }
1590
1670
 
1591
1671
  const tierMatch = TIER_PATTERN.exec(frontmatter);