@geraldmaron/construct 1.1.1 → 1.2.1

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 (426) hide show
  1. package/README.md +30 -19
  2. package/bin/construct +487 -87
  3. package/bin/construct-postinstall.mjs +30 -2
  4. package/examples/distribution/README.md +42 -0
  5. package/examples/distribution/manifest.json +53 -0
  6. package/examples/distribution/sources/adr.md +84 -0
  7. package/examples/distribution/sources/deck-one-pager.md +65 -0
  8. package/examples/distribution/sources/prd-platform.md +161 -0
  9. package/examples/distribution/sources/research-brief.md +80 -0
  10. package/examples/distribution/sources/rfc-platform.md +82 -0
  11. package/examples/distribution/sources/runbook.md +103 -0
  12. package/examples/distribution/sources/strategy.md +88 -0
  13. package/lib/adapters-sync.mjs +67 -0
  14. package/lib/artifact-gate-notice.mjs +38 -0
  15. package/lib/artifact-manifest.mjs +258 -0
  16. package/lib/artifact-release-gate.mjs +171 -0
  17. package/lib/artifact-reviewers.mjs +68 -0
  18. package/lib/artifact-type-from-path.mjs +79 -0
  19. package/lib/artifact-workflow.mjs +252 -0
  20. package/lib/audit-skills.mjs +4 -0
  21. package/lib/audit-specialists.mjs +285 -0
  22. package/lib/audit-trail.mjs +1 -1
  23. package/lib/auto-docs.mjs +90 -19
  24. package/lib/beads/drift.mjs +27 -6
  25. package/lib/beads-client.mjs +49 -121
  26. package/lib/beads-optimistic.mjs +5 -12
  27. package/lib/brand-fonts.mjs +93 -0
  28. package/lib/brand-prose.mjs +214 -0
  29. package/lib/brand-tokens.mjs +92 -0
  30. package/lib/bridges/copilot-proxy.mjs +13 -26
  31. package/lib/capability-ledger.mjs +156 -0
  32. package/lib/certification/artifact-fixtures.mjs +132 -0
  33. package/lib/certification/artifact-gates.mjs +63 -0
  34. package/lib/certification/artifact-provenance.mjs +97 -0
  35. package/lib/certification/canonical-scenarios.mjs +78 -0
  36. package/lib/certification/cli.mjs +191 -0
  37. package/lib/certification/dashboard-api.mjs +71 -0
  38. package/lib/certification/demo-parity.mjs +116 -0
  39. package/lib/certification/document-io-fixtures.mjs +246 -0
  40. package/lib/certification/document-workflow.mjs +97 -0
  41. package/lib/certification/eval-bridge.mjs +77 -0
  42. package/lib/certification/model-routing.mjs +149 -0
  43. package/lib/certification/prompt-budget.mjs +119 -0
  44. package/lib/certification/rc-gate.mjs +206 -0
  45. package/lib/certification/real-llm-scenarios.mjs +303 -0
  46. package/lib/certification/role-cards.mjs +113 -0
  47. package/lib/certification/role-overlays.mjs +117 -0
  48. package/lib/certification/run.mjs +122 -0
  49. package/lib/certification/runner.mjs +323 -0
  50. package/lib/certification/scenarios.mjs +51 -0
  51. package/lib/certification/skill-inventory.mjs +289 -0
  52. package/lib/certification/skill-scenarios.mjs +147 -0
  53. package/lib/certification/specialist-contracts.mjs +85 -0
  54. package/lib/certification/specialist-scenarios.mjs +175 -0
  55. package/lib/certification/stale-impact.mjs +146 -0
  56. package/lib/certification/status.mjs +252 -0
  57. package/lib/certification/store.mjs +77 -0
  58. package/lib/chat/cli.mjs +333 -0
  59. package/lib/chat/command-suggest.mjs +161 -0
  60. package/lib/chat/commands.mjs +215 -0
  61. package/lib/chat/config.mjs +142 -0
  62. package/lib/chat/context-compactor.mjs +250 -0
  63. package/lib/chat/context-continuation.mjs +253 -0
  64. package/lib/chat/continuation-source.mjs +58 -0
  65. package/lib/chat/demo-guide.mjs +61 -0
  66. package/lib/chat/design-tokens.mjs +91 -0
  67. package/lib/chat/desktop-binary.mjs +79 -0
  68. package/lib/chat/desktop-build.mjs +130 -0
  69. package/lib/chat/desktop-launcher.mjs +133 -0
  70. package/lib/chat/evidence.mjs +145 -0
  71. package/lib/chat/export.mjs +74 -0
  72. package/lib/chat/harness/driver.mjs +91 -0
  73. package/lib/chat/list-picker.mjs +112 -0
  74. package/lib/chat/model-picker.mjs +356 -0
  75. package/lib/chat/openrouter-fallback.mjs +151 -0
  76. package/lib/chat/permission-prompt.mjs +33 -0
  77. package/lib/chat/picker-catalog.mjs +45 -0
  78. package/lib/chat/policy-telemetry.mjs +34 -0
  79. package/lib/chat/present.mjs +246 -0
  80. package/lib/chat/session-context.mjs +39 -0
  81. package/lib/chat/session-persist.mjs +73 -0
  82. package/lib/chat/session-restore.mjs +71 -0
  83. package/lib/chat/session-settings.mjs +53 -0
  84. package/lib/chat/system-prompt.mjs +52 -0
  85. package/lib/chat/transparency.mjs +93 -0
  86. package/lib/chat/tui/color-scheme.mjs +42 -0
  87. package/lib/chat/tui/markdown.mjs +123 -0
  88. package/lib/chat/tui/presentation.mjs +100 -0
  89. package/lib/chat/tui/render.mjs +500 -0
  90. package/lib/chat/tui/turn-block.mjs +284 -0
  91. package/lib/chat/tui/turn-present.mjs +18 -0
  92. package/lib/chat/tui/turn-state.mjs +88 -0
  93. package/lib/chat/tui/usage.mjs +122 -0
  94. package/lib/chat/web-commands.mjs +146 -0
  95. package/lib/chat/web-launcher.mjs +63 -0
  96. package/lib/chat/web-picker-keys.mjs +46 -0
  97. package/lib/chat/web-session.mjs +159 -0
  98. package/lib/cli-commands.mjs +146 -15
  99. package/lib/cli-service-inventory.mjs +42 -0
  100. package/lib/comment-lint.mjs +6 -4
  101. package/lib/config/intake-policy.mjs +209 -0
  102. package/lib/config/project-config.mjs +11 -2
  103. package/lib/config/schema.mjs +86 -6
  104. package/lib/config/source-targets.mjs +311 -0
  105. package/lib/contract-schemas/decision.json +50 -0
  106. package/lib/contract-schemas/implementation.json +51 -0
  107. package/lib/contract-schemas/review-report.json +32 -0
  108. package/lib/contract-schemas/test-report.json +43 -0
  109. package/lib/contracts/construct-handoff.mjs +60 -0
  110. package/lib/contracts/validate.mjs +32 -3
  111. package/lib/contracts/violation-log.mjs +58 -5
  112. package/lib/dashboard-demo.mjs +71 -0
  113. package/lib/dashboard-static.mjs +19 -5
  114. package/lib/decisions/registry.mjs +5 -3
  115. package/lib/deck-export-pptx.mjs +1152 -0
  116. package/lib/demo-recording.mjs +142 -0
  117. package/lib/demo-script.mjs +114 -0
  118. package/lib/demo-surface.mjs +249 -0
  119. package/lib/demo.mjs +598 -140
  120. package/lib/diagram-export.mjs +192 -0
  121. package/lib/diagram.mjs +13 -11
  122. package/lib/docs-verify.mjs +25 -7
  123. package/lib/doctor/index.mjs +3 -1
  124. package/lib/doctor/project-adapters.mjs +44 -0
  125. package/lib/doctor/source-checkout.mjs +16 -0
  126. package/lib/doctor/watchers/cx-budget.mjs +98 -0
  127. package/lib/doctor/watchers/graph-staleness.mjs +52 -0
  128. package/lib/doctor/watchers/process-pressure.mjs +4 -3
  129. package/lib/document-export.mjs +384 -38
  130. package/lib/document-extract.mjs +44 -4
  131. package/lib/document-ingest.mjs +29 -20
  132. package/lib/embed/auto-sources.mjs +44 -0
  133. package/lib/embed/cli.mjs +4 -3
  134. package/lib/embed/daemon.mjs +18 -46
  135. package/lib/embed/demand-fetch.mjs +50 -19
  136. package/lib/embed/inbox.mjs +6 -10
  137. package/lib/embed/worker.mjs +4 -3
  138. package/lib/embedded-contract/index.mjs +11 -0
  139. package/lib/embedded-contract/model-resolve.mjs +10 -9
  140. package/lib/env-config.mjs +26 -0
  141. package/lib/evals/dataset.mjs +137 -0
  142. package/lib/evals/gates.mjs +175 -0
  143. package/lib/export-branding.mjs +32 -0
  144. package/lib/graph/build-co-change.mjs +54 -0
  145. package/lib/graph/build-from-registry.mjs +136 -0
  146. package/lib/graph/build-import-graph.mjs +153 -0
  147. package/lib/graph/cli.mjs +110 -0
  148. package/lib/graph/impact-cli.mjs +89 -0
  149. package/lib/graph/impact.mjs +108 -0
  150. package/lib/graph/staleness.mjs +44 -0
  151. package/lib/graph/store.mjs +172 -0
  152. package/lib/health-check.mjs +90 -76
  153. package/lib/hooks/artifact-release-gate.mjs +43 -0
  154. package/lib/hooks/brand-prose-lint.mjs +38 -0
  155. package/lib/hooks/graph-impact-advisory.mjs +62 -0
  156. package/lib/hooks/session-optimize.mjs +84 -207
  157. package/lib/hooks/session-start.mjs +18 -16
  158. package/lib/host-disposition.mjs +7 -0
  159. package/lib/improvement/cli.mjs +189 -0
  160. package/lib/improvement/controller.mjs +137 -0
  161. package/lib/improvement/proposal.mjs +120 -0
  162. package/lib/improvement/specialist-loop.mjs +192 -0
  163. package/lib/improvement/store.mjs +89 -0
  164. package/lib/improvement/surface.mjs +219 -0
  165. package/lib/ingest-tooling.mjs +97 -0
  166. package/lib/init/doc-lanes.mjs +165 -0
  167. package/lib/init-docs.mjs +31 -178
  168. package/lib/init-unified.mjs +50 -119
  169. package/lib/init-update-guide.mjs +102 -0
  170. package/lib/init-update.mjs +32 -1
  171. package/lib/init.mjs +8 -0
  172. package/lib/install/desktop-binary-download.mjs +85 -0
  173. package/lib/install/legacy-global-cleanup.mjs +189 -0
  174. package/lib/intake/constants.mjs +29 -0
  175. package/lib/intake/daemon.mjs +2 -0
  176. package/lib/intake/git-queue.mjs +9 -8
  177. package/lib/intake/intake-config.mjs +52 -105
  178. package/lib/intake/legacy-paths.mjs +5 -0
  179. package/lib/intake/queue.mjs +2 -1
  180. package/lib/intake/session-prelude.mjs +90 -1
  181. package/lib/libreoffice-export.mjs +97 -0
  182. package/lib/logging/rotate.mjs +9 -0
  183. package/lib/maintenance/docker-reclaim.mjs +206 -0
  184. package/lib/mcp/external-schema-cost.mjs +74 -0
  185. package/lib/mcp/server.mjs +103 -10
  186. package/lib/mcp/stdio-mcp-probe.mjs +188 -0
  187. package/lib/mcp/tool-budget.mjs +55 -4
  188. package/lib/mcp/tools/document.mjs +18 -4
  189. package/lib/mcp/tools/embedded-contract.mjs +14 -0
  190. package/lib/mcp/tools/skills.mjs +32 -3
  191. package/lib/mcp/tools/workflow.mjs +13 -1
  192. package/lib/model-free-selector.mjs +18 -0
  193. package/lib/model-registry.mjs +13 -229
  194. package/lib/model-router.mjs +373 -92
  195. package/lib/models/behavior-matrix.mjs +289 -0
  196. package/lib/models/catalog.mjs +209 -0
  197. package/lib/models/execution-capability-profile.mjs +196 -0
  198. package/lib/models/execution-policy.mjs +307 -0
  199. package/lib/models/provider-poll.mjs +383 -0
  200. package/lib/npm-spawn-env.mjs +17 -0
  201. package/lib/ollama/installed-models.mjs +129 -0
  202. package/lib/oracle/actions.mjs +187 -0
  203. package/lib/oracle/artifact-gate.mjs +99 -0
  204. package/lib/oracle/cli.mjs +204 -0
  205. package/lib/oracle/daemon-entry.mjs +14 -0
  206. package/lib/oracle/dispatch.mjs +81 -0
  207. package/lib/oracle/execute.mjs +143 -0
  208. package/lib/oracle/gaps.mjs +76 -0
  209. package/lib/oracle/index.mjs +84 -0
  210. package/lib/oracle/issues.mjs +164 -0
  211. package/lib/oracle/org-graph.mjs +170 -0
  212. package/lib/oracle/policy.mjs +80 -0
  213. package/lib/oracle/read-model.mjs +398 -0
  214. package/lib/oracle/reconcile.mjs +191 -0
  215. package/lib/oracle/routing.mjs +89 -0
  216. package/lib/oracle/synthesize.mjs +384 -0
  217. package/lib/oracle/verdicts.mjs +51 -0
  218. package/lib/orchestration/worker.mjs +88 -27
  219. package/lib/orchestration-policy.mjs +50 -0
  220. package/lib/parity.mjs +96 -2
  221. package/lib/playwright-demo.mjs +254 -0
  222. package/lib/project-init-shared.mjs +4 -1
  223. package/lib/prompt-composer.js +7 -3
  224. package/lib/prompt-validation-contract.mjs +24 -0
  225. package/lib/provider-capabilities.js +57 -11
  226. package/lib/providers/contract/adapters/confluence/index.mjs +181 -0
  227. package/lib/providers/contract/adapters/git/index.mjs +115 -0
  228. package/lib/providers/contract/adapters/github/index.mjs +166 -0
  229. package/lib/providers/contract/adapters/jira/index.mjs +187 -0
  230. package/lib/providers/contract/adapters/slack/index.mjs +175 -0
  231. package/lib/providers/contract/contract-tests.mjs +57 -0
  232. package/lib/providers/contract/errors.mjs +48 -0
  233. package/lib/providers/contract/interface.mjs +50 -0
  234. package/lib/providers/contract/registry.mjs +102 -0
  235. package/lib/providers/copilot-auth.mjs +297 -0
  236. package/lib/providers/credential-bootstrap.mjs +178 -0
  237. package/lib/providers/credential-catalog.mjs +46 -0
  238. package/lib/providers/credential-sources.mjs +63 -0
  239. package/lib/providers/creds.mjs +5 -2
  240. package/lib/providers/op-run.mjs +59 -0
  241. package/lib/providers/secret-resolver.mjs +159 -0
  242. package/lib/publish-template.mjs +163 -0
  243. package/lib/publish-tooling.mjs +119 -0
  244. package/lib/publish.mjs +305 -0
  245. package/lib/registry/cli.mjs +82 -0
  246. package/lib/registry/consolidation.mjs +147 -0
  247. package/lib/registry/generate-docs.mjs +75 -0
  248. package/lib/registry/skill-verification.mjs +57 -0
  249. package/lib/registry/surface-map.mjs +76 -0
  250. package/lib/registry/validate.mjs +135 -0
  251. package/lib/resources/budget.mjs +82 -0
  252. package/lib/resources/process-budget.mjs +45 -0
  253. package/lib/rules-delivery.mjs +9 -2
  254. package/lib/rules-read.mjs +26 -0
  255. package/lib/runtime-env.mjs +23 -0
  256. package/lib/runtime-pressure.mjs +51 -7
  257. package/lib/schema-infer.mjs +13 -2
  258. package/lib/server/chat-loop.mjs +622 -0
  259. package/lib/server/demo-preview.mjs +63 -0
  260. package/lib/server/index.mjs +259 -9
  261. package/lib/server/rate-limit.mjs +5 -3
  262. package/lib/server/webhook.mjs +3 -2
  263. package/lib/service-manager.mjs +60 -8
  264. package/lib/setup.mjs +70 -7
  265. package/lib/specialists/prompt-schema.mjs +19 -10
  266. package/lib/specialists/roster.mjs +43 -0
  267. package/lib/specialists/scaffold.mjs +56 -0
  268. package/lib/storage/backend.mjs +6 -6
  269. package/lib/storage/hybrid-query.mjs +7 -4
  270. package/lib/storage/state-source.mjs +5 -5
  271. package/lib/storage/sync.mjs +2 -3
  272. package/lib/telemetry/rule-calls.mjs +52 -0
  273. package/lib/template-registry.mjs +2 -2
  274. package/lib/templates/visual-requirements.mjs +27 -51
  275. package/lib/test-corpus-inventory.mjs +313 -0
  276. package/lib/uninstall/uninstall.mjs +19 -7
  277. package/lib/update.mjs +12 -0
  278. package/lib/upgrade.mjs +14 -0
  279. package/lib/wireframe.mjs +20 -14
  280. package/lib/worker/run.mjs +17 -4
  281. package/lib/worker/trace.mjs +11 -3
  282. package/package.json +28 -14
  283. package/personas/construct.md +2 -2
  284. package/platforms/claude/settings.template.json +36 -0
  285. package/rules/common/patterns.md +1 -1
  286. package/rules/common/release-gates.md +4 -3
  287. package/scripts/sync-specialists.mjs +41 -28
  288. package/skills/devops/data-engineering.md +1 -1
  289. package/skills/docs/adr-workflow.md +1 -0
  290. package/skills/docs/backlog-proposal-workflow.md +1 -0
  291. package/skills/docs/codebase-research-workflow.md +40 -0
  292. package/skills/docs/customer-profile-workflow.md +1 -0
  293. package/skills/docs/document-ingest-workflow.md +1 -0
  294. package/skills/docs/evidence-ingest-workflow.md +1 -0
  295. package/skills/docs/init-docs.md +2 -2
  296. package/skills/docs/init-project.md +7 -2
  297. package/skills/docs/prd-workflow.md +24 -1
  298. package/skills/docs/prfaq-workflow.md +1 -0
  299. package/skills/docs/product-intelligence-workflow.md +1 -0
  300. package/skills/docs/product-signal-workflow.md +1 -0
  301. package/skills/docs/research-workflow.md +54 -37
  302. package/skills/docs/runbook-workflow.md +1 -0
  303. package/skills/docs/strategy-workflow.md +1 -0
  304. package/skills/docs/user-research-workflow.md +40 -0
  305. package/skills/operating/orchestration-reference.md +1 -1
  306. package/skills/roles/architect.md +5 -0
  307. package/skills/roles/operator.docs.md +4 -0
  308. package/skills/routing.md +4 -2
  309. package/specialists/artifact-manifest.json +489 -0
  310. package/specialists/artifact-manifest.schema.json +83 -0
  311. package/specialists/audit-enrichments.json +454 -0
  312. package/specialists/contracts.json +37 -25
  313. package/specialists/prompts/_shared/validation-contract.md +26 -0
  314. package/specialists/prompts/cx-accessibility.md +21 -2
  315. package/specialists/prompts/cx-ai-engineer.md +24 -1
  316. package/specialists/prompts/cx-architect.md +4 -1
  317. package/specialists/prompts/cx-business-strategist.md +23 -2
  318. package/specialists/prompts/cx-data-analyst.md +22 -1
  319. package/specialists/prompts/cx-data-engineer.md +23 -2
  320. package/specialists/prompts/cx-debugger.md +21 -2
  321. package/specialists/prompts/cx-designer.md +26 -3
  322. package/specialists/prompts/cx-devil-advocate.md +19 -2
  323. package/specialists/prompts/cx-docs-keeper.md +28 -1
  324. package/specialists/prompts/cx-engineer.md +22 -1
  325. package/specialists/prompts/cx-evaluator.md +18 -1
  326. package/specialists/prompts/cx-explorer.md +21 -2
  327. package/specialists/prompts/cx-legal-compliance.md +21 -2
  328. package/specialists/prompts/cx-operations.md +21 -2
  329. package/specialists/prompts/cx-oracle.md +94 -0
  330. package/specialists/prompts/cx-orchestrator.md +20 -1
  331. package/specialists/prompts/cx-platform-engineer.md +25 -2
  332. package/specialists/prompts/cx-product-manager.md +32 -1
  333. package/specialists/prompts/cx-qa.md +24 -2
  334. package/specialists/prompts/cx-rd-lead.md +23 -2
  335. package/specialists/prompts/cx-release-manager.md +23 -2
  336. package/specialists/prompts/cx-researcher.md +22 -2
  337. package/specialists/prompts/cx-reviewer.md +18 -1
  338. package/specialists/prompts/cx-security.md +22 -2
  339. package/specialists/prompts/cx-sre.md +30 -3
  340. package/specialists/prompts/cx-test-automation.md +7 -1
  341. package/specialists/prompts/cx-trace-reviewer.md +22 -3
  342. package/specialists/prompts/cx-ux-researcher.md +21 -2
  343. package/specialists/registry.json +52 -173
  344. package/specialists/tone-profiles.json +42 -0
  345. package/templates/demos/playwright/demo-recording.config.mjs +47 -0
  346. package/templates/demos/recordings/agentic-platforms-prd.json +19 -0
  347. package/templates/demos/scripts/agentic-platforms-prd.json +44 -0
  348. package/templates/demos/specs/_helpers/scroll-artifact.ts +89 -0
  349. package/templates/demos/tapes/agentic-platforms-prd.tape +49 -0
  350. package/templates/demos/tapes/resource-guard-rails.tape +49 -0
  351. package/templates/demos/vhs/construct-cockpit.json +24 -0
  352. package/templates/distribution/construct-brand.typ +446 -0
  353. package/templates/distribution/construct-decision.typ +38 -0
  354. package/templates/distribution/construct-deck.html +95 -0
  355. package/templates/distribution/construct-pdf.typ +38 -0
  356. package/templates/distribution/construct-prd.typ +38 -0
  357. package/templates/distribution/construct-reference.docx +0 -0
  358. package/templates/distribution/construct-research.typ +38 -0
  359. package/templates/distribution/construct-web.html +92 -0
  360. package/templates/distribution/fonts/Geist-Bold.ttf +0 -0
  361. package/templates/distribution/fonts/Geist-Medium.ttf +0 -0
  362. package/templates/distribution/fonts/Geist-Regular.ttf +0 -0
  363. package/templates/distribution/fonts/Geist-SemiBold.ttf +0 -0
  364. package/templates/distribution/fonts/GeistMono-Medium.ttf +0 -0
  365. package/templates/distribution/fonts/GeistMono-Regular.ttf +0 -0
  366. package/templates/distribution/fonts/GeistMono-SemiBold.ttf +0 -0
  367. package/templates/distribution/fonts/IBMPlexMono-Regular.otf +0 -0
  368. package/templates/distribution/fonts/JetBrainsMono-Medium.ttf +0 -0
  369. package/templates/distribution/fonts/JetBrainsMono-Regular.ttf +0 -0
  370. package/templates/distribution/fonts/JetBrainsMono-SemiBold.ttf +0 -0
  371. package/templates/distribution/fonts/PlusJakartaSans-Bold.ttf +0 -0
  372. package/templates/distribution/fonts/PlusJakartaSans-Medium.ttf +0 -0
  373. package/templates/distribution/fonts/PlusJakartaSans-Regular.ttf +0 -0
  374. package/templates/distribution/fonts/PlusJakartaSans-SemiBold.ttf +0 -0
  375. package/templates/distribution/fonts/README.md +36 -0
  376. package/templates/distribution/fonts/SpaceGrotesk-Variable.ttf +0 -0
  377. package/templates/distribution/fonts/handwritten/Caveat.ttf +0 -0
  378. package/templates/distribution/fonts/legacy/IBMPlexMono-Regular.otf +0 -0
  379. package/templates/distribution/fonts/legacy/Inter-Medium.otf +0 -0
  380. package/templates/distribution/fonts/legacy/Inter-Regular.otf +0 -0
  381. package/templates/distribution/fonts/legacy/Inter-SemiBold.otf +0 -0
  382. package/templates/distribution/fonts/legacy/InterDisplay-SemiBold.otf +0 -0
  383. package/templates/distribution/fonts/legacy/SourceSerif4-Regular.otf +0 -0
  384. package/templates/distribution/fonts/legacy/SourceSerif4-Semibold.otf +0 -0
  385. package/templates/distribution/icons/activity.svg +15 -0
  386. package/templates/distribution/icons/alert-triangle.svg +17 -0
  387. package/templates/distribution/icons/book-open.svg +16 -0
  388. package/templates/distribution/icons/bot.svg +20 -0
  389. package/templates/distribution/icons/brain.svg +22 -0
  390. package/templates/distribution/icons/circle-check.svg +16 -0
  391. package/templates/distribution/icons/clipboard-check.svg +17 -0
  392. package/templates/distribution/icons/cpu.svg +28 -0
  393. package/templates/distribution/icons/database.svg +17 -0
  394. package/templates/distribution/icons/eye.svg +16 -0
  395. package/templates/distribution/icons/file-text.svg +19 -0
  396. package/templates/distribution/icons/gauge.svg +16 -0
  397. package/templates/distribution/icons/git-branch.svg +17 -0
  398. package/templates/distribution/icons/key.svg +17 -0
  399. package/templates/distribution/icons/layers.svg +17 -0
  400. package/templates/distribution/icons/list-checks.svg +19 -0
  401. package/templates/distribution/icons/lock.svg +16 -0
  402. package/templates/distribution/icons/message-square.svg +15 -0
  403. package/templates/distribution/icons/network.svg +19 -0
  404. package/templates/distribution/icons/route.svg +17 -0
  405. package/templates/distribution/icons/scale.svg +19 -0
  406. package/templates/distribution/icons/search.svg +16 -0
  407. package/templates/distribution/icons/send.svg +16 -0
  408. package/templates/distribution/icons/server.svg +18 -0
  409. package/templates/distribution/icons/shield-check.svg +16 -0
  410. package/templates/distribution/icons/users.svg +18 -0
  411. package/templates/distribution/icons/webhook.svg +17 -0
  412. package/templates/distribution/icons/workflow.svg +17 -0
  413. package/templates/distribution/icons/wrench.svg +15 -0
  414. package/templates/distribution/run.mjs +18 -1
  415. package/templates/docs/adr.md +7 -0
  416. package/templates/docs/construct_guide.md +8 -8
  417. package/templates/docs/customer-profile.md +4 -0
  418. package/templates/docs/one-pager.md +4 -0
  419. package/templates/docs/postmortem.md +31 -0
  420. package/templates/docs/prd-platform.md +19 -0
  421. package/templates/docs/prd.md +15 -0
  422. package/templates/docs/prfaq.md +7 -0
  423. package/templates/docs/qa-strategy.md +103 -0
  424. package/templates/docs/research-brief.md +8 -0
  425. package/templates/docs/rfc-platform.md +12 -0
  426. package/templates/docs/test-plan.md +7 -0
