heyspex 0.1.0 → 0.2.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/.claude-plugin/marketplace.json +14 -0
- package/README.md +21 -0
- package/bin/hs.mjs +1089 -104
- package/bin/spexcode.mjs +262 -0
- package/dev-factory/engine/worktree.mjs +740 -0
- package/dev-factory/lib/code-repo-root.mjs +67 -0
- package/dev-factory/lib/context-budget.mjs +84 -0
- package/dev-factory/lib/epic-detect.mjs +190 -0
- package/dev-factory/lib/fleet-queue-file.mjs +846 -0
- package/dev-factory/lib/fleet-queue.mjs +426 -0
- package/dev-factory/lib/fleet-state.mjs +14 -18
- package/dev-factory/lib/model-catalog.mjs +338 -0
- package/dev-factory/lib/seat-context.mjs +199 -0
- package/dev-factory/lib/spec-io.mjs +93 -0
- package/dev-factory/lib/sufficiency-score.mjs +8 -2
- package/dev-factory/lib/worktree-allocator.mjs +744 -0
- package/dev-factory/model-catalog.json +222 -0
- package/dist/cli/hs-check.mjs +1127 -103
- package/dist/cli/hs-claim.mjs +981 -63
- package/dist/cli/hs-connect.mjs +10059 -1729
- package/dist/cli/hs-defer.mjs +5507 -3072
- package/dist/cli/hs-dev.mjs +576 -0
- package/dist/cli/hs-env.mjs +416 -302
- package/dist/cli/hs-index-rebuild.mjs +7334 -0
- package/dist/cli/hs-index-reconcile.mjs +8138 -0
- package/dist/cli/hs-init.mjs +2840 -149
- package/dist/cli/hs-install.mjs +7214 -594
- package/dist/cli/hs-join.mjs +10640 -2421
- package/dist/cli/hs-vault-pull.mjs +8157 -6370
- package/dist/cli/hs-vault-push.mjs +6433 -0
- package/dist/cli/hs-vault.mjs +4029 -1555
- package/dist/cli/hs-workspace.mjs +2702 -538
- package/dist/cli-templates/customer-vault/specs/entity-model.md +594 -601
- package/dist/cli-templates/customer-vault/specs/protocols/canonical-frontmatter-templates.md +110 -177
- package/dist/cli-templates/customer-vault/specs/protocols/destructive-action-checklist.md +122 -0
- package/dist/cli-templates/customer-vault/specs/protocols/memory-protocol.md +13 -15
- package/dist/cli-templates/customer-vault/specs/protocols/session-close-protocol.md +91 -0
- package/dist/cli-templates/customer-vault/specs/protocols/spec-hierarchy.md +150 -168
- package/dist/cli-templates/customer-vault/specs/protocols/spec-summary-authoring.md +83 -0
- package/dist/cli-templates/harness-commands/claude/hs-auto.md +36 -0
- package/dist/cli-templates/harness-commands/claude/hs-backfill.md +28 -0
- package/dist/cli-templates/harness-commands/claude/hs-capture.md +62 -0
- package/dist/cli-templates/harness-commands/claude/hs-defer.md +29 -0
- package/dist/cli-templates/harness-commands/claude/hs-dev.md +20 -0
- package/dist/cli-templates/harness-commands/claude/hs-doctor.md +32 -0
- package/dist/cli-templates/harness-commands/claude/hs-ground.md +30 -0
- package/dist/cli-templates/harness-commands/claude/hs-handoff.md +33 -0
- package/dist/cli-templates/harness-commands/claude/hs-log.md +27 -0
- package/dist/cli-templates/harness-commands/claude/hs-next.md +29 -0
- package/dist/cli-templates/harness-commands/claude/hs-promote.md +70 -0
- package/dist/cli-templates/harness-commands/claude/hs-push.md +35 -0
- package/dist/cli-templates/harness-commands/claude/hs-qc.md +27 -0
- package/dist/cli-templates/harness-commands/claude/hs-res.md +63 -0
- package/dist/cli-templates/harness-commands/claude/hs-resolve.md +23 -0
- package/dist/cli-templates/harness-commands/claude/hs-review.md +23 -0
- package/dist/cli-templates/harness-commands/claude/hs-view.md +28 -0
- package/dist/cli-templates/harness-commands/claude/kill-ports.md +32 -0
- package/dist/cli-templates/harness-commands/claude/prototype.md +91 -0
- package/dist/cli-templates/harness-commands/cursor/hs-auto.md +40 -0
- package/dist/cli-templates/harness-commands/cursor/hs-backfill.md +31 -0
- package/dist/cli-templates/harness-commands/cursor/hs-capture.md +65 -0
- package/dist/cli-templates/harness-commands/cursor/hs-defer.md +32 -0
- package/dist/cli-templates/harness-commands/cursor/hs-dev.md +24 -0
- package/dist/cli-templates/harness-commands/cursor/hs-doctor.md +36 -0
- package/dist/cli-templates/harness-commands/cursor/hs-ground.md +33 -0
- package/dist/cli-templates/harness-commands/cursor/hs-handoff.md +37 -0
- package/dist/cli-templates/harness-commands/cursor/hs-log.md +31 -0
- package/dist/cli-templates/harness-commands/cursor/hs-next.md +32 -0
- package/dist/cli-templates/harness-commands/cursor/hs-promote.md +73 -0
- package/dist/cli-templates/harness-commands/cursor/hs-push.md +38 -0
- package/dist/cli-templates/harness-commands/cursor/hs-qc.md +30 -0
- package/dist/cli-templates/harness-commands/cursor/hs-res.md +67 -0
- package/dist/cli-templates/harness-commands/cursor/hs-resolve.md +26 -0
- package/dist/cli-templates/harness-commands/cursor/hs-review.md +26 -0
- package/dist/cli-templates/harness-commands/cursor/hs-view.md +32 -0
- package/dist/cli-templates/harness-commands/cursor/kill-ports.md +35 -0
- package/dist/cli-templates/harness-commands/cursor/prototype.md +95 -0
- package/docs/copilot-plugin-install.md +162 -0
- package/docs/driving-heyspex-from-copilot.md +165 -0
- package/docs/mcp-client-config.md +259 -6
- package/package.json +249 -124
- package/plugins/heyspex/.claude-plugin/plugin.json +12 -0
- package/plugins/heyspex/.cursor-plugin/plugin.json +15 -0
- package/plugins/heyspex/.mcp.json +12 -0
- package/plugins/heyspex/README.md +309 -0
- package/plugins/heyspex/bin/launch-mcp.mjs +95 -0
- package/plugins/heyspex/bin/run-hook.mjs +80 -0
- package/plugins/heyspex/hooks/cursor-hooks.json +308 -0
- package/plugins/heyspex/hooks/hooks.json +418 -0
- package/plugins/heyspex/mcp.json +13 -0
- package/scripts/check-mcp-freshness.mjs +17 -9
- package/scripts/claude-hooks/_env-vault-events.mjs +68 -0
- package/scripts/claude-hooks/_guardrail-events.mjs +96 -0
- package/scripts/claude-hooks/_guardrail-loader.mjs +494 -0
- package/scripts/claude-hooks/_intent-classifier-router.mjs +456 -0
- package/scripts/claude-hooks/_intent-classifier-semantic.mjs +356 -0
- package/scripts/claude-hooks/_run.mjs +413 -0
- package/scripts/claude-hooks/_transcript-text.mjs +60 -0
- package/scripts/claude-hooks/_vault-retrieval.mjs +322 -0
- package/scripts/claude-hooks/agent-spawn-delegation-gate.mjs +133 -0
- package/scripts/claude-hooks/capture-execution-gate.mjs +51 -0
- package/scripts/claude-hooks/capture-workflow-gate.mjs +11 -0
- package/scripts/claude-hooks/check-auto-memory-budget.mjs +396 -0
- package/scripts/claude-hooks/check-claude-md-budget.mjs +327 -0
- package/scripts/claude-hooks/check-mcp-freshness-hook.mjs +14 -0
- package/scripts/claude-hooks/claim-before-code-gate.mjs +11 -0
- package/scripts/claude-hooks/claim-liveness-shared.mjs +577 -0
- package/scripts/claude-hooks/close-terminal-on-ship.mjs +64 -0
- package/scripts/claude-hooks/coo-denylist-gate.mjs +11 -0
- package/scripts/claude-hooks/coo-guard-check.mjs +11 -0
- package/scripts/claude-hooks/deep-research-cost-gate.mjs +11 -0
- package/scripts/claude-hooks/dev-server-task-reminder.mjs +15 -0
- package/scripts/claude-hooks/diagnose-capture-reminder.mjs +11 -0
- package/scripts/claude-hooks/directive-obligation-gate.mjs +11 -0
- package/scripts/claude-hooks/emit-claim-status.mjs +41 -0
- package/scripts/claude-hooks/enforce-session-sequence.mjs +11 -0
- package/scripts/claude-hooks/enforce-vault-naming.mjs +11 -0
- package/scripts/claude-hooks/env-vault-guard.mjs +11 -0
- package/scripts/claude-hooks/epic-milestone-phrasing-gate.mjs +11 -0
- package/scripts/claude-hooks/git-identity-guard.mjs +11 -0
- package/scripts/claude-hooks/git-reflog-audit.mjs +15 -0
- package/scripts/claude-hooks/git-stash-gate.mjs +11 -0
- package/scripts/claude-hooks/guard-auto-memory-writes.mjs +137 -0
- package/scripts/claude-hooks/id-display-format-gate.mjs +11 -0
- package/scripts/claude-hooks/inject-communication-style.mjs +86 -0
- package/scripts/claude-hooks/inject-coo-seat.mjs +78 -0
- package/scripts/claude-hooks/inject-dev-server-awareness.mjs +11 -0
- package/scripts/claude-hooks/inject-doctrine.mjs +13 -0
- package/scripts/claude-hooks/inject-vault-context.mjs +14 -0
- package/scripts/claude-hooks/intent-classifier-trigger.mjs +279 -0
- package/scripts/claude-hooks/log-session-activity.mjs +341 -0
- package/scripts/claude-hooks/naughty-list-gate.mjs +11 -0
- package/scripts/claude-hooks/notify-status-flip.mjs +72 -0
- package/scripts/claude-hooks/plan-exit-ingest-gate.mjs +170 -0
- package/scripts/claude-hooks/plan-ingest-close-reminder.mjs +66 -0
- package/scripts/claude-hooks/plan-review-gate.mjs +11 -0
- package/scripts/claude-hooks/preflight-reminder.mjs +262 -0
- package/scripts/claude-hooks/protected-action-intent.mjs +221 -0
- package/scripts/claude-hooks/research-method-guard.mjs +398 -0
- package/scripts/claude-hooks/research-protocol-gate.mjs +11 -0
- package/scripts/claude-hooks/session-end.mjs +451 -0
- package/scripts/claude-hooks/settings-write-gate.mjs +11 -0
- package/scripts/claude-hooks/settlement-check-gate.mjs +39 -0
- package/scripts/claude-hooks/status-flip-detect.mjs +17 -0
- package/scripts/claude-hooks/terminal-close-shared.mjs +426 -0
- package/scripts/claude-hooks/terminal-close-watcher.mjs +122 -0
- package/scripts/claude-hooks/vault-mirror-catchup.mjs +11 -0
- package/scripts/claude-hooks/vault-write-tier-guard.mjs +11 -0
- package/scripts/claude-hooks/wip-checkpoint.mjs +302 -0
- package/scripts/cursor-hooks/manifest.mjs +281 -0
- package/scripts/dev-server-status.mjs +34 -13
- package/scripts/dev-server-stop.mjs +313 -0
- package/scripts/heyspex-mode.mjs +288 -0
- package/scripts/hs-auto-resolve.mjs +240 -0
- package/scripts/hs-auto-source.mjs +398 -0
- package/scripts/hs-auto.mjs +134 -0
- package/scripts/hs-capture-cli.mjs +326 -0
- package/scripts/hs-classifier.mjs +136 -0
- package/scripts/hs-companion.mjs +88 -0
- package/scripts/hs-gateway.mjs +115 -0
- package/scripts/hs-mode.mjs +125 -0
- package/scripts/hs-next.mjs +441 -0
- package/scripts/hs-settings.mjs +338 -0
- package/scripts/hs-view.mjs +496 -0
- package/scripts/hs-worktree.mjs +201 -0
- package/scripts/install-claude-code-hooks.mjs +272 -0
- package/scripts/install-cursor-hooks.mjs +471 -0
- package/scripts/lib/agent-env-markers.mjs +38 -0
- package/scripts/lib/console-url-resolver.mjs +199 -61
- package/scripts/lib/customer-kit.mjs +1203 -0
- package/scripts/lib/dev-mode.mjs +249 -32
- package/scripts/lib/directive-obligations.mjs +349 -0
- package/scripts/lib/heyspex-home.mjs +72 -0
- package/scripts/lib/hook-dispositions.mjs +210 -0
- package/scripts/lib/hook-vault-header.mjs +30 -0
- package/scripts/lib/hs-command.mjs +277 -0
- package/scripts/lib/hs-companion-settings.mjs +472 -0
- package/scripts/lib/hs-source-branch.mjs +196 -0
- package/scripts/lib/hs-source-commit.mjs +59 -5
- package/scripts/lib/hs-source-identity.mjs +526 -0
- package/scripts/lib/hs-source-log.mjs +13 -22
- package/scripts/lib/hs-source-preflight.mjs +18 -4
- package/scripts/lib/hs-source-push.mjs +386 -307
- package/scripts/lib/hs-worktree-args.mjs +115 -0
- package/scripts/lib/shared-path-helpers.mjs +134 -0
- package/scripts/lib/standalone-doctor.mjs +540 -3
- package/scripts/lib/subagent-git-lock.mjs +109 -0
- package/scripts/lib/template-pin.mjs +34 -0
- package/scripts/lib/window-placement.mjs +109 -0
- package/scripts/provenance-lib.mjs +91 -0
- package/scripts/stamp-provenance-on-edit.mjs +222 -0
- package/scripts/wip-ledger-health.mjs +653 -0
- package/spexharness/doctrine/baseline/SPEXCODE-DOCTRINE.baseline.json +29 -0
- package/spexharness/src/adapters/audit.mjs +197 -0
- package/spexharness/src/adapters/claude-code.mjs +449 -0
- package/spexharness/src/adapters/copilot.mjs +236 -0
- package/spexharness/src/adapters/detect-harness.mjs +34 -0
- package/spexharness/src/chat/tui/ansi.mjs +448 -0
- package/spexharness/src/chat/tui/palette.mjs +22 -0
- package/spexharness/src/chat/tui/select.mjs +208 -0
- package/spexharness/src/config-root.mjs +54 -0
- package/spexharness/src/context-budget.mjs +141 -0
- package/spexharness/src/dev-server-control.mjs +389 -0
- package/spexharness/src/doctrine/behaviour.mjs +371 -0
- package/spexharness/src/doctrine/corpus.mjs +733 -0
- package/spexharness/src/doctrine/inject.mjs +338 -0
- package/spexharness/src/doctrine/merge.mjs +184 -0
- package/spexharness/src/doctrine/sources.mjs +252 -0
- package/spexharness/src/doctrine/synthesize.mjs +401 -0
- package/spexharness/src/doctrine/vault-source.mjs +574 -0
- package/spexharness/src/doctrine/writer.mjs +176 -0
- package/spexharness/src/events.mjs +84 -0
- package/spexharness/src/gates/_shell-write-targets.mjs +77 -0
- package/spexharness/src/gates/capture-workflow-gate.mjs +202 -0
- package/spexharness/src/gates/claim-before-code-gate.decide.mjs +33 -0
- package/spexharness/src/gates/claim-before-code-gate.mjs +129 -0
- package/spexharness/src/gates/deep-research-cost-gate.mjs +275 -0
- package/spexharness/src/gates/diagnose-capture-reminder.mjs +112 -0
- package/spexharness/src/gates/directive-obligation-gate.decide.mjs +132 -0
- package/spexharness/src/gates/directive-obligation-gate.mjs +183 -0
- package/spexharness/src/gates/enforce-session-sequence.mjs +895 -0
- package/spexharness/src/gates/enforce-vault-naming.mjs +215 -0
- package/spexharness/src/gates/epic-milestone-phrasing-gate.mjs +233 -0
- package/spexharness/src/gates/git-identity-guard.decide.mjs +436 -0
- package/spexharness/src/gates/git-identity-guard.mjs +61 -0
- package/spexharness/src/gates/git-reflog-audit.mjs +151 -0
- package/spexharness/src/gates/git-stash-gate.decide.mjs +361 -0
- package/spexharness/src/gates/git-stash-gate.mjs +159 -0
- package/spexharness/src/gates/id-display-format-gate.mjs +190 -0
- package/spexharness/src/gates/inject-doctrine-gate.mjs +89 -0
- package/spexharness/src/gates/inject-vault-context.mjs +239 -0
- package/spexharness/src/gates/naughty-list-gate.mjs +213 -0
- package/spexharness/src/gates/plan-review-gate.mjs +233 -0
- package/spexharness/src/gates/preflight-reminder-gate.mjs +265 -0
- package/spexharness/src/gates/research-protocol-gate.mjs +175 -0
- package/spexharness/src/gates/settings-write-gate.decide.mjs +96 -0
- package/spexharness/src/gates/settings-write-gate.mjs +104 -0
- package/spexharness/src/gates/vault-mirror-catchup.mjs +124 -0
- package/spexharness/src/gates/vault-write-tier-guard.decide.mjs +125 -0
- package/spexharness/src/gates/vault-write-tier-guard.mjs +42 -0
- package/spexharness/src/git-reflog-audit.mjs +294 -0
- package/spexharness/src/intent-classifier.mjs +196 -0
- package/spexharness/src/intent-directive.mjs +626 -0
- package/spexharness/src/profile/learn.mjs +551 -0
- package/spexharness/src/profile/proposals.mjs +357 -0
- package/spexharness/src/profile/vault-inclusion.mjs +386 -0
- package/spexharness/src/redact.mjs +151 -0
- package/spexharness/src/rule-table.mjs +849 -0
- package/spexharness/src/seat-context.mjs +122 -0
- package/spexharness/src/session-bootstrap.mjs +512 -0
- package/spexharness/src/session-record.mjs +815 -0
- package/spexharness/src/token-inventory.mjs +207 -0
- package/vendor/heyspex-mcp/account-switch.mjs +96 -0
- package/vendor/heyspex-mcp/alignment-gate.mjs +315 -1
- package/vendor/heyspex-mcp/blocker-resolver.mjs +37 -2
- package/vendor/heyspex-mcp/breakdown-ladder.mjs +119 -4
- package/vendor/heyspex-mcp/breakdown-source.mjs +271 -0
- package/vendor/heyspex-mcp/breakdown-writer.mjs +23 -3
- package/vendor/heyspex-mcp/capture-buffer-patches.mjs +77 -8
- package/vendor/heyspex-mcp/capture-channel.mjs +55 -5
- package/vendor/heyspex-mcp/capture-console-client.mjs +117 -3
- package/vendor/heyspex-mcp/capture-focus.mjs +1061 -31
- package/vendor/heyspex-mcp/capture-outbox.mjs +15 -5
- package/vendor/heyspex-mcp/capture-sources.mjs +195 -0
- package/vendor/heyspex-mcp/capture-tools.mjs +1264 -0
- package/vendor/heyspex-mcp/claim-tools.mjs +1147 -0
- package/vendor/heyspex-mcp/cli-verbs.mjs +2074 -0
- package/vendor/heyspex-mcp/cli.mjs +207 -15524
- package/vendor/heyspex-mcp/close-branch-report.mjs +102 -0
- package/vendor/heyspex-mcp/cloud-tool-surface.mjs +128 -0
- package/vendor/heyspex-mcp/cloud-vault-writes.mjs +602 -0
- package/vendor/heyspex-mcp/config-seed.yaml +15 -0
- package/vendor/heyspex-mcp/config-settings.mjs +290 -7
- package/vendor/heyspex-mcp/console-fetch.mjs +822 -17
- package/vendor/heyspex-mcp/console-transport.mjs +172 -0
- package/vendor/heyspex-mcp/credentials.mjs +585 -0
- package/vendor/heyspex-mcp/defer-spec.mjs +44 -4
- package/vendor/heyspex-mcp/deferred-reclaim.mjs +29 -3
- package/vendor/heyspex-mcp/design-handlers.mjs +31 -0
- package/vendor/heyspex-mcp/design-tools.mjs +4 -1
- package/vendor/heyspex-mcp/dev-factory-bridge.mjs +4 -4
- package/vendor/heyspex-mcp/dev-mode-core.mjs +45 -0
- package/vendor/heyspex-mcp/dev-tools.mjs +131 -0
- package/vendor/heyspex-mcp/diagnose-handlers.mjs +54 -0
- package/vendor/heyspex-mcp/events-tools.mjs +207 -0
- package/vendor/heyspex-mcp/filing-tools.mjs +723 -0
- package/vendor/heyspex-mcp/fleet-handlers.mjs +37 -0
- package/vendor/heyspex-mcp/fleet-queue-tools.mjs +71 -15
- package/vendor/heyspex-mcp/focus-tools.mjs +190 -0
- package/vendor/heyspex-mcp/folder-trust-store.mjs +60 -0
- package/vendor/heyspex-mcp/http-transport.mjs +302 -0
- package/vendor/heyspex-mcp/list-tools.mjs +257 -0
- package/vendor/heyspex-mcp/login-handoff.mjs +15 -1
- package/vendor/heyspex-mcp/mcp-config.mjs +107 -0
- package/vendor/heyspex-mcp/memory-tools.mjs +523 -0
- package/vendor/heyspex-mcp/mvp-mode.mjs +53 -6
- package/vendor/heyspex-mcp/obligation-backstop.mjs +103 -0
- package/vendor/heyspex-mcp/occupied-keys.mjs +142 -0
- package/vendor/heyspex-mcp/on-deck-brief.mjs +3377 -0
- package/vendor/heyspex-mcp/on-deck-scheduler.mjs +1708 -0
- package/vendor/heyspex-mcp/package-root.mjs +32 -0
- package/vendor/heyspex-mcp/peer-presence.mjs +8 -1
- package/vendor/heyspex-mcp/plan-ground.mjs +621 -652
- package/vendor/heyspex-mcp/plan-push.mjs +908 -22
- package/vendor/heyspex-mcp/plan-review.mjs +124 -30
- package/vendor/heyspex-mcp/preflight-gate.mjs +0 -0
- package/vendor/heyspex-mcp/procedure-handlers.mjs +327 -0
- package/vendor/heyspex-mcp/procedure-tools.mjs +1181 -970
- package/vendor/heyspex-mcp/promote-capture.mjs +388 -69
- package/vendor/heyspex-mcp/promote-diagnose.mjs +74 -0
- package/vendor/heyspex-mcp/promote-research-to-diagnose.mjs +9 -2
- package/vendor/heyspex-mcp/promote-research.mjs +666 -53
- package/vendor/heyspex-mcp/promotion-helpers.mjs +251 -0
- package/vendor/heyspex-mcp/prompts.mjs +1460 -0
- package/vendor/heyspex-mcp/read-path.mjs +191 -15
- package/vendor/heyspex-mcp/receipt-shape.mjs +146 -0
- package/vendor/heyspex-mcp/recipe-handlers.mjs +117 -0
- package/vendor/heyspex-mcp/regen-tasks.mjs +210 -3
- package/vendor/heyspex-mcp/register-spec.mjs +266 -54
- package/vendor/heyspex-mcp/request-context.mjs +83 -0
- package/vendor/heyspex-mcp/research-focus.mjs +207 -4
- package/vendor/heyspex-mcp/research-handlers.mjs +625 -0
- package/vendor/heyspex-mcp/research-tools.mjs +4 -4
- package/vendor/heyspex-mcp/response-budget.mjs +348 -0
- package/vendor/heyspex-mcp/review-focus.mjs +25 -1
- package/vendor/heyspex-mcp/roadmap-default.mjs +70 -0
- package/vendor/heyspex-mcp/search-specs.mjs +280 -0
- package/vendor/heyspex-mcp/seat-block-ledger.mjs +113 -0
- package/vendor/heyspex-mcp/seat-identity.mjs +2 -6
- package/vendor/heyspex-mcp/session-owner.mjs +109 -0
- package/vendor/heyspex-mcp/session-tools.mjs +162 -0
- package/vendor/heyspex-mcp/settings-registry.mjs +232 -0
- package/vendor/heyspex-mcp/settings-tools.mjs +85 -12
- package/vendor/heyspex-mcp/spec-edit-tools.mjs +453 -0
- package/vendor/heyspex-mcp/spec-id-lib.mjs +93 -17
- package/vendor/heyspex-mcp/split-research.mjs +304 -56
- package/vendor/heyspex-mcp/stdio-rpc.mjs +22 -0
- package/vendor/heyspex-mcp/submit-for-review.mjs +150 -7
- package/vendor/heyspex-mcp/summary-contract.mjs +105 -0
- package/vendor/heyspex-mcp/summary-write-tools.mjs +228 -0
- package/vendor/heyspex-mcp/symptom-query.mjs +146 -0
- package/vendor/heyspex-mcp/test-helpers.mjs +123 -1
- package/vendor/heyspex-mcp/tool-catalog.mjs +2819 -0
- package/vendor/heyspex-mcp/tool-dispatch.mjs +657 -0
- package/vendor/heyspex-mcp/tool-helpers.mjs +325 -0
- package/vendor/heyspex-mcp/validate-linkage.mjs +414 -27
- package/vendor/heyspex-mcp/vault-io.mjs +720 -0
- package/vendor/heyspex-mcp/vault-less-endpoints.mjs +52 -0
- package/vendor/heyspex-mcp/vault-resolve.mjs +554 -68
- package/vendor/heyspex-mcp/verify-reproduction.mjs +270 -30
- package/vendor/heyspex-mcp/whoami.mjs +92 -0
- package/vendor/heyspex-mcp/write-outbox.mjs +512 -0
|
@@ -52,6 +52,7 @@ import { scoreSufficiency } from '../../dev-factory/lib/sufficiency-score.mjs';
|
|
|
52
52
|
// capture-focus.mjs deep-dive reader rather than a third regex copy.
|
|
53
53
|
import { extractCaptureExpandedConceptFromBody } from './capture-focus.mjs';
|
|
54
54
|
import {
|
|
55
|
+
collectOccupiedKeysEitherKind,
|
|
55
56
|
mintFileKey,
|
|
56
57
|
mintFamilyKey,
|
|
57
58
|
parseSpecId,
|
|
@@ -82,7 +83,27 @@ import {
|
|
|
82
83
|
bumpUpdated,
|
|
83
84
|
appendChangeLogRow,
|
|
84
85
|
commitWrites,
|
|
86
|
+
fetchSpecRecordViaConsole,
|
|
87
|
+
resolveWriteModeFromDeps,
|
|
88
|
+
splitRawSpecForConsole,
|
|
89
|
+
cloudWriteFailureResult,
|
|
85
90
|
} from './promotion-helpers.mjs';
|
|
91
|
+
// Cloud-mode vault WRITES ([[HS-BYU4-CWPX-TASK-promotion-writes-go-through-the-console]]).
|
|
92
|
+
// The write-side twin of read-path.mjs: in `cloud` write mode a create becomes
|
|
93
|
+
// POST /api/specs (server-minted id + family + filename) and an edit becomes
|
|
94
|
+
// PUT /api/specs/:id. Nothing here opens a socket itself — `consoleFetch` is
|
|
95
|
+
// the same injected dep the cloud-tier READS already use.
|
|
96
|
+
import {
|
|
97
|
+
createSpecViaConsole,
|
|
98
|
+
updateSpecViaConsole,
|
|
99
|
+
stripServerMintedFields,
|
|
100
|
+
} from './cloud-vault-writes.mjs';
|
|
101
|
+
// Cloud-tier read selector ([[HS-KR16-CWPX-TASK-promotion-reads-through-the-cloud-tier]]) —
|
|
102
|
+
// reused verbatim, never copied. `readViaSelector` picks disk vs neon; the
|
|
103
|
+
// neon branch below fetches through `fetchSpecRecordViaConsole` (an injected
|
|
104
|
+
// consoleFetch, not read-path.mjs's own transport) so tests never open a
|
|
105
|
+
// socket, matching this file's existing deps-injection convention.
|
|
106
|
+
import { readViaSelector } from './read-path.mjs';
|
|
86
107
|
// Reuse the exact same placeholder-count logic RESEARCH_STILL_STUB uses to
|
|
87
108
|
// refuse `heyspex_promote_research` on a stub research doc — proactively
|
|
88
109
|
// surfaced here at creation/extension time instead of only reactively at
|
|
@@ -99,6 +120,15 @@ import { findUnresolvedResearchPlaceholders } from './promote-research.mjs';
|
|
|
99
120
|
// vendor/heyspex-mcp/spec-id-lib.mjs (`parseSpecId`).
|
|
100
121
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
101
122
|
|
|
123
|
+
// Placeholders the CLOUD path feeds the shared content builders in place of a
|
|
124
|
+
// locally-minted id / file key / family key. They never reach the console:
|
|
125
|
+
// `stripServerMintedFields` removes `id:` and `file_id:` from the request, and
|
|
126
|
+
// an un-inheritable `family:` is deleted alongside them. Their only job is to
|
|
127
|
+
// keep the builder's template well-formed so its BODY is byte-for-byte the
|
|
128
|
+
// markdown the disk path would have written.
|
|
129
|
+
const CLOUD_PENDING_ID = 'HS-PENDING';
|
|
130
|
+
const CLOUD_PENDING_KEY = 'PENDING';
|
|
131
|
+
|
|
102
132
|
/** True when `id` (either ordering) parses to a spec with the given type label. */
|
|
103
133
|
function hasTypeLabel(id, label) {
|
|
104
134
|
const p = parseSpecId(id);
|
|
@@ -295,6 +325,38 @@ function findResearchByIdLocal(vaultRoot, researchId) {
|
|
|
295
325
|
return null;
|
|
296
326
|
}
|
|
297
327
|
|
|
328
|
+
/**
|
|
329
|
+
* Cloud-tier sibling of {@link findCaptureByIdLocal} (HS-KR16). `tier`,
|
|
330
|
+
* `consoleUrl` and `consoleFetch` are resolved ONCE per handler call
|
|
331
|
+
* (`handlePromoteCapture` in capture-tools.mjs) and threaded down here —
|
|
332
|
+
* never re-resolved inside this helper.
|
|
333
|
+
*/
|
|
334
|
+
async function findCaptureByIdViaTier(vaultRoot, captureId, { tier, consoleUrl, consoleFetch }) {
|
|
335
|
+
return readViaSelector({
|
|
336
|
+
allowDiskFallback: false,
|
|
337
|
+
tier,
|
|
338
|
+
diskRead: () => findCaptureByIdLocal(vaultRoot, captureId),
|
|
339
|
+
neonRead: () => fetchSpecRecordViaConsole(consoleFetch, captureId),
|
|
340
|
+
routeLabel: 'GET /api/specs/[id]',
|
|
341
|
+
consoleUrl,
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Cloud-tier sibling of {@link findResearchByIdLocal} (HS-KR16). Same
|
|
347
|
+
* contract: a bare record, or `null` on a genuine not-found.
|
|
348
|
+
*/
|
|
349
|
+
async function findResearchByIdViaTier(vaultRoot, researchId, { tier, consoleUrl, consoleFetch }) {
|
|
350
|
+
return readViaSelector({
|
|
351
|
+
allowDiskFallback: false,
|
|
352
|
+
tier,
|
|
353
|
+
diskRead: () => findResearchByIdLocal(vaultRoot, researchId),
|
|
354
|
+
neonRead: () => fetchSpecRecordViaConsole(consoleFetch, researchId),
|
|
355
|
+
routeLabel: 'GET /api/specs/[id]',
|
|
356
|
+
consoleUrl,
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
|
|
298
360
|
function findResearchByBasename(vaultRoot, basename) {
|
|
299
361
|
const absPath = join(vaultRoot, 'specs', 'research', `${basename}.md`);
|
|
300
362
|
if (!existsSync(absPath)) return null;
|
|
@@ -652,6 +714,26 @@ async function runExecutePhase(args, ctx, deps) {
|
|
|
652
714
|
const now = (deps?.clock ?? isoNow)();
|
|
653
715
|
const today = now.slice(0, 10);
|
|
654
716
|
const author = 'promote-capture (agent)';
|
|
717
|
+
// Vault WRITE mode ([[HS-BYU4-CWPX-TASK-promotion-writes-go-through-the-console]]).
|
|
718
|
+
// Resolved ONCE by the caller (`resolveVaultWriteMode()`) and threaded in on
|
|
719
|
+
// `deps`; absent (every pre-existing caller and test) it is `local`, so the
|
|
720
|
+
// disk path below is byte-identical to before this task.
|
|
721
|
+
//
|
|
722
|
+
// CREATE FIRST, LINK SECOND: on disk the flow stages every write and commits
|
|
723
|
+
// them as one transaction, because it mints the research filename itself. In
|
|
724
|
+
// cloud mode the console mints the id, the family and the filename, so the
|
|
725
|
+
// basename each capture's `promoted_to:` / `informs_research:` links to is
|
|
726
|
+
// only knowable AFTER `POST /api/specs` returns. The order therefore
|
|
727
|
+
// inverts: POST the research doc, take its basename, then PUT each capture.
|
|
728
|
+
const writeMode = resolveWriteModeFromDeps(deps);
|
|
729
|
+
const cloudDeps = { consoleFetch: deps?.consoleFetch, consoleUrl: deps?.consoleUrl };
|
|
730
|
+
// Steps that have ALREADY landed in the cloud vault, in order. There is no
|
|
731
|
+
// rollback (the console has no un-create) — on a mid-sequence failure this
|
|
732
|
+
// list is what the caller is told actually happened.
|
|
733
|
+
const cloudCompleted = [];
|
|
734
|
+
// PUTs queued while the primary create is being resolved; flushed at the
|
|
735
|
+
// commit point below, in order.
|
|
736
|
+
const cloudUpdates = [];
|
|
655
737
|
|
|
656
738
|
// ── Resolve / allocate target research basename + path ────────────────────
|
|
657
739
|
let researchBasename;
|
|
@@ -672,28 +754,49 @@ async function runExecutePhase(args, ctx, deps) {
|
|
|
672
754
|
// (ctx.captures[0]) just below. No fallback: era-3 `id:` keys onto the
|
|
673
755
|
// file key alone, so a shared family can never collide.
|
|
674
756
|
const primaryCapture = ctx.captures[0] ?? null;
|
|
675
|
-
|
|
676
|
-
(
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
757
|
+
const inherited = primaryCapture
|
|
758
|
+
? inheritedFamilyKey(
|
|
759
|
+
primaryCapture.frontmatter?.id ?? null,
|
|
760
|
+
primaryCapture.frontmatter,
|
|
761
|
+
primaryCapture.basename,
|
|
762
|
+
)
|
|
763
|
+
: null;
|
|
764
|
+
if (writeMode === 'cloud') {
|
|
765
|
+
// NOTHING is minted locally in cloud mode (AC3): `mintFileKey` /
|
|
766
|
+
// `mintFamilyKey` both derive their answer by scanning the vault for
|
|
767
|
+
// occupied keys, and in a file-less vault that scan sees an EMPTY set —
|
|
768
|
+
// so a key minted here can collide with one the cloud vault already
|
|
769
|
+
// holds. The console mints the file key, the family and the filename
|
|
770
|
+
// inside `createSpec`. An INHERITED family is still passed through (it
|
|
771
|
+
// is read off the source capture, not minted, and `createSpec` honours
|
|
772
|
+
// an explicit `family:` over parent inheritance); with no inheritable
|
|
773
|
+
// family the field is simply omitted and the server picks one.
|
|
774
|
+
researchFamilyKey = inherited ?? null;
|
|
775
|
+
researchFileKey = null;
|
|
776
|
+
researchId = null;
|
|
777
|
+
researchBasename = null;
|
|
778
|
+
researchAbsPath = null;
|
|
779
|
+
researchRelPath = null;
|
|
780
|
+
} else {
|
|
781
|
+
researchFamilyKey =
|
|
782
|
+
inherited ?? mintFamilyKey(ctx.vaultRoot, { randomUnit: deps?.randomUnit });
|
|
783
|
+
// Per-file FILE KEY (owner ruling 2026-07-10) — filename + `file_id:` SSOT,
|
|
784
|
+
// and (V3) the anchor for `id:` itself ([[HS-ZU1Q-TASK-PNAS-flip-new-spec-minting-to-the-file-key]]).
|
|
785
|
+
// Disk occupancy ([[HS-FJWL]]): the `writeMode === 'cloud'` branch above
|
|
786
|
+
// mints NOTHING locally, so this is the local-vault branch only.
|
|
787
|
+
researchFileKey = mintFileKey(collectOccupiedKeysEitherKind(ctx.vaultRoot), { randomUnit: deps?.randomUnit });
|
|
788
|
+
researchId = `HS-${researchFileKey}`;
|
|
789
|
+
const outlineName = args.accepted_outline?.name ?? args.new_research_name;
|
|
790
|
+
const slug = deriveSlug(outlineName);
|
|
791
|
+
researchBasename = `${researchId}-${researchFamilyKey}-RES-${slug}`;
|
|
792
|
+
researchAbsPath = join(
|
|
793
|
+
ctx.vaultRoot,
|
|
794
|
+
'specs',
|
|
795
|
+
'research',
|
|
796
|
+
`${researchBasename}.md`,
|
|
797
|
+
);
|
|
798
|
+
researchRelPath = join('specs', 'research', `${researchBasename}.md`);
|
|
799
|
+
}
|
|
697
800
|
researchCreated = true;
|
|
698
801
|
|
|
699
802
|
// Sanity: accepted_outline is required to actually create the file.
|
|
@@ -775,10 +878,14 @@ async function runExecutePhase(args, ctx, deps) {
|
|
|
775
878
|
// interim `JTBD (job-to-be-done):` leading block, when present.
|
|
776
879
|
const jtbdSeed =
|
|
777
880
|
ctx.captures.length > 0 ? extractCaptureJtbd(ctx.captures[0].raw) : '';
|
|
881
|
+
// In cloud mode the id/file key/family placeholders below are stripped
|
|
882
|
+
// before the request goes out (`stripServerMintedFields`) — the builder is
|
|
883
|
+
// reused verbatim rather than forked so the BODY the console stores is the
|
|
884
|
+
// same markdown the disk path writes.
|
|
778
885
|
const newContent = buildResearchStubContent(
|
|
779
|
-
researchId,
|
|
780
|
-
researchFileKey,
|
|
781
|
-
researchFamilyKey,
|
|
886
|
+
researchId ?? CLOUD_PENDING_ID,
|
|
887
|
+
researchFileKey ?? CLOUD_PENDING_KEY,
|
|
888
|
+
researchFamilyKey ?? CLOUD_PENDING_KEY,
|
|
782
889
|
outlineForCreate,
|
|
783
890
|
captureBasenames,
|
|
784
891
|
enhancesBasename,
|
|
@@ -786,7 +893,42 @@ async function runExecutePhase(args, ctx, deps) {
|
|
|
786
893
|
suggestedType,
|
|
787
894
|
jtbdSeed,
|
|
788
895
|
);
|
|
789
|
-
|
|
896
|
+
if (writeMode === 'cloud') {
|
|
897
|
+
// ── CREATE FIRST ────────────────────────────────────────────────────
|
|
898
|
+
const { frontmatter, body } = splitRawSpecForConsole(newContent);
|
|
899
|
+
const cleaned = stripServerMintedFields(frontmatter);
|
|
900
|
+
if (!researchFamilyKey) delete cleaned.family;
|
|
901
|
+
let created;
|
|
902
|
+
try {
|
|
903
|
+
created = await createSpecViaConsole({
|
|
904
|
+
...cloudDeps,
|
|
905
|
+
type: 'research',
|
|
906
|
+
name: outlineForCreate.name,
|
|
907
|
+
frontmatter: cleaned,
|
|
908
|
+
body,
|
|
909
|
+
actingAgent: deps?.actingAgent ?? null,
|
|
910
|
+
});
|
|
911
|
+
} catch (err) {
|
|
912
|
+
return cloudWriteFailureResult(err, {
|
|
913
|
+
step: { kind: 'create', type: 'research', name: outlineForCreate.name },
|
|
914
|
+
completed: cloudCompleted,
|
|
915
|
+
});
|
|
916
|
+
}
|
|
917
|
+
// ── …LINK SECOND: every sibling write below now has a real basename.
|
|
918
|
+
researchId = created.specId;
|
|
919
|
+
researchBasename = created.basename;
|
|
920
|
+
researchRelPath = created.relPath;
|
|
921
|
+
researchAbsPath = null;
|
|
922
|
+
cloudCompleted.push({
|
|
923
|
+
kind: 'create',
|
|
924
|
+
type: 'research',
|
|
925
|
+
spec_id: created.specId,
|
|
926
|
+
basename: created.basename,
|
|
927
|
+
rel_path: created.relPath,
|
|
928
|
+
});
|
|
929
|
+
} else {
|
|
930
|
+
writes.push({ kind: 'create', absPath: researchAbsPath, content: newContent });
|
|
931
|
+
}
|
|
790
932
|
finalResearchContent = newContent;
|
|
791
933
|
} else {
|
|
792
934
|
const updated = buildUpdatedResearchContent(
|
|
@@ -800,11 +942,23 @@ async function runExecutePhase(args, ctx, deps) {
|
|
|
800
942
|
},
|
|
801
943
|
);
|
|
802
944
|
if (updated.changed) {
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
945
|
+
if (writeMode === 'cloud') {
|
|
946
|
+
// The target research already exists, so its basename is already
|
|
947
|
+
// known — this PUT carries no create-first dependency and is simply
|
|
948
|
+
// queued with the capture PUTs below.
|
|
949
|
+
cloudUpdates.push({
|
|
950
|
+
label: 'research',
|
|
951
|
+
specId: researchId,
|
|
952
|
+
basename: researchBasename,
|
|
953
|
+
content: updated.content,
|
|
954
|
+
});
|
|
955
|
+
} else {
|
|
956
|
+
writes.push({
|
|
957
|
+
kind: 'update',
|
|
958
|
+
absPath: researchAbsPath,
|
|
959
|
+
content: updated.content,
|
|
960
|
+
});
|
|
961
|
+
}
|
|
808
962
|
}
|
|
809
963
|
finalResearchContent = updated.changed ? updated.content : ctx.targetResolution.research.raw;
|
|
810
964
|
}
|
|
@@ -842,15 +996,28 @@ async function runExecutePhase(args, ctx, deps) {
|
|
|
842
996
|
promoted_to_set: updated.setPromotedTo,
|
|
843
997
|
});
|
|
844
998
|
if (updated.changed) {
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
999
|
+
if (writeMode === 'cloud') {
|
|
1000
|
+
cloudUpdates.push({
|
|
1001
|
+
label: 'capture',
|
|
1002
|
+
specId: c.frontmatter?.id ?? c.basename,
|
|
1003
|
+
basename: c.basename,
|
|
1004
|
+
content: updated.content,
|
|
1005
|
+
});
|
|
1006
|
+
} else {
|
|
1007
|
+
writes.push({
|
|
1008
|
+
kind: 'update',
|
|
1009
|
+
absPath: c.absPath,
|
|
1010
|
+
content: updated.content,
|
|
1011
|
+
});
|
|
1012
|
+
}
|
|
850
1013
|
}
|
|
851
1014
|
}
|
|
852
1015
|
|
|
853
|
-
|
|
1016
|
+
const nothingToWrite =
|
|
1017
|
+
writeMode === 'cloud'
|
|
1018
|
+
? cloudCompleted.length === 0 && cloudUpdates.length === 0
|
|
1019
|
+
: writes.length === 0;
|
|
1020
|
+
if (nothingToWrite) {
|
|
854
1021
|
// Idempotent no-op: research already references every input capture and
|
|
855
1022
|
// each capture already references the research. Surface this clearly.
|
|
856
1023
|
return {
|
|
@@ -871,13 +1038,41 @@ async function runExecutePhase(args, ctx, deps) {
|
|
|
871
1038
|
}
|
|
872
1039
|
|
|
873
1040
|
// ── Commit ────────────────────────────────────────────────────────────────
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
1041
|
+
if (writeMode === 'cloud') {
|
|
1042
|
+
// Flush the queued PUTs IN ORDER, stopping at the first failure. No
|
|
1043
|
+
// rollback: the error result names the failing step and lists what landed.
|
|
1044
|
+
for (const u of cloudUpdates) {
|
|
1045
|
+
const { frontmatter, body } = splitRawSpecForConsole(u.content);
|
|
1046
|
+
try {
|
|
1047
|
+
const res = await updateSpecViaConsole({
|
|
1048
|
+
...cloudDeps,
|
|
1049
|
+
specId: u.specId,
|
|
1050
|
+
frontmatter,
|
|
1051
|
+
body,
|
|
1052
|
+
});
|
|
1053
|
+
cloudCompleted.push({
|
|
1054
|
+
kind: 'update',
|
|
1055
|
+
type: u.label,
|
|
1056
|
+
spec_id: res.specId,
|
|
1057
|
+
basename: res.basename ?? u.basename,
|
|
1058
|
+
rel_path: res.relPath,
|
|
1059
|
+
});
|
|
1060
|
+
} catch (err) {
|
|
1061
|
+
return cloudWriteFailureResult(err, {
|
|
1062
|
+
step: { kind: 'update', type: u.label, spec_id: u.specId, basename: u.basename },
|
|
1063
|
+
completed: cloudCompleted,
|
|
1064
|
+
});
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
} else {
|
|
1068
|
+
const commitResult = commitWrites(writes);
|
|
1069
|
+
if (!commitResult.ok) {
|
|
1070
|
+
return {
|
|
1071
|
+
error: 'WRITE_FAILED',
|
|
1072
|
+
message: commitResult.error?.message ?? 'unknown write failure',
|
|
1073
|
+
rollback_error: commitResult.error?.rollbackError,
|
|
1074
|
+
};
|
|
1075
|
+
}
|
|
881
1076
|
}
|
|
882
1077
|
|
|
883
1078
|
return {
|
|
@@ -887,7 +1082,11 @@ async function runExecutePhase(args, ctx, deps) {
|
|
|
887
1082
|
research_path: researchRelPath,
|
|
888
1083
|
research_created: researchCreated,
|
|
889
1084
|
captures: capturePromotionResults,
|
|
890
|
-
|
|
1085
|
+
...(writeMode === 'cloud' ? { write_mode: 'cloud' } : {}),
|
|
1086
|
+
writes:
|
|
1087
|
+
writeMode === 'cloud'
|
|
1088
|
+
? cloudCompleted.map((w) => ({ kind: w.kind, path: w.rel_path ?? null, spec_id: w.spec_id }))
|
|
1089
|
+
: writes.map((w) => ({ kind: w.kind, path: w.absPath })),
|
|
891
1090
|
// TARGET_RESEARCH_CLOSED only occurs on the existing-research branch
|
|
892
1091
|
// (researchCreated === false). Family inheritance on the NEW-research
|
|
893
1092
|
// branch no longer has a fallback case to warn about (era-3 minting —
|
|
@@ -919,6 +1118,19 @@ export async function executePromoteCapture(vaultRoot, args = {}, deps = {}) {
|
|
|
919
1118
|
const validationError = validateInputShape(args);
|
|
920
1119
|
if (validationError) return validationError;
|
|
921
1120
|
|
|
1121
|
+
// Cloud-tier reads ([[HS-KR16-CWPX-TASK-promotion-reads-through-the-cloud-tier]]):
|
|
1122
|
+
// `deps.tier` is resolved ONCE by the caller (`handlePromoteCapture` in
|
|
1123
|
+
// capture-tools.mjs, via `resolveReadTier()`) and threaded down — never
|
|
1124
|
+
// re-resolved here. Absent (every pre-existing caller/test) defaults to
|
|
1125
|
+
// `disk`, so disk-tier behavior is byte-identical to before this task.
|
|
1126
|
+
// Cloud-mode vault WRITES ([[HS-BYU4-CWPX-TASK-promotion-writes-go-through-the-console]]):
|
|
1127
|
+
// `deps.writeMode` is resolved ONCE by the caller (`resolveVaultWriteMode()`)
|
|
1128
|
+
// exactly like `deps.tier`, and defaults to `local` so every pre-existing
|
|
1129
|
+
// caller/test writes to disk unchanged. It is consumed in `runExecutePhase`.
|
|
1130
|
+
const tier = deps.tier ?? 'disk';
|
|
1131
|
+
const consoleUrl = deps.consoleUrl;
|
|
1132
|
+
const consoleFetchDep = deps.consoleFetch;
|
|
1133
|
+
|
|
922
1134
|
// 1. Load every input capture; fail on first miss. Ids collapse to the
|
|
923
1135
|
// short stored form first (display long `HS-XV4W-HV6H` → `HS-XV4W`) so the
|
|
924
1136
|
// basename-prefix and frontmatter-id matches inside the loader hit for any
|
|
@@ -926,7 +1138,11 @@ export async function executePromoteCapture(vaultRoot, args = {}, deps = {}) {
|
|
|
926
1138
|
const captures = [];
|
|
927
1139
|
for (const rawCaptureId of args.capture_ids) {
|
|
928
1140
|
const captureId = shortLookupIdOf(rawCaptureId) ?? rawCaptureId;
|
|
929
|
-
const capture =
|
|
1141
|
+
const capture = await findCaptureByIdViaTier(vaultRoot, captureId, {
|
|
1142
|
+
tier,
|
|
1143
|
+
consoleUrl,
|
|
1144
|
+
consoleFetch: consoleFetchDep,
|
|
1145
|
+
});
|
|
930
1146
|
if (!capture) {
|
|
931
1147
|
return {
|
|
932
1148
|
error: 'CAPTURE_NOT_FOUND',
|
|
@@ -1008,9 +1224,10 @@ export async function executePromoteCapture(vaultRoot, args = {}, deps = {}) {
|
|
|
1008
1224
|
if (args.target_research === 'NEW') {
|
|
1009
1225
|
targetResolution = { mode: 'new' };
|
|
1010
1226
|
} else {
|
|
1011
|
-
const research =
|
|
1227
|
+
const research = await findResearchByIdViaTier(
|
|
1012
1228
|
vaultRoot,
|
|
1013
1229
|
shortLookupIdOf(args.target_research) ?? args.target_research,
|
|
1230
|
+
{ tier, consoleUrl, consoleFetch: consoleFetchDep },
|
|
1014
1231
|
);
|
|
1015
1232
|
if (!research) {
|
|
1016
1233
|
return {
|
|
@@ -1190,7 +1407,19 @@ export async function executePromoteCaptureToDiagnose(vaultRoot, args = {}, deps
|
|
|
1190
1407
|
const severityRaw = typeof args.severity === 'string' ? args.severity.trim().toLowerCase() : 'warning';
|
|
1191
1408
|
const severity = VALID_DIAGNOSE_SEVERITIES.has(severityRaw) ? severityRaw : 'warning';
|
|
1192
1409
|
|
|
1193
|
-
|
|
1410
|
+
// Same once-per-call resolution rule as executePromoteCapture: `tier` and
|
|
1411
|
+
// `writeMode` come in on `deps`, already resolved, and default to
|
|
1412
|
+
// `disk`/`local` so every pre-existing caller is byte-identical.
|
|
1413
|
+
const tier = deps?.tier ?? 'disk';
|
|
1414
|
+
const writeMode = resolveWriteModeFromDeps(deps);
|
|
1415
|
+
const cloudDeps = { consoleFetch: deps?.consoleFetch, consoleUrl: deps?.consoleUrl };
|
|
1416
|
+
const cloudCompleted = [];
|
|
1417
|
+
|
|
1418
|
+
const capture = await findCaptureByIdViaTier(vaultRoot, captureId, {
|
|
1419
|
+
tier,
|
|
1420
|
+
consoleUrl: deps?.consoleUrl,
|
|
1421
|
+
consoleFetch: deps?.consoleFetch,
|
|
1422
|
+
});
|
|
1194
1423
|
if (!capture) {
|
|
1195
1424
|
return { error: 'CAPTURE_NOT_FOUND', message: `capture ${captureId} not found under specs/captures/` };
|
|
1196
1425
|
}
|
|
@@ -1207,23 +1436,73 @@ export async function executePromoteCaptureToDiagnose(vaultRoot, args = {}, deps
|
|
|
1207
1436
|
|
|
1208
1437
|
// A capture→diagnose promotion has no documented family-inheritance hop
|
|
1209
1438
|
// (unlike capture→research) — mints a fresh family, same as any other
|
|
1210
|
-
// parentless spec.
|
|
1211
|
-
|
|
1439
|
+
// parentless spec. In CLOUD mode nothing is minted here at all: the console
|
|
1440
|
+
// mints the file key, the family and the filename (AC3).
|
|
1441
|
+
const familyKey =
|
|
1442
|
+
writeMode === 'cloud' ? null : mintFamilyKey(vaultRoot, { randomUnit: deps?.randomUnit });
|
|
1212
1443
|
// Per-file FILE KEY (owner ruling 2026-07-10) — filename + `file_id:` SSOT,
|
|
1213
1444
|
// and (V3) the anchor for `id:` itself.
|
|
1214
|
-
|
|
1215
|
-
|
|
1445
|
+
// Disk occupancy ([[HS-FJWL]]): guarded by the same `writeMode === 'cloud'`
|
|
1446
|
+
// ternary as the family above — in cloud mode nothing is minted here at all.
|
|
1447
|
+
const fileKey =
|
|
1448
|
+
writeMode === 'cloud'
|
|
1449
|
+
? null
|
|
1450
|
+
: mintFileKey(collectOccupiedKeysEitherKind(vaultRoot), { randomUnit: deps?.randomUnit });
|
|
1216
1451
|
const slug = deriveSlug(name);
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1452
|
+
let diagnoseId = fileKey ? `HS-${fileKey}` : null;
|
|
1453
|
+
let diagnoseBasename = fileKey ? `${diagnoseId}-${familyKey}-DIAG-${slug}` : null;
|
|
1454
|
+
let diagnoseRelPath = fileKey ? `specs/diagnoses/${diagnoseBasename}.md` : null;
|
|
1455
|
+
const diagnoseAbsPath = fileKey
|
|
1456
|
+
? join(vaultRoot, 'specs', 'diagnoses', `${diagnoseBasename}.md`)
|
|
1457
|
+
: null;
|
|
1458
|
+
|
|
1459
|
+
const diagnoseContent = buildDiagnoseContent(
|
|
1460
|
+
diagnoseId ?? CLOUD_PENDING_ID,
|
|
1461
|
+
fileKey ?? CLOUD_PENDING_KEY,
|
|
1462
|
+
familyKey ?? CLOUD_PENDING_KEY,
|
|
1463
|
+
name,
|
|
1464
|
+
severity,
|
|
1465
|
+
capture.basename,
|
|
1466
|
+
coreIdea,
|
|
1467
|
+
now,
|
|
1468
|
+
);
|
|
1220
1469
|
|
|
1221
1470
|
const writes = [];
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1471
|
+
if (writeMode === 'cloud') {
|
|
1472
|
+
// CREATE FIRST — the capture's `promoted_to:` links to the basename this
|
|
1473
|
+
// POST returns, so it cannot be built until the POST has answered.
|
|
1474
|
+
const { frontmatter, body } = splitRawSpecForConsole(diagnoseContent);
|
|
1475
|
+
const cleaned = stripServerMintedFields(frontmatter);
|
|
1476
|
+
delete cleaned.family; // never inherited on this hop; let the server mint it
|
|
1477
|
+
let created;
|
|
1478
|
+
try {
|
|
1479
|
+
created = await createSpecViaConsole({
|
|
1480
|
+
...cloudDeps,
|
|
1481
|
+
type: 'diagnose',
|
|
1482
|
+
name,
|
|
1483
|
+
frontmatter: cleaned,
|
|
1484
|
+
body,
|
|
1485
|
+
actingAgent: deps?.actingAgent ?? null,
|
|
1486
|
+
});
|
|
1487
|
+
} catch (err) {
|
|
1488
|
+
return cloudWriteFailureResult(err, {
|
|
1489
|
+
step: { kind: 'create', type: 'diagnose', name },
|
|
1490
|
+
completed: cloudCompleted,
|
|
1491
|
+
});
|
|
1492
|
+
}
|
|
1493
|
+
diagnoseId = created.specId;
|
|
1494
|
+
diagnoseBasename = created.basename;
|
|
1495
|
+
diagnoseRelPath = created.relPath;
|
|
1496
|
+
cloudCompleted.push({
|
|
1497
|
+
kind: 'create',
|
|
1498
|
+
type: 'diagnose',
|
|
1499
|
+
spec_id: created.specId,
|
|
1500
|
+
basename: created.basename,
|
|
1501
|
+
rel_path: created.relPath,
|
|
1502
|
+
});
|
|
1503
|
+
} else {
|
|
1504
|
+
writes.push({ kind: 'create', absPath: diagnoseAbsPath, content: diagnoseContent });
|
|
1505
|
+
}
|
|
1227
1506
|
|
|
1228
1507
|
const captureUpdate = buildUpdatedCaptureContentForDiagnose(capture.raw, {
|
|
1229
1508
|
diagnoseBasename,
|
|
@@ -1232,16 +1511,50 @@ export async function executePromoteCaptureToDiagnose(vaultRoot, args = {}, deps
|
|
|
1232
1511
|
author: 'promote-capture (agent)',
|
|
1233
1512
|
});
|
|
1234
1513
|
if (captureUpdate.changed) {
|
|
1235
|
-
|
|
1514
|
+
if (writeMode === 'cloud') {
|
|
1515
|
+
// …LINK SECOND.
|
|
1516
|
+
const { frontmatter, body } = splitRawSpecForConsole(captureUpdate.content);
|
|
1517
|
+
try {
|
|
1518
|
+
const res = await updateSpecViaConsole({
|
|
1519
|
+
...cloudDeps,
|
|
1520
|
+
specId: getScalarField(getFrontmatterBlock(capture.raw)?.block ?? '', 'id') ?? capture.basename,
|
|
1521
|
+
frontmatter,
|
|
1522
|
+
body,
|
|
1523
|
+
});
|
|
1524
|
+
cloudCompleted.push({
|
|
1525
|
+
kind: 'update',
|
|
1526
|
+
type: 'capture',
|
|
1527
|
+
spec_id: res.specId,
|
|
1528
|
+
basename: res.basename ?? capture.basename,
|
|
1529
|
+
rel_path: res.relPath,
|
|
1530
|
+
});
|
|
1531
|
+
} catch (err) {
|
|
1532
|
+
return cloudWriteFailureResult(err, {
|
|
1533
|
+
step: { kind: 'update', type: 'capture', basename: capture.basename },
|
|
1534
|
+
completed: cloudCompleted,
|
|
1535
|
+
});
|
|
1536
|
+
}
|
|
1537
|
+
} else {
|
|
1538
|
+
writes.push({ kind: 'update', absPath: capture.absPath, content: captureUpdate.content });
|
|
1539
|
+
}
|
|
1236
1540
|
}
|
|
1237
1541
|
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1542
|
+
if (writeMode !== 'cloud') {
|
|
1543
|
+
const commitResult = commitWrites(writes);
|
|
1544
|
+
if (!commitResult.ok) {
|
|
1545
|
+
return {
|
|
1546
|
+
error: 'WRITE_FAILED',
|
|
1547
|
+
message: commitResult.error?.message ?? 'unknown write failure',
|
|
1548
|
+
rollback_error: commitResult.error?.rollbackError,
|
|
1549
|
+
};
|
|
1550
|
+
}
|
|
1551
|
+
}
|
|
1552
|
+
|
|
1553
|
+
// By this point every path (local mint or cloud create-first) has replaced
|
|
1554
|
+
// the transitional fileKey-pending null with a real id — narrow it here so
|
|
1555
|
+
// callers (e.g. `promote-diagnose.ts`) see `string`, not `string | null`.
|
|
1556
|
+
if (!diagnoseId || !diagnoseBasename || !diagnoseRelPath) {
|
|
1557
|
+
throw new Error('promoteCaptureToDiagnose: missing diagnose id after promotion (unexpected)');
|
|
1245
1558
|
}
|
|
1246
1559
|
|
|
1247
1560
|
return {
|
|
@@ -1257,7 +1570,11 @@ export async function executePromoteCaptureToDiagnose(vaultRoot, args = {}, deps
|
|
|
1257
1570
|
informs_diagnose_added: captureUpdate.changed,
|
|
1258
1571
|
promoted_to_set: captureUpdate.setPromotedTo,
|
|
1259
1572
|
},
|
|
1260
|
-
|
|
1573
|
+
...(writeMode === 'cloud' ? { write_mode: 'cloud' } : {}),
|
|
1574
|
+
writes:
|
|
1575
|
+
writeMode === 'cloud'
|
|
1576
|
+
? cloudCompleted.map((w) => ({ kind: w.kind, path: w.rel_path ?? null, spec_id: w.spec_id }))
|
|
1577
|
+
: writes.map((w) => ({ kind: w.kind, path: w.absPath })),
|
|
1261
1578
|
human_review_required: false,
|
|
1262
1579
|
};
|
|
1263
1580
|
}
|
|
@@ -1266,6 +1583,8 @@ export const __testables = {
|
|
|
1266
1583
|
validateInputShape,
|
|
1267
1584
|
findCaptureByIdLocal,
|
|
1268
1585
|
findResearchByIdLocal,
|
|
1586
|
+
findCaptureByIdViaTier,
|
|
1587
|
+
findResearchByIdViaTier,
|
|
1269
1588
|
findResearchByBasename,
|
|
1270
1589
|
collectOccupiedResearchIds,
|
|
1271
1590
|
collectOccupiedDiagnoseIds,
|