@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,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/providers/github/index.mjs — GitHub data-source provider.
|
|
3
|
+
*
|
|
4
|
+
* Capabilities: read, search, webhook.
|
|
5
|
+
*
|
|
6
|
+
* Auth: reads `GITHUB_TOKEN` (or `GH_TOKEN`) from env. Without a token,
|
|
7
|
+
* unauthenticated GitHub API rate limits apply (60 req/h/IP).
|
|
8
|
+
*
|
|
9
|
+
* Config (per call):
|
|
10
|
+
* - repo: "owner/name" — required for read
|
|
11
|
+
* - query: free-text search (issues, PRs, code) for search
|
|
12
|
+
* - kind: 'issues' | 'prs' | 'code' (default 'issues')
|
|
13
|
+
*
|
|
14
|
+
* The provider is intentionally narrow in v1 — issue + PR + code search +
|
|
15
|
+
* a single repo metadata read. Webhook signature verification is in place
|
|
16
|
+
* so consumers can mount a receiver without re-implementing it.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import crypto from 'node:crypto';
|
|
20
|
+
|
|
21
|
+
const API = 'https://api.github.com';
|
|
22
|
+
|
|
23
|
+
function authHeader(env) {
|
|
24
|
+
const token = env.GITHUB_TOKEN || env.GH_TOKEN;
|
|
25
|
+
return token ? { Authorization: `Bearer ${token}` } : {};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async function ghFetch(pathOrUrl, env, init = {}) {
|
|
29
|
+
const url = pathOrUrl.startsWith('http') ? pathOrUrl : `${API}${pathOrUrl}`;
|
|
30
|
+
const headers = {
|
|
31
|
+
'Accept': 'application/vnd.github+json',
|
|
32
|
+
'X-GitHub-Api-Version': '2022-11-28',
|
|
33
|
+
'User-Agent': 'construct-github-provider',
|
|
34
|
+
...authHeader(env),
|
|
35
|
+
...(init.headers || {}),
|
|
36
|
+
};
|
|
37
|
+
const res = await fetch(url, { ...init, headers });
|
|
38
|
+
if (!res.ok) {
|
|
39
|
+
const body = await res.text().catch(() => '');
|
|
40
|
+
const err = new Error(`GitHub ${res.status} ${res.statusText}: ${body.slice(0, 200)}`);
|
|
41
|
+
err.status = res.status;
|
|
42
|
+
err.rateLimitRemaining = Number(res.headers.get('x-ratelimit-remaining'));
|
|
43
|
+
throw err;
|
|
44
|
+
}
|
|
45
|
+
return res.json();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function create({ env = process.env } = {}) {
|
|
49
|
+
return {
|
|
50
|
+
meta: {
|
|
51
|
+
id: 'github',
|
|
52
|
+
displayName: 'GitHub',
|
|
53
|
+
capabilities: ['read', 'search', 'webhook'],
|
|
54
|
+
description: 'Repos, issues, PRs, and code search.',
|
|
55
|
+
},
|
|
56
|
+
|
|
57
|
+
configSchema: {
|
|
58
|
+
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
59
|
+
type: 'object',
|
|
60
|
+
properties: {
|
|
61
|
+
repo: { type: 'string', pattern: '^[^/]+/[^/]+$', description: 'owner/name' },
|
|
62
|
+
kind: { enum: ['issues', 'prs', 'code'], default: 'issues' },
|
|
63
|
+
query: { type: 'string' },
|
|
64
|
+
webhookSecret: { type: 'string', description: 'webhook signature secret' },
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
|
|
68
|
+
async health() {
|
|
69
|
+
try {
|
|
70
|
+
const r = await fetch(`${API}/rate_limit`, { headers: authHeader(env) });
|
|
71
|
+
if (!r.ok) return { ok: false, detail: `rate_limit endpoint ${r.status}` };
|
|
72
|
+
const data = await r.json();
|
|
73
|
+
const remaining = data?.resources?.core?.remaining;
|
|
74
|
+
return {
|
|
75
|
+
ok: true,
|
|
76
|
+
detail: `${data?.resources?.core?.limit || 0} req/h limit; ${remaining ?? 0} remaining`,
|
|
77
|
+
authenticated: !!(env.GITHUB_TOKEN || env.GH_TOKEN),
|
|
78
|
+
};
|
|
79
|
+
} catch (err) {
|
|
80
|
+
return { ok: false, detail: err.message };
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
|
|
84
|
+
async read(config) {
|
|
85
|
+
if (!config?.repo) throw new Error('github.read: config.repo required (owner/name)');
|
|
86
|
+
return ghFetch(`/repos/${config.repo}`, env);
|
|
87
|
+
},
|
|
88
|
+
|
|
89
|
+
async search(config) {
|
|
90
|
+
const kind = config?.kind || 'issues';
|
|
91
|
+
const query = config?.query;
|
|
92
|
+
if (!query) throw new Error('github.search: config.query required');
|
|
93
|
+
const path = kind === 'code'
|
|
94
|
+
? `/search/code?q=${encodeURIComponent(query)}`
|
|
95
|
+
: kind === 'prs'
|
|
96
|
+
? `/search/issues?q=${encodeURIComponent(query + ' is:pr')}`
|
|
97
|
+
: `/search/issues?q=${encodeURIComponent(query + ' is:issue')}`;
|
|
98
|
+
const data = await ghFetch(path, env);
|
|
99
|
+
return Array.isArray(data?.items) ? data.items : [];
|
|
100
|
+
},
|
|
101
|
+
|
|
102
|
+
async webhook(config, request) {
|
|
103
|
+
const secret = config?.webhookSecret;
|
|
104
|
+
if (!secret) {
|
|
105
|
+
return { ok: false, error: 'webhookSecret not configured' };
|
|
106
|
+
}
|
|
107
|
+
const signature = request?.headers?.['x-hub-signature-256'];
|
|
108
|
+
if (!signature || !signature.startsWith('sha256=')) {
|
|
109
|
+
return { ok: false, error: 'missing or malformed signature header' };
|
|
110
|
+
}
|
|
111
|
+
const expected = 'sha256=' + crypto
|
|
112
|
+
.createHmac('sha256', secret)
|
|
113
|
+
.update(typeof request.body === 'string' ? request.body : Buffer.from(request.body))
|
|
114
|
+
.digest('hex');
|
|
115
|
+
const valid = crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
|
|
116
|
+
if (!valid) return { ok: false, error: 'signature mismatch' };
|
|
117
|
+
return {
|
|
118
|
+
ok: true,
|
|
119
|
+
event: request?.headers?.['x-github-event'] || 'unknown',
|
|
120
|
+
delivery: request?.headers?.['x-github-delivery'] || null,
|
|
121
|
+
};
|
|
122
|
+
},
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export default create;
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/providers/registry.mjs — provider resolution + lifecycle.
|
|
3
|
+
*
|
|
4
|
+
* Resolution order (last-write wins):
|
|
5
|
+
*
|
|
6
|
+
* 1. Built-in factories under `lib/providers/<id>/index.mjs` (loaded lazily).
|
|
7
|
+
* 2. Operator overrides at `~/.construct/providers.json`:
|
|
8
|
+
* { "providers": [{ "id": "...", "package": "...", "options": {} }] }
|
|
9
|
+
* 3. Project overrides at `<projectRoot>/.cx/providers.json` (same shape).
|
|
10
|
+
*
|
|
11
|
+
* Built-in providers ship with the package. Plugin providers are arbitrary
|
|
12
|
+
* npm modules (or local paths) that export the contract. The registry runs
|
|
13
|
+
* `assertProviderContract` against every loaded provider before returning
|
|
14
|
+
* it, so a misconfigured plugin produces a clear error rather than a runtime
|
|
15
|
+
* surprise inside core code.
|
|
16
|
+
*
|
|
17
|
+
* Provider config (auth tokens, default queries, allowlists) is layered:
|
|
18
|
+
* `~/.construct/config.env` for credentials, `<projectRoot>/.cx/providers.yaml`
|
|
19
|
+
* for non-secret per-project settings. The YAML file references env vars
|
|
20
|
+
* by name so secrets never land in source control.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import { readFileSync, existsSync } from 'node:fs';
|
|
24
|
+
import { homedir } from 'node:os';
|
|
25
|
+
import { join, isAbsolute, resolve, dirname } from 'node:path';
|
|
26
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
27
|
+
import { assertProviderContract } from './contract.mjs';
|
|
28
|
+
import { getBreaker } from './circuit-breaker.mjs';
|
|
29
|
+
|
|
30
|
+
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
31
|
+
|
|
32
|
+
export const BUILT_INS = ['github', 'atlassian-jira', 'atlassian-confluence', 'slack', 'salesforce'];
|
|
33
|
+
|
|
34
|
+
function readJsonIfExists(path) {
|
|
35
|
+
try {
|
|
36
|
+
if (!existsSync(path)) return null;
|
|
37
|
+
return JSON.parse(readFileSync(path, 'utf8'));
|
|
38
|
+
} catch { return null; }
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async function loadFactoryFromPackage(pkg, rootDir) {
|
|
42
|
+
if (!pkg || typeof pkg !== 'string') {
|
|
43
|
+
throw new Error('provider plugin package must be a non-empty string');
|
|
44
|
+
}
|
|
45
|
+
if (pkg.startsWith('github:')) {
|
|
46
|
+
throw new Error(
|
|
47
|
+
`github: plugin spec '${pkg}' is not auto-installed. ` +
|
|
48
|
+
`Install via 'npm install ${pkg}' first, then reference by package name.`
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
if (isAbsolute(pkg) || pkg.startsWith('./') || pkg.startsWith('../')) {
|
|
52
|
+
const abs = isAbsolute(pkg) ? pkg : resolve(rootDir, pkg);
|
|
53
|
+
return import(pathToFileURL(abs).href);
|
|
54
|
+
}
|
|
55
|
+
return import(pkg);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async function loadBuiltIn(id) {
|
|
59
|
+
const candidate = join(HERE, id, 'index.mjs');
|
|
60
|
+
if (!existsSync(candidate)) return null;
|
|
61
|
+
return import(pathToFileURL(candidate).href);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Wrap every async I/O method on a provider with its per-id circuit breaker
|
|
65
|
+
// so a downed remote system fails fast for the cooldown window instead of
|
|
66
|
+
// blocking every caller on the same broken backend. `health` is intentionally
|
|
67
|
+
// not wrapped — it's the probe consumers use to surface the breaker's state.
|
|
68
|
+
const BREAKER_METHODS = ['read', 'search', 'watch', 'write', 'webhook'];
|
|
69
|
+
|
|
70
|
+
function wrapWithBreaker(provider) {
|
|
71
|
+
const breaker = getBreaker(`provider:${provider.meta.id}`, {
|
|
72
|
+
failureThreshold: 5,
|
|
73
|
+
cooldownMs: 30_000,
|
|
74
|
+
});
|
|
75
|
+
for (const method of BREAKER_METHODS) {
|
|
76
|
+
if (typeof provider[method] !== 'function') continue;
|
|
77
|
+
const original = provider[method].bind(provider);
|
|
78
|
+
provider[method] = (...args) => breaker.run(original, ...args);
|
|
79
|
+
}
|
|
80
|
+
return provider;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async function instantiate(mod, options) {
|
|
84
|
+
const factory = mod?.create || mod?.default;
|
|
85
|
+
if (typeof factory !== 'function') {
|
|
86
|
+
throw new Error(`provider module must export a 'create' or default factory`);
|
|
87
|
+
}
|
|
88
|
+
const instance = await factory(options || {});
|
|
89
|
+
assertProviderContract(instance);
|
|
90
|
+
return wrapWithBreaker(instance);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function gatherOverrides(rootDir) {
|
|
94
|
+
const out = [];
|
|
95
|
+
const userConfig = readJsonIfExists(join(homedir(), '.construct', 'providers.json'));
|
|
96
|
+
if (userConfig?.providers) out.push(...userConfig.providers);
|
|
97
|
+
const projectConfig = readJsonIfExists(join(rootDir, '.cx', 'providers.json'));
|
|
98
|
+
if (projectConfig?.providers) out.push(...projectConfig.providers);
|
|
99
|
+
return out;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Resolve every provider Construct should know about. Returns:
|
|
104
|
+
*
|
|
105
|
+
* { providers: { [id]: instance }, errors: [...], sources: { [id]: 'built-in'|'plugin:<pkg>' } }
|
|
106
|
+
*/
|
|
107
|
+
export async function resolveProviders({ rootDir = process.cwd(), env = process.env } = {}) {
|
|
108
|
+
const providers = {};
|
|
109
|
+
const sources = {};
|
|
110
|
+
const errors = [];
|
|
111
|
+
|
|
112
|
+
for (const id of BUILT_INS) {
|
|
113
|
+
try {
|
|
114
|
+
const mod = await loadBuiltIn(id);
|
|
115
|
+
if (!mod) continue;
|
|
116
|
+
const instance = await instantiate(mod, { env });
|
|
117
|
+
providers[id] = instance;
|
|
118
|
+
sources[id] = 'built-in';
|
|
119
|
+
} catch (err) {
|
|
120
|
+
errors.push({ id, source: 'built-in', error: err.message });
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
for (const entry of gatherOverrides(rootDir)) {
|
|
125
|
+
const id = entry?.id;
|
|
126
|
+
const pkg = entry?.package;
|
|
127
|
+
if (!id || !pkg) {
|
|
128
|
+
errors.push({ id: id || '(unknown)', source: 'override', error: 'override entry must have id and package' });
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
try {
|
|
132
|
+
const mod = await loadFactoryFromPackage(pkg, rootDir);
|
|
133
|
+
const instance = await instantiate(mod, { env, ...(entry.options || {}) });
|
|
134
|
+
if (instance.meta.id !== id) {
|
|
135
|
+
errors.push({ id, source: pkg, error: `meta.id is '${instance.meta.id}' but override declared id='${id}'` });
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
providers[id] = instance;
|
|
139
|
+
sources[id] = `plugin:${pkg}`;
|
|
140
|
+
} catch (err) {
|
|
141
|
+
errors.push({ id, source: pkg, error: err.message });
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return { providers, sources, errors };
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export async function describeProviders({ rootDir = process.cwd(), env = process.env } = {}) {
|
|
149
|
+
const { providers, sources, errors } = await resolveProviders({ rootDir, env });
|
|
150
|
+
const summary = await Promise.all(
|
|
151
|
+
Object.entries(providers).map(async ([id, p]) => {
|
|
152
|
+
let health = { ok: false, detail: 'health probe not invoked' };
|
|
153
|
+
try { health = await p.health({}); } catch (err) { health = { ok: false, detail: err.message }; }
|
|
154
|
+
return {
|
|
155
|
+
id,
|
|
156
|
+
displayName: p.meta.displayName,
|
|
157
|
+
description: p.meta.description || null,
|
|
158
|
+
capabilities: [...p.meta.capabilities],
|
|
159
|
+
source: sources[id],
|
|
160
|
+
health,
|
|
161
|
+
};
|
|
162
|
+
})
|
|
163
|
+
);
|
|
164
|
+
return { summary, errors };
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Standalone validation for a candidate override entry: imports the package
|
|
168
|
+
// and runs the contract check without touching the on-disk overrides. Lets
|
|
169
|
+
// callers (e.g. dashboard write endpoint) verify before persisting.
|
|
170
|
+
|
|
171
|
+
export async function validateProviderEntry({ id, package: pkg, options = {} }, { rootDir = process.cwd(), env = process.env } = {}) {
|
|
172
|
+
if (!id || typeof id !== 'string') return { ok: false, error: 'id required' };
|
|
173
|
+
if (!pkg || typeof pkg !== 'string') return { ok: false, error: 'package required' };
|
|
174
|
+
try {
|
|
175
|
+
const mod = await loadFactoryFromPackage(pkg, rootDir);
|
|
176
|
+
const instance = await instantiate(mod, { env, ...options });
|
|
177
|
+
if (instance.meta.id !== id) {
|
|
178
|
+
return { ok: false, error: `meta.id is '${instance.meta.id}' but entry declared id='${id}'` };
|
|
179
|
+
}
|
|
180
|
+
return { ok: true };
|
|
181
|
+
} catch (err) {
|
|
182
|
+
return { ok: false, error: err.message };
|
|
183
|
+
}
|
|
184
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/providers/salesforce/index.mjs — Salesforce data-source provider.
|
|
3
|
+
*
|
|
4
|
+
* Capabilities: search, read.
|
|
5
|
+
*
|
|
6
|
+
* Auth: OAuth password flow via env:
|
|
7
|
+
* SALESFORCE_INSTANCE_URL e.g. https://yourorg.my.salesforce.com
|
|
8
|
+
* SALESFORCE_ACCESS_TOKEN bearer token (operator obtains via sfdx or OAuth flow)
|
|
9
|
+
*
|
|
10
|
+
* Config (per call):
|
|
11
|
+
* - soql: SOQL query string
|
|
12
|
+
* - sobject: object type (Account|Opportunity|Contact|Custom__c) for read
|
|
13
|
+
* - id: record id for read
|
|
14
|
+
*
|
|
15
|
+
* The provider intentionally avoids embedding an OAuth flow itself —
|
|
16
|
+
* operators are expected to supply an access token. A future enhancement
|
|
17
|
+
* can land an interactive OAuth flow with refresh-token persistence.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
const API_VERSION = 'v60.0';
|
|
21
|
+
|
|
22
|
+
function authConfig(env) {
|
|
23
|
+
const instanceUrl = (env.SALESFORCE_INSTANCE_URL || '').replace(/\/$/, '');
|
|
24
|
+
const accessToken = env.SALESFORCE_ACCESS_TOKEN || '';
|
|
25
|
+
return { instanceUrl, accessToken, ok: Boolean(instanceUrl && accessToken) };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async function sfFetch(path, env, init = {}) {
|
|
29
|
+
const auth = authConfig(env);
|
|
30
|
+
if (!auth.ok) throw new Error('salesforce: SALESFORCE_INSTANCE_URL and SALESFORCE_ACCESS_TOKEN required');
|
|
31
|
+
const res = await fetch(`${auth.instanceUrl}${path}`, {
|
|
32
|
+
...init,
|
|
33
|
+
headers: {
|
|
34
|
+
'Accept': 'application/json',
|
|
35
|
+
'Content-Type': 'application/json',
|
|
36
|
+
Authorization: `Bearer ${auth.accessToken}`,
|
|
37
|
+
...(init.headers || {}),
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
if (!res.ok) {
|
|
41
|
+
const body = await res.text().catch(() => '');
|
|
42
|
+
throw new Error(`Salesforce ${res.status} ${res.statusText}: ${body.slice(0, 200)}`);
|
|
43
|
+
}
|
|
44
|
+
return res.json();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function create({ env = process.env } = {}) {
|
|
48
|
+
return {
|
|
49
|
+
meta: {
|
|
50
|
+
id: 'salesforce',
|
|
51
|
+
displayName: 'Salesforce',
|
|
52
|
+
capabilities: ['read', 'search'],
|
|
53
|
+
description: 'Accounts, opportunities, custom objects, SOQL.',
|
|
54
|
+
},
|
|
55
|
+
|
|
56
|
+
configSchema: {
|
|
57
|
+
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
58
|
+
type: 'object',
|
|
59
|
+
properties: {
|
|
60
|
+
soql: { type: 'string' },
|
|
61
|
+
sobject: { type: 'string' },
|
|
62
|
+
id: { type: 'string', pattern: '^[a-zA-Z0-9]{15,18}$' },
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
|
|
66
|
+
async health() {
|
|
67
|
+
const auth = authConfig(env);
|
|
68
|
+
if (!auth.ok) {
|
|
69
|
+
return { ok: false, detail: 'SALESFORCE_INSTANCE_URL / SALESFORCE_ACCESS_TOKEN not set' };
|
|
70
|
+
}
|
|
71
|
+
try {
|
|
72
|
+
await sfFetch(`/services/data/${API_VERSION}/`, env);
|
|
73
|
+
return { ok: true, detail: `connected to ${auth.instanceUrl}` };
|
|
74
|
+
} catch (err) {
|
|
75
|
+
return { ok: false, detail: err.message };
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
|
|
79
|
+
async read(config) {
|
|
80
|
+
if (!config?.sobject || !config?.id) {
|
|
81
|
+
throw new Error('salesforce.read: config.sobject and config.id required');
|
|
82
|
+
}
|
|
83
|
+
return sfFetch(
|
|
84
|
+
`/services/data/${API_VERSION}/sobjects/${encodeURIComponent(config.sobject)}/${encodeURIComponent(config.id)}`,
|
|
85
|
+
env
|
|
86
|
+
);
|
|
87
|
+
},
|
|
88
|
+
|
|
89
|
+
async search(config) {
|
|
90
|
+
if (!config?.soql) throw new Error('salesforce.search: config.soql required');
|
|
91
|
+
const data = await sfFetch(
|
|
92
|
+
`/services/data/${API_VERSION}/query?q=${encodeURIComponent(config.soql)}`,
|
|
93
|
+
env
|
|
94
|
+
);
|
|
95
|
+
return Array.isArray(data?.records) ? data.records : [];
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export default create;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/providers/slack/index.mjs — Slack data-source provider.
|
|
3
|
+
*
|
|
4
|
+
* Capabilities: read, search.
|
|
5
|
+
*
|
|
6
|
+
* Auth: `SLACK_BOT_TOKEN` (xoxb-...) or `SLACK_USER_TOKEN` (xoxp-...) from env.
|
|
7
|
+
*
|
|
8
|
+
* Config (per call):
|
|
9
|
+
* - channel: channel id or name (#engineering or C0123)
|
|
10
|
+
* - query: free-text search.messages query
|
|
11
|
+
* - count: integer (default 20, max 100)
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const API = 'https://slack.com/api';
|
|
15
|
+
const DEFAULT_COUNT = 20;
|
|
16
|
+
const HARD_COUNT = 100;
|
|
17
|
+
|
|
18
|
+
function token(env) {
|
|
19
|
+
return env.SLACK_BOT_TOKEN || env.SLACK_USER_TOKEN || '';
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async function slackFetch(method, env, params = {}) {
|
|
23
|
+
const tok = token(env);
|
|
24
|
+
if (!tok) throw new Error('slack: SLACK_BOT_TOKEN or SLACK_USER_TOKEN required');
|
|
25
|
+
const url = new URL(`${API}/${method}`);
|
|
26
|
+
for (const [k, v] of Object.entries(params)) url.searchParams.set(k, String(v));
|
|
27
|
+
const res = await fetch(url, { headers: { Authorization: `Bearer ${tok}` } });
|
|
28
|
+
if (!res.ok) throw new Error(`slack ${res.status} ${res.statusText}`);
|
|
29
|
+
const data = await res.json();
|
|
30
|
+
if (!data.ok) throw new Error(`slack api error: ${data.error || 'unknown'}`);
|
|
31
|
+
return data;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function create({ env = process.env } = {}) {
|
|
35
|
+
return {
|
|
36
|
+
meta: {
|
|
37
|
+
id: 'slack',
|
|
38
|
+
displayName: 'Slack',
|
|
39
|
+
capabilities: ['read', 'search'],
|
|
40
|
+
description: 'Channel messages, threads, search.',
|
|
41
|
+
},
|
|
42
|
+
|
|
43
|
+
configSchema: {
|
|
44
|
+
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
45
|
+
type: 'object',
|
|
46
|
+
properties: {
|
|
47
|
+
channel: { type: 'string' },
|
|
48
|
+
query: { type: 'string' },
|
|
49
|
+
count: { type: 'integer', minimum: 1, maximum: HARD_COUNT, default: DEFAULT_COUNT },
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
|
|
53
|
+
async health() {
|
|
54
|
+
const tok = token(env);
|
|
55
|
+
if (!tok) return { ok: false, detail: 'SLACK_BOT_TOKEN or SLACK_USER_TOKEN not set' };
|
|
56
|
+
try {
|
|
57
|
+
const data = await slackFetch('auth.test', env);
|
|
58
|
+
return { ok: true, detail: `team=${data.team || '?'} user=${data.user || '?'}` };
|
|
59
|
+
} catch (err) {
|
|
60
|
+
return { ok: false, detail: err.message };
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
|
|
64
|
+
async read(config) {
|
|
65
|
+
if (!config?.channel) throw new Error('slack.read: config.channel required');
|
|
66
|
+
const limit = Math.min(config?.count || DEFAULT_COUNT, HARD_COUNT);
|
|
67
|
+
const data = await slackFetch('conversations.history', env, { channel: config.channel, limit });
|
|
68
|
+
return data.messages || [];
|
|
69
|
+
},
|
|
70
|
+
|
|
71
|
+
async search(config) {
|
|
72
|
+
if (!config?.query) throw new Error('slack.search: config.query required');
|
|
73
|
+
const count = Math.min(config?.count || DEFAULT_COUNT, HARD_COUNT);
|
|
74
|
+
const data = await slackFetch('search.messages', env, { query: config.query, count });
|
|
75
|
+
return data.messages?.matches || [];
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export default create;
|
package/lib/reflect.mjs
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/reflect.mjs — construct reflect command implementation.
|
|
3
|
+
*
|
|
4
|
+
* Captures improvement feedback from chat sessions and updates Construct core
|
|
5
|
+
* knowledge base with actionable insights for continuous self-improvement.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
9
|
+
import { join, resolve } from 'node:path';
|
|
10
|
+
import { addObservation } from './observation-store.mjs';
|
|
11
|
+
import { loadConstructEnv } from './env-config.mjs';
|
|
12
|
+
import { KNOWLEDGE_ROOT, KNOWLEDGE_SUBDIRS, inferKnowledgeTarget } from './knowledge/layout.mjs';
|
|
13
|
+
|
|
14
|
+
const HOME = process.env.HOME || process.env.USERPROFILE;
|
|
15
|
+
const USER_ENV_PATH = join(HOME, '.construct', 'config.env');
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Main reflect command handler.
|
|
19
|
+
* @param {string[]} args - Command line arguments
|
|
20
|
+
*/
|
|
21
|
+
export async function runReflectCli(args) {
|
|
22
|
+
// Parse arguments
|
|
23
|
+
let target = 'internal';
|
|
24
|
+
let summary = '';
|
|
25
|
+
|
|
26
|
+
for (const arg of args) {
|
|
27
|
+
if (arg.startsWith('--target=')) {
|
|
28
|
+
target = arg.split('=')[1];
|
|
29
|
+
} else if (arg.startsWith('--summary=')) {
|
|
30
|
+
summary = arg.split('=')[1];
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Validate target
|
|
35
|
+
const validTargets = KNOWLEDGE_SUBDIRS.map(s => `knowledge/${s}`);
|
|
36
|
+
const validShorthandTargets = KNOWLEDGE_SUBDIRS; // internal, external, etc.
|
|
37
|
+
const isValidTarget = validTargets.includes(target) || validShorthandTargets.includes(target);
|
|
38
|
+
if (!isValidTarget) {
|
|
39
|
+
console.error(`Error: Invalid target '${target}'. Valid targets: ${validTargets.join(', ')}`);
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Normalize target to full format if shorthand was provided
|
|
44
|
+
if (validShorthandTargets.includes(target) && !target.startsWith('knowledge/')) {
|
|
45
|
+
target = `knowledge/${target}`;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// If no summary provided, try to get it from stdin or prompt
|
|
49
|
+
if (!summary) {
|
|
50
|
+
// For now, we'll require explicit summary - in future could read from chat context
|
|
51
|
+
console.error('Error: --summary=<text> is required to capture improvement feedback');
|
|
52
|
+
console.error('Example: construct reflect --target=internal --summary="Improve Slack channel intent parsing to handle edge cases"');
|
|
53
|
+
process.exit(1);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Resolve the knowledge directory for this target
|
|
57
|
+
const knowledgeSubdir = target.replace('knowledge/', '');
|
|
58
|
+
const knowledgeDir = join(process.cwd(), KNOWLEDGE_ROOT, knowledgeSubdir);
|
|
59
|
+
|
|
60
|
+
// Ensure the knowledge directory exists
|
|
61
|
+
if (!existsSync(knowledgeDir)) {
|
|
62
|
+
mkdirSync(knowledgeDir, { recursive: true });
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Generate a filename based on timestamp and summary hash
|
|
66
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
67
|
+
const summarySlug = summary
|
|
68
|
+
.toLowerCase()
|
|
69
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
70
|
+
.replace(/^-|-$/g, '')
|
|
71
|
+
.slice(0, 50);
|
|
72
|
+
const filename = `${timestamp}-${summarySlug}.md`;
|
|
73
|
+
const filePath = join(knowledgeDir, filename);
|
|
74
|
+
|
|
75
|
+
// Create the markdown content
|
|
76
|
+
const content = [
|
|
77
|
+
'---',
|
|
78
|
+
`source_path: reflect-command`,
|
|
79
|
+
`source_relative_path: ${filename}`,
|
|
80
|
+
`source_extension: .md`,
|
|
81
|
+
`extracted_at: ${new Date().toISOString()}`,
|
|
82
|
+
`extraction_method: reflect-command`,
|
|
83
|
+
`characters: ${summary.length}`,
|
|
84
|
+
`truncated: false`,
|
|
85
|
+
`output_path: ${JSON.stringify(filePath)}`,
|
|
86
|
+
`output_relative_path: ${JSON.stringify(filename)}`,
|
|
87
|
+
'---',
|
|
88
|
+
'',
|
|
89
|
+
`# Improvement Feedback`,
|
|
90
|
+
'',
|
|
91
|
+
summary,
|
|
92
|
+
'',
|
|
93
|
+
'## Context',
|
|
94
|
+
'',
|
|
95
|
+
`- Captured via: construct reflect`,
|
|
96
|
+
`- Timestamp: ${new Date().toISOString()}`,
|
|
97
|
+
`- Target knowledge directory: ${knowledgeSubdir}/`,
|
|
98
|
+
'',
|
|
99
|
+
'## Action Items',
|
|
100
|
+
'',
|
|
101
|
+
'- [ ] Review this feedback for validity and actionability',
|
|
102
|
+
'- [ ] Determine if this requires code changes, documentation updates, or process improvements',
|
|
103
|
+
'- [ ] If actionable, create appropriate issues in the tracker',
|
|
104
|
+
'- [ ] Update relevant documentation or code based on this insight',
|
|
105
|
+
'',
|
|
106
|
+
].join('\n');
|
|
107
|
+
|
|
108
|
+
// Write the file
|
|
109
|
+
writeFileSync(filePath, content);
|
|
110
|
+
|
|
111
|
+
// Record an observation about this feedback
|
|
112
|
+
const observationSummary = `[reflect] Improvement feedback captured: ${summary.slice(0, 100)}${summary.length > 100 ? '...' : ''}`;
|
|
113
|
+
addObservation(process.cwd(), {
|
|
114
|
+
role: 'construct',
|
|
115
|
+
category: 'insight', // Feedback is typically an insight unless it's about a problem
|
|
116
|
+
summary: observationSummary,
|
|
117
|
+
content: `Feedback captured via construct reflect:\n\n${summary}\n\nStored as: ${filePath}\nTarget: ${knowledgeSubdir}/`,
|
|
118
|
+
tags: ['reflect', 'improvement-feedback', `knowledge:${knowledgeSubdir}`],
|
|
119
|
+
confidence: 0.9,
|
|
120
|
+
source: 'reflect-command',
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
console.log(`✓ Improvement feedback captured and stored as: ${filePath}`);
|
|
124
|
+
console.log(` Target knowledge directory: ${knowledgeSubdir}/`);
|
|
125
|
+
console.log(` Use 'construct ingest --target=${target} <file>' to manually add similar feedback in the future`);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* CLI entry point for direct execution.
|
|
130
|
+
*/
|
|
131
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
132
|
+
const args = process.argv.slice(2);
|
|
133
|
+
runReflectCli(args).catch((err) => {
|
|
134
|
+
console.error(`Error: ${err.message}`);
|
|
135
|
+
process.exit(1);
|
|
136
|
+
});
|
|
137
|
+
}
|