@jigyasudham/veto 3.3.0 → 3.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +89 -7
- package/dist/adapters/index.d.ts.map +1 -1
- package/dist/adapters/index.js +1 -0
- package/dist/adapters/index.js.map +1 -1
- package/dist/agents/llm-runner.d.ts.map +1 -1
- package/dist/agents/llm-runner.js +78 -8
- package/dist/agents/llm-runner.js.map +1 -1
- package/dist/cli/context-guidance.d.ts +16 -0
- package/dist/cli/context-guidance.d.ts.map +1 -0
- package/dist/cli/context-guidance.js +20 -0
- package/dist/cli/context-guidance.js.map +1 -0
- package/dist/cli/leftover-guides.d.ts +38 -0
- package/dist/cli/leftover-guides.d.ts.map +1 -0
- package/dist/cli/leftover-guides.js +135 -0
- package/dist/cli/leftover-guides.js.map +1 -0
- package/dist/cli/lessons.d.ts +17 -0
- package/dist/cli/lessons.d.ts.map +1 -0
- package/dist/cli/lessons.js +448 -0
- package/dist/cli/lessons.js.map +1 -0
- package/dist/cli/statusline-hosts.d.ts +36 -0
- package/dist/cli/statusline-hosts.d.ts.map +1 -0
- package/dist/cli/statusline-hosts.js +212 -0
- package/dist/cli/statusline-hosts.js.map +1 -0
- package/dist/cli/statusline.d.ts +7 -0
- package/dist/cli/statusline.d.ts.map +1 -1
- package/dist/cli/statusline.js +69 -11
- package/dist/cli/statusline.js.map +1 -1
- package/dist/cli.js +114 -47
- package/dist/cli.js.map +1 -1
- package/dist/council/llm-council.d.ts.map +1 -1
- package/dist/council/llm-council.js +13 -8
- package/dist/council/llm-council.js.map +1 -1
- package/dist/lessons/adapters/claude.d.ts +10 -0
- package/dist/lessons/adapters/claude.d.ts.map +1 -0
- package/dist/lessons/adapters/claude.js +18 -0
- package/dist/lessons/adapters/claude.js.map +1 -0
- package/dist/lessons/adapters/codex.d.ts +7 -0
- package/dist/lessons/adapters/codex.d.ts.map +1 -0
- package/dist/lessons/adapters/codex.js +9 -0
- package/dist/lessons/adapters/codex.js.map +1 -0
- package/dist/lessons/adapters/gemini.d.ts +4 -0
- package/dist/lessons/adapters/gemini.d.ts.map +1 -0
- package/dist/lessons/adapters/gemini.js +44 -0
- package/dist/lessons/adapters/gemini.js.map +1 -0
- package/dist/lessons/adapters/index.d.ts +6 -0
- package/dist/lessons/adapters/index.d.ts.map +1 -0
- package/dist/lessons/adapters/index.js +14 -0
- package/dist/lessons/adapters/index.js.map +1 -0
- package/dist/lessons/adapters/markdown.d.ts +16 -0
- package/dist/lessons/adapters/markdown.d.ts.map +1 -0
- package/dist/lessons/adapters/markdown.js +130 -0
- package/dist/lessons/adapters/markdown.js.map +1 -0
- package/dist/lessons/adapters/types.d.ts +27 -0
- package/dist/lessons/adapters/types.d.ts.map +1 -0
- package/dist/lessons/adapters/types.js +2 -0
- package/dist/lessons/adapters/types.js.map +1 -0
- package/dist/lessons/classify.d.ts +33 -0
- package/dist/lessons/classify.d.ts.map +1 -0
- package/dist/lessons/classify.js +108 -0
- package/dist/lessons/classify.js.map +1 -0
- package/dist/lessons/consent.d.ts +30 -0
- package/dist/lessons/consent.d.ts.map +1 -0
- package/dist/lessons/consent.js +86 -0
- package/dist/lessons/consent.js.map +1 -0
- package/dist/lessons/discover.d.ts +26 -0
- package/dist/lessons/discover.d.ts.map +1 -0
- package/dist/lessons/discover.js +65 -0
- package/dist/lessons/discover.js.map +1 -0
- package/dist/lessons/harvest.d.ts +84 -0
- package/dist/lessons/harvest.d.ts.map +1 -0
- package/dist/lessons/harvest.js +271 -0
- package/dist/lessons/harvest.js.map +1 -0
- package/dist/lessons/identity.d.ts +23 -0
- package/dist/lessons/identity.d.ts.map +1 -0
- package/dist/lessons/identity.js +81 -0
- package/dist/lessons/identity.js.map +1 -0
- package/dist/lessons/manage.d.ts +163 -0
- package/dist/lessons/manage.d.ts.map +1 -0
- package/dist/lessons/manage.js +337 -0
- package/dist/lessons/manage.js.map +1 -0
- package/dist/lessons/mask.d.ts +6 -0
- package/dist/lessons/mask.d.ts.map +1 -0
- package/dist/lessons/mask.js +22 -0
- package/dist/lessons/mask.js.map +1 -0
- package/dist/lessons/on-save.d.ts +18 -0
- package/dist/lessons/on-save.d.ts.map +1 -0
- package/dist/lessons/on-save.js +53 -0
- package/dist/lessons/on-save.js.map +1 -0
- package/dist/lessons/select.d.ts +23 -0
- package/dist/lessons/select.d.ts.map +1 -0
- package/dist/lessons/select.js +136 -0
- package/dist/lessons/select.js.map +1 -0
- package/dist/lessons/source-project.d.ts +9 -0
- package/dist/lessons/source-project.d.ts.map +1 -0
- package/dist/lessons/source-project.js +149 -0
- package/dist/lessons/source-project.js.map +1 -0
- package/dist/lessons/store.d.ts +135 -0
- package/dist/lessons/store.d.ts.map +1 -0
- package/dist/lessons/store.js +222 -0
- package/dist/lessons/store.js.map +1 -0
- package/dist/memory/config.d.ts +27 -0
- package/dist/memory/config.d.ts.map +1 -1
- package/dist/memory/config.js +67 -0
- package/dist/memory/config.js.map +1 -1
- package/dist/memory/local.d.ts.map +1 -1
- package/dist/memory/local.js +19 -1
- package/dist/memory/local.js.map +1 -1
- package/dist/memory/schema.d.ts +1 -1
- package/dist/memory/schema.d.ts.map +1 -1
- package/dist/memory/schema.js +104 -0
- package/dist/memory/schema.js.map +1 -1
- package/dist/server/handlers/council.d.ts.map +1 -1
- package/dist/server/handlers/council.js +40 -4
- package/dist/server/handlers/council.js.map +1 -1
- package/dist/server/handlers/memory.d.ts.map +1 -1
- package/dist/server/handlers/memory.js +13 -3
- package/dist/server/handlers/memory.js.map +1 -1
- package/dist/server/handlers/session.d.ts.map +1 -1
- package/dist/server/handlers/session.js +61 -21
- package/dist/server/handlers/session.js.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +12 -3
- package/dist/server.js.map +1 -1
- package/dist/transcripts/cache.d.ts +1 -0
- package/dist/transcripts/cache.d.ts.map +1 -1
- package/dist/transcripts/cache.js +0 -0
- package/dist/transcripts/cache.js.map +1 -1
- package/dist/transcripts/claude-paths.d.ts +5 -0
- package/dist/transcripts/claude-paths.d.ts.map +1 -0
- package/dist/transcripts/claude-paths.js +21 -0
- package/dist/transcripts/claude-paths.js.map +1 -0
- package/dist/transcripts/context.d.ts +41 -0
- package/dist/transcripts/context.d.ts.map +1 -0
- package/dist/transcripts/context.js +144 -0
- package/dist/transcripts/context.js.map +1 -0
- package/dist/transcripts/discover.d.ts +12 -2
- package/dist/transcripts/discover.d.ts.map +1 -1
- package/dist/transcripts/discover.js +55 -7
- package/dist/transcripts/discover.js.map +1 -1
- package/dist/transcripts/manage.d.ts +8 -0
- package/dist/transcripts/manage.d.ts.map +1 -1
- package/dist/transcripts/manage.js +28 -13
- package/dist/transcripts/manage.js.map +1 -1
- package/dist/transcripts/mapping.d.ts.map +1 -1
- package/dist/transcripts/mapping.js +4 -2
- package/dist/transcripts/mapping.js.map +1 -1
- package/dist/transcripts/on-save.d.ts +9 -0
- package/dist/transcripts/on-save.d.ts.map +1 -1
- package/dist/transcripts/on-save.js +36 -9
- package/dist/transcripts/on-save.js.map +1 -1
- package/dist/transcripts/project-key.d.ts +4 -0
- package/dist/transcripts/project-key.d.ts.map +1 -0
- package/dist/transcripts/project-key.js +30 -0
- package/dist/transcripts/project-key.js.map +1 -0
- package/dist/transcripts/recall.d.ts +4 -0
- package/dist/transcripts/recall.d.ts.map +1 -1
- package/dist/transcripts/recall.js +6 -5
- package/dist/transcripts/recall.js.map +1 -1
- package/dist/transcripts/stopwords.d.ts +2 -0
- package/dist/transcripts/stopwords.d.ts.map +1 -0
- package/dist/transcripts/stopwords.js +8 -0
- package/dist/transcripts/stopwords.js.map +1 -0
- package/package.json +1 -1
- package/server.json +2 -2
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { existsSync, readdirSync, statSync } from 'node:fs';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { basename, join } from 'node:path';
|
|
4
|
+
function candidate(source, sourcePath, root, extra = {}) {
|
|
5
|
+
try {
|
|
6
|
+
const stat = statSync(sourcePath);
|
|
7
|
+
return stat.isFile() ? { source, sourcePath, fileName: basename(sourcePath), mtimeMs: stat.mtimeMs, global: false, root, ...extra } : null;
|
|
8
|
+
}
|
|
9
|
+
catch {
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
function claudeMemoryFiles(claudeHome) {
|
|
14
|
+
const projects = join(claudeHome, 'projects');
|
|
15
|
+
if (!existsSync(projects))
|
|
16
|
+
return [];
|
|
17
|
+
const out = [];
|
|
18
|
+
let folders;
|
|
19
|
+
try {
|
|
20
|
+
folders = readdirSync(projects, { withFileTypes: true });
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return out;
|
|
24
|
+
}
|
|
25
|
+
for (const folder of folders) {
|
|
26
|
+
if (!folder.isDirectory())
|
|
27
|
+
continue;
|
|
28
|
+
const claudeFolder = join(projects, folder.name);
|
|
29
|
+
const memory = join(claudeFolder, 'memory');
|
|
30
|
+
let files;
|
|
31
|
+
try {
|
|
32
|
+
files = readdirSync(memory, { withFileTypes: true });
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
for (const file of files) {
|
|
38
|
+
if (!file.isFile() || !file.name.toLowerCase().endsWith('.md'))
|
|
39
|
+
continue;
|
|
40
|
+
const found = candidate('claude', join(memory, file.name), claudeHome, { claudeFolder, claudeSlug: folder.name });
|
|
41
|
+
if (found)
|
|
42
|
+
out.push(found);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return out;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Known native-memory locations only; discovery reads directory listings and
|
|
49
|
+
* file metadata and performs no writes. CODEX_HOME relocates ~/.codex, as it
|
|
50
|
+
* does for Codex itself.
|
|
51
|
+
*/
|
|
52
|
+
export function discoverNativeMemorySources(home = homedir()) {
|
|
53
|
+
const codexHome = home === homedir() && process.env.CODEX_HOME ? process.env.CODEX_HOME : join(home, '.codex');
|
|
54
|
+
const out = claudeMemoryFiles(join(home, '.claude'));
|
|
55
|
+
for (const path of [join(codexHome, 'AGENTS.md'), join(codexHome, 'AGENTS.override.md')]) {
|
|
56
|
+
const found = candidate('codex', path, codexHome, { global: true });
|
|
57
|
+
if (found)
|
|
58
|
+
out.push(found);
|
|
59
|
+
}
|
|
60
|
+
const gemini = candidate('gemini', join(home, '.gemini', 'GEMINI.md'), join(home, '.gemini'), { global: true });
|
|
61
|
+
if (gemini)
|
|
62
|
+
out.push(gemini);
|
|
63
|
+
return out.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=discover.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"discover.js","sourceRoot":"","sources":["../../src/lessons/discover.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { type LessonSource } from './adapters/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Bump when anything that decides what a file yields changes: this harvester,
|
|
4
|
+
* the adapters, mask.ts or classify.ts. Every remembered file fingerprint is
|
|
5
|
+
* invalidated, so the next pass re-reads and re-classifies everything.
|
|
6
|
+
* `tests/lessons-harvester-version.test.ts` fails if those files change
|
|
7
|
+
* without this being bumped, so the reminder is not a comment alone.
|
|
8
|
+
*/
|
|
9
|
+
export declare const HARVESTER_VERSION = 1;
|
|
10
|
+
export type HarvestOutcome = {
|
|
11
|
+
status: 'harvested';
|
|
12
|
+
inserted: number;
|
|
13
|
+
updated: number;
|
|
14
|
+
removed: number;
|
|
15
|
+
} | {
|
|
16
|
+
status: 'unchanged';
|
|
17
|
+
} | {
|
|
18
|
+
status: 'skipped';
|
|
19
|
+
reason: 'empty' | 'index' | 'veto-authored';
|
|
20
|
+
removed: number;
|
|
21
|
+
} | {
|
|
22
|
+
status: 'disabled' | 'unavailable';
|
|
23
|
+
reason: string;
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Read and harvest a single host-native memory file. The source is only ever
|
|
27
|
+
* opened for reading. An unrecognised shape disables that host's harvester
|
|
28
|
+
* (the format-drift canary) and leaves its existing rows untouched; selection
|
|
29
|
+
* stops serving them while the host is disabled.
|
|
30
|
+
*/
|
|
31
|
+
export declare function harvestNativeMemory(input: {
|
|
32
|
+
source: LessonSource;
|
|
33
|
+
sourcePath: string;
|
|
34
|
+
projectIdentity: string;
|
|
35
|
+
projectLabel?: string | null;
|
|
36
|
+
/** The project's own names; a note naming its project stays project scope. */
|
|
37
|
+
projectNames?: string[];
|
|
38
|
+
global?: boolean;
|
|
39
|
+
/** The host directory this file was found under; it may not resolve outside it. */
|
|
40
|
+
root?: string;
|
|
41
|
+
/** Ignore what this file looked like last time and read it again regardless. */
|
|
42
|
+
forced?: boolean;
|
|
43
|
+
}): HarvestOutcome;
|
|
44
|
+
export type LessonSyncReport = {
|
|
45
|
+
consent: boolean;
|
|
46
|
+
sources: number;
|
|
47
|
+
harvested: number;
|
|
48
|
+
/** Files whose bytes and project are what they were last time; nothing was parsed. */
|
|
49
|
+
unchanged: number;
|
|
50
|
+
skipped: number;
|
|
51
|
+
disabled: number;
|
|
52
|
+
unavailable: number;
|
|
53
|
+
/** Sources belonging to a project kept out of sharing; nothing of theirs is stored. */
|
|
54
|
+
excluded: number;
|
|
55
|
+
inserted: number;
|
|
56
|
+
updated: number;
|
|
57
|
+
removed: number;
|
|
58
|
+
unresolvedProjects: number;
|
|
59
|
+
/** Files the pass never reached because it ran out of its time budget. */
|
|
60
|
+
notReached: number;
|
|
61
|
+
elapsedMs: number;
|
|
62
|
+
};
|
|
63
|
+
export type SyncOptions = {
|
|
64
|
+
home?: string;
|
|
65
|
+
/** Re-read every file even if it looks untouched. Policy changes need this. */
|
|
66
|
+
forced?: boolean;
|
|
67
|
+
/**
|
|
68
|
+
* Stop once this many milliseconds have gone, checked before every file.
|
|
69
|
+
* What is left waits for the next pass; the work is idempotent, so nothing
|
|
70
|
+
* is lost by stopping. Only the save path sets it.
|
|
71
|
+
*/
|
|
72
|
+
budgetMs?: number;
|
|
73
|
+
};
|
|
74
|
+
/**
|
|
75
|
+
* One pass: discover every known native-memory file, harvest what changed
|
|
76
|
+
* under its project's identity, and delete rows whose file has gone. Nothing
|
|
77
|
+
* is read until the user has accepted consent v2, and an excluded project's
|
|
78
|
+
* files are never read at all.
|
|
79
|
+
*
|
|
80
|
+
* A pass given a budget stops cleanly when it runs out and reports what it did
|
|
81
|
+
* not reach. Every step is idempotent, so the next pass simply carries on.
|
|
82
|
+
*/
|
|
83
|
+
export declare function syncLessonSources(options?: string | SyncOptions): LessonSyncReport;
|
|
84
|
+
//# sourceMappingURL=harvest.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"harvest.d.ts","sourceRoot":"","sources":["../../src/lessons/harvest.ts"],"names":[],"mappings":"AAIA,OAAO,EAAqB,KAAK,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAY3E;;;;;;GAMG;AACH,eAAO,MAAM,iBAAiB,IAAI,CAAC;AAgBnC,MAAM,MAAM,cAAc,GACtB;IAAE,MAAM,EAAE,WAAW,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAC3E;IAAE,MAAM,EAAE,WAAW,CAAA;CAAE,GACvB;IAAE,MAAM,EAAE,SAAS,CAAC;IAAC,MAAM,EAAE,OAAO,GAAG,OAAO,GAAG,eAAe,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GACnF;IAAE,MAAM,EAAE,UAAU,GAAG,aAAa,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAsC3D;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE;IACzC,MAAM,EAAE,YAAY,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,EAAE,MAAM,CAAC;IACxB,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,8EAA8E;IAC9E,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,mFAAmF;IACnF,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,gFAAgF;IAChF,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB,GAAG,cAAc,CA4EjB;AAED,MAAM,MAAM,gBAAgB,GAAG;IAC7B,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,sFAAsF;IACtF,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,uFAAuF;IACvF,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,0EAA0E;IAC1E,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACxB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,+EAA+E;IAC/E,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AAyCF;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,GAAE,MAAM,GAAG,WAAgB,GAAG,gBAAgB,CAsDtF"}
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { readFileSync, realpathSync, statSync } from 'node:fs';
|
|
3
|
+
import { basename, dirname, relative, isAbsolute } from 'node:path';
|
|
4
|
+
import { isLessonsSharingEnabled } from '../memory/config.js';
|
|
5
|
+
import { parseNativeMemory } from './adapters/index.js';
|
|
6
|
+
import { classifyLesson } from './classify.js';
|
|
7
|
+
import { discoverNativeMemorySources } from './discover.js';
|
|
8
|
+
import { projectNames, resolveProjectIdentity } from './identity.js';
|
|
9
|
+
import { maskLessonText } from './mask.js';
|
|
10
|
+
import { resolveClaudeProjectFolder } from './source-project.js';
|
|
11
|
+
import { deleteLessonSource, disableLessonSource, isLessonSourceEnabled, isProjectExcluded, lessonFileState, purgeVanishedLessonFileState, purgeVanishedLessonSources, recordLessonFileState, recordLessonFileUnavailable, syncLessons, unresolvedProjectCount, } from './store.js';
|
|
12
|
+
/**
|
|
13
|
+
* Bump when anything that decides what a file yields changes: this harvester,
|
|
14
|
+
* the adapters, mask.ts or classify.ts. Every remembered file fingerprint is
|
|
15
|
+
* invalidated, so the next pass re-reads and re-classifies everything.
|
|
16
|
+
* `tests/lessons-harvester-version.test.ts` fails if those files change
|
|
17
|
+
* without this being bumped, so the reminder is not a comment alone.
|
|
18
|
+
*/
|
|
19
|
+
export const HARVESTER_VERSION = 1;
|
|
20
|
+
/**
|
|
21
|
+
* A memory file is a hand-written note; anything this large is not one. The
|
|
22
|
+
* cap stops an unbounded read on the save path (council 534e2bd5, security).
|
|
23
|
+
*/
|
|
24
|
+
const MAX_SOURCE_BYTES = 2 * 1024 * 1024;
|
|
25
|
+
/**
|
|
26
|
+
* After this many passes in a row where a file could not be read, a save stops
|
|
27
|
+
* probing it. Reading it is what costs seconds when the drive holding it is no
|
|
28
|
+
* longer plugged in, and a save must not wait for that. A command still tries
|
|
29
|
+
* every time, so plugging the drive back in and running `veto lessons` works.
|
|
30
|
+
*/
|
|
31
|
+
const UNAVAILABLE_BACKOFF_AFTER = 3;
|
|
32
|
+
/**
|
|
33
|
+
* A memory file is followed only while it resolves inside the host directory
|
|
34
|
+
* it was discovered under: a link dropped into a memory folder must not turn
|
|
35
|
+
* the harvester into a reader of arbitrary files elsewhere on the machine.
|
|
36
|
+
*
|
|
37
|
+
* The boundary is that host directory and NOT the user's home, because neither
|
|
38
|
+
* is reliably inside the other — CODEX_HOME and a relocated Claude directory
|
|
39
|
+
* both live wherever the user puts them, and a home-based rule would refuse
|
|
40
|
+
* every note those users have.
|
|
41
|
+
*
|
|
42
|
+
* The whole path is resolved rather than just its last part, so a symlinked
|
|
43
|
+
* folder above the file is caught as well.
|
|
44
|
+
*/
|
|
45
|
+
function resolvesOutsideRoot(sourcePath, root) {
|
|
46
|
+
try {
|
|
47
|
+
const rel = relative(realpathSync(root), realpathSync(sourcePath));
|
|
48
|
+
return rel.startsWith('..') || isAbsolute(rel);
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Everything that decides what this file yields: its bytes, and the project
|
|
56
|
+
* identity and names that classification reads. Identity is part of it because
|
|
57
|
+
* an alias can move a file to another project without touching its contents —
|
|
58
|
+
* skipping on content alone would silently ignore `veto lessons alias`.
|
|
59
|
+
*/
|
|
60
|
+
function fingerprint(raw, input) {
|
|
61
|
+
return createHash('sha256')
|
|
62
|
+
.update(raw).update('\u0000')
|
|
63
|
+
.update(input.projectIdentity).update('\u0000')
|
|
64
|
+
.update(input.projectLabel ?? '').update('\u0000')
|
|
65
|
+
.update((input.projectNames ?? []).join('\u0000')).update('\u0000')
|
|
66
|
+
.update(input.global === true ? '1' : '0')
|
|
67
|
+
.digest('hex');
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Read and harvest a single host-native memory file. The source is only ever
|
|
71
|
+
* opened for reading. An unrecognised shape disables that host's harvester
|
|
72
|
+
* (the format-drift canary) and leaves its existing rows untouched; selection
|
|
73
|
+
* stops serving them while the host is disabled.
|
|
74
|
+
*/
|
|
75
|
+
export function harvestNativeMemory(input) {
|
|
76
|
+
if (!isLessonSourceEnabled(input.source))
|
|
77
|
+
return { status: 'disabled', reason: 'source disabled after format drift' };
|
|
78
|
+
// Defaults to the file's own folder when a caller names no root, which is
|
|
79
|
+
// the strictest sensible reading of "where this file is allowed to live".
|
|
80
|
+
if (resolvesOutsideRoot(input.sourcePath, input.root ?? dirname(input.sourcePath))) {
|
|
81
|
+
return { status: 'unavailable', reason: "memory file resolves outside its host's own folder and was not read" };
|
|
82
|
+
}
|
|
83
|
+
// Stat first: an unchanged file costs this and nothing more. No transaction
|
|
84
|
+
// is opened on this path, so a quiet pass takes no write lock at all.
|
|
85
|
+
let size;
|
|
86
|
+
let mtime;
|
|
87
|
+
try {
|
|
88
|
+
const stat = statSync(input.sourcePath);
|
|
89
|
+
size = stat.size;
|
|
90
|
+
mtime = stat.mtime.toISOString();
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
recordLessonFileUnavailable(input.source, input.sourcePath);
|
|
94
|
+
return { status: 'unavailable', reason: 'native memory file could not be read' };
|
|
95
|
+
}
|
|
96
|
+
if (size > MAX_SOURCE_BYTES) {
|
|
97
|
+
// Left alone rather than emptied: this is a file we declined to read, not
|
|
98
|
+
// a file we read and found nothing in.
|
|
99
|
+
return { status: 'unavailable', reason: `memory file is larger than ${MAX_SOURCE_BYTES / (1024 * 1024)} MB and was not read` };
|
|
100
|
+
}
|
|
101
|
+
const known = input.forced ? null : lessonFileState(input.source, input.sourcePath);
|
|
102
|
+
// Identity is part of the comparison because an alias, or a drive coming
|
|
103
|
+
// back, moves a file to another project without touching a byte of it. It
|
|
104
|
+
// costs nothing here: the caller has already worked out which project this
|
|
105
|
+
// file belongs to.
|
|
106
|
+
const current = known?.harvester_version === HARVESTER_VERSION && known?.project_identity === input.projectIdentity;
|
|
107
|
+
if (current && known?.mtime === mtime && known?.size === size)
|
|
108
|
+
return { status: 'unchanged' };
|
|
109
|
+
let raw;
|
|
110
|
+
try {
|
|
111
|
+
raw = readFileSync(input.sourcePath, 'utf8');
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
recordLessonFileUnavailable(input.source, input.sourcePath);
|
|
115
|
+
return { status: 'unavailable', reason: 'native memory file could not be read' };
|
|
116
|
+
}
|
|
117
|
+
// mtime moved but the content did not — a touch, a sync tool, a restore.
|
|
118
|
+
// Remember the new mtime so the cheap path hits next time, and parse nothing.
|
|
119
|
+
const digest = fingerprint(raw, input);
|
|
120
|
+
const remember = () => recordLessonFileState({
|
|
121
|
+
source: input.source, sourcePath: input.sourcePath, mtime, size, contentHash: digest,
|
|
122
|
+
projectIdentity: input.projectIdentity, harvesterVersion: HARVESTER_VERSION,
|
|
123
|
+
});
|
|
124
|
+
if (current && known?.content_hash === digest) {
|
|
125
|
+
remember();
|
|
126
|
+
return { status: 'unchanged' };
|
|
127
|
+
}
|
|
128
|
+
const fileName = basename(input.sourcePath);
|
|
129
|
+
const parsed = parseNativeMemory(input.source, raw, fileName);
|
|
130
|
+
if (!parsed.ok) {
|
|
131
|
+
disableLessonSource(input.source, `${fileName}: ${parsed.reason}`);
|
|
132
|
+
return { status: 'disabled', reason: parsed.reason };
|
|
133
|
+
}
|
|
134
|
+
const base = { sourceCli: input.source, sourcePath: input.sourcePath, projectIdentity: input.projectIdentity, projectLabel: input.projectLabel ?? null, sourceMtime: mtime };
|
|
135
|
+
if ('skip' in parsed) {
|
|
136
|
+
const removed = syncLessons({ ...base, sections: [] }).removed;
|
|
137
|
+
remember();
|
|
138
|
+
return { status: 'skipped', reason: parsed.skip, removed };
|
|
139
|
+
}
|
|
140
|
+
const sections = parsed.sections.map(section => {
|
|
141
|
+
const masked = maskLessonText(section.text);
|
|
142
|
+
const classified = classifyLesson({
|
|
143
|
+
source: input.source, fileName, global: input.global === true, frontmatter: parsed.frontmatter,
|
|
144
|
+
section, maskedText: masked.text, secrets: masked.secrets, projectNames: input.projectNames,
|
|
145
|
+
});
|
|
146
|
+
return { anchor: section.anchor, textMasked: masked.text, ...classified };
|
|
147
|
+
});
|
|
148
|
+
const synced = syncLessons({ ...base, sections });
|
|
149
|
+
remember();
|
|
150
|
+
return { status: 'harvested', ...synced };
|
|
151
|
+
}
|
|
152
|
+
function sourceProject(source, folders) {
|
|
153
|
+
if (source.global || !source.claudeFolder || !source.claudeSlug)
|
|
154
|
+
return { identity: `global:${source.source}`, label: null, names: [] };
|
|
155
|
+
const cached = folders.get(source.claudeFolder);
|
|
156
|
+
if (cached)
|
|
157
|
+
return cached;
|
|
158
|
+
const { projectDir } = resolveClaudeProjectFolder(source.claudeFolder, source.claudeSlug);
|
|
159
|
+
const resolved = projectDir
|
|
160
|
+
? { identity: resolveProjectIdentity(projectDir), label: basename(projectDir.replace(/[\\/]+$/, '')) || projectDir, names: projectNames(projectDir) }
|
|
161
|
+
: { identity: `claude-slug:${source.claudeSlug}`, label: source.claudeSlug, names: [] };
|
|
162
|
+
folders.set(source.claudeFolder, resolved);
|
|
163
|
+
return resolved;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Whether a file can be passed over without reading it or resolving which
|
|
167
|
+
* project it belongs to. Deliberately cheap: one stat and one indexed read.
|
|
168
|
+
* Project resolution is the expensive part of a pass — it can walk a drive
|
|
169
|
+
* root — so a quiet pass must never get that far.
|
|
170
|
+
*/
|
|
171
|
+
function untouchedSince(source, recheckUnresolved) {
|
|
172
|
+
const known = lessonFileState(source.source, source.sourcePath);
|
|
173
|
+
if (!known)
|
|
174
|
+
return false;
|
|
175
|
+
// A file that has failed to be read this many times running is treated as
|
|
176
|
+
// gone until a command looks for it again; even the stat is skipped, because
|
|
177
|
+
// the stat is the part that hangs.
|
|
178
|
+
if (!recheckUnresolved && known.unavailable_streak >= UNAVAILABLE_BACKOFF_AFTER)
|
|
179
|
+
return 'gone';
|
|
180
|
+
if (known.harvester_version !== HARVESTER_VERSION)
|
|
181
|
+
return false;
|
|
182
|
+
// A folder never traced to a directory on this machine may become traceable
|
|
183
|
+
// when a drive is plugged back in, and only resolving it again would notice.
|
|
184
|
+
// A pass with time to spare looks; a save does not, because that lookup is
|
|
185
|
+
// the one that walks a drive root.
|
|
186
|
+
if (recheckUnresolved && known.project_identity?.startsWith('claude-slug:'))
|
|
187
|
+
return false;
|
|
188
|
+
try {
|
|
189
|
+
const stat = statSync(source.sourcePath);
|
|
190
|
+
return known.mtime === stat.mtime.toISOString() && known.size === stat.size;
|
|
191
|
+
}
|
|
192
|
+
catch {
|
|
193
|
+
return false;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* One pass: discover every known native-memory file, harvest what changed
|
|
198
|
+
* under its project's identity, and delete rows whose file has gone. Nothing
|
|
199
|
+
* is read until the user has accepted consent v2, and an excluded project's
|
|
200
|
+
* files are never read at all.
|
|
201
|
+
*
|
|
202
|
+
* A pass given a budget stops cleanly when it runs out and reports what it did
|
|
203
|
+
* not reach. Every step is idempotent, so the next pass simply carries on.
|
|
204
|
+
*/
|
|
205
|
+
export function syncLessonSources(options = {}) {
|
|
206
|
+
const opts = typeof options === 'string' ? { home: options } : options;
|
|
207
|
+
const started = Date.now();
|
|
208
|
+
const report = {
|
|
209
|
+
consent: isLessonsSharingEnabled(), sources: 0, harvested: 0, unchanged: 0, skipped: 0, disabled: 0, unavailable: 0, excluded: 0,
|
|
210
|
+
inserted: 0, updated: 0, removed: 0, unresolvedProjects: 0, notReached: 0, elapsedMs: 0,
|
|
211
|
+
};
|
|
212
|
+
if (!report.consent)
|
|
213
|
+
return report;
|
|
214
|
+
const sources = discoverNativeMemorySources(opts.home);
|
|
215
|
+
report.sources = sources.length;
|
|
216
|
+
const folders = new Map();
|
|
217
|
+
// Only a pass that is not racing a save can afford to look for folders that
|
|
218
|
+
// were unresolved last time.
|
|
219
|
+
const recheckUnresolved = opts.budgetMs === undefined;
|
|
220
|
+
let index = 0;
|
|
221
|
+
for (const source of sources) {
|
|
222
|
+
// Checked before every file, not once at the start: a single slow stat on
|
|
223
|
+
// a drive that is no longer plugged in must not carry the whole pass past
|
|
224
|
+
// its bound (council 534e2bd5, devil).
|
|
225
|
+
if (opts.budgetMs !== undefined && Date.now() - started >= opts.budgetMs) {
|
|
226
|
+
report.notReached = sources.length - index;
|
|
227
|
+
break;
|
|
228
|
+
}
|
|
229
|
+
index++;
|
|
230
|
+
if (!opts.forced) {
|
|
231
|
+
const state = untouchedSince(source, recheckUnresolved);
|
|
232
|
+
if (state === 'gone') {
|
|
233
|
+
report.unavailable++;
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
if (state) {
|
|
237
|
+
report.unchanged++;
|
|
238
|
+
continue;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
const project = sourceProject(source, folders);
|
|
242
|
+
if (isProjectExcluded(project.identity)) {
|
|
243
|
+
// Not even read: an excluded project's notes never reach the store.
|
|
244
|
+
report.excluded++;
|
|
245
|
+
report.removed += deleteLessonSource(source.source, source.sourcePath);
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
248
|
+
const outcome = harvestNativeMemory({
|
|
249
|
+
source: source.source, sourcePath: source.sourcePath, projectIdentity: project.identity,
|
|
250
|
+
projectLabel: project.label, projectNames: project.names, global: source.global, root: source.root, forced: opts.forced,
|
|
251
|
+
});
|
|
252
|
+
report[outcome.status]++;
|
|
253
|
+
if (outcome.status === 'harvested') {
|
|
254
|
+
report.inserted += outcome.inserted;
|
|
255
|
+
report.updated += outcome.updated;
|
|
256
|
+
report.removed += outcome.removed;
|
|
257
|
+
}
|
|
258
|
+
if (outcome.status === 'skipped')
|
|
259
|
+
report.removed += outcome.removed;
|
|
260
|
+
}
|
|
261
|
+
report.unresolvedProjects = unresolvedProjectCount();
|
|
262
|
+
// A pass cut short has not seen every source, so it cannot conclude that a
|
|
263
|
+
// missing one is gone for good.
|
|
264
|
+
if (report.notReached === 0) {
|
|
265
|
+
report.removed += purgeVanishedLessonSources(sources);
|
|
266
|
+
purgeVanishedLessonFileState(sources);
|
|
267
|
+
}
|
|
268
|
+
report.elapsedMs = Date.now() - started;
|
|
269
|
+
return report;
|
|
270
|
+
}
|
|
271
|
+
//# 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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"}
|