@geraldmaron/construct 1.1.0 → 1.2.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 (425) hide show
  1. package/README.md +30 -18
  2. package/bin/construct +392 -91
  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 +112 -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/audit-skills.mjs +4 -0
  20. package/lib/audit-specialists.mjs +285 -0
  21. package/lib/audit-trail.mjs +77 -10
  22. package/lib/auto-docs.mjs +87 -18
  23. package/lib/beads/drift.mjs +27 -6
  24. package/lib/beads-client.mjs +49 -121
  25. package/lib/beads-optimistic.mjs +5 -12
  26. package/lib/brand-fonts.mjs +93 -0
  27. package/lib/brand-prose.mjs +214 -0
  28. package/lib/brand-tokens.mjs +92 -0
  29. package/lib/bridges/copilot-proxy.mjs +13 -26
  30. package/lib/capability-ledger.mjs +156 -0
  31. package/lib/certification/artifact-fixtures.mjs +132 -0
  32. package/lib/certification/artifact-gates.mjs +63 -0
  33. package/lib/certification/artifact-provenance.mjs +97 -0
  34. package/lib/certification/canonical-scenarios.mjs +78 -0
  35. package/lib/certification/cli.mjs +191 -0
  36. package/lib/certification/dashboard-api.mjs +71 -0
  37. package/lib/certification/demo-parity.mjs +116 -0
  38. package/lib/certification/document-io-fixtures.mjs +246 -0
  39. package/lib/certification/document-workflow.mjs +97 -0
  40. package/lib/certification/eval-bridge.mjs +77 -0
  41. package/lib/certification/model-routing.mjs +149 -0
  42. package/lib/certification/prompt-budget.mjs +119 -0
  43. package/lib/certification/rc-gate.mjs +206 -0
  44. package/lib/certification/real-llm-scenarios.mjs +303 -0
  45. package/lib/certification/role-cards.mjs +113 -0
  46. package/lib/certification/role-overlays.mjs +117 -0
  47. package/lib/certification/run.mjs +122 -0
  48. package/lib/certification/runner.mjs +323 -0
  49. package/lib/certification/scenarios.mjs +51 -0
  50. package/lib/certification/skill-inventory.mjs +289 -0
  51. package/lib/certification/skill-scenarios.mjs +147 -0
  52. package/lib/certification/specialist-contracts.mjs +85 -0
  53. package/lib/certification/specialist-scenarios.mjs +175 -0
  54. package/lib/certification/stale-impact.mjs +146 -0
  55. package/lib/certification/status.mjs +252 -0
  56. package/lib/certification/store.mjs +77 -0
  57. package/lib/chat/cli.mjs +333 -0
  58. package/lib/chat/command-suggest.mjs +161 -0
  59. package/lib/chat/commands.mjs +215 -0
  60. package/lib/chat/config.mjs +142 -0
  61. package/lib/chat/context-compactor.mjs +250 -0
  62. package/lib/chat/context-continuation.mjs +253 -0
  63. package/lib/chat/continuation-source.mjs +58 -0
  64. package/lib/chat/demo-guide.mjs +61 -0
  65. package/lib/chat/design-tokens.mjs +91 -0
  66. package/lib/chat/desktop-binary.mjs +79 -0
  67. package/lib/chat/desktop-build.mjs +130 -0
  68. package/lib/chat/desktop-launcher.mjs +133 -0
  69. package/lib/chat/evidence.mjs +145 -0
  70. package/lib/chat/export.mjs +74 -0
  71. package/lib/chat/harness/driver.mjs +91 -0
  72. package/lib/chat/list-picker.mjs +112 -0
  73. package/lib/chat/model-picker.mjs +356 -0
  74. package/lib/chat/openrouter-fallback.mjs +151 -0
  75. package/lib/chat/permission-prompt.mjs +33 -0
  76. package/lib/chat/picker-catalog.mjs +45 -0
  77. package/lib/chat/policy-telemetry.mjs +34 -0
  78. package/lib/chat/present.mjs +245 -0
  79. package/lib/chat/session-context.mjs +39 -0
  80. package/lib/chat/session-persist.mjs +73 -0
  81. package/lib/chat/session-restore.mjs +71 -0
  82. package/lib/chat/session-settings.mjs +53 -0
  83. package/lib/chat/system-prompt.mjs +52 -0
  84. package/lib/chat/transparency.mjs +93 -0
  85. package/lib/chat/tui/color-scheme.mjs +42 -0
  86. package/lib/chat/tui/markdown.mjs +123 -0
  87. package/lib/chat/tui/presentation.mjs +100 -0
  88. package/lib/chat/tui/render.mjs +500 -0
  89. package/lib/chat/tui/turn-block.mjs +284 -0
  90. package/lib/chat/tui/turn-present.mjs +18 -0
  91. package/lib/chat/tui/turn-state.mjs +88 -0
  92. package/lib/chat/tui/usage.mjs +122 -0
  93. package/lib/chat/web-commands.mjs +146 -0
  94. package/lib/chat/web-launcher.mjs +63 -0
  95. package/lib/chat/web-picker-keys.mjs +46 -0
  96. package/lib/chat/web-session.mjs +159 -0
  97. package/lib/cli-commands.mjs +232 -11
  98. package/lib/comment-lint.mjs +6 -4
  99. package/lib/config/schema.mjs +36 -5
  100. package/lib/contract-schemas/decision.json +50 -0
  101. package/lib/contract-schemas/implementation.json +51 -0
  102. package/lib/contract-schemas/review-report.json +32 -0
  103. package/lib/contract-schemas/test-report.json +43 -0
  104. package/lib/contracts/construct-handoff.mjs +60 -0
  105. package/lib/contracts/validate.mjs +32 -3
  106. package/lib/contracts/violation-log.mjs +58 -5
  107. package/lib/dashboard-demo.mjs +71 -0
  108. package/lib/dashboard-static.mjs +19 -5
  109. package/lib/decisions/registry.mjs +5 -3
  110. package/lib/deck-export-pptx.mjs +1152 -0
  111. package/lib/demo-recording.mjs +142 -0
  112. package/lib/demo-script.mjs +114 -0
  113. package/lib/demo-surface.mjs +249 -0
  114. package/lib/demo.mjs +703 -0
  115. package/lib/diagram-export.mjs +192 -0
  116. package/lib/diagram.mjs +302 -0
  117. package/lib/docs-verify.mjs +25 -7
  118. package/lib/doctor/index.mjs +4 -2
  119. package/lib/doctor/project-adapters.mjs +44 -0
  120. package/lib/doctor/watchers/cx-budget.mjs +98 -0
  121. package/lib/doctor/watchers/graph-staleness.mjs +52 -0
  122. package/lib/doctor/watchers/process-pressure.mjs +4 -3
  123. package/lib/doctor/watchers/service-health.mjs +1 -1
  124. package/lib/document-export.mjs +360 -36
  125. package/lib/document-extract.mjs +88 -0
  126. package/lib/document-ingest.mjs +32 -18
  127. package/lib/embed/cli.mjs +2 -2
  128. package/lib/embed/daemon.mjs +18 -46
  129. package/lib/embed/semantic.mjs +5 -2
  130. package/lib/embed/worker.mjs +2 -2
  131. package/lib/embedded-contract/model-resolve.mjs +10 -9
  132. package/lib/env-config.mjs +26 -0
  133. package/lib/evals/dataset.mjs +137 -0
  134. package/lib/evals/gates.mjs +175 -0
  135. package/lib/graph/build-co-change.mjs +54 -0
  136. package/lib/graph/build-from-registry.mjs +136 -0
  137. package/lib/graph/build-import-graph.mjs +153 -0
  138. package/lib/graph/cli.mjs +110 -0
  139. package/lib/graph/impact-cli.mjs +89 -0
  140. package/lib/graph/impact.mjs +108 -0
  141. package/lib/graph/staleness.mjs +44 -0
  142. package/lib/graph/store.mjs +172 -0
  143. package/lib/handoffs/cleanup.mjs +1 -1
  144. package/lib/health-check.mjs +90 -76
  145. package/lib/hooks/artifact-release-gate.mjs +43 -0
  146. package/lib/hooks/audit-trail.mjs +14 -28
  147. package/lib/hooks/brand-prose-lint.mjs +38 -0
  148. package/lib/hooks/graph-impact-advisory.mjs +62 -0
  149. package/lib/hooks/session-optimize.mjs +84 -207
  150. package/lib/hooks/session-start.mjs +4 -5
  151. package/lib/host-disposition.mjs +7 -0
  152. package/lib/improvement/cli.mjs +189 -0
  153. package/lib/improvement/controller.mjs +137 -0
  154. package/lib/improvement/proposal.mjs +120 -0
  155. package/lib/improvement/specialist-loop.mjs +192 -0
  156. package/lib/improvement/store.mjs +89 -0
  157. package/lib/improvement/surface.mjs +219 -0
  158. package/lib/ingest-tooling.mjs +97 -0
  159. package/lib/init/doc-lanes.mjs +165 -0
  160. package/lib/init-docs.mjs +31 -178
  161. package/lib/init-unified.mjs +27 -97
  162. package/lib/init-update-guide.mjs +102 -0
  163. package/lib/init-update.mjs +32 -1
  164. package/lib/init.mjs +8 -0
  165. package/lib/install/desktop-binary-download.mjs +85 -0
  166. package/lib/install/legacy-global-cleanup.mjs +189 -0
  167. package/lib/intake/daemon.mjs +2 -0
  168. package/lib/intake/git-queue.mjs +9 -8
  169. package/lib/intake/queue.mjs +2 -1
  170. package/lib/intake/session-prelude.mjs +90 -1
  171. package/lib/libreoffice-export.mjs +97 -0
  172. package/lib/logging/rotate.mjs +9 -0
  173. package/lib/maintenance/docker-reclaim.mjs +206 -0
  174. package/lib/mcp/external-schema-cost.mjs +74 -0
  175. package/lib/mcp/server.mjs +84 -9
  176. package/lib/mcp/stdio-mcp-probe.mjs +188 -0
  177. package/lib/mcp/tool-budget.mjs +55 -4
  178. package/lib/mcp/tools/document.mjs +18 -4
  179. package/lib/mcp/tools/skills.mjs +32 -3
  180. package/lib/mcp/tools/workflow.mjs +13 -1
  181. package/lib/model-free-selector.mjs +18 -0
  182. package/lib/model-registry.mjs +13 -229
  183. package/lib/model-router.mjs +425 -93
  184. package/lib/models/behavior-matrix.mjs +289 -0
  185. package/lib/models/catalog.mjs +209 -0
  186. package/lib/models/execution-capability-profile.mjs +196 -0
  187. package/lib/models/execution-policy.mjs +307 -0
  188. package/lib/models/provider-poll.mjs +383 -0
  189. package/lib/npm-spawn-env.mjs +17 -0
  190. package/lib/ollama/installed-models.mjs +129 -0
  191. package/lib/oracle/actions.mjs +187 -0
  192. package/lib/oracle/artifact-gate.mjs +99 -0
  193. package/lib/oracle/cli.mjs +204 -0
  194. package/lib/oracle/daemon-entry.mjs +14 -0
  195. package/lib/oracle/dispatch.mjs +81 -0
  196. package/lib/oracle/execute.mjs +143 -0
  197. package/lib/oracle/gaps.mjs +76 -0
  198. package/lib/oracle/index.mjs +84 -0
  199. package/lib/oracle/issues.mjs +164 -0
  200. package/lib/oracle/org-graph.mjs +170 -0
  201. package/lib/oracle/policy.mjs +80 -0
  202. package/lib/oracle/read-model.mjs +398 -0
  203. package/lib/oracle/reconcile.mjs +191 -0
  204. package/lib/oracle/routing.mjs +89 -0
  205. package/lib/oracle/synthesize.mjs +384 -0
  206. package/lib/oracle/verdicts.mjs +51 -0
  207. package/lib/orchestration/worker.mjs +88 -27
  208. package/lib/orchestration-policy.mjs +50 -0
  209. package/lib/parity.mjs +96 -2
  210. package/lib/persona-sections.mjs +66 -0
  211. package/lib/playwright-demo.mjs +252 -0
  212. package/lib/project-init-shared.mjs +4 -1
  213. package/lib/prompt-composer.js +9 -4
  214. package/lib/prompt-validation-contract.mjs +24 -0
  215. package/lib/provider-capabilities.js +57 -11
  216. package/lib/providers/contract/adapters/confluence/index.mjs +181 -0
  217. package/lib/providers/contract/adapters/git/index.mjs +115 -0
  218. package/lib/providers/contract/adapters/github/index.mjs +166 -0
  219. package/lib/providers/contract/adapters/jira/index.mjs +187 -0
  220. package/lib/providers/contract/adapters/slack/index.mjs +175 -0
  221. package/lib/providers/contract/contract-tests.mjs +57 -0
  222. package/lib/providers/contract/errors.mjs +48 -0
  223. package/lib/providers/contract/interface.mjs +50 -0
  224. package/lib/providers/contract/registry.mjs +102 -0
  225. package/lib/providers/copilot-auth.mjs +297 -0
  226. package/lib/providers/credential-bootstrap.mjs +178 -0
  227. package/lib/providers/credential-catalog.mjs +46 -0
  228. package/lib/providers/credential-sources.mjs +63 -0
  229. package/lib/providers/creds.mjs +5 -2
  230. package/lib/providers/op-run.mjs +59 -0
  231. package/lib/providers/secret-resolver.mjs +159 -0
  232. package/lib/publish-template.mjs +163 -0
  233. package/lib/publish-tooling.mjs +119 -0
  234. package/lib/publish.mjs +305 -0
  235. package/lib/registry/cli.mjs +82 -0
  236. package/lib/registry/consolidation.mjs +147 -0
  237. package/lib/registry/generate-docs.mjs +75 -0
  238. package/lib/registry/skill-verification.mjs +57 -0
  239. package/lib/registry/surface-map.mjs +76 -0
  240. package/lib/registry/validate.mjs +135 -0
  241. package/lib/resources/budget.mjs +82 -0
  242. package/lib/resources/process-budget.mjs +45 -0
  243. package/lib/rules-delivery.mjs +9 -2
  244. package/lib/rules-read.mjs +26 -0
  245. package/lib/runtime-env.mjs +23 -0
  246. package/lib/runtime-pressure.mjs +51 -7
  247. package/lib/schema-infer.mjs +13 -2
  248. package/lib/server/chat-loop.mjs +622 -0
  249. package/lib/server/demo-preview.mjs +63 -0
  250. package/lib/server/index.mjs +229 -8
  251. package/lib/server/langfuse-login.mjs +3 -3
  252. package/lib/service-manager.mjs +61 -9
  253. package/lib/session-store.mjs +1 -1
  254. package/lib/setup.mjs +102 -2
  255. package/lib/specialists/prompt-schema.mjs +19 -10
  256. package/lib/specialists/roster.mjs +43 -0
  257. package/lib/specialists/scaffold.mjs +56 -0
  258. package/lib/storage/backend.mjs +6 -6
  259. package/lib/storage/embeddings-local.mjs +6 -3
  260. package/lib/storage/file-lock.mjs +18 -11
  261. package/lib/storage/hybrid-query.mjs +7 -4
  262. package/lib/storage/state-source.mjs +5 -5
  263. package/lib/storage/sync.mjs +2 -3
  264. package/lib/telemetry/rule-calls.mjs +52 -0
  265. package/lib/template-registry.mjs +2 -2
  266. package/lib/templates/visual-requirements.mjs +27 -51
  267. package/lib/test-corpus-inventory.mjs +313 -0
  268. package/lib/uninstall/uninstall.mjs +19 -7
  269. package/lib/update.mjs +12 -0
  270. package/lib/upgrade.mjs +14 -0
  271. package/lib/wireframe.mjs +20 -14
  272. package/lib/worker/run.mjs +17 -4
  273. package/lib/worker/trace.mjs +11 -3
  274. package/package.json +29 -13
  275. package/personas/construct.md +13 -13
  276. package/platforms/claude/settings.template.json +36 -0
  277. package/rules/common/patterns.md +1 -1
  278. package/rules/common/release-gates.md +4 -3
  279. package/scripts/sync-specialists.mjs +187 -60
  280. package/skills/devops/data-engineering.md +1 -1
  281. package/skills/docs/adr-workflow.md +1 -0
  282. package/skills/docs/backlog-proposal-workflow.md +1 -0
  283. package/skills/docs/codebase-research-workflow.md +40 -0
  284. package/skills/docs/customer-profile-workflow.md +1 -0
  285. package/skills/docs/document-ingest-workflow.md +1 -0
  286. package/skills/docs/evidence-ingest-workflow.md +1 -0
  287. package/skills/docs/init-docs.md +2 -2
  288. package/skills/docs/init-project.md +8 -3
  289. package/skills/docs/prd-workflow.md +24 -1
  290. package/skills/docs/prfaq-workflow.md +1 -0
  291. package/skills/docs/product-intelligence-workflow.md +1 -0
  292. package/skills/docs/product-signal-workflow.md +1 -0
  293. package/skills/docs/research-workflow.md +54 -37
  294. package/skills/docs/runbook-workflow.md +1 -0
  295. package/skills/docs/strategy-workflow.md +1 -0
  296. package/skills/docs/user-research-workflow.md +40 -0
  297. package/skills/operating/orchestration-reference.md +1 -1
  298. package/skills/roles/architect.md +5 -0
  299. package/skills/roles/operator.docs.md +4 -0
  300. package/skills/routing.md +4 -2
  301. package/specialists/artifact-manifest.json +480 -0
  302. package/specialists/artifact-manifest.schema.json +53 -0
  303. package/specialists/audit-enrichments.json +454 -0
  304. package/specialists/contracts.json +37 -25
  305. package/specialists/prompts/_shared/validation-contract.md +26 -0
  306. package/specialists/prompts/cx-accessibility.md +21 -2
  307. package/specialists/prompts/cx-ai-engineer.md +24 -1
  308. package/specialists/prompts/cx-architect.md +4 -1
  309. package/specialists/prompts/cx-business-strategist.md +23 -2
  310. package/specialists/prompts/cx-data-analyst.md +22 -1
  311. package/specialists/prompts/cx-data-engineer.md +23 -2
  312. package/specialists/prompts/cx-debugger.md +21 -2
  313. package/specialists/prompts/cx-designer.md +26 -3
  314. package/specialists/prompts/cx-devil-advocate.md +19 -2
  315. package/specialists/prompts/cx-docs-keeper.md +28 -1
  316. package/specialists/prompts/cx-engineer.md +22 -1
  317. package/specialists/prompts/cx-evaluator.md +18 -1
  318. package/specialists/prompts/cx-explorer.md +21 -2
  319. package/specialists/prompts/cx-legal-compliance.md +21 -2
  320. package/specialists/prompts/cx-operations.md +21 -2
  321. package/specialists/prompts/cx-oracle.md +94 -0
  322. package/specialists/prompts/cx-orchestrator.md +20 -1
  323. package/specialists/prompts/cx-platform-engineer.md +25 -2
  324. package/specialists/prompts/cx-product-manager.md +32 -1
  325. package/specialists/prompts/cx-qa.md +24 -2
  326. package/specialists/prompts/cx-rd-lead.md +23 -2
  327. package/specialists/prompts/cx-release-manager.md +23 -2
  328. package/specialists/prompts/cx-researcher.md +22 -2
  329. package/specialists/prompts/cx-reviewer.md +18 -1
  330. package/specialists/prompts/cx-security.md +22 -2
  331. package/specialists/prompts/cx-sre.md +30 -3
  332. package/specialists/prompts/cx-test-automation.md +7 -1
  333. package/specialists/prompts/cx-trace-reviewer.md +22 -3
  334. package/specialists/prompts/cx-ux-researcher.md +21 -2
  335. package/specialists/registry.json +52 -173
  336. package/specialists/tone-profiles.json +42 -0
  337. package/templates/demos/playwright/demo-recording.config.mjs +47 -0
  338. package/templates/demos/recordings/agentic-platforms-prd.json +19 -0
  339. package/templates/demos/scripts/agentic-platforms-prd.json +44 -0
  340. package/templates/demos/specs/_helpers/scroll-artifact.ts +89 -0
  341. package/templates/demos/tapes/agentic-platforms-prd.tape +49 -0
  342. package/templates/demos/tapes/resource-guard-rails.tape +49 -0
  343. package/templates/demos/vhs/construct-cockpit.json +24 -0
  344. package/templates/distribution/construct-brand.typ +446 -0
  345. package/templates/distribution/construct-decision.typ +38 -0
  346. package/templates/distribution/construct-deck.html +95 -0
  347. package/templates/distribution/construct-pdf.typ +38 -0
  348. package/templates/distribution/construct-prd.typ +38 -0
  349. package/templates/distribution/construct-research.typ +38 -0
  350. package/templates/distribution/construct-web.html +92 -0
  351. package/templates/distribution/fonts/Geist-Bold.ttf +0 -0
  352. package/templates/distribution/fonts/Geist-Medium.ttf +0 -0
  353. package/templates/distribution/fonts/Geist-Regular.ttf +0 -0
  354. package/templates/distribution/fonts/Geist-SemiBold.ttf +0 -0
  355. package/templates/distribution/fonts/GeistMono-Medium.ttf +0 -0
  356. package/templates/distribution/fonts/GeistMono-Regular.ttf +0 -0
  357. package/templates/distribution/fonts/GeistMono-SemiBold.ttf +0 -0
  358. package/templates/distribution/fonts/IBMPlexMono-Regular.otf +0 -0
  359. package/templates/distribution/fonts/JetBrainsMono-Medium.ttf +0 -0
  360. package/templates/distribution/fonts/JetBrainsMono-Regular.ttf +0 -0
  361. package/templates/distribution/fonts/JetBrainsMono-SemiBold.ttf +0 -0
  362. package/templates/distribution/fonts/PlusJakartaSans-Bold.ttf +0 -0
  363. package/templates/distribution/fonts/PlusJakartaSans-Medium.ttf +0 -0
  364. package/templates/distribution/fonts/PlusJakartaSans-Regular.ttf +0 -0
  365. package/templates/distribution/fonts/PlusJakartaSans-SemiBold.ttf +0 -0
  366. package/templates/distribution/fonts/README.md +36 -0
  367. package/templates/distribution/fonts/SpaceGrotesk-Variable.ttf +0 -0
  368. package/templates/distribution/fonts/handwritten/Caveat.ttf +0 -0
  369. package/templates/distribution/fonts/legacy/IBMPlexMono-Regular.otf +0 -0
  370. package/templates/distribution/fonts/legacy/Inter-Medium.otf +0 -0
  371. package/templates/distribution/fonts/legacy/Inter-Regular.otf +0 -0
  372. package/templates/distribution/fonts/legacy/Inter-SemiBold.otf +0 -0
  373. package/templates/distribution/fonts/legacy/InterDisplay-SemiBold.otf +0 -0
  374. package/templates/distribution/fonts/legacy/SourceSerif4-Regular.otf +0 -0
  375. package/templates/distribution/fonts/legacy/SourceSerif4-Semibold.otf +0 -0
  376. package/templates/distribution/icons/activity.svg +15 -0
  377. package/templates/distribution/icons/alert-triangle.svg +17 -0
  378. package/templates/distribution/icons/book-open.svg +16 -0
  379. package/templates/distribution/icons/bot.svg +20 -0
  380. package/templates/distribution/icons/brain.svg +22 -0
  381. package/templates/distribution/icons/circle-check.svg +16 -0
  382. package/templates/distribution/icons/clipboard-check.svg +17 -0
  383. package/templates/distribution/icons/cpu.svg +28 -0
  384. package/templates/distribution/icons/database.svg +17 -0
  385. package/templates/distribution/icons/eye.svg +16 -0
  386. package/templates/distribution/icons/file-text.svg +19 -0
  387. package/templates/distribution/icons/gauge.svg +16 -0
  388. package/templates/distribution/icons/git-branch.svg +17 -0
  389. package/templates/distribution/icons/key.svg +17 -0
  390. package/templates/distribution/icons/layers.svg +17 -0
  391. package/templates/distribution/icons/list-checks.svg +19 -0
  392. package/templates/distribution/icons/lock.svg +16 -0
  393. package/templates/distribution/icons/message-square.svg +15 -0
  394. package/templates/distribution/icons/network.svg +19 -0
  395. package/templates/distribution/icons/route.svg +17 -0
  396. package/templates/distribution/icons/scale.svg +19 -0
  397. package/templates/distribution/icons/search.svg +16 -0
  398. package/templates/distribution/icons/send.svg +16 -0
  399. package/templates/distribution/icons/server.svg +18 -0
  400. package/templates/distribution/icons/shield-check.svg +16 -0
  401. package/templates/distribution/icons/users.svg +18 -0
  402. package/templates/distribution/icons/webhook.svg +17 -0
  403. package/templates/distribution/icons/workflow.svg +17 -0
  404. package/templates/distribution/icons/wrench.svg +15 -0
  405. package/templates/distribution/run.mjs +18 -1
  406. package/templates/docs/adr.md +7 -0
  407. package/templates/docs/construct_guide.md +10 -10
  408. package/templates/docs/customer-profile.md +4 -0
  409. package/templates/docs/one-pager.md +4 -0
  410. package/templates/docs/postmortem.md +31 -0
  411. package/templates/docs/prd-platform.md +19 -0
  412. package/templates/docs/prd.md +15 -0
  413. package/templates/docs/prfaq.md +7 -0
  414. package/templates/docs/qa-strategy.md +103 -0
  415. package/templates/docs/research-brief.md +8 -0
  416. package/templates/docs/rfc-platform.md +12 -0
  417. package/templates/docs/test-plan.md +7 -0
  418. package/lib/ingest/chunker.mjs +0 -94
  419. package/lib/ingest/pipeline.mjs +0 -53
  420. package/lib/ingest/store.mjs +0 -82
  421. package/lib/mode-commands.mjs +0 -122
  422. package/lib/policy/unified-gates.mjs +0 -96
  423. package/lib/profiles/validate-custom.mjs +0 -114
  424. package/lib/services/telemetry-backend.mjs +0 -177
  425. package/lib/storage/fusion.mjs +0 -95
