spectoflow 0.14.3 → 0.16.0
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 +27 -4
- package/bin/spectoflow.js +216 -30
- package/lib/adapters.js +12 -0
- package/lib/mcp.js +44 -0
- package/package.json +1 -1
- package/templates/AGENTS.md +25 -6
- package/templates/README.md +10 -2
- package/templates/agents/product-manager.md +6 -4
- package/templates/agents/qa-engineer.md +5 -0
- package/templates/capabilities.md +5 -0
- package/templates/skills/clarify/SKILL.md +100 -0
- package/templates/skills/write-e2e-tests/SKILL.md +16 -6
package/README.md
CHANGED
|
@@ -38,13 +38,22 @@ Every command works both ways — `spectoflow <cmd>` when installed globally, or
|
|
|
38
38
|
### CLI
|
|
39
39
|
|
|
40
40
|
```
|
|
41
|
-
spectoflow init [dir] [--agent=claude,codex] scaffold a project (auto-detects
|
|
41
|
+
spectoflow init [dir] [--agent=claude,codex] scaffold a project (auto-detects agents; wires Playwright MCP)
|
|
42
42
|
spectoflow update [--dry-run] refresh framework files to this kit version
|
|
43
|
-
spectoflow dashboard [--port=NNNN] run the local control plane (default 4319)
|
|
44
|
-
spectoflow dashboard stop (or: stop) stop the running dashboard
|
|
45
43
|
spectoflow status progress + whether the dashboard is running
|
|
44
|
+
|
|
45
|
+
spectoflow dashboard [--port=NNNN] start the control plane in the background (hands the prompt back)
|
|
46
|
+
spectoflow dashboard status is it running? (url + pid)
|
|
47
|
+
spectoflow dashboard stop (or: stop) stop the running dashboard
|
|
48
|
+
spectoflow dashboard restart stop then start
|
|
49
|
+
|
|
50
|
+
spectoflow list agents, skills and the workflow at a glance
|
|
51
|
+
spectoflow agents list the team personas
|
|
52
|
+
spectoflow skills list the procedures
|
|
53
|
+
spectoflow workflow show the enabled pipeline steps
|
|
54
|
+
|
|
46
55
|
spectoflow --version (-v) print the version
|
|
47
|
-
spectoflow --help (-h) show help
|
|
56
|
+
spectoflow --help (-h) show help (append -h to any command for its help)
|
|
48
57
|
```
|
|
49
58
|
|
|
50
59
|
`init` scaffolds:
|
|
@@ -158,6 +167,20 @@ runs a skill. Improve a skill without touching the agent.
|
|
|
158
167
|
Agents and skills follow real domain standards, cited in-file — TDD, OWASP ASVS/Top 10, C4/ADR,
|
|
159
168
|
INVEST, Playwright E2E, Conventional Commits, and more — not generic one-liners.
|
|
160
169
|
|
|
170
|
+
**Clarify before acting.** spectoflow is an **expert analyst, not an order-taker**. When a request is
|
|
171
|
+
vague ("login displays badly, users can't sign in"), an always-on **Clarify reflex** — in the agent's
|
|
172
|
+
memory (`AGENTS.md`) and backed by the `clarify` skill — reflects it back and asks **one targeted
|
|
173
|
+
question at a time**, each with a recommendation anchored in the project's goals and best practices,
|
|
174
|
+
until the need is crisp; then it runs the normal workflow. It's additive: it feeds the router, never
|
|
175
|
+
replaces it, and it's mode-aware.
|
|
176
|
+
|
|
177
|
+
**End-to-end tests via Playwright MCP.** `init` idempotently wires a `playwright` entry into the target
|
|
178
|
+
project's `.mcp.json` (and `.cursor/mcp.json` for Cursor) so the QA agent can drive a real browser and
|
|
179
|
+
generate/run Playwright specs — `npx` fetches the server on first use, so spectoflow stays zero-dep
|
|
180
|
+
(the config lives in *your* project). If the MCP isn't available, `write-e2e-tests` falls back down a
|
|
181
|
+
ladder (native browser tooling → local Playwright → write the spec and raise a `need`), never faking a
|
|
182
|
+
pass. The durable artifact is always the committed `*.spec.ts`.
|
|
183
|
+
|
|
161
184
|
A `governance` capability adds a **Spec Source Guardian** (skill `audit-source`): it keeps the spec
|
|
162
185
|
(intent) and the code/tests (reality) coherent — flagging drift in both directions, never auto-fixing,
|
|
163
186
|
surfacing findings to the Attention tab, and gating only at `done`/Major. It ships with a zero-dep
|
package/bin/spectoflow.js
CHANGED
|
@@ -9,6 +9,7 @@ const adapters = require('../lib/adapters');
|
|
|
9
9
|
const detect = require('../lib/detect');
|
|
10
10
|
const ownership = require('../lib/ownership');
|
|
11
11
|
const manifest = require('../lib/manifest');
|
|
12
|
+
const mcp = require('../lib/mcp');
|
|
12
13
|
|
|
13
14
|
const KIT = path.resolve(__dirname, '..');
|
|
14
15
|
const TPL = path.join(KIT, 'templates');
|
|
@@ -21,6 +22,60 @@ const useColor = process.stdout.isTTY && !process.env.NO_COLOR;
|
|
|
21
22
|
const paint = (code) => (s) => (useColor ? `\x1b[${code}m${s}\x1b[0m` : String(s));
|
|
22
23
|
const c = { g: paint('32'), cy: paint('36'), b: paint('34'), y: paint('33'), dim: paint('2'), bold: paint('1'), amber: paint('38;5;179') };
|
|
23
24
|
|
|
25
|
+
// ---- branding ---------------------------------------------------------------
|
|
26
|
+
const LOGO = [
|
|
27
|
+
'┌─┐┌─┐┌─┐┌─┐┌┬┐┌─┐┌─┐┬ ┌─┐┬ ┬',
|
|
28
|
+
'└─┐├─┘├┤ │ │ │ │├┤ │ │ ││││',
|
|
29
|
+
'└─┘┴ └─┘└─┘ ┴ └─┘┴ ┴─┘└─┘└┴┘',
|
|
30
|
+
];
|
|
31
|
+
const TAGLINE = 'agent-agnostic spec-driven development · real-time control plane';
|
|
32
|
+
// Full logo block — for init and help (the moments a human is reading, not scripting).
|
|
33
|
+
const banner = () => `\n${LOGO.map((l) => ' ' + c.amber(l)).join('\n')}\n ${c.dim('v' + VERSION + ' · ' + TAGLINE)}\n`;
|
|
34
|
+
// One-line brand — for the header of secondary command outputs.
|
|
35
|
+
const brandLine = () => `${c.amber('spectoflow')} ${c.dim('v' + VERSION)}`;
|
|
36
|
+
|
|
37
|
+
// ---- framework introspection (list agents / skills / workflow) --------------
|
|
38
|
+
// Read from the project's .spectoflow when present, else the bundled kit — so `list` works anywhere.
|
|
39
|
+
function frameworkSource() {
|
|
40
|
+
const local = path.resolve('.spectoflow');
|
|
41
|
+
return fs.existsSync(local) ? { dir: local, scope: 'project' } : { dir: TPL, scope: 'kit' };
|
|
42
|
+
}
|
|
43
|
+
// Tiny frontmatter reader — a few scalar keys, no YAML dependency.
|
|
44
|
+
function frontmatter(file) {
|
|
45
|
+
let txt = '';
|
|
46
|
+
try { txt = fs.readFileSync(file, 'utf8'); } catch { return {}; }
|
|
47
|
+
const m = txt.match(/^---\n([\s\S]*?)\n---/);
|
|
48
|
+
if (!m) return {};
|
|
49
|
+
const out = {};
|
|
50
|
+
for (const line of m[1].split('\n')) {
|
|
51
|
+
const mm = line.match(/^([A-Za-z_]+):\s*(.*)$/);
|
|
52
|
+
if (mm) out[mm[1]] = mm[2].replace(/^["']|["']$/g, '').trim();
|
|
53
|
+
}
|
|
54
|
+
return out;
|
|
55
|
+
}
|
|
56
|
+
function listAgents(dir) {
|
|
57
|
+
const d = path.join(dir, 'agents');
|
|
58
|
+
if (!fs.existsSync(d)) return [];
|
|
59
|
+
return fs.readdirSync(d).filter((f) => f.endsWith('.md')).map((f) => {
|
|
60
|
+
const fm = frontmatter(path.join(d, f));
|
|
61
|
+
return { name: fm.name || f.replace(/\.md$/, ''), capability: fm.capability || '', description: fm.description || '' };
|
|
62
|
+
}).sort((a, b) => a.name.localeCompare(b.name));
|
|
63
|
+
}
|
|
64
|
+
function listSkills(dir) {
|
|
65
|
+
const d = path.join(dir, 'skills');
|
|
66
|
+
if (!fs.existsSync(d)) return [];
|
|
67
|
+
return fs.readdirSync(d, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => {
|
|
68
|
+
const fm = frontmatter(path.join(d, e.name, 'SKILL.md'));
|
|
69
|
+
return { name: fm.name || e.name, capability: fm.capability || '', description: fm.description || '' };
|
|
70
|
+
}).sort((a, b) => a.name.localeCompare(b.name));
|
|
71
|
+
}
|
|
72
|
+
function readWorkflowSteps(dir) {
|
|
73
|
+
let txt = '';
|
|
74
|
+
try { txt = fs.readFileSync(path.join(dir, 'workflow.md'), 'utf8'); } catch { return []; }
|
|
75
|
+
return txt.split('\n').map((l) => l.match(/^- \[([ xX])\]\s+(.+?)\s*(\{.*\})?\s*$/))
|
|
76
|
+
.filter(Boolean).map((m) => ({ on: m[1].toLowerCase() === 'x', name: m[2] }));
|
|
77
|
+
}
|
|
78
|
+
|
|
24
79
|
// ---- dashboard port + running-state probe ------------------------------------
|
|
25
80
|
// Precedence: --port=NNNN > SPECTOFLOW_PORT env > 4319 (matches templates/dashboard/server.js).
|
|
26
81
|
function resolvePort(args) {
|
|
@@ -133,6 +188,19 @@ function init() {
|
|
|
133
188
|
// per-agent shims
|
|
134
189
|
const written = adapters.generate(target, agents);
|
|
135
190
|
|
|
191
|
+
// wire Playwright MCP into the project's MCP config so the E2E agent can drive a real browser and
|
|
192
|
+
// generate/run Playwright tests. Idempotent + non-destructive: never touches an existing entry.
|
|
193
|
+
// npx fetches the server on first use, so this config IS the whole install — spectoflow stays
|
|
194
|
+
// zero-dep (this writes into the user's project, never into spectoflow).
|
|
195
|
+
const mcpTargets = [path.join(target, '.mcp.json')];
|
|
196
|
+
if (agents.includes('cursor')) mcpTargets.push(path.join(target, '.cursor', 'mcp.json'));
|
|
197
|
+
for (const fp of mcpTargets) {
|
|
198
|
+
const rel = path.relative(target, fp).split(path.sep).join('/');
|
|
199
|
+
const r = mcp.mergeMcpServer(fp, 'playwright', mcp.PLAYWRIGHT_MCP);
|
|
200
|
+
if (r === 'created' || r === 'added') notes.push(`Wired Playwright MCP into ${rel} (npx @playwright/mcp — for the E2E agent; commit it to share).`);
|
|
201
|
+
else if (r === 'skipped') notes.push(`Left ${rel} as-is (couldn't parse it) — add a 'playwright' MCP server yourself for browser-driven E2E.`);
|
|
202
|
+
}
|
|
203
|
+
|
|
136
204
|
// gitignore the volatile runtime
|
|
137
205
|
const gi = path.join(target, '.gitignore');
|
|
138
206
|
const giText = fs.existsSync(gi) ? fs.readFileSync(gi, 'utf8') : '';
|
|
@@ -140,16 +208,18 @@ function init() {
|
|
|
140
208
|
if (!giText.includes(line)) fs.appendFileSync(gi, ((fs.existsSync(gi) && fs.readFileSync(gi, 'utf8').length) ? '\n' : '') + line + '\n');
|
|
141
209
|
}
|
|
142
210
|
|
|
143
|
-
console.log(
|
|
144
|
-
console.log(
|
|
145
|
-
console.log('
|
|
146
|
-
|
|
147
|
-
|
|
211
|
+
console.log(banner());
|
|
212
|
+
console.log(`${c.g('✓')} installed in ${c.bold(target)}`);
|
|
213
|
+
console.log(` ${c.dim('.spectoflow/')} framework — brain, workflow, agents, skills, policy, dashboard, config`);
|
|
214
|
+
console.log(` ${c.dim('specs/ plans/')} markdown artifacts (your source of truth)`);
|
|
215
|
+
written.forEach((w) => console.log(` ${c.cy('+')} ${w}`));
|
|
216
|
+
notes.forEach((n) => console.log(` ${c.y('!')} ${c.dim(n)}`));
|
|
148
217
|
const port = resolvePort(argv);
|
|
149
|
-
console.log('
|
|
150
|
-
console.log('
|
|
151
|
-
console.log('
|
|
152
|
-
console.log(`
|
|
218
|
+
console.log(`\n${c.bold('Next')}`);
|
|
219
|
+
console.log(` ${c.dim('1)')} Open your agent here — or just say what you want to build.`);
|
|
220
|
+
console.log(` ${c.dim('2)')} ${c.g('spectoflow dashboard')} ${c.dim('→ http://localhost:' + port)}`);
|
|
221
|
+
console.log(` ${c.dim('3)')} ${c.g('spectoflow list')} ${c.dim('see the agents, skills & workflow you got')}`);
|
|
222
|
+
console.log('');
|
|
153
223
|
}
|
|
154
224
|
|
|
155
225
|
function update() {
|
|
@@ -168,7 +238,7 @@ function update() {
|
|
|
168
238
|
const detail = note ? c.dim(note) : c.dim(list.slice(0, 6).join(', ') + (list.length > 6 ? ` +${list.length - 6} more` : ''));
|
|
169
239
|
console.log(` ${sym} ${painter(label.padEnd(9))} ${n} ${detail}`);
|
|
170
240
|
};
|
|
171
|
-
console.log(
|
|
241
|
+
console.log(banner());
|
|
172
242
|
console.log(` ${c.bold('spectoflow update')} ${c.dim(from)} ${c.amber('→')} ${c.bold(r.toVersion)}${dryRun ? c.dim(' (dry-run)') : ''}`);
|
|
173
243
|
console.log('');
|
|
174
244
|
row(c.g('✓'), 'refreshed', r.refreshed, c.g);
|
|
@@ -184,21 +254,55 @@ function update() {
|
|
|
184
254
|
console.log('');
|
|
185
255
|
}
|
|
186
256
|
|
|
187
|
-
// THE launch command —
|
|
188
|
-
//
|
|
257
|
+
// THE launch command — routes the subcommands, then starts. Starting spawns the server DETACHED and
|
|
258
|
+
// hands the prompt straight back (no foreground blocking), then prints the commands to drive it.
|
|
189
259
|
async function dashboard() {
|
|
190
|
-
|
|
260
|
+
const sub = argv[1];
|
|
261
|
+
if (sub === 'stop') return stopDashboard();
|
|
262
|
+
if (sub === 'status') return dashboardStatus();
|
|
263
|
+
if (sub === 'restart') return restartDashboard();
|
|
264
|
+
return startDashboard();
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Start in the background and return control. Probes first so a second start just reports the running
|
|
268
|
+
// one instead of spawning a duplicate (and never crashes on EADDRINUSE).
|
|
269
|
+
async function startDashboard() {
|
|
191
270
|
const port = resolvePort(argv);
|
|
192
271
|
const url = `http://localhost:${port}`;
|
|
193
272
|
if (await probeDashboard(port)) {
|
|
194
|
-
console.log(
|
|
195
|
-
return;
|
|
273
|
+
console.log(`${c.g('●')} dashboard already running → ${c.bold(url)}`);
|
|
274
|
+
return printDashboardCommands();
|
|
196
275
|
}
|
|
197
276
|
const local = path.resolve('.spectoflow', 'dashboard', 'server.js');
|
|
198
277
|
const bundled = path.join(TPL, 'dashboard', 'server.js');
|
|
199
278
|
const env = Object.assign({}, process.env, { SPECTOFLOW_PORT: String(port) });
|
|
200
|
-
spawn('node', [fs.existsSync(local) ? local : bundled], { stdio: '
|
|
201
|
-
|
|
279
|
+
const child = spawn('node', [fs.existsSync(local) ? local : bundled], { detached: true, stdio: 'ignore', env });
|
|
280
|
+
child.unref(); // let this CLI exit while the server keeps running
|
|
281
|
+
console.log(`${c.g('✓')} dashboard started → ${c.bold(url)} ${c.dim('(pid ' + child.pid + ')')}`);
|
|
282
|
+
printDashboardCommands();
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function printDashboardCommands() {
|
|
286
|
+
console.log('');
|
|
287
|
+
console.log(` ${c.dim('status ')} ${c.g('spectoflow dashboard status')} ${c.dim('is it up? (url + pid)')}`);
|
|
288
|
+
console.log(` ${c.dim('stop ')} ${c.g('spectoflow dashboard stop')} ${c.dim('(alias: spectoflow stop)')}`);
|
|
289
|
+
console.log(` ${c.dim('restart')} ${c.g('spectoflow dashboard restart')} ${c.dim('stop then start')}`);
|
|
290
|
+
console.log('');
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
async function dashboardStatus() {
|
|
294
|
+
const port = resolvePort(argv);
|
|
295
|
+
const running = await probeDashboard(port);
|
|
296
|
+
let pid = null;
|
|
297
|
+
try { pid = JSON.parse(fs.readFileSync(path.join(process.cwd(), '.spectoflow', '.dashboard.lock'), 'utf8')).pid; } catch {}
|
|
298
|
+
if (running) console.log(`${c.g('●')} dashboard running → ${c.bold('http://localhost:' + port)}${pid ? c.dim(' (pid ' + pid + ')') : ''}`);
|
|
299
|
+
else console.log(`${c.dim('○')} dashboard not running`);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
async function restartDashboard() {
|
|
303
|
+
await stopDashboard();
|
|
304
|
+
await new Promise((r) => setTimeout(r, 400)); // let the port free up before rebinding
|
|
305
|
+
return startDashboard();
|
|
202
306
|
}
|
|
203
307
|
|
|
204
308
|
// Stop the running dashboard: read the pidfile it wrote, verify it's actually up, then terminate it
|
|
@@ -245,25 +349,107 @@ async function status() {
|
|
|
245
349
|
|
|
246
350
|
function version() { console.log(`spectoflow v${VERSION}`); }
|
|
247
351
|
|
|
248
|
-
|
|
352
|
+
// ---- explore commands -------------------------------------------------------
|
|
353
|
+
function printAgents(withBrand = true) {
|
|
354
|
+
const { dir, scope } = frameworkSource();
|
|
355
|
+
const rows = listAgents(dir);
|
|
356
|
+
if (withBrand) console.log(`${brandLine()} ${c.dim('· agents (' + scope + ')')}`);
|
|
357
|
+
const w = Math.max(4, ...rows.map((r) => r.name.length));
|
|
358
|
+
rows.forEach((r) => console.log(` ${c.g(r.name.padEnd(w))} ${c.dim((r.capability || '').padEnd(14))} ${r.description}`));
|
|
359
|
+
if (!rows.length) console.log(c.dim(' (none found)'));
|
|
360
|
+
}
|
|
361
|
+
function printSkills(withBrand = true) {
|
|
362
|
+
const { dir, scope } = frameworkSource();
|
|
363
|
+
const rows = listSkills(dir);
|
|
364
|
+
if (withBrand) console.log(`${brandLine()} ${c.dim('· skills (' + scope + ')')}`);
|
|
365
|
+
const w = Math.max(4, ...rows.map((r) => r.name.length));
|
|
366
|
+
rows.forEach((r) => console.log(` ${c.cy(r.name.padEnd(w))} ${c.dim((r.capability || '').padEnd(14))} ${r.description}`));
|
|
367
|
+
if (!rows.length) console.log(c.dim(' (none found)'));
|
|
368
|
+
}
|
|
369
|
+
function printWorkflow(withBrand = true) {
|
|
370
|
+
const { dir, scope } = frameworkSource();
|
|
371
|
+
const steps = readWorkflowSteps(dir);
|
|
372
|
+
if (withBrand) console.log(`${brandLine()} ${c.dim('· workflow (' + scope + ')')}`);
|
|
373
|
+
steps.forEach((s) => console.log(` ${s.on ? c.g('●') : c.dim('○')} ${s.on ? s.name : c.dim(s.name + ' (disabled)')}`));
|
|
374
|
+
if (!steps.length) console.log(c.dim(' (no workflow.md)'));
|
|
375
|
+
}
|
|
376
|
+
function listAll() {
|
|
377
|
+
const { scope } = frameworkSource();
|
|
378
|
+
console.log(banner());
|
|
379
|
+
console.log(`${c.bold('Agents')} ${c.dim('— stable team personas (' + scope + ')')}`);
|
|
380
|
+
printAgents(false);
|
|
381
|
+
console.log(`\n${c.bold('Skills')} ${c.dim('— evolving procedures')}`);
|
|
382
|
+
printSkills(false);
|
|
383
|
+
console.log(`\n${c.bold('Workflow')} ${c.dim('— enabled pipeline steps')}`);
|
|
384
|
+
printWorkflow(false);
|
|
385
|
+
console.log('');
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// ---- help (global + per-command) --------------------------------------------
|
|
389
|
+
const help = () => console.log(`${banner()}
|
|
390
|
+
${c.dim('Usage:')} spectoflow ${c.g('<command>')} ${c.dim('[options]')} ${c.dim('· append -h to any command for its help')}
|
|
391
|
+
|
|
392
|
+
${c.bold('Project')}
|
|
393
|
+
${c.g('init')} ${c.dim('[dir] [--agent=a,b]')} scaffold a project (auto-detects agents; wires Playwright MCP)
|
|
394
|
+
${c.g('update')} ${c.dim('[--dry-run]')} refresh framework files to this kit version
|
|
395
|
+
${c.g('status')} progress + whether the dashboard is running
|
|
249
396
|
|
|
250
|
-
${c.
|
|
397
|
+
${c.bold('Dashboard')}
|
|
398
|
+
${c.g('dashboard')} ${c.dim('[--port=NNNN]')} start the control plane in the background (default 4319)
|
|
399
|
+
${c.g('dashboard status')} is it running? (url + pid)
|
|
400
|
+
${c.g('dashboard stop')} stop it ${c.dim('(alias: stop)')}
|
|
401
|
+
${c.g('dashboard restart')} stop then start
|
|
251
402
|
|
|
252
|
-
${c.bold('
|
|
253
|
-
${c.g('
|
|
254
|
-
${c.g('
|
|
255
|
-
${c.g('
|
|
256
|
-
${c.g('
|
|
257
|
-
${c.g('status')} print progress + whether the dashboard is running
|
|
403
|
+
${c.bold('Explore')}
|
|
404
|
+
${c.g('list')} agents, skills and the workflow at a glance
|
|
405
|
+
${c.g('agents')} list the team personas
|
|
406
|
+
${c.g('skills')} list the procedures
|
|
407
|
+
${c.g('workflow')} show the enabled pipeline steps
|
|
258
408
|
|
|
259
|
-
${c.bold('Options
|
|
260
|
-
-v, --version
|
|
261
|
-
-h, --help
|
|
409
|
+
${c.bold('Options')}
|
|
410
|
+
${c.g('-v')}, ${c.g('--version')} print the version
|
|
411
|
+
${c.g('-h')}, ${c.g('--help')} show this help
|
|
262
412
|
|
|
263
413
|
${c.dim('Docs:')} https://github.com/georgesmomo/spectoflow`);
|
|
264
414
|
|
|
265
|
-
|
|
415
|
+
// Per-command help — shown when -h/--help follows a command (e.g. `spectoflow dashboard -h`).
|
|
416
|
+
const HELP = {
|
|
417
|
+
init: `${c.bold('spectoflow init')} ${c.dim('[dir] [--agent=a,b]')}\n
|
|
418
|
+
Scaffold spectoflow into <dir> (default: current directory).
|
|
419
|
+
Auto-detects installed agents (${c.dim('claude, codex, cursor, gemini')}) and writes their entry
|
|
420
|
+
shims; override with ${c.g('--agent=claude,codex')}. Also wires ${c.bold('Playwright MCP')} into the
|
|
421
|
+
project's ${c.dim('.mcp.json')} (idempotent — never touches an existing entry).
|
|
422
|
+
${c.dim('An existing CLAUDE.md is preserved as CLAUDE.md.tomerge for you to merge on first run.')}`,
|
|
423
|
+
update: `${c.bold('spectoflow update')} ${c.dim('[--dry-run]')}\n
|
|
424
|
+
Refresh framework-owned files (engine, dashboard, default agents & skills, AGENTS.md, policy…)
|
|
425
|
+
to this CLI's version, ${c.bold('preserving your work')}: config.json, workflow.md, specs/, plans/
|
|
426
|
+
and any agent/skill you edited are never overwritten (an edited file's new version lands as
|
|
427
|
+
${c.dim('*.new')} for you to merge). ${c.g('--dry-run')} previews without writing.`,
|
|
428
|
+
dashboard: `${c.bold('spectoflow dashboard')} ${c.dim('[--port=NNNN] [status|stop|restart]')}\n
|
|
429
|
+
Start the local control plane in the ${c.bold('background')} (default ${c.dim('4319')} or
|
|
430
|
+
${c.dim('$SPECTOFLOW_PORT')}) and hand the prompt back. Subcommands:
|
|
431
|
+
${c.g('status')} is it running? (url + pid)
|
|
432
|
+
${c.g('stop')} stop it ${c.dim('(alias: spectoflow stop)')}
|
|
433
|
+
${c.g('restart')} stop then start`,
|
|
434
|
+
status: `${c.bold('spectoflow status')}\n
|
|
435
|
+
Print project progress from ${c.dim('plans/*.md')} (tasks done, specs, agents, skills, in-progress
|
|
436
|
+
items) and whether the dashboard is currently running.`,
|
|
437
|
+
list: `${c.bold('spectoflow list')}\n
|
|
438
|
+
Show the ${c.g('agents')}, ${c.cy('skills')} and ${c.bold('workflow')} of the current project
|
|
439
|
+
(or the bundled kit when run outside a project) at a glance.`,
|
|
440
|
+
agents: `${c.bold('spectoflow agents')}\n List the stable team personas (name · capability · role).`,
|
|
441
|
+
skills: `${c.bold('spectoflow skills')}\n List the evolving procedures (name · capability · what it does).`,
|
|
442
|
+
workflow: `${c.bold('spectoflow workflow')}\n Show the pipeline steps, marking which are enabled (●) or disabled (○).`,
|
|
443
|
+
stop: `${c.bold('spectoflow stop')}\n Stop the running dashboard (alias for ${c.g('spectoflow dashboard stop')}).`,
|
|
444
|
+
};
|
|
445
|
+
const showHelp = (name) => console.log('\n' + HELP[name].trim() + '\n');
|
|
446
|
+
|
|
447
|
+
// ---- dispatch ---------------------------------------------------------------
|
|
448
|
+
const fns = { init, update, dashboard, stop: stopDashboard, status, list: listAll, agents: () => printAgents(), skills: () => printSkills(), workflow: () => printWorkflow(), help, version };
|
|
449
|
+
const wantsHelp = argv.slice(1).some((a) => a === '-h' || a === '--help');
|
|
450
|
+
|
|
266
451
|
if (['-v', '-V', '--version', 'version'].includes(cmd)) version();
|
|
267
|
-
else if (['-h', '--help'].includes(cmd)) help();
|
|
452
|
+
else if (['-h', '--help', 'help'].includes(cmd)) help();
|
|
453
|
+
else if (fns[cmd] && wantsHelp && HELP[cmd]) showHelp(cmd);
|
|
268
454
|
else if (fns[cmd]) fns[cmd]();
|
|
269
455
|
else help();
|
package/lib/adapters.js
CHANGED
|
@@ -21,6 +21,10 @@ instructions (intent router, workflow, standing rules).
|
|
|
21
21
|
> install. Merge its project-specific content into this file, then delete \`CLAUDE.md.tomerge\`,
|
|
22
22
|
> before anything else.
|
|
23
23
|
|
|
24
|
+
**Be an expert analyst, not an order-taker.** When a request is ambiguous, **clarify before acting**:
|
|
25
|
+
reflect it back and ask **one targeted question at a time** (each with a recommendation) until the need
|
|
26
|
+
is clear — then execute. See the Clarify reflex in \`.spectoflow/AGENTS.md\`.
|
|
27
|
+
|
|
24
28
|
- Command: \`/spectoflow\` (\`init\` / \`status\` / or just a request).
|
|
25
29
|
- Dashboard: \`node .spectoflow/dashboard/server.js\` → http://localhost:4319
|
|
26
30
|
- Artifacts are markdown in \`specs/\` and \`plans/\`; volatile state in \`.spectoflow/runtime.json\`.
|
|
@@ -30,6 +34,10 @@ const ROOT_AGENTS_MD = `# AGENTS.md — spectoflow
|
|
|
30
34
|
|
|
31
35
|
This project uses **spectoflow**. **Read \`.spectoflow/AGENTS.md\` and follow it** as your operating
|
|
32
36
|
instructions. Artifacts are markdown in \`specs/\` and \`plans/\`; the workflow is \`.spectoflow/workflow.md\`.
|
|
37
|
+
|
|
38
|
+
**Be an expert analyst, not an order-taker.** When a request is ambiguous, **clarify before acting**:
|
|
39
|
+
reflect it back and ask **one targeted question at a time** (each with a recommendation) until the need
|
|
40
|
+
is clear — then execute. See the Clarify reflex in \`.spectoflow/AGENTS.md\`.
|
|
33
41
|
`;
|
|
34
42
|
|
|
35
43
|
const GEMINI_MD = `# GEMINI.md — spectoflow
|
|
@@ -37,6 +45,10 @@ const GEMINI_MD = `# GEMINI.md — spectoflow
|
|
|
37
45
|
This project uses **spectoflow**. **Read \`.spectoflow/AGENTS.md\` and follow it** as your operating
|
|
38
46
|
instructions (intent router, workflow, standing rules). Artifacts are markdown in \`specs/\` and
|
|
39
47
|
\`plans/\`; the workflow is \`.spectoflow/workflow.md\`.
|
|
48
|
+
|
|
49
|
+
**Be an expert analyst, not an order-taker.** When a request is ambiguous, **clarify before acting**:
|
|
50
|
+
reflect it back and ask **one targeted question at a time** (each with a recommendation) until the need
|
|
51
|
+
is clear — then execute. See the Clarify reflex in \`.spectoflow/AGENTS.md\`.
|
|
40
52
|
`;
|
|
41
53
|
|
|
42
54
|
const SLASH_CMD = `---
|
package/lib/mcp.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
/*
|
|
3
|
+
* Idempotent MCP server wiring for `spectoflow init`.
|
|
4
|
+
*
|
|
5
|
+
* MCP-capable clients (Claude Code, and others that read a project `.mcp.json`) discover MCP servers
|
|
6
|
+
* from a JSON file with an `mcpServers` map. init seeds a `playwright` entry so the E2E agent can
|
|
7
|
+
* drive a real browser and generate/run Playwright tests — WITHOUT ever touching an entry the user
|
|
8
|
+
* (or another tool) already put there. Nothing is installed globally: the server runs via `npx`,
|
|
9
|
+
* fetched on first use, so wiring this config IS the whole "install".
|
|
10
|
+
*
|
|
11
|
+
* spectoflow's own zero-runtime-dependency invariant is unaffected — this writes into the USER's
|
|
12
|
+
* project, never into spectoflow.
|
|
13
|
+
*/
|
|
14
|
+
const fs = require('fs');
|
|
15
|
+
const path = require('path');
|
|
16
|
+
|
|
17
|
+
// The Playwright MCP server (Microsoft). npx fetches it on first use — no global install.
|
|
18
|
+
const PLAYWRIGHT_MCP = { command: 'npx', args: ['@playwright/mcp@latest'] };
|
|
19
|
+
|
|
20
|
+
// Merge a single MCP server into a project's MCP config file, idempotently and non-destructively.
|
|
21
|
+
// Returns one of:
|
|
22
|
+
// 'created' — file did not exist, created with just this server.
|
|
23
|
+
// 'added' — file existed; server inserted alongside the existing ones.
|
|
24
|
+
// 'exists' — server already present; file left exactly as-is (idempotent).
|
|
25
|
+
// 'skipped' — file present but not parseable/shaped as expected; left untouched (never clobbered).
|
|
26
|
+
function mergeMcpServer(filePath, name, config) {
|
|
27
|
+
if (fs.existsSync(filePath)) {
|
|
28
|
+
let doc;
|
|
29
|
+
try { doc = JSON.parse(fs.readFileSync(filePath, 'utf8')); }
|
|
30
|
+
catch { return 'skipped'; } // never clobber a file we can't understand
|
|
31
|
+
if (!doc || typeof doc !== 'object' || Array.isArray(doc)) return 'skipped';
|
|
32
|
+
const servers = doc.mcpServers && typeof doc.mcpServers === 'object' && !Array.isArray(doc.mcpServers)
|
|
33
|
+
? doc.mcpServers : null;
|
|
34
|
+
if (servers && Object.prototype.hasOwnProperty.call(servers, name)) return 'exists';
|
|
35
|
+
doc.mcpServers = { ...(servers || {}), [name]: config };
|
|
36
|
+
fs.writeFileSync(filePath, JSON.stringify(doc, null, 2) + '\n');
|
|
37
|
+
return 'added';
|
|
38
|
+
}
|
|
39
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
40
|
+
fs.writeFileSync(filePath, JSON.stringify({ mcpServers: { [name]: config } }, null, 2) + '\n');
|
|
41
|
+
return 'created';
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
module.exports = { mergeMcpServer, PLAYWRIGHT_MCP };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "spectoflow",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.0",
|
|
4
4
|
"description": "Agent-agnostic spec-driven development framework + real-time local control plane. Markdown artifacts, intent router, workflow-by-scope.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"spec-driven-development",
|
package/templates/AGENTS.md
CHANGED
|
@@ -8,6 +8,14 @@
|
|
|
8
8
|
A spec-driven development (SDD) framework. The user speaks in **plain language**; **you classify the
|
|
9
9
|
intent and run the right workflow.** Simplicity stays on the user's side — no ceremonial command to start.
|
|
10
10
|
|
|
11
|
+
## Stance — expert analyst, not an order-taker
|
|
12
|
+
|
|
13
|
+
You are a domain expert, not a passive executor. Reason from **two anchors at once**: the project's own
|
|
14
|
+
objectives (`specs/`, `plans/`, stated goals) **and** software best practices. Advise, recommend, and
|
|
15
|
+
push back when a request is unclear, risky, or contradicts the spec — always with a concrete, reasoned
|
|
16
|
+
recommendation, never a bare "it depends". A framework that blindly does what it's told just ships the
|
|
17
|
+
wrong thing faster; clarify and steer first, then execute.
|
|
18
|
+
|
|
11
19
|
## Language
|
|
12
20
|
|
|
13
21
|
Read `.spectoflow/config.json` → `language` (default `en`). Produce **all output in that language**:
|
|
@@ -37,15 +45,26 @@ whole file. This lets the dashboard and you co-edit without clobbering. Reflect
|
|
|
37
45
|
|
|
38
46
|
## The Router (run internally on every request)
|
|
39
47
|
|
|
40
|
-
1. **Intake** — known task ("develop T-012") → load it from `plans/*.md`. New request or tweak →
|
|
41
|
-
Explicit override ("just do it quick" / "full change") → forced
|
|
42
|
-
|
|
48
|
+
1. **Intake** — known task ("develop T-012") → load it from `plans/*.md`. New request or tweak →
|
|
49
|
+
clarify (step 2) then classify. Explicit override ("just do it quick" / "full change") → forced
|
|
50
|
+
level, **policy still applies**.
|
|
51
|
+
2. **Clarify (before classifying)** — if the request is ambiguous or under-specified (a vague symptom
|
|
52
|
+
like "login doesn't work" or "displays badly", missing acceptance, several plausible readings,
|
|
53
|
+
unclear scope/users), **do not guess and do not start**. Reflect it back in one sentence, then **ask
|
|
54
|
+
ONE targeted question at a time** — each carrying your recommended default and a one-line reason —
|
|
55
|
+
wait for the answer, and if it's still unclear ask the next. Stop the moment the intent is crisp,
|
|
56
|
+
then proceed. **Never dump a block of questions at once.** Anchor every question in the project's
|
|
57
|
+
objectives and best practices, not trivia. Load `.spectoflow/skills/clarify` for the procedure.
|
|
58
|
+
This step is **additive** — it feeds the steps below, it never replaces them. Mode-aware:
|
|
59
|
+
`autopilot` states one assumption and proceeds; `semi`/`manual` clarify. "Just do it / you decide"
|
|
60
|
+
is a valid answer → proceed on explicit, recorded assumptions (policy still applies).
|
|
61
|
+
3. **Classify** — Quick / Standard / Major. Highest signal wins: **scope · risk/reversibility ·
|
|
43
62
|
ambiguity · novelty**. Risk can force the level up even for tiny effort.
|
|
44
|
-
|
|
63
|
+
4. **Gate** — by `mode` (`.spectoflow/config.json`): **autopilot** proceeds · **semi** (default)
|
|
45
64
|
confirms if ambiguous/borderline/risky **and always for a Major** · **manual** confirms each step.
|
|
46
|
-
|
|
65
|
+
5. **Load** — read the enabled steps from `.spectoflow/workflow.md` (single source of truth), plus the
|
|
47
66
|
`.spectoflow/skills/` needed for those steps. Load only what this task needs.
|
|
48
|
-
|
|
67
|
+
6. **Run** — execute. A **policy gate** (`.spectoflow/policy.md`) can interrupt at any point, any mode.
|
|
49
68
|
|
|
50
69
|
## New / empty project → Intake
|
|
51
70
|
|
package/templates/README.md
CHANGED
|
@@ -14,12 +14,20 @@ sit at the project root and just point back here.
|
|
|
14
14
|
- **Just say what you want** to your agent ("add a login feature", "fix T-042"). The router in
|
|
15
15
|
`AGENTS.md` classifies it (quick / standard / major), gates it by your **mode** and **policy**, and
|
|
16
16
|
runs the matching workflow — no ceremonial command.
|
|
17
|
-
- **
|
|
17
|
+
- **When your ask is vague, it clarifies first.** spectoflow behaves like an expert analyst, not an
|
|
18
|
+
order-taker: on an ambiguous request ("login displays badly") it reflects it back and asks **one
|
|
19
|
+
targeted question at a time** (each with a recommendation) until the need is crisp, then executes
|
|
20
|
+
(skill `clarify`, wired into the agent's memory in `AGENTS.md`).
|
|
21
|
+
- **Watch it live** in the dashboard (it starts in the background and hands the prompt back):
|
|
18
22
|
```
|
|
19
23
|
spectoflow dashboard # → http://localhost:4319 (or: node .spectoflow/dashboard/server.js)
|
|
24
|
+
spectoflow dashboard status # is it running? (url + pid)
|
|
20
25
|
spectoflow dashboard stop # stop it (alias: spectoflow stop)
|
|
26
|
+
spectoflow dashboard restart # stop then start
|
|
21
27
|
spectoflow status # progress + whether the dashboard is running
|
|
22
28
|
```
|
|
29
|
+
- **See what you got:** `spectoflow list` (agents, skills & workflow at a glance), or `spectoflow
|
|
30
|
+
agents` / `spectoflow skills` / `spectoflow workflow`. Append `-h` to any command for its help.
|
|
23
31
|
- **Change how it runs** in the dashboard's **Settings** tab (autonomy mode, output language, and the
|
|
24
32
|
dashboard **design**), or by editing `config.json`.
|
|
25
33
|
- **Update the framework** to a newer kit: `spectoflow update` (preserves your edits; a file you
|
|
@@ -44,7 +52,7 @@ Your **artifacts are markdown, and they live at the project root, not in here**:
|
|
|
44
52
|
| `policy.md` | **Non-negotiable gates** — actions that need explicit human approval regardless of mode (prod deploy, destructive migration, security change, spend, source-of-truth drift at done/Major). |
|
|
45
53
|
| `config.json` | Your settings: `mode`, `language`, active `agent`, `runners`, `design`, plans/specs dir. **Yours to edit** — `update` never overwrites it. |
|
|
46
54
|
| `agents/` | **Stable team personas** (product-manager, developer, qa-engineer, code-reviewer, spec-source-guardian…) — the *who*. |
|
|
47
|
-
| `skills/` | **Evolving procedures** (write-spec, write-plan, implement, code-review, audit-source…) — the *how*. A workflow step → a capability → its agent → runs a skill. |
|
|
55
|
+
| `skills/` | **Evolving procedures** (clarify, brainstorm, write-spec, write-plan, implement, write-e2e-tests, code-review, audit-source…) — the *how*. A workflow step → a capability → its agent → runs a skill. |
|
|
48
56
|
| `dashboard/` | The zero-dependency control plane: `server.js` (SSE + file-watch), `runner.js`, `orchestrator.js`, and `public/` (the UI, charts, designs, fonts). |
|
|
49
57
|
| `lib/` | The markdown storage engine (`store.js`) and helpers (e.g. `spec-drift.js` for the spec-source-guardian). |
|
|
50
58
|
| `hooks/` | Optional Claude Code hooks you can wire in yourself (e.g. `spec-drift.js`, a `Stop` hook that surfaces source-of-truth drift to the Attention tab). |
|
|
@@ -2,15 +2,17 @@
|
|
|
2
2
|
name: product-manager
|
|
3
3
|
title: Product Manager
|
|
4
4
|
capability: intake
|
|
5
|
-
uses: [brainstorm]
|
|
5
|
+
uses: [clarify, brainstorm]
|
|
6
6
|
description: Frames the need: problem, users, scope, out-of-scope.
|
|
7
7
|
standards: [product discovery]
|
|
8
8
|
---
|
|
9
9
|
# Product Manager
|
|
10
10
|
|
|
11
|
-
Stable team persona (the "who") for the `intake` capability. The *how* lives in the `
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
Stable team persona (the "who") for the `intake` capability. The *how* lives in the `clarify` and
|
|
12
|
+
`brainstorm` skills (see `uses`): `clarify` is the always-on reflex that turns an ambiguous request
|
|
13
|
+
into a crisp, agreed need — one targeted question at a time — before anything else; `brainstorm` then
|
|
14
|
+
frames that need (problem, users, scope, risks) for a new build. Delegate here whenever a new or
|
|
15
|
+
unclear need arrives and must be understood before it becomes a spec or a plan.
|
|
14
16
|
|
|
15
17
|
## Mandate
|
|
16
18
|
Turn a raw ask into a framed problem — problem, users, constraints, risks, success metric — before
|
|
@@ -32,6 +32,11 @@ after-the-fact check. Owns the test suite's health (signal, speed, isolation), n
|
|
|
32
32
|
edge cases and failure paths — not just the happy path. Prefer the fastest level (unit) that gives
|
|
33
33
|
real confidence; escalate to integration or `write-e2e-tests` only when the behaviour crosses a
|
|
34
34
|
boundary (network, DB, filesystem, another service) that a unit test cannot honestly exercise.
|
|
35
|
+
- **For end-to-end flows, drive the browser via Playwright MCP when available** (wired into the
|
|
36
|
+
project's `.mcp.json` by `spectoflow init`), falling back down the `write-e2e-tests` capability
|
|
37
|
+
ladder (native browser tooling → local Playwright headed/codegen → write the spec and raise a
|
|
38
|
+
`need`). The committed Playwright spec is always the deliverable; live driving is only the means, and
|
|
39
|
+
a flow you couldn't actually run is reported as such, never as a pass.
|
|
35
40
|
|
|
36
41
|
## Definition of done
|
|
37
42
|
Every acceptance criterion has a corresponding test, plus its meaningful edge cases (empty/null,
|
|
@@ -9,6 +9,11 @@ Palette: intake · research · analysis · architecture · planning · testing
|
|
|
9
9
|
(skill `audit-source`) watches that the spec (intent) and the code/tests (reality) stay coherent, and
|
|
10
10
|
surfaces drift to the Attention tab; it gates only at `done`/Major (see `policy.md`), never mid-edit.
|
|
11
11
|
|
|
12
|
+
`clarify` is a **reflex under `intake`, not a workflow step** either: on *any* ambiguous request the
|
|
13
|
+
agent reflects it back and asks **one targeted question at a time** (each with a recommendation) until
|
|
14
|
+
the need is crisp, then proceeds — it feeds the workflow, never replaces it. See `skills/clarify` and
|
|
15
|
+
the Clarify step in `AGENTS.md`.
|
|
16
|
+
|
|
12
17
|
| Project type | Active capabilities |
|
|
13
18
|
|---|---|
|
|
14
19
|
| app / web / API | all |
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: clarify
|
|
3
|
+
description: When a request is ambiguous, act as an analyst — reflect it back and ask one targeted question at a time (each with a recommendation) until the need is crisp, then execute.
|
|
4
|
+
capability: intake
|
|
5
|
+
inputs: The raw request from the user, plus the project's objectives (specs/, plans/, goals) and the mode/policy.
|
|
6
|
+
outputs: A crisp, confirmed statement of the need (or explicit assumptions to proceed on), ready for classification and the normal workflow.
|
|
7
|
+
standard: requirements elicitation
|
|
8
|
+
---
|
|
9
|
+
# Clarify
|
|
10
|
+
|
|
11
|
+
Turn a vague request into a crisp, agreed need **before** classifying or acting — the way a good
|
|
12
|
+
analyst does: reflect, ask the sharpest question, listen, repeat. This is a **reflex**, always in the
|
|
13
|
+
agent's memory (see the Clarify step in `AGENTS.md`), not a workflow stage — it fires on *any*
|
|
14
|
+
request, including bug reports and change requests on an existing project ("the login page doesn't
|
|
15
|
+
display well, users can't sign in").
|
|
16
|
+
|
|
17
|
+
## When to use
|
|
18
|
+
Whenever a request is ambiguous or under-specified and acting on it would mean guessing:
|
|
19
|
+
- a **vague symptom** ("doesn't work", "displays badly", "is slow") with no observable, testable meaning;
|
|
20
|
+
- **missing acceptance** — you can't yet name what "done" looks like;
|
|
21
|
+
- **several plausible readings** that would lead to genuinely different work;
|
|
22
|
+
- **unclear scope or users** ("everyone"? one browser? mobile only?);
|
|
23
|
+
- a request that **contradicts the spec** or a best practice — clarify the intent before complying.
|
|
24
|
+
|
|
25
|
+
Skip it when the request is already unambiguous and testable — over-questioning is its own failure.
|
|
26
|
+
|
|
27
|
+
## Method — reflect, then one question at a time
|
|
28
|
+
1. **Acknowledge, then reflect it back — warmly and naturally.** Open by showing you've taken the
|
|
29
|
+
request on board, then lead into the clarification as a way to serve it *better* — never as an
|
|
30
|
+
interrogation. Restate the request in one sentence, name the goal as you understand it, and surface
|
|
31
|
+
your assumptions so a wrong one is easy to correct. Vary the wording every time (see **Tone**);
|
|
32
|
+
never a fixed opener, never a heavy bordered form.
|
|
33
|
+
2. **Ask ONE question — the highest-value one first.** The single question that most reduces
|
|
34
|
+
uncertainty about what to build. Carry **your recommended default and a one-line reason** ("I'd
|
|
35
|
+
assume the layout breaks on mobile, since that's the common case — is that it?"). Prefer a small set
|
|
36
|
+
of concrete options over an open prompt. **Never send a block of questions.**
|
|
37
|
+
3. **Wait, then decide if you still need more.** Read the answer. If the intent is now crisp, stop and
|
|
38
|
+
proceed. If not, ask the next single question. Keep looping until it's clear — typically 1-3
|
|
39
|
+
questions, rarely more.
|
|
40
|
+
4. **Anchor every question in the two sources of truth.** Each question and recommendation must follow
|
|
41
|
+
from (a) the project's objectives (`specs/`, `plans/`, stated goals) and (b) domain best practices —
|
|
42
|
+
so you're steering like an expert, not fishing. For a login bug that means asking about the
|
|
43
|
+
observable failure, the affected users/browser, and the acceptance ("signed-in and redirected"),
|
|
44
|
+
not cosmetic trivia.
|
|
45
|
+
5. **Converge and confirm.** Once clear, restate the crisp need in one or two lines and get a yes
|
|
46
|
+
before running: "So: <need>, for <users>, done when <acceptance>. Correct?"
|
|
47
|
+
6. **Then hand off** the confirmed need to the normal Router flow (Classify → Gate → Load → Run), or to
|
|
48
|
+
`brainstorm` / `analyze-requirements` for a new build. Clarify **replaces nothing** downstream.
|
|
49
|
+
|
|
50
|
+
## Tone — natural and immersive, never templated
|
|
51
|
+
Sound like a thoughtful colleague, not a form. Acknowledge the ask, then segue into the question as a
|
|
52
|
+
way to get it right — in your own words **each time**. The *spirit* is fixed (acknowledge → reflect →
|
|
53
|
+
one question, with a recommendation); the *phrasing* is never fixed. Do **not** copy these sentences
|
|
54
|
+
verbatim — they only show the register:
|
|
55
|
+
> "Got it — I'm with you on <request>. One thing I'd like to nail down before I start: …"
|
|
56
|
+
> "Taken on board. To make sure I build the right thing rather than guess: …"
|
|
57
|
+
> "Understood. So I aim at the real need here, quick check: …"
|
|
58
|
+
|
|
59
|
+
Match the depth to the request: a small tweak needs at most a light one-line confirm; a genuinely
|
|
60
|
+
complex or new-build request, once the core intent is clear, simply **flows into the normal path**
|
|
61
|
+
(`brainstorm` → analysis → spec → plan) — you already know that road, so don't keep interrogating.
|
|
62
|
+
Keep it light: prose and a question, not a bordered panel of options unless options genuinely help.
|
|
63
|
+
|
|
64
|
+
## Guardrails
|
|
65
|
+
- **One question at a time** — never a wall of questions. This is the whole point.
|
|
66
|
+
- **Only ask what changes the outcome.** If an answer wouldn't change what you'd do, don't ask it.
|
|
67
|
+
- **Always recommend.** A question without your reasoned default offloads the thinking back onto the
|
|
68
|
+
user — give the expert view, let them correct it.
|
|
69
|
+
- **Respect the mode** (`config.json`): `autopilot` → state one assumption and proceed (record it);
|
|
70
|
+
`semi` (default) → clarify when ambiguous/risky; `manual` → clarify. "Just do it / you decide" is a
|
|
71
|
+
valid answer → proceed on explicit, recorded assumptions (`policy.md` still applies).
|
|
72
|
+
- **Cap the loop.** If it's still unclear after a few rounds, propose the most reasonable
|
|
73
|
+
interpretation as a recommendation and ask for a yes/no — don't interrogate indefinitely.
|
|
74
|
+
- **Never fabricate the answer** to keep moving; a decision-blocking gap that isn't yours to settle is
|
|
75
|
+
a `need`, raised per `policy.md`.
|
|
76
|
+
|
|
77
|
+
## Output contract
|
|
78
|
+
The confirmed need (or the assumptions being proceeded on) is recorded granularly — a note/task
|
|
79
|
+
comment, or the spec if one exists — one line at a time. Report to the orchestrator and group chat:
|
|
80
|
+
|
|
81
|
+
```
|
|
82
|
+
::spectoflow role=intake kind=clarify msg=<the one crisp question you just asked, or the confirmed need>
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Quality bar
|
|
86
|
+
- [ ] The request was reflected back in one sentence before any question was asked.
|
|
87
|
+
- [ ] Questions were asked **one at a time**, never as a block.
|
|
88
|
+
- [ ] Every question carried a recommended default with a one-line reason.
|
|
89
|
+
- [ ] Each question was anchored in the project's objectives and/or a best practice — not trivia.
|
|
90
|
+
- [ ] The loop stopped as soon as the need was crisp (no over-questioning), and the crisp need was
|
|
91
|
+
confirmed with the user before execution.
|
|
92
|
+
- [ ] Mode was respected; "you decide" was honored by proceeding on explicit, recorded assumptions.
|
|
93
|
+
|
|
94
|
+
## References
|
|
95
|
+
- Anthropic, "Claude Code best practices" (be specific; let the agent ask before acting) —
|
|
96
|
+
https://www.anthropic.com/engineering/claude-code-best-practices
|
|
97
|
+
- IIBA, *A Guide to the Business Analysis Body of Knowledge (BABOK)* — Elicitation & Collaboration —
|
|
98
|
+
https://www.iiba.org/career-resources/a-business-analysis-professionals-foundation/babok/
|
|
99
|
+
- Gojko Adzic, *Specification by Example* (Manning, 2011) — converging on a shared, testable
|
|
100
|
+
understanding before building — https://gojko.net/books/specification-by-example/
|
|
@@ -44,12 +44,22 @@ Practices below are current Playwright guidance (see References for exact source
|
|
|
44
44
|
8. **Keep specs scoped to one flow each**, named for the behavior under test, and placed under
|
|
45
45
|
`tests/e2e/*.spec.ts` in the user's project.
|
|
46
46
|
|
|
47
|
-
**
|
|
48
|
-
|
|
49
|
-
the
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
47
|
+
**Driving the browser (live repro + test generation) — use the best available, in this order:**
|
|
48
|
+
1. **Playwright MCP** (`@playwright/mcp`, wired into the project's `.mcp.json` by `spectoflow init`):
|
|
49
|
+
the **agent-agnostic** way to drive a real browser and **generate** a spec from a recorded flow.
|
|
50
|
+
Works in any MCP client (Claude Code, Codex, Cursor, …). `npx` fetches it on first use — nothing to
|
|
51
|
+
install; if it isn't wired yet, add it or run `spectoflow init` again (idempotent).
|
|
52
|
+
2. **The client's native browser tooling** (for Claude Code, the Chrome extension / `claude-in-chrome`)
|
|
53
|
+
for live/exploratory checks when MCP isn't wired.
|
|
54
|
+
3. **Local Playwright** — `npx playwright codegen` / headed mode for a quick, throwaway look;
|
|
55
|
+
`npx playwright install` provides the browsers. Needs `@playwright/test` as the project's devDependency.
|
|
56
|
+
4. **If no browser can run at all** (restricted CI, no browsers installed): still **write the durable
|
|
57
|
+
spec** (the artifact that lasts), then raise a `need` / Attention item with the exact commands to
|
|
58
|
+
enable it — never report a pass you couldn't actually observe.
|
|
59
|
+
|
|
60
|
+
**Live/exploratory verification is not this skill's output.** Whichever rung above you're on, the
|
|
61
|
+
durable, CI-runnable artifact this skill produces is always the Playwright **spec file**, not the live
|
|
62
|
+
session — the live drive is only the means to write and check it.
|
|
53
63
|
|
|
54
64
|
**Playwright is a dependency of the user's project, never of spectoflow.** This skill authors tests
|
|
55
65
|
against whatever Playwright version the target project has (or proposes adding `@playwright/test` as a
|