pi-anchor 0.3.0 → 0.5.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 +38 -174
- package/README_zh.md +36 -184
- package/assets/hero-en.svg +48 -47
- package/assets/hero-zh.svg +48 -47
- package/bin/anchor.js +68 -70
- package/dist/chunk-VXEQDTK4.js +1310 -0
- package/dist/extension-Cq1NH1jT.d.cts +205 -0
- package/dist/extension-Cq1NH1jT.d.ts +205 -0
- package/dist/extension.cjs +617 -1354
- package/dist/extension.d.cts +2 -20
- package/dist/extension.d.ts +2 -20
- package/dist/extension.js +6 -536
- package/dist/index.cjs +452 -1152
- package/dist/index.d.cts +22 -250
- package/dist/index.d.ts +22 -250
- package/dist/index.js +21 -103
- package/package.json +2 -2
- package/src/extension.ts +265 -376
- package/src/index.ts +1 -6
- package/src/pi-types.d.ts +5 -1
- package/src/store.ts +385 -427
- package/src/tui.ts +166 -307
- package/src/types.ts +35 -63
- package/dist/chunk-Q7IILJFA.js +0 -1563
- package/dist/store-t2_TDhJn.d.cts +0 -210
- package/dist/store-t2_TDhJn.d.ts +0 -210
- package/src/context_injector.ts +0 -166
- package/src/decay.ts +0 -125
- package/src/matcher.ts +0 -202
- package/src/observer.ts +0 -141
- package/src/protocol.ts +0 -95
- package/src/settlement.ts +0 -140
package/dist/index.cjs
CHANGED
|
@@ -30,89 +30,52 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
30
30
|
// src/index.ts
|
|
31
31
|
var index_exports = {};
|
|
32
32
|
__export(index_exports, {
|
|
33
|
-
AnchorProtocol: () => AnchorProtocol,
|
|
34
33
|
AnchorStore: () => AnchorStore,
|
|
35
34
|
COMMENT_FORMATS: () => COMMENT_FORMATS,
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
EPHEMERAL_DECAY_POLICY: () => EPHEMERAL_DECAY_POLICY,
|
|
39
|
-
SessionTouchObserver: () => SessionTouchObserver,
|
|
35
|
+
MS_PER_DAY: () => MS_PER_DAY,
|
|
36
|
+
QUIET_AFTER_DAYS: () => QUIET_AFTER_DAYS,
|
|
40
37
|
acquireSyncLock: () => acquireSyncLock,
|
|
41
38
|
atomicRenameWithRetry: () => atomicRenameWithRetry,
|
|
42
|
-
classifyAnchor: () => classifyAnchor,
|
|
43
|
-
detectDurability: () => detectDurability,
|
|
44
|
-
detectRecurrence: () => detectRecurrence,
|
|
45
|
-
detectTargetDate: () => detectTargetDate,
|
|
46
|
-
evaluateAnchorDecay: () => evaluateAnchorDecay,
|
|
47
39
|
extractPathsFromText: () => extractPathsFromText,
|
|
40
|
+
extractProgressFromText: () => extractProgressFromText,
|
|
48
41
|
findMatchedAnchors: () => findMatchedAnchors,
|
|
49
|
-
formatCreationTime: () => formatCreationTime,
|
|
50
42
|
formatOrigin: () => formatOrigin,
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
formatSettlementCard: () => formatSettlementCard,
|
|
54
|
-
formatTargetDate: () => formatTargetDate,
|
|
55
|
-
generateSettlementProposals: () => generateSettlementProposals,
|
|
43
|
+
formatProgressBar: () => formatProgressBar,
|
|
44
|
+
formatRelativeAge: () => formatRelativeAge,
|
|
56
45
|
getDefaultStorageDir: () => getDefaultStorageDir,
|
|
57
46
|
getDisplayWidth: () => getDisplayWidth,
|
|
58
|
-
getEphemeralDecayPolicy: () => getEphemeralDecayPolicy,
|
|
59
|
-
getTodayDateString: () => getTodayDateString,
|
|
60
47
|
globToRegExp: () => globToRegExp,
|
|
61
|
-
groupAnchorsByQuadrant: () => groupAnchorsByQuadrant,
|
|
62
48
|
hasActiveUI: () => hasActiveUI,
|
|
63
49
|
isAnchorTargetingWorkspace: () => isAnchorTargetingWorkspace,
|
|
64
50
|
isSameOrSubPath: () => isSameOrSubPath,
|
|
65
51
|
makeSafeTaskAnnotation: () => makeSafeTaskAnnotation,
|
|
66
52
|
matchAnchorAgainstTouchedFiles: () => matchAnchorAgainstTouchedFiles,
|
|
53
|
+
matchesCommitMessage: () => matchesCommitMessage,
|
|
67
54
|
normalizePath: () => normalizePath,
|
|
68
55
|
openAnchorDashboard: () => openAnchorDashboard,
|
|
69
56
|
padToWidth: () => padToWidth,
|
|
57
|
+
parseAnchorCommandInput: () => parseAnchorCommandInput,
|
|
58
|
+
parseQuickNoteInput: () => parseQuickNoteInput,
|
|
70
59
|
renderActiveAnchorsContext: () => renderActiveAnchorsContext,
|
|
71
60
|
renderColdStartAnchorsContext: () => renderColdStartAnchorsContext,
|
|
72
|
-
|
|
61
|
+
resolveAnchorTarget: () => resolveAnchorTarget,
|
|
73
62
|
sortAnchorsForDisplay: () => sortAnchorsForDisplay,
|
|
74
63
|
stripAnsi: () => stripAnsi,
|
|
75
|
-
|
|
64
|
+
sweepQuietNotes: () => sweepQuietNotes,
|
|
76
65
|
truncateToWidth: () => truncateToWidth,
|
|
77
|
-
tryRealtimeAutoSettle: () => tryRealtimeAutoSettle,
|
|
78
66
|
updateAnchorStatusBar: () => updateAnchorStatusBar,
|
|
79
67
|
updateStartupBanner: () => updateStartupBanner
|
|
80
68
|
});
|
|
81
69
|
module.exports = __toCommonJS(index_exports);
|
|
82
70
|
|
|
83
71
|
// src/types.ts
|
|
84
|
-
var
|
|
85
|
-
|
|
86
|
-
// 7 days active before quiet sleep
|
|
87
|
-
sleepDays: 30,
|
|
88
|
-
graveyardDays: 9999
|
|
89
|
-
// Never silently auto-delete unfinished tasks
|
|
90
|
-
};
|
|
91
|
-
var DURABLE_DECAY_POLICY = {
|
|
92
|
-
activeDays: 7,
|
|
93
|
-
// 7 days active before quiet sleep
|
|
94
|
-
sleepDays: 30,
|
|
95
|
-
graveyardDays: 9999
|
|
96
|
-
// Never permanently auto-dropped; preserved indefinitely
|
|
97
|
-
};
|
|
98
|
-
var DEFAULT_DECAY_POLICY = {
|
|
99
|
-
activeDays: 7,
|
|
100
|
-
sleepDays: 30,
|
|
101
|
-
graveyardDays: 9999
|
|
102
|
-
};
|
|
72
|
+
var QUIET_AFTER_DAYS = 7;
|
|
73
|
+
var MS_PER_DAY = 864e5;
|
|
103
74
|
|
|
104
75
|
// src/store.ts
|
|
105
76
|
var import_node_fs = __toESM(require("fs"), 1);
|
|
106
|
-
var import_node_path2 = __toESM(require("path"), 1);
|
|
107
|
-
var import_node_os = __toESM(require("os"), 1);
|
|
108
|
-
|
|
109
|
-
// src/matcher.ts
|
|
110
77
|
var import_node_path = __toESM(require("path"), 1);
|
|
111
|
-
|
|
112
|
-
const collapsed = pattern.slice(0, 512).replace(/\/+(\*\*\/+)+/g, "/**/").replace(/\*{3,}/g, "**");
|
|
113
|
-
const escaped = collapsed.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\/\*\*\//g, "___GLOB_DIR_SLASH___").replace(/\*\*/g, "___GLOB_STAR_STAR___").replace(/\*/g, "[^/]*").replace(/___GLOB_DIR_SLASH___/g, "(?:/|/(?:[^/]+/)+)").replace(/___GLOB_STAR_STAR___/g, ".*");
|
|
114
|
-
return new RegExp(`^${escaped}$`);
|
|
115
|
-
}
|
|
78
|
+
var import_node_os = __toESM(require("os"), 1);
|
|
116
79
|
function normalizePath(filePath, baseCwd) {
|
|
117
80
|
if (typeof filePath !== "string") return "";
|
|
118
81
|
let norm = filePath.trim().replace(/\\/g, "/").replace(/^\.\//, "");
|
|
@@ -128,14 +91,19 @@ function normalizePath(filePath, baseCwd) {
|
|
|
128
91
|
}
|
|
129
92
|
return norm;
|
|
130
93
|
}
|
|
131
|
-
function isSameOrSubPath(
|
|
132
|
-
if (!
|
|
133
|
-
const normA =
|
|
134
|
-
const
|
|
135
|
-
const
|
|
136
|
-
const
|
|
137
|
-
const
|
|
138
|
-
return
|
|
94
|
+
function isSameOrSubPath(dirA, dirB) {
|
|
95
|
+
if (!dirA || !dirB) return false;
|
|
96
|
+
const normA = dirA.trim().replace(/\\/g, "/").replace(/\/+$/, "");
|
|
97
|
+
const normB = dirB.trim().replace(/\\/g, "/").replace(/\/+$/, "");
|
|
98
|
+
const isWin = process.platform === "win32" || /^[a-zA-Z]:\//.test(normA) || /^[a-zA-Z]:\//.test(normB);
|
|
99
|
+
const a = isWin ? normA.toLowerCase() : normA;
|
|
100
|
+
const b = isWin ? normB.toLowerCase() : normB;
|
|
101
|
+
return a === b || b.startsWith(a + "/") || a.startsWith(b + "/");
|
|
102
|
+
}
|
|
103
|
+
function globToRegExp(pattern) {
|
|
104
|
+
const collapsed = pattern.slice(0, 512).replace(/\/+(\*\*\/+)+/g, "/**/").replace(/\*{3,}/g, "**");
|
|
105
|
+
const escaped = collapsed.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\/\*\*\//g, "___GLOB_DIR_SLASH___").replace(/\*\*/g, "___GLOB_STAR_STAR___").replace(/\*/g, "[^/]*").replace(/___GLOB_DIR_SLASH___/g, "(?:/|/(?:[^/]+/)+)").replace(/___GLOB_STAR_STAR___/g, ".*");
|
|
106
|
+
return new RegExp(`^${escaped}$`);
|
|
139
107
|
}
|
|
140
108
|
function extractPathsFromText(text) {
|
|
141
109
|
if (!text) return [];
|
|
@@ -150,6 +118,32 @@ function extractPathsFromText(text) {
|
|
|
150
118
|
}
|
|
151
119
|
return Array.from(results);
|
|
152
120
|
}
|
|
121
|
+
function extractProgressFromText(rawTitle) {
|
|
122
|
+
if (!rawTitle) return { cleanTitle: "" };
|
|
123
|
+
const stepParts = Array.from(rawTitle.matchAll(/(?:^|\s)(\d+)\.\s*([^\d]+?)(?=\s+\d+\.\s|$)/g));
|
|
124
|
+
if (stepParts.length >= 2) {
|
|
125
|
+
const steps = stepParts.map((m) => m[2].trim().replace(/[;;,,]+$/, "")).filter(Boolean);
|
|
126
|
+
const firstMatchIdx = rawTitle.indexOf(stepParts[0][0]);
|
|
127
|
+
const prefix = rawTitle.slice(0, firstMatchIdx).replace(/[::\-—\s]+$/, "").trim();
|
|
128
|
+
return {
|
|
129
|
+
cleanTitle: prefix || steps.join(" \u2192 "),
|
|
130
|
+
progress: { done: 0, total: steps.length, steps }
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
const ratioMatch = rawTitle.match(/(?:\[(\d{1,3})\/(\d{1,3})\]|\((\d{1,3})\/(\d{1,3})\)|(?:^|\s)(\d{1,3})\/(\d{1,3})(?=\s|$))/);
|
|
134
|
+
if (ratioMatch) {
|
|
135
|
+
const done = parseInt(ratioMatch[1] ?? ratioMatch[3] ?? ratioMatch[5], 10);
|
|
136
|
+
const total = parseInt(ratioMatch[2] ?? ratioMatch[4] ?? ratioMatch[6], 10);
|
|
137
|
+
if (!isNaN(done) && !isNaN(total) && total >= 2 && total <= 500 && done >= 0 && done <= total) {
|
|
138
|
+
const stripped = rawTitle.replace(ratioMatch[0], " ").replace(/\s+/g, " ").trim();
|
|
139
|
+
return {
|
|
140
|
+
cleanTitle: stripped || rawTitle.trim(),
|
|
141
|
+
progress: { done, total }
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return { cleanTitle: rawTitle.trim() };
|
|
146
|
+
}
|
|
153
147
|
function isAnchorTargetingWorkspace(anchor, targetCwd) {
|
|
154
148
|
if (!targetCwd) return true;
|
|
155
149
|
const normTarget = targetCwd.trim().replace(/\\/g, "/").replace(/\/+$/, "");
|
|
@@ -157,8 +151,7 @@ function isAnchorTargetingWorkspace(anchor, targetCwd) {
|
|
|
157
151
|
if (anchor.project && anchor.project !== "global") {
|
|
158
152
|
const normProj = anchor.project.trim().replace(/\\/g, "/").replace(/\/+$/, "");
|
|
159
153
|
if (isSameOrSubPath(normProj, normTarget)) return true;
|
|
160
|
-
|
|
161
|
-
if (projBase && projBase === targetBase) return true;
|
|
154
|
+
if (import_node_path.default.basename(normProj).toLowerCase() === targetBase) return true;
|
|
162
155
|
}
|
|
163
156
|
if (anchor.files && anchor.files.length > 0) {
|
|
164
157
|
for (const f of anchor.files) {
|
|
@@ -183,12 +176,9 @@ function matchAnchorAgainstTouchedFiles(anchor, touchedFiles) {
|
|
|
183
176
|
matchedExact.push(touched);
|
|
184
177
|
break;
|
|
185
178
|
}
|
|
186
|
-
if (normPattern.includes("*")) {
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
matchedExact.push(touched);
|
|
190
|
-
break;
|
|
191
|
-
}
|
|
179
|
+
if (normPattern.includes("*") && globToRegExp(normPattern).test(touched)) {
|
|
180
|
+
matchedExact.push(touched);
|
|
181
|
+
break;
|
|
192
182
|
}
|
|
193
183
|
const dirPrefix = normPattern.endsWith("/") ? normPattern : normPattern + "/";
|
|
194
184
|
if (touched.startsWith(dirPrefix)) {
|
|
@@ -198,193 +188,138 @@ function matchAnchorAgainstTouchedFiles(anchor, touchedFiles) {
|
|
|
198
188
|
}
|
|
199
189
|
}
|
|
200
190
|
if (matchedExact.length > 0) {
|
|
201
|
-
return {
|
|
202
|
-
anchor,
|
|
203
|
-
score: 1,
|
|
204
|
-
matchedFiles: Array.from(new Set(matchedExact)),
|
|
205
|
-
reason: "exact-file"
|
|
206
|
-
};
|
|
191
|
+
return { anchor, score: 1, matchedFiles: Array.from(new Set(matchedExact)), reason: "exact-file" };
|
|
207
192
|
}
|
|
208
193
|
if (matchedPrefix.length > 0) {
|
|
209
|
-
return {
|
|
210
|
-
anchor,
|
|
211
|
-
score: 0.85,
|
|
212
|
-
matchedFiles: Array.from(new Set(matchedPrefix)),
|
|
213
|
-
reason: "dir-prefix"
|
|
214
|
-
};
|
|
194
|
+
return { anchor, score: 0.85, matchedFiles: Array.from(new Set(matchedPrefix)), reason: "dir-prefix" };
|
|
215
195
|
}
|
|
216
196
|
return null;
|
|
217
197
|
}
|
|
218
198
|
function findMatchedAnchors(anchors, touchedFiles) {
|
|
219
|
-
const priorityWeight = { p0: 3, p1: 2, p2: 1 };
|
|
220
199
|
const results = [];
|
|
221
200
|
for (const anchor of anchors) {
|
|
222
|
-
const
|
|
223
|
-
if (
|
|
224
|
-
results.push(match);
|
|
225
|
-
}
|
|
201
|
+
const m = matchAnchorAgainstTouchedFiles(anchor, touchedFiles);
|
|
202
|
+
if (m) results.push(m);
|
|
226
203
|
}
|
|
227
|
-
return results.sort((a, b) =>
|
|
228
|
-
if (b.score !== a.score) {
|
|
229
|
-
return b.score - a.score;
|
|
230
|
-
}
|
|
231
|
-
const weightA = priorityWeight[a.anchor.priority] || 0;
|
|
232
|
-
const weightB = priorityWeight[b.anchor.priority] || 0;
|
|
233
|
-
if (weightB !== weightA) {
|
|
234
|
-
return weightB - weightA;
|
|
235
|
-
}
|
|
236
|
-
return b.anchor.lastTouchedAt - a.anchor.lastTouchedAt;
|
|
237
|
-
});
|
|
204
|
+
return results.sort((a, b) => b.score - a.score);
|
|
238
205
|
}
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
206
|
+
var COMMENT_FORMATS = {
|
|
207
|
+
".ts": (m) => `// ${m}`,
|
|
208
|
+
".tsx": (m) => `// ${m}`,
|
|
209
|
+
".js": (m) => `// ${m}`,
|
|
210
|
+
".jsx": (m) => `// ${m}`,
|
|
211
|
+
".mjs": (m) => `// ${m}`,
|
|
212
|
+
".cjs": (m) => `// ${m}`,
|
|
213
|
+
".go": (m) => `// ${m}`,
|
|
214
|
+
".rs": (m) => `// ${m}`,
|
|
215
|
+
".c": (m) => `// ${m}`,
|
|
216
|
+
".cpp": (m) => `// ${m}`,
|
|
217
|
+
".h": (m) => `// ${m}`,
|
|
218
|
+
".cs": (m) => `// ${m}`,
|
|
219
|
+
".java": (m) => `// ${m}`,
|
|
220
|
+
".kt": (m) => `// ${m}`,
|
|
221
|
+
".swift": (m) => `// ${m}`,
|
|
222
|
+
".dart": (m) => `// ${m}`,
|
|
223
|
+
".py": (m) => `# ${m}`,
|
|
224
|
+
".rb": (m) => `# ${m}`,
|
|
225
|
+
".sh": (m) => `# ${m}`,
|
|
226
|
+
".bash": (m) => `# ${m}`,
|
|
227
|
+
".yaml": (m) => `# ${m}`,
|
|
228
|
+
".yml": (m) => `# ${m}`,
|
|
229
|
+
".toml": (m) => `# ${m}`,
|
|
230
|
+
".ps1": (m) => `# ${m}`,
|
|
231
|
+
".html": (m) => `<!-- ${m} -->`,
|
|
232
|
+
".xml": (m) => `<!-- ${m} -->`,
|
|
233
|
+
".svg": (m) => `<!-- ${m} -->`,
|
|
234
|
+
".vue": (m) => `<!-- ${m} -->`,
|
|
235
|
+
".svelte": (m) => `<!-- ${m} -->`,
|
|
236
|
+
".css": (m) => `/* ${m} */`,
|
|
237
|
+
".scss": (m) => `/* ${m} */`,
|
|
238
|
+
".sql": (m) => `-- ${m}`,
|
|
239
|
+
".lua": (m) => `-- ${m}`
|
|
240
|
+
};
|
|
241
|
+
function makeSafeTaskAnnotation(filePath, anchor) {
|
|
242
|
+
const ext = import_node_path.default.extname(filePath).toLowerCase();
|
|
243
|
+
const fmt = COMMENT_FORMATS[ext];
|
|
244
|
+
if (!fmt) return null;
|
|
245
|
+
const num = anchor.id.replace(/^anc-/, "");
|
|
246
|
+
return "\n\n" + fmt(`\u2316 Note #${num}: ${anchor.title}`);
|
|
247
|
+
}
|
|
248
|
+
function matchesCommitMessage(anchor, commitMessages) {
|
|
249
|
+
const idLower = anchor.id.toLowerCase();
|
|
250
|
+
const numId = `#${anchor.id.replace(/^anc-/, "")}`;
|
|
251
|
+
const title = anchor.title.toLowerCase();
|
|
252
|
+
let titleTokens = [];
|
|
253
|
+
if (typeof Intl !== "undefined" && Intl.Segmenter) {
|
|
254
|
+
try {
|
|
255
|
+
const seg = new Intl.Segmenter("und", { granularity: "word" });
|
|
256
|
+
titleTokens = Array.from(seg.segment(title)).filter((s) => s.isWordLike).map((s) => s.segment.trim().toLowerCase()).filter((t) => t.length >= 2);
|
|
257
|
+
} catch {
|
|
258
|
+
titleTokens = title.split(/[\s,._\-\/]+/).filter((t) => t.length >= 2);
|
|
268
259
|
}
|
|
260
|
+
} else {
|
|
261
|
+
titleTokens = title.split(/[\s,._\-\/]+/).filter((t) => t.length >= 2);
|
|
269
262
|
}
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
}
|
|
280
|
-
return { ...EPHEMERAL_DECAY_POLICY };
|
|
281
|
-
}
|
|
282
|
-
function detectTargetDate(titleOrDate, now = Date.now()) {
|
|
283
|
-
const lower = titleOrDate.toLowerCase().trim();
|
|
284
|
-
const MS_DAY = 24 * 60 * 60 * 1e3;
|
|
285
|
-
if (lower.includes("\u4ECA\u5929") || lower.includes("\u4ECA\u65E5") || lower.includes("\u4ECA\u665A") || lower === "today" || lower === "tonight") {
|
|
286
|
-
return getTodayDateString(now);
|
|
287
|
-
}
|
|
288
|
-
if (lower.includes("\u660E\u5929") || lower === "tomorrow") {
|
|
289
|
-
return getTodayDateString(now + MS_DAY);
|
|
290
|
-
}
|
|
291
|
-
if (lower.includes("\u540E\u5929")) {
|
|
292
|
-
return getTodayDateString(now + 2 * MS_DAY);
|
|
293
|
-
}
|
|
294
|
-
if (lower.includes("\u5927\u540E\u5929")) {
|
|
295
|
-
return getTodayDateString(now + 3 * MS_DAY);
|
|
296
|
-
}
|
|
297
|
-
const weekdayMap = {
|
|
298
|
-
"\u5468\u65E5": 0,
|
|
299
|
-
"\u661F\u671F\u65E5": 0,
|
|
300
|
-
"sunday": 0,
|
|
301
|
-
"sun": 0,
|
|
302
|
-
"\u5468\u4E00": 1,
|
|
303
|
-
"\u661F\u671F\u4E00": 1,
|
|
304
|
-
"monday": 1,
|
|
305
|
-
"mon": 1,
|
|
306
|
-
"\u5468\u4E8C": 2,
|
|
307
|
-
"\u661F\u671F\u4E8C": 2,
|
|
308
|
-
"tuesday": 2,
|
|
309
|
-
"tue": 2,
|
|
310
|
-
"\u5468\u4E09": 3,
|
|
311
|
-
"\u661F\u671F\u4E09": 3,
|
|
312
|
-
"wednesday": 3,
|
|
313
|
-
"wed": 3,
|
|
314
|
-
"\u5468\u56DB": 4,
|
|
315
|
-
"\u661F\u671F\u56DB": 4,
|
|
316
|
-
"thursday": 4,
|
|
317
|
-
"thu": 4,
|
|
318
|
-
"\u5468\u4E94": 5,
|
|
319
|
-
"\u661F\u671F\u4E94": 5,
|
|
320
|
-
"friday": 5,
|
|
321
|
-
"fri": 5,
|
|
322
|
-
"\u5468\u516D": 6,
|
|
323
|
-
"\u661F\u671F\u516D": 6,
|
|
324
|
-
"saturday": 6,
|
|
325
|
-
"sat": 6
|
|
326
|
-
};
|
|
327
|
-
const isNextWeek = lower.includes("\u4E0B\u5468") || lower.includes("next");
|
|
328
|
-
for (const [name, targetDay] of Object.entries(weekdayMap)) {
|
|
329
|
-
if (lower.includes(name)) {
|
|
330
|
-
const curDay = new Date(now).getDay();
|
|
331
|
-
let diff = targetDay - curDay;
|
|
332
|
-
if (diff <= 0 || isNextWeek) {
|
|
333
|
-
diff += 7;
|
|
263
|
+
for (const msg of commitMessages) {
|
|
264
|
+
const msgLower = msg.toLowerCase();
|
|
265
|
+
if (msgLower.includes(idLower) || msgLower.includes(numId)) {
|
|
266
|
+
return { matched: true, message: msg };
|
|
267
|
+
}
|
|
268
|
+
if (titleTokens.length > 0) {
|
|
269
|
+
const hits = titleTokens.filter((tok) => msgLower.includes(tok)).length;
|
|
270
|
+
if (hits >= Math.min(2, titleTokens.length)) {
|
|
271
|
+
return { matched: true, message: msg };
|
|
334
272
|
}
|
|
335
|
-
return getTodayDateString(now + diff * MS_DAY);
|
|
336
273
|
}
|
|
337
274
|
}
|
|
338
|
-
|
|
339
|
-
if (fullDateMatch) {
|
|
340
|
-
const y = fullDateMatch[1];
|
|
341
|
-
const m = fullDateMatch[2].padStart(2, "0");
|
|
342
|
-
const d = fullDateMatch[3].padStart(2, "0");
|
|
343
|
-
return `${y}-${m}-${d}`;
|
|
344
|
-
}
|
|
345
|
-
const mmddMatch = titleOrDate.match(/\b(\d{1,2})-(\d{1,2})\b/);
|
|
346
|
-
if (mmddMatch) {
|
|
347
|
-
const curYear = new Date(now).getFullYear();
|
|
348
|
-
const m = mmddMatch[1].padStart(2, "0");
|
|
349
|
-
const d = mmddMatch[2].padStart(2, "0");
|
|
350
|
-
return `${curYear}-${m}-${d}`;
|
|
351
|
-
}
|
|
352
|
-
return void 0;
|
|
353
|
-
}
|
|
354
|
-
function detectRecurrence(title) {
|
|
355
|
-
const lower = title.toLowerCase();
|
|
356
|
-
if (lower.includes("\u6BCF\u5929") || lower.includes("\u6BCF\u65E5") || lower.includes("daily") || lower.includes("every day")) {
|
|
357
|
-
return "daily";
|
|
358
|
-
}
|
|
359
|
-
return void 0;
|
|
275
|
+
return { matched: false };
|
|
360
276
|
}
|
|
361
|
-
function
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
277
|
+
function renderColdStartAnchorsContext(store, cwdOrNow, nowArg) {
|
|
278
|
+
let cwd;
|
|
279
|
+
let now = Date.now();
|
|
280
|
+
if (typeof cwdOrNow === "number") now = cwdOrNow;
|
|
281
|
+
else if (typeof cwdOrNow === "string") cwd = cwdOrNow;
|
|
282
|
+
if (typeof nowArg === "number") now = nowArg;
|
|
283
|
+
sweepQuietNotes(store, now);
|
|
284
|
+
const activeNotes = store.list({ status: "active", cwd });
|
|
285
|
+
if (activeNotes.length === 0) return "";
|
|
286
|
+
const lines = activeNotes.map((a) => {
|
|
287
|
+
const num = a.id.replace(/^anc-/, "");
|
|
288
|
+
const files = a.files.length > 0 ? ` (${a.files.slice(0, 2).join(", ")})` : "";
|
|
289
|
+
const prog = a.progress ? ` [${a.progress.done}/${a.progress.total}${a.progress.steps && a.progress.steps[a.progress.done] ? ` \xB7 next: ${a.progress.steps[a.progress.done]}` : ""}]` : "";
|
|
290
|
+
const desc = a.description ? ` \u2014 ${a.description}` : "";
|
|
291
|
+
return `\u2022 #${num} [${a.project}] ${a.title}${prog}${files}${desc}`;
|
|
292
|
+
});
|
|
293
|
+
return [
|
|
294
|
+
`<sticky-notes count="${activeNotes.length}">`,
|
|
295
|
+
`Pinned notes from previous sessions:`,
|
|
296
|
+
lines.join("\n"),
|
|
297
|
+
`When a note is completed, call anchor(action="settle", id="...") to tear it off.`,
|
|
298
|
+
`</sticky-notes>`
|
|
299
|
+
].join("\n");
|
|
367
300
|
}
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
const src = import_node_path2.default.join(legacyPiDir, file);
|
|
379
|
-
const dest = import_node_path2.default.join(primaryDir, file);
|
|
380
|
-
if (import_node_fs.default.existsSync(src) && !import_node_fs.default.existsSync(dest)) {
|
|
381
|
-
import_node_fs.default.copyFileSync(src, dest);
|
|
382
|
-
}
|
|
301
|
+
var renderActiveAnchorsContext = renderColdStartAnchorsContext;
|
|
302
|
+
function sweepQuietNotes(store, now = Date.now()) {
|
|
303
|
+
return store.withLock(() => {
|
|
304
|
+
const state = store.loadState();
|
|
305
|
+
const quieted = [];
|
|
306
|
+
for (const a of state.anchors) {
|
|
307
|
+
if (a.status === "active" && now - a.lastTouchedAt > QUIET_AFTER_DAYS * MS_PER_DAY) {
|
|
308
|
+
a.status = "sleeping";
|
|
309
|
+
a.updatedAt = now;
|
|
310
|
+
quieted.push(a.id);
|
|
383
311
|
}
|
|
384
|
-
} catch {
|
|
385
312
|
}
|
|
386
|
-
|
|
387
|
-
|
|
313
|
+
if (quieted.length > 0) {
|
|
314
|
+
state.lastSweepAt = now;
|
|
315
|
+
store.saveState(state);
|
|
316
|
+
}
|
|
317
|
+
return { quieted };
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
function getDefaultStorageDir() {
|
|
321
|
+
if (process.env.ANCHOR_DIR) return import_node_path.default.resolve(process.env.ANCHOR_DIR);
|
|
322
|
+
return import_node_path.default.join(import_node_os.default.homedir(), ".anchor");
|
|
388
323
|
}
|
|
389
324
|
function sleepSync(ms) {
|
|
390
325
|
const end = Date.now() + ms;
|
|
@@ -406,22 +341,18 @@ function acquireSyncLock(lockPath, maxWaitMs = 1500, staleTimeoutMs = 5e3) {
|
|
|
406
341
|
attempt++;
|
|
407
342
|
try {
|
|
408
343
|
const fd = import_node_fs.default.openSync(lockPath, "wx");
|
|
409
|
-
|
|
410
|
-
import_node_fs.default.writeFileSync(fd, JSON.stringify(meta), "utf-8");
|
|
344
|
+
import_node_fs.default.writeFileSync(fd, JSON.stringify({ pid: process.pid, createdAt: Date.now() }), "utf-8");
|
|
411
345
|
import_node_fs.default.closeSync(fd);
|
|
412
346
|
return () => {
|
|
413
347
|
try {
|
|
414
|
-
if (import_node_fs.default.existsSync(lockPath))
|
|
415
|
-
import_node_fs.default.unlinkSync(lockPath);
|
|
416
|
-
}
|
|
348
|
+
if (import_node_fs.default.existsSync(lockPath)) import_node_fs.default.unlinkSync(lockPath);
|
|
417
349
|
} catch {
|
|
418
350
|
}
|
|
419
351
|
};
|
|
420
352
|
} catch (err) {
|
|
421
353
|
if (err.code === "EEXIST") {
|
|
422
354
|
try {
|
|
423
|
-
const
|
|
424
|
-
const meta = JSON.parse(content);
|
|
355
|
+
const meta = JSON.parse(import_node_fs.default.readFileSync(lockPath, "utf-8"));
|
|
425
356
|
const isStale = Date.now() - meta.createdAt > staleTimeoutMs;
|
|
426
357
|
const isDead = meta.pid && !isProcessAlive(meta.pid);
|
|
427
358
|
if (isStale || isDead) {
|
|
@@ -433,8 +364,7 @@ function acquireSyncLock(lockPath, maxWaitMs = 1500, staleTimeoutMs = 5e3) {
|
|
|
433
364
|
}
|
|
434
365
|
} catch {
|
|
435
366
|
}
|
|
436
|
-
|
|
437
|
-
sleepSync(delay);
|
|
367
|
+
sleepSync(Math.min(40, Math.floor(attempt * 4 + Math.random() * 8)));
|
|
438
368
|
continue;
|
|
439
369
|
}
|
|
440
370
|
break;
|
|
@@ -443,50 +373,46 @@ function acquireSyncLock(lockPath, maxWaitMs = 1500, staleTimeoutMs = 5e3) {
|
|
|
443
373
|
return () => {
|
|
444
374
|
};
|
|
445
375
|
}
|
|
446
|
-
function atomicRenameWithRetry(tempPath, targetPath,
|
|
447
|
-
|
|
376
|
+
function atomicRenameWithRetry(tempPath, targetPath, maxRetries = 5) {
|
|
377
|
+
let lastErr;
|
|
378
|
+
for (let i = 0; i < maxRetries; i++) {
|
|
448
379
|
try {
|
|
449
380
|
import_node_fs.default.renameSync(tempPath, targetPath);
|
|
450
381
|
return;
|
|
451
382
|
} catch (err) {
|
|
452
|
-
|
|
453
|
-
if (
|
|
454
|
-
|
|
455
|
-
sleepSync(delay);
|
|
383
|
+
lastErr = err;
|
|
384
|
+
if (err.code === "EPERM" || err.code === "EACCES" || err.code === "EBUSY") {
|
|
385
|
+
sleepSync(15 * (i + 1));
|
|
456
386
|
continue;
|
|
457
387
|
}
|
|
458
|
-
|
|
459
|
-
try {
|
|
460
|
-
if (import_node_fs.default.existsSync(tempPath)) import_node_fs.default.renameSync(tempPath, crashDump);
|
|
461
|
-
} catch {
|
|
462
|
-
}
|
|
463
|
-
throw err;
|
|
388
|
+
break;
|
|
464
389
|
}
|
|
465
390
|
}
|
|
391
|
+
try {
|
|
392
|
+
import_node_fs.default.copyFileSync(tempPath, targetPath);
|
|
393
|
+
import_node_fs.default.unlinkSync(tempPath);
|
|
394
|
+
} catch {
|
|
395
|
+
throw lastErr;
|
|
396
|
+
}
|
|
466
397
|
}
|
|
467
398
|
var AnchorStore = class {
|
|
468
399
|
storageDir;
|
|
469
400
|
statePath;
|
|
401
|
+
lockPath;
|
|
470
402
|
archivePath;
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
this.storageDir = getDefaultStorageDir();
|
|
477
|
-
}
|
|
478
|
-
this.statePath = import_node_path2.default.join(this.storageDir, "state.json");
|
|
479
|
-
this.archivePath = import_node_path2.default.join(this.storageDir, "archive.jsonl");
|
|
480
|
-
this.graveyardPath = import_node_path2.default.join(this.storageDir, "graveyard.jsonl");
|
|
481
|
-
this.ensureDirs();
|
|
403
|
+
constructor(storageDir) {
|
|
404
|
+
this.storageDir = storageDir ? import_node_path.default.resolve(storageDir) : getDefaultStorageDir();
|
|
405
|
+
this.statePath = import_node_path.default.join(this.storageDir, "state.json");
|
|
406
|
+
this.lockPath = import_node_path.default.join(this.storageDir, "state.lock");
|
|
407
|
+
this.archivePath = import_node_path.default.join(this.storageDir, "archive.jsonl");
|
|
482
408
|
}
|
|
483
|
-
|
|
484
|
-
|
|
409
|
+
ensureDirs() {
|
|
410
|
+
if (!import_node_fs.default.existsSync(this.storageDir)) {
|
|
411
|
+
import_node_fs.default.mkdirSync(this.storageDir, { recursive: true });
|
|
412
|
+
}
|
|
485
413
|
}
|
|
486
|
-
/**
|
|
487
|
-
* Execute mutation within a cross-process exclusive lock
|
|
488
|
-
*/
|
|
489
414
|
withLock(fn) {
|
|
415
|
+
this.ensureDirs();
|
|
490
416
|
const release = acquireSyncLock(this.lockPath);
|
|
491
417
|
try {
|
|
492
418
|
return fn();
|
|
@@ -494,84 +420,35 @@ var AnchorStore = class {
|
|
|
494
420
|
release();
|
|
495
421
|
}
|
|
496
422
|
}
|
|
497
|
-
ensureDirs() {
|
|
498
|
-
if (!import_node_fs.default.existsSync(this.storageDir)) {
|
|
499
|
-
try {
|
|
500
|
-
import_node_fs.default.mkdirSync(this.storageDir, { recursive: true });
|
|
501
|
-
} catch {
|
|
502
|
-
}
|
|
503
|
-
}
|
|
504
|
-
}
|
|
505
|
-
/**
|
|
506
|
-
* Load store state. If corrupt or missing, returns safe default.
|
|
507
|
-
*/
|
|
508
423
|
loadState() {
|
|
509
424
|
this.ensureDirs();
|
|
510
425
|
if (!import_node_fs.default.existsSync(this.statePath)) {
|
|
511
|
-
return {
|
|
512
|
-
version: 1,
|
|
513
|
-
anchors: [],
|
|
514
|
-
lastSweepAt: Date.now()
|
|
515
|
-
};
|
|
516
|
-
}
|
|
517
|
-
let raw = null;
|
|
518
|
-
for (let attempt = 1; attempt <= 3; attempt++) {
|
|
519
|
-
try {
|
|
520
|
-
raw = import_node_fs.default.readFileSync(this.statePath, "utf-8");
|
|
521
|
-
break;
|
|
522
|
-
} catch (err) {
|
|
523
|
-
if ((err?.code === "EBUSY" || err?.code === "EPERM") && attempt < 3) {
|
|
524
|
-
sleepSync(15);
|
|
525
|
-
continue;
|
|
526
|
-
}
|
|
527
|
-
}
|
|
528
|
-
}
|
|
529
|
-
if (raw === null) {
|
|
530
|
-
return { version: 1, anchors: [], lastSweepAt: Date.now() };
|
|
426
|
+
return { version: 1, anchors: [], lastSweepAt: Date.now(), nextId: 1 };
|
|
531
427
|
}
|
|
532
428
|
try {
|
|
533
|
-
const
|
|
534
|
-
if (!
|
|
535
|
-
|
|
429
|
+
const parsed = JSON.parse(import_node_fs.default.readFileSync(this.statePath, "utf-8"));
|
|
430
|
+
if (!parsed || !Array.isArray(parsed.anchors)) {
|
|
431
|
+
return { version: 1, anchors: [], lastSweepAt: Date.now(), nextId: 1 };
|
|
536
432
|
}
|
|
537
|
-
|
|
538
|
-
...a,
|
|
539
|
-
files: Array.isArray(a.files) ? a.files.filter((f) => typeof f === "string") : [],
|
|
540
|
-
tags: Array.isArray(a.tags) ? a.tags.filter((t) => typeof t === "string") : [],
|
|
541
|
-
decay: {
|
|
542
|
-
activeDays: Number.isFinite(a.decay?.activeDays) && a.decay.activeDays > 0 ? a.decay.activeDays : DURABLE_DECAY_POLICY.activeDays,
|
|
543
|
-
sleepDays: Number.isFinite(a.decay?.sleepDays) && a.decay.sleepDays > 0 ? a.decay.sleepDays : DURABLE_DECAY_POLICY.sleepDays,
|
|
544
|
-
graveyardDays: Number.isFinite(a.decay?.graveyardDays) && a.decay.graveyardDays > 0 ? a.decay.graveyardDays : DURABLE_DECAY_POLICY.graveyardDays
|
|
545
|
-
}
|
|
546
|
-
}));
|
|
547
|
-
return data;
|
|
433
|
+
return parsed;
|
|
548
434
|
} catch {
|
|
549
|
-
const backupPath = import_node_path2.default.join(this.storageDir, `state.corrupt.${Date.now()}.json`);
|
|
550
435
|
try {
|
|
551
|
-
import_node_fs.default.
|
|
436
|
+
import_node_fs.default.copyFileSync(this.statePath, import_node_path.default.join(this.storageDir, `state.corrupt.${Date.now()}.json`));
|
|
552
437
|
} catch {
|
|
553
438
|
}
|
|
554
|
-
return {
|
|
555
|
-
version: 1,
|
|
556
|
-
anchors: [],
|
|
557
|
-
lastSweepAt: Date.now()
|
|
558
|
-
};
|
|
439
|
+
return { version: 1, anchors: [], lastSweepAt: Date.now(), nextId: 1 };
|
|
559
440
|
}
|
|
560
441
|
}
|
|
561
|
-
/**
|
|
562
|
-
* Atomically save store state via temp file + atomic rename with Windows NTFS spin-retry
|
|
563
|
-
*/
|
|
564
442
|
saveState(state) {
|
|
565
443
|
this.ensureDirs();
|
|
566
|
-
const
|
|
567
|
-
const tempPath = import_node_path2.default.join(this.storageDir, `state.tmp.${process.pid}.${Date.now()}.${rand}`);
|
|
444
|
+
const tempPath = `${this.statePath}.${process.pid}.${Date.now()}.tmp`;
|
|
568
445
|
import_node_fs.default.writeFileSync(tempPath, JSON.stringify(state, null, 2), "utf-8");
|
|
569
446
|
atomicRenameWithRetry(tempPath, this.statePath);
|
|
570
447
|
}
|
|
571
448
|
generateId(state) {
|
|
572
449
|
const numbers = state.anchors.map((a) => {
|
|
573
|
-
const
|
|
574
|
-
return
|
|
450
|
+
const m = a.id.match(/^anc-(\d+)$/);
|
|
451
|
+
return m ? parseInt(m[1], 10) : 0;
|
|
575
452
|
}).filter((n) => n > 0);
|
|
576
453
|
const maxExisting = numbers.length > 0 ? Math.max(...numbers) : 0;
|
|
577
454
|
const nextNum = Math.max(maxExisting + 1, state.nextId || 1);
|
|
@@ -579,38 +456,30 @@ var AnchorStore = class {
|
|
|
579
456
|
return `anc-${nextNum}`;
|
|
580
457
|
}
|
|
581
458
|
/**
|
|
582
|
-
*
|
|
459
|
+
* Pin a new sticky note
|
|
583
460
|
*/
|
|
584
461
|
create(input) {
|
|
585
462
|
return this.withLock(() => {
|
|
586
463
|
const state = this.loadState();
|
|
587
464
|
const now = Date.now();
|
|
588
465
|
const id = this.generateId(state);
|
|
466
|
+
const { cleanTitle, progress: parsedProgress } = extractProgressFromText(input.title);
|
|
589
467
|
const explicitFiles = (input.files || []).map((f) => normalizePath(f)).filter(Boolean);
|
|
590
|
-
const inferredFiles = explicitFiles.length > 0 ? explicitFiles : extractPathsFromText(`${
|
|
468
|
+
const inferredFiles = explicitFiles.length > 0 ? explicitFiles : extractPathsFromText(`${cleanTitle} ${input.description || ""}`);
|
|
591
469
|
let cwd = input.cwd ? normalizePath(input.cwd) : "";
|
|
592
470
|
if (input.project && (/^[a-zA-Z]:[\\/]/.test(input.project.trim()) || input.project.trim().startsWith("/"))) {
|
|
593
471
|
cwd = normalizePath(input.project.trim());
|
|
594
472
|
} else if (inferredFiles.length > 0 && (/^[a-zA-Z]:\//.test(inferredFiles[0]) || inferredFiles[0].startsWith("/"))) {
|
|
595
|
-
const firstDir =
|
|
596
|
-
if (firstDir && firstDir !== ".")
|
|
597
|
-
cwd = normalizePath(firstDir);
|
|
598
|
-
}
|
|
473
|
+
const firstDir = import_node_path.default.dirname(inferredFiles[0]);
|
|
474
|
+
if (firstDir && firstDir !== ".") cwd = normalizePath(firstDir);
|
|
599
475
|
}
|
|
600
|
-
const projectName = input.project ? input.project.trim() : cwd ?
|
|
601
|
-
const recurrence = input.recurrence || detectRecurrence(input.title);
|
|
602
|
-
const targetDate = input.targetDate || detectTargetDate(input.title, now);
|
|
603
|
-
const durability = recurrence ? "durable" : input.durability || detectDurability(input.title);
|
|
604
|
-
const decayPolicy = durability === "ephemeral" ? getEphemeralDecayPolicy(input.title) : DURABLE_DECAY_POLICY;
|
|
476
|
+
const projectName = input.project ? input.project.trim() : cwd ? import_node_path.default.basename(cwd) : "global";
|
|
605
477
|
const anchor = {
|
|
606
478
|
id,
|
|
607
|
-
title:
|
|
479
|
+
title: cleanTitle,
|
|
608
480
|
description: input.description?.trim(),
|
|
609
481
|
priority: input.priority || "p1",
|
|
610
482
|
status: "active",
|
|
611
|
-
durability,
|
|
612
|
-
recurrence,
|
|
613
|
-
targetDate,
|
|
614
483
|
project: projectName,
|
|
615
484
|
cwd,
|
|
616
485
|
createdAt: now,
|
|
@@ -618,8 +487,8 @@ var AnchorStore = class {
|
|
|
618
487
|
lastTouchedAt: now,
|
|
619
488
|
files: inferredFiles,
|
|
620
489
|
tags: (input.tags || []).map((t) => t.trim()).filter(Boolean),
|
|
621
|
-
|
|
622
|
-
|
|
490
|
+
...input.sessionFile ? { sessionFile: normalizePath(input.sessionFile) } : {},
|
|
491
|
+
...input.progress || parsedProgress ? { progress: input.progress || parsedProgress } : {}
|
|
623
492
|
};
|
|
624
493
|
state.anchors.push(anchor);
|
|
625
494
|
this.saveState(state);
|
|
@@ -629,31 +498,19 @@ var AnchorStore = class {
|
|
|
629
498
|
findAnchorIndex(anchors, id) {
|
|
630
499
|
const clean = id.trim().replace(/^#/, "");
|
|
631
500
|
return anchors.findIndex(
|
|
632
|
-
(a) => a.id === clean || a.id === `anc-${clean}` || (
|
|
501
|
+
(a) => a.id === clean || a.id === `anc-${clean}` || (/^\d+$/.test(clean) ? a.id === `anc-${parseInt(clean, 10)}` : false)
|
|
633
502
|
);
|
|
634
503
|
}
|
|
635
|
-
/**
|
|
636
|
-
* Get an anchor by ID (supports 'anc-1', '#anc-1', '1', '01')
|
|
637
|
-
*/
|
|
638
504
|
get(id) {
|
|
639
505
|
const state = this.loadState();
|
|
640
506
|
const idx = this.findAnchorIndex(state.anchors, id);
|
|
641
507
|
return idx === -1 ? void 0 : state.anchors[idx];
|
|
642
508
|
}
|
|
643
|
-
/**
|
|
644
|
-
* List anchors. By default, if cwd is provided, only returns anchors
|
|
645
|
-
* belonging to this cwd or marked as global (''). Pass all: true for global view.
|
|
646
|
-
*/
|
|
647
509
|
list(filter) {
|
|
648
510
|
const state = this.loadState();
|
|
649
511
|
const targetCwd = filter?.cwd ? normalizePath(filter.cwd) : null;
|
|
650
|
-
const today = getTodayDateString(filter?.now);
|
|
651
512
|
return state.anchors.filter((a) => {
|
|
652
|
-
if (!filter?.all && filter?.status === "active" && a.recurrence === "daily" && a.lastCompletedDate === today) {
|
|
653
|
-
return false;
|
|
654
|
-
}
|
|
655
513
|
if (filter?.status && a.status !== filter.status) return false;
|
|
656
|
-
if (filter?.priority && a.priority !== filter.priority) return false;
|
|
657
514
|
if (!filter?.all && targetCwd) {
|
|
658
515
|
if (a.files.length > 0 && (a.cwd || a.project) && !isAnchorTargetingWorkspace(a, targetCwd)) {
|
|
659
516
|
return false;
|
|
@@ -662,19 +519,13 @@ var AnchorStore = class {
|
|
|
662
519
|
return true;
|
|
663
520
|
});
|
|
664
521
|
}
|
|
665
|
-
/**
|
|
666
|
-
* Update an existing anchor
|
|
667
|
-
*/
|
|
668
522
|
update(id, patch) {
|
|
669
523
|
return this.withLock(() => {
|
|
670
524
|
const state = this.loadState();
|
|
671
525
|
const idx = this.findAnchorIndex(state.anchors, id);
|
|
672
|
-
if (idx === -1) {
|
|
673
|
-
throw new Error(`Anchor not found: ${id}`);
|
|
674
|
-
}
|
|
675
|
-
const current = state.anchors[idx];
|
|
526
|
+
if (idx === -1) throw new Error(`Note not found: ${id}`);
|
|
676
527
|
const updated = {
|
|
677
|
-
...
|
|
528
|
+
...state.anchors[idx],
|
|
678
529
|
...patch,
|
|
679
530
|
updatedAt: Date.now()
|
|
680
531
|
};
|
|
@@ -686,60 +537,70 @@ var AnchorStore = class {
|
|
|
686
537
|
return updated;
|
|
687
538
|
});
|
|
688
539
|
}
|
|
689
|
-
|
|
690
|
-
* Touch an anchor to refresh its decay window and reactivate sleeping state
|
|
691
|
-
*/
|
|
692
|
-
touch(id, timestamp = Date.now()) {
|
|
540
|
+
touch(id, timestamp = Date.now(), sessionFile) {
|
|
693
541
|
return this.withLock(() => {
|
|
694
542
|
const state = this.loadState();
|
|
695
543
|
const idx = this.findAnchorIndex(state.anchors, id);
|
|
696
|
-
if (idx === -1) {
|
|
697
|
-
throw new Error(`Anchor not found: ${id}`);
|
|
698
|
-
}
|
|
544
|
+
if (idx === -1) throw new Error(`Note not found: ${id}`);
|
|
699
545
|
const current = state.anchors[idx];
|
|
700
546
|
current.lastTouchedAt = timestamp;
|
|
701
547
|
current.updatedAt = timestamp;
|
|
702
548
|
if (current.status === "sleeping") {
|
|
703
549
|
current.status = "active";
|
|
704
550
|
}
|
|
551
|
+
if (sessionFile) {
|
|
552
|
+
current.sessionFile = normalizePath(sessionFile);
|
|
553
|
+
}
|
|
705
554
|
state.anchors[idx] = current;
|
|
706
555
|
this.saveState(state);
|
|
707
556
|
return current;
|
|
708
557
|
});
|
|
709
558
|
}
|
|
710
559
|
/**
|
|
711
|
-
*
|
|
560
|
+
* Advance (or set) progress on a sticky note:
|
|
561
|
+
* - If delta is provided (default +1), increments `done` up to `total`.
|
|
562
|
+
* - If note had no progress yet, initializes a 4-step bar (`1/4`).
|
|
712
563
|
*/
|
|
713
|
-
|
|
564
|
+
step(id, delta = 1, explicitTotal) {
|
|
714
565
|
return this.withLock(() => {
|
|
715
566
|
const state = this.loadState();
|
|
716
567
|
const idx = this.findAnchorIndex(state.anchors, id);
|
|
717
|
-
if (idx === -1) {
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
const
|
|
721
|
-
const
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
import_node_fs.default.appendFileSync(this.archivePath, JSON.stringify({ ...anchor, settledForDate: today }) + "\n", "utf-8");
|
|
733
|
-
this.saveState(state);
|
|
734
|
-
return anchor;
|
|
568
|
+
if (idx === -1) throw new Error(`Note not found: ${id}`);
|
|
569
|
+
const current = state.anchors[idx];
|
|
570
|
+
const now = Date.now();
|
|
571
|
+
const total = explicitTotal && explicitTotal >= 2 ? explicitTotal : current.progress?.total || 4;
|
|
572
|
+
const prevDone = current.progress?.done || 0;
|
|
573
|
+
const nextDone = Math.max(0, Math.min(total, prevDone + delta));
|
|
574
|
+
current.progress = {
|
|
575
|
+
...current.progress || {},
|
|
576
|
+
done: nextDone,
|
|
577
|
+
total
|
|
578
|
+
};
|
|
579
|
+
current.lastTouchedAt = now;
|
|
580
|
+
current.updatedAt = now;
|
|
581
|
+
if (current.status === "sleeping") {
|
|
582
|
+
current.status = "active";
|
|
735
583
|
}
|
|
584
|
+
state.anchors[idx] = current;
|
|
585
|
+
this.saveState(state);
|
|
586
|
+
return { anchor: current, completed: nextDone >= total };
|
|
587
|
+
});
|
|
588
|
+
}
|
|
589
|
+
/**
|
|
590
|
+
* Tear off (settle) a completed sticky note and archive it to archive.jsonl
|
|
591
|
+
*/
|
|
592
|
+
settle(id, evidence = {}, now = Date.now()) {
|
|
593
|
+
return this.withLock(() => {
|
|
594
|
+
const state = this.loadState();
|
|
595
|
+
const idx = this.findAnchorIndex(state.anchors, id);
|
|
596
|
+
if (idx === -1) throw new Error(`Note not found: ${id}`);
|
|
736
597
|
const [settled] = state.anchors.splice(idx, 1);
|
|
737
598
|
settled.status = "settled";
|
|
738
599
|
settled.updatedAt = now;
|
|
739
600
|
settled.evidence = {
|
|
740
601
|
...evidence,
|
|
741
602
|
settledAt: now,
|
|
742
|
-
settledBy: evidence.settledBy || "manual
|
|
603
|
+
settledBy: evidence.settledBy || "manual"
|
|
743
604
|
};
|
|
744
605
|
this.ensureDirs();
|
|
745
606
|
import_node_fs.default.appendFileSync(this.archivePath, JSON.stringify(settled) + "\n", "utf-8");
|
|
@@ -748,12 +609,12 @@ var AnchorStore = class {
|
|
|
748
609
|
});
|
|
749
610
|
}
|
|
750
611
|
/**
|
|
751
|
-
*
|
|
612
|
+
* Put back (undo) the last torn-off sticky note
|
|
752
613
|
*/
|
|
753
614
|
undoSettle() {
|
|
754
615
|
return this.withLock(() => {
|
|
755
616
|
if (!import_node_fs.default.existsSync(this.archivePath)) {
|
|
756
|
-
throw new Error("No archived
|
|
617
|
+
throw new Error("No archived notes to restore");
|
|
757
618
|
}
|
|
758
619
|
const raw = import_node_fs.default.readFileSync(this.archivePath, "utf-8");
|
|
759
620
|
const lines = raw.split(/\r?\n/).map((l) => l.trim()).filter(Boolean);
|
|
@@ -762,23 +623,18 @@ var AnchorStore = class {
|
|
|
762
623
|
const candidate = lines.pop();
|
|
763
624
|
try {
|
|
764
625
|
const parsed = JSON.parse(candidate);
|
|
765
|
-
if (parsed && typeof parsed
|
|
626
|
+
if (parsed && typeof parsed.id === "string" && typeof parsed.title === "string") {
|
|
766
627
|
anchor = parsed;
|
|
767
628
|
}
|
|
768
629
|
} catch {
|
|
769
630
|
}
|
|
770
631
|
}
|
|
771
|
-
if (!anchor)
|
|
772
|
-
throw new Error("Archive is empty, nothing to undo");
|
|
773
|
-
}
|
|
632
|
+
if (!anchor) throw new Error("Archive is empty, nothing to undo");
|
|
774
633
|
import_node_fs.default.writeFileSync(this.archivePath, lines.length > 0 ? lines.join("\n") + "\n" : "", "utf-8");
|
|
775
634
|
anchor.status = "active";
|
|
776
635
|
anchor.updatedAt = Date.now();
|
|
636
|
+
anchor.lastTouchedAt = Date.now();
|
|
777
637
|
delete anchor.evidence;
|
|
778
|
-
delete anchor.settledForDate;
|
|
779
|
-
if (anchor.recurrence === "daily") {
|
|
780
|
-
delete anchor.lastCompletedDate;
|
|
781
|
-
}
|
|
782
638
|
const state = this.loadState();
|
|
783
639
|
const existingIdx = state.anchors.findIndex((a) => a.id === anchor.id);
|
|
784
640
|
if (existingIdx !== -1) {
|
|
@@ -789,320 +645,19 @@ var AnchorStore = class {
|
|
|
789
645
|
state.anchors.push(anchor);
|
|
790
646
|
}
|
|
791
647
|
} else {
|
|
792
|
-
const
|
|
793
|
-
if (
|
|
794
|
-
const restoredNum = parseInt(match[1], 10);
|
|
795
|
-
state.nextId = Math.max(state.nextId || 1, restoredNum + 1);
|
|
796
|
-
}
|
|
648
|
+
const m = anchor.id.match(/^anc-(\d+)$/);
|
|
649
|
+
if (m) state.nextId = Math.max(state.nextId || 1, parseInt(m[1], 10) + 1);
|
|
797
650
|
state.anchors.push(anchor);
|
|
798
651
|
}
|
|
799
652
|
this.saveState(state);
|
|
800
653
|
return anchor;
|
|
801
654
|
});
|
|
802
655
|
}
|
|
803
|
-
/**
|
|
804
|
-
* Evict anchor to graveyard.jsonl upon total decay expiry
|
|
805
|
-
*/
|
|
806
|
-
dropToGraveyard(id, reason) {
|
|
807
|
-
return this.withLock(() => {
|
|
808
|
-
const state = this.loadState();
|
|
809
|
-
const idx = state.anchors.findIndex((a) => a.id === id);
|
|
810
|
-
if (idx === -1) {
|
|
811
|
-
throw new Error(`Anchor not found: ${id}`);
|
|
812
|
-
}
|
|
813
|
-
const [anchor] = state.anchors.splice(idx, 1);
|
|
814
|
-
anchor.status = "graveyard";
|
|
815
|
-
anchor.updatedAt = Date.now();
|
|
816
|
-
anchor.evidence = {
|
|
817
|
-
summary: reason,
|
|
818
|
-
settledAt: Date.now()
|
|
819
|
-
};
|
|
820
|
-
this.ensureDirs();
|
|
821
|
-
import_node_fs.default.appendFileSync(this.graveyardPath, JSON.stringify(anchor) + "\n", "utf-8");
|
|
822
|
-
this.saveState(state);
|
|
823
|
-
return anchor;
|
|
824
|
-
});
|
|
825
|
-
}
|
|
826
|
-
getArchive(filter) {
|
|
827
|
-
if (!import_node_fs.default.existsSync(this.archivePath)) return [];
|
|
828
|
-
try {
|
|
829
|
-
const raw = import_node_fs.default.readFileSync(this.archivePath, "utf-8");
|
|
830
|
-
const all = [];
|
|
831
|
-
for (const l of raw.split(/\r?\n/)) {
|
|
832
|
-
const trimmed = l.trim();
|
|
833
|
-
if (!trimmed) continue;
|
|
834
|
-
try {
|
|
835
|
-
const parsed = JSON.parse(trimmed);
|
|
836
|
-
if (parsed && typeof parsed === "object" && typeof parsed.id === "string") all.push(parsed);
|
|
837
|
-
} catch {
|
|
838
|
-
}
|
|
839
|
-
}
|
|
840
|
-
if (filter?.all || !filter?.cwd) return all;
|
|
841
|
-
const targetCwd = normalizePath(filter.cwd);
|
|
842
|
-
return all.filter((a) => !a.cwd || isSameOrSubPath(a.cwd, targetCwd));
|
|
843
|
-
} catch {
|
|
844
|
-
return [];
|
|
845
|
-
}
|
|
846
|
-
}
|
|
847
|
-
getGraveyard(filter) {
|
|
848
|
-
if (!import_node_fs.default.existsSync(this.graveyardPath)) return [];
|
|
849
|
-
try {
|
|
850
|
-
const raw = import_node_fs.default.readFileSync(this.graveyardPath, "utf-8");
|
|
851
|
-
const all = [];
|
|
852
|
-
for (const l of raw.split(/\r?\n/)) {
|
|
853
|
-
const trimmed = l.trim();
|
|
854
|
-
if (!trimmed) continue;
|
|
855
|
-
try {
|
|
856
|
-
const parsed = JSON.parse(trimmed);
|
|
857
|
-
if (parsed && typeof parsed === "object" && typeof parsed.id === "string") all.push(parsed);
|
|
858
|
-
} catch {
|
|
859
|
-
}
|
|
860
|
-
}
|
|
861
|
-
if (filter?.all || !filter?.cwd) return all;
|
|
862
|
-
const targetCwd = normalizePath(filter.cwd);
|
|
863
|
-
return all.filter((a) => !a.cwd || isSameOrSubPath(a.cwd, targetCwd));
|
|
864
|
-
} catch {
|
|
865
|
-
return [];
|
|
866
|
-
}
|
|
867
|
-
}
|
|
868
656
|
};
|
|
869
657
|
|
|
870
|
-
// src/decay.ts
|
|
871
|
-
var import_node_fs2 = __toESM(require("fs"), 1);
|
|
872
|
-
var MS_PER_DAY = 864e5;
|
|
873
|
-
function evaluateAnchorDecay(anchor, now = Date.now()) {
|
|
874
|
-
if (anchor.status === "settled" || anchor.status === "graveyard") {
|
|
875
|
-
return {
|
|
876
|
-
currentStatus: anchor.status,
|
|
877
|
-
nextStatus: anchor.status,
|
|
878
|
-
daysUntouched: 0,
|
|
879
|
-
remainingActiveDays: 0,
|
|
880
|
-
remainingSleepDays: 0
|
|
881
|
-
};
|
|
882
|
-
}
|
|
883
|
-
const elapsedMs = Math.max(0, now - anchor.lastTouchedAt);
|
|
884
|
-
const daysUntouched = elapsedMs / MS_PER_DAY;
|
|
885
|
-
const activeDays = anchor.decay.activeDays;
|
|
886
|
-
const graveyardDays = anchor.decay.graveyardDays;
|
|
887
|
-
let nextStatus = anchor.status;
|
|
888
|
-
if (daysUntouched <= activeDays) {
|
|
889
|
-
nextStatus = "active";
|
|
890
|
-
} else if (daysUntouched <= graveyardDays) {
|
|
891
|
-
nextStatus = "sleeping";
|
|
892
|
-
} else {
|
|
893
|
-
nextStatus = "graveyard";
|
|
894
|
-
}
|
|
895
|
-
return {
|
|
896
|
-
currentStatus: anchor.status,
|
|
897
|
-
nextStatus,
|
|
898
|
-
daysUntouched: Math.round(daysUntouched * 10) / 10,
|
|
899
|
-
remainingActiveDays: Math.max(0, Math.round((activeDays - daysUntouched) * 10) / 10),
|
|
900
|
-
remainingSleepDays: Math.max(0, Math.round((graveyardDays - daysUntouched) * 10) / 10)
|
|
901
|
-
};
|
|
902
|
-
}
|
|
903
|
-
function sweepStore(store, now = Date.now()) {
|
|
904
|
-
return store.withLock(() => {
|
|
905
|
-
const state = store.loadState();
|
|
906
|
-
const result = {
|
|
907
|
-
transitionedToSleeping: [],
|
|
908
|
-
wokenToActive: [],
|
|
909
|
-
evictedToGraveyard: []
|
|
910
|
-
};
|
|
911
|
-
const toEvict = [];
|
|
912
|
-
let stateModified = false;
|
|
913
|
-
for (const anchor of state.anchors) {
|
|
914
|
-
const evalResult = evaluateAnchorDecay(anchor, now);
|
|
915
|
-
if (evalResult.nextStatus === "graveyard") {
|
|
916
|
-
toEvict.push(anchor);
|
|
917
|
-
} else if (evalResult.nextStatus !== anchor.status) {
|
|
918
|
-
if (evalResult.nextStatus === "sleeping" && anchor.status === "active") {
|
|
919
|
-
anchor.status = "sleeping";
|
|
920
|
-
anchor.updatedAt = now;
|
|
921
|
-
result.transitionedToSleeping.push(anchor.id);
|
|
922
|
-
stateModified = true;
|
|
923
|
-
} else if (evalResult.nextStatus === "active" && anchor.status === "sleeping") {
|
|
924
|
-
anchor.status = "active";
|
|
925
|
-
anchor.updatedAt = now;
|
|
926
|
-
result.wokenToActive.push(anchor.id);
|
|
927
|
-
stateModified = true;
|
|
928
|
-
}
|
|
929
|
-
}
|
|
930
|
-
}
|
|
931
|
-
if (toEvict.length > 0) {
|
|
932
|
-
const evictIds = new Set(toEvict.map((e) => e.id));
|
|
933
|
-
state.anchors = state.anchors.filter((a) => !evictIds.has(a.id));
|
|
934
|
-
stateModified = true;
|
|
935
|
-
const graveyardLines = toEvict.map((exp) => {
|
|
936
|
-
const rec = {
|
|
937
|
-
...exp,
|
|
938
|
-
status: "graveyard",
|
|
939
|
-
updatedAt: now,
|
|
940
|
-
evictedAt: now,
|
|
941
|
-
evictionReason: `Exceeded decay threshold (${exp.decay.graveyardDays} days untouched)`
|
|
942
|
-
};
|
|
943
|
-
return JSON.stringify(rec);
|
|
944
|
-
}).join("\n") + "\n";
|
|
945
|
-
import_node_fs2.default.appendFileSync(store.graveyardPath, graveyardLines, "utf-8");
|
|
946
|
-
for (const exp of toEvict) {
|
|
947
|
-
result.evictedToGraveyard.push(exp.id);
|
|
948
|
-
}
|
|
949
|
-
}
|
|
950
|
-
state.lastSweepAt = now;
|
|
951
|
-
if (stateModified) {
|
|
952
|
-
store.saveState(state);
|
|
953
|
-
}
|
|
954
|
-
return result;
|
|
955
|
-
});
|
|
956
|
-
}
|
|
957
|
-
|
|
958
|
-
// src/settlement.ts
|
|
959
|
-
var import_node_child_process = require("child_process");
|
|
960
|
-
function runPhysicalVerification(anchor, cwd) {
|
|
961
|
-
if (!anchor.verifyCommand?.trim()) {
|
|
962
|
-
return { success: false, output: "No verification command specified" };
|
|
963
|
-
}
|
|
964
|
-
const targetCwd = anchor.cwd || cwd;
|
|
965
|
-
try {
|
|
966
|
-
const stdout = (0, import_node_child_process.execSync)(anchor.verifyCommand, {
|
|
967
|
-
cwd: targetCwd,
|
|
968
|
-
encoding: "utf-8",
|
|
969
|
-
timeout: 8e3,
|
|
970
|
-
maxBuffer: 4 * 1024 * 1024,
|
|
971
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
972
|
-
});
|
|
973
|
-
return { success: true, output: stdout.trim() };
|
|
974
|
-
} catch (err) {
|
|
975
|
-
return { success: false, output: String(err.stderr || err.stdout || err.message).trim() };
|
|
976
|
-
}
|
|
977
|
-
}
|
|
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) {
|
|
1008
|
-
const activeAndSleeping = store.list({ cwd }).filter((a) => a.status === "active" || a.status === "sleeping");
|
|
1009
|
-
const matches = findMatchedAnchors(activeAndSleeping, touchedFiles);
|
|
1010
|
-
const seenIds = /* @__PURE__ */ new Set();
|
|
1011
|
-
const proposals = [];
|
|
1012
|
-
for (const match of matches) {
|
|
1013
|
-
seenIds.add(match.anchor.id);
|
|
1014
|
-
const fileList = match.matchedFiles.slice(0, 3).join(", ");
|
|
1015
|
-
const moreSuffix = match.matchedFiles.length > 3 ? ` (+${match.matchedFiles.length - 3} more)` : "";
|
|
1016
|
-
proposals.push({
|
|
1017
|
-
anchor: match.anchor,
|
|
1018
|
-
matchedFiles: match.matchedFiles,
|
|
1019
|
-
reason: `Modified ${fileList}${moreSuffix}`,
|
|
1020
|
-
recommendedAction: match.score >= 0.85 ? "settle" : "defer"
|
|
1021
|
-
});
|
|
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
|
-
}
|
|
1038
|
-
return proposals;
|
|
1039
|
-
}
|
|
1040
|
-
function formatSettlementCard(proposal) {
|
|
1041
|
-
const a = proposal.anchor;
|
|
1042
|
-
const tagStr = a.tags.length > 0 ? ` [${a.tags.join(", ")}]` : "";
|
|
1043
|
-
const fileStr = proposal.matchedFiles.slice(0, 2).join(", ");
|
|
1044
|
-
return [
|
|
1045
|
-
`\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`,
|
|
1046
|
-
`\u2693 \u5173\u95E8\u7ED3\u6848\u63D0\u8BAE | Anchor Settlement`,
|
|
1047
|
-
` \u4EFB\u52A1 #${a.id}: ${a.title}${tagStr} (${a.priority.toUpperCase()})`,
|
|
1048
|
-
` \u8BC1\u636E\u89E6\u53D1: \u4FEE\u6539\u4E86 ${fileStr}`,
|
|
1049
|
-
` [Enter \u786E\u8BA4\u7ED3\u6848\u5E76\u5F52\u6863] / [Tab \u6682\u672A\u5B8C\u6210\uFF0C\u7EE7\u7EED\u6302\u8D77]`,
|
|
1050
|
-
`\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`
|
|
1051
|
-
].join("\n");
|
|
1052
|
-
}
|
|
1053
|
-
|
|
1054
658
|
// src/tui.ts
|
|
1055
|
-
var
|
|
1056
|
-
|
|
1057
|
-
if (anchor.recurrence === "daily") {
|
|
1058
|
-
return "Habits";
|
|
1059
|
-
}
|
|
1060
|
-
if (!anchor.targetDate) {
|
|
1061
|
-
return "Backlog";
|
|
1062
|
-
}
|
|
1063
|
-
const todayStr = getTodayDateString(now);
|
|
1064
|
-
if (anchor.targetDate <= todayStr) {
|
|
1065
|
-
return "Today";
|
|
1066
|
-
}
|
|
1067
|
-
return "Upcoming";
|
|
1068
|
-
}
|
|
1069
|
-
function groupAnchorsByQuadrant(anchors, now = Date.now()) {
|
|
1070
|
-
const groups = {
|
|
1071
|
-
today: [],
|
|
1072
|
-
upcoming: [],
|
|
1073
|
-
habits: [],
|
|
1074
|
-
backlog: []
|
|
1075
|
-
};
|
|
1076
|
-
for (const a of anchors) {
|
|
1077
|
-
const q = classifyAnchor(a, now);
|
|
1078
|
-
if (q === "Today") groups.today.push(a);
|
|
1079
|
-
else if (q === "Upcoming") groups.upcoming.push(a);
|
|
1080
|
-
else if (q === "Habits") groups.habits.push(a);
|
|
1081
|
-
else groups.backlog.push(a);
|
|
1082
|
-
}
|
|
1083
|
-
return groups;
|
|
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
|
-
}
|
|
659
|
+
var import_node_fs2 = __toESM(require("fs"), 1);
|
|
660
|
+
var import_node_path2 = __toESM(require("path"), 1);
|
|
1106
661
|
var ANSI_REGEX = /\x1b\[[0-9;]*[a-zA-Z]|\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)/g;
|
|
1107
662
|
function stripAnsi(str) {
|
|
1108
663
|
return str.replace(ANSI_REGEX, "");
|
|
@@ -1154,14 +709,12 @@ function truncateToWidth(str, maxWidth, ellipsis = "\u2026") {
|
|
|
1154
709
|
const charLen = codePoint > 65535 ? 2 : 1;
|
|
1155
710
|
const fullChar = singleLine.slice(i, i + charLen);
|
|
1156
711
|
const charWidth = getDisplayWidth(fullChar);
|
|
1157
|
-
if (accumWidth + charWidth > target)
|
|
1158
|
-
break;
|
|
1159
|
-
}
|
|
712
|
+
if (accumWidth + charWidth > target) break;
|
|
1160
713
|
accumulated += fullChar;
|
|
1161
714
|
accumWidth += charWidth;
|
|
1162
715
|
i += charLen;
|
|
1163
716
|
}
|
|
1164
|
-
accumulated = accumulated.replace(/\s*[\(\[\{(【]
|
|
717
|
+
accumulated = accumulated.replace(/\s*[\(\[\{(【][^)\]})】]*$/, "");
|
|
1165
718
|
return accumulated + ellipsis + (singleLine.includes("\x1B") ? "\x1B[0m" : "");
|
|
1166
719
|
}
|
|
1167
720
|
function padToWidth(str, targetWidth) {
|
|
@@ -1170,90 +723,68 @@ function padToWidth(str, targetWidth) {
|
|
|
1170
723
|
if (current >= targetWidth) return truncated;
|
|
1171
724
|
return truncated + " ".repeat(targetWidth - current);
|
|
1172
725
|
}
|
|
1173
|
-
function
|
|
1174
|
-
if (
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
}
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
const
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
if (anchor.targetDate === in2dStr) {
|
|
1191
|
-
return "In 2d";
|
|
1192
|
-
}
|
|
1193
|
-
const in3dStr = getTodayDateString(now + 3 * MS_DAY);
|
|
1194
|
-
if (anchor.targetDate === in3dStr) {
|
|
1195
|
-
return "In 3d";
|
|
1196
|
-
}
|
|
1197
|
-
if (anchor.targetDate < todayStr) {
|
|
1198
|
-
return "Overdue";
|
|
1199
|
-
}
|
|
1200
|
-
const parts = anchor.targetDate.split("-");
|
|
1201
|
-
if (parts.length === 3) {
|
|
1202
|
-
return `${parts[1]}-${parts[2]}`;
|
|
1203
|
-
}
|
|
1204
|
-
return anchor.targetDate;
|
|
726
|
+
function formatProgressBar(progress, cells = 4) {
|
|
727
|
+
if (!progress || !progress.total || progress.total < 1) return "";
|
|
728
|
+
const done = Math.max(0, Math.min(progress.total, progress.done || 0));
|
|
729
|
+
const filled = Math.min(cells, Math.max(done > 0 ? 1 : 0, Math.round(done / progress.total * cells)));
|
|
730
|
+
const finalFilled = done >= progress.total ? cells : done === 0 ? 0 : filled;
|
|
731
|
+
const bar = "\u25A0".repeat(finalFilled) + "\u25A1".repeat(Math.max(0, cells - finalFilled));
|
|
732
|
+
return `[${bar} ${done}/${progress.total}]`;
|
|
733
|
+
}
|
|
734
|
+
function formatRelativeAge(timestamp, now = Date.now()) {
|
|
735
|
+
const diffSec = Math.max(0, Math.floor((now - timestamp) / 1e3));
|
|
736
|
+
if (diffSec < 60) return "just now";
|
|
737
|
+
const diffMin = Math.floor(diffSec / 60);
|
|
738
|
+
if (diffMin < 60) return `${diffMin}m ago`;
|
|
739
|
+
const diffHours = Math.floor(diffMin / 60);
|
|
740
|
+
if (diffHours < 24) return `${diffHours}h ago`;
|
|
741
|
+
const diffDays = Math.floor(diffHours / 24);
|
|
742
|
+
return `${diffDays}d ago`;
|
|
1205
743
|
}
|
|
1206
|
-
function
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
const timeStr = `${pad(d.getHours())}:${pad(d.getMinutes())}`;
|
|
1211
|
-
const todayStr = getTodayDateString(now);
|
|
1212
|
-
const createdDayStr = getTodayDateString(timestamp);
|
|
1213
|
-
if (createdDayStr === todayStr) {
|
|
1214
|
-
return `Today ${timeStr}`;
|
|
744
|
+
function formatOrigin(anchor) {
|
|
745
|
+
if (typeof anchor === "string") {
|
|
746
|
+
if (!anchor) return "global";
|
|
747
|
+
return import_node_path2.default.basename(anchor.replace(/\\/g, "/")) || "global";
|
|
1215
748
|
}
|
|
1216
|
-
|
|
1217
|
-
if (
|
|
1218
|
-
|
|
749
|
+
let proj = "global";
|
|
750
|
+
if (anchor.project && anchor.project !== "global") {
|
|
751
|
+
const normProj = anchor.project.replace(/\\/g, "/").replace(/\/+$/, "");
|
|
752
|
+
if (normProj.includes("/")) {
|
|
753
|
+
const parts = normProj.split("/").filter(Boolean);
|
|
754
|
+
proj = parts.slice(-2).join("/");
|
|
755
|
+
} else {
|
|
756
|
+
proj = normProj;
|
|
757
|
+
}
|
|
758
|
+
} else if (anchor.cwd) {
|
|
759
|
+
proj = import_node_path2.default.basename(anchor.cwd.replace(/\\/g, "/")) || "global";
|
|
1219
760
|
}
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
761
|
+
if (anchor.files && anchor.files.length > 0) {
|
|
762
|
+
const shortFile = import_node_path2.default.basename(anchor.files[0].replace(/\\/g, "/"));
|
|
763
|
+
if (shortFile && !proj.endsWith(shortFile)) {
|
|
764
|
+
return `${proj} \xB7 ${shortFile}`;
|
|
765
|
+
}
|
|
1223
766
|
}
|
|
1224
|
-
return
|
|
1225
|
-
}
|
|
1226
|
-
function formatRelativeTime(timestamp, now = Date.now()) {
|
|
1227
|
-
return formatCreationTime(timestamp, now);
|
|
767
|
+
return proj;
|
|
1228
768
|
}
|
|
1229
|
-
function
|
|
1230
|
-
return
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
if (typeof anchor === "object") {
|
|
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;
|
|
769
|
+
function sortAnchorsForDisplay(anchors, cwd) {
|
|
770
|
+
return [...anchors].sort((a, b) => {
|
|
771
|
+
if (a.status !== b.status) {
|
|
772
|
+
return a.status === "active" ? -1 : 1;
|
|
1241
773
|
}
|
|
1242
|
-
if (
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
774
|
+
if (cwd) {
|
|
775
|
+
const aMatch = isAnchorTargetingWorkspace(a, cwd) ? 0 : 1;
|
|
776
|
+
const bMatch = isAnchorTargetingWorkspace(b, cwd) ? 0 : 1;
|
|
777
|
+
if (aMatch !== bMatch) return aMatch - bMatch;
|
|
778
|
+
}
|
|
779
|
+
return b.lastTouchedAt - a.lastTouchedAt;
|
|
780
|
+
});
|
|
1247
781
|
}
|
|
1248
782
|
function updateAnchorStatusBar(ctx, store) {
|
|
1249
783
|
if (!hasActiveUI(ctx)) return;
|
|
1250
784
|
try {
|
|
1251
785
|
const active = store.list({ status: "active", cwd: ctx.cwd });
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
return;
|
|
1255
|
-
}
|
|
1256
|
-
ctx.ui.setStatus("anchor", `\u2316 ${active.length}`);
|
|
786
|
+
ctx.ui.setStatus("anchor", active.length > 0 ? `\u2316 ${active.length}` : void 0);
|
|
787
|
+
updateStartupBanner(ctx, store);
|
|
1257
788
|
} catch {
|
|
1258
789
|
}
|
|
1259
790
|
}
|
|
@@ -1262,446 +793,215 @@ function updateStartupBanner(ctx, store) {
|
|
|
1262
793
|
try {
|
|
1263
794
|
const active = store.list({ status: "active", cwd: ctx.cwd });
|
|
1264
795
|
if (active.length === 0) {
|
|
1265
|
-
ctx.ui.setWidget("anchor-startup", void 0);
|
|
796
|
+
ctx.ui.setWidget("anchor-startup", void 0, { placement: "belowEditor" });
|
|
1266
797
|
return;
|
|
1267
798
|
}
|
|
1268
|
-
const
|
|
1269
|
-
const sorted = [...groups.today, ...groups.upcoming, ...groups.habits, ...groups.backlog];
|
|
1270
|
-
const topItems = sorted.slice(0, 3);
|
|
799
|
+
const sorted = sortAnchorsForDisplay(active, ctx.cwd);
|
|
1271
800
|
const factory = (_tui, theme) => ({
|
|
1272
801
|
render: (width) => {
|
|
1273
|
-
const maxTitleWidth = Math.max(12, width - 36);
|
|
1274
802
|
const fg = theme?.fg ? theme.fg.bind(theme) : (_c, text) => text;
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
const
|
|
1280
|
-
const
|
|
1281
|
-
const
|
|
1282
|
-
const
|
|
1283
|
-
const
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
lines.push(truncateToWidth(fg("dim", ` (+${sorted.length - 3} more \xB7 run /anchor to inspect)`), width));
|
|
803
|
+
if (sorted.length === 1) {
|
|
804
|
+
const a = sorted[0];
|
|
805
|
+
const num = a.id.replace(/^anc-/, "");
|
|
806
|
+
const bar = a.progress ? `${fg("accent", formatProgressBar(a.progress))} ` : "";
|
|
807
|
+
const nextStep = a.progress?.steps && a.progress.steps[a.progress.done] ? fg("muted", ` \u25B8 ${a.progress.steps[a.progress.done]}`) : "";
|
|
808
|
+
const origin = formatOrigin(a);
|
|
809
|
+
const sessionIcon = a.sessionFile ? " \u21AA" : "";
|
|
810
|
+
const prefix = `${fg("accent", "\u2316")} ${fg("dim", `#${num}`)} ${bar}`;
|
|
811
|
+
const suffix = ` ${fg("muted", `(${origin}${sessionIcon})`)}`;
|
|
812
|
+
const availTitle = Math.max(12, width - getDisplayWidth(prefix) - getDisplayWidth(nextStep) - getDisplayWidth(suffix) - 2);
|
|
813
|
+
const titleClipped = truncateToWidth(a.title, availTitle);
|
|
814
|
+
return [truncateToWidth(`${prefix}${titleClipped}${nextStep}${suffix}`, width)];
|
|
1288
815
|
}
|
|
1289
|
-
|
|
816
|
+
const countTag = fg("accent", `\u2316 ${sorted.length} notes: `);
|
|
817
|
+
const segments = sorted.slice(0, 3).map((a) => {
|
|
818
|
+
const num = a.id.replace(/^anc-/, "");
|
|
819
|
+
const bar = a.progress ? `${formatProgressBar(a.progress)} ` : "";
|
|
820
|
+
const shortTitle = truncateToWidth(a.title, Math.max(14, Math.floor((width - 20) / Math.min(3, sorted.length)) - 12));
|
|
821
|
+
return `${fg("dim", `#${num}`)} ${bar}${shortTitle}`;
|
|
822
|
+
});
|
|
823
|
+
const more = sorted.length > 3 ? fg("dim", ` +${sorted.length - 3}`) : "";
|
|
824
|
+
return [truncateToWidth(`${countTag}${segments.join(fg("dim", " \xB7 "))}${more}`, width)];
|
|
1290
825
|
},
|
|
1291
826
|
invalidate: () => {
|
|
1292
827
|
}
|
|
1293
828
|
});
|
|
1294
829
|
const content = ctx.ui?.theme ? factory : factory().render(80);
|
|
1295
|
-
ctx.ui.setWidget("anchor-startup", content, { placement: "
|
|
830
|
+
ctx.ui.setWidget("anchor-startup", content, { placement: "belowEditor" });
|
|
1296
831
|
} catch {
|
|
1297
832
|
}
|
|
1298
833
|
}
|
|
1299
834
|
async function openAnchorDashboard(ctx, store) {
|
|
1300
835
|
if (!hasActiveUI(ctx)) return;
|
|
1301
|
-
const
|
|
1302
|
-
if (
|
|
1303
|
-
ctx.ui.notify("\u2316 No
|
|
836
|
+
const allNotes = store.list({ cwd: ctx.cwd, all: true }).filter((a) => a.status === "active" || a.status === "sleeping");
|
|
837
|
+
if (allNotes.length === 0) {
|
|
838
|
+
ctx.ui.notify("\u2316 No sticky notes. Type `pin: <note>` or `/pin <note>` to stick one.", "info");
|
|
1304
839
|
return;
|
|
1305
840
|
}
|
|
1306
|
-
const
|
|
841
|
+
const sorted = sortAnchorsForDisplay(allNotes, ctx.cwd);
|
|
1307
842
|
const optionMap = /* @__PURE__ */ new Map();
|
|
1308
843
|
const displayOptions = [];
|
|
1309
844
|
const termCols = process.stdout?.columns || 96;
|
|
1310
|
-
const isNarrow = termCols <
|
|
1311
|
-
const titleWidth = isNarrow ? Math.max(
|
|
1312
|
-
for (
|
|
1313
|
-
const a = sortedList[i];
|
|
845
|
+
const isNarrow = termCols < 84;
|
|
846
|
+
const titleWidth = isNarrow ? Math.max(20, termCols - 30) : 42;
|
|
847
|
+
for (const a of sorted) {
|
|
1314
848
|
const num = a.id.replace(/^anc-/, "").padStart(2, "0");
|
|
1315
|
-
const
|
|
1316
|
-
const origin = formatOrigin(a);
|
|
1317
|
-
const
|
|
1318
|
-
const created = formatCreationTime(a.createdAt);
|
|
1319
|
-
const titleWithFiles = a.files && a.files.length > 0 ? `${a.title} [${a.files.slice(0, 1).join(", ")}]` : a.title;
|
|
849
|
+
const bar = a.progress ? `${formatProgressBar(a.progress)} ` : "";
|
|
850
|
+
const origin = formatOrigin(a) + (a.sessionFile ? " \u21AA" : "");
|
|
851
|
+
const age = a.status === "sleeping" ? `${formatRelativeAge(a.createdAt)} \xB7 quiet` : formatRelativeAge(a.createdAt);
|
|
1320
852
|
const colNum = `${num} `;
|
|
1321
|
-
const colTitle = padToWidth(
|
|
1322
|
-
const colOrigin = padToWidth(origin,
|
|
1323
|
-
const
|
|
1324
|
-
const colCreated = created;
|
|
1325
|
-
const label = isNarrow ? `${colNum}${badge}${colTitle} ${colTarget}`.trimEnd() : `${colNum}${badge}${colTitle} ${colOrigin} ${colTarget} ${colCreated}`.trimEnd();
|
|
853
|
+
const colTitle = padToWidth(`${bar}${a.title}`, titleWidth);
|
|
854
|
+
const colOrigin = padToWidth(origin, 22);
|
|
855
|
+
const label = isNarrow ? `${colNum}${colTitle} ${age}`.trimEnd() : `${colNum}${colTitle} ${colOrigin} ${age}`.trimEnd();
|
|
1326
856
|
optionMap.set(label, a);
|
|
1327
857
|
displayOptions.push(label);
|
|
1328
858
|
}
|
|
1329
|
-
const selected = await ctx.ui.select("\u2316
|
|
859
|
+
const selected = await ctx.ui.select("\u2316 Notes (select a note):", displayOptions);
|
|
1330
860
|
if (!selected) return;
|
|
1331
861
|
const anchor = optionMap.get(selected);
|
|
1332
862
|
if (!anchor) return;
|
|
863
|
+
const currentSessionFile = ctx.sessionManager?.getSessionFile?.();
|
|
864
|
+
const canJumpSession = Boolean(
|
|
865
|
+
anchor.sessionFile && import_node_fs2.default.existsSync(anchor.sessionFile) && typeof ctx.switchSession === "function" && (!currentSessionFile || import_node_path2.default.resolve(anchor.sessionFile) !== import_node_path2.default.resolve(currentSessionFile))
|
|
866
|
+
);
|
|
867
|
+
const stepLabel = anchor.progress ? `\u25B6 Advance step (${anchor.progress.done}/${anchor.progress.total} \u2794 ${Math.min(anchor.progress.total, anchor.progress.done + 1)}/${anchor.progress.total})` : "\u25B6 Start progress bar (1/4)";
|
|
1333
868
|
const actions = [
|
|
1334
|
-
"\u2713
|
|
1335
|
-
|
|
1336
|
-
"\
|
|
1337
|
-
"\
|
|
869
|
+
"\u2713 Done (tear off note)",
|
|
870
|
+
stepLabel,
|
|
871
|
+
...canJumpSession ? ["\u21AA Jump to session (resume linked conversation)"] : [],
|
|
872
|
+
...anchor.status === "sleeping" ? ["\u26A1 Wake up (mark active)"] : [],
|
|
873
|
+
"\u2139 Read full note",
|
|
874
|
+
"\u21A9 Back"
|
|
1338
875
|
];
|
|
1339
|
-
const action = await ctx.ui.select(
|
|
1340
|
-
`\u2316 Anchor #${anchor.id}: "${anchor.title}"`,
|
|
1341
|
-
actions
|
|
1342
|
-
);
|
|
876
|
+
const action = await ctx.ui.select(`\u2316 #${anchor.id.replace(/^anc-/, "")}: "${anchor.title}"`, actions);
|
|
1343
877
|
if (!action || action.startsWith("\u21A9")) return;
|
|
1344
878
|
if (action.startsWith("\u2713")) {
|
|
1345
|
-
store.settle(anchor.id, { settledBy: "manual
|
|
1346
|
-
|
|
1347
|
-
ctx
|
|
879
|
+
store.settle(anchor.id, { settledBy: "manual" });
|
|
880
|
+
ctx.ui.notify(`\u2316 Torn off #${anchor.id.replace(/^anc-/, "")}: "${anchor.title}" (/anchor undo to put back)`, "info");
|
|
881
|
+
updateAnchorStatusBar(ctx, store);
|
|
882
|
+
} else if (action.startsWith("\u25B6")) {
|
|
883
|
+
const { anchor: updated, completed } = store.step(anchor.id, 1);
|
|
1348
884
|
updateAnchorStatusBar(ctx, store);
|
|
885
|
+
if (completed) {
|
|
886
|
+
ctx.ui.notify(
|
|
887
|
+
`\u2316 #${updated.id.replace(/^anc-/, "")} reached ${formatProgressBar(updated.progress)}! Run /done ${updated.id.replace(/^anc-/, "")} to tear off.`,
|
|
888
|
+
"info"
|
|
889
|
+
);
|
|
890
|
+
} else {
|
|
891
|
+
ctx.ui.notify(`\u2316 Advanced #${updated.id.replace(/^anc-/, "")} to ${formatProgressBar(updated.progress)}`, "info");
|
|
892
|
+
}
|
|
893
|
+
} else if (action.startsWith("\u21AA") && anchor.sessionFile) {
|
|
894
|
+
try {
|
|
895
|
+
await ctx.switchSession(anchor.sessionFile);
|
|
896
|
+
} catch (err) {
|
|
897
|
+
ctx.ui.notify(`Could not switch session: ${err.message}`, "error");
|
|
898
|
+
}
|
|
1349
899
|
} else if (action.startsWith("\u26A1")) {
|
|
1350
900
|
store.touch(anchor.id);
|
|
1351
|
-
ctx.ui.notify(`\u2316 Woke up #${anchor.id}: "${anchor.title}"`, "info");
|
|
901
|
+
ctx.ui.notify(`\u2316 Woke up #${anchor.id.replace(/^anc-/, "")}: "${anchor.title}"`, "info");
|
|
1352
902
|
updateAnchorStatusBar(ctx, store);
|
|
1353
903
|
} else if (action.startsWith("\u2139")) {
|
|
1354
904
|
const details = [
|
|
1355
|
-
|
|
1356
|
-
anchor.
|
|
1357
|
-
anchor.
|
|
1358
|
-
anchor.files
|
|
1359
|
-
anchor.
|
|
1360
|
-
`
|
|
905
|
+
`\u2316 Note #${anchor.id.replace(/^anc-/, "")}: ${anchor.title}`,
|
|
906
|
+
anchor.progress ? `Progress: ${formatProgressBar(anchor.progress)}${anchor.progress.steps ? ` (${anchor.progress.steps.join(" \u2192 ")})` : ""}` : null,
|
|
907
|
+
anchor.description ? `Details: ${anchor.description}` : null,
|
|
908
|
+
anchor.files.length > 0 ? `Files: ${anchor.files.join(", ")}` : null,
|
|
909
|
+
anchor.sessionFile ? `Session: ${import_node_path2.default.basename(anchor.sessionFile)}` : null,
|
|
910
|
+
`Workspace: ${anchor.project || "global"} \xB7 Pinned ${formatRelativeAge(anchor.createdAt)}`
|
|
1361
911
|
].filter(Boolean).join("\n");
|
|
1362
912
|
ctx.ui.notify(details, "info");
|
|
1363
913
|
}
|
|
1364
914
|
}
|
|
1365
915
|
|
|
1366
|
-
// src/
|
|
1367
|
-
var
|
|
1368
|
-
var
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
*/
|
|
1384
|
-
recordToolCall(toolName, input) {
|
|
1385
|
-
if (!input || typeof input !== "object") return;
|
|
1386
|
-
const lowerTool = (toolName || "").toLowerCase();
|
|
1387
|
-
const isMutation = MUTATION_TOOLS.has(lowerTool);
|
|
1388
|
-
const paths = [];
|
|
1389
|
-
const pathField = input.path || input.filePath || input.file;
|
|
1390
|
-
if (typeof pathField === "string") {
|
|
1391
|
-
paths.push(normalizePath(pathField));
|
|
1392
|
-
}
|
|
1393
|
-
if (Array.isArray(input.paths)) {
|
|
1394
|
-
for (const p of input.paths) {
|
|
1395
|
-
if (typeof p === "string") {
|
|
1396
|
-
paths.push(normalizePath(p));
|
|
1397
|
-
}
|
|
1398
|
-
}
|
|
1399
|
-
}
|
|
1400
|
-
for (const p of paths) {
|
|
1401
|
-
this.touchedFiles.add(p);
|
|
1402
|
-
if (isMutation) {
|
|
1403
|
-
this.modifiedFiles.add(p);
|
|
1404
|
-
} else {
|
|
1405
|
-
this.inspectedFiles.add(p);
|
|
1406
|
-
}
|
|
1407
|
-
}
|
|
1408
|
-
if (lowerTool === "bash" || lowerTool === "powershell") {
|
|
1409
|
-
const cmd = String(input.command || "");
|
|
1410
|
-
if (cmd.includes("git commit")) {
|
|
1411
|
-
this.committed = true;
|
|
1412
|
-
const msgMatch = cmd.match(/-m\s+["']([^"']+)["']/);
|
|
1413
|
-
if (msgMatch && msgMatch[1]) {
|
|
1414
|
-
this.commitMessages.push(msgMatch[1]);
|
|
1415
|
-
}
|
|
1416
|
-
}
|
|
1417
|
-
}
|
|
1418
|
-
}
|
|
1419
|
-
/**
|
|
1420
|
-
* Add a file path manually (e.g. from git status diff)
|
|
1421
|
-
*/
|
|
1422
|
-
addTouchedFile(filePath, isModified = true) {
|
|
1423
|
-
if (filePath) {
|
|
1424
|
-
const norm = normalizePath(filePath);
|
|
1425
|
-
this.touchedFiles.add(norm);
|
|
1426
|
-
if (isModified) {
|
|
1427
|
-
this.modifiedFiles.add(norm);
|
|
1428
|
-
} else {
|
|
1429
|
-
this.inspectedFiles.add(norm);
|
|
1430
|
-
}
|
|
1431
|
-
}
|
|
1432
|
-
}
|
|
1433
|
-
getModifiedFiles() {
|
|
1434
|
-
return Array.from(this.modifiedFiles);
|
|
1435
|
-
}
|
|
1436
|
-
getInspectedFiles() {
|
|
1437
|
-
return Array.from(this.inspectedFiles);
|
|
1438
|
-
}
|
|
1439
|
-
getTouchedFiles() {
|
|
1440
|
-
return Array.from(this.touchedFiles);
|
|
1441
|
-
}
|
|
1442
|
-
hasCommitted() {
|
|
1443
|
-
return this.committed;
|
|
1444
|
-
}
|
|
1445
|
-
getCommitMessages() {
|
|
1446
|
-
return [...this.commitMessages];
|
|
916
|
+
// src/extension.ts
|
|
917
|
+
var import_typebox = require("@sinclair/typebox");
|
|
918
|
+
var import_node_fs3 = __toESM(require("fs"), 1);
|
|
919
|
+
function parseAnchorCommandInput(raw) {
|
|
920
|
+
const trimmed = (raw || "").trim();
|
|
921
|
+
if (!trimmed || trimmed === "list" || trimmed === "ls") {
|
|
922
|
+
return { mode: "dashboard" };
|
|
923
|
+
}
|
|
924
|
+
if (trimmed === "undo") {
|
|
925
|
+
return { mode: "undo" };
|
|
926
|
+
}
|
|
927
|
+
const settleMatch = trimmed.match(/^(?:done|rm|close|settle)\s+(.+)$/i);
|
|
928
|
+
if (settleMatch) {
|
|
929
|
+
return {
|
|
930
|
+
mode: "settle",
|
|
931
|
+
targetId: settleMatch[1].trim().replace(/^#/, "")
|
|
932
|
+
};
|
|
1447
933
|
}
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
if (typeof Intl !== "undefined" && Intl.Segmenter) {
|
|
1456
|
-
try {
|
|
1457
|
-
const segmenter = new Intl.Segmenter("und", { granularity: "word" });
|
|
1458
|
-
titleTokens = Array.from(segmenter.segment(title)).filter((s) => s.isWordLike).map((s) => s.segment.trim().toLowerCase()).filter((t) => t.length >= 2);
|
|
1459
|
-
} catch {
|
|
1460
|
-
titleTokens = title.split(/[\s,._\-\/]+/).filter((t) => t.length >= 2);
|
|
1461
|
-
}
|
|
1462
|
-
} else {
|
|
1463
|
-
titleTokens = title.split(/[\s,._\-\/]+/).filter((t) => t.length >= 2);
|
|
1464
|
-
}
|
|
1465
|
-
for (const msg of this.commitMessages) {
|
|
1466
|
-
const msgLower = msg.toLowerCase();
|
|
1467
|
-
if (msgLower.includes(idLower)) {
|
|
1468
|
-
return { matched: true, message: msg };
|
|
1469
|
-
}
|
|
1470
|
-
if (titleTokens.length > 0) {
|
|
1471
|
-
const matchingCount = titleTokens.filter((tok) => msgLower.includes(tok)).length;
|
|
1472
|
-
if (matchingCount >= Math.min(2, titleTokens.length)) {
|
|
1473
|
-
return { matched: true, message: msg };
|
|
1474
|
-
}
|
|
1475
|
-
}
|
|
1476
|
-
}
|
|
1477
|
-
return { matched: false };
|
|
934
|
+
const stepMatch = trimmed.match(/^(?:step|\+)\s*(\S+)(?:\s+([+-]?\d+))?$/i);
|
|
935
|
+
if (stepMatch) {
|
|
936
|
+
return {
|
|
937
|
+
mode: "step",
|
|
938
|
+
targetId: stepMatch[1].trim().replace(/^#/, ""),
|
|
939
|
+
delta: stepMatch[2] ? parseInt(stepMatch[2], 10) : 1
|
|
940
|
+
};
|
|
1478
941
|
}
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
".ts": (m) => `// ${m}`,
|
|
1486
|
-
".tsx": (m) => `// ${m}`,
|
|
1487
|
-
".js": (m) => `// ${m}`,
|
|
1488
|
-
".jsx": (m) => `// ${m}`,
|
|
1489
|
-
".mjs": (m) => `// ${m}`,
|
|
1490
|
-
".cjs": (m) => `// ${m}`,
|
|
1491
|
-
".java": (m) => `// ${m}`,
|
|
1492
|
-
".kt": (m) => `// ${m}`,
|
|
1493
|
-
".swift": (m) => `// ${m}`,
|
|
1494
|
-
".go": (m) => `// ${m}`,
|
|
1495
|
-
".rs": (m) => `// ${m}`,
|
|
1496
|
-
".c": (m) => `// ${m}`,
|
|
1497
|
-
".cpp": (m) => `// ${m}`,
|
|
1498
|
-
".h": (m) => `// ${m}`,
|
|
1499
|
-
".hpp": (m) => `// ${m}`,
|
|
1500
|
-
".cs": (m) => `// ${m}`,
|
|
1501
|
-
".dart": (m) => `// ${m}`,
|
|
1502
|
-
".scala": (m) => `// ${m}`,
|
|
1503
|
-
".zig": (m) => `// ${m}`,
|
|
1504
|
-
".proto": (m) => `// ${m}`,
|
|
1505
|
-
// Hash #
|
|
1506
|
-
".py": (m) => `# ${m}`,
|
|
1507
|
-
".rb": (m) => `# ${m}`,
|
|
1508
|
-
".sh": (m) => `# ${m}`,
|
|
1509
|
-
".bash": (m) => `# ${m}`,
|
|
1510
|
-
".zsh": (m) => `# ${m}`,
|
|
1511
|
-
".yaml": (m) => `# ${m}`,
|
|
1512
|
-
".yml": (m) => `# ${m}`,
|
|
1513
|
-
".toml": (m) => `# ${m}`,
|
|
1514
|
-
".conf": (m) => `# ${m}`,
|
|
1515
|
-
".dockerfile": (m) => `# ${m}`,
|
|
1516
|
-
".ps1": (m) => `# ${m}`,
|
|
1517
|
-
".r": (m) => `# ${m}`,
|
|
1518
|
-
// HTML / XML <!-- -->
|
|
1519
|
-
".html": (m) => `<!-- ${m} -->`,
|
|
1520
|
-
".xml": (m) => `<!-- ${m} -->`,
|
|
1521
|
-
".svg": (m) => `<!-- ${m} -->`,
|
|
1522
|
-
".vue": (m) => `<!-- ${m} -->`,
|
|
1523
|
-
".svelte": (m) => `<!-- ${m} -->`,
|
|
1524
|
-
// Block comment /* */
|
|
1525
|
-
".css": (m) => `/* ${m} */`,
|
|
1526
|
-
".scss": (m) => `/* ${m} */`,
|
|
1527
|
-
".less": (m) => `/* ${m} */`,
|
|
1528
|
-
// SQL / Lua / Haskell --
|
|
1529
|
-
".sql": (m) => `-- ${m}`,
|
|
1530
|
-
".lua": (m) => `-- ${m}`,
|
|
1531
|
-
".hs": (m) => `-- ${m}`,
|
|
1532
|
-
// Batch REM
|
|
1533
|
-
".bat": (m) => `REM ${m}`,
|
|
1534
|
-
".cmd": (m) => `REM ${m}`
|
|
1535
|
-
};
|
|
1536
|
-
function makeSafeTaskAnnotation(filePath, anchor) {
|
|
1537
|
-
const ext = import_node_path4.default.extname(filePath).toLowerCase();
|
|
1538
|
-
const formatter = COMMENT_FORMATS[ext];
|
|
1539
|
-
if (!formatter) {
|
|
1540
|
-
return null;
|
|
942
|
+
const jumpMatch = trimmed.match(/^(?:jump|resume|session)\s+(.+)$/i);
|
|
943
|
+
if (jumpMatch) {
|
|
944
|
+
return {
|
|
945
|
+
mode: "jump",
|
|
946
|
+
targetId: jumpMatch[1].trim().replace(/^#/, "")
|
|
947
|
+
};
|
|
1541
948
|
}
|
|
1542
|
-
return
|
|
949
|
+
return {
|
|
950
|
+
mode: "pin",
|
|
951
|
+
title: trimmed
|
|
952
|
+
};
|
|
1543
953
|
}
|
|
1544
|
-
function
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
if (typeof cwdOrNow === "number") {
|
|
1548
|
-
now = cwdOrNow;
|
|
1549
|
-
} else if (typeof cwdOrNow === "string") {
|
|
1550
|
-
cwd = cwdOrNow;
|
|
1551
|
-
}
|
|
1552
|
-
if (typeof nowArg === "number") {
|
|
1553
|
-
now = nowArg;
|
|
1554
|
-
}
|
|
1555
|
-
sweepStore(store, now);
|
|
1556
|
-
const activeAnchors = store.list({ status: "active", cwd });
|
|
1557
|
-
if (activeAnchors.length === 0) {
|
|
1558
|
-
return "";
|
|
1559
|
-
}
|
|
1560
|
-
const lines = activeAnchors.map((a) => {
|
|
1561
|
-
const fileHint = a.files.length > 0 ? ` (${a.files.slice(0, 2).join(", ")})` : "";
|
|
1562
|
-
const tagHint = a.tags.length > 0 ? ` [${a.tags.join(", ")}]` : "";
|
|
1563
|
-
const projHint = a.cwd ? `[${a.project}]` : "[global]";
|
|
1564
|
-
let targetHint = "";
|
|
1565
|
-
if (a.recurrence === "daily") {
|
|
1566
|
-
targetHint = " [\u4ECA\u65E5\u5FAA\u73AF\xB7Daily]";
|
|
1567
|
-
} else if (a.targetDate) {
|
|
1568
|
-
const todayStr = getTodayDateString(now);
|
|
1569
|
-
if (a.targetDate === todayStr) {
|
|
1570
|
-
targetHint = " [\u4ECA\u65E5\u805A\u7126\xB7Due Today]";
|
|
1571
|
-
} else if (a.targetDate < todayStr) {
|
|
1572
|
-
targetHint = " [\u5DF2\u903E\u671F\xB7Overdue]";
|
|
1573
|
-
} else {
|
|
1574
|
-
targetHint = ` [\u6392\u671F:${a.targetDate}]`;
|
|
1575
|
-
}
|
|
1576
|
-
} else {
|
|
1577
|
-
targetHint = " [\u957F\u671F\u613F\u666F\xB7Someday]";
|
|
1578
|
-
}
|
|
1579
|
-
const ageDays = Math.floor((now - a.createdAt) / (24 * 60 * 60 * 1e3));
|
|
1580
|
-
const ageHint = ageDays > 0 ? ` (${ageDays}d old)` : "";
|
|
1581
|
-
const failureSnippet = preflightFailures?.get(a.id);
|
|
1582
|
-
const verifyHint = failureSnippet ? ` [\u26A1 \u7269\u7406\u9A8C\u8BC1\u5931\u8D25: ${failureSnippet.replace(/\s+/g, " ").slice(-100)}]` : a.verifyCommand ? ` [verify: ${a.verifyCommand}]` : "";
|
|
1583
|
-
return `\u2022 #${a.id} ${projHint}${targetHint} [${a.priority.toUpperCase()}] ${a.title}${verifyHint}${ageHint}${tagHint}${fileHint}`;
|
|
1584
|
-
});
|
|
1585
|
-
return [
|
|
1586
|
-
`<active-anchors count="${activeAnchors.length}">`,
|
|
1587
|
-
`Unfinished cross-session commitments from previous sessions:`,
|
|
1588
|
-
lines.join("\n"),
|
|
1589
|
-
``,
|
|
1590
|
-
`CONVERSATIONAL PROTOCOL (HUMAN PARTNER TONE):`,
|
|
1591
|
-
`- If the user opens the session with a casual greeting or continuation ("hi", "\u5728\u5417", "\u7EE7\u7EED", "\u65E9\u4E0A\u597D"), naturally prioritize items tagged [\u4ECA\u65E5\u805A\u7126\xB7Due Today] in a friendly assistant tone (e.g. "\u55E8\uFF01\u4ECA\u5929\u89C4\u5212\u7684\u3010...\u3011\u73B0\u5728\u5904\u7406\u8FD8\u662F\u770B\u522B\u7684\uFF1F").`,
|
|
1592
|
-
`- If an anchor has [\u26A1 \u7269\u7406\u9A8C\u8BC1\u5931\u8D25], its verifyCommand failed on session start. Target the failing assertion directly.`,
|
|
1593
|
-
`- If the user issues a direct, concrete new instruction (e.g. "\u5E2E\u6211\u67E5\u4E0B\u8FD9\u4E2A\u62A5\u9519"), DO NOT interrupt their flow. Focus directly on their instruction.`,
|
|
1594
|
-
`- When a commitment is satisfied during work, automatically call anchor(action="settle", id=...) to clear it.`,
|
|
1595
|
-
`</active-anchors>`
|
|
1596
|
-
].join("\n");
|
|
954
|
+
function parseQuickNoteInput(text) {
|
|
955
|
+
const m = (text || "").trim().match(/^(?:pin|note|便签|备忘)\s*[::]\s*(.+)$/i);
|
|
956
|
+
return m && m[1].trim() ? m[1].trim() : null;
|
|
1597
957
|
}
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
this.store = store;
|
|
1607
|
-
this.observer = new SessionTouchObserver();
|
|
1608
|
-
}
|
|
1609
|
-
/**
|
|
1610
|
-
* Run session initialization lifecycle: sweeps stale anchors and resets session state
|
|
1611
|
-
*/
|
|
1612
|
-
handleSessionStart(now = Date.now()) {
|
|
1613
|
-
this.observer.clear();
|
|
1614
|
-
this.annotatedThisSession.clear();
|
|
1615
|
-
const sweep = sweepStore(this.store, now);
|
|
1616
|
-
return { sleepingCount: sweep.transitionedToSleeping.length };
|
|
1617
|
-
}
|
|
1618
|
-
/**
|
|
1619
|
-
* Handle turn lifecycle: injects cold-start prompt on turn 1, returns null (0 tokens) on turn 2+
|
|
1620
|
-
*/
|
|
1621
|
-
handleBeforeTurn(turnCount, cwd, now = Date.now()) {
|
|
1622
|
-
if (turnCount <= 1) {
|
|
1623
|
-
return renderColdStartAnchorsContext(this.store, cwd, now) || null;
|
|
958
|
+
function resolveAnchorTarget(store, idOrIndex, cwd) {
|
|
959
|
+
const clean = idOrIndex.trim().replace(/^#/, "");
|
|
960
|
+
if (/^\d+$/.test(clean)) {
|
|
961
|
+
const seqIndex = parseInt(clean, 10);
|
|
962
|
+
const visible = store.list({ cwd, all: true }).filter((a) => a.status === "active" || a.status === "sleeping");
|
|
963
|
+
const sorted = sortAnchorsForDisplay(visible, cwd);
|
|
964
|
+
if (seqIndex >= 1 && seqIndex <= sorted.length) {
|
|
965
|
+
return sorted[seqIndex - 1].id;
|
|
1624
966
|
}
|
|
1625
|
-
return null;
|
|
1626
967
|
}
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
* and returns safe language-specific JIT annotation (or null if syntax incompatible/already annotated).
|
|
1630
|
-
*/
|
|
1631
|
-
handleToolResult(input) {
|
|
1632
|
-
if (!input.isError) {
|
|
1633
|
-
this.observer.recordToolCall(input.toolName, input.filePath ? { path: input.filePath } : {});
|
|
1634
|
-
}
|
|
1635
|
-
if (!input.filePath) {
|
|
1636
|
-
return { annotation: null, matchedAnchors: [] };
|
|
1637
|
-
}
|
|
1638
|
-
const touchedPath = normalizePath(input.filePath);
|
|
1639
|
-
const anchors = this.store.list({ cwd: input.cwd }).filter((a2) => a2.status === "active" || a2.status === "sleeping");
|
|
1640
|
-
const matches = findMatchedAnchors(anchors, [touchedPath]);
|
|
1641
|
-
if (matches.length === 0) {
|
|
1642
|
-
return { annotation: null, matchedAnchors: [] };
|
|
1643
|
-
}
|
|
1644
|
-
if (input.isMutation && !input.isError) {
|
|
1645
|
-
for (const m of matches) {
|
|
1646
|
-
this.store.touch(m.anchor.id);
|
|
1647
|
-
}
|
|
1648
|
-
}
|
|
1649
|
-
const a = matches[0].anchor;
|
|
1650
|
-
if (!this.annotatedThisSession.has(a.id)) {
|
|
1651
|
-
this.annotatedThisSession.add(a.id);
|
|
1652
|
-
const annotation = makeSafeTaskAnnotation(touchedPath, a);
|
|
1653
|
-
return { annotation, matchedAnchors: matches.map((m) => m.anchor) };
|
|
1654
|
-
}
|
|
1655
|
-
return { annotation: null, matchedAnchors: matches.map((m) => m.anchor) };
|
|
1656
|
-
}
|
|
1657
|
-
};
|
|
968
|
+
return store.get(clean)?.id;
|
|
969
|
+
}
|
|
1658
970
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1659
971
|
0 && (module.exports = {
|
|
1660
|
-
AnchorProtocol,
|
|
1661
972
|
AnchorStore,
|
|
1662
973
|
COMMENT_FORMATS,
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
EPHEMERAL_DECAY_POLICY,
|
|
1666
|
-
SessionTouchObserver,
|
|
974
|
+
MS_PER_DAY,
|
|
975
|
+
QUIET_AFTER_DAYS,
|
|
1667
976
|
acquireSyncLock,
|
|
1668
977
|
atomicRenameWithRetry,
|
|
1669
|
-
classifyAnchor,
|
|
1670
|
-
detectDurability,
|
|
1671
|
-
detectRecurrence,
|
|
1672
|
-
detectTargetDate,
|
|
1673
|
-
evaluateAnchorDecay,
|
|
1674
978
|
extractPathsFromText,
|
|
979
|
+
extractProgressFromText,
|
|
1675
980
|
findMatchedAnchors,
|
|
1676
|
-
formatCreationTime,
|
|
1677
981
|
formatOrigin,
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
formatSettlementCard,
|
|
1681
|
-
formatTargetDate,
|
|
1682
|
-
generateSettlementProposals,
|
|
982
|
+
formatProgressBar,
|
|
983
|
+
formatRelativeAge,
|
|
1683
984
|
getDefaultStorageDir,
|
|
1684
985
|
getDisplayWidth,
|
|
1685
|
-
getEphemeralDecayPolicy,
|
|
1686
|
-
getTodayDateString,
|
|
1687
986
|
globToRegExp,
|
|
1688
|
-
groupAnchorsByQuadrant,
|
|
1689
987
|
hasActiveUI,
|
|
1690
988
|
isAnchorTargetingWorkspace,
|
|
1691
989
|
isSameOrSubPath,
|
|
1692
990
|
makeSafeTaskAnnotation,
|
|
1693
991
|
matchAnchorAgainstTouchedFiles,
|
|
992
|
+
matchesCommitMessage,
|
|
1694
993
|
normalizePath,
|
|
1695
994
|
openAnchorDashboard,
|
|
1696
995
|
padToWidth,
|
|
996
|
+
parseAnchorCommandInput,
|
|
997
|
+
parseQuickNoteInput,
|
|
1697
998
|
renderActiveAnchorsContext,
|
|
1698
999
|
renderColdStartAnchorsContext,
|
|
1699
|
-
|
|
1000
|
+
resolveAnchorTarget,
|
|
1700
1001
|
sortAnchorsForDisplay,
|
|
1701
1002
|
stripAnsi,
|
|
1702
|
-
|
|
1003
|
+
sweepQuietNotes,
|
|
1703
1004
|
truncateToWidth,
|
|
1704
|
-
tryRealtimeAutoSettle,
|
|
1705
1005
|
updateAnchorStatusBar,
|
|
1706
1006
|
updateStartupBanner
|
|
1707
1007
|
});
|