@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,391 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/observation-store.mjs — Role-scoped observation store with vector indexing.
|
|
3
|
+
*
|
|
4
|
+
* Stores distilled insights that specialists learn during work:
|
|
5
|
+
* - patterns, anti-patterns, dependency relationships, decisions, insights
|
|
6
|
+
* - each scoped to a role (cx-engineer, cx-architect, etc.)
|
|
7
|
+
* - vector-indexed for semantic search via hashing-bow-v1
|
|
8
|
+
*
|
|
9
|
+
* Storage layout:
|
|
10
|
+
* .cx/observations/index.json — lightweight listing for fast filtering
|
|
11
|
+
* .cx/observations/<id>.json — full observation record
|
|
12
|
+
* .cx/observations/vectors.json — local vector index for semantic search
|
|
13
|
+
*/
|
|
14
|
+
import crypto from 'node:crypto';
|
|
15
|
+
import fs from 'node:fs';
|
|
16
|
+
import path from 'node:path';
|
|
17
|
+
import { cosineSimilarity, rankByBm25 } from './storage/embeddings.mjs';
|
|
18
|
+
import { embedSync as embedText } from './storage/embeddings-legacy.mjs';
|
|
19
|
+
import { embedText as embedTextEngine } from './storage/embeddings-engine.mjs';
|
|
20
|
+
import { VectorClient } from './storage/vector-client.mjs';
|
|
21
|
+
import { withFileLockSync } from './storage/file-lock.mjs';
|
|
22
|
+
|
|
23
|
+
const OBS_DIR = '.cx/observations';
|
|
24
|
+
const INDEX_FILE = 'index.json';
|
|
25
|
+
const VECTORS_FILE = 'vectors.json';
|
|
26
|
+
const MAX_INDEX = 1000;
|
|
27
|
+
const MAX_SUMMARY = 500;
|
|
28
|
+
const MAX_CONTENT = 2000;
|
|
29
|
+
const MAX_TAGS = 10;
|
|
30
|
+
// Size guardrail: surfaces in `construct doctor` when the observation tree
|
|
31
|
+
// exceeds this byte cap. Doesn't drop data — the operator decides whether to
|
|
32
|
+
// run `construct memory consolidate` (which prunes archive) or raise the cap.
|
|
33
|
+
const MAX_BYTES = 50 * 1024 * 1024;
|
|
34
|
+
|
|
35
|
+
const VALID_CATEGORIES = new Set([
|
|
36
|
+
'pattern', 'anti-pattern', 'dependency', 'decision', 'insight', 'session-summary',
|
|
37
|
+
]);
|
|
38
|
+
|
|
39
|
+
function ensureDir(dir) {
|
|
40
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function obsDir(rootDir) {
|
|
44
|
+
return path.join(rootDir, OBS_DIR);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function indexPath(rootDir) {
|
|
48
|
+
return path.join(obsDir(rootDir), INDEX_FILE);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function vectorsPath(rootDir) {
|
|
52
|
+
return path.join(obsDir(rootDir), VECTORS_FILE);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function generateId() {
|
|
56
|
+
const ts = Date.now().toString(36);
|
|
57
|
+
const rand = crypto.randomBytes(4).toString('hex');
|
|
58
|
+
return `obs-${ts}-${rand}`;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function clamp(str, max) {
|
|
62
|
+
if (!str || str.length <= max) return str || null;
|
|
63
|
+
return str.slice(0, max - 1) + '\u2026';
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function readIndex(rootDir) {
|
|
67
|
+
const p = indexPath(rootDir);
|
|
68
|
+
if (!fs.existsSync(p)) return [];
|
|
69
|
+
try {
|
|
70
|
+
return JSON.parse(fs.readFileSync(p, 'utf8'));
|
|
71
|
+
} catch {
|
|
72
|
+
return [];
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function logCapDrop(rootDir, kind, dropped, total) {
|
|
77
|
+
if (dropped <= 0) return;
|
|
78
|
+
try {
|
|
79
|
+
const logPath = path.join(rootDir, '.cx', 'observation-cap-warnings.jsonl');
|
|
80
|
+
fs.mkdirSync(path.dirname(logPath), { recursive: true });
|
|
81
|
+
const entry = JSON.stringify({
|
|
82
|
+
ts: new Date().toISOString(),
|
|
83
|
+
kind,
|
|
84
|
+
dropped,
|
|
85
|
+
total,
|
|
86
|
+
cap: MAX_INDEX,
|
|
87
|
+
remedy: 'run `construct memory consolidate` or raise --max-stored',
|
|
88
|
+
});
|
|
89
|
+
fs.appendFileSync(logPath, entry + '\n');
|
|
90
|
+
} catch { /* cap warnings are best-effort */ }
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function writeIndex(rootDir, entries) {
|
|
94
|
+
ensureDir(obsDir(rootDir));
|
|
95
|
+
const trimmed = entries.slice(0, MAX_INDEX);
|
|
96
|
+
if (entries.length > MAX_INDEX) {
|
|
97
|
+
logCapDrop(rootDir, 'observation-index', entries.length - MAX_INDEX, entries.length);
|
|
98
|
+
}
|
|
99
|
+
fs.writeFileSync(indexPath(rootDir), JSON.stringify(trimmed, null, 2) + '\n');
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function readVectors(rootDir) {
|
|
103
|
+
const p = vectorsPath(rootDir);
|
|
104
|
+
if (!fs.existsSync(p)) return [];
|
|
105
|
+
try {
|
|
106
|
+
return JSON.parse(fs.readFileSync(p, 'utf8'));
|
|
107
|
+
} catch {
|
|
108
|
+
return [];
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function writeVectors(rootDir, records) {
|
|
113
|
+
ensureDir(obsDir(rootDir));
|
|
114
|
+
if (records.length > MAX_INDEX) {
|
|
115
|
+
logCapDrop(rootDir, 'observation-vectors', records.length - MAX_INDEX, records.length);
|
|
116
|
+
}
|
|
117
|
+
fs.writeFileSync(vectorsPath(rootDir), JSON.stringify(records, null, 2) + '\n');
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Add a new observation and vectorize it for semantic search.
|
|
122
|
+
* Uses SQL + neural embeddings when available, falls back to local JSON + hashing.
|
|
123
|
+
*/
|
|
124
|
+
export async function addObservation(rootDir, {
|
|
125
|
+
role = 'unknown',
|
|
126
|
+
category = 'insight',
|
|
127
|
+
summary = '',
|
|
128
|
+
content = '',
|
|
129
|
+
tags = [],
|
|
130
|
+
project = null,
|
|
131
|
+
confidence = 0.8,
|
|
132
|
+
source = null,
|
|
133
|
+
gitSha = null,
|
|
134
|
+
} = {}) {
|
|
135
|
+
const id = generateId();
|
|
136
|
+
const now = new Date().toISOString();
|
|
137
|
+
|
|
138
|
+
const effectiveCategory = VALID_CATEGORIES.has(category) ? category : 'insight';
|
|
139
|
+
const clampedSummary = clamp(String(summary), MAX_SUMMARY);
|
|
140
|
+
const clampedContent = clamp(String(content), MAX_CONTENT);
|
|
141
|
+
const clampedTags = (Array.isArray(tags) ? tags : []).slice(0, MAX_TAGS).map(String);
|
|
142
|
+
|
|
143
|
+
const record = {
|
|
144
|
+
id,
|
|
145
|
+
role: String(role),
|
|
146
|
+
category: effectiveCategory,
|
|
147
|
+
summary: clampedSummary,
|
|
148
|
+
content: clampedContent,
|
|
149
|
+
tags: clampedTags,
|
|
150
|
+
project: project ? String(project) : null,
|
|
151
|
+
confidence: Math.max(0, Math.min(1, Number(confidence) || 0.8)),
|
|
152
|
+
source: source || null,
|
|
153
|
+
gitSha: gitSha ? String(gitSha).slice(0, 40) : null,
|
|
154
|
+
createdAt: now,
|
|
155
|
+
updatedAt: now,
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
// Lock the per-store index so concurrent writers (CLI + hooks) cannot
|
|
159
|
+
// corrupt the index/vectors JSON pair. The observation JSON file is
|
|
160
|
+
// unique-per-id and doesn't need the lock; the shared structures do.
|
|
161
|
+
ensureDir(obsDir(rootDir));
|
|
162
|
+
fs.writeFileSync(
|
|
163
|
+
path.join(obsDir(rootDir), `${id}.json`),
|
|
164
|
+
JSON.stringify(record, null, 2) + '\n',
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
withFileLockSync(indexPath(rootDir), () => {
|
|
168
|
+
const index = readIndex(rootDir);
|
|
169
|
+
index.unshift({
|
|
170
|
+
id,
|
|
171
|
+
role: record.role,
|
|
172
|
+
category: record.category,
|
|
173
|
+
summary: record.summary,
|
|
174
|
+
project: record.project,
|
|
175
|
+
createdAt: now,
|
|
176
|
+
});
|
|
177
|
+
writeIndex(rootDir, index);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
// Try SQL storage with neural embeddings
|
|
181
|
+
try {
|
|
182
|
+
const client = new VectorClient();
|
|
183
|
+
if (await client.isHealthy() && await client.isPgvectorEnabled()) {
|
|
184
|
+
const searchText = [record.summary, record.content, ...record.tags].filter(Boolean).join(' ');
|
|
185
|
+
const { embedding } = await embedTextEngine(searchText);
|
|
186
|
+
await client.storeObservation({
|
|
187
|
+
...record,
|
|
188
|
+
embedding,
|
|
189
|
+
});
|
|
190
|
+
await client.close();
|
|
191
|
+
return record;
|
|
192
|
+
}
|
|
193
|
+
await client.close();
|
|
194
|
+
} catch {
|
|
195
|
+
// Fall through to local vector storage
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Fallback: local JSON vector index with hashing-bow-v1.
|
|
199
|
+
const searchText = [record.summary, record.content, ...record.tags].filter(Boolean).join(' ');
|
|
200
|
+
const embedding = embedText(searchText);
|
|
201
|
+
withFileLockSync(vectorsPath(rootDir), () => {
|
|
202
|
+
const vectors = readVectors(rootDir);
|
|
203
|
+
vectors.push({ id, embedding, role: record.role, category: record.category, project: record.project });
|
|
204
|
+
writeVectors(rootDir, vectors.slice(-MAX_INDEX));
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
return record;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Search observations using SQL + neural embeddings when available,
|
|
212
|
+
* falling back to hybrid cosine + BM25 on local JSON.
|
|
213
|
+
*/
|
|
214
|
+
export async function searchObservations(rootDir, query, {
|
|
215
|
+
role = null,
|
|
216
|
+
category = null,
|
|
217
|
+
project = null,
|
|
218
|
+
limit = 10,
|
|
219
|
+
} = {}) {
|
|
220
|
+
if (!query) return [];
|
|
221
|
+
|
|
222
|
+
// Try SQL + neural embeddings first
|
|
223
|
+
try {
|
|
224
|
+
const client = new VectorClient();
|
|
225
|
+
if (await client.isHealthy() && await client.isPgvectorEnabled()) {
|
|
226
|
+
const { embedding } = await embedTextEngine(String(query));
|
|
227
|
+
const results = await client.searchObservations({
|
|
228
|
+
project: project || rootDir.split('/').pop(),
|
|
229
|
+
queryEmbedding: embedding,
|
|
230
|
+
limit,
|
|
231
|
+
minSimilarity: 0.1,
|
|
232
|
+
role,
|
|
233
|
+
category,
|
|
234
|
+
});
|
|
235
|
+
await client.close();
|
|
236
|
+
return results.map((r) => ({
|
|
237
|
+
id: r.id,
|
|
238
|
+
role: r.role,
|
|
239
|
+
category: r.category,
|
|
240
|
+
summary: r.summary,
|
|
241
|
+
content: r.content,
|
|
242
|
+
tags: r.tags || [],
|
|
243
|
+
confidence: r.confidence,
|
|
244
|
+
source: r.source,
|
|
245
|
+
createdAt: r.created_at,
|
|
246
|
+
score: r.similarity,
|
|
247
|
+
}));
|
|
248
|
+
}
|
|
249
|
+
await client.close();
|
|
250
|
+
} catch {
|
|
251
|
+
// Fall through to local JSON search
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Fallback: local JSON + hashing-bow-v1
|
|
255
|
+
const queryEmbedding = embedText(String(query));
|
|
256
|
+
let vectors = readVectors(rootDir);
|
|
257
|
+
|
|
258
|
+
if (role) vectors = vectors.filter((v) => v.role === role);
|
|
259
|
+
if (category) vectors = vectors.filter((v) => v.category === category);
|
|
260
|
+
if (project) vectors = vectors.filter((v) => v.project === project);
|
|
261
|
+
|
|
262
|
+
// Cosine pass
|
|
263
|
+
const cosineScored = vectors
|
|
264
|
+
.map((v) => ({
|
|
265
|
+
id: v.id,
|
|
266
|
+
score: cosineSimilarity(queryEmbedding, v.embedding || []),
|
|
267
|
+
}))
|
|
268
|
+
.filter(({ score }) => score > 0.05);
|
|
269
|
+
|
|
270
|
+
// Load full records for BM25 pass
|
|
271
|
+
const candidateIds = new Set(cosineScored.map((v) => v.id));
|
|
272
|
+
const recentIndex = readIndex(rootDir);
|
|
273
|
+
const filtered = role ? recentIndex.filter((e) => e.role === role) : recentIndex;
|
|
274
|
+
const filteredCat = category ? filtered.filter((e) => e.category === category) : filtered;
|
|
275
|
+
const filteredProj = project ? filteredCat.filter((e) => e.project === project) : filteredCat;
|
|
276
|
+
for (const entry of filteredProj.slice(0, Math.max(limit * 3, 30))) candidateIds.add(entry.id);
|
|
277
|
+
|
|
278
|
+
const candidateRecords = [...candidateIds]
|
|
279
|
+
.map((id) => getObservation(rootDir, id))
|
|
280
|
+
.filter(Boolean)
|
|
281
|
+
.map((r) => ({ ...r, text: [r.summary, r.content, ...(r.tags || [])].filter(Boolean).join(' ') }));
|
|
282
|
+
|
|
283
|
+
const bm25Scored = rankByBm25(candidateRecords, query, { limit: limit * 2 });
|
|
284
|
+
|
|
285
|
+
// Merge: highest score wins per id
|
|
286
|
+
const scoreMap = new Map();
|
|
287
|
+
for (const { id, score } of cosineScored) {
|
|
288
|
+
scoreMap.set(id, score);
|
|
289
|
+
}
|
|
290
|
+
for (const item of bm25Scored) {
|
|
291
|
+
const prev = scoreMap.get(item.id) || 0;
|
|
292
|
+
const bm25Max = bm25Scored[0]?.score || 1;
|
|
293
|
+
const normalized = Math.min(item.score / bm25Max, 1);
|
|
294
|
+
scoreMap.set(item.id, Math.max(prev, normalized));
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
return [...scoreMap.entries()]
|
|
298
|
+
.sort((a, b) => b[1] - a[1])
|
|
299
|
+
.slice(0, limit)
|
|
300
|
+
.map(([id, score]) => {
|
|
301
|
+
const record = getObservation(rootDir, id);
|
|
302
|
+
return record ? { ...record, score } : null;
|
|
303
|
+
})
|
|
304
|
+
.filter(Boolean);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* List observations from the index with optional filters.
|
|
309
|
+
*/
|
|
310
|
+
export function listObservations(rootDir, {
|
|
311
|
+
role = null,
|
|
312
|
+
category = null,
|
|
313
|
+
project = null,
|
|
314
|
+
limit = 20,
|
|
315
|
+
} = {}) {
|
|
316
|
+
let entries = readIndex(rootDir);
|
|
317
|
+
if (role) entries = entries.filter((e) => e.role === role);
|
|
318
|
+
if (category) entries = entries.filter((e) => e.category === category);
|
|
319
|
+
if (project) entries = entries.filter((e) => e.project === project);
|
|
320
|
+
return entries.slice(0, limit);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Load a full observation record by ID.
|
|
325
|
+
*/
|
|
326
|
+
export function getObservation(rootDir, id) {
|
|
327
|
+
const filePath = path.join(obsDir(rootDir), `${id}.json`);
|
|
328
|
+
if (!fs.existsSync(filePath)) return null;
|
|
329
|
+
try {
|
|
330
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
331
|
+
} catch {
|
|
332
|
+
return null;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Delete an observation by id.
|
|
338
|
+
*/
|
|
339
|
+
export function deleteObservation(rootDir, id) {
|
|
340
|
+
const filePath = path.join(obsDir(rootDir), `${id}.json`);
|
|
341
|
+
if (fs.existsSync(filePath)) fs.rmSync(filePath, { force: true });
|
|
342
|
+
|
|
343
|
+
withFileLockSync(indexPath(rootDir), () => {
|
|
344
|
+
const index = readIndex(rootDir);
|
|
345
|
+
const filtered = index.filter((e) => e.id !== id);
|
|
346
|
+
if (filtered.length !== index.length) writeIndex(rootDir, filtered);
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
withFileLockSync(vectorsPath(rootDir), () => {
|
|
350
|
+
const vectors = readVectors(rootDir);
|
|
351
|
+
const filteredVec = vectors.filter((v) => v.id !== id);
|
|
352
|
+
if (filteredVec.length !== vectors.length) writeVectors(rootDir, filteredVec);
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
return true;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Count observations, optionally filtered.
|
|
360
|
+
*/
|
|
361
|
+
export function countObservations(rootDir, { role = null, project = null } = {}) {
|
|
362
|
+
let entries = readIndex(rootDir);
|
|
363
|
+
if (role) entries = entries.filter((e) => e.role === role);
|
|
364
|
+
if (project) entries = entries.filter((e) => e.project === project);
|
|
365
|
+
return entries.length;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// On-disk byte size of the observation tree (live + archive) at rootDir.
|
|
369
|
+
// Backs the `Observation size` doctor surface so size pressure shows up
|
|
370
|
+
// independently of count-based caps.
|
|
371
|
+
export function getObservationsSize(rootDir) {
|
|
372
|
+
const root = obsDir(rootDir);
|
|
373
|
+
if (!fs.existsSync(root)) return 0;
|
|
374
|
+
let total = 0;
|
|
375
|
+
const walk = (dir) => {
|
|
376
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
377
|
+
const full = path.join(dir, entry.name);
|
|
378
|
+
if (entry.isDirectory()) walk(full);
|
|
379
|
+
else if (entry.isFile()) {
|
|
380
|
+
try { total += fs.statSync(full).size; } catch { /* best effort */ }
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
};
|
|
384
|
+
walk(root);
|
|
385
|
+
return total;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
export function checkObservationsSize(rootDir, { cap = MAX_BYTES } = {}) {
|
|
389
|
+
const size = getObservationsSize(rootDir);
|
|
390
|
+
return { size, cap, ok: size <= cap };
|
|
391
|
+
}
|