@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.
- package/CHANGELOG.md +34 -34
- package/LICENSE +21 -21
- package/README.md +230 -230
- package/dist/cli.js +667 -76
- package/dist/lib/chronicle.js +105 -0
- package/dist/lib/init-flow.js +139 -0
- package/dist/lib/providers.js +46 -0
- package/dist/lib/sources/antigravity.js +142 -0
- package/dist/lib/sources/index.js +172 -0
- package/dist/lib/vault.js +118 -0
- package/package.json +14 -2
- package/dist/templates/index.template.js +0 -36
- package/src/cli.ts +0 -121
- package/src/templates/.cursorrules +0 -55
- package/src/templates/AGENT_INSTRUCTIONS.md +0 -60
- package/src/templates/index.template.tsx +0 -34
- package/tsconfig.json +0 -15
|
@@ -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
|
+
}
|