@fingerskier/augment 0.1.10 → 0.2.1
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 +20 -2
- package/dist/cli.d.ts +6 -0
- package/dist/cli.js +43 -2
- package/dist/cli.js.map +1 -1
- package/dist/constants.d.ts +3 -0
- package/dist/constants.js +19 -3
- package/dist/constants.js.map +1 -1
- package/dist/daemon/client.d.ts +5 -0
- package/dist/daemon/client.js +15 -6
- package/dist/daemon/client.js.map +1 -1
- package/dist/daemon/http.d.ts +1 -1
- package/dist/daemon/http.js +159 -53
- package/dist/daemon/http.js.map +1 -1
- package/dist/daemon/main.js +15 -1
- package/dist/daemon/main.js.map +1 -1
- package/dist/daemon/scheduler.d.ts +17 -0
- package/dist/daemon/scheduler.js +77 -0
- package/dist/daemon/scheduler.js.map +1 -0
- package/dist/daemon/single-instance.d.ts +32 -0
- package/dist/daemon/single-instance.js +131 -0
- package/dist/daemon/single-instance.js.map +1 -0
- package/dist/db.d.ts +13 -1
- package/dist/db.js +166 -105
- package/dist/db.js.map +1 -1
- package/dist/hooks.js +28 -3
- package/dist/hooks.js.map +1 -1
- package/dist/install.d.ts +2 -0
- package/dist/install.js +50 -8
- package/dist/install.js.map +1 -1
- package/dist/mcp/server.js +30 -4
- package/dist/mcp/server.js.map +1 -1
- package/dist/recall.d.ts +2 -0
- package/dist/recall.js +1 -0
- package/dist/recall.js.map +1 -1
- package/dist/service.d.ts +64 -1
- package/dist/service.js +269 -35
- package/dist/service.js.map +1 -1
- package/dist/sleep.d.ts +43 -0
- package/dist/sleep.js +129 -0
- package/dist/sleep.js.map +1 -0
- package/dist/tally.d.ts +46 -0
- package/dist/tally.js +94 -0
- package/dist/tally.js.map +1 -0
- package/dist/types.d.ts +6 -0
- package/dist/util/text.d.ts +3 -0
- package/dist/util/text.js +26 -2
- package/dist/util/text.js.map +1 -1
- package/docs/FEATURES.md +155 -50
- package/docs/verify-memory-flow.md +28 -9
- package/integrations/examples/claude-mcp-config.json +0 -3
- package/integrations/examples/codex-mcp-config.json +0 -3
- package/package.json +1 -1
package/dist/sleep.js
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { cosineSimilarity } from "./util/text.js";
|
|
2
|
+
export function clusterSleepCandidates(input) {
|
|
3
|
+
const byProject = new Map();
|
|
4
|
+
for (const memory of input.memories) {
|
|
5
|
+
const group = byProject.get(memory.project_name) ?? [];
|
|
6
|
+
group.push(memory);
|
|
7
|
+
byProject.set(memory.project_name, group);
|
|
8
|
+
}
|
|
9
|
+
const clusters = [];
|
|
10
|
+
for (const [projectName, group] of byProject) {
|
|
11
|
+
clusters.push(...clusterProject(projectName, group, input));
|
|
12
|
+
}
|
|
13
|
+
return clusters.sort((a, b) => b.members.length - a.members.length || a.oldest.localeCompare(b.oldest));
|
|
14
|
+
}
|
|
15
|
+
function clusterProject(projectName, group, input) {
|
|
16
|
+
const indexById = new Map(group.map((memory, index) => [memory.id, index]));
|
|
17
|
+
const parent = group.map((_, index) => index);
|
|
18
|
+
const find = (index) => (parent[index] === index ? index : (parent[index] = find(parent[index])));
|
|
19
|
+
const union = (a, b) => {
|
|
20
|
+
parent[find(a)] = find(b);
|
|
21
|
+
};
|
|
22
|
+
const linkEdges = new Set();
|
|
23
|
+
for (const link of input.links) {
|
|
24
|
+
const from = indexById.get(link.from_memory_id);
|
|
25
|
+
const to = indexById.get(link.to_memory_id);
|
|
26
|
+
if (from !== undefined && to !== undefined && from !== to) {
|
|
27
|
+
union(from, to);
|
|
28
|
+
linkEdges.add(edgeKey(from, to));
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
// O(n^2) pairwise cosine over one project's settled WORK memories. At the
|
|
32
|
+
// observed corpus scale (tens of WORK per project) this is microseconds;
|
|
33
|
+
// revisit only if a single project's eligible set approaches ~1k.
|
|
34
|
+
const cosineEdges = new Set();
|
|
35
|
+
const cosines = new Map();
|
|
36
|
+
for (let a = 0; a < group.length; a += 1) {
|
|
37
|
+
const vectorA = input.embeddings.get(group[a].id);
|
|
38
|
+
if (!vectorA) {
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
for (let b = a + 1; b < group.length; b += 1) {
|
|
42
|
+
const vectorB = input.embeddings.get(group[b].id);
|
|
43
|
+
if (!vectorB) {
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
const cosine = cosineSimilarity(vectorA, vectorB);
|
|
47
|
+
cosines.set(edgeKey(a, b), cosine);
|
|
48
|
+
if (cosine >= input.minCosine) {
|
|
49
|
+
union(a, b);
|
|
50
|
+
cosineEdges.add(edgeKey(a, b));
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
const components = new Map();
|
|
55
|
+
for (let index = 0; index < group.length; index += 1) {
|
|
56
|
+
const root = find(index);
|
|
57
|
+
components.set(root, [...(components.get(root) ?? []), index]);
|
|
58
|
+
}
|
|
59
|
+
const clusters = [];
|
|
60
|
+
for (const component of components.values()) {
|
|
61
|
+
if (component.length < 2) {
|
|
62
|
+
continue; // a singleton has nothing to consolidate with
|
|
63
|
+
}
|
|
64
|
+
const members = component
|
|
65
|
+
.map((index) => group[index])
|
|
66
|
+
.sort((a, b) => a.updated_at.localeCompare(b.updated_at))
|
|
67
|
+
.map((memory) => ({
|
|
68
|
+
id: memory.id,
|
|
69
|
+
relative_path: memory.relative_path,
|
|
70
|
+
name: memory.name,
|
|
71
|
+
updated_at: memory.updated_at,
|
|
72
|
+
size: memory.size,
|
|
73
|
+
}));
|
|
74
|
+
let cosineSum = 0;
|
|
75
|
+
let cosinePairs = 0;
|
|
76
|
+
for (let x = 0; x < component.length; x += 1) {
|
|
77
|
+
for (let y = x + 1; y < component.length; y += 1) {
|
|
78
|
+
const cosine = cosines.get(edgeKey(component[x], component[y]));
|
|
79
|
+
if (cosine !== undefined) {
|
|
80
|
+
cosineSum += cosine;
|
|
81
|
+
cosinePairs += 1;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
const inComponent = new Set(component);
|
|
86
|
+
clusters.push({
|
|
87
|
+
project_name: projectName,
|
|
88
|
+
members,
|
|
89
|
+
mean_cosine: cosinePairs > 0 ? cosineSum / cosinePairs : 0,
|
|
90
|
+
link_edges: countEdges(linkEdges, inComponent),
|
|
91
|
+
cosine_edges: countEdges(cosineEdges, inComponent),
|
|
92
|
+
oldest: members[0].updated_at,
|
|
93
|
+
newest: members[members.length - 1].updated_at,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
return clusters;
|
|
97
|
+
}
|
|
98
|
+
function edgeKey(a, b) {
|
|
99
|
+
return a < b ? `${a}:${b}` : `${b}:${a}`;
|
|
100
|
+
}
|
|
101
|
+
function countEdges(edges, component) {
|
|
102
|
+
let count = 0;
|
|
103
|
+
for (const key of edges) {
|
|
104
|
+
const [a, b] = key.split(":").map(Number);
|
|
105
|
+
if (component.has(a) && component.has(b)) {
|
|
106
|
+
count += 1;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return count;
|
|
110
|
+
}
|
|
111
|
+
export function renderSleepCandidatesText(clusters, eligible, minCosine, minAgeDays) {
|
|
112
|
+
if (clusters.length === 0) {
|
|
113
|
+
return (`No sleep candidates: ${eligible} eligible WORK memories ` +
|
|
114
|
+
`(>=${minAgeDays}d settled), none connected by links or cosine>=${minCosine}. Nothing to consolidate.\n`);
|
|
115
|
+
}
|
|
116
|
+
let out = `Sleep candidates (READ-ONLY preview — nothing is consolidated or modified; ` +
|
|
117
|
+
`${eligible} eligible WORK memories >=${minAgeDays}d settled, cosine>=${minCosine}):\n`;
|
|
118
|
+
clusters.forEach((cluster, index) => {
|
|
119
|
+
out +=
|
|
120
|
+
`\n[cluster:${index + 1} project:${cluster.project_name} members:${cluster.members.length} ` +
|
|
121
|
+
`mean_cosine:${cluster.mean_cosine.toFixed(2)} edges:${cluster.link_edges}link/${cluster.cosine_edges}cosine ` +
|
|
122
|
+
`window:${cluster.oldest.slice(0, 10)}..${cluster.newest.slice(0, 10)}]\n`;
|
|
123
|
+
for (const member of cluster.members) {
|
|
124
|
+
out += ` - ${member.relative_path} (${member.updated_at.slice(0, 10)}, ${member.size} chars)\n`;
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
return out;
|
|
128
|
+
}
|
|
129
|
+
//# sourceMappingURL=sleep.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sleep.js","sourceRoot":"","sources":["../src/sleep.ts"],"names":[],"mappings":"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"}
|
package/dist/tally.d.ts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Phase-2 dogfood tally (council 2026-06-30): ~30 human-scored datapoints over
|
|
3
|
+
* ~2 weeks of real use, per injection surface, never a CI gate. Data lives as
|
|
4
|
+
* JSONL under `<stateDir>/tally/` — deliberately OUTSIDE the memory corpus so
|
|
5
|
+
* the corpus Phase 2 measures stays clean.
|
|
6
|
+
*/
|
|
7
|
+
export declare const TALLY_TARGET = 30;
|
|
8
|
+
export declare const TALLY_VERDICTS: readonly ["helpful", "harmless", "harmful", "correct-abstention"];
|
|
9
|
+
export type TallyVerdict = (typeof TALLY_VERDICTS)[number];
|
|
10
|
+
/** One human rating of a recall outcome. */
|
|
11
|
+
export interface TallyRating {
|
|
12
|
+
ts: string;
|
|
13
|
+
verdict: TallyVerdict;
|
|
14
|
+
/** Injection surface being rated: user-prompt-submit | pre-tool-use | mcp-search | cli-recall | unknown. */
|
|
15
|
+
surface: string;
|
|
16
|
+
note?: string;
|
|
17
|
+
}
|
|
18
|
+
/** One automatically-logged injection event (what search actually returned). */
|
|
19
|
+
export interface TallyEvent {
|
|
20
|
+
ts: string;
|
|
21
|
+
surface: string;
|
|
22
|
+
project?: string;
|
|
23
|
+
/** Query text, truncated by the writer — context for later audit, not for ranking. */
|
|
24
|
+
query: string;
|
|
25
|
+
abstained: boolean;
|
|
26
|
+
result_count: number;
|
|
27
|
+
top_score?: number;
|
|
28
|
+
/** Durable relative paths of the memories injected. */
|
|
29
|
+
paths: string[];
|
|
30
|
+
}
|
|
31
|
+
export interface TallySummary {
|
|
32
|
+
total: number;
|
|
33
|
+
target: number;
|
|
34
|
+
by_verdict: Record<TallyVerdict, number>;
|
|
35
|
+
by_surface: Record<string, number>;
|
|
36
|
+
first_at?: string;
|
|
37
|
+
last_at?: string;
|
|
38
|
+
}
|
|
39
|
+
export declare function isTallyVerdict(value: unknown): value is TallyVerdict;
|
|
40
|
+
export declare function ratingsPath(stateDir: string): string;
|
|
41
|
+
export declare function eventsPath(stateDir: string): string;
|
|
42
|
+
export declare function appendTallyRating(stateDir: string, rating: TallyRating): Promise<void>;
|
|
43
|
+
export declare function appendTallyEvent(stateDir: string, event: TallyEvent): Promise<void>;
|
|
44
|
+
export declare function readTallyRatings(stateDir: string): Promise<TallyRating[]>;
|
|
45
|
+
export declare function summarizeTally(ratings: TallyRating[]): TallySummary;
|
|
46
|
+
export declare function renderTallySummary(summary: TallySummary): string;
|
package/dist/tally.js
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { appendFile, mkdir, readFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
/**
|
|
4
|
+
* Phase-2 dogfood tally (council 2026-06-30): ~30 human-scored datapoints over
|
|
5
|
+
* ~2 weeks of real use, per injection surface, never a CI gate. Data lives as
|
|
6
|
+
* JSONL under `<stateDir>/tally/` — deliberately OUTSIDE the memory corpus so
|
|
7
|
+
* the corpus Phase 2 measures stays clean.
|
|
8
|
+
*/
|
|
9
|
+
export const TALLY_TARGET = 30;
|
|
10
|
+
export const TALLY_VERDICTS = ["helpful", "harmless", "harmful", "correct-abstention"];
|
|
11
|
+
export function isTallyVerdict(value) {
|
|
12
|
+
return typeof value === "string" && TALLY_VERDICTS.includes(value);
|
|
13
|
+
}
|
|
14
|
+
export function ratingsPath(stateDir) {
|
|
15
|
+
return path.join(stateDir, "tally", "ratings.jsonl");
|
|
16
|
+
}
|
|
17
|
+
export function eventsPath(stateDir) {
|
|
18
|
+
return path.join(stateDir, "tally", "events.jsonl");
|
|
19
|
+
}
|
|
20
|
+
async function appendLine(file, record) {
|
|
21
|
+
await mkdir(path.dirname(file), { recursive: true });
|
|
22
|
+
await appendFile(file, `${JSON.stringify(record)}\n`, "utf8");
|
|
23
|
+
}
|
|
24
|
+
export async function appendTallyRating(stateDir, rating) {
|
|
25
|
+
await appendLine(ratingsPath(stateDir), rating);
|
|
26
|
+
}
|
|
27
|
+
export async function appendTallyEvent(stateDir, event) {
|
|
28
|
+
await appendLine(eventsPath(stateDir), event);
|
|
29
|
+
}
|
|
30
|
+
export async function readTallyRatings(stateDir) {
|
|
31
|
+
let raw;
|
|
32
|
+
try {
|
|
33
|
+
raw = await readFile(ratingsPath(stateDir), "utf8");
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return [];
|
|
37
|
+
}
|
|
38
|
+
const ratings = [];
|
|
39
|
+
for (const line of raw.split("\n")) {
|
|
40
|
+
if (!line.trim()) {
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
try {
|
|
44
|
+
const parsed = JSON.parse(line);
|
|
45
|
+
if (isTallyVerdict(parsed.verdict)) {
|
|
46
|
+
ratings.push(parsed);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
// A corrupt line loses one datapoint, never the tally.
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return ratings;
|
|
54
|
+
}
|
|
55
|
+
export function summarizeTally(ratings) {
|
|
56
|
+
const by_verdict = {
|
|
57
|
+
helpful: 0,
|
|
58
|
+
harmless: 0,
|
|
59
|
+
harmful: 0,
|
|
60
|
+
"correct-abstention": 0,
|
|
61
|
+
};
|
|
62
|
+
const by_surface = {};
|
|
63
|
+
for (const rating of ratings) {
|
|
64
|
+
by_verdict[rating.verdict] += 1;
|
|
65
|
+
by_surface[rating.surface || "unknown"] = (by_surface[rating.surface || "unknown"] ?? 0) + 1;
|
|
66
|
+
}
|
|
67
|
+
return {
|
|
68
|
+
total: ratings.length,
|
|
69
|
+
target: TALLY_TARGET,
|
|
70
|
+
by_verdict,
|
|
71
|
+
by_surface,
|
|
72
|
+
...(ratings.length > 0
|
|
73
|
+
? { first_at: ratings[0].ts, last_at: ratings[ratings.length - 1].ts }
|
|
74
|
+
: {}),
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
export function renderTallySummary(summary) {
|
|
78
|
+
const lines = [
|
|
79
|
+
`Dogfood tally: ${summary.total}/${summary.target} datapoints`,
|
|
80
|
+
` helpful: ${summary.by_verdict.helpful} harmless: ${summary.by_verdict.harmless} ` +
|
|
81
|
+
`harmful: ${summary.by_verdict.harmful} correct-abstention: ${summary.by_verdict["correct-abstention"]}`,
|
|
82
|
+
];
|
|
83
|
+
const surfaces = Object.entries(summary.by_surface)
|
|
84
|
+
.map(([surface, count]) => `${surface}:${count}`)
|
|
85
|
+
.join(" ");
|
|
86
|
+
if (surfaces) {
|
|
87
|
+
lines.push(` by surface: ${surfaces}`);
|
|
88
|
+
}
|
|
89
|
+
if (summary.first_at && summary.last_at) {
|
|
90
|
+
lines.push(` window: ${summary.first_at} .. ${summary.last_at}`);
|
|
91
|
+
}
|
|
92
|
+
return lines.join("\n");
|
|
93
|
+
}
|
|
94
|
+
//# sourceMappingURL=tally.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tally.js","sourceRoot":"","sources":["../src/tally.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
CHANGED
|
@@ -72,6 +72,12 @@ export interface SearchInput {
|
|
|
72
72
|
* ranks highest; raise it to demand a stronger match.
|
|
73
73
|
*/
|
|
74
74
|
min_score?: number;
|
|
75
|
+
/**
|
|
76
|
+
* Which injection surface issued this search (user-prompt-submit,
|
|
77
|
+
* pre-tool-use, mcp-search, cli-recall). Tally instrumentation only —
|
|
78
|
+
* never affects ranking.
|
|
79
|
+
*/
|
|
80
|
+
surface?: string;
|
|
75
81
|
}
|
|
76
82
|
export interface SearchResult {
|
|
77
83
|
memory: MemoryRecord;
|
package/dist/util/text.d.ts
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
export declare function tokens(value: string): string[];
|
|
2
2
|
export declare function lexicalScore(query: string, document: string): number;
|
|
3
|
+
/** lexicalScore against a pre-tokenized query — callers scoring many documents
|
|
4
|
+
* against the same query tokenize it once instead of once per document. */
|
|
5
|
+
export declare function lexicalScoreWith(queryTokens: ReadonlySet<string>, document: string): number;
|
|
3
6
|
export declare function cosineSimilarity(a: number[], b: number[]): number;
|
|
4
7
|
export declare function clamp01(value: number): number;
|
package/dist/util/text.js
CHANGED
|
@@ -1,9 +1,33 @@
|
|
|
1
1
|
const WORD_RE = /[a-z0-9_./-]+/gi;
|
|
2
|
+
const SEGMENT_RE = /[/._-]+/;
|
|
3
|
+
// A compound token (a file path, a dotted or dashed identifier) also contributes
|
|
4
|
+
// its segments, so "/repo/src/deploy/pipeline.ts" can match a document that says
|
|
5
|
+
// "src/deploy/pipeline.ts" or just "deploy pipeline". The compound itself stays
|
|
6
|
+
// in the list so byte-identical mentions keep their full weight.
|
|
2
7
|
export function tokens(value) {
|
|
3
|
-
|
|
8
|
+
const out = [];
|
|
9
|
+
for (const match of value.toLowerCase().matchAll(WORD_RE)) {
|
|
10
|
+
const token = match[0];
|
|
11
|
+
if (token.length > 1) {
|
|
12
|
+
out.push(token);
|
|
13
|
+
}
|
|
14
|
+
const segments = token.split(SEGMENT_RE);
|
|
15
|
+
if (segments.length > 1) {
|
|
16
|
+
for (const segment of segments) {
|
|
17
|
+
if (segment.length > 1) {
|
|
18
|
+
out.push(segment);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return out;
|
|
4
24
|
}
|
|
5
25
|
export function lexicalScore(query, document) {
|
|
6
|
-
|
|
26
|
+
return lexicalScoreWith(new Set(tokens(query)), document);
|
|
27
|
+
}
|
|
28
|
+
/** lexicalScore against a pre-tokenized query — callers scoring many documents
|
|
29
|
+
* against the same query tokenize it once instead of once per document. */
|
|
30
|
+
export function lexicalScoreWith(queryTokens, document) {
|
|
7
31
|
if (queryTokens.size === 0) {
|
|
8
32
|
return 0;
|
|
9
33
|
}
|
package/dist/util/text.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"text.js","sourceRoot":"","sources":["../../src/util/text.ts"],"names":[],"mappings":"AAAA,MAAM,OAAO,GAAG,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"text.js","sourceRoot":"","sources":["../../src/util/text.ts"],"names":[],"mappings":"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"}
|
package/docs/FEATURES.md
CHANGED
|
@@ -18,7 +18,7 @@ Augment is a local RAG memory system for coding agents.
|
|
|
18
18
|
org/repo/kind/name.md
|
|
19
19
|
```
|
|
20
20
|
|
|
21
|
-
- Supported
|
|
21
|
+
- Supported kinds:
|
|
22
22
|
- `ISSUE`
|
|
23
23
|
- `ARCH`
|
|
24
24
|
- `TODO`
|
|
@@ -54,43 +54,30 @@ Body text.
|
|
|
54
54
|
- The database includes metadata, body text, embeddings, gzip density score, and
|
|
55
55
|
derived link rows.
|
|
56
56
|
- `Xenova/bge-small-en-v1.5` via `@huggingface/transformers` provides 384D
|
|
57
|
-
embeddings.
|
|
57
|
+
embeddings. The model revision is recorded as a tripwire constant
|
|
58
|
+
(`PINNED_MODEL_REVISION`) for reproducibility.
|
|
59
|
+
- Indexing is incremental: an external edit reindexes only the changed file, a
|
|
60
|
+
full rebuild skips unchanged files by mtime, and the daemon's own writes are
|
|
61
|
+
indexed inline (no watcher round-trip).
|
|
58
62
|
- The database file is rebuildable and should not be synced as canonical state.
|
|
59
63
|
|
|
60
64
|
Tables:
|
|
61
65
|
|
|
62
|
-
- `projects`
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
- `
|
|
69
|
-
|
|
70
|
-
- `project_id`
|
|
71
|
-
- `kind`
|
|
72
|
-
- `name`
|
|
73
|
-
- `relative_path`
|
|
74
|
-
- `content_hash`
|
|
75
|
-
- `frontmatter_hash`
|
|
76
|
-
- `size`
|
|
77
|
-
- `density`
|
|
78
|
-
- `mtime`
|
|
79
|
-
- `memory_embeddings`
|
|
80
|
-
- `memory_id`
|
|
81
|
-
- `model_id`
|
|
82
|
-
- `model_version`
|
|
83
|
-
- `dimensions`
|
|
84
|
-
- `vector`
|
|
85
|
-
- `embedded_hash`
|
|
86
|
-
- `links`
|
|
87
|
-
- `id`
|
|
88
|
-
- `from_memory_id`
|
|
89
|
-
- `to_memory_id`
|
|
90
|
-
- `type`
|
|
66
|
+
- `projects` — `id`, `name`, `org`, `repo`, `source`, `created_at`, `updated_at`.
|
|
67
|
+
- `memories` — `id`, `project_id`, `kind`, `name`, `relative_path`,
|
|
68
|
+
`content_hash`, `frontmatter_hash`, `size`, `density`, `mtime`, `tags_json`,
|
|
69
|
+
`links_json`, `body`, `created_at`, `updated_at`.
|
|
70
|
+
- `memory_embeddings` — `memory_id`, `model_id`, `model_version`, `dimensions`,
|
|
71
|
+
`vector_json`, `embedded_hash`, `embedded_at`.
|
|
72
|
+
- `links` — `id`, `from_memory_id`, `to_memory_id`, `type`, `created_at`
|
|
73
|
+
(unique on `from` + `to` + `type`).
|
|
91
74
|
|
|
92
75
|
Links are written to the source memory's frontmatter using target relative paths.
|
|
93
|
-
The daemon rebuilds local `links` rows from those declarations.
|
|
76
|
+
The daemon rebuilds local `links` rows from those declarations. A mutation
|
|
77
|
+
(`upsert`/`link`/`unlink`) repairs only the touched memory's link subgraph, so
|
|
78
|
+
link IDs survive *unrelated* mutations within a daemon run — but the touched
|
|
79
|
+
memory's own outbound link IDs are reassigned each time, and all IDs churn on a
|
|
80
|
+
full index rebuild. Relative paths remain the durable identity.
|
|
94
81
|
|
|
95
82
|
## Daemon
|
|
96
83
|
|
|
@@ -99,6 +86,67 @@ The daemon rebuilds local `links` rows from those declarations.
|
|
|
99
86
|
- It exposes a localhost HTTP control endpoint.
|
|
100
87
|
- It binds to `127.0.0.1`, uses a dynamic port by default, writes a discovery
|
|
101
88
|
file, and serves all routes without auth (loopback-only; no token required).
|
|
89
|
+
- It starts serving immediately and builds the initial index in the background
|
|
90
|
+
(a cold corpus no longer blocks hooks or clients on the port);
|
|
91
|
+
`GET /index/status` reports `{ ok, ready, error? }`.
|
|
92
|
+
- Besides memory/link CRUD it serves `/health`, `/index/rebuild`, and the
|
|
93
|
+
dashboard UI.
|
|
94
|
+
- Memories can be deleted via the dashboard or `DELETE /memories/:id` (the file
|
|
95
|
+
and DB row are removed and inbound frontmatter links in other memories are
|
|
96
|
+
scrubbed). Delete is deliberately **not** exposed as an MCP tool.
|
|
97
|
+
|
|
98
|
+
## Dashboard
|
|
99
|
+
|
|
100
|
+
A daemon-served single-page web UI for browsing and editing memories as a graph:
|
|
101
|
+
|
|
102
|
+
- Project graph drills into a per-project memory subgraph (nodes colored by
|
|
103
|
+
kind, edges labeled by link type).
|
|
104
|
+
- Ad hoc semantic search, memory create/edit/delete, link create/delete.
|
|
105
|
+
- Launched via `augment dashboard [--no-open]`; loopback-only; honors
|
|
106
|
+
`AUGMENT_DAEMON_PORT`.
|
|
107
|
+
- Fully self-contained assets (Cytoscape bundled from `node_modules`; no CDN).
|
|
108
|
+
|
|
109
|
+
## CLI
|
|
110
|
+
|
|
111
|
+
Run without a clone via `npx -y @fingerskier/augment <command>`:
|
|
112
|
+
|
|
113
|
+
- `status` — print the resolved config (default command; `config` is an alias).
|
|
114
|
+
- `install <codex|claude|all>` — wire agent integrations (see
|
|
115
|
+
[Agent Integration Installer](#agent-integration-installer)).
|
|
116
|
+
- `dashboard [--no-open]` — start the daemon and open the web UI.
|
|
117
|
+
- `recall "<query>" [--project <org/repo>] [--cwd <dir>] [--limit N]
|
|
118
|
+
[--max-chars N] [--min-score X]` — print the same context-injection text the
|
|
119
|
+
hooks use. No match prints the explicit abstention message ("No memory met
|
|
120
|
+
the relevance threshold …"); output is empty only on failure (daemon down,
|
|
121
|
+
blank query).
|
|
122
|
+
- `hook <session-start|user-prompt-submit|pre-tool-use|stop>` — run a lifecycle
|
|
123
|
+
hook over stdin JSON. Host-wired by `install`; rarely called directly.
|
|
124
|
+
- `help` — full usage.
|
|
125
|
+
|
|
126
|
+
## Lifecycle Hooks
|
|
127
|
+
|
|
128
|
+
Both installed integrations wire the same `hooks/hooks.json` (Claude Code and
|
|
129
|
+
Codex share the event names and stdin-JSON contract). Every handler **fails
|
|
130
|
+
open** — any error yields empty output and exit 0, so a broken hook can never
|
|
131
|
+
break the host agent.
|
|
132
|
+
|
|
133
|
+
- `SessionStart` — warms the daemon and registers the project.
|
|
134
|
+
- `UserPromptSubmit` — recalls memory for the prompt and injects it as
|
|
135
|
+
`additionalContext` before the model sees the prompt.
|
|
136
|
+
- `PreToolUse` (matcher `Edit|Write|Bash`) — recalls additional memory keyed to
|
|
137
|
+
the tool action (the edited file / the Bash command). Uses a raised relevance
|
|
138
|
+
floor (0.45, calibrated on the real model — tool queries are command/path
|
|
139
|
+
shaped, not prose) and injects each distinct query at most once per session.
|
|
140
|
+
Injection-only: never blocks, gates, or rewrites a tool call.
|
|
141
|
+
- `Stop` — blocks the turn once with a directive to upsert a memory, then allows
|
|
142
|
+
the stop (loop-guarded per turn).
|
|
143
|
+
|
|
144
|
+
Installed hook commands invoke the install-time-provisioned runtime directly
|
|
145
|
+
(`node <prefix>/node_modules/@fingerskier/augment/dist/bin/augment.js hook
|
|
146
|
+
<event>`) — no npm bootstrap on the hot path.
|
|
147
|
+
|
|
148
|
+
The verification process for the whole flow is
|
|
149
|
+
[docs/verify-memory-flow.md](verify-memory-flow.md).
|
|
102
150
|
|
|
103
151
|
## MCP Tools
|
|
104
152
|
|
|
@@ -132,11 +180,11 @@ Inputs:
|
|
|
132
180
|
{
|
|
133
181
|
project_name?: string;
|
|
134
182
|
query: string;
|
|
135
|
-
limit?: number;
|
|
136
|
-
max_chars?: number;
|
|
183
|
+
limit?: number; // default 5, max 20
|
|
184
|
+
max_chars?: number; // default 12000, max 50000
|
|
137
185
|
kinds?: Array<"ISSUE" | "ARCH" | "TODO" | "SPEC" | "WORK">;
|
|
138
186
|
include_linked?: boolean;
|
|
139
|
-
min_score?: number;
|
|
187
|
+
min_score?: number; // 0..1 relevance floor; default 0.4, 0 disables
|
|
140
188
|
}
|
|
141
189
|
```
|
|
142
190
|
|
|
@@ -157,11 +205,16 @@ Behavior:
|
|
|
157
205
|
- Applies a minimum relevance threshold (`min_score`, default
|
|
158
206
|
`DEFAULT_SEARCH_MIN_SCORE` = 0.4) and **abstains** when nothing clears it —
|
|
159
207
|
returning no results and a "nothing injected" message rather than surfacing an
|
|
160
|
-
unrelated memory.
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
208
|
+
unrelated memory. The floor is calibrated against the real embedding model:
|
|
209
|
+
after the empirically-anchored cosine rescale (`SEMANTIC_COSINE_BASELINE`
|
|
210
|
+
0.33), an unrelated memory blends to ~0.12 while an on-topic one lands ~0.60,
|
|
211
|
+
so 0.4 separates abstention from recall with margin. Pass `min_score: 0` to
|
|
212
|
+
disable the floor (never abstain), or raise it to demand a stronger match.
|
|
213
|
+
- Packs results under a fair-share character budget: each result may use an even
|
|
214
|
+
split of the remaining `max_chars`, with unused share rolling forward; an
|
|
215
|
+
oversized body is head-truncated with a visible `[truncated]` marker rather
|
|
216
|
+
than omitted, and a result is skipped only when fewer than 80 usable characters
|
|
217
|
+
remain. Bodies are whole-file prefixes — never summarized.
|
|
165
218
|
|
|
166
219
|
### `upsert`
|
|
167
220
|
|
|
@@ -188,6 +241,9 @@ contradicts or omits something the prior memory held, Augment preserves the prio
|
|
|
188
241
|
memory intact and stores the new content as its own memory rather than silently
|
|
189
242
|
overwriting history.
|
|
190
243
|
|
|
244
|
+
Content is capped at 12,000 characters; an oversized upsert is rejected — split
|
|
245
|
+
the note instead.
|
|
246
|
+
|
|
191
247
|
### `read`
|
|
192
248
|
|
|
193
249
|
Reads one memory by local numeric ID.
|
|
@@ -200,19 +256,24 @@ Inputs:
|
|
|
200
256
|
|
|
201
257
|
### `link`
|
|
202
258
|
|
|
203
|
-
Creates a directional link between
|
|
204
|
-
|
|
259
|
+
Creates a directional link between memories and persists it as a relative-path
|
|
260
|
+
link in the source memory frontmatter.
|
|
205
261
|
|
|
206
262
|
Inputs:
|
|
207
263
|
|
|
208
264
|
```ts
|
|
209
265
|
{
|
|
210
|
-
from_memory_id: number;
|
|
211
|
-
to_memory_id: number;
|
|
266
|
+
from_memory_id: number | string;
|
|
267
|
+
to_memory_id: number | string;
|
|
212
268
|
type?: "DEPENDS_ON" | "BLOCKS" | "IMPLEMENTS" | "PARENT" | "RELATED";
|
|
213
269
|
}
|
|
214
270
|
```
|
|
215
271
|
|
|
272
|
+
Each reference may be a local numeric ID **or** a relative path
|
|
273
|
+
(`org/repo/KIND/name.md`). Paths are the durable handle — they survive index
|
|
274
|
+
rebuilds, while numeric IDs churn. An all-digit string is interpreted as a
|
|
275
|
+
numeric ID.
|
|
276
|
+
|
|
216
277
|
### `list_links`
|
|
217
278
|
|
|
218
279
|
Lists inbound and outbound links involving a memory.
|
|
@@ -233,6 +294,39 @@ Inputs:
|
|
|
233
294
|
{ id: number }
|
|
234
295
|
```
|
|
235
296
|
|
|
297
|
+
### `tally`
|
|
298
|
+
|
|
299
|
+
Record one Phase-2 dogfood datapoint rating recalled memory quality.
|
|
300
|
+
|
|
301
|
+
- Input: `verdict` (`helpful` | `harmless` | `harmful` | `correct-abstention`),
|
|
302
|
+
optional `surface`, optional `note`.
|
|
303
|
+
- Returns the running tally (target: ~30 datapoints).
|
|
304
|
+
- Verdict semantics: `helpful` = injected memory materially helped;
|
|
305
|
+
`harmless` = injected but neutral/ignored; `harmful` = misled or wasted
|
|
306
|
+
context; `correct-abstention` = nothing was injected, and that was right.
|
|
307
|
+
- Storage: `<stateDir>/tally/ratings.jsonl` — outside the memory corpus, so
|
|
308
|
+
rating never mutates what Phase 2 measures. Every `/memories/search` also
|
|
309
|
+
appends an event line (surface, query prefix, abstained, result paths) to
|
|
310
|
+
`<stateDir>/tally/events.jsonl` for audit; event logging is best-effort and
|
|
311
|
+
can never fail a search.
|
|
312
|
+
- CLI twin: `augment tally [verdict] [--surface S] [--note "..."]`; bare
|
|
313
|
+
`augment tally` prints the summary.
|
|
314
|
+
|
|
315
|
+
### `sleep_candidates`
|
|
316
|
+
|
|
317
|
+
READ-ONLY preview of sleep consolidation. Clusters settled WORK memories
|
|
318
|
+
(default: older than 3 days by `updated_at`) within each project into candidate
|
|
319
|
+
groups a future `sleep` pass would distill into SPEC/ARCH records.
|
|
320
|
+
|
|
321
|
+
- Edges: existing links of any type, or raw cosine >= `min_cosine`
|
|
322
|
+
(default 0.75 — semantic-only, deliberately above the blended recall floor;
|
|
323
|
+
see `SLEEP_MIN_COSINE` in `src/constants.ts`).
|
|
324
|
+
- Clusters never span projects. Output reports durable `relative_path`s.
|
|
325
|
+
- Input: optional `project_name` (inferred from cwd otherwise), `min_cosine`,
|
|
326
|
+
`min_age_days`, `limit` (default 20, `SLEEP_MAX_CLUSTERS`).
|
|
327
|
+
- Mutates nothing. The write-back (`sleep_apply`) is deferred behind the
|
|
328
|
+
Phase-2 dogfood go/no-go and does not exist.
|
|
329
|
+
|
|
236
330
|
## Configuration
|
|
237
331
|
|
|
238
332
|
Resolution order:
|
|
@@ -272,13 +366,16 @@ Codex install creates or updates:
|
|
|
272
366
|
- `.mcp.json` pointing to the published `@fingerskier/augment` package through
|
|
273
367
|
an isolated npm `--prefix` directory.
|
|
274
368
|
- `skills/augment-context/SKILL.md`.
|
|
369
|
+
- `hooks/hooks.json` wiring SessionStart / UserPromptSubmit /
|
|
370
|
+
PreToolUse (`Edit|Write|Bash`) / Stop.
|
|
275
371
|
- A personal or repo marketplace entry.
|
|
276
372
|
|
|
277
373
|
Claude install creates or updates:
|
|
278
374
|
|
|
279
375
|
- A real Claude Code plugin folder named `augment` with
|
|
280
376
|
`.claude-plugin/plugin.json`, an auto-discovered `skills/augment-context/SKILL.md`,
|
|
281
|
-
|
|
377
|
+
a bundled `.mcp.json` (same cwd-safe MCP command shape as Codex), and
|
|
378
|
+
`hooks/hooks.json` (auto-discovered lifecycle hooks).
|
|
282
379
|
- A `.claude-plugin/marketplace.json` entry so the plugin is installable via
|
|
283
380
|
`/plugin marketplace add` + `/plugin install augment@<marketplace>`.
|
|
284
381
|
- A direct MCP registration so the server is active immediately: repo scope writes
|
|
@@ -292,11 +389,19 @@ Claude install creates or updates:
|
|
|
292
389
|
`--no-plugin`, falling back to printing the manual `/plugin` commands.
|
|
293
390
|
- A marked Augment block in `CLAUDE.md` carrying the always-on memory workflow.
|
|
294
391
|
|
|
392
|
+
Both installers also provision the shared hook runtime once via
|
|
393
|
+
`npm install --prefix ~/.augment/npm-exec @fingerskier/augment`, so hook commands
|
|
394
|
+
run through a direct `node <entry>` invocation (tens of milliseconds) instead of
|
|
395
|
+
an npm bootstrap on every event. If npm is unavailable at install time the
|
|
396
|
+
installer prints the manual command; hooks fail open (silent no-ops) until it is
|
|
397
|
+
run.
|
|
398
|
+
|
|
295
399
|
## Deferred
|
|
296
400
|
|
|
297
|
-
-
|
|
298
|
-
-
|
|
299
|
-
- `dream`.
|
|
300
|
-
- Remote sync.
|
|
401
|
+
- `sleep` / `dream` (memory consolidation) — gated on the retrieval-quality
|
|
402
|
+
dogfood go/no-go; see [ROADMAP.md](../ROADMAP.md).
|
|
301
403
|
- Memory `move`.
|
|
302
|
-
|
|
404
|
+
|
|
405
|
+
Not planned:
|
|
406
|
+
|
|
407
|
+
- Remote sync — file sync (e.g. Dropbox) is the supported multi-machine story.
|