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
|
@@ -59,11 +59,26 @@ var init_hs_command = __esm({
|
|
|
59
59
|
}
|
|
60
60
|
});
|
|
61
61
|
|
|
62
|
+
// scripts/lib/heyspex-home.mjs
|
|
63
|
+
import { homedir } from "node:os";
|
|
64
|
+
function heyspexHome(env = process.env) {
|
|
65
|
+
const override = env?.HEYSPEX_HOME;
|
|
66
|
+
if (typeof override === "string" && override.trim().length > 0) {
|
|
67
|
+
return override.trim();
|
|
68
|
+
}
|
|
69
|
+
return homedir();
|
|
70
|
+
}
|
|
71
|
+
var init_heyspex_home = __esm({
|
|
72
|
+
"scripts/lib/heyspex-home.mjs"() {
|
|
73
|
+
"use strict";
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
|
|
62
77
|
// vendor/heyspex-mcp/credentials.mjs
|
|
63
78
|
import { mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
64
|
-
import os, { homedir } from "node:os";
|
|
79
|
+
import os, { homedir as homedir2 } from "node:os";
|
|
65
80
|
import { dirname as dirname2, join as join2, resolve } from "node:path";
|
|
66
|
-
function credentialsFilePath(homeDir =
|
|
81
|
+
function credentialsFilePath(homeDir = heyspexHome()) {
|
|
67
82
|
return join2(homeDir, ".heyspex", "credentials.yaml");
|
|
68
83
|
}
|
|
69
84
|
function parseMcpToken(raw) {
|
|
@@ -159,7 +174,7 @@ function readLegacyConfigAccountId(read, homeDir) {
|
|
|
159
174
|
const value = raw?.match(CONFIG_ACCOUNT_ID_RE)?.[1]?.trim() ?? "";
|
|
160
175
|
return value || null;
|
|
161
176
|
}
|
|
162
|
-
function readCurrentAccountId({ homeDir =
|
|
177
|
+
function readCurrentAccountId({ homeDir = heyspexHome(), readFile } = {}) {
|
|
163
178
|
const read = readFile ?? ((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) {
|
|
@@ -172,6 +187,7 @@ var MCP_TOKEN_LINE_RE, CREDENTIALS_SCHEMA_VERSION, LEGACY_ACCOUNT_ID, ACCOUNT_FI
|
|
|
172
187
|
var init_credentials = __esm({
|
|
173
188
|
"vendor/heyspex-mcp/credentials.mjs"() {
|
|
174
189
|
"use strict";
|
|
190
|
+
init_heyspex_home();
|
|
175
191
|
MCP_TOKEN_LINE_RE = /^mcp_token:[ \t]*["']?([^"'\r\n#]+)/m;
|
|
176
192
|
CREDENTIALS_SCHEMA_VERSION = 2;
|
|
177
193
|
LEGACY_ACCOUNT_ID = "legacy";
|
|
@@ -183,9 +199,9 @@ var init_credentials = __esm({
|
|
|
183
199
|
|
|
184
200
|
// vendor/heyspex-mcp/session-owner.mjs
|
|
185
201
|
import { readFileSync as readFileSync2 } from "node:fs";
|
|
186
|
-
import { homedir as
|
|
202
|
+
import { homedir as homedir3 } from "node:os";
|
|
187
203
|
import { join as join3 } from "node:path";
|
|
188
|
-
function resolveSessionOwnerUncached({ env = process.env, homeDir =
|
|
204
|
+
function resolveSessionOwnerUncached({ env = process.env, homeDir = heyspexHome(), readFile } = {}) {
|
|
189
205
|
const fromEnv = typeof env?.HS_ACCOUNT_ID === "string" ? env.HS_ACCOUNT_ID.trim() : "";
|
|
190
206
|
if (fromEnv) return fromEnv;
|
|
191
207
|
return readCurrentAccountId({ homeDir, readFile });
|
|
@@ -193,6 +209,7 @@ function resolveSessionOwnerUncached({ env = process.env, homeDir = homedir2(),
|
|
|
193
209
|
var init_session_owner = __esm({
|
|
194
210
|
"vendor/heyspex-mcp/session-owner.mjs"() {
|
|
195
211
|
"use strict";
|
|
212
|
+
init_heyspex_home();
|
|
196
213
|
init_credentials();
|
|
197
214
|
}
|
|
198
215
|
});
|
|
@@ -227,20 +244,47 @@ var init_vault_less_endpoints = __esm({
|
|
|
227
244
|
}
|
|
228
245
|
});
|
|
229
246
|
|
|
247
|
+
// scripts/lib/shared-path-helpers.mjs
|
|
248
|
+
import { isAbsolute, resolve as resolve2, sep } from "node:path";
|
|
249
|
+
var init_shared_path_helpers = __esm({
|
|
250
|
+
"scripts/lib/shared-path-helpers.mjs"() {
|
|
251
|
+
"use strict";
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
// vendor/heyspex-mcp/write-outbox.mjs
|
|
256
|
+
import {
|
|
257
|
+
readdirSync,
|
|
258
|
+
readFileSync as readFileSync3,
|
|
259
|
+
writeFileSync as writeFileSync2,
|
|
260
|
+
mkdirSync as mkdirSync2,
|
|
261
|
+
existsSync as existsSync2,
|
|
262
|
+
renameSync as renameSync2,
|
|
263
|
+
rmSync
|
|
264
|
+
} from "node:fs";
|
|
265
|
+
import { basename, join as join4, isAbsolute as isAbsolute2 } from "node:path";
|
|
266
|
+
var init_write_outbox = __esm({
|
|
267
|
+
"vendor/heyspex-mcp/write-outbox.mjs"() {
|
|
268
|
+
"use strict";
|
|
269
|
+
init_shared_path_helpers();
|
|
270
|
+
}
|
|
271
|
+
});
|
|
272
|
+
|
|
230
273
|
// vendor/heyspex-mcp/vault-resolve.mjs
|
|
231
|
-
import { existsSync as
|
|
232
|
-
import { homedir as
|
|
233
|
-
import { dirname as dirname3, join as
|
|
274
|
+
import { existsSync as existsSync3, readFileSync as readFileSync4 } from "node:fs";
|
|
275
|
+
import { homedir as homedir4, tmpdir } from "node:os";
|
|
276
|
+
import { dirname as dirname3, join as join5, resolve as resolve3 } from "node:path";
|
|
234
277
|
var init_vault_resolve = __esm({
|
|
235
278
|
"vendor/heyspex-mcp/vault-resolve.mjs"() {
|
|
236
279
|
"use strict";
|
|
280
|
+
init_heyspex_home();
|
|
237
281
|
}
|
|
238
282
|
});
|
|
239
283
|
|
|
240
284
|
// vendor/heyspex-mcp/console-fetch.mjs
|
|
241
|
-
import { readFileSync as
|
|
242
|
-
import { homedir as
|
|
243
|
-
import { join as
|
|
285
|
+
import { readFileSync as readFileSync5 } from "node:fs";
|
|
286
|
+
import { homedir as homedir5 } from "node:os";
|
|
287
|
+
import { join as join6 } from "node:path";
|
|
244
288
|
function isCloudVaultId(value) {
|
|
245
289
|
return typeof value === "string" && CLOUD_VAULT_ID_RE.test(value.trim());
|
|
246
290
|
}
|
|
@@ -248,16 +292,33 @@ var CLOUD_VAULT_ID_RE, SCOPE_REFUSAL_CODES;
|
|
|
248
292
|
var init_console_fetch = __esm({
|
|
249
293
|
"vendor/heyspex-mcp/console-fetch.mjs"() {
|
|
250
294
|
"use strict";
|
|
295
|
+
init_heyspex_home();
|
|
251
296
|
init_credentials();
|
|
252
297
|
init_session_owner();
|
|
253
298
|
init_vault_less_endpoints();
|
|
254
299
|
init_hs_command();
|
|
300
|
+
init_write_outbox();
|
|
255
301
|
init_vault_resolve();
|
|
256
302
|
CLOUD_VAULT_ID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
257
303
|
SCOPE_REFUSAL_CODES = Object.freeze(["VAULT_SCOPE_MISMATCH", "ACCOUNT_MISMATCH", "VAULT_NOT_REGISTERED"]);
|
|
258
304
|
}
|
|
259
305
|
});
|
|
260
306
|
|
|
307
|
+
// spex-console/src/lib/heyspex-home.ts
|
|
308
|
+
import { homedir as homedir6 } from "node:os";
|
|
309
|
+
function heyspexHome2(env = process.env) {
|
|
310
|
+
const override = env?.HEYSPEX_HOME;
|
|
311
|
+
if (typeof override === "string" && override.trim().length > 0) {
|
|
312
|
+
return override.trim();
|
|
313
|
+
}
|
|
314
|
+
return homedir6();
|
|
315
|
+
}
|
|
316
|
+
var init_heyspex_home2 = __esm({
|
|
317
|
+
"spex-console/src/lib/heyspex-home.ts"() {
|
|
318
|
+
"use strict";
|
|
319
|
+
}
|
|
320
|
+
});
|
|
321
|
+
|
|
261
322
|
// spex-console/src/lib/vault-registry/types.ts
|
|
262
323
|
function emptyVaultRegistry() {
|
|
263
324
|
return {
|
|
@@ -278,14 +339,13 @@ var init_types = __esm({
|
|
|
278
339
|
});
|
|
279
340
|
|
|
280
341
|
// spex-console/src/lib/vault-registry/registry-file.ts
|
|
281
|
-
import { existsSync as
|
|
282
|
-
import {
|
|
283
|
-
import { dirname as dirname4, join as join6, resolve as resolve3 } from "node:path";
|
|
342
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync6, renameSync as renameSync3, writeFileSync as writeFileSync3 } from "node:fs";
|
|
343
|
+
import { dirname as dirname4, join as join7, resolve as resolve4 } from "node:path";
|
|
284
344
|
function vaultRegistryDir(opts = {}) {
|
|
285
|
-
return opts.registryDir ??
|
|
345
|
+
return opts.registryDir ?? join7(heyspexHome2(), ".heyspex");
|
|
286
346
|
}
|
|
287
347
|
function vaultRegistryFilePath(opts = {}) {
|
|
288
|
-
return
|
|
348
|
+
return join7(vaultRegistryDir(opts), "registry.json");
|
|
289
349
|
}
|
|
290
350
|
function isNonEmptyString(v) {
|
|
291
351
|
return typeof v === "string" && v.trim().length > 0;
|
|
@@ -386,9 +446,9 @@ function parseVaultRegistryFile(json) {
|
|
|
386
446
|
}
|
|
387
447
|
function readRegistryJson(opts) {
|
|
388
448
|
const filePath = vaultRegistryFilePath(opts);
|
|
389
|
-
if (!
|
|
449
|
+
if (!existsSync4(filePath)) return { kind: "missing" };
|
|
390
450
|
try {
|
|
391
|
-
const raw =
|
|
451
|
+
const raw = readFileSync6(filePath, "utf8");
|
|
392
452
|
const json = JSON.parse(raw);
|
|
393
453
|
if (registryFileSchemaOf(json) === null) return { kind: "unreadable" };
|
|
394
454
|
return { kind: "ok", raw, json };
|
|
@@ -417,17 +477,18 @@ function loadVaultRegistry(opts = {}) {
|
|
|
417
477
|
var init_registry_file = __esm({
|
|
418
478
|
"spex-console/src/lib/vault-registry/registry-file.ts"() {
|
|
419
479
|
"use strict";
|
|
480
|
+
init_heyspex_home2();
|
|
420
481
|
init_types();
|
|
421
482
|
init_types();
|
|
422
483
|
}
|
|
423
484
|
});
|
|
424
485
|
|
|
425
486
|
// scripts/lib/dev-mode.mjs
|
|
426
|
-
import { mkdirSync as
|
|
427
|
-
import { homedir as
|
|
428
|
-
import { join as
|
|
429
|
-
function devModeFilePath(homeDir =
|
|
430
|
-
return
|
|
487
|
+
import { mkdirSync as mkdirSync4, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "node:fs";
|
|
488
|
+
import { homedir as homedir7 } from "node:os";
|
|
489
|
+
import { join as join8 } from "node:path";
|
|
490
|
+
function devModeFilePath(homeDir = heyspexHome()) {
|
|
491
|
+
return join8(homeDir, ".heyspex", DEV_MODE_FILENAME);
|
|
431
492
|
}
|
|
432
493
|
function normalizeMode(raw) {
|
|
433
494
|
const v = String(raw ?? "").trim().toLowerCase();
|
|
@@ -451,7 +512,7 @@ function deriveSemantics(mode, reliance) {
|
|
|
451
512
|
vaultWriteMode: local ? "local" : "cloud"
|
|
452
513
|
};
|
|
453
514
|
}
|
|
454
|
-
function readLocalDevModeRecord({ homeDir =
|
|
515
|
+
function readLocalDevModeRecord({ homeDir = heyspexHome(), readFile = defaultReadFile } = {}) {
|
|
455
516
|
let raw;
|
|
456
517
|
try {
|
|
457
518
|
raw = readFile(devModeFilePath(homeDir));
|
|
@@ -481,7 +542,7 @@ function readLocalDevModeRecord({ homeDir = homedir6(), readFile = defaultReadFi
|
|
|
481
542
|
}
|
|
482
543
|
function resolveDevMode({
|
|
483
544
|
env = process.env,
|
|
484
|
-
homeDir =
|
|
545
|
+
homeDir = heyspexHome(),
|
|
485
546
|
readFile = defaultReadFile,
|
|
486
547
|
cloudSetting = void 0
|
|
487
548
|
} = {}) {
|
|
@@ -544,18 +605,19 @@ var CLOUD_CONSOLE_URL, LOCAL_CONSOLE_URL, DEV_MODE_FILENAME, defaultReadFile;
|
|
|
544
605
|
var init_dev_mode = __esm({
|
|
545
606
|
"scripts/lib/dev-mode.mjs"() {
|
|
546
607
|
"use strict";
|
|
608
|
+
init_heyspex_home();
|
|
547
609
|
init_credentials();
|
|
548
610
|
CLOUD_CONSOLE_URL = "https://app.heyspex.com";
|
|
549
611
|
LOCAL_CONSOLE_URL = "http://localhost:3000";
|
|
550
612
|
DEV_MODE_FILENAME = "dev-mode.json";
|
|
551
|
-
defaultReadFile = (p) =>
|
|
613
|
+
defaultReadFile = (p) => readFileSync7(p, "utf8");
|
|
552
614
|
}
|
|
553
615
|
});
|
|
554
616
|
|
|
555
617
|
// scripts/lib/console-url-resolver.mjs
|
|
556
|
-
import { readFileSync as
|
|
557
|
-
import { homedir as
|
|
558
|
-
import { join as
|
|
618
|
+
import { readFileSync as readFileSync8 } from "node:fs";
|
|
619
|
+
import { homedir as homedir8 } from "node:os";
|
|
620
|
+
import { join as join9 } from "node:path";
|
|
559
621
|
function trimTrailingSlash(url) {
|
|
560
622
|
return url.replace(/\/+$/, "");
|
|
561
623
|
}
|
|
@@ -578,9 +640,9 @@ function warnStaleLoopbackCredentialsOnce(credentialsUrl, derivedUrl, { log }) {
|
|
|
578
640
|
} catch {
|
|
579
641
|
}
|
|
580
642
|
}
|
|
581
|
-
function readCredentialsConsoleUrl({ homeDir =
|
|
643
|
+
function readCredentialsConsoleUrl({ homeDir = heyspexHome(), readFile = defaultReadFile2 } = {}) {
|
|
582
644
|
try {
|
|
583
|
-
const raw = readFile(
|
|
645
|
+
const raw = readFile(join9(homeDir, ".heyspex", "credentials.yaml"));
|
|
584
646
|
const match = String(raw ?? "").match(/^console_url:\s*["']?([^"'\r\n#]+)/m);
|
|
585
647
|
const value = match?.[1]?.trim();
|
|
586
648
|
return value && value.length > 0 ? trimTrailingSlash(value) : null;
|
|
@@ -589,12 +651,12 @@ function readCredentialsConsoleUrl({ homeDir = homedir7(), readFile = defaultRea
|
|
|
589
651
|
}
|
|
590
652
|
}
|
|
591
653
|
function defaultReadFile2(p) {
|
|
592
|
-
return
|
|
654
|
+
return readFileSync8(p, "utf8");
|
|
593
655
|
}
|
|
594
656
|
function readDiscoveryFileConsoleUrl(repoRoot, { readDiscovery: readDiscovery2 = defaultReadFile2 } = {}) {
|
|
595
657
|
if (!repoRoot) return null;
|
|
596
658
|
try {
|
|
597
|
-
const discovery = String(readDiscovery2(
|
|
659
|
+
const discovery = String(readDiscovery2(join9(repoRoot, ".heyspex", "console-url")) ?? "").trim();
|
|
598
660
|
if (discovery.length > 0) return trimTrailingSlash(discovery);
|
|
599
661
|
} catch {
|
|
600
662
|
}
|
|
@@ -614,7 +676,7 @@ function resolveModeEnvelope({ env, homeDir }) {
|
|
|
614
676
|
function resolveConsoleUrl({
|
|
615
677
|
repoRoot,
|
|
616
678
|
env = process.env,
|
|
617
|
-
homeDir =
|
|
679
|
+
homeDir = heyspexHome(),
|
|
618
680
|
strict = false,
|
|
619
681
|
devMode,
|
|
620
682
|
log = (line) => process.stderr.write(line),
|
|
@@ -647,6 +709,7 @@ var DEFAULT_CONSOLE_URL, LOOPBACK_HOSTS, staleLoopbackCredentialsWarned, Console
|
|
|
647
709
|
var init_console_url_resolver = __esm({
|
|
648
710
|
"scripts/lib/console-url-resolver.mjs"() {
|
|
649
711
|
"use strict";
|
|
712
|
+
init_heyspex_home();
|
|
650
713
|
init_dev_mode();
|
|
651
714
|
DEFAULT_CONSOLE_URL = LOCAL_CONSOLE_URL;
|
|
652
715
|
LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "::1", "[::1]", "0.0.0.0"]);
|
|
@@ -700,13 +763,12 @@ var init_default_workspace = __esm({
|
|
|
700
763
|
});
|
|
701
764
|
|
|
702
765
|
// spex-console/src/lib/cli/vault-workspace-shared.ts
|
|
703
|
-
import { readFileSync as
|
|
704
|
-
import os2 from "node:os";
|
|
766
|
+
import { readFileSync as readFileSync9 } from "node:fs";
|
|
705
767
|
function resolveConfiguredAccountId(opts = {}) {
|
|
706
768
|
return resolveSessionOwnerUncached({
|
|
707
769
|
env: opts.env ?? process.env,
|
|
708
|
-
homeDir: opts.homeDir ??
|
|
709
|
-
readFile: opts.readFile ?? ((p) =>
|
|
770
|
+
homeDir: opts.homeDir ?? heyspexHome2(),
|
|
771
|
+
readFile: opts.readFile ?? ((p) => readFileSync9(p, "utf-8"))
|
|
710
772
|
});
|
|
711
773
|
}
|
|
712
774
|
function resolveCloudAccountIdOrRefuse(opts = {}) {
|
|
@@ -739,6 +801,7 @@ var CLOUD_ACCOUNT_ID_REQUIRED_MESSAGE;
|
|
|
739
801
|
var init_vault_workspace_shared = __esm({
|
|
740
802
|
"spex-console/src/lib/cli/vault-workspace-shared.ts"() {
|
|
741
803
|
"use strict";
|
|
804
|
+
init_heyspex_home2();
|
|
742
805
|
init_session_owner();
|
|
743
806
|
init_default_workspace();
|
|
744
807
|
CLOUD_ACCOUNT_ID_REQUIRED_MESSAGE = "refusing -- no signed-in account (no HS_ACCOUNT_ID env var, and no account_id: in ~/.heyspex/config.yaml). Run `hs login` first; nothing was written.";
|
|
@@ -1225,11 +1288,11 @@ var require_excerpt = __commonJS({
|
|
|
1225
1288
|
if (typeof opts.excerpt === "function") {
|
|
1226
1289
|
return opts.excerpt(file, opts);
|
|
1227
1290
|
}
|
|
1228
|
-
const
|
|
1229
|
-
if (
|
|
1291
|
+
const sep2 = file.data.excerpt_separator || opts.excerpt_separator;
|
|
1292
|
+
if (sep2 == null && (opts.excerpt === false || opts.excerpt == null)) {
|
|
1230
1293
|
return file;
|
|
1231
1294
|
}
|
|
1232
|
-
const delimiter2 = typeof opts.excerpt === "string" ? opts.excerpt :
|
|
1295
|
+
const delimiter2 = typeof opts.excerpt === "string" ? opts.excerpt : sep2 || opts.delimiters[0];
|
|
1233
1296
|
const idx = file.content.indexOf(delimiter2);
|
|
1234
1297
|
if (idx !== -1) {
|
|
1235
1298
|
file.excerpt = file.content.slice(0, idx);
|
|
@@ -1294,7 +1357,7 @@ var require_parse = __commonJS({
|
|
|
1294
1357
|
var require_gray_matter = __commonJS({
|
|
1295
1358
|
"node_modules/gray-matter/index.js"(exports2, module2) {
|
|
1296
1359
|
"use strict";
|
|
1297
|
-
var
|
|
1360
|
+
var fs11 = __require("fs");
|
|
1298
1361
|
var sections = require_section_matter();
|
|
1299
1362
|
var defaults = require_defaults();
|
|
1300
1363
|
var stringify = require_stringify();
|
|
@@ -1378,7 +1441,7 @@ var require_gray_matter = __commonJS({
|
|
|
1378
1441
|
return stringify(file, data, options2);
|
|
1379
1442
|
};
|
|
1380
1443
|
matter4.read = function(filepath, options2) {
|
|
1381
|
-
const str3 =
|
|
1444
|
+
const str3 = fs11.readFileSync(filepath, "utf8");
|
|
1382
1445
|
const file = matter4(str3, options2);
|
|
1383
1446
|
file.path = filepath;
|
|
1384
1447
|
return file;
|
|
@@ -1853,7 +1916,7 @@ var init_diagnose_skeleton = __esm({
|
|
|
1853
1916
|
});
|
|
1854
1917
|
|
|
1855
1918
|
// dist/cli/._sql-orm-inert.cli-shim.mjs
|
|
1856
|
-
var inert, customType, sql, and, eq, isNull, inArray, bigint, bigserial, boolean, foreignKey, index, integer, jsonb, pgEnum, pgTable, primaryKey, text, timestamp, uniqueIndex;
|
|
1919
|
+
var inert, customType, sql, and, eq, isNull, desc, gt, inArray, bigint, bigserial, boolean, foreignKey, index, integer, jsonb, pgEnum, pgTable, primaryKey, text, timestamp, uniqueIndex;
|
|
1857
1920
|
var init_sql_orm_inert_cli_shim = __esm({
|
|
1858
1921
|
"dist/cli/._sql-orm-inert.cli-shim.mjs"() {
|
|
1859
1922
|
"use strict";
|
|
@@ -1876,6 +1939,8 @@ var init_sql_orm_inert_cli_shim = __esm({
|
|
|
1876
1939
|
and = inert;
|
|
1877
1940
|
eq = inert;
|
|
1878
1941
|
isNull = inert;
|
|
1942
|
+
desc = inert;
|
|
1943
|
+
gt = inert;
|
|
1879
1944
|
inArray = inert;
|
|
1880
1945
|
bigint = inert;
|
|
1881
1946
|
bigserial = inert;
|
|
@@ -1904,7 +1969,7 @@ var init_interfaces = __esm({
|
|
|
1904
1969
|
});
|
|
1905
1970
|
|
|
1906
1971
|
// spex-console/src/lib/db/schema/postgres.ts
|
|
1907
|
-
var edgeTypeEnum, validationSeverityEnum, outboxOpEnum, tsvector, specs, tags, edges, wikilinks, specLinks, aliases, validationIssues, indexMeta, frontmatterFields, eventOutbox, captureSessions, handoffContext, claimEventKindEnum, activeClaimStatusEnum, pendingReasonEnum, focusEventKindEnum, specEventKindEnum, focusPin, captureFocusPin, captureFocusSelection, focusSelection, focusSkips, activeClaims, claimWaiters, fileClaims, claimEvents, focusEvents, focusAreas, focusAreasEvents, specEvents, rateLimitBuckets, userVaults, milestoneSnapshots, modelRegistryEntries, launchIntentStatusEnum, launchIntents, companionRelayPresence, companionSettingsStore, consoleSettingsStore, wipLedger, wipLedgerArchive, gatherResults, agentSessions, usageEvents, streamEvents, jobStreams, resolutionStatusEnum, resolutionFocusPin, resolutionLedger, workspaces, vaults, accountSettings, vaultGrants, pendingVaultGrants, githubInstallations, fleetMachines, fleetTasks, fleetResults, userApiKeys, apiKeys, mcpSessions, pairingCodes, companionSessions, inviteRequests, notifications, webhookIngestions, specSettlements;
|
|
1972
|
+
var edgeTypeEnum, validationSeverityEnum, outboxOpEnum, tsvector, specs, tags, edges, wikilinks, specLinks, aliases, validationIssues, indexMeta, frontmatterFields, eventOutbox, captureSessions, handoffContext, claimEventKindEnum, activeClaimStatusEnum, pendingReasonEnum, focusEventKindEnum, specEventKindEnum, focusPin, captureFocusPin, captureFocusSelection, focusSelection, focusSkips, activeClaims, claimWaiters, fileClaims, claimEvents, focusEvents, focusAreas, focusAreasEvents, specEvents, rateLimitBuckets, userVaults, milestoneSnapshots, modelRegistryEntries, launchIntentStatusEnum, launchIntents, companionRelayPresence, companionSettingsStore, consoleSettingsStore, wipLedger, wipLedgerArchive, gatherResults, agentSessions, usageEvents, streamEvents, jobStreams, resolutionStatusEnum, resolutionFocusPin, resolutionLedger, workspaces, vaults, accountSettings, vaultGrants, pendingVaultGrants, githubInstallations, fleetMachines, fleetTasks, fleetResults, userApiKeys, apiKeys, mcpSessions, pairingCodes, companionSessions, inviteRequests, notifications, webhookIngestions, specSettlements, sessionBranchDeclarations;
|
|
1908
1973
|
var init_postgres = __esm({
|
|
1909
1974
|
"spex-console/src/lib/db/schema/postgres.ts"() {
|
|
1910
1975
|
"use strict";
|
|
@@ -2346,7 +2411,13 @@ var init_postgres = __esm({
|
|
|
2346
2411
|
"priority_change",
|
|
2347
2412
|
"parent_change",
|
|
2348
2413
|
"claim_acquired",
|
|
2349
|
-
"claim_released"
|
|
2414
|
+
"claim_released",
|
|
2415
|
+
// Collaborator write audit (HS-FWB6, migration 0077).
|
|
2416
|
+
"spec_created",
|
|
2417
|
+
"spec_edited",
|
|
2418
|
+
"spec_deleted",
|
|
2419
|
+
"capture_written",
|
|
2420
|
+
"wip_recorded"
|
|
2350
2421
|
]);
|
|
2351
2422
|
focusPin = pgTable(
|
|
2352
2423
|
"focus_pin",
|
|
@@ -3539,11 +3610,43 @@ var init_postgres = __esm({
|
|
|
3539
3610
|
// `owner/name`
|
|
3540
3611
|
branch: text("branch").notNull(),
|
|
3541
3612
|
commitSha: text("commit_sha").notNull(),
|
|
3542
|
-
settledAt: timestamp("settled_at", { withTimezone: true }).notNull().default(sql`now()`)
|
|
3613
|
+
settledAt: timestamp("settled_at", { withTimezone: true }).notNull().default(sql`now()`),
|
|
3614
|
+
// The pull request that carries this settlement
|
|
3615
|
+
// ([[HS-9D25-CWPX-EPIC-branch-lifecycle-open-a-pull-request]]). NULL until
|
|
3616
|
+
// the branch has gone quiet and a PR was opened or found; a write made
|
|
3617
|
+
// after its PR merged stays NULL, which is what triggers the next PR.
|
|
3618
|
+
pullRequestNumber: integer("pull_request_number"),
|
|
3619
|
+
pullRequestUrl: text("pull_request_url")
|
|
3543
3620
|
},
|
|
3544
3621
|
(table) => [
|
|
3545
3622
|
// "latest settlement for this spec" — the read path's only query.
|
|
3546
|
-
index("spec_settlements_spec_settled_idx").on(table.specId, table.settledAt.desc())
|
|
3623
|
+
index("spec_settlements_spec_settled_idx").on(table.specId, table.settledAt.desc()),
|
|
3624
|
+
// The PR sweep's only query: branches with settlements not yet in a PR.
|
|
3625
|
+
index("spec_settlements_unproposed_idx").on(table.vaultId, table.repo, table.branch).where(sql`${table.pullRequestNumber} IS NULL`)
|
|
3626
|
+
]
|
|
3627
|
+
);
|
|
3628
|
+
sessionBranchDeclarations = pgTable(
|
|
3629
|
+
"session_branch_declarations",
|
|
3630
|
+
{
|
|
3631
|
+
id: integer("id").primaryKey().generatedAlwaysAsIdentity(),
|
|
3632
|
+
vaultId: text("vault_id").notNull().default(DEFAULT_VAULT_ID),
|
|
3633
|
+
actorKey: text("actor_key").notNull(),
|
|
3634
|
+
sessionId: text("session_id").notNull(),
|
|
3635
|
+
branch: text("branch").notNull(),
|
|
3636
|
+
declaredAt: timestamp("declared_at", { withTimezone: true }).notNull().default(sql`now()`),
|
|
3637
|
+
expiresAt: timestamp("expires_at", { withTimezone: true }).notNull()
|
|
3638
|
+
},
|
|
3639
|
+
(table) => [
|
|
3640
|
+
uniqueIndex("session_branch_declarations_uq").on(
|
|
3641
|
+
table.vaultId,
|
|
3642
|
+
table.actorKey,
|
|
3643
|
+
table.sessionId
|
|
3644
|
+
),
|
|
3645
|
+
index("session_branch_declarations_lookup_idx").on(
|
|
3646
|
+
table.vaultId,
|
|
3647
|
+
table.actorKey,
|
|
3648
|
+
table.declaredAt.desc()
|
|
3649
|
+
)
|
|
3547
3650
|
]
|
|
3548
3651
|
);
|
|
3549
3652
|
}
|
|
@@ -3709,7 +3812,7 @@ var init_satellite = __esm({
|
|
|
3709
3812
|
});
|
|
3710
3813
|
|
|
3711
3814
|
// spex-console/src/lib/vault-resolve/index.ts
|
|
3712
|
-
import { existsSync as
|
|
3815
|
+
import { existsSync as existsSync5, readFileSync as readFileSync11 } from "node:fs";
|
|
3713
3816
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
3714
3817
|
import path5 from "node:path";
|
|
3715
3818
|
var init_vault_resolve2 = __esm({
|
|
@@ -3748,7 +3851,7 @@ var init_resolve_status = __esm({
|
|
|
3748
3851
|
});
|
|
3749
3852
|
|
|
3750
3853
|
// spex-console/src/lib/vault-resolve/active.ts
|
|
3751
|
-
import { existsSync as
|
|
3854
|
+
import { existsSync as existsSync6 } from "node:fs";
|
|
3752
3855
|
import path6 from "node:path";
|
|
3753
3856
|
function fallbackSpecsDir() {
|
|
3754
3857
|
const override = process.env.HEYSPEX_VAULT_DIR;
|
|
@@ -3969,8 +4072,8 @@ function singleFlightDebounce(fn, delayMs) {
|
|
|
3969
4072
|
})();
|
|
3970
4073
|
}
|
|
3971
4074
|
return function debounced() {
|
|
3972
|
-
return new Promise((
|
|
3973
|
-
const resolver = { resolve:
|
|
4075
|
+
return new Promise((resolve5, reject) => {
|
|
4076
|
+
const resolver = { resolve: resolve5, reject };
|
|
3974
4077
|
if (inFlight) {
|
|
3975
4078
|
if (queuedResolvers) {
|
|
3976
4079
|
queuedResolvers.push(resolver);
|
|
@@ -4263,6 +4366,55 @@ var init_branch_policy = __esm({
|
|
|
4263
4366
|
}
|
|
4264
4367
|
});
|
|
4265
4368
|
|
|
4369
|
+
// spex-console/src/lib/git-export/branch-resolver.ts
|
|
4370
|
+
function isValidSessionBranch(name, defaultBranch) {
|
|
4371
|
+
const b = name.trim();
|
|
4372
|
+
if (!b) return { ok: false, reason: "branch name is empty" };
|
|
4373
|
+
if (b === defaultBranch) {
|
|
4374
|
+
return { ok: false, reason: `\`${b}\` is the default branch; the drain never writes it` };
|
|
4375
|
+
}
|
|
4376
|
+
if (PROTECTED_SESSION_BRANCHES.includes(b)) {
|
|
4377
|
+
return { ok: false, reason: `\`${b}\` is a protected ref` };
|
|
4378
|
+
}
|
|
4379
|
+
const hasControlChar = [...b].some((c) => c.charCodeAt(0) < 32 || c.charCodeAt(0) === 127);
|
|
4380
|
+
if (hasControlChar || /[\s~^:?*[\\]/.test(b)) {
|
|
4381
|
+
return { ok: false, reason: "branch name contains characters git refs do not allow" };
|
|
4382
|
+
}
|
|
4383
|
+
if (b.includes("..") || b.includes("@{") || b.includes("//")) {
|
|
4384
|
+
return { ok: false, reason: "branch name contains a forbidden sequence (.. @{ //)" };
|
|
4385
|
+
}
|
|
4386
|
+
if (b.startsWith("/") || b.endsWith("/") || b.startsWith("-") || b.endsWith(".") || b === "@") {
|
|
4387
|
+
return { ok: false, reason: "branch name has a forbidden leading or trailing character" };
|
|
4388
|
+
}
|
|
4389
|
+
if (b.split("/").some((seg) => seg.startsWith(".") || seg.endsWith(".lock"))) {
|
|
4390
|
+
return {
|
|
4391
|
+
ok: false,
|
|
4392
|
+
reason: "branch name has a path segment starting with . or ending in .lock"
|
|
4393
|
+
};
|
|
4394
|
+
}
|
|
4395
|
+
return { ok: true };
|
|
4396
|
+
}
|
|
4397
|
+
function resolveSessionTargetBranch(input) {
|
|
4398
|
+
const dated = resolveTargetBranch({
|
|
4399
|
+
user: input.user,
|
|
4400
|
+
date: input.date,
|
|
4401
|
+
defaultBranch: input.defaultBranch
|
|
4402
|
+
});
|
|
4403
|
+
const declared = input.sessionBranch?.trim();
|
|
4404
|
+
if (!declared) return { branch: dated, source: "dated" };
|
|
4405
|
+
const check = isValidSessionBranch(declared, input.defaultBranch);
|
|
4406
|
+
if (!check.ok) return { branch: dated, source: "dated", reason: check.reason };
|
|
4407
|
+
return { branch: declared, source: "session" };
|
|
4408
|
+
}
|
|
4409
|
+
var PROTECTED_SESSION_BRANCHES;
|
|
4410
|
+
var init_branch_resolver = __esm({
|
|
4411
|
+
"spex-console/src/lib/git-export/branch-resolver.ts"() {
|
|
4412
|
+
"use strict";
|
|
4413
|
+
init_branch_policy();
|
|
4414
|
+
PROTECTED_SESSION_BRANCHES = ["main", "master", "HEAD", "trunk"];
|
|
4415
|
+
}
|
|
4416
|
+
});
|
|
4417
|
+
|
|
4266
4418
|
// spex-console/src/lib/git-export/config.ts
|
|
4267
4419
|
function parseGithubRemote(githubRemote) {
|
|
4268
4420
|
const match = githubRemote.match(/^([^/\s]+)\/([^/\s]+)$/);
|
|
@@ -4350,10 +4502,10 @@ function trashPathFor(relPath, now) {
|
|
|
4350
4502
|
const normalized = relPath.split("\\").join("/");
|
|
4351
4503
|
const slashIdx = normalized.lastIndexOf("/");
|
|
4352
4504
|
const folder = slashIdx === -1 ? "" : normalized.slice(0, slashIdx);
|
|
4353
|
-
const
|
|
4505
|
+
const basename2 = slashIdx === -1 ? normalized : normalized.slice(slashIdx + 1);
|
|
4354
4506
|
const stamp = now.toISOString().replace(/[:.]/g, "-");
|
|
4355
4507
|
const trashDir = folder ? `${folder}/.trash` : ".trash";
|
|
4356
|
-
return `${trashDir}/${stamp}-${
|
|
4508
|
+
return `${trashDir}/${stamp}-${basename2}`;
|
|
4357
4509
|
}
|
|
4358
4510
|
function materializeSpecMarkdown(bundle) {
|
|
4359
4511
|
return stringifySpecMarkdown(bundle.bodyText, bundle.frontmatter);
|
|
@@ -4662,6 +4814,39 @@ var init_github_client = __esm({
|
|
|
4662
4814
|
}
|
|
4663
4815
|
});
|
|
4664
4816
|
|
|
4817
|
+
// spex-console/src/lib/git-export/session-branch-store.ts
|
|
4818
|
+
async function readActiveDeclaration(db, vaultId, actorKey, now) {
|
|
4819
|
+
const rows = await db.select().from(sessionBranchDeclarations).where(
|
|
4820
|
+
and(
|
|
4821
|
+
eq(sessionBranchDeclarations.vaultId, vaultId),
|
|
4822
|
+
eq(sessionBranchDeclarations.actorKey, actorKey),
|
|
4823
|
+
gt(sessionBranchDeclarations.expiresAt, now)
|
|
4824
|
+
)
|
|
4825
|
+
).orderBy(desc(sessionBranchDeclarations.declaredAt)).limit(1);
|
|
4826
|
+
const row = rows[0];
|
|
4827
|
+
if (!row) return null;
|
|
4828
|
+
return {
|
|
4829
|
+
vaultId: row.vaultId,
|
|
4830
|
+
actorKey: row.actorKey,
|
|
4831
|
+
sessionId: row.sessionId,
|
|
4832
|
+
branch: row.branch,
|
|
4833
|
+
declaredAt: row.declaredAt.toISOString(),
|
|
4834
|
+
expiresAt: row.expiresAt.toISOString()
|
|
4835
|
+
};
|
|
4836
|
+
}
|
|
4837
|
+
async function readActiveSessionBranch(db, vaultId, actorKey, now) {
|
|
4838
|
+
return (await readActiveDeclaration(db, vaultId, actorKey, now))?.branch ?? null;
|
|
4839
|
+
}
|
|
4840
|
+
var SESSION_BRANCH_TTL_MS;
|
|
4841
|
+
var init_session_branch_store = __esm({
|
|
4842
|
+
"spex-console/src/lib/git-export/session-branch-store.ts"() {
|
|
4843
|
+
"use strict";
|
|
4844
|
+
init_sql_orm_inert_cli_shim();
|
|
4845
|
+
init_postgres();
|
|
4846
|
+
SESSION_BRANCH_TTL_MS = 12 * 60 * 60 * 1e3;
|
|
4847
|
+
}
|
|
4848
|
+
});
|
|
4849
|
+
|
|
4665
4850
|
// spex-console/src/lib/git-export/settlement-store.ts
|
|
4666
4851
|
async function recordSettlement(tx, input) {
|
|
4667
4852
|
await tx.insert(specSettlements).values({
|
|
@@ -4892,6 +5077,7 @@ async function processVault(db, vaultId, env, now, deps, batchLimit, jobTimeoutM
|
|
|
4892
5077
|
result: { status: "error", message: target.message }
|
|
4893
5078
|
};
|
|
4894
5079
|
}
|
|
5080
|
+
const lookupSessionBranch = deps.lookupSessionBranch ?? ((vId, actor) => readActiveSessionBranch(tx, vId, actor, now()));
|
|
4895
5081
|
const { config, tokenSource, installationId } = target;
|
|
4896
5082
|
const provider = createPatCommitProvider(() => config.token);
|
|
4897
5083
|
let pendingSettlement = null;
|
|
@@ -4905,11 +5091,18 @@ async function processVault(db, vaultId, env, now, deps, batchLimit, jobTimeoutM
|
|
|
4905
5091
|
fetchBundles: (specIds) => fetchDrainBundles(tx, vaultId, specIds),
|
|
4906
5092
|
resolveActorIdentity,
|
|
4907
5093
|
commit: async (plan, message, coAuthor) => {
|
|
4908
|
-
|
|
5094
|
+
let sessionBranch = null;
|
|
5095
|
+
try {
|
|
5096
|
+
sessionBranch = await lookupSessionBranch(vaultId, coAuthor.name);
|
|
5097
|
+
} catch {
|
|
5098
|
+
sessionBranch = null;
|
|
5099
|
+
}
|
|
5100
|
+
const targetBranch = resolveSessionTargetBranch({
|
|
4909
5101
|
user: coAuthor.name,
|
|
4910
5102
|
date: now(),
|
|
4911
|
-
defaultBranch: config.branch
|
|
4912
|
-
|
|
5103
|
+
defaultBranch: config.branch,
|
|
5104
|
+
sessionBranch
|
|
5105
|
+
}).branch;
|
|
4913
5106
|
const outcome = await commitPlanToGitHub(
|
|
4914
5107
|
provider,
|
|
4915
5108
|
config,
|
|
@@ -5050,12 +5243,13 @@ var init_drain_runner = __esm({
|
|
|
5050
5243
|
init_postgres();
|
|
5051
5244
|
init_github_app();
|
|
5052
5245
|
init_advisory_lock();
|
|
5053
|
-
|
|
5246
|
+
init_branch_resolver();
|
|
5054
5247
|
init_config();
|
|
5055
5248
|
init_drain();
|
|
5056
5249
|
init_drain_skip();
|
|
5057
5250
|
init_export_status();
|
|
5058
5251
|
init_github_client();
|
|
5252
|
+
init_session_branch_store();
|
|
5059
5253
|
init_settlement_store();
|
|
5060
5254
|
BOT_NAME = "heyspex-bot";
|
|
5061
5255
|
BOT_EMAIL = "bot@heyspex.app";
|
|
@@ -5780,7 +5974,7 @@ var init_search_body_hit = __esm({
|
|
|
5780
5974
|
|
|
5781
5975
|
// spex-console/src/lib/vault.ts
|
|
5782
5976
|
import { spawn } from "node:child_process";
|
|
5783
|
-
import { readdirSync, readFileSync as
|
|
5977
|
+
import { readdirSync as readdirSync2, readFileSync as readFileSync12, statSync } from "node:fs";
|
|
5784
5978
|
import { promises as fs7 } from "node:fs";
|
|
5785
5979
|
import path13 from "node:path";
|
|
5786
5980
|
import yaml3 from "js-yaml";
|
|
@@ -5823,18 +6017,18 @@ function extractBodyLinks(body) {
|
|
|
5823
6017
|
return links;
|
|
5824
6018
|
}
|
|
5825
6019
|
function toSummary(absPath, root, fm, body) {
|
|
5826
|
-
const
|
|
6020
|
+
const basename2 = path13.basename(absPath).replace(/\.md$/i, "");
|
|
5827
6021
|
const relPath = path13.relative(root, absPath).split(path13.sep).join("/");
|
|
5828
6022
|
return {
|
|
5829
|
-
id: typeof fm.id === "string" && fm.id ? fm.id :
|
|
5830
|
-
name: typeof fm.name === "string" && fm.name ? fm.name :
|
|
6023
|
+
id: typeof fm.id === "string" && fm.id ? fm.id : basename2,
|
|
6024
|
+
name: typeof fm.name === "string" && fm.name ? fm.name : basename2,
|
|
5831
6025
|
type: typeof fm.type === "string" ? fm.type : "unknown",
|
|
5832
6026
|
status: typeof fm.status === "string" ? fm.status : "unknown",
|
|
5833
6027
|
effort: typeof fm.effort === "number" ? fm.effort : null,
|
|
5834
6028
|
owner: typeof fm.owner === "string" ? fm.owner : null,
|
|
5835
6029
|
tags: Array.isArray(fm.tags) ? fm.tags.map(String) : [],
|
|
5836
6030
|
archived: fm.archived === true,
|
|
5837
|
-
basename,
|
|
6031
|
+
basename: basename2,
|
|
5838
6032
|
relPath,
|
|
5839
6033
|
links: extractBodyLinks(body),
|
|
5840
6034
|
created: normalizeDateField(fm.created),
|
|
@@ -5920,9 +6114,9 @@ function invalidateVaultCacheForFile(absPath) {
|
|
|
5920
6114
|
else evictProcessVaultHotRows();
|
|
5921
6115
|
}
|
|
5922
6116
|
function specIdFromAbsPath(absPath) {
|
|
5923
|
-
const
|
|
5924
|
-
if (!
|
|
5925
|
-
const parsed = parseSpecId(
|
|
6117
|
+
const basename2 = path13.basename(absPath).replace(/\.md$/i, "");
|
|
6118
|
+
if (!basename2) return null;
|
|
6119
|
+
const parsed = parseSpecId(basename2);
|
|
5926
6120
|
if (parsed?.era === 3 && parsed.fileKey) return `HS-${parsed.fileKey.toUpperCase()}`;
|
|
5927
6121
|
return null;
|
|
5928
6122
|
}
|
|
@@ -6178,8 +6372,8 @@ var init_vault = __esm({
|
|
|
6178
6372
|
|
|
6179
6373
|
// spex-console/bin/hs-index-reconcile.ts
|
|
6180
6374
|
import { execFileSync } from "node:child_process";
|
|
6181
|
-
import { appendFileSync, mkdirSync as
|
|
6182
|
-
import { promises as
|
|
6375
|
+
import { appendFileSync, mkdirSync as mkdirSync6, readFileSync as readFileSync13, writeFileSync as writeFileSync6 } from "node:fs";
|
|
6376
|
+
import { promises as fs10 } from "node:fs";
|
|
6183
6377
|
import path14 from "node:path";
|
|
6184
6378
|
import process4 from "node:process";
|
|
6185
6379
|
|
|
@@ -6346,7 +6540,7 @@ function resolveSyncVaultTarget(input) {
|
|
|
6346
6540
|
detail: `heyspex.yaml vault_id${drift}`
|
|
6347
6541
|
};
|
|
6348
6542
|
}
|
|
6349
|
-
if (input.pointer.status === "read" && input.pointer.vaultId) {
|
|
6543
|
+
if (input.allowPointer && input.pointer.status === "read" && input.pointer.vaultId) {
|
|
6350
6544
|
return {
|
|
6351
6545
|
ok: true,
|
|
6352
6546
|
vaultId: input.pointer.vaultId,
|
|
@@ -6354,9 +6548,9 @@ function resolveSyncVaultTarget(input) {
|
|
|
6354
6548
|
detail: "this account's active vault, read from the console (heyspex.yaml names none)"
|
|
6355
6549
|
};
|
|
6356
6550
|
}
|
|
6357
|
-
const why = input.pointer.status === "unreadable" ? `the console could not answer for the active vault (${input.pointer.detail})` : "the console reports no active vault for this account";
|
|
6551
|
+
const why = !input.allowPointer ? "the account-wide active vault is not a binding and was not consulted (set HEYSPEX_ALLOW_POINTER_VAULT=1 to follow it on purpose)" : input.pointer.status === "unreadable" ? `the console could not answer for the active vault (${input.pointer.detail})` : "the console reports no active vault for this account";
|
|
6358
6552
|
return refuse(
|
|
6359
|
-
`cannot tell which vault to ${verb === "pull" ? "pull into" : verb === "index reconcile" ? "reconcile" : "rebuild"}: heyspex.yaml names no vault_id and ${why}. Nothing was changed. The registry.json pointer copy was deliberately not used (it can be stale and target the wrong vault). Name the vault: \`hs --vault <id> ${verb}\`; check this repo's wiring with \`hs connect --check\`; or
|
|
6553
|
+
`cannot tell which vault to ${verb === "pull" ? "pull into" : verb === "push" ? "push from" : verb === "import" ? "import into" : verb === "index reconcile" ? "reconcile" : "rebuild"}: heyspex.yaml names no vault_id and ${why}. Nothing was changed. The registry.json pointer copy was deliberately not used (it can be stale and target the wrong vault). Name the vault: \`hs --vault <id> ${verb}\`; check this repo's wiring with \`hs connect --check\`; or bind this repo with a heyspex.yaml vault_id (a committed satellite marker is portable).`
|
|
6360
6554
|
);
|
|
6361
6555
|
}
|
|
6362
6556
|
async function resolveSyncVaultTargetFromEnv(deps) {
|
|
@@ -6378,8 +6572,11 @@ async function resolveSyncVaultTargetFromEnv(deps) {
|
|
|
6378
6572
|
} catch {
|
|
6379
6573
|
registryRowId = null;
|
|
6380
6574
|
}
|
|
6575
|
+
const allowPointer = ["1", "true", "yes"].includes(
|
|
6576
|
+
(deps.env.HEYSPEX_ALLOW_POINTER_VAULT ?? "").trim().toLowerCase()
|
|
6577
|
+
);
|
|
6381
6578
|
let pointer = { status: "unreadable", detail: "not read" };
|
|
6382
|
-
if (!override && !markerVaultId) {
|
|
6579
|
+
if (!override && !markerVaultId && allowPointer) {
|
|
6383
6580
|
const http = {
|
|
6384
6581
|
fetchImpl: deps.fetchImpl ?? fetch,
|
|
6385
6582
|
consoleUrl: deps.consoleUrl,
|
|
@@ -6401,7 +6598,8 @@ async function resolveSyncVaultTargetFromEnv(deps) {
|
|
|
6401
6598
|
markerVaultId,
|
|
6402
6599
|
registryRowId,
|
|
6403
6600
|
pointer,
|
|
6404
|
-
verb: deps.verb
|
|
6601
|
+
verb: deps.verb,
|
|
6602
|
+
allowPointer
|
|
6405
6603
|
});
|
|
6406
6604
|
}
|
|
6407
6605
|
|
|
@@ -6504,9 +6702,9 @@ function isRoutableStatus(status, type) {
|
|
|
6504
6702
|
if (ROUTE_SETTABLE_STATUSES.has(status)) return true;
|
|
6505
6703
|
return (type ?? "").toLowerCase() === "adr" && ADR_SETTABLE_STATUSES.has(status);
|
|
6506
6704
|
}
|
|
6507
|
-
function matchesSelector(id,
|
|
6705
|
+
function matchesSelector(id, basename2, selectors) {
|
|
6508
6706
|
const i = id.toLowerCase();
|
|
6509
|
-
const b =
|
|
6707
|
+
const b = basename2.toLowerCase();
|
|
6510
6708
|
return selectors.some((raw) => {
|
|
6511
6709
|
const s = raw.trim().toLowerCase();
|
|
6512
6710
|
return s.length > 0 && (i === s || b === s || i.startsWith(`${s}-`) || b.startsWith(`${s}-`));
|
|
@@ -6546,20 +6744,20 @@ function decideReconcilePlan({
|
|
|
6546
6744
|
const c = cloudById.get(id) ?? null;
|
|
6547
6745
|
const primary = d ?? c;
|
|
6548
6746
|
const relPath = reconcileRelPath(primary.relPath);
|
|
6549
|
-
const
|
|
6747
|
+
const basename2 = primary.basename || basenameOfRelPath(relPath);
|
|
6550
6748
|
const type = d?.type ?? c?.type ?? null;
|
|
6551
6749
|
const base = {
|
|
6552
6750
|
id,
|
|
6553
6751
|
type,
|
|
6554
|
-
basename,
|
|
6752
|
+
basename: basename2,
|
|
6555
6753
|
relPath,
|
|
6556
6754
|
disk: view(d),
|
|
6557
6755
|
cloud: view(c),
|
|
6558
6756
|
...d?.closeReason ? { closeReason: d.closeReason } : {}
|
|
6559
6757
|
};
|
|
6560
6758
|
if (d && isCapabilityDoc(d.relPath)) capDocsOnDisk += 1;
|
|
6561
|
-
if (only.length > 0 && !matchesSelector(id,
|
|
6562
|
-
if (skip.length > 0 && matchesSelector(id,
|
|
6759
|
+
if (only.length > 0 && !matchesSelector(id, basename2, only)) continue;
|
|
6760
|
+
if (skip.length > 0 && matchesSelector(id, basename2, skip)) {
|
|
6563
6761
|
rows.push(hold({ ...base, winner: "none" }, "skipped", "held by --skip"));
|
|
6564
6762
|
continue;
|
|
6565
6763
|
}
|
|
@@ -6827,10 +7025,10 @@ async function applyReconcilePlan(deps, plan) {
|
|
|
6827
7025
|
}
|
|
6828
7026
|
return summarizeResult(rows, plan, sideEffects);
|
|
6829
7027
|
}
|
|
6830
|
-
function interpretPatch(requested, res,
|
|
7028
|
+
function interpretPatch(requested, res, basename2, sideEffects) {
|
|
6831
7029
|
const json = rec(res.json);
|
|
6832
7030
|
if (res.status >= 200 && res.status < 300) {
|
|
6833
|
-
pushSideEffects(sideEffects,
|
|
7031
|
+
pushSideEffects(sideEffects, basename2, json);
|
|
6834
7032
|
const coercedTo = str2(json.coerced_to) ?? str2(json.coercedTo);
|
|
6835
7033
|
if (coercedTo && coercedTo !== requested) {
|
|
6836
7034
|
return {
|
|
@@ -7109,23 +7307,22 @@ async function postEdgesChunk(input, specs2) {
|
|
|
7109
7307
|
}
|
|
7110
7308
|
|
|
7111
7309
|
// spex-console/src/lib/vault-sync/pull-io.ts
|
|
7112
|
-
import { promises as
|
|
7113
|
-
import
|
|
7114
|
-
import nodePath from "node:path";
|
|
7310
|
+
import { promises as fs9 } from "node:fs";
|
|
7311
|
+
import nodePath2 from "node:path";
|
|
7115
7312
|
|
|
7116
7313
|
// spex-console/src/lib/dev-mode.ts
|
|
7314
|
+
init_heyspex_home2();
|
|
7117
7315
|
init_credentials();
|
|
7118
|
-
import { mkdirSync as
|
|
7119
|
-
import {
|
|
7120
|
-
import { join as join9 } from "node:path";
|
|
7316
|
+
import { mkdirSync as mkdirSync5, readFileSync as readFileSync10, writeFileSync as writeFileSync5 } from "node:fs";
|
|
7317
|
+
import { join as join10 } from "node:path";
|
|
7121
7318
|
var CLOUD_CONSOLE_URL2 = "https://app.heyspex.com";
|
|
7122
|
-
var defaultReadFile3 = (p) =>
|
|
7319
|
+
var defaultReadFile3 = (p) => readFileSync10(p, "utf8");
|
|
7123
7320
|
function readCredentialsMcpToken({
|
|
7124
|
-
homeDir =
|
|
7321
|
+
homeDir = heyspexHome2(),
|
|
7125
7322
|
readFile = defaultReadFile3
|
|
7126
7323
|
} = {}) {
|
|
7127
7324
|
try {
|
|
7128
|
-
const raw = readFile(
|
|
7325
|
+
const raw = readFile(join10(homeDir, ".heyspex", "credentials.yaml"));
|
|
7129
7326
|
const match = raw.match(/^mcp_token:\s*["']?([^"'\r\n#]+)/m);
|
|
7130
7327
|
return match?.[1]?.trim() || null;
|
|
7131
7328
|
} catch {
|
|
@@ -7134,8 +7331,19 @@ function readCredentialsMcpToken({
|
|
|
7134
7331
|
}
|
|
7135
7332
|
|
|
7136
7333
|
// spex-console/src/lib/vault-sync/pull-io.ts
|
|
7334
|
+
init_heyspex_home2();
|
|
7137
7335
|
init_vault();
|
|
7138
7336
|
init_vault_frontmatter();
|
|
7337
|
+
|
|
7338
|
+
// spex-console/src/lib/vault-sync/ledger.ts
|
|
7339
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
7340
|
+
import { promises as fs8 } from "node:fs";
|
|
7341
|
+
import nodePath from "node:path";
|
|
7342
|
+
function hashContent(content) {
|
|
7343
|
+
return createHash3("sha256").update(content, "utf8").digest("hex");
|
|
7344
|
+
}
|
|
7345
|
+
|
|
7346
|
+
// spex-console/src/lib/vault-sync/pull-io.ts
|
|
7139
7347
|
var LIST_FIELDS = "id,basename,relPath,type,status,updated,updatedAt";
|
|
7140
7348
|
var PULL_LIST_PAGE_LIMIT = 500;
|
|
7141
7349
|
var PULL_LIST_MAX_PAGES = 2e3;
|
|
@@ -7149,7 +7357,7 @@ var VaultPullSourceError = class extends Error {
|
|
|
7149
7357
|
};
|
|
7150
7358
|
function resolvePullToken({
|
|
7151
7359
|
env = process.env,
|
|
7152
|
-
homeDir =
|
|
7360
|
+
homeDir = heyspexHome2()
|
|
7153
7361
|
} = {}) {
|
|
7154
7362
|
const fromEnv = env.HEYSPEX_MCP_TOKEN?.trim();
|
|
7155
7363
|
if (fromEnv) return fromEnv;
|
|
@@ -7248,22 +7456,68 @@ async function fetchRemoteSpecContent(http, item) {
|
|
|
7248
7456
|
}
|
|
7249
7457
|
return { body: spec.body, frontmatter };
|
|
7250
7458
|
}
|
|
7459
|
+
function remoteStamp(spec) {
|
|
7460
|
+
if (!spec) return null;
|
|
7461
|
+
const fm = spec.frontmatter;
|
|
7462
|
+
for (const candidate of [fm?.updated, spec.updatedAt, spec.updated]) {
|
|
7463
|
+
const raw = candidate instanceof Date ? candidate.toISOString() : candidate;
|
|
7464
|
+
if (typeof raw === "string" && raw.trim()) {
|
|
7465
|
+
const ms = Date.parse(raw);
|
|
7466
|
+
if (!Number.isNaN(ms)) return /T/.test(raw) ? new Date(ms).toISOString() : raw.trim();
|
|
7467
|
+
}
|
|
7468
|
+
}
|
|
7469
|
+
return null;
|
|
7470
|
+
}
|
|
7471
|
+
async function fetchRemoteRefAndContent(http, selector) {
|
|
7472
|
+
const { status, json, text: text2 } = await getJson(http, `api/specs/${encodeURIComponent(selector)}`);
|
|
7473
|
+
if (status === 404) return null;
|
|
7474
|
+
if (status !== 200) {
|
|
7475
|
+
throw new VaultPullSourceError(
|
|
7476
|
+
`GET /api/specs/${selector} returned ${status}: ${text2.slice(0, 200)}`,
|
|
7477
|
+
status
|
|
7478
|
+
);
|
|
7479
|
+
}
|
|
7480
|
+
const spec = json?.spec;
|
|
7481
|
+
const fm = spec?.frontmatter && typeof spec.frontmatter === "object" ? spec.frontmatter : null;
|
|
7482
|
+
if (!spec || typeof spec.body !== "string" || !fm || Object.keys(fm).length === 0) {
|
|
7483
|
+
throw new VaultPullSourceError(`GET /api/specs/${selector} returned no usable spec`, status);
|
|
7484
|
+
}
|
|
7485
|
+
return {
|
|
7486
|
+
ref: {
|
|
7487
|
+
id: String(spec.id ?? fm.id ?? ""),
|
|
7488
|
+
basename: typeof spec.basename === "string" ? spec.basename : null,
|
|
7489
|
+
relPath: typeof spec.relPath === "string" ? spec.relPath : null,
|
|
7490
|
+
type: typeof spec.type === "string" ? spec.type : null,
|
|
7491
|
+
status: typeof spec.status === "string" ? spec.status : null,
|
|
7492
|
+
updated: typeof spec.updated === "string" ? spec.updated : null,
|
|
7493
|
+
updatedAt: remoteStamp(spec)
|
|
7494
|
+
},
|
|
7495
|
+
content: { body: spec.body, frontmatter: fm }
|
|
7496
|
+
};
|
|
7497
|
+
}
|
|
7498
|
+
async function hashFile(abs) {
|
|
7499
|
+
try {
|
|
7500
|
+
return hashContent(await fs9.readFile(abs, "utf8"));
|
|
7501
|
+
} catch {
|
|
7502
|
+
return null;
|
|
7503
|
+
}
|
|
7504
|
+
}
|
|
7251
7505
|
async function listLocalSpecRelPaths(root = vaultRoot()) {
|
|
7252
7506
|
const out = [];
|
|
7253
7507
|
async function walk(dir) {
|
|
7254
7508
|
let entries;
|
|
7255
7509
|
try {
|
|
7256
|
-
entries = await
|
|
7510
|
+
entries = await fs9.readdir(dir, { withFileTypes: true });
|
|
7257
7511
|
} catch {
|
|
7258
7512
|
return;
|
|
7259
7513
|
}
|
|
7260
7514
|
for (const entry of entries) {
|
|
7261
|
-
const full =
|
|
7515
|
+
const full = nodePath2.join(dir, entry.name);
|
|
7262
7516
|
if (entry.isDirectory()) {
|
|
7263
7517
|
if (entry.name === "node_modules" || entry.name.startsWith(".")) continue;
|
|
7264
7518
|
await walk(full);
|
|
7265
7519
|
} else if (entry.isFile() && entry.name.toLowerCase().endsWith(".md")) {
|
|
7266
|
-
out.push(
|
|
7520
|
+
out.push(nodePath2.relative(root, full).split(nodePath2.sep).join("/"));
|
|
7267
7521
|
}
|
|
7268
7522
|
}
|
|
7269
7523
|
}
|
|
@@ -7275,20 +7529,52 @@ function createVaultPullDeps({
|
|
|
7275
7529
|
token,
|
|
7276
7530
|
fetchImpl,
|
|
7277
7531
|
localRoot,
|
|
7278
|
-
scopeHeaders
|
|
7532
|
+
scopeHeaders,
|
|
7533
|
+
ledger,
|
|
7534
|
+
onLocalWrite
|
|
7279
7535
|
}) {
|
|
7280
7536
|
const http = { baseUrl, token, fetchImpl, scopeHeaders };
|
|
7537
|
+
const prefetched = /* @__PURE__ */ new Map();
|
|
7281
7538
|
return {
|
|
7282
7539
|
listRemote: () => listRemoteSpecs(http),
|
|
7283
|
-
|
|
7540
|
+
getRemoteRef: async (selector) => {
|
|
7541
|
+
const hit = await fetchRemoteRefAndContent(http, selector);
|
|
7542
|
+
if (!hit) return null;
|
|
7543
|
+
if (hit.ref.basename) prefetched.set(hit.ref.basename, hit.content);
|
|
7544
|
+
return hit.ref;
|
|
7545
|
+
},
|
|
7546
|
+
fetchRemoteSpec: async (item) => {
|
|
7547
|
+
const cached = prefetched.get(item.basename);
|
|
7548
|
+
if (cached) {
|
|
7549
|
+
prefetched.delete(item.basename);
|
|
7550
|
+
return cached;
|
|
7551
|
+
}
|
|
7552
|
+
return fetchRemoteSpecContent(http, item);
|
|
7553
|
+
},
|
|
7554
|
+
readLedger: ledger ? () => ledger.snapshot() : void 0,
|
|
7555
|
+
hashLocal: (relPath) => hashFile(nodePath2.join(localRoot ?? vaultRoot(), relPath)),
|
|
7556
|
+
finish: ledger ? () => ledger.flush() : void 0,
|
|
7284
7557
|
listLocal: () => listLocalSpecRelPaths(localRoot ?? vaultRoot()),
|
|
7285
7558
|
serialize: stringifySpecMarkdown,
|
|
7286
|
-
writeSpec: localRoot ? async (relPath, content) => {
|
|
7287
|
-
const abs =
|
|
7288
|
-
await
|
|
7289
|
-
|
|
7559
|
+
writeSpec: localRoot ? async (relPath, content, opts) => {
|
|
7560
|
+
const abs = nodePath2.join(localRoot, relPath);
|
|
7561
|
+
await fs9.mkdir(nodePath2.dirname(abs), { recursive: true });
|
|
7562
|
+
onLocalWrite?.(relPath);
|
|
7563
|
+
await fs9.writeFile(abs, content, {
|
|
7564
|
+
encoding: "utf8",
|
|
7565
|
+
flag: opts?.overwrite === true ? "w" : "wx"
|
|
7566
|
+
});
|
|
7290
7567
|
} : (relPath, content) => writeSpecBytesToVault(relPath, content),
|
|
7291
|
-
afterWrite: (relPath) =>
|
|
7568
|
+
afterWrite: async (relPath, info) => {
|
|
7569
|
+
if (ledger && info) {
|
|
7570
|
+
ledger.record(relPath, {
|
|
7571
|
+
hash: hashContent(info.content),
|
|
7572
|
+
remoteUpdatedAt: info.remoteUpdatedAt,
|
|
7573
|
+
by: "pull"
|
|
7574
|
+
});
|
|
7575
|
+
}
|
|
7576
|
+
await updateVaultCacheEntry(nodePath2.join(localRoot ?? vaultRoot(), relPath));
|
|
7577
|
+
}
|
|
7292
7578
|
};
|
|
7293
7579
|
}
|
|
7294
7580
|
|
|
@@ -7447,7 +7733,7 @@ function say(args, text2) {
|
|
|
7447
7733
|
}
|
|
7448
7734
|
function readDiscovery(p) {
|
|
7449
7735
|
try {
|
|
7450
|
-
return
|
|
7736
|
+
return readFileSync13(p, "utf-8");
|
|
7451
7737
|
} catch {
|
|
7452
7738
|
return null;
|
|
7453
7739
|
}
|
|
@@ -7459,7 +7745,7 @@ function resolveConsole(args) {
|
|
|
7459
7745
|
}
|
|
7460
7746
|
function readMarkerVaultId(vaultRoot2) {
|
|
7461
7747
|
try {
|
|
7462
|
-
return parseVaultId(
|
|
7748
|
+
return parseVaultId(readFileSync13(path14.join(vaultRoot2, "heyspex.yaml"), "utf8"));
|
|
7463
7749
|
} catch {
|
|
7464
7750
|
return null;
|
|
7465
7751
|
}
|
|
@@ -7479,7 +7765,7 @@ async function walkLocal(args) {
|
|
|
7479
7765
|
async function walk(dir) {
|
|
7480
7766
|
let entries;
|
|
7481
7767
|
try {
|
|
7482
|
-
entries = await
|
|
7768
|
+
entries = await fs10.readdir(dir, { withFileTypes: true });
|
|
7483
7769
|
} catch {
|
|
7484
7770
|
return;
|
|
7485
7771
|
}
|
|
@@ -7501,7 +7787,7 @@ async function walkLocal(args) {
|
|
|
7501
7787
|
for (const abs of files) {
|
|
7502
7788
|
const relPath = path14.relative(specsDir, abs).split(path14.sep).join("/");
|
|
7503
7789
|
try {
|
|
7504
|
-
const content = await
|
|
7790
|
+
const content = await fs10.readFile(abs, "utf8");
|
|
7505
7791
|
const parsed = parseSpecFromContent(content, {
|
|
7506
7792
|
relPath: `specs/${relPath}`,
|
|
7507
7793
|
basenameFallback: path14.basename(abs).replace(/\.md$/i, "")
|
|
@@ -7635,15 +7921,15 @@ function reportStamp() {
|
|
|
7635
7921
|
}
|
|
7636
7922
|
function writeReport(vaultRoot2, payload) {
|
|
7637
7923
|
const dir = path14.join(vaultRoot2, ".heyspex", "index-reconcile");
|
|
7638
|
-
|
|
7924
|
+
mkdirSync6(dir, { recursive: true });
|
|
7639
7925
|
const file = path14.join(dir, `${reportStamp()}.json`);
|
|
7640
|
-
|
|
7926
|
+
writeFileSync6(file, `${JSON.stringify(payload, null, 2)}
|
|
7641
7927
|
`, "utf-8");
|
|
7642
7928
|
return file;
|
|
7643
7929
|
}
|
|
7644
7930
|
function appendJournal(vaultRoot2, message) {
|
|
7645
7931
|
const heyspexDir = path14.join(vaultRoot2, ".heyspex");
|
|
7646
|
-
|
|
7932
|
+
mkdirSync6(heyspexDir, { recursive: true });
|
|
7647
7933
|
appendFileSync(
|
|
7648
7934
|
path14.join(heyspexDir, "index-reconcile.log"),
|
|
7649
7935
|
`[${(/* @__PURE__ */ new Date()).toISOString()}] ${message}
|
|
@@ -7740,8 +8026,8 @@ async function main() {
|
|
|
7740
8026
|
localRoot: path14.join(args.vaultRoot, "specs")
|
|
7741
8027
|
});
|
|
7742
8028
|
const deps = {
|
|
7743
|
-
patchStatus: async (
|
|
7744
|
-
const route = `/api/specs/${encodeURIComponent(
|
|
8029
|
+
patchStatus: async (basename2, body) => {
|
|
8030
|
+
const route = `/api/specs/${encodeURIComponent(basename2)}/status`;
|
|
7745
8031
|
try {
|
|
7746
8032
|
const res = await fetch(`${consoleUrl}${route}`, {
|
|
7747
8033
|
method: "PATCH",
|
|
@@ -7775,7 +8061,7 @@ async function main() {
|
|
|
7775
8061
|
const content = await pull.fetchRemoteSpec(item);
|
|
7776
8062
|
if (!content) throw new Error("remote returned no content for this spec");
|
|
7777
8063
|
const bytes = pull.serialize(content.body, content.frontmatter);
|
|
7778
|
-
await pull.writeSpec(item.relPath, bytes);
|
|
8064
|
+
await pull.writeSpec(item.relPath, bytes, { overwrite: item.overwrites });
|
|
7779
8065
|
try {
|
|
7780
8066
|
await pull.afterWrite?.(item.relPath);
|
|
7781
8067
|
} catch {
|