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
|
@@ -42,8 +42,27 @@ import {
|
|
|
42
42
|
mkdirSync,
|
|
43
43
|
unlinkSync,
|
|
44
44
|
} from 'node:fs';
|
|
45
|
-
import { join } from 'node:path';
|
|
46
|
-
import {
|
|
45
|
+
import { join, isAbsolute, resolve, sep } from 'node:path';
|
|
46
|
+
import {
|
|
47
|
+
deriveSlug,
|
|
48
|
+
fetchSpecRecordViaConsole,
|
|
49
|
+
resolveWriteModeFromDeps,
|
|
50
|
+
splitRawSpecForConsole,
|
|
51
|
+
cloudWriteFailureResult,
|
|
52
|
+
} from './promotion-helpers.mjs';
|
|
53
|
+
// Cloud-mode vault WRITES + the cloud-tier read selector
|
|
54
|
+
// ([[HS-BYU4-CWPX-TASK-promotion-writes-go-through-the-console]], HS-KR16).
|
|
55
|
+
// Split has the same create-first, link-second shape as the other promotion
|
|
56
|
+
// flows: each child's "Split-from" callout names its SIBLINGS, and the
|
|
57
|
+
// original's `split_into:` names every child — basenames the console only
|
|
58
|
+
// mints when the POST lands.
|
|
59
|
+
import {
|
|
60
|
+
createSpecViaConsole,
|
|
61
|
+
updateSpecViaConsole,
|
|
62
|
+
stripServerMintedFields,
|
|
63
|
+
} from './cloud-vault-writes.mjs';
|
|
64
|
+
import { readViaSelector } from './read-path.mjs';
|
|
65
|
+
|
|
47
66
|
import { yamlScalar } from './yaml-scalar.mjs';
|
|
48
67
|
import {
|
|
49
68
|
allocateFileKey,
|
|
@@ -60,6 +79,14 @@ import {
|
|
|
60
79
|
// caller would only learn that reactively when heyspex_promote_research
|
|
61
80
|
// later refuses a child with RESEARCH_STILL_STUB.
|
|
62
81
|
import { findUnresolvedResearchPlaceholders } from './promote-research.mjs';
|
|
82
|
+
import { assertPathUnderRoot } from '../../scripts/lib/shared-path-helpers.mjs';
|
|
83
|
+
|
|
84
|
+
// Placeholders the CLOUD path feeds `buildChildResearchContent` in place of a
|
|
85
|
+
// locally-minted id / file key / family key; `stripServerMintedFields` removes
|
|
86
|
+
// them from the POST body (plus an explicit `delete` of an un-inheritable
|
|
87
|
+
// `family:`), so the console never sees them.
|
|
88
|
+
const CLOUD_PENDING_ID = 'HS-PENDING';
|
|
89
|
+
const CLOUD_PENDING_KEY = 'PENDING';
|
|
63
90
|
|
|
64
91
|
// Id validation/classification is via parseSpecId (dual-read: accepts type-first
|
|
65
92
|
// HS-RES-<key> / HS-MIL-<key> / HS-CAP-<key> and the family-first orderings, plus
|
|
@@ -216,7 +243,7 @@ function appendChangeLogRow(body, dateStr, author, message) {
|
|
|
216
243
|
return body.replace(/\s*$/, '') + `\n${row}\n`;
|
|
217
244
|
}
|
|
218
245
|
|
|
219
|
-
function commitWrites(writes) {
|
|
246
|
+
function commitWrites(writes, vaultRoot) {
|
|
220
247
|
const snapshots = new Map();
|
|
221
248
|
const performed = [];
|
|
222
249
|
try {
|
|
@@ -230,7 +257,10 @@ function commitWrites(writes) {
|
|
|
230
257
|
0,
|
|
231
258
|
Math.max(w.absPath.lastIndexOf('/'), w.absPath.lastIndexOf('\\')),
|
|
232
259
|
);
|
|
233
|
-
if (dir)
|
|
260
|
+
if (dir) {
|
|
261
|
+
if (vaultRoot) assertPathUnderRoot(vaultRoot, dir, 'split-research write');
|
|
262
|
+
mkdirSync(dir, { recursive: true });
|
|
263
|
+
}
|
|
234
264
|
writeFileSync(w.absPath, w.content, 'utf8');
|
|
235
265
|
performed.push(w);
|
|
236
266
|
} else if (w.kind === 'update') {
|
|
@@ -396,6 +426,49 @@ function findCaptureByIdOrBasename(vaultRoot, ref) {
|
|
|
396
426
|
return null;
|
|
397
427
|
}
|
|
398
428
|
|
|
429
|
+
// ── Cloud-tier siblings of the three lookups above (HS-KR16 pattern) ────────
|
|
430
|
+
// `tier` is resolved ONCE per call by `executeSplitResearch` from `deps.tier`
|
|
431
|
+
// and threaded down; absent it is `disk`, so every pre-existing caller reads
|
|
432
|
+
// exactly as before. The neon branch reuses `fetchSpecRecordViaConsole` — the
|
|
433
|
+
// same injected-`consoleFetch` reader promote-capture/promote-research use —
|
|
434
|
+
// so a test drives the whole flow with a recorder and never opens a socket.
|
|
435
|
+
// These exist because a cloud WRITE is pointless if the INPUTS cannot be
|
|
436
|
+
// found: the writes below all derive from the original research doc and the
|
|
437
|
+
// captures it names.
|
|
438
|
+
|
|
439
|
+
async function findResearchByIdViaTier(vaultRoot, researchId, { tier, consoleUrl, consoleFetch }) {
|
|
440
|
+
return readViaSelector({
|
|
441
|
+
allowDiskFallback: false,
|
|
442
|
+
tier,
|
|
443
|
+
diskRead: () => findResearchByIdLocal(vaultRoot, researchId),
|
|
444
|
+
neonRead: () => fetchSpecRecordViaConsole(consoleFetch, researchId),
|
|
445
|
+
routeLabel: 'GET /api/specs/[id]',
|
|
446
|
+
consoleUrl,
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
async function findMilestoneViaTier(vaultRoot, ref, { tier, consoleUrl, consoleFetch }) {
|
|
451
|
+
return readViaSelector({
|
|
452
|
+
allowDiskFallback: false,
|
|
453
|
+
tier,
|
|
454
|
+
diskRead: () => findMilestoneByIdOrBasename(vaultRoot, ref),
|
|
455
|
+
neonRead: () => fetchSpecRecordViaConsole(consoleFetch, ref),
|
|
456
|
+
routeLabel: 'GET /api/specs/[id]',
|
|
457
|
+
consoleUrl,
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
async function findCaptureViaTier(vaultRoot, ref, { tier, consoleUrl, consoleFetch }) {
|
|
462
|
+
return readViaSelector({
|
|
463
|
+
allowDiskFallback: false,
|
|
464
|
+
tier,
|
|
465
|
+
diskRead: () => findCaptureByIdOrBasename(vaultRoot, ref),
|
|
466
|
+
neonRead: () => fetchSpecRecordViaConsole(consoleFetch, ref),
|
|
467
|
+
routeLabel: 'GET /api/specs/[id]',
|
|
468
|
+
consoleUrl,
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
|
|
399
472
|
function collectOccupiedResearchIds(vaultRoot) {
|
|
400
473
|
const occupied = new Set();
|
|
401
474
|
const dir = join(vaultRoot, 'specs', 'research');
|
|
@@ -601,7 +674,20 @@ export async function executeSplitResearch(vaultRoot, args = {}, deps = {}) {
|
|
|
601
674
|
const validationError = validateInputShape(args);
|
|
602
675
|
if (validationError) return validationError;
|
|
603
676
|
|
|
604
|
-
|
|
677
|
+
// Tier + write mode are resolved ONCE, here, by the caller and threaded down
|
|
678
|
+
// — never re-resolved inside a helper. Both default to the disk behaviour so
|
|
679
|
+
// every pre-existing caller/test is byte-identical
|
|
680
|
+
// ([[HS-BYU4-CWPX-TASK-promotion-writes-go-through-the-console]]).
|
|
681
|
+
const tierDeps = {
|
|
682
|
+
tier: deps?.tier ?? 'disk',
|
|
683
|
+
consoleUrl: deps?.consoleUrl,
|
|
684
|
+
consoleFetch: deps?.consoleFetch,
|
|
685
|
+
};
|
|
686
|
+
const writeMode = resolveWriteModeFromDeps(deps);
|
|
687
|
+
const cloudDeps = { consoleFetch: deps?.consoleFetch, consoleUrl: deps?.consoleUrl };
|
|
688
|
+
const cloudCompleted = [];
|
|
689
|
+
|
|
690
|
+
const research = await findResearchByIdViaTier(vaultRoot, args.research_id, tierDeps);
|
|
605
691
|
if (!research) {
|
|
606
692
|
return {
|
|
607
693
|
error: 'RESEARCH_NOT_FOUND',
|
|
@@ -650,9 +736,16 @@ export async function executeSplitResearch(vaultRoot, args = {}, deps = {}) {
|
|
|
650
736
|
// Resolve all splits up-front, before any writes.
|
|
651
737
|
// One occupied set across the whole loop (owner ruling 2026-07-10) — children
|
|
652
738
|
// are not on disk yet, so a fresh vault walk per split could repeat a key.
|
|
653
|
-
|
|
739
|
+
//
|
|
740
|
+
// In CLOUD mode nothing is minted here at all: `collectOccupiedKeysEitherKind`
|
|
741
|
+
// is a vault SCAN, and in a file-less vault it returns an EMPTY set — a key
|
|
742
|
+
// drawn against it can collide with one the cloud vault already holds. The
|
|
743
|
+
// console mints every child's file key, family and filename.
|
|
744
|
+
const occupiedFileKeys =
|
|
745
|
+
writeMode === 'cloud' ? new Set() : collectOccupiedKeysEitherKind(vaultRoot); // union: file+family keys (HS-SJOT)
|
|
654
746
|
const childRecords = [];
|
|
655
747
|
const captureChildMap = new Map();
|
|
748
|
+
const captureRecords = new Map(); // basename -> record (raw reused by the write phase)
|
|
656
749
|
|
|
657
750
|
for (let i = 0; i < args.splits.length; i++) {
|
|
658
751
|
const split = args.splits[i];
|
|
@@ -661,7 +754,7 @@ export async function executeSplitResearch(vaultRoot, args = {}, deps = {}) {
|
|
|
661
754
|
if (split.target_milestone === 'NEW') {
|
|
662
755
|
targetMilestone = { absent: true, newName: split.new_milestone_name.trim() };
|
|
663
756
|
} else {
|
|
664
|
-
const ms =
|
|
757
|
+
const ms = await findMilestoneViaTier(vaultRoot, split.target_milestone, tierDeps);
|
|
665
758
|
if (!ms) {
|
|
666
759
|
return {
|
|
667
760
|
error: 'MILESTONE_NOT_FOUND',
|
|
@@ -673,7 +766,8 @@ export async function executeSplitResearch(vaultRoot, args = {}, deps = {}) {
|
|
|
673
766
|
|
|
674
767
|
const childCaptureBasenames = [];
|
|
675
768
|
for (const captureRef of split.sources_captures) {
|
|
676
|
-
const cap =
|
|
769
|
+
const cap = await findCaptureViaTier(vaultRoot, captureRef, tierDeps);
|
|
770
|
+
if (cap) captureRecords.set(cap.basename, cap);
|
|
677
771
|
if (!cap) {
|
|
678
772
|
return {
|
|
679
773
|
error: 'CAPTURE_NOT_FOUND',
|
|
@@ -695,15 +789,18 @@ export async function executeSplitResearch(vaultRoot, args = {}, deps = {}) {
|
|
|
695
789
|
// original itself has no derivable family (should not occur for a real
|
|
696
790
|
// research doc).
|
|
697
791
|
const childFamilyKey =
|
|
698
|
-
|
|
792
|
+
writeMode === 'cloud'
|
|
793
|
+
? (originalFamilyKey ?? null)
|
|
794
|
+
: (originalFamilyKey ?? mintFamilyKey(vaultRoot, { randomUnit: deps?.randomUnit }));
|
|
699
795
|
// Per-file FILE KEY (owner ruling 2026-07-10) — filename + `file_id:` SSOT,
|
|
700
796
|
// and (V3) the anchor for `id:` itself
|
|
701
797
|
// ([[HS-ZU1Q-TASK-PNAS-flip-new-spec-minting-to-the-file-key]]).
|
|
702
|
-
const childFileKey =
|
|
703
|
-
|
|
704
|
-
|
|
798
|
+
const childFileKey =
|
|
799
|
+
writeMode === 'cloud' ? null : allocateFileKey(occupiedFileKeys, { randomUnit: deps?.randomUnit });
|
|
800
|
+
if (childFileKey) occupiedFileKeys.add(childFileKey);
|
|
801
|
+
const childId = childFileKey ? `HS-${childFileKey}` : null;
|
|
705
802
|
const slug = deriveSlug(split.name);
|
|
706
|
-
const basename = `${childId}-${childFamilyKey}-RES-${slug}
|
|
803
|
+
const basename = childFileKey ? `${childId}-${childFamilyKey}-RES-${slug}` : null;
|
|
707
804
|
|
|
708
805
|
childRecords.push({
|
|
709
806
|
id: childId,
|
|
@@ -716,9 +813,13 @@ export async function executeSplitResearch(vaultRoot, args = {}, deps = {}) {
|
|
|
716
813
|
bodySlice: split.body_slice,
|
|
717
814
|
});
|
|
718
815
|
|
|
816
|
+
// Hold the child RECORD, not its basename: in cloud mode the basename is
|
|
817
|
+
// still null here and only lands when that child's POST answers, so the
|
|
818
|
+
// capture→children map is read lazily at write time in both modes.
|
|
819
|
+
const childRecord = childRecords[childRecords.length - 1];
|
|
719
820
|
for (const capBasename of childCaptureBasenames) {
|
|
720
821
|
if (!captureChildMap.has(capBasename)) captureChildMap.set(capBasename, []);
|
|
721
|
-
captureChildMap.get(capBasename).push(
|
|
822
|
+
captureChildMap.get(capBasename).push(childRecord);
|
|
722
823
|
}
|
|
723
824
|
}
|
|
724
825
|
|
|
@@ -727,17 +828,16 @@ export async function executeSplitResearch(vaultRoot, args = {}, deps = {}) {
|
|
|
727
828
|
const today = now.slice(0, 10);
|
|
728
829
|
const author = 'split-research (agent)';
|
|
729
830
|
const writes = [];
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
basename: child.basename,
|
|
831
|
+
|
|
832
|
+
const buildChildContent = (child, siblingBasenames) =>
|
|
833
|
+
buildChildResearchContent({
|
|
834
|
+
id: child.id ?? CLOUD_PENDING_ID,
|
|
835
|
+
fileKey: child.fileKey ?? CLOUD_PENDING_KEY,
|
|
836
|
+
familyKey: child.familyKey ?? CLOUD_PENDING_KEY,
|
|
837
|
+
basename: child.basename ?? '',
|
|
738
838
|
name: child.name,
|
|
739
839
|
originalBasename: research.basename,
|
|
740
|
-
siblingBasenames
|
|
840
|
+
siblingBasenames,
|
|
741
841
|
targetMilestone: child.targetMilestone,
|
|
742
842
|
capturesBasenames: child.capturesBasenames,
|
|
743
843
|
bodySlice: child.bodySlice,
|
|
@@ -745,42 +845,186 @@ export async function executeSplitResearch(vaultRoot, args = {}, deps = {}) {
|
|
|
745
845
|
today,
|
|
746
846
|
author,
|
|
747
847
|
});
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
848
|
+
|
|
849
|
+
if (writeMode === 'cloud') {
|
|
850
|
+
// ── CREATE FIRST ────────────────────────────────────────────────────────
|
|
851
|
+
// Each child is POSTed with an EMPTY sibling list (a sibling's basename is
|
|
852
|
+
// minted by the console, so it cannot be known before that sibling's own
|
|
853
|
+
// POST answers), then re-PUT once every basename is in hand. The sibling
|
|
854
|
+
// cross-links live only in the "Split-from:" body callout, so the follow-up
|
|
855
|
+
// PUT replaces the BODY and keeps the frontmatter the console stored.
|
|
856
|
+
for (const child of childRecords) {
|
|
857
|
+
const content = buildChildContent(child, []);
|
|
858
|
+
child.unresolvedPlaceholderCount = findUnresolvedResearchPlaceholders(content);
|
|
859
|
+
const { frontmatter, body } = splitRawSpecForConsole(content);
|
|
860
|
+
const cleaned = stripServerMintedFields(frontmatter);
|
|
861
|
+
if (!child.familyKey) delete cleaned.family;
|
|
862
|
+
let created;
|
|
863
|
+
try {
|
|
864
|
+
created = await createSpecViaConsole({
|
|
865
|
+
...cloudDeps,
|
|
866
|
+
type: 'research',
|
|
867
|
+
name: child.name,
|
|
868
|
+
frontmatter: cleaned,
|
|
869
|
+
body,
|
|
870
|
+
actingAgent: deps?.actingAgent ?? null,
|
|
871
|
+
});
|
|
872
|
+
} catch (err) {
|
|
873
|
+
return cloudWriteFailureResult(err, {
|
|
874
|
+
step: { kind: 'create', type: 'research', name: child.name },
|
|
875
|
+
completed: cloudCompleted,
|
|
876
|
+
});
|
|
877
|
+
}
|
|
878
|
+
child.id = created.specId;
|
|
879
|
+
child.basename = created.basename;
|
|
880
|
+
child.relPath = created.relPath;
|
|
881
|
+
child.cloudFrontmatter = created.spec?.frontmatter ?? cleaned;
|
|
882
|
+
cloudCompleted.push({
|
|
883
|
+
kind: 'create',
|
|
884
|
+
type: 'research',
|
|
885
|
+
spec_id: created.specId,
|
|
886
|
+
basename: created.basename,
|
|
887
|
+
rel_path: created.relPath,
|
|
888
|
+
});
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
const cloudChildBasenames = childRecords.map((c) => c.basename);
|
|
892
|
+
|
|
893
|
+
// ── …LINK SECOND: sibling callouts, then the original, then the captures.
|
|
894
|
+
if (childRecords.length > 1) {
|
|
895
|
+
for (const child of childRecords) {
|
|
896
|
+
const rebuilt = buildChildContent(child, cloudChildBasenames);
|
|
897
|
+
const { body } = splitRawSpecForConsole(rebuilt);
|
|
898
|
+
try {
|
|
899
|
+
const res = await updateSpecViaConsole({
|
|
900
|
+
...cloudDeps,
|
|
901
|
+
specId: child.id,
|
|
902
|
+
frontmatter: child.cloudFrontmatter,
|
|
903
|
+
body,
|
|
904
|
+
});
|
|
905
|
+
cloudCompleted.push({
|
|
906
|
+
kind: 'update',
|
|
907
|
+
type: 'research.siblings',
|
|
908
|
+
spec_id: res.specId,
|
|
909
|
+
basename: res.basename ?? child.basename,
|
|
910
|
+
rel_path: res.relPath,
|
|
911
|
+
});
|
|
912
|
+
} catch (err) {
|
|
913
|
+
return cloudWriteFailureResult(err, {
|
|
914
|
+
step: { kind: 'update', type: 'research.siblings', basename: child.basename },
|
|
915
|
+
completed: cloudCompleted,
|
|
916
|
+
});
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
const originalUpdatedCloud = buildUpdatedOriginalContent(research.raw, {
|
|
922
|
+
childBasenames: cloudChildBasenames,
|
|
923
|
+
now,
|
|
924
|
+
today,
|
|
925
|
+
author,
|
|
753
926
|
});
|
|
754
|
-
|
|
927
|
+
{
|
|
928
|
+
const { frontmatter, body } = splitRawSpecForConsole(originalUpdatedCloud);
|
|
929
|
+
try {
|
|
930
|
+
const res = await updateSpecViaConsole({
|
|
931
|
+
...cloudDeps,
|
|
932
|
+
specId: originalId,
|
|
933
|
+
frontmatter,
|
|
934
|
+
body,
|
|
935
|
+
});
|
|
936
|
+
cloudCompleted.push({
|
|
937
|
+
kind: 'update',
|
|
938
|
+
type: 'research.original',
|
|
939
|
+
spec_id: res.specId,
|
|
940
|
+
basename: res.basename ?? research.basename,
|
|
941
|
+
rel_path: res.relPath,
|
|
942
|
+
});
|
|
943
|
+
} catch (err) {
|
|
944
|
+
return cloudWriteFailureResult(err, {
|
|
945
|
+
step: { kind: 'update', type: 'research.original', basename: research.basename },
|
|
946
|
+
completed: cloudCompleted,
|
|
947
|
+
});
|
|
948
|
+
}
|
|
949
|
+
}
|
|
755
950
|
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
951
|
+
for (const [capBasename, childList] of captureChildMap.entries()) {
|
|
952
|
+
const capRecord = captureRecords.get(capBasename);
|
|
953
|
+
if (!capRecord) continue;
|
|
954
|
+
const capUpdated = buildUpdatedCaptureContent(
|
|
955
|
+
capRecord.raw,
|
|
956
|
+
childList.map((c) => c.basename),
|
|
957
|
+
now,
|
|
958
|
+
);
|
|
959
|
+
if (capUpdated === capRecord.raw) continue;
|
|
960
|
+
const { frontmatter, body } = splitRawSpecForConsole(capUpdated);
|
|
961
|
+
try {
|
|
962
|
+
const res = await updateSpecViaConsole({
|
|
963
|
+
...cloudDeps,
|
|
964
|
+
specId: getScalarField(getFrontmatterBlock(capRecord.raw)?.block ?? '', 'id') ?? capBasename,
|
|
965
|
+
frontmatter,
|
|
966
|
+
body,
|
|
967
|
+
});
|
|
968
|
+
cloudCompleted.push({
|
|
969
|
+
kind: 'update',
|
|
970
|
+
type: 'capture',
|
|
971
|
+
spec_id: res.specId,
|
|
972
|
+
basename: res.basename ?? capBasename,
|
|
973
|
+
rel_path: res.relPath,
|
|
974
|
+
});
|
|
975
|
+
} catch (err) {
|
|
976
|
+
return cloudWriteFailureResult(err, {
|
|
977
|
+
step: { kind: 'update', type: 'capture', basename: capBasename },
|
|
978
|
+
completed: cloudCompleted,
|
|
979
|
+
});
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
} else {
|
|
983
|
+
const childBasenames = childRecords.map((c) => c.basename);
|
|
984
|
+
|
|
985
|
+
for (const child of childRecords) {
|
|
986
|
+
const content = buildChildContent(child, childBasenames);
|
|
987
|
+
child.unresolvedPlaceholderCount = findUnresolvedResearchPlaceholders(content);
|
|
988
|
+
writes.push({
|
|
989
|
+
kind: 'create',
|
|
990
|
+
absPath: join(vaultRoot, 'specs', 'research', `${child.basename}.md`),
|
|
991
|
+
content,
|
|
992
|
+
});
|
|
993
|
+
}
|
|
767
994
|
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
995
|
+
const originalUpdated = buildUpdatedOriginalContent(research.raw, {
|
|
996
|
+
childBasenames,
|
|
997
|
+
now,
|
|
998
|
+
today,
|
|
999
|
+
author,
|
|
1000
|
+
});
|
|
1001
|
+
writes.push({
|
|
1002
|
+
kind: 'update',
|
|
1003
|
+
absPath: research.absPath,
|
|
1004
|
+
content: originalUpdated,
|
|
1005
|
+
});
|
|
1006
|
+
|
|
1007
|
+
for (const [capBasename, childList] of captureChildMap.entries()) {
|
|
1008
|
+
const capAbs = join(vaultRoot, 'specs', 'captures', `${capBasename}.md`);
|
|
1009
|
+
const capRaw = readFileSync(capAbs, 'utf8');
|
|
1010
|
+
const capUpdated = buildUpdatedCaptureContent(
|
|
1011
|
+
capRaw,
|
|
1012
|
+
childList.map((c) => c.basename),
|
|
1013
|
+
now,
|
|
1014
|
+
);
|
|
1015
|
+
if (capUpdated !== capRaw) {
|
|
1016
|
+
writes.push({ kind: 'update', absPath: capAbs, content: capUpdated });
|
|
1017
|
+
}
|
|
774
1018
|
}
|
|
775
|
-
}
|
|
776
1019
|
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
1020
|
+
const commitResult = commitWrites(writes, vaultRoot);
|
|
1021
|
+
if (!commitResult.ok) {
|
|
1022
|
+
return {
|
|
1023
|
+
error: 'WRITE_FAILED',
|
|
1024
|
+
message: commitResult.error?.message ?? 'unknown write failure',
|
|
1025
|
+
rollback_error: commitResult.error?.rollbackError,
|
|
1026
|
+
};
|
|
1027
|
+
}
|
|
784
1028
|
}
|
|
785
1029
|
|
|
786
1030
|
const anyChildStillStub = childRecords.some((c) => c.unresolvedPlaceholderCount > 0);
|
|
@@ -790,10 +1034,11 @@ export async function executeSplitResearch(vaultRoot, args = {}, deps = {}) {
|
|
|
790
1034
|
archive_basename: research.basename,
|
|
791
1035
|
archive_path: research.relPath,
|
|
792
1036
|
archive_status_after: getScalarField(fm.block, 'status') ?? 'open',
|
|
1037
|
+
...(writeMode === 'cloud' ? { write_mode: 'cloud' } : {}),
|
|
793
1038
|
children: childRecords.map((c) => ({
|
|
794
1039
|
id: c.id,
|
|
795
1040
|
basename: c.basename,
|
|
796
|
-
path: join('specs', 'research', `${c.basename}.md`),
|
|
1041
|
+
path: c.relPath ?? join('specs', 'research', `${c.basename}.md`),
|
|
797
1042
|
target_milestone: c.targetMilestone.absent ? 'NEW' : c.targetMilestone.basename,
|
|
798
1043
|
sources_captures: c.capturesBasenames,
|
|
799
1044
|
// Same placeholder count RESEARCH_STILL_STUB would later refuse
|
|
@@ -802,7 +1047,10 @@ export async function executeSplitResearch(vaultRoot, args = {}, deps = {}) {
|
|
|
802
1047
|
research_still_stub: c.unresolvedPlaceholderCount > 0,
|
|
803
1048
|
unresolved_placeholder_count: c.unresolvedPlaceholderCount,
|
|
804
1049
|
})),
|
|
805
|
-
writes:
|
|
1050
|
+
writes:
|
|
1051
|
+
writeMode === 'cloud'
|
|
1052
|
+
? cloudCompleted.map((w) => ({ kind: w.kind, path: w.rel_path ?? null, spec_id: w.spec_id }))
|
|
1053
|
+
: writes.map((w) => ({ kind: w.kind, path: w.absPath })),
|
|
806
1054
|
// No fallback warning: every child unconditionally inherits the
|
|
807
1055
|
// original's family key (V3 shared-surname model — see the block comment
|
|
808
1056
|
// above inheritedFamilyKey).
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// JSON-RPC writers for the stdio transport — the only place the MCP server
|
|
2
|
+
// writes to stdout. Extracted from cli.mjs under
|
|
3
|
+
// [[HS-BLNF-6W61-DIAG-cli-mjs-has-grown-into-a-13-665-line]] (no behavior change).
|
|
4
|
+
|
|
5
|
+
export function sendResponse(id, result) {
|
|
6
|
+
const response = { jsonrpc: '2.0', id, result };
|
|
7
|
+
process.stdout.write(JSON.stringify(response) + '\n');
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function sendError(id, code, message, data) {
|
|
11
|
+
const response = {
|
|
12
|
+
jsonrpc: '2.0',
|
|
13
|
+
id,
|
|
14
|
+
error: { code, message, ...(data && { data }) },
|
|
15
|
+
};
|
|
16
|
+
process.stdout.write(JSON.stringify(response) + '\n');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function sendNotification(method, params) {
|
|
20
|
+
const notification = { jsonrpc: '2.0', method, params };
|
|
21
|
+
process.stdout.write(JSON.stringify(notification) + '\n');
|
|
22
|
+
}
|