@fingerskier/augment 0.1.9 → 0.2.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.
Files changed (52) hide show
  1. package/README.md +20 -2
  2. package/dist/cli.d.ts +6 -0
  3. package/dist/cli.js +43 -2
  4. package/dist/cli.js.map +1 -1
  5. package/dist/constants.d.ts +6 -0
  6. package/dist/constants.js +46 -6
  7. package/dist/constants.js.map +1 -1
  8. package/dist/daemon/client.d.ts +5 -0
  9. package/dist/daemon/client.js +15 -6
  10. package/dist/daemon/client.js.map +1 -1
  11. package/dist/daemon/http.d.ts +1 -1
  12. package/dist/daemon/http.js +159 -53
  13. package/dist/daemon/http.js.map +1 -1
  14. package/dist/daemon/main.js +15 -1
  15. package/dist/daemon/main.js.map +1 -1
  16. package/dist/daemon/scheduler.d.ts +17 -0
  17. package/dist/daemon/scheduler.js +77 -0
  18. package/dist/daemon/scheduler.js.map +1 -0
  19. package/dist/daemon/single-instance.d.ts +32 -0
  20. package/dist/daemon/single-instance.js +131 -0
  21. package/dist/daemon/single-instance.js.map +1 -0
  22. package/dist/db.d.ts +13 -1
  23. package/dist/db.js +166 -105
  24. package/dist/db.js.map +1 -1
  25. package/dist/embeddings.js +8 -0
  26. package/dist/embeddings.js.map +1 -1
  27. package/dist/hooks.js +28 -3
  28. package/dist/hooks.js.map +1 -1
  29. package/dist/install.d.ts +2 -0
  30. package/dist/install.js +41 -7
  31. package/dist/install.js.map +1 -1
  32. package/dist/mcp/server.js +30 -4
  33. package/dist/mcp/server.js.map +1 -1
  34. package/dist/recall.d.ts +2 -0
  35. package/dist/recall.js +1 -0
  36. package/dist/recall.js.map +1 -1
  37. package/dist/service.d.ts +64 -1
  38. package/dist/service.js +298 -38
  39. package/dist/service.js.map +1 -1
  40. package/dist/sleep.d.ts +43 -0
  41. package/dist/sleep.js +129 -0
  42. package/dist/sleep.js.map +1 -0
  43. package/dist/tally.d.ts +46 -0
  44. package/dist/tally.js +94 -0
  45. package/dist/tally.js.map +1 -0
  46. package/dist/types.d.ts +6 -0
  47. package/dist/util/text.d.ts +3 -0
  48. package/dist/util/text.js +26 -2
  49. package/dist/util/text.js.map +1 -1
  50. package/docs/FEATURES.md +161 -51
  51. package/docs/verify-memory-flow.md +28 -9
  52. package/package.json +2 -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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"}
@@ -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;
@@ -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
- return Array.from(value.toLowerCase().matchAll(WORD_RE), (match) => match[0]).filter((token) => token.length > 1);
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
- const queryTokens = new Set(tokens(query));
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
  }