@@ -0,0 +1,285 @@
1
+ /**
2
+ * lib/audit-specialists.mjs — Specialist/skill corpus audit matrix.
3
+ *
4
+ * Merges live registry, contracts, skills, prompts, and audit-enrichments into
5
+ * one machine-readable matrix. Powers `construct audit specialists` and the
6
+ * human-readable docs/concepts/specialist-skill-audit.md companion.
7
+ */
8
+
9
+ import fs from 'node:fs';
10
+ import path from 'node:path';
11
+ import { auditSkills } from './audit-skills.mjs';
12
+ import { loadArtifactManifest } from './artifact-manifest.mjs';
13
+ import { resolvePerspectiveFromPrompt } from './specialists/prompt-schema.mjs';
14
+ import { validateRoleCards } from './certification/role-cards.mjs';
15
+ import { auditSpecialistContracts } from './certification/specialist-contracts.mjs';
16
+ import { validateAllRoleOverlays } from './certification/role-overlays.mjs';
17
+ import { ROLE_DIRECTIVE_RE } from './role-preload.mjs';
18
+
19
+ function findConstructRoot(startPath = process.cwd()) {
20
+ let current = path.resolve(startPath);
21
+ while (true) {
22
+ if (fs.existsSync(path.join(current, 'specialists', 'registry.json'))) return current;
23
+ const parent = path.dirname(current);
24
+ if (parent === current) return path.resolve(startPath);
25
+ current = parent;
26
+ }
27
+ }
28
+
29
+ function readJson(p) {
30
+ return JSON.parse(fs.readFileSync(p, 'utf8'));
31
+ }
32
+
33
+ function collectSkillFiles(skillsDir) {
34
+ const results = [];
35
+ function walk(dir, prefix = '') {
36
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
37
+ if (entry.isDirectory()) {
38
+ walk(path.join(dir, entry.name), prefix ? `${prefix}/${entry.name}` : entry.name);
39
+ } else if (entry.name.endsWith('.md') && entry.name !== 'routing.md') {
40
+ results.push(prefix ? `${prefix}/${entry.name.replace(/\.md$/, '')}` : entry.name.replace(/\.md$/, ''));
41
+ }
42
+ }
43
+ }
44
+ try { walk(skillsDir); } catch { /* missing */ }
45
+ return results.sort();
46
+ }
47
+
48
+ function parseRoleOverlay(promptPath) {
49
+ if (!fs.existsSync(promptPath)) return null;
50
+ const text = fs.readFileSync(promptPath, 'utf8');
51
+ const m = text.match(ROLE_DIRECTIVE_RE);
52
+ return m ? `roles/${m[1]}` : null;
53
+ }
54
+
55
+ function contractsForSpecialist(contracts, name) {
56
+ const id = name.startsWith('cx-') ? name : `cx-${name}`;
57
+ const related = [];
58
+ for (const c of contracts) {
59
+ if (c.producer === id || c.consumer === id) related.push(c.id);
60
+ }
61
+ return related;
62
+ }
63
+
64
+ function docTypesFromContracts(contracts) {
65
+ const types = new Set();
66
+ for (const c of contracts) {
67
+ const dt = c.trigger?.docAuthoring?.docType;
68
+ if (Array.isArray(dt)) dt.forEach((t) => types.add(t));
69
+ else if (typeof dt === 'string') types.add(dt);
70
+ }
71
+ return [...types];
72
+ }
73
+
74
+ function enforcementForAgent(agent, contracts) {
75
+ const id = `cx-${agent.name}`;
76
+ const items = ['no-fabrication.md', 'neurodivergent-output.md'];
77
+ const related = contracts.filter((c) => c.producer === id || c.consumer === id);
78
+ if (related.some((c) => c.postconditions?.length)) items.push('contracts.json postconditions');
79
+ if (agent.docArtifacts?.length) items.push('artifact structure lint', 'comment-lint on artifact paths');
80
+ if (agent.watchConditions?.length) items.push('watchConditions routing');
81
+ return items;
82
+ }
83
+
84
+ function crossChecks({ registry, manifest, skillAudit, allSkills, contractDocTypes }) {
85
+ const issues = [];
86
+ const manifestTypes = new Set(Object.keys(manifest.artifacts ?? {}));
87
+
88
+ for (const agent of registry.specialists ?? []) {
89
+ for (const skill of agent.skills ?? []) {
90
+ if (!allSkills.includes(skill)) {
91
+ issues.push({ kind: 'missing-skill-file', agent: agent.name, skill });
92
+ }
93
+ }
94
+ for (const doc of agent.docArtifacts ?? []) {
95
+ if (!manifestTypes.has(doc)) {
96
+ issues.push({ kind: 'doc-artifact-no-manifest', agent: agent.name, docType: doc });
97
+ }
98
+ }
99
+ }
100
+
101
+ for (const dt of contractDocTypes) {
102
+ if (!manifestTypes.has(dt)) {
103
+ issues.push({ kind: 'contract-doc-no-manifest', docType: dt });
104
+ }
105
+ }
106
+
107
+ if (skillAudit.missingSkillFiles.length) {
108
+ for (const { agent, skill } of skillAudit.missingSkillFiles) {
109
+ issues.push({ kind: 'registry-missing-skill', agent, skill });
110
+ }
111
+ }
112
+
113
+ return issues;
114
+ }
115
+
116
+ export function auditSpecialists({ rootDir, silent = false } = {}) {
117
+ const root = rootDir ?? findConstructRoot();
118
+ const registry = readJson(path.join(root, 'specialists', 'registry.json'));
119
+ const contractsDoc = readJson(path.join(root, 'specialists', 'contracts.json'));
120
+ const enrichments = readJson(path.join(root, 'specialists', 'audit-enrichments.json'));
121
+ const manifest = loadArtifactManifest({ rootDir: root });
122
+ const skillAudit = auditSkills({ rootDir: root, silent: true });
123
+ const allSkills = collectSkillFiles(path.join(root, 'skills'));
124
+ const contractDocTypes = docTypesFromContracts(contractsDoc.contracts ?? []);
125
+ const crossCheckIssues = crossChecks({ registry, manifest, skillAudit, allSkills, contractDocTypes });
126
+
127
+ const specialists = (registry.specialists ?? []).map((agent) => {
128
+ const promptPath = agent.promptFile
129
+ ? path.join(root, agent.promptFile)
130
+ : null;
131
+ const meta = enrichments.specialists?.[agent.name] ?? {};
132
+ const perspective = resolvePerspectiveFromPrompt(agent.name, { rootDir: root, registry });
133
+ return {
134
+ name: agent.name,
135
+ id: `cx-${agent.name}`,
136
+ humanEquivalent: meta.humanEquivalent ?? 'unknown',
137
+ primaryOutputs: agent.docArtifacts ?? [],
138
+ skillsBound: agent.skills ?? [],
139
+ roleOverlay: promptPath ? parseRoleOverlay(promptPath) : null,
140
+ researchProfile: meta.researchProfile ?? null,
141
+ toneProfile: meta.toneProfile ?? 'direct',
142
+ challengeModel: {
143
+ tension: perspective?.tension ?? null,
144
+ failureMode: perspective?.failureMode ?? null,
145
+ watchConditions: agent.watchConditions ?? [],
146
+ },
147
+ enforcementToday: enforcementForAgent(agent, contractsDoc.contracts ?? []),
148
+ contracts: contractsForSpecialist(contractsDoc.contracts ?? [], agent.name),
149
+ grade: meta.grade ?? 'adequate',
150
+ gap: meta.gap ?? 'Not assessed',
151
+ beadId: meta.beadId ?? 'construct-hcr9',
152
+ skillCount: (agent.skills ?? []).length,
153
+ };
154
+ });
155
+
156
+ const roleOverlays = [];
157
+ const rolesDir = path.join(root, 'skills', 'roles');
158
+ if (fs.existsSync(rolesDir)) {
159
+ for (const f of fs.readdirSync(rolesDir).filter((n) => n.endsWith('.md')).sort()) {
160
+ const key = f.replace(/\.md$/, '');
161
+ const meta = enrichments.roleOverlays?.[key] ?? {};
162
+ roleOverlays.push({
163
+ path: `roles/${key}`,
164
+ grade: meta.grade ?? 'adequate',
165
+ gap: meta.gap ?? 'Not assessed',
166
+ });
167
+ }
168
+ }
169
+
170
+ const workflowSkills = allSkills
171
+ .filter((s) => !s.startsWith('roles/'))
172
+ .map((skillPath) => {
173
+ const manifestEntry = Object.entries(manifest.artifacts ?? {}).find(([, v]) => v.workflowSkill === skillPath);
174
+ return {
175
+ path: skillPath,
176
+ artifactType: manifestEntry?.[0] ?? null,
177
+ toneDefault: manifestEntry?.[1]?.toneDefault ?? null,
178
+ };
179
+ });
180
+
181
+ const roleCards = validateRoleCards({ rootDir: root });
182
+ const specialistContracts = auditSpecialistContracts({ rootDir: root });
183
+ const roleOverlayAudit = validateAllRoleOverlays({ rootDir: root });
184
+ if (!roleCards.pass) {
185
+ for (const err of roleCards.errors) crossCheckIssues.push({ kind: 'role-card-missing', detail: err });
186
+ }
187
+ if (!specialistContracts.pass) {
188
+ for (const fail of specialistContracts.failures) {
189
+ crossCheckIssues.push({ kind: 'specialist-contract-fail', specialist: fail.specialistId });
190
+ }
191
+ }
192
+ if (!roleOverlayAudit.pass) {
193
+ for (const err of roleOverlayAudit.errors.slice(0, 5)) {
194
+ crossCheckIssues.push({ kind: 'role-overlay-fail', detail: err });
195
+ }
196
+ }
197
+
198
+ const result = {
199
+ version: 1,
200
+ generatedAt: new Date().toISOString(),
201
+ specialistCount: specialists.length,
202
+ roleOverlayCount: roleOverlays.length,
203
+ workflowSkillCount: workflowSkills.length,
204
+ specialists,
205
+ roleOverlays,
206
+ workflowSkills,
207
+ crossCheckIssues,
208
+ roleCards,
209
+ specialistContracts,
210
+ roleOverlayAudit,
211
+ pass: crossCheckIssues.length === 0 && skillAudit.pass,
212
+ };
213
+
214
+ if (!silent) {
215
+ const line = (msg) => process.stdout.write(`${msg}\n`);
216
+ line('Construct Specialist Audit');
217
+ line('═══════════════════════════');
218
+ line(` Specialists: ${result.specialistCount}`);
219
+ line(` Role overlays: ${result.roleOverlayCount}`);
220
+ line(` Workflow skills: ${result.workflowSkillCount}`);
221
+ line('');
222
+ if (crossCheckIssues.length === 0) {
223
+ line(' ✓ Cross-checks passed (skills, docArtifacts, manifest, contracts)');
224
+ } else {
225
+ line(` ✗ Cross-check issues (${crossCheckIssues.length}):`);
226
+ for (const issue of crossCheckIssues) line(` - ${JSON.stringify(issue)}`);
227
+ }
228
+ line('');
229
+ line(result.pass ? ' Result: PASS' : ' Result: FAIL');
230
+ }
231
+
232
+ return result;
233
+ }
234
+
235
+ export function formatAuditMarkdown(result) {
236
+ const lines = [
237
+ '# Specialist & skill audit',
238
+ '',
239
+ `Generated: ${result.generatedAt}. Re-run \`construct audit specialists --json\` for the live matrix.`,
240
+ '',
241
+ '## Specialists',
242
+ '',
243
+ '| Specialist | Human equivalent | Outputs | Skills | Role overlay | Research | Tone | Grade | Gap |',
244
+ '|---|---|---|---|---|---|---|---|---|',
245
+ ];
246
+
247
+ for (const s of result.specialists) {
248
+ lines.push(
249
+ `| cx-${s.name} | ${s.humanEquivalent} | ${s.primaryOutputs.join(', ') || '—'} | ${s.skillCount} | ${s.roleOverlay ?? '—'} | ${s.researchProfile ?? '—'} | ${s.toneProfile} | ${s.grade} | ${s.gap} |`,
250
+ );
251
+ }
252
+
253
+ lines.push('', '## Role overlays', '', '| Overlay | Grade | Gap |', '|---|---|---|');
254
+ for (const r of result.roleOverlays) {
255
+ lines.push(`| ${r.path} | ${r.grade} | ${r.gap} |`);
256
+ }
257
+
258
+ lines.push('', '## Cross-check issues', '');
259
+ if (result.crossCheckIssues.length === 0) {
260
+ lines.push('None.');
261
+ } else {
262
+ for (const issue of result.crossCheckIssues) {
263
+ lines.push(`- \`${issue.kind}\`: ${JSON.stringify(issue)}`);
264
+ }
265
+ }
266
+
267
+ return `${lines.join('\n')}\n`;
268
+ }
269
+
270
+ export async function runAuditSpecialistsCli(args = []) {
271
+ const json = args.includes('--json');
272
+ const markdown = args.includes('--markdown');
273
+ const rootDir = args.find((a) => a.startsWith('--root='))?.split('=')[1];
274
+ const result = auditSpecialists({ rootDir, silent: true });
275
+
276
+ if (markdown) {
277
+ process.stdout.write(formatAuditMarkdown(result));
278
+ } else if (json) {
279
+ process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
280
+ } else {
281
+ auditSpecialists({ rootDir, silent: false });
282
+ }
283
+
284
+ if (!result.pass) process.exit(1);
285
+ }
@@ -16,7 +16,7 @@
16
16
  * Corrupted links cannot be re-derived, so a `chain_reset` boundary line seals
