heyspex 0.1.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 +157 -0
- package/bin/hs.mjs +1028 -0
- package/bin/postinstall.mjs +88 -0
- package/dev-factory/lib/fleet-readiness.mjs +274 -0
- package/dev-factory/lib/fleet-state.mjs +250 -0
- package/dev-factory/lib/sufficiency-score.mjs +441 -0
- package/dist/cli/hs-check.mjs +747 -0
- package/dist/cli/hs-claim.mjs +541 -0
- package/dist/cli/hs-connect.mjs +3436 -0
- package/dist/cli/hs-defer.mjs +8455 -0
- package/dist/cli/hs-env.mjs +36845 -0
- package/dist/cli/hs-init.mjs +1145 -0
- package/dist/cli/hs-install.mjs +1390 -0
- package/dist/cli/hs-join.mjs +38553 -0
- package/dist/cli/hs-vault-pull.mjs +7782 -0
- package/dist/cli/hs-vault.mjs +4166 -0
- package/dist/cli/hs-workspace.mjs +2898 -0
- package/dist/cli-templates/customer-vault/specs/entity-model.md +1735 -0
- package/dist/cli-templates/customer-vault/specs/protocols/canonical-frontmatter-templates.md +531 -0
- package/dist/cli-templates/customer-vault/specs/protocols/memory-protocol.md +48 -0
- package/dist/cli-templates/customer-vault/specs/protocols/spec-hierarchy.md +548 -0
- package/docs/mcp-client-config.md +200 -0
- package/package.json +124 -0
- package/scripts/check-mcp-freshness.mjs +379 -0
- package/scripts/dev-server-status.mjs +722 -0
- package/scripts/gateway-skills-lib.mjs +421 -0
- package/scripts/lib/console-url-resolver.mjs +152 -0
- package/scripts/lib/dev-mode.mjs +369 -0
- package/scripts/lib/hs-link.mjs +154 -0
- package/scripts/lib/hs-source-commit.mjs +207 -0
- package/scripts/lib/hs-source-log.mjs +366 -0
- package/scripts/lib/hs-source-preflight.mjs +402 -0
- package/scripts/lib/hs-source-push.mjs +307 -0
- package/scripts/lib/hs-source-secret-scan.mjs +263 -0
- package/scripts/lib/standalone-doctor.mjs +89 -0
- package/scripts/vault-id-lib.mjs +173 -0
- package/vendor/heyspex-mcp/alignment-gate.mjs +1347 -0
- package/vendor/heyspex-mcp/blocker-resolver.mjs +891 -0
- package/vendor/heyspex-mcp/breakdown-ladder.mjs +309 -0
- package/vendor/heyspex-mcp/breakdown-source.mjs +236 -0
- package/vendor/heyspex-mcp/breakdown-writer.mjs +524 -0
- package/vendor/heyspex-mcp/brief-observability.mjs +67 -0
- package/vendor/heyspex-mcp/canvas-inbox.mjs +130 -0
- package/vendor/heyspex-mcp/capture-active-rehydrate.mjs +76 -0
- package/vendor/heyspex-mcp/capture-buffer-patches.mjs +147 -0
- package/vendor/heyspex-mcp/capture-channel.mjs +577 -0
- package/vendor/heyspex-mcp/capture-console-client.mjs +290 -0
- package/vendor/heyspex-mcp/capture-focus.mjs +1366 -0
- package/vendor/heyspex-mcp/capture-kind-suggest.mjs +121 -0
- package/vendor/heyspex-mcp/capture-outbox.mjs +239 -0
- package/vendor/heyspex-mcp/capture-stage-gate.mjs +160 -0
- package/vendor/heyspex-mcp/cli.mjs +15754 -0
- package/vendor/heyspex-mcp/config-settings.mjs +419 -0
- package/vendor/heyspex-mcp/console-fetch.mjs +342 -0
- package/vendor/heyspex-mcp/defer-spec.mjs +527 -0
- package/vendor/heyspex-mcp/deferred-reclaim.mjs +284 -0
- package/vendor/heyspex-mcp/design-tools.mjs +449 -0
- package/vendor/heyspex-mcp/dev-factory-bridge.mjs +66 -0
- package/vendor/heyspex-mcp/diagnose-skeleton.mjs +114 -0
- package/vendor/heyspex-mcp/fleet-queue-tools.mjs +241 -0
- package/vendor/heyspex-mcp/hooks-engine.mjs +399 -0
- package/vendor/heyspex-mcp/login-handoff.mjs +438 -0
- package/vendor/heyspex-mcp/mcp-subprocess.mjs +136 -0
- package/vendor/heyspex-mcp/mvp-mode.mjs +101 -0
- package/vendor/heyspex-mcp/next-resolution.mjs +240 -0
- package/vendor/heyspex-mcp/next-review.mjs +548 -0
- package/vendor/heyspex-mcp/peer-presence.mjs +430 -0
- package/vendor/heyspex-mcp/plan-ground-conflicts.mjs +321 -0
- package/vendor/heyspex-mcp/plan-ground.mjs +2001 -0
- package/vendor/heyspex-mcp/plan-ingest.mjs +476 -0
- package/vendor/heyspex-mcp/plan-push.mjs +1826 -0
- package/vendor/heyspex-mcp/plan-review.mjs +1419 -0
- package/vendor/heyspex-mcp/preflight-gate.mjs +0 -0
- package/vendor/heyspex-mcp/procedure-tools.mjs +970 -0
- package/vendor/heyspex-mcp/promote-capture.mjs +1280 -0
- package/vendor/heyspex-mcp/promote-diagnose.mjs +187 -0
- package/vendor/heyspex-mcp/promote-research-to-diagnose.mjs +338 -0
- package/vendor/heyspex-mcp/promote-research.mjs +1553 -0
- package/vendor/heyspex-mcp/promotion-helpers.mjs +280 -0
- package/vendor/heyspex-mcp/read-path.mjs +138 -0
- package/vendor/heyspex-mcp/recipe-tools.mjs +258 -0
- package/vendor/heyspex-mcp/regen-tasks.mjs +326 -0
- package/vendor/heyspex-mcp/register-spec.mjs +417 -0
- package/vendor/heyspex-mcp/research-focus.mjs +476 -0
- package/vendor/heyspex-mcp/research-tools.mjs +109 -0
- package/vendor/heyspex-mcp/research-type-suggest.mjs +112 -0
- package/vendor/heyspex-mcp/resolution-focus.mjs +45 -0
- package/vendor/heyspex-mcp/review-focus.mjs +55 -0
- package/vendor/heyspex-mcp/review-rollup.mjs +47 -0
- package/vendor/heyspex-mcp/run-front.mjs +60 -0
- package/vendor/heyspex-mcp/scheduler.mjs +813 -0
- package/vendor/heyspex-mcp/seat-identity.mjs +281 -0
- package/vendor/heyspex-mcp/settings-tools.mjs +119 -0
- package/vendor/heyspex-mcp/spec-id-lib.mjs +790 -0
- package/vendor/heyspex-mcp/split-research.mjs +832 -0
- package/vendor/heyspex-mcp/submit-for-review.mjs +190 -0
- package/vendor/heyspex-mcp/summary-contract.mjs +162 -0
- package/vendor/heyspex-mcp/test-helpers.mjs +43 -0
- package/vendor/heyspex-mcp/validate-linkage.mjs +574 -0
- package/vendor/heyspex-mcp/vault-resolve.mjs +297 -0
- package/vendor/heyspex-mcp/vault-settings.mjs +87 -0
- package/vendor/heyspex-mcp/verify-reproduction.mjs +314 -0
- package/vendor/heyspex-mcp/why-now.mjs +64 -0
- package/vendor/heyspex-mcp/yaml-scalar.mjs +34 -0
package/bin/hs.mjs
ADDED
|
@@ -0,0 +1,1028 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* hs — HeySpex vault CLI (foundation for indexer / future commands).
|
|
4
|
+
*
|
|
5
|
+
* Per [[HS-TASK-DE5Z-hs-index-rebuild]] and [[HS-EPIC-0027-implement-hs-bd-successor-m4-cli-mcp]].
|
|
6
|
+
*
|
|
7
|
+
* Dispatcher only: parses `hs <namespace> <command>`, locates the vault root,
|
|
8
|
+
* and delegates to the matching implementation inside `spex-console/bin/`.
|
|
9
|
+
*
|
|
10
|
+
* Implementations live in spex-console/ because they import the TypeScript indexer
|
|
11
|
+
* service directly. tsx (a workspace devDep, hoisted to the repo-root
|
|
12
|
+
* node_modules) executes the .ts entrypoints.
|
|
13
|
+
*/
|
|
14
|
+
import { spawn } from "node:child_process";
|
|
15
|
+
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
16
|
+
import path from "node:path";
|
|
17
|
+
import process from "node:process";
|
|
18
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
19
|
+
|
|
20
|
+
import { runtimeFilesCheckRow } from "../scripts/lib/standalone-doctor.mjs";
|
|
21
|
+
|
|
22
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
23
|
+
const PKG_ROOT = path.resolve(__dirname, "..");
|
|
24
|
+
|
|
25
|
+
// Captured before anything in this process could ever chdir (it doesn't
|
|
26
|
+
// today, but every dispatched entry below is spawned with its own `cwd`
|
|
27
|
+
// override — see dispatchSpexConsoleEntry). This is the ONE true record of
|
|
28
|
+
// where the caller actually invoked `hs` from; forward it (HS_INVOKED_CWD)
|
|
29
|
+
// to every dispatched entry whose cwd-derived logic must resolve the
|
|
30
|
+
// caller's repo, not the platform checkout dir the entry is spawned in
|
|
31
|
+
// (dispatchSpexConsoleEntry's spawns run with cwd=spex-console/; several raw
|
|
32
|
+
// spawns below run with cwd=PKG_ROOT). Set on process.env itself (rather than
|
|
33
|
+
// threaded through each individual spawn call) so it reaches EVERY dispatch —
|
|
34
|
+
// both the ones that build `env` by spreading `...process.env` and the ones
|
|
35
|
+
// that pass no `env` override at all and so inherit process.env verbatim —
|
|
36
|
+
// without having to touch every call site. Per
|
|
37
|
+
// [[HS-NXNP-36X3-DIAG-the-hs-launcher-loses-the-caller-s]]: `hs connect` was
|
|
38
|
+
// the first verb fixed this way (its own `HS_INVOKED_CWD` env entry below is
|
|
39
|
+
// now redundant with this but kept — see the connect dispatch — as it costs
|
|
40
|
+
// nothing and documents the specific need).
|
|
41
|
+
const INVOKED_CWD = process.cwd();
|
|
42
|
+
process.env.HS_INVOKED_CWD = INVOKED_CWD;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Walk up from `start` until we find a directory containing `specs/`.
|
|
46
|
+
* That directory is the vault root.
|
|
47
|
+
*/
|
|
48
|
+
function findVaultRoot(start) {
|
|
49
|
+
let dir = path.resolve(start);
|
|
50
|
+
while (true) {
|
|
51
|
+
const candidate = path.join(dir, "specs");
|
|
52
|
+
if (existsSync(candidate) && statSync(candidate).isDirectory()) {
|
|
53
|
+
return dir;
|
|
54
|
+
}
|
|
55
|
+
const parent = path.dirname(dir);
|
|
56
|
+
if (parent === dir) return null;
|
|
57
|
+
dir = parent;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Extract the global `hs --vault <id> <subcommand>` one-shot override
|
|
63
|
+
* ([[HS-Q1HZ-TASK-hs-vault-cli-list-register-switch-active]]). `--vault`
|
|
64
|
+
* (or `--vault=<id>`) may appear anywhere in argv -- it is stripped out here
|
|
65
|
+
* so the remaining tokens parse as a normal `hs <namespace> <command>`
|
|
66
|
+
* invocation; the extracted id is forwarded via `HS_VAULT_ID_OVERRIDE` env
|
|
67
|
+
* to whatever entry gets dispatched. This never mutates the persisted active
|
|
68
|
+
* pointer -- it only feeds `resolveActiveVaultRoot`'s `options.vaultId` for
|
|
69
|
+
* that one invocation (consumed today by `hs vault active`).
|
|
70
|
+
*/
|
|
71
|
+
function extractGlobalVaultFlag(argv) {
|
|
72
|
+
const idx = argv.findIndex(
|
|
73
|
+
(a) => a === "--vault" || a.startsWith("--vault="),
|
|
74
|
+
);
|
|
75
|
+
if (idx === -1) return { vaultIdOverride: undefined, rest: argv };
|
|
76
|
+
const arg = argv[idx];
|
|
77
|
+
if (arg.startsWith("--vault=")) {
|
|
78
|
+
return {
|
|
79
|
+
vaultIdOverride: arg.slice("--vault=".length),
|
|
80
|
+
rest: [...argv.slice(0, idx), ...argv.slice(idx + 1)],
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
return {
|
|
84
|
+
vaultIdOverride: argv[idx + 1],
|
|
85
|
+
rest: [...argv.slice(0, idx), ...argv.slice(idx + 2)],
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function usage() {
|
|
90
|
+
process.stdout.write(
|
|
91
|
+
[
|
|
92
|
+
"hs — HeySpex vault CLI",
|
|
93
|
+
"",
|
|
94
|
+
"Usage:",
|
|
95
|
+
" hs login [--console-url <url>] [--label <t>] [--json] Sign in via your browser (no code to type)",
|
|
96
|
+
" hs init [--console-url <url>] [--force] Write the heyspex.yaml vault marker",
|
|
97
|
+
" hs install [--target <dir>] [--platform <dir>] Bootstrap vault + Cursor/Claude MCP",
|
|
98
|
+
" hs invite [<name>] Mint a short pairing code (other computer: hs join CODE)",
|
|
99
|
+
" hs join <code> Join with a pairing code (LAN auto-find; no IP to type)",
|
|
100
|
+
" hs join [<repo>] [--dir d] [--code c] [--at url] [--profile p] [--skip steps] [--json] [--yes] Fresh-machine onboarding (clone → toolchain → deps → doctor → agent layer → secrets → verify)",
|
|
101
|
+
" hs vault list|register|switch|active Manage registered vaults + active pointer",
|
|
102
|
+
" hs workspace list|create|switch Manage registered workspaces (company containers)",
|
|
103
|
+
" hs connect [--dry-run] [--json] | --check [--json] Detect+scaffold+register this repo (or read-only check)",
|
|
104
|
+
" hs --vault <id> <subcommand> One-shot vault override for this invocation",
|
|
105
|
+
" hs doctor [--fix] [--json] [--check-gateway] Check every install surface; --fix repairs what it can",
|
|
106
|
+
" hs gateway validate [slug] [--json] Validate user-authored gateway skills (.heyspex/skills/user/)",
|
|
107
|
+
" hs mode on|off|status|toggle Master switch: HeySpex layer on or off (restart Claude Code)",
|
|
108
|
+
" hs dev [mode:local|mode:cloud|mode:prod] Dev/prod mode + local/cloud reliance (status when bare)",
|
|
109
|
+
" hs setup claude|cursor [--check|--uninstall] Install agent lifecycle hooks",
|
|
110
|
+
" hs skills sync|check Propagate skills to Cursor/Codex/Claude (cwd-safe)",
|
|
111
|
+
" hs index rebuild [--verbose] [--dry-run] Full idempotent rebuild of the index",
|
|
112
|
+
" hs pull [--apply] [--only <id>] [--overwrite] Bring cloud-written specs down to the disk vault (reports unless --apply)",
|
|
113
|
+
" hs milestone show <id> [--json] Show a milestone (flags needs-breakdown when epicless)",
|
|
114
|
+
" hs factory status|fleet|results [--json] Dev-Factory read-only status (fleet, routing, calibrations, results)",
|
|
115
|
+
" hs factory run status [--json] Live assisted/autonomous dispatcher run (pids, queue, stop hints)",
|
|
116
|
+
" hs code-watcher scan [--json] Capture build-correctness failures (tsc) → Resolution",
|
|
117
|
+
" hs server status [--json] Ground-truth dev-server status (running? which port[s]?)",
|
|
118
|
+
" hs view <surface> [spec-id] [--width N --height N --browser edge|chrome] Open one surface in a chromeless app-mode window",
|
|
119
|
+
" hs server stop [--json] Stop the spex-console dev server (any terminal)",
|
|
120
|
+
" hs defer <id> [--until <when>] [--json] Time-shift a spec (defer_until)",
|
|
121
|
+
" hs undefer <id> [--json] Clear a spec's defer_until",
|
|
122
|
+
" hs spawn [<id>] [--dry-run] [--json] Launch a fresh /hs-next agent in a new terminal",
|
|
123
|
+
" hs capture \"<text>\"|--clipboard|--file <p> [--add] Capture → console drafts (--add chains a group)",
|
|
124
|
+
" hs companion start|stop|status|build Desktop companion tray app",
|
|
125
|
+
" hs registry list [--json] List launch registry entries",
|
|
126
|
+
" hs env init|set|list|run|export|rotate Encrypted env vault (ADR-0020)",
|
|
127
|
+
" hs env profiles | key export|import Named profiles + second-machine key transfer",
|
|
128
|
+
" hs env grant add|list|remove | key generate Contractor/CI decrypt grants (ADR-0021)",
|
|
129
|
+
" hs settings list|get|set Read/change workflow settings (.heyspex/config.yaml)",
|
|
130
|
+
" hs classifier stats [--json] [--recent N] Intent Classifier decision stats (read-only)",
|
|
131
|
+
" hs next [spec_id] [--summary] [--json] Resolve on-deck brief (MCP fallback)",
|
|
132
|
+
" hs auto [filename] [--json] Resolve /hs-auto dispatch plan",
|
|
133
|
+
" hs mcp call <tool> [--arg key=value ...] [--args '<json>'] [--args-file <path>] [--json] One-shot MCP tool",
|
|
134
|
+
" hs mcp pair <code> [--console-url <url>] Redeem a console pairing code (cross-machine fallback for hs login)",
|
|
135
|
+
" hs test run <id|manifest> [--format ...] Run open-format API tests (Hurl/.http) per AC",
|
|
136
|
+
" hs test gate [dir] [--report <j>] [--threshold n] Mutation-testing gate — reject suites that kill no mutants",
|
|
137
|
+
" hs test conform <spec> --url <u> | --report <x> OpenAPI conformance / docs↔impl drift (Schemathesis)",
|
|
138
|
+
" hs check run <id|manifest> [--json] Verify acceptance criteria vs the frozen checks manifest (per-AC pass/fail)",
|
|
139
|
+
" hs revalidate [--json] [--apply] Re-run the frozen checks of already-stamped work; report what has decayed",
|
|
140
|
+
" hs agent import <collection> <role> Import a community agent into a type:agent vault spec",
|
|
141
|
+
" hs agent list [collection] List vendored agent collections / roles",
|
|
142
|
+
"",
|
|
143
|
+
"Control plane (Neon — requires a reachable console):",
|
|
144
|
+
" hs pin get|set <id>|clear Read/set/clear the on-deck pin",
|
|
145
|
+
" hs claim acquire|release <id> | list Atomic spec claims",
|
|
146
|
+
" hs claim progress <id> --stage <verb> Report live work-stage",
|
|
147
|
+
" hs handoff record|load [--lane <k>] Resume-context coordination",
|
|
148
|
+
" hs events show <id> [--kind --since] Per-spec audit log",
|
|
149
|
+
" hs export-events [--since <date>] Idempotent audit export → specs/events/",
|
|
150
|
+
" hs prereq <blocked-id> [<action> <prereq>] Cross-milestone prerequisite resolution",
|
|
151
|
+
" hs queue submit|next|claim|complete|fail|list Fleet-queue dispatch (Neon, direct — no console hop)",
|
|
152
|
+
"",
|
|
153
|
+
" hs <verb> --help Per-verb help",
|
|
154
|
+
" hs --help Show this help",
|
|
155
|
+
" hs --version Show CLI version",
|
|
156
|
+
"",
|
|
157
|
+
"Vault root is detected by walking up from the current directory until a",
|
|
158
|
+
"specs/ folder is found. Override with HS_VAULT_ROOT.",
|
|
159
|
+
"",
|
|
160
|
+
].join("\n"),
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Run a spex-console-side TypeScript entrypoint (under `spex-console/bin/`) via
|
|
166
|
+
* tsx, passing `args` and the resolved vault root. Used by every `hs` verb whose
|
|
167
|
+
* implementation imports the spex-console TypeScript directly.
|
|
168
|
+
*
|
|
169
|
+
* Three cases, in order ([[HS-JOQX-0JNK-TASK-published-package-runs-hs-init-and-hs]]):
|
|
170
|
+
* 1. Monorepo checkout: `.ts` source AND `tsx` both present — run via tsx,
|
|
171
|
+
* exactly as before this task.
|
|
172
|
+
* 2. Published install of a BUNDLED verb (currently only `hs-init.ts` /
|
|
173
|
+
* `hs-install.ts` — see `scripts/build-cli-bundle.mjs`'s doc comment
|
|
174
|
+
* for why only these two): neither the source nor tsx exists, but
|
|
175
|
+
* `dist/cli/<name>.mjs` does — run the plain-JS bundle directly with
|
|
176
|
+
* `node`, no tsx/TypeScript involved.
|
|
177
|
+
* 3. Neither exists: the package install is incomplete (a verb this
|
|
178
|
+
* package version never bundled, or a broken install) — same exit
|
|
179
|
+
* code as before, reworded.
|
|
180
|
+
*/
|
|
181
|
+
function dispatchSpexConsoleEntry(entryFile, args, vaultRoot, extraEnv = {}) {
|
|
182
|
+
const consoleDir = path.join(PKG_ROOT, "spex-console");
|
|
183
|
+
const tsEntry = path.join(consoleDir, "bin", entryFile);
|
|
184
|
+
const tsxEntry = path.join(
|
|
185
|
+
PKG_ROOT,
|
|
186
|
+
"node_modules",
|
|
187
|
+
"tsx",
|
|
188
|
+
"dist",
|
|
189
|
+
"cli.mjs",
|
|
190
|
+
);
|
|
191
|
+
const bundleEntry = path.join(
|
|
192
|
+
PKG_ROOT,
|
|
193
|
+
"dist",
|
|
194
|
+
"cli",
|
|
195
|
+
entryFile.replace(/\.ts$/, ".mjs"),
|
|
196
|
+
);
|
|
197
|
+
|
|
198
|
+
const spawnEnv = { ...process.env, HS_VAULT_ROOT: vaultRoot, ...extraEnv };
|
|
199
|
+
let child;
|
|
200
|
+
|
|
201
|
+
if (existsSync(tsEntry) && existsSync(tsxEntry)) {
|
|
202
|
+
// Case 1 — monorepo: invoke tsx's ESM entry directly with node. This
|
|
203
|
+
// avoids npm's .cmd shim (which triggers Node 24's `shell:true`
|
|
204
|
+
// deprecation warning on Windows) and works identically across
|
|
205
|
+
// platforms. tsx is hoisted to the repo-root node_modules by the npm
|
|
206
|
+
// workspace install, not spex-console/node_modules.
|
|
207
|
+
child = spawn(process.execPath, [tsxEntry, tsEntry, ...args], {
|
|
208
|
+
cwd: consoleDir,
|
|
209
|
+
stdio: "inherit",
|
|
210
|
+
env: spawnEnv,
|
|
211
|
+
});
|
|
212
|
+
} else if (existsSync(bundleEntry)) {
|
|
213
|
+
// Case 2 — published install, verb was bundled: plain node, no tsx/TS.
|
|
214
|
+
// cwd is the CALLER's real invocation directory (not a spex-console/
|
|
215
|
+
// that doesn't exist in a published install) so a relative path the
|
|
216
|
+
// caller typed (e.g. `hs init --customer-vault vault`) resolves the
|
|
217
|
+
// same way it would for any other CLI the caller ran from that shell.
|
|
218
|
+
child = spawn(process.execPath, [bundleEntry, ...args], {
|
|
219
|
+
cwd: INVOKED_CWD,
|
|
220
|
+
stdio: "inherit",
|
|
221
|
+
env: spawnEnv,
|
|
222
|
+
});
|
|
223
|
+
} else {
|
|
224
|
+
// Case 3 — package incomplete: this verb has neither a monorepo source
|
|
225
|
+
// (with tsx) nor a bundled fallback.
|
|
226
|
+
process.stderr.write(
|
|
227
|
+
`hs: this package install is incomplete — no implementation for ` +
|
|
228
|
+
`\`${entryFile}\` (checked ${tsEntry}, ${bundleEntry}).\n` +
|
|
229
|
+
` If you're in a source checkout, run \`npm install\` first.\n` +
|
|
230
|
+
` If you installed the published \`heyspex\` package, this verb ` +
|
|
231
|
+
`is not yet available outside a source checkout.\n`,
|
|
232
|
+
);
|
|
233
|
+
process.exit(1);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
child.on("exit", (code, signal) => {
|
|
237
|
+
if (signal) {
|
|
238
|
+
process.exit(1);
|
|
239
|
+
}
|
|
240
|
+
process.exit(code ?? 0);
|
|
241
|
+
});
|
|
242
|
+
child.on("error", (err) => {
|
|
243
|
+
process.stderr.write(`hs: failed to spawn ${entryFile}: ${err.message}\n`);
|
|
244
|
+
process.exit(1);
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* `hs doctor` when running from a published-package install (no
|
|
250
|
+
* `scripts/heyspex-doctor.mjs` on disk — that script only ships inside a
|
|
251
|
+
* source clone; see the dispatch site for why). Checks the one thing that
|
|
252
|
+
* genuinely applies here and is exactly what
|
|
253
|
+
* [[HS-BFXF-WBJU-DIAG-hs-never-resolves-on-a-fresh-machine]] is about: did
|
|
254
|
+
* `npm install` actually put the sibling `heyspex` / `heyspex-mcp` bins on
|
|
255
|
+
* PATH alongside this one. `hs` itself trivially "resolved" (this process IS
|
|
256
|
+
* it), so there is nothing to check about `hs` other than reporting its
|
|
257
|
+
* version.
|
|
258
|
+
*/
|
|
259
|
+
function runStandaloneDoctor(args) {
|
|
260
|
+
const jsonOut = args.includes("--json");
|
|
261
|
+
let version = "0.0.0";
|
|
262
|
+
try {
|
|
263
|
+
version =
|
|
264
|
+
JSON.parse(readFileSync(path.join(PKG_ROOT, "package.json"), "utf-8"))
|
|
265
|
+
.version || version;
|
|
266
|
+
} catch {
|
|
267
|
+
// fall through with the placeholder
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const report = {
|
|
271
|
+
mode: "published-install",
|
|
272
|
+
version,
|
|
273
|
+
binPath: process.argv[1],
|
|
274
|
+
ok: true,
|
|
275
|
+
checks: [],
|
|
276
|
+
};
|
|
277
|
+
report.checks.push({
|
|
278
|
+
id: "hs-resolved",
|
|
279
|
+
status: "ok",
|
|
280
|
+
detail: `hs ${version} → ${process.argv[1]}`,
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
// `runtime-files` ([[HS-67ES-0JNK-TASK-fresh-install-honesty-statusline-only]]
|
|
284
|
+
// AC3): verifies the package's own runtime files (dynamic-imported/spawned
|
|
285
|
+
// by the MCP server and `hs source`, plus the two `dist/cli` bundles `hs
|
|
286
|
+
// init`/`hs install` dispatch to) actually exist next to THIS `bin/hs.mjs`
|
|
287
|
+
// — the check `hs doctor` did not have before, so a broken package (a
|
|
288
|
+
// runtime file missing from `package.json` `files`) passed every existing
|
|
289
|
+
// check and only surfaced on the customer's FIFTH command
|
|
290
|
+
// (`heyspex_preflight`'s `MODULE_NOT_FOUND`), not the first.
|
|
291
|
+
const runtimeFilesRow = runtimeFilesCheckRow(PKG_ROOT);
|
|
292
|
+
if (runtimeFilesRow.status !== "ok") report.ok = false;
|
|
293
|
+
report.checks.push(runtimeFilesRow);
|
|
294
|
+
|
|
295
|
+
const hsLinkLib = path.join(PKG_ROOT, "scripts", "lib", "hs-link.mjs");
|
|
296
|
+
if (existsSync(hsLinkLib)) {
|
|
297
|
+
// Deliberately synchronous top-level work below via a nested async IIFE
|
|
298
|
+
// — `missingSiblingBins` is a plain sync fs walk, but `import()` itself
|
|
299
|
+
// is async and this function must stay callable from a non-async call
|
|
300
|
+
// site without turning the whole dispatcher async.
|
|
301
|
+
import(pathToFileURL(hsLinkLib).href)
|
|
302
|
+
.then(({ missingSiblingBins }) => {
|
|
303
|
+
const strays = missingSiblingBins();
|
|
304
|
+
if (strays.length > 0) {
|
|
305
|
+
report.ok = false;
|
|
306
|
+
report.checks.push({
|
|
307
|
+
id: "sibling-bins-on-path",
|
|
308
|
+
status: "fail",
|
|
309
|
+
detail: `${strays.join(", ")} not on PATH`,
|
|
310
|
+
human:
|
|
311
|
+
`This \`hs\` resolved because it was invoked directly (npx, or ` +
|
|
312
|
+
`node_modules/.bin) — but ${strays.join(" / ")} will not resolve ` +
|
|
313
|
+
`from anywhere else on this machine unless you run ` +
|
|
314
|
+
"`npm install -g heyspex`.",
|
|
315
|
+
});
|
|
316
|
+
} else {
|
|
317
|
+
report.checks.push({
|
|
318
|
+
id: "sibling-bins-on-path",
|
|
319
|
+
status: "ok",
|
|
320
|
+
detail: "hs / heyspex / heyspex-mcp all resolve on PATH",
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
printStandaloneDoctorReport(report, jsonOut);
|
|
324
|
+
})
|
|
325
|
+
.catch(() => {
|
|
326
|
+
printStandaloneDoctorReport(report, jsonOut);
|
|
327
|
+
});
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
printStandaloneDoctorReport(report, jsonOut);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
function printStandaloneDoctorReport(report, jsonOut) {
|
|
334
|
+
if (jsonOut) {
|
|
335
|
+
process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
|
|
336
|
+
process.exit(report.ok ? 0 : 1);
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
process.stdout.write(
|
|
340
|
+
"HeySpex setup check — published-package install (not a source clone)\n\n",
|
|
341
|
+
);
|
|
342
|
+
for (const check of report.checks) {
|
|
343
|
+
const label = check.status === "ok" ? " ok " : " FAIL ";
|
|
344
|
+
process.stdout.write(`[ ${label} ] ${check.detail}\n`);
|
|
345
|
+
if (check.human) process.stdout.write(` ${check.human}\n`);
|
|
346
|
+
}
|
|
347
|
+
process.stdout.write(
|
|
348
|
+
"\nNote: the full `hs doctor` (git hooks, Cursor wiring, console secrets, and\n" +
|
|
349
|
+
"dozens of other source-checkout-only checks) only ships inside a clone of\n" +
|
|
350
|
+
"https://github.com/pwhermanson/workflow-protocol — this is the subset that\n" +
|
|
351
|
+
"applies to a published install.\n",
|
|
352
|
+
);
|
|
353
|
+
process.exit(report.ok ? 0 : 1);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
function main(argv) {
|
|
357
|
+
if (argv.length === 0 || argv[0] === "--help" || argv[0] === "-h") {
|
|
358
|
+
usage();
|
|
359
|
+
process.exit(0);
|
|
360
|
+
}
|
|
361
|
+
if (argv[0] === "--version" || argv[0] === "-V") {
|
|
362
|
+
// Read root package.json for version. This file is ESM (no `require`
|
|
363
|
+
// in scope) — a prior version of this block called the undefined global
|
|
364
|
+
// `require(...)`, which threw and was silently swallowed by the catch
|
|
365
|
+
// below, so `hs --version` always printed the "0.0.0" fallback instead
|
|
366
|
+
// of the real version (found while proving the HS-BFXF fresh-install
|
|
367
|
+
// repro against a packed `heyspex` tarball).
|
|
368
|
+
try {
|
|
369
|
+
const pkg = JSON.parse(
|
|
370
|
+
readFileSync(path.join(PKG_ROOT, "package.json"), "utf-8"),
|
|
371
|
+
);
|
|
372
|
+
process.stdout.write(`hs ${pkg.version || "0.0.0"}\n`);
|
|
373
|
+
} catch {
|
|
374
|
+
process.stdout.write("hs 0.0.0\n");
|
|
375
|
+
}
|
|
376
|
+
process.exit(0);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
const { vaultIdOverride, rest: argvAfterVaultFlag } =
|
|
380
|
+
extractGlobalVaultFlag(argv);
|
|
381
|
+
argv = argvAfterVaultFlag;
|
|
382
|
+
|
|
383
|
+
const namespace = argv[0];
|
|
384
|
+
const command = argv[1];
|
|
385
|
+
const rest = argv.slice(2);
|
|
386
|
+
|
|
387
|
+
const vaultRoot = process.env.HS_VAULT_ROOT || findVaultRoot(process.cwd());
|
|
388
|
+
|
|
389
|
+
// `hs init` writes the top-level heyspex.yaml vault marker
|
|
390
|
+
// ([[HS-EPIC-AEHR-audit-export-heyspex-yaml-and-heyspex-retirement]] §2). It
|
|
391
|
+
// may run on a fresh directory that has no specs/ yet, so it bypasses the
|
|
392
|
+
// hard vault gate below and falls back to the current directory.
|
|
393
|
+
if (namespace === "init") {
|
|
394
|
+
dispatchSpexConsoleEntry("hs-init.ts", argv, vaultRoot || process.cwd());
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// `hs install` bootstraps a customer/project vault + agent MCP wiring. May run
|
|
399
|
+
// on a fresh directory with no specs/ yet (like init).
|
|
400
|
+
if (namespace === "install") {
|
|
401
|
+
dispatchSpexConsoleEntry(
|
|
402
|
+
"hs-install.ts",
|
|
403
|
+
argv,
|
|
404
|
+
vaultRoot || process.cwd(),
|
|
405
|
+
{
|
|
406
|
+
HS_PKG_ROOT: PKG_ROOT,
|
|
407
|
+
},
|
|
408
|
+
);
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// `hs join` — fresh-machine onboarding orchestrator (R18): clone → toolchain
|
|
413
|
+
// → deps → doctor → agent layer → secrets → verify. May run on a machine
|
|
414
|
+
// with no vault at all (that is its whole point), so it bypasses the hard
|
|
415
|
+
// vault gate like init/install. The launch cwd is forwarded because the
|
|
416
|
+
// dispatcher runs entries with cwd=spex-console.
|
|
417
|
+
if (namespace === "join") {
|
|
418
|
+
dispatchSpexConsoleEntry("hs-join.ts", argv, vaultRoot || process.cwd(), {
|
|
419
|
+
HS_JOIN_CWD: process.cwd(),
|
|
420
|
+
});
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// `hs invite` — TeamViewer-short host side. Alias of `hs env invite`
|
|
425
|
+
// (optional name defaults to "peer"). Needs a vault (this machine owns it).
|
|
426
|
+
if (namespace === "invite") {
|
|
427
|
+
if (!vaultRoot) {
|
|
428
|
+
process.stderr.write(
|
|
429
|
+
"hs: no vault detected — could not find a specs/ folder " +
|
|
430
|
+
"walking up from the current directory. " +
|
|
431
|
+
"Set HS_VAULT_ROOT or run from inside a vault.\n",
|
|
432
|
+
);
|
|
433
|
+
process.exit(1);
|
|
434
|
+
}
|
|
435
|
+
dispatchSpexConsoleEntry("hs-env.ts", ["env", "invite", ...argv.slice(1)], vaultRoot);
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// `hs setup claude|cursor` — install agent lifecycle hooks into the vault root.
|
|
440
|
+
if (namespace === "setup") {
|
|
441
|
+
const agent = command?.toLowerCase();
|
|
442
|
+
const SCRIPTS = {
|
|
443
|
+
claude: "install-claude-code-hooks.mjs",
|
|
444
|
+
cursor: "install-cursor-hooks.mjs",
|
|
445
|
+
};
|
|
446
|
+
if (!agent || !Object.hasOwn(SCRIPTS, agent)) {
|
|
447
|
+
process.stderr.write(
|
|
448
|
+
`hs setup: expected \`claude\` or \`cursor\` (got \`${command ?? ""}\`)\n` +
|
|
449
|
+
" hs setup claude [--check|--uninstall] Claude Code lifecycle hooks\n" +
|
|
450
|
+
" hs setup cursor [--check|--uninstall] Cursor CLI lifecycle hooks\n",
|
|
451
|
+
);
|
|
452
|
+
process.exit(2);
|
|
453
|
+
}
|
|
454
|
+
const setupFlags = rest.filter(
|
|
455
|
+
(a) => a === "--check" || a === "--uninstall",
|
|
456
|
+
);
|
|
457
|
+
const script = path.join(PKG_ROOT, "scripts", SCRIPTS[agent]);
|
|
458
|
+
const target = vaultRoot || process.cwd();
|
|
459
|
+
const child = spawn(process.execPath, [script, ...setupFlags], {
|
|
460
|
+
cwd: target,
|
|
461
|
+
stdio: "inherit",
|
|
462
|
+
env: { ...process.env, HS_SETUP_TARGET: target },
|
|
463
|
+
});
|
|
464
|
+
child.on("exit", (code, signal) => process.exit(signal ? 1 : (code ?? 0)));
|
|
465
|
+
child.on("error", (err) => {
|
|
466
|
+
process.stderr.write(
|
|
467
|
+
`hs setup: failed to run ${SCRIPTS[agent]}: ${err.message}\n`,
|
|
468
|
+
);
|
|
469
|
+
process.exit(1);
|
|
470
|
+
});
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
// `hs skills sync|check` — propagate .commandcode/skills → Cursor/Codex/Claude shims.
|
|
475
|
+
if (namespace === "skills") {
|
|
476
|
+
const action = command?.toLowerCase();
|
|
477
|
+
if (action !== "sync" && action !== "check") {
|
|
478
|
+
process.stderr.write(
|
|
479
|
+
`hs skills: expected \`sync\` or \`check\` (got \`${command ?? ""}\`)\n` +
|
|
480
|
+
" hs skills sync Copy skills + slash shims to all agent harnesses\n" +
|
|
481
|
+
" hs skills check Exit 1 if any harness copy is stale\n",
|
|
482
|
+
);
|
|
483
|
+
process.exit(2);
|
|
484
|
+
}
|
|
485
|
+
const script = path.join(PKG_ROOT, "scripts", "sync-skills.mjs");
|
|
486
|
+
const target = vaultRoot || PKG_ROOT;
|
|
487
|
+
const childArgs = action === "check" ? ["--check"] : [];
|
|
488
|
+
const child = spawn(process.execPath, [script, ...childArgs], {
|
|
489
|
+
cwd: target,
|
|
490
|
+
stdio: "inherit",
|
|
491
|
+
});
|
|
492
|
+
child.on("exit", (code, signal) => process.exit(signal ? 1 : (code ?? 0)));
|
|
493
|
+
child.on("error", (err) => {
|
|
494
|
+
process.stderr.write(
|
|
495
|
+
`hs skills: failed to run sync-skills.mjs: ${err.message}\n`,
|
|
496
|
+
);
|
|
497
|
+
process.exit(1);
|
|
498
|
+
});
|
|
499
|
+
return;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
// `hs vault list|register|switch|active` — the vault registry + active
|
|
503
|
+
// pointer CLI ([[HS-Q1HZ-TASK-hs-vault-cli-list-register-switch-active]]).
|
|
504
|
+
// May run outside any vault (registering a fresh directory, or listing/
|
|
505
|
+
// switching from anywhere), so it bypasses the hard vault gate below like
|
|
506
|
+
// init/install/setup.
|
|
507
|
+
if (namespace === "vault") {
|
|
508
|
+
dispatchSpexConsoleEntry("hs-vault.ts", argv, vaultRoot || process.cwd(), {
|
|
509
|
+
...(vaultIdOverride ? { HS_VAULT_ID_OVERRIDE: vaultIdOverride } : {}),
|
|
510
|
+
});
|
|
511
|
+
return;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
// `hs workspace list|create|switch` — the workspace (company container)
|
|
515
|
+
// registry CLI ([[HS-Q1HZ-TASK-hs-workspace-cli-list-create-switch]]). Same
|
|
516
|
+
// no-vault-required rationale as `hs vault` above.
|
|
517
|
+
if (namespace === "workspace") {
|
|
518
|
+
dispatchSpexConsoleEntry(
|
|
519
|
+
"hs-workspace.ts",
|
|
520
|
+
argv,
|
|
521
|
+
vaultRoot || process.cwd(),
|
|
522
|
+
);
|
|
523
|
+
return;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
// `hs login` — browser hand-off pairing (RFC 8252 loopback):
|
|
527
|
+
// one-shot 127.0.0.1 listener → default browser at <console>/companion/connect
|
|
528
|
+
// → the console mints the single-use code and redirects it back → silent
|
|
529
|
+
// redeem into ~/.heyspex/credentials.yaml. Same redemption as `hs mcp pair`,
|
|
530
|
+
// which stays as the cross-machine typed-code fallback
|
|
531
|
+
// ([[HS-IFQM-IZQ7-EPIC-hs-login-browser-hand-off-for-cli-and]]). This is an
|
|
532
|
+
// auth verb, so it bypasses the hard vault gate below like init/install —
|
|
533
|
+
// signing in is exactly what you do BEFORE you have a vault here.
|
|
534
|
+
if (namespace === "login") {
|
|
535
|
+
const cliPath = path.join(PKG_ROOT, "vendor", "heyspex-mcp", "cli.mjs");
|
|
536
|
+
const loginRoot = vaultRoot || process.cwd();
|
|
537
|
+
const child = spawn(process.execPath, [cliPath, "login", ...argv.slice(1)], {
|
|
538
|
+
cwd: PKG_ROOT,
|
|
539
|
+
stdio: "inherit",
|
|
540
|
+
env: {
|
|
541
|
+
...process.env,
|
|
542
|
+
HEYSPEX_VAULT_ROOT: loginRoot,
|
|
543
|
+
HS_VAULT_ROOT: loginRoot,
|
|
544
|
+
HEYSPEX_DISABLE_STDIO_BOOTSTRAP: "1",
|
|
545
|
+
},
|
|
546
|
+
});
|
|
547
|
+
child.on("exit", (code, signal) => process.exit(signal ? 1 : (code ?? 0)));
|
|
548
|
+
child.on("error", (err) => {
|
|
549
|
+
process.stderr.write(`hs: failed to run login: ${err.message}\n`);
|
|
550
|
+
process.exit(1);
|
|
551
|
+
});
|
|
552
|
+
return;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
// `hs connect --check [--json]` — the read-only connect-checks report
|
|
556
|
+
// ([[HS-I2OO-EKLJ-TASK-shared-connect-checks-module-with-a]] on
|
|
557
|
+
// [[HS-QYH8-EKLJ-EPIC-hs-connect-one-command-repo-connect-verb]]). May run
|
|
558
|
+
// in an unconnected repo (no specs/ yet — that is the point), so it
|
|
559
|
+
// bypasses the hard vault gate below like init/install/vault/workspace.
|
|
560
|
+
if (namespace === "connect") {
|
|
561
|
+
dispatchSpexConsoleEntry("hs-connect.ts", argv, vaultRoot || process.cwd(), {
|
|
562
|
+
HS_INVOKED_CWD: INVOKED_CWD,
|
|
563
|
+
});
|
|
564
|
+
return;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
// `hs doctor [--fix]` — verify every install surface (MCP, hooks, skills,
|
|
568
|
+
// statusline, git hooks, console secrets) and repair what can repair itself.
|
|
569
|
+
// Diagnosing whether the install itself is healthy must not first require a
|
|
570
|
+
// vault to exist, so — like init/install/connect — this bypasses the hard
|
|
571
|
+
// vault gate below. [[HS-BFXF-WBJU-DIAG-hs-never-resolves-on-a-fresh-machine]]:
|
|
572
|
+
// the very first thing a stranger runs after `npm install heyspex` in an
|
|
573
|
+
// empty project is exactly this, from a directory with no `specs/` yet.
|
|
574
|
+
if (namespace === "doctor") {
|
|
575
|
+
const script = path.join(PKG_ROOT, "scripts", "heyspex-doctor.mjs");
|
|
576
|
+
if (!existsSync(script)) {
|
|
577
|
+
// Published-package install (per HS-ZJSP-0JNK): the full doctor audits
|
|
578
|
+
// dozens of source-checkout-only surfaces (git hooks, Cursor wiring,
|
|
579
|
+
// console secrets…) that a `npm install heyspex` consumer never has, so
|
|
580
|
+
// it is deliberately not in the package's `files`. Run the small
|
|
581
|
+
// subset that DOES apply to a published install instead of crashing
|
|
582
|
+
// with MODULE_NOT_FOUND.
|
|
583
|
+
runStandaloneDoctor(argv.slice(1));
|
|
584
|
+
return;
|
|
585
|
+
}
|
|
586
|
+
const child = spawn(process.execPath, [script, ...argv.slice(1)], {
|
|
587
|
+
cwd: PKG_ROOT,
|
|
588
|
+
stdio: "inherit",
|
|
589
|
+
env: { ...process.env, HS_VAULT_ROOT: vaultRoot },
|
|
590
|
+
});
|
|
591
|
+
child.on("exit", (code, signal) => process.exit(signal ? 1 : (code ?? 0)));
|
|
592
|
+
child.on("error", (err) => {
|
|
593
|
+
process.stderr.write(`hs: failed to run hs doctor: ${err.message}\n`);
|
|
594
|
+
process.exit(1);
|
|
595
|
+
});
|
|
596
|
+
return;
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
if (!vaultRoot) {
|
|
600
|
+
process.stderr.write(
|
|
601
|
+
"hs: no vault detected — could not find a specs/ folder " +
|
|
602
|
+
"walking up from the current directory. " +
|
|
603
|
+
"Set HS_VAULT_ROOT or run from inside a vault.\n",
|
|
604
|
+
);
|
|
605
|
+
process.exit(1);
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
if (namespace === "index" && command === "rebuild") {
|
|
609
|
+
dispatchSpexConsoleEntry("hs-index-rebuild.ts", rest, vaultRoot);
|
|
610
|
+
return;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
// `hs pull` — cloud → disk vault reconciliation. In cloud mode a filed spec
|
|
614
|
+
// lands in the cloud content store ONLY, invisible to promote / vault scans /
|
|
615
|
+
// wikilink resolution / git ([[HS-OQNH-21DO-DIAG-cloud-written-specs-are-invisible-to]]).
|
|
616
|
+
// This is the missing pull half. Additive and one-directional: it never
|
|
617
|
+
// pushes, never deletes, and only REPORTS unless `--apply` is given.
|
|
618
|
+
// Forwards the full argv; the entrypoint drops the leading `pull` token.
|
|
619
|
+
if (namespace === "pull") {
|
|
620
|
+
dispatchSpexConsoleEntry("hs-vault-pull.ts", argv, vaultRoot);
|
|
621
|
+
return;
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
// `hs milestone show <id>` — print a milestone, flagging needs-breakdown when
|
|
625
|
+
// it has no epics ([[HS-TASK-HMSE]] / [[HS-EPIC-MNBD]]). Forwards the full
|
|
626
|
+
// argv; the entrypoint drops the leading `milestone` token.
|
|
627
|
+
if (namespace === "milestone") {
|
|
628
|
+
dispatchSpexConsoleEntry("hs-milestone.ts", argv, vaultRoot);
|
|
629
|
+
return;
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
if (namespace === "factory" && command === "run") {
|
|
633
|
+
dispatchSpexConsoleEntry("hs-factory-run.ts", argv, vaultRoot);
|
|
634
|
+
return;
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
// `hs factory status|fleet|results` — Dev-Factory read-only status surface
|
|
638
|
+
// (fleet, routing table, calibrations, launch results). CLI face of the
|
|
639
|
+
// shared core reader that also backs /api/admin/factory/* ([[HS-0DG1-EPIC]] —
|
|
640
|
+
// capability-three-faces). Forwards the full argv; the entrypoint drops the
|
|
641
|
+
// leading `factory` token.
|
|
642
|
+
if (namespace === "factory") {
|
|
643
|
+
dispatchSpexConsoleEntry("hs-factory.ts", argv, vaultRoot);
|
|
644
|
+
return;
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
// `hs code-watcher scan` — run the build tools (tsc --noEmit; test/lint to
|
|
648
|
+
// follow), parse failures, and write the build-correctness snapshot the
|
|
649
|
+
// Resolution surface reads ([[HS-TASK-2B4P]] / [[HS-EPIC-RHNJ]]). Forwards the
|
|
650
|
+
// full argv; the entrypoint drops the leading `code-watcher` token.
|
|
651
|
+
if (namespace === "code-watcher") {
|
|
652
|
+
dispatchSpexConsoleEntry("hs-code-watcher.ts", argv, vaultRoot);
|
|
653
|
+
return;
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
// `hs server status` / `hs server stop` — ground-truth status and stop for
|
|
657
|
+
// the spex-console Next.js dev server. Pure Node cores under scripts/
|
|
658
|
+
// (same as npm run console:status / console:stop). Call status before any
|
|
659
|
+
// server-impacting task; stop from any terminal (not only the one that
|
|
660
|
+
// started it). See [[dev-server-lifecycle-and-db-safety]].
|
|
661
|
+
if (namespace === "server" && (command === "status" || command === "stop")) {
|
|
662
|
+
const scriptName =
|
|
663
|
+
command === "stop" ? "dev-server-stop.mjs" : "dev-server-status.mjs";
|
|
664
|
+
const script = path.join(PKG_ROOT, "scripts", scriptName);
|
|
665
|
+
const child = spawn(process.execPath, [script, ...rest], {
|
|
666
|
+
cwd: PKG_ROOT,
|
|
667
|
+
stdio: "inherit",
|
|
668
|
+
env: { ...process.env, HS_VAULT_ROOT: vaultRoot },
|
|
669
|
+
});
|
|
670
|
+
child.on("exit", (code, signal) => process.exit(signal ? 1 : (code ?? 0)));
|
|
671
|
+
child.on("error", (err) => {
|
|
672
|
+
process.stderr.write(
|
|
673
|
+
`hs: failed to run ${scriptName}: ${err.message}\n`,
|
|
674
|
+
);
|
|
675
|
+
process.exit(1);
|
|
676
|
+
});
|
|
677
|
+
return;
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
// `hs source status|preflight|commit|push` — SpexSource: the local-git
|
|
681
|
+
// preflight/commit/push path for agent sessions
|
|
682
|
+
// ([[HS-Q0K8-CWPX-EPIC-spexsource-local-git-preflight-commit]]). Pure Node
|
|
683
|
+
// cores under scripts/lib/, invoked directly (not via dispatchSpexConsoleEntry
|
|
684
|
+
// — these are plain .mjs modules, not spex-console TS entries). Forwards the
|
|
685
|
+
// full argv; each script drops the leading `source <verb>` tokens itself.
|
|
686
|
+
if (namespace === "source") {
|
|
687
|
+
// `hs source --help` / `-h` — bare usage, no subprocess spawned. Must be
|
|
688
|
+
// checked BEFORE the SOURCE_ENTRYPOINTS lookup below: `command` here is
|
|
689
|
+
// "--help"/"-h", which is not a known verb, so without this special case
|
|
690
|
+
// it fell into the "unknown verb" error path (exit 2) instead of exiting
|
|
691
|
+
// 0 with usage ([[HS-PO09-0JNK-TASK-packed-mcp-server-runs-the-agent-loop]] AC4).
|
|
692
|
+
if (command === "--help" || command === "-h") {
|
|
693
|
+
process.stdout.write(
|
|
694
|
+
"hs source — SpexSource local-git preflight/commit/push for agent sessions\n\n" +
|
|
695
|
+
" hs source status Read-only tree status + path-scoped stage plan\n" +
|
|
696
|
+
" hs source preflight Alias of status\n" +
|
|
697
|
+
" hs source commit Stage + commit per the preflight plan\n" +
|
|
698
|
+
" hs source push Push the committed branch\n" +
|
|
699
|
+
" hs source log <spec-id> Commit-history lookup for a spec (console settlement,\n" +
|
|
700
|
+
" falling back to local git log)\n",
|
|
701
|
+
);
|
|
702
|
+
process.exit(0);
|
|
703
|
+
}
|
|
704
|
+
const SOURCE_ENTRYPOINTS = {
|
|
705
|
+
status: "hs-source-preflight.mjs",
|
|
706
|
+
preflight: "hs-source-preflight.mjs",
|
|
707
|
+
commit: "hs-source-commit.mjs",
|
|
708
|
+
push: "hs-source-push.mjs",
|
|
709
|
+
log: "hs-source-log.mjs",
|
|
710
|
+
};
|
|
711
|
+
const scriptName = SOURCE_ENTRYPOINTS[command];
|
|
712
|
+
if (!scriptName) {
|
|
713
|
+
process.stderr.write(
|
|
714
|
+
`hs: unknown \`hs source\` verb \`${command ?? ""}\` — expected one of: ${Object.keys(SOURCE_ENTRYPOINTS).join(", ")}\n`,
|
|
715
|
+
);
|
|
716
|
+
process.exit(2);
|
|
717
|
+
}
|
|
718
|
+
const script = path.join(PKG_ROOT, "scripts", "lib", scriptName);
|
|
719
|
+
const child = spawn(process.execPath, [script, command, ...rest], {
|
|
720
|
+
cwd: PKG_ROOT,
|
|
721
|
+
stdio: "inherit",
|
|
722
|
+
env: { ...process.env, HS_VAULT_ROOT: vaultRoot },
|
|
723
|
+
});
|
|
724
|
+
child.on("exit", (code, signal) => process.exit(signal ? 1 : (code ?? 0)));
|
|
725
|
+
child.on("error", (err) => {
|
|
726
|
+
process.stderr.write(`hs: failed to run ${scriptName}: ${err.message}\n`);
|
|
727
|
+
process.exit(1);
|
|
728
|
+
});
|
|
729
|
+
return;
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
// `hs view <surface> [spec-id]` — open one console surface in a chromeless
|
|
733
|
+
// app-mode browser window (the solo console gauge —
|
|
734
|
+
// [[HS-RU2R-I87U-RES-solo-surface-cli-launcher-single]]). Probes ground-truth
|
|
735
|
+
// server status first; a down console prints a clear message, never a blank window.
|
|
736
|
+
if (namespace === "view") {
|
|
737
|
+
const script = path.join(PKG_ROOT, "scripts", "hs-view.mjs");
|
|
738
|
+
const child = spawn(process.execPath, [script, ...argv.slice(1)], {
|
|
739
|
+
cwd: PKG_ROOT,
|
|
740
|
+
stdio: "inherit",
|
|
741
|
+
env: { ...process.env, HS_VAULT_ROOT: vaultRoot },
|
|
742
|
+
});
|
|
743
|
+
child.on("exit", (code, signal) => process.exit(signal ? 1 : (code ?? 0)));
|
|
744
|
+
child.on("error", (err) => {
|
|
745
|
+
process.stderr.write(`hs: failed to run hs view: ${err.message}\n`);
|
|
746
|
+
process.exit(1);
|
|
747
|
+
});
|
|
748
|
+
return;
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
// `hs dev [mode:local|mode:cloud|mode:prod]` — dev/prod mode + local/cloud
|
|
752
|
+
// reliance (epic HS-Q6M2). Bare `hs dev` prints the resolved mode + sources;
|
|
753
|
+
// mode:* switches (writes ~/.heyspex/dev-mode.json + credentials.yaml
|
|
754
|
+
// console_url, best-effort cloud admin setting). Forwards the full argv;
|
|
755
|
+
// the entrypoint drops the leading `dev` token.
|
|
756
|
+
if (namespace === "dev") {
|
|
757
|
+
dispatchSpexConsoleEntry("hs-dev.ts", argv, vaultRoot);
|
|
758
|
+
return;
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
// `hs defer <id>` / `hs undefer <id>` are top-level verbs (not namespaced):
|
|
762
|
+
// forward the full argv (verb + args) to the spex-console entrypoint.
|
|
763
|
+
if (namespace === "defer" || namespace === "undefer") {
|
|
764
|
+
dispatchSpexConsoleEntry("hs-defer.ts", argv, vaultRoot);
|
|
765
|
+
return;
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
// `hs spawn [<id>] [--prompt ...] [--dry-run] [--json]` — launch a FRESH
|
|
769
|
+
// /hs-next agent session in a new terminal ([[HS-EPIC-NWUE]] / [[HS-TASK-MAPK]]).
|
|
770
|
+
// The CLI face of the What's Next hero Launch button + POST /api/agent/start;
|
|
771
|
+
// forwards the full argv (verb + args), the entrypoint drops the leading token.
|
|
772
|
+
if (namespace === "spawn") {
|
|
773
|
+
dispatchSpexConsoleEntry("hs-spawn.ts", argv, vaultRoot);
|
|
774
|
+
return;
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
// `hs capture "<text>"|--clipboard|--file <p> [--add]` — CLI capture into
|
|
778
|
+
// the console drafts inbox; --add chains into the previous CLI capture's
|
|
779
|
+
// group (the multi-capture "add another?" gesture). Pure Node, posts the
|
|
780
|
+
// same heyspex.capture/0.1 envelope the companion sends to /api/drafts.
|
|
781
|
+
if (namespace === "capture") {
|
|
782
|
+
const script = path.join(PKG_ROOT, "scripts", "hs-capture-cli.mjs");
|
|
783
|
+
const captureArgs = command ? [command, ...rest] : rest;
|
|
784
|
+
const child = spawn(process.execPath, [script, ...captureArgs], {
|
|
785
|
+
cwd: PKG_ROOT,
|
|
786
|
+
stdio: "inherit",
|
|
787
|
+
env: { ...process.env, HS_VAULT_ROOT: vaultRoot },
|
|
788
|
+
});
|
|
789
|
+
child.on("exit", (code, signal) => process.exit(signal ? 1 : (code ?? 0)));
|
|
790
|
+
child.on("error", (err) => {
|
|
791
|
+
process.stderr.write(`hs: failed to run capture: ${err.message}\n`);
|
|
792
|
+
process.exit(1);
|
|
793
|
+
});
|
|
794
|
+
return;
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
// `hs companion start|stop|status|build` — companion tray host (companion/src/HeySpex.Companion.Host).
|
|
798
|
+
if (namespace === "companion") {
|
|
799
|
+
const script = path.join(PKG_ROOT, "scripts", "hs-companion.mjs");
|
|
800
|
+
const companionArgs = command ? [command, ...rest] : ["start", ...rest];
|
|
801
|
+
const child = spawn(process.execPath, [script, ...companionArgs], {
|
|
802
|
+
cwd: PKG_ROOT,
|
|
803
|
+
stdio: "inherit",
|
|
804
|
+
});
|
|
805
|
+
child.on("exit", (code, signal) => process.exit(signal ? 1 : (code ?? 0)));
|
|
806
|
+
child.on("error", (err) => {
|
|
807
|
+
process.stderr.write(`hs: failed to run companion: ${err.message}\n`);
|
|
808
|
+
process.exit(1);
|
|
809
|
+
});
|
|
810
|
+
return;
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
if (namespace === "registry") {
|
|
814
|
+
dispatchSpexConsoleEntry("hs-registry.ts", argv, vaultRoot);
|
|
815
|
+
return;
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
// `hs env init|set|list|run|export|rotate` — encrypted env vault (ADR-0020).
|
|
819
|
+
// Pure local fs (no console): forwards the full argv, the entrypoint drops the
|
|
820
|
+
// leading `env` token. [[HS-CDFS-TASK]] / [[HS-9VD2-TASK]].
|
|
821
|
+
if (namespace === "env") {
|
|
822
|
+
dispatchSpexConsoleEntry("hs-env.ts", argv, vaultRoot);
|
|
823
|
+
return;
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
// `hs next [spec_id] [--summary] [--json]` — resolve the on-deck brief without
|
|
827
|
+
// MCP ([[HS-TASK-ISK7]] / [[tool-agnostic-mcp-first-cli-second]]).
|
|
828
|
+
if (namespace === "next") {
|
|
829
|
+
const script = path.join(PKG_ROOT, "scripts", "hs-next.mjs");
|
|
830
|
+
const child = spawn(process.execPath, [script, ...argv.slice(1)], {
|
|
831
|
+
cwd: PKG_ROOT,
|
|
832
|
+
stdio: "inherit",
|
|
833
|
+
env: { ...process.env, HS_VAULT_ROOT: vaultRoot },
|
|
834
|
+
});
|
|
835
|
+
child.on("exit", (code, signal) => process.exit(signal ? 1 : (code ?? 0)));
|
|
836
|
+
child.on("error", (err) => {
|
|
837
|
+
process.stderr.write(`hs: failed to run hs next: ${err.message}\n`);
|
|
838
|
+
process.exit(1);
|
|
839
|
+
});
|
|
840
|
+
return;
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
// `hs mode on|off|status|toggle` — the master switch for the whole HeySpex
|
|
844
|
+
// agent layer (hooks, MCP server, statusline, CLAUDE.md agent contract).
|
|
845
|
+
// Turning it off makes this repo behave like a plain codebase in Claude Code.
|
|
846
|
+
// Core: scripts/heyspex-mode.mjs · docs/claude-code-setup.md.
|
|
847
|
+
if (namespace === "mode") {
|
|
848
|
+
const script = path.join(PKG_ROOT, "scripts", "hs-mode.mjs");
|
|
849
|
+
const child = spawn(process.execPath, [script, ...argv.slice(1)], {
|
|
850
|
+
cwd: PKG_ROOT,
|
|
851
|
+
stdio: "inherit",
|
|
852
|
+
env: { ...process.env, HS_VAULT_ROOT: vaultRoot },
|
|
853
|
+
});
|
|
854
|
+
child.on("exit", (code, signal) => process.exit(signal ? 1 : (code ?? 0)));
|
|
855
|
+
child.on("error", (err) => {
|
|
856
|
+
process.stderr.write(`hs: failed to run hs mode: ${err.message}\n`);
|
|
857
|
+
process.exit(1);
|
|
858
|
+
});
|
|
859
|
+
return;
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
// `hs gateway validate [slug] [--json]` — validate user-authored gateway
|
|
863
|
+
// skills and their scope coverage (HS-WEC3; core: scripts/gateway-skills-lib.mjs).
|
|
864
|
+
// Registration subcommands print a blocked-on-HS-DCCP notice until the
|
|
865
|
+
// connector's server-registration table exists.
|
|
866
|
+
if (namespace === "gateway") {
|
|
867
|
+
const script = path.join(PKG_ROOT, "scripts", "hs-gateway.mjs");
|
|
868
|
+
const child = spawn(process.execPath, [script, ...argv.slice(1)], {
|
|
869
|
+
cwd: PKG_ROOT,
|
|
870
|
+
stdio: "inherit",
|
|
871
|
+
env: { ...process.env, HS_VAULT_ROOT: vaultRoot },
|
|
872
|
+
});
|
|
873
|
+
child.on("exit", (code, signal) => process.exit(signal ? 1 : (code ?? 0)));
|
|
874
|
+
child.on("error", (err) => {
|
|
875
|
+
process.stderr.write(`hs: failed to run hs gateway: ${err.message}\n`);
|
|
876
|
+
process.exit(1);
|
|
877
|
+
});
|
|
878
|
+
return;
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
// `hs settings list|get|set` — read/change HeySpex workflow settings in
|
|
882
|
+
// .heyspex/config.yaml (settings capability; core: vendor/heyspex-mcp/
|
|
883
|
+
// config-settings.mjs; write-permission rules: agent-settings-protocol).
|
|
884
|
+
if (namespace === "settings") {
|
|
885
|
+
const script = path.join(PKG_ROOT, "scripts", "hs-settings.mjs");
|
|
886
|
+
const child = spawn(process.execPath, [script, ...argv], {
|
|
887
|
+
cwd: PKG_ROOT,
|
|
888
|
+
stdio: "inherit",
|
|
889
|
+
env: { ...process.env, HS_VAULT_ROOT: vaultRoot },
|
|
890
|
+
});
|
|
891
|
+
child.on("exit", (code, signal) => process.exit(signal ? 1 : (code ?? 0)));
|
|
892
|
+
child.on("error", (err) => {
|
|
893
|
+
process.stderr.write(`hs: failed to run hs settings: ${err.message}\n`);
|
|
894
|
+
process.exit(1);
|
|
895
|
+
});
|
|
896
|
+
return;
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
// `hs classifier stats` — read-only view of HeySpex Intent Classifier decisions
|
|
900
|
+
// (.heyspex/intent-classifier-history.jsonl). Pure local read.
|
|
901
|
+
if (namespace === "classifier") {
|
|
902
|
+
const script = path.join(PKG_ROOT, "scripts", "hs-classifier.mjs");
|
|
903
|
+
const child = spawn(process.execPath, [script, ...argv], {
|
|
904
|
+
cwd: PKG_ROOT,
|
|
905
|
+
stdio: "inherit",
|
|
906
|
+
env: { ...process.env, HS_VAULT_ROOT: vaultRoot },
|
|
907
|
+
});
|
|
908
|
+
child.on("exit", (code, signal) => process.exit(signal ? 1 : (code ?? 0)));
|
|
909
|
+
child.on("error", (err) => {
|
|
910
|
+
process.stderr.write(`hs: failed to run hs classifier: ${err.message}\n`);
|
|
911
|
+
process.exit(1);
|
|
912
|
+
});
|
|
913
|
+
return;
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
// `hs auto [filename] [--json]` — resolve /hs-auto COO dispatch plan.
|
|
917
|
+
if (namespace === "auto") {
|
|
918
|
+
const script = path.join(PKG_ROOT, "scripts", "hs-auto.mjs");
|
|
919
|
+
const child = spawn(process.execPath, [script, ...argv.slice(1)], {
|
|
920
|
+
cwd: PKG_ROOT,
|
|
921
|
+
stdio: "inherit",
|
|
922
|
+
env: { ...process.env, HS_VAULT_ROOT: vaultRoot },
|
|
923
|
+
});
|
|
924
|
+
child.on("exit", (code, signal) => process.exit(signal ? 1 : (code ?? 0)));
|
|
925
|
+
child.on("error", (err) => {
|
|
926
|
+
process.stderr.write(`hs: failed to run hs auto: ${err.message}\n`);
|
|
927
|
+
process.exit(1);
|
|
928
|
+
});
|
|
929
|
+
return;
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
// `hs mcp call <tool> [--arg key=value ...] [--args '<json>'] [--args-file <path>] [--json]`
|
|
933
|
+
// without the stdio JSON-RPC loop (parity for agents when heyspex_* tools are
|
|
934
|
+
// missing from the palette — [[cursor-mcp-official-notes]]).
|
|
935
|
+
// `hs mcp pair <code> [--console-url <url>]` — redeem a console pairing
|
|
936
|
+
// code for a persistent `hsmcp_` bearer, stored in ~/.heyspex/credentials.yaml
|
|
937
|
+
// ([[HS-GD45-MCPC-TASK-mcp-cloud-auth-shared-console-resolver]]).
|
|
938
|
+
if (namespace === "mcp") {
|
|
939
|
+
if (command !== "call" && command !== "pair" && command !== "login") {
|
|
940
|
+
process.stderr.write(
|
|
941
|
+
`hs: unknown mcp subcommand \`${command ?? ""}\` (expected: call | pair | login)\n` +
|
|
942
|
+
" hs mcp call <tool> [--arg key=value ...] [--args '<json>'] [--args-file <path>] [--json]\n" +
|
|
943
|
+
" hs mcp pair <code> [--console-url <url>] [--label <text>] [--json]\n" +
|
|
944
|
+
" hs mcp login [--console-url <url>] [--label <text>] [--json] (alias of `hs login`)\n",
|
|
945
|
+
);
|
|
946
|
+
process.exit(2);
|
|
947
|
+
}
|
|
948
|
+
const cliPath = path.join(PKG_ROOT, "vendor", "heyspex-mcp", "cli.mjs");
|
|
949
|
+
const child = spawn(process.execPath, [cliPath, command, ...rest], {
|
|
950
|
+
cwd: PKG_ROOT,
|
|
951
|
+
stdio: "inherit",
|
|
952
|
+
env: {
|
|
953
|
+
...process.env,
|
|
954
|
+
HEYSPEX_VAULT_ROOT: vaultRoot,
|
|
955
|
+
HS_VAULT_ROOT: vaultRoot,
|
|
956
|
+
// One-shot MCP tool — no stdio loop, no stderr brief-tier noise in PS.
|
|
957
|
+
HEYSPEX_DISABLE_STDIO_BOOTSTRAP: "1",
|
|
958
|
+
},
|
|
959
|
+
});
|
|
960
|
+
child.on("exit", (code, signal) => process.exit(signal ? 1 : (code ?? 0)));
|
|
961
|
+
child.on("error", (err) => {
|
|
962
|
+
process.stderr.write(`hs: failed to run mcp ${command}: ${err.message}\n`);
|
|
963
|
+
process.exit(1);
|
|
964
|
+
});
|
|
965
|
+
return;
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
// `hs test run <id|manifest>` — run open-format API/contract tests (Hurl/.http)
|
|
969
|
+
// bound to acceptance criteria ([[HS-EPIC-Q0KU-open-format-test-substrate-hs-test-run-h]];
|
|
970
|
+
// supersedes the .spex plan per [[HS-RES-W0F6-testing-direction-pivot-open-formats-mut]]).
|
|
971
|
+
// Forwards the full argv; the entrypoint drops the leading `test` token.
|
|
972
|
+
if (namespace === "test") {
|
|
973
|
+
dispatchSpexConsoleEntry("hs-test.ts", argv, vaultRoot);
|
|
974
|
+
return;
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
// `hs check run <id|manifest>` — verify a spec's acceptance criteria against its
|
|
978
|
+
// frozen checks manifest, per-criterion pass/fail against the real tree
|
|
979
|
+
// ([[HS-EPIC-DCEP-acceptance-criteria-checks-manifest-dete]], the deterministic
|
|
980
|
+
// kernel of [[HS-MIL-Y5DN-verification-engine-shared-deterministic]]). Forwards
|
|
981
|
+
// the full argv; the entrypoint drops the leading `check` token.
|
|
982
|
+
if (namespace === "check") {
|
|
983
|
+
dispatchSpexConsoleEntry("hs-check.ts", argv, vaultRoot);
|
|
984
|
+
return;
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
// `hs revalidate` — re-run the FROZEN manifests of work already stamped
|
|
988
|
+
// `review_pending`/`in_review` and report what has decayed since the stamp
|
|
989
|
+
// ([[HS-R5LF-36QL-EPIC-stamped-work-is-re-verified-on-a]]). Read-only with
|
|
990
|
+
// respect to the repo; the Resolution plan is a dry run unless `--apply`.
|
|
991
|
+
if (namespace === "revalidate") {
|
|
992
|
+
dispatchSpexConsoleEntry("hs-revalidate.ts", argv, vaultRoot);
|
|
993
|
+
return;
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
// `hs agent import <collection> <role>` / `hs agent list` — import a community
|
|
997
|
+
// agent definition from a vendored collection into a `type: agent` vault spec
|
|
998
|
+
// ([[HS-TASK-6D5P]] / [[HS-EPIC-TMXZ-default-agent-roster-and-import-path]] AC#2).
|
|
999
|
+
// The CLI face of the shared import normalizer; forwards the full argv, the
|
|
1000
|
+
// entrypoint drops the leading `agent` token.
|
|
1001
|
+
if (namespace === "agent") {
|
|
1002
|
+
dispatchSpexConsoleEntry("hs-agent.ts", argv, vaultRoot);
|
|
1003
|
+
return;
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
// Neon control-plane verbs ([[HS-EPIC-CLMW-hs-cli-mcp-wrappers-for-control-plane]]).
|
|
1007
|
+
// Each forwards the full argv (namespace/verb + args); the entrypoint drops
|
|
1008
|
+
// the leading namespace token and parses the rest.
|
|
1009
|
+
const CONTROL_ENTRYPOINTS = {
|
|
1010
|
+
pin: "hs-pin.ts",
|
|
1011
|
+
claim: "hs-claim.ts",
|
|
1012
|
+
handoff: "hs-handoff.ts",
|
|
1013
|
+
events: "hs-events.ts",
|
|
1014
|
+
"export-events": "hs-export-events.ts",
|
|
1015
|
+
prereq: "hs-prereq.ts",
|
|
1016
|
+
queue: "hs-queue.ts",
|
|
1017
|
+
};
|
|
1018
|
+
if (Object.hasOwn(CONTROL_ENTRYPOINTS, namespace)) {
|
|
1019
|
+
dispatchSpexConsoleEntry(CONTROL_ENTRYPOINTS[namespace], argv, vaultRoot);
|
|
1020
|
+
return;
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
process.stderr.write(`hs: unknown command \`${argv.join(" ")}\`\n\n`);
|
|
1024
|
+
usage();
|
|
1025
|
+
process.exit(2);
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
main(process.argv.slice(2));
|