@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
package/lib/document-ingest.mjs
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import { existsSync, mkdirSync, readdirSync, statSync, writeFileSync } from 'node:fs';
|
|
11
11
|
import { basename, dirname, extname, isAbsolute, join, parse, relative, resolve, sep } from 'node:path';
|
|
12
|
-
import {
|
|
12
|
+
import { extractDocumentTextAsync, extractDocumentTextNodeNative, extractDocumentMetadata, isExtractableDocumentPath } from './document-extract.mjs';
|
|
13
13
|
import { syncFileStateToSql } from './storage/sync.mjs';
|
|
14
14
|
import { stampFrontmatter } from './doc-stamp.mjs';
|
|
15
15
|
import { KNOWLEDGE_ROOT, KNOWLEDGE_SUBDIRS } from './knowledge/layout.mjs';
|
|
@@ -157,9 +157,9 @@ function resolveOutputPath(sourcePath, { cwd, outputPath, outputDir, target }) {
|
|
|
157
157
|
// can stall (a large PDF, a slow model download) or fail. Without a bound the
|
|
158
158
|
// caller hangs: the CLI never returns, and the ingest_document MCP tool blocks
|
|
159
159
|
// until the client times out and surfaces an opaque error. Bound the whole
|
|
160
|
-
// docling attempt and fall back to the
|
|
161
|
-
// a usable result, with the fallback recorded in droppedInfo. Override
|
|
162
|
-
// with CONSTRUCT_DOCLING_TIMEOUT_MS (0 disables the timeout).
|
|
160
|
+
// docling attempt and fall back to the node-native extractor so ingest always
|
|
161
|
+
// returns a usable result, with the fallback recorded in droppedInfo. Override
|
|
162
|
+
// the bound with CONSTRUCT_DOCLING_TIMEOUT_MS (0 disables the timeout).
|
|
163
163
|
|
|
164
164
|
const DOCLING_TIMEOUT_MS = (() => {
|
|
165
165
|
const raw = Number(process.env.CONSTRUCT_DOCLING_TIMEOUT_MS);
|
|
@@ -176,15 +176,15 @@ export function withTimeout(promise, ms, onTimeoutMessage) {
|
|
|
176
176
|
|
|
177
177
|
/**
|
|
178
178
|
* Run the high-fidelity (docling) extractor with a bound, falling back to the
|
|
179
|
-
*
|
|
179
|
+
* node-native extractor on timeout or any failure so a caller never hangs. The
|
|
180
180
|
* extractor functions are injectable for testing; production uses the real
|
|
181
|
-
* docling-async and
|
|
181
|
+
* docling-async and node-native extractors.
|
|
182
182
|
*/
|
|
183
183
|
export async function extractWithDoclingFallback(sourcePath, {
|
|
184
184
|
maxChars = null,
|
|
185
185
|
timeoutMs = DOCLING_TIMEOUT_MS,
|
|
186
186
|
asyncExtract = extractDocumentTextAsync,
|
|
187
|
-
|
|
187
|
+
nodeNativeExtract = extractDocumentTextNodeNative,
|
|
188
188
|
} = {}) {
|
|
189
189
|
try {
|
|
190
190
|
return await withTimeout(
|
|
@@ -193,24 +193,23 @@ export async function extractWithDoclingFallback(sourcePath, {
|
|
|
193
193
|
`docling extraction exceeded ${Math.round(timeoutMs / 1000)}s`,
|
|
194
194
|
);
|
|
195
195
|
} catch (err) {
|
|
196
|
-
const
|
|
197
|
-
|
|
198
|
-
...(
|
|
196
|
+
const fallback = await nodeNativeExtract(sourcePath, { maxChars });
|
|
197
|
+
fallback.droppedInfo = [
|
|
198
|
+
...(fallback.droppedInfo ?? []),
|
|
199
199
|
{
|
|
200
200
|
kind: 'docling-fallback',
|
|
201
201
|
count: 1,
|
|
202
|
-
reason: `High-fidelity (docling) extraction failed or timed out (${err.message}); used the
|
|
202
|
+
reason: `High-fidelity (docling) extraction failed or timed out (${err.message}); used the node-native extractor. Re-run after \`construct install --with-docling\`, or pass --fidelity=fast to skip docling.`,
|
|
203
203
|
recoverable: true,
|
|
204
204
|
},
|
|
205
205
|
];
|
|
206
|
-
return
|
|
206
|
+
return fallback;
|
|
207
207
|
}
|
|
208
208
|
}
|
|
209
209
|
|
|
210
210
|
// The default adapter path prefers the Node-native extractor (unpdf/mammoth) so
|
|
211
|
-
// plain PDF/DOCX ingest needs no Python venv;
|
|
212
|
-
//
|
|
213
|
-
// on docling, reserved for scanned/layout-critical and audio/video.
|
|
211
|
+
// plain PDF/DOCX ingest needs no Python venv; high-fidelity stays on docling for
|
|
212
|
+
// scanned/layout-critical content and formats without a Node backend.
|
|
214
213
|
|
|
215
214
|
function extractViaAdapter(sourcePath, { highFidelity, maxChars }) {
|
|
216
215
|
return highFidelity
|
|
@@ -354,6 +353,7 @@ export async function ingestDocuments(inputPaths, {
|
|
|
354
353
|
droppedInfo: results.flatMap((r) => r.droppedInfo.map((d) => ({ ...d, sourcePath: r.sourcePath }))),
|
|
355
354
|
droppedCount: totalDrops,
|
|
356
355
|
highFidelity,
|
|
356
|
+
fidelity: highFidelity ? 'high' : 'fast',
|
|
357
357
|
ingestion: {
|
|
358
358
|
strategy: resolvedStrategy.strategy,
|
|
359
359
|
fallback: resolvedStrategy.fallback,
|
|
@@ -373,7 +373,7 @@ export async function runIngestCli(argv = process.argv.slice(2), { cwd = process
|
|
|
373
373
|
let target = 'knowledge/internal';
|
|
374
374
|
let sync = false;
|
|
375
375
|
let strict = false;
|
|
376
|
-
let
|
|
376
|
+
let fidelity = 'high';
|
|
377
377
|
let strategy = null;
|
|
378
378
|
let orchestration = null;
|
|
379
379
|
|
|
@@ -384,21 +384,30 @@ export async function runIngestCli(argv = process.argv.slice(2), { cwd = process
|
|
|
384
384
|
else if (arg.startsWith('--strategy=')) strategy = arg.split('=').slice(1).join('=');
|
|
385
385
|
else if (arg === '--strategy') strategy = '';
|
|
386
386
|
else if (arg.startsWith('--orchestration=')) orchestration = arg.split('=').slice(1).join('=');
|
|
387
|
-
else if (arg
|
|
387
|
+
else if (arg.startsWith('--fidelity=')) {
|
|
388
|
+
const value = arg.split('=').slice(1).join('=');
|
|
389
|
+
if (value !== 'fast' && value !== 'high') {
|
|
390
|
+
throw new Error(`Unsupported fidelity: ${value}. Valid values: fast, high`);
|
|
391
|
+
}
|
|
392
|
+
fidelity = value;
|
|
393
|
+
} else if (arg === '--sync') sync = true;
|
|
388
394
|
else if (arg === '--strict') strict = true;
|
|
389
|
-
else if (arg === '--legacy-extractor')
|
|
395
|
+
else if (arg === '--legacy-extractor') fidelity = 'fast';
|
|
390
396
|
else if (strategy === '') strategy = arg;
|
|
391
397
|
else inputs.push(arg);
|
|
392
398
|
}
|
|
393
399
|
|
|
400
|
+
const highFidelity = fidelity === 'high';
|
|
401
|
+
|
|
394
402
|
if (inputs.length === 0) {
|
|
395
403
|
throw new Error(
|
|
396
404
|
`Usage: construct ingest <file-or-dir> [more paths] [--out=FILE] [--out-dir=DIR] ` +
|
|
397
|
-
`[--target=sibling|knowledge/<subdir>] [--strategy=adapter|provider] [--orchestration=prompt-only|orchestrated] [--sync] [--strict] [--
|
|
405
|
+
`[--target=sibling|knowledge/<subdir>] [--strategy=adapter|provider] [--orchestration=prompt-only|orchestrated] [--sync] [--strict] [--fidelity=fast|high]\n` +
|
|
398
406
|
` --strategy: extraction strategy override (adapter = local extractors; provider = configured provider/model)\n` +
|
|
399
407
|
` --orchestration: prompt-only (deterministic extraction) or orchestrated (engage the specialist chain)\n` +
|
|
400
408
|
` --strict: exit non-zero if any extraction drops occur\n` +
|
|
401
|
-
` --
|
|
409
|
+
` --fidelity=fast|high: fast = node-native extractors; high = docling (default)\n` +
|
|
410
|
+
` --legacy-extractor: deprecated alias for --fidelity=fast\n` +
|
|
402
411
|
` knowledge subdirs: ${KNOWLEDGE_SUBDIRS.join(', ')}`,
|
|
403
412
|
);
|
|
404
413
|
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/embed/auto-sources.mjs — resolve embed auto-discovery sources from project config.
|
|
3
|
+
*
|
|
4
|
+
* When embed.yaml is absent, builds provider source records from typed
|
|
5
|
+
* construct.config.json targets merged with legacy env lists. Falls back
|
|
6
|
+
* to broad provider defaults only when no targets are configured.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { loadProjectConfig } from '../config/project-config.mjs';
|
|
10
|
+
import {
|
|
11
|
+
resolveEffectiveSourceTargetsFromConfig,
|
|
12
|
+
targetsToEmbedSources,
|
|
13
|
+
legacyEnvSourceTargets,
|
|
14
|
+
} from '../config/source-targets.mjs';
|
|
15
|
+
|
|
16
|
+
export function resolveAutoEmbedSources({ cwd = process.cwd(), env = process.env, registry }) {
|
|
17
|
+
const { config } = loadProjectConfig(cwd, env);
|
|
18
|
+
const targets = resolveEffectiveSourceTargetsFromConfig(config, env);
|
|
19
|
+
|
|
20
|
+
if (targets.length === 0) {
|
|
21
|
+
return registry.autoSources(env);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const configuredProviders = new Set(targets.map((t) => t.provider));
|
|
25
|
+
const hasLegacyForUnconfigured = (provider) =>
|
|
26
|
+
legacyEnvSourceTargets(env).some((t) => t.provider === provider);
|
|
27
|
+
|
|
28
|
+
const sources = targetsToEmbedSources(targets);
|
|
29
|
+
|
|
30
|
+
for (const name of registry.names()) {
|
|
31
|
+
if (configuredProviders.has(name)) continue;
|
|
32
|
+
if (hasLegacyForUnconfigured(name)) continue;
|
|
33
|
+
const instance = registry.get(name);
|
|
34
|
+
if (typeof instance?.defaultSources !== 'function') continue;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const seen = new Set();
|
|
38
|
+
return sources.filter((s) => {
|
|
39
|
+
const key = `${s.provider}:${(s.refs ?? []).join(',')}:${JSON.stringify(s.repos ?? s.project ?? s.team ?? s.channels ?? '')}`;
|
|
40
|
+
if (seen.has(key)) return false;
|
|
41
|
+
seen.add(key);
|
|
42
|
+
return true;
|
|
43
|
+
});
|
|
44
|
+
}
|
package/lib/embed/cli.mjs
CHANGED
|
@@ -392,16 +392,17 @@ async function cmdEmbedSnapshot(args, { homeDir = os.homedir() } = {}) {
|
|
|
392
392
|
const { loadEmbedConfig, EMPTY_CONFIG } = await import('./config.mjs');
|
|
393
393
|
const { SnapshotEngine, renderMarkdown } = await import('./snapshot.mjs');
|
|
394
394
|
const { ProviderRegistry } = await import('./providers/registry.mjs');
|
|
395
|
-
const {
|
|
395
|
+
const { prepareConstructEnv } = await import('../runtime-env.mjs');
|
|
396
396
|
|
|
397
|
-
const env =
|
|
397
|
+
const env = prepareConstructEnv();
|
|
398
398
|
const registry = await ProviderRegistry.fromEnv(env);
|
|
399
399
|
|
|
400
400
|
let cfg;
|
|
401
401
|
if (fs.existsSync(configPath)) {
|
|
402
402
|
cfg = loadEmbedConfig(configPath);
|
|
403
403
|
} else {
|
|
404
|
-
|
|
404
|
+
const { resolveAutoEmbedSources } = await import('./auto-sources.mjs');
|
|
405
|
+
cfg = { ...EMPTY_CONFIG, sources: resolveAutoEmbedSources({ cwd: process.cwd(), env, registry }) };
|
|
405
406
|
}
|
|
406
407
|
|
|
407
408
|
if (!cfg.sources.length) {
|
package/lib/embed/daemon.mjs
CHANGED
|
@@ -58,54 +58,11 @@ function logCatch(source, err) {
|
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
/**
|
|
61
|
-
* Resolve an LLM API key
|
|
62
|
-
* Supports 1Password op:// refs in .zshrc or other shell config files for the
|
|
63
|
-
* telemetry-generation probe.
|
|
61
|
+
* Resolve an LLM API key via the shared secret resolver (cached op read per process).
|
|
64
62
|
*/
|
|
65
63
|
async function resolveLLMKey(varName, env) {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
const files = [join(homeDir, '.construct', 'config.env'), join(homeDir, '.env')];
|
|
69
|
-
for (const file of files) {
|
|
70
|
-
try {
|
|
71
|
-
if (!existsSync(file)) continue;
|
|
72
|
-
const content = readFileSync(file, 'utf8');
|
|
73
|
-
const m = content.match(new RegExp(`^${varName}=["']?(.+?)["']?$`, 'm'));
|
|
74
|
-
if (m && m[1]) return m[1].trim();
|
|
75
|
-
} catch { /* skip */ }
|
|
76
|
-
}
|
|
77
|
-
// Check shell rc files for exports and command substitutions (including op:// refs)
|
|
78
|
-
const rcs = [join(homeDir, '.zshrc'), join(homeDir, '.bashrc'), join(homeDir, '.bash_profile'), join(homeDir, '.profile')];
|
|
79
|
-
const { spawnSync } = await import('child_process');
|
|
80
|
-
for (const rc of rcs) {
|
|
81
|
-
if (!existsSync(rc)) continue;
|
|
82
|
-
try {
|
|
83
|
-
const content = readFileSync(rc, 'utf8');
|
|
84
|
-
|
|
85
|
-
// op:// ref via 1Password CLI
|
|
86
|
-
const opRe = new RegExp(`export\\s+${varName}=["']?\\$\\(op read '([^']+)'\\)["']?`, 'm');
|
|
87
|
-
const opMatch = content.match(opRe);
|
|
88
|
-
if (opMatch) {
|
|
89
|
-
try {
|
|
90
|
-
const r = spawnSync('op', ['read', opMatch[1]], { encoding: 'utf8', timeout: 5000 });
|
|
91
|
-
if (r.status === 0 && r.stdout?.trim()) return r.stdout.trim();
|
|
92
|
-
} catch { /* op read failed */ }
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// Source the rc file and echo — handles any $(...) command substitution
|
|
96
|
-
const anyExportRe = new RegExp(`^\\s*export\\s+${varName}=`, 'm');
|
|
97
|
-
if (anyExportRe.test(content)) {
|
|
98
|
-
try {
|
|
99
|
-
const r = spawnSync('zsh', ['-c', `source "${rc}" 2>/dev/null; echo "\${${varName}}"`], { encoding: 'utf8', timeout: 5000 });
|
|
100
|
-
if (r.status === 0 && r.stdout?.trim()) {
|
|
101
|
-
const val = r.stdout.trim().split('\n').pop()?.trim();
|
|
102
|
-
if (val && !val.includes('ERROR') && !val.includes('op read') && !val.startsWith('$(')) return val;
|
|
103
|
-
}
|
|
104
|
-
} catch { /* shell eval failed */ }
|
|
105
|
-
}
|
|
106
|
-
} catch { /* skip */ }
|
|
107
|
-
}
|
|
108
|
-
return null;
|
|
64
|
+
const { resolveSecret } = await import('../providers/secret-resolver.mjs');
|
|
65
|
+
return resolveSecret(varName, { env, cwd: process.cwd() });
|
|
109
66
|
}
|
|
110
67
|
|
|
111
68
|
// ─── Root dir resolution ─────────────────────────────────────────────────────
|
|
@@ -614,6 +571,21 @@ export class EmbedDaemon {
|
|
|
614
571
|
{ runImmediately: true },
|
|
615
572
|
);
|
|
616
573
|
|
|
574
|
+
// ── Job 7: rss-cap ────────────────────────────────────────────────────────
|
|
575
|
+
const { memoryCapMbFor, rssOverCap, currentRssMb } = await import('../resources/process-budget.mjs');
|
|
576
|
+
const rssCapMb = memoryCapMbFor('embed-daemon', this.#rootDir, this.#env);
|
|
577
|
+
this.#scheduler.register(
|
|
578
|
+
'rss-cap',
|
|
579
|
+
60_000,
|
|
580
|
+
async () => {
|
|
581
|
+
if (!rssOverCap(rssCapMb)) return;
|
|
582
|
+
process.stderr.write(
|
|
583
|
+
`[embed] RSS ${Math.round(currentRssMb())}MB exceeds cap ${rssCapMb}MB — stopping daemon\n`,
|
|
584
|
+
);
|
|
585
|
+
this.stop();
|
|
586
|
+
},
|
|
587
|
+
);
|
|
588
|
+
|
|
617
589
|
// ── Job 0: telemetry-setup ───────────────────────────────────────────────────
|
|
618
590
|
// On startup — ensure annotation queues and eval configs exist (idempotent)
|
|
619
591
|
this.#scheduler.register(
|
|
@@ -19,6 +19,11 @@ import { homedir } from 'node:os';
|
|
|
19
19
|
import { join } from 'node:path';
|
|
20
20
|
import { loadConstructEnv } from '../env-config.mjs';
|
|
21
21
|
import { addObservation } from '../observation-store.mjs';
|
|
22
|
+
import { loadProjectConfig } from '../config/project-config.mjs';
|
|
23
|
+
import {
|
|
24
|
+
resolveEffectiveSourceTargetsFromConfig,
|
|
25
|
+
resolveKnownSourcesFromTargets,
|
|
26
|
+
} from '../config/source-targets.mjs';
|
|
22
27
|
|
|
23
28
|
// ─── Self-query detection ────────────────────────────────────────────────────
|
|
24
29
|
|
|
@@ -62,7 +67,13 @@ function isSelfQuery(query) {
|
|
|
62
67
|
* @param {object} env
|
|
63
68
|
* @returns {{ id: string, provider: string, ref: string, display: string }[]}
|
|
64
69
|
*/
|
|
65
|
-
export function resolveKnownSources(env = process.env) {
|
|
70
|
+
export function resolveKnownSources(env = process.env, cwd = process.cwd()) {
|
|
71
|
+
const { config } = loadProjectConfig(cwd, env);
|
|
72
|
+
const targets = resolveEffectiveSourceTargetsFromConfig(config, env);
|
|
73
|
+
if (targets.length > 0) {
|
|
74
|
+
return resolveKnownSourcesFromTargets(targets);
|
|
75
|
+
}
|
|
76
|
+
|
|
66
77
|
const sources = [];
|
|
67
78
|
|
|
68
79
|
// GitHub repos
|
|
@@ -112,8 +123,8 @@ export function resolveKnownSources(env = process.env) {
|
|
|
112
123
|
* @param {object} env
|
|
113
124
|
* @returns {{ provider: string, ref: string, display: string } | null}
|
|
114
125
|
*/
|
|
115
|
-
export function matchSourceFromQuery(query, env = process.env) {
|
|
116
|
-
const sources = resolveKnownSources(env);
|
|
126
|
+
export function matchSourceFromQuery(query, env = process.env, cwd = process.cwd()) {
|
|
127
|
+
const sources = resolveKnownSources(env, cwd);
|
|
117
128
|
const q = query.toLowerCase();
|
|
118
129
|
|
|
119
130
|
// Exact or substring match — longest id wins
|
|
@@ -138,13 +149,12 @@ export function matchSourceFromQuery(query, env = process.env) {
|
|
|
138
149
|
* @param {object} [opts.env] - Env override
|
|
139
150
|
* @returns {Promise<DemandFetchResult>}
|
|
140
151
|
*/
|
|
141
|
-
export async function demandFetch({ query, rootDir, env } = {}) {
|
|
152
|
+
export async function demandFetch({ query, rootDir, env, cwd } = {}) {
|
|
142
153
|
const resolvedEnv = env ?? loadConstructEnv();
|
|
143
154
|
const root = rootDir ?? (resolvedEnv.CX_DATA_DIR?.trim() || homedir());
|
|
155
|
+
const projectRoot = cwd ?? root;
|
|
144
156
|
|
|
145
|
-
|
|
146
|
-
// always fetch from that provider — even if isSelfQuery() also fires.
|
|
147
|
-
const match = matchSourceFromQuery(query, resolvedEnv);
|
|
157
|
+
const match = matchSourceFromQuery(query, resolvedEnv, projectRoot);
|
|
148
158
|
|
|
149
159
|
// Self-referential queries about Construct itself → route to knowledge_search,
|
|
150
160
|
// but only when no external provider source was matched.
|
|
@@ -168,8 +178,7 @@ export async function demandFetch({ query, rootDir, env } = {}) {
|
|
|
168
178
|
}
|
|
169
179
|
|
|
170
180
|
if (!match) {
|
|
171
|
-
|
|
172
|
-
return demandFetchAll({ query, rootDir: root, env: resolvedEnv });
|
|
181
|
+
return demandFetchAll({ query, rootDir: root, env: resolvedEnv, cwd: projectRoot });
|
|
173
182
|
}
|
|
174
183
|
|
|
175
184
|
// Dynamically import provider registry to avoid loading all providers at module init
|
|
@@ -188,7 +197,7 @@ export async function demandFetch({ query, rootDir, env } = {}) {
|
|
|
188
197
|
}
|
|
189
198
|
|
|
190
199
|
// Build read calls for the matched source and execute them
|
|
191
|
-
const readCalls = buildReadCalls(match, resolvedEnv);
|
|
200
|
+
const readCalls = buildReadCalls(match, resolvedEnv, projectRoot);
|
|
192
201
|
if (!readCalls.length) {
|
|
193
202
|
return {
|
|
194
203
|
ok: false,
|
|
@@ -281,23 +290,32 @@ export async function demandFetch({ query, rootDir, env } = {}) {
|
|
|
281
290
|
* @param {object} [opts.env] - Resolved env
|
|
282
291
|
* @returns {Promise<DemandFetchResult>}
|
|
283
292
|
*/
|
|
284
|
-
async function demandFetchAll({ query, rootDir, env } = {}) {
|
|
293
|
+
async function demandFetchAll({ query, rootDir, env, cwd } = {}) {
|
|
285
294
|
const { ProviderRegistry } = await import('./providers/registry.mjs');
|
|
286
295
|
const registry = await ProviderRegistry.fromEnv(env);
|
|
287
|
-
const
|
|
296
|
+
const projectRoot = cwd ?? rootDir ?? process.cwd();
|
|
297
|
+
const { config } = loadProjectConfig(projectRoot, env);
|
|
298
|
+
const targets = resolveEffectiveSourceTargetsFromConfig(config, env);
|
|
299
|
+
const scoped = targets.length > 0;
|
|
300
|
+
|
|
301
|
+
const providerNames = scoped
|
|
302
|
+
? [...new Set(targets.map((t) => t.provider))].filter((name) => registry.get(name))
|
|
303
|
+
: registry.names();
|
|
288
304
|
|
|
289
305
|
if (!providerNames.length) {
|
|
290
306
|
return {
|
|
291
307
|
ok: false,
|
|
292
308
|
reason: 'no_providers',
|
|
293
|
-
message:
|
|
309
|
+
message: scoped
|
|
310
|
+
? 'No provider credentials configured for the declared source targets'
|
|
311
|
+
: 'No provider credentials configured in config.env',
|
|
294
312
|
items: [],
|
|
295
313
|
};
|
|
296
314
|
}
|
|
297
315
|
|
|
298
316
|
const allItems = [];
|
|
299
317
|
const errors = [];
|
|
300
|
-
const sources = resolveKnownSources(env);
|
|
318
|
+
const sources = resolveKnownSources(env, projectRoot);
|
|
301
319
|
|
|
302
320
|
// Group known sources by provider so we can batch-fetch per-provider
|
|
303
321
|
const byProvider = new Map();
|
|
@@ -306,11 +324,16 @@ async function demandFetchAll({ query, rootDir, env } = {}) {
|
|
|
306
324
|
byProvider.get(src.provider).push(src);
|
|
307
325
|
}
|
|
308
326
|
|
|
309
|
-
// Also include providers that have no explicit sources (Jira, Linear default fetch)
|
|
310
327
|
for (const name of providerNames) {
|
|
311
328
|
if (!byProvider.has(name)) byProvider.set(name, []);
|
|
312
329
|
}
|
|
313
330
|
|
|
331
|
+
if (scoped) {
|
|
332
|
+
for (const key of [...byProvider.keys()]) {
|
|
333
|
+
if (!providerNames.includes(key)) byProvider.delete(key);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
314
337
|
for (const [providerName, provSources] of byProvider) {
|
|
315
338
|
const provider = registry.get(providerName);
|
|
316
339
|
if (!provider) continue;
|
|
@@ -318,10 +341,12 @@ async function demandFetchAll({ query, rootDir, env } = {}) {
|
|
|
318
341
|
// Build read calls — one set per known source, or a default if none listed
|
|
319
342
|
const matchList = provSources.length > 0
|
|
320
343
|
? provSources
|
|
321
|
-
: [{ provider: providerName, ref: null, display: providerName }];
|
|
344
|
+
: (scoped ? [] : [{ provider: providerName, ref: null, display: providerName }]);
|
|
345
|
+
|
|
346
|
+
if (!matchList.length) continue;
|
|
322
347
|
|
|
323
348
|
for (const src of matchList) {
|
|
324
|
-
const readCalls = buildReadCalls(src, env);
|
|
349
|
+
const readCalls = buildReadCalls(src, env, projectRoot);
|
|
325
350
|
if (!readCalls.length) continue;
|
|
326
351
|
for (const { ref, opts, display } of readCalls) {
|
|
327
352
|
try {
|
|
@@ -384,7 +409,7 @@ async function demandFetchAll({ query, rootDir, env } = {}) {
|
|
|
384
409
|
* @param {object} env
|
|
385
410
|
* @returns {{ ref: string, opts: object, display: string }[]}
|
|
386
411
|
*/
|
|
387
|
-
function buildReadCalls(match, env) {
|
|
412
|
+
function buildReadCalls(match, env, cwd = process.cwd()) {
|
|
388
413
|
if (match.provider === 'github') {
|
|
389
414
|
const opts = { repos: [match.ref], limit: 25 };
|
|
390
415
|
return [
|
|
@@ -397,7 +422,13 @@ function buildReadCalls(match, env) {
|
|
|
397
422
|
];
|
|
398
423
|
}
|
|
399
424
|
if (match.provider === 'jira') {
|
|
400
|
-
const
|
|
425
|
+
const { config } = loadProjectConfig(cwd, env);
|
|
426
|
+
const targets = resolveEffectiveSourceTargetsFromConfig(config, env)
|
|
427
|
+
.filter((t) => t.provider === 'jira')
|
|
428
|
+
.map((t) => t.selector.project);
|
|
429
|
+
const projects = targets.length
|
|
430
|
+
? targets
|
|
431
|
+
: (env.JIRA_PROJECTS ?? '').split(',').map((p) => p.trim()).filter(Boolean);
|
|
401
432
|
const recencyDays = parseInt(env.JIRA_FETCH_RECENCY_DAYS, 10) || 30;
|
|
402
433
|
|
|
403
434
|
let jql;
|
package/lib/embed/inbox.mjs
CHANGED
|
@@ -148,6 +148,12 @@ export function resolveInboxDirs(rootDir, env = process.env) {
|
|
|
148
148
|
const config = loadIntakeConfig(rootDir, env);
|
|
149
149
|
const dirs = [];
|
|
150
150
|
|
|
151
|
+
if (config.includeRootInbox ?? config.includeArchetypeInbox) {
|
|
152
|
+
const rootInbox = join(rootDir, ARCHETYPE_INBOX_SUBDIR);
|
|
153
|
+
mkdirSync(rootInbox, { recursive: true });
|
|
154
|
+
dirs.push(rootInbox);
|
|
155
|
+
}
|
|
156
|
+
|
|
151
157
|
if (config.includeProjectInbox) {
|
|
152
158
|
const projectInbox = join(rootDir, DEFAULT_INBOX_SUBDIR);
|
|
153
159
|
mkdirSync(projectInbox, { recursive: true });
|
|
@@ -159,16 +165,6 @@ export function resolveInboxDirs(rootDir, env = process.env) {
|
|
|
159
165
|
if (existsSync(docsIntake)) dirs.push(docsIntake);
|
|
160
166
|
}
|
|
161
167
|
|
|
162
|
-
// Archetype inbox is a profile-driven drop zone for raw source files
|
|
163
|
-
// (capabilities.intake.inbox). Off by default; init flips it on when the
|
|
164
|
-
// active profile declares the capability so non-archetype projects keep
|
|
165
|
-
// their pre-Piece-C behavior.
|
|
166
|
-
|
|
167
|
-
if (config.includeArchetypeInbox) {
|
|
168
|
-
const archetypeInbox = join(rootDir, ARCHETYPE_INBOX_SUBDIR);
|
|
169
|
-
if (existsSync(archetypeInbox)) dirs.push(archetypeInbox);
|
|
170
|
-
}
|
|
171
|
-
|
|
172
168
|
for (const candidate of config.parentDirs) {
|
|
173
169
|
if (!candidate) continue;
|
|
174
170
|
if (!existsSync(candidate)) continue;
|
package/lib/embed/worker.mjs
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
import os from 'node:os';
|
|
10
10
|
import path from 'node:path';
|
|
11
|
-
import {
|
|
11
|
+
import { prepareConstructEnv } from '../runtime-env.mjs';
|
|
12
12
|
import { EmbedDaemon } from './daemon.mjs';
|
|
13
13
|
|
|
14
14
|
function parseArgv(argv) {
|
|
@@ -19,7 +19,7 @@ function parseArgv(argv) {
|
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
const configPath = parseArgv(process.argv.slice(2));
|
|
22
|
-
const env =
|
|
22
|
+
const env = prepareConstructEnv();
|
|
23
23
|
|
|
24
24
|
// If no config file, build an auto-discovered config inline and inject it
|
|
25
25
|
let daemonOpts = { env };
|
|
@@ -28,8 +28,9 @@ if (configPath) {
|
|
|
28
28
|
} else {
|
|
29
29
|
const { ProviderRegistry } = await import('./providers/registry.mjs');
|
|
30
30
|
const { EMPTY_CONFIG } = await import('./config.mjs');
|
|
31
|
+
const { resolveAutoEmbedSources } = await import('./auto-sources.mjs');
|
|
31
32
|
const registry = await ProviderRegistry.fromEnv(env);
|
|
32
|
-
const sources =
|
|
33
|
+
const sources = resolveAutoEmbedSources({ cwd: process.cwd(), env, registry });
|
|
33
34
|
daemonOpts.registry = registry;
|
|
34
35
|
daemonOpts.config = {
|
|
35
36
|
...EMPTY_CONFIG,
|
|
@@ -13,6 +13,7 @@ import { recommendPlan as recommendPlanCore } from './triage.mjs';
|
|
|
13
13
|
import { invokeWorkflow as invokeWorkflowCore } from './workflow-invoke.mjs';
|
|
14
14
|
import { buildCapabilityContract } from './capability.mjs';
|
|
15
15
|
import { resolveExecution as resolveExecutionCore } from './execution.mjs';
|
|
16
|
+
import { planArtifactWorkflow as planArtifactWorkflowCore, runArtifactWorkflow as runArtifactWorkflowCore } from '../artifact-workflow.mjs';
|
|
16
17
|
|
|
17
18
|
export { CONTRACT_VERSION, MIN_CLIENT_CONTRACT_VERSION, isClientCompatible } from './contract-version.mjs';
|
|
18
19
|
export { APPROVAL_MODES } from './audit.mjs';
|
|
@@ -85,3 +86,13 @@ export function describeCapabilities({ env, cwd, rootDir } = {}) {
|
|
|
85
86
|
export function resolveExecution(request = {}, { env, cwd, registryPath } = {}) {
|
|
86
87
|
return wrapContractResult(resolveExecutionCore(request, { env, cwd, registryPath }), { surface: 'sdk', env, cwd });
|
|
87
88
|
}
|
|
89
|
+
|
|
90
|
+
/** Plan a manifest-backed artifact workflow without executing specialists. */
|
|
91
|
+
export function planArtifactWorkflow(request = {}, { env, cwd, rootDir } = {}) {
|
|
92
|
+
return wrapContractResult(planArtifactWorkflowCore(request, { cwd, rootDir }), { surface: 'sdk', env, cwd });
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** Run only locally observable artifact steps and return truthful provenance. */
|
|
96
|
+
export function runArtifactWorkflow(request = {}, { env, cwd, rootDir } = {}) {
|
|
97
|
+
return wrapContractResult(runArtifactWorkflowCore(request, { cwd, rootDir }), { surface: 'sdk', env, cwd });
|
|
98
|
+
}
|
|
@@ -61,15 +61,15 @@ function matchCapabilities(modelId, requested, warnings) {
|
|
|
61
61
|
return matched;
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
-
function familyByProvider(hostProvider, env) {
|
|
64
|
+
function familyByProvider(hostProvider, env, allowAmbient) {
|
|
65
65
|
if (!hostProvider || typeof hostProvider !== 'string') return null;
|
|
66
|
-
const families = listModelFamilies({ env });
|
|
66
|
+
const families = listModelFamilies({ env, allowAmbient });
|
|
67
67
|
return families.find((f) => f.id === hostProvider)
|
|
68
68
|
|| families.find((f) => f.id === `openrouter-${hostProvider}`)
|
|
69
69
|
|| null;
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
function success({ selectedModel, family, tier, resolutionSource, fallbackReason = null, tierSource = null, capabilities, warnings, env }) {
|
|
72
|
+
function success({ selectedModel, family, tier, resolutionSource, fallbackReason = null, tierSource = null, capabilities, warnings, env, allowAmbient }) {
|
|
73
73
|
return {
|
|
74
74
|
selectedModel,
|
|
75
75
|
selectedProvider: family ? family.id : null,
|
|
@@ -123,28 +123,28 @@ function configError({ tier, reason, remediation, warnings }) {
|
|
|
123
123
|
* @param {string} [opts.registryPath]
|
|
124
124
|
* @returns {object}
|
|
125
125
|
*/
|
|
126
|
-
export function resolveEmbeddedModel(request = {}, { env = process.env, registryPath = null } = {}) {
|
|
126
|
+
export function resolveEmbeddedModel(request = {}, { env = process.env, registryPath = null, allowAmbient = env === process.env } = {}) {
|
|
127
127
|
const { workflowType, requestedTier, hostModel, hostProvider, capabilities, allowCrossProviderFallback = false } = request;
|
|
128
128
|
const tier = resolveTier({ requestedTier, workflowType });
|
|
129
129
|
const warnings = [];
|
|
130
130
|
|
|
131
|
-
const hostFamily = hostModel ? describeModelFamily(hostModel, { env }) : null;
|
|
131
|
+
const hostFamily = hostModel ? describeModelFamily(hostModel, { env, allowAmbient }) : null;
|
|
132
132
|
|
|
133
133
|
if (hostModel && hostFamily) {
|
|
134
|
-
return success({ selectedModel: hostModel, family: hostFamily, tier, resolutionSource: 'host-model', capabilities, warnings, env });
|
|
134
|
+
return success({ selectedModel: hostModel, family: hostFamily, tier, resolutionSource: 'host-model', capabilities, warnings, env, allowAmbient });
|
|
135
135
|
}
|
|
136
136
|
if (hostModel && !hostFamily) {
|
|
137
137
|
warnings.push(`Host model "${hostModel}" is not a recognized provider family; attempting same-family or tier fallback.`);
|
|
138
138
|
}
|
|
139
139
|
|
|
140
|
-
const family = hostFamily || familyByProvider(hostProvider, env);
|
|
140
|
+
const family = hostFamily || familyByProvider(hostProvider, env, allowAmbient);
|
|
141
141
|
if (family) {
|
|
142
142
|
const model = family.tiers[tier];
|
|
143
143
|
if (model) {
|
|
144
144
|
const fallbackReason = hostModel
|
|
145
145
|
? `Exact host model unavailable; resolved the ${tier} model in the same provider family (${family.id}).`
|
|
146
146
|
: `Resolved the ${tier} model within the host provider family (${family.id}).`;
|
|
147
|
-
return success({ selectedModel: model, family, tier, resolutionSource: 'same-family-fallback', fallbackReason, capabilities, warnings, env });
|
|
147
|
+
return success({ selectedModel: model, family, tier, resolutionSource: 'same-family-fallback', fallbackReason, capabilities, warnings, env, allowAmbient });
|
|
148
148
|
}
|
|
149
149
|
}
|
|
150
150
|
|
|
@@ -166,7 +166,7 @@ export function resolveEmbeddedModel(request = {}, { env = process.env, registry
|
|
|
166
166
|
: 'No host context supplied; used the Construct tier default.';
|
|
167
167
|
return success({
|
|
168
168
|
selectedModel: defaultModel,
|
|
169
|
-
family: describeModelFamily(defaultModel, { env }),
|
|
169
|
+
family: describeModelFamily(defaultModel, { env, allowAmbient }),
|
|
170
170
|
tier,
|
|
171
171
|
resolutionSource: 'tier-default',
|
|
172
172
|
fallbackReason,
|
|
@@ -174,6 +174,7 @@ export function resolveEmbeddedModel(request = {}, { env = process.env, registry
|
|
|
174
174
|
capabilities,
|
|
175
175
|
warnings,
|
|
176
176
|
env,
|
|
177
|
+
allowAmbient,
|
|
177
178
|
});
|
|
178
179
|
}
|
|
179
180
|
|
package/lib/env-config.mjs
CHANGED
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
import fs from 'node:fs';
|
|
9
9
|
import os from 'node:os';
|
|
10
10
|
import path from 'node:path';
|
|
11
|
+
import { API_KEY_CREDENTIALS } from './providers/credential-catalog.mjs';
|
|
12
|
+
import { extractOpRef } from './providers/secret-resolver.mjs';
|
|
11
13
|
|
|
12
14
|
export function parseEnvFile(filePath) {
|
|
13
15
|
if (!filePath || !fs.existsSync(filePath)) return {};
|
|
@@ -88,6 +90,22 @@ const ENV_SHADOW_WARN = new Set([
|
|
|
88
90
|
'CONSTRUCT_DEPLOYMENT_MODE',
|
|
89
91
|
]);
|
|
90
92
|
|
|
93
|
+
const CREDENTIAL_ENV_KEYS = new Set(
|
|
94
|
+
API_KEY_CREDENTIALS.flatMap((entry) => entry.envVars),
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
function isOpReference(value) {
|
|
98
|
+
return Boolean(extractOpRef(value));
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function preferInjectedCredential(envValue, fileValue) {
|
|
102
|
+
return Boolean(
|
|
103
|
+
envValue
|
|
104
|
+
&& !isOpReference(envValue)
|
|
105
|
+
&& isOpReference(fileValue),
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
91
109
|
export function loadConstructEnv({ rootDir, homeDir = os.homedir(), env = process.env, warn = true } = {}) {
|
|
92
110
|
const rootEnv = rootDir ? parseEnvFile(path.join(rootDir, '.env')) : {};
|
|
93
111
|
const userEnv = parseEnvFile(getUserEnvPath(homeDir));
|
|
@@ -103,6 +121,7 @@ export function loadConstructEnv({ rootDir, homeDir = os.homedir(), env = proces
|
|
|
103
121
|
// silently and no user action is needed.
|
|
104
122
|
if (!env[key]) continue;
|
|
105
123
|
if (key in fileEnv && fileEnv[key] !== env[key]) {
|
|
124
|
+
if (preferInjectedCredential(env[key], fileEnv[key])) continue;
|
|
106
125
|
process.stderr.write(
|
|
107
126
|
`[construct] WARNING: process.env.${key} (${env[key].slice(0, 6)}…) ` +
|
|
108
127
|
`differs from config.env value (${fileEnv[key].slice(0, 6)}…). ` +
|
|
@@ -115,7 +134,14 @@ export function loadConstructEnv({ rootDir, homeDir = os.homedir(), env = proces
|
|
|
115
134
|
|
|
116
135
|
// process.env is the base; config.env (file) values override shell values
|
|
117
136
|
// so that credentials set in the dashboard always win over stale shell exports.
|
|
137
|
+
// Materialized credentials from `op run` (or an explicit export) win over stored
|
|
138
|
+
// op:// references so one 1Password auth per invocation is enough.
|
|
118
139
|
const merged = { ...env, ...fileEnv };
|
|
140
|
+
for (const key of CREDENTIAL_ENV_KEYS) {
|
|
141
|
+
if (preferInjectedCredential(env[key], fileEnv[key])) {
|
|
142
|
+
merged[key] = env[key];
|
|
143
|
+
}
|
|
144
|
+
}
|
|
119
145
|
const databaseUrl = resolveDatabaseUrl(merged);
|
|
120
146
|
if (databaseUrl && !cleanEnvValue(merged.DATABASE_URL)) {
|
|
121
147
|
merged.DATABASE_URL = databaseUrl;
|