pi-anchor 0.2.0 → 0.2.2
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 +101 -177
- package/README_zh.md +119 -187
- package/dist/{chunk-T62PHIQI.js → chunk-G72UHPBN.js} +261 -128
- package/dist/extension.cjs +332 -178
- package/dist/extension.js +77 -51
- package/dist/index.cjs +263 -128
- package/dist/index.d.cts +7 -1
- package/dist/index.d.ts +7 -1
- package/dist/index.js +5 -1
- package/package.json +5 -4
- package/src/decay.ts +54 -52
- package/src/extension.ts +77 -58
- package/src/matcher.ts +27 -6
- package/src/settlement.ts +5 -3
- package/src/store.ts +75 -18
- package/src/tui.ts +133 -68
- package/src/types.ts +1 -0
package/dist/extension.js
CHANGED
|
@@ -3,15 +3,18 @@ import {
|
|
|
3
3
|
SessionTouchObserver,
|
|
4
4
|
findMatchedAnchors,
|
|
5
5
|
generateSettlementProposals,
|
|
6
|
+
hasActiveUI,
|
|
7
|
+
isSameOrSubPath,
|
|
6
8
|
makeSafeTaskAnnotation,
|
|
7
9
|
normalizePath,
|
|
8
10
|
openAnchorDashboard,
|
|
9
11
|
renderColdStartAnchorsContext,
|
|
10
12
|
runPhysicalVerification,
|
|
11
13
|
sweepStore,
|
|
14
|
+
truncateToWidth,
|
|
12
15
|
updateAnchorStatusBar,
|
|
13
16
|
updateStartupBanner
|
|
14
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-G72UHPBN.js";
|
|
15
18
|
|
|
16
19
|
// src/extension.ts
|
|
17
20
|
import { Type } from "@sinclair/typebox";
|
|
@@ -34,7 +37,7 @@ function extension_default(pi) {
|
|
|
34
37
|
}
|
|
35
38
|
const activeAnchors = store.list({ cwd: ctx.cwd }).filter((a) => a.status === "active");
|
|
36
39
|
for (const a of activeAnchors) {
|
|
37
|
-
if (a.verifyCommand) {
|
|
40
|
+
if (a.verifyCommand && (!a.cwd || isSameOrSubPath(a.cwd, ctx.cwd))) {
|
|
38
41
|
try {
|
|
39
42
|
const res = runPhysicalVerification(a, ctx.cwd);
|
|
40
43
|
if (res.success) {
|
|
@@ -55,21 +58,32 @@ function extension_default(pi) {
|
|
|
55
58
|
const pendingPath = path.join(home, ".anchor", "pending_exit.json");
|
|
56
59
|
if (fs.existsSync(pendingPath)) {
|
|
57
60
|
const pending = JSON.parse(fs.readFileSync(pendingPath, "utf-8"));
|
|
58
|
-
|
|
59
|
-
|
|
61
|
+
const isExpired = Date.now() - pending.timestamp >= 24 * 60 * 60 * 1e3;
|
|
62
|
+
if (!isExpired && isSameOrSubPath(pending.cwd, ctx.cwd)) {
|
|
63
|
+
ctx.ui.notify(`\u2316 [Exit Guard] Detected ${pending.modifiedCount} uncommitted changes from previous session exit. Run /pin to record.`, "info");
|
|
64
|
+
fs.unlinkSync(pendingPath);
|
|
65
|
+
} else if (isExpired) {
|
|
66
|
+
fs.unlinkSync(pendingPath);
|
|
60
67
|
}
|
|
61
|
-
fs.unlinkSync(pendingPath);
|
|
62
68
|
}
|
|
63
69
|
} catch (_) {
|
|
64
70
|
}
|
|
65
71
|
updateAnchorStatusBar(ctx, store);
|
|
66
72
|
if (event.reason !== "resume") {
|
|
67
73
|
updateStartupBanner(ctx, store);
|
|
74
|
+
} else if (hasActiveUI(ctx)) {
|
|
75
|
+
try {
|
|
76
|
+
ctx.ui.setWidget("anchor-startup", void 0);
|
|
77
|
+
} catch {
|
|
78
|
+
}
|
|
68
79
|
}
|
|
69
80
|
});
|
|
70
81
|
pi.on("agent_start", async (_event, ctx) => {
|
|
71
|
-
if (ctx
|
|
72
|
-
|
|
82
|
+
if (hasActiveUI(ctx)) {
|
|
83
|
+
try {
|
|
84
|
+
ctx.ui.setWidget("anchor-startup", void 0);
|
|
85
|
+
} catch {
|
|
86
|
+
}
|
|
73
87
|
}
|
|
74
88
|
});
|
|
75
89
|
pi.on("before_agent_start", async (event, ctx) => {
|
|
@@ -93,7 +107,7 @@ ${contextSnippet}`
|
|
|
93
107
|
}
|
|
94
108
|
const pathInput = event.input?.path;
|
|
95
109
|
if (typeof pathInput !== "string") return;
|
|
96
|
-
const touchedPath = normalizePath(pathInput);
|
|
110
|
+
const touchedPath = normalizePath(pathInput, ctx.cwd);
|
|
97
111
|
const anchors = store.list({ cwd: ctx.cwd }).filter((a) => a.status === "active" || a.status === "sleeping");
|
|
98
112
|
const matches = findMatchedAnchors(anchors, [touchedPath]);
|
|
99
113
|
if (matches.length > 0) {
|
|
@@ -121,20 +135,37 @@ ${contextSnippet}`
|
|
|
121
135
|
}
|
|
122
136
|
}
|
|
123
137
|
});
|
|
124
|
-
pi.on("session_shutdown", async (
|
|
138
|
+
pi.on("session_shutdown", async (event, ctx) => {
|
|
139
|
+
let gitStatus = "";
|
|
125
140
|
try {
|
|
126
|
-
|
|
141
|
+
gitStatus = execSync("git status --porcelain", {
|
|
127
142
|
cwd: ctx.cwd,
|
|
128
143
|
encoding: "utf-8",
|
|
129
|
-
timeout:
|
|
144
|
+
timeout: 2e3,
|
|
130
145
|
stdio: ["ignore", "pipe", "ignore"]
|
|
131
|
-
});
|
|
132
|
-
const changed = gitStatus.split(
|
|
146
|
+
}).replace(/[\r\n]+$/, "");
|
|
147
|
+
const changed = gitStatus.split(/\r?\n/).filter(Boolean).map((l) => l.slice(3).replace(/^"(.*)"$/, "$1").trim()).filter(Boolean);
|
|
133
148
|
for (const f of changed) {
|
|
134
149
|
observer.addTouchedFile(f);
|
|
135
150
|
}
|
|
136
151
|
} catch {
|
|
137
152
|
}
|
|
153
|
+
if (event?.reason === "quit" && gitStatus) {
|
|
154
|
+
try {
|
|
155
|
+
const activeAnchors = store.list({ cwd: ctx.cwd }).filter((a) => a.status === "active");
|
|
156
|
+
if (activeAnchors.length === 0) {
|
|
157
|
+
const home = process.env.HOME || process.env.USERPROFILE || "";
|
|
158
|
+
const pendingPath = path.join(home, ".anchor", "pending_exit.json");
|
|
159
|
+
const data = {
|
|
160
|
+
cwd: ctx.cwd,
|
|
161
|
+
timestamp: Date.now(),
|
|
162
|
+
modifiedCount: gitStatus.split("\n").filter(Boolean).length
|
|
163
|
+
};
|
|
164
|
+
fs.writeFileSync(pendingPath, JSON.stringify(data, null, 2), "utf-8");
|
|
165
|
+
}
|
|
166
|
+
} catch {
|
|
167
|
+
}
|
|
168
|
+
}
|
|
138
169
|
const modified = observer.getModifiedFiles();
|
|
139
170
|
if (modified.length === 0) return;
|
|
140
171
|
const proposals = generateSettlementProposals(store, modified, ctx.cwd);
|
|
@@ -148,7 +179,12 @@ ${contextSnippet}`
|
|
|
148
179
|
summary: `Auto-settled via Git commit: ${commitMatch.message}`,
|
|
149
180
|
touchedFiles: prop.matchedFiles
|
|
150
181
|
});
|
|
151
|
-
|
|
182
|
+
if (hasActiveUI(ctx)) {
|
|
183
|
+
try {
|
|
184
|
+
ctx.ui.notify(`\u2316 Auto-settled #${a.id} via Git commit (run /anchor undo to revert)`, "info");
|
|
185
|
+
} catch {
|
|
186
|
+
}
|
|
187
|
+
}
|
|
152
188
|
continue;
|
|
153
189
|
}
|
|
154
190
|
if (a.verifyCommand) {
|
|
@@ -159,22 +195,30 @@ ${contextSnippet}`
|
|
|
159
195
|
summary: `Automated test passed: ${a.verifyCommand}`,
|
|
160
196
|
touchedFiles: prop.matchedFiles
|
|
161
197
|
});
|
|
162
|
-
|
|
198
|
+
if (hasActiveUI(ctx)) {
|
|
199
|
+
try {
|
|
200
|
+
ctx.ui.notify(`\u2316 Test passed (${a.verifyCommand}), auto-settled #${a.id}`, "info");
|
|
201
|
+
} catch {
|
|
202
|
+
}
|
|
203
|
+
}
|
|
163
204
|
continue;
|
|
164
205
|
}
|
|
165
206
|
}
|
|
166
|
-
if (!ctx
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
207
|
+
if (!hasActiveUI(ctx)) continue;
|
|
208
|
+
try {
|
|
209
|
+
const ok = await ctx.ui.confirm(
|
|
210
|
+
"\u2316 Settle Anchor Task",
|
|
211
|
+
`Task #${a.id} [${a.title}] touched files (${prop.matchedFiles.slice(0, 2).join(", ")}).
|
|
170
212
|
Mark as completed and archive?`
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
213
|
+
);
|
|
214
|
+
if (ok) {
|
|
215
|
+
store.settle(a.id, {
|
|
216
|
+
settledBy: "one-tap-settlement",
|
|
217
|
+
touchedFiles: prop.matchedFiles
|
|
218
|
+
});
|
|
219
|
+
ctx.ui.notify(`\u2316 Settled #${a.id}: "${a.title}"`, "info");
|
|
220
|
+
}
|
|
221
|
+
} catch {
|
|
178
222
|
}
|
|
179
223
|
}
|
|
180
224
|
});
|
|
@@ -224,7 +268,7 @@ Mark as completed and archive?`
|
|
|
224
268
|
if (args.priority) title += ` ${theme.fg("muted", `[${args.priority}]`)}`;
|
|
225
269
|
}
|
|
226
270
|
return {
|
|
227
|
-
render: () => [title],
|
|
271
|
+
render: (width) => [truncateToWidth(title, width)],
|
|
228
272
|
invalidate: () => {
|
|
229
273
|
}
|
|
230
274
|
};
|
|
@@ -233,14 +277,14 @@ Mark as completed and archive?`
|
|
|
233
277
|
if (result.isError) {
|
|
234
278
|
const errText = result.content?.[0]?.text || "Execution failed";
|
|
235
279
|
return {
|
|
236
|
-
render: () => [theme.fg("error", `\u2717 ${errText}`)],
|
|
280
|
+
render: (width) => [truncateToWidth(theme.fg("error", `\u2717 ${errText}`), width)],
|
|
237
281
|
invalidate: () => {
|
|
238
282
|
}
|
|
239
283
|
};
|
|
240
284
|
}
|
|
241
285
|
const text = result.content?.[0]?.text || "OK";
|
|
242
286
|
return {
|
|
243
|
-
render: () => [theme.fg("success", `\u2714 ${text}`)],
|
|
287
|
+
render: (width) => [truncateToWidth(theme.fg("success", `\u2714 ${text}`), width)],
|
|
244
288
|
invalidate: () => {
|
|
245
289
|
}
|
|
246
290
|
};
|
|
@@ -376,28 +420,10 @@ Mark as completed and archive?`
|
|
|
376
420
|
updateAnchorStatusBar(ctx, store);
|
|
377
421
|
}
|
|
378
422
|
});
|
|
379
|
-
pi.
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
cwd: ctx.cwd,
|
|
384
|
-
encoding: "utf-8",
|
|
385
|
-
timeout: 2e3,
|
|
386
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
387
|
-
}).trim();
|
|
388
|
-
const activeAnchors = store.list({ cwd: ctx.cwd }).filter((a) => a.status === "active");
|
|
389
|
-
if (gitStatus && activeAnchors.length === 0) {
|
|
390
|
-
const home = process.env.HOME || process.env.USERPROFILE || "";
|
|
391
|
-
const pendingPath = path.join(home, ".anchor", "pending_exit.json");
|
|
392
|
-
const data = {
|
|
393
|
-
cwd: ctx.cwd,
|
|
394
|
-
timestamp: Date.now(),
|
|
395
|
-
modifiedCount: gitStatus.split("\n").filter(Boolean).length
|
|
396
|
-
};
|
|
397
|
-
fs.writeFileSync(pendingPath, JSON.stringify(data, null, 2), "utf-8");
|
|
398
|
-
}
|
|
399
|
-
} catch (_) {
|
|
400
|
-
}
|
|
423
|
+
pi.registerCommand("anchors", {
|
|
424
|
+
description: "Inspect and settle active anchors",
|
|
425
|
+
handler: async (_args, ctx) => {
|
|
426
|
+
await openAnchorDashboard(ctx, store);
|
|
401
427
|
}
|
|
402
428
|
});
|
|
403
429
|
}
|