pi-anchor 0.3.0 → 0.4.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,167 @@
1
+ import { ExtensionAPI } from '@earendil-works/pi-coding-agent';
2
+
3
+ /**
4
+ * Anchor Data Contracts
5
+ * Minimalist cross-session sticky notes stored in ~/.anchor/state.json
6
+ */
7
+ type AnchorStatus = 'active' | 'sleeping' | 'settled';
8
+ type AnchorPriority = 'p0' | 'p1' | 'p2';
9
+ interface AnchorEvidence {
10
+ settledAt?: number;
11
+ settledBy?: 'commit' | 'manual' | 'one-tap-settlement' | 'manual-command' | 'verification-test';
12
+ commitHash?: string;
13
+ summary?: string;
14
+ touchedFiles?: string[];
15
+ }
16
+ interface Anchor {
17
+ /** Sequential identifier, e.g. 'anc-1' */
18
+ id: string;
19
+ /** Sticky note text */
20
+ title: string;
21
+ /** Optional extra context */
22
+ description?: string;
23
+ /** Lifecycle status: 'active' (recent) or 'sleeping' (>7d untouched, 0 prompt tokens) */
24
+ status: AnchorStatus;
25
+ /** Short workspace label (e.g. 'anchor', 'College/优化', or 'global') */
26
+ project: string;
27
+ /** Workspace directory path where the note applies */
28
+ cwd: string;
29
+ /** Creation timestamp (ms) */
30
+ createdAt: number;
31
+ /** Last update timestamp (ms) */
32
+ updatedAt: number;
33
+ /** Last activity timestamp (ms) */
34
+ lastTouchedAt: number;
35
+ /** Associated file paths or globs (explicit or auto-extracted from note text) */
36
+ files: string[];
37
+ /** Optional tags */
38
+ tags: string[];
39
+ /** Optional due date ('YYYY-MM-DD') */
40
+ targetDate?: string;
41
+ /** Optional priority kept for backward compatibility */
42
+ priority?: AnchorPriority;
43
+ /** Settlement metadata when archived */
44
+ evidence?: AnchorEvidence;
45
+ }
46
+ interface AnchorStoreState {
47
+ version: number;
48
+ anchors: Anchor[];
49
+ lastSweepAt: number;
50
+ nextId?: number;
51
+ }
52
+ interface TouchMatchResult {
53
+ anchor: Anchor;
54
+ score: number;
55
+ matchedFiles: string[];
56
+ reason: 'exact-file' | 'dir-prefix';
57
+ }
58
+ /** 7 days without activity moves a note to quiet mode (still visible in UI, 0 prompt tokens in unrelated workspaces) */
59
+ declare const QUIET_AFTER_DAYS = 7;
60
+ declare const MS_PER_DAY = 86400000;
61
+
62
+ /**
63
+ * Normalize path separators to forward slashes and strip baseCwd prefix when inside baseCwd
64
+ */
65
+ declare function normalizePath(filePath: string, baseCwd?: string): string;
66
+ /**
67
+ * Safe boundary-aware directory comparison (case-insensitive on Windows)
68
+ */
69
+ declare function isSameOrSubPath(dirA?: string, dirB?: string): boolean;
70
+ /**
71
+ * Convert simple glob pattern (*.ts, src/**\/*.ts) to safe RegExp
72
+ */
73
+ declare function globToRegExp(pattern: string): RegExp;
74
+ /**
75
+ * Clever Touch 1: Automatically spot file or directory paths mentioned inside a sticky note
76
+ * (e.g. "src/auth.ts", "D:/College/优化/notes.md") so the user never has to fill out forms.
77
+ */
78
+ declare function extractPathsFromText(text?: string): string[];
79
+ /**
80
+ * Check if a sticky note belongs to or targets the given workspace directory
81
+ */
82
+ declare function isAnchorTargetingWorkspace(anchor: Anchor, targetCwd?: string): boolean;
83
+ /**
84
+ * Match touched files against an anchor's file patterns
85
+ */
86
+ declare function matchAnchorAgainstTouchedFiles(anchor: Anchor, touchedFiles: string[]): TouchMatchResult | null;
87
+ declare function findMatchedAnchors(anchors: Anchor[], touchedFiles: string[]): TouchMatchResult[];
88
+ /**
89
+ * Clever Touch 2: Syntax-safe 1-line comment whisper when the AI reads/edits a matching file.
90
+ * Strictly skips JSON, ENV, lockfiles, and binaries so parsers are never broken.
91
+ */
92
+ declare const COMMENT_FORMATS: Record<string, (msg: string) => string>;
93
+ declare function makeSafeTaskAnnotation(filePath: string, anchor: Anchor): string | null;
94
+ /**
95
+ * Clever Touch 3: Match a `git commit -m "..."` message against a sticky note (supports CJK & English)
96
+ */
97
+ declare function matchesCommitMessage(anchor: Anchor, commitMessages: string[]): {
98
+ matched: boolean;
99
+ message?: string;
100
+ };
101
+ /**
102
+ * Render ultra-compact Turn-1 sticky note context for the AI.
103
+ * Turn 2+ consumes 0 tokens.
104
+ */
105
+ declare function renderColdStartAnchorsContext(store: AnchorStore, cwdOrNow?: string | number, nowArg?: number): string;
106
+ declare const renderActiveAnchorsContext: typeof renderColdStartAnchorsContext;
107
+ /**
108
+ * Transition notes untouched for >7 days to 'sleeping' (quiet mode) so they don't burn Turn-1 tokens
109
+ * in unrelated sessions, while NEVER deleting them.
110
+ */
111
+ declare function sweepQuietNotes(store: AnchorStore, now?: number): {
112
+ quieted: string[];
113
+ };
114
+ declare function getDefaultStorageDir(): string;
115
+ declare function acquireSyncLock(lockPath: string, maxWaitMs?: number, staleTimeoutMs?: number): () => void;
116
+ declare function atomicRenameWithRetry(tempPath: string, targetPath: string, maxRetries?: number): void;
117
+ declare class AnchorStore {
118
+ readonly storageDir: string;
119
+ readonly statePath: string;
120
+ readonly lockPath: string;
121
+ readonly archivePath: string;
122
+ constructor(storageDir?: string);
123
+ private ensureDirs;
124
+ withLock<T>(fn: () => T): T;
125
+ loadState(): AnchorStoreState;
126
+ saveState(state: AnchorStoreState): void;
127
+ private generateId;
128
+ /**
129
+ * Pin a new sticky note
130
+ */
131
+ create(input: {
132
+ title: string;
133
+ description?: string;
134
+ priority?: AnchorPriority;
135
+ cwd?: string;
136
+ project?: string;
137
+ files?: string[];
138
+ tags?: string[];
139
+ }): Anchor;
140
+ private findAnchorIndex;
141
+ get(id: string): Anchor | undefined;
142
+ list(filter?: {
143
+ status?: AnchorStatus;
144
+ cwd?: string;
145
+ all?: boolean;
146
+ }): Anchor[];
147
+ update(id: string, patch: Partial<Omit<Anchor, 'id' | 'createdAt'>>): Anchor;
148
+ touch(id: string, timestamp?: number): Anchor;
149
+ /**
150
+ * Tear off (settle) a completed sticky note and archive it to archive.jsonl
151
+ */
152
+ settle(id: string, evidence?: AnchorEvidence, now?: number): Anchor;
153
+ /**
154
+ * Put back (undo) the last torn-off sticky note
155
+ */
156
+ undoSettle(): Anchor;
157
+ }
158
+
159
+ declare function parseAnchorCommandInput(raw: string): {
160
+ mode: 'dashboard' | 'undo' | 'settle' | 'pin';
161
+ targetId?: string;
162
+ title?: string;
163
+ };
164
+ declare function resolveAnchorTarget(store: AnchorStore, idOrIndex: string, cwd?: string): string | undefined;
165
+ declare function export_default(pi: ExtensionAPI): void;
166
+
167
+ export { type Anchor as A, COMMENT_FORMATS as C, MS_PER_DAY as M, QUIET_AFTER_DAYS as Q, type TouchMatchResult as T, AnchorStore as a, type AnchorEvidence as b, type AnchorPriority as c, type AnchorStatus as d, type AnchorStoreState as e, acquireSyncLock as f, atomicRenameWithRetry as g, extractPathsFromText as h, findMatchedAnchors as i, getDefaultStorageDir as j, globToRegExp as k, isAnchorTargetingWorkspace as l, isSameOrSubPath as m, makeSafeTaskAnnotation as n, matchAnchorAgainstTouchedFiles as o, matchesCommitMessage as p, normalizePath as q, parseAnchorCommandInput as r, renderActiveAnchorsContext as s, renderColdStartAnchorsContext as t, resolveAnchorTarget as u, sweepQuietNotes as v, export_default as w };
@@ -0,0 +1,167 @@
1
+ import { ExtensionAPI } from '@earendil-works/pi-coding-agent';
2
+
3
+ /**
4
+ * Anchor Data Contracts
5
+ * Minimalist cross-session sticky notes stored in ~/.anchor/state.json
6
+ */
7
+ type AnchorStatus = 'active' | 'sleeping' | 'settled';
8
+ type AnchorPriority = 'p0' | 'p1' | 'p2';
9
+ interface AnchorEvidence {
10
+ settledAt?: number;
11
+ settledBy?: 'commit' | 'manual' | 'one-tap-settlement' | 'manual-command' | 'verification-test';
12
+ commitHash?: string;
13
+ summary?: string;
14
+ touchedFiles?: string[];
15
+ }
16
+ interface Anchor {
17
+ /** Sequential identifier, e.g. 'anc-1' */
18
+ id: string;
19
+ /** Sticky note text */
20
+ title: string;
21
+ /** Optional extra context */
22
+ description?: string;
23
+ /** Lifecycle status: 'active' (recent) or 'sleeping' (>7d untouched, 0 prompt tokens) */
24
+ status: AnchorStatus;
25
+ /** Short workspace label (e.g. 'anchor', 'College/优化', or 'global') */
26
+ project: string;
27
+ /** Workspace directory path where the note applies */
28
+ cwd: string;
29
+ /** Creation timestamp (ms) */
30
+ createdAt: number;
31
+ /** Last update timestamp (ms) */
32
+ updatedAt: number;
33
+ /** Last activity timestamp (ms) */
34
+ lastTouchedAt: number;
35
+ /** Associated file paths or globs (explicit or auto-extracted from note text) */
36
+ files: string[];
37
+ /** Optional tags */
38
+ tags: string[];
39
+ /** Optional due date ('YYYY-MM-DD') */
40
+ targetDate?: string;
41
+ /** Optional priority kept for backward compatibility */
42
+ priority?: AnchorPriority;
43
+ /** Settlement metadata when archived */
44
+ evidence?: AnchorEvidence;
45
+ }
46
+ interface AnchorStoreState {
47
+ version: number;
48
+ anchors: Anchor[];
49
+ lastSweepAt: number;
50
+ nextId?: number;
51
+ }
52
+ interface TouchMatchResult {
53
+ anchor: Anchor;
54
+ score: number;
55
+ matchedFiles: string[];
56
+ reason: 'exact-file' | 'dir-prefix';
57
+ }
58
+ /** 7 days without activity moves a note to quiet mode (still visible in UI, 0 prompt tokens in unrelated workspaces) */
59
+ declare const QUIET_AFTER_DAYS = 7;
60
+ declare const MS_PER_DAY = 86400000;
61
+
62
+ /**
63
+ * Normalize path separators to forward slashes and strip baseCwd prefix when inside baseCwd
64
+ */
65
+ declare function normalizePath(filePath: string, baseCwd?: string): string;
66
+ /**
67
+ * Safe boundary-aware directory comparison (case-insensitive on Windows)
68
+ */
69
+ declare function isSameOrSubPath(dirA?: string, dirB?: string): boolean;
70
+ /**
71
+ * Convert simple glob pattern (*.ts, src/**\/*.ts) to safe RegExp
72
+ */
73
+ declare function globToRegExp(pattern: string): RegExp;
74
+ /**
75
+ * Clever Touch 1: Automatically spot file or directory paths mentioned inside a sticky note
76
+ * (e.g. "src/auth.ts", "D:/College/优化/notes.md") so the user never has to fill out forms.
77
+ */
78
+ declare function extractPathsFromText(text?: string): string[];
79
+ /**
80
+ * Check if a sticky note belongs to or targets the given workspace directory
81
+ */
82
+ declare function isAnchorTargetingWorkspace(anchor: Anchor, targetCwd?: string): boolean;
83
+ /**
84
+ * Match touched files against an anchor's file patterns
85
+ */
86
+ declare function matchAnchorAgainstTouchedFiles(anchor: Anchor, touchedFiles: string[]): TouchMatchResult | null;
87
+ declare function findMatchedAnchors(anchors: Anchor[], touchedFiles: string[]): TouchMatchResult[];
88
+ /**
89
+ * Clever Touch 2: Syntax-safe 1-line comment whisper when the AI reads/edits a matching file.
90
+ * Strictly skips JSON, ENV, lockfiles, and binaries so parsers are never broken.
91
+ */
92
+ declare const COMMENT_FORMATS: Record<string, (msg: string) => string>;
93
+ declare function makeSafeTaskAnnotation(filePath: string, anchor: Anchor): string | null;
94
+ /**
95
+ * Clever Touch 3: Match a `git commit -m "..."` message against a sticky note (supports CJK & English)
96
+ */
97
+ declare function matchesCommitMessage(anchor: Anchor, commitMessages: string[]): {
98
+ matched: boolean;
99
+ message?: string;
100
+ };
101
+ /**
102
+ * Render ultra-compact Turn-1 sticky note context for the AI.
103
+ * Turn 2+ consumes 0 tokens.
104
+ */
105
+ declare function renderColdStartAnchorsContext(store: AnchorStore, cwdOrNow?: string | number, nowArg?: number): string;
106
+ declare const renderActiveAnchorsContext: typeof renderColdStartAnchorsContext;
107
+ /**
108
+ * Transition notes untouched for >7 days to 'sleeping' (quiet mode) so they don't burn Turn-1 tokens
109
+ * in unrelated sessions, while NEVER deleting them.
110
+ */
111
+ declare function sweepQuietNotes(store: AnchorStore, now?: number): {
112
+ quieted: string[];
113
+ };
114
+ declare function getDefaultStorageDir(): string;
115
+ declare function acquireSyncLock(lockPath: string, maxWaitMs?: number, staleTimeoutMs?: number): () => void;
116
+ declare function atomicRenameWithRetry(tempPath: string, targetPath: string, maxRetries?: number): void;
117
+ declare class AnchorStore {
118
+ readonly storageDir: string;
119
+ readonly statePath: string;
120
+ readonly lockPath: string;
121
+ readonly archivePath: string;
122
+ constructor(storageDir?: string);
123
+ private ensureDirs;
124
+ withLock<T>(fn: () => T): T;
125
+ loadState(): AnchorStoreState;
126
+ saveState(state: AnchorStoreState): void;
127
+ private generateId;
128
+ /**
129
+ * Pin a new sticky note
130
+ */
131
+ create(input: {
132
+ title: string;
133
+ description?: string;
134
+ priority?: AnchorPriority;
135
+ cwd?: string;
136
+ project?: string;
137
+ files?: string[];
138
+ tags?: string[];
139
+ }): Anchor;
140
+ private findAnchorIndex;
141
+ get(id: string): Anchor | undefined;
142
+ list(filter?: {
143
+ status?: AnchorStatus;
144
+ cwd?: string;
145
+ all?: boolean;
146
+ }): Anchor[];
147
+ update(id: string, patch: Partial<Omit<Anchor, 'id' | 'createdAt'>>): Anchor;
148
+ touch(id: string, timestamp?: number): Anchor;
149
+ /**
150
+ * Tear off (settle) a completed sticky note and archive it to archive.jsonl
151
+ */
152
+ settle(id: string, evidence?: AnchorEvidence, now?: number): Anchor;
153
+ /**
154
+ * Put back (undo) the last torn-off sticky note
155
+ */
156
+ undoSettle(): Anchor;
157
+ }
158
+
159
+ declare function parseAnchorCommandInput(raw: string): {
160
+ mode: 'dashboard' | 'undo' | 'settle' | 'pin';
161
+ targetId?: string;
162
+ title?: string;
163
+ };
164
+ declare function resolveAnchorTarget(store: AnchorStore, idOrIndex: string, cwd?: string): string | undefined;
165
+ declare function export_default(pi: ExtensionAPI): void;
166
+
167
+ export { type Anchor as A, COMMENT_FORMATS as C, MS_PER_DAY as M, QUIET_AFTER_DAYS as Q, type TouchMatchResult as T, AnchorStore as a, type AnchorEvidence as b, type AnchorPriority as c, type AnchorStatus as d, type AnchorStoreState as e, acquireSyncLock as f, atomicRenameWithRetry as g, extractPathsFromText as h, findMatchedAnchors as i, getDefaultStorageDir as j, globToRegExp as k, isAnchorTargetingWorkspace as l, isSameOrSubPath as m, makeSafeTaskAnnotation as n, matchAnchorAgainstTouchedFiles as o, matchesCommitMessage as p, normalizePath as q, parseAnchorCommandInput as r, renderActiveAnchorsContext as s, renderColdStartAnchorsContext as t, resolveAnchorTarget as u, sweepQuietNotes as v, export_default as w };