@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.
- package/README.md +30 -19
- package/bin/construct +487 -87
- package/bin/construct-postinstall.mjs +30 -2
- package/examples/distribution/README.md +42 -0
- package/examples/distribution/manifest.json +53 -0
- package/examples/distribution/sources/adr.md +84 -0
- package/examples/distribution/sources/deck-one-pager.md +65 -0
- package/examples/distribution/sources/prd-platform.md +161 -0
- package/examples/distribution/sources/research-brief.md +80 -0
- package/examples/distribution/sources/rfc-platform.md +82 -0
- package/examples/distribution/sources/runbook.md +103 -0
- package/examples/distribution/sources/strategy.md +88 -0
- package/lib/adapters-sync.mjs +67 -0
- package/lib/artifact-gate-notice.mjs +38 -0
- package/lib/artifact-manifest.mjs +258 -0
- package/lib/artifact-release-gate.mjs +171 -0
- package/lib/artifact-reviewers.mjs +68 -0
- package/lib/artifact-type-from-path.mjs +79 -0
- package/lib/artifact-workflow.mjs +252 -0
- package/lib/audit-skills.mjs +4 -0
- package/lib/audit-specialists.mjs +285 -0
- package/lib/audit-trail.mjs +1 -1
- package/lib/auto-docs.mjs +90 -19
- package/lib/beads/drift.mjs +27 -6
- package/lib/beads-client.mjs +49 -121
- package/lib/beads-optimistic.mjs +5 -12
- package/lib/brand-fonts.mjs +93 -0
- package/lib/brand-prose.mjs +214 -0
- package/lib/brand-tokens.mjs +92 -0
- package/lib/bridges/copilot-proxy.mjs +13 -26
- package/lib/capability-ledger.mjs +156 -0
- package/lib/certification/artifact-fixtures.mjs +132 -0
- package/lib/certification/artifact-gates.mjs +63 -0
- package/lib/certification/artifact-provenance.mjs +97 -0
- package/lib/certification/canonical-scenarios.mjs +78 -0
- package/lib/certification/cli.mjs +191 -0
- package/lib/certification/dashboard-api.mjs +71 -0
- package/lib/certification/demo-parity.mjs +116 -0
- package/lib/certification/document-io-fixtures.mjs +246 -0
- package/lib/certification/document-workflow.mjs +97 -0
- package/lib/certification/eval-bridge.mjs +77 -0
- package/lib/certification/model-routing.mjs +149 -0
- package/lib/certification/prompt-budget.mjs +119 -0
- package/lib/certification/rc-gate.mjs +206 -0
- package/lib/certification/real-llm-scenarios.mjs +303 -0
- package/lib/certification/role-cards.mjs +113 -0
- package/lib/certification/role-overlays.mjs +117 -0
- package/lib/certification/run.mjs +122 -0
- package/lib/certification/runner.mjs +323 -0
- package/lib/certification/scenarios.mjs +51 -0
- package/lib/certification/skill-inventory.mjs +289 -0
- package/lib/certification/skill-scenarios.mjs +147 -0
- package/lib/certification/specialist-contracts.mjs +85 -0
- package/lib/certification/specialist-scenarios.mjs +175 -0
- package/lib/certification/stale-impact.mjs +146 -0
- package/lib/certification/status.mjs +252 -0
- package/lib/certification/store.mjs +77 -0
- package/lib/chat/cli.mjs +333 -0
- package/lib/chat/command-suggest.mjs +161 -0
- package/lib/chat/commands.mjs +215 -0
- package/lib/chat/config.mjs +142 -0
- package/lib/chat/context-compactor.mjs +250 -0
- package/lib/chat/context-continuation.mjs +253 -0
- package/lib/chat/continuation-source.mjs +58 -0
- package/lib/chat/demo-guide.mjs +61 -0
- package/lib/chat/design-tokens.mjs +91 -0
- package/lib/chat/desktop-binary.mjs +79 -0
- package/lib/chat/desktop-build.mjs +130 -0
- package/lib/chat/desktop-launcher.mjs +133 -0
- package/lib/chat/evidence.mjs +145 -0
- package/lib/chat/export.mjs +74 -0
- package/lib/chat/harness/driver.mjs +91 -0
- package/lib/chat/list-picker.mjs +112 -0
- package/lib/chat/model-picker.mjs +356 -0
- package/lib/chat/openrouter-fallback.mjs +151 -0
- package/lib/chat/permission-prompt.mjs +33 -0
- package/lib/chat/picker-catalog.mjs +45 -0
- package/lib/chat/policy-telemetry.mjs +34 -0
- package/lib/chat/present.mjs +246 -0
- package/lib/chat/session-context.mjs +39 -0
- package/lib/chat/session-persist.mjs +73 -0
- package/lib/chat/session-restore.mjs +71 -0
- package/lib/chat/session-settings.mjs +53 -0
- package/lib/chat/system-prompt.mjs +52 -0
- package/lib/chat/transparency.mjs +93 -0
- package/lib/chat/tui/color-scheme.mjs +42 -0
- package/lib/chat/tui/markdown.mjs +123 -0
- package/lib/chat/tui/presentation.mjs +100 -0
- package/lib/chat/tui/render.mjs +500 -0
- package/lib/chat/tui/turn-block.mjs +284 -0
- package/lib/chat/tui/turn-present.mjs +18 -0
- package/lib/chat/tui/turn-state.mjs +88 -0
- package/lib/chat/tui/usage.mjs +122 -0
- package/lib/chat/web-commands.mjs +146 -0
- package/lib/chat/web-launcher.mjs +63 -0
- package/lib/chat/web-picker-keys.mjs +46 -0
- package/lib/chat/web-session.mjs +159 -0
- package/lib/cli-commands.mjs +146 -15
- package/lib/cli-service-inventory.mjs +42 -0
- package/lib/comment-lint.mjs +6 -4
- package/lib/config/intake-policy.mjs +209 -0
- package/lib/config/project-config.mjs +11 -2
- package/lib/config/schema.mjs +86 -6
- package/lib/config/source-targets.mjs +311 -0
- package/lib/contract-schemas/decision.json +50 -0
- package/lib/contract-schemas/implementation.json +51 -0
- package/lib/contract-schemas/review-report.json +32 -0
- package/lib/contract-schemas/test-report.json +43 -0
- package/lib/contracts/construct-handoff.mjs +60 -0
- package/lib/contracts/validate.mjs +32 -3
- package/lib/contracts/violation-log.mjs +58 -5
- package/lib/dashboard-demo.mjs +71 -0
- package/lib/dashboard-static.mjs +19 -5
- package/lib/decisions/registry.mjs +5 -3
- package/lib/deck-export-pptx.mjs +1152 -0
- package/lib/demo-recording.mjs +142 -0
- package/lib/demo-script.mjs +114 -0
- package/lib/demo-surface.mjs +249 -0
- package/lib/demo.mjs +598 -140
- package/lib/diagram-export.mjs +192 -0
- package/lib/diagram.mjs +13 -11
- package/lib/docs-verify.mjs +25 -7
- package/lib/doctor/index.mjs +3 -1
- package/lib/doctor/project-adapters.mjs +44 -0
- package/lib/doctor/source-checkout.mjs +16 -0
- package/lib/doctor/watchers/cx-budget.mjs +98 -0
- package/lib/doctor/watchers/graph-staleness.mjs +52 -0
- package/lib/doctor/watchers/process-pressure.mjs +4 -3
- package/lib/document-export.mjs +384 -38
- package/lib/document-extract.mjs +44 -4
- package/lib/document-ingest.mjs +29 -20
- package/lib/embed/auto-sources.mjs +44 -0
- package/lib/embed/cli.mjs +4 -3
- package/lib/embed/daemon.mjs +18 -46
- package/lib/embed/demand-fetch.mjs +50 -19
- package/lib/embed/inbox.mjs +6 -10
- package/lib/embed/worker.mjs +4 -3
- package/lib/embedded-contract/index.mjs +11 -0
- package/lib/embedded-contract/model-resolve.mjs +10 -9
- package/lib/env-config.mjs +26 -0
- package/lib/evals/dataset.mjs +137 -0
- package/lib/evals/gates.mjs +175 -0
- package/lib/export-branding.mjs +32 -0
- package/lib/graph/build-co-change.mjs +54 -0
- package/lib/graph/build-from-registry.mjs +136 -0
- package/lib/graph/build-import-graph.mjs +153 -0
- package/lib/graph/cli.mjs +110 -0
- package/lib/graph/impact-cli.mjs +89 -0
- package/lib/graph/impact.mjs +108 -0
- package/lib/graph/staleness.mjs +44 -0
- package/lib/graph/store.mjs +172 -0
- package/lib/health-check.mjs +90 -76
- package/lib/hooks/artifact-release-gate.mjs +43 -0
- package/lib/hooks/brand-prose-lint.mjs +38 -0
- package/lib/hooks/graph-impact-advisory.mjs +62 -0
- package/lib/hooks/session-optimize.mjs +84 -207
- package/lib/hooks/session-start.mjs +18 -16
- package/lib/host-disposition.mjs +7 -0
- package/lib/improvement/cli.mjs +189 -0
- package/lib/improvement/controller.mjs +137 -0
- package/lib/improvement/proposal.mjs +120 -0
- package/lib/improvement/specialist-loop.mjs +192 -0
- package/lib/improvement/store.mjs +89 -0
- package/lib/improvement/surface.mjs +219 -0
- package/lib/ingest-tooling.mjs +97 -0
- package/lib/init/doc-lanes.mjs +165 -0
- package/lib/init-docs.mjs +31 -178
- package/lib/init-unified.mjs +50 -119
- package/lib/init-update-guide.mjs +102 -0
- package/lib/init-update.mjs +32 -1
- package/lib/init.mjs +8 -0
- package/lib/install/desktop-binary-download.mjs +85 -0
- package/lib/install/legacy-global-cleanup.mjs +189 -0
- package/lib/intake/constants.mjs +29 -0
- package/lib/intake/daemon.mjs +2 -0
- package/lib/intake/git-queue.mjs +9 -8
- package/lib/intake/intake-config.mjs +52 -105
- package/lib/intake/legacy-paths.mjs +5 -0
- package/lib/intake/queue.mjs +2 -1
- package/lib/intake/session-prelude.mjs +90 -1
- package/lib/libreoffice-export.mjs +97 -0
- package/lib/logging/rotate.mjs +9 -0
- package/lib/maintenance/docker-reclaim.mjs +206 -0
- package/lib/mcp/external-schema-cost.mjs +74 -0
- package/lib/mcp/server.mjs +103 -10
- package/lib/mcp/stdio-mcp-probe.mjs +188 -0
- package/lib/mcp/tool-budget.mjs +55 -4
- package/lib/mcp/tools/document.mjs +18 -4
- package/lib/mcp/tools/embedded-contract.mjs +14 -0
- package/lib/mcp/tools/skills.mjs +32 -3
- package/lib/mcp/tools/workflow.mjs +13 -1
- package/lib/model-free-selector.mjs +18 -0
- package/lib/model-registry.mjs +13 -229
- package/lib/model-router.mjs +373 -92
- package/lib/models/behavior-matrix.mjs +289 -0
- package/lib/models/catalog.mjs +209 -0
- package/lib/models/execution-capability-profile.mjs +196 -0
- package/lib/models/execution-policy.mjs +307 -0
- package/lib/models/provider-poll.mjs +383 -0
- package/lib/npm-spawn-env.mjs +17 -0
- package/lib/ollama/installed-models.mjs +129 -0
- package/lib/oracle/actions.mjs +187 -0
- package/lib/oracle/artifact-gate.mjs +99 -0
- package/lib/oracle/cli.mjs +204 -0
- package/lib/oracle/daemon-entry.mjs +14 -0
- package/lib/oracle/dispatch.mjs +81 -0
- package/lib/oracle/execute.mjs +143 -0
- package/lib/oracle/gaps.mjs +76 -0
- package/lib/oracle/index.mjs +84 -0
- package/lib/oracle/issues.mjs +164 -0
- package/lib/oracle/org-graph.mjs +170 -0
- package/lib/oracle/policy.mjs +80 -0
- package/lib/oracle/read-model.mjs +398 -0
- package/lib/oracle/reconcile.mjs +191 -0
- package/lib/oracle/routing.mjs +89 -0
- package/lib/oracle/synthesize.mjs +384 -0
- package/lib/oracle/verdicts.mjs +51 -0
- package/lib/orchestration/worker.mjs +88 -27
- package/lib/orchestration-policy.mjs +50 -0
- package/lib/parity.mjs +96 -2
- package/lib/playwright-demo.mjs +254 -0
- package/lib/project-init-shared.mjs +4 -1
- package/lib/prompt-composer.js +7 -3
- package/lib/prompt-validation-contract.mjs +24 -0
- package/lib/provider-capabilities.js +57 -11
- package/lib/providers/contract/adapters/confluence/index.mjs +181 -0
- package/lib/providers/contract/adapters/git/index.mjs +115 -0
- package/lib/providers/contract/adapters/github/index.mjs +166 -0
- package/lib/providers/contract/adapters/jira/index.mjs +187 -0
- package/lib/providers/contract/adapters/slack/index.mjs +175 -0
- package/lib/providers/contract/contract-tests.mjs +57 -0
- package/lib/providers/contract/errors.mjs +48 -0
- package/lib/providers/contract/interface.mjs +50 -0
- package/lib/providers/contract/registry.mjs +102 -0
- package/lib/providers/copilot-auth.mjs +297 -0
- package/lib/providers/credential-bootstrap.mjs +178 -0
- package/lib/providers/credential-catalog.mjs +46 -0
- package/lib/providers/credential-sources.mjs +63 -0
- package/lib/providers/creds.mjs +5 -2
- package/lib/providers/op-run.mjs +59 -0
- package/lib/providers/secret-resolver.mjs +159 -0
- package/lib/publish-template.mjs +163 -0
- package/lib/publish-tooling.mjs +119 -0
- package/lib/publish.mjs +305 -0
- package/lib/registry/cli.mjs +82 -0
- package/lib/registry/consolidation.mjs +147 -0
- package/lib/registry/generate-docs.mjs +75 -0
- package/lib/registry/skill-verification.mjs +57 -0
- package/lib/registry/surface-map.mjs +76 -0
- package/lib/registry/validate.mjs +135 -0
- package/lib/resources/budget.mjs +82 -0
- package/lib/resources/process-budget.mjs +45 -0
- package/lib/rules-delivery.mjs +9 -2
- package/lib/rules-read.mjs +26 -0
- package/lib/runtime-env.mjs +23 -0
- package/lib/runtime-pressure.mjs +51 -7
- package/lib/schema-infer.mjs +13 -2
- package/lib/server/chat-loop.mjs +622 -0
- package/lib/server/demo-preview.mjs +63 -0
- package/lib/server/index.mjs +259 -9
- package/lib/server/rate-limit.mjs +5 -3
- package/lib/server/webhook.mjs +3 -2
- package/lib/service-manager.mjs +60 -8
- package/lib/setup.mjs +70 -7
- package/lib/specialists/prompt-schema.mjs +19 -10
- package/lib/specialists/roster.mjs +43 -0
- package/lib/specialists/scaffold.mjs +56 -0
- package/lib/storage/backend.mjs +6 -6
- package/lib/storage/hybrid-query.mjs +7 -4
- package/lib/storage/state-source.mjs +5 -5
- package/lib/storage/sync.mjs +2 -3
- package/lib/telemetry/rule-calls.mjs +52 -0
- package/lib/template-registry.mjs +2 -2
- package/lib/templates/visual-requirements.mjs +27 -51
- package/lib/test-corpus-inventory.mjs +313 -0
- package/lib/uninstall/uninstall.mjs +19 -7
- package/lib/update.mjs +12 -0
- package/lib/upgrade.mjs +14 -0
- package/lib/wireframe.mjs +20 -14
- package/lib/worker/run.mjs +17 -4
- package/lib/worker/trace.mjs +11 -3
- package/package.json +28 -14
- package/personas/construct.md +2 -2
- package/platforms/claude/settings.template.json +36 -0
- package/rules/common/patterns.md +1 -1
- package/rules/common/release-gates.md +4 -3
- package/scripts/sync-specialists.mjs +41 -28
- package/skills/devops/data-engineering.md +1 -1
- package/skills/docs/adr-workflow.md +1 -0
- package/skills/docs/backlog-proposal-workflow.md +1 -0
- package/skills/docs/codebase-research-workflow.md +40 -0
- package/skills/docs/customer-profile-workflow.md +1 -0
- package/skills/docs/document-ingest-workflow.md +1 -0
- package/skills/docs/evidence-ingest-workflow.md +1 -0
- package/skills/docs/init-docs.md +2 -2
- package/skills/docs/init-project.md +7 -2
- package/skills/docs/prd-workflow.md +24 -1
- package/skills/docs/prfaq-workflow.md +1 -0
- package/skills/docs/product-intelligence-workflow.md +1 -0
- package/skills/docs/product-signal-workflow.md +1 -0
- package/skills/docs/research-workflow.md +54 -37
- package/skills/docs/runbook-workflow.md +1 -0
- package/skills/docs/strategy-workflow.md +1 -0
- package/skills/docs/user-research-workflow.md +40 -0
- package/skills/operating/orchestration-reference.md +1 -1
- package/skills/roles/architect.md +5 -0
- package/skills/roles/operator.docs.md +4 -0
- package/skills/routing.md +4 -2
- package/specialists/artifact-manifest.json +489 -0
- package/specialists/artifact-manifest.schema.json +83 -0
- package/specialists/audit-enrichments.json +454 -0
- package/specialists/contracts.json +37 -25
- package/specialists/prompts/_shared/validation-contract.md +26 -0
- package/specialists/prompts/cx-accessibility.md +21 -2
- package/specialists/prompts/cx-ai-engineer.md +24 -1
- package/specialists/prompts/cx-architect.md +4 -1
- package/specialists/prompts/cx-business-strategist.md +23 -2
- package/specialists/prompts/cx-data-analyst.md +22 -1
- package/specialists/prompts/cx-data-engineer.md +23 -2
- package/specialists/prompts/cx-debugger.md +21 -2
- package/specialists/prompts/cx-designer.md +26 -3
- package/specialists/prompts/cx-devil-advocate.md +19 -2
- package/specialists/prompts/cx-docs-keeper.md +28 -1
- package/specialists/prompts/cx-engineer.md +22 -1
- package/specialists/prompts/cx-evaluator.md +18 -1
- package/specialists/prompts/cx-explorer.md +21 -2
- package/specialists/prompts/cx-legal-compliance.md +21 -2
- package/specialists/prompts/cx-operations.md +21 -2
- package/specialists/prompts/cx-oracle.md +94 -0
- package/specialists/prompts/cx-orchestrator.md +20 -1
- package/specialists/prompts/cx-platform-engineer.md +25 -2
- package/specialists/prompts/cx-product-manager.md +32 -1
- package/specialists/prompts/cx-qa.md +24 -2
- package/specialists/prompts/cx-rd-lead.md +23 -2
- package/specialists/prompts/cx-release-manager.md +23 -2
- package/specialists/prompts/cx-researcher.md +22 -2
- package/specialists/prompts/cx-reviewer.md +18 -1
- package/specialists/prompts/cx-security.md +22 -2
- package/specialists/prompts/cx-sre.md +30 -3
- package/specialists/prompts/cx-test-automation.md +7 -1
- package/specialists/prompts/cx-trace-reviewer.md +22 -3
- package/specialists/prompts/cx-ux-researcher.md +21 -2
- package/specialists/registry.json +52 -173
- package/specialists/tone-profiles.json +42 -0
- package/templates/demos/playwright/demo-recording.config.mjs +47 -0
- package/templates/demos/recordings/agentic-platforms-prd.json +19 -0
- package/templates/demos/scripts/agentic-platforms-prd.json +44 -0
- package/templates/demos/specs/_helpers/scroll-artifact.ts +89 -0
- package/templates/demos/tapes/agentic-platforms-prd.tape +49 -0
- package/templates/demos/tapes/resource-guard-rails.tape +49 -0
- package/templates/demos/vhs/construct-cockpit.json +24 -0
- package/templates/distribution/construct-brand.typ +446 -0
- package/templates/distribution/construct-decision.typ +38 -0
- package/templates/distribution/construct-deck.html +95 -0
- package/templates/distribution/construct-pdf.typ +38 -0
- package/templates/distribution/construct-prd.typ +38 -0
- package/templates/distribution/construct-reference.docx +0 -0
- package/templates/distribution/construct-research.typ +38 -0
- package/templates/distribution/construct-web.html +92 -0
- package/templates/distribution/fonts/Geist-Bold.ttf +0 -0
- package/templates/distribution/fonts/Geist-Medium.ttf +0 -0
- package/templates/distribution/fonts/Geist-Regular.ttf +0 -0
- package/templates/distribution/fonts/Geist-SemiBold.ttf +0 -0
- package/templates/distribution/fonts/GeistMono-Medium.ttf +0 -0
- package/templates/distribution/fonts/GeistMono-Regular.ttf +0 -0
- package/templates/distribution/fonts/GeistMono-SemiBold.ttf +0 -0
- package/templates/distribution/fonts/IBMPlexMono-Regular.otf +0 -0
- package/templates/distribution/fonts/JetBrainsMono-Medium.ttf +0 -0
- package/templates/distribution/fonts/JetBrainsMono-Regular.ttf +0 -0
- package/templates/distribution/fonts/JetBrainsMono-SemiBold.ttf +0 -0
- package/templates/distribution/fonts/PlusJakartaSans-Bold.ttf +0 -0
- package/templates/distribution/fonts/PlusJakartaSans-Medium.ttf +0 -0
- package/templates/distribution/fonts/PlusJakartaSans-Regular.ttf +0 -0
- package/templates/distribution/fonts/PlusJakartaSans-SemiBold.ttf +0 -0
- package/templates/distribution/fonts/README.md +36 -0
- package/templates/distribution/fonts/SpaceGrotesk-Variable.ttf +0 -0
- package/templates/distribution/fonts/handwritten/Caveat.ttf +0 -0
- package/templates/distribution/fonts/legacy/IBMPlexMono-Regular.otf +0 -0
- package/templates/distribution/fonts/legacy/Inter-Medium.otf +0 -0
- package/templates/distribution/fonts/legacy/Inter-Regular.otf +0 -0
- package/templates/distribution/fonts/legacy/Inter-SemiBold.otf +0 -0
- package/templates/distribution/fonts/legacy/InterDisplay-SemiBold.otf +0 -0
- package/templates/distribution/fonts/legacy/SourceSerif4-Regular.otf +0 -0
- package/templates/distribution/fonts/legacy/SourceSerif4-Semibold.otf +0 -0
- package/templates/distribution/icons/activity.svg +15 -0
- package/templates/distribution/icons/alert-triangle.svg +17 -0
- package/templates/distribution/icons/book-open.svg +16 -0
- package/templates/distribution/icons/bot.svg +20 -0
- package/templates/distribution/icons/brain.svg +22 -0
- package/templates/distribution/icons/circle-check.svg +16 -0
- package/templates/distribution/icons/clipboard-check.svg +17 -0
- package/templates/distribution/icons/cpu.svg +28 -0
- package/templates/distribution/icons/database.svg +17 -0
- package/templates/distribution/icons/eye.svg +16 -0
- package/templates/distribution/icons/file-text.svg +19 -0
- package/templates/distribution/icons/gauge.svg +16 -0
- package/templates/distribution/icons/git-branch.svg +17 -0
- package/templates/distribution/icons/key.svg +17 -0
- package/templates/distribution/icons/layers.svg +17 -0
- package/templates/distribution/icons/list-checks.svg +19 -0
- package/templates/distribution/icons/lock.svg +16 -0
- package/templates/distribution/icons/message-square.svg +15 -0
- package/templates/distribution/icons/network.svg +19 -0
- package/templates/distribution/icons/route.svg +17 -0
- package/templates/distribution/icons/scale.svg +19 -0
- package/templates/distribution/icons/search.svg +16 -0
- package/templates/distribution/icons/send.svg +16 -0
- package/templates/distribution/icons/server.svg +18 -0
- package/templates/distribution/icons/shield-check.svg +16 -0
- package/templates/distribution/icons/users.svg +18 -0
- package/templates/distribution/icons/webhook.svg +17 -0
- package/templates/distribution/icons/workflow.svg +17 -0
- package/templates/distribution/icons/wrench.svg +15 -0
- package/templates/distribution/run.mjs +18 -1
- package/templates/docs/adr.md +7 -0
- package/templates/docs/construct_guide.md +8 -8
- package/templates/docs/customer-profile.md +4 -0
- package/templates/docs/one-pager.md +4 -0
- package/templates/docs/postmortem.md +31 -0
- package/templates/docs/prd-platform.md +19 -0
- package/templates/docs/prd.md +15 -0
- package/templates/docs/prfaq.md +7 -0
- package/templates/docs/qa-strategy.md +103 -0
- package/templates/docs/research-brief.md +8 -0
- package/templates/docs/rfc-platform.md +12 -0
- package/templates/docs/test-plan.md +7 -0
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* lib/hooks/session-optimize.mjs — Session end optimization hook — triggers agent optimization for low-performers.
|
|
4
4
|
*
|
|
5
5
|
* Runs as a Stop hook at session end. Checks recent performance reviews for agents with avgScore < 0.7
|
|
6
|
-
* and
|
|
6
|
+
* and schedules `construct optimize <agent> --dry-run` detached so the Stop hook returns within budget.
|
|
7
7
|
*
|
|
8
8
|
* @p95ms 300
|
|
9
9
|
* @maxBlockingScope Stop (non-blocking, async)
|
|
@@ -12,110 +12,102 @@
|
|
|
12
12
|
* @matcher *
|
|
13
13
|
* @exits 0 = pass
|
|
14
14
|
*/
|
|
15
|
-
import { readFileSync, existsSync, writeFileSync, mkdirSync } from 'fs';
|
|
15
|
+
import { readFileSync, existsSync, writeFileSync, mkdirSync, readdirSync, statSync } from 'fs';
|
|
16
16
|
import { join, dirname } from 'path';
|
|
17
17
|
import { homedir } from 'os';
|
|
18
|
-
import {
|
|
18
|
+
import { spawn } from 'child_process';
|
|
19
19
|
import { fileURLToPath } from 'url';
|
|
20
20
|
|
|
21
21
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
22
22
|
const CONSTRUCT_BIN = join(__dirname, '..', '..', 'bin', 'construct');
|
|
23
23
|
|
|
24
|
-
const OPTIMIZATION_THRESHOLD = 0.7;
|
|
25
|
-
const MIN_TRACES_PER_AGENT = 3;
|
|
26
|
-
const OPTIMIZATION_DAYS = 7;
|
|
24
|
+
const OPTIMIZATION_THRESHOLD = 0.7;
|
|
25
|
+
const MIN_TRACES_PER_AGENT = 3;
|
|
26
|
+
const OPTIMIZATION_DAYS = 7;
|
|
27
27
|
const OPTIMIZATION_LOG_DIR = join(homedir(), '.cx', 'optimization-logs');
|
|
28
|
+
const REVIEWS_DIR = join(homedir(), '.cx', 'performance-reviews');
|
|
28
29
|
|
|
29
|
-
// Ensure optimization log directory exists
|
|
30
30
|
mkdirSync(OPTIMIZATION_LOG_DIR, { recursive: true });
|
|
31
31
|
|
|
32
32
|
let input = {};
|
|
33
33
|
try { input = JSON.parse(readFileSync(0, 'utf8')); } catch { process.exit(0); }
|
|
34
34
|
|
|
35
|
-
// Only run for Construct sessions
|
|
36
35
|
const cwd = input?.cwd || process.cwd();
|
|
37
36
|
const projectName = cwd.split('/').pop() || 'project';
|
|
38
37
|
|
|
39
|
-
// Skip if this is not a Construct project or in global mode
|
|
40
38
|
if (projectName !== 'construct' && !existsSync(join(cwd, '.cx'))) {
|
|
41
39
|
process.exit(0);
|
|
42
40
|
}
|
|
43
41
|
|
|
44
|
-
// Reviews older than this trigger an auto-refresh on the next session-stop.
|
|
45
|
-
// Six hours keeps optimization signal current without re-running review on
|
|
46
|
-
// every short session. Operators can still run `construct review` manually.
|
|
47
42
|
const REVIEW_REFRESH_MS = 6 * 60 * 60 * 1000;
|
|
48
43
|
|
|
44
|
+
function spawnDetached(args, { cwd: workDir = cwd } = {}) {
|
|
45
|
+
try {
|
|
46
|
+
const child = spawn(process.execPath, args, {
|
|
47
|
+
cwd: workDir,
|
|
48
|
+
stdio: 'ignore',
|
|
49
|
+
detached: true,
|
|
50
|
+
env: process.env,
|
|
51
|
+
});
|
|
52
|
+
child.unref();
|
|
53
|
+
return true;
|
|
54
|
+
} catch {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function listReviewJsonFiles(dir) {
|
|
60
|
+
if (!existsSync(dir)) return [];
|
|
61
|
+
return readdirSync(dir)
|
|
62
|
+
.filter((name) => name.endsWith('.json'))
|
|
63
|
+
.map((name) => join(dir, name));
|
|
64
|
+
}
|
|
65
|
+
|
|
49
66
|
function maybeRefreshReviews() {
|
|
50
|
-
const reviewsDir = join(homedir(), '.cx', 'performance-reviews');
|
|
51
67
|
let needsRefresh = false;
|
|
52
68
|
try {
|
|
53
|
-
|
|
69
|
+
const files = listReviewJsonFiles(REVIEWS_DIR);
|
|
70
|
+
if (files.length === 0) {
|
|
54
71
|
needsRefresh = true;
|
|
55
72
|
} else {
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
const m = Number(spawnSync('stat', ['-f', '%m', f], { encoding: 'utf8' }).stdout.trim());
|
|
65
|
-
return Number.isFinite(m) ? Math.max(acc, m * 1000) : acc;
|
|
66
|
-
} catch { return acc; }
|
|
67
|
-
}, 0);
|
|
68
|
-
if (Date.now() - newestMs > REVIEW_REFRESH_MS) needsRefresh = true;
|
|
69
|
-
}
|
|
73
|
+
const newestMs = files.reduce((acc, filePath) => {
|
|
74
|
+
try {
|
|
75
|
+
return Math.max(acc, statSync(filePath).mtimeMs);
|
|
76
|
+
} catch {
|
|
77
|
+
return acc;
|
|
78
|
+
}
|
|
79
|
+
}, 0);
|
|
80
|
+
if (Date.now() - newestMs > REVIEW_REFRESH_MS) needsRefresh = true;
|
|
70
81
|
}
|
|
71
|
-
} catch {
|
|
82
|
+
} catch {
|
|
83
|
+
/* refresh is best-effort */
|
|
84
|
+
}
|
|
72
85
|
|
|
73
86
|
if (!needsRefresh) return;
|
|
74
87
|
|
|
75
|
-
|
|
76
|
-
try {
|
|
77
|
-
spawnSync(process.execPath, [CONSTRUCT_BIN, 'review', '--quiet'], {
|
|
78
|
-
stdio: 'ignore',
|
|
79
|
-
timeout: 60_000,
|
|
80
|
-
detached: true,
|
|
81
|
-
});
|
|
82
|
-
} catch { /* refresh is best-effort; manual `construct review` is always available */ }
|
|
88
|
+
spawnDetached([CONSTRUCT_BIN, 'review', '--quiet']);
|
|
83
89
|
}
|
|
84
90
|
|
|
85
|
-
// Read the most recent performance review
|
|
86
91
|
function getLatestPerformanceReview() {
|
|
87
|
-
const reviewsDir = join(homedir(), '.cx', 'performance-reviews');
|
|
88
|
-
if (!existsSync(reviewsDir)) return null;
|
|
89
|
-
|
|
90
92
|
try {
|
|
91
|
-
const files =
|
|
92
|
-
encoding: 'utf8',
|
|
93
|
-
timeout: 5000
|
|
94
|
-
}).stdout.trim().split('\n').filter(Boolean);
|
|
95
|
-
|
|
93
|
+
const files = listReviewJsonFiles(REVIEWS_DIR);
|
|
96
94
|
if (files.length === 0) return null;
|
|
97
|
-
|
|
98
|
-
// Sort by modification time, newest first
|
|
95
|
+
|
|
99
96
|
files.sort((a, b) => {
|
|
100
97
|
try {
|
|
101
|
-
|
|
102
|
-
const statB = spawnSync('stat', ['-f', '%m', b], { encoding: 'utf8' }).stdout.trim();
|
|
103
|
-
return parseInt(statB) - parseInt(statA);
|
|
98
|
+
return statSync(b).mtimeMs - statSync(a).mtimeMs;
|
|
104
99
|
} catch {
|
|
105
100
|
return 0;
|
|
106
101
|
}
|
|
107
102
|
});
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
const content = readFileSync(latestFile, 'utf8');
|
|
111
|
-
return JSON.parse(content);
|
|
103
|
+
|
|
104
|
+
return JSON.parse(readFileSync(files[0], 'utf8'));
|
|
112
105
|
} catch (error) {
|
|
113
106
|
console.error(`Error reading performance reviews: ${error.message}`);
|
|
114
107
|
return null;
|
|
115
108
|
}
|
|
116
109
|
}
|
|
117
110
|
|
|
118
|
-
// Check if agent needs optimization
|
|
119
111
|
function needsOptimization(agentStats) {
|
|
120
112
|
return (
|
|
121
113
|
agentStats.scoredInvocations >= MIN_TRACES_PER_AGENT &&
|
|
@@ -124,101 +116,25 @@ function needsOptimization(agentStats) {
|
|
|
124
116
|
);
|
|
125
117
|
}
|
|
126
118
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
const duration = Date.now() - startTime;
|
|
145
|
-
|
|
146
|
-
// Log the optimization attempt
|
|
147
|
-
const logEntry = {
|
|
148
|
-
timestamp: new Date().toISOString(),
|
|
149
|
-
agent: agentName,
|
|
150
|
-
durationMs: duration,
|
|
151
|
-
success: result.status === 0,
|
|
152
|
-
stdout: result.stdout,
|
|
153
|
-
stderr: result.stderr,
|
|
154
|
-
exitCode: result.status,
|
|
155
|
-
signal: result.signal
|
|
156
|
-
};
|
|
157
|
-
|
|
158
|
-
writeFileSync(logFile, JSON.stringify(logEntry, null, 2) + '\n');
|
|
159
|
-
|
|
160
|
-
if (result.status === 0) {
|
|
161
|
-
console.log(`✅ Optimization for ${agentName} completed in ${duration}ms`);
|
|
162
|
-
|
|
163
|
-
// Record observation about the optimization
|
|
164
|
-
recordOptimizationObservation(agentName, true, duration);
|
|
165
|
-
} else {
|
|
166
|
-
console.log(`❌ Optimization for ${agentName} failed with code ${result.status}`);
|
|
167
|
-
console.log(`Stderr: ${result.stderr.slice(0, 500)}...`);
|
|
168
|
-
|
|
169
|
-
// Record observation about the failure
|
|
170
|
-
recordOptimizationObservation(agentName, false, duration, result.stderr);
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
return result.status === 0;
|
|
174
|
-
} catch (error) {
|
|
175
|
-
console.error(`❌ Optimization for ${agentName} threw error: ${error.message}`);
|
|
176
|
-
|
|
177
|
-
const logEntry = {
|
|
178
|
-
timestamp: new Date().toISOString(),
|
|
179
|
-
agent: agentName,
|
|
180
|
-
durationMs: Date.now() - startTime,
|
|
181
|
-
success: false,
|
|
182
|
-
error: error.message,
|
|
183
|
-
stack: error.stack
|
|
184
|
-
};
|
|
185
|
-
|
|
186
|
-
writeFileSync(logFile, JSON.stringify(logEntry, null, 2) + '\n');
|
|
187
|
-
return false;
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
// Record optimization observation for learning
|
|
192
|
-
function recordOptimizationObservation(agentName, success, durationMs, error = null) {
|
|
193
|
-
const observation = {
|
|
194
|
-
role: 'construct',
|
|
195
|
-
category: success ? 'pattern' : 'anti-pattern',
|
|
196
|
-
summary: success
|
|
197
|
-
? `Auto-optimized ${agentName} in ${durationMs}ms`
|
|
198
|
-
: `Auto-optimization failed for ${agentName}: ${error?.slice(0, 200) || 'unknown error'}`,
|
|
199
|
-
content: success
|
|
200
|
-
? `Automatic optimization triggered for ${agentName} after performance review showed avgScore < ${OPTIMIZATION_THRESHOLD}. Optimization completed successfully in ${durationMs}ms.`
|
|
201
|
-
: `Automatic optimization failed for ${agentName}. Error: ${error || 'Unknown'}. System should notify maintainer.`,
|
|
202
|
-
tags: ['auto-optimization', 'performance-review', agentName],
|
|
203
|
-
confidence: 0.9
|
|
204
|
-
};
|
|
205
|
-
|
|
206
|
-
try {
|
|
207
|
-
// Use existing observation recording if available
|
|
208
|
-
const observationFile = join(homedir(), '.cx', 'observations', `${Date.now()}-${agentName}-optimization.json`);
|
|
209
|
-
mkdirSync(dirname(observationFile), { recursive: true });
|
|
210
|
-
writeFileSync(observationFile, JSON.stringify(observation, null, 2) + '\n');
|
|
211
|
-
} catch (error) {
|
|
212
|
-
// Non-critical - observation recording failed
|
|
213
|
-
console.error(`Failed to record optimization observation: ${error.message}`);
|
|
214
|
-
}
|
|
119
|
+
function scheduleAgentOptimization(agentName) {
|
|
120
|
+
const logFile = join(OPTIMIZATION_LOG_DIR, `${new Date().toISOString().split('T')[0]}-${agentName}-scheduled.json`);
|
|
121
|
+
const scheduled = spawnDetached([
|
|
122
|
+
CONSTRUCT_BIN,
|
|
123
|
+
'optimize',
|
|
124
|
+
agentName,
|
|
125
|
+
`--days=${OPTIMIZATION_DAYS}`,
|
|
126
|
+
'--dry-run',
|
|
127
|
+
]);
|
|
128
|
+
writeFileSync(logFile, `${JSON.stringify({
|
|
129
|
+
timestamp: new Date().toISOString(),
|
|
130
|
+
agent: agentName,
|
|
131
|
+
scheduled,
|
|
132
|
+
mode: 'detached-dry-run',
|
|
133
|
+
})}\n`);
|
|
134
|
+
return scheduled;
|
|
215
135
|
}
|
|
216
136
|
|
|
217
|
-
// Main optimization logic
|
|
218
137
|
function main() {
|
|
219
|
-
// If the most recent review is missing or stale (>6h), kick off a refresh
|
|
220
|
-
// detached so the Stop hook returns promptly. The refresh runs on the next
|
|
221
|
-
// session's Stop if it's still in flight; nothing here blocks user work.
|
|
222
138
|
maybeRefreshReviews();
|
|
223
139
|
|
|
224
140
|
const review = getLatestPerformanceReview();
|
|
@@ -226,92 +142,53 @@ function main() {
|
|
|
226
142
|
console.log('📊 No performance review data found. Run `construct review` first.');
|
|
227
143
|
process.exit(0);
|
|
228
144
|
}
|
|
229
|
-
|
|
230
|
-
console.log(`📊 Checking ${review.agentStats.length} agents from performance review ${review.generated}`);
|
|
231
|
-
|
|
145
|
+
|
|
232
146
|
const agentsNeedingOptimization = review.agentStats.filter(needsOptimization);
|
|
233
|
-
|
|
147
|
+
|
|
234
148
|
if (agentsNeedingOptimization.length === 0) {
|
|
235
149
|
console.log(`✅ All ${review.agentStats.length} agents have scores >= ${OPTIMIZATION_THRESHOLD} or insufficient data`);
|
|
236
150
|
process.exit(0);
|
|
237
151
|
}
|
|
238
|
-
|
|
239
|
-
console.log(`🔧
|
|
240
|
-
|
|
241
|
-
console.log(` • ${agent.name}: avgScore=${agent.avgScore}, invocations=${agent.invocations}`);
|
|
242
|
-
});
|
|
243
|
-
|
|
244
|
-
// Sequential optimization keeps prompt-tuning deterministic and avoids
|
|
245
|
-
// colliding writes to per-agent prompt files.
|
|
246
|
-
let optimizedCount = 0;
|
|
247
|
-
let failedCount = 0;
|
|
248
|
-
|
|
152
|
+
|
|
153
|
+
console.log(`🔧 Scheduling ${agentsNeedingOptimization.length} detached optimization preview(s):`);
|
|
154
|
+
let scheduledCount = 0;
|
|
249
155
|
for (const agent of agentsNeedingOptimization) {
|
|
250
|
-
|
|
251
|
-
if (
|
|
252
|
-
optimizedCount++;
|
|
253
|
-
} else {
|
|
254
|
-
failedCount++;
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
// Small delay between optimizations to avoid rate limits
|
|
258
|
-
if (agentsNeedingOptimization.length > 1) {
|
|
259
|
-
spawnSync('sleep', ['10'], { encoding: 'utf8' });
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
// Summary
|
|
264
|
-
console.log(`\n📈 Optimization Summary:`);
|
|
265
|
-
console.log(` • Agents checked: ${review.agentStats.length}`);
|
|
266
|
-
console.log(` • Needing optimization: ${agentsNeedingOptimization.length}`);
|
|
267
|
-
console.log(` • Successfully optimized: ${optimizedCount}`);
|
|
268
|
-
console.log(` • Failed: ${failedCount}`);
|
|
269
|
-
|
|
270
|
-
if (optimizedCount > 0) {
|
|
271
|
-
console.log(`\n💡 Optimized agents will be evaluated in the next performance review.`);
|
|
272
|
-
console.log(` Run \`construct review\` after next session to see improvements.`);
|
|
156
|
+
console.log(` • ${agent.name}: avgScore=${agent.avgScore}, invocations=${agent.invocations}`);
|
|
157
|
+
if (scheduleAgentOptimization(agent.name)) scheduledCount += 1;
|
|
273
158
|
}
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
159
|
+
|
|
160
|
+
const summaryFile = join(OPTIMIZATION_LOG_DIR, `${new Date().toISOString().split('T')[0]}-summary.json`);
|
|
161
|
+
writeFileSync(summaryFile, `${JSON.stringify({
|
|
277
162
|
timestamp: new Date().toISOString(),
|
|
278
163
|
project: projectName,
|
|
279
164
|
reviewDate: review.generated,
|
|
280
165
|
agentsChecked: review.agentStats.length,
|
|
281
166
|
agentsNeedingOptimization: agentsNeedingOptimization.length,
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
agents: agentsNeedingOptimization.map(a => ({
|
|
167
|
+
scheduledCount,
|
|
168
|
+
agents: agentsNeedingOptimization.map((a) => ({
|
|
285
169
|
name: a.name,
|
|
286
170
|
avgScore: a.avgScore,
|
|
287
171
|
invocations: a.invocations,
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
const summaryFile = join(OPTIMIZATION_LOG_DIR, `${new Date().toISOString().split('T')[0]}-summary.json`);
|
|
293
|
-
writeFileSync(summaryFile, JSON.stringify(summary, null, 2) + '\n');
|
|
172
|
+
})),
|
|
173
|
+
}, null, 2)}\n`);
|
|
174
|
+
|
|
175
|
+
console.log(`\n📈 Optimization previews scheduled: ${scheduledCount}/${agentsNeedingOptimization.length}`);
|
|
294
176
|
}
|
|
295
177
|
|
|
296
|
-
// Run main logic
|
|
297
178
|
try {
|
|
298
179
|
main();
|
|
299
180
|
} catch (error) {
|
|
300
181
|
console.error(`❌ Session optimization hook crashed: ${error.message}`);
|
|
301
|
-
console.error(error.stack);
|
|
302
|
-
|
|
303
|
-
// Log the crash
|
|
304
182
|
const crashLog = join(OPTIMIZATION_LOG_DIR, 'crashes', `${Date.now()}-crash.json`);
|
|
305
183
|
mkdirSync(dirname(crashLog), { recursive: true });
|
|
306
|
-
writeFileSync(crashLog, JSON.stringify({
|
|
184
|
+
writeFileSync(crashLog, `${JSON.stringify({
|
|
307
185
|
timestamp: new Date().toISOString(),
|
|
308
186
|
error: error.message,
|
|
309
187
|
stack: error.stack,
|
|
310
188
|
cwd,
|
|
311
|
-
projectName
|
|
312
|
-
}, null, 2)
|
|
313
|
-
|
|
189
|
+
projectName,
|
|
190
|
+
}, null, 2)}\n`);
|
|
314
191
|
process.exit(1);
|
|
315
192
|
}
|
|
316
193
|
|
|
317
|
-
process.exit(0);
|
|
194
|
+
process.exit(0);
|
|
@@ -265,28 +265,31 @@ const selfKnowledgeNote = '\nSelf-knowledge: call `knowledge_search` for any Con
|
|
|
265
265
|
let sourcesNote = '';
|
|
266
266
|
let embedStatusNote = '';
|
|
267
267
|
try {
|
|
268
|
-
const
|
|
269
|
-
const
|
|
270
|
-
const
|
|
271
|
-
const
|
|
268
|
+
const { loadProjectConfig } = await import('../config/project-config.mjs');
|
|
269
|
+
const { resolveEffectiveSourceTargetsFromConfig } = await import('../config/source-targets.mjs');
|
|
270
|
+
const { config } = loadProjectConfig(process.cwd(), process.env);
|
|
271
|
+
const targets = resolveEffectiveSourceTargetsFromConfig(config, process.env);
|
|
272
|
+
const repos = targets.filter((t) => t.provider === 'github').map((t) => t.selector.repo);
|
|
273
|
+
const jiraTargets = targets.filter((t) => t.provider === 'jira');
|
|
274
|
+
const linearTargets = targets.filter((t) => t.provider === 'linear');
|
|
275
|
+
const slackTargets = targets.filter((t) => t.provider === 'slack');
|
|
276
|
+
const hasSources = targets.length > 0;
|
|
272
277
|
if (hasSources) {
|
|
273
278
|
const parts = [];
|
|
274
279
|
if (repos.length > 0) parts.push(`GitHub repos: ${repos.join(', ')}`);
|
|
275
|
-
if (
|
|
276
|
-
if (
|
|
277
|
-
|
|
278
|
-
// tool for questions about them. The "fetch first, don't answer from
|
|
279
|
-
// memory" policy lives in the persona.
|
|
280
|
+
if (jiraTargets.length > 0) parts.push(`Jira projects: ${jiraTargets.map((t) => t.selector.project).join(', ')}`);
|
|
281
|
+
if (linearTargets.length > 0) parts.push(`Linear teams: ${linearTargets.map((t) => t.selector.team).join(', ')}`);
|
|
282
|
+
if (slackTargets.length > 0) parts.push(`Slack channels: ${slackTargets.map((t) => t.selector.channel).join(', ')}`);
|
|
280
283
|
sourcesNote = '\nProvider sources wired: ' + parts.join(' · ') +
|
|
281
284
|
'. Use `provider_fetch` for any question about them.\n';
|
|
282
285
|
}
|
|
286
|
+
} catch { /* best effort */ }
|
|
283
287
|
|
|
284
|
-
|
|
288
|
+
try {
|
|
285
289
|
const { resolveEmbedStatus, autoStartEmbedIfNeeded } = await import('../embed/cli.mjs');
|
|
286
290
|
const embedStatus = resolveEmbedStatus(process.env);
|
|
287
291
|
if (embedStatus.level !== 'none') {
|
|
288
292
|
embedStatusNote = `\n## Embed daemon\n${embedStatus.label} · ${embedStatus.detail}\n`;
|
|
289
|
-
// Auto-start if CX_AUTO_EMBED=1 and daemon is stopped
|
|
290
293
|
if (embedStatus.level === 'stopped' && process.env.CX_AUTO_EMBED === '1') {
|
|
291
294
|
const result = await autoStartEmbedIfNeeded(process.env);
|
|
292
295
|
if (result.started) {
|
|
@@ -319,11 +322,10 @@ try {
|
|
|
319
322
|
// plugin so both platforms surface the same prelude on session start.
|
|
320
323
|
|
|
321
324
|
let intakeReviewNote = '';
|
|
322
|
-
let brokerStatusNote = '';
|
|
323
325
|
try {
|
|
324
|
-
const {
|
|
325
|
-
|
|
326
|
-
|
|
326
|
+
const { buildSessionPrelude } = await import('../intake/session-prelude.mjs');
|
|
327
|
+
const prelude = buildSessionPrelude({ cwd, env: process.env });
|
|
328
|
+
intakeReviewNote = prelude ? `\n${prelude}\n` : '';
|
|
327
329
|
} catch { /* best effort */ }
|
|
328
330
|
|
|
329
331
|
// Missing-env-vars notice. Reads .env.example from the project root,
|
|
@@ -383,7 +385,7 @@ try {
|
|
|
383
385
|
// that pollutes the caller's output contract, so the resolved output mode routes
|
|
384
386
|
// the payload to stdout (interactive), stderr, or a debug log (silent).
|
|
385
387
|
|
|
386
|
-
const payload = `${header}\n${body}${stateNote}${efficiencyNote}${observationsNote}${concurrencyNote}${skillScopeNote}${dropNote}${embedStatusNote}${sourcesNote}${selfKnowledgeNote}${rolesNote}${intakeReviewNote}${
|
|
388
|
+
const payload = `${header}\n${body}${stateNote}${efficiencyNote}${observationsNote}${concurrencyNote}${skillScopeNote}${dropNote}${embedStatusNote}${sourcesNote}${selfKnowledgeNote}${rolesNote}${intakeReviewNote}${permissionPostureNote}${envCheckNote}\n${footer}${pendingNote}\n`;
|
|
387
389
|
try {
|
|
388
390
|
const { mode } = resolveHookOutputMode({ cwd, env: process.env });
|
|
389
391
|
writeHookContext({ payload, mode });
|
package/lib/host-disposition.mjs
CHANGED
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
* lib/host-disposition.mjs — single source of truth for the disposition of
|
|
3
3
|
* every artifact Construct creates in a host project (ADR-0027 §1).
|
|
4
4
|
*
|
|
5
|
+
* Scope model (see docs/concepts/project-scopes.md):
|
|
6
|
+
* templates/, docs/ — package/repo durable sources (committed)
|
|
7
|
+
* .construct/ — per-project launcher (ignored)
|
|
8
|
+
* .cx/ — per-project runtime: intake, oracle, observations, demos (ignored)
|
|
9
|
+
* ~/.construct/ — user credentials and doctor state
|
|
10
|
+
* ~/.cx/ — user-scope telemetry fallback
|
|
11
|
+
*
|
|
5
12
|
* `IGNORED_PATTERNS` enumerates machine-specific, sync-regenerated artifacts
|
|
6
13
|
* that must never be committed; the init `.gitignore` writer and the
|
|
7
14
|
* `gitignore-coverage` reconciliation both read from here, and uninstall /
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/improvement/cli.mjs — CLI handlers for `construct improvement <subcommand>`.
|
|
3
|
+
*
|
|
4
|
+
* Subcommands: submit, review, pending, show, approve, apply, rollback, list.
|
|
5
|
+
* Each command reads or writes durable state under `.cx/improvement/` and routes
|
|
6
|
+
* proposals through the governed controller; apply and rollback record human acts
|
|
7
|
+
* but do not mutate live artifacts.
|
|
8
|
+
*/
|
|
9
|
+
import fs from 'node:fs';
|
|
10
|
+
|
|
11
|
+
import {
|
|
12
|
+
applyRecord,
|
|
13
|
+
approveRecord,
|
|
14
|
+
formatReviewSummary,
|
|
15
|
+
listPending,
|
|
16
|
+
resolveKnownApprovers,
|
|
17
|
+
reviewRecord,
|
|
18
|
+
rollbackRecord,
|
|
19
|
+
submitBundle,
|
|
20
|
+
} from './surface.mjs';
|
|
21
|
+
import { listRecords, loadRecord } from './store.mjs';
|
|
22
|
+
|
|
23
|
+
function readJsonArg(args, prefix) {
|
|
24
|
+
const hit = args.find((a) => a.startsWith(prefix));
|
|
25
|
+
if (!hit) return null;
|
|
26
|
+
const file = hit.slice(prefix.length);
|
|
27
|
+
return JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function flagValue(args, prefix) {
|
|
31
|
+
const hit = args.find((a) => a.startsWith(prefix));
|
|
32
|
+
return hit ? hit.slice(prefix.length) : null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function usage() {
|
|
36
|
+
process.stderr.write(
|
|
37
|
+
'Usage: construct improvement <submit|review|pending|show|approve|apply|rollback|list> [--json]\n'
|
|
38
|
+
+ ' submit --bundle=FILE | (--trace=FILE --dataset=FILE --report=FILE [--trigger=KIND])\n'
|
|
39
|
+
+ ' review <id>\n'
|
|
40
|
+
+ ' pending\n'
|
|
41
|
+
+ ' show <id>\n'
|
|
42
|
+
+ ' approve <id> [--identity=USER]\n'
|
|
43
|
+
+ ' apply <id> [--monitor=ID]\n'
|
|
44
|
+
+ ' rollback <id> [--reason=TEXT]\n'
|
|
45
|
+
+ ' list [--state=STATE]\n',
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export async function runImprovementCli(args, { projectDir = process.cwd() } = {}) {
|
|
50
|
+
const json = args.includes('--json');
|
|
51
|
+
const sub = args.find((a) => !a.startsWith('-'));
|
|
52
|
+
const positional = args.filter((a) => !a.startsWith('-'));
|
|
53
|
+
|
|
54
|
+
if (!sub || sub === '--help' || sub === '-h') {
|
|
55
|
+
usage();
|
|
56
|
+
return sub === '--help' || sub === '-h' ? 0 : 1;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const write = (payload) => {
|
|
60
|
+
process.stdout.write(JSON.stringify(payload, null, 2) + '\n');
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
switch (sub) {
|
|
64
|
+
case 'submit': {
|
|
65
|
+
const bundle = readJsonArg(args, '--bundle=');
|
|
66
|
+
const trace = bundle?.trace ?? readJsonArg(args, '--trace=');
|
|
67
|
+
const dataset = bundle?.dataset ?? readJsonArg(args, '--dataset=');
|
|
68
|
+
const evaluationReport = bundle?.evaluationReport ?? readJsonArg(args, '--report=');
|
|
69
|
+
const triggerKind = flagValue(args, '--trigger=') || bundle?.trigger?.kind || 'operator-request';
|
|
70
|
+
const trigger = bundle?.trigger || { kind: triggerKind, optIn: true };
|
|
71
|
+
const result = submitBundle({
|
|
72
|
+
projectDir,
|
|
73
|
+
bundle: bundle || {
|
|
74
|
+
trace,
|
|
75
|
+
trigger,
|
|
76
|
+
dataset,
|
|
77
|
+
evaluationReport,
|
|
78
|
+
approver: flagValue(args, '--approver=') || resolveKnownApprovers(projectDir)[0] || null,
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
if (json) write(result);
|
|
82
|
+
else if (!result.ok) {
|
|
83
|
+
process.stderr.write(`submit refused: ${result.stage || result.error}\n`);
|
|
84
|
+
if (result.governance?.refusals?.length) {
|
|
85
|
+
process.stderr.write(` refusals: ${result.governance.refusals.join(', ')}\n`);
|
|
86
|
+
}
|
|
87
|
+
return 1;
|
|
88
|
+
} else {
|
|
89
|
+
process.stdout.write(`submitted ${result.record.id} → ${result.record.proposal.state}\n`);
|
|
90
|
+
}
|
|
91
|
+
return result.ok ? 0 : 1;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
case 'review': {
|
|
95
|
+
const id = positional[1];
|
|
96
|
+
if (!id) { usage(); return 1; }
|
|
97
|
+
const result = reviewRecord(projectDir, id);
|
|
98
|
+
if (!result.ok) {
|
|
99
|
+
process.stderr.write(`review failed: ${result.error}\n`);
|
|
100
|
+
return 1;
|
|
101
|
+
}
|
|
102
|
+
if (json) write(result);
|
|
103
|
+
else process.stdout.write(formatReviewSummary(result.record, result.governance) + '\n');
|
|
104
|
+
return 0;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
case 'pending': {
|
|
108
|
+
const pending = listPending(projectDir);
|
|
109
|
+
if (json) write({ pending });
|
|
110
|
+
else {
|
|
111
|
+
if (!pending.length) process.stdout.write('No proposals awaiting approval.\n');
|
|
112
|
+
for (const row of pending) {
|
|
113
|
+
process.stdout.write(`${row.id} · ${row.proposal?.type} · ${row.proposal?.state}\n`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return 0;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
case 'show': {
|
|
120
|
+
const id = positional[1];
|
|
121
|
+
if (!id) { usage(); return 1; }
|
|
122
|
+
const record = loadRecord(projectDir, id);
|
|
123
|
+
if (!record) {
|
|
124
|
+
process.stderr.write(`not found: ${id}\n`);
|
|
125
|
+
return 1;
|
|
126
|
+
}
|
|
127
|
+
if (json) write(record);
|
|
128
|
+
else process.stdout.write(formatReviewSummary(record) + '\n');
|
|
129
|
+
return 0;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
case 'approve': {
|
|
133
|
+
const id = positional[1];
|
|
134
|
+
if (!id) { usage(); return 1; }
|
|
135
|
+
const result = approveRecord(projectDir, id, { identity: flagValue(args, '--identity=') });
|
|
136
|
+
if (!result.ok) {
|
|
137
|
+
process.stderr.write(`approve failed: ${result.error}\n`);
|
|
138
|
+
return 1;
|
|
139
|
+
}
|
|
140
|
+
if (json) write(result);
|
|
141
|
+
else process.stdout.write(`approved ${id} → ${result.record.proposal.state}\n`);
|
|
142
|
+
return 0;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
case 'apply': {
|
|
146
|
+
const id = positional[1];
|
|
147
|
+
if (!id) { usage(); return 1; }
|
|
148
|
+
const result = applyRecord(projectDir, id, { monitor: flagValue(args, '--monitor=') });
|
|
149
|
+
if (!result.ok) {
|
|
150
|
+
process.stderr.write(`apply failed: ${result.error}\n`);
|
|
151
|
+
return 1;
|
|
152
|
+
}
|
|
153
|
+
if (json) write(result);
|
|
154
|
+
else process.stdout.write(`applied ${id} → ${result.record.proposal.state}\n`);
|
|
155
|
+
return 0;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
case 'rollback': {
|
|
159
|
+
const id = positional[1];
|
|
160
|
+
if (!id) { usage(); return 1; }
|
|
161
|
+
const result = rollbackRecord(projectDir, id, { reason: flagValue(args, '--reason=') || 'operator-rollback' });
|
|
162
|
+
if (!result.ok) {
|
|
163
|
+
process.stderr.write(`rollback failed: ${result.error}\n`);
|
|
164
|
+
return 1;
|
|
165
|
+
}
|
|
166
|
+
if (json) write(result);
|
|
167
|
+
else process.stdout.write(`rolled back ${id} → ${result.record.proposal.state}\n`);
|
|
168
|
+
return 0;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
case 'list': {
|
|
172
|
+
const state = flagValue(args, '--state=');
|
|
173
|
+
const rows = listRecords(projectDir, { state });
|
|
174
|
+
if (json) write({ proposals: rows });
|
|
175
|
+
else {
|
|
176
|
+
if (!rows.length) process.stdout.write('No improvement proposals.\n');
|
|
177
|
+
for (const row of rows) {
|
|
178
|
+
process.stdout.write(`${row.id} · ${row.proposal?.type} · ${row.proposal?.state}\n`);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return 0;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
default:
|
|
185
|
+
process.stderr.write(`Unknown subcommand: ${sub}\n`);
|
|
186
|
+
usage();
|
|
187
|
+
return 1;
|
|
188
|
+
}
|
|
189
|
+
}
|