@geraldmaron/construct 1.4.2 → 1.5.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 +36 -0
- package/README.md +41 -7
- package/bin/construct +1027 -64
- package/examples/distribution/sources/deck-one-pager.md +1 -1
- package/lib/acp/server.mjs +21 -7
- package/lib/adapters-sync.mjs +2 -1
- package/lib/audit-trail.mjs +185 -2
- package/lib/beads/auto-close.mjs +2 -1
- package/lib/beads/drift.mjs +2 -1
- package/lib/bridges/copilot-proxy.mjs +20 -5
- package/lib/certification/skill-inventory.mjs +10 -1
- package/lib/cli/approvals.mjs +147 -0
- package/lib/cli-commands.mjs +105 -14
- package/lib/comment-lint.mjs +127 -8
- package/lib/config/schema.mjs +6 -29
- package/lib/config/source-target-registry.mjs +70 -0
- package/lib/config/source-targets.mjs +179 -205
- package/lib/contracts/coverage.mjs +77 -0
- package/lib/contracts/validate.mjs +102 -13
- package/lib/contracts/violation-log.mjs +50 -4
- package/lib/db/migrate.mjs +69 -0
- package/lib/db/migrations/001_orchestration_runs.sql +9 -0
- package/lib/db/migrations/002_queue_provider.sql +50 -0
- package/lib/db/migrations/003_worker_registry.sql +17 -0
- package/lib/db/migrations/004_trace_events.sql +16 -0
- package/lib/db/migrations/005_shared_memory.sql +15 -0
- package/lib/db/migrations/006_orchestration_runs_tenant.sql +5 -0
- package/lib/decisions/enforced-baseline.json +0 -2
- package/lib/decisions/registry.mjs +3 -2
- package/lib/deployment/parity-contract.mjs +1 -1
- package/lib/deployment-mode.mjs +78 -2
- package/lib/diagram-export.mjs +10 -1
- package/lib/distill.mjs +5 -2
- package/lib/docs-verify.mjs +2 -1
- package/lib/doctor/cli.mjs +1 -0
- package/lib/doctor/command-on-path.mjs +24 -0
- package/lib/doctor/diagnosis.mjs +89 -0
- package/lib/doctor/embedding-health.mjs +50 -0
- package/lib/doctor/engine-health.mjs +93 -0
- package/lib/doctor/graph-validate.mjs +47 -0
- package/lib/doctor/index.mjs +18 -4
- package/lib/doctor/sidecar-providers.mjs +56 -0
- package/lib/doctor/watchers/consistency.mjs +93 -1
- package/lib/doctor/watchers/cx-budget.mjs +1 -1
- package/lib/doctor/watchers/graph-staleness.mjs +1 -1
- package/lib/doctor/watchers/mcp-protocol.mjs +17 -0
- package/lib/doctor/watchers/oracle-liveness.mjs +92 -0
- package/lib/doctor/watchers/orchestration-runs.mjs +108 -0
- package/lib/doctor/watchers/provider-breaker.mjs +78 -0
- package/lib/document-export.mjs +52 -27
- package/lib/document-extract/docling-client.mjs +9 -5
- package/lib/document-extract/docling-sidecar.py +30 -0
- package/lib/document-extract.mjs +1 -1
- package/lib/document-ingest.mjs +9 -0
- package/lib/embed/approval-queue.mjs +184 -88
- package/lib/embed/authority-guard.mjs +65 -2
- package/lib/embed/capability-jobs.mjs +365 -0
- package/lib/embed/capability-lifecycle.mjs +219 -0
- package/lib/embed/capability-loader.mjs +303 -0
- package/lib/embed/capability-runtime.mjs +58 -0
- package/lib/embed/cli.mjs +185 -8
- package/lib/embed/config.mjs +4 -0
- package/lib/embed/daemon.mjs +125 -6
- package/lib/embed/demand-fetch.mjs +190 -54
- package/lib/embed/inbox.mjs +12 -10
- package/lib/embed/presets/ops-triage.mjs +236 -0
- package/lib/embed/presets/pm-feedback.mjs +341 -0
- package/lib/embed/presets/tpm.mjs +401 -0
- package/lib/embed/providers/jira.mjs +72 -1
- package/lib/embed/providers/registry.mjs +140 -33
- package/lib/embedded-contract/capability.mjs +4 -0
- package/lib/embedded-contract/execution.mjs +1 -1
- package/lib/embedded-contract/model-resolve.mjs +53 -3
- package/lib/embedded-contract/workflow-defs.mjs +48 -73
- package/lib/embedded-contract/workflow-invoke.mjs +144 -4
- package/lib/embedded-contract/workflows/architecture-review.manifest.json +15 -0
- package/lib/embedded-contract/workflows/data-structure.manifest.json +14 -0
- package/lib/embedded-contract/workflows/evidence-ingest.manifest.json +14 -0
- package/lib/embedded-contract/workflows/memo-draft.manifest.json +14 -0
- package/lib/embedded-contract/workflows/operations-triage.manifest.json +18 -0
- package/lib/embedded-contract/workflows/operations.manifest.json +18 -0
- package/lib/embedded-contract/workflows/pm-feedback.manifest.json +18 -0
- package/lib/embedded-contract/workflows/prd-draft.manifest.json +15 -0
- package/lib/embedded-contract/workflows/proposal-review.manifest.json +15 -0
- package/lib/embedded-contract/workflows/research-synthesis.manifest.json +14 -0
- package/lib/embedded-contract/workflows/risk-review.manifest.json +15 -0
- package/lib/embedded-contract/workflows/structure-notes.manifest.json +14 -0
- package/lib/embedded-contract/workflows/transcript-process.manifest.json +14 -0
- package/lib/embedded-contract/workflows/triage.manifest.json +14 -0
- package/lib/env-config.mjs +50 -9
- package/lib/extensions/index.mjs +27 -0
- package/lib/extensions/loader.mjs +120 -0
- package/lib/extensions/manifest-schema.mjs +141 -0
- package/lib/extensions/manifests/anthropic.manifest.json +12 -0
- package/lib/extensions/manifests/atlassian-confluence.manifest.json +12 -0
- package/lib/extensions/manifests/atlassian-jira.manifest.json +53 -0
- package/lib/extensions/manifests/directory.manifest.json +12 -0
- package/lib/extensions/manifests/docling.manifest.json +37 -0
- package/lib/extensions/manifests/echo.manifest.json +8 -0
- package/lib/extensions/manifests/feedback.manifest.json +12 -0
- package/lib/extensions/manifests/github-copilot.manifest.json +12 -0
- package/lib/extensions/manifests/github.manifest.json +52 -0
- package/lib/extensions/manifests/linear.manifest.json +50 -0
- package/lib/extensions/manifests/local.manifest.json +12 -0
- package/lib/extensions/manifests/ollama.manifest.json +12 -0
- package/lib/extensions/manifests/openai.manifest.json +12 -0
- package/lib/extensions/manifests/openrouter-anthropic.manifest.json +12 -0
- package/lib/extensions/manifests/openrouter-deepseek.manifest.json +12 -0
- package/lib/extensions/manifests/openrouter-google.manifest.json +12 -0
- package/lib/extensions/manifests/openrouter-llama.manifest.json +12 -0
- package/lib/extensions/manifests/openrouter-qwen.manifest.json +12 -0
- package/lib/extensions/manifests/openrouter.manifest.json +12 -0
- package/lib/extensions/manifests/postgres.manifest.json +12 -0
- package/lib/extensions/manifests/salesforce.manifest.json +12 -0
- package/lib/extensions/manifests/slack.manifest.json +58 -0
- package/lib/extensions/manifests/whisper.manifest.json +36 -0
- package/lib/extensions/validate.mjs +175 -0
- package/lib/features.mjs +13 -9
- package/lib/flows/checkpoint.mjs +184 -0
- package/lib/flows/constants.mjs +27 -0
- package/lib/flows/define.mjs +146 -0
- package/lib/flows/engine.mjs +249 -0
- package/lib/flows/errors.mjs +19 -0
- package/lib/flows/index.mjs +27 -0
- package/lib/flows/joins.mjs +18 -0
- package/lib/flows/schema.mjs +90 -0
- package/lib/flows/state.mjs +31 -0
- package/lib/frameworks/loader.mjs +99 -0
- package/lib/frameworks/schema.mjs +157 -0
- package/lib/graph/build-from-corpus.mjs +67 -0
- package/lib/graph/build-from-embed.mjs +82 -0
- package/lib/graph/build-from-registry.mjs +164 -3
- package/lib/graph/cli.mjs +456 -12
- package/lib/graph/gap-queries.mjs +156 -0
- package/lib/graph/gaps.mjs +41 -0
- package/lib/graph/impacted.mjs +129 -0
- package/lib/graph/runtime-evidence.mjs +177 -0
- package/lib/graph/security-coverage.mjs +113 -0
- package/lib/graph/staleness.mjs +241 -10
- package/lib/graph/store.mjs +24 -7
- package/lib/graph/validate.mjs +161 -0
- package/lib/headhunt.mjs +9 -8
- package/lib/health-check.mjs +15 -7
- package/lib/hook-health.mjs +1 -1
- package/lib/hooks/agent-tracker.mjs +10 -4
- package/lib/hooks/edit-guard.mjs +3 -3
- package/lib/hooks/graph-impact-advisory.mjs +2 -2
- package/lib/hooks/guard-bash.mjs +41 -15
- package/lib/hooks/mcp-health-check.mjs +11 -4
- package/lib/hooks/model-fallback.mjs +50 -6
- package/lib/hooks/orchestration-dispatch-guard.mjs +14 -3
- package/lib/hooks/pre-compact.mjs +181 -173
- package/lib/hooks/rule-verifier.mjs +2 -1
- package/lib/hooks/session-reflect.mjs +2 -1
- package/lib/hooks/session-start.mjs +3 -3
- package/lib/host-capabilities.mjs +13 -2
- package/lib/host-disposition.mjs +19 -7
- package/lib/identity.mjs +92 -0
- package/lib/ingest/degraded-extract.mjs +96 -0
- package/lib/ingest/docling-remote.mjs +2 -1
- package/lib/ingest/provider-extract.mjs +7 -19
- package/lib/ingest/sidecar-providers.mjs +196 -0
- package/lib/ingest-tooling.mjs +11 -5
- package/lib/init-unified.mjs +57 -45
- package/lib/init-update.mjs +2 -1
- package/lib/install/legacy-global-cleanup.mjs +25 -0
- package/lib/intake/daemon.mjs +99 -8
- package/lib/intake/git-queue.mjs +110 -38
- package/lib/intake/queue-registry.mjs +50 -0
- package/lib/intake/queue.mjs +133 -17
- package/lib/intake/session-prelude.mjs +57 -8
- package/lib/integrations/intake-integrations.mjs +61 -111
- package/lib/intent-classifier.mjs +43 -5
- package/lib/libreoffice-export.mjs +8 -8
- package/lib/logging/rotate.mjs +5 -4
- package/lib/mcp/broker.mjs +332 -17
- package/lib/mcp/denied-store.mjs +95 -0
- package/lib/mcp/destructive-gate.mjs +30 -0
- package/lib/mcp/dispatch-envelope.mjs +199 -0
- package/lib/mcp/memory-bridge.mjs +2 -1
- package/lib/mcp/server.mjs +154 -1411
- package/lib/mcp/tool-definitions-memory.mjs +376 -0
- package/lib/mcp/tool-definitions-project.mjs +271 -0
- package/lib/mcp/tool-definitions-skills.mjs +311 -0
- package/lib/mcp/tool-definitions-workflow.mjs +398 -0
- package/lib/mcp/tool-definitions.mjs +25 -0
- package/lib/mcp/tool-registry.mjs +107 -0
- package/lib/mcp/tool-safety.mjs +1 -0
- package/lib/mcp/tools/orchestration-delegation-next.tool.mjs +68 -0
- package/lib/mcp/tools/orchestration-run.mjs +165 -25
- package/lib/mcp/tools/orchestration-task-result.tool.mjs +77 -0
- package/lib/mcp/tools/provider-write.mjs +187 -0
- package/lib/mcp/tools/scope.mjs +0 -3
- package/lib/mcp/tools/skills.mjs +1 -1
- package/lib/mcp/tools/storage.mjs +0 -8
- package/lib/mcp/transport/auth.mjs +238 -0
- package/lib/mcp/transport/http.mjs +136 -0
- package/lib/mcp/transport/mode.mjs +31 -0
- package/lib/mcp/transport/stdio.mjs +22 -0
- package/lib/mcp-catalog.json +4 -4
- package/lib/mcp-manager.mjs +34 -23
- package/lib/mcp-platform-config.mjs +31 -7
- package/lib/mode-capabilities.mjs +109 -0
- package/lib/model-router.mjs +42 -9
- package/lib/models/catalog.mjs +40 -1
- package/lib/net-guard.mjs +247 -0
- package/lib/observation-store.mjs +6 -2
- package/lib/ollama/provision-context.mjs +2 -1
- package/lib/opencode-config.mjs +22 -3
- package/lib/opencode-runtime-plugin.mjs +120 -2
- package/lib/oracle/actions.mjs +204 -10
- package/lib/oracle/cli.mjs +24 -3
- package/lib/oracle/dispatch.mjs +2 -1
- package/lib/oracle/execute.mjs +2 -2
- package/lib/oracle/gaps.mjs +3 -3
- package/lib/oracle/heartbeat.mjs +53 -0
- package/lib/oracle/read-model.mjs +69 -13
- package/lib/oracle/reconcile.mjs +3 -46
- package/lib/oracle/routing.mjs +37 -31
- package/lib/oracle/synthesize.mjs +1 -1
- package/lib/orchestration/classification.mjs +434 -0
- package/lib/orchestration/delegation-flow.mjs +132 -0
- package/lib/orchestration/flow-selection.mjs +418 -0
- package/lib/orchestration/gates.mjs +244 -0
- package/lib/orchestration/host-sampling.mjs +121 -0
- package/lib/orchestration/policy-constants.mjs +48 -0
- package/lib/orchestration/provider-outcome.mjs +197 -0
- package/lib/orchestration/readiness.mjs +114 -13
- package/lib/orchestration/run-store-postgres.mjs +32 -26
- package/lib/orchestration/run-store-sqlite.mjs +8 -14
- package/lib/orchestration/run-store.mjs +25 -12
- package/lib/orchestration/runtime.mjs +446 -39
- package/lib/orchestration/store.mjs +87 -12
- package/lib/orchestration/trace-store.mjs +125 -0
- package/lib/orchestration/web-capability.mjs +3 -2
- package/lib/orchestration/worker-runtime.mjs +162 -0
- package/lib/orchestration/worker.mjs +528 -89
- package/lib/orchestration-policy.mjs +67 -1111
- package/lib/packs/cli.mjs +144 -0
- package/lib/packs/core-pack.mjs +112 -0
- package/lib/packs/enablement.mjs +187 -0
- package/lib/packs/index.mjs +23 -0
- package/lib/packs/loader.mjs +176 -0
- package/lib/packs/manifest-schema.mjs +58 -0
- package/lib/packs/prompts.mjs +120 -0
- package/lib/packs/validate.mjs +208 -0
- package/lib/plugin-registry.mjs +4 -5
- package/lib/policy/audit-gate.mjs +42 -0
- package/lib/policy/consumption-budget.mjs +149 -0
- package/lib/policy/engine.mjs +81 -6
- package/lib/policy/role-authority.mjs +89 -0
- package/lib/prompt-composer.js +19 -3
- package/lib/providers/contract/adapters/confluence/governed-write.mjs +215 -0
- package/lib/providers/contract/adapters/confluence/manifest.json +17 -0
- package/lib/providers/contract/adapters/confluence/transport.mjs +135 -0
- package/lib/providers/contract/adapters/github/governed-write.mjs +121 -0
- package/lib/providers/contract/adapters/github/index.mjs +38 -3
- package/lib/providers/contract/adapters/jira/adf.mjs +151 -0
- package/lib/providers/contract/adapters/jira/createmeta.mjs +143 -0
- package/lib/providers/contract/adapters/jira/governed-write.mjs +182 -0
- package/lib/providers/contract/adapters/jira/manifest.json +17 -0
- package/lib/providers/contract/adapters/jira/transport.mjs +119 -0
- package/lib/providers/contract.mjs +207 -0
- package/lib/providers/credential-bootstrap.mjs +7 -4
- package/lib/providers/credential-sources.mjs +14 -2
- package/lib/providers/directory/index.mjs +273 -0
- package/lib/providers/feedback/index.mjs +392 -0
- package/lib/providers/filter-audit.mjs +68 -0
- package/lib/providers/filter-schema.mjs +54 -0
- package/lib/providers/github/index.mjs +31 -0
- package/lib/providers/instance-config.mjs +215 -0
- package/lib/providers/op-locate.mjs +96 -0
- package/lib/providers/op-run.mjs +64 -9
- package/lib/providers/registry.mjs +26 -3
- package/lib/providers/secret-audit-wiring.mjs +6 -0
- package/lib/providers/secret-resolver.mjs +240 -23
- package/lib/publish-template.mjs +6 -3
- package/lib/publish-tooling.mjs +4 -0
- package/lib/publish.mjs +32 -4
- package/lib/queue/pg-queue.mjs +395 -0
- package/lib/reflect.mjs +2 -1
- package/lib/registry/assemble.mjs +28 -2
- package/lib/registry/consolidation.mjs +8 -1
- package/lib/registry/custom-scaffold.mjs +200 -0
- package/lib/registry/custom-schema.mjs +137 -0
- package/lib/registry/loader.mjs +8 -3
- package/lib/registry/manifests/format-engines.default.json +15 -0
- package/lib/registry/manifests/surface-map.default.json +46 -0
- package/lib/registry/retired-paths.mjs +1 -0
- package/lib/registry/surface-map.mjs +100 -50
- package/lib/render-visual-check.mjs +240 -0
- package/lib/resources/budget.mjs +40 -17
- package/lib/roles/flavor-bindings.mjs +36 -14
- package/lib/roles/gateway.mjs +15 -8
- package/lib/roots.mjs +107 -0
- package/lib/runtime/uv-bootstrap.mjs +32 -6
- package/lib/runtime/whisper-bootstrap.mjs +11 -3
- package/lib/runtime-env.mjs +5 -2
- package/lib/scheduler/index.mjs +8 -20
- package/lib/schema-infer.mjs +31 -2
- package/lib/scopes/lifecycle.mjs +29 -25
- package/lib/scopes/loader.mjs +49 -12
- package/lib/scopes/teams.mjs +1 -1
- package/lib/security/ingest-boundary.mjs +80 -0
- package/lib/security/recall-wrapper.mjs +99 -0
- package/lib/security/trust.mjs +132 -0
- package/lib/service-manager.mjs +38 -19
- package/lib/setup.mjs +41 -23
- package/lib/skills-apply.mjs +4 -2
- package/lib/specialists/postconditions.mjs +2 -2
- package/lib/state-root.mjs +147 -0
- package/lib/status.mjs +597 -6
- package/lib/storage/admin.mjs +77 -5
- package/lib/storage/backend-registry.mjs +73 -0
- package/lib/storage/backend.mjs +63 -9
- package/lib/storage/embeddings-openai.mjs +12 -2
- package/lib/storage/hybrid-query.mjs +11 -3
- package/lib/storage/retrieval-hardening.mjs +384 -0
- package/lib/storage/shared-memory.mjs +158 -0
- package/lib/storage/vector-client.mjs +69 -2
- package/lib/team/health.mjs +72 -0
- package/lib/telemetry/backends/local.mjs +9 -3
- package/lib/telemetry/client.mjs +3 -7
- package/lib/template-registry.mjs +10 -12
- package/lib/tenant/context.mjs +82 -0
- package/lib/tenant/isolation.mjs +129 -0
- package/lib/test-corpus-inventory.mjs +104 -2
- package/lib/uninstall/uninstall.mjs +78 -12
- package/lib/validator.mjs +4 -6
- package/lib/worker/entrypoint.mjs +2 -1
- package/lib/worker/run.mjs +2 -2
- package/lib/worker/trace.mjs +5 -11
- package/lib/workflow-state.mjs +52 -8
- package/lib/workflows/liveness.mjs +203 -0
- package/lib/workflows/loader.mjs +177 -0
- package/lib/workflows/manifest-schema.mjs +71 -0
- package/lib/workflows/surface-parity.mjs +118 -0
- package/lib/workflows/validate.mjs +127 -0
- package/lib/writes/control-plane.mjs +140 -0
- package/lib/writes/envelope.mjs +206 -0
- package/lib/writes/sent-log.mjs +86 -0
- package/lib/writes/write-intent.mjs +109 -0
- package/package.json +16 -8
- package/personas/construct.md +12 -3
- package/registry/capabilities.json +143 -36
- package/rules/common/comments.md +1 -0
- package/schemas/project-config.schema.json +0 -12
- package/schemas/unified-registry.schema.json +2 -4
- package/scripts/sync-specialists.mjs +284 -81
- package/skills/docs/adr-workflow.md +1 -1
- package/skills/docs/codebase-research-workflow.md +3 -3
- package/skills/docs/prd-workflow.md +5 -6
- package/skills/docs/runbook-workflow.md +2 -2
- package/skills/docs/user-research-workflow.md +3 -3
- package/skills/operating/fleet-health-routing.md +77 -0
- package/skills/roles/ai-engineer.md +1 -1
- package/skills/roles/architect.md +0 -1
- package/skills/roles/business-strategist.md +1 -1
- package/skills/roles/data-analyst.telemetry.md +1 -1
- package/skills/roles/data-engineer.md +1 -1
- package/skills/roles/data-engineer.pipeline.md +1 -1
- package/skills/roles/data-engineer.vector-retrieval.md +1 -2
- package/skills/roles/data-engineer.warehouse.md +1 -1
- package/skills/roles/designer.accessibility.md +1 -1
- package/skills/roles/designer.md +0 -1
- package/skills/roles/devil-advocate.md +1 -1
- package/skills/roles/docs-keeper.md +1 -1
- package/skills/roles/evaluator.md +1 -1
- package/skills/roles/explorer.md +1 -1
- package/skills/roles/platform-engineer.md +1 -1
- package/skills/roles/qa.ai-eval.md +1 -2
- package/skills/roles/qa.api-contract.md +0 -1
- package/skills/roles/qa.data-pipeline.md +0 -1
- package/skills/roles/qa.md +0 -1
- package/skills/roles/qa.web-ui.md +0 -1
- package/skills/roles/release-manager.md +1 -1
- package/skills/roles/researcher.md +0 -2
- package/skills/roles/reviewer.md +0 -3
- package/skills/roles/security.ai.md +1 -1
- package/skills/roles/security.legal-compliance.md +1 -1
- package/skills/roles/security.md +0 -1
- package/skills/roles/security.privacy.md +0 -1
- package/skills/roles/security.supply-chain.md +1 -1
- package/skills/roles/sre.md +1 -1
- package/skills/roles/test-automation.md +1 -1
- package/skills/roles/trace-reviewer.md +1 -1
- package/skills/roles/ux-researcher.md +1 -1
- package/specialists/artifact-manifest.json +36 -36
- package/specialists/org/contracts/accessibility-to-qa.json +11 -3
- package/specialists/org/contracts/any-to-business-strategist.json +19 -7
- package/specialists/org/contracts/any-to-debugger.json +7 -1
- package/specialists/org/contracts/any-to-designer.json +7 -1
- package/specialists/org/contracts/any-to-docs-keeper.json +18 -4
- package/specialists/org/contracts/any-to-explorer.json +13 -4
- package/specialists/org/contracts/any-to-sre-incident.json +6 -2
- package/specialists/org/contracts/any-to-trace-reviewer.json +16 -4
- package/specialists/org/contracts/architect-to-ai-engineer.json +6 -2
- package/specialists/org/contracts/architect-to-data-engineer.json +17 -5
- package/specialists/org/contracts/architect-to-devil-advocate.json +11 -3
- package/specialists/org/contracts/architect-to-engineer.json +20 -4
- package/specialists/org/contracts/architect-to-evaluator.json +15 -5
- package/specialists/org/contracts/architect-to-legal-compliance.json +15 -5
- package/specialists/org/contracts/architect-to-operations.json +17 -4
- package/specialists/org/contracts/architect-to-platform-engineer.json +20 -4
- package/specialists/org/contracts/construct-to-orchestrator.json +10 -2
- package/specialists/org/contracts/data-analyst-to-product-manager.json +11 -2
- package/specialists/org/contracts/engineer-to-qa.json +20 -4
- package/specialists/org/contracts/engineer-to-reviewer.json +29 -5
- package/specialists/org/contracts/explorer-to-engineer.json +11 -3
- package/specialists/org/contracts/legal-compliance-to-release-manager.json +12 -4
- package/specialists/org/contracts/operations-to-user.json +53 -0
- package/specialists/org/contracts/operations-triage-output.json +53 -0
- package/specialists/org/contracts/pm-requirements-candidates.json +53 -0
- package/specialists/org/contracts/product-manager-to-architect.json +30 -10
- package/specialists/org/contracts/product-manager-to-data-analyst.json +13 -3
- package/specialists/org/contracts/product-manager-to-ux-researcher.json +15 -5
- package/specialists/org/contracts/qa-to-release-manager.json +11 -3
- package/specialists/org/contracts/researcher-to-architect.json +10 -2
- package/specialists/org/contracts/researcher-to-product-manager.json +16 -5
- package/specialists/org/contracts/reviewer-to-security.json +17 -3
- package/specialists/org/contracts/test-automation-to-engineer.json +11 -3
- package/specialists/org/contracts/trace-reviewer-to-sre.json +12 -4
- package/specialists/org/contracts/user-to-construct.json +11 -4
- package/specialists/org/frameworks/cx-architect-constraint-option-failure.md +66 -0
- package/specialists/org/frameworks/cx-engineer-feasibility-blast-radius.md +66 -0
- package/specialists/org/frameworks/cx-ops-dependency-sequencing.md +74 -0
- package/specialists/org/frameworks/cx-pm-value-tradeoff.md +66 -0
- package/specialists/org/frameworks/cx-qa-risk-based-coverage.md +69 -0
- package/specialists/org/groups/engineering-group.json +2 -6
- package/specialists/org/groups/governance-group.json +2 -3
- package/specialists/org/groups/operations-group.json +5 -8
- package/specialists/org/groups/product-group.json +4 -8
- package/specialists/org/groups/quality-group.json +3 -7
- package/specialists/org/groups/strategy-group.json +6 -9
- package/specialists/org/models.json.example +8 -0
- package/specialists/org/scopes/creative.json +6 -1
- package/specialists/org/scopes/operations.json +7 -1
- package/specialists/org/scopes/research.json +6 -1
- package/specialists/org/scopes/rnd.json +7 -1
- package/specialists/org/specialists/cx-architect.json +27 -8
- package/specialists/org/specialists/cx-designer.json +22 -8
- package/specialists/org/specialists/cx-engineer.json +71 -7
- package/specialists/org/specialists/cx-operations.json +89 -15
- package/specialists/org/specialists/cx-orchestrator.json +16 -4
- package/specialists/org/specialists/cx-product-manager.json +28 -9
- package/specialists/org/specialists/cx-qa.json +16 -6
- package/specialists/org/specialists/cx-researcher.json +40 -7
- package/specialists/org/specialists/cx-reviewer.json +61 -11
- package/specialists/org/specialists/cx-security.json +30 -10
- package/specialists/org/teams/design-team.json +3 -4
- package/specialists/org/teams/engineering-team.json +3 -10
- package/specialists/org/teams/governance-team.json +3 -5
- package/specialists/org/teams/operations-team.json +6 -12
- package/specialists/org/teams/product-management-team.json +2 -3
- package/specialists/org/teams/quality-team.json +4 -12
- package/specialists/org/teams/research-team.json +3 -3
- package/specialists/org/teams/strategy-team.json +7 -14
- package/specialists/prompts/cx-architect.md +20 -1
- package/specialists/prompts/cx-data-analyst.md +1 -1
- package/specialists/prompts/cx-designer.md +12 -4
- package/specialists/prompts/cx-engineer.md +15 -1
- package/specialists/prompts/cx-operations.md +14 -2
- package/specialists/prompts/cx-orchestrator.md +9 -5
- package/specialists/prompts/cx-product-manager.md +5 -1
- package/specialists/prompts/cx-qa.md +6 -2
- package/specialists/prompts/cx-researcher.md +25 -30
- package/specialists/prompts/cx-reviewer.md +19 -1
- package/specialists/prompts/cx-security.md +5 -1
- package/templates/distribution/construct-brand.typ +123 -59
- package/templates/distribution/construct-decision.typ +6 -3
- package/templates/distribution/construct-pdf.typ +11 -4
- package/templates/distribution/construct-prd.typ +6 -3
- package/templates/distribution/construct-research.typ +7 -4
- package/lib/providers/contract/adapters/git/index.mjs +0 -115
- package/lib/providers/contract/adapters/slack/index.mjs +0 -175
- package/specialists/org/contracts/business-strategist-to-product-manager.json +0 -39
- package/specialists/org/contracts/construct-to-rd-lead.json +0 -53
- package/specialists/org/contracts/data-engineer-to-platform-engineer.json +0 -36
- package/specialists/org/contracts/designer-to-accessibility.json +0 -36
- package/specialists/org/contracts/platform-engineer-to-engineer.json +0 -31
- package/specialists/org/contracts/qa-to-test-automation.json +0 -42
- package/specialists/org/contracts/rd-lead-to-architect.json +0 -38
- package/specialists/org/contracts/sre-to-release-manager.json +0 -36
- package/specialists/org/specialists/cx-accessibility.json +0 -69
- package/specialists/org/specialists/cx-ai-engineer.json +0 -75
- package/specialists/org/specialists/cx-business-strategist.json +0 -72
- package/specialists/org/specialists/cx-data-engineer.json +0 -71
- package/specialists/org/specialists/cx-devil-advocate.json +0 -71
- package/specialists/org/specialists/cx-docs-keeper.json +0 -82
- package/specialists/org/specialists/cx-evaluator.json +0 -69
- package/specialists/org/specialists/cx-explorer.json +0 -69
- package/specialists/org/specialists/cx-legal-compliance.json +0 -74
- package/specialists/org/specialists/cx-oracle.json +0 -46
- package/specialists/org/specialists/cx-platform-engineer.json +0 -80
- package/specialists/org/specialists/cx-rd-lead.json +0 -73
- package/specialists/org/specialists/cx-release-manager.json +0 -77
- package/specialists/org/specialists/cx-sre.json +0 -94
- package/specialists/org/specialists/cx-test-automation.json +0 -69
- package/specialists/org/specialists/cx-trace-reviewer.json +0 -69
- package/specialists/org/specialists/cx-ux-researcher.json +0 -68
- package/specialists/org/teams/accessibility-team.json +0 -39
- package/specialists/org/teams/ux-research-team.json +0 -39
- package/specialists/prompts/cx-accessibility.md +0 -55
- package/specialists/prompts/cx-ai-engineer.md +0 -124
- package/specialists/prompts/cx-business-strategist.md +0 -58
- package/specialists/prompts/cx-data-engineer.md +0 -55
- package/specialists/prompts/cx-devil-advocate.md +0 -59
- package/specialists/prompts/cx-docs-keeper.md +0 -164
- package/specialists/prompts/cx-evaluator.md +0 -49
- package/specialists/prompts/cx-explorer.md +0 -71
- package/specialists/prompts/cx-legal-compliance.md +0 -58
- package/specialists/prompts/cx-oracle.md +0 -98
- package/specialists/prompts/cx-platform-engineer.md +0 -97
- package/specialists/prompts/cx-rd-lead.md +0 -59
- package/specialists/prompts/cx-release-manager.md +0 -54
- package/specialists/prompts/cx-sre.md +0 -111
- package/specialists/prompts/cx-test-automation.md +0 -55
- package/specialists/prompts/cx-trace-reviewer.md +0 -101
- package/specialists/prompts/cx-ux-researcher.md +0 -53
|
@@ -21,10 +21,12 @@ import { join } from 'path';
|
|
|
21
21
|
import { writeContextState, contextMarkdownPath, contextJsonPath } from '../context-state.mjs';
|
|
22
22
|
import { flushReadTrackerDeltas } from '../read-tracker-store.mjs';
|
|
23
23
|
import { doctorRoot } from '../config/xdg.mjs';
|
|
24
|
+
import { isMainModule } from '../roots.mjs';
|
|
24
25
|
|
|
25
26
|
// Section headings that are auto-generated by this hook. Any other heading in
|
|
26
27
|
// the existing context.md is treated as manually authored and preserved.
|
|
27
|
-
|
|
28
|
+
// Exported so tests exercise the real set instead of a re-pasted copy.
|
|
29
|
+
export const AUTO_SECTION_NAMES = new Set([
|
|
28
30
|
'what was in progress',
|
|
29
31
|
'files changed this session',
|
|
30
32
|
'decisions captured',
|
|
@@ -38,7 +40,7 @@ const AUTO_SECTION_NAMES = new Set([
|
|
|
38
40
|
* Parse a markdown string into `[{heading, level, body}]` sections.
|
|
39
41
|
* Returns an empty array for empty/missing input.
|
|
40
42
|
*/
|
|
41
|
-
function parseSections(md) {
|
|
43
|
+
export function parseSections(md) {
|
|
42
44
|
if (!md) return [];
|
|
43
45
|
const lines = md.split('\n');
|
|
44
46
|
const sections = [];
|
|
@@ -60,7 +62,7 @@ function parseSections(md) {
|
|
|
60
62
|
* Extract sections from an existing context.md that were NOT auto-generated.
|
|
61
63
|
* Returns a markdown string (may be empty) to append after the auto content.
|
|
62
64
|
*/
|
|
63
|
-
function extractManualSections(mdPath) {
|
|
65
|
+
export function extractManualSections(mdPath) {
|
|
64
66
|
if (!existsSync(mdPath)) return '';
|
|
65
67
|
let existing = '';
|
|
66
68
|
try { existing = readFileSync(mdPath, 'utf8'); } catch { return ''; }
|
|
@@ -76,7 +78,7 @@ function extractManualSections(mdPath) {
|
|
|
76
78
|
* Merge non-empty arrays from existing context.json into the new state to
|
|
77
79
|
* prevent the hook from discarding decisions/filesChanged captured manually.
|
|
78
80
|
*/
|
|
79
|
-
function mergeExistingJson(jsonPath, newState) {
|
|
81
|
+
export function mergeExistingJson(jsonPath, newState) {
|
|
80
82
|
if (!existsSync(jsonPath)) return newState;
|
|
81
83
|
let existing = {};
|
|
82
84
|
try { existing = JSON.parse(readFileSync(jsonPath, 'utf8')); } catch { return newState; }
|
|
@@ -89,198 +91,204 @@ function mergeExistingJson(jsonPath, newState) {
|
|
|
89
91
|
return merged;
|
|
90
92
|
}
|
|
91
93
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
+
// Guard the hook body so importing this module for its exported helpers
|
|
95
|
+
// (tests, other tools) does not trigger the stdin read + process.exit(0).
|
|
96
|
+
// Mirrors the entrypoint guard already used in lib/hooks/rule-verifier.mjs.
|
|
94
97
|
|
|
95
|
-
|
|
98
|
+
if (isMainModule(import.meta.url)) {
|
|
99
|
+
let input = {};
|
|
100
|
+
try { input = JSON.parse(readFileSync(0, 'utf8')); } catch { process.exit(0); }
|
|
96
101
|
|
|
97
|
-
|
|
98
|
-
const transcriptPath = input?.transcript_path || '';
|
|
102
|
+
try { flushReadTrackerDeltas({ env: process.env }); } catch { /* best effort */ }
|
|
99
103
|
|
|
100
|
-
const
|
|
101
|
-
const
|
|
102
|
-
const pendingTodos = [];
|
|
103
|
-
const warnFlagsPath = join(doctorRoot(), 'warn-flags.txt');
|
|
104
|
-
const efficiencyPath = join(doctorRoot(), 'session-efficiency.json');
|
|
105
|
-
let lastSummary = '';
|
|
104
|
+
const cwd = input?.cwd || process.cwd();
|
|
105
|
+
const transcriptPath = input?.transcript_path || '';
|
|
106
106
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
107
|
+
const filesChanged = [];
|
|
108
|
+
const decisions = [];
|
|
109
|
+
const pendingTodos = [];
|
|
110
|
+
const warnFlagsPath = join(doctorRoot(), 'warn-flags.txt');
|
|
111
|
+
const efficiencyPath = join(doctorRoot(), 'session-efficiency.json');
|
|
112
|
+
let lastSummary = '';
|
|
113
|
+
|
|
114
|
+
if (transcriptPath && existsSync(transcriptPath)) {
|
|
111
115
|
try {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
.
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
116
|
+
const transcriptRaw = readFileSync(transcriptPath, 'utf8');
|
|
117
|
+
let transcript;
|
|
118
|
+
try {
|
|
119
|
+
transcript = JSON.parse(transcriptRaw);
|
|
120
|
+
} catch {
|
|
121
|
+
transcript = transcriptRaw
|
|
122
|
+
.split('\n')
|
|
123
|
+
.filter(Boolean)
|
|
124
|
+
.map((line) => {
|
|
125
|
+
try { return JSON.parse(line); } catch { return null; }
|
|
126
|
+
})
|
|
127
|
+
.filter(Boolean);
|
|
128
|
+
}
|
|
129
|
+
const messages = Array.isArray(transcript) ? transcript : (transcript.messages || []);
|
|
130
|
+
const recent = messages.slice(-60);
|
|
124
131
|
|
|
125
|
-
|
|
126
|
-
|
|
132
|
+
// Track the latest TodoWrite call to get current todo state
|
|
133
|
+
let latestTodos = null;
|
|
127
134
|
|
|
128
|
-
|
|
129
|
-
|
|
135
|
+
for (const msg of recent) {
|
|
136
|
+
if (msg.role === 'tool' || msg.type === 'tool_result') continue;
|
|
130
137
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
138
|
+
// Handle both array-of-blocks and plain-string content formats
|
|
139
|
+
const blocks = Array.isArray(msg.content)
|
|
140
|
+
? msg.content
|
|
141
|
+
: (typeof msg.content === 'string' ? [{ type: 'text', text: msg.content }] : []);
|
|
135
142
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
}
|
|
145
|
-
if (block.type === 'text' && msg.role === 'assistant') {
|
|
146
|
-
const text = (block.text || '').trim();
|
|
147
|
-
// Prefer the last substantive response (>80 chars) over short one-liners
|
|
148
|
-
if (text.length > 80 || (!lastSummary && text.length > 0)) {
|
|
149
|
-
lastSummary = text.slice(0, 400);
|
|
143
|
+
for (const block of blocks) {
|
|
144
|
+
if (block.type === 'tool_use' && (block.name === 'Write' || block.name === 'Edit')) {
|
|
145
|
+
const fp = block.input?.file_path || block.input?.path || '';
|
|
146
|
+
if (fp && !filesChanged.includes(fp)) filesChanged.push(fp);
|
|
147
|
+
}
|
|
148
|
+
if (block.type === 'tool_use' && block.name === 'TodoWrite') {
|
|
149
|
+
const todos = block.input?.todos;
|
|
150
|
+
if (Array.isArray(todos)) latestTodos = todos;
|
|
150
151
|
}
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
if (
|
|
152
|
+
if (block.type === 'text' && msg.role === 'assistant') {
|
|
153
|
+
const text = (block.text || '').trim();
|
|
154
|
+
// Prefer the last substantive response (>80 chars) over short one-liners
|
|
155
|
+
if (text.length > 80 || (!lastSummary && text.length > 0)) {
|
|
156
|
+
lastSummary = text.slice(0, 400);
|
|
157
|
+
}
|
|
158
|
+
const decisionMatches = text.match(/(?:^|\n)(?:DECISION:|Decision:|\*\*Decision\*\*:)\s*([^\n]+)/g) || [];
|
|
159
|
+
for (const match of decisionMatches) {
|
|
160
|
+
const cleaned = match.replace(/^\n?/, '').replace(/^(?:DECISION:|Decision:|\*\*Decision\*\*:)\s*/, '').trim();
|
|
161
|
+
if (cleaned && !decisions.includes(cleaned)) decisions.push(cleaned);
|
|
162
|
+
}
|
|
155
163
|
}
|
|
156
164
|
}
|
|
157
165
|
}
|
|
158
|
-
}
|
|
159
166
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
167
|
+
// Extract pending/in_progress todos from latest TodoWrite
|
|
168
|
+
if (latestTodos) {
|
|
169
|
+
for (const todo of latestTodos) {
|
|
170
|
+
if (todo.status === 'pending' || todo.status === 'in_progress') {
|
|
171
|
+
pendingTodos.push(todo);
|
|
172
|
+
}
|
|
165
173
|
}
|
|
166
174
|
}
|
|
167
|
-
}
|
|
168
175
|
|
|
169
|
-
|
|
170
|
-
}
|
|
176
|
+
} catch { /* best effort */ }
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const warnFlags = (() => {
|
|
180
|
+
try { return readFileSync(warnFlagsPath, 'utf8').trim(); } catch { return ''; }
|
|
181
|
+
})();
|
|
171
182
|
|
|
172
|
-
const
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
`${stats.
|
|
181
|
-
`${stats.
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
183
|
+
const efficiencySummary = (() => {
|
|
184
|
+
try {
|
|
185
|
+
const stats = JSON.parse(readFileSync(efficiencyPath, 'utf8'));
|
|
186
|
+
const parts = [
|
|
187
|
+
`${stats.readCount || 0} reads`,
|
|
188
|
+
`${stats.uniqueFileCount || 0} unique files`,
|
|
189
|
+
];
|
|
190
|
+
if (stats.repeatedReadCount) parts.push(`${stats.repeatedReadCount} repeated reads`);
|
|
191
|
+
if (stats.largeReadCount) parts.push(`${stats.largeReadCount} large reads`);
|
|
192
|
+
if (stats.totalBytesRead) parts.push(`${Math.round(stats.totalBytesRead / 1024)} KB read`);
|
|
193
|
+
return parts.join(' · ');
|
|
194
|
+
} catch {
|
|
195
|
+
return '';
|
|
196
|
+
}
|
|
197
|
+
})();
|
|
198
|
+
|
|
199
|
+
const projectName = cwd.split('/').pop() || 'project';
|
|
200
|
+
const date = new Date().toISOString().slice(0, 16).replace('T', ' ');
|
|
201
|
+
|
|
202
|
+
const contextLines = [
|
|
203
|
+
`# Session Context`,
|
|
204
|
+
`Last saved: ${date}`,
|
|
205
|
+
``,
|
|
206
|
+
`## What was in progress`,
|
|
207
|
+
lastSummary || '(no summary available)',
|
|
208
|
+
``,
|
|
209
|
+
];
|
|
210
|
+
|
|
211
|
+
if (filesChanged.length) {
|
|
212
|
+
contextLines.push('## Files changed this session');
|
|
213
|
+
for (const f of filesChanged.slice(0, 20)) contextLines.push(`- ${f}`);
|
|
214
|
+
contextLines.push('');
|
|
189
215
|
}
|
|
190
|
-
})();
|
|
191
|
-
|
|
192
|
-
const projectName = cwd.split('/').pop() || 'project';
|
|
193
|
-
const date = new Date().toISOString().slice(0, 16).replace('T', ' ');
|
|
194
|
-
|
|
195
|
-
const contextLines = [
|
|
196
|
-
`# Session Context`,
|
|
197
|
-
`Last saved: ${date}`,
|
|
198
|
-
``,
|
|
199
|
-
`## What was in progress`,
|
|
200
|
-
lastSummary || '(no summary available)',
|
|
201
|
-
``,
|
|
202
|
-
];
|
|
203
|
-
|
|
204
|
-
if (filesChanged.length) {
|
|
205
|
-
contextLines.push('## Files changed this session');
|
|
206
|
-
for (const f of filesChanged.slice(0, 20)) contextLines.push(`- ${f}`);
|
|
207
|
-
contextLines.push('');
|
|
208
|
-
}
|
|
209
216
|
|
|
210
|
-
if (decisions.length) {
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
}
|
|
217
|
+
if (decisions.length) {
|
|
218
|
+
contextLines.push('## Decisions captured');
|
|
219
|
+
for (const decision of decisions.slice(-10)) contextLines.push(`- ${decision}`);
|
|
220
|
+
contextLines.push('');
|
|
221
|
+
}
|
|
215
222
|
|
|
216
|
-
if (pendingTodos.length) {
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
223
|
+
if (pendingTodos.length) {
|
|
224
|
+
contextLines.push('## Pending TODOs');
|
|
225
|
+
for (const todo of pendingTodos) {
|
|
226
|
+
const status = todo.status === 'in_progress' ? '[~]' : '[ ]';
|
|
227
|
+
contextLines.push(`${status} ${todo.content || todo.title || todo.text || JSON.stringify(todo)}`);
|
|
228
|
+
}
|
|
229
|
+
contextLines.push('');
|
|
221
230
|
}
|
|
222
|
-
contextLines.push('');
|
|
223
|
-
}
|
|
224
231
|
|
|
225
|
-
if (efficiencySummary) {
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
}
|
|
232
|
+
if (efficiencySummary) {
|
|
233
|
+
contextLines.push('## Session efficiency snapshot');
|
|
234
|
+
contextLines.push(efficiencySummary);
|
|
235
|
+
contextLines.push('');
|
|
236
|
+
}
|
|
230
237
|
|
|
231
|
-
contextLines.push('## Open issues');
|
|
232
|
-
contextLines.push(warnFlags || 'None');
|
|
233
|
-
|
|
234
|
-
const content = contextLines.join('\n') + '\n';
|
|
235
|
-
|
|
236
|
-
const projectCxDir = join(cwd, '.cx');
|
|
237
|
-
try { mkdirSync(projectCxDir, { recursive: true }); } catch { /* exists */ }
|
|
238
|
-
|
|
239
|
-
// Preserve manually-written sections from the existing context.md.
|
|
240
|
-
const projectMdPath = contextMarkdownPath(cwd);
|
|
241
|
-
const manualSections = extractManualSections(projectMdPath);
|
|
242
|
-
const finalContent = content + (manualSections ? '\n' + manualSections : '');
|
|
243
|
-
|
|
244
|
-
const contextJson = {
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
};
|
|
254
|
-
|
|
255
|
-
// Merge non-empty arrays from the existing JSON so manually recorded state
|
|
256
|
-
// is not silently dropped when the transcript scan finds nothing new.
|
|
257
|
-
const mergedJson = mergeExistingJson(contextJsonPath(cwd), contextJson);
|
|
258
|
-
|
|
259
|
-
try { writeContextState(cwd, { ...mergedJson, contextSummary: lastSummary }, { markdown: finalContent }); } catch { /* best effort */ }
|
|
260
|
-
if (decisions.length) {
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
}
|
|
238
|
+
contextLines.push('## Open issues');
|
|
239
|
+
contextLines.push(warnFlags || 'None');
|
|
240
|
+
|
|
241
|
+
const content = contextLines.join('\n') + '\n';
|
|
242
|
+
|
|
243
|
+
const projectCxDir = join(cwd, '.cx');
|
|
244
|
+
try { mkdirSync(projectCxDir, { recursive: true }); } catch { /* exists */ }
|
|
245
|
+
|
|
246
|
+
// Preserve manually-written sections from the existing context.md.
|
|
247
|
+
const projectMdPath = contextMarkdownPath(cwd);
|
|
248
|
+
const manualSections = extractManualSections(projectMdPath);
|
|
249
|
+
const finalContent = content + (manualSections ? '\n' + manualSections : '');
|
|
250
|
+
|
|
251
|
+
const contextJson = {
|
|
252
|
+
source: 'pre-compact',
|
|
253
|
+
projectName,
|
|
254
|
+
lastSummary,
|
|
255
|
+
filesChanged: filesChanged.slice(0, 20),
|
|
256
|
+
decisions: decisions.slice(-10),
|
|
257
|
+
pendingTodos: pendingTodos.slice(0, 20),
|
|
258
|
+
efficiencySummary,
|
|
259
|
+
warnFlags: warnFlags || 'None',
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
// Merge non-empty arrays from the existing JSON so manually recorded state
|
|
263
|
+
// is not silently dropped when the transcript scan finds nothing new.
|
|
264
|
+
const mergedJson = mergeExistingJson(contextJsonPath(cwd), contextJson);
|
|
265
|
+
|
|
266
|
+
try { writeContextState(cwd, { ...mergedJson, contextSummary: lastSummary }, { markdown: finalContent }); } catch { /* best effort */ }
|
|
267
|
+
if (decisions.length) {
|
|
268
|
+
const decisionsDir = join(projectCxDir, 'decisions');
|
|
269
|
+
const slug = `${date.replace(/[^0-9]/g, '')}-session-decisions`;
|
|
270
|
+
const decisionDoc = [
|
|
271
|
+
'# Session Decisions',
|
|
272
|
+
`Captured: ${date}`,
|
|
273
|
+
'',
|
|
274
|
+
'## Decisions',
|
|
275
|
+
...decisions.slice(-10).map((decision) => `- ${decision}`),
|
|
276
|
+
'',
|
|
277
|
+
'## Files changed',
|
|
278
|
+
...(filesChanged.length ? filesChanged.slice(0, 20).map((file) => `- ${file}`) : ['- None captured']),
|
|
279
|
+
'',
|
|
280
|
+
].join('\n');
|
|
281
|
+
try { mkdirSync(decisionsDir, { recursive: true }); } catch { /* exists */ }
|
|
282
|
+
try { writeFileSync(join(decisionsDir, `${slug}.md`), decisionDoc); } catch { /* best effort */ }
|
|
283
|
+
}
|
|
277
284
|
|
|
278
|
-
const globalCxDir = join(doctorRoot(), '.cx');
|
|
279
|
-
try { mkdirSync(globalCxDir, { recursive: true }); } catch { /* exists */ }
|
|
280
|
-
const globalMdPath = contextMarkdownPath(doctorRoot());
|
|
281
|
-
const globalManualSections = extractManualSections(globalMdPath);
|
|
282
|
-
const globalFinalContent = content + (globalManualSections ? '\n' + globalManualSections : '');
|
|
283
|
-
const globalMergedJson = mergeExistingJson(contextJsonPath(doctorRoot()), contextJson);
|
|
284
|
-
try { writeContextState(doctorRoot(), { ...globalMergedJson, contextSummary: lastSummary }, { markdown: globalFinalContent }); } catch { /* best effort */ }
|
|
285
|
+
const globalCxDir = join(doctorRoot(), '.cx');
|
|
286
|
+
try { mkdirSync(globalCxDir, { recursive: true }); } catch { /* exists */ }
|
|
287
|
+
const globalMdPath = contextMarkdownPath(doctorRoot());
|
|
288
|
+
const globalManualSections = extractManualSections(globalMdPath);
|
|
289
|
+
const globalFinalContent = content + (globalManualSections ? '\n' + globalManualSections : '');
|
|
290
|
+
const globalMergedJson = mergeExistingJson(contextJsonPath(doctorRoot()), contextJson);
|
|
291
|
+
try { writeContextState(doctorRoot(), { ...globalMergedJson, contextSummary: lastSummary }, { markdown: globalFinalContent }); } catch { /* best effort */ }
|
|
285
292
|
|
|
286
|
-
process.exit(0);
|
|
293
|
+
process.exit(0);
|
|
294
|
+
}
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
import { readFileSync, existsSync, appendFileSync, mkdirSync } from 'node:fs';
|
|
30
30
|
import { dirname, join } from 'node:path';
|
|
31
31
|
import { logHookFailure } from './_lib/log.mjs';
|
|
32
|
+
import { isMainModule } from '../roots.mjs';
|
|
32
33
|
|
|
33
34
|
const AUDIT_REL = '.cx/audit.jsonl';
|
|
34
35
|
|
|
@@ -202,7 +203,7 @@ function writeAudit(cwd, payload) {
|
|
|
202
203
|
catch (err) { logHookFailure({ hook: 'rule-verifier', err, phase: 'append-audit' }); }
|
|
203
204
|
}
|
|
204
205
|
|
|
205
|
-
if (import.meta.url
|
|
206
|
+
if (isMainModule(import.meta.url)) {
|
|
206
207
|
let input = {};
|
|
207
208
|
try { input = JSON.parse(readFileSync(0, 'utf8')); }
|
|
208
209
|
catch (err) { logHookFailure({ hook: 'rule-verifier', err, phase: 'parse-stdin' }); process.exit(0); }
|
|
@@ -21,12 +21,13 @@
|
|
|
21
21
|
*/
|
|
22
22
|
import { readFileSync, existsSync } from 'node:fs';
|
|
23
23
|
import { join } from 'node:path';
|
|
24
|
+
import { resolveNonNegativeSetting } from '../env-config.mjs';
|
|
24
25
|
|
|
25
26
|
// 500ms keeps the per-turn Stop-hook tax small (ADR-0029): reflection is
|
|
26
27
|
// best-effort and a LanceDB write that overruns the budget is left to the
|
|
27
28
|
// embed daemon's reconcile to backfill. CONSTRUCT_REFLECT_BUDGET_MS raises the
|
|
28
29
|
// ceiling where the write must complete deterministically (slow disks, tests).
|
|
29
|
-
const HARD_BUDGET_MS =
|
|
30
|
+
const HARD_BUDGET_MS = resolveNonNegativeSetting(process.env, 'CONSTRUCT_REFLECT_BUDGET_MS', 500);
|
|
30
31
|
const startedAt = Date.now();
|
|
31
32
|
|
|
32
33
|
// Opt-out before any heavy work — the env check is cheaper than parsing input.
|
|
@@ -192,7 +192,7 @@ const header = `## Resuming — ${projectName} · ${date}
|
|
|
192
192
|
const footer = [statusLine, recentLine].filter(Boolean).join(' · ');
|
|
193
193
|
|
|
194
194
|
const body = context || (contextStale
|
|
195
|
-
? '## Stale context — last context.md update >7 days old.
|
|
195
|
+
? '## Stale context — last context.md update >7 days old. Edit .cx/context.md directly (or use the remember:context skill) to update it — the session-tracking-refresh hook refreshes it automatically on edit.\n'
|
|
196
196
|
: '## Fresh start — no prior context found.\n');
|
|
197
197
|
// Tier 3 hint — efficiency snapshot fires only when status is 'degraded'
|
|
198
198
|
// (repeated-read ratio or byte threshold exceeded). 'configured' (large reads
|
|
@@ -292,8 +292,8 @@ try {
|
|
|
292
292
|
const embedStatus = resolveEmbedStatus(process.env);
|
|
293
293
|
if (embedStatus.level !== 'none') {
|
|
294
294
|
embedStatusNote = `\n## Embed daemon\n${embedStatus.label} · ${embedStatus.detail}\n`;
|
|
295
|
-
if (embedStatus.level === 'stopped'
|
|
296
|
-
const result = await autoStartEmbedIfNeeded(process.env);
|
|
295
|
+
if (embedStatus.level === 'stopped') {
|
|
296
|
+
const result = await autoStartEmbedIfNeeded(process.env, undefined, undefined, process.cwd());
|
|
297
297
|
if (result.started) {
|
|
298
298
|
embedStatusNote = `\n## Embed daemon\nembed: auto-started (pid ${result.pid}) · background polling active\n`;
|
|
299
299
|
}
|
|
@@ -13,6 +13,7 @@ import fs from "node:fs";
|
|
|
13
13
|
import net from "node:net";
|
|
14
14
|
import os from "node:os";
|
|
15
15
|
import path from "node:path";
|
|
16
|
+
import { isMainModule } from './roots.mjs';
|
|
16
17
|
|
|
17
18
|
const LOWEST_PORT = 0;
|
|
18
19
|
const HIGHEST_PORT = 65535;
|
|
@@ -133,9 +134,13 @@ export function detectActiveSessions() {
|
|
|
133
134
|
}
|
|
134
135
|
} catch { /* not logged in or gh cli missing */ }
|
|
135
136
|
|
|
136
|
-
// Anthropic / Claude Code check
|
|
137
|
+
// Anthropic / Claude Code check. MCP server definitions live in
|
|
138
|
+
// ~/.claude.json's top-level `mcpServers`, not settings.json (construct-ranh);
|
|
139
|
+
// settings.json is still checked for `primaryModel` and any legacy `mcpServers`
|
|
140
|
+
// a user configured by hand before that was confirmed inert.
|
|
137
141
|
const home = os.homedir();
|
|
138
142
|
const claudeSettings = path.join(home, ".claude", "settings.json");
|
|
143
|
+
const claudeUserConfig = path.join(home, ".claude.json");
|
|
139
144
|
try {
|
|
140
145
|
if (fs.existsSync(claudeSettings)) {
|
|
141
146
|
const settings = JSON.parse(fs.readFileSync(claudeSettings, "utf8"));
|
|
@@ -143,6 +148,12 @@ export function detectActiveSessions() {
|
|
|
143
148
|
active.push("anthropic");
|
|
144
149
|
}
|
|
145
150
|
}
|
|
151
|
+
if (!active.includes("anthropic") && fs.existsSync(claudeUserConfig)) {
|
|
152
|
+
const userConfig = JSON.parse(fs.readFileSync(claudeUserConfig, "utf8"));
|
|
153
|
+
if (userConfig?.mcpServers) {
|
|
154
|
+
active.push("anthropic");
|
|
155
|
+
}
|
|
156
|
+
}
|
|
146
157
|
} catch { /* invalid or missing config */ }
|
|
147
158
|
|
|
148
159
|
return active;
|
|
@@ -278,7 +289,7 @@ export function printHostCapabilities(hosts = detectHostCapabilities()) {
|
|
|
278
289
|
}
|
|
279
290
|
}
|
|
280
291
|
|
|
281
|
-
if (import.meta.url
|
|
292
|
+
if (isMainModule(import.meta.url)) {
|
|
282
293
|
const args = new Set(process.argv.slice(2));
|
|
283
294
|
if (args.has('--json')) {
|
|
284
295
|
process.stdout.write(formatHostCapabilitiesJson());
|
package/lib/host-disposition.mjs
CHANGED
|
@@ -1,20 +1,32 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* lib/host-disposition.mjs — single source of truth for the disposition of
|
|
3
|
-
* every artifact Construct creates in a host project (ADR-0027 §1
|
|
3
|
+
* every artifact Construct creates in a host project (ADR-0027 §1, extended
|
|
4
|
+
* by ADR-0066 §"Decision" for the machine-scoped heavy-state split).
|
|
4
5
|
*
|
|
5
6
|
* Scope model (see docs/guides/concepts/project-scopes.md):
|
|
6
|
-
* templates/, docs/
|
|
7
|
-
* .construct/
|
|
8
|
-
* .cx/
|
|
9
|
-
*
|
|
10
|
-
*
|
|
7
|
+
* templates/, docs/ — package/repo durable sources (committed)
|
|
8
|
+
* .construct/ — per-project launcher (ignored)
|
|
9
|
+
* .cx/ — per-project config-layer + small runtime
|
|
10
|
+
* markers: context.md/json, workflow.json,
|
|
11
|
+
* intake, oracle, observations, knowledge,
|
|
12
|
+
* graph, demos (ignored)
|
|
13
|
+
* ~/.construct/projects/<key>/ — per-project machine-scoped heavy state:
|
|
14
|
+
* traces, orchestration runs, worker logs,
|
|
15
|
+
* the LanceDB vector index, docling/whisper
|
|
16
|
+
* bootstrap venvs (ADR-0066)
|
|
17
|
+
* ~/.construct/ — user credentials and doctor state
|
|
18
|
+
* ~/.cx/ — user-scope telemetry fallback
|
|
11
19
|
*
|
|
12
20
|
* `IGNORED_PATTERNS` enumerates machine-specific, sync-regenerated artifacts
|
|
13
21
|
* that must never be committed; the init `.gitignore` writer and the
|
|
14
22
|
* `gitignore-coverage` reconciliation both read from here, and uninstall /
|
|
15
23
|
* adapter-prune consume `ADAPTER_DIRS`. Keep this 1:1 with the host-relevant
|
|
16
24
|
* subset of Construct's own repo .gitignore so forward-fix and backward-repair
|
|
17
|
-
* never diverge.
|
|
25
|
+
* never diverge. `.cx/` covers both the config-layer files and the small
|
|
26
|
+
* runtime markers that remain project-local post-ADR-0066 — the heavy
|
|
27
|
+
* categories that moved to `~/.construct/projects/<key>/` are resolved
|
|
28
|
+
* through `lib/state-root.mjs`, not listed here, since they were never
|
|
29
|
+
* project-relative paths to gitignore in the first place.
|
|
18
30
|
*/
|
|
19
31
|
|
|
20
32
|
import fs from 'node:fs';
|
package/lib/identity.mjs
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/identity.mjs — actor and service identity for the control plane.
|
|
3
|
+
*
|
|
4
|
+
* Resolves human vs service identity from request headers (X-Construct-Actor-Id,
|
|
5
|
+
* -Tenant-Id, -Session-Id) or env fallback, and validates it per deployment mode
|
|
6
|
+
* (team/enterprise reject implicit-solo identity). Feeds policy, approval, and audit.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { getDeploymentMode } from './deployment-mode.mjs';
|
|
10
|
+
import os from 'node:os';
|
|
11
|
+
|
|
12
|
+
export class IdentityError extends Error {
|
|
13
|
+
constructor(message) { super(message); this.name = 'IdentityError'; }
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function serviceIdentity({ serviceId, deploymentMode, role, source } = {}) {
|
|
17
|
+
return {
|
|
18
|
+
type: 'service',
|
|
19
|
+
serviceId: serviceId || 'construct-mcp',
|
|
20
|
+
deploymentMode: deploymentMode || 'solo',
|
|
21
|
+
pid: process.pid,
|
|
22
|
+
role: role || null,
|
|
23
|
+
source: source || 'implicit-solo',
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function humanIdentity({ userId, tenantId, sessionId, role, source } = {}) {
|
|
28
|
+
return {
|
|
29
|
+
type: 'human',
|
|
30
|
+
userId: userId || null,
|
|
31
|
+
tenantId: tenantId || null,
|
|
32
|
+
sessionId: sessionId || null,
|
|
33
|
+
role: role || null,
|
|
34
|
+
source: source || 'headers',
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function resolveIdentity(params = {}, { env = process.env, cwd } = {}) {
|
|
39
|
+
const mode = getDeploymentMode(env, { cwd });
|
|
40
|
+
const meta = params?._meta || {};
|
|
41
|
+
|
|
42
|
+
const actorId = meta['X-Construct-Actor-Id'] || '';
|
|
43
|
+
const tenantId = meta['X-Construct-Tenant-Id'] || '';
|
|
44
|
+
const sessionId = meta['X-Construct-Session-Id'] || '';
|
|
45
|
+
const hasHeaders = Boolean(actorId);
|
|
46
|
+
|
|
47
|
+
if ((mode === 'team' || mode === 'enterprise') && !hasHeaders) {
|
|
48
|
+
const role = env.CONSTRUCT_ROLE;
|
|
49
|
+
if (role) {
|
|
50
|
+
return serviceIdentity({ serviceId: `role:${role}`, deploymentMode: mode, role, source: 'env-fallback' });
|
|
51
|
+
}
|
|
52
|
+
throw new IdentityError(`Missing actor identity in ${mode} mode. Set X-Construct-Actor-Id header or CONSTRUCT_ROLE env var.`);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (hasHeaders) {
|
|
56
|
+
const isHuman = actorId.includes('@');
|
|
57
|
+
if (isHuman) {
|
|
58
|
+
return humanIdentity({ userId: actorId, tenantId: tenantId || null, sessionId: sessionId || null, role: env.CONSTRUCT_ROLE || null, source: 'headers' });
|
|
59
|
+
}
|
|
60
|
+
return serviceIdentity({ serviceId: actorId, deploymentMode: mode, role: env.CONSTRUCT_ROLE || null, source: 'headers' });
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return serviceIdentity({ serviceId: `local-${os.hostname()}`, deploymentMode: mode, role: env.CONSTRUCT_ROLE || null, source: 'implicit-solo' });
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function identityRole(identity) {
|
|
67
|
+
return identity?.role || null;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function validateIdentity(identity, mode) {
|
|
71
|
+
if (!identity) return 'Identity is required';
|
|
72
|
+
if (!identity.type) return 'Identity type is required';
|
|
73
|
+
if (identity.type === 'human' && !identity.userId) return 'Human identity requires userId';
|
|
74
|
+
if (identity.type === 'service' && !identity.serviceId) return 'Service identity requires serviceId';
|
|
75
|
+
if ((mode === 'team' || mode === 'enterprise') && identity.source === 'implicit-solo') {
|
|
76
|
+
return `Implicit identity not allowed in ${mode} mode`;
|
|
77
|
+
}
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function identityToRecord(identity) {
|
|
82
|
+
if (!identity) return {};
|
|
83
|
+
return {
|
|
84
|
+
type: identity.type,
|
|
85
|
+
userId: identity.userId || null,
|
|
86
|
+
serviceId: identity.serviceId || null,
|
|
87
|
+
tenantId: identity.tenantId || null,
|
|
88
|
+
sessionId: identity.sessionId || null,
|
|
89
|
+
role: identity.role || null,
|
|
90
|
+
deploymentMode: identity.deploymentMode || null,
|
|
91
|
+
};
|
|
92
|
+
}
|