@titan-design/active-work 0.6.0 → 0.7.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/aw.js +1 -1
- package/dist/{chunk-RLSQSE7I.js → chunk-KAR6NOS2.js} +306 -50
- package/dist/chunk-KAR6NOS2.js.map +1 -0
- package/dist/cli.js +155 -281
- package/dist/cli.js.map +1 -1
- package/docs/cli-reference.md +4 -0
- package/package.json +1 -1
- package/dist/chunk-RLSQSE7I.js.map +0 -1
package/dist/aw.js
CHANGED
|
@@ -151,7 +151,7 @@ function parseLauncherFlags(args) {
|
|
|
151
151
|
|
|
152
152
|
// src/commands/_open-helpers.ts
|
|
153
153
|
import { promises as fs11 } from "fs";
|
|
154
|
-
import
|
|
154
|
+
import path12 from "path";
|
|
155
155
|
|
|
156
156
|
// src/schemas/brief.ts
|
|
157
157
|
import { z } from "zod";
|
|
@@ -461,7 +461,7 @@ async function writeArtifactsFile(initiativeDir, artifacts) {
|
|
|
461
461
|
|
|
462
462
|
// src/bootstrap/prompt.ts
|
|
463
463
|
import { promises as fs10 } from "fs";
|
|
464
|
-
import
|
|
464
|
+
import path11 from "path";
|
|
465
465
|
|
|
466
466
|
// src/schemas/task.ts
|
|
467
467
|
import { z as z3 } from "zod";
|
|
@@ -1014,10 +1014,228 @@ async function writeNoteFile(initiativeDir, frontmatter, body) {
|
|
|
1014
1014
|
return { path: fullPath, filename };
|
|
1015
1015
|
}
|
|
1016
1016
|
|
|
1017
|
+
// src/bootstrap/rank-notes.ts
|
|
1018
|
+
import { fuseByRRF } from "@titan-design/retrieval";
|
|
1019
|
+
|
|
1020
|
+
// src/session-index/graph.ts
|
|
1021
|
+
import Database from "better-sqlite3";
|
|
1022
|
+
import { openSessionGraph } from "@titan-design/session-graph";
|
|
1023
|
+
import { runMigrations, WatermarkTable } from "@titan-design/store-sqlite";
|
|
1024
|
+
import path8 from "path";
|
|
1025
|
+
|
|
1026
|
+
// src/workspace-index/schema.ts
|
|
1027
|
+
import { kitDdl } from "@titan-design/store-sqlite";
|
|
1028
|
+
import { MIGRATIONS as SESSION_GRAPH_MIGRATIONS } from "@titan-design/session-graph";
|
|
1029
|
+
var WORKSPACE_KIT = {
|
|
1030
|
+
watermark: "workspace_file",
|
|
1031
|
+
edge: "edge",
|
|
1032
|
+
spanFts: "search"
|
|
1033
|
+
};
|
|
1034
|
+
var WORKSPACE_SPAN_SOURCE_BASE = 1e9;
|
|
1035
|
+
var DOMAIN_DDL = `
|
|
1036
|
+
CREATE TABLE IF NOT EXISTS initiative (
|
|
1037
|
+
path TEXT PRIMARY KEY,
|
|
1038
|
+
initiative_ref TEXT NOT NULL UNIQUE,
|
|
1039
|
+
slug TEXT NOT NULL,
|
|
1040
|
+
title TEXT,
|
|
1041
|
+
state TEXT,
|
|
1042
|
+
rank INTEGER,
|
|
1043
|
+
ship_target TEXT,
|
|
1044
|
+
owner TEXT,
|
|
1045
|
+
task_prefix TEXT,
|
|
1046
|
+
updated TEXT
|
|
1047
|
+
);
|
|
1048
|
+
|
|
1049
|
+
CREATE TABLE IF NOT EXISTS note (
|
|
1050
|
+
path TEXT PRIMARY KEY,
|
|
1051
|
+
note_ref TEXT NOT NULL UNIQUE,
|
|
1052
|
+
initiative TEXT NOT NULL,
|
|
1053
|
+
filename TEXT NOT NULL,
|
|
1054
|
+
kind TEXT NOT NULL,
|
|
1055
|
+
title TEXT NOT NULL,
|
|
1056
|
+
created TEXT,
|
|
1057
|
+
tags TEXT,
|
|
1058
|
+
hits INTEGER NOT NULL DEFAULT 0,
|
|
1059
|
+
promoted_at TEXT
|
|
1060
|
+
);
|
|
1061
|
+
CREATE INDEX IF NOT EXISTS idx_note_initiative ON note(initiative);
|
|
1062
|
+
|
|
1063
|
+
CREATE TABLE IF NOT EXISTS workspace_task (
|
|
1064
|
+
path TEXT PRIMARY KEY,
|
|
1065
|
+
task_ref TEXT NOT NULL,
|
|
1066
|
+
initiative TEXT NOT NULL,
|
|
1067
|
+
task_id TEXT NOT NULL,
|
|
1068
|
+
title TEXT NOT NULL,
|
|
1069
|
+
status TEXT NOT NULL,
|
|
1070
|
+
priority INTEGER,
|
|
1071
|
+
severity TEXT,
|
|
1072
|
+
estimate REAL,
|
|
1073
|
+
tags TEXT,
|
|
1074
|
+
created TEXT,
|
|
1075
|
+
updated TEXT,
|
|
1076
|
+
done_at TEXT
|
|
1077
|
+
);
|
|
1078
|
+
CREATE INDEX IF NOT EXISTS idx_workspace_task_ref ON workspace_task(task_ref);
|
|
1079
|
+
|
|
1080
|
+
CREATE TABLE IF NOT EXISTS session_record (
|
|
1081
|
+
path TEXT PRIMARY KEY,
|
|
1082
|
+
session_ref TEXT NOT NULL,
|
|
1083
|
+
initiative TEXT NOT NULL,
|
|
1084
|
+
session_id TEXT NOT NULL,
|
|
1085
|
+
started TEXT,
|
|
1086
|
+
ended TEXT,
|
|
1087
|
+
track TEXT,
|
|
1088
|
+
parent_session_id TEXT
|
|
1089
|
+
);
|
|
1090
|
+
CREATE INDEX IF NOT EXISTS idx_session_record_ref ON session_record(session_ref);
|
|
1091
|
+
|
|
1092
|
+
CREATE TABLE IF NOT EXISTS source (
|
|
1093
|
+
path TEXT PRIMARY KEY,
|
|
1094
|
+
source_ref TEXT NOT NULL UNIQUE,
|
|
1095
|
+
initiative TEXT NOT NULL,
|
|
1096
|
+
title TEXT,
|
|
1097
|
+
kind TEXT,
|
|
1098
|
+
added TEXT
|
|
1099
|
+
);
|
|
1100
|
+
`;
|
|
1101
|
+
function nextVersion(chain) {
|
|
1102
|
+
return (chain[chain.length - 1]?.version ?? 0) + 1;
|
|
1103
|
+
}
|
|
1104
|
+
var SPAN_SOURCE_INDEX = `
|
|
1105
|
+
CREATE INDEX IF NOT EXISTS idx_search_span_source ON search_span(source_id);
|
|
1106
|
+
`;
|
|
1107
|
+
var WORKSPACE_MIGRATIONS = [
|
|
1108
|
+
{
|
|
1109
|
+
version: nextVersion(SESSION_GRAPH_MIGRATIONS),
|
|
1110
|
+
name: "workspace index tables",
|
|
1111
|
+
up: (db) => {
|
|
1112
|
+
db.exec(kitDdl({ watermark: WORKSPACE_KIT.watermark }));
|
|
1113
|
+
db.exec(DOMAIN_DDL);
|
|
1114
|
+
db.exec(SPAN_SOURCE_INDEX);
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
];
|
|
1118
|
+
var PRESERVE_DDL = `
|
|
1119
|
+
CREATE TABLE IF NOT EXISTS preserved_row (
|
|
1120
|
+
table_name TEXT NOT NULL,
|
|
1121
|
+
identity TEXT NOT NULL,
|
|
1122
|
+
row_key TEXT NOT NULL,
|
|
1123
|
+
payload TEXT NOT NULL,
|
|
1124
|
+
origin TEXT NOT NULL,
|
|
1125
|
+
mode TEXT NOT NULL DEFAULT 'insert',
|
|
1126
|
+
preserved_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now')),
|
|
1127
|
+
PRIMARY KEY (table_name, row_key)
|
|
1128
|
+
);
|
|
1129
|
+
`;
|
|
1130
|
+
var PRESERVE_MIGRATION = {
|
|
1131
|
+
version: nextVersion([...SESSION_GRAPH_MIGRATIONS, ...WORKSPACE_MIGRATIONS]),
|
|
1132
|
+
name: "preserved rows",
|
|
1133
|
+
up: (db) => db.exec(PRESERVE_DDL)
|
|
1134
|
+
};
|
|
1135
|
+
var MIGRATIONS = [
|
|
1136
|
+
...SESSION_GRAPH_MIGRATIONS,
|
|
1137
|
+
...WORKSPACE_MIGRATIONS,
|
|
1138
|
+
PRESERVE_MIGRATION
|
|
1139
|
+
];
|
|
1140
|
+
|
|
1141
|
+
// src/session-index/graph.ts
|
|
1142
|
+
var SCHEMA_VERSION = MIGRATIONS[MIGRATIONS.length - 1]?.version ?? 0;
|
|
1143
|
+
function defaultGraphPath() {
|
|
1144
|
+
return path8.join(getMinerRoot(), "graph.sqlite3");
|
|
1145
|
+
}
|
|
1146
|
+
function openGraph(dbPath = defaultGraphPath()) {
|
|
1147
|
+
const graph = openSessionGraph(dbPath);
|
|
1148
|
+
runMigrations(graph.db, MIGRATIONS);
|
|
1149
|
+
return {
|
|
1150
|
+
...graph,
|
|
1151
|
+
workspaceFiles: new WatermarkTable(graph.db, { name: WORKSPACE_KIT.watermark })
|
|
1152
|
+
};
|
|
1153
|
+
}
|
|
1154
|
+
function openGraphReadOnly(dbPath = defaultGraphPath()) {
|
|
1155
|
+
return new Database(dbPath, { readonly: true });
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
// src/bootstrap/rank-notes.ts
|
|
1159
|
+
var FOREIGN_CAP = 3;
|
|
1160
|
+
var FOREIGN_FLOOR_PER_TERM = 3.5;
|
|
1161
|
+
var SEARCH_DEPTH = 300;
|
|
1162
|
+
var STOP_WORDS = new Set(
|
|
1163
|
+
"a an and are as at be but by for from in into is it of on or the to with via than then that this these those over under new old add fix use using not no all any each per".split(" ")
|
|
1164
|
+
);
|
|
1165
|
+
function subjectTerms(subject) {
|
|
1166
|
+
const tokens = subject.toLowerCase().match(/[\p{L}\p{N}_]+/gu) ?? [];
|
|
1167
|
+
return [...new Set(tokens.filter((token) => token.length > 2 && !STOP_WORDS.has(token)))];
|
|
1168
|
+
}
|
|
1169
|
+
function subjectOf(input) {
|
|
1170
|
+
if (input.about !== void 0 && input.about.trim().length > 0) return input.about.trim();
|
|
1171
|
+
return [input.topTaskTitle, input.briefTitle].filter(Boolean).join(" ").trim();
|
|
1172
|
+
}
|
|
1173
|
+
function graphNoteRelevance(dbPath) {
|
|
1174
|
+
return (terms) => {
|
|
1175
|
+
const graph = openGraph(dbPath ?? defaultGraphPath());
|
|
1176
|
+
try {
|
|
1177
|
+
const expression = terms.map((term) => `"${term}"`).join(" OR ");
|
|
1178
|
+
const titleOf = graph.db.prepare("SELECT title FROM note WHERE note_ref = ? LIMIT 1");
|
|
1179
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1180
|
+
const hits = [];
|
|
1181
|
+
for (const span of graph.spans.search(expression, SEARCH_DEPTH, { ownerPrefix: "note:" })) {
|
|
1182
|
+
if (seen.has(span.ownerRef)) continue;
|
|
1183
|
+
seen.add(span.ownerRef);
|
|
1184
|
+
const row = titleOf.get(span.ownerRef);
|
|
1185
|
+
hits.push({
|
|
1186
|
+
ref: span.ownerRef,
|
|
1187
|
+
scorePerTerm: -span.rank / terms.length,
|
|
1188
|
+
...row ? { title: row.title } : {}
|
|
1189
|
+
});
|
|
1190
|
+
}
|
|
1191
|
+
return hits;
|
|
1192
|
+
} finally {
|
|
1193
|
+
graph.db.close();
|
|
1194
|
+
}
|
|
1195
|
+
};
|
|
1196
|
+
}
|
|
1197
|
+
function refOf(slug, note) {
|
|
1198
|
+
return `note:${slug}/${note.filename}`;
|
|
1199
|
+
}
|
|
1200
|
+
function initiativeOf(ref) {
|
|
1201
|
+
return ref.slice("note:".length).split("/")[0] ?? "";
|
|
1202
|
+
}
|
|
1203
|
+
function fuseWithRecency(notes, slug, relevantRefs) {
|
|
1204
|
+
const byRef = new Map(notes.map((note) => [refOf(slug, note), note]));
|
|
1205
|
+
const fused = fuseByRRF([
|
|
1206
|
+
{ name: "recency", hits: notes.map((note, i) => ({ id: refOf(slug, note), rank: i + 1 })) },
|
|
1207
|
+
{
|
|
1208
|
+
name: "relevance",
|
|
1209
|
+
hits: relevantRefs.filter((ref) => byRef.has(ref)).map((ref, i) => ({ id: ref, rank: i + 1 }))
|
|
1210
|
+
}
|
|
1211
|
+
]);
|
|
1212
|
+
return fused.map((result) => byRef.get(result.id)).filter((note) => !!note);
|
|
1213
|
+
}
|
|
1214
|
+
function rankNotes(input) {
|
|
1215
|
+
const dateOrder = { local: input.notes, foreign: [], ranked: false };
|
|
1216
|
+
if (input.notes.length === 0) return dateOrder;
|
|
1217
|
+
const terms = subjectTerms(input.subject);
|
|
1218
|
+
if (terms.length === 0) return dateOrder;
|
|
1219
|
+
let hits;
|
|
1220
|
+
try {
|
|
1221
|
+
hits = (input.relevance ?? graphNoteRelevance())(terms);
|
|
1222
|
+
} catch {
|
|
1223
|
+
return dateOrder;
|
|
1224
|
+
}
|
|
1225
|
+
if (hits.length === 0) return dateOrder;
|
|
1226
|
+
const foreign = hits.filter((hit) => initiativeOf(hit.ref) !== input.slug).filter((hit) => hit.scorePerTerm >= FOREIGN_FLOOR_PER_TERM).slice(0, FOREIGN_CAP).map((hit) => ({
|
|
1227
|
+
initiative: initiativeOf(hit.ref),
|
|
1228
|
+
title: hit.title ?? hit.ref,
|
|
1229
|
+
ref: hit.ref
|
|
1230
|
+
}));
|
|
1231
|
+
const localRefs = hits.filter((hit) => initiativeOf(hit.ref) === input.slug).map((hit) => hit.ref);
|
|
1232
|
+
return { local: fuseWithRecency(input.notes, input.slug, localRefs), foreign, ranked: true };
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1017
1235
|
// src/sessions/lease.ts
|
|
1018
1236
|
import { promises as fs9, unlinkSync } from "fs";
|
|
1019
1237
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
1020
|
-
import
|
|
1238
|
+
import path9 from "path";
|
|
1021
1239
|
|
|
1022
1240
|
// src/schemas/lease.ts
|
|
1023
1241
|
import { z as z7 } from "zod";
|
|
@@ -1086,10 +1304,10 @@ var ONESHOT_TTL_MS = 90 * 6e4;
|
|
|
1086
1304
|
var LAUNCHER_MAX_AGE_MS = 36 * 60 * 6e4;
|
|
1087
1305
|
var LEASE_DIR_NAME = ".sessions";
|
|
1088
1306
|
function leaseDir(activeRoot, slug) {
|
|
1089
|
-
return
|
|
1307
|
+
return path9.join(activeRoot, LEASE_DIR_NAME, slug);
|
|
1090
1308
|
}
|
|
1091
1309
|
function leasePath(activeRoot, slug, leaseId) {
|
|
1092
|
-
return
|
|
1310
|
+
return path9.join(leaseDir(activeRoot, slug), `${leaseId}.json`);
|
|
1093
1311
|
}
|
|
1094
1312
|
async function acquireLease(input) {
|
|
1095
1313
|
const {
|
|
@@ -1192,7 +1410,7 @@ async function readLiveLeases(input) {
|
|
|
1192
1410
|
const live = [];
|
|
1193
1411
|
for (const name of entries) {
|
|
1194
1412
|
if (!name.endsWith(".json")) continue;
|
|
1195
|
-
const file =
|
|
1413
|
+
const file = path9.join(dir, name);
|
|
1196
1414
|
const lease = await readOneLease(file);
|
|
1197
1415
|
if (!lease || !isLive(lease, now, isAlive, getComm)) {
|
|
1198
1416
|
await unlinkQuietly(file);
|
|
@@ -1207,7 +1425,7 @@ async function readLiveLeases(input) {
|
|
|
1207
1425
|
}
|
|
1208
1426
|
}
|
|
1209
1427
|
async function sweepAllLeases(activeRoot, options = {}) {
|
|
1210
|
-
const root =
|
|
1428
|
+
const root = path9.join(activeRoot, LEASE_DIR_NAME);
|
|
1211
1429
|
let slugs;
|
|
1212
1430
|
try {
|
|
1213
1431
|
const entries = await fs9.readdir(root, { withFileTypes: true });
|
|
@@ -1233,7 +1451,7 @@ async function sweepAllLeases(activeRoot, options = {}) {
|
|
|
1233
1451
|
|
|
1234
1452
|
// src/utils/git-gh.ts
|
|
1235
1453
|
import { spawn } from "child_process";
|
|
1236
|
-
import
|
|
1454
|
+
import path10 from "path";
|
|
1237
1455
|
var DEFAULT_TIMEOUT_MS = 1e4;
|
|
1238
1456
|
var defaultRunner = (bin, args, opts = {}) => new Promise((resolve, reject) => {
|
|
1239
1457
|
const child = spawn(bin, args, {
|
|
@@ -1285,7 +1503,7 @@ function looksLikeOrgRepo(repo) {
|
|
|
1285
1503
|
}
|
|
1286
1504
|
function resolveLocalRepoPath(repo) {
|
|
1287
1505
|
if (looksLikeOrgRepo(repo)) return null;
|
|
1288
|
-
return
|
|
1506
|
+
return path10.resolve(expandTilde(repo));
|
|
1289
1507
|
}
|
|
1290
1508
|
async function deriveOrgRepoFromPath(repoPath) {
|
|
1291
1509
|
try {
|
|
@@ -1356,7 +1574,7 @@ function describe2(err) {
|
|
|
1356
1574
|
return err instanceof Error ? err.message : String(err);
|
|
1357
1575
|
}
|
|
1358
1576
|
async function loadTasks(initiativeDir) {
|
|
1359
|
-
const tasksDir =
|
|
1577
|
+
const tasksDir = path11.join(initiativeDir, "tasks");
|
|
1360
1578
|
let entries;
|
|
1361
1579
|
try {
|
|
1362
1580
|
entries = await fs10.readdir(tasksDir);
|
|
@@ -1367,7 +1585,7 @@ async function loadTasks(initiativeDir) {
|
|
|
1367
1585
|
const tasks = [];
|
|
1368
1586
|
const malformed = [];
|
|
1369
1587
|
for (const filename of ymlFiles) {
|
|
1370
|
-
const fullPath =
|
|
1588
|
+
const fullPath = path11.join(tasksDir, filename);
|
|
1371
1589
|
try {
|
|
1372
1590
|
tasks.push(await readYaml(fullPath, TaskSchema));
|
|
1373
1591
|
} catch (err) {
|
|
@@ -1380,7 +1598,7 @@ function isMissingFile(err) {
|
|
|
1380
1598
|
return err?.code === "ENOENT";
|
|
1381
1599
|
}
|
|
1382
1600
|
async function loadArtifacts(initiativeDir) {
|
|
1383
|
-
const artifactsPath =
|
|
1601
|
+
const artifactsPath = path11.join(initiativeDir, "artifacts.yml");
|
|
1384
1602
|
const empty = { branches: [], stashes: [], worktrees: [] };
|
|
1385
1603
|
try {
|
|
1386
1604
|
return { artifacts: await readYaml(artifactsPath, ArtifactsSchema) };
|
|
@@ -1489,24 +1707,35 @@ function renderNoteLine(note) {
|
|
|
1489
1707
|
const { kind, title, created } = note.frontmatter;
|
|
1490
1708
|
return `- [${kind}] ${title} (${created})`;
|
|
1491
1709
|
}
|
|
1492
|
-
function renderDurableNotes(loaded, slug) {
|
|
1493
|
-
const {
|
|
1710
|
+
function renderDurableNotes(loaded, ranking, slug) {
|
|
1711
|
+
const { malformed } = loaded;
|
|
1712
|
+
const notes = ranking.local;
|
|
1494
1713
|
if (notes.length === 0 && malformed.length === 0) return null;
|
|
1495
1714
|
const shown = notes.slice(0, DURABLE_NOTES_LIMIT);
|
|
1496
1715
|
const lines = shown.map(renderNoteLine);
|
|
1497
1716
|
const overflow = notes.length - shown.length;
|
|
1717
|
+
const ordering = ranking.ranked ? "most relevant" : "newest";
|
|
1498
1718
|
if (overflow > 0) {
|
|
1499
|
-
|
|
1719
|
+
const rest = ranking.ranked ? "more" : "older";
|
|
1720
|
+
lines.push(`(+${overflow} ${rest} \u2014 \`active-work note list ${slug}\`)`);
|
|
1500
1721
|
}
|
|
1501
1722
|
if (malformed.length > 0) {
|
|
1502
1723
|
lines.push(
|
|
1503
1724
|
`(${malformed.length} note file(s) unreadable \u2014 run \`active-work note list ${slug}\`)`
|
|
1504
1725
|
);
|
|
1505
1726
|
}
|
|
1506
|
-
const heading = overflow > 0 ? `# Durable notes (
|
|
1727
|
+
const heading = overflow > 0 ? `# Durable notes (${ordering} ${shown.length} of ${notes.length})` : `# Durable notes (${notes.length})`;
|
|
1507
1728
|
return `${heading}
|
|
1508
1729
|
${lines.join("\n")}`;
|
|
1509
1730
|
}
|
|
1731
|
+
function renderForeignNotes(ranking) {
|
|
1732
|
+
if (ranking.foreign.length === 0) return null;
|
|
1733
|
+
const lines = ranking.foreign.map(
|
|
1734
|
+
(note) => `- [from \`${note.initiative}\`] ${note.title} (\`${note.ref}\`)`
|
|
1735
|
+
);
|
|
1736
|
+
return `# From other initiatives
|
|
1737
|
+
${lines.join("\n")}`;
|
|
1738
|
+
}
|
|
1510
1739
|
function renderNoOpenLoops(newestSession) {
|
|
1511
1740
|
if (newestSession?.frontmatter.no_loops === true) {
|
|
1512
1741
|
return `Nothing hanging \u2014 the ${endedDate(newestSession.ended)} session asserted the ledger is clear.`;
|
|
@@ -1883,7 +2112,7 @@ function renderBriefState(brief, now) {
|
|
|
1883
2112
|
${lines.join("\n")}`;
|
|
1884
2113
|
}
|
|
1885
2114
|
async function loadBrief(initiativeDir, slug) {
|
|
1886
|
-
const briefPath =
|
|
2115
|
+
const briefPath = path11.join(initiativeDir, "brief.md");
|
|
1887
2116
|
try {
|
|
1888
2117
|
return await readMarkdownWithSchema(briefPath, BriefFrontmatterSchema);
|
|
1889
2118
|
} catch (err) {
|
|
@@ -1904,9 +2133,11 @@ async function assembleBootstrap(input) {
|
|
|
1904
2133
|
adhoc = false,
|
|
1905
2134
|
detectSiblings = true,
|
|
1906
2135
|
siblingProbe = readLiveLeases,
|
|
1907
|
-
ownLeaseId
|
|
2136
|
+
ownLeaseId,
|
|
2137
|
+
about,
|
|
2138
|
+
noteRelevance
|
|
1908
2139
|
} = input;
|
|
1909
|
-
const initiativeDir =
|
|
2140
|
+
const initiativeDir = path11.join(activeRoot, slug);
|
|
1910
2141
|
const { frontmatter: brief, body: briefBody } = await loadBrief(initiativeDir, slug);
|
|
1911
2142
|
const [loaded, loadedTasks, loadedArtifacts, notes] = await Promise.all([
|
|
1912
2143
|
loadSessionsNewestFirst(initiativeDir),
|
|
@@ -1923,7 +2154,7 @@ async function assembleBootstrap(input) {
|
|
|
1923
2154
|
const narrativeSession = latestCanonical ?? sessions[0];
|
|
1924
2155
|
const usedFallbackTrack = !latestCanonical && narrativeSession !== void 0;
|
|
1925
2156
|
const parallelBody = renderParallelSessions(selectParallelSessions(sessions, narrativeSession));
|
|
1926
|
-
const briefExcerpt = truncateLines(briefBody, BRIEF_BODY_MAX_LINES,
|
|
2157
|
+
const briefExcerpt = truncateLines(briefBody, BRIEF_BODY_MAX_LINES, path11.join(initiativeDir, "brief.md")) || "_(no brief body)_";
|
|
1927
2158
|
const { body: tasksBody, count: openTaskCount } = renderTopTasks(tasks, topNTasks, slug);
|
|
1928
2159
|
const { body: recentlyDoneBody, count: recentlyDoneCount } = renderRecentlyDone(
|
|
1929
2160
|
tasks,
|
|
@@ -1958,6 +2189,16 @@ async function assembleBootstrap(input) {
|
|
|
1958
2189
|
}
|
|
1959
2190
|
}
|
|
1960
2191
|
const topTaskTitle = tasks.filter((t) => t.status === "open").sort(compareTasksByPriority)[0]?.title;
|
|
2192
|
+
const noteRanking = rankNotes({
|
|
2193
|
+
notes: notes.notes,
|
|
2194
|
+
slug,
|
|
2195
|
+
subject: subjectOf({
|
|
2196
|
+
...about !== void 0 ? { about } : {},
|
|
2197
|
+
briefTitle: brief.title,
|
|
2198
|
+
...topTaskTitle !== void 0 ? { topTaskTitle } : {}
|
|
2199
|
+
}),
|
|
2200
|
+
...noteRelevance ? { relevance: noteRelevance } : {}
|
|
2201
|
+
});
|
|
1961
2202
|
const sections = [];
|
|
1962
2203
|
sections.push(
|
|
1963
2204
|
adhoc ? `Starting an ad-hoc session on \`${slug}\` (${brief.title}). This session is scoped to ad-hoc work related to this workstream \u2014 not necessarily its handoff or current top task. The context below is background so you're oriented; wait for the user to describe the specific ad-hoc task before acting.` : `Starting a session on \`${slug}\` (${brief.title}).`
|
|
@@ -1975,7 +2216,7 @@ ${briefExcerpt}`);
|
|
|
1975
2216
|
const sessionExcerpt = truncateLines(
|
|
1976
2217
|
narrativeSession.body,
|
|
1977
2218
|
SESSION_BODY_MAX_LINES,
|
|
1978
|
-
|
|
2219
|
+
path11.join(initiativeDir, "sessions", `${narrativeSession.sessionFile}.md`)
|
|
1979
2220
|
) || "_(empty session body)_";
|
|
1980
2221
|
const ended = endedDate(narrativeSession.frontmatter.ended);
|
|
1981
2222
|
const trackLabel = usedFallbackTrack ? ` (${narrativeSession.frontmatter.track})` : "";
|
|
@@ -2002,10 +2243,12 @@ ${recentlyDoneBody}`);
|
|
|
2002
2243
|
Moved ${archivedTaskIds.length} stale done task(s) to tasks/archive/: ${archivedTaskIds.join(", ")}`
|
|
2003
2244
|
);
|
|
2004
2245
|
}
|
|
2005
|
-
const notesBody = renderDurableNotes(notes, slug);
|
|
2246
|
+
const notesBody = renderDurableNotes(notes, noteRanking, slug);
|
|
2006
2247
|
if (notesBody) sections.push(notesBody);
|
|
2248
|
+
const foreignBody = renderForeignNotes(noteRanking);
|
|
2249
|
+
if (foreignBody) sections.push(foreignBody);
|
|
2007
2250
|
if (artifactsError) {
|
|
2008
|
-
const artifactsPath =
|
|
2251
|
+
const artifactsPath = path11.join(initiativeDir, "artifacts.yml");
|
|
2009
2252
|
sections.push(
|
|
2010
2253
|
`# Open artifacts
|
|
2011
2254
|
_${artifactsPath} exists but could not be read (${artifactsError}). Branch and stash context is MISSING from this bootstrap \u2014 do not treat the working tree as clean. Run \`active-work doctor\`._`
|
|
@@ -2073,22 +2316,22 @@ async function resolveSlug(activeRoot, input) {
|
|
|
2073
2316
|
throw new NotFoundError(`No initiative matches '${input}'. Known: ${slugs.join(", ")}`);
|
|
2074
2317
|
}
|
|
2075
2318
|
function resolveLaunchCwd(activeRoot, slug) {
|
|
2076
|
-
return
|
|
2319
|
+
return path12.join(activeRoot, slug);
|
|
2077
2320
|
}
|
|
2078
2321
|
async function resolveCwdHint(activeRoot, slug) {
|
|
2079
|
-
const registered = await readRegisteredWorktrees(
|
|
2322
|
+
const registered = await readRegisteredWorktrees(path12.join(activeRoot, slug));
|
|
2080
2323
|
const preferred = defaultWorktreePath(registered);
|
|
2081
|
-
return preferred === null ?
|
|
2324
|
+
return preferred === null ? path12.join(activeRoot, slug) : expandTilde(preferred);
|
|
2082
2325
|
}
|
|
2083
2326
|
function isInside(child, parent) {
|
|
2084
|
-
const rel =
|
|
2085
|
-
return rel === "" || !rel.startsWith("..") && !
|
|
2327
|
+
const rel = path12.relative(parent, child);
|
|
2328
|
+
return rel === "" || !rel.startsWith("..") && !path12.isAbsolute(rel);
|
|
2086
2329
|
}
|
|
2087
2330
|
async function canonicalize(p) {
|
|
2088
2331
|
try {
|
|
2089
2332
|
return await fs11.realpath(p);
|
|
2090
2333
|
} catch {
|
|
2091
|
-
return
|
|
2334
|
+
return path12.resolve(p);
|
|
2092
2335
|
}
|
|
2093
2336
|
}
|
|
2094
2337
|
async function resolveSlugFromCwd(activeRoot, cwd) {
|
|
@@ -2097,16 +2340,16 @@ async function resolveSlugFromCwd(activeRoot, cwd) {
|
|
|
2097
2340
|
let best = null;
|
|
2098
2341
|
let tiedAtBest = false;
|
|
2099
2342
|
for (const slug of slugs) {
|
|
2100
|
-
const briefPath =
|
|
2343
|
+
const briefPath = path12.join(activeRoot, slug, "brief.md");
|
|
2101
2344
|
try {
|
|
2102
2345
|
await readMarkdownWithSchema(briefPath, BriefFrontmatterSchema);
|
|
2103
2346
|
} catch {
|
|
2104
2347
|
continue;
|
|
2105
2348
|
}
|
|
2106
|
-
const registered = await readRegisteredWorktrees(
|
|
2349
|
+
const registered = await readRegisteredWorktrees(path12.join(activeRoot, slug));
|
|
2107
2350
|
for (const entry of registered) {
|
|
2108
2351
|
const displayPath = expandTilde(entry.path);
|
|
2109
|
-
if (!
|
|
2352
|
+
if (!path12.isAbsolute(displayPath)) continue;
|
|
2110
2353
|
const canonical = await canonicalize(displayPath);
|
|
2111
2354
|
if (!isInside(resolvedCwd, canonical)) continue;
|
|
2112
2355
|
const depth = canonical.length;
|
|
@@ -2123,16 +2366,16 @@ async function resolveSlugFromCwd(activeRoot, cwd) {
|
|
|
2123
2366
|
}
|
|
2124
2367
|
|
|
2125
2368
|
// src/commands/open.ts
|
|
2126
|
-
import
|
|
2369
|
+
import path15 from "path";
|
|
2127
2370
|
import { z as z9 } from "zod";
|
|
2128
2371
|
|
|
2129
2372
|
// src/bootstrap/archive-tasks.ts
|
|
2130
2373
|
import { promises as fsp } from "fs";
|
|
2131
|
-
import
|
|
2374
|
+
import path13 from "path";
|
|
2132
2375
|
var MS_PER_DAY3 = 864e5;
|
|
2133
2376
|
async function archiveStaleTasks(initiativeDir, opts) {
|
|
2134
2377
|
if (!(opts.retentionDays > 0)) return [];
|
|
2135
|
-
const tasksDir =
|
|
2378
|
+
const tasksDir = path13.join(initiativeDir, "tasks");
|
|
2136
2379
|
let entries;
|
|
2137
2380
|
try {
|
|
2138
2381
|
entries = await fsp.readdir(tasksDir);
|
|
@@ -2141,10 +2384,10 @@ async function archiveStaleTasks(initiativeDir, opts) {
|
|
|
2141
2384
|
}
|
|
2142
2385
|
const ymlFiles = entries.filter((n) => n.endsWith(".yml") || n.endsWith(".yaml"));
|
|
2143
2386
|
const cutoffMs = opts.now.getTime() - opts.retentionDays * MS_PER_DAY3;
|
|
2144
|
-
const archiveDir =
|
|
2387
|
+
const archiveDir = path13.join(tasksDir, "archive");
|
|
2145
2388
|
const archived = [];
|
|
2146
2389
|
for (const filename of ymlFiles) {
|
|
2147
|
-
const fullPath =
|
|
2390
|
+
const fullPath = path13.join(tasksDir, filename);
|
|
2148
2391
|
let doneAt;
|
|
2149
2392
|
let id;
|
|
2150
2393
|
try {
|
|
@@ -2159,7 +2402,7 @@ async function archiveStaleTasks(initiativeDir, opts) {
|
|
|
2159
2402
|
if (Number.isNaN(doneMs) || doneMs > cutoffMs) continue;
|
|
2160
2403
|
try {
|
|
2161
2404
|
await fsp.mkdir(archiveDir, { recursive: true });
|
|
2162
|
-
await fsp.rename(fullPath,
|
|
2405
|
+
await fsp.rename(fullPath, path13.join(archiveDir, filename));
|
|
2163
2406
|
archived.push(id);
|
|
2164
2407
|
} catch {
|
|
2165
2408
|
}
|
|
@@ -2169,7 +2412,7 @@ async function archiveStaleTasks(initiativeDir, opts) {
|
|
|
2169
2412
|
|
|
2170
2413
|
// src/utils/global-config.ts
|
|
2171
2414
|
import { promises as fs12 } from "fs";
|
|
2172
|
-
import
|
|
2415
|
+
import path14 from "path";
|
|
2173
2416
|
import { z as z8 } from "zod";
|
|
2174
2417
|
var GlobalConfigSchema = z8.object({
|
|
2175
2418
|
channels: z8.array(channelTarget).optional()
|
|
@@ -2178,7 +2421,7 @@ var FALLBACK_DEFAULT_CHANNELS = ["plugin:agent-chat@agent-chat-local"];
|
|
|
2178
2421
|
async function readGlobalConfig(configRoot = getConfigRoot()) {
|
|
2179
2422
|
let raw;
|
|
2180
2423
|
try {
|
|
2181
|
-
raw = await fs12.readFile(
|
|
2424
|
+
raw = await fs12.readFile(path14.join(configRoot, "config.json"), "utf8");
|
|
2182
2425
|
} catch {
|
|
2183
2426
|
return {};
|
|
2184
2427
|
}
|
|
@@ -2210,6 +2453,7 @@ var ArgsSchema2 = z9.object({
|
|
|
2210
2453
|
// Frame the bootstrap prompt as ad-hoc work related to the workstream rather
|
|
2211
2454
|
// than a continuation of its handoff / top task.
|
|
2212
2455
|
adhoc: z9.boolean().optional(),
|
|
2456
|
+
about: z9.string().min(1).optional(),
|
|
2213
2457
|
// Skip the sibling-session probe (and the lease write that goes with it).
|
|
2214
2458
|
no_sibling_check: z9.boolean().optional(),
|
|
2215
2459
|
// Internal: `aw` calls this command in-process and holds a `launcher` lease
|
|
@@ -2254,7 +2498,7 @@ var STATE_ORDER = {
|
|
|
2254
2498
|
done: 3
|
|
2255
2499
|
};
|
|
2256
2500
|
async function loadInitiativeSummary(activeRoot, slug) {
|
|
2257
|
-
const briefPath =
|
|
2501
|
+
const briefPath = path15.join(activeRoot, slug, "brief.md");
|
|
2258
2502
|
try {
|
|
2259
2503
|
const { frontmatter } = await readMarkdownWithSchema(briefPath, BriefFrontmatterSchema);
|
|
2260
2504
|
return {
|
|
@@ -2294,10 +2538,10 @@ async function claimOneshotLease(activeRoot, slug, cwd) {
|
|
|
2294
2538
|
}
|
|
2295
2539
|
}
|
|
2296
2540
|
async function bootstrapInitiative(activeRoot, slug, opts) {
|
|
2297
|
-
const briefPath =
|
|
2541
|
+
const briefPath = path15.join(activeRoot, slug, "brief.md");
|
|
2298
2542
|
const { frontmatter: brief } = await readMarkdownWithSchema(briefPath, BriefFrontmatterSchema);
|
|
2299
2543
|
const cwdHint = opts.cwdHintOverride ?? await resolveCwdHint(activeRoot, slug);
|
|
2300
|
-
const archivedTaskIds = await archiveStaleTasks(
|
|
2544
|
+
const archivedTaskIds = await archiveStaleTasks(path15.join(activeRoot, slug), {
|
|
2301
2545
|
retentionDays: ARCHIVE_DONE_AFTER_DAYS,
|
|
2302
2546
|
now: /* @__PURE__ */ new Date()
|
|
2303
2547
|
});
|
|
@@ -2308,6 +2552,7 @@ async function bootstrapInitiative(activeRoot, slug, opts) {
|
|
|
2308
2552
|
includeLiveStatus: !opts.offline,
|
|
2309
2553
|
archivedTaskIds,
|
|
2310
2554
|
adhoc: opts.adhoc,
|
|
2555
|
+
...opts.about !== void 0 ? { about: opts.about } : {},
|
|
2311
2556
|
detectSiblings,
|
|
2312
2557
|
...process.env.AW_LEASE_ID ? { ownLeaseId: process.env.AW_LEASE_ID } : {}
|
|
2313
2558
|
});
|
|
@@ -2344,6 +2589,10 @@ var openCommand = defineCommand({
|
|
|
2344
2589
|
long: "--pick",
|
|
2345
2590
|
description: "Always return the picker list; skip resolving the initiative from the current directory."
|
|
2346
2591
|
},
|
|
2592
|
+
about: {
|
|
2593
|
+
long: "--about",
|
|
2594
|
+
description: "What this session is about, ranking the notes against it instead of the top task and brief titles."
|
|
2595
|
+
},
|
|
2347
2596
|
adhoc: {
|
|
2348
2597
|
long: "--adhoc",
|
|
2349
2598
|
description: "Frame the prompt as ad-hoc work on the workstream (awaiting the user\u2019s task), not a continuation of the handoff / top task."
|
|
@@ -2353,7 +2602,7 @@ var openCommand = defineCommand({
|
|
|
2353
2602
|
description: "Skip the check for another session already live on this initiative, and do not record a lease for this one."
|
|
2354
2603
|
}
|
|
2355
2604
|
},
|
|
2356
|
-
usage: "active-work open [slug] [--offline] [--cwd <dir>] [--pick] [--adhoc] [--no-sibling-check]"
|
|
2605
|
+
usage: "active-work open [slug] [--offline] [--cwd <dir>] [--pick] [--adhoc] [--about <text>] [--no-sibling-check]"
|
|
2357
2606
|
},
|
|
2358
2607
|
async run(args, ctx) {
|
|
2359
2608
|
const activeRoot = ctx.activeRoot ?? getActiveRoot();
|
|
@@ -2365,6 +2614,7 @@ var openCommand = defineCommand({
|
|
|
2365
2614
|
offline: args.offline,
|
|
2366
2615
|
resolvedFrom: "slug",
|
|
2367
2616
|
adhoc: args.adhoc,
|
|
2617
|
+
...args.about !== void 0 ? { about: args.about } : {},
|
|
2368
2618
|
detectSiblings,
|
|
2369
2619
|
deferLease
|
|
2370
2620
|
});
|
|
@@ -2378,6 +2628,7 @@ var openCommand = defineCommand({
|
|
|
2378
2628
|
resolvedFrom: "cwd",
|
|
2379
2629
|
cwdHintOverride: matched.worktreePath,
|
|
2380
2630
|
adhoc: args.adhoc,
|
|
2631
|
+
...args.about !== void 0 ? { about: args.about } : {},
|
|
2381
2632
|
detectSiblings,
|
|
2382
2633
|
deferLease
|
|
2383
2634
|
});
|
|
@@ -2395,11 +2646,11 @@ import { z as z10 } from "zod";
|
|
|
2395
2646
|
// src/sessions/resolve-session-location.ts
|
|
2396
2647
|
import { promises as fs13 } from "fs";
|
|
2397
2648
|
import os2 from "os";
|
|
2398
|
-
import
|
|
2649
|
+
import path16 from "path";
|
|
2399
2650
|
import matter2 from "gray-matter";
|
|
2400
2651
|
async function findInActiveWork(activeRoot, sessionId) {
|
|
2401
2652
|
for (const slug of await listInitiativeSlugs(activeRoot)) {
|
|
2402
|
-
const sessionsDir =
|
|
2653
|
+
const sessionsDir = path16.join(activeRoot, slug, "sessions");
|
|
2403
2654
|
let filenames;
|
|
2404
2655
|
try {
|
|
2405
2656
|
filenames = await fs13.readdir(sessionsDir);
|
|
@@ -2410,7 +2661,7 @@ async function findInActiveWork(activeRoot, sessionId) {
|
|
|
2410
2661
|
if (!filename.endsWith(".md") || !filename.includes(sessionId)) continue;
|
|
2411
2662
|
let raw;
|
|
2412
2663
|
try {
|
|
2413
|
-
raw = await fs13.readFile(
|
|
2664
|
+
raw = await fs13.readFile(path16.join(sessionsDir, filename), "utf8");
|
|
2414
2665
|
} catch {
|
|
2415
2666
|
continue;
|
|
2416
2667
|
}
|
|
@@ -2423,7 +2674,7 @@ async function findInActiveWork(activeRoot, sessionId) {
|
|
|
2423
2674
|
return null;
|
|
2424
2675
|
}
|
|
2425
2676
|
function transcriptsRoot() {
|
|
2426
|
-
return process.env.CLAUDE_PROJECTS_ROOT ??
|
|
2677
|
+
return process.env.CLAUDE_PROJECTS_ROOT ?? path16.join(os2.homedir(), ".claude", "projects");
|
|
2427
2678
|
}
|
|
2428
2679
|
async function extractCwd(filePath) {
|
|
2429
2680
|
const raw = await fs13.readFile(filePath, "utf8");
|
|
@@ -2452,7 +2703,7 @@ async function findInClaudeProjects(sessionId) {
|
|
|
2452
2703
|
}
|
|
2453
2704
|
const targetName = `${sessionId}.jsonl`;
|
|
2454
2705
|
for (const dir of projectDirs) {
|
|
2455
|
-
const candidate =
|
|
2706
|
+
const candidate = path16.join(root, dir, targetName);
|
|
2456
2707
|
try {
|
|
2457
2708
|
await fs13.access(candidate);
|
|
2458
2709
|
} catch {
|
|
@@ -2574,6 +2825,11 @@ export {
|
|
|
2574
2825
|
source_add_default,
|
|
2575
2826
|
loadNotesFromDir,
|
|
2576
2827
|
writeNoteFile,
|
|
2828
|
+
WORKSPACE_SPAN_SOURCE_BASE,
|
|
2829
|
+
SCHEMA_VERSION,
|
|
2830
|
+
defaultGraphPath,
|
|
2831
|
+
openGraph,
|
|
2832
|
+
openGraphReadOnly,
|
|
2577
2833
|
readPidFile,
|
|
2578
2834
|
removePidFile,
|
|
2579
2835
|
resolveDaemonPort,
|
|
@@ -2604,4 +2860,4 @@ export {
|
|
|
2604
2860
|
resume_default,
|
|
2605
2861
|
color
|
|
2606
2862
|
};
|
|
2607
|
-
//# sourceMappingURL=chunk-
|
|
2863
|
+
//# sourceMappingURL=chunk-KAR6NOS2.js.map
|