fluffy-context 0.1.0 → 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/LICENSE +21 -0
- package/README.md +86 -136
- package/dist/src/agent/api.d.ts +8 -0
- package/dist/src/agent/api.js +285 -0
- package/dist/src/agent/index.d.ts +3 -0
- package/dist/src/agent/index.js +1 -0
- package/dist/src/agent/types.d.ts +50 -0
- package/dist/src/agent/types.js +1 -0
- package/dist/src/capture/context-filter.d.ts +7 -0
- package/dist/src/cli/main.d.ts +2 -0
- package/dist/src/cli/main.js +382 -30
- package/dist/src/git/git-adapter.d.ts +2 -0
- package/dist/src/hooks/claude-code.d.ts +1 -0
- package/dist/src/hooks/claude-code.js +84 -0
- package/dist/src/integrations/claude-code.d.ts +19 -0
- package/dist/src/integrations/claude-code.js +122 -0
- package/dist/src/mcp/main.d.ts +1 -0
- package/dist/src/mcp/main.js +5 -0
- package/dist/src/mcp/server.d.ts +2 -0
- package/dist/src/mcp/server.js +40 -0
- package/dist/src/project/project-resolver.d.ts +1 -0
- package/dist/src/runtime/diagnostics.d.ts +6 -0
- package/dist/src/runtime/diagnostics.js +34 -5
- package/dist/src/runtime/init.d.ts +7 -0
- package/dist/src/runtime/knowledge.d.ts +9 -0
- package/dist/src/runtime/knowledge.js +179 -12
- package/dist/src/runtime/notes.d.ts +6 -0
- package/dist/src/runtime/notes.js +173 -0
- package/dist/src/runtime/runtime.d.ts +11 -0
- package/dist/src/runtime/runtime.js +48 -18
- package/dist/src/runtime/types.d.ts +294 -0
- package/dist/src/storage/atomic-write.d.ts +1 -0
- package/dist/src/storage/json-store.d.ts +6 -0
- package/dist/src/storage/layout.d.ts +13 -0
- package/dist/src/storage/layout.js +3 -0
- package/dist/src/storage/lock.d.ts +1 -0
- package/dist/src/version.d.ts +1 -0
- package/dist/src/version.js +1 -0
- package/package.json +48 -11
- package/skills/fluffy-context/SKILL.md +345 -0
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
|
+
import { access, readdir } from 'node:fs/promises';
|
|
3
|
+
import { resolveProjectRoot } from '../project/project-resolver.js';
|
|
4
|
+
import { filterPaths } from '../capture/context-filter.js';
|
|
5
|
+
import { atomicWriteJson } from '../storage/atomic-write.js';
|
|
6
|
+
import { isRecord, readJson } from '../storage/json-store.js';
|
|
7
|
+
import { contextMetadataPath, contextsRoot, locksDirectory, notesPath, snapshotPath, snapshotsDirectory } from '../storage/layout.js';
|
|
8
|
+
import { withLock } from '../storage/lock.js';
|
|
9
|
+
function id() {
|
|
10
|
+
return `note-${Date.now().toString(36)}-${crypto.randomBytes(4).toString('hex')}`;
|
|
11
|
+
}
|
|
12
|
+
function isNotFound(error) {
|
|
13
|
+
return typeof error === 'object' && error !== null && 'code' in error && error.code === 'ENOENT';
|
|
14
|
+
}
|
|
15
|
+
function isNote(value) {
|
|
16
|
+
return isRecord(value)
|
|
17
|
+
&& value.schemaVersion === 1
|
|
18
|
+
&& typeof value.noteId === 'string'
|
|
19
|
+
&& ['problem', 'action', 'observation', 'decision'].includes(value.kind)
|
|
20
|
+
&& typeof value.message === 'string'
|
|
21
|
+
&& value.message.trim().length > 0
|
|
22
|
+
&& ['agent', 'human'].includes(value.actor)
|
|
23
|
+
&& (typeof value.contextId === 'string' || value.contextId === null)
|
|
24
|
+
&& (typeof value.snapshotId === 'string' || value.snapshotId === null)
|
|
25
|
+
&& Array.isArray(value.relatedFiles) && value.relatedFiles.every((item) => typeof item === 'string')
|
|
26
|
+
&& ['open', 'resolved'].includes(value.status)
|
|
27
|
+
&& (typeof value.absorbedBySnapshotId === 'string' || value.absorbedBySnapshotId === null)
|
|
28
|
+
&& typeof value.createdAt === 'string'
|
|
29
|
+
&& typeof value.updatedAt === 'string';
|
|
30
|
+
}
|
|
31
|
+
function isNoteFile(value) {
|
|
32
|
+
return isRecord(value) && value.schemaVersion === 1 && Array.isArray(value.items) && value.items.every(isNote);
|
|
33
|
+
}
|
|
34
|
+
async function loadNotes(projectRoot) {
|
|
35
|
+
try {
|
|
36
|
+
return await readJson(notesPath(projectRoot), isNoteFile);
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
if (isNotFound(error))
|
|
40
|
+
return { schemaVersion: 1, items: [] };
|
|
41
|
+
throw error;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
async function validateSource(projectRoot, contextId, snapshotId) {
|
|
45
|
+
if (!contextId && snapshotId)
|
|
46
|
+
throw new Error('source snapshot requires source context');
|
|
47
|
+
if (!contextId)
|
|
48
|
+
return;
|
|
49
|
+
await access(contextMetadataPath(projectRoot, contextId));
|
|
50
|
+
if (snapshotId)
|
|
51
|
+
await access(snapshotPath(projectRoot, contextId, snapshotId));
|
|
52
|
+
}
|
|
53
|
+
function limited(value, budget) {
|
|
54
|
+
return value.length <= budget ? value : value.slice(0, Math.max(0, budget - 1)) + '…';
|
|
55
|
+
}
|
|
56
|
+
export async function addNote(startPath, message, input = {}) {
|
|
57
|
+
const projectRoot = await resolveProjectRoot(startPath);
|
|
58
|
+
const normalizedMessage = message.trim();
|
|
59
|
+
if (!normalizedMessage)
|
|
60
|
+
throw new Error('note message must not be empty');
|
|
61
|
+
await validateSource(projectRoot, input.contextId, input.snapshotId);
|
|
62
|
+
const relatedFiles = await filterPaths(projectRoot, (input.relatedFiles ?? []).map((item) => item.trim()).filter(Boolean));
|
|
63
|
+
return withLock(`${locksDirectory(projectRoot)}/notes.lock`, async () => {
|
|
64
|
+
const file = await loadNotes(projectRoot);
|
|
65
|
+
const now = new Date().toISOString();
|
|
66
|
+
const note = {
|
|
67
|
+
noteId: id(),
|
|
68
|
+
schemaVersion: 1,
|
|
69
|
+
kind: input.kind ?? 'observation',
|
|
70
|
+
message: normalizedMessage,
|
|
71
|
+
actor: input.actor ?? 'agent',
|
|
72
|
+
contextId: input.contextId ?? null,
|
|
73
|
+
snapshotId: input.snapshotId ?? null,
|
|
74
|
+
relatedFiles,
|
|
75
|
+
status: input.status ?? 'open',
|
|
76
|
+
absorbedBySnapshotId: null,
|
|
77
|
+
createdAt: now,
|
|
78
|
+
updatedAt: now,
|
|
79
|
+
};
|
|
80
|
+
await atomicWriteJson(notesPath(projectRoot), { schemaVersion: 1, items: [...file.items, note] });
|
|
81
|
+
return note;
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
export async function listNotes(startPath, query = {}) {
|
|
85
|
+
const projectRoot = await resolveProjectRoot(startPath);
|
|
86
|
+
const file = await loadNotes(projectRoot);
|
|
87
|
+
const limit = query.limit ?? 50;
|
|
88
|
+
const maxChars = query.maxChars ?? 500;
|
|
89
|
+
return file.items
|
|
90
|
+
.filter((note) => !query.contextId || note.contextId === query.contextId)
|
|
91
|
+
.filter((note) => !query.openOnly || (note.status === 'open' && note.absorbedBySnapshotId === null))
|
|
92
|
+
.sort((left, right) => right.createdAt.localeCompare(left.createdAt))
|
|
93
|
+
.slice(0, limit)
|
|
94
|
+
.map((note) => ({ ...note, message: limited(note.message, maxChars) }));
|
|
95
|
+
}
|
|
96
|
+
function noteActivity(note) {
|
|
97
|
+
if (!note.contextId)
|
|
98
|
+
return null;
|
|
99
|
+
return {
|
|
100
|
+
type: 'note',
|
|
101
|
+
occurredAt: note.createdAt,
|
|
102
|
+
contextId: note.contextId,
|
|
103
|
+
snapshotId: note.snapshotId ?? undefined,
|
|
104
|
+
noteId: note.noteId,
|
|
105
|
+
kind: note.kind,
|
|
106
|
+
message: note.message,
|
|
107
|
+
actor: note.actor,
|
|
108
|
+
status: note.status,
|
|
109
|
+
absorbedBySnapshotId: note.absorbedBySnapshotId,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
async function checkpointActivities(projectRoot, contextId) {
|
|
113
|
+
const items = [];
|
|
114
|
+
let contextIds;
|
|
115
|
+
try {
|
|
116
|
+
contextIds = await readdir(contextsRoot(projectRoot));
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
contextIds = [];
|
|
120
|
+
}
|
|
121
|
+
for (const id of contextIds) {
|
|
122
|
+
if (contextId && id !== contextId)
|
|
123
|
+
continue;
|
|
124
|
+
try {
|
|
125
|
+
const context = await readJson(contextMetadataPath(projectRoot, id), (value) => isRecord(value) && typeof value.id === 'string' && typeof value.title === 'string' && (typeof value.currentSnapshotId === 'string' || value.currentSnapshotId === null));
|
|
126
|
+
if (!context.currentSnapshotId)
|
|
127
|
+
continue;
|
|
128
|
+
const snapshots = await readdir(snapshotsDirectory(projectRoot, id));
|
|
129
|
+
for (const fileName of snapshots.filter((name) => name.endsWith('.json'))) {
|
|
130
|
+
const snapshot = await readJson(snapshotPath(projectRoot, id, fileName.slice(0, -5)), (value) => isRecord(value) && value.schemaVersion === 1 && typeof value.snapshotId === 'string' && typeof value.createdAt === 'string' && (value.mode === 'baseline' || value.mode === 'patch'));
|
|
131
|
+
items.push({ type: 'checkpoint', occurredAt: snapshot.createdAt, contextId: id, snapshotId: snapshot.snapshotId, title: context.title });
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
// Doctor reports malformed activity sources.
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return items;
|
|
139
|
+
}
|
|
140
|
+
export async function listActivity(startPath, query = {}) {
|
|
141
|
+
const projectRoot = await resolveProjectRoot(startPath);
|
|
142
|
+
const notes = await loadNotes(projectRoot);
|
|
143
|
+
const noteItems = notes.items.map(noteActivity).filter((item) => item !== null);
|
|
144
|
+
const checkpointItems = await checkpointActivities(projectRoot, query.contextId);
|
|
145
|
+
const maxChars = query.maxChars ?? 500;
|
|
146
|
+
const items = [...noteItems, ...checkpointItems]
|
|
147
|
+
.filter((item) => !query.contextId || item.contextId === query.contextId)
|
|
148
|
+
.filter((item) => !query.since || item.occurredAt >= query.since)
|
|
149
|
+
.filter((item) => !query.openOnly || (item.type === 'note' && item.status === 'open' && item.absorbedBySnapshotId === null))
|
|
150
|
+
.sort((left, right) => right.occurredAt.localeCompare(left.occurredAt))
|
|
151
|
+
.slice(0, query.limit ?? 50)
|
|
152
|
+
.map((item) => item.message ? { ...item, message: limited(item.message, maxChars) } : item);
|
|
153
|
+
return { projectRoot, items };
|
|
154
|
+
}
|
|
155
|
+
export async function absorbNotes(startPath, contextId, snapshotId) {
|
|
156
|
+
const projectRoot = await resolveProjectRoot(startPath);
|
|
157
|
+
return withLock(`${locksDirectory(projectRoot)}/notes.lock`, async () => {
|
|
158
|
+
const file = await loadNotes(projectRoot);
|
|
159
|
+
let absorbed = 0;
|
|
160
|
+
const items = file.items.map((note) => {
|
|
161
|
+
if (note.contextId !== contextId || note.absorbedBySnapshotId !== null)
|
|
162
|
+
return note;
|
|
163
|
+
absorbed += 1;
|
|
164
|
+
return { ...note, absorbedBySnapshotId: snapshotId, updatedAt: new Date().toISOString() };
|
|
165
|
+
});
|
|
166
|
+
if (absorbed > 0)
|
|
167
|
+
await atomicWriteJson(notesPath(projectRoot), { schemaVersion: 1, items });
|
|
168
|
+
return absorbed;
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
export function isValidNote(value) {
|
|
172
|
+
return isNote(value);
|
|
173
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { CheckpointInput, CheckpointResult, IndexFile, ResumeResult, StructuredContext } from './types.js';
|
|
2
|
+
export interface CheckpointOptions {
|
|
3
|
+
minSaveIntervalMs?: number;
|
|
4
|
+
}
|
|
5
|
+
export declare function rebuildSnapshot(projectRoot: string, contextId: string, snapshotId: string, seen?: Set<string>): Promise<StructuredContext>;
|
|
6
|
+
export declare function rebuildIndex(projectRoot: string, persist?: boolean): Promise<IndexFile>;
|
|
7
|
+
export declare function checkpoint(startPath: string | undefined, input: CheckpointInput, options?: CheckpointOptions): Promise<CheckpointResult>;
|
|
8
|
+
export interface ResumeOptions {
|
|
9
|
+
touchLastUsedAt?: boolean;
|
|
10
|
+
}
|
|
11
|
+
export declare function resume(startPath?: string, contextId?: string, maxChars?: number, options?: ResumeOptions): Promise<ResumeResult>;
|
|
@@ -8,6 +8,7 @@ import { withLock } from '../storage/lock.js';
|
|
|
8
8
|
import { contextDirectory, contextMetadataPath, contextsRoot, indexPath, locksDirectory, snapshotPath, snapshotsDirectory, } from '../storage/layout.js';
|
|
9
9
|
import { loadManifest } from './init.js';
|
|
10
10
|
import { filterPaths } from '../capture/context-filter.js';
|
|
11
|
+
import { absorbNotes } from './notes.js';
|
|
11
12
|
const emptyContext = () => ({
|
|
12
13
|
progressSummary: '',
|
|
13
14
|
lastError: null,
|
|
@@ -46,6 +47,29 @@ function limitedList(values, budget) {
|
|
|
46
47
|
function applyPatch(base, patch) {
|
|
47
48
|
return { ...base, ...patch };
|
|
48
49
|
}
|
|
50
|
+
function isContextPatch(value) {
|
|
51
|
+
if (!isRecord(value))
|
|
52
|
+
return false;
|
|
53
|
+
const fields = ['progressSummary', 'lastError', 'completed', 'pendingTasks', 'decisions', 'risks', 'relatedFiles'];
|
|
54
|
+
if (Object.keys(value).some((key) => !fields.includes(key)))
|
|
55
|
+
return false;
|
|
56
|
+
return fields.every((field) => {
|
|
57
|
+
if (!(field in value))
|
|
58
|
+
return true;
|
|
59
|
+
const fieldValue = value[field];
|
|
60
|
+
if (field === 'lastError')
|
|
61
|
+
return typeof fieldValue === 'string' || fieldValue === null;
|
|
62
|
+
if (field === 'progressSummary')
|
|
63
|
+
return typeof fieldValue === 'string';
|
|
64
|
+
return isStringArray(fieldValue);
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
function normalizeText(value) {
|
|
68
|
+
return value.trim();
|
|
69
|
+
}
|
|
70
|
+
function normalizeList(values) {
|
|
71
|
+
return values.map(normalizeText).filter(Boolean);
|
|
72
|
+
}
|
|
49
73
|
function diffContext(previous, current) {
|
|
50
74
|
const patch = {};
|
|
51
75
|
if (previous.progressSummary !== current.progressSummary)
|
|
@@ -65,16 +89,17 @@ function diffContext(previous, current) {
|
|
|
65
89
|
return patch;
|
|
66
90
|
}
|
|
67
91
|
async function contextFromInput(projectRoot, input, previous) {
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
92
|
+
const progressSummary = input.progressSummary === undefined ? previous?.progressSummary ?? '' : normalizeText(input.progressSummary);
|
|
93
|
+
const lastError = input.lastError === undefined
|
|
94
|
+
? previous?.lastError ?? null
|
|
95
|
+
: normalizeText(input.lastError ?? '') || null;
|
|
96
|
+
const completed = input.completed === undefined ? previous?.completed ?? [] : normalizeList(input.completed);
|
|
97
|
+
const pendingTasks = input.pendingTasks === undefined ? previous?.pendingTasks ?? [] : normalizeList(input.pendingTasks);
|
|
98
|
+
const decisions = input.decisions === undefined ? previous?.decisions ?? [] : normalizeList(input.decisions);
|
|
99
|
+
const risks = input.risks === undefined ? previous?.risks ?? [] : normalizeList(input.risks);
|
|
100
|
+
const relatedFilesInput = input.relatedFiles === undefined ? previous?.relatedFiles ?? [] : normalizeList(input.relatedFiles);
|
|
101
|
+
const relatedFiles = await filterPaths(projectRoot, relatedFilesInput);
|
|
102
|
+
return { progressSummary, lastError, completed, pendingTasks, decisions, risks, relatedFiles };
|
|
78
103
|
}
|
|
79
104
|
function isStructuredContext(value) {
|
|
80
105
|
return isRecord(value)
|
|
@@ -126,7 +151,7 @@ async function loadSnapshot(projectRoot, contextId, snapshotId) {
|
|
|
126
151
|
}
|
|
127
152
|
return snapshot;
|
|
128
153
|
}
|
|
129
|
-
async function rebuildSnapshot(projectRoot, contextId, snapshotId, seen = new Set()) {
|
|
154
|
+
export async function rebuildSnapshot(projectRoot, contextId, snapshotId, seen = new Set()) {
|
|
130
155
|
if (seen.has(snapshotId))
|
|
131
156
|
throw new Error(`snapshot cycle detected at ${snapshotId}`);
|
|
132
157
|
seen.add(snapshotId);
|
|
@@ -139,7 +164,7 @@ async function rebuildSnapshot(projectRoot, contextId, snapshotId, seen = new Se
|
|
|
139
164
|
content = snapshot.content;
|
|
140
165
|
}
|
|
141
166
|
else {
|
|
142
|
-
if (!snapshot.parentSnapshotId || !snapshot.patch || !
|
|
167
|
+
if (!snapshot.parentSnapshotId || !snapshot.patch || !isContextPatch(snapshot.patch))
|
|
143
168
|
throw new Error(`incomplete patch snapshot ${snapshotId}`);
|
|
144
169
|
const parentSnapshot = await loadSnapshot(projectRoot, contextId, snapshot.parentSnapshotId);
|
|
145
170
|
if (parentSnapshot.baseSnapshotId !== snapshot.baseSnapshotId)
|
|
@@ -151,7 +176,7 @@ async function rebuildSnapshot(projectRoot, contextId, snapshotId, seen = new Se
|
|
|
151
176
|
}
|
|
152
177
|
return content;
|
|
153
178
|
}
|
|
154
|
-
export async function rebuildIndex(projectRoot) {
|
|
179
|
+
export async function rebuildIndex(projectRoot, persist = true) {
|
|
155
180
|
const contexts = [];
|
|
156
181
|
let entries = [];
|
|
157
182
|
try {
|
|
@@ -178,7 +203,8 @@ export async function rebuildIndex(projectRoot) {
|
|
|
178
203
|
}
|
|
179
204
|
}
|
|
180
205
|
const index = { schemaVersion: 1, rebuiltAt: new Date().toISOString(), contexts };
|
|
181
|
-
|
|
206
|
+
if (persist)
|
|
207
|
+
await atomicWriteJson(indexPath(projectRoot), index);
|
|
182
208
|
return index;
|
|
183
209
|
}
|
|
184
210
|
async function updateIndex(projectRoot, entry) {
|
|
@@ -279,14 +305,16 @@ export async function checkpoint(startPath, input, options = {}) {
|
|
|
279
305
|
currentSnapshotId: snapshotId,
|
|
280
306
|
updatedAt: now,
|
|
281
307
|
});
|
|
308
|
+
if (input.absorbNotes)
|
|
309
|
+
await absorbNotes(projectRoot, contextId, snapshotId);
|
|
282
310
|
return { status: 'saved', context: updatedContext, snapshot };
|
|
283
311
|
});
|
|
284
312
|
}
|
|
285
|
-
export async function resume(startPath, contextId, maxChars = 4000) {
|
|
313
|
+
export async function resume(startPath, contextId, maxChars = 4000, options = {}) {
|
|
286
314
|
const projectRoot = await resolveProjectRoot(startPath);
|
|
287
315
|
await loadManifest(projectRoot);
|
|
288
316
|
const git = await readGitState(projectRoot);
|
|
289
|
-
const index = await rebuildIndex(projectRoot);
|
|
317
|
+
const index = await rebuildIndex(projectRoot, options.touchLastUsedAt !== false);
|
|
290
318
|
const candidates = index.contexts
|
|
291
319
|
.filter((entry) => !contextId || entry.contextId === contextId)
|
|
292
320
|
.filter((entry) => ['active', 'stable'].includes(entry.status))
|
|
@@ -300,8 +328,10 @@ export async function resume(startPath, contextId, maxChars = 4000) {
|
|
|
300
328
|
const snapshot = await loadSnapshot(projectRoot, candidate.contextId, context.currentSnapshotId);
|
|
301
329
|
const content = await rebuildSnapshot(projectRoot, candidate.contextId, snapshot.snapshotId);
|
|
302
330
|
const drift = context.branch !== git.branch || context.commit !== git.commit;
|
|
303
|
-
|
|
304
|
-
|
|
331
|
+
if (options.touchLastUsedAt !== false) {
|
|
332
|
+
context.lastUsedAt = new Date().toISOString();
|
|
333
|
+
await atomicWriteJson(contextMetadataPath(projectRoot, context.id), context);
|
|
334
|
+
}
|
|
305
335
|
const budget = { remaining: Math.max(0, maxChars) };
|
|
306
336
|
const resumeSummary = {
|
|
307
337
|
progressSummary: limited(content.progressSummary, budget.remaining),
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
export type ContextStatus = 'draft' | 'active' | 'stable' | 'archived' | 'deleted';
|
|
2
|
+
export type SnapshotMode = 'baseline' | 'patch';
|
|
3
|
+
export interface GitState {
|
|
4
|
+
root: string | null;
|
|
5
|
+
branch: string | null;
|
|
6
|
+
commit: string | null;
|
|
7
|
+
}
|
|
8
|
+
export interface ContextMetadata {
|
|
9
|
+
id: string;
|
|
10
|
+
schemaVersion: 1;
|
|
11
|
+
title: string;
|
|
12
|
+
projectRoot: string;
|
|
13
|
+
branch: string | null;
|
|
14
|
+
commit: string | null;
|
|
15
|
+
createdAt: string;
|
|
16
|
+
updatedAt: string;
|
|
17
|
+
lastUsedAt: string | null;
|
|
18
|
+
usageCount: number;
|
|
19
|
+
status: ContextStatus;
|
|
20
|
+
currentSnapshotId: string | null;
|
|
21
|
+
tags: string[];
|
|
22
|
+
dependencies: string[];
|
|
23
|
+
}
|
|
24
|
+
export interface StructuredContext {
|
|
25
|
+
progressSummary: string;
|
|
26
|
+
lastError: string | null;
|
|
27
|
+
completed: string[];
|
|
28
|
+
pendingTasks: string[];
|
|
29
|
+
decisions: string[];
|
|
30
|
+
risks: string[];
|
|
31
|
+
relatedFiles: string[];
|
|
32
|
+
}
|
|
33
|
+
export type ContextPatch = Partial<StructuredContext>;
|
|
34
|
+
export interface Snapshot {
|
|
35
|
+
snapshotId: string;
|
|
36
|
+
schemaVersion: 1;
|
|
37
|
+
contextId: string;
|
|
38
|
+
parentSnapshotId: string | null;
|
|
39
|
+
baseSnapshotId: string;
|
|
40
|
+
mode: SnapshotMode;
|
|
41
|
+
createdAt: string;
|
|
42
|
+
branch: string | null;
|
|
43
|
+
commit: string | null;
|
|
44
|
+
contentHash: string;
|
|
45
|
+
rebuildHash: string;
|
|
46
|
+
content?: StructuredContext;
|
|
47
|
+
patch?: ContextPatch;
|
|
48
|
+
}
|
|
49
|
+
export interface Manifest {
|
|
50
|
+
schemaVersion: 1;
|
|
51
|
+
layoutVersion: 'compact-json-v1';
|
|
52
|
+
projectId: string;
|
|
53
|
+
projectRoot: string;
|
|
54
|
+
initializedAt: string;
|
|
55
|
+
}
|
|
56
|
+
export interface IndexEntry {
|
|
57
|
+
contextId: string;
|
|
58
|
+
title: string;
|
|
59
|
+
status: ContextStatus;
|
|
60
|
+
branch: string | null;
|
|
61
|
+
commit: string | null;
|
|
62
|
+
currentSnapshotId: string | null;
|
|
63
|
+
updatedAt: string;
|
|
64
|
+
}
|
|
65
|
+
export interface IndexFile {
|
|
66
|
+
schemaVersion: 1;
|
|
67
|
+
rebuiltAt: string;
|
|
68
|
+
contexts: IndexEntry[];
|
|
69
|
+
}
|
|
70
|
+
export interface CheckpointInput {
|
|
71
|
+
contextId?: string;
|
|
72
|
+
absorbNotes?: boolean;
|
|
73
|
+
title?: string;
|
|
74
|
+
progressSummary?: string;
|
|
75
|
+
lastError?: string | null;
|
|
76
|
+
completed?: string[];
|
|
77
|
+
pendingTasks?: string[];
|
|
78
|
+
decisions?: string[];
|
|
79
|
+
risks?: string[];
|
|
80
|
+
relatedFiles?: string[];
|
|
81
|
+
}
|
|
82
|
+
export type NoteKind = 'problem' | 'action' | 'observation' | 'decision';
|
|
83
|
+
export type NoteStatus = 'open' | 'resolved';
|
|
84
|
+
export type NoteActor = 'agent' | 'human';
|
|
85
|
+
export interface Note {
|
|
86
|
+
noteId: string;
|
|
87
|
+
schemaVersion: 1;
|
|
88
|
+
kind: NoteKind;
|
|
89
|
+
message: string;
|
|
90
|
+
actor: NoteActor;
|
|
91
|
+
contextId: string | null;
|
|
92
|
+
snapshotId: string | null;
|
|
93
|
+
relatedFiles: string[];
|
|
94
|
+
status: NoteStatus;
|
|
95
|
+
absorbedBySnapshotId: string | null;
|
|
96
|
+
createdAt: string;
|
|
97
|
+
updatedAt: string;
|
|
98
|
+
}
|
|
99
|
+
export interface NoteInput {
|
|
100
|
+
kind?: NoteKind;
|
|
101
|
+
actor?: NoteActor;
|
|
102
|
+
contextId?: string;
|
|
103
|
+
snapshotId?: string;
|
|
104
|
+
relatedFiles?: string[];
|
|
105
|
+
status?: NoteStatus;
|
|
106
|
+
}
|
|
107
|
+
export interface NoteFile {
|
|
108
|
+
schemaVersion: 1;
|
|
109
|
+
items: Note[];
|
|
110
|
+
}
|
|
111
|
+
export interface NoteQuery {
|
|
112
|
+
contextId?: string;
|
|
113
|
+
openOnly?: boolean;
|
|
114
|
+
limit?: number;
|
|
115
|
+
maxChars?: number;
|
|
116
|
+
}
|
|
117
|
+
export interface ActivityItem {
|
|
118
|
+
type: 'note' | 'checkpoint';
|
|
119
|
+
occurredAt: string;
|
|
120
|
+
contextId: string;
|
|
121
|
+
snapshotId?: string;
|
|
122
|
+
noteId?: string;
|
|
123
|
+
kind?: NoteKind;
|
|
124
|
+
message?: string;
|
|
125
|
+
actor?: NoteActor;
|
|
126
|
+
status?: NoteStatus;
|
|
127
|
+
absorbedBySnapshotId?: string | null;
|
|
128
|
+
title?: string;
|
|
129
|
+
}
|
|
130
|
+
export interface ActivityQuery extends NoteQuery {
|
|
131
|
+
since?: string;
|
|
132
|
+
}
|
|
133
|
+
export interface ActivityResult {
|
|
134
|
+
projectRoot: string;
|
|
135
|
+
items: ActivityItem[];
|
|
136
|
+
}
|
|
137
|
+
export type KnowledgeStatus = 'candidate' | 'verified' | 'deprecated' | 'rejected';
|
|
138
|
+
export type DeadendStatus = 'candidate' | 'verified' | 'obsolete' | 'rejected';
|
|
139
|
+
export interface Knowledge {
|
|
140
|
+
knowledgeId: string;
|
|
141
|
+
kind: string;
|
|
142
|
+
statement: string;
|
|
143
|
+
scope: string;
|
|
144
|
+
confidence: number;
|
|
145
|
+
status: KnowledgeStatus;
|
|
146
|
+
sourceContextIds: string[];
|
|
147
|
+
sourceSnapshotIds: string[];
|
|
148
|
+
supportingEvidence: string[];
|
|
149
|
+
createdAt: string;
|
|
150
|
+
updatedAt: string;
|
|
151
|
+
}
|
|
152
|
+
export interface KnowledgeInput {
|
|
153
|
+
kind?: string;
|
|
154
|
+
scope?: string;
|
|
155
|
+
sourceContextId?: string;
|
|
156
|
+
sourceSnapshotId?: string;
|
|
157
|
+
evidence?: string[];
|
|
158
|
+
}
|
|
159
|
+
export interface KnowledgeDiscoverOptions {
|
|
160
|
+
scope?: string;
|
|
161
|
+
statuses?: KnowledgeStatus[];
|
|
162
|
+
limit?: number;
|
|
163
|
+
maxChars?: number;
|
|
164
|
+
}
|
|
165
|
+
export interface KnowledgeDiscoveryHit {
|
|
166
|
+
knowledge: Knowledge;
|
|
167
|
+
score: number;
|
|
168
|
+
matchedTerms: string[];
|
|
169
|
+
matchedFields: string[];
|
|
170
|
+
why: string[];
|
|
171
|
+
}
|
|
172
|
+
export interface KnowledgeConflictGroup {
|
|
173
|
+
scope: string;
|
|
174
|
+
knowledgeIds: string[];
|
|
175
|
+
statements: string[];
|
|
176
|
+
}
|
|
177
|
+
export interface KnowledgeDiscoveryResult {
|
|
178
|
+
query: string;
|
|
179
|
+
total: number;
|
|
180
|
+
truncated: boolean;
|
|
181
|
+
hits: KnowledgeDiscoveryHit[];
|
|
182
|
+
duplicateIds: string[][];
|
|
183
|
+
possibleConflicts: KnowledgeConflictGroup[];
|
|
184
|
+
}
|
|
185
|
+
export interface DeadendDiscoverOptions {
|
|
186
|
+
scope?: string;
|
|
187
|
+
statuses?: DeadendStatus[];
|
|
188
|
+
limit?: number;
|
|
189
|
+
maxChars?: number;
|
|
190
|
+
}
|
|
191
|
+
export interface DeadendDiscoveryHit {
|
|
192
|
+
deadend: Deadend;
|
|
193
|
+
score: number;
|
|
194
|
+
matchedTerms: string[];
|
|
195
|
+
matchedFields: string[];
|
|
196
|
+
}
|
|
197
|
+
export interface DeadendDiscoveryResult {
|
|
198
|
+
query: string;
|
|
199
|
+
total: number;
|
|
200
|
+
truncated: boolean;
|
|
201
|
+
hits: DeadendDiscoveryHit[];
|
|
202
|
+
}
|
|
203
|
+
export interface DeadendInput {
|
|
204
|
+
scope?: string;
|
|
205
|
+
sourceContextId?: string;
|
|
206
|
+
sourceSnapshotId?: string;
|
|
207
|
+
evidence?: string[];
|
|
208
|
+
}
|
|
209
|
+
export interface Deadend {
|
|
210
|
+
deadendId: string;
|
|
211
|
+
attempt: string;
|
|
212
|
+
reason: string;
|
|
213
|
+
observedEvidence: string[];
|
|
214
|
+
scope: string;
|
|
215
|
+
status: DeadendStatus;
|
|
216
|
+
sourceContextId: string | null;
|
|
217
|
+
sourceSnapshotId: string | null;
|
|
218
|
+
createdAt: string;
|
|
219
|
+
updatedAt: string;
|
|
220
|
+
}
|
|
221
|
+
export interface KnowledgeFile {
|
|
222
|
+
schemaVersion: 1;
|
|
223
|
+
items: Knowledge[];
|
|
224
|
+
}
|
|
225
|
+
export interface DeadendFile {
|
|
226
|
+
schemaVersion: 1;
|
|
227
|
+
items: Deadend[];
|
|
228
|
+
}
|
|
229
|
+
export interface ResumeSummary extends StructuredContext {
|
|
230
|
+
branchOrCommitDrift: boolean;
|
|
231
|
+
}
|
|
232
|
+
export interface ResumeResult {
|
|
233
|
+
context: ContextMetadata;
|
|
234
|
+
snapshot: Snapshot;
|
|
235
|
+
resumeSummary: ResumeSummary;
|
|
236
|
+
details: StructuredContext;
|
|
237
|
+
}
|
|
238
|
+
export interface AgentLoadOptions {
|
|
239
|
+
contextId?: string;
|
|
240
|
+
maxChars?: number;
|
|
241
|
+
includeDetails?: boolean;
|
|
242
|
+
query?: string;
|
|
243
|
+
scope?: string;
|
|
244
|
+
}
|
|
245
|
+
export interface AgentLoadResult {
|
|
246
|
+
projectRoot: string;
|
|
247
|
+
context: ContextMetadata;
|
|
248
|
+
snapshot: Snapshot;
|
|
249
|
+
resumeSummary: ResumeSummary;
|
|
250
|
+
details?: StructuredContext;
|
|
251
|
+
knowledge: KnowledgeDiscoveryResult | null;
|
|
252
|
+
verifiedDeadendIds: string[];
|
|
253
|
+
}
|
|
254
|
+
export interface AgentSaveResult {
|
|
255
|
+
projectRoot: string;
|
|
256
|
+
result: CheckpointResult;
|
|
257
|
+
}
|
|
258
|
+
export interface ContextSearchOptions {
|
|
259
|
+
contextId?: string;
|
|
260
|
+
scope?: string;
|
|
261
|
+
limit?: number;
|
|
262
|
+
maxChars?: number;
|
|
263
|
+
}
|
|
264
|
+
export interface ContextSearchHit {
|
|
265
|
+
context: ContextMetadata;
|
|
266
|
+
snapshotId: string;
|
|
267
|
+
score: number;
|
|
268
|
+
matchedTerms: string[];
|
|
269
|
+
matchedFields: string[];
|
|
270
|
+
summary: ResumeSummary;
|
|
271
|
+
}
|
|
272
|
+
export interface ContextSearchResult {
|
|
273
|
+
projectRoot: string;
|
|
274
|
+
query: string;
|
|
275
|
+
total: number;
|
|
276
|
+
truncated: boolean;
|
|
277
|
+
hits: ContextSearchHit[];
|
|
278
|
+
knowledge: KnowledgeDiscoveryResult;
|
|
279
|
+
verifiedDeadendIds: string[];
|
|
280
|
+
}
|
|
281
|
+
export type CheckpointResult = {
|
|
282
|
+
status: 'saved';
|
|
283
|
+
context: ContextMetadata;
|
|
284
|
+
snapshot: Snapshot;
|
|
285
|
+
} | {
|
|
286
|
+
status: 'no_change';
|
|
287
|
+
context: ContextMetadata;
|
|
288
|
+
snapshot: Snapshot;
|
|
289
|
+
} | {
|
|
290
|
+
status: 'rate_limited';
|
|
291
|
+
context: ContextMetadata;
|
|
292
|
+
snapshot: Snapshot;
|
|
293
|
+
retryAt: string;
|
|
294
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function atomicWriteJson(filePath: string, value: unknown): Promise<void>;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export declare class JsonStoreError extends Error {
|
|
2
|
+
readonly filePath: string;
|
|
3
|
+
constructor(filePath: string, message: string);
|
|
4
|
+
}
|
|
5
|
+
export declare function isRecord(value: unknown): value is Record<string, unknown>;
|
|
6
|
+
export declare function readJson<T>(filePath: string, validate?: (value: unknown) => value is T): Promise<T>;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export declare function contextRoot(projectRoot: string): string;
|
|
2
|
+
export declare function manifestPath(projectRoot: string): string;
|
|
3
|
+
export declare function indexPath(projectRoot: string): string;
|
|
4
|
+
export declare function ignoredPath(projectRoot: string): string;
|
|
5
|
+
export declare function contextsRoot(projectRoot: string): string;
|
|
6
|
+
export declare function contextDirectory(projectRoot: string, contextId: string): string;
|
|
7
|
+
export declare function contextMetadataPath(projectRoot: string, contextId: string): string;
|
|
8
|
+
export declare function snapshotsDirectory(projectRoot: string, contextId: string): string;
|
|
9
|
+
export declare function snapshotPath(projectRoot: string, contextId: string, snapshotId: string): string;
|
|
10
|
+
export declare function locksDirectory(projectRoot: string): string;
|
|
11
|
+
export declare function knowledgePath(projectRoot: string): string;
|
|
12
|
+
export declare function deadendsPath(projectRoot: string): string;
|
|
13
|
+
export declare function notesPath(projectRoot: string): string;
|
|
@@ -35,3 +35,6 @@ export function knowledgePath(projectRoot) {
|
|
|
35
35
|
export function deadendsPath(projectRoot) {
|
|
36
36
|
return path.join(contextRoot(projectRoot), 'deadends.json');
|
|
37
37
|
}
|
|
38
|
+
export function notesPath(projectRoot) {
|
|
39
|
+
return path.join(contextRoot(projectRoot), 'notes.json');
|
|
40
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function withLock<T>(lockPath: string, operation: () => Promise<T>): Promise<T>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const VERSION = "0.3.0";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const VERSION = '0.3.0';
|