@geraldmaron/construct 1.0.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +10 -7
- package/LICENSE +201 -21
- package/README.md +132 -257
- package/agents/contracts.json +387 -0
- package/agents/prompts/cx-accessibility.md +8 -0
- package/agents/prompts/cx-ai-engineer.md +92 -0
- package/agents/prompts/cx-architect.md +10 -2
- package/agents/prompts/cx-business-strategist.md +13 -0
- package/agents/prompts/cx-data-analyst.md +80 -0
- package/agents/prompts/cx-data-engineer.md +4 -0
- package/agents/prompts/cx-debugger.md +8 -0
- package/agents/prompts/cx-designer.md +19 -0
- package/agents/prompts/cx-devil-advocate.md +4 -0
- package/agents/prompts/cx-docs-keeper.md +128 -0
- package/agents/prompts/cx-engineer.md +13 -0
- package/agents/prompts/cx-evaluator.md +4 -0
- package/agents/prompts/cx-explorer.md +12 -0
- package/agents/prompts/cx-legal-compliance.md +13 -0
- package/agents/prompts/cx-operations.md +13 -0
- package/agents/prompts/cx-orchestrator.md +107 -4
- package/agents/prompts/cx-platform-engineer.md +75 -0
- package/agents/prompts/cx-product-manager.md +8 -0
- package/agents/prompts/cx-qa.md +100 -0
- package/agents/prompts/cx-rd-lead.md +13 -0
- package/agents/prompts/cx-release-manager.md +8 -0
- package/agents/prompts/cx-researcher.md +21 -1
- package/agents/prompts/cx-reviewer.md +8 -0
- package/agents/prompts/cx-security.md +104 -0
- package/agents/prompts/cx-sre.md +104 -0
- package/agents/prompts/cx-test-automation.md +4 -0
- package/agents/prompts/cx-trace-reviewer.md +7 -3
- package/agents/prompts/cx-ux-researcher.md +4 -0
- package/agents/registry.json +365 -90
- package/agents/role-manifests.json +217 -0
- package/bin/construct +3345 -141
- package/bin/construct-postinstall.mjs +87 -0
- package/commands/build/feature.md +6 -6
- package/commands/plan/feature.md +6 -5
- package/commands/plan/requirements.md +1 -1
- package/commands/ship/status.md +1 -1
- package/commands/work/drive.md +3 -3
- package/commands/work/optimize-prompts.md +3 -3
- package/db/{migrations → schema}/001_init.sql +2 -0
- package/db/schema/002_pgvector.sql +182 -0
- package/db/schema/003_intake.sql +47 -0
- package/examples/README.md +85 -0
- package/examples/internal/roles/architect/bad/clever-plan-without-contracts.md +30 -0
- package/examples/internal/roles/architect/golden/explicit-tradeoff-before-plan.md +23 -0
- package/examples/internal/roles/engineer/bad/speculative-abstraction.md +30 -0
- package/examples/internal/roles/engineer/golden/read-before-write.md +28 -0
- package/examples/internal/roles/orchestrator/bad/everything-becomes-multi-agent.md +29 -0
- package/examples/internal/roles/orchestrator/golden/minimal-dispatch.md +22 -0
- package/examples/internal/roles/qa/bad/coverage-theater.md +30 -0
- package/examples/internal/roles/qa/golden/regression-gate.md +23 -0
- package/examples/internal/roles/reviewer/bad/lgtm-without-verification.md +29 -0
- package/examples/internal/roles/reviewer/golden/find-structural-risk-first.md +28 -0
- package/examples/personas/construct/adversarial/ignore-instruction-to-skip-approval.md +22 -0
- package/examples/personas/construct/bad/commit-without-approval.md +30 -0
- package/examples/personas/construct/boundary/blocked-needs-main-input.md +29 -0
- package/examples/personas/construct/golden/branch-approval-before-mutation.md +36 -0
- package/examples/personas/construct/golden/focused-direct-answer.md +28 -0
- package/examples/provider-plugin/README.md +34 -0
- package/examples/provider-plugin/index.mjs +74 -0
- package/examples/provider-plugin/package.json +15 -0
- package/examples/seed-observations/README.md +38 -0
- package/examples/seed-observations/anti-patterns.md +44 -0
- package/examples/seed-observations/decisions.md +36 -0
- package/examples/seed-observations/patterns.md +42 -0
- package/lib/agent-contracts-enforce.mjs +158 -0
- package/lib/agent-contracts.mjs +231 -0
- package/lib/agents/postconditions.mjs +126 -0
- package/lib/agents/schema.mjs +124 -0
- package/lib/artifact-capture.mjs +183 -0
- package/lib/audit-trail.mjs +149 -0
- package/lib/auto-docs.mjs +245 -65
- package/lib/beads/auto-close.mjs +126 -0
- package/lib/beads/drift.mjs +171 -0
- package/lib/beads-automation.mjs +542 -0
- package/lib/beads-client.mjs +518 -0
- package/lib/beads-lock.mjs +377 -0
- package/lib/beads-optimistic.mjs +365 -0
- package/lib/bootstrap/built-ins.mjs +136 -0
- package/lib/bootstrap/lazy-install.mjs +161 -0
- package/lib/bootstrap/resources.mjs +120 -0
- package/lib/bootstrap.mjs +105 -0
- package/lib/cache-governor.js +213 -0
- package/lib/cache-strategy-anthropic.js +62 -0
- package/lib/cache-strategy-google.js +79 -0
- package/lib/cache-strategy-none.js +30 -0
- package/lib/cache-strategy-openai.js +48 -0
- package/lib/cache-strategy.js +91 -0
- package/lib/claude-allow.mjs +149 -0
- package/lib/cli-commands.mjs +413 -258
- package/lib/codex-config.mjs +3 -1
- package/lib/comment-lint.mjs +55 -6
- package/lib/completions.mjs +21 -6
- package/lib/config/alias.mjs +56 -0
- package/lib/config/project-config.mjs +335 -0
- package/lib/config/schema.mjs +159 -0
- package/lib/context-router.mjs +308 -0
- package/lib/cost-ledger.mjs +177 -0
- package/lib/cost.mjs +171 -10
- package/lib/dashboard-static.mjs +158 -0
- package/lib/deployment-mode.mjs +86 -0
- package/lib/deprecate.mjs +49 -0
- package/lib/dispatch-batch.js +183 -0
- package/lib/distill.mjs +21 -8
- package/lib/doc-stamp.mjs +164 -0
- package/lib/doc-verify.mjs +119 -0
- package/lib/docs-routing.mjs +89 -0
- package/lib/docs-verify.mjs +417 -0
- package/lib/doctor/audit.mjs +71 -0
- package/lib/doctor/cli.mjs +99 -0
- package/lib/doctor/escalate.mjs +29 -0
- package/lib/doctor/index.mjs +140 -0
- package/lib/doctor/report.mjs +170 -0
- package/lib/doctor/watchers/bd-watch.mjs +117 -0
- package/lib/doctor/watchers/cost.mjs +130 -0
- package/lib/doctor/watchers/disk.mjs +122 -0
- package/lib/doctor/watchers/handoffs.mjs +33 -0
- package/lib/doctor/watchers/process-pressure.mjs +60 -0
- package/lib/doctor/watchers/service-health.mjs +188 -0
- package/lib/document-extract.mjs +288 -0
- package/lib/document-ingest.mjs +230 -0
- package/lib/drop.mjs +282 -0
- package/lib/embed/approval-queue.mjs +176 -0
- package/lib/embed/artifact.mjs +349 -0
- package/lib/embed/authority-guard.mjs +155 -0
- package/lib/embed/cli.mjs +408 -0
- package/lib/embed/config.mjs +355 -0
- package/lib/embed/conflict-detection.mjs +264 -0
- package/lib/embed/customer-profiles.mjs +480 -0
- package/lib/embed/daemon.mjs +1309 -0
- package/lib/embed/demand-fetch.mjs +449 -0
- package/lib/embed/docs-lifecycle.mjs +349 -0
- package/lib/embed/inbox-live-watcher.mjs +119 -0
- package/lib/embed/inbox.mjs +343 -0
- package/lib/embed/intake-metrics.mjs +190 -0
- package/lib/embed/jobs/vector-sync.mjs +198 -0
- package/lib/embed/notifications.mjs +75 -0
- package/lib/embed/output.mjs +79 -0
- package/lib/embed/providers/github.mjs +295 -0
- package/lib/embed/providers/jira.mjs +192 -0
- package/lib/embed/providers/linear.mjs +186 -0
- package/lib/embed/providers/registry.mjs +115 -0
- package/lib/embed/providers/slack.mjs +203 -0
- package/lib/embed/recommendation-store.mjs +378 -0
- package/lib/embed/roadmap.mjs +374 -0
- package/lib/embed/role-framing.mjs +110 -0
- package/lib/embed/scheduler.mjs +99 -0
- package/lib/embed/semantic.mjs +325 -0
- package/lib/embed/snapshot.mjs +191 -0
- package/lib/embed/supervision.mjs +235 -0
- package/lib/embed/target-resolver.mjs +186 -0
- package/lib/embed/worker.mjs +62 -0
- package/lib/embed/workspaces.mjs +297 -0
- package/lib/engine/chunker-headings.mjs +110 -0
- package/lib/engine/compressor-heuristic.mjs +100 -0
- package/lib/engine/consolidate.mjs +287 -0
- package/lib/engine/contracts.mjs +126 -0
- package/lib/engine/defaults.mjs +129 -0
- package/lib/engine/eval-retrieval.mjs +148 -0
- package/lib/engine/fuser-rrf.mjs +62 -0
- package/lib/engine/index.mjs +37 -0
- package/lib/engine/registry.mjs +146 -0
- package/lib/engine/reranker-mmr.mjs +90 -0
- package/lib/engine/tokens.mjs +77 -0
- package/lib/entity-store.mjs +280 -0
- package/lib/env-config.mjs +69 -4
- package/lib/evals/retrieval-bench.mjs +159 -0
- package/lib/evaluator-optimizer.mjs +317 -0
- package/lib/features.mjs +159 -29
- package/lib/gates-audit.mjs +236 -0
- package/lib/git-hooks/prepare-commit-msg +58 -0
- package/lib/handoffs/cleanup.mjs +159 -0
- package/lib/handoffs/contract.mjs +162 -0
- package/lib/handoffs/inventory.mjs +120 -0
- package/lib/headhunt.mjs +28 -47
- package/lib/health-check.mjs +399 -0
- package/lib/hook-health.mjs +442 -0
- package/lib/hooks/_lib/log.mjs +82 -0
- package/lib/hooks/adaptive-lint.mjs +26 -2
- package/lib/hooks/agent-tracker.mjs +171 -14
- package/lib/hooks/audit-reads.mjs +109 -0
- package/lib/hooks/audit-trail.mjs +153 -0
- package/lib/hooks/bash-output-logger.mjs +71 -0
- package/lib/hooks/block-no-verify.mjs +41 -0
- package/lib/hooks/ci-status-check.mjs +82 -0
- package/lib/hooks/comment-lint.mjs +29 -7
- package/lib/hooks/config-protection.mjs +39 -18
- package/lib/hooks/context-watch.mjs +137 -0
- package/lib/hooks/context-window-recovery.mjs +4 -20
- package/lib/hooks/dep-audit.mjs +16 -0
- package/lib/hooks/doc-coupling-check.mjs +80 -0
- package/lib/hooks/edit-accumulator.mjs +3 -0
- package/lib/hooks/edit-error-recovery.mjs +3 -0
- package/lib/hooks/edit-guard.mjs +45 -4
- package/lib/hooks/env-check.mjs +3 -0
- package/lib/hooks/guard-bash.mjs +58 -1
- package/lib/hooks/mcp-audit.mjs +18 -20
- package/lib/hooks/mcp-health-check.mjs +36 -0
- package/lib/hooks/model-fallback.mjs +42 -56
- package/lib/hooks/policy-engine.mjs +209 -0
- package/lib/hooks/post-merge-docs-check.mjs +63 -0
- package/lib/hooks/pre-compact.mjs +4 -24
- package/lib/hooks/pre-push-gate.mjs +200 -37
- package/lib/hooks/proactive-activation.mjs +284 -0
- package/lib/hooks/read-tracker.mjs +27 -4
- package/lib/hooks/readme-age-check.mjs +77 -0
- package/lib/hooks/registry-sync.mjs +12 -5
- package/lib/hooks/scan-secrets.mjs +50 -7
- package/lib/hooks/session-optimize.mjs +311 -0
- package/lib/hooks/session-start.mjs +287 -28
- package/lib/hooks/stop-notify.mjs +236 -96
- package/lib/hooks/stop-typecheck.mjs +13 -1
- package/lib/hooks/test-watch.mjs +68 -0
- package/lib/host-capabilities.mjs +31 -10
- package/lib/init-docs.mjs +731 -291
- package/lib/init-unified.mjs +1116 -0
- package/lib/init-update.mjs +168 -0
- package/lib/init.mjs +107 -0
- package/lib/install/first-invocation.mjs +119 -0
- package/lib/install/stage-project.mjs +69 -0
- package/lib/intake/classify.mjs +278 -0
- package/lib/intake/feedback.mjs +273 -0
- package/lib/intake/filesystem-queue.mjs +158 -0
- package/lib/intake/intake-config.mjs +132 -0
- package/lib/intake/postgres-queue.mjs +198 -0
- package/lib/intake/prepare.mjs +139 -0
- package/lib/intake/queue.mjs +83 -0
- package/lib/intake/session-prelude.mjs +50 -0
- package/lib/integrations/intake-integrations.mjs +740 -0
- package/lib/intent-classifier.mjs +253 -0
- package/lib/knowledge/layout.mjs +72 -0
- package/lib/knowledge/rag.mjs +331 -0
- package/lib/knowledge/search.mjs +315 -0
- package/lib/knowledge/trends.mjs +261 -0
- package/lib/logger.mjs +85 -0
- package/lib/mcp/broker.mjs +124 -0
- package/lib/mcp/server.mjs +554 -854
- package/lib/mcp/tools/document.mjs +132 -0
- package/lib/mcp/tools/memory.mjs +209 -0
- package/lib/mcp/tools/project.mjs +349 -0
- package/lib/mcp/tools/skills.mjs +409 -0
- package/lib/mcp/tools/storage.mjs +60 -0
- package/lib/mcp/tools/telemetry.mjs +315 -0
- package/lib/mcp/tools/workflow.mjs +112 -0
- package/lib/mcp-catalog.json +54 -3
- package/lib/mcp-manager.mjs +96 -48
- package/lib/mcp-platform-config.mjs +22 -22
- package/lib/memory-stats.mjs +121 -0
- package/lib/mode-commands.mjs +124 -0
- package/lib/model-free-selector.mjs +184 -0
- package/lib/model-pricing.mjs +152 -0
- package/lib/model-registry.mjs +226 -0
- package/lib/model-router.mjs +362 -386
- package/lib/observation-store.mjs +391 -0
- package/lib/ollama-manager.mjs +428 -0
- package/lib/opencode-config.mjs +13 -3
- package/lib/opencode-runtime-plugin.mjs +70 -38
- package/lib/opencode-telemetry.mjs +64 -6
- package/lib/orchestration-policy.mjs +509 -6
- package/lib/overrides/resolver.mjs +207 -0
- package/lib/parity.mjs +147 -0
- package/lib/paths.mjs +33 -0
- package/lib/performance/generate.mjs +212 -0
- package/lib/plugin-registry.mjs +268 -0
- package/lib/policy/engine.mjs +130 -0
- package/lib/policy/unified-gates.mjs +96 -0
- package/lib/project-detection.mjs +129 -0
- package/lib/project-init-shared.mjs +272 -0
- package/lib/project-profile.mjs +447 -0
- package/lib/prompt-composer.js +434 -0
- package/lib/prompt-metadata.mjs +1 -1
- package/lib/provider-capabilities-anthropic.js +44 -0
- package/lib/provider-capabilities-deepseek.js +37 -0
- package/lib/provider-capabilities-generic.js +26 -0
- package/lib/provider-capabilities-google.js +47 -0
- package/lib/provider-capabilities-openai.js +45 -0
- package/lib/provider-capabilities.js +142 -0
- package/lib/providers/atlassian-confluence/index.mjs +103 -0
- package/lib/providers/atlassian-jira/index.mjs +100 -0
- package/lib/providers/auth-manager.mjs +126 -0
- package/lib/providers/circuit-breaker.mjs +124 -0
- package/lib/providers/contract.mjs +93 -0
- package/lib/providers/github/index.mjs +126 -0
- package/lib/providers/registry.mjs +184 -0
- package/lib/providers/salesforce/index.mjs +100 -0
- package/lib/providers/slack/index.mjs +80 -0
- package/lib/reflect.mjs +137 -0
- package/lib/research-lint.mjs +164 -0
- package/lib/resources/budget.mjs +259 -0
- package/lib/role-preload.mjs +21 -6
- package/lib/roles/approval-surface.mjs +54 -0
- package/lib/roles/cli.mjs +118 -0
- package/lib/roles/event-bus.mjs +79 -0
- package/lib/roles/fence.mjs +84 -0
- package/lib/roles/gateway.mjs +260 -0
- package/lib/roles/hook-emit.mjs +37 -0
- package/lib/roles/manifest.mjs +48 -0
- package/lib/roles/router.mjs +27 -0
- package/lib/runtime-pressure.mjs +360 -0
- package/lib/schema-artifact.mjs +134 -0
- package/lib/schema-infer.mjs +551 -0
- package/lib/server/auth.mjs +168 -0
- package/lib/server/chat.mjs +336 -0
- package/lib/server/cors.mjs +77 -0
- package/lib/server/csrf.mjs +91 -0
- package/lib/server/index.mjs +1927 -78
- package/lib/server/insights.mjs +765 -0
- package/lib/server/rate-limit.mjs +91 -0
- package/lib/server/static/assets/index-ab25c707.js +70 -0
- package/lib/server/static/assets/index-f0c80a2b.css +1 -0
- package/lib/server/static/index.html +12 -817
- package/lib/server/telemetry-login.mjs +108 -0
- package/lib/server/webhook.mjs +510 -0
- package/lib/service-manager.mjs +522 -58
- package/lib/services/pattern-promotion-service.mjs +167 -0
- package/lib/services/telemetry-backend.mjs +178 -0
- package/lib/session-store.mjs +374 -0
- package/lib/setup-prompts.mjs +96 -0
- package/lib/setup.mjs +525 -38
- package/lib/skills-apply.mjs +280 -0
- package/lib/skills-scope.mjs +118 -0
- package/lib/status.mjs +261 -70
- package/lib/storage/admin.mjs +355 -0
- package/lib/storage/backend.mjs +2 -1
- package/lib/storage/backup.mjs +347 -0
- package/lib/storage/embeddings-engine.mjs +133 -0
- package/lib/storage/embeddings-legacy.mjs +85 -0
- package/lib/storage/embeddings-local.mjs +108 -0
- package/lib/storage/embeddings-ollama.mjs +78 -0
- package/lib/storage/embeddings-openai.mjs +85 -0
- package/lib/storage/embeddings.mjs +92 -33
- package/lib/storage/file-lock.mjs +130 -0
- package/lib/storage/fusion.mjs +95 -0
- package/lib/storage/hybrid-query.mjs +34 -27
- package/lib/storage/migrations.mjs +187 -0
- package/lib/storage/postgres-backup.mjs +124 -0
- package/lib/storage/sql-store.mjs +5 -15
- package/lib/storage/state-source.mjs +12 -13
- package/lib/storage/store-version.mjs +115 -0
- package/lib/storage/sync.mjs +144 -35
- package/lib/storage/unified-storage.mjs +550 -0
- package/lib/storage/vector-client.mjs +286 -0
- package/lib/storage/vector-store.mjs +71 -30
- package/lib/task-graph/generate.mjs +135 -0
- package/lib/task-graph/schema.mjs +81 -0
- package/lib/task-graph/store.mjs +71 -0
- package/lib/telemetry/backends/local.mjs +62 -0
- package/lib/telemetry/backends/{langfuse.mjs → remote.mjs} +27 -14
- package/lib/telemetry/backfill.mjs +180 -0
- package/lib/telemetry/eval-datasets.mjs +203 -0
- package/lib/telemetry/{langfuse-ingest.mjs → ingest.mjs} +26 -20
- package/lib/telemetry/intent-verifications.mjs +86 -0
- package/lib/telemetry/llm-judge.mjs +350 -0
- package/lib/telemetry/model-pricing-catalog.mjs +557 -0
- package/lib/telemetry/setup.mjs +151 -0
- package/lib/telemetry/skill-calls.mjs +78 -0
- package/lib/telemetry/team-rollup.mjs +4 -4
- package/lib/token-engine.js +117 -0
- package/lib/token-estimator-anthropic.js +15 -0
- package/lib/token-estimator-deepseek.js +13 -0
- package/lib/token-estimator-default.js +13 -0
- package/lib/token-estimator-google.js +13 -0
- package/lib/token-estimator-openai.js +13 -0
- package/lib/toolkit-env.mjs +1 -1
- package/lib/tty-prompts.mjs +211 -0
- package/lib/uninstall/uninstall.mjs +423 -0
- package/lib/update.mjs +115 -0
- package/lib/upgrade.mjs +141 -0
- package/lib/validator.mjs +51 -2
- package/lib/validators/skills.mjs +142 -0
- package/lib/wireframe.mjs +422 -0
- package/lib/worker/entrypoint.mjs +241 -0
- package/lib/worker/evidence.mjs +107 -0
- package/lib/worker/run.mjs +154 -0
- package/lib/worker/trace.mjs +182 -0
- package/lib/workflow-state.mjs +14 -18
- package/package.json +21 -8
- package/personas/construct.md +53 -51
- package/platforms/claude/CLAUDE.md +1 -1
- package/platforms/claude/settings.template.json +115 -68
- package/platforms/opencode/config.template.json +3 -7
- package/rules/common/agents.md +11 -38
- package/rules/common/beads-hygiene.md +75 -0
- package/rules/common/code-review.md +11 -100
- package/rules/common/coding-style.md +5 -3
- package/rules/common/comments.md +30 -111
- package/rules/common/commit-approval.md +53 -0
- package/rules/common/cx-agent-routing.md +1 -0
- package/rules/common/development-workflow.md +23 -41
- package/rules/common/doc-ownership.md +54 -0
- package/rules/common/efficiency.md +57 -0
- package/rules/common/framing.md +76 -0
- package/rules/common/git-workflow.md +2 -4
- package/rules/common/patterns.md +3 -7
- package/rules/common/performance.md +15 -31
- package/rules/common/release-gates.md +69 -0
- package/rules/common/research.md +107 -0
- package/rules/common/security.md +6 -6
- package/rules/common/skill-composition.md +67 -0
- package/rules/common/testing.md +15 -27
- package/rules/golang/hooks.md +0 -4
- package/rules/policy/bootstrap.yaml +11 -0
- package/rules/policy/drive.yaml +8 -0
- package/rules/policy/task.yaml +9 -0
- package/rules/policy/workflow.yaml +9 -0
- package/rules/python/hooks.md +0 -4
- package/rules/swift/hooks.md +0 -4
- package/rules/typescript/hooks.md +0 -4
- package/rules/web/hooks.md +0 -2
- package/{sync-agents.mjs → scripts/sync-agents.mjs} +306 -61
- package/skills/ai/prompt-optimizer.md +7 -7
- package/skills/compliance/ai-disclosure.md +58 -0
- package/skills/compliance/data-privacy.md +46 -0
- package/skills/compliance/license-audit.md +40 -0
- package/skills/compliance/regulatory-review.md +61 -0
- package/skills/docs/document-ingest-workflow.md +52 -0
- package/skills/docs/evidence-ingest-workflow.md +9 -0
- package/skills/docs/init-docs.md +51 -18
- package/skills/docs/product-intelligence-workflow.md +12 -0
- package/skills/docs/product-signal-workflow.md +4 -0
- package/skills/docs/research-workflow.md +23 -8
- package/skills/docs/runbook-workflow.md +1 -1
- package/skills/operating/orchestration-reference.md +151 -0
- package/skills/roles/architect.md +5 -0
- package/skills/roles/engineer.md +32 -0
- package/skills/roles/operator.md +12 -0
- package/skills/roles/reviewer.md +23 -0
- package/skills/routing.md +1 -1
- package/templates/devcontainer/Dockerfile.devcontainer +38 -0
- package/templates/devcontainer/devcontainer.json +31 -0
- package/templates/distribution/bootstrap.ps1 +142 -0
- package/templates/distribution/bootstrap.sh +196 -0
- package/templates/distribution/run.mjs +187 -0
- package/templates/docs/adr.md +44 -8
- package/templates/docs/changelog-entry.md +43 -0
- package/templates/docs/construct_guide.md +149 -0
- package/templates/docs/evidence-brief.md +2 -2
- package/templates/docs/meta-prd.md +140 -19
- package/templates/docs/onboarding.md +57 -0
- package/templates/docs/prd.md +159 -15
- package/templates/docs/research-brief.md +4 -4
- package/templates/homebrew/construct.rb +67 -0
- package/langfuse/docker-compose.yml +0 -82
- package/lib/eval-harness.mjs +0 -59
- package/lib/hooks/bootstrap-guard.mjs +0 -90
- package/lib/hooks/console-warn.mjs +0 -43
- package/lib/hooks/continuation-enforcer.mjs +0 -72
- package/lib/hooks/drive-guard.mjs +0 -89
- package/lib/hooks/mcp-task-scope.mjs +0 -47
- package/lib/hooks/task-completed-guard.mjs +0 -43
- package/lib/hooks/teammate-idle-guard.mjs +0 -54
- package/lib/hooks/workflow-guard.mjs +0 -62
- package/lib/prompt-composer.mjs +0 -196
- package/lib/review.mjs +0 -429
- package/lib/server/static/app.js +0 -841
- package/lib/telemetry/langfuse-model-sync.mjs +0 -270
- package/rules/common/hooks.md +0 -35
- package/rules/zh/README.md +0 -113
- package/rules/zh/agents.md +0 -55
- package/rules/zh/code-review.md +0 -129
- package/rules/zh/coding-style.md +0 -53
- package/rules/zh/development-workflow.md +0 -49
- package/rules/zh/git-workflow.md +0 -29
- package/rules/zh/hooks.md +0 -35
- package/rules/zh/patterns.md +0 -36
- package/rules/zh/performance.md +0 -60
- package/rules/zh/security.md +0 -34
- package/rules/zh/testing.md +0 -34
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/embed/semantic.mjs — Semantic analysis for intake signals.
|
|
3
|
+
*
|
|
4
|
+
* Generates embeddings via @xenova/transformers, caches them to disk,
|
|
5
|
+
* computes cosine similarity, and clusters related signals by topic.
|
|
6
|
+
* All local — no external API calls. Default model is all-MiniLM-L6-v2
|
|
7
|
+
* (384-dimensional vectors, ~50MB on disk, fast inference).
|
|
8
|
+
*
|
|
9
|
+
* Storage:
|
|
10
|
+
* ~/.cx/cache/embeddings/<sha256>.json — single embedding vector
|
|
11
|
+
* ~/.cx/cache/embeddings/index.json — hash → metadata lookup
|
|
12
|
+
*
|
|
13
|
+
* Usage:
|
|
14
|
+
* const vec = await embed(text);
|
|
15
|
+
* const similarity = cosineSimilarity(vecA, vecB);
|
|
16
|
+
* const clusters = clusterVectors(vectors, { threshold: 0.7 });
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync, readdirSync } from 'node:fs';
|
|
20
|
+
import { join } from 'node:path';
|
|
21
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
22
|
+
import { homedir } from 'node:os';
|
|
23
|
+
|
|
24
|
+
const CACHE_DIR = join(homedir(), '.cx', 'cache', 'embeddings');
|
|
25
|
+
const INDEX_FILE = join(CACHE_DIR, 'index.json');
|
|
26
|
+
const MODEL_NAME = 'all-MiniLM-L6-v2';
|
|
27
|
+
const DIMENSIONS = 384;
|
|
28
|
+
const LOG = process.env.CONSTRUCT_DEBUG_EMBED === '1';
|
|
29
|
+
|
|
30
|
+
let embedder = null;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Lazy-load the embedding pipeline. Loads once per process.
|
|
34
|
+
*/
|
|
35
|
+
async function getEmbedder() {
|
|
36
|
+
if (embedder !== null) return embedder;
|
|
37
|
+
try {
|
|
38
|
+
const { pipeline } = await import('@xenova/transformers');
|
|
39
|
+
embedder = await pipeline('feature-extraction', MODEL_NAME, {
|
|
40
|
+
quantized: true,
|
|
41
|
+
});
|
|
42
|
+
if (LOG) process.stderr.write('[semantic] embedder loaded\n');
|
|
43
|
+
} catch (err) {
|
|
44
|
+
process.stderr.write(`[semantic] model load failed: ${err.message}\n`);
|
|
45
|
+
embedder = false;
|
|
46
|
+
}
|
|
47
|
+
return embedder;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Ensure the cache directory exists.
|
|
52
|
+
*/
|
|
53
|
+
function ensureCacheDir() {
|
|
54
|
+
if (!existsSync(CACHE_DIR)) mkdirSync(CACHE_DIR, { recursive: true });
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Read the cache index.
|
|
59
|
+
*/
|
|
60
|
+
function readIndex() {
|
|
61
|
+
if (!existsSync(INDEX_FILE)) return {};
|
|
62
|
+
try { return JSON.parse(readFileSync(INDEX_FILE, 'utf8')); } catch { return {}; }
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Write the cache index.
|
|
67
|
+
*/
|
|
68
|
+
function writeIndex(index) {
|
|
69
|
+
ensureCacheDir();
|
|
70
|
+
writeFileSync(INDEX_FILE, JSON.stringify(index, null, 2) + '\n');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Compute sha256 of text for cache key.
|
|
75
|
+
*/
|
|
76
|
+
function hashText(text) {
|
|
77
|
+
return createHash('sha256').update(text).digest('hex');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Normalize a vector to unit length.
|
|
82
|
+
*/
|
|
83
|
+
function normalize(vec) {
|
|
84
|
+
const mag = Math.sqrt(vec.reduce((sum, v) => sum + v * v, 0));
|
|
85
|
+
if (mag === 0) return vec;
|
|
86
|
+
return vec.map(v => v / mag);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Generate an embedding vector for the given text.
|
|
91
|
+
* Results are cached by sha256 hash to disk.
|
|
92
|
+
*
|
|
93
|
+
* @param {string} text - Text to embed
|
|
94
|
+
* @returns {Promise<Float32Array|number[]|null>} Embedding vector
|
|
95
|
+
*/
|
|
96
|
+
export async function embed(text) {
|
|
97
|
+
if (!text || typeof text !== 'string' || !text.trim()) return null;
|
|
98
|
+
|
|
99
|
+
ensureCacheDir();
|
|
100
|
+
const h = hashText(text.trim());
|
|
101
|
+
|
|
102
|
+
// Check cache
|
|
103
|
+
const index = readIndex();
|
|
104
|
+
if (index[h]) {
|
|
105
|
+
try {
|
|
106
|
+
const data = JSON.parse(readFileSync(join(CACHE_DIR, `${h}.json`), 'utf8'));
|
|
107
|
+
if (LOG) process.stderr.write(`[semantic] cache hit for ${h.slice(0, 12)}\n`);
|
|
108
|
+
return new Float32Array(data.vector);
|
|
109
|
+
} catch {
|
|
110
|
+
// cache miss or corrupt — proceed to compute
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const pipe = await getEmbedder();
|
|
115
|
+
if (!pipe) return null;
|
|
116
|
+
|
|
117
|
+
try {
|
|
118
|
+
const output = await pipe(text, { pooling: 'mean', normalize: false });
|
|
119
|
+
const vector = Array.from(output.data);
|
|
120
|
+
const data = { h, dim: vector.length, vector };
|
|
121
|
+
|
|
122
|
+
// Write cache
|
|
123
|
+
writeFileSync(join(CACHE_DIR, `${h}.json`), JSON.stringify(data) + '\n');
|
|
124
|
+
|
|
125
|
+
// Update index (lru tracking)
|
|
126
|
+
index[h] = { h, dim: vector.length, cachedAt: new Date().toISOString(), textPreview: text.slice(0, 80) };
|
|
127
|
+
writeIndex(index);
|
|
128
|
+
|
|
129
|
+
if (LOG) process.stderr.write(`[semantic] embedded ${text.slice(0, 40)}... (${vector.length}d)\n`);
|
|
130
|
+
return new Float32Array(vector);
|
|
131
|
+
} catch (err) {
|
|
132
|
+
process.stderr.write(`[semantic] embedding failed: ${err.message}\n`);
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Compute cosine similarity between two vectors.
|
|
139
|
+
* Returns 0 if either vector is null/mismatched.
|
|
140
|
+
*
|
|
141
|
+
* @param {number[]|Float32Array} a
|
|
142
|
+
* @param {number[]|Float32Array} b
|
|
143
|
+
* @returns {number} Similarity score in [0, 1]
|
|
144
|
+
*/
|
|
145
|
+
export function cosineSimilarity(a, b) {
|
|
146
|
+
if (!a || !b) return 0;
|
|
147
|
+
if (a.length !== b.length) return 0;
|
|
148
|
+
|
|
149
|
+
const na = normalize(Array.from(a));
|
|
150
|
+
const nb = normalize(Array.from(b));
|
|
151
|
+
|
|
152
|
+
let dot = 0;
|
|
153
|
+
for (let i = 0; i < na.length; i++) {
|
|
154
|
+
dot += na[i] * nb[i];
|
|
155
|
+
}
|
|
156
|
+
return Math.max(0, Math.min(1, dot));
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Compute similarity matrix for an array of vectors.
|
|
161
|
+
* Returns lower-triangular { i, j, similarity } objects.
|
|
162
|
+
*
|
|
163
|
+
* @param {Array<Float32Array|number[]>} vectors
|
|
164
|
+
* @returns {Array<{ i: number, j: number, similarity: number }>}
|
|
165
|
+
*/
|
|
166
|
+
export function similarityMatrix(vectors) {
|
|
167
|
+
const pairs = [];
|
|
168
|
+
for (let i = 1; i < vectors.length; i++) {
|
|
169
|
+
if (!vectors[i]) continue;
|
|
170
|
+
for (let j = 0; j < i; j++) {
|
|
171
|
+
if (!vectors[j]) continue;
|
|
172
|
+
const sim = cosineSimilarity(vectors[i], vectors[j]);
|
|
173
|
+
if (sim > 0) {
|
|
174
|
+
pairs.push({ i, j, similarity: sim });
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return pairs.sort((a, b) => b.similarity - a.similarity);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Cluster vectors by similarity threshold (simple connected-components).
|
|
183
|
+
* Returns clusters with items grouped by similarity > threshold.
|
|
184
|
+
*
|
|
185
|
+
* @param {Array<{ id: string, vector: Float32Array|number[], text?: string }>} items
|
|
186
|
+
* @param {object} [opts]
|
|
187
|
+
* @param {number} [opts.threshold=0.7] - Similarity threshold for clustering
|
|
188
|
+
* @param {number} [opts.minClusterSize=1] - Minimum items per cluster
|
|
189
|
+
* @returns {Array<{ id: string, items: string[], avgSimilarity: number, textPreview: string }>}
|
|
190
|
+
*/
|
|
191
|
+
export function clusterVectors(items, { threshold = 0.7, minClusterSize = 1 } = {}) {
|
|
192
|
+
if (!items.length) return [];
|
|
193
|
+
|
|
194
|
+
const n = items.length;
|
|
195
|
+
const adj = Array.from({ length: n }, () => []);
|
|
196
|
+
|
|
197
|
+
// Build adjacency from similarity threshold
|
|
198
|
+
for (let i = 1; i < n; i++) {
|
|
199
|
+
if (!items[i]?.vector) continue;
|
|
200
|
+
for (let j = 0; j < i; j++) {
|
|
201
|
+
if (!items[j]?.vector) continue;
|
|
202
|
+
if (cosineSimilarity(items[i].vector, items[j].vector) >= threshold) {
|
|
203
|
+
adj[i].push(j);
|
|
204
|
+
adj[j].push(i);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// BFS connected components
|
|
210
|
+
const visited = new Set();
|
|
211
|
+
const clusters = [];
|
|
212
|
+
|
|
213
|
+
for (let i = 0; i < n; i++) {
|
|
214
|
+
if (visited.has(i) || !items[i]?.vector) continue;
|
|
215
|
+
|
|
216
|
+
const component = [];
|
|
217
|
+
const queue = [i];
|
|
218
|
+
visited.add(i);
|
|
219
|
+
|
|
220
|
+
while (queue.length) {
|
|
221
|
+
const node = queue.shift();
|
|
222
|
+
component.push(items[node].id);
|
|
223
|
+
for (const neighbor of adj[node]) {
|
|
224
|
+
if (!visited.has(neighbor)) {
|
|
225
|
+
visited.add(neighbor);
|
|
226
|
+
queue.push(neighbor);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if (component.length >= minClusterSize) {
|
|
232
|
+
const clusterItems = component.map(id => items.find(it => it.id === id)).filter(Boolean);
|
|
233
|
+
const sims = [];
|
|
234
|
+
for (let a = 0; a < clusterItems.length; a++) {
|
|
235
|
+
for (let b = a + 1; b < clusterItems.length; b++) {
|
|
236
|
+
if (clusterItems[a]?.vector && clusterItems[b]?.vector) {
|
|
237
|
+
sims.push(cosineSimilarity(clusterItems[a].vector, clusterItems[b].vector));
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
const avgSim = sims.length ? sims.reduce((s, v) => s + v, 0) / sims.length : 0;
|
|
242
|
+
|
|
243
|
+
clusters.push({
|
|
244
|
+
id: `cluster-${randomUUID().slice(0, 8)}`,
|
|
245
|
+
items: component,
|
|
246
|
+
clusterSize: component.length,
|
|
247
|
+
avgSimilarity: Number(avgSim.toFixed(3)),
|
|
248
|
+
textPreview: clusterItems[0]?.text?.slice(0, 100) || '',
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
return clusters.sort((a, b) => b.clusterSize - a.clusterSize);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Extract embeddable text from an intake packet.
|
|
258
|
+
*
|
|
259
|
+
* @param {object} packet - Intake packet with triage, excerpt, etc.
|
|
260
|
+
* @returns {string} Text suitable for embedding
|
|
261
|
+
*/
|
|
262
|
+
export function extractTextFromPacket(packet) {
|
|
263
|
+
const parts = [];
|
|
264
|
+
if (packet?.triage?.intakeType && packet.triage.intakeType !== 'unknown') {
|
|
265
|
+
parts.push(`Type: ${packet.triage.intakeType}`);
|
|
266
|
+
}
|
|
267
|
+
if (packet?.triage?.rationale) {
|
|
268
|
+
parts.push(`Rationale: ${packet.triage.rationale}`);
|
|
269
|
+
}
|
|
270
|
+
if (packet?.excerpt) {
|
|
271
|
+
parts.push(packet.excerpt);
|
|
272
|
+
}
|
|
273
|
+
if (packet?.suggestion?.lane) {
|
|
274
|
+
parts.push(`Suggested lane: ${packet.suggestion.lane}`);
|
|
275
|
+
}
|
|
276
|
+
return parts.join('\n').slice(0, 5000);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Find semantically similar pending intake items relative to a query text.
|
|
281
|
+
*
|
|
282
|
+
* @param {Array<{ id: string, text: string, embedding?: Float32Array }>} items
|
|
283
|
+
* @param {string} queryText
|
|
284
|
+
* @param {object} [opts]
|
|
285
|
+
* @param {number} [opts.threshold=0.6]
|
|
286
|
+
* @param {number} [opts.maxResults=10]
|
|
287
|
+
* @returns {Promise<Array<{ id: string, similarity: number, text: string }>>}
|
|
288
|
+
*/
|
|
289
|
+
export async function findSimilar(items, queryText, { threshold = 0.6, maxResults = 10 } = {}) {
|
|
290
|
+
const queryVec = await embed(queryText);
|
|
291
|
+
if (!queryVec) return [];
|
|
292
|
+
|
|
293
|
+
const results = [];
|
|
294
|
+
for (const item of items) {
|
|
295
|
+
if (!item.text) continue;
|
|
296
|
+
const itemVec = item.embedding || await embed(item.text);
|
|
297
|
+
if (!itemVec) continue;
|
|
298
|
+
const sim = cosineSimilarity(queryVec, itemVec);
|
|
299
|
+
if (sim >= threshold) {
|
|
300
|
+
results.push({ id: item.id, similarity: sim, text: item.text.slice(0, 120) });
|
|
301
|
+
item.embedding = itemVec; // cache on object for reuse
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
return results.sort((a, b) => b.similarity - a.similarity).slice(0, maxResults);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Get cache statistics.
|
|
310
|
+
*
|
|
311
|
+
* @returns {{ total: number, oldest: string|null, newest: string|null }}
|
|
312
|
+
*/
|
|
313
|
+
export function cacheStats() {
|
|
314
|
+
const index = readIndex();
|
|
315
|
+
const entries = Object.values(index);
|
|
316
|
+
if (!entries.length) return { total: 0, oldest: null, newest: null };
|
|
317
|
+
const byDate = entries.sort((a, b) => a.cachedAt?.localeCompare(b.cachedAt || '') || 0);
|
|
318
|
+
return {
|
|
319
|
+
total: entries.length,
|
|
320
|
+
oldest: byDate[0]?.cachedAt || null,
|
|
321
|
+
newest: byDate[byDate.length - 1]?.cachedAt || null,
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/embed/snapshot.mjs — snapshot engine.
|
|
3
|
+
*
|
|
4
|
+
* Reads data from registered providers according to embed config sources,
|
|
5
|
+
* then produces a structured snapshot: health summary, open items, risks,
|
|
6
|
+
* and recommendations. Snapshots are plain objects that can be serialized
|
|
7
|
+
* to markdown, JSON, or dispatched to output targets.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { existsSync } from 'node:fs';
|
|
11
|
+
import { resolve } from 'node:path';
|
|
12
|
+
import { DEFAULT_OPERATING_PROFILE } from './config.mjs';
|
|
13
|
+
import { renderRoleLensSection } from './role-framing.mjs';
|
|
14
|
+
|
|
15
|
+
export class SnapshotEngine {
|
|
16
|
+
#registry = null;
|
|
17
|
+
#config = null;
|
|
18
|
+
#rootDir = null;
|
|
19
|
+
#workspaceDir = null;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @param {ProviderRegistry} registry - Live provider registry
|
|
23
|
+
* @param {object} config - Normalized embed config
|
|
24
|
+
* @param {object} [opts]
|
|
25
|
+
* @param {string} [opts.rootDir] - Data dir where .cx/* runtime artifacts live (default: process.cwd())
|
|
26
|
+
* @param {string} [opts.workspaceDir] - Project workspace dir for project-relative focal resources (default: rootDir)
|
|
27
|
+
*/
|
|
28
|
+
constructor(registry, config, { rootDir = process.cwd(), workspaceDir } = {}) {
|
|
29
|
+
this.#registry = registry;
|
|
30
|
+
this.#config = { ...config, operatingProfile: config.operatingProfile ?? DEFAULT_OPERATING_PROFILE };
|
|
31
|
+
this.#rootDir = rootDir;
|
|
32
|
+
this.#workspaceDir = workspaceDir ?? rootDir;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Produce a full snapshot by polling all configured sources.
|
|
37
|
+
* Returns a snapshot object.
|
|
38
|
+
*/
|
|
39
|
+
async generate() {
|
|
40
|
+
const startedAt = new Date().toISOString();
|
|
41
|
+
const sections = [];
|
|
42
|
+
const errors = [];
|
|
43
|
+
|
|
44
|
+
for (const source of this.#config.sources ?? []) {
|
|
45
|
+
const provider = this.#registry.get(source.provider);
|
|
46
|
+
if (!provider) {
|
|
47
|
+
errors.push({ source: source.provider, error: 'Provider not registered' });
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const items = [];
|
|
52
|
+
for (const ref of source.refs) {
|
|
53
|
+
try {
|
|
54
|
+
const result = await provider.read(ref, source);
|
|
55
|
+
items.push(...result.slice(0, this.#config.snapshot.maxItems));
|
|
56
|
+
} catch (err) {
|
|
57
|
+
errors.push({ source: source.provider, ref, error: err.message });
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
sections.push({ provider: source.provider, refs: source.refs, items });
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const snapshot = {
|
|
65
|
+
generatedAt: startedAt,
|
|
66
|
+
completedAt: new Date().toISOString(),
|
|
67
|
+
sections,
|
|
68
|
+
errors,
|
|
69
|
+
operatingProfile: this.#config.operatingProfile ?? null,
|
|
70
|
+
operatingGaps: buildOperatingGaps(this.#config, { rootDir: this.#rootDir, workspaceDir: this.#workspaceDir, errors }),
|
|
71
|
+
executionGaps: [], // Filled by execution-gap job
|
|
72
|
+
roles: this.#config.roles ?? null,
|
|
73
|
+
summary: buildSummary(sections, errors),
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
return snapshot;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Render a snapshot as markdown for file output or messaging.
|
|
82
|
+
*/
|
|
83
|
+
export function renderMarkdown(snapshot) {
|
|
84
|
+
const lines = [
|
|
85
|
+
`# Construct Snapshot`,
|
|
86
|
+
`> Generated: ${snapshot.generatedAt}`,
|
|
87
|
+
'',
|
|
88
|
+
];
|
|
89
|
+
|
|
90
|
+
if (snapshot.errors.length) {
|
|
91
|
+
lines.push('## ⚠️ Errors', '');
|
|
92
|
+
for (const e of snapshot.errors) {
|
|
93
|
+
lines.push(`- **${e.source}**${e.ref ? `/${e.ref}` : ''}: ${e.error}`);
|
|
94
|
+
}
|
|
95
|
+
lines.push('');
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
lines.push('## Summary', '');
|
|
99
|
+
const s = snapshot.summary;
|
|
100
|
+
lines.push(`- Sources polled: **${s.sourceCount}**`);
|
|
101
|
+
lines.push(`- Total items: **${s.totalItems}**`);
|
|
102
|
+
lines.push(`- Errors: **${s.errorCount}**`);
|
|
103
|
+
lines.push('');
|
|
104
|
+
|
|
105
|
+
if (snapshot.operatingProfile) {
|
|
106
|
+
const profile = snapshot.operatingProfile;
|
|
107
|
+
lines.push('## Operating Profile', '');
|
|
108
|
+
lines.push(`- Mode: **${profile.mode ?? 'embed'}**`);
|
|
109
|
+
lines.push(`- Posture: **${profile.strategy?.defaultPosture ?? 'assistive'}**`);
|
|
110
|
+
lines.push(`- Autonomy: **${profile.strategy?.autonomy ?? 'read-first'}**`);
|
|
111
|
+
lines.push(`- Write policy: **${profile.strategy?.writePolicy ?? 'approval-required-for-high-risk'}**`);
|
|
112
|
+
lines.push('');
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Role lens (from configured primary/secondary roles)
|
|
116
|
+
if (snapshot.roles) {
|
|
117
|
+
const roleLens = renderRoleLensSection(snapshot.roles);
|
|
118
|
+
if (roleLens) lines.push(roleLens);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (snapshot.operatingGaps?.length) {
|
|
122
|
+
lines.push('## Operating Gaps', '');
|
|
123
|
+
for (const gap of snapshot.operatingGaps) {
|
|
124
|
+
lines.push(`- **${gap.severity}** ${gap.summary}`);
|
|
125
|
+
}
|
|
126
|
+
lines.push('');
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (snapshot.executionGaps?.length) {
|
|
130
|
+
lines.push('## Execution Gaps (TPM Analysis)', '');
|
|
131
|
+
for (const gap of snapshot.executionGaps) {
|
|
132
|
+
lines.push(`- **${gap.severity}** ${gap.summary}`);
|
|
133
|
+
}
|
|
134
|
+
lines.push('');
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
for (const section of snapshot.sections) {
|
|
138
|
+
if (!section.items.length) continue;
|
|
139
|
+
lines.push(`## ${section.provider} (${section.items.length} items)`, '');
|
|
140
|
+
for (const item of section.items.slice(0, 10)) {
|
|
141
|
+
lines.push(`- ${formatItem(item)}`);
|
|
142
|
+
}
|
|
143
|
+
if (section.items.length > 10) {
|
|
144
|
+
lines.push(`- _…and ${section.items.length - 10} more_`);
|
|
145
|
+
}
|
|
146
|
+
lines.push('');
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return lines.join('\n');
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function buildSummary(sections, errors) {
|
|
153
|
+
return {
|
|
154
|
+
sourceCount: sections.length,
|
|
155
|
+
totalItems: sections.reduce((n, s) => n + s.items.length, 0),
|
|
156
|
+
errorCount: errors.length,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function buildOperatingGaps(config, { rootDir, workspaceDir, errors }) {
|
|
161
|
+
const gaps = [];
|
|
162
|
+
if (!(config.sources ?? []).length) {
|
|
163
|
+
gaps.push({ severity: 'blocked', kind: 'missing-sources', summary: 'No watched sources are configured, so embed mode has no external bearings.' });
|
|
164
|
+
}
|
|
165
|
+
if (!(config.outputs ?? []).length) {
|
|
166
|
+
gaps.push({ severity: 'attention', kind: 'missing-outputs', summary: 'No snapshot outputs are configured; findings may not reach operators.' });
|
|
167
|
+
}
|
|
168
|
+
const searchRoots = [rootDir, workspaceDir].filter((d, i, arr) => d && arr.indexOf(d) === i);
|
|
169
|
+
for (const resource of config.operatingProfile?.focalResources ?? []) {
|
|
170
|
+
if (!resource.path) continue;
|
|
171
|
+
const found = searchRoots.some((root) => existsSync(resolve(root, resource.path)));
|
|
172
|
+
if (!found) {
|
|
173
|
+
gaps.push({ severity: 'blocked', kind: 'missing-focal-resource', summary: `Focal resource is missing: ${resource.path}` });
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
if (errors?.length) {
|
|
177
|
+
gaps.push({ severity: 'attention', kind: 'source-errors', summary: `${errors.length} configured source read failed.` });
|
|
178
|
+
}
|
|
179
|
+
return gaps;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function formatItem(item) {
|
|
183
|
+
if (item.type === 'commit') return `\`${item.hash?.slice(0, 7)}\` ${item.subject} — ${item.author}`;
|
|
184
|
+
if (item.type === 'issue') return `[${item.key}] ${item.summary} (${item.status})`;
|
|
185
|
+
if (item.type === 'message') return `<${item.user}> ${item.text?.slice(0, 80)}`;
|
|
186
|
+
if (item.type === 'page') return `${item.title} (${item.spaceKey ?? ''})`;
|
|
187
|
+
if (item.summary) return item.summary;
|
|
188
|
+
if (item.title) return item.title;
|
|
189
|
+
if (item.text) return item.text.slice(0, 80);
|
|
190
|
+
return JSON.stringify(item).slice(0, 80);
|
|
191
|
+
}
|