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
|
@@ -46,16 +46,19 @@
|
|
|
46
46
|
* import) so tests can stub scripts/lib/dev-mode.mjs and prove the verdict
|
|
47
47
|
* comes from `resolveDevMode` itself, not from string-matching this file.
|
|
48
48
|
*/
|
|
49
|
-
import {
|
|
50
|
-
import {
|
|
51
|
-
import {
|
|
52
|
-
|
|
53
|
-
|
|
49
|
+
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
50
|
+
import { execFileSync } from 'node:child_process';
|
|
51
|
+
import { existsSync } from 'node:fs';
|
|
52
|
+
import { join } from 'node:path';
|
|
53
|
+
import { importDevModeCore } from './dev-mode-core.mjs';
|
|
54
54
|
// THIS PACKAGE's own root (vendor/heyspex-mcp/read-path.mjs -> two levels
|
|
55
55
|
// up) — repo root in the monorepo, `node_modules/heyspex` in a packed
|
|
56
56
|
// install. Used to locate this package's OWN sibling code (dev-mode.mjs);
|
|
57
|
-
// see the module doc above for why this is never the vault root.
|
|
58
|
-
|
|
57
|
+
// see the module doc above for why this is never the vault root. Derived in
|
|
58
|
+
// ONE place (package-root.mjs) so a bundler bakes one build-machine path
|
|
59
|
+
// into the console's remote-MCP chunk instead of one per module.
|
|
60
|
+
import { PACKAGE_ROOT } from './package-root.mjs';
|
|
61
|
+
import { getActiveConfig } from './vault-resolve.mjs';
|
|
59
62
|
|
|
60
63
|
/**
|
|
61
64
|
* Resolve the VAULT root via the same shared 5-tier resolver every other MCP
|
|
@@ -78,8 +81,11 @@ export function resolveVaultRoot() {
|
|
|
78
81
|
|
|
79
82
|
/** Real loader: dynamic-imports the shared dev-mode core from THIS PACKAGE's own root. */
|
|
80
83
|
export async function loadRealDevMode() {
|
|
81
|
-
|
|
82
|
-
|
|
84
|
+
// Path candidate first (stdio / packed installs), then the static
|
|
85
|
+
// specifier a bundler can trace — see dev-mode-core.mjs. In a bundled
|
|
86
|
+
// deployment `import.meta.url` no longer points at a real checkout, so the
|
|
87
|
+
// path-built URL misses and the fallback is what actually loads.
|
|
88
|
+
return importDevModeCore([join(PACKAGE_ROOT, 'scripts', 'lib', 'dev-mode.mjs')]);
|
|
83
89
|
}
|
|
84
90
|
|
|
85
91
|
/**
|
|
@@ -94,6 +100,27 @@ export class ConsoleUnreachableCloudModeError extends Error {
|
|
|
94
100
|
}
|
|
95
101
|
}
|
|
96
102
|
|
|
103
|
+
/**
|
|
104
|
+
* `{ degraded:true, source:'cloud', warning }` when the resolved vault is a
|
|
105
|
+
* satellite binding whose vault clone is absent on this machine (marker with
|
|
106
|
+
* vault_path, no clone), else null. Same record shape as the outage fallback.
|
|
107
|
+
*/
|
|
108
|
+
export function vaultNotClonedInfo() {
|
|
109
|
+
try {
|
|
110
|
+
const cfg = getActiveConfig();
|
|
111
|
+
if (cfg && cfg.vaultCloned === false) {
|
|
112
|
+
return {
|
|
113
|
+
degraded: true,
|
|
114
|
+
source: 'cloud',
|
|
115
|
+
warning: `vault ${cfg.vaultId} is bound by this repo's heyspex.yaml but is not cloned at ${cfg.vaultPath ?? 'its vault_path'} on this machine; answered from the cloud (cloud-only mode). Clone the vault repo there for local reads.`,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
} catch {
|
|
119
|
+
/* resolver failure: not a clone problem */
|
|
120
|
+
}
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
|
|
97
124
|
/**
|
|
98
125
|
* resolveReadTier({ env, homeDir, loadDevMode? }) -> "disk" | "neon"
|
|
99
126
|
*
|
|
@@ -112,23 +139,172 @@ export async function resolveReadTier({ env, homeDir, loadDevMode = loadRealDevM
|
|
|
112
139
|
}
|
|
113
140
|
|
|
114
141
|
/**
|
|
115
|
-
*
|
|
142
|
+
* resolveVaultWriteMode({ env, homeDir, loadDevMode? }) -> "local" | "cloud"
|
|
143
|
+
*
|
|
144
|
+
* The write-side twin of {@link resolveReadTier}: whatever
|
|
145
|
+
* `resolveDevMode(...).vaultWriteMode` says — `local` only for dev mode with
|
|
146
|
+
* local reliance, `cloud` for prod and for dev with cloud reliance. A
|
|
147
|
+
* create-flow that writes the vault directly (save_research) consults THIS
|
|
148
|
+
* before touching disk, so a cloud-reliant session never leaves a file the
|
|
149
|
+
* console's index has never heard of (the split-brain a disk write in cloud
|
|
150
|
+
* mode produces). Same injectable loader as the read tier.
|
|
151
|
+
*/
|
|
152
|
+
export async function resolveVaultWriteMode({ env, homeDir, loadDevMode = loadRealDevMode } = {}) {
|
|
153
|
+
const mod = await loadDevMode();
|
|
154
|
+
const resolved = mod.resolveDevMode({ env, homeDir });
|
|
155
|
+
return resolved.vaultWriteMode === 'local' ? 'local' : 'cloud';
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* A console read that failed, carrying the transport verdict so
|
|
160
|
+
* {@link readViaSelector} can tell an OUTAGE (status 0, 5xx, timeout/network)
|
|
161
|
+
* from an ANSWER (401/403/404/409). Thrown by the `neonRead` closures; build
|
|
162
|
+
* one from a consoleFetch result with {@link consoleReadErrorFrom}.
|
|
163
|
+
*/
|
|
164
|
+
export class ConsoleReadError extends Error {
|
|
165
|
+
constructor(message, { status, errorKind, route } = {}) {
|
|
166
|
+
super(message);
|
|
167
|
+
this.name = 'ConsoleReadError';
|
|
168
|
+
this.status = status;
|
|
169
|
+
this.errorKind = errorKind;
|
|
170
|
+
this.route = route;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** Build a {@link ConsoleReadError} from a consoleFetch result `{status, errorKind}`. */
|
|
175
|
+
export function consoleReadErrorFrom(res, message, route) {
|
|
176
|
+
const e = new ConsoleReadError(message, { status: res?.status, errorKind: res?.errorKind, route });
|
|
177
|
+
if (res?.authProblem || res?.refusal) e.authProblem = true;
|
|
178
|
+
return e;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* True when `err` says the console was unreachable or broken (status 0, any
|
|
183
|
+
* 5xx, errorKind timeout/abort/network). False for answered 4xx verdicts
|
|
184
|
+
* (401/403/404/409) and for anything unclassified.
|
|
185
|
+
*/
|
|
186
|
+
export function isOutageClass(err) {
|
|
187
|
+
if (!err || typeof err !== 'object') return false;
|
|
188
|
+
if (err.authProblem) return false; // sign-in / scope refusals are not outages
|
|
189
|
+
const { status, errorKind } = err;
|
|
190
|
+
if (errorKind === 'timeout' || errorKind === 'abort' || errorKind === 'network') return true;
|
|
191
|
+
if (typeof status === 'number' && (status === 0 || status >= 500)) return true;
|
|
192
|
+
return false;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Per-call collector so callers whose reader returns a non-object (Set, array,
|
|
196
|
+
// null) can still learn that the answer came from the local clone.
|
|
197
|
+
const degradationScope = new AsyncLocalStorage();
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Run `fn`, collecting a {@link readViaSelector} degradation record for every
|
|
201
|
+
* disk-fallback read made inside it. Resolves `{ value, degraded }` where
|
|
202
|
+
* `degraded` is null or `{ degraded:true, source:'disk', cloud_error, warning }`.
|
|
203
|
+
*/
|
|
204
|
+
export async function withDegradationScope(fn) {
|
|
205
|
+
const records = [];
|
|
206
|
+
const value = await degradationScope.run(records, fn);
|
|
207
|
+
return { value, degraded: records[0] ?? null };
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function lastCommitIso(root) {
|
|
211
|
+
try {
|
|
212
|
+
const out = execFileSync('git', ['-C', root, 'log', '-1', '--format=%cI'], {
|
|
213
|
+
encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'], timeout: 3000,
|
|
214
|
+
}).trim();
|
|
215
|
+
return out || null;
|
|
216
|
+
} catch {
|
|
217
|
+
return null;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Stamp a degradation record onto a reader's result. Plain objects get the
|
|
223
|
+
* fields spread in; an MCP tool result (`{content:[{type:'text',text:<json>}]}`)
|
|
224
|
+
* gets them merged into the JSON payload inside `text` (and spread on the
|
|
225
|
+
* envelope too); any other shape (Set/array/null/primitive) is returned
|
|
226
|
+
* unchanged — it rides the {@link withDegradationScope} collector instead.
|
|
227
|
+
*/
|
|
228
|
+
export function mergeDegradation(result, info) {
|
|
229
|
+
if (!isPlainObject(result) || !info) return result;
|
|
230
|
+
const first = Array.isArray(result.content) ? result.content[0] : null;
|
|
231
|
+
if (first && first.type === 'text' && typeof first.text === 'string') {
|
|
232
|
+
try {
|
|
233
|
+
const parsed = JSON.parse(first.text);
|
|
234
|
+
if (isPlainObject(parsed)) {
|
|
235
|
+
return { ...result, ...info, content: [{ ...first, text: JSON.stringify({ ...parsed, ...info }, null, 2) }, ...result.content.slice(1)] };
|
|
236
|
+
}
|
|
237
|
+
} catch {
|
|
238
|
+
/* not JSON — fall through to the envelope spread */
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
return { ...result, ...info };
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function isPlainObject(v) {
|
|
245
|
+
return v !== null && typeof v === 'object' && !Array.isArray(v) && Object.getPrototypeOf(v) === Object.prototype;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* readViaSelector({ tier, diskRead, neonRead, routeLabel, consoleUrl, vaultRoot?, allowDiskFallback? })
|
|
116
250
|
*
|
|
117
251
|
* Runs the read matching `tier`:
|
|
118
252
|
* - "disk": calls `diskRead()` (`neonRead` is never invoked).
|
|
119
|
-
* - "neon": calls `neonRead()`. On failure
|
|
120
|
-
*
|
|
121
|
-
*
|
|
122
|
-
*
|
|
253
|
+
* - "neon": calls `neonRead()`. On failure:
|
|
254
|
+
* * DEGRADE ("degrade, don't die"): when `diskRead` exists, the failure
|
|
255
|
+
* is outage-class ({@link isOutageClass}), a local clone exists
|
|
256
|
+
* (`<vaultRoot>/specs`), and HEYSPEX_NO_DISK_FALLBACK !== '1', answer
|
|
257
|
+
* from `diskRead()`. A plain-object result is returned spread with
|
|
258
|
+
* `degraded: true, source: 'disk', cloud_error, warning`; any other
|
|
259
|
+
* shape (Set/array/null) is returned unchanged and the same record is
|
|
260
|
+
* pushed to the active {@link withDegradationScope} collector.
|
|
261
|
+
* * otherwise wraps the error into {@link ConsoleUnreachableCloudModeError}
|
|
262
|
+
* naming `consoleUrl` and `routeLabel` (4xx, no clone, no diskRead,
|
|
263
|
+
* opt-out env, or `allowDiskFallback: false` for write preconditions).
|
|
123
264
|
*/
|
|
124
|
-
export async function readViaSelector({ tier, diskRead, neonRead, routeLabel, consoleUrl }) {
|
|
265
|
+
export async function readViaSelector({ tier, diskRead, neonRead, routeLabel, consoleUrl, vaultRoot, allowDiskFallback = true }) {
|
|
266
|
+
// A satellite whose marker names a vault that is not cloned here is
|
|
267
|
+
// CLOUD-ONLY: a disk read would silently answer empty. Serve the cloud read
|
|
268
|
+
// (when this read has one) and declare it - never a silent empty answer.
|
|
269
|
+
const uncloned = vaultNotClonedInfo();
|
|
270
|
+
if (uncloned && typeof neonRead === 'function') {
|
|
271
|
+
const result = await neonRead();
|
|
272
|
+
const store = degradationScope.getStore();
|
|
273
|
+
if (store) store.push(uncloned);
|
|
274
|
+
return mergeDegradation(result, uncloned);
|
|
275
|
+
}
|
|
125
276
|
if (tier === 'disk') {
|
|
277
|
+
if (uncloned) {
|
|
278
|
+
throw new ConsoleUnreachableCloudModeError(
|
|
279
|
+
`VAULT_NOT_CLONED_LOCALLY: route "${routeLabel}" has no cloud read and ${uncloned.warning}`,
|
|
280
|
+
);
|
|
281
|
+
}
|
|
126
282
|
return diskRead();
|
|
127
283
|
}
|
|
128
284
|
if (tier === 'neon') {
|
|
129
285
|
try {
|
|
130
286
|
return await neonRead();
|
|
131
287
|
} catch (err) {
|
|
288
|
+
const root = vaultRoot ?? resolveVaultRoot();
|
|
289
|
+
if (
|
|
290
|
+
allowDiskFallback &&
|
|
291
|
+
typeof diskRead === 'function' &&
|
|
292
|
+
isOutageClass(err) &&
|
|
293
|
+
process.env.HEYSPEX_NO_DISK_FALLBACK !== '1' &&
|
|
294
|
+
root && existsSync(join(root, 'specs'))
|
|
295
|
+
) {
|
|
296
|
+
const result = await diskRead();
|
|
297
|
+
const kind = err.status ? `HTTP ${err.status}` : (err.errorKind ?? 'unreachable');
|
|
298
|
+
const info = {
|
|
299
|
+
degraded: true,
|
|
300
|
+
source: 'disk',
|
|
301
|
+
cloud_error: String(err?.message ?? err),
|
|
302
|
+
warning: `Console ${consoleUrl} failed (${kind}); answered from the local clone at ${root} (last commit ${lastCommitIso(root)}). Writes still require the console.`,
|
|
303
|
+
};
|
|
304
|
+
const store = degradationScope.getStore();
|
|
305
|
+
if (store) store.push(info);
|
|
306
|
+
return mergeDegradation(result, info);
|
|
307
|
+
}
|
|
132
308
|
throw new ConsoleUnreachableCloudModeError(
|
|
133
309
|
`CONSOLE_UNREACHABLE_CLOUD_MODE: route "${routeLabel}" failed against console ${consoleUrl} — ${String(err?.message ?? err)}`,
|
|
134
310
|
);
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
// The read-back receipt envelope for `hs` verbs that live in plain .mjs under
|
|
2
|
+
// vendor/heyspex-mcp/ and so cannot import the TypeScript receipt
|
|
3
|
+
// (spex-console/src/lib/cli/receipt.ts, [[HS-STCY-WW9R-TASK-add-a-shared-receipt-that-reads-back]]).
|
|
4
|
+
// Same envelope, same words: `{ local, cloud, verified, agree, diffs, changed,
|
|
5
|
+
// failed?, retry? }`, a two-column "local (this machine) | cloud (console)"
|
|
6
|
+
// table and one "Result:" line with the same anchors (verified / unverified /
|
|
7
|
+
// disagree / failed). Only the side fields differ per verb, so a verb passes a
|
|
8
|
+
// row table naming which local field and which cloud field each row shows
|
|
9
|
+
// and whether the row must agree. `hs login` is the first user
|
|
10
|
+
// ([[HS-CRNE-WW9R-TASK-give-hs-install-and-hs-login-the]], epic
|
|
11
|
+
// [[HS-L6HW-WW9R-EPIC-one-http-transport-and-measured]] invariant 6).
|
|
12
|
+
//
|
|
13
|
+
// Pure: no IO, never throws.
|
|
14
|
+
|
|
15
|
+
/** `EXIT.ACCOUNT_MISMATCH` in spex-console/src/lib/cli/control-shared.ts — the console confirmed another account. */
|
|
16
|
+
export const EXIT_ACCOUNT_MISMATCH = 20;
|
|
17
|
+
/** `EXIT.RECEIPT_DISAGREE` in control-shared.ts — both sides were read back and disagree. */
|
|
18
|
+
export const EXIT_RECEIPT_DISAGREE = 21;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @typedef {{ read: boolean, reason?: string, [field: string]: unknown }} ReceiptSide
|
|
22
|
+
* @typedef {{ label: string, local?: string, cloud?: string, compare?: boolean }} ReceiptRow
|
|
23
|
+
* One table row: the local side's field, the cloud side's field, and
|
|
24
|
+
* whether the two must be equal for `agree` (a diff is named by `label`).
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
/** A side that was not read back, with the reason. */
|
|
28
|
+
export function unreadSide(reason) {
|
|
29
|
+
return { read: false, reason };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const META = new Set(['read', 'reason']);
|
|
33
|
+
|
|
34
|
+
function valueOf(side, field) {
|
|
35
|
+
if (!side || !field) return null;
|
|
36
|
+
const v = side[field];
|
|
37
|
+
return v === undefined ? null : v;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Build the receipt from the two read-backs.
|
|
42
|
+
* - `verified`: both sides were read back;
|
|
43
|
+
* - `diffs`: every `compare` row whose two values differ (only when verified);
|
|
44
|
+
* - `agree`: verified with no diffs;
|
|
45
|
+
* - `changed`: per side, every field that differs from the `before` read of
|
|
46
|
+
* that side (`[]` on an unchanged re-run; a side with no before read, or
|
|
47
|
+
* not read after, contributes nothing).
|
|
48
|
+
* @param {{ local: ReceiptSide | null, cloud: ReceiptSide | null, rows: ReceiptRow[],
|
|
49
|
+
* before?: { local?: ReceiptSide | null, cloud?: ReceiptSide | null },
|
|
50
|
+
* failed?: { side: 'local' | 'cloud', reason?: string }, retry?: string }} input
|
|
51
|
+
*/
|
|
52
|
+
export function buildSidesReceipt({ local, cloud, rows, before = {}, failed, retry }) {
|
|
53
|
+
const l = local ?? unreadSide('not read back');
|
|
54
|
+
const c = cloud ?? unreadSide('not read back');
|
|
55
|
+
if (failed?.reason) (failed.side === 'cloud' ? c : l).reason = failed.reason;
|
|
56
|
+
const verified = l.read === true && c.read === true;
|
|
57
|
+
|
|
58
|
+
const diffs = [];
|
|
59
|
+
if (verified) {
|
|
60
|
+
for (const row of rows) {
|
|
61
|
+
if (!row.compare) continue;
|
|
62
|
+
const lv = valueOf(l, row.local);
|
|
63
|
+
const cv = valueOf(c, row.cloud);
|
|
64
|
+
if (lv !== cv) diffs.push({ field: row.label, local: lv, cloud: cv });
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const changed = [];
|
|
69
|
+
for (const [name, after] of [['local', l], ['cloud', c]]) {
|
|
70
|
+
const prior = before[name];
|
|
71
|
+
if (!prior?.read || !after.read) continue;
|
|
72
|
+
const fields = new Set([...Object.keys(prior), ...Object.keys(after)].filter((f) => !META.has(f)));
|
|
73
|
+
for (const field of fields) {
|
|
74
|
+
const from = valueOf(prior, field);
|
|
75
|
+
const to = valueOf(after, field);
|
|
76
|
+
if (from !== to) changed.push({ side: name, field, from, to });
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
local: l,
|
|
82
|
+
cloud: c,
|
|
83
|
+
verified,
|
|
84
|
+
agree: verified && diffs.length === 0,
|
|
85
|
+
diffs,
|
|
86
|
+
changed,
|
|
87
|
+
...(failed ? { failed: failed.side } : {}),
|
|
88
|
+
...(retry ? { retry } : {}),
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** The one-word state a script or test can branch on (same as receipt.ts `receiptStatus`). */
|
|
93
|
+
export function sidesReceiptStatus(receipt) {
|
|
94
|
+
if (receipt.failed) return 'failed';
|
|
95
|
+
if (!receipt.verified) return 'unverified';
|
|
96
|
+
if (!receipt.agree) return 'disagree';
|
|
97
|
+
return 'verified';
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function cell(side, field) {
|
|
101
|
+
if (!field) return '';
|
|
102
|
+
if (!side.read) return '(not read)';
|
|
103
|
+
const v = valueOf(side, field);
|
|
104
|
+
if (v === null) return '(none)';
|
|
105
|
+
if (typeof v === 'boolean') return v ? 'yes' : 'no';
|
|
106
|
+
return String(v);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const show = (v) => (v === null || v === undefined ? '(none)' : String(v));
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* The text sink: the same two-column table and "Result:" line as receipt.ts
|
|
113
|
+
* `formatReceipt`, rows taken from `rows`.
|
|
114
|
+
* @param {ReturnType<typeof buildSidesReceipt>} receipt
|
|
115
|
+
* @param {ReceiptRow[]} rows
|
|
116
|
+
*/
|
|
117
|
+
export function formatSidesReceipt(receipt, rows) {
|
|
118
|
+
const table = [
|
|
119
|
+
['', 'local (this machine)', 'cloud (console)'],
|
|
120
|
+
...rows.map((r) => [r.label, cell(receipt.local, r.local), cell(receipt.cloud, r.cloud)]),
|
|
121
|
+
];
|
|
122
|
+
const w0 = Math.max(...table.map((r) => r[0].length));
|
|
123
|
+
const w1 = Math.max(...table.map((r) => r[1].length));
|
|
124
|
+
const lines = [
|
|
125
|
+
'Read back after the write:',
|
|
126
|
+
...table.map(([a, b, c]) => ` ${a.padEnd(w0)} ${b.padEnd(w1)} ${c}`.trimEnd()),
|
|
127
|
+
];
|
|
128
|
+
const status = sidesReceiptStatus(receipt);
|
|
129
|
+
const retry = receipt.retry ? ` Retry: \`${receipt.retry}\`` : '';
|
|
130
|
+
if (status === 'failed') {
|
|
131
|
+
const reason = receipt[receipt.failed].reason;
|
|
132
|
+
lines.push(`Result: failed -- the ${receipt.failed} half did not land${reason ? ` (${reason})` : ''}.${retry}`);
|
|
133
|
+
} else if (status === 'unverified') {
|
|
134
|
+
const unread = ['local', 'cloud'].filter((s) => !receipt[s].read);
|
|
135
|
+
const why = unread.map((s) => `${s}${receipt[s].reason ? `: ${receipt[s].reason}` : ''}`).join('; ');
|
|
136
|
+
lines.push(`Result: unverified -- the ${unread.join(' and ')} side was not read back (${why}).`);
|
|
137
|
+
} else if (status === 'disagree') {
|
|
138
|
+
lines.push(
|
|
139
|
+
`Result: the two sides disagree on ${receipt.diffs.map((d) => d.field).join(', ')}.${retry}`,
|
|
140
|
+
...receipt.diffs.map((d) => ` ${d.field}: local ${show(d.local)} | cloud ${show(d.cloud)}`),
|
|
141
|
+
);
|
|
142
|
+
} else {
|
|
143
|
+
lines.push('Result: verified -- both sides were read back and agree.');
|
|
144
|
+
}
|
|
145
|
+
return lines.join('\n');
|
|
146
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
// heyspex_start_recipe and heyspex_list_recipes handlers (recipe-tools.mjs holds the store). Extracted from cli.mjs under
|
|
2
|
+
// [[HS-BLNF-6W61-DIAG-cli-mjs-has-grown-into-a-13-665-line]] (no behavior change).
|
|
3
|
+
|
|
4
|
+
import { wrapDesignResult } from './tool-helpers.mjs';
|
|
5
|
+
import { executeListRecipes, executeStartRecipe } from './recipe-tools.mjs';
|
|
6
|
+
import { detectAgentIdentity } from './mcp-config.mjs';
|
|
7
|
+
import { consoleErrorMessage } from './console-fetch.mjs';
|
|
8
|
+
import { createCaptureSessionOnConsole, writeCaptureFocusVendor } from './capture-tools.mjs';
|
|
9
|
+
|
|
10
|
+
// heyspex_list_recipes — pure catalog scan wrapped for the tool surface.
|
|
11
|
+
export function handleListRecipes(vaultRoot, args) {
|
|
12
|
+
return wrapDesignResult(executeListRecipes(vaultRoot, args));
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// heyspex_start_recipe — resolve the recipe (pure) then, when the console is
|
|
16
|
+
// reachable, create + pin a capture session tied to it (mirrors
|
|
17
|
+
// handlePinCapture). The recipe start context is returned in every case so the
|
|
18
|
+
// flow can be walked even when the capture could not be created (offline).
|
|
19
|
+
export async function handleStartRecipe(vaultRoot, args) {
|
|
20
|
+
const resolved = executeStartRecipe(vaultRoot, args);
|
|
21
|
+
if (resolved.error) {
|
|
22
|
+
return {
|
|
23
|
+
isError: true,
|
|
24
|
+
content: [{ type: 'text', text: JSON.stringify(resolved, null, 2) }],
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
const recipe = resolved.recipe;
|
|
28
|
+
const identity = detectAgentIdentity();
|
|
29
|
+
|
|
30
|
+
const createResult = await createCaptureSessionOnConsole({
|
|
31
|
+
opened_by: 'agent',
|
|
32
|
+
opened_by_session: identity.sessionId,
|
|
33
|
+
mode: 'attended',
|
|
34
|
+
status: 'pending',
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
if (createResult.ok && createResult.session) {
|
|
38
|
+
const session = createResult.session;
|
|
39
|
+
const pin = await writeCaptureFocusVendor(vaultRoot, {
|
|
40
|
+
specId: session.id,
|
|
41
|
+
specPath: session.specPath || '',
|
|
42
|
+
reason: `Started workflow-recipe ${recipe.recipe_id} (${recipe.name})`,
|
|
43
|
+
claim: { claimed_by: identity.agent, claim_session: identity.sessionId },
|
|
44
|
+
});
|
|
45
|
+
if (!pin.pinned) {
|
|
46
|
+
// The session row WAS created and the recipe context is intact — both are
|
|
47
|
+
// returned so nothing is lost and no second session gets minted. But the
|
|
48
|
+
// message may not say "pinned" over a pin that never persisted
|
|
49
|
+
// ([[HS-IOJW-5YDH-DIAG]] sweep: report the result, not the request).
|
|
50
|
+
return {
|
|
51
|
+
isError: true,
|
|
52
|
+
content: [{
|
|
53
|
+
type: 'text',
|
|
54
|
+
text: JSON.stringify({
|
|
55
|
+
started: true,
|
|
56
|
+
pinned: false,
|
|
57
|
+
error: 'CAPTURE_PIN_NOT_WRITTEN',
|
|
58
|
+
source: 'database',
|
|
59
|
+
capture: { spec_id: session.id, spec_path: session.specPath || null },
|
|
60
|
+
pin,
|
|
61
|
+
recipe,
|
|
62
|
+
message:
|
|
63
|
+
`Capture session ${session.id} was created for workflow-recipe ${recipe.recipe_id}, but the ` +
|
|
64
|
+
`capture-on-deck pin was NOT written (${pin.reason}). A later /hs-capture with no id will ` +
|
|
65
|
+
'resolve to a DIFFERENT capture. Do not re-run heyspex_start_recipe (it would mint a second ' +
|
|
66
|
+
`session) — re-pin with heyspex_pin_capture({ spec_id: "${session.id}" }) once the console is healthy.`,
|
|
67
|
+
}, null, 2),
|
|
68
|
+
}],
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
return {
|
|
72
|
+
content: [{
|
|
73
|
+
type: 'text',
|
|
74
|
+
text: JSON.stringify({
|
|
75
|
+
started: true,
|
|
76
|
+
pinned: true,
|
|
77
|
+
pin,
|
|
78
|
+
source: 'database',
|
|
79
|
+
capture: { spec_id: session.id, spec_path: session.specPath || null },
|
|
80
|
+
recipe,
|
|
81
|
+
message: `Capture session ${session.id} created and pinned (${pin.tier}) for workflow-recipe ${recipe.recipe_id}. Walk the capture_sequence, then file the spec pre-filled from spec_template (recipe_id + tool_scope stamp provenance).`,
|
|
82
|
+
}, null, 2),
|
|
83
|
+
}],
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// The create failed → the recipe context is still fully returned so the flow
|
|
88
|
+
// can be walked / filed later. No divergent local capture file is minted
|
|
89
|
+
// (capture is online-only, [[heyspex-is-online-only-no-offline]]).
|
|
90
|
+
//
|
|
91
|
+
// HS-I6HT: this used to hardcode "HeySpex can't reach the server right now"
|
|
92
|
+
// for EVERY failure, so a sign-in failure printed an outage sentence next to
|
|
93
|
+
// a CONSOLE_NOT_SIGNED_IN code — two contradictory stories in one payload.
|
|
94
|
+
// `consoleErrorMessage` already writes the right sentence per class (sign-in,
|
|
95
|
+
// 401, wrong port, timeout, genuinely down); use it whenever it has one, and
|
|
96
|
+
// keep the old wording only as the last-resort fallback.
|
|
97
|
+
const answered =
|
|
98
|
+
typeof createResult.result?.body?.message === 'string'
|
|
99
|
+
? createResult.result.body.message
|
|
100
|
+
: null;
|
|
101
|
+
const whatHappened =
|
|
102
|
+
(createResult.result ? consoleErrorMessage(createResult.result, answered) : null)
|
|
103
|
+
|| "HeySpex can't reach the server right now.";
|
|
104
|
+
return {
|
|
105
|
+
content: [{
|
|
106
|
+
type: 'text',
|
|
107
|
+
text: JSON.stringify({
|
|
108
|
+
started: false,
|
|
109
|
+
status: 'deferred',
|
|
110
|
+
error: createResult.error || 'CONSOLE_UNREACHABLE',
|
|
111
|
+
recipe,
|
|
112
|
+
message:
|
|
113
|
+
`${whatHappened} The capture wasn't created, but the recipe's steps are returned below and nothing is lost. Fix the cause above, then call again to pin the capture.`,
|
|
114
|
+
}, null, 2),
|
|
115
|
+
}],
|
|
116
|
+
};
|
|
117
|
+
}
|