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
|
@@ -19,6 +19,15 @@
|
|
|
19
19
|
* `status:` — the status API owns coercion, rollup, and spec events ([[HS-DIAG-DOSH]]),
|
|
20
20
|
* and the reproduction gate lives inside it, so a clean record is what unlocks the
|
|
21
21
|
* close rather than this module deciding on its own.
|
|
22
|
+
*
|
|
23
|
+
* Cloud contract ([[HS-7UJZ-O86I-DIAG]]): a diagnose filed through the console in
|
|
24
|
+
* cloud mode exists ONLY as a Neon row — there is no `specs/diagnoses/*.md` for the
|
|
25
|
+
* disk scan to find. When the session's console reliance is `cloud` the diagnose is
|
|
26
|
+
* resolved and the verdict recorded THROUGH the console (`GET` + `PUT
|
|
27
|
+
* /api/specs/:id`, the same frontmatter fields + change-log row the disk path
|
|
28
|
+
* writes); in local reliance the disk scan runs first, exactly as before, and the
|
|
29
|
+
* console is consulted only when the scan finds nothing. The close still goes
|
|
30
|
+
* through the status PATCH either way.
|
|
22
31
|
*/
|
|
23
32
|
|
|
24
33
|
import { existsSync, readdirSync, readFileSync } from 'node:fs';
|
|
@@ -78,15 +87,7 @@ export const SPAWNED_WORK_FIELDS = ['spawned_epics', 'spawned_tasks'];
|
|
|
78
87
|
* ([[HS-B1ZT-QCP2-DIAG-heyspex-promote-research-cannot-resolve]]).
|
|
79
88
|
*/
|
|
80
89
|
export function resolveDiagnoseRef(vaultRoot, rawRef) {
|
|
81
|
-
const ref =
|
|
82
|
-
typeof rawRef === 'string'
|
|
83
|
-
? rawRef
|
|
84
|
-
.trim()
|
|
85
|
-
.replace(/^\[\[|\]\]$/g, '')
|
|
86
|
-
.replace(/^['"]|['"]$/g, '')
|
|
87
|
-
.replace(/\.md$/i, '')
|
|
88
|
-
.trim()
|
|
89
|
-
: '';
|
|
90
|
+
const ref = normalizeDiagnoseRef(rawRef);
|
|
90
91
|
if (!ref) return { error: 'INVALID_ARGS', message: 'spec_id is required' };
|
|
91
92
|
|
|
92
93
|
const dir = join(vaultRoot, 'specs', 'diagnoses');
|
|
@@ -99,6 +100,7 @@ export function resolveDiagnoseRef(vaultRoot, rawRef) {
|
|
|
99
100
|
const load = (file) => {
|
|
100
101
|
const absPath = join(dir, file);
|
|
101
102
|
return {
|
|
103
|
+
source: 'disk',
|
|
102
104
|
basename: file.replace(/\.md$/i, ''),
|
|
103
105
|
absPath,
|
|
104
106
|
relPath: join('specs', 'diagnoses', file),
|
|
@@ -146,6 +148,138 @@ export function resolveDiagnoseRef(vaultRoot, rawRef) {
|
|
|
146
148
|
return notFound;
|
|
147
149
|
}
|
|
148
150
|
|
|
151
|
+
/** Strip a spec reference to the bare basename / id: `[[x]]`, quotes, `.md`. */
|
|
152
|
+
function normalizeDiagnoseRef(rawRef) {
|
|
153
|
+
return typeof rawRef === 'string'
|
|
154
|
+
? rawRef
|
|
155
|
+
.trim()
|
|
156
|
+
.replace(/^\[\[|\]\]$/g, '')
|
|
157
|
+
.replace(/^['"]|['"]$/g, '')
|
|
158
|
+
.replace(/\.md$/i, '')
|
|
159
|
+
.trim()
|
|
160
|
+
: '';
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Resolve a diagnose reference through the console — `GET /api/specs/:id`, the
|
|
165
|
+
* same read `heyspex_submit_for_review` makes ([[HS-7UJZ-O86I-DIAG]]).
|
|
166
|
+
*
|
|
167
|
+
* The route accepts the frontmatter id, the basename, and (GET-only) a unique
|
|
168
|
+
* id prefix, and answers `404 ambiguous_id` with candidates when a prefix names
|
|
169
|
+
* more than one spec — mapped here onto the same error codes the disk resolver
|
|
170
|
+
* returns so a caller reads one contract. A transport failure or a non-404
|
|
171
|
+
* refusal is NOT "not found": it is reported as `CONSOLE_UNREACHABLE` so the
|
|
172
|
+
* caller can tell "no such diagnose" from "could not ask".
|
|
173
|
+
*/
|
|
174
|
+
export async function resolveDiagnoseViaConsole(consoleFetch, rawRef) {
|
|
175
|
+
const ref = normalizeDiagnoseRef(rawRef);
|
|
176
|
+
if (!ref) return { error: 'INVALID_ARGS', message: 'spec_id is required' };
|
|
177
|
+
const route = `GET /api/specs/${ref}`;
|
|
178
|
+
if (typeof consoleFetch !== 'function') {
|
|
179
|
+
return { error: 'CONSOLE_UNREACHABLE', message: `${route}: no console transport available` };
|
|
180
|
+
}
|
|
181
|
+
let res;
|
|
182
|
+
try {
|
|
183
|
+
res = await consoleFetch(`/api/specs/${encodeURIComponent(ref)}`);
|
|
184
|
+
} catch (err) {
|
|
185
|
+
return { error: 'CONSOLE_UNREACHABLE', message: `${route} failed: ${err?.message ?? err}` };
|
|
186
|
+
}
|
|
187
|
+
const notFound = {
|
|
188
|
+
error: 'DIAGNOSE_NOT_FOUND',
|
|
189
|
+
message: `diagnose ${rawRef} is not in the console's index either (${route} → 404)`,
|
|
190
|
+
status: 404,
|
|
191
|
+
};
|
|
192
|
+
if (!res?.ok) {
|
|
193
|
+
if (res?.status === 404) {
|
|
194
|
+
if (res.body?.error === 'ambiguous_id' && Array.isArray(res.body?.candidates)) {
|
|
195
|
+
return {
|
|
196
|
+
error: 'DIAGNOSE_ID_AMBIGUOUS',
|
|
197
|
+
message: `spec_id ${rawRef} is a prefix of ${res.body.candidates.length} specs in the console's index — pass the full basename instead`,
|
|
198
|
+
candidates: res.body.candidates,
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
return notFound;
|
|
202
|
+
}
|
|
203
|
+
return {
|
|
204
|
+
error: 'CONSOLE_UNREACHABLE',
|
|
205
|
+
message: `${route} returned ${res?.status ?? 0}${res?.error ? `: ${res.error}` : ''}`,
|
|
206
|
+
status: res?.status ?? 0,
|
|
207
|
+
console_response: res?.body ?? null,
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
const spec = res.body?.spec;
|
|
211
|
+
if (!spec || typeof spec !== 'object') return notFound;
|
|
212
|
+
const frontmatter =
|
|
213
|
+
spec.frontmatter && typeof spec.frontmatter === 'object' ? { ...spec.frontmatter } : {};
|
|
214
|
+
const relPath = typeof spec.relPath === 'string' ? spec.relPath : null;
|
|
215
|
+
const basename =
|
|
216
|
+
(typeof spec.basename === 'string' && spec.basename) ||
|
|
217
|
+
(relPath ? relPath.split('/').pop().replace(/\.md$/i, '') : null) ||
|
|
218
|
+
String(spec.id ?? frontmatter.id ?? ref);
|
|
219
|
+
return {
|
|
220
|
+
record: {
|
|
221
|
+
source: 'console',
|
|
222
|
+
basename,
|
|
223
|
+
specId: String(frontmatter.id ?? spec.id ?? basename),
|
|
224
|
+
relPath,
|
|
225
|
+
type: String(frontmatter.type ?? spec.type ?? ''),
|
|
226
|
+
status: String(frontmatter.status ?? spec.status ?? ''),
|
|
227
|
+
frontmatter,
|
|
228
|
+
body: typeof spec.body === 'string' ? spec.body : '',
|
|
229
|
+
},
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Pick the record to write the verdict on — disk or console ([[HS-7UJZ-O86I-DIAG]]).
|
|
235
|
+
*
|
|
236
|
+
* `cloud` reliance asks the console FIRST: that is where a cloud-filed diagnose
|
|
237
|
+
* lives, and the close gate in the status API reads the same index, so a disk
|
|
238
|
+
* write there would be a split brain the gate never sees. Only a console 404
|
|
239
|
+
* falls back to the local scan (an unpushed file in a vault clone). Any other
|
|
240
|
+
* reliance keeps the disk scan first — byte-identical to the pre-cloud path —
|
|
241
|
+
* and only asks the console when the scan finds nothing; a console that cannot
|
|
242
|
+
* be reached then leaves the disk answer (`DIAGNOSE_NOT_FOUND`) standing, with
|
|
243
|
+
* the console's answer attached.
|
|
244
|
+
*/
|
|
245
|
+
export async function resolveDiagnoseRecord({ vaultRoot, consoleFetch, rawRef, reliance = null }) {
|
|
246
|
+
if (reliance === 'cloud') {
|
|
247
|
+
const viaConsole = await resolveDiagnoseViaConsole(consoleFetch, rawRef);
|
|
248
|
+
if (viaConsole.error !== 'DIAGNOSE_NOT_FOUND') return viaConsole;
|
|
249
|
+
const local = resolveDiagnoseRef(vaultRoot, rawRef);
|
|
250
|
+
if (!local.error) return local;
|
|
251
|
+
return { ...viaConsole, message: `${viaConsole.message}; ${local.message}` };
|
|
252
|
+
}
|
|
253
|
+
const local = resolveDiagnoseRef(vaultRoot, rawRef);
|
|
254
|
+
if (local.error !== 'DIAGNOSE_NOT_FOUND') return local;
|
|
255
|
+
const viaConsole = await resolveDiagnoseViaConsole(consoleFetch, rawRef);
|
|
256
|
+
if (viaConsole.error === 'DIAGNOSE_NOT_FOUND' || viaConsole.error === 'CONSOLE_UNREACHABLE') {
|
|
257
|
+
return {
|
|
258
|
+
...local,
|
|
259
|
+
message: `${local.message}; ${viaConsole.message}`,
|
|
260
|
+
console: { error: viaConsole.error, status: viaConsole.status ?? null },
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
return viaConsole;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/** One frontmatter link value (string or list) as bare basenames. */
|
|
267
|
+
function refListFromFrontmatter(value) {
|
|
268
|
+
const entries = Array.isArray(value) ? value : typeof value === 'string' ? [value] : [];
|
|
269
|
+
const out = [];
|
|
270
|
+
for (const entry of entries) {
|
|
271
|
+
const ref = normalizeDiagnoseRef(typeof entry === 'string' ? entry : '');
|
|
272
|
+
if (ref) out.push(ref);
|
|
273
|
+
}
|
|
274
|
+
return out;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/** {@link spawnedWorkRefs} for parsed frontmatter (the console's `spec.frontmatter`). */
|
|
278
|
+
export function spawnedWorkRefsFromFrontmatter(frontmatter) {
|
|
279
|
+
if (!frontmatter || typeof frontmatter !== 'object') return [];
|
|
280
|
+
return SPAWNED_WORK_FIELDS.flatMap((field) => refListFromFrontmatter(frontmatter[field]));
|
|
281
|
+
}
|
|
282
|
+
|
|
149
283
|
/** Every spec this diagnose spawned — the link fields, never a parent edge. */
|
|
150
284
|
export function spawnedWorkRefs(block) {
|
|
151
285
|
const out = [];
|
|
@@ -181,6 +315,33 @@ export function buildVerifiedContent(raw, { result, evidence, by, now, today })
|
|
|
181
315
|
return `${fm.open}${block}${fm.close}${body}`;
|
|
182
316
|
}
|
|
183
317
|
|
|
318
|
+
function changeLogMessage(result, evidence) {
|
|
319
|
+
const verdict =
|
|
320
|
+
result === 'no_longer_reproduces'
|
|
321
|
+
? 'Reproduction re-run: NO LONGER REPRODUCES.'
|
|
322
|
+
: 'Reproduction re-run: STILL REPRODUCES.';
|
|
323
|
+
return `${verdict} ${evidence}`;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* The console twin of {@link buildVerifiedContent}: the `{ frontmatter, body }`
|
|
328
|
+
* pair `PUT /api/specs/:id` takes, carrying the same four verdict fields and the
|
|
329
|
+
* same change-log row. The fields REPLACE any prior verdict (object spread), as
|
|
330
|
+
* `setScalarField` does on disk. `updated:` is left to the console — `updateSpec`
|
|
331
|
+
* stamps it on every write.
|
|
332
|
+
*/
|
|
333
|
+
export function buildVerifiedConsoleWrite(record, { result, evidence, by, now, today }) {
|
|
334
|
+
const frontmatter = {
|
|
335
|
+
...record.frontmatter,
|
|
336
|
+
[REPRODUCTION_RERUN_FIELDS.at]: now,
|
|
337
|
+
[REPRODUCTION_RERUN_FIELDS.by]: by,
|
|
338
|
+
[REPRODUCTION_RERUN_FIELDS.result]: result,
|
|
339
|
+
[REPRODUCTION_RERUN_FIELDS.evidence]: evidence,
|
|
340
|
+
};
|
|
341
|
+
const body = appendChangeLogRow(record.body ?? '', today, by, changeLogMessage(result, evidence));
|
|
342
|
+
return { frontmatter, body };
|
|
343
|
+
}
|
|
344
|
+
|
|
184
345
|
/**
|
|
185
346
|
* @param {{
|
|
186
347
|
* vaultRoot: string,
|
|
@@ -188,7 +349,12 @@ export function buildVerifiedContent(raw, { result, evidence, by, now, today })
|
|
|
188
349
|
* args?: Record<string, unknown>,
|
|
189
350
|
* clock?: () => string,
|
|
190
351
|
* agent?: string,
|
|
352
|
+
* getReliance?: () => Promise<string|null>,
|
|
191
353
|
* }} ctx
|
|
354
|
+
*
|
|
355
|
+
* `getReliance` resolves the session's console reliance (`cloud` | `local` |
|
|
356
|
+
* null). It defaults to "unknown", which keeps the disk-first order — the
|
|
357
|
+
* handler injects the real dev-mode resolver.
|
|
192
358
|
*/
|
|
193
359
|
export async function executeVerifyReproduction({
|
|
194
360
|
vaultRoot,
|
|
@@ -196,6 +362,7 @@ export async function executeVerifyReproduction({
|
|
|
196
362
|
args = {},
|
|
197
363
|
clock = isoNow,
|
|
198
364
|
agent = 'agent',
|
|
365
|
+
getReliance = async () => null,
|
|
199
366
|
}) {
|
|
200
367
|
const result = typeof args.result === 'string' ? args.result.trim().toLowerCase() : '';
|
|
201
368
|
if (!REPRODUCTION_VERDICTS.includes(result)) {
|
|
@@ -214,18 +381,45 @@ export async function executeVerifyReproduction({
|
|
|
214
381
|
};
|
|
215
382
|
}
|
|
216
383
|
|
|
217
|
-
|
|
384
|
+
let reliance = null;
|
|
385
|
+
try {
|
|
386
|
+
reliance = await getReliance();
|
|
387
|
+
} catch {
|
|
388
|
+
reliance = null;
|
|
389
|
+
}
|
|
390
|
+
const resolved = await resolveDiagnoseRecord({
|
|
391
|
+
vaultRoot,
|
|
392
|
+
consoleFetch,
|
|
393
|
+
rawRef: args.spec_id,
|
|
394
|
+
reliance,
|
|
395
|
+
});
|
|
218
396
|
if (resolved.error) return resolved;
|
|
219
397
|
const diagnose = resolved.record;
|
|
398
|
+
const viaConsole = diagnose.source === 'console';
|
|
220
399
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
400
|
+
let fm = null;
|
|
401
|
+
let type;
|
|
402
|
+
let specId;
|
|
403
|
+
let statusBefore;
|
|
404
|
+
let spawned;
|
|
405
|
+
if (viaConsole) {
|
|
406
|
+
type = diagnose.type.trim().toLowerCase();
|
|
407
|
+
specId = diagnose.specId;
|
|
408
|
+
statusBefore = (diagnose.status || 'open').trim().toLowerCase();
|
|
409
|
+
spawned = spawnedWorkRefsFromFrontmatter(diagnose.frontmatter);
|
|
410
|
+
} else {
|
|
411
|
+
fm = getFrontmatterBlock(diagnose.raw);
|
|
412
|
+
if (!fm) {
|
|
413
|
+
return {
|
|
414
|
+
error: 'DIAGNOSE_FRONTMATTER_MISSING',
|
|
415
|
+
message: `${diagnose.basename} has no frontmatter — cannot record a reproduction re-run`,
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
type = (getScalarField(fm.block, 'type') ?? '').trim().toLowerCase();
|
|
419
|
+
specId = getScalarField(fm.block, 'id') ?? diagnose.basename;
|
|
420
|
+
statusBefore = (getScalarField(fm.block, 'status') ?? 'open').trim().toLowerCase();
|
|
421
|
+
spawned = spawnedWorkRefs(fm.block);
|
|
227
422
|
}
|
|
228
|
-
const type = (getScalarField(fm.block, 'type') ?? '').trim().toLowerCase();
|
|
229
423
|
if (type && type !== 'diagnose') {
|
|
230
424
|
return {
|
|
231
425
|
error: 'NOT_A_DIAGNOSE',
|
|
@@ -234,27 +428,47 @@ export async function executeVerifyReproduction({
|
|
|
234
428
|
};
|
|
235
429
|
}
|
|
236
430
|
|
|
237
|
-
const specId = getScalarField(fm.block, 'id') ?? diagnose.basename;
|
|
238
|
-
const statusBefore = (getScalarField(fm.block, 'status') ?? 'open').trim().toLowerCase();
|
|
239
|
-
const spawned = spawnedWorkRefs(fm.block);
|
|
240
|
-
|
|
241
431
|
const now = clock();
|
|
242
432
|
const today = todayDate();
|
|
243
433
|
const by = typeof args.by === 'string' && args.by.trim() ? args.by.trim() : agent;
|
|
244
434
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
}
|
|
435
|
+
if (viaConsole) {
|
|
436
|
+
// Neon-only diagnose ([[HS-7UJZ-O86I-DIAG]]): the verdict lands through the
|
|
437
|
+
// console's own spec write, `PUT /api/specs/:id` — the route every cloud-mode
|
|
438
|
+
// edit already goes through (cloud-vault-writes.mjs) — so the status API's
|
|
439
|
+
// close gate reads the verdict from the same index this call just wrote.
|
|
440
|
+
const write = buildVerifiedConsoleWrite(diagnose, { result, evidence, by, now, today });
|
|
441
|
+
const put = await consoleFetch(`/api/specs/${encodeURIComponent(specId)}`, {
|
|
442
|
+
method: 'PUT',
|
|
443
|
+
body: write,
|
|
444
|
+
});
|
|
445
|
+
if (!put?.ok) {
|
|
446
|
+
return {
|
|
447
|
+
error: 'WRITE_FAILED',
|
|
448
|
+
message:
|
|
449
|
+
`PUT /api/specs/${specId} returned ${put?.status ?? 0}` +
|
|
450
|
+
(put?.error ? `: ${put.error}` : '') +
|
|
451
|
+
' — the verdict was NOT recorded.',
|
|
452
|
+
status: put?.status ?? 0,
|
|
453
|
+
console_response: put?.body ?? null,
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
} else {
|
|
457
|
+
const content = buildVerifiedContent(diagnose.raw, { result, evidence, by, now, today });
|
|
458
|
+
const commit = commitWrites([{ kind: 'update', absPath: diagnose.absPath, content }]);
|
|
459
|
+
if (!commit.ok) {
|
|
460
|
+
return {
|
|
461
|
+
error: 'WRITE_FAILED',
|
|
462
|
+
message: commit.error?.message ?? 'unknown write failure',
|
|
463
|
+
};
|
|
464
|
+
}
|
|
252
465
|
}
|
|
253
466
|
|
|
254
467
|
const recorded = {
|
|
255
468
|
spec_id: specId,
|
|
256
469
|
basename: diagnose.basename,
|
|
257
|
-
path: diagnose.relPath,
|
|
470
|
+
path: diagnose.relPath ?? null,
|
|
471
|
+
recorded_via: viaConsole ? 'console' : 'disk',
|
|
258
472
|
result,
|
|
259
473
|
evidence,
|
|
260
474
|
by,
|
|
@@ -262,6 +476,7 @@ export async function executeVerifyReproduction({
|
|
|
262
476
|
spawned_work: spawned,
|
|
263
477
|
status_before: statusBefore,
|
|
264
478
|
};
|
|
479
|
+
const where = viaConsole ? "in the console's index" : 'on disk';
|
|
265
480
|
|
|
266
481
|
// A run that still reproduces records and closes nothing — the diagnose is left
|
|
267
482
|
// exactly as it was, and its Resolution row stays open.
|
|
@@ -293,6 +508,31 @@ export async function executeVerifyReproduction({
|
|
|
293
508
|
body: { status: 'closed', close_reason: `Reproduction re-run ${now}: no longer reproduces.` },
|
|
294
509
|
});
|
|
295
510
|
if (!res?.ok) {
|
|
511
|
+
const refusalReason = typeof res?.body?.error === 'string' ? res.body.error : null;
|
|
512
|
+
// `REPRODUCTION_NOT_REVERIFIED` is the ONE refusal this call cannot honestly
|
|
513
|
+
// report as a soft "verdict recorded, could not close". The gate refuses on a
|
|
514
|
+
// missing/unclean verdict — and the clean verdict was written to disk a few
|
|
515
|
+
// lines above, by this very call. So the gate did not see the file it is
|
|
516
|
+
// gating on, and nothing the caller does next clears the reason it was given.
|
|
517
|
+
// Reported softly, an agent read it literally and handed off a finished bug as
|
|
518
|
+
// unfinished ([[HS-SC2Q-E7TN-DIAG-closing-a-diagnose-takes-two-calls-the]]).
|
|
519
|
+
// Surfaced as an error, so it reads as the console-side fault it is.
|
|
520
|
+
if (refusalReason === 'REPRODUCTION_NOT_REVERIFIED') {
|
|
521
|
+
return {
|
|
522
|
+
...recorded,
|
|
523
|
+
error: 'CLOSE_GATE_STALE_VERDICT',
|
|
524
|
+
closed: false,
|
|
525
|
+
status: res?.status,
|
|
526
|
+
console_response: res?.body,
|
|
527
|
+
message:
|
|
528
|
+
`The clean verdict IS ${where} in \`${diagnose.basename}\`, but the console's close gate ` +
|
|
529
|
+
'refused with `REPRODUCTION_NOT_REVERIFIED` — it read a copy of the frontmatter from before ' +
|
|
530
|
+
'this write. This is a console-side staleness fault, not unfinished work: do NOT report the ' +
|
|
531
|
+
'diagnose as un-closed. Verify the console build carries the read-your-own-write heal in ' +
|
|
532
|
+
'`apply-status-transition.ts`; re-running this call closes the spec once the console has ' +
|
|
533
|
+
'caught up.',
|
|
534
|
+
};
|
|
535
|
+
}
|
|
296
536
|
return {
|
|
297
537
|
...recorded,
|
|
298
538
|
closed: false,
|
|
@@ -300,7 +540,7 @@ export async function executeVerifyReproduction({
|
|
|
300
540
|
status: res?.status,
|
|
301
541
|
console_response: res?.body,
|
|
302
542
|
message:
|
|
303
|
-
|
|
543
|
+
`Verdict recorded ${where}, but the status API did not close the spec. The record stands — ` +
|
|
304
544
|
're-run the close once the reason in `console_response` is cleared.',
|
|
305
545
|
};
|
|
306
546
|
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
// whoami.mjs — "which account does this machine's credential belong to?"
|
|
2
|
+
//
|
|
3
|
+
// ONE probe shared by `hs whoami` (cli-verbs.mjs `runWhoamiCli`) and the
|
|
4
|
+
// install/connect identity gate (spex-console/src/lib/cli/install-identity.ts),
|
|
5
|
+
// so the account a human is shown is exactly the account a later write would
|
|
6
|
+
// bind to ([[HS-HAQI-KODY-DIAG-hs-install-and-hs-connect-bind-a-new]]).
|
|
7
|
+
//
|
|
8
|
+
// Plain Node ESM, zero dependencies (spex-console imports it straight from
|
|
9
|
+
// TypeScript, same as folder-trust-store.mjs). Never prints, logs or returns
|
|
10
|
+
// token material: callers only ever see an identity verdict.
|
|
11
|
+
|
|
12
|
+
import { heyspexHome } from '../../scripts/lib/heyspex-home.mjs';
|
|
13
|
+
import { homedir } from 'node:os';
|
|
14
|
+
|
|
15
|
+
import { readBearer } from './credentials.mjs';
|
|
16
|
+
|
|
17
|
+
/** Console route that resolves an `hsmcp_` / `hscmp_` bearer to its account. */
|
|
18
|
+
export const SESSION_PATH = '/api/mcp/session';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* The bearer `hs whoami`, `hs connect` / `hs join` and the MCP server all send:
|
|
22
|
+
* `HEYSPEX_MCP_TOKEN` env first, then the top-level `mcp_token:` key of
|
|
23
|
+
* `~/.heyspex/credentials.yaml` — resolved by the ONE shared reader
|
|
24
|
+
* (`./credentials.mjs`, [[HS-HL0D-WW9R-TASK-share-one-bearer-reader-and-one-console]]),
|
|
25
|
+
* the same module `readPairedToken()` in spex-console/src/lib/cli/control-shared.ts
|
|
26
|
+
* delegates to. Never a first `hsmcp_`/`hscmp_` match anywhere in the file: a
|
|
27
|
+
* Companion token under `clerk:` is not this machine's MCP bearer, and probing
|
|
28
|
+
* it would show the human one account and bind another.
|
|
29
|
+
*
|
|
30
|
+
* @param {{ env?: NodeJS.ProcessEnv | Record<string, string | undefined>, homeDir?: string, readFile?: (p: string) => string }} [opts]
|
|
31
|
+
* @returns {string | null}
|
|
32
|
+
*/
|
|
33
|
+
export function readStoredBearer({ env = process.env, homeDir = heyspexHome(), readFile } = {}) {
|
|
34
|
+
return readBearer({ env, homeDir, readFile }).token;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Ask `consoleUrl` who `token` belongs to.
|
|
39
|
+
*
|
|
40
|
+
* Returns one of:
|
|
41
|
+
* { kind: 'no_credential' }
|
|
42
|
+
* { kind: 'identified', accountId, email, displayName, consoleUrl }
|
|
43
|
+
* { kind: 'rejected', status } — 401/403: token unknown or revoked
|
|
44
|
+
* { kind: 'unsupported', status } — 404/405: console predates this route
|
|
45
|
+
* { kind: 'unreachable', message } — network error / non-JSON / 5xx
|
|
46
|
+
*
|
|
47
|
+
* @param {{ consoleUrl?: string, token?: string | null, fetchImpl?: (url: string, init?: object) => Promise<any> }} [opts]
|
|
48
|
+
*/
|
|
49
|
+
export async function probeCredentialIdentity({ consoleUrl, token, fetchImpl = fetch } = {}) {
|
|
50
|
+
if (!token) return { kind: 'no_credential' };
|
|
51
|
+
const base = String(consoleUrl ?? '').trim().replace(/\/+$/, '');
|
|
52
|
+
let res;
|
|
53
|
+
try {
|
|
54
|
+
res = await fetchImpl(`${base}${SESSION_PATH}`, {
|
|
55
|
+
method: 'GET',
|
|
56
|
+
headers: { authorization: `Bearer ${token}` },
|
|
57
|
+
cache: 'no-store',
|
|
58
|
+
});
|
|
59
|
+
} catch (err) {
|
|
60
|
+
return { kind: 'unreachable', message: err?.message ?? String(err) };
|
|
61
|
+
}
|
|
62
|
+
if (res.status === 401 || res.status === 403) return { kind: 'rejected', status: res.status };
|
|
63
|
+
if (res.status === 404 || res.status === 405) return { kind: 'unsupported', status: res.status };
|
|
64
|
+
let body = null;
|
|
65
|
+
try {
|
|
66
|
+
body = await res.json();
|
|
67
|
+
} catch {
|
|
68
|
+
body = null;
|
|
69
|
+
}
|
|
70
|
+
if (!res.ok || !body || typeof body.userId !== 'string' || !body.userId.trim()) {
|
|
71
|
+
return { kind: 'unreachable', message: `console returned ${res.status}` };
|
|
72
|
+
}
|
|
73
|
+
return {
|
|
74
|
+
kind: 'identified',
|
|
75
|
+
accountId: body.userId.trim(),
|
|
76
|
+
email: typeof body.email === 'string' && body.email.trim() ? body.email.trim() : null,
|
|
77
|
+
displayName:
|
|
78
|
+
typeof body.displayName === 'string' && body.displayName.trim() ? body.displayName.trim() : null,
|
|
79
|
+
// How the console authenticated this caller (the route's `mode`, e.g. a
|
|
80
|
+
// bearer); the `hs login` receipt shows it ([[HS-CRNE-WW9R-TASK-give-hs-install-and-hs-login-the]]).
|
|
81
|
+
mode: typeof body.mode === 'string' && body.mode.trim() ? body.mode.trim() : null,
|
|
82
|
+
consoleUrl: base,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* `email (account id)`, or just the account id when the console has no email for it.
|
|
88
|
+
* @param {{ email?: string | null, accountId: string }} identity
|
|
89
|
+
*/
|
|
90
|
+
export function formatIdentity({ email, accountId }) {
|
|
91
|
+
return email ? `${email} (${accountId})` : accountId;
|
|
92
|
+
}
|