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