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