@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
package/lib/env-config.mjs
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* lib/env-config.mjs —
|
|
2
|
+
* lib/env-config.mjs — Read and write Construct environment configuration files.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Provides helpers to load ~/.cx/env, merge with process.env, and persist
|
|
5
|
+
* key/value pairs back to disk. Backs setup, model-router, and the MCP
|
|
6
|
+
* server when they resolve API keys and model overrides without leaking secrets.
|
|
5
7
|
*/
|
|
6
8
|
import fs from 'node:fs';
|
|
7
9
|
import os from 'node:os';
|
|
@@ -46,8 +48,71 @@ export function writeEnvValues(filePath, values = {}) {
|
|
|
46
48
|
fs.writeFileSync(filePath, `${content}\n`, 'utf8');
|
|
47
49
|
}
|
|
48
50
|
|
|
49
|
-
|
|
51
|
+
function cleanEnvValue(value) {
|
|
52
|
+
return String(value ?? '').trim();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function resolveDatabaseUrl(env = process.env) {
|
|
56
|
+
const explicit = cleanEnvValue(env.DATABASE_URL);
|
|
57
|
+
if (explicit) return explicit;
|
|
58
|
+
|
|
59
|
+
const host = cleanEnvValue(env.DB_HOST);
|
|
60
|
+
const name = cleanEnvValue(env.DB_NAME);
|
|
61
|
+
const user = cleanEnvValue(env.DB_USER);
|
|
62
|
+
const password = cleanEnvValue(env.DB_PASSWORD);
|
|
63
|
+
const port = cleanEnvValue(env.DB_PORT) || '5432';
|
|
64
|
+
|
|
65
|
+
if (!host || !name || !user || !password) return '';
|
|
66
|
+
|
|
67
|
+
return `postgresql://${encodeURIComponent(user)}:${encodeURIComponent(password)}@${host}:${port}/${name}`;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Keys that are expected to be set in process.env and should not trigger
|
|
71
|
+
// shadow warnings (e.g. standard Node/shell vars, CI variables).
|
|
72
|
+
const ENV_SHADOW_IGNORE = new Set([
|
|
73
|
+
'PATH', 'HOME', 'USER', 'SHELL', 'TERM', 'PWD', 'OLDPWD', 'LOGNAME',
|
|
74
|
+
'TMPDIR', 'LANG', 'LC_ALL', 'LC_CTYPE', 'TZ', 'SHLVL', '_',
|
|
75
|
+
'NODE_ENV', 'NODE_PATH', 'NODE_OPTIONS',
|
|
76
|
+
'npm_lifecycle_event', 'npm_package_name', 'npm_package_version',
|
|
77
|
+
]);
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Keys that configure Construct services. When process.env provides one
|
|
81
|
+
* of these and a .env file also sets it to a *different* value, a one-time
|
|
82
|
+
* warning fires so stale exported vars are not silently authoritative.
|
|
83
|
+
*/
|
|
84
|
+
const ENV_SHADOW_WARN = new Set([
|
|
85
|
+
'OPENROUTER_API_KEY', 'ANTHROPIC_API_KEY', 'OPENAI_API_KEY',
|
|
86
|
+
'DASHBOARD_PORT', 'MEMORY_PORT', 'BRIDGE_PORT',
|
|
87
|
+
'CONSTRUCT_INSTANCE_ID',
|
|
88
|
+
'CONSTRUCT_DEPLOYMENT_MODE',
|
|
89
|
+
]);
|
|
90
|
+
|
|
91
|
+
export function loadConstructEnv({ rootDir, homeDir = os.homedir(), env = process.env, warn = true } = {}) {
|
|
50
92
|
const rootEnv = rootDir ? parseEnvFile(path.join(rootDir, '.env')) : {};
|
|
51
93
|
const userEnv = parseEnvFile(getUserEnvPath(homeDir));
|
|
52
|
-
|
|
94
|
+
const fileEnv = { ...rootEnv, ...userEnv };
|
|
95
|
+
|
|
96
|
+
if (warn) {
|
|
97
|
+
for (const key of Object.keys(env)) {
|
|
98
|
+
if (ENV_SHADOW_IGNORE.has(key)) continue;
|
|
99
|
+
if (!ENV_SHADOW_WARN.has(key)) continue;
|
|
100
|
+
if (key in fileEnv && fileEnv[key] !== env[key]) {
|
|
101
|
+
process.stderr.write(
|
|
102
|
+
`[construct] WARNING: process.env.${key} (${env[key].slice(0, 6)}…) shadows ` +
|
|
103
|
+
`config.env value (${fileEnv[key].slice(0, 6)}…). ` +
|
|
104
|
+
`Unset the shell variable or update ~/.construct/config.env.\n`,
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// process.env is the base; config.env (file) values override shell values
|
|
111
|
+
// so that credentials set in the dashboard always win over stale shell exports.
|
|
112
|
+
const merged = { ...env, ...fileEnv };
|
|
113
|
+
const databaseUrl = resolveDatabaseUrl(merged);
|
|
114
|
+
if (databaseUrl && !cleanEnvValue(merged.DATABASE_URL)) {
|
|
115
|
+
merged.DATABASE_URL = databaseUrl;
|
|
116
|
+
}
|
|
117
|
+
return merged;
|
|
53
118
|
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/evals/retrieval-bench.mjs — retrieval benchmark runner with structured metrics.
|
|
3
|
+
*
|
|
4
|
+
* Drives a retrieval function against a set of fixture queries with
|
|
5
|
+
* expected document ids, then summarizes recall@K, precision@K, MRR,
|
|
6
|
+
* latency p50/p95, fallback usage, and empty-result rate. Designed to
|
|
7
|
+
* gate CI on retrieval regressions: pass a stable fixture set, fail
|
|
8
|
+
* the run when any metric drops below threshold.
|
|
9
|
+
*
|
|
10
|
+
* Fixture shape:
|
|
11
|
+
* {
|
|
12
|
+
* name, query, expectedIds: string[], filters?: object,
|
|
13
|
+
* k?: number, mustNotInclude?: string[]
|
|
14
|
+
* }
|
|
15
|
+
*
|
|
16
|
+
* Result shape:
|
|
17
|
+
* {
|
|
18
|
+
* summary: { recallAt5, precisionAt5, mrr, p50LatencyMs, p95LatencyMs,
|
|
19
|
+
* emptyRate, fallbackRate, queries: N, passed: N, regressed: [...] },
|
|
20
|
+
* perQuery: [{ name, recall, precision, reciprocalRank, latencyMs, fallback }]
|
|
21
|
+
* }
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
function percentile(values, p) {
|
|
25
|
+
if (values.length === 0) return 0;
|
|
26
|
+
const sorted = [...values].sort((a, b) => a - b);
|
|
27
|
+
const idx = Math.min(sorted.length - 1, Math.floor((p / 100) * sorted.length));
|
|
28
|
+
return sorted[idx];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function uniq(values) {
|
|
32
|
+
return [...new Set(values)];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function recallForQuery(actualIds, expectedIds) {
|
|
36
|
+
if (expectedIds.length === 0) return 1;
|
|
37
|
+
const hit = expectedIds.filter((id) => actualIds.includes(id)).length;
|
|
38
|
+
return hit / expectedIds.length;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function precisionForQuery(actualIds, expectedIds) {
|
|
42
|
+
if (actualIds.length === 0) return 0;
|
|
43
|
+
const hit = actualIds.filter((id) => expectedIds.includes(id)).length;
|
|
44
|
+
return hit / actualIds.length;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function mrrForQuery(actualIds, expectedIds) {
|
|
48
|
+
for (let i = 0; i < actualIds.length; i++) {
|
|
49
|
+
if (expectedIds.includes(actualIds[i])) return 1 / (i + 1);
|
|
50
|
+
}
|
|
51
|
+
return 0;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Run a benchmark suite.
|
|
56
|
+
*
|
|
57
|
+
* @param {object} opts
|
|
58
|
+
* @param {Array} opts.fixtures
|
|
59
|
+
* @param {Function} opts.search — async (query, options) → { ids, fallback?, results? }
|
|
60
|
+
* @param {object} [opts.thresholds] — optional regression gates
|
|
61
|
+
* @returns {Promise<{summary, perQuery}>}
|
|
62
|
+
*/
|
|
63
|
+
export async function runRetrievalBench({ fixtures, search, thresholds = {} } = {}) {
|
|
64
|
+
if (!Array.isArray(fixtures) || fixtures.length === 0) {
|
|
65
|
+
throw new Error('runRetrievalBench: at least one fixture is required');
|
|
66
|
+
}
|
|
67
|
+
if (typeof search !== 'function') {
|
|
68
|
+
throw new Error('runRetrievalBench: search function is required');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const perQuery = [];
|
|
72
|
+
const latencies = [];
|
|
73
|
+
let emptyCount = 0;
|
|
74
|
+
let fallbackCount = 0;
|
|
75
|
+
|
|
76
|
+
for (const fixture of fixtures) {
|
|
77
|
+
const k = fixture.k || 5;
|
|
78
|
+
const started = Date.now();
|
|
79
|
+
const response = await search(fixture.query, { filters: fixture.filters, k });
|
|
80
|
+
const latency = Date.now() - started;
|
|
81
|
+
latencies.push(latency);
|
|
82
|
+
|
|
83
|
+
const ids = uniq(response.ids || (response.results || []).map((r) => r.id || r.source_path || r.path)).slice(0, k);
|
|
84
|
+
const fallback = Boolean(response.fallback);
|
|
85
|
+
if (fallback) fallbackCount += 1;
|
|
86
|
+
if (ids.length === 0) emptyCount += 1;
|
|
87
|
+
|
|
88
|
+
const recall = recallForQuery(ids, fixture.expectedIds || []);
|
|
89
|
+
const precision = precisionForQuery(ids, fixture.expectedIds || []);
|
|
90
|
+
const reciprocalRank = mrrForQuery(ids, fixture.expectedIds || []);
|
|
91
|
+
const mustNotMatched = (fixture.mustNotInclude || []).some((id) => ids.includes(id));
|
|
92
|
+
|
|
93
|
+
perQuery.push({
|
|
94
|
+
name: fixture.name || fixture.query.slice(0, 40),
|
|
95
|
+
recall,
|
|
96
|
+
precision,
|
|
97
|
+
reciprocalRank,
|
|
98
|
+
latencyMs: latency,
|
|
99
|
+
fallback,
|
|
100
|
+
mustNotMatched,
|
|
101
|
+
returnedIds: ids,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const n = perQuery.length;
|
|
106
|
+
const avg = (selector) => perQuery.reduce((acc, q) => acc + selector(q), 0) / n;
|
|
107
|
+
const summary = {
|
|
108
|
+
queries: n,
|
|
109
|
+
recallAt5: avg((q) => q.recall),
|
|
110
|
+
precisionAt5: avg((q) => q.precision),
|
|
111
|
+
mrr: avg((q) => q.reciprocalRank),
|
|
112
|
+
p50LatencyMs: percentile(latencies, 50),
|
|
113
|
+
p95LatencyMs: percentile(latencies, 95),
|
|
114
|
+
emptyRate: emptyCount / n,
|
|
115
|
+
fallbackRate: fallbackCount / n,
|
|
116
|
+
regressed: [],
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
if (thresholds.minRecallAt5 !== undefined && summary.recallAt5 < thresholds.minRecallAt5) {
|
|
120
|
+
summary.regressed.push({ metric: 'recallAt5', actual: summary.recallAt5, expected: thresholds.minRecallAt5 });
|
|
121
|
+
}
|
|
122
|
+
if (thresholds.minMrr !== undefined && summary.mrr < thresholds.minMrr) {
|
|
123
|
+
summary.regressed.push({ metric: 'mrr', actual: summary.mrr, expected: thresholds.minMrr });
|
|
124
|
+
}
|
|
125
|
+
if (thresholds.maxP95LatencyMs !== undefined && summary.p95LatencyMs > thresholds.maxP95LatencyMs) {
|
|
126
|
+
summary.regressed.push({ metric: 'p95LatencyMs', actual: summary.p95LatencyMs, expected: thresholds.maxP95LatencyMs });
|
|
127
|
+
}
|
|
128
|
+
if (perQuery.some((q) => q.mustNotMatched)) {
|
|
129
|
+
summary.regressed.push({ metric: 'mustNotInclude', actual: perQuery.filter((q) => q.mustNotMatched).map((q) => q.name) });
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
summary.passed = n - summary.regressed.length;
|
|
133
|
+
|
|
134
|
+
return { summary, perQuery };
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export function formatBenchSummary({ summary, perQuery }) {
|
|
138
|
+
const lines = [];
|
|
139
|
+
lines.push(`Retrieval benchmark — ${summary.queries} queries`);
|
|
140
|
+
lines.push(` recall@5 ${summary.recallAt5.toFixed(3)}`);
|
|
141
|
+
lines.push(` precision@5 ${summary.precisionAt5.toFixed(3)}`);
|
|
142
|
+
lines.push(` MRR ${summary.mrr.toFixed(3)}`);
|
|
143
|
+
lines.push(` p50 latency ${summary.p50LatencyMs}ms`);
|
|
144
|
+
lines.push(` p95 latency ${summary.p95LatencyMs}ms`);
|
|
145
|
+
lines.push(` empty rate ${(summary.emptyRate * 100).toFixed(1)}%`);
|
|
146
|
+
lines.push(` fallback ${(summary.fallbackRate * 100).toFixed(1)}%`);
|
|
147
|
+
if (summary.regressed.length > 0) {
|
|
148
|
+
lines.push(' Regressed:');
|
|
149
|
+
for (const r of summary.regressed) lines.push(` - ${r.metric}: ${JSON.stringify(r.actual)} (expected ${JSON.stringify(r.expected)})`);
|
|
150
|
+
}
|
|
151
|
+
if (perQuery && perQuery.length > 0) {
|
|
152
|
+
lines.push('');
|
|
153
|
+
lines.push('Per query:');
|
|
154
|
+
for (const q of perQuery) {
|
|
155
|
+
lines.push(` ${q.name.padEnd(40)} recall=${q.recall.toFixed(2)} mrr=${q.reciprocalRank.toFixed(2)} ${q.latencyMs}ms${q.fallback ? ' [fallback]' : ''}`);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return lines.join('\n');
|
|
159
|
+
}
|
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/evaluator-optimizer.mjs — Iterative refinement loop for document authoring.
|
|
3
|
+
*
|
|
4
|
+
* Implements the evaluator-optimizer pattern from Anthropic's best practices:
|
|
5
|
+
* 1. Generator creates a draft
|
|
6
|
+
* 2. Evaluator scores against rubric (0.0 - 1.0)
|
|
7
|
+
* 3. If score < threshold, generate revision with feedback
|
|
8
|
+
* 4. Repeat until score >= threshold or max iterations reached
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
* const result = await runEvaluatorOptimizer({
|
|
12
|
+
* request: 'Write a PRD for feature X',
|
|
13
|
+
* docType: 'prd',
|
|
14
|
+
* threshold: 0.7,
|
|
15
|
+
* maxIterations: 3,
|
|
16
|
+
* });
|
|
17
|
+
*
|
|
18
|
+
* Returns:
|
|
19
|
+
* {
|
|
20
|
+
* content: string,
|
|
21
|
+
* iterations: number,
|
|
22
|
+
* finalScore: number,
|
|
23
|
+
* scores: [0.6, 0.75, 0.82],
|
|
24
|
+
* feedback: [...],
|
|
25
|
+
* reachedThreshold: boolean,
|
|
26
|
+
* }
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
import { getTemplate } from './templates.mjs';
|
|
30
|
+
|
|
31
|
+
const DEFAULT_THRESHOLD = 0.7;
|
|
32
|
+
const DEFAULT_MAX_ITERATIONS = 3;
|
|
33
|
+
|
|
34
|
+
const RUBRICS = {
|
|
35
|
+
prd: {
|
|
36
|
+
name: 'PRD Quality Rubric',
|
|
37
|
+
criteria: [
|
|
38
|
+
{ id: 'summary', name: 'Summary', description: 'One paragraph that captures what, who, why', weight: 0.1 },
|
|
39
|
+
{ id: 'problem', name: 'Problem Statement', description: 'Clear user pain, not solution-speak', weight: 0.15 },
|
|
40
|
+
{ id: 'goals', name: 'Goals', description: '3-5 outcome-focused goals, not activities', weight: 0.1 },
|
|
41
|
+
{ id: 'scope', name: 'Scope', description: 'In/out of scope clearly bounded', weight: 0.1 },
|
|
42
|
+
{ id: 'phases', name: 'Phases', description: 'Each phase independently shippable', weight: 0.15 },
|
|
43
|
+
{ id: 'requirements', name: 'Requirements', description: 'FRs/NFRs with acceptance criteria', weight: 0.2 },
|
|
44
|
+
{ id: 'metrics', name: 'Success Metrics', description: 'Leading and lagging indicators', weight: 0.1 },
|
|
45
|
+
{ id: 'risks', name: 'Risks', description: 'Likelihood/impact with mitigations', weight: 0.1 },
|
|
46
|
+
],
|
|
47
|
+
},
|
|
48
|
+
adr: {
|
|
49
|
+
name: 'ADR Quality Rubric',
|
|
50
|
+
criteria: [
|
|
51
|
+
{ id: 'title', name: 'Title', description: 'Clear, imperative format', weight: 0.05 },
|
|
52
|
+
{ id: 'status', name: 'Status', description: 'Proposed/Accepted/Deprecated with date', weight: 0.05 },
|
|
53
|
+
{ id: 'context', name: 'Context', description: 'Forces, constraints, assumptions', weight: 0.2 },
|
|
54
|
+
{ id: 'decision', name: 'Decision', description: 'Clear statement of what was decided', weight: 0.25 },
|
|
55
|
+
{ id: 'alternatives', name: 'Alternatives', description: 'Rejected options with rationale', weight: 0.2 },
|
|
56
|
+
{ id: 'consequences', name: 'Consequences', description: 'Positive, negative, neutral outcomes', weight: 0.15 },
|
|
57
|
+
{ id: 'compliance', name: 'Compliance', description: 'Links to related ADRs, PRDs', weight: 0.1 },
|
|
58
|
+
],
|
|
59
|
+
},
|
|
60
|
+
rfc: {
|
|
61
|
+
name: 'RFC Quality Rubric',
|
|
62
|
+
criteria: [
|
|
63
|
+
{ id: 'summary', name: 'Summary', description: 'Executive summary for busy readers', weight: 0.1 },
|
|
64
|
+
{ id: 'motivation', name: 'Motivation', description: 'Why this matters now', weight: 0.15 },
|
|
65
|
+
{ id: 'proposal', name: 'Proposal', description: 'Clear, specific technical change', weight: 0.25 },
|
|
66
|
+
{ id: 'alternatives', name: 'Alternatives Considered', description: 'Other approaches and why not', weight: 0.15 },
|
|
67
|
+
{ id: 'implementation', name: 'Implementation Plan', description: 'Phases, milestones, owners', weight: 0.15 },
|
|
68
|
+
{ id: 'risks', name: 'Risks & Mitigations', description: 'What could go wrong', weight: 0.1 },
|
|
69
|
+
{ id: 'success', name: 'Success Criteria', description: 'How we know this worked', weight: 0.1 },
|
|
70
|
+
],
|
|
71
|
+
},
|
|
72
|
+
runbook: {
|
|
73
|
+
name: 'Runbook Quality Rubric',
|
|
74
|
+
criteria: [
|
|
75
|
+
{ id: 'trigger', name: 'Trigger', description: 'When to run this runbook', weight: 0.1 },
|
|
76
|
+
{ id: 'steps', name: 'Steps', description: 'Numbered, executable steps', weight: 0.3 },
|
|
77
|
+
{ id: 'verification', name: 'Verification', description: 'How to confirm it worked', weight: 0.2 },
|
|
78
|
+
{ id: 'rollback', name: 'Rollback', description: 'How to undo if needed', weight: 0.15 },
|
|
79
|
+
{ id: 'escalation', name: 'Escalation', description: 'Who to call if stuck', weight: 0.1 },
|
|
80
|
+
{ id: 'references', name: 'References', description: 'Related docs, runbooks, alerts', weight: 0.15 },
|
|
81
|
+
],
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Evaluate a document against its rubric.
|
|
87
|
+
* Returns a score (0.0 - 1.0) and detailed feedback.
|
|
88
|
+
*/
|
|
89
|
+
export async function evaluateDocument({ content, docType, evaluatorFn = null }) {
|
|
90
|
+
const rubric = RUBRICS[docType];
|
|
91
|
+
if (!rubric) {
|
|
92
|
+
throw new Error(`Unknown document type: ${docType}. Available: ${Object.keys(RUBRICS).join(', ')}`);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// If custom evaluator function provided, use it
|
|
96
|
+
if (evaluatorFn) {
|
|
97
|
+
return await evaluatorFn({ content, docType, rubric });
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Default: deterministic rubric-based evaluation
|
|
101
|
+
// In production, this would call an LLM evaluator
|
|
102
|
+
// For now, we use structural checks as a proxy
|
|
103
|
+
|
|
104
|
+
const scores = [];
|
|
105
|
+
const feedback = [];
|
|
106
|
+
|
|
107
|
+
for (const criterion of rubric.criteria) {
|
|
108
|
+
// Check if the criterion section exists in the content
|
|
109
|
+
const sectionPatterns = getSectionPatterns(docType, criterion.id);
|
|
110
|
+
let score = 0;
|
|
111
|
+
let found = false;
|
|
112
|
+
|
|
113
|
+
for (const pattern of sectionPatterns) {
|
|
114
|
+
if (content.toLowerCase().includes(pattern.toLowerCase())) {
|
|
115
|
+
found = true;
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (found) {
|
|
121
|
+
// Check for quality indicators
|
|
122
|
+
const qualityIndicators = getQualityIndicators(criterion.id);
|
|
123
|
+
let qualityScore = 0.5; // Base score for having the section
|
|
124
|
+
|
|
125
|
+
for (const indicator of qualityIndicators) {
|
|
126
|
+
if (content.toLowerCase().includes(indicator.toLowerCase())) {
|
|
127
|
+
qualityScore += 0.1;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
score = Math.min(1.0, qualityScore);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
scores.push(score * criterion.weight);
|
|
135
|
+
feedback.push({
|
|
136
|
+
criterion: criterion.name,
|
|
137
|
+
score,
|
|
138
|
+
maxScore: 1.0,
|
|
139
|
+
weight: criterion.weight,
|
|
140
|
+
comment: found ? `Section present, quality: ${(score * 100).toFixed(0)}%` : 'Section missing',
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const finalScore = scores.reduce((a, b) => a + b, 0);
|
|
145
|
+
|
|
146
|
+
return {
|
|
147
|
+
score: finalScore,
|
|
148
|
+
feedback,
|
|
149
|
+
rubric: rubric.name,
|
|
150
|
+
docType,
|
|
151
|
+
evaluatedAt: new Date().toISOString(),
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function getSectionPatterns(docType, criterionId) {
|
|
156
|
+
const patterns = {
|
|
157
|
+
prd: {
|
|
158
|
+
summary: ['## summary', '## overview', '## executive summary'],
|
|
159
|
+
problem: ['## problem', '## problem statement', '## background'],
|
|
160
|
+
goals: ['## goals', '## objectives'],
|
|
161
|
+
scope: ['## in scope', '## out of scope', '## scope'],
|
|
162
|
+
phases: ['## phase', '## phases'],
|
|
163
|
+
requirements: ['functional', 'non-functional', 'FR-', 'NFR-', 'acceptance'],
|
|
164
|
+
metrics: ['## success metric', '## metrics', 'kpi'],
|
|
165
|
+
risks: ['## risk', '## risks', 'mitigation'],
|
|
166
|
+
},
|
|
167
|
+
adr: {
|
|
168
|
+
title: ['# adr:', '## title'],
|
|
169
|
+
status: ['## status', 'status:'],
|
|
170
|
+
context: ['## context', '## background', '## forces'],
|
|
171
|
+
decision: ['## decision', '## proposed solution'],
|
|
172
|
+
alternatives: ['## alternatives', '## alternative', 'rejected'],
|
|
173
|
+
consequences: ['## consequences', '## implications'],
|
|
174
|
+
compliance: ['## compliance', '## related', 'links to'],
|
|
175
|
+
},
|
|
176
|
+
rfc: {
|
|
177
|
+
summary: ['## summary', '## abstract', '## overview'],
|
|
178
|
+
motivation: ['## motivation', '## why', '## rationale'],
|
|
179
|
+
proposal: ['## proposal', '## design', '## solution'],
|
|
180
|
+
alternatives: ['## alternatives', '## alternative approaches'],
|
|
181
|
+
implementation: ['## implementation', '## plan', '## timeline'],
|
|
182
|
+
risks: ['## risks', '## concerns'],
|
|
183
|
+
success: ['## success criteria', '## how we measure'],
|
|
184
|
+
},
|
|
185
|
+
runbook: {
|
|
186
|
+
trigger: ['## trigger', '## when to run', '## purpose'],
|
|
187
|
+
steps: ['## steps', '## procedure', '## instructions'],
|
|
188
|
+
verification: ['## verification', '## confirm', '## validate'],
|
|
189
|
+
rollback: ['## rollback', '## undo', '## revert'],
|
|
190
|
+
escalation: ['## escalation', '## contacts', '## who to call'],
|
|
191
|
+
references: ['## references', '## related', '## see also'],
|
|
192
|
+
},
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
return patterns[docType]?.[criterionId] || [];
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function getQualityIndicators(criterionId) {
|
|
199
|
+
const indicators = {
|
|
200
|
+
summary: ['one paragraph', 'in brief', 'tl;dr'],
|
|
201
|
+
problem: ['user', 'customer', 'pain', 'currently unable'],
|
|
202
|
+
goals: ['reduce', 'increase', 'eliminate', 'from', 'to'],
|
|
203
|
+
scope: ['explicitly', 'deferred', 'not included'],
|
|
204
|
+
phases: ['phase 1', 'phase 2', 'phase 3', 'independently shippable'],
|
|
205
|
+
requirements: ['must', 'should', 'acceptance:', 'falsifiable'],
|
|
206
|
+
metrics: ['leading', 'lagging', 'baseline', 'target'],
|
|
207
|
+
risks: ['likelihood', 'impact', 'mitigation', 'low', 'medium', 'high'],
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
return indicators[criterionId] || [];
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Generate revision feedback based on evaluation results.
|
|
215
|
+
*/
|
|
216
|
+
export function generateRevisionFeedback(evaluationResult) {
|
|
217
|
+
const improvements = [];
|
|
218
|
+
|
|
219
|
+
for (const item of evaluationResult.feedback) {
|
|
220
|
+
if (item.score < 0.7) {
|
|
221
|
+
improvements.push(`- **${item.criterion}** (score: ${(item.score * 100).toFixed(0)}%): ${item.comment}`);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (improvements.length === 0) {
|
|
226
|
+
return 'Document meets all quality criteria. No revisions needed.';
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return `Please revise the document to address the following:\n\n${improvements.join('\n')}`;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Run the full evaluator-optimizer loop.
|
|
234
|
+
*/
|
|
235
|
+
export async function runEvaluatorOptimizer({
|
|
236
|
+
request,
|
|
237
|
+
docType,
|
|
238
|
+
threshold = DEFAULT_THRESHOLD,
|
|
239
|
+
maxIterations = DEFAULT_MAX_ITERATIONS,
|
|
240
|
+
generatorFn,
|
|
241
|
+
evaluatorFn,
|
|
242
|
+
}) {
|
|
243
|
+
if (!generatorFn) {
|
|
244
|
+
throw new Error('generatorFn is required — provide a function that generates document drafts');
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const results = {
|
|
248
|
+
content: null,
|
|
249
|
+
iterations: 0,
|
|
250
|
+
finalScore: 0,
|
|
251
|
+
scores: [],
|
|
252
|
+
feedback: [],
|
|
253
|
+
reachedThreshold: false,
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
let currentContent = null;
|
|
257
|
+
let iterationFeedback = null;
|
|
258
|
+
|
|
259
|
+
for (let iteration = 1; iteration <= maxIterations; iteration++) {
|
|
260
|
+
results.iterations = iteration;
|
|
261
|
+
|
|
262
|
+
// Generate draft (or revision)
|
|
263
|
+
currentContent = await generatorFn({
|
|
264
|
+
request,
|
|
265
|
+
docType,
|
|
266
|
+
iteration,
|
|
267
|
+
previousFeedback: iterationFeedback,
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
// Evaluate
|
|
271
|
+
const evaluation = await evaluateDocument({
|
|
272
|
+
content: currentContent,
|
|
273
|
+
docType,
|
|
274
|
+
evaluatorFn,
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
results.scores.push(evaluation.score);
|
|
278
|
+
results.feedback.push(evaluation.feedback);
|
|
279
|
+
results.finalScore = evaluation.score;
|
|
280
|
+
|
|
281
|
+
if (evaluation.score >= threshold) {
|
|
282
|
+
results.reachedThreshold = true;
|
|
283
|
+
results.content = currentContent;
|
|
284
|
+
break;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Generate feedback for next iteration
|
|
288
|
+
iterationFeedback = generateRevisionFeedback(evaluation);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
results.content = currentContent;
|
|
292
|
+
|
|
293
|
+
return results;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Get available rubrics for a document type.
|
|
298
|
+
*/
|
|
299
|
+
export function getRubric(docType) {
|
|
300
|
+
return RUBRICS[docType] || null;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* List all available rubrics.
|
|
305
|
+
*/
|
|
306
|
+
export function listRubrics() {
|
|
307
|
+
return Object.keys(RUBRICS).map(type => ({
|
|
308
|
+
docType: type,
|
|
309
|
+
name: RUBRICS[type].name,
|
|
310
|
+
criteriaCount: RUBRICS[type].criteria.length,
|
|
311
|
+
criteria: RUBRICS[type].criteria.map(c => ({
|
|
312
|
+
id: c.id,
|
|
313
|
+
name: c.name,
|
|
314
|
+
weight: c.weight,
|
|
315
|
+
})),
|
|
316
|
+
}));
|
|
317
|
+
}
|