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/bin/hs.mjs
CHANGED
|
@@ -12,16 +12,240 @@
|
|
|
12
12
|
* node_modules) executes the .ts entrypoints.
|
|
13
13
|
*/
|
|
14
14
|
import { spawn } from "node:child_process";
|
|
15
|
-
import { existsSync, readFileSync,
|
|
15
|
+
import { existsSync, readFileSync, realpathSync } from "node:fs";
|
|
16
16
|
import path from "node:path";
|
|
17
17
|
import process from "node:process";
|
|
18
18
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
19
19
|
|
|
20
|
-
import {
|
|
20
|
+
import {
|
|
21
|
+
foldConnectionReport,
|
|
22
|
+
runBundledConnectCheck,
|
|
23
|
+
runtimeFilesCheckRow,
|
|
24
|
+
templateCheckRow,
|
|
25
|
+
vaultConfigCheckRow,
|
|
26
|
+
} from "../scripts/lib/standalone-doctor.mjs";
|
|
27
|
+
import {
|
|
28
|
+
isVaultRoot,
|
|
29
|
+
readSatellitePin,
|
|
30
|
+
resolveActiveVault,
|
|
31
|
+
} from "../vendor/heyspex-mcp/vault-resolve.mjs";
|
|
21
32
|
|
|
22
33
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
23
34
|
const PKG_ROOT = path.resolve(__dirname, "..");
|
|
24
35
|
|
|
36
|
+
/**
|
|
37
|
+
* Every script path `hs` spawns directly by filesystem path (as opposed to
|
|
38
|
+
* `dispatchSpexConsoleEntry`'s own three-case tsx/bundle/missing resolution,
|
|
39
|
+
* which already carries its own existence handling). Each entry is
|
|
40
|
+
* PKG_ROOT-relative, posix-separated.
|
|
41
|
+
*
|
|
42
|
+
* Why this exists ([[HS-561M]]): `scripts/verify-mcp-package.mjs`'s
|
|
43
|
+
* `checkClosure` proves the STATIC IMPORT closure of vendor/heyspex-mcp/cli.mjs
|
|
44
|
+
* is fully packed — it says nothing about the ~15 verbs below that reach a
|
|
45
|
+
* script by `spawn()`ing a path string, which is invisible to any import-graph
|
|
46
|
+
* walk. Every dispatch site below resolves its script path THROUGH this
|
|
47
|
+
* manifest (`path.join(PKG_ROOT, SPAWN_TARGETS[key].path)`) instead of
|
|
48
|
+
* building the path inline, so `verify-mcp-package.mjs`'s `spawnTargets` check
|
|
49
|
+
* (which imports this const) can assert every non-`devOnly` entry is actually
|
|
50
|
+
* in the packed tarball file list, naming whichever one is missing.
|
|
51
|
+
*
|
|
52
|
+
* `devOnly: true` entries are scripts this package deliberately does NOT ship
|
|
53
|
+
* (see each `reason`) — the pack gate skips them, and the dispatch site for
|
|
54
|
+
* each already guards the missing case at runtime with a clear message
|
|
55
|
+
* (`doctor` falls back to `runStandaloneDoctor`; `skills` prints a
|
|
56
|
+
* published-install notice — see their dispatch sites below).
|
|
57
|
+
*/
|
|
58
|
+
export const SPAWN_TARGETS = {
|
|
59
|
+
"setup:claude": { path: "scripts/install-claude-code-hooks.mjs" },
|
|
60
|
+
"setup:cursor": { path: "scripts/install-cursor-hooks.mjs" },
|
|
61
|
+
"skills:sync": {
|
|
62
|
+
path: "scripts/sync-skills.mjs",
|
|
63
|
+
devOnly: true,
|
|
64
|
+
reason:
|
|
65
|
+
"sync-skills.mjs propagates THIS platform repo's OWN .commandcode/skills " +
|
|
66
|
+
"source content (Command Code SSOT, resolved from the script's own file " +
|
|
67
|
+
"location, not the caller's vault) — that content has no meaning outside " +
|
|
68
|
+
"a clone of the HeySpex platform repo and is not shipped.",
|
|
69
|
+
},
|
|
70
|
+
"heyspex-mcp-cli": { path: "vendor/heyspex-mcp/cli.mjs" }, // `hs login` + `hs mcp *`
|
|
71
|
+
doctor: {
|
|
72
|
+
path: "scripts/heyspex-doctor.mjs",
|
|
73
|
+
devOnly: true,
|
|
74
|
+
reason:
|
|
75
|
+
"the full doctor audits dozens of source-checkout-only surfaces (git " +
|
|
76
|
+
"hooks, Cursor wiring, console secrets, ...) a published install never " +
|
|
77
|
+
"has by design — the dispatch site below already falls back to " +
|
|
78
|
+
"runStandaloneDoctor() when this file is absent.",
|
|
79
|
+
},
|
|
80
|
+
"server:status": { path: "scripts/dev-server-status.mjs" },
|
|
81
|
+
"server:stop": { path: "scripts/dev-server-stop.mjs" },
|
|
82
|
+
wip: { path: "scripts/wip-ledger-health.mjs" },
|
|
83
|
+
worktree: { path: "scripts/hs-worktree.mjs" },
|
|
84
|
+
"source:status": { path: "scripts/lib/hs-source-preflight.mjs" },
|
|
85
|
+
"source:preflight": { path: "scripts/lib/hs-source-preflight.mjs" },
|
|
86
|
+
"source:commit": { path: "scripts/lib/hs-source-commit.mjs" },
|
|
87
|
+
"source:push": { path: "scripts/lib/hs-source-push.mjs" },
|
|
88
|
+
"source:log": { path: "scripts/lib/hs-source-log.mjs" },
|
|
89
|
+
// `hs source identity` — the ONE sanctioned git-identity writer (human-only
|
|
90
|
+
// set/clear, read-only show). Incident 2026-09-19, vault lesson
|
|
91
|
+
// `when-git-says-author-identity-unknown`; docs/admin/workflow/git-identity.md.
|
|
92
|
+
"source:identity": { path: "scripts/lib/hs-source-identity.mjs" },
|
|
93
|
+
view: { path: "scripts/hs-view.mjs" },
|
|
94
|
+
capture: { path: "scripts/hs-capture-cli.mjs" },
|
|
95
|
+
companion: { path: "scripts/hs-companion.mjs" },
|
|
96
|
+
next: { path: "scripts/hs-next.mjs" },
|
|
97
|
+
mode: { path: "scripts/hs-mode.mjs" },
|
|
98
|
+
gateway: { path: "scripts/hs-gateway.mjs" },
|
|
99
|
+
settings: { path: "scripts/hs-settings.mjs" },
|
|
100
|
+
classifier: { path: "scripts/hs-classifier.mjs" },
|
|
101
|
+
auto: { path: "scripts/hs-auto.mjs" },
|
|
102
|
+
"plugin:launch-mcp": { path: "plugins/heyspex/bin/launch-mcp.mjs" },
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Neon control-plane verbs ([[HS-EPIC-CLMW-hs-cli-mcp-wrappers-for-control-plane]]):
|
|
107
|
+
* each namespace maps to a spex-console TS entry, dispatched via
|
|
108
|
+
* `dispatchSpexConsoleEntry` in `main()` below. Hoisted to module scope (was
|
|
109
|
+
* previously declared inline inside `main()`) and exported alongside
|
|
110
|
+
* `SPAWN_TARGETS` so `BUILTIN_VERBS` below — and anything else that needs the
|
|
111
|
+
* full verb manifest, e.g. `scripts/verify-verb-sweep.mjs` — can derive these
|
|
112
|
+
* eight verb names from the dispatcher's own table instead of hand-copying it.
|
|
113
|
+
*/
|
|
114
|
+
export const CONTROL_ENTRYPOINTS = {
|
|
115
|
+
pin: "hs-pin.ts",
|
|
116
|
+
claim: "hs-claim.ts",
|
|
117
|
+
handoff: "hs-handoff.ts",
|
|
118
|
+
events: "hs-events.ts",
|
|
119
|
+
"export-events": "hs-export-events.ts",
|
|
120
|
+
prereq: "hs-prereq.ts",
|
|
121
|
+
queue: "hs-queue.ts",
|
|
122
|
+
// `hs model` - one read-only list of every seat that has a model behind it
|
|
123
|
+
// (launch seats from settings.json, SpexFleet seats from routing-table.json).
|
|
124
|
+
model: "hs-model.ts",
|
|
125
|
+
// `hs trash` - the bin every spec delete soft-deletes into. Nothing looked
|
|
126
|
+
// at it before, so deleted specs accumulated on disk forever.
|
|
127
|
+
trash: "hs-trash.ts",
|
|
128
|
+
// `hs admin test-user create` — mint one throwaway test customer against a
|
|
129
|
+
// stated environment ([[HS-4Z1N-SMXX-EPIC-hs-admin-test-user-create-one-command]]).
|
|
130
|
+
// Wraps POST /api/admin/users/create, so it belongs with the other verbs
|
|
131
|
+
// that are thin shells over a console endpoint.
|
|
132
|
+
admin: "hs-admin.ts",
|
|
133
|
+
// `hs seat` — read/add/update/disable a SpexFleet seat, matching
|
|
134
|
+
// /api/seat-registry's operations 1:1 ([[HS-W2H1-NYPR-TASK-create-the-api-endpoint-that-lets-a]]
|
|
135
|
+
// of epic [[HS-RJVM-NYPR-EPIC-seat-registry-every-role-spexfleet-runs]]).
|
|
136
|
+
// Reads/writes dev-factory/seats.json directly through `@/lib/seat-registry`
|
|
137
|
+
// — the same core the API route calls — so it works with the console
|
|
138
|
+
// stopped, same shape as `hs model` / `hs registry`.
|
|
139
|
+
seat: "hs-seat.ts",
|
|
140
|
+
// `hs voice` — add/build/show/set/test/refresh/export a coded voice profile,
|
|
141
|
+
// matching /api/voice's operations 1:1
|
|
142
|
+
// ([[HS-PQPJ-DOJY-TASK-build-the-hs-voice-cli-and-matching]] of epic
|
|
143
|
+
// [[HS-8PG1-DOJY-EPIC-voice-as-a-deterministic-system-a-coded]]). Reads/
|
|
144
|
+
// writes .heyspex/voice-profiles directly through @/lib/voice-cli — the
|
|
145
|
+
// same core the API route calls — so it works with the console stopped,
|
|
146
|
+
// same shape as `hs model` / `hs seat`.
|
|
147
|
+
voice: "hs-voice.ts",
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Every top-level `hs <namespace>` verb `main()` recognizes below, listed in
|
|
152
|
+
* the same order as their dispatch branches — the single manifest
|
|
153
|
+
* [[HS-G65Z-TASK-every-hs-verb-sweeps-clean-against-a]]'s
|
|
154
|
+
* `scripts/verify-verb-sweep.mjs` imports to enumerate what to sweep, instead
|
|
155
|
+
* of hand-maintaining a second, driftable copy of this list in the sweep
|
|
156
|
+
* script. Deleting a verb's dispatch branch above and its entry here (both in
|
|
157
|
+
* this file) is enough to drop it from the sweep with no sweep-script edit.
|
|
158
|
+
* Does NOT distinguish spawn-backed vs. spex-console-TS-backed vs. bundled —
|
|
159
|
+
* `SPAWN_TARGETS` above already carries that detail for the verbs it covers;
|
|
160
|
+
* this list is deliberately just "what does `hs` accept as argv[0]".
|
|
161
|
+
*/
|
|
162
|
+
export const BUILTIN_VERBS = [
|
|
163
|
+
"init",
|
|
164
|
+
"install",
|
|
165
|
+
"join",
|
|
166
|
+
"invite",
|
|
167
|
+
"setup",
|
|
168
|
+
"skills",
|
|
169
|
+
"vault",
|
|
170
|
+
"workspace",
|
|
171
|
+
"login",
|
|
172
|
+
"logout",
|
|
173
|
+
"whoami",
|
|
174
|
+
"connect",
|
|
175
|
+
"doctor",
|
|
176
|
+
"index",
|
|
177
|
+
"pull",
|
|
178
|
+
"milestone",
|
|
179
|
+
"factory",
|
|
180
|
+
"code-watcher",
|
|
181
|
+
"server",
|
|
182
|
+
"wip",
|
|
183
|
+
"worktree",
|
|
184
|
+
"source",
|
|
185
|
+
"view",
|
|
186
|
+
"dev",
|
|
187
|
+
"defer",
|
|
188
|
+
"undefer",
|
|
189
|
+
"spawn",
|
|
190
|
+
"capture",
|
|
191
|
+
"companion",
|
|
192
|
+
"registry",
|
|
193
|
+
"env",
|
|
194
|
+
"next",
|
|
195
|
+
"mode",
|
|
196
|
+
"gateway",
|
|
197
|
+
"settings",
|
|
198
|
+
"classifier",
|
|
199
|
+
"auto",
|
|
200
|
+
"mcp",
|
|
201
|
+
"test",
|
|
202
|
+
"harness",
|
|
203
|
+
"hooks",
|
|
204
|
+
"bench",
|
|
205
|
+
"check",
|
|
206
|
+
"revalidate",
|
|
207
|
+
"agent",
|
|
208
|
+
"chat",
|
|
209
|
+
"trust",
|
|
210
|
+
"doctrine",
|
|
211
|
+
...Object.keys(CONTROL_ENTRYPOINTS),
|
|
212
|
+
];
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Does this `hs next ...` invocation need the console? Mirror of
|
|
216
|
+
* `isNextConsoleCommand` in `spex-console/src/lib/cli/next.ts` - that module
|
|
217
|
+
* owns the rule; this copy exists because the dispatcher must choose a half
|
|
218
|
+
* BEFORE spawning either one, and cannot import TypeScript. Keep the two in
|
|
219
|
+
* step: any launch/settings flag added there belongs here too.
|
|
220
|
+
*/
|
|
221
|
+
export function isNextConsoleArgv(args) {
|
|
222
|
+
if (
|
|
223
|
+
args[0] === "model" ||
|
|
224
|
+
args[0] === "permission-mode" ||
|
|
225
|
+
args[0] === "claim" ||
|
|
226
|
+
args[0] === "front"
|
|
227
|
+
)
|
|
228
|
+
return true;
|
|
229
|
+
return args.some(
|
|
230
|
+
(a) =>
|
|
231
|
+
a === "--launch" ||
|
|
232
|
+
a === "--force" ||
|
|
233
|
+
a === "--claim" ||
|
|
234
|
+
a === "--no-claim" ||
|
|
235
|
+
a === "--model" ||
|
|
236
|
+
a.startsWith("--model=") ||
|
|
237
|
+
a === "--permission-mode" ||
|
|
238
|
+
a.startsWith("--permission-mode="),
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/** Resolve a SPAWN_TARGETS entry to an absolute path under PKG_ROOT. */
|
|
243
|
+
function spawnTargetPath(key) {
|
|
244
|
+
const entry = SPAWN_TARGETS[key];
|
|
245
|
+
if (!entry) throw new Error(`hs: unknown SPAWN_TARGETS key "${key}"`);
|
|
246
|
+
return path.join(PKG_ROOT, ...entry.path.split("/"));
|
|
247
|
+
}
|
|
248
|
+
|
|
25
249
|
// Captured before anything in this process could ever chdir (it doesn't
|
|
26
250
|
// today, but every dispatched entry below is spawned with its own `cwd`
|
|
27
251
|
// override — see dispatchSpexConsoleEntry). This is the ONE true record of
|
|
@@ -42,22 +266,167 @@ const INVOKED_CWD = process.cwd();
|
|
|
42
266
|
process.env.HS_INVOKED_CWD = INVOKED_CWD;
|
|
43
267
|
|
|
44
268
|
/**
|
|
45
|
-
* Walk up from `start` until we find a directory containing
|
|
46
|
-
*
|
|
269
|
+
* Walk up from `start` until we find a vault root — a directory containing
|
|
270
|
+
* a `heyspex.yaml` marker file OR a `specs/` dir. Delegates the "is this a
|
|
271
|
+
* vault root" test to the shared `isVaultRoot` helper (same one the MCP
|
|
272
|
+
* resolver's cwd walk uses) so the two walks can never disagree about what
|
|
273
|
+
* counts as a vault ([[HS-GKAI]]).
|
|
47
274
|
*/
|
|
48
|
-
function findVaultRoot(start) {
|
|
275
|
+
export function findVaultRoot(start, stopAt) {
|
|
49
276
|
let dir = path.resolve(start);
|
|
277
|
+
const boundary = stopAt ? path.resolve(stopAt) : null;
|
|
50
278
|
while (true) {
|
|
51
|
-
|
|
52
|
-
|
|
279
|
+
// A SATELLITE repo (a code repo attached to an existing vault -- see
|
|
280
|
+
// spex-console/src/lib/cli/satellite.ts) carries no marker of its own and
|
|
281
|
+
// names its vault in its MCP manifests instead. Checked FIRST at each
|
|
282
|
+
// level so an explicit attachment beats a vault that merely happens to
|
|
283
|
+
// sit further up the tree, and so `hs` in a satellite follows THAT vault
|
|
284
|
+
// rather than whatever the machine-global active pointer last named.
|
|
285
|
+
const pinned = readSatellitePin(dir);
|
|
286
|
+
if (pinned) {
|
|
287
|
+
return pinned;
|
|
288
|
+
}
|
|
289
|
+
if (isVaultRoot(dir)) {
|
|
53
290
|
return dir;
|
|
54
291
|
}
|
|
292
|
+
// `stopAt` is a TEST-ONLY walk boundary (mirrors vault-resolve.mjs's
|
|
293
|
+
// `cwdWalkRoot`) — checked, then the walk stops, never climbing past it.
|
|
294
|
+
// Unset in production, so the walk always reaches the real filesystem
|
|
295
|
+
// root; tests fence it inside a disposable fixture so an unrelated
|
|
296
|
+
// ancestor higher up the real tree (e.g. a stray `specs/` dir left over
|
|
297
|
+
// in a temp folder) cannot produce a false match ([[HS-0I1G]]).
|
|
298
|
+
if (boundary && dir === boundary) return null;
|
|
55
299
|
const parent = path.dirname(dir);
|
|
56
300
|
if (parent === dir) return null;
|
|
57
301
|
dir = parent;
|
|
58
302
|
}
|
|
59
303
|
}
|
|
60
304
|
|
|
305
|
+
/**
|
|
306
|
+
* Registry fallback for when the specs/ walk-up finds nothing — since the
|
|
307
|
+
* repo split (2026-08-31) the vault lives outside the code repo, so `hs`
|
|
308
|
+
* run from the code repo would otherwise exit "no vault detected".
|
|
309
|
+
* Delegates to the shared resolver (same one the MCP server trusts) but
|
|
310
|
+
* only accepts a result that names a real vault: the resolver's last-resort
|
|
311
|
+
* cwd fallback is rejected so the "no vault detected" error below still
|
|
312
|
+
* fires when there is genuinely no vault anywhere.
|
|
313
|
+
*
|
|
314
|
+
* The test is deliberately "anything but `cwd-heuristic`", so every real
|
|
315
|
+
* tier passes it: `explicit`, `account-pointer`, `env-vault-root`,
|
|
316
|
+
* `env-vault-dir`, and `satellite-pin` (an on-disk manifest attachment found
|
|
317
|
+
* by the resolver's tier-3 walk -- that tier reported the untrue
|
|
318
|
+
* `env-vault-root` until 2026-09-18; both labels are accepted here, so the
|
|
319
|
+
* rename changed nothing). `cwd-marker` and `marker-registry-mismatch` (a
|
|
320
|
+
* heyspex.yaml `vault_id` named the vault, [[HS-VVNQ]]) pass too.
|
|
321
|
+
*/
|
|
322
|
+
export function findRegistryVaultRoot(options = {}) {
|
|
323
|
+
let resolved;
|
|
324
|
+
try {
|
|
325
|
+
resolved = resolveActiveVault(options);
|
|
326
|
+
} catch {
|
|
327
|
+
return null;
|
|
328
|
+
}
|
|
329
|
+
if (!resolved || resolved.source === "cwd-heuristic") return null;
|
|
330
|
+
if (!existsSync(resolved.specsDirAbs)) return null;
|
|
331
|
+
return resolved.repoRootAbs;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* The vault-resolution ladder `main()` runs at the top of every invocation,
|
|
336
|
+
* factored out so it is unit-testable without spawning a child process
|
|
337
|
+
* ([[HS-0I1G-B0IT-TASK-track-and-announce-the-registry-active]]).
|
|
338
|
+
*
|
|
339
|
+
* Reports `usedRegistryFallback: true` only when NONE of `HS_VAULT_ROOT`, an
|
|
340
|
+
* explicit `--target`-equivalent, or a marker/satellite pin found walking up
|
|
341
|
+
* from `cwd` answered, and `findRegistryVaultRoot()` (the machine's
|
|
342
|
+
* last-active vault, `~/.heyspex/registry.json`) is what resolved the root.
|
|
343
|
+
* `hs install` uses that flag to warn (always) and — when `--register-vault`
|
|
344
|
+
* is given without `--dry-run` — refuse, so a customer onboarding an empty
|
|
345
|
+
* directory doesn't silently write into whatever vault this machine last had
|
|
346
|
+
* active in some other terminal/console tab.
|
|
347
|
+
*
|
|
348
|
+
* `stopAt` is a TEST-ONLY walk-boundary fence, threaded through to both
|
|
349
|
+
* `findVaultRoot`'s own walk and `resolveActiveVault`'s inner cwd walk (via
|
|
350
|
+
* its `cwdWalkRoot` option) — production callers (`main()`) omit it.
|
|
351
|
+
*
|
|
352
|
+
* @param {string} cwd
|
|
353
|
+
* @param {NodeJS.ProcessEnv} env
|
|
354
|
+
* @param {string} [stopAt]
|
|
355
|
+
* @returns {{ vaultRoot: string|null, usedRegistryFallback: boolean }}
|
|
356
|
+
*/
|
|
357
|
+
export function resolveVaultRootForMain(cwd, env, stopAt) {
|
|
358
|
+
if (env.HS_VAULT_ROOT) {
|
|
359
|
+
return { vaultRoot: env.HS_VAULT_ROOT, usedRegistryFallback: false };
|
|
360
|
+
}
|
|
361
|
+
const walkedRoot = findVaultRoot(cwd, stopAt);
|
|
362
|
+
if (walkedRoot) {
|
|
363
|
+
return { vaultRoot: walkedRoot, usedRegistryFallback: false };
|
|
364
|
+
}
|
|
365
|
+
// Same ONE order as both resolvers ([[HS-VVNQ]], shared fixture
|
|
366
|
+
// spex-console/src/lib/vault-resolve/precedence-fixtures.json):
|
|
367
|
+
// `HS_VAULT_ROOT` (this CLI's explicit tier) → the walk above (satellite
|
|
368
|
+
// pin, then marker) → `HEYSPEX_VAULT_ROOT`/`HEYSPEX_VAULT_DIR` env → the
|
|
369
|
+
// registry pointer, the last two via the shared resolver. `cwd` is passed
|
|
370
|
+
// explicitly so the resolver's own walk starts where ours did — left to
|
|
371
|
+
// its default it would start from `CLAUDE_PROJECT_DIR`, and a marker found
|
|
372
|
+
// THERE (a different folder) would be mislabelled a registry fallback.
|
|
373
|
+
const registryRoot = findRegistryVaultRoot(
|
|
374
|
+
stopAt ? { cwd, cwdWalkRoot: stopAt } : { cwd },
|
|
375
|
+
);
|
|
376
|
+
return {
|
|
377
|
+
vaultRoot: registryRoot,
|
|
378
|
+
usedRegistryFallback: Boolean(registryRoot),
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/** True when `argv` carries `--attach-vault` (bare or `=value` form). */
|
|
383
|
+
export function hasAttachVaultFlag(argv) {
|
|
384
|
+
return argv.some(
|
|
385
|
+
(a) => a === "--attach-vault" || a.startsWith("--attach-vault="),
|
|
386
|
+
);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* True when `argv` carries an explicit `--target` (bare or `=value` form) —
|
|
391
|
+
* mirrors `install.ts`'s own `parseInstallArgs` flag test. `hs-install.ts`'s
|
|
392
|
+
* `resolveInstallTargetDir` already honors `--target` over `HS_VAULT_ROOT`
|
|
393
|
+
* unconditionally, so a caller who named their target explicitly is never
|
|
394
|
+
* actually affected by the registry fallback `bin/hs.mjs` computed for
|
|
395
|
+
* `HS_VAULT_ROOT` — warning or refusing anyway would be a false alarm about a
|
|
396
|
+
* value the install never consults.
|
|
397
|
+
*/
|
|
398
|
+
export function hasExplicitTargetFlag(argv) {
|
|
399
|
+
return argv.some((a) => a === "--target" || a.startsWith("--target="));
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* The warning `hs install` prints (to stderr, in both `--dry-run` and real
|
|
404
|
+
* output) when the target it is about to use came ONLY from the registry
|
|
405
|
+
* active-vault fallback — i.e. cwd sits under no vault at all and this
|
|
406
|
+
* machine's last-active vault pointer answered instead.
|
|
407
|
+
*/
|
|
408
|
+
export function registryFallbackWarningLine(cwd, vaultRoot) {
|
|
409
|
+
return (
|
|
410
|
+
`hs install: no vault found in or above ${cwd} — falling back to this ` +
|
|
411
|
+
`machine's last-active vault (${vaultRoot}, from ~/.heyspex/registry.json). ` +
|
|
412
|
+
"Pass --target <dir> to install into this directory instead.\n"
|
|
413
|
+
);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* The refusal `hs install --register-vault` (without `--dry-run`) prints and
|
|
418
|
+
* exits non-zero on instead of registering, when the fallback above is what
|
|
419
|
+
* answered — registering here would file the machine's last-active vault
|
|
420
|
+
* under a fresh pointer, not the one the caller meant.
|
|
421
|
+
*/
|
|
422
|
+
export function registryFallbackRefusalLine() {
|
|
423
|
+
return (
|
|
424
|
+
"hs install: refusing to run --register-vault from an empty directory that " +
|
|
425
|
+
"resolved only through the registry active-vault fallback. Pass --target <dir> " +
|
|
426
|
+
"naming the vault to register, or --dry-run to preview first.\n"
|
|
427
|
+
);
|
|
428
|
+
}
|
|
429
|
+
|
|
61
430
|
/**
|
|
62
431
|
* Extract the global `hs --vault <id> <subcommand>` one-shot override
|
|
63
432
|
* ([[HS-Q1HZ-TASK-hs-vault-cli-list-register-switch-active]]). `--vault`
|
|
@@ -93,8 +462,14 @@ function usage() {
|
|
|
93
462
|
"",
|
|
94
463
|
"Usage:",
|
|
95
464
|
" hs login [--console-url <url>] [--label <t>] [--json] Sign in via your browser (no code to type)",
|
|
465
|
+
" hs login --switch-account [<id|email>] | --create-account Switch to a stored account, or choose/create one in the browser",
|
|
466
|
+
" hs login --migrate [--dry-run] Re-key a single-account credentials file under its confirmed account",
|
|
467
|
+
" hs whoami [--account <id|email>] [--json] Which account this machine is signed in as (email, id, source, console)",
|
|
468
|
+
" hs logout [--account <id|email>] [--json] Sign one account out of this machine (default: the current one)",
|
|
96
469
|
" hs init [--console-url <url>] [--force] Write the heyspex.yaml vault marker",
|
|
97
470
|
" hs install [--target <dir>] [--platform <dir>] Bootstrap vault + Cursor/Claude MCP",
|
|
471
|
+
" hs install --attach-vault <id|path> Wire THIS code repo to an existing vault",
|
|
472
|
+
" (satellite: no specs/, no heyspex.yaml here)",
|
|
98
473
|
" hs invite [<name>] Mint a short pairing code (other computer: hs join CODE)",
|
|
99
474
|
" hs join <code> Join with a pairing code (LAN auto-find; no IP to type)",
|
|
100
475
|
" hs join [<repo>] [--dir d] [--code c] [--at url] [--profile p] [--skip steps] [--json] [--yes] Fresh-machine onboarding (clone → toolchain → deps → doctor → agent layer → secrets → verify)",
|
|
@@ -108,20 +483,33 @@ function usage() {
|
|
|
108
483
|
" hs dev [mode:local|mode:cloud|mode:prod] Dev/prod mode + local/cloud reliance (status when bare)",
|
|
109
484
|
" hs setup claude|cursor [--check|--uninstall] Install agent lifecycle hooks",
|
|
110
485
|
" hs skills sync|check Propagate skills to Cursor/Codex/Claude (cwd-safe)",
|
|
486
|
+
" hs skills add <module-path> --name <n> --trigger </slash> [--export <n>] [--description <t>] Register a skill (extension seam)",
|
|
487
|
+
" hs skills remove <name> Remove a customer-registered skill",
|
|
488
|
+
" hs skills list [--json] List registered skills (built-ins first, locked)",
|
|
111
489
|
" hs index rebuild [--verbose] [--dry-run] Full idempotent rebuild of the index",
|
|
490
|
+
" hs index reconcile [--apply] [--cap-docs keep-out|index] Per-spec newest-wins between disk and cloud (reports unless --apply)",
|
|
112
491
|
" hs pull [--apply] [--only <id>] [--overwrite] Bring cloud-written specs down to the disk vault (reports unless --apply)",
|
|
492
|
+
" hs source status|preflight|commit|push Local git: tree status, path-scoped checks, commit, safe push",
|
|
493
|
+
" hs source identity [show|set|clear] Git author identity: show (read-only) / set, clear (HUMAN-ONLY)",
|
|
113
494
|
" hs milestone show <id> [--json] Show a milestone (flags needs-breakdown when epicless)",
|
|
114
495
|
" hs factory status|fleet|results [--json] Dev-Factory read-only status (fleet, routing, calibrations, results)",
|
|
115
496
|
" hs factory run status [--json] Live assisted/autonomous dispatcher run (pids, queue, stop hints)",
|
|
116
497
|
" hs code-watcher scan [--json] Capture build-correctness failures (tsc) → Resolution",
|
|
117
498
|
" hs server status [--json] Ground-truth dev-server status (running? which port[s]?)",
|
|
499
|
+
" hs wip status [--json] WIP ledger health report (lines vs 600-line budget, age tiers, archive)",
|
|
500
|
+
" hs wip restore <id> [--json] [--force] Restore an archived WIP ledger entry back onto the live ledger",
|
|
501
|
+
" hs worktree list|alloc|release|reap [--json] [--dry-run] Session/build worktree registry (HS-03DO)",
|
|
118
502
|
" hs view <surface> [spec-id] [--width N --height N --browser edge|chrome] Open one surface in a chromeless app-mode window",
|
|
119
503
|
" hs server stop [--json] Stop the spex-console dev server (any terminal)",
|
|
504
|
+
" hs source status|preflight|commit|push SpexSource local git operations",
|
|
505
|
+
" hs source identity [show|set|clear] Git author identity (set/clear are human-only; see hs source --help)",
|
|
120
506
|
" hs defer <id> [--until <when>] [--json] Time-shift a spec (defer_until)",
|
|
121
507
|
" hs undefer <id> [--json] Clear a spec's defer_until",
|
|
122
508
|
" hs spawn [<id>] [--dry-run] [--json] Launch a fresh /hs-next agent in a new terminal",
|
|
123
|
-
|
|
509
|
+
' hs capture "<text>"|--clipboard|--file <p> [--add] Capture → console drafts (--add chains a group)',
|
|
124
510
|
" hs companion start|stop|status|build Desktop companion tray app",
|
|
511
|
+
" hs companion settings [list|get <k>|set <k> <v>] [--json] Every Companion setting (console-backed)",
|
|
512
|
+
" hs companion mute|unmute|volume|orb|wake|activation|hotkey [<v>] Setting shortcuts (omit <v> to read)",
|
|
125
513
|
" hs registry list [--json] List launch registry entries",
|
|
126
514
|
" hs env init|set|list|run|export|rotate Encrypted env vault (ADR-0020)",
|
|
127
515
|
" hs env profiles | key export|import Named profiles + second-machine key transfer",
|
|
@@ -129,23 +517,52 @@ function usage() {
|
|
|
129
517
|
" hs settings list|get|set Read/change workflow settings (.heyspex/config.yaml)",
|
|
130
518
|
" hs classifier stats [--json] [--recent N] Intent Classifier decision stats (read-only)",
|
|
131
519
|
" hs next [spec_id] [--summary] [--json] Resolve on-deck brief (MCP fallback)",
|
|
520
|
+
" hs next <id> --launch [--dry-run] [--claim] [--backend <anthropic|ollama|deepseek>] Launch a session on that spec",
|
|
521
|
+
" hs next model|permission-mode [<kind>] [<v>] Per-kind launch defaults",
|
|
522
|
+
" hs next backend [<kind>] [<v>] Per-kind launch backend default (anthropic|ollama|deepseek)",
|
|
523
|
+
" hs next claim [on|off] Auto-claim vs ask-first default",
|
|
524
|
+
" hs next front [handheld|assist|auto] Default front: one task with you, or a dispatcher run",
|
|
525
|
+
" hs next assist|auto <milestone...> --launch Arm a dispatcher run on one or more milestones",
|
|
526
|
+
" hs model [<seat>] [--launch|--fleet] Every seat and the model set for it",
|
|
527
|
+
" hs seat list|add|update|disable [--json] Read/add/update/disable a seat in the registry (dev-factory/seats.json)",
|
|
528
|
+
" hs voice add|build|show|set|test|refresh|export Build and manage a coded voice profile",
|
|
529
|
+
" hs trash [list|restore|empty] The bin spec deletes go into",
|
|
132
530
|
" hs auto [filename] [--json] Resolve /hs-auto dispatch plan",
|
|
133
531
|
" hs mcp call <tool> [--arg key=value ...] [--args '<json>'] [--args-file <path>] [--json] One-shot MCP tool",
|
|
134
532
|
" hs mcp pair <code> [--console-url <url>] Redeem a console pairing code (cross-machine fallback for hs login)",
|
|
533
|
+
" hs mcp install --remote [--write] [--json] Write/print .mcp.json for the cloud (remote) MCP endpoint",
|
|
135
534
|
" hs test run <id|manifest> [--format ...] Run open-format API tests (Hurl/.http) per AC",
|
|
136
535
|
" hs test gate [dir] [--report <j>] [--threshold n] Mutation-testing gate — reject suites that kill no mutants",
|
|
137
536
|
" hs test conform <spec> --url <u> | --report <x> OpenAPI conformance / docs↔impl drift (Schemathesis)",
|
|
537
|
+
" hs chat [--model <name>] [--script <file>] [--seat <seat>] HeySpex's own chat front (any model); !<cmd> or /shell for CLI passthrough",
|
|
538
|
+
" hs trust list List spexcode's stored folder-trust decisions",
|
|
539
|
+
" hs trust revoke [path] Clear one folder's trust decision (default: cwd) so the next launch asks again",
|
|
540
|
+
" hs doctrine extract [--since <iso>] [--project <name>] [--dry-run] Stream every session/taste source into .heyspex/doctrine/observations.jsonl",
|
|
541
|
+
" hs doctrine learn [--full] [--max-batches <n>] [--dry-run] Extract, then synthesize into spexharness/doctrine/SPEXCODE-DOCTRINE.md/json",
|
|
542
|
+
" hs doctrine show Print the current SPEXCODE-DOCTRINE.md",
|
|
543
|
+
" hs harness gates [--json] List every SpexHarness gate: id, event, severity, last fixture result",
|
|
544
|
+
" hs hooks add <module-path> --event <event> [--id <id>] [--priority <n>] Register a hook (extension seam)",
|
|
545
|
+
" hs hooks remove <id> Remove a customer-registered hook",
|
|
546
|
+
" hs hooks list [--json] List registered hooks (built-ins first, locked)",
|
|
138
547
|
" hs check run <id|manifest> [--json] Verify acceptance criteria vs the frozen checks manifest (per-AC pass/fail)",
|
|
548
|
+
" hs check judge <id> [--session <id>] [--dry-run] Decorrelated judge seat: verdicts on method:human criteria, posts to the verification API",
|
|
139
549
|
" hs revalidate [--json] [--apply] Re-run the frozen checks of already-stamped work; report what has decayed",
|
|
140
550
|
" hs agent import <collection> <role> Import a community agent into a type:agent vault spec",
|
|
141
551
|
" hs agent list [collection] List vendored agent collections / roles",
|
|
142
552
|
"",
|
|
553
|
+
" node dev-factory/spexcrawl/spexcrawl-cli.mjs search|scrape|crawl SpexCrawl — web",
|
|
554
|
+
" search/scrape/crawl CLI (standalone script,",
|
|
555
|
+
" not an `hs` verb; also reachable from the",
|
|
556
|
+
" Companion)",
|
|
557
|
+
"",
|
|
143
558
|
"Control plane (Neon — requires a reachable console):",
|
|
144
559
|
" hs pin get|set <id>|clear Read/set/clear the on-deck pin",
|
|
145
560
|
" hs claim acquire|release <id> | list Atomic spec claims",
|
|
146
561
|
" hs claim progress <id> --stage <verb> Report live work-stage",
|
|
147
|
-
" hs handoff record|load [--lane <k>] Resume-context coordination",
|
|
562
|
+
" hs handoff record|load [--lane <k>] Resume-context coordination (never launches)",
|
|
563
|
+
" hs handoff <CODE> [--no-launch] Resolve + LAUNCH the lane's resume by default",
|
|
148
564
|
" hs events show <id> [--kind --since] Per-spec audit log",
|
|
565
|
+
" hs admin test-user create --env <e> Mint one throwaway test customer",
|
|
149
566
|
" hs export-events [--since <date>] Idempotent audit export → specs/events/",
|
|
150
567
|
" hs prereq <blocked-id> [<action> <prereq>] Cross-milestone prerequisite resolution",
|
|
151
568
|
" hs queue submit|next|claim|complete|fail|list Fleet-queue dispatch (Neon, direct — no console hop)",
|
|
@@ -155,7 +572,13 @@ function usage() {
|
|
|
155
572
|
" hs --version Show CLI version",
|
|
156
573
|
"",
|
|
157
574
|
"Vault root is detected by walking up from the current directory until a",
|
|
158
|
-
"specs/ folder is found
|
|
575
|
+
"heyspex.yaml marker or a specs/ folder is found, then falling back to the",
|
|
576
|
+
"registry's active vault (~/.heyspex/registry.json). Override with HS_VAULT_ROOT.",
|
|
577
|
+
"When hs install resolves ONLY through that registry fallback (a fully empty",
|
|
578
|
+
"cwd — no heyspex.yaml/specs/ found walking up, no HS_VAULT_ROOT, no --target),",
|
|
579
|
+
"it warns, naming the fallback vault, in both --dry-run and real output; and",
|
|
580
|
+
"`hs install --register-vault` without --dry-run in that case refuses instead of",
|
|
581
|
+
"registering, asking for --target. --attach-vault (satellite mode) is exempt.",
|
|
159
582
|
"",
|
|
160
583
|
].join("\n"),
|
|
161
584
|
);
|
|
@@ -169,9 +592,13 @@ function usage() {
|
|
|
169
592
|
* Three cases, in order ([[HS-JOQX-0JNK-TASK-published-package-runs-hs-init-and-hs]]):
|
|
170
593
|
* 1. Monorepo checkout: `.ts` source AND `tsx` both present — run via tsx,
|
|
171
594
|
* exactly as before this task.
|
|
172
|
-
* 2. Published install of a BUNDLED verb
|
|
173
|
-
* `hs-install.ts`
|
|
174
|
-
*
|
|
595
|
+
* 2. Published install of a BUNDLED verb — currently `hs-init.ts`,
|
|
596
|
+
* `hs-install.ts`, `hs-claim.ts`, `hs-check.ts`, `hs-env.ts`,
|
|
597
|
+
* `hs-connect.ts`, `hs-join.ts`, `hs-vault.ts`, `hs-workspace.ts`,
|
|
598
|
+
* `hs-defer.ts`, `hs-vault-pull.ts`, `hs-dev.ts`, `hs-index-rebuild.ts`,
|
|
599
|
+
* `hs-index-reconcile.ts` — see `scripts/build-cli-bundle.mjs`'s
|
|
600
|
+
* `ENTRIES` array and doc comment for the authoritative list and why
|
|
601
|
+
* each qualifies: neither the source nor tsx exists, but
|
|
175
602
|
* `dist/cli/<name>.mjs` does — run the plain-JS bundle directly with
|
|
176
603
|
* `node`, no tsx/TypeScript involved.
|
|
177
604
|
* 3. Neither exists: the package install is incomplete (a verb this
|
|
@@ -256,7 +683,7 @@ function dispatchSpexConsoleEntry(entryFile, args, vaultRoot, extraEnv = {}) {
|
|
|
256
683
|
* it), so there is nothing to check about `hs` other than reporting its
|
|
257
684
|
* version.
|
|
258
685
|
*/
|
|
259
|
-
function runStandaloneDoctor(args) {
|
|
686
|
+
function runStandaloneDoctor(args, vaultRoot) {
|
|
260
687
|
const jsonOut = args.includes("--json");
|
|
261
688
|
let version = "0.0.0";
|
|
262
689
|
try {
|
|
@@ -292,6 +719,46 @@ function runStandaloneDoctor(args) {
|
|
|
292
719
|
if (runtimeFilesRow.status !== "ok") report.ok = false;
|
|
293
720
|
report.checks.push(runtimeFilesRow);
|
|
294
721
|
|
|
722
|
+
// `vault-config` ([[HS-4C76-9W86-DIAG-heyspex-config-yaml-has-no-single-home]]
|
|
723
|
+
// fix 5): the vault's `.heyspex/config.yaml` (ADR-0009: vault root, git-
|
|
724
|
+
// tracked) must exist — without it every settings reader runs on defaults.
|
|
725
|
+
// `vaultRoot` is main()'s resolution (HS_VAULT_ROOT → walk-up → registry);
|
|
726
|
+
// null means no vault at all, which the row reports as its own failure.
|
|
727
|
+
const vaultConfigRow = vaultConfigCheckRow(vaultRoot ?? null);
|
|
728
|
+
if (vaultConfigRow.status !== "ok") report.ok = false;
|
|
729
|
+
report.checks.push(vaultConfigRow);
|
|
730
|
+
|
|
731
|
+
// `vault-template` (HS-KH0Q): shipped core docs + template_version pin present.
|
|
732
|
+
const templateRow = templateCheckRow(vaultRoot ?? null);
|
|
733
|
+
if (templateRow.status === "fail") report.ok = false;
|
|
734
|
+
report.checks.push(templateRow);
|
|
735
|
+
|
|
736
|
+
// `connection` ([[HS-W73V-WW9R-TASK-make-both-doctor-shapes-fail-on-cloud]],
|
|
737
|
+
// invariants 8 and 10): the BUNDLED `hs connect --check --json` from the
|
|
738
|
+
// invoked cwd, folded by the SAME function the full doctor uses
|
|
739
|
+
// (`foldConnectionReport`), so a customer machine gets the local-vs-cloud
|
|
740
|
+
// rows too. A disagreeing row fails the run with its repair command; an
|
|
741
|
+
// unreadable cloud side (offline, signed out) is a warn and leaves `ok`
|
|
742
|
+
// alone. The check reads the console with GET only. Its subject is the repo
|
|
743
|
+
// the caller stands in (the walk-up from the invoked cwd, else the cwd
|
|
744
|
+
// itself), NOT `vaultRoot`: that can be an inherited HS_VAULT_ROOT or the
|
|
745
|
+
// registry fallback, i.e. ANOTHER repo (seen when `hs connect`'s acceptance
|
|
746
|
+
// pass ran this doctor in a fresh repo and it audited the previous one).
|
|
747
|
+
const connectSubject = findVaultRoot(INVOKED_CWD) ?? INVOKED_CWD;
|
|
748
|
+
const connection = foldConnectionReport(
|
|
749
|
+
runBundledConnectCheck(PKG_ROOT, {
|
|
750
|
+
cwd: INVOKED_CWD,
|
|
751
|
+
env: {
|
|
752
|
+
...process.env,
|
|
753
|
+
HS_INVOKED_CWD: INVOKED_CWD,
|
|
754
|
+
HS_VAULT_ROOT: connectSubject,
|
|
755
|
+
},
|
|
756
|
+
}),
|
|
757
|
+
);
|
|
758
|
+
if (!connection.ok) report.ok = false;
|
|
759
|
+
report.checks.push(...connection.rows);
|
|
760
|
+
report.connection = connection.json;
|
|
761
|
+
|
|
295
762
|
const hsLinkLib = path.join(PKG_ROOT, "scripts", "lib", "hs-link.mjs");
|
|
296
763
|
if (existsSync(hsLinkLib)) {
|
|
297
764
|
// Deliberately synchronous top-level work below via a nested async IIFE
|
|
@@ -340,8 +807,15 @@ function printStandaloneDoctorReport(report, jsonOut) {
|
|
|
340
807
|
"HeySpex setup check — published-package install (not a source clone)\n\n",
|
|
341
808
|
);
|
|
342
809
|
for (const check of report.checks) {
|
|
343
|
-
const label =
|
|
344
|
-
|
|
810
|
+
const label =
|
|
811
|
+
check.status === "ok"
|
|
812
|
+
? " ok "
|
|
813
|
+
: check.status === "warn"
|
|
814
|
+
? " warn "
|
|
815
|
+
: " FAIL ";
|
|
816
|
+
process.stdout.write(
|
|
817
|
+
`[ ${label} ] ${check.label ? `${check.label}: ` : ""}${check.detail}\n`,
|
|
818
|
+
);
|
|
345
819
|
if (check.human) process.stdout.write(` ${check.human}\n`);
|
|
346
820
|
}
|
|
347
821
|
process.stdout.write(
|
|
@@ -384,20 +858,65 @@ function main(argv) {
|
|
|
384
858
|
const command = argv[1];
|
|
385
859
|
const rest = argv.slice(2);
|
|
386
860
|
|
|
387
|
-
const vaultRoot
|
|
861
|
+
const { vaultRoot, usedRegistryFallback } = resolveVaultRootForMain(
|
|
862
|
+
process.cwd(),
|
|
863
|
+
process.env,
|
|
864
|
+
);
|
|
388
865
|
|
|
389
|
-
// `hs init` writes the top-level heyspex.yaml vault marker
|
|
390
|
-
// ([[HS-EPIC-AEHR-audit-export-heyspex-yaml-and-heyspex-retirement]]
|
|
391
|
-
//
|
|
392
|
-
//
|
|
866
|
+
// `hs init` writes the top-level heyspex.yaml vault marker AND scaffolds
|
|
867
|
+
// specs/ ([[HS-EPIC-AEHR-audit-export-heyspex-yaml-and-heyspex-retirement]]
|
|
868
|
+
// §2). It always acts on the caller's invocation directory (`INVOKED_CWD`,
|
|
869
|
+
// captured before any dispatch below runs) — NEVER on `vaultRoot`'s
|
|
870
|
+
// walk-up/registry result. `hs init` may legitimately run *inside* a stray
|
|
871
|
+
// ancestor's `specs/` folder (a half-set-up nested checkout, a doc repo
|
|
872
|
+
// that happens to have a `specs/` dir, etc.); using `vaultRoot` here would
|
|
873
|
+
// silently write the marker into that unrelated ancestor instead of where
|
|
874
|
+
// the user actually typed `hs init` (HS-HISX: first fresh-machine walkthrough
|
|
875
|
+
// break — the vault marker landed in the wrong folder).
|
|
876
|
+
// HS_PKG_ROOT is forwarded so the entrypoint can resolve the vendored
|
|
877
|
+
// `.heyspex/config.yaml` seed template (`vendor/heyspex-mcp/config-seed.yaml`)
|
|
878
|
+
// from the PACKAGE root, never from the caller's cwd (HS-4C76 fix 4).
|
|
393
879
|
if (namespace === "init") {
|
|
394
|
-
dispatchSpexConsoleEntry("hs-init.ts", argv,
|
|
880
|
+
dispatchSpexConsoleEntry("hs-init.ts", argv, INVOKED_CWD, {
|
|
881
|
+
HS_PKG_ROOT: PKG_ROOT,
|
|
882
|
+
});
|
|
395
883
|
return;
|
|
396
884
|
}
|
|
397
885
|
|
|
398
886
|
// `hs install` bootstraps a customer/project vault + agent MCP wiring. May run
|
|
399
887
|
// on a fresh directory with no specs/ yet (like init).
|
|
888
|
+
//
|
|
889
|
+
// [[HS-0I1G-B0IT-TASK-track-and-announce-the-registry-active]]: when the
|
|
890
|
+
// resolution above bottomed out at the registry active-vault fallback
|
|
891
|
+
// (nothing set HS_VAULT_ROOT, and no marker/satellite pin was found walking
|
|
892
|
+
// up from cwd), say so — silently bootstrapping into whatever vault this
|
|
893
|
+
// machine last had active in some OTHER terminal/console tab is exactly the
|
|
894
|
+
// wrong-vault failure this task closes. Two flags are exempt because
|
|
895
|
+
// `hs-install.ts` never actually consults the vaultRoot this fallback
|
|
896
|
+
// computed for them, so warning/refusing about it would be a false alarm:
|
|
897
|
+
// `--attach-vault` (satellite mode) — its target resolution
|
|
898
|
+
// (resolveInstallTargetDir's satellite branch) ignores HS_VAULT_ROOT
|
|
899
|
+
// entirely and reads HS_INVOKED_CWD instead (and `--register-vault` is
|
|
900
|
+
// already mutually exclusive with it via assertSatelliteFlagCombination, so
|
|
901
|
+
// the refusal below can never apply either way) — and an explicit
|
|
902
|
+
// `--target`, which resolveInstallTargetDir honors over HS_VAULT_ROOT
|
|
903
|
+
// unconditionally (see hasExplicitTargetFlag's doc comment).
|
|
400
904
|
if (namespace === "install") {
|
|
905
|
+
if (
|
|
906
|
+
usedRegistryFallback &&
|
|
907
|
+
!hasAttachVaultFlag(argv) &&
|
|
908
|
+
!hasExplicitTargetFlag(argv)
|
|
909
|
+
) {
|
|
910
|
+
process.stderr.write(
|
|
911
|
+
registryFallbackWarningLine(process.cwd(), vaultRoot),
|
|
912
|
+
);
|
|
913
|
+
const isDryRun = argv.includes("--dry-run");
|
|
914
|
+
const isRegisterVault = argv.includes("--register-vault");
|
|
915
|
+
if (isRegisterVault && !isDryRun) {
|
|
916
|
+
process.stderr.write(registryFallbackRefusalLine());
|
|
917
|
+
process.exit(1);
|
|
918
|
+
}
|
|
919
|
+
}
|
|
401
920
|
dispatchSpexConsoleEntry(
|
|
402
921
|
"hs-install.ts",
|
|
403
922
|
argv,
|
|
@@ -426,24 +945,28 @@ function main(argv) {
|
|
|
426
945
|
if (namespace === "invite") {
|
|
427
946
|
if (!vaultRoot) {
|
|
428
947
|
process.stderr.write(
|
|
429
|
-
"hs: no vault detected — could not find a
|
|
430
|
-
"walking up from the current directory. " +
|
|
948
|
+
"hs: no vault detected — could not find a heyspex.yaml marker or a " +
|
|
949
|
+
"specs/ folder walking up from the current directory. " +
|
|
431
950
|
"Set HS_VAULT_ROOT or run from inside a vault.\n",
|
|
432
951
|
);
|
|
433
952
|
process.exit(1);
|
|
434
953
|
}
|
|
435
|
-
dispatchSpexConsoleEntry(
|
|
954
|
+
dispatchSpexConsoleEntry(
|
|
955
|
+
"hs-env.ts",
|
|
956
|
+
["env", "invite", ...argv.slice(1)],
|
|
957
|
+
vaultRoot,
|
|
958
|
+
);
|
|
436
959
|
return;
|
|
437
960
|
}
|
|
438
961
|
|
|
439
962
|
// `hs setup claude|cursor` — install agent lifecycle hooks into the vault root.
|
|
440
963
|
if (namespace === "setup") {
|
|
441
964
|
const agent = command?.toLowerCase();
|
|
442
|
-
const
|
|
443
|
-
claude: "
|
|
444
|
-
cursor: "
|
|
965
|
+
const SPAWN_KEYS = {
|
|
966
|
+
claude: "setup:claude",
|
|
967
|
+
cursor: "setup:cursor",
|
|
445
968
|
};
|
|
446
|
-
if (!agent || !Object.hasOwn(
|
|
969
|
+
if (!agent || !Object.hasOwn(SPAWN_KEYS, agent)) {
|
|
447
970
|
process.stderr.write(
|
|
448
971
|
`hs setup: expected \`claude\` or \`cursor\` (got \`${command ?? ""}\`)\n` +
|
|
449
972
|
" hs setup claude [--check|--uninstall] Claude Code lifecycle hooks\n" +
|
|
@@ -454,7 +977,7 @@ function main(argv) {
|
|
|
454
977
|
const setupFlags = rest.filter(
|
|
455
978
|
(a) => a === "--check" || a === "--uninstall",
|
|
456
979
|
);
|
|
457
|
-
const script =
|
|
980
|
+
const script = spawnTargetPath(SPAWN_KEYS[agent]);
|
|
458
981
|
const target = vaultRoot || process.cwd();
|
|
459
982
|
const child = spawn(process.execPath, [script, ...setupFlags], {
|
|
460
983
|
cwd: target,
|
|
@@ -464,7 +987,7 @@ function main(argv) {
|
|
|
464
987
|
child.on("exit", (code, signal) => process.exit(signal ? 1 : (code ?? 0)));
|
|
465
988
|
child.on("error", (err) => {
|
|
466
989
|
process.stderr.write(
|
|
467
|
-
`hs setup: failed to run ${
|
|
990
|
+
`hs setup: failed to run ${SPAWN_TARGETS[SPAWN_KEYS[agent]].path}: ${err.message}\n`,
|
|
468
991
|
);
|
|
469
992
|
process.exit(1);
|
|
470
993
|
});
|
|
@@ -472,17 +995,44 @@ function main(argv) {
|
|
|
472
995
|
}
|
|
473
996
|
|
|
474
997
|
// `hs skills sync|check` — propagate .commandcode/skills → Cursor/Codex/Claude shims.
|
|
998
|
+
// `hs skills add|remove|list` — a DIFFERENT verb: the extension seam's skill
|
|
999
|
+
// registry ([[HS-8N3K-NYPR-TASK-add-cli-verbs-so-a-developer-can]],
|
|
1000
|
+
// [[HS-8EDJ-NYPR-EPIC-extension-seam-for-other-developers]]), routed to its
|
|
1001
|
+
// own entrypoint below so it never touches the sync/check spawn path.
|
|
475
1002
|
if (namespace === "skills") {
|
|
476
1003
|
const action = command?.toLowerCase();
|
|
1004
|
+
if (action === "add" || action === "remove" || action === "list") {
|
|
1005
|
+
dispatchSpexConsoleEntry(
|
|
1006
|
+
"hs-skills.ts",
|
|
1007
|
+
argv,
|
|
1008
|
+
vaultRoot || process.cwd(),
|
|
1009
|
+
);
|
|
1010
|
+
return;
|
|
1011
|
+
}
|
|
477
1012
|
if (action !== "sync" && action !== "check") {
|
|
478
1013
|
process.stderr.write(
|
|
479
|
-
`hs skills: expected \`sync\` or \`
|
|
480
|
-
" hs skills sync
|
|
481
|
-
" hs skills check
|
|
1014
|
+
`hs skills: expected \`sync\`, \`check\`, \`add\`, \`remove\`, or \`list\` (got \`${command ?? ""}\`)\n` +
|
|
1015
|
+
" hs skills sync Copy skills + slash shims to all agent harnesses\n" +
|
|
1016
|
+
" hs skills check Exit 1 if any harness copy is stale\n" +
|
|
1017
|
+
" hs skills add <module-path> --name <n> --trigger </slash> Register a skill (extension seam)\n" +
|
|
1018
|
+
" hs skills remove <name> Remove a customer-registered skill\n" +
|
|
1019
|
+
" hs skills list [--json] List registered skills\n",
|
|
482
1020
|
);
|
|
483
1021
|
process.exit(2);
|
|
484
1022
|
}
|
|
485
|
-
const script =
|
|
1023
|
+
const script = spawnTargetPath("skills:sync");
|
|
1024
|
+
if (!existsSync(script)) {
|
|
1025
|
+
// `skills:sync` is a deliberately dev-only SPAWN_TARGETS entry — see its
|
|
1026
|
+
// `reason` above. A published `npm install heyspex` never has
|
|
1027
|
+
// `.commandcode/skills` (the platform's own SSOT content) on disk, so
|
|
1028
|
+
// report that plainly instead of a bare MODULE_NOT_FOUND.
|
|
1029
|
+
process.stderr.write(
|
|
1030
|
+
"hs skills: not available in a published `heyspex` install — this verb " +
|
|
1031
|
+
"propagates the HeySpex platform repo's OWN `.commandcode/skills` " +
|
|
1032
|
+
"source content, which only exists inside a clone of that repo.\n",
|
|
1033
|
+
);
|
|
1034
|
+
process.exit(1);
|
|
1035
|
+
}
|
|
486
1036
|
const target = vaultRoot || PKG_ROOT;
|
|
487
1037
|
const childArgs = action === "check" ? ["--check"] : [];
|
|
488
1038
|
const child = spawn(process.execPath, [script, ...childArgs], {
|
|
@@ -532,18 +1082,22 @@ function main(argv) {
|
|
|
532
1082
|
// auth verb, so it bypasses the hard vault gate below like init/install —
|
|
533
1083
|
// signing in is exactly what you do BEFORE you have a vault here.
|
|
534
1084
|
if (namespace === "login") {
|
|
535
|
-
const cliPath =
|
|
1085
|
+
const cliPath = spawnTargetPath("heyspex-mcp-cli");
|
|
536
1086
|
const loginRoot = vaultRoot || process.cwd();
|
|
537
|
-
const child = spawn(
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
1087
|
+
const child = spawn(
|
|
1088
|
+
process.execPath,
|
|
1089
|
+
[cliPath, "login", ...argv.slice(1)],
|
|
1090
|
+
{
|
|
1091
|
+
cwd: PKG_ROOT,
|
|
1092
|
+
stdio: "inherit",
|
|
1093
|
+
env: {
|
|
1094
|
+
...process.env,
|
|
1095
|
+
HEYSPEX_VAULT_ROOT: loginRoot,
|
|
1096
|
+
HS_VAULT_ROOT: loginRoot,
|
|
1097
|
+
HEYSPEX_DISABLE_STDIO_BOOTSTRAP: "1",
|
|
1098
|
+
},
|
|
545
1099
|
},
|
|
546
|
-
|
|
1100
|
+
);
|
|
547
1101
|
child.on("exit", (code, signal) => process.exit(signal ? 1 : (code ?? 0)));
|
|
548
1102
|
child.on("error", (err) => {
|
|
549
1103
|
process.stderr.write(`hs: failed to run login: ${err.message}\n`);
|
|
@@ -552,15 +1106,53 @@ function main(argv) {
|
|
|
552
1106
|
return;
|
|
553
1107
|
}
|
|
554
1108
|
|
|
1109
|
+
// `hs whoami` — which account this machine's credential belongs to (email,
|
|
1110
|
+
// account id, console), asked of GET /api/mcp/session. The account a repo
|
|
1111
|
+
// would be attached to by `hs connect` / `hs install`
|
|
1112
|
+
// ([[HS-HAQI-KODY-DIAG-hs-install-and-hs-connect-bind-a-new]]). Auth verb:
|
|
1113
|
+
// bypasses the vault gate like `hs login`.
|
|
1114
|
+
// `hs logout [--account <id|email>]` — remove one stored account's token
|
|
1115
|
+
// ([[HS-UA9S-WW9R-TASK-store-credentials-per-account-login]]). Auth verb:
|
|
1116
|
+
// bypasses the vault gate like `hs login` / `hs whoami`.
|
|
1117
|
+
if (namespace === "whoami" || namespace === "logout") {
|
|
1118
|
+
const cliPath = spawnTargetPath("heyspex-mcp-cli");
|
|
1119
|
+
const whoamiRoot = vaultRoot || process.cwd();
|
|
1120
|
+
const child = spawn(
|
|
1121
|
+
process.execPath,
|
|
1122
|
+
[cliPath, namespace, ...argv.slice(1)],
|
|
1123
|
+
{
|
|
1124
|
+
cwd: PKG_ROOT,
|
|
1125
|
+
stdio: "inherit",
|
|
1126
|
+
env: {
|
|
1127
|
+
...process.env,
|
|
1128
|
+
HEYSPEX_VAULT_ROOT: whoamiRoot,
|
|
1129
|
+
HS_VAULT_ROOT: whoamiRoot,
|
|
1130
|
+
HEYSPEX_DISABLE_STDIO_BOOTSTRAP: "1",
|
|
1131
|
+
},
|
|
1132
|
+
},
|
|
1133
|
+
);
|
|
1134
|
+
child.on("exit", (code, signal) => process.exit(signal ? 1 : (code ?? 0)));
|
|
1135
|
+
child.on("error", (err) => {
|
|
1136
|
+
process.stderr.write(`hs: failed to run ${namespace}: ${err.message}\n`);
|
|
1137
|
+
process.exit(1);
|
|
1138
|
+
});
|
|
1139
|
+
return;
|
|
1140
|
+
}
|
|
1141
|
+
|
|
555
1142
|
// `hs connect --check [--json]` — the read-only connect-checks report
|
|
556
1143
|
// ([[HS-I2OO-EKLJ-TASK-shared-connect-checks-module-with-a]] on
|
|
557
1144
|
// [[HS-QYH8-EKLJ-EPIC-hs-connect-one-command-repo-connect-verb]]). May run
|
|
558
1145
|
// in an unconnected repo (no specs/ yet — that is the point), so it
|
|
559
1146
|
// bypasses the hard vault gate below like init/install/vault/workspace.
|
|
560
1147
|
if (namespace === "connect") {
|
|
561
|
-
dispatchSpexConsoleEntry(
|
|
562
|
-
|
|
563
|
-
|
|
1148
|
+
dispatchSpexConsoleEntry(
|
|
1149
|
+
"hs-connect.ts",
|
|
1150
|
+
argv,
|
|
1151
|
+
vaultRoot || process.cwd(),
|
|
1152
|
+
{
|
|
1153
|
+
HS_INVOKED_CWD: INVOKED_CWD,
|
|
1154
|
+
},
|
|
1155
|
+
);
|
|
564
1156
|
return;
|
|
565
1157
|
}
|
|
566
1158
|
|
|
@@ -572,7 +1164,7 @@ function main(argv) {
|
|
|
572
1164
|
// the very first thing a stranger runs after `npm install heyspex` in an
|
|
573
1165
|
// empty project is exactly this, from a directory with no `specs/` yet.
|
|
574
1166
|
if (namespace === "doctor") {
|
|
575
|
-
const script =
|
|
1167
|
+
const script = spawnTargetPath("doctor");
|
|
576
1168
|
if (!existsSync(script)) {
|
|
577
1169
|
// Published-package install (per HS-ZJSP-0JNK): the full doctor audits
|
|
578
1170
|
// dozens of source-checkout-only surfaces (git hooks, Cursor wiring,
|
|
@@ -580,7 +1172,7 @@ function main(argv) {
|
|
|
580
1172
|
// it is deliberately not in the package's `files`. Run the small
|
|
581
1173
|
// subset that DOES apply to a published install instead of crashing
|
|
582
1174
|
// with MODULE_NOT_FOUND.
|
|
583
|
-
runStandaloneDoctor(argv.slice(1));
|
|
1175
|
+
runStandaloneDoctor(argv.slice(1), vaultRoot);
|
|
584
1176
|
return;
|
|
585
1177
|
}
|
|
586
1178
|
const child = spawn(process.execPath, [script, ...argv.slice(1)], {
|
|
@@ -598,15 +1190,28 @@ function main(argv) {
|
|
|
598
1190
|
|
|
599
1191
|
if (!vaultRoot) {
|
|
600
1192
|
process.stderr.write(
|
|
601
|
-
"hs: no vault detected — could not find a
|
|
602
|
-
"walking up from the current directory. " +
|
|
1193
|
+
"hs: no vault detected — could not find a heyspex.yaml marker or a " +
|
|
1194
|
+
"specs/ folder walking up from the current directory. " +
|
|
603
1195
|
"Set HS_VAULT_ROOT or run from inside a vault.\n",
|
|
604
1196
|
);
|
|
605
1197
|
process.exit(1);
|
|
606
1198
|
}
|
|
607
1199
|
|
|
608
1200
|
if (namespace === "index" && command === "rebuild") {
|
|
609
|
-
dispatchSpexConsoleEntry("hs-index-rebuild.ts", rest, vaultRoot
|
|
1201
|
+
dispatchSpexConsoleEntry("hs-index-rebuild.ts", rest, vaultRoot, {
|
|
1202
|
+
...(vaultIdOverride ? { HS_VAULT_ID_OVERRIDE: vaultIdOverride } : {}),
|
|
1203
|
+
});
|
|
1204
|
+
return;
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
// `hs index reconcile` — per-spec newest-wins between the disk vault and the
|
|
1208
|
+
// cloud index ([[HS-MS75]]). Read-only unless `--apply`; status moves go
|
|
1209
|
+
// through the status route (refusals are listed), content through the
|
|
1210
|
+
// phased reindex route with preserve_status; never deletes, never finalizes.
|
|
1211
|
+
if (namespace === "index" && command === "reconcile") {
|
|
1212
|
+
dispatchSpexConsoleEntry("hs-index-reconcile.ts", rest, vaultRoot, {
|
|
1213
|
+
...(vaultIdOverride ? { HS_VAULT_ID_OVERRIDE: vaultIdOverride } : {}),
|
|
1214
|
+
});
|
|
610
1215
|
return;
|
|
611
1216
|
}
|
|
612
1217
|
|
|
@@ -617,7 +1222,9 @@ function main(argv) {
|
|
|
617
1222
|
// pushes, never deletes, and only REPORTS unless `--apply` is given.
|
|
618
1223
|
// Forwards the full argv; the entrypoint drops the leading `pull` token.
|
|
619
1224
|
if (namespace === "pull") {
|
|
620
|
-
dispatchSpexConsoleEntry("hs-vault-pull.ts", argv, vaultRoot
|
|
1225
|
+
dispatchSpexConsoleEntry("hs-vault-pull.ts", argv, vaultRoot, {
|
|
1226
|
+
...(vaultIdOverride ? { HS_VAULT_ID_OVERRIDE: vaultIdOverride } : {}),
|
|
1227
|
+
});
|
|
621
1228
|
return;
|
|
622
1229
|
}
|
|
623
1230
|
|
|
@@ -629,6 +1236,14 @@ function main(argv) {
|
|
|
629
1236
|
return;
|
|
630
1237
|
}
|
|
631
1238
|
|
|
1239
|
+
// NOTE: `hs source` is handled further down by the plain-Node
|
|
1240
|
+
// scripts/lib/hs-source-*.mjs cores ([[HS-Q0K8-CWPX-EPIC-spexsource-local-git-preflight-commit]]),
|
|
1241
|
+
// which ship inside the published package. An earlier TypeScript build of the
|
|
1242
|
+
// same verb ([[HS-KN93-V4RB-EPIC-local-git-preflight-commit-and-push-for]],
|
|
1243
|
+
// spex-console/bin/hs-source.ts + src/lib/source-local/) used to be dispatched
|
|
1244
|
+
// from this spot; it was superseded when the two branches merged and must not
|
|
1245
|
+
// be re-wired here, or the package-shipping handler below becomes dead code.
|
|
1246
|
+
|
|
632
1247
|
if (namespace === "factory" && command === "run") {
|
|
633
1248
|
dispatchSpexConsoleEntry("hs-factory-run.ts", argv, vaultRoot);
|
|
634
1249
|
return;
|
|
@@ -659,9 +1274,8 @@ function main(argv) {
|
|
|
659
1274
|
// server-impacting task; stop from any terminal (not only the one that
|
|
660
1275
|
// started it). See [[dev-server-lifecycle-and-db-safety]].
|
|
661
1276
|
if (namespace === "server" && (command === "status" || command === "stop")) {
|
|
662
|
-
const
|
|
663
|
-
|
|
664
|
-
const script = path.join(PKG_ROOT, "scripts", scriptName);
|
|
1277
|
+
const spawnKey = command === "stop" ? "server:stop" : "server:status";
|
|
1278
|
+
const script = spawnTargetPath(spawnKey);
|
|
665
1279
|
const child = spawn(process.execPath, [script, ...rest], {
|
|
666
1280
|
cwd: PKG_ROOT,
|
|
667
1281
|
stdio: "inherit",
|
|
@@ -670,7 +1284,55 @@ function main(argv) {
|
|
|
670
1284
|
child.on("exit", (code, signal) => process.exit(signal ? 1 : (code ?? 0)));
|
|
671
1285
|
child.on("error", (err) => {
|
|
672
1286
|
process.stderr.write(
|
|
673
|
-
`hs: failed to run ${
|
|
1287
|
+
`hs: failed to run ${SPAWN_TARGETS[spawnKey].path}: ${err.message}\n`,
|
|
1288
|
+
);
|
|
1289
|
+
process.exit(1);
|
|
1290
|
+
});
|
|
1291
|
+
return;
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1294
|
+
// `hs wip status` / `hs wip restore <id>` — health report + restore for the
|
|
1295
|
+
// WIP hand-off ledger (`.heyspex/wip-state.json`) and its archive
|
|
1296
|
+
// (`.heyspex/wip-archive/*.jsonl`). Pure Node core under scripts/ (same
|
|
1297
|
+
// pattern as `hs server status/stop` above). See
|
|
1298
|
+
// [[HS-B8OK-PFMN]] / capture [[HS-817Y-83XQ]] ("the WIP ledger maintains
|
|
1299
|
+
// itself"). Forwards `command` plus the rest of argv; the script itself
|
|
1300
|
+
// decides status vs restore off that first token.
|
|
1301
|
+
if (namespace === "wip" && (command === "status" || command === "restore")) {
|
|
1302
|
+
const script = spawnTargetPath("wip");
|
|
1303
|
+
const child = spawn(process.execPath, [script, command, ...rest], {
|
|
1304
|
+
cwd: PKG_ROOT,
|
|
1305
|
+
stdio: "inherit",
|
|
1306
|
+
env: { ...process.env, HS_VAULT_ROOT: vaultRoot },
|
|
1307
|
+
});
|
|
1308
|
+
child.on("exit", (code, signal) => process.exit(signal ? 1 : (code ?? 0)));
|
|
1309
|
+
child.on("error", (err) => {
|
|
1310
|
+
process.stderr.write(
|
|
1311
|
+
`hs: failed to run wip-ledger-health.mjs: ${err.message}\n`,
|
|
1312
|
+
);
|
|
1313
|
+
process.exit(1);
|
|
1314
|
+
});
|
|
1315
|
+
return;
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1318
|
+
// `hs worktree list|alloc|release|reap|rebind` — session/build worktree
|
|
1319
|
+
// registry (HS-03DO / HS-V9XH). Pure Node core under scripts/, itself a
|
|
1320
|
+
// thin shell over dev-factory/lib/worktree-allocator.mjs (called in-process,
|
|
1321
|
+
// never shelled out to). Same dispatch shape as `hs server status/stop` and
|
|
1322
|
+
// `hs wip status/restore` above. Forwards the full sub-argv; the entrypoint
|
|
1323
|
+
// drops the leading `worktree` token itself.
|
|
1324
|
+
if (namespace === "worktree") {
|
|
1325
|
+
const script = spawnTargetPath("worktree");
|
|
1326
|
+
const worktreeArgs = command ? [command, ...rest] : rest;
|
|
1327
|
+
const child = spawn(process.execPath, [script, ...worktreeArgs], {
|
|
1328
|
+
cwd: PKG_ROOT,
|
|
1329
|
+
stdio: "inherit",
|
|
1330
|
+
env: { ...process.env, HS_VAULT_ROOT: vaultRoot },
|
|
1331
|
+
});
|
|
1332
|
+
child.on("exit", (code, signal) => process.exit(signal ? 1 : (code ?? 0)));
|
|
1333
|
+
child.on("error", (err) => {
|
|
1334
|
+
process.stderr.write(
|
|
1335
|
+
`hs: failed to run hs-worktree.mjs: ${err.message}\n`,
|
|
674
1336
|
);
|
|
675
1337
|
process.exit(1);
|
|
676
1338
|
});
|
|
@@ -697,25 +1359,36 @@ function main(argv) {
|
|
|
697
1359
|
" hs source commit Stage + commit per the preflight plan\n" +
|
|
698
1360
|
" hs source push Push the committed branch\n" +
|
|
699
1361
|
" hs source log <spec-id> Commit-history lookup for a spec (console settlement,\n" +
|
|
700
|
-
" falling back to local git log)\n"
|
|
1362
|
+
" falling back to local git log)\n" +
|
|
1363
|
+
" hs source identity [show] [--json]\n" +
|
|
1364
|
+
" Effective git user.name/user.email, where each comes from,\n" +
|
|
1365
|
+
" and whether a repo-local override is authorized (read-only)\n" +
|
|
1366
|
+
" hs source identity set --email <email> [--name <name>] [--global] [--human]\n" +
|
|
1367
|
+
" HUMAN-ONLY: write the identity and record the authorization\n" +
|
|
1368
|
+
" in .heyspex/git-identity.json (agents are refused, exit 3)\n" +
|
|
1369
|
+
" hs source identity clear [--human]\n" +
|
|
1370
|
+
" HUMAN-ONLY: remove the repo-local [user] override + authorization\n" +
|
|
1371
|
+
" Type set/clear yourself — terminal: hs source identity set --email you@example.com\n" +
|
|
1372
|
+
" inside Claude Code: !hs source identity set --email you@example.com --human\n",
|
|
701
1373
|
);
|
|
702
1374
|
process.exit(0);
|
|
703
1375
|
}
|
|
704
|
-
const
|
|
705
|
-
status: "
|
|
706
|
-
preflight: "
|
|
707
|
-
commit: "
|
|
708
|
-
push: "
|
|
709
|
-
log: "
|
|
1376
|
+
const SOURCE_SPAWN_KEYS = {
|
|
1377
|
+
status: "source:status",
|
|
1378
|
+
preflight: "source:preflight",
|
|
1379
|
+
commit: "source:commit",
|
|
1380
|
+
push: "source:push",
|
|
1381
|
+
log: "source:log",
|
|
1382
|
+
identity: "source:identity",
|
|
710
1383
|
};
|
|
711
|
-
const
|
|
712
|
-
if (!
|
|
1384
|
+
const spawnKey = SOURCE_SPAWN_KEYS[command];
|
|
1385
|
+
if (!spawnKey) {
|
|
713
1386
|
process.stderr.write(
|
|
714
|
-
`hs: unknown \`hs source\` verb \`${command ?? ""}\` — expected one of: ${Object.keys(
|
|
1387
|
+
`hs: unknown \`hs source\` verb \`${command ?? ""}\` — expected one of: ${Object.keys(SOURCE_SPAWN_KEYS).join(", ")}\n`,
|
|
715
1388
|
);
|
|
716
1389
|
process.exit(2);
|
|
717
1390
|
}
|
|
718
|
-
const script =
|
|
1391
|
+
const script = spawnTargetPath(spawnKey);
|
|
719
1392
|
const child = spawn(process.execPath, [script, command, ...rest], {
|
|
720
1393
|
cwd: PKG_ROOT,
|
|
721
1394
|
stdio: "inherit",
|
|
@@ -723,7 +1396,9 @@ function main(argv) {
|
|
|
723
1396
|
});
|
|
724
1397
|
child.on("exit", (code, signal) => process.exit(signal ? 1 : (code ?? 0)));
|
|
725
1398
|
child.on("error", (err) => {
|
|
726
|
-
process.stderr.write(
|
|
1399
|
+
process.stderr.write(
|
|
1400
|
+
`hs: failed to run ${SPAWN_TARGETS[spawnKey].path}: ${err.message}\n`,
|
|
1401
|
+
);
|
|
727
1402
|
process.exit(1);
|
|
728
1403
|
});
|
|
729
1404
|
return;
|
|
@@ -734,7 +1409,7 @@ function main(argv) {
|
|
|
734
1409
|
// [[HS-RU2R-I87U-RES-solo-surface-cli-launcher-single]]). Probes ground-truth
|
|
735
1410
|
// server status first; a down console prints a clear message, never a blank window.
|
|
736
1411
|
if (namespace === "view") {
|
|
737
|
-
const script =
|
|
1412
|
+
const script = spawnTargetPath("view");
|
|
738
1413
|
const child = spawn(process.execPath, [script, ...argv.slice(1)], {
|
|
739
1414
|
cwd: PKG_ROOT,
|
|
740
1415
|
stdio: "inherit",
|
|
@@ -779,7 +1454,7 @@ function main(argv) {
|
|
|
779
1454
|
// group (the multi-capture "add another?" gesture). Pure Node, posts the
|
|
780
1455
|
// same heyspex.capture/0.1 envelope the companion sends to /api/drafts.
|
|
781
1456
|
if (namespace === "capture") {
|
|
782
|
-
const script =
|
|
1457
|
+
const script = spawnTargetPath("capture");
|
|
783
1458
|
const captureArgs = command ? [command, ...rest] : rest;
|
|
784
1459
|
const child = spawn(process.execPath, [script, ...captureArgs], {
|
|
785
1460
|
cwd: PKG_ROOT,
|
|
@@ -795,12 +1470,17 @@ function main(argv) {
|
|
|
795
1470
|
}
|
|
796
1471
|
|
|
797
1472
|
// `hs companion start|stop|status|build` — companion tray host (companion/src/HeySpex.Companion.Host).
|
|
1473
|
+
// `hs companion settings ...` and the shortcuts (mute|volume|orb|wake|...) — every
|
|
1474
|
+
// Companion setting via the console's /api/companion/settings ([[HS-UDH1]]); the
|
|
1475
|
+
// script resolves the console through the shared resolver, which needs the
|
|
1476
|
+
// vault root for the `.heyspex/console-url` discovery file (same as `capture`).
|
|
798
1477
|
if (namespace === "companion") {
|
|
799
|
-
const script =
|
|
1478
|
+
const script = spawnTargetPath("companion");
|
|
800
1479
|
const companionArgs = command ? [command, ...rest] : ["start", ...rest];
|
|
801
1480
|
const child = spawn(process.execPath, [script, ...companionArgs], {
|
|
802
1481
|
cwd: PKG_ROOT,
|
|
803
1482
|
stdio: "inherit",
|
|
1483
|
+
env: { ...process.env, HS_VAULT_ROOT: vaultRoot },
|
|
804
1484
|
});
|
|
805
1485
|
child.on("exit", (code, signal) => process.exit(signal ? 1 : (code ?? 0)));
|
|
806
1486
|
child.on("error", (err) => {
|
|
@@ -825,12 +1505,28 @@ function main(argv) {
|
|
|
825
1505
|
|
|
826
1506
|
// `hs next [spec_id] [--summary] [--json]` — resolve the on-deck brief without
|
|
827
1507
|
// MCP ([[HS-TASK-ISK7]] / [[tool-agnostic-mcp-first-cli-second]]).
|
|
1508
|
+
// `hs next` has TWO halves and this branch picks one.
|
|
1509
|
+
//
|
|
1510
|
+
// - `hs next [spec_id] [--summary] [--json]` resolves the on-deck brief
|
|
1511
|
+
// with no console and no MCP (`scripts/hs-next.mjs`).
|
|
1512
|
+
// - `hs next <id> --launch`, and the `model` / `permission-mode` / `claim`
|
|
1513
|
+
// subcommands, need the console (POST /api/agent/start, PUT
|
|
1514
|
+
// /api/settings) and run through `spex-console/bin/hs-next-launch.ts` -
|
|
1515
|
+
// the same shape `hs handoff` uses.
|
|
828
1516
|
if (namespace === "next") {
|
|
829
|
-
|
|
1517
|
+
if (isNextConsoleArgv(argv.slice(1))) {
|
|
1518
|
+
dispatchSpexConsoleEntry("hs-next-launch.ts", argv, vaultRoot);
|
|
1519
|
+
return;
|
|
1520
|
+
}
|
|
1521
|
+
const script = spawnTargetPath("next");
|
|
830
1522
|
const child = spawn(process.execPath, [script, ...argv.slice(1)], {
|
|
831
1523
|
cwd: PKG_ROOT,
|
|
832
1524
|
stdio: "inherit",
|
|
833
|
-
|
|
1525
|
+
// hs-next.mjs loads vendor/heyspex-mcp/cli.mjs, which is CODE, not vault
|
|
1526
|
+
// content — since the repo split, HS_VAULT_ROOT can point at a checkout
|
|
1527
|
+
// (e.g. heyspex-specs) with no vendor/ at all
|
|
1528
|
+
// ([[HS-1LCC-4XKS-DIAG-frozen-checks-run-against-the-vault]]).
|
|
1529
|
+
env: { ...process.env, HS_VAULT_ROOT: vaultRoot, HS_PKG_ROOT: PKG_ROOT },
|
|
834
1530
|
});
|
|
835
1531
|
child.on("exit", (code, signal) => process.exit(signal ? 1 : (code ?? 0)));
|
|
836
1532
|
child.on("error", (err) => {
|
|
@@ -845,7 +1541,7 @@ function main(argv) {
|
|
|
845
1541
|
// Turning it off makes this repo behave like a plain codebase in Claude Code.
|
|
846
1542
|
// Core: scripts/heyspex-mode.mjs · docs/claude-code-setup.md.
|
|
847
1543
|
if (namespace === "mode") {
|
|
848
|
-
const script =
|
|
1544
|
+
const script = spawnTargetPath("mode");
|
|
849
1545
|
const child = spawn(process.execPath, [script, ...argv.slice(1)], {
|
|
850
1546
|
cwd: PKG_ROOT,
|
|
851
1547
|
stdio: "inherit",
|
|
@@ -864,7 +1560,7 @@ function main(argv) {
|
|
|
864
1560
|
// Registration subcommands print a blocked-on-HS-DCCP notice until the
|
|
865
1561
|
// connector's server-registration table exists.
|
|
866
1562
|
if (namespace === "gateway") {
|
|
867
|
-
const script =
|
|
1563
|
+
const script = spawnTargetPath("gateway");
|
|
868
1564
|
const child = spawn(process.execPath, [script, ...argv.slice(1)], {
|
|
869
1565
|
cwd: PKG_ROOT,
|
|
870
1566
|
stdio: "inherit",
|
|
@@ -882,7 +1578,7 @@ function main(argv) {
|
|
|
882
1578
|
// .heyspex/config.yaml (settings capability; core: vendor/heyspex-mcp/
|
|
883
1579
|
// config-settings.mjs; write-permission rules: agent-settings-protocol).
|
|
884
1580
|
if (namespace === "settings") {
|
|
885
|
-
const script =
|
|
1581
|
+
const script = spawnTargetPath("settings");
|
|
886
1582
|
const child = spawn(process.execPath, [script, ...argv], {
|
|
887
1583
|
cwd: PKG_ROOT,
|
|
888
1584
|
stdio: "inherit",
|
|
@@ -899,7 +1595,7 @@ function main(argv) {
|
|
|
899
1595
|
// `hs classifier stats` — read-only view of HeySpex Intent Classifier decisions
|
|
900
1596
|
// (.heyspex/intent-classifier-history.jsonl). Pure local read.
|
|
901
1597
|
if (namespace === "classifier") {
|
|
902
|
-
const script =
|
|
1598
|
+
const script = spawnTargetPath("classifier");
|
|
903
1599
|
const child = spawn(process.execPath, [script, ...argv], {
|
|
904
1600
|
cwd: PKG_ROOT,
|
|
905
1601
|
stdio: "inherit",
|
|
@@ -915,7 +1611,7 @@ function main(argv) {
|
|
|
915
1611
|
|
|
916
1612
|
// `hs auto [filename] [--json]` — resolve /hs-auto COO dispatch plan.
|
|
917
1613
|
if (namespace === "auto") {
|
|
918
|
-
const script =
|
|
1614
|
+
const script = spawnTargetPath("auto");
|
|
919
1615
|
const child = spawn(process.execPath, [script, ...argv.slice(1)], {
|
|
920
1616
|
cwd: PKG_ROOT,
|
|
921
1617
|
stdio: "inherit",
|
|
@@ -936,16 +1632,22 @@ function main(argv) {
|
|
|
936
1632
|
// code for a persistent `hsmcp_` bearer, stored in ~/.heyspex/credentials.yaml
|
|
937
1633
|
// ([[HS-GD45-MCPC-TASK-mcp-cloud-auth-shared-console-resolver]]).
|
|
938
1634
|
if (namespace === "mcp") {
|
|
939
|
-
if (
|
|
1635
|
+
if (
|
|
1636
|
+
command !== "call" &&
|
|
1637
|
+
command !== "pair" &&
|
|
1638
|
+
command !== "login" &&
|
|
1639
|
+
command !== "install"
|
|
1640
|
+
) {
|
|
940
1641
|
process.stderr.write(
|
|
941
|
-
`hs: unknown mcp subcommand \`${command ?? ""}\` (expected: call | pair | login)\n` +
|
|
1642
|
+
`hs: unknown mcp subcommand \`${command ?? ""}\` (expected: call | pair | login | install)\n` +
|
|
942
1643
|
" hs mcp call <tool> [--arg key=value ...] [--args '<json>'] [--args-file <path>] [--json]\n" +
|
|
943
1644
|
" hs mcp pair <code> [--console-url <url>] [--label <text>] [--json]\n" +
|
|
944
|
-
" hs mcp login [--console-url <url>] [--label <text>] [--json] (alias of `hs login`)\n"
|
|
1645
|
+
" hs mcp login [--console-url <url>] [--label <text>] [--json] (alias of `hs login`)\n" +
|
|
1646
|
+
" hs mcp install --remote [--console-url <url>] [--write] [--json] Wire a client to the cloud endpoint\n",
|
|
945
1647
|
);
|
|
946
1648
|
process.exit(2);
|
|
947
1649
|
}
|
|
948
|
-
const cliPath =
|
|
1650
|
+
const cliPath = spawnTargetPath("heyspex-mcp-cli");
|
|
949
1651
|
const child = spawn(process.execPath, [cliPath, command, ...rest], {
|
|
950
1652
|
cwd: PKG_ROOT,
|
|
951
1653
|
stdio: "inherit",
|
|
@@ -959,7 +1661,9 @@ function main(argv) {
|
|
|
959
1661
|
});
|
|
960
1662
|
child.on("exit", (code, signal) => process.exit(signal ? 1 : (code ?? 0)));
|
|
961
1663
|
child.on("error", (err) => {
|
|
962
|
-
process.stderr.write(
|
|
1664
|
+
process.stderr.write(
|
|
1665
|
+
`hs: failed to run mcp ${command}: ${err.message}\n`,
|
|
1666
|
+
);
|
|
963
1667
|
process.exit(1);
|
|
964
1668
|
});
|
|
965
1669
|
return;
|
|
@@ -974,13 +1678,54 @@ function main(argv) {
|
|
|
974
1678
|
return;
|
|
975
1679
|
}
|
|
976
1680
|
|
|
1681
|
+
// `hs harness gates [--json]` — list every registered SpexHarness gate (id,
|
|
1682
|
+
// event, severity, last fixture result), reading the live gate registry +
|
|
1683
|
+
// rule table straight from spexharness/ — no vault/console dependency
|
|
1684
|
+
// ([[HS-XKYY-NYPR-TASK-hs-harness-gates-and-a-console]]).
|
|
1685
|
+
if (namespace === "harness") {
|
|
1686
|
+
dispatchSpexConsoleEntry("hs-harness.ts", argv, vaultRoot || process.cwd());
|
|
1687
|
+
return;
|
|
1688
|
+
}
|
|
1689
|
+
|
|
1690
|
+
// `hs hooks add|remove|list` — the CLI face of the extension seam
|
|
1691
|
+
// ([[HS-8N3K-NYPR-TASK-add-cli-verbs-so-a-developer-can]],
|
|
1692
|
+
// [[HS-8EDJ-NYPR-EPIC-extension-seam-for-other-developers]]). Reads/writes
|
|
1693
|
+
// `.heyspex/harness.json` straight through `spexharness/src/extension/
|
|
1694
|
+
// cli-face.mjs` — the SAME core the API face
|
|
1695
|
+
// (spex-console/src/app/api/harness/extension/route.ts) calls — no
|
|
1696
|
+
// vault/console dependency, same shell shape as `harness gates` above.
|
|
1697
|
+
if (namespace === "hooks") {
|
|
1698
|
+
dispatchSpexConsoleEntry("hs-hooks.ts", argv, vaultRoot || process.cwd());
|
|
1699
|
+
return;
|
|
1700
|
+
}
|
|
1701
|
+
|
|
1702
|
+
// `hs bench <phase1|phase2> --model <name> --harness <on|off>` — run the
|
|
1703
|
+
// harness-on-versus-harness-off benchmark's task set once and write a dated
|
|
1704
|
+
// report under dev-factory/reports/calibrations/bench/
|
|
1705
|
+
// ([[HS-DQ0S-NYPR-TASK-add-a-bench-command-that-runs-the-same]], epic
|
|
1706
|
+
// [[HS-IZCF-NYPR-EPIC-harness-on-versus-harness-off-benchmark]]). Thin CLI
|
|
1707
|
+
// verb only — the real orchestration lives in spexharness/src/bench/*.mjs;
|
|
1708
|
+
// this dispatch branch just reaches the thin shell like `harness` above.
|
|
1709
|
+
if (namespace === "bench") {
|
|
1710
|
+
dispatchSpexConsoleEntry("hs-bench.ts", argv, vaultRoot || process.cwd());
|
|
1711
|
+
return;
|
|
1712
|
+
}
|
|
1713
|
+
|
|
977
1714
|
// `hs check run <id|manifest>` — verify a spec's acceptance criteria against its
|
|
978
1715
|
// frozen checks manifest, per-criterion pass/fail against the real tree
|
|
979
1716
|
// ([[HS-EPIC-DCEP-acceptance-criteria-checks-manifest-dete]], the deterministic
|
|
980
1717
|
// kernel of [[HS-MIL-Y5DN-verification-engine-shared-deterministic]]). Forwards
|
|
981
1718
|
// the full argv; the entrypoint drops the leading `check` token.
|
|
1719
|
+
//
|
|
1720
|
+
// HS_PKG_ROOT is forwarded so the entrypoint can resolve a check's `cwd`
|
|
1721
|
+
// against the CODE root rather than `vaultRoot` — since the repo split, the
|
|
1722
|
+
// vault and the code can be two different checkouts, and a check almost
|
|
1723
|
+
// always runs code, not vault content
|
|
1724
|
+
// ([[HS-1LCC-4XKS-DIAG-frozen-checks-run-against-the-vault]]).
|
|
982
1725
|
if (namespace === "check") {
|
|
983
|
-
dispatchSpexConsoleEntry("hs-check.ts", argv, vaultRoot
|
|
1726
|
+
dispatchSpexConsoleEntry("hs-check.ts", argv, vaultRoot, {
|
|
1727
|
+
HS_PKG_ROOT: PKG_ROOT,
|
|
1728
|
+
});
|
|
984
1729
|
return;
|
|
985
1730
|
}
|
|
986
1731
|
|
|
@@ -988,8 +1733,15 @@ function main(argv) {
|
|
|
988
1733
|
// `review_pending`/`in_review` and report what has decayed since the stamp
|
|
989
1734
|
// ([[HS-R5LF-36QL-EPIC-stamped-work-is-re-verified-on-a]]). Read-only with
|
|
990
1735
|
// respect to the repo; the Resolution plan is a dry run unless `--apply`.
|
|
1736
|
+
//
|
|
1737
|
+
// HS_PKG_ROOT is forwarded for the same reason `check` forwards it: the sweep
|
|
1738
|
+
// re-runs those same frozen manifests, so a check's `cwd` has to resolve
|
|
1739
|
+
// against the CODE root, not the vault
|
|
1740
|
+
// ([[HS-1LCC-4XKS-DIAG-frozen-checks-run-against-the-vault]]).
|
|
991
1741
|
if (namespace === "revalidate") {
|
|
992
|
-
dispatchSpexConsoleEntry("hs-revalidate.ts", argv, vaultRoot
|
|
1742
|
+
dispatchSpexConsoleEntry("hs-revalidate.ts", argv, vaultRoot, {
|
|
1743
|
+
HS_PKG_ROOT: PKG_ROOT,
|
|
1744
|
+
});
|
|
993
1745
|
return;
|
|
994
1746
|
}
|
|
995
1747
|
|
|
@@ -1003,18 +1755,49 @@ function main(argv) {
|
|
|
1003
1755
|
return;
|
|
1004
1756
|
}
|
|
1005
1757
|
|
|
1758
|
+
// `hs chat [--model <name>] [--script <file>] [--seat <seat>]` — HeySpex's own chat
|
|
1759
|
+
// front (step 0 of [[HS-2KYS-NYPR-EPIC-phase-1-end-to-end-on-deepseek-and-on-a]], the
|
|
1760
|
+
// prerequisite that finishes [[HS-F7N3-NYPR-EPIC-the-powershell-front-hs-chat]]). Thin
|
|
1761
|
+
// CLI verb only — every real behavior lives in spexharness/src/chat/*.mjs; this
|
|
1762
|
+
// dispatch branch just reaches the thin shell like every other verb here. Forwards the
|
|
1763
|
+
// full argv; the entrypoint drops the leading `chat` token.
|
|
1764
|
+
if (namespace === "chat") {
|
|
1765
|
+
dispatchSpexConsoleEntry("hs-chat.ts", argv, vaultRoot || process.cwd());
|
|
1766
|
+
return;
|
|
1767
|
+
}
|
|
1768
|
+
|
|
1769
|
+
// `hs trust list` / `hs trust revoke [path]` — standalone management of the machine-local
|
|
1770
|
+
// folder-trust store `hs chat`'s first-run prompt writes to
|
|
1771
|
+
// ([[HS-25AJ-8CMW-TASK-folder-trust-revoke-through-the]], deferred from
|
|
1772
|
+
// [[HS-ZBEB-8CMW-TASK-folder-trust-for-spexcode-first-run]], which shipped only `hs chat
|
|
1773
|
+
// --revoke-trust`). Thin CLI verb — same shell shape as `chat`/`hooks`/`bench` above; the real
|
|
1774
|
+
// logic lives in `spex-console/src/lib/cli/trust-cli.ts` over `folder-trust.ts`'s pure
|
|
1775
|
+
// functions. Forwards the full argv; the entrypoint drops the leading `trust` token.
|
|
1776
|
+
if (namespace === "trust") {
|
|
1777
|
+
dispatchSpexConsoleEntry("hs-trust.ts", argv, vaultRoot || process.cwd());
|
|
1778
|
+
return;
|
|
1779
|
+
}
|
|
1780
|
+
|
|
1781
|
+
// `hs doctrine extract|learn|show` — SpexCode Doctrine corpus reader + synthesis
|
|
1782
|
+
// ([[HS-TWTC-8CMW-TASK-dr1-doctrine-corpus-reader-stream-every]],
|
|
1783
|
+
// [[HS-YMPY-8CMW-TASK-dr2-doctrine-synthesis-a-routed]] of epic
|
|
1784
|
+
// [[HS-KNHZ-8CMW-EPIC-spexcode-doctrine-v0-the-foundational]]). Thin CLI verb —
|
|
1785
|
+
// same shell shape as `chat`/`trust`/`harness` above; the real logic lives in
|
|
1786
|
+
// `spexharness/src/doctrine/*.mjs`. Forwards the full argv; the entrypoint drops
|
|
1787
|
+
// the leading `doctrine` token.
|
|
1788
|
+
if (namespace === "doctrine") {
|
|
1789
|
+
dispatchSpexConsoleEntry(
|
|
1790
|
+
"hs-doctrine.ts",
|
|
1791
|
+
argv,
|
|
1792
|
+
vaultRoot || process.cwd(),
|
|
1793
|
+
);
|
|
1794
|
+
return;
|
|
1795
|
+
}
|
|
1796
|
+
|
|
1006
1797
|
// Neon control-plane verbs ([[HS-EPIC-CLMW-hs-cli-mcp-wrappers-for-control-plane]]).
|
|
1007
1798
|
// Each forwards the full argv (namespace/verb + args); the entrypoint drops
|
|
1008
|
-
// the leading namespace token and parses the rest.
|
|
1009
|
-
|
|
1010
|
-
pin: "hs-pin.ts",
|
|
1011
|
-
claim: "hs-claim.ts",
|
|
1012
|
-
handoff: "hs-handoff.ts",
|
|
1013
|
-
events: "hs-events.ts",
|
|
1014
|
-
"export-events": "hs-export-events.ts",
|
|
1015
|
-
prereq: "hs-prereq.ts",
|
|
1016
|
-
queue: "hs-queue.ts",
|
|
1017
|
-
};
|
|
1799
|
+
// the leading namespace token and parses the rest. (Table itself now lives
|
|
1800
|
+
// at module scope as CONTROL_ENTRYPOINTS — see its definition above main().)
|
|
1018
1801
|
if (Object.hasOwn(CONTROL_ENTRYPOINTS, namespace)) {
|
|
1019
1802
|
dispatchSpexConsoleEntry(CONTROL_ENTRYPOINTS[namespace], argv, vaultRoot);
|
|
1020
1803
|
return;
|
|
@@ -1025,4 +1808,54 @@ function main(argv) {
|
|
|
1025
1808
|
process.exit(2);
|
|
1026
1809
|
}
|
|
1027
1810
|
|
|
1028
|
-
|
|
1811
|
+
// Only run the CLI when this file is executed directly (`node bin/hs.mjs` /
|
|
1812
|
+
// the `hs`/`heyspex` bin shims), not when another script imports it — e.g.
|
|
1813
|
+
// `scripts/verify-mcp-package.mjs`'s `spawnTargets` check and
|
|
1814
|
+
// `scripts/lib/spawn-target-manifest.test.mjs` import `SPAWN_TARGETS` above
|
|
1815
|
+
// without wanting `main()` to run the dispatcher against THEIR argv.
|
|
1816
|
+
//
|
|
1817
|
+
// A naive `import.meta.url === pathToFileURL(process.argv[1]).href` (the
|
|
1818
|
+
// pattern `scripts/verify-mcp-package.mjs`'s own `isMain` guard uses — safe
|
|
1819
|
+
// there because that script is never reached through an npm bin shim) is
|
|
1820
|
+
// NOT enough here: an `npm link`ed / globally-installed `hs` runs
|
|
1821
|
+
// `node "<npm-prefix>/node_modules/heyspex/bin/hs.mjs"`, and when that path
|
|
1822
|
+
// is a symlink (npm link's install shape), Node's default module resolution
|
|
1823
|
+
// realpaths it for `import.meta.url` but leaves `process.argv[1]` as the
|
|
1824
|
+
// ORIGINAL (symlinked) path the shim passed — so the two never matched,
|
|
1825
|
+
// `isMain` came out false, and the globally-installed `hs` silently printed
|
|
1826
|
+
// nothing and exited 0 for every verb ([[HS-561M]] regression). Fix:
|
|
1827
|
+
// realpath BOTH sides before comparing (so the symlink resolves the same way
|
|
1828
|
+
// on each side), case-insensitively on Windows (drive-letter casing can
|
|
1829
|
+
// differ between how a shim built the path and how realpath reports it).
|
|
1830
|
+
function isSameFile(a, b) {
|
|
1831
|
+
const normalize = (p) => {
|
|
1832
|
+
let real;
|
|
1833
|
+
try {
|
|
1834
|
+
real = realpathSync.native(p);
|
|
1835
|
+
} catch {
|
|
1836
|
+
real = path.resolve(p);
|
|
1837
|
+
}
|
|
1838
|
+
return process.platform === "win32" ? real.toLowerCase() : real;
|
|
1839
|
+
};
|
|
1840
|
+
return normalize(a) === normalize(b);
|
|
1841
|
+
}
|
|
1842
|
+
|
|
1843
|
+
const isMain = (() => {
|
|
1844
|
+
if (!process.argv[1]) return false;
|
|
1845
|
+
try {
|
|
1846
|
+
if (isSameFile(process.argv[1], fileURLToPath(import.meta.url)))
|
|
1847
|
+
return true;
|
|
1848
|
+
} catch {
|
|
1849
|
+
// fall through to the basename fallback below
|
|
1850
|
+
}
|
|
1851
|
+
// Belt-and-suspenders fallback for install shapes realpath can't reconcile
|
|
1852
|
+
// (a shim invoking through an intermediate wrapper, a path realpath can't
|
|
1853
|
+
// stat, etc.): recognize this file by its own known bin names rather than
|
|
1854
|
+
// failing closed into silent no-output.
|
|
1855
|
+
const base = path.basename(process.argv[1]).toLowerCase();
|
|
1856
|
+
return base === "hs.mjs" || base === "hs" || base === "heyspex";
|
|
1857
|
+
})();
|
|
1858
|
+
|
|
1859
|
+
if (isMain) {
|
|
1860
|
+
main(process.argv.slice(2));
|
|
1861
|
+
}
|