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/spexcode.mjs
ADDED
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* bin/spexcode.mjs — `spexcode`, the named PATH launcher for SpexCode
|
|
4
|
+
* (formerly SpexHarness — historical name only), HeySpex's own agent
|
|
5
|
+
* harness. Running `spexcode` opens a SpexCode session: it forwards to the
|
|
6
|
+
* chat front (`hs chat`, i.e. `bin/hs.mjs`'s `chat` verb dispatching to
|
|
7
|
+
* `spex-console/bin/hs-chat.ts`).
|
|
8
|
+
*
|
|
9
|
+
* Epic: [[HS-MSDL-8CMW-EPIC-spexcode-ships-as-a-named-path-launcher]]
|
|
10
|
+
* Research: [[HS-TJKO-8CMW-RES-spexcode-powershell-launcher-seat-model]]
|
|
11
|
+
*
|
|
12
|
+
* This file is a THIN forwarder plus a `--seat` guard — no chat-loop code,
|
|
13
|
+
* no model logic. Every real behavior lives in `hs chat`
|
|
14
|
+
* (spex-console/src/lib/cli/chat.ts, spexharness/src/chat/*.mjs).
|
|
15
|
+
*
|
|
16
|
+
* Two responsibilities, both pure-testable (see the exported functions
|
|
17
|
+
* below, exercised without spawning a real process or touching the real
|
|
18
|
+
* `dev-factory/seats.json`):
|
|
19
|
+
*
|
|
20
|
+
* 1. HS-FWTK — before forwarding, if `--seat <id>` / `--seat=<id>` is
|
|
21
|
+
* present, refuse the seat when its `dev-factory/seats.json`
|
|
22
|
+
* `fill.backends[]` has no entry whose `kind` is `SPEXCODE_BACKEND_KIND`
|
|
23
|
+
* ("spexcode") — naming the seat and the exact registry entry to change
|
|
24
|
+
* — and exit 2 without spawning anything.
|
|
25
|
+
* 2. HS-HF24 — otherwise, spawn `node bin/hs.mjs chat <...args>` with every
|
|
26
|
+
* argument forwarded unchanged, `stdio: "inherit"`, SIGINT forwarded to
|
|
27
|
+
* the child so its own graceful-shutdown path
|
|
28
|
+
* (spexharness/src/chat/chat-sigint.mjs) runs, and exit with the
|
|
29
|
+
* child's exit code (or 1 on a spawn error).
|
|
30
|
+
*/
|
|
31
|
+
import { spawn } from "node:child_process";
|
|
32
|
+
import { readFileSync, realpathSync } from "node:fs";
|
|
33
|
+
import path from "node:path";
|
|
34
|
+
import process from "node:process";
|
|
35
|
+
import { fileURLToPath } from "node:url";
|
|
36
|
+
|
|
37
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
38
|
+
|
|
39
|
+
/** `bin/hs.mjs`, resolved relative to THIS file (not the caller's cwd) so
|
|
40
|
+
* `spexcode` works from any directory once it's on PATH. */
|
|
41
|
+
const HS_PATH = path.join(__dirname, "hs.mjs");
|
|
42
|
+
|
|
43
|
+
/** `dev-factory/seats.json`, resolved relative to this file's location
|
|
44
|
+
* (`bin/` is one level under the repo root). */
|
|
45
|
+
const SEATS_JSON_PATH = path.resolve(__dirname, "..", "dev-factory", "seats.json");
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* The `fill.backends[].kind` string a seat must carry for `spexcode` to
|
|
49
|
+
* launch it. NOT one of the coarse categories already in use elsewhere in
|
|
50
|
+
* `seats.json` (`cloud-api`, `ide-session`, `local`, `human`, `routed`) —
|
|
51
|
+
* per research §"Seats & Backend Routing", "SpexCode is one more backend
|
|
52
|
+
* kind alongside Claude Code, Cursor, OpenCode, and CommandCode", so it gets
|
|
53
|
+
* its own kind value rather than overloading an existing category. The
|
|
54
|
+
* `coo-phase-1` seat's `fill.backends[]` carries a `"spexcode/hs-chat"`
|
|
55
|
+
* entry with this kind (see dev-factory/seats.json) so bare `spexcode` and
|
|
56
|
+
* `spexcode --seat coo-phase-1` are never refused.
|
|
57
|
+
*/
|
|
58
|
+
export const SPEXCODE_BACKEND_KIND = "spexcode";
|
|
59
|
+
|
|
60
|
+
/** Every argument `spexcode` receives is forwarded unchanged to `hs chat`. */
|
|
61
|
+
export function buildForwardArgs(argv) {
|
|
62
|
+
return ["chat", ...argv];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Parse `--seat X` or `--seat=X` out of argv. Returns `null` when no `--seat`
|
|
67
|
+
* flag is present (or `--seat` is the last token with no value — `hs chat`'s
|
|
68
|
+
* own parser reports that usage error once forwarded).
|
|
69
|
+
*/
|
|
70
|
+
export function parseSeatArg(argv) {
|
|
71
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
72
|
+
const token = argv[i];
|
|
73
|
+
if (token === "--seat") {
|
|
74
|
+
const value = argv[i + 1];
|
|
75
|
+
return value !== undefined ? value : null;
|
|
76
|
+
}
|
|
77
|
+
if (token.startsWith("--seat=")) {
|
|
78
|
+
return token.slice("--seat=".length);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Find a seat by id in a parsed `seats.json` object. `null` when absent. */
|
|
85
|
+
export function findSeat(seatsRegistry, seatId) {
|
|
86
|
+
const seats = Array.isArray(seatsRegistry?.seats) ? seatsRegistry.seats : [];
|
|
87
|
+
return seats.find((seat) => seat && seat.id === seatId) ?? null;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** True when `seat.fill.backends[]` has an entry with kind `"spexcode"`. */
|
|
91
|
+
export function seatHasSpexCodeBackend(seat) {
|
|
92
|
+
const backends = Array.isArray(seat?.fill?.backends) ? seat.fill.backends : [];
|
|
93
|
+
return backends.some((backend) => backend && backend.kind === SPEXCODE_BACKEND_KIND);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Decide whether `argv` (as given to `spexcode`, before the `chat` verb is
|
|
98
|
+
* prepended) may forward to `hs chat`. Pure — takes the already-parsed
|
|
99
|
+
* `seats.json` object so tests never touch the filesystem.
|
|
100
|
+
*
|
|
101
|
+
* @param {readonly string[]} argv
|
|
102
|
+
* @param {{seats?: Array}} seatsRegistry
|
|
103
|
+
* @returns {{allowed: true} | {allowed: false, message: string}}
|
|
104
|
+
*/
|
|
105
|
+
export function resolveSeatGuard(argv, seatsRegistry) {
|
|
106
|
+
const seatId = parseSeatArg(argv);
|
|
107
|
+
if (seatId === null) return { allowed: true };
|
|
108
|
+
|
|
109
|
+
const seat = findSeat(seatsRegistry, seatId);
|
|
110
|
+
if (seat && seatHasSpexCodeBackend(seat)) {
|
|
111
|
+
return { allowed: true };
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const reason = seat
|
|
115
|
+
? `has no fill.backends[] entry with kind "${SPEXCODE_BACKEND_KIND}"`
|
|
116
|
+
: "is not a known seat in dev-factory/seats.json";
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
allowed: false,
|
|
120
|
+
message:
|
|
121
|
+
`spexcode: refusing --seat "${seatId}" — seat "${seatId}" ${reason}. ` +
|
|
122
|
+
`Fix: add a backend with kind "${SPEXCODE_BACKEND_KIND}" to dev-factory/seats.json's ` +
|
|
123
|
+
`seats[${seatId}].fill.backends (see the "coo-phase-1" seat's "spexcode/hs-chat" entry ` +
|
|
124
|
+
`for an example), then retry. Nothing was started.`,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** Load + JSON.parse `dev-factory/seats.json`. Isolated so tests can stub it. */
|
|
129
|
+
export function loadSeatsRegistry(seatsJsonPath = SEATS_JSON_PATH) {
|
|
130
|
+
return JSON.parse(readFileSync(seatsJsonPath, "utf8"));
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Spawn `node hs.mjs chat <...argv>`, inheriting stdio, forwarding SIGINT to
|
|
135
|
+
* the child so its own graceful-shutdown path runs, and resolving with the
|
|
136
|
+
* child's exit code (or `1` on a spawn error or an unclean signal exit).
|
|
137
|
+
* `spawnFn` is injectable so tests never spawn a real process.
|
|
138
|
+
*
|
|
139
|
+
* @param {readonly string[]} argv
|
|
140
|
+
* @param {{hsPath?: string, spawnFn?: typeof spawn}} [opts]
|
|
141
|
+
* @returns {Promise<number>}
|
|
142
|
+
*/
|
|
143
|
+
export function runForward(argv, { hsPath = HS_PATH, spawnFn = spawn } = {}) {
|
|
144
|
+
return new Promise((resolve) => {
|
|
145
|
+
const child = spawnFn(process.execPath, [hsPath, ...buildForwardArgs(argv)], {
|
|
146
|
+
stdio: "inherit",
|
|
147
|
+
shell: false,
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
const forwardSigint = () => {
|
|
151
|
+
child.kill("SIGINT");
|
|
152
|
+
};
|
|
153
|
+
process.on("SIGINT", forwardSigint);
|
|
154
|
+
|
|
155
|
+
const cleanup = () => {
|
|
156
|
+
process.removeListener("SIGINT", forwardSigint);
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
child.on("error", () => {
|
|
160
|
+
cleanup();
|
|
161
|
+
resolve(1);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
child.on("exit", (code, signal) => {
|
|
165
|
+
cleanup();
|
|
166
|
+
if (typeof code === "number") {
|
|
167
|
+
resolve(code);
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
resolve(signal ? 1 : 0);
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* The full orchestration — seat guard, then forward — with every dependency
|
|
177
|
+
* injectable so tests exercise it end to end without touching the real
|
|
178
|
+
* filesystem or spawning a real process.
|
|
179
|
+
*
|
|
180
|
+
* @param {readonly string[]} argv
|
|
181
|
+
* @param {{spawnFn?: typeof spawn, hsPath?: string, loadRegistry?: typeof loadSeatsRegistry, stderr?: {write: Function}}} [deps]
|
|
182
|
+
* @returns {Promise<number>}
|
|
183
|
+
*/
|
|
184
|
+
export async function runSpexcode(argv, deps = {}) {
|
|
185
|
+
const {
|
|
186
|
+
spawnFn = spawn,
|
|
187
|
+
hsPath = HS_PATH,
|
|
188
|
+
loadRegistry = loadSeatsRegistry,
|
|
189
|
+
stderr = process.stderr,
|
|
190
|
+
} = deps;
|
|
191
|
+
|
|
192
|
+
const seatId = parseSeatArg(argv);
|
|
193
|
+
if (seatId !== null) {
|
|
194
|
+
let registry;
|
|
195
|
+
try {
|
|
196
|
+
registry = loadRegistry();
|
|
197
|
+
} catch (err) {
|
|
198
|
+
stderr.write(
|
|
199
|
+
`spexcode: could not read dev-factory/seats.json to check --seat "${seatId}": ` +
|
|
200
|
+
`${err && err.message ? err.message : err}\n`,
|
|
201
|
+
);
|
|
202
|
+
return 2;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const guard = resolveSeatGuard(argv, registry);
|
|
206
|
+
if (!guard.allowed) {
|
|
207
|
+
stderr.write(guard.message + "\n");
|
|
208
|
+
return 2;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return runForward(argv, { hsPath, spawnFn });
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
async function main() {
|
|
216
|
+
const argv = process.argv.slice(2);
|
|
217
|
+
process.exitCode = await runSpexcode(argv);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Same fix as bin/hs.mjs's `isSameFile`/`isMain` ([[HS-561M]]): a naive
|
|
222
|
+
* `pathToFileURL(process.argv[1]).href === import.meta.url` idiom breaks
|
|
223
|
+
* under `npm link`. The global shim invokes
|
|
224
|
+
* `node "<npm-prefix>/node_modules/heyspex/bin/spexcode.mjs"`; that path is
|
|
225
|
+
* a symlink (npm link's install shape), and Node's default module
|
|
226
|
+
* resolution realpaths it for `import.meta.url` but leaves `process.argv[1]`
|
|
227
|
+
* as the ORIGINAL (symlinked) path the shim passed. The two never match,
|
|
228
|
+
* `isMain` comes out false, and the globally-installed `spexcode` silently
|
|
229
|
+
* prints nothing and exits 0 for every invocation. Fix: realpath BOTH sides
|
|
230
|
+
* before comparing (case-insensitively on Windows, where drive-letter casing
|
|
231
|
+
* can differ between how a shim built the path and how realpath reports it).
|
|
232
|
+
*/
|
|
233
|
+
function isSameFile(a, b) {
|
|
234
|
+
const normalize = (p) => {
|
|
235
|
+
let real;
|
|
236
|
+
try {
|
|
237
|
+
real = realpathSync.native(p);
|
|
238
|
+
} catch {
|
|
239
|
+
real = path.resolve(p);
|
|
240
|
+
}
|
|
241
|
+
return process.platform === "win32" ? real.toLowerCase() : real;
|
|
242
|
+
};
|
|
243
|
+
return normalize(a) === normalize(b);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const isMain = (() => {
|
|
247
|
+
if (!process.argv[1]) return false;
|
|
248
|
+
try {
|
|
249
|
+
if (isSameFile(process.argv[1], fileURLToPath(import.meta.url))) return true;
|
|
250
|
+
} catch {
|
|
251
|
+
// fall through to the basename fallback below
|
|
252
|
+
}
|
|
253
|
+
// Belt-and-suspenders fallback for install shapes realpath can't reconcile:
|
|
254
|
+
// recognize this file by its own known bin name rather than failing closed
|
|
255
|
+
// into silent no-output.
|
|
256
|
+
const base = path.basename(process.argv[1]).toLowerCase();
|
|
257
|
+
return base === "spexcode.mjs" || base === "spexcode";
|
|
258
|
+
})();
|
|
259
|
+
|
|
260
|
+
if (isMain) {
|
|
261
|
+
main();
|
|
262
|
+
}
|