@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,449 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/embed/demand-fetch.mjs — on-demand provider snapshot.
|
|
3
|
+
*
|
|
4
|
+
* Fires a targeted one-shot snapshot for a named source when knowledge is
|
|
5
|
+
* stale or absent. Triggered by the MCP `provider_fetch` tool when Construct
|
|
6
|
+
* detects a user question names a known configured repo or project.
|
|
7
|
+
*
|
|
8
|
+
* Design:
|
|
9
|
+
* - Resolves source config from env (same path as the daemon's auto-discovery)
|
|
10
|
+
* - Matches a query string against known repo names, project keys, and aliases
|
|
11
|
+
* - Runs a single provider.read() call for the matched source
|
|
12
|
+
* - Writes results as observations into the knowledge base
|
|
13
|
+
* - Returns a structured result the MCP layer can surface directly
|
|
14
|
+
*
|
|
15
|
+
* Does NOT require the daemon to be running.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { homedir } from 'node:os';
|
|
19
|
+
import { join } from 'node:path';
|
|
20
|
+
import { loadConstructEnv } from '../env-config.mjs';
|
|
21
|
+
import { addObservation } from '../observation-store.mjs';
|
|
22
|
+
|
|
23
|
+
// ─── Self-query detection ────────────────────────────────────────────────────
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Detect queries that are asking about Construct itself (not an external provider).
|
|
27
|
+
* These should route to knowledge_search, not to external providers.
|
|
28
|
+
*
|
|
29
|
+
* IMPORTANT: Only match when the query is clearly about Construct-the-tool,
|
|
30
|
+
* not about an external repo/project that happens to use similar words.
|
|
31
|
+
* When in doubt, let matchSourceFromQuery() decide — provider queries should
|
|
32
|
+
* never be blocked by this gate.
|
|
33
|
+
*/
|
|
34
|
+
const SELF_QUERY_PATTERNS = [
|
|
35
|
+
/\bconstruct\b/i,
|
|
36
|
+
/\bwhat (is|are) (this|the) (tool|system|agent|platform)\b/i,
|
|
37
|
+
/\bhow does (this|the) (tool|system|agent) work\b/i,
|
|
38
|
+
/\bwhat can (you|it|this) do\b/i,
|
|
39
|
+
/\bwhat commands?\b/i,
|
|
40
|
+
/\bavailable commands?\b/i,
|
|
41
|
+
/\bembed (mode|daemon)\b/i,
|
|
42
|
+
/\bauthority guard\b/i,
|
|
43
|
+
/\bcx[\s\/]knowledge\b/i,
|
|
44
|
+
/\bprovider (framework|interface|abstraction)\b/i,
|
|
45
|
+
];
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* @param {string} query
|
|
49
|
+
* @returns {boolean}
|
|
50
|
+
*/
|
|
51
|
+
function isSelfQuery(query) {
|
|
52
|
+
if (!query) return false;
|
|
53
|
+
return SELF_QUERY_PATTERNS.some(re => re.test(query));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// ─── Source name resolution ──────────────────────────────────────────────────
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Build a flat list of known source identifiers from env.
|
|
60
|
+
* Maps each identifier to a { provider, ref } descriptor.
|
|
61
|
+
*
|
|
62
|
+
* @param {object} env
|
|
63
|
+
* @returns {{ id: string, provider: string, ref: string, display: string }[]}
|
|
64
|
+
*/
|
|
65
|
+
export function resolveKnownSources(env = process.env) {
|
|
66
|
+
const sources = [];
|
|
67
|
+
|
|
68
|
+
// GitHub repos
|
|
69
|
+
const repos = (env.GITHUB_REPOS ?? '').split(',').map(r => r.trim()).filter(Boolean);
|
|
70
|
+
for (const repo of repos) {
|
|
71
|
+
const short = repo.split('/').pop(); // "project-iverson" from "hashicorp/project-iverson"
|
|
72
|
+
sources.push({ id: repo.toLowerCase(), provider: 'github', ref: repo, display: repo });
|
|
73
|
+
sources.push({ id: short.toLowerCase(), provider: 'github', ref: repo, display: repo });
|
|
74
|
+
// Normalised: "projectiverson", "iverson"
|
|
75
|
+
sources.push({ id: short.replace(/-/g, '').toLowerCase(), provider: 'github', ref: repo, display: repo });
|
|
76
|
+
// Last word: "iverson" from "project-iverson"
|
|
77
|
+
const words = short.split('-');
|
|
78
|
+
if (words.length > 1) {
|
|
79
|
+
sources.push({ id: words[words.length - 1].toLowerCase(), provider: 'github', ref: repo, display: repo });
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Jira projects
|
|
84
|
+
const jiraUrl = (env.JIRA_BASE_URL ?? '').trim();
|
|
85
|
+
if (jiraUrl) {
|
|
86
|
+
// JIRA_PROJECTS=PLAT,INF,ENG — optional explicit list
|
|
87
|
+
const projects = (env.JIRA_PROJECTS ?? '').split(',').map(p => p.trim()).filter(Boolean);
|
|
88
|
+
for (const proj of projects) {
|
|
89
|
+
sources.push({ id: proj.toLowerCase(), provider: 'jira', ref: proj, display: `Jira/${proj}` });
|
|
90
|
+
}
|
|
91
|
+
// Always add a generic "jira" entry
|
|
92
|
+
sources.push({ id: 'jira', provider: 'jira', ref: null, display: 'Jira' });
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Linear
|
|
96
|
+
if (env.LINEAR_API_KEY) {
|
|
97
|
+
sources.push({ id: 'linear', provider: 'linear', ref: null, display: 'Linear' });
|
|
98
|
+
const teams = (env.LINEAR_TEAMS ?? '').split(',').map(t => t.trim()).filter(Boolean);
|
|
99
|
+
for (const team of teams) {
|
|
100
|
+
sources.push({ id: team.toLowerCase(), provider: 'linear', ref: team, display: `Linear/${team}` });
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return sources;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Match a free-text query against known source identifiers.
|
|
109
|
+
* Returns the best match or null.
|
|
110
|
+
*
|
|
111
|
+
* @param {string} query
|
|
112
|
+
* @param {object} env
|
|
113
|
+
* @returns {{ provider: string, ref: string, display: string } | null}
|
|
114
|
+
*/
|
|
115
|
+
export function matchSourceFromQuery(query, env = process.env) {
|
|
116
|
+
const sources = resolveKnownSources(env);
|
|
117
|
+
const q = query.toLowerCase();
|
|
118
|
+
|
|
119
|
+
// Exact or substring match — longest id wins
|
|
120
|
+
const matches = sources.filter(s => q.includes(s.id));
|
|
121
|
+
if (!matches.length) return null;
|
|
122
|
+
|
|
123
|
+
// Prefer longer (more specific) ids
|
|
124
|
+
matches.sort((a, b) => b.id.length - a.id.length);
|
|
125
|
+
return matches[0];
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// ─── On-demand fetch ─────────────────────────────────────────────────────────
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Fire a targeted one-shot fetch for a named source. When no specific source
|
|
132
|
+
* matches the query, falls back to fetching from ALL configured providers
|
|
133
|
+
* (the "I don't know what you mean, pull everything fresh" path).
|
|
134
|
+
*
|
|
135
|
+
* @param {object} opts
|
|
136
|
+
* @param {string} opts.query - Free-text query naming the source
|
|
137
|
+
* @param {string} [opts.rootDir] - Data root dir (default: homedir())
|
|
138
|
+
* @param {object} [opts.env] - Env override
|
|
139
|
+
* @returns {Promise<DemandFetchResult>}
|
|
140
|
+
*/
|
|
141
|
+
export async function demandFetch({ query, rootDir, env } = {}) {
|
|
142
|
+
const resolvedEnv = env ?? loadConstructEnv();
|
|
143
|
+
const root = rootDir ?? (resolvedEnv.CX_DATA_DIR?.trim() || homedir());
|
|
144
|
+
|
|
145
|
+
// Provider match takes priority: if the query names a configured repo/project,
|
|
146
|
+
// always fetch from that provider — even if isSelfQuery() also fires.
|
|
147
|
+
const match = matchSourceFromQuery(query, resolvedEnv);
|
|
148
|
+
|
|
149
|
+
// Self-referential queries about Construct itself → route to knowledge_search,
|
|
150
|
+
// but only when no external provider source was matched.
|
|
151
|
+
if (!match && isSelfQuery(query)) {
|
|
152
|
+
const { knowledgeSearch } = await import('../knowledge/search.mjs');
|
|
153
|
+
const result = knowledgeSearch({ query, topK: 5 });
|
|
154
|
+
return {
|
|
155
|
+
ok: result.ok,
|
|
156
|
+
reason: result.ok ? 'knowledge_search' : 'knowledge_search_empty',
|
|
157
|
+
message: result.message,
|
|
158
|
+
items: result.hits.map(h => ({
|
|
159
|
+
title: h.heading || h.file,
|
|
160
|
+
summary: h.text,
|
|
161
|
+
url: null,
|
|
162
|
+
state: null,
|
|
163
|
+
source: h.file,
|
|
164
|
+
})),
|
|
165
|
+
knowledgeHits: result.hits,
|
|
166
|
+
sources: result.sources,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (!match) {
|
|
171
|
+
// No specific source match and not a self-query — fetch from all configured providers
|
|
172
|
+
return demandFetchAll({ query, rootDir: root, env: resolvedEnv });
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Dynamically import provider registry to avoid loading all providers at module init
|
|
176
|
+
const { ProviderRegistry } = await import('./providers/registry.mjs');
|
|
177
|
+
const registry = await ProviderRegistry.fromEnv(resolvedEnv);
|
|
178
|
+
const provider = registry.get(match.provider);
|
|
179
|
+
|
|
180
|
+
if (!provider) {
|
|
181
|
+
return {
|
|
182
|
+
ok: false,
|
|
183
|
+
reason: 'provider_not_registered',
|
|
184
|
+
message: `Provider "${match.provider}" is not registered (check credentials in config.env)`,
|
|
185
|
+
items: [],
|
|
186
|
+
match,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Build read calls for the matched source and execute them
|
|
191
|
+
const readCalls = buildReadCalls(match, resolvedEnv);
|
|
192
|
+
if (!readCalls.length) {
|
|
193
|
+
return {
|
|
194
|
+
ok: false,
|
|
195
|
+
reason: 'unsupported_provider',
|
|
196
|
+
message: `No read strategy defined for provider "${match.provider}"`,
|
|
197
|
+
items: [],
|
|
198
|
+
match,
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
let rawItems = [];
|
|
203
|
+
try {
|
|
204
|
+
for (const { ref, opts } of readCalls) {
|
|
205
|
+
const items = await provider.read(ref, opts);
|
|
206
|
+
if (items?.length) rawItems.push(...items);
|
|
207
|
+
}
|
|
208
|
+
} catch (err) {
|
|
209
|
+
return {
|
|
210
|
+
ok: false,
|
|
211
|
+
reason: 'fetch_error',
|
|
212
|
+
message: `Failed to fetch from ${match.display}: ${err.message}`,
|
|
213
|
+
items: [],
|
|
214
|
+
match,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (!rawItems?.length) {
|
|
219
|
+
return {
|
|
220
|
+
ok: true,
|
|
221
|
+
reason: 'empty',
|
|
222
|
+
message: `${match.display} returned no items (check permissions or try again)`,
|
|
223
|
+
items: [],
|
|
224
|
+
match,
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Separate real items from error sentinels the provider may have returned
|
|
229
|
+
const errorItems = rawItems.filter(i => i.type === 'error');
|
|
230
|
+
const dataItems = rawItems.filter(i => i.type !== 'error');
|
|
231
|
+
|
|
232
|
+
if (!dataItems.length && errorItems.length) {
|
|
233
|
+
const errMsg = errorItems.map(i => i.message).join('; ');
|
|
234
|
+
return {
|
|
235
|
+
ok: false,
|
|
236
|
+
reason: 'provider_error',
|
|
237
|
+
message: `${match.display} returned errors: ${errMsg}`,
|
|
238
|
+
items: [],
|
|
239
|
+
errors: errorItems,
|
|
240
|
+
match,
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Write each item as an observation so future queries find them in the knowledge base
|
|
245
|
+
let written = 0;
|
|
246
|
+
for (const item of dataItems) {
|
|
247
|
+
try {
|
|
248
|
+
addObservation(root, {
|
|
249
|
+
role: 'construct',
|
|
250
|
+
category: 'insight',
|
|
251
|
+
summary: item.title ?? item.summary ?? item.id,
|
|
252
|
+
content: buildObservationContent(item, match),
|
|
253
|
+
tags: ['demand-fetch', match.provider, match.ref ?? match.provider],
|
|
254
|
+
confidence: 0.9,
|
|
255
|
+
source: `demand-fetch:${match.provider}`,
|
|
256
|
+
});
|
|
257
|
+
written++;
|
|
258
|
+
} catch (err) { process.stderr.write('[demand-fetch.mjs] store-observation: ' + (err?.message ?? String(err)) + '\n'); }
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
return {
|
|
262
|
+
ok: true,
|
|
263
|
+
reason: 'fetched',
|
|
264
|
+
message: `Fetched ${dataItems.length} item(s) from ${match.display} and stored ${written} observation(s)${errorItems.length ? ` (${errorItems.length} error(s): ${errorItems.map(i => i.message).join('; ')})` : ''}`,
|
|
265
|
+
items: dataItems,
|
|
266
|
+
match,
|
|
267
|
+
written,
|
|
268
|
+
errors: errorItems.length ? errorItems : undefined,
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// ─── Universal fetch (all providers) ─────────────────────────────────────────
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Fetch from ALL configured providers. Used when no specific source matches a
|
|
276
|
+
* query — gives Construct a broad current-state refresh across every integration.
|
|
277
|
+
*
|
|
278
|
+
* @param {object} opts
|
|
279
|
+
* @param {string} [opts.query] - Original query (for logging/tagging only)
|
|
280
|
+
* @param {string} [opts.rootDir] - Data root dir
|
|
281
|
+
* @param {object} [opts.env] - Resolved env
|
|
282
|
+
* @returns {Promise<DemandFetchResult>}
|
|
283
|
+
*/
|
|
284
|
+
async function demandFetchAll({ query, rootDir, env } = {}) {
|
|
285
|
+
const { ProviderRegistry } = await import('./providers/registry.mjs');
|
|
286
|
+
const registry = await ProviderRegistry.fromEnv(env);
|
|
287
|
+
const providerNames = registry.names();
|
|
288
|
+
|
|
289
|
+
if (!providerNames.length) {
|
|
290
|
+
return {
|
|
291
|
+
ok: false,
|
|
292
|
+
reason: 'no_providers',
|
|
293
|
+
message: 'No provider credentials configured in config.env',
|
|
294
|
+
items: [],
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const allItems = [];
|
|
299
|
+
const errors = [];
|
|
300
|
+
const sources = resolveKnownSources(env);
|
|
301
|
+
|
|
302
|
+
// Group known sources by provider so we can batch-fetch per-provider
|
|
303
|
+
const byProvider = new Map();
|
|
304
|
+
for (const src of sources) {
|
|
305
|
+
if (!byProvider.has(src.provider)) byProvider.set(src.provider, []);
|
|
306
|
+
byProvider.get(src.provider).push(src);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// Also include providers that have no explicit sources (Jira, Linear default fetch)
|
|
310
|
+
for (const name of providerNames) {
|
|
311
|
+
if (!byProvider.has(name)) byProvider.set(name, []);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
for (const [providerName, provSources] of byProvider) {
|
|
315
|
+
const provider = registry.get(providerName);
|
|
316
|
+
if (!provider) continue;
|
|
317
|
+
|
|
318
|
+
// Build read calls — one set per known source, or a default if none listed
|
|
319
|
+
const matchList = provSources.length > 0
|
|
320
|
+
? provSources
|
|
321
|
+
: [{ provider: providerName, ref: null, display: providerName }];
|
|
322
|
+
|
|
323
|
+
for (const src of matchList) {
|
|
324
|
+
const readCalls = buildReadCalls(src, env);
|
|
325
|
+
if (!readCalls.length) continue;
|
|
326
|
+
for (const { ref, opts, display } of readCalls) {
|
|
327
|
+
try {
|
|
328
|
+
const items = await provider.read(ref, opts);
|
|
329
|
+
if (items?.length) {
|
|
330
|
+
allItems.push(...items);
|
|
331
|
+
for (const item of items) {
|
|
332
|
+
try {
|
|
333
|
+
addObservation(rootDir, {
|
|
334
|
+
role: 'construct',
|
|
335
|
+
category: 'insight',
|
|
336
|
+
summary: item.title ?? item.summary ?? item.id,
|
|
337
|
+
content: buildObservationContent(item, { provider: providerName, ref: src.ref, display: display ?? src.display ?? providerName }),
|
|
338
|
+
tags: ['demand-fetch', 'universal', providerName],
|
|
339
|
+
confidence: 0.85,
|
|
340
|
+
source: `demand-fetch:all:${providerName}`,
|
|
341
|
+
});
|
|
342
|
+
} catch (err) { process.stderr.write('[demand-fetch.mjs] universal-store-obs: ' + (err?.message ?? String(err)) + '\n'); }
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
} catch (err) {
|
|
346
|
+
errors.push({ provider: providerName, ref: src.ref ?? null, error: err.message });
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
const knownSourceList = sources.map(s => s.display).filter((v, i, a) => a.indexOf(v) === i).join(', ') || 'none';
|
|
353
|
+
|
|
354
|
+
if (!allItems.length && errors.length) {
|
|
355
|
+
return {
|
|
356
|
+
ok: false,
|
|
357
|
+
reason: 'fetch_errors',
|
|
358
|
+
message: `Universal fetch failed for all providers. Errors: ${errors.map(e => `${e.provider}: ${e.error}`).join('; ')}`,
|
|
359
|
+
items: [],
|
|
360
|
+
errors,
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
return {
|
|
365
|
+
ok: true,
|
|
366
|
+
reason: allItems.length ? 'fetched_all' : 'empty_all',
|
|
367
|
+
message: allItems.length
|
|
368
|
+
? `Universal fetch: ${allItems.length} item(s) from ${providerNames.join(', ')}${errors.length ? ` (${errors.length} provider error(s))` : ''}`
|
|
369
|
+
: `Universal fetch returned no items from any provider (${knownSourceList})`,
|
|
370
|
+
items: allItems,
|
|
371
|
+
written: allItems.length,
|
|
372
|
+
providers: providerNames,
|
|
373
|
+
errors: errors.length ? errors : undefined,
|
|
374
|
+
query,
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* Build a list of { ref, opts } pairs to pass to provider.read(ref, opts).
|
|
382
|
+
*
|
|
383
|
+
* @param {{ provider: string, ref: string|null, display: string }} match
|
|
384
|
+
* @param {object} env
|
|
385
|
+
* @returns {{ ref: string, opts: object, display: string }[]}
|
|
386
|
+
*/
|
|
387
|
+
function buildReadCalls(match, env) {
|
|
388
|
+
if (match.provider === 'github') {
|
|
389
|
+
const opts = { repos: [match.ref], limit: 25 };
|
|
390
|
+
return [
|
|
391
|
+
{ ref: 'meta', opts, display: match.display },
|
|
392
|
+
{ ref: 'readme', opts, display: match.display },
|
|
393
|
+
{ ref: 'docs', opts, display: match.display },
|
|
394
|
+
{ ref: 'prs', opts, display: match.display },
|
|
395
|
+
{ ref: 'issues', opts, display: match.display },
|
|
396
|
+
{ ref: 'commits', opts, display: match.display },
|
|
397
|
+
];
|
|
398
|
+
}
|
|
399
|
+
if (match.provider === 'jira') {
|
|
400
|
+
const projects = (env.JIRA_PROJECTS ?? '').split(',').map(p => p.trim()).filter(Boolean);
|
|
401
|
+
const recencyDays = parseInt(env.JIRA_FETCH_RECENCY_DAYS, 10) || 30;
|
|
402
|
+
|
|
403
|
+
let jql;
|
|
404
|
+
if (match.ref) {
|
|
405
|
+
// Targeted project fetch — no recency cutoff, user asked for this project
|
|
406
|
+
jql = `project = "${match.ref}" AND statusCategory != Done ORDER BY updated DESC`;
|
|
407
|
+
} else if (projects.length > 0) {
|
|
408
|
+
// Broad/fallback fetch — scoped to configured projects with recency guard
|
|
409
|
+
const projectList = projects.map(p => `"${p}"`).join(', ');
|
|
410
|
+
jql = `project IN (${projectList}) AND updated >= -${recencyDays}d ORDER BY updated DESC`;
|
|
411
|
+
} else {
|
|
412
|
+
// No JIRA_PROJECTS configured — skip Jira entirely rather than defaulting to user-scoped
|
|
413
|
+
return [];
|
|
414
|
+
}
|
|
415
|
+
return [{ ref: 'issues', opts: { jql, limit: 50 }, display: match.display }];
|
|
416
|
+
}
|
|
417
|
+
if (match.provider === 'linear') {
|
|
418
|
+
return [{ ref: 'issues', opts: { team: match.ref ?? null, limit: 50 }, display: match.display }];
|
|
419
|
+
}
|
|
420
|
+
return [];
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
function buildObservationContent(item, match) {
|
|
424
|
+
const lines = [`Source: ${match.display}`, `Provider: ${match.provider}`];
|
|
425
|
+
if (item.url) lines.push(`URL: ${item.url}`);
|
|
426
|
+
|
|
427
|
+
if (item.type === 'meta') {
|
|
428
|
+
if (item.description) lines.push(`Description: ${item.description}`);
|
|
429
|
+
if (item.language) lines.push(`Language: ${item.language}`);
|
|
430
|
+
if (item.visibility) lines.push(`Visibility: ${item.visibility}`);
|
|
431
|
+
if (item.topics?.length) lines.push(`Topics: ${item.topics.join(', ')}`);
|
|
432
|
+
if (item.defaultBranch) lines.push(`Default branch: ${item.defaultBranch}`);
|
|
433
|
+
if (item.pushedAt) lines.push(`Last push: ${item.pushedAt}`);
|
|
434
|
+
return lines.join('\n');
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
if (item.type === 'doc') {
|
|
438
|
+
if (item.path) lines.push(`Path: ${item.path}`);
|
|
439
|
+
if (item.content) lines.push(`\n${item.content}`);
|
|
440
|
+
return lines.join('\n');
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
if (item.state ?? item.status) lines.push(`Status: ${item.state ?? item.status}`);
|
|
444
|
+
if (item.body ?? item.description) {
|
|
445
|
+
const body = String(item.body ?? item.description ?? '').slice(0, 500);
|
|
446
|
+
if (body) lines.push(`\n${body}`);
|
|
447
|
+
}
|
|
448
|
+
return lines.join('\n');
|
|
449
|
+
}
|