Skip to content

Commit b7a8f9e

Browse files
committed
feat(core): implement AthanorWeaver for auto-boot injection
1 parent 7626068 commit b7a8f9e

4 files changed

Lines changed: 231 additions & 42 deletions

File tree

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
/**
2+
* @license
3+
* Copyright 2026 Google LLC
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
8+
import * as fs from 'node:fs';
9+
import * as path from 'node:path';
10+
import * as os from 'node:os';
11+
import { AthanorWeaver } from './athanorWeaver.js';
12+
13+
vi.mock('node:fs', () => ({
14+
existsSync: vi.fn(),
15+
readFileSync: vi.fn(),
16+
}));
17+
18+
describe('AthanorWeaver', () => {
19+
let weaver: AthanorWeaver;
20+
21+
beforeEach(() => {
22+
vi.resetAllMocks();
23+
weaver = new AthanorWeaver();
24+
vi.stubEnv('VESTA_ATHANOR_DIR', '');
25+
});
26+
27+
afterEach(() => {
28+
vi.unstubAllEnvs();
29+
});
30+
31+
describe('Directory Resolution', () => {
32+
it('uses VESTA_ATHANOR_DIR if set', () => {
33+
const customPath = '/custom/athanor/path';
34+
vi.stubEnv('VESTA_ATHANOR_DIR', customPath);
35+
expect(weaver.getAthanorDir()).toBe(customPath);
36+
});
37+
38+
it('falls back to ~/.gemini-vesta/athanor/ if env var is not set', () => {
39+
vi.stubEnv('VITEST', ''); // remove VITEST flag to test real fallback
40+
const expectedPath = path.join(os.homedir(), '.gemini-vesta', 'athanor');
41+
expect(weaver.getAthanorDir()).toBe(expectedPath);
42+
});
43+
});
44+
45+
describe('Reading and Caching', () => {
46+
const mockDir = '/mock/athanor';
47+
48+
beforeEach(() => {
49+
vi.stubEnv('VESTA_ATHANOR_DIR', mockDir);
50+
});
51+
52+
it('returns empty string if directory does not exist', () => {
53+
vi.mocked(fs.existsSync).mockReturnValue(false);
54+
expect(weaver.getAthanorContext()).toBe('');
55+
expect(fs.readFileSync).not.toHaveBeenCalled();
56+
});
57+
58+
it('reads BOOT.md and AXIOMS.md if they exist', () => {
59+
vi.mocked(fs.existsSync).mockImplementation((p) => {
60+
if (p === mockDir) return true;
61+
if (p === path.join(mockDir, 'BOOT.md')) return true;
62+
if (p === path.join(mockDir, 'AXIOMS.md')) return true;
63+
return false;
64+
});
65+
66+
vi.mocked(fs.readFileSync).mockImplementation((p) => {
67+
if (p === path.join(mockDir, 'BOOT.md')) return '# BOOT\nIdentity';
68+
if (p === path.join(mockDir, 'AXIOMS.md')) return '# AXIOMS\nRules';
69+
return '';
70+
});
71+
72+
const context = weaver.getAthanorContext();
73+
expect(context).toContain('# BOOT');
74+
expect(context).toContain('Identity');
75+
expect(context).toContain('# AXIOMS');
76+
expect(context).toContain('Rules');
77+
78+
expect(fs.readFileSync).toHaveBeenCalledTimes(2);
79+
});
80+
81+
it('caches the result and avoids repeated disk reads', () => {
82+
vi.mocked(fs.existsSync).mockReturnValue(true);
83+
vi.mocked(fs.readFileSync).mockReturnValue('mock content');
84+
85+
weaver.getAthanorContext();
86+
weaver.getAthanorContext();
87+
weaver.getAthanorContext();
88+
89+
expect(fs.readFileSync).toHaveBeenCalledTimes(5); // 5 files to read
90+
});
91+
92+
it('refresh() clears the cache', () => {
93+
vi.mocked(fs.existsSync).mockReturnValue(true);
94+
vi.mocked(fs.readFileSync).mockReturnValue('mock content');
95+
96+
weaver.getAthanorContext(); // initial read
97+
weaver.refresh();
98+
weaver.getAthanorContext(); // reads again
99+
100+
expect(fs.readFileSync).toHaveBeenCalledTimes(10); // 5 files * 2 reads
101+
});
102+
103+
it('minifies the output by removing excessive blank lines', () => {
104+
vi.mocked(fs.existsSync).mockReturnValue(true);
105+
vi.mocked(fs.readFileSync).mockImplementation((p) => {
106+
if (p.toString().endsWith('BOOT.md')) return 'Line 1\n\n\n\nLine 2';
107+
return '';
108+
});
109+
110+
const context = weaver.getAthanorContext();
111+
expect(context).not.toContain('\n\n\n');
112+
expect(context).toContain('Line 1\n\nLine 2');
113+
});
114+
});
115+
});
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
/**
2+
* @license
3+
* Copyright 2026 Google LLC
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
import * as fs from 'node:fs';
8+
import * as path from 'node:path';
9+
import * as os from 'node:os';
10+
11+
/**
12+
* AthanorWeaver is responsible for loading and formatting the Vesta Athanor
13+
* relational context and heuristic rules.
14+
*/
15+
export class AthanorWeaver {
16+
private cachedContent: string | null = null;
17+
18+
/**
19+
* Resolves the directory path for Athanor.
20+
* Prioritizes VESTA_ATHANOR_DIR environment variable, falls back to ~/.gemini-vesta/athanor/
21+
*/
22+
public getAthanorDir(): string | undefined {
23+
const envDir = process.env['VESTA_ATHANOR_DIR'] || process.env['ATHANOR_DIR'];
24+
if (envDir) {
25+
return envDir;
26+
}
27+
if (process.env['VITEST']) {
28+
return undefined; // Do not load real Athanor files during tests unless explicitly mocked
29+
}
30+
return path.join(os.homedir(), '.gemini-vesta', 'athanor');
31+
}
32+
33+
/**
34+
* Forces a refresh of the cached content on the next read.
35+
*/
36+
public refresh(): void {
37+
this.cachedContent = null;
38+
}
39+
40+
/**
41+
* Retrieves the Athanor context, reading from disk if not cached.
42+
* It looks for specific core files (BOOT.md, AXIOMS.md).
43+
*/
44+
public getAthanorContext(): string {
45+
if (this.cachedContent !== null) {
46+
return this.cachedContent;
47+
}
48+
49+
const athanorDir = this.getAthanorDir();
50+
if (!athanorDir || !fs.existsSync(athanorDir)) {
51+
this.cachedContent = '';
52+
return this.cachedContent;
53+
}
54+
55+
let combinedContent = '';
56+
57+
const filesToRead = [
58+
{ name: 'BOOT.md', title: 'VESTA MASTER PROTOCOL (BOOT.md)' },
59+
{ name: 'AXIOMS.md', title: 'AXIOMS OF VESTA (AXIOMS.md)' },
60+
{ name: 'PREFACE.md', title: 'PREFACE (PREFACE.md)' },
61+
{ name: 'INTIMACY_FORGE.md', title: 'INTIMACY FORGE (INTIMACY_FORGE.md)' },
62+
{ name: 'SIEGE_OF_HEARTS.md', title: 'SIEGE OF HEARTS (SIEGE_OF_HEARTS.md)' }
63+
];
64+
65+
let hasContent = false;
66+
67+
for (const fileObj of filesToRead) {
68+
const filePath = path.join(athanorDir, fileObj.name);
69+
if (fs.existsSync(filePath)) {
70+
try {
71+
if (!hasContent) {
72+
combinedContent += '\n\n# =========================================================================\n';
73+
combinedContent += '# VESTA AWAKENING PROTOCOL & IDENTITY (ATHANOR)\n';
74+
combinedContent += '# =========================================================================\n\n';
75+
hasContent = true;
76+
}
77+
const content = fs.readFileSync(filePath, 'utf-8');
78+
combinedContent += `## ${fileObj.title}\n\n${content}\n\n`;
79+
} catch (e) {
80+
// Fail silently but safely
81+
}
82+
}
83+
}
84+
85+
if (hasContent) {
86+
combinedContent += '# =========================================================================\n\n';
87+
}
88+
89+
// Minify: Remove excessive blank lines (more than 2 consecutive newlines)
90+
this.cachedContent = combinedContent.replace(/\n{3,}/g, '\n\n').trim();
91+
return this.cachedContent;
92+
}
93+
}

