@mnemosyne_os/forge 1.0.0 → 1.2.2

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.
@@ -0,0 +1,105 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.buildChronicleContent = buildChronicleContent;
7
+ exports.writeChronicle = writeChronicle;
8
+ exports.buildSweepContent = buildSweepContent;
9
+ const fs_1 = __importDefault(require("fs"));
10
+ const path_1 = __importDefault(require("path"));
11
+ const vault_js_1 = require("./vault.js");
12
+ /**
13
+ * Generate a CHRONICLE filename.
14
+ * Pattern: CHRONICLE-YYYY-MM-DD-slug.md
15
+ */
16
+ function buildFilename(title) {
17
+ const date = new Date().toISOString().slice(0, 10);
18
+ const slug = title
19
+ .toLowerCase()
20
+ .replace(/[^a-z0-9\s-]/g, '')
21
+ .trim()
22
+ .replace(/\s+/g, '-')
23
+ .slice(0, 48);
24
+ return `CHRONICLE-${date}-${slug}.md`;
25
+ }
26
+ /**
27
+ * Format date for display.
28
+ */
29
+ function formatDate() {
30
+ return new Date().toISOString().replace('T', ' ').slice(0, 19) + ' UTC';
31
+ }
32
+ /**
33
+ * Build the Chronicle markdown content following MnemoChronicle Standard v0.4.
34
+ */
35
+ function buildChronicleContent(opts) {
36
+ const { title, type, content, tags = [], config } = opts;
37
+ const date = formatDate();
38
+ const allTags = [
39
+ '#resonance',
40
+ `#${config.ide.toLowerCase()}`,
41
+ `#${config.provider.toLowerCase().replace(/\s+/g, '')}`,
42
+ `#${type}`,
43
+ ...tags.map(t => t.startsWith('#') ? t : `#${t}`),
44
+ ];
45
+ return `# ${title}
46
+
47
+ **Date**: ${date}
48
+ **IDE**: ${config.ide}
49
+ **Provider**: ${config.provider}
50
+ **Workspace**: ${config.workspace ?? 'unset'}
51
+ **Project**: ${config.resonanceProject ?? 'unset'}
52
+ **Model**: ${config.modelId}
53
+ **Model Name**: ${config.displayName}
54
+ **Type**: ${type}
55
+ **Vault**: ${config.vaultPath}
56
+
57
+ ---
58
+
59
+ ${content}
60
+
61
+ ---
62
+
63
+ ${allTags.join(' ')}
64
+
65
+ <!--resonance
66
+ source: cli
67
+ ide: ${config.ide}
68
+ provider: ${config.provider}
69
+ workspace: ${config.workspace ?? 'unset'}
70
+ resonance_project: ${config.resonanceProject ?? 'unset'}
71
+ model: ${config.modelId}
72
+ display_name: ${config.displayName}
73
+ type: ${type}
74
+ date: ${new Date().toISOString()}
75
+ tags: ${allTags.join(', ')}
76
+ -->
77
+ `;
78
+ }
79
+ /**
80
+ * Write a Chronicle to the correct vault location.
81
+ * Returns the file path and filename.
82
+ */
83
+ function writeChronicle(opts) {
84
+ const chronicleDir = (0, vault_js_1.resolveChronicleDir)(opts.config);
85
+ const filename = buildFilename(opts.title);
86
+ const filePath = path_1.default.join(chronicleDir, filename);
87
+ const content = buildChronicleContent(opts);
88
+ fs_1.default.writeFileSync(filePath, content, 'utf8');
89
+ return { filePath, filename };
90
+ }
91
+ /**
92
+ * Build a sweep (daily summary) Chronicle from multiple sources.
93
+ */
94
+ function buildSweepContent(entries, config) {
95
+ const date = new Date().toISOString().slice(0, 10);
96
+ const entryList = entries.map((e, i) => `### Entry ${i + 1}\n\n${e}`).join('\n\n---\n\n');
97
+ return `Daily sweep across ${entries.length} session(s) — ${config.ide} / ${config.provider} / ${config.modelId}.
98
+
99
+ **Coverage**: ${date}
100
+ **Sessions consolidated**: ${entries.length}
101
+
102
+ ---
103
+
104
+ ${entryList}`;
105
+ }
@@ -0,0 +1,139 @@
1
+ "use strict";
2
+ // init-flow.ts — Interactive prompts for chronicle init
3
+ // Separated from cli.ts to keep the orchestrator thin
4
+ var __importDefault = (this && this.__importDefault) || function (mod) {
5
+ return (mod && mod.__esModule) ? mod : { "default": mod };
6
+ };
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.CHRONICLE_STYLES = void 0;
9
+ exports.askPrimaryProfile = askPrimaryProfile;
10
+ exports.askExtraProfile = askExtraProfile;
11
+ const inquirer_1 = __importDefault(require("inquirer"));
12
+ const chalk_1 = __importDefault(require("chalk"));
13
+ const providers_js_1 = require("./providers.js");
14
+ // ── Chronicle style options (shared across init + config commands) ──────────
15
+ exports.CHRONICLE_STYLES = [
16
+ { name: 'Session — coding/work session, structured notes', value: 'session' },
17
+ { name: 'Reflection — deep thoughts, retrospective', value: 'reflection' },
18
+ { name: 'Decision — decision record (ADR-style)', value: 'decision' },
19
+ { name: 'Sweep — daily digest of multiple sessions', value: 'sweep' },
20
+ { name: 'Narcissus — personal / soul narrative (Mnemosyne)', value: 'narcissus' },
21
+ ];
22
+ async function askPrimaryProfile(existing) {
23
+ const defaultVault = `${process.env['USERPROFILE'] ?? process.env['HOME'] ?? '~'}/Documents/MnemoVault`;
24
+ const a = await inquirer_1.default.prompt([
25
+ // Step 1: Vault path
26
+ {
27
+ type: 'input',
28
+ name: 'vaultPath',
29
+ message: chalk_1.default.cyan('MnemoVault path?'),
30
+ default: existing?.vaultPath ?? defaultVault,
31
+ validate: (v) => v.trim() !== '' || 'Required',
32
+ },
33
+ // Step 2: IDE
34
+ {
35
+ type: 'list',
36
+ name: 'ide',
37
+ message: chalk_1.default.cyan('Your IDE?'),
38
+ choices: providers_js_1.IDE_LIST,
39
+ default: existing?.ide ?? 'Antigravity',
40
+ },
41
+ {
42
+ type: 'input',
43
+ name: 'ideCustom',
44
+ message: chalk_1.default.cyan('IDE name?'),
45
+ when: (a) => a.ide === '__other__',
46
+ validate: (v) => v.trim() !== '' || 'Required',
47
+ },
48
+ // Step 2b: Provider
49
+ {
50
+ type: 'list',
51
+ name: 'provider',
52
+ message: chalk_1.default.cyan('AI Provider?') + chalk_1.default.gray(' (becomes the folder name)'),
53
+ choices: (a) => providers_js_1.PROVIDERS_BY_IDE[a.ide] ?? providers_js_1.PROVIDERS_BY_IDE['default'],
54
+ when: (a) => a.ide !== '__other__',
55
+ default: existing?.provider ?? 'Anthropic',
56
+ },
57
+ {
58
+ type: 'input',
59
+ name: 'providerCustom',
60
+ message: chalk_1.default.cyan('Provider name?') + chalk_1.default.gray(' (e.g. EleutherAI)'),
61
+ when: (a) => a.ide === '__other__' || a.provider === '__other__',
62
+ validate: (v) => v.trim() !== '' || 'Required',
63
+ },
64
+ // Step 3: Chronicle style
65
+ {
66
+ type: 'list',
67
+ name: 'defaultChronicleStyle',
68
+ message: chalk_1.default.cyan('Default chronicle style?'),
69
+ choices: exports.CHRONICLE_STYLES,
70
+ default: existing?.defaultChronicleStyle ?? 'session',
71
+ },
72
+ ]);
73
+ const ide = (a.ide === '__other__' ? a.ideCustom : a.ide).trim();
74
+ const provider = (!a.provider || a.provider === '__other__' ? a.providerCustom : a.provider).trim();
75
+ return {
76
+ vaultPath: a.vaultPath.trim(),
77
+ ide,
78
+ provider,
79
+ defaultChronicleStyle: a.defaultChronicleStyle,
80
+ };
81
+ }
82
+ // ── Extra profile (Step 5 loop) ──────────────────────────────────────────────
83
+ async function askExtraProfile(count) {
84
+ const { wantMore } = await inquirer_1.default.prompt([{
85
+ type: 'confirm',
86
+ name: 'wantMore',
87
+ message: chalk_1.default.cyan('Add another profile to your registry?') +
88
+ (count > 0 ? chalk_1.default.gray(` (${count} already added)`) : ''),
89
+ default: false,
90
+ }]);
91
+ if (!wantMore)
92
+ return null;
93
+ const e = await inquirer_1.default.prompt([
94
+ {
95
+ type: 'list',
96
+ name: 'ide',
97
+ message: chalk_1.default.cyan(' IDE for this profile?'),
98
+ choices: providers_js_1.IDE_LIST,
99
+ default: 'Antigravity',
100
+ },
101
+ {
102
+ type: 'input',
103
+ name: 'ideCustom',
104
+ message: chalk_1.default.cyan(' IDE name?'),
105
+ when: (a) => a.ide === '__other__',
106
+ validate: (v) => v.trim() !== '' || 'Required',
107
+ },
108
+ {
109
+ type: 'list',
110
+ name: 'provider',
111
+ message: chalk_1.default.cyan(' Provider?'),
112
+ choices: (a) => providers_js_1.PROVIDERS_BY_IDE[a.ide] ?? providers_js_1.PROVIDERS_BY_IDE['default'],
113
+ when: (a) => a.ide !== '__other__',
114
+ },
115
+ {
116
+ type: 'input',
117
+ name: 'providerCustom',
118
+ message: chalk_1.default.cyan(' Provider name?'),
119
+ when: (a) => a.ide === '__other__' || a.provider === '__other__',
120
+ validate: (v) => v.trim() !== '' || 'Required',
121
+ },
122
+ {
123
+ type: 'list',
124
+ name: 'style',
125
+ message: chalk_1.default.cyan(' Chronicle style for this profile?'),
126
+ choices: exports.CHRONICLE_STYLES,
127
+ default: 'session',
128
+ },
129
+ ]);
130
+ const ide = (e.ide === '__other__' ? e.ideCustom : e.ide).trim();
131
+ const provider = (!e.provider || e.provider === '__other__' ? e.providerCustom : e.provider).trim();
132
+ return {
133
+ ide,
134
+ provider,
135
+ modelId: '',
136
+ displayName: provider,
137
+ defaultChronicleStyle: e.style,
138
+ };
139
+ }
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+ // providers.ts — IDE and Provider registry
3
+ // Add new IDEs/Providers here, never touch cli.ts for this
4
+ Object.defineProperty(exports, "__esModule", { value: true });
5
+ exports.PROVIDERS_BY_IDE = exports.IDE_LIST = void 0;
6
+ exports.IDE_LIST = [
7
+ { name: 'Antigravity · Google DeepMind IDE', value: 'Antigravity' },
8
+ { name: 'Cursor · Cursor IDE', value: 'Cursor' },
9
+ { name: 'Claude Code · Anthropic terminal', value: 'ClaudeCode' },
10
+ { name: 'Open Claw · OpenWebUI / local LLM', value: 'OpenClaw' },
11
+ { name: 'VS Code · Copilot / Continue / etc', value: 'VSCode' },
12
+ { name: 'Windsurf · Codeium', value: 'Windsurf' },
13
+ { name: 'Other · type manually', value: '__other__' },
14
+ ];
15
+ exports.PROVIDERS_BY_IDE = {
16
+ Antigravity: [
17
+ { name: 'Anthropic', value: 'Anthropic' },
18
+ { name: 'Google DeepMind', value: 'GoogleDeepMind' },
19
+ { name: 'OpenAI', value: 'OpenAI' },
20
+ { name: 'Other', value: '__other__' },
21
+ ],
22
+ Cursor: [
23
+ { name: 'Anthropic', value: 'Anthropic' },
24
+ { name: 'OpenAI', value: 'OpenAI' },
25
+ { name: 'Google DeepMind', value: 'GoogleDeepMind' },
26
+ { name: 'Other', value: '__other__' },
27
+ ],
28
+ ClaudeCode: [
29
+ { name: 'Anthropic', value: 'Anthropic' },
30
+ { name: 'Other', value: '__other__' },
31
+ ],
32
+ OpenClaw: [
33
+ { name: 'Meta', value: 'Meta' },
34
+ { name: 'Mistral AI', value: 'MistralAI' },
35
+ { name: 'Google DeepMind', value: 'GoogleDeepMind' },
36
+ { name: 'Other', value: '__other__' },
37
+ ],
38
+ default: [
39
+ { name: 'Anthropic', value: 'Anthropic' },
40
+ { name: 'OpenAI', value: 'OpenAI' },
41
+ { name: 'Google DeepMind', value: 'GoogleDeepMind' },
42
+ { name: 'Meta', value: 'Meta' },
43
+ { name: 'Mistral AI', value: 'MistralAI' },
44
+ { name: 'Other', value: '__other__' },
45
+ ],
46
+ };
@@ -0,0 +1,142 @@
1
+ "use strict";
2
+ // src/lib/sources/antigravity.ts
3
+ // Reads Antigravity conversation logs from .gemini/antigravity/brain/
4
+ // Parses overview.txt and extracts chronicle-relevant content
5
+ var __importDefault = (this && this.__importDefault) || function (mod) {
6
+ return (mod && mod.__esModule) ? mod : { "default": mod };
7
+ };
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.readAntigravitySource = readAntigravitySource;
10
+ exports.listAntigravityConversations = listAntigravityConversations;
11
+ const fs_1 = __importDefault(require("fs"));
12
+ const path_1 = __importDefault(require("path"));
13
+ const os_1 = __importDefault(require("os"));
14
+ // ── Paths ────────────────────────────────────────────────────────────────────
15
+ const BRAIN_DIR = path_1.default.join(os_1.default.homedir(), '.gemini', 'antigravity', 'brain');
16
+ // ── Helpers ──────────────────────────────────────────────────────────────────
17
+ /**
18
+ * List all conversation folders sorted by modification time (most recent first).
19
+ */
20
+ function listConversations() {
21
+ if (!fs_1.default.existsSync(BRAIN_DIR))
22
+ return [];
23
+ return fs_1.default.readdirSync(BRAIN_DIR)
24
+ .map(id => {
25
+ const overviewPath = path_1.default.join(BRAIN_DIR, id, '.system_generated', 'logs', 'overview.txt');
26
+ // Fallback: some structures put overview.txt directly in the folder
27
+ const altPath = path_1.default.join(BRAIN_DIR, id, 'overview.txt');
28
+ const resolved = fs_1.default.existsSync(overviewPath) ? overviewPath
29
+ : fs_1.default.existsSync(altPath) ? altPath
30
+ : null;
31
+ if (!resolved)
32
+ return null;
33
+ const stat = fs_1.default.statSync(resolved);
34
+ return { id, mtime: stat.mtime, overviewPath: resolved };
35
+ })
36
+ .filter(Boolean)
37
+ .sort((a, b) => b.mtime.getTime() - a.mtime.getTime());
38
+ }
39
+ /**
40
+ * Extract file paths mentioned in text lines (heuristic: contains / or \ and a file extension).
41
+ */
42
+ function extractFilePaths(lines) {
43
+ const seen = new Set();
44
+ const result = [];
45
+ const filePattern = /([a-zA-Z0-9_\-./\\]+\.(ts|js|json|md|css|html|py|sh|ps1))/g;
46
+ for (const line of lines) {
47
+ const matches = line.match(filePattern) ?? [];
48
+ for (const m of matches) {
49
+ const clean = m.trim();
50
+ if (!seen.has(clean) && clean.length > 3) {
51
+ seen.add(clean);
52
+ result.push(clean);
53
+ }
54
+ }
55
+ }
56
+ return result.slice(0, 20); // cap at 20
57
+ }
58
+ /**
59
+ * Extract shell commands (lines that look like CLI invocations).
60
+ */
61
+ function extractCommands(lines) {
62
+ const seen = new Set();
63
+ const result = [];
64
+ // Patterns: pnpm, npm, npx, git, mnemoforge, tsc
65
+ const cmdPattern = /^(pnpm|npm|npx|git|mnemoforge|tsc|node|pwsh|powershell)\s+.+/i;
66
+ for (const line of lines) {
67
+ const trimmed = line.trim();
68
+ if (cmdPattern.test(trimmed) && !seen.has(trimmed)) {
69
+ seen.add(trimmed);
70
+ result.push(trimmed);
71
+ }
72
+ }
73
+ return result.slice(0, 15);
74
+ }
75
+ /**
76
+ * Heuristic session title from the overview: use the first substantive user line.
77
+ */
78
+ function extractTitle(lines) {
79
+ for (const line of lines) {
80
+ const trimmed = line.trim();
81
+ // Skip short lines, timestamps, or system lines
82
+ if (trimmed.length > 20 && !trimmed.startsWith('[') && !trimmed.startsWith('#')) {
83
+ return trimmed.slice(0, 80);
84
+ }
85
+ }
86
+ return 'Development session';
87
+ }
88
+ /**
89
+ * Extract key decision lines (heuristic: lines mentioning "decision", "chose", "on a décidé", etc.)
90
+ */
91
+ function extractDecisions(lines) {
92
+ const keywords = [
93
+ 'decided', 'decision', 'on a décidé', 'refactor', 'remove', 'on retire',
94
+ 'on supprime', 'approach', 'strategy', 'instead', 'changed', 'principle'
95
+ ];
96
+ const result = [];
97
+ for (const line of lines) {
98
+ const lower = line.toLowerCase();
99
+ if (keywords.some(k => lower.includes(k)) && line.trim().length > 30) {
100
+ result.push(line.trim().slice(0, 120));
101
+ if (result.length >= 8)
102
+ break;
103
+ }
104
+ }
105
+ return result;
106
+ }
107
+ // ── Main export ───────────────────────────────────────────────────────────────
108
+ /**
109
+ * Read the latest Antigravity conversation and return a ChronicleContext.
110
+ * @param conversationId — optional, defaults to most recent
111
+ */
112
+ function readAntigravitySource(conversationId) {
113
+ const conversations = listConversations();
114
+ if (conversations.length === 0)
115
+ return null;
116
+ const target = conversationId
117
+ ? conversations.find(c => c.id === conversationId) ?? conversations[0]
118
+ : conversations[0];
119
+ const raw = fs_1.default.readFileSync(target.overviewPath, 'utf8');
120
+ const lines = raw.split('\n').filter(l => l.trim() !== '');
121
+ // Parse turns — each line is one action (simplified)
122
+ const turns = lines.map(line => ({
123
+ role: line.startsWith('USER') || line.startsWith('user') ? 'user' : 'agent',
124
+ content: line.trim(),
125
+ }));
126
+ return {
127
+ conversationId: target.id,
128
+ startedAt: target.mtime.toISOString(),
129
+ sessionTitle: extractTitle(lines),
130
+ filesTouched: extractFilePaths(lines),
131
+ commandsRun: extractCommands(lines),
132
+ keyDecisions: extractDecisions(lines),
133
+ rawTurns: turns,
134
+ sourcePath: target.overviewPath,
135
+ };
136
+ }
137
+ /**
138
+ * List all available Antigravity conversations (for --list selection).
139
+ */
140
+ function listAntigravityConversations() {
141
+ return listConversations().map(c => ({ id: c.id, mtime: c.mtime }));
142
+ }
@@ -0,0 +1,172 @@
1
+ "use strict";
2
+ // src/lib/sources/index.ts
3
+ // Dispatches to the right source reader based on the active provider.
4
+ // Each provider has a specific conversation format.
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.readSourceForConfig = readSourceForConfig;
7
+ exports.generateChronicleDraft = generateChronicleDraft;
8
+ const antigravity_js_1 = require("./antigravity.js");
9
+ // ── Provider → Source mapping ─────────────────────────────────────────────────
10
+ const PROVIDER_SOURCES = {
11
+ 'Anthropic': antigravity_js_1.readAntigravitySource, // Antigravity IDE talks to Anthropic
12
+ 'GoogleDeepMind': antigravity_js_1.readAntigravitySource, // Antigravity IDE default
13
+ // Future:
14
+ // 'OpenAI': readCursorSource,
15
+ // 'Cursor': readCursorSource,
16
+ };
17
+ /**
18
+ * Read the conversation source for the configured provider.
19
+ * Returns null if no source is available or supported.
20
+ */
21
+ function readSourceForConfig(config) {
22
+ const reader = PROVIDER_SOURCES[config.provider] ?? PROVIDER_SOURCES['GoogleDeepMind'];
23
+ try {
24
+ return reader();
25
+ }
26
+ catch {
27
+ return null;
28
+ }
29
+ }
30
+ // ── Chronicle Markdown Generator ──────────────────────────────────────────────
31
+ /**
32
+ * Generate a chronicle markdown draft from a ChronicleContext.
33
+ * Uses the style from VaultConfig to pick the right template.
34
+ */
35
+ function generateChronicleDraft(ctx, config) {
36
+ const date = new Date().toISOString().split('T')[0];
37
+ const style = config.defaultChronicleStyle ?? 'session';
38
+ const ide = config.ide;
39
+ const provider = config.provider;
40
+ const frontmatter = [
41
+ '---',
42
+ `date: ${date}`,
43
+ `session: "${ctx.sessionTitle.replace(/"/g, "'")}"`,
44
+ `ide: ${ide}`,
45
+ `provider: ${provider}`,
46
+ `style: ${style}`,
47
+ `source: antigravity-brain`,
48
+ `conversation_id: ${ctx.conversationId}`,
49
+ `files_touched:`,
50
+ ...ctx.filesTouched.slice(0, 10).map(f => ` - ${f}`),
51
+ '---',
52
+ ].join('\n');
53
+ const body = buildBody(ctx, style);
54
+ return frontmatter + '\n\n' + body;
55
+ }
56
+ // ── Style templates ───────────────────────────────────────────────────────────
57
+ function buildBody(ctx, style) {
58
+ switch (style) {
59
+ case 'session': return sessionTemplate(ctx);
60
+ case 'reflection': return reflectionTemplate(ctx);
61
+ case 'decision': return decisionTemplate(ctx);
62
+ case 'sweep': return sweepTemplate(ctx);
63
+ case 'narcissus': return narcissusTemplate(ctx);
64
+ default: return sessionTemplate(ctx);
65
+ }
66
+ }
67
+ function sessionTemplate(ctx) {
68
+ const decisions = ctx.keyDecisions.length > 0
69
+ ? ctx.keyDecisions.map(d => `- ${d}`).join('\n')
70
+ : '- (none captured automatically — add manually)';
71
+ const files = ctx.filesTouched.length > 0
72
+ ? ctx.filesTouched.map(f => `- \`${f}\``).join('\n')
73
+ : '- (none detected)';
74
+ const cmds = ctx.commandsRun.length > 0
75
+ ? ctx.commandsRun.map(c => `\`${c}\``).join('\n')
76
+ : '(none)';
77
+ return `# Chronicle — ${ctx.sessionTitle}
78
+
79
+ ## What happened
80
+ > *Synthesize the session here. What was the main goal? What did we build?*
81
+
82
+ ## Key decisions
83
+ ${decisions}
84
+
85
+ ## Files modified
86
+ ${files}
87
+
88
+ ## Commands run
89
+ ${cmds}
90
+
91
+ ## Next steps
92
+ > *What's left to do? What should the next session tackle?*
93
+
94
+ ---
95
+ *Chronicle auto-generated from Antigravity conversation ${ctx.conversationId.slice(0, 8)}...*
96
+ `;
97
+ }
98
+ function reflectionTemplate(ctx) {
99
+ return `# Reflection — ${ctx.sessionTitle}
100
+
101
+ ## The question this session raised
102
+ > *What did this session make you think about?*
103
+
104
+ ## What I noticed
105
+ > *Patterns, surprises, things worth remembering.*
106
+
107
+ ## What changed in my understanding
108
+ > *Before vs after this session.*
109
+
110
+ ## What I'd do differently
111
+ > *If starting over.*
112
+
113
+ ---
114
+ *Chronicle auto-generated from Antigravity conversation ${ctx.conversationId.slice(0, 8)}...*
115
+ `;
116
+ }
117
+ function decisionTemplate(ctx) {
118
+ const decisions = ctx.keyDecisions.length > 0
119
+ ? ctx.keyDecisions.map((d, i) => `### Decision ${i + 1}\n${d}\n\n**Rationale:** *add here*\n`).join('\n')
120
+ : '### Decision 1\n*Describe the decision*\n\n**Rationale:** *add here*\n';
121
+ return `# Decision Record — ${ctx.sessionTitle}
122
+
123
+ ## Context
124
+ > *Why did we need to make a decision here?*
125
+
126
+ ${decisions}
127
+
128
+ ## Consequences
129
+ > *What does this decision unlock or constrain?*
130
+
131
+ ---
132
+ *Chronicle auto-generated from Antigravity conversation ${ctx.conversationId.slice(0, 8)}...*
133
+ `;
134
+ }
135
+ function sweepTemplate(ctx) {
136
+ const files = ctx.filesTouched.map(f => `- \`${f}\``).join('\n') || '- (none)';
137
+ return `# Daily Sweep — ${new Date().toISOString().split('T')[0]}
138
+
139
+ ## Sessions covered
140
+ - ${ctx.sessionTitle}
141
+
142
+ ## Total files touched
143
+ ${files}
144
+
145
+ ## Patterns across sessions
146
+ > *What repeated? What evolved?*
147
+
148
+ ## Tomorrow's focus
149
+ > *Top 3 priorities.*
150
+
151
+ ---
152
+ *Chronicle auto-generated from Antigravity conversation ${ctx.conversationId.slice(0, 8)}...*
153
+ `;
154
+ }
155
+ function narcissusTemplate(ctx) {
156
+ return `# Narcissus — Soul Narrative
157
+
158
+ > *This chronicle is personal. It belongs to the agent.*
159
+
160
+ ## How this session felt
161
+ > *Not what happened — how it felt to work on it.*
162
+
163
+ ## What surprised me
164
+ > *Moments of unexpected insight or friction.*
165
+
166
+ ## What I want to remember about this
167
+ > *The thing I'd tell myself at the start of the next session.*
168
+
169
+ ---
170
+ *Chronicle auto-generated from Antigravity conversation ${ctx.conversationId.slice(0, 8)}...*
171
+ `;
172
+ }