pi-anchor 0.1.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.
@@ -0,0 +1,448 @@
1
+ import { ExtensionContext } from '@earendil-works/pi-coding-agent';
2
+
3
+ /**
4
+ * Anchor Data Contracts & Type Definitions
5
+ * Single Global Source of Truth with Zero Workspace Contamination.
6
+ */
7
+ type AnchorStatus = 'active' | 'sleeping' | 'settled' | 'graveyard';
8
+ type AnchorPriority = 'p0' | 'p1' | 'p2';
9
+ type AnchorDurability = 'ephemeral' | 'durable';
10
+ interface AnchorDecayPolicy {
11
+ /** Days before an untouched active anchor transitions to sleeping */
12
+ activeDays: number;
13
+ /** Days an anchor remains sleeping before dropping to graveyard */
14
+ sleepDays: number;
15
+ /** Total maximum days before permanent graveyard sweep (9999 = never) */
16
+ graveyardDays: number;
17
+ }
18
+ interface AnchorEvidence {
19
+ settledAt?: number;
20
+ settledBy?: 'one-tap-settlement' | 'manual-command' | 'verification-test';
21
+ commitHash?: string;
22
+ summary?: string;
23
+ touchedFiles?: string[];
24
+ }
25
+ interface Anchor {
26
+ /** Unique sequential identifier, e.g., 'anc-1' */
27
+ id: string;
28
+ /** Short imperative title of the task */
29
+ title: string;
30
+ /** Optional detailed context or notes */
31
+ description?: string;
32
+ /** Task priority */
33
+ priority: AnchorPriority;
34
+ /** Current lifecycle status */
35
+ status: AnchorStatus;
36
+ /** Durability tier: ephemeral (short-lived reminder, 48h TTL) or durable (long-term architecture vision) */
37
+ durability: AnchorDurability;
38
+ /** Canonical project name (e.g. 'PPT', 'X', or 'global') */
39
+ project: string;
40
+ /** Canonical root directory where the task was anchored (empty string for global tasks) */
41
+ cwd: string;
42
+ /** Creation timestamp in ms */
43
+ createdAt: number;
44
+ /** Last update timestamp in ms */
45
+ updatedAt: number;
46
+ /** Last touched timestamp in ms (resets on file touch) */
47
+ lastTouchedAt: number;
48
+ /** Associated file paths or globs, e.g. ['src/auth/*.ts'] */
49
+ files: string[];
50
+ /** Expected completion date in format 'YYYY-MM-DD' (e.g. '2026-09-24') */
51
+ targetDate?: string;
52
+ /** Optional shell command for automated physical verification (e.g. 'npm test') */
53
+ verifyCommand?: string;
54
+ /** Topic or domain tags, e.g. ['auth', 'security'] */
55
+ tags: string[];
56
+ /** Optional recurrence pattern: 'daily' for recurring daily habits */
57
+ recurrence?: 'daily';
58
+ /** Last completed date string 'YYYY-MM-DD' for recurring tasks */
59
+ lastCompletedDate?: string;
60
+ /** Decay policy overrides */
61
+ decay: AnchorDecayPolicy;
62
+ /** Settlement evidence metadata upon closure */
63
+ evidence?: AnchorEvidence;
64
+ }
65
+ interface AnchorStoreState {
66
+ version: number;
67
+ anchors: Anchor[];
68
+ lastSweepAt: number;
69
+ }
70
+ interface TouchMatchResult {
71
+ anchor: Anchor;
72
+ score: number;
73
+ matchedFiles: string[];
74
+ reason: 'exact-file' | 'dir-prefix' | 'tag-keyword';
75
+ }
76
+ interface SettlementProposal {
77
+ anchor: Anchor;
78
+ matchedFiles: string[];
79
+ reason: string;
80
+ recommendedAction: 'settle' | 'defer';
81
+ }
82
+ declare const EPHEMERAL_DECAY_POLICY: AnchorDecayPolicy;
83
+ declare const DURABLE_DECAY_POLICY: AnchorDecayPolicy;
84
+ declare const DEFAULT_DECAY_POLICY: AnchorDecayPolicy;
85
+
86
+ /**
87
+ * Detect whether a task is a short-term ephemeral reminder or a long-term architectural vision
88
+ */
89
+ declare function detectDurability(title: string): AnchorDurability;
90
+ /**
91
+ * Get custom ephemeral decay policy based on temporal span (e.g. 后天, 大后天)
92
+ */
93
+ declare function getEphemeralDecayPolicy(title: string): AnchorDecayPolicy;
94
+ /**
95
+ * Detect expected completion date from title keywords or explicit date string, returning 'YYYY-MM-DD'
96
+ */
97
+ declare function detectTargetDate(titleOrDate: string, now?: number): string | undefined;
98
+ /**
99
+ * Detect recurring patterns, e.g. daily habits
100
+ */
101
+ declare function detectRecurrence(title: string): 'daily' | undefined;
102
+ /**
103
+ * Get current date string 'YYYY-MM-DD' in local timezone
104
+ */
105
+ declare function getTodayDateString(timestamp?: number): string;
106
+ /**
107
+ * Resolve the default storage directory for Anchor (~/.anchor).
108
+ * Hierarchy:
109
+ * 1. Process environment: ANCHOR_DIR
110
+ * 2. Canonical user-global store: ~/.anchor/
111
+ * 3. Legacy Pi harness fallback: ~/.pi/agent/anchors/ (migrates automatically if present)
112
+ */
113
+ declare function getDefaultStorageDir(): string;
114
+ /**
115
+ * Acquire exclusive lockfile using atomic 'wx' file creation (zero external dependencies).
116
+ * Features:
117
+ * - 0 async infection (100% synchronous)
118
+ * - Automatic stale lock eviction (>5000ms or dead PID)
119
+ * - Exponential backoff with jitter
120
+ */
121
+ declare function acquireSyncLock(lockPath: string, maxWaitMs?: number, staleTimeoutMs?: number): () => void;
122
+ /**
123
+ * Windows-tolerant atomic rename with exponential spin-retry to combat NTFS EBUSY/EPERM file locks
124
+ */
125
+ declare function atomicRenameWithRetry(tempPath: string, targetPath: string, maxAttempts?: number): void;
126
+ declare class AnchorStore {
127
+ readonly storageDir: string;
128
+ readonly statePath: string;
129
+ readonly archivePath: string;
130
+ readonly graveyardPath: string;
131
+ constructor(customStorageDir?: string);
132
+ get lockPath(): string;
133
+ /**
134
+ * Execute mutation within a cross-process exclusive lock
135
+ */
136
+ withLock<T>(fn: () => T): T;
137
+ private ensureDirs;
138
+ /**
139
+ * Load store state. If corrupt or missing, returns safe default.
140
+ */
141
+ loadState(): AnchorStoreState;
142
+ /**
143
+ * Atomically save store state via temp file + atomic rename with Windows NTFS spin-retry
144
+ */
145
+ saveState(state: AnchorStoreState): void;
146
+ private generateId;
147
+ /**
148
+ * Create a new Anchor with automatic project and cwd tagging
149
+ */
150
+ create(input: {
151
+ title: string;
152
+ description?: string;
153
+ priority?: AnchorPriority;
154
+ durability?: AnchorDurability;
155
+ recurrence?: 'daily';
156
+ cwd?: string;
157
+ project?: string;
158
+ targetDate?: string;
159
+ files?: string[];
160
+ tags?: string[];
161
+ verifyCommand?: string;
162
+ decay?: AnchorDecayPolicy;
163
+ }): Anchor;
164
+ private findAnchorIndex;
165
+ /**
166
+ * Get an anchor by ID (supports 'anc-1', '#anc-1', '1', '01')
167
+ */
168
+ get(id: string): Anchor | undefined;
169
+ /**
170
+ * List anchors. By default, if cwd is provided, only returns anchors
171
+ * belonging to this cwd or marked as global (''). Pass all: true for global view.
172
+ */
173
+ list(filter?: {
174
+ status?: AnchorStatus;
175
+ priority?: AnchorPriority;
176
+ cwd?: string;
177
+ all?: boolean;
178
+ now?: number;
179
+ }): Anchor[];
180
+ /**
181
+ * Update an existing anchor
182
+ */
183
+ update(id: string, patch: Partial<Omit<Anchor, 'id' | 'createdAt'>>): Anchor;
184
+ /**
185
+ * Touch an anchor to refresh its decay window and reactivate sleeping state
186
+ */
187
+ touch(id: string, timestamp?: number): Anchor;
188
+ /**
189
+ * Settle an anchor (recurring daily habits complete for today and wake up tomorrow)
190
+ */
191
+ settle(id: string, evidence?: AnchorEvidence, now?: number): Anchor;
192
+ /**
193
+ * Reverse/undo the last settled anchor, popping it from archive.jsonl back into state.json
194
+ */
195
+ undoSettle(): Anchor;
196
+ /**
197
+ * Evict anchor to graveyard.jsonl upon total decay expiry
198
+ */
199
+ dropToGraveyard(id: string, reason: string): Anchor;
200
+ getArchive(filter?: {
201
+ cwd?: string;
202
+ all?: boolean;
203
+ }): Anchor[];
204
+ getGraveyard(filter?: {
205
+ cwd?: string;
206
+ all?: boolean;
207
+ }): Anchor[];
208
+ }
209
+
210
+ interface DecayEvaluation {
211
+ currentStatus: AnchorStatus;
212
+ nextStatus: AnchorStatus;
213
+ daysUntouched: number;
214
+ remainingActiveDays: number;
215
+ remainingSleepDays: number;
216
+ }
217
+ /**
218
+ * Calculate precise decay status based on elapsed time since last touch
219
+ */
220
+ declare function evaluateAnchorDecay(anchor: Anchor, now?: number): DecayEvaluation;
221
+ interface SweepResult {
222
+ transitionedToSleeping: string[];
223
+ wokenToActive: string[];
224
+ evictedToGraveyard: string[];
225
+ }
226
+ /**
227
+ * Run decay sweep across store: transitions statuses and evicts expired anchors
228
+ */
229
+ declare function sweepStore(store: AnchorStore, now?: number): SweepResult;
230
+
231
+ /**
232
+ * Convert simple glob pattern (e.g. *.ts, src/**\/*.ts, src/auth/*.ts) to RegExp
233
+ */
234
+ declare function globToRegExp(pattern: string): RegExp;
235
+ /**
236
+ * Normalize path separators to POSIX forward slashes and optionally relative to baseCwd
237
+ */
238
+ declare function normalizePath(filePath: string, baseCwd?: string): string;
239
+ /**
240
+ * Match touched files against an anchor's registered physical patterns
241
+ * Only anchors with explicit files/globs/prefixes are matched against file touches.
242
+ * Anchors with files: [] (pure mental notes or macro tasks) NEVER trigger on file operations.
243
+ */
244
+ declare function matchAnchorAgainstTouchedFiles(anchor: Anchor, touchedFiles: string[]): TouchMatchResult | null;
245
+ /**
246
+ * Filter and sort anchors that match a set of touched files
247
+ * Prioritizes high confidence (exact > prefix > keyword) and priority (P0 > P1 > P2).
248
+ */
249
+ declare function findMatchedAnchors(anchors: Anchor[], touchedFiles: string[]): TouchMatchResult[];
250
+
251
+ /**
252
+ * Execute physical verification test command for an anchor
253
+ */
254
+ declare function runPhysicalVerification(anchor: Anchor, cwd: string): {
255
+ success: boolean;
256
+ output: string;
257
+ };
258
+ /**
259
+ * Generate settlement candidates based on files touched during the session
260
+ */
261
+ declare function generateSettlementProposals(store: AnchorStore, touchedFiles: string[], cwd?: string): SettlementProposal[];
262
+ /**
263
+ * Format settlement proposal into terminal-ready card
264
+ */
265
+ declare function formatSettlementCard(proposal: SettlementProposal): string;
266
+
267
+ type AnchorQuadrant = 'Today' | 'Upcoming' | 'Habits' | 'Backlog';
268
+ interface GroupedAnchors {
269
+ today: Anchor[];
270
+ upcoming: Anchor[];
271
+ habits: Anchor[];
272
+ backlog: Anchor[];
273
+ }
274
+ /**
275
+ * Classify anchor into one of four cognitive quadrants:
276
+ * - 'Today': Due today or overdue
277
+ * - 'Upcoming': Due in the future (tomorrow, in 2d, specific dates)
278
+ * - 'Habits': Daily recurring habits
279
+ * - 'Backlog': Open-ended long-term vision
280
+ */
281
+ declare function classifyAnchor(anchor: Anchor, now?: number): AnchorQuadrant;
282
+ /**
283
+ * Group active anchors by cognitive quadrant
284
+ */
285
+ declare function groupAnchorsByQuadrant(anchors: Anchor[], now?: number): GroupedAnchors;
286
+ /**
287
+ * Strip ANSI escape codes from string
288
+ */
289
+ declare function stripAnsi(str: string): string;
290
+ /**
291
+ * Calculate display width in terminal columns, accounting for:
292
+ * - ANSI escape codes (width 0)
293
+ * - CJK ideographs & fullwidth forms (width 2)
294
+ * - Japanese Kana (width 2)
295
+ * - Korean Hangul syllables & Jamo (width 2)
296
+ * - Emojis & Pictographs (width 2)
297
+ * - Zero-width characters & joiners (width 0)
298
+ * - Standard ASCII (width 1)
299
+ */
300
+ declare function getDisplayWidth(str: string): number;
301
+ /**
302
+ * Truncate string to target terminal visual width with ellipsis
303
+ */
304
+ declare function truncateToWidth(str: string, maxWidth: number, ellipsis?: string): string;
305
+ /**
306
+ * Pad and/or truncate string to exact visual column width, eliminating column tearing
307
+ */
308
+ declare function padToWidth(str: string, targetWidth: number): string;
309
+ /**
310
+ * Format expected completion date in human-intuitive terms:
311
+ * - 'Daily' for daily recurring habits
312
+ * - 'Today' if targetDate is today
313
+ * - 'Tomorrow' if targetDate is tomorrow
314
+ * - 'In 2d' / 'In 3d' for near-term dates
315
+ * - 'MM-DD' for future dates
316
+ * - 'Overdue' if targetDate is past
317
+ * - 'Someday' for open-ended architecture/long-term vision
318
+ */
319
+ declare function formatTargetDate(anchor: Anchor, now?: number): string;
320
+ /**
321
+ * Format creation timestamp as concise date/time:
322
+ * - 'Today HH:MM' if created today
323
+ * - 'Yesterday HH:MM' if created yesterday
324
+ * - 'MM-DD HH:MM' if created this year
325
+ * - 'YYYY-MM-DD' if older
326
+ */
327
+ declare function formatCreationTime(timestamp: number, now?: number): string;
328
+ /**
329
+ * Backward compatibility alias
330
+ */
331
+ declare function formatRelativeTime(timestamp: number, now?: number): string;
332
+ /**
333
+ * Backward compatibility alias (now returns target date representation)
334
+ */
335
+ declare function formatRemainingTtl(anchor: Anchor, now?: number): string | undefined;
336
+ /**
337
+ * Format origin creation workspace folder (provenance: where the task was born/created)
338
+ */
339
+ declare function formatOrigin(anchor: Anchor | string): string;
340
+ /**
341
+ * Update footer status bar capsule: `⌖ 1`
342
+ */
343
+ declare function updateAnchorStatusBar(ctx: ExtensionContext, store: AnchorStore): void;
344
+ /**
345
+ * Render lightweight startup banner widget above the editor.
346
+ * Features 3-4 lines with quadrant badges and target date hints.
347
+ * Automatically cleared on agent_start (when user sends first message).
348
+ */
349
+ declare function updateStartupBanner(ctx: ExtensionContext, store: AnchorStore): void;
350
+ /**
351
+ * Clean, columnar-aligned checklist with Target Date and Creation Time columns
352
+ */
353
+ declare function openAnchorDashboard(ctx: ExtensionContext, store: AnchorStore): Promise<void>;
354
+
355
+ declare class SessionTouchObserver {
356
+ private touchedFiles;
357
+ private modifiedFiles;
358
+ private inspectedFiles;
359
+ private committed;
360
+ private commitMessages;
361
+ clear(): void;
362
+ /**
363
+ * Observe and record a tool call invocation, separating inspection from mutation
364
+ */
365
+ recordToolCall(toolName: string, input: Record<string, unknown>): void;
366
+ /**
367
+ * Add a file path manually (e.g. from git status diff)
368
+ */
369
+ addTouchedFile(filePath: string, isModified?: boolean): void;
370
+ getModifiedFiles(): string[];
371
+ getInspectedFiles(): string[];
372
+ getTouchedFiles(): string[];
373
+ hasCommitted(): boolean;
374
+ getCommitMessages(): string[];
375
+ /**
376
+ * Check if any commit message explicitly references or resolves an anchor
377
+ */
378
+ matchesCommit(anchor: Anchor): {
379
+ matched: boolean;
380
+ message?: string;
381
+ };
382
+ }
383
+
384
+ /**
385
+ * Universal multi-language comment generator.
386
+ * Maps file extension to safe comment syntax.
387
+ * Formats that do not safely support comments (e.g. .json, .env, binary) return null.
388
+ */
389
+ declare const COMMENT_FORMATS: Record<string, (msg: string) => string>;
390
+ /**
391
+ * Format safe task context comment for a specific file.
392
+ * Returns null for formats that do not safely support comments (e.g. .json, .env, .lock)
393
+ */
394
+ declare function makeSafeTaskAnnotation(filePath: string, anchor: Anchor): string | null;
395
+ /**
396
+ * Render ultra-compact cold-start context block for session turn 1.
397
+ * Only active anchors for the current project context are injected.
398
+ * From turn 2 onwards, this returns empty string (0 tokens).
399
+ */
400
+ declare function renderColdStartAnchorsContext(store: AnchorStore, cwdOrNow?: string | number, nowArg?: number): string;
401
+ /** Legacy alias for backwards compatibility */
402
+ declare const renderActiveAnchorsContext: typeof renderColdStartAnchorsContext;
403
+
404
+ /**
405
+ * Universal Agent Adapter Interface.
406
+ * Any AI coding harness (Pi, Claude Code, Cursor, Aider, custom MCP server) implements this thin layer.
407
+ */
408
+ interface AgentAdapter {
409
+ readonly name: string;
410
+ getCwd(): string;
411
+ notify?(message: string, level: 'info' | 'warn' | 'error'): void;
412
+ }
413
+ /**
414
+ * Anchor protocol engine for cross-session task management.
415
+ * Decoupled, harness-agnostic core managing state, decay, JIT annotations, and settlement.
416
+ */
417
+ declare class AnchorProtocol {
418
+ readonly store: AnchorStore;
419
+ readonly observer: SessionTouchObserver;
420
+ private readonly annotatedThisSession;
421
+ constructor(store: AnchorStore);
422
+ /**
423
+ * Run session initialization lifecycle: sweeps stale anchors and resets session state
424
+ */
425
+ handleSessionStart(now?: number): {
426
+ sleepingCount: number;
427
+ };
428
+ /**
429
+ * Handle turn lifecycle: injects cold-start prompt on turn 1, returns null (0 tokens) on turn 2+
430
+ */
431
+ handleBeforeTurn(turnCount: number, cwd: string, now?: number): string | null;
432
+ /**
433
+ * Handle tool execution result: detects touched paths, refreshes mutation touch timers,
434
+ * and returns safe language-specific JIT annotation (or null if syntax incompatible/already annotated).
435
+ */
436
+ handleToolResult(input: {
437
+ toolName: string;
438
+ filePath?: string;
439
+ isError?: boolean;
440
+ isMutation?: boolean;
441
+ cwd: string;
442
+ }): {
443
+ annotation: string | null;
444
+ matchedAnchors: Anchor[];
445
+ };
446
+ }
447
+
448
+ export { type AgentAdapter, type Anchor, type AnchorDecayPolicy, type AnchorDurability, type AnchorEvidence, type AnchorPriority, AnchorProtocol, type AnchorQuadrant, type AnchorStatus, AnchorStore, type AnchorStoreState, COMMENT_FORMATS, DEFAULT_DECAY_POLICY, DURABLE_DECAY_POLICY, type DecayEvaluation, EPHEMERAL_DECAY_POLICY, type GroupedAnchors, SessionTouchObserver, type SettlementProposal, type SweepResult, type TouchMatchResult, acquireSyncLock, atomicRenameWithRetry, classifyAnchor, detectDurability, detectRecurrence, detectTargetDate, evaluateAnchorDecay, findMatchedAnchors, formatCreationTime, formatOrigin, formatRelativeTime, formatRemainingTtl, formatSettlementCard, formatTargetDate, generateSettlementProposals, getDefaultStorageDir, getDisplayWidth, getEphemeralDecayPolicy, getTodayDateString, globToRegExp, groupAnchorsByQuadrant, makeSafeTaskAnnotation, matchAnchorAgainstTouchedFiles, normalizePath, openAnchorDashboard, padToWidth, renderActiveAnchorsContext, renderColdStartAnchorsContext, runPhysicalVerification, stripAnsi, sweepStore, truncateToWidth, updateAnchorStatusBar, updateStartupBanner };
package/dist/index.js ADDED
@@ -0,0 +1,144 @@
1
+ import {
2
+ AnchorStore,
3
+ COMMENT_FORMATS,
4
+ DEFAULT_DECAY_POLICY,
5
+ DURABLE_DECAY_POLICY,
6
+ EPHEMERAL_DECAY_POLICY,
7
+ SessionTouchObserver,
8
+ acquireSyncLock,
9
+ atomicRenameWithRetry,
10
+ classifyAnchor,
11
+ detectDurability,
12
+ detectRecurrence,
13
+ detectTargetDate,
14
+ evaluateAnchorDecay,
15
+ findMatchedAnchors,
16
+ formatCreationTime,
17
+ formatOrigin,
18
+ formatRelativeTime,
19
+ formatRemainingTtl,
20
+ formatSettlementCard,
21
+ formatTargetDate,
22
+ generateSettlementProposals,
23
+ getDefaultStorageDir,
24
+ getDisplayWidth,
25
+ getEphemeralDecayPolicy,
26
+ getTodayDateString,
27
+ globToRegExp,
28
+ groupAnchorsByQuadrant,
29
+ makeSafeTaskAnnotation,
30
+ matchAnchorAgainstTouchedFiles,
31
+ normalizePath,
32
+ openAnchorDashboard,
33
+ padToWidth,
34
+ renderActiveAnchorsContext,
35
+ renderColdStartAnchorsContext,
36
+ runPhysicalVerification,
37
+ stripAnsi,
38
+ sweepStore,
39
+ truncateToWidth,
40
+ updateAnchorStatusBar,
41
+ updateStartupBanner
42
+ } from "./chunk-NNYUD5O7.js";
43
+
44
+ // src/protocol.ts
45
+ var AnchorProtocol = class {
46
+ store;
47
+ observer;
48
+ annotatedThisSession = /* @__PURE__ */ new Set();
49
+ constructor(store) {
50
+ this.store = store;
51
+ this.observer = new SessionTouchObserver();
52
+ }
53
+ /**
54
+ * Run session initialization lifecycle: sweeps stale anchors and resets session state
55
+ */
56
+ handleSessionStart(now = Date.now()) {
57
+ this.observer.clear();
58
+ this.annotatedThisSession.clear();
59
+ const sweep = sweepStore(this.store, now);
60
+ return { sleepingCount: sweep.transitionedToSleeping.length };
61
+ }
62
+ /**
63
+ * Handle turn lifecycle: injects cold-start prompt on turn 1, returns null (0 tokens) on turn 2+
64
+ */
65
+ handleBeforeTurn(turnCount, cwd, now = Date.now()) {
66
+ if (turnCount <= 1) {
67
+ return renderColdStartAnchorsContext(this.store, cwd, now) || null;
68
+ }
69
+ return null;
70
+ }
71
+ /**
72
+ * Handle tool execution result: detects touched paths, refreshes mutation touch timers,
73
+ * and returns safe language-specific JIT annotation (or null if syntax incompatible/already annotated).
74
+ */
75
+ handleToolResult(input) {
76
+ if (!input.isError) {
77
+ this.observer.recordToolCall(input.toolName, input.filePath ? { path: input.filePath } : {});
78
+ }
79
+ if (!input.filePath) {
80
+ return { annotation: null, matchedAnchors: [] };
81
+ }
82
+ const touchedPath = normalizePath(input.filePath);
83
+ const anchors = this.store.list({ cwd: input.cwd }).filter((a2) => a2.status === "active" || a2.status === "sleeping");
84
+ const matches = findMatchedAnchors(anchors, [touchedPath]);
85
+ if (matches.length === 0) {
86
+ return { annotation: null, matchedAnchors: [] };
87
+ }
88
+ if (input.isMutation && !input.isError) {
89
+ for (const m of matches) {
90
+ this.store.touch(m.anchor.id);
91
+ }
92
+ }
93
+ const a = matches[0].anchor;
94
+ if (!this.annotatedThisSession.has(a.id)) {
95
+ this.annotatedThisSession.add(a.id);
96
+ const annotation = makeSafeTaskAnnotation(touchedPath, a);
97
+ return { annotation, matchedAnchors: matches.map((m) => m.anchor) };
98
+ }
99
+ return { annotation: null, matchedAnchors: matches.map((m) => m.anchor) };
100
+ }
101
+ };
102
+ export {
103
+ AnchorProtocol,
104
+ AnchorStore,
105
+ COMMENT_FORMATS,
106
+ DEFAULT_DECAY_POLICY,
107
+ DURABLE_DECAY_POLICY,
108
+ EPHEMERAL_DECAY_POLICY,
109
+ SessionTouchObserver,
110
+ acquireSyncLock,
111
+ atomicRenameWithRetry,
112
+ classifyAnchor,
113
+ detectDurability,
114
+ detectRecurrence,
115
+ detectTargetDate,
116
+ evaluateAnchorDecay,
117
+ findMatchedAnchors,
118
+ formatCreationTime,
119
+ formatOrigin,
120
+ formatRelativeTime,
121
+ formatRemainingTtl,
122
+ formatSettlementCard,
123
+ formatTargetDate,
124
+ generateSettlementProposals,
125
+ getDefaultStorageDir,
126
+ getDisplayWidth,
127
+ getEphemeralDecayPolicy,
128
+ getTodayDateString,
129
+ globToRegExp,
130
+ groupAnchorsByQuadrant,
131
+ makeSafeTaskAnnotation,
132
+ matchAnchorAgainstTouchedFiles,
133
+ normalizePath,
134
+ openAnchorDashboard,
135
+ padToWidth,
136
+ renderActiveAnchorsContext,
137
+ renderColdStartAnchorsContext,
138
+ runPhysicalVerification,
139
+ stripAnsi,
140
+ sweepStore,
141
+ truncateToWidth,
142
+ updateAnchorStatusBar,
143
+ updateStartupBanner
144
+ };
package/package.json ADDED
@@ -0,0 +1,64 @@
1
+ {
2
+ "name": "pi-anchor",
3
+ "version": "0.1.0",
4
+ "description": "Zero-pollution, 0-idle-token task memory for AI coding agents",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "module": "dist/index.js",
8
+ "types": "dist/index.d.ts",
9
+ "files": [
10
+ "dist",
11
+ "src",
12
+ "bin",
13
+ "README.md",
14
+ "README_zh.md",
15
+ "LICENSE"
16
+ ],
17
+ "bin": {
18
+ "anchor": "bin/anchor.js"
19
+ },
20
+ "pi": {
21
+ "extensions": [
22
+ "dist/extension.js"
23
+ ],
24
+ "image": "https://raw.githubusercontent.com/3ZEROS12/anchor/main/assets/hero.svg"
25
+ },
26
+ "scripts": {
27
+ "build": "tsup src/index.ts src/extension.ts --format esm,cjs --dts --clean",
28
+ "typecheck": "tsc --noEmit",
29
+ "test": "tsx --test",
30
+ "prepack": "npm run build"
31
+ },
32
+ "keywords": [
33
+ "pi-package",
34
+ "ai-agent",
35
+ "coding-agent",
36
+ "task-management",
37
+ "memory",
38
+ "context-window",
39
+ "zero-pollution",
40
+ "pi-extension"
41
+ ],
42
+ "author": "Jason Song <jiaxinsong312@gmail.com>",
43
+ "license": "MIT",
44
+ "repository": {
45
+ "type": "git",
46
+ "url": "git+https://github.com/3ZEROS12/anchor.git"
47
+ },
48
+ "homepage": "https://github.com/3ZEROS12/anchor#readme",
49
+ "bugs": {
50
+ "url": "https://github.com/3ZEROS12/anchor/issues"
51
+ },
52
+ "publishConfig": {
53
+ "access": "public"
54
+ },
55
+ "devDependencies": {
56
+ "@types/node": "^26.6.2",
57
+ "tsup": "^8.5.1",
58
+ "tsx": "^4.23.15",
59
+ "typescript": "^5.8.2"
60
+ },
61
+ "dependencies": {
62
+ "@sinclair/typebox": "^0.34.52"
63
+ }
64
+ }