@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,374 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/embed/roadmap.mjs — living roadmap document for embed mode.
|
|
3
|
+
*
|
|
4
|
+
* The roadmap is a **running structured document** for a team or project:
|
|
5
|
+
* - Organized by year → quarter → theme/initiative
|
|
6
|
+
* - Items tied to work-tracking refs (Jira issues, Linear cycles, GitHub milestones)
|
|
7
|
+
* - Cross-linked to related docs (ADRs, PRDs, memos)
|
|
8
|
+
* - Updated incrementally as work progresses (not regenerated from scratch)
|
|
9
|
+
*
|
|
10
|
+
* Construct keeps it current by:
|
|
11
|
+
* - Reconciling snapshot items against existing roadmap entries
|
|
12
|
+
* - Advancing status when linked issues close or transition
|
|
13
|
+
* - Adding new items that appear in tracked sources
|
|
14
|
+
* - Archiving completed quarters
|
|
15
|
+
*
|
|
16
|
+
* Storage:
|
|
17
|
+
* docs/roadmap.md in the target docs lane (not hardcoded to .cx/)
|
|
18
|
+
*
|
|
19
|
+
* Format:
|
|
20
|
+
* # Roadmap
|
|
21
|
+
* > Last updated: <ISO timestamp>
|
|
22
|
+
* > Sources: github, jira, linear
|
|
23
|
+
*
|
|
24
|
+
* ## 2026
|
|
25
|
+
* ### Q2 (Apr–Jun)
|
|
26
|
+
* #### Theme: Embed Mode Redesign
|
|
27
|
+
* - [x] Target resolver — PROJ-123 · ADR-005
|
|
28
|
+
* - [ ] Docs lifecycle — PROJ-124
|
|
29
|
+
* - [ ] Dashboard notifications — PROJ-125 · PRD-012
|
|
30
|
+
*
|
|
31
|
+
* ### Q1 (Jan–Mar) ✓
|
|
32
|
+
* #### Theme: Core Platform
|
|
33
|
+
* - [x] MCP server — PROJ-100
|
|
34
|
+
* - [x] Provider registry — PROJ-101
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
import { existsSync, readFileSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
38
|
+
import { join } from 'node:path';
|
|
39
|
+
import { searchObservations } from '../observation-store.mjs';
|
|
40
|
+
import { renderRoleLensSection } from './role-framing.mjs';
|
|
41
|
+
|
|
42
|
+
// ─── Quarter/Year utilities ──────────────────────────────────────────────────
|
|
43
|
+
|
|
44
|
+
function currentQuarter() {
|
|
45
|
+
const now = new Date();
|
|
46
|
+
const q = Math.ceil((now.getMonth() + 1) / 3);
|
|
47
|
+
return { year: now.getFullYear(), quarter: q };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function quarterLabel(q) {
|
|
51
|
+
const months = ['Jan–Mar', 'Apr–Jun', 'Jul–Sep', 'Oct–Dec'];
|
|
52
|
+
return `Q${q.quarter} (${months[q.quarter - 1]})`;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function quarterKey(q) {
|
|
56
|
+
return `${q.year}-Q${q.quarter}`;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// ─── Roadmap data model ──────────────────────────────────────────────────────
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* A roadmap entry represents one trackable item.
|
|
63
|
+
* @typedef {object} RoadmapEntry
|
|
64
|
+
* @property {string} title
|
|
65
|
+
* @property {string} status - 'planned' | 'in-progress' | 'done' | 'cancelled'
|
|
66
|
+
* @property {string[]} refs - work-tracking refs (e.g. 'PROJ-123', '#45', 'ENG-100')
|
|
67
|
+
* @property {string[]} docs - linked docs (e.g. 'ADR-005', 'PRD-012')
|
|
68
|
+
* @property {string|null} theme - grouping theme/initiative
|
|
69
|
+
* @property {string} quarter - e.g. '2026-Q2'
|
|
70
|
+
*/
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Parse an existing roadmap.md into a structured model.
|
|
74
|
+
* Returns { meta, entries[] } or null if file doesn't exist.
|
|
75
|
+
*/
|
|
76
|
+
export function parseRoadmap(filePath) {
|
|
77
|
+
if (!existsSync(filePath)) return null;
|
|
78
|
+
|
|
79
|
+
const content = readFileSync(filePath, 'utf8');
|
|
80
|
+
const lines = content.split('\n');
|
|
81
|
+
const entries = [];
|
|
82
|
+
let currentYear = null;
|
|
83
|
+
let currentQtr = null;
|
|
84
|
+
let currentTheme = null;
|
|
85
|
+
|
|
86
|
+
for (const line of lines) {
|
|
87
|
+
// Year header: ## 2026
|
|
88
|
+
const yearMatch = line.match(/^## (\d{4})/);
|
|
89
|
+
if (yearMatch) { currentYear = parseInt(yearMatch[1]); continue; }
|
|
90
|
+
|
|
91
|
+
// Quarter header: ### Q2 (Apr–Jun)
|
|
92
|
+
const qtrMatch = line.match(/^### Q(\d)/);
|
|
93
|
+
if (qtrMatch && currentYear) {
|
|
94
|
+
currentQtr = `${currentYear}-Q${qtrMatch[1]}`;
|
|
95
|
+
currentTheme = null;
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Theme header: #### Theme: ...
|
|
100
|
+
const themeMatch = line.match(/^#### (?:Theme: )?(.+)/);
|
|
101
|
+
if (themeMatch) { currentTheme = themeMatch[1].trim(); continue; }
|
|
102
|
+
|
|
103
|
+
// Entry: - [x] or - [ ] title — refs · docs
|
|
104
|
+
const entryMatch = line.match(/^- \[([ x])\] (.+)/);
|
|
105
|
+
if (entryMatch && currentQtr) {
|
|
106
|
+
const done = entryMatch[1] === 'x';
|
|
107
|
+
const rest = entryMatch[2];
|
|
108
|
+
|
|
109
|
+
// Extract refs (PROJ-123, #45, ENG-100 patterns) and docs (ADR-005, PRD-012)
|
|
110
|
+
const refs = [];
|
|
111
|
+
const docs = [];
|
|
112
|
+
const refPattern = /([A-Z]+-\d+|#\d+)/g;
|
|
113
|
+
const docPattern = /(ADR-\d+|PRD-\d+|RFC-\d+|MEMO-\d+)/g;
|
|
114
|
+
let m;
|
|
115
|
+
while ((m = refPattern.exec(rest)) !== null) {
|
|
116
|
+
if (docPattern.test(m[1])) docs.push(m[1]);
|
|
117
|
+
else refs.push(m[1]);
|
|
118
|
+
}
|
|
119
|
+
// Re-run doc pattern on full string
|
|
120
|
+
const docMatches = rest.match(/(ADR-\d+|PRD-\d+|RFC-\d+|MEMO-\d+)/g) ?? [];
|
|
121
|
+
docs.push(...docMatches.filter((d) => !docs.includes(d)));
|
|
122
|
+
|
|
123
|
+
// Title is everything before the first ref separator
|
|
124
|
+
const title = rest.split(/\s+[—·]\s+/)[0].trim();
|
|
125
|
+
|
|
126
|
+
entries.push({
|
|
127
|
+
title,
|
|
128
|
+
status: done ? 'done' : 'planned',
|
|
129
|
+
refs,
|
|
130
|
+
docs,
|
|
131
|
+
theme: currentTheme,
|
|
132
|
+
quarter: currentQtr,
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return { entries };
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Reconcile snapshot items against existing roadmap entries.
|
|
142
|
+
* - Items in snapshot that match a roadmap ref: update status
|
|
143
|
+
* - Items in snapshot with no roadmap entry: add to current quarter
|
|
144
|
+
* - Roadmap entries whose refs are now closed: mark done
|
|
145
|
+
*
|
|
146
|
+
* @param {RoadmapEntry[]} existing - Parsed roadmap entries
|
|
147
|
+
* @param {object[]} snapshotItems - Open items from snapshot sections
|
|
148
|
+
* @param {object} [opts]
|
|
149
|
+
* @returns {RoadmapEntry[]} Updated entry list
|
|
150
|
+
*/
|
|
151
|
+
export function reconcileEntries(existing, snapshotItems, opts = {}) {
|
|
152
|
+
const entries = [...existing];
|
|
153
|
+
const { year, quarter } = currentQuarter();
|
|
154
|
+
const currentQtrKey = quarterKey({ year, quarter });
|
|
155
|
+
|
|
156
|
+
// Index existing entries by ref for fast lookup
|
|
157
|
+
const byRef = new Map();
|
|
158
|
+
for (const entry of entries) {
|
|
159
|
+
for (const ref of entry.refs) byRef.set(ref, entry);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Track which snapshot items matched
|
|
163
|
+
const matched = new Set();
|
|
164
|
+
|
|
165
|
+
for (const item of snapshotItems) {
|
|
166
|
+
const key = item.key ?? (item.number ? `#${item.number}` : null);
|
|
167
|
+
if (!key) continue;
|
|
168
|
+
|
|
169
|
+
if (byRef.has(key)) {
|
|
170
|
+
// Update status
|
|
171
|
+
const entry = byRef.get(key);
|
|
172
|
+
const itemStatus = normalizeStatus(item.status);
|
|
173
|
+
if (itemStatus === 'done' && entry.status !== 'done') {
|
|
174
|
+
entry.status = 'done';
|
|
175
|
+
} else if (itemStatus === 'in-progress' && entry.status === 'planned') {
|
|
176
|
+
entry.status = 'in-progress';
|
|
177
|
+
}
|
|
178
|
+
matched.add(key);
|
|
179
|
+
} else {
|
|
180
|
+
// New item — add to current quarter
|
|
181
|
+
const title = item.summary ?? item.title ?? item.subject ?? '(untitled)';
|
|
182
|
+
entries.push({
|
|
183
|
+
title,
|
|
184
|
+
status: normalizeStatus(item.status),
|
|
185
|
+
refs: key ? [key] : [],
|
|
186
|
+
docs: [],
|
|
187
|
+
theme: null,
|
|
188
|
+
quarter: currentQtrKey,
|
|
189
|
+
});
|
|
190
|
+
matched.add(key);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return entries;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function normalizeStatus(raw) {
|
|
198
|
+
const s = String(raw ?? '').toLowerCase();
|
|
199
|
+
if (/done|closed|merged|resolved|completed/.test(s)) return 'done';
|
|
200
|
+
if (/in.?progress|active|doing|started/.test(s)) return 'in-progress';
|
|
201
|
+
if (/cancelled|won.?t|wont/.test(s)) return 'cancelled';
|
|
202
|
+
return 'planned';
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Render entries back to roadmap markdown.
|
|
207
|
+
*/
|
|
208
|
+
export function renderRoadmap(entries, opts = {}) {
|
|
209
|
+
const { roles, sources } = opts;
|
|
210
|
+
const updatedAt = new Date().toISOString();
|
|
211
|
+
|
|
212
|
+
const lines = [
|
|
213
|
+
'# Roadmap',
|
|
214
|
+
`> Last updated: ${updatedAt}`,
|
|
215
|
+
];
|
|
216
|
+
if (sources?.length) {
|
|
217
|
+
lines.push(`> Sources: ${sources.join(', ')}`);
|
|
218
|
+
}
|
|
219
|
+
lines.push('');
|
|
220
|
+
|
|
221
|
+
// Role lens
|
|
222
|
+
if (roles) {
|
|
223
|
+
const roleLens = renderRoleLensSection(roles);
|
|
224
|
+
if (roleLens) lines.push(roleLens);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Group by year → quarter → theme
|
|
228
|
+
const byYear = new Map();
|
|
229
|
+
for (const entry of entries) {
|
|
230
|
+
const [yearStr, qtr] = (entry.quarter ?? '').split('-');
|
|
231
|
+
const year = parseInt(yearStr) || currentQuarter().year;
|
|
232
|
+
if (!byYear.has(year)) byYear.set(year, new Map());
|
|
233
|
+
const quarters = byYear.get(year);
|
|
234
|
+
if (!quarters.has(entry.quarter)) quarters.set(entry.quarter, []);
|
|
235
|
+
quarters.get(entry.quarter).push(entry);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Sort years descending (current year first)
|
|
239
|
+
const sortedYears = [...byYear.keys()].sort((a, b) => b - a);
|
|
240
|
+
|
|
241
|
+
for (const year of sortedYears) {
|
|
242
|
+
lines.push(`## ${year}`, '');
|
|
243
|
+
const quarters = byYear.get(year);
|
|
244
|
+
// Sort quarters descending within year
|
|
245
|
+
const sortedQtrs = [...quarters.keys()].sort().reverse();
|
|
246
|
+
|
|
247
|
+
for (const qtrKey of sortedQtrs) {
|
|
248
|
+
const qtrEntries = quarters.get(qtrKey);
|
|
249
|
+
const allDone = qtrEntries.every((e) => e.status === 'done' || e.status === 'cancelled');
|
|
250
|
+
const qNum = parseInt(qtrKey.split('Q')[1]) || 1;
|
|
251
|
+
const label = quarterLabel({ year, quarter: qNum });
|
|
252
|
+
lines.push(`### ${label}${allDone ? ' ✓' : ''}`, '');
|
|
253
|
+
|
|
254
|
+
// Group by theme within quarter
|
|
255
|
+
const byTheme = new Map();
|
|
256
|
+
for (const entry of qtrEntries) {
|
|
257
|
+
const theme = entry.theme ?? '_ungrouped';
|
|
258
|
+
if (!byTheme.has(theme)) byTheme.set(theme, []);
|
|
259
|
+
byTheme.get(theme).push(entry);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
for (const [theme, items] of byTheme) {
|
|
263
|
+
if (theme !== '_ungrouped') {
|
|
264
|
+
lines.push(`#### ${theme}`, '');
|
|
265
|
+
}
|
|
266
|
+
for (const item of items) {
|
|
267
|
+
const check = item.status === 'done' ? 'x' : ' ';
|
|
268
|
+
const refStr = item.refs.length ? ` — ${item.refs.join(', ')}` : '';
|
|
269
|
+
const docStr = item.docs.length ? ` · ${item.docs.join(', ')}` : '';
|
|
270
|
+
const statusTag = item.status === 'in-progress' ? ' 🔄' : item.status === 'cancelled' ? ' ~~cancelled~~' : '';
|
|
271
|
+
lines.push(`- [${check}] ${item.title}${statusTag}${refStr}${docStr}`);
|
|
272
|
+
}
|
|
273
|
+
lines.push('');
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
if (!entries.length) {
|
|
279
|
+
lines.push('_No roadmap items tracked yet._', '');
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
return lines.join('\n');
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Generate or update the roadmap for a target.
|
|
287
|
+
*
|
|
288
|
+
* If a roadmap already exists, parses it and reconciles with snapshot data.
|
|
289
|
+
* If not, creates a new one from snapshot items.
|
|
290
|
+
*
|
|
291
|
+
* @param {object} opts
|
|
292
|
+
* @param {string} opts.targetPath - Target root path (docs/roadmap.md will be created here)
|
|
293
|
+
* @param {object} opts.snapshot - Last snapshot from embed
|
|
294
|
+
* @param {object} [opts.roles] - Configured roles for role lens
|
|
295
|
+
* @returns {{ path: string, updatedAt: string, itemCount: number, isNew: boolean }}
|
|
296
|
+
*/
|
|
297
|
+
export async function generateRoadmap({ targetPath, snapshot, roles }) {
|
|
298
|
+
const updatedAt = new Date().toISOString();
|
|
299
|
+
|
|
300
|
+
if (!snapshot) {
|
|
301
|
+
return { path: null, updatedAt, itemCount: 0, skipped: true, reason: 'no snapshot available' };
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
const roadmapPath = join(targetPath, 'docs', 'roadmap.md');
|
|
305
|
+
|
|
306
|
+
// Parse existing or start fresh
|
|
307
|
+
const existing = parseRoadmap(roadmapPath);
|
|
308
|
+
const isNew = !existing;
|
|
309
|
+
|
|
310
|
+
// Extract open items from snapshot
|
|
311
|
+
const snapshotItems = [];
|
|
312
|
+
for (const section of snapshot.sections ?? []) {
|
|
313
|
+
for (const item of section.items ?? []) {
|
|
314
|
+
snapshotItems.push({ ...item, _provider: section.provider });
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// Reconcile
|
|
319
|
+
const entries = reconcileEntries(existing?.entries ?? [], snapshotItems);
|
|
320
|
+
|
|
321
|
+
// Determine sources
|
|
322
|
+
const sources = [...new Set((snapshot.sections ?? []).map((s) => s.provider))];
|
|
323
|
+
|
|
324
|
+
// Render
|
|
325
|
+
const markdown = renderRoadmap(entries, { roles, sources });
|
|
326
|
+
|
|
327
|
+
// Write
|
|
328
|
+
mkdirSync(join(targetPath, 'docs'), { recursive: true });
|
|
329
|
+
writeFileSync(roadmapPath, markdown, 'utf8');
|
|
330
|
+
|
|
331
|
+
return { path: roadmapPath, updatedAt, itemCount: entries.length, isNew };
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Produce a short Slack-friendly summary of the roadmap (≤3000 chars).
|
|
336
|
+
*/
|
|
337
|
+
export async function roadmapSlackSummary({ targetPath, snapshot, roles }) {
|
|
338
|
+
const result = await generateRoadmap({ targetPath, snapshot, roles });
|
|
339
|
+
if (result.skipped) return null;
|
|
340
|
+
|
|
341
|
+
const { year, quarter } = currentQuarter();
|
|
342
|
+
const qtrKey = quarterKey({ year, quarter });
|
|
343
|
+
|
|
344
|
+
const roadmapPath = join(targetPath, 'docs', 'roadmap.md');
|
|
345
|
+
const parsed = parseRoadmap(roadmapPath);
|
|
346
|
+
const currentItems = (parsed?.entries ?? []).filter((e) => e.quarter === qtrKey);
|
|
347
|
+
|
|
348
|
+
const done = currentItems.filter((e) => e.status === 'done').length;
|
|
349
|
+
const total = currentItems.length;
|
|
350
|
+
|
|
351
|
+
const lines = [
|
|
352
|
+
`*Roadmap* — ${quarterLabel({ year, quarter })} ${year} — ${done}/${total} done`,
|
|
353
|
+
];
|
|
354
|
+
|
|
355
|
+
const inProgress = currentItems.filter((e) => e.status === 'in-progress').slice(0, 5);
|
|
356
|
+
if (inProgress.length) {
|
|
357
|
+
lines.push('\n*In progress:*');
|
|
358
|
+
inProgress.forEach((item, i) => {
|
|
359
|
+
const refs = item.refs.length ? ` (${item.refs.join(', ')})` : '';
|
|
360
|
+
lines.push(`${i + 1}. ${item.title}${refs}`);
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
const planned = currentItems.filter((e) => e.status === 'planned').slice(0, 5);
|
|
365
|
+
if (planned.length) {
|
|
366
|
+
lines.push('\n*Up next:*');
|
|
367
|
+
planned.forEach((item, i) => {
|
|
368
|
+
lines.push(`${i + 1}. ${item.title}`);
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
lines.push(`\nFull roadmap: \`docs/roadmap.md\``);
|
|
373
|
+
return lines.join('\n').slice(0, 3000);
|
|
374
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/embed/role-framing.mjs — load and apply role orientations for embed mode.
|
|
3
|
+
*
|
|
4
|
+
* When embed has configured roles (primary/secondary), this module:
|
|
5
|
+
* 1. Loads the embedOrientation from the agent registry for each role
|
|
6
|
+
* 2. Provides a combined lens (focusAreas, riskSignals, artifactBias)
|
|
7
|
+
* 3. Renders a "Role Lens" section for inclusion in snapshots/roadmaps
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { readFileSync } from 'node:fs';
|
|
11
|
+
import { join, dirname } from 'node:path';
|
|
12
|
+
import { fileURLToPath } from 'node:url';
|
|
13
|
+
|
|
14
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
15
|
+
const REGISTRY_PATH = join(__dirname, '..', '..', 'agents', 'registry.json');
|
|
16
|
+
|
|
17
|
+
let _registryCache = null;
|
|
18
|
+
|
|
19
|
+
function loadRegistry() {
|
|
20
|
+
if (_registryCache) return _registryCache;
|
|
21
|
+
try {
|
|
22
|
+
_registryCache = JSON.parse(readFileSync(REGISTRY_PATH, 'utf8'));
|
|
23
|
+
} catch (err) {
|
|
24
|
+
process.stderr.write('[role-framing.mjs] loadRegistry: ' + (err?.message ?? String(err)) + '\n');
|
|
25
|
+
_registryCache = { agents: [] };
|
|
26
|
+
}
|
|
27
|
+
return _registryCache;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Get the embedOrientation for a named agent role.
|
|
32
|
+
* @param {string} roleName - e.g. 'product-manager', 'architect'
|
|
33
|
+
* @returns {object|null} The embedOrientation object or null
|
|
34
|
+
*/
|
|
35
|
+
export function getOrientation(roleName) {
|
|
36
|
+
if (!roleName) return null;
|
|
37
|
+
const registry = loadRegistry();
|
|
38
|
+
const agent = (registry.agents ?? []).find((a) => a.name === roleName);
|
|
39
|
+
return agent?.embedOrientation ?? null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Build a combined role lens from primary and secondary roles.
|
|
44
|
+
* Primary focusAreas/riskSignals come first; secondary ones are appended (deduplicated).
|
|
45
|
+
*
|
|
46
|
+
* @param {object} roles - { primary: string|null, secondary: string|null }
|
|
47
|
+
* @returns {object} { focusAreas: string[], riskSignals: string[], artifactBias: string[], roles: { primary, secondary } }
|
|
48
|
+
*/
|
|
49
|
+
export function buildRoleLens(roles) {
|
|
50
|
+
const primary = getOrientation(roles?.primary);
|
|
51
|
+
const secondary = getOrientation(roles?.secondary);
|
|
52
|
+
|
|
53
|
+
if (!primary && !secondary) return null;
|
|
54
|
+
|
|
55
|
+
const focusAreas = [...(primary?.focusAreas ?? [])];
|
|
56
|
+
const riskSignals = [...(primary?.riskSignals ?? [])];
|
|
57
|
+
const artifactBias = [...(primary?.artifactBias ?? [])];
|
|
58
|
+
|
|
59
|
+
// Append secondary, deduplicated
|
|
60
|
+
for (const area of secondary?.focusAreas ?? []) {
|
|
61
|
+
if (!focusAreas.includes(area)) focusAreas.push(area);
|
|
62
|
+
}
|
|
63
|
+
for (const sig of secondary?.riskSignals ?? []) {
|
|
64
|
+
if (!riskSignals.includes(sig)) riskSignals.push(sig);
|
|
65
|
+
}
|
|
66
|
+
for (const bias of secondary?.artifactBias ?? []) {
|
|
67
|
+
if (!artifactBias.includes(bias)) artifactBias.push(bias);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
focusAreas,
|
|
72
|
+
riskSignals,
|
|
73
|
+
artifactBias,
|
|
74
|
+
roles: { primary: roles.primary, secondary: roles.secondary },
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Render a Role Lens markdown section for inclusion in snapshot/roadmap output.
|
|
80
|
+
* Returns empty string if no roles configured.
|
|
81
|
+
*
|
|
82
|
+
* @param {object} roles - { primary, secondary } from embed config
|
|
83
|
+
* @returns {string}
|
|
84
|
+
*/
|
|
85
|
+
export function renderRoleLensSection(roles) {
|
|
86
|
+
const lens = buildRoleLens(roles);
|
|
87
|
+
if (!lens) return '';
|
|
88
|
+
|
|
89
|
+
const lines = ['## Role Lens', ''];
|
|
90
|
+
|
|
91
|
+
if (lens.roles.primary) lines.push(`- **Primary**: ${lens.roles.primary}`);
|
|
92
|
+
if (lens.roles.secondary) lines.push(`- **Secondary**: ${lens.roles.secondary}`);
|
|
93
|
+
lines.push('');
|
|
94
|
+
|
|
95
|
+
if (lens.focusAreas.length) {
|
|
96
|
+
lines.push(`**Focus**: ${lens.focusAreas.join(', ')}`);
|
|
97
|
+
}
|
|
98
|
+
if (lens.riskSignals.length) {
|
|
99
|
+
lines.push(`**Risk signals**: ${lens.riskSignals.join(', ')}`);
|
|
100
|
+
}
|
|
101
|
+
if (lens.artifactBias.length) {
|
|
102
|
+
lines.push(`**Artifact bias**: ${lens.artifactBias.join(', ')}`);
|
|
103
|
+
}
|
|
104
|
+
lines.push('');
|
|
105
|
+
|
|
106
|
+
return lines.join('\n');
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** Reset internal cache (for testing). */
|
|
110
|
+
export function _resetCache() { _registryCache = null; }
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/embed/scheduler.mjs — interval-based task scheduler for embed mode.
|
|
3
|
+
*
|
|
4
|
+
* Lightweight alternative to a full cron library — runs tasks at fixed
|
|
5
|
+
* intervals, tracks last-run times, and supports graceful shutdown.
|
|
6
|
+
* Zero external deps.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export class Scheduler {
|
|
10
|
+
#tasks = new Map(); // id → { fn, intervalMs, lastRun, timer, label }
|
|
11
|
+
#running = false;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Register a task. Returns the task id.
|
|
15
|
+
* @param {string} label - Human-readable name for logging
|
|
16
|
+
* @param {number} intervalMs - How often to run (ms)
|
|
17
|
+
* @param {Function} fn - Async function to call; receives { label, lastRun }
|
|
18
|
+
* @param {object} [opts]
|
|
19
|
+
* @param {boolean} [opts.runImmediately=false] - Run once before first interval fires
|
|
20
|
+
* @param {boolean} [opts.unref=false] - Call timer.unref() so this task does not keep the event loop alive (use in tests)
|
|
21
|
+
*/
|
|
22
|
+
register(label, intervalMs, fn, { runImmediately = false, unref = false } = {}) {
|
|
23
|
+
const id = `${label}-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
24
|
+
const task = { label, intervalMs, fn, lastRun: null, timer: null, runImmediately, unref };
|
|
25
|
+
this.#tasks.set(id, task);
|
|
26
|
+
|
|
27
|
+
if (this.#running) {
|
|
28
|
+
this.#startTask(id, task, runImmediately);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return id;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Start all registered tasks.
|
|
36
|
+
*/
|
|
37
|
+
start() {
|
|
38
|
+
if (this.#running) return;
|
|
39
|
+
this.#running = true;
|
|
40
|
+
for (const [id, task] of this.#tasks) {
|
|
41
|
+
this.#startTask(id, task, task.runImmediately);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Stop all tasks gracefully.
|
|
47
|
+
*/
|
|
48
|
+
stop() {
|
|
49
|
+
this.#running = false;
|
|
50
|
+
for (const task of this.#tasks.values()) {
|
|
51
|
+
if (task.timer) {
|
|
52
|
+
clearInterval(task.timer);
|
|
53
|
+
task.timer = null;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Remove a task by id.
|
|
60
|
+
*/
|
|
61
|
+
unregister(id) {
|
|
62
|
+
const task = this.#tasks.get(id);
|
|
63
|
+
if (task?.timer) clearInterval(task.timer);
|
|
64
|
+
this.#tasks.delete(id);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* List registered tasks with their status.
|
|
69
|
+
*/
|
|
70
|
+
status() {
|
|
71
|
+
return [...this.#tasks.entries()].map(([id, t]) => ({
|
|
72
|
+
id,
|
|
73
|
+
label: t.label,
|
|
74
|
+
intervalMs: t.intervalMs,
|
|
75
|
+
lastRun: t.lastRun,
|
|
76
|
+
active: Boolean(t.timer),
|
|
77
|
+
}));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
#startTask(id, task, runImmediately) {
|
|
81
|
+
if (runImmediately) {
|
|
82
|
+
this.#runTask(task).catch((err) => {
|
|
83
|
+
process.stderr.write(`[scheduler] ${task.label} error: ${err.message}\n`);
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
task.timer = setInterval(() => {
|
|
87
|
+
this.#runTask(task).catch((err) => {
|
|
88
|
+
process.stderr.write(`[scheduler] ${task.label} error: ${err.message}\n`);
|
|
89
|
+
});
|
|
90
|
+
}, task.intervalMs);
|
|
91
|
+
// Tasks keep the event loop alive by default. Tests opt in to unref().
|
|
92
|
+
if (task.unref && task.timer.unref) task.timer.unref();
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async #runTask(task) {
|
|
96
|
+
task.lastRun = new Date();
|
|
97
|
+
await task.fn({ label: task.label, lastRun: task.lastRun });
|
|
98
|
+
}
|
|
99
|
+
}
|