@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 CHANGED
@@ -12,7 +12,7 @@ import {
12
12
  releaseLeaseSync,
13
13
  resolveLaunchCwd,
14
14
  resume_default
15
- } from "./chunk-RLSQSE7I.js";
15
+ } from "./chunk-KAR6NOS2.js";
16
16
 
17
17
  // src/aw.ts
18
18
  import { spawn } from "child_process";
@@ -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 path11 from "path";
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 path10 from "path";
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 path8 from "path";
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 path8.join(activeRoot, LEASE_DIR_NAME, slug);
1307
+ return path9.join(activeRoot, LEASE_DIR_NAME, slug);
1090
1308
  }
1091
1309
  function leasePath(activeRoot, slug, leaseId) {
1092
- return path8.join(leaseDir(activeRoot, slug), `${leaseId}.json`);
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 = path8.join(dir, name);
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 = path8.join(activeRoot, LEASE_DIR_NAME);
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 path9 from "path";
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 path9.resolve(expandTilde(repo));
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 = path10.join(initiativeDir, "tasks");
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 = path10.join(tasksDir, filename);
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 = path10.join(initiativeDir, "artifacts.yml");
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 { notes, malformed } = loaded;
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
- lines.push(`(+${overflow} older \u2014 \`active-work note list ${slug}\`)`);
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 (newest ${shown.length} of ${notes.length})` : `# Durable notes (${notes.length})`;
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 = path10.join(initiativeDir, "brief.md");
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 = path10.join(activeRoot, slug);
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, path10.join(initiativeDir, "brief.md")) || "_(no brief body)_";
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
- path10.join(initiativeDir, "sessions", `${narrativeSession.sessionFile}.md`)
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 = path10.join(initiativeDir, "artifacts.yml");
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 path11.join(activeRoot, slug);
2319
+ return path12.join(activeRoot, slug);
2077
2320
  }
2078
2321
  async function resolveCwdHint(activeRoot, slug) {
2079
- const registered = await readRegisteredWorktrees(path11.join(activeRoot, slug));
2322
+ const registered = await readRegisteredWorktrees(path12.join(activeRoot, slug));
2080
2323
  const preferred = defaultWorktreePath(registered);
2081
- return preferred === null ? path11.join(activeRoot, slug) : expandTilde(preferred);
2324
+ return preferred === null ? path12.join(activeRoot, slug) : expandTilde(preferred);
2082
2325
  }
2083
2326
  function isInside(child, parent) {
2084
- const rel = path11.relative(parent, child);
2085
- return rel === "" || !rel.startsWith("..") && !path11.isAbsolute(rel);
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 path11.resolve(p);
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 = path11.join(activeRoot, slug, "brief.md");
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(path11.join(activeRoot, slug));
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 (!path11.isAbsolute(displayPath)) continue;
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 path14 from "path";
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 path12 from "path";
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 = path12.join(initiativeDir, "tasks");
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 = path12.join(tasksDir, "archive");
2387
+ const archiveDir = path13.join(tasksDir, "archive");
2145
2388
  const archived = [];
2146
2389
  for (const filename of ymlFiles) {
2147
- const fullPath = path12.join(tasksDir, filename);
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, path12.join(archiveDir, filename));
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 path13 from "path";
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(path13.join(configRoot, "config.json"), "utf8");
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 = path14.join(activeRoot, slug, "brief.md");
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 = path14.join(activeRoot, slug, "brief.md");
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(path14.join(activeRoot, slug), {
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 path15 from "path";
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 = path15.join(activeRoot, slug, "sessions");
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(path15.join(sessionsDir, filename), "utf8");
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 ?? path15.join(os2.homedir(), ".claude", "projects");
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 = path15.join(root, dir, targetName);
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-RLSQSE7I.js.map
2863
+ //# sourceMappingURL=chunk-KAR6NOS2.js.map