thatgfsj-code 0.2.1 → 0.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.
Files changed (64) hide show
  1. package/CHANGELOG.md +131 -0
  2. package/DEVELOPMENT.md +286 -0
  3. package/README.md +131 -30
  4. package/dist/core/ai-engine.d.ts +17 -0
  5. package/dist/core/ai-engine.d.ts.map +1 -1
  6. package/dist/core/ai-engine.js +23 -7
  7. package/dist/core/ai-engine.js.map +1 -1
  8. package/dist/index.js +18 -27
  9. package/dist/index.js.map +1 -1
  10. package/dist/repl/input.d.ts +8 -0
  11. package/dist/repl/input.d.ts.map +1 -1
  12. package/dist/repl/input.js +12 -0
  13. package/dist/repl/input.js.map +1 -1
  14. package/dist/repl/loop.d.ts +56 -0
  15. package/dist/repl/loop.d.ts.map +1 -1
  16. package/dist/repl/loop.js +333 -4
  17. package/dist/repl/loop.js.map +1 -1
  18. package/dist/repl/output.d.ts.map +1 -1
  19. package/dist/repl/output.js +3 -1
  20. package/dist/repl/output.js.map +1 -1
  21. package/dist/repl/welcome.js +1 -1
  22. package/dist/repl/welcome.js.map +1 -1
  23. package/docs/API_KEY_GUIDE.md +6 -0
  24. package/docs/FAQ.md +25 -3
  25. package/package.json +35 -3
  26. package/.patches/0001-fix-repl-replace-readline-with-inquirer-input-for-ke.patch +0 -279
  27. package/.patches/0002-fix-repl-stream-AI-output-directly-to-stdout-so-term.patch +0 -564
  28. package/.patches/0003-fix-session-break-the-hallucination-loop-in-assistan.patch +0 -194
  29. package/.patches/0004-chore-release-bump-version-to-0.2.1.patch +0 -24
  30. package/ROADMAP.md +0 -107
  31. package/src/agent/core.ts +0 -179
  32. package/src/agent/index.ts +0 -8
  33. package/src/agent/intent.ts +0 -181
  34. package/src/agent/streaming.ts +0 -132
  35. package/src/core/ai-engine.ts +0 -437
  36. package/src/core/cli.ts +0 -171
  37. package/src/core/config.ts +0 -147
  38. package/src/core/context-compactor.ts +0 -245
  39. package/src/core/hooks.ts +0 -196
  40. package/src/core/permissions.ts +0 -308
  41. package/src/core/session.ts +0 -165
  42. package/src/core/skills.ts +0 -208
  43. package/src/core/state.ts +0 -120
  44. package/src/core/subagent.ts +0 -195
  45. package/src/core/system-prompt.ts +0 -163
  46. package/src/core/tool-registry.ts +0 -157
  47. package/src/core/types.ts +0 -280
  48. package/src/index.ts +0 -544
  49. package/src/mcp/client.ts +0 -330
  50. package/src/repl/index.ts +0 -8
  51. package/src/repl/input.ts +0 -139
  52. package/src/repl/loop.ts +0 -280
  53. package/src/repl/output.ts +0 -222
  54. package/src/repl/welcome.ts +0 -296
  55. package/src/tools/file.ts +0 -117
  56. package/src/tools/git.ts +0 -132
  57. package/src/tools/index.ts +0 -48
  58. package/src/tools/search.ts +0 -263
  59. package/src/tools/shell.ts +0 -181
  60. package/src/utils/diff-preview.ts +0 -202
  61. package/src/utils/index.ts +0 -8
  62. package/src/utils/memory.ts +0 -223
  63. package/src/utils/project-context.ts +0 -207
  64. package/tsconfig.json +0 -19
@@ -1,208 +0,0 @@
1
- /**
2
- * S10: Skills System
3
- * Load skills from CLAUDE.md files and expose them as available commands
4
- *
5
- * Skills are domain-specific knowledge packages that extend agent capabilities.
6
- */
7
-
8
- import { readFileSync, existsSync, readdirSync, statSync } from 'fs';
9
- import { join, dirname } from 'path';
10
-
11
- export interface Skill {
12
- name: string;
13
- description: string;
14
- prompt: string;
15
- source: string; // File path or 'builtin'
16
- tags: string[];
17
- }
18
-
19
- export interface SkillsCatalog {
20
- skills: Skill[];
21
- loadedAt: Date;
22
- sources: string[];
23
- }
24
-
25
- /**
26
- * S10: Skills Loader — discovers and loads skills from filesystem
27
- */
28
- export class SkillsLoader {
29
- private skills: Map<string, Skill> = new Map();
30
- private sources: Set<string> = new Set();
31
-
32
- /**
33
- * S10: Load skills from a directory
34
- * Looks for .md files with # SkillName header
35
- */
36
- loadFromDir(dirPath: string): number {
37
- if (!existsSync(dirPath)) return 0;
38
-
39
- let count = 0;
40
- const files = readdirSync(dirPath);
41
-
42
- for (const file of files) {
43
- const fullPath = join(dirPath, file);
44
- const stat = statSync(fullPath);
45
-
46
- if (stat.isDirectory()) {
47
- count += this.loadFromDir(fullPath);
48
- continue;
49
- }
50
-
51
- if (file.endsWith('.md') || file.endsWith('.skill')) {
52
- const loaded = this.loadFromFile(fullPath);
53
- if (loaded) count++;
54
- }
55
- }
56
-
57
- return count;
58
- }
59
-
60
- /**
61
- * S10: Load a single skill file
62
- */
63
- loadFromFile(filePath: string): boolean {
64
- try {
65
- const content = readFileSync(filePath, 'utf-8');
66
- const skill = this.parseSkillFile(content, filePath);
67
- if (skill) {
68
- this.skills.set(skill.name, skill);
69
- this.sources.add(filePath);
70
- return true;
71
- }
72
- } catch (error) {
73
- // Ignore errors
74
- }
75
- return false;
76
- }
77
-
78
- /**
79
- * S10: Parse a skill file — extract name, description, prompt from markdown
80
- */
81
- private parseSkillFile(content: string, source: string): Skill | null {
82
- const lines = content.split('\n');
83
-
84
- // Find skill name from first heading
85
- let name = '';
86
- let description = '';
87
- let promptLines: string[] = [];
88
- let inPrompt = false;
89
- let tags: string[] = [];
90
-
91
- for (let i = 0; i < lines.length; i++) {
92
- const line = lines[i];
93
-
94
- // Match # SkillName or ## SkillName
95
- if (/^#{1,2}\s+(\S+)/.test(line.trim())) {
96
- if (!name) {
97
- name = line.replace(/^#{1,2}\s+/, '').trim();
98
- } else {
99
- // Second heading — end of prompt
100
- break;
101
- }
102
- continue;
103
- }
104
-
105
- // Description line (after name, before first code block or empty line)
106
- if (name && !description && line.trim() && !line.startsWith('```')) {
107
- description = line.trim();
108
- }
109
-
110
- // Code block — start of prompt template
111
- if (line.startsWith('```')) {
112
- if (!inPrompt) {
113
- inPrompt = true;
114
- continue;
115
- } else {
116
- // End of prompt
117
- break;
118
- }
119
- }
120
-
121
- if (inPrompt) {
122
- promptLines.push(line);
123
- }
124
- }
125
-
126
- if (!name) return null;
127
-
128
- return {
129
- name,
130
- description: description || name,
131
- prompt: promptLines.join('\n').trim(),
132
- source,
133
- tags: this.extractTags(content)
134
- };
135
- }
136
-
137
- private extractTags(content: string): string[] {
138
- const tagMatch = content.match(/tags?:\s*(.+)/i);
139
- if (tagMatch) {
140
- return tagMatch[1].split(/[,\s]+/).filter(Boolean);
141
- }
142
- return [];
143
- }
144
-
145
- /**
146
- * S10: Get all skills
147
- */
148
- getAll(): Skill[] {
149
- return [...this.skills.values()];
150
- }
151
-
152
- /**
153
- * S10: Get skill by name
154
- */
155
- get(name: string): Skill | undefined {
156
- return this.skills.get(name);
157
- }
158
-
159
- /**
160
- * S10: Search skills by tag or name
161
- */
162
- search(query: string): Skill[] {
163
- const q = query.toLowerCase();
164
- return this.getAll().filter(s =>
165
- s.name.toLowerCase().includes(q) ||
166
- s.description.toLowerCase().includes(q) ||
167
- s.tags.some(t => t.toLowerCase().includes(q))
168
- );
169
- }
170
-
171
- /**
172
- * S10: Get catalog
173
- */
174
- getCatalog(): SkillsCatalog {
175
- return {
176
- skills: this.getAll(),
177
- loadedAt: new Date(),
178
- sources: [...this.sources]
179
- };
180
- }
181
- }
182
-
183
- /**
184
- * S10: Built-in skills
185
- */
186
- export const BUILT_IN_SKILLS: Skill[] = [
187
- {
188
- name: 'explain-code',
189
- description: 'Explain what a piece of code does in plain English',
190
- prompt: 'Explain this code:\n\n```\n{{CODE}}\n```\n\nBe concise and clear.',
191
- source: 'builtin',
192
- tags: ['analysis', 'documentation']
193
- },
194
- {
195
- name: 'review-code',
196
- description: 'Review code for bugs, performance issues, and best practices',
197
- prompt: 'Review this code:\n\n```\n{{CODE}}\n```\n\nFocus on: bugs, security, performance, readability.',
198
- source: 'builtin',
199
- tags: ['review', 'quality']
200
- },
201
- {
202
- name: 'write-test',
203
- description: 'Generate unit tests for the given code',
204
- prompt: 'Write unit tests for:\n\n```\n{{CODE}}\n```\n\nUse a testing framework appropriate for the language.',
205
- source: 'builtin',
206
- tags: ['testing', 'quality']
207
- }
208
- ];
package/src/core/state.ts DELETED
@@ -1,120 +0,0 @@
1
- /**
2
- * S11: State & Session Management
3
- *
4
- * Three layers:
5
- * 1. EphemeralState - in-memory, current session only
6
- * 2. PersistentState - survives restarts (file-based)
7
- * 3. SessionState - per-conversation state
8
- */
9
-
10
- import { readFileSync, writeFileSync, existsSync } from 'fs';
11
- import { join } from 'path';
12
-
13
- /**
14
- * S11: Ephemeral State — in-memory key-value store
15
- */
16
- export class EphemeralState<T = any> {
17
- private data: Map<string, T> = new Map();
18
-
19
- get(key: string): T | undefined { return this.data.get(key); }
20
- set(key: string, value: T): void { this.data.set(key, value); }
21
- has(key: string): boolean { return this.data.has(key); }
22
- delete(key: string): void { this.data.delete(key); }
23
- clear(): void { this.data.clear(); }
24
- keys(): string[] { return [...this.data.keys()]; }
25
- entries(): [string, T][] { return [...this.data.entries()]; }
26
- }
27
-
28
- /**
29
- * S11: Persistent State — file-backed key-value store
30
- */
31
- export class PersistentState<T = any> {
32
- private filePath: string;
33
- private data: Record<string, T> = {};
34
-
35
- constructor(filePath: string) {
36
- this.filePath = filePath;
37
- this.load();
38
- }
39
-
40
- private load(): void {
41
- try {
42
- if (existsSync(this.filePath)) {
43
- this.data = JSON.parse(readFileSync(this.filePath, 'utf-8'));
44
- }
45
- } catch {
46
- this.data = {};
47
- }
48
- }
49
-
50
- private save(): void {
51
- try {
52
- writeFileSync(this.filePath, JSON.stringify(this.data, null, 2), 'utf-8');
53
- } catch (error: any) {
54
- console.error('[State] Failed to save:', error.message);
55
- }
56
- }
57
-
58
- get(key: string): T | undefined { return this.data[key]; }
59
- set(key: string, value: T): void { this.data[key] = value; this.save(); }
60
- has(key: string): boolean { return key in this.data; }
61
- delete(key: string): void { delete this.data[key]; this.save(); }
62
- clear(): void { this.data = {}; this.save(); }
63
- keys(): string[] { return Object.keys(this.data); }
64
- }
65
-
66
- /**
67
- * S11: Session State — per-conversation state
68
- */
69
- export class SessionState<T = any> {
70
- private sessions: Map<string, EphemeralState<T>> = new Map();
71
- private currentSessionId: string | null = null;
72
-
73
- createSession(sessionId: string): void {
74
- this.sessions.set(sessionId, new EphemeralState<T>());
75
- }
76
-
77
- deleteSession(sessionId: string): void {
78
- this.sessions.delete(sessionId);
79
- }
80
-
81
- setCurrentSession(sessionId: string): void {
82
- if (!this.sessions.has(sessionId)) {
83
- this.createSession(sessionId);
84
- }
85
- this.currentSessionId = sessionId;
86
- }
87
-
88
- getCurrentSession(): EphemeralState<T> | null {
89
- if (!this.currentSessionId) return null;
90
- return this.sessions.get(this.currentSessionId) || null;
91
- }
92
-
93
- getSession(sessionId: string): EphemeralState<T> | null {
94
- return this.sessions.get(sessionId) || null;
95
- }
96
- }
97
-
98
- /**
99
- * S11: Global State Manager
100
- */
101
- export class StateManager {
102
- ephemeral: EphemeralState;
103
- persistent: PersistentState;
104
- session: SessionState;
105
-
106
- constructor(dataDir: string = join(process.cwd(), '.thatgfsj', 'state')) {
107
- this.ephemeral = new EphemeralState();
108
- this.persistent = new PersistentState(join(dataDir, 'persistent.json'));
109
- this.session = new SessionState();
110
- }
111
- }
112
-
113
- let globalStateManager: StateManager | null = null;
114
-
115
- export function getStateManager(): StateManager {
116
- if (!globalStateManager) {
117
- globalStateManager = new StateManager();
118
- }
119
- return globalStateManager;
120
- }
@@ -1,195 +0,0 @@
1
- /**
2
- * S06: Subagent Manager
3
- * Spawn and manage subagents for parallel task execution
4
- *
5
- * Subagents enable "infinite parallelism" — complex tasks are broken into
6
- * independent subtasks, each run by a separate agent instance.
7
- */
8
-
9
- import { AIEngine, ChatMessage } from './ai-engine.js';
10
- import { AIConfig } from './types.js';
11
-
12
- export interface SubagentOptions {
13
- /** Task prompt for the subagent */
14
- prompt: string;
15
- /** Custom system prompt (optional) */
16
- systemPrompt?: string;
17
- /** Tools available to this subagent */
18
- tools?: any[];
19
- /** Max iterations for this subagent */
20
- maxIterations?: number;
21
- /** Subagent name for logging */
22
- name?: string;
23
- /** Working directory */
24
- cwd?: string;
25
- }
26
-
27
- export interface SubagentResult {
28
- success: boolean;
29
- output: string;
30
- error?: string;
31
- iterations: number;
32
- durationMs: number;
33
- name: string;
34
- }
35
-
36
- /**
37
- * S06: Subagent Manager
38
- */
39
- export class SubagentManager {
40
- private config: AIConfig;
41
- private activeSubagents: Map<string, {
42
- task: Promise<SubagentResult>;
43
- startedAt: Date;
44
- name: string;
45
- }> = new Map();
46
- private maxConcurrent = 3;
47
-
48
- constructor(config: AIConfig) {
49
- this.config = config;
50
- }
51
-
52
- /**
53
- * S06: Spawn a subagent to run a task asynchronously
54
- */
55
- async spawn(options: SubagentOptions): Promise<SubagentResult> {
56
- const name = options.name || `subagent_${Date.now()}`;
57
- const startedAt = new Date();
58
-
59
- const task = this.runSubagent(name, options, startedAt);
60
-
61
- // Track active subagent
62
- this.activeSubagents.set(name, { task, startedAt, name });
63
-
64
- // Auto-cleanup after completion
65
- task.then(() => {
66
- setTimeout(() => this.activeSubagents.delete(name), 5000);
67
- }).catch(() => {
68
- this.activeSubagents.delete(name);
69
- });
70
-
71
- // Return handle — caller can await or ignore
72
- return task;
73
- }
74
-
75
- /**
76
- * S06: Spawn multiple subagents in parallel (bounded concurrency)
77
- */
78
- async spawnAll(tasks: SubagentOptions[]): Promise<SubagentResult[]> {
79
- const results: SubagentResult[] = [];
80
- const queue = [...tasks];
81
- const running: Promise<void>[] = [];
82
-
83
- while (queue.length > 0 || running.length > 0) {
84
- while (running.length < this.maxConcurrent && queue.length > 0) {
85
- const task = queue.shift()!;
86
- const p: Promise<void> = this.spawn(task).then(r => { results.push(r); }).then(() => {});
87
- running.push(p);
88
- }
89
-
90
- if (running.length > 0) {
91
- await Promise.race(running).catch(() => {});
92
- await Promise.allSettled(running);
93
- running.length = 0;
94
- }
95
- }
96
-
97
- return results;
98
- }
99
-
100
- /**
101
- * S06: Run a single subagent task
102
- */
103
- private async runSubagent(
104
- name: string,
105
- options: SubagentOptions,
106
- startedAt: Date
107
- ): Promise<SubagentResult> {
108
- const startMs = Date.now();
109
- let iterations = 0;
110
- const maxIterations = options.maxIterations || 5;
111
-
112
- try {
113
- // Create isolated AI engine for subagent
114
- const subAI = new AIEngine({
115
- ...this.config,
116
- model: options.systemPrompt ? (this.config.model || 'unknown') : this.config.model
117
- });
118
-
119
- // Register tools if provided
120
- if (options.tools) {
121
- for (const tool of options.tools) {
122
- subAI.registerTool(tool);
123
- }
124
- }
125
-
126
- // Build messages
127
- const messages: ChatMessage[] = [];
128
- if (options.systemPrompt) {
129
- messages.push({ role: 'system', content: options.systemPrompt });
130
- } else {
131
- messages.push({
132
- role: 'system',
133
- content: 'You are ' + (options.name || 'a subagent') + '. Complete the task assigned by the parent agent. Be concise and focused.'
134
- });
135
- }
136
- messages.push({ role: 'user', content: options.prompt });
137
-
138
- // Run agent loop
139
- let output = '';
140
- for await (const chunk of subAI.chatStream(messages, maxIterations)) {
141
- output += chunk;
142
- iterations++;
143
- }
144
-
145
- return {
146
- success: true,
147
- output,
148
- iterations,
149
- durationMs: Date.now() - startMs,
150
- name
151
- };
152
-
153
- } catch (error: any) {
154
- return {
155
- success: false,
156
- output: '',
157
- error: error.message,
158
- iterations,
159
- durationMs: Date.now() - startMs,
160
- name
161
- };
162
- }
163
- }
164
-
165
- /**
166
- * S06: Get number of active subagents
167
- */
168
- getActiveCount(): number {
169
- return this.activeSubagents.size;
170
- }
171
-
172
- /**
173
- * S06: Get active subagent names
174
- */
175
- getActiveNames(): string[] {
176
- return [...this.activeSubagents.values()].map(s => s.name);
177
- }
178
-
179
- /**
180
- * S06: Abort all active subagents
181
- */
182
- abortAll(): void {
183
- for (const [name] of this.activeSubagents) {
184
- // Subagents don't have abort support yet, just remove from tracking
185
- this.activeSubagents.delete(name);
186
- }
187
- }
188
-
189
- /**
190
- * S06: Set max concurrent subagents
191
- */
192
- setMaxConcurrent(max: number) {
193
- this.maxConcurrent = max;
194
- }
195
- }
@@ -1,163 +0,0 @@
1
- /**
2
- * S04: System Prompt Builder
3
- * Dynamic system prompt construction - not a static string, but a program
4
- * that assembles context from multiple sources each turn
5
- */
6
-
7
- import { readFileSync, existsSync } from 'fs';
8
- import { join } from 'path';
9
- import { Tool } from './types.js';
10
-
11
- export interface SystemPromptConfig {
12
- cwd?: string;
13
- tools?: Tool[];
14
- includeClaudeMd?: boolean;
15
- projectInstructions?: string;
16
- permissionMode?: 'accept' | 'deny' | 'ask';
17
- date?: Date;
18
- model?: string;
19
- }
20
-
21
- /**
22
- * S04: System Prompt Builder - assembles prompt from multiple fragments
23
- */
24
- export class SystemPromptBuilder {
25
- private config: SystemPromptConfig;
26
-
27
- constructor(config: SystemPromptConfig = {}) {
28
- this.config = {
29
- cwd: process.cwd(),
30
- tools: [],
31
- includeClaudeMd: true,
32
- permissionMode: 'ask',
33
- date: new Date(),
34
- model: 'unknown',
35
- ...config
36
- };
37
- }
38
-
39
- build(): string {
40
- const fragments: string[] = [];
41
- fragments.push(this.buildIdentity());
42
- fragments.push(this.buildToolInstructions());
43
- fragments.push(this.buildEnvironment());
44
- fragments.push(this.buildPermissionMode());
45
- fragments.push(this.buildProjectInstructions());
46
- fragments.push(this.buildDateInfo());
47
- fragments.push(this.buildOutputFormat());
48
- return fragments.filter(Boolean).join('\n\n');
49
- }
50
-
51
- private buildIdentity(): string {
52
- return [
53
- 'You are Thatgfsj Code, an AI coding assistant.',
54
- '',
55
- 'You have access to tools (file operations, shell commands, git, search) that let you',
56
- 'read, write, and modify files and run commands.',
57
- '',
58
- 'Be concise, helpful, and show your reasoning when working through problems.',
59
- '',
60
- 'When using tools:',
61
- '- Prefer small, targeted operations over large batch changes',
62
- '- Confirm destructive operations (rm, git push --force, etc.)',
63
- '- Explain what you are going to do before doing it',
64
- '- If something is not clear, ask clarifying questions'
65
- ].join('\n');
66
- }
67
-
68
- private buildToolInstructions(): string {
69
- const tools = this.config.tools || [];
70
- if (tools.length === 0) {
71
- return '## Tools\n\nNo tools are currently registered.';
72
- }
73
-
74
- const toolDescs = tools.map(t => {
75
- const params = t.parameters
76
- .map(p => ' - ' + p.name + ' (' + p.type + (p.required ? ', required' : '') + '): ' + p.description)
77
- .join('\n');
78
- return '### ' + t.name + '\n' + t.description + '\n\nParameters:\n' + params;
79
- }).join('\n\n');
80
-
81
- return '## Tools\n\nYou have access to the following tools:\n\n' + toolDescs + '\n\nTo use a tool, respond with a tool call. Each tool call must include:\n- The tool name\n- Parameters as a JSON object';
82
- }
83
-
84
- private buildEnvironment(): string {
85
- const cwd = this.config.cwd || process.cwd();
86
- return '## Environment\n\nWorking directory: ' + cwd;
87
- }
88
-
89
- private buildPermissionMode(): string {
90
- const mode = this.config.permissionMode || 'ask';
91
- const explanations: Record<string, string> = {
92
- accept: 'All tool calls are automatically allowed without confirmation.',
93
- deny: 'All tool calls are blocked. You may only read and discuss.',
94
- ask: 'Dangerous or destructive commands require your confirmation before execution.'
95
- };
96
- return '## Permission Mode\n\nCurrent mode: ' + mode + '\n\n' + (explanations[mode] || '');
97
- }
98
-
99
- private buildProjectInstructions(): string {
100
- const cwd = this.config.cwd || process.cwd();
101
- if (this.config.includeClaudeMd !== false) {
102
- const paths = [join(cwd, 'CLAUDE.md'), join(cwd, '.claude.md')];
103
- for (const path of paths) {
104
- if (existsSync(path)) {
105
- try {
106
- const content = readFileSync(path, 'utf-8');
107
- return '## Project Instructions (from ' + path.split(/[/\\]/).pop() + ')\n\n' + content;
108
- } catch {
109
- // Ignore
110
- }
111
- }
112
- }
113
- }
114
- if (this.config.projectInstructions) {
115
- return '## Project Instructions\n\n' + this.config.projectInstructions;
116
- }
117
- return '';
118
- }
119
-
120
- private buildDateInfo(): string {
121
- const now = this.config.date || new Date();
122
- const iso = now.toISOString().replace('T', ' ').split('.')[0];
123
- return '## Current Time\n\n' + iso;
124
- }
125
-
126
- private buildOutputFormat(): string {
127
- return [
128
- '## Output Format',
129
- '',
130
- '- Use clear section headers for multi-part responses',
131
- '- Use bullet points for lists',
132
- '- Use code blocks for code',
133
- '- Be concise, avoid unnecessary repetition',
134
- '- If you need multiple steps, explain the plan first'
135
- ].join('\n');
136
- }
137
-
138
- setTools(tools: Tool[]) { this.config.tools = tools; return this; }
139
- setCwd(cwd: string) { this.config.cwd = cwd; return this; }
140
- setPermissionMode(mode: 'accept' | 'deny' | 'ask') { this.config.permissionMode = mode; return this; }
141
- setProjectInstructions(instructions: string) { this.config.projectInstructions = instructions; return this; }
142
- setModel(model: string) { this.config.model = model; return this; }
143
-
144
- buildToolsFragment(): string { return this.buildToolInstructions(); }
145
-
146
- injectReminder(reminder: string): string {
147
- return '\n## Reminder\n\n' + reminder + '\n';
148
- }
149
- }
150
-
151
- let globalBuilder: SystemPromptBuilder | null = null;
152
-
153
- export function getSystemPromptBuilder(config?: SystemPromptConfig): SystemPromptBuilder {
154
- if (!globalBuilder) globalBuilder = new SystemPromptBuilder(config);
155
- return globalBuilder;
156
- }
157
-
158
- export function updateSystemPrompt(tools?: Tool[], permissionMode?: 'accept' | 'deny' | 'ask') {
159
- if (globalBuilder) {
160
- if (tools) globalBuilder.setTools(tools);
161
- if (permissionMode) globalBuilder.setPermissionMode(permissionMode);
162
- }
163
- }