@@ -0,0 +1,76 @@
1
+ /**
2
+ * lib/registry/surface-map.mjs — ADR-0039 primary interaction surface per command group.
3
+ *
4
+ * Declarative map from CLI command name to primary surface tier. The CLI registry
5
+ * remains the substrate; this file governs discovery posture without removing verbs.
6
+ *
7
+ * Surfaces:
8
+ * agent-mcp — MCP tool canonical; CLI --json twin
9
+ * thin-cli — human types this at a prompt
10
+ * tui — interactive loop emphasized
11
+ * dashboard — visual/telemetry emphasized
12
+ * internal — CI/harness only
13
+ */
14
+
15
+ export const SURFACE_TIERS = ['agent-mcp', 'thin-cli', 'tui', 'dashboard', 'internal'];
16
+
17
+ /** @type {Record<string, string>} */
18
+ export const COMMAND_SURFACE = {
19
+ install: 'thin-cli',
20
+ init: 'thin-cli',
21
+ dev: 'thin-cli',
22
+ dashboard: 'thin-cli',
23
+ stop: 'thin-cli',
24
+ status: 'thin-cli',
25
+ doctor: 'thin-cli',
26
+ sync: 'thin-cli',
27
+ ingest: 'agent-mcp',
28
+ drop: 'agent-mcp',
29
+ distill: 'agent-mcp',
30
+ ask: 'agent-mcp',
31
+ search: 'agent-mcp',
32
+ knowledge: 'agent-mcp',
33
+ memory: 'agent-mcp',
34
+ reflect: 'agent-mcp',
35
+ intake: 'tui',
36
+ workflow: 'agent-mcp',
37
+ graph: 'agent-mcp',
38
+ capability: 'agent-mcp',
39
+ execution: 'agent-mcp',
40
+ orchestrate: 'agent-mcp',
41
+ models: 'agent-mcp',
42
+ profile: 'tui',
43
+ sandbox: 'tui',
44
+ review: 'dashboard',
45
+ telemetry: 'dashboard',
46
+ evals: 'dashboard',
47
+ improvement: 'dashboard',
48
+ ci: 'thin-cli',
49
+ docs: 'thin-cli',
50
+ export: 'agent-mcp',
51
+ diagram: 'agent-mcp',
52
+ demo: 'agent-mcp',
53
+ beads: 'thin-cli',
54
+ hook: 'internal',
55
+ 'lint:comments': 'internal',
56
+ 'lint:agents': 'internal',
57
+ 'registry:status': 'internal',
58
+ 'registry:validate': 'internal',
59
+ 'registry:generate-docs': 'internal',
60
+ rules: 'thin-cli',
61
+ };
62
+
63
+ export function surfaceForCommand(name) {
64
+ if (COMMAND_SURFACE[name]) return COMMAND_SURFACE[name];
65
+ if (name.includes(':')) return 'internal';
66
+ return 'thin-cli';
67
+ }
68
+
69
+ export function commandsBySurface(commands) {
70
+ const grouped = Object.fromEntries(SURFACE_TIERS.map((t) => [t, []]));
71
+ for (const cmd of commands) {
72
+ const surface = cmd.surface ?? surfaceForCommand(cmd.name);
73
+ grouped[surface]?.push(cmd.name);
74
+ }
75
+ return grouped;
76
+ }
@@ -0,0 +1,135 @@
1
+ /**
2
+ * lib/registry/validate.mjs — validate registry/capabilities.json against repo reality.
3
+ *
4
+ * Checks that declared skills, templates, workflow types, and verification paths
5
+ * resolve on disk; flags stale lastValidated entries; ensures every embedded
6
+ * workflow type has a registry row. Consumed by `construct registry validate`
7
+ * and `construct doctor`.
8
+ */
9
+
10
+ import fs from 'node:fs';
11
+ import path from 'node:path';
12
+ import { fileURLToPath } from 'node:url';
13
+
14
+ import { WORKFLOW_TYPES } from '../embedded-contract/workflow-defs.mjs';
15
+ import { STRUCTURE_REQUIREMENTS } from '../templates/visual-requirements.mjs';
16
+
17
+ const MODULE_DIR = path.dirname(fileURLToPath(import.meta.url));
18
+ const REPO_ROOT = path.resolve(MODULE_DIR, '..', '..');
19
+ const REGISTRY_PATH = path.join(REPO_ROOT, 'registry', 'capabilities.json');
20
+ const STALE_MS = 90 * 24 * 60 * 60 * 1000;
21
+
22
+ function exists(rel) {
23
+ return fs.existsSync(path.join(REPO_ROOT, rel));
24
+ }
25
+
26
+ function resolveSkillPath(skillId) {
27
+ return path.join(REPO_ROOT, 'skills', `${skillId}.md`);
28
+ }
29
+
30
+ function resolveTemplate(name) {
31
+ const shipped = path.join(REPO_ROOT, 'templates', 'docs', `${name}.md`);
32
+ if (exists(path.relative(REPO_ROOT, shipped))) return true;
33
+ return false;
34
+ }
35
+
36
+ export function loadCapabilityRegistry({ rootDir = REPO_ROOT } = {}) {
37
+ const file = path.join(rootDir, 'registry', 'capabilities.json');
38
+ if (!fs.existsSync(file)) {
39
+ return { version: 0, capabilities: [], error: 'registry/capabilities.json missing' };
40
+ }
41
+ try {
42
+ return JSON.parse(fs.readFileSync(file, 'utf8'));
43
+ } catch (err) {
44
+ return { version: 0, capabilities: [], error: err?.message || String(err) };
45
+ }
46
+ }
47
+
48
+ export function validateCapabilityRegistry({ rootDir = REPO_ROOT, now = Date.now() } = {}) {
49
+ const raw = loadCapabilityRegistry({ rootDir });
50
+ const errors = [];
51
+ const warnings = [];
52
+ const ids = new Set();
53
+
54
+ if (raw.error) errors.push(raw.error);
55
+
56
+ for (const cap of raw.capabilities ?? []) {
57
+ if (!cap.id) {
58
+ errors.push('capability missing id');
59
+ continue;
60
+ }
61
+ if (ids.has(cap.id)) errors.push(`duplicate capability id: ${cap.id}`);
62
+ ids.add(cap.id);
63
+
64
+ if (cap.skill && !fs.existsSync(resolveSkillPath(cap.skill))) {
65
+ errors.push(`${cap.id}: skill file missing: skills/${cap.skill}.md`);
66
+ }
67
+
68
+ if (cap.embeddedWorkflow && !WORKFLOW_TYPES.includes(cap.embeddedWorkflow)) {
69
+ errors.push(`${cap.id}: unknown embeddedWorkflow "${cap.embeddedWorkflow}"`);
70
+ }
71
+
72
+ if (cap.template && !resolveTemplate(cap.template)) {
73
+ errors.push(`${cap.id}: template missing: templates/docs/${cap.template}.md`);
74
+ }
75
+
76
+ const ver = cap.verification ?? {};
77
+ if (ver.functional && !exists(ver.functional)) {
78
+ errors.push(`${cap.id}: verification.functional missing: ${ver.functional}`);
79
+ }
80
+ if (ver.hostEmulation && !exists(ver.hostEmulation)) {
81
+ errors.push(`${cap.id}: verification.hostEmulation missing: ${ver.hostEmulation}`);
82
+ }
83
+ if (ver.structureRequirements && !STRUCTURE_REQUIREMENTS[ver.structureRequirements]) {
84
+ errors.push(`${cap.id}: unknown structureRequirements "${ver.structureRequirements}"`);
85
+ }
86
+
87
+ const hasTest = ver.functional || ver.hostEmulation || ver.untestableRationale;
88
+ if (!hasTest && (cap.criticality === 'P0' || cap.criticality === 'P1')) {
89
+ warnings.push(`${cap.id}: P0/P1 capability has no functional test or untestableRationale`);
90
+ }
91
+
92
+ const surfaces = cap.surfaces ?? {};
93
+ for (const [surface, status] of Object.entries(surfaces)) {
94
+ if (!status?.supported) continue;
95
+ if (status.primary) {
96
+ const tierPath = path.join('tests', 'capabilities', cap.id, `${surface}.test.mjs`);
97
+ const hasSurfaceTest = exists(tierPath);
98
+ const hasFunctional = !!(ver.functional || ver.hostEmulation);
99
+ if (!hasSurfaceTest && !hasFunctional && !ver.untestableRationale) {
100
+ warnings.push(`${cap.id}: primary surface "${surface}" has no tests/capabilities/${cap.id}/${surface}.test.mjs`);
101
+ }
102
+ }
103
+ }
104
+
105
+ if (cap.lastValidated) {
106
+ const age = now - Date.parse(cap.lastValidated);
107
+ if (Number.isFinite(age) && age > STALE_MS) {
108
+ warnings.push(`${cap.id}: lastValidated older than 90 days (${cap.lastValidated})`);
109
+ }
110
+ } else if (cap.criticality === 'P0') {
111
+ warnings.push(`${cap.id}: P0 capability never validated (lastValidated null)`);
112
+ }
113
+
114
+ for (const dep of cap.dependencies?.skills ?? []) {
115
+ if (!fs.existsSync(resolveSkillPath(dep))) {
116
+ errors.push(`${cap.id}: dependency skill missing: skills/${dep}.md`);
117
+ }
118
+ }
119
+ }
120
+
121
+ for (const wf of WORKFLOW_TYPES) {
122
+ const covered = (raw.capabilities ?? []).some((c) => c.embeddedWorkflow === wf);
123
+ if (!covered) {
124
+ warnings.push(`embedded workflow "${wf}" has no registry entry yet`);
125
+ }
126
+ }
127
+
128
+ return {
129
+ valid: errors.length === 0,
130
+ errors,
131
+ warnings,
132
+ count: (raw.capabilities ?? []).length,
133
+ registryPath: path.join(rootDir, 'registry', 'capabilities.json'),
134
+ };
135
+ }
@@ -82,6 +82,16 @@ export function measureUsage(projectRoot, env = process.env) {
82
82
  return result;
83
83
  }
