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 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 installed agents)
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('spectoflow installed in', target);
144
- console.log(' .spectoflow/ framework (brain, workflow, agents, skills, policy, dashboard, config)');
145
- console.log(' specs/ plans/ markdown artifacts (your source of truth)');
146
- written.forEach((w) => console.log(' + ' + w));
147
- notes.forEach((n) => console.log(' ! ' + n));
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('\nNext:');
150
- console.log(' 1) Open your agent here — or just say what you want to build.');
151
- console.log(' 2) spectoflow dashboard');
152
- console.log(` http://localhost:${port}`);
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 — prints the URL clearly and won't crash on EADDRINUSE: it probes first
188
- // and, if a dashboard is already up on that port, just reports it instead of spawning a second one.
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
- if (argv[1] === 'stop' || argv.includes('stop')) return stopDashboard();
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(`spectoflow dashboard already running → ${url}`);
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: 'inherit', env });
201
- console.log(`spectoflow dashboard ${url}`);
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
- const help = () => console.log(`${c.bold('spectoflow')} ${c.amber('v' + VERSION)} — agent-agnostic spec-driven development
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.dim('Usage:')} spectoflow <command> [options]
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('Commands:')}
253
- ${c.g('init')} [dir] [--agent=claude,codex] scaffold a project (auto-detects installed agents)
254
- ${c.g('update')} [--dry-run] refresh framework files to this kit version
255
- ${c.g('dashboard')} [--port=NNNN] run the local control plane (default 4319, or $SPECTOFLOW_PORT)
256
- ${c.g('dashboard stop')} stop the running dashboard (alias: ${c.g('stop')})
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 print the version
261
- -h, --help show this 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
- const fns = { init, update, dashboard, stop: stopDashboard, status, help, version };
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.14.3",
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",
@@ -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 → classify.
41
- Explicit override ("just do it quick" / "full change") → forced level, **policy still applies**.
42
- 2. **Classify** Quick / Standard / Major. Highest signal wins: **scope · risk/reversibility ·
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
- 3. **Gate** — by `mode` (`.spectoflow/config.json`): **autopilot** proceeds · **semi** (default)
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
- 4. **Load** — read the enabled steps from `.spectoflow/workflow.md` (single source of truth), plus the
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
- 5. **Run** — execute. A **policy gate** (`.spectoflow/policy.md`) can interrupt at any point, any mode.
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
 
@@ -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
- - **Watch it live** in the dashboard:
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 `brainstorm`
12
- skill (see `uses`). Delegate here whenever a new need arrives and must be framed before it becomes a
13
- spec or a plan.
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
- **Live/exploratory verification is not this skill's output.** When an agent needs to *see* a change work
48
- right now (e.g. eyeballing a UI during development), it uses its native browser tooling (for Claude Code,
49
- the Chrome extension / `claude-in-chrome`) to drive the real browser interactively. If that tooling is
50
- unavailable, the fallback is Playwright in headed mode or `playwright codegen` for a quick, throwaway
51
- look never a substitute for the committed suite. Either way, the durable, CI-runnable artifact this
52
- skill produces is always the Playwright spec file, not the live session.
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