@@ -1 +1 @@
1
- {"version":3,"file":"text.js","sourceRoot":"","sources":["../../src/util/text.ts"],"names":[],"mappings":"AAAA,MAAM,OAAO,GAAG,iBAAiB,CAAC;AAElC,MAAM,UAAU,MAAM,CAAC,KAAa;IAClC,OAAO,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;AACpH,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,KAAa,EAAE,QAAgB;IAC1D,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;IAC3C,IAAI,WAAW,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;QAC3B,OAAO,CAAC,CAAC;IACX,CAAC;IAED,MAAM,cAAc,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC;IACjD,IAAI,IAAI,GAAG,CAAC,CAAC;IACb,KAAK,MAAM,KAAK,IAAI,WAAW,EAAE,CAAC;QAChC,IAAI,cAAc,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;YAC9B,IAAI,IAAI,CAAC,CAAC;QACZ,CAAC;IACH,CAAC;IACD,OAAO,IAAI,GAAG,WAAW,CAAC,IAAI,CAAC;AACjC,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,CAAW,EAAE,CAAW;IACvD,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,MAAM,EAAE,CAAC;QAC5C,OAAO,CAAC,CAAC;IACX,CAAC;IAED,IAAI,GAAG,GAAG,CAAC,CAAC;IACZ,IAAI,IAAI,GAAG,CAAC,CAAC;IACb,IAAI,IAAI,GAAG,CAAC,CAAC;IACb,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,CAAC,CAAC,MAAM,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC;QACjD,GAAG,IAAI,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC;QAC3B,IAAI,IAAI,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC;QAC5B,IAAI,IAAI,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC;IAC9B,CAAC;IAED,IAAI,IAAI,KAAK,CAAC,IAAI,IAAI,KAAK,CAAC,EAAE,CAAC;QAC7B,OAAO,CAAC,CAAC;IACX,CAAC;IACD,OAAO,GAAG,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;AACnD,CAAC;AAED,MAAM,UAAU,OAAO,CAAC,KAAa;IACnC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;QAC5B,OAAO,CAAC,CAAC;IACX,CAAC;IACD,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC;AACzC,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 MVP kinds:
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
- - `id`
64
- - `name`
65
- - `org`
66
- - `repo`
67
- - `source`
68
- - `memories`
69
- - `id`
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; // 0..1 relevance floor; default 0.4, 0 disables
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. An orthogonal/unrelated memory floors at ~0.275, so the
161
- default sits above that floor. Pass `min_score: 0` to disable the floor (never
162
- abstain), or raise it to demand a stronger match. The default is provisional
163
- and intended for calibration against the real embedding model.
164
- - Does not summarize or extract arbitrary excerpts.
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
 
@@ -181,7 +234,15 @@ Inputs:
181
234
  ```
182
235
 
183
236
  If `memory_id` is absent, Augment may automatically update a highly similar
184
- memory in the same project and kind.
237
+ memory in the same project and kind — but only when the new content subsumes the
238
+ existing one (every distinct term of the prior body survives in the new content),
239
+ so the in-place update is a lossless refresh. If the new content instead
240
+ contradicts or omits something the prior memory held, Augment preserves the prior
241
+ memory intact and stores the new content as its own memory rather than silently
242
+ overwriting history.
243
+
244
+ Content is capped at 12,000 characters; an oversized upsert is rejected — split
245
+ the note instead.
185
246
 
186
247
  ### `read`
187
248
 
@@ -195,19 +256,24 @@ Inputs:
195
256
 
196
257
  ### `link`
197
258
 
198
- Creates a directional link between local memory IDs and persists it as a
199
- relative-path link in the source memory frontmatter.
259
+ Creates a directional link between memories and persists it as a relative-path
260
+ link in the source memory frontmatter.
200
261
 
201
262
  Inputs:
202
263
 
203
264
  ```ts
204
265
  {
205
- from_memory_id: number;
206
- to_memory_id: number;
266
+ from_memory_id: number | string;
267
+ to_memory_id: number | string;
207
268
  type?: "DEPENDS_ON" | "BLOCKS" | "IMPLEMENTS" | "PARENT" | "RELATED";
208
269
  }
209
270
  ```
210
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
+
211
277
  ### `list_links`
212
278
 
213
279
  Lists inbound and outbound links involving a memory.
@@ -228,6 +294,39 @@ Inputs:
228
294
  { id: number }
229
295
  ```
230
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
+
231
330
  ## Configuration
232
331
 
233
332
  Resolution order:
@@ -267,13 +366,16 @@ Codex install creates or updates:
267
366
  - `.mcp.json` pointing to the published `@fingerskier/augment` package through
268
367
  an isolated npm `--prefix` directory.
269
368
  - `skills/augment-context/SKILL.md`.
369
+ - `hooks/hooks.json` wiring SessionStart / UserPromptSubmit /
370
+ PreToolUse (`Edit|Write|Bash`) / Stop.
270
371
  - A personal or repo marketplace entry.
271
372
 
272
373
  Claude install creates or updates:
273
374
 
274
375
  - A real Claude Code plugin folder named `augment` with
275
376
  `.claude-plugin/plugin.json`, an auto-discovered `skills/augment-context/SKILL.md`,
276
- and a bundled `.mcp.json` (same cwd-safe MCP command shape as Codex).
377
+ a bundled `.mcp.json` (same cwd-safe MCP command shape as Codex), and
378
+ `hooks/hooks.json` (auto-discovered lifecycle hooks).
277
379
  - A `.claude-plugin/marketplace.json` entry so the plugin is installable via
278
380
  `/plugin marketplace add` + `/plugin install augment@<marketplace>`.
279
381
  - A direct MCP registration so the server is active immediately: repo scope writes
@@ -287,11 +389,19 @@ Claude install creates or updates:
287
389
  `--no-plugin`, falling back to printing the manual `/plugin` commands.
288
390
  - A marked Augment block in `CLAUDE.md` carrying the always-on memory workflow.
289
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
+
290
399
  ## Deferred
291
400
 
292
- - Dashboard.
293
- - `sleep`.
294
- - `dream`.
295
- - Remote sync.
401
+ - `sleep` / `dream` (memory consolidation) — gated on the retrieval-quality
402
+ dogfood go/no-go; see [ROADMAP.md](../ROADMAP.md).
296
403
  - Memory `move`.
297
- - CLI aliases and UX beyond diagnostics/config helpers.
404
+
405
+ Not planned:
406
+
407
+ - Remote sync — file sync (e.g. Dropbox) is the supported multi-machine story.