@titan-design/active-work 0.3.0 → 0.4.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-BK25ASXA.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
  );
@@ -5198,22 +5200,145 @@ import { watchTree } from "@titan-design/daemon";
5198
5200
 
5199
5201
  // src/session-index/graph.ts
5200
5202
  import Database from "better-sqlite3";
5201
- import { MIGRATIONS, openSessionGraph } from "@titan-design/session-graph";
5203
+ import { openSessionGraph } from "@titan-design/session-graph";
5204
+ import { runMigrations, WatermarkTable } from "@titan-design/store-sqlite";
5202
5205
  import path46 from "path";
5206
+
5207
+ // src/workspace-index/schema.ts
5208
+ import { kitDdl } from "@titan-design/store-sqlite";
5209
+ import { MIGRATIONS as SESSION_GRAPH_MIGRATIONS } from "@titan-design/session-graph";
5210
+ var WORKSPACE_KIT = {
5211
+ watermark: "workspace_file",
5212
+ edge: "edge",
5213
+ spanFts: "search"
5214
+ };
5215
+ var WORKSPACE_SPAN_SOURCE_BASE = 1e9;
5216
+ var DOMAIN_DDL = `
5217
+ CREATE TABLE IF NOT EXISTS initiative (
5218
+ path TEXT PRIMARY KEY,
5219
+ initiative_ref TEXT NOT NULL UNIQUE,
5220
+ slug TEXT NOT NULL,
5221
+ title TEXT,
5222
+ state TEXT,
5223
+ rank INTEGER,
5224
+ ship_target TEXT,
5225
+ owner TEXT,
5226
+ task_prefix TEXT,
5227
+ updated TEXT
5228
+ );
5229
+
5230
+ CREATE TABLE IF NOT EXISTS note (
5231
+ path TEXT PRIMARY KEY,
5232
+ note_ref TEXT NOT NULL UNIQUE,
5233
+ initiative TEXT NOT NULL,
5234
+ filename TEXT NOT NULL,
5235
+ kind TEXT NOT NULL,
5236
+ title TEXT NOT NULL,
5237
+ created TEXT,
5238
+ tags TEXT,
5239
+ hits INTEGER NOT NULL DEFAULT 0,
5240
+ promoted_at TEXT
5241
+ );
5242
+ CREATE INDEX IF NOT EXISTS idx_note_initiative ON note(initiative);
5243
+
5244
+ CREATE TABLE IF NOT EXISTS workspace_task (
5245
+ path TEXT PRIMARY KEY,
5246
+ task_ref TEXT NOT NULL,
5247
+ initiative TEXT NOT NULL,
5248
+ task_id TEXT NOT NULL,
5249
+ title TEXT NOT NULL,
5250
+ status TEXT NOT NULL,
5251
+ priority INTEGER,
5252
+ severity TEXT,
5253
+ estimate REAL,
5254
+ tags TEXT,
5255
+ created TEXT,
5256
+ updated TEXT,
5257
+ done_at TEXT
5258
+ );
5259
+ CREATE INDEX IF NOT EXISTS idx_workspace_task_ref ON workspace_task(task_ref);
5260
+
5261
+ CREATE TABLE IF NOT EXISTS session_record (
5262
+ path TEXT PRIMARY KEY,
5263
+ session_ref TEXT NOT NULL,
5264
+ initiative TEXT NOT NULL,
5265
+ session_id TEXT NOT NULL,
5266
+ started TEXT,
5267
+ ended TEXT,
5268
+ track TEXT,
5269
+ parent_session_id TEXT
5270
+ );
5271
+ CREATE INDEX IF NOT EXISTS idx_session_record_ref ON session_record(session_ref);
5272
+
5273
+ CREATE TABLE IF NOT EXISTS source (
5274
+ path TEXT PRIMARY KEY,
5275
+ source_ref TEXT NOT NULL UNIQUE,
5276
+ initiative TEXT NOT NULL,
5277
+ title TEXT,
5278
+ kind TEXT,
5279
+ added TEXT
5280
+ );
5281
+ `;
5282
+ function nextVersion(chain) {
5283
+ return (chain[chain.length - 1]?.version ?? 0) + 1;
5284
+ }
5285
+ var SPAN_SOURCE_INDEX = `
5286
+ CREATE INDEX IF NOT EXISTS idx_search_span_source ON search_span(source_id);
5287
+ `;
5288
+ var WORKSPACE_MIGRATIONS = [
5289
+ {
5290
+ version: nextVersion(SESSION_GRAPH_MIGRATIONS),
5291
+ name: "workspace index tables",
5292
+ up: (db) => {
5293
+ db.exec(kitDdl({ watermark: WORKSPACE_KIT.watermark }));
5294
+ db.exec(DOMAIN_DDL);
5295
+ db.exec(SPAN_SOURCE_INDEX);
5296
+ }
5297
+ }
5298
+ ];
5299
+ var PRESERVE_DDL = `
5300
+ CREATE TABLE IF NOT EXISTS preserved_row (
5301
+ table_name TEXT NOT NULL,
5302
+ identity TEXT NOT NULL,
5303
+ row_key TEXT NOT NULL,
5304
+ payload TEXT NOT NULL,
5305
+ origin TEXT NOT NULL,
5306
+ mode TEXT NOT NULL DEFAULT 'insert',
5307
+ preserved_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now')),
5308
+ PRIMARY KEY (table_name, row_key)
5309
+ );
5310
+ `;
5311
+ var PRESERVE_MIGRATION = {
5312
+ version: nextVersion([...SESSION_GRAPH_MIGRATIONS, ...WORKSPACE_MIGRATIONS]),
5313
+ name: "preserved rows",
5314
+ up: (db) => db.exec(PRESERVE_DDL)
5315
+ };
5316
+ var MIGRATIONS = [
5317
+ ...SESSION_GRAPH_MIGRATIONS,
5318
+ ...WORKSPACE_MIGRATIONS,
5319
+ PRESERVE_MIGRATION
5320
+ ];
5321
+
5322
+ // src/session-index/graph.ts
5203
5323
  var SCHEMA_VERSION = MIGRATIONS[MIGRATIONS.length - 1]?.version ?? 0;
5204
5324
  function defaultGraphPath() {
5205
5325
  return path46.join(getMinerRoot(), "graph.sqlite3");
5206
5326
  }
5207
5327
  function openGraph(dbPath = defaultGraphPath()) {
5208
- return openSessionGraph(dbPath);
5328
+ const graph = openSessionGraph(dbPath);
5329
+ runMigrations(graph.db, MIGRATIONS);
5330
+ return {
5331
+ ...graph,
5332
+ workspaceFiles: new WatermarkTable(graph.db, { name: WORKSPACE_KIT.watermark })
5333
+ };
5209
5334
  }
5210
5335
  function openGraphReadOnly(dbPath = defaultGraphPath()) {
5211
5336
  return new Database(dbPath, { readonly: true });
5212
5337
  }
5213
5338
 
5214
5339
  // src/session-index/refresh.ts
5215
- import path48 from "path";
5216
- import { promises as fs36 } from "fs";
5340
+ import path56 from "path";
5341
+ import { promises as fs44 } from "fs";
5217
5342
  import lockfile from "proper-lockfile";
5218
5343
  import { discoverTranscripts, transcriptsRoot } from "@titan-design/session-read";
5219
5344
  import { refreshCorpus, resetIndex } from "@titan-design/session-graph";
@@ -5274,6 +5399,873 @@ function taskResolver(root) {
5274
5399
  };
5275
5400
  }
5276
5401
 
