@oevery/rsp 3.1.0-beta.2 → 3.1.0-beta.3

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.
@@ -22,9 +22,7 @@ import {
22
22
  } from "./chunk-OTJFL6XH.mjs";
23
23
 
24
24
  // src/status/inspect.ts
25
- import { existsSync } from "fs";
26
- import { readFile as readFile3, stat } from "fs/promises";
27
- import { join as join3 } from "path";
25
+ import { readFile as readFile5, stat } from "fs/promises";
28
26
 
29
27
  // src/core/change-group.ts
30
28
  import { readFile } from "fs/promises";
@@ -967,7 +965,7 @@ async function inspectChangeDependencies(options = {}) {
967
965
  const executable = new Set(graphValid ? waves.flat() : []);
968
966
  const nodes = [.../* @__PURE__ */ new Set([...openNames, ...edges.map((edge) => edge.requires)])].sort().map((name) => {
969
967
  let state;
970
- if (archivedNames.has(name))
968
+ if (!openNames.has(name) && archivedNames.has(name))
971
969
  state = "archived";
972
970
  else if (!openNames.has(name))
973
971
  state = "missing";
@@ -1104,246 +1102,12 @@ function findCycleDiagnostics(edges) {
1104
1102
  return diagnostics;
1105
1103
  }
1106
1104
 
1107
- // src/status/inspect.ts
1108
- async function inspectProjectStatus(options = {}) {
1109
- const runtime = [];
1110
- const diagnostics = [];
1111
- let manage = resolveManagePolicy({});
1112
- try {
1113
- const configInspection = await inspectRspConfig();
1114
- const configValid = configInspection.issues.length === 0;
1115
- manage = resolveManagePolicy(configInspection.config, { configValid });
1116
- if (!configValid) {
1117
- diagnostics.push({
1118
- severity: "error",
1119
- code: "invalid_config",
1120
- path: CONFIG_PATH,
1121
- message: configInspection.issues.join("; ")
1122
- });
1123
- }
1124
- } catch (error) {
1125
- manage = resolveManagePolicy({}, { configValid: false });
1126
- diagnostics.push({
1127
- severity: "error",
1128
- code: "invalid_config",
1129
- path: CONFIG_PATH,
1130
- message: toErrorMessage(error)
1131
- });
1132
- }
1133
- const focusTree = await inspectFocusTree();
1134
- for (const diagnostic of focusTree.diagnostics) {
1135
- diagnostics.push({
1136
- severity: "error",
1137
- code: diagnostic.code,
1138
- change: diagnostic.input,
1139
- path: diagnostic.path,
1140
- message: diagnostic.message
1141
- });
1142
- }
1143
- const rawFocusedSet = new Set(focusTree.markers.map((marker) => marker.name));
1144
- const focusedSet = /* @__PURE__ */ new Set();
1145
- const focusedPaths = /* @__PURE__ */ new Map();
1146
- for (const name of rawFocusedSet) {
1147
- try {
1148
- const ref = resolveWorkRef(name, { executable: true });
1149
- focusedSet.add(ref.name);
1150
- focusedPaths.set(ref.name, ref.path);
1151
- } catch (error) {
1152
- if (!(error instanceof WorkRefError))
1153
- throw error;
1154
- diagnostics.push({
1155
- severity: "error",
1156
- code: error.code,
1157
- path: `.rsp/focus.d/${name}`,
1158
- change: name,
1159
- message: error.message
1160
- });
1161
- }
1162
- }
1163
- const workTree = await inspectWorkTree();
1164
- diagnostics.push(...workTree.diagnostics.map((diagnostic) => ({
1165
- severity: "error",
1166
- code: diagnostic.code,
1167
- change: diagnostic.input,
1168
- path: diagnostic.path,
1169
- message: diagnostic.message
1170
- })));
1171
- const groupInspection = await inspectChangeGroups({ workTree, focusTree });
1172
- diagnostics.push(...groupInspection.diagnostics);
1173
- const dependencyInspection = await inspectChangeDependencies({
1174
- workTree,
1175
- preferredOrder: groupInspection.groups.flatMap((group) => group.slices.map((slice) => slice.name))
1176
- });
1177
- diagnostics.push(...dependencyInspection.diagnostics);
1178
- const changeMap = new Map(workTree.changes.map((ref) => [ref.name, ref.path]));
1179
- const allNames = [.../* @__PURE__ */ new Set([...changeMap.keys(), ...focusedSet])].sort();
1180
- const records = [];
1181
- const nowMs = options.nowMs ?? Date.now();
1182
- for (const name of allNames) {
1183
- const path = changeMap.get(name);
1184
- let kind = "\u2014";
1185
- let title = name;
1186
- let blockerEntries = [];
1187
- let readiness = emptyReadiness(false);
1188
- let ageDays = null;
1189
- const isFocused = focusedSet.has(name);
1190
- let isBlocked = false;
1191
- let done = 0;
1192
- let total = 0;
1193
- let progressKnown = false;
1194
- if (path) {
1195
- try {
1196
- const content = await readFile3(path, "utf-8");
1197
- title = extractChangeTitle(content) ?? name;
1198
- blockerEntries = extractBlockerEntries(content);
1199
- try {
1200
- const frontmatter = parseFrontmatter(content);
1201
- kind = frontmatter?.kind ? String(frontmatter.kind) : "\u2014";
1202
- } catch (error) {
1203
- kind = "(invalid)";
1204
- runtime.push({
1205
- code: "frontmatter_parse_failed",
1206
- operation: "parseFrontmatter",
1207
- path,
1208
- message: toErrorMessage(error)
1209
- });
1210
- }
1211
- isBlocked = dependencyInspection.activeBlockers.get(name) ?? hasMeaningfulBlockers(content);
1212
- const checkboxes = countCheckboxes(content);
1213
- done = checkboxes.done;
1214
- total = checkboxes.total;
1215
- progressKnown = true;
1216
- const details = collectArchiveReadiness(content, { activeBlockers: isBlocked });
1217
- readiness = {
1218
- incompleteTasks: details.taskTodos.length,
1219
- incompleteVerify: details.verifyTodos.length,
1220
- activeBlockers: details.activeBlockers,
1221
- missingScenarios: details.missingScenarios,
1222
- deterministic: details.deterministic,
1223
- semantic: details.semantic,
1224
- archiveReady: details.archiveReady
1225
- };
1226
- } catch (error) {
1227
- diagnostics.push({
1228
- severity: "error",
1229
- code: "change_read_failed",
1230
- change: name,
1231
- path,
1232
- message: "unable to read open Change",
1233
- hint: toErrorMessage(error)
1234
- });
1235
- runtime.push({
1236
- code: "change_read_failed",
1237
- operation: "readFile",
1238
- path,
1239
- message: toErrorMessage(error)
1240
- });
1241
- }
1242
- try {
1243
- const metadata = await stat(path);
1244
- ageDays = Math.floor((nowMs - metadata.mtime.getTime()) / (1e3 * 60 * 60 * 24));
1245
- } catch (error) {
1246
- diagnostics.push({
1247
- severity: "error",
1248
- code: "change_stat_failed",
1249
- change: name,
1250
- path,
1251
- message: "unable to inspect open Change metadata",
1252
- hint: toErrorMessage(error)
1253
- });
1254
- runtime.push({
1255
- code: "change_stat_failed",
1256
- operation: "stat",
1257
- path,
1258
- message: toErrorMessage(error)
1259
- });
1260
- }
1261
- } else {
1262
- kind = "(missing)";
1263
- const path2 = focusedPaths.get(name);
1264
- diagnostics.push({
1265
- severity: "error",
1266
- code: "focused_change_missing",
1267
- change: name,
1268
- path: path2,
1269
- message: "focus marker points to a missing Change file"
1270
- });
1271
- runtime.push({
1272
- code: "focused_change_missing",
1273
- operation: "resolveChange",
1274
- path: path2,
1275
- message: "focus marker points to a missing change file"
1276
- });
1277
- }
1278
- records.push({
1279
- output: {
1280
- name,
1281
- kind,
1282
- progress: { done, total },
1283
- ageDays,
1284
- isFocused,
1285
- isBlocked,
1286
- path: path ? normalizeLogicalPath(path) : null
1287
- },
1288
- progressKnown,
1289
- title,
1290
- blockerEntries,
1291
- readiness
1292
- });
1293
- }
1294
- return {
1295
- manage,
1296
- focused: [...focusedSet].sort(),
1297
- records,
1298
- groups: groupInspection.groups,
1299
- plan: dependencyInspection.plan,
1300
- archiveTrend: await readArchiveTrend(runtime),
1301
- diagnostics,
1302
- runtime
1303
- };
1304
- }
1305
- function emptyReadiness(activeBlockers) {
1306
- return {
1307
- incompleteTasks: 0,
1308
- incompleteVerify: 0,
1309
- activeBlockers,
1310
- missingScenarios: false,
1311
- deterministic: "pass",
1312
- semantic: "needs-review",
1313
- archiveReady: activeBlockers ? "no" : "judgment"
1314
- };
1315
- }
1316
- function extractChangeTitle(content) {
1317
- const heading = content.split(/\r?\n/).find((line) => line.startsWith("# Change: "));
1318
- return heading?.slice("# Change: ".length).trim() || null;
1319
- }
1320
- function extractBlockerEntries(content) {
1321
- return extractSection(content, "Blockers").split("\n").map((line) => line.trim()).filter((line) => /^[-*]\s+/.test(line)).map((line) => line.replace(/^[-*]\s+/, "").trim()).filter((line) => line.toLowerCase() !== "none");
1322
- }
1323
- async function readArchiveTrend(runtime) {
1324
- const indexPath = join3(ARCHIVES_DIR, "INDEX.md");
1325
- if (!existsSync(indexPath))
1326
- return [];
1327
- try {
1328
- const content = await readFile3(indexPath, "utf-8");
1329
- const dateRe = /^\| (\d{4}-\d{2})-\d{2} \|/gm;
1330
- const counts = {};
1331
- let match;
1332
- while ((match = dateRe.exec(content)) !== null) {
1333
- const month = match[1];
1334
- counts[month] = (counts[month] || 0) + 1;
1335
- }
1336
- return Object.keys(counts).sort().map((month) => ({ month, count: counts[month] }));
1337
- } catch (error) {
1338
- runtime.push({
1339
- code: "archive_index_read_failed",
1340
- operation: "readFile",
1341
- path: indexPath,
1342
- message: toErrorMessage(error)
1343
- });
1344
- return [];
1345
- }
1346
- }
1105
+ // src/history/query.ts
1106
+ import { readFile as readFile4 } from "fs/promises";
1107
+
1108
+ // src/history/inspect.ts
1109
+ import { readFile as readFile3 } from "fs/promises";
1110
+ import { basename, dirname as dirname2, relative as relative3 } from "path";
1347
1111
 
1348
1112
  // src/history/model.ts
1349
1113
  var HISTORY_DEFAULT_LIMIT = 20;
@@ -1369,14 +1133,12 @@ var ArchiveHistoryError = class extends Error {
1369
1133
  };
1370
1134
 
1371
1135
  // src/history/inspect.ts
1372
- import { readFile as readFile4 } from "fs/promises";
1373
- import { basename, dirname as dirname2, relative as relative3 } from "path";
1374
1136
  var EXECUTABLE_WORK_REF_RE2 = /^[a-z0-9-]+(?:\/[a-z0-9-]+)?$/;
1375
1137
  var GROUP_NAME_RE = /^[a-z0-9-]+$/;
1376
1138
  var ARCHIVE_NAME_RE = /^(\d{4}-\d{2}-\d{2})_(.+)\.md$/;
1377
1139
  async function inspectArchiveHistory(options = {}) {
1378
1140
  const archivesDir = options.archivesDir ?? ARCHIVES_DIR;
1379
- const tree = await inspectArchiveTree({ archivesDir });
1141
+ const tree = options.archiveTree ?? await inspectArchiveTree({ archivesDir });
1380
1142
  const diagnostics = tree.diagnostics.map((diagnostic) => ({
1381
1143
  severity: "error",
1382
1144
  code: diagnostic.code,
@@ -1397,7 +1159,7 @@ async function inspectArchiveHistory(options = {}) {
1397
1159
  const path = archiveOutputPath(sourcePath, archivesDir);
1398
1160
  let content;
1399
1161
  try {
1400
- content = await readFile4(sourcePath, "utf-8");
1162
+ content = await readFile3(sourcePath, "utf-8");
1401
1163
  } catch (error) {
1402
1164
  const message = toErrorMessage(error);
1403
1165
  diagnostics.push({ severity: "error", code: "archive_read_failed", path, message: `unable to read archived work: ${message}` });
@@ -1479,7 +1241,8 @@ function parseArchiveEntry(sourcePath, path, archivesDir, content) {
1479
1241
  summary: summary.value,
1480
1242
  summaryTruncated: summary.truncated,
1481
1243
  path,
1482
- sourcePath
1244
+ sourcePath,
1245
+ searchSummary: rawSummary
1483
1246
  }
1484
1247
  };
1485
1248
  }
@@ -1527,11 +1290,11 @@ function compareDiagnostics(left, right) {
1527
1290
  }
1528
1291
 
1529
1292
  // src/history/query.ts
1530
- import { readFile as readFile5 } from "fs/promises";
1531
1293
  function queryArchiveHistory(records, query = {}) {
1532
1294
  validateArchiveHistoryQuery(query);
1533
1295
  const limit = query.limit ?? HISTORY_DEFAULT_LIMIT;
1534
- const matched = records.filter((record) => (!query.since || record.date >= query.since) && (!query.until || record.date <= query.until) && (!query.kind || record.kind === query.kind) && (!query.group || record.group === query.group)).sort(compareHistoryRecords2);
1296
+ const matched = records.filter((record) => (!query.since || record.date >= query.since) && (!query.until || record.date <= query.until) && (!query.kind || record.kind === query.kind) && (!query.group || record.group === query.group) && (!query.search || `${record.workRef}
1297
+ ${record.searchSummary ?? record.summary}`.toLowerCase().includes(query.search.toLowerCase()))).sort(compareHistoryRecords2);
1535
1298
  const selected = matched.slice(0, limit);
1536
1299
  return {
1537
1300
  records: selected.map(toOutputRecord),
@@ -1556,6 +1319,8 @@ function validateArchiveHistoryQuery(query) {
1556
1319
  throw new ArchiveHistoryError("invalid_history_kind", "history kind must be non-empty");
1557
1320
  if (query.group !== void 0 && !/^[a-z0-9-]+$/.test(query.group))
1558
1321
  throw new ArchiveHistoryError("invalid_history_group", "history group must be one lowercase kebab-case segment");
1322
+ if (query.search !== void 0 && query.search.trim() === "")
1323
+ throw new ArchiveHistoryError("invalid_history_search", "history search must be non-empty");
1559
1324
  }
1560
1325
  function selectArchiveHistoryRecord(records, selector) {
1561
1326
  const matches = "path" in selector ? records.filter((record) => record.path === selector.path) : records.filter((record) => record.workRef === selector.workRef);
@@ -1571,7 +1336,7 @@ function selectArchiveHistoryRecord(records, selector) {
1571
1336
  async function readArchiveHistoryDetail(record) {
1572
1337
  let content;
1573
1338
  try {
1574
- content = await readFile5(record.sourcePath, "utf-8");
1339
+ content = await readFile4(record.sourcePath, "utf-8");
1575
1340
  } catch (error) {
1576
1341
  throw new ArchiveHistoryError("archive_read_failed", `unable to read archived Change ${record.path}: ${error instanceof Error ? error.message : String(error)}`);
1577
1342
  }
@@ -1596,7 +1361,7 @@ function historyInspectionComplete(inspection) {
1596
1361
  return inspection.diagnostics.every((diagnostic) => diagnostic.severity !== "error");
1597
1362
  }
1598
1363
  function toOutputRecord(record) {
1599
- const { sourcePath: _sourcePath, ...output } = record;
1364
+ const { searchSummary: _searchSummary, sourcePath: _sourcePath, ...output } = record;
1600
1365
  return output;
1601
1366
  }
1602
1367
  function boundEvidence(section, omitNone = false) {
@@ -1617,6 +1382,234 @@ function compareHistoryRecords2(left, right) {
1617
1382
  return right.date.localeCompare(left.date) || left.workRef.localeCompare(right.workRef) || left.path.localeCompare(right.path);
1618
1383
  }
1619
1384
 
1385
+ // src/status/inspect.ts
1386
+ async function inspectProjectStatus(options = {}) {
1387
+ const runtime = [];
1388
+ const diagnostics = [];
1389
+ let manage = resolveManagePolicy({});
1390
+ try {
1391
+ const configInspection = await inspectRspConfig();
1392
+ const configValid = configInspection.issues.length === 0;
1393
+ manage = resolveManagePolicy(configInspection.config, { configValid });
1394
+ if (!configValid) {
1395
+ diagnostics.push({
1396
+ severity: "error",
1397
+ code: "invalid_config",
1398
+ path: CONFIG_PATH,
1399
+ message: configInspection.issues.join("; ")
1400
+ });
1401
+ }
1402
+ } catch (error) {
1403
+ manage = resolveManagePolicy({}, { configValid: false });
1404
+ diagnostics.push({
1405
+ severity: "error",
1406
+ code: "invalid_config",
1407
+ path: CONFIG_PATH,
1408
+ message: toErrorMessage(error)
1409
+ });
1410
+ }
1411
+ const focusTree = await inspectFocusTree();
1412
+ for (const diagnostic of focusTree.diagnostics) {
1413
+ diagnostics.push({
1414
+ severity: "error",
1415
+ code: diagnostic.code,
1416
+ change: diagnostic.input,
1417
+ path: diagnostic.path,
1418
+ message: diagnostic.message
1419
+ });
1420
+ }
1421
+ const rawFocusedSet = new Set(focusTree.markers.map((marker) => marker.name));
1422
+ const focusedSet = /* @__PURE__ */ new Set();
1423
+ const focusedPaths = /* @__PURE__ */ new Map();
1424
+ for (const name of rawFocusedSet) {
1425
+ try {
1426
+ const ref = resolveWorkRef(name, { executable: true });
1427
+ focusedSet.add(ref.name);
1428
+ focusedPaths.set(ref.name, ref.path);
1429
+ } catch (error) {
1430
+ if (!(error instanceof WorkRefError))
1431
+ throw error;
1432
+ diagnostics.push({
1433
+ severity: "error",
1434
+ code: error.code,
1435
+ path: `.rsp/focus.d/${name}`,
1436
+ change: name,
1437
+ message: error.message
1438
+ });
1439
+ }
1440
+ }
1441
+ const workTree = await inspectWorkTree();
1442
+ const archiveTree = await inspectArchiveTree();
1443
+ const historyInspection = await inspectArchiveHistory({ archiveTree });
1444
+ diagnostics.push(...workTree.diagnostics.map((diagnostic) => ({
1445
+ severity: "error",
1446
+ code: diagnostic.code,
1447
+ change: diagnostic.input,
1448
+ path: diagnostic.path,
1449
+ message: diagnostic.message
1450
+ })));
1451
+ const groupInspection = await inspectChangeGroups({ workTree, focusTree, archiveTree });
1452
+ diagnostics.push(...groupInspection.diagnostics);
1453
+ const dependencyInspection = await inspectChangeDependencies({
1454
+ workTree,
1455
+ archiveTree,
1456
+ preferredOrder: groupInspection.groups.flatMap((group) => group.slices.map((slice) => slice.name))
1457
+ });
1458
+ diagnostics.push(...dependencyInspection.diagnostics);
1459
+ const changeMap = new Map(workTree.changes.map((ref) => [ref.name, ref.path]));
1460
+ const allNames = [.../* @__PURE__ */ new Set([...changeMap.keys(), ...focusedSet])].sort();
1461
+ const records = [];
1462
+ const nowMs = options.nowMs ?? Date.now();
1463
+ for (const name of allNames) {
1464
+ const path = changeMap.get(name);
1465
+ let kind = "\u2014";
1466
+ let title = name;
1467
+ let blockerEntries = [];
1468
+ let readiness = emptyReadiness(false);
1469
+ let ageDays = null;
1470
+ const isFocused = focusedSet.has(name);
1471
+ let isBlocked = false;
1472
+ let done = 0;
1473
+ let total = 0;
1474
+ let progressKnown = false;
1475
+ if (path) {
1476
+ try {
1477
+ const content = await readFile5(path, "utf-8");
1478
+ title = extractChangeTitle(content) ?? name;
1479
+ blockerEntries = extractBlockerEntries(content);
1480
+ try {
1481
+ const frontmatter = parseFrontmatter(content);
1482
+ kind = frontmatter?.kind ? String(frontmatter.kind) : "\u2014";
1483
+ } catch (error) {
1484
+ kind = "(invalid)";
1485
+ runtime.push({
1486
+ code: "frontmatter_parse_failed",
1487
+ operation: "parseFrontmatter",
1488
+ path,
1489
+ message: toErrorMessage(error)
1490
+ });
1491
+ }
1492
+ isBlocked = dependencyInspection.activeBlockers.get(name) ?? hasMeaningfulBlockers(content);
1493
+ const checkboxes = countCheckboxes(content);
1494
+ done = checkboxes.done;
1495
+ total = checkboxes.total;
1496
+ progressKnown = true;
1497
+ const details = collectArchiveReadiness(content, { activeBlockers: isBlocked });
1498
+ readiness = {
1499
+ incompleteTasks: details.taskTodos.length,
1500
+ incompleteVerify: details.verifyTodos.length,
1501
+ activeBlockers: details.activeBlockers,
1502
+ missingScenarios: details.missingScenarios,
1503
+ deterministic: details.deterministic,
1504
+ semantic: details.semantic,
1505
+ archiveReady: details.archiveReady
1506
+ };
1507
+ } catch (error) {
1508
+ diagnostics.push({
1509
+ severity: "error",
1510
+ code: "change_read_failed",
1511
+ change: name,
1512
+ path,
1513
+ message: "unable to read open Change",
1514
+ hint: toErrorMessage(error)
1515
+ });
1516
+ runtime.push({
1517
+ code: "change_read_failed",
1518
+ operation: "readFile",
1519
+ path,
1520
+ message: toErrorMessage(error)
1521
+ });
1522
+ }
1523
+ try {
1524
+ const metadata = await stat(path);
1525
+ ageDays = Math.floor((nowMs - metadata.mtime.getTime()) / (1e3 * 60 * 60 * 24));
1526
+ } catch (error) {
1527
+ diagnostics.push({
1528
+ severity: "error",
1529
+ code: "change_stat_failed",
1530
+ change: name,
1531
+ path,
1532
+ message: "unable to inspect open Change metadata",
1533
+ hint: toErrorMessage(error)
1534
+ });
1535
+ runtime.push({
1536
+ code: "change_stat_failed",
1537
+ operation: "stat",
1538
+ path,
1539
+ message: toErrorMessage(error)
1540
+ });
1541
+ }
1542
+ } else {
1543
+ kind = "(missing)";
1544
+ const path2 = focusedPaths.get(name);
1545
+ diagnostics.push({
1546
+ severity: "error",
1547
+ code: "focused_change_missing",
1548
+ change: name,
1549
+ path: path2,
1550
+ message: "focus marker points to a missing Change file"
1551
+ });
1552
+ runtime.push({
1553
+ code: "focused_change_missing",
1554
+ operation: "resolveChange",
1555
+ path: path2,
1556
+ message: "focus marker points to a missing change file"
1557
+ });
1558
+ }
1559
+ records.push({
1560
+ output: {
1561
+ name,
1562
+ kind,
1563
+ progress: { done, total },
1564
+ ageDays,
1565
+ isFocused,
1566
+ isBlocked,
1567
+ path: path ? normalizeLogicalPath(path) : null
1568
+ },
1569
+ progressKnown,
1570
+ title,
1571
+ blockerEntries,
1572
+ readiness
1573
+ });
1574
+ }
1575
+ return {
1576
+ manage,
1577
+ focused: [...focusedSet].sort(),
1578
+ records,
1579
+ groups: groupInspection.groups,
1580
+ plan: dependencyInspection.plan,
1581
+ archiveTrend: historyInspectionComplete(historyInspection) ? deriveArchiveTrend(historyInspection.records) : [],
1582
+ diagnostics: [...diagnostics, ...historyInspection.rootExists ? historyInspection.diagnostics : []],
1583
+ runtime: [...runtime, ...historyInspection.runtime]
1584
+ };
1585
+ }
1586
+ function emptyReadiness(activeBlockers) {
1587
+ return {
1588
+ incompleteTasks: 0,
1589
+ incompleteVerify: 0,
1590
+ activeBlockers,
1591
+ missingScenarios: false,
1592
+ deterministic: "pass",
1593
+ semantic: "needs-review",
1594
+ archiveReady: activeBlockers ? "no" : "judgment"
1595
+ };
1596
+ }
1597
+ function extractChangeTitle(content) {
1598
+ const heading = content.split(/\r?\n/).find((line) => line.startsWith("# Change: "));
1599
+ return heading?.slice("# Change: ".length).trim() || null;
1600
+ }
1601
+ function extractBlockerEntries(content) {
1602
+ return extractSection(content, "Blockers").split("\n").map((line) => line.trim()).filter((line) => /^[-*]\s+/.test(line)).map((line) => line.replace(/^[-*]\s+/, "").trim()).filter((line) => line.toLowerCase() !== "none");
1603
+ }
1604
+ function deriveArchiveTrend(records) {
1605
+ const counts = /* @__PURE__ */ new Map();
1606
+ for (const record of records) {
1607
+ const month = record.date.slice(0, 7);
1608
+ counts.set(month, (counts.get(month) ?? 0) + 1);
1609
+ }
1610
+ return [...counts].sort(([left], [right]) => left.localeCompare(right)).map(([month, count]) => ({ month, count }));
1611
+ }
1612
+
1620
1613
  // src/status/dependency-forest.ts
1621
1614
  function defaultRoots(plan) {
1622
1615
  const selected = new Set(plan.nodes.filter((node) => node.selection === "selected").map((node) => node.name));
@@ -14,17 +14,21 @@ import { chmod, copyFile, mkdir, readdir, readFile, rename, rm } from "fs/promis
14
14
  import { dirname, join, relative } from "path";
15
15
  var DEFAULT_PACKAGED_SKILL_NAMES = [
16
16
  "rsp",
17
- "rsp-address-review",
18
17
  "rsp-commit",
19
18
  "rsp-design",
20
19
  "rsp-diagnose",
21
20
  "rsp-implement",
22
21
  "rsp-manage",
23
22
  "rsp-release-docs",
23
+ "rsp-resolve-findings",
24
24
  "rsp-review",
25
25
  "rsp-shape",
26
26
  "rsp-tdd"
27
27
  ];
28
+ var OBSOLETE_PACKAGED_SKILL_RENAMES = {
29
+ "rsp-address-review": "rsp-resolve-findings",
30
+ "rsp-codebase-audit": "rsp-structural-audit"
31
+ };
28
32
  function unsupportedEntry(path, label) {
29
33
  return new ManagedPathError(path, `unsupported entry in the ${label} tree: ${path}`);
30
34
  }
@@ -199,8 +203,9 @@ async function installPackagedSkills(args = {}, options = {}) {
199
203
  }
200
204
  const skills = requestedNames.map((name) => packagedSkillsByName.get(name)).sort((a, b) => a.name.localeCompare(b.name));
201
205
  const targetRoot = resolveManagedDirectoryChain(projectRoot, [".agents", "skills"], "project Skills root");
202
- const result = { installed: [], unchanged: [], replaced: [] };
206
+ const result = { installed: [], removed: [], unchanged: [], replaced: [] };
203
207
  const conflicts = [];
208
+ const obsoleteConflicts = [];
204
209
  const targetTreeIdentities = /* @__PURE__ */ new Map();
205
210
  for (const skill of skills) {
206
211
  const target = join(targetRoot, skill.name);
@@ -221,8 +226,30 @@ async function installPackagedSkills(args = {}, options = {}) {
221
226
  conflicts.push(skill.name);
222
227
  }
223
228
  }
229
+ const selectedNames = new Set(skills.map((skill) => skill.name));
230
+ const obsoleteTreeIdentities = /* @__PURE__ */ new Map();
231
+ for (const [obsolete, replacement] of Object.entries(OBSOLETE_PACKAGED_SKILL_RENAMES)) {
232
+ if (!selectedNames.has(replacement))
233
+ continue;
234
+ const target = join(targetRoot, obsolete);
235
+ if (inspectTarget(target, obsolete) === "missing")
236
+ continue;
237
+ const targetIdentity = inspectDirectoryIdentity(target, `obsolete installed Skill ${obsolete}`);
238
+ assertDirectoryIdentities([targetIdentity], `obsolete installed Skill ${obsolete}`);
239
+ if (args.force) {
240
+ result.removed.push(obsolete);
241
+ obsoleteTreeIdentities.set(obsolete, targetIdentity);
242
+ } else {
243
+ obsoleteConflicts.push(`${obsolete} -> ${replacement}`);
244
+ }
245
+ }
246
+ const conflictMessages = [];
224
247
  if (conflicts.length > 0)
225
- throw new Error(`conflicting packaged Skills: ${conflicts.join(", ")}; rerun with --force to replace only these package-owned directories`);
248
+ conflictMessages.push(`conflicting packaged Skills: ${conflicts.join(", ")}`);
249
+ if (obsoleteConflicts.length > 0)
250
+ conflictMessages.push(`obsolete packaged Skill renames: ${obsoleteConflicts.join(", ")}`);
251
+ if (conflictMessages.length > 0)
252
+ throw new Error(`${conflictMessages.join("; ")}; rerun with --force to replace or remove only these package-owned directories`);
226
253
  if (args.dryRun)
227
254
  return result;
228
255
  await options.onMutationStep?.({ phase: "before-target-root-mutation", targetRoot });
@@ -252,6 +279,15 @@ async function installPackagedSkills(args = {}, options = {}) {
252
279
  for (const name of result.replaced) {
253
280
  assertDirectoryIdentities([targetTreeIdentities.get(name)], `installed Skill ${name}`);
254
281
  }
282
+ for (const name of result.removed) {
283
+ assertDirectoryIdentities([obsoleteTreeIdentities.get(name)], `obsolete installed Skill ${name}`);
284
+ }
285
+ for (const name of result.removed) {
286
+ assertDirectoryIdentities(targetIdentities, "project Skills root");
287
+ assertDirectoryIdentities([obsoleteTreeIdentities.get(name)], `obsolete installed Skill ${name}`);
288
+ await renamePath(join(targetRoot, name), join(previousRoot, name));
289
+ movedPrevious.push(name);
290
+ }
255
291
  for (const name of result.replaced) {
256
292
  assertDirectoryIdentities(targetIdentities, "project Skills root");
257
293
  assertDirectoryIdentities([targetTreeIdentities.get(name)], `installed Skill ${name}`);
@@ -308,7 +344,7 @@ async function installPackagedSkills(args = {}, options = {}) {
308
344
  }
309
345
  function printSkillInstallResult(result, dryRun = false) {
310
346
  const prefix = dryRun ? "would be " : "";
311
- for (const status of ["installed", "unchanged", "replaced"]) {
347
+ for (const status of ["installed", "unchanged", "replaced", "removed"]) {
312
348
  if (result[status].length > 0)
313
349
  console.log(` ${prefix}${status}: ${result[status].join(", ")}`);
314
350
  }