@titan-design/active-work 0.3.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -11,6 +11,7 @@ import {
11
11
  NOTE_TITLE_MAX_LENGTH,
12
12
  NextStepSchema,
13
13
  NotFoundError,
14
+ NoteFrontmatterSchema,
14
15
  NoteKindSchema,
15
16
  SessionFrontmatterSchema,
16
17
  SessionIdSchema,
@@ -23,6 +24,7 @@ import {
23
24
  WorktreeEntrySchema,
24
25
  assembleBootstrap,
25
26
  atomicWrite,
27
+ coerceDates,
26
28
  color,
27
29
  defineCommand,
28
30
  deriveOpenLoops,
@@ -74,7 +76,7 @@ import {
74
76
  writeFrontmatter,
75
77
  writeNoteFile,
76
78
  writeYaml
77
- } from "./chunk-HSGZOWS3.js";
79
+ } from "./chunk-RLSQSE7I.js";
78
80
 
79
81
  // src/cli.ts
80
82
  import { Command, CommanderError } from "commander";
@@ -261,7 +263,7 @@ var new_default = defineCommand({
261
263
  }
262
264
  const rank = await computeNextRank(ctx.activeRoot);
263
265
  const task_prefix = derivePrefix(args.slug);
264
- const frontmatter = {
266
+ const frontmatter2 = {
265
267
  schema_version: 1,
266
268
  title: args.title,
267
269
  updated: today(),
@@ -281,7 +283,7 @@ Why: ...
281
283
  `;
282
284
  await writeFrontmatter(
283
285
  path2.join(dir, "brief.md"),
284
- frontmatter,
286
+ frontmatter2,
285
287
  briefBody,
286
288
  BriefFrontmatterSchema
287
289
  );
@@ -452,11 +454,11 @@ var set_default = defineCommand({
452
454
  throw new NotFoundError(`Initiative not found: ${args.slug}`);
453
455
  }
454
456
  await withFileLock(getLockPath(args.slug), async () => {
455
- const { frontmatter, body } = await readRawFrontmatter(briefPath);
456
- setPath(frontmatter, args.field, args.value);
457
- frontmatter.updated = today();
457
+ const { frontmatter: frontmatter2, body } = await readRawFrontmatter(briefPath);
458
+ setPath(frontmatter2, args.field, args.value);
459
+ frontmatter2.updated = today();
458
460
  try {
459
- await writeFrontmatter(briefPath, frontmatter, body, BriefFrontmatterSchema);
461
+ await writeFrontmatter(briefPath, frontmatter2, body, BriefFrontmatterSchema);
460
462
  } catch (err) {
461
463
  const reason = err instanceof Error ? err.message : String(err);
462
464
  throw new ValidationError(
@@ -497,9 +499,9 @@ var touch_default = defineCommand({
497
499
  }
498
500
  const updated = today();
499
501
  await withFileLock(getLockPath(args.slug), async () => {
500
- const { frontmatter, body } = await readRawFrontmatter(briefPath);
501
- frontmatter.updated = updated;
502
- await writeFrontmatter(briefPath, frontmatter, body, BriefFrontmatterSchema);
502
+ const { frontmatter: frontmatter2, body } = await readRawFrontmatter(briefPath);
503
+ frontmatter2.updated = updated;
504
+ await writeFrontmatter(briefPath, frontmatter2, body, BriefFrontmatterSchema);
503
505
  });
504
506
  return { slug: args.slug, updated };
505
507
  }
@@ -531,8 +533,8 @@ async function loadAllBriefs() {
531
533
  continue;
532
534
  }
533
535
  if (!stat.isFile()) continue;
534
- const { frontmatter, body } = await readFrontmatter(briefPath, BriefFrontmatterSchema);
535
- briefs.push({ slug: name, briefPath, frontmatter, body });
536
+ const { frontmatter: frontmatter2, body } = await readFrontmatter(briefPath, BriefFrontmatterSchema);
537
+ briefs.push({ slug: name, briefPath, frontmatter: frontmatter2, body });
536
538
  }
537
539
  briefs.sort((a, b) => a.slug.localeCompare(b.slug));
538
540
  return briefs;
@@ -1084,11 +1086,11 @@ var task_delete_default = defineCommand({
1084
1086
  if (n !== null && n === onDiskMax) {
1085
1087
  const stored = readTaskSeq(brief, onDiskMax);
1086
1088
  if (stored < n) {
1087
- const frontmatter = {
1089
+ const frontmatter2 = {
1088
1090
  ...brief.frontmatter,
1089
1091
  task_seq: n
1090
1092
  };
1091
- await writeFrontmatter(brief.path, frontmatter, brief.body, BriefFrontmatterSchema);
1093
+ await writeFrontmatter(brief.path, frontmatter2, brief.body, BriefFrontmatterSchema);
1092
1094
  }
1093
1095
  }
1094
1096
  await fs10.unlink(file);
@@ -1515,8 +1517,8 @@ var NO_UPSTREAM = { ahead: null, behind: null };
1515
1517
  function shortBranch(ref) {
1516
1518
  return ref.replace(/^refs\/heads\//, "");
1517
1519
  }
1518
- function emptyWorktree(path65) {
1519
- return { path: path65, head: null, branch: null, detached: false, bare: false };
1520
+ function emptyWorktree(path67) {
1521
+ return { path: path67, head: null, branch: null, detached: false, bare: false };
1520
1522
  }
1521
1523
  function parseWorktreePorcelain(stdout) {
1522
1524
  const out = [];
@@ -2000,10 +2002,10 @@ var session_list_default = defineCommand({
2000
2002
  for (const filename of filenames) {
2001
2003
  const fullPath = path19.join(sessionsDir, filename);
2002
2004
  try {
2003
- const { frontmatter, body } = await readSession(fullPath);
2005
+ const { frontmatter: frontmatter2, body } = await readSession(fullPath);
2004
2006
  entries.push({
2005
2007
  filename,
2006
- frontmatter,
2008
+ frontmatter: frontmatter2,
2007
2009
  first_line: extractFirstLine(body)
2008
2010
  });
2009
2011
  } catch (err) {
@@ -2427,9 +2429,9 @@ async function verifyTaskIds(initiativeDir, ids) {
2427
2429
  }
2428
2430
  async function stampBriefUpdated(briefPath) {
2429
2431
  const updated = today();
2430
- const { frontmatter, body } = await readRawFrontmatter(briefPath);
2431
- frontmatter.updated = updated;
2432
- await writeFrontmatter(briefPath, frontmatter, body, BriefFrontmatterSchema);
2432
+ const { frontmatter: frontmatter2, body } = await readRawFrontmatter(briefPath);
2433
+ frontmatter2.updated = updated;
2434
+ await writeFrontmatter(briefPath, frontmatter2, body, BriefFrontmatterSchema);
2433
2435
  return updated;
2434
2436
  }
2435
2437
  async function writeWrap(args, briefPath, body, ledger) {
@@ -2714,14 +2716,14 @@ var note_add_default = defineCommand({
2714
2716
  throw new NotFoundError(`Initiative not found: ${args.slug}`);
2715
2717
  }
2716
2718
  const body = args.body ?? await fs20.readFile(args.body_file, "utf8");
2717
- const frontmatter = {
2719
+ const frontmatter2 = {
2718
2720
  kind: args.kind,
2719
2721
  title: args.title,
2720
2722
  created: today(),
2721
2723
  ...args.tags && args.tags.length > 0 ? { tags: args.tags } : {}
2722
2724
  };
2723
2725
  return withFileLock(getLockPath(args.slug), async () => {
2724
- const written = await writeNoteFile(initiativeDir, frontmatter, body);
2726
+ const written = await writeNoteFile(initiativeDir, frontmatter2, body);
2725
2727
  return { ...written, kind: args.kind, title: args.title };
2726
2728
  });
2727
2729
  }
@@ -3059,13 +3061,13 @@ async function classifyWorktree(entry) {
3059
3061
  if (await isWorktreePresent(entry.path)) return { keep: true };
3060
3062
  return { keep: false, reason: "worktree path missing" };
3061
3063
  }
3062
- async function partition(entries, classify2, describe2) {
3064
+ async function partition(entries, classify2, describe3) {
3063
3065
  const keep = [];
3064
3066
  const pruned = [];
3065
3067
  for (const entry of entries) {
3066
3068
  const verdict = await classify2(entry);
3067
3069
  if (verdict.keep) keep.push(entry);
3068
- else pruned.push({ ...describe2(entry), reason: verdict.reason });
3070
+ else pruned.push({ ...describe3(entry), reason: verdict.reason });
3069
3071
  }
3070
3072
  return { keep, pruned };
3071
3073
  }
@@ -3411,9 +3413,9 @@ async function listSources(initiativeDir) {
3411
3413
  );
3412
3414
  }
3413
3415
  function extractSourceReferences(body) {
3414
- const matches = body.matchAll(/sources\/([A-Za-z0-9._/-]+\.md)/g);
3416
+ const matches2 = body.matchAll(/sources\/([A-Za-z0-9._/-]+\.md)/g);
3415
3417
  const seen = /* @__PURE__ */ new Set();
3416
- for (const match of matches) {
3418
+ for (const match of matches2) {
3417
3419
  seen.add(match[1]);
3418
3420
  }
3419
3421
  return [...seen].sort();
@@ -3557,9 +3559,9 @@ async function scanInitiatives(activeRoot) {
3557
3559
  continue;
3558
3560
  }
3559
3561
  try {
3560
- const { frontmatter } = await readFrontmatter(briefPath, BriefFrontmatterSchema);
3562
+ const { frontmatter: frontmatter2 } = await readFrontmatter(briefPath, BriefFrontmatterSchema);
3561
3563
  const worktrees = await readRegisteredWorktrees(path32.join(activeRoot, slug));
3562
- entries.push({ slug, frontmatter, worktrees });
3564
+ entries.push({ slug, frontmatter: frontmatter2, worktrees });
3563
3565
  } catch (err) {
3564
3566
  const message = err instanceof Error ? err.message : String(err);
3565
3567
  errors.push({ slug, error: message });
@@ -3607,13 +3609,13 @@ var audit_default = defineCommand({
3607
3609
  async run() {
3608
3610
  const activeRoot = getActiveRoot();
3609
3611
  const { entries, errors } = await scanInitiatives(activeRoot);
3610
- const initiatives = [...entries].sort(compareInitiatives).map(({ slug, frontmatter }) => ({
3612
+ const initiatives = [...entries].sort(compareInitiatives).map(({ slug, frontmatter: frontmatter2 }) => ({
3611
3613
  slug,
3612
- title: frontmatter.title,
3613
- state: frontmatter.state,
3614
- ...frontmatter.rank !== void 0 ? { rank: frontmatter.rank } : {},
3615
- updated: frontmatter.updated,
3616
- ...frontmatter.ship_target !== void 0 ? { ship_target: frontmatter.ship_target } : {}
3614
+ title: frontmatter2.title,
3615
+ state: frontmatter2.state,
3616
+ ...frontmatter2.rank !== void 0 ? { rank: frontmatter2.rank } : {},
3617
+ updated: frontmatter2.updated,
3618
+ ...frontmatter2.ship_target !== void 0 ? { ship_target: frontmatter2.ship_target } : {}
3617
3619
  }));
3618
3620
  return {
3619
3621
  initiatives,
@@ -3736,12 +3738,12 @@ function matchBody(match, base, body) {
3736
3738
  return out;
3737
3739
  }
3738
3740
  function scanSessionFile(input, scan) {
3739
- const { slug, stem, rel, id, match, frontmatter, body } = input;
3741
+ const { slug, stem, rel, id, match, frontmatter: frontmatter2, body } = input;
3740
3742
  const base = { slug, source: "session", file: rel };
3741
- if (id === stem || id === frontmatter.session_id) {
3743
+ if (id === stem || id === frontmatter2.session_id) {
3742
3744
  scan.subject = { kind: "session", slug, file: rel, title: stem };
3743
3745
  }
3744
- frontmatter.next_steps.forEach((step, i) => {
3746
+ frontmatter2.next_steps.forEach((step, i) => {
3745
3747
  if (`${stem}#${step.id}` === id) {
3746
3748
  scan.subject = { kind: "loop", slug, file: rel, title: step.text };
3747
3749
  return;
@@ -3753,7 +3755,7 @@ function scanSessionFile(input, scan) {
3753
3755
  ])
3754
3756
  );
3755
3757
  });