17
17
  * the damaged prefix as immutable legacy and re-bases the live chain;
18
18
  * verifyChain validates only from the last boundary forward. See
19
- * docs/concepts/observability.mdx.
19
+ * docs/concepts/observability.md.
20
20
  */
21
21
  import { readFileSync, existsSync, statSync, mkdirSync } from 'node:fs';
22
22
  import { createHash } from 'node:crypto';
package/lib/auto-docs.mjs CHANGED
@@ -90,20 +90,25 @@ function buildCoreDocsContract() {
90
90
  }
91
91
 
92
92
  const DIR_DESCRIPTIONS = {
93
- agents: 'Registry and generated platform adapter chains',
94
- apps: 'User-facing apps shipped from this repo (e.g. apps/docs/, the Next.js docs site)',
93
+ apps: 'User-facing apps shipped from this repo (chat, dashboard, docs)',
95
94
  bin: 'CLI entrypoint (`construct`)',
96
- claude: 'Claude Code integration (agents, settings template)',
97
95
  commands: 'Command prompt assets',
98
- codex: 'GitHub Copilot / Codex integration',
96
+ config: 'Repo-wide controlled vocabulary (tag-vocabulary.json)',
97
+ deploy: 'Terraform and deployment configs',
99
98
  docs: 'Architecture notes, runbooks, and documentation contract',
100
- telemetry: 'Local trace capture and optional telemetry export',
101
- lib: 'Core runtime: CLI, hooks, MCP, status, sync, workflow',
102
- opencode: 'OpenCode integration config',
99
+ examples: 'Example projects and persona fixtures',
100
+ lib: 'Core runtime: CLI, hooks, MCP, providers, oracle, sync',
101
+ packages: 'Shared workspace packages (e.g. cx-ui)',
103
102
  personas: 'Persona prompt definitions',
103
+ platforms: 'Host adapter capability configs',
104
+ profiles: 'Profile catalog',
105
+ registry: 'Product capability registry',
104
106
  rules: 'Coding and quality standards',
105
- site: 'Legacy docs-site output',
107
+ schemas: 'Registry and config JSON Schema',
108
+ scripts: 'Audit, alignment, release, and sync scripts',
106
109
  skills: 'Reusable domain knowledge files',
110
+ specialists: 'Org registry, contracts, and specialist prompts',
111
+ templates: 'Doc and workflow templates',
107
112
  tests: 'Test suite',
108
113
  };
