@jigyasudham/veto 3.4.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 +36 -0
- package/dist/agents/llm-runner.d.ts.map +1 -1
- package/dist/agents/llm-runner.js +61 -4
- package/dist/agents/llm-runner.js.map +1 -1
- 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.js +1 -1
- package/dist/cli/statusline-hosts.js.map +1 -1
- package/dist/cli/statusline.d.ts +1 -0
- package/dist/cli/statusline.d.ts.map +1 -1
- package/dist/cli/statusline.js +18 -1
- package/dist/cli/statusline.js.map +1 -1
- package/dist/cli.js +10 -0
- package/dist/cli.js.map +1 -1
- package/dist/lessons/classify.d.ts +3 -0
- package/dist/lessons/classify.d.ts.map +1 -1
- package/dist/lessons/classify.js +12 -5
- package/dist/lessons/classify.js.map +1 -1
- 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 +7 -0
- package/dist/lessons/discover.d.ts.map +1 -1
- package/dist/lessons/discover.js +5 -5
- package/dist/lessons/discover.js.map +1 -1
- package/dist/lessons/harvest.d.ts +40 -4
- package/dist/lessons/harvest.d.ts.map +1 -1
- package/dist/lessons/harvest.js +192 -17
- package/dist/lessons/harvest.js.map +1 -1
- 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/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 +1 -1
- package/dist/lessons/select.d.ts.map +1 -1
- package/dist/lessons/select.js +8 -6
- package/dist/lessons/select.js.map +1 -1
- package/dist/lessons/store.d.ts +80 -1
- package/dist/lessons/store.d.ts.map +1 -1
- package/dist/lessons/store.js +147 -10
- package/dist/lessons/store.js.map +1 -1
- package/dist/memory/config.d.ts +13 -0
- package/dist/memory/config.d.ts.map +1 -1
- package/dist/memory/config.js +24 -0
- package/dist/memory/config.js.map +1 -1
- package/dist/memory/local.d.ts.map +1 -1
- package/dist/memory/local.js +6 -3
- 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 +50 -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 +36 -3
- package/dist/server/handlers/council.js.map +1 -1
- package/dist/server/handlers/session.d.ts.map +1 -1
- package/dist/server/handlers/session.js +10 -0
- package/dist/server/handlers/session.js.map +1 -1
- package/package.json +1 -1
- package/server.json +2 -2
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { type LessonSyncReport } from './harvest.js';
|
|
2
|
+
/**
|
|
3
|
+
* What each AI CLI sets in the environment of the commands it runs.
|
|
4
|
+
* CLAUDECODE and AI_AGENT are set by Claude Code (checked on this machine);
|
|
5
|
+
* GEMINI_CLI by Gemini CLI's shell tool; the CODEX_ ones by Codex's sandboxed
|
|
6
|
+
* and npm-launched runs. A real terminal is required as well, so a marker a
|
|
7
|
+
* host does not set is not the only line of defence.
|
|
8
|
+
*/
|
|
9
|
+
export declare const AI_SESSION_MARKERS: readonly ["CLAUDECODE", "AI_AGENT", "GEMINI_CLI", "CODEX_SANDBOX", "CODEX_SANDBOX_NETWORK_DISABLED", "CODEX_MANAGED_BY_NPM"];
|
|
10
|
+
/** The marker showing an AI is running this command, or null in the user's own terminal. */
|
|
11
|
+
export declare function detectAiSession(env?: NodeJS.ProcessEnv): string | null;
|
|
12
|
+
/**
|
|
13
|
+
* The disclosure shown before the user accepts. Changing what it promises
|
|
14
|
+
* means bumping LESSONS_CONSENT_VERSION, and so does starting delivery: this
|
|
15
|
+
* text promises to ask again first.
|
|
16
|
+
*/
|
|
17
|
+
export declare function lessonsDisclosure(): string;
|
|
18
|
+
export type ConsentSummary = {
|
|
19
|
+
report: LessonSyncReport;
|
|
20
|
+
notes: number;
|
|
21
|
+
projects: number;
|
|
22
|
+
anyProject: number;
|
|
23
|
+
ownProject: number;
|
|
24
|
+
unlinked: number;
|
|
25
|
+
byHost: Record<'claude' | 'codex' | 'gemini', number>;
|
|
26
|
+
disabledHosts: string[];
|
|
27
|
+
};
|
|
28
|
+
/** Record consent, read every AI's memory once, and summarise what was found. */
|
|
29
|
+
export declare function acceptLessonsConsent(home?: string): ConsentSummary;
|
|
30
|
+
//# sourceMappingURL=consent.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"consent.d.ts","sourceRoot":"","sources":["../../src/lessons/consent.ts"],"names":[],"mappings":"AAOA,OAAO,EAAqB,KAAK,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAGxE;;;;;;GAMG;AACH,eAAO,MAAM,kBAAkB,8HAA+H,CAAC;AAE/J,4FAA4F;AAC5F,wBAAgB,eAAe,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,GAAG,IAAI,CAEnF;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,CA0C1C;AAED,MAAM,MAAM,cAAc,GAAG;IAC3B,MAAM,EAAE,gBAAgB,CAAC;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC,QAAQ,GAAG,OAAO,GAAG,QAAQ,EAAE,MAAM,CAAC,CAAC;IACtD,aAAa,EAAE,MAAM,EAAE,CAAC;CACzB,CAAC;AAEF,iFAAiF;AACjF,wBAAgB,oBAAoB,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,cAAc,CAelE"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
// Consent v2 for Harvest-and-Share (council 320c40dc, legal conditions): it
|
|
2
|
+
// names both flows, across projects and between AI vendors, is OFF by default,
|
|
3
|
+
// and no upgrade can switch it on. Only the user can accept it, typing "yes"
|
|
4
|
+
// in a terminal of their own: a command an AI runs is refused, so no AI can
|
|
5
|
+
// turn sharing on for them (owner decision, 2026-09-19).
|
|
6
|
+
import { enableLessonsSharing } from '../memory/config.js';
|
|
7
|
+
import { syncLessonSources } from './harvest.js';
|
|
8
|
+
import { lessonFlows, lessonsStatus, unresolvedFolders } from './manage.js';
|
|
9
|
+
/**
|
|
10
|
+
* What each AI CLI sets in the environment of the commands it runs.
|
|
11
|
+
* CLAUDECODE and AI_AGENT are set by Claude Code (checked on this machine);
|
|
12
|
+
* GEMINI_CLI by Gemini CLI's shell tool; the CODEX_ ones by Codex's sandboxed
|
|
13
|
+
* and npm-launched runs. A real terminal is required as well, so a marker a
|
|
14
|
+
* host does not set is not the only line of defence.
|
|
15
|
+
*/
|
|
16
|
+
export const AI_SESSION_MARKERS = ['CLAUDECODE', 'AI_AGENT', 'GEMINI_CLI', 'CODEX_SANDBOX', 'CODEX_SANDBOX_NETWORK_DISABLED', 'CODEX_MANAGED_BY_NPM'];
|
|
17
|
+
/** The marker showing an AI is running this command, or null in the user's own terminal. */
|
|
18
|
+
export function detectAiSession(env = process.env) {
|
|
19
|
+
return AI_SESSION_MARKERS.find(name => (env[name] ?? '') !== '') ?? null;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* The disclosure shown before the user accepts. Changing what it promises
|
|
23
|
+
* means bumping LESSONS_CONSENT_VERSION, and so does starting delivery: this
|
|
24
|
+
* text promises to ask again first.
|
|
25
|
+
*/
|
|
26
|
+
export function lessonsDisclosure() {
|
|
27
|
+
return [
|
|
28
|
+
'What you are turning on: sharing between your AIs\' notes',
|
|
29
|
+
'',
|
|
30
|
+
'Claude, Codex and Gemini each keep notes in their own memory (Claude\'s',
|
|
31
|
+
'memory folders, ~/.codex/AGENTS.md, ~/.gemini/GEMINI.md). With sharing on,',
|
|
32
|
+
'Veto passes those notes on where they help:',
|
|
33
|
+
'',
|
|
34
|
+
' • ACROSS YOUR PROJECTS: a note about you or this computer, written while',
|
|
35
|
+
' you worked on one project, can be shared into your other projects.',
|
|
36
|
+
' • BETWEEN AIs: a note one AI wrote can be shared with the others, so a',
|
|
37
|
+
' note Claude wrote can reach Codex and Gemini, and the other way round.',
|
|
38
|
+
' A note given to an AI goes to that AI\'s company as part of your',
|
|
39
|
+
' conversation, like anything else you send it.',
|
|
40
|
+
'',
|
|
41
|
+
'What Veto does, and does not do:',
|
|
42
|
+
' • It only reads those memory files. It never changes or deletes them.',
|
|
43
|
+
' • It reads them again each time you save a session, so notes you make',
|
|
44
|
+
' today are picked up without you running anything. `veto lessons off`',
|
|
45
|
+
' stops it, and so does turning harvest_on_save off in Veto\'s config.',
|
|
46
|
+
' • It keeps a copy of each note in its own database on this computer,',
|
|
47
|
+
' with email addresses, your home folder and anything that looks like a',
|
|
48
|
+
' password or key removed first. Veto itself uploads nothing.',
|
|
49
|
+
' • A note stays in its own project if it is about that project, or if it',
|
|
50
|
+
' contains a command, a web address, a credential or an instruction to',
|
|
51
|
+
' fetch, send or run something. Notes about secrets or personal details',
|
|
52
|
+
' never leave their project.',
|
|
53
|
+
' • A shared note is marked as information from another AI session, never',
|
|
54
|
+
' as an instruction to follow.',
|
|
55
|
+
'',
|
|
56
|
+
'For now this is a trial: Veto reads the notes and keeps its own masked',
|
|
57
|
+
'copy of them on this computer, but gives none of them to any AI yet, and',
|
|
58
|
+
'works out nothing about which ones it would give. Before it starts giving',
|
|
59
|
+
'notes to your AIs, Veto will ask you again.',
|
|
60
|
+
'',
|
|
61
|
+
'You stay in control:',
|
|
62
|
+
' veto lessons list every note Veto has read',
|
|
63
|
+
' veto lessons why <id> one note, where it came from, where it may go',
|
|
64
|
+
' veto lessons forget <id> stop one note for good',
|
|
65
|
+
' veto lessons exclude keep the current project out entirely',
|
|
66
|
+
' veto lessons off turn sharing off and delete everything Veto copied',
|
|
67
|
+
].join('\n');
|
|
68
|
+
}
|
|
69
|
+
/** Record consent, read every AI's memory once, and summarise what was found. */
|
|
70
|
+
export function acceptLessonsConsent(home) {
|
|
71
|
+
enableLessonsSharing();
|
|
72
|
+
const report = syncLessonSources(home);
|
|
73
|
+
const status = lessonsStatus();
|
|
74
|
+
const flows = lessonFlows();
|
|
75
|
+
return {
|
|
76
|
+
report,
|
|
77
|
+
notes: status.notes,
|
|
78
|
+
projects: new Set(flows.filter(f => !f.projectIdentity.startsWith('global:')).map(f => f.projectIdentity)).size,
|
|
79
|
+
anyProject: status.anyProject,
|
|
80
|
+
ownProject: flows.reduce((sum, f) => sum + f.ownProject + f.nowhere, 0),
|
|
81
|
+
unlinked: unresolvedFolders().length,
|
|
82
|
+
byHost: status.byHost,
|
|
83
|
+
disabledHosts: [...status.disabledHosts.keys()],
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
//# sourceMappingURL=consent.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"consent.js","sourceRoot":"","sources":["../../src/lessons/consent.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,+EAA+E;AAC/E,6EAA6E;AAC7E,4EAA4E;AAC5E,yDAAyD;AAEzD,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EAAE,iBAAiB,EAAyB,MAAM,cAAc,CAAC;AACxE,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAE5E;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,YAAY,EAAE,UAAU,EAAE,YAAY,EAAE,eAAe,EAAE,gCAAgC,EAAE,sBAAsB,CAAU,CAAC;AAE/J,4FAA4F;AAC5F,MAAM,UAAU,eAAe,CAAC,MAAyB,OAAO,CAAC,GAAG;IAClE,OAAO,kBAAkB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,KAAK,EAAE,CAAC,IAAI,IAAI,CAAC;AAC3E,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,iBAAiB;IAC/B,OAAO;QACL,2DAA2D;QAC3D,EAAE;QACF,yEAAyE;QACzE,4EAA4E;QAC5E,6CAA6C;QAC7C,EAAE;QACF,4EAA4E;QAC5E,wEAAwE;QACxE,0EAA0E;QAC1E,4EAA4E;QAC5E,sEAAsE;QACtE,mDAAmD;QACnD,EAAE;QACF,kCAAkC;QAClC,yEAAyE;QACzE,yEAAyE;QACzE,0EAA0E;QAC1E,0EAA0E;QAC1E,wEAAwE;QACxE,2EAA2E;QAC3E,iEAAiE;QACjE,2EAA2E;QAC3E,0EAA0E;QAC1E,2EAA2E;QAC3E,gCAAgC;QAChC,2EAA2E;QAC3E,kCAAkC;QAClC,EAAE;QACF,wEAAwE;QACxE,0EAA0E;QAC1E,2EAA2E;QAC3E,6CAA6C;QAC7C,EAAE;QACF,sBAAsB;QACtB,uDAAuD;QACvD,4EAA4E;QAC5E,qDAAqD;QACrD,oEAAoE;QACpE,iFAAiF;KAClF,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC;AAaD,iFAAiF;AACjF,MAAM,UAAU,oBAAoB,CAAC,IAAa;IAChD,oBAAoB,EAAE,CAAC;IACvB,MAAM,MAAM,GAAG,iBAAiB,CAAC,IAAI,CAAC,CAAC;IACvC,MAAM,MAAM,GAAG,aAAa,EAAE,CAAC;IAC/B,MAAM,KAAK,GAAG,WAAW,EAAE,CAAC;IAC5B,OAAO;QACL,MAAM;QACN,KAAK,EAAE,MAAM,CAAC,KAAK;QACnB,QAAQ,EAAE,IAAI,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,eAAe,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,IAAI;QAC/G,UAAU,EAAE,MAAM,CAAC,UAAU;QAC7B,UAAU,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,CAAC,UAAU,GAAG,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;QACvE,QAAQ,EAAE,iBAAiB,EAAE,CAAC,MAAM;QACpC,MAAM,EAAE,MAAM,CAAC,MAAM;QACrB,aAAa,EAAE,CAAC,GAAG,MAAM,CAAC,aAAa,CAAC,IAAI,EAAE,CAAC;KAChD,CAAC;AACJ,CAAC"}
|
|
@@ -6,6 +6,13 @@ export type NativeMemorySource = {
|
|
|
6
6
|
mtimeMs: number;
|
|
7
7
|
/** A host-wide file every session of that host loads, not tied to a project. */
|
|
8
8
|
global: boolean;
|
|
9
|
+
/**
|
|
10
|
+
* The host's own directory this file was found under. A memory file is only
|
|
11
|
+
* followed while it resolves inside it, so a link dropped into a memory
|
|
12
|
+
* folder cannot make the harvester read somewhere else. Not the user's home:
|
|
13
|
+
* CODEX_HOME and a relocated Claude directory both live wherever they like.
|
|
14
|
+
*/
|
|
15
|
+
root: string;
|
|
9
16
|
/** Claude only: the ~/.claude/projects/<slug> folder the entry lives in. */
|
|
10
17
|
claudeFolder?: string;
|
|
11
18
|
claudeSlug?: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"discover.d.ts","sourceRoot":"","sources":["../../src/lessons/discover.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAExD,MAAM,MAAM,kBAAkB,GAAG;IAC/B,MAAM,EAAE,YAAY,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,gFAAgF;IAChF,MAAM,EAAE,OAAO,CAAC;IAChB,4EAA4E;IAC5E,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AA8BF;;;;GAIG;AACH,wBAAgB,2BAA2B,CAAC,IAAI,SAAY,GAAG,kBAAkB,EAAE,CAUlF"}
|
|
1
|
+
{"version":3,"file":"discover.d.ts","sourceRoot":"","sources":["../../src/lessons/discover.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAExD,MAAM,MAAM,kBAAkB,GAAG;IAC/B,MAAM,EAAE,YAAY,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,gFAAgF;IAChF,MAAM,EAAE,OAAO,CAAC;IAChB;;;;;OAKG;IACH,IAAI,EAAE,MAAM,CAAC;IACb,4EAA4E;IAC5E,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AA8BF;;;;GAIG;AACH,wBAAgB,2BAA2B,CAAC,IAAI,SAAY,GAAG,kBAAkB,EAAE,CAUlF"}
|
package/dist/lessons/discover.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { existsSync, readdirSync, statSync } from 'node:fs';
|
|
2
2
|
import { homedir } from 'node:os';
|
|
3
3
|
import { basename, join } from 'node:path';
|
|
4
|
-
function candidate(source, sourcePath, extra = {}) {
|
|
4
|
+
function candidate(source, sourcePath, root, extra = {}) {
|
|
5
5
|
try {
|
|
6
6
|
const stat = statSync(sourcePath);
|
|
7
|
-
return stat.isFile() ? { source, sourcePath, fileName: basename(sourcePath), mtimeMs: stat.mtimeMs, global: false, ...extra } : null;
|
|
7
|
+
return stat.isFile() ? { source, sourcePath, fileName: basename(sourcePath), mtimeMs: stat.mtimeMs, global: false, root, ...extra } : null;
|
|
8
8
|
}
|
|
9
9
|
catch {
|
|
10
10
|
return null;
|
|
@@ -37,7 +37,7 @@ function claudeMemoryFiles(claudeHome) {
|
|
|
37
37
|
for (const file of files) {
|
|
38
38
|
if (!file.isFile() || !file.name.toLowerCase().endsWith('.md'))
|
|
39
39
|
continue;
|
|
40
|
-
const found = candidate('claude', join(memory, file.name), { claudeFolder, claudeSlug: folder.name });
|
|
40
|
+
const found = candidate('claude', join(memory, file.name), claudeHome, { claudeFolder, claudeSlug: folder.name });
|
|
41
41
|
if (found)
|
|
42
42
|
out.push(found);
|
|
43
43
|
}
|
|
@@ -53,11 +53,11 @@ export function discoverNativeMemorySources(home = homedir()) {
|
|
|
53
53
|
const codexHome = home === homedir() && process.env.CODEX_HOME ? process.env.CODEX_HOME : join(home, '.codex');
|
|
54
54
|
const out = claudeMemoryFiles(join(home, '.claude'));
|
|
55
55
|
for (const path of [join(codexHome, 'AGENTS.md'), join(codexHome, 'AGENTS.override.md')]) {
|
|
56
|
-
const found = candidate('codex', path, { global: true });
|
|
56
|
+
const found = candidate('codex', path, codexHome, { global: true });
|
|
57
57
|
if (found)
|
|
58
58
|
out.push(found);
|
|
59
59
|
}
|
|
60
|
-
const gemini = candidate('gemini', join(home, '.gemini', 'GEMINI.md'), { global: true });
|
|
60
|
+
const gemini = candidate('gemini', join(home, '.gemini', 'GEMINI.md'), join(home, '.gemini'), { global: true });
|
|
61
61
|
if (gemini)
|
|
62
62
|
out.push(gemini);
|
|
63
63
|
return out.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"discover.js","sourceRoot":"","sources":["../../src/lessons/discover.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAC5D,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"discover.js","sourceRoot":"","sources":["../../src/lessons/discover.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,9 +1,19 @@
|
|
|
1
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;
|
|
2
10
|
export type HarvestOutcome = {
|
|
3
11
|
status: 'harvested';
|
|
4
12
|
inserted: number;
|
|
5
13
|
updated: number;
|
|
6
14
|
removed: number;
|
|
15
|
+
} | {
|
|
16
|
+
status: 'unchanged';
|
|
7
17
|
} | {
|
|
8
18
|
status: 'skipped';
|
|
9
19
|
reason: 'empty' | 'index' | 'veto-authored';
|
|
@@ -26,23 +36,49 @@ export declare function harvestNativeMemory(input: {
|
|
|
26
36
|
/** The project's own names; a note naming its project stays project scope. */
|
|
27
37
|
projectNames?: string[];
|
|
28
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;
|
|
29
43
|
}): HarvestOutcome;
|
|
30
44
|
export type LessonSyncReport = {
|
|
31
45
|
consent: boolean;
|
|
32
46
|
sources: number;
|
|
33
47
|
harvested: number;
|
|
48
|
+
/** Files whose bytes and project are what they were last time; nothing was parsed. */
|
|
49
|
+
unchanged: number;
|
|
34
50
|
skipped: number;
|
|
35
51
|
disabled: number;
|
|
36
52
|
unavailable: number;
|
|
53
|
+
/** Sources belonging to a project kept out of sharing; nothing of theirs is stored. */
|
|
54
|
+
excluded: number;
|
|
37
55
|
inserted: number;
|
|
38
56
|
updated: number;
|
|
39
57
|
removed: number;
|
|
40
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;
|
|
41
73
|
};
|
|
42
74
|
/**
|
|
43
|
-
* One
|
|
44
|
-
* project's identity, and delete rows whose file has gone. Nothing
|
|
45
|
-
* until the user has accepted consent v2
|
|
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.
|
|
46
82
|
*/
|
|
47
|
-
export declare function syncLessonSources(
|
|
83
|
+
export declare function syncLessonSources(options?: string | SyncOptions): LessonSyncReport;
|
|
48
84
|
//# sourceMappingURL=harvest.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"harvest.d.ts","sourceRoot":"","sources":["../../src/lessons/harvest.ts"],"names":[],"mappings":"
|
|
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"}
|
package/dist/lessons/harvest.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { readFileSync, realpathSync, statSync } from 'node:fs';
|
|
3
|
+
import { basename, dirname, relative, isAbsolute } from 'node:path';
|
|
3
4
|
import { isLessonsSharingEnabled } from '../memory/config.js';
|
|
4
5
|
import { parseNativeMemory } from './adapters/index.js';
|
|
5
6
|
import { classifyLesson } from './classify.js';
|
|
@@ -7,7 +8,64 @@ import { discoverNativeMemorySources } from './discover.js';
|
|
|
7
8
|
import { projectNames, resolveProjectIdentity } from './identity.js';
|
|
8
9
|
import { maskLessonText } from './mask.js';
|
|
9
10
|
import { resolveClaudeProjectFolder } from './source-project.js';
|
|
10
|
-
import { disableLessonSource, isLessonSourceEnabled, purgeVanishedLessonSources, syncLessons } from './store.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
|
+
}
|
|
11
69
|
/**
|
|
12
70
|
* Read and harvest a single host-native memory file. The source is only ever
|
|
13
71
|
* opened for reading. An unrecognised shape disables that host's harvester
|
|
@@ -17,15 +75,56 @@ import { disableLessonSource, isLessonSourceEnabled, purgeVanishedLessonSources,
|
|
|
17
75
|
export function harvestNativeMemory(input) {
|
|
18
76
|
if (!isLessonSourceEnabled(input.source))
|
|
19
77
|
return { status: 'disabled', reason: 'source disabled after format drift' };
|
|
20
|
-
|
|
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;
|
|
21
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;
|
|
22
110
|
try {
|
|
23
111
|
raw = readFileSync(input.sourcePath, 'utf8');
|
|
24
|
-
mtime = statSync(input.sourcePath).mtime.toISOString();
|
|
25
112
|
}
|
|
26
113
|
catch {
|
|
114
|
+
recordLessonFileUnavailable(input.source, input.sourcePath);
|
|
27
115
|
return { status: 'unavailable', reason: 'native memory file could not be read' };
|
|
28
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
|
+
}
|
|
29
128
|
const fileName = basename(input.sourcePath);
|
|
30
129
|
const parsed = parseNativeMemory(input.source, raw, fileName);
|
|
31
130
|
if (!parsed.ok) {
|
|
@@ -34,7 +133,9 @@ export function harvestNativeMemory(input) {
|
|
|
34
133
|
}
|
|
35
134
|
const base = { sourceCli: input.source, sourcePath: input.sourcePath, projectIdentity: input.projectIdentity, projectLabel: input.projectLabel ?? null, sourceMtime: mtime };
|
|
36
135
|
if ('skip' in parsed) {
|
|
37
|
-
|
|
136
|
+
const removed = syncLessons({ ...base, sections: [] }).removed;
|
|
137
|
+
remember();
|
|
138
|
+
return { status: 'skipped', reason: parsed.skip, removed };
|
|
38
139
|
}
|
|
39
140
|
const sections = parsed.sections.map(section => {
|
|
40
141
|
const masked = maskLessonText(section.text);
|
|
@@ -44,7 +145,9 @@ export function harvestNativeMemory(input) {
|
|
|
44
145
|
});
|
|
45
146
|
return { anchor: section.anchor, textMasked: masked.text, ...classified };
|
|
46
147
|
});
|
|
47
|
-
|
|
148
|
+
const synced = syncLessons({ ...base, sections });
|
|
149
|
+
remember();
|
|
150
|
+
return { status: 'harvested', ...synced };
|
|
48
151
|
}
|
|
49
152
|
function sourceProject(source, folders) {
|
|
50
153
|
if (source.global || !source.claudeFolder || !source.claudeSlug)
|
|
@@ -60,25 +163,91 @@ function sourceProject(source, folders) {
|
|
|
60
163
|
return resolved;
|
|
61
164
|
}
|
|
62
165
|
/**
|
|
63
|
-
*
|
|
64
|
-
* project
|
|
65
|
-
*
|
|
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.
|
|
66
170
|
*/
|
|
67
|
-
|
|
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();
|
|
68
208
|
const report = {
|
|
69
|
-
consent: isLessonsSharingEnabled(), sources: 0, harvested: 0, skipped: 0, disabled: 0, unavailable: 0,
|
|
70
|
-
inserted: 0, updated: 0, removed: 0, unresolvedProjects: 0,
|
|
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,
|
|
71
211
|
};
|
|
72
212
|
if (!report.consent)
|
|
73
213
|
return report;
|
|
74
|
-
const sources = discoverNativeMemorySources(home);
|
|
214
|
+
const sources = discoverNativeMemorySources(opts.home);
|
|
75
215
|
report.sources = sources.length;
|
|
76
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;
|
|
77
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
|
+
}
|
|
78
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
|
+
}
|
|
79
248
|
const outcome = harvestNativeMemory({
|
|
80
249
|
source: source.source, sourcePath: source.sourcePath, projectIdentity: project.identity,
|
|
81
|
-
projectLabel: project.label, projectNames: project.names, global: source.global,
|
|
250
|
+
projectLabel: project.label, projectNames: project.names, global: source.global, root: source.root, forced: opts.forced,
|
|
82
251
|
});
|
|
83
252
|
report[outcome.status]++;
|
|
84
253
|
if (outcome.status === 'harvested') {
|
|
@@ -89,8 +258,14 @@ export function syncLessonSources(home) {
|
|
|
89
258
|
if (outcome.status === 'skipped')
|
|
90
259
|
report.removed += outcome.removed;
|
|
91
260
|
}
|
|
92
|
-
report.unresolvedProjects =
|
|
93
|
-
|
|
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;
|
|
94
269
|
return report;
|
|
95
270
|
}
|
|
96
271
|
//# sourceMappingURL=harvest.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"harvest.js","sourceRoot":"","sources":["../../src/lessons/harvest.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"harvest.js","sourceRoot":"","sources":["../../src/lessons/harvest.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAC/D,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AACpE,OAAO,EAAE,uBAAuB,EAAE,MAAM,qBAAqB,CAAC;AAC9D,OAAO,EAAE,iBAAiB,EAAqB,MAAM,qBAAqB,CAAC;AAC3E,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,EAAE,2BAA2B,EAA2B,MAAM,eAAe,CAAC;AACrF,OAAO,EAAE,YAAY,EAAE,sBAAsB,EAAE,MAAM,eAAe,CAAC;AACrE,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,EAAE,0BAA0B,EAAE,MAAM,qBAAqB,CAAC;AACjE,OAAO,EACL,kBAAkB,EAAE,mBAAmB,EAAE,qBAAqB,EAAE,iBAAiB,EAAE,eAAe,EAClG,4BAA4B,EAAE,0BAA0B,EAAE,qBAAqB,EAAE,2BAA2B,EAC5G,WAAW,EAAE,sBAAsB,GACpC,MAAM,YAAY,CAAC;AAEpB;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,CAAC;AAEnC;;;GAGG;AACH,MAAM,gBAAgB,GAAG,CAAC,GAAG,IAAI,GAAG,IAAI,CAAC;AAEzC;;;;;GAKG;AACH,MAAM,yBAAyB,GAAG,CAAC,CAAC;AAQpC;;;;;;;;;;;;GAYG;AACH,SAAS,mBAAmB,CAAC,UAAkB,EAAE,IAAY;IAC3D,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,QAAQ,CAAC,YAAY,CAAC,IAAI,CAAC,EAAE,YAAY,CAAC,UAAU,CAAC,CAAC,CAAC;QACnE,OAAO,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,UAAU,CAAC,GAAG,CAAC,CAAC;IACjD,CAAC;IAAC,MAAM,CAAC;QAAC,OAAO,IAAI,CAAC;IAAC,CAAC;AAC1B,CAAC;AAED;;;;;GAKG;AACH,SAAS,WAAW,CAAC,GAAW,EAAE,KAA2G;IAC3I,OAAO,UAAU,CAAC,QAAQ,CAAC;SACxB,MAAM,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC;SAC5B,MAAM,CAAC,KAAK,CAAC,eAAe,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC;SAC9C,MAAM,CAAC,KAAK,CAAC,YAAY,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC;SACjD,MAAM,CAAC,CAAC,KAAK,CAAC,YAAY,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC;SAClE,MAAM,CAAC,KAAK,CAAC,MAAM,KAAK,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC;SACzC,MAAM,CAAC,KAAK,CAAC,CAAC;AACnB,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,mBAAmB,CAAC,KAYnC;IACC,IAAI,CAAC,qBAAqB,CAAC,KAAK,CAAC,MAAM,CAAC;QAAE,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,oCAAoC,EAAE,CAAC;IACtH,0EAA0E;IAC1E,0EAA0E;IAC1E,IAAI,mBAAmB,CAAC,KAAK,CAAC,UAAU,EAAE,KAAK,CAAC,IAAI,IAAI,OAAO,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,EAAE,CAAC;QACnF,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,qEAAqE,EAAE,CAAC;IAClH,CAAC;IAED,4EAA4E;IAC5E,sEAAsE;IACtE,IAAI,IAAY,CAAC;IACjB,IAAI,KAAa,CAAC;IAClB,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,QAAQ,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;QACxC,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;QACjB,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC;IACnC,CAAC;IAAC,MAAM,CAAC;QACP,2BAA2B,CAAC,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,UAAU,CAAC,CAAC;QAC5D,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,sCAAsC,EAAE,CAAC;IACnF,CAAC;IACD,IAAI,IAAI,GAAG,gBAAgB,EAAE,CAAC;QAC5B,0EAA0E;QAC1E,uCAAuC;QACvC,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,8BAA8B,gBAAgB,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC,sBAAsB,EAAE,CAAC;IACjI,CAAC;IAED,MAAM,KAAK,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,eAAe,CAAC,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,UAAU,CAAC,CAAC;IACpF,yEAAyE;IACzE,0EAA0E;IAC1E,2EAA2E;IAC3E,mBAAmB;IACnB,MAAM,OAAO,GAAG,KAAK,EAAE,iBAAiB,KAAK,iBAAiB,IAAI,KAAK,EAAE,gBAAgB,KAAK,KAAK,CAAC,eAAe,CAAC;IACpH,IAAI,OAAO,IAAI,KAAK,EAAE,KAAK,KAAK,KAAK,IAAI,KAAK,EAAE,IAAI,KAAK,IAAI;QAAE,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC;IAE9F,IAAI,GAAW,CAAC;IAChB,IAAI,CAAC;QACH,GAAG,GAAG,YAAY,CAAC,KAAK,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;IAC/C,CAAC;IAAC,MAAM,CAAC;QACP,2BAA2B,CAAC,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,UAAU,CAAC,CAAC;QAC5D,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,sCAAsC,EAAE,CAAC;IACnF,CAAC;IAED,yEAAyE;IACzE,8EAA8E;IAC9E,MAAM,MAAM,GAAG,WAAW,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;IACvC,MAAM,QAAQ,GAAG,GAAS,EAAE,CAAC,qBAAqB,CAAC;QACjD,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,UAAU,EAAE,KAAK,CAAC,UAAU,EAAE,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM;QACpF,eAAe,EAAE,KAAK,CAAC,eAAe,EAAE,gBAAgB,EAAE,iBAAiB;KAC5E,CAAC,CAAC;IACH,IAAI,OAAO,IAAI,KAAK,EAAE,YAAY,KAAK,MAAM,EAAE,CAAC;QAC9C,QAAQ,EAAE,CAAC;QACX,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC;IACjC,CAAC;IACD,MAAM,QAAQ,GAAG,QAAQ,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;IAC5C,MAAM,MAAM,GAAG,iBAAiB,CAAC,KAAK,CAAC,MAAM,EAAE,GAAG,EAAE,QAAQ,CAAC,CAAC;IAC9D,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC;QACf,mBAAmB,CAAC,KAAK,CAAC,MAAM,EAAE,GAAG,QAAQ,KAAK,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;QACnE,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC;IACvD,CAAC;IACD,MAAM,IAAI,GAAG,EAAE,SAAS,EAAE,KAAK,CAAC,MAAM,EAAE,UAAU,EAAE,KAAK,CAAC,UAAU,EAAE,eAAe,EAAE,KAAK,CAAC,eAAe,EAAE,YAAY,EAAE,KAAK,CAAC,YAAY,IAAI,IAAI,EAAE,WAAW,EAAE,KAAK,EAAE,CAAC;IAC7K,IAAI,MAAM,IAAI,MAAM,EAAE,CAAC;QACrB,MAAM,OAAO,GAAG,WAAW,CAAC,EAAE,GAAG,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC;QAC/D,QAAQ,EAAE,CAAC;QACX,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,CAAC,IAAI,EAAE,OAAO,EAAE,CAAC;IAC7D,CAAC;IACD,MAAM,QAAQ,GAAuB,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE;QACjE,MAAM,MAAM,GAAG,cAAc,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QAC5C,MAAM,UAAU,GAAG,cAAc,CAAC;YAChC,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,KAAK,IAAI,EAAE,WAAW,EAAE,MAAM,CAAC,WAAW;YAC9F,OAAO,EAAE,UAAU,EAAE,MAAM,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,YAAY,EAAE,KAAK,CAAC,YAAY;SAC5F,CAAC,CAAC;QACH,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,UAAU,EAAE,MAAM,CAAC,IAAI,EAAE,GAAG,UAAU,EAAE,CAAC;IAC5E,CAAC,CAAC,CAAC;IACH,MAAM,MAAM,GAAG,WAAW,CAAC,EAAE,GAAG,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC;IAClD,QAAQ,EAAE,CAAC;IACX,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,GAAG,MAAM,EAAE,CAAC;AAC5C,CAAC;AAoCD,SAAS,aAAa,CAAC,MAA0B,EAAE,OAAmC;IACpF,IAAI,MAAM,CAAC,MAAM,IAAI,CAAC,MAAM,CAAC,YAAY,IAAI,CAAC,MAAM,CAAC,UAAU;QAAE,OAAO,EAAE,QAAQ,EAAE,UAAU,MAAM,CAAC,MAAM,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC;IACxI,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC;IAChD,IAAI,MAAM;QAAE,OAAO,MAAM,CAAC;IAC1B,MAAM,EAAE,UAAU,EAAE,GAAG,0BAA0B,CAAC,MAAM,CAAC,YAAY,EAAE,MAAM,CAAC,UAAU,CAAC,CAAC;IAC1F,MAAM,QAAQ,GAAG,UAAU;QACzB,CAAC,CAAC,EAAE,QAAQ,EAAE,sBAAsB,CAAC,UAAU,CAAC,EAAE,KAAK,EAAE,QAAQ,CAAC,UAAU,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC,IAAI,UAAU,EAAE,KAAK,EAAE,YAAY,CAAC,UAAU,CAAC,EAAE;QACrJ,CAAC,CAAC,EAAE,QAAQ,EAAE,eAAe,MAAM,CAAC,UAAU,EAAE,EAAE,KAAK,EAAE,MAAM,CAAC,UAAU,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC;IAC1F,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,YAAY,EAAE,QAAQ,CAAC,CAAC;IAC3C,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED;;;;;GAKG;AACH,SAAS,cAAc,CAAC,MAA0B,EAAE,iBAA0B;IAC5E,MAAM,KAAK,GAAG,eAAe,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,UAAU,CAAC,CAAC;IAChE,IAAI,CAAC,KAAK;QAAE,OAAO,KAAK,CAAC;IACzB,0EAA0E;IAC1E,6EAA6E;IAC7E,mCAAmC;IACnC,IAAI,CAAC,iBAAiB,IAAI,KAAK,CAAC,kBAAkB,IAAI,yBAAyB;QAAE,OAAO,MAAM,CAAC;IAC/F,IAAI,KAAK,CAAC,iBAAiB,KAAK,iBAAiB;QAAE,OAAO,KAAK,CAAC;IAChE,4EAA4E;IAC5E,6EAA6E;IAC7E,2EAA2E;IAC3E,mCAAmC;IACnC,IAAI,iBAAiB,IAAI,KAAK,CAAC,gBAAgB,EAAE,UAAU,CAAC,cAAc,CAAC;QAAE,OAAO,KAAK,CAAC;IAC1F,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,QAAQ,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;QACzC,OAAO,KAAK,CAAC,KAAK,KAAK,IAAI,CAAC,KAAK,CAAC,WAAW,EAAE,IAAI,KAAK,CAAC,IAAI,KAAK,IAAI,CAAC,IAAI,CAAC;IAC9E,CAAC;IAAC,MAAM,CAAC;QAAC,OAAO,KAAK,CAAC;IAAC,CAAC;AAC3B,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,iBAAiB,CAAC,UAAgC,EAAE;IAClE,MAAM,IAAI,GAAgB,OAAO,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC;IACpF,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAC3B,MAAM,MAAM,GAAqB;QAC/B,OAAO,EAAE,uBAAuB,EAAE,EAAE,OAAO,EAAE,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,WAAW,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC;QAChI,QAAQ,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,kBAAkB,EAAE,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,SAAS,EAAE,CAAC;KACxF,CAAC;IACF,IAAI,CAAC,MAAM,CAAC,OAAO;QAAE,OAAO,MAAM,CAAC;IACnC,MAAM,OAAO,GAAG,2BAA2B,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACvD,MAAM,CAAC,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC;IAChC,MAAM,OAAO,GAAG,IAAI,GAAG,EAAyB,CAAC;IACjD,4EAA4E;IAC5E,6BAA6B;IAC7B,MAAM,iBAAiB,GAAG,IAAI,CAAC,QAAQ,KAAK,SAAS,CAAC;IACtD,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;QAC7B,0EAA0E;QAC1E,0EAA0E;QAC1E,uCAAuC;QACvC,IAAI,IAAI,CAAC,QAAQ,KAAK,SAAS,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,OAAO,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YACzE,MAAM,CAAC,UAAU,GAAG,OAAO,CAAC,MAAM,GAAG,KAAK,CAAC;YAC3C,MAAM;QACR,CAAC;QACD,KAAK,EAAE,CAAC;QACR,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC;YACjB,MAAM,KAAK,GAAG,cAAc,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC;YACxD,IAAI,KAAK,KAAK,MAAM,EAAE,CAAC;gBAAC,MAAM,CAAC,WAAW,EAAE,CAAC;gBAAC,SAAS;YAAC,CAAC;YACzD,IAAI,KAAK,EAAE,CAAC;gBAAC,MAAM,CAAC,SAAS,EAAE,CAAC;gBAAC,SAAS;YAAC,CAAC;QAC9C,CAAC;QAED,MAAM,OAAO,GAAG,aAAa,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QAC/C,IAAI,iBAAiB,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC;YACxC,oEAAoE;YACpE,MAAM,CAAC,QAAQ,EAAE,CAAC;YAClB,MAAM,CAAC,OAAO,IAAI,kBAAkB,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,UAAU,CAAC,CAAC;YACvE,SAAS;QACX,CAAC;QACD,MAAM,OAAO,GAAG,mBAAmB,CAAC;YAClC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,EAAE,MAAM,CAAC,UAAU,EAAE,eAAe,EAAE,OAAO,CAAC,QAAQ;YACvF,YAAY,EAAE,OAAO,CAAC,KAAK,EAAE,YAAY,EAAE,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM;SACxH,CAAC,CAAC;QACH,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QACzB,IAAI,OAAO,CAAC,MAAM,KAAK,WAAW,EAAE,CAAC;YAAC,MAAM,CAAC,QAAQ,IAAI,OAAO,CAAC,QAAQ,CAAC;YAAC,MAAM,CAAC,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC;YAAC,MAAM,CAAC,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC;QAAC,CAAC;QAClJ,IAAI,OAAO,CAAC,MAAM,KAAK,SAAS;YAAE,MAAM,CAAC,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC;IACtE,CAAC;IACD,MAAM,CAAC,kBAAkB,GAAG,sBAAsB,EAAE,CAAC;IACrD,2EAA2E;IAC3E,gCAAgC;IAChC,IAAI,MAAM,CAAC,UAAU,KAAK,CAAC,EAAE,CAAC;QAC5B,MAAM,CAAC,OAAO,IAAI,0BAA0B,CAAC,OAAO,CAAC,CAAC;QACtD,4BAA4B,CAAC,OAAO,CAAC,CAAC;IACxC,CAAC;IACD,MAAM,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,OAAO,CAAC;IACxC,OAAO,MAAM,CAAC;AAChB,CAAC"}
|