5402
+ // src/workspace-index/refresh.ts
5403
+ import { promises as fs43 } from "fs";
5404
+ import matter4 from "gray-matter";
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
+ async function readAttempt(file, watermarkId) {
6035
+ try {
6036
+ return { file, watermarkId, record: await readRecord(file), reason: null };
6037
+ } catch (err) {
6038
+ return {
6039
+ file,
6040
+ watermarkId,
6041
+ record: null,
6042
+ reason: err instanceof Error ? err.message : String(err)
6043
+ };
6044
+ }
6045
+ }
6046
+ function applyBatch(graph, writer, batch) {
6047
+ graph.db.transaction(() => {
6048
+ for (const attempt of batch) {
6049
+ if (attempt.record === null) {
6050
+ writer.purge(attempt.file.path, attempt.watermarkId);
6051
+ graph.workspaceFiles.markStatus(attempt.file.path, "quarantined", attempt.reason);
6052
+ continue;
6053
+ }
6054
+ writer.apply(attempt.record, attempt.watermarkId);
6055
+ graph.workspaceFiles.advance(attempt.file.path, {
6056
+ lastOffset: attempt.file.size,
6057
+ fileSize: attempt.file.size,
6058
+ fileMtime: attempt.file.mtime
6059
+ });
6060
+ }
6061
+ })();
6062
+ }
6063
+ function removeVanished(graph, writer, present) {
6064
+ let removed = 0;
6065
+ for (const row of graph.workspaceFiles.list()) {
6066
+ if (present.has(row.sourceKey)) continue;
6067
+ writer.purge(row.sourceKey, row.sourceId);
6068
+ graph.db.prepare("DELETE FROM workspace_file WHERE source_id = ?").run(row.sourceId);
6069
+ removed++;
6070
+ }
6071
+ return removed;
6072
+ }
6073
+ async function collectNoteFacts(activeRoot, graph) {
6074
+ const rows = graph.db.prepare("SELECT path, note_ref, initiative, tags FROM note ORDER BY path").all();
6075
+ const facts = await Promise.all(
6076
+ rows.map(async (row) => ({
6077
+ ref: row.note_ref,
6078
+ initiative: row.initiative,
6079
+ tags: JSON.parse(row.tags ?? "[]") ?? [],
6080
+ body: await readBody(toAbsolute(activeRoot, row.path))
6081
+ }))
6082
+ );
6083
+ return facts;
6084
+ }
6085
+ async function readBody(absolutePath) {
6086
+ try {
6087
+ return matter4(await fs43.readFile(absolutePath, "utf8")).content;
6088
+ } catch {
6089
+ return "";
6090
+ }
6091
+ }
6092
+ function countRows(graph) {
6093
+ const count = (table) => graph.db.prepare(`SELECT COUNT(*) AS n FROM "${table}"`).get().n;
6094
+ return {
6095
+ initiative: count("initiative"),
6096
+ note: count("note"),
6097
+ task: count("workspace_task"),
6098
+ session: count("session_record"),
6099
+ source: count("source")
6100
+ };
6101
+ }
6102
+ async function refreshWorkspace(graph, options = {}) {
6103
+ const activeRoot = options.activeRoot ?? getActiveRoot();
6104
+ const writer = new WorkspaceWriter(graph);
6105
+ const files = await scanWorkspace(activeRoot);
6106
+ const malformed = [];
6107
+ let indexed = 0;
6108
+ let unchanged = 0;
6109
+ const changed = [];
6110
+ graph.db.transaction(() => {
6111
+ for (const file of files) {
6112
+ const row = graph.workspaceFiles.ensure(file.path);
6113
+ if (!options.full && isUnchanged(row, file)) unchanged++;
6114
+ else changed.push({ file, watermarkId: row.sourceId });
6115
+ }
6116
+ })();
6117
+ for (let start = 0; start < changed.length; start += BATCH) {
6118
+ const batch = await Promise.all(
6119
+ changed.slice(start, start + BATCH).map((e) => readAttempt(e.file, e.watermarkId))
6120
+ );
6121
+ applyBatch(graph, writer, batch);
6122
+ indexed += batch.filter((a) => a.record !== null).length;
6123
+ for (const failed of batch.filter((a) => a.record === null)) {
6124
+ malformed.push({ path: failed.file.path, reason: failed.reason ?? "unreadable" });
6125
+ }
6126
+ }
6127
+ const removed = removeVanished(graph, writer, new Set(files.map((f) => f.path)));
6128
+ const edges = rebuildEdges(graph, await collectNoteFacts(activeRoot, graph));
6129
+ return {
6130
+ files: files.length,
6131
+ indexed,
6132
+ unchanged,
6133
+ removed,
6134
+ malformed,
6135
+ rows: countRows(graph),
6136
+ edges,
6137
+ orphanRatio: graph.spans.orphanRatio()
6138
+ };
6139
+ }
6140
+
6141
+ // src/session-index/preserve.ts
6142
+ var UPSERT = `
6143
+ INSERT INTO preserved_row (table_name, identity, row_key, payload, origin, mode)
6144
+ VALUES (@table, @identity, @key, @payload, @origin, @mode)
6145
+ ON CONFLICT (table_name, row_key) DO UPDATE SET
6146
+ identity = excluded.identity, payload = excluded.payload,
6147
+ origin = excluded.origin, mode = excluded.mode
6148
+ `;
6149
+ function preserveRow(graph, row) {
6150
+ if (row.identity.length === 0) throw new Error(`preserveRow: ${row.table} needs an identity`);
6151
+ if (row.mode === "merge" && row.identity.length !== 1) {
6152
+ throw new Error(`preserveRow: a merge over ${row.table} matches exactly one column`);
6153
+ }
6154
+ const missing = row.mode === "insert" ? row.identity.filter((c) => !(c in row.payload)) : [];
6155
+ if (missing.length > 0) {
6156
+ throw new Error(`preserveRow: ${row.table} payload is missing ${missing.join(", ")}`);
6157
+ }
6158
+ graph.db.prepare(UPSERT).run({
6159
+ ...row,
6160
+ identity: JSON.stringify(row.identity),
6161
+ payload: JSON.stringify(row.payload)
6162
+ });
6163
+ }
6164
+ function listPreserved(graph) {
6165
+ const rows = graph.db.prepare("SELECT * FROM preserved_row ORDER BY table_name, row_key").all();
6166
+ return rows.map((row) => ({
6167
+ table: row.table_name,
6168
+ identity: JSON.parse(row.identity),
6169
+ key: row.row_key,
6170
+ payload: JSON.parse(row.payload),
6171
+ origin: row.origin,
6172
+ mode: row.mode
6173
+ }));
6174
+ }
6175
+ var UNREACHABLE_SESSIONS = `
6176
+ SELECT s.session_id FROM session s
6177
+ WHERE NOT EXISTS (
6178
+ SELECT 1 FROM fact f JOIN transcript t ON t.source_id = f.transcript_id
6179
+ WHERE f.session_id = s.session_id AND t.status = 'ok'
6180
+ )
6181
+ ORDER BY s.session_id
6182
+ `;
6183
+ var SESSION_OWNED = [
6184
+ { table: "session", identity: ["session_id"], key: (r) => String(r.session_id) },
6185
+ { table: "turn", identity: ["prompt_id"], key: (r) => String(r.prompt_id) },
6186
+ {
6187
+ table: "session_model_usage",
6188
+ identity: ["session_id", "model"],
6189
+ key: (r) => `${r.session_id}:${r.model}`
6190
+ },
6191
+ {
6192
+ table: "fact",
6193
+ identity: ["transcript_id", "byte_offset"],
6194
+ key: (r) => `${r.transcript_id}:${r.byte_offset}`
6195
+ },
6196
+ {
6197
+ table: "permission_phase",
6198
+ identity: ["session_id", "t_valid", "to_mode"],
6199
+ key: (r) => `${r.session_id}:${r.t_valid}:${r.to_mode}`
6200
+ },
6201
+ {
6202
+ table: "human_edit",
6203
+ identity: ["session_id", "file_path", "ts"],
6204
+ key: (r) => `${r.session_id}:${r.file_path}:${r.ts}`
6205
+ }
6206
+ ];
6207
+ var SYNTHETIC_KEYS = /* @__PURE__ */ new Set(["fact_id", "phase_id", "edit_id", "checkpoint_id"]);
6208
+ function payloadOf(row) {
6209
+ return Object.fromEntries(Object.entries(row).filter(([column]) => !SYNTHETIC_KEYS.has(column)));
6210
+ }
6211
+ function preserveUnreachable(graph, origin) {
6212
+ const ids = graph.db.prepare(UNREACHABLE_SESSIONS).all().map(
6213
+ (row) => row.session_id
6214
+ );
6215
+ if (ids.length === 0) return 0;
6216
+ const placeholders = ids.map(() => "?").join(", ");
6217
+ let preserved = 0;
6218
+ graph.db.transaction(() => {
6219
+ for (const spec of SESSION_OWNED) {
6220
+ const rows = graph.db.prepare(`SELECT * FROM "${spec.table}" WHERE session_id IN (${placeholders})`).all(...ids);
6221
+ for (const row of rows) {
6222
+ const payload = payloadOf(row);
6223
+ preserveRow(graph, {
6224
+ table: spec.table,
6225
+ identity: spec.identity,
6226
+ key: spec.key(row),
6227
+ payload,
6228
+ origin,
6229
+ mode: "insert"
6230
+ });
6231
+ preserved++;
6232
+ }
6233
+ }
6234
+ })();
6235
+ return preserved;
6236
+ }
6237
+ function restore(graph, row) {
6238
+ const columns = Object.keys(row.payload);
6239
+ const names = columns.map((c) => `"${c}"`).join(", ");
6240
+ const values = columns.map((c) => `@${c}`).join(", ");
6241
+ const match = row.identity.map((c) => `"${c}" IS @${c}`).join(" AND ");
6242
+ const info = graph.db.prepare(
6243
+ `INSERT INTO "${row.table}" (${names}) SELECT ${values}
6244
+ WHERE NOT EXISTS (SELECT 1 FROM "${row.table}" WHERE ${match})`
6245
+ ).run(row.payload);
6246
+ return info.changes > 0;
6247
+ }
6248
+ function merge(graph, row) {
6249
+ const assignments = Object.keys(row.payload).map((c) => `"${c}" = @${c}`).join(", ");
6250
+ const info = graph.db.prepare(`UPDATE "${row.table}" SET ${assignments} WHERE "${row.identity[0]}" = @__key`).run({ ...row.payload, __key: row.key });
6251
+ return info.changes > 0;
6252
+ }
6253
+ function replayPreserved(graph) {
6254
+ const summary = { restored: 0, merged: 0, skipped: 0 };
6255
+ graph.db.transaction(() => {
6256
+ for (const row of listPreserved(graph)) {
6257
+ if (row.mode === "merge") {
6258
+ if (merge(graph, row)) summary.merged++;
6259
+ else summary.skipped++;
6260
+ continue;
6261
+ }
6262
+ if (restore(graph, row)) summary.restored++;
6263
+ else summary.skipped++;
6264
+ }
6265
+ })();
6266
+ return summary;
6267
+ }
6268
+
5277
6269
  // src/session-index/refresh.ts
5278
6270
  var LOCK_STALE_MS = 6e4;