84
84
 
85
+ let lastBudgetWarnAt = 0;
86
+
87
+ export function warnBudgetOnce(message, env = process.env) {
88
+ if (env.NODE_ENV === 'test' && env.CONSTRUCT_BUDGET_WARN_IN_TEST !== '1') return;
89
+ const now = Date.now();
90
+ if (now - lastBudgetWarnAt < 60_000) return;
91
+ lastBudgetWarnAt = now;
92
+ process.stderr.write(`[construct:budget] ${message}\n`);
93
+ }
94
+
85
95
  export function reserveOrReject(projectRoot, category, sizeBytes, env = process.env) {
86
96
  if (!CATEGORY_PATHS[category]) {
87
97
  return { ok: true, source: 'unknown-category' };
@@ -110,6 +120,78 @@ export function reserveOrReject(projectRoot, category, sizeBytes, env = process.
110
120
  return { ok: true };
111
121
  }
112
122
 
123
+ function listHardRejectFiles(projectRoot) {
124
+ const files = [];
125
+ for (const cat of HARD_REJECT_CATEGORIES) {
126
+ const rel = CATEGORY_PATHS[cat];
127
+ if (!rel) continue;
128
+ const dir = path.join(projectRoot, rel);
129
+ if (!fs.existsSync(dir)) continue;
130
+ const walk = (current) => {
131
+ for (const entry of fs.readdirSync(current, { withFileTypes: true })) {
132
+ const full = path.join(current, entry.name);
133
+ if (entry.isDirectory()) {
134
+ walk(full);
135
+ continue;
136
+ }
137
+ try {
138
+ const stat = fs.statSync(full);
139
+ files.push({
140
+ category: cat,
141
+ path: full,
142
+ mtimeMs: stat.mtimeMs,
143
+ bytes: stat.size,
144
+ });
145
+ } catch { /* race */ }
146
+ }
147
+ };
148
+ walk(dir);
149
+ }
150
+ files.sort((a, b) => a.mtimeMs - b.mtimeMs);
151
+ return files;
152
+ }
153
+
154
+ export function planEmergencyReclaim(projectRoot, bytesNeeded, env = process.env) {
155
+ const usage = measureUsage(projectRoot, env);
156
+ const bytesToFree = Math.max(0, usage.totalCxBytes + bytesNeeded - usage.totalCxCap);
157
+ if (bytesToFree <= 0) return [];
158
+
159
+ const actions = [];
160
+ let freed = 0;
161
+ for (const file of listHardRejectFiles(projectRoot)) {
162
+ if (freed >= bytesToFree) break;
163
+ actions.push({
164
+ category: file.category,
165
+ path: file.path,
166
+ reason: 'emergency-reclaim',
167
+ bytes: file.bytes,
168
+ });
169
+ freed += file.bytes;
170
+ }
171
+ return actions;
172
+ }
173
+
174
+ export function ensureDiskWrite(projectRoot, category, sizeBytes, env = process.env) {
175
+ let check = reserveOrReject(projectRoot, category, sizeBytes, env);
176
+ if (check.ok) return check;
177
+
178
+ if (!HARD_REJECT_CATEGORIES.has(category)) {
179
+ return check;
180
+ }
181
+
182
+ const actions = planEmergencyReclaim(projectRoot, sizeBytes, env);
183
+ let reclaimed = { removed: [], bytesFreed: 0 };
184
+ if (actions.length) {
185
+ reclaimed = executePrune(actions);
186
+ check = reserveOrReject(projectRoot, category, sizeBytes, env);
187
+ if (check.ok) {
188
+ return { ...check, reclaimed };
189
+ }
190
+ }
191
+
192
+ return { ...check, reclaimed };
193
+ }
194
+
113
195
  export function planPrune(projectRoot, env = process.env, opts = {}) {
114
196
  const budgets = loadResourceBudgets(projectRoot, env);
115
197
  const disk = budgets?.disk ?? {};
@@ -0,0 +1,45 @@
1
+ /**
2
+ * lib/resources/process-budget.mjs — RSS caps from construct.config.json.
3
+ *
4
+ * Maps long-running Construct processes to `resources.process.*` limits so
5
+ * daemon runners and embed workers share one config surface with doctor.
6
+ */
7
+
8
+ import { loadProjectConfig } from '../config/project-config.mjs';
9
+ import { DEFAULT_MEMORY_CAP_MB } from '../daemons/contract.mjs';
10
+
11
+ export function loadProcessBudgets(projectRoot, env = process.env) {
12
+ const { config } = loadProjectConfig(projectRoot, env);
13
+ const process = config?.resources?.process ?? {};
14
+ return {
15
+ embedDaemonMaxRssMb: process.embedDaemonMaxRssMb ?? 800,
16
+ mcpServerMaxRssMb: process.mcpServerMaxRssMb ?? 250,
17
+ workerReplicaMaxRssMb: process.workerReplicaMaxRssMb ?? DEFAULT_MEMORY_CAP_MB,
18
+ };
19
+ }
20
+
21
+ export function memoryCapMbFor(daemonName, projectRoot, env = process.env) {
22
+ const budgets = loadProcessBudgets(projectRoot, env);
23
+ switch (daemonName) {
24
+ case 'embed':
25
+ case 'embed-daemon':
26
+ return budgets.embedDaemonMaxRssMb;
27
+ case 'mcp':
28
+ case 'construct-mcp':
29
+ return budgets.mcpServerMaxRssMb;
30
+ case 'intake':
31
+ case 'oracle':
32
+ case 'worker':
33
+ return budgets.workerReplicaMaxRssMb;
34
+ default:
35
+ return DEFAULT_MEMORY_CAP_MB;
36
+ }
37
+ }
38
+
39
+ export function currentRssMb() {
40
+ return process.memoryUsage().rss / (1024 * 1024);
41
+ }
42
+
43
+ export function rssOverCap(capMb) {
44
+ return currentRssMb() > capMb;
45
+ }
@@ -15,11 +15,18 @@
15
15
  import fs from 'node:fs';
16
16
  import path from 'node:path';
17
17
 
18
+ import { logRuleCall } from './telemetry/rule-calls.mjs';
19
+
18
20
  const SKIP_DIRS = new Set(['node_modules', '.git', '.cx', 'dist', 'build', '.next', 'vendor', '.beads']);
19
21
  const MAX_FILES = 5000;
20
22
 
21
- function parseRule(absPath) {
23
+ function parseRule(absPath, ruleRel) {
22
24
  const raw = fs.readFileSync(absPath, 'utf8');
25
+ logRuleCall({
26
+ rulePath: `rules/${ruleRel.replace(/\.md$/, '')}.md`,
27
+ source: 'sync',
28
+ callerContext: 'rules-delivery',
29
+ });
23
30
  const fm = raw.match(/^---\n([\s\S]*?)\n---\n?/);
24
31
  if (!fm) return null;
25
32
  const globs = [...fm[1].matchAll(/^\s*-\s+"([^"]+)"\s*$/gm)].map((m) => m[1]);
@@ -38,7 +45,7 @@ export function collectGlobScopedRules(rulesDir) {
38
45
  const childRel = rel ? `${rel}/${e.name}` : e.name;
39
46
  if (e.isDirectory()) walk(full, childRel);
40
47
  else if (e.name.endsWith('.md')) {
41
- const parsed = parseRule(full);
48
+ const parsed = parseRule(full, childRel);
42
49
  if (parsed) out.push({ rule: childRel.replace(/\.md$/, ''), ...parsed });
43
50
  }
44
51
  }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * lib/rules-read.mjs — read rule files with reference telemetry.
3
+ *
4
+ * Central read path for enforcement and sync tooling so `construct rules usage`
5
+ * can roll up which rules are actually loaded at runtime.
6
+ */
7
+
8
+ import fs from 'node:fs';
9
+ import path from 'node:path';
10
+ import { fileURLToPath } from 'node:url';
11
+
12
+ import { logRuleCall } from './telemetry/rule-calls.mjs';
13
+
14
+ const REPO_ROOT = path.join(path.dirname(fileURLToPath(import.meta.url)), '..');
15
+
16
+ export function normalizeRulePath(rel) {
17
+ const trimmed = String(rel || '').replace(/^\.\//, '');
18
+ if (trimmed.startsWith('rules/')) return trimmed;
19
+ return `rules/${trimmed.replace(/\.md$/, '')}.md`;
20
+ }
21
+
22
+ export function readRuleFile(rel, { rootDir = REPO_ROOT, source = 'validation', callerContext, env } = {}) {
23
+ const rulePath = normalizeRulePath(rel);
24
+ logRuleCall({ rulePath, source, callerContext }, { env });
25
+ return fs.readFileSync(path.join(rootDir, rulePath), 'utf8');
26
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * lib/runtime-env.mjs — Construct runtime environment with credential bootstrap.
3
+ *
4
+ * prepareConstructEnv merges ~/.construct/config.env into the process environment.
5
+ * Credential auto-link from 1Password runs only when autoLink:true (setup-credentials).
6
+ */
7
+
8
+ import { loadConstructEnv } from './env-config.mjs';
9
+ import { ensureConstructCredentials } from './providers/credential-bootstrap.mjs';
10
+
11
+ export { loadConstructEnv } from './env-config.mjs';
12
+
13
+ export function prepareConstructEnv({
14
+ rootDir, homeDir, env = process.env, warn = true, cwd, autoLink = false,
15
+ } = {}) {
16
+ ensureConstructCredentials({
17
+ env,
18
+ cwd: cwd || rootDir || process.cwd(),
19
+ home: homeDir,
20
+ autoLink,
21
+ });
22
+ return loadConstructEnv({ rootDir, homeDir, env, warn });
23
+ }
@@ -91,6 +91,19 @@ function isHelperCommand(command) {
91
91
  return /(context7-mcp|playwright-mcp|mcp-server-sequential-thinking)/.test(command);
92
92
  }
93
93
 
94
+ // VHS demo recorder spawns ttyd with a fixed flag set. Orphaned instances retain
95
+ // resources after VHS exits without teardown.
96
+
97
+ function isVhsTtydCommand(command) {
98
+ return /\bttyd\b/.test(command)
99
+ && /--once\b/.test(command)
100
+ && /rendererType canvas/.test(command);
101
+ }
102
+
103
+ function isVhsRecorderCommand(command) {
104
+ return /(?:^|\/)vhs(?:\s|$)/.test(command);
105
+ }
106
+
94
107
  function isCassIndexCommand(command) {
95
108
  return /\bcass index\b/.test(command);
96
109
  }
@@ -166,6 +179,28 @@ export function buildCleanupPlan(processEntries = [], thresholds = {}) {
166
179
  }
167
180
  }
168
181
 
182
+ const leakedVhsTtyd = processEntries
183
+ .filter((entry) => isVhsTtydCommand(entry.command)
184
+ && (entry.ppid === 1 || entry.elapsedSeconds >= 3600))
185
+ .sort((a, b) => b.elapsedSeconds - a.elapsedSeconds);
186
+ for (const entry of leakedVhsTtyd) {
187
+ if (!seen.has(entry.pid)) {
188
+ seen.add(entry.pid);
189
+ terminate.push({ ...entry, reason: 'leaked-vhs-ttyd' });
190
+ }
191
+ }
192
+
193
+ const staleVhs = processEntries
194
+ .filter((entry) => isVhsRecorderCommand(entry.command)
195
+ && entry.elapsedSeconds >= maxHelperAgeSeconds)
196
+ .sort((a, b) => b.elapsedSeconds - a.elapsedSeconds);
197
+ for (const entry of staleVhs) {
198
+ if (!seen.has(entry.pid)) {
199
+ seen.add(entry.pid);
200
+ terminate.push({ ...entry, reason: 'stale-vhs-recorder' });
201
+ }
202
+ }
203
+
169
204
  const cassCandidates = processEntries
170
205
  .filter((entry) => isCassIndexCommand(entry.command) && entry.elapsedSeconds >= maxCassIndexAgeSeconds)
171
206
  .sort((a, b) => b.elapsedSeconds - a.elapsedSeconds)
@@ -179,18 +214,27 @@ export function buildCleanupPlan(processEntries = [], thresholds = {}) {
179
214
  }
180
215
 
181
216
  function parsePsLine(line) {
182
- const match = line.match(/^\s*(\d+)\s+(\d+)\s+([0-9:-]+)\s+(.*)$/);
217
+ const match = line.match(/^\s*(\d+)\s+(\d+)\s+(\d+)\s+([0-9:-]+)\s+(.*)$/);
183
218
  if (!match) return null;
184
219
  return {
185
220
  pid: Number(match[1]),
186
- rssKb: Number(match[2]),
187
- elapsedSeconds: parseElapsedSeconds(match[3]),
188
- command: match[4],
221
+ ppid: Number(match[2]),
222
+ rssKb: Number(match[3]),
223
+ elapsedSeconds: parseElapsedSeconds(match[4]),
224
+ command: match[5],
189
225
  };
190
226
  }
191
227
 
228
+ function isPressureManagedEntry(entry) {
229
+ return isOpencodeCommand(entry.command)
230
+ || isHelperCommand(entry.command)
231
+ || isCassIndexCommand(entry.command)
232
+ || isVhsTtydCommand(entry.command)
233
+ || isVhsRecorderCommand(entry.command);
234
+ }
235
+
192
236
  export function listPressureProcesses({ spawnSyncFn = spawnSync } = {}) {
193
- const result = spawnSyncFn('ps', ['-ax', '-o', 'pid=,rss=,etime=,command='], {
237
+ const result = spawnSyncFn('ps', ['-ax', '-o', 'pid=,ppid=,rss=,etime=,command='], {
194
238
  encoding: 'utf8',
195
239
  stdio: ['ignore', 'pipe', 'ignore'],
196
240
  });
@@ -199,7 +243,7 @@ export function listPressureProcesses({ spawnSyncFn = spawnSync } = {}) {
199
243
  .split(/\r?\n/)
200
244
  .map(parsePsLine)
201
245
  .filter(Boolean)
202
- .filter((entry) => isOpencodeCommand(entry.command) || isHelperCommand(entry.command) || isCassIndexCommand(entry.command));
246
+ .filter(isPressureManagedEntry);
203
247
  }
204
248
 
205
249
  export function readSwapUsage({ spawnSyncFn = spawnSync } = {}) {
@@ -270,7 +314,7 @@ export function runPressureRelease({
270
314
  for (const entry of targets) {
271
315
  try {
272
316
  killFn(entry.pid, 'SIGTERM');
273
- killed.push({ pid: entry.pid, reason: entry.reason });
317
+ killed.push({ pid: entry.pid, reason: entry.reason, command: entry.command });
274
318
  } catch {
275
319
  // Process may already be gone or we lack permission — pressure relief
276
320
  // continues regardless; the loop is opportunistic per-pid, not transactional.
@@ -21,7 +21,7 @@
21
21
  * { name, type, description, required, pattern, example, enum, format, nested_fields }
22
22
  */
23
23
 
24
- import { extractDocumentText } from './document-extract.mjs';
24
+ import { extractDocumentTextNodeNative, extractDocumentTextAsync } from './document-extract.mjs';
25
25
  import { readCurrentModels, readOpenRouterApiKeyFromOpenCodeConfig } from './model-router.mjs';
26
26
  import { getUserEnvPath } from './env-config.mjs';
27
27
  import { pollFreeModels, topForTier } from './model-free-selector.mjs';
@@ -348,6 +348,17 @@ function buildUserPrompt(extracted) {
348
348
  ].join('\n');
349
349
  }
350
350
 
351
+ async function extractForSchemaInfer(filePath, { maxChars }) {
352
+ try {
353
+ return await extractDocumentTextNodeNative(filePath, { maxChars });
354
+ } catch (err) {
355
+ if (err.code === 'OFFICE_REQUIRES_DOCLING' || err.code === 'PDF_REQUIRES_DOCLING') {
356
+ return extractDocumentTextAsync(filePath, { maxChars });
357
+ }
358
+ throw err;
359
+ }
360
+ }
361
+
351
362
  /**
352
363
  * Infer a structured schema from a document file.
353
364
  *
@@ -372,7 +383,7 @@ export async function inferDocumentSchema(filePath, { maxChars = 40_000 } = {})
372
383
  environment,
373
384
  });
374
385
 
375
- const extracted = extractDocumentText(filePath, { maxChars });
386
+ const extracted = await extractForSchemaInfer(filePath, { maxChars });
376
387
  if (!extracted.text || extracted.text.trim().length === 0) {
377
388
  void lfUpdateTrace({ id: traceId, output: { error: 'no-text-extracted' }, metadata: { status: 'error' } });
378
389
  throw new Error(`No text could be extracted from: ${filePath}`);