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.
- package/README.md +29 -14
- package/README_zh.md +29 -14
- package/assets/hero-en.svg +87 -0
- package/assets/hero-zh.svg +87 -0
- package/assets/hero.svg +108 -0
- package/bin/anchor.js +38 -51
- package/dist/{chunk-G72UHPBN.js → chunk-Q7IILJFA.js} +165 -43
- package/dist/extension.cjs +291 -62
- package/dist/extension.d.cts +16 -1
- package/dist/extension.d.ts +16 -1
- package/dist/extension.js +133 -23
- package/dist/index.cjs +169 -43
- package/dist/index.d.cts +63 -242
- package/dist/index.d.ts +63 -242
- package/dist/index.js +9 -1
- package/dist/store-t2_TDhJn.d.cts +210 -0
- package/dist/store-t2_TDhJn.d.ts +210 -0
- package/package.json +21 -5
- package/src/extension.ts +167 -30
- package/src/matcher.ts +51 -0
- package/src/settlement.ts +72 -6
- package/src/store.ts +25 -9
- package/src/tui.ts +77 -29
- package/src/types.ts +7 -7
package/dist/extension.cjs
CHANGED
|
@@ -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:
|
|
46
|
-
//
|
|
47
|
-
sleepDays:
|
|
48
|
-
|
|
49
|
-
|
|
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:
|
|
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:
|
|
227
|
+
return { activeDays: 7, sleepDays: 30, graveyardDays: 9999 };
|
|
193
228
|
}
|
|
194
229
|
if (lower.includes("\u540E\u5929")) {
|
|
195
|
-
return { activeDays:
|
|
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
|
|
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:
|
|
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 && !
|
|
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
|
|
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 ? `
|
|
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:
|
|
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 (
|
|
1289
|
-
|
|
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
|
|
1348
|
-
if (
|
|
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
|
|
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) :
|
|
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
|
|
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,
|
|
1375
|
-
const colTarget = padToWidth(target,
|
|
1488
|
+
const colOrigin = padToWidth(origin, 12);
|
|
1489
|
+
const colTarget = padToWidth(target, 14);
|
|
1376
1490
|
const colCreated = created;
|
|
1377
|
-
const label = isNarrow ? `${colNum}${
|
|
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
|
|
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
|
-
|
|
1428
|
-
|
|
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(`\
|
|
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
|
|
1957
|
+
description: "Inspect, pin, or settle active anchors (e.g. /anchor, /anchor done 1, /anchor undo)",
|
|
1773
1958
|
handler: async (args, ctx) => {
|
|
1774
|
-
const
|
|
1775
|
-
if (
|
|
1959
|
+
const parsed = parseAnchorCommandInput(args);
|
|
1960
|
+
if (parsed.mode === "dashboard") {
|
|
1776
1961
|
await openAnchorDashboard(ctx, store);
|
|
1777
1962
|
return;
|
|
1778
1963
|
}
|
|
1779
|
-
if (
|
|
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
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
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
|
|
1798
|
-
|
|
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: "
|
|
2005
|
+
description: "Pin a task across sessions (e.g. /pin Refactor auth --due tomorrow)",
|
|
1804
2006
|
handler: async (args, ctx) => {
|
|
1805
|
-
const
|
|
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({
|
|
1811
|
-
|
|
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
|
+
});
|
package/dist/extension.d.cts
CHANGED
|
@@ -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 };
|
package/dist/extension.d.ts
CHANGED
|
@@ -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 };
|