@jigyasudham/veto 3.3.0 → 3.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (148) hide show
  1. package/README.md +53 -7
  2. package/dist/adapters/index.d.ts.map +1 -1
  3. package/dist/adapters/index.js +1 -0
  4. package/dist/adapters/index.js.map +1 -1
  5. package/dist/agents/llm-runner.d.ts.map +1 -1
  6. package/dist/agents/llm-runner.js +17 -4
  7. package/dist/agents/llm-runner.js.map +1 -1
  8. package/dist/cli/context-guidance.d.ts +16 -0
  9. package/dist/cli/context-guidance.d.ts.map +1 -0
  10. package/dist/cli/context-guidance.js +20 -0
  11. package/dist/cli/context-guidance.js.map +1 -0
  12. package/dist/cli/leftover-guides.d.ts +38 -0
  13. package/dist/cli/leftover-guides.d.ts.map +1 -0
  14. package/dist/cli/leftover-guides.js +135 -0
  15. package/dist/cli/leftover-guides.js.map +1 -0
  16. package/dist/cli/statusline-hosts.d.ts +36 -0
  17. package/dist/cli/statusline-hosts.d.ts.map +1 -0
  18. package/dist/cli/statusline-hosts.js +212 -0
  19. package/dist/cli/statusline-hosts.js.map +1 -0
  20. package/dist/cli/statusline.d.ts +6 -0
  21. package/dist/cli/statusline.d.ts.map +1 -1
  22. package/dist/cli/statusline.js +51 -10
  23. package/dist/cli/statusline.js.map +1 -1
  24. package/dist/cli.js +104 -47
  25. package/dist/cli.js.map +1 -1
  26. package/dist/council/llm-council.d.ts.map +1 -1
  27. package/dist/council/llm-council.js +13 -8
  28. package/dist/council/llm-council.js.map +1 -1
  29. package/dist/lessons/adapters/claude.d.ts +10 -0
  30. package/dist/lessons/adapters/claude.d.ts.map +1 -0
  31. package/dist/lessons/adapters/claude.js +18 -0
  32. package/dist/lessons/adapters/claude.js.map +1 -0
  33. package/dist/lessons/adapters/codex.d.ts +7 -0
  34. package/dist/lessons/adapters/codex.d.ts.map +1 -0
  35. package/dist/lessons/adapters/codex.js +9 -0
  36. package/dist/lessons/adapters/codex.js.map +1 -0
  37. package/dist/lessons/adapters/gemini.d.ts +4 -0
  38. package/dist/lessons/adapters/gemini.d.ts.map +1 -0
  39. package/dist/lessons/adapters/gemini.js +44 -0
  40. package/dist/lessons/adapters/gemini.js.map +1 -0
  41. package/dist/lessons/adapters/index.d.ts +6 -0
  42. package/dist/lessons/adapters/index.d.ts.map +1 -0
  43. package/dist/lessons/adapters/index.js +14 -0
  44. package/dist/lessons/adapters/index.js.map +1 -0
  45. package/dist/lessons/adapters/markdown.d.ts +16 -0
  46. package/dist/lessons/adapters/markdown.d.ts.map +1 -0
  47. package/dist/lessons/adapters/markdown.js +130 -0
  48. package/dist/lessons/adapters/markdown.js.map +1 -0
  49. package/dist/lessons/adapters/types.d.ts +27 -0
  50. package/dist/lessons/adapters/types.d.ts.map +1 -0
  51. package/dist/lessons/adapters/types.js +2 -0
  52. package/dist/lessons/adapters/types.js.map +1 -0
  53. package/dist/lessons/classify.d.ts +30 -0
  54. package/dist/lessons/classify.d.ts.map +1 -0
  55. package/dist/lessons/classify.js +101 -0
  56. package/dist/lessons/classify.js.map +1 -0
  57. package/dist/lessons/discover.d.ts +19 -0
  58. package/dist/lessons/discover.d.ts.map +1 -0
  59. package/dist/lessons/discover.js +65 -0
  60. package/dist/lessons/discover.js.map +1 -0
  61. package/dist/lessons/harvest.d.ts +48 -0
  62. package/dist/lessons/harvest.d.ts.map +1 -0
  63. package/dist/lessons/harvest.js +96 -0
  64. package/dist/lessons/harvest.js.map +1 -0
  65. package/dist/lessons/identity.d.ts +23 -0
  66. package/dist/lessons/identity.d.ts.map +1 -0
  67. package/dist/lessons/identity.js +81 -0
  68. package/dist/lessons/identity.js.map +1 -0
  69. package/dist/lessons/mask.d.ts +6 -0
  70. package/dist/lessons/mask.d.ts.map +1 -0
  71. package/dist/lessons/mask.js +22 -0
  72. package/dist/lessons/mask.js.map +1 -0
  73. package/dist/lessons/select.d.ts +23 -0
  74. package/dist/lessons/select.d.ts.map +1 -0
  75. package/dist/lessons/select.js +134 -0
  76. package/dist/lessons/select.js.map +1 -0
  77. package/dist/lessons/source-project.d.ts +9 -0
  78. package/dist/lessons/source-project.d.ts.map +1 -0
  79. package/dist/lessons/source-project.js +149 -0
  80. package/dist/lessons/source-project.js.map +1 -0
  81. package/dist/lessons/store.d.ts +56 -0
  82. package/dist/lessons/store.d.ts.map +1 -0
  83. package/dist/lessons/store.js +85 -0
  84. package/dist/lessons/store.js.map +1 -0
  85. package/dist/memory/config.d.ts +14 -0
  86. package/dist/memory/config.d.ts.map +1 -1
  87. package/dist/memory/config.js +43 -0
  88. package/dist/memory/config.js.map +1 -1
  89. package/dist/memory/local.d.ts.map +1 -1
  90. package/dist/memory/local.js +16 -1
  91. package/dist/memory/local.js.map +1 -1
  92. package/dist/memory/schema.d.ts +1 -1
  93. package/dist/memory/schema.d.ts.map +1 -1
  94. package/dist/memory/schema.js +54 -0
  95. package/dist/memory/schema.js.map +1 -1
  96. package/dist/server/handlers/council.d.ts.map +1 -1
  97. package/dist/server/handlers/council.js +4 -1
  98. package/dist/server/handlers/council.js.map +1 -1
  99. package/dist/server/handlers/memory.d.ts.map +1 -1
  100. package/dist/server/handlers/memory.js +13 -3
  101. package/dist/server/handlers/memory.js.map +1 -1
  102. package/dist/server/handlers/session.d.ts.map +1 -1
  103. package/dist/server/handlers/session.js +51 -21
  104. package/dist/server/handlers/session.js.map +1 -1
  105. package/dist/server.d.ts.map +1 -1
  106. package/dist/server.js +12 -3
  107. package/dist/server.js.map +1 -1
  108. package/dist/transcripts/cache.d.ts +1 -0
  109. package/dist/transcripts/cache.d.ts.map +1 -1
  110. package/dist/transcripts/cache.js +0 -0
  111. package/dist/transcripts/cache.js.map +1 -1
  112. package/dist/transcripts/claude-paths.d.ts +5 -0
  113. package/dist/transcripts/claude-paths.d.ts.map +1 -0
  114. package/dist/transcripts/claude-paths.js +21 -0
  115. package/dist/transcripts/claude-paths.js.map +1 -0
  116. package/dist/transcripts/context.d.ts +41 -0
  117. package/dist/transcripts/context.d.ts.map +1 -0
  118. package/dist/transcripts/context.js +144 -0
  119. package/dist/transcripts/context.js.map +1 -0
  120. package/dist/transcripts/discover.d.ts +12 -2
  121. package/dist/transcripts/discover.d.ts.map +1 -1
  122. package/dist/transcripts/discover.js +55 -7
  123. package/dist/transcripts/discover.js.map +1 -1
  124. package/dist/transcripts/manage.d.ts +8 -0
  125. package/dist/transcripts/manage.d.ts.map +1 -1
  126. package/dist/transcripts/manage.js +28 -13
  127. package/dist/transcripts/manage.js.map +1 -1
  128. package/dist/transcripts/mapping.d.ts.map +1 -1
  129. package/dist/transcripts/mapping.js +4 -2
  130. package/dist/transcripts/mapping.js.map +1 -1
  131. package/dist/transcripts/on-save.d.ts +9 -0
  132. package/dist/transcripts/on-save.d.ts.map +1 -1
  133. package/dist/transcripts/on-save.js +36 -9
  134. package/dist/transcripts/on-save.js.map +1 -1
  135. package/dist/transcripts/project-key.d.ts +4 -0
  136. package/dist/transcripts/project-key.d.ts.map +1 -0
  137. package/dist/transcripts/project-key.js +30 -0
  138. package/dist/transcripts/project-key.js.map +1 -0
  139. package/dist/transcripts/recall.d.ts +4 -0
  140. package/dist/transcripts/recall.d.ts.map +1 -1
  141. package/dist/transcripts/recall.js +6 -5
  142. package/dist/transcripts/recall.js.map +1 -1
  143. package/dist/transcripts/stopwords.d.ts +2 -0
  144. package/dist/transcripts/stopwords.d.ts.map +1 -0
  145. package/dist/transcripts/stopwords.js +8 -0
  146. package/dist/transcripts/stopwords.js.map +1 -0
  147. package/package.json +1 -1
  148. package/server.json +2 -2
