@geraldmaron/construct 1.0.0 → 1.0.2
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 +9 -6
- 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 +1000 -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 +523 -36
- 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
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* lib/hooks/continuation-enforcer.mjs — Continuation enforcer hook — ensures agents complete tasks before stopping.
|
|
4
|
-
*
|
|
5
|
-
* Runs as PreToolUse on Stop. Checks workflow state for incomplete tasks and blocks the stop if high-priority work is unfinished, prompting the agent to continue.
|
|
6
|
-
*/
|
|
7
|
-
// PostToolUse(TodoWrite) — fires after every todo list update.
|
|
8
|
-
// 1. Counts remaining tasks and reminds agent to continue.
|
|
9
|
-
// 2. Updates .cx/drive-state.json with current loop iteration state.
|
|
10
|
-
import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'fs';
|
|
11
|
-
import { join } from 'path';
|
|
12
|
-
|
|
13
|
-
let input = {};
|
|
14
|
-
try { input = JSON.parse(readFileSync(0, 'utf8')); } catch { process.exit(0); }
|
|
15
|
-
|
|
16
|
-
const cwd = input?.cwd || process.cwd();
|
|
17
|
-
const todos = input?.tool_input?.todos || [];
|
|
18
|
-
|
|
19
|
-
const pending = todos.filter(t => t.status === 'pending');
|
|
20
|
-
const inProgress = todos.filter(t => t.status === 'in_progress');
|
|
21
|
-
const done = todos.filter(t => t.status === 'completed');
|
|
22
|
-
const remaining = pending.length + inProgress.length;
|
|
23
|
-
|
|
24
|
-
if (remaining > 0) {
|
|
25
|
-
const parts = [`[continuation-enforcer] ${remaining} task${remaining !== 1 ? 's' : ''} remain. Continue until all are complete.`];
|
|
26
|
-
if (inProgress.length > 0) parts.push(` In progress: ${inProgress.map(t => t.content).join(', ')}`);
|
|
27
|
-
if (pending.length > 0) {
|
|
28
|
-
const shown = pending.slice(0, 5).map(t => t.content).join(', ');
|
|
29
|
-
const more = pending.length > 5 ? ` (+${pending.length - 5} more)` : '';
|
|
30
|
-
parts.push(` Pending: ${shown}${more}`);
|
|
31
|
-
}
|
|
32
|
-
process.stdout.write(parts.join('\n') + '\n');
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// Update .cx/drive-state.json if drive mode is active
|
|
36
|
-
const driveStatePath = join(cwd, '.cx', 'drive-state.json');
|
|
37
|
-
if (!existsSync(driveStatePath)) process.exit(0);
|
|
38
|
-
|
|
39
|
-
try {
|
|
40
|
-
let ds = {};
|
|
41
|
-
try { ds = JSON.parse(readFileSync(driveStatePath, 'utf8')); } catch { /* fresh */ }
|
|
42
|
-
|
|
43
|
-
if (!ds.active) process.exit(0);
|
|
44
|
-
|
|
45
|
-
const total = todos.length;
|
|
46
|
-
ds.pendingTodos = remaining;
|
|
47
|
-
ds.updatedAt = new Date().toISOString();
|
|
48
|
-
ds.momentumScore = total > 0 ? done.length / total : 0;
|
|
49
|
-
ds.canStop = remaining === 0;
|
|
50
|
-
|
|
51
|
-
// Track current iteration snapshot
|
|
52
|
-
if (!ds.iterations) ds.iterations = [];
|
|
53
|
-
const n = ds.iteration || 1;
|
|
54
|
-
const existing = ds.iterations.findIndex(i => i.n === n);
|
|
55
|
-
const snapshot = {
|
|
56
|
-
n,
|
|
57
|
-
updatedAt: new Date().toISOString(),
|
|
58
|
-
pendingTodos: remaining,
|
|
59
|
-
doneTodos: done.length,
|
|
60
|
-
totalTodos: total,
|
|
61
|
-
inProgress: inProgress.map(t => t.content),
|
|
62
|
-
};
|
|
63
|
-
if (existing >= 0) ds.iterations[existing] = { ...ds.iterations[existing], ...snapshot };
|
|
64
|
-
else ds.iterations.push(snapshot);
|
|
65
|
-
|
|
66
|
-
// Increment iteration when all todos complete — ready for next loop pass
|
|
67
|
-
if (remaining === 0 && total > 0) ds.iteration = (ds.iteration || 1) + 1;
|
|
68
|
-
|
|
69
|
-
writeFileSync(driveStatePath, JSON.stringify(ds, null, 2));
|
|
70
|
-
} catch { /* best effort */ }
|
|
71
|
-
|
|
72
|
-
process.exit(0);
|
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* lib/hooks/drive-guard.mjs — Drive guard hook — enforces that work/drive commands follow the orchestration policy.
|
|
4
|
-
*
|
|
5
|
-
* Runs as PreToolUse on Bash for construct do/drive commands. Validates that the requested goal is routed through the orchestrator rather than bypassing the policy.
|
|
6
|
-
*/
|
|
7
|
-
// Stop hook — blocks session stop when drive mode is active and acceptance criteria lack evidence.
|
|
8
|
-
// Reads .cx/drive-state.json (active loop manifest) + .cx/workflow.json (acceptance criteria).
|
|
9
|
-
// Superior to OmO's boulder: tracks per-criterion evidence, iteration history, momentum score.
|
|
10
|
-
import { readFileSync, existsSync } from 'fs';
|
|
11
|
-
import { join } from 'path';
|
|
12
|
-
|
|
13
|
-
let input = {};
|
|
14
|
-
try { input = JSON.parse(readFileSync(0, 'utf8')); } catch { process.exit(0); }
|
|
15
|
-
|
|
16
|
-
const cwd = input?.cwd || process.cwd();
|
|
17
|
-
const driveStatePath = join(cwd, '.cx', 'drive-state.json');
|
|
18
|
-
const workflowPath = join(cwd, '.cx', 'workflow.json');
|
|
19
|
-
|
|
20
|
-
if (!existsSync(driveStatePath)) process.exit(0);
|
|
21
|
-
|
|
22
|
-
let driveState = {};
|
|
23
|
-
try { driveState = JSON.parse(readFileSync(driveStatePath, 'utf8')); } catch { process.exit(0); }
|
|
24
|
-
|
|
25
|
-
if (!driveState.active || driveState.canStop) process.exit(0);
|
|
26
|
-
|
|
27
|
-
// Collect all acceptance criteria from non-done workflow tasks
|
|
28
|
-
let allCriteria = [];
|
|
29
|
-
let pendingTaskCount = 0;
|
|
30
|
-
try {
|
|
31
|
-
const wf = JSON.parse(readFileSync(workflowPath, 'utf8'));
|
|
32
|
-
const activeTasks = (wf.tasks || []).filter(t => t.status !== 'done' && t.status !== 'skipped');
|
|
33
|
-
pendingTaskCount = activeTasks.length;
|
|
34
|
-
for (const t of activeTasks) {
|
|
35
|
-
for (const c of (t.acceptanceCriteria || [])) {
|
|
36
|
-
if (!allCriteria.includes(c)) allCriteria.push(c);
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
} catch { /* no workflow — rely on todo count */ }
|
|
40
|
-
|
|
41
|
-
const criteriaStatus = driveState.criteriaStatus || {};
|
|
42
|
-
const metCriteria = allCriteria.filter(c => criteriaStatus[c]?.met);
|
|
43
|
-
const unmetCriteria = allCriteria.filter(c => !criteriaStatus[c]?.met);
|
|
44
|
-
const pendingTodos = driveState.pendingTodos || 0;
|
|
45
|
-
const iteration = driveState.iteration || 1;
|
|
46
|
-
const momentum = driveState.momentumScore != null ? Math.round(driveState.momentumScore * 100) : null;
|
|
47
|
-
|
|
48
|
-
// Decide whether to block
|
|
49
|
-
const hasPendingWork = (unmetCriteria.length > 0) || (allCriteria.length === 0 && (pendingTodos > 0 || pendingTaskCount > 0));
|
|
50
|
-
|
|
51
|
-
if (!hasPendingWork) {
|
|
52
|
-
// All criteria met or no criteria defined and no pending work — allow stop
|
|
53
|
-
process.exit(0);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// Emit rich drive-state report
|
|
57
|
-
const lines = [
|
|
58
|
-
`[drive-guard] Drive mode active — iteration ${iteration}. Cannot stop yet.`,
|
|
59
|
-
'',
|
|
60
|
-
];
|
|
61
|
-
|
|
62
|
-
if (unmetCriteria.length > 0) {
|
|
63
|
-
lines.push(`Unmet criteria (${unmetCriteria.length}/${allCriteria.length}):`);
|
|
64
|
-
for (const c of unmetCriteria) lines.push(` ✗ ${c}`);
|
|
65
|
-
lines.push('');
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
if (metCriteria.length > 0) {
|
|
69
|
-
lines.push(`Verified criteria (${metCriteria.length}/${allCriteria.length}):`);
|
|
70
|
-
for (const c of metCriteria) {
|
|
71
|
-
const ev = criteriaStatus[c]?.evidence || 'recorded';
|
|
72
|
-
lines.push(` ✓ ${c} — ${ev.slice(0, 80)}`);
|
|
73
|
-
}
|
|
74
|
-
lines.push('');
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
if (allCriteria.length === 0) {
|
|
78
|
-
lines.push(`Remaining tasks: ${pendingTodos} todos, ${pendingTaskCount} workflow tasks`);
|
|
79
|
-
lines.push('');
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
if (momentum != null) lines.push(`Momentum: ${momentum}% (iteration ${iteration})`);
|
|
83
|
-
|
|
84
|
-
lines.push('');
|
|
85
|
-
lines.push(`[drive-guard] Record evidence by updating .cx/drive-state.json criteriaStatus, then criteria will pass.`);
|
|
86
|
-
lines.push(`[drive-guard] To mark a criterion met: set criteriaStatus["<criterion>"] = { met: true, evidence: "..." }`);
|
|
87
|
-
|
|
88
|
-
process.stderr.write(lines.join('\n') + '\n');
|
|
89
|
-
process.exit(2);
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* lib/hooks/mcp-task-scope.mjs — MCP task scope hook — constrains MCP tool use to the active workflow task scope.
|
|
4
|
-
*
|
|
5
|
-
* Runs as PreToolUse on MCP tool calls. Reads the active workflow task and warns when MCP tools are used outside the declared task scope.
|
|
6
|
-
*/
|
|
7
|
-
// PreToolUse(mcp__*) — task-aware MCP scope advisor.
|
|
8
|
-
// Reads the active workflow task's mcpScope field.
|
|
9
|
-
// Warns (non-blocking) if calling an MCP not declared for the active task.
|
|
10
|
-
// Exceeds OmO's per-task scoped MCPs: adds attribution, guidance, and audit trail
|
|
11
|
-
// without requiring runtime MCP lifecycle management.
|
|
12
|
-
import { readFileSync, existsSync } from 'fs';
|
|
13
|
-
import { join } from 'path';
|
|
14
|
-
|
|
15
|
-
let input = {};
|
|
16
|
-
try { input = JSON.parse(readFileSync(0, 'utf8')); } catch { process.exit(0); }
|
|
17
|
-
|
|
18
|
-
const cwd = input?.cwd || process.cwd();
|
|
19
|
-
const toolName = input?.tool_name || '';
|
|
20
|
-
|
|
21
|
-
const match = toolName.match(/^mcp__([^_]+(?:__[^_]+)*)__/);
|
|
22
|
-
if (!match) process.exit(0);
|
|
23
|
-
const mcpServer = match[1].replace(/__/g, '-');
|
|
24
|
-
|
|
25
|
-
const workflowPath = join(cwd, '.cx', 'workflow.json');
|
|
26
|
-
if (!existsSync(workflowPath)) process.exit(0);
|
|
27
|
-
|
|
28
|
-
try {
|
|
29
|
-
const wf = JSON.parse(readFileSync(workflowPath, 'utf8'));
|
|
30
|
-
const activeTaskKey = wf.currentTaskKey;
|
|
31
|
-
if (!activeTaskKey) process.exit(0);
|
|
32
|
-
|
|
33
|
-
const task = (wf.tasks || []).find(t => t.key === activeTaskKey);
|
|
34
|
-
if (!task?.mcpScope || task.mcpScope.length === 0) process.exit(0);
|
|
35
|
-
|
|
36
|
-
const scope = task.mcpScope;
|
|
37
|
-
const inScope = scope.some(s => mcpServer.includes(s) || s.includes(mcpServer));
|
|
38
|
-
|
|
39
|
-
if (!inScope) {
|
|
40
|
-
process.stderr.write(
|
|
41
|
-
`[mcp-scope] ${mcpServer} is not in mcpScope for task "${activeTaskKey}".\n` +
|
|
42
|
-
`[mcp-scope] Declared scope: ${scope.join(', ')}. Proceeding — verify this call is intentional.\n`
|
|
43
|
-
);
|
|
44
|
-
}
|
|
45
|
-
} catch { /* best effort */ }
|
|
46
|
-
|
|
47
|
-
process.exit(0);
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* lib/hooks/task-completed-guard.mjs — Task completed guard hook — enforces verification evidence before marking tasks done.
|
|
4
|
-
*
|
|
5
|
-
* Runs as PreToolUse on workflow_update_task. Checks that implement-phase tasks include verification evidence before allowing status to be set to done. Exits 2 to block.
|
|
6
|
-
*/
|
|
7
|
-
import { loadWorkflow, validateWorkflowState } from '../workflow-state.mjs';
|
|
8
|
-
|
|
9
|
-
function readInput() {
|
|
10
|
-
try {
|
|
11
|
-
const chunks = [];
|
|
12
|
-
process.stdin.on('data', (chunk) => chunks.push(chunk));
|
|
13
|
-
return new Promise((resolve) => {
|
|
14
|
-
process.stdin.on('end', () => {
|
|
15
|
-
try {
|
|
16
|
-
const raw = Buffer.concat(chunks).toString('utf8');
|
|
17
|
-
resolve(raw ? JSON.parse(raw) : {});
|
|
18
|
-
} catch {
|
|
19
|
-
resolve({});
|
|
20
|
-
}
|
|
21
|
-
});
|
|
22
|
-
});
|
|
23
|
-
} catch {
|
|
24
|
-
return {};
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const input = await readInput();
|
|
29
|
-
const cwd = input.cwd || input.workspace_dir || process.cwd();
|
|
30
|
-
const workflow = loadWorkflow(cwd);
|
|
31
|
-
|
|
32
|
-
if (!workflow) process.exit(0);
|
|
33
|
-
|
|
34
|
-
const result = validateWorkflowState(workflow);
|
|
35
|
-
if (result.valid) process.exit(0);
|
|
36
|
-
|
|
37
|
-
process.stderr.write([
|
|
38
|
-
'[construct] Task completion blocked by workflow validation.',
|
|
39
|
-
...result.errors.map((error) => `- ${error}`),
|
|
40
|
-
'Update .cx/workflow.json with owner, acceptance criteria, dependencies, and verification before marking the task complete.',
|
|
41
|
-
'',
|
|
42
|
-
].join('\n'));
|
|
43
|
-
process.exit(2);
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* lib/hooks/teammate-idle-guard.mjs — Teammate idle guard hook — warns when a spawned agent has been idle too long.
|
|
4
|
-
*
|
|
5
|
-
* Runs as PostToolUse after Agent tool calls. Tracks agent start times and emits a warning if an agent appears idle or unresponsive beyond the idle threshold.
|
|
6
|
-
*/
|
|
7
|
-
import { loadWorkflow } from '../workflow-state.mjs';
|
|
8
|
-
|
|
9
|
-
async function readInput() {
|
|
10
|
-
const chunks = [];
|
|
11
|
-
for await (const chunk of process.stdin) chunks.push(chunk);
|
|
12
|
-
try {
|
|
13
|
-
const raw = Buffer.concat(chunks).toString('utf8');
|
|
14
|
-
return raw ? JSON.parse(raw) : {};
|
|
15
|
-
} catch {
|
|
16
|
-
return {};
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
function namesFromInput(input) {
|
|
21
|
-
return [
|
|
22
|
-
input.teammate,
|
|
23
|
-
input.teammate_name,
|
|
24
|
-
input.agent,
|
|
25
|
-
input.agent_name,
|
|
26
|
-
input.name,
|
|
27
|
-
].filter((value) => typeof value === 'string' && value.trim()).map((value) => value.trim());
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
const input = await readInput();
|
|
31
|
-
const cwd = input.cwd || input.workspace_dir || process.cwd();
|
|
32
|
-
const names = namesFromInput(input);
|
|
33
|
-
const workflow = loadWorkflow(cwd);
|
|
34
|
-
|
|
35
|
-
if (!workflow || names.length === 0) process.exit(0);
|
|
36
|
-
|
|
37
|
-
const active = (workflow.tasks || []).find((task) =>
|
|
38
|
-
names.includes(task.owner)
|
|
39
|
-
&& task.status === 'in-progress'
|
|
40
|
-
&& (!Array.isArray(task.notes) || task.notes.length === 0)
|
|
41
|
-
&& (!Array.isArray(task.verification) || task.verification.length === 0)
|
|
42
|
-
);
|
|
43
|
-
|
|
44
|
-
if (!active) process.exit(0);
|
|
45
|
-
|
|
46
|
-
process.stderr.write([
|
|
47
|
-
`[construct] ${names[0]} is going idle with active task ${active.key} but no workflow update.`,
|
|
48
|
-
'Before going idle, update .cx/workflow.json with one of:',
|
|
49
|
-
'- status=done plus verification evidence',
|
|
50
|
-
'- status=blocked with the blocker',
|
|
51
|
-
'- status=blocked_needs_user and a NEEDS_MAIN_INPUT packet for the primary persona',
|
|
52
|
-
'',
|
|
53
|
-
].join('\n'));
|
|
54
|
-
process.exit(2);
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* lib/hooks/workflow-guard.mjs — Workflow guard hook — enforces that significant work flows through the workflow state.
|
|
4
|
-
*
|
|
5
|
-
* Runs as PreToolUse on implement tasks. Checks that the active workflow task is set before allowing non-trivial Bash or Edit operations. Warns without blocking.
|
|
6
|
-
*/
|
|
7
|
-
import { readFileSync } from "node:fs";
|
|
8
|
-
import { loadWorkflow, alignmentFindings, summarizeWorkflow } from "../workflow-state.mjs";
|
|
9
|
-
|
|
10
|
-
let input = {};
|
|
11
|
-
try {
|
|
12
|
-
input = JSON.parse(readFileSync(0, "utf8"));
|
|
13
|
-
} catch {
|
|
14
|
-
process.exit(0);
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
const text = [
|
|
18
|
-
input?.prompt,
|
|
19
|
-
input?.message,
|
|
20
|
-
input?.transcript,
|
|
21
|
-
input?.last_message,
|
|
22
|
-
input?.assistant_message
|
|
23
|
-
].filter(Boolean).join("\n").toLowerCase();
|
|
24
|
-
|
|
25
|
-
if (/\b(stop|pause|halt|enough|abort|cancel)\b/.test(text)) {
|
|
26
|
-
process.exit(0);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const cwd = input?.cwd || process.cwd();
|
|
30
|
-
const workflow = loadWorkflow(cwd);
|
|
31
|
-
if (!workflow || workflow.status !== "in-progress") {
|
|
32
|
-
process.exit(0);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const tasks = workflow.tasks || [];
|
|
36
|
-
const openTasks = tasks.filter((task) => !["done", "skipped"].includes(task.status));
|
|
37
|
-
const findings = alignmentFindings(workflow).filter((finding) => finding.severity === "HIGH");
|
|
38
|
-
|
|
39
|
-
if (openTasks.length === 0 && findings.length === 0) {
|
|
40
|
-
process.exit(0);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
const next = tasks.find((task) => task.key === workflow.currentTaskKey) || openTasks[0];
|
|
44
|
-
const lines = [
|
|
45
|
-
"Construct workflow is still active.",
|
|
46
|
-
summarizeWorkflow(workflow)
|
|
47
|
-
];
|
|
48
|
-
|
|
49
|
-
if (next) {
|
|
50
|
-
lines.push(`Next task: ${next.key} ${next.title} -> ${next.owner} [${next.status}]`);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
if (findings.length > 0) {
|
|
54
|
-
lines.push(`Alignment blockers: ${findings.length}`);
|
|
55
|
-
for (const finding of findings.slice(0, 3)) {
|
|
56
|
-
lines.push(`- ${finding.task ? `${finding.task}: ` : ""}${finding.issue}`);
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
lines.push("Continue the current workflow, update .cx/workflow.json, or explicitly say stop/pause to end.");
|
|
61
|
-
console.error(lines.join("\n"));
|
|
62
|
-
process.exit(2);
|
package/lib/prompt-composer.mjs
DELETED
|
@@ -1,196 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* lib/prompt-composer.mjs — Assemble the final agent prompt from core file, task packet, and context digest.
|
|
3
|
-
*
|
|
4
|
-
* Called by the orchestrator and MCP dispatch layer before invoking a specialist agent.
|
|
5
|
-
* Inlines role anti-pattern guidance from skills/roles/ when a role directive is present in the prompt.
|
|
6
|
-
* Produces a deterministic, cache-friendly prompt string with a stable token footprint.
|
|
7
|
-
*/
|
|
8
|
-
import crypto from 'node:crypto';
|
|
9
|
-
import fs from 'node:fs';
|
|
10
|
-
import path from 'node:path';
|
|
11
|
-
|
|
12
|
-
import { buildContextDigest, readContextState } from './context-state.mjs';
|
|
13
|
-
import { resolveExecutionContractModelMetadata, selectModelTierForWorkCategory } from './model-router.mjs';
|
|
14
|
-
import { routeRequest } from './orchestration-policy.mjs';
|
|
15
|
-
import { resolvePromptEntry, resolvePromptMetadata } from './prompt-metadata.mjs';
|
|
16
|
-
import { loadWorkflow } from './workflow-state.mjs';
|
|
17
|
-
|
|
18
|
-
function compactText(text, limit = 1200) {
|
|
19
|
-
if (!text) return '';
|
|
20
|
-
const normalized = String(text).trim();
|
|
21
|
-
return normalized.length <= limit ? normalized : `${normalized.slice(0, limit)}…`;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function readPromptBody(promptFile, rootDir) {
|
|
25
|
-
const filePath = path.join(rootDir, promptFile);
|
|
26
|
-
if (!fs.existsSync(filePath)) return '';
|
|
27
|
-
return fs.readFileSync(filePath, 'utf8').trim();
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export function resolveBasePrompt(entryOrName, {
|
|
31
|
-
rootDir = process.cwd(),
|
|
32
|
-
registry,
|
|
33
|
-
fallback = '',
|
|
34
|
-
} = {}) {
|
|
35
|
-
return resolvePromptContract(entryOrName, { rootDir, registry, fallback }).prompt;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export function resolvePromptContract(entryOrName, {
|
|
39
|
-
rootDir = process.cwd(),
|
|
40
|
-
registry,
|
|
41
|
-
fallback = '',
|
|
42
|
-
} = {}) {
|
|
43
|
-
const directEntry = entryOrName && typeof entryOrName === 'object' ? entryOrName : null;
|
|
44
|
-
if (directEntry?.prompt) {
|
|
45
|
-
return {
|
|
46
|
-
prompt: String(directEntry.prompt).trim(),
|
|
47
|
-
metadata: resolvePromptMetadata(directEntry.name || entryOrName, { rootDir, registry }),
|
|
48
|
-
};
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
const agentName = directEntry?.name || entryOrName;
|
|
52
|
-
if (!agentName) return { prompt: fallback, metadata: {} };
|
|
53
|
-
|
|
54
|
-
const composed = composePrompt(agentName, { rootDir, registry });
|
|
55
|
-
return {
|
|
56
|
-
prompt: composed.prompt || fallback,
|
|
57
|
-
metadata: composed.metadata || {},
|
|
58
|
-
};
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function resolveActiveTask(workflow) {
|
|
62
|
-
if (!workflow || !Array.isArray(workflow.tasks)) return null;
|
|
63
|
-
if (workflow.currentTaskKey) {
|
|
64
|
-
const currentTask = workflow.tasks.find((task) => task.key === workflow.currentTaskKey && !['done', 'skipped'].includes(task.status));
|
|
65
|
-
if (currentTask) return currentTask;
|
|
66
|
-
}
|
|
67
|
-
return workflow.tasks.find((task) => ['in-progress', 'in_progress', 'blocked_needs_user'].includes(task.status)) || null;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
export function composePrompt(agentName, {
|
|
71
|
-
rootDir = process.cwd(),
|
|
72
|
-
registry,
|
|
73
|
-
task = null,
|
|
74
|
-
contextState = null,
|
|
75
|
-
hostConstraints = null,
|
|
76
|
-
intent = null,
|
|
77
|
-
workCategory = null,
|
|
78
|
-
} = {}) {
|
|
79
|
-
const entry = resolvePromptEntry(agentName, { rootDir, registry });
|
|
80
|
-
if (!entry?.promptFile) return { metadata: {}, fragments: [], prompt: '' };
|
|
81
|
-
|
|
82
|
-
const metadata = resolvePromptMetadata(agentName, { rootDir, registry });
|
|
83
|
-
const fragments = [];
|
|
84
|
-
|
|
85
|
-
fragments.push({ type: 'core', label: entry.name, content: readPromptBody(entry.promptFile, rootDir) });
|
|
86
|
-
|
|
87
|
-
if (intent || workCategory) {
|
|
88
|
-
fragments.push({
|
|
89
|
-
type: 'task-context',
|
|
90
|
-
label: 'task-classification',
|
|
91
|
-
content: compactText(`Intent: ${intent || 'unknown'}\nWork category: ${workCategory || 'unknown'}`, 200),
|
|
92
|
-
});
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
if (task) {
|
|
96
|
-
const taskBlock = [
|
|
97
|
-
task.title ? `Task: ${task.title}` : null,
|
|
98
|
-
task.owner ? `Owner: ${task.owner}` : null,
|
|
99
|
-
Array.isArray(task.acceptanceCriteria) && task.acceptanceCriteria.length ? `Acceptance: ${task.acceptanceCriteria.join('; ')}` : null,
|
|
100
|
-
Array.isArray(task.readFirst) && task.readFirst.length ? `Read first: ${task.readFirst.join(', ')}` : null,
|
|
101
|
-
Array.isArray(task.doNotChange) && task.doNotChange.length ? `Do not change: ${task.doNotChange.join(', ')}` : null,
|
|
102
|
-
].filter(Boolean).join('\n');
|
|
103
|
-
if (taskBlock) fragments.push({ type: 'task-packet', label: 'workflow-task', content: compactText(taskBlock, 500) });
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
const digest = buildContextDigest(contextState);
|
|
107
|
-
if (digest) {
|
|
108
|
-
fragments.push({ type: 'context-digest', label: 'context', content: compactText(JSON.stringify(digest), 600) });
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
if (hostConstraints) {
|
|
112
|
-
fragments.push({ type: 'host-constraints', label: 'host', content: compactText(JSON.stringify(hostConstraints), 300) });
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
const prompt = fragments.filter((fragment) => fragment.content).map((fragment) => fragment.content).join('\n\n');
|
|
116
|
-
return { metadata, fragments, prompt };
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
export function summarizePromptComposition(agentName, options = {}) {
|
|
120
|
-
const route = options.route || (options.request ? routeRequest({ request: options.request }) : null);
|
|
121
|
-
const executionContractModel = options.executionContractModel
|
|
122
|
-
|| resolveExecutionContractModelMetadata({
|
|
123
|
-
envValues: options.envValues || {},
|
|
124
|
-
registryModels: options.registryModels || {},
|
|
125
|
-
requestedTier: options.requestedTier || selectModelTierForWorkCategory(route?.workCategory),
|
|
126
|
-
workCategory: route?.workCategory || null,
|
|
127
|
-
});
|
|
128
|
-
const composed = composePrompt(agentName, {
|
|
129
|
-
...options,
|
|
130
|
-
intent: options.intent || route?.intent || null,
|
|
131
|
-
workCategory: options.workCategory || route?.workCategory || null,
|
|
132
|
-
});
|
|
133
|
-
const fragmentTypes = composed.fragments.map((fragment) => fragment.type);
|
|
134
|
-
const composedPromptHash = composed.prompt
|
|
135
|
-
? crypto.createHash('sha256').update(composed.prompt).digest('hex')
|
|
136
|
-
: null;
|
|
137
|
-
const task = options.task || null;
|
|
138
|
-
|
|
139
|
-
return {
|
|
140
|
-
...composed.metadata,
|
|
141
|
-
...(composedPromptHash ? {
|
|
142
|
-
composedPromptHash,
|
|
143
|
-
composedPromptVersion: composedPromptHash.slice(0, 12),
|
|
144
|
-
} : {}),
|
|
145
|
-
promptFragmentCount: composed.fragments.length,
|
|
146
|
-
promptFragmentTypes: fragmentTypes,
|
|
147
|
-
promptHasTaskPacket: fragmentTypes.includes('task-packet'),
|
|
148
|
-
promptHasContextDigest: fragmentTypes.includes('context-digest'),
|
|
149
|
-
promptHasHostConstraints: fragmentTypes.includes('host-constraints'),
|
|
150
|
-
...(task?.key ? { workflowTaskKey: task.key } : {}),
|
|
151
|
-
...(task?.owner ? { workflowTaskOwner: task.owner } : {}),
|
|
152
|
-
...(task?.phase ? { workflowTaskPhase: task.phase } : {}),
|
|
153
|
-
...(route ? {
|
|
154
|
-
routeIntent: route.intent,
|
|
155
|
-
routeTrack: route.track,
|
|
156
|
-
routeWorkCategory: route.workCategory,
|
|
157
|
-
routeSpecialists: route.specialists,
|
|
158
|
-
routeDispatchPlan: route.dispatchPlan,
|
|
159
|
-
} : {}),
|
|
160
|
-
executionContractModel,
|
|
161
|
-
};
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
export function resolveRuntimePromptMetadata(agentName, {
|
|
165
|
-
rootDir = process.cwd(),
|
|
166
|
-
registry,
|
|
167
|
-
workflow = null,
|
|
168
|
-
task = null,
|
|
169
|
-
contextState = null,
|
|
170
|
-
request = null,
|
|
171
|
-
route = null,
|
|
172
|
-
registryModels = {},
|
|
173
|
-
envValues = {},
|
|
174
|
-
executionContractModel = null,
|
|
175
|
-
hostConstraints = null,
|
|
176
|
-
} = {}) {
|
|
177
|
-
const resolvedWorkflow = workflow ?? loadWorkflow(rootDir);
|
|
178
|
-
const resolvedTask = task ?? resolveActiveTask(resolvedWorkflow);
|
|
179
|
-
const resolvedContextState = contextState ?? readContextState(rootDir);
|
|
180
|
-
const resolvedRoute = route || (request ? routeRequest({ request }) : null);
|
|
181
|
-
|
|
182
|
-
return summarizePromptComposition(agentName, {
|
|
183
|
-
rootDir,
|
|
184
|
-
registry,
|
|
185
|
-
task: resolvedTask,
|
|
186
|
-
contextState: resolvedContextState,
|
|
187
|
-
request,
|
|
188
|
-
route: resolvedRoute,
|
|
189
|
-
registryModels,
|
|
190
|
-
envValues,
|
|
191
|
-
executionContractModel,
|
|
192
|
-
hostConstraints,
|
|
193
|
-
intent: resolvedRoute?.intent || null,
|
|
194
|
-
workCategory: resolvedRoute?.workCategory || null,
|
|
195
|
-
});
|
|
196
|
-
}
|