pi-anchor 0.2.3 → 0.3.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 +29 -14
- package/README_zh.md +29 -14
- package/assets/hero-en.svg +87 -0
- package/assets/hero-zh.svg +87 -0
- package/assets/hero.svg +108 -0
- package/bin/anchor.js +38 -51
- package/dist/{chunk-G72UHPBN.js → chunk-Q7IILJFA.js} +165 -43
- package/dist/extension.cjs +291 -62
- package/dist/extension.d.cts +16 -1
- package/dist/extension.d.ts +16 -1
- package/dist/extension.js +133 -23
- package/dist/index.cjs +169 -43
- package/dist/index.d.cts +63 -242
- package/dist/index.d.ts +63 -242
- package/dist/index.js +9 -1
- package/dist/store-t2_TDhJn.d.cts +210 -0
- package/dist/store-t2_TDhJn.d.ts +210 -0
- package/package.json +21 -5
- package/src/extension.ts +167 -30
- package/src/matcher.ts +51 -0
- package/src/settlement.ts +72 -6
- package/src/store.ts +25 -9
- package/src/tui.ts +77 -29
- package/src/types.ts +7 -7
package/dist/index.d.ts
CHANGED
|
@@ -1,213 +1,7 @@
|
|
|
1
|
+
import { a as AnchorStatus, b as Anchor, A as AnchorStore, T as TouchMatchResult, S as SettlementProposal } from './store-t2_TDhJn.js';
|
|
2
|
+
export { c as AnchorDecayPolicy, d as AnchorDurability, e as AnchorEvidence, f as AnchorPriority, g as AnchorStoreState, D as DEFAULT_DECAY_POLICY, h as DURABLE_DECAY_POLICY, E as EPHEMERAL_DECAY_POLICY, i as acquireSyncLock, j as atomicRenameWithRetry, k as detectDurability, l as detectRecurrence, m as detectTargetDate, n as getDefaultStorageDir, o as getEphemeralDecayPolicy, p as getTodayDateString } from './store-t2_TDhJn.js';
|
|
1
3
|
import { ExtensionContext } from '@earendil-works/pi-coding-agent';
|
|
2
4
|
|
|
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
|
-
nextId?: number;
|
|
70
|
-
}
|
|
71
|
-
interface TouchMatchResult {
|
|
72
|
-
anchor: Anchor;
|
|
73
|
-
score: number;
|
|
74
|
-
matchedFiles: string[];
|
|
75
|
-
reason: 'exact-file' | 'dir-prefix' | 'tag-keyword';
|
|
76
|
-
}
|
|
77
|
-
interface SettlementProposal {
|
|
78
|
-
anchor: Anchor;
|
|
79
|
-
matchedFiles: string[];
|
|
80
|
-
reason: string;
|
|
81
|
-
recommendedAction: 'settle' | 'defer';
|
|
82
|
-
}
|
|
83
|
-
declare const EPHEMERAL_DECAY_POLICY: AnchorDecayPolicy;
|
|
84
|
-
declare const DURABLE_DECAY_POLICY: AnchorDecayPolicy;
|
|
85
|
-
declare const DEFAULT_DECAY_POLICY: AnchorDecayPolicy;
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Detect whether a task is a short-term ephemeral reminder or a long-term architectural vision
|
|
89
|
-
*/
|
|
90
|
-
declare function detectDurability(title: string): AnchorDurability;
|
|
91
|
-
/**
|
|
92
|
-
* Get custom ephemeral decay policy based on temporal span (e.g. 后天, 大后天)
|
|
93
|
-
*/
|
|
94
|
-
declare function getEphemeralDecayPolicy(title: string): AnchorDecayPolicy;
|
|
95
|
-
/**
|
|
96
|
-
* Detect expected completion date from title keywords or explicit date string, returning 'YYYY-MM-DD'
|
|
97
|
-
*/
|
|
98
|
-
declare function detectTargetDate(titleOrDate: string, now?: number): string | undefined;
|
|
99
|
-
/**
|
|
100
|
-
* Detect recurring patterns, e.g. daily habits
|
|
101
|
-
*/
|
|
102
|
-
declare function detectRecurrence(title: string): 'daily' | undefined;
|
|
103
|
-
/**
|
|
104
|
-
* Get current date string 'YYYY-MM-DD' in local timezone
|
|
105
|
-
*/
|
|
106
|
-
declare function getTodayDateString(timestamp?: number): string;
|
|
107
|
-
/**
|
|
108
|
-
* Resolve the default storage directory for Anchor (~/.anchor).
|
|
109
|
-
* Hierarchy:
|
|
110
|
-
* 1. Process environment: ANCHOR_DIR
|
|
111
|
-
* 2. Canonical user-global store: ~/.anchor/
|
|
112
|
-
* 3. Legacy Pi harness fallback: ~/.pi/agent/anchors/ (migrates automatically if present)
|
|
113
|
-
*/
|
|
114
|
-
declare function getDefaultStorageDir(): string;
|
|
115
|
-
/**
|
|
116
|
-
* Acquire exclusive lockfile using atomic 'wx' file creation (zero external dependencies).
|
|
117
|
-
* Features:
|
|
118
|
-
* - 0 async infection (100% synchronous)
|
|
119
|
-
* - Automatic stale lock eviction (>5000ms or dead PID)
|
|
120
|
-
* - Exponential backoff with jitter
|
|
121
|
-
*/
|
|
122
|
-
declare function acquireSyncLock(lockPath: string, maxWaitMs?: number, staleTimeoutMs?: number): () => void;
|
|
123
|
-
/**
|
|
124
|
-
* Windows-tolerant atomic rename with exponential spin-retry to combat NTFS EBUSY/EPERM file locks
|
|
125
|
-
*/
|
|
126
|
-
declare function atomicRenameWithRetry(tempPath: string, targetPath: string, maxAttempts?: number): void;
|
|
127
|
-
declare class AnchorStore {
|
|
128
|
-
readonly storageDir: string;
|
|
129
|
-
readonly statePath: string;
|
|
130
|
-
readonly archivePath: string;
|
|
131
|
-
readonly graveyardPath: string;
|
|
132
|
-
constructor(customStorageDir?: string);
|
|
133
|
-
get lockPath(): string;
|
|
134
|
-
/**
|
|
135
|
-
* Execute mutation within a cross-process exclusive lock
|
|
136
|
-
*/
|
|
137
|
-
withLock<T>(fn: () => T): T;
|
|
138
|
-
private ensureDirs;
|
|
139
|
-
/**
|
|
140
|
-
* Load store state. If corrupt or missing, returns safe default.
|
|
141
|
-
*/
|
|
142
|
-
loadState(): AnchorStoreState;
|
|
143
|
-
/**
|
|
144
|
-
* Atomically save store state via temp file + atomic rename with Windows NTFS spin-retry
|
|
145
|
-
*/
|
|
146
|
-
saveState(state: AnchorStoreState): void;
|
|
147
|
-
private generateId;
|
|
148
|
-
/**
|
|
149
|
-
* Create a new Anchor with automatic project and cwd tagging
|
|
150
|
-
*/
|
|
151
|
-
create(input: {
|
|
152
|
-
title: string;
|
|
153
|
-
description?: string;
|
|
154
|
-
priority?: AnchorPriority;
|
|
155
|
-
durability?: AnchorDurability;
|
|
156
|
-
recurrence?: 'daily';
|
|
157
|
-
cwd?: string;
|
|
158
|
-
project?: string;
|
|
159
|
-
targetDate?: string;
|
|
160
|
-
files?: string[];
|
|
161
|
-
tags?: string[];
|
|
162
|
-
verifyCommand?: string;
|
|
163
|
-
decay?: AnchorDecayPolicy;
|
|
164
|
-
}): Anchor;
|
|
165
|
-
private findAnchorIndex;
|
|
166
|
-
/**
|
|
167
|
-
* Get an anchor by ID (supports 'anc-1', '#anc-1', '1', '01')
|
|
168
|
-
*/
|
|
169
|
-
get(id: string): Anchor | undefined;
|
|
170
|
-
/**
|
|
171
|
-
* List anchors. By default, if cwd is provided, only returns anchors
|
|
172
|
-
* belonging to this cwd or marked as global (''). Pass all: true for global view.
|
|
173
|
-
*/
|
|
174
|
-
list(filter?: {
|
|
175
|
-
status?: AnchorStatus;
|
|
176
|
-
priority?: AnchorPriority;
|
|
177
|
-
cwd?: string;
|
|
178
|
-
all?: boolean;
|
|
179
|
-
now?: number;
|
|
180
|
-
}): Anchor[];
|
|
181
|
-
/**
|
|
182
|
-
* Update an existing anchor
|
|
183
|
-
*/
|
|
184
|
-
update(id: string, patch: Partial<Omit<Anchor, 'id' | 'createdAt'>>): Anchor;
|
|
185
|
-
/**
|
|
186
|
-
* Touch an anchor to refresh its decay window and reactivate sleeping state
|
|
187
|
-
*/
|
|
188
|
-
touch(id: string, timestamp?: number): Anchor;
|
|
189
|
-
/**
|
|
190
|
-
* Settle an anchor (recurring daily habits complete for today and wake up tomorrow)
|
|
191
|
-
*/
|
|
192
|
-
settle(id: string, evidence?: AnchorEvidence, now?: number): Anchor;
|
|
193
|
-
/**
|
|
194
|
-
* Reverse/undo the last settled anchor, popping it from archive.jsonl back into state.json
|
|
195
|
-
*/
|
|
196
|
-
undoSettle(): Anchor;
|
|
197
|
-
/**
|
|
198
|
-
* Evict anchor to graveyard.jsonl upon total decay expiry
|
|
199
|
-
*/
|
|
200
|
-
dropToGraveyard(id: string, reason: string): Anchor;
|
|
201
|
-
getArchive(filter?: {
|
|
202
|
-
cwd?: string;
|
|
203
|
-
all?: boolean;
|
|
204
|
-
}): Anchor[];
|
|
205
|
-
getGraveyard(filter?: {
|
|
206
|
-
cwd?: string;
|
|
207
|
-
all?: boolean;
|
|
208
|
-
}): Anchor[];
|
|
209
|
-
}
|
|
210
|
-
|
|
211
5
|
interface DecayEvaluation {
|
|
212
6
|
currentStatus: AnchorStatus;
|
|
213
7
|
nextStatus: AnchorStatus;
|
|
@@ -241,6 +35,16 @@ declare function normalizePath(filePath: string, baseCwd?: string): string;
|
|
|
241
35
|
* Compare two workspace CWDs safely (case-insensitive on Windows, supports subdirectory scoping)
|
|
242
36
|
*/
|
|
243
37
|
declare function isSameOrSubPath(anchorCwd?: string, targetCwd?: string): boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Extract explicit file or directory paths mentioned in natural language title/description
|
|
40
|
+
* (e.g. "D:/College/优化/EXTERNAL_ARCHITECTURE_INSPIRATIONS.md", "src/auth/jwt.ts")
|
|
41
|
+
*/
|
|
42
|
+
declare function extractPathsFromText(text?: string): string[];
|
|
43
|
+
/**
|
|
44
|
+
* Determine whether an anchor specifically targets a given workspace directory (`targetCwd`),
|
|
45
|
+
* checking `anchor.cwd`, `anchor.project`, and `anchor.files`.
|
|
46
|
+
*/
|
|
47
|
+
declare function isAnchorTargetingWorkspace(anchor: Anchor, targetCwd?: string): boolean;
|
|
244
48
|
/**
|
|
245
49
|
* Match touched files against an anchor's registered physical patterns
|
|
246
50
|
* Only anchors with explicit files/globs/prefixes are matched against file touches.
|
|
@@ -253,6 +57,35 @@ declare function matchAnchorAgainstTouchedFiles(anchor: Anchor, touchedFiles: st
|
|
|
253
57
|
*/
|
|
254
58
|
declare function findMatchedAnchors(anchors: Anchor[], touchedFiles: string[]): TouchMatchResult[];
|
|
255
59
|
|
|
60
|
+
declare class SessionTouchObserver {
|
|
61
|
+
private touchedFiles;
|
|
62
|
+
private modifiedFiles;
|
|
63
|
+
private inspectedFiles;
|
|
64
|
+
private committed;
|
|
65
|
+
private commitMessages;
|
|
66
|
+
clear(): void;
|
|
67
|
+
/**
|
|
68
|
+
* Observe and record a tool call invocation, separating inspection from mutation
|
|
69
|
+
*/
|
|
70
|
+
recordToolCall(toolName: string, input: Record<string, unknown>): void;
|
|
71
|
+
/**
|
|
72
|
+
* Add a file path manually (e.g. from git status diff)
|
|
73
|
+
*/
|
|
74
|
+
addTouchedFile(filePath: string, isModified?: boolean): void;
|
|
75
|
+
getModifiedFiles(): string[];
|
|
76
|
+
getInspectedFiles(): string[];
|
|
77
|
+
getTouchedFiles(): string[];
|
|
78
|
+
hasCommitted(): boolean;
|
|
79
|
+
getCommitMessages(): string[];
|
|
80
|
+
/**
|
|
81
|
+
* Check if any commit message explicitly references or resolves an anchor
|
|
82
|
+
*/
|
|
83
|
+
matchesCommit(anchor: Anchor): {
|
|
84
|
+
matched: boolean;
|
|
85
|
+
message?: string;
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
256
89
|
/**
|
|
257
90
|
* Execute physical verification test command for an anchor
|
|
258
91
|
*/
|
|
@@ -261,9 +94,18 @@ declare function runPhysicalVerification(anchor: Anchor, cwd: string): {
|
|
|
261
94
|
output: string;
|
|
262
95
|
};
|
|
263
96
|
/**
|
|
264
|
-
*
|
|
97
|
+
* Real-time autonomous settlement during active session:
|
|
98
|
+
* Triggered immediately when a non-error shell command runs `git commit` or a matching `verifyCommand`.
|
|
99
|
+
* Works for BOTH file-bound and file-less (`files: []`) anchors!
|
|
265
100
|
*/
|
|
266
|
-
declare function
|
|
101
|
+
declare function tryRealtimeAutoSettle(store: AnchorStore, observer: SessionTouchObserver, cwd?: string, executedCommand?: string): Array<{
|
|
102
|
+
anchor: Anchor;
|
|
103
|
+
reason: string;
|
|
104
|
+
}>;
|
|
105
|
+
/**
|
|
106
|
+
* Generate settlement candidates based on files touched or commits made during the session
|
|
107
|
+
*/
|
|
108
|
+
declare function generateSettlementProposals(store: AnchorStore, touchedFiles: string[], cwd?: string, observer?: SessionTouchObserver): SettlementProposal[];
|
|
267
109
|
/**
|
|
268
110
|
* Format settlement proposal into terminal-ready card
|
|
269
111
|
*/
|
|
@@ -288,6 +130,14 @@ declare function classifyAnchor(anchor: Anchor, now?: number): AnchorQuadrant;
|
|
|
288
130
|
* Group active anchors by cognitive quadrant
|
|
289
131
|
*/
|
|
290
132
|
declare function groupAnchorsByQuadrant(anchors: Anchor[], now?: number): GroupedAnchors;
|
|
133
|
+
/**
|
|
134
|
+
* Sort unfinished anchors into a single compact, priority-driven engineering list:
|
|
135
|
+
* 1. Active before Sleeping (quiet)
|
|
136
|
+
* 2. Matching current workspace before other workspaces
|
|
137
|
+
* 3. Due Today / Overdue before open-ended
|
|
138
|
+
* 4. Priority (P0 -> P1 -> P2)
|
|
139
|
+
*/
|
|
140
|
+
declare function sortAnchorsForDisplay(anchors: Anchor[], cwd?: string, now?: number): Anchor[];
|
|
291
141
|
/**
|
|
292
142
|
* Strip ANSI escape codes from string
|
|
293
143
|
*/
|
|
@@ -340,7 +190,7 @@ declare function formatRelativeTime(timestamp: number, now?: number): string;
|
|
|
340
190
|
*/
|
|
341
191
|
declare function formatRemainingTtl(anchor: Anchor, now?: number): string | undefined;
|
|
342
192
|
/**
|
|
343
|
-
* Format origin
|
|
193
|
+
* Format origin workspace / project label concisely
|
|
344
194
|
*/
|
|
345
195
|
declare function formatOrigin(anchor: Anchor | string): string;
|
|
346
196
|
/**
|
|
@@ -354,39 +204,10 @@ declare function updateAnchorStatusBar(ctx: ExtensionContext, store: AnchorStore
|
|
|
354
204
|
*/
|
|
355
205
|
declare function updateStartupBanner(ctx: ExtensionContext, store: AnchorStore): void;
|
|
356
206
|
/**
|
|
357
|
-
* Clean,
|
|
207
|
+
* Clean, flat priority-sorted engineering checklist (includes both active and quiet/sleeping tasks so nothing is ever lost)
|
|
358
208
|
*/
|
|
359
209
|
declare function openAnchorDashboard(ctx: ExtensionContext, store: AnchorStore): Promise<void>;
|
|
360
210
|
|
|
361
|
-
declare class SessionTouchObserver {
|
|
362
|
-
private touchedFiles;
|
|
363
|
-
private modifiedFiles;
|
|
364
|
-
private inspectedFiles;
|
|
365
|
-
private committed;
|
|
366
|
-
private commitMessages;
|
|
367
|
-
clear(): void;
|
|
368
|
-
/**
|
|
369
|
-
* Observe and record a tool call invocation, separating inspection from mutation
|
|
370
|
-
*/
|
|
371
|
-
recordToolCall(toolName: string, input: Record<string, unknown>): void;
|
|
372
|
-
/**
|
|
373
|
-
* Add a file path manually (e.g. from git status diff)
|
|
374
|
-
*/
|
|
375
|
-
addTouchedFile(filePath: string, isModified?: boolean): void;
|
|
376
|
-
getModifiedFiles(): string[];
|
|
377
|
-
getInspectedFiles(): string[];
|
|
378
|
-
getTouchedFiles(): string[];
|
|
379
|
-
hasCommitted(): boolean;
|
|
380
|
-
getCommitMessages(): string[];
|
|
381
|
-
/**
|
|
382
|
-
* Check if any commit message explicitly references or resolves an anchor
|
|
383
|
-
*/
|
|
384
|
-
matchesCommit(anchor: Anchor): {
|
|
385
|
-
matched: boolean;
|
|
386
|
-
message?: string;
|
|
387
|
-
};
|
|
388
|
-
}
|
|
389
|
-
|
|
390
211
|
/**
|
|
391
212
|
* Universal multi-language comment generator.
|
|
392
213
|
* Maps file extension to safe comment syntax.
|
|
@@ -451,4 +272,4 @@ declare class AnchorProtocol {
|
|
|
451
272
|
};
|
|
452
273
|
}
|
|
453
274
|
|
|
454
|
-
export { type AgentAdapter,
|
|
275
|
+
export { type AgentAdapter, Anchor, AnchorProtocol, type AnchorQuadrant, AnchorStatus, AnchorStore, COMMENT_FORMATS, type DecayEvaluation, type GroupedAnchors, SessionTouchObserver, SettlementProposal, type SweepResult, TouchMatchResult, classifyAnchor, evaluateAnchorDecay, extractPathsFromText, findMatchedAnchors, formatCreationTime, formatOrigin, formatRelativeTime, formatRemainingTtl, formatSettlementCard, formatTargetDate, generateSettlementProposals, getDisplayWidth, globToRegExp, groupAnchorsByQuadrant, hasActiveUI, isAnchorTargetingWorkspace, isSameOrSubPath, makeSafeTaskAnnotation, matchAnchorAgainstTouchedFiles, normalizePath, openAnchorDashboard, padToWidth, renderActiveAnchorsContext, renderColdStartAnchorsContext, runPhysicalVerification, sortAnchorsForDisplay, stripAnsi, sweepStore, truncateToWidth, tryRealtimeAutoSettle, updateAnchorStatusBar, updateStartupBanner };
|
package/dist/index.js
CHANGED
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
detectRecurrence,
|
|
13
13
|
detectTargetDate,
|
|
14
14
|
evaluateAnchorDecay,
|
|
15
|
+
extractPathsFromText,
|
|
15
16
|
findMatchedAnchors,
|
|
16
17
|
formatCreationTime,
|
|
17
18
|
formatOrigin,
|
|
@@ -27,6 +28,7 @@ import {
|
|
|
27
28
|
globToRegExp,
|
|
28
29
|
groupAnchorsByQuadrant,
|
|
29
30
|
hasActiveUI,
|
|
31
|
+
isAnchorTargetingWorkspace,
|
|
30
32
|
isSameOrSubPath,
|
|
31
33
|
makeSafeTaskAnnotation,
|
|
32
34
|
matchAnchorAgainstTouchedFiles,
|
|
@@ -36,12 +38,14 @@ import {
|
|
|
36
38
|
renderActiveAnchorsContext,
|
|
37
39
|
renderColdStartAnchorsContext,
|
|
38
40
|
runPhysicalVerification,
|
|
41
|
+
sortAnchorsForDisplay,
|
|
39
42
|
stripAnsi,
|
|
40
43
|
sweepStore,
|
|
41
44
|
truncateToWidth,
|
|
45
|
+
tryRealtimeAutoSettle,
|
|
42
46
|
updateAnchorStatusBar,
|
|
43
47
|
updateStartupBanner
|
|
44
|
-
} from "./chunk-
|
|
48
|
+
} from "./chunk-Q7IILJFA.js";
|
|
45
49
|
|
|
46
50
|
// src/protocol.ts
|
|
47
51
|
var AnchorProtocol = class {
|
|
@@ -116,6 +120,7 @@ export {
|
|
|
116
120
|
detectRecurrence,
|
|
117
121
|
detectTargetDate,
|
|
118
122
|
evaluateAnchorDecay,
|
|
123
|
+
extractPathsFromText,
|
|
119
124
|
findMatchedAnchors,
|
|
120
125
|
formatCreationTime,
|
|
121
126
|
formatOrigin,
|
|
@@ -131,6 +136,7 @@ export {
|
|
|
131
136
|
globToRegExp,
|
|
132
137
|
groupAnchorsByQuadrant,
|
|
133
138
|
hasActiveUI,
|
|
139
|
+
isAnchorTargetingWorkspace,
|
|
134
140
|
isSameOrSubPath,
|
|
135
141
|
makeSafeTaskAnnotation,
|
|
136
142
|
matchAnchorAgainstTouchedFiles,
|
|
@@ -140,9 +146,11 @@ export {
|
|
|
140
146
|
renderActiveAnchorsContext,
|
|
141
147
|
renderColdStartAnchorsContext,
|
|
142
148
|
runPhysicalVerification,
|
|
149
|
+
sortAnchorsForDisplay,
|
|
143
150
|
stripAnsi,
|
|
144
151
|
sweepStore,
|
|
145
152
|
truncateToWidth,
|
|
153
|
+
tryRealtimeAutoSettle,
|
|
146
154
|
updateAnchorStatusBar,
|
|
147
155
|
updateStartupBanner
|
|
148
156
|
};
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Anchor Data Contracts & Type Definitions
|
|
3
|
+
* Single Global Source of Truth with Zero Workspace Contamination.
|
|
4
|
+
*/
|
|
5
|
+
type AnchorStatus = 'active' | 'sleeping' | 'settled' | 'graveyard';
|
|
6
|
+
type AnchorPriority = 'p0' | 'p1' | 'p2';
|
|
7
|
+
type AnchorDurability = 'ephemeral' | 'durable';
|
|
8
|
+
interface AnchorDecayPolicy {
|
|
9
|
+
/** Days before an untouched active anchor transitions to sleeping */
|
|
10
|
+
activeDays: number;
|
|
11
|
+
/** Days an anchor remains sleeping before dropping to graveyard */
|
|
12
|
+
sleepDays: number;
|
|
13
|
+
/** Total maximum days before permanent graveyard sweep (9999 = never) */
|
|
14
|
+
graveyardDays: number;
|
|
15
|
+
}
|
|
16
|
+
interface AnchorEvidence {
|
|
17
|
+
settledAt?: number;
|
|
18
|
+
settledBy?: 'one-tap-settlement' | 'manual-command' | 'verification-test';
|
|
19
|
+
commitHash?: string;
|
|
20
|
+
summary?: string;
|
|
21
|
+
touchedFiles?: string[];
|
|
22
|
+
}
|
|
23
|
+
interface Anchor {
|
|
24
|
+
/** Unique sequential identifier, e.g., 'anc-1' */
|
|
25
|
+
id: string;
|
|
26
|
+
/** Short imperative title of the task */
|
|
27
|
+
title: string;
|
|
28
|
+
/** Optional detailed context or notes */
|
|
29
|
+
description?: string;
|
|
30
|
+
/** Task priority */
|
|
31
|
+
priority: AnchorPriority;
|
|
32
|
+
/** Current lifecycle status */
|
|
33
|
+
status: AnchorStatus;
|
|
34
|
+
/** Durability tier: ephemeral (short-lived reminder, 48h TTL) or durable (long-term architecture vision) */
|
|
35
|
+
durability: AnchorDurability;
|
|
36
|
+
/** Canonical project name (e.g. 'PPT', 'X', or 'global') */
|
|
37
|
+
project: string;
|
|
38
|
+
/** Canonical root directory where the task was anchored (empty string for global tasks) */
|
|
39
|
+
cwd: string;
|
|
40
|
+
/** Creation timestamp in ms */
|
|
41
|
+
createdAt: number;
|
|
42
|
+
/** Last update timestamp in ms */
|
|
43
|
+
updatedAt: number;
|
|
44
|
+
/** Last touched timestamp in ms (resets on file touch) */
|
|
45
|
+
lastTouchedAt: number;
|
|
46
|
+
/** Associated file paths or globs, e.g. ['src/auth/*.ts'] */
|
|
47
|
+
files: string[];
|
|
48
|
+
/** Expected completion date in format 'YYYY-MM-DD' (e.g. '2026-09-24') */
|
|
49
|
+
targetDate?: string;
|
|
50
|
+
/** Optional shell command for automated physical verification (e.g. 'npm test') */
|
|
51
|
+
verifyCommand?: string;
|
|
52
|
+
/** Topic or domain tags, e.g. ['auth', 'security'] */
|
|
53
|
+
tags: string[];
|
|
54
|
+
/** Optional recurrence pattern: 'daily' for recurring daily habits */
|
|
55
|
+
recurrence?: 'daily';
|
|
56
|
+
/** Last completed date string 'YYYY-MM-DD' for recurring tasks */
|
|
57
|
+
lastCompletedDate?: string;
|
|
58
|
+
/** Decay policy overrides */
|
|
59
|
+
decay: AnchorDecayPolicy;
|
|
60
|
+
/** Settlement evidence metadata upon closure */
|
|
61
|
+
evidence?: AnchorEvidence;
|
|
62
|
+
}
|
|
63
|
+
interface AnchorStoreState {
|
|
64
|
+
version: number;
|
|
65
|
+
anchors: Anchor[];
|
|
66
|
+
lastSweepAt: number;
|
|
67
|
+
nextId?: number;
|
|
68
|
+
}
|
|
69
|
+
interface TouchMatchResult {
|
|
70
|
+
anchor: Anchor;
|
|
71
|
+
score: number;
|
|
72
|
+
matchedFiles: string[];
|
|
73
|
+
reason: 'exact-file' | 'dir-prefix' | 'tag-keyword';
|
|
74
|
+
}
|
|
75
|
+
interface SettlementProposal {
|
|
76
|
+
anchor: Anchor;
|
|
77
|
+
matchedFiles: string[];
|
|
78
|
+
reason: string;
|
|
79
|
+
recommendedAction: 'settle' | 'defer';
|
|
80
|
+
}
|
|
81
|
+
declare const EPHEMERAL_DECAY_POLICY: AnchorDecayPolicy;
|
|
82
|
+
declare const DURABLE_DECAY_POLICY: AnchorDecayPolicy;
|
|
83
|
+
declare const DEFAULT_DECAY_POLICY: AnchorDecayPolicy;
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Detect whether a task is a short-term ephemeral reminder or a long-term architectural vision
|
|
87
|
+
*/
|
|
88
|
+
declare function detectDurability(title: string): AnchorDurability;
|
|
89
|
+
/**
|
|
90
|
+
* Get custom ephemeral decay policy based on temporal span.
|
|
91
|
+
* Note: graveyardDays is always 9999 so unfinished tasks are NEVER silently deleted.
|
|
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
|
+
export { AnchorStore as A, DEFAULT_DECAY_POLICY as D, EPHEMERAL_DECAY_POLICY as E, type SettlementProposal as S, type TouchMatchResult as T, type AnchorStatus as a, type Anchor as b, type AnchorDecayPolicy as c, type AnchorDurability as d, type AnchorEvidence as e, type AnchorPriority as f, type AnchorStoreState as g, DURABLE_DECAY_POLICY as h, acquireSyncLock as i, atomicRenameWithRetry as j, detectDurability as k, detectRecurrence as l, detectTargetDate as m, getDefaultStorageDir as n, getEphemeralDecayPolicy as o, getTodayDateString as p };
|