@@ -0,0 +1,96 @@
1
+ import { readFileSync, statSync } from 'node:fs';
2
+ import { basename } from 'node:path';
3
+ import { isLessonsSharingEnabled } from '../memory/config.js';
4
+ import { parseNativeMemory } from './adapters/index.js';
5
+ import { classifyLesson } from './classify.js';
6
+ import { discoverNativeMemorySources } from './discover.js';
7
+ import { projectNames, resolveProjectIdentity } from './identity.js';
8
+ import { maskLessonText } from './mask.js';
9
+ import { resolveClaudeProjectFolder } from './source-project.js';
10
+ import { disableLessonSource, isLessonSourceEnabled, purgeVanishedLessonSources, syncLessons } from './store.js';
11
+ /**
12
+ * Read and harvest a single host-native memory file. The source is only ever
13
+ * opened for reading. An unrecognised shape disables that host's harvester
14
+ * (the format-drift canary) and leaves its existing rows untouched; selection
15
+ * stops serving them while the host is disabled.
16
+ */
17
+ export function harvestNativeMemory(input) {
18
+ if (!isLessonSourceEnabled(input.source))
19
+ return { status: 'disabled', reason: 'source disabled after format drift' };
20
+ let raw;
21
+ let mtime;
22
+ try {
23
+ raw = readFileSync(input.sourcePath, 'utf8');
24
+ mtime = statSync(input.sourcePath).mtime.toISOString();
25
+ }
26
+ catch {
27
+ return { status: 'unavailable', reason: 'native memory file could not be read' };
28
+ }
29
+ const fileName = basename(input.sourcePath);
30
+ const parsed = parseNativeMemory(input.source, raw, fileName);
31
+ if (!parsed.ok) {
32
+ disableLessonSource(input.source, `${fileName}: ${parsed.reason}`);
33
+ return { status: 'disabled', reason: parsed.reason };
34
+ }
35
+ const base = { sourceCli: input.source, sourcePath: input.sourcePath, projectIdentity: input.projectIdentity, projectLabel: input.projectLabel ?? null, sourceMtime: mtime };
36
+ if ('skip' in parsed) {
37
+ return { status: 'skipped', reason: parsed.skip, removed: syncLessons({ ...base, sections: [] }).removed };
38
+ }
39
+ const sections = parsed.sections.map(section => {
40
+ const masked = maskLessonText(section.text);
41
+ const classified = classifyLesson({
42
+ source: input.source, fileName, global: input.global === true, frontmatter: parsed.frontmatter,
43
+ section, maskedText: masked.text, secrets: masked.secrets, projectNames: input.projectNames,
44
+ });
45
+ return { anchor: section.anchor, textMasked: masked.text, ...classified };
46
+ });
47
+ return { status: 'harvested', ...syncLessons({ ...base, sections }) };
48
+ }
49
+ function sourceProject(source, folders) {
50
+ if (source.global || !source.claudeFolder || !source.claudeSlug)
51
+ return { identity: `global:${source.source}`, label: null, names: [] };
52
+ const cached = folders.get(source.claudeFolder);
53
+ if (cached)
54
+ return cached;
55
+ const { projectDir } = resolveClaudeProjectFolder(source.claudeFolder, source.claudeSlug);
56
+ const resolved = projectDir
57
+ ? { identity: resolveProjectIdentity(projectDir), label: basename(projectDir.replace(/[\\/]+$/, '')) || projectDir, names: projectNames(projectDir) }
58
+ : { identity: `claude-slug:${source.claudeSlug}`, label: source.claudeSlug, names: [] };
59
+ folders.set(source.claudeFolder, resolved);
60
+ return resolved;
61
+ }
62
+ /**
63
+ * One full pass: discover every known native-memory file, harvest it under its
64
+ * project's identity, and delete rows whose file has gone. Nothing is read
65
+ * until the user has accepted consent v2.
66
+ */
67
+ export function syncLessonSources(home) {
68
+ const report = {
69
+ consent: isLessonsSharingEnabled(), sources: 0, harvested: 0, skipped: 0, disabled: 0, unavailable: 0,
70
+ inserted: 0, updated: 0, removed: 0, unresolvedProjects: 0,
71
+ };
72
+ if (!report.consent)
73
+ return report;
74
+ const sources = discoverNativeMemorySources(home);
75
+ report.sources = sources.length;
76
+ const folders = new Map();
77
+ for (const source of sources) {
78
+ const project = sourceProject(source, folders);
79
+ const outcome = harvestNativeMemory({
80
+ source: source.source, sourcePath: source.sourcePath, projectIdentity: project.identity,
81
+ projectLabel: project.label, projectNames: project.names, global: source.global,
82
+ });
83
+ report[outcome.status]++;
84
+ if (outcome.status === 'harvested') {
85
+ report.inserted += outcome.inserted;
86
+ report.updated += outcome.updated;
87
+ report.removed += outcome.removed;
88
+ }
89
+ if (outcome.status === 'skipped')
90
+ report.removed += outcome.removed;
91
+ }
92
+ report.unresolvedProjects = [...folders.values()].filter(f => f.identity.startsWith('claude-slug:')).length;
93
+ report.removed += purgeVanishedLessonSources(sources);
94
+ return report;
95
+ }
96
+ //# sourceMappingURL=harvest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"harvest.js","sourceRoot":"","sources":["../../src/lessons/harvest.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * The alias key for a checkout: absolute, no trailing separator, and on
3
+ * Windows case-folded (D:\Veto and d:\veto are one folder there).
4
+ */
5
+ export declare function canonicalProjectPath(projectDir: string): string;
6
+ /** `git@host:o/r.git`, `https://user:tok@host/o/r` and `ssh://git@host/o/r` are one remote. */
7
+ export declare function normalizeRemote(url: string): string;
8
+ /**
9
+ * A repository is identified by its origin remote and its ROOT commit(s),
10
+ * never by where it is checked out, so the D:, F: and G: copies of one repo
11
+ * share an identity. HEAD would not do: it moves with every commit. A folder
12
+ * without git history falls back to its own path.
13
+ */
14
+ export declare function computeProjectIdentity(projectDir: string): string;
15
+ export declare function resolveProjectIdentity(projectDir: string): string;
16
+ /**
17
+ * What a project is called: its folder name and its origin repository's name.
18
+ * A note naming its own project is about that project (see classify.ts).
19
+ */
20
+ export declare function projectNames(projectDir: string): string[];
21
+ /** Explicitly unify an additional checked-out path with an existing identity. */
22
+ export declare function addProjectAlias(aliasPath: string, projectIdentity: string): void;
23
+ //# sourceMappingURL=identity.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"identity.d.ts","sourceRoot":"","sources":["../../src/lessons/identity.ts"],"names":[],"mappings":"AAUA;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAK/D;AAED,+FAA+F;AAC/F,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAKnD;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAQjE;AAED,wBAAgB,sBAAsB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAWjE;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,EAAE,CAQzD;AAED,iFAAiF;AACjF,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,GAAG,IAAI,CAIhF"}
@@ -0,0 +1,81 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { execFileSync } from 'node:child_process';
3
+ import { basename, resolve } from 'node:path';
4
+ import { getDb, normalizeProjectDir } from '../memory/local.js';
5
+ function git(projectDir, args) {
6
+ try {
7
+ return execFileSync('git', args, { cwd: projectDir, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'], windowsHide: true }).trim() || null;
8
+ }
9
+ catch {
10
+ return null;
11
+ }
12
+ }
13
+ /**
14
+ * The alias key for a checkout: absolute, no trailing separator, and on
15
+ * Windows case-folded (D:\Veto and d:\veto are one folder there).
16
+ */
17
+ export function canonicalProjectPath(projectDir) {
18
+ let path = resolve(projectDir);
19
+ if (!/^[A-Za-z]:\\$|^\/$/.test(path))
20
+ path = path.replace(/[\\/]+$/, '');
21
+ path = normalizeProjectDir(path);
22
+ return process.platform === 'win32' ? path.toLowerCase() : path;
23
+ }
24
+ /** `git@host:o/r.git`, `https://user:tok@host/o/r` and `ssh://git@host/o/r` are one remote. */
25
+ export function normalizeRemote(url) {
26
+ let u = url.trim().replace(/\/+$/, '').replace(/\.git$/i, '');
27
+ const scp = /^[\w.-]+@([^:/]+):(?!\/)(.+)$/.exec(u);
28
+ u = scp ? `${scp[1]}/${scp[2]}` : u.replace(/^[a-z][a-z0-9+.-]*:\/\//i, '').replace(/^[^@/]+@/, '');
29
+ return u.toLowerCase();
30
+ }
31
+ /**
32
+ * A repository is identified by its origin remote and its ROOT commit(s),
33
+ * never by where it is checked out, so the D:, F: and G: copies of one repo
34
+ * share an identity. HEAD would not do: it moves with every commit. A folder
35
+ * without git history falls back to its own path.
36
+ */
37
+ export function computeProjectIdentity(projectDir) {
38
+ const roots = git(projectDir, ['rev-list', '--max-parents=0', 'HEAD']);
39
+ if (roots) {
40
+ const remote = git(projectDir, ['config', '--get', 'remote.origin.url']);
41
+ const key = [remote ? normalizeRemote(remote) : '', ...roots.split(/\s+/).sort()].join('\n');
42
+ return `git:${createHash('sha256').update(key).digest('hex').slice(0, 16)}`;
43
+ }
44
+ return `path:${canonicalProjectPath(projectDir)}`;
45
+ }
46
+ export function resolveProjectIdentity(projectDir) {
47
+ const alias = canonicalProjectPath(projectDir);
48
+ const db = getDb();
49
+ const known = db.prepare('SELECT project_identity FROM project_identity_aliases WHERE alias_path = ?').get(alias);
50
+ // A git identity, or an alias set by hand, is final. Only this folder's own
51
+ // path fallback is re-checked, so a folder that later gains git history
52
+ // joins its other checkouts.
53
+ if (known && known.project_identity !== `path:${alias}`)
54
+ return known.project_identity;
55
+ const identity = computeProjectIdentity(projectDir);
56
+ if (known?.project_identity !== identity)
57
+ addProjectAlias(projectDir, identity);
58
+ return identity;
59
+ }
60
+ /**
61
+ * What a project is called: its folder name and its origin repository's name.
62
+ * A note naming its own project is about that project (see classify.ts).
63
+ */
64
+ export function projectNames(projectDir) {
65
+ const names = new Set();
66
+ const folder = basename(projectDir.replace(/[\\/]+$/, ''));
67
+ if (folder)
68
+ names.add(folder);
69
+ const remote = git(projectDir, ['config', '--get', 'remote.origin.url']);
70
+ const repoName = remote ? normalizeRemote(remote).split('/').pop() : null;
71
+ if (repoName)
72
+ names.add(repoName);
73
+ return [...names].filter(name => name.length >= 3);
74
+ }
75
+ /** Explicitly unify an additional checked-out path with an existing identity. */
76
+ export function addProjectAlias(aliasPath, projectIdentity) {
77
+ getDb().prepare(`INSERT INTO project_identity_aliases (alias_path, project_identity, updated_at) VALUES (?, ?, ?)
78
+ ON CONFLICT(alias_path) DO UPDATE SET project_identity = excluded.project_identity, updated_at = excluded.updated_at`)
79
+ .run(canonicalProjectPath(aliasPath), projectIdentity, new Date().toISOString());
80
+ }
81
+ //# sourceMappingURL=identity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"identity.js","sourceRoot":"","sources":["../../src/lessons/identity.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ /** Remove data that must not leave the originating native-memory document. */
2
+ export declare function maskLessonText(input: string): {
3
+ text: string;
4
+ secrets: number;
5
+ };
6
+ //# sourceMappingURL=mask.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mask.d.ts","sourceRoot":"","sources":["../../src/lessons/mask.ts"],"names":[],"mappings":"AAWA,8EAA8E;AAC9E,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAU/E"}
@@ -0,0 +1,22 @@
1
+ import { mask } from '../transcripts/mask.js';
2
+ const EMAIL_RE = /\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/gi;
3
+ // A home directory in every spelling a note can carry it: C:\Users\x (also
4
+ // JSON-escaped), C:/Users/x, Git Bash /c/Users/x, /Users/x and /home/x. The
5
+ // lookbehind keeps a URL path like example.com/home/x from matching.
6
+ const WINDOWS_HOME_RE = /\b[A-Z]:(?:\\\\|\\|\/)Users(?:\\\\|\\|\/)[^\\\/\s"'`)]+/gi;
7
+ const POSIX_HOME_RE = /(?<![\w.:\/~-])(?:\/[a-z](?=\/Users\/))?\/(?:Users|home)\/[^\/\s"'`)]+/gi;
8
+ // Claude's project-folder slug of a home path: C--Users-<name>-...
9
+ const HOME_SLUG_RE = /\b([A-Z])--Users-[^-\s/\\]+/gi;
10
+ /** Remove data that must not leave the originating native-memory document. */
11
+ export function maskLessonText(input) {
12
+ const masked = mask(input);
13
+ return {
14
+ secrets: masked.count,
15
+ text: masked.text
16
+ .replace(EMAIL_RE, '[email redacted]')
17
+ .replace(WINDOWS_HOME_RE, '~')
18
+ .replace(POSIX_HOME_RE, '~')
19
+ .replace(HOME_SLUG_RE, '$1--Users-[user]'),
20
+ };
21
+ }
22
+ //# sourceMappingURL=mask.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mask.js","sourceRoot":"","sources":["../../src/lessons/mask.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,wBAAwB,CAAC;AAE9C,MAAM,QAAQ,GAAG,6CAA6C,CAAC;AAC/D,2EAA2E;AAC3E,4EAA4E;AAC5E,qEAAqE;AACrE,MAAM,eAAe,GAAG,2DAA2D,CAAC;AACpF,MAAM,aAAa,GAAG,0EAA0E,CAAC;AACjG,mEAAmE;AACnE,MAAM,YAAY,GAAG,+BAA+B,CAAC;AAErD,8EAA8E;AAC9E,MAAM,UAAU,cAAc,CAAC,KAAa;IAC1C,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC;IAC3B,OAAO;QACL,OAAO,EAAE,MAAM,CAAC,KAAK;QACrB,IAAI,EAAE,MAAM,CAAC,IAAI;aACd,OAAO,CAAC,QAAQ,EAAE,kBAAkB,CAAC;aACrC,OAAO,CAAC,eAAe,EAAE,GAAG,CAAC;aAC7B,OAAO,CAAC,aAAa,EAAE,GAAG,CAAC;aAC3B,OAAO,CAAC,YAAY,EAAE,kBAAkB,CAAC;KAC7C,CAAC;AACJ,CAAC"}
@@ -0,0 +1,23 @@
1
+ import type { LessonSource } from './adapters/index.js';
2
+ export type ShadowSelection = {
3
+ lessonIds: string[];
4
+ estimatedTokens: number;
5
+ targetProjectIdentity: string | null;
6
+ reason: 'selected' | 'no_match' | 'consent_off';
7
+ };
8
+ /** Rough, model-agnostic: about four characters per token. */
9
+ export declare const estimateTokens: (text: string) => number;
10
+ /** Deterministic, non-delivering selection used only for prospective shadow logs. */
11
+ export declare function selectLessonsForShadow(input: {
12
+ query: string;
13
+ targetProjectDir: string;
14
+ targetHost: LessonSource;
15
+ now?: number;
16
+ }): ShadowSelection;
17
+ /** Append-only prospective evidence. It records IDs and metadata, never delivery. */
18
+ export declare function logShadowSelection(input: {
19
+ query: string;
20
+ targetHost: LessonSource;
21
+ selection: ShadowSelection;
22
+ }): string;
23
+ //# sourceMappingURL=select.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"select.d.ts","sourceRoot":"","sources":["../../src/lessons/select.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAcxD,MAAM,MAAM,eAAe,GAAG;IAC5B,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC;IACxB,qBAAqB,EAAE,MAAM,GAAG,IAAI,CAAC;IACrC,MAAM,EAAE,UAAU,GAAG,UAAU,GAAG,aAAa,CAAC;CACjD,CAAC;AAIF,8DAA8D;AAC9D,eAAO,MAAM,cAAc,GAAI,MAAM,MAAM,KAAG,MAAoC,CAAC;AA2DnF,qFAAqF;AACrF,wBAAgB,sBAAsB,CAAC,KAAK,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,gBAAgB,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,YAAY,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,eAAe,CA8ClJ;AAED,qFAAqF;AACrF,wBAAgB,kBAAkB,CAAC,KAAK,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,YAAY,CAAC;IAAC,SAAS,EAAE,eAAe,CAAA;CAAE,GAAG,MAAM,CAOzH"}
@@ -0,0 +1,134 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { basename, dirname } from 'node:path';
3
+ import { getDb } from '../memory/local.js';
4
+ import { tokenize } from '../transcripts/tokenize.js';
5
+ import { STOPWORDS } from '../transcripts/stopwords.js';
6
+ import { isLessonsSharingEnabled } from '../memory/config.js';
7
+ import { resolveProjectIdentity } from './identity.js';
8
+ import { claudeProjectSlug, sameClaudeSlug } from './source-project.js';
9
+ import { disabledLessonSources } from './store.js';
10
+ const K1 = 1.2;
11
+ const B = 0.75;
12
+ const TOKEN_CAP = 500;
13
+ // A note must share at least this many distinct query terms: one common word
14
+ // in common ("code", "user") is coincidence, not relevance.
15
+ const MIN_MATCHED_TERMS = 2;
16
+ const STATUS_TTL_MS = 7 * 24 * 60 * 60 * 1000;
17
+ const STATUS_TITLE_RE = /\b(status|progress|next steps?|todo|wip|in progress)\b/i;
18
+ const title = (row) => row.text_masked.split('\n', 1)[0].trim();
19
+ /** Rough, model-agnostic: about four characters per token. */
20
+ export const estimateTokens = (text) => Math.ceil(text.length / 4);
21
+ /**
22
+ * The host already has this note in context, so re-injecting it only spends
23
+ * the budget. Claude loads the memory folder of the project it runs in; Codex
24
+ * and Gemini load their global file in every session. A Claude note from the
25
+ * SAME project but another checkout's folder (D: vs F:) is not loaded, and is
26
+ * exactly what sharing is for.
27
+ */
28
+ function nativelyLoaded(row, host, projectDir) {
29
+ if (row.source_cli !== host)
30
+ return false;
31
+ if (host !== 'claude')
32
+ return true;
33
+ return sameClaudeSlug(basename(dirname(dirname(row.source_path))), claudeProjectSlug(projectDir));
34
+ }
35
+ function staleStatus(row, now) {
36
+ return STATUS_TITLE_RE.test(title(row)) && now - Date.parse(row.source_mtime) > STATUS_TTL_MS;
37
+ }
38
+ /**
39
+ * Which rows may reach this project at all: its own project's notes (any
40
+ * scope, quarantined included, since they never leave the project), plus other
41
+ * projects' user/machine notes that are not quarantined.
42
+ */
43
+ function eligible(row, targetIdentity) {
44
+ if (row.project_identity === targetIdentity)
45
+ return true;
46
+ return row.scope !== 'project' && row.quarantined === 0;
47
+ }
48
+ /**
49
+ * BM25 of each candidate against the query. Collection statistics (document
50
+ * frequency, average length) come from the WHOLE lessons table: computed over
51
+ * the few eligible candidates they would make any shared word look rare.
52
+ */
53
+ function rank(query, candidates, collection) {
54
+ // Scored on sub-tokens (recall), gated on whole words: `git-bash` expands to
55
+ // three terms but is one word, and one word must not clear the two-word bar.
56
+ const terms = [...new Set(tokenize(query).filter(term => !STOPWORDS.has(term)))];
57
+ const words = [...new Set(tokenize(query, false).filter(word => !STOPWORDS.has(word)))];
58
+ const docs = new Map(collection.map(row => [row.id, tokenize(row.text_masked)]));
59
+ const avg = [...docs.values()].reduce((sum, doc) => sum + doc.length, 0) / Math.max(docs.size, 1);
60
+ const df = new Map(terms.map(term => [term, [...docs.values()].filter(doc => doc.includes(term)).length]));
61
+ const needed = Math.min(MIN_MATCHED_TERMS, words.length);
62
+ return candidates.map(row => {
63
+ const doc = docs.get(row.id) ?? tokenize(row.text_masked);
64
+ const docWords = new Set(tokenize(row.text_masked, false));
65
+ const matched = words.filter(word => docWords.has(word)).length;
66
+ let score = 0;
67
+ for (const term of terms) {
68
+ const tf = doc.filter(token => token === term).length;
69
+ if (!tf)
70
+ continue;
71
+ const n = df.get(term) ?? 0;
72
+ const idf = Math.log(1 + (docs.size - n + 0.5) / (n + 0.5));
73
+ score += idf * (tf * (K1 + 1)) / (tf + K1 * (1 - B + B * (doc.length / Math.max(avg, 1))));
74
+ }
75
+ return { row, score: matched >= needed ? score : 0, matched };
76
+ });
77
+ }
78
+ /** Deterministic, non-delivering selection used only for prospective shadow logs. */
79
+ export function selectLessonsForShadow(input) {
80
+ if (!isLessonsSharingEnabled())
81
+ return { lessonIds: [], estimatedTokens: 0, targetProjectIdentity: null, reason: 'consent_off' };
82
+ const targetIdentity = resolveProjectIdentity(input.targetProjectDir);
83
+ const now = input.now ?? Date.now();
84
+ const disabled = disabledLessonSources();
85
+ const all = getDb().prepare('SELECT * FROM lessons').all();
86
+ const candidates = all.filter(row => !disabled.has(row.source_cli)
87
+ && !nativelyLoaded(row, input.targetHost, input.targetProjectDir)
88
+ && !staleStatus(row, now)
89
+ && eligible(row, targetIdentity));
90
+ // Relevance first, then newest source on ties; a note without enough query
91
+ // terms in common is not selected at all, however much budget is left.
92
+ const ranked = rank(input.query, candidates, all)
93
+ .filter(entry => entry.score > 0)
94
+ .sort((a, b) => b.score - a.score || Date.parse(b.row.source_mtime) - Date.parse(a.row.source_mtime) || a.row.id.localeCompare(b.row.id));
95
+ // Newest wins on conflict: the same entry held in several places (one memory
96
+ // file copied into the D:, F: and G: checkouts' folders) counts once, as its
97
+ // newest version, even when an older copy ranks higher. Identical text from
98
+ // different entries is served once too.
99
+ const entryKey = (row) => `${basename(row.source_path).toLowerCase()}#${row.section_anchor}`;
100
+ const newestByEntry = new Map();
101
+ for (const row of candidates) {
102
+ const held = newestByEntry.get(entryKey(row));
103
+ if (!held || Date.parse(row.source_mtime) > Date.parse(held.source_mtime))
104
+ newestByEntry.set(entryKey(row), row);
105
+ }
106
+ const chosen = [];
107
+ const seenText = new Set();
108
+ let tokens = 0;
109
+ for (const { row } of ranked) {
110
+ if (newestByEntry.get(entryKey(row)) !== row || seenText.has(row.text_masked))
111
+ continue;
112
+ const estimate = estimateTokens(row.text_masked);
113
+ if (tokens + estimate > TOKEN_CAP)
114
+ continue;
115
+ chosen.push(row);
116
+ seenText.add(row.text_masked);
117
+ tokens += estimate;
118
+ }
119
+ return {
120
+ lessonIds: chosen.map(row => row.id),
121
+ estimatedTokens: tokens,
122
+ targetProjectIdentity: targetIdentity,
123
+ reason: chosen.length ? 'selected' : 'no_match',
124
+ };
125
+ }
126
+ /** Append-only prospective evidence. It records IDs and metadata, never delivery. */
127
+ export function logShadowSelection(input) {
128
+ const id = randomUUID();
129
+ getDb().prepare(`INSERT INTO lesson_shadow_log (id, query, target_project_identity, target_host, lesson_ids, estimated_tokens, reason, created_at)
130
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)`)
131
+ .run(id, input.query, input.selection.targetProjectIdentity ?? '', input.targetHost, JSON.stringify(input.selection.lessonIds), input.selection.estimatedTokens, input.selection.reason, new Date().toISOString());
132
+ return id;
133
+ }
134
+ //# sourceMappingURL=select.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"select.js","sourceRoot":"","sources":["../../src/lessons/select.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ import { claudeProjectSlug, sameClaudeSlug } from '../transcripts/claude-paths.js';
2
+ export { claudeProjectSlug, sameClaudeSlug };
3
+ export type ClaudeFolderProject = {
4
+ projectDir: string | null;
5
+ via: 'transcript' | 'known' | 'walk' | 'unresolved';
6
+ };
7
+ /** Resolve a ~/.claude/projects/<slug> folder to the project directory it was created for. */
8
+ export declare function resolveClaudeProjectFolder(folder: string, slug: string): ClaudeFolderProject;
9
+ //# sourceMappingURL=source-project.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"source-project.d.ts","sourceRoot":"","sources":["../../src/lessons/source-project.ts"],"names":[],"mappings":"AAiBA,OAAO,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AAEnF,OAAO,EAAE,iBAAiB,EAAE,cAAc,EAAE,CAAC;AAqF7C,MAAM,MAAM,mBAAmB,GAAG;IAAE,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,GAAG,EAAE,YAAY,GAAG,OAAO,GAAG,MAAM,GAAG,YAAY,CAAA;CAAE,CAAC;AAErH,8FAA8F;AAC9F,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,mBAAmB,CAQ5F"}
@@ -0,0 +1,149 @@
1
+ // Which project does a Claude memory folder belong to?
2
+ //
3
+ // Claude Code keeps a project's memory in ~/.claude/projects/<slug>/memory,
4
+ // where <slug> is the project's cwd with every non-alphanumeric character
5
+ // replaced by '-'. The slug is lossy (D:\a-b and D:\a\b share one), so it is
6
+ // reversed from evidence, most reliable first:
7
+ // 1. a transcript in the same folder recording its `cwd` (Claude deletes
8
+ // these after about 30 days, so many folders have none left);
9
+ // 2. a project directory Veto already knows about with the same slug;
10
+ // 3. a walk down from the drive or filesystem root, entering only folders
11
+ // whose own slug prefixes what is left. The walk is bounded, and more
12
+ // than one match counts as unresolved.
13
+ // Unresolved folders keep a slug identity that matches no other project.
14
+ import { closeSync, openSync, readdirSync, readSync, statSync } from 'node:fs';
15
+ import { join } from 'node:path';
16
+ import { getDb } from '../memory/local.js';
17
+ import { claudeProjectSlug, sameClaudeSlug } from '../transcripts/claude-paths.js';
18
+ export { claudeProjectSlug, sameClaudeSlug };
19
+ const HEAD_BYTES = 256 * 1024;
20
+ const TRANSCRIPTS_TO_TRY = 3;
21
+ const WALK_BUDGET = 400;
22
+ const WALK_DEPTH = 16;
23
+ const win32 = process.platform === 'win32';
24
+ function readHead(path) {
25
+ let fd = null;
26
+ try {
27
+ fd = openSync(path, 'r');
28
+ const buf = Buffer.allocUnsafe(HEAD_BYTES);
29
+ return buf.toString('utf8', 0, readSync(fd, buf, 0, HEAD_BYTES, 0));
30
+ }
31
+ catch {
32
+ return '';
33
+ }
34
+ finally {
35
+ if (fd !== null) {
36
+ try {
37
+ closeSync(fd);
38
+ }
39
+ catch { /* ignore */ }
40
+ }
41
+ }
42
+ }
43
+ function cwdFromTranscripts(folder, slug) {
44
+ let names;
45
+ try {
46
+ names = readdirSync(folder).filter(name => name.endsWith('.jsonl'));
47
+ }
48
+ catch {
49
+ return null;
50
+ }
51
+ const newest = names
52
+ .map(name => { try {
53
+ return { path: join(folder, name), mtime: statSync(join(folder, name)).mtimeMs };
54
+ }
55
+ catch {
56
+ return null;
57
+ } })
58
+ .filter((entry) => entry !== null)
59
+ .sort((a, b) => b.mtime - a.mtime)
60
+ .slice(0, TRANSCRIPTS_TO_TRY);
61
+ for (const { path } of newest) {
62
+ for (const match of readHead(path).matchAll(/"cwd"\s*:\s*("(?:[^"\\]|\\.)*")/g)) {
63
+ try {
64
+ const cwd = JSON.parse(match[1]);
65
+ if (cwd && sameClaudeSlug(claudeProjectSlug(cwd), slug))
66
+ return cwd;
67
+ }
68
+ catch { /* a truncated field at the end of the head */ }
69
+ }
70
+ }
71
+ return null;
72
+ }
73
+ function cwdFromKnownProjects(slug) {
74
+ const db = getDb();
75
+ // Keyed case-folded on Windows so D:\Veto and d:\veto count once; the first
76
+ // spelling seen (sessions before the case-folded alias table) is kept.
77
+ const found = new Map();
78
+ for (const sql of [
79
+ 'SELECT DISTINCT project_dir AS dir FROM sessions WHERE project_dir IS NOT NULL',
80
+ 'SELECT alias_path AS dir FROM project_identity_aliases',
81
+ ]) {
82
+ try {
83
+ for (const { dir } of db.prepare(sql).all()) {
84
+ const key = (win32 ? dir.toLowerCase() : dir).replace(/[\\/]+$/, '');
85
+ if (sameClaudeSlug(claudeProjectSlug(dir), slug) && !found.has(key))
86
+ found.set(key, dir);
87
+ }
88
+ }
89
+ catch { /* table absent in an old database */ }
90
+ }
91
+ return found.size === 1 ? [...found.values()][0] : null;
92
+ }
93
+ function cwdFromWalk(slug) {
94
+ const drive = /^([A-Za-z])--(.*)$/.exec(slug);
95
+ let root;
96
+ let rest;
97
+ if (win32 && drive) {
98
+ root = `${drive[1]}:\\`;
99
+ rest = drive[2];
100
+ }
101
+ else if (!win32 && slug.startsWith('-')) {
102
+ root = '/';
103
+ rest = slug.slice(1);
104
+ }
105
+ else
106
+ return null;
107
+ const matches = [];
108
+ let budget = WALK_BUDGET;
109
+ const visit = (dir, remaining, depth) => {
110
+ if (matches.length > 1 || budget-- <= 0 || depth > WALK_DEPTH)
111
+ return;
112
+ let entries;
113
+ try {
114
+ entries = readdirSync(dir, { withFileTypes: true });
115
+ }
116
+ catch {
117
+ return;
118
+ }
119
+ for (const entry of entries) {
120
+ if (!entry.isDirectory() && !entry.isSymbolicLink())
121
+ continue;
122
+ const part = claudeProjectSlug(entry.name);
123
+ if (sameClaudeSlug(part, remaining)) {
124
+ matches.push(join(dir, entry.name));
125
+ continue;
126
+ }
127
+ const prefix = `${part}-`;
128
+ if (sameClaudeSlug(remaining.slice(0, prefix.length), prefix))
129
+ visit(join(dir, entry.name), remaining.slice(prefix.length), depth + 1);
130
+ }
131
+ };
132
+ if (rest)
133
+ visit(root, rest, 0);
134
+ return matches.length === 1 ? matches[0] : null;
135
+ }
136
+ /** Resolve a ~/.claude/projects/<slug> folder to the project directory it was created for. */
137
+ export function resolveClaudeProjectFolder(folder, slug) {
138
+ const fromTranscript = cwdFromTranscripts(folder, slug);
139
+ if (fromTranscript)
140
+ return { projectDir: fromTranscript, via: 'transcript' };
141
+ const known = cwdFromKnownProjects(slug);
142
+ if (known)
143
+ return { projectDir: known, via: 'known' };
144
+ const walked = cwdFromWalk(slug);
145
+ if (walked)
146
+ return { projectDir: walked, via: 'walk' };
147
+ return { projectDir: null, via: 'unresolved' };
148
+ }
149
+ //# sourceMappingURL=source-project.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"source-project.js","sourceRoot":"","sources":["../../src/lessons/source-project.ts"],"names":[],"mappings":"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"}