@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
@@ -19,10 +19,24 @@
19
19
  * frontmatter keys it carries, and its position in the sorted catalogue. This
20
20
  * is the host's INPUT and it is fully file-measurable.
21
21
  *
22
- * OBSERVATION (self-reported) — which entries actually arrived as bare names
23
- * in a live session. Only the agent reading its own context can see this, so
24
- * it is supplied as a file and recorded AS a self-report. It is not
25
- * enforcement and nothing here pretends otherwise.
22
+ * OBSERVATION — which entries actually arrived, and how many did not. This
23
+ * side has TWO sources and every record says which one it came from:
24
+ *
25
+ * `self-report` — an agent reading its own context lists the bare names.
26
+ * Only the agent can see this. It is not enforcement and nothing here
27
+ * pretends otherwise. This is the claude-shaped path.
28
+ *
29
+ * `host-event` — the host publishes its own truncation on a machine
30
+ * channel. `codex exec --json` emits an `item.completed` error event whose
31
+ * message states how many entries it dropped, so the count is READ, never
32
+ * transcribed. This is the codex-shaped path and it is deterministic.
33
+ *
34
+ * Hosts do not truncate the same way, so a record also carries a
35
+ * `truncation_mode`: `per-entry` (some entries arrive bare, others full —
36
+ * claude) versus `budget-strip-and-drop` (every description stripped, then N
37
+ * entries dropped wholesale — codex). Pooling the two into one verdict would
38
+ * average two different mechanisms into a number describing neither, so the
39
+ * report is always per host.
26
40
  *
27
41
  * The join of the two answers the question the census could not: not "how many
28
42
  * are bare" but WHICH PROPERTY separates bare from described. That is the
@@ -33,370 +47,213 @@
33
47
  * while an earlier one was bare, which no head-N budget explains.
34
48
  *
35
49
  * PRIVACY BY CONSTRUCTION. An observation record carries skill names, integer
36
- * counts and a host label. The record type has no field able to hold prompt
37
- * text, file bodies, paths outside the catalogue, or user content — the same
38
- * shape `domain-safety-pii` § Surface 2 requires of a log event. Do not widen
39
- * it with a free-form field.
50
+ * counts, a host label and two closed enums. The record type has no field able
51
+ * to hold prompt text, file bodies, paths outside the catalogue, or user
52
+ * content — the same shape `domain-safety-pii` § Surface 2 requires of a log
53
+ * event. The codex path reads a host message and keeps only the integer in it;
54
+ * the message text itself never reaches a record. Do not widen it with a
55
+ * free-form field.
56
+ *
57
+ * A ZERO IS NEVER INFERRED FROM SILENCE. If the host's budget event is absent
58
+ * or unparseable — reworded upstream, or removed — the capture fails loudly
59
+ * rather than recording `dropped: 0`, because a broken parser and a fixed
60
+ * defect would otherwise look identical. Recording a genuine no-truncation run
61
+ * takes an explicit `--assert-no-truncation`.
40
62
  *
41
- * Exit codes: 0 report produced · 1 usage/IO error or an empty catalogue root
42
- * (a catalogue scan that found nothing must never read as a clean result).
63
+ * Exit codes: 0 report produced · 1 usage/IO error, an empty catalogue root
64
+ * (a catalogue scan that found nothing must never read as a clean result), or
65
+ * an unreadable host observation.
43
66
  */
44
67
 
45
68
  import * as fs from 'node:fs';
69
+ import * as os from 'node:os';
46
70
  import * as path from 'node:path';
47
71
  import { fileURLToPath } from 'node:url';
48
72
 
73
+ import {
74
+ DEFAULT_CATALOGUE_ROOTS,
75
+ OBSERVATION_LOG,
76
+ analyzeSelector,
77
+ buildHostEventRecord,
78
+ buildObservationRecord,
79
+ formatPerHostVerdicts,
80
+ formatReport,
81
+ knownHostLimits,
82
+ measureCatalogueVolume,
83
+ parseHostBudgetEvent,
84
+ readObservationLog,
85
+ readProjectedCatalogue,
86
+ type SelectorReport,
87
+ } from './_lib/skill_catalogue.js';
88
+
89
+ // Re-exported so the CLI module stays the one public name for this tool.
90
+ export * from './_lib/skill_catalogue.js';
91
+
49
92
  const _HERE = fileURLToPath(import.meta.url);
50
93
  const REPO = path.resolve(path.dirname(_HERE), '..', '..');
51
94
 
