pi-anchor 0.2.1 → 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/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-B652T7QR.js";
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
- if (pending.cwd === ctx.cwd && Date.now() - pending.timestamp < 24 * 60 * 60 * 1e3) {
60
- ctx.ui.notify(`\u2316 [Anchor \u9000\u51FA\u5B88\u62A4] \u68C0\u6D4B\u5230\u4E0A\u6B21\u5F02\u5E38\u9000\u51FA\u4E14\u6709 ${pending.modifiedCount} \u4E2A\u672A\u63D0\u4EA4\u6539\u52A8\uFF0C\u53EF\u7528 anchor(action="pin") \u56FA\u5316\u4EFB\u52A1\u3002`, "info");
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.hasUI && ctx.ui) {
73
- ctx.ui.setWidget("anchor-startup", void 0);
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 (_event, ctx) => {
138
+ pi.on("session_shutdown", async (event, ctx) => {
139
+ let gitStatus = "";
126
140
  try {
127
- const gitStatus = execSync("git status --porcelain", {
141
+ gitStatus = execSync("git status --porcelain", {
128
142
  cwd: ctx.cwd,
129
143
  encoding: "utf-8",
130
- timeout: 3e3,
144
+ timeout: 2e3,
131
145
  stdio: ["ignore", "pipe", "ignore"]
132
- });
133
- const changed = gitStatus.split("\n").map((l) => l.slice(3).trim()).filter(Boolean);
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
- ctx.ui.notify(`\u2316 Auto-settled #${a.id} via Git commit (run /anchor undo to revert)`, "info");
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
- ctx.ui.notify(`\u2316 Test passed (${a.verifyCommand}), auto-settled #${a.id}`, "info");
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.hasUI || !ctx.ui) continue;
168
- const ok = await ctx.ui.confirm(
169
- "\u2316 Settle Anchor Task",
170
- `Task #${a.id} [${a.title}] touched files (${prop.matchedFiles.slice(0, 2).join(", ")}).
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
- if (ok) {
174
- store.settle(a.id, {
175
- settledBy: "one-tap-settlement",
176
- touchedFiles: prop.matchedFiles
177
- });
178
- ctx.ui.notify(`\u2316 Settled #${a.id}: "${a.title}"`, "info");
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.on("session_shutdown", async (event, ctx) => {
381
- if (event.reason === "quit") {
382
- try {
383
- const gitStatus = execSync("git status --porcelain", {
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
  }