pi-anchor 0.2.3 → 0.3.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.
@@ -30,7 +30,9 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/extension.ts
31
31
  var extension_exports = {};
32
32
  __export(extension_exports, {
33
- default: () => extension_default
33
+ default: () => extension_default,
34
+ parseAnchorCommandInput: () => parseAnchorCommandInput,
35
+ resolveAnchorTarget: () => resolveAnchorTarget
34
36
  });
35
37
  module.exports = __toCommonJS(extension_exports);
36
38
  var import_typebox = require("@sinclair/typebox");
@@ -42,15 +44,15 @@ var import_node_os = __toESM(require("os"), 1);
42
44
 
43
45
  // src/types.ts
44
46
  var EPHEMERAL_DECAY_POLICY = {
45
- activeDays: 1,
46
- // 24 hours active
47
- sleepDays: 1,
48
- // 24 hours sleeping
49
- graveyardDays: 2
50
- // 48 hours total before auto-clearing
47
+ activeDays: 7,
48
+ // 7 days active before quiet sleep
49
+ sleepDays: 30,
50
+ graveyardDays: 9999
51
+ // Never silently auto-delete unfinished tasks
51
52
  };
52
53
  var DURABLE_DECAY_POLICY = {
53
- activeDays: 3,
54
+ activeDays: 7,
55
+ // 7 days active before quiet sleep
54
56
  sleepDays: 30,
55
57
  graveyardDays: 9999
56
58
  // Never permanently auto-dropped; preserved indefinitely
@@ -87,6 +89,39 @@ function isSameOrSubPath(anchorCwd, targetCwd) {
87
89
  const cmpT = isWinStyle ? normT.toLowerCase() : normT;
88
90
  return cmpA === cmpT || cmpT.startsWith(cmpA + "/") || cmpA.startsWith(cmpT + "/");
89
91
  }
92
+ function extractPathsFromText(text) {
93
+ if (!text) return [];
94
+ const results = /* @__PURE__ */ new Set();
95
+ const regex = /(?:[a-zA-Z]:[\\/][^\s"'`,。;!?、,;)>\]]+|(?:\b(?:src|lib|dist|tests|test|docs|scripts|packages|extensions|bin|assets)[\\/][^\s"'`,。;!?、,;)>\]]+)|(?:\b[\w.-]+\.(?:ts|tsx|js|jsx|mjs|cjs|py|rs|go|java|c|cpp|h|cs|md|json|yaml|yml|toml|html|css|sql|sh)\b))/g;
96
+ let match;
97
+ while ((match = regex.exec(text)) !== null) {
98
+ const cleaned = normalizePath(match[0].replace(/[.:;,'")\]}>]+$/, ""));
99
+ if (cleaned && cleaned.length >= 3) {
100
+ results.add(cleaned);
101
+ }
102
+ }
103
+ return Array.from(results);
104
+ }
105
+ function isAnchorTargetingWorkspace(anchor, targetCwd) {
106
+ if (!targetCwd) return true;
107
+ const normTarget = targetCwd.trim().replace(/\\/g, "/").replace(/\/+$/, "");
108
+ const targetBase = import_node_path.default.basename(normTarget).toLowerCase();
109
+ if (anchor.project && anchor.project !== "global") {
110
+ const normProj = anchor.project.trim().replace(/\\/g, "/").replace(/\/+$/, "");
111
+ if (isSameOrSubPath(normProj, normTarget)) return true;
112
+ const projBase = import_node_path.default.basename(normProj).toLowerCase();
113
+ if (projBase && projBase === targetBase) return true;
114
+ }
115
+ if (anchor.files && anchor.files.length > 0) {
116
+ for (const f of anchor.files) {
117
+ if (isSameOrSubPath(f, normTarget)) return true;
118
+ }
119
+ }
120
+ if (anchor.cwd && isSameOrSubPath(anchor.cwd, normTarget)) {
121
+ return true;
122
+ }
123
+ return false;
124
+ }
90
125
  function matchAnchorAgainstTouchedFiles(anchor, touchedFiles) {
91
126
  if (!anchor.files || anchor.files.length === 0) return null;
92
127
  const normTouched = touchedFiles.map((f) => normalizePath(f, anchor.cwd)).filter(Boolean);
@@ -189,10 +224,10 @@ function detectDurability(title) {
189
224
  function getEphemeralDecayPolicy(title) {
190
225
  const lower = title.toLowerCase();
191
226
  if (lower.includes("\u5927\u540E\u5929")) {
192
- return { activeDays: 3, sleepDays: 1, graveyardDays: 4 };
227
+ return { activeDays: 7, sleepDays: 30, graveyardDays: 9999 };
193
228
  }
194
229
  if (lower.includes("\u540E\u5929")) {
195
- return { activeDays: 2, sleepDays: 1, graveyardDays: 3 };
230
+ return { activeDays: 7, sleepDays: 30, graveyardDays: 9999 };
196
231
  }
197
232
  return { ...EPHEMERAL_DECAY_POLICY };
198
233
  }
@@ -503,7 +538,17 @@ var AnchorStore = class {
503
538
  const state = this.loadState();
504
539
  const now = Date.now();
505
540
  const id = this.generateId(state);
506
- const cwd = input.cwd ? normalizePath(input.cwd) : "";
541
+ const explicitFiles = (input.files || []).map((f) => normalizePath(f)).filter(Boolean);
542
+ const inferredFiles = explicitFiles.length > 0 ? explicitFiles : extractPathsFromText(`${input.title} ${input.description || ""}`);
543
+ let cwd = input.cwd ? normalizePath(input.cwd) : "";
544
+ if (input.project && (/^[a-zA-Z]:[\\/]/.test(input.project.trim()) || input.project.trim().startsWith("/"))) {
545
+ cwd = normalizePath(input.project.trim());
546
+ } else if (inferredFiles.length > 0 && (/^[a-zA-Z]:\//.test(inferredFiles[0]) || inferredFiles[0].startsWith("/"))) {
547
+ const firstDir = import_node_path2.default.dirname(inferredFiles[0]);
548
+ if (firstDir && firstDir !== ".") {
549
+ cwd = normalizePath(firstDir);
550
+ }
551
+ }
507
552
  const projectName = input.project ? input.project.trim() : cwd ? import_node_path2.default.basename(cwd) : "global";
508
553
  const recurrence = input.recurrence || detectRecurrence(input.title);
509
554
  const targetDate = input.targetDate || detectTargetDate(input.title, now);
@@ -523,7 +568,7 @@ var AnchorStore = class {
523
568
  createdAt: now,
524
569
  updatedAt: now,
525
570
  lastTouchedAt: now,
526
- files: (input.files || []).map((f) => normalizePath(f)).filter(Boolean),
571
+ files: inferredFiles,
527
572
  tags: (input.tags || []).map((t) => t.trim()).filter(Boolean),
528
573
  verifyCommand: input.verifyCommand?.trim() || void 0,
529
574
  decay: input.decay ? { ...input.decay } : { ...decayPolicy }
@@ -562,7 +607,7 @@ var AnchorStore = class {
562
607
  if (filter?.status && a.status !== filter.status) return false;
563
608
  if (filter?.priority && a.priority !== filter.priority) return false;
564
609
  if (!filter?.all && targetCwd) {
565
- if (a.files.length > 0 && a.cwd && !isSameOrSubPath(a.cwd, targetCwd)) {
610
+ if (a.files.length > 0 && (a.cwd || a.project) && !isAnchorTargetingWorkspace(a, targetCwd)) {
566
611
  return false;
567
612
  }
568
613
  }
@@ -1115,20 +1160,66 @@ function runPhysicalVerification(anchor, cwd) {
1115
1160
  return { success: false, output: String(err.stderr || err.stdout || err.message).trim() };
1116
1161
  }
1117
1162
  }
1118
- function generateSettlementProposals(store, touchedFiles, cwd) {
1163
+ function tryRealtimeAutoSettle(store, observer, cwd, executedCommand) {
1164
+ const candidates = store.list({ cwd }).filter((a) => a.status === "active" || a.status === "sleeping");
1165
+ if (candidates.length === 0) return [];
1166
+ const settledList = [];
1167
+ const cmdTrimmed = (executedCommand || "").trim();
1168
+ for (const a of candidates) {
1169
+ if (observer.hasCommitted()) {
1170
+ const commitMatch = observer.matchesCommit(a);
1171
+ if (commitMatch.matched && (!cwd || isAnchorTargetingWorkspace(a, cwd))) {
1172
+ const settled = store.settle(a.id, {
1173
+ settledBy: "verification-test",
1174
+ summary: `Auto-settled via Git commit: ${commitMatch.message}`,
1175
+ touchedFiles: observer.getModifiedFiles()
1176
+ });
1177
+ settledList.push({ anchor: settled, reason: `git commit ("${commitMatch.message}")` });
1178
+ continue;
1179
+ }
1180
+ }
1181
+ if (a.verifyCommand && cmdTrimmed && cmdTrimmed.includes(a.verifyCommand.trim())) {
1182
+ const settled = store.settle(a.id, {
1183
+ settledBy: "verification-test",
1184
+ summary: `Auto-settled via passing command: ${a.verifyCommand}`,
1185
+ touchedFiles: observer.getModifiedFiles()
1186
+ });
1187
+ settledList.push({ anchor: settled, reason: `verifyCommand (${a.verifyCommand})` });
1188
+ }
1189
+ }
1190
+ return settledList;
1191
+ }
1192
+ function generateSettlementProposals(store, touchedFiles, cwd, observer) {
1119
1193
  const activeAndSleeping = store.list({ cwd }).filter((a) => a.status === "active" || a.status === "sleeping");
1120
1194
  const matches = findMatchedAnchors(activeAndSleeping, touchedFiles);
1195
+ const seenIds = /* @__PURE__ */ new Set();
1121
1196
  const proposals = [];
1122
1197
  for (const match of matches) {
1198
+ seenIds.add(match.anchor.id);
1123
1199
  const fileList = match.matchedFiles.slice(0, 3).join(", ");
1124
- const moreSuffix = match.matchedFiles.length > 3 ? ` \u7B49 ${match.matchedFiles.length} \u4E2A\u6587\u4EF6` : "";
1200
+ const moreSuffix = match.matchedFiles.length > 3 ? ` (+${match.matchedFiles.length - 3} more)` : "";
1125
1201
  proposals.push({
1126
1202
  anchor: match.anchor,
1127
1203
  matchedFiles: match.matchedFiles,
1128
- reason: `\u672C\u6B21\u4F1A\u8BDD\u4FEE\u6539\u4E86 ${fileList}${moreSuffix}`,
1204
+ reason: `Modified ${fileList}${moreSuffix}`,
1129
1205
  recommendedAction: match.score >= 0.85 ? "settle" : "defer"
1130
1206
  });
1131
1207
  }
1208
+ if (observer && observer.hasCommitted()) {
1209
+ for (const a of activeAndSleeping) {
1210
+ if (seenIds.has(a.id)) continue;
1211
+ const cm = observer.matchesCommit(a);
1212
+ if (cm.matched) {
1213
+ seenIds.add(a.id);
1214
+ proposals.push({
1215
+ anchor: a,
1216
+ matchedFiles: touchedFiles.slice(0, 3),
1217
+ reason: `Matched git commit: "${cm.message}"`,
1218
+ recommendedAction: "settle"
1219
+ });
1220
+ }
1221
+ }
1222
+ }
1132
1223
  return proposals;
1133
1224
  }
1134
1225
 
@@ -1163,6 +1254,27 @@ function groupAnchorsByQuadrant(anchors, now = Date.now()) {
1163
1254
  }
1164
1255
  return groups;
1165
1256
  }
1257
+ var PRIORITY_RANK = { p0: 0, p1: 1, p2: 2 };
1258
+ function sortAnchorsForDisplay(anchors, cwd, now = Date.now()) {
1259
+ const todayStr = getTodayDateString(now);
1260
+ return [...anchors].sort((a, b) => {
1261
+ if (a.status !== b.status) {
1262
+ return a.status === "active" ? -1 : 1;
1263
+ }
1264
+ if (cwd) {
1265
+ const aMatch = isAnchorTargetingWorkspace(a, cwd) ? 0 : 1;
1266
+ const bMatch = isAnchorTargetingWorkspace(b, cwd) ? 0 : 1;
1267
+ if (aMatch !== bMatch) return aMatch - bMatch;
1268
+ }
1269
+ const aDue = a.targetDate && a.targetDate <= todayStr ? 0 : 1;
1270
+ const bDue = b.targetDate && b.targetDate <= todayStr ? 0 : 1;
1271
+ if (aDue !== bDue) return aDue - bDue;
1272
+ const pA = PRIORITY_RANK[a.priority] ?? 1;
1273
+ const pB = PRIORITY_RANK[b.priority] ?? 1;
1274
+ if (pA !== pB) return pA - pB;
1275
+ return a.createdAt - b.createdAt;
1276
+ });
1277
+ }
1166
1278
  var ANSI_REGEX = /\x1b\[[0-9;]*[a-zA-Z]|\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)/g;
1167
1279
  function stripAnsi(str) {
1168
1280
  return str.replace(ANSI_REGEX, "");
@@ -1285,11 +1397,19 @@ function formatCreationTime(timestamp, now = Date.now()) {
1285
1397
  }
1286
1398
  function formatOrigin(anchor) {
1287
1399
  if (typeof anchor === "object") {
1288
- if (!anchor.cwd) return anchor.project || "global";
1289
- return import_node_path4.default.basename(anchor.cwd) || "global";
1400
+ if (anchor.project && anchor.project !== "global") {
1401
+ const normProj = anchor.project.replace(/\\/g, "/").replace(/\/+$/, "");
1402
+ if (normProj.includes("/")) {
1403
+ const parts = normProj.split("/").filter(Boolean);
1404
+ return parts.slice(-2).join("/");
1405
+ }
1406
+ return normProj;
1407
+ }
1408
+ if (!anchor.cwd) return "global";
1409
+ return import_node_path4.default.basename(anchor.cwd.replace(/\\/g, "/")) || "global";
1290
1410
  }
1291
1411
  if (!anchor) return "global";
1292
- return import_node_path4.default.basename(anchor) || "global";
1412
+ return import_node_path4.default.basename(anchor.replace(/\\/g, "/")) || "global";
1293
1413
  }
1294
1414
  function updateAnchorStatusBar(ctx, store) {
1295
1415
  if (!hasActiveUI(ctx)) return;
@@ -1344,51 +1464,47 @@ function updateStartupBanner(ctx, store) {
1344
1464
  }
1345
1465
  async function openAnchorDashboard(ctx, store) {
1346
1466
  if (!hasActiveUI(ctx)) return;
1347
- const list = store.list({ status: "active", cwd: ctx.cwd });
1348
- if (list.length === 0) {
1467
+ const allUnfinished = store.list({ cwd: ctx.cwd, all: true }).filter((a) => a.status === "active" || a.status === "sleeping");
1468
+ if (allUnfinished.length === 0) {
1349
1469
  ctx.ui.notify("\u2316 No active anchors. Use /pin <task> to record.", "info");
1350
1470
  return;
1351
1471
  }
1352
- const groups = groupAnchorsByQuadrant(list);
1353
- const sortedList = [
1354
- ...groups.today,
1355
- ...groups.upcoming,
1356
- ...groups.habits,
1357
- ...groups.backlog
1358
- ];
1472
+ const sortedList = sortAnchorsForDisplay(allUnfinished, ctx.cwd);
1359
1473
  const optionMap = /* @__PURE__ */ new Map();
1360
1474
  const displayOptions = [];
1361
1475
  const termCols = process.stdout?.columns || 96;
1362
1476
  const isNarrow = termCols < 88;
1363
- const titleWidth = isNarrow ? Math.max(18, termCols - 36) : 34;
1477
+ const titleWidth = isNarrow ? Math.max(18, termCols - 36) : 36;
1364
1478
  for (let i = 0; i < sortedList.length; i++) {
1365
1479
  const a = sortedList[i];
1366
1480
  const num = a.id.replace(/^anc-/, "").padStart(2, "0");
1367
- const category = padToWidth(`[${classifyAnchor(a)}]`, 12);
1481
+ const badge = a.status === "sleeping" ? padToWidth("[Quiet]", 10) : padToWidth(`[${a.priority.toUpperCase()}]`, 10);
1368
1482
  const origin = formatOrigin(a);
1369
- const target = formatTargetDate(a);
1483
+ const target = a.status === "sleeping" ? "Quiet (0 tok)" : formatTargetDate(a);
1370
1484
  const created = formatCreationTime(a.createdAt);
1371
1485
  const titleWithFiles = a.files && a.files.length > 0 ? `${a.title} [${a.files.slice(0, 1).join(", ")}]` : a.title;
1372
1486
  const colNum = `${num} `;
1373
1487
  const colTitle = padToWidth(titleWithFiles, titleWidth);
1374
- const colOrigin = padToWidth(origin, 10);
1375
- const colTarget = padToWidth(target, 12);
1488
+ const colOrigin = padToWidth(origin, 12);
1489
+ const colTarget = padToWidth(target, 14);
1376
1490
  const colCreated = created;
1377
- const label = isNarrow ? `${colNum}${category}${colTitle} ${colTarget}`.trimEnd() : `${colNum}${category}${colTitle} ${colOrigin} ${colTarget} ${colCreated}`.trimEnd();
1491
+ const label = isNarrow ? `${colNum}${badge}${colTitle} ${colTarget}`.trimEnd() : `${colNum}${badge}${colTitle} ${colOrigin} ${colTarget} ${colCreated}`.trimEnd();
1378
1492
  optionMap.set(label, a);
1379
1493
  displayOptions.push(label);
1380
1494
  }
1381
- const selected = await ctx.ui.select("\u2316 Anchors (select to inspect or settle):", displayOptions);
1495
+ const selected = await ctx.ui.select("\u2316 Anchors (select to settle, wake, or inspect):", displayOptions);
1382
1496
  if (!selected) return;
1383
1497
  const anchor = optionMap.get(selected);
1384
1498
  if (!anchor) return;
1499
+ const actions = [
1500
+ "\u2713 Settle (Mark completed and archive)",
1501
+ ...anchor.status === "sleeping" ? ["\u26A1 Wake up (Activate for current session)"] : [],
1502
+ "\u2139 View details & associated files",
1503
+ "\u21A9 Cancel"
1504
+ ];
1385
1505
  const action = await ctx.ui.select(
1386
1506
  `\u2316 Anchor #${anchor.id}: "${anchor.title}"`,
1387
- [
1388
- "\u2713 Settle (Mark completed and archive)",
1389
- "\u2139 View details & associated files",
1390
- "\u21A9 Cancel"
1391
- ]
1507
+ actions
1392
1508
  );
1393
1509
  if (!action || action.startsWith("\u21A9")) return;
1394
1510
  if (action.startsWith("\u2713")) {
@@ -1396,9 +1512,13 @@ async function openAnchorDashboard(ctx, store) {
1396
1512
  const successMsg = anchor.recurrence === "daily" ? `\u2316 Completed for today: "${anchor.title}" (resets tomorrow)` : `\u2316 Settled #${anchor.id}: "${anchor.title}" (/anchor undo to revert)`;
1397
1513
  ctx.ui.notify(successMsg, "info");
1398
1514
  updateAnchorStatusBar(ctx, store);
1515
+ } else if (action.startsWith("\u26A1")) {
1516
+ store.touch(anchor.id);
1517
+ ctx.ui.notify(`\u2316 Woke up #${anchor.id}: "${anchor.title}"`, "info");
1518
+ updateAnchorStatusBar(ctx, store);
1399
1519
  } else if (action.startsWith("\u2139")) {
1400
1520
  const details = [
1401
- `#${anchor.id} [${anchor.priority.toUpperCase()}] ${anchor.title}`,
1521
+ `#${anchor.id} [${anchor.priority.toUpperCase()} \xB7 ${anchor.status.toUpperCase()}] ${anchor.title}`,
1402
1522
  anchor.description ? `Description: ${anchor.description}` : null,
1403
1523
  anchor.targetDate ? `Target Date: ${anchor.targetDate} (${formatTargetDate(anchor)})` : null,
1404
1524
  anchor.files && anchor.files.length > 0 ? `Files: ${anchor.files.join(", ")}` : null,
@@ -1414,6 +1534,49 @@ var import_node_child_process2 = require("child_process");
1414
1534
  var import_node_fs3 = __toESM(require("fs"), 1);
1415
1535
  var import_node_path5 = __toESM(require("path"), 1);
1416
1536
  var MUTATION_TOOLS2 = /* @__PURE__ */ new Set(["edit", "write", "patch", "apply_diff", "create_file", "modify"]);
1537
+ function parseAnchorCommandInput(raw, now = Date.now()) {
1538
+ const trimmed = (raw || "").trim();
1539
+ if (!trimmed || trimmed === "list" || trimmed === "ls") {
1540
+ return { mode: "dashboard" };
1541
+ }
1542
+ if (trimmed === "undo") {
1543
+ return { mode: "undo" };
1544
+ }
1545
+ const settleMatch = trimmed.match(/^(?:done|rm|close|settle)\s+(.+)$/i);
1546
+ if (settleMatch) {
1547
+ return {
1548
+ mode: "settle",
1549
+ targetId: settleMatch[1].trim().replace(/^#/, "")
1550
+ };
1551
+ }
1552
+ let targetDate;
1553
+ const tokens = trimmed.split(/\s+/);
1554
+ const dueIdx = tokens.findIndex((t) => t === "--due" || t === "-d");
1555
+ if (dueIdx !== -1 && tokens[dueIdx + 1]) {
1556
+ const rawDue = tokens[dueIdx + 1];
1557
+ targetDate = detectTargetDate(rawDue, now) || rawDue;
1558
+ tokens.splice(dueIdx, 2);
1559
+ }
1560
+ const cleanTitle = tokens.join(" ").trim();
1561
+ return {
1562
+ mode: "pin",
1563
+ title: cleanTitle || trimmed,
1564
+ targetDate
1565
+ };
1566
+ }
1567
+ function resolveAnchorTarget(store, idOrIndex, cwd) {
1568
+ const clean = idOrIndex.trim().replace(/^#/, "");
1569
+ if (/^\d+$/.test(clean)) {
1570
+ const seqIndex = parseInt(clean, 10);
1571
+ const visible = store.list({ cwd, all: true }).filter((a) => a.status === "active" || a.status === "sleeping");
1572
+ const sorted = sortAnchorsForDisplay(visible, cwd);
1573
+ if (seqIndex >= 1 && seqIndex <= sorted.length) {
1574
+ return sorted[seqIndex - 1].id;
1575
+ }
1576
+ }
1577
+ const direct = store.get(clean);
1578
+ return direct?.id;
1579
+ }
1417
1580
  function extension_default(pi) {
1418
1581
  const store = new AnchorStore();
1419
1582
  const observer = new SessionTouchObserver();
@@ -1424,8 +1587,12 @@ function extension_default(pi) {
1424
1587
  annotatedThisSession.clear();
1425
1588
  preflightFailures.clear();
1426
1589
  const sweep = sweepStore(store);
1427
- if (sweep.transitionedToSleeping.length > 0) {
1428
- ctx.ui.notify(`Anchor: ${sweep.transitionedToSleeping.length} \u4E2A\u975E\u6D3B\u8DC3\u4EFB\u52A1\u5DF2\u8FDB\u5165\u4F11\u7720`, "info");
1590
+ const sleepingForWorkspace = store.list({ status: "sleeping", all: true }).filter((a) => (a.files.length > 0 || a.project && a.project !== "global" || a.cwd) && isAnchorTargetingWorkspace(a, ctx.cwd));
1591
+ for (const sa of sleepingForWorkspace) {
1592
+ store.touch(sa.id);
1593
+ }
1594
+ if (sweep.transitionedToSleeping.length > 0 && sleepingForWorkspace.length === 0) {
1595
+ ctx.ui.notify(`\u2316 Anchor: ${sweep.transitionedToSleeping.length} inactive task(s) moved to quiet mode (/anchor to view)`, "info");
1429
1596
  }
1430
1597
  const activeAnchors = store.list({ cwd: ctx.cwd }).filter((a) => a.status === "active");
1431
1598
  for (const a of activeAnchors) {
@@ -1437,7 +1604,7 @@ function extension_default(pi) {
1437
1604
  settledBy: "verification-test",
1438
1605
  summary: `Physical pre-flight test passed: ${a.verifyCommand}`
1439
1606
  });
1440
- ctx.ui.notify(`\u2693 \u7269\u7406\u9A8C\u8BC1\u901A\u8FC7\uFF01\u951A\u70B9 #${a.id} ("${a.title}") \u5DF2\u81EA\u52A8\u7ED3\u6848\u5F52\u6863\u3002`, "info");
1607
+ ctx.ui.notify(`\u2316 Verification passed! Auto-settled #${a.id}: "${a.title}"`, "info");
1441
1608
  } else {
1442
1609
  preflightFailures.set(a.id, res.output.slice(-250));
1443
1610
  }
@@ -1494,8 +1661,26 @@ ${contextSnippet}`
1494
1661
  }
1495
1662
  });
1496
1663
  pi.on("tool_result", async (event, ctx) => {
1664
+ const lowerTool = String(event.toolName || "").toLowerCase();
1497
1665
  if (!event.isError) {
1498
1666
  observer.recordToolCall(event.toolName, event.input || {});
1667
+ if (lowerTool === "bash" || lowerTool === "powershell") {
1668
+ const executedCmd = String(event.input?.command || "");
1669
+ if (executedCmd.includes("git commit") || executedCmd.length > 0) {
1670
+ const settledNow = tryRealtimeAutoSettle(store, observer, ctx.cwd, executedCmd);
1671
+ if (settledNow.length > 0) {
1672
+ updateAnchorStatusBar(ctx, store);
1673
+ if (hasActiveUI(ctx)) {
1674
+ for (const item of settledNow) {
1675
+ try {
1676
+ ctx.ui.notify(`\u2316 Auto-settled #${item.anchor.id} ("${item.anchor.title}") via ${item.reason}`, "info");
1677
+ } catch {
1678
+ }
1679
+ }
1680
+ }
1681
+ }
1682
+ }
1683
+ }
1499
1684
  }
1500
1685
  const pathInput = event.input?.path;
1501
1686
  if (typeof pathInput !== "string") return;
@@ -1559,8 +1744,8 @@ ${contextSnippet}`
1559
1744
  }
1560
1745
  }
1561
1746
  const modified = observer.getModifiedFiles();
1562
- if (modified.length === 0) return;
1563
- const proposals = generateSettlementProposals(store, modified, ctx.cwd);
1747
+ if (modified.length === 0 && !observer.hasCommitted()) return;
1748
+ const proposals = generateSettlementProposals(store, modified, ctx.cwd, observer);
1564
1749
  if (proposals.length === 0) return;
1565
1750
  for (const prop of proposals) {
1566
1751
  const a = prop.anchor;
@@ -1769,14 +1954,14 @@ Mark as completed and archive?`
1769
1954
  }
1770
1955
  });
1771
1956
  pi.registerCommand("anchor", {
1772
- description: "Inspect and settle active anchors",
1957
+ description: "Inspect, pin, or settle active anchors (e.g. /anchor, /anchor done 1, /anchor undo)",
1773
1958
  handler: async (args, ctx) => {
1774
- const input = (args || "").trim();
1775
- if (!input || input === "list" || input === "ls") {
1959
+ const parsed = parseAnchorCommandInput(args);
1960
+ if (parsed.mode === "dashboard") {
1776
1961
  await openAnchorDashboard(ctx, store);
1777
1962
  return;
1778
1963
  }
1779
- if (input === "undo") {
1964
+ if (parsed.mode === "undo") {
1780
1965
  try {
1781
1966
  const restored = store.undoSettle();
1782
1967
  ctx.ui.notify(`\u2316 Restored #${restored.id}: "${restored.title}"`, "info");
@@ -1786,29 +1971,68 @@ Mark as completed and archive?`
1786
1971
  }
1787
1972
  return;
1788
1973
  }
1789
- const targetId = input.startsWith("#") ? input.slice(1) : input;
1790
- const item = store.get(targetId);
1791
- if (item) {
1792
- store.settle(targetId, { settledBy: "manual-command" });
1793
- ctx.ui.notify(`\u2316 Settled: "${item.title}"`, "info");
1974
+ if (parsed.mode === "settle" && parsed.targetId) {
1975
+ const resolvedId = resolveAnchorTarget(store, parsed.targetId, ctx.cwd);
1976
+ if (!resolvedId) {
1977
+ ctx.ui.notify(`\u2316 Anchor not found: ${parsed.targetId}`, "error");
1978
+ return;
1979
+ }
1980
+ const settled = store.settle(resolvedId, { settledBy: "manual-command" });
1981
+ ctx.ui.notify(`\u2316 Settled #${settled.id}: "${settled.title}" (/anchor undo to revert)`, "info");
1794
1982
  updateAnchorStatusBar(ctx, store);
1795
1983
  return;
1796
1984
  }
1797
- const anc = store.create({ title: input, cwd: ctx.cwd });
1798
- ctx.ui.notify(`\u2316 Pinned #${anc.id}: "${anc.title}"`, "info");
1985
+ const rawTrimmed = (args || "").trim();
1986
+ if (/^(?:#?anc-\d+|#\d+)$/i.test(rawTrimmed)) {
1987
+ const resolvedId = resolveAnchorTarget(store, rawTrimmed, ctx.cwd);
1988
+ if (resolvedId) {
1989
+ const settled = store.settle(resolvedId, { settledBy: "manual-command" });
1990
+ ctx.ui.notify(`\u2316 Settled #${settled.id}: "${settled.title}" (/anchor undo to revert)`, "info");
1991
+ updateAnchorStatusBar(ctx, store);
1992
+ return;
1993
+ }
1994
+ }
1995
+ const anc = store.create({
1996
+ title: parsed.title || rawTrimmed,
1997
+ targetDate: parsed.targetDate,
1998
+ cwd: ctx.cwd
1999
+ });
2000
+ ctx.ui.notify(`\u2316 Pinned #${anc.id}: "${anc.title}"${anc.targetDate ? ` (due ${anc.targetDate})` : ""}`, "info");
1799
2001
  updateAnchorStatusBar(ctx, store);
1800
2002
  }
1801
2003
  });
1802
2004
  pi.registerCommand("pin", {
1803
- description: "Quickly pin a task across sessions",
2005
+ description: "Pin a task across sessions (e.g. /pin Refactor auth --due tomorrow)",
1804
2006
  handler: async (args, ctx) => {
1805
- const title = (args || "").trim();
1806
- if (!title) {
2007
+ const parsed = parseAnchorCommandInput(args);
2008
+ if (parsed.mode === "dashboard" || !parsed.title) {
1807
2009
  await openAnchorDashboard(ctx, store);
1808
2010
  return;
1809
2011
  }
1810
- const anc = store.create({ title, cwd: ctx.cwd });
1811
- ctx.ui.notify(`\u2316 Pinned #${anc.id}: "${anc.title}"`, "info");
2012
+ const anc = store.create({
2013
+ title: parsed.title,
2014
+ targetDate: parsed.targetDate,
2015
+ cwd: ctx.cwd
2016
+ });
2017
+ ctx.ui.notify(`\u2316 Pinned #${anc.id}: "${anc.title}"${anc.targetDate ? ` (due ${anc.targetDate})` : ""}`, "info");
2018
+ updateAnchorStatusBar(ctx, store);
2019
+ }
2020
+ });
2021
+ pi.registerCommand("done", {
2022
+ description: "Complete an active anchor by row number or ID (e.g. /done 1)",
2023
+ handler: async (args, ctx) => {
2024
+ const target = (args || "").trim();
2025
+ if (!target) {
2026
+ await openAnchorDashboard(ctx, store);
2027
+ return;
2028
+ }
2029
+ const resolvedId = resolveAnchorTarget(store, target, ctx.cwd);
2030
+ if (!resolvedId) {
2031
+ ctx.ui.notify(`\u2316 Anchor not found: ${target}`, "error");
2032
+ return;
2033
+ }
2034
+ const settled = store.settle(resolvedId, { settledBy: "manual-command" });
2035
+ ctx.ui.notify(`\u2316 Settled #${settled.id}: "${settled.title}" (/anchor undo to revert)`, "info");
1812
2036
  updateAnchorStatusBar(ctx, store);
1813
2037
  }
1814
2038
  });
@@ -1819,3 +2043,8 @@ Mark as completed and archive?`
1819
2043
  }
1820
2044
  });
1821
2045
  }
2046
+ // Annotate the CommonJS export names for ESM import in node:
2047
+ 0 && (module.exports = {
2048
+ parseAnchorCommandInput,
2049
+ resolveAnchorTarget
2050
+ });
@@ -1,5 +1,20 @@
1
1
  import { ExtensionAPI } from '@earendil-works/pi-coding-agent';
2
+ import { A as AnchorStore } from './store-t2_TDhJn.cjs';
2
3
 
4
+ /**
5
+ * Parse inline `--due <date>` / `-d <date>` flags and `done`/`rm`/`close`/`settle` subcommands
6
+ * for `/anchor` and `/pin` slash commands.
7
+ */
8
+ declare function parseAnchorCommandInput(raw: string, now?: number): {
9
+ mode: 'dashboard' | 'undo' | 'settle' | 'pin';
10
+ targetId?: string;
11
+ title?: string;
12
+ targetDate?: string;
13
+ };
14
+ /**
15
+ * Resolve either an explicit anchor ID ('anc-2', '2') or a 1-based visual dashboard index ('01', '1')
16
+ */
17
+ declare function resolveAnchorTarget(store: AnchorStore, idOrIndex: string, cwd?: string): string | undefined;
3
18
  declare function export_default(pi: ExtensionAPI): void;
4
19
 
5
- export { export_default as default };
20
+ export { export_default as default, parseAnchorCommandInput, resolveAnchorTarget };
@@ -1,5 +1,20 @@
1
1
  import { ExtensionAPI } from '@earendil-works/pi-coding-agent';
2
+ import { A as AnchorStore } from './store-t2_TDhJn.js';
2
3
 
4
+ /**
5
+ * Parse inline `--due <date>` / `-d <date>` flags and `done`/`rm`/`close`/`settle` subcommands
6
+ * for `/anchor` and `/pin` slash commands.
7
+ */
8
+ declare function parseAnchorCommandInput(raw: string, now?: number): {
9
+ mode: 'dashboard' | 'undo' | 'settle' | 'pin';
10
+ targetId?: string;
11
+ title?: string;
12
+ targetDate?: string;
13
+ };
14
+ /**
15
+ * Resolve either an explicit anchor ID ('anc-2', '2') or a 1-based visual dashboard index ('01', '1')
16
+ */
17
+ declare function resolveAnchorTarget(store: AnchorStore, idOrIndex: string, cwd?: string): string | undefined;
3
18
  declare function export_default(pi: ExtensionAPI): void;
4
19
 
5
- export { export_default as default };
20
+ export { export_default as default, parseAnchorCommandInput, resolveAnchorTarget };