@geraldmaron/construct 1.0.0 → 1.0.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/.env.example +10 -7
- package/LICENSE +201 -21
- package/README.md +132 -257
- package/agents/contracts.json +387 -0
- package/agents/prompts/cx-accessibility.md +8 -0
- package/agents/prompts/cx-ai-engineer.md +92 -0
- package/agents/prompts/cx-architect.md +10 -2
- package/agents/prompts/cx-business-strategist.md +13 -0
- package/agents/prompts/cx-data-analyst.md +80 -0
- package/agents/prompts/cx-data-engineer.md +4 -0
- package/agents/prompts/cx-debugger.md +8 -0
- package/agents/prompts/cx-designer.md +19 -0
- package/agents/prompts/cx-devil-advocate.md +4 -0
- package/agents/prompts/cx-docs-keeper.md +128 -0
- package/agents/prompts/cx-engineer.md +13 -0
- package/agents/prompts/cx-evaluator.md +4 -0
- package/agents/prompts/cx-explorer.md +12 -0
- package/agents/prompts/cx-legal-compliance.md +13 -0
- package/agents/prompts/cx-operations.md +13 -0
- package/agents/prompts/cx-orchestrator.md +107 -4
- package/agents/prompts/cx-platform-engineer.md +75 -0
- package/agents/prompts/cx-product-manager.md +8 -0
- package/agents/prompts/cx-qa.md +100 -0
- package/agents/prompts/cx-rd-lead.md +13 -0
- package/agents/prompts/cx-release-manager.md +8 -0
- package/agents/prompts/cx-researcher.md +21 -1
- package/agents/prompts/cx-reviewer.md +8 -0
- package/agents/prompts/cx-security.md +104 -0
- package/agents/prompts/cx-sre.md +104 -0
- package/agents/prompts/cx-test-automation.md +4 -0
- package/agents/prompts/cx-trace-reviewer.md +7 -3
- package/agents/prompts/cx-ux-researcher.md +4 -0
- package/agents/registry.json +365 -90
- package/agents/role-manifests.json +217 -0
- package/bin/construct +3345 -141
- package/bin/construct-postinstall.mjs +87 -0
- package/commands/build/feature.md +6 -6
- package/commands/plan/feature.md +6 -5
- package/commands/plan/requirements.md +1 -1
- package/commands/ship/status.md +1 -1
- package/commands/work/drive.md +3 -3
- package/commands/work/optimize-prompts.md +3 -3
- package/db/{migrations → schema}/001_init.sql +2 -0
- package/db/schema/002_pgvector.sql +182 -0
- package/db/schema/003_intake.sql +47 -0
- package/examples/README.md +85 -0
- package/examples/internal/roles/architect/bad/clever-plan-without-contracts.md +30 -0
- package/examples/internal/roles/architect/golden/explicit-tradeoff-before-plan.md +23 -0
- package/examples/internal/roles/engineer/bad/speculative-abstraction.md +30 -0
- package/examples/internal/roles/engineer/golden/read-before-write.md +28 -0
- package/examples/internal/roles/orchestrator/bad/everything-becomes-multi-agent.md +29 -0
- package/examples/internal/roles/orchestrator/golden/minimal-dispatch.md +22 -0
- package/examples/internal/roles/qa/bad/coverage-theater.md +30 -0
- package/examples/internal/roles/qa/golden/regression-gate.md +23 -0
- package/examples/internal/roles/reviewer/bad/lgtm-without-verification.md +29 -0
- package/examples/internal/roles/reviewer/golden/find-structural-risk-first.md +28 -0
- package/examples/personas/construct/adversarial/ignore-instruction-to-skip-approval.md +22 -0
- package/examples/personas/construct/bad/commit-without-approval.md +30 -0
- package/examples/personas/construct/boundary/blocked-needs-main-input.md +29 -0
- package/examples/personas/construct/golden/branch-approval-before-mutation.md +36 -0
- package/examples/personas/construct/golden/focused-direct-answer.md +28 -0
- package/examples/provider-plugin/README.md +34 -0
- package/examples/provider-plugin/index.mjs +74 -0
- package/examples/provider-plugin/package.json +15 -0
- package/examples/seed-observations/README.md +38 -0
- package/examples/seed-observations/anti-patterns.md +44 -0
- package/examples/seed-observations/decisions.md +36 -0
- package/examples/seed-observations/patterns.md +42 -0
- package/lib/agent-contracts-enforce.mjs +158 -0
- package/lib/agent-contracts.mjs +231 -0
- package/lib/agents/postconditions.mjs +126 -0
- package/lib/agents/schema.mjs +124 -0
- package/lib/artifact-capture.mjs +183 -0
- package/lib/audit-trail.mjs +149 -0
- package/lib/auto-docs.mjs +245 -65
- package/lib/beads/auto-close.mjs +126 -0
- package/lib/beads/drift.mjs +171 -0
- package/lib/beads-automation.mjs +542 -0
- package/lib/beads-client.mjs +518 -0
- package/lib/beads-lock.mjs +377 -0
- package/lib/beads-optimistic.mjs +365 -0
- package/lib/bootstrap/built-ins.mjs +136 -0
- package/lib/bootstrap/lazy-install.mjs +161 -0
- package/lib/bootstrap/resources.mjs +120 -0
- package/lib/bootstrap.mjs +105 -0
- package/lib/cache-governor.js +213 -0
- package/lib/cache-strategy-anthropic.js +62 -0
- package/lib/cache-strategy-google.js +79 -0
- package/lib/cache-strategy-none.js +30 -0
- package/lib/cache-strategy-openai.js +48 -0
- package/lib/cache-strategy.js +91 -0
- package/lib/claude-allow.mjs +149 -0
- package/lib/cli-commands.mjs +413 -258
- package/lib/codex-config.mjs +3 -1
- package/lib/comment-lint.mjs +55 -6
- package/lib/completions.mjs +21 -6
- package/lib/config/alias.mjs +56 -0
- package/lib/config/project-config.mjs +335 -0
- package/lib/config/schema.mjs +159 -0
- package/lib/context-router.mjs +308 -0
- package/lib/cost-ledger.mjs +177 -0
- package/lib/cost.mjs +171 -10
- package/lib/dashboard-static.mjs +158 -0
- package/lib/deployment-mode.mjs +86 -0
- package/lib/deprecate.mjs +49 -0
- package/lib/dispatch-batch.js +183 -0
- package/lib/distill.mjs +21 -8
- package/lib/doc-stamp.mjs +164 -0
- package/lib/doc-verify.mjs +119 -0
- package/lib/docs-routing.mjs +89 -0
- package/lib/docs-verify.mjs +417 -0
- package/lib/doctor/audit.mjs +71 -0
- package/lib/doctor/cli.mjs +99 -0
- package/lib/doctor/escalate.mjs +29 -0
- package/lib/doctor/index.mjs +140 -0
- package/lib/doctor/report.mjs +170 -0
- package/lib/doctor/watchers/bd-watch.mjs +117 -0
- package/lib/doctor/watchers/cost.mjs +130 -0
- package/lib/doctor/watchers/disk.mjs +122 -0
- package/lib/doctor/watchers/handoffs.mjs +33 -0
- package/lib/doctor/watchers/process-pressure.mjs +60 -0
- package/lib/doctor/watchers/service-health.mjs +188 -0
- package/lib/document-extract.mjs +288 -0
- package/lib/document-ingest.mjs +230 -0
- package/lib/drop.mjs +282 -0
- package/lib/embed/approval-queue.mjs +176 -0
- package/lib/embed/artifact.mjs +349 -0
- package/lib/embed/authority-guard.mjs +155 -0
- package/lib/embed/cli.mjs +408 -0
- package/lib/embed/config.mjs +355 -0
- package/lib/embed/conflict-detection.mjs +264 -0
- package/lib/embed/customer-profiles.mjs +480 -0
- package/lib/embed/daemon.mjs +1309 -0
- package/lib/embed/demand-fetch.mjs +449 -0
- package/lib/embed/docs-lifecycle.mjs +349 -0
- package/lib/embed/inbox-live-watcher.mjs +119 -0
- package/lib/embed/inbox.mjs +343 -0
- package/lib/embed/intake-metrics.mjs +190 -0
- package/lib/embed/jobs/vector-sync.mjs +198 -0
- package/lib/embed/notifications.mjs +75 -0
- package/lib/embed/output.mjs +79 -0
- package/lib/embed/providers/github.mjs +295 -0
- package/lib/embed/providers/jira.mjs +192 -0
- package/lib/embed/providers/linear.mjs +186 -0
- package/lib/embed/providers/registry.mjs +115 -0
- package/lib/embed/providers/slack.mjs +203 -0
- package/lib/embed/recommendation-store.mjs +378 -0
- package/lib/embed/roadmap.mjs +374 -0
- package/lib/embed/role-framing.mjs +110 -0
- package/lib/embed/scheduler.mjs +99 -0
- package/lib/embed/semantic.mjs +325 -0
- package/lib/embed/snapshot.mjs +191 -0
- package/lib/embed/supervision.mjs +235 -0
- package/lib/embed/target-resolver.mjs +186 -0
- package/lib/embed/worker.mjs +62 -0
- package/lib/embed/workspaces.mjs +297 -0
- package/lib/engine/chunker-headings.mjs +110 -0
- package/lib/engine/compressor-heuristic.mjs +100 -0
- package/lib/engine/consolidate.mjs +287 -0
- package/lib/engine/contracts.mjs +126 -0
- package/lib/engine/defaults.mjs +129 -0
- package/lib/engine/eval-retrieval.mjs +148 -0
- package/lib/engine/fuser-rrf.mjs +62 -0
- package/lib/engine/index.mjs +37 -0
- package/lib/engine/registry.mjs +146 -0
- package/lib/engine/reranker-mmr.mjs +90 -0
- package/lib/engine/tokens.mjs +77 -0
- package/lib/entity-store.mjs +280 -0
- package/lib/env-config.mjs +69 -4
- package/lib/evals/retrieval-bench.mjs +159 -0
- package/lib/evaluator-optimizer.mjs +317 -0
- package/lib/features.mjs +159 -29
- package/lib/gates-audit.mjs +236 -0
- package/lib/git-hooks/prepare-commit-msg +58 -0
- package/lib/handoffs/cleanup.mjs +159 -0
- package/lib/handoffs/contract.mjs +162 -0
- package/lib/handoffs/inventory.mjs +120 -0
- package/lib/headhunt.mjs +28 -47
- package/lib/health-check.mjs +399 -0
- package/lib/hook-health.mjs +442 -0
- package/lib/hooks/_lib/log.mjs +82 -0
- package/lib/hooks/adaptive-lint.mjs +26 -2
- package/lib/hooks/agent-tracker.mjs +171 -14
- package/lib/hooks/audit-reads.mjs +109 -0
- package/lib/hooks/audit-trail.mjs +153 -0
- package/lib/hooks/bash-output-logger.mjs +71 -0
- package/lib/hooks/block-no-verify.mjs +41 -0
- package/lib/hooks/ci-status-check.mjs +82 -0
- package/lib/hooks/comment-lint.mjs +29 -7
- package/lib/hooks/config-protection.mjs +39 -18
- package/lib/hooks/context-watch.mjs +137 -0
- package/lib/hooks/context-window-recovery.mjs +4 -20
- package/lib/hooks/dep-audit.mjs +16 -0
- package/lib/hooks/doc-coupling-check.mjs +80 -0
- package/lib/hooks/edit-accumulator.mjs +3 -0
- package/lib/hooks/edit-error-recovery.mjs +3 -0
- package/lib/hooks/edit-guard.mjs +45 -4
- package/lib/hooks/env-check.mjs +3 -0
- package/lib/hooks/guard-bash.mjs +58 -1
- package/lib/hooks/mcp-audit.mjs +18 -20
- package/lib/hooks/mcp-health-check.mjs +36 -0
- package/lib/hooks/model-fallback.mjs +42 -56
- package/lib/hooks/policy-engine.mjs +209 -0
- package/lib/hooks/post-merge-docs-check.mjs +63 -0
- package/lib/hooks/pre-compact.mjs +4 -24
- package/lib/hooks/pre-push-gate.mjs +200 -37
- package/lib/hooks/proactive-activation.mjs +284 -0
- package/lib/hooks/read-tracker.mjs +27 -4
- package/lib/hooks/readme-age-check.mjs +77 -0
- package/lib/hooks/registry-sync.mjs +12 -5
- package/lib/hooks/scan-secrets.mjs +50 -7
- package/lib/hooks/session-optimize.mjs +311 -0
- package/lib/hooks/session-start.mjs +287 -28
- package/lib/hooks/stop-notify.mjs +236 -96
- package/lib/hooks/stop-typecheck.mjs +13 -1
- package/lib/hooks/test-watch.mjs +68 -0
- package/lib/host-capabilities.mjs +31 -10
- package/lib/init-docs.mjs +731 -291
- package/lib/init-unified.mjs +1116 -0
- package/lib/init-update.mjs +168 -0
- package/lib/init.mjs +107 -0
- package/lib/install/first-invocation.mjs +119 -0
- package/lib/install/stage-project.mjs +69 -0
- package/lib/intake/classify.mjs +278 -0
- package/lib/intake/feedback.mjs +273 -0
- package/lib/intake/filesystem-queue.mjs +158 -0
- package/lib/intake/intake-config.mjs +132 -0
- package/lib/intake/postgres-queue.mjs +198 -0
- package/lib/intake/prepare.mjs +139 -0
- package/lib/intake/queue.mjs +83 -0
- package/lib/intake/session-prelude.mjs +50 -0
- package/lib/integrations/intake-integrations.mjs +740 -0
- package/lib/intent-classifier.mjs +253 -0
- package/lib/knowledge/layout.mjs +72 -0
- package/lib/knowledge/rag.mjs +331 -0
- package/lib/knowledge/search.mjs +315 -0
- package/lib/knowledge/trends.mjs +261 -0
- package/lib/logger.mjs +85 -0
- package/lib/mcp/broker.mjs +124 -0
- package/lib/mcp/server.mjs +554 -854
- package/lib/mcp/tools/document.mjs +132 -0
- package/lib/mcp/tools/memory.mjs +209 -0
- package/lib/mcp/tools/project.mjs +349 -0
- package/lib/mcp/tools/skills.mjs +409 -0
- package/lib/mcp/tools/storage.mjs +60 -0
- package/lib/mcp/tools/telemetry.mjs +315 -0
- package/lib/mcp/tools/workflow.mjs +112 -0
- package/lib/mcp-catalog.json +54 -3
- package/lib/mcp-manager.mjs +96 -48
- package/lib/mcp-platform-config.mjs +22 -22
- package/lib/memory-stats.mjs +121 -0
- package/lib/mode-commands.mjs +124 -0
- package/lib/model-free-selector.mjs +184 -0
- package/lib/model-pricing.mjs +152 -0
- package/lib/model-registry.mjs +226 -0
- package/lib/model-router.mjs +362 -386
- package/lib/observation-store.mjs +391 -0
- package/lib/ollama-manager.mjs +428 -0
- package/lib/opencode-config.mjs +13 -3
- package/lib/opencode-runtime-plugin.mjs +70 -38
- package/lib/opencode-telemetry.mjs +64 -6
- package/lib/orchestration-policy.mjs +509 -6
- package/lib/overrides/resolver.mjs +207 -0
- package/lib/parity.mjs +147 -0
- package/lib/paths.mjs +33 -0
- package/lib/performance/generate.mjs +212 -0
- package/lib/plugin-registry.mjs +268 -0
- package/lib/policy/engine.mjs +130 -0
- package/lib/policy/unified-gates.mjs +96 -0
- package/lib/project-detection.mjs +129 -0
- package/lib/project-init-shared.mjs +272 -0
- package/lib/project-profile.mjs +447 -0
- package/lib/prompt-composer.js +434 -0
- package/lib/prompt-metadata.mjs +1 -1
- package/lib/provider-capabilities-anthropic.js +44 -0
- package/lib/provider-capabilities-deepseek.js +37 -0
- package/lib/provider-capabilities-generic.js +26 -0
- package/lib/provider-capabilities-google.js +47 -0
- package/lib/provider-capabilities-openai.js +45 -0
- package/lib/provider-capabilities.js +142 -0
- package/lib/providers/atlassian-confluence/index.mjs +103 -0
- package/lib/providers/atlassian-jira/index.mjs +100 -0
- package/lib/providers/auth-manager.mjs +126 -0
- package/lib/providers/circuit-breaker.mjs +124 -0
- package/lib/providers/contract.mjs +93 -0
- package/lib/providers/github/index.mjs +126 -0
- package/lib/providers/registry.mjs +184 -0
- package/lib/providers/salesforce/index.mjs +100 -0
- package/lib/providers/slack/index.mjs +80 -0
- package/lib/reflect.mjs +137 -0
- package/lib/research-lint.mjs +164 -0
- package/lib/resources/budget.mjs +259 -0
- package/lib/role-preload.mjs +21 -6
- package/lib/roles/approval-surface.mjs +54 -0
- package/lib/roles/cli.mjs +118 -0
- package/lib/roles/event-bus.mjs +79 -0
- package/lib/roles/fence.mjs +84 -0
- package/lib/roles/gateway.mjs +260 -0
- package/lib/roles/hook-emit.mjs +37 -0
- package/lib/roles/manifest.mjs +48 -0
- package/lib/roles/router.mjs +27 -0
- package/lib/runtime-pressure.mjs +360 -0
- package/lib/schema-artifact.mjs +134 -0
- package/lib/schema-infer.mjs +551 -0
- package/lib/server/auth.mjs +168 -0
- package/lib/server/chat.mjs +336 -0
- package/lib/server/cors.mjs +77 -0
- package/lib/server/csrf.mjs +91 -0
- package/lib/server/index.mjs +1927 -78
- package/lib/server/insights.mjs +765 -0
- package/lib/server/rate-limit.mjs +91 -0
- package/lib/server/static/assets/index-ab25c707.js +70 -0
- package/lib/server/static/assets/index-f0c80a2b.css +1 -0
- package/lib/server/static/index.html +12 -817
- package/lib/server/telemetry-login.mjs +108 -0
- package/lib/server/webhook.mjs +510 -0
- package/lib/service-manager.mjs +522 -58
- package/lib/services/pattern-promotion-service.mjs +167 -0
- package/lib/services/telemetry-backend.mjs +178 -0
- package/lib/session-store.mjs +374 -0
- package/lib/setup-prompts.mjs +96 -0
- package/lib/setup.mjs +525 -38
- package/lib/skills-apply.mjs +280 -0
- package/lib/skills-scope.mjs +118 -0
- package/lib/status.mjs +261 -70
- package/lib/storage/admin.mjs +355 -0
- package/lib/storage/backend.mjs +2 -1
- package/lib/storage/backup.mjs +347 -0
- package/lib/storage/embeddings-engine.mjs +133 -0
- package/lib/storage/embeddings-legacy.mjs +85 -0
- package/lib/storage/embeddings-local.mjs +108 -0
- package/lib/storage/embeddings-ollama.mjs +78 -0
- package/lib/storage/embeddings-openai.mjs +85 -0
- package/lib/storage/embeddings.mjs +92 -33
- package/lib/storage/file-lock.mjs +130 -0
- package/lib/storage/fusion.mjs +95 -0
- package/lib/storage/hybrid-query.mjs +34 -27
- package/lib/storage/migrations.mjs +187 -0
- package/lib/storage/postgres-backup.mjs +124 -0
- package/lib/storage/sql-store.mjs +5 -15
- package/lib/storage/state-source.mjs +12 -13
- package/lib/storage/store-version.mjs +115 -0
- package/lib/storage/sync.mjs +144 -35
- package/lib/storage/unified-storage.mjs +550 -0
- package/lib/storage/vector-client.mjs +286 -0
- package/lib/storage/vector-store.mjs +71 -30
- package/lib/task-graph/generate.mjs +135 -0
- package/lib/task-graph/schema.mjs +81 -0
- package/lib/task-graph/store.mjs +71 -0
- package/lib/telemetry/backends/local.mjs +62 -0
- package/lib/telemetry/backends/{langfuse.mjs → remote.mjs} +27 -14
- package/lib/telemetry/backfill.mjs +180 -0
- package/lib/telemetry/eval-datasets.mjs +203 -0
- package/lib/telemetry/{langfuse-ingest.mjs → ingest.mjs} +26 -20
- package/lib/telemetry/intent-verifications.mjs +86 -0
- package/lib/telemetry/llm-judge.mjs +350 -0
- package/lib/telemetry/model-pricing-catalog.mjs +557 -0
- package/lib/telemetry/setup.mjs +151 -0
- package/lib/telemetry/skill-calls.mjs +78 -0
- package/lib/telemetry/team-rollup.mjs +4 -4
- package/lib/token-engine.js +117 -0
- package/lib/token-estimator-anthropic.js +15 -0
- package/lib/token-estimator-deepseek.js +13 -0
- package/lib/token-estimator-default.js +13 -0
- package/lib/token-estimator-google.js +13 -0
- package/lib/token-estimator-openai.js +13 -0
- package/lib/toolkit-env.mjs +1 -1
- package/lib/tty-prompts.mjs +211 -0
- package/lib/uninstall/uninstall.mjs +423 -0
- package/lib/update.mjs +115 -0
- package/lib/upgrade.mjs +141 -0
- package/lib/validator.mjs +51 -2
- package/lib/validators/skills.mjs +142 -0
- package/lib/wireframe.mjs +422 -0
- package/lib/worker/entrypoint.mjs +241 -0
- package/lib/worker/evidence.mjs +107 -0
- package/lib/worker/run.mjs +154 -0
- package/lib/worker/trace.mjs +182 -0
- package/lib/workflow-state.mjs +14 -18
- package/package.json +21 -8
- package/personas/construct.md +53 -51
- package/platforms/claude/CLAUDE.md +1 -1
- package/platforms/claude/settings.template.json +115 -68
- package/platforms/opencode/config.template.json +3 -7
- package/rules/common/agents.md +11 -38
- package/rules/common/beads-hygiene.md +75 -0
- package/rules/common/code-review.md +11 -100
- package/rules/common/coding-style.md +5 -3
- package/rules/common/comments.md +30 -111
- package/rules/common/commit-approval.md +53 -0
- package/rules/common/cx-agent-routing.md +1 -0
- package/rules/common/development-workflow.md +23 -41
- package/rules/common/doc-ownership.md +54 -0
- package/rules/common/efficiency.md +57 -0
- package/rules/common/framing.md +76 -0
- package/rules/common/git-workflow.md +2 -4
- package/rules/common/patterns.md +3 -7
- package/rules/common/performance.md +15 -31
- package/rules/common/release-gates.md +69 -0
- package/rules/common/research.md +107 -0
- package/rules/common/security.md +6 -6
- package/rules/common/skill-composition.md +67 -0
- package/rules/common/testing.md +15 -27
- package/rules/golang/hooks.md +0 -4
- package/rules/policy/bootstrap.yaml +11 -0
- package/rules/policy/drive.yaml +8 -0
- package/rules/policy/task.yaml +9 -0
- package/rules/policy/workflow.yaml +9 -0
- package/rules/python/hooks.md +0 -4
- package/rules/swift/hooks.md +0 -4
- package/rules/typescript/hooks.md +0 -4
- package/rules/web/hooks.md +0 -2
- package/{sync-agents.mjs → scripts/sync-agents.mjs} +306 -61
- package/skills/ai/prompt-optimizer.md +7 -7
- package/skills/compliance/ai-disclosure.md +58 -0
- package/skills/compliance/data-privacy.md +46 -0
- package/skills/compliance/license-audit.md +40 -0
- package/skills/compliance/regulatory-review.md +61 -0
- package/skills/docs/document-ingest-workflow.md +52 -0
- package/skills/docs/evidence-ingest-workflow.md +9 -0
- package/skills/docs/init-docs.md +51 -18
- package/skills/docs/product-intelligence-workflow.md +12 -0
- package/skills/docs/product-signal-workflow.md +4 -0
- package/skills/docs/research-workflow.md +23 -8
- package/skills/docs/runbook-workflow.md +1 -1
- package/skills/operating/orchestration-reference.md +151 -0
- package/skills/roles/architect.md +5 -0
- package/skills/roles/engineer.md +32 -0
- package/skills/roles/operator.md +12 -0
- package/skills/roles/reviewer.md +23 -0
- package/skills/routing.md +1 -1
- package/templates/devcontainer/Dockerfile.devcontainer +38 -0
- package/templates/devcontainer/devcontainer.json +31 -0
- package/templates/distribution/bootstrap.ps1 +142 -0
- package/templates/distribution/bootstrap.sh +196 -0
- package/templates/distribution/run.mjs +187 -0
- package/templates/docs/adr.md +44 -8
- package/templates/docs/changelog-entry.md +43 -0
- package/templates/docs/construct_guide.md +149 -0
- package/templates/docs/evidence-brief.md +2 -2
- package/templates/docs/meta-prd.md +140 -19
- package/templates/docs/onboarding.md +57 -0
- package/templates/docs/prd.md +159 -15
- package/templates/docs/research-brief.md +4 -4
- package/templates/homebrew/construct.rb +67 -0
- package/langfuse/docker-compose.yml +0 -82
- package/lib/eval-harness.mjs +0 -59
- package/lib/hooks/bootstrap-guard.mjs +0 -90
- package/lib/hooks/console-warn.mjs +0 -43
- package/lib/hooks/continuation-enforcer.mjs +0 -72
- package/lib/hooks/drive-guard.mjs +0 -89
- package/lib/hooks/mcp-task-scope.mjs +0 -47
- package/lib/hooks/task-completed-guard.mjs +0 -43
- package/lib/hooks/teammate-idle-guard.mjs +0 -54
- package/lib/hooks/workflow-guard.mjs +0 -62
- package/lib/prompt-composer.mjs +0 -196
- package/lib/review.mjs +0 -429
- package/lib/server/static/app.js +0 -841
- package/lib/telemetry/langfuse-model-sync.mjs +0 -270
- package/rules/common/hooks.md +0 -35
- package/rules/zh/README.md +0 -113
- package/rules/zh/agents.md +0 -55
- package/rules/zh/code-review.md +0 -129
- package/rules/zh/coding-style.md +0 -53
- package/rules/zh/development-workflow.md +0 -49
- package/rules/zh/git-workflow.md +0 -29
- package/rules/zh/hooks.md +0 -35
- package/rules/zh/patterns.md +0 -36
- package/rules/zh/performance.md +0 -60
- package/rules/zh/security.md +0 -34
- package/rules/zh/testing.md +0 -34
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/embed/config.mjs — embed mode configuration schema and loader.
|
|
3
|
+
*
|
|
4
|
+
* Reads embed.yaml from ~/.construct/embed.yaml (or a supplied path) and validates
|
|
5
|
+
* the file against the expected schema. Returns a parsed, normalized config object.
|
|
6
|
+
*
|
|
7
|
+
* Schema:
|
|
8
|
+
*
|
|
9
|
+
* sources:
|
|
10
|
+
* - provider: github
|
|
11
|
+
* repo: owner/name # single repo
|
|
12
|
+
* repos: [owner/a, owner/b] # OR list of repos
|
|
13
|
+
* refs: [prs, issues, commits]
|
|
14
|
+
* branch: main # for commits ref
|
|
15
|
+
* limit: 25
|
|
16
|
+
*
|
|
17
|
+
* - provider: slack
|
|
18
|
+
* channel: C12345678 # single channel ID or name
|
|
19
|
+
* channels: [general, eng] # OR list
|
|
20
|
+
* refs: [messages]
|
|
21
|
+
* oldest: 86400 # seconds of history (default 24h)
|
|
22
|
+
*
|
|
23
|
+
* - provider: linear
|
|
24
|
+
* team: ENG # team key (optional)
|
|
25
|
+
* project: My Project # project name filter (optional)
|
|
26
|
+
* refs: [issues, cycles]
|
|
27
|
+
* states: [Todo, In Progress]
|
|
28
|
+
*
|
|
29
|
+
* - provider: jira
|
|
30
|
+
* project: PROJ # project key
|
|
31
|
+
* projects: [PROJ, INFRA] # OR list
|
|
32
|
+
* refs: [issues, sprints]
|
|
33
|
+
* jql: "project = PROJ AND sprint in openSprints()" # override JQL
|
|
34
|
+
*
|
|
35
|
+
* outputs:
|
|
36
|
+
* - type: markdown # write snapshot to a file
|
|
37
|
+
* path: .cx/snapshot.md
|
|
38
|
+
* - type: slack # post to a Slack channel
|
|
39
|
+
* provider: slack
|
|
40
|
+
* channel: C12345
|
|
41
|
+
* - type: dashboard # push to the Construct dashboard
|
|
42
|
+
*
|
|
43
|
+
* approval:
|
|
44
|
+
* require: # action types that need approval before dispatch
|
|
45
|
+
* - issue.create
|
|
46
|
+
* - pr.merge
|
|
47
|
+
* timeout_ms: 3600000 # how long to wait before auto-expiring (default: 1h)
|
|
48
|
+
* fallback: reject # reject | proceed (default: reject)
|
|
49
|
+
*
|
|
50
|
+
* snapshot:
|
|
51
|
+
* intervalMs: 300000 # how often to regenerate full snapshot (default: 5m)
|
|
52
|
+
* maxItems: 100 # max items per source in snapshot
|
|
53
|
+
*/
|
|
54
|
+
|
|
55
|
+
import fs from 'node:fs';
|
|
56
|
+
import os from 'node:os';
|
|
57
|
+
import path from 'node:path';
|
|
58
|
+
|
|
59
|
+
const DEFAULTS = {
|
|
60
|
+
snapshot: { intervalMs: 300_000, maxItems: 100 },
|
|
61
|
+
approval: { require: [], timeout_ms: 3_600_000, fallback: 'reject' },
|
|
62
|
+
roles: { primary: null, secondary: null },
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Target types for embed output routing.
|
|
67
|
+
*
|
|
68
|
+
* targets:
|
|
69
|
+
* - type: repo
|
|
70
|
+
* ref: github.com/org/project-x # remote — interact via provider
|
|
71
|
+
* - type: repo
|
|
72
|
+
* path: /Users/user/Github/local-project # local filesystem
|
|
73
|
+
* - type: repo
|
|
74
|
+
* ref: github.com/org/project-y
|
|
75
|
+
* path: /Users/user/Github/project-y # both — prefer local, fall back to remote
|
|
76
|
+
* - type: workspace # always present as fallback
|
|
77
|
+
* path: ~/.construct/workspace
|
|
78
|
+
*
|
|
79
|
+
* roles:
|
|
80
|
+
* primary: product-manager # shapes embed analysis lens
|
|
81
|
+
* secondary: architect # persistent background concern
|
|
82
|
+
*/
|
|
83
|
+
export const DEFAULT_WORKSPACE_PATH = path.join(os.homedir(), '.construct', 'workspace');
|
|
84
|
+
|
|
85
|
+
export const WORKSPACE_DOCS_LANES = ['adrs', 'prds', 'memos', 'notes', 'intake'];
|
|
86
|
+
|
|
87
|
+
export const OPERATING_PROFILE_VERSION = 'embed-operating-profile/v1';
|
|
88
|
+
|
|
89
|
+
export const DEFAULT_OPERATING_PROFILE = {
|
|
90
|
+
schemaVersion: OPERATING_PROFILE_VERSION,
|
|
91
|
+
mode: 'embed',
|
|
92
|
+
mission: 'Maintain situational awareness across connected project systems and turn weak signals into actionable plans, drafts, risks, and approval-queued work.',
|
|
93
|
+
strategy: {
|
|
94
|
+
defaultPosture: 'assistive',
|
|
95
|
+
autonomy: 'read-first',
|
|
96
|
+
writePolicy: 'approval-required-for-high-risk',
|
|
97
|
+
dependencyPolicy: 'zero-deps',
|
|
98
|
+
precedence: 'approval rules override operating profile preferences',
|
|
99
|
+
},
|
|
100
|
+
focalResources: [
|
|
101
|
+
{ type: 'plan', path: 'plan.md', purpose: 'active implementation plan' },
|
|
102
|
+
{ type: 'architecture', path: 'docs/concepts/architecture.md', purpose: 'system architecture and operating model' },
|
|
103
|
+
{ type: 'knowledge', path: '.cx/knowledge', purpose: 'typed project knowledge and ingested evidence' },
|
|
104
|
+
{ type: 'runtime', path: '.cx/roadmap.md', purpose: 'generated roadmap output' },
|
|
105
|
+
],
|
|
106
|
+
approach: [
|
|
107
|
+
'read configured sources',
|
|
108
|
+
'compare observed activity with mission and focal resources',
|
|
109
|
+
'identify gaps, risks, stale decisions, and missing artifacts',
|
|
110
|
+
'draft roadmaps, PRDs, RFCs, ADRs, memos, status updates, summaries, and wireframes',
|
|
111
|
+
'route high-risk writes through the approval queue',
|
|
112
|
+
],
|
|
113
|
+
authority: {
|
|
114
|
+
read: 'autonomous',
|
|
115
|
+
summarize: 'autonomous',
|
|
116
|
+
draftArtifacts: 'autonomous',
|
|
117
|
+
createIssues: 'approval-queued',
|
|
118
|
+
updateIssues: 'approval-queued',
|
|
119
|
+
publishDocs: 'approval-queued',
|
|
120
|
+
externalPost: 'approval-queued',
|
|
121
|
+
repoWrites: 'approval-queued',
|
|
122
|
+
},
|
|
123
|
+
responsibilities: {
|
|
124
|
+
gapAnalysis: true,
|
|
125
|
+
issueLifecycle: { create: 'approval-queued', update: 'approval-queued', dedupe: 'recommend' },
|
|
126
|
+
artifacts: {
|
|
127
|
+
roadmaps: 'generate',
|
|
128
|
+
prds: 'draft',
|
|
129
|
+
rfcs: 'draft',
|
|
130
|
+
adrs: 'draft',
|
|
131
|
+
memos: 'draft',
|
|
132
|
+
statusUpdates: 'generate',
|
|
133
|
+
summaries: 'generate',
|
|
134
|
+
wireframes: 'draft',
|
|
135
|
+
risks: 'generate',
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
riskModel: {
|
|
139
|
+
detect: ['blocked work', 'missing owner', 'stale cadence', 'source errors', 'missing focal resource', 'unapproved high-risk action'],
|
|
140
|
+
dashboardStatus: { ok: 'no known gaps', attention: 'gaps or source errors exist', blocked: 'required focal resource or approval path missing' },
|
|
141
|
+
},
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
export const EMPTY_CONFIG = {
|
|
145
|
+
sources: [],
|
|
146
|
+
outputs: [],
|
|
147
|
+
targets: [{ type: 'workspace', path: DEFAULT_WORKSPACE_PATH }],
|
|
148
|
+
roles: { ...DEFAULTS.roles },
|
|
149
|
+
snapshot: { ...DEFAULTS.snapshot },
|
|
150
|
+
approval: { ...DEFAULTS.approval },
|
|
151
|
+
operatingProfile: DEFAULT_OPERATING_PROFILE,
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
function deepMerge(base, override) {
|
|
155
|
+
if (!override || typeof override !== 'object' || Array.isArray(override)) return base;
|
|
156
|
+
const merged = { ...base };
|
|
157
|
+
for (const [key, value] of Object.entries(override)) {
|
|
158
|
+
if (Array.isArray(value)) merged[key] = value;
|
|
159
|
+
else if (value && typeof value === 'object' && base[key] && typeof base[key] === 'object' && !Array.isArray(base[key])) {
|
|
160
|
+
merged[key] = deepMerge(base[key], value);
|
|
161
|
+
} else {
|
|
162
|
+
merged[key] = value;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return merged;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export function loadEmbedConfig(configPath) {
|
|
169
|
+
if (!fs.existsSync(configPath)) {
|
|
170
|
+
throw new Error(`Embed config not found: ${configPath}`);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const raw = fs.readFileSync(configPath, 'utf8');
|
|
174
|
+
// Minimal YAML subset parser: handles the flat-ish schema we use.
|
|
175
|
+
// Full YAML parsing requires a dep; we use a hand-rolled parser for zero-dep core.
|
|
176
|
+
const parsed = parseEmbedYaml(raw);
|
|
177
|
+
return normalize(parsed);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export function normalize(raw) {
|
|
181
|
+
const config = {
|
|
182
|
+
sources: [],
|
|
183
|
+
outputs: [],
|
|
184
|
+
targets: [],
|
|
185
|
+
roles: { ...DEFAULTS.roles, ...(raw.roles ?? {}) },
|
|
186
|
+
snapshot: { ...DEFAULTS.snapshot, ...(raw.snapshot ?? {}) },
|
|
187
|
+
approval: { ...DEFAULTS.approval, ...(raw.approval ?? {}) },
|
|
188
|
+
operatingProfile: deepMerge(DEFAULT_OPERATING_PROFILE, raw.operatingProfile ?? {}),
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
for (const src of raw.sources ?? []) {
|
|
192
|
+
if (!src.provider) throw new Error('Each source must have a "provider" field');
|
|
193
|
+
config.sources.push({
|
|
194
|
+
...src,
|
|
195
|
+
provider: src.provider,
|
|
196
|
+
refs: Array.isArray(src.refs) ? src.refs : [src.refs ?? 'status'],
|
|
197
|
+
intervalMs: src.intervalMs ?? 60_000,
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
for (const out of raw.outputs ?? []) {
|
|
202
|
+
if (!out.type) throw new Error('Each output must have a "type" field');
|
|
203
|
+
config.outputs.push({ ...out });
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Targets: explicit list, always has workspace fallback
|
|
207
|
+
for (const t of raw.targets ?? []) {
|
|
208
|
+
if (!t.type) throw new Error('Each target must have a "type" field');
|
|
209
|
+
config.targets.push({ ...t });
|
|
210
|
+
}
|
|
211
|
+
// Ensure workspace fallback is always present
|
|
212
|
+
if (!config.targets.some((t) => t.type === 'workspace')) {
|
|
213
|
+
config.targets.push({ type: 'workspace', path: DEFAULT_WORKSPACE_PATH });
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return config;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Hand-rolled parser for the embed YAML schema.
|
|
221
|
+
* Supports: top-level keys, nested objects, lists of objects, scalar values.
|
|
222
|
+
* Not a general YAML parser — only handles what embed.yaml uses.
|
|
223
|
+
*/
|
|
224
|
+
export function parseEmbedYaml(text) {
|
|
225
|
+
const lines = text.split('\n');
|
|
226
|
+
const root = {};
|
|
227
|
+
let i = 0;
|
|
228
|
+
|
|
229
|
+
function peek() { return lines[i]; }
|
|
230
|
+
function advance() { return lines[i++]; }
|
|
231
|
+
function indent(line) { return line.match(/^(\s*)/)[1].length; }
|
|
232
|
+
function isComment(line) { return line.trimStart().startsWith('#'); }
|
|
233
|
+
function isEmpty(line) { return line.trim() === ''; }
|
|
234
|
+
|
|
235
|
+
function parseValue(val) {
|
|
236
|
+
const v = val.trim();
|
|
237
|
+
if (v === 'true') return true;
|
|
238
|
+
if (v === 'false') return false;
|
|
239
|
+
if (v === 'null' || v === '~' || v === '') return null;
|
|
240
|
+
if (v === '[]') return [];
|
|
241
|
+
if (v === '{}') return {};
|
|
242
|
+
// Inline flow list: [a, b, c]
|
|
243
|
+
if (v.startsWith('[') && v.endsWith(']')) {
|
|
244
|
+
const inner = v.slice(1, -1).trim();
|
|
245
|
+
if (inner === '') return [];
|
|
246
|
+
return inner.split(',').map((s) => parseValue(s.trim()));
|
|
247
|
+
}
|
|
248
|
+
const n = Number(v);
|
|
249
|
+
if (!Number.isNaN(n) && v !== '') return n;
|
|
250
|
+
// Strip quotes
|
|
251
|
+
if ((v.startsWith('"') && v.endsWith('"')) || (v.startsWith("'") && v.endsWith("'"))) {
|
|
252
|
+
return v.slice(1, -1);
|
|
253
|
+
}
|
|
254
|
+
return v;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function parseBlock(baseIndent) {
|
|
258
|
+
const obj = {};
|
|
259
|
+
while (i < lines.length) {
|
|
260
|
+
const line = peek();
|
|
261
|
+
if (isEmpty(line) || isComment(line)) { advance(); continue; }
|
|
262
|
+
const lineIndent = indent(line);
|
|
263
|
+
if (lineIndent < baseIndent) break;
|
|
264
|
+
if (lineIndent > baseIndent) { advance(); continue; } // skip malformed
|
|
265
|
+
|
|
266
|
+
advance();
|
|
267
|
+
const trimmed = line.trim();
|
|
268
|
+
|
|
269
|
+
// List item
|
|
270
|
+
if (trimmed.startsWith('- ')) {
|
|
271
|
+
// Not expected at block level — handled by parseList
|
|
272
|
+
break;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const colonIdx = trimmed.indexOf(':');
|
|
276
|
+
if (colonIdx === -1) continue;
|
|
277
|
+
const key = trimmed.slice(0, colonIdx).trim();
|
|
278
|
+
const rest = trimmed.slice(colonIdx + 1);
|
|
279
|
+
|
|
280
|
+
if (rest.trim() === '') {
|
|
281
|
+
// Next lines are either a list or nested object
|
|
282
|
+
const nextLine = lines[i];
|
|
283
|
+
if (!nextLine) { obj[key] = null; continue; }
|
|
284
|
+
const nextTrimmed = nextLine.trimStart();
|
|
285
|
+
if (nextTrimmed.startsWith('- ')) {
|
|
286
|
+
obj[key] = parseList(lineIndent + 2);
|
|
287
|
+
} else {
|
|
288
|
+
obj[key] = parseBlock(lineIndent + 2);
|
|
289
|
+
}
|
|
290
|
+
} else {
|
|
291
|
+
obj[key] = parseValue(rest);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
return obj;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function parseList(baseIndent) {
|
|
298
|
+
const arr = [];
|
|
299
|
+
while (i < lines.length) {
|
|
300
|
+
const line = peek();
|
|
301
|
+
if (isEmpty(line) || isComment(line)) { advance(); continue; }
|
|
302
|
+
const lineIndent = indent(line);
|
|
303
|
+
if (lineIndent < baseIndent) break;
|
|
304
|
+
|
|
305
|
+
const trimmed = line.trim();
|
|
306
|
+
if (!trimmed.startsWith('- ')) break;
|
|
307
|
+
advance();
|
|
308
|
+
|
|
309
|
+
const rest = trimmed.slice(2).trim();
|
|
310
|
+
if (rest === '') {
|
|
311
|
+
// Multi-line list item object
|
|
312
|
+
arr.push(parseBlock(lineIndent + 2));
|
|
313
|
+
} else if (rest.includes(':')) {
|
|
314
|
+
// Inline object in list: "- provider: github"
|
|
315
|
+
const item = {};
|
|
316
|
+
const colonIdx = rest.indexOf(':');
|
|
317
|
+
const key = rest.slice(0, colonIdx).trim();
|
|
318
|
+
const val = rest.slice(colonIdx + 1).trim();
|
|
319
|
+
item[key] = val === '' ? parseBlock(lineIndent + 2) : parseValue(val);
|
|
320
|
+
// Consume continuation keys at same level
|
|
321
|
+
while (i < lines.length) {
|
|
322
|
+
const next = lines[i];
|
|
323
|
+
if (isEmpty(next) || isComment(next)) { i++; continue; }
|
|
324
|
+
if (indent(next) <= lineIndent) break;
|
|
325
|
+
i++;
|
|
326
|
+
const nt = next.trim();
|
|
327
|
+
const ci = nt.indexOf(':');
|
|
328
|
+
if (ci === -1) continue;
|
|
329
|
+
const nk = nt.slice(0, ci).trim();
|
|
330
|
+
const nv = nt.slice(ci + 1).trim();
|
|
331
|
+
if (nv === '') {
|
|
332
|
+
const nextNext = lines[i];
|
|
333
|
+
if (nextNext && nextNext.trimStart().startsWith('- ')) {
|
|
334
|
+
item[nk] = parseList(indent(next) + 2);
|
|
335
|
+
} else {
|
|
336
|
+
item[nk] = parseBlock(indent(next) + 2);
|
|
337
|
+
}
|
|
338
|
+
} else {
|
|
339
|
+
item[nk] = parseValue(nv);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
arr.push(item);
|
|
343
|
+
} else {
|
|
344
|
+
arr.push(parseValue(rest));
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
return arr;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
return parseBlock(0);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
export function defaultEmbedConfigPath() {
|
|
354
|
+
return path.join(os.homedir(), '.construct', 'embed.yaml');
|
|
355
|
+
}
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/embed/conflict-detection.mjs — Detect overlaps and contradictions
|
|
3
|
+
* between new intake signals and existing PRDs, ADRs, RFCs.
|
|
4
|
+
*
|
|
5
|
+
* Uses semantic similarity (via lib/embed/semantic.mjs) to compare new
|
|
6
|
+
* signals against the corpus of existing artifacts. Flags potential
|
|
7
|
+
* conflicts when similarity exceeds configurable thresholds.
|
|
8
|
+
*
|
|
9
|
+
* Two detection modes:
|
|
10
|
+
* 1. Overlap detection — new signal addresses same topic as existing artifact
|
|
11
|
+
* 2. Contradiction detection — new signal contradicts a documented decision
|
|
12
|
+
*
|
|
13
|
+
* Storage:
|
|
14
|
+
* ~/.cx/product-intel/artifact-index.json — cached artifact embeddings
|
|
15
|
+
* Rebuilt on first use or when artifact file count changes.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync, statSync } from 'node:fs';
|
|
19
|
+
import { join } from 'node:path';
|
|
20
|
+
import { homedir } from 'node:os';
|
|
21
|
+
import { embed, cosineSimilarity, extractTextFromPacket } from './semantic.mjs';
|
|
22
|
+
import { listArtifacts } from './artifact.mjs';
|
|
23
|
+
|
|
24
|
+
const INDEX_DIR = join(homedir(), '.cx', 'product-intel');
|
|
25
|
+
const INDEX_FILE = join(INDEX_DIR, 'artifact-index.json');
|
|
26
|
+
|
|
27
|
+
const OVERLAP_THRESHOLD = 0.6;
|
|
28
|
+
const CONTRADICTION_THRESHOLD = 0.7;
|
|
29
|
+
const STALE_INDEX_HOURS = 24;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Ensure index directory exists.
|
|
33
|
+
*/
|
|
34
|
+
function ensureDir() {
|
|
35
|
+
if (!existsSync(INDEX_DIR)) mkdirSync(INDEX_DIR, { recursive: true });
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
let _artifactIndex = null;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Build or load the artifact index (cached by count + mtime).
|
|
42
|
+
* Index maps artifact path → { text, embedding, type, title }.
|
|
43
|
+
*
|
|
44
|
+
* @param {object} [opts]
|
|
45
|
+
* @param {string} [opts.rootDir] - Project root
|
|
46
|
+
* @param {boolean} [opts.forceRebuild] - Force rebuild even if cache is fresh
|
|
47
|
+
* @returns {Promise<Array<{ path: string, type: string, title: string, text: string, embedding: Float32Array|null }>>}
|
|
48
|
+
*/
|
|
49
|
+
export async function buildArtifactIndex({ rootDir, forceRebuild = false } = {}) {
|
|
50
|
+
const root = rootDir || process.cwd();
|
|
51
|
+
ensureDir();
|
|
52
|
+
|
|
53
|
+
const artifacts = listArtifacts({ rootDir: root });
|
|
54
|
+
const cacheKey = artifacts.map(a => `${a.relativePath}:${statSync(a.path).mtimeMs}`).join('|');
|
|
55
|
+
const cacheHash = cacheKey.length;
|
|
56
|
+
|
|
57
|
+
// Check if cached index is fresh
|
|
58
|
+
if (!forceRebuild && existsSync(INDEX_FILE)) {
|
|
59
|
+
try {
|
|
60
|
+
const cached = JSON.parse(readFileSync(INDEX_FILE, 'utf8'));
|
|
61
|
+
if (cached.cacheHash === cacheHash && cached.index?.length === artifacts.length) {
|
|
62
|
+
const age = Date.now() - new Date(cached.builtAt).getTime();
|
|
63
|
+
if (age < STALE_INDEX_HOURS * 60 * 60 * 1000) {
|
|
64
|
+
_artifactIndex = cached.index;
|
|
65
|
+
return _artifactIndex;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
} catch { /* rebuild */ }
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Build index — read artifact files, extract text, generate embeddings
|
|
72
|
+
const index = [];
|
|
73
|
+
for (const artifact of artifacts) {
|
|
74
|
+
try {
|
|
75
|
+
const content = readFileSync(artifact.path, 'utf8');
|
|
76
|
+
const text = extractArtifactText(content, artifact);
|
|
77
|
+
const vector = await embed(text);
|
|
78
|
+
index.push({
|
|
79
|
+
path: artifact.relativePath,
|
|
80
|
+
type: artifact.type,
|
|
81
|
+
title: artifact.title,
|
|
82
|
+
status: artifact.status,
|
|
83
|
+
text: text.slice(0, 2000),
|
|
84
|
+
embedding: vector ? Array.from(vector) : null,
|
|
85
|
+
});
|
|
86
|
+
} catch {
|
|
87
|
+
// skip unreadable artifacts
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
_artifactIndex = index;
|
|
92
|
+
|
|
93
|
+
// Persist index (without large embeddings if too many)
|
|
94
|
+
const persistent = index.map(a => ({
|
|
95
|
+
...a,
|
|
96
|
+
embedding: a.embedding ? Array.from(a.embedding) : null,
|
|
97
|
+
}));
|
|
98
|
+
|
|
99
|
+
writeFileSync(INDEX_FILE, JSON.stringify({
|
|
100
|
+
cacheHash,
|
|
101
|
+
builtAt: new Date().toISOString(),
|
|
102
|
+
artifactCount: artifacts.length,
|
|
103
|
+
index: persistent,
|
|
104
|
+
}, null, 2) + '\n');
|
|
105
|
+
|
|
106
|
+
return index;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Extract searchable text from an artifact file.
|
|
111
|
+
*
|
|
112
|
+
* @param {string} content - Raw markdown content
|
|
113
|
+
* @param {object} artifact - Artifact metadata
|
|
114
|
+
* @returns {string} Cleaned text for embedding
|
|
115
|
+
*/
|
|
116
|
+
function extractArtifactText(content, artifact) {
|
|
117
|
+
// Strip frontmatter
|
|
118
|
+
const body = content.replace(/^---[\s\S]*?---\n*/, '');
|
|
119
|
+
|
|
120
|
+
// Extract key sections based on type
|
|
121
|
+
const title = artifact.title || '';
|
|
122
|
+
const type = artifact.type || '';
|
|
123
|
+
|
|
124
|
+
const sections = [];
|
|
125
|
+
|
|
126
|
+
if (type === 'adr') {
|
|
127
|
+
const context = body.match(/## Context\s*\n([\s\S]*?)(?=^## )/m)?.[1] || '';
|
|
128
|
+
const decision = body.match(/## Decision\s*\n([\s\S]*?)(?=^## )/m)?.[1] || '';
|
|
129
|
+
sections.push(title, context, decision);
|
|
130
|
+
} else if (type === 'prd') {
|
|
131
|
+
const problem = body.match(/## Problem\s*\n([\s\S]*?)(?=^## )/m)?.[1] || '';
|
|
132
|
+
const goals = body.match(/## Goals\s*\n([\s\S]*?)(?=^## )/m)?.[1] || '';
|
|
133
|
+
sections.push(title, problem, goals);
|
|
134
|
+
} else if (type === 'rfc') {
|
|
135
|
+
const summary = body.match(/## Summary\s*\n([\s\S]*?)(?=^## )/m)?.[1] || '';
|
|
136
|
+
const motivation = body.match(/## Motivation\s*\n([\s\S]*?)(?=^## )/m)?.[1] || '';
|
|
137
|
+
sections.push(title, summary, motivation);
|
|
138
|
+
} else {
|
|
139
|
+
// Generic — use first 2000 chars
|
|
140
|
+
sections.push(title, body.slice(0, 2000));
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return sections.filter(Boolean).join('\n').slice(0, 3000);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Check a new intake signal for potential conflicts with existing artifacts.
|
|
148
|
+
*
|
|
149
|
+
* @param {object} packet - Intake packet with triage, excerpt
|
|
150
|
+
* @param {object} [opts]
|
|
151
|
+
* @param {string} [opts.rootDir]
|
|
152
|
+
* @param {number} [opts.overlapThreshold=0.6]
|
|
153
|
+
* @param {number} [opts.contradictionThreshold=0.7]
|
|
154
|
+
* @returns {Promise<Array<{ type: 'overlap'|'contradiction', severity: string, artifact: string, similarity: number, summary: string }>>}
|
|
155
|
+
*/
|
|
156
|
+
export async function detectConflicts(packet, { rootDir, overlapThreshold = OVERLAP_THRESHOLD, contradictionThreshold = CONTRADICTION_THRESHOLD } = {}) {
|
|
157
|
+
const text = extractTextFromPacket(packet);
|
|
158
|
+
if (!text) return [];
|
|
159
|
+
|
|
160
|
+
const signalVec = await embed(text);
|
|
161
|
+
if (!signalVec) return [];
|
|
162
|
+
|
|
163
|
+
const index = await buildArtifactIndex({ rootDir });
|
|
164
|
+
const conflicts = [];
|
|
165
|
+
|
|
166
|
+
for (const entry of index) {
|
|
167
|
+
if (!entry.embedding) continue;
|
|
168
|
+
const entryVec = new Float32Array(entry.embedding);
|
|
169
|
+
const sim = cosineSimilarity(signalVec, entryVec);
|
|
170
|
+
|
|
171
|
+
if (sim >= overlapThreshold) {
|
|
172
|
+
// Determine if this is overlap or potential contradiction
|
|
173
|
+
const isAdr = entry.type === 'adr';
|
|
174
|
+
const isContradictory = isAdr && sim >= contradictionThreshold;
|
|
175
|
+
|
|
176
|
+
conflicts.push({
|
|
177
|
+
type: isContradictory ? 'contradiction' : 'overlap',
|
|
178
|
+
severity: sim >= 0.8 ? 'high' : sim >= 0.7 ? 'medium' : 'low',
|
|
179
|
+
artifact: entry.path,
|
|
180
|
+
artifactType: entry.type,
|
|
181
|
+
title: entry.title,
|
|
182
|
+
status: entry.status,
|
|
183
|
+
similarity: Number(sim.toFixed(3)),
|
|
184
|
+
summary: isContradictory
|
|
185
|
+
? `Potential contradiction with ${entry.type.toUpperCase()}-${entry.title} (similarity: ${sim.toFixed(2)})`
|
|
186
|
+
: `Overlaps with ${entry.type.toUpperCase()}-${entry.title} (similarity: ${sim.toFixed(2)})`,
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return conflicts.sort((a, b) => b.similarity - a.similarity);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Scan all pending intake items for inter-item conflicts
|
|
196
|
+
* (two items addressing same topic from different angles).
|
|
197
|
+
*
|
|
198
|
+
* @param {Array<object>} pendingPackets
|
|
199
|
+
* @param {object} [opts]
|
|
200
|
+
* @param {number} [opts.threshold=0.7]
|
|
201
|
+
* @returns {Promise<Array<{ itemA: string, itemB: string, similarity: number, summary: string }>>}
|
|
202
|
+
*/
|
|
203
|
+
export async function detectInterItemConflicts(pendingPackets, { threshold = 0.7 } = {}) {
|
|
204
|
+
if (pendingPackets.length < 2) return [];
|
|
205
|
+
|
|
206
|
+
const items = [];
|
|
207
|
+
for (const packet of pendingPackets) {
|
|
208
|
+
const text = extractTextFromPacket(packet);
|
|
209
|
+
if (!text) continue;
|
|
210
|
+
const vector = await embed(text);
|
|
211
|
+
if (vector) {
|
|
212
|
+
items.push({ id: packet.id, text, embedding: vector });
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const conflicts = [];
|
|
217
|
+
for (let i = 1; i < items.length; i++) {
|
|
218
|
+
for (let j = 0; j < i; j++) {
|
|
219
|
+
const sim = cosineSimilarity(items[i].embedding, items[j].embedding);
|
|
220
|
+
if (sim >= threshold) {
|
|
221
|
+
conflicts.push({
|
|
222
|
+
itemA: items[i].id,
|
|
223
|
+
itemB: items[j].id,
|
|
224
|
+
similarity: Number(sim.toFixed(3)),
|
|
225
|
+
summary: `Intake items conflict: ${items[i].id.slice(0, 20)} vs ${items[j].id.slice(0, 20)} (${(sim * 100).toFixed(0)}% similar)`,
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return conflicts.sort((a, b) => b.similarity - a.similarity);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Reset the cached artifact index (call when artifacts change).
|
|
236
|
+
*/
|
|
237
|
+
export function resetArtifactIndex() {
|
|
238
|
+
_artifactIndex = null;
|
|
239
|
+
if (existsSync(INDEX_FILE)) {
|
|
240
|
+
const { unlinkSync } = require('node:fs');
|
|
241
|
+
try { unlinkSync(INDEX_FILE); } catch { /* ignore */ }
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Get artifact index stats.
|
|
247
|
+
*
|
|
248
|
+
* @param {object} [opts]
|
|
249
|
+
* @param {string} [opts.rootDir]
|
|
250
|
+
* @returns {Promise<{ total: number, byType: object, cached: boolean }>}
|
|
251
|
+
*/
|
|
252
|
+
export async function artifactIndexStats({ rootDir } = {}) {
|
|
253
|
+
const index = await buildArtifactIndex({ rootDir });
|
|
254
|
+
const byType = {};
|
|
255
|
+
for (const entry of index) {
|
|
256
|
+
byType[entry.type] = (byType[entry.type] || 0) + 1;
|
|
257
|
+
}
|
|
258
|
+
return {
|
|
259
|
+
total: index.length,
|
|
260
|
+
byType,
|
|
261
|
+
indexed: index.filter(e => e.embedding).length,
|
|
262
|
+
cached: _artifactIndex !== null,
|
|
263
|
+
};
|
|
264
|
+
}
|