5279
6271
  function refreshLockPath() {
@@ -5281,8 +6273,8 @@ function refreshLockPath() {
5281
6273
  }
5282
6274
  async function withRefreshLock(fn) {
5283
6275
  const target = refreshLockPath();
5284
- await fs36.mkdir(path48.dirname(target), { recursive: true });
5285
- await fs36.writeFile(target, "", { flag: "a" });
6276
+ await fs44.mkdir(path56.dirname(target), { recursive: true });
6277
+ await fs44.writeFile(target, "", { flag: "a" });
5286
6278
  const release = await lockfile.lock(target, {
5287
6279
  realpath: false,
5288
6280
  stale: LOCK_STALE_MS,
@@ -5300,7 +6292,11 @@ async function runRefresh(options = {}) {
5300
6292
  const graph = options.graph ?? openGraph(options.dbPath ?? defaultGraphPath());
5301
6293
  const owned = options.graph === void 0;
5302
6294
  try {
5303
- if (options.full) resetIndex(graph);
6295
+ if (options.full) {
6296
+ preserveUnreachable(graph, "transcript pruned before this rebuild (TP-41)");
6297
+ resetIndex(graph);
6298
+ resetWorkspaceIndex(graph);
6299
+ }
5304
6300
  const discovered = await discoverTranscripts(options.root ?? transcriptsRoot());
5305
6301
  const visiting = discovered.slice(0, options.limit ?? discovered.length);
5306
6302
  const verify = options.verifyHashes ?? options.full ?? false;
@@ -5310,6 +6306,11 @@ async function runRefresh(options = {}) {
5310
6306
  withContentHash: verify,
5311
6307
  resolveTasks: taskResolver(options.taskRoot)
5312
6308
  });
6309
+ const workspace = options.skipWorkspace ? null : await refreshWorkspace(graph, {
6310
+ activeRoot: options.activeRoot ?? options.taskRoot,
6311
+ full: options.full
6312
+ });
6313
+ const preserved = replayPreserved(graph);
5313
6314
  return {
5314
6315
  startedAt: startedAt2,
5315
6316
  durationMs: Date.now() - started,
@@ -5325,9 +6326,14 @@ async function runRefresh(options = {}) {
5325
6326
  turnsRolledUp: summary.turnsRolledUp,
5326
6327
  tasksRequested: summary.tasks.requested,
5327
6328
  tasksApplied: summary.tasks.applied,
6329
+ workspace,
6330
+ preserved,
5328
6331
  errors: [
5329
6332
  ...summary.tasks.failed ? [`tasks: ${summary.tasks.error ?? "resolver failed"}`] : [],
5330
- ...quarantineErrors(graph)
6333
+ ...quarantineErrors(graph),
6334
+ ...(workspace?.malformed ?? []).map(
6335
+ (entry) => `workspace: ${entry.path} \u2014 ${entry.reason}`
6336
+ )
5331
6337
  ]
5332
6338
  };
5333
6339
  } finally {
@@ -5798,8 +6804,8 @@ var mcp_status_default = defineCommand({
5798
6804
  });
5799
6805
 
5800
6806
  // src/commands/mcp-logs.ts
5801
- import { promises as fs37 } from "fs";
5802
- import path49 from "path";
6807
+ import { promises as fs45 } from "fs";
6808
+ import path57 from "path";
5803
6809
  import { z as z46 } from "zod";
5804
6810
  var ArgsSchema42 = z46.object({
5805
6811
  lines: z46.number().int().positive().optional()
@@ -5823,10 +6829,10 @@ var mcp_logs_default = defineCommand({
5823
6829
  },
5824
6830
  async run(args) {
5825
6831
  const n = args.lines ?? DEFAULT_LINES;
5826
- const logPath = path49.join(getStateRoot(), "daemon.log");
6832
+ const logPath = path57.join(getStateRoot(), "daemon.log");
5827
6833
  let content;
5828
6834
  try {
5829
- content = await fs37.readFile(logPath, "utf8");
6835
+ content = await fs45.readFile(logPath, "utf8");
5830
6836
  } catch (err) {
5831
6837
  if (err.code === "ENOENT") {
5832
6838
  return { lines: [] };
@@ -5852,8 +6858,8 @@ import { discoverTranscripts as discoverTranscripts2, transcriptsRoot as transcr
5852
6858
  import { Clusterer } from "@titan-design/cluster";
5853
6859
 
5854
6860
  // src/drain/store.ts
5855
- import { promises as fs38 } from "fs";
5856
- import path50 from "path";
6861
+ import { promises as fs46 } from "fs";
6862
+ import path58 from "path";
5857
6863
 
5858
6864
  // src/schemas/template.ts
5859
6865
  import { z as z47 } from "zod";
@@ -5883,10 +6889,10 @@ var TemplatesFileSchema = z47.object({
5883
6889
 
5884
6890
  // src/drain/store.ts
5885
6891
  function templatesPath(root) {
5886
- return path50.join(root, "templates.yml");
6892
+ return path58.join(root, "templates.yml");
5887
6893
  }
5888
6894
  function occurrencesPath(root) {
5889
- return path50.join(root, "occurrences.jsonl");
6895
+ return path58.join(root, "occurrences.jsonl");
5890
6896
  }
5891
6897
  async function loadTemplates(root = getMinerRoot()) {
5892
6898
  try {
@@ -5898,15 +6904,15 @@ async function loadTemplates(root = getMinerRoot()) {
5898
6904
  }
5899
6905
  }
5900
6906
  async function saveTemplates(templates, root = getMinerRoot()) {
5901
- await fs38.mkdir(root, { recursive: true });
6907
+ await fs46.mkdir(root, { recursive: true });
5902
6908
  await writeYaml(templatesPath(root), { templates }, TemplatesFileSchema);
5903
6909
  }
5904
6910
  async function appendOccurrence(occurrence, root = getMinerRoot()) {
5905
6911
  const parsed = OccurrenceSchema.parse(occurrence);
5906
6912
  const target = occurrencesPath(root);
5907
- await fs38.mkdir(root, { recursive: true });
6913
+ await fs46.mkdir(root, { recursive: true });
5908
6914
  await withFileLock(`${target}.lock`, async () => {
5909
- await fs38.appendFile(target, `${JSON.stringify(parsed)}
6915
+ await fs46.appendFile(target, `${JSON.stringify(parsed)}
5910
6916
  `, "utf8");
5911
6917
  });
5912
6918
  }
@@ -5914,17 +6920,17 @@ async function appendOccurrences(occurrences, root = getMinerRoot()) {
5914
6920
  if (occurrences.length === 0) return;
5915
6921
  const parsed = occurrences.map((o) => OccurrenceSchema.parse(o));
5916
6922
  const target = occurrencesPath(root);
5917
- await fs38.mkdir(root, { recursive: true });
6923
+ await fs46.mkdir(root, { recursive: true });
5918
6924
  const body = `${parsed.map((o) => JSON.stringify(o)).join("\n")}
5919
6925
  `;
5920
6926
  await withFileLock(`${target}.lock`, async () => {
5921
- await fs38.appendFile(target, body, "utf8");
6927
+ await fs46.appendFile(target, body, "utf8");
5922
6928
  });
5923
6929
  }
5924
6930
 
5925
6931
  // src/drain/tree-store.ts
5926
- import { promises as fs39 } from "fs";
5927
- import path51 from "path";
6932
+ import { promises as fs47 } from "fs";
6933
+ import path59 from "path";
5928
6934
  import { z as z48 } from "zod";
5929
6935
  var PartitionSchema = z48.object({
5930
6936
  partition: z48.string().min(1),
@@ -5948,7 +6954,7 @@ var LegacySnapshotFileSchema = z48.object({
5948
6954
  trees: z48.array(PartitionSchema.omit({ partition: true }).extend({ toolType: z48.string().min(1) }))
5949
6955
  });
5950
6956
  function snapshotPath(root) {
5951
- return path51.join(root, "drain-trees.json");
6957
+ return path59.join(root, "drain-trees.json");
5952
6958
  }
5953
6959
  function empty() {
5954
6960
  return { version: 1, partitions: [] };
@@ -5956,7 +6962,7 @@ function empty() {
5956
6962
  async function loadTreeSnapshots(root = getMinerRoot()) {
5957
6963
  let raw;
5958
6964
  try {
5959
- raw = await fs39.readFile(snapshotPath(root), "utf8");
6965
+ raw = await fs47.readFile(snapshotPath(root), "utf8");
5960
6966
  } catch (err) {
5961
6967
  if (err instanceof Error && "code" in err && err.code === "ENOENT") return empty();
5962
6968
  throw err;
@@ -5976,7 +6982,7 @@ async function loadTreeSnapshots(root = getMinerRoot()) {
5976
6982
  return parsed.success ? parsed.data : empty();
5977
6983
  }
5978
6984
  async function saveTreeSnapshots(snapshot, root = getMinerRoot()) {
5979
- await fs39.mkdir(root, { recursive: true });
6985
+ await fs47.mkdir(root, { recursive: true });
5980
6986
  await atomicWrite(
5981
6987
  snapshotPath(root),
5982
6988
  `${JSON.stringify(TreeSnapshotFileSchema.parse(snapshot))}
@@ -6126,8 +7132,8 @@ function extractBlobs(line, toolNames) {
6126
7132
  }
6127
7133
 
6128
7134
  // src/drain/reader-state.ts
6129
- import { promises as fs40 } from "fs";
6130
- import path52 from "path";
7135
+ import { promises as fs48 } from "fs";
7136
+ import path60 from "path";
6131
7137
  import { z as z49 } from "zod";
6132
7138
  var TranscriptStateSchema = z49.object({
6133
7139
  /** `~`-relative path, matching `session-index/discover.ts`'s display form. */
@@ -6148,11 +7154,11 @@ var ReaderStateSchema = z49.object({
6148
7154
  });
6149
7155
  var MAX_PENDING_TOOL_NAMES = 256;
6150
7156
  function readerStatePath(root = getMinerRoot()) {
6151
- return path52.join(root, "reader-state.json");
7157
+ return path60.join(root, "reader-state.json");
6152
7158
  }
6153
7159
  async function loadReaderState(root = getMinerRoot()) {
6154
7160
  try {
6155
- const raw = await fs40.readFile(readerStatePath(root), "utf8");
7161
+ const raw = await fs48.readFile(readerStatePath(root), "utf8");
6156
7162
  return ReaderStateSchema.parse(JSON.parse(raw));
6157
7163
  } catch (err) {
6158
7164
  if (err instanceof Error && "code" in err && err.code === "ENOENT") {
@@ -6162,7 +7168,7 @@ async function loadReaderState(root = getMinerRoot()) {
6162
7168
  }
6163
7169
  }
6164
7170
  async function saveReaderState(state, root = getMinerRoot()) {
6165
- await fs40.mkdir(root, { recursive: true });
7171
+ await fs48.mkdir(root, { recursive: true });
6166
7172
  await atomicWrite(
6167
7173
  readerStatePath(root),
6168
7174
  `${JSON.stringify(ReaderStateSchema.parse(state), null, 2)}
@@ -6374,6 +7380,22 @@ var ArgsSchema44 = z51.object({
6374
7380
  limit: z51.coerce.number().int().positive().optional(),
6375
7381
  verify_hashes: z51.boolean().optional()
6376
7382
  });
7383
+ var WorkspaceSchema = z51.object({
7384
+ files: z51.number(),
7385
+ indexed: z51.number(),
7386
+ unchanged: z51.number(),
7387
+ removed: z51.number(),
7388
+ malformed: z51.array(z51.object({ path: z51.string(), reason: z51.string() })),
7389
+ rows: z51.object({
7390
+ initiative: z51.number(),
7391
+ note: z51.number(),
7392
+ task: z51.number(),
7393
+ session: z51.number(),
7394
+ source: z51.number()
7395
+ }),
7396
+ edges: z51.object({ holds: z51.number(), mentions: z51.number(), sharesTag: z51.number() }),
7397
+ orphanRatio: z51.number()
7398
+ });
6377
7399
  var ResultSchema40 = z51.object({
6378
7400
  startedAt: z51.string(),
6379
7401
  durationMs: z51.number(),
@@ -6389,6 +7411,8 @@ var ResultSchema40 = z51.object({
6389
7411
  turnsRolledUp: z51.number(),
6390
7412
  tasksRequested: z51.number(),
6391
7413
  tasksApplied: z51.number(),
7414
+ workspace: WorkspaceSchema.nullable(),
7415
+ preserved: z51.object({ restored: z51.number(), merged: z51.number(), skipped: z51.number() }),
6392
7416
  errors: z51.array(z51.string())
6393
7417
  });
6394
7418
  var miner_refresh_default = defineCommand({
@@ -6425,7 +7449,7 @@ import { z as z52 } from "zod";
6425
7449
 
6426
7450
  // src/session-index/liveness.ts
6427
7451
  import { existsSync as existsSync2 } from "fs";
6428
- import { RELATIONS, toAbsolutePath } from "@titan-design/session-read";
7452
+ import { RELATIONS as RELATIONS2, toAbsolutePath } from "@titan-design/session-read";
6429
7453
  var SKIP_TABLES = /^(sqlite_|_migration$|search_fts)/;
6430
7454
  var EXPECTED_EMPTY = {
6431
7455
  "edge.t_invalid": "NULL means current; the index is rebuilt from scratch, so no edge is ever superseded in place",
@@ -6433,20 +7457,29 @@ var EXPECTED_EMPTY = {
6433
7457
  "edge.attrs": "kit column; the session graph writes no per-edge attributes"
6434
7458
  };
6435
7459
  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" },
7460
+ // `session` is the odd one out: `session` stores the bare id, every other
7461
+ // entity table stores the prefixed ref. Comparing without allowing for that
7462
+ // reports every session endpoint as dangling — a false alarm, which in a
7463
+ // diagnostic is worse than no check at all.
7464
+ session: [
7465
+ { table: "session", column: "session_id", bare: true },
7466
+ { table: "session_record", column: "session_ref" }
7467
+ ],
7468
+ agent: [{ table: "subagent", column: "agent_ref" }],
7469
+ file: [{ table: "file", column: "file_ref" }],
7470
+ branch: [{ table: "branch", column: "branch_ref" }],
7471
+ task: [
7472
+ { table: "task", column: "task_ref" },
7473
+ { table: "workspace_task", column: "task_ref" }
7474
+ ],
7475
+ artifact: [{ table: "artifact", column: "artifact_ref" }],
6446
7476
  // `pr` is keyed by `pr_ref` like every other entity table. It was left
6447
7477
  // unmapped on the belief that it was keyed by `(number, repo)` — that is
6448
7478
  // `pr_merge_observation`, a different table (AW-107).
6449
- pr: { table: "pr", column: "pr_ref" }
7479
+ pr: [{ table: "pr", column: "pr_ref" }],
7480
+ initiative: [{ table: "initiative", column: "initiative_ref" }],
7481
+ note: [{ table: "note", column: "note_ref" }],
7482
+ source: [{ table: "source", column: "source_ref" }]
6450
7483
  };
6451
7484
  function tableNames(db) {
6452
7485
  const rows = db.prepare("SELECT name FROM sqlite_master WHERE type = 'table' ORDER BY name").all();
@@ -6468,11 +7501,12 @@ function scanTable(db, table) {
6468
7501
  nonNull: row[`c${index}`] ?? 0
6469
7502
  }));
6470
7503
  }
7504
+ var WORKSPACE_RELATIONS = ["holds", "mentions", "shares_tag"];
6471
7505
  function relationLiveness(db) {
6472
7506
  const observed = new Map(
6473
7507
  db.prepare("SELECT relation, COUNT(*) AS n FROM edge GROUP BY relation").all().map((row) => [row.relation, row.n])
6474
7508
  );
6475
- const declared = Object.values(RELATIONS);
7509
+ const declared = [...Object.values(RELATIONS2), ...WORKSPACE_RELATIONS];
6476
7510
  const report2 = declared.map((relation) => ({
6477
7511
  relation,
6478
7512
  count: observed.get(relation) ?? 0,
@@ -6492,19 +7526,22 @@ function refNamespaces(db) {
6492
7526
  ) WHERE namespace <> '' GROUP BY namespace ORDER BY namespace`
6493
7527
  ).all();
6494
7528
  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}:%` });
7529
+ const targets = REF_TABLES[namespace];
7530
+ if (!targets) return { namespace, edges: n, dangling: null };
7531
+ const { c } = db.prepare(danglingSql(namespace, targets)).get({ prefix: `${namespace}:%` });
6505
7532
  return { namespace, edges: n, dangling: c };
6506
7533
  });
6507
7534
  }
7535
+ function danglingSql(namespace, targets) {
7536
+ const absent = targets.map((target) => {
7537
+ const lhs = target.bare ? `substr(e.ref, ${namespace.length + 2})` : "e.ref";
7538
+ return `NOT EXISTS (SELECT 1 FROM "${target.table}" t WHERE t."${target.column}" = ${lhs})`;
7539
+ }).join(" AND ");
7540
+ return `SELECT COUNT(*) AS c FROM (
7541
+ SELECT source_ref AS ref FROM edge UNION ALL SELECT target_ref FROM edge
7542
+ ) e
7543
+ WHERE e.ref LIKE @prefix AND ${absent}`;
7544
+ }
6508
7545
  function staleTranscripts(db) {
6509
7546
  const rows = db.prepare("SELECT source_key FROM transcript WHERE status = 'ok'").all();
6510
7547
  return rows.filter((row) => !existsSync2(toAbsolutePath(row.source_key))).length;
@@ -6767,8 +7804,8 @@ async function readStdinJson(stream = process.stdin) {
6767
7804
  }
6768
7805
 
6769
7806
  // src/utils/agent-chat-hook-state.ts
6770
- import { promises as fs41 } from "fs";
6771
- import path53 from "path";
7807
+ import { promises as fs49 } from "fs";
7808
+ import path61 from "path";
6772
7809
  import { z as z54 } from "zod";
6773
7810
  var SpawnContextSchema = z54.object({
6774
7811
  slug: z54.string().min(1),
@@ -6788,19 +7825,19 @@ var SpawnContextSchema = z54.object({
6788
7825
  briefing: z54.string().nullable().default(null)
6789
7826
  });
6790
7827
  function stateDir() {
6791
- return path53.join(getStateRoot(), "agent-chat-hooks");
7828
+ return path61.join(getStateRoot(), "agent-chat-hooks");
6792
7829
  }
6793
7830
  function stateFile(agentId) {
6794
- return path53.join(stateDir(), `${agentId}.json`);
7831
+ return path61.join(stateDir(), `${agentId}.json`);
6795
7832
  }
6796
7833
  async function stashSpawnContext(agentId, context) {
6797
- await fs41.mkdir(stateDir(), { recursive: true });
7834
+ await fs49.mkdir(stateDir(), { recursive: true });
6798
7835
  await atomicWrite(stateFile(agentId), JSON.stringify(context));
6799
7836
  }
6800
7837
  async function readSpawnContext(agentId) {
6801
7838
  let raw;
6802
7839
  try {
6803
- raw = await fs41.readFile(stateFile(agentId), "utf8");
7840
+ raw = await fs49.readFile(stateFile(agentId), "utf8");
6804
7841
  } catch {
6805
7842
  return null;
6806
7843
  }
@@ -6809,7 +7846,7 @@ async function readSpawnContext(agentId) {
6809
7846
  }
6810
7847
  async function takeSpawnContext(agentId) {
6811
7848
  const context = await readSpawnContext(agentId);
6812
- await fs41.rm(stateFile(agentId), { force: true });
7849
+ await fs49.rm(stateFile(agentId), { force: true });
6813
7850
  return context;
6814
7851
  }
6815
7852
  async function peekSpawnContext(agentId) {
@@ -6960,9 +7997,9 @@ import { readFile, writeFile } from "fs/promises";
6960
7997
  import { join } from "path";
6961
7998
 
6962
7999
  // src/migrations/v1-to-v2-artifacts.ts
6963
- import { promises as fs42 } from "fs";
6964
- import path54 from "path";
6965
- import YAML from "yaml";
8000
+ import { promises as fs50 } from "fs";
8001
+ import path62 from "path";
8002
+ import YAML4 from "yaml";
6966
8003
  function asArray(value) {
6967
8004
  return Array.isArray(value) ? value : [];
6968
8005
  }
@@ -6987,13 +8024,13 @@ function normaliseStash(s) {
6987
8024
  async function migrateOne(filePath) {
6988
8025
  let raw;
6989
8026
  try {
6990
- raw = await fs42.readFile(filePath, "utf8");
8027
+ raw = await fs50.readFile(filePath, "utf8");
6991
8028
  } catch {
6992
8029
  return { changed: false, droppedPrs: [] };
6993
8030
  }
6994
8031
  let parsed;
6995
8032
  try {
6996
- parsed = YAML.parse(raw) ?? {};
8033
+ parsed = YAML4.parse(raw) ?? {};
6997
8034
  } catch {
6998
8035
  return { changed: false, droppedPrs: [] };
6999
8036
  }
@@ -7017,38 +8054,38 @@ async function migrateOne(filePath) {
7017
8054
  async function walkArtifactsFiles(activeRoot) {
7018
8055
  const out = [];
7019
8056
  try {
7020
- const entries = await fs42.readdir(activeRoot, { withFileTypes: true });
8057
+ const entries = await fs50.readdir(activeRoot, { withFileTypes: true });
7021
8058
  for (const entry of entries) {
7022
8059
  if (!entry.isDirectory()) continue;
7023
8060
  if (entry.name.startsWith(".")) continue;
7024
- const candidate = path54.join(activeRoot, entry.name, "artifacts.yml");
8061
+ const candidate = path62.join(activeRoot, entry.name, "artifacts.yml");
7025
8062
  try {
7026
- await fs42.access(candidate);
8063
+ await fs50.access(candidate);
7027
8064
  out.push(candidate);
7028
8065
  } catch {
7029
8066
  }
7030
8067
  }
7031
8068
  } catch {
7032
8069
  }
7033
- const archiveRoot = path54.resolve(activeRoot, "..");
8070
+ const archiveRoot = path62.resolve(activeRoot, "..");
7034
8071
  try {
7035
- const domains = await fs42.readdir(archiveRoot, { withFileTypes: true });
8072
+ const domains = await fs50.readdir(archiveRoot, { withFileTypes: true });
7036
8073
  for (const domain of domains) {
7037
8074
  if (!domain.isDirectory()) continue;
7038
8075
  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");
8076
+ if (path62.join(archiveRoot, domain.name) === path62.resolve(activeRoot)) continue;
8077
+ const archiveDir = path62.join(archiveRoot, domain.name, "archive");
7041
8078
  let archived;
7042
8079
  try {
7043
- archived = await fs42.readdir(archiveDir, { withFileTypes: true });
8080
+ archived = await fs50.readdir(archiveDir, { withFileTypes: true });
7044
8081
  } catch {
7045
8082
  continue;
7046
8083
  }
7047
8084
  for (const entry of archived) {
7048
8085
  if (!entry.isDirectory()) continue;
7049
- const candidate = path54.join(archiveDir, entry.name, "artifacts.yml");
8086
+ const candidate = path62.join(archiveDir, entry.name, "artifacts.yml");
7050
8087
  try {
7051
- await fs42.access(candidate);
8088
+ await fs50.access(candidate);
7052
8089
  out.push(candidate);
7053
8090
  } catch {
7054
8091
  }
@@ -7060,15 +8097,15 @@ async function walkArtifactsFiles(activeRoot) {
7060
8097
  }
7061
8098
  async function appendMigrationLog(activeRoot, lines) {
7062
8099
  if (lines.length === 0) return;
7063
- const logPath = path54.join(activeRoot, ".migrations.log");
8100
+ const logPath = path62.join(activeRoot, ".migrations.log");
7064
8101
  const stamp = (/* @__PURE__ */ new Date()).toISOString();
7065
8102
  const body = lines.map((l) => `${stamp} v1->v2 ${l}
7066
8103
  `).join("");
7067
8104
  try {
7068
- await fs42.mkdir(activeRoot, { recursive: true });
8105
+ await fs50.mkdir(activeRoot, { recursive: true });
7069
8106
  } catch {
7070
8107
  }
7071
- await fs42.appendFile(logPath, body, "utf8");
8108
+ await fs50.appendFile(logPath, body, "utf8");
7072
8109
  }
7073
8110
  var v1ToV2Artifacts = {
7074
8111
  from: 1,
@@ -7091,11 +8128,11 @@ var v1ToV2Artifacts = {
7091
8128
  };
7092
8129
 
7093
8130
  // src/migrations/v2-to-v3-open-loops.ts
7094
- import { promises as fs45 } from "fs";
7095
- import path56 from "path";
8131
+ import { promises as fs53 } from "fs";
8132
+ import path64 from "path";
7096
8133
 
7097
8134
  // src/migrations/v3-proposal.ts
7098
- import { promises as fs43 } from "fs";
8135
+ import { promises as fs51 } from "fs";
7099
8136
  import { z as z57 } from "zod";
7100
8137
 
7101
8138
  // src/migrations/data/v3-open-loops-proposal.ts
@@ -7970,7 +9007,7 @@ async function loadProposal() {
7970
9007
  if (override !== void 0 && override.length > 0) {
7971
9008
  let raw;
7972
9009
  try {
7973
- raw = await fs43.readFile(override, "utf8");
9010
+ raw = await fs51.readFile(override, "utf8");
7974
9011
  } catch {
7975
9012
  throw new ValidationError(`${PROPOSAL_PATH_ENV} points at an unreadable file: ${override}`);
7976
9013
  }
@@ -7991,18 +9028,18 @@ async function loadProposal() {
7991
9028
  }
7992
9029
 
7993
9030
  // src/migrations/v3-repairs.ts
7994
- import { promises as fs44 } from "fs";
7995
- import path55 from "path";
9031
+ import { promises as fs52 } from "fs";
9032
+ import path63 from "path";
7996
9033
  var KNOWN_REPAIRS = [
7997
9034
  {
7998
- file: path55.join("audiobook", "sessions", "2026-07-23-0549-2026-07-26-book1-m4b-packaging.md"),
9035
+ file: path63.join("audiobook", "sessions", "2026-07-23-0549-2026-07-26-book1-m4b-packaging.md"),
7999
9036
  kind: "retrack",
8000
9037
  why: "track is a branch name ('feat/tts-quality'), not one of canonical|sidecar|adhoc"
8001
9038
  },
8002
9039
  {
8003
- file: path55.join("voltras-workspace", "sessions", "ARCHIVED-handoff-through-2026-07-15.md"),
9040
+ file: path63.join("voltras-workspace", "sessions", "ARCHIVED-handoff-through-2026-07-15.md"),
8004
9041
  kind: "relocate",
8005
- target: path55.join("voltras-workspace", "sources", "ARCHIVED-handoff-through-2026-07-15.md"),
9042
+ target: path63.join("voltras-workspace", "sources", "ARCHIVED-handoff-through-2026-07-15.md"),
8006
9043
  why: "not a session file \u2014 a hand-archived handoff parked in sessions/"
8007
9044
  }
8008
9045
  ];
@@ -8016,11 +9053,11 @@ var BRIEF_REPAIRS = {
8016
9053
  };
8017
9054
  }
8018
9055
  };
8019
- function repairBriefFrontmatter(slug, frontmatter) {
8020
- const repair = BRIEF_REPAIRS[slug]?.(frontmatter);
8021
- if (repair == null) return { frontmatter, applied: [] };
9056
+ function repairBriefFrontmatter(slug, frontmatter2) {
9057
+ const repair = BRIEF_REPAIRS[slug]?.(frontmatter2);
9058
+ if (repair == null) return { frontmatter: frontmatter2, applied: [] };
8022
9059
  return {
8023
- frontmatter: { ...frontmatter, ...repair.patch },
9060
+ frontmatter: { ...frontmatter2, ...repair.patch },
8024
9061
  applied: [repair.detail]
8025
9062
  };
8026
9063
  }
@@ -8050,9 +9087,9 @@ async function planRetrack(fullPath, repair) {
8050
9087
  repaired: { frontmatter: parsed.data, body: raw.body }
8051
9088
  };
8052
9089
  }
8053
- async function exists2(p) {
9090
+ async function exists3(p) {
8054
9091
  try {
8055
- await fs44.access(p);
9092
+ await fs52.access(p);
8056
9093
  return true;
8057
9094
  } catch {
8058
9095
  return false;
@@ -8061,8 +9098,8 @@ async function exists2(p) {
8061
9098
  async function planRepairs(activeRoot) {
8062
9099
  const plans = [];
8063
9100
  for (const repair of KNOWN_REPAIRS) {
8064
- const fullPath = path55.join(activeRoot, repair.file);
8065
- if (!await exists2(fullPath)) {
9101
+ const fullPath = path63.join(activeRoot, repair.file);
9102
+ if (!await exists3(fullPath)) {
8066
9103
  plans.push({ action: "skip", file: repair.file, detail: "already absent" });
8067
9104
  continue;
8068
9105
  }
@@ -8071,7 +9108,7 @@ async function planRepairs(activeRoot) {
8071
9108
  continue;
8072
9109
  }
8073
9110
  const target = repair.target;
8074
- if (await exists2(path55.join(activeRoot, target))) {
9111
+ if (await exists3(path63.join(activeRoot, target))) {
8075
9112
  plans.push({
8076
9113
  action: "skip",
8077
9114
  file: repair.file,
@@ -8085,7 +9122,7 @@ async function planRepairs(activeRoot) {
8085
9122
  return plans;
8086
9123
  }
8087
9124
  async function applyRepair(activeRoot, plan) {
8088
- const fullPath = path55.join(activeRoot, plan.file);
9125
+ const fullPath = path63.join(activeRoot, plan.file);
8089
9126
  if (plan.action === "retrack" && plan.repaired !== void 0) {
8090
9127
  await writeFrontmatter(
8091
9128
  fullPath,
@@ -8096,34 +9133,34 @@ async function applyRepair(activeRoot, plan) {
8096
9133
  return;
8097
9134
  }
8098
9135
  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);
9136
+ const target = path63.join(activeRoot, plan.target);
9137
+ await fs52.mkdir(path63.dirname(target), { recursive: true });
9138
+ await fs52.rename(fullPath, target);
8102
9139
  }
8103
9140
  }
8104
9141
 
8105
9142
  // src/migrations/v2-to-v3-open-loops.ts
8106
9143
  var HANDOFF_FILE = "handoff.md";
8107
- var HANDOFF_ARCHIVE = path56.join("sources", "handoff-archive.md");
9144
+ var HANDOFF_ARCHIVE = path64.join("sources", "handoff-archive.md");
8108
9145
  async function pathExists(p) {
8109
9146
  try {
8110
- await fs45.access(p);
9147
+ await fs53.access(p);
8111
9148
  return true;
8112
9149
  } catch {
8113
9150
  return false;
8114
9151
  }
8115
9152
  }
8116
- async function listInitiativeSlugs2(activeRoot) {
9153
+ async function listInitiativeSlugs3(activeRoot) {
8117
9154
  let entries;
8118
9155
  try {
8119
- entries = await fs45.readdir(activeRoot, { withFileTypes: true });
9156
+ entries = await fs53.readdir(activeRoot, { withFileTypes: true });
8120
9157
  } catch {
8121
9158
  return [];
8122
9159
  }
8123
9160
  const slugs = [];
8124
9161
  for (const entry of entries) {
8125
9162
  if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
8126
- if (await pathExists(path56.join(activeRoot, entry.name, "brief.md"))) {
9163
+ if (await pathExists(path64.join(activeRoot, entry.name, "brief.md"))) {
8127
9164
  slugs.push(entry.name);
8128
9165
  }
8129
9166
  }
@@ -8131,12 +9168,12 @@ async function listInitiativeSlugs2(activeRoot) {
8131
9168
  }
8132
9169
  async function maxOnDiskTaskNumber2(initiativeDir, prefix) {
8133
9170
  const re = new RegExp(`^${prefix}-(\\d+)\\.yml$`);
8134
- const tasksDir = path56.join(initiativeDir, "tasks");
9171
+ const tasksDir = path64.join(initiativeDir, "tasks");
8135
9172
  let max = 0;
8136
- for (const dir of [tasksDir, path56.join(tasksDir, "archive")]) {
9173
+ for (const dir of [tasksDir, path64.join(tasksDir, "archive")]) {
8137
9174
  let names;
8138
9175
  try {
8139
- names = await fs45.readdir(dir);
9176
+ names = await fs53.readdir(dir);
8140
9177
  } catch {
8141
9178
  continue;
8142
9179
  }
@@ -8147,15 +9184,15 @@ async function maxOnDiskTaskNumber2(initiativeDir, prefix) {
8147
9184
  }
8148
9185
  return max;
8149
9186
  }
8150
- async function nextTaskSeq(initiativeDir, frontmatter) {
8151
- if (frontmatter.task_seq !== void 0) return null;
8152
- const prefix = frontmatter.task_prefix;
9187
+ async function nextTaskSeq(initiativeDir, frontmatter2) {
9188
+ if (frontmatter2.task_seq !== void 0) return null;
9189
+ const prefix = frontmatter2.task_prefix;
8153
9190
  if (typeof prefix !== "string" || prefix.length === 0) return null;
8154
9191
  const max = await maxOnDiskTaskNumber2(initiativeDir, prefix);
8155
9192
  return max > 0 ? max : null;
8156
9193
  }
8157
9194
  async function planBrief(initiativeDir, slug) {
8158
- const raw = await readRawFrontmatter(path56.join(initiativeDir, "brief.md"));
9195
+ const raw = await readRawFrontmatter(path64.join(initiativeDir, "brief.md"));
8159
9196
  const repaired = repairBriefFrontmatter(slug, raw.frontmatter);
8160
9197
  const taskSeq = await nextTaskSeq(initiativeDir, repaired.frontmatter);
8161
9198
  if (taskSeq === null && repaired.applied.length === 0) return { write: null };
@@ -8172,12 +9209,12 @@ async function planBrief(initiativeDir, slug) {
8172
9209
  };
8173
9210
  }
8174
9211
  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";
9212
+ if (!await pathExists(path64.join(initiativeDir, HANDOFF_FILE))) return "absent";
9213
+ if (await pathExists(path64.join(initiativeDir, HANDOFF_ARCHIVE))) return "archive-exists";
8177
9214
  return "archive-and-remove";
8178
9215
  }
8179
9216
  function buildOpenSession(entry) {
8180
- const frontmatter = SessionFrontmatterSchema.parse({
9217
+ const frontmatter2 = SessionFrontmatterSchema.parse({
8181
9218
  session_id: entry.session_id,
8182
9219
  // A synthetic session is an instant, not an interval: it stands for a
8183
9220
  // state read off the handoff, so `started === ended`.
@@ -8189,11 +9226,11 @@ function buildOpenSession(entry) {
8189
9226
  resolves: []
8190
9227
  });
8191
9228
  const stem = buildSessionStem(entry.ended, entry.session_id);
8192
- return { kind: "open", stem, frontmatter, body: buildOpenBody(entry, stem) };
9229
+ return { kind: "open", stem, frontmatter: frontmatter2, body: buildOpenBody(entry, stem) };
8193
9230
  }
8194
9231
  function buildAbandonSession(entry, openStem, abandonedAt) {
8195
9232
  const sessionId = `${entry.session_id}-abandonment`;
8196
- const frontmatter = SessionFrontmatterSchema.parse({
9233
+ const frontmatter2 = SessionFrontmatterSchema.parse({
8197
9234
  session_id: sessionId,
8198
9235
  started: abandonedAt,
8199
9236
  ended: abandonedAt,
@@ -8209,7 +9246,7 @@ function buildAbandonSession(entry, openStem, abandonedAt) {
8209
9246
  return {
8210
9247
  kind: "abandon",
8211
9248
  stem,
8212
- frontmatter,
9249
+ frontmatter: frontmatter2,
8213
9250
  body: buildAbandonBody(entry, openStem, stem)
8214
9251
  };
8215
9252
  }
@@ -8263,7 +9300,7 @@ function draftSessions(entry, abandonedAt) {
8263
9300
  return [open, buildAbandonSession(entry, open.stem, abandonedAt)];
8264
9301
  }
8265
9302
  async function planInitiative(activeRoot, slug, entry, abandonedAt) {
8266
- const initiativeDir = path56.join(activeRoot, slug);
9303
+ const initiativeDir = path64.join(activeRoot, slug);
8267
9304
  const brief = await planBrief(initiativeDir, slug);
8268
9305
  const base = {
8269
9306
  slug,
@@ -8290,7 +9327,7 @@ function assertProposalSlugsExist(proposal, known) {
8290
9327
  }
8291
9328
  async function planV2ToV3(activeRoot) {
8292
9329
  const { proposal, origin } = await loadProposal();
8293
- const slugs = await listInitiativeSlugs2(activeRoot);
9330
+ const slugs = await listInitiativeSlugs3(activeRoot);
8294
9331
  assertProposalSlugsExist(proposal, slugs);
8295
9332
  const byslug = new Map(proposal.initiatives.map((i) => [i.slug, i]));
8296
9333
  const initiatives = [];
@@ -8302,11 +9339,11 @@ async function planV2ToV3(activeRoot) {
8302
9339
  return { proposalOrigin: origin, initiatives, repairs: await planRepairs(activeRoot) };
8303
9340
  }
8304
9341
  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);
9342
+ const source = path64.join(initiativeDir, HANDOFF_FILE);
9343
+ const target = path64.join(initiativeDir, HANDOFF_ARCHIVE);
9344
+ await fs53.mkdir(path64.dirname(target), { recursive: true });
9345
+ await fs53.copyFile(source, target);
9346
+ await fs53.rm(source);
8310
9347
  }
8311
9348
  async function writePlannedSession(activeRoot, slug, session) {
8312
9349
  const fm = session.frontmatter;
@@ -8323,15 +9360,15 @@ async function writePlannedSession(activeRoot, slug, session) {
8323
9360
  });
8324
9361
  }
8325
9362
  async function applyInitiative(activeRoot, plan) {
8326
- const initiativeDir = path56.join(activeRoot, plan.slug);
8327
- await withFileLock(path56.join(initiativeDir, ".lock"), async () => {
9363
+ const initiativeDir = path64.join(activeRoot, plan.slug);
9364
+ await withFileLock(path64.join(initiativeDir, ".lock"), async () => {
8328
9365
  for (const session of plan.sessions) {
8329
9366
  if (session.exists) continue;
8330
9367
  await writePlannedSession(activeRoot, plan.slug, session);
8331
9368
  }
8332
9369
  if (plan.brief !== null) {
8333
9370
  await writeFrontmatter(
8334
- path56.join(initiativeDir, "brief.md"),
9371
+ path64.join(initiativeDir, "brief.md"),
8335
9372
  plan.brief.frontmatter,
8336
9373
  plan.brief.body,
8337
9374
  BriefFrontmatterSchema
@@ -8361,13 +9398,13 @@ var v2ToV3OpenLoops = {
8361
9398
  };
8362
9399
 
8363
9400
  // 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";
9401
+ import { promises as fs54 } from "fs";
9402
+ import path65 from "path";
9403
+ import matter5 from "gray-matter";
9404
+ import YAML5 from "yaml";
8368
9405
  function normalize(value) {
8369
- const expanded = value.startsWith("~") ? path57.join(process.env.HOME ?? "", value.slice(1)) : value;
8370
- return path57.resolve(expanded);
9406
+ const expanded = value.startsWith("~") ? path65.join(process.env.HOME ?? "", value.slice(1)) : value;
9407
+ return path65.resolve(expanded);
8371
9408
  }
8372
9409
  function toEntries(raw) {
8373
9410
  if (!raw || typeof raw !== "object" || Array.isArray(raw)) return [];
@@ -8403,7 +9440,7 @@ function mergeWorktrees(existing, incoming) {
8403
9440
  }
8404
9441
  async function readArtifacts2(file) {
8405
9442
  try {
8406
- const raw = YAML2.parse(await fs46.readFile(file, "utf8"));
9443
+ const raw = YAML5.parse(await fs54.readFile(file, "utf8"));
8407
9444
  return {
8408
9445
  branches: Array.isArray(raw?.branches) ? raw.branches : [],
8409
9446
  stashes: Array.isArray(raw?.stashes) ? raw.stashes : [],
@@ -8414,20 +9451,20 @@ async function readArtifacts2(file) {
8414
9451
  }
8415
9452
  }
8416
9453
  async function migrateOne2(initiativeDir) {
8417
- const briefPath = path57.join(initiativeDir, "brief.md");
9454
+ const briefPath = path65.join(initiativeDir, "brief.md");
8418
9455
  let raw;
8419
9456
  try {
8420
- raw = await fs46.readFile(briefPath, "utf8");
9457
+ raw = await fs54.readFile(briefPath, "utf8");
8421
9458
  } catch {
8422
9459
  return 0;
8423
9460
  }
8424
- const parsed = matter3(raw);
9461
+ const parsed = matter5(raw);
8425
9462
  const data = parsed.data;
8426
9463
  if (!("worktrees" in data)) return 0;
8427
9464
  const incoming = toEntries(data.worktrees);
8428
9465
  delete data.worktrees;
8429
9466
  if (incoming.length > 0) {
8430
- const artifactsPath2 = path57.join(initiativeDir, "artifacts.yml");
9467
+ const artifactsPath2 = path65.join(initiativeDir, "artifacts.yml");
8431
9468
  const current = await readArtifacts2(artifactsPath2);
8432
9469
  await writeYaml(
8433
9470
  artifactsPath2,
@@ -8439,22 +9476,22 @@ async function migrateOne2(initiativeDir) {
8439
9476
  ArtifactsSchema
8440
9477
  );
8441
9478
  }
8442
- await atomicWrite(briefPath, matter3.stringify(parsed.content, data));
9479
+ await atomicWrite(briefPath, matter5.stringify(parsed.content, data));
8443
9480
  return incoming.length;
8444
9481
  }
8445
9482
  async function initiativeDirs(activeRoot) {
8446
9483
  const out = [];
8447
9484
  let entries;
8448
9485
  try {
8449
- entries = await fs46.readdir(activeRoot, { withFileTypes: true });
9486
+ entries = await fs54.readdir(activeRoot, { withFileTypes: true });
8450
9487
  } catch {
8451
9488
  return out;
8452
9489
  }
8453
9490
  for (const entry of entries) {
8454
9491
  if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
8455
- const dir = path57.join(activeRoot, entry.name);
9492
+ const dir = path65.join(activeRoot, entry.name);
8456
9493
  try {
8457
- await fs46.access(path57.join(dir, "brief.md"));
9494
+ await fs54.access(path65.join(dir, "brief.md"));
8458
9495
  out.push(dir);
8459
9496
  } catch {
8460
9497
  }
@@ -8469,12 +9506,12 @@ var v3ToV4Worktrees = {
8469
9506
  const moved = [];
8470
9507
  for (const dir of await initiativeDirs(activeRoot)) {
8471
9508
  const count = await migrateOne2(dir);
8472
- if (count > 0) moved.push(`${path57.basename(dir)} ${count} worktree(s)`);
9509
+ if (count > 0) moved.push(`${path65.basename(dir)} ${count} worktree(s)`);
8473
9510
  }
8474
9511
  if (moved.length === 0) return;
8475
9512
  const stamp = (/* @__PURE__ */ new Date()).toISOString();
8476
- await fs46.appendFile(
8477
- path57.join(activeRoot, ".migrations.log"),
9513
+ await fs54.appendFile(
9514
+ path65.join(activeRoot, ".migrations.log"),
8478
9515
  moved.map((line) => `${stamp} v3->v4 ${line}
8479
9516
  `).join(""),
8480
9517
  "utf8"
@@ -8489,7 +9526,7 @@ var CURRENT_VERSION = targetVersion(MIGRATIONS2);
8489
9526
  function targetVersion(migrations) {
8490
9527
  return migrations.reduce((highest, m) => Math.max(highest, m.to), BASE_VERSION);
8491
9528
  }
8492
- async function runMigrations(activeRoot, fromVersion, migrations = MIGRATIONS2) {
9529
+ async function runMigrations2(activeRoot, fromVersion, migrations = MIGRATIONS2) {
8493
9530
  if (fromVersion === CURRENT_VERSION) {
8494
9531
  return { ran: [] };
8495
9532
  }
@@ -8527,10 +9564,10 @@ var SCHEMA_VERSION_FILENAME = ".schema-version";
8527
9564
  var schemaVersionPath = (activeRoot) => join(activeRoot, SCHEMA_VERSION_FILENAME);
8528
9565
  var isNodeErrnoException = (err) => typeof err === "object" && err !== null && "code" in err;
8529
9566
  async function readSchemaVersion(activeRoot) {
8530
- const path65 = schemaVersionPath(activeRoot);
9567
+ const path67 = schemaVersionPath(activeRoot);
8531
9568
  let raw;
8532
9569
  try {
8533
- raw = await readFile(path65, "utf8");
9570
+ raw = await readFile(path67, "utf8");
8534
9571
  } catch (err) {
8535
9572
  if (isNodeErrnoException(err) && err.code === "ENOENT") {
8536
9573
  return 0;
@@ -8540,13 +9577,13 @@ async function readSchemaVersion(activeRoot) {
8540
9577
  const trimmed = raw.trim();
8541
9578
  if (trimmed === "" || !/^\d+$/.test(trimmed)) {
8542
9579
  throw new Error(
8543
- `Invalid schema version in ${path65}: expected a positive integer, got ${JSON.stringify(raw)}`
9580
+ `Invalid schema version in ${path67}: expected a positive integer, got ${JSON.stringify(raw)}`
8544
9581
  );
8545
9582
  }
8546
9583
  const parsed = Number(trimmed);
8547
9584
  if (!Number.isInteger(parsed) || parsed <= 0) {
8548
9585
  throw new Error(
8549
- `Invalid schema version in ${path65}: expected a positive integer, got ${JSON.stringify(raw)}`
9586
+ `Invalid schema version in ${path67}: expected a positive integer, got ${JSON.stringify(raw)}`
8550
9587
  );
8551
9588
  }
8552
9589
  return parsed;
@@ -8559,10 +9596,10 @@ async function writeSchemaVersion(activeRoot, version) {
8559
9596
  `, "utf8");
8560
9597
  }
8561
9598
  async function readRawSchemaVersion(activeRoot) {
8562
- const path65 = schemaVersionPath(activeRoot);
9599
+ const path67 = schemaVersionPath(activeRoot);
8563
9600
  let raw;
8564
9601
  try {
8565
- raw = await readFile(path65, "utf8");
9602
+ raw = await readFile(path67, "utf8");
8566
9603
  } catch (err) {
8567
9604
  if (isNodeErrnoException(err) && err.code === "ENOENT") {
8568
9605
  return { present: false };
@@ -8572,13 +9609,13 @@ async function readRawSchemaVersion(activeRoot) {
8572
9609
  const trimmed = raw.trim();
8573
9610
  if (trimmed === "" || !/^\d+$/.test(trimmed)) {
8574
9611
  throw new Error(
8575
- `Invalid schema version in ${path65}: expected a non-negative integer, got ${JSON.stringify(raw)}`
9612
+ `Invalid schema version in ${path67}: expected a non-negative integer, got ${JSON.stringify(raw)}`
8576
9613
  );
8577
9614
  }
8578
9615
  const parsed = Number(trimmed);
8579
9616
  if (!Number.isInteger(parsed) || parsed < 0) {
8580
9617
  throw new Error(
8581
- `Invalid schema version in ${path65}: expected a non-negative integer, got ${JSON.stringify(raw)}`
9618
+ `Invalid schema version in ${path67}: expected a non-negative integer, got ${JSON.stringify(raw)}`
8582
9619
  );
8583
9620
  }
8584
9621
  return { present: true, version: parsed };
@@ -8598,7 +9635,7 @@ async function ensureSchemaVersion(activeRoot) {
8598
9635
  if (before === CURRENT_VERSION) {
8599
9636
  return { before, after: before, migrated: false, ran: [] };
8600
9637
  }
8601
- const { ran } = await runMigrations(activeRoot, before);
9638
+ const { ran } = await runMigrations2(activeRoot, before);
8602
9639
  await writeSchemaVersion(activeRoot, CURRENT_VERSION);
8603
9640
  return {
8604
9641
  before,
@@ -8619,7 +9656,7 @@ function isLinux(platform = process.platform) {
8619
9656
  return platform === "linux";
8620
9657
  }
8621
9658
  function resolveLocalDeps(deps) {
8622
- const fs53 = deps.fs ?? fsp;
9659
+ const fs57 = deps.fs ?? fsp;
8623
9660
  const spawn6 = deps.spawn ?? nodeSpawn;
8624
9661
  const homeDir = deps.paths?.homeDir ?? os3.homedir();
8625
9662
  const paths = deps.paths ?? {
@@ -8629,7 +9666,7 @@ function resolveLocalDeps(deps) {
8629
9666
  homeDir
8630
9667
  };
8631
9668
  const cliEntry = deps.cliEntry ?? process.argv[1] ?? "active-work";
8632
- return { fs: fs53, spawn: spawn6, paths, cliEntry, platform: process.platform };
9669
+ return { fs: fs57, spawn: spawn6, paths, cliEntry, platform: process.platform };
8633
9670
  }
8634
9671
  function getUnitDir(homeDir) {
8635
9672
  return nodePath.join(homeDir, ".config", "systemd", "user");
@@ -8700,7 +9737,7 @@ async function isUnitActive(deps = {}) {
8700
9737
  return result.code === 0;
8701
9738
  }
8702
9739
  async function stepInstallSupervision(deps = {}, opts = {}) {
8703
- const { fs: fs53, spawn: spawn6, paths, cliEntry, platform } = resolveLocalDeps(deps);
9740
+ const { fs: fs57, spawn: spawn6, paths, cliEntry, platform } = resolveLocalDeps(deps);
8704
9741
  if (!isLinux(platform)) {
8705
9742
  return {
8706
9743
  ok: true,
@@ -8713,16 +9750,16 @@ async function stepInstallSupervision(deps = {}, opts = {}) {
8713
9750
  const unitPath = getUnitPath(paths.homeDir);
8714
9751
  const desired = renderUnit({ cliEntry, port: opts.port });
8715
9752
  try {
8716
- await fs53.mkdir(unitDir, { recursive: true });
9753
+ await fs57.mkdir(unitDir, { recursive: true });
8717
9754
  let existing = null;
8718
9755
  try {
8719
- existing = await fs53.readFile(unitPath, "utf8");
9756
+ existing = await fs57.readFile(unitPath, "utf8");
8720
9757
  } catch (err) {
8721
9758
  if (err.code !== "ENOENT") throw err;
8722
9759
  }
8723
9760
  const unchanged = existing === desired;
8724
9761
  if (!unchanged) {
8725
- await fs53.writeFile(unitPath, desired, "utf8");
9762
+ await fs57.writeFile(unitPath, desired, "utf8");
8726
9763
  }
8727
9764
  const reload = await runOnce(spawn6, "systemctl", ["--user", "daemon-reload"]);
8728
9765
  if (reload.spawnError) {
@@ -8784,7 +9821,7 @@ async function stepInstallSupervision(deps = {}, opts = {}) {
8784
9821
  }
8785
9822
  }
8786
9823
  async function uninstallSupervision(deps = {}) {
8787
- const { fs: fs53, spawn: spawn6, paths, platform } = resolveLocalDeps(deps);
9824
+ const { fs: fs57, spawn: spawn6, paths, platform } = resolveLocalDeps(deps);
8788
9825
  if (!isLinux(platform)) {
8789
9826
  return {
8790
9827
  ok: true,
@@ -8797,7 +9834,7 @@ async function uninstallSupervision(deps = {}) {
8797
9834
  try {
8798
9835
  let present = true;
8799
9836
  try {
8800
- await fs53.stat(unitPath);
9837
+ await fs57.stat(unitPath);
8801
9838
  } catch (err) {
8802
9839
  if (err.code === "ENOENT") present = false;
8803
9840
  else throw err;
@@ -8812,7 +9849,7 @@ async function uninstallSupervision(deps = {}) {
8812
9849
  }
8813
9850
  const disable = await runOnce(spawn6, "systemctl", ["--user", "disable", "--now", UNIT_NAME]);
8814
9851
  if (disable.spawnError) {
8815
- await fs53.rm(unitPath, { force: true });
9852
+ await fs57.rm(unitPath, { force: true });
8816
9853
  return {
8817
9854
  ok: true,
8818
9855
  name: STEP_SUPERVISION,
@@ -8820,7 +9857,7 @@ async function uninstallSupervision(deps = {}) {
8820
9857
  message: `Removed ${unitPath} (systemctl unavailable: ${disable.spawnError.message})`
8821
9858
  };
8822
9859
  }
8823
- await fs53.rm(unitPath, { force: true });
9860
+ await fs57.rm(unitPath, { force: true });
8824
9861
  const reload = await runOnce(spawn6, "systemctl", ["--user", "daemon-reload"]);
8825
9862
  if (reload.code !== 0 && !reload.spawnError) {
8826
9863
  return {
@@ -8856,7 +9893,7 @@ function isDarwin(platform = process.platform) {
8856
9893
  return platform === "darwin";
8857
9894
  }
8858
9895
  function resolveLocalDeps2(deps) {
8859
- const fs53 = deps.fs ?? fsp2;
9896
+ const fs57 = deps.fs ?? fsp2;
8860
9897
  const spawn6 = deps.spawn ?? nodeSpawn2;
8861
9898
  const homeDir = deps.paths?.homeDir ?? os4.homedir();
8862
9899
  const paths = deps.paths ?? {
@@ -8867,7 +9904,7 @@ function resolveLocalDeps2(deps) {
8867
9904
  };
8868
9905
  const cliEntry = deps.cliEntry ?? process.argv[1] ?? "active-work";
8869
9906
  const uid = process.getuid?.() ?? 0;
8870
- return { fs: fs53, spawn: spawn6, paths, cliEntry, uid, platform: process.platform };
9907
+ return { fs: fs57, spawn: spawn6, paths, cliEntry, uid, platform: process.platform };
8871
9908
  }
8872
9909
  function getAgentDir(homeDir) {
8873
9910
  return nodePath2.join(homeDir, "Library", "LaunchAgents");
@@ -8955,7 +9992,7 @@ async function isAgentLoaded(deps = {}) {
8955
9992
  return result.code === 0;
8956
9993
  }
8957
9994
  async function installLaunchAgent(deps = {}, opts = {}) {
8958
- const { fs: fs53, spawn: spawn6, paths, cliEntry, uid, platform } = resolveLocalDeps2(deps);
9995
+ const { fs: fs57, spawn: spawn6, paths, cliEntry, uid, platform } = resolveLocalDeps2(deps);
8959
9996
  if (!isDarwin(platform)) {
8960
9997
  return {
8961
9998
  ok: true,
@@ -8967,11 +10004,11 @@ async function installLaunchAgent(deps = {}, opts = {}) {
8967
10004
  const plistPath = getPlistPath(paths.homeDir);
8968
10005
  const desired = renderPlist({ cliEntry, homeDir: paths.homeDir, port: opts.port });
8969
10006
  try {
8970
- await fs53.mkdir(getAgentDir(paths.homeDir), { recursive: true });
8971
- await fs53.mkdir(getLogDir(paths.homeDir), { recursive: true });
10007
+ await fs57.mkdir(getAgentDir(paths.homeDir), { recursive: true });
10008
+ await fs57.mkdir(getLogDir(paths.homeDir), { recursive: true });
8972
10009
  let existing = null;
8973
10010
  try {
8974
- existing = await fs53.readFile(plistPath, "utf8");
10011
+ existing = await fs57.readFile(plistPath, "utf8");
8975
10012
  } catch (err) {
8976
10013
  if (err.code !== "ENOENT") throw err;
8977
10014
  }
@@ -8985,7 +10022,7 @@ async function installLaunchAgent(deps = {}, opts = {}) {
8985
10022
  };
8986
10023
  }
8987
10024
  if (!unchanged) {
8988
- await fs53.writeFile(plistPath, desired, "utf8");
10025
+ await fs57.writeFile(plistPath, desired, "utf8");
8989
10026
  }
8990
10027
  const bootout = await runOnce2(spawn6, "launchctl", ["bootout", serviceTarget(uid)]);
8991
10028
  if (bootout.spawnError) {
@@ -9026,7 +10063,7 @@ async function installLaunchAgent(deps = {}, opts = {}) {
9026
10063
  }
9027
10064
  }
9028
10065
  async function uninstallLaunchAgent(deps = {}) {
9029
- const { fs: fs53, spawn: spawn6, paths, uid, platform } = resolveLocalDeps2(deps);
10066
+ const { fs: fs57, spawn: spawn6, paths, uid, platform } = resolveLocalDeps2(deps);
9030
10067
  if (!isDarwin(platform)) {
9031
10068
  return {
9032
10069
  ok: true,
@@ -9039,7 +10076,7 @@ async function uninstallLaunchAgent(deps = {}) {
9039
10076
  try {
9040
10077
  let present = true;
9041
10078
  try {
9042
- await fs53.stat(plistPath);
10079
+ await fs57.stat(plistPath);
9043
10080
  } catch (err) {
9044
10081
  if (err.code === "ENOENT") present = false;
9045
10082
  else throw err;
@@ -9053,7 +10090,7 @@ async function uninstallLaunchAgent(deps = {}) {
9053
10090
  };
9054
10091
  }
9055
10092
  const bootout = await runOnce2(spawn6, "launchctl", ["bootout", serviceTarget(uid)]);
9056
- await fs53.rm(plistPath, { force: true });
10093
+ await fs57.rm(plistPath, { force: true });
9057
10094
  if (bootout.spawnError) {
9058
10095
  return {
9059
10096
  ok: true,
@@ -9114,7 +10151,7 @@ function findRepoRoot() {
9114
10151
  return nodePath3.resolve(nodePath3.dirname(fileURLToPath2(import.meta.url)), "..", "..");
9115
10152
  }
9116
10153
  function resolveDeps(deps) {
9117
- const fs53 = deps.fs ?? fsp3;
10154
+ const fs57 = deps.fs ?? fsp3;
9118
10155
  const spawn6 = deps.spawn ?? nodeSpawn3;
9119
10156
  const prompts = deps.prompts ?? clackPrompts;
9120
10157
  const homeDir = deps.paths?.homeDir ?? os5.homedir();
@@ -9127,7 +10164,7 @@ function resolveDeps(deps) {
9127
10164
  const repoRoot = deps.repoRoot ?? findRepoRoot();
9128
10165
  const cliEntry = deps.cliEntry ?? process.argv[1] ?? "active-work";
9129
10166
  return {
9130
- fs: fs53,
10167
+ fs: fs57,
9131
10168
  spawn: spawn6,
9132
10169
  prompts,
9133
10170
  paths,
@@ -9170,24 +10207,24 @@ async function stepCheckNode(deps = {}) {
9170
10207
  message: `Node v${process.versions.node} OK`
9171
10208
  };
9172
10209
  }
9173
- async function ensureDir(fs53, dir) {
10210
+ async function ensureDir(fs57, dir) {
9174
10211
  try {
9175
- const stat = await fs53.stat(dir);
10212
+ const stat = await fs57.stat(dir);
9176
10213
  if (stat.isDirectory()) return { created: false };
9177
10214
  throw new Error(`${dir} exists but is not a directory`);
9178
10215
  } catch (err) {
9179
10216
  const code = err.code;
9180
10217
  if (code !== "ENOENT") throw err;
9181
10218
  }
9182
- await fs53.mkdir(dir, { recursive: true });
10219
+ await fs57.mkdir(dir, { recursive: true });
9183
10220
  return { created: true };
9184
10221
  }
9185
10222
  async function stepCreateActiveRoot(deps = {}) {
9186
- const { fs: fs53, paths } = resolveDeps(deps);
10223
+ const { fs: fs57, paths } = resolveDeps(deps);
9187
10224
  try {
9188
10225
  const created = [];
9189
10226
  for (const dir of [paths.activeRoot, paths.stateRoot, paths.configRoot]) {
9190
- const { created: didCreate } = await ensureDir(fs53, dir);
10227
+ const { created: didCreate } = await ensureDir(fs57, dir);
9191
10228
  if (didCreate) created.push(dir);
9192
10229
  }
9193
10230
  const message = created.length === 0 ? `Active/state/config dirs already present` : `Created ${created.length} dir(s)`;
@@ -9236,18 +10273,18 @@ var CONFIG_STUB = {
9236
10273
  channels: ["plugin:agent-chat@agent-chat-local"]
9237
10274
  };
9238
10275
  async function stepWriteConfigStub(deps = {}) {
9239
- const { fs: fs53, paths, update } = resolveDeps(deps);
10276
+ const { fs: fs57, paths, update } = resolveDeps(deps);
9240
10277
  const configPath = nodePath3.join(paths.configRoot, "config.json");
9241
10278
  try {
9242
- await ensureDir(fs53, paths.configRoot);
9243
- let exists3 = false;
10279
+ await ensureDir(fs57, paths.configRoot);
10280
+ let exists4 = false;
9244
10281
  try {
9245
- await fs53.stat(configPath);
9246
- exists3 = true;
10282
+ await fs57.stat(configPath);
10283
+ exists4 = true;
9247
10284
  } catch (err) {
9248
10285
  if (err.code !== "ENOENT") throw err;
9249
10286
  }
9250
- if (exists3 && !update) {
10287
+ if (exists4 && !update) {
9251
10288
  return {
9252
10289
  ok: true,
9253
10290
  name: STEP_CONFIG,
@@ -9255,7 +10292,7 @@ async function stepWriteConfigStub(deps = {}) {
9255
10292
  message: `Config exists at ${configPath} (left untouched)`
9256
10293
  };
9257
10294
  }
9258
- await fs53.writeFile(configPath, JSON.stringify(CONFIG_STUB, null, 2) + "\n", "utf8");
10295
+ await fs57.writeFile(configPath, JSON.stringify(CONFIG_STUB, null, 2) + "\n", "utf8");
9259
10296
  return {
9260
10297
  ok: true,
9261
10298
  name: STEP_CONFIG,
@@ -9270,40 +10307,40 @@ async function stepWriteConfigStub(deps = {}) {
9270
10307
  };
9271
10308
  }
9272
10309
  }
9273
- async function pathExists2(fs53, p) {
10310
+ async function pathExists2(fs57, p) {
9274
10311
  try {
9275
- await fs53.stat(p);
10312
+ await fs57.stat(p);
9276
10313
  return true;
9277
10314
  } catch (err) {
9278
10315
  if (err.code === "ENOENT") return false;
9279
10316
  throw err;
9280
10317
  }
9281
10318
  }
9282
- async function copyTree(fs53, src, dest) {
9283
- const cp = fs53.cp;
10319
+ async function copyTree(fs57, src, dest) {
10320
+ const cp = fs57.cp;
9284
10321
  if (cp) {
9285
10322
  await cp(src, dest, { recursive: true });
9286
10323
  return;
9287
10324
  }
9288
- await fs53.mkdir(dest, { recursive: true });
9289
- const entries = await fs53.readdir(src, { withFileTypes: true });
10325
+ await fs57.mkdir(dest, { recursive: true });
10326
+ const entries = await fs57.readdir(src, { withFileTypes: true });
9290
10327
  for (const entry of entries) {
9291
10328
  const from = nodePath3.join(src, entry.name);
9292
10329
  const to = nodePath3.join(dest, entry.name);
9293
10330
  if (entry.isDirectory()) {
9294
- await copyTree(fs53, from, to);
10331
+ await copyTree(fs57, from, to);
9295
10332
  } else {
9296
- await fs53.copyFile(from, to);
10333
+ await fs57.copyFile(from, to);
9297
10334
  }
9298
10335
  }
9299
10336
  }
9300
10337
  async function stepInstallSkill(deps = {}) {
9301
- const { fs: fs53, paths, repoRoot } = resolveDeps(deps);
10338
+ const { fs: fs57, paths, repoRoot } = resolveDeps(deps);
9302
10339
  const targetDir = nodePath3.join(paths.homeDir, ".claude", "skills", "active-work");
9303
10340
  const targetMarker = nodePath3.join(targetDir, "SKILL.md");
9304
10341
  const sourceDir = nodePath3.join(repoRoot, "skill");
9305
10342
  try {
9306
- if (await pathExists2(fs53, targetMarker)) {
10343
+ if (await pathExists2(fs57, targetMarker)) {
9307
10344
  return {
9308
10345
  ok: true,
9309
10346
  name: STEP_SKILL,
@@ -9311,7 +10348,7 @@ async function stepInstallSkill(deps = {}) {
9311
10348
  message: `Skill already installed at ${targetDir}`
9312
10349
  };
9313
10350
  }
9314
- if (!await pathExists2(fs53, sourceDir)) {
10351
+ if (!await pathExists2(fs57, sourceDir)) {
9315
10352
  return {
9316
10353
  ok: true,
9317
10354
  name: STEP_SKILL,
@@ -9319,8 +10356,8 @@ async function stepInstallSkill(deps = {}) {
9319
10356
  message: `Skill source not found at ${sourceDir}; skipping`
9320
10357
  };
9321
10358
  }
9322
- await fs53.mkdir(nodePath3.dirname(targetDir), { recursive: true });
9323
- await copyTree(fs53, sourceDir, targetDir);
10359
+ await fs57.mkdir(nodePath3.dirname(targetDir), { recursive: true });
10360
+ await copyTree(fs57, sourceDir, targetDir);
9324
10361
  return {
9325
10362
  ok: true,
9326
10363
  name: STEP_SKILL,
@@ -9336,12 +10373,12 @@ async function stepInstallSkill(deps = {}) {
9336
10373
  }
9337
10374
  }
9338
10375
  async function stepInstallCommand(deps = {}) {
9339
- const { fs: fs53, paths, repoRoot } = resolveDeps(deps);
10376
+ const { fs: fs57, paths, repoRoot } = resolveDeps(deps);
9340
10377
  const targetDir = nodePath3.join(paths.homeDir, ".claude", "commands");
9341
10378
  const target = nodePath3.join(targetDir, "aw-prompt.md");
9342
10379
  const source = nodePath3.join(repoRoot, "claude-commands", "aw-prompt.md");
9343
10380
  try {
9344
- if (!await pathExists2(fs53, source)) {
10381
+ if (!await pathExists2(fs57, source)) {
9345
10382
  return {
9346
10383
  ok: true,
9347
10384
  name: STEP_COMMAND,
@@ -9349,8 +10386,8 @@ async function stepInstallCommand(deps = {}) {
9349
10386
  message: `Command source not found at ${source}; skipping`
9350
10387
  };
9351
10388
  }
9352
- await fs53.mkdir(targetDir, { recursive: true });
9353
- await fs53.copyFile(source, target);
10389
+ await fs57.mkdir(targetDir, { recursive: true });
10390
+ await fs57.copyFile(source, target);
9354
10391
  return {
9355
10392
  ok: true,
9356
10393
  name: STEP_COMMAND,
@@ -9451,11 +10488,11 @@ function agentChatHooksPath(paths) {
9451
10488
  return nodePath3.join(home, "hooks.json");
9452
10489
  }
9453
10490
  async function stepRegisterAgentChatHooks(deps = {}) {
9454
- const { fs: fs53, paths } = resolveDeps(deps);
10491
+ const { fs: fs57, paths } = resolveDeps(deps);
9455
10492
  const hooksPath = agentChatHooksPath(paths);
9456
10493
  const agentChatHome = nodePath3.dirname(hooksPath);
9457
10494
  try {
9458
- await fs53.stat(agentChatHome);
10495
+ await fs57.stat(agentChatHome);
9459
10496
  } catch (err) {
9460
10497
  if (err.code === "ENOENT") {
9461
10498
  return {
@@ -9470,7 +10507,7 @@ async function stepRegisterAgentChatHooks(deps = {}) {
9470
10507
  try {
9471
10508
  let config = {};
9472
10509
  try {
9473
- const raw = await fs53.readFile(hooksPath, "utf8");
10510
+ const raw = await fs57.readFile(hooksPath, "utf8");
9474
10511
  const parsed = JSON.parse(raw);
9475
10512
  if (typeof parsed === "object" && parsed !== null) config = parsed;
9476
10513
  } catch (err) {
@@ -9499,7 +10536,7 @@ async function stepRegisterAgentChatHooks(deps = {}) {
9499
10536
  message: `Already registered in ${hooksPath}`
9500
10537
  };
9501
10538
  }
9502
- await fs53.writeFile(hooksPath, JSON.stringify(config, null, 2) + "\n", "utf8");
10539
+ await fs57.writeFile(hooksPath, JSON.stringify(config, null, 2) + "\n", "utf8");
9503
10540
  return {
9504
10541
  ok: true,
9505
10542
  name: STEP_AGENT_CHAT_HOOKS,
@@ -9673,10 +10710,10 @@ async function confirmStep(prompts, yes, message, initial = true) {
9673
10710
  return answer === true;
9674
10711
  }
9675
10712
  async function uninstallSkill(deps = {}) {
9676
- const { fs: fs53, paths } = resolveDeps(deps);
10713
+ const { fs: fs57, paths } = resolveDeps(deps);
9677
10714
  const target = nodePath3.join(paths.homeDir, ".claude", "skills", "active-work");
9678
10715
  try {
9679
- if (!await pathExists2(fs53, target)) {
10716
+ if (!await pathExists2(fs57, target)) {
9680
10717
  return {
9681
10718
  ok: true,
9682
10719
  name: STEP_SKILL,
@@ -9684,7 +10721,7 @@ async function uninstallSkill(deps = {}) {
9684
10721
  message: `Skill not present at ${target}`
9685
10722
  };
9686
10723
  }
9687
- await fs53.rm(target, { recursive: true, force: true });
10724
+ await fs57.rm(target, { recursive: true, force: true });
9688
10725
  return {
9689
10726
  ok: true,
9690
10727
  name: STEP_SKILL,
@@ -9696,10 +10733,10 @@ async function uninstallSkill(deps = {}) {
9696
10733
  }
9697
10734
  }
9698
10735
  async function uninstallCommand(deps = {}) {
9699
- const { fs: fs53, paths } = resolveDeps(deps);
10736
+ const { fs: fs57, paths } = resolveDeps(deps);
9700
10737
  const target = nodePath3.join(paths.homeDir, ".claude", "commands", "aw-prompt.md");
9701
10738
  try {
9702
- if (!await pathExists2(fs53, target)) {
10739
+ if (!await pathExists2(fs57, target)) {
9703
10740
  return {
9704
10741
  ok: true,
9705
10742
  name: STEP_COMMAND,
@@ -9707,7 +10744,7 @@ async function uninstallCommand(deps = {}) {
9707
10744
  message: `Command not present at ${target}`
9708
10745
  };
9709
10746
  }
9710
- await fs53.rm(target, { force: true });
10747
+ await fs57.rm(target, { force: true });
9711
10748
  return {
9712
10749
  ok: true,
9713
10750
  name: STEP_COMMAND,
@@ -9972,64 +11009,33 @@ import { promises as fsp4 } from "fs";
9972
11009
  import nodePath4 from "path";
9973
11010
  import os6 from "os";
9974
11011
 
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;
11012
+ // src/workspace-index/doctor.ts
11013
+ import { promises as fs55 } from "fs";
11014
+ var INDEXED_REFS = `
11015
+ SELECT initiative_ref AS ref, path FROM initiative
11016
+ UNION ALL SELECT note_ref, path FROM note
11017
+ UNION ALL SELECT task_ref, path FROM workspace_task
11018
+ UNION ALL SELECT session_ref, path FROM session_record
11019
+ UNION ALL SELECT source_ref, path FROM source
11020
+ ORDER BY path
11021
+ `;
11022
+ async function checkWorkspaceIndex(graph, activeRoot) {
11023
+ const indexed = graph.db.prepare(INDEXED_REFS).all();
11024
+ const results = await Promise.all(
11025
+ indexed.map(async (row) => await opens(toAbsolute(activeRoot, row.path)) ? null : row)
11026
+ );
11027
+ return {
11028
+ checked: indexed.length,
11029
+ missing: results.filter((row) => row !== null).map((row) => `${row.ref} -> ${row.path}`)
11030
+ };
10007
11031
  }
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;
11032
+ async function opens(absolutePath) {
10025
11033
  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;
11034
+ await fs55.access(absolutePath);
11035
+ return true;
11036
+ } catch {
11037
+ return false;
10031
11038
  }
10032
- return entries.filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) => e.name).sort();
10033
11039
  }
10034
11040
 
10035
11041
  // src/doctor.ts
@@ -10078,19 +11084,19 @@ function parseMajor(version) {
10078
11084
  const match = /^v?(\d+)\./.exec(version);
10079
11085
  return match ? Number(match[1]) : 0;
10080
11086
  }
10081
- async function fileExists3(fs53, target) {
11087
+ async function fileExists3(fs57, target) {
10082
11088
  try {
10083
- await fs53.access(target);
11089
+ await fs57.access(target);
10084
11090
  return true;
10085
11091
  } catch {
10086
11092
  return false;
10087
11093
  }
10088
11094
  }
10089
11095
  var MCP_SERVER_NAMES = ["@hjewkes/active-work", "active-work"];
10090
- async function readMcpRegistered(fs53, homeDir) {
11096
+ async function readMcpRegistered(fs57, homeDir) {
10091
11097
  const configPath = nodePath4.join(homeDir, ".claude.json");
10092
11098
  try {
10093
- const raw = await fs53.readFile(configPath, "utf8");
11099
+ const raw = await fs57.readFile(configPath, "utf8");
10094
11100
  const parsed = JSON.parse(raw);
10095
11101
  const servers = parsed.mcpServers ?? {};
10096
11102
  return MCP_SERVER_NAMES.some((name) => Boolean(servers[name]));
@@ -10112,9 +11118,9 @@ async function checkNode(deps) {
10112
11118
  };
10113
11119
  }
10114
11120
  async function checkActiveRoot(deps) {
10115
- const fs53 = deps.fs ?? fsp4;
11121
+ const fs57 = deps.fs ?? fsp4;
10116
11122
  const activeRoot = deps.activeRoot ?? getActiveRoot();
10117
- if (!await fileExists3(fs53, activeRoot)) {
11123
+ if (!await fileExists3(fs57, activeRoot)) {
10118
11124
  return {
10119
11125
  name: "active-root",
10120
11126
  status: "fail",
@@ -10122,7 +11128,7 @@ async function checkActiveRoot(deps) {
10122
11128
  };
10123
11129
  }
10124
11130
  const schemaFile = nodePath4.join(activeRoot, ".schema-version");
10125
- if (!await fileExists3(fs53, schemaFile)) {
11131
+ if (!await fileExists3(fs57, schemaFile)) {
10126
11132
  return {
10127
11133
  name: "active-root",
10128
11134
  status: "warn",
@@ -10159,9 +11165,9 @@ async function checkDaemon(deps) {
10159
11165
  };
10160
11166
  }
10161
11167
  async function checkMcp(deps) {
10162
- const fs53 = deps.fs ?? fsp4;
11168
+ const fs57 = deps.fs ?? fsp4;
10163
11169
  const homeDir = deps.homeDir ?? os6.homedir();
10164
- if (await readMcpRegistered(fs53, homeDir)) {
11170
+ if (await readMcpRegistered(fs57, homeDir)) {
10165
11171
  return { name: "mcp-registration", status: "ok", detail: "registered in ~/.claude.json" };
10166
11172
  }
10167
11173
  return {
@@ -10171,10 +11177,10 @@ async function checkMcp(deps) {
10171
11177
  };
10172
11178
  }
10173
11179
  async function checkSkill(deps) {
10174
- const fs53 = deps.fs ?? fsp4;
11180
+ const fs57 = deps.fs ?? fsp4;
10175
11181
  const homeDir = deps.homeDir ?? os6.homedir();
10176
11182
  const skill = nodePath4.join(homeDir, ".claude", "skills", "active-work", "SKILL.md");
10177
- if (await fileExists3(fs53, skill)) {
11183
+ if (await fileExists3(fs57, skill)) {
10178
11184
  return { name: "skill", status: "ok", detail: skill };
10179
11185
  }
10180
11186
  return {
@@ -10251,7 +11257,7 @@ function sessionFilesCheck(malformed) {
10251
11257
  }
10252
11258
  async function checkSessions(deps) {
10253
11259
  const activeRoot = deps.activeRoot ?? getActiveRoot();
10254
- const slugs = await listInitiativeSlugs3(activeRoot);
11260
+ const slugs = await listInitiativeSlugs2(activeRoot);
10255
11261
  const byKind = /* @__PURE__ */ new Map();
10256
11262
  const malformed = [];
10257
11263
  const badTaskRefs = [];
@@ -10293,7 +11299,7 @@ function noteTitlesCheck(entries) {
10293
11299
  }
10294
11300
  async function checkNoteTitles(deps) {
10295
11301
  const activeRoot = deps.activeRoot ?? getActiveRoot();
10296
- const slugs = await listInitiativeSlugs3(activeRoot);
11302
+ const slugs = await listInitiativeSlugs2(activeRoot);
10297
11303
  const overlong = [];
10298
11304
  for (const slug of slugs) {
10299
11305
  const { notes } = await loadNotesFromDir(nodePath4.join(activeRoot, slug));
@@ -10333,9 +11339,42 @@ function artifactHashesCheck(drifted) {
10333
11339
  detail: `hand-edited outside active-work: ${drifted.join("; ")}`
10334
11340
  };
10335
11341
  }
11342
+ function workspaceIndexCheck(health) {
11343
+ if (health === null) {
11344
+ return { name: "workspace-index", status: "ok", detail: "no index built yet" };
11345
+ }
11346
+ if (health.missing.length === 0) {
11347
+ return {
11348
+ name: "workspace-index",
11349
+ status: "ok",
11350
+ detail: `${health.checked} indexed refs all resolve to a file that opens`
11351
+ };
11352
+ }
11353
+ return {
11354
+ name: "workspace-index",
11355
+ status: "warn",
11356
+ // Brain's whole failure, made visible: 5,013 of 5,065 notes detached on one
11357
+ // directory move and nothing noticed, because nothing checked.
11358
+ 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("; ")}`
11359
+ };
11360
+ }
11361
+ async function checkWorkspaceIndexRefs(deps) {
11362
+ const activeRoot = deps.activeRoot ?? getActiveRoot();
11363
+ let graph;
11364
+ try {
11365
+ graph = openGraph();
11366
+ } catch {
11367
+ return workspaceIndexCheck(null);
11368
+ }
11369
+ try {
11370
+ return workspaceIndexCheck(await checkWorkspaceIndex(graph, activeRoot));
11371
+ } finally {
11372
+ graph.db.close();
11373
+ }
11374
+ }
10336
11375
  async function checkArtifactHashes(deps) {
10337
11376
  const activeRoot = deps.activeRoot ?? getActiveRoot();
10338
- const slugs = await listInitiativeSlugs3(activeRoot);
11377
+ const slugs = await listInitiativeSlugs2(activeRoot);
10339
11378
  const drifted = [];
10340
11379
  for (const slug of slugs) {
10341
11380
  const findings = await lintHashes(slug, nodePath4.join(activeRoot, slug));
@@ -10344,7 +11383,7 @@ async function checkArtifactHashes(deps) {
10344
11383
  return artifactHashesCheck(drifted);
10345
11384
  }
10346
11385
  async function runDoctor(deps = {}) {
10347
- const [installChecks, sessionChecks, noteTitles, artifactHashes, leases] = await Promise.all([
11386
+ const [installChecks, sessionChecks, noteTitles, artifactHashes, leases, workspaceIndex] = await Promise.all([
10348
11387
  Promise.all([
10349
11388
  checkNode(deps),
10350
11389
  checkActiveRoot(deps),
@@ -10356,9 +11395,17 @@ async function runDoctor(deps = {}) {
10356
11395
  checkSessions(deps),
10357
11396
  checkNoteTitles(deps),
10358
11397
  checkArtifactHashes(deps),
10359
- checkLeases(deps)
11398
+ checkLeases(deps),
11399
+ checkWorkspaceIndexRefs(deps)
10360
11400
  ]);
10361
- const checks = [...installChecks, ...sessionChecks, noteTitles, artifactHashes, leases];
11401
+ const checks = [
11402
+ ...installChecks,
11403
+ ...sessionChecks,
11404
+ noteTitles,
11405
+ artifactHashes,
11406
+ leases,
11407
+ workspaceIndex
11408
+ ];
10362
11409
  return { ok: checks.every((c) => c.status !== "fail"), checks };
10363
11410
  }
10364
11411
 
@@ -10428,7 +11475,7 @@ var ResultSchema48 = z61.object({
10428
11475
  repairs: z61.array(z61.object({ action: z61.string(), file: z61.string(), detail: z61.string() })),
10429
11476
  uncovered: z61.array(z61.string())
10430
11477
  });
10431
- function describe(plan, applied) {
11478
+ function describe2(plan, applied) {
10432
11479
  const initiatives = plan.initiatives.map((i) => ({
10433
11480
  slug: i.slug,
10434
11481
  sessions: i.sessions.map((s) => ({
@@ -10514,7 +11561,7 @@ var migrate_default = defineCommand({
10514
11561
  async run(args, ctx) {
10515
11562
  if (args.apply !== true) {
10516
11563
  const plan2 = await planV2ToV3(ctx.activeRoot);
10517
- const result2 = describe(plan2, false);
11564
+ const result2 = describe2(plan2, false);
10518
11565
  if (ctx.format !== "json") process.stderr.write(render(result2));
10519
11566
  return result2;
10520
11567
  }
@@ -10530,7 +11577,7 @@ var migrate_default = defineCommand({
10530
11577
  const plan = await planV2ToV3(ctx.activeRoot);
10531
11578
  await applyV2ToV3(ctx.activeRoot, plan);
10532
11579
  await writeSchemaVersion(ctx.activeRoot, CURRENT_VERSION);
10533
- const result = describe(plan, true);
11580
+ const result = describe2(plan, true);
10534
11581
  if (ctx.format !== "json") process.stderr.write(render(result));
10535
11582
  return result;
10536
11583
  }
@@ -10785,16 +11832,16 @@ for (const cmd of ALL_COMMANDS) {
10785
11832
  import { readCommanderOption } from "@titan-design/registry";
10786
11833
 
10787
11834
  // src/utils/usage-log.ts
10788
- import { promises as fs52 } from "fs";
10789
- import path64 from "path";
11835
+ import { promises as fs56 } from "fs";
11836
+ import path66 from "path";
10790
11837
  function usageLogPath() {
10791
- return path64.join(getStateRoot(), "usage.jsonl");
11838
+ return path66.join(getStateRoot(), "usage.jsonl");
10792
11839
  }
10793
11840
  async function appendUsage(rec) {
10794
11841
  try {
10795
11842
  const file = usageLogPath();
10796
- await fs52.mkdir(path64.dirname(file), { recursive: true });
10797
- await fs52.appendFile(file, JSON.stringify(rec) + "\n", "utf8");
11843
+ await fs56.mkdir(path66.dirname(file), { recursive: true });
11844
+ await fs56.appendFile(file, JSON.stringify(rec) + "\n", "utf8");
10798
11845
  } catch {
10799
11846
  }
10800
11847
  }