codemini-cli 0.5.8 → 0.5.9
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 +30 -0
- package/codemini-web/dist/assets/{highlighted-body-OFNGDK62-CCcxtQK_.js → highlighted-body-OFNGDK62-HgeDi9HJ.js} +1 -1
- package/codemini-web/dist/assets/index-BSdIdn3L.css +2 -0
- package/codemini-web/dist/assets/{index-Cy4HN-FS.js → index-C4tKT3v4.js} +95 -93
- package/codemini-web/dist/assets/mermaid-GHXKKRXX-CDgkkDBg.js +1 -0
- package/codemini-web/dist/index.html +2 -2
- package/codemini-web/lib/runtime-bridge.js +547 -546
- package/codemini-web/server.js +318 -233
- package/package.json +67 -67
- package/skills/codemini.skills.json +40 -0
- package/src/commands/skill.js +16 -5
- package/src/core/chat-runtime.js +72 -13
- package/src/core/command-loader.js +120 -25
- package/src/core/command-policy.js +34 -10
- package/src/core/config-store.js +11 -0
- package/src/core/tools.js +114 -65
- package/codemini-web/dist/assets/index-CMISAOFr.css +0 -2
- package/codemini-web/dist/assets/mermaid-GHXKKRXX-BWFzYc7A.js +0 -1
package/package.json
CHANGED
|
@@ -1,67 +1,67 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "codemini-cli",
|
|
3
|
-
"version": "0.5.
|
|
4
|
-
"description": "Coding CLI optimized for small-model workflows and Windows PowerShell",
|
|
5
|
-
"keywords": [
|
|
6
|
-
"cli",
|
|
7
|
-
"ai",
|
|
8
|
-
"coding-assistant",
|
|
9
|
-
"developer-tools",
|
|
10
|
-
"terminal",
|
|
11
|
-
"windows",
|
|
12
|
-
"powershell"
|
|
13
|
-
],
|
|
14
|
-
"type": "module",
|
|
15
|
-
"homepage": "https://github.com/havingautism/Codemini-CLI#readme",
|
|
16
|
-
"bugs": {
|
|
17
|
-
"url": "https://github.com/havingautism/Codemini-CLI/issues"
|
|
18
|
-
},
|
|
19
|
-
"repository": {
|
|
20
|
-
"type": "git",
|
|
21
|
-
"url": "git+https://github.com/havingautism/Codemini-CLI.git"
|
|
22
|
-
},
|
|
23
|
-
"bin": {
|
|
24
|
-
"codemini": "bin/coder.js",
|
|
25
|
-
"coder": "bin/coder.js"
|
|
26
|
-
},
|
|
27
|
-
"scripts": {
|
|
28
|
-
"start": "node bin/coder.js",
|
|
29
|
-
"test": "node --test tests/*.test.js",
|
|
30
|
-
"build:web": "npm install --prefix codemini-web && npm run build --prefix codemini-web",
|
|
31
|
-
"prepack": "npm run build:web",
|
|
32
|
-
"pack:offline": "npm pack",
|
|
33
|
-
"bump:patch": "npm version patch --no-git-tag-version",
|
|
34
|
-
"bump:minor": "npm version minor --no-git-tag-version",
|
|
35
|
-
"bump:major": "npm version major --no-git-tag-version"
|
|
36
|
-
},
|
|
37
|
-
"files": [
|
|
38
|
-
"bin",
|
|
39
|
-
"src",
|
|
40
|
-
"codemini-web/server.js",
|
|
41
|
-
"codemini-web/lib",
|
|
42
|
-
"codemini-web/dist",
|
|
43
|
-
"codemini-web/codemini_logo.png",
|
|
44
|
-
"souls",
|
|
45
|
-
"templates",
|
|
46
|
-
"skills",
|
|
47
|
-
"README.md",
|
|
48
|
-
"OPERATIONS.md",
|
|
49
|
-
"deployment.md"
|
|
50
|
-
],
|
|
51
|
-
"engines": {
|
|
52
|
-
"node": ">=22"
|
|
53
|
-
},
|
|
54
|
-
"publishConfig": {
|
|
55
|
-
"access": "public"
|
|
56
|
-
},
|
|
57
|
-
"dependencies": {
|
|
58
|
-
"@cursorless/tree-sitter-wasms": "^0.8.1",
|
|
59
|
-
"cheerio": "^1.1.2",
|
|
60
|
-
"cli-truncate": "^6.0.0",
|
|
61
|
-
"ink": "^7.0.0",
|
|
62
|
-
"react": "^19.2.5",
|
|
63
|
-
"strip-ansi": "^7.2.0",
|
|
64
|
-
"web-tree-sitter": "^0.26.8"
|
|
65
|
-
},
|
|
66
|
-
"license": "MIT"
|
|
67
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "codemini-cli",
|
|
3
|
+
"version": "0.5.9",
|
|
4
|
+
"description": "Coding CLI optimized for small-model workflows and Windows PowerShell",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"cli",
|
|
7
|
+
"ai",
|
|
8
|
+
"coding-assistant",
|
|
9
|
+
"developer-tools",
|
|
10
|
+
"terminal",
|
|
11
|
+
"windows",
|
|
12
|
+
"powershell"
|
|
13
|
+
],
|
|
14
|
+
"type": "module",
|
|
15
|
+
"homepage": "https://github.com/havingautism/Codemini-CLI#readme",
|
|
16
|
+
"bugs": {
|
|
17
|
+
"url": "https://github.com/havingautism/Codemini-CLI/issues"
|
|
18
|
+
},
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "git+https://github.com/havingautism/Codemini-CLI.git"
|
|
22
|
+
},
|
|
23
|
+
"bin": {
|
|
24
|
+
"codemini": "bin/coder.js",
|
|
25
|
+
"coder": "bin/coder.js"
|
|
26
|
+
},
|
|
27
|
+
"scripts": {
|
|
28
|
+
"start": "node bin/coder.js",
|
|
29
|
+
"test": "node --test tests/*.test.js",
|
|
30
|
+
"build:web": "npm install --prefix codemini-web && npm run build --prefix codemini-web",
|
|
31
|
+
"prepack": "npm run build:web",
|
|
32
|
+
"pack:offline": "npm pack",
|
|
33
|
+
"bump:patch": "npm version patch --no-git-tag-version",
|
|
34
|
+
"bump:minor": "npm version minor --no-git-tag-version",
|
|
35
|
+
"bump:major": "npm version major --no-git-tag-version"
|
|
36
|
+
},
|
|
37
|
+
"files": [
|
|
38
|
+
"bin",
|
|
39
|
+
"src",
|
|
40
|
+
"codemini-web/server.js",
|
|
41
|
+
"codemini-web/lib",
|
|
42
|
+
"codemini-web/dist",
|
|
43
|
+
"codemini-web/codemini_logo.png",
|
|
44
|
+
"souls",
|
|
45
|
+
"templates",
|
|
46
|
+
"skills",
|
|
47
|
+
"README.md",
|
|
48
|
+
"OPERATIONS.md",
|
|
49
|
+
"deployment.md"
|
|
50
|
+
],
|
|
51
|
+
"engines": {
|
|
52
|
+
"node": ">=22"
|
|
53
|
+
},
|
|
54
|
+
"publishConfig": {
|
|
55
|
+
"access": "public"
|
|
56
|
+
},
|
|
57
|
+
"dependencies": {
|
|
58
|
+
"@cursorless/tree-sitter-wasms": "^0.8.1",
|
|
59
|
+
"cheerio": "^1.1.2",
|
|
60
|
+
"cli-truncate": "^6.0.0",
|
|
61
|
+
"ink": "^7.0.0",
|
|
62
|
+
"react": "^19.2.5",
|
|
63
|
+
"strip-ansi": "^7.2.0",
|
|
64
|
+
"web-tree-sitter": "^0.26.8"
|
|
65
|
+
},
|
|
66
|
+
"license": "MIT"
|
|
67
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 1,
|
|
3
|
+
"skills": {
|
|
4
|
+
"superpowers-lite": {
|
|
5
|
+
"description": "Default concise workflow for coding tasks; keep context tight, choose the right route, and verify before claiming success.",
|
|
6
|
+
"mode": "always",
|
|
7
|
+
"triggers": [],
|
|
8
|
+
"enabled": true,
|
|
9
|
+
"priority": 100
|
|
10
|
+
},
|
|
11
|
+
"brainstorm": {
|
|
12
|
+
"description": "Use when a feature or behavior request has multiple reasonable approaches and the missing piece is user preference, tradeoff choice, or key constraint.",
|
|
13
|
+
"mode": "agent_requested",
|
|
14
|
+
"triggers": [],
|
|
15
|
+
"enabled": true,
|
|
16
|
+
"priority": 70
|
|
17
|
+
},
|
|
18
|
+
"writing-plans": {
|
|
19
|
+
"description": "Use when you have a clear goal or approved design for a non-trivial task, before touching code.",
|
|
20
|
+
"mode": "agent_requested",
|
|
21
|
+
"triggers": [],
|
|
22
|
+
"enabled": true,
|
|
23
|
+
"priority": 75
|
|
24
|
+
},
|
|
25
|
+
"grill-me": {
|
|
26
|
+
"description": "Use when the user explicitly asks to pressure-test a plan, architecture choice, PR, launch, or product idea.",
|
|
27
|
+
"mode": "agent_requested",
|
|
28
|
+
"triggers": [],
|
|
29
|
+
"enabled": true,
|
|
30
|
+
"priority": 65
|
|
31
|
+
},
|
|
32
|
+
"project-requirements": {
|
|
33
|
+
"description": "Create a project requirements report with repository inspection and structured output artifacts.",
|
|
34
|
+
"mode": "manual",
|
|
35
|
+
"triggers": [],
|
|
36
|
+
"enabled": true,
|
|
37
|
+
"priority": 50
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
package/src/commands/skill.js
CHANGED
|
@@ -77,9 +77,15 @@ export async function listSkillEntries({ scope = 'all', cwd = process.cwd() } =
|
|
|
77
77
|
name: command.name,
|
|
78
78
|
version: command.metadata?.version || '0.0.0',
|
|
79
79
|
description: command.metadata?.description || '',
|
|
80
|
+
mode: command.metadata?.mode || '',
|
|
81
|
+
triggers: Array.isArray(command.metadata?.triggers) ? command.metadata.triggers : [],
|
|
80
82
|
scope: itemScope,
|
|
81
83
|
path: command.path,
|
|
82
|
-
enabled:
|
|
84
|
+
enabled: command.metadata?.enabled === false
|
|
85
|
+
? false
|
|
86
|
+
: itemScope === 'builtin'
|
|
87
|
+
? true
|
|
88
|
+
: config.skills?.enabled?.[command.name] !== false
|
|
83
89
|
});
|
|
84
90
|
}
|
|
85
91
|
return entries.sort((a, b) => `${a.scope}:${a.name}`.localeCompare(`${b.scope}:${b.name}`));
|
|
@@ -93,14 +99,19 @@ async function readSkillMeta(name, { scope = 'all', cwd = process.cwd() } = {})
|
|
|
93
99
|
}
|
|
94
100
|
const dir = path.dirname(found.path);
|
|
95
101
|
const manifestPath = path.join(dir, 'manifest.json');
|
|
102
|
+
const catalogPath = path.join(path.dirname(dir), 'codemini.skills.json');
|
|
96
103
|
let manifest = null;
|
|
97
104
|
try {
|
|
98
|
-
|
|
105
|
+
const catalog = JSON.parse(await fs.readFile(catalogPath, 'utf8'));
|
|
106
|
+
manifest = catalog?.skills?.[found.name] || null;
|
|
99
107
|
} catch {
|
|
100
|
-
|
|
108
|
+
try {
|
|
109
|
+
manifest = JSON.parse(await fs.readFile(manifestPath, 'utf8'));
|
|
110
|
+
} catch {
|
|
111
|
+
manifest = null;
|
|
112
|
+
}
|
|
101
113
|
}
|
|
102
|
-
const
|
|
103
|
-
const skillPath = found.path || path.join(dir, entryFile);
|
|
114
|
+
const skillPath = found.path || path.join(dir, 'SKILL.md');
|
|
104
115
|
try {
|
|
105
116
|
const content = await fs.readFile(skillPath, 'utf8');
|
|
106
117
|
const firstLines = content.split('\n').slice(0, 20).join('\n');
|
package/src/core/chat-runtime.js
CHANGED
|
@@ -136,6 +136,7 @@ function getCompletionCopy(language = 'zh') {
|
|
|
136
136
|
'soul.preset': 'soul 预设',
|
|
137
137
|
'soul.custom_path': '自定义 soul 路径',
|
|
138
138
|
'policy.safe_mode': '安全模式开关',
|
|
139
|
+
'policy.allowed_paths': '安全模式目录白名单',
|
|
139
140
|
'policy.allow_dangerous_commands': '危险命令开关'
|
|
140
141
|
},
|
|
141
142
|
optionHints: {
|
|
@@ -145,6 +146,7 @@ function getCompletionCopy(language = 'zh') {
|
|
|
145
146
|
'execution.mode': '可选:auto | normal | plan',
|
|
146
147
|
'shell.default': '常用:bash | powershell',
|
|
147
148
|
'policy.safe_mode': '可选:true | false',
|
|
149
|
+
'policy.allowed_paths': 'JSON 数组,例如 ["D:\\\\shared"]',
|
|
148
150
|
'policy.allow_dangerous_commands': '可选:true | false',
|
|
149
151
|
'context.prompt_budget_audit': '可选:true | false'
|
|
150
152
|
},
|
|
@@ -243,6 +245,7 @@ function getCompletionCopy(language = 'zh') {
|
|
|
243
245
|
'soul.preset': 'soul preset',
|
|
244
246
|
'soul.custom_path': 'custom soul prompt path',
|
|
245
247
|
'policy.safe_mode': 'safe mode switch',
|
|
248
|
+
'policy.allowed_paths': 'safe-mode allowed path roots',
|
|
246
249
|
'policy.allow_dangerous_commands': 'dangerous command allowance'
|
|
247
250
|
},
|
|
248
251
|
optionHints: {
|
|
@@ -252,6 +255,7 @@ function getCompletionCopy(language = 'zh') {
|
|
|
252
255
|
'execution.mode': 'options: auto | normal | plan',
|
|
253
256
|
'shell.default': 'common: bash | powershell',
|
|
254
257
|
'policy.safe_mode': 'options: true | false',
|
|
258
|
+
'policy.allowed_paths': 'JSON array, for example ["D:\\\\shared"]',
|
|
255
259
|
'policy.allow_dangerous_commands': 'options: true | false',
|
|
256
260
|
'context.prompt_budget_audit': 'options: true | false'
|
|
257
261
|
},
|
|
@@ -1265,6 +1269,7 @@ function isBundledSkillCommand(command) {
|
|
|
1265
1269
|
}
|
|
1266
1270
|
|
|
1267
1271
|
function isSkillEnabled(config, name, command = null) {
|
|
1272
|
+
if (command?.metadata?.enabled === false) return false;
|
|
1268
1273
|
if (isBundledSkillCommand(command)) return true;
|
|
1269
1274
|
return config.skills?.enabled?.[name] !== false;
|
|
1270
1275
|
}
|
|
@@ -2301,6 +2306,9 @@ function buildRuntimeStateSnapshot({ currentSession, config, model, executionMod
|
|
|
2301
2306
|
maxContextTokens,
|
|
2302
2307
|
pendingPlanApproval: planState?.status === 'pending_approval'
|
|
2303
2308
|
? { goal: planState.goal, summary: planState.finalSummary || planState.summary, filePath: planState.filePath, steps: planState.steps || [] }
|
|
2309
|
+
: null,
|
|
2310
|
+
pendingReflectSkill: planState?.status === 'pending_reflect_skill'
|
|
2311
|
+
? buildPendingReflectSkillSnapshot(planState)
|
|
2304
2312
|
: null
|
|
2305
2313
|
};
|
|
2306
2314
|
Object.defineProperties(snapshot, {
|
|
@@ -2314,11 +2322,6 @@ function buildRuntimeStateSnapshot({ currentSession, config, model, executionMod
|
|
|
2314
2322
|
enumerable: false,
|
|
2315
2323
|
writable: false
|
|
2316
2324
|
},
|
|
2317
|
-
pendingReflectSkill: {
|
|
2318
|
-
value: currentSession?.planState?.status === 'pending_reflect_skill',
|
|
2319
|
-
enumerable: false,
|
|
2320
|
-
writable: false
|
|
2321
|
-
},
|
|
2322
2325
|
replyLanguage: {
|
|
2323
2326
|
value: getReplyLanguage(config),
|
|
2324
2327
|
enumerable: false,
|
|
@@ -2329,7 +2332,6 @@ function buildRuntimeStateSnapshot({ currentSession, config, model, executionMod
|
|
|
2329
2332
|
...snapshot,
|
|
2330
2333
|
currentContextTokens,
|
|
2331
2334
|
contextUsagePct,
|
|
2332
|
-
pendingReflectSkill: currentSession?.planState?.status === 'pending_reflect_skill',
|
|
2333
2335
|
replyLanguage: getReplyLanguage(config)
|
|
2334
2336
|
}),
|
|
2335
2337
|
enumerable: false,
|
|
@@ -2513,6 +2515,21 @@ function buildPendingReflectSkillMessage(reflectState) {
|
|
|
2513
2515
|
return lines.join('\n');
|
|
2514
2516
|
}
|
|
2515
2517
|
|
|
2518
|
+
function buildPendingReflectSkillSnapshot(reflectState) {
|
|
2519
|
+
const candidates = Array.isArray(reflectState?.candidates) ? reflectState.candidates : [];
|
|
2520
|
+
const candidate = candidates[0] || null;
|
|
2521
|
+
if (!candidate) return null;
|
|
2522
|
+
return {
|
|
2523
|
+
scope: reflectState?.targetScope || 'project',
|
|
2524
|
+
request: reflectState?.request || '',
|
|
2525
|
+
name: candidate.name || '',
|
|
2526
|
+
description: candidate.description || '',
|
|
2527
|
+
confidence: Number(candidate.confidence ?? 0.75),
|
|
2528
|
+
targetPath: candidate.targetPath || '',
|
|
2529
|
+
content: candidate.content || ''
|
|
2530
|
+
};
|
|
2531
|
+
}
|
|
2532
|
+
|
|
2516
2533
|
function buildApprovedPlanExecutionPrompt(planState, approvalText = '') {
|
|
2517
2534
|
const requirementPacket = buildGoalRequirementPacket(planState?.goal || '', 'coder');
|
|
2518
2535
|
const lines = [
|
|
@@ -2740,8 +2757,10 @@ async function askModel({
|
|
|
2740
2757
|
}
|
|
2741
2758
|
}
|
|
2742
2759
|
|
|
2760
|
+
const shouldGenerateTitle = text
|
|
2761
|
+
? !session.messages.some((msg) => msg?.role === 'user')
|
|
2762
|
+
: false;
|
|
2743
2763
|
if (text) {
|
|
2744
|
-
const shouldGenerateTitle = !session.messages.some((msg) => msg?.role === 'user');
|
|
2745
2764
|
const modelExtra =
|
|
2746
2765
|
typeof modelText === 'string' && modelText && modelText !== text ? { model_content: modelText } : {};
|
|
2747
2766
|
const userMessage = stampedMessage('user', text, modelExtra);
|
|
@@ -2773,7 +2792,16 @@ async function askModel({
|
|
|
2773
2792
|
|
|
2774
2793
|
const { definitions, handlers, formatters, deferredDefinitions, dispose: disposeTools } = getBuiltinTools({
|
|
2775
2794
|
workspaceRoot: process.cwd(),
|
|
2776
|
-
config
|
|
2795
|
+
config: {
|
|
2796
|
+
...config,
|
|
2797
|
+
policy: {
|
|
2798
|
+
...(config.policy || {}),
|
|
2799
|
+
allowed_paths: [
|
|
2800
|
+
...(Array.isArray(config.policy?.allowed_paths) ? config.policy.allowed_paths : []),
|
|
2801
|
+
path.join(getSessionsDir(), String(session.id))
|
|
2802
|
+
]
|
|
2803
|
+
}
|
|
2804
|
+
},
|
|
2777
2805
|
sessionId: session.id,
|
|
2778
2806
|
onSystemEvent: onAgentEvent,
|
|
2779
2807
|
getTodos: () => normalizeTodos(session.todos),
|
|
@@ -3004,7 +3032,7 @@ async function askModel({
|
|
|
3004
3032
|
await flushScheduledSave();
|
|
3005
3033
|
await saveSession(session);
|
|
3006
3034
|
// Generate a better title asynchronously after saving
|
|
3007
|
-
if (
|
|
3035
|
+
if (shouldGenerateTitle) {
|
|
3008
3036
|
const titleSessionId = session.id;
|
|
3009
3037
|
generateSessionTitle({
|
|
3010
3038
|
userText: text,
|
|
@@ -4242,11 +4270,22 @@ export async function createChatRuntime({
|
|
|
4242
4270
|
if (hasPendingPlanApproval(currentSession)) {
|
|
4243
4271
|
executionMode = 'plan';
|
|
4244
4272
|
}
|
|
4273
|
+
let compactState = null;
|
|
4274
|
+
const normalizeCompactThreshold = (value, fallback = 60) => {
|
|
4275
|
+
const num = Number(value);
|
|
4276
|
+
if (!Number.isFinite(num)) return fallback;
|
|
4277
|
+
return Math.min(95, Math.max(50, num));
|
|
4278
|
+
};
|
|
4279
|
+
const syncCompactStateFromConfig = () => {
|
|
4280
|
+
if (!compactState) return;
|
|
4281
|
+
compactState.threshold = normalizeCompactThreshold(config.context?.preflight_trigger_pct, 60);
|
|
4282
|
+
};
|
|
4245
4283
|
const syncRuntimeFromConfig = async ({ model: nextModel } = {}) => {
|
|
4246
4284
|
const configuredMode = String(config.execution?.mode || 'normal');
|
|
4247
4285
|
executionMode = hasPendingPlanApproval(currentSession)
|
|
4248
4286
|
? 'plan'
|
|
4249
4287
|
: (['normal', 'auto', 'plan'].includes(configuredMode) ? configuredMode : 'normal');
|
|
4288
|
+
syncCompactStateFromConfig();
|
|
4250
4289
|
|
|
4251
4290
|
const resolvedModel = String(nextModel || '').trim();
|
|
4252
4291
|
if (resolvedModel) {
|
|
@@ -4269,10 +4308,10 @@ export async function createChatRuntime({
|
|
|
4269
4308
|
// Set up tool result store under session directory
|
|
4270
4309
|
const sessionResultsDir = path.join(getSessionsDir(), String(currentSession.id));
|
|
4271
4310
|
setResultDir(sessionResultsDir);
|
|
4272
|
-
|
|
4311
|
+
compactState = {
|
|
4273
4312
|
backupMessages: null,
|
|
4274
4313
|
autoEnabled: true,
|
|
4275
|
-
threshold: 60,
|
|
4314
|
+
threshold: normalizeCompactThreshold(config.context?.preflight_trigger_pct, 60),
|
|
4276
4315
|
mode: 'conservative'
|
|
4277
4316
|
};
|
|
4278
4317
|
let compactedForModel = currentSession.compact?.view || null;
|
|
@@ -4356,6 +4395,7 @@ export async function createChatRuntime({
|
|
|
4356
4395
|
'soul.preset',
|
|
4357
4396
|
'soul.custom_path',
|
|
4358
4397
|
'policy.safe_mode',
|
|
4398
|
+
'policy.allowed_paths',
|
|
4359
4399
|
'policy.allow_dangerous_commands'
|
|
4360
4400
|
];
|
|
4361
4401
|
|
|
@@ -4777,6 +4817,11 @@ export async function createChatRuntime({
|
|
|
4777
4817
|
};
|
|
4778
4818
|
|
|
4779
4819
|
const persistAssistantExchange = async (userText, assistantText, { includeUser = true, extra = {} } = {}) => {
|
|
4820
|
+
const priorUserCount = currentSession.messages.filter((msg) => msg?.role === 'user').length;
|
|
4821
|
+
const priorAssistantCount = currentSession.messages.filter((msg) => msg?.role === 'assistant').length;
|
|
4822
|
+
const shouldGenerateTitle =
|
|
4823
|
+
(includeUser && userText && priorUserCount === 0) ||
|
|
4824
|
+
(!includeUser && userText && priorUserCount === 1 && priorAssistantCount === 0);
|
|
4780
4825
|
if (includeUser && userText) {
|
|
4781
4826
|
appendSessionMessage(stampedMessage('user', userText));
|
|
4782
4827
|
}
|
|
@@ -4787,7 +4832,7 @@ export async function createChatRuntime({
|
|
|
4787
4832
|
currentSession.mode = executionMode || config.execution?.mode || 'normal';
|
|
4788
4833
|
await saveSession(currentSession);
|
|
4789
4834
|
// Generate a better title asynchronously after saving
|
|
4790
|
-
if (shouldReplaceSessionTitle(currentSession.title)) {
|
|
4835
|
+
if (shouldGenerateTitle || shouldReplaceSessionTitle(currentSession.title)) {
|
|
4791
4836
|
const titleSessionId = currentSession.id;
|
|
4792
4837
|
generateSessionTitle({
|
|
4793
4838
|
userText,
|
|
@@ -5115,6 +5160,7 @@ export async function createChatRuntime({
|
|
|
5115
5160
|
});
|
|
5116
5161
|
currentSession.planState = null;
|
|
5117
5162
|
executionMode = 'auto';
|
|
5163
|
+
if (onAgentEvent) onAgentEvent({ type: 'reflect:approval_cleared' });
|
|
5118
5164
|
await reloadCommandsAndSkills();
|
|
5119
5165
|
const text = `Reflect skill written and loaded: /${written.draft.name}\nPath: ${written.filePath}`;
|
|
5120
5166
|
await persistLocalExchange(line, text, { includeUser: false });
|
|
@@ -5172,6 +5218,12 @@ export async function createChatRuntime({
|
|
|
5172
5218
|
workspaceRoot: process.cwd()
|
|
5173
5219
|
})
|
|
5174
5220
|
};
|
|
5221
|
+
if (onAgentEvent) {
|
|
5222
|
+
onAgentEvent({
|
|
5223
|
+
type: 'reflect:pending_approval',
|
|
5224
|
+
draft: buildPendingReflectSkillSnapshot(currentSession.planState)
|
|
5225
|
+
});
|
|
5226
|
+
}
|
|
5175
5227
|
const text = `Reflect skill draft revised.\n${buildPendingReflectSkillMessage(currentSession.planState)}`;
|
|
5176
5228
|
await persistLocalExchange(line, text);
|
|
5177
5229
|
return { type: 'system', text };
|
|
@@ -5209,6 +5261,7 @@ export async function createChatRuntime({
|
|
|
5209
5261
|
if (hasPendingReflectSkill(currentSession)) {
|
|
5210
5262
|
currentSession.planState = null;
|
|
5211
5263
|
executionMode = 'auto';
|
|
5264
|
+
if (onAgentEvent) onAgentEvent({ type: 'reflect:approval_cleared' });
|
|
5212
5265
|
const text = 'Reflect skill draft discarded.';
|
|
5213
5266
|
await persistLocalExchange(line, text, { includeUser: false });
|
|
5214
5267
|
return { type: 'system', text };
|
|
@@ -5668,6 +5721,12 @@ export async function createChatRuntime({
|
|
|
5668
5721
|
request: parsedReflect.request,
|
|
5669
5722
|
candidates
|
|
5670
5723
|
};
|
|
5724
|
+
if (onAgentEvent) {
|
|
5725
|
+
onAgentEvent({
|
|
5726
|
+
type: 'reflect:pending_approval',
|
|
5727
|
+
draft: buildPendingReflectSkillSnapshot(currentSession.planState)
|
|
5728
|
+
});
|
|
5729
|
+
}
|
|
5671
5730
|
const text = buildPendingReflectSkillMessage(currentSession.planState);
|
|
5672
5731
|
await persistLocalExchange(line, text);
|
|
5673
5732
|
return { type: 'system', text };
|
|
@@ -5735,7 +5794,7 @@ export async function createChatRuntime({
|
|
|
5735
5794
|
await resetConfig();
|
|
5736
5795
|
config = await loadConfig();
|
|
5737
5796
|
await syncRuntimeFromConfig({ model: resolveDefaultModel(config) });
|
|
5738
|
-
|
|
5797
|
+
syncCompactStateFromConfig();
|
|
5739
5798
|
compactState.mode = 'conservative';
|
|
5740
5799
|
compactState.autoEnabled = true;
|
|
5741
5800
|
const text = 'Config reset complete';
|
|
@@ -11,6 +11,8 @@ import { readSkillRegistry } from './skill-registry.js';
|
|
|
11
11
|
|
|
12
12
|
const MODULE_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
13
13
|
const BUNDLED_SKILLS_DIR = path.resolve(MODULE_DIR, '..', '..', 'skills');
|
|
14
|
+
const SKILL_CATALOG_FILE = 'codemini.skills.json';
|
|
15
|
+
const FRONTMATTER_READ_BYTES = 16 * 1024;
|
|
14
16
|
|
|
15
17
|
function parseArrayText(value) {
|
|
16
18
|
const inner = value.slice(1, -1).trim();
|
|
@@ -46,6 +48,79 @@ function parseFrontmatter(raw) {
|
|
|
46
48
|
return { metadata, content };
|
|
47
49
|
}
|
|
48
50
|
|
|
51
|
+
function readFrontmatterMetadata(filePath) {
|
|
52
|
+
let fd;
|
|
53
|
+
try {
|
|
54
|
+
fd = fs.openSync(filePath, 'r');
|
|
55
|
+
const buffer = Buffer.alloc(FRONTMATTER_READ_BYTES);
|
|
56
|
+
const bytesRead = fs.readSync(fd, buffer, 0, buffer.length, 0);
|
|
57
|
+
const raw = buffer.subarray(0, bytesRead).toString('utf8');
|
|
58
|
+
if (!raw.startsWith('---\n')) return {};
|
|
59
|
+
const end = raw.indexOf('\n---\n', 4);
|
|
60
|
+
if (end === -1) return {};
|
|
61
|
+
return parseFrontmatter(raw.slice(0, end + 5)).metadata;
|
|
62
|
+
} catch {
|
|
63
|
+
return {};
|
|
64
|
+
} finally {
|
|
65
|
+
if (fd !== undefined) {
|
|
66
|
+
try { fs.closeSync(fd); } catch {}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function readSkillCatalog(baseDir) {
|
|
72
|
+
const catalogPath = path.join(baseDir, SKILL_CATALOG_FILE);
|
|
73
|
+
try {
|
|
74
|
+
const parsed = JSON.parse(fs.readFileSync(catalogPath, 'utf8'));
|
|
75
|
+
return parsed && typeof parsed === 'object' && parsed.skills && typeof parsed.skills === 'object'
|
|
76
|
+
? parsed.skills
|
|
77
|
+
: {};
|
|
78
|
+
} catch {
|
|
79
|
+
return {};
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function normalizeStringArray(value) {
|
|
84
|
+
if (Array.isArray(value)) {
|
|
85
|
+
return value.map((item) => String(item || '').trim()).filter(Boolean);
|
|
86
|
+
}
|
|
87
|
+
const single = String(value || '').trim();
|
|
88
|
+
return single ? [single] : [];
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function catalogMetadata(catalog, name) {
|
|
92
|
+
const entry = catalog?.[name];
|
|
93
|
+
if (!entry || typeof entry !== 'object') return {};
|
|
94
|
+
return {
|
|
95
|
+
...(entry.description ? { description: String(entry.description) } : {}),
|
|
96
|
+
...(entry.mode ? { mode: String(entry.mode) } : {}),
|
|
97
|
+
...(entry.enabled !== undefined ? { enabled: entry.enabled !== false } : {}),
|
|
98
|
+
...(entry.priority !== undefined ? { priority: Number(entry.priority) } : {}),
|
|
99
|
+
triggers: normalizeStringArray(entry.triggers)
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function commandWithContent(command, parsedContent) {
|
|
104
|
+
if (parsedContent !== undefined) {
|
|
105
|
+
return { ...command, content: parsedContent };
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
let cached;
|
|
109
|
+
let loaded = false;
|
|
110
|
+
return Object.defineProperty({ ...command }, 'content', {
|
|
111
|
+
enumerable: true,
|
|
112
|
+
configurable: true,
|
|
113
|
+
get() {
|
|
114
|
+
if (!loaded) {
|
|
115
|
+
const raw = fs.readFileSync(command.path, 'utf8');
|
|
116
|
+
cached = parseFrontmatter(raw).content;
|
|
117
|
+
loaded = true;
|
|
118
|
+
}
|
|
119
|
+
return cached;
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
49
124
|
function safeEntries(dir) {
|
|
50
125
|
try {
|
|
51
126
|
return fs.readdirSync(dir);
|
|
@@ -104,77 +179,95 @@ function loadMarkdownCommandsFromDir(baseDir, source, out) {
|
|
|
104
179
|
|
|
105
180
|
function loadLegacySkillsFromDir(baseDir, source, out) {
|
|
106
181
|
if (!fs.existsSync(baseDir)) return;
|
|
182
|
+
const catalog = readSkillCatalog(baseDir);
|
|
107
183
|
for (const entry of safeEntries(baseDir)) {
|
|
108
184
|
if (!isSafeEntry(entry)) continue;
|
|
109
185
|
const full = path.join(baseDir, entry);
|
|
110
186
|
const stat = fs.statSync(full);
|
|
111
187
|
if (!stat.isDirectory()) continue;
|
|
188
|
+
const catalogMeta = catalogMetadata(catalog, entry);
|
|
112
189
|
const skillFile = path.join(full, 'SKILL.md');
|
|
113
190
|
if (!fs.existsSync(skillFile)) continue;
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
setCommand(out, entry, {
|
|
191
|
+
const frontmatter = readFrontmatterMetadata(skillFile);
|
|
192
|
+
setCommand(out, entry, commandWithContent({
|
|
117
193
|
name: entry,
|
|
118
194
|
source: `${source}-skill`,
|
|
119
195
|
path: skillFile,
|
|
120
196
|
metadata: {
|
|
121
|
-
|
|
197
|
+
...frontmatter,
|
|
198
|
+
...catalogMeta,
|
|
199
|
+
description: catalogMeta.description || frontmatter.description || 'Legacy skill',
|
|
122
200
|
type: 'skill'
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
});
|
|
201
|
+
}
|
|
202
|
+
}));
|
|
126
203
|
}
|
|
127
204
|
}
|
|
128
205
|
|
|
129
206
|
function loadBundledSkillsFromDir(baseDir, out) {
|
|
130
207
|
if (!fs.existsSync(baseDir)) return;
|
|
208
|
+
const catalog = readSkillCatalog(baseDir);
|
|
131
209
|
for (const entry of safeEntries(baseDir)) {
|
|
132
210
|
if (!isSafeEntry(entry)) continue;
|
|
133
211
|
const full = path.join(baseDir, entry);
|
|
134
212
|
const stat = fs.statSync(full);
|
|
135
213
|
if (!stat.isDirectory()) continue;
|
|
214
|
+
const catalogMeta = catalogMetadata(catalog, entry);
|
|
136
215
|
const skillFile = path.join(full, 'SKILL.md');
|
|
137
216
|
if (!fs.existsSync(skillFile)) continue;
|
|
138
|
-
const
|
|
139
|
-
|
|
140
|
-
setCommand(out, entry, {
|
|
217
|
+
const frontmatter = readFrontmatterMetadata(skillFile);
|
|
218
|
+
setCommand(out, entry, commandWithContent({
|
|
141
219
|
name: entry,
|
|
142
220
|
source: 'bundled-skill',
|
|
143
221
|
path: skillFile,
|
|
144
222
|
metadata: {
|
|
145
|
-
...
|
|
223
|
+
...frontmatter,
|
|
224
|
+
...catalogMeta,
|
|
146
225
|
type: 'skill',
|
|
147
|
-
version:
|
|
148
|
-
description:
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
|
|
226
|
+
version: frontmatter.version || '0.1.0',
|
|
227
|
+
description: catalogMeta.description || frontmatter.description || 'Bundled skill'
|
|
228
|
+
}
|
|
229
|
+
}));
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function applySkillCatalogPatches(baseDir, out) {
|
|
234
|
+
const catalog = readSkillCatalog(baseDir);
|
|
235
|
+
for (const name of Object.keys(catalog)) {
|
|
236
|
+
const existing = out.get(name);
|
|
237
|
+
if (!existing || existing.metadata?.type !== 'skill') continue;
|
|
238
|
+
const meta = catalogMetadata(catalog, name);
|
|
239
|
+
existing.metadata = {
|
|
240
|
+
...existing.metadata,
|
|
241
|
+
...meta,
|
|
242
|
+
description: meta.description || existing.metadata.description || ''
|
|
243
|
+
};
|
|
152
244
|
}
|
|
153
245
|
}
|
|
154
246
|
|
|
155
247
|
function loadInstalledSkillsFromRegistry(baseDir, registry, out) {
|
|
156
248
|
if (!registry || !Array.isArray(registry.skills)) return;
|
|
249
|
+
const catalog = readSkillCatalog(baseDir);
|
|
157
250
|
for (const skill of registry.skills) {
|
|
158
251
|
if (skill.enabled === false) continue;
|
|
159
252
|
const name = skill.name;
|
|
160
253
|
if (out.has(name)) continue;
|
|
254
|
+
const catalogMeta = catalogMetadata(catalog, name);
|
|
161
255
|
const entry = skill.entryFile || 'SKILL.md';
|
|
162
256
|
const full = path.join(baseDir, name, entry);
|
|
163
257
|
if (!fs.existsSync(full)) continue;
|
|
164
|
-
const
|
|
165
|
-
|
|
166
|
-
setCommand(out, name, {
|
|
258
|
+
const frontmatter = readFrontmatterMetadata(full);
|
|
259
|
+
setCommand(out, name, commandWithContent({
|
|
167
260
|
name,
|
|
168
261
|
source: 'registry-skill',
|
|
169
262
|
path: full,
|
|
170
263
|
metadata: {
|
|
171
|
-
...
|
|
264
|
+
...frontmatter,
|
|
265
|
+
...catalogMeta,
|
|
172
266
|
type: 'skill',
|
|
173
|
-
version: skill.version ||
|
|
174
|
-
description: skill.description ||
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
});
|
|
267
|
+
version: skill.version || frontmatter.version || '0.0.0',
|
|
268
|
+
description: catalogMeta.description || skill.description || frontmatter.description || 'Installed skill'
|
|
269
|
+
}
|
|
270
|
+
}));
|
|
178
271
|
}
|
|
179
272
|
}
|
|
180
273
|
|
|
@@ -201,10 +294,12 @@ export async function loadCommandsAndSkills(cwd = process.cwd()) {
|
|
|
201
294
|
const commands = new Map();
|
|
202
295
|
|
|
203
296
|
loadBundledSkillsFromDir(BUNDLED_SKILLS_DIR, commands);
|
|
297
|
+
applySkillCatalogPatches(getProjectSkillsDir(cwd), commands);
|
|
204
298
|
loadMarkdownCommandsFromDir(getCommandsDir(), 'global', commands);
|
|
205
299
|
loadMarkdownCommandsFromDir(getProjectCommandsDir(cwd), 'project', commands);
|
|
206
300
|
loadLegacySkillsFromDir(getSkillsDir(), 'global', commands);
|
|
207
301
|
loadLegacySkillsFromDir(getProjectSkillsDir(cwd), 'project', commands);
|
|
302
|
+
applySkillCatalogPatches(getProjectSkillsDir(cwd), commands);
|
|
208
303
|
const registry = await readSkillRegistry();
|
|
209
304
|
loadInstalledSkillsFromRegistry(getSkillsDir(), registry, commands);
|
|
210
305
|
|