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.js
CHANGED
|
@@ -1,20 +1,24 @@
|
|
|
1
1
|
import {
|
|
2
2
|
AnchorStore,
|
|
3
3
|
SessionTouchObserver,
|
|
4
|
+
detectTargetDate,
|
|
4
5
|
findMatchedAnchors,
|
|
5
6
|
generateSettlementProposals,
|
|
6
7
|
hasActiveUI,
|
|
8
|
+
isAnchorTargetingWorkspace,
|
|
7
9
|
isSameOrSubPath,
|
|
8
10
|
makeSafeTaskAnnotation,
|
|
9
11
|
normalizePath,
|
|
10
12
|
openAnchorDashboard,
|
|
11
13
|
renderColdStartAnchorsContext,
|
|
12
14
|
runPhysicalVerification,
|
|
15
|
+
sortAnchorsForDisplay,
|
|
13
16
|
sweepStore,
|
|
14
17
|
truncateToWidth,
|
|
18
|
+
tryRealtimeAutoSettle,
|
|
15
19
|
updateAnchorStatusBar,
|
|
16
20
|
updateStartupBanner
|
|
17
|
-
} from "./chunk-
|
|
21
|
+
} from "./chunk-Q7IILJFA.js";
|
|
18
22
|
|
|
19
23
|
// src/extension.ts
|
|
20
24
|
import { Type } from "@sinclair/typebox";
|
|
@@ -22,6 +26,49 @@ import { execSync } from "child_process";
|
|
|
22
26
|
import fs from "fs";
|
|
23
27
|
import path from "path";
|
|
24
28
|
var MUTATION_TOOLS = /* @__PURE__ */ new Set(["edit", "write", "patch", "apply_diff", "create_file", "modify"]);
|
|
29
|
+
function parseAnchorCommandInput(raw, now = Date.now()) {
|
|
30
|
+
const trimmed = (raw || "").trim();
|
|
31
|
+
if (!trimmed || trimmed === "list" || trimmed === "ls") {
|
|
32
|
+
return { mode: "dashboard" };
|
|
33
|
+
}
|
|
34
|
+
if (trimmed === "undo") {
|
|
35
|
+
return { mode: "undo" };
|
|
36
|
+
}
|
|
37
|
+
const settleMatch = trimmed.match(/^(?:done|rm|close|settle)\s+(.+)$/i);
|
|
38
|
+
if (settleMatch) {
|
|
39
|
+
return {
|
|
40
|
+
mode: "settle",
|
|
41
|
+
targetId: settleMatch[1].trim().replace(/^#/, "")
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
let targetDate;
|
|
45
|
+
const tokens = trimmed.split(/\s+/);
|
|
46
|
+
const dueIdx = tokens.findIndex((t) => t === "--due" || t === "-d");
|
|
47
|
+
if (dueIdx !== -1 && tokens[dueIdx + 1]) {
|
|
48
|
+
const rawDue = tokens[dueIdx + 1];
|
|
49
|
+
targetDate = detectTargetDate(rawDue, now) || rawDue;
|
|
50
|
+
tokens.splice(dueIdx, 2);
|
|
51
|
+
}
|
|
52
|
+
const cleanTitle = tokens.join(" ").trim();
|
|
53
|
+
return {
|
|
54
|
+
mode: "pin",
|
|
55
|
+
title: cleanTitle || trimmed,
|
|
56
|
+
targetDate
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
function resolveAnchorTarget(store, idOrIndex, cwd) {
|
|
60
|
+
const clean = idOrIndex.trim().replace(/^#/, "");
|
|
61
|
+
if (/^\d+$/.test(clean)) {
|
|
62
|
+
const seqIndex = parseInt(clean, 10);
|
|
63
|
+
const visible = store.list({ cwd, all: true }).filter((a) => a.status === "active" || a.status === "sleeping");
|
|
64
|
+
const sorted = sortAnchorsForDisplay(visible, cwd);
|
|
65
|
+
if (seqIndex >= 1 && seqIndex <= sorted.length) {
|
|
66
|
+
return sorted[seqIndex - 1].id;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
const direct = store.get(clean);
|
|
70
|
+
return direct?.id;
|
|
71
|
+
}
|
|
25
72
|
function extension_default(pi) {
|
|
26
73
|
const store = new AnchorStore();
|
|
27
74
|
const observer = new SessionTouchObserver();
|
|
@@ -32,8 +79,12 @@ function extension_default(pi) {
|
|
|
32
79
|
annotatedThisSession.clear();
|
|
33
80
|
preflightFailures.clear();
|
|
34
81
|
const sweep = sweepStore(store);
|
|
35
|
-
|
|
36
|
-
|
|
82
|
+
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));
|
|
83
|
+
for (const sa of sleepingForWorkspace) {
|
|
84
|
+
store.touch(sa.id);
|
|
85
|
+
}
|
|
86
|
+
if (sweep.transitionedToSleeping.length > 0 && sleepingForWorkspace.length === 0) {
|
|
87
|
+
ctx.ui.notify(`\u2316 Anchor: ${sweep.transitionedToSleeping.length} inactive task(s) moved to quiet mode (/anchor to view)`, "info");
|
|
37
88
|
}
|
|
38
89
|
const activeAnchors = store.list({ cwd: ctx.cwd }).filter((a) => a.status === "active");
|
|
39
90
|
for (const a of activeAnchors) {
|
|
@@ -45,7 +96,7 @@ function extension_default(pi) {
|
|
|
45
96
|
settledBy: "verification-test",
|
|
46
97
|
summary: `Physical pre-flight test passed: ${a.verifyCommand}`
|
|
47
98
|
});
|
|
48
|
-
ctx.ui.notify(`\
|
|
99
|
+
ctx.ui.notify(`\u2316 Verification passed! Auto-settled #${a.id}: "${a.title}"`, "info");
|
|
49
100
|
} else {
|
|
50
101
|
preflightFailures.set(a.id, res.output.slice(-250));
|
|
51
102
|
}
|
|
@@ -102,8 +153,26 @@ ${contextSnippet}`
|
|
|
102
153
|
}
|
|
103
154
|
});
|
|
104
155
|
pi.on("tool_result", async (event, ctx) => {
|
|
156
|
+
const lowerTool = String(event.toolName || "").toLowerCase();
|
|
105
157
|
if (!event.isError) {
|
|
106
158
|
observer.recordToolCall(event.toolName, event.input || {});
|
|
159
|
+
if (lowerTool === "bash" || lowerTool === "powershell") {
|
|
160
|
+
const executedCmd = String(event.input?.command || "");
|
|
161
|
+
if (executedCmd.includes("git commit") || executedCmd.length > 0) {
|
|
162
|
+
const settledNow = tryRealtimeAutoSettle(store, observer, ctx.cwd, executedCmd);
|
|
163
|
+
if (settledNow.length > 0) {
|
|
164
|
+
updateAnchorStatusBar(ctx, store);
|
|
165
|
+
if (hasActiveUI(ctx)) {
|
|
166
|
+
for (const item of settledNow) {
|
|
167
|
+
try {
|
|
168
|
+
ctx.ui.notify(`\u2316 Auto-settled #${item.anchor.id} ("${item.anchor.title}") via ${item.reason}`, "info");
|
|
169
|
+
} catch {
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
107
176
|
}
|
|
108
177
|
const pathInput = event.input?.path;
|
|
109
178
|
if (typeof pathInput !== "string") return;
|
|
@@ -167,8 +236,8 @@ ${contextSnippet}`
|
|
|
167
236
|
}
|
|
168
237
|
}
|
|
169
238
|
const modified = observer.getModifiedFiles();
|
|
170
|
-
if (modified.length === 0) return;
|
|
171
|
-
const proposals = generateSettlementProposals(store, modified, ctx.cwd);
|
|
239
|
+
if (modified.length === 0 && !observer.hasCommitted()) return;
|
|
240
|
+
const proposals = generateSettlementProposals(store, modified, ctx.cwd, observer);
|
|
172
241
|
if (proposals.length === 0) return;
|
|
173
242
|
for (const prop of proposals) {
|
|
174
243
|
const a = prop.anchor;
|
|
@@ -377,14 +446,14 @@ Mark as completed and archive?`
|
|
|
377
446
|
}
|
|
378
447
|
});
|
|
379
448
|
pi.registerCommand("anchor", {
|
|
380
|
-
description: "Inspect
|
|
449
|
+
description: "Inspect, pin, or settle active anchors (e.g. /anchor, /anchor done 1, /anchor undo)",
|
|
381
450
|
handler: async (args, ctx) => {
|
|
382
|
-
const
|
|
383
|
-
if (
|
|
451
|
+
const parsed = parseAnchorCommandInput(args);
|
|
452
|
+
if (parsed.mode === "dashboard") {
|
|
384
453
|
await openAnchorDashboard(ctx, store);
|
|
385
454
|
return;
|
|
386
455
|
}
|
|
387
|
-
if (
|
|
456
|
+
if (parsed.mode === "undo") {
|
|
388
457
|
try {
|
|
389
458
|
const restored = store.undoSettle();
|
|
390
459
|
ctx.ui.notify(`\u2316 Restored #${restored.id}: "${restored.title}"`, "info");
|
|
@@ -394,29 +463,68 @@ Mark as completed and archive?`
|
|
|
394
463
|
}
|
|
395
464
|
return;
|
|
396
465
|
}
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
466
|
+
if (parsed.mode === "settle" && parsed.targetId) {
|
|
467
|
+
const resolvedId = resolveAnchorTarget(store, parsed.targetId, ctx.cwd);
|
|
468
|
+
if (!resolvedId) {
|
|
469
|
+
ctx.ui.notify(`\u2316 Anchor not found: ${parsed.targetId}`, "error");
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
const settled = store.settle(resolvedId, { settledBy: "manual-command" });
|
|
473
|
+
ctx.ui.notify(`\u2316 Settled #${settled.id}: "${settled.title}" (/anchor undo to revert)`, "info");
|
|
402
474
|
updateAnchorStatusBar(ctx, store);
|
|
403
475
|
return;
|
|
404
476
|
}
|
|
405
|
-
const
|
|
406
|
-
|
|
477
|
+
const rawTrimmed = (args || "").trim();
|
|
478
|
+
if (/^(?:#?anc-\d+|#\d+)$/i.test(rawTrimmed)) {
|
|
479
|
+
const resolvedId = resolveAnchorTarget(store, rawTrimmed, ctx.cwd);
|
|
480
|
+
if (resolvedId) {
|
|
481
|
+
const settled = store.settle(resolvedId, { settledBy: "manual-command" });
|
|
482
|
+
ctx.ui.notify(`\u2316 Settled #${settled.id}: "${settled.title}" (/anchor undo to revert)`, "info");
|
|
483
|
+
updateAnchorStatusBar(ctx, store);
|
|
484
|
+
return;
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
const anc = store.create({
|
|
488
|
+
title: parsed.title || rawTrimmed,
|
|
489
|
+
targetDate: parsed.targetDate,
|
|
490
|
+
cwd: ctx.cwd
|
|
491
|
+
});
|
|
492
|
+
ctx.ui.notify(`\u2316 Pinned #${anc.id}: "${anc.title}"${anc.targetDate ? ` (due ${anc.targetDate})` : ""}`, "info");
|
|
407
493
|
updateAnchorStatusBar(ctx, store);
|
|
408
494
|
}
|
|
409
495
|
});
|
|
410
496
|
pi.registerCommand("pin", {
|
|
411
|
-
description: "
|
|
497
|
+
description: "Pin a task across sessions (e.g. /pin Refactor auth --due tomorrow)",
|
|
412
498
|
handler: async (args, ctx) => {
|
|
413
|
-
const
|
|
414
|
-
if (!title) {
|
|
499
|
+
const parsed = parseAnchorCommandInput(args);
|
|
500
|
+
if (parsed.mode === "dashboard" || !parsed.title) {
|
|
415
501
|
await openAnchorDashboard(ctx, store);
|
|
416
502
|
return;
|
|
417
503
|
}
|
|
418
|
-
const anc = store.create({
|
|
419
|
-
|
|
504
|
+
const anc = store.create({
|
|
505
|
+
title: parsed.title,
|
|
506
|
+
targetDate: parsed.targetDate,
|
|
507
|
+
cwd: ctx.cwd
|
|
508
|
+
});
|
|
509
|
+
ctx.ui.notify(`\u2316 Pinned #${anc.id}: "${anc.title}"${anc.targetDate ? ` (due ${anc.targetDate})` : ""}`, "info");
|
|
510
|
+
updateAnchorStatusBar(ctx, store);
|
|
511
|
+
}
|
|
512
|
+
});
|
|
513
|
+
pi.registerCommand("done", {
|
|
514
|
+
description: "Complete an active anchor by row number or ID (e.g. /done 1)",
|
|
515
|
+
handler: async (args, ctx) => {
|
|
516
|
+
const target = (args || "").trim();
|
|
517
|
+
if (!target) {
|
|
518
|
+
await openAnchorDashboard(ctx, store);
|
|
519
|
+
return;
|
|
520
|
+
}
|
|
521
|
+
const resolvedId = resolveAnchorTarget(store, target, ctx.cwd);
|
|
522
|
+
if (!resolvedId) {
|
|
523
|
+
ctx.ui.notify(`\u2316 Anchor not found: ${target}`, "error");
|
|
524
|
+
return;
|
|
525
|
+
}
|
|
526
|
+
const settled = store.settle(resolvedId, { settledBy: "manual-command" });
|
|
527
|
+
ctx.ui.notify(`\u2316 Settled #${settled.id}: "${settled.title}" (/anchor undo to revert)`, "info");
|
|
420
528
|
updateAnchorStatusBar(ctx, store);
|
|
421
529
|
}
|
|
422
530
|
});
|
|
@@ -428,5 +536,7 @@ Mark as completed and archive?`
|
|
|
428
536
|
});
|
|
429
537
|
}
|
|
430
538
|
export {
|
|
431
|
-
extension_default as default
|
|
539
|
+
extension_default as default,
|
|
540
|
+
parseAnchorCommandInput,
|
|
541
|
+
resolveAnchorTarget
|
|
432
542
|
};
|
package/dist/index.cjs
CHANGED
|
@@ -44,6 +44,7 @@ __export(index_exports, {
|
|
|
44
44
|
detectRecurrence: () => detectRecurrence,
|
|
45
45
|
detectTargetDate: () => detectTargetDate,
|
|
46
46
|
evaluateAnchorDecay: () => evaluateAnchorDecay,
|
|
47
|
+
extractPathsFromText: () => extractPathsFromText,
|
|
47
48
|
findMatchedAnchors: () => findMatchedAnchors,
|
|
48
49
|
formatCreationTime: () => formatCreationTime,
|
|
49
50
|
formatOrigin: () => formatOrigin,
|
|
@@ -59,6 +60,7 @@ __export(index_exports, {
|
|
|
59
60
|
globToRegExp: () => globToRegExp,
|
|
60
61
|
groupAnchorsByQuadrant: () => groupAnchorsByQuadrant,
|
|
61
62
|
hasActiveUI: () => hasActiveUI,
|
|
63
|
+
isAnchorTargetingWorkspace: () => isAnchorTargetingWorkspace,
|
|
62
64
|
isSameOrSubPath: () => isSameOrSubPath,
|
|
63
65
|
makeSafeTaskAnnotation: () => makeSafeTaskAnnotation,
|
|
64
66
|
matchAnchorAgainstTouchedFiles: () => matchAnchorAgainstTouchedFiles,
|
|
@@ -68,9 +70,11 @@ __export(index_exports, {
|
|
|
68
70
|
renderActiveAnchorsContext: () => renderActiveAnchorsContext,
|
|
69
71
|
renderColdStartAnchorsContext: () => renderColdStartAnchorsContext,
|
|
70
72
|
runPhysicalVerification: () => runPhysicalVerification,
|
|
73
|
+
sortAnchorsForDisplay: () => sortAnchorsForDisplay,
|
|
71
74
|
stripAnsi: () => stripAnsi,
|
|
72
75
|
sweepStore: () => sweepStore,
|
|
73
76
|
truncateToWidth: () => truncateToWidth,
|
|
77
|
+
tryRealtimeAutoSettle: () => tryRealtimeAutoSettle,
|
|
74
78
|
updateAnchorStatusBar: () => updateAnchorStatusBar,
|
|
75
79
|
updateStartupBanner: () => updateStartupBanner
|
|
76
80
|
});
|
|
@@ -78,23 +82,23 @@ module.exports = __toCommonJS(index_exports);
|
|
|
78
82
|
|
|
79
83
|
// src/types.ts
|
|
80
84
|
var EPHEMERAL_DECAY_POLICY = {
|
|
81
|
-
activeDays:
|
|
82
|
-
//
|
|
83
|
-
sleepDays:
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
// 48 hours total before auto-clearing
|
|
85
|
+
activeDays: 7,
|
|
86
|
+
// 7 days active before quiet sleep
|
|
87
|
+
sleepDays: 30,
|
|
88
|
+
graveyardDays: 9999
|
|
89
|
+
// Never silently auto-delete unfinished tasks
|
|
87
90
|
};
|
|
88
91
|
var DURABLE_DECAY_POLICY = {
|
|
89
|
-
activeDays:
|
|
92
|
+
activeDays: 7,
|
|
93
|
+
// 7 days active before quiet sleep
|
|
90
94
|
sleepDays: 30,
|
|
91
95
|
graveyardDays: 9999
|
|
92
96
|
// Never permanently auto-dropped; preserved indefinitely
|
|
93
97
|
};
|
|
94
98
|
var DEFAULT_DECAY_POLICY = {
|
|
95
|
-
activeDays:
|
|
96
|
-
sleepDays:
|
|
97
|
-
graveyardDays:
|
|
99
|
+
activeDays: 7,
|
|
100
|
+
sleepDays: 30,
|
|
101
|
+
graveyardDays: 9999
|
|
98
102
|
};
|
|
99
103
|
|
|
100
104
|
// src/store.ts
|
|
@@ -133,6 +137,39 @@ function isSameOrSubPath(anchorCwd, targetCwd) {
|
|
|
133
137
|
const cmpT = isWinStyle ? normT.toLowerCase() : normT;
|
|
134
138
|
return cmpA === cmpT || cmpT.startsWith(cmpA + "/") || cmpA.startsWith(cmpT + "/");
|
|
135
139
|
}
|
|
140
|
+
function extractPathsFromText(text) {
|
|
141
|
+
if (!text) return [];
|
|
142
|
+
const results = /* @__PURE__ */ new Set();
|
|
143
|
+
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;
|
|
144
|
+
let match;
|
|
145
|
+
while ((match = regex.exec(text)) !== null) {
|
|
146
|
+
const cleaned = normalizePath(match[0].replace(/[.:;,'")\]}>]+$/, ""));
|
|
147
|
+
if (cleaned && cleaned.length >= 3) {
|
|
148
|
+
results.add(cleaned);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return Array.from(results);
|
|
152
|
+
}
|
|
153
|
+
function isAnchorTargetingWorkspace(anchor, targetCwd) {
|
|
154
|
+
if (!targetCwd) return true;
|
|
155
|
+
const normTarget = targetCwd.trim().replace(/\\/g, "/").replace(/\/+$/, "");
|
|
156
|
+
const targetBase = import_node_path.default.basename(normTarget).toLowerCase();
|
|
157
|
+
if (anchor.project && anchor.project !== "global") {
|
|
158
|
+
const normProj = anchor.project.trim().replace(/\\/g, "/").replace(/\/+$/, "");
|
|
159
|
+
if (isSameOrSubPath(normProj, normTarget)) return true;
|
|
160
|
+
const projBase = import_node_path.default.basename(normProj).toLowerCase();
|
|
161
|
+
if (projBase && projBase === targetBase) return true;
|
|
162
|
+
}
|
|
163
|
+
if (anchor.files && anchor.files.length > 0) {
|
|
164
|
+
for (const f of anchor.files) {
|
|
165
|
+
if (isSameOrSubPath(f, normTarget)) return true;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
if (anchor.cwd && isSameOrSubPath(anchor.cwd, normTarget)) {
|
|
169
|
+
return true;
|
|
170
|
+
}
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
136
173
|
function matchAnchorAgainstTouchedFiles(anchor, touchedFiles) {
|
|
137
174
|
if (!anchor.files || anchor.files.length === 0) return null;
|
|
138
175
|
const normTouched = touchedFiles.map((f) => normalizePath(f, anchor.cwd)).filter(Boolean);
|
|
@@ -235,10 +272,10 @@ function detectDurability(title) {
|
|
|
235
272
|
function getEphemeralDecayPolicy(title) {
|
|
236
273
|
const lower = title.toLowerCase();
|
|
237
274
|
if (lower.includes("\u5927\u540E\u5929")) {
|
|
238
|
-
return { activeDays:
|
|
275
|
+
return { activeDays: 7, sleepDays: 30, graveyardDays: 9999 };
|
|
239
276
|
}
|
|
240
277
|
if (lower.includes("\u540E\u5929")) {
|
|
241
|
-
return { activeDays:
|
|
278
|
+
return { activeDays: 7, sleepDays: 30, graveyardDays: 9999 };
|
|
242
279
|
}
|
|
243
280
|
return { ...EPHEMERAL_DECAY_POLICY };
|
|
244
281
|
}
|
|
@@ -549,7 +586,17 @@ var AnchorStore = class {
|
|
|
549
586
|
const state = this.loadState();
|
|
550
587
|
const now = Date.now();
|
|
551
588
|
const id = this.generateId(state);
|
|
552
|
-
const
|
|
589
|
+
const explicitFiles = (input.files || []).map((f) => normalizePath(f)).filter(Boolean);
|
|
590
|
+
const inferredFiles = explicitFiles.length > 0 ? explicitFiles : extractPathsFromText(`${input.title} ${input.description || ""}`);
|
|
591
|
+
let cwd = input.cwd ? normalizePath(input.cwd) : "";
|
|
592
|
+
if (input.project && (/^[a-zA-Z]:[\\/]/.test(input.project.trim()) || input.project.trim().startsWith("/"))) {
|
|
593
|
+
cwd = normalizePath(input.project.trim());
|
|
594
|
+
} else if (inferredFiles.length > 0 && (/^[a-zA-Z]:\//.test(inferredFiles[0]) || inferredFiles[0].startsWith("/"))) {
|
|
595
|
+
const firstDir = import_node_path2.default.dirname(inferredFiles[0]);
|
|
596
|
+
if (firstDir && firstDir !== ".") {
|
|
597
|
+
cwd = normalizePath(firstDir);
|
|
598
|
+
}
|
|
599
|
+
}
|
|
553
600
|
const projectName = input.project ? input.project.trim() : cwd ? import_node_path2.default.basename(cwd) : "global";
|
|
554
601
|
const recurrence = input.recurrence || detectRecurrence(input.title);
|
|
555
602
|
const targetDate = input.targetDate || detectTargetDate(input.title, now);
|
|
@@ -569,7 +616,7 @@ var AnchorStore = class {
|
|
|
569
616
|
createdAt: now,
|
|
570
617
|
updatedAt: now,
|
|
571
618
|
lastTouchedAt: now,
|
|
572
|
-
files:
|
|
619
|
+
files: inferredFiles,
|
|
573
620
|
tags: (input.tags || []).map((t) => t.trim()).filter(Boolean),
|
|
574
621
|
verifyCommand: input.verifyCommand?.trim() || void 0,
|
|
575
622
|
decay: input.decay ? { ...input.decay } : { ...decayPolicy }
|
|
@@ -608,7 +655,7 @@ var AnchorStore = class {
|
|
|
608
655
|
if (filter?.status && a.status !== filter.status) return false;
|
|
609
656
|
if (filter?.priority && a.priority !== filter.priority) return false;
|
|
610
657
|
if (!filter?.all && targetCwd) {
|
|
611
|
-
if (a.files.length > 0 && a.cwd && !
|
|
658
|
+
if (a.files.length > 0 && (a.cwd || a.project) && !isAnchorTargetingWorkspace(a, targetCwd)) {
|
|
612
659
|
return false;
|
|
613
660
|
}
|
|
614
661
|
}
|
|
@@ -928,20 +975,66 @@ function runPhysicalVerification(anchor, cwd) {
|
|
|
928
975
|
return { success: false, output: String(err.stderr || err.stdout || err.message).trim() };
|
|
929
976
|
}
|
|
930
977
|
}
|
|
931
|
-
function
|
|
978
|
+
function tryRealtimeAutoSettle(store, observer, cwd, executedCommand) {
|
|
979
|
+
const candidates = store.list({ cwd }).filter((a) => a.status === "active" || a.status === "sleeping");
|
|
980
|
+
if (candidates.length === 0) return [];
|
|
981
|
+
const settledList = [];
|
|
982
|
+
const cmdTrimmed = (executedCommand || "").trim();
|
|
983
|
+
for (const a of candidates) {
|
|
984
|
+
if (observer.hasCommitted()) {
|
|
985
|
+
const commitMatch = observer.matchesCommit(a);
|
|
986
|
+
if (commitMatch.matched && (!cwd || isAnchorTargetingWorkspace(a, cwd))) {
|
|
987
|
+
const settled = store.settle(a.id, {
|
|
988
|
+
settledBy: "verification-test",
|
|
989
|
+
summary: `Auto-settled via Git commit: ${commitMatch.message}`,
|
|
990
|
+
touchedFiles: observer.getModifiedFiles()
|
|
991
|
+
});
|
|
992
|
+
settledList.push({ anchor: settled, reason: `git commit ("${commitMatch.message}")` });
|
|
993
|
+
continue;
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
if (a.verifyCommand && cmdTrimmed && cmdTrimmed.includes(a.verifyCommand.trim())) {
|
|
997
|
+
const settled = store.settle(a.id, {
|
|
998
|
+
settledBy: "verification-test",
|
|
999
|
+
summary: `Auto-settled via passing command: ${a.verifyCommand}`,
|
|
1000
|
+
touchedFiles: observer.getModifiedFiles()
|
|
1001
|
+
});
|
|
1002
|
+
settledList.push({ anchor: settled, reason: `verifyCommand (${a.verifyCommand})` });
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
return settledList;
|
|
1006
|
+
}
|
|
1007
|
+
function generateSettlementProposals(store, touchedFiles, cwd, observer) {
|
|
932
1008
|
const activeAndSleeping = store.list({ cwd }).filter((a) => a.status === "active" || a.status === "sleeping");
|
|
933
1009
|
const matches = findMatchedAnchors(activeAndSleeping, touchedFiles);
|
|
1010
|
+
const seenIds = /* @__PURE__ */ new Set();
|
|
934
1011
|
const proposals = [];
|
|
935
1012
|
for (const match of matches) {
|
|
1013
|
+
seenIds.add(match.anchor.id);
|
|
936
1014
|
const fileList = match.matchedFiles.slice(0, 3).join(", ");
|
|
937
|
-
const moreSuffix = match.matchedFiles.length > 3 ? `
|
|
1015
|
+
const moreSuffix = match.matchedFiles.length > 3 ? ` (+${match.matchedFiles.length - 3} more)` : "";
|
|
938
1016
|
proposals.push({
|
|
939
1017
|
anchor: match.anchor,
|
|
940
1018
|
matchedFiles: match.matchedFiles,
|
|
941
|
-
reason:
|
|
1019
|
+
reason: `Modified ${fileList}${moreSuffix}`,
|
|
942
1020
|
recommendedAction: match.score >= 0.85 ? "settle" : "defer"
|
|
943
1021
|
});
|
|
944
1022
|
}
|
|
1023
|
+
if (observer && observer.hasCommitted()) {
|
|
1024
|
+
for (const a of activeAndSleeping) {
|
|
1025
|
+
if (seenIds.has(a.id)) continue;
|
|
1026
|
+
const cm = observer.matchesCommit(a);
|
|
1027
|
+
if (cm.matched) {
|
|
1028
|
+
seenIds.add(a.id);
|
|
1029
|
+
proposals.push({
|
|
1030
|
+
anchor: a,
|
|
1031
|
+
matchedFiles: touchedFiles.slice(0, 3),
|
|
1032
|
+
reason: `Matched git commit: "${cm.message}"`,
|
|
1033
|
+
recommendedAction: "settle"
|
|
1034
|
+
});
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
945
1038
|
return proposals;
|
|
946
1039
|
}
|
|
947
1040
|
function formatSettlementCard(proposal) {
|
|
@@ -989,6 +1082,27 @@ function groupAnchorsByQuadrant(anchors, now = Date.now()) {
|
|
|
989
1082
|
}
|
|
990
1083
|
return groups;
|
|
991
1084
|
}
|
|
1085
|
+
var PRIORITY_RANK = { p0: 0, p1: 1, p2: 2 };
|
|
1086
|
+
function sortAnchorsForDisplay(anchors, cwd, now = Date.now()) {
|
|
1087
|
+
const todayStr = getTodayDateString(now);
|
|
1088
|
+
return [...anchors].sort((a, b) => {
|
|
1089
|
+
if (a.status !== b.status) {
|
|
1090
|
+
return a.status === "active" ? -1 : 1;
|
|
1091
|
+
}
|
|
1092
|
+
if (cwd) {
|
|
1093
|
+
const aMatch = isAnchorTargetingWorkspace(a, cwd) ? 0 : 1;
|
|
1094
|
+
const bMatch = isAnchorTargetingWorkspace(b, cwd) ? 0 : 1;
|
|
1095
|
+
if (aMatch !== bMatch) return aMatch - bMatch;
|
|
1096
|
+
}
|
|
1097
|
+
const aDue = a.targetDate && a.targetDate <= todayStr ? 0 : 1;
|
|
1098
|
+
const bDue = b.targetDate && b.targetDate <= todayStr ? 0 : 1;
|
|
1099
|
+
if (aDue !== bDue) return aDue - bDue;
|
|
1100
|
+
const pA = PRIORITY_RANK[a.priority] ?? 1;
|
|
1101
|
+
const pB = PRIORITY_RANK[b.priority] ?? 1;
|
|
1102
|
+
if (pA !== pB) return pA - pB;
|
|
1103
|
+
return a.createdAt - b.createdAt;
|
|
1104
|
+
});
|
|
1105
|
+
}
|
|
992
1106
|
var ANSI_REGEX = /\x1b\[[0-9;]*[a-zA-Z]|\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)/g;
|
|
993
1107
|
function stripAnsi(str) {
|
|
994
1108
|
return str.replace(ANSI_REGEX, "");
|
|
@@ -1117,11 +1231,19 @@ function formatRemainingTtl(anchor, now = Date.now()) {
|
|
|
1117
1231
|
}
|
|
1118
1232
|
function formatOrigin(anchor) {
|
|
1119
1233
|
if (typeof anchor === "object") {
|
|
1120
|
-
if (
|
|
1121
|
-
|
|
1234
|
+
if (anchor.project && anchor.project !== "global") {
|
|
1235
|
+
const normProj = anchor.project.replace(/\\/g, "/").replace(/\/+$/, "");
|
|
1236
|
+
if (normProj.includes("/")) {
|
|
1237
|
+
const parts = normProj.split("/").filter(Boolean);
|
|
1238
|
+
return parts.slice(-2).join("/");
|
|
1239
|
+
}
|
|
1240
|
+
return normProj;
|
|
1241
|
+
}
|
|
1242
|
+
if (!anchor.cwd) return "global";
|
|
1243
|
+
return import_node_path3.default.basename(anchor.cwd.replace(/\\/g, "/")) || "global";
|
|
1122
1244
|
}
|
|
1123
1245
|
if (!anchor) return "global";
|
|
1124
|
-
return import_node_path3.default.basename(anchor) || "global";
|
|
1246
|
+
return import_node_path3.default.basename(anchor.replace(/\\/g, "/")) || "global";
|
|
1125
1247
|
}
|
|
1126
1248
|
function updateAnchorStatusBar(ctx, store) {
|
|
1127
1249
|
if (!hasActiveUI(ctx)) return;
|
|
@@ -1176,51 +1298,47 @@ function updateStartupBanner(ctx, store) {
|
|
|
1176
1298
|
}
|
|
1177
1299
|
async function openAnchorDashboard(ctx, store) {
|
|
1178
1300
|
if (!hasActiveUI(ctx)) return;
|
|
1179
|
-
const
|
|
1180
|
-
if (
|
|
1301
|
+
const allUnfinished = store.list({ cwd: ctx.cwd, all: true }).filter((a) => a.status === "active" || a.status === "sleeping");
|
|
1302
|
+
if (allUnfinished.length === 0) {
|
|
1181
1303
|
ctx.ui.notify("\u2316 No active anchors. Use /pin <task> to record.", "info");
|
|
1182
1304
|
return;
|
|
1183
1305
|
}
|
|
1184
|
-
const
|
|
1185
|
-
const sortedList = [
|
|
1186
|
-
...groups.today,
|
|
1187
|
-
...groups.upcoming,
|
|
1188
|
-
...groups.habits,
|
|
1189
|
-
...groups.backlog
|
|
1190
|
-
];
|
|
1306
|
+
const sortedList = sortAnchorsForDisplay(allUnfinished, ctx.cwd);
|
|
1191
1307
|
const optionMap = /* @__PURE__ */ new Map();
|
|
1192
1308
|
const displayOptions = [];
|
|
1193
1309
|
const termCols = process.stdout?.columns || 96;
|
|
1194
1310
|
const isNarrow = termCols < 88;
|
|
1195
|
-
const titleWidth = isNarrow ? Math.max(18, termCols - 36) :
|
|
1311
|
+
const titleWidth = isNarrow ? Math.max(18, termCols - 36) : 36;
|
|
1196
1312
|
for (let i = 0; i < sortedList.length; i++) {
|
|
1197
1313
|
const a = sortedList[i];
|
|
1198
1314
|
const num = a.id.replace(/^anc-/, "").padStart(2, "0");
|
|
1199
|
-
const
|
|
1315
|
+
const badge = a.status === "sleeping" ? padToWidth("[Quiet]", 10) : padToWidth(`[${a.priority.toUpperCase()}]`, 10);
|
|
1200
1316
|
const origin = formatOrigin(a);
|
|
1201
|
-
const target = formatTargetDate(a);
|
|
1317
|
+
const target = a.status === "sleeping" ? "Quiet (0 tok)" : formatTargetDate(a);
|
|
1202
1318
|
const created = formatCreationTime(a.createdAt);
|
|
1203
1319
|
const titleWithFiles = a.files && a.files.length > 0 ? `${a.title} [${a.files.slice(0, 1).join(", ")}]` : a.title;
|
|
1204
1320
|
const colNum = `${num} `;
|
|
1205
1321
|
const colTitle = padToWidth(titleWithFiles, titleWidth);
|
|
1206
|
-
const colOrigin = padToWidth(origin,
|
|
1207
|
-
const colTarget = padToWidth(target,
|
|
1322
|
+
const colOrigin = padToWidth(origin, 12);
|
|
1323
|
+
const colTarget = padToWidth(target, 14);
|
|
1208
1324
|
const colCreated = created;
|
|
1209
|
-
const label = isNarrow ? `${colNum}${
|
|
1325
|
+
const label = isNarrow ? `${colNum}${badge}${colTitle} ${colTarget}`.trimEnd() : `${colNum}${badge}${colTitle} ${colOrigin} ${colTarget} ${colCreated}`.trimEnd();
|
|
1210
1326
|
optionMap.set(label, a);
|
|
1211
1327
|
displayOptions.push(label);
|
|
1212
1328
|
}
|
|
1213
|
-
const selected = await ctx.ui.select("\u2316 Anchors (select to
|
|
1329
|
+
const selected = await ctx.ui.select("\u2316 Anchors (select to settle, wake, or inspect):", displayOptions);
|
|
1214
1330
|
if (!selected) return;
|
|
1215
1331
|
const anchor = optionMap.get(selected);
|
|
1216
1332
|
if (!anchor) return;
|
|
1333
|
+
const actions = [
|
|
1334
|
+
"\u2713 Settle (Mark completed and archive)",
|
|
1335
|
+
...anchor.status === "sleeping" ? ["\u26A1 Wake up (Activate for current session)"] : [],
|
|
1336
|
+
"\u2139 View details & associated files",
|
|
1337
|
+
"\u21A9 Cancel"
|
|
1338
|
+
];
|
|
1217
1339
|
const action = await ctx.ui.select(
|
|
1218
1340
|
`\u2316 Anchor #${anchor.id}: "${anchor.title}"`,
|
|
1219
|
-
|
|
1220
|
-
"\u2713 Settle (Mark completed and archive)",
|
|
1221
|
-
"\u2139 View details & associated files",
|
|
1222
|
-
"\u21A9 Cancel"
|
|
1223
|
-
]
|
|
1341
|
+
actions
|
|
1224
1342
|
);
|
|
1225
1343
|
if (!action || action.startsWith("\u21A9")) return;
|
|
1226
1344
|
if (action.startsWith("\u2713")) {
|
|
@@ -1228,9 +1346,13 @@ async function openAnchorDashboard(ctx, store) {
|
|
|
1228
1346
|
const successMsg = anchor.recurrence === "daily" ? `\u2316 Completed for today: "${anchor.title}" (resets tomorrow)` : `\u2316 Settled #${anchor.id}: "${anchor.title}" (/anchor undo to revert)`;
|
|
1229
1347
|
ctx.ui.notify(successMsg, "info");
|
|
1230
1348
|
updateAnchorStatusBar(ctx, store);
|
|
1349
|
+
} else if (action.startsWith("\u26A1")) {
|
|
1350
|
+
store.touch(anchor.id);
|
|
1351
|
+
ctx.ui.notify(`\u2316 Woke up #${anchor.id}: "${anchor.title}"`, "info");
|
|
1352
|
+
updateAnchorStatusBar(ctx, store);
|
|
1231
1353
|
} else if (action.startsWith("\u2139")) {
|
|
1232
1354
|
const details = [
|
|
1233
|
-
`#${anchor.id} [${anchor.priority.toUpperCase()}] ${anchor.title}`,
|
|
1355
|
+
`#${anchor.id} [${anchor.priority.toUpperCase()} \xB7 ${anchor.status.toUpperCase()}] ${anchor.title}`,
|
|
1234
1356
|
anchor.description ? `Description: ${anchor.description}` : null,
|
|
1235
1357
|
anchor.targetDate ? `Target Date: ${anchor.targetDate} (${formatTargetDate(anchor)})` : null,
|
|
1236
1358
|
anchor.files && anchor.files.length > 0 ? `Files: ${anchor.files.join(", ")}` : null,
|
|
@@ -1549,6 +1671,7 @@ var AnchorProtocol = class {
|
|
|
1549
1671
|
detectRecurrence,
|
|
1550
1672
|
detectTargetDate,
|
|
1551
1673
|
evaluateAnchorDecay,
|
|
1674
|
+
extractPathsFromText,
|
|
1552
1675
|
findMatchedAnchors,
|
|
1553
1676
|
formatCreationTime,
|
|
1554
1677
|
formatOrigin,
|
|
@@ -1564,6 +1687,7 @@ var AnchorProtocol = class {
|
|
|
1564
1687
|
globToRegExp,
|
|
1565
1688
|
groupAnchorsByQuadrant,
|
|
1566
1689
|
hasActiveUI,
|
|
1690
|
+
isAnchorTargetingWorkspace,
|
|
1567
1691
|
isSameOrSubPath,
|
|
1568
1692
|
makeSafeTaskAnnotation,
|
|
1569
1693
|
matchAnchorAgainstTouchedFiles,
|
|
@@ -1573,9 +1697,11 @@ var AnchorProtocol = class {
|
|
|
1573
1697
|
renderActiveAnchorsContext,
|
|
1574
1698
|
renderColdStartAnchorsContext,
|
|
1575
1699
|
runPhysicalVerification,
|
|
1700
|
+
sortAnchorsForDisplay,
|
|
1576
1701
|
stripAnsi,
|
|
1577
1702
|
sweepStore,
|
|
1578
1703
|
truncateToWidth,
|
|
1704
|
+
tryRealtimeAutoSettle,
|
|
1579
1705
|
updateAnchorStatusBar,
|
|
1580
1706
|
updateStartupBanner
|
|
1581
1707
|
});
|