heyspex 0.2.0 → 0.2.1
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 +14 -0
- package/bin/hs.mjs +163 -11
- package/dist/cli/hs-check.mjs +88 -46
- package/dist/cli/hs-claim.mjs +84 -39
- package/dist/cli/hs-connect.mjs +583 -129
- package/dist/cli/hs-defer.mjs +251 -30
- package/dist/cli/hs-dev.mjs +20 -8
- package/dist/cli/hs-env.mjs +62 -34
- package/dist/cli/hs-index-rebuild.mjs +288 -83
- package/dist/cli/hs-index-reconcile.mjs +397 -111
- package/dist/cli/hs-init.mjs +2143 -91
- package/dist/cli/hs-install.mjs +628 -106
- package/dist/cli/hs-join.mjs +686 -236
- package/dist/cli/hs-vault-pull.mjs +3189 -179
- package/dist/cli/hs-vault-push.mjs +6433 -0
- package/dist/cli/hs-vault.mjs +396 -178
- package/dist/cli/hs-workspace.mjs +289 -151
- 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 -28
- 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-resolve.md +23 -23
- package/dist/cli-templates/harness-commands/claude/hs-review.md +23 -23
- package/dist/cli-templates/harness-commands/claude/hs-view.md +28 -26
- 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 -22
- 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-resolve.md +17 -17
- package/dist/cli-templates/harness-commands/cursor/hs-review.md +17 -17
- package/dist/cli-templates/harness-commands/cursor/hs-view.md +24 -22
- 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 +162 -0
- package/docs/driving-heyspex-from-copilot.md +165 -0
- package/package.json +20 -2
- package/plugins/heyspex/.claude-plugin/plugin.json +12 -12
- package/plugins/heyspex/.cursor-plugin/plugin.json +15 -15
- package/plugins/heyspex/README.md +309 -0
- package/plugins/heyspex/bin/run-hook.mjs +20 -2
- package/plugins/heyspex/hooks/cursor-hooks.json +81 -0
- package/plugins/heyspex/hooks/hooks.json +94 -37
- package/scripts/claude-hooks/_intent-classifier-router.mjs +456 -410
- package/scripts/claude-hooks/_intent-classifier-semantic.mjs +5 -0
- package/scripts/claude-hooks/_run.mjs +316 -23
- package/scripts/claude-hooks/capture-execution-gate.mjs +51 -0
- package/scripts/claude-hooks/claim-liveness-shared.mjs +2 -0
- package/scripts/claude-hooks/diagnose-capture-reminder.mjs +11 -0
- package/scripts/claude-hooks/directive-obligation-gate.mjs +11 -0
- package/scripts/claude-hooks/inject-communication-style.mjs +20 -0
- package/scripts/claude-hooks/intent-classifier-trigger.mjs +279 -247
- package/scripts/claude-hooks/session-end.mjs +2 -0
- package/scripts/claude-hooks/vault-mirror-catchup.mjs +11 -0
- package/scripts/claude-hooks/vault-write-tier-guard.mjs +11 -0
- package/scripts/claude-hooks/wip-checkpoint.mjs +5 -1
- package/scripts/cursor-hooks/manifest.mjs +46 -0
- package/scripts/heyspex-mode.mjs +20 -0
- package/scripts/hs-mode.mjs +1 -1
- package/scripts/hs-view.mjs +180 -17
- package/scripts/install-claude-code-hooks.mjs +3 -1
- package/scripts/lib/console-url-resolver.mjs +3 -2
- package/scripts/lib/customer-kit.mjs +327 -10
- package/scripts/lib/dev-mode.mjs +7 -6
- package/scripts/lib/directive-obligations.mjs +349 -0
- package/scripts/lib/hook-dispositions.mjs +210 -0
- package/scripts/lib/hook-vault-header.mjs +30 -0
- package/scripts/lib/hs-source-branch.mjs +196 -0
- package/scripts/lib/hs-source-log.mjs +2 -1
- package/scripts/lib/hs-source-preflight.mjs +18 -4
- package/scripts/lib/hs-source-push.mjs +386 -356
- package/scripts/lib/window-placement.mjs +109 -0
- package/spexharness/src/adapters/claude-code.mjs +11 -3
- package/spexharness/src/adapters/copilot.mjs +236 -0
- package/spexharness/src/adapters/detect-harness.mjs +34 -0
- package/spexharness/src/gates/_shell-write-targets.mjs +77 -0
- package/spexharness/src/gates/deep-research-cost-gate.mjs +4 -3
- package/spexharness/src/gates/diagnose-capture-reminder.mjs +112 -0
- package/spexharness/src/gates/directive-obligation-gate.decide.mjs +132 -0
- package/spexharness/src/gates/directive-obligation-gate.mjs +183 -0
- package/spexharness/src/gates/vault-mirror-catchup.mjs +124 -0
- package/spexharness/src/gates/vault-write-tier-guard.decide.mjs +125 -0
- package/spexharness/src/gates/vault-write-tier-guard.mjs +42 -0
- package/spexharness/src/intent-classifier.mjs +4 -2
- package/spexharness/src/intent-directive.mjs +626 -541
- package/spexharness/src/rule-table.mjs +31 -1
- package/vendor/heyspex-mcp/account-switch.mjs +6 -5
- package/vendor/heyspex-mcp/capture-buffer-patches.mjs +31 -3
- package/vendor/heyspex-mcp/capture-console-client.mjs +37 -2
- package/vendor/heyspex-mcp/capture-focus.mjs +26 -2
- package/vendor/heyspex-mcp/capture-outbox.mjs +10 -4
- package/vendor/heyspex-mcp/capture-sources.mjs +195 -0
- package/vendor/heyspex-mcp/capture-tools.mjs +56 -11
- package/vendor/heyspex-mcp/claim-tools.mjs +25 -2
- package/vendor/heyspex-mcp/cli-verbs.mjs +19 -21
- package/vendor/heyspex-mcp/cli.mjs +7 -1
- package/vendor/heyspex-mcp/close-branch-report.mjs +102 -0
- package/vendor/heyspex-mcp/cloud-vault-writes.mjs +197 -7
- package/vendor/heyspex-mcp/console-fetch.mjs +192 -28
- package/vendor/heyspex-mcp/console-transport.mjs +7 -1
- package/vendor/heyspex-mcp/credentials.mjs +9 -8
- package/vendor/heyspex-mcp/dev-tools.mjs +7 -0
- package/vendor/heyspex-mcp/filing-tools.mjs +35 -0
- package/vendor/heyspex-mcp/fleet-queue-tools.mjs +2 -1
- package/vendor/heyspex-mcp/folder-trust-store.mjs +2 -1
- package/vendor/heyspex-mcp/http-transport.mjs +2 -0
- package/vendor/heyspex-mcp/list-tools.mjs +1 -1
- package/vendor/heyspex-mcp/mcp-config.mjs +14 -1
- package/vendor/heyspex-mcp/memory-tools.mjs +523 -523
- package/vendor/heyspex-mcp/obligation-backstop.mjs +103 -0
- package/vendor/heyspex-mcp/occupied-keys.mjs +1 -0
- package/vendor/heyspex-mcp/on-deck-brief.mjs +19 -2
- package/vendor/heyspex-mcp/on-deck-scheduler.mjs +1708 -1708
- package/vendor/heyspex-mcp/plan-push.mjs +3 -2
- package/vendor/heyspex-mcp/preflight-gate.mjs +1 -0
- package/vendor/heyspex-mcp/procedure-handlers.mjs +327 -325
- package/vendor/heyspex-mcp/procedure-tools.mjs +1181 -1159
- package/vendor/heyspex-mcp/promote-capture.mjs +2 -0
- package/vendor/heyspex-mcp/promote-research.mjs +6 -0
- package/vendor/heyspex-mcp/read-path.mjs +162 -6
- package/vendor/heyspex-mcp/request-context.mjs +25 -1
- package/vendor/heyspex-mcp/research-handlers.mjs +6 -4
- package/vendor/heyspex-mcp/roadmap-default.mjs +70 -0
- package/vendor/heyspex-mcp/session-owner.mjs +3 -2
- package/vendor/heyspex-mcp/session-tools.mjs +10 -0
- package/vendor/heyspex-mcp/spec-edit-tools.mjs +8 -1
- package/vendor/heyspex-mcp/split-research.mjs +3 -0
- package/vendor/heyspex-mcp/submit-for-review.mjs +18 -1
- package/vendor/heyspex-mcp/summary-write-tools.mjs +5 -1
- package/vendor/heyspex-mcp/test-helpers.mjs +18 -3
- package/vendor/heyspex-mcp/tool-catalog.mjs +7 -1
- package/vendor/heyspex-mcp/tool-dispatch.mjs +18 -0
- package/vendor/heyspex-mcp/vault-io.mjs +720 -724
- package/vendor/heyspex-mcp/vault-resolve.mjs +197 -9
- package/vendor/heyspex-mcp/whoami.mjs +2 -1
- package/vendor/heyspex-mcp/write-outbox.mjs +512 -0
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "heyspex-marketplace",
|
|
3
|
+
"owner": {
|
|
4
|
+
"name": "pwher",
|
|
5
|
+
"email": "paul@monstergrowth.com"
|
|
6
|
+
},
|
|
7
|
+
"plugins": [
|
|
8
|
+
{
|
|
9
|
+
"name": "heyspex",
|
|
10
|
+
"source": "./plugins/heyspex",
|
|
11
|
+
"description": "SpexCode plugin (for Claude Code and GitHub Copilot CLI): HeySpex vault integration, MCP server (spec vault tools + hs-* prompts) and lifecycle hooks. Prototype — see plugins/heyspex/README.md before enabling."
|
|
12
|
+
}
|
|
13
|
+
]
|
|
14
|
+
}
|
package/bin/hs.mjs
CHANGED
|
@@ -25,7 +25,9 @@ import {
|
|
|
25
25
|
vaultConfigCheckRow,
|
|
26
26
|
} from "../scripts/lib/standalone-doctor.mjs";
|
|
27
27
|
import {
|
|
28
|
+
findBrokenSatellitePin,
|
|
28
29
|
isVaultRoot,
|
|
30
|
+
readMarkerSatellite,
|
|
29
31
|
readSatellitePin,
|
|
30
32
|
resolveActiveVault,
|
|
31
33
|
} from "../vendor/heyspex-mcp/vault-resolve.mjs";
|
|
@@ -85,6 +87,7 @@ export const SPAWN_TARGETS = {
|
|
|
85
87
|
"source:preflight": { path: "scripts/lib/hs-source-preflight.mjs" },
|
|
86
88
|
"source:commit": { path: "scripts/lib/hs-source-commit.mjs" },
|
|
87
89
|
"source:push": { path: "scripts/lib/hs-source-push.mjs" },
|
|
90
|
+
"source:branch": { path: "scripts/lib/hs-source-branch.mjs" },
|
|
88
91
|
"source:log": { path: "scripts/lib/hs-source-log.mjs" },
|
|
89
92
|
// `hs source identity` — the ONE sanctioned git-identity writer (human-only
|
|
90
93
|
// set/clear, read-only show). Incident 2026-09-19, vault lesson
|
|
@@ -175,6 +178,8 @@ export const BUILTIN_VERBS = [
|
|
|
175
178
|
"doctor",
|
|
176
179
|
"index",
|
|
177
180
|
"pull",
|
|
181
|
+
"push",
|
|
182
|
+
"import",
|
|
178
183
|
"milestone",
|
|
179
184
|
"factory",
|
|
180
185
|
"code-watcher",
|
|
@@ -286,6 +291,13 @@ export function findVaultRoot(start, stopAt) {
|
|
|
286
291
|
if (pinned) {
|
|
287
292
|
return pinned;
|
|
288
293
|
}
|
|
294
|
+
// A committed, portable satellite marker (heyspex.yaml with a relative
|
|
295
|
+
// `vault_path`): the attached vault's clone when it is on this machine,
|
|
296
|
+
// else this repo itself (cloud-only; the marker's vault_id binds it).
|
|
297
|
+
const marked = readMarkerSatellite(dir);
|
|
298
|
+
if (marked) {
|
|
299
|
+
return marked.vaultRootAbs ?? dir;
|
|
300
|
+
}
|
|
289
301
|
if (isVaultRoot(dir)) {
|
|
290
302
|
return dir;
|
|
291
303
|
}
|
|
@@ -331,6 +343,72 @@ export function findRegistryVaultRoot(options = {}) {
|
|
|
331
343
|
return resolved.repoRootAbs;
|
|
332
344
|
}
|
|
333
345
|
|
|
346
|
+
/** Env hatch: follow the account-wide active-vault pointer on purpose (same name the MCP transport uses). */
|
|
347
|
+
export function pointerVaultAllowed(env = process.env) {
|
|
348
|
+
const v = String(env.HEYSPEX_ALLOW_POINTER_VAULT ?? "").trim().toLowerCase();
|
|
349
|
+
return v === "1" || v === "true" || v === "yes";
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Verbs that may legitimately run with only the machine's last-active vault
|
|
354
|
+
* (or none): they onboard, sign in, or manage the registry itself. EVERY other
|
|
355
|
+
* verb acts on a vault, and P1 (owner, 2026-10-16) says that vault is the one
|
|
356
|
+
* bound to the repo the agent runs in -- never the account-wide pointer.
|
|
357
|
+
*/
|
|
358
|
+
const POINTER_FALLBACK_NAMESPACES = new Set([
|
|
359
|
+
"init", "install", "join", "setup", "skills", "vault", "workspace",
|
|
360
|
+
"login", "whoami", "logout", "doctor", "registry", "companion", "trust",
|
|
361
|
+
]);
|
|
362
|
+
|
|
363
|
+
/** True when `hs <namespace> <command>` may run on the pointer-only fallback. */
|
|
364
|
+
export function pointerFallbackAllowedFor(namespace, command) {
|
|
365
|
+
if (namespace === "mcp") return command === "pair" || command === "login" || command === "install";
|
|
366
|
+
return POINTER_FALLBACK_NAMESPACES.has(namespace);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/** The refusal printed when a satellite's pin / marker cannot be honoured. */
|
|
370
|
+
export function brokenPinRefusalLine(broken) {
|
|
371
|
+
return (
|
|
372
|
+
`hs: this repo's vault pin cannot be honoured, so nothing was run. ${broken.dir}/${broken.source} ` +
|
|
373
|
+
`names \`${broken.raw}\`, but ${broken.reason}. ` +
|
|
374
|
+
"Fix the pin (a relative path such as ../<vault-repo> or ${CLAUDE_PROJECT_DIR:-.}/../<vault-repo> is portable), " +
|
|
375
|
+
"or commit a heyspex.yaml with vault_id and vault_path; hs will NOT fall back to the account-wide active vault.\n"
|
|
376
|
+
);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/** Nearest directory at/above `start` whose heyspex.yaml is a satellite marker (has vault_path), else null. */
|
|
380
|
+
export function markerSatelliteDirFrom(start) {
|
|
381
|
+
let dir = path.resolve(start);
|
|
382
|
+
for (let i = 0; i < 256; i += 1) {
|
|
383
|
+
if (readMarkerSatellite(dir)) return dir;
|
|
384
|
+
const parent = path.dirname(dir);
|
|
385
|
+
if (parent === dir) return null;
|
|
386
|
+
dir = parent;
|
|
387
|
+
}
|
|
388
|
+
return null;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/** The refusal for disk-writing verbs in a satellite whose vault clone is missing. */
|
|
392
|
+
export function unclonedVaultRefusalLine(repo) {
|
|
393
|
+
const m = readMarkerSatellite(repo);
|
|
394
|
+
return (
|
|
395
|
+
`hs: ${repo} is bound to vault ${m?.vaultId ?? "(no vault_id)"} by its heyspex.yaml, but the vault ` +
|
|
396
|
+
`is not cloned at ${m?.vaultPath ?? "its vault_path"} on this machine, so this verb has no local vault to act on. ` +
|
|
397
|
+
"Clone the vault repo to that relative path (agents and cloud-routed MCP writes work without it).\n"
|
|
398
|
+
);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/** The refusal printed when only the account-wide pointer answered and the verb acts on a vault. */
|
|
402
|
+
export function pointerOnlyRefusalLine(cwd, vaultRoot) {
|
|
403
|
+
return (
|
|
404
|
+
`hs: ${cwd} is not bound to a HeySpex vault (no heyspex.yaml, no specs/, no vault pin in .mcp.json), ` +
|
|
405
|
+
`so \`hs\` refused to follow the account-wide active vault${vaultRoot ? ` (${vaultRoot})` : ""}. ` +
|
|
406
|
+
"Bind this repo: run `hs connect` in a vault repo, `hs install --attach-vault <id> --target <repo>` for a code repo, " +
|
|
407
|
+
"or commit a heyspex.yaml with vault_id and a relative vault_path. " +
|
|
408
|
+
"To follow the pointer on purpose set HEYSPEX_ALLOW_POINTER_VAULT=1.\n"
|
|
409
|
+
);
|
|
410
|
+
}
|
|
411
|
+
|
|
334
412
|
/**
|
|
335
413
|
* The vault-resolution ladder `main()` runs at the top of every invocation,
|
|
336
414
|
* factored out so it is unit-testable without spawning a child process
|
|
@@ -358,6 +436,12 @@ export function resolveVaultRootForMain(cwd, env, stopAt) {
|
|
|
358
436
|
if (env.HS_VAULT_ROOT) {
|
|
359
437
|
return { vaultRoot: env.HS_VAULT_ROOT, usedRegistryFallback: false };
|
|
360
438
|
}
|
|
439
|
+
// A pin that cannot be honoured is a REFUSAL, never a reason to fall
|
|
440
|
+
// through to the account-wide pointer (owner principle P1, 2026-10-16).
|
|
441
|
+
const brokenPin = findBrokenSatellitePin(cwd, stopAt);
|
|
442
|
+
if (brokenPin) {
|
|
443
|
+
return { vaultRoot: null, usedRegistryFallback: false, brokenPin };
|
|
444
|
+
}
|
|
361
445
|
const walkedRoot = findVaultRoot(cwd, stopAt);
|
|
362
446
|
if (walkedRoot) {
|
|
363
447
|
return { vaultRoot: walkedRoot, usedRegistryFallback: false };
|
|
@@ -488,8 +572,10 @@ function usage() {
|
|
|
488
572
|
" hs skills list [--json] List registered skills (built-ins first, locked)",
|
|
489
573
|
" hs index rebuild [--verbose] [--dry-run] Full idempotent rebuild of the index",
|
|
490
574
|
" hs index reconcile [--apply] [--cap-docs keep-out|index] Per-spec newest-wins between disk and cloud (reports unless --apply)",
|
|
491
|
-
" hs pull [--apply] [--only <id>] [--overwrite] Bring cloud-written specs down to the disk vault (reports unless --apply)",
|
|
492
|
-
" hs
|
|
575
|
+
" hs pull [--apply] [--only <id>] [--overwrite] Bring cloud-written specs down to the disk vault (reports unless --apply; --watch --push adds the disk -> cloud leg)",
|
|
576
|
+
" hs push [--apply] [--only <id>] [--force] Send local spec edits up to the cloud vault (reports unless --apply)",
|
|
577
|
+
" hs import <source-dir> [--folders a,b] [--plan f] [--dry-run] [--limit N] Import a doc tree as GROUNDED spec-import captures (never raw, never research)",
|
|
578
|
+
" hs source status|preflight|commit|push|branch Local git: tree status, path-scoped checks, commit, safe push, session branch",
|
|
493
579
|
" hs source identity [show|set|clear] Git author identity: show (read-only) / set, clear (HUMAN-ONLY)",
|
|
494
580
|
" hs milestone show <id> [--json] Show a milestone (flags needs-breakdown when epicless)",
|
|
495
581
|
" hs factory status|fleet|results [--json] Dev-Factory read-only status (fleet, routing, calibrations, results)",
|
|
@@ -858,10 +944,27 @@ function main(argv) {
|
|
|
858
944
|
const command = argv[1];
|
|
859
945
|
const rest = argv.slice(2);
|
|
860
946
|
|
|
861
|
-
const
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
)
|
|
947
|
+
const resolvedMain = resolveVaultRootForMain(process.cwd(), process.env);
|
|
948
|
+
const { usedRegistryFallback, brokenPin } = resolvedMain;
|
|
949
|
+
// P1: the pointer is not a binding. A verb that acts on a vault runs with
|
|
950
|
+
// NO vault (and is refused at the gate) rather than on whatever vault the
|
|
951
|
+
// console last selected; onboarding/registry verbs keep the old fallback.
|
|
952
|
+
const pointerOnly =
|
|
953
|
+
usedRegistryFallback &&
|
|
954
|
+
!pointerVaultAllowed(process.env) &&
|
|
955
|
+
!pointerFallbackAllowedFor(namespace, command);
|
|
956
|
+
const vaultRoot = pointerOnly ? null : resolvedMain.vaultRoot;
|
|
957
|
+
if (pointerOnly) {
|
|
958
|
+
process.stderr.write(pointerOnlyRefusalLine(process.cwd(), resolvedMain.vaultRoot));
|
|
959
|
+
}
|
|
960
|
+
if (
|
|
961
|
+
brokenPin &&
|
|
962
|
+
namespace !== "doctor" &&
|
|
963
|
+
!pointerFallbackAllowedFor(namespace, command)
|
|
964
|
+
) {
|
|
965
|
+
process.stderr.write(brokenPinRefusalLine(brokenPin));
|
|
966
|
+
process.exit(2);
|
|
967
|
+
}
|
|
865
968
|
|
|
866
969
|
// `hs init` writes the top-level heyspex.yaml vault marker AND scaffolds
|
|
867
970
|
// specs/ ([[HS-EPIC-AEHR-audit-export-heyspex-yaml-and-heyspex-retirement]]
|
|
@@ -920,7 +1023,9 @@ function main(argv) {
|
|
|
920
1023
|
dispatchSpexConsoleEntry(
|
|
921
1024
|
"hs-install.ts",
|
|
922
1025
|
argv,
|
|
923
|
-
|
|
1026
|
+
// In a committed-marker satellite the install target is the CODE repo
|
|
1027
|
+
// (MCP entry, hooks, commands), never the vault clone the marker names.
|
|
1028
|
+
markerSatelliteDirFrom(INVOKED_CWD) ?? (vaultRoot || process.cwd()),
|
|
924
1029
|
{
|
|
925
1030
|
HS_PKG_ROOT: PKG_ROOT,
|
|
926
1031
|
},
|
|
@@ -1197,6 +1302,18 @@ function main(argv) {
|
|
|
1197
1302
|
process.exit(1);
|
|
1198
1303
|
}
|
|
1199
1304
|
|
|
1305
|
+
// A committed satellite marker whose vault clone is NOT on this machine
|
|
1306
|
+
// resolves to the code repo itself (cloud-only binding). Verbs that write the
|
|
1307
|
+
// vault's files to disk must never scaffold specs/ into a code repo.
|
|
1308
|
+
if (
|
|
1309
|
+
["pull", "push", "index"].includes(namespace) &&
|
|
1310
|
+
readMarkerSatellite(vaultRoot) &&
|
|
1311
|
+
!existsSync(path.join(vaultRoot, "specs"))
|
|
1312
|
+
) {
|
|
1313
|
+
process.stderr.write(unclonedVaultRefusalLine(vaultRoot));
|
|
1314
|
+
process.exit(2);
|
|
1315
|
+
}
|
|
1316
|
+
|
|
1200
1317
|
if (namespace === "index" && command === "rebuild") {
|
|
1201
1318
|
dispatchSpexConsoleEntry("hs-index-rebuild.ts", rest, vaultRoot, {
|
|
1202
1319
|
...(vaultIdOverride ? { HS_VAULT_ID_OVERRIDE: vaultIdOverride } : {}),
|
|
@@ -1228,6 +1345,31 @@ function main(argv) {
|
|
|
1228
1345
|
return;
|
|
1229
1346
|
}
|
|
1230
1347
|
|
|
1348
|
+
// `hs import <source-dir>` — a doc tree becomes GROUNDED spec-import captures
|
|
1349
|
+
// in the cwd's vault (heyspex.yaml marker, never the account pointer): group-
|
|
1350
|
+
// intent pass, foundational-to-specific order, sequential Ground=Expand with
|
|
1351
|
+
// a running batch ledger, one intent-map doc, source links; never promotes
|
|
1352
|
+
// ([[HS-0GOV-43G9-CAP-the-spec-import-function-should-not-stop]]). Forwards
|
|
1353
|
+
// the full argv; the entrypoint drops the leading `import` token and resolves
|
|
1354
|
+
// a relative <source-dir> against HS_INVOKED_CWD.
|
|
1355
|
+
if (namespace === "import") {
|
|
1356
|
+
dispatchSpexConsoleEntry("hs-import.ts", argv, vaultRoot, {
|
|
1357
|
+
HS_INVOKED_CWD: INVOKED_CWD,
|
|
1358
|
+
...(vaultIdOverride ? { HS_VAULT_ID_OVERRIDE: vaultIdOverride } : {}),
|
|
1359
|
+
});
|
|
1360
|
+
return;
|
|
1361
|
+
}
|
|
1362
|
+
|
|
1363
|
+
// `hs push` — disk -> cloud (SpexRelay local leg, HS-FCNH). Report-only
|
|
1364
|
+
// unless --apply; never creates specs, never pushes status, refuses over a
|
|
1365
|
+
// newer cloud copy. Forwards the full argv; the entrypoint drops `push`.
|
|
1366
|
+
if (namespace === "push") {
|
|
1367
|
+
dispatchSpexConsoleEntry("hs-vault-push.ts", argv, vaultRoot, {
|
|
1368
|
+
...(vaultIdOverride ? { HS_VAULT_ID_OVERRIDE: vaultIdOverride } : {}),
|
|
1369
|
+
});
|
|
1370
|
+
return;
|
|
1371
|
+
}
|
|
1372
|
+
|
|
1231
1373
|
// `hs milestone show <id>` — print a milestone, flagging needs-breakdown when
|
|
1232
1374
|
// it has no epics ([[HS-TASK-HMSE]] / [[HS-EPIC-MNBD]]). Forwards the full
|
|
1233
1375
|
// argv; the entrypoint drops the leading `milestone` token.
|
|
@@ -1357,7 +1499,9 @@ function main(argv) {
|
|
|
1357
1499
|
" hs source status Read-only tree status + path-scoped stage plan\n" +
|
|
1358
1500
|
" hs source preflight Alias of status\n" +
|
|
1359
1501
|
" hs source commit Stage + commit per the preflight plan\n" +
|
|
1360
|
-
" hs source push
|
|
1502
|
+
" hs source push [branch] Push the committed branch (no branch: the shared session-branch resolver's target)\n" +
|
|
1503
|
+
" hs source branch declare [<branch>] | show\n" +
|
|
1504
|
+
" Declare the branch this session works on (cloud spec saves then land on it) / show the target\n" +
|
|
1361
1505
|
" hs source log <spec-id> Commit-history lookup for a spec (console settlement,\n" +
|
|
1362
1506
|
" falling back to local git log)\n" +
|
|
1363
1507
|
" hs source identity [show] [--json]\n" +
|
|
@@ -1378,6 +1522,7 @@ function main(argv) {
|
|
|
1378
1522
|
preflight: "source:preflight",
|
|
1379
1523
|
commit: "source:commit",
|
|
1380
1524
|
push: "source:push",
|
|
1525
|
+
branch: "source:branch",
|
|
1381
1526
|
log: "source:log",
|
|
1382
1527
|
identity: "source:identity",
|
|
1383
1528
|
};
|
|
@@ -1648,13 +1793,20 @@ function main(argv) {
|
|
|
1648
1793
|
process.exit(2);
|
|
1649
1794
|
}
|
|
1650
1795
|
const cliPath = spawnTargetPath("heyspex-mcp-cli");
|
|
1796
|
+
// The MCP engine resolves its vault by walking UP from ITS cwd. Launching
|
|
1797
|
+
// it with cwd = the HeySpex checkout made every `hs mcp call` read the
|
|
1798
|
+
// HeySpex vault no matter where the agent stood (found 2026-10-16 from
|
|
1799
|
+
// mlc-specs / mlc-app). Run it from the repo the caller is in; an explicit
|
|
1800
|
+
// HS_VAULT_ROOT keeps winning (the engine is started inside that vault).
|
|
1801
|
+
const mcpCwd = process.env.HS_VAULT_ROOT && vaultRoot ? vaultRoot : INVOKED_CWD;
|
|
1651
1802
|
const child = spawn(process.execPath, [cliPath, command, ...rest], {
|
|
1652
|
-
cwd:
|
|
1803
|
+
cwd: mcpCwd,
|
|
1653
1804
|
stdio: "inherit",
|
|
1654
1805
|
env: {
|
|
1655
1806
|
...process.env,
|
|
1656
|
-
|
|
1657
|
-
|
|
1807
|
+
CLAUDE_PROJECT_DIR: mcpCwd,
|
|
1808
|
+
HEYSPEX_VAULT_ROOT: vaultRoot ?? "",
|
|
1809
|
+
HS_VAULT_ROOT: vaultRoot ?? "",
|
|
1658
1810
|
// One-shot MCP tool — no stdio loop, no stderr brief-tier noise in PS.
|
|
1659
1811
|
HEYSPEX_DISABLE_STDIO_BOOTSTRAP: "1",
|
|
1660
1812
|
},
|
package/dist/cli/hs-check.mjs
CHANGED
|
@@ -5,11 +5,26 @@ var __esm = (fn, res) => function __init() {
|
|
|
5
5
|
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
6
6
|
};
|
|
7
7
|
|
|
8
|
+
// scripts/lib/heyspex-home.mjs
|
|
9
|
+
import { homedir } from "node:os";
|
|
10
|
+
function heyspexHome(env = process.env) {
|
|
11
|
+
const override = env?.HEYSPEX_HOME;
|
|
12
|
+
if (typeof override === "string" && override.trim().length > 0) {
|
|
13
|
+
return override.trim();
|
|
14
|
+
}
|
|
15
|
+
return homedir();
|
|
16
|
+
}
|
|
17
|
+
var init_heyspex_home = __esm({
|
|
18
|
+
"scripts/lib/heyspex-home.mjs"() {
|
|
19
|
+
"use strict";
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
|
|
8
23
|
// vendor/heyspex-mcp/credentials.mjs
|
|
9
24
|
import { mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
10
|
-
import os, { homedir } from "node:os";
|
|
25
|
+
import os, { homedir as homedir2 } from "node:os";
|
|
11
26
|
import { dirname as dirname2, join as join2, resolve as resolve2 } from "node:path";
|
|
12
|
-
function credentialsFilePath(homeDir =
|
|
27
|
+
function credentialsFilePath(homeDir = heyspexHome()) {
|
|
13
28
|
return join2(homeDir, ".heyspex", "credentials.yaml");
|
|
14
29
|
}
|
|
15
30
|
function parseMcpToken(raw) {
|
|
@@ -122,14 +137,14 @@ function readLegacyConfigAccountId(read, homeDir) {
|
|
|
122
137
|
const value = raw?.match(CONFIG_ACCOUNT_ID_RE)?.[1]?.trim() ?? "";
|
|
123
138
|
return value || null;
|
|
124
139
|
}
|
|
125
|
-
function readBearer({ env = process.env, homeDir =
|
|
140
|
+
function readBearer({ env = process.env, homeDir = heyspexHome(), readFile, accountId } = {}) {
|
|
126
141
|
const read = readFile ?? ((p) => readFileSync(p, "utf8"));
|
|
127
142
|
const credentialsText = readTextOrNull(read, credentialsFilePath(homeDir));
|
|
128
143
|
const named = typeof accountId === "string" && accountId.trim() ? accountId.trim() : null;
|
|
129
144
|
const legacyAccountId = named && parseCredentialsStore(credentialsText).schemaVersion === 1 ? readLegacyConfigAccountId(read, homeDir) : null;
|
|
130
145
|
return resolveBearer({ envToken: env?.[MCP_TOKEN_ENV], credentialsText, accountId: named, legacyAccountId });
|
|
131
146
|
}
|
|
132
|
-
function readCurrentAccountId({ homeDir =
|
|
147
|
+
function readCurrentAccountId({ homeDir = heyspexHome(), readFile } = {}) {
|
|
133
148
|
const read = readFile ?? ((p) => readFileSync(p, "utf8"));
|
|
134
149
|
const store = parseCredentialsStore(readTextOrNull(read, credentialsFilePath(homeDir)));
|
|
135
150
|
if (store.schemaVersion === 2 && store.currentAccountId && store.currentAccountId !== LEGACY_ACCOUNT_ID) {
|
|
@@ -142,6 +157,7 @@ var MCP_TOKEN_ENV, MCP_TOKEN_LINE_RE, CREDENTIALS_SCHEMA_VERSION, LEGACY_ACCOUNT
|
|
|
142
157
|
var init_credentials = __esm({
|
|
143
158
|
"vendor/heyspex-mcp/credentials.mjs"() {
|
|
144
159
|
"use strict";
|
|
160
|
+
init_heyspex_home();
|
|
145
161
|
MCP_TOKEN_ENV = "HEYSPEX_MCP_TOKEN";
|
|
146
162
|
MCP_TOKEN_LINE_RE = /^mcp_token:[ \t]*["']?([^"'\r\n#]+)/m;
|
|
147
163
|
CREDENTIALS_SCHEMA_VERSION = 2;
|
|
@@ -153,11 +169,11 @@ var init_credentials = __esm({
|
|
|
153
169
|
});
|
|
154
170
|
|
|
155
171
|
// scripts/lib/dev-mode.mjs
|
|
156
|
-
import { mkdirSync as
|
|
157
|
-
import { homedir as
|
|
158
|
-
import { join as
|
|
159
|
-
function devModeFilePath(homeDir =
|
|
160
|
-
return
|
|
172
|
+
import { mkdirSync as mkdirSync4, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "node:fs";
|
|
173
|
+
import { homedir as homedir7 } from "node:os";
|
|
174
|
+
import { join as join8 } from "node:path";
|
|
175
|
+
function devModeFilePath(homeDir = heyspexHome()) {
|
|
176
|
+
return join8(homeDir, ".heyspex", DEV_MODE_FILENAME);
|
|
161
177
|
}
|
|
162
178
|
function normalizeMode(raw) {
|
|
163
179
|
const v = String(raw ?? "").trim().toLowerCase();
|
|
@@ -181,7 +197,7 @@ function deriveSemantics(mode, reliance) {
|
|
|
181
197
|
vaultWriteMode: local ? "local" : "cloud"
|
|
182
198
|
};
|
|
183
199
|
}
|
|
184
|
-
function readLocalDevModeRecord({ homeDir =
|
|
200
|
+
function readLocalDevModeRecord({ homeDir = heyspexHome(), readFile = defaultReadFile } = {}) {
|
|
185
201
|
let raw;
|
|
186
202
|
try {
|
|
187
203
|
raw = readFile(devModeFilePath(homeDir));
|
|
@@ -211,7 +227,7 @@ function readLocalDevModeRecord({ homeDir = homedir6(), readFile = defaultReadFi
|
|
|
211
227
|
}
|
|
212
228
|
function resolveDevMode({
|
|
213
229
|
env = process.env,
|
|
214
|
-
homeDir =
|
|
230
|
+
homeDir = heyspexHome(),
|
|
215
231
|
readFile = defaultReadFile,
|
|
216
232
|
cloudSetting = void 0
|
|
217
233
|
} = {}) {
|
|
@@ -274,17 +290,18 @@ var CLOUD_CONSOLE_URL, LOCAL_CONSOLE_URL, DEV_MODE_FILENAME, defaultReadFile;
|
|
|
274
290
|
var init_dev_mode = __esm({
|
|
275
291
|
"scripts/lib/dev-mode.mjs"() {
|
|
276
292
|
"use strict";
|
|
293
|
+
init_heyspex_home();
|
|
277
294
|
init_credentials();
|
|
278
295
|
CLOUD_CONSOLE_URL = "https://app.heyspex.com";
|
|
279
296
|
LOCAL_CONSOLE_URL = "http://localhost:3000";
|
|
280
297
|
DEV_MODE_FILENAME = "dev-mode.json";
|
|
281
|
-
defaultReadFile = (p) =>
|
|
298
|
+
defaultReadFile = (p) => readFileSync7(p, "utf8");
|
|
282
299
|
}
|
|
283
300
|
});
|
|
284
301
|
|
|
285
302
|
// spex-console/bin/hs-check.ts
|
|
286
303
|
import { spawn, spawnSync } from "node:child_process";
|
|
287
|
-
import { existsSync as
|
|
304
|
+
import { existsSync as existsSync5, readFileSync as readFileSync9 } from "node:fs";
|
|
288
305
|
import path2 from "node:path";
|
|
289
306
|
import process3 from "node:process";
|
|
290
307
|
|
|
@@ -822,17 +839,19 @@ function hsCommand(args = "", options = {}) {
|
|
|
822
839
|
}
|
|
823
840
|
|
|
824
841
|
// vendor/heyspex-mcp/console-fetch.mjs
|
|
842
|
+
init_heyspex_home();
|
|
825
843
|
init_credentials();
|
|
826
|
-
import { readFileSync as
|
|
827
|
-
import { homedir as
|
|
828
|
-
import { join as
|
|
844
|
+
import { readFileSync as readFileSync5 } from "node:fs";
|
|
845
|
+
import { homedir as homedir5 } from "node:os";
|
|
846
|
+
import { join as join6 } from "node:path";
|
|
829
847
|
|
|
830
848
|
// vendor/heyspex-mcp/session-owner.mjs
|
|
849
|
+
init_heyspex_home();
|
|
831
850
|
init_credentials();
|
|
832
851
|
import { readFileSync as readFileSync2 } from "node:fs";
|
|
833
|
-
import { homedir as
|
|
852
|
+
import { homedir as homedir3 } from "node:os";
|
|
834
853
|
import { join as join3 } from "node:path";
|
|
835
|
-
function resolveSessionOwnerUncached({ env = process.env, homeDir =
|
|
854
|
+
function resolveSessionOwnerUncached({ env = process.env, homeDir = heyspexHome(), readFile } = {}) {
|
|
836
855
|
const fromEnv = typeof env?.HS_ACCOUNT_ID === "string" ? env.HS_ACCOUNT_ID.trim() : "";
|
|
837
856
|
if (fromEnv) return fromEnv;
|
|
838
857
|
return readCurrentAccountId({ homeDir, readFile });
|
|
@@ -862,10 +881,23 @@ function isVaultLessEndpoint(method, path3) {
|
|
|
862
881
|
});
|
|
863
882
|
}
|
|
864
883
|
|
|
884
|
+
// vendor/heyspex-mcp/write-outbox.mjs
|
|
885
|
+
import {
|
|
886
|
+
readdirSync,
|
|
887
|
+
readFileSync as readFileSync3,
|
|
888
|
+
writeFileSync as writeFileSync2,
|
|
889
|
+
mkdirSync as mkdirSync2,
|
|
890
|
+
existsSync as existsSync2,
|
|
891
|
+
renameSync as renameSync2,
|
|
892
|
+
rmSync
|
|
893
|
+
} from "node:fs";
|
|
894
|
+
import { basename, join as join4, isAbsolute as isAbsolute2 } from "node:path";
|
|
895
|
+
|
|
865
896
|
// vendor/heyspex-mcp/vault-resolve.mjs
|
|
866
|
-
|
|
867
|
-
import {
|
|
868
|
-
import {
|
|
897
|
+
init_heyspex_home();
|
|
898
|
+
import { existsSync as existsSync3, readFileSync as readFileSync4 } from "node:fs";
|
|
899
|
+
import { homedir as homedir4, tmpdir } from "node:os";
|
|
900
|
+
import { dirname as dirname3, join as join5, resolve as resolve3 } from "node:path";
|
|
869
901
|
|
|
870
902
|
// vendor/heyspex-mcp/console-fetch.mjs
|
|
871
903
|
var VAULT_NOT_REGISTERED = "VAULT_NOT_REGISTERED";
|
|
@@ -897,9 +929,9 @@ function isCloudVaultId(value) {
|
|
|
897
929
|
var MARKER_VAULT_ID_RE = /^vault_id:[ \t]*["']?([^"'\r\n#]+)/m;
|
|
898
930
|
function readMarkerVaultId(repoRootAbs, { readFile } = {}) {
|
|
899
931
|
if (typeof repoRootAbs !== "string" || !repoRootAbs.trim()) return null;
|
|
900
|
-
const read = readFile ?? ((p) =>
|
|
932
|
+
const read = readFile ?? ((p) => readFileSync5(p, "utf8"));
|
|
901
933
|
try {
|
|
902
|
-
const raw = read(
|
|
934
|
+
const raw = read(join6(repoRootAbs, "heyspex.yaml"));
|
|
903
935
|
const value = String(raw ?? "").match(MARKER_VAULT_ID_RE)?.[1]?.trim() ?? "";
|
|
904
936
|
return value || null;
|
|
905
937
|
} catch {
|
|
@@ -919,9 +951,18 @@ var SCOPE_REFUSAL_CODES = Object.freeze(["VAULT_SCOPE_MISMATCH", "ACCOUNT_MISMAT
|
|
|
919
951
|
init_credentials();
|
|
920
952
|
|
|
921
953
|
// spex-console/src/lib/vault-registry/registry-file.ts
|
|
922
|
-
import { existsSync as
|
|
923
|
-
import {
|
|
924
|
-
|
|
954
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync6, renameSync as renameSync3, writeFileSync as writeFileSync3 } from "node:fs";
|
|
955
|
+
import { dirname as dirname4, join as join7, resolve as resolve4 } from "node:path";
|
|
956
|
+
|
|
957
|
+
// spex-console/src/lib/heyspex-home.ts
|
|
958
|
+
import { homedir as homedir6 } from "node:os";
|
|
959
|
+
function heyspexHome2(env = process.env) {
|
|
960
|
+
const override = env?.HEYSPEX_HOME;
|
|
961
|
+
if (typeof override === "string" && override.trim().length > 0) {
|
|
962
|
+
return override.trim();
|
|
963
|
+
}
|
|
964
|
+
return homedir6();
|
|
965
|
+
}
|
|
925
966
|
|
|
926
967
|
// spex-console/src/lib/vault-registry/types.ts
|
|
927
968
|
var LEGACY_REGISTRY_ACCOUNT_ID = "legacy";
|
|
@@ -938,10 +979,10 @@ function emptyVaultRegistry() {
|
|
|
938
979
|
|
|
939
980
|
// spex-console/src/lib/vault-registry/registry-file.ts
|
|
940
981
|
function vaultRegistryDir(opts = {}) {
|
|
941
|
-
return opts.registryDir ??
|
|
982
|
+
return opts.registryDir ?? join7(heyspexHome2(), ".heyspex");
|
|
942
983
|
}
|
|
943
984
|
function vaultRegistryFilePath(opts = {}) {
|
|
944
|
-
return
|
|
985
|
+
return join7(vaultRegistryDir(opts), "registry.json");
|
|
945
986
|
}
|
|
946
987
|
function isNonEmptyString(v) {
|
|
947
988
|
return typeof v === "string" && v.trim().length > 0;
|
|
@@ -1042,9 +1083,9 @@ function parseVaultRegistryFile(json) {
|
|
|
1042
1083
|
}
|
|
1043
1084
|
function readRegistryJson(opts) {
|
|
1044
1085
|
const filePath = vaultRegistryFilePath(opts);
|
|
1045
|
-
if (!
|
|
1086
|
+
if (!existsSync4(filePath)) return { kind: "missing" };
|
|
1046
1087
|
try {
|
|
1047
|
-
const raw =
|
|
1088
|
+
const raw = readFileSync6(filePath, "utf8");
|
|
1048
1089
|
const json = JSON.parse(raw);
|
|
1049
1090
|
if (registryFileSchemaOf(json) === null) return { kind: "unreadable" };
|
|
1050
1091
|
return { kind: "ok", raw, json };
|
|
@@ -1319,10 +1360,11 @@ function resolveControlScopeFromMachine(vaultRoot, opts = {}) {
|
|
|
1319
1360
|
}
|
|
1320
1361
|
|
|
1321
1362
|
// scripts/lib/console-url-resolver.mjs
|
|
1363
|
+
init_heyspex_home();
|
|
1322
1364
|
init_dev_mode();
|
|
1323
|
-
import { readFileSync as
|
|
1324
|
-
import { homedir as
|
|
1325
|
-
import { join as
|
|
1365
|
+
import { readFileSync as readFileSync8 } from "node:fs";
|
|
1366
|
+
import { homedir as homedir8 } from "node:os";
|
|
1367
|
+
import { join as join9 } from "node:path";
|
|
1326
1368
|
var DEFAULT_CONSOLE_URL = LOCAL_CONSOLE_URL;
|
|
1327
1369
|
function trimTrailingSlash(url) {
|
|
1328
1370
|
return url.replace(/\/+$/, "");
|
|
@@ -1348,9 +1390,9 @@ function warnStaleLoopbackCredentialsOnce(credentialsUrl, derivedUrl, { log }) {
|
|
|
1348
1390
|
} catch {
|
|
1349
1391
|
}
|
|
1350
1392
|
}
|
|
1351
|
-
function readCredentialsConsoleUrl({ homeDir =
|
|
1393
|
+
function readCredentialsConsoleUrl({ homeDir = heyspexHome(), readFile = defaultReadFile2 } = {}) {
|
|
1352
1394
|
try {
|
|
1353
|
-
const raw = readFile(
|
|
1395
|
+
const raw = readFile(join9(homeDir, ".heyspex", "credentials.yaml"));
|
|
1354
1396
|
const match = String(raw ?? "").match(/^console_url:\s*["']?([^"'\r\n#]+)/m);
|
|
1355
1397
|
const value = match?.[1]?.trim();
|
|
1356
1398
|
return value && value.length > 0 ? trimTrailingSlash(value) : null;
|
|
@@ -1359,12 +1401,12 @@ function readCredentialsConsoleUrl({ homeDir = homedir7(), readFile = defaultRea
|
|
|
1359
1401
|
}
|
|
1360
1402
|
}
|
|
1361
1403
|
function defaultReadFile2(p) {
|
|
1362
|
-
return
|
|
1404
|
+
return readFileSync8(p, "utf8");
|
|
1363
1405
|
}
|
|
1364
1406
|
function readDiscoveryFileConsoleUrl(repoRoot, { readDiscovery = defaultReadFile2 } = {}) {
|
|
1365
1407
|
if (!repoRoot) return null;
|
|
1366
1408
|
try {
|
|
1367
|
-
const discovery = String(readDiscovery(
|
|
1409
|
+
const discovery = String(readDiscovery(join9(repoRoot, ".heyspex", "console-url")) ?? "").trim();
|
|
1368
1410
|
if (discovery.length > 0) return trimTrailingSlash(discovery);
|
|
1369
1411
|
} catch {
|
|
1370
1412
|
}
|
|
@@ -1392,7 +1434,7 @@ function resolveModeEnvelope({ env, homeDir }) {
|
|
|
1392
1434
|
function resolveConsoleUrl({
|
|
1393
1435
|
repoRoot,
|
|
1394
1436
|
env = process.env,
|
|
1395
|
-
homeDir =
|
|
1437
|
+
homeDir = heyspexHome(),
|
|
1396
1438
|
strict = false,
|
|
1397
1439
|
devMode,
|
|
1398
1440
|
log = (line) => process.stderr.write(line),
|
|
@@ -1489,10 +1531,10 @@ function manifestForSpecId(vaultRoot, specId) {
|
|
|
1489
1531
|
}
|
|
1490
1532
|
function resolveManifestPath(target, vaultRoot, invokedCwd) {
|
|
1491
1533
|
const literal = path2.resolve(invokedCwd, target);
|
|
1492
|
-
if (
|
|
1534
|
+
if (existsSync5(literal)) return literal;
|
|
1493
1535
|
if (target.endsWith(".json")) return null;
|
|
1494
1536
|
const byId = manifestForSpecId(vaultRoot, target);
|
|
1495
|
-
return
|
|
1537
|
+
return existsSync5(byId) ? byId : null;
|
|
1496
1538
|
}
|
|
1497
1539
|
function countTscErrors(output) {
|
|
1498
1540
|
const matches = output.match(/error TS\d+/g);
|
|
@@ -1506,12 +1548,12 @@ function resolveWindowsPosixShell() {
|
|
|
1506
1548
|
"C:\\Program Files\\Git\\bin\\bash.exe"
|
|
1507
1549
|
].filter((c) => typeof c === "string" && c.length > 0);
|
|
1508
1550
|
for (const candidate of candidates) {
|
|
1509
|
-
if (
|
|
1551
|
+
if (existsSync5(candidate)) return cachedPosixShell = candidate;
|
|
1510
1552
|
}
|
|
1511
1553
|
const where = spawnSync("where", ["bash"], { encoding: "utf8", windowsHide: true });
|
|
1512
1554
|
if (where.status === 0 && where.stdout) {
|
|
1513
1555
|
const hit = where.stdout.split(/\r?\n/).map((line) => line.trim()).filter(Boolean).find((line) => !/\\system32\\/i.test(line));
|
|
1514
|
-
if (hit &&
|
|
1556
|
+
if (hit && existsSync5(hit)) return cachedPosixShell = hit;
|
|
1515
1557
|
}
|
|
1516
1558
|
return cachedPosixShell = null;
|
|
1517
1559
|
}
|
|
@@ -1600,7 +1642,7 @@ async function main() {
|
|
|
1600
1642
|
if (process3.argv[3] === "judge") {
|
|
1601
1643
|
const pkgRoot = resolveCodeRoot({ vaultRoot });
|
|
1602
1644
|
const judgeScript = path2.join(pkgRoot, "dev-factory", "judge.mjs");
|
|
1603
|
-
if (!
|
|
1645
|
+
if (!existsSync5(judgeScript)) {
|
|
1604
1646
|
process3.stderr.write(
|
|
1605
1647
|
`hs check judge: script not found at ${judgeScript} (source checkout only \u2014 not available in a published install).
|
|
1606
1648
|
`
|
|
@@ -1642,7 +1684,7 @@ async function main() {
|
|
|
1642
1684
|
process3.exitCode = EXIT.NOT_FOUND;
|
|
1643
1685
|
return;
|
|
1644
1686
|
}
|
|
1645
|
-
const { manifest, error } = parseChecksManifest(
|
|
1687
|
+
const { manifest, error } = parseChecksManifest(readFileSync9(manifestPath, "utf-8"));
|
|
1646
1688
|
if (error) {
|
|
1647
1689
|
process3.stderr.write(`hs check: ${manifestPath}: ${error}
|
|
1648
1690
|
`);
|
|
@@ -1674,7 +1716,7 @@ async function main() {
|
|
|
1674
1716
|
process3.exitCode = reportExitCode(report);
|
|
1675
1717
|
}
|
|
1676
1718
|
async function reportVerdict(target, report, vaultRoot, unattended = false) {
|
|
1677
|
-
if (
|
|
1719
|
+
if (existsSync5(target) || target.endsWith(".json")) {
|
|
1678
1720
|
process3.stderr.write(
|
|
1679
1721
|
"hs check: --report needs a spec-id target (to identify the claim), not a manifest path.\n"
|
|
1680
1722
|
);
|
|
@@ -1682,7 +1724,7 @@ async function reportVerdict(target, report, vaultRoot, unattended = false) {
|
|
|
1682
1724
|
}
|
|
1683
1725
|
const consoleUrl = resolveSpexConsoleUrl(vaultRoot, (p) => {
|
|
1684
1726
|
try {
|
|
1685
|
-
return
|
|
1727
|
+
return readFileSync9(p, "utf-8");
|
|
1686
1728
|
} catch {
|
|
1687
1729
|
return null;
|
|
1688
1730
|
}
|