52
- /** Catalogue roots tried in order; the first that exists wins. */
53
- export const DEFAULT_CATALOGUE_ROOTS = ['.claude/skills', 'src/skills'] as const;
54
-
55
- /** Where observations accumulate. Append-only, one JSON object per line. */
56
- export const OBSERVATION_LOG = path.join(
57
- 'agents',
58
- 'evidence',
59
- 'metrics',
60
- 'skill-catalogue.jsonl',
61
- );
62
-
63
- export interface CatalogueEntry {
64
- /** Skill directory name — the identifier the host lists. */
65
- name: string;
66
- /** 1-based position in the alphabetically sorted catalogue. */
67
- position: number;
68
- /** Whether the projected SKILL.md declares a `description:`. */
69
- hasDescription: boolean;
70
- /** Character length of the declared description; 0 when absent. */
71
- descriptionLength: number;
72
- /** Top-level frontmatter keys the entry declares. */
73
- frontmatterKeys: string[];
74
- }
75
-
76
- export interface SelectorCandidate {
77
- /** Stable identifier for the property under test. */
78
- id: string;
79
- /** Human-readable statement of what the property is. */
80
- describes: string;
81
- /** True when the property's values are disjoint across the two groups. */
82
- separates: boolean;
83
- /** Why it does or does not separate, in one line. */
84
- detail: string;
95
+ function resolveCatalogueRoot(explicit: string | null): string {
96
+ if (explicit) {
97
+ if (!fs.existsSync(explicit)) {
98
+ throw new Error(`catalogue root does not exist: ${explicit}`);
99
+ }
100
+ return explicit;
101
+ }
102
+ for (const candidate of DEFAULT_CATALOGUE_ROOTS) {
103
+ const abs = path.join(REPO, candidate);
104
+ if (fs.existsSync(abs)) return abs;
105
+ }
106
+ throw new Error(
107
+ `no catalogue root found — tried ${DEFAULT_CATALOGUE_ROOTS.join(', ')} under ${REPO}`,
108
+ );
85
109
  }
86
110
 
87
- export interface SelectorReport {
88
- catalogueRoot: string;
89
- entriesTotal: number;
90
- observedBare: string[];
91
- observedDescribed: string[];
92
- /** Observed names that the projection does not know. */
93
- unknownObserved: string[];
94
- candidates: SelectorCandidate[];
95
- verdict: 'selector-found' | 'no-selector' | 'insufficient-observation';
111
+ function argValue(flag: string): string | null {
112
+ const index = process.argv.indexOf(flag);
113
+ if (index === -1 || index + 1 >= process.argv.length) return null;
114
+ return process.argv[index + 1]!;
96
115
  }
97
116
 
98
- /** Frontmatter block between the leading `---` fences, or "" when absent. */
99
- function frontmatterOf(content: string): string {
100
- if (!content.startsWith('---')) return '';
101
- const end = content.indexOf('\n---', 3);
102
- if (end === -1) return '';
103
- return content.slice(3, end);
117
+ /** `~` expanded against the current user's home. */
118
+ function expandHome(p: string): string {
119
+ return p.startsWith('~/') ? path.join(os.homedir(), p.slice(2)) : p;
104
120
  }
105
121
 
106
122
  /**
107
- * Top-level keys of a frontmatter block. Deliberately shallow: a nested key is
108
- * not a property the host's catalogue selector could plausibly read, and a full
109
- * YAML parse would drag a dependency into a measurement tool.
123
+ * `--limits` — publish the per-host picture: what each host was measured to
124
+ * deliver, and whether the observed hosts truncate the same way.
110
125
  */
111
- function topLevelKeys(frontmatter: string): string[] {
112
- const keys: string[] = [];
113
- for (const line of frontmatter.split('\n')) {
114
- const match = /^([A-Za-z_][A-Za-z0-9_-]*):/.exec(line);
115
- if (match) keys.push(match[1]!);
116
- }
117
- return keys;
118
- }
126
+ function runLimitsMode(): number {
127
+ const records = readObservationLog(path.join(REPO, OBSERVATION_LOG));
128
+ const limits = knownHostLimits(records);
119
129
 
120
- /**
121
- * The declared description with surrounding quotes stripped, or "".
122
- *
123
- * A block scalar (`description: >-` / `|`) puts the text on the FOLLOWING
124
- * lines, so taking the remainder of the header line would yield the
125
- * two-character indicator and report `descriptionLength: 2`. That would feed
126
- * the length-based selector candidate noise, and a length threshold could be
127
- * reported or refuted on it. Block scalars are therefore folded properly.
128
- */
129
- function descriptionOf(frontmatter: string): string {
130
- const lines = frontmatter.split('\n');
131
- for (let i = 0; i < lines.length; i += 1) {
132
- const match = /^description:\s*(.*)$/.exec(lines[i]!);
133
- if (!match) continue;
134
- const rest = match[1]!.trim();
135
-
136
- if (/^[|>][-+]?\d*$/.test(rest)) {
137
- const body: string[] = [];
138
- for (let j = i + 1; j < lines.length; j += 1) {
139
- const line = lines[j]!;
140
- if (line.trim() === '') {
141
- body.push('');
142
- continue;
143
- }
144
- if (!/^\s/.test(line)) break; // dedented, so the block ended
145
- body.push(line.trim());
146
- }
147
- return body.join(' ').trim();
148
- }
149
-
150
- return rest.replace(/^["']|["']$/g, '');
130
+ if (process.argv.includes('--json')) {
131
+ process.stdout.write(
132
+ `${JSON.stringify({ hosts: [...limits.values()], observations: records.length }, null, 2)}\n`,
133
+ );
134
+ return 0;
151
135
  }
152
- return '';
153
- }
154
-
155
- /**
156
- * Read every `<root>/<name>/SKILL.md` into a catalogue entry, sorted by name —
157
- * the order a host listing follows and therefore the order a positional
158
- * hypothesis is measured against.
159
- */
160
- export function readProjectedCatalogue(root: string): CatalogueEntry[] {
161
- // Membership is decided by "does <name>/SKILL.md resolve", never by
162
- // `Dirent.isDirectory()`. The host-facing projection is a tree of SYMLINKS
163
- // into `dist/agent-src/skills/`, and a Dirent for a symlink reports
164
- // `isDirectory() === false` — that filter silently read 47 of 289 entries
165
- // and reported the result as a complete catalogue. `existsSync` follows
166
- // the link, so the scan sees what the host sees.
167
- const names = fs
168
- .readdirSync(root)
169
- .filter((n) => fs.existsSync(path.join(root, n, 'SKILL.md')))
170
- .sort();
171
136
 
172
- return names.map((name, index) => {
173
- const content = fs.readFileSync(path.join(root, name, 'SKILL.md'), 'utf-8');
174
- const frontmatter = frontmatterOf(content);
175
- const description = descriptionOf(frontmatter);
176
- return {
177
- name,
178
- position: index + 1,
179
- hasDescription: description.length > 0,
180
- descriptionLength: description.length,
181
- frontmatterKeys: topLevelKeys(frontmatter),
182
- };
183
- });
137
+ process.stdout.write(`${formatPerHostVerdicts(records)}\n\n`);
138
+ if (limits.size === 0) {
139
+ process.stdout.write(
140
+ 'measured truncations: none.\nOnly a host that publishes its own dropped count yields one; a\nself-reported observation states which entries arrived bare, which is a\nselector question, not a ceiling.\n',
141
+ );
142
+ return 0;
143
+ }
144
+ process.stdout.write('measured truncations:\n');
145
+ for (const limit of [...limits.values()].sort((a, b) => a.host.localeCompare(b.host))) {
146
+ process.stdout.write(
147
+ ` ${limit.host}: dropped ${limit.droppedEntries} entries on ${limit.observedAt}, ` +
148
+ `at a projection of ${limit.projectedVolume}\n`,
149
+ );
150
+ }
151
+ return 0;
184
152
  }
185
153
 
186
- /** True when two number sets occupy disjoint ranges in either direction. */
187
- function rangesAreDisjoint(a: number[], b: number[]): boolean {
188
- if (a.length === 0 || b.length === 0) return false;
189
- const maxA = Math.max(...a);
190
- const minA = Math.min(...a);
191
- const maxB = Math.max(...b);
192
- const minB = Math.min(...b);
193
- return maxA < minB || maxB < minA;
154
+ /** `--volume <host-root>` — the projection half for one host, byte-accurate. */
155
+ function runVolumeMode(rootArg: string): number {
156
+ const root = expandHome(rootArg);
157
+ if (!fs.existsSync(root)) {
158
+ process.stderr.write(`❌ host root does not exist: ${root}\n`);
159
+ return 1;
160
+ }
161
+ const volume = measureCatalogueVolume(argValue('--host') ?? path.basename(root).replace(/^\./, ''), root);
162
+ if (process.argv.includes('--json')) {
163
+ process.stdout.write(`${JSON.stringify(volume, null, 2)}\n`);
164
+ return 0;
165
+ }
166
+ process.stdout.write(
167
+ `host: ${volume.host}\nroot: ${volume.root}\n` +
168
+ `skill entries: ${volume.skillEntries}\ncommand entries: ${volume.commandEntries}\n` +
169
+ `artefacts offered: ${volume.artefacts}\n` +
170
+ `description payload: ${volume.descriptionBytes} bytes\n\n` +
171
+ 'This is the PROJECTION half — what the host is offered, not what it delivered.\n',
172
+ );
173
+ return 0;
194
174
  }
195
175
 
196
176
  /**
197
- * Join the projection against an observation and report which property, if
198
- * any, separates the bare entries from the described ones.
177
+ * `--host-event <file|->` — record what the host itself published.
199
178
  *
200
- * `observedBare` is the list of entries a live session saw without a
201
- * description. Everything else in the projection is treated as observed
202
- * described, which is the honest reading of a whole-catalogue observation —
203
- * a partial observation would need its own described list, so the caller
204
- * passes one when it has it.
179
+ * The deterministic path. `-` reads stdin, so the capture composes directly:
180
+ * codex exec --json --skip-git-repo-check "reply with exactly: OK" \
181
+ * | capture_skill_catalogue --host-event - --host codex --host-root ~/.codex
205
182
  */
206
- export function analyzeSelector(
207
- projected: CatalogueEntry[],
208
- observedBare: readonly string[],
209
- observedDescribed?: readonly string[],
210
- ): Omit<SelectorReport, 'catalogueRoot'> {
211
- const known = new Map(projected.map((e) => [e.name, e]));
212
- const bare = observedBare.filter((n) => known.has(n));
213
- const describedInput =
214
- observedDescribed ?? projected.map((e) => e.name).filter((n) => !bare.includes(n));
215
- const described = describedInput.filter((n) => known.has(n));
216
-
217
- // Both self-reported lists are validated, not only the bare one: a typo in
218
- // --described silently shrank the described set and weakened the analysis,
219
- // while the same typo in --observed was surfaced.
220
- const unknownObserved = [
221
- ...new Set([
222
- ...observedBare.filter((n) => !known.has(n)),
223
- ...(observedDescribed ?? []).filter((n) => !known.has(n)),
224
- ]),
225
- ].sort();
226
-
227
- const base = {
228
- entriesTotal: projected.length,
229
- observedBare: [...bare].sort(),
230
- observedDescribed: [...described].sort(),
231
- unknownObserved,
232
- };
233
-
234
- if (bare.length === 0 || described.length === 0) {
235
- return {
236
- ...base,
237
- candidates: [],
238
- verdict: 'insufficient-observation' as const,
239
- };
183
+ function runHostEventMode(source: string): number {
184
+ const stream =
185
+ source === '-'
186
+ ? fs.readFileSync(0, 'utf-8')
187
+ : fs.readFileSync(expandHome(source), 'utf-8');
188
+
189
+ const hostRootArg = argValue('--host-root');
190
+ if (!hostRootArg) {
191
+ process.stderr.write(
192
+ '❌ --host-event requires --host-root <dir> — the dropped count is only\n' +
193
+ ' meaningful against the number of artefacts the host was offered.\n',
194
+ );
195
+ return 1;
240
196
  }
241
-
242
- const bareEntries = bare.map((n) => known.get(n)!);
243
- const describedEntries = described.map((n) => known.get(n)!);
244
- const candidates: SelectorCandidate[] = [];
245
-
246
- // 1. The projection itself — does the entry even declare a description?
247
- const bareWithDesc = bareEntries.filter((e) => e.hasDescription).length;
248
- const describedWithoutDesc = describedEntries.filter((e) => !e.hasDescription).length;
249
- candidates.push({
250
- id: 'declares-description',
251
- describes: 'the projected SKILL.md declares a `description:`',
252
- separates: bareWithDesc === 0 && describedWithoutDesc === 0,
253
- detail: `${bareWithDesc}/${bareEntries.length} bare entries declare one; ${describedWithoutDesc}/${describedEntries.length} described entries do not`,
254
- });
255
-
256
- // 2. The head-N hypothesis the census proposed — falsifiable here.
257
- const barePositions = bareEntries.map((e) => e.position);
258
- const describedPositions = describedEntries.map((e) => e.position);
259
- const positional = Math.max(...describedPositions) < Math.min(...barePositions);
260
- candidates.push({
261
- id: 'positional-head',
262
- describes: 'every described entry sorts before every bare entry (a head-N budget)',
263
- separates: positional,
264
- detail: positional
265
- ? `described top out at #${Math.max(...describedPositions)}, bare start at #${Math.min(...barePositions)}`
266
- : `described reach #${Math.max(...describedPositions)} while bare start at #${Math.min(...barePositions)} — the ranges overlap, so no head-N cut explains this`,
267
- });
268
-
269
- // 3. Description length as a budget proxy.
270
- const lengthSeparates = rangesAreDisjoint(
271
- bareEntries.map((e) => e.descriptionLength),
272
- describedEntries.map((e) => e.descriptionLength),
273
- );
274
- candidates.push({
275
- id: 'description-length',
276
- describes: 'description length ranges do not overlap between the groups',
277
- separates: lengthSeparates,
278
- detail: lengthSeparates
279
- ? 'a length threshold separates the two groups'
280
- : 'length ranges overlap',
281
- });
282
-
283
- // 4. Any frontmatter key whose presence tracks the split exactly.
284
- const allKeys = new Set<string>();
285
- for (const entry of projected) for (const key of entry.frontmatterKeys) allKeys.add(key);
286
- for (const key of [...allKeys].sort()) {
287
- const bareHas = bareEntries.filter((e) => e.frontmatterKeys.includes(key)).length;
288
- const describedHas = describedEntries.filter((e) =>
289
- e.frontmatterKeys.includes(key),
290
- ).length;
291
- const separates =
292
- (bareHas === 0 && describedHas === describedEntries.length) ||
293
- (describedHas === 0 && bareHas === bareEntries.length);
294
- if (!separates) continue;
295
- candidates.push({
296
- id: `frontmatter:${key}`,
297
- describes: `presence of the \`${key}:\` frontmatter key`,
298
- separates: true,
299
- detail: `bare ${bareHas}/${bareEntries.length}, described ${describedHas}/${describedEntries.length}`,
300
- });
197
+ const host = argValue('--host') ?? 'unknown';
198
+ const volume = measureCatalogueVolume(host, expandHome(hostRootArg));
199
+ if (volume.artefacts === 0) {
200
+ process.stderr.write(`❌ host root ${volume.root} offers no catalogue artefacts\n`);
201
+ return 1;
301
202
  }
302
203
 
303
- return {
304
- ...base,
305
- candidates,
306
- verdict: candidates.some((c) => c.separates)
307
- ? ('selector-found' as const)
308
- : ('no-selector' as const),
309
- };
310
- }
311
-
312
- /** One append-only observation record. No field can hold free-form content. */
313
- export interface ObservationRecord {
314
- schema: 1;
315
- observed_at: string;
316
- host: string;
317
- entries_total: number;
318
- bare_count: number;
319
- described_count: number;
320
- bare_names: string[];
321
- verdict: SelectorReport['verdict'];
322
- separating_candidates: string[];
323
- }
324
-
325
- export function buildObservationRecord(
326
- report: SelectorReport,
327
- host: string,
328
- observedAt: string,
329
- ): ObservationRecord {
330
- return {
331
- schema: 1,
332
- observed_at: observedAt,
333
- host,
334
- entries_total: report.entriesTotal,
335
- bare_count: report.observedBare.length,
336
- described_count: report.observedDescribed.length,
337
- bare_names: report.observedBare,
338
- verdict: report.verdict,
339
- separating_candidates: report.candidates.filter((c) => c.separates).map((c) => c.id),
340
- };
341
- }
342
-
343
- export function formatReport(report: SelectorReport): string {
344
- const lines: string[] = [];
345
- lines.push(`catalogue root: ${report.catalogueRoot}`);
346
- lines.push(`entries: ${report.entriesTotal}`);
347
- lines.push(
348
- `observed: ${report.observedBare.length} bare · ${report.observedDescribed.length} described (self-reported)`,
349
- );
350
- if (report.unknownObserved.length > 0) {
351
- lines.push(
352
- `⚠️ ${report.unknownObserved.length} observed name(s) absent from the projection: ${report.unknownObserved.join(', ')}`,
353
- );
354
- }
355
- if (report.verdict === 'insufficient-observation') {
356
- lines.push('');
357
- lines.push(
358
- 'verdict: insufficient-observation — an observation needs at least one bare AND one described entry to separate anything.',
204
+ const event = parseHostBudgetEvent(stream);
205
+ if (event === null) {
206
+ if (!process.argv.includes('--assert-no-truncation')) {
207
+ process.stderr.write(
208
+ '❌ no parseable skills-budget event in this stream.\n' +
209
+ ' That is UNKNOWN, not zero: a reworded or removed host message looks\n' +
210
+ ' exactly like a fixed defect. If this run genuinely did not truncate,\n' +
211
+ ' say so with --assert-no-truncation.\n',
212
+ );
213
+ return 1;
214
+ }
215
+ process.stdout.write(
216
+ `host: ${host}\noffered: ${volume.artefacts}\ndropped: 0 (asserted — no budget event in the stream)\n`,
359
217
  );
360
- return lines.join('\n');
361
- }
362
- lines.push('');
363
- lines.push('selector candidates:');
364
- for (const candidate of report.candidates) {
365
- lines.push(` ${candidate.separates ? '✅' : '❌'} ${candidate.id} — ${candidate.describes}`);
366
- lines.push(` ${candidate.detail}`);
218
+ return 0;
367
219
  }
368
- lines.push('');
369
- lines.push(
370
- report.verdict === 'selector-found'
371
- ? 'verdict: selector-found — a delivery fix can act on the separating property above.'
372
- : 'verdict: no-selector — no measured property separates the groups; the selector is host-internal on this evidence. Publish the null rather than guessing a fix.',
220
+
221
+ process.stdout.write(
222
+ `host: ${host}\nroot: ${volume.root}\n` +
223
+ `artefacts offered: ${volume.artefacts} (${volume.skillEntries} skills + ${volume.commandEntries} commands)\n` +
224
+ `description payload: ${volume.descriptionBytes} bytes\n` +
225
+ `dropped by the host: ${event.droppedCount}\n` +
226
+ `descriptions stripped: ${event.descriptionsStripped ? 'yes — all of them' : 'no'}\n\n` +
227
+ 'Source: host-event (read from the host\'s own JSON channel, not self-reported).\n' +
228
+ 'No delivered count is printed: the offered figure above is THIS tool\'s\n' +
229
+ 'projection and the dropped figure is the host\'s, and a controlled probe\n' +
230
+ 'showed they do not share a denominator. Subtracting them would invent a number.\n',
373
231
  );
374
- return lines.join('\n');
375
- }
376
232
 
377
- function resolveCatalogueRoot(explicit: string | null): string {
378
- if (explicit) {
379
- if (!fs.existsSync(explicit)) {
380
- throw new Error(`catalogue root does not exist: ${explicit}`);
233
+ if (process.argv.includes('--record')) {
234
+ const stampedAt = argValue('--observed-at');
235
+ if (!stampedAt) {
236
+ process.stderr.write('❌ --record requires --observed-at <ISO date>\n');
237
+ return 1;
381
238
  }
382
- return explicit;
383
- }
384
- for (const candidate of DEFAULT_CATALOGUE_ROOTS) {
385
- const abs = path.join(REPO, candidate);
386
- if (fs.existsSync(abs)) return abs;
239
+ const logPath = path.join(REPO, OBSERVATION_LOG);
240
+ fs.mkdirSync(path.dirname(logPath), { recursive: true });
241
+ const record = buildHostEventRecord(host, stampedAt, volume.artefacts, event, volume.skillEntries);
242
+ fs.appendFileSync(logPath, `${JSON.stringify(record)}\n`);
243
+ process.stdout.write(`recorded → ${OBSERVATION_LOG}\n`);
387
244
  }
388
- throw new Error(
389
- `no catalogue root found — tried ${DEFAULT_CATALOGUE_ROOTS.join(', ')} under ${REPO}`,
390
- );
391
- }
392
-
393
- function argValue(flag: string): string | null {
394
- const index = process.argv.indexOf(flag);
395
- if (index === -1 || index + 1 >= process.argv.length) return null;
396
- return process.argv[index + 1]!;
245
+ return 0;
397
246
  }
398
247
 
399
248
  function main(): number {
249
+ if (process.argv.includes('--limits')) return runLimitsMode();
250
+
251
+ const volumeRoot = argValue('--volume');
252
+ if (volumeRoot) return runVolumeMode(volumeRoot);
253
+
254
+ const hostEvent = argValue('--host-event');
255
+ if (hostEvent) return runHostEventMode(hostEvent);
256
+
400
257
  const root = resolveCatalogueRoot(argValue('--catalogue-root'));
401
258
  const projected = readProjectedCatalogue(root);
402
259