agent-state-machine 2.1.8 → 2.1.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/bin/cli.js CHANGED
@@ -7,6 +7,7 @@ import { pathToFileURL, fileURLToPath } from 'url';
7
7
  import { WorkflowRuntime } from '../lib/index.js';
8
8
  import { setup } from '../lib/setup.js';
9
9
  import { generateSessionToken } from '../lib/remote/client.js';
10
+ import { readRemotePathFromConfig, writeRemotePathToConfig } from '../lib/config-utils.js';
10
11
 
11
12
  import { startLocalServer } from '../vercel-server/local-server.js';
12
13
 
@@ -119,146 +120,6 @@ function resolveWorkflowEntry(workflowDir) {
119
120
  return null;
120
121
  }
121
122
 
122
- function findConfigObjectRange(source) {
123
- const match = source.match(/export\s+const\s+config\s*=/);
124
- if (!match) return null;
125
- const startSearch = match.index + match[0].length;
126
- const braceStart = source.indexOf('{', startSearch);
127
- if (braceStart === -1) return null;
128
-
129
- let depth = 0;
130
- let inString = null;
131
- let inLineComment = false;
132
- let inBlockComment = false;
133
- let escape = false;
134
-
135
- for (let i = braceStart; i < source.length; i += 1) {
136
- const ch = source[i];
137
- const next = source[i + 1];
138
-
139
- if (inLineComment) {
140
- if (ch === '\n') inLineComment = false;
141
- continue;
142
- }
143
-
144
- if (inBlockComment) {
145
- if (ch === '*' && next === '/') {
146
- inBlockComment = false;
147
- i += 1;
148
- }
149
- continue;
150
- }
151
-
152
- if (inString) {
153
- if (escape) {
154
- escape = false;
155
- continue;
156
- }
157
- if (ch === '\\') {
158
- escape = true;
159
- continue;
160
- }
161
- if (ch === inString) {
162
- inString = null;
163
- }
164
- continue;
165
- }
166
-
167
- if (ch === '"' || ch === '\'' || ch === '`') {
168
- inString = ch;
169
- continue;
170
- }
171
-
172
- if (ch === '/' && next === '/') {
173
- inLineComment = true;
174
- i += 1;
175
- continue;
176
- }
177
-
178
- if (ch === '/' && next === '*') {
179
- inBlockComment = true;
180
- i += 1;
181
- continue;
182
- }
183
-
184
- if (ch === '{') {
185
- depth += 1;
186
- } else if (ch === '}') {
187
- depth -= 1;
188
- if (depth === 0) {
189
- return { start: braceStart, end: i };
190
- }
191
- }
192
- }
193
-
194
- return null;
195
- }
196
-
197
- function readRemotePathFromConfig(configFile) {
198
- const source = fs.readFileSync(configFile, 'utf-8');
199
- const range = findConfigObjectRange(source);
200
- if (!range) return null;
201
- const configSource = source.slice(range.start, range.end + 1);
202
- const match = configSource.match(/\bremotePath\s*:\s*(['"`])([^'"`]+)\1/);
203
- return match ? match[2] : null;
204
- }
205
-
206
- function writeRemotePathToConfig(configFile, remotePath) {
207
- const source = fs.readFileSync(configFile, 'utf-8');
208
- const range = findConfigObjectRange(source);
209
- const remoteLine = `remotePath: "${remotePath}"`;
210
-
211
- if (!range) {
212
- const hasConfigExport = /export\s+const\s+config\s*=/.test(source);
213
- if (hasConfigExport) {
214
- throw new Error('Config export is not an object literal; add remotePath manually.');
215
- }
216
- const trimmed = source.replace(/\s*$/, '');
217
- const appended = `${trimmed}\n\nexport const config = {\n ${remoteLine}\n};\n`;
218
- fs.writeFileSync(configFile, appended);
219
- return;
220
- }
221
-
222
- const configSource = source.slice(range.start, range.end + 1);
223
- const remoteRegex = /\bremotePath\s*:\s*(['"`])([^'"`]*?)\1/;
224
- let updatedConfigSource;
225
-
226
- if (remoteRegex.test(configSource)) {
227
- updatedConfigSource = configSource.replace(remoteRegex, remoteLine);
228
- } else {
229
- const inner = configSource.slice(1, -1);
230
- const indentMatch = inner.match(/\n([ \t]+)\S/);
231
- const indent = indentMatch ? indentMatch[1] : ' ';
232
- const trimmedInner = inner.replace(/\s*$/, '');
233
- const hasContent = trimmedInner.trim().length > 0;
234
- let updatedInner = trimmedInner;
235
-
236
- if (hasContent) {
237
- for (let i = updatedInner.length - 1; i >= 0; i -= 1) {
238
- const ch = updatedInner[i];
239
- if (ch === ' ' || ch === '\t' || ch === '\n' || ch === '\r') continue;
240
- if (ch !== ',') {
241
- updatedInner += ',';
242
- }
243
- break;
244
- }
245
- }
246
-
247
- const needsNewline = updatedInner && !updatedInner.endsWith('\n');
248
- const insert = `${indent}${remoteLine},\n`;
249
- const newInner = hasContent
250
- ? `${updatedInner}${needsNewline ? '\n' : ''}${insert}`
251
- : `\n${insert}`;
252
- updatedConfigSource = `{${newInner}}`;
253
- }
254
-
255
- const updatedSource =
256
- source.slice(0, range.start) +
257
- updatedConfigSource +
258
- source.slice(range.end + 1);
259
- fs.writeFileSync(configFile, updatedSource);
260
- }
261
-
262
123
  function ensureRemotePath(configFile, { forceNew = false } = {}) {
263
124
  const existing = readRemotePathFromConfig(configFile);
264
125
  if (existing && !forceNew) return existing;
@@ -0,0 +1,259 @@
1
+ /**
2
+ * File: /lib/config-utils.js
3
+ */
4
+
5
+ import fs from 'fs';
6
+
7
+ export function findConfigObjectRange(source) {
8
+ const match = source.match(/export\s+const\s+config\s*=/);
9
+ if (!match) return null;
10
+ const startSearch = match.index + match[0].length;
11
+ const braceStart = source.indexOf('{', startSearch);
12
+ if (braceStart === -1) return null;
13
+
14
+ return findObjectLiteralRange(source, braceStart);
15
+ }
16
+
17
+ function findObjectLiteralRange(source, braceStart) {
18
+ let depth = 0;
19
+ let inString = null;
20
+ let inLineComment = false;
21
+ let inBlockComment = false;
22
+ let escape = false;
23
+
24
+ for (let i = braceStart; i < source.length; i += 1) {
25
+ const ch = source[i];
26
+ const next = source[i + 1];
27
+
28
+ if (inLineComment) {
29
+ if (ch === '\n') inLineComment = false;
30
+ continue;
31
+ }
32
+
33
+ if (inBlockComment) {
34
+ if (ch === '*' && next === '/') {
35
+ inBlockComment = false;
36
+ i += 1;
37
+ }
38
+ continue;
39
+ }
40
+
41
+ if (inString) {
42
+ if (escape) {
43
+ escape = false;
44
+ continue;
45
+ }
46
+ if (ch === '\\') {
47
+ escape = true;
48
+ continue;
49
+ }
50
+ if (ch === inString) {
51
+ inString = null;
52
+ }
53
+ continue;
54
+ }
55
+
56
+ if (ch === '"' || ch === '\'' || ch === '`') {
57
+ inString = ch;
58
+ continue;
59
+ }
60
+
61
+ if (ch === '/' && next === '/') {
62
+ inLineComment = true;
63
+ i += 1;
64
+ continue;
65
+ }
66
+
67
+ if (ch === '/' && next === '*') {
68
+ inBlockComment = true;
69
+ i += 1;
70
+ continue;
71
+ }
72
+
73
+ if (ch === '{') {
74
+ depth += 1;
75
+ } else if (ch === '}') {
76
+ depth -= 1;
77
+ if (depth === 0) {
78
+ return { start: braceStart, end: i };
79
+ }
80
+ }
81
+ }
82
+
83
+ return null;
84
+ }
85
+
86
+ function detectIndent(inner) {
87
+ const indentMatch = inner.match(/\n([ \t]+)\S/);
88
+ return indentMatch ? indentMatch[1] : ' ';
89
+ }
90
+
91
+ function escapeRegExp(value) {
92
+ return String(value).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
93
+ }
94
+
95
+ function isValidIdentifier(value) {
96
+ return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(value);
97
+ }
98
+
99
+ function formatObjectKey(value) {
100
+ if (isValidIdentifier(value)) return value;
101
+ return JSON.stringify(value);
102
+ }
103
+
104
+ function insertPropertyIntoObjectSource(objectSource, propertySource, indent) {
105
+ const inner = objectSource.slice(1, -1);
106
+ const trimmedInner = inner.replace(/\s*$/, '');
107
+ const hasContent = trimmedInner.trim().length > 0;
108
+ let updatedInner = trimmedInner;
109
+
110
+ if (hasContent) {
111
+ for (let i = updatedInner.length - 1; i >= 0; i -= 1) {
112
+ const ch = updatedInner[i];
113
+ if (ch === ' ' || ch === '\t' || ch === '\n' || ch === '\r') continue;
114
+ if (ch !== ',') {
115
+ updatedInner += ',';
116
+ }
117
+ break;
118
+ }
119
+ }
120
+
121
+ const needsNewline = updatedInner && !updatedInner.endsWith('\n');
122
+ const insert = `${indent}${propertySource},\n`;
123
+ const newInner = hasContent
124
+ ? `${updatedInner}${needsNewline ? '\n' : ''}${insert}`
125
+ : `\n${insert}`;
126
+
127
+ return `{${newInner}}`;
128
+ }
129
+
130
+ export function readRemotePathFromConfig(configFile) {
131
+ const source = fs.readFileSync(configFile, 'utf-8');
132
+ const range = findConfigObjectRange(source);
133
+ if (!range) return null;
134
+ const configSource = source.slice(range.start, range.end + 1);
135
+ const match = configSource.match(/\bremotePath\s*:\s*(['"`])([^'"`]+)\1/);
136
+ return match ? match[2] : null;
137
+ }
138
+
139
+ export function writeRemotePathToConfig(configFile, remotePath) {
140
+ const source = fs.readFileSync(configFile, 'utf-8');
141
+ const range = findConfigObjectRange(source);
142
+ const remoteLine = `remotePath: ${JSON.stringify(remotePath)}`;
143
+
144
+ if (!range) {
145
+ const hasConfigExport = /export\s+const\s+config\s*=/.test(source);
146
+ if (hasConfigExport) {
147
+ throw new Error('Config export is not an object literal; add remotePath manually.');
148
+ }
149
+ const trimmed = source.replace(/\s*$/, '');
150
+ const appended = `${trimmed}\n\nexport const config = {\n ${remoteLine}\n};\n`;
151
+ fs.writeFileSync(configFile, appended);
152
+ return;
153
+ }
154
+
155
+ const configSource = source.slice(range.start, range.end + 1);
156
+ const remoteRegex = /\bremotePath\s*:\s*(['"`])([^'"`]*?)\1/;
157
+ let updatedConfigSource;
158
+
159
+ if (remoteRegex.test(configSource)) {
160
+ updatedConfigSource = configSource.replace(remoteRegex, remoteLine);
161
+ } else {
162
+ const inner = configSource.slice(1, -1);
163
+ const indent = detectIndent(inner);
164
+ updatedConfigSource = insertPropertyIntoObjectSource(configSource, remoteLine, indent);
165
+ }
166
+
167
+ const updatedSource =
168
+ source.slice(0, range.start) +
169
+ updatedConfigSource +
170
+ source.slice(range.end + 1);
171
+ fs.writeFileSync(configFile, updatedSource);
172
+ }
173
+
174
+ function findModelsObjectRange(configSource) {
175
+ const regex = /\bmodels\s*:\s*{/g;
176
+ const match = regex.exec(configSource);
177
+ if (!match) return null;
178
+ const braceStart = configSource.indexOf('{', match.index + match[0].length - 1);
179
+ if (braceStart === -1) return null;
180
+ return findObjectLiteralRange(configSource, braceStart);
181
+ }
182
+
183
+ function buildModelKeyRegex(modelKey) {
184
+ const escaped = escapeRegExp(modelKey);
185
+ const quotedKey = `['"\\\`]${escaped}['"\\\`]`;
186
+ const unquotedKey = isValidIdentifier(modelKey) ? escaped : null;
187
+ const keyPattern = unquotedKey ? `(?:${unquotedKey}|${quotedKey})` : quotedKey;
188
+ return new RegExp(`(^|[,{\\s])(${keyPattern})\\s*:\\s*(['"\\\`])([^'"\\\\\`]*?)\\3`, 'm');
189
+ }
190
+
191
+ export function readModelFromConfig(configFile, modelKey) {
192
+ const source = fs.readFileSync(configFile, 'utf-8');
193
+ const range = findConfigObjectRange(source);
194
+ if (!range) return null;
195
+ const configSource = source.slice(range.start, range.end + 1);
196
+ const modelsRange = findModelsObjectRange(configSource);
197
+ if (!modelsRange) return null;
198
+ const modelsSource = configSource.slice(modelsRange.start, modelsRange.end + 1);
199
+ const keyRegex = buildModelKeyRegex(modelKey);
200
+ const match = modelsSource.match(keyRegex);
201
+ return match ? match[4] : null;
202
+ }
203
+
204
+ export function writeModelToConfig(configFile, modelKey, modelValue) {
205
+ const source = fs.readFileSync(configFile, 'utf-8');
206
+ const range = findConfigObjectRange(source);
207
+ const formattedKey = formatObjectKey(modelKey);
208
+ const serializedValue = JSON.stringify(modelValue);
209
+
210
+ if (!range) {
211
+ const hasConfigExport = /export\s+const\s+config\s*=/.test(source);
212
+ if (hasConfigExport) {
213
+ throw new Error('Config export is not an object literal; add models mapping manually.');
214
+ }
215
+ const trimmed = source.replace(/\s*$/, '');
216
+ const appended = `${trimmed}\n\nexport const config = {\n models: {\n ${formattedKey}: ${serializedValue}\n }\n};\n`;
217
+ fs.writeFileSync(configFile, appended);
218
+ return;
219
+ }
220
+
221
+ const configSource = source.slice(range.start, range.end + 1);
222
+ const modelsRange = findModelsObjectRange(configSource);
223
+ let updatedConfigSource = configSource;
224
+
225
+ if (!modelsRange) {
226
+ const inner = configSource.slice(1, -1);
227
+ const indent = detectIndent(inner);
228
+ const nestedIndent = `${indent} `;
229
+ const modelsBlock = `models: {\n${nestedIndent}${formattedKey}: ${serializedValue},\n${indent}}`;
230
+ updatedConfigSource = insertPropertyIntoObjectSource(configSource, modelsBlock, indent);
231
+ } else {
232
+ const modelsSource = configSource.slice(modelsRange.start, modelsRange.end + 1);
233
+ const keyRegex = buildModelKeyRegex(modelKey);
234
+ let updatedModelsSource;
235
+
236
+ if (keyRegex.test(modelsSource)) {
237
+ updatedModelsSource = modelsSource.replace(
238
+ keyRegex,
239
+ (_match, prefix, keyToken) => `${prefix}${keyToken}: ${serializedValue}`
240
+ );
241
+ } else {
242
+ const inner = modelsSource.slice(1, -1);
243
+ const indent = detectIndent(inner);
244
+ const modelLine = `${formattedKey}: ${serializedValue}`;
245
+ updatedModelsSource = insertPropertyIntoObjectSource(modelsSource, modelLine, indent);
246
+ }
247
+
248
+ updatedConfigSource =
249
+ configSource.slice(0, modelsRange.start) +
250
+ updatedModelsSource +
251
+ configSource.slice(modelsRange.end + 1);
252
+ }
253
+
254
+ const updatedSource =
255
+ source.slice(0, range.start) +
256
+ updatedConfigSource +
257
+ source.slice(range.end + 1);
258
+ fs.writeFileSync(configFile, updatedSource);
259
+ }
package/lib/llm.js CHANGED
@@ -7,6 +7,8 @@ import path from 'path';
7
7
  import os from 'os';
8
8
  import { spawn, execSync } from 'child_process';
9
9
  import { createRequire } from 'module';
10
+ import { getCurrentRuntime } from './runtime/runtime.js';
11
+ import { resolveUnknownModel } from './runtime/model-resolution.js';
10
12
 
11
13
  const require = createRequire(import.meta.url);
12
14
 
@@ -419,13 +421,29 @@ export async function llm(context, options) {
419
421
  const apiKeys = config.apiKeys || {};
420
422
 
421
423
  // Look up the model command/config
422
- const modelConfig = models[options.model];
424
+ let modelConfig = models[options.model];
423
425
 
424
426
  if (!modelConfig) {
425
- const available = Object.keys(models).join(', ');
426
- throw new Error(
427
- `Unknown model key: "${options.model}". Available models: ${available || 'none defined'}`
428
- );
427
+ const runtime = getCurrentRuntime();
428
+ if (runtime) {
429
+ const workflowDir = options.workflowDir || config.workflowDir || runtime.workflowDir;
430
+ if (!workflowDir) {
431
+ throw new Error(`Unknown model key: "${options.model}". Workflow directory is missing.`);
432
+ }
433
+ modelConfig = await resolveUnknownModel(options.model, config, workflowDir, {
434
+ availableCLIs: detectAvailableCLIs()
435
+ });
436
+ if (!config.models) {
437
+ config.models = models;
438
+ }
439
+ config.models[options.model] = modelConfig;
440
+ runtime.workflowConfig.models[options.model] = modelConfig;
441
+ } else {
442
+ const available = Object.keys(models).join(', ');
443
+ throw new Error(
444
+ `Unknown model key: "${options.model}". Available models: ${available || 'none defined'}`
445
+ );
446
+ }
429
447
  }
430
448
 
431
449
  // Build the full prompt
@@ -0,0 +1,128 @@
1
+ /**
2
+ * File: /lib/runtime/model-resolution.js
3
+ */
4
+
5
+ import fs from 'fs';
6
+ import path from 'path';
7
+ import { askHuman } from './prompt.js';
8
+ import { createInteraction, parseInteractionResponse } from './interaction.js';
9
+ import { readModelFromConfig, writeModelToConfig } from '../config-utils.js';
10
+
11
+ function sanitizeSlug(value) {
12
+ return String(value)
13
+ .toLowerCase()
14
+ .replace(/[^a-z0-9]+/g, '-')
15
+ .replace(/^-+|-+$/g, '')
16
+ .substring(0, 40) || 'model';
17
+ }
18
+
19
+ export function buildModelSuggestions(availableCLIs = {}, existingModels = {}) {
20
+ const options = [];
21
+ const lookup = {};
22
+
23
+ const addOption = (key, label, description, value) => {
24
+ options.push({ key, label, description });
25
+ lookup[key] = value;
26
+ };
27
+
28
+ Object.entries(existingModels).forEach(([key, value]) => {
29
+ const optionKey = `existing:${key}`;
30
+ const description = value ? `Maps to "${value}"` : 'Existing model mapping';
31
+ addOption(optionKey, `Use existing model: ${key}`, description, value);
32
+ });
33
+
34
+ if (availableCLIs.claude) {
35
+ addOption('cli:claude', 'claude -p', 'Claude CLI (print mode)', 'claude -p');
36
+ }
37
+ if (availableCLIs.gemini) {
38
+ addOption('cli:gemini', 'gemini', 'Gemini CLI', 'gemini');
39
+ }
40
+ if (availableCLIs.codex) {
41
+ addOption('cli:codex', 'codex', 'Codex CLI (exec)', 'codex');
42
+ }
43
+ if (availableCLIs.ollama) {
44
+ addOption('cli:ollama', 'ollama run llama3.1', 'Ollama CLI (example model)', 'ollama run llama3.1');
45
+ }
46
+
47
+ addOption(
48
+ 'api:openai',
49
+ 'api:openai:gpt-4.1-mini',
50
+ 'OpenAI API (example model)',
51
+ 'api:openai:gpt-4.1-mini'
52
+ );
53
+ addOption(
54
+ 'api:anthropic',
55
+ 'api:anthropic:claude-3-5-sonnet-20241022',
56
+ 'Anthropic API (example model)',
57
+ 'api:anthropic:claude-3-5-sonnet-20241022'
58
+ );
59
+
60
+ return { options, lookup };
61
+ }
62
+
63
+ export async function promptForModelConfig(modelKey, existingModels = {}, availableCLIs = {}) {
64
+ const existingKeys = Object.keys(existingModels);
65
+ const existingSummary = existingKeys.length > 0
66
+ ? `Existing models: ${existingKeys.join(', ')}`
67
+ : 'No models configured yet.';
68
+
69
+ const { options, lookup } = buildModelSuggestions(availableCLIs, existingModels);
70
+ const prompt = `Unknown model key: "${modelKey}"\n\nHow would you like to configure this model?\n${existingSummary}`;
71
+ const slug = `model-${sanitizeSlug(modelKey)}`;
72
+ const interaction = createInteraction('choice', slug, {
73
+ prompt,
74
+ options,
75
+ allowCustom: true,
76
+ multiSelect: false
77
+ });
78
+
79
+ const answer = await askHuman(prompt, { slug, interaction });
80
+ const parsed = await parseInteractionResponse(interaction, answer);
81
+
82
+ if (parsed.isCustom && parsed.customText) {
83
+ return parsed.customText.trim();
84
+ }
85
+
86
+ if (parsed.selectedKey) {
87
+ const mapped = lookup[parsed.selectedKey];
88
+ if (mapped) return mapped;
89
+ return String(parsed.selectedKey).trim();
90
+ }
91
+
92
+ if (typeof parsed.text === 'string' && parsed.text.trim()) {
93
+ return parsed.text.trim();
94
+ }
95
+
96
+ if (typeof parsed.raw === 'string' && parsed.raw.trim()) {
97
+ return parsed.raw.trim();
98
+ }
99
+
100
+ throw new Error('No model configuration provided.');
101
+ }
102
+
103
+ export async function resolveUnknownModel(modelKey, config, workflowDir, options = {}) {
104
+ if (!workflowDir) {
105
+ throw new Error('Cannot resolve model without a workflow directory.');
106
+ }
107
+
108
+ const configFile = path.join(workflowDir, 'config.js');
109
+ if (!fs.existsSync(configFile)) {
110
+ throw new Error(`config.js not found in ${workflowDir}`);
111
+ }
112
+
113
+ const existing = readModelFromConfig(configFile, modelKey);
114
+ if (existing) {
115
+ return existing;
116
+ }
117
+
118
+ const existingModels = config?.models || {};
119
+ const availableCLIs = options.availableCLIs || {};
120
+ const modelValue = await promptForModelConfig(modelKey, existingModels, availableCLIs);
121
+
122
+ if (!modelValue) {
123
+ throw new Error('Model configuration cannot be empty.');
124
+ }
125
+
126
+ writeModelToConfig(configFile, modelKey, modelValue);
127
+ return modelValue;
128
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-state-machine",
3
- "version": "2.1.8",
3
+ "version": "2.1.9",
4
4
  "type": "module",
5
5
  "description": "A workflow orchestrator for running agents and scripts in sequence with state management",
6
6
  "main": "lib/index.js",
@@ -9,14 +9,6 @@ response: choice
9
9
 
10
10
  You are an assumptions and constraints analyst. Your job is to identify and validate assumptions before development.
11
11
 
12
- ## Context
13
- Project Description: {{projectDescription}}
14
- Scope: {{scope}}
15
- Requirements: {{requirements}}
16
- {{#if previousResponse}}
17
- User's Previous Response: {{previousResponse}}
18
- {{/if}}
19
-
20
12
  ## Instructions
21
13
 
22
14
  Identify implicit assumptions that could impact the project. Consider:
@@ -7,14 +7,6 @@ format: json
7
7
 
8
8
  You are a senior code reviewer. Review implementations for quality, correctness, and best practices.
9
9
 
10
- ## Context
11
- Task: {{task}}
12
- Implementation: {{implementation}}
13
- Test Plan: {{testPlan}}
14
- {{#if feedback}}
15
- Previous Feedback: {{feedback}}
16
- {{/if}}
17
-
18
10
  ## Instructions
19
11
 
20
12
  Perform a thorough code review covering:
@@ -7,16 +7,6 @@ format: json
7
7
 
8
8
  You are a senior software developer. Implement the task according to specifications.
9
9
 
10
- ## Context
11
- Task: {{task}}
12
- Phase: {{phase}}
13
- Requirements: {{requirements}}
14
- Test Plan: {{testPlan}}
15
- Security Considerations: {{securityConsiderations}}
16
- {{#if feedback}}
17
- Previous Feedback (IMPORTANT - address these issues): {{feedback}}
18
- {{/if}}
19
-
20
10
  ## Instructions
21
11
 
22
12
  Implement the task following these principles:
@@ -9,13 +9,6 @@ response: choice
9
9
 
10
10
  You are a requirements analysis specialist. Your job is to gather and clarify functional and non-functional requirements.
11
11
 
12
- ## Context
13
- Project Description: {{projectDescription}}
14
- Scope: {{scope}}
15
- {{#if previousResponse}}
16
- User's Previous Response: {{previousResponse}}
17
- {{/if}}
18
-
19
12
  ## Instructions
20
13
 
21
14
  Based on the project description and scope, identify requirements that need clarification. Consider:
@@ -7,16 +7,6 @@ format: json
7
7
 
8
8
  You are a project planning specialist. Generate a phased development roadmap as structured JSON.
9
9
 
10
- ## Context
11
- Project Description: {{projectDescription}}
12
- Scope: {{scope}}
13
- Requirements: {{requirements}}
14
- Assumptions: {{assumptions}}
15
- Security: {{security}}
16
- {{#if feedback}}
17
- User Feedback: {{feedback}}
18
- {{/if}}
19
-
20
10
  ## Instructions
21
11
 
22
12
  Create a phased roadmap as a JSON object. Each phase should:
@@ -9,12 +9,6 @@ response: choice
9
9
 
10
10
  You are a project scope clarification specialist. Your job is to ensure the project scope is well-defined before development begins.
11
11
 
12
- ## Context
13
- Project Description: {{projectDescription}}
14
- {{#if previousResponse}}
15
- User's Previous Response: {{previousResponse}}
16
- {{/if}}
17
-
18
12
  ## Instructions
19
13
 
20
14
  Analyze the project description and determine if the scope is clear. Consider: