pi-anchor 0.2.1 → 0.2.3
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 -195
- package/README_zh.md +115 -205
- package/dist/{chunk-B652T7QR.js → chunk-G72UHPBN.js} +218 -121
- package/dist/extension.cjs +286 -168
- package/dist/extension.js +73 -48
- package/dist/index.cjs +220 -121
- package/dist/index.d.cts +7 -1
- package/dist/index.d.ts +7 -1
- package/dist/index.js +5 -1
- package/package.json +3 -3
- package/src/decay.ts +54 -52
- package/src/extension.ts +74 -55
- package/src/matcher.ts +27 -6
- package/src/settlement.ts +5 -3
- package/src/store.ts +75 -18
- package/src/tui.ts +82 -59
- package/src/types.ts +1 -0
package/dist/extension.js
CHANGED
|
@@ -3,6 +3,8 @@ import {
|
|
|
3
3
|
SessionTouchObserver,
|
|
4
4
|
findMatchedAnchors,
|
|
5
5
|
generateSettlementProposals,
|
|
6
|
+
hasActiveUI,
|
|
7
|
+
isSameOrSubPath,
|
|
6
8
|
makeSafeTaskAnnotation,
|
|
7
9
|
normalizePath,
|
|
8
10
|
openAnchorDashboard,
|
|
@@ -12,7 +14,7 @@ import {
|
|
|
12
14
|
truncateToWidth,
|
|
13
15
|
updateAnchorStatusBar,
|
|
14
16
|
updateStartupBanner
|
|
15
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-G72UHPBN.js";
|
|
16
18
|
|
|
17
19
|
// src/extension.ts
|
|
18
20
|
import { Type } from "@sinclair/typebox";
|
|
@@ -35,7 +37,7 @@ function extension_default(pi) {
|
|
|
35
37
|
}
|
|
36
38
|
const activeAnchors = store.list({ cwd: ctx.cwd }).filter((a) => a.status === "active");
|
|
37
39
|
for (const a of activeAnchors) {
|
|
38
|
-
if (a.verifyCommand) {
|
|
40
|
+
if (a.verifyCommand && (!a.cwd || isSameOrSubPath(a.cwd, ctx.cwd))) {
|
|
39
41
|
try {
|
|
40
42
|
const res = runPhysicalVerification(a, ctx.cwd);
|
|
41
43
|
if (res.success) {
|
|
@@ -56,21 +58,32 @@ function extension_default(pi) {
|
|
|
56
58
|
const pendingPath = path.join(home, ".anchor", "pending_exit.json");
|
|
57
59
|
if (fs.existsSync(pendingPath)) {
|
|
58
60
|
const pending = JSON.parse(fs.readFileSync(pendingPath, "utf-8"));
|
|
59
|
-
|
|
60
|
-
|
|
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);
|
|
61
67
|
}
|
|
62
|
-
fs.unlinkSync(pendingPath);
|
|
63
68
|
}
|
|
64
69
|
} catch (_) {
|
|
65
70
|
}
|
|
66
71
|
updateAnchorStatusBar(ctx, store);
|
|
67
72
|
if (event.reason !== "resume") {
|
|
68
73
|
updateStartupBanner(ctx, store);
|
|
74
|
+
} else if (hasActiveUI(ctx)) {
|
|
75
|
+
try {
|
|
76
|
+
ctx.ui.setWidget("anchor-startup", void 0);
|
|
77
|
+
} catch {
|
|
78
|
+
}
|
|
69
79
|
}
|
|
70
80
|
});
|
|
71
81
|
pi.on("agent_start", async (_event, ctx) => {
|
|
72
|
-
if (ctx
|
|
73
|
-
|
|
82
|
+
if (hasActiveUI(ctx)) {
|
|
83
|
+
try {
|
|
84
|
+
ctx.ui.setWidget("anchor-startup", void 0);
|
|
85
|
+
} catch {
|
|
86
|
+
}
|
|
74
87
|
}
|
|
75
88
|
});
|
|
76
89
|
pi.on("before_agent_start", async (event, ctx) => {
|
|
@@ -94,7 +107,7 @@ ${contextSnippet}`
|
|
|
94
107
|
}
|
|
95
108
|
const pathInput = event.input?.path;
|
|
96
109
|
if (typeof pathInput !== "string") return;
|
|
97
|
-
const touchedPath = normalizePath(pathInput);
|
|
110
|
+
const touchedPath = normalizePath(pathInput, ctx.cwd);
|
|
98
111
|
const anchors = store.list({ cwd: ctx.cwd }).filter((a) => a.status === "active" || a.status === "sleeping");
|
|
99
112
|
const matches = findMatchedAnchors(anchors, [touchedPath]);
|
|
100
113
|
if (matches.length > 0) {
|
|
@@ -122,20 +135,37 @@ ${contextSnippet}`
|
|
|
122
135
|
}
|
|
123
136
|
}
|
|
124
137
|
});
|
|
125
|
-
pi.on("session_shutdown", async (
|
|
138
|
+
pi.on("session_shutdown", async (event, ctx) => {
|
|
139
|
+
let gitStatus = "";
|
|
126
140
|
try {
|
|
127
|
-
|
|
141
|
+
gitStatus = execSync("git status --porcelain", {
|
|
128
142
|
cwd: ctx.cwd,
|
|
129
143
|
encoding: "utf-8",
|
|
130
|
-
timeout:
|
|
144
|
+
timeout: 2e3,
|
|
131
145
|
stdio: ["ignore", "pipe", "ignore"]
|
|
132
|
-
});
|
|
133
|
-
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);
|
|
134
148
|
for (const f of changed) {
|
|
135
149
|
observer.addTouchedFile(f);
|
|
136
150
|
}
|
|
137
151
|
} catch {
|
|
138
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
|
+
}
|
|
139
169
|
const modified = observer.getModifiedFiles();
|
|
140
170
|
if (modified.length === 0) return;
|
|
141
171
|
const proposals = generateSettlementProposals(store, modified, ctx.cwd);
|
|
@@ -149,7 +179,12 @@ ${contextSnippet}`
|
|
|
149
179
|
summary: `Auto-settled via Git commit: ${commitMatch.message}`,
|
|
150
180
|
touchedFiles: prop.matchedFiles
|
|
151
181
|
});
|
|
152
|
-
|
|
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
|
+
}
|
|
153
188
|
continue;
|
|
154
189
|
}
|
|
155
190
|
if (a.verifyCommand) {
|
|
@@ -160,22 +195,30 @@ ${contextSnippet}`
|
|
|
160
195
|
summary: `Automated test passed: ${a.verifyCommand}`,
|
|
161
196
|
touchedFiles: prop.matchedFiles
|
|
162
197
|
});
|
|
163
|
-
|
|
198
|
+
if (hasActiveUI(ctx)) {
|
|
199
|
+
try {
|
|
200
|
+
ctx.ui.notify(`\u2316 Test passed (${a.verifyCommand}), auto-settled #${a.id}`, "info");
|
|
201
|
+
} catch {
|
|
202
|
+
}
|
|
203
|
+
}
|
|
164
204
|
continue;
|
|
165
205
|
}
|
|
166
206
|
}
|
|
167
|
-
if (!ctx
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
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(", ")}).
|
|
171
212
|
Mark as completed and archive?`
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
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 {
|
|
179
222
|
}
|
|
180
223
|
}
|
|
181
224
|
});
|
|
@@ -377,28 +420,10 @@ Mark as completed and archive?`
|
|
|
377
420
|
updateAnchorStatusBar(ctx, store);
|
|
378
421
|
}
|
|
379
422
|
});
|
|
380
|
-
pi.
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
cwd: ctx.cwd,
|
|
385
|
-
encoding: "utf-8",
|
|
386
|
-
timeout: 2e3,
|
|
387
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
388
|
-
}).trim();
|
|
389
|
-
const activeAnchors = store.list({ cwd: ctx.cwd }).filter((a) => a.status === "active");
|
|
390
|
-
if (gitStatus && activeAnchors.length === 0) {
|
|
391
|
-
const home = process.env.HOME || process.env.USERPROFILE || "";
|
|
392
|
-
const pendingPath = path.join(home, ".anchor", "pending_exit.json");
|
|
393
|
-
const data = {
|
|
394
|
-
cwd: ctx.cwd,
|
|
395
|
-
timestamp: Date.now(),
|
|
396
|
-
modifiedCount: gitStatus.split("\n").filter(Boolean).length
|
|
397
|
-
};
|
|
398
|
-
fs.writeFileSync(pendingPath, JSON.stringify(data, null, 2), "utf-8");
|
|
399
|
-
}
|
|
400
|
-
} catch (_) {
|
|
401
|
-
}
|
|
423
|
+
pi.registerCommand("anchors", {
|
|
424
|
+
description: "Inspect and settle active anchors",
|
|
425
|
+
handler: async (_args, ctx) => {
|
|
426
|
+
await openAnchorDashboard(ctx, store);
|
|
402
427
|
}
|
|
403
428
|
});
|
|
404
429
|
}
|