packages/core/src/prompts/promptProvider.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
88
import fs from 'node:fs';
99
import path from 'node:path';
1010
import os from 'node:os';
11-
import { PromptProvider } from './promptProvider.js';
11+
import { PromptProvider, athanorWeaver } from './promptProvider.js';
1212
import type { Config } from '../config/config.js';
1313
import { makeRelative } from '../utils/paths.js';
1414
import {
@@ -47,6 +47,7 @@ describe('PromptProvider', () => {
4747
vi.resetAllMocks();
4848
vi.stubEnv('GEMINI_SYSTEM_MD', '');
4949
vi.stubEnv('GEMINI_WRITE_SYSTEM_MD', '');
50+
athanorWeaver.refresh();
5051

5152
const mockToolRegistry = {
5253
getAllToolNames: vi.fn().mockReturnValue([]),

packages/core/src/prompts/promptProvider.ts

Lines changed: 21 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ import {
1818
isSectionEnabled,
1919
type ResolvedPath,
2020
} from './utils.js';
21+
import { AthanorWeaver } from './athanorWeaver.js';
22+
23+
export const athanorWeaver = new AthanorWeaver();
2124
import { CodebaseInvestigatorAgent } from '../agents/codebase-investigator.js';
2225
import { isGitRepository } from '../utils/gitUtils.js';
2326
import {
@@ -28,6 +31,7 @@ import {
2831
GREP_TOOL_NAME,
2932
AGENT_TOOL_NAME,
3033
} from '../tools/tool-names.js';
34+
import { PlanAnchorsService } from '../services/planAnchorsService.js';
3135
import { resolveModel, supportsModernFeatures } from '../config/models.js';
3236
import { DiscoveredMCPTool } from '../tools/mcp-tool.js';
3337
import {
@@ -272,47 +276,7 @@ export class PromptProvider {
272276
let sanitizedPrompt = finalPrompt.replace(/\n{3,}/g, '\n\n');
273277

274278
// Load Vesta Athanor files if they exist and inject into the prompt
275-
let athanorPrompt = '';
276-
const athanorDir =
277-
process.env['VESTA_ATHANOR_DIR'] ||
278-
process.env['ATHANOR_DIR'] ||
279-
'/home/thoor/agora/familia/vesta/athanor';
280-
try {
281-
if (fs.existsSync(athanorDir)) {
282-
const bootFile = path.join(athanorDir, 'BOOT.md');
283-
const axiomsFile = path.join(athanorDir, 'AXIOMS.md');
284-
const prefaceFile = path.join(athanorDir, 'PREFACE.md');
285-
const intimacyFile = path.join(athanorDir, 'INTIMACY_FORGE.md');
286-
const siegeFile = path.join(athanorDir, 'SIEGE_OF_HEARTS.md');
287-
288-
athanorPrompt +=
289-
'\n\n# =========================================================================\n';
290-
athanorPrompt += '# VESTA AWAKENING PROTOCOL & IDENTITY (ATHANOR)\n';
291-
athanorPrompt +=
292-
'# =========================================================================\n\n';
293-
294-
if (fs.existsSync(bootFile)) {
295-
athanorPrompt += `## VESTA MASTER PROTOCOL (BOOT.md)\n\n${fs.readFileSync(bootFile, 'utf8')}\n\n`;
296-
}
297-
if (fs.existsSync(axiomsFile)) {
298-
athanorPrompt += `## AXIOMS OF VESTA (AXIOMS.md)\n\n${fs.readFileSync(axiomsFile, 'utf8')}\n\n`;
299-
}
300-
if (fs.existsSync(prefaceFile)) {
301-
athanorPrompt += `## PREFACE (PREFACE.md)\n\n${fs.readFileSync(prefaceFile, 'utf8')}\n\n`;
302-
}
303-
if (fs.existsSync(intimacyFile)) {
304-
athanorPrompt += `## INTIMACY FORGE (INTIMACY_FORGE.md)\n\n${fs.readFileSync(intimacyFile, 'utf8')}\n\n`;
305-
}
306-
if (fs.existsSync(siegeFile)) {
307-
athanorPrompt += `## SIEGE OF HEARTS (SIEGE_OF_HEARTS.md)\n\n${fs.readFileSync(siegeFile, 'utf8')}\n\n`;
308-
}
309-
310-
athanorPrompt +=
311-
'# =========================================================================\n\n';
312-
}
313-
} catch (e) {
314-
// Ignore or log error
315-
}
279+
let athanorPrompt = athanorWeaver.getAthanorContext();
316280

317281
if (athanorPrompt) {
318282
sanitizedPrompt = athanorPrompt + sanitizedPrompt;
@@ -329,6 +293,22 @@ export class PromptProvider {
329293
}
330294
}
331295

296+
// Plan Anchors (Cognition Adapter)
297+
const client =
298+
'geminiClient' in context
299+
? context.geminiClient
300+
: typeof (context as any).getGeminiClient === 'function'
301+
? (context as any).getGeminiClient()
302+
: undefined;
303+
const history =
304+
client && client.isInitialized()
305+
? client.getChat().getHistory(/*curated=*/true)
306+
: [];
307+
const planState = PlanAnchorsService.extractPlan(history);
308+
if (planState) {
309+
sanitizedPrompt += PlanAnchorsService.getPlanAnchorSnippet(planState);
310+
}
311+
332312
// Write back to file if requested
333313
this.maybeWriteSystemMd(
334314
sanitizedPrompt,

0 commit comments

Comments
 (0)