3756
- frontmatter.resolves.forEach((entry, i) => {
3758
+ frontmatter2.resolves.forEach((entry, i) => {
3757
3759
  scan.references.push(
3758
3760
  ...matchFields(match, base, [
3759
3761
  [`resolves[${i}].ref`, entry.ref],
@@ -3768,12 +3770,12 @@ async function scanSessions(slug, id, match, scan) {
3768
3770
  for (const file of await listFiles(dir, ".md")) {
3769
3771
  const rel = path33.join("sessions", file);
3770
3772
  try {
3771
- const { frontmatter, body } = await readFrontmatter(
3773
+ const { frontmatter: frontmatter2, body } = await readFrontmatter(
3772
3774
  path33.join(dir, file),
3773
3775
  SessionFrontmatterSchema
3774
3776
  );
3775
3777
  const stem = file.slice(0, -".md".length);
3776
- scanSessionFile({ slug, stem, rel, id, match, frontmatter, body }, scan);
3778
+ scanSessionFile({ slug, stem, rel, id, match, frontmatter: frontmatter2, body }, scan);
3777
3779
  } catch (err) {
3778
3780
  scan.errors.push({ file: rel, error: errorText(err) });
3779
3781
  }
@@ -3897,9 +3899,9 @@ var list_default = defineCommand({
3897
3899
  const backburner = [];
3898
3900
  const paused = [];
3899
3901
  const done = [];
3900
- for (const { slug, frontmatter } of entries) {
3901
- const item = toItem(slug, frontmatter);
3902
- switch (frontmatter.state) {
3902
+ for (const { slug, frontmatter: frontmatter2 } of entries) {
3903
+ const item = toItem(slug, frontmatter2);
3904
+ switch (frontmatter2.state) {
3903
3905
  case "focused":
3904
3906
  focused.push(item);
3905
3907
  break;
@@ -3986,10 +3988,10 @@ var worktree_set_default = defineCommand({
3986
3988
  const initiativeDir = path34.join(getActiveRoot(), args.slug);
3987
3989
  const briefPath = path34.join(initiativeDir, "brief.md");
3988
3990
  return withFileLock(getLockPath(args.slug), async () => {
3989
- let frontmatter;
3991
+ let frontmatter2;
3990
3992
  let body;
3991
3993
  try {
3992
- ({ frontmatter, body } = await readFrontmatter(briefPath, BriefFrontmatterSchema));
3994
+ ({ frontmatter: frontmatter2, body } = await readFrontmatter(briefPath, BriefFrontmatterSchema));
3993
3995
  } catch (err) {
3994
3996
  throw new ValidationError(err instanceof Error ? err.message : String(err));
3995
3997
  }
@@ -4022,7 +4024,7 @@ var worktree_set_default = defineCommand({
4022
4024
  });
4023
4025
  await writeFrontmatter(
4024
4026
  briefPath,
4025
- { ...frontmatter, updated: today() },
4027
+ { ...frontmatter2, updated: today() },
4026
4028
  body,
4027
4029
  BriefFrontmatterSchema
4028
4030
  );
@@ -4060,10 +4062,10 @@ var worktree_set_default_default = defineCommand({
4060
4062
  const initiativeDir = path35.join(getActiveRoot(), slug);
4061
4063
  const briefPath = path35.join(initiativeDir, "brief.md");
4062
4064
  return withFileLock(getLockPath(slug), async () => {
4063
- let frontmatter;
4065
+ let frontmatter2;
4064
4066
  let body;
4065
4067
  try {
4066
- ({ frontmatter, body } = await readFrontmatter(briefPath, BriefFrontmatterSchema));
4068
+ ({ frontmatter: frontmatter2, body } = await readFrontmatter(briefPath, BriefFrontmatterSchema));
4067
4069
  } catch (err) {
4068
4070
  throw new ValidationError(err instanceof Error ? err.message : String(err));
4069
4071
  }
@@ -4079,7 +4081,7 @@ var worktree_set_default_default = defineCommand({
4079
4081
  await writeArtifactsFile(initiativeDir, { ...artifacts, worktrees });
4080
4082
  await writeFrontmatter(
4081
4083
  briefPath,
4082
- { ...frontmatter, updated: today() },
4084
+ { ...frontmatter2, updated: today() },
4083
4085
  body,
4084
4086
  BriefFrontmatterSchema
4085
4087
  );
@@ -5037,7 +5039,7 @@ var edit_default = edit;
5037
5039
 
5038
5040
  // src/commands/mcp-serve.ts
5039
5041
  import { spawn as spawn3 } from "child_process";
5040
- import { z as z42 } from "zod";
5042
+ import { z as z43 } from "zod";
5041
5043
 
5042
5044
  // src/server/mcp.ts
5043
5045
  import {
@@ -5053,9 +5055,8 @@ import {
5053
5055
  toolNameToCommandName as pkgToolNameToCommandName
5054
5056
  } from "@titan-design/registry";
5055
5057
 
5056
- // src/server/health.ts
5057
- var DAEMON_VERSION = "0.1.0";
5058
- var startedAt = Date.now();
5058
+ // src/version.ts
5059
+ var BUILD_VERSION = "0.5.0";
5059
5060
 
5060
5061
  // src/server/mcp.ts
5061
5062
  var TOOL_NAME_PREFIX = "active__";
@@ -5066,7 +5067,7 @@ function mcpOptions() {
5066
5067
  formatError,
5067
5068
  toolPrefix: TOOL_NAME_PREFIX,
5068
5069
  name: "@hjewkes/active-work",
5069
- version: DAEMON_VERSION
5070
+ version: BUILD_VERSION
5070
5071
  };
5071
5072
  }
5072
5073
  async function runMcpStdio() {
@@ -5198,22 +5199,145 @@ import { watchTree } from "@titan-design/daemon";
5198
5199
 
5199
5200
  // src/session-index/graph.ts
5200
5201
  import Database from "better-sqlite3";
5201
- import { MIGRATIONS, openSessionGraph } from "@titan-design/session-graph";
5202
+ import { openSessionGraph } from "@titan-design/session-graph";
5203
+ import { runMigrations, WatermarkTable } from "@titan-design/store-sqlite";
5202
5204
  import path46 from "path";
5205
+
5206
+ // src/workspace-index/schema.ts
5207
+ import { kitDdl } from "@titan-design/store-sqlite";
5208
+ import { MIGRATIONS as SESSION_GRAPH_MIGRATIONS } from "@titan-design/session-graph";
5209
+ var WORKSPACE_KIT = {
5210
+ watermark: "workspace_file",
5211
+ edge: "edge",
5212
+ spanFts: "search"
5213
+ };
5214
+ var WORKSPACE_SPAN_SOURCE_BASE = 1e9;
5215
+ var DOMAIN_DDL = `
5216
+ CREATE TABLE IF NOT EXISTS initiative (
5217
+ path TEXT PRIMARY KEY,
5218
+ initiative_ref TEXT NOT NULL UNIQUE,
5219
+ slug TEXT NOT NULL,
5220
+ title TEXT,
5221
+ state TEXT,
5222
+ rank INTEGER,
5223
+ ship_target TEXT,
5224
+ owner TEXT,
5225
+ task_prefix TEXT,
5226
+ updated TEXT
5227
+ );
5228
+
5229
+ CREATE TABLE IF NOT EXISTS note (
5230
+ path TEXT PRIMARY KEY,
5231
+ note_ref TEXT NOT NULL UNIQUE,
5232
+ initiative TEXT NOT NULL,
5233
+ filename TEXT NOT NULL,
5234
+ kind TEXT NOT NULL,
5235
+ title TEXT NOT NULL,
5236
+ created TEXT,
5237
+ tags TEXT,
5238
+ hits INTEGER NOT NULL DEFAULT 0,
5239
+ promoted_at TEXT
5240
+ );
5241
+ CREATE INDEX IF NOT EXISTS idx_note_initiative ON note(initiative);
5242
+
5243
+ CREATE TABLE IF NOT EXISTS workspace_task (
5244
+ path TEXT PRIMARY KEY,
5245
+ task_ref TEXT NOT NULL,
5246
+ initiative TEXT NOT NULL,
5247
+ task_id TEXT NOT NULL,
5248
+ title TEXT NOT NULL,
5249
+ status TEXT NOT NULL,
5250
+ priority INTEGER,
5251
+ severity TEXT,
5252
+ estimate REAL,
5253
+ tags TEXT,
5254
+ created TEXT,
5255
+ updated TEXT,
5256
+ done_at TEXT
5257
+ );
5258
+ CREATE INDEX IF NOT EXISTS idx_workspace_task_ref ON workspace_task(task_ref);
5259
+
5260
+ CREATE TABLE IF NOT EXISTS session_record (
5261
+ path TEXT PRIMARY KEY,
5262
+ session_ref TEXT NOT NULL,
5263
+ initiative TEXT NOT NULL,
5264
+ session_id TEXT NOT NULL,
5265
+ started TEXT,
5266
+ ended TEXT,
5267
+ track TEXT,
5268
+ parent_session_id TEXT
5269
+ );
5270
+ CREATE INDEX IF NOT EXISTS idx_session_record_ref ON session_record(session_ref);
5271
+
5272
+ CREATE TABLE IF NOT EXISTS source (
5273
+ path TEXT PRIMARY KEY,
5274
+ source_ref TEXT NOT NULL UNIQUE,
5275
+ initiative TEXT NOT NULL,
5276
+ title TEXT,
5277
+ kind TEXT,
5278
+ added TEXT
5279
+ );
5280
+ `;
5281
+ function nextVersion(chain) {
5282
+ return (chain[chain.length - 1]?.version ?? 0) + 1;
5283
+ }
5284
+ var SPAN_SOURCE_INDEX = `
5285
+ CREATE INDEX IF NOT EXISTS idx_search_span_source ON search_span(source_id);
5286
+ `;
5287
+ var WORKSPACE_MIGRATIONS = [
5288
+ {
5289
+ version: nextVersion(SESSION_GRAPH_MIGRATIONS),
5290
+ name: "workspace index tables",
5291
+ up: (db) => {
5292
+ db.exec(kitDdl({ watermark: WORKSPACE_KIT.watermark }));
5293
+ db.exec(DOMAIN_DDL);
5294
+ db.exec(SPAN_SOURCE_INDEX);
5295
+ }
5296
+ }
5297
+ ];
5298
+ var PRESERVE_DDL = `
5299
+ CREATE TABLE IF NOT EXISTS preserved_row (
5300
+ table_name TEXT NOT NULL,
5301
+ identity TEXT NOT NULL,
5302
+ row_key TEXT NOT NULL,
5303
+ payload TEXT NOT NULL,
5304
+ origin TEXT NOT NULL,
5305
+ mode TEXT NOT NULL DEFAULT 'insert',
5306
+ preserved_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now')),
5307
+ PRIMARY KEY (table_name, row_key)
5308
+ );
5309
+ `;
5310
+ var PRESERVE_MIGRATION = {
5311
+ version: nextVersion([...SESSION_GRAPH_MIGRATIONS, ...WORKSPACE_MIGRATIONS]),
5312
+ name: "preserved rows",
5313
+ up: (db) => db.exec(PRESERVE_DDL)
5314
+ };
5315
+ var MIGRATIONS = [
5316
+ ...SESSION_GRAPH_MIGRATIONS,
5317
+ ...WORKSPACE_MIGRATIONS,
5318
+ PRESERVE_MIGRATION
5319
+ ];
5320
+
5321
+ // src/session-index/graph.ts
5203
5322
  var SCHEMA_VERSION = MIGRATIONS[MIGRATIONS.length - 1]?.version ?? 0;
5204
5323
  function defaultGraphPath() {
5205
5324
  return path46.join(getMinerRoot(), "graph.sqlite3");
5206
5325
  }
5207
5326
  function openGraph(dbPath = defaultGraphPath()) {
5208
- return openSessionGraph(dbPath);
5327
+ const graph = openSessionGraph(dbPath);
5328
+ runMigrations(graph.db, MIGRATIONS);
5329
+ return {
5330
+ ...graph,
5331
+ workspaceFiles: new WatermarkTable(graph.db, { name: WORKSPACE_KIT.watermark })
5332
+ };
5209
5333
  }
5210
5334
  function openGraphReadOnly(dbPath = defaultGraphPath()) {
5211
5335
  return new Database(dbPath, { readonly: true });
5212
5336
  }
5213
5337
 
5214
5338
  // src/session-index/refresh.ts
5215
- import path48 from "path";
5216
- import { promises as fs36 } from "fs";
5339
+ import path56 from "path";
5340
+ import { promises as fs44 } from "fs";
5217
5341
  import lockfile from "proper-lockfile";
5218
5342
  import { discoverTranscripts, transcriptsRoot } from "@titan-design/session-read";
5219
5343
  import { refreshCorpus, resetIndex } from "@titan-design/session-graph";
@@ -5250,28 +5374,897 @@ async function readInitiative(root, slug) {
5250
5374
  continue;
5251
5375
  }
5252
5376
  }
5253
- return found;
5377
+ return found;
5378
+ }
5379
+ async function loadTaskStore(root = getActiveRoot()) {
5380
+ const store = /* @__PURE__ */ new Map();
5381
+ for (const slug of await initiativeSlugs(root)) {
5382
+ for (const [id, task] of await readInitiative(root, slug)) {
5383
+ if (store.has(id)) store.set(id, AMBIGUOUS);
5384
+ else store.set(id, task);
5385
+ }
5386
+ }
5387
+ return store;
5388
+ }
5389
+ function taskResolver(root) {
5390
+ return async (taskIds) => {
5391
+ const store = await loadTaskStore(root);
5392
+ const resolved = /* @__PURE__ */ new Map();
5393
+ for (const taskId of taskIds) {
5394
+ const task = store.get(taskId);
5395
+ if (task) resolved.set(taskId, task);
5396
+ }
5397
+ return resolved;
5398
+ };
5399
+ }
5400
+
5401
+ // src/workspace-index/refresh.ts
5402
+ import { promises as fs43 } from "fs";
5403
+ import matter4 from "gray-matter";
5404
+ import { z as z42 } from "zod";
5405
+
5406
+ // src/workspace-index/refs.ts
5407
+ import path48 from "path";
5408
+ import { refKind } from "@titan-design/store-sqlite";
5409
+ var initiativeRef = refKind("initiative");
5410
+ var noteRef = refKind("note");
5411
+ var taskRef = refKind("task");
5412
+ var sessionRef = refKind("session");
5413
+ var sourceRef = refKind("source");
5414
+ function toRelative(activeRoot, absolutePath) {
5415
+ return path48.relative(activeRoot, absolutePath).split(path48.sep).join("/");
5416
+ }
5417
+ function toAbsolute(activeRoot, relativePath) {
5418
+ return path48.join(activeRoot, ...relativePath.split("/"));
5419
+ }
5420
+
5421
+ // src/workspace-index/mentions.ts
5422
+ var TASK_ID = /(?<![A-Za-z0-9])([A-Za-z][A-Za-z0-9]*-\d+)(?![A-Za-z0-9])/g;
5423
+ var SESSION_ID = /(?<![A-Za-z0-9_-])(session_[A-Za-z0-9]+|[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})(?![A-Za-z0-9-])/gi;
5424
+ var NOTE_REF = /(?<![A-Za-z0-9])note:([A-Za-z0-9._-]+\/[A-Za-z0-9._-]+\.md)/g;
5425
+ var SOURCE_PATH = /sources\/([A-Za-z0-9._/-]+\.md)/g;
5426
+ function matches(body, pattern) {
5427
+ return [...body.matchAll(pattern)].map((match) => match[1]);
5428
+ }
5429
+ function extractMentions(body, initiative, index) {
5430
+ const found = /* @__PURE__ */ new Set();
5431
+ for (const id of matches(body, TASK_ID)) {
5432
+ if (index.taskIds.has(id.toUpperCase())) found.add(taskRef(id.toUpperCase()));
5433
+ }
5434
+ for (const id of matches(body, SESSION_ID)) {
5435
+ if (index.sessionIds.has(id)) found.add(sessionRef(id));
5436
+ }
5437
+ for (const id of matches(body, NOTE_REF)) {
5438
+ if (index.noteRefs.has(noteRef(id))) found.add(noteRef(id));
5439
+ }
5440
+ for (const path67 of matches(body, SOURCE_PATH)) {
5441
+ const scoped = `${initiative}/${path67}`;
5442
+ if (index.sourcePaths.has(scoped)) found.add(sourceRef(scoped));
5443
+ }
5444
+ return [...found].sort();
5445
+ }
5446
+
5447
+ // src/workspace-index/edges.ts
5448
+ var RELATIONS = {
5449
+ HOLDS: "holds",
5450
+ MENTIONS: "mentions",
5451
+ SHARES_TAG: "shares_tag"
5452
+ };
5453
+ var SHARES_TAG_MAX_NOTES = 10;
5454
+ function rebuildEdges(graph, notes) {
5455
+ const relations = Object.values(RELATIONS);
5456
+ const placeholders = relations.map(() => "?").join(", ");
5457
+ return graph.db.transaction(() => {
5458
+ graph.db.prepare(`DELETE FROM edge WHERE relation IN (${placeholders})`).run(...relations);
5459
+ return {
5460
+ holds: assertHolds(graph),
5461
+ mentions: assertMentions(graph, notes),
5462
+ sharesTag: assertSharesTag(graph, notes)
5463
+ };
5464
+ })();
5465
+ }
5466
+ var OWNED_REFS = `
5467
+ SELECT note_ref AS ref, initiative FROM note
5468
+ UNION ALL SELECT task_ref AS ref, initiative FROM workspace_task
5469
+ UNION ALL SELECT session_ref AS ref, initiative FROM session_record
5470
+ UNION ALL SELECT source_ref AS ref, initiative FROM source
5471
+ `;
5472
+ function assertHolds(graph) {
5473
+ const rows = graph.db.prepare(OWNED_REFS).all();
5474
+ let written = 0;
5475
+ for (const row of rows) {
5476
+ const inserted = graph.edges.assert({
5477
+ sourceRef: initiativeRef(row.initiative),
5478
+ relation: RELATIONS.HOLDS,
5479
+ targetRef: row.ref
5480
+ });
5481
+ if (inserted) written++;
5482
+ }
5483
+ return written;
5484
+ }
5485
+ function assertMentions(graph, notes) {
5486
+ const index = buildRefIndex(graph);
5487
+ let written = 0;
5488
+ for (const note of notes) {
5489
+ for (const target of extractMentions(note.body, note.initiative, index)) {
5490
+ if (target === note.ref) continue;
5491
+ const inserted = graph.edges.assert({
5492
+ sourceRef: note.ref,
5493
+ relation: RELATIONS.MENTIONS,
5494
+ targetRef: target
5495
+ });
5496
+ if (inserted) written++;
5497
+ }
5498
+ }
5499
+ return written;
5500
+ }
5501
+ function buildRefIndex(graph) {
5502
+ const column = (sql) => graph.db.prepare(sql).all().map((row) => row.value);
5503
+ return {
5504
+ taskIds: new Set(column("SELECT DISTINCT task_id AS value FROM workspace_task")),
5505
+ sessionIds: new Set(column("SELECT DISTINCT session_id AS value FROM session_record")),
5506
+ noteRefs: new Set(column("SELECT note_ref AS value FROM note")),
5507
+ // `source:` is 7 characters; what remains is the `<slug>/<path>` the ref was minted from.
5508
+ sourcePaths: new Set(column("SELECT substr(source_ref, 8) AS value FROM source"))
5509
+ };
5510
+ }
5511
+ function assertSharesTag(graph, notes) {
5512
+ let written = 0;
5513
+ for (const group of qualifyingTagGroups(notes)) {
5514
+ for (const [a, b] of crossInitiativePairs(group)) {
5515
+ for (const edge of [
5516
+ { sourceRef: a.ref, targetRef: b.ref },
5517
+ { sourceRef: b.ref, targetRef: a.ref }
5518
+ ]) {
5519
+ if (graph.edges.assert({ ...edge, relation: RELATIONS.SHARES_TAG })) written++;
5520
+ }
5521
+ }
5522
+ }
5523
+ return written;
5524
+ }
5525
+ function qualifyingTagGroups(notes) {
5526
+ const byTag = /* @__PURE__ */ new Map();
5527
+ for (const note of notes) {
5528
+ for (const tag of new Set(note.tags)) {
5529
+ const group = byTag.get(tag);
5530
+ if (group) group.push(note);
5531
+ else byTag.set(tag, [note]);
5532
+ }
5533
+ }
5534
+ return [...byTag.values()].filter(
5535
+ (group) => group.length <= SHARES_TAG_MAX_NOTES && new Set(group.map((note) => note.initiative)).size > 1
5536
+ );
5537
+ }
5538
+ function crossInitiativePairs(group) {
5539
+ const sorted = [...group].sort((a, b) => a.ref.localeCompare(b.ref));
5540
+ const pairs = [];
5541
+ for (let i = 0; i < sorted.length; i++) {
5542
+ for (let j = i + 1; j < sorted.length; j++) {
5543
+ if (sorted[i].initiative !== sorted[j].initiative) pairs.push([sorted[i], sorted[j]]);
5544
+ }
5545
+ }
5546
+ return pairs;
5547
+ }
5548
+
5549
+ // src/workspace-index/read.ts
5550
+ import { promises as fs36 } from "fs";
5551
+ import matter3 from "gray-matter";
5552
+ import YAML from "yaml";
5553
+
5554
+ // src/workspace-index/spans.ts
5555
+ var FRONTMATTER_FENCE = /^---\r?\n/;
5556
+ var FRONTMATTER_END = /^---[ \t]*\r?$/;
5557
+ function bodyByteOffset(raw) {
5558
+ if (!FRONTMATTER_FENCE.test(raw)) return 0;
5559
+ const lines = raw.split("\n");
5560
+ let consumed = lines[0].length + 1;
5561
+ for (let i = 1; i < lines.length; i++) {
5562
+ consumed += lines[i].length + 1;
5563
+ if (FRONTMATTER_END.test(lines[i])) return Buffer.byteLength(raw.slice(0, consumed), "utf8");
5564
+ }
5565
+ return 0;
5566
+ }
5567
+ function wholeFile(raw) {
5568
+ return { byteOffset: 0, byteLength: Buffer.byteLength(raw, "utf8") };
5569
+ }
5570
+ function locate(raw, field, text) {
5571
+ const trimmed = text.trim();
5572
+ if (trimmed.length === 0) return null;
5573
+ const at = raw.indexOf(trimmed);
5574
+ if (at < 0) return { field, text: trimmed, ...wholeFile(raw) };
5575
+ return {
5576
+ field,
5577
+ text: trimmed,
5578
+ byteOffset: Buffer.byteLength(raw.slice(0, at), "utf8"),
5579
+ byteLength: Buffer.byteLength(trimmed, "utf8")
5580
+ };
5581
+ }
5582
+ function sectionSpans(body, baseByteOffset, field = "body") {
5583
+ const spans = [];
5584
+ let cursor = baseByteOffset;
5585
+ for (const section of splitOnHeadings(body)) {
5586
+ const byteLength = Buffer.byteLength(section, "utf8");
5587
+ if (section.trim().length > 0) {
5588
+ spans.push({ field, text: section, byteOffset: cursor, byteLength });
5589
+ }
5590
+ cursor += byteLength;
5591
+ }
5592
+ return spans;
5593
+ }
5594
+ function splitOnHeadings(body) {
5595
+ const lines = body.split("\n");
5596
+ const sections = [];
5597
+ let current = [];
5598
+ for (const line of lines) {
5599
+ if (/^#{1,6}\s/.test(line) && current.length > 0) {
5600
+ sections.push(current.join("\n") + "\n");
5601
+ current = [];
5602
+ }
5603
+ current.push(line);
5604
+ }
5605
+ if (current.length > 0) sections.push(current.join("\n"));
5606
+ return sections;
5607
+ }
5608
+
5609
+ // src/workspace-index/read.ts
5610
+ var READERS = {
5611
+ initiative: readInitiative2,
5612
+ note: readNote,
5613
+ task: readTask,
5614
+ session: readSession2,
5615
+ source: readSource
5616
+ };
5617
+ async function readRecord(file) {
5618
+ const raw = await fs36.readFile(file.absolutePath, "utf8");
5619
+ return READERS[file.class](file, raw);
5620
+ }
5621
+ function frontmatter(raw) {
5622
+ const parsed = matter3(raw);
5623
+ return { data: coerceDates(parsed.data), body: parsed.content };
5624
+ }
5625
+ function jsonTags(tags) {
5626
+ return tags.length === 0 ? null : JSON.stringify(tags);
5627
+ }
5628
+ function readInitiative2(file, raw) {
5629
+ const { data, body } = frontmatter(raw);
5630
+ const brief = BriefFrontmatterSchema.parse(data);
5631
+ const lead = firstProse(body);
5632
+ return {
5633
+ file,
5634
+ ref: initiativeRef(file.slug),
5635
+ row: {
5636
+ slug: file.slug,
5637
+ title: brief.title,
5638
+ state: brief.state,
5639
+ rank: brief.rank ?? null,
5640
+ ship_target: brief.ship_target ?? null,
5641
+ owner: brief.owner ?? null,
5642
+ task_prefix: brief.task_prefix,
5643
+ updated: brief.updated
5644
+ },
5645
+ spans: compact([
5646
+ locate(raw, "title", brief.title),
5647
+ lead === void 0 ? null : locate(raw, "body", lead)
5648
+ ]),
5649
+ tags: [],
5650
+ body: ""
5651
+ };
5652
+ }
5653
+ function readNote(file, raw) {
5654
+ const { data, body } = frontmatter(raw);
5655
+ const note = NoteFrontmatterSchema.parse(data);
5656
+ const tags = note.tags ?? [];
5657
+ return {
5658
+ file,
5659
+ ref: noteRef(`${file.slug}/${basename(file.path)}`),
5660
+ row: {
5661
+ initiative: file.slug,
5662
+ filename: basename(file.path),
5663
+ kind: note.kind,
5664
+ title: note.title,
5665
+ created: note.created,
5666
+ tags: jsonTags(tags),
5667
+ // `hits` and `promoted_at` are the promotion machinery of the design's
5668
+ // §4 and the one thing here not derivable from the files, so a rebuild
5669
+ // resets them to zero. Owner accepted that on 2026-09-10.
5670
+ hits: 0,
5671
+ promoted_at: null
5672
+ },
5673
+ spans: [
5674
+ ...compact([locate(raw, "title", note.title)]),
5675
+ ...sectionSpans(body, bodyByteOffset(raw))
5676
+ ],
5677
+ tags,
5678
+ body
5679
+ };
5680
+ }
5681
+ function readTask(file, raw) {
5682
+ const task = TaskSchema.parse(coerceDates(YAML.parse(raw)));
5683
+ return {
5684
+ file,
5685
+ ref: taskRef(task.id),
5686
+ row: {
5687
+ initiative: file.slug,
5688
+ task_id: task.id,
5689
+ title: task.title,
5690
+ status: task.status,
5691
+ priority: task.priority,
5692
+ severity: task.severity ?? null,
5693
+ estimate: task.estimate ?? null,
5694
+ tags: jsonTags(task.tags ?? []),
5695
+ created: task.created,
5696
+ updated: task.updated,
5697
+ done_at: task.done_at
5698
+ },
5699
+ spans: compact([
5700
+ locate(raw, "title", task.title),
5701
+ task.done_when !== void 0 && locate(raw, "done_when", task.done_when),
5702
+ task.notes !== void 0 && locate(raw, "notes", task.notes)
5703
+ ]),
5704
+ tags: [],
5705
+ body: ""
5706
+ };
5707
+ }
5708
+ function readSession2(file, raw) {
5709
+ const { data, body } = frontmatter(raw);
5710
+ const session = SessionFrontmatterSchema.parse(data);
5711
+ return {
5712
+ file,
5713
+ ref: sessionRef(session.session_id),
5714
+ row: {
5715
+ initiative: file.slug,
5716
+ session_id: session.session_id,
5717
+ started: session.started,
5718
+ ended: session.ended,
5719
+ track: session.track,
5720
+ parent_session_id: session.parent_session_id ?? null
5721
+ },
5722
+ spans: sectionSpans(body, bodyByteOffset(raw)),
5723
+ tags: [],
5724
+ body: ""
5725
+ };
5726
+ }
5727
+ function readSource(file, raw) {
5728
+ const title = firstHeading2(raw) ?? basename(file.path).replace(/\.md$/, "");
5729
+ return {
5730
+ file,
5731
+ ref: sourceRef(`${file.slug}/${sourcesRelative(file.path)}`),
5732
+ row: {
5733
+ initiative: file.slug,
5734
+ title,
5735
+ kind: inferSourceKind(basename(file.path)),
5736
+ added: null
5737
+ },
5738
+ spans: [...compact([locate(raw, "title", title)]), ...sectionSpans(raw, 0)],
5739
+ tags: [],
5740
+ body: ""
5741
+ };
5742
+ }
5743
+ var PR_FILENAME2 = /^pr-\d+-/;
5744
+ var DEEPDIVE_FILENAME2 = /^deepdive-/;
5745
+ var SESSION_FILENAME2 = /^\d{4}-\d{2}-\d{2}-/;
5746
+ function inferSourceKind(filename) {
5747
+ if (PR_FILENAME2.test(filename)) return "pr";
5748
+ if (DEEPDIVE_FILENAME2.test(filename)) return "deepdive";
5749
+ if (SESSION_FILENAME2.test(filename)) return "session";
5750
+ return "pointer";
5751
+ }
5752
+ function basename(relativePath) {
5753
+ return relativePath.slice(relativePath.lastIndexOf("/") + 1);
5754
+ }
5755
+ function sourcesRelative(relativePath) {
5756
+ return relativePath.replace(/^[^/]+\/sources\//, "");
5757
+ }
5758
+ function firstHeading2(text) {
5759
+ for (const line of text.split("\n", 200)) {
5760
+ const match = /^#{1,6}\s+(.+?)\s*$/.exec(line);
5761
+ if (match) return match[1];
5762
+ }
5763
+ return void 0;
5764
+ }
5765
+ function firstProse(body) {
5766
+ for (const block of body.split(/\n\s*\n/)) {
5767
+ const trimmed = block.trim();
5768
+ if (trimmed.length > 0 && !trimmed.startsWith("#")) return trimmed;
5769
+ }
5770
+ return void 0;
5771
+ }
5772
+ function compact(spans) {
5773
+ return spans.filter((span) => Boolean(span));
5774
+ }
5775
+
5776
+ // src/workspace-index/scan.ts
5777
+ import { promises as fs42 } from "fs";
5778
+ import path55 from "path";
5779
+
5780
+ // src/lint/index.ts
5781
+ import { promises as fs41 } from "fs";
5782
+ import path54 from "path";
5783
+
5784
+ // src/lint/brief.ts
5785
+ import { promises as fs37 } from "fs";
5786
+ import path49 from "path";
5787
+
5788
+ // src/lint/hashes.ts
5789
+ import { promises as fs38 } from "fs";
5790
+ import path50 from "path";
5791
+ async function lintHashes(slug, initiativeDir) {
5792
+ const manifest = await readArtifactHashes(initiativeDir);
5793
+ const findings = [];
5794
+ for (const [relPath, storedHash] of Object.entries(manifest)) {
5795
+ let content;
5796
+ try {
5797
+ content = await fs38.readFile(path50.join(initiativeDir, relPath), "utf8");
5798
+ } catch (err) {
5799
+ const code = err.code;
5800
+ if (code === "ENOENT") continue;
5801
+ throw err;
5802
+ }
5803
+ if (hashContent(content) === storedHash) continue;
5804
+ findings.push({
5805
+ level: "warn",
5806
+ slug,
5807
+ file: relPath,
5808
+ message: `${relPath} was hand-edited outside active-work (content no longer matches the last CLI write) \u2014 re-apply the change through the CLI/MCP tools instead of editing the file directly`
5809
+ });
5810
+ }
5811
+ return findings;
5812
+ }
5813
+
5814
+ // src/lint/open-loops.ts
5815
+ import path51 from "path";
5816
+
5817
+ // src/lint/task.ts
5818
+ import { promises as fs39 } from "fs";
5819
+ import path52 from "path";
5820
+ import YAML2 from "yaml";
5821
+
5822
+ // src/lint/zero-loops.ts
5823
+ import { promises as fs40 } from "fs";
5824
+ import path53 from "path";
5825
+ import YAML3 from "yaml";
5826
+
5827
+ // src/lint/index.ts
5828
+ async function listInitiativeSlugs2(activeRoot) {
5829
+ let entries;
5830
+ try {
5831
+ entries = await fs41.readdir(activeRoot, { withFileTypes: true });
5832
+ } catch (err) {
5833
+ const code = err.code;
5834
+ if (code === "ENOENT") return [];
5835
+ throw err;
5836
+ }
5837
+ return entries.filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) => e.name).sort();
5838
+ }
5839
+
5840
+ // src/workspace-index/scan.ts
5841
+ async function readDirents(dir) {
5842
+ try {
5843
+ return await fs42.readdir(dir, { withFileTypes: true });
5844
+ } catch {
5845
+ return [];
5846
+ }
5847
+ }
5848
+ function isVisible(entry, extensions) {
5849
+ return entry.isFile() && !entry.name.startsWith(".") && extensions.some((ext) => entry.name.endsWith(ext));
5850
+ }
5851
+ async function filesIn(dir, extensions) {
5852
+ const entries = await readDirents(dir);
5853
+ return entries.filter((entry) => isVisible(entry, extensions)).map((entry) => path55.join(dir, entry.name)).sort();
5854
+ }
5855
+ async function taskFiles(tasksDir) {
5856
+ const own = await filesIn(tasksDir, [".yml", ".yaml"]);
5857
+ const archive = await filesIn(path55.join(tasksDir, "archive"), [".yml", ".yaml"]);
5858
+ return [...own, ...archive];
5859
+ }
5860
+ async function collectSlug(activeRoot, slug) {
5861
+ const dir = path55.join(activeRoot, slug);
5862
+ const brief = path55.join(dir, "brief.md");
5863
+ const [notes, tasks, sessions2, sources] = await Promise.all([
5864
+ filesIn(path55.join(dir, "sources", "notes"), [".md"]),
5865
+ taskFiles(path55.join(dir, "tasks")),
5866
+ filesIn(path55.join(dir, "sessions"), [".md"]),
5867
+ filesIn(path55.join(dir, "sources"), [".md"])
5868
+ ]);
5869
+ return [
5870
+ ...notes.map((p) => ["note", p]),
5871
+ ...tasks.map((p) => ["task", p]),
5872
+ ...sessions2.map((p) => ["session", p]),
5873
+ ...sources.map((p) => ["source", p]),
5874
+ ...await exists2(brief) ? [["initiative", brief]] : []
5875
+ ];
5876
+ }
5877
+ async function exists2(target) {
5878
+ try {
5879
+ await fs42.access(target);
5880
+ return true;
5881
+ } catch {
5882
+ return false;
5883
+ }
5884
+ }
5885
+ async function describe(activeRoot, slug, entry) {
5886
+ const [cls, absolutePath] = entry;
5887
+ try {
5888
+ const stat = await fs42.stat(absolutePath);
5889
+ return {
5890
+ class: cls,
5891
+ slug,
5892
+ path: toRelative(activeRoot, absolutePath),
5893
+ absolutePath,
5894
+ size: stat.size,
5895
+ mtime: stat.mtime.toISOString()
5896
+ };
5897
+ } catch {
5898
+ return null;
5899
+ }
5900
+ }
5901
+ async function scanWorkspace(activeRoot) {
5902
+ const slugs = await listInitiativeSlugs2(activeRoot);
5903
+ const perSlug = await Promise.all(
5904
+ slugs.map(async (slug) => {
5905
+ const entries = await collectSlug(activeRoot, slug);
5906
+ const described = await Promise.all(entries.map((e) => describe(activeRoot, slug, e)));
5907
+ return described.filter((file) => file !== null);
5908
+ })
5909
+ );
5910
+ return perSlug.flat().sort((a, b) => a.path.localeCompare(b.path));
5911
+ }
5912
+
5913
+ // src/workspace-index/write.ts
5914
+ var TABLES = {
5915
+ initiative: {
5916
+ table: "initiative",
5917
+ refColumn: "initiative_ref",
5918
+ columns: ["slug", "title", "state", "rank", "ship_target", "owner", "task_prefix", "updated"]
5919
+ },
5920
+ note: {
5921
+ table: "note",
5922
+ refColumn: "note_ref",
5923
+ columns: ["initiative", "filename", "kind", "title", "created", "tags", "hits", "promoted_at"]
5924
+ },
5925
+ task: {
5926
+ table: "workspace_task",
5927
+ refColumn: "task_ref",
5928
+ columns: [
5929
+ "initiative",
5930
+ "task_id",
5931
+ "title",
5932
+ "status",
5933
+ "priority",
5934
+ "severity",
5935
+ "estimate",
5936
+ "tags",
5937
+ "created",
5938
+ "updated",
5939
+ "done_at"
5940
+ ]
5941
+ },
5942
+ session: {
5943
+ table: "session_record",
5944
+ refColumn: "session_ref",
5945
+ columns: ["initiative", "session_id", "started", "ended", "track", "parent_session_id"]
5946
+ },
5947
+ source: {
5948
+ table: "source",
5949
+ refColumn: "source_ref",
5950
+ columns: ["initiative", "title", "kind", "added"]
5951
+ }
5952
+ };
5953
+ var CLASSES = Object.keys(TABLES);
5954
+ function insertSql({ table, refColumn, columns }) {
5955
+ const all = ["path", refColumn, ...columns];
5956
+ const names = all.map((c) => `"${c}"`).join(", ");
5957
+ return `INSERT OR REPLACE INTO "${table}" (${names}) VALUES (${all.map(() => "?").join(", ")})`;
5958
+ }
5959
+ function spanSourceId(watermarkId) {
5960
+ return WORKSPACE_SPAN_SOURCE_BASE + watermarkId;
5961
+ }
5962
+ var WorkspaceWriter = class {
5963
+ constructor(graph) {
5964
+ this.graph = graph;
5965
+ this.inserts = Object.fromEntries(
5966
+ CLASSES.map((cls) => [cls, graph.db.prepare(insertSql(TABLES[cls]))])
5967
+ );
5968
+ this.deletes = CLASSES.map(
5969
+ (cls) => graph.db.prepare(`DELETE FROM "${TABLES[cls].table}" WHERE path = ?`)
5970
+ );
5971
+ this.deleteSpans = graph.db.prepare("DELETE FROM search_span WHERE source_id = ?");
5972
+ }
5973
+ graph;
5974
+ inserts;
5975
+ deletes;
5976
+ deleteSpans;
5977
+ /**
5978
+ * Delete every row and span one file produced.
5979
+ *
5980
+ * Spans go by `source_id`, not by owner ref: `session:` owners are shared
5981
+ * with the transcript side of the same table, so purging by owner would take
5982
+ * a mined transcript's spans with it. The offset id names the file alone.
5983
+ *
5984
+ * This is the *opposite* of the transcript policy in the same database. A
5985
+ * transcript that disappears keeps its rows on purpose — Claude Code prunes
5986
+ * transcripts, and the sessions they witnessed still happened. A workspace
5987
+ * file that disappears was deleted or archived by the operator, and its rows
5988
+ * are a claim about a file that no longer says anything.
5989
+ */
5990
+ purge(relativePath, watermarkId) {
5991
+ for (const statement of this.deletes) statement.run(relativePath);
5992
+ this.deleteSpans.run(spanSourceId(watermarkId));
5993
+ }
5994
+ /**
5995
+ * Write one record's row and spans, replacing whatever the file produced
5996
+ * before. Not transactional on its own — the caller batches many of these
5997
+ * into one transaction, because a WAL commit per file is the difference
5998
+ * between a pass that runs in a moment and one that does not.
5999
+ */
6000
+ apply(record, watermarkId) {
6001
+ const spec = TABLES[record.file.class];
6002
+ this.purge(record.file.path, watermarkId);
6003
+ this.inserts[record.file.class].run(
6004
+ record.file.path,
6005
+ record.ref,
6006
+ ...spec.columns.map((column) => record.row[column] ?? null)
6007
+ );
6008
+ for (const span of record.spans) {
6009
+ this.graph.spans.index(
6010
+ {
6011
+ ownerRef: record.ref,
6012
+ field: span.field,
6013
+ sourceId: spanSourceId(watermarkId),
6014
+ byteOffset: span.byteOffset,
6015
+ byteLength: span.byteLength
6016
+ },
6017
+ span.text
6018
+ );
6019
+ }
6020
+ }
6021
+ };
6022
+ function resetWorkspaceIndex(graph) {
6023
+ graph.db.transaction(() => {
6024
+ for (const cls of CLASSES) graph.db.exec(`DELETE FROM "${TABLES[cls].table}"`);
6025
+ for (const row of graph.workspaceFiles.list()) graph.workspaceFiles.rewind(row.sourceKey);
6026
+ })();
6027
+ }
6028
+
6029
+ // src/workspace-index/refresh.ts
6030
+ function isUnchanged(row, file) {
6031
+ return row.lastOffset > 0 && row.fileSize === file.size && row.fileMtime === file.mtime;
6032
+ }
6033
+ var BATCH = 200;
6034
+ function describeFailure(err) {
6035
+ if (err instanceof z42.ZodError) {
6036
+ return err.issues.map((issue) => `${issue.path.join(".") || "(root)"}: ${issue.message}`).join("; ");
6037
+ }
6038
+ return err instanceof Error ? err.message : String(err);
6039
+ }
6040
+ async function readAttempt(file, watermarkId) {
6041
+ try {
6042
+ return { file, watermarkId, record: await readRecord(file), reason: null };
6043
+ } catch (err) {
6044
+ return { file, watermarkId, record: null, reason: describeFailure(err) };
6045
+ }
6046
+ }
6047
+ function applyBatch(graph, writer, batch) {
6048
+ graph.db.transaction(() => {
6049
+ for (const attempt of batch) {
6050
+ if (attempt.record === null) {
6051
+ writer.purge(attempt.file.path, attempt.watermarkId);
6052
+ graph.workspaceFiles.markStatus(attempt.file.path, "quarantined", attempt.reason);
6053
+ continue;
6054
+ }
6055
+ writer.apply(attempt.record, attempt.watermarkId);
6056
+ graph.workspaceFiles.advance(attempt.file.path, {
6057
+ lastOffset: attempt.file.size,
6058
+ fileSize: attempt.file.size,
6059
+ fileMtime: attempt.file.mtime
6060
+ });
6061
+ }
6062
+ })();
6063
+ }
6064
+ function removeVanished(graph, writer, present) {
6065
+ let removed = 0;
6066
+ for (const row of graph.workspaceFiles.list()) {
6067
+ if (present.has(row.sourceKey)) continue;
6068
+ writer.purge(row.sourceKey, row.sourceId);
6069
+ graph.db.prepare("DELETE FROM workspace_file WHERE source_id = ?").run(row.sourceId);
6070
+ removed++;
6071
+ }
6072
+ return removed;
6073
+ }
6074
+ async function collectNoteFacts(activeRoot, graph) {
6075
+ const rows = graph.db.prepare("SELECT path, note_ref, initiative, tags FROM note ORDER BY path").all();
6076
+ const facts = await Promise.all(
6077
+ rows.map(async (row) => ({
6078
+ ref: row.note_ref,
6079
+ initiative: row.initiative,
6080
+ tags: JSON.parse(row.tags ?? "[]") ?? [],
6081
+ body: await readBody(toAbsolute(activeRoot, row.path))
6082
+ }))
6083
+ );
6084
+ return facts;
6085
+ }
6086
+ async function readBody(absolutePath) {
6087
+ try {
6088
+ return matter4(await fs43.readFile(absolutePath, "utf8")).content;
6089
+ } catch {
6090
+ return "";
6091
+ }
6092
+ }
6093
+ function countRows(graph) {
6094
+ const count = (table) => graph.db.prepare(`SELECT COUNT(*) AS n FROM "${table}"`).get().n;
6095
+ return {
6096
+ initiative: count("initiative"),
6097
+ note: count("note"),
6098
+ task: count("workspace_task"),
6099
+ session: count("session_record"),
6100
+ source: count("source")
6101
+ };
6102
+ }
6103
+ async function refreshWorkspace(graph, options = {}) {
6104
+ const activeRoot = options.activeRoot ?? getActiveRoot();
6105
+ const writer = new WorkspaceWriter(graph);
6106
+ const files = await scanWorkspace(activeRoot);
6107
+ const malformed = [];
6108
+ let indexed = 0;
6109
+ let unchanged = 0;
6110
+ const changed = [];
6111
+ graph.db.transaction(() => {
6112
+ for (const file of files) {
6113
+ const row = graph.workspaceFiles.ensure(file.path);
6114
+ if (!options.full && isUnchanged(row, file)) unchanged++;
6115
+ else changed.push({ file, watermarkId: row.sourceId });
6116
+ }
6117
+ })();
6118
+ for (let start = 0; start < changed.length; start += BATCH) {
6119
+ const batch = await Promise.all(
6120
+ changed.slice(start, start + BATCH).map((e) => readAttempt(e.file, e.watermarkId))
6121
+ );
6122
+ applyBatch(graph, writer, batch);
6123
+ indexed += batch.filter((a) => a.record !== null).length;
6124
+ for (const failed of batch.filter((a) => a.record === null)) {
6125
+ malformed.push({ path: failed.file.path, reason: failed.reason ?? "unreadable" });
6126
+ }
6127
+ }
6128
+ const removed = removeVanished(graph, writer, new Set(files.map((f) => f.path)));
6129
+ const edges = rebuildEdges(graph, await collectNoteFacts(activeRoot, graph));
6130
+ return {
6131
+ files: files.length,
6132
+ indexed,
6133
+ unchanged,
6134
+ removed,
6135
+ malformed,
6136
+ rows: countRows(graph),
6137
+ edges,
6138
+ orphanRatio: graph.spans.orphanRatio()
6139
+ };
6140
+ }
6141
+
6142
+ // src/session-index/preserve.ts
6143
+ var UPSERT = `
6144
+ INSERT INTO preserved_row (table_name, identity, row_key, payload, origin, mode)
6145
+ VALUES (@table, @identity, @key, @payload, @origin, @mode)
6146
+ ON CONFLICT (table_name, row_key) DO UPDATE SET
6147
+ identity = excluded.identity, payload = excluded.payload,
6148
+ origin = excluded.origin, mode = excluded.mode
6149
+ `;
6150
+ function preserveRow(graph, row) {
6151
+ if (row.identity.length === 0) throw new Error(`preserveRow: ${row.table} needs an identity`);
6152
+ if (row.mode === "merge" && row.identity.length !== 1) {
6153
+ throw new Error(`preserveRow: a merge over ${row.table} matches exactly one column`);
6154
+ }
6155
+ const missing = row.mode === "insert" ? row.identity.filter((c) => !(c in row.payload)) : [];
6156
+ if (missing.length > 0) {
6157
+ throw new Error(`preserveRow: ${row.table} payload is missing ${missing.join(", ")}`);
6158
+ }
6159
+ graph.db.prepare(UPSERT).run({
6160
+ ...row,
6161
+ identity: JSON.stringify(row.identity),
6162
+ payload: JSON.stringify(row.payload)
6163
+ });
5254
6164
  }
5255
- async function loadTaskStore(root = getActiveRoot()) {
5256
- const store = /* @__PURE__ */ new Map();
5257
- for (const slug of await initiativeSlugs(root)) {
5258
- for (const [id, task] of await readInitiative(root, slug)) {
5259
- if (store.has(id)) store.set(id, AMBIGUOUS);
5260
- else store.set(id, task);
5261
- }
6165
+ function listPreserved(graph) {
6166
+ const rows = graph.db.prepare("SELECT * FROM preserved_row ORDER BY table_name, row_key").all();
6167
+ return rows.map((row) => ({
6168
+ table: row.table_name,
6169
+ identity: JSON.parse(row.identity),
6170
+ key: row.row_key,
6171
+ payload: JSON.parse(row.payload),
6172
+ origin: row.origin,
6173
+ mode: row.mode
6174
+ }));
6175
+ }
6176
+ var UNREACHABLE_SESSIONS = `
6177
+ SELECT s.session_id FROM session s
6178
+ WHERE NOT EXISTS (
6179
+ SELECT 1 FROM fact f JOIN transcript t ON t.source_id = f.transcript_id
6180
+ WHERE f.session_id = s.session_id AND t.status = 'ok'
6181
+ )
6182
+ ORDER BY s.session_id
6183
+ `;
6184
+ var SESSION_OWNED = [
6185
+ { table: "session", identity: ["session_id"], key: (r) => String(r.session_id) },
6186
+ { table: "turn", identity: ["prompt_id"], key: (r) => String(r.prompt_id) },
6187
+ {
6188
+ table: "session_model_usage",
6189
+ identity: ["session_id", "model"],
6190
+ key: (r) => `${r.session_id}:${r.model}`
6191
+ },
6192
+ {
6193
+ table: "fact",
6194
+ identity: ["transcript_id", "byte_offset"],
6195
+ key: (r) => `${r.transcript_id}:${r.byte_offset}`
6196
+ },
6197
+ {
6198
+ table: "permission_phase",
6199
+ identity: ["session_id", "t_valid", "to_mode"],
6200
+ key: (r) => `${r.session_id}:${r.t_valid}:${r.to_mode}`
6201
+ },
6202
+ {
6203
+ table: "human_edit",
6204
+ identity: ["session_id", "file_path", "ts"],
6205
+ key: (r) => `${r.session_id}:${r.file_path}:${r.ts}`
5262
6206
  }
5263
- return store;
6207
+ ];
6208
+ var SYNTHETIC_KEYS = /* @__PURE__ */ new Set(["fact_id", "phase_id", "edit_id", "checkpoint_id"]);
6209
+ function payloadOf(row) {
6210
+ return Object.fromEntries(Object.entries(row).filter(([column]) => !SYNTHETIC_KEYS.has(column)));
5264
6211
  }
5265
- function taskResolver(root) {
5266
- return async (taskIds) => {
5267
- const store = await loadTaskStore(root);
5268
- const resolved = /* @__PURE__ */ new Map();
5269
- for (const taskId of taskIds) {
5270
- const task = store.get(taskId);
5271
- if (task) resolved.set(taskId, task);
6212
+ function preserveUnreachable(graph, origin) {
6213
+ const ids = graph.db.prepare(UNREACHABLE_SESSIONS).all().map(
6214
+ (row) => row.session_id
6215
+ );
6216
+ if (ids.length === 0) return 0;
6217
+ const placeholders = ids.map(() => "?").join(", ");
6218
+ let preserved = 0;
6219
+ graph.db.transaction(() => {
6220
+ for (const spec of SESSION_OWNED) {
6221
+ const rows = graph.db.prepare(`SELECT * FROM "${spec.table}" WHERE session_id IN (${placeholders})`).all(...ids);
6222
+ for (const row of rows) {
6223
+ const payload = payloadOf(row);
6224
+ preserveRow(graph, {
6225
+ table: spec.table,
6226
+ identity: spec.identity,
6227
+ key: spec.key(row),
6228
+ payload,
6229
+ origin,
6230
+ mode: "insert"
6231
+ });
6232
+ preserved++;
6233
+ }
5272
6234
  }
5273
- return resolved;
5274
- };
6235
+ })();
6236
+ return preserved;
6237
+ }
6238
+ function restore(graph, row) {
6239
+ const columns = Object.keys(row.payload);
6240
+ const names = columns.map((c) => `"${c}"`).join(", ");
6241
+ const values = columns.map((c) => `@${c}`).join(", ");
6242
+ const match = row.identity.map((c) => `"${c}" IS @${c}`).join(" AND ");
6243
+ const info = graph.db.prepare(
6244
+ `INSERT INTO "${row.table}" (${names}) SELECT ${values}
6245
+ WHERE NOT EXISTS (SELECT 1 FROM "${row.table}" WHERE ${match})`
6246
+ ).run(row.payload);
6247
+ return info.changes > 0;
6248
+ }
6249
+ function merge(graph, row) {
6250
+ const assignments = Object.keys(row.payload).map((c) => `"${c}" = @${c}`).join(", ");
6251
+ const info = graph.db.prepare(`UPDATE "${row.table}" SET ${assignments} WHERE "${row.identity[0]}" = @__key`).run({ ...row.payload, __key: row.key });
6252
+ return info.changes > 0;
6253
+ }
6254
+ function replayPreserved(graph) {
6255
+ const summary = { restored: 0, merged: 0, skipped: 0 };
6256
+ graph.db.transaction(() => {
6257
+ for (const row of listPreserved(graph)) {
6258
+ if (row.mode === "merge") {
6259
+ if (merge(graph, row)) summary.merged++;
6260
+ else summary.skipped++;
6261
+ continue;
6262
+ }
6263
+ if (restore(graph, row)) summary.restored++;
6264
+ else summary.skipped++;
6265
+ }
6266
+ })();
6267
+ return summary;
5275
6268
  }
5276
6269
 
5277
6270
  // src/session-index/refresh.ts
@@ -5281,8 +6274,8 @@ function refreshLockPath() {
5281
6274
  }
5282
6275
  async function withRefreshLock(fn) {
5283
6276
  const target = refreshLockPath();
5284
- await fs36.mkdir(path48.dirname(target), { recursive: true });
5285
- await fs36.writeFile(target, "", { flag: "a" });
6277
+ await fs44.mkdir(path56.dirname(target), { recursive: true });
6278
+ await fs44.writeFile(target, "", { flag: "a" });
5286
6279
  const release = await lockfile.lock(target, {
5287
6280
  realpath: false,
5288
6281
  stale: LOCK_STALE_MS,
@@ -5295,12 +6288,16 @@ async function withRefreshLock(fn) {
5295
6288
  }
5296
6289
  }
5297
6290
  async function runRefresh(options = {}) {
5298
- const startedAt2 = (/* @__PURE__ */ new Date()).toISOString();
6291
+ const startedAt = (/* @__PURE__ */ new Date()).toISOString();
5299
6292
  const started = Date.now();
5300
6293
  const graph = options.graph ?? openGraph(options.dbPath ?? defaultGraphPath());
5301
6294
  const owned = options.graph === void 0;
5302
6295
  try {
5303
- if (options.full) resetIndex(graph);
6296
+ if (options.full) {
6297
+ preserveUnreachable(graph, "transcript pruned before this rebuild (TP-41)");
6298
+ resetIndex(graph);
6299
+ resetWorkspaceIndex(graph);
6300
+ }
5304
6301
  const discovered = await discoverTranscripts(options.root ?? transcriptsRoot());
5305
6302
  const visiting = discovered.slice(0, options.limit ?? discovered.length);
5306
6303
  const verify = options.verifyHashes ?? options.full ?? false;
@@ -5310,8 +6307,13 @@ async function runRefresh(options = {}) {
5310
6307
  withContentHash: verify,
5311
6308
  resolveTasks: taskResolver(options.taskRoot)
5312
6309
  });
6310
+ const workspace = options.skipWorkspace ? null : await refreshWorkspace(graph, {
6311
+ activeRoot: options.activeRoot ?? options.taskRoot,
6312
+ full: options.full
6313
+ });
6314
+ const preserved = replayPreserved(graph);
5313
6315
  return {
5314
- startedAt: startedAt2,
6316
+ startedAt,
5315
6317
  durationMs: Date.now() - started,
5316
6318
  transcripts: discovered.length,
5317
6319
  scanned: visiting.length,
@@ -5325,9 +6327,14 @@ async function runRefresh(options = {}) {
5325
6327
  turnsRolledUp: summary.turnsRolledUp,
5326
6328
  tasksRequested: summary.tasks.requested,
5327
6329
  tasksApplied: summary.tasks.applied,
6330
+ workspace,
6331
+ preserved,
5328
6332
  errors: [
5329
6333
  ...summary.tasks.failed ? [`tasks: ${summary.tasks.error ?? "resolver failed"}`] : [],
5330
- ...quarantineErrors(graph)
6334
+ ...quarantineErrors(graph),
6335
+ ...(workspace?.malformed ?? []).map(
6336
+ (entry) => `workspace: ${entry.path} \u2014 ${entry.reason}`
6337
+ )
5331
6338
  ]
5332
6339
  };
5333
6340
  } finally {
@@ -5492,7 +6499,7 @@ async function runDaemon(options = {}) {
5492
6499
  stateDir: getStateRoot(),
5493
6500
  port: resolvePort(options),
5494
6501
  watchRoot: getActiveRoot(),
5495
- version: DAEMON_VERSION,
6502
+ version: BUILD_VERSION,
5496
6503
  logger: log,
5497
6504
  health: () => ({ index: toHealthIndexState(indexWatch?.status()) }),
5498
6505
  mountRoutes: (app) => {
@@ -5531,15 +6538,15 @@ async function runDaemon(options = {}) {
5531
6538
  }
5532
6539
 
5533
6540
  // src/commands/mcp-serve.ts
5534
- var ArgsSchema38 = z42.object({
5535
- stdio: z42.boolean().optional(),
5536
- detach: z42.boolean().optional(),
5537
- port: z42.number().int().positive().optional()
6541
+ var ArgsSchema38 = z43.object({
6542
+ stdio: z43.boolean().optional(),
6543
+ detach: z43.boolean().optional(),
6544
+ port: z43.number().int().positive().optional()
5538
6545
  });
5539
- var ResultSchema34 = z42.object({
5540
- mode: z42.enum(["stdio", "http", "detached"]),
5541
- pid: z42.number().optional(),
5542
- port: z42.number().optional()
6546
+ var ResultSchema34 = z43.object({
6547
+ mode: z43.enum(["stdio", "http", "detached"]),
6548
+ pid: z43.number().optional(),
6549
+ port: z43.number().optional()
5543
6550
  });
5544
6551
  function detachedSpawn(port) {
5545
6552
  const entry = process.argv[1];
@@ -5594,11 +6601,11 @@ var mcp_serve_default = defineCommand({
5594
6601
  });
5595
6602
 
5596
6603
  // src/commands/mcp-stop.ts
5597
- import { z as z43 } from "zod";
5598
- var ArgsSchema39 = z43.object({});
5599
- var ResultSchema35 = z43.union([
5600
- z43.object({ stopped: z43.literal(true), pid: z43.number() }),
5601
- z43.object({ stopped: z43.literal(false), reason: z43.string() })
6604
+ import { z as z44 } from "zod";
6605
+ var ArgsSchema39 = z44.object({});
6606
+ var ResultSchema35 = z44.union([
6607
+ z44.object({ stopped: z44.literal(true), pid: z44.number() }),
6608
+ z44.object({ stopped: z44.literal(false), reason: z44.string() })
5602
6609
  ]);
5603
6610
  var SHUTDOWN_TIMEOUT_MS = 3e3;
5604
6611
  var POLL_INTERVAL_MS = 100;
@@ -5643,13 +6650,13 @@ var mcp_stop_default = defineCommand({
5643
6650
 
5644
6651
  // src/commands/mcp-restart.ts
5645
6652
  import { spawn as spawn4 } from "child_process";
5646
- import { z as z44 } from "zod";
5647
- var ArgsSchema40 = z44.object({
5648
- port: z44.number().int().positive().optional()
6653
+ import { z as z45 } from "zod";
6654
+ var ArgsSchema40 = z45.object({
6655
+ port: z45.number().int().positive().optional()
5649
6656
  });
5650
- var ResultSchema36 = z44.object({
5651
- pid: z44.number(),
5652
- port: z44.number()
6657
+ var ResultSchema36 = z45.object({
6658
+ pid: z45.number(),
6659
+ port: z45.number()
5653
6660
  });
5654
6661
  var SHUTDOWN_TIMEOUT_MS2 = 15e3;
5655
6662
  var KILL_TIMEOUT_MS = 3e3;
@@ -5737,17 +6744,17 @@ var mcp_restart_default = defineCommand({
5737
6744
  });
5738
6745
 
5739
6746
  // src/commands/mcp-status.ts
5740
- import { z as z45 } from "zod";
5741
- var ArgsSchema41 = z45.object({});
5742
- var ResultSchema37 = z45.object({
5743
- running: z45.boolean(),
5744
- pid: z45.number().optional(),
5745
- port: z45.number().optional(),
5746
- version: z45.string().optional(),
5747
- uptime_ms: z45.number().optional(),
5748
- healthy: z45.boolean().optional(),
6747
+ import { z as z46 } from "zod";
6748
+ var ArgsSchema41 = z46.object({});
6749
+ var ResultSchema37 = z46.object({
6750
+ running: z46.boolean(),
6751
+ pid: z46.number().optional(),
6752
+ port: z46.number().optional(),
6753
+ version: z46.string().optional(),
6754
+ uptime_ms: z46.number().optional(),
6755
+ healthy: z46.boolean().optional(),
5749
6756
  /** Answering `/health` with no PID file naming it — see `removePidFile`. */
5750
- orphaned: z45.boolean().optional()
6757
+ orphaned: z46.boolean().optional()
5751
6758
  });
5752
6759
  async function statusByPort(port) {
5753
6760
  const health = await probeHealth(port);
@@ -5798,14 +6805,14 @@ var mcp_status_default = defineCommand({
5798
6805
  });
5799
6806
 
5800
6807
  // src/commands/mcp-logs.ts
5801
- import { promises as fs37 } from "fs";
5802
- import path49 from "path";
5803
- import { z as z46 } from "zod";
5804
- var ArgsSchema42 = z46.object({
5805
- lines: z46.number().int().positive().optional()
6808
+ import { promises as fs45 } from "fs";
6809
+ import path57 from "path";
6810
+ import { z as z47 } from "zod";
6811
+ var ArgsSchema42 = z47.object({
6812
+ lines: z47.number().int().positive().optional()
5806
6813
  });
5807
- var ResultSchema38 = z46.object({
5808
- lines: z46.array(z46.string())
6814
+ var ResultSchema38 = z47.object({
6815
+ lines: z47.array(z47.string())
5809
6816
  });
5810
6817
  var DEFAULT_LINES = 50;
5811
6818
  var mcp_logs_default = defineCommand({
@@ -5823,10 +6830,10 @@ var mcp_logs_default = defineCommand({
5823
6830
  },
5824
6831
  async run(args) {
5825
6832
  const n = args.lines ?? DEFAULT_LINES;
5826
- const logPath = path49.join(getStateRoot(), "daemon.log");
6833
+ const logPath = path57.join(getStateRoot(), "daemon.log");
5827
6834
  let content;
5828
6835
  try {
5829
- content = await fs37.readFile(logPath, "utf8");
6836
+ content = await fs45.readFile(logPath, "utf8");
5830
6837
  } catch (err) {
5831
6838
  if (err.code === "ENOENT") {
5832
6839
  return { lines: [] };
@@ -5842,7 +6849,7 @@ var mcp_logs_default = defineCommand({
5842
6849
  });
5843
6850
 
5844
6851
  // src/commands/miner-drain-ingest.ts
5845
- import { z as z50 } from "zod";
6852
+ import { z as z51 } from "zod";
5846
6853
 
5847
6854
  // src/drain/transcript-reader.ts
5848
6855
  import { nextOffset, prefixHash, readJsonLines, resumePoint } from "@titan-design/locator";
@@ -5852,41 +6859,41 @@ import { discoverTranscripts as discoverTranscripts2, transcriptsRoot as transcr
5852
6859
  import { Clusterer } from "@titan-design/cluster";
5853
6860
 
5854
6861
  // src/drain/store.ts
5855
- import { promises as fs38 } from "fs";
5856
- import path50 from "path";
6862
+ import { promises as fs46 } from "fs";
6863
+ import path58 from "path";
5857
6864
 
5858
6865
  // src/schemas/template.ts
5859
- import { z as z47 } from "zod";
5860
- var LocatorSchema = z47.tuple([
5861
- z47.number().int().nonnegative(),
5862
- z47.number().int().nonnegative(),
5863
- z47.number().int().positive()
6866
+ import { z as z48 } from "zod";
6867
+ var LocatorSchema = z48.tuple([
6868
+ z48.number().int().nonnegative(),
6869
+ z48.number().int().nonnegative(),
6870
+ z48.number().int().positive()
5864
6871
  ]);
5865
- var TemplateSchema = z47.object({
5866
- templateId: z47.string().min(1),
5867
- toolType: z47.string().min(1),
5868
- maskedSignature: z47.string().min(1),
5869
- createdAt: z47.string().min(1),
5870
- occurrenceCount: z47.number().int().nonnegative(),
6872
+ var TemplateSchema = z48.object({
6873
+ templateId: z48.string().min(1),
6874
+ toolType: z48.string().min(1),
6875
+ maskedSignature: z48.string().min(1),
6876
+ createdAt: z48.string().min(1),
6877
+ occurrenceCount: z48.number().int().nonnegative(),
5871
6878
  exemplarLocator: LocatorSchema
5872
6879
  });
5873
- var OccurrenceSchema = z47.object({
5874
- templateId: z47.string().min(1),
6880
+ var OccurrenceSchema = z48.object({
6881
+ templateId: z48.string().min(1),
5875
6882
  locator: LocatorSchema,
5876
- sessionId: z47.string().min(1),
5877
- timestamp: z47.string().min(1),
5878
- extractedParams: z47.record(z47.string(), z47.string()).optional()
6883
+ sessionId: z48.string().min(1),
6884
+ timestamp: z48.string().min(1),
6885
+ extractedParams: z48.record(z48.string(), z48.string()).optional()
5879
6886
  });
5880
- var TemplatesFileSchema = z47.object({
5881
- templates: z47.array(TemplateSchema).default([])
6887
+ var TemplatesFileSchema = z48.object({
6888
+ templates: z48.array(TemplateSchema).default([])
5882
6889
  });
5883
6890
 
5884
6891
  // src/drain/store.ts
5885
6892
  function templatesPath(root) {
5886
- return path50.join(root, "templates.yml");
6893
+ return path58.join(root, "templates.yml");
5887
6894
  }
5888
6895
  function occurrencesPath(root) {
5889
- return path50.join(root, "occurrences.jsonl");
6896
+ return path58.join(root, "occurrences.jsonl");
5890
6897
  }
5891
6898
  async function loadTemplates(root = getMinerRoot()) {
5892
6899
  try {
@@ -5898,15 +6905,15 @@ async function loadTemplates(root = getMinerRoot()) {
5898
6905
  }
5899
6906
  }
5900
6907
  async function saveTemplates(templates, root = getMinerRoot()) {
5901
- await fs38.mkdir(root, { recursive: true });
6908
+ await fs46.mkdir(root, { recursive: true });
5902
6909
  await writeYaml(templatesPath(root), { templates }, TemplatesFileSchema);
5903
6910
  }
5904
6911
  async function appendOccurrence(occurrence, root = getMinerRoot()) {
5905
6912
  const parsed = OccurrenceSchema.parse(occurrence);
5906
6913
  const target = occurrencesPath(root);
5907
- await fs38.mkdir(root, { recursive: true });
6914
+ await fs46.mkdir(root, { recursive: true });
5908
6915
  await withFileLock(`${target}.lock`, async () => {
5909
- await fs38.appendFile(target, `${JSON.stringify(parsed)}
6916
+ await fs46.appendFile(target, `${JSON.stringify(parsed)}
5910
6917
  `, "utf8");
5911
6918
  });
5912
6919
  }
@@ -5914,41 +6921,41 @@ async function appendOccurrences(occurrences, root = getMinerRoot()) {
5914
6921
  if (occurrences.length === 0) return;
5915
6922
  const parsed = occurrences.map((o) => OccurrenceSchema.parse(o));
5916
6923
  const target = occurrencesPath(root);
5917
- await fs38.mkdir(root, { recursive: true });
6924
+ await fs46.mkdir(root, { recursive: true });
5918
6925
  const body = `${parsed.map((o) => JSON.stringify(o)).join("\n")}
5919
6926
  `;
5920
6927
  await withFileLock(`${target}.lock`, async () => {
5921
- await fs38.appendFile(target, body, "utf8");
6928
+ await fs46.appendFile(target, body, "utf8");
5922
6929
  });
5923
6930
  }
5924
6931
 
5925
6932
  // src/drain/tree-store.ts
5926
- import { promises as fs39 } from "fs";
5927
- import path51 from "path";
5928
- import { z as z48 } from "zod";
5929
- var PartitionSchema = z48.object({
5930
- partition: z48.string().min(1),
5931
- nextClusterId: z48.number().int().positive(),
5932
- clusters: z48.array(
5933
- z48.object({
5934
- clusterId: z48.number().int().positive(),
5935
- tokens: z48.array(z48.string()),
5936
- size: z48.number().int().nonnegative()
6933
+ import { promises as fs47 } from "fs";
6934
+ import path59 from "path";
6935
+ import { z as z49 } from "zod";
6936
+ var PartitionSchema = z49.object({
6937
+ partition: z49.string().min(1),
6938
+ nextClusterId: z49.number().int().positive(),
6939
+ clusters: z49.array(
6940
+ z49.object({
6941
+ clusterId: z49.number().int().positive(),
6942
+ tokens: z49.array(z49.string()),
6943
+ size: z49.number().int().nonnegative()
5937
6944
  })
5938
6945
  ),
5939
6946
  /** `clusterId -> templateId`, as pairs so the file has a stable order. */
5940
- templateIds: z48.array(z48.tuple([z48.number().int().positive(), z48.string().min(1)]))
6947
+ templateIds: z49.array(z49.tuple([z49.number().int().positive(), z49.string().min(1)]))
5941
6948
  });
5942
- var TreeSnapshotFileSchema = z48.object({
5943
- version: z48.literal(1).default(1),
5944
- partitions: z48.array(PartitionSchema).default([])
6949
+ var TreeSnapshotFileSchema = z49.object({
6950
+ version: z49.literal(1).default(1),
6951
+ partitions: z49.array(PartitionSchema).default([])
5945
6952
  });
5946
- var LegacySnapshotFileSchema = z48.object({
5947
- version: z48.literal(1),
5948
- trees: z48.array(PartitionSchema.omit({ partition: true }).extend({ toolType: z48.string().min(1) }))
6953
+ var LegacySnapshotFileSchema = z49.object({
6954
+ version: z49.literal(1),
6955
+ trees: z49.array(PartitionSchema.omit({ partition: true }).extend({ toolType: z49.string().min(1) }))
5949
6956
  });
5950
6957
  function snapshotPath(root) {
5951
- return path51.join(root, "drain-trees.json");
6958
+ return path59.join(root, "drain-trees.json");
5952
6959
  }
5953
6960
  function empty() {
5954
6961
  return { version: 1, partitions: [] };
@@ -5956,7 +6963,7 @@ function empty() {
5956
6963
  async function loadTreeSnapshots(root = getMinerRoot()) {
5957
6964
  let raw;
5958
6965
  try {
5959
- raw = await fs39.readFile(snapshotPath(root), "utf8");
6966
+ raw = await fs47.readFile(snapshotPath(root), "utf8");
5960
6967
  } catch (err) {
5961
6968
  if (err instanceof Error && "code" in err && err.code === "ENOENT") return empty();
5962
6969
  throw err;
@@ -5976,7 +6983,7 @@ async function loadTreeSnapshots(root = getMinerRoot()) {
5976
6983
  return parsed.success ? parsed.data : empty();
5977
6984
  }
5978
6985
  async function saveTreeSnapshots(snapshot, root = getMinerRoot()) {
5979
- await fs39.mkdir(root, { recursive: true });
6986
+ await fs47.mkdir(root, { recursive: true });
5980
6987
  await atomicWrite(
5981
6988
  snapshotPath(root),
5982
6989
  `${JSON.stringify(TreeSnapshotFileSchema.parse(snapshot))}
@@ -6126,33 +7133,33 @@ function extractBlobs(line, toolNames) {
6126
7133
  }
6127
7134
 
6128
7135
  // src/drain/reader-state.ts
6129
- import { promises as fs40 } from "fs";
6130
- import path52 from "path";
6131
- import { z as z49 } from "zod";
6132
- var TranscriptStateSchema = z49.object({
7136
+ import { promises as fs48 } from "fs";
7137
+ import path60 from "path";
7138
+ import { z as z50 } from "zod";
7139
+ var TranscriptStateSchema = z50.object({
6133
7140
  /** `~`-relative path, matching `session-index/discover.ts`'s display form. */
6134
- path: z49.string().min(1),
6135
- lastByteOffset: z49.number().int().nonnegative().default(0),
7141
+ path: z50.string().min(1),
7142
+ lastByteOffset: z50.number().int().nonnegative().default(0),
6136
7143
  /** sha256 of bytes `[0, lastByteOffset)`; detects rewrite vs. append. */
6137
- prefixHash: z49.string().nullable().default(null),
7144
+ prefixHash: z50.string().nullable().default(null),
6138
7145
  /**
6139
7146
  * `tool_use_id -> tool name` pairs seen but not yet consumed by a result.
6140
7147
  * Persisted because a chunk boundary routinely falls between an assistant's
6141
7148
  * `tool_use` and the user line carrying its result.
6142
7149
  */
6143
- pendingToolNames: z49.record(z49.string(), z49.string()).default({})
7150
+ pendingToolNames: z50.record(z50.string(), z50.string()).default({})
6144
7151
  });
6145
- var ReaderStateSchema = z49.object({
6146
- version: z49.literal(1).default(1),
6147
- transcripts: z49.array(TranscriptStateSchema).default([])
7152
+ var ReaderStateSchema = z50.object({
7153
+ version: z50.literal(1).default(1),
7154
+ transcripts: z50.array(TranscriptStateSchema).default([])
6148
7155
  });
6149
7156
  var MAX_PENDING_TOOL_NAMES = 256;
6150
7157
  function readerStatePath(root = getMinerRoot()) {
6151
- return path52.join(root, "reader-state.json");
7158
+ return path60.join(root, "reader-state.json");
6152
7159
  }
6153
7160
  async function loadReaderState(root = getMinerRoot()) {
6154
7161
  try {
6155
- const raw = await fs40.readFile(readerStatePath(root), "utf8");
7162
+ const raw = await fs48.readFile(readerStatePath(root), "utf8");
6156
7163
  return ReaderStateSchema.parse(JSON.parse(raw));
6157
7164
  } catch (err) {
6158
7165
  if (err instanceof Error && "code" in err && err.code === "ENOENT") {
@@ -6162,7 +7169,7 @@ async function loadReaderState(root = getMinerRoot()) {
6162
7169
  }
6163
7170
  }
6164
7171
  async function saveReaderState(state, root = getMinerRoot()) {
6165
- await fs40.mkdir(root, { recursive: true });
7172
+ await fs48.mkdir(root, { recursive: true });
6166
7173
  await atomicWrite(
6167
7174
  readerStatePath(root),
6168
7175
  `${JSON.stringify(ReaderStateSchema.parse(state), null, 2)}
@@ -6255,7 +7262,7 @@ async function ingestLine(parsed, locator, toolNames, ingestor, counters, sample
6255
7262
  }
6256
7263
  }
6257
7264
  async function runDrainIngest(options = {}) {
6258
- const startedAt2 = (/* @__PURE__ */ new Date()).toISOString();
7265
+ const startedAt = (/* @__PURE__ */ new Date()).toISOString();
6259
7266
  const started = process.hrtime.bigint();
6260
7267
  const root = options.root ?? getMinerRoot();
6261
7268
  const corpusRoot = options.corpusRoot ?? transcriptsRoot3();
@@ -6302,7 +7309,7 @@ async function runDrainIngest(options = {}) {
6302
7309
  await ingestor.flush();
6303
7310
  await saveReaderState(state, root);
6304
7311
  return {
6305
- startedAt: startedAt2,
7312
+ startedAt,
6306
7313
  durationMs: Number(process.hrtime.bigint() - started) / 1e6,
6307
7314
  transcripts: discovered.length,
6308
7315
  scanned,
@@ -6315,27 +7322,27 @@ async function runDrainIngest(options = {}) {
6315
7322
  }
6316
7323
 
6317
7324
  // src/commands/miner-drain-ingest.ts
6318
- var ArgsSchema43 = z50.object({
6319
- full: z50.boolean().optional(),
6320
- limit: z50.coerce.number().int().positive().optional(),
6321
- verify_hashes: z50.boolean().optional()
6322
- });
6323
- var ResultSchema39 = z50.object({
6324
- startedAt: z50.string(),
6325
- durationMs: z50.number(),
6326
- transcripts: z50.number(),
6327
- scanned: z50.number(),
6328
- unchanged: z50.number(),
6329
- rewound: z50.number(),
6330
- linesRead: z50.number(),
6331
- malformedLines: z50.number(),
6332
- blobs: z50.number(),
6333
- ingested: z50.number(),
6334
- newTemplates: z50.number(),
6335
- templates: z50.number(),
6336
- evicting: z50.boolean(),
6337
- curve: z50.array(z50.object({ blobs: z50.number(), templates: z50.number(), evicting: z50.boolean() })),
6338
- errors: z50.array(z50.string())
7325
+ var ArgsSchema43 = z51.object({
7326
+ full: z51.boolean().optional(),
7327
+ limit: z51.coerce.number().int().positive().optional(),
7328
+ verify_hashes: z51.boolean().optional()
7329
+ });
7330
+ var ResultSchema39 = z51.object({
7331
+ startedAt: z51.string(),
7332
+ durationMs: z51.number(),
7333
+ transcripts: z51.number(),
7334
+ scanned: z51.number(),
7335
+ unchanged: z51.number(),
7336
+ rewound: z51.number(),
7337
+ linesRead: z51.number(),
7338
+ malformedLines: z51.number(),
7339
+ blobs: z51.number(),
7340
+ ingested: z51.number(),
7341
+ newTemplates: z51.number(),
7342
+ templates: z51.number(),
7343
+ evicting: z51.boolean(),
7344
+ curve: z51.array(z51.object({ blobs: z51.number(), templates: z51.number(), evicting: z51.boolean() })),
7345
+ errors: z51.array(z51.string())
6339
7346
  });
6340
7347
  var miner_drain_ingest_default = defineCommand({
6341
7348
  name: "miner.drain-ingest",
@@ -6368,28 +7375,46 @@ var miner_drain_ingest_default = defineCommand({
6368
7375
  });
6369
7376
 
6370
7377
  // src/commands/miner-refresh.ts
6371
- import { z as z51 } from "zod";
6372
- var ArgsSchema44 = z51.object({
6373
- full: z51.boolean().optional(),
6374
- limit: z51.coerce.number().int().positive().optional(),
6375
- verify_hashes: z51.boolean().optional()
6376
- });
6377
- var ResultSchema40 = z51.object({
6378
- startedAt: z51.string(),
6379
- durationMs: z51.number(),
6380
- transcripts: z51.number(),
6381
- scanned: z51.number(),
6382
- indexed: z51.number(),
6383
- rewound: z51.number(),
6384
- unchanged: z51.number(),
6385
- quarantined: z51.number(),
6386
- missing: z51.number(),
6387
- reconciledMissing: z51.number(),
6388
- factsAdded: z51.number(),
6389
- turnsRolledUp: z51.number(),
6390
- tasksRequested: z51.number(),
6391
- tasksApplied: z51.number(),
6392
- errors: z51.array(z51.string())
7378
+ import { z as z52 } from "zod";
7379
+ var ArgsSchema44 = z52.object({
7380
+ full: z52.boolean().optional(),
7381
+ limit: z52.coerce.number().int().positive().optional(),
7382
+ verify_hashes: z52.boolean().optional()
7383
+ });
7384
+ var WorkspaceSchema = z52.object({
7385
+ files: z52.number(),
7386
+ indexed: z52.number(),
7387
+ unchanged: z52.number(),
7388
+ removed: z52.number(),
7389
+ malformed: z52.array(z52.object({ path: z52.string(), reason: z52.string() })),
7390
+ rows: z52.object({
7391
+ initiative: z52.number(),
7392
+ note: z52.number(),
7393
+ task: z52.number(),
7394
+ session: z52.number(),
7395
+ source: z52.number()
7396
+ }),
7397
+ edges: z52.object({ holds: z52.number(), mentions: z52.number(), sharesTag: z52.number() }),
7398
+ orphanRatio: z52.number()
7399
+ });
7400
+ var ResultSchema40 = z52.object({
7401
+ startedAt: z52.string(),
7402
+ durationMs: z52.number(),
7403
+ transcripts: z52.number(),
7404
+ scanned: z52.number(),
7405
+ indexed: z52.number(),
7406
+ rewound: z52.number(),
7407
+ unchanged: z52.number(),
7408
+ quarantined: z52.number(),
7409
+ missing: z52.number(),
7410
+ reconciledMissing: z52.number(),
7411
+ factsAdded: z52.number(),
7412
+ turnsRolledUp: z52.number(),
7413
+ tasksRequested: z52.number(),
7414
+ tasksApplied: z52.number(),
7415
+ workspace: WorkspaceSchema.nullable(),
7416
+ preserved: z52.object({ restored: z52.number(), merged: z52.number(), skipped: z52.number() }),
7417
+ errors: z52.array(z52.string())
6393
7418
  });
6394
7419
  var miner_refresh_default = defineCommand({
6395
7420
  name: "miner.refresh",
@@ -6421,11 +7446,11 @@ var miner_refresh_default = defineCommand({
6421
7446
 
6422
7447
  // src/commands/miner-liveness.ts
6423
7448
  import { existsSync as existsSync3 } from "fs";
6424
- import { z as z52 } from "zod";
7449
+ import { z as z53 } from "zod";
6425
7450
 
6426
7451
  // src/session-index/liveness.ts
6427
7452
  import { existsSync as existsSync2 } from "fs";
6428
- import { RELATIONS, toAbsolutePath } from "@titan-design/session-read";
7453
+ import { RELATIONS as RELATIONS2, toAbsolutePath } from "@titan-design/session-read";
6429
7454
  var SKIP_TABLES = /^(sqlite_|_migration$|search_fts)/;
6430
7455
  var EXPECTED_EMPTY = {
6431
7456
  "edge.t_invalid": "NULL means current; the index is rebuilt from scratch, so no edge is ever superseded in place",
@@ -6433,20 +7458,29 @@ var EXPECTED_EMPTY = {
6433
7458
  "edge.attrs": "kit column; the session graph writes no per-edge attributes"
6434
7459
  };
6435
7460
  var REF_TABLES = {
6436
- // `session` is the odd one out: it stores the bare id, every other entity
6437
- // table stores the prefixed ref. Comparing without allowing for that reports
6438
- // every session endpoint as dangling — a false alarm, which in a diagnostic
6439
- // is worse than no check at all.
6440
- session: { table: "session", column: "session_id", bare: true },
6441
- agent: { table: "subagent", column: "agent_ref" },
6442
- file: { table: "file", column: "file_ref" },
6443
- branch: { table: "branch", column: "branch_ref" },
6444
- task: { table: "task", column: "task_ref" },
6445
- artifact: { table: "artifact", column: "artifact_ref" },
7461
+ // `session` is the odd one out: `session` stores the bare id, every other
7462
+ // entity table stores the prefixed ref. Comparing without allowing for that
7463
+ // reports every session endpoint as dangling — a false alarm, which in a
7464
+ // diagnostic is worse than no check at all.
7465
+ session: [
7466
+ { table: "session", column: "session_id", bare: true },
7467
+ { table: "session_record", column: "session_ref" }
7468
+ ],
7469
+ agent: [{ table: "subagent", column: "agent_ref" }],
7470
+ file: [{ table: "file", column: "file_ref" }],
7471
+ branch: [{ table: "branch", column: "branch_ref" }],
7472
+ task: [
7473
+ { table: "task", column: "task_ref" },
7474
+ { table: "workspace_task", column: "task_ref" }
7475
+ ],
7476
+ artifact: [{ table: "artifact", column: "artifact_ref" }],
6446
7477
  // `pr` is keyed by `pr_ref` like every other entity table. It was left
6447
7478
  // unmapped on the belief that it was keyed by `(number, repo)` — that is
6448
7479
  // `pr_merge_observation`, a different table (AW-107).
6449
- pr: { table: "pr", column: "pr_ref" }
7480
+ pr: [{ table: "pr", column: "pr_ref" }],
7481
+ initiative: [{ table: "initiative", column: "initiative_ref" }],
7482
+ note: [{ table: "note", column: "note_ref" }],
7483
+ source: [{ table: "source", column: "source_ref" }]
6450
7484
  };
6451
7485
  function tableNames(db) {
6452
7486
  const rows = db.prepare("SELECT name FROM sqlite_master WHERE type = 'table' ORDER BY name").all();
@@ -6468,11 +7502,12 @@ function scanTable(db, table) {
6468
7502
  nonNull: row[`c${index}`] ?? 0
6469
7503
  }));
6470
7504
  }
7505
+ var WORKSPACE_RELATIONS = ["holds", "mentions", "shares_tag"];
6471
7506
  function relationLiveness(db) {
6472
7507
  const observed = new Map(
6473
7508
  db.prepare("SELECT relation, COUNT(*) AS n FROM edge GROUP BY relation").all().map((row) => [row.relation, row.n])
6474
7509
  );
6475
- const declared = Object.values(RELATIONS);
7510
+ const declared = [...Object.values(RELATIONS2), ...WORKSPACE_RELATIONS];
6476
7511
  const report2 = declared.map((relation) => ({
6477
7512
  relation,
6478
7513
  count: observed.get(relation) ?? 0,
@@ -6492,19 +7527,22 @@ function refNamespaces(db) {
6492
7527
  ) WHERE namespace <> '' GROUP BY namespace ORDER BY namespace`
6493
7528
  ).all();
6494
7529
  return rows.map(({ namespace, n }) => {
6495
- const target = REF_TABLES[namespace];
6496
- if (!target) return { namespace, edges: n, dangling: null };
6497
- const lhs = target.bare ? `substr(e.ref, ${namespace.length + 2})` : "e.ref";
6498
- const { c } = db.prepare(
6499
- `SELECT COUNT(*) AS c FROM (
6500
- SELECT source_ref AS ref FROM edge UNION ALL SELECT target_ref FROM edge
6501
- ) e
6502
- WHERE e.ref LIKE @prefix
6503
- AND NOT EXISTS (SELECT 1 FROM "${target.table}" t WHERE t."${target.column}" = ${lhs})`
6504
- ).get({ prefix: `${namespace}:%` });
7530
+ const targets = REF_TABLES[namespace];
7531
+ if (!targets) return { namespace, edges: n, dangling: null };
7532
+ const { c } = db.prepare(danglingSql(namespace, targets)).get({ prefix: `${namespace}:%` });
6505
7533
  return { namespace, edges: n, dangling: c };
6506
7534
  });
6507
7535
  }
7536
+ function danglingSql(namespace, targets) {
7537
+ const absent = targets.map((target) => {
7538
+ const lhs = target.bare ? `substr(e.ref, ${namespace.length + 2})` : "e.ref";
7539
+ return `NOT EXISTS (SELECT 1 FROM "${target.table}" t WHERE t."${target.column}" = ${lhs})`;
7540
+ }).join(" AND ");
7541
+ return `SELECT COUNT(*) AS c FROM (
7542
+ SELECT source_ref AS ref FROM edge UNION ALL SELECT target_ref FROM edge
7543
+ ) e
7544
+ WHERE e.ref LIKE @prefix AND ${absent}`;
7545
+ }
6508
7546
  function staleTranscripts(db) {
6509
7547
  const rows = db.prepare("SELECT source_key FROM transcript WHERE status = 'ok'").all();
6510
7548
  return rows.filter((row) => !existsSync2(toAbsolutePath(row.source_key))).length;
@@ -6525,22 +7563,22 @@ function runLiveness(db) {
6525
7563
  }
6526
7564
 
6527
7565
  // src/commands/miner-liveness.ts
6528
- var ArgsSchema45 = z52.object({});
6529
- var ResultSchema41 = z52.object({
6530
- emptyColumns: z52.array(
6531
- z52.object({ table: z52.string(), column: z52.string(), rows: z52.number(), nonNull: z52.number() })
7566
+ var ArgsSchema45 = z53.object({});
7567
+ var ResultSchema41 = z53.object({
7568
+ emptyColumns: z53.array(
7569
+ z53.object({ table: z53.string(), column: z53.string(), rows: z53.number(), nonNull: z53.number() })
6532
7570
  ),
6533
- unusedRelations: z52.array(z52.string()),
6534
- undeclaredRelations: z52.array(z52.string()),
6535
- danglingNamespaces: z52.array(
6536
- z52.object({ namespace: z52.string(), edges: z52.number(), dangling: z52.number() })
7571
+ unusedRelations: z53.array(z53.string()),
7572
+ undeclaredRelations: z53.array(z53.string()),
7573
+ danglingNamespaces: z53.array(
7574
+ z53.object({ namespace: z53.string(), edges: z53.number(), dangling: z53.number() })
6537
7575
  ),
6538
- unmappedNamespaces: z52.array(z52.string()),
6539
- expectedEmptyColumns: z52.array(
6540
- z52.object({ table: z52.string(), column: z52.string(), reason: z52.string() })
7576
+ unmappedNamespaces: z53.array(z53.string()),
7577
+ expectedEmptyColumns: z53.array(
7578
+ z53.object({ table: z53.string(), column: z53.string(), reason: z53.string() })
6541
7579
  ),
6542
- staleTranscripts: z52.number(),
6543
- transcripts: z52.number()
7580
+ staleTranscripts: z53.number(),
7581
+ transcripts: z53.number()
6544
7582
  });
6545
7583
  function report(result) {
6546
7584
  const lines = [color.bold("active-work miner liveness")];
@@ -6631,40 +7669,40 @@ var miner_liveness_default = defineCommand({
6631
7669
 
6632
7670
  // src/commands/miner-status.ts
6633
7671
  import { existsSync as existsSync4, statSync } from "fs";
6634
- import { z as z53 } from "zod";
6635
- var ArgsSchema46 = z53.object({});
6636
- var ResultSchema42 = z53.object({
6637
- dbPath: z53.string(),
6638
- schemaVersion: z53.number(),
6639
- sizeBytes: z53.number(),
6640
- counts: z53.object({
6641
- transcripts: z53.number(),
6642
- sessions: z53.number(),
6643
- facts: z53.number(),
6644
- turns: z53.number(),
6645
- edges: z53.number(),
6646
- spans: z53.number()
7672
+ import { z as z54 } from "zod";
7673
+ var ArgsSchema46 = z54.object({});
7674
+ var ResultSchema42 = z54.object({
7675
+ dbPath: z54.string(),
7676
+ schemaVersion: z54.number(),
7677
+ sizeBytes: z54.number(),
7678
+ counts: z54.object({
7679
+ transcripts: z54.number(),
7680
+ sessions: z54.number(),
7681
+ facts: z54.number(),
7682
+ turns: z54.number(),
7683
+ edges: z54.number(),
7684
+ spans: z54.number()
6647
7685
  }),
6648
- transcripts: z53.object({
6649
- ok: z53.number(),
6650
- quarantined: z53.number(),
6651
- missing: z53.number()
7686
+ transcripts: z54.object({
7687
+ ok: z54.number(),
7688
+ quarantined: z54.number(),
7689
+ missing: z54.number()
6652
7690
  }),
6653
- watermark: z53.object({
6654
- lastIndexedAt: z53.string().nullable(),
6655
- behindBytes: z53.number()
7691
+ watermark: z54.object({
7692
+ lastIndexedAt: z54.string().nullable(),
7693
+ behindBytes: z54.number()
6656
7694
  }),
6657
- fts: z53.object({
6658
- rows: z53.number(),
6659
- orphanRows: z53.number(),
6660
- needsFullRebuild: z53.boolean()
7695
+ fts: z54.object({
7696
+ rows: z54.number(),
7697
+ orphanRows: z54.number(),
7698
+ needsFullRebuild: z54.boolean()
6661
7699
  }),
6662
- daemon: z53.object({
6663
- indexing: z53.boolean(),
6664
- pending: z53.boolean(),
6665
- lastRunAt: z53.string().nullable(),
6666
- lastDurationMs: z53.number().nullable(),
6667
- consecutiveErrors: z53.number()
7700
+ daemon: z54.object({
7701
+ indexing: z54.boolean(),
7702
+ pending: z54.boolean(),
7703
+ lastRunAt: z54.string().nullable(),
7704
+ lastDurationMs: z54.number().nullable(),
7705
+ consecutiveErrors: z54.number()
6668
7706
  }).nullable()
6669
7707
  });
6670
7708
  var ORPHAN_WARN_RATIO = 0.2;
@@ -6748,7 +7786,7 @@ var miner_status_default = defineCommand({
6748
7786
  });
6749
7787
 
6750
7788
  // src/commands/hooks-agent-chat-spawn.ts
6751
- import { z as z55 } from "zod";
7789
+ import { z as z56 } from "zod";
6752
7790
 
6753
7791
  // src/utils/read-stdin-json.ts
6754
7792
  async function readStdinJson(stream = process.stdin) {
@@ -6767,14 +7805,14 @@ async function readStdinJson(stream = process.stdin) {
6767
7805
  }
6768
7806
 
6769
7807
  // src/utils/agent-chat-hook-state.ts
6770
- import { promises as fs41 } from "fs";
6771
- import path53 from "path";
6772
- import { z as z54 } from "zod";
6773
- var SpawnContextSchema = z54.object({
6774
- slug: z54.string().min(1),
6775
- sessionId: z54.string().min(1),
6776
- name: z54.string(),
6777
- started: z54.string(),
7808
+ import { promises as fs49 } from "fs";
7809
+ import path61 from "path";
7810
+ import { z as z55 } from "zod";
7811
+ var SpawnContextSchema = z55.object({
7812
+ slug: z55.string().min(1),
7813
+ sessionId: z55.string().min(1),
7814
+ name: z55.string(),
7815
+ started: z55.string(),
6778
7816
  /**
6779
7817
  * The spawning session, already resolved from the payload's `parent`
6780
7818
  * agentId to a session id. Resolved at spawn time on purpose: `parent` names
@@ -6782,25 +7820,25 @@ var SpawnContextSchema = z54.object({
6782
7820
  * another live entry in this same directory — which is gone by the time
6783
7821
  * on_complete runs, because reading one deletes it.
6784
7822
  */
6785
- parentSessionId: z54.string().min(1).nullable().default(null),
7823
+ parentSessionId: z55.string().min(1).nullable().default(null),
6786
7824
  /** agent-chat profile and briefing slug, for the recorded session's prose. */
6787
- profile: z54.string().nullable().default(null),
6788
- briefing: z54.string().nullable().default(null)
7825
+ profile: z55.string().nullable().default(null),
7826
+ briefing: z55.string().nullable().default(null)
6789
7827
  });
6790
7828
  function stateDir() {
6791
- return path53.join(getStateRoot(), "agent-chat-hooks");
7829
+ return path61.join(getStateRoot(), "agent-chat-hooks");
6792
7830
  }
6793
7831
  function stateFile(agentId) {
6794
- return path53.join(stateDir(), `${agentId}.json`);
7832
+ return path61.join(stateDir(), `${agentId}.json`);
6795
7833
  }
6796
7834
  async function stashSpawnContext(agentId, context) {
6797
- await fs41.mkdir(stateDir(), { recursive: true });
7835
+ await fs49.mkdir(stateDir(), { recursive: true });
6798
7836
  await atomicWrite(stateFile(agentId), JSON.stringify(context));
6799
7837
  }
6800
7838
  async function readSpawnContext(agentId) {
6801
7839
  let raw;
6802
7840
  try {
6803
- raw = await fs41.readFile(stateFile(agentId), "utf8");
7841
+ raw = await fs49.readFile(stateFile(agentId), "utf8");
6804
7842
  } catch {
6805
7843
  return null;
6806
7844
  }
@@ -6809,7 +7847,7 @@ async function readSpawnContext(agentId) {
6809
7847
  }
6810
7848
  async function takeSpawnContext(agentId) {
6811
7849
  const context = await readSpawnContext(agentId);
6812
- await fs41.rm(stateFile(agentId), { force: true });
7850
+ await fs49.rm(stateFile(agentId), { force: true });
6813
7851
  return context;
6814
7852
  }
6815
7853
  async function peekSpawnContext(agentId) {
@@ -6817,10 +7855,10 @@ async function peekSpawnContext(agentId) {
6817
7855
  }
6818
7856
 
6819
7857
  // src/commands/hooks-agent-chat-spawn.ts
6820
- var ArgsSchema47 = z55.object({});
6821
- var ResultSchema43 = z55.object({
6822
- matched: z55.boolean(),
6823
- slug: z55.string().nullable()
7858
+ var ArgsSchema47 = z56.object({});
7859
+ var ResultSchema43 = z56.object({
7860
+ matched: z56.boolean(),
7861
+ slug: z56.string().nullable()
6824
7862
  });
6825
7863
  function str2(source, key) {
6826
7864
  const value = source?.[key];
@@ -6862,11 +7900,11 @@ var hooks_agent_chat_spawn_default = defineCommand({
6862
7900
 
6863
7901
  // src/commands/hooks-agent-chat-complete.ts
6864
7902
  import { spawn as spawn5 } from "child_process";
6865
- import { z as z56 } from "zod";
6866
- var ArgsSchema48 = z56.object({});
6867
- var ResultSchema44 = z56.object({
6868
- recorded: z56.boolean(),
6869
- slug: z56.string().nullable()
7903
+ import { z as z57 } from "zod";
7904
+ var ArgsSchema48 = z57.object({});
7905
+ var ResultSchema44 = z57.object({
7906
+ recorded: z57.boolean(),
7907
+ slug: z57.string().nullable()
6870
7908
  });
6871
7909
  function str3(source, key) {
6872
7910
  const value = source?.[key];
@@ -6945,7 +7983,7 @@ var hooks_agent_chat_complete_default = defineCommand({
6945
7983
  });
6946
7984
 
6947
7985
  // src/commands/setup.ts
6948
- import { z as z58 } from "zod";
7986
+ import { z as z59 } from "zod";
6949
7987
 
6950
7988
  // src/setup/steps.ts
6951
7989
  import { promises as fsp3, existsSync as existsSync5 } from "fs";
@@ -6960,9 +7998,9 @@ import { readFile, writeFile } from "fs/promises";
6960
7998
  import { join } from "path";
6961
7999
 
6962
8000
  // src/migrations/v1-to-v2-artifacts.ts
6963
- import { promises as fs42 } from "fs";
6964
- import path54 from "path";
6965
- import YAML from "yaml";
8001
+ import { promises as fs50 } from "fs";
8002
+ import path62 from "path";
8003
+ import YAML4 from "yaml";
6966
8004
  function asArray(value) {
6967
8005
  return Array.isArray(value) ? value : [];
6968
8006
  }
@@ -6987,13 +8025,13 @@ function normaliseStash(s) {
6987
8025
  async function migrateOne(filePath) {
6988
8026
  let raw;
6989
8027
  try {
6990
- raw = await fs42.readFile(filePath, "utf8");
8028
+ raw = await fs50.readFile(filePath, "utf8");
6991
8029
  } catch {
6992
8030
  return { changed: false, droppedPrs: [] };
6993
8031
  }
6994
8032
  let parsed;
6995
8033
  try {
6996
- parsed = YAML.parse(raw) ?? {};
8034
+ parsed = YAML4.parse(raw) ?? {};
6997
8035
  } catch {
6998
8036
  return { changed: false, droppedPrs: [] };
6999
8037
  }
@@ -7017,38 +8055,38 @@ async function migrateOne(filePath) {
7017
8055
  async function walkArtifactsFiles(activeRoot) {
7018
8056
  const out = [];
7019
8057
  try {
7020
- const entries = await fs42.readdir(activeRoot, { withFileTypes: true });
8058
+ const entries = await fs50.readdir(activeRoot, { withFileTypes: true });
7021
8059
  for (const entry of entries) {
7022
8060
  if (!entry.isDirectory()) continue;
7023
8061
  if (entry.name.startsWith(".")) continue;
7024
- const candidate = path54.join(activeRoot, entry.name, "artifacts.yml");
8062
+ const candidate = path62.join(activeRoot, entry.name, "artifacts.yml");
7025
8063
  try {
7026
- await fs42.access(candidate);
8064
+ await fs50.access(candidate);
7027
8065
  out.push(candidate);
7028
8066
  } catch {
7029
8067
  }
7030
8068
  }
7031
8069
  } catch {
7032
8070
  }
7033
- const archiveRoot = path54.resolve(activeRoot, "..");
8071
+ const archiveRoot = path62.resolve(activeRoot, "..");
7034
8072
  try {
7035
- const domains = await fs42.readdir(archiveRoot, { withFileTypes: true });
8073
+ const domains = await fs50.readdir(archiveRoot, { withFileTypes: true });
7036
8074
  for (const domain of domains) {
7037
8075
  if (!domain.isDirectory()) continue;
7038
8076
  if (domain.name.startsWith(".")) continue;
7039
- if (path54.join(archiveRoot, domain.name) === path54.resolve(activeRoot)) continue;
7040
- const archiveDir = path54.join(archiveRoot, domain.name, "archive");
8077
+ if (path62.join(archiveRoot, domain.name) === path62.resolve(activeRoot)) continue;
8078
+ const archiveDir = path62.join(archiveRoot, domain.name, "archive");
7041
8079
  let archived;
7042
8080
  try {
7043
- archived = await fs42.readdir(archiveDir, { withFileTypes: true });
8081
+ archived = await fs50.readdir(archiveDir, { withFileTypes: true });
7044
8082
  } catch {
7045
8083
  continue;
7046
8084
  }
7047
8085
  for (const entry of archived) {
7048
8086
  if (!entry.isDirectory()) continue;
7049
- const candidate = path54.join(archiveDir, entry.name, "artifacts.yml");
8087
+ const candidate = path62.join(archiveDir, entry.name, "artifacts.yml");
7050
8088
  try {
7051
- await fs42.access(candidate);
8089
+ await fs50.access(candidate);
7052
8090
  out.push(candidate);
7053
8091
  } catch {
7054
8092
  }
@@ -7060,15 +8098,15 @@ async function walkArtifactsFiles(activeRoot) {
7060
8098
  }
7061
8099
  async function appendMigrationLog(activeRoot, lines) {
7062
8100
  if (lines.length === 0) return;
7063
- const logPath = path54.join(activeRoot, ".migrations.log");
8101
+ const logPath = path62.join(activeRoot, ".migrations.log");
7064
8102
  const stamp = (/* @__PURE__ */ new Date()).toISOString();
7065
8103
  const body = lines.map((l) => `${stamp} v1->v2 ${l}
7066
8104
  `).join("");
7067
8105
  try {
7068
- await fs42.mkdir(activeRoot, { recursive: true });
8106
+ await fs50.mkdir(activeRoot, { recursive: true });
7069
8107
  } catch {
7070
8108
  }
7071
- await fs42.appendFile(logPath, body, "utf8");
8109
+ await fs50.appendFile(logPath, body, "utf8");
7072
8110
  }
7073
8111
  var v1ToV2Artifacts = {
7074
8112
  from: 1,
@@ -7091,12 +8129,12 @@ var v1ToV2Artifacts = {
7091
8129
  };
7092
8130
 
7093
8131
  // src/migrations/v2-to-v3-open-loops.ts
7094
- import { promises as fs45 } from "fs";
7095
- import path56 from "path";
8132
+ import { promises as fs53 } from "fs";
8133
+ import path64 from "path";
7096
8134
 
7097
8135
  // src/migrations/v3-proposal.ts
7098
- import { promises as fs43 } from "fs";
7099
- import { z as z57 } from "zod";
8136
+ import { promises as fs51 } from "fs";
8137
+ import { z as z58 } from "zod";
7100
8138
 
7101
8139
  // src/migrations/data/v3-open-loops-proposal.ts
7102
8140
  var V3_OPEN_LOOPS_PROPOSAL = {
@@ -7892,36 +8930,36 @@ var KEBAB_SESSION_ID = SessionIdSchema.regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/, {
7892
8930
  message: "session_id must be kebab-case ([a-z0-9-], no leading/trailing dash)"
7893
8931
  });
7894
8932
  var ISO_INSTANT = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/;
7895
- var AbandonedSchema = z57.object({ note: z57.string().min(1) });
8933
+ var AbandonedSchema = z58.object({ note: z58.string().min(1) });
7896
8934
  var ProposalNextStepSchema = NextStepSchema.extend({
7897
8935
  abandoned: AbandonedSchema.optional()
7898
8936
  });
7899
- var ProposalInitiativeSchema = z57.object({
7900
- slug: z57.string().min(1),
8937
+ var ProposalInitiativeSchema = z58.object({
8938
+ slug: z58.string().min(1),
7901
8939
  /**
7902
8940
  * Real last-touch of the initiative, hand-supplied. Never `Date.now()` and
7903
8941
  * never file mtime — several initiatives have mtimes months adrift from
7904
8942
  * their true last-touch, and the whole point of back-dating is to preserve
7905
8943
  * the staleness signal.
7906
8944
  */
7907
- ended: z57.string().regex(ISO_INSTANT, {
8945
+ ended: z58.string().regex(ISO_INSTANT, {
7908
8946
  message: "ended must be an ISO 8601 instant with timezone"
7909
8947
  }),
7910
8948
  session_id: KEBAB_SESSION_ID,
7911
- body: z57.string().min(1),
7912
- next_steps: z57.array(ProposalNextStepSchema).default([])
8949
+ body: z58.string().min(1),
8950
+ next_steps: z58.array(ProposalNextStepSchema).default([])
7913
8951
  });
7914
- var ProposalSchema = z57.object({
8952
+ var ProposalSchema = z58.object({
7915
8953
  /**
7916
8954
  * When the abandonment decision was made. Hand-supplied rather than read
7917
8955
  * from the clock: the second session's filename derives from it, and the
7918
8956
  * migration keys idempotence on exact paths, so `Date.now()` would mint a
7919
8957
  * fresh path — and a duplicate abandonment session — on every re-run.
7920
8958
  */
7921
- abandoned_at: z57.string().regex(ISO_INSTANT, {
8959
+ abandoned_at: z58.string().regex(ISO_INSTANT, {
7922
8960
  message: "abandoned_at must be an ISO 8601 instant with timezone"
7923
8961
  }).optional(),
7924
- initiatives: z57.array(ProposalInitiativeSchema)
8962
+ initiatives: z58.array(ProposalInitiativeSchema)
7925
8963
  }).superRefine((value, ctx) => {
7926
8964
  const withAbandoned = value.initiatives.filter(
7927
8965
  (i) => i.next_steps.some((n) => n.abandoned !== void 0)
@@ -7970,7 +9008,7 @@ async function loadProposal() {
7970
9008
  if (override !== void 0 && override.length > 0) {
7971
9009
  let raw;
7972
9010
  try {
7973
- raw = await fs43.readFile(override, "utf8");
9011
+ raw = await fs51.readFile(override, "utf8");
7974
9012
  } catch {
7975
9013
  throw new ValidationError(`${PROPOSAL_PATH_ENV} points at an unreadable file: ${override}`);
7976
9014
  }
@@ -7991,18 +9029,18 @@ async function loadProposal() {
7991
9029
  }
7992
9030
 
7993
9031
  // src/migrations/v3-repairs.ts
7994
- import { promises as fs44 } from "fs";
7995
- import path55 from "path";
9032
+ import { promises as fs52 } from "fs";
9033
+ import path63 from "path";
7996
9034
  var KNOWN_REPAIRS = [
7997
9035
  {
7998
- file: path55.join("audiobook", "sessions", "2026-07-23-0549-2026-07-26-book1-m4b-packaging.md"),
9036
+ file: path63.join("audiobook", "sessions", "2026-07-23-0549-2026-07-26-book1-m4b-packaging.md"),
7999
9037
  kind: "retrack",
8000
9038
  why: "track is a branch name ('feat/tts-quality'), not one of canonical|sidecar|adhoc"
8001
9039
  },
8002
9040
  {
8003
- file: path55.join("voltras-workspace", "sessions", "ARCHIVED-handoff-through-2026-07-15.md"),
9041
+ file: path63.join("voltras-workspace", "sessions", "ARCHIVED-handoff-through-2026-07-15.md"),
8004
9042
  kind: "relocate",
8005
- target: path55.join("voltras-workspace", "sources", "ARCHIVED-handoff-through-2026-07-15.md"),
9043
+ target: path63.join("voltras-workspace", "sources", "ARCHIVED-handoff-through-2026-07-15.md"),
8006
9044
  why: "not a session file \u2014 a hand-archived handoff parked in sessions/"
8007
9045
  }
8008
9046
  ];
@@ -8016,11 +9054,11 @@ var BRIEF_REPAIRS = {
8016
9054
  };
8017
9055
  }
8018
9056
  };
8019
- function repairBriefFrontmatter(slug, frontmatter) {
8020
- const repair = BRIEF_REPAIRS[slug]?.(frontmatter);
8021
- if (repair == null) return { frontmatter, applied: [] };
9057
+ function repairBriefFrontmatter(slug, frontmatter2) {
9058
+ const repair = BRIEF_REPAIRS[slug]?.(frontmatter2);
9059
+ if (repair == null) return { frontmatter: frontmatter2, applied: [] };
8022
9060
  return {
8023
- frontmatter: { ...frontmatter, ...repair.patch },
9061
+ frontmatter: { ...frontmatter2, ...repair.patch },
8024
9062
  applied: [repair.detail]
8025
9063
  };
8026
9064
  }
@@ -8050,9 +9088,9 @@ async function planRetrack(fullPath, repair) {
8050
9088
  repaired: { frontmatter: parsed.data, body: raw.body }
8051
9089
  };
8052
9090
  }
8053
- async function exists2(p) {
9091
+ async function exists3(p) {
8054
9092
  try {
8055
- await fs44.access(p);
9093
+ await fs52.access(p);
8056
9094
  return true;
8057
9095
  } catch {
8058
9096
  return false;
@@ -8061,8 +9099,8 @@ async function exists2(p) {
8061
9099
  async function planRepairs(activeRoot) {
8062
9100
  const plans = [];
8063
9101
  for (const repair of KNOWN_REPAIRS) {
8064
- const fullPath = path55.join(activeRoot, repair.file);
8065
- if (!await exists2(fullPath)) {
9102
+ const fullPath = path63.join(activeRoot, repair.file);
9103
+ if (!await exists3(fullPath)) {
8066
9104
  plans.push({ action: "skip", file: repair.file, detail: "already absent" });
8067
9105
  continue;
8068
9106
  }
@@ -8071,7 +9109,7 @@ async function planRepairs(activeRoot) {
8071
9109
  continue;
8072
9110
  }
8073
9111
  const target = repair.target;
8074
- if (await exists2(path55.join(activeRoot, target))) {
9112
+ if (await exists3(path63.join(activeRoot, target))) {
8075
9113
  plans.push({
8076
9114
  action: "skip",
8077
9115
  file: repair.file,
@@ -8085,7 +9123,7 @@ async function planRepairs(activeRoot) {
8085
9123
  return plans;
8086
9124
  }
8087
9125
  async function applyRepair(activeRoot, plan) {
8088
- const fullPath = path55.join(activeRoot, plan.file);
9126
+ const fullPath = path63.join(activeRoot, plan.file);
8089
9127
  if (plan.action === "retrack" && plan.repaired !== void 0) {
8090
9128
  await writeFrontmatter(
8091
9129
  fullPath,
@@ -8096,34 +9134,34 @@ async function applyRepair(activeRoot, plan) {
8096
9134
  return;
8097
9135
  }
8098
9136
  if (plan.action === "relocate") {
8099
- const target = path55.join(activeRoot, plan.target);
8100
- await fs44.mkdir(path55.dirname(target), { recursive: true });
8101
- await fs44.rename(fullPath, target);
9137
+ const target = path63.join(activeRoot, plan.target);
9138
+ await fs52.mkdir(path63.dirname(target), { recursive: true });
9139
+ await fs52.rename(fullPath, target);
8102
9140
  }
8103
9141
  }
8104
9142
 
8105
9143
  // src/migrations/v2-to-v3-open-loops.ts
8106
9144
  var HANDOFF_FILE = "handoff.md";
8107
- var HANDOFF_ARCHIVE = path56.join("sources", "handoff-archive.md");
9145
+ var HANDOFF_ARCHIVE = path64.join("sources", "handoff-archive.md");
8108
9146
  async function pathExists(p) {
8109
9147
  try {
8110
- await fs45.access(p);
9148
+ await fs53.access(p);
8111
9149
  return true;
8112
9150
  } catch {
8113
9151
  return false;
8114
9152
  }
8115
9153
  }
8116
- async function listInitiativeSlugs2(activeRoot) {
9154
+ async function listInitiativeSlugs3(activeRoot) {
8117
9155
  let entries;
8118
9156
  try {
8119
- entries = await fs45.readdir(activeRoot, { withFileTypes: true });
9157
+ entries = await fs53.readdir(activeRoot, { withFileTypes: true });
8120
9158
  } catch {
8121
9159
  return [];
8122
9160
  }
8123
9161
  const slugs = [];
8124
9162
  for (const entry of entries) {
8125
9163
  if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
8126
- if (await pathExists(path56.join(activeRoot, entry.name, "brief.md"))) {
9164
+ if (await pathExists(path64.join(activeRoot, entry.name, "brief.md"))) {
8127
9165
  slugs.push(entry.name);
8128
9166
  }
8129
9167
  }
@@ -8131,12 +9169,12 @@ async function listInitiativeSlugs2(activeRoot) {
8131
9169
  }
8132
9170
  async function maxOnDiskTaskNumber2(initiativeDir, prefix) {
8133
9171
  const re = new RegExp(`^${prefix}-(\\d+)\\.yml$`);
8134
- const tasksDir = path56.join(initiativeDir, "tasks");
9172
+ const tasksDir = path64.join(initiativeDir, "tasks");
8135
9173
  let max = 0;
8136
- for (const dir of [tasksDir, path56.join(tasksDir, "archive")]) {
9174
+ for (const dir of [tasksDir, path64.join(tasksDir, "archive")]) {
8137
9175
  let names;
8138
9176
  try {
8139
- names = await fs45.readdir(dir);
9177
+ names = await fs53.readdir(dir);
8140
9178
  } catch {
8141
9179
  continue;
8142
9180
  }
@@ -8147,15 +9185,15 @@ async function maxOnDiskTaskNumber2(initiativeDir, prefix) {
8147
9185
  }
8148
9186
  return max;
8149
9187
  }
8150
- async function nextTaskSeq(initiativeDir, frontmatter) {
8151
- if (frontmatter.task_seq !== void 0) return null;
8152
- const prefix = frontmatter.task_prefix;
9188
+ async function nextTaskSeq(initiativeDir, frontmatter2) {
9189
+ if (frontmatter2.task_seq !== void 0) return null;
9190
+ const prefix = frontmatter2.task_prefix;
8153
9191
  if (typeof prefix !== "string" || prefix.length === 0) return null;
8154
9192
  const max = await maxOnDiskTaskNumber2(initiativeDir, prefix);
8155
9193
  return max > 0 ? max : null;
8156
9194
  }
8157
9195
  async function planBrief(initiativeDir, slug) {
8158
- const raw = await readRawFrontmatter(path56.join(initiativeDir, "brief.md"));
9196
+ const raw = await readRawFrontmatter(path64.join(initiativeDir, "brief.md"));
8159
9197
  const repaired = repairBriefFrontmatter(slug, raw.frontmatter);
8160
9198
  const taskSeq = await nextTaskSeq(initiativeDir, repaired.frontmatter);
8161
9199
  if (taskSeq === null && repaired.applied.length === 0) return { write: null };
@@ -8172,12 +9210,12 @@ async function planBrief(initiativeDir, slug) {
8172
9210
  };
8173
9211
  }
8174
9212
  async function planHandoff(initiativeDir) {
8175
- if (!await pathExists(path56.join(initiativeDir, HANDOFF_FILE))) return "absent";
8176
- if (await pathExists(path56.join(initiativeDir, HANDOFF_ARCHIVE))) return "archive-exists";
9213
+ if (!await pathExists(path64.join(initiativeDir, HANDOFF_FILE))) return "absent";
9214
+ if (await pathExists(path64.join(initiativeDir, HANDOFF_ARCHIVE))) return "archive-exists";
8177
9215
  return "archive-and-remove";
8178
9216
  }
8179
9217
  function buildOpenSession(entry) {
8180
- const frontmatter = SessionFrontmatterSchema.parse({
9218
+ const frontmatter2 = SessionFrontmatterSchema.parse({
8181
9219
  session_id: entry.session_id,
8182
9220
  // A synthetic session is an instant, not an interval: it stands for a
8183
9221
  // state read off the handoff, so `started === ended`.
@@ -8189,11 +9227,11 @@ function buildOpenSession(entry) {
8189
9227
  resolves: []
8190
9228
  });
8191
9229
  const stem = buildSessionStem(entry.ended, entry.session_id);
8192
- return { kind: "open", stem, frontmatter, body: buildOpenBody(entry, stem) };
9230
+ return { kind: "open", stem, frontmatter: frontmatter2, body: buildOpenBody(entry, stem) };
8193
9231
  }
8194
9232
  function buildAbandonSession(entry, openStem, abandonedAt) {
8195
9233
  const sessionId = `${entry.session_id}-abandonment`;
8196
- const frontmatter = SessionFrontmatterSchema.parse({
9234
+ const frontmatter2 = SessionFrontmatterSchema.parse({
8197
9235
  session_id: sessionId,
8198
9236
  started: abandonedAt,
8199
9237
  ended: abandonedAt,
@@ -8209,7 +9247,7 @@ function buildAbandonSession(entry, openStem, abandonedAt) {
8209
9247
  return {
8210
9248
  kind: "abandon",
8211
9249
  stem,
8212
- frontmatter,
9250
+ frontmatter: frontmatter2,
8213
9251
  body: buildAbandonBody(entry, openStem, stem)
8214
9252
  };
8215
9253
  }
@@ -8263,7 +9301,7 @@ function draftSessions(entry, abandonedAt) {
8263
9301
  return [open, buildAbandonSession(entry, open.stem, abandonedAt)];
8264
9302
  }
8265
9303
  async function planInitiative(activeRoot, slug, entry, abandonedAt) {
8266
- const initiativeDir = path56.join(activeRoot, slug);
9304
+ const initiativeDir = path64.join(activeRoot, slug);
8267
9305
  const brief = await planBrief(initiativeDir, slug);
8268
9306
  const base = {
8269
9307
  slug,
@@ -8290,7 +9328,7 @@ function assertProposalSlugsExist(proposal, known) {
8290
9328
  }
8291
9329
  async function planV2ToV3(activeRoot) {
8292
9330
  const { proposal, origin } = await loadProposal();
8293
- const slugs = await listInitiativeSlugs2(activeRoot);
9331
+ const slugs = await listInitiativeSlugs3(activeRoot);
8294
9332
  assertProposalSlugsExist(proposal, slugs);
8295
9333
  const byslug = new Map(proposal.initiatives.map((i) => [i.slug, i]));
8296
9334
  const initiatives = [];
@@ -8302,11 +9340,11 @@ async function planV2ToV3(activeRoot) {
8302
9340
  return { proposalOrigin: origin, initiatives, repairs: await planRepairs(activeRoot) };
8303
9341
  }
8304
9342
  async function archiveHandoff(initiativeDir) {
8305
- const source = path56.join(initiativeDir, HANDOFF_FILE);
8306
- const target = path56.join(initiativeDir, HANDOFF_ARCHIVE);
8307
- await fs45.mkdir(path56.dirname(target), { recursive: true });
8308
- await fs45.copyFile(source, target);
8309
- await fs45.rm(source);
9343
+ const source = path64.join(initiativeDir, HANDOFF_FILE);
9344
+ const target = path64.join(initiativeDir, HANDOFF_ARCHIVE);
9345
+ await fs53.mkdir(path64.dirname(target), { recursive: true });
9346
+ await fs53.copyFile(source, target);
9347
+ await fs53.rm(source);
8310
9348
  }
8311
9349
  async function writePlannedSession(activeRoot, slug, session) {
8312
9350
  const fm = session.frontmatter;
@@ -8323,15 +9361,15 @@ async function writePlannedSession(activeRoot, slug, session) {
8323
9361
  });
8324
9362
  }
8325
9363
  async function applyInitiative(activeRoot, plan) {
8326
- const initiativeDir = path56.join(activeRoot, plan.slug);
8327
- await withFileLock(path56.join(initiativeDir, ".lock"), async () => {
9364
+ const initiativeDir = path64.join(activeRoot, plan.slug);
9365
+ await withFileLock(path64.join(initiativeDir, ".lock"), async () => {
8328
9366
  for (const session of plan.sessions) {
8329
9367
  if (session.exists) continue;
8330
9368
  await writePlannedSession(activeRoot, plan.slug, session);
8331
9369
  }
8332
9370
  if (plan.brief !== null) {
8333
9371
  await writeFrontmatter(
8334
- path56.join(initiativeDir, "brief.md"),
9372
+ path64.join(initiativeDir, "brief.md"),
8335
9373
  plan.brief.frontmatter,
8336
9374
  plan.brief.body,
8337
9375
  BriefFrontmatterSchema
@@ -8361,13 +9399,13 @@ var v2ToV3OpenLoops = {
8361
9399
  };
8362
9400
 
8363
9401
  // src/migrations/v3-to-v4-worktrees.ts
8364
- import { promises as fs46 } from "fs";
8365
- import path57 from "path";
8366
- import matter3 from "gray-matter";
8367
- import YAML2 from "yaml";
9402
+ import { promises as fs54 } from "fs";
9403
+ import path65 from "path";
9404
+ import matter5 from "gray-matter";
9405
+ import YAML5 from "yaml";
8368
9406
  function normalize(value) {
8369
- const expanded = value.startsWith("~") ? path57.join(process.env.HOME ?? "", value.slice(1)) : value;
8370
- return path57.resolve(expanded);
9407
+ const expanded = value.startsWith("~") ? path65.join(process.env.HOME ?? "", value.slice(1)) : value;
9408
+ return path65.resolve(expanded);
8371
9409
  }
8372
9410
  function toEntries(raw) {
8373
9411
  if (!raw || typeof raw !== "object" || Array.isArray(raw)) return [];
@@ -8403,7 +9441,7 @@ function mergeWorktrees(existing, incoming) {
8403
9441
  }
8404
9442
  async function readArtifacts2(file) {
8405
9443
  try {
8406
- const raw = YAML2.parse(await fs46.readFile(file, "utf8"));
9444
+ const raw = YAML5.parse(await fs54.readFile(file, "utf8"));
8407
9445
  return {
8408
9446
  branches: Array.isArray(raw?.branches) ? raw.branches : [],
8409
9447
  stashes: Array.isArray(raw?.stashes) ? raw.stashes : [],
@@ -8414,20 +9452,20 @@ async function readArtifacts2(file) {
8414
9452
  }
8415
9453
  }
8416
9454
  async function migrateOne2(initiativeDir) {
8417
- const briefPath = path57.join(initiativeDir, "brief.md");
9455
+ const briefPath = path65.join(initiativeDir, "brief.md");
8418
9456
  let raw;
8419
9457
  try {
8420
- raw = await fs46.readFile(briefPath, "utf8");
9458
+ raw = await fs54.readFile(briefPath, "utf8");
8421
9459
  } catch {
8422
9460
  return 0;
8423
9461
  }
8424
- const parsed = matter3(raw);
9462
+ const parsed = matter5(raw);
8425
9463
  const data = parsed.data;
8426
9464
  if (!("worktrees" in data)) return 0;
8427
9465
  const incoming = toEntries(data.worktrees);
8428
9466
  delete data.worktrees;
8429
9467
  if (incoming.length > 0) {
8430
- const artifactsPath2 = path57.join(initiativeDir, "artifacts.yml");
9468
+ const artifactsPath2 = path65.join(initiativeDir, "artifacts.yml");
8431
9469
  const current = await readArtifacts2(artifactsPath2);
8432
9470
  await writeYaml(
8433
9471
  artifactsPath2,
@@ -8439,22 +9477,22 @@ async function migrateOne2(initiativeDir) {
8439
9477
  ArtifactsSchema
8440
9478
  );
8441
9479
  }
8442
- await atomicWrite(briefPath, matter3.stringify(parsed.content, data));
9480
+ await atomicWrite(briefPath, matter5.stringify(parsed.content, data));
8443
9481
  return incoming.length;
8444
9482
  }
8445
9483
  async function initiativeDirs(activeRoot) {
8446
9484
  const out = [];
8447
9485
  let entries;
8448
9486
  try {
8449
- entries = await fs46.readdir(activeRoot, { withFileTypes: true });
9487
+ entries = await fs54.readdir(activeRoot, { withFileTypes: true });
8450
9488
  } catch {
8451
9489
  return out;
8452
9490
  }
8453
9491
  for (const entry of entries) {
8454
9492
  if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
8455
- const dir = path57.join(activeRoot, entry.name);
9493
+ const dir = path65.join(activeRoot, entry.name);
8456
9494
  try {
8457
- await fs46.access(path57.join(dir, "brief.md"));
9495
+ await fs54.access(path65.join(dir, "brief.md"));
8458
9496
  out.push(dir);
8459
9497
  } catch {
8460
9498
  }
@@ -8469,12 +9507,12 @@ var v3ToV4Worktrees = {
8469
9507
  const moved = [];
8470
9508
  for (const dir of await initiativeDirs(activeRoot)) {
8471
9509
  const count = await migrateOne2(dir);
8472
- if (count > 0) moved.push(`${path57.basename(dir)} ${count} worktree(s)`);
9510
+ if (count > 0) moved.push(`${path65.basename(dir)} ${count} worktree(s)`);
8473
9511
  }
8474
9512
  if (moved.length === 0) return;
8475
9513
  const stamp = (/* @__PURE__ */ new Date()).toISOString();
8476
- await fs46.appendFile(
8477
- path57.join(activeRoot, ".migrations.log"),
9514
+ await fs54.appendFile(
9515
+ path65.join(activeRoot, ".migrations.log"),
8478
9516
  moved.map((line) => `${stamp} v3->v4 ${line}
8479
9517
  `).join(""),
8480
9518
  "utf8"
@@ -8489,7 +9527,7 @@ var CURRENT_VERSION = targetVersion(MIGRATIONS2);
8489
9527
  function targetVersion(migrations) {
8490
9528
  return migrations.reduce((highest, m) => Math.max(highest, m.to), BASE_VERSION);
8491
9529
  }
8492
- async function runMigrations(activeRoot, fromVersion, migrations = MIGRATIONS2) {
9530
+ async function runMigrations2(activeRoot, fromVersion, migrations = MIGRATIONS2) {
8493
9531
  if (fromVersion === CURRENT_VERSION) {
8494
9532
  return { ran: [] };
8495
9533
  }
@@ -8527,10 +9565,10 @@ var SCHEMA_VERSION_FILENAME = ".schema-version";
8527
9565
  var schemaVersionPath = (activeRoot) => join(activeRoot, SCHEMA_VERSION_FILENAME);
8528
9566
  var isNodeErrnoException = (err) => typeof err === "object" && err !== null && "code" in err;
8529
9567
  async function readSchemaVersion(activeRoot) {
8530
- const path65 = schemaVersionPath(activeRoot);
9568
+ const path67 = schemaVersionPath(activeRoot);
8531
9569
  let raw;
8532
9570
  try {
8533
- raw = await readFile(path65, "utf8");
9571
+ raw = await readFile(path67, "utf8");
8534
9572
  } catch (err) {
8535
9573
  if (isNodeErrnoException(err) && err.code === "ENOENT") {
8536
9574
  return 0;
@@ -8540,13 +9578,13 @@ async function readSchemaVersion(activeRoot) {
8540
9578
  const trimmed = raw.trim();
8541
9579
  if (trimmed === "" || !/^\d+$/.test(trimmed)) {
8542
9580
  throw new Error(
8543
- `Invalid schema version in ${path65}: expected a positive integer, got ${JSON.stringify(raw)}`
9581
+ `Invalid schema version in ${path67}: expected a positive integer, got ${JSON.stringify(raw)}`
8544
9582
  );
8545
9583
  }
8546
9584
  const parsed = Number(trimmed);
8547
9585
  if (!Number.isInteger(parsed) || parsed <= 0) {
8548
9586
  throw new Error(
8549
- `Invalid schema version in ${path65}: expected a positive integer, got ${JSON.stringify(raw)}`
9587
+ `Invalid schema version in ${path67}: expected a positive integer, got ${JSON.stringify(raw)}`
8550
9588
  );
8551
9589
  }
8552
9590
  return parsed;
@@ -8559,10 +9597,10 @@ async function writeSchemaVersion(activeRoot, version) {
8559
9597
  `, "utf8");
8560
9598
  }
8561
9599
  async function readRawSchemaVersion(activeRoot) {
8562
- const path65 = schemaVersionPath(activeRoot);
9600
+ const path67 = schemaVersionPath(activeRoot);
8563
9601
  let raw;
8564
9602
  try {
8565
- raw = await readFile(path65, "utf8");
9603
+ raw = await readFile(path67, "utf8");
8566
9604
  } catch (err) {
8567
9605
  if (isNodeErrnoException(err) && err.code === "ENOENT") {
8568
9606
  return { present: false };
@@ -8572,13 +9610,13 @@ async function readRawSchemaVersion(activeRoot) {
8572
9610
  const trimmed = raw.trim();
8573
9611
  if (trimmed === "" || !/^\d+$/.test(trimmed)) {
8574
9612
  throw new Error(
8575
- `Invalid schema version in ${path65}: expected a non-negative integer, got ${JSON.stringify(raw)}`
9613
+ `Invalid schema version in ${path67}: expected a non-negative integer, got ${JSON.stringify(raw)}`
8576
9614
  );
8577
9615
  }
8578
9616
  const parsed = Number(trimmed);
8579
9617
  if (!Number.isInteger(parsed) || parsed < 0) {
8580
9618
  throw new Error(
8581
- `Invalid schema version in ${path65}: expected a non-negative integer, got ${JSON.stringify(raw)}`
9619
+ `Invalid schema version in ${path67}: expected a non-negative integer, got ${JSON.stringify(raw)}`
8582
9620
  );
8583
9621
  }
8584
9622
  return { present: true, version: parsed };
@@ -8598,7 +9636,7 @@ async function ensureSchemaVersion(activeRoot) {
8598
9636
  if (before === CURRENT_VERSION) {
8599
9637
  return { before, after: before, migrated: false, ran: [] };
8600
9638
  }
8601
- const { ran } = await runMigrations(activeRoot, before);
9639
+ const { ran } = await runMigrations2(activeRoot, before);
8602
9640
  await writeSchemaVersion(activeRoot, CURRENT_VERSION);
8603
9641
  return {
8604
9642
  before,
@@ -8619,7 +9657,7 @@ function isLinux(platform = process.platform) {
8619
9657
  return platform === "linux";
8620
9658
  }
8621
9659
  function resolveLocalDeps(deps) {
8622
- const fs53 = deps.fs ?? fsp;
9660
+ const fs57 = deps.fs ?? fsp;
8623
9661
  const spawn6 = deps.spawn ?? nodeSpawn;
8624
9662
  const homeDir = deps.paths?.homeDir ?? os3.homedir();
8625
9663
  const paths = deps.paths ?? {
@@ -8629,7 +9667,7 @@ function resolveLocalDeps(deps) {
8629
9667
  homeDir
8630
9668
  };
8631
9669
  const cliEntry = deps.cliEntry ?? process.argv[1] ?? "active-work";
8632
- return { fs: fs53, spawn: spawn6, paths, cliEntry, platform: process.platform };
9670
+ return { fs: fs57, spawn: spawn6, paths, cliEntry, platform: process.platform };
8633
9671
  }
8634
9672
  function getUnitDir(homeDir) {
8635
9673
  return nodePath.join(homeDir, ".config", "systemd", "user");
@@ -8700,7 +9738,7 @@ async function isUnitActive(deps = {}) {
8700
9738
  return result.code === 0;
8701
9739
  }
8702
9740
  async function stepInstallSupervision(deps = {}, opts = {}) {
8703
- const { fs: fs53, spawn: spawn6, paths, cliEntry, platform } = resolveLocalDeps(deps);
9741
+ const { fs: fs57, spawn: spawn6, paths, cliEntry, platform } = resolveLocalDeps(deps);
8704
9742
  if (!isLinux(platform)) {
8705
9743
  return {
8706
9744
  ok: true,
@@ -8713,16 +9751,16 @@ async function stepInstallSupervision(deps = {}, opts = {}) {
8713
9751
  const unitPath = getUnitPath(paths.homeDir);
8714
9752
  const desired = renderUnit({ cliEntry, port: opts.port });
8715
9753
  try {
8716
- await fs53.mkdir(unitDir, { recursive: true });
9754
+ await fs57.mkdir(unitDir, { recursive: true });
8717
9755
  let existing = null;
8718
9756
  try {
8719
- existing = await fs53.readFile(unitPath, "utf8");
9757
+ existing = await fs57.readFile(unitPath, "utf8");
8720
9758
  } catch (err) {
8721
9759
  if (err.code !== "ENOENT") throw err;
8722
9760
  }
8723
9761
  const unchanged = existing === desired;
8724
9762
  if (!unchanged) {
8725
- await fs53.writeFile(unitPath, desired, "utf8");
9763
+ await fs57.writeFile(unitPath, desired, "utf8");
8726
9764
  }
8727
9765
  const reload = await runOnce(spawn6, "systemctl", ["--user", "daemon-reload"]);
8728
9766
  if (reload.spawnError) {
@@ -8784,7 +9822,7 @@ async function stepInstallSupervision(deps = {}, opts = {}) {
8784
9822
  }
8785
9823
  }
8786
9824
  async function uninstallSupervision(deps = {}) {
8787
- const { fs: fs53, spawn: spawn6, paths, platform } = resolveLocalDeps(deps);
9825
+ const { fs: fs57, spawn: spawn6, paths, platform } = resolveLocalDeps(deps);
8788
9826
  if (!isLinux(platform)) {
8789
9827
  return {
8790
9828
  ok: true,
@@ -8797,7 +9835,7 @@ async function uninstallSupervision(deps = {}) {
8797
9835
  try {
8798
9836
  let present = true;
8799
9837
  try {
8800
- await fs53.stat(unitPath);
9838
+ await fs57.stat(unitPath);
8801
9839
  } catch (err) {
8802
9840
  if (err.code === "ENOENT") present = false;
8803
9841
  else throw err;
@@ -8812,7 +9850,7 @@ async function uninstallSupervision(deps = {}) {
8812
9850
  }
8813
9851
  const disable = await runOnce(spawn6, "systemctl", ["--user", "disable", "--now", UNIT_NAME]);
8814
9852
  if (disable.spawnError) {
8815
- await fs53.rm(unitPath, { force: true });
9853
+ await fs57.rm(unitPath, { force: true });
8816
9854
  return {
8817
9855
  ok: true,
8818
9856
  name: STEP_SUPERVISION,
@@ -8820,7 +9858,7 @@ async function uninstallSupervision(deps = {}) {
8820
9858
  message: `Removed ${unitPath} (systemctl unavailable: ${disable.spawnError.message})`
8821
9859
  };
8822
9860
  }
8823
- await fs53.rm(unitPath, { force: true });
9861
+ await fs57.rm(unitPath, { force: true });
8824
9862
  const reload = await runOnce(spawn6, "systemctl", ["--user", "daemon-reload"]);
8825
9863
  if (reload.code !== 0 && !reload.spawnError) {
8826
9864
  return {
@@ -8856,7 +9894,7 @@ function isDarwin(platform = process.platform) {
8856
9894
  return platform === "darwin";
8857
9895
  }
8858
9896
  function resolveLocalDeps2(deps) {
8859
- const fs53 = deps.fs ?? fsp2;
9897
+ const fs57 = deps.fs ?? fsp2;
8860
9898
  const spawn6 = deps.spawn ?? nodeSpawn2;
8861
9899
  const homeDir = deps.paths?.homeDir ?? os4.homedir();
8862
9900
  const paths = deps.paths ?? {
@@ -8867,7 +9905,7 @@ function resolveLocalDeps2(deps) {
8867
9905
  };
8868
9906
  const cliEntry = deps.cliEntry ?? process.argv[1] ?? "active-work";
8869
9907
  const uid = process.getuid?.() ?? 0;
8870
- return { fs: fs53, spawn: spawn6, paths, cliEntry, uid, platform: process.platform };
9908
+ return { fs: fs57, spawn: spawn6, paths, cliEntry, uid, platform: process.platform };
8871
9909
  }
8872
9910
  function getAgentDir(homeDir) {
8873
9911
  return nodePath2.join(homeDir, "Library", "LaunchAgents");
@@ -8955,7 +9993,7 @@ async function isAgentLoaded(deps = {}) {
8955
9993
  return result.code === 0;
8956
9994
  }
8957
9995
  async function installLaunchAgent(deps = {}, opts = {}) {
8958
- const { fs: fs53, spawn: spawn6, paths, cliEntry, uid, platform } = resolveLocalDeps2(deps);
9996
+ const { fs: fs57, spawn: spawn6, paths, cliEntry, uid, platform } = resolveLocalDeps2(deps);
8959
9997
  if (!isDarwin(platform)) {
8960
9998
  return {
8961
9999
  ok: true,
@@ -8967,11 +10005,11 @@ async function installLaunchAgent(deps = {}, opts = {}) {
8967
10005
  const plistPath = getPlistPath(paths.homeDir);
8968
10006
  const desired = renderPlist({ cliEntry, homeDir: paths.homeDir, port: opts.port });
8969
10007
  try {
8970
- await fs53.mkdir(getAgentDir(paths.homeDir), { recursive: true });
8971
- await fs53.mkdir(getLogDir(paths.homeDir), { recursive: true });
10008
+ await fs57.mkdir(getAgentDir(paths.homeDir), { recursive: true });
10009
+ await fs57.mkdir(getLogDir(paths.homeDir), { recursive: true });
8972
10010
  let existing = null;
8973
10011
  try {
8974
- existing = await fs53.readFile(plistPath, "utf8");
10012
+ existing = await fs57.readFile(plistPath, "utf8");
8975
10013
  } catch (err) {
8976
10014
  if (err.code !== "ENOENT") throw err;
8977
10015
  }
@@ -8985,7 +10023,7 @@ async function installLaunchAgent(deps = {}, opts = {}) {
8985
10023
  };
8986
10024
  }
8987
10025
  if (!unchanged) {
8988
- await fs53.writeFile(plistPath, desired, "utf8");
10026
+ await fs57.writeFile(plistPath, desired, "utf8");
8989
10027
  }
8990
10028
  const bootout = await runOnce2(spawn6, "launchctl", ["bootout", serviceTarget(uid)]);
8991
10029
  if (bootout.spawnError) {
@@ -9026,7 +10064,7 @@ async function installLaunchAgent(deps = {}, opts = {}) {
9026
10064
  }
9027
10065
  }
9028
10066
  async function uninstallLaunchAgent(deps = {}) {
9029
- const { fs: fs53, spawn: spawn6, paths, uid, platform } = resolveLocalDeps2(deps);
10067
+ const { fs: fs57, spawn: spawn6, paths, uid, platform } = resolveLocalDeps2(deps);
9030
10068
  if (!isDarwin(platform)) {
9031
10069
  return {
9032
10070
  ok: true,
@@ -9039,7 +10077,7 @@ async function uninstallLaunchAgent(deps = {}) {
9039
10077
  try {
9040
10078
  let present = true;
9041
10079
  try {
9042
- await fs53.stat(plistPath);
10080
+ await fs57.stat(plistPath);
9043
10081
  } catch (err) {
9044
10082
  if (err.code === "ENOENT") present = false;
9045
10083
  else throw err;
@@ -9053,7 +10091,7 @@ async function uninstallLaunchAgent(deps = {}) {
9053
10091
  };
9054
10092
  }
9055
10093
  const bootout = await runOnce2(spawn6, "launchctl", ["bootout", serviceTarget(uid)]);
9056
- await fs53.rm(plistPath, { force: true });
10094
+ await fs57.rm(plistPath, { force: true });
9057
10095
  if (bootout.spawnError) {
9058
10096
  return {
9059
10097
  ok: true,
@@ -9114,7 +10152,7 @@ function findRepoRoot() {
9114
10152
  return nodePath3.resolve(nodePath3.dirname(fileURLToPath2(import.meta.url)), "..", "..");
9115
10153
  }
9116
10154
  function resolveDeps(deps) {
9117
- const fs53 = deps.fs ?? fsp3;
10155
+ const fs57 = deps.fs ?? fsp3;
9118
10156
  const spawn6 = deps.spawn ?? nodeSpawn3;
9119
10157
  const prompts = deps.prompts ?? clackPrompts;
9120
10158
  const homeDir = deps.paths?.homeDir ?? os5.homedir();
@@ -9127,7 +10165,7 @@ function resolveDeps(deps) {
9127
10165
  const repoRoot = deps.repoRoot ?? findRepoRoot();
9128
10166
  const cliEntry = deps.cliEntry ?? process.argv[1] ?? "active-work";
9129
10167
  return {
9130
- fs: fs53,
10168
+ fs: fs57,
9131
10169
  spawn: spawn6,
9132
10170
  prompts,
9133
10171
  paths,
@@ -9170,24 +10208,24 @@ async function stepCheckNode(deps = {}) {
9170
10208
  message: `Node v${process.versions.node} OK`
9171
10209
  };
9172
10210
  }
9173
- async function ensureDir(fs53, dir) {
10211
+ async function ensureDir(fs57, dir) {
9174
10212
  try {
9175
- const stat = await fs53.stat(dir);
10213
+ const stat = await fs57.stat(dir);
9176
10214
  if (stat.isDirectory()) return { created: false };
9177
10215
  throw new Error(`${dir} exists but is not a directory`);
9178
10216
  } catch (err) {
9179
10217
  const code = err.code;
9180
10218
  if (code !== "ENOENT") throw err;
9181
10219
  }
9182
- await fs53.mkdir(dir, { recursive: true });
10220
+ await fs57.mkdir(dir, { recursive: true });
9183
10221
  return { created: true };
9184
10222
  }
9185
10223
  async function stepCreateActiveRoot(deps = {}) {
9186
- const { fs: fs53, paths } = resolveDeps(deps);
10224
+ const { fs: fs57, paths } = resolveDeps(deps);
9187
10225
  try {
9188
10226
  const created = [];
9189
10227
  for (const dir of [paths.activeRoot, paths.stateRoot, paths.configRoot]) {
9190
- const { created: didCreate } = await ensureDir(fs53, dir);
10228
+ const { created: didCreate } = await ensureDir(fs57, dir);
9191
10229
  if (didCreate) created.push(dir);
9192
10230
  }
9193
10231
  const message = created.length === 0 ? `Active/state/config dirs already present` : `Created ${created.length} dir(s)`;
@@ -9236,18 +10274,18 @@ var CONFIG_STUB = {
9236
10274
  channels: ["plugin:agent-chat@agent-chat-local"]
9237
10275
  };
9238
10276
  async function stepWriteConfigStub(deps = {}) {
9239
- const { fs: fs53, paths, update } = resolveDeps(deps);
10277
+ const { fs: fs57, paths, update } = resolveDeps(deps);
9240
10278
  const configPath = nodePath3.join(paths.configRoot, "config.json");
9241
10279
  try {
9242
- await ensureDir(fs53, paths.configRoot);
9243
- let exists3 = false;
10280
+ await ensureDir(fs57, paths.configRoot);
10281
+ let exists4 = false;
9244
10282
  try {
9245
- await fs53.stat(configPath);
9246
- exists3 = true;
10283
+ await fs57.stat(configPath);
10284
+ exists4 = true;
9247
10285
  } catch (err) {
9248
10286
  if (err.code !== "ENOENT") throw err;
9249
10287
  }
9250
- if (exists3 && !update) {
10288
+ if (exists4 && !update) {
9251
10289
  return {
9252
10290
  ok: true,
9253
10291
  name: STEP_CONFIG,
@@ -9255,7 +10293,7 @@ async function stepWriteConfigStub(deps = {}) {
9255
10293
  message: `Config exists at ${configPath} (left untouched)`
9256
10294
  };
9257
10295
  }
9258
- await fs53.writeFile(configPath, JSON.stringify(CONFIG_STUB, null, 2) + "\n", "utf8");
10296
+ await fs57.writeFile(configPath, JSON.stringify(CONFIG_STUB, null, 2) + "\n", "utf8");
9259
10297
  return {
9260
10298
  ok: true,
9261
10299
  name: STEP_CONFIG,
@@ -9270,40 +10308,40 @@ async function stepWriteConfigStub(deps = {}) {
9270
10308
  };
9271
10309
  }
9272
10310
  }
9273
- async function pathExists2(fs53, p) {
10311
+ async function pathExists2(fs57, p) {
9274
10312
  try {
9275
- await fs53.stat(p);
10313
+ await fs57.stat(p);
9276
10314
  return true;
9277
10315
  } catch (err) {
9278
10316
  if (err.code === "ENOENT") return false;
9279
10317
  throw err;
9280
10318
  }
9281
10319
  }
9282
- async function copyTree(fs53, src, dest) {
9283
- const cp = fs53.cp;
10320
+ async function copyTree(fs57, src, dest) {
10321
+ const cp = fs57.cp;
9284
10322
  if (cp) {
9285
10323
  await cp(src, dest, { recursive: true });
9286
10324
  return;
9287
10325
  }
9288
- await fs53.mkdir(dest, { recursive: true });
9289
- const entries = await fs53.readdir(src, { withFileTypes: true });
10326
+ await fs57.mkdir(dest, { recursive: true });
10327
+ const entries = await fs57.readdir(src, { withFileTypes: true });
9290
10328
  for (const entry of entries) {
9291
10329
  const from = nodePath3.join(src, entry.name);
9292
10330
  const to = nodePath3.join(dest, entry.name);
9293
10331
  if (entry.isDirectory()) {
9294
- await copyTree(fs53, from, to);
10332
+ await copyTree(fs57, from, to);
9295
10333
  } else {
9296
- await fs53.copyFile(from, to);
10334
+ await fs57.copyFile(from, to);
9297
10335
  }
9298
10336
  }
9299
10337
  }
9300
10338
  async function stepInstallSkill(deps = {}) {
9301
- const { fs: fs53, paths, repoRoot } = resolveDeps(deps);
10339
+ const { fs: fs57, paths, repoRoot } = resolveDeps(deps);
9302
10340
  const targetDir = nodePath3.join(paths.homeDir, ".claude", "skills", "active-work");
9303
10341
  const targetMarker = nodePath3.join(targetDir, "SKILL.md");
9304
10342
  const sourceDir = nodePath3.join(repoRoot, "skill");
9305
10343
  try {
9306
- if (await pathExists2(fs53, targetMarker)) {
10344
+ if (await pathExists2(fs57, targetMarker)) {
9307
10345
  return {
9308
10346
  ok: true,
9309
10347
  name: STEP_SKILL,
@@ -9311,7 +10349,7 @@ async function stepInstallSkill(deps = {}) {
9311
10349
  message: `Skill already installed at ${targetDir}`
9312
10350
  };
9313
10351
  }
9314
- if (!await pathExists2(fs53, sourceDir)) {
10352
+ if (!await pathExists2(fs57, sourceDir)) {
9315
10353
  return {
9316
10354
  ok: true,
9317
10355
  name: STEP_SKILL,
@@ -9319,8 +10357,8 @@ async function stepInstallSkill(deps = {}) {
9319
10357
  message: `Skill source not found at ${sourceDir}; skipping`
9320
10358
  };
9321
10359
  }
9322
- await fs53.mkdir(nodePath3.dirname(targetDir), { recursive: true });
9323
- await copyTree(fs53, sourceDir, targetDir);
10360
+ await fs57.mkdir(nodePath3.dirname(targetDir), { recursive: true });
10361
+ await copyTree(fs57, sourceDir, targetDir);
9324
10362
  return {
9325
10363
  ok: true,
9326
10364
  name: STEP_SKILL,
@@ -9336,12 +10374,12 @@ async function stepInstallSkill(deps = {}) {
9336
10374
  }
9337
10375
  }
9338
10376
  async function stepInstallCommand(deps = {}) {
9339
- const { fs: fs53, paths, repoRoot } = resolveDeps(deps);
10377
+ const { fs: fs57, paths, repoRoot } = resolveDeps(deps);
9340
10378
  const targetDir = nodePath3.join(paths.homeDir, ".claude", "commands");
9341
10379
  const target = nodePath3.join(targetDir, "aw-prompt.md");
9342
10380
  const source = nodePath3.join(repoRoot, "claude-commands", "aw-prompt.md");
9343
10381
  try {
9344
- if (!await pathExists2(fs53, source)) {
10382
+ if (!await pathExists2(fs57, source)) {
9345
10383
  return {
9346
10384
  ok: true,
9347
10385
  name: STEP_COMMAND,
@@ -9349,8 +10387,8 @@ async function stepInstallCommand(deps = {}) {
9349
10387
  message: `Command source not found at ${source}; skipping`
9350
10388
  };
9351
10389
  }
9352
- await fs53.mkdir(targetDir, { recursive: true });
9353
- await fs53.copyFile(source, target);
10390
+ await fs57.mkdir(targetDir, { recursive: true });
10391
+ await fs57.copyFile(source, target);
9354
10392
  return {
9355
10393
  ok: true,
9356
10394
  name: STEP_COMMAND,
@@ -9451,11 +10489,11 @@ function agentChatHooksPath(paths) {
9451
10489
  return nodePath3.join(home, "hooks.json");
9452
10490
  }
9453
10491
  async function stepRegisterAgentChatHooks(deps = {}) {
9454
- const { fs: fs53, paths } = resolveDeps(deps);
10492
+ const { fs: fs57, paths } = resolveDeps(deps);
9455
10493
  const hooksPath = agentChatHooksPath(paths);
9456
10494
  const agentChatHome = nodePath3.dirname(hooksPath);
9457
10495
  try {
9458
- await fs53.stat(agentChatHome);
10496
+ await fs57.stat(agentChatHome);
9459
10497
  } catch (err) {
9460
10498
  if (err.code === "ENOENT") {
9461
10499
  return {
@@ -9470,7 +10508,7 @@ async function stepRegisterAgentChatHooks(deps = {}) {
9470
10508
  try {
9471
10509
  let config = {};
9472
10510
  try {
9473
- const raw = await fs53.readFile(hooksPath, "utf8");
10511
+ const raw = await fs57.readFile(hooksPath, "utf8");
9474
10512
  const parsed = JSON.parse(raw);
9475
10513
  if (typeof parsed === "object" && parsed !== null) config = parsed;
9476
10514
  } catch (err) {
@@ -9499,7 +10537,7 @@ async function stepRegisterAgentChatHooks(deps = {}) {
9499
10537
  message: `Already registered in ${hooksPath}`
9500
10538
  };
9501
10539
  }
9502
- await fs53.writeFile(hooksPath, JSON.stringify(config, null, 2) + "\n", "utf8");
10540
+ await fs57.writeFile(hooksPath, JSON.stringify(config, null, 2) + "\n", "utf8");
9503
10541
  return {
9504
10542
  ok: true,
9505
10543
  name: STEP_AGENT_CHAT_HOOKS,
@@ -9673,10 +10711,10 @@ async function confirmStep(prompts, yes, message, initial = true) {
9673
10711
  return answer === true;
9674
10712
  }
9675
10713
  async function uninstallSkill(deps = {}) {
9676
- const { fs: fs53, paths } = resolveDeps(deps);
10714
+ const { fs: fs57, paths } = resolveDeps(deps);
9677
10715
  const target = nodePath3.join(paths.homeDir, ".claude", "skills", "active-work");
9678
10716
  try {
9679
- if (!await pathExists2(fs53, target)) {
10717
+ if (!await pathExists2(fs57, target)) {
9680
10718
  return {
9681
10719
  ok: true,
9682
10720
  name: STEP_SKILL,
@@ -9684,7 +10722,7 @@ async function uninstallSkill(deps = {}) {
9684
10722
  message: `Skill not present at ${target}`
9685
10723
  };
9686
10724
  }
9687
- await fs53.rm(target, { recursive: true, force: true });
10725
+ await fs57.rm(target, { recursive: true, force: true });
9688
10726
  return {
9689
10727
  ok: true,
9690
10728
  name: STEP_SKILL,
@@ -9696,10 +10734,10 @@ async function uninstallSkill(deps = {}) {
9696
10734
  }
9697
10735
  }
9698
10736
  async function uninstallCommand(deps = {}) {
9699
- const { fs: fs53, paths } = resolveDeps(deps);
10737
+ const { fs: fs57, paths } = resolveDeps(deps);
9700
10738
  const target = nodePath3.join(paths.homeDir, ".claude", "commands", "aw-prompt.md");
9701
10739
  try {
9702
- if (!await pathExists2(fs53, target)) {
10740
+ if (!await pathExists2(fs57, target)) {
9703
10741
  return {
9704
10742
  ok: true,
9705
10743
  name: STEP_COMMAND,
@@ -9707,7 +10745,7 @@ async function uninstallCommand(deps = {}) {
9707
10745
  message: `Command not present at ${target}`
9708
10746
  };
9709
10747
  }
9710
- await fs53.rm(target, { force: true });
10748
+ await fs57.rm(target, { force: true });
9711
10749
  return {
9712
10750
  ok: true,
9713
10751
  name: STEP_COMMAND,
@@ -9851,20 +10889,20 @@ async function runUninstall(deps = {}) {
9851
10889
  }
9852
10890
 
9853
10891
  // src/commands/setup.ts
9854
- var ArgsSchema49 = z58.object({
9855
- update: z58.boolean().optional(),
9856
- yes: z58.boolean().optional()
10892
+ var ArgsSchema49 = z59.object({
10893
+ update: z59.boolean().optional(),
10894
+ yes: z59.boolean().optional()
9857
10895
  });
9858
- var StepSchema = z58.object({
9859
- name: z58.string(),
9860
- ok: z58.boolean(),
9861
- done: z58.boolean().optional(),
9862
- message: z58.string().optional(),
9863
- error: z58.string().optional()
10896
+ var StepSchema = z59.object({
10897
+ name: z59.string(),
10898
+ ok: z59.boolean(),
10899
+ done: z59.boolean().optional(),
10900
+ message: z59.string().optional(),
10901
+ error: z59.string().optional()
9864
10902
  });
9865
- var ResultSchema45 = z58.object({
9866
- banner: z58.string(),
9867
- steps: z58.array(StepSchema)
10903
+ var ResultSchema45 = z59.object({
10904
+ banner: z59.string(),
10905
+ steps: z59.array(StepSchema)
9868
10906
  });
9869
10907
  function printStep(step) {
9870
10908
  if (step.ok) {
@@ -9917,19 +10955,19 @@ var setup_default = defineCommand({
9917
10955
  });
9918
10956
 
9919
10957
  // src/commands/uninstall.ts
9920
- import { z as z59 } from "zod";
9921
- var ArgsSchema50 = z59.object({
9922
- yes: z59.boolean().optional()
10958
+ import { z as z60 } from "zod";
10959
+ var ArgsSchema50 = z60.object({
10960
+ yes: z60.boolean().optional()
9923
10961
  });
9924
- var StepSchema2 = z59.object({
9925
- name: z59.string(),
9926
- done: z59.boolean(),
9927
- message: z59.string().optional(),
9928
- error: z59.string().optional()
10962
+ var StepSchema2 = z60.object({
10963
+ name: z60.string(),
10964
+ done: z60.boolean(),
10965
+ message: z60.string().optional(),
10966
+ error: z60.string().optional()
9929
10967
  });
9930
- var ResultSchema46 = z59.object({
9931
- steps: z59.array(StepSchema2),
9932
- activeRootPreservedAt: z59.string()
10968
+ var ResultSchema46 = z60.object({
10969
+ steps: z60.array(StepSchema2),
10970
+ activeRootPreservedAt: z60.string()
9933
10971
  });
9934
10972
  var uninstall_default = defineCommand({
9935
10973
  name: "uninstall",
@@ -9965,71 +11003,40 @@ var uninstall_default = defineCommand({
9965
11003
  });
9966
11004
 
9967
11005
  // src/commands/doctor.ts
9968
- import { z as z60 } from "zod";
11006
+ import { z as z61 } from "zod";
9969
11007
 
9970
11008
  // src/doctor.ts
9971
11009
  import { promises as fsp4 } from "fs";
9972
11010
  import nodePath4 from "path";
9973
11011
  import os6 from "os";
9974
11012
 
9975
- // src/lint/index.ts
9976
- import { promises as fs51 } from "fs";
9977
- import path63 from "path";
9978
-
9979
- // src/lint/brief.ts
9980
- import { promises as fs47 } from "fs";
9981
- import path58 from "path";
9982
-
9983
- // src/lint/hashes.ts
9984
- import { promises as fs48 } from "fs";
9985
- import path59 from "path";
9986
- async function lintHashes(slug, initiativeDir) {
9987
- const manifest = await readArtifactHashes(initiativeDir);
9988
- const findings = [];
9989
- for (const [relPath, storedHash] of Object.entries(manifest)) {
9990
- let content;
9991
- try {
9992
- content = await fs48.readFile(path59.join(initiativeDir, relPath), "utf8");
9993
- } catch (err) {
9994
- const code = err.code;
9995
- if (code === "ENOENT") continue;
9996
- throw err;
9997
- }
9998
- if (hashContent(content) === storedHash) continue;
9999
- findings.push({
10000
- level: "warn",
10001
- slug,
10002
- file: relPath,
10003
- message: `${relPath} was hand-edited outside active-work (content no longer matches the last CLI write) \u2014 re-apply the change through the CLI/MCP tools instead of editing the file directly`
10004
- });
10005
- }
10006
- return findings;
11013
+ // src/workspace-index/doctor.ts
11014
+ import { promises as fs55 } from "fs";
11015
+ var INDEXED_REFS = `
11016
+ SELECT initiative_ref AS ref, path FROM initiative
11017
+ UNION ALL SELECT note_ref, path FROM note
11018
+ UNION ALL SELECT task_ref, path FROM workspace_task
11019
+ UNION ALL SELECT session_ref, path FROM session_record
11020
+ UNION ALL SELECT source_ref, path FROM source
11021
+ ORDER BY path
11022
+ `;
11023
+ async function checkWorkspaceIndex(graph, activeRoot) {
11024
+ const indexed = graph.db.prepare(INDEXED_REFS).all();
11025
+ const results = await Promise.all(
11026
+ indexed.map(async (row) => await opens(toAbsolute(activeRoot, row.path)) ? null : row)
11027
+ );
11028
+ return {
11029
+ checked: indexed.length,
11030
+ missing: results.filter((row) => row !== null).map((row) => `${row.ref} -> ${row.path}`)
11031
+ };
10007
11032
  }
10008
-
10009
- // src/lint/open-loops.ts
10010
- import path60 from "path";
10011
-
10012
- // src/lint/task.ts
10013
- import { promises as fs49 } from "fs";
10014
- import path61 from "path";
10015
- import YAML3 from "yaml";
10016
-
10017
- // src/lint/zero-loops.ts
10018
- import { promises as fs50 } from "fs";
10019
- import path62 from "path";
10020
- import YAML4 from "yaml";
10021
-
10022
- // src/lint/index.ts
10023
- async function listInitiativeSlugs3(activeRoot) {
10024
- let entries;
11033
+ async function opens(absolutePath) {
10025
11034
  try {
10026
- entries = await fs51.readdir(activeRoot, { withFileTypes: true });
10027
- } catch (err) {
10028
- const code = err.code;
10029
- if (code === "ENOENT") return [];
10030
- throw err;
11035
+ await fs55.access(absolutePath);
11036
+ return true;
11037
+ } catch {
11038
+ return false;
10031
11039
  }
10032
- return entries.filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) => e.name).sort();
10033
11040
  }
10034
11041
 
10035
11042
  // src/doctor.ts
@@ -10078,19 +11085,19 @@ function parseMajor(version) {
10078
11085
  const match = /^v?(\d+)\./.exec(version);
10079
11086
  return match ? Number(match[1]) : 0;
10080
11087
  }
10081
- async function fileExists3(fs53, target) {
11088
+ async function fileExists3(fs57, target) {
10082
11089
  try {
10083
- await fs53.access(target);
11090
+ await fs57.access(target);
10084
11091
  return true;
10085
11092
  } catch {
10086
11093
  return false;
10087
11094
  }
10088
11095
  }
10089
11096
  var MCP_SERVER_NAMES = ["@hjewkes/active-work", "active-work"];
10090
- async function readMcpRegistered(fs53, homeDir) {
11097
+ async function readMcpRegistered(fs57, homeDir) {
10091
11098
  const configPath = nodePath4.join(homeDir, ".claude.json");
10092
11099
  try {
10093
- const raw = await fs53.readFile(configPath, "utf8");
11100
+ const raw = await fs57.readFile(configPath, "utf8");
10094
11101
  const parsed = JSON.parse(raw);
10095
11102
  const servers = parsed.mcpServers ?? {};
10096
11103
  return MCP_SERVER_NAMES.some((name) => Boolean(servers[name]));
@@ -10112,9 +11119,9 @@ async function checkNode(deps) {
10112
11119
  };
10113
11120
  }
10114
11121
  async function checkActiveRoot(deps) {
10115
- const fs53 = deps.fs ?? fsp4;
11122
+ const fs57 = deps.fs ?? fsp4;
10116
11123
  const activeRoot = deps.activeRoot ?? getActiveRoot();
10117
- if (!await fileExists3(fs53, activeRoot)) {
11124
+ if (!await fileExists3(fs57, activeRoot)) {
10118
11125
  return {
10119
11126
  name: "active-root",
10120
11127
  status: "fail",
@@ -10122,7 +11129,7 @@ async function checkActiveRoot(deps) {
10122
11129
  };
10123
11130
  }
10124
11131
  const schemaFile = nodePath4.join(activeRoot, ".schema-version");
10125
- if (!await fileExists3(fs53, schemaFile)) {
11132
+ if (!await fileExists3(fs57, schemaFile)) {
10126
11133
  return {
10127
11134
  name: "active-root",
10128
11135
  status: "warn",
@@ -10159,9 +11166,9 @@ async function checkDaemon(deps) {
10159
11166
  };
10160
11167
  }
10161
11168
  async function checkMcp(deps) {
10162
- const fs53 = deps.fs ?? fsp4;
11169
+ const fs57 = deps.fs ?? fsp4;
10163
11170
  const homeDir = deps.homeDir ?? os6.homedir();
10164
- if (await readMcpRegistered(fs53, homeDir)) {
11171
+ if (await readMcpRegistered(fs57, homeDir)) {
10165
11172
  return { name: "mcp-registration", status: "ok", detail: "registered in ~/.claude.json" };
10166
11173
  }
10167
11174
  return {
@@ -10171,10 +11178,10 @@ async function checkMcp(deps) {
10171
11178
  };
10172
11179
  }
10173
11180
  async function checkSkill(deps) {
10174
- const fs53 = deps.fs ?? fsp4;
11181
+ const fs57 = deps.fs ?? fsp4;
10175
11182
  const homeDir = deps.homeDir ?? os6.homedir();
10176
11183
  const skill = nodePath4.join(homeDir, ".claude", "skills", "active-work", "SKILL.md");
10177
- if (await fileExists3(fs53, skill)) {
11184
+ if (await fileExists3(fs57, skill)) {
10178
11185
  return { name: "skill", status: "ok", detail: skill };
10179
11186
  }
10180
11187
  return {
@@ -10251,7 +11258,7 @@ function sessionFilesCheck(malformed) {
10251
11258
  }
10252
11259
  async function checkSessions(deps) {
10253
11260
  const activeRoot = deps.activeRoot ?? getActiveRoot();
10254
- const slugs = await listInitiativeSlugs3(activeRoot);
11261
+ const slugs = await listInitiativeSlugs2(activeRoot);
10255
11262
  const byKind = /* @__PURE__ */ new Map();
10256
11263
  const malformed = [];
10257
11264
  const badTaskRefs = [];
@@ -10293,7 +11300,7 @@ function noteTitlesCheck(entries) {
10293
11300
  }
10294
11301
  async function checkNoteTitles(deps) {
10295
11302
  const activeRoot = deps.activeRoot ?? getActiveRoot();
10296
- const slugs = await listInitiativeSlugs3(activeRoot);
11303
+ const slugs = await listInitiativeSlugs2(activeRoot);
10297
11304
  const overlong = [];
10298
11305
  for (const slug of slugs) {
10299
11306
  const { notes } = await loadNotesFromDir(nodePath4.join(activeRoot, slug));
@@ -10333,9 +11340,42 @@ function artifactHashesCheck(drifted) {
10333
11340
  detail: `hand-edited outside active-work: ${drifted.join("; ")}`
10334
11341
  };
10335
11342
  }
11343
+ function workspaceIndexCheck(health) {
11344
+ if (health === null) {
11345
+ return { name: "workspace-index", status: "ok", detail: "no index built yet" };
11346
+ }
11347
+ if (health.missing.length === 0) {
11348
+ return {
11349
+ name: "workspace-index",
11350
+ status: "ok",
11351
+ detail: `${health.checked} indexed refs all resolve to a file that opens`
11352
+ };
11353
+ }
11354
+ return {
11355
+ name: "workspace-index",
11356
+ status: "warn",
11357
+ // Brain's whole failure, made visible: 5,013 of 5,065 notes detached on one
11358
+ // directory move and nothing noticed, because nothing checked.
11359
+ detail: `${health.missing.length} of ${health.checked} indexed refs point at a file that no longer opens (run \`active-work miner refresh\`): ${health.missing.slice(0, 5).join("; ")}`
11360
+ };
11361
+ }
11362
+ async function checkWorkspaceIndexRefs(deps) {
11363
+ const activeRoot = deps.activeRoot ?? getActiveRoot();
11364
+ let graph;
11365
+ try {
11366
+ graph = openGraph();
11367
+ } catch {
11368
+ return workspaceIndexCheck(null);
11369
+ }
11370
+ try {
11371
+ return workspaceIndexCheck(await checkWorkspaceIndex(graph, activeRoot));
11372
+ } finally {
11373
+ graph.db.close();
11374
+ }
11375
+ }
10336
11376
  async function checkArtifactHashes(deps) {
10337
11377
  const activeRoot = deps.activeRoot ?? getActiveRoot();
10338
- const slugs = await listInitiativeSlugs3(activeRoot);
11378
+ const slugs = await listInitiativeSlugs2(activeRoot);
10339
11379
  const drifted = [];
10340
11380
  for (const slug of slugs) {
10341
11381
  const findings = await lintHashes(slug, nodePath4.join(activeRoot, slug));
@@ -10344,7 +11384,7 @@ async function checkArtifactHashes(deps) {
10344
11384
  return artifactHashesCheck(drifted);
10345
11385
  }
10346
11386
  async function runDoctor(deps = {}) {
10347
- const [installChecks, sessionChecks, noteTitles, artifactHashes, leases] = await Promise.all([
11387
+ const [installChecks, sessionChecks, noteTitles, artifactHashes, leases, workspaceIndex] = await Promise.all([
10348
11388
  Promise.all([
10349
11389
  checkNode(deps),
10350
11390
  checkActiveRoot(deps),
@@ -10356,22 +11396,30 @@ async function runDoctor(deps = {}) {
10356
11396
  checkSessions(deps),
10357
11397
  checkNoteTitles(deps),
10358
11398
  checkArtifactHashes(deps),
10359
- checkLeases(deps)
11399
+ checkLeases(deps),
11400
+ checkWorkspaceIndexRefs(deps)
10360
11401
  ]);
10361
- const checks = [...installChecks, ...sessionChecks, noteTitles, artifactHashes, leases];
11402
+ const checks = [
11403
+ ...installChecks,
11404
+ ...sessionChecks,
11405
+ noteTitles,
11406
+ artifactHashes,
11407
+ leases,
11408
+ workspaceIndex
11409
+ ];
10362
11410
  return { ok: checks.every((c) => c.status !== "fail"), checks };
10363
11411
  }
10364
11412
 
10365
11413
  // src/commands/doctor.ts
10366
- var ArgsSchema51 = z60.object({});
10367
- var CheckSchema = z60.object({
10368
- name: z60.string(),
10369
- status: z60.enum(["ok", "warn", "fail"]),
10370
- detail: z60.string()
11414
+ var ArgsSchema51 = z61.object({});
11415
+ var CheckSchema = z61.object({
11416
+ name: z61.string(),
11417
+ status: z61.enum(["ok", "warn", "fail"]),
11418
+ detail: z61.string()
10371
11419
  });
10372
- var ResultSchema47 = z60.object({
10373
- ok: z60.boolean(),
10374
- checks: z60.array(CheckSchema)
11420
+ var ResultSchema47 = z61.object({
11421
+ ok: z61.boolean(),
11422
+ checks: z61.array(CheckSchema)
10375
11423
  });
10376
11424
  function badge(status) {
10377
11425
  if (status === "ok") return color.green("OK ");
@@ -10399,36 +11447,36 @@ var doctor_default = defineCommand({
10399
11447
  });
10400
11448
 
10401
11449
  // src/commands/migrate.ts
10402
- import { z as z61 } from "zod";
10403
- var ArgsSchema52 = z61.object({
10404
- dry_run: z61.boolean().optional(),
10405
- apply: z61.boolean().optional()
10406
- });
10407
- var SessionSchema = z61.object({
10408
- kind: z61.enum(["open", "abandon"]),
10409
- action: z61.enum(["write", "exists"]),
10410
- file: z61.string(),
10411
- ended: z61.string(),
10412
- loops: z61.number().int().nonnegative(),
10413
- resolves: z61.number().int().nonnegative()
10414
- });
10415
- var InitiativeSchema = z61.object({
10416
- slug: z61.string(),
10417
- sessions: z61.array(SessionSchema),
10418
- task_seq_backfill: z61.number().int().positive().nullable(),
10419
- brief_repairs: z61.array(z61.string()),
10420
- brief_blocked: z61.string().optional(),
10421
- handoff: z61.enum(["archive-and-remove", "archive-exists", "absent"]),
10422
- note: z61.string().optional()
10423
- });
10424
- var ResultSchema48 = z61.object({
10425
- applied: z61.boolean(),
10426
- proposal: z61.string(),
10427
- initiatives: z61.array(InitiativeSchema),
10428
- repairs: z61.array(z61.object({ action: z61.string(), file: z61.string(), detail: z61.string() })),
10429
- uncovered: z61.array(z61.string())
10430
- });
10431
- function describe(plan, applied) {
11450
+ import { z as z62 } from "zod";
11451
+ var ArgsSchema52 = z62.object({
11452
+ dry_run: z62.boolean().optional(),
11453
+ apply: z62.boolean().optional()
11454
+ });
11455
+ var SessionSchema = z62.object({
11456
+ kind: z62.enum(["open", "abandon"]),
11457
+ action: z62.enum(["write", "exists"]),
11458
+ file: z62.string(),
11459
+ ended: z62.string(),
11460
+ loops: z62.number().int().nonnegative(),
11461
+ resolves: z62.number().int().nonnegative()
11462
+ });
11463
+ var InitiativeSchema = z62.object({
11464
+ slug: z62.string(),
11465
+ sessions: z62.array(SessionSchema),
11466
+ task_seq_backfill: z62.number().int().positive().nullable(),
11467
+ brief_repairs: z62.array(z62.string()),
11468
+ brief_blocked: z62.string().optional(),
11469
+ handoff: z62.enum(["archive-and-remove", "archive-exists", "absent"]),
11470
+ note: z62.string().optional()
11471
+ });
11472
+ var ResultSchema48 = z62.object({
11473
+ applied: z62.boolean(),
11474
+ proposal: z62.string(),
11475
+ initiatives: z62.array(InitiativeSchema),
11476
+ repairs: z62.array(z62.object({ action: z62.string(), file: z62.string(), detail: z62.string() })),
11477
+ uncovered: z62.array(z62.string())
11478
+ });
11479
+ function describe2(plan, applied) {
10432
11480
  const initiatives = plan.initiatives.map((i) => ({
10433
11481
  slug: i.slug,
10434
11482
  sessions: i.sessions.map((s) => ({
@@ -10514,7 +11562,7 @@ var migrate_default = defineCommand({
10514
11562
  async run(args, ctx) {
10515
11563
  if (args.apply !== true) {
10516
11564
  const plan2 = await planV2ToV3(ctx.activeRoot);
10517
- const result2 = describe(plan2, false);
11565
+ const result2 = describe2(plan2, false);
10518
11566
  if (ctx.format !== "json") process.stderr.write(render(result2));
10519
11567
  return result2;
10520
11568
  }
@@ -10530,7 +11578,7 @@ var migrate_default = defineCommand({
10530
11578
  const plan = await planV2ToV3(ctx.activeRoot);
10531
11579
  await applyV2ToV3(ctx.activeRoot, plan);
10532
11580
  await writeSchemaVersion(ctx.activeRoot, CURRENT_VERSION);
10533
- const result = describe(plan, true);
11581
+ const result = describe2(plan, true);
10534
11582
  if (ctx.format !== "json") process.stderr.write(render(result));
10535
11583
  return result;
10536
11584
  }
@@ -10538,18 +11586,18 @@ var migrate_default = defineCommand({
10538
11586
 
10539
11587
  // src/commands/sync.ts
10540
11588
  import os7 from "os";
10541
- import { z as z62 } from "zod";
10542
- var ArgsSchema53 = z62.object({
10543
- message: z62.string().min(1).optional(),
10544
- require_clean: z62.boolean().optional()
10545
- });
10546
- var ResultSchema49 = z62.object({
10547
- branch: z62.string(),
10548
- committed: z62.boolean(),
10549
- committed_files: z62.number().int(),
10550
- rebased: z62.boolean(),
10551
- pushed: z62.boolean(),
10552
- summary: z62.string()
11589
+ import { z as z63 } from "zod";
11590
+ var ArgsSchema53 = z63.object({
11591
+ message: z63.string().min(1).optional(),
11592
+ require_clean: z63.boolean().optional()
11593
+ });
11594
+ var ResultSchema49 = z63.object({
11595
+ branch: z63.string(),
11596
+ committed: z63.boolean(),
11597
+ committed_files: z63.number().int(),
11598
+ rebased: z63.boolean(),
11599
+ pushed: z63.boolean(),
11600
+ summary: z63.string()
10553
11601
  });
10554
11602
  async function git(root, args) {
10555
11603
  return getGitRunner()("git", ["-C", root, ...args]);
@@ -10785,16 +11833,16 @@ for (const cmd of ALL_COMMANDS) {
10785
11833
  import { readCommanderOption } from "@titan-design/registry";
10786
11834
 
10787
11835
  // src/utils/usage-log.ts
10788
- import { promises as fs52 } from "fs";
10789
- import path64 from "path";
11836
+ import { promises as fs56 } from "fs";
11837
+ import path66 from "path";
10790
11838
  function usageLogPath() {
10791
- return path64.join(getStateRoot(), "usage.jsonl");
11839
+ return path66.join(getStateRoot(), "usage.jsonl");
10792
11840
  }
10793
11841
  async function appendUsage(rec) {
10794
11842
  try {
10795
11843
  const file = usageLogPath();
10796
- await fs52.mkdir(path64.dirname(file), { recursive: true });
10797
- await fs52.appendFile(file, JSON.stringify(rec) + "\n", "utf8");
11844
+ await fs56.mkdir(path66.dirname(file), { recursive: true });
11845
+ await fs56.appendFile(file, JSON.stringify(rec) + "\n", "utf8");
10798
11846
  } catch {
10799
11847
  }
10800
11848
  }
@@ -10965,7 +12013,7 @@ function attachCommand(root, cmd) {
10965
12013
  function buildProgram() {
10966
12014
  const program = new Command();
10967
12015
  program.exitOverride();
10968
- program.name("active-work").description("active-work CLI \u2014 durable workspace state for engineering work").version("0.1.0").option("--json", "emit machine-readable JSON envelope on stdout").addHelpText(
12016
+ program.name("active-work").description("active-work CLI \u2014 durable workspace state for engineering work").version(BUILD_VERSION).option("--json", "emit machine-readable JSON envelope on stdout").addHelpText(
10969
12017
  "after",
10970
12018
  "\nRun `active-work <command> --help` for command-specific options.\nTip: `aw [slug]` launches Claude with the bootstrap prompt.\n"
10971
12019
  );