@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
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# bootstrap policy
|
|
2
|
+
# Controls session bootstrap enforcement (write/bash gating until session grounded).
|
|
3
|
+
|
|
4
|
+
rule: bootstrap
|
|
5
|
+
hook_event: PreToolUse
|
|
6
|
+
blocked_tools: [Write, Edit, MultiEdit, NotebookEdit, TodoWrite, Bash]
|
|
7
|
+
bootstrap_trio: [workflow_status, project_context, memory_search]
|
|
8
|
+
read_threshold: 3
|
|
9
|
+
benign_bash_pattern: "^(git (status|log|diff|branch|show|rev-parse)|ls|cat|head|tail|pwd|wc|file|echo|which|node --version|npm --version)\\b"
|
|
10
|
+
action: block
|
|
11
|
+
message: "Requires session bootstrap. Run workflow_status + project_context + memory_search first (or 3+ read calls)."
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# drive policy
|
|
2
|
+
# Controls stop-blocking when drive mode is active and acceptance criteria are unmet.
|
|
3
|
+
|
|
4
|
+
rule: drive
|
|
5
|
+
hook_event: Stop
|
|
6
|
+
condition: drive_state.active == true AND drive_state.canStop != true
|
|
7
|
+
action: block
|
|
8
|
+
message: "Drive mode active — unmet criteria remain. Record evidence in .cx/drive-state.json to unblock."
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# task-complete policy
|
|
2
|
+
# Blocks marking a workflow task as done without verification evidence.
|
|
3
|
+
|
|
4
|
+
rule: task-complete
|
|
5
|
+
hook_event: PreToolUse
|
|
6
|
+
trigger_tools: [mcp__construct-mcp__workflow_update_task]
|
|
7
|
+
condition: task.verification is empty OR task.acceptanceCriteria is empty
|
|
8
|
+
action: block
|
|
9
|
+
message: "Task completion requires owner, acceptance criteria, and verification evidence in .cx/workflow.json."
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# workflow policy
|
|
2
|
+
# Controls UserPromptSubmit routing — ensures significant work flows through workflow state.
|
|
3
|
+
|
|
4
|
+
rule: workflow
|
|
5
|
+
hook_event: UserPromptSubmit
|
|
6
|
+
condition: workflow.status == "in-progress" AND (open_tasks.length > 0 OR high_severity_findings.length > 0)
|
|
7
|
+
bypass_patterns: ["\\b(stop|pause|halt|enough|abort|cancel)\\b"]
|
|
8
|
+
action: block
|
|
9
|
+
message: "Workflow is active. Continue the current task, update .cx/workflow.json, or say stop/pause to end."
|
package/rules/python/hooks.md
CHANGED
|
@@ -10,12 +10,8 @@ paths:
|
|
|
10
10
|
---
|
|
11
11
|
# Python Hooks
|
|
12
12
|
|
|
13
|
-
> This file extends [common/hooks.md](../common/hooks.md) with Python specific content.
|
|
14
|
-
|
|
15
13
|
## PostToolUse Hooks
|
|
16
14
|
|
|
17
|
-
Configure in `~/.claude/settings.json`:
|
|
18
|
-
|
|
19
15
|
- **black/ruff**: Auto-format `.py` files after edit
|
|
20
16
|
- **mypy/pyright**: Run type checking after editing `.py` files
|
|
21
17
|
|
package/rules/swift/hooks.md
CHANGED
|
@@ -10,12 +10,8 @@ paths:
|
|
|
10
10
|
---
|
|
11
11
|
# Swift Hooks
|
|
12
12
|
|
|
13
|
-
> This file extends [common/hooks.md](../common/hooks.md) with Swift specific content.
|
|
14
|
-
|
|
15
13
|
## PostToolUse Hooks
|
|
16
14
|
|
|
17
|
-
Configure in `~/.claude/settings.json`:
|
|
18
|
-
|
|
19
15
|
- **SwiftFormat**: Auto-format `.swift` files after edit
|
|
20
16
|
- **SwiftLint**: Run lint checks after editing `.swift` files
|
|
21
17
|
- **swift build**: Type-check modified packages after edit
|
|
@@ -12,12 +12,8 @@ paths:
|
|
|
12
12
|
---
|
|
13
13
|
# TypeScript/JavaScript Hooks
|
|
14
14
|
|
|
15
|
-
> This file extends [common/hooks.md](../common/hooks.md) with TypeScript/JavaScript specific content.
|
|
16
|
-
|
|
17
15
|
## PostToolUse Hooks
|
|
18
16
|
|
|
19
|
-
Configure in `~/.claude/settings.json`:
|
|
20
|
-
|
|
21
17
|
- **Prettier**: Auto-format JS/TS files after edit
|
|
22
18
|
- **TypeScript check**: Run `tsc` after editing `.ts`/`.tsx` files
|
|
23
19
|
- **console.log warning**: Warn about `console.log` in edited files
|
package/rules/web/hooks.md
CHANGED
|
@@ -4,12 +4,20 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Reads registry.json, resolves env vars and model tiers, then writes Claude Code,
|
|
6
6
|
* OpenCode, Codex, Copilot, VS Code, and Cursor adapters. Called by 'construct sync'.
|
|
7
|
-
*
|
|
7
|
+
*
|
|
8
|
+
* Flags:
|
|
9
|
+
* --dry-run Print a diff of what would change without writing anything.
|
|
10
|
+
* --force Bypass prompt word-cap hard stop (still warns).
|
|
11
|
+
* --project Write to the current project's .claude/ directory only.
|
|
12
|
+
* --compress-personas Run the engine's Compressor on every persona prompt
|
|
13
|
+
* before writing platform adapters. The source persona
|
|
14
|
+
* file is unchanged; only the runtime adapter is shorter.
|
|
15
|
+
* Lossy by definition — opt-in only.
|
|
8
16
|
*/
|
|
9
17
|
import fs from "node:fs";
|
|
10
18
|
import path from "node:path";
|
|
11
19
|
import os from "node:os";
|
|
12
|
-
import { generateCompletions } from "
|
|
20
|
+
import { generateCompletions } from "../lib/completions.mjs";
|
|
13
21
|
import {
|
|
14
22
|
buildCodexMcpEntry,
|
|
15
23
|
getCodexConfigPath,
|
|
@@ -20,25 +28,27 @@ import {
|
|
|
20
28
|
serializeCodexMcpTable,
|
|
21
29
|
tomlString,
|
|
22
30
|
writeCodexConfig,
|
|
23
|
-
} from "
|
|
24
|
-
import { findOpenCodeConfigPath, readOpenCodeConfig, writeOpenCodeConfig } from "
|
|
25
|
-
import { resolvePromptContract } from "
|
|
31
|
+
} from "../lib/codex-config.mjs";
|
|
32
|
+
import { findOpenCodeConfigPath, readOpenCodeConfig, writeOpenCodeConfig } from "../lib/opencode-config.mjs";
|
|
33
|
+
import { resolvePromptContract } from "../lib/prompt-composer.js";
|
|
26
34
|
import {
|
|
27
35
|
buildClaudeMcpEntry,
|
|
28
36
|
buildOpenCodeMcpEntry,
|
|
29
37
|
getOpenCodeMcpId,
|
|
30
|
-
} from "
|
|
31
|
-
import { loadConstructEnv } from "
|
|
32
|
-
import { inlineRoleAntiPatterns, PROMPT_WORD_CAP } from "
|
|
33
|
-
import { resolveTiersForPrimary } from "
|
|
38
|
+
} from "../lib/mcp-platform-config.mjs";
|
|
39
|
+
import { loadConstructEnv } from "../lib/env-config.mjs";
|
|
40
|
+
import { inlineRoleAntiPatterns, PROMPT_WORD_CAP } from "../lib/role-preload.mjs";
|
|
41
|
+
import { resolveTiersForPrimary } from "../lib/model-router.mjs";
|
|
42
|
+
import { stampFrontmatter } from "../lib/doc-stamp.mjs";
|
|
34
43
|
|
|
35
44
|
const home = os.homedir();
|
|
36
|
-
const root = path.resolve(import.meta.dirname);
|
|
45
|
+
const root = path.resolve(import.meta.dirname, "..");
|
|
37
46
|
|
|
38
47
|
const mergedEnv = loadConstructEnv({ rootDir: root, homeDir: home, env: process.env });
|
|
39
48
|
for (const [key, value] of Object.entries(mergedEnv)) {
|
|
40
49
|
if (!(key in process.env)) process.env[key] = value;
|
|
41
50
|
}
|
|
51
|
+
if (!process.env.CX_TOOLKIT_DIR) process.env.CX_TOOLKIT_DIR = root;
|
|
42
52
|
const registryPath = path.join(root, "agents", "registry.json");
|
|
43
53
|
const registry = JSON.parse(fs.readFileSync(registryPath, "utf8"));
|
|
44
54
|
|
|
@@ -94,8 +104,8 @@ function validateRegistry(registry) {
|
|
|
94
104
|
errors.push(`models.${tier}: missing tier object`);
|
|
95
105
|
continue;
|
|
96
106
|
}
|
|
97
|
-
if (
|
|
98
|
-
errors.push(`models.${tier}: primary
|
|
107
|
+
if (t.primary !== null && (typeof t.primary !== "string" || !t.primary)) {
|
|
108
|
+
errors.push(`models.${tier}: primary must be null or a non-empty string`);
|
|
99
109
|
}
|
|
100
110
|
}
|
|
101
111
|
}
|
|
@@ -110,6 +120,95 @@ if (validationErrors.length > 0) {
|
|
|
110
120
|
process.exit(1);
|
|
111
121
|
}
|
|
112
122
|
|
|
123
|
+
// --- Dry-run + lockfile + two-phase write infrastructure ---
|
|
124
|
+
|
|
125
|
+
const DRY_RUN = process.argv.includes("--dry-run");
|
|
126
|
+
const COMPRESS_PERSONAS = process.argv.includes("--compress-personas");
|
|
127
|
+
const lockPath = path.join(root, ".cx", "sync.lock");
|
|
128
|
+
const stagingDir = path.join(root, ".cx", "sync-staging");
|
|
129
|
+
|
|
130
|
+
/** Acquire an exclusive lockfile. Aborts if already held by a live process. */
|
|
131
|
+
function acquireLock() {
|
|
132
|
+
if (DRY_RUN) return;
|
|
133
|
+
try {
|
|
134
|
+
fs.mkdirSync(path.dirname(lockPath), { recursive: true });
|
|
135
|
+
fs.writeFileSync(lockPath, String(process.pid), { flag: "wx" });
|
|
136
|
+
} catch (err) {
|
|
137
|
+
if (err.code === "EEXIST") {
|
|
138
|
+
const holder = fs.readFileSync(lockPath, "utf8").trim();
|
|
139
|
+
// Check whether the holding process is still alive
|
|
140
|
+
let holderAlive = false;
|
|
141
|
+
try { process.kill(Number(holder), 0); holderAlive = true; } catch { /* dead */ }
|
|
142
|
+
if (holderAlive) {
|
|
143
|
+
console.error(`[sync] Another sync is already running (pid ${holder}). Aborting.`);
|
|
144
|
+
console.error(`[sync] If this is stale, remove .cx/sync.lock and retry.`);
|
|
145
|
+
process.exit(1);
|
|
146
|
+
}
|
|
147
|
+
// Stale lock — steal it
|
|
148
|
+
fs.writeFileSync(lockPath, String(process.pid), { flag: "w" });
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
throw err;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/** Release the lockfile. Called in a finally block. */
|
|
156
|
+
function releaseLock() {
|
|
157
|
+
if (DRY_RUN) return;
|
|
158
|
+
try { fs.unlinkSync(lockPath); } catch { /* already gone */ }
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Staging-aware write. In dry-run mode, writes to the staging dir instead of the
|
|
163
|
+
* real destination and records the path pair for diff output. In normal mode,
|
|
164
|
+
* writes to staging first, then the real path is swapped in by commitStaging().
|
|
165
|
+
*/
|
|
166
|
+
const _stagedPairs = []; // [{ staging, real, content }]
|
|
167
|
+
|
|
168
|
+
function writeFile(file, content) {
|
|
169
|
+
mkdirp(path.dirname(file));
|
|
170
|
+
const stamped = file.endsWith('.md') ? stampFrontmatter(content, { generator: 'construct/sync-agents' }) : content;
|
|
171
|
+
|
|
172
|
+
if (DRY_RUN) {
|
|
173
|
+
// Stage in memory only — compare against current on-disk content.
|
|
174
|
+
let current = "";
|
|
175
|
+
try { current = fs.readFileSync(file, "utf8"); } catch { /* new file */ }
|
|
176
|
+
if (current !== stamped) _stagedPairs.push({ real: file, staging: null, content: stamped, current });
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Two-phase: write to staging, commit later.
|
|
181
|
+
const rel = path.relative(root, file);
|
|
182
|
+
const stagingPath = path.join(stagingDir, rel);
|
|
183
|
+
mkdirp(path.dirname(stagingPath));
|
|
184
|
+
fs.writeFileSync(stagingPath, stamped);
|
|
185
|
+
_stagedPairs.push({ real: file, staging: stagingPath, content: stamped });
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/** Atomically rename all staged files into their real destinations. */
|
|
189
|
+
function commitStaging() {
|
|
190
|
+
for (const { real, staging } of _stagedPairs) {
|
|
191
|
+
if (!staging) continue;
|
|
192
|
+
mkdirp(path.dirname(real));
|
|
193
|
+
fs.renameSync(staging, real);
|
|
194
|
+
}
|
|
195
|
+
// Clean up staging dir.
|
|
196
|
+
try { fs.rmSync(stagingDir, { recursive: true, force: true }); } catch { /* ok */ }
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/** Print a human-readable diff summary for --dry-run mode. */
|
|
200
|
+
function printDryRunDiff() {
|
|
201
|
+
if (_stagedPairs.length === 0) {
|
|
202
|
+
console.log("[sync --dry-run] No changes — all outputs are already up to date.");
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
console.log(`[sync --dry-run] ${_stagedPairs.length} file(s) would change:\n`);
|
|
206
|
+
for (const { real } of _stagedPairs) {
|
|
207
|
+
console.log(` ~ ${path.relative(root, real)}`);
|
|
208
|
+
}
|
|
209
|
+
console.log("\nRe-run without --dry-run to apply.");
|
|
210
|
+
}
|
|
211
|
+
|
|
113
212
|
const systemName = registry.system;
|
|
114
213
|
const agentPrefix = `${registry.prefix}-`;
|
|
115
214
|
const sharedGuidance = registry.sharedGuidance ?? [];
|
|
@@ -248,7 +347,6 @@ function resolveModelChain(entry) {
|
|
|
248
347
|
}
|
|
249
348
|
|
|
250
349
|
function mkdirp(dir) { fs.mkdirSync(dir, { recursive: true }); }
|
|
251
|
-
function writeFile(file, content) { mkdirp(path.dirname(file)); fs.writeFileSync(file, content); }
|
|
252
350
|
|
|
253
351
|
function adapterName(entry) {
|
|
254
352
|
return entry.isPersona ? entry.name : `${agentPrefix}${entry.name}`;
|
|
@@ -269,8 +367,8 @@ function loadPersonaPrompt(persona) {
|
|
|
269
367
|
return prompt;
|
|
270
368
|
}
|
|
271
369
|
|
|
272
|
-
function buildAgentRoster(allEntries) {
|
|
273
|
-
return allEntries.map((e) => `- ${adapterName(e)}: ${e.description}`).join("\n");
|
|
370
|
+
export function buildAgentRoster(allEntries) {
|
|
371
|
+
return allEntries.map((e) => `- ${adapterName(e)}: ${e.when_to_use || e.description}`).join("\n");
|
|
274
372
|
}
|
|
275
373
|
|
|
276
374
|
function buildModelGuidanceBlock(entry) {
|
|
@@ -304,7 +402,7 @@ function buildPrompt(entry, allEntries, platform) {
|
|
|
304
402
|
fallback: entry.prompt || '',
|
|
305
403
|
}).prompt;
|
|
306
404
|
|
|
307
|
-
prompt = inlineRoleAntiPatterns(prompt, root, entry.name);
|
|
405
|
+
prompt = inlineRoleAntiPatterns(prompt, root, entry.name, console.warn, { preload: entry.preloadRoleGuidance === true });
|
|
308
406
|
|
|
309
407
|
if (entry.injectAgentRoster && allEntries) {
|
|
310
408
|
const roster = buildAgentRoster(allEntries);
|
|
@@ -325,7 +423,20 @@ function buildPrompt(entry, allEntries, platform) {
|
|
|
325
423
|
const wordCount = prompt.split(/\s+/).filter(Boolean).length;
|
|
326
424
|
const effectiveCap = Number(entry.wordCapOverride) > 0 ? entry.wordCapOverride : PROMPT_WORD_CAP;
|
|
327
425
|
if (wordCount > effectiveCap) {
|
|
328
|
-
|
|
426
|
+
const msg = `[sync] ${entry.name}: prompt is ${wordCount} words (cap ${effectiveCap})`;
|
|
427
|
+
if (process.env.CONSTRUCT_SYNC_FORCE === '1' || process.argv.includes('--force')) {
|
|
428
|
+
console.warn(`${msg} — proceeding due to --force / CONSTRUCT_SYNC_FORCE=1.`);
|
|
429
|
+
} else {
|
|
430
|
+
console.error(`${msg}`);
|
|
431
|
+
console.error(
|
|
432
|
+
`[sync] Hard cap exceeded. Options:\n` +
|
|
433
|
+
` - trim the prompt body or move detail to a skill (preferred)\n` +
|
|
434
|
+
` - set "wordCapOverride": <N> on this entry in agents/registry.json with a written reason\n` +
|
|
435
|
+
` - re-run with --force or CONSTRUCT_SYNC_FORCE=1 as a temporary escape hatch\n` +
|
|
436
|
+
`Prompt budget is a hard contract because every over-cap agent degrades every session that dispatches it.`,
|
|
437
|
+
);
|
|
438
|
+
process.exit(1);
|
|
439
|
+
}
|
|
329
440
|
}
|
|
330
441
|
|
|
331
442
|
return prompt;
|
|
@@ -349,6 +460,7 @@ function readManifest(dir) {
|
|
|
349
460
|
}
|
|
350
461
|
|
|
351
462
|
function writeManifest(dir, files) {
|
|
463
|
+
if (DRY_RUN) return;
|
|
352
464
|
fs.writeFileSync(path.join(dir, MANIFEST_FILE), [...files].sort().join("\n") + "\n");
|
|
353
465
|
}
|
|
354
466
|
|
|
@@ -360,7 +472,7 @@ function removeStaleAdapters(dir, ext, entries) {
|
|
|
360
472
|
expected.add(`${adapterName(e)}${ext}`);
|
|
361
473
|
}
|
|
362
474
|
|
|
363
|
-
//
|
|
475
|
+
// Stale manifest entries — delete files not in the current expected set.
|
|
364
476
|
const previouslyWritten = readManifest(dir);
|
|
365
477
|
for (const file of previouslyWritten) {
|
|
366
478
|
if (!expected.has(file) && fs.existsSync(path.join(dir, file))) {
|
|
@@ -421,11 +533,84 @@ ${buildPrompt(entry, allEntries, "claude")}
|
|
|
421
533
|
`;
|
|
422
534
|
}
|
|
423
535
|
|
|
536
|
+
/**
|
|
537
|
+
* Rewrite the home-mode hook command pattern
|
|
538
|
+
* node "$HOME/.construct/lib/hooks/<name>.mjs"
|
|
539
|
+
* into the project-portable form
|
|
540
|
+
* node .construct/run.mjs hook <name>
|
|
541
|
+
* so the resulting settings.json works on any clone where the project ships
|
|
542
|
+
* the .construct/ launcher (committed by `npm install`'s postinstall or by
|
|
543
|
+
* `construct init`). The launcher resolves Construct via node_modules → npx
|
|
544
|
+
* → globally-installed CLI → cached binary → docker, in that order, so it
|
|
545
|
+
* works for non-Node ecosystems too. Other commands (inline node -e
|
|
546
|
+
* snippets, npx block-no-verify@…) are left untouched.
|
|
547
|
+
*/
|
|
548
|
+
function makeHooksPortable(hooksJson) {
|
|
549
|
+
// Operate on the in-memory object so we don't fight JSON string escaping.
|
|
550
|
+
const replaceCommand = (cmd) => {
|
|
551
|
+
if (typeof cmd !== 'string') return cmd;
|
|
552
|
+
const m = cmd.match(/^node\s+"?\$HOME\/\.construct\/lib\/hooks\/([a-z0-9-]+)\.mjs"?\s*(.*)$/);
|
|
553
|
+
if (!m) return cmd;
|
|
554
|
+
const [, name, rest] = m;
|
|
555
|
+
return `node .construct/run.mjs hook ${name}${rest ? ' ' + rest.trim() : ''}`;
|
|
556
|
+
};
|
|
557
|
+
|
|
558
|
+
const walk = (node) => {
|
|
559
|
+
if (Array.isArray(node)) return node.map(walk);
|
|
560
|
+
if (node && typeof node === 'object') {
|
|
561
|
+
const out = {};
|
|
562
|
+
for (const [k, v] of Object.entries(node)) {
|
|
563
|
+
out[k] = k === 'command' ? replaceCommand(v) : walk(v);
|
|
564
|
+
}
|
|
565
|
+
return out;
|
|
566
|
+
}
|
|
567
|
+
return node;
|
|
568
|
+
};
|
|
569
|
+
|
|
570
|
+
return JSON.stringify(walk(hooksJson));
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
/**
|
|
574
|
+
* Materialise a project-local `.claude/settings.json` from the home template,
|
|
575
|
+
* with hook commands rewritten to be path-relative to whatever Construct
|
|
576
|
+
* install the project carries. Merges into an existing settings.json
|
|
577
|
+
* if one is already in the project; otherwise creates a fresh one.
|
|
578
|
+
*/
|
|
579
|
+
function writeProjectClaudeSettings(targetDir) {
|
|
580
|
+
const settingsPath = path.join(targetDir, ".claude", "settings.json");
|
|
581
|
+
const templatePath = path.join(root, "platforms", "claude", "settings.template.json");
|
|
582
|
+
if (!fs.existsSync(templatePath)) return;
|
|
583
|
+
|
|
584
|
+
const template = JSON.parse(fs.readFileSync(templatePath, "utf8"));
|
|
585
|
+
|
|
586
|
+
const existing = fs.existsSync(settingsPath)
|
|
587
|
+
? JSON.parse(fs.readFileSync(settingsPath, "utf8"))
|
|
588
|
+
: {};
|
|
589
|
+
|
|
590
|
+
if (template.hooks) {
|
|
591
|
+
existing.hooks = JSON.parse(makeHooksPortable(template.hooks));
|
|
592
|
+
}
|
|
593
|
+
if (template.permissions) {
|
|
594
|
+
existing.permissions ??= template.permissions;
|
|
595
|
+
}
|
|
596
|
+
if (template.mcpServers) {
|
|
597
|
+
existing.mcpServers ??= {};
|
|
598
|
+
for (const [id, mcpDef] of Object.entries(template.mcpServers)) {
|
|
599
|
+
if (existing.mcpServers[id]) continue;
|
|
600
|
+
existing.mcpServers[id] = mcpDef;
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
if (DRY_RUN) return;
|
|
605
|
+
mkdirp(path.dirname(settingsPath));
|
|
606
|
+
writeFile(settingsPath, JSON.stringify(existing, null, 2) + "\n");
|
|
607
|
+
}
|
|
608
|
+
|
|
424
609
|
function syncClaude(entries, targetDir = null) {
|
|
425
610
|
const claudeAgentsDir = targetDir
|
|
426
611
|
? path.join(targetDir, ".claude", "agents")
|
|
427
612
|
: path.join(home, ".claude", "agents");
|
|
428
|
-
mkdirp(claudeAgentsDir);
|
|
613
|
+
if (!DRY_RUN) mkdirp(claudeAgentsDir);
|
|
429
614
|
|
|
430
615
|
for (const entry of entries) {
|
|
431
616
|
const name = adapterName(entry);
|
|
@@ -434,6 +619,11 @@ function syncClaude(entries, targetDir = null) {
|
|
|
434
619
|
}
|
|
435
620
|
removeStaleAdapters(claudeAgentsDir, ".md", entries);
|
|
436
621
|
|
|
622
|
+
if (targetDir) {
|
|
623
|
+
writeProjectClaudeSettings(targetDir);
|
|
624
|
+
return;
|
|
625
|
+
}
|
|
626
|
+
|
|
437
627
|
if (!targetDir) {
|
|
438
628
|
const claudeMdPath = path.join(home, ".claude", "CLAUDE.md");
|
|
439
629
|
const existing = fs.existsSync(claudeMdPath) ? fs.readFileSync(claudeMdPath, "utf8") : "# Claude Global Instructions\n";
|
|
@@ -475,7 +665,7 @@ ${agentList || "(all specialists are internal — routed through Construct)"}`;
|
|
|
475
665
|
if (settings.mcpServers[id] && !hasPlaceholder) continue;
|
|
476
666
|
settings.mcpServers[id] = buildClaudeMcpEntry(id, mcpDef, process.env);
|
|
477
667
|
}
|
|
478
|
-
fs.writeFileSync(claudeSettingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
668
|
+
if (!DRY_RUN) fs.writeFileSync(claudeSettingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
479
669
|
}
|
|
480
670
|
}
|
|
481
671
|
}
|
|
@@ -510,10 +700,14 @@ function hasCodexMcpTable(text, id) {
|
|
|
510
700
|
return new RegExp(`^\\[mcp_servers\\.(?:${escapeRegExp(id)}|${escapeRegExp(tomlString(id))})\\]`, "m").test(text);
|
|
511
701
|
}
|
|
512
702
|
|
|
703
|
+
function isCodexMcpSupported(id) {
|
|
704
|
+
return id !== "memory";
|
|
705
|
+
}
|
|
706
|
+
|
|
513
707
|
function syncCodex(entries) {
|
|
514
708
|
const codexDir = path.join(home, ".codex");
|
|
515
709
|
const codexAgentsDir = path.join(codexDir, "agents");
|
|
516
|
-
mkdirp(codexAgentsDir);
|
|
710
|
+
if (!DRY_RUN) mkdirp(codexAgentsDir);
|
|
517
711
|
|
|
518
712
|
for (const entry of entries) {
|
|
519
713
|
writeFile(path.join(codexAgentsDir, `${adapterName(entry)}.toml`), codexAgentToml(entry, entries));
|
|
@@ -524,10 +718,11 @@ function syncCodex(entries) {
|
|
|
524
718
|
const existing = removeDanglingConstructMcpMarkers(removeDanglingConstructMcpTimeouts(readCodexConfig(configPath)));
|
|
525
719
|
const entryNames = entries.map(adapterName);
|
|
526
720
|
const registryMcp = registry.mcpServers ?? {};
|
|
527
|
-
const
|
|
721
|
+
const existingMcpIds = Object.keys(registryMcp).filter((id) => hasCodexMcpTable(existing, id));
|
|
722
|
+
const mcpIds = existingMcpIds.filter(isCodexMcpSupported);
|
|
528
723
|
const withoutManagedTables = removeDanglingConstructMcpMarkers(removeTomlTables(
|
|
529
724
|
removeCodexAgentTables(existing, entryNames),
|
|
530
|
-
|
|
725
|
+
existingMcpIds.flatMap((id) => [`mcp_servers.${id}`, `mcp_servers.${tomlString(id)}`]),
|
|
531
726
|
));
|
|
532
727
|
const hasAgentsRoot = /^\[agents\]\s*$/m.test(withoutManagedTables);
|
|
533
728
|
const rootBlock = hasAgentsRoot ? "" : "[agents]\nmax_threads = 6\nmax_depth = 1\n\n";
|
|
@@ -570,7 +765,7 @@ When using this prompt, stay within the role above and adapt to the current repo
|
|
|
570
765
|
|
|
571
766
|
function syncCopilot(entries) {
|
|
572
767
|
const promptsDir = path.join(home, ".github", "prompts");
|
|
573
|
-
mkdirp(promptsDir);
|
|
768
|
+
if (!DRY_RUN) mkdirp(promptsDir);
|
|
574
769
|
for (const entry of entries) {
|
|
575
770
|
writeFile(path.join(promptsDir, `${adapterName(entry)}.prompt.md`), copilotPrompt(entry, entries));
|
|
576
771
|
}
|
|
@@ -634,7 +829,7 @@ function syncVSCode() {
|
|
|
634
829
|
mcpServers[id] = buildClaudeMcpEntry(id, mcpDef, process.env);
|
|
635
830
|
}
|
|
636
831
|
settings["github.copilot.mcpServers"] = mcpServers;
|
|
637
|
-
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
832
|
+
if (!DRY_RUN) fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
638
833
|
synced = true;
|
|
639
834
|
} catch {
|
|
640
835
|
// Skip unreadable settings files
|
|
@@ -661,7 +856,7 @@ function syncCursor() {
|
|
|
661
856
|
if (config.mcpServers[id] && !hasPlaceholder) continue;
|
|
662
857
|
config.mcpServers[id] = buildClaudeMcpEntry(id, mcpDef, process.env);
|
|
663
858
|
}
|
|
664
|
-
fs.writeFileSync(cursorMcpPath, JSON.stringify(config, null, 2) + "\n");
|
|
859
|
+
if (!DRY_RUN) fs.writeFileSync(cursorMcpPath, JSON.stringify(config, null, 2) + "\n");
|
|
665
860
|
return true;
|
|
666
861
|
} catch {
|
|
667
862
|
return false;
|
|
@@ -757,6 +952,10 @@ function syncOpencode(entries) {
|
|
|
757
952
|
for (const [id, mcpDef] of Object.entries(registryMcp)) {
|
|
758
953
|
const openCodeId = getOpenCodeMcpId(id);
|
|
759
954
|
if (openCodeId !== id) delete config.mcp[id];
|
|
955
|
+
if (id === 'memory' && config.mcp.cass && !config.mcp[openCodeId]) {
|
|
956
|
+
config.mcp[openCodeId] = config.mcp.cass;
|
|
957
|
+
}
|
|
958
|
+
if (id === 'memory') delete config.mcp.cass;
|
|
760
959
|
const existing = JSON.stringify(config.mcp[openCodeId] ?? "");
|
|
761
960
|
const hasPlaceholder = existing.includes("__") && existing.includes("__");
|
|
762
961
|
const argsHaveTemplates = (mcpDef.args ?? []).some((a) => typeof a === 'string' && a.includes('__'));
|
|
@@ -796,7 +995,7 @@ function syncOpencode(entries) {
|
|
|
796
995
|
writeOpenCodeConfig(config, configPath);
|
|
797
996
|
|
|
798
997
|
const sourcePluginsDir = path.join(root, "platforms", "opencode", "plugins");
|
|
799
|
-
if (fs.existsSync(sourcePluginsDir)) {
|
|
998
|
+
if (fs.existsSync(sourcePluginsDir) && !DRY_RUN) {
|
|
800
999
|
mkdirp(pluginsDir);
|
|
801
1000
|
for (const file of fs.readdirSync(sourcePluginsDir)) {
|
|
802
1001
|
if (!file.endsWith(".js") && !file.endsWith(".ts")) continue;
|
|
@@ -808,7 +1007,7 @@ function syncOpencode(entries) {
|
|
|
808
1007
|
}
|
|
809
1008
|
|
|
810
1009
|
config.plugin = [...config.plugin, managedPluginPath];
|
|
811
|
-
writeOpenCodeConfig(config, configPath);
|
|
1010
|
+
if (!DRY_RUN) writeOpenCodeConfig(config, configPath);
|
|
812
1011
|
|
|
813
1012
|
return true;
|
|
814
1013
|
}
|
|
@@ -828,19 +1027,20 @@ function syncCommands(targetDir = null) {
|
|
|
828
1027
|
if (!domain.isDirectory()) continue;
|
|
829
1028
|
const domainDir = path.join(sourceCommandsDir, domain.name);
|
|
830
1029
|
const targetDomainDir = path.join(claudeCommandsDir, domain.name);
|
|
831
|
-
mkdirp(targetDomainDir);
|
|
1030
|
+
if (!DRY_RUN) mkdirp(targetDomainDir);
|
|
832
1031
|
|
|
833
1032
|
for (const file of fs.readdirSync(domainDir)) {
|
|
834
1033
|
if (!file.endsWith(".md")) continue;
|
|
1034
|
+
count++;
|
|
1035
|
+
if (DRY_RUN) continue;
|
|
835
1036
|
const source = path.join(domainDir, file);
|
|
836
1037
|
const target = path.join(targetDomainDir, file);
|
|
837
1038
|
fs.copyFileSync(source, target);
|
|
838
|
-
count++;
|
|
839
1039
|
}
|
|
840
1040
|
}
|
|
841
1041
|
|
|
842
1042
|
// Clean up stale command files not in source
|
|
843
|
-
if (fs.existsSync(claudeCommandsDir)) {
|
|
1043
|
+
if (!DRY_RUN && fs.existsSync(claudeCommandsDir)) {
|
|
844
1044
|
for (const domain of fs.readdirSync(claudeCommandsDir, { withFileTypes: true })) {
|
|
845
1045
|
if (!domain.isDirectory()) continue;
|
|
846
1046
|
const sourceDomainDir = path.join(sourceCommandsDir, domain.name);
|
|
@@ -865,35 +1065,80 @@ function syncCommands(targetDir = null) {
|
|
|
865
1065
|
const projectDir = process.argv.includes("--project") ? process.cwd() : null;
|
|
866
1066
|
const entries = buildEntries();
|
|
867
1067
|
|
|
868
|
-
if (
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
}
|
|
873
|
-
const
|
|
874
|
-
const
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
1068
|
+
if (COMPRESS_PERSONAS) {
|
|
1069
|
+
// Run the engine's Compressor on every persona prompt so the runtime
|
|
1070
|
+
// adapter file is shorter than the source persona on disk. The source
|
|
1071
|
+
// file stays unchanged so authors keep editing the readable version.
|
|
1072
|
+
const { getEngine } = await import('./lib/engine/index.mjs');
|
|
1073
|
+
const engine = await getEngine({ rootDir: root });
|
|
1074
|
+
const compressor = engine.layers.compressor;
|
|
1075
|
+
let totalIn = 0;
|
|
1076
|
+
let totalOut = 0;
|
|
1077
|
+
for (const entry of entries) {
|
|
1078
|
+
if (!entry.isPersona || !entry.prompt) continue;
|
|
1079
|
+
const before = entry.prompt;
|
|
1080
|
+
try {
|
|
1081
|
+
const after = await compressor.compress(before, { ratio: 0.6 });
|
|
1082
|
+
if (typeof after === 'string' && after.length > 0) {
|
|
1083
|
+
entry.prompt = after;
|
|
1084
|
+
totalIn += before.length;
|
|
1085
|
+
totalOut += after.length;
|
|
1086
|
+
}
|
|
1087
|
+
} catch (err) {
|
|
1088
|
+
console.warn(`compress-personas: skipping ${entry.name}: ${err.message}`);
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
if (totalIn > 0) {
|
|
1092
|
+
const ratio = ((totalOut / totalIn) * 100).toFixed(0);
|
|
1093
|
+
console.log(`compress-personas: ${totalIn} → ${totalOut} chars (${ratio}%) across ${entries.filter((e) => e.isPersona).length} personas`);
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
acquireLock();
|
|
1098
|
+
try {
|
|
1099
|
+
if (projectDir) {
|
|
1100
|
+
syncClaude(entries, projectDir);
|
|
1101
|
+
const cmdCount = syncCommands(projectDir);
|
|
1102
|
+
if (DRY_RUN) {
|
|
1103
|
+
printDryRunDiff();
|
|
1104
|
+
} else {
|
|
1105
|
+
commitStaging();
|
|
1106
|
+
console.log(`Synced ${entries.length} agents + ${cmdCount} commands to ${path.join(projectDir, ".claude/")} (project mode).`);
|
|
1107
|
+
}
|
|
1108
|
+
} else {
|
|
1109
|
+
const personaCount = entries.filter((e) => e.isPersona).length;
|
|
1110
|
+
const agentCount = entries.filter((e) => !e.isPersona).length;
|
|
1111
|
+
|
|
1112
|
+
syncCodex(entries);
|
|
1113
|
+
syncClaude(entries);
|
|
1114
|
+
syncCopilot(entries);
|
|
1115
|
+
const opencodeOk = syncOpencode(entries);
|
|
1116
|
+
const vscodeOk = syncVSCode();
|
|
1117
|
+
const cursorOk = syncCursor();
|
|
1118
|
+
const cmdCount = syncCommands();
|
|
1119
|
+
|
|
1120
|
+
if (DRY_RUN) {
|
|
1121
|
+
printDryRunDiff();
|
|
1122
|
+
} else {
|
|
1123
|
+
commitStaging();
|
|
1124
|
+
|
|
1125
|
+
const targets = [
|
|
1126
|
+
"Codex",
|
|
1127
|
+
"Claude Code",
|
|
1128
|
+
"Copilot",
|
|
1129
|
+
opencodeOk && "OpenCode",
|
|
1130
|
+
vscodeOk && "VS Code",
|
|
1131
|
+
cursorOk && "Cursor",
|
|
1132
|
+
].filter(Boolean).join(", ");
|
|
1133
|
+
console.log(`Synced ${personaCount} personas + ${agentCount} specialists + ${cmdCount} commands to ${targets}.`);
|
|
1134
|
+
|
|
1135
|
+
// Regenerate shell completions so new commands are immediately tab-completable
|
|
1136
|
+
const completionsDir = generateCompletions();
|
|
1137
|
+
if (completionsDir) {
|
|
1138
|
+
console.log(`Completions updated → ${completionsDir}`);
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
898
1141
|
}
|
|
1142
|
+
} finally {
|
|
1143
|
+
releaseLock();
|
|
899
1144
|
}
|