heyspex 0.2.5 → 0.2.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/bin/hs.mjs +349 -38
- package/bin/spexcode.mjs +31 -0
- package/dev-factory/lib/dispatch-guard.mjs +75 -0
- package/dev-factory/lib/local-firecrawl.mjs +294 -0
- package/dev-factory/lib/runner-path.mjs +51 -0
- package/dev-factory/lib/seat-context.mjs +20 -4
- package/dev-factory/model-catalog.json +222 -222
- package/dev-factory/spexcrawl/firecrawl-cli.mjs +755 -0
- package/dev-factory/spexcrawl/spexcrawl-cli.mjs +734 -0
- package/dist/cli/hs-connect.mjs +40 -3
- package/dist/cli/hs-defer.mjs +3817 -3741
- package/dist/cli/hs-events.mjs +1296 -0
- package/dist/cli/hs-export-events.mjs +1252 -0
- package/dist/cli/hs-handoff.mjs +2576 -0
- package/dist/cli/hs-index-rebuild.mjs +228 -37
- package/dist/cli/hs-index-reconcile.mjs +231 -40
- package/dist/cli/hs-init.mjs +22 -3
- package/dist/cli/hs-install.mjs +22 -3
- package/dist/cli/hs-join.mjs +40 -3
- package/dist/cli/hs-next-launch.mjs +2777 -0
- package/dist/cli/hs-pin.mjs +1336 -0
- package/dist/cli/hs-prereq.mjs +1380 -0
- package/dist/cli/hs-revalidate.mjs +3257 -0
- package/dist/cli/hs-spawn.mjs +12403 -0
- package/dist/cli/hs-test.mjs +1046 -0
- package/dist/cli/hs-trash.mjs +362 -0
- package/dist/cli/hs-trust.mjs +197 -0
- package/dist/cli/hs-vault-pull.mjs +233 -42
- package/dist/cli/hs-vault-push.mjs +231 -40
- package/dist/cli/hs-vault.mjs +21 -7
- package/dist/cli/hs-workspace.mjs +19 -1
- package/dist/cli-templates/customer-vault/specs/entity-model.md +1728 -1728
- package/dist/cli-templates/customer-vault/specs/protocols/canonical-frontmatter-templates.md +464 -464
- package/dist/cli-templates/customer-vault/specs/protocols/destructive-action-checklist.md +122 -122
- package/dist/cli-templates/customer-vault/specs/protocols/memory-protocol.md +46 -46
- package/dist/cli-templates/customer-vault/specs/protocols/spec-hierarchy.md +530 -530
- package/dist/cli-templates/customer-vault/specs/protocols/spec-summary-authoring.md +83 -83
- package/dist/cli-templates/harness-commands/claude/hs-backfill.md +28 -28
- package/dist/cli-templates/harness-commands/claude/hs-capture.md +62 -62
- package/dist/cli-templates/harness-commands/claude/hs-defer.md +29 -29
- package/dist/cli-templates/harness-commands/claude/hs-ground.md +30 -30
- package/dist/cli-templates/harness-commands/claude/hs-next.md +29 -29
- package/dist/cli-templates/harness-commands/claude/hs-promote.md +70 -70
- package/dist/cli-templates/harness-commands/claude/hs-push.md +35 -35
- package/dist/cli-templates/harness-commands/claude/hs-qc.md +27 -27
- package/dist/cli-templates/harness-commands/claude/hs-res.md +83 -63
- package/dist/cli-templates/harness-commands/claude/hs-resolve.md +23 -23
- package/dist/cli-templates/harness-commands/claude/hs-view.md +28 -28
- package/dist/cli-templates/harness-commands/claude/kill-ports.md +32 -32
- package/dist/cli-templates/harness-commands/claude/prototype.md +91 -91
- package/dist/cli-templates/harness-commands/cursor/hs-backfill.md +22 -22
- package/dist/cli-templates/harness-commands/cursor/hs-capture.md +56 -56
- package/dist/cli-templates/harness-commands/cursor/hs-defer.md +23 -23
- package/dist/cli-templates/harness-commands/cursor/hs-ground.md +24 -24
- package/dist/cli-templates/harness-commands/cursor/hs-next.md +23 -23
- package/dist/cli-templates/harness-commands/cursor/hs-promote.md +64 -64
- package/dist/cli-templates/harness-commands/cursor/hs-push.md +29 -29
- package/dist/cli-templates/harness-commands/cursor/hs-qc.md +21 -21
- package/dist/cli-templates/harness-commands/cursor/hs-res.md +78 -58
- package/dist/cli-templates/harness-commands/cursor/hs-resolve.md +17 -17
- package/dist/cli-templates/harness-commands/cursor/hs-view.md +23 -23
- package/dist/cli-templates/harness-commands/cursor/kill-ports.md +26 -26
- package/dist/cli-templates/harness-commands/cursor/prototype.md +86 -86
- package/docs/copilot-plugin-install.md +9 -2
- package/docs/mcp-client-config.md +6 -0
- package/package.json +266 -257
- package/plugins/heyspex/.claude-plugin/plugin.json +1 -1
- package/plugins/heyspex/.cursor-plugin/plugin.json +1 -1
- package/plugins/heyspex/README.md +6 -0
- package/plugins/heyspex/bin/launch-mcp.mjs +17 -0
- package/plugins/heyspex/hooks/hooks.json +418 -418
- package/scripts/claude-hooks/_intent-classifier-semantic.mjs +2 -2
- package/scripts/claude-hooks/wip-checkpoint.mjs +17 -2
- package/scripts/claude-statusline/statusline-command.mjs +287 -27
- package/scripts/hs-companion.mjs +41 -3
- package/scripts/install-claude-code-hooks.mjs +104 -3
- package/scripts/install-copilot-hooks.mjs +21 -1
- package/scripts/install-cursor-hooks.mjs +4 -3
- package/scripts/lib/capture-execution-guard.mjs +221 -0
- package/scripts/lib/companion-install.mjs +768 -0
- package/scripts/lib/customer-kit.mjs +53 -3
- package/scripts/lib/hs-companion-settings.mjs +26 -0
- package/scripts/lib/hs-source-push.mjs +386 -386
- package/scripts/record-grounding.mjs +38 -0
- package/spexharness/doctrine/baseline/SPEXCODE-DOCTRINE.baseline.json +29 -29
- package/spexharness/src/adapters/copilot-models.mjs +179 -0
- package/spexharness/src/adapters/harness-detect.mjs +148 -0
- package/spexharness/src/gates/directive-obligation-gate.decide.mjs +132 -132
- package/spexharness/src/gates/vault-write-tier-guard.decide.mjs +153 -125
- package/spexharness/src/seat-context.mjs +10 -5
- package/spexharness/src/session-bootstrap.mjs +6 -3
- package/vendor/heyspex-mcp/cli.mjs +6 -1
- package/vendor/heyspex-mcp/config-seed.yaml +15 -15
- package/vendor/heyspex-mcp/dev-factory-overlay.mjs +142 -0
- package/vendor/heyspex-mcp/http-transport.mjs +1 -1
- package/vendor/heyspex-mcp/memory-tools.mjs +523 -523
- package/vendor/heyspex-mcp/on-deck-scheduler.mjs +1708 -1708
- package/vendor/heyspex-mcp/procedure-handlers.mjs +327 -327
- package/vendor/heyspex-mcp/procedure-tools.mjs +1209 -1181
- package/vendor/heyspex-mcp/research-tools.mjs +22 -2
- package/vendor/heyspex-mcp/seat-assign-cli.mjs +242 -0
- package/vendor/heyspex-mcp/seat-assignment-rules.mjs +96 -0
- package/vendor/heyspex-mcp/seat-assignments-client.mjs +265 -0
- package/vendor/heyspex-mcp/seat-defaults-cli.mjs +45 -0
- package/vendor/heyspex-mcp/seat-defaults.mjs +232 -0
- package/vendor/heyspex-mcp/seat-identity.mjs +171 -0
- package/vendor/heyspex-mcp/test-helpers.mjs +37 -23
- package/vendor/heyspex-mcp/vault-io.mjs +720 -720
- package/bin/postinstall.mjs +0 -88
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"name": "heyspex",
|
|
10
10
|
"source": "./plugins/heyspex",
|
|
11
11
|
"displayName": "HeySpex",
|
|
12
|
-
"description": "
|
|
12
|
+
"description": "SpexCode plugin (for Claude Code), also installable in GitHub Copilot CLI: HeySpex vault integration, MCP server (spec vault tools + hs-* prompts) and lifecycle hooks. Prototype — see plugins/heyspex/README.md before enabling."
|
|
13
13
|
}
|
|
14
14
|
]
|
|
15
15
|
}
|
package/bin/hs.mjs
CHANGED
|
@@ -12,6 +12,8 @@
|
|
|
12
12
|
* node_modules) executes the .ts entrypoints.
|
|
13
13
|
*/
|
|
14
14
|
import { BINDING_VERBS, runVaultBindingCommand } from "./hs-vault-binding.mjs";
|
|
15
|
+
import { findProjectRoot } from "../scripts/lib/customer-kit.mjs";
|
|
16
|
+
import { companionDoctorRow } from "../scripts/lib/companion-install.mjs";
|
|
15
17
|
import { spawn } from "node:child_process";
|
|
16
18
|
import { existsSync, readFileSync, realpathSync } from "node:fs";
|
|
17
19
|
import path from "node:path";
|
|
@@ -36,6 +38,77 @@ import {
|
|
|
36
38
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
37
39
|
const PKG_ROOT = path.resolve(__dirname, "..");
|
|
38
40
|
|
|
41
|
+
/**
|
|
42
|
+
* True when this `hs` runs from an installed `heyspex` npm package rather than
|
|
43
|
+
* a HeySpex source checkout. Same signal `hs doctor` already dispatches on:
|
|
44
|
+
* scripts/heyspex-doctor.mjs only exists in a source clone (never in `files`).
|
|
45
|
+
*/
|
|
46
|
+
const IS_INSTALLED_PACKAGE = !existsSync(path.join(PKG_ROOT, "scripts", "heyspex-doctor.mjs"));
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Help lines for verbs that only work inside a HeySpex source checkout (dev
|
|
50
|
+
* server, Dev-Factory, seat registry, Companion build, the SpexCrawl dev path).
|
|
51
|
+
* `hs --help` from an installed package drops them and says so once.
|
|
52
|
+
*/
|
|
53
|
+
const DEV_ONLY_HELP_PREFIXES = [
|
|
54
|
+
" hs server ",
|
|
55
|
+
" hs factory ",
|
|
56
|
+
" hs seat ",
|
|
57
|
+
// Platform-operator verbs whose implementations are not in the npm package:
|
|
58
|
+
// test-customer minting (admin), the Neon fleet queue, SpexCode Doctrine
|
|
59
|
+
// synthesis over this checkout's spexharness/ tree.
|
|
60
|
+
" hs admin ",
|
|
61
|
+
" hs queue ",
|
|
62
|
+
" hs doctrine ",
|
|
63
|
+
// Verbs listed in the source-checkout help whose implementation the npm
|
|
64
|
+
// package does not carry (see NOT_IN_PACKAGE_VERBS below for each reason).
|
|
65
|
+
// Invoking one from an installed package prints a clear "not available in
|
|
66
|
+
// the npm package" line instead of "install is incomplete".
|
|
67
|
+
" hs agent ",
|
|
68
|
+
" hs chat ",
|
|
69
|
+
" hs code-watcher ",
|
|
70
|
+
" hs harness ",
|
|
71
|
+
" hs hooks ",
|
|
72
|
+
" hs import ",
|
|
73
|
+
" hs milestone ",
|
|
74
|
+
" hs model ",
|
|
75
|
+
" hs registry ",
|
|
76
|
+
" hs skills ",
|
|
77
|
+
" hs voice ",
|
|
78
|
+
" hs check judge ",
|
|
79
|
+
" hs bench ",
|
|
80
|
+
" node dev-factory/",
|
|
81
|
+
" search/scrape/crawl CLI",
|
|
82
|
+
" not an `hs` verb",
|
|
83
|
+
" Companion)",
|
|
84
|
+
];
|
|
85
|
+
|
|
86
|
+
function helpForThisInstall(lines) {
|
|
87
|
+
if (!IS_INSTALLED_PACKAGE) return lines;
|
|
88
|
+
const out = [];
|
|
89
|
+
for (const line of lines) {
|
|
90
|
+
if (line.startsWith(" hs seat set|reset|assignments")) {
|
|
91
|
+
out.push(" hs seat list|set|reset [--harness <h>] Your own model per SpexFleet seat in this vault (Claude Code / Copilot)");
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
if (DEV_ONLY_HELP_PREFIXES.some((p) => line.startsWith(p))) continue;
|
|
95
|
+
if (line.startsWith(" hs companion start|stop|status|build")) {
|
|
96
|
+
out.push(" hs companion start|stop|status Desktop companion tray app (installed by `hs companion install`)");
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
out.push(line);
|
|
100
|
+
}
|
|
101
|
+
out.push(
|
|
102
|
+
"HeySpex developer-only verbs (hs server, hs factory, hs seat add|update|disable, hs admin, hs queue,",
|
|
103
|
+
"hs doctrine, hs companion build) need a HeySpex source checkout and are not",
|
|
104
|
+
"listed here. Neither are verbs whose implementation the npm package does not",
|
|
105
|
+
"carry (hs agent, hs chat, hs code-watcher, hs harness, hs hooks, hs import,",
|
|
106
|
+
"hs milestone, hs model, hs registry, hs skills, hs voice, hs check judge).",
|
|
107
|
+
"",
|
|
108
|
+
);
|
|
109
|
+
return out;
|
|
110
|
+
}
|
|
111
|
+
|
|
39
112
|
/**
|
|
40
113
|
* Every script path `hs` spawns directly by filesystem path (as opposed to
|
|
41
114
|
* `dispatchSpexConsoleEntry`'s own three-case tsx/bundle/missing resolution,
|
|
@@ -545,8 +618,23 @@ function extractGlobalVaultFlag(argv) {
|
|
|
545
618
|
}
|
|
546
619
|
|
|
547
620
|
function usage() {
|
|
548
|
-
process.stdout.write(
|
|
549
|
-
|
|
621
|
+
process.stdout.write(usageLines().join("\n"));
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
/**
|
|
625
|
+
* `hs <namespace> --help` for a namespace whose own handler has no help text
|
|
626
|
+
* (its first token must be a sub-verb): print this install's `hs --help`
|
|
627
|
+
* lines for that namespace and exit 0, instead of "unknown verb --help".
|
|
628
|
+
*/
|
|
629
|
+
const HELP_FROM_USAGE_NAMESPACES = new Set(["index", "mcp", "wip", "worktree", "skills"]);
|
|
630
|
+
|
|
631
|
+
/** @param {string} namespace */
|
|
632
|
+
export function namespaceHelpLines(namespace, lines = usageLines()) {
|
|
633
|
+
return lines.filter((l) => l.startsWith(` hs ${namespace} `));
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
function usageLines() {
|
|
637
|
+
return helpForThisInstall([
|
|
550
638
|
"hs — HeySpex vault CLI",
|
|
551
639
|
"",
|
|
552
640
|
"Usage:",
|
|
@@ -573,7 +661,7 @@ function usage() {
|
|
|
573
661
|
" hs gateway validate [slug] [--json] Validate user-authored gateway skills (.heyspex/skills/user/)",
|
|
574
662
|
" hs mode on|off|status|toggle Master switch: HeySpex layer on or off (restart Claude Code)",
|
|
575
663
|
" hs dev [mode:local|mode:cloud|mode:prod] Dev/prod mode + local/cloud reliance (status when bare)",
|
|
576
|
-
" hs setup claude|cursor|copilot [--check|--uninstall] Install agent lifecycle hooks (copilot also: --print
|
|
664
|
+
" hs setup claude|cursor|copilot [--check|--uninstall] Install agent lifecycle hooks (claude, copilot also: --print; copilot: --hooks)",
|
|
577
665
|
" hs skills sync|check Propagate skills to Cursor/Codex/Claude (cwd-safe)",
|
|
578
666
|
" hs skills add <module-path> --name <n> --trigger </slash> [--export <n>] [--description <t>] Register a skill (extension seam)",
|
|
579
667
|
" hs skills remove <name> Remove a customer-registered skill",
|
|
@@ -595,13 +683,12 @@ function usage() {
|
|
|
595
683
|
" hs worktree list|alloc|release|reap [--json] [--dry-run] Session/build worktree registry (HS-03DO)",
|
|
596
684
|
" hs view <surface> [spec-id] [--width N --height N --browser edge|chrome] Open one surface in a chromeless app-mode window",
|
|
597
685
|
" hs server stop [--json] Stop the spex-console dev server (any terminal)",
|
|
598
|
-
" hs source status|preflight|commit|push SpexSource local git operations",
|
|
599
|
-
" hs source identity [show|set|clear] Git author identity (set/clear are human-only; see hs source --help)",
|
|
600
686
|
" hs defer <id> [--until <when>] [--json] Time-shift a spec (defer_until)",
|
|
601
687
|
" hs undefer <id> [--json] Clear a spec's defer_until",
|
|
602
688
|
" hs spawn [<id>] [--dry-run] [--json] Launch a fresh /hs-next agent in a new terminal",
|
|
603
689
|
' hs capture "<text>"|--clipboard|--file <p> [--add] Capture → console drafts (--add chains a group)',
|
|
604
690
|
" hs companion start|stop|status|build Desktop companion tray app",
|
|
691
|
+
" hs companion install [--quiet] [--dry-run] Download, verify and run the signed SpexCompanion setup (Windows x64)",
|
|
605
692
|
" hs companion settings [list|get <k>|set <k> <v>] [--json] Every Companion setting (console-backed)",
|
|
606
693
|
" hs companion mute|unmute|volume|orb|wake|activation|hotkey [<v>] Setting shortcuts (omit <v> to read)",
|
|
607
694
|
" hs registry list [--json] List launch registry entries",
|
|
@@ -619,6 +706,7 @@ function usage() {
|
|
|
619
706
|
" hs next assist|auto <milestone...> --launch Arm a dispatcher run on one or more milestones",
|
|
620
707
|
" hs model [<seat>] [--launch|--fleet] Every seat and the model set for it",
|
|
621
708
|
" hs seat list|add|update|disable [--json] Read/add/update/disable a seat in the registry (dev-factory/seats.json)",
|
|
709
|
+
" hs seat set|reset|assignments [--harness] Your own model per SpexFleet seat, this vault (customers: hs seat list|set|reset)",
|
|
622
710
|
" hs voice add|build|show|set|test|refresh|export Build and manage a coded voice profile",
|
|
623
711
|
" hs trash [list|restore|empty] The bin spec deletes go into",
|
|
624
712
|
" hs auto [filename] [--json] Resolve /hs-auto dispatch plan",
|
|
@@ -648,6 +736,9 @@ function usage() {
|
|
|
648
736
|
" search/scrape/crawl CLI (standalone script,",
|
|
649
737
|
" not an `hs` verb; also reachable from the",
|
|
650
738
|
" Companion)",
|
|
739
|
+
" node dev-factory/spexcrawl/firecrawl-cli.mjs search|scrape Firecrawl — finds the",
|
|
740
|
+
" self-hosted Firecrawl in this machine's",
|
|
741
|
+
" Docker (no env var, no API key)",
|
|
651
742
|
"",
|
|
652
743
|
"Control plane (Neon — requires a reachable console):",
|
|
653
744
|
" hs pin get|set <id>|clear Read/set/clear the on-deck pin",
|
|
@@ -674,8 +765,7 @@ function usage() {
|
|
|
674
765
|
"`hs install --register-vault` without --dry-run in that case refuses instead of",
|
|
675
766
|
"registering, asking for --target. --attach-vault (satellite mode) is exempt.",
|
|
676
767
|
"",
|
|
677
|
-
]
|
|
678
|
-
);
|
|
768
|
+
]);
|
|
679
769
|
}
|
|
680
770
|
|
|
681
771
|
/**
|
|
@@ -689,8 +779,11 @@ function usage() {
|
|
|
689
779
|
* 2. Published install of a BUNDLED verb — currently `hs-init.ts`,
|
|
690
780
|
* `hs-install.ts`, `hs-claim.ts`, `hs-check.ts`, `hs-env.ts`,
|
|
691
781
|
* `hs-connect.ts`, `hs-join.ts`, `hs-vault.ts`, `hs-workspace.ts`,
|
|
692
|
-
* `hs-defer.ts`, `hs-vault-pull.ts`, `hs-
|
|
693
|
-
* `hs-index-
|
|
782
|
+
* `hs-defer.ts`, `hs-vault-pull.ts`, `hs-vault-push.ts`, `hs-dev.ts`,
|
|
783
|
+
* `hs-index-rebuild.ts`, `hs-index-reconcile.ts`, `hs-handoff.ts`,
|
|
784
|
+
* `hs-pin.ts`, `hs-events.ts`, `hs-export-events.ts`, `hs-prereq.ts`,
|
|
785
|
+
* `hs-trash.ts`, `hs-trust.ts`, `hs-next-launch.ts`, `hs-revalidate.ts`,
|
|
786
|
+
* `hs-test.ts`, `hs-spawn.ts` — see `scripts/build-cli-bundle.mjs`'s
|
|
694
787
|
* `ENTRIES` array and doc comment for the authoritative list and why
|
|
695
788
|
* each qualifies: neither the source nor tsx exists, but
|
|
696
789
|
* `dist/cli/<name>.mjs` does — run the plain-JS bundle directly with
|
|
@@ -778,6 +871,10 @@ function dispatchSpexConsoleEntry(entryFile, args, vaultRoot, extraEnv = {}) {
|
|
|
778
871
|
* version.
|
|
779
872
|
*/
|
|
780
873
|
function runStandaloneDoctor(args, vaultRoot) {
|
|
874
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
875
|
+
process.stdout.write(`Usage:\n${namespaceHelpLines("doctor").join("\n")}\n`);
|
|
876
|
+
process.exit(0);
|
|
877
|
+
}
|
|
781
878
|
const jsonOut = args.includes("--json");
|
|
782
879
|
let version = "0.0.0";
|
|
783
880
|
try {
|
|
@@ -827,6 +924,11 @@ function runStandaloneDoctor(args, vaultRoot) {
|
|
|
827
924
|
if (templateRow.status === "fail") report.ok = false;
|
|
828
925
|
report.checks.push(templateRow);
|
|
829
926
|
|
|
927
|
+
// `companion` (HS-056P): is SpexCompanion installed? Optional, so it is never
|
|
928
|
+
// a failure -- missing on Windows is a warn that offers
|
|
929
|
+
// `npx heyspex companion install`; other platforms get a skipped note.
|
|
930
|
+
report.checks.push(companionDoctorRow());
|
|
931
|
+
|
|
830
932
|
// `connection` ([[HS-W73V-WW9R-TASK-make-both-doctor-shapes-fail-on-cloud]],
|
|
831
933
|
// invariants 8 and 10): the BUNDLED `hs connect --check --json` from the
|
|
832
934
|
// invoked cwd, folded by the SAME function the full doctor uses
|
|
@@ -891,6 +993,45 @@ function runStandaloneDoctor(args, vaultRoot) {
|
|
|
891
993
|
printStandaloneDoctorReport(report, jsonOut);
|
|
892
994
|
}
|
|
893
995
|
|
|
996
|
+
/** Usage for `hs setup`; printed by --help/-h (exit 0) and on a bad target (exit 2). */
|
|
997
|
+
const SETUP_USAGE =
|
|
998
|
+
"Usage:\n" +
|
|
999
|
+
" hs setup claude [--check|--uninstall|--print] Claude Code lifecycle hooks (--print: setup steps; alias claude-code)\n" +
|
|
1000
|
+
" hs setup cursor [--check|--uninstall] Cursor CLI lifecycle hooks\n" +
|
|
1001
|
+
" hs setup copilot [--check|--uninstall|--print|--hooks|--statusline [--force]] GitHub Copilot (plugin; --hooks for the cloud agent; --statusline opt-in status line)\n" +
|
|
1002
|
+
" hs setup <target> --help This help (never installs anything)\n";
|
|
1003
|
+
|
|
1004
|
+
/**
|
|
1005
|
+
* `hs setup claude --print` (alias `claude-code`): the Claude Code steps for a
|
|
1006
|
+
* repo that uses the heyspex npm package — the sibling of `hs setup copilot
|
|
1007
|
+
* --print`. Text only; writes nothing. The one-source-of-hooks note is backed
|
|
1008
|
+
* by the `yields-to-plugin` marker in the committed launcher
|
|
1009
|
+
* (scripts/lib/customer-kit.mjs LAUNCHER_YIELD_MARKER).
|
|
1010
|
+
*/
|
|
1011
|
+
function claudeCodeSetupSteps() {
|
|
1012
|
+
return [
|
|
1013
|
+
"# Using HeySpex in Claude Code",
|
|
1014
|
+
"",
|
|
1015
|
+
"1. Open this repo in Claude Code (run `claude` from the repo root).",
|
|
1016
|
+
"2. On first open Claude Code asks whether to use the project MCP server `heyspex`",
|
|
1017
|
+
" (declared in this repo's .mcp.json). Approve it. No .mcp.json yet? Run `npx hs install`.",
|
|
1018
|
+
" Not signed in on this machine? Run `npx hs login` first.",
|
|
1019
|
+
"3. Check: ask Claude to run `heyspex_preflight`, or run `npx hs doctor`.",
|
|
1020
|
+
"",
|
|
1021
|
+
"Optional: the SpexCode plugin (for Claude Code), from the copy inside node_modules:",
|
|
1022
|
+
" claude plugin marketplace add ./node_modules/heyspex",
|
|
1023
|
+
" claude plugin install heyspex@heyspex-marketplace",
|
|
1024
|
+
" (or in a session: /plugin marketplace add ./node_modules/heyspex, then",
|
|
1025
|
+
" /plugin install heyspex@heyspex-marketplace)",
|
|
1026
|
+
"",
|
|
1027
|
+
"One source of hooks: the plugin and this repo's .claude/heyspex/run-hook.mjs yield to",
|
|
1028
|
+
"each other. When the plugin is enabled on this machine the committed launcher exits",
|
|
1029
|
+
"silently and the plugin runs every hook, so each hook fires once. Set",
|
|
1030
|
+
"HEYSPEX_REPO_HOOKS=1 to keep the repo hooks as the source instead.",
|
|
1031
|
+
"",
|
|
1032
|
+
].join("\n");
|
|
1033
|
+
}
|
|
1034
|
+
|
|
894
1035
|
function printStandaloneDoctorReport(report, jsonOut) {
|
|
895
1036
|
if (jsonOut) {
|
|
896
1037
|
process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
|
|
@@ -913,14 +1054,115 @@ function printStandaloneDoctorReport(report, jsonOut) {
|
|
|
913
1054
|
if (check.human) process.stdout.write(` ${check.human}\n`);
|
|
914
1055
|
}
|
|
915
1056
|
process.stdout.write(
|
|
916
|
-
"\nNote: the
|
|
917
|
-
"
|
|
918
|
-
"
|
|
919
|
-
"applies to a published install.\n",
|
|
1057
|
+
"\nNote: this is the setup check for a published install. The full platform\n" +
|
|
1058
|
+
"doctor (git hooks, console secrets and other source-checkout checks) is for\n" +
|
|
1059
|
+
"HeySpex developers only and is not part of the npm package.\n",
|
|
920
1060
|
);
|
|
921
1061
|
process.exit(report.ok ? 0 : 1);
|
|
922
1062
|
}
|
|
923
1063
|
|
|
1064
|
+
/** The tray-host subcommands of `hs companion` (bare `hs companion` means start). */
|
|
1065
|
+
const COMPANION_HOST_COMMANDS = new Set(["start", "stop", "status", "build"]);
|
|
1066
|
+
|
|
1067
|
+
/**
|
|
1068
|
+
* The `hs companion` host subcommand to answer with "not distributed through
|
|
1069
|
+
* the npm package yet", or null. Non-null only for start|stop|status|build (or
|
|
1070
|
+
* a bare `hs companion`) when `pkgRoot` has no companion/ source -- an
|
|
1071
|
+
* installed `heyspex` package. Pure apart from the injectable `exists`.
|
|
1072
|
+
* @param {string | undefined} namespace
|
|
1073
|
+
* @param {string | undefined} command
|
|
1074
|
+
* @param {{ pkgRoot?: string, exists?: (p: string) => boolean }} [opts]
|
|
1075
|
+
* @returns {string | null}
|
|
1076
|
+
*/
|
|
1077
|
+
export function companionHostWithoutSource(namespace, command, { pkgRoot = PKG_ROOT, exists = existsSync } = {}) {
|
|
1078
|
+
if (namespace !== "companion") return null;
|
|
1079
|
+
const sub = command === undefined ? "start" : command;
|
|
1080
|
+
if (!COMPANION_HOST_COMMANDS.has(sub)) return null;
|
|
1081
|
+
return exists(path.join(pkgRoot, "companion")) ? null : sub;
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
/**
|
|
1085
|
+
* Verbs (`"<namespace>"` or `"<namespace> <sub>"`) a source checkout runs but
|
|
1086
|
+
* the npm package deliberately does not carry, each with the reason a
|
|
1087
|
+
* customer sees. `hs --help` from an installed package hides their lines
|
|
1088
|
+
* (DEV_ONLY_HELP_PREFIXES); invoking one exits 1 with
|
|
1089
|
+
* {@link notInPackageMessage} instead of dispatchSpexConsoleEntry's
|
|
1090
|
+
* "install is incomplete". A verb that IS bundled (scripts/build-cli-bundle.mjs
|
|
1091
|
+
* ENTRIES) never belongs here.
|
|
1092
|
+
*/
|
|
1093
|
+
export const NOT_IN_PACKAGE_VERBS = Object.freeze({
|
|
1094
|
+
admin: "platform-operator test-customer minting.",
|
|
1095
|
+
queue: "the SpexFleet build queue runs only on the HeySpex platform.",
|
|
1096
|
+
seat: "SpexFleet seat registry, platform build system only.",
|
|
1097
|
+
agent: "the vendored agent collections it imports from are not shipped.",
|
|
1098
|
+
chat: "the SpexCode chat front needs the SpexCode engine, which is not in the npm package.",
|
|
1099
|
+
"code-watcher": "it needs the HeySpex source tree and its TypeScript toolchain.",
|
|
1100
|
+
harness: "SpexCode gate fixtures live in the SpexCode engine, which is not in the npm package.",
|
|
1101
|
+
hooks: "the SpexCode extension seam is read by the SpexCode engine, which is not in the npm package.",
|
|
1102
|
+
import: "it uses the SpexFleet model router, which is not in the npm package.",
|
|
1103
|
+
milestone: "use the console or the heyspex_* MCP tools to read a milestone.",
|
|
1104
|
+
model: "the SpexFleet seat routing table is not in the npm package.",
|
|
1105
|
+
registry: "the launch registry is managed from the console.",
|
|
1106
|
+
skills: "skill sync and the SpexCode skill registry need a HeySpex source checkout.",
|
|
1107
|
+
voice: "voice profiles feed the SpexVoice Companion, which is not in the npm package yet.",
|
|
1108
|
+
"check judge": "the decorrelated judge seat runs on the HeySpex platform.",
|
|
1109
|
+
factory: "SpexFleet/dev-factory internals are platform-only.",
|
|
1110
|
+
doctrine: "SpexCode Doctrine synthesis internals are platform-only.",
|
|
1111
|
+
bench: "SpexCode bench internals are platform-only.",
|
|
1112
|
+
server: "the spex-console dev server is part of the HeySpex source checkout; the hosted console is app.heyspex.com.",
|
|
1113
|
+
});
|
|
1114
|
+
|
|
1115
|
+
/**
|
|
1116
|
+
* Is this `hs <verb> ... --help|-h` (a help flag after the verb, before any
|
|
1117
|
+
* `--` end-of-options marker)? Bare `hs --help` is handled before dispatch.
|
|
1118
|
+
* @param {string[]} argv the argv after the global --vault flag is removed
|
|
1119
|
+
*/
|
|
1120
|
+
export function isHelpInvocation(argv) {
|
|
1121
|
+
for (const a of argv.slice(1)) {
|
|
1122
|
+
if (a === "--") return false;
|
|
1123
|
+
if (a === "--help" || a === "-h") return true;
|
|
1124
|
+
}
|
|
1125
|
+
return false;
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
/**
|
|
1129
|
+
* The NOT_IN_PACKAGE_VERBS key this invocation hits from an installed
|
|
1130
|
+
* package, or null. Pure apart from `installed`.
|
|
1131
|
+
* @param {string | undefined} namespace
|
|
1132
|
+
* @param {string | undefined} command
|
|
1133
|
+
* @param {{ installed?: boolean }} [opts]
|
|
1134
|
+
* @returns {string | null}
|
|
1135
|
+
*/
|
|
1136
|
+
export function notInPackageVerb(namespace, command, { installed = IS_INSTALLED_PACKAGE } = {}) {
|
|
1137
|
+
if (!installed || !namespace) return null;
|
|
1138
|
+
const two = command ? `${namespace} ${command}` : null;
|
|
1139
|
+
if (two && Object.hasOwn(NOT_IN_PACKAGE_VERBS, two)) return two;
|
|
1140
|
+
return Object.hasOwn(NOT_IN_PACKAGE_VERBS, namespace) ? namespace : null;
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
/** @param {string} key a NOT_IN_PACKAGE_VERBS key */
|
|
1144
|
+
export function notInPackageMessage(key) {
|
|
1145
|
+
return (
|
|
1146
|
+
`hs ${key}: not available in the npm package — ${NOT_IN_PACKAGE_VERBS[key]}\n` +
|
|
1147
|
+
` It runs only inside a HeySpex source checkout. \`hs --help\` lists every verb this install can run.\n`
|
|
1148
|
+
);
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
/**
|
|
1152
|
+
* Does `hs seat <command>` go to the customer seat-assignment CLI
|
|
1153
|
+
* (vendor/heyspex-mcp/seat-assign-cli.mjs, epic HS-K9L7)? Installed package:
|
|
1154
|
+
* list|assignments|set|reset|defaults|report-fallback|help (and a bare `hs seat`).
|
|
1155
|
+
* Source checkout: the same minus `list`/bare, which stay the dev-factory
|
|
1156
|
+
* registry (`hs-seat.ts`).
|
|
1157
|
+
*/
|
|
1158
|
+
export function routesToSeatAssignCli(namespace, command, { installed = IS_INSTALLED_PACKAGE } = {}) {
|
|
1159
|
+
if (namespace !== "seat") return false;
|
|
1160
|
+
if (installed) {
|
|
1161
|
+
return command === undefined || ["list", "assignments", "set", "reset", "defaults", "report-fallback", "help", "--help", "-h"].includes(command);
|
|
1162
|
+
}
|
|
1163
|
+
return ["assignments", "set", "reset", "defaults", "report-fallback"].includes(command);
|
|
1164
|
+
}
|
|
1165
|
+
|
|
924
1166
|
function main(argv) {
|
|
925
1167
|
if (argv.length === 0 || argv[0] === "--help" || argv[0] === "-h") {
|
|
926
1168
|
usage();
|
|
@@ -952,12 +1194,66 @@ function main(argv) {
|
|
|
952
1194
|
const command = argv[1];
|
|
953
1195
|
const rest = argv.slice(2);
|
|
954
1196
|
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
1197
|
+
// `hs seat list|set|reset|defaults|report-fallback` — the signed-in user's OWN
|
|
1198
|
+
// seat models (HS-K9L7). Installed package: these subcommands. Source checkout:
|
|
1199
|
+
// set|reset|defaults|assignments|report-fallback (list stays the registry).
|
|
1200
|
+
if (routesToSeatAssignCli(namespace, command)) {
|
|
1201
|
+
import(pathToFileURL(path.join(PKG_ROOT, "vendor", "heyspex-mcp", "seat-assign-cli.mjs")).href)
|
|
1202
|
+
.then((m) => m.runSeatAssignCli(argv.slice(1)))
|
|
1203
|
+
.then((code) => process.exit(code));
|
|
1204
|
+
return;
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
// A source-checkout-only verb from an installed package: say so before vault
|
|
1208
|
+
// resolution can refuse with an unrelated binding error.
|
|
1209
|
+
const missingVerb = notInPackageVerb(namespace, command);
|
|
1210
|
+
if (missingVerb) {
|
|
1211
|
+
process.stderr.write(notInPackageMessage(missingVerb));
|
|
1212
|
+
process.exit(1);
|
|
1213
|
+
}
|
|
1214
|
+
if (
|
|
1215
|
+
HELP_FROM_USAGE_NAMESPACES.has(namespace) &&
|
|
1216
|
+
(command === "--help" || command === "-h")
|
|
1217
|
+
) {
|
|
1218
|
+
const lines = namespaceHelpLines(namespace);
|
|
1219
|
+
if (lines.length) {
|
|
1220
|
+
process.stdout.write(`Usage:\n${lines.join("\n")}\n`);
|
|
1221
|
+
process.exit(0);
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
// `hs companion start|stop|status|build` from an installed package (no
|
|
1226
|
+
// companion/ source in the tarball) and `hs companion install` anywhere are
|
|
1227
|
+
// machine-local and need no vault: run them BEFORE vault resolution can
|
|
1228
|
+
// refuse with "not bound to a vault", which sends a customer to fix a binding
|
|
1229
|
+
// that would not help. scripts/hs-companion.mjs drives an installed
|
|
1230
|
+
// SpexCompanion, or offers `npx heyspex companion install` when there is none
|
|
1231
|
+
// (HS-056P; this replaced the "not distributed through the npm package yet" answer).
|
|
1232
|
+
const companionSub = companionHostWithoutSource(namespace, command);
|
|
1233
|
+
if (companionSub || (namespace === "companion" && command === "install")) {
|
|
1234
|
+
const companionArgs = command ? [command, ...rest] : ["start", ...rest];
|
|
1235
|
+
const child = spawn(process.execPath, [spawnTargetPath("companion"), ...companionArgs], {
|
|
1236
|
+
cwd: INVOKED_CWD,
|
|
1237
|
+
stdio: "inherit",
|
|
1238
|
+
});
|
|
1239
|
+
child.on("exit", (code, signal) => process.exit(signal ? 1 : (code ?? 0)));
|
|
1240
|
+
child.on("error", (err) => {
|
|
1241
|
+
process.stderr.write(`hs companion: ${err.message}\n`);
|
|
1242
|
+
process.exit(1);
|
|
1243
|
+
});
|
|
1244
|
+
return;
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1247
|
+
const resolved = resolveVaultRootForMain(process.cwd(), process.env);
|
|
1248
|
+
const { usedRegistryFallback, error: vaultError } = resolved;
|
|
1249
|
+
// `hs <verb> --help` must always print help and exit 0, bound vault or not
|
|
1250
|
+
// (install QC: ~28 verbs refused with VAULT_UNBOUND from an unbound folder).
|
|
1251
|
+
// Help never reads or writes a vault, so an unresolved vault falls back to the
|
|
1252
|
+
// invocation folder and the vault refusals below (broken pin, no vault,
|
|
1253
|
+
// uncloned satellite) are skipped; each verb's own handler prints its help.
|
|
1254
|
+
const helpRequested = isHelpInvocation(argv);
|
|
1255
|
+
const vaultRoot = resolved.vaultRoot || (helpRequested ? INVOKED_CWD : resolved.vaultRoot);
|
|
1256
|
+
const brokenPin = helpRequested ? null : resolved.brokenPin;
|
|
961
1257
|
// The registry pointer is never consulted ([[HS-TYJA]]), so the only P1
|
|
962
1258
|
// refusal left is a satellite pin that cannot be honoured; onboarding /
|
|
963
1259
|
// registry verbs and `hs doctor` still run so the pin can be repaired.
|
|
@@ -1066,7 +1362,18 @@ function main(argv) {
|
|
|
1066
1362
|
|
|
1067
1363
|
// `hs setup claude|cursor` — install agent lifecycle hooks into the vault root.
|
|
1068
1364
|
if (namespace === "setup") {
|
|
1069
|
-
const agent = command?.toLowerCase();
|
|
1365
|
+
const agent = command?.toLowerCase() === "claude-code" ? "claude" : command?.toLowerCase();
|
|
1366
|
+
// --help/-h anywhere (`hs setup --help`, `hs setup copilot --help`) prints usage and
|
|
1367
|
+
// exits 0 BEFORE any installer is spawned: the installers ignore unknown flags, so a
|
|
1368
|
+
// forwarded --help used to run the real install (Copilot: into ~/.copilot).
|
|
1369
|
+
if ([command, ...rest].some((a) => a === "--help" || a === "-h")) {
|
|
1370
|
+
process.stdout.write(SETUP_USAGE);
|
|
1371
|
+
process.exit(0);
|
|
1372
|
+
}
|
|
1373
|
+
if (agent === "claude" && rest.includes("--print")) {
|
|
1374
|
+
process.stdout.write(claudeCodeSetupSteps());
|
|
1375
|
+
process.exit(0);
|
|
1376
|
+
}
|
|
1070
1377
|
const SPAWN_KEYS = {
|
|
1071
1378
|
claude: "setup:claude",
|
|
1072
1379
|
cursor: "setup:cursor",
|
|
@@ -1074,10 +1381,7 @@ function main(argv) {
|
|
|
1074
1381
|
};
|
|
1075
1382
|
if (!agent || !Object.hasOwn(SPAWN_KEYS, agent)) {
|
|
1076
1383
|
process.stderr.write(
|
|
1077
|
-
`hs setup: expected \`claude\`, \`cursor\` or \`copilot\` (got \`${command ?? ""}\`)\n` +
|
|
1078
|
-
" hs setup claude [--check|--uninstall] Claude Code lifecycle hooks\n" +
|
|
1079
|
-
" hs setup cursor [--check|--uninstall] Cursor CLI lifecycle hooks\n" +
|
|
1080
|
-
" hs setup copilot [--check|--uninstall|--print|--hooks|--statusline [--force]] GitHub Copilot (plugin; --hooks for the cloud agent; --statusline opt-in status line)\n",
|
|
1384
|
+
`hs setup: expected \`claude\`, \`cursor\` or \`copilot\` (got \`${command ?? ""}\`)\n` + SETUP_USAGE,
|
|
1081
1385
|
);
|
|
1082
1386
|
process.exit(2);
|
|
1083
1387
|
}
|
|
@@ -1095,8 +1399,10 @@ function main(argv) {
|
|
|
1095
1399
|
}
|
|
1096
1400
|
}
|
|
1097
1401
|
const script = spawnTargetPath(SPAWN_KEYS[agent]);
|
|
1098
|
-
//
|
|
1099
|
-
|
|
1402
|
+
// Every agent sets up the PROJECT you are standing in (nearest .git /
|
|
1403
|
+
// heyspex.yaml at or above cwd) -- never the (possibly sibling) vault
|
|
1404
|
+
// root, and never this package's own folder under node_modules/.
|
|
1405
|
+
const target = findProjectRoot(INVOKED_CWD);
|
|
1100
1406
|
const child = spawn(process.execPath, [script, ...setupFlags], {
|
|
1101
1407
|
cwd: target,
|
|
1102
1408
|
stdio: "inherit",
|
|
@@ -1315,6 +1621,22 @@ function main(argv) {
|
|
|
1315
1621
|
return;
|
|
1316
1622
|
}
|
|
1317
1623
|
|
|
1624
|
+
// `hs trust list` / `hs trust revoke [path]` — standalone management of the machine-local
|
|
1625
|
+
// folder-trust store `hs chat`'s first-run prompt writes to
|
|
1626
|
+
// ([[HS-25AJ-8CMW-TASK-folder-trust-revoke-through-the]], deferred from
|
|
1627
|
+
// [[HS-ZBEB-8CMW-TASK-folder-trust-for-spexcode-first-run]], which shipped only `hs chat
|
|
1628
|
+
// --revoke-trust`). Thin CLI verb — same shell shape as `chat`/`hooks`/`bench` below; the real
|
|
1629
|
+
// logic lives in `spex-console/src/lib/cli/trust-cli.ts` over `folder-trust.ts`'s pure
|
|
1630
|
+
// functions. Forwards the full argv; the entrypoint drops the leading `trust` token.
|
|
1631
|
+
// Machine-local, never acts on a vault, so it runs ABOVE the hard vault gate
|
|
1632
|
+
// (it is in POINTER_FALLBACK_NAMESPACES): since HS-TYJA (a2bbb0698) dropped the
|
|
1633
|
+
// registry-pointer fallback, `hs trust revoke` from an unbound dir hit
|
|
1634
|
+
// VAULT_UNBOUND here.
|
|
1635
|
+
if (namespace === "trust") {
|
|
1636
|
+
dispatchSpexConsoleEntry("hs-trust.ts", argv, vaultRoot || process.cwd());
|
|
1637
|
+
return;
|
|
1638
|
+
}
|
|
1639
|
+
|
|
1318
1640
|
if (!vaultRoot) {
|
|
1319
1641
|
process.stderr.write(noVaultMessage(vaultError));
|
|
1320
1642
|
process.exit(1);
|
|
@@ -1325,6 +1647,7 @@ function main(argv) {
|
|
|
1325
1647
|
// vault's files to disk must never scaffold specs/ into a code repo.
|
|
1326
1648
|
if (
|
|
1327
1649
|
["pull", "push", "index"].includes(namespace) &&
|
|
1650
|
+
!helpRequested &&
|
|
1328
1651
|
readMarkerSatellite(vaultRoot) &&
|
|
1329
1652
|
!existsSync(path.join(vaultRoot, "specs"))
|
|
1330
1653
|
) {
|
|
@@ -1936,18 +2259,6 @@ function main(argv) {
|
|
|
1936
2259
|
return;
|
|
1937
2260
|
}
|
|
1938
2261
|
|
|
1939
|
-
// `hs trust list` / `hs trust revoke [path]` — standalone management of the machine-local
|
|
1940
|
-
// folder-trust store `hs chat`'s first-run prompt writes to
|
|
1941
|
-
// ([[HS-25AJ-8CMW-TASK-folder-trust-revoke-through-the]], deferred from
|
|
1942
|
-
// [[HS-ZBEB-8CMW-TASK-folder-trust-for-spexcode-first-run]], which shipped only `hs chat
|
|
1943
|
-
// --revoke-trust`). Thin CLI verb — same shell shape as `chat`/`hooks`/`bench` above; the real
|
|
1944
|
-
// logic lives in `spex-console/src/lib/cli/trust-cli.ts` over `folder-trust.ts`'s pure
|
|
1945
|
-
// functions. Forwards the full argv; the entrypoint drops the leading `trust` token.
|
|
1946
|
-
if (namespace === "trust") {
|
|
1947
|
-
dispatchSpexConsoleEntry("hs-trust.ts", argv, vaultRoot || process.cwd());
|
|
1948
|
-
return;
|
|
1949
|
-
}
|
|
1950
|
-
|
|
1951
2262
|
// `hs doctrine extract|learn|show` — SpexCode Doctrine corpus reader + synthesis
|
|
1952
2263
|
// ([[HS-TWTC-8CMW-TASK-dr1-doctrine-corpus-reader-stream-every]],
|
|
1953
2264
|
// [[HS-YMPY-8CMW-TASK-dr2-doctrine-synthesis-a-routed]] of epic
|
package/bin/spexcode.mjs
CHANGED
|
@@ -33,6 +33,8 @@ import { readFileSync, realpathSync } from "node:fs";
|
|
|
33
33
|
import path from "node:path";
|
|
34
34
|
import process from "node:process";
|
|
35
35
|
import { fileURLToPath } from "node:url";
|
|
36
|
+
import { PRODUCT_SEATS, canonicalSeatId, defaultModelForSeat } from "../vendor/heyspex-mcp/seat-defaults.mjs";
|
|
37
|
+
import { detectHarness } from "../spexharness/src/adapters/harness-detect.mjs";
|
|
36
38
|
|
|
37
39
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
38
40
|
|
|
@@ -200,6 +202,29 @@ export function resolveSeatGuard(argv, seatsRegistry) {
|
|
|
200
202
|
};
|
|
201
203
|
}
|
|
202
204
|
|
|
205
|
+
/**
|
|
206
|
+
* HS-MDZB (epic HS-C65J): a customer install ships no dev-factory/seats.json (that file is the
|
|
207
|
+
* owner's own fleet). When it is ABSENT, a product seat id is allowed and runs on the harness
|
|
208
|
+
* default (Claude tiers under Claude Code, `auto` under Copilot); an unknown id is still refused.
|
|
209
|
+
* Pure: env in, a guard verdict plus a one-line notice out.
|
|
210
|
+
* @returns {{allowed: boolean, message?: string, notice?: string}}
|
|
211
|
+
*/
|
|
212
|
+
export function resolveSeatGuardWithoutRegistry(seatId, env = {}) {
|
|
213
|
+
const id = canonicalSeatId(seatId);
|
|
214
|
+
const known = PRODUCT_SEATS.some((s) => s.id === id);
|
|
215
|
+
if (!known) {
|
|
216
|
+
return {
|
|
217
|
+
allowed: false,
|
|
218
|
+
message: `spexcode: refusing --seat "${seatId}" — not a HeySpex seat (known: ${PRODUCT_SEATS.map((s) => s.id).join(", ")}).`,
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
const d = defaultModelForSeat(id, detectHarness(env).harness);
|
|
222
|
+
const notice = d.model
|
|
223
|
+
? `spexcode: seat "${id}" uses the ${d.harness} default model "${d.model}".`
|
|
224
|
+
: `spexcode: seat "${id}" has no default model (no harness detected; set HEYSPEX_HARNESS=claude-code or copilot).`;
|
|
225
|
+
return { allowed: true, notice };
|
|
226
|
+
}
|
|
227
|
+
|
|
203
228
|
/** Load + JSON.parse `dev-factory/seats.json`. Isolated so tests can stub it. */
|
|
204
229
|
export function loadSeatsRegistry(seatsJsonPath = SEATS_JSON_PATH) {
|
|
205
230
|
return JSON.parse(readFileSync(seatsJsonPath, "utf8"));
|
|
@@ -273,6 +298,12 @@ export async function runSpexcode(argv, deps = {}) {
|
|
|
273
298
|
try {
|
|
274
299
|
registry = loadRegistry();
|
|
275
300
|
} catch (err) {
|
|
301
|
+
if (err && err.code === "ENOENT") {
|
|
302
|
+
const guard = resolveSeatGuardWithoutRegistry(seatId, deps.env ?? process.env);
|
|
303
|
+
stderr.write((guard.allowed ? guard.notice : guard.message) + "\n");
|
|
304
|
+
if (!guard.allowed) return 2;
|
|
305
|
+
return runForward(argv, { hsPath, spawnFn });
|
|
306
|
+
}
|
|
276
307
|
stderr.write(
|
|
277
308
|
`spexcode: could not read dev-factory/seats.json to check --seat "${seatId}": ` +
|
|
278
309
|
`${err && err.message ? err.message : err}\n`,
|