@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,349 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/embed/artifact.mjs — On-demand and embed-triggered artifact generation.
|
|
3
|
+
*
|
|
4
|
+
* Generates PRDs, RFCs, and ADRs as structured markdown files under docs/.
|
|
5
|
+
* Each artifact type has a fixed schema and front-matter. Numbering is auto-
|
|
6
|
+
* incremented by scanning the target directory. Entry points cover the CLI,
|
|
7
|
+
* the embed daemon when analysis triggers a recommendation, and the dashboard
|
|
8
|
+
* API.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { readFileSync, writeFileSync, existsSync, readdirSync, mkdirSync } from 'fs';
|
|
12
|
+
import { join } from 'path';
|
|
13
|
+
import { randomUUID } from 'crypto';
|
|
14
|
+
|
|
15
|
+
// ── Constants ──────────────────────────────────────────────────────────────
|
|
16
|
+
|
|
17
|
+
const ARTIFACT_TYPES = ['prd', 'adr', 'rfc'];
|
|
18
|
+
|
|
19
|
+
const DIRS = {
|
|
20
|
+
prd: 'docs/prd',
|
|
21
|
+
adr: 'docs/adr',
|
|
22
|
+
rfc: 'docs/rfc',
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
// ── Numbering ──────────────────────────────────────────────────────────────
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Scan a directory for existing artifacts and return the next sequence number.
|
|
29
|
+
* Files must match the pattern NNNN-*.md.
|
|
30
|
+
*/
|
|
31
|
+
function nextSequenceNumber(dir) {
|
|
32
|
+
if (!existsSync(dir)) return 1;
|
|
33
|
+
const files = readdirSync(dir).filter(f => /^\d{4}-.*\.md$/.test(f));
|
|
34
|
+
if (!files.length) return 1;
|
|
35
|
+
const nums = files.map(f => parseInt(f.slice(0, 4), 10)).filter(n => !isNaN(n));
|
|
36
|
+
return Math.max(...nums) + 1;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function padNum(n) {
|
|
40
|
+
return String(n).padStart(4, '0');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function slugify(title) {
|
|
44
|
+
return title
|
|
45
|
+
.toLowerCase()
|
|
46
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
47
|
+
.replace(/^-+|-+$/g, '')
|
|
48
|
+
.slice(0, 60);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// ── Front-matter ───────────────────────────────────────────────────────────
|
|
52
|
+
|
|
53
|
+
function buildFrontMatter(fields) {
|
|
54
|
+
const lines = ['---'];
|
|
55
|
+
for (const [k, v] of Object.entries(fields)) {
|
|
56
|
+
lines.push(`${k}: ${v}`);
|
|
57
|
+
}
|
|
58
|
+
lines.push('---', '');
|
|
59
|
+
return lines.join('\n');
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// ── Templates ──────────────────────────────────────────────────────────────
|
|
63
|
+
|
|
64
|
+
function prdTemplate({ number, title, owner, problem, goals, nonGoals, users }) {
|
|
65
|
+
const num = padNum(number);
|
|
66
|
+
const date = new Date().toISOString().slice(0, 10);
|
|
67
|
+
const fm = buildFrontMatter({
|
|
68
|
+
cx_doc_id: randomUUID(),
|
|
69
|
+
created_at: new Date().toISOString(),
|
|
70
|
+
updated_at: new Date().toISOString(),
|
|
71
|
+
generator: 'construct/artifact',
|
|
72
|
+
status: 'draft',
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
return `${fm}# PRD-${num}: ${title}
|
|
76
|
+
|
|
77
|
+
- **Date**: ${date}
|
|
78
|
+
- **Owner**: ${owner || 'TBD'}
|
|
79
|
+
- **Status**: draft
|
|
80
|
+
|
|
81
|
+
## Problem
|
|
82
|
+
|
|
83
|
+
${problem || 'TODO: Describe the problem this PRD addresses.'}
|
|
84
|
+
|
|
85
|
+
## Users
|
|
86
|
+
|
|
87
|
+
| Segment | Description | Current workaround |
|
|
88
|
+
|---|---|---|
|
|
89
|
+
| TODO | TODO | TODO |
|
|
90
|
+
|
|
91
|
+
## Goals
|
|
92
|
+
|
|
93
|
+
${(goals || []).map(g => `- ${g}`).join('\n') || '- TODO'}
|
|
94
|
+
|
|
95
|
+
## Non-goals
|
|
96
|
+
|
|
97
|
+
${(nonGoals || []).map(g => `- ${g}`).join('\n') || '- TODO'}
|
|
98
|
+
|
|
99
|
+
## Requirements
|
|
100
|
+
|
|
101
|
+
### Functional
|
|
102
|
+
|
|
103
|
+
| ID | Requirement | Priority |
|
|
104
|
+
|---|---|---|
|
|
105
|
+
| F1 | TODO | P0 |
|
|
106
|
+
|
|
107
|
+
### Non-functional
|
|
108
|
+
|
|
109
|
+
| ID | Requirement | Target |
|
|
110
|
+
|---|---|---|
|
|
111
|
+
| NF1 | TODO | TODO |
|
|
112
|
+
|
|
113
|
+
## Open questions
|
|
114
|
+
|
|
115
|
+
- TODO
|
|
116
|
+
`;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function adrTemplate({ number, title, status, context, decision, consequences, alternatives }) {
|
|
120
|
+
const num = padNum(number);
|
|
121
|
+
const fm = buildFrontMatter({
|
|
122
|
+
cx_doc_id: randomUUID(),
|
|
123
|
+
created_at: new Date().toISOString(),
|
|
124
|
+
updated_at: new Date().toISOString(),
|
|
125
|
+
generator: 'construct/artifact',
|
|
126
|
+
body_hash: 'sha256:placeholder',
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
const altSection = (alternatives || []).length
|
|
130
|
+
? alternatives.map(a => `### ${a.title}\n\n${a.reason || 'Rejected.'}`).join('\n\n')
|
|
131
|
+
: '### Alternative A\n\nTODO: describe and explain rejection.';
|
|
132
|
+
|
|
133
|
+
return `${fm}# ADR-${num}: ${title}
|
|
134
|
+
|
|
135
|
+
## Status
|
|
136
|
+
|
|
137
|
+
${status || 'Proposed'}
|
|
138
|
+
|
|
139
|
+
## Context
|
|
140
|
+
|
|
141
|
+
${context || 'TODO: Describe the context and problem that led to this decision.'}
|
|
142
|
+
|
|
143
|
+
## Decision
|
|
144
|
+
|
|
145
|
+
${decision || 'TODO: State the architectural decision.'}
|
|
146
|
+
|
|
147
|
+
## Consequences
|
|
148
|
+
|
|
149
|
+
${(consequences || []).map(c => `- ${c}`).join('\n') || '- TODO'}
|
|
150
|
+
|
|
151
|
+
## Rejected alternatives
|
|
152
|
+
|
|
153
|
+
${altSection}
|
|
154
|
+
`;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function rfcTemplate({ number, title, author, summary, motivation, design, drawbacks, alternatives, unresolved }) {
|
|
158
|
+
const num = padNum(number);
|
|
159
|
+
const date = new Date().toISOString().slice(0, 10);
|
|
160
|
+
const fm = buildFrontMatter({
|
|
161
|
+
cx_doc_id: randomUUID(),
|
|
162
|
+
created_at: new Date().toISOString(),
|
|
163
|
+
updated_at: new Date().toISOString(),
|
|
164
|
+
generator: 'construct/artifact',
|
|
165
|
+
status: 'draft',
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
return `${fm}# RFC-${num}: ${title}
|
|
169
|
+
|
|
170
|
+
- **Date**: ${date}
|
|
171
|
+
- **Author**: ${author || 'TBD'}
|
|
172
|
+
- **Status**: draft
|
|
173
|
+
|
|
174
|
+
## Summary
|
|
175
|
+
|
|
176
|
+
${summary || 'TODO: One-paragraph summary of the change being proposed.'}
|
|
177
|
+
|
|
178
|
+
## Motivation
|
|
179
|
+
|
|
180
|
+
${motivation || 'TODO: Why is this change needed? What problem does it solve?'}
|
|
181
|
+
|
|
182
|
+
## Design
|
|
183
|
+
|
|
184
|
+
${design || 'TODO: Detailed description of the proposed design or implementation.'}
|
|
185
|
+
|
|
186
|
+
## Drawbacks
|
|
187
|
+
|
|
188
|
+
${(drawbacks || []).map(d => `- ${d}`).join('\n') || '- TODO'}
|
|
189
|
+
|
|
190
|
+
## Alternatives
|
|
191
|
+
|
|
192
|
+
${(alternatives || []).map(a => `- ${a}`).join('\n') || '- TODO'}
|
|
193
|
+
|
|
194
|
+
## Unresolved questions
|
|
195
|
+
|
|
196
|
+
${(unresolved || []).map(u => `- ${u}`).join('\n') || '- TODO'}
|
|
197
|
+
`;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// ── Core generator ─────────────────────────────────────────────────────────
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Generate an artifact file.
|
|
204
|
+
*
|
|
205
|
+
* @param {object} opts
|
|
206
|
+
* @param {string} opts.type - 'prd' | 'adr' | 'rfc'
|
|
207
|
+
* @param {string} opts.title - Human-readable title (required)
|
|
208
|
+
* @param {string} [opts.rootDir] - Project root (default: cwd)
|
|
209
|
+
* @param {object} [opts.fields] - Type-specific fields (see templates above)
|
|
210
|
+
* @param {boolean} [opts.dryRun] - If true, return content without writing
|
|
211
|
+
* @returns {{ path: string, content: string, number: number }}
|
|
212
|
+
*/
|
|
213
|
+
export function generateArtifact({ type, title, rootDir, fields = {}, dryRun = false }) {
|
|
214
|
+
if (!ARTIFACT_TYPES.includes(type)) {
|
|
215
|
+
throw new Error(`Unknown artifact type "${type}". Must be one of: ${ARTIFACT_TYPES.join(', ')}`);
|
|
216
|
+
}
|
|
217
|
+
if (!title || typeof title !== 'string' || !title.trim()) {
|
|
218
|
+
throw new Error('title is required');
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const root = rootDir || process.cwd();
|
|
222
|
+
const dir = join(root, DIRS[type]);
|
|
223
|
+
|
|
224
|
+
const number = nextSequenceNumber(dir);
|
|
225
|
+
const slug = slugify(title.trim());
|
|
226
|
+
const filename = `${padNum(number)}-${slug}.md`;
|
|
227
|
+
const filePath = join(dir, filename);
|
|
228
|
+
|
|
229
|
+
let content;
|
|
230
|
+
const ctx = { number, title: title.trim(), ...fields };
|
|
231
|
+
|
|
232
|
+
if (type === 'prd') content = prdTemplate(ctx);
|
|
233
|
+
else if (type === 'adr') content = adrTemplate(ctx);
|
|
234
|
+
else content = rfcTemplate(ctx);
|
|
235
|
+
|
|
236
|
+
if (!dryRun) {
|
|
237
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
238
|
+
writeFileSync(filePath, content, 'utf8');
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
return { path: filePath, relativePath: join(DIRS[type], filename), content, number };
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// ── List artifacts ─────────────────────────────────────────────────────────
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* List all artifacts of a given type (or all types).
|
|
248
|
+
*
|
|
249
|
+
* @param {object} opts
|
|
250
|
+
* @param {string} [opts.type] - Filter by type; if omitted, returns all
|
|
251
|
+
* @param {string} [opts.rootDir] - Project root
|
|
252
|
+
* @returns {Array<{ type, number, slug, filename, title, status, path }>}
|
|
253
|
+
*/
|
|
254
|
+
export function listArtifacts({ type, rootDir } = {}) {
|
|
255
|
+
const root = rootDir || process.cwd();
|
|
256
|
+
const types = type ? [type] : ARTIFACT_TYPES;
|
|
257
|
+
const results = [];
|
|
258
|
+
|
|
259
|
+
for (const t of types) {
|
|
260
|
+
const dir = join(root, DIRS[t]);
|
|
261
|
+
if (!existsSync(dir)) continue;
|
|
262
|
+
|
|
263
|
+
for (const file of readdirSync(dir).sort()) {
|
|
264
|
+
if (!/^\d{4}-.*\.md$/.test(file)) continue;
|
|
265
|
+
const filePath = join(dir, file);
|
|
266
|
+
let titleLine = file.replace('.md', '').replace(/^\d{4}-/, '').replace(/-/g, ' ');
|
|
267
|
+
let status = 'unknown';
|
|
268
|
+
|
|
269
|
+
try {
|
|
270
|
+
const raw = readFileSync(filePath, 'utf8');
|
|
271
|
+
const headingMatch = raw.match(/^# .+?: (.+)$/m);
|
|
272
|
+
if (headingMatch) titleLine = headingMatch[1].trim();
|
|
273
|
+
const statusMatch = raw.match(/\*\*Status\*\*: (\S+)|^## Status\s+\n+(\S[^\n]*)/m);
|
|
274
|
+
if (statusMatch) status = (statusMatch[1] || statusMatch[2] || 'unknown').trim().toLowerCase();
|
|
275
|
+
} catch { /* ignore read errors */ }
|
|
276
|
+
|
|
277
|
+
results.push({
|
|
278
|
+
type: t,
|
|
279
|
+
number: parseInt(file.slice(0, 4), 10),
|
|
280
|
+
filename: file,
|
|
281
|
+
title: titleLine,
|
|
282
|
+
status,
|
|
283
|
+
path: filePath,
|
|
284
|
+
relativePath: join(DIRS[t], file),
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
return results;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// ── Embed trigger ──────────────────────────────────────────────────────────
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Evaluate a snapshot and return artifact recommendations.
|
|
296
|
+
* Called by the embed daemon after each snapshot cycle.
|
|
297
|
+
*
|
|
298
|
+
* @param {object} snapshot - Snapshot from SnapshotEngine
|
|
299
|
+
* @param {object} [opts]
|
|
300
|
+
* @param {string} [opts.rootDir]
|
|
301
|
+
* @returns {Array<{ type, title, reason, fields }>}
|
|
302
|
+
*/
|
|
303
|
+
export function recommendArtifacts(snapshot, { rootDir } = {}) {
|
|
304
|
+
const root = rootDir || process.cwd();
|
|
305
|
+
const existing = listArtifacts({ rootDir: root });
|
|
306
|
+
const recommendations = [];
|
|
307
|
+
|
|
308
|
+
// If no PRD exists, recommend one
|
|
309
|
+
const hasPrd = existing.some(a => a.type === 'prd');
|
|
310
|
+
if (!hasPrd) {
|
|
311
|
+
recommendations.push({
|
|
312
|
+
type: 'prd',
|
|
313
|
+
title: 'Project Overview',
|
|
314
|
+
reason: 'No PRD found. A product requirements document helps align contributors and agents.',
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// If snapshot has errors and no ADR for error handling, recommend one
|
|
319
|
+
const snapshotHasErrors = snapshot?.summary?.some?.(s => s.status === 'error');
|
|
320
|
+
if (snapshotHasErrors) {
|
|
321
|
+
const hasErrorAdr = existing.some(a =>
|
|
322
|
+
a.type === 'adr' && /error|resilience|failure/i.test(a.title)
|
|
323
|
+
);
|
|
324
|
+
if (!hasErrorAdr) {
|
|
325
|
+
recommendations.push({
|
|
326
|
+
type: 'adr',
|
|
327
|
+
title: 'Error Handling and Resilience Strategy',
|
|
328
|
+
reason: 'Snapshot detected provider errors. Document the error-handling strategy.',
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// If there are multiple providers and no integration RFC, recommend one
|
|
334
|
+
const providerCount = snapshot?.providers?.length ?? 0;
|
|
335
|
+
if (providerCount >= 3) {
|
|
336
|
+
const hasIntegrationRfc = existing.some(a =>
|
|
337
|
+
a.type === 'rfc' && /integration|provider/i.test(a.title)
|
|
338
|
+
);
|
|
339
|
+
if (!hasIntegrationRfc) {
|
|
340
|
+
recommendations.push({
|
|
341
|
+
type: 'rfc',
|
|
342
|
+
title: 'Multi-Provider Integration Strategy',
|
|
343
|
+
reason: `${providerCount} providers are configured. Consider documenting the integration approach as an RFC.`,
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
return recommendations;
|
|
349
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/embed/authority-guard.mjs — runtime enforcement of operating profile authority boundaries.
|
|
3
|
+
*
|
|
4
|
+
* Maps the authority fields declared in the embedded operating profile to
|
|
5
|
+
* actual allow/queue/deny decisions. Consults the ApprovalQueue for
|
|
6
|
+
* `approval-queued` actions and lets `autonomous` actions pass immediately.
|
|
7
|
+
*
|
|
8
|
+
* Authority fields (from DEFAULT_OPERATING_PROFILE.authority):
|
|
9
|
+
* - read → autonomous
|
|
10
|
+
* - summarize → autonomous
|
|
11
|
+
* - draftArtifacts → autonomous
|
|
12
|
+
* - createIssues → approval-queued
|
|
13
|
+
* - updateIssues → approval-queued
|
|
14
|
+
* - publishDocs → approval-queued
|
|
15
|
+
* - externalPost → approval-queued
|
|
16
|
+
* - repoWrites → approval-queued
|
|
17
|
+
*
|
|
18
|
+
* Usage:
|
|
19
|
+
* const guard = new AuthorityGuard(operatingProfile, approvalQueue);
|
|
20
|
+
* const result = await guard.check('externalPost', { description: 'Post roadmap to Slack' });
|
|
21
|
+
* if (result.allowed) { ... execute ... }
|
|
22
|
+
* else { ... it's been queued or denied ... }
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
// ─── Action-type → authority-key mapping ────────────────────────────────────
|
|
26
|
+
|
|
27
|
+
const ACTION_TO_AUTHORITY = {
|
|
28
|
+
// Read / summarize
|
|
29
|
+
read: 'read',
|
|
30
|
+
summarize: 'summarize',
|
|
31
|
+
// Artifact drafting
|
|
32
|
+
draftArtifacts: 'draftArtifacts',
|
|
33
|
+
'artifact:prd': 'draftArtifacts',
|
|
34
|
+
'artifact:adr': 'draftArtifacts',
|
|
35
|
+
'artifact:rfc': 'draftArtifacts',
|
|
36
|
+
'artifact:memo': 'draftArtifacts',
|
|
37
|
+
// Issue management
|
|
38
|
+
createIssues: 'createIssues',
|
|
39
|
+
'issue:create': 'createIssues',
|
|
40
|
+
updateIssues: 'updateIssues',
|
|
41
|
+
'issue:update': 'updateIssues',
|
|
42
|
+
// Documentation publishing
|
|
43
|
+
publishDocs: 'publishDocs',
|
|
44
|
+
'docs:publish': 'publishDocs',
|
|
45
|
+
'docs:write': 'publishDocs',
|
|
46
|
+
// External messaging
|
|
47
|
+
externalPost: 'externalPost',
|
|
48
|
+
'slack:post': 'externalPost',
|
|
49
|
+
'email:send': 'externalPost',
|
|
50
|
+
// Repo writes
|
|
51
|
+
repoWrites: 'repoWrites',
|
|
52
|
+
'git:commit': 'repoWrites',
|
|
53
|
+
'git:push': 'repoWrites',
|
|
54
|
+
'git:pr': 'repoWrites',
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Resolve the authority key for an action type.
|
|
59
|
+
* Falls back to the action type itself (for forward-compat).
|
|
60
|
+
*/
|
|
61
|
+
function resolveAuthorityKey(actionType) {
|
|
62
|
+
return ACTION_TO_AUTHORITY[actionType] ?? actionType;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// ─── AuthorityGuard ──────────────────────────────────────────────────────────
|
|
66
|
+
|
|
67
|
+
export class AuthorityGuard {
|
|
68
|
+
#authority;
|
|
69
|
+
#approvalQueue;
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* @param {object} operatingProfile - The embed config's operatingProfile object
|
|
73
|
+
* @param {ApprovalQueue} approvalQueue
|
|
74
|
+
*/
|
|
75
|
+
constructor(operatingProfile, approvalQueue) {
|
|
76
|
+
this.#authority = operatingProfile?.authority ?? {};
|
|
77
|
+
this.#approvalQueue = approvalQueue;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Check whether an action is allowed under the current authority profile.
|
|
82
|
+
*
|
|
83
|
+
* @param {string} actionType - e.g. 'externalPost', 'artifact:prd', 'issue:create'
|
|
84
|
+
* @param {object} [meta] - Optional metadata: { description, payload }
|
|
85
|
+
* @returns {{ allowed: boolean, mode: string, queueId?: string, reason?: string }}
|
|
86
|
+
*/
|
|
87
|
+
async check(actionType, meta = {}) {
|
|
88
|
+
const key = resolveAuthorityKey(actionType);
|
|
89
|
+
const level = this.#authority[key] ?? 'approval-queued'; // fail-safe default
|
|
90
|
+
|
|
91
|
+
if (level === 'autonomous') {
|
|
92
|
+
return { allowed: true, mode: 'autonomous' };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (level === 'denied') {
|
|
96
|
+
return { allowed: false, mode: 'denied', reason: `Authority level for "${key}" is denied` };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// approval-queued: consult the ApprovalQueue
|
|
100
|
+
if (this.#approvalQueue) {
|
|
101
|
+
// Check if there's already an approved item for this action
|
|
102
|
+
const mode = this.#approvalQueue.approvalMode(actionType);
|
|
103
|
+
if (mode === 'auto') {
|
|
104
|
+
return { allowed: true, mode: 'auto-approved' };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Queue the action and return not-yet-allowed
|
|
108
|
+
const queueId = this.#approvalQueue.enqueue({
|
|
109
|
+
type: actionType,
|
|
110
|
+
authorityKey: key,
|
|
111
|
+
description: meta.description ?? actionType,
|
|
112
|
+
payload: meta.payload ?? {},
|
|
113
|
+
requestedAt: new Date().toISOString(),
|
|
114
|
+
});
|
|
115
|
+
return {
|
|
116
|
+
allowed: false,
|
|
117
|
+
mode: 'queued',
|
|
118
|
+
queueId,
|
|
119
|
+
reason: `Action "${actionType}" requires human approval (authority: ${key} = approval-queued)`,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// No approval queue present — default to deny for safety
|
|
124
|
+
return {
|
|
125
|
+
allowed: false,
|
|
126
|
+
mode: 'no-queue',
|
|
127
|
+
reason: `Action "${actionType}" requires approval but no ApprovalQueue is configured`,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Synchronous check — returns allowed: true only for autonomous actions.
|
|
133
|
+
* Use this where async is not possible.
|
|
134
|
+
*/
|
|
135
|
+
checkSync(actionType) {
|
|
136
|
+
const key = resolveAuthorityKey(actionType);
|
|
137
|
+
const level = this.#authority[key] ?? 'approval-queued';
|
|
138
|
+
return { allowed: level === 'autonomous', mode: level };
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Return a summary of the current authority settings for diagnostics.
|
|
143
|
+
*/
|
|
144
|
+
summary() {
|
|
145
|
+
const autonomous = [];
|
|
146
|
+
const queued = [];
|
|
147
|
+
const denied = [];
|
|
148
|
+
for (const [k, v] of Object.entries(this.#authority)) {
|
|
149
|
+
if (v === 'autonomous') autonomous.push(k);
|
|
150
|
+
else if (v === 'denied') denied.push(k);
|
|
151
|
+
else queued.push(k);
|
|
152
|
+
}
|
|
153
|
+
return { autonomous, queued, denied };
|
|
154
|
+
}
|
|
155
|
+
}
|