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
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* hs-command.mjs — the ONE resolver for "how do you actually run a HeySpex
|
|
4
|
+
* command on THIS machine"
|
|
5
|
+
* ([[HS-I6HT-EKYU-DIAG-every-message-tells-the-user-to-run-hs]]).
|
|
6
|
+
*
|
|
7
|
+
* THE DEFECT THIS EXISTS TO KILL. Every place that told a human what to run
|
|
8
|
+
* hard-coded `hs login`. On the owner's own machine that command does not
|
|
9
|
+
* exist — `which hs` finds nothing in Git Bash and `Get-Command hs` returns
|
|
10
|
+
* nothing in PowerShell, because the package declares the `hs` bin but
|
|
11
|
+
* nothing has ever linked it globally. The product printed advice that could
|
|
12
|
+
* not be carried out, which is the same shape as the console-unreachable
|
|
13
|
+
* message that sent agents off to start a local dev server.
|
|
14
|
+
*
|
|
15
|
+
* THE FIX IS THE MESSAGE, NOT THE PATH (decided 2026-08-23, see the diagnose).
|
|
16
|
+
* Linking `hs` globally would point one command name at whatever branch
|
|
17
|
+
* happens to be checked out, and would do nothing at all for a customer who
|
|
18
|
+
* never links it — so it fixes one machine and leaves the product defect
|
|
19
|
+
* standing. Instead: every message asks THIS module for the runnable form and
|
|
20
|
+
* prints that.
|
|
21
|
+
*
|
|
22
|
+
* `hs` resolves on PATH → `hs login`
|
|
23
|
+
* it does not → `node "C:\path\to\checkout\bin\hs.mjs" login`
|
|
24
|
+
*
|
|
25
|
+
* NEVER PRINT AN `npx` FORM. THIS IS MEASURED, NOT ASSUMED — do not
|
|
26
|
+
* re-derive it. The tempting fallback is `npx hs login`, and inside a
|
|
27
|
+
* checkout it even works, which is exactly what makes it a trap. It fails
|
|
28
|
+
* for the reader because **`npx` resolves from the reader's CURRENT WORKING
|
|
29
|
+
* DIRECTORY, not from wherever the message was printed.** A human reads an
|
|
30
|
+
* error in one window and pastes the command into whatever terminal is open,
|
|
31
|
+
* which is usually not the checkout — the owner did precisely that today,
|
|
32
|
+
* typing `hs login` at a shell sitting in his home folder. Measured
|
|
33
|
+
* 2026-08-23 from a directory outside the repo:
|
|
34
|
+
*
|
|
35
|
+
* npx --no -- hs --version
|
|
36
|
+
* → npm error npx canceled due to missing packages: ["hs@2.0.1"]
|
|
37
|
+
* npx --no -- heyspex --version
|
|
38
|
+
* → npm error 404 Not Found - GET https://registry.npmjs.org/heyspex
|
|
39
|
+
*
|
|
40
|
+
* So `npx hs` offers to install an unrelated public package (and WITHOUT
|
|
41
|
+
* `--no` it simply installs it), and `npx heyspex` cannot work either because
|
|
42
|
+
* this package is not published publicly. That is the same trap
|
|
43
|
+
* [[never-print-npx-hs-it-installs-a]] recorded on 2026-08-12 — note that
|
|
44
|
+
* lesson predates a rename and still calls the package `workflow-pm`; the
|
|
45
|
+
* name is now `heyspex`, and neither name is a safe npx target.
|
|
46
|
+
*
|
|
47
|
+
* THE ABSOLUTE-PATH RULE. When `hs` is not on PATH we print an absolute-path
|
|
48
|
+
* invocation of THIS install's dispatcher: `node <abs>/bin/hs.mjs <args>`.
|
|
49
|
+
* The path is resolved from THIS MODULE'S OWN LOCATION (`import.meta.url`),
|
|
50
|
+
* never from `process.cwd()`, so the printed command runs from any folder,
|
|
51
|
+
* touches no registry, and cannot resolve to somebody else's package. The
|
|
52
|
+
* reader provably has this install, because this install is what printed the
|
|
53
|
+
* message. It is also the form the lesson itself blesses: a repo-local
|
|
54
|
+
* dispatcher is valid precisely when the checkout exists — which is the case
|
|
55
|
+
* here and was NOT the case in the fresh-machine join scenario that lesson
|
|
56
|
+
* was written about. Do NOT reuse this helper to build text handed to a
|
|
57
|
+
* machine that has no HeySpex yet; that path is a public join URL, not a
|
|
58
|
+
* command line.
|
|
59
|
+
*
|
|
60
|
+
* SYNCHRONOUS, CACHED, NEVER THROWS. The lookup is a plain scan of `PATH`
|
|
61
|
+
* with `fs.existsSync` — no `child_process`, so it costs microseconds and is
|
|
62
|
+
* safe to call while building an error string. The answer is cached after
|
|
63
|
+
* the first call (PATH does not change inside a process). Every failure mode
|
|
64
|
+
* degrades to the absolute-path dispatcher form rather than throwing,
|
|
65
|
+
* because the caller is usually already in the middle of reporting a
|
|
66
|
+
* different failure.
|
|
67
|
+
*
|
|
68
|
+
* SHELL-INDEPENDENT. Node sees the Windows-form `PATH` (`C:\…;C:\…`) whether
|
|
69
|
+
* it was launched from Git Bash, PowerShell, or cmd — the MSYS layer
|
|
70
|
+
* converts it back on the way into a native binary — so one `path.delimiter`
|
|
71
|
+
* split gives the same answer in all three. On Windows the scan accepts the
|
|
72
|
+
* extensionless shim (what Git Bash runs), every `PATHEXT` variant (what cmd
|
|
73
|
+
* runs), and `.ps1` (what PowerShell runs); `npm link` writes all of them
|
|
74
|
+
* together, so any one of them proves the command is live. The dispatcher
|
|
75
|
+
* path is always double-quoted on Windows — see `shellQuote` for the
|
|
76
|
+
* measurement, and note that a BARE backslash path is broken in Git Bash
|
|
77
|
+
* specifically, which is the shell coding agents use.
|
|
78
|
+
*
|
|
79
|
+
* Plain Node ESM, zero deps beyond `node:fs`/`node:path`/`node:url`, and —
|
|
80
|
+
* deliberately — ZERO imports of other repo files. `console-url-resolver.mjs`
|
|
81
|
+
* grew a static import of `dev-mode.mjs` on 2026-08-23 and broke a clean
|
|
82
|
+
* install of the published package (module-not-found on load) until the file
|
|
83
|
+
* was added to the package list. If this module ever gains a repo import,
|
|
84
|
+
* run `node scripts/verify-mcp-package.mjs` before calling the work done.
|
|
85
|
+
*/
|
|
86
|
+
import { existsSync } from 'node:fs';
|
|
87
|
+
import { delimiter, dirname, join } from 'node:path';
|
|
88
|
+
import { fileURLToPath } from 'node:url';
|
|
89
|
+
|
|
90
|
+
/** The command name the package declares (`bin.hs` in package.json). */
|
|
91
|
+
export const HS_BIN_NAME = 'hs';
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Absolute path to THIS install's CLI dispatcher, resolved from this
|
|
95
|
+
* module's own location — `<root>/scripts/lib/hs-command.mjs` → up three →
|
|
96
|
+
* `<root>`, then `bin/hs.mjs`. That layout holds in a checkout AND in the
|
|
97
|
+
* published package (both `bin/hs.mjs` and `scripts/lib/hs-command.mjs` are
|
|
98
|
+
* in the package's `files` list). Never derived from `process.cwd()`.
|
|
99
|
+
*/
|
|
100
|
+
export const HS_DISPATCHER_PATH = (() => {
|
|
101
|
+
try {
|
|
102
|
+
const here = dirname(fileURLToPath(import.meta.url)); // <root>/scripts/lib
|
|
103
|
+
return join(dirname(dirname(here)), 'bin', 'hs.mjs');
|
|
104
|
+
} catch {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
})();
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Quote the dispatcher path for pasting into a shell.
|
|
111
|
+
*
|
|
112
|
+
* MEASURED 2026-08-23 from the home folder, running the printed string
|
|
113
|
+
* verbatim in each shell:
|
|
114
|
+
*
|
|
115
|
+
* node C:\…\bin\hs.mjs Git Bash ✗ PowerShell ✓ cmd ✓
|
|
116
|
+
* node "C:\…\bin\hs.mjs" Git Bash ✓ PowerShell ✓ cmd ✓
|
|
117
|
+
*
|
|
118
|
+
* Git Bash eats the backslashes as escapes when the path is bare, and Git
|
|
119
|
+
* Bash is the shell coding agents run commands in — so an unquoted Windows
|
|
120
|
+
* path would re-create the exact defect this module exists to kill, for the
|
|
121
|
+
* agent audience. Any path carrying a backslash or whitespace is therefore
|
|
122
|
+
* always double-quoted; a clean POSIX path is left bare.
|
|
123
|
+
*/
|
|
124
|
+
function shellQuote(p) {
|
|
125
|
+
return /[\\\s]/.test(p) ? `"${p}"` : p;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* What a message prints in front of a subcommand when `hs` is NOT on PATH:
|
|
130
|
+
* `node <abs>/bin/hs.mjs`. Falls back to the bare name only if this module
|
|
131
|
+
* cannot locate itself at all (should be impossible; never throw).
|
|
132
|
+
*/
|
|
133
|
+
export const HS_DISPATCHER_PREFIX = HS_DISPATCHER_PATH
|
|
134
|
+
? `node ${shellQuote(HS_DISPATCHER_PATH)}`
|
|
135
|
+
: HS_BIN_NAME;
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Windows-only extensions to try on top of the bare name. `PATHEXT` covers
|
|
139
|
+
* cmd/PowerShell's own list; we add the extensionless shim (Git Bash runs
|
|
140
|
+
* it) and `.ps1` (PowerShell runs it, and it is absent from the default
|
|
141
|
+
* `PATHEXT`).
|
|
142
|
+
*/
|
|
143
|
+
function candidateNames(name, env) {
|
|
144
|
+
if (process.platform !== 'win32') return [name];
|
|
145
|
+
// Lower-cased on purpose: npm writes `hs.cmd` / `hs.ps1` in lower case, and
|
|
146
|
+
// the Windows filesystem is case-insensitive, so one spelling is enough.
|
|
147
|
+
const pathext = (env.PATHEXT || '.COM;.EXE;.BAT;.CMD')
|
|
148
|
+
.split(';')
|
|
149
|
+
.map((e) => e.trim().toLowerCase())
|
|
150
|
+
.filter(Boolean);
|
|
151
|
+
const extensions = [...new Set([...pathext, '.ps1'])];
|
|
152
|
+
return [name, ...extensions.map((ext) => name + ext)];
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Is `name` an executable somewhere on `PATH`? Pure `fs.existsSync` — no
|
|
157
|
+
* subprocess. Returns the absolute path of the first hit, or `null`.
|
|
158
|
+
*
|
|
159
|
+
* Injectable `env` and `exists` so tests can describe a machine instead of
|
|
160
|
+
* depending on the one they run on.
|
|
161
|
+
*/
|
|
162
|
+
export function findOnPath(name, { env = process.env, exists = existsSync } = {}) {
|
|
163
|
+
try {
|
|
164
|
+
const raw = env.PATH ?? env.Path ?? env.path ?? '';
|
|
165
|
+
for (const dir of String(raw).split(delimiter)) {
|
|
166
|
+
const trimmed = dir.trim().replace(/^"(.*)"$/, '$1');
|
|
167
|
+
if (!trimmed) continue;
|
|
168
|
+
for (const candidate of candidateNames(name, env)) {
|
|
169
|
+
const full = join(trimmed, candidate);
|
|
170
|
+
try {
|
|
171
|
+
if (exists(full)) return full;
|
|
172
|
+
} catch {
|
|
173
|
+
// An unreadable or denied PATH entry is not a match — keep scanning.
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
} catch {
|
|
178
|
+
// A malformed PATH is treated as "not found", never as a crash.
|
|
179
|
+
}
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
let cached = null;
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Test/CI escape hatch: force the "is `hs` on PATH" answer WITHOUT touching
|
|
187
|
+
* PATH itself, so a fixture recorded on one machine's PATH state stays
|
|
188
|
+
* reproducible on another's. THE DEFECT THIS EXISTS TO KILL: a golden
|
|
189
|
+
* recorded when `hs` was not linked started failing the moment `npm link`
|
|
190
|
+
* put it on this developer's PATH — the resolver was correct, the fixture
|
|
191
|
+
* was silently machine-dependent. Recognized values (read via
|
|
192
|
+
* `describeHsCommand`'s `env`, or `process.env` when none is injected):
|
|
193
|
+
*
|
|
194
|
+
* "1" → force on-PATH (prefix = `hs`, as a linked install would show)
|
|
195
|
+
* "0" → force NOT on PATH (prefix = the absolute dispatcher form)
|
|
196
|
+
* anything else (including unset) → the real PATH scan decides, unchanged
|
|
197
|
+
*
|
|
198
|
+
* Never set by product code — only test/CI fixtures reach for this, and
|
|
199
|
+
* only to PIN a canonical form, never to fake a real machine's state for a
|
|
200
|
+
* human-facing message.
|
|
201
|
+
*/
|
|
202
|
+
export const HS_ON_PATH_OVERRIDE_ENV = 'HEYSPEX_HS_ON_PATH';
|
|
203
|
+
|
|
204
|
+
function onPathOverride(env) {
|
|
205
|
+
const raw = String(env?.[HS_ON_PATH_OVERRIDE_ENV] ?? '');
|
|
206
|
+
if (raw === '1') return true;
|
|
207
|
+
if (raw === '0') return false;
|
|
208
|
+
return undefined;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Describe how HeySpex commands run on this machine.
|
|
213
|
+
*
|
|
214
|
+
* @returns {{ onPath: boolean, prefix: string, resolvedPath: string|null,
|
|
215
|
+
* dispatcherPath: string|null }}
|
|
216
|
+
* `prefix` is what a message should put in front of a subcommand — `"hs"`
|
|
217
|
+
* when the bin is on PATH, else `node <abs>/bin/hs.mjs`.
|
|
218
|
+
*/
|
|
219
|
+
export function describeHsCommand({ env, exists, useCache = true } = {}) {
|
|
220
|
+
const injected = env !== undefined || exists !== undefined;
|
|
221
|
+
const effectiveEnv = env ?? process.env;
|
|
222
|
+
|
|
223
|
+
const override = onPathOverride(effectiveEnv);
|
|
224
|
+
if (override !== undefined) {
|
|
225
|
+
// The override wins outright — it is never merged with a real PATH scan,
|
|
226
|
+
// and it is never cached (it's a cheap string compare, and caching it
|
|
227
|
+
// would let one test's override leak into the next call).
|
|
228
|
+
return {
|
|
229
|
+
onPath: override,
|
|
230
|
+
prefix: override ? HS_BIN_NAME : HS_DISPATCHER_PREFIX,
|
|
231
|
+
resolvedPath: null,
|
|
232
|
+
dispatcherPath: HS_DISPATCHER_PATH,
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (useCache && !injected && cached) return cached;
|
|
237
|
+
|
|
238
|
+
const resolvedPath = findOnPath(HS_BIN_NAME, {
|
|
239
|
+
env: env ?? process.env,
|
|
240
|
+
exists: exists ?? existsSync,
|
|
241
|
+
});
|
|
242
|
+
const answer = {
|
|
243
|
+
onPath: resolvedPath !== null,
|
|
244
|
+
prefix: resolvedPath !== null ? HS_BIN_NAME : HS_DISPATCHER_PREFIX,
|
|
245
|
+
resolvedPath,
|
|
246
|
+
dispatcherPath: HS_DISPATCHER_PATH,
|
|
247
|
+
};
|
|
248
|
+
if (useCache && !injected) cached = answer;
|
|
249
|
+
return answer;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* The runnable form of a HeySpex command, ready to paste into a message.
|
|
254
|
+
*
|
|
255
|
+
* hsCommand('login') → 'hs login'
|
|
256
|
+
* or 'node "C:\dev-projects\workflow-protocol\bin\hs.mjs" login'
|
|
257
|
+
* hsCommand() → 'hs' or 'node "…\bin\hs.mjs"'
|
|
258
|
+
*
|
|
259
|
+
* Never throws: an unexpected failure yields the absolute-path dispatcher
|
|
260
|
+
* form, which runs from any working directory on a machine that has this
|
|
261
|
+
* install.
|
|
262
|
+
*/
|
|
263
|
+
export function hsCommand(args = '', options = {}) {
|
|
264
|
+
let prefix = HS_DISPATCHER_PREFIX;
|
|
265
|
+
try {
|
|
266
|
+
prefix = describeHsCommand(options).prefix;
|
|
267
|
+
} catch {
|
|
268
|
+
// Fall through to the dispatcher form — a message must never fail to render.
|
|
269
|
+
}
|
|
270
|
+
const tail = String(args ?? '').trim();
|
|
271
|
+
return tail ? `${prefix} ${tail}` : prefix;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/** Test seam: forget the cached PATH lookup. */
|
|
275
|
+
export function resetHsCommandCache() {
|
|
276
|
+
cached = null;
|
|
277
|
+
}
|