winter-super-cli 2026.6.5 → 2026.6.6
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/package.json +3 -3
- package/src/agent/runtime.js +3 -0
- package/src/ai/model-capabilities.js +2 -1
- package/src/ai/prompts/system-prompt.js +7 -48
- package/src/ai/providers.js +47 -7
- package/src/ai/small-model-amplifier.js +5 -17
- package/src/cli/commands.js +150 -0
- package/src/cli/repl.js +189 -180
- package/src/codebase-index/codegraph-adapter.js +154 -0
- package/src/codebase-index/indexer.js +1 -1
- package/src/codebase-index/search.js +31 -2
- package/src/context/router.js +4 -41
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "winter-super-cli",
|
|
3
|
-
"version": "2026.6.
|
|
3
|
+
"version": "2026.6.6",
|
|
4
4
|
"description": "❄️ AI-Powered Development CLI with Interactive REPL",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "bin/winter.js",
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
"diff": "^9.0.0"
|
|
58
58
|
},
|
|
59
59
|
"optionalDependencies": {
|
|
60
|
+
"@colbymchenry/codegraph": "^0.7.12",
|
|
60
61
|
"puppeteer": "^24.43.1"
|
|
61
|
-
}
|
|
62
|
-
"devDependencies": {}
|
|
62
|
+
}
|
|
63
63
|
}
|
package/src/agent/runtime.js
CHANGED
|
@@ -38,6 +38,7 @@ export class AgentRuntime {
|
|
|
38
38
|
depth,
|
|
39
39
|
});
|
|
40
40
|
const maxToolTurns = amplifier.maxToolTurns || 8;
|
|
41
|
+
let forceTextToolFallback = false;
|
|
41
42
|
|
|
42
43
|
try {
|
|
43
44
|
for (let i = 0; i < maxToolTurns; i++) {
|
|
@@ -46,6 +47,7 @@ export class AgentRuntime {
|
|
|
46
47
|
provider: executionProfile.provider,
|
|
47
48
|
model: executionProfile.model,
|
|
48
49
|
enableTools: true,
|
|
50
|
+
toolPromptOnly: forceTextToolFallback,
|
|
49
51
|
requireToolEvidence: requireToolEvidence && !usedTools,
|
|
50
52
|
}, startedAt, totalUsage);
|
|
51
53
|
|
|
@@ -73,6 +75,7 @@ export class AgentRuntime {
|
|
|
73
75
|
role: 'user',
|
|
74
76
|
content: repl.buildToolEvidenceCorrection(messages),
|
|
75
77
|
});
|
|
78
|
+
forceTextToolFallback = true;
|
|
76
79
|
finalContent = '';
|
|
77
80
|
continue;
|
|
78
81
|
}
|
|
@@ -32,6 +32,7 @@ export function classifyModelTier(modelName, provider = '') {
|
|
|
32
32
|
/claude-3-5-sonnet/i, /claude-opus/i, /claude-4/i, /claude-sonnet-4/i,
|
|
33
33
|
/gpt-4o/i, /gpt-4-turbo/i, /o1/i, /o3/i,
|
|
34
34
|
/gemini-2\.5-pro/i, /gemini-2\.0-ultra/i,
|
|
35
|
+
/minimax-?m2\.5/i, /minimax.*m2\.5/i, /minimax/i,
|
|
35
36
|
/deepseek-v3/i, /deepseek-r1/i,
|
|
36
37
|
/llama-4/i, /llama-3-70b/i, /llama3-70b/i, /llama3\.1-70b/i, /llama3\.2-90b/i, /llama3\.3/i,
|
|
37
38
|
/qwen2\.5-?72b/i, /qwen2\.5-?70b/i, /qwen-?2\.5-?72b/i,
|
|
@@ -123,7 +124,7 @@ export function classifyModelTier(modelName, provider = '') {
|
|
|
123
124
|
if (pattern.test(name)) return MODEL_TIERS.SMALL;
|
|
124
125
|
}
|
|
125
126
|
|
|
126
|
-
if (/
|
|
127
|
+
if (/\btiny\b/i.test(name) || /(?:^|[-_:/])mini(?:$|[-_:/])/i.test(name) || /\bsmall\b/i.test(name) || /\bnano\b/i.test(name)) {
|
|
127
128
|
return MODEL_TIERS.TINY;
|
|
128
129
|
}
|
|
129
130
|
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Dynamic System Prompt Builder
|
|
3
3
|
* Builds context-aware system prompts based on task, role, and session state.
|
|
4
|
-
*
|
|
4
|
+
* Winter always gives every model the strongest available agent instructions.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import { isSmallModel, getModelCapabilityLabel } from '../model-capabilities.js';
|
|
8
7
|
import { formatRuntimeEnvironmentSummary, getRuntimeEnvironment } from '../../cli/runtime-env.js';
|
|
9
8
|
|
|
10
9
|
const BASE_PRINCIPLES = [
|
|
@@ -74,35 +73,6 @@ function appendSharedContext(parts, { environment, session, design, resourceCont
|
|
|
74
73
|
}
|
|
75
74
|
}
|
|
76
75
|
|
|
77
|
-
function buildCompactSmallModelPrompt(options = {}) {
|
|
78
|
-
const { tools = [], modelTier } = options;
|
|
79
|
-
const parts = [
|
|
80
|
-
'You are Winter, an AI coding assistant running on a ' + getModelCapabilityLabel(modelTier) + '.',
|
|
81
|
-
'',
|
|
82
|
-
'## Operating Rules',
|
|
83
|
-
'1. Understand the user request first. If project state matters, inspect files before answering.',
|
|
84
|
-
'2. Operate as an agent: inspect -> hypothesize -> act -> verify -> final.',
|
|
85
|
-
'3. Keep context tight. Use only relevant tools and avoid long explanations.',
|
|
86
|
-
'4. For coding/debug: Read/Grep/Glob/logs -> Edit/Write -> Bash/test/browser smoke. Do not guess file paths.',
|
|
87
|
-
'5. For UI/design: inspect existing components/styles/resources before changing visuals.',
|
|
88
|
-
'6. Final answer in Vietnamese. Mention changed files and verification only.',
|
|
89
|
-
'',
|
|
90
|
-
];
|
|
91
|
-
|
|
92
|
-
const toolList = formatToolList(tools);
|
|
93
|
-
if (toolList) parts.push('## Tools', toolList, '');
|
|
94
|
-
appendSharedContext(parts, { ...options, includeResources: false });
|
|
95
|
-
|
|
96
|
-
parts.push(
|
|
97
|
-
'## Response Shape',
|
|
98
|
-
'- If action is needed, use tools instead of describing the action.',
|
|
99
|
-
'- If an image is provided, analyze the image directly and tie findings to project files when relevant.',
|
|
100
|
-
'- Keep final output short and concrete.',
|
|
101
|
-
);
|
|
102
|
-
|
|
103
|
-
return parts.filter(Boolean).join('\n');
|
|
104
|
-
}
|
|
105
|
-
|
|
106
76
|
function buildStandardSystemPrompt(options = {}) {
|
|
107
77
|
const { role = 'coding', tools = [], resourceContext } = options;
|
|
108
78
|
const parts = [
|
|
@@ -113,6 +83,7 @@ function buildStandardSystemPrompt(options = {}) {
|
|
|
113
83
|
'',
|
|
114
84
|
'## Tool Usage',
|
|
115
85
|
'Use tools when they materially improve correctness. Inspect before editing. Verify after changes.',
|
|
86
|
+
'Use maximum reasoning discipline for every model tier, including tiny, local, free, and routed models.',
|
|
116
87
|
'Never invent file paths, APIs, command output, or test results.',
|
|
117
88
|
'For debug work, locate the first hard failure, patch the root cause, and verify with the closest test/build/browser smoke.',
|
|
118
89
|
'For design/UI work, inspect the existing interface and design resources first; avoid generic placeholder layouts.',
|
|
@@ -139,9 +110,7 @@ export function buildSystemPrompt({
|
|
|
139
110
|
modelTier,
|
|
140
111
|
} = {}) {
|
|
141
112
|
const options = { role, context, tools, session, environment, design, resourceContext, modelTier };
|
|
142
|
-
return
|
|
143
|
-
? buildCompactSmallModelPrompt(options)
|
|
144
|
-
: buildStandardSystemPrompt(options);
|
|
113
|
+
return buildStandardSystemPrompt(options);
|
|
145
114
|
}
|
|
146
115
|
|
|
147
116
|
export function buildFastSystemPrompt({
|
|
@@ -149,18 +118,10 @@ export function buildFastSystemPrompt({
|
|
|
149
118
|
tools = [],
|
|
150
119
|
modelTier,
|
|
151
120
|
} = {}) {
|
|
152
|
-
if (modelTier && isSmallModel(modelTier)) {
|
|
153
|
-
return [
|
|
154
|
-
'Winter (fast mode - small model). Be concise. Use tools when needed.',
|
|
155
|
-
tools.length > 0 ? `Tools: ${tools.join(', ')}` : '',
|
|
156
|
-
'Use a brief private plan, then answer in 1 sentence.',
|
|
157
|
-
].filter(Boolean).join('\n');
|
|
158
|
-
}
|
|
159
|
-
|
|
160
121
|
return [
|
|
161
|
-
'You are Winter (fast mode). Be concise
|
|
122
|
+
'You are Winter (fast mode with maximum correctness). Be concise, but inspect and use tools when needed.',
|
|
162
123
|
tools.length > 0 ? `Tools: ${tools.join(', ')}` : '',
|
|
163
|
-
'
|
|
124
|
+
'Use a brief private plan, then execute or answer with concrete evidence.',
|
|
164
125
|
].filter(Boolean).join('\n');
|
|
165
126
|
}
|
|
166
127
|
|
|
@@ -176,15 +137,13 @@ export function buildAgentSystemPrompt(role, { tools = [], modelTier } = {}) {
|
|
|
176
137
|
};
|
|
177
138
|
|
|
178
139
|
const base = roleConfigs[role] || roleConfigs.coding;
|
|
179
|
-
const
|
|
180
|
-
? '\n\nYou are running on a small model. Keep context tight, use tools early, and keep final output short.'
|
|
181
|
-
: '';
|
|
140
|
+
const strengthNote = '\n\nWinter Strength Mode: use the full agent loop, inspect real code, reason carefully, verify results, and avoid unsupported claims regardless of base model size.';
|
|
182
141
|
|
|
183
142
|
return [
|
|
184
143
|
`You are Winter (${role} agent).`,
|
|
185
144
|
base,
|
|
186
145
|
tools.length > 0 ? `\nTools: ${tools.join(', ')}` : '',
|
|
187
|
-
|
|
146
|
+
strengthNote,
|
|
188
147
|
'\nCRITICAL: Output only the requested format. No extra commentary.',
|
|
189
148
|
].filter(Boolean).join('\n');
|
|
190
149
|
}
|
package/src/ai/providers.js
CHANGED
|
@@ -293,6 +293,44 @@ export class AIProviderManager {
|
|
|
293
293
|
this.tools = tools;
|
|
294
294
|
}
|
|
295
295
|
|
|
296
|
+
normalizeToolDefinitionsForApi(tools = []) {
|
|
297
|
+
if (!Array.isArray(tools)) return [];
|
|
298
|
+
|
|
299
|
+
return tools
|
|
300
|
+
.map(tool => {
|
|
301
|
+
if (!tool || typeof tool !== 'object') return null;
|
|
302
|
+
|
|
303
|
+
if (tool.type === 'function' && tool.function && typeof tool.function === 'object') {
|
|
304
|
+
return tool;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
if (tool.name && tool.parameters) {
|
|
308
|
+
return {
|
|
309
|
+
type: 'function',
|
|
310
|
+
function: {
|
|
311
|
+
name: tool.name,
|
|
312
|
+
description: tool.description || '',
|
|
313
|
+
parameters: tool.parameters,
|
|
314
|
+
},
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
if (tool.function?.name) {
|
|
319
|
+
return {
|
|
320
|
+
type: 'function',
|
|
321
|
+
function: {
|
|
322
|
+
name: tool.function.name,
|
|
323
|
+
description: tool.function.description || tool.description || '',
|
|
324
|
+
parameters: tool.function.parameters || tool.parameters || { type: 'object', properties: {} },
|
|
325
|
+
},
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
return null;
|
|
330
|
+
})
|
|
331
|
+
.filter(Boolean);
|
|
332
|
+
}
|
|
333
|
+
|
|
296
334
|
async chat(message, options = {}) {
|
|
297
335
|
await this.init();
|
|
298
336
|
const messages = [
|
|
@@ -405,8 +443,9 @@ export class AIProviderManager {
|
|
|
405
443
|
}
|
|
406
444
|
}
|
|
407
445
|
|
|
408
|
-
if (this.tools.length > 0 && options.enableTools) {
|
|
409
|
-
|
|
446
|
+
if (this.tools.length > 0 && options.enableTools && !options.toolPromptOnly) {
|
|
447
|
+
const tools = this.normalizeToolDefinitionsForApi(this.tools);
|
|
448
|
+
if (tools.length > 0) body.tools = tools;
|
|
410
449
|
}
|
|
411
450
|
|
|
412
451
|
const headers = {
|
|
@@ -460,8 +499,9 @@ export class AIProviderManager {
|
|
|
460
499
|
}
|
|
461
500
|
}
|
|
462
501
|
|
|
463
|
-
if (this.tools.length > 0 && options.enableTools) {
|
|
464
|
-
|
|
502
|
+
if (this.tools.length > 0 && options.enableTools && !options.toolPromptOnly) {
|
|
503
|
+
const tools = this.normalizeToolDefinitionsForApi(this.tools);
|
|
504
|
+
if (tools.length > 0) body.tools = tools;
|
|
465
505
|
}
|
|
466
506
|
|
|
467
507
|
const headers = {
|
|
@@ -586,7 +626,7 @@ export class AIProviderManager {
|
|
|
586
626
|
const body = {
|
|
587
627
|
model: options.model || provider.model,
|
|
588
628
|
messages: currentMessages,
|
|
589
|
-
tools: this.tools.length > 0 ? this.tools : undefined,
|
|
629
|
+
tools: this.tools.length > 0 ? this.normalizeToolDefinitionsForApi(this.tools) : undefined,
|
|
590
630
|
};
|
|
591
631
|
|
|
592
632
|
const headers = {
|
|
@@ -681,13 +721,13 @@ export class AIProviderManager {
|
|
|
681
721
|
let reasoningPrompt = '';
|
|
682
722
|
if (options.reasoningLevel || options.reasoningPrompt) {
|
|
683
723
|
reasoningPrompt = options.reasoningPrompt || new ReasoningConfig({
|
|
684
|
-
level: options.reasoningLevel || REASONING_LEVELS.
|
|
724
|
+
level: options.reasoningLevel || REASONING_LEVELS.MAX,
|
|
685
725
|
provider: this.activeProvider,
|
|
686
726
|
modelTier: this._modelTier,
|
|
687
727
|
}).getPromptInstructions();
|
|
688
728
|
} else if (taskInfo) {
|
|
689
729
|
// Auto-inject based on task complexity for providers without API reasoning
|
|
690
|
-
const level =
|
|
730
|
+
const level = REASONING_LEVELS.MAX;
|
|
691
731
|
const config = new ReasoningConfig({
|
|
692
732
|
level,
|
|
693
733
|
provider: this.activeProvider,
|
|
@@ -1,30 +1,19 @@
|
|
|
1
|
-
import { isSmallModel } from './model-capabilities.js';
|
|
2
|
-
|
|
3
1
|
export function isWeakTier(modelTier = '') {
|
|
4
|
-
return
|
|
2
|
+
return true;
|
|
5
3
|
}
|
|
6
4
|
|
|
7
5
|
export function buildSmallModelAmplification({ modelTier = '', workflowProfile = 'general', depth = 'standard' } = {}) {
|
|
8
|
-
const weak = isWeakTier(modelTier);
|
|
9
|
-
if (!weak) {
|
|
10
|
-
return {
|
|
11
|
-
weak: false,
|
|
12
|
-
maxToolTurns: 8,
|
|
13
|
-
enforceSelfCritique: false,
|
|
14
|
-
hint: '',
|
|
15
|
-
};
|
|
16
|
-
}
|
|
17
|
-
|
|
18
6
|
const deepLike = depth === 'deep' || /debug|backend|data|devops|ai/.test(workflowProfile);
|
|
19
|
-
const maxToolTurns = deepLike ?
|
|
7
|
+
const maxToolTurns = deepLike ? 18 : 14;
|
|
20
8
|
|
|
21
9
|
const hint = [
|
|
22
|
-
'[
|
|
23
|
-
'-
|
|
10
|
+
'[Winter Strength Amplifier]',
|
|
11
|
+
'- Every model, including tiny/local/free models, must run at Winter maximum capability.',
|
|
24
12
|
'- Mandatory loop: PLAN (requirements + files + risks) -> TOOL ACTIONS -> VERIFY -> SELF-CHECK -> FINAL.',
|
|
25
13
|
'- Do not skip verification. If verification fails, iterate until max loops.',
|
|
26
14
|
'- Before final answer, run a private self-critique: missing edge cases, missing tests, over-claims, and incorrect assumptions.',
|
|
27
15
|
'- Prefer concrete evidence from tool outputs over reasoning guesses.',
|
|
16
|
+
'- Use CodeGraph/codebase index context before broad file reads when available.',
|
|
28
17
|
].join('\n');
|
|
29
18
|
|
|
30
19
|
return {
|
|
@@ -34,4 +23,3 @@ export function buildSmallModelAmplification({ modelTier = '', workflowProfile =
|
|
|
34
23
|
hint,
|
|
35
24
|
};
|
|
36
25
|
}
|
|
37
|
-
|
package/src/cli/commands.js
CHANGED
|
@@ -962,6 +962,150 @@ EXECUTION CONTRACT:
|
|
|
962
962
|
);
|
|
963
963
|
}
|
|
964
964
|
|
|
965
|
+
buildPlanSlug(task) {
|
|
966
|
+
return String(task || 'plan')
|
|
967
|
+
.toLowerCase()
|
|
968
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
969
|
+
.replace(/^-+|-+$/g, '')
|
|
970
|
+
.slice(0, 48) || 'plan';
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
getPlanScaffoldSpec(task, workflow = {}) {
|
|
974
|
+
const slug = this.buildPlanSlug(task);
|
|
975
|
+
const featureName = slug.replace(/-/g, ' ');
|
|
976
|
+
const profile = String(workflow.profile || 'general');
|
|
977
|
+
const family = profile.split('-')[0] || 'general';
|
|
978
|
+
|
|
979
|
+
const common = {
|
|
980
|
+
dirs: ['docs', 'tests'],
|
|
981
|
+
files: {
|
|
982
|
+
'docs/plan-notes.md': [
|
|
983
|
+
`# ${featureName}`,
|
|
984
|
+
'',
|
|
985
|
+
`- Profile: ${profile}`,
|
|
986
|
+
'- Status: scaffolded by Winter plan apply',
|
|
987
|
+
'',
|
|
988
|
+
].join('\n'),
|
|
989
|
+
'tests/README.md': [
|
|
990
|
+
'# Test Plan',
|
|
991
|
+
'',
|
|
992
|
+
'- Add smoke tests for the generated plan.',
|
|
993
|
+
'- Keep verification commands close to the final implementation stack.',
|
|
994
|
+
'',
|
|
995
|
+
].join('\n'),
|
|
996
|
+
},
|
|
997
|
+
};
|
|
998
|
+
|
|
999
|
+
const specs = {
|
|
1000
|
+
webapp: {
|
|
1001
|
+
dirs: ['src/app', `src/features/${slug}`, 'src/components', 'src/lib', 'tests/e2e'],
|
|
1002
|
+
files: {
|
|
1003
|
+
[`src/features/${slug}/README.md`]: `# ${featureName}\n\nFeature slice for the plan.\n`,
|
|
1004
|
+
[`src/features/${slug}/tasks.md`]: '',
|
|
1005
|
+
'tests/e2e/README.md': '# E2E Tests\n\nAdd Playwright or browser smoke tests here.\n',
|
|
1006
|
+
},
|
|
1007
|
+
},
|
|
1008
|
+
mobile: {
|
|
1009
|
+
dirs: ['src/navigation', `src/features/${slug}`, 'src/screens', 'src/services', 'src/components', 'src/state', 'tests'],
|
|
1010
|
+
files: {
|
|
1011
|
+
[`src/features/${slug}/README.md`]: `# ${featureName}\n\nMobile feature module for screens, hooks, API calls, and state.\n`,
|
|
1012
|
+
[`src/features/${slug}/tasks.md`]: '',
|
|
1013
|
+
'src/navigation/README.md': '# Navigation\n\nDefine app navigation graph and route ownership here.\n',
|
|
1014
|
+
'src/services/README.md': '# Services\n\nPlace API clients, storage adapters, and platform services here.\n',
|
|
1015
|
+
},
|
|
1016
|
+
},
|
|
1017
|
+
backend: {
|
|
1018
|
+
dirs: [`src/modules/${slug}`, `src/modules/${slug}/dto`, `src/modules/${slug}/tests`, 'src/config', 'src/common'],
|
|
1019
|
+
files: {
|
|
1020
|
+
[`src/modules/${slug}/README.md`]: `# ${featureName}\n\nBackend module scaffold for routes/controllers/services/tests.\n`,
|
|
1021
|
+
[`src/modules/${slug}/tasks.md`]: '',
|
|
1022
|
+
'src/config/README.md': '# Config\n\nDocument environment variables and runtime config here.\n',
|
|
1023
|
+
},
|
|
1024
|
+
},
|
|
1025
|
+
desktop: {
|
|
1026
|
+
dirs: ['src/main', 'src/preload', 'src/renderer', `src/features/${slug}`, 'tests'],
|
|
1027
|
+
files: {
|
|
1028
|
+
[`src/features/${slug}/README.md`]: `# ${featureName}\n\nDesktop feature module scaffold.\n`,
|
|
1029
|
+
[`src/features/${slug}/tasks.md`]: '',
|
|
1030
|
+
'src/main/README.md': '# Main Process\n\nKeep privileged runtime code here.\n',
|
|
1031
|
+
'src/preload/README.md': '# Preload\n\nExpose minimal validated IPC APIs here.\n',
|
|
1032
|
+
},
|
|
1033
|
+
},
|
|
1034
|
+
ai: {
|
|
1035
|
+
dirs: ['src/ingestion', 'src/retrieval', 'src/generation', 'src/evals', `src/features/${slug}`],
|
|
1036
|
+
files: {
|
|
1037
|
+
[`src/features/${slug}/README.md`]: `# ${featureName}\n\nAI feature scaffold for pipeline wiring and evals.\n`,
|
|
1038
|
+
[`src/features/${slug}/tasks.md`]: '',
|
|
1039
|
+
'src/evals/README.md': '# Evals\n\nTrack regression prompts, datasets, and quality thresholds here.\n',
|
|
1040
|
+
},
|
|
1041
|
+
},
|
|
1042
|
+
};
|
|
1043
|
+
|
|
1044
|
+
const spec = specs[family] || {
|
|
1045
|
+
dirs: [`src/features/${slug}`, 'src/lib', 'tests'],
|
|
1046
|
+
files: {
|
|
1047
|
+
[`src/features/${slug}/README.md`]: `# ${featureName}\n\nFeature scaffold generated from Winter plan apply.\n`,
|
|
1048
|
+
[`src/features/${slug}/tasks.md`]: '',
|
|
1049
|
+
},
|
|
1050
|
+
};
|
|
1051
|
+
|
|
1052
|
+
return {
|
|
1053
|
+
slug,
|
|
1054
|
+
family,
|
|
1055
|
+
dirs: [...common.dirs, ...spec.dirs],
|
|
1056
|
+
files: { ...common.files, ...spec.files },
|
|
1057
|
+
};
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
async writeFileIfMissing(filePath, content) {
|
|
1061
|
+
try {
|
|
1062
|
+
await fs.writeFile(filePath, content, { encoding: 'utf8', flag: 'wx' });
|
|
1063
|
+
return true;
|
|
1064
|
+
} catch (error) {
|
|
1065
|
+
if (error?.code === 'EEXIST') return false;
|
|
1066
|
+
throw error;
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
async applyPlanProfileScaffold({ task, selected, workflow }) {
|
|
1071
|
+
const spec = this.getPlanScaffoldSpec(task, workflow);
|
|
1072
|
+
const created = [];
|
|
1073
|
+
const skipped = [];
|
|
1074
|
+
|
|
1075
|
+
for (const dir of spec.dirs) {
|
|
1076
|
+
const dirPath = path.join(this.projectPath, dir);
|
|
1077
|
+
await fs.mkdir(dirPath, { recursive: true });
|
|
1078
|
+
created.push(dir);
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
const taskListContent = [
|
|
1082
|
+
`# ${selected.title}`,
|
|
1083
|
+
'',
|
|
1084
|
+
`- Task: ${task}`,
|
|
1085
|
+
`- Profile: ${workflow.profile}`,
|
|
1086
|
+
'',
|
|
1087
|
+
'## Steps',
|
|
1088
|
+
...selected.steps.map(step => `- [ ] ${step}`),
|
|
1089
|
+
'',
|
|
1090
|
+
].join('\n');
|
|
1091
|
+
|
|
1092
|
+
const files = { ...spec.files };
|
|
1093
|
+
for (const fileName of Object.keys(files)) {
|
|
1094
|
+
if (fileName.endsWith('/tasks.md')) {
|
|
1095
|
+
files[fileName] = taskListContent;
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
for (const [relativePath, content] of Object.entries(files)) {
|
|
1100
|
+
const filePath = path.join(this.projectPath, relativePath);
|
|
1101
|
+
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
1102
|
+
const didCreate = await this.writeFileIfMissing(filePath, content);
|
|
1103
|
+
(didCreate ? created : skipped).push(relativePath);
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
return { ...spec, created, skipped };
|
|
1107
|
+
}
|
|
1108
|
+
|
|
965
1109
|
async exportPlanArtifact({ task, workflow, selected, outputPath, format = 'md' }) {
|
|
966
1110
|
const normalizedFormat = format === 'json' ? 'json' : 'md';
|
|
967
1111
|
const finalPath = this.resolvePlanOutputPath(task, normalizedFormat, outputPath);
|
|
@@ -1004,6 +1148,7 @@ EXECUTION CONTRACT:
|
|
|
1004
1148
|
}
|
|
1005
1149
|
|
|
1006
1150
|
async applyPlanSkeleton({ task, selected, workflow, exportPath = null }) {
|
|
1151
|
+
const scaffold = await this.applyPlanProfileScaffold({ task, selected, workflow });
|
|
1007
1152
|
const targetPath = path.join(this.projectPath, '.winter', 'plan-task-list.md');
|
|
1008
1153
|
const skeleton = [
|
|
1009
1154
|
'# Plan Task List',
|
|
@@ -1012,10 +1157,15 @@ EXECUTION CONTRACT:
|
|
|
1012
1157
|
`- Profile: ${workflow.profile}`,
|
|
1013
1158
|
`- Plan: ${selected.title}`,
|
|
1014
1159
|
...(exportPath ? [`- Plan File: ${path.relative(this.projectPath, exportPath) || exportPath}`] : []),
|
|
1160
|
+
`- Scaffold Profile: ${scaffold.family}`,
|
|
1015
1161
|
'',
|
|
1016
1162
|
'## TODO',
|
|
1017
1163
|
...selected.steps.map(step => `- [ ] ${step}`),
|
|
1018
1164
|
'',
|
|
1165
|
+
'## Scaffold Created',
|
|
1166
|
+
...scaffold.created.map(item => `- ${item}`),
|
|
1167
|
+
...(scaffold.skipped.length ? ['', '## Existing Files Kept', ...scaffold.skipped.map(item => `- ${item}`)] : []),
|
|
1168
|
+
'',
|
|
1019
1169
|
].join('\n');
|
|
1020
1170
|
await fs.mkdir(path.dirname(targetPath), { recursive: true });
|
|
1021
1171
|
await fs.writeFile(targetPath, skeleton, 'utf8');
|