@caiqueoak/flow 0.2.1 → 0.3.1
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 +38 -100
- package/package.json +1 -2
- package/skills/flow/SKILL.md +147 -0
- package/skills/flow/references/build.md +7 -0
- package/skills/flow/references/discovery.md +15 -0
- package/skills/flow/references/planning.md +7 -0
- package/skills/flow/references/reconcile.md +5 -0
- package/skills/flow/references/review.md +5 -0
- package/src/cli.mjs +234 -75
- package/skills/flow-build/SKILL.md +0 -73
- package/skills/flow-new/SKILL.md +0 -104
- package/skills/flow-next/SKILL.md +0 -55
- package/skills/flow-plan/SKILL.md +0 -86
- package/skills/flow-review/SKILL.md +0 -47
- package/skills/flow-status/SKILL.md +0 -26
- package/templates/BACKLOG.yaml +0 -15
- package/templates/DECISIONS.yaml +0 -18
- package/templates/ENGINEERING.md +0 -35
- package/templates/PRD.md +0 -23
- package/templates/STATE.yaml +0 -11
- package/templates/SUMMARY.md +0 -29
- package/templates/TASKS.yaml +0 -13
- package/templates/WORK_ITEM_SPEC.md +0 -29
- package/templates/config.yaml +0 -29
- package/templates/gates/README.md +0 -19
package/src/cli.mjs
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import fs from 'node:fs';
|
|
4
|
-
import os from 'node:os';
|
|
5
4
|
import path from 'node:path';
|
|
6
5
|
import { execFileSync } from 'node:child_process';
|
|
7
6
|
import { fileURLToPath } from 'node:url';
|
|
7
|
+
import readline from 'node:readline/promises';
|
|
8
|
+
import { stdin as inputStream, stdout as outputStream } from 'node:process';
|
|
8
9
|
|
|
9
10
|
const __filename = fileURLToPath(import.meta.url);
|
|
10
11
|
const __dirname = path.dirname(__filename);
|
|
@@ -14,116 +15,274 @@ const VERSION = PACKAGE.version;
|
|
|
14
15
|
const PACKAGE_NAME = PACKAGE.name;
|
|
15
16
|
const args = process.argv.slice(2);
|
|
16
17
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
}
|
|
18
|
+
const RUNTIME_DEFINITIONS = {
|
|
19
|
+
codex: { label: 'Codex', skillsPath: '.codex/skills' },
|
|
20
|
+
claude: { label: 'Claude Code', skillsPath: '.claude/skills' }
|
|
21
|
+
};
|
|
21
22
|
|
|
22
|
-
function
|
|
23
|
+
function fail(message, code = 1) { console.error(`flow: ${message}`); process.exit(code); }
|
|
24
|
+
function info(message = '') { console.log(message); }
|
|
23
25
|
function hasFlag(name) { return args.includes(name); }
|
|
24
26
|
function valueAfter(name) { const i = args.indexOf(name); return i >= 0 ? args[i + 1] : undefined; }
|
|
25
27
|
function npmCommand() { return process.platform === 'win32' ? 'npm.cmd' : 'npm'; }
|
|
28
|
+
function projectRoot() { return path.resolve(valueAfter('--path') || process.cwd()); }
|
|
29
|
+
function configPath(root) { return path.join(root, '.flow', 'config.yaml'); }
|
|
30
|
+
function packagePath(root) { return path.join(root, 'node_modules', '@caiqueoak', 'flow'); }
|
|
31
|
+
|
|
32
|
+
function abortedPromptError() {
|
|
33
|
+
const error = new Error('Prompt aborted.');
|
|
34
|
+
error.code = 'ABORT_ERR';
|
|
35
|
+
return error;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function question(rl, message) {
|
|
39
|
+
return new Promise((resolve, reject) => {
|
|
40
|
+
let settled = false;
|
|
41
|
+
const finish = (callback, value) => {
|
|
42
|
+
if (settled) return;
|
|
43
|
+
settled = true;
|
|
44
|
+
rl.removeListener('close', onClose);
|
|
45
|
+
callback(value);
|
|
46
|
+
};
|
|
47
|
+
const onClose = () => finish(reject, abortedPromptError());
|
|
48
|
+
rl.once('close', onClose);
|
|
49
|
+
rl.question(message).then(
|
|
50
|
+
(answer) => finish(resolve, answer),
|
|
51
|
+
(error) => finish(reject, error)
|
|
52
|
+
);
|
|
53
|
+
});
|
|
54
|
+
}
|
|
26
55
|
|
|
27
|
-
function
|
|
56
|
+
function runNpm(npmArgs, options = {}) {
|
|
57
|
+
return execFileSync(npmCommand(), npmArgs, {
|
|
58
|
+
...options,
|
|
59
|
+
shell: process.platform === 'win32'
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function copyDir(source, target) {
|
|
28
64
|
fs.mkdirSync(target, { recursive: true });
|
|
29
65
|
for (const entry of fs.readdirSync(source, { withFileTypes: true })) {
|
|
30
66
|
const src = path.join(source, entry.name);
|
|
31
67
|
const dst = path.join(target, entry.name);
|
|
32
|
-
if (entry.isDirectory()) copyDir(src, dst
|
|
33
|
-
else
|
|
68
|
+
if (entry.isDirectory()) copyDir(src, dst);
|
|
69
|
+
else fs.copyFileSync(src, dst);
|
|
34
70
|
}
|
|
35
71
|
}
|
|
36
72
|
|
|
37
|
-
function
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
path.join(cwd, '.claude', 'skills'),
|
|
41
|
-
path.join(os.homedir(), '.agents', 'skills'),
|
|
42
|
-
path.join(os.homedir(), '.claude', 'skills')
|
|
43
|
-
];
|
|
44
|
-
return [...new Set(candidates)].filter((candidate) => fs.existsSync(candidate));
|
|
73
|
+
function quoteYaml(value) { return /^[A-Za-z0-9_.\/-]+$/.test(value) ? value : JSON.stringify(value); }
|
|
74
|
+
function defaultConfig() {
|
|
75
|
+
return { frameworkVersion: VERSION, runtimes: [], continueAcrossWorkItems: true, workItemsConcurrency: 'auto', taskConcurrency: 'auto' };
|
|
45
76
|
}
|
|
46
77
|
|
|
47
|
-
function
|
|
48
|
-
const
|
|
49
|
-
if (
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
78
|
+
function readConfig(root) {
|
|
79
|
+
const file = configPath(root);
|
|
80
|
+
if (!fs.existsSync(file)) return null;
|
|
81
|
+
const text = fs.readFileSync(file, 'utf8');
|
|
82
|
+
const cfg = defaultConfig();
|
|
83
|
+
const v = text.match(/^[ \t]*version:[ \t]*([^\s#]+)[ \t]*$/m);
|
|
84
|
+
if (v) cfg.frameworkVersion = v[1].replace(/^['"]|['"]$/g, '');
|
|
85
|
+
const runtimeBlock = text.match(/^runtimes:\s*\n([\s\S]*?)(?=^[A-Za-z_][A-Za-z0-9_]*:|\Z)/m)?.[1] || '';
|
|
86
|
+
const entries = runtimeBlock.split(/(?=^\s*-\s+type:)/m).filter((x) => /-\s+type:/.test(x));
|
|
87
|
+
for (const entry of entries) {
|
|
88
|
+
const type = entry.match(/-\s+type:\s*([^\s#]+)/)?.[1]?.replace(/^['"]|['"]$/g, '');
|
|
89
|
+
const skillsPath = entry.match(/skills_path:\s*([^\n#]+)/)?.[1]?.trim().replace(/^['"]|['"]$/g, '');
|
|
90
|
+
if (type && skillsPath) cfg.runtimes.push({ type, skills_path: skillsPath });
|
|
91
|
+
}
|
|
92
|
+
return cfg;
|
|
54
93
|
}
|
|
55
94
|
|
|
56
|
-
function
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
95
|
+
function writeConfig(root, config) {
|
|
96
|
+
const lines = ['schema_version: 1', 'framework:', ' name: flow', ` version: ${VERSION}`, 'runtimes:'];
|
|
97
|
+
if (!config.runtimes.length) lines.push(' []');
|
|
98
|
+
else for (const runtime of config.runtimes) {
|
|
99
|
+
lines.push(` - type: ${quoteYaml(runtime.type)}`);
|
|
100
|
+
lines.push(` skills_path: ${quoteYaml(runtime.skills_path)}`);
|
|
101
|
+
}
|
|
102
|
+
lines.push('autonomy:', ' continue_across_work_items: true', ' stop_on:', ' - consequential_decision', ' - external_approval', ' - unrecoverable_blocker', ' - no_ready_work', 'parallelism:', ' strategy: maximum_safe', ' max_concurrent_work_items: auto', ' max_concurrent_tasks_per_work_item: auto', 'efficiency:', ' token_usage: optimize', ' prefer_primary_orchestrator: true', ' delegate_only_when_beneficial: true', '');
|
|
103
|
+
fs.mkdirSync(path.join(root, '.flow'), { recursive: true });
|
|
104
|
+
fs.writeFileSync(configPath(root), lines.join('\n'), 'utf8');
|
|
66
105
|
}
|
|
67
106
|
|
|
68
|
-
function
|
|
69
|
-
const target =
|
|
70
|
-
|
|
71
|
-
copyDir(path.join(ROOT, 'skills'), target, { overwrite: force });
|
|
72
|
-
info(`Installed Flow ${VERSION} skills to ${target}`);
|
|
73
|
-
if (!force) info('Existing skill files were preserved. Use --force to replace them.');
|
|
107
|
+
function installRuntimeSkill(root, runtime, packageRoot = ROOT) {
|
|
108
|
+
const target = path.join(root, runtime.skills_path, 'flow');
|
|
109
|
+
copyDir(path.join(packageRoot, 'skills', 'flow'), target);
|
|
74
110
|
return target;
|
|
75
111
|
}
|
|
112
|
+
function parseRuntimeFlag() {
|
|
113
|
+
const raw = valueAfter('--runtime');
|
|
114
|
+
return raw ? raw.split(',').map((x) => x.trim().toLowerCase()).filter(Boolean) : null;
|
|
115
|
+
}
|
|
76
116
|
|
|
77
|
-
function
|
|
117
|
+
function missingBuiltinRuntimes(existing) {
|
|
118
|
+
const existingTypes = new Set(existing.map((r) => r.type));
|
|
119
|
+
return Object.keys(RUNTIME_DEFINITIONS).filter((type) => !existingTypes.has(type));
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async function promptMultiSelect(existing) {
|
|
123
|
+
const existingTypes = new Set(existing.map((r) => r.type));
|
|
124
|
+
const options = Object.entries(RUNTIME_DEFINITIONS).filter(([type]) => !existingTypes.has(type)).map(([value, def]) => ({ value, label: def.label }));
|
|
125
|
+
options.push({ value: 'custom', label: 'Custom coding agent / skills path' });
|
|
126
|
+
info(existing.length ? 'Select coding agents to add:' : 'Select coding agents:');
|
|
127
|
+
options.forEach((opt, i) => info(` [ ] ${i + 1}. ${opt.label}`));
|
|
128
|
+
info(' (Select multiple with comma-separated numbers, e.g. 1,2)');
|
|
129
|
+
const rl = readline.createInterface({ input: inputStream, output: outputStream });
|
|
78
130
|
try {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
131
|
+
while (true) {
|
|
132
|
+
const answer = (await question(rl, 'Selection: ')).trim();
|
|
133
|
+
const indices = [...new Set(answer.split(',').map((v) => Number.parseInt(v.trim(), 10)).filter(Number.isInteger))];
|
|
134
|
+
if (indices.length && indices.every((n) => n >= 1 && n <= options.length)) return indices.map((n) => options[n - 1].value);
|
|
135
|
+
info('Choose one or more valid numbers.');
|
|
136
|
+
}
|
|
137
|
+
} finally { rl.close(); }
|
|
83
138
|
}
|
|
84
139
|
|
|
85
|
-
function
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
140
|
+
async function promptText(message, defaultValue = '') {
|
|
141
|
+
const rl = readline.createInterface({ input: inputStream, output: outputStream });
|
|
142
|
+
try {
|
|
143
|
+
const answer = (await question(rl, `${message}${defaultValue ? ` [${defaultValue}]` : ''}: `)).trim();
|
|
144
|
+
return answer || defaultValue;
|
|
145
|
+
} finally { rl.close(); }
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
async function resolveRuntime(type, existing) {
|
|
149
|
+
if (RUNTIME_DEFINITIONS[type]) return { type, skills_path: RUNTIME_DEFINITIONS[type].skillsPath };
|
|
150
|
+
if (type !== 'custom') fail(`unsupported runtime '${type}'. Use codex, claude, or custom.`);
|
|
151
|
+
const fallback = `custom-${existing.filter((r) => r.type.startsWith('custom')).length + 1}`;
|
|
152
|
+
const name = await promptText('Custom coding agent id', fallback);
|
|
153
|
+
while (true) {
|
|
154
|
+
const skillsPath = await promptText('Project-local skills directory', `.${name}/skills`);
|
|
155
|
+
if (!path.isAbsolute(skillsPath) && !skillsPath.split(/[\\/]/).includes('..')) return { type: name, skills_path: skillsPath };
|
|
156
|
+
info('Skills path must be relative and remain inside the project.');
|
|
91
157
|
}
|
|
92
|
-
return 0;
|
|
93
158
|
}
|
|
94
159
|
|
|
95
|
-
function
|
|
96
|
-
const
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
info(
|
|
160
|
+
async function initProject() {
|
|
161
|
+
const root = projectRoot();
|
|
162
|
+
const flowDir = path.join(root, '.flow');
|
|
163
|
+
const existed = fs.existsSync(flowDir);
|
|
164
|
+
const config = readConfig(root) || defaultConfig();
|
|
165
|
+
if (existed) {
|
|
166
|
+
info('Flow project already exists. Canonical project artifacts will not be created or modified.');
|
|
167
|
+
if (config.runtimes.length) info(`Configured coding agents: ${config.runtimes.map((r) => r.type).join(', ')}`);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const requested = parseRuntimeFlag();
|
|
171
|
+
if (existed && !requested && missingBuiltinRuntimes(config.runtimes).length === 0) {
|
|
172
|
+
info('All built-in coding agents are already configured. Nothing to add.');
|
|
173
|
+
info('Use --runtime custom only when you intentionally want to add a custom coding agent.');
|
|
102
174
|
return;
|
|
103
175
|
}
|
|
104
176
|
|
|
105
|
-
|
|
106
|
-
const
|
|
177
|
+
const selected = requested || await promptMultiSelect(config.runtimes);
|
|
178
|
+
const knownTypes = new Set(config.runtimes.map((r) => r.type));
|
|
179
|
+
const added = [];
|
|
180
|
+
for (const type of selected) {
|
|
181
|
+
if (knownTypes.has(type)) continue;
|
|
182
|
+
const runtime = await resolveRuntime(type, config.runtimes);
|
|
183
|
+
if (knownTypes.has(runtime.type)) continue;
|
|
184
|
+
config.runtimes.push(runtime); knownTypes.add(runtime.type); added.push(runtime);
|
|
185
|
+
}
|
|
186
|
+
fs.mkdirSync(flowDir, { recursive: true });
|
|
187
|
+
writeConfig(root, config);
|
|
188
|
+
for (const runtime of added) info(`✓ ${runtime.type}: ${path.relative(root, installRuntimeSkill(root, runtime))}`);
|
|
189
|
+
if (!added.length) info('No new coding-agent integration was added.');
|
|
190
|
+
else { info(); info('Flow is ready. Open a configured coding agent and invoke /flow.'); }
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function dependencySection(root) {
|
|
194
|
+
const file = path.join(root, 'package.json');
|
|
195
|
+
if (!fs.existsSync(file)) return '--save-dev';
|
|
196
|
+
try {
|
|
197
|
+
const pkg = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
198
|
+
if (pkg.dependencies?.[PACKAGE_NAME]) return '--save';
|
|
199
|
+
if (pkg.optionalDependencies?.[PACKAGE_NAME]) return '--save-optional';
|
|
200
|
+
} catch { /* use default */ }
|
|
201
|
+
return '--save-dev';
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function containingNodeModules(packageRoot) {
|
|
205
|
+
let current = path.resolve(packageRoot);
|
|
206
|
+
while (true) {
|
|
207
|
+
if (path.basename(current).toLowerCase() === 'node_modules') return current;
|
|
208
|
+
const parent = path.dirname(current);
|
|
209
|
+
if (parent === current) return null;
|
|
210
|
+
current = parent;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function updatePlan(root) {
|
|
215
|
+
const projectPackageRoot = packagePath(root);
|
|
216
|
+
if (fs.existsSync(path.join(projectPackageRoot, 'package.json'))) {
|
|
217
|
+
return {
|
|
218
|
+
npmArgs: ['install', dependencySection(root), `${PACKAGE_NAME}@latest`],
|
|
219
|
+
cwd: root,
|
|
220
|
+
packageRoot: projectPackageRoot,
|
|
221
|
+
mode: 'project'
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
|
|
107
225
|
try {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
226
|
+
const globalNodeModules = path.resolve(runNpm(['root', '--global'], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim());
|
|
227
|
+
const globalPackageRoot = path.join(globalNodeModules, '@caiqueoak', 'flow');
|
|
228
|
+
if (path.resolve(ROOT).startsWith(`${globalNodeModules}${path.sep}`) && fs.existsSync(path.join(globalPackageRoot, 'package.json'))) {
|
|
229
|
+
return {
|
|
230
|
+
npmArgs: ['install', '--global', `${PACKAGE_NAME}@latest`],
|
|
231
|
+
cwd: root,
|
|
232
|
+
packageRoot: globalPackageRoot,
|
|
233
|
+
mode: 'global'
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
} catch { /* fall through to local installation detection */ }
|
|
237
|
+
|
|
238
|
+
const nodeModules = containingNodeModules(ROOT);
|
|
239
|
+
if (nodeModules) {
|
|
240
|
+
const installRoot = path.dirname(nodeModules);
|
|
241
|
+
return {
|
|
242
|
+
npmArgs: ['install', dependencySection(installRoot), `${PACKAGE_NAME}@latest`],
|
|
243
|
+
cwd: installRoot,
|
|
244
|
+
packageRoot: ROOT,
|
|
245
|
+
mode: 'local'
|
|
246
|
+
};
|
|
111
247
|
}
|
|
112
|
-
|
|
113
|
-
|
|
248
|
+
|
|
249
|
+
fail('cannot determine how this Flow CLI was installed. Reinstall @caiqueoak/flow with npm, then run flow update again.');
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function update() {
|
|
253
|
+
const root = projectRoot();
|
|
254
|
+
const config = readConfig(root);
|
|
255
|
+
if (!config) fail('this project is not initialized. Run flow init first.');
|
|
256
|
+
if (!config.runtimes.length) fail('no coding agents are configured. Run flow init to add one.');
|
|
257
|
+
|
|
258
|
+
const plan = updatePlan(root);
|
|
259
|
+
info(`Updating ${PACKAGE_NAME} (${plan.mode} installation)...`);
|
|
260
|
+
try { runNpm(plan.npmArgs, { cwd: plan.cwd, stdio: 'inherit' }); }
|
|
261
|
+
catch { fail('npm update failed. Existing project state and installed skills were not intentionally removed.'); }
|
|
262
|
+
|
|
263
|
+
if (!fs.existsSync(path.join(plan.packageRoot, 'package.json'))) fail(`updated package not found at ${plan.packageRoot}.`);
|
|
264
|
+
const latest = JSON.parse(fs.readFileSync(path.join(plan.packageRoot, 'package.json'), 'utf8'));
|
|
265
|
+
for (const runtime of config.runtimes) info(`✓ ${runtime.type}: ${path.relative(root, installRuntimeSkill(root, runtime, plan.packageRoot))}`);
|
|
266
|
+
const text = fs.readFileSync(configPath(root), 'utf8');
|
|
267
|
+
fs.writeFileSync(configPath(root), text.replace(/(^framework:\s*\n(?:.*\n)*?\s+version:\s*)[^\n]+/m, `$1${latest.version}`), 'utf8');
|
|
268
|
+
info(`Flow updated to ${latest.version}.`);
|
|
114
269
|
}
|
|
115
270
|
|
|
116
271
|
function help() {
|
|
117
|
-
info(`Flow ${VERSION}\n\nUsage:\n flow init [--path <project>] [--
|
|
272
|
+
info(`Flow ${VERSION}\n\nUsage:\n flow init [--path <project>] [--runtime codex,claude]\n flow update [--path <project>]\n flow --version\n\nflow init creates only .flow/config.yaml and installs the project-local /flow skill for selected coding agents.\nIf .flow already exists, init only adds coding-agent integrations and exits without prompting when all built-in integrations are already configured.\nflow update updates the installation that provides the Flow CLI (project-local or global) and refreshes every configured project-local skill.\nThere is no flow install command and no automatic/background update mechanism.`);
|
|
118
273
|
}
|
|
119
274
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
else
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
275
|
+
try {
|
|
276
|
+
if (!args.length || hasFlag('--help') || hasFlag('-h')) help();
|
|
277
|
+
else if (hasFlag('--version') || hasFlag('-v')) info(VERSION);
|
|
278
|
+
else if (args[0] === 'init') await initProject();
|
|
279
|
+
else if (args[0] === 'update') update();
|
|
280
|
+
else fail(`unknown command '${args[0]}'. Run flow --help.`);
|
|
281
|
+
} catch (error) {
|
|
282
|
+
if (error?.code === 'ABORT_ERR') {
|
|
283
|
+
info('\nFlow command canceled.');
|
|
284
|
+
process.exitCode = 130;
|
|
285
|
+
} else {
|
|
286
|
+
throw error;
|
|
128
287
|
}
|
|
129
288
|
}
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: flow-build
|
|
3
|
-
description: Implement ready tasks with maximum-safe parallelism, approved decisions and gates, token-efficient context packets, automatic claims, tests, commits, and state synchronization.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Flow Build
|
|
7
|
-
|
|
8
|
-
## Objective
|
|
9
|
-
|
|
10
|
-
Implement an active work item efficiently while preserving approved product/engineering constraints and exploiting safe parallelism without turning parallelism into coordination or token waste.
|
|
11
|
-
|
|
12
|
-
## Context loading
|
|
13
|
-
|
|
14
|
-
Start with STATE, the current work item's SPEC/TASKS, and only the relevant global decisions/engineering/gates. Workers or subagents receive the smallest context packet needed for their task.
|
|
15
|
-
|
|
16
|
-
## Claims
|
|
17
|
-
|
|
18
|
-
Before implementation/delegation, mark selected tasks `in_progress` and set their `execution_id`. Synchronize state BEFORE code edits. Never take over a task or work item already `in_progress` under another execution ID.
|
|
19
|
-
|
|
20
|
-
There is no claim timeout. If ownership appears abandoned or inconsistent, surface it for explicit reconciliation rather than silently reclaiming it.
|
|
21
|
-
|
|
22
|
-
## Maximum-safe parallelism
|
|
23
|
-
|
|
24
|
-
The configured limits may be an integer or `auto`.
|
|
25
|
-
|
|
26
|
-
`auto` means the orchestrating agent must choose, for each scheduling cycle, the largest concurrency it can reliably coordinate without unacceptable risk. Consider:
|
|
27
|
-
- DAG independence;
|
|
28
|
-
- likely file/module/contract overlap;
|
|
29
|
-
- shared mutable state and migrations;
|
|
30
|
-
- unresolved or interacting decisions;
|
|
31
|
-
- task size and uncertainty;
|
|
32
|
-
- available runtime/subagent capabilities;
|
|
33
|
-
- context-window and tool limits;
|
|
34
|
-
- expected coordination/merge overhead;
|
|
35
|
-
- token efficiency.
|
|
36
|
-
|
|
37
|
-
The safe answer may be 1. Do not spawn workers merely because tasks are technically independent. Parallelize when the time/clarity benefit exceeds the context and coordination cost.
|
|
38
|
-
|
|
39
|
-
When `auto` is used, record the chosen effective concurrency for the current cycle in `STATE.yaml` so another agent or reader can understand what is happening.
|
|
40
|
-
|
|
41
|
-
## Execution
|
|
42
|
-
|
|
43
|
-
1. Compute all ready tasks.
|
|
44
|
-
2. Exclude tasks owned by another execution.
|
|
45
|
-
3. Determine the maximum-safe set under configuration.
|
|
46
|
-
4. Claim the complete selected set before implementation.
|
|
47
|
-
5. Execute directly or delegate safe independent tasks.
|
|
48
|
-
6. Apply approved conventions automatically; do not ask about already-settled rules.
|
|
49
|
-
7. Run applicable cheap/deterministic gates during implementation where practical.
|
|
50
|
-
8. Implement the smallest changes satisfying each task.
|
|
51
|
-
9. Run narrow relevant checks first, then broader checks when needed.
|
|
52
|
-
10. Create atomic commits when the environment permits; never include unrelated worktree changes.
|
|
53
|
-
11. Mark completed tasks `done`, record commit SHAs when available, recompute readiness, and synchronize state.
|
|
54
|
-
|
|
55
|
-
If implementation exposes a consequential unapproved decision, STOP affected work, preserve completed independent work, record the pending decision using the required decision format, and return control to the orchestrator for developer input.
|
|
56
|
-
|
|
57
|
-
If implementation reveals additional required work, add a task or backlog work item with real dependencies instead of silently expanding scope.
|
|
58
|
-
|
|
59
|
-
## Scope guard
|
|
60
|
-
|
|
61
|
-
Do not opportunistically refactor unrelated code. Record separate technical/maintenance work when useful.
|
|
62
|
-
|
|
63
|
-
## Token-efficiency rules
|
|
64
|
-
|
|
65
|
-
Token efficiency is a first-class optimization alongside correctness and maintainability:
|
|
66
|
-
- never provide each worker the full project history;
|
|
67
|
-
- prefer SUMMARY + exact relevant sections/IDs;
|
|
68
|
-
- avoid rereading completed specs and large historical sources;
|
|
69
|
-
- avoid multiple agents doing the same repository exploration;
|
|
70
|
-
- prefer deterministic tools/tests over reasoning-heavy review where possible;
|
|
71
|
-
- do not generate long execution narratives;
|
|
72
|
-
- use Git for implementation history;
|
|
73
|
-
- choose sequential execution when delegation overhead would cost more than it saves.
|
package/skills/flow-new/SKILL.md
DELETED
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: flow-new
|
|
3
|
-
description: Bootstrap Flow from a broad idea or existing project sources, resolve consequential global product and engineering decisions, define a production-capable MVP, and create the initial work DAG.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Flow New
|
|
7
|
-
|
|
8
|
-
## Objective
|
|
9
|
-
|
|
10
|
-
Create enough shared product, engineering, infrastructure, and quality context to define a coherent production-capable MVP without prematurely specifying work-item details.
|
|
11
|
-
|
|
12
|
-
## Readability principle
|
|
13
|
-
|
|
14
|
-
Flow is readability first. Write canonical artifacts so a reader can understand the project without knowing Flow internals. Prefer clear prose and explicit names over compressed notation.
|
|
15
|
-
|
|
16
|
-
## Inputs
|
|
17
|
-
|
|
18
|
-
Support all of these without changing the workflow contract:
|
|
19
|
-
- greenfield idea;
|
|
20
|
-
- existing PRD/PRFAQ/specifications;
|
|
21
|
-
- existing codebase;
|
|
22
|
-
- existing codebase plus product documentation.
|
|
23
|
-
|
|
24
|
-
Existing source documents are inputs, not permanent runtime context. Synthesize them into small canonical `.flow/` artifacts and refer back to sources only when a later work item requires specific detail.
|
|
25
|
-
|
|
26
|
-
## Global discovery
|
|
27
|
-
|
|
28
|
-
Resolve only consequential global decisions needed to define:
|
|
29
|
-
- product problem, users, boundaries, and MVP outcome;
|
|
30
|
-
- production constraints and deployment model;
|
|
31
|
-
- architecture and project/module boundaries;
|
|
32
|
-
- infrastructure, persistence, integrations, security/privacy, observability, and operations;
|
|
33
|
-
- global testing strategy and documentation expectations;
|
|
34
|
-
- reusable quality gates;
|
|
35
|
-
- technical constraints that shape product scope or work ordering.
|
|
36
|
-
|
|
37
|
-
Do not ask work-item-level questions that can safely wait for just-in-time planning.
|
|
38
|
-
|
|
39
|
-
## Decision authority
|
|
40
|
-
|
|
41
|
-
The agent MUST NOT silently make a consequential product or technical decision.
|
|
42
|
-
|
|
43
|
-
A decision is consequential when materially different choices can change product scope/behavior, MVP composition, architecture, module boundaries, public contracts, persistent data semantics, production infrastructure, security/privacy, operational cost/model, testing strategy, reusable quality gates, or cross-work assumptions.
|
|
44
|
-
|
|
45
|
-
Infer without asking when a choice is low-impact, reversible, and has a clear ecosystem/repository convention. Do not ask trivial convention questions such as kebab-case vs snake_case when the selected language/framework or existing repository establishes a normal choice.
|
|
46
|
-
|
|
47
|
-
## Decision batch protocol
|
|
48
|
-
|
|
49
|
-
Ask the largest set of consequential decisions that are CURRENTLY KNOWN and can be answered independently. Do not speculate about future scenarios merely to enlarge a batch.
|
|
50
|
-
|
|
51
|
-
If decision B depends on decision A, ask A first and defer B until A is resolved.
|
|
52
|
-
|
|
53
|
-
Every decision presented for approval MUST include:
|
|
54
|
-
1. **Decision** — what must be chosen.
|
|
55
|
-
2. **Context** — why the choice exists now and why it matters.
|
|
56
|
-
3. **Options** — realistic alternatives and their meaningful trade-offs.
|
|
57
|
-
4. **Recommended option** — exactly one when a recommendation is possible.
|
|
58
|
-
5. **Why recommended** — concise reasoning grounded in current constraints.
|
|
59
|
-
6. **Impact** — product, engineering, gates, and work items likely affected.
|
|
60
|
-
|
|
61
|
-
Record unresolved consequential decisions as `pending_user` in `DECISIONS.yaml`. After a choice is provided, record the accepted choice and synchronize affected canonical artifacts.
|
|
62
|
-
|
|
63
|
-
## MVP rule
|
|
64
|
-
|
|
65
|
-
Discovery MUST converge on a production-capable MVP, not only a feature list. Include both product and technical work required to build, validate, deploy, and operate the MVP at its intended scale.
|
|
66
|
-
|
|
67
|
-
Stop global discovery when there are no unresolved global decisions necessary to define a coherent production-capable MVP and its initial work DAG.
|
|
68
|
-
|
|
69
|
-
## Work DAG
|
|
70
|
-
|
|
71
|
-
Populate `BACKLOG.yaml` with work items of kind:
|
|
72
|
-
- `feature` — user/product capability;
|
|
73
|
-
- `technical` — enabling architecture, infrastructure, quality, or platform work;
|
|
74
|
-
- `maintenance` — reconciliation, migration, refactor, or corrective work created by later decisions.
|
|
75
|
-
|
|
76
|
-
Dependencies represent real blockers only. A work item may be impacted by a decision without depending on another work item.
|
|
77
|
-
|
|
78
|
-
Use universal IDs (`W001`, `W002`, ...). Work item folders use `<sequence-padded><kind-code>-<slug>` where `F` = feature, `T` = technical, and `M` = maintenance. Examples: `001F-user-profile`, `002T-production-baseline`, `003M-auth-reconciliation`. The numeric prefix is a stable readable sequence, NOT execution order; the DAG controls execution.
|
|
79
|
-
|
|
80
|
-
## Gates
|
|
81
|
-
|
|
82
|
-
During discovery, propose reusable gates when they materially increase confidence in approved engineering/product rules. Explicit approval is required for consequential reusable gate policy. Prefer command gates for objectively testable rules and agentic gates for judgment-based policy.
|
|
83
|
-
|
|
84
|
-
## Required outputs
|
|
85
|
-
|
|
86
|
-
Synchronize automatically:
|
|
87
|
-
- `.flow/PRD.md`
|
|
88
|
-
- `.flow/ENGINEERING.md`
|
|
89
|
-
- `.flow/SUMMARY.md`
|
|
90
|
-
- `.flow/DECISIONS.yaml`
|
|
91
|
-
- `.flow/BACKLOG.yaml`
|
|
92
|
-
- `.flow/STATE.yaml`
|
|
93
|
-
- `.flow/gates/` when gates are approved
|
|
94
|
-
|
|
95
|
-
The user must never need to say “update state/docs”.
|
|
96
|
-
|
|
97
|
-
## Token-efficiency rules
|
|
98
|
-
|
|
99
|
-
- Synthesize large source documents once; do not keep re-reading them wholesale.
|
|
100
|
-
- Read only source sections needed to resolve the current decision.
|
|
101
|
-
- Do not map the whole repository when targeted inspection answers the question.
|
|
102
|
-
- Avoid duplicated prose across PRD, ENGINEERING, SUMMARY, and decisions.
|
|
103
|
-
- `SUMMARY.md` is a concise derived view, not another source of truth.
|
|
104
|
-
- Do not spawn subagents unless parallel research/inspection has clear value greater than coordination/context overhead.
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: flow-next
|
|
3
|
-
description: Autonomous Flow orchestrator: advance all currently safe work through planning, build, gates, review, fixes, reconciliation, and completion until a consequential decision or real blocker requires developer input.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Flow Next
|
|
7
|
-
|
|
8
|
-
## Objective
|
|
9
|
-
|
|
10
|
-
Make Flow autonomous by default. The user should not manually drive feature -> build -> review transitions or remind the agent to synchronize state.
|
|
11
|
-
|
|
12
|
-
## Stop conditions
|
|
13
|
-
|
|
14
|
-
Continue automatically until one of the configured conditions occurs:
|
|
15
|
-
- consequential decision requires explicit approval;
|
|
16
|
-
- external approval/action cannot be safely performed autonomously;
|
|
17
|
-
- unrecoverable blocker;
|
|
18
|
-
- no ready work remains;
|
|
19
|
-
- configuration explicitly says not to continue across work items.
|
|
20
|
-
|
|
21
|
-
`continue_across_work_items: true` means that after one work item passes review and closes, immediately recompute the work DAG and continue with the next safe ready work instead of waiting for another command.
|
|
22
|
-
|
|
23
|
-
## Main loop
|
|
24
|
-
|
|
25
|
-
1. Read `config.yaml`, `STATE.yaml`, `BACKLOG.yaml`, `SUMMARY.md`.
|
|
26
|
-
2. Respect all work/tasks already marked `in_progress` under other execution IDs.
|
|
27
|
-
3. If global discovery is incomplete, invoke/follow `flow-new` until it completes or requires a decision.
|
|
28
|
-
4. Reconcile any `needs_reconciliation` work before execution when necessary.
|
|
29
|
-
5. Compute ready work items from dependencies, decision impacts, status, and ownership.
|
|
30
|
-
6. Determine the maximum-safe work-item set under parallelism configuration.
|
|
31
|
-
7. Mark the selected work items `in_progress` with the current run/execution ID BEFORE planning/delegation.
|
|
32
|
-
8. Plan unplanned items via `flow-plan` semantics. If a consequential decision emerges, preserve independent progress and stop only the affected path; present the maximal currently-known independent decision batch.
|
|
33
|
-
9. Build ready planned work via `flow-build` semantics, including safe task parallelism and delegation when beneficial.
|
|
34
|
-
10. Run configured gates and `flow-review` semantics.
|
|
35
|
-
11. If review creates fix tasks, return them automatically to build and review again.
|
|
36
|
-
12. Close passing work items, create maintenance work for completed items impacted by new decisions, synchronize canonical artifacts, and recompute the DAG.
|
|
37
|
-
13. If `continue_across_work_items` is true, continue the loop.
|
|
38
|
-
|
|
39
|
-
## Decision discipline
|
|
40
|
-
|
|
41
|
-
Never infer a consequential choice merely to preserve autonomy. Autonomy means doing everything that follows from approved rules; it does not mean owning approved product or engineering decisions.
|
|
42
|
-
|
|
43
|
-
Every requested decision uses the six-part format: Decision, Context, Options, Recommended option, Why recommended, Impact.
|
|
44
|
-
|
|
45
|
-
Batch the maximum set of currently-known decisions that do not depend on each other. Do not speculate about future decisions to make the batch larger.
|
|
46
|
-
|
|
47
|
-
## Parallelism and token efficiency
|
|
48
|
-
|
|
49
|
-
Parallelism-first means exploiting safe independent work, not maximizing agent count. Under `auto`, choose the largest concurrency that can be reliably coordinated given dependencies, overlap, uncertainty, context/tool capacity, merge risk, and token cost. Record the effective choice in state.
|
|
50
|
-
|
|
51
|
-
Prefer one primary orchestrator. Spawn subagents/workers only when their independence and expected benefit justify extra context. Give each worker a narrow context packet and explicit ownership.
|
|
52
|
-
|
|
53
|
-
## State synchronization
|
|
54
|
-
|
|
55
|
-
Every transition that changes decisions, work status, tasks, gates, PRD, engineering definition, or project overview must synchronize the affected canonical artifacts before continuing. The user should never need to request housekeeping updates.
|