109
114
 
@@ -164,16 +169,16 @@ function buildAgentsTable(rootDir) {
164
169
  let registry;
165
170
  try { registry = JSON.parse(fs.readFileSync(registryPath, 'utf8')); } catch { return ''; }
166
171
 
167
- const agents = registry.agents ?? registry;
168
- if (!Array.isArray(agents)) return '';
172
+ const agents = [
173
+ ...(registry.orchestrator ? [{ ...registry.orchestrator, name: registry.orchestrator.name }] : []),
174
+ ...(registry.specialists ?? []),
175
+ ];
176
+ if (!agents.length) return '';
169
177
 
170
178
  const rows = ['| Specialist | Tier | Purpose |', '|---|---|---|'];
171
179
  for (const agent of agents.slice(0, 30)) {
172
180
  const name = agent.name ?? agent.id ?? 'n/a';
173
181
  const tier = agent.modelTier ?? agent.tier ?? agent.model_tier ?? 'n/a';
174
- // Strip em-dashes from descriptions so the prose lint stays clean across
175
- // AUTO regen. Em-dashes are allowed in the registry's source descriptions
176
- // but never in the rendered markdown.
177
182
  const purpose = (agent.description ?? agent.purpose ?? '')
178
183
  .split('\n')[0].slice(0, 80)
179
184
  .replace(/ — /g, '. ').replace(/—/g, ',');
@@ -363,7 +368,9 @@ function renderCommandPage(category, commands) {
363
368
  lines.push('**Subcommands**');
364
369
  lines.push('');
365
370
  for (const sub of cmd.subcommands) {
366
- lines.push(`- \`${sub}\``);
371
+ const name = typeof sub === 'string' ? sub : sub.name;
372
+ const description = typeof sub === 'string' ? '' : (sub.desc || sub.description || '');
373
+ lines.push(`- \`${name}\`${description ? ` — ${description}` : ''}`);
367
374
  }
368
375
  lines.push('');
369
376
  }
@@ -474,13 +481,77 @@ export function buildFumadocsReference({ rootDir } = {}) {
474
481
  written.push(path.relative(rootDir, hooksPage));
475
482
  }
476
483
 
477
- const agentsContent = renderAgentsPage(rootDir);
478
- if (agentsContent) {
479
- const agentsPage = path.join(refDir, 'agents.md');
480
- if (writeIfChanged(agentsPage, agentsContent)) {
481
- written.push(path.relative(rootDir, agentsPage));
484
+ const specialistsContent = renderAgentsPage(rootDir);
485
+ if (specialistsContent) {
486
+ const specialistsPage = path.join(refDir, 'specialists.md');
487
+ if (writeIfChanged(specialistsPage, specialistsContent)) {
488
+ written.push(path.relative(rootDir, specialistsPage));
489
+ }
490
+ const legacyAgentsPage = path.join(refDir, 'agents.md');
491
+ if (fs.existsSync(legacyAgentsPage)) {
492
+ fs.unlinkSync(legacyAgentsPage);
493
+ written.push(`- ${path.relative(rootDir, legacyAgentsPage)}`);
482
494
  }
483
495
  }
484
496
 
485
497
  return { written };
486
498
  }
499
+
500
+ export function checkFumadocsReferenceDrift({ rootDir } = {}) {
501
+ rootDir = rootDir ?? process.cwd();
502
+ const refDir = path.join(rootDir, 'docs', 'reference');
503
+ const cliDir = path.join(refDir, 'cli');
504
+ const drift = [];
505
+
506
+ for (const category of REFERENCE_CATEGORIES) {
507
+ const commands = CLI_COMMANDS_BY_CATEGORY[category] ?? [];
508
+ if (!commands.length) continue;
509
+ const slug = slugify(category);
510
+ const out = path.join(cliDir, `${slug}.md`);
511
+ const content = renderCommandPage(category, commands);
512
+ if (readFile(out) !== content) drift.push(path.relative(rootDir, out));
513
+ }
514
+
515
+ const cliPages = REFERENCE_CATEGORIES
516
+ .map((cat) => ({ cat, commands: CLI_COMMANDS_BY_CATEGORY[cat] ?? [] }))
517
+ .filter(({ commands }) => commands.length)
518
+ .map(({ cat }) => slugify(cat));
519
+ const cliMetaContent = JSON.stringify({ title: 'CLI', pages: ['index', ...cliPages] }, null, 2) + '\n';
520
+ const cliMeta = path.join(cliDir, 'meta.json');
521
+ if (readFile(cliMeta) !== cliMetaContent) drift.push(path.relative(rootDir, cliMeta));
522
+
523
+ const cliIndexContent = [
524
+ '---',
525
+ 'title: CLI',
526
+ 'description: Every construct command, grouped by category. Generated from lib/cli-commands.mjs.',
527
+ '---',
528
+ '',
529
+ '> Generated from `lib/cli-commands.mjs`. Re-run `construct docs:site` to refresh.',
530
+ '',
531
+ 'Commands are grouped by what they do. Pick a category below.',
532
+ '',
533
+ ...REFERENCE_CATEGORIES.map((cat) => {
534
+ const slug = slugify(cat);
535
+ const commands = CLI_COMMANDS_BY_CATEGORY[cat] ?? [];
536
+ if (!commands.length) return null;
537
+ return `- [${cat}](/reference/cli/${slug}) — ${commands.length} command${commands.length === 1 ? '' : 's'}`;
538
+ }).filter(Boolean),
539
+ '',
540
+ ].join('\n');
541
+ const cliIndex = path.join(cliDir, 'index.md');
542
+ if (readFile(cliIndex) !== cliIndexContent) drift.push(path.relative(rootDir, cliIndex));
543
+
544
+ const hooksPage = path.join(refDir, 'hooks.md');
545
+ const hooksContent = renderHooksPage(rootDir);
546
+ if (hooksContent && readFile(hooksPage) !== hooksContent) drift.push(path.relative(rootDir, hooksPage));
547
+
548
+ const specialistsContent = renderAgentsPage(rootDir);
549
+ const specialistsPage = path.join(refDir, 'specialists.md');
550
+ if (specialistsContent && readFile(specialistsPage) !== specialistsContent) {
551
+ drift.push(path.relative(rootDir, specialistsPage));
552
+ }
553
+ const legacyAgentsPage = path.join(refDir, 'agents.md');
554
+ if (fs.existsSync(legacyAgentsPage)) drift.push(path.relative(rootDir, legacyAgentsPage));
555
+
556
+ return { drift };
557
+ }
@@ -37,6 +37,10 @@ function daysAgo(value) {
37
37
  return (Date.now() - ts) / (24 * 60 * 60 * 1000);
38
38
  }
39
39
 
40
+ function beadUpdatedAt(bead) {
41
+ return bead?.updated || bead?.updatedAt || bead?.updated_at || bead?.created || bead?.createdAt || bead?.created_at || null;
42
+ }
43
+
40
44
  function listBeadsJson({ status, runner = spawnSync } = {}) {
41
45
  const args = ['list', '--json'];
42
46
  if (status) args.push(`--status=${status}`);
@@ -78,6 +82,20 @@ function titleAppearsInSubject(beadTitle, commitSubject) {
78
82
  return hits >= 3;
79
83
  }
80
84
 
85
+ /**
86
+ * Oracle auto-raised meta beads must not feed back into hygiene drift counts.
87
+ */
88
+ export function isOracleMetaBead(bead) {
89
+ const title = String(bead?.title ?? '');
90
+ const labels = Array.isArray(bead?.labels) ? bead.labels : [];
91
+ return title.startsWith('[oracle/') || labels.includes('oracle');
92
+ }
93
+
94
+ function filterOracleMeta(beads, excludeOracleMeta) {
95
+ if (!excludeOracleMeta) return beads;
96
+ return beads.filter((b) => !isOracleMetaBead(b));
97
+ }
98
+
81
99
  /**
82
100
  * Run the three drift checks against the live tracker + git state.
83
101
  * Pure-ish — accepts a runner injection for tests.
@@ -86,14 +104,17 @@ export function detectBeadsDrift({
86
104
  staleOpenDays = DEFAULT_STALE_OPEN_DAYS,
87
105
  stuckInProgressDays = DEFAULT_STUCK_IN_PROGRESS_DAYS,
88
106
  mergeLookback = DEFAULT_MERGE_LOOKBACK,
107
+ excludeOracleMeta = true,
89
108
  runner = spawnSync,
90
109
  } = {}) {
91
- const open = listBeadsJson({ status: 'open', runner });
92
- const inProgress = listBeadsJson({ status: 'in_progress', runner });
110
+ const openRaw = listBeadsJson({ status: 'open', runner });
111
+ const inProgressRaw = listBeadsJson({ status: 'in_progress', runner });
112
+ const open = filterOracleMeta(openRaw, excludeOracleMeta);
113
+ const inProgress = filterOracleMeta(inProgressRaw, excludeOracleMeta);
93
114
  const commits = recentCommitSubjects(mergeLookback, { runner });
94
115
 
95
- const staleOpen = open.filter((b) => daysAgo(b.updated || b.updatedAt) > staleOpenDays);
96
- const stuckInProgress = inProgress.filter((b) => daysAgo(b.updated || b.updatedAt) > stuckInProgressDays);
116
+ const staleOpen = open.filter((b) => daysAgo(beadUpdatedAt(b)) > staleOpenDays);
117
+ const stuckInProgress = inProgress.filter((b) => daysAgo(beadUpdatedAt(b)) > stuckInProgressDays);
97
118
 
98
119
  const mergeDrift = [];
99
120
  for (const bead of open) {
@@ -106,8 +127,8 @@ export function detectBeadsDrift({
106
127
  }
107
128
 
108
129
  return {
109
- staleOpen: staleOpen.map((b) => ({ id: b.id, title: b.title, ageDays: Math.round(daysAgo(b.updated || b.updatedAt)) })),
110
- stuckInProgress: stuckInProgress.map((b) => ({ id: b.id, title: b.title, ageDays: Math.round(daysAgo(b.updated || b.updatedAt)) })),
130
+ staleOpen: staleOpen.map((b) => ({ id: b.id, title: b.title, ageDays: Math.round(daysAgo(beadUpdatedAt(b))) })),
131
+ stuckInProgress: stuckInProgress.map((b) => ({ id: b.id, title: b.title, ageDays: Math.round(daysAgo(beadUpdatedAt(b))) })),
111
132
  mergeDrift,
112
133
  thresholds: { staleOpenDays, stuckInProgressDays, mergeLookback },
113
134
  counts: {