codemini-cli 0.5.9 → 0.5.10
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 +346 -247
- package/codemini-web/dist/assets/{highlighted-body-OFNGDK62-HgeDi9HJ.js → highlighted-body-OFNGDK62-7HL7yft8.js} +1 -1
- package/codemini-web/dist/assets/{index-C4tKT3v4.js → index-BK75hMb2.js} +4 -4
- package/codemini-web/dist/assets/mermaid-GHXKKRXX-Dg9qh8mg.js +1 -0
- package/codemini-web/dist/index.html +1 -1
- package/package.json +1 -1
- package/skills/brainstorm/SKILL.md +8 -3
- package/src/core/ast.js +30 -15
- package/src/core/chat-runtime.js +16 -3
- package/src/core/config-store.js +3 -1
- package/src/core/project-index.js +21 -2
- package/src/core/project-instructions.js +98 -0
- package/src/core/shell.js +79 -73
- package/src/core/system-prompt-composer.js +10 -0
- package/codemini-web/dist/assets/mermaid-GHXKKRXX-CDgkkDBg.js +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{i as e}from"./index-BK75hMb2.js";export{e as Mermaid};
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
document.documentElement.dataset.palette = palette;
|
|
15
15
|
})();
|
|
16
16
|
</script>
|
|
17
|
-
<script type="module" crossorigin src="/assets/index-
|
|
17
|
+
<script type="module" crossorigin src="/assets/index-BK75hMb2.js"></script>
|
|
18
18
|
<link rel="modulepreload" crossorigin href="/assets/rolldown-runtime-S-ySWqyJ.js">
|
|
19
19
|
<link rel="stylesheet" crossorigin href="/assets/index-BSdIdn3L.css">
|
|
20
20
|
</head>
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: brainstorm
|
|
3
3
|
description: Lightweight brainstorming skill. Use when a feature or behavior request has multiple reasonable approaches and the missing piece is user preference, tradeoff choice, or key constraint.
|
|
4
|
-
version: 0.2.
|
|
4
|
+
version: 0.2.1
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
Use this skill only when the task needs clarification or option comparison before coding.
|
|
@@ -18,6 +18,7 @@ Do NOT skip this skill for tasks that appear straightforward but lack clear requ
|
|
|
18
18
|
2. **Give 2-3 short options** only when the blocking constraint is already clear. Keep options concrete and focused on the main tradeoff.
|
|
19
19
|
3. **Present conclusions as suggested decisions**, not final choices.
|
|
20
20
|
4. **Do NOT write code, pseudo-code, file edits, or broad repo exploration** while direction is still being chosen.
|
|
21
|
+
5. **Stop after your brainstorm response.** Do not say "I will start", "starting now", "I'll edit", or otherwise transition into implementation in the same turn.
|
|
21
22
|
|
|
22
23
|
## Output Formats
|
|
23
24
|
|
|
@@ -54,6 +55,8 @@ Suggested decision:
|
|
|
54
55
|
- reason: <why>
|
|
55
56
|
```
|
|
56
57
|
|
|
58
|
+
After Mode B, STOP. Wait for the user to approve, reject, or revise the suggested decision.
|
|
59
|
+
|
|
57
60
|
## Self-Review
|
|
58
61
|
|
|
59
62
|
Before presenting options or a suggested decision, quickly check:
|
|
@@ -64,9 +67,11 @@ Before presenting options or a suggested decision, quickly check:
|
|
|
64
67
|
|
|
65
68
|
## Exit
|
|
66
69
|
|
|
67
|
-
|
|
70
|
+
Brainstorm ends only when the user sends a later message that clearly approves a direction, for example "use option 2", "按这个做", "确认,开始实现", or "直接写代码".
|
|
71
|
+
|
|
72
|
+
After that later user approval:
|
|
68
73
|
|
|
69
74
|
- If the task is small and clear enough to implement directly → proceed to code.
|
|
70
75
|
- If the task is non-trivial or touches multiple areas → YOU MUST invoke `writing-plans` to create an implementation plan before coding.
|
|
71
76
|
|
|
72
|
-
Do NOT
|
|
77
|
+
Do NOT treat your own suggested decision as approval. Do NOT continue from the brainstorm conclusion into planning or implementation until the user has explicitly approved a direction in a separate message.
|
package/src/core/ast.js
CHANGED
|
@@ -4,6 +4,7 @@ import { createRequire } from 'node:module';
|
|
|
4
4
|
import { Parser, Language, Query } from 'web-tree-sitter';
|
|
5
5
|
import { LANGUAGE_ALIASES, EXTENSION_LANGUAGE_MAP } from './constants.js';
|
|
6
6
|
import { sha256Prefixed as sha256 } from './crypto-utils.js';
|
|
7
|
+
import { BoundedCache } from './bounded-cache.js';
|
|
7
8
|
|
|
8
9
|
const require = createRequire(import.meta.url);
|
|
9
10
|
|
|
@@ -43,7 +44,7 @@ const parserInitPromise = Parser.init({
|
|
|
43
44
|
return scriptName === 'web-tree-sitter.wasm' ? TREE_SITTER_WASM_PATH : scriptName;
|
|
44
45
|
}
|
|
45
46
|
});
|
|
46
|
-
const languageCache = new
|
|
47
|
+
const languageCache = new BoundedCache({ maxSize: 16, ttlMs: 60 * 60 * 1000 });
|
|
47
48
|
|
|
48
49
|
function clipText(text, maxLen = 220) {
|
|
49
50
|
const normalized = String(text || '').replace(/\s+/g, ' ').trim();
|
|
@@ -113,15 +114,34 @@ async function loadLanguage(language) {
|
|
|
113
114
|
if (languageCache.has(language)) return languageCache.get(language);
|
|
114
115
|
const wasmPath = LANGUAGE_WASM_PATHS[language];
|
|
115
116
|
if (!wasmPath) throw new Error(`Unsupported Tree-sitter language: ${language}`);
|
|
116
|
-
const
|
|
117
|
-
languageCache.set(language,
|
|
118
|
-
|
|
117
|
+
const loadPromise = Language.load(wasmPath);
|
|
118
|
+
languageCache.set(language, loadPromise);
|
|
119
|
+
try {
|
|
120
|
+
return await loadPromise;
|
|
121
|
+
} catch (error) {
|
|
122
|
+
languageCache.delete(language);
|
|
123
|
+
throw error;
|
|
124
|
+
}
|
|
119
125
|
}
|
|
120
126
|
|
|
121
|
-
async function
|
|
127
|
+
async function getParser(language) {
|
|
122
128
|
const loadedLanguage = await loadLanguage(language);
|
|
123
129
|
const parser = new Parser();
|
|
124
130
|
parser.setLanguage(loadedLanguage);
|
|
131
|
+
return { parser, loadedLanguage };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function deleteParsed(parsed) {
|
|
135
|
+
try {
|
|
136
|
+
parsed?.tree?.delete?.();
|
|
137
|
+
} catch {}
|
|
138
|
+
try {
|
|
139
|
+
parsed?.parser?.delete?.();
|
|
140
|
+
} catch {}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
async function parseContent(content, language) {
|
|
144
|
+
const { parser, loadedLanguage } = await getParser(language);
|
|
125
145
|
const tree = parser.parse(content);
|
|
126
146
|
return { parser, tree, loadedLanguage };
|
|
127
147
|
}
|
|
@@ -188,8 +208,7 @@ export async function findEnclosingSymbol(content, filePath, line) {
|
|
|
188
208
|
} catch {
|
|
189
209
|
return null;
|
|
190
210
|
} finally {
|
|
191
|
-
|
|
192
|
-
if (parser) parser.delete();
|
|
211
|
+
deleteParsed({ tree, parser });
|
|
193
212
|
}
|
|
194
213
|
}
|
|
195
214
|
|
|
@@ -223,8 +242,7 @@ export async function queryAst(root, args) {
|
|
|
223
242
|
}
|
|
224
243
|
|
|
225
244
|
query.delete();
|
|
226
|
-
parsed
|
|
227
|
-
parsed.parser.delete();
|
|
245
|
+
deleteParsed(parsed);
|
|
228
246
|
|
|
229
247
|
return {
|
|
230
248
|
path: relativePath,
|
|
@@ -261,8 +279,7 @@ export async function readAstNode(root, args) {
|
|
|
261
279
|
child_summaries: node.namedChildren.slice(0, 8).map((child) => summarizeNode(child))
|
|
262
280
|
};
|
|
263
281
|
|
|
264
|
-
parsed
|
|
265
|
-
parsed.parser.delete();
|
|
282
|
+
deleteParsed(parsed);
|
|
266
283
|
return result;
|
|
267
284
|
}
|
|
268
285
|
|
|
@@ -277,15 +294,13 @@ export async function resolveAstTarget(root, relativePath, astTarget) {
|
|
|
277
294
|
const parsed = await parseFile(root, relativePath, astTarget.language);
|
|
278
295
|
const node = exactNodeForTarget(parsed.tree.rootNode, astTarget);
|
|
279
296
|
if (!node) {
|
|
280
|
-
parsed
|
|
281
|
-
parsed.parser.delete();
|
|
297
|
+
deleteParsed(parsed);
|
|
282
298
|
throw new Error('AST target no longer matches the current file');
|
|
283
299
|
}
|
|
284
300
|
|
|
285
301
|
const currentHash = sha256(node.text);
|
|
286
302
|
if (String(astTarget.range_hash || '') !== currentHash) {
|
|
287
|
-
parsed
|
|
288
|
-
parsed.parser.delete();
|
|
303
|
+
deleteParsed(parsed);
|
|
289
304
|
throw new Error('ast_target range_hash mismatch; the selected node changed and is now stale');
|
|
290
305
|
}
|
|
291
306
|
|
package/src/core/chat-runtime.js
CHANGED
|
@@ -128,6 +128,8 @@ function getCompletionCopy(language = 'zh') {
|
|
|
128
128
|
'context.prompt_budget_audit': 'Prompt 预算审计开关',
|
|
129
129
|
'context.microcompact_enabled': '微压缩(micro-compact)开关',
|
|
130
130
|
'context.microcompact_keep_recent': '微压缩保留最近工具结果数',
|
|
131
|
+
'context.project_instructions_enabled': '项目 AGENTS.md 注入开关',
|
|
132
|
+
'context.project_instructions_max_chars': '项目 AGENTS.md 字符上限',
|
|
131
133
|
'sessions.max_sessions': '会话保留上限',
|
|
132
134
|
'sessions.retention_days': '会话保留天数',
|
|
133
135
|
'shell.default': '默认 shell',
|
|
@@ -148,7 +150,9 @@ function getCompletionCopy(language = 'zh') {
|
|
|
148
150
|
'policy.safe_mode': '可选:true | false',
|
|
149
151
|
'policy.allowed_paths': 'JSON 数组,例如 ["D:\\\\shared"]',
|
|
150
152
|
'policy.allow_dangerous_commands': '可选:true | false',
|
|
151
|
-
'context.prompt_budget_audit': '可选:true | false'
|
|
153
|
+
'context.prompt_budget_audit': '可选:true | false',
|
|
154
|
+
'context.project_instructions_enabled': '可选:true | false',
|
|
155
|
+
'context.project_instructions_max_chars': '建议:8000-12000'
|
|
152
156
|
},
|
|
153
157
|
describeSet: (label, hint) => `设置${label}${hint ? `(${hint})` : ''}`,
|
|
154
158
|
describeGet: (label, hint) => `查看${label}${hint ? `(${hint})` : ''}`,
|
|
@@ -237,6 +241,8 @@ function getCompletionCopy(language = 'zh') {
|
|
|
237
241
|
'context.prompt_budget_audit': 'prompt budget audit switch',
|
|
238
242
|
'context.microcompact_enabled': 'micro-compact enabled',
|
|
239
243
|
'context.microcompact_keep_recent': 'micro-compact keep recent tool results',
|
|
244
|
+
'context.project_instructions_enabled': 'project AGENTS.md injection switch',
|
|
245
|
+
'context.project_instructions_max_chars': 'project AGENTS.md character limit',
|
|
240
246
|
'sessions.max_sessions': 'stored session limit',
|
|
241
247
|
'sessions.retention_days': 'session retention days',
|
|
242
248
|
'shell.default': 'default shell',
|
|
@@ -257,7 +263,9 @@ function getCompletionCopy(language = 'zh') {
|
|
|
257
263
|
'policy.safe_mode': 'options: true | false',
|
|
258
264
|
'policy.allowed_paths': 'JSON array, for example ["D:\\\\shared"]',
|
|
259
265
|
'policy.allow_dangerous_commands': 'options: true | false',
|
|
260
|
-
'context.prompt_budget_audit': 'options: true | false'
|
|
266
|
+
'context.prompt_budget_audit': 'options: true | false',
|
|
267
|
+
'context.project_instructions_enabled': 'options: true | false',
|
|
268
|
+
'context.project_instructions_max_chars': 'recommended: 8000-12000'
|
|
261
269
|
},
|
|
262
270
|
describeSet: (label, hint) => `set the ${label}${hint ? ` (${hint})` : ''}`,
|
|
263
271
|
describeGet: (label, hint) => `show the ${label}${hint ? ` (${hint})` : ''}`,
|
|
@@ -2287,7 +2295,10 @@ function summarizePromptBudgetAudit(audit) {
|
|
|
2287
2295
|
}
|
|
2288
2296
|
|
|
2289
2297
|
function buildRuntimeStateSnapshot({ currentSession, config, model, executionMode, extraSession }) {
|
|
2290
|
-
const
|
|
2298
|
+
const activeParentMessages = Array.isArray(currentSession?.compact?.view) && currentSession.compact.view.length > 0
|
|
2299
|
+
? currentSession.compact.view
|
|
2300
|
+
: currentSession?.messages || [];
|
|
2301
|
+
const parentTokens = estimateMessagesTokens(activeParentMessages);
|
|
2291
2302
|
const subTokens = extraSession ? estimateMessagesTokens(extraSession.messages || []) : 0;
|
|
2292
2303
|
const currentContextTokens = parentTokens + subTokens;
|
|
2293
2304
|
const maxContextTokens = effectiveMaxContextTokens(config);
|
|
@@ -4388,6 +4399,8 @@ export async function createChatRuntime({
|
|
|
4388
4399
|
'context.read_file_max_chars',
|
|
4389
4400
|
'context.microcompact_enabled',
|
|
4390
4401
|
'context.microcompact_keep_recent',
|
|
4402
|
+
'context.project_instructions_enabled',
|
|
4403
|
+
'context.project_instructions_max_chars',
|
|
4391
4404
|
'sessions.max_sessions',
|
|
4392
4405
|
'sessions.retention_days',
|
|
4393
4406
|
'shell.timeout_ms',
|
package/src/core/config-store.js
CHANGED
|
@@ -36,7 +36,9 @@ const DEFAULT_CONFIG = {
|
|
|
36
36
|
read_file_max_chars: 24000,
|
|
37
37
|
prompt_budget_audit: false,
|
|
38
38
|
microcompact_enabled: true,
|
|
39
|
-
microcompact_keep_recent: 5
|
|
39
|
+
microcompact_keep_recent: 5,
|
|
40
|
+
project_instructions_enabled: true,
|
|
41
|
+
project_instructions_max_chars: 12000
|
|
40
42
|
},
|
|
41
43
|
execution: {
|
|
42
44
|
mode: 'normal',
|
|
@@ -25,6 +25,7 @@ const PROJECT_MARKER_FILES = new Set([
|
|
|
25
25
|
const LANGUAGE_BY_EXT = EXTENSION_LANGUAGE_MAP;
|
|
26
26
|
|
|
27
27
|
const initCache = new BoundedCache({ maxSize: 32, ttlMs: 10 * 60 * 1000 });
|
|
28
|
+
const ignoreRulesCache = new BoundedCache({ maxSize: 128, ttlMs: 60 * 1000 });
|
|
28
29
|
const PROJECT_CONTEXT_MAX_FILES = 6;
|
|
29
30
|
|
|
30
31
|
function clipList(values, max = 32) {
|
|
@@ -92,9 +93,24 @@ function gitignorePatternToRegex(pattern) {
|
|
|
92
93
|
}
|
|
93
94
|
|
|
94
95
|
async function readIgnoreFileRules(cwd, fileName) {
|
|
96
|
+
const filePath = path.join(cwd, fileName);
|
|
97
|
+
const stat = await safeStat(filePath);
|
|
98
|
+
const cacheKey = `${filePath}:${Number(stat?.mtimeMs || 0)}:${Number(stat?.size || 0)}`;
|
|
99
|
+
if (ignoreRulesCache.has(cacheKey)) return ignoreRulesCache.get(cacheKey);
|
|
100
|
+
|
|
101
|
+
for (const key of ignoreRulesCache.keys()) {
|
|
102
|
+
if (String(key).startsWith(`${filePath}:`) && key !== cacheKey) {
|
|
103
|
+
ignoreRulesCache.delete(key);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
95
107
|
try {
|
|
96
|
-
|
|
97
|
-
|
|
108
|
+
if (!stat?.isFile()) {
|
|
109
|
+
ignoreRulesCache.set(cacheKey, []);
|
|
110
|
+
return [];
|
|
111
|
+
}
|
|
112
|
+
const raw = await fs.readFile(filePath, 'utf8');
|
|
113
|
+
const rules = raw
|
|
98
114
|
.split(/\r?\n/)
|
|
99
115
|
.map((line) => line.trim())
|
|
100
116
|
.filter((line) => line && !line.startsWith('#'))
|
|
@@ -114,7 +130,10 @@ async function readIgnoreFileRules(cwd, fileName) {
|
|
|
114
130
|
};
|
|
115
131
|
})
|
|
116
132
|
.filter((rule) => rule.normalized);
|
|
133
|
+
ignoreRulesCache.set(cacheKey, rules);
|
|
134
|
+
return rules;
|
|
117
135
|
} catch {
|
|
136
|
+
ignoreRulesCache.set(cacheKey, []);
|
|
118
137
|
return [];
|
|
119
138
|
}
|
|
120
139
|
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
4
|
+
const DEFAULT_MAX_CHARS = 12000;
|
|
5
|
+
const CANDIDATE_FILES = [
|
|
6
|
+
'AGENTS.md',
|
|
7
|
+
path.join('.agents', 'AGENTS.md'),
|
|
8
|
+
path.join('.agents', 'agents.md'),
|
|
9
|
+
path.join('.codemini', 'AGENTS.md'),
|
|
10
|
+
'CLAUDE.md'
|
|
11
|
+
];
|
|
12
|
+
|
|
13
|
+
function trimProjectInstructions(value, maxChars = DEFAULT_MAX_CHARS) {
|
|
14
|
+
const text = String(value || '').trim();
|
|
15
|
+
if (!text) return '';
|
|
16
|
+
const limit = Math.max(1000, Number(maxChars) || DEFAULT_MAX_CHARS);
|
|
17
|
+
if (text.length <= limit) return text;
|
|
18
|
+
return `${text.slice(0, limit - 120).trimEnd()}\n\n[Project instructions truncated: keep AGENTS.md concise or move details into linked docs.]`;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async function readFirstExistingFile(cwd, candidates = CANDIDATE_FILES) {
|
|
22
|
+
let current = path.resolve(cwd);
|
|
23
|
+
while (true) {
|
|
24
|
+
for (const candidate of candidates) {
|
|
25
|
+
const absolutePath = path.resolve(current, candidate);
|
|
26
|
+
let stat;
|
|
27
|
+
try {
|
|
28
|
+
stat = await fs.stat(absolutePath);
|
|
29
|
+
} catch {
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
if (!stat?.isFile()) continue;
|
|
33
|
+
const content = await fs.readFile(absolutePath, 'utf8');
|
|
34
|
+
const relativePath = path.relative(cwd, absolutePath) || candidate;
|
|
35
|
+
return {
|
|
36
|
+
path: absolutePath,
|
|
37
|
+
relativePath: relativePath.split(path.sep).join('/'),
|
|
38
|
+
content
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
const parent = path.dirname(current);
|
|
42
|
+
if (parent === current) break;
|
|
43
|
+
current = parent;
|
|
44
|
+
}
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export async function loadProjectInstructions({
|
|
49
|
+
cwd = process.cwd(),
|
|
50
|
+
config = {},
|
|
51
|
+
maxChars = config?.context?.project_instructions_max_chars
|
|
52
|
+
} = {}) {
|
|
53
|
+
const enabled = config?.context?.project_instructions_enabled !== false;
|
|
54
|
+
if (!enabled) return '';
|
|
55
|
+
|
|
56
|
+
const found = await readFirstExistingFile(cwd);
|
|
57
|
+
if (!found) return '';
|
|
58
|
+
|
|
59
|
+
const body = trimProjectInstructions(found.content, maxChars);
|
|
60
|
+
if (!body) return '';
|
|
61
|
+
|
|
62
|
+
return [
|
|
63
|
+
'Project Instructions:',
|
|
64
|
+
`Source: ${found.relativePath}`,
|
|
65
|
+
body
|
|
66
|
+
].join('\n');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function buildDefaultAgentsMd() {
|
|
70
|
+
return `# AGENTS.md
|
|
71
|
+
|
|
72
|
+
This file gives coding agents stable project instructions. Keep it short and use it as a map, not as full documentation.
|
|
73
|
+
|
|
74
|
+
## Project
|
|
75
|
+
|
|
76
|
+
- Describe what this repository is and the main runtime or product surface.
|
|
77
|
+
- Note required runtime versions and package managers.
|
|
78
|
+
|
|
79
|
+
## Commands
|
|
80
|
+
|
|
81
|
+
- Install: \`npm install\`
|
|
82
|
+
- Test: \`npm test\`
|
|
83
|
+
- Build: add the project build command here.
|
|
84
|
+
|
|
85
|
+
## Task Routing
|
|
86
|
+
|
|
87
|
+
- CLI or command behavior: list the entry files here.
|
|
88
|
+
- Runtime behavior: list the core runtime files here.
|
|
89
|
+
- Web UI behavior: list the server, state, and component roots here.
|
|
90
|
+
- Tests: list focused test files for common changes.
|
|
91
|
+
|
|
92
|
+
## Rules
|
|
93
|
+
|
|
94
|
+
- Use project/file indexes for orientation, then inspect real source files before editing.
|
|
95
|
+
- Keep generated output and build artifacts out of manual edits.
|
|
96
|
+
- Put reusable workflows in skills; put always-needed project facts and routing rules here.
|
|
97
|
+
`;
|
|
98
|
+
}
|
package/src/core/shell.js
CHANGED
|
@@ -23,6 +23,74 @@ const READY_OUTPUT_PATTERNS = [
|
|
|
23
23
|
/\bhttp:\/\/127\.0\.0\.1\b/i,
|
|
24
24
|
/\bhttp:\/\/localhost\b/i
|
|
25
25
|
];
|
|
26
|
+
const INSTALL_COMMAND_PATTERNS = [
|
|
27
|
+
/\b(?:npm|pnpm|yarn|bun)\s+install\b/i,
|
|
28
|
+
/\b(?:npm|pnpm|yarn|bun)\s+(?:ci|i|add)\b/i,
|
|
29
|
+
/\buv\s+pip\s+install\b/i,
|
|
30
|
+
/\bpip\s+install\b/i,
|
|
31
|
+
/\bcargo\s+install\b/i,
|
|
32
|
+
/\bbundle\s+install\b/i,
|
|
33
|
+
/\bcomposer\s+install\b/i
|
|
34
|
+
];
|
|
35
|
+
const BUILD_COMMAND_RE = /\b(?:build|compile|bundle|pack|transpile)\b/i;
|
|
36
|
+
const TEST_COMMAND_PATTERNS = [
|
|
37
|
+
/\b(?:npm|pnpm|yarn|bun)\s+(?:run\s+)?(?:test|lint|check|typecheck)\b/i,
|
|
38
|
+
/\b(?:jest|vitest|mocha|ava|pytest|go\s+test|cargo\s+test|dotnet\s+test)\b/i
|
|
39
|
+
];
|
|
40
|
+
const FRONTEND_SERVICE_PATTERNS = [
|
|
41
|
+
/\bvite\b/i,
|
|
42
|
+
/\bnext\s+dev\b/i,
|
|
43
|
+
/\bnuxt\s+dev\b/i,
|
|
44
|
+
/\bastro\s+dev\b/i,
|
|
45
|
+
/\bremix\s+dev\b/i,
|
|
46
|
+
/\bsvelte-kit\s+dev\b/i,
|
|
47
|
+
/\bwebpack\s+serve\b/i,
|
|
48
|
+
/\bvue-cli-service\s+serve\b/i,
|
|
49
|
+
/\breact-scripts\s+start\b/i,
|
|
50
|
+
/\bstorybook\b/i,
|
|
51
|
+
/\b(?:npm|pnpm|yarn|bun)\s+(?:run\s+)?(?:dev|start|serve|preview)\b.*\b(?:client|frontend|front-end|web|ui)\b/i,
|
|
52
|
+
/\b(?:client|frontend|front-end|web|ui)\b.*\b(?:dev|start|serve|preview)\b/i
|
|
53
|
+
];
|
|
54
|
+
const BACKEND_SERVICE_PATTERNS = [
|
|
55
|
+
/\bpython\s+-m\s+http\.server\b/i,
|
|
56
|
+
/\buvicorn\b/i,
|
|
57
|
+
/\bgunicorn\b/i,
|
|
58
|
+
/\bflask\s+run\b/i,
|
|
59
|
+
/\bdjango\s+runserver\b/i,
|
|
60
|
+
/\brails\s+(?:s|server)\b/i,
|
|
61
|
+
/\bmvn(?:w)?\s+spring-boot:run\b/i,
|
|
62
|
+
/\bgradle(?:w)?\s+bootRun\b/i,
|
|
63
|
+
/\bgradle(?:w)?\s+run\b/i,
|
|
64
|
+
/\bjava\b.*\bserver\b/i,
|
|
65
|
+
/\bdotnet\s+run\b/i,
|
|
66
|
+
/\bgo\s+run\b.*\b(server|cmd\/server|main\.go)\b/i,
|
|
67
|
+
/\bnest\s+start\b/i,
|
|
68
|
+
/\bnodemon\b/i,
|
|
69
|
+
/\bts-node-dev\b/i,
|
|
70
|
+
/\bair\b/i,
|
|
71
|
+
/\bphp\s+artisan\s+serve\b/i,
|
|
72
|
+
/\bsymfony\s+server:start\b/i,
|
|
73
|
+
/\b(?:npm|pnpm|yarn|bun)\s+(?:run\s+)?(?:dev|start|serve|preview)\b.*\b(?:server|api|backend)\b/i,
|
|
74
|
+
/\b(?:server|api|backend)\b.*\b(?:dev|start|serve|preview)\b/i
|
|
75
|
+
];
|
|
76
|
+
const DATABASE_SERVICE_PATTERNS = [
|
|
77
|
+
/\bpostgres(?:ql)?\b/i,
|
|
78
|
+
/\bmysql\b/i,
|
|
79
|
+
/\bmariadb\b/i,
|
|
80
|
+
/\bmongod\b/i,
|
|
81
|
+
/\bredis-server\b/i,
|
|
82
|
+
/\b(?:docker|docker-compose|docker compose)\s+.*\b(?:db|database|postgres|mysql|mongo|redis)\b/i,
|
|
83
|
+
/\b(?:db|database|postgres|mysql|mongo|redis)\b.*\b(?:start|up|serve|run)\b/i
|
|
84
|
+
];
|
|
85
|
+
const DOCKER_SERVICE_PATTERNS = [
|
|
86
|
+
/\bdocker\s+compose\s+up\b/i,
|
|
87
|
+
/\bdocker-compose\s+up\b/i,
|
|
88
|
+
/\bdocker\s+run\b/i,
|
|
89
|
+
/\bdocker\s+start\b/i
|
|
90
|
+
];
|
|
91
|
+
const PACKAGE_SERVICE_RE = /\b(?:npm|pnpm|yarn|bun)\s+(?:run\s+)?(?:dev|start|serve|preview|watch)\b/i;
|
|
92
|
+
const VITE_OR_SERVE_RE = /\b(?:vite|serve)\b/i;
|
|
93
|
+
const SERVICE_HINT_RE = /\b(?:watch|serve|server|dev|preview)\b/i;
|
|
26
94
|
const AUTO_STOP_GRACE_MS = 150;
|
|
27
95
|
const LONG_RUNNING_STARTUP_WINDOW_MS = 1500;
|
|
28
96
|
|
|
@@ -41,105 +109,43 @@ export function classifyCommandIntent(command) {
|
|
|
41
109
|
return { kind: 'generic', longRunning: false };
|
|
42
110
|
}
|
|
43
111
|
|
|
44
|
-
if (
|
|
45
|
-
/\b(?:npm|pnpm|yarn|bun)\s+install\b/i.test(value) ||
|
|
46
|
-
/\b(?:npm|pnpm|yarn|bun)\s+(?:ci|i|add)\b/i.test(value) ||
|
|
47
|
-
/\buv\s+pip\s+install\b/i.test(value) ||
|
|
48
|
-
/\bpip\s+install\b/i.test(value) ||
|
|
49
|
-
/\bcargo\s+install\b/i.test(value) ||
|
|
50
|
-
/\bbundle\s+install\b/i.test(value) ||
|
|
51
|
-
/\bcomposer\s+install\b/i.test(value)
|
|
52
|
-
) {
|
|
112
|
+
if (matchesAny(value, INSTALL_COMMAND_PATTERNS)) {
|
|
53
113
|
return { kind: 'install', longRunning: false };
|
|
54
114
|
}
|
|
55
115
|
|
|
56
|
-
if (
|
|
116
|
+
if (BUILD_COMMAND_RE.test(value)) {
|
|
57
117
|
return { kind: 'build', longRunning: false };
|
|
58
118
|
}
|
|
59
119
|
|
|
60
|
-
if (
|
|
61
|
-
/\b(?:npm|pnpm|yarn|bun)\s+(?:run\s+)?(?:test|lint|check|typecheck)\b/i.test(value) ||
|
|
62
|
-
/\b(?:jest|vitest|mocha|ava|pytest|go\s+test|cargo\s+test|dotnet\s+test)\b/i.test(value)
|
|
63
|
-
) {
|
|
120
|
+
if (matchesAny(value, TEST_COMMAND_PATTERNS)) {
|
|
64
121
|
return { kind: 'test', longRunning: false };
|
|
65
122
|
}
|
|
66
123
|
|
|
67
|
-
|
|
68
|
-
/\bvite\b/i,
|
|
69
|
-
/\bnext\s+dev\b/i,
|
|
70
|
-
/\bnuxt\s+dev\b/i,
|
|
71
|
-
/\bastro\s+dev\b/i,
|
|
72
|
-
/\bremix\s+dev\b/i,
|
|
73
|
-
/\bsvelte-kit\s+dev\b/i,
|
|
74
|
-
/\bwebpack\s+serve\b/i,
|
|
75
|
-
/\bvue-cli-service\s+serve\b/i,
|
|
76
|
-
/\breact-scripts\s+start\b/i,
|
|
77
|
-
/\bstorybook\b/i,
|
|
78
|
-
/\b(?:npm|pnpm|yarn|bun)\s+(?:run\s+)?(?:dev|start|serve|preview)\b.*\b(?:client|frontend|front-end|web|ui)\b/i,
|
|
79
|
-
/\b(?:client|frontend|front-end|web|ui)\b.*\b(?:dev|start|serve|preview)\b/i
|
|
80
|
-
];
|
|
81
|
-
if (matchesAny(value, frontendServicePatterns)) {
|
|
124
|
+
if (matchesAny(value, FRONTEND_SERVICE_PATTERNS)) {
|
|
82
125
|
return { kind: 'frontend-service', longRunning: true };
|
|
83
126
|
}
|
|
84
127
|
|
|
85
|
-
|
|
86
|
-
/\bpython\s+-m\s+http\.server\b/i,
|
|
87
|
-
/\buvicorn\b/i,
|
|
88
|
-
/\bgunicorn\b/i,
|
|
89
|
-
/\bflask\s+run\b/i,
|
|
90
|
-
/\bdjango\s+runserver\b/i,
|
|
91
|
-
/\brails\s+(?:s|server)\b/i,
|
|
92
|
-
/\bmvn(?:w)?\s+spring-boot:run\b/i,
|
|
93
|
-
/\bgradle(?:w)?\s+bootRun\b/i,
|
|
94
|
-
/\bgradle(?:w)?\s+run\b/i,
|
|
95
|
-
/\bjava\b.*\bserver\b/i,
|
|
96
|
-
/\bdotnet\s+run\b/i,
|
|
97
|
-
/\bgo\s+run\b.*\b(server|cmd\/server|main\.go)\b/i,
|
|
98
|
-
/\bnest\s+start\b/i,
|
|
99
|
-
/\bnodemon\b/i,
|
|
100
|
-
/\bts-node-dev\b/i,
|
|
101
|
-
/\bair\b/i,
|
|
102
|
-
/\bphp\s+artisan\s+serve\b/i,
|
|
103
|
-
/\bsymfony\s+server:start\b/i,
|
|
104
|
-
/\b(?:npm|pnpm|yarn|bun)\s+(?:run\s+)?(?:dev|start|serve|preview)\b.*\b(?:server|api|backend)\b/i,
|
|
105
|
-
/\b(?:server|api|backend)\b.*\b(?:dev|start|serve|preview)\b/i
|
|
106
|
-
];
|
|
107
|
-
if (matchesAny(value, backendServicePatterns)) {
|
|
128
|
+
if (matchesAny(value, BACKEND_SERVICE_PATTERNS)) {
|
|
108
129
|
return { kind: 'backend-service', longRunning: true };
|
|
109
130
|
}
|
|
110
131
|
|
|
111
|
-
|
|
112
|
-
/\bpostgres(?:ql)?\b/i,
|
|
113
|
-
/\bmysql\b/i,
|
|
114
|
-
/\bmariadb\b/i,
|
|
115
|
-
/\bmongod\b/i,
|
|
116
|
-
/\bredis-server\b/i,
|
|
117
|
-
/\b(?:docker|docker-compose|docker compose)\s+.*\b(?:db|database|postgres|mysql|mongo|redis)\b/i,
|
|
118
|
-
/\b(?:db|database|postgres|mysql|mongo|redis)\b.*\b(?:start|up|serve|run)\b/i
|
|
119
|
-
];
|
|
120
|
-
if (matchesAny(value, databaseServicePatterns)) {
|
|
132
|
+
if (matchesAny(value, DATABASE_SERVICE_PATTERNS)) {
|
|
121
133
|
return { kind: 'database-service', longRunning: true };
|
|
122
134
|
}
|
|
123
135
|
|
|
124
|
-
|
|
125
|
-
/\bdocker\s+compose\s+up\b/i,
|
|
126
|
-
/\bdocker-compose\s+up\b/i,
|
|
127
|
-
/\bdocker\s+run\b/i,
|
|
128
|
-
/\bdocker\s+start\b/i
|
|
129
|
-
];
|
|
130
|
-
if (matchesAny(value, dockerServicePatterns)) {
|
|
136
|
+
if (matchesAny(value, DOCKER_SERVICE_PATTERNS)) {
|
|
131
137
|
return { kind: 'docker-service', longRunning: true };
|
|
132
138
|
}
|
|
133
139
|
|
|
134
140
|
if (
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
141
|
+
PACKAGE_SERVICE_RE.test(value) ||
|
|
142
|
+
VITE_OR_SERVE_RE.test(value) ||
|
|
143
|
+
SERVICE_HINT_RE.test(value)
|
|
138
144
|
) {
|
|
139
145
|
return { kind: 'service', longRunning: true };
|
|
140
146
|
}
|
|
141
147
|
|
|
142
|
-
if (
|
|
148
|
+
if (SERVICE_HINT_RE.test(value)) {
|
|
143
149
|
return { kind: 'service', longRunning: true };
|
|
144
150
|
}
|
|
145
151
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { buildMemorySnapshot } from './memory-prompt.js';
|
|
2
|
+
import { loadProjectInstructions } from './project-instructions.js';
|
|
2
3
|
import { buildSystemPromptWithReplyLanguage, stripReplyLanguageDirective } from './reply-language.js';
|
|
3
4
|
import { buildSystemPromptWithSoul } from './soul.js';
|
|
4
5
|
|
|
@@ -17,6 +18,8 @@ export async function composeSystemPrompt({
|
|
|
17
18
|
skillsPrompt = '',
|
|
18
19
|
memorySnapshot,
|
|
19
20
|
includeMemory = true,
|
|
21
|
+
projectInstructionsSnippet,
|
|
22
|
+
includeProjectInstructions = true,
|
|
20
23
|
projectContextSnippet = '',
|
|
21
24
|
projectContextGuidance = '',
|
|
22
25
|
extraPrompts = [],
|
|
@@ -30,8 +33,15 @@ export async function composeSystemPrompt({
|
|
|
30
33
|
: includeMemory
|
|
31
34
|
? await buildMemorySnapshot({ config, workspaceRoot }).catch(() => '')
|
|
32
35
|
: '';
|
|
36
|
+
const projectInstructionsPrompt = projectInstructionsSnippet !== undefined
|
|
37
|
+
? projectInstructionsSnippet
|
|
38
|
+
: includeProjectInstructions
|
|
39
|
+
? await loadProjectInstructions({ cwd: workspaceRoot, config }).catch(() => '')
|
|
40
|
+
: '';
|
|
41
|
+
const hasProjectInstructions = /\bProject Instructions:\s*\n/i.test(shellAndSoul);
|
|
33
42
|
const body = joinPromptParts([
|
|
34
43
|
shellAndSoul,
|
|
44
|
+
hasProjectInstructions ? '' : projectInstructionsPrompt,
|
|
35
45
|
skillsPrompt,
|
|
36
46
|
memoryPrompt,
|
|
37
47
|
projectContextSnippet,
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{i as e}from"./index-C4tKT3v4.js";export{e as Mermaid};
|