spectoflow 0.15.0 → 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:
package/bin/spectoflow.js CHANGED
@@ -22,6 +22,60 @@ const useColor = process.stdout.isTTY && !process.env.NO_COLOR;
22
22
  const paint = (code) => (s) => (useColor ? `\x1b[${code}m${s}\x1b[0m` : String(s));
23
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') };
24
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
+
25
79
  // ---- dashboard port + running-state probe ------------------------------------
26
80
  // Precedence: --port=NNNN > SPECTOFLOW_PORT env > 4319 (matches templates/dashboard/server.js).
27
81
  function resolvePort(args) {
@@ -154,16 +208,18 @@ function init() {
154
208
  if (!giText.includes(line)) fs.appendFileSync(gi, ((fs.existsSync(gi) && fs.readFileSync(gi, 'utf8').length) ? '\n' : '') + line + '\n');
155
209
  }
156
210
 
157
- console.log('spectoflow installed in', target);
158
- console.log(' .spectoflow/ framework (brain, workflow, agents, skills, policy, dashboard, config)');
159
- console.log(' specs/ plans/ markdown artifacts (your source of truth)');
160
- written.forEach((w) => console.log(' + ' + w));
161
- 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)}`));
162
217
  const port = resolvePort(argv);
163
- console.log('\nNext:');
164
- console.log(' 1) Open your agent here — or just say what you want to build.');
165
- console.log(' 2) spectoflow dashboard');
166
- 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('');
167
223
  }
168
224
 
169
225
  function update() {
@@ -182,7 +238,7 @@ function update() {
182
238
  const detail = note ? c.dim(note) : c.dim(list.slice(0, 6).join(', ') + (list.length > 6 ? ` +${list.length - 6} more` : ''));
183
239
  console.log(` ${sym} ${painter(label.padEnd(9))} ${n} ${detail}`);
184
240
  };
185
- console.log('');
241
+ console.log(banner());
186
242
  console.log(` ${c.bold('spectoflow update')} ${c.dim(from)} ${c.amber('→')} ${c.bold(r.toVersion)}${dryRun ? c.dim(' (dry-run)') : ''}`);
187
243
  console.log('');
188
244
  row(c.g('✓'), 'refreshed', r.refreshed, c.g);
@@ -198,21 +254,55 @@ function update() {
198
254
  console.log('');
199
255
  }
200
256
 
201
- // THE launch command — prints the URL clearly and won't crash on EADDRINUSE: it probes first
202
- // 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.
203
259
  async function dashboard() {
204
- 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() {
205
270
  const port = resolvePort(argv);
206
271
  const url = `http://localhost:${port}`;
207
272
  if (await probeDashboard(port)) {
208
- console.log(`spectoflow dashboard already running → ${url}`);
209
- return;
273
+ console.log(`${c.g('●')} dashboard already running → ${c.bold(url)}`);
274
+ return printDashboardCommands();
210
275
  }
211
276
  const local = path.resolve('.spectoflow', 'dashboard', 'server.js');
212
277
  const bundled = path.join(TPL, 'dashboard', 'server.js');
213
278
  const env = Object.assign({}, process.env, { SPECTOFLOW_PORT: String(port) });
214
- spawn('node', [fs.existsSync(local) ? local : bundled], { stdio: 'inherit', env });
215
- 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();
216
306
  }
217
307
 
218
308
  // Stop the running dashboard: read the pidfile it wrote, verify it's actually up, then terminate it
@@ -259,25 +349,107 @@ async function status() {
259
349
 
260
350
  function version() { console.log(`spectoflow v${VERSION}`); }
261
351
 
262
- 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')}
263
391
 
264
- ${c.dim('Usage:')} spectoflow <command> [options]
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
265
396
 
266
- ${c.bold('Commands:')}
267
- ${c.g('init')} [dir] [--agent=claude,codex] scaffold a project (auto-detects installed agents)
268
- ${c.g('update')} [--dry-run] refresh framework files to this kit version
269
- ${c.g('dashboard')} [--port=NNNN] run the local control plane (default 4319, or $SPECTOFLOW_PORT)
270
- ${c.g('dashboard stop')} stop the running dashboard (alias: ${c.g('stop')})
271
- ${c.g('status')} print progress + whether the dashboard is running
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
272
402
 
273
- ${c.bold('Options:')}
274
- -v, --version print the version
275
- -h, --help show this help
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
408
+
409
+ ${c.bold('Options')}
410
+ ${c.g('-v')}, ${c.g('--version')} print the version
411
+ ${c.g('-h')}, ${c.g('--help')} show this help
276
412
 
277
413
  ${c.dim('Docs:')} https://github.com/georgesmomo/spectoflow`);
278
414
 
279
- 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
+
280
451
  if (['-v', '-V', '--version', 'version'].includes(cmd)) version();
281
- 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);
282
454
  else if (fns[cmd]) fns[cmd]();
283
455
  else help();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spectoflow",
3
- "version": "0.15.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",
@@ -18,12 +18,16 @@ sit at the project root and just point back here.
18
18
  order-taker: on an ambiguous request ("login displays badly") it reflects it back and asks **one
19
19
  targeted question at a time** (each with a recommendation) until the need is crisp, then executes
20
20
  (skill `clarify`, wired into the agent's memory in `AGENTS.md`).
21
- - **Watch it live** in the dashboard:
21
+ - **Watch it live** in the dashboard (it starts in the background and hands the prompt back):
22
22
  ```
23
23
  spectoflow dashboard # → http://localhost:4319 (or: node .spectoflow/dashboard/server.js)
24
+ spectoflow dashboard status # is it running? (url + pid)
24
25
  spectoflow dashboard stop # stop it (alias: spectoflow stop)
26
+ spectoflow dashboard restart # stop then start
25
27
  spectoflow status # progress + whether the dashboard is running
26
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.
27
31
  - **Change how it runs** in the dashboard's **Settings** tab (autonomy mode, output language, and the
28
32
  dashboard **design**), or by editing `config.json`.
29
33
  - **Update the framework** to a newer kit: `spectoflow update` (preserves your edits; a file you
@@ -25,8 +25,11 @@ Whenever a request is ambiguous or under-specified and acting on it would mean g
25
25
  Skip it when the request is already unambiguous and testable — over-questioning is its own failure.
26
26
 
27
27
  ## Method — reflect, then one question at a time
28
- 1. **Reflect it back.** Restate the request in one sentence and name the goal as you understand it.
29
- Surface your assumptions explicitly so a wrong one is easy to correct.
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.
30
33
  2. **Ask ONE question — the highest-value one first.** The single question that most reduces
31
34
  uncertainty about what to build. Carry **your recommended default and a one-line reason** ("I'd
32
35
  assume the layout breaks on mobile, since that's the common case — is that it?"). Prefer a small set
@@ -44,6 +47,20 @@ Skip it when the request is already unambiguous and testable — over-questionin
44
47
  6. **Then hand off** the confirmed need to the normal Router flow (Classify → Gate → Load → Run), or to
45
48
  `brainstorm` / `analyze-requirements` for a new build. Clarify **replaces nothing** downstream.
46
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
+
47
64
  ## Guardrails
48
65
  - **One question at a time** — never a wall of questions. This is the whole point.
49
66
  - **Only ask what changes the outcome.** If an answer wouldn't change what you'd do, don't ask it.