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
package/dist/cli/hs-connect.mjs
CHANGED
|
@@ -35,11 +35,26 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
35
35
|
mod
|
|
36
36
|
));
|
|
37
37
|
|
|
38
|
+
// scripts/lib/heyspex-home.mjs
|
|
39
|
+
import { homedir } from "node:os";
|
|
40
|
+
function heyspexHome(env = process.env) {
|
|
41
|
+
const override = env?.HEYSPEX_HOME;
|
|
42
|
+
if (typeof override === "string" && override.trim().length > 0) {
|
|
43
|
+
return override.trim();
|
|
44
|
+
}
|
|
45
|
+
return homedir();
|
|
46
|
+
}
|
|
47
|
+
var init_heyspex_home = __esm({
|
|
48
|
+
"scripts/lib/heyspex-home.mjs"() {
|
|
49
|
+
"use strict";
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
|
|
38
53
|
// vendor/heyspex-mcp/credentials.mjs
|
|
39
54
|
import { mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
40
|
-
import os, { homedir } from "node:os";
|
|
55
|
+
import os, { homedir as homedir2 } from "node:os";
|
|
41
56
|
import { dirname, join, resolve } from "node:path";
|
|
42
|
-
function credentialsFilePath(homeDir =
|
|
57
|
+
function credentialsFilePath(homeDir = heyspexHome()) {
|
|
43
58
|
return join(homeDir, ".heyspex", "credentials.yaml");
|
|
44
59
|
}
|
|
45
60
|
function parseMcpToken(raw) {
|
|
@@ -152,14 +167,14 @@ function readLegacyConfigAccountId(read, homeDir) {
|
|
|
152
167
|
const value = raw?.match(CONFIG_ACCOUNT_ID_RE)?.[1]?.trim() ?? "";
|
|
153
168
|
return value || null;
|
|
154
169
|
}
|
|
155
|
-
function readBearer({ env = process.env, homeDir =
|
|
170
|
+
function readBearer({ env = process.env, homeDir = heyspexHome(), readFile: readFile2, accountId } = {}) {
|
|
156
171
|
const read = readFile2 ?? ((p) => readFileSync(p, "utf8"));
|
|
157
172
|
const credentialsText = readTextOrNull(read, credentialsFilePath(homeDir));
|
|
158
173
|
const named = typeof accountId === "string" && accountId.trim() ? accountId.trim() : null;
|
|
159
174
|
const legacyAccountId = named && parseCredentialsStore(credentialsText).schemaVersion === 1 ? readLegacyConfigAccountId(read, homeDir) : null;
|
|
160
175
|
return resolveBearer({ envToken: env?.[MCP_TOKEN_ENV], credentialsText, accountId: named, legacyAccountId });
|
|
161
176
|
}
|
|
162
|
-
function readCurrentAccountId({ homeDir =
|
|
177
|
+
function readCurrentAccountId({ homeDir = heyspexHome(), readFile: readFile2 } = {}) {
|
|
163
178
|
const read = readFile2 ?? ((p) => readFileSync(p, "utf8"));
|
|
164
179
|
const store = parseCredentialsStore(readTextOrNull(read, credentialsFilePath(homeDir)));
|
|
165
180
|
if (store.schemaVersion === 2 && store.currentAccountId && store.currentAccountId !== LEGACY_ACCOUNT_ID) {
|
|
@@ -177,6 +192,7 @@ var MCP_TOKEN_ENV, MCP_TOKEN_LINE_RE, CREDENTIALS_SCHEMA_VERSION, LEGACY_ACCOUNT
|
|
|
177
192
|
var init_credentials = __esm({
|
|
178
193
|
"vendor/heyspex-mcp/credentials.mjs"() {
|
|
179
194
|
"use strict";
|
|
195
|
+
init_heyspex_home();
|
|
180
196
|
MCP_TOKEN_ENV = "HEYSPEX_MCP_TOKEN";
|
|
181
197
|
MCP_TOKEN_LINE_RE = /^mcp_token:[ \t]*["']?([^"'\r\n#]+)/m;
|
|
182
198
|
CREDENTIALS_SCHEMA_VERSION = 2;
|
|
@@ -189,9 +205,9 @@ var init_credentials = __esm({
|
|
|
189
205
|
|
|
190
206
|
// scripts/lib/dev-mode.mjs
|
|
191
207
|
import { mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
192
|
-
import { homedir as
|
|
208
|
+
import { homedir as homedir3 } from "node:os";
|
|
193
209
|
import { join as join2 } from "node:path";
|
|
194
|
-
function devModeFilePath(homeDir =
|
|
210
|
+
function devModeFilePath(homeDir = heyspexHome()) {
|
|
195
211
|
return join2(homeDir, ".heyspex", DEV_MODE_FILENAME);
|
|
196
212
|
}
|
|
197
213
|
function normalizeMode(raw) {
|
|
@@ -216,7 +232,7 @@ function deriveSemantics(mode, reliance) {
|
|
|
216
232
|
vaultWriteMode: local ? "local" : "cloud"
|
|
217
233
|
};
|
|
218
234
|
}
|
|
219
|
-
function readLocalDevModeRecord({ homeDir =
|
|
235
|
+
function readLocalDevModeRecord({ homeDir = heyspexHome(), readFile: readFile2 = defaultReadFile } = {}) {
|
|
220
236
|
let raw;
|
|
221
237
|
try {
|
|
222
238
|
raw = readFile2(devModeFilePath(homeDir));
|
|
@@ -246,7 +262,7 @@ function readLocalDevModeRecord({ homeDir = homedir2(), readFile: readFile2 = de
|
|
|
246
262
|
}
|
|
247
263
|
function resolveDevMode({
|
|
248
264
|
env = process.env,
|
|
249
|
-
homeDir =
|
|
265
|
+
homeDir = heyspexHome(),
|
|
250
266
|
readFile: readFile2 = defaultReadFile,
|
|
251
267
|
cloudSetting = void 0
|
|
252
268
|
} = {}) {
|
|
@@ -309,6 +325,7 @@ var CLOUD_CONSOLE_URL, LOCAL_CONSOLE_URL, DEV_MODE_FILENAME, defaultReadFile;
|
|
|
309
325
|
var init_dev_mode = __esm({
|
|
310
326
|
"scripts/lib/dev-mode.mjs"() {
|
|
311
327
|
"use strict";
|
|
328
|
+
init_heyspex_home();
|
|
312
329
|
init_credentials();
|
|
313
330
|
CLOUD_CONSOLE_URL = "https://app.heyspex.com";
|
|
314
331
|
LOCAL_CONSOLE_URL = "http://localhost:3000";
|
|
@@ -982,21 +999,22 @@ var require_gray_matter = __commonJS({
|
|
|
982
999
|
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
983
1000
|
import {
|
|
984
1001
|
copyFileSync,
|
|
985
|
-
existsSync as
|
|
986
|
-
mkdirSync as
|
|
987
|
-
readdirSync as
|
|
988
|
-
readFileSync as
|
|
1002
|
+
existsSync as existsSync6,
|
|
1003
|
+
mkdirSync as mkdirSync6,
|
|
1004
|
+
readdirSync as readdirSync3,
|
|
1005
|
+
readFileSync as readFileSync11,
|
|
989
1006
|
statSync,
|
|
990
|
-
writeFileSync as
|
|
1007
|
+
writeFileSync as writeFileSync6
|
|
991
1008
|
} from "node:fs";
|
|
992
1009
|
import path6 from "node:path";
|
|
993
1010
|
import process4 from "node:process";
|
|
994
1011
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
995
1012
|
|
|
996
1013
|
// scripts/lib/console-url-resolver.mjs
|
|
1014
|
+
init_heyspex_home();
|
|
997
1015
|
init_dev_mode();
|
|
998
1016
|
import { readFileSync as readFileSync3 } from "node:fs";
|
|
999
|
-
import { homedir as
|
|
1017
|
+
import { homedir as homedir4 } from "node:os";
|
|
1000
1018
|
import { join as join3 } from "node:path";
|
|
1001
1019
|
var DEFAULT_CONSOLE_URL = LOCAL_CONSOLE_URL;
|
|
1002
1020
|
function trimTrailingSlash(url) {
|
|
@@ -1023,7 +1041,7 @@ function warnStaleLoopbackCredentialsOnce(credentialsUrl, derivedUrl, { log }) {
|
|
|
1023
1041
|
} catch {
|
|
1024
1042
|
}
|
|
1025
1043
|
}
|
|
1026
|
-
function readCredentialsConsoleUrl({ homeDir =
|
|
1044
|
+
function readCredentialsConsoleUrl({ homeDir = heyspexHome(), readFile: readFile2 = defaultReadFile2 } = {}) {
|
|
1027
1045
|
try {
|
|
1028
1046
|
const raw = readFile2(join3(homeDir, ".heyspex", "credentials.yaml"));
|
|
1029
1047
|
const match = String(raw ?? "").match(/^console_url:\s*["']?([^"'\r\n#]+)/m);
|
|
@@ -1067,7 +1085,7 @@ function resolveModeEnvelope({ env, homeDir }) {
|
|
|
1067
1085
|
function resolveConsoleUrl({
|
|
1068
1086
|
repoRoot,
|
|
1069
1087
|
env = process.env,
|
|
1070
|
-
homeDir =
|
|
1088
|
+
homeDir = heyspexHome(),
|
|
1071
1089
|
strict = false,
|
|
1072
1090
|
devMode,
|
|
1073
1091
|
log = (line) => process.stderr.write(line),
|
|
@@ -1116,7 +1134,7 @@ function resolveCodeRoot({
|
|
|
1116
1134
|
}
|
|
1117
1135
|
|
|
1118
1136
|
// spex-console/src/lib/cli/connect.ts
|
|
1119
|
-
import { readdirSync } from "node:fs";
|
|
1137
|
+
import { readdirSync as readdirSync2 } from "node:fs";
|
|
1120
1138
|
|
|
1121
1139
|
// dist/cli/._sql-orm-inert.cli-shim.mjs
|
|
1122
1140
|
var inert = new Proxy(function inert2() {
|
|
@@ -1153,14 +1171,24 @@ var uniqueIndex = inert;
|
|
|
1153
1171
|
|
|
1154
1172
|
// spex-console/src/lib/cli/vault-workspace-shared.ts
|
|
1155
1173
|
import { readFileSync as readFileSync5 } from "node:fs";
|
|
1156
|
-
|
|
1174
|
+
|
|
1175
|
+
// spex-console/src/lib/heyspex-home.ts
|
|
1176
|
+
import { homedir as homedir5 } from "node:os";
|
|
1177
|
+
function heyspexHome2(env = process.env) {
|
|
1178
|
+
const override = env?.HEYSPEX_HOME;
|
|
1179
|
+
if (typeof override === "string" && override.trim().length > 0) {
|
|
1180
|
+
return override.trim();
|
|
1181
|
+
}
|
|
1182
|
+
return homedir5();
|
|
1183
|
+
}
|
|
1157
1184
|
|
|
1158
1185
|
// vendor/heyspex-mcp/session-owner.mjs
|
|
1186
|
+
init_heyspex_home();
|
|
1159
1187
|
init_credentials();
|
|
1160
1188
|
import { readFileSync as readFileSync4 } from "node:fs";
|
|
1161
|
-
import { homedir as
|
|
1189
|
+
import { homedir as homedir6 } from "node:os";
|
|
1162
1190
|
import { join as join4 } from "node:path";
|
|
1163
|
-
function resolveSessionOwnerUncached({ env = process.env, homeDir =
|
|
1191
|
+
function resolveSessionOwnerUncached({ env = process.env, homeDir = heyspexHome(), readFile: readFile2 } = {}) {
|
|
1164
1192
|
const fromEnv = typeof env?.HS_ACCOUNT_ID === "string" ? env.HS_ACCOUNT_ID.trim() : "";
|
|
1165
1193
|
if (fromEnv) return fromEnv;
|
|
1166
1194
|
return readCurrentAccountId({ homeDir, readFile: readFile2 });
|
|
@@ -1189,7 +1217,7 @@ function resolveOfflineDefaultWorkspace(accountId, localWorkspaceIds) {
|
|
|
1189
1217
|
function resolveConfiguredAccountId(opts = {}) {
|
|
1190
1218
|
return resolveSessionOwnerUncached({
|
|
1191
1219
|
env: opts.env ?? process.env,
|
|
1192
|
-
homeDir: opts.homeDir ??
|
|
1220
|
+
homeDir: opts.homeDir ?? heyspexHome2(),
|
|
1193
1221
|
readFile: opts.readFile ?? ((p) => readFileSync5(p, "utf-8"))
|
|
1194
1222
|
});
|
|
1195
1223
|
}
|
|
@@ -1741,7 +1769,13 @@ var specEventKindEnum = pgEnum("spec_event_kind", [
|
|
|
1741
1769
|
"priority_change",
|
|
1742
1770
|
"parent_change",
|
|
1743
1771
|
"claim_acquired",
|
|
1744
|
-
"claim_released"
|
|
1772
|
+
"claim_released",
|
|
1773
|
+
// Collaborator write audit (HS-FWB6, migration 0077).
|
|
1774
|
+
"spec_created",
|
|
1775
|
+
"spec_edited",
|
|
1776
|
+
"spec_deleted",
|
|
1777
|
+
"capture_written",
|
|
1778
|
+
"wip_recorded"
|
|
1745
1779
|
]);
|
|
1746
1780
|
var focusPin = pgTable(
|
|
1747
1781
|
"focus_pin",
|
|
@@ -2934,11 +2968,43 @@ var specSettlements = pgTable(
|
|
|
2934
2968
|
// `owner/name`
|
|
2935
2969
|
branch: text("branch").notNull(),
|
|
2936
2970
|
commitSha: text("commit_sha").notNull(),
|
|
2937
|
-
settledAt: timestamp("settled_at", { withTimezone: true }).notNull().default(sql`now()`)
|
|
2971
|
+
settledAt: timestamp("settled_at", { withTimezone: true }).notNull().default(sql`now()`),
|
|
2972
|
+
// The pull request that carries this settlement
|
|
2973
|
+
// ([[HS-9D25-CWPX-EPIC-branch-lifecycle-open-a-pull-request]]). NULL until
|
|
2974
|
+
// the branch has gone quiet and a PR was opened or found; a write made
|
|
2975
|
+
// after its PR merged stays NULL, which is what triggers the next PR.
|
|
2976
|
+
pullRequestNumber: integer("pull_request_number"),
|
|
2977
|
+
pullRequestUrl: text("pull_request_url")
|
|
2938
2978
|
},
|
|
2939
2979
|
(table) => [
|
|
2940
2980
|
// "latest settlement for this spec" — the read path's only query.
|
|
2941
|
-
index("spec_settlements_spec_settled_idx").on(table.specId, table.settledAt.desc())
|
|
2981
|
+
index("spec_settlements_spec_settled_idx").on(table.specId, table.settledAt.desc()),
|
|
2982
|
+
// The PR sweep's only query: branches with settlements not yet in a PR.
|
|
2983
|
+
index("spec_settlements_unproposed_idx").on(table.vaultId, table.repo, table.branch).where(sql`${table.pullRequestNumber} IS NULL`)
|
|
2984
|
+
]
|
|
2985
|
+
);
|
|
2986
|
+
var sessionBranchDeclarations = pgTable(
|
|
2987
|
+
"session_branch_declarations",
|
|
2988
|
+
{
|
|
2989
|
+
id: integer("id").primaryKey().generatedAlwaysAsIdentity(),
|
|
2990
|
+
vaultId: text("vault_id").notNull().default(DEFAULT_VAULT_ID),
|
|
2991
|
+
actorKey: text("actor_key").notNull(),
|
|
2992
|
+
sessionId: text("session_id").notNull(),
|
|
2993
|
+
branch: text("branch").notNull(),
|
|
2994
|
+
declaredAt: timestamp("declared_at", { withTimezone: true }).notNull().default(sql`now()`),
|
|
2995
|
+
expiresAt: timestamp("expires_at", { withTimezone: true }).notNull()
|
|
2996
|
+
},
|
|
2997
|
+
(table) => [
|
|
2998
|
+
uniqueIndex("session_branch_declarations_uq").on(
|
|
2999
|
+
table.vaultId,
|
|
3000
|
+
table.actorKey,
|
|
3001
|
+
table.sessionId
|
|
3002
|
+
),
|
|
3003
|
+
index("session_branch_declarations_lookup_idx").on(
|
|
3004
|
+
table.vaultId,
|
|
3005
|
+
table.actorKey,
|
|
3006
|
+
table.declaredAt.desc()
|
|
3007
|
+
)
|
|
2942
3008
|
]
|
|
2943
3009
|
);
|
|
2944
3010
|
|
|
@@ -3031,7 +3097,6 @@ async function runNeonReachabilityProbe(attempt, options2 = {}, label = "Neon re
|
|
|
3031
3097
|
|
|
3032
3098
|
// spex-console/src/lib/vault-registry/registry-file.ts
|
|
3033
3099
|
import { existsSync, mkdirSync as mkdirSync3, readFileSync as readFileSync6, renameSync as renameSync2, writeFileSync as writeFileSync3 } from "node:fs";
|
|
3034
|
-
import { homedir as homedir5 } from "node:os";
|
|
3035
3100
|
import { dirname as dirname2, join as join5, resolve as resolve3 } from "node:path";
|
|
3036
3101
|
|
|
3037
3102
|
// spex-console/src/lib/vault-registry/types.ts
|
|
@@ -3049,7 +3114,7 @@ function emptyVaultRegistry() {
|
|
|
3049
3114
|
|
|
3050
3115
|
// spex-console/src/lib/vault-registry/registry-file.ts
|
|
3051
3116
|
function vaultRegistryDir(opts = {}) {
|
|
3052
|
-
return opts.registryDir ?? join5(
|
|
3117
|
+
return opts.registryDir ?? join5(heyspexHome2(), ".heyspex");
|
|
3053
3118
|
}
|
|
3054
3119
|
function vaultRegistryFilePath(opts = {}) {
|
|
3055
3120
|
return join5(vaultRegistryDir(opts), "registry.json");
|
|
@@ -3219,7 +3284,7 @@ function samePathLoose(a, b) {
|
|
|
3219
3284
|
}
|
|
3220
3285
|
function assertNotRealHomeRegistryUnderTest(filePath) {
|
|
3221
3286
|
if (!underTestRunner()) return;
|
|
3222
|
-
if (samePathLoose(dirname2(filePath), join5(
|
|
3287
|
+
if (samePathLoose(dirname2(filePath), join5(heyspexHome2(), ".heyspex"))) {
|
|
3223
3288
|
throw new Error(
|
|
3224
3289
|
`registry-file: refusing to write ${filePath} under a test runner \u2014 that is the real ~/.heyspex registry. Pass opts.registryDir (a temp dir).`
|
|
3225
3290
|
);
|
|
@@ -3669,12 +3734,17 @@ async function listWorkspacesNeon(accountId, deps = {}) {
|
|
|
3669
3734
|
id: workspaces.id,
|
|
3670
3735
|
displayName: workspaces.displayName,
|
|
3671
3736
|
githubOrg: workspaces.githubOrg,
|
|
3672
|
-
createdAt: workspaces.createdAt
|
|
3737
|
+
createdAt: workspaces.createdAt,
|
|
3738
|
+
archived: workspaces.archived
|
|
3673
3739
|
}).from(workspaces).where(eq(workspaces.accountId, accountId));
|
|
3674
3740
|
return rows.map((r) => ({
|
|
3675
3741
|
id: r.id,
|
|
3676
3742
|
display_name: r.displayName,
|
|
3677
3743
|
created_at: (r.createdAt instanceof Date ? r.createdAt : /* @__PURE__ */ new Date()).toISOString(),
|
|
3744
|
+
// The flag `setWorkspaceArchivedNeon` writes; `list.ts` filters on it.
|
|
3745
|
+
// On a cloud host this row IS the roster (no registry.json to carry the
|
|
3746
|
+
// flag), so leaving it out made an archived workspace list forever.
|
|
3747
|
+
archived: r.archived,
|
|
3678
3748
|
...r.githubOrg ? { github_org: r.githubOrg } : {}
|
|
3679
3749
|
}));
|
|
3680
3750
|
} catch {
|
|
@@ -3961,7 +4031,134 @@ async function createWorkspace(input, deps = {}) {
|
|
|
3961
4031
|
import { createHash } from "node:crypto";
|
|
3962
4032
|
import fs from "node:fs";
|
|
3963
4033
|
import path2 from "node:path";
|
|
4034
|
+
import os2 from "node:os";
|
|
3964
4035
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
4036
|
+
|
|
4037
|
+
// scripts/lib/hook-dispositions.mjs
|
|
4038
|
+
var HOSTS = Object.freeze(["claude-code", "cursor", "copilot"]);
|
|
4039
|
+
var DISPOSITIONS = Object.freeze(["mapped", "advisory-degraded", "unportable", "not-shipped"]);
|
|
4040
|
+
var mapped = (note) => Object.freeze({ disposition: "mapped", ...note ? { note } : {} });
|
|
4041
|
+
var degraded = (note) => Object.freeze({ disposition: "advisory-degraded", note });
|
|
4042
|
+
var unportable = (control) => Object.freeze({ disposition: "unportable", control });
|
|
4043
|
+
var notShipped = Object.freeze({ disposition: "not-shipped" });
|
|
4044
|
+
var CC = mapped();
|
|
4045
|
+
var COPILOT_PROMPT_DROPPED = "Copilot drops command-hook output on userPromptSubmitted (no prompt-time context channel); the text moves to sessionStart / .github/copilot-instructions.md.";
|
|
4046
|
+
var CURSOR_PROMPT_USER_MESSAGE = "Cursor beforeSubmitPrompt honors user_message only (no additional_context): the text surfaces in the transcript instead of as invisible context.";
|
|
4047
|
+
var COPILOT_NO_PLAN_TOOL = "MCP preflight refusals (heyspex_claim_spec / heyspex_submit_for_review / heyspex_save_research) + .github/copilot-instructions.md plan rule (state the plan in chat, claim the spec before editing) + HS-20SW directive-obligation-gate claim-before-build deny";
|
|
4048
|
+
var HOOK_DISPOSITIONS = Object.freeze({
|
|
4049
|
+
// ── customer: session start / compaction ──────────────────────────────────
|
|
4050
|
+
"wip-checkpoint": {
|
|
4051
|
+
"claude-code": CC,
|
|
4052
|
+
cursor: mapped("preCompact is relayed as SessionStart(compact)."),
|
|
4053
|
+
copilot: mapped("sessionStart additionalContext is delivered; the preCompact leg writes WIP only (no output channel), re-injected at the next sessionStart.")
|
|
4054
|
+
},
|
|
4055
|
+
"inject-doctrine": { "claude-code": CC, cursor: mapped(), copilot: mapped("sessionStart additionalContext.") },
|
|
4056
|
+
"check-auto-memory-budget": { "claude-code": CC, cursor: mapped(), copilot: mapped("sessionStart additionalContext.") },
|
|
4057
|
+
"check-claude-md-budget": { "claude-code": CC, cursor: mapped(), copilot: mapped("sessionStart additionalContext.") },
|
|
4058
|
+
"git-reflog-audit": { "claude-code": CC, cursor: mapped(), copilot: mapped("sessionStart additionalContext and agentStop.") },
|
|
4059
|
+
"vault-mirror-catchup": { "claude-code": CC, cursor: mapped(), copilot: mapped("Detached side effect on sessionStart.") },
|
|
4060
|
+
// ── customer: every user turn ─────────────────────────────────────────────
|
|
4061
|
+
"preflight-reminder": {
|
|
4062
|
+
"claude-code": CC,
|
|
4063
|
+
cursor: degraded(CURSOR_PROMPT_USER_MESSAGE),
|
|
4064
|
+
copilot: degraded(`${COPILOT_PROMPT_DROPPED} Real enforcement is the MCP preflight refusal.`)
|
|
4065
|
+
},
|
|
4066
|
+
"intent-classifier-trigger": {
|
|
4067
|
+
"claude-code": CC,
|
|
4068
|
+
cursor: degraded(CURSOR_PROMPT_USER_MESSAGE),
|
|
4069
|
+
copilot: degraded(
|
|
4070
|
+
`${COPILOT_PROMPT_DROPPED} The hook still runs and arms obligation state; the HS-20SW directive-obligation-gate turns that state into a hard deny.`
|
|
4071
|
+
)
|
|
4072
|
+
},
|
|
4073
|
+
"inject-communication-style": {
|
|
4074
|
+
"claude-code": CC,
|
|
4075
|
+
cursor: degraded(CURSOR_PROMPT_USER_MESSAGE),
|
|
4076
|
+
copilot: degraded(COPILOT_PROMPT_DROPPED)
|
|
4077
|
+
},
|
|
4078
|
+
"inject-vault-context": {
|
|
4079
|
+
"claude-code": CC,
|
|
4080
|
+
cursor: degraded(CURSOR_PROMPT_USER_MESSAGE),
|
|
4081
|
+
copilot: degraded(`${COPILOT_PROMPT_DROPPED} Per-prompt vault retrieval has no Copilot channel at all.`)
|
|
4082
|
+
},
|
|
4083
|
+
// ── customer: before a tool call (hard gates) ─────────────────────────────
|
|
4084
|
+
"git-identity-guard": { "claude-code": CC, cursor: mapped("beforeShellExecution. Always on."), copilot: mapped("preToolUse on bash/powershell. Always on.") },
|
|
4085
|
+
"vault-write-tier-guard": { "claude-code": CC, cursor: mapped(), copilot: mapped("preToolUse on create/edit/bash/powershell.") },
|
|
4086
|
+
"settings-write-gate": { "claude-code": CC, cursor: mapped(), copilot: mapped("preToolUse on create/edit/bash/powershell and the MCP settings tool.") },
|
|
4087
|
+
"emit-claim-status": { "claude-code": CC, cursor: mapped(), copilot: mapped("Telemetry on preToolUse/postToolUse/agentStop; the cloud agent needs a network allow rule for the console.") },
|
|
4088
|
+
"enforce-vault-naming": { "claude-code": CC, cursor: mapped(), copilot: mapped("preToolUse on create/bash/powershell.") },
|
|
4089
|
+
"guard-auto-memory-writes": { "claude-code": CC, cursor: mapped(), copilot: mapped("preToolUse on create/bash/powershell.") },
|
|
4090
|
+
"directive-obligation-gate": {
|
|
4091
|
+
"claude-code": CC,
|
|
4092
|
+
cursor: mapped(),
|
|
4093
|
+
copilot: mapped("HS-20SW: hard deny on task/create/edit/write-shaped shell while a capture obligation is unmet; the only hard capture-on-idea guarantee on Copilot.")
|
|
4094
|
+
},
|
|
4095
|
+
"claim-before-code-gate": {
|
|
4096
|
+
"claude-code": CC,
|
|
4097
|
+
cursor: degraded("Armed only by plan-exit-ingest-gate, which has no Cursor trigger, so it stays dormant; covered by the HS-20SW claim-before-build rule."),
|
|
4098
|
+
copilot: degraded("Armed only by plan-exit-ingest-gate, which has no Copilot trigger (no ExitPlanMode tool), so it stays dormant; covered by the HS-20SW claim-before-build rule.")
|
|
4099
|
+
},
|
|
4100
|
+
"capture-workflow-gate": {
|
|
4101
|
+
"claude-code": CC,
|
|
4102
|
+
cursor: mapped(),
|
|
4103
|
+
copilot: mapped("preToolUse on the MCP heyspex_file_spec tool; MCP tool naming in hook payloads is unverified live (matched by suffix).")
|
|
4104
|
+
},
|
|
4105
|
+
"diagnose-capture-reminder": {
|
|
4106
|
+
"claude-code": CC,
|
|
4107
|
+
cursor: mapped("preToolUse agent_message delivers the reminder."),
|
|
4108
|
+
copilot: degraded("Copilot preToolUse has no context channel for an inject, so this advisory is silent (lost).")
|
|
4109
|
+
},
|
|
4110
|
+
"git-stash-gate": {
|
|
4111
|
+
"claude-code": CC,
|
|
4112
|
+
cursor: mapped(),
|
|
4113
|
+
copilot: mapped('The Claude "ask" becomes a deny where no user is present (cloud agent, CLI autopilot).')
|
|
4114
|
+
},
|
|
4115
|
+
"research-protocol-gate": { "claude-code": CC, cursor: mapped("Always on."), copilot: mapped("preToolUse on web_fetch/web_search and the MCP save_research tool. Always on; the MCP refusal duplicates it.") },
|
|
4116
|
+
"research-method-guard": {
|
|
4117
|
+
"claude-code": CC,
|
|
4118
|
+
cursor: mapped("preToolUse with a Skill|Workflow tool filter. Always on."),
|
|
4119
|
+
copilot: unportable("MCP preflight refusal on heyspex_save_research (hard floor, hook-independent) + .github/copilot-instructions.md research rule; Copilot has no Skill or Workflow tool to hook.")
|
|
4120
|
+
},
|
|
4121
|
+
"deep-research-cost-gate": {
|
|
4122
|
+
"claude-code": CC,
|
|
4123
|
+
cursor: mapped("preToolUse with a Workflow|Skill tool filter."),
|
|
4124
|
+
copilot: unportable("MCP preflight refusal on heyspex_save_research + .github/copilot-instructions.md cost-confirmation rule; Copilot has no Skill or Workflow tool to hook.")
|
|
4125
|
+
},
|
|
4126
|
+
"plan-review-gate": {
|
|
4127
|
+
"claude-code": CC,
|
|
4128
|
+
cursor: unportable(".cursor/rules/plan-review-required.mdc (always-on rule)"),
|
|
4129
|
+
copilot: unportable(COPILOT_NO_PLAN_TOOL)
|
|
4130
|
+
},
|
|
4131
|
+
"plan-exit-ingest-gate": {
|
|
4132
|
+
"claude-code": CC,
|
|
4133
|
+
cursor: unportable(".cursor/rules/plan-review-required.mdc + spec-creation-linkage.mdc"),
|
|
4134
|
+
copilot: unportable(COPILOT_NO_PLAN_TOOL)
|
|
4135
|
+
},
|
|
4136
|
+
// ── customer: end of turn / session ───────────────────────────────────────
|
|
4137
|
+
"naughty-list-gate": { "claude-code": CC, cursor: mapped("stop followup_message re-drive."), copilot: mapped('agentStop {decision:"block"} re-drive, capped at 8 consecutive blocks.') },
|
|
4138
|
+
"id-display-format-gate": { "claude-code": CC, cursor: mapped("stop followup_message re-drive."), copilot: mapped("agentStop re-drive; reads the transcript (format is not a stable API).") },
|
|
4139
|
+
"epic-milestone-phrasing-gate": { "claude-code": CC, cursor: mapped("stop followup_message re-drive."), copilot: mapped("agentStop re-drive; reads the transcript (format is not a stable API).") },
|
|
4140
|
+
"enforce-session-sequence": { "claude-code": CC, cursor: mapped("stop followup_message re-drive."), copilot: mapped("agentStop re-drive, capped at 8 consecutive blocks.") },
|
|
4141
|
+
"notify-status-flip": { "claude-code": CC, cursor: mapped(), copilot: mapped("Side effect on postToolUse.") },
|
|
4142
|
+
"close-terminal-on-ship": { "claude-code": CC, cursor: mapped(), copilot: mapped("Side effect on postToolUse / sessionEnd.") },
|
|
4143
|
+
"scripts/stamp-provenance-on-edit.mjs": { "claude-code": CC, cursor: mapped(), copilot: mapped("Side effect on postToolUse.") },
|
|
4144
|
+
"session-end": { "claude-code": CC, cursor: mapped(), copilot: mapped("sessionEnd.") },
|
|
4145
|
+
// ── platform: heyspex-app development machinery (never installed in a customer repo) ──
|
|
4146
|
+
"inject-dev-server-awareness": { "claude-code": CC, cursor: mapped(), copilot: notShipped },
|
|
4147
|
+
"inject-coo-seat": { "claude-code": CC, cursor: mapped(), copilot: notShipped },
|
|
4148
|
+
"coo-guard-check": { "claude-code": CC, cursor: mapped(), copilot: notShipped },
|
|
4149
|
+
"coo-denylist-gate": { "claude-code": CC, cursor: mapped(), copilot: notShipped },
|
|
4150
|
+
"agent-spawn-delegation-gate": { "claude-code": CC, cursor: mapped("subagentStart."), copilot: notShipped },
|
|
4151
|
+
"capture-execution-gate": { "claude-code": CC, cursor: mapped("beforeShellExecution + subagentStart."), copilot: notShipped },
|
|
4152
|
+
"check-mcp-freshness-hook": { "claude-code": CC, cursor: mapped(), copilot: notShipped },
|
|
4153
|
+
"dev-server-task-reminder": { "claude-code": CC, cursor: mapped(), copilot: notShipped },
|
|
4154
|
+
"env-vault-guard": { "claude-code": CC, cursor: mapped("Always on."), copilot: notShipped },
|
|
4155
|
+
"log-session-activity": { "claude-code": CC, cursor: mapped(), copilot: notShipped }
|
|
4156
|
+
});
|
|
4157
|
+
function dispositionFor(hook, host) {
|
|
4158
|
+
return HOOK_DISPOSITIONS[hook]?.[host] ?? null;
|
|
4159
|
+
}
|
|
4160
|
+
|
|
4161
|
+
// scripts/lib/customer-kit.mjs
|
|
3965
4162
|
var HOOK_SCOPES = Object.freeze({
|
|
3966
4163
|
// ── customer: vault / workflow / safety gates ─────────────────────────────
|
|
3967
4164
|
"wip-checkpoint": { scope: "customer", why: "Previous-session WIP handoff, read from the active vault." },
|
|
@@ -3977,6 +4174,7 @@ var HOOK_SCOPES = Object.freeze({
|
|
|
3977
4174
|
"inject-communication-style": { scope: "customer", why: "Plain-language communication rules each turn." },
|
|
3978
4175
|
"inject-vault-context": { scope: "customer", why: "Lexical vault retrieval for the prompt." },
|
|
3979
4176
|
"git-identity-guard": { scope: "customer", why: "Agents never write git identity (owner ruling 2026-09-19)." },
|
|
4177
|
+
"vault-write-tier-guard": { scope: "customer", why: "Cloud write mode: disk edits to vault specs are a mirror the console never reads back (HS-FCNH)." },
|
|
3980
4178
|
"settings-write-gate": { scope: "customer", why: "Approval before the vault's .heyspex/config.yaml changes." },
|
|
3981
4179
|
"emit-claim-status": { scope: "customer", why: "Claim liveness heartbeat to the console." },
|
|
3982
4180
|
"enforce-vault-naming": { scope: "customer", why: "Naming V3 filename guard (HS-RY2N)." },
|
|
@@ -3984,7 +4182,10 @@ var HOOK_SCOPES = Object.freeze({
|
|
|
3984
4182
|
"plan-review-gate": { scope: "customer", why: "Plan review + QC markers before ExitPlanMode." },
|
|
3985
4183
|
"plan-exit-ingest-gate": { scope: "customer", why: "Plan ingest on ExitPlanMode; arms claim-before-code." },
|
|
3986
4184
|
"claim-before-code-gate": { scope: "customer", why: "First code edit after plan exit needs a claim." },
|
|
4185
|
+
"directive-obligation-gate": { scope: "customer", why: "A capture_on_idea directive leaves a durable obligation; an unmet one denies sub-agent spawns, product-code writes and builds, and re-drives Stop once. Also claim-before-build (HS-20SW)." },
|
|
3987
4186
|
"capture-workflow-gate": { scope: "customer", why: "No implementation spec straight out of an open capture." },
|
|
4187
|
+
"vault-mirror-catchup": { scope: "customer", why: "Cloud write mode: a SessionStart detached `hs pull --apply` closes a stale disk mirror before the first tool call (HS-XJFQ)." },
|
|
4188
|
+
"diagnose-capture-reminder": { scope: "customer", why: "Warn when a diagnose is filed with no capture holding the owner's words (HS-IK2D)." },
|
|
3988
4189
|
"git-stash-gate": { scope: "customer", why: "Always ask before a mutating git stash." },
|
|
3989
4190
|
"research-method-guard": { scope: "customer", why: "Research method guard (HS-EN4F)." },
|
|
3990
4191
|
"deep-research-cost-gate": { scope: "customer", why: "Cost confirmation before a paid deep-research launch." },
|
|
@@ -4003,6 +4204,7 @@ var HOOK_SCOPES = Object.freeze({
|
|
|
4003
4204
|
"coo-guard-check": { scope: "platform", why: "dev-factory breakdown-delegation guard." },
|
|
4004
4205
|
"coo-denylist-gate": { scope: "platform", why: "dev-factory COO window deny list." },
|
|
4005
4206
|
"agent-spawn-delegation-gate": { scope: "platform", why: "dev-factory seat routing for subagents." },
|
|
4207
|
+
"capture-execution-gate": { scope: "platform", why: "Blocks dev-factory research/paid-model/routing surfaces past an ungrounded capture (HS-KNJ8)." },
|
|
4006
4208
|
"check-mcp-freshness-hook": { scope: "platform", why: "Freshness of the vendored MCP build \u2014 a platform-dev concern." },
|
|
4007
4209
|
"dev-server-task-reminder": { scope: "platform", why: "spex-console dev server + Neon DB safety." },
|
|
4008
4210
|
"env-vault-guard": { scope: "platform", why: "Guards heyspex-app's encrypted env vault paths." },
|
|
@@ -4021,6 +4223,7 @@ var CLAUDE_HOOK_ROWS = Object.freeze([
|
|
|
4021
4223
|
{ event: "SessionStart", hook: "check-claude-md-budget", timeout: 5 },
|
|
4022
4224
|
{ event: "SessionStart", hook: "check-mcp-freshness-hook", timeout: 5 },
|
|
4023
4225
|
{ event: "SessionStart", hook: "git-reflog-audit", timeout: 5 },
|
|
4226
|
+
{ event: "SessionStart", hook: "vault-mirror-catchup", timeout: 5 },
|
|
4024
4227
|
{ event: "PreCompact", hook: "wip-checkpoint", timeout: 5 },
|
|
4025
4228
|
{ event: "UserPromptSubmit", hook: "preflight-reminder", timeout: 5 },
|
|
4026
4229
|
{ event: "UserPromptSubmit", hook: "intent-classifier-trigger", timeout: 5 },
|
|
@@ -4028,10 +4231,13 @@ var CLAUDE_HOOK_ROWS = Object.freeze([
|
|
|
4028
4231
|
{ event: "UserPromptSubmit", hook: "inject-vault-context", timeout: 10 },
|
|
4029
4232
|
{ event: "PreToolUse", matcher: "Write|Edit|MultiEdit|NotebookEdit|Bash|PowerShell", hook: "env-vault-guard", timeout: 5 },
|
|
4030
4233
|
{ event: "PreToolUse", matcher: "Bash|PowerShell", hook: "git-identity-guard", timeout: 5 },
|
|
4234
|
+
{ event: "PreToolUse", matcher: "Write|Edit|MultiEdit|NotebookEdit|Bash|PowerShell", hook: "vault-write-tier-guard", timeout: 5 },
|
|
4031
4235
|
{ event: "PreToolUse", matcher: "Write|Edit|NotebookEdit|Bash|PowerShell|mcp__heyspex__heyspex_settings_set", hook: "settings-write-gate", timeout: 5 },
|
|
4032
4236
|
{ event: "PreToolUse", matcher: ".*", hook: "emit-claim-status", args: ["--event=PreToolUse"], timeout: 5 },
|
|
4033
4237
|
{ event: "PreToolUse", matcher: ".*", hook: "coo-denylist-gate", timeout: 5 },
|
|
4034
4238
|
{ event: "PreToolUse", matcher: "Agent|Task", hook: "agent-spawn-delegation-gate", timeout: 5 },
|
|
4239
|
+
{ event: "PreToolUse", matcher: "Bash|PowerShell|Agent|Task", hook: "capture-execution-gate", timeout: 5 },
|
|
4240
|
+
{ event: "PreToolUse", matcher: "Agent|Task|Edit|Write|MultiEdit|NotebookEdit|Bash|PowerShell", hook: "directive-obligation-gate", timeout: 5 },
|
|
4035
4241
|
{ event: "PreToolUse", matcher: "mcp__heyspex__heyspex_claim_spec", hook: "coo-guard-check", timeout: 5 },
|
|
4036
4242
|
{ event: "PreToolUse", matcher: "Write|NotebookEdit|Bash|PowerShell", hook: "enforce-vault-naming", timeout: 5 },
|
|
4037
4243
|
{ event: "PreToolUse", matcher: "Write|NotebookEdit|Bash|PowerShell", hook: "guard-auto-memory-writes", timeout: 5 },
|
|
@@ -4040,6 +4246,7 @@ var CLAUDE_HOOK_ROWS = Object.freeze([
|
|
|
4040
4246
|
{ event: "PreToolUse", matcher: "ExitPlanMode", hook: "plan-exit-ingest-gate", timeout: 5 },
|
|
4041
4247
|
{ event: "PreToolUse", matcher: "Edit|MultiEdit|Write", hook: "claim-before-code-gate", timeout: 5 },
|
|
4042
4248
|
{ event: "PreToolUse", matcher: "mcp__heyspex__heyspex_file_spec", hook: "capture-workflow-gate", timeout: 8 },
|
|
4249
|
+
{ event: "PreToolUse", matcher: "mcp__heyspex__heyspex_file_spec", hook: "diagnose-capture-reminder", timeout: 5 },
|
|
4043
4250
|
{ event: "PreToolUse", matcher: "Bash|PowerShell|mcp__neon__prepare_database_migration|mcp__neon__complete_database_migration|mcp__neon__reset_from_parent|mcp__neon__delete_branch|mcp__Neon__prepare_database_migration|mcp__Neon__complete_database_migration|mcp__Neon__reset_from_parent|mcp__Neon__delete_branch", hook: "dev-server-task-reminder", timeout: 10 },
|
|
4044
4251
|
{ event: "PreToolUse", matcher: "Bash|PowerShell", hook: "git-stash-gate", timeout: 10 },
|
|
4045
4252
|
{ event: "PreToolUse", matcher: "Skill|Workflow", hook: "research-method-guard", timeout: 5 },
|
|
@@ -4052,7 +4259,9 @@ var CLAUDE_HOOK_ROWS = Object.freeze([
|
|
|
4052
4259
|
{ event: "Stop", hook: "epic-milestone-phrasing-gate", timeout: 5 },
|
|
4053
4260
|
{ event: "Stop", hook: "git-reflog-audit", timeout: 5 },
|
|
4054
4261
|
{ event: "Stop", hook: "enforce-session-sequence", timeout: 5 },
|
|
4262
|
+
{ event: "Stop", hook: "directive-obligation-gate", timeout: 5 },
|
|
4055
4263
|
{ event: "PostToolUse", matcher: ".*", hook: "emit-claim-status", args: ["--event=PostToolUse"], timeout: 5 },
|
|
4264
|
+
{ event: "PostToolUse", matcher: "Skill|mcp__heyspex__heyspex_update_capture|mcp__heyspex__heyspex_promote_capture.*|mcp__heyspex__heyspex_file_spec|mcp__heyspex__heyspex_claim_spec|mcp__heyspex__heyspex_release_spec|mcp__heyspex__heyspex_defer_spec", hook: "directive-obligation-gate", timeout: 5 },
|
|
4056
4265
|
{ event: "PostToolUse", matcher: "Edit|Write|MultiEdit", hook: "notify-status-flip", timeout: 5 },
|
|
4057
4266
|
{ event: "PostToolUse", matcher: "Edit|Write|MultiEdit", hook: "close-terminal-on-ship", timeout: 5 },
|
|
4058
4267
|
{ event: "PostToolUse", matcher: "Edit|Write|MultiEdit", hook: "scripts/stamp-provenance-on-edit.mjs", timeout: 5 },
|
|
@@ -4137,9 +4346,22 @@ var CUSTOMER_LAUNCHERS = Object.freeze({
|
|
|
4137
4346
|
});
|
|
4138
4347
|
function customerRunnerPrefix(_platformRoot, surface = "claude") {
|
|
4139
4348
|
const l = CUSTOMER_LAUNCHERS[surface];
|
|
4349
|
+
if (surface === "claude") return CUSTOMER_RUNNER_PREFIX;
|
|
4140
4350
|
return `node "$${l.projectDirEnv}/${l.rel.join("/")}"`;
|
|
4141
4351
|
}
|
|
4142
|
-
var
|
|
4352
|
+
var BOOTSTRAP_WALK = "var d=process.cwd(),r=e.CLAUDE_PROJECT_DIR;if(!r||!f.existsSync(p.join(r,...n))){r='';for(;;){if(f.existsSync(p.join(d,...n))){r=d;break}var q=p.dirname(d);if(q===d)break;d=q}}";
|
|
4353
|
+
function buildBootstrap({ plugin = false } = {}) {
|
|
4354
|
+
const walk = BOOTSTRAP_WALK;
|
|
4355
|
+
const body = plugin ? "var f=require('fs'),p=require('path'),u=require('url'),e=process.env,n=['scripts','claude-hooks','_run.mjs'],s='',m=e.CLAUDE_PLUGIN_ROOT||e.COPILOT_PLUGIN_ROOT||e.PLUGIN_ROOT;if(m&&f.existsSync(p.join(m,'bin','run-hook.mjs')))s=p.join(m,'bin','run-hook.mjs');if(!s){" + walk + "if(r){e.CLAUDE_PROJECT_DIR=r;s=p.join(r,...n)}}" : "var f=require('fs'),p=require('path'),u=require('url'),e=process.env,n=['scripts','claude-hooks','_run.mjs'],s='';" + walk + "if(r){e.CLAUDE_PROJECT_DIR=r;s=p.join(r,...n)}";
|
|
4356
|
+
return body + "if(!s)process.exit(0);process.argv=[process.argv[0],s].concat(process.argv.slice(1));import(u.pathToFileURL(s).href).catch(function(){process.exit(0)});" + (plugin ? "//bin/run-hook.mjs" : "//scripts/claude-hooks/_run.mjs");
|
|
4357
|
+
}
|
|
4358
|
+
var PLATFORM_RUNNER_PREFIX = 'node -e "' + buildBootstrap() + '"';
|
|
4359
|
+
var PLUGIN_RUNNER_PREFIX = 'node -e "' + buildBootstrap({ plugin: true }) + '"';
|
|
4360
|
+
function buildCustomerBootstrap() {
|
|
4361
|
+
const rel = CUSTOMER_LAUNCHERS.claude.rel.map((s) => "'" + s + "'").join(",");
|
|
4362
|
+
return "var f=require('fs'),p=require('path'),u=require('url'),e=process.env,n=[" + rel + "],s='';" + BOOTSTRAP_WALK + "if(r){e.CLAUDE_PROJECT_DIR=r;s=p.join(r,...n)}if(!s)process.exit(0);process.argv=[process.argv[0],s].concat(process.argv.slice(1));import(u.pathToFileURL(s).href).catch(function(){process.exit(0)});//" + CUSTOMER_LAUNCHERS.claude.rel.join("/");
|
|
4363
|
+
}
|
|
4364
|
+
var CUSTOMER_RUNNER_PREFIX = 'node -e "' + buildCustomerBootstrap() + '"';
|
|
4143
4365
|
var LAUNCHER_TEMPLATE = [
|
|
4144
4366
|
"#!/usr/bin/env node",
|
|
4145
4367
|
"// HeySpex hook launcher -- GENERATED by hs install / hs connect. Commit it with this repo.",
|
|
@@ -4217,7 +4439,12 @@ var LAUNCHER_TEMPLATE = [
|
|
|
4217
4439
|
"// (the Cursor one) runs exactly as if Cursor had invoked it directly.",
|
|
4218
4440
|
"const shim = resolve(root, ...SHIM);",
|
|
4219
4441
|
"process.argv[1] = shim;",
|
|
4220
|
-
"
|
|
4442
|
+
"try {",
|
|
4443
|
+
" await import(pathToFileURL(shim).href);",
|
|
4444
|
+
"} catch (err) {",
|
|
4445
|
+
" process.stderr.write('[heyspex] hook ' + (process.argv[2] ?? '?') + ' skipped: ' + (err && err.message ? err.message : err) + '\\n');",
|
|
4446
|
+
" process.exit(0);",
|
|
4447
|
+
"}",
|
|
4221
4448
|
""
|
|
4222
4449
|
].join("\n");
|
|
4223
4450
|
function renderCustomerLauncher(surface = "claude") {
|
|
@@ -4324,7 +4551,6 @@ function renderPluginHooksDocument() {
|
|
|
4324
4551
|
});
|
|
4325
4552
|
}
|
|
4326
4553
|
return {
|
|
4327
|
-
_comment: "GENERATED from scripts/lib/customer-kit.mjs (customer-scoped rows only) \u2014 do not edit by hand; run `node scripts/lib/customer-kit.mjs --write-plugin-hooks`. Every command goes through bin/run-hook.mjs, which finds the hook runtime (HEYSPEX_PLATFORM_ROOT / HEYSPEX_MCP_ENTRY when set in the machine's own env, then the plugin's own location, node_modules/heyspex, $CLAUDE_PROJECT_DIR) \u2014 never $CLAUDE_PROJECT_DIR/scripts, which only exists in heyspex-app. WARNING: a repo whose .claude/settings.json already carries the hs install kit hooks runs every hook twice if this plugin is also enabled \u2014 use one or the other.",
|
|
4328
4554
|
hooks
|
|
4329
4555
|
};
|
|
4330
4556
|
}
|
|
@@ -4332,6 +4558,75 @@ function renderPluginHooksText() {
|
|
|
4332
4558
|
return `${JSON.stringify(renderPluginHooksDocument(), null, 2)}
|
|
4333
4559
|
`;
|
|
4334
4560
|
}
|
|
4561
|
+
var COPILOT_EVENT_NAMES = Object.freeze({
|
|
4562
|
+
SessionStart: "sessionStart",
|
|
4563
|
+
SessionEnd: "sessionEnd",
|
|
4564
|
+
UserPromptSubmit: "userPromptSubmitted",
|
|
4565
|
+
PreToolUse: "preToolUse",
|
|
4566
|
+
PostToolUse: "postToolUse",
|
|
4567
|
+
Stop: "agentStop",
|
|
4568
|
+
PreCompact: "preCompact"
|
|
4569
|
+
});
|
|
4570
|
+
var GITHUB_HOOKS_STATE_ARMING = Object.freeze(["intent-classifier-trigger"]);
|
|
4571
|
+
var COPILOT_TOOL_NAMES = Object.freeze({
|
|
4572
|
+
Write: "create",
|
|
4573
|
+
Edit: "edit",
|
|
4574
|
+
MultiEdit: "edit",
|
|
4575
|
+
NotebookEdit: "edit",
|
|
4576
|
+
Bash: "bash",
|
|
4577
|
+
PowerShell: "powershell",
|
|
4578
|
+
Agent: "task",
|
|
4579
|
+
Task: "task",
|
|
4580
|
+
WebFetch: "web_fetch",
|
|
4581
|
+
WebSearch: "web_search"
|
|
4582
|
+
});
|
|
4583
|
+
function toCopilotMatcher(matcher) {
|
|
4584
|
+
if (!matcher) return void 0;
|
|
4585
|
+
const out = [];
|
|
4586
|
+
for (const part of matcher.split("|")) {
|
|
4587
|
+
let t = null;
|
|
4588
|
+
if (part === ".*") t = ".*";
|
|
4589
|
+
else if (COPILOT_TOOL_NAMES[part]) t = COPILOT_TOOL_NAMES[part];
|
|
4590
|
+
else if (part.startsWith(MCP_PROJECT_PREFIX)) t = ".*" + part.slice(MCP_PROJECT_PREFIX.length);
|
|
4591
|
+
else if (part.startsWith(MCP_PLUGIN_PREFIX)) t = ".*" + part.slice(MCP_PLUGIN_PREFIX.length);
|
|
4592
|
+
if (t && !out.includes(t)) out.push(t);
|
|
4593
|
+
}
|
|
4594
|
+
if (out.includes(".*")) return ".*";
|
|
4595
|
+
return out.length ? out.join("|") : null;
|
|
4596
|
+
}
|
|
4597
|
+
function githubHookNames() {
|
|
4598
|
+
const names = /* @__PURE__ */ new Set();
|
|
4599
|
+
for (const r of customerHookRows()) {
|
|
4600
|
+
if (dispositionFor(r.hook, "copilot")?.disposition === "mapped" || GITHUB_HOOKS_STATE_ARMING.includes(r.hook)) names.add(r.hook);
|
|
4601
|
+
}
|
|
4602
|
+
return names;
|
|
4603
|
+
}
|
|
4604
|
+
function githubHookCommand(row2) {
|
|
4605
|
+
const tail = hookTail(row2);
|
|
4606
|
+
return `R=node_modules/heyspex/scripts/claude-hooks/_run.mjs; [ -f "$R" ] || R=./scripts/claude-hooks/_run.mjs; [ -f "$R" ] || exit 0; exec node "$R" ${tail}`;
|
|
4607
|
+
}
|
|
4608
|
+
function renderGithubHooksDocument() {
|
|
4609
|
+
const allowed = githubHookNames();
|
|
4610
|
+
const hooks = {};
|
|
4611
|
+
for (const row2 of customerHookRows()) {
|
|
4612
|
+
if (!allowed.has(row2.hook)) continue;
|
|
4613
|
+
const event = COPILOT_EVENT_NAMES[row2.event];
|
|
4614
|
+
if (!event) continue;
|
|
4615
|
+
const matcher = toCopilotMatcher(row2.matcher);
|
|
4616
|
+
if (matcher === null) continue;
|
|
4617
|
+
(hooks[event] ??= []).push({
|
|
4618
|
+
type: "command",
|
|
4619
|
+
...matcher ? { matcher } : {},
|
|
4620
|
+
bash: githubHookCommand(row2),
|
|
4621
|
+
timeoutSec: row2.timeout
|
|
4622
|
+
});
|
|
4623
|
+
}
|
|
4624
|
+
return { version: 1, hooks };
|
|
4625
|
+
}
|
|
4626
|
+
function renderGithubHooksText() {
|
|
4627
|
+
return `${JSON.stringify(renderGithubHooksDocument(), null, 2)}
|
|
4628
|
+
`;
|
|
4629
|
+
}
|
|
4335
4630
|
var HARNESS_COMMAND_SURFACES = Object.freeze([
|
|
4336
4631
|
{
|
|
4337
4632
|
installedDir: Object.freeze([".claude", "commands"]),
|
|
@@ -4464,21 +4759,35 @@ if (invokedDirectly(import.meta.url, "customer-kit.mjs")) {
|
|
|
4464
4759
|
process.exit(1);
|
|
4465
4760
|
}
|
|
4466
4761
|
console.log("customer-kit: plugins/heyspex/hooks/hooks.json up to date");
|
|
4762
|
+
} else if (process.argv.includes("--write-github-hooks") || process.argv.includes("--check-github-hooks")) {
|
|
4763
|
+
const ghTarget = path2.join(platformRoot, ".github", "hooks", "heyspex.json");
|
|
4764
|
+
const ghWant = renderGithubHooksText();
|
|
4765
|
+
const ghSame = (fsDeps.readFile(ghTarget) ?? "").replace(/\r\n/g, "\n") === ghWant;
|
|
4766
|
+
if (process.argv.includes("--write-github-hooks")) {
|
|
4767
|
+
if (!ghSame) {
|
|
4768
|
+
fs.mkdirSync(path2.dirname(ghTarget), { recursive: true });
|
|
4769
|
+
fs.writeFileSync(ghTarget, ghWant, "utf8");
|
|
4770
|
+
}
|
|
4771
|
+
console.log(`customer-kit: ${ghSame ? "unchanged" : "wrote"} ${path2.relative(platformRoot, ghTarget).replace(/\\/g, "/")}`);
|
|
4772
|
+
} else if (!ghSame) {
|
|
4773
|
+
console.error("customer-kit: .github/hooks/heyspex.json is stale - run `node scripts/lib/customer-kit.mjs --write-github-hooks`");
|
|
4774
|
+
process.exit(1);
|
|
4775
|
+
} else {
|
|
4776
|
+
console.log("customer-kit: .github/hooks/heyspex.json up to date");
|
|
4777
|
+
}
|
|
4467
4778
|
} else {
|
|
4468
4779
|
console.log("usage: node scripts/lib/customer-kit.mjs --write-plugin-hooks | --check-plugin-hooks");
|
|
4469
4780
|
}
|
|
4470
4781
|
}
|
|
4471
4782
|
|
|
4472
4783
|
// spex-console/src/lib/cli/connect-checks.ts
|
|
4473
|
-
import { homedir as homedir9 } from "node:os";
|
|
4474
4784
|
import { dirname as dirname5 } from "node:path";
|
|
4475
4785
|
import { pathToFileURL as pathToFileURL2 } from "node:url";
|
|
4476
4786
|
|
|
4477
4787
|
// spex-console/src/lib/dev-mode.ts
|
|
4478
|
-
init_credentials();
|
|
4479
4788
|
import { mkdirSync as mkdirSync4, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "node:fs";
|
|
4480
|
-
import { homedir as homedir6 } from "node:os";
|
|
4481
4789
|
import { join as join6 } from "node:path";
|
|
4790
|
+
init_credentials();
|
|
4482
4791
|
var CLOUD_CONSOLE_URL2 = "https://app.heyspex.com";
|
|
4483
4792
|
var LOCAL_CONSOLE_URL2 = "http://localhost:3000";
|
|
4484
4793
|
function normalizeMode2(raw) {
|
|
@@ -4511,12 +4820,12 @@ function deriveSemantics2(mode, reliance) {
|
|
|
4511
4820
|
vaultWriteMode: local ? "local" : "cloud"
|
|
4512
4821
|
};
|
|
4513
4822
|
}
|
|
4514
|
-
function devModeFilePath2(homeDir =
|
|
4823
|
+
function devModeFilePath2(homeDir = heyspexHome2()) {
|
|
4515
4824
|
return join6(homeDir, ".heyspex", "dev-mode.json");
|
|
4516
4825
|
}
|
|
4517
4826
|
var defaultReadFile3 = (p) => readFileSync7(p, "utf8");
|
|
4518
4827
|
function readLocalDevModeRecord2({
|
|
4519
|
-
homeDir =
|
|
4828
|
+
homeDir = heyspexHome2(),
|
|
4520
4829
|
readFile: readFile2 = defaultReadFile3
|
|
4521
4830
|
} = {}) {
|
|
4522
4831
|
let raw;
|
|
@@ -4551,7 +4860,7 @@ function readLocalDevModeRecord2({
|
|
|
4551
4860
|
}
|
|
4552
4861
|
function resolveDevMode2({
|
|
4553
4862
|
env = process.env,
|
|
4554
|
-
homeDir =
|
|
4863
|
+
homeDir = heyspexHome2(),
|
|
4555
4864
|
readFile: readFile2 = defaultReadFile3,
|
|
4556
4865
|
cloudSetting = void 0
|
|
4557
4866
|
} = {}) {
|
|
@@ -4662,10 +4971,11 @@ function shellQuote(p) {
|
|
|
4662
4971
|
var HS_DISPATCHER_PREFIX = HS_DISPATCHER_PATH ? `node ${shellQuote(HS_DISPATCHER_PATH)}` : HS_BIN_NAME;
|
|
4663
4972
|
|
|
4664
4973
|
// vendor/heyspex-mcp/console-fetch.mjs
|
|
4974
|
+
init_heyspex_home();
|
|
4665
4975
|
init_credentials();
|
|
4666
|
-
import { readFileSync as
|
|
4976
|
+
import { readFileSync as readFileSync10 } from "node:fs";
|
|
4667
4977
|
import { homedir as homedir8 } from "node:os";
|
|
4668
|
-
import { join as
|
|
4978
|
+
import { join as join10 } from "node:path";
|
|
4669
4979
|
|
|
4670
4980
|
// vendor/heyspex-mcp/vault-less-endpoints.mjs
|
|
4671
4981
|
var VAULT_LESS_ENDPOINTS = Object.freeze([
|
|
@@ -4691,10 +5001,23 @@ function isVaultLessEndpoint(method, path7) {
|
|
|
4691
5001
|
});
|
|
4692
5002
|
}
|
|
4693
5003
|
|
|
5004
|
+
// vendor/heyspex-mcp/write-outbox.mjs
|
|
5005
|
+
import {
|
|
5006
|
+
readdirSync,
|
|
5007
|
+
readFileSync as readFileSync8,
|
|
5008
|
+
writeFileSync as writeFileSync5,
|
|
5009
|
+
mkdirSync as mkdirSync5,
|
|
5010
|
+
existsSync as existsSync3,
|
|
5011
|
+
renameSync as renameSync3,
|
|
5012
|
+
rmSync
|
|
5013
|
+
} from "node:fs";
|
|
5014
|
+
import { basename, join as join8, isAbsolute as isAbsolute2 } from "node:path";
|
|
5015
|
+
|
|
4694
5016
|
// vendor/heyspex-mcp/vault-resolve.mjs
|
|
4695
|
-
|
|
5017
|
+
init_heyspex_home();
|
|
5018
|
+
import { existsSync as existsSync4, readFileSync as readFileSync9 } from "node:fs";
|
|
4696
5019
|
import { homedir as homedir7, tmpdir } from "node:os";
|
|
4697
|
-
import { dirname as dirname4, join as
|
|
5020
|
+
import { dirname as dirname4, join as join9, resolve as resolve4 } from "node:path";
|
|
4698
5021
|
|
|
4699
5022
|
// vendor/heyspex-mcp/console-fetch.mjs
|
|
4700
5023
|
var SCOPE_REFUSAL_CODES = Object.freeze(["VAULT_SCOPE_MISMATCH", "ACCOUNT_MISMATCH", "VAULT_NOT_REGISTERED"]);
|
|
@@ -5002,7 +5325,7 @@ function runInit(args, deps) {
|
|
|
5002
5325
|
};
|
|
5003
5326
|
}
|
|
5004
5327
|
const vaultId = existingVaultId ?? args.adoptedVaultId ?? mintVaultId();
|
|
5005
|
-
const accountId =
|
|
5328
|
+
const accountId = null;
|
|
5006
5329
|
const content = renderHeyspexYaml({ ...args, vaultId, accountId });
|
|
5007
5330
|
try {
|
|
5008
5331
|
deps.writeFile(markerPath, content);
|
|
@@ -5268,6 +5591,62 @@ function isAbsolutePathForCompare(p) {
|
|
|
5268
5591
|
const s = p.trim();
|
|
5269
5592
|
return /^[\\/]/.test(s) || /^[a-zA-Z]:[\\/]/.test(s);
|
|
5270
5593
|
}
|
|
5594
|
+
function expandPinPlaceholders(value, repoRoot) {
|
|
5595
|
+
return value.replace(/\$\{(?:CLAUDE_PROJECT_DIR|workspaceFolder)(?::-[^}]*)?\}/g, repoRoot);
|
|
5596
|
+
}
|
|
5597
|
+
function lexicalJoin(base, rel) {
|
|
5598
|
+
const parts = `${base}/${rel}`.replace(/\\/g, "/").split("/");
|
|
5599
|
+
const out = [];
|
|
5600
|
+
for (const seg of parts) {
|
|
5601
|
+
if (seg === "" && out.length > 0) continue;
|
|
5602
|
+
if (seg === ".") continue;
|
|
5603
|
+
if (seg === ".." && out.length > 1) {
|
|
5604
|
+
out.pop();
|
|
5605
|
+
continue;
|
|
5606
|
+
}
|
|
5607
|
+
out.push(seg);
|
|
5608
|
+
}
|
|
5609
|
+
return out.join("/");
|
|
5610
|
+
}
|
|
5611
|
+
function parseMarkerVaultPath(content) {
|
|
5612
|
+
const m = content.match(/^vault_path:[ \t]*(.+?)[ \t]*$/m);
|
|
5613
|
+
if (!m) return null;
|
|
5614
|
+
const v = m[1].trim().replace(/\s+#.*$/, "").replace(/^"(.*)"$/, "$1").replace(/^'(.*)'$/, "$1").trim();
|
|
5615
|
+
if (!v || v.includes("${") || /^[\\/]/.test(v) || /^[a-zA-Z]:[\\/]/.test(v)) return null;
|
|
5616
|
+
return v;
|
|
5617
|
+
}
|
|
5618
|
+
function markerVaultIdOf(content) {
|
|
5619
|
+
const m = content.match(/^vault_id:\s*(.+?)\s*$/m);
|
|
5620
|
+
if (!m) return null;
|
|
5621
|
+
const v = m[1].trim().replace(/^"(.*)"$/, "$1").replace(/^'(.*)'$/, "$1");
|
|
5622
|
+
return v.length > 0 ? v : null;
|
|
5623
|
+
}
|
|
5624
|
+
function detectMarkerAttachment(repoRoot, deps) {
|
|
5625
|
+
const markerPath = deps.joinPath(repoRoot, "heyspex.yaml");
|
|
5626
|
+
if (!deps.fileExists(markerPath)) return null;
|
|
5627
|
+
const raw = deps.readFile(markerPath);
|
|
5628
|
+
const vaultPath = raw ? parseMarkerVaultPath(raw) : null;
|
|
5629
|
+
if (!raw || !vaultPath) return null;
|
|
5630
|
+
const vaultRootAbs = lexicalJoin(repoRoot, vaultPath);
|
|
5631
|
+
const markerId = markerVaultIdOf(raw);
|
|
5632
|
+
let cloned = deps.fileExists(deps.joinPath(vaultRootAbs, "specs"));
|
|
5633
|
+
if (cloned && markerId) {
|
|
5634
|
+
const theirs = deps.readFile(deps.joinPath(vaultRootAbs, "heyspex.yaml"));
|
|
5635
|
+
const theirId = theirs ? markerVaultIdOf(theirs) : null;
|
|
5636
|
+
if (theirId && theirId !== markerId) cloned = false;
|
|
5637
|
+
}
|
|
5638
|
+
const registry = loadVaultRegistry(deps.registryOpts);
|
|
5639
|
+
const record = registry.vaults.find((v) => samePath(v.repo_root_abs, vaultRootAbs));
|
|
5640
|
+
return {
|
|
5641
|
+
vaultRootAbs,
|
|
5642
|
+
vaultId: markerId ?? record?.id ?? null,
|
|
5643
|
+
pins: [{ source: "heyspex.yaml", value: vaultRootAbs }],
|
|
5644
|
+
conflicting: false,
|
|
5645
|
+
viaMarker: true,
|
|
5646
|
+
cloned,
|
|
5647
|
+
vaultPath
|
|
5648
|
+
};
|
|
5649
|
+
}
|
|
5271
5650
|
function readPinnedVaultRoot(repoRoot, deps) {
|
|
5272
5651
|
const pins = [];
|
|
5273
5652
|
for (const rel of SATELLITE_MANIFEST_RELS) {
|
|
@@ -5284,13 +5663,19 @@ function readPinnedVaultRoot(repoRoot, deps) {
|
|
|
5284
5663
|
}
|
|
5285
5664
|
if (typeof value !== "string") continue;
|
|
5286
5665
|
const trimmed = value.trim();
|
|
5287
|
-
if (!trimmed
|
|
5288
|
-
|
|
5289
|
-
|
|
5666
|
+
if (!trimmed) continue;
|
|
5667
|
+
const expanded = expandPinPlaceholders(trimmed, repoRoot);
|
|
5668
|
+
if (expanded.includes("${")) continue;
|
|
5669
|
+
if (/^[a-zA-Z]:(?![\\/])/.test(expanded)) continue;
|
|
5670
|
+
const abs = isAbsolutePathForCompare(expanded) ? expanded.includes("..") ? lexicalJoin(expanded, ".") : expanded : lexicalJoin(repoRoot, expanded);
|
|
5671
|
+
if (expanded !== trimmed && isAtOrUnder(abs, repoRoot)) continue;
|
|
5672
|
+
pins.push({ source: rel.join("/"), value: abs });
|
|
5290
5673
|
}
|
|
5291
5674
|
return pins;
|
|
5292
5675
|
}
|
|
5293
5676
|
function detectSatelliteAttachment(repoRoot, deps) {
|
|
5677
|
+
const viaMarker = detectMarkerAttachment(repoRoot, deps);
|
|
5678
|
+
if (viaMarker) return viaMarker;
|
|
5294
5679
|
const pins = readPinnedVaultRoot(repoRoot, deps).filter(
|
|
5295
5680
|
(pin) => !isAtOrUnder(pin.value, repoRoot)
|
|
5296
5681
|
);
|
|
@@ -5304,6 +5689,7 @@ function detectSatelliteAttachment(repoRoot, deps) {
|
|
|
5304
5689
|
function findSatelliteRepoRoot(startDir, deps) {
|
|
5305
5690
|
let dir = deps.resolveDir(startDir);
|
|
5306
5691
|
for (let i = 0; i < 256; i += 1) {
|
|
5692
|
+
if (detectMarkerAttachment(dir, deps)) return dir;
|
|
5307
5693
|
const pins = readPinnedVaultRoot(dir, deps);
|
|
5308
5694
|
if (pins.some((pin) => !isAtOrUnder(deps.resolveDir(pin.value), dir))) {
|
|
5309
5695
|
return dir;
|
|
@@ -5419,7 +5805,7 @@ function workspaceAgreementRow(vaultRecord, cloudWorkspaceId) {
|
|
|
5419
5805
|
function checksHomeDir(deps) {
|
|
5420
5806
|
if (deps.homeDir) return deps.homeDir;
|
|
5421
5807
|
const registryDir = deps.registryOpts?.registryDir;
|
|
5422
|
-
return registryDir ? dirname5(registryDir) :
|
|
5808
|
+
return registryDir ? dirname5(registryDir) : heyspexHome2();
|
|
5423
5809
|
}
|
|
5424
5810
|
function accountAgreementRow(yamlContent, vaultRecord, deps, opts) {
|
|
5425
5811
|
const id = "account_agreement";
|
|
@@ -5427,14 +5813,14 @@ function accountAgreementRow(yamlContent, vaultRecord, deps, opts) {
|
|
|
5427
5813
|
if (yamlContent === null) {
|
|
5428
5814
|
return { id, label, status: "not_applicable", detail: "no heyspex.yaml yet" };
|
|
5429
5815
|
}
|
|
5430
|
-
const
|
|
5816
|
+
const legacyMarkerAccount = parseAccountId(yamlContent);
|
|
5817
|
+
const recorded = vaultRecord && vaultRecord.account_id && vaultRecord.account_id !== LEGACY_REGISTRY_ACCOUNT_ID ? vaultRecord.account_id : null;
|
|
5431
5818
|
if (!recorded) {
|
|
5432
5819
|
return {
|
|
5433
5820
|
id,
|
|
5434
5821
|
label,
|
|
5435
|
-
status: "
|
|
5436
|
-
detail: "
|
|
5437
|
-
remedy: "hs connect"
|
|
5822
|
+
status: "not_applicable",
|
|
5823
|
+
detail: "the account binding is machine-local (the registry row), and none exists on this machine yet; the signed-in account is used" + (legacyMarkerAccount ? ` (legacy account: ${legacyMarkerAccount} in heyspex.yaml is ignored)` : "")
|
|
5438
5824
|
};
|
|
5439
5825
|
}
|
|
5440
5826
|
const cloud = opts.cloudVaultAccountId ?? null;
|
|
@@ -5462,28 +5848,25 @@ function accountAgreementRow(yamlContent, vaultRecord, deps, opts) {
|
|
|
5462
5848
|
remedy
|
|
5463
5849
|
};
|
|
5464
5850
|
}
|
|
5465
|
-
if (vaultRecord && vaultRecord.account_id !== recorded) {
|
|
5466
|
-
const legacy = vaultRecord.account_id === LEGACY_REGISTRY_ACCOUNT_ID;
|
|
5467
|
-
return {
|
|
5468
|
-
id,
|
|
5469
|
-
label,
|
|
5470
|
-
status: "stale",
|
|
5471
|
-
detail: `the registry row for this repo is keyed to ${legacy ? "no account (legacy)" : vaultRecord.account_id}, heyspex.yaml says ${recorded} -- run \`hs connect\` to backfill it`,
|
|
5472
|
-
remedy: "hs connect"
|
|
5473
|
-
};
|
|
5474
|
-
}
|
|
5475
5851
|
const current = resolveSessionOwnerUncached({
|
|
5476
5852
|
env,
|
|
5477
5853
|
homeDir,
|
|
5478
5854
|
readFile: (p) => readFile2(p) ?? ""
|
|
5479
5855
|
});
|
|
5480
5856
|
const signedIn = current && current !== recorded ? `; signed in as ${current} -- sessions here still use ${recorded}'s sign-in` : "";
|
|
5481
|
-
|
|
5857
|
+
const legacyNote = legacyMarkerAccount && legacyMarkerAccount !== recorded ? `; legacy account: ${legacyMarkerAccount} in heyspex.yaml is ignored` : "";
|
|
5858
|
+
return {
|
|
5859
|
+
id,
|
|
5860
|
+
label,
|
|
5861
|
+
status: "present",
|
|
5862
|
+
detail: `account: ${recorded} (machine-local)${signedIn}${legacyNote}`
|
|
5863
|
+
};
|
|
5482
5864
|
}
|
|
5483
5865
|
function runConnectChecks(repoRoot, deps, opts = {}) {
|
|
5484
5866
|
const checks = [];
|
|
5485
5867
|
const satellite = opts.satellite !== void 0 ? opts.satellite : detectSatelliteAttachment(repoRoot, deps);
|
|
5486
|
-
const
|
|
5868
|
+
const cloudOnly = Boolean(satellite?.viaMarker) && satellite?.cloned === false;
|
|
5869
|
+
const vaultSubject = satellite && !cloudOnly ? satellite.vaultRootAbs : repoRoot;
|
|
5487
5870
|
const yamlPath = deps.joinPath(vaultSubject, "heyspex.yaml");
|
|
5488
5871
|
const yamlContent = readIfExists(deps, yamlPath);
|
|
5489
5872
|
const vaultId = yamlContent ? parseVaultId(yamlContent) : null;
|
|
@@ -5511,11 +5894,12 @@ function runConnectChecks(repoRoot, deps, opts = {}) {
|
|
|
5511
5894
|
)
|
|
5512
5895
|
);
|
|
5513
5896
|
const workspaceRecord = vaultRecord ? registry.workspaces.find((w) => w.id === vaultRecord.workspace_id) : void 0;
|
|
5897
|
+
const ownVaultRegistered = Boolean(vaultId) && Boolean(vaultRecord) && vaultRecord?.id === vaultId;
|
|
5514
5898
|
checks.push(
|
|
5515
5899
|
row(
|
|
5516
5900
|
"workspace_registered",
|
|
5517
5901
|
"workspace registered in ~/.heyspex/registry.json",
|
|
5518
|
-
Boolean(workspaceRecord),
|
|
5902
|
+
Boolean(workspaceRecord) || ownVaultRegistered,
|
|
5519
5903
|
satelliteDetail(
|
|
5520
5904
|
satellite,
|
|
5521
5905
|
workspaceRecord ? `id: ${workspaceRecord.id}` : vaultRecord ? `vault's workspace "${vaultRecord.workspace_id}" not found in registry` : satellite ? "the attached vault is not registered on this machine" : "no vault registered for this repo"
|
|
@@ -5636,9 +6020,38 @@ function runConnectChecks(repoRoot, deps, opts = {}) {
|
|
|
5636
6020
|
id: "satellite_attachment",
|
|
5637
6021
|
label: "attached vault (satellite repo)",
|
|
5638
6022
|
status: satellite.conflicting ? "stale" : "present",
|
|
5639
|
-
detail: satellite.conflicting ? `manifests pin different vaults (${pinned}) -- re-run \`hs install --attach-vault <id> --force\` to make them agree` : `${satellite.vaultId ?? "(unregistered)"} at ${satellite.vaultRootAbs} (${pinned})`
|
|
6023
|
+
detail: satellite.viaMarker ? `${satellite.vaultId ?? "(no vault_id)"} via heyspex.yaml vault_path ${satellite.vaultPath} -> ${satellite.vaultRootAbs} (${satellite.cloned ? "cloned here" : "NOT cloned here: cloud-only mode"})` : satellite.conflicting ? `manifests pin different vaults (${pinned}) -- re-run \`hs install --attach-vault <id> --force\` to make them agree` : `${satellite.vaultId ?? "(unregistered)"} at ${satellite.vaultRootAbs} (${pinned})`
|
|
5640
6024
|
});
|
|
5641
6025
|
}
|
|
6026
|
+
if (satellite?.viaMarker) {
|
|
6027
|
+
const na = (id, detail) => {
|
|
6028
|
+
const r = checks.find((c) => c.id === id);
|
|
6029
|
+
if (r && (r.status === "missing" || r.status === "stale")) {
|
|
6030
|
+
r.status = "not_applicable";
|
|
6031
|
+
r.detail = detail;
|
|
6032
|
+
delete r.remedy;
|
|
6033
|
+
}
|
|
6034
|
+
};
|
|
6035
|
+
const noRow = "not required: this repo's committed heyspex.yaml binds the vault by id (a registry row appears after `hs connect` in the vault clone)";
|
|
6036
|
+
na("vault_registered", noRow);
|
|
6037
|
+
na("workspace_registered", noRow);
|
|
6038
|
+
na("id_agreement", noRow);
|
|
6039
|
+
na("account_agreement", "portable marker records no account; the signed-in account is used");
|
|
6040
|
+
if (cloudOnly) {
|
|
6041
|
+
na(
|
|
6042
|
+
"specs_dir",
|
|
6043
|
+
`satellite, vault not cloned locally -- cloud mode (clone the vault repo at ${satellite.vaultPath ?? "its vault_path"} for local reads and \`hs pull\`)`
|
|
6044
|
+
);
|
|
6045
|
+
}
|
|
6046
|
+
if (!vaultId) {
|
|
6047
|
+
const y = checks.find((c) => c.id === "heyspex_yaml");
|
|
6048
|
+
if (y) {
|
|
6049
|
+
y.status = "stale";
|
|
6050
|
+
y.detail = "heyspex.yaml has a vault_path but no vault_id; the satellite cannot name its vault";
|
|
6051
|
+
y.remedy = "add `vault_id: <the vault's uuid>` to heyspex.yaml (find it with `hs vault list`), then re-run `hs connect --check`";
|
|
6052
|
+
}
|
|
6053
|
+
}
|
|
6054
|
+
}
|
|
5642
6055
|
const connected = checks.every((c) => c.status === "present" || c.status === "not_applicable");
|
|
5643
6056
|
return {
|
|
5644
6057
|
repo_root: repoRoot,
|
|
@@ -5647,7 +6060,8 @@ function runConnectChecks(repoRoot, deps, opts = {}) {
|
|
|
5647
6060
|
...satellite ? {
|
|
5648
6061
|
satellite: {
|
|
5649
6062
|
attached_vault_root: satellite.vaultRootAbs,
|
|
5650
|
-
attached_vault_id: satellite.vaultId
|
|
6063
|
+
attached_vault_id: satellite.vaultId,
|
|
6064
|
+
...satellite.viaMarker ? { via_marker: true, cloned: satellite.cloned === true } : {}
|
|
5651
6065
|
}
|
|
5652
6066
|
} : {}
|
|
5653
6067
|
};
|
|
@@ -5773,6 +6187,22 @@ function pointerVsRepoVerdict(input) {
|
|
|
5773
6187
|
repair: `hs vault switch ${repoVault}`,
|
|
5774
6188
|
detail: `the cloud pointer names ${cloudPointer ?? "(none)"} but the registry pointer (its local cache) names ${registryPointer}: the cache is stale`
|
|
5775
6189
|
});
|
|
6190
|
+
if (markerVaultId && input.markerBoundSatellite && !registryRowId) {
|
|
6191
|
+
return {
|
|
6192
|
+
branch: "explicit_vault",
|
|
6193
|
+
agree: true,
|
|
6194
|
+
repair: null,
|
|
6195
|
+
detail: `this marker-bound satellite names ${markerVaultId} in its committed heyspex.yaml, so CLI and MCP requests route to it by id; no registry row is needed and the account pointer (${cloudPointer ?? "(none)"}) is informational`
|
|
6196
|
+
};
|
|
6197
|
+
}
|
|
6198
|
+
if (markerVaultId && registryRowId === markerVaultId) {
|
|
6199
|
+
return {
|
|
6200
|
+
branch: "explicit_vault",
|
|
6201
|
+
agree: true,
|
|
6202
|
+
repair: null,
|
|
6203
|
+
detail: cloudPointer === markerVaultId && (registryPointer === null || registryPointer === markerVaultId) ? `both pointers name ${markerVaultId}` : `the account's active pointer names ${cloudPointer ?? "(none)"} (the browser console's choice${registryPointer !== null && registryPointer !== cloudPointer ? `; the local cache names ${registryPointer}` : ""}); this repo names its own vault ${markerVaultId} explicitly in heyspex.yaml, so CLI and MCP requests route to it and the difference is informational (optional: \`hs vault switch ${markerVaultId}\`)`
|
|
6204
|
+
};
|
|
6205
|
+
}
|
|
5776
6206
|
if (markerVaultId) {
|
|
5777
6207
|
if (registryRowId !== markerVaultId) {
|
|
5778
6208
|
const rekey = ROUTING_FACTS.find((f) => f.id === "C")?.caches.find((c) => c.id === "registry-vault-id")?.repair ?? "hs vault migrate-registry";
|
|
@@ -5988,7 +6418,7 @@ async function readCloudView(http, tokens) {
|
|
|
5988
6418
|
}
|
|
5989
6419
|
async function runCloudViewChecks(repoRoot, deps, opts = {}) {
|
|
5990
6420
|
const satellite = opts.satellite !== void 0 ? opts.satellite : detectSatelliteAttachment(repoRoot, deps);
|
|
5991
|
-
const vaultSubject = satellite ? satellite.vaultRootAbs : repoRoot;
|
|
6421
|
+
const vaultSubject = satellite && !(satellite.viaMarker && satellite.cloned === false) ? satellite.vaultRootAbs : repoRoot;
|
|
5992
6422
|
const marker = readIfExists(deps, deps.joinPath(vaultSubject, "heyspex.yaml"));
|
|
5993
6423
|
const vaultId = marker ? parseVaultId(marker) : null;
|
|
5994
6424
|
const env = deps.env ?? process.env;
|
|
@@ -5999,7 +6429,11 @@ async function runCloudViewChecks(repoRoot, deps, opts = {}) {
|
|
|
5999
6429
|
homeDir,
|
|
6000
6430
|
readFile: (p) => readFile2(p) ?? ""
|
|
6001
6431
|
});
|
|
6002
|
-
const
|
|
6432
|
+
const rowForAccount = loadVaultRegistry(deps.registryOpts).vaults.find(
|
|
6433
|
+
(v) => samePath(v.repo_root_abs, vaultSubject)
|
|
6434
|
+
);
|
|
6435
|
+
const boundAccount = rowForAccount?.account_id && rowForAccount.account_id !== LEGACY_REGISTRY_ACCOUNT_ID ? rowForAccount.account_id : null;
|
|
6436
|
+
const repoAccount = boundAccount ?? signedInLocal ?? null;
|
|
6003
6437
|
const http = deps.http;
|
|
6004
6438
|
let tokens = { current: null, repo: null };
|
|
6005
6439
|
if (http && http.token !== void 0) tokens = { current: http.token, repo: http.token };
|
|
@@ -6033,7 +6467,8 @@ async function runCloudViewChecks(repoRoot, deps, opts = {}) {
|
|
|
6033
6467
|
markerVaultId: vaultId,
|
|
6034
6468
|
registryRowId: vaultRecord?.id ?? null,
|
|
6035
6469
|
cloudPointer: view.cloudPointer,
|
|
6036
|
-
registryPointer: localPointer
|
|
6470
|
+
registryPointer: localPointer,
|
|
6471
|
+
markerBoundSatellite: Boolean(satellite?.viaMarker)
|
|
6037
6472
|
});
|
|
6038
6473
|
rows.push(
|
|
6039
6474
|
compared(
|
|
@@ -6106,6 +6541,19 @@ async function runCloudViewChecks(repoRoot, deps, opts = {}) {
|
|
|
6106
6541
|
rows.push(notApplicableRow(id, "no heyspex.yaml and no signed-in account"));
|
|
6107
6542
|
} else if (view.sessionFailure) {
|
|
6108
6543
|
rows.push(unknownRow(id, repoAccount, view.sessionFailure));
|
|
6544
|
+
} else if (!repoAccount && satellite?.viaMarker) {
|
|
6545
|
+
rows.push(
|
|
6546
|
+
compared(
|
|
6547
|
+
id,
|
|
6548
|
+
{
|
|
6549
|
+
local: "(portable marker: no account recorded)",
|
|
6550
|
+
cloud: view.email ? `${view.userId} (${view.email})` : view.userId
|
|
6551
|
+
},
|
|
6552
|
+
true,
|
|
6553
|
+
"hs login",
|
|
6554
|
+
`portable marker; signed in as ${view.email ?? view.userId}`
|
|
6555
|
+
)
|
|
6556
|
+
);
|
|
6109
6557
|
} else {
|
|
6110
6558
|
const agree = repoAccount !== null && (repoAccount === view.userId || repoAccount === view.email);
|
|
6111
6559
|
rows.push(
|
|
@@ -6243,7 +6691,7 @@ function writeProjectContract(ctx, options2 = {}) {
|
|
|
6243
6691
|
}
|
|
6244
6692
|
|
|
6245
6693
|
// spex-console/src/lib/cli/init.customer-vault.ts
|
|
6246
|
-
import { basename } from "node:path";
|
|
6694
|
+
import { basename as basename2 } from "node:path";
|
|
6247
6695
|
|
|
6248
6696
|
// spex-console/src/lib/cli/template-manifest.sources.mjs
|
|
6249
6697
|
var UNIVERSAL_TEMPLATE_ROWS = [
|
|
@@ -6754,7 +7202,7 @@ function findManagedSpecs(deps, targetDir, manifestDests) {
|
|
|
6754
7202
|
return out;
|
|
6755
7203
|
}
|
|
6756
7204
|
function defaultVaultName(targetDir) {
|
|
6757
|
-
return
|
|
7205
|
+
return basename2(targetDir) || targetDir;
|
|
6758
7206
|
}
|
|
6759
7207
|
function runCustomerVaultInit(args, deps, options2 = {}) {
|
|
6760
7208
|
const { targetDir } = args;
|
|
@@ -6902,14 +7350,14 @@ function templatePinHook() {
|
|
|
6902
7350
|
}
|
|
6903
7351
|
|
|
6904
7352
|
// spex-console/src/lib/cli/principle-selection.ts
|
|
6905
|
-
import { basename as
|
|
7353
|
+
import { basename as basename3 } from "node:path";
|
|
6906
7354
|
var PRINCIPLES_INDEX_DEST = "specs/principles-index.md";
|
|
6907
7355
|
function principleKey(row2) {
|
|
6908
|
-
const base =
|
|
7356
|
+
const base = basename3(row2.dest, ".md");
|
|
6909
7357
|
return base.startsWith("principle-") ? base.slice("principle-".length) : base;
|
|
6910
7358
|
}
|
|
6911
7359
|
function principleWikilink(row2) {
|
|
6912
|
-
return
|
|
7360
|
+
return basename3(row2.dest, ".md");
|
|
6913
7361
|
}
|
|
6914
7362
|
function parsePrinciplesFlag(value, rows) {
|
|
6915
7363
|
const principleRows = rows.filter((row2) => row2.group === "principle");
|
|
@@ -7020,7 +7468,7 @@ function applyCustomerTemplate(args) {
|
|
|
7020
7468
|
}
|
|
7021
7469
|
|
|
7022
7470
|
// spex-console/src/lib/cli/install.ts
|
|
7023
|
-
import {
|
|
7471
|
+
import { isAbsolute as isAbsolute3, resolve as resolveNodePath2 } from "node:path";
|
|
7024
7472
|
|
|
7025
7473
|
// scripts/install-cursor-hooks.mjs
|
|
7026
7474
|
import fs2 from "node:fs";
|
|
@@ -7037,10 +7485,13 @@ var HOOKS = [
|
|
|
7037
7485
|
{ event: "sessionStart", hook: "wip-checkpoint", why: "Surface the previous session WIP handoff before work resumes." },
|
|
7038
7486
|
{ event: "sessionStart", hook: "inject-dev-server-awareness", why: "Once-per-session reminder that dev-server status is a tool call, not a guess." },
|
|
7039
7487
|
{ event: "sessionStart", hook: "inject-coo-seat", why: "Inject the dev-factory COO seat mandate when the COO window is open." },
|
|
7488
|
+
{ event: "sessionStart", hook: "inject-doctrine", why: "Inject the baseline SpexCode Doctrine edition at session start." },
|
|
7040
7489
|
{ event: "sessionStart", hook: "coo-guard-check", why: "Warn at session start when a breakdown has not been delegated." },
|
|
7041
7490
|
{ event: "sessionStart", hook: "check-auto-memory-budget", why: "Self-heal and budget-check the auto-memory index." },
|
|
7042
7491
|
{ event: "sessionStart", hook: "check-claude-md-budget", why: "Advisory budget check on the always-loaded agent contract." },
|
|
7043
7492
|
{ event: "sessionStart", hook: "check-mcp-freshness-hook", why: "Warn when the running MCP server is stale versus vendor." },
|
|
7493
|
+
{ event: "sessionStart", hook: "git-reflog-audit", why: "Session-start git audit (stash/reset/rebase witness)." },
|
|
7494
|
+
{ event: "sessionStart", hook: "vault-mirror-catchup", why: "Cloud write mode: detached hs pull --apply closes a stale disk mirror (HS-XJFQ)." },
|
|
7044
7495
|
// ── Compaction ──────────────────────────────────────────────────────────
|
|
7045
7496
|
// Claude re-runs SessionStart(source: compact) after compaction; Cursor fires
|
|
7046
7497
|
// preCompact BEFORE it, so the shim relays this one as SessionStart(compact)
|
|
@@ -7054,6 +7505,7 @@ var HOOKS = [
|
|
|
7054
7505
|
{ event: "beforeSubmitPrompt", hook: "preflight-reminder", why: "Name the required procedure before a filing/destructive/protocol turn." },
|
|
7055
7506
|
{ event: "beforeSubmitPrompt", hook: "intent-classifier-trigger", why: "Route capture / research / settings / defer intents to the right skill." },
|
|
7056
7507
|
{ event: "beforeSubmitPrompt", hook: "inject-communication-style", why: "Re-assert the plain-language communication rules each turn." },
|
|
7508
|
+
{ event: "beforeSubmitPrompt", hook: "inject-vault-context", timeout: 10, why: "Lexical vault retrieval for the prompt (surfaced as a user message)." },
|
|
7057
7509
|
// ── Before every tool call (any tool) ───────────────────────────────────
|
|
7058
7510
|
{ event: "preToolUse", matcher: ".*", hook: "emit-claim-status", args: ["--event=PreToolUse"], why: "Claim liveness heartbeat to the console." },
|
|
7059
7511
|
{ event: "preToolUse", matcher: ".*", hook: "coo-denylist-gate", why: "Hard deny the COO-window deny list." },
|
|
@@ -7064,21 +7516,27 @@ var HOOKS = [
|
|
|
7064
7516
|
// the vault guard because removing a profile file is the loss it exists to
|
|
7065
7517
|
// prevent, and Claude had no separate delete tool to list.
|
|
7066
7518
|
{ event: "preToolUse", matcher: "Write|Delete", hook: "env-vault-guard", why: "Block destructive writes to the encrypted env vault (ALWAYS ON)." },
|
|
7519
|
+
{ event: "preToolUse", matcher: "Write", hook: "vault-write-tier-guard", why: "Deny disk writes to vault specs in cloud write mode (HS-FCNH)." },
|
|
7067
7520
|
{ event: "preToolUse", matcher: "Write", hook: "settings-write-gate", why: "Require approval before .heyspex/config.yaml changes." },
|
|
7068
7521
|
{ event: "preToolUse", matcher: "Write", hook: "enforce-vault-naming", why: "Block vault filenames that violate the naming registry." },
|
|
7069
7522
|
{ event: "preToolUse", matcher: "Write", hook: "guard-auto-memory-writes", why: "Block new topic files in the auto-memory directory." },
|
|
7070
7523
|
{ event: "preToolUse", matcher: "Write", hook: "preflight-reminder", why: "Preflight reminder on the edit path itself." },
|
|
7071
7524
|
{ event: "preToolUse", matcher: "Write", hook: "claim-before-code-gate", why: "Deny the first code edit after plan exit with no claim." },
|
|
7525
|
+
{ event: "preToolUse", matcher: "Write", hook: "directive-obligation-gate", why: "Deny product-code writes while a capture_on_idea obligation is unmet, and (claim-before-build) with no claim/capture (HS-20SW)." },
|
|
7072
7526
|
// ── Before a shell command ──────────────────────────────────────────────
|
|
7073
7527
|
// beforeShellExecution, not preToolUse/Shell: it is the only event guaranteed
|
|
7074
7528
|
// to carry the command string, and Cursor honors allow/deny/ask on it.
|
|
7075
7529
|
{ event: "beforeShellExecution", hook: "env-vault-guard", why: "Block shell writes/exfiltration targeting the env vault (ALWAYS ON)." },
|
|
7530
|
+
{ event: "beforeShellExecution", hook: "vault-write-tier-guard", why: "Deny shell writes to vault specs in cloud write mode (HS-FCNH)." },
|
|
7076
7531
|
{ event: "beforeShellExecution", hook: "settings-write-gate", why: "Catch shell writes to .heyspex/config.yaml." },
|
|
7077
7532
|
{ event: "beforeShellExecution", hook: "enforce-vault-naming", why: "Catch vault files created via the shell." },
|
|
7078
7533
|
{ event: "beforeShellExecution", hook: "guard-auto-memory-writes", why: "Catch auto-memory topic files created via the shell." },
|
|
7079
7534
|
{ event: "beforeShellExecution", hook: "preflight-reminder", why: "Preflight reminder on rm -rf / force-push shaped commands." },
|
|
7080
7535
|
{ event: "beforeShellExecution", hook: "dev-server-task-reminder", timeout: 10, why: "Dev-server start/stop hard gate plus DB-safety reminder." },
|
|
7081
7536
|
{ event: "beforeShellExecution", hook: "git-stash-gate", timeout: 10, why: "Always ask before a mutating git stash." },
|
|
7537
|
+
{ event: "beforeShellExecution", hook: "git-identity-guard", why: "Agents never write git identity (owner ruling 2026-09-19, ALWAYS ON)." },
|
|
7538
|
+
{ event: "beforeShellExecution", hook: "capture-execution-gate", why: "Block dev-factory research/paid-model/routing shell commands past an ungrounded capture (HS-KNJ8, platform)." },
|
|
7539
|
+
{ event: "beforeShellExecution", hook: "directive-obligation-gate", why: "Deny build commands and product-code shell writes while a capture obligation is unmet (HS-20SW)." },
|
|
7082
7540
|
// ── Before specific MCP tools ───────────────────────────────────────────
|
|
7083
7541
|
// Cursor fires preToolUse for MCP tools too (matcher `MCP:<name>`), so these
|
|
7084
7542
|
// stay on preToolUse rather than beforeMCPExecution — registering both would
|
|
@@ -7088,15 +7546,20 @@ var HOOKS = [
|
|
|
7088
7546
|
{ event: "preToolUse", matcher: ".*", tool: "heyspex_file_spec$", hook: "capture-workflow-gate", timeout: 8, why: "Deny filing an implementation spec straight out of an open capture." },
|
|
7089
7547
|
{ event: "preToolUse", matcher: ".*", tool: "heyspex_settings_set$", hook: "settings-write-gate", why: "Require approval for settings changes made through MCP." },
|
|
7090
7548
|
{ event: "preToolUse", matcher: ".*", tool: "[Nn]eon__(prepare_database_migration|complete_database_migration|reset_from_parent|delete_branch)$", hook: "dev-server-task-reminder", timeout: 10, why: "DB-safety gate on Neon migration/reset/delete tools." },
|
|
7549
|
+
{ event: "preToolUse", matcher: ".*", tool: "heyspex_file_spec$", hook: "diagnose-capture-reminder", why: "Warn when a diagnose is filed with no capture holding the owner words (HS-IK2D)." },
|
|
7550
|
+
{ event: "preToolUse", matcher: ".*", tool: "^(Workflow|Skill)$", hook: "research-method-guard", why: "Research method guard on Skill/Workflow launches (HS-EN4F, ALWAYS ON)." },
|
|
7091
7551
|
{ event: "preToolUse", matcher: ".*", tool: "^(Workflow|Skill)$", hook: "deep-research-cost-gate", why: "Cost confirmation before a paid deep-research harness launch." },
|
|
7092
7552
|
{ event: "preToolUse", matcher: ".*", tool: "^(WebSearch|WebFetch|web_search|web_fetch)$|heyspex_save_research$", hook: "research-protocol-gate", why: "WebSearch/WebFetch/save_research refused until the research preflight ran (HS-K0ZP, HS-EN4F)." },
|
|
7093
7553
|
// ── Before a subagent spawn ─────────────────────────────────────────────
|
|
7094
7554
|
// subagentStart over preToolUse/Task: it carries subagent_type and the model,
|
|
7095
7555
|
// which is exactly what the routing gate decides on, and it can deny.
|
|
7096
7556
|
{ event: "subagentStart", hook: "agent-spawn-delegation-gate", why: "Block a build/breakdown/critic subagent on an off-routing model." },
|
|
7557
|
+
{ event: "subagentStart", hook: "capture-execution-gate", why: "Block a dev-factory subagent spawn past an ungrounded capture (HS-KNJ8, platform)." },
|
|
7558
|
+
{ event: "subagentStart", hook: "directive-obligation-gate", why: "Deny a build subagent while a capture obligation is unmet or the session has no claim/capture (HS-20SW)." },
|
|
7097
7559
|
// ── After every tool call ───────────────────────────────────────────────
|
|
7098
7560
|
{ event: "postToolUse", matcher: ".*", hook: "emit-claim-status", args: ["--event=PostToolUse"], why: "Claim liveness heartbeat after the call resolves." },
|
|
7099
7561
|
{ event: "postToolUse", matcher: ".*", hook: "log-session-activity", args: ["--event=PostToolUse"], why: "Append the tool result row to the session log." },
|
|
7562
|
+
{ event: "postToolUse", matcher: ".*", hook: "directive-obligation-gate", why: "Discharge the capture obligation on a capture write; track claim acquire/release (HS-20SW)." },
|
|
7100
7563
|
{ event: "postToolUse", matcher: ".*", hook: "close-terminal-on-ship", why: "Close the Launch terminal once the spec ships." },
|
|
7101
7564
|
{ event: "postToolUse", matcher: "Write", hook: "notify-status-flip", why: "Sound on an in_review status flip." },
|
|
7102
7565
|
{ event: "postToolUse", matcher: "Write", hook: "scripts/stamp-provenance-on-edit.mjs", why: "Stamp assisted_by provenance on direct spec edits." },
|
|
@@ -7109,7 +7572,11 @@ var HOOKS = [
|
|
|
7109
7572
|
// hook re-drives the agent — the same effect as Claude's exit-2 block.
|
|
7110
7573
|
{ event: "stop", hook: "emit-claim-status", args: ["--event=Stop"], why: "Mark the claim pending-on-response." },
|
|
7111
7574
|
{ event: "stop", hook: "naughty-list-gate", why: "Re-drive the turn when the final message uses banned jargon." },
|
|
7575
|
+
{ event: "stop", hook: "id-display-format-gate", why: "Re-drive the turn when a reply breaks the Naming V3 display-id format." },
|
|
7576
|
+
{ event: "stop", hook: "epic-milestone-phrasing-gate", why: "Re-drive the turn when a reply breaks the epic/milestone phrasing rules." },
|
|
7577
|
+
{ event: "stop", hook: "git-reflog-audit", why: "Session-close git audit backstop (stash/reset/rebase witness)." },
|
|
7112
7578
|
{ event: "stop", hook: "enforce-session-sequence", why: "Re-drive the turn when work shipped without record_wip." },
|
|
7579
|
+
{ event: "stop", hook: "directive-obligation-gate", why: "Re-drive the turn ONCE (followup_message) when a capture obligation is still unmet (HS-20SW)." },
|
|
7113
7580
|
// ── Session end ─────────────────────────────────────────────────────────
|
|
7114
7581
|
{ event: "sessionEnd", hook: "session-end", why: "Release the claim and notify the console." },
|
|
7115
7582
|
{ event: "sessionEnd", hook: "close-terminal-on-ship", why: "Final terminal cleanup." }
|
|
@@ -8747,6 +9214,7 @@ var INSTALL_HELP = [
|
|
|
8747
9214
|
" hs install [--target <dir>] [--platform <dir>] [--force] [--dry-run] [--json]",
|
|
8748
9215
|
" [--no-cursor] [--no-claude] [--claude-apply]",
|
|
8749
9216
|
" [--plugin | --plugin-apply] [--plugin-scope user|project|local]",
|
|
9217
|
+
" [--copilot-hooks | --copilot-plugin | --copilot-plugin-apply]",
|
|
8750
9218
|
" [--no-statusline]",
|
|
8751
9219
|
" [--cc | --command-code] [--register-vault [--workspace <id>]]",
|
|
8752
9220
|
" [--remote[=<url>]]",
|
|
@@ -8805,6 +9273,15 @@ var INSTALL_HELP = [
|
|
|
8805
9273
|
` the platform's .claude-plugin/marketplace.json (fallback: ${PLUGIN_INSTALL_SPEC}).`,
|
|
8806
9274
|
" --plugin-scope <s> Plugin install scope: user, project (default), or local.",
|
|
8807
9275
|
" Implies --plugin.",
|
|
9276
|
+
" --copilot-hooks Write .github/hooks/heyspex.json (GitHub Copilot hooks) into the",
|
|
9277
|
+
" target. Commands resolve node_modules/heyspex first, so an npm-only",
|
|
9278
|
+
" install (no heyspex repo checkout) works. Pick this OR the plugin.",
|
|
9279
|
+
" --copilot-plugin Print `copilot plugin marketplace add '<platform>'` +",
|
|
9280
|
+
" `copilot plugin install <plugin>@<marketplace>` (the platform root is",
|
|
9281
|
+
" node_modules/heyspex for a published install).",
|
|
9282
|
+
" --copilot-plugin-apply Run those two commands when `copilot` is on PATH.",
|
|
9283
|
+
" Requesting the Copilot plugin AND --copilot-hooks warns and skips the",
|
|
9284
|
+
" hooks file (both would fire every hook twice).",
|
|
8808
9285
|
" --remote[=<url>] Write .mcp.json for the REMOTE (cloud) MCP endpoint instead of",
|
|
8809
9286
|
` the local stdio server \u2014 the Streamable-HTTP form against`,
|
|
8810
9287
|
` ${DEFAULT_REMOTE_MCP_URL} (or <url>), authenticated by the`,
|
|
@@ -8853,15 +9330,15 @@ var INSTALL_HELP = [
|
|
|
8853
9330
|
|
|
8854
9331
|
// spex-console/src/lib/cli/install-identity.ts
|
|
8855
9332
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
8856
|
-
import { existsSync as
|
|
8857
|
-
import {
|
|
8858
|
-
import { join as join10 } from "node:path";
|
|
9333
|
+
import { existsSync as existsSync5 } from "node:fs";
|
|
9334
|
+
import { join as join11 } from "node:path";
|
|
8859
9335
|
import { createInterface } from "node:readline/promises";
|
|
8860
9336
|
init_credentials();
|
|
8861
9337
|
|
|
8862
9338
|
// vendor/heyspex-mcp/whoami.mjs
|
|
9339
|
+
init_heyspex_home();
|
|
8863
9340
|
init_credentials();
|
|
8864
|
-
import { homedir as
|
|
9341
|
+
import { homedir as homedir9 } from "node:os";
|
|
8865
9342
|
var SESSION_PATH = "/api/mcp/session";
|
|
8866
9343
|
async function probeCredentialIdentity({ consoleUrl, token, fetchImpl = fetch } = {}) {
|
|
8867
9344
|
if (!token) return { kind: "no_credential" };
|
|
@@ -9116,7 +9593,7 @@ async function runIdentityGate(io, flags, verb, wording = ATTACH_WORDING) {
|
|
|
9116
9593
|
"identity_unconfirmed"
|
|
9117
9594
|
);
|
|
9118
9595
|
}
|
|
9119
|
-
function readConfiguredAccountId(env = process.env, homeDir =
|
|
9596
|
+
function readConfiguredAccountId(env = process.env, homeDir = heyspexHome2()) {
|
|
9120
9597
|
return resolveSessionOwnerUncached({ env, homeDir });
|
|
9121
9598
|
}
|
|
9122
9599
|
function accountMismatchRepair(repoAccount) {
|
|
@@ -9145,7 +9622,7 @@ function isInteractiveTerminal() {
|
|
|
9145
9622
|
}
|
|
9146
9623
|
function defaultIdentityGateIo(opts) {
|
|
9147
9624
|
const consoleUrl = opts.consoleUrl.trim().replace(/\/+$/, "");
|
|
9148
|
-
const loginCli = [opts.platformRoot, resolveCodeRoot({})].filter((r) => Boolean(r && r.trim())).map((r) =>
|
|
9625
|
+
const loginCli = [opts.platformRoot, resolveCodeRoot({})].filter((r) => Boolean(r && r.trim())).map((r) => join11(r, "vendor", "heyspex-mcp", "cli.mjs")).find((p) => existsSync5(p));
|
|
9149
9626
|
return {
|
|
9150
9627
|
consoleUrl,
|
|
9151
9628
|
interactive: opts.interactive ?? isInteractiveTerminal(),
|
|
@@ -9393,7 +9870,7 @@ function remedyFor(row2, satellite) {
|
|
|
9393
9870
|
case "heyspex_yaml":
|
|
9394
9871
|
case "vault_registered":
|
|
9395
9872
|
case "workspace_registered":
|
|
9396
|
-
return satellite ? null : "hs connect";
|
|
9873
|
+
return satellite ? row2.remedy ?? null : "hs connect";
|
|
9397
9874
|
case "mcp_entry":
|
|
9398
9875
|
return satellite ? "hs install --attach-vault <vault-id> --force" : "hs connect";
|
|
9399
9876
|
case "plugin_surface":
|
|
@@ -9401,7 +9878,7 @@ function remedyFor(row2, satellite) {
|
|
|
9401
9878
|
case "git_hooks":
|
|
9402
9879
|
return "node scripts/install-git-hooks.mjs";
|
|
9403
9880
|
case "satellite_attachment":
|
|
9404
|
-
return "hs install --attach-vault <vault-id> --force";
|
|
9881
|
+
return row2.remedy ?? "hs install --attach-vault <vault-id> --force";
|
|
9405
9882
|
case "active_vault":
|
|
9406
9883
|
case "account_agreement":
|
|
9407
9884
|
case "workspace_agreement":
|
|
@@ -9424,7 +9901,10 @@ function formatReport(report) {
|
|
|
9424
9901
|
const lines = [
|
|
9425
9902
|
`hs connect --check \u2014 ${report.repo_root}`,
|
|
9426
9903
|
...report.satellite ? [
|
|
9427
|
-
`satellite repo \u2014 attached to vault ${report.satellite.attached_vault_id ?? "(unregistered)"} at ${report.satellite.attached_vault_root}; the vault rows below describe THAT repo
|
|
9904
|
+
`satellite repo \u2014 attached to vault ${report.satellite.attached_vault_id ?? "(unregistered)"} at ${report.satellite.attached_vault_root}; the vault rows below describe THAT repo.`,
|
|
9905
|
+
...report.satellite.via_marker ? [
|
|
9906
|
+
report.satellite.cloned ? "bound by this repo's committed heyspex.yaml (vault_id + relative vault_path)." : "bound by this repo's committed heyspex.yaml; vault not cloned locally -- cloud mode (cloud reads and writes work; clone the vault at its vault_path for local reads)."
|
|
9907
|
+
] : []
|
|
9428
9908
|
] : [],
|
|
9429
9909
|
"",
|
|
9430
9910
|
...report.checks.flatMap((c) => [
|
|
@@ -9504,7 +9984,7 @@ var CANONICAL_VAULT_SUBFOLDERS = [
|
|
|
9504
9984
|
function bareSpecsDirWarning(specsDir, deps) {
|
|
9505
9985
|
let entries = [];
|
|
9506
9986
|
try {
|
|
9507
|
-
entries =
|
|
9987
|
+
entries = readdirSync2(specsDir);
|
|
9508
9988
|
} catch {
|
|
9509
9989
|
return null;
|
|
9510
9990
|
}
|
|
@@ -9626,9 +10106,10 @@ async function runConnectFullRun(args, deps) {
|
|
|
9626
10106
|
};
|
|
9627
10107
|
}
|
|
9628
10108
|
if (!satellite && strictAccount.ok) {
|
|
9629
|
-
const
|
|
9630
|
-
|
|
9631
|
-
|
|
10109
|
+
const boundRow = loadVaultRegistry(deps.registryOpts).vaults.find(
|
|
10110
|
+
(v) => samePath(v.repo_root_abs, repoRoot)
|
|
10111
|
+
);
|
|
10112
|
+
const repoAccount = boundRow?.account_id && boundRow.account_id !== "legacy" ? boundRow.account_id : null;
|
|
9632
10113
|
if (repoAccount && repoAccount !== strictAccount.accountId) {
|
|
9633
10114
|
return accountMismatchRefusal("hs connect", repoAccount, strictAccount.accountId);
|
|
9634
10115
|
}
|
|
@@ -9731,37 +10212,14 @@ async function runConnectFullRun(args, deps) {
|
|
|
9731
10212
|
if (isPresent("heyspex_yaml")) {
|
|
9732
10213
|
const content = deps.fileExists(yamlPath) ? deps.readFile(yamlPath) : null;
|
|
9733
10214
|
vaultId = content ? parseVaultId(content) ?? void 0 : void 0;
|
|
9734
|
-
const
|
|
9735
|
-
|
|
9736
|
-
|
|
9737
|
-
|
|
9738
|
-
|
|
9739
|
-
|
|
9740
|
-
|
|
9741
|
-
|
|
9742
|
-
});
|
|
9743
|
-
} else if (args.dryRun) {
|
|
9744
|
-
steps.push({
|
|
9745
|
-
id: "heyspex_yaml",
|
|
9746
|
-
label: "heyspex.yaml vault marker",
|
|
9747
|
-
status: "run",
|
|
9748
|
-
detail: `would record account: ${accountId} in ${yamlPath}`
|
|
9749
|
-
});
|
|
9750
|
-
} else {
|
|
9751
|
-
try {
|
|
9752
|
-
deps.writeFile(yamlPath, accountUpdate.content);
|
|
9753
|
-
assertMarker(deps.readFile(yamlPath), yamlPath, vaultId, accountId);
|
|
9754
|
-
steps.push({
|
|
9755
|
-
id: "heyspex_yaml",
|
|
9756
|
-
label: "heyspex.yaml vault marker",
|
|
9757
|
-
status: "run",
|
|
9758
|
-
detail: `recorded account: ${accountId} in ${yamlPath}`,
|
|
9759
|
-
confirmed_by: "read heyspex.yaml"
|
|
9760
|
-
});
|
|
9761
|
-
} catch (err) {
|
|
9762
|
-
failStep("heyspex_yaml", "heyspex.yaml vault marker", err);
|
|
9763
|
-
}
|
|
9764
|
-
}
|
|
10215
|
+
const legacyAccount = content ? parseAccountId(content) : null;
|
|
10216
|
+
steps.push({
|
|
10217
|
+
id: "heyspex_yaml",
|
|
10218
|
+
label: "heyspex.yaml vault marker",
|
|
10219
|
+
status: "skipped",
|
|
10220
|
+
detail: legacyAccount ? `already initialized (legacy account: ${legacyAccount} in the marker is ignored -- the account binding is machine-local)` : "already initialized",
|
|
10221
|
+
confirmed_by: "read heyspex.yaml"
|
|
10222
|
+
});
|
|
9765
10223
|
} else if (args.dryRun) {
|
|
9766
10224
|
steps.push({
|
|
9767
10225
|
id: "heyspex_yaml",
|
|
@@ -9799,8 +10257,7 @@ async function runConnectFullRun(args, deps) {
|
|
|
9799
10257
|
vaultRoot: "specs/",
|
|
9800
10258
|
force: false,
|
|
9801
10259
|
json: false,
|
|
9802
|
-
adoptedVaultId
|
|
9803
|
-
...accountId ? { accountId } : {}
|
|
10260
|
+
adoptedVaultId
|
|
9804
10261
|
},
|
|
9805
10262
|
initDeps
|
|
9806
10263
|
);
|
|
@@ -10828,9 +11285,6 @@ function assertMarker(content, path7, vaultId, accountId) {
|
|
|
10828
11285
|
if (vaultId && back !== vaultId) {
|
|
10829
11286
|
throw new Error(`${path7} reads back vault_id ${back ?? "(none)"}, not ${vaultId}`);
|
|
10830
11287
|
}
|
|
10831
|
-
if (accountId && parseAccountId(content) !== accountId) {
|
|
10832
|
-
throw new Error(`${path7} does not record account: ${accountId} after the write`);
|
|
10833
|
-
}
|
|
10834
11288
|
}
|
|
10835
11289
|
var MIRRORED_SKILL_DIRS = [
|
|
10836
11290
|
[".claude", "skills"],
|
|
@@ -11201,21 +11655,21 @@ function runEntry(name, main2, options2 = {}) {
|
|
|
11201
11655
|
// spex-console/bin/hs-connect.ts
|
|
11202
11656
|
function readFile(p) {
|
|
11203
11657
|
try {
|
|
11204
|
-
return
|
|
11658
|
+
return readFileSync11(p, "utf8");
|
|
11205
11659
|
} catch {
|
|
11206
11660
|
return null;
|
|
11207
11661
|
}
|
|
11208
11662
|
}
|
|
11209
11663
|
function listDir(p) {
|
|
11210
11664
|
try {
|
|
11211
|
-
return
|
|
11665
|
+
return readdirSync3(p, { withFileTypes: true }).filter((e) => e.isFile()).map((e) => e.name);
|
|
11212
11666
|
} catch {
|
|
11213
11667
|
return [];
|
|
11214
11668
|
}
|
|
11215
11669
|
}
|
|
11216
11670
|
var packageRoot = path6.resolve(path6.dirname(fileURLToPath4(import.meta.url)), "..", "..");
|
|
11217
11671
|
var satelliteWalkDeps = {
|
|
11218
|
-
fileExists: (p) =>
|
|
11672
|
+
fileExists: (p) => existsSync6(p),
|
|
11219
11673
|
readFile,
|
|
11220
11674
|
joinPath: (...segments) => path6.join(...segments),
|
|
11221
11675
|
parentDir: (p) => path6.dirname(p),
|
|
@@ -11272,11 +11726,11 @@ async function main() {
|
|
|
11272
11726
|
repoRoot,
|
|
11273
11727
|
cwd,
|
|
11274
11728
|
platformRoot: path6.resolve(platformRoot),
|
|
11275
|
-
fileExists: (p) =>
|
|
11729
|
+
fileExists: (p) => existsSync6(p),
|
|
11276
11730
|
readFile,
|
|
11277
11731
|
listDir,
|
|
11278
|
-
writeFile: (p, c) =>
|
|
11279
|
-
mkdir: (p) =>
|
|
11732
|
+
writeFile: (p, c) => writeFileSync6(p, c, "utf8"),
|
|
11733
|
+
mkdir: (p) => mkdirSync6(p, { recursive: true }),
|
|
11280
11734
|
copyFile: (src, dest) => copyFileSync(src, dest),
|
|
11281
11735
|
isDirectory: (p) => {
|
|
11282
11736
|
try {
|