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
package/dist/cli/hs-check.mjs
CHANGED
|
@@ -1,11 +1,377 @@
|
|
|
1
1
|
import { createRequire as __hsCreateRequire } from 'node:module';
|
|
2
2
|
const require = __hsCreateRequire(import.meta.url);
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __esm = (fn, res) => function __init() {
|
|
5
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
// scripts/lib/heyspex-home.mjs
|
|
9
|
+
import { homedir } from "node:os";
|
|
10
|
+
function heyspexHome(env = process.env) {
|
|
11
|
+
const override = env?.HEYSPEX_HOME;
|
|
12
|
+
if (typeof override === "string" && override.trim().length > 0) {
|
|
13
|
+
return override.trim();
|
|
14
|
+
}
|
|
15
|
+
return homedir();
|
|
16
|
+
}
|
|
17
|
+
var init_heyspex_home = __esm({
|
|
18
|
+
"scripts/lib/heyspex-home.mjs"() {
|
|
19
|
+
"use strict";
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
// vendor/heyspex-mcp/credentials.mjs
|
|
24
|
+
import { mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
25
|
+
import os, { homedir as homedir2 } from "node:os";
|
|
26
|
+
import { dirname as dirname2, join as join2, resolve as resolve2 } from "node:path";
|
|
27
|
+
function credentialsFilePath(homeDir = heyspexHome()) {
|
|
28
|
+
return join2(homeDir, ".heyspex", "credentials.yaml");
|
|
29
|
+
}
|
|
30
|
+
function parseMcpToken(raw) {
|
|
31
|
+
if (typeof raw !== "string" || raw.length === 0) return null;
|
|
32
|
+
const token = raw.match(MCP_TOKEN_LINE_RE)?.[1]?.trim();
|
|
33
|
+
return token ? token : null;
|
|
34
|
+
}
|
|
35
|
+
function isValidAccountId(id) {
|
|
36
|
+
return typeof id === "string" && ACCOUNT_ID_RE.test(id);
|
|
37
|
+
}
|
|
38
|
+
function topLevelScalar(raw, key) {
|
|
39
|
+
const re = new RegExp(`^${key}:[ \\t]*["']?([^"'\\r\\n#]+)`, "m");
|
|
40
|
+
const value = raw.match(re)?.[1]?.trim();
|
|
41
|
+
return value ? value : null;
|
|
42
|
+
}
|
|
43
|
+
function decodeScalar(text) {
|
|
44
|
+
const t = text.trim();
|
|
45
|
+
if (t === "" || t === "null" || t === "~") return null;
|
|
46
|
+
if (t.startsWith('"')) {
|
|
47
|
+
try {
|
|
48
|
+
const v = JSON.parse(t);
|
|
49
|
+
return typeof v === "string" && v ? v : null;
|
|
50
|
+
} catch {
|
|
51
|
+
return t.replace(/^"|"$/g, "") || null;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
if (t.startsWith("'")) return t.replace(/^'|'$/g, "") || null;
|
|
55
|
+
const bare = t.replace(/\s+#.*$/, "").trim();
|
|
56
|
+
return bare || null;
|
|
57
|
+
}
|
|
58
|
+
function emptyAccount() {
|
|
59
|
+
return { mcp_token: null, console_url: null, email: null, paired_at: null };
|
|
60
|
+
}
|
|
61
|
+
function findAccountsBlock(lines) {
|
|
62
|
+
const start = lines.findIndex((l) => /^accounts:[ \t]*(#.*)?$/.test(l));
|
|
63
|
+
if (start < 0) return null;
|
|
64
|
+
let end = start + 1;
|
|
65
|
+
while (end < lines.length && (lines[end].trim() === "" || /^[ \t]/.test(lines[end]))) end += 1;
|
|
66
|
+
while (end > start + 1 && lines[end - 1].trim() === "") end -= 1;
|
|
67
|
+
return { start, end };
|
|
68
|
+
}
|
|
69
|
+
function parseAccountsBlock(lines) {
|
|
70
|
+
const accounts = {};
|
|
71
|
+
const block = findAccountsBlock(lines);
|
|
72
|
+
if (!block) return accounts;
|
|
73
|
+
let current = null;
|
|
74
|
+
for (const line of lines.slice(block.start + 1, block.end)) {
|
|
75
|
+
const header = line.match(/^ {2}([^\s:#][^:]*):[ \t]*(#.*)?$/);
|
|
76
|
+
if (header) {
|
|
77
|
+
const id = decodeScalar(header[1]);
|
|
78
|
+
current = isValidAccountId(id) ? id : null;
|
|
79
|
+
if (current) accounts[current] = emptyAccount();
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
const field = line.match(/^ {4}([a-z_]+):(.*)$/);
|
|
83
|
+
if (field && current && ACCOUNT_FIELDS.includes(field[1])) {
|
|
84
|
+
accounts[current][field[1]] = decodeScalar(field[2]);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return accounts;
|
|
88
|
+
}
|
|
89
|
+
function parseCredentialsStore(raw) {
|
|
90
|
+
const text = typeof raw === "string" ? raw : "";
|
|
91
|
+
if (topLevelScalar(text, "schema_version") !== String(CREDENTIALS_SCHEMA_VERSION)) {
|
|
92
|
+
const token = parseMcpToken(text);
|
|
93
|
+
if (!token) return { schemaVersion: 1, currentAccountId: null, accounts: {} };
|
|
94
|
+
return {
|
|
95
|
+
schemaVersion: 1,
|
|
96
|
+
currentAccountId: LEGACY_ACCOUNT_ID,
|
|
97
|
+
accounts: {
|
|
98
|
+
[LEGACY_ACCOUNT_ID]: { ...emptyAccount(), mcp_token: token, console_url: topLevelScalar(text, "console_url") }
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
const accounts = parseAccountsBlock(text.split(/\r?\n/));
|
|
103
|
+
const current = topLevelScalar(text, "current_account_id");
|
|
104
|
+
return {
|
|
105
|
+
schemaVersion: 2,
|
|
106
|
+
currentAccountId: current && accounts[current] ? current : null,
|
|
107
|
+
accounts
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
function resolveBearer({ envToken, credentialsText, accountId, legacyAccountId } = {}) {
|
|
111
|
+
const fromEnv = typeof envToken === "string" ? envToken.trim() : "";
|
|
112
|
+
if (fromEnv) return { token: fromEnv, source: "env" };
|
|
113
|
+
const store = parseCredentialsStore(credentialsText);
|
|
114
|
+
let entry;
|
|
115
|
+
if (typeof accountId === "string" && accountId.trim()) {
|
|
116
|
+
const wanted = accountId.trim();
|
|
117
|
+
entry = store.accounts[wanted];
|
|
118
|
+
if (!entry && store.schemaVersion === 1 && legacyAccountId && wanted === legacyAccountId) {
|
|
119
|
+
entry = store.accounts[LEGACY_ACCOUNT_ID];
|
|
120
|
+
}
|
|
121
|
+
} else {
|
|
122
|
+
entry = store.currentAccountId ? store.accounts[store.currentAccountId] : void 0;
|
|
123
|
+
}
|
|
124
|
+
if (entry?.mcp_token) return { token: entry.mcp_token, source: "credentials-file" };
|
|
125
|
+
return { token: null, source: null };
|
|
126
|
+
}
|
|
127
|
+
function readTextOrNull(read, path3) {
|
|
128
|
+
try {
|
|
129
|
+
const raw = read(path3);
|
|
130
|
+
return typeof raw === "string" ? raw : null;
|
|
131
|
+
} catch {
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
function readLegacyConfigAccountId(read, homeDir) {
|
|
136
|
+
const raw = readTextOrNull(read, join2(homeDir, ".heyspex", "config.yaml"));
|
|
137
|
+
const value = raw?.match(CONFIG_ACCOUNT_ID_RE)?.[1]?.trim() ?? "";
|
|
138
|
+
return value || null;
|
|
139
|
+
}
|
|
140
|
+
function readBearer({ env = process.env, homeDir = heyspexHome(), readFile, accountId } = {}) {
|
|
141
|
+
const read = readFile ?? ((p) => readFileSync(p, "utf8"));
|
|
142
|
+
const credentialsText = readTextOrNull(read, credentialsFilePath(homeDir));
|
|
143
|
+
const named = typeof accountId === "string" && accountId.trim() ? accountId.trim() : null;
|
|
144
|
+
const legacyAccountId = named && parseCredentialsStore(credentialsText).schemaVersion === 1 ? readLegacyConfigAccountId(read, homeDir) : null;
|
|
145
|
+
return resolveBearer({ envToken: env?.[MCP_TOKEN_ENV], credentialsText, accountId: named, legacyAccountId });
|
|
146
|
+
}
|
|
147
|
+
function readCurrentAccountId({ homeDir = heyspexHome(), readFile } = {}) {
|
|
148
|
+
const read = readFile ?? ((p) => readFileSync(p, "utf8"));
|
|
149
|
+
const store = parseCredentialsStore(readTextOrNull(read, credentialsFilePath(homeDir)));
|
|
150
|
+
if (store.schemaVersion === 2 && store.currentAccountId && store.currentAccountId !== LEGACY_ACCOUNT_ID) {
|
|
151
|
+
return store.currentAccountId;
|
|
152
|
+
}
|
|
153
|
+
if (store.schemaVersion === 2 && store.currentAccountId === null) return null;
|
|
154
|
+
return readLegacyConfigAccountId(read, homeDir);
|
|
155
|
+
}
|
|
156
|
+
var MCP_TOKEN_ENV, MCP_TOKEN_LINE_RE, CREDENTIALS_SCHEMA_VERSION, LEGACY_ACCOUNT_ID, ACCOUNT_FIELDS, ACCOUNT_ID_RE, CONFIG_ACCOUNT_ID_RE;
|
|
157
|
+
var init_credentials = __esm({
|
|
158
|
+
"vendor/heyspex-mcp/credentials.mjs"() {
|
|
159
|
+
"use strict";
|
|
160
|
+
init_heyspex_home();
|
|
161
|
+
MCP_TOKEN_ENV = "HEYSPEX_MCP_TOKEN";
|
|
162
|
+
MCP_TOKEN_LINE_RE = /^mcp_token:[ \t]*["']?([^"'\r\n#]+)/m;
|
|
163
|
+
CREDENTIALS_SCHEMA_VERSION = 2;
|
|
164
|
+
LEGACY_ACCOUNT_ID = "legacy";
|
|
165
|
+
ACCOUNT_FIELDS = ["mcp_token", "console_url", "email", "paired_at"];
|
|
166
|
+
ACCOUNT_ID_RE = /^[A-Za-z0-9][A-Za-z0-9_.@+-]*$/;
|
|
167
|
+
CONFIG_ACCOUNT_ID_RE = /^account_id:\s*["']?([^"'\r\n#]+)/m;
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
// scripts/lib/dev-mode.mjs
|
|
172
|
+
import { mkdirSync as mkdirSync4, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "node:fs";
|
|
173
|
+
import { homedir as homedir7 } from "node:os";
|
|
174
|
+
import { join as join8 } from "node:path";
|
|
175
|
+
function devModeFilePath(homeDir = heyspexHome()) {
|
|
176
|
+
return join8(homeDir, ".heyspex", DEV_MODE_FILENAME);
|
|
177
|
+
}
|
|
178
|
+
function normalizeMode(raw) {
|
|
179
|
+
const v = String(raw ?? "").trim().toLowerCase();
|
|
180
|
+
if (v === "prod" || v === "production") return { mode: "prod", reliance: "cloud" };
|
|
181
|
+
if (v === "dev") return { mode: "dev", reliance: null };
|
|
182
|
+
if (v === "local") return { mode: "dev", reliance: "local" };
|
|
183
|
+
if (v === "cloud") return { mode: "dev", reliance: "cloud" };
|
|
184
|
+
return null;
|
|
185
|
+
}
|
|
186
|
+
function normalizeReliance(raw) {
|
|
187
|
+
const v = String(raw ?? "").trim().toLowerCase();
|
|
188
|
+
return v === "local" || v === "cloud" ? v : null;
|
|
189
|
+
}
|
|
190
|
+
function deriveSemantics(mode, reliance) {
|
|
191
|
+
const effectiveReliance = mode === "prod" ? "cloud" : reliance ?? "local";
|
|
192
|
+
const local = mode === "dev" && effectiveReliance === "local";
|
|
193
|
+
return {
|
|
194
|
+
reliance: effectiveReliance,
|
|
195
|
+
consoleUrl: local ? LOCAL_CONSOLE_URL : CLOUD_CONSOLE_URL,
|
|
196
|
+
readTier: local ? "disk" : "neon",
|
|
197
|
+
vaultWriteMode: local ? "local" : "cloud"
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
function readLocalDevModeRecord({ homeDir = heyspexHome(), readFile = defaultReadFile } = {}) {
|
|
201
|
+
let raw;
|
|
202
|
+
try {
|
|
203
|
+
raw = readFile(devModeFilePath(homeDir));
|
|
204
|
+
} catch {
|
|
205
|
+
return { record: null, parseError: null };
|
|
206
|
+
}
|
|
207
|
+
try {
|
|
208
|
+
const parsed = JSON.parse(raw);
|
|
209
|
+
const mode = parsed?.mode === "prod" || parsed?.mode === "dev" ? parsed.mode : null;
|
|
210
|
+
const reliance = normalizeReliance(parsed?.reliance);
|
|
211
|
+
if (!mode || mode === "dev" && !reliance) {
|
|
212
|
+
return { record: null, parseError: `invalid dev-mode record: ${JSON.stringify(parsed)}` };
|
|
213
|
+
}
|
|
214
|
+
return {
|
|
215
|
+
record: {
|
|
216
|
+
mode,
|
|
217
|
+
reliance: mode === "prod" ? "cloud" : reliance,
|
|
218
|
+
console_url: typeof parsed.console_url === "string" ? parsed.console_url : null,
|
|
219
|
+
updated_at: typeof parsed.updated_at === "string" ? parsed.updated_at : null,
|
|
220
|
+
source: typeof parsed.source === "string" ? parsed.source : null
|
|
221
|
+
},
|
|
222
|
+
parseError: null
|
|
223
|
+
};
|
|
224
|
+
} catch (err) {
|
|
225
|
+
return { record: null, parseError: `unparseable dev-mode.json: ${String(err?.message ?? err)}` };
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
function resolveDevMode({
|
|
229
|
+
env = process.env,
|
|
230
|
+
homeDir = heyspexHome(),
|
|
231
|
+
readFile = defaultReadFile,
|
|
232
|
+
cloudSetting = void 0
|
|
233
|
+
} = {}) {
|
|
234
|
+
const local = readLocalDevModeRecord({ homeDir, readFile });
|
|
235
|
+
const cloudKnown = cloudSetting !== void 0;
|
|
236
|
+
const cloudValue = cloudKnown && cloudSetting ? {
|
|
237
|
+
mode: cloudSetting.mode === "dev" ? "dev" : "prod",
|
|
238
|
+
reliance: normalizeReliance(cloudSetting.reliance) ?? "cloud"
|
|
239
|
+
} : null;
|
|
240
|
+
let mode;
|
|
241
|
+
let reliance;
|
|
242
|
+
let source;
|
|
243
|
+
let relianceSource;
|
|
244
|
+
const envParsed = normalizeMode(env.HEYSPEX_DEV_MODE);
|
|
245
|
+
if (envParsed) {
|
|
246
|
+
mode = envParsed.mode;
|
|
247
|
+
const envReliance = envParsed.reliance ?? normalizeReliance(env.HEYSPEX_DEV_RELIANCE);
|
|
248
|
+
reliance = envReliance ?? "local";
|
|
249
|
+
source = "env";
|
|
250
|
+
relianceSource = envReliance ? "env" : "assumed-local";
|
|
251
|
+
} else if (local.record) {
|
|
252
|
+
mode = local.record.mode;
|
|
253
|
+
reliance = local.record.reliance;
|
|
254
|
+
source = "local-override";
|
|
255
|
+
} else if (cloudValue) {
|
|
256
|
+
mode = cloudValue.mode;
|
|
257
|
+
reliance = cloudValue.reliance;
|
|
258
|
+
source = "cloud-setting";
|
|
259
|
+
} else {
|
|
260
|
+
mode = "prod";
|
|
261
|
+
reliance = "cloud";
|
|
262
|
+
source = "default";
|
|
263
|
+
}
|
|
264
|
+
const semantics = deriveSemantics(mode, reliance);
|
|
265
|
+
const consoleUrl = source === "local-override" && local.record?.console_url ? local.record.console_url.replace(/\/+$/, "") : semantics.consoleUrl;
|
|
266
|
+
const staleAgainstCloud = source === "local-override" && cloudValue !== null && (cloudValue.mode !== mode || mode === "dev" && cloudValue.reliance !== semantics.reliance);
|
|
267
|
+
return {
|
|
268
|
+
mode,
|
|
269
|
+
reliance: semantics.reliance,
|
|
270
|
+
consoleUrl,
|
|
271
|
+
readTier: semantics.readTier,
|
|
272
|
+
vaultWriteMode: semantics.vaultWriteMode,
|
|
273
|
+
sources: {
|
|
274
|
+
mode: source,
|
|
275
|
+
reliance: relianceSource ?? source,
|
|
276
|
+
consoleUrl: source === "local-override" && local.record?.console_url ? "local-override" : "derived",
|
|
277
|
+
readTier: "derived",
|
|
278
|
+
vaultWriteMode: "derived"
|
|
279
|
+
},
|
|
280
|
+
localOverride: {
|
|
281
|
+
present: local.record !== null,
|
|
282
|
+
record: local.record,
|
|
283
|
+
parseError: local.parseError,
|
|
284
|
+
staleAgainstCloud
|
|
285
|
+
},
|
|
286
|
+
cloudSetting: { known: cloudKnown, value: cloudValue }
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
var CLOUD_CONSOLE_URL, LOCAL_CONSOLE_URL, DEV_MODE_FILENAME, defaultReadFile;
|
|
290
|
+
var init_dev_mode = __esm({
|
|
291
|
+
"scripts/lib/dev-mode.mjs"() {
|
|
292
|
+
"use strict";
|
|
293
|
+
init_heyspex_home();
|
|
294
|
+
init_credentials();
|
|
295
|
+
CLOUD_CONSOLE_URL = "https://app.heyspex.com";
|
|
296
|
+
LOCAL_CONSOLE_URL = "http://localhost:3000";
|
|
297
|
+
DEV_MODE_FILENAME = "dev-mode.json";
|
|
298
|
+
defaultReadFile = (p) => readFileSync7(p, "utf8");
|
|
299
|
+
}
|
|
300
|
+
});
|
|
3
301
|
|
|
4
302
|
// spex-console/bin/hs-check.ts
|
|
5
303
|
import { spawn, spawnSync } from "node:child_process";
|
|
6
|
-
import { existsSync, readFileSync as
|
|
304
|
+
import { existsSync as existsSync5, readFileSync as readFileSync9 } from "node:fs";
|
|
7
305
|
import path2 from "node:path";
|
|
8
|
-
import
|
|
306
|
+
import process3 from "node:process";
|
|
307
|
+
|
|
308
|
+
// scripts/lib/shared-path-helpers.mjs
|
|
309
|
+
import { isAbsolute, resolve, sep } from "node:path";
|
|
310
|
+
function resolveCodeRoot({
|
|
311
|
+
explicit,
|
|
312
|
+
packageRoot,
|
|
313
|
+
vaultRoot,
|
|
314
|
+
env = process.env
|
|
315
|
+
} = {}) {
|
|
316
|
+
const fromExplicit = typeof explicit === "string" ? explicit.trim() : "";
|
|
317
|
+
if (fromExplicit) return fromExplicit;
|
|
318
|
+
const fromEnv = typeof env?.HS_PKG_ROOT === "string" ? env.HS_PKG_ROOT.trim() : "";
|
|
319
|
+
if (fromEnv) return fromEnv;
|
|
320
|
+
const fromPackage = typeof packageRoot === "string" ? packageRoot.trim() : "";
|
|
321
|
+
if (fromPackage) return fromPackage;
|
|
322
|
+
const fromVault = typeof vaultRoot === "string" ? vaultRoot.trim() : "";
|
|
323
|
+
return fromVault;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// spexharness/src/verifier-seat.mjs
|
|
327
|
+
function grantsClose({ manifestFound, parseError = null, criteria = [] }) {
|
|
328
|
+
if (!manifestFound) {
|
|
329
|
+
return {
|
|
330
|
+
closeGranted: false,
|
|
331
|
+
reason: "no frozen manifest \u2014 NOT verified",
|
|
332
|
+
verifiedCount: 0,
|
|
333
|
+
failedCount: 0,
|
|
334
|
+
totalCount: 0
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
if (parseError) {
|
|
338
|
+
return {
|
|
339
|
+
closeGranted: false,
|
|
340
|
+
reason: `frozen manifest is unparseable (${parseError}) \u2014 NOT verified`,
|
|
341
|
+
verifiedCount: 0,
|
|
342
|
+
failedCount: 0,
|
|
343
|
+
totalCount: 0
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
const list = Array.isArray(criteria) ? criteria : [];
|
|
347
|
+
const failed = list.filter((c) => !c.passed);
|
|
348
|
+
const totalCount = list.length;
|
|
349
|
+
if (failed.length > 0) {
|
|
350
|
+
return {
|
|
351
|
+
closeGranted: false,
|
|
352
|
+
reason: `${failed.length} of ${totalCount} machine criteria failed`,
|
|
353
|
+
verifiedCount: totalCount - failed.length,
|
|
354
|
+
failedCount: failed.length,
|
|
355
|
+
totalCount
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
if (totalCount === 0) {
|
|
359
|
+
return {
|
|
360
|
+
closeGranted: false,
|
|
361
|
+
reason: "no machine-verifiable criteria \u2014 nothing was verified",
|
|
362
|
+
verifiedCount: 0,
|
|
363
|
+
failedCount: 0,
|
|
364
|
+
totalCount: 0
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
return {
|
|
368
|
+
closeGranted: true,
|
|
369
|
+
reason: `frozen manifest GREEN \u2014 ${totalCount} machine criteria verified`,
|
|
370
|
+
verifiedCount: totalCount,
|
|
371
|
+
failedCount: 0,
|
|
372
|
+
totalCount
|
|
373
|
+
};
|
|
374
|
+
}
|
|
9
375
|
|
|
10
376
|
// spex-console/src/lib/cli/checks-manifest.ts
|
|
11
377
|
var EXIT = {
|
|
@@ -31,11 +397,13 @@ function parseCheckRunArgs(argv) {
|
|
|
31
397
|
let target;
|
|
32
398
|
let json = false;
|
|
33
399
|
let report = false;
|
|
400
|
+
let unattended = false;
|
|
34
401
|
for (let i = 1; i < argv.length; i++) {
|
|
35
402
|
const arg = argv[i];
|
|
36
403
|
if (arg === "--help" || arg === "-h") return { help: true };
|
|
37
404
|
else if (arg === "--json") json = true;
|
|
38
405
|
else if (arg === "--report") report = true;
|
|
406
|
+
else if (arg === "--unattended") unattended = true;
|
|
39
407
|
else if (arg === "--manifest" || arg.startsWith("--manifest=")) {
|
|
40
408
|
const value = arg.includes("=") ? arg.slice("--manifest=".length) : argv[++i];
|
|
41
409
|
if (value === void 0) {
|
|
@@ -53,7 +421,7 @@ function parseCheckRunArgs(argv) {
|
|
|
53
421
|
if (!target) {
|
|
54
422
|
return { error: { code: EXIT.USAGE, message: "run requires a spec id or manifest path" } };
|
|
55
423
|
}
|
|
56
|
-
return { args: { verb: "run", target, json, report } };
|
|
424
|
+
return { args: { verb: "run", target, json, report, unattended } };
|
|
57
425
|
}
|
|
58
426
|
function buildVerdict(report) {
|
|
59
427
|
return {
|
|
@@ -73,12 +441,17 @@ var KIND_ALIASES = {
|
|
|
73
441
|
function joinUnderRoot(root, rel) {
|
|
74
442
|
if (rel === "" || rel === ".") return root;
|
|
75
443
|
if (/^(?:[a-zA-Z]:[\\/]|[\\/])/.test(rel)) return rel;
|
|
76
|
-
const
|
|
77
|
-
return `${root.replace(/[\\/]+$/, "")}${
|
|
444
|
+
const sep2 = root.includes("\\") && !root.includes("/") ? "\\" : "/";
|
|
445
|
+
return `${root.replace(/[\\/]+$/, "")}${sep2}${rel.replace(/^[\\/]+/, "")}`;
|
|
78
446
|
}
|
|
79
447
|
function resolveCheckCwds(check, roots) {
|
|
80
448
|
if (typeof check.cwd === "string" && check.cwd.trim() !== "") {
|
|
81
|
-
|
|
449
|
+
const raw = check.cwd.trim();
|
|
450
|
+
const vaultMatch = /^vault:(.*)$/.exec(raw);
|
|
451
|
+
if (vaultMatch) {
|
|
452
|
+
return [joinUnderRoot(roots.vaultRoot ?? roots.repoRoot, vaultMatch[1].trim())];
|
|
453
|
+
}
|
|
454
|
+
return [joinUnderRoot(roots.repoRoot, raw)];
|
|
82
455
|
}
|
|
83
456
|
return roots.repoRoot === roots.consoleRoot ? [roots.repoRoot] : [roots.repoRoot, roots.consoleRoot];
|
|
84
457
|
}
|
|
@@ -193,14 +566,17 @@ function parseChecksManifest(text) {
|
|
|
193
566
|
}
|
|
194
567
|
};
|
|
195
568
|
}
|
|
196
|
-
var ZERO_MATCH_RE = /Passed:\s*0\b|No test is available|No test files found|No test matches the given testcase filter
|
|
197
|
-
|
|
569
|
+
var ZERO_MATCH_RE = /Passed:\s*0\b|No test is available|No test files found|No test matches the given testcase filter|^\s*(?:ℹ|#)\s*(?:tests|pass)\s+0\s*$/i;
|
|
570
|
+
var NODE_TEST_FILE_PASS_RE = /^\s*(?:✔|ok\s+\d+\s+-)\s+\S*\.(?:test|spec)\.[cm]?[jt]sx?\s*(?:\(|$)/;
|
|
571
|
+
var NODE_TEST_FILTER_RE = /--test-(?:name|skip)-pattern/;
|
|
572
|
+
function detectZeroMatch(rawOutput, run) {
|
|
198
573
|
if (!rawOutput) return null;
|
|
199
|
-
const
|
|
574
|
+
const filtered = run !== void 0 && NODE_TEST_FILTER_RE.test(run);
|
|
575
|
+
const line = rawOutput.split(/\r?\n/).find((l) => ZERO_MATCH_RE.test(l) || filtered && NODE_TEST_FILE_PASS_RE.test(l));
|
|
200
576
|
return line ? line.trim() : null;
|
|
201
577
|
}
|
|
202
578
|
function evaluateCheck(check, outcome) {
|
|
203
|
-
if (detectZeroMatch(outcome.rawOutput) !== null) return false;
|
|
579
|
+
if (detectZeroMatch(outcome.rawOutput, check.run) !== null) return false;
|
|
204
580
|
if (check.expect === "no-net-new") {
|
|
205
581
|
return (outcome.errorCount ?? 0) <= check.baseline;
|
|
206
582
|
}
|
|
@@ -364,6 +740,9 @@ var HELP = [
|
|
|
364
740
|
" --report PATCH the verdict to the spec's active claim (the PCFL gate).",
|
|
365
741
|
" Requires a spec-id target (identifies the claim). The verify",
|
|
366
742
|
" exit code is unchanged; reporting is a side effect.",
|
|
743
|
+
" --unattended Declare that no human is present at this flip (pipeline",
|
|
744
|
+
" caller). Rides --report: the server's no-manifest",
|
|
745
|
+
" self-report fallback then fails closed instead of shipping.",
|
|
367
746
|
" --json Emit a machine-readable JSON result (the gate's input shape).",
|
|
368
747
|
" --help, -h Show this help.",
|
|
369
748
|
"",
|
|
@@ -372,12 +751,380 @@ var HELP = [
|
|
|
372
751
|
].join("\n");
|
|
373
752
|
|
|
374
753
|
// spex-console/src/lib/cli/control-shared.ts
|
|
375
|
-
import
|
|
376
|
-
|
|
377
|
-
|
|
754
|
+
import path from "node:path";
|
|
755
|
+
|
|
756
|
+
// scripts/lib/hs-command.mjs
|
|
757
|
+
import { existsSync } from "node:fs";
|
|
758
|
+
import { delimiter, dirname, join } from "node:path";
|
|
759
|
+
import { fileURLToPath } from "node:url";
|
|
760
|
+
var HS_BIN_NAME = "hs";
|
|
761
|
+
var HS_DISPATCHER_PATH = (() => {
|
|
762
|
+
try {
|
|
763
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
764
|
+
return join(dirname(dirname(here)), "bin", "hs.mjs");
|
|
765
|
+
} catch {
|
|
766
|
+
return null;
|
|
767
|
+
}
|
|
768
|
+
})();
|
|
769
|
+
function shellQuote(p) {
|
|
770
|
+
return /[\\\s]/.test(p) ? `"${p}"` : p;
|
|
771
|
+
}
|
|
772
|
+
var HS_DISPATCHER_PREFIX = HS_DISPATCHER_PATH ? `node ${shellQuote(HS_DISPATCHER_PATH)}` : HS_BIN_NAME;
|
|
773
|
+
function candidateNames(name, env) {
|
|
774
|
+
if (process.platform !== "win32") return [name];
|
|
775
|
+
const pathext = (env.PATHEXT || ".COM;.EXE;.BAT;.CMD").split(";").map((e) => e.trim().toLowerCase()).filter(Boolean);
|
|
776
|
+
const extensions = [.../* @__PURE__ */ new Set([...pathext, ".ps1"])];
|
|
777
|
+
return [name, ...extensions.map((ext) => name + ext)];
|
|
778
|
+
}
|
|
779
|
+
function findOnPath(name, { env = process.env, exists = existsSync } = {}) {
|
|
780
|
+
try {
|
|
781
|
+
const raw = env.PATH ?? env.Path ?? env.path ?? "";
|
|
782
|
+
for (const dir of String(raw).split(delimiter)) {
|
|
783
|
+
const trimmed = dir.trim().replace(/^"(.*)"$/, "$1");
|
|
784
|
+
if (!trimmed) continue;
|
|
785
|
+
for (const candidate of candidateNames(name, env)) {
|
|
786
|
+
const full = join(trimmed, candidate);
|
|
787
|
+
try {
|
|
788
|
+
if (exists(full)) return full;
|
|
789
|
+
} catch {
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
} catch {
|
|
794
|
+
}
|
|
795
|
+
return null;
|
|
796
|
+
}
|
|
797
|
+
var cached = null;
|
|
798
|
+
var HS_ON_PATH_OVERRIDE_ENV = "HEYSPEX_HS_ON_PATH";
|
|
799
|
+
function onPathOverride(env) {
|
|
800
|
+
const raw = String(env?.[HS_ON_PATH_OVERRIDE_ENV] ?? "");
|
|
801
|
+
if (raw === "1") return true;
|
|
802
|
+
if (raw === "0") return false;
|
|
803
|
+
return void 0;
|
|
804
|
+
}
|
|
805
|
+
function describeHsCommand({ env, exists, useCache = true } = {}) {
|
|
806
|
+
const injected = env !== void 0 || exists !== void 0;
|
|
807
|
+
const effectiveEnv = env ?? process.env;
|
|
808
|
+
const override = onPathOverride(effectiveEnv);
|
|
809
|
+
if (override !== void 0) {
|
|
810
|
+
return {
|
|
811
|
+
onPath: override,
|
|
812
|
+
prefix: override ? HS_BIN_NAME : HS_DISPATCHER_PREFIX,
|
|
813
|
+
resolvedPath: null,
|
|
814
|
+
dispatcherPath: HS_DISPATCHER_PATH
|
|
815
|
+
};
|
|
816
|
+
}
|
|
817
|
+
if (useCache && !injected && cached) return cached;
|
|
818
|
+
const resolvedPath = findOnPath(HS_BIN_NAME, {
|
|
819
|
+
env: env ?? process.env,
|
|
820
|
+
exists: exists ?? existsSync
|
|
821
|
+
});
|
|
822
|
+
const answer = {
|
|
823
|
+
onPath: resolvedPath !== null,
|
|
824
|
+
prefix: resolvedPath !== null ? HS_BIN_NAME : HS_DISPATCHER_PREFIX,
|
|
825
|
+
resolvedPath,
|
|
826
|
+
dispatcherPath: HS_DISPATCHER_PATH
|
|
827
|
+
};
|
|
828
|
+
if (useCache && !injected) cached = answer;
|
|
829
|
+
return answer;
|
|
830
|
+
}
|
|
831
|
+
function hsCommand(args = "", options = {}) {
|
|
832
|
+
let prefix = HS_DISPATCHER_PREFIX;
|
|
833
|
+
try {
|
|
834
|
+
prefix = describeHsCommand(options).prefix;
|
|
835
|
+
} catch {
|
|
836
|
+
}
|
|
837
|
+
const tail = String(args ?? "").trim();
|
|
838
|
+
return tail ? `${prefix} ${tail}` : prefix;
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
// vendor/heyspex-mcp/console-fetch.mjs
|
|
842
|
+
init_heyspex_home();
|
|
843
|
+
init_credentials();
|
|
844
|
+
import { readFileSync as readFileSync5 } from "node:fs";
|
|
845
|
+
import { homedir as homedir5 } from "node:os";
|
|
846
|
+
import { join as join6 } from "node:path";
|
|
847
|
+
|
|
848
|
+
// vendor/heyspex-mcp/session-owner.mjs
|
|
849
|
+
init_heyspex_home();
|
|
850
|
+
init_credentials();
|
|
851
|
+
import { readFileSync as readFileSync2 } from "node:fs";
|
|
852
|
+
import { homedir as homedir3 } from "node:os";
|
|
853
|
+
import { join as join3 } from "node:path";
|
|
854
|
+
function resolveSessionOwnerUncached({ env = process.env, homeDir = heyspexHome(), readFile } = {}) {
|
|
855
|
+
const fromEnv = typeof env?.HS_ACCOUNT_ID === "string" ? env.HS_ACCOUNT_ID.trim() : "";
|
|
856
|
+
if (fromEnv) return fromEnv;
|
|
857
|
+
return readCurrentAccountId({ homeDir, readFile });
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
// vendor/heyspex-mcp/vault-less-endpoints.mjs
|
|
861
|
+
var VAULT_LESS_ENDPOINTS = Object.freeze([
|
|
862
|
+
"* /api/mcp/session",
|
|
863
|
+
"* /api/mcp/pairing-codes",
|
|
864
|
+
"POST /api/vaults",
|
|
865
|
+
"GET /api/vaults",
|
|
866
|
+
"POST /api/workspaces",
|
|
867
|
+
"GET /api/workspaces",
|
|
868
|
+
"* /api/admin/dev-mode"
|
|
869
|
+
]);
|
|
870
|
+
function pathnameOf(path3) {
|
|
871
|
+
const raw = String(path3 ?? "").split("?")[0].split("#")[0];
|
|
872
|
+
const noTrailing = raw.length > 1 ? raw.replace(/\/+$/, "") : raw;
|
|
873
|
+
return noTrailing || "/";
|
|
874
|
+
}
|
|
875
|
+
function isVaultLessEndpoint(method, path3) {
|
|
876
|
+
const m = String(method || "GET").toUpperCase();
|
|
877
|
+
const pathname = pathnameOf(path3);
|
|
878
|
+
return VAULT_LESS_ENDPOINTS.some((row) => {
|
|
879
|
+
const [rowMethod, rowPath] = row.split(" ");
|
|
880
|
+
return rowPath === pathname && (rowMethod === "*" || rowMethod === m);
|
|
881
|
+
});
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
// vendor/heyspex-mcp/write-outbox.mjs
|
|
885
|
+
import {
|
|
886
|
+
readdirSync,
|
|
887
|
+
readFileSync as readFileSync3,
|
|
888
|
+
writeFileSync as writeFileSync2,
|
|
889
|
+
mkdirSync as mkdirSync2,
|
|
890
|
+
existsSync as existsSync2,
|
|
891
|
+
renameSync as renameSync2,
|
|
892
|
+
rmSync
|
|
893
|
+
} from "node:fs";
|
|
894
|
+
import { basename, join as join4, isAbsolute as isAbsolute2 } from "node:path";
|
|
895
|
+
|
|
896
|
+
// vendor/heyspex-mcp/vault-resolve.mjs
|
|
897
|
+
init_heyspex_home();
|
|
898
|
+
import { existsSync as existsSync3, readFileSync as readFileSync4 } from "node:fs";
|
|
899
|
+
import { homedir as homedir4, tmpdir } from "node:os";
|
|
900
|
+
import { dirname as dirname3, join as join5, resolve as resolve3 } from "node:path";
|
|
901
|
+
|
|
902
|
+
// vendor/heyspex-mcp/console-fetch.mjs
|
|
903
|
+
var VAULT_NOT_REGISTERED = "VAULT_NOT_REGISTERED";
|
|
904
|
+
var VaultNotRegisteredError = class extends Error {
|
|
905
|
+
/**
|
|
906
|
+
* @param {string | null | undefined} repoRootAbs the root the resolver settled on
|
|
907
|
+
* @param {string | null | undefined} source the resolver tier that produced it
|
|
908
|
+
* @param {{ reason?: 'unregistered' | 'legacy-id', legacyId?: string | null }} [detail]
|
|
909
|
+
*/
|
|
910
|
+
constructor(repoRootAbs, source, { reason = "unregistered", legacyId = null } = {}) {
|
|
911
|
+
const root = typeof repoRootAbs === "string" && repoRootAbs.trim() ? repoRootAbs.trim() : null;
|
|
912
|
+
const where = root ? ` (${root})` : "";
|
|
913
|
+
const why = reason === "legacy-id" ? `This repo${where} is registered on this machine under a legacy id (\`${legacyId}\`), not the vault id the console knows, and its heyspex.yaml carries no \`vault_id\` to stand in for it, ` : `This repo${where} is not connected to a HeySpex vault on this machine (\`hs connect\` has not registered it), `;
|
|
914
|
+
super(
|
|
915
|
+
why + `so the console call was refused before it was sent. Run \`${hsCommand("connect")}\`${root ? ` in ${root}` : ""} to ${reason === "legacy-id" ? "re-register" : "connect"} it, then try again. Nothing was sent.`
|
|
916
|
+
);
|
|
917
|
+
this.name = "VaultNotRegisteredError";
|
|
918
|
+
this.code = VAULT_NOT_REGISTERED;
|
|
919
|
+
this.reason = reason;
|
|
920
|
+
this.repoRootAbs = root;
|
|
921
|
+
this.source = typeof source === "string" ? source : null;
|
|
922
|
+
this.legacyId = legacyId;
|
|
923
|
+
}
|
|
924
|
+
};
|
|
925
|
+
var CLOUD_VAULT_ID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
926
|
+
function isCloudVaultId(value) {
|
|
927
|
+
return typeof value === "string" && CLOUD_VAULT_ID_RE.test(value.trim());
|
|
928
|
+
}
|
|
929
|
+
var MARKER_VAULT_ID_RE = /^vault_id:[ \t]*["']?([^"'\r\n#]+)/m;
|
|
930
|
+
function readMarkerVaultId(repoRootAbs, { readFile } = {}) {
|
|
931
|
+
if (typeof repoRootAbs !== "string" || !repoRootAbs.trim()) return null;
|
|
932
|
+
const read = readFile ?? ((p) => readFileSync5(p, "utf8"));
|
|
933
|
+
try {
|
|
934
|
+
const raw = read(join6(repoRootAbs, "heyspex.yaml"));
|
|
935
|
+
const value = String(raw ?? "").match(MARKER_VAULT_ID_RE)?.[1]?.trim() ?? "";
|
|
936
|
+
return value || null;
|
|
937
|
+
} catch {
|
|
938
|
+
return null;
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
function cloudVaultIdFor(resolved, { readMarkerVaultId: readMarker = readMarkerVaultId } = {}) {
|
|
942
|
+
const rowId = resolved.vaultId.trim();
|
|
943
|
+
if (isCloudVaultId(rowId)) return rowId;
|
|
944
|
+
const marker = readMarker(resolved.repoRootAbs);
|
|
945
|
+
if (isCloudVaultId(marker)) return marker.trim();
|
|
946
|
+
throw new VaultNotRegisteredError(resolved.repoRootAbs, resolved.source, { reason: "legacy-id", legacyId: rowId });
|
|
947
|
+
}
|
|
948
|
+
var SCOPE_REFUSAL_CODES = Object.freeze(["VAULT_SCOPE_MISMATCH", "ACCOUNT_MISMATCH", "VAULT_NOT_REGISTERED"]);
|
|
949
|
+
|
|
950
|
+
// spex-console/src/lib/cli/control-shared.ts
|
|
951
|
+
init_credentials();
|
|
952
|
+
|
|
953
|
+
// spex-console/src/lib/vault-registry/registry-file.ts
|
|
954
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync6, renameSync as renameSync3, writeFileSync as writeFileSync3 } from "node:fs";
|
|
955
|
+
import { dirname as dirname4, join as join7, resolve as resolve4 } from "node:path";
|
|
956
|
+
|
|
957
|
+
// spex-console/src/lib/heyspex-home.ts
|
|
958
|
+
import { homedir as homedir6 } from "node:os";
|
|
959
|
+
function heyspexHome2(env = process.env) {
|
|
960
|
+
const override = env?.HEYSPEX_HOME;
|
|
961
|
+
if (typeof override === "string" && override.trim().length > 0) {
|
|
962
|
+
return override.trim();
|
|
963
|
+
}
|
|
964
|
+
return homedir6();
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
// spex-console/src/lib/vault-registry/types.ts
|
|
968
|
+
var LEGACY_REGISTRY_ACCOUNT_ID = "legacy";
|
|
969
|
+
var VAULT_REGISTRY_SCHEMA_VERSION = 2;
|
|
970
|
+
function emptyVaultRegistry() {
|
|
971
|
+
return {
|
|
972
|
+
schema_version: VAULT_REGISTRY_SCHEMA_VERSION,
|
|
973
|
+
current_account_id: null,
|
|
974
|
+
accounts: {},
|
|
975
|
+
workspaces: [],
|
|
976
|
+
vaults: []
|
|
977
|
+
};
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
// spex-console/src/lib/vault-registry/registry-file.ts
|
|
981
|
+
function vaultRegistryDir(opts = {}) {
|
|
982
|
+
return opts.registryDir ?? join7(heyspexHome2(), ".heyspex");
|
|
983
|
+
}
|
|
984
|
+
function vaultRegistryFilePath(opts = {}) {
|
|
985
|
+
return join7(vaultRegistryDir(opts), "registry.json");
|
|
986
|
+
}
|
|
987
|
+
function isNonEmptyString(v) {
|
|
988
|
+
return typeof v === "string" && v.trim().length > 0;
|
|
989
|
+
}
|
|
990
|
+
function accountOf(o) {
|
|
991
|
+
return isNonEmptyString(o.account_id) ? o.account_id : LEGACY_REGISTRY_ACCOUNT_ID;
|
|
992
|
+
}
|
|
993
|
+
function normalizeWorkspace(raw) {
|
|
994
|
+
if (!raw || typeof raw !== "object") return null;
|
|
995
|
+
const o = raw;
|
|
996
|
+
if (!isNonEmptyString(o.id)) return null;
|
|
997
|
+
if (!isNonEmptyString(o.display_name)) return null;
|
|
998
|
+
if (!isNonEmptyString(o.created_at)) return null;
|
|
999
|
+
const record = {
|
|
1000
|
+
id: o.id,
|
|
1001
|
+
display_name: o.display_name,
|
|
1002
|
+
created_at: o.created_at,
|
|
1003
|
+
archived: o.archived === true,
|
|
1004
|
+
account_id: accountOf(o)
|
|
1005
|
+
};
|
|
1006
|
+
if (isNonEmptyString(o.github_org)) record.github_org = o.github_org;
|
|
1007
|
+
return record;
|
|
1008
|
+
}
|
|
1009
|
+
function normalizeVault(raw) {
|
|
1010
|
+
if (!raw || typeof raw !== "object") return null;
|
|
1011
|
+
const o = raw;
|
|
1012
|
+
if (!isNonEmptyString(o.id)) return null;
|
|
1013
|
+
if (!isNonEmptyString(o.workspace_id)) return null;
|
|
1014
|
+
if (!isNonEmptyString(o.display_name)) return null;
|
|
1015
|
+
if (!isNonEmptyString(o.repo_root_abs)) return null;
|
|
1016
|
+
if (typeof o.schema_version !== "number") return null;
|
|
1017
|
+
if (!isNonEmptyString(o.registered_at)) return null;
|
|
1018
|
+
const record = {
|
|
1019
|
+
id: o.id,
|
|
1020
|
+
workspace_id: o.workspace_id,
|
|
1021
|
+
display_name: o.display_name,
|
|
1022
|
+
repo_root_abs: o.repo_root_abs,
|
|
1023
|
+
schema_version: o.schema_version,
|
|
1024
|
+
registered_at: o.registered_at,
|
|
1025
|
+
archived: o.archived === true,
|
|
1026
|
+
account_id: accountOf(o)
|
|
1027
|
+
};
|
|
1028
|
+
if (isNonEmptyString(o.github_remote)) record.github_remote = o.github_remote;
|
|
1029
|
+
return record;
|
|
1030
|
+
}
|
|
1031
|
+
function normalizeStoredPointers(raw) {
|
|
1032
|
+
const o = raw && typeof raw === "object" ? raw : {};
|
|
1033
|
+
const out = {};
|
|
1034
|
+
for (const field of ["active_vault_id", "active_workspace_id"]) {
|
|
1035
|
+
const v = o[field];
|
|
1036
|
+
if (v === null || isNonEmptyString(v)) out[field] = v;
|
|
1037
|
+
}
|
|
1038
|
+
return out;
|
|
1039
|
+
}
|
|
1040
|
+
function normalizePointers(raw) {
|
|
1041
|
+
const o = raw && typeof raw === "object" ? raw : {};
|
|
1042
|
+
return {
|
|
1043
|
+
active_vault_id: isNonEmptyString(o.active_vault_id) ? o.active_vault_id : null,
|
|
1044
|
+
active_workspace_id: isNonEmptyString(o.active_workspace_id) ? o.active_workspace_id : null
|
|
1045
|
+
};
|
|
1046
|
+
}
|
|
1047
|
+
function registryFileSchemaOf(json) {
|
|
1048
|
+
if (!json || typeof json !== "object" || Array.isArray(json)) return null;
|
|
1049
|
+
return json.schema_version === 2 ? 2 : 1;
|
|
1050
|
+
}
|
|
1051
|
+
function parseVaultRegistryFile(json) {
|
|
1052
|
+
const schema = registryFileSchemaOf(json);
|
|
1053
|
+
if (schema === null) return emptyVaultRegistry();
|
|
1054
|
+
const o = json;
|
|
1055
|
+
const workspacesRaw = Array.isArray(o.workspaces) ? o.workspaces : [];
|
|
1056
|
+
const workspaces = workspacesRaw.map(normalizeWorkspace).filter((w) => w !== null);
|
|
1057
|
+
const vaultsRaw = Array.isArray(o.vaults) ? o.vaults : [];
|
|
1058
|
+
const vaults = vaultsRaw.map(normalizeVault).filter((v) => v !== null);
|
|
1059
|
+
if (schema === 1) {
|
|
1060
|
+
const legacy = normalizePointers(o);
|
|
1061
|
+
const hasLegacyState = legacy.active_vault_id !== null || legacy.active_workspace_id !== null || workspaces.length > 0 || vaults.length > 0;
|
|
1062
|
+
return {
|
|
1063
|
+
schema_version: VAULT_REGISTRY_SCHEMA_VERSION,
|
|
1064
|
+
current_account_id: hasLegacyState ? LEGACY_REGISTRY_ACCOUNT_ID : null,
|
|
1065
|
+
accounts: hasLegacyState ? { [LEGACY_REGISTRY_ACCOUNT_ID]: legacy } : {},
|
|
1066
|
+
workspaces,
|
|
1067
|
+
vaults
|
|
1068
|
+
};
|
|
1069
|
+
}
|
|
1070
|
+
const accounts = {};
|
|
1071
|
+
if (o.accounts && typeof o.accounts === "object" && !Array.isArray(o.accounts)) {
|
|
1072
|
+
for (const [id, pointers] of Object.entries(o.accounts)) {
|
|
1073
|
+
if (isNonEmptyString(id)) accounts[id] = normalizeStoredPointers(pointers);
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
return {
|
|
1077
|
+
schema_version: VAULT_REGISTRY_SCHEMA_VERSION,
|
|
1078
|
+
current_account_id: isNonEmptyString(o.current_account_id) ? o.current_account_id : null,
|
|
1079
|
+
accounts,
|
|
1080
|
+
workspaces,
|
|
1081
|
+
vaults
|
|
1082
|
+
};
|
|
1083
|
+
}
|
|
1084
|
+
function readRegistryJson(opts) {
|
|
1085
|
+
const filePath = vaultRegistryFilePath(opts);
|
|
1086
|
+
if (!existsSync4(filePath)) return { kind: "missing" };
|
|
1087
|
+
try {
|
|
1088
|
+
const raw = readFileSync6(filePath, "utf8");
|
|
1089
|
+
const json = JSON.parse(raw);
|
|
1090
|
+
if (registryFileSchemaOf(json) === null) return { kind: "unreadable" };
|
|
1091
|
+
return { kind: "ok", raw, json };
|
|
1092
|
+
} catch {
|
|
1093
|
+
return { kind: "unreadable" };
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
function inspectVaultRegistry(opts = {}) {
|
|
1097
|
+
const read = readRegistryJson(opts);
|
|
1098
|
+
if (read.kind !== "ok") {
|
|
1099
|
+
return {
|
|
1100
|
+
registry: emptyVaultRegistry(),
|
|
1101
|
+
file_schema_version: null,
|
|
1102
|
+
registry_unreadable: read.kind === "unreadable"
|
|
1103
|
+
};
|
|
1104
|
+
}
|
|
1105
|
+
return {
|
|
1106
|
+
registry: parseVaultRegistryFile(read.json),
|
|
1107
|
+
file_schema_version: registryFileSchemaOf(read.json),
|
|
1108
|
+
registry_unreadable: false
|
|
1109
|
+
};
|
|
1110
|
+
}
|
|
1111
|
+
function loadVaultRegistry(opts = {}) {
|
|
1112
|
+
return inspectVaultRegistry(opts).registry;
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
// spex-console/src/lib/cli/control-shared.ts
|
|
378
1116
|
var EXIT2 = {
|
|
379
1117
|
/** Success. */
|
|
380
1118
|
OK: 0,
|
|
1119
|
+
/**
|
|
1120
|
+
* A read-only CHECK ran correctly and its answer is "no" — `hs connect
|
|
1121
|
+
* --check` on an unconnected repo. Distinct from {@link EXIT.USAGE} so a
|
|
1122
|
+
* script can tell "you typed the command wrong" from "the command worked,
|
|
1123
|
+
* the repo is not connected". Both were `1` until 2026-09-18, which made
|
|
1124
|
+
* `--check` unusable in a conditional: every caller had to parse stdout to
|
|
1125
|
+
* find out which of the two had happened.
|
|
1126
|
+
*/
|
|
1127
|
+
CHECK_FAILED: 2,
|
|
381
1128
|
/** Usage error — unknown subcommand/flag, or a missing required argument. */
|
|
382
1129
|
USAGE: 1,
|
|
383
1130
|
/** Claim race lost — the spec is already claimed by another agent (409). */
|
|
@@ -399,31 +1146,94 @@ var EXIT2 = {
|
|
|
399
1146
|
* from `USAGE`/`INVALID_ARG` — this is an authorization refusal, not a
|
|
400
1147
|
* malformed command.
|
|
401
1148
|
*/
|
|
402
|
-
FORBIDDEN: 15
|
|
1149
|
+
FORBIDDEN: 15,
|
|
1150
|
+
/**
|
|
1151
|
+
* A CLOUD write refused because no account id could be resolved (no
|
|
1152
|
+
* `HS_ACCOUNT_ID` env var, no `account_id:` in `~/.heyspex/config.yaml`) --
|
|
1153
|
+
* `resolveCloudAccountIdOrRefuse` in `vault-workspace-shared.ts`
|
|
1154
|
+
* ([[HS-YU1S-UAGY-TASK-cloud-writes-refuse-without-a]]). Distinct from
|
|
1155
|
+
* `FORBIDDEN` -- this is "not signed in at all", not "signed in but not
|
|
1156
|
+
* authorized for this override".
|
|
1157
|
+
*/
|
|
1158
|
+
SIGN_IN_REQUIRED: 16,
|
|
1159
|
+
/**
|
|
1160
|
+
* Signed in, but the account a repo would be attached to was not confirmed:
|
|
1161
|
+
* a non-interactive install/connect with no matching `--account` / `--yes`,
|
|
1162
|
+
* an `account_id` that disagrees with the bearer's user, or the human
|
|
1163
|
+
* aborting the identity prompt (`install-identity.ts`,
|
|
1164
|
+
* [[HS-HAQI-KODY-DIAG-hs-install-and-hs-connect-bind-a-new]]).
|
|
1165
|
+
*/
|
|
1166
|
+
IDENTITY_UNCONFIRMED: 17,
|
|
1167
|
+
/**
|
|
1168
|
+
* The repo this verb runs in is not registered on this machine (no
|
|
1169
|
+
* `~/.heyspex/registry.json` row for its root, or a legacy slug row with no
|
|
1170
|
+
* cloud `vault_id` in its heyspex.yaml), so the call was refused LOCALLY —
|
|
1171
|
+
* nothing was sent. Names `hs connect`
|
|
1172
|
+
* ([[HS-MVG4-WW9R-TASK-mcp-and-hs-name-account-vault-on-every]]).
|
|
1173
|
+
*/
|
|
1174
|
+
VAULT_NOT_REGISTERED: 18,
|
|
1175
|
+
/**
|
|
1176
|
+
* The local half of a setup verb (`hs connect`, `hs vault *`, `hs workspace
|
|
1177
|
+
* *`) was written but the CLOUD half did not land -- the console refused
|
|
1178
|
+
* it or was never reached. The verb's receipt names the failed half and the
|
|
1179
|
+
* exact command to re-run
|
|
1180
|
+
* ([[HS-STCY-WW9R-TASK-add-a-shared-receipt-that-reads-back]], invariant 6).
|
|
1181
|
+
* Distinct from {@link EXIT.WRITE_FAILED} so a script can tell "re-run to
|
|
1182
|
+
* sync the cloud" from a generic server failure.
|
|
1183
|
+
*/
|
|
1184
|
+
CLOUD_WRITE_FAILED: 19,
|
|
1185
|
+
/**
|
|
1186
|
+
* The repo's committed `heyspex.yaml` records `account: <A>` but the machine
|
|
1187
|
+
* is signed in as another account, so `hs connect` refused and wrote
|
|
1188
|
+
* nothing (marker, registry, cloud). Repair: `hs login --switch-account <A>`
|
|
1189
|
+
* ([[HS-VWUD-WW9R-TASK-record-the-connecting-account-in]], invariant 4).
|
|
1190
|
+
*/
|
|
1191
|
+
ACCOUNT_MISMATCH: 20,
|
|
1192
|
+
/**
|
|
1193
|
+
* Both sides of a setup verb's receipt were read back and still DISAGREE
|
|
1194
|
+
* after a short bounded re-read of the cloud side (a briefly stale read is
|
|
1195
|
+
* absorbed first). The write may well have landed; what the verb cannot
|
|
1196
|
+
* claim is that this machine and the console now say the same thing. The
|
|
1197
|
+
* receipt prints the diffs and the command to re-run
|
|
1198
|
+
* ([[HS-FKSR-WW9R-TASK-make-one-hs-connect-run-create-a-cloud]], invariant 6).
|
|
1199
|
+
* Distinct from {@link EXIT.CLOUD_WRITE_FAILED} (a write that did not land).
|
|
1200
|
+
*/
|
|
1201
|
+
RECEIPT_DISAGREE: 21
|
|
403
1202
|
};
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
}
|
|
1203
|
+
var VAULT_ID_HEADER = "x-heyspex-vault-id";
|
|
1204
|
+
var ACCOUNT_ID_HEADER = "x-heyspex-account-id";
|
|
1205
|
+
function readPairedBearer(opts = {}) {
|
|
1206
|
+
return readBearer(opts);
|
|
1207
|
+
}
|
|
1208
|
+
function readPairedToken(opts = {}) {
|
|
1209
|
+
return readPairedBearer(opts).token ?? void 0;
|
|
412
1210
|
}
|
|
413
|
-
function bearerHeaderFor(consoleUrl) {
|
|
1211
|
+
function bearerHeaderFor(consoleUrl, opts = {}) {
|
|
414
1212
|
try {
|
|
415
1213
|
const host = new URL(consoleUrl).hostname;
|
|
416
1214
|
if (host === "localhost" || host === "127.0.0.1" || host === "0.0.0.0" || host === "::1") {
|
|
417
1215
|
return {};
|
|
418
1216
|
}
|
|
419
|
-
const token = readPairedToken();
|
|
1217
|
+
const token = readPairedToken(opts);
|
|
420
1218
|
return token ? { authorization: `Bearer ${token}` } : {};
|
|
421
1219
|
} catch {
|
|
422
1220
|
return {};
|
|
423
1221
|
}
|
|
424
1222
|
}
|
|
1223
|
+
function scopeHeadersFor(scope, method, path3) {
|
|
1224
|
+
if (scope === "none") {
|
|
1225
|
+
if (!isVaultLessEndpoint(method, path3)) {
|
|
1226
|
+
throw new Error(
|
|
1227
|
+
`controlRequest: scope "none" is only allowed for the vault-less endpoints (${VAULT_LESS_ENDPOINTS.join(", ")}); ${method.toUpperCase()} ${path3} is vault-scoped and must name { accountId, vaultId }.`
|
|
1228
|
+
);
|
|
1229
|
+
}
|
|
1230
|
+
return {};
|
|
1231
|
+
}
|
|
1232
|
+
return { [VAULT_ID_HEADER]: scope.vaultId, [ACCOUNT_ID_HEADER]: scope.accountId };
|
|
1233
|
+
}
|
|
425
1234
|
async function controlRequest(deps, req) {
|
|
426
1235
|
const url = `${deps.consoleUrl}${req.path}`;
|
|
1236
|
+
const scopeHeaders = scopeHeadersFor(deps.scope, req.method, req.path);
|
|
427
1237
|
let res;
|
|
428
1238
|
try {
|
|
429
1239
|
res = await deps.fetchImpl(url, {
|
|
@@ -434,7 +1244,10 @@ async function controlRequest(deps, req) {
|
|
|
434
1244
|
// authenticates every control call; loopback never asked, so the header
|
|
435
1245
|
// is attached only when the target is non-loopback and a paired token
|
|
436
1246
|
// exists. Same `hsmcp_` bearer the MCP server presents.
|
|
437
|
-
...bearerHeaderFor(deps.consoleUrl)
|
|
1247
|
+
...bearerHeaderFor(deps.consoleUrl),
|
|
1248
|
+
// Invariant 2: name the vault and the account on every call, or the
|
|
1249
|
+
// console serves whatever its leftover pointer says.
|
|
1250
|
+
...scopeHeaders
|
|
438
1251
|
},
|
|
439
1252
|
...req.body !== void 0 ? { body: JSON.stringify(req.body) } : {}
|
|
440
1253
|
});
|
|
@@ -479,42 +1292,188 @@ async function controlRequest(deps, req) {
|
|
|
479
1292
|
}
|
|
480
1293
|
return { ok: true, status: res.status, body: parsed };
|
|
481
1294
|
}
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
1295
|
+
function normalizeRoot(p) {
|
|
1296
|
+
return path.resolve(p).replace(/[\\/]+/g, "/").replace(/\/+$/, "").toLowerCase();
|
|
1297
|
+
}
|
|
1298
|
+
function resolveControlScope(input) {
|
|
1299
|
+
const target = normalizeRoot(input.vaultRoot);
|
|
1300
|
+
const row = input.vaults.find((v) => normalizeRoot(v.repo_root_abs) === target) ?? null;
|
|
1301
|
+
if (!row) {
|
|
1302
|
+
return {
|
|
1303
|
+
ok: false,
|
|
1304
|
+
error: {
|
|
1305
|
+
code: EXIT2.VAULT_NOT_REGISTERED,
|
|
1306
|
+
message: `this repo (${input.vaultRoot}) is not connected to a HeySpex vault on this machine (\`hs connect\` has not registered it), so the call was refused before it was sent. Run \`${hsCommand("connect")}\` in ${input.vaultRoot}, then try again.`,
|
|
1307
|
+
payload: {
|
|
1308
|
+
ok: false,
|
|
1309
|
+
error: "vault_not_registered",
|
|
1310
|
+
reason: "unregistered",
|
|
1311
|
+
vault_root: input.vaultRoot
|
|
1312
|
+
},
|
|
1313
|
+
isError: true
|
|
1314
|
+
}
|
|
1315
|
+
};
|
|
1316
|
+
}
|
|
1317
|
+
let vaultId;
|
|
1318
|
+
try {
|
|
1319
|
+
vaultId = cloudVaultIdFor(
|
|
1320
|
+
{ vaultId: row.id, repoRootAbs: row.repo_root_abs, source: "registry" },
|
|
1321
|
+
input.readMarkerVaultId ? { readMarkerVaultId: input.readMarkerVaultId } : {}
|
|
1322
|
+
);
|
|
1323
|
+
} catch (err) {
|
|
1324
|
+
if (!(err instanceof VaultNotRegisteredError)) throw err;
|
|
1325
|
+
const refused = err;
|
|
1326
|
+
return {
|
|
1327
|
+
ok: false,
|
|
1328
|
+
error: {
|
|
1329
|
+
code: EXIT2.VAULT_NOT_REGISTERED,
|
|
1330
|
+
message: refused.message,
|
|
1331
|
+
payload: {
|
|
1332
|
+
ok: false,
|
|
1333
|
+
error: "vault_not_registered",
|
|
1334
|
+
reason: refused.reason ?? "legacy-id",
|
|
1335
|
+
vault_root: input.vaultRoot,
|
|
1336
|
+
legacy_id: refused.legacyId ?? row.id
|
|
1337
|
+
},
|
|
1338
|
+
isError: true
|
|
1339
|
+
}
|
|
1340
|
+
};
|
|
1341
|
+
}
|
|
1342
|
+
const accountId = input.accountId?.trim() ?? "";
|
|
1343
|
+
if (!accountId) {
|
|
1344
|
+
return {
|
|
1345
|
+
ok: false,
|
|
1346
|
+
error: {
|
|
1347
|
+
code: EXIT2.SIGN_IN_REQUIRED,
|
|
1348
|
+
message: `no HeySpex account is configured on this machine (\`hs login\` has not been run), so the call was refused before it was sent. Run \`${hsCommand("login")}\` on this machine, then try again.`,
|
|
1349
|
+
payload: { ok: false, error: "sign_in_required", vault_root: input.vaultRoot },
|
|
1350
|
+
isError: true
|
|
1351
|
+
}
|
|
1352
|
+
};
|
|
1353
|
+
}
|
|
1354
|
+
return { ok: true, scope: { accountId, vaultId } };
|
|
1355
|
+
}
|
|
1356
|
+
function resolveControlScopeFromMachine(vaultRoot, opts = {}) {
|
|
1357
|
+
const registry = loadVaultRegistry(opts.registry);
|
|
1358
|
+
const accountId = resolveSessionOwnerUncached({ env: opts.env, homeDir: opts.homeDir });
|
|
1359
|
+
return resolveControlScope({ vaultRoot, accountId, vaults: registry.vaults });
|
|
1360
|
+
}
|
|
487
1361
|
|
|
488
1362
|
// scripts/lib/console-url-resolver.mjs
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
import {
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
function
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
1363
|
+
init_heyspex_home();
|
|
1364
|
+
init_dev_mode();
|
|
1365
|
+
import { readFileSync as readFileSync8 } from "node:fs";
|
|
1366
|
+
import { homedir as homedir8 } from "node:os";
|
|
1367
|
+
import { join as join9 } from "node:path";
|
|
1368
|
+
var DEFAULT_CONSOLE_URL = LOCAL_CONSOLE_URL;
|
|
1369
|
+
function trimTrailingSlash(url) {
|
|
1370
|
+
return url.replace(/\/+$/, "");
|
|
1371
|
+
}
|
|
1372
|
+
var LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "::1", "[::1]", "0.0.0.0"]);
|
|
1373
|
+
function isLoopbackConsoleUrl(url) {
|
|
1374
|
+
if (typeof url !== "string" || !url) return false;
|
|
1375
|
+
try {
|
|
1376
|
+
return LOOPBACK_HOSTS.has(new URL(url).hostname.toLowerCase());
|
|
1377
|
+
} catch {
|
|
1378
|
+
return false;
|
|
1379
|
+
}
|
|
1380
|
+
}
|
|
1381
|
+
var staleLoopbackCredentialsWarned = false;
|
|
1382
|
+
function warnStaleLoopbackCredentialsOnce(credentialsUrl, derivedUrl, { log }) {
|
|
1383
|
+
if (staleLoopbackCredentialsWarned) return;
|
|
1384
|
+
staleLoopbackCredentialsWarned = true;
|
|
499
1385
|
try {
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
1386
|
+
log(
|
|
1387
|
+
`[heyspex] ignoring console_url ${credentialsUrl} from ~/.heyspex/credentials.yaml: the resolved reliance is cloud, and a loopback console cannot serve it \u2014 using ${derivedUrl} (set HEYSPEX_CONSOLE_URL to override explicitly, or run \`hs dev mode:local\` to go back to a local console).
|
|
1388
|
+
`
|
|
1389
|
+
);
|
|
1390
|
+
} catch {
|
|
1391
|
+
}
|
|
1392
|
+
}
|
|
1393
|
+
function readCredentialsConsoleUrl({ homeDir = heyspexHome(), readFile = defaultReadFile2 } = {}) {
|
|
1394
|
+
try {
|
|
1395
|
+
const raw = readFile(join9(homeDir, ".heyspex", "credentials.yaml"));
|
|
1396
|
+
const match = String(raw ?? "").match(/^console_url:\s*["']?([^"'\r\n#]+)/m);
|
|
504
1397
|
const value = match?.[1]?.trim();
|
|
505
|
-
|
|
1398
|
+
return value && value.length > 0 ? trimTrailingSlash(value) : null;
|
|
506
1399
|
} catch {
|
|
1400
|
+
return null;
|
|
507
1401
|
}
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
1402
|
+
}
|
|
1403
|
+
function defaultReadFile2(p) {
|
|
1404
|
+
return readFileSync8(p, "utf8");
|
|
1405
|
+
}
|
|
1406
|
+
function readDiscoveryFileConsoleUrl(repoRoot, { readDiscovery = defaultReadFile2 } = {}) {
|
|
1407
|
+
if (!repoRoot) return null;
|
|
1408
|
+
try {
|
|
1409
|
+
const discovery = String(readDiscovery(join9(repoRoot, ".heyspex", "console-url")) ?? "").trim();
|
|
1410
|
+
if (discovery.length > 0) return trimTrailingSlash(discovery);
|
|
1411
|
+
} catch {
|
|
1412
|
+
}
|
|
1413
|
+
return null;
|
|
1414
|
+
}
|
|
1415
|
+
var ConsoleUrlUnresolvedError = class extends Error {
|
|
1416
|
+
constructor() {
|
|
1417
|
+
super(
|
|
1418
|
+
"resolveConsoleUrl: local reliance with no HEYSPEX_CONSOLE_URL env, no console_url: in ~/.heyspex/credentials.yaml, and no .heyspex/console-url discovery file \u2014 refusing to silently fall back to the localhost:3000 default in strict mode."
|
|
1419
|
+
);
|
|
1420
|
+
this.name = "ConsoleUrlUnresolvedError";
|
|
1421
|
+
}
|
|
1422
|
+
};
|
|
1423
|
+
function resolveModeEnvelope({ env, homeDir }) {
|
|
1424
|
+
try {
|
|
1425
|
+
const resolved = resolveDevMode({ env, homeDir });
|
|
1426
|
+
return {
|
|
1427
|
+
reliance: resolved.reliance === "cloud" ? "cloud" : "local",
|
|
1428
|
+
consoleUrl: resolved.consoleUrl || DEFAULT_CONSOLE_URL
|
|
1429
|
+
};
|
|
1430
|
+
} catch {
|
|
1431
|
+
return { reliance: "local", consoleUrl: DEFAULT_CONSOLE_URL };
|
|
1432
|
+
}
|
|
1433
|
+
}
|
|
1434
|
+
function resolveConsoleUrl({
|
|
1435
|
+
repoRoot,
|
|
1436
|
+
env = process.env,
|
|
1437
|
+
homeDir = heyspexHome(),
|
|
1438
|
+
strict = false,
|
|
1439
|
+
devMode,
|
|
1440
|
+
log = (line) => process.stderr.write(line),
|
|
1441
|
+
readFile = defaultReadFile2,
|
|
1442
|
+
readDiscovery = defaultReadFile2
|
|
1443
|
+
} = {}) {
|
|
1444
|
+
const envUrl = env.HEYSPEX_CONSOLE_URL?.trim();
|
|
1445
|
+
if (envUrl) return trimTrailingSlash(envUrl);
|
|
1446
|
+
const mode = devMode ?? resolveModeEnvelope({ env, homeDir });
|
|
1447
|
+
const cloud = mode.reliance === "cloud";
|
|
1448
|
+
const derivedUrl = trimTrailingSlash(
|
|
1449
|
+
cloud && isLoopbackConsoleUrl(mode.consoleUrl) ? CLOUD_CONSOLE_URL : mode.consoleUrl || DEFAULT_CONSOLE_URL
|
|
1450
|
+
);
|
|
1451
|
+
const credentialsUrl = readCredentialsConsoleUrl({ homeDir, readFile });
|
|
1452
|
+
if (credentialsUrl) {
|
|
1453
|
+
if (cloud && isLoopbackConsoleUrl(credentialsUrl)) {
|
|
1454
|
+
warnStaleLoopbackCredentialsOnce(credentialsUrl, derivedUrl, { log });
|
|
1455
|
+
} else {
|
|
1456
|
+
return credentialsUrl;
|
|
515
1457
|
}
|
|
516
1458
|
}
|
|
517
|
-
|
|
1459
|
+
if (!cloud) {
|
|
1460
|
+
const discoveryUrl = readDiscoveryFileConsoleUrl(repoRoot, { readDiscovery });
|
|
1461
|
+
if (discoveryUrl) return discoveryUrl;
|
|
1462
|
+
if (strict) throw new ConsoleUrlUnresolvedError();
|
|
1463
|
+
}
|
|
1464
|
+
return derivedUrl;
|
|
1465
|
+
}
|
|
1466
|
+
|
|
1467
|
+
// spex-console/src/lib/cli/defer.ts
|
|
1468
|
+
function resolveSpexConsoleUrl(vaultRoot, readDiscovery, opts = {}) {
|
|
1469
|
+
return resolveConsoleUrl({
|
|
1470
|
+
repoRoot: vaultRoot,
|
|
1471
|
+
env: opts.env,
|
|
1472
|
+
homeDir: opts.homeDir,
|
|
1473
|
+
readFile: opts.readFile,
|
|
1474
|
+
readDiscovery,
|
|
1475
|
+
log: opts.log
|
|
1476
|
+
});
|
|
518
1477
|
}
|
|
519
1478
|
var HELP2 = [
|
|
520
1479
|
"Usage:",
|
|
@@ -538,16 +1497,44 @@ var HELP2 = [
|
|
|
538
1497
|
"Exit codes: 0 ok \xB7 1 usage \xB7 10 not-found \xB7 11 bad-arg \xB7 12 console-unreachable \xB7 13 write-failed"
|
|
539
1498
|
].join("\n");
|
|
540
1499
|
|
|
1500
|
+
// spex-console/bin/run-entry.ts
|
|
1501
|
+
import process2 from "node:process";
|
|
1502
|
+
var BinExit = class extends Error {
|
|
1503
|
+
code;
|
|
1504
|
+
constructor(code) {
|
|
1505
|
+
super(`exit ${code}`);
|
|
1506
|
+
this.name = "BinExit";
|
|
1507
|
+
this.code = code;
|
|
1508
|
+
}
|
|
1509
|
+
};
|
|
1510
|
+
function runEntry(name, main2, options = {}) {
|
|
1511
|
+
const { fallbackCode = 1, unhandledLabel = "unhandled" } = options;
|
|
1512
|
+
void (async () => {
|
|
1513
|
+
try {
|
|
1514
|
+
await main2();
|
|
1515
|
+
} catch (err) {
|
|
1516
|
+
if (err instanceof BinExit) {
|
|
1517
|
+
process2.exitCode = err.code;
|
|
1518
|
+
return;
|
|
1519
|
+
}
|
|
1520
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1521
|
+
process2.stderr.write(`${name}: ${unhandledLabel}: ${message}
|
|
1522
|
+
`);
|
|
1523
|
+
process2.exitCode = fallbackCode;
|
|
1524
|
+
}
|
|
1525
|
+
})();
|
|
1526
|
+
}
|
|
1527
|
+
|
|
541
1528
|
// spex-console/bin/hs-check.ts
|
|
542
1529
|
function manifestForSpecId(vaultRoot, specId) {
|
|
543
1530
|
return path2.join(vaultRoot, "specs", "checks", `${specId}.checks.json`);
|
|
544
1531
|
}
|
|
545
1532
|
function resolveManifestPath(target, vaultRoot, invokedCwd) {
|
|
546
1533
|
const literal = path2.resolve(invokedCwd, target);
|
|
547
|
-
if (
|
|
1534
|
+
if (existsSync5(literal)) return literal;
|
|
548
1535
|
if (target.endsWith(".json")) return null;
|
|
549
1536
|
const byId = manifestForSpecId(vaultRoot, target);
|
|
550
|
-
return
|
|
1537
|
+
return existsSync5(byId) ? byId : null;
|
|
551
1538
|
}
|
|
552
1539
|
function countTscErrors(output) {
|
|
553
1540
|
const matches = output.match(/error TS\d+/g);
|
|
@@ -557,31 +1544,31 @@ var cachedPosixShell;
|
|
|
557
1544
|
function resolveWindowsPosixShell() {
|
|
558
1545
|
if (cachedPosixShell !== void 0) return cachedPosixShell;
|
|
559
1546
|
const candidates = [
|
|
560
|
-
|
|
1547
|
+
process3.env.HEYSPEX_CHECK_SHELL,
|
|
561
1548
|
"C:\\Program Files\\Git\\bin\\bash.exe"
|
|
562
1549
|
].filter((c) => typeof c === "string" && c.length > 0);
|
|
563
1550
|
for (const candidate of candidates) {
|
|
564
|
-
if (
|
|
1551
|
+
if (existsSync5(candidate)) return cachedPosixShell = candidate;
|
|
565
1552
|
}
|
|
566
1553
|
const where = spawnSync("where", ["bash"], { encoding: "utf8", windowsHide: true });
|
|
567
1554
|
if (where.status === 0 && where.stdout) {
|
|
568
1555
|
const hit = where.stdout.split(/\r?\n/).map((line) => line.trim()).filter(Boolean).find((line) => !/\\system32\\/i.test(line));
|
|
569
|
-
if (hit &&
|
|
1556
|
+
if (hit && existsSync5(hit)) return cachedPosixShell = hit;
|
|
570
1557
|
}
|
|
571
1558
|
return cachedPosixShell = null;
|
|
572
1559
|
}
|
|
573
1560
|
var MISSING_BINARY_RE = /command not found|is not recognized as an internal or external command/i;
|
|
574
1561
|
var warnedCmdFallback = false;
|
|
575
1562
|
function runCheckInDirectory(check, cwd) {
|
|
576
|
-
return new Promise((
|
|
1563
|
+
return new Promise((resolve5) => {
|
|
577
1564
|
const started = Date.now();
|
|
578
1565
|
const needsErrorCount = check.expect === "no-net-new";
|
|
579
1566
|
const needsStdout = check.expect === "empty-output";
|
|
580
1567
|
const stdio = ["ignore", "pipe", "pipe"];
|
|
581
|
-
const posixShell =
|
|
582
|
-
if (
|
|
1568
|
+
const posixShell = process3.platform === "win32" ? resolveWindowsPosixShell() : null;
|
|
1569
|
+
if (process3.platform === "win32" && !posixShell && !warnedCmdFallback) {
|
|
583
1570
|
warnedCmdFallback = true;
|
|
584
|
-
|
|
1571
|
+
process3.stderr.write(
|
|
585
1572
|
"hs check: WARNING \u2014 no POSIX shell found (git-bash missing; set HEYSPEX_CHECK_SHELL to a bash.exe).\n Falling back to cmd.exe: POSIX-authored checks (test/grep/find/globs) will MISREPORT \u2014 false-fails, and inverted checks can false-PASS (HS-FCW8).\n"
|
|
586
1573
|
);
|
|
587
1574
|
}
|
|
@@ -596,7 +1583,7 @@ function runCheckInDirectory(check, cwd) {
|
|
|
596
1583
|
captured += chunk.toString();
|
|
597
1584
|
});
|
|
598
1585
|
child.on("error", (err) => {
|
|
599
|
-
|
|
1586
|
+
resolve5({ exitCode: -1, durationMs: Date.now() - started, message: err.message });
|
|
600
1587
|
});
|
|
601
1588
|
child.on("close", (code) => {
|
|
602
1589
|
const durationMs = Date.now() - started;
|
|
@@ -615,15 +1602,16 @@ function runCheckInDirectory(check, cwd) {
|
|
|
615
1602
|
const trimmed = captured.trim();
|
|
616
1603
|
outcome.message = trimmed.length > 0 ? trimmed.split("\n").slice(-3).join(" ") : `command exited ${exitCode}`;
|
|
617
1604
|
}
|
|
618
|
-
|
|
1605
|
+
resolve5(outcome);
|
|
619
1606
|
});
|
|
620
1607
|
});
|
|
621
1608
|
}
|
|
622
|
-
function makeRunCheck(vaultRoot) {
|
|
1609
|
+
function makeRunCheck(vaultRoot, codeRoot) {
|
|
623
1610
|
return async (check) => {
|
|
624
1611
|
const cwds = resolveCheckCwds(check, {
|
|
625
|
-
repoRoot:
|
|
626
|
-
consoleRoot: path2.join(
|
|
1612
|
+
repoRoot: codeRoot,
|
|
1613
|
+
consoleRoot: path2.join(codeRoot, "spex-console"),
|
|
1614
|
+
vaultRoot
|
|
627
1615
|
});
|
|
628
1616
|
let lastFailure = null;
|
|
629
1617
|
for (const cwd of cwds) {
|
|
@@ -643,105 +1631,141 @@ function makeRunCheck(vaultRoot) {
|
|
|
643
1631
|
};
|
|
644
1632
|
}
|
|
645
1633
|
async function main() {
|
|
646
|
-
const vaultRoot =
|
|
1634
|
+
const vaultRoot = process3.env.HS_VAULT_ROOT;
|
|
647
1635
|
if (!vaultRoot) {
|
|
648
|
-
|
|
1636
|
+
process3.stderr.write(
|
|
649
1637
|
"hs check: HS_VAULT_ROOT is not set (the dispatcher must resolve and pass it).\n"
|
|
650
1638
|
);
|
|
651
|
-
|
|
1639
|
+
process3.exitCode = EXIT.USAGE;
|
|
1640
|
+
return;
|
|
652
1641
|
}
|
|
653
|
-
|
|
1642
|
+
if (process3.argv[3] === "judge") {
|
|
1643
|
+
const pkgRoot = resolveCodeRoot({ vaultRoot });
|
|
1644
|
+
const judgeScript = path2.join(pkgRoot, "dev-factory", "judge.mjs");
|
|
1645
|
+
if (!existsSync5(judgeScript)) {
|
|
1646
|
+
process3.stderr.write(
|
|
1647
|
+
`hs check judge: script not found at ${judgeScript} (source checkout only \u2014 not available in a published install).
|
|
1648
|
+
`
|
|
1649
|
+
);
|
|
1650
|
+
process3.exitCode = EXIT.NOT_FOUND;
|
|
1651
|
+
return;
|
|
1652
|
+
}
|
|
1653
|
+
const forwardArgs = process3.argv.slice(4);
|
|
1654
|
+
const child = spawnSync(process3.execPath, [judgeScript, ...forwardArgs], {
|
|
1655
|
+
cwd: pkgRoot,
|
|
1656
|
+
stdio: "inherit",
|
|
1657
|
+
windowsHide: true
|
|
1658
|
+
});
|
|
1659
|
+
process3.exitCode = child.status ?? 1;
|
|
1660
|
+
return;
|
|
1661
|
+
}
|
|
1662
|
+
const parsed = parseCheckRunArgs(process3.argv.slice(3));
|
|
654
1663
|
if (parsed.help) {
|
|
655
|
-
|
|
1664
|
+
process3.stdout.write(`${HELP}
|
|
656
1665
|
`);
|
|
657
|
-
|
|
1666
|
+
process3.exitCode = EXIT.OK;
|
|
1667
|
+
return;
|
|
658
1668
|
}
|
|
659
1669
|
if (parsed.error) {
|
|
660
|
-
|
|
1670
|
+
process3.stderr.write(`hs check: ${parsed.error.message}
|
|
661
1671
|
`);
|
|
662
|
-
|
|
1672
|
+
process3.exitCode = parsed.error.code;
|
|
1673
|
+
return;
|
|
663
1674
|
}
|
|
664
1675
|
const args = parsed.args;
|
|
665
|
-
const invokedCwd =
|
|
1676
|
+
const invokedCwd = process3.env.HS_INVOKED_CWD?.trim() || process3.cwd();
|
|
666
1677
|
const manifestPath = resolveManifestPath(args.target, vaultRoot, invokedCwd);
|
|
667
1678
|
if (!manifestPath) {
|
|
668
|
-
|
|
1679
|
+
process3.stderr.write(
|
|
669
1680
|
`hs check: no manifest for "${args.target}".
|
|
670
1681
|
Pass a manifest path, or create ${path2.join("specs", "checks", `${args.target}.checks.json`)} under the vault root.
|
|
671
1682
|
`
|
|
672
1683
|
);
|
|
673
|
-
|
|
1684
|
+
process3.exitCode = EXIT.NOT_FOUND;
|
|
1685
|
+
return;
|
|
674
1686
|
}
|
|
675
|
-
const { manifest, error } = parseChecksManifest(
|
|
1687
|
+
const { manifest, error } = parseChecksManifest(readFileSync9(manifestPath, "utf-8"));
|
|
676
1688
|
if (error) {
|
|
677
|
-
|
|
1689
|
+
process3.stderr.write(`hs check: ${manifestPath}: ${error}
|
|
678
1690
|
`);
|
|
679
|
-
|
|
1691
|
+
process3.exitCode = EXIT.INVALID_MANIFEST;
|
|
1692
|
+
return;
|
|
680
1693
|
}
|
|
681
|
-
const
|
|
682
|
-
const
|
|
1694
|
+
const codeRoot = resolveCodeRoot({ vaultRoot });
|
|
1695
|
+
const report = await runChecksManifest(manifest, {
|
|
1696
|
+
runCheck: makeRunCheck(vaultRoot, codeRoot)
|
|
1697
|
+
});
|
|
1698
|
+
const verifierSeatDecision = grantsClose({
|
|
1699
|
+
manifestFound: true,
|
|
1700
|
+
parseError: null,
|
|
1701
|
+
criteria: report.criteria.filter((c) => c.method !== "human").map((c) => ({ index: c.ac, passed: c.verdict === "pass" }))
|
|
1702
|
+
});
|
|
1703
|
+
const sink = report.passed ? process3.stdout : process3.stderr;
|
|
683
1704
|
if (args.json) {
|
|
684
|
-
sink.write(
|
|
685
|
-
|
|
1705
|
+
sink.write(
|
|
1706
|
+
`${JSON.stringify({ ...reportToJson(report), verifierSeat: verifierSeatDecision })}
|
|
1707
|
+
`
|
|
1708
|
+
);
|
|
686
1709
|
} else {
|
|
687
1710
|
sink.write(`${formatReport(report)}
|
|
688
1711
|
`);
|
|
689
1712
|
}
|
|
690
1713
|
if (args.report) {
|
|
691
|
-
await reportVerdict(args.target, report, vaultRoot);
|
|
1714
|
+
await reportVerdict(args.target, report, vaultRoot, args.unattended);
|
|
692
1715
|
}
|
|
693
|
-
|
|
1716
|
+
process3.exitCode = reportExitCode(report);
|
|
694
1717
|
}
|
|
695
|
-
async function reportVerdict(target, report, vaultRoot) {
|
|
696
|
-
if (
|
|
697
|
-
|
|
1718
|
+
async function reportVerdict(target, report, vaultRoot, unattended = false) {
|
|
1719
|
+
if (existsSync5(target) || target.endsWith(".json")) {
|
|
1720
|
+
process3.stderr.write(
|
|
698
1721
|
"hs check: --report needs a spec-id target (to identify the claim), not a manifest path.\n"
|
|
699
1722
|
);
|
|
700
1723
|
return;
|
|
701
1724
|
}
|
|
702
1725
|
const consoleUrl = resolveSpexConsoleUrl(vaultRoot, (p) => {
|
|
703
1726
|
try {
|
|
704
|
-
return
|
|
1727
|
+
return readFileSync9(p, "utf-8");
|
|
705
1728
|
} catch {
|
|
706
1729
|
return null;
|
|
707
1730
|
}
|
|
708
1731
|
});
|
|
709
1732
|
const verdict = buildVerdict(report);
|
|
1733
|
+
const scoped = resolveControlScopeFromMachine(vaultRoot);
|
|
1734
|
+
if (!scoped.ok) {
|
|
1735
|
+
process3.stderr.write(`hs check: verdict not reported \u2014 ${scoped.error.message}
|
|
1736
|
+
`);
|
|
1737
|
+
return;
|
|
1738
|
+
}
|
|
710
1739
|
const res = await controlRequest(
|
|
711
|
-
{ fetchImpl: fetch, consoleUrl },
|
|
1740
|
+
{ fetchImpl: fetch, consoleUrl, scope: scoped.scope },
|
|
712
1741
|
{
|
|
713
1742
|
method: "PATCH",
|
|
714
1743
|
path: `/api/specs/${encodeURIComponent(target)}/claim/status`,
|
|
715
|
-
body: { status: "working", verification: verdict }
|
|
1744
|
+
body: { status: "working", verification: verdict, unattended }
|
|
716
1745
|
}
|
|
717
1746
|
);
|
|
718
1747
|
if (!res.ok) {
|
|
719
|
-
|
|
1748
|
+
process3.stderr.write(`hs check: verdict not reported \u2014 ${res.error.message}
|
|
720
1749
|
`);
|
|
721
1750
|
return;
|
|
722
1751
|
}
|
|
723
1752
|
if (res.status === 404) {
|
|
724
|
-
|
|
1753
|
+
process3.stderr.write(
|
|
725
1754
|
`hs check: no active claim for ${target} \u2014 verdict not reported (claim it first).
|
|
726
1755
|
`
|
|
727
1756
|
);
|
|
728
1757
|
return;
|
|
729
1758
|
}
|
|
730
1759
|
if (res.status !== 200) {
|
|
731
|
-
|
|
1760
|
+
process3.stderr.write(`hs check: verdict not reported \u2014 console returned ${res.status}.
|
|
732
1761
|
`);
|
|
733
1762
|
return;
|
|
734
1763
|
}
|
|
735
1764
|
const flipped = res.body?.autoShip;
|
|
736
1765
|
const note = flipped && flipped.flipped !== false ? ` \u2192 gate flipped to ${flipped.effective_status}` : "";
|
|
737
|
-
|
|
1766
|
+
process3.stderr.write(
|
|
738
1767
|
`hs check: reported verdict (manifestPassed=${verdict.manifestPassed})${note}
|
|
739
1768
|
`
|
|
740
1769
|
);
|
|
741
1770
|
}
|
|
742
|
-
|
|
743
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
744
|
-
process2.stderr.write(`hs check: unhandled: ${message}
|
|
745
|
-
`);
|
|
746
|
-
process2.exit(EXIT.RUNNER_MISSING);
|
|
747
|
-
});
|
|
1771
|
+
runEntry("hs check", main, { fallbackCode: EXIT.RUNNER_MISSING });
|