spectoflow 0.27.2 → 0.28.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
@@ -118,18 +118,20 @@ detected it falls back to claude + codex.
118
118
  **Empty project** → your agent asks what to build and runs Intake (brainstorm → analysis → spec → plan).
119
119
  **Existing project** → an existing `CLAUDE.md` is preserved as `CLAUDE.md.tomerge` (merged on first run);
120
120
  an existing `AGENTS.md` or `GEMINI.md` is kept as-is and gets a short, delimited spectoflow section
121
- appended (`<!-- spectoflow:start -->` … `<!-- spectoflow:end -->`) pointing to `.spectoflow/AGENTS.md` —
121
+ appended (`<!-- spectoflow:start -->` … `<!-- spectoflow:end -->`) pointing to `.spectoflow/SPECTOFLOW.md` —
122
122
  `spectoflow update` adds it too to a project installed before that; existing `plans/*.md` tasks are given
123
123
  stable ids.
124
124
 
125
- **Two `AGENTS.md`, on purpose.** The one at your project root is a thin pointer, in the place each agent
126
- reads natively. The real brain (intent router, workflow, rules) is `.spectoflow/AGENTS.md`, owned by the
127
- framework and refreshed by `spectoflow update`.
125
+ **Entry files vs. the brain.** `CLAUDE.md`, `AGENTS.md` and `GEMINI.md` at your project root are thin
126
+ pointers, in the place each agent reads natively. The real brain (intent router, workflow, rules) is
127
+ `.spectoflow/SPECTOFLOW.md`, owned by the framework and refreshed by `spectoflow update`. (Before 0.28 it
128
+ was named `.spectoflow/AGENTS.md`, easy to confuse with the root one — `update` renames it, keeping any
129
+ edits you made, and rewrites the old path in your entry files.)
128
130
 
129
131
  ## Update
130
132
 
131
133
  `init` is idempotent (it never overwrites), so it can't refresh an installed project. `spectoflow
132
- update` refreshes **framework-owned** files (engine, `AGENTS.md`, `capabilities.md`, `policy.md`,
134
+ update` refreshes **framework-owned** files (engine, `SPECTOFLOW.md`, `capabilities.md`, `policy.md`,
133
135
  default agents & skills) to the CLI's version — retiring the project's own vendored dashboard folder
134
136
  along the way for anyone updating from before v0.24 — while **preserving your work** —
135
137
  `config.json`, `workflow.md`, `specs/`, `plans/`, and any agent/skill you created or edited are never
@@ -353,7 +355,7 @@ INVEST, Playwright E2E, Conventional Commits, and more — not generic one-liner
353
355
  ### Clarify before acting
354
356
 
355
357
  spectoflow is an **expert analyst, not an order-taker**. When a request is vague ("login displays
356
- badly, users can't sign in"), an always-on **Clarify reflex** — in the agent's memory (`AGENTS.md`)
358
+ badly, users can't sign in"), an always-on **Clarify reflex** — in the agent's memory (`SPECTOFLOW.md`)
357
359
  and backed by the `clarify` skill — reflects it back and asks **one targeted question at a time**,
358
360
  each with a recommendation anchored in the project's goals and best practices, until the need is
359
361
  crisp; then it runs the normal workflow. It's additive: it feeds the router, never replaces it, and
package/bin/spectoflow.js CHANGED
@@ -133,7 +133,7 @@ async function update() {
133
133
  const r = require('../lib/update').runUpdate({ projectRoot: root, templatesDir: TPL, version: VERSION, dryRun, force });
134
134
 
135
135
  const from = r.fromVersion || 'unknown';
136
- const changed = r.refreshed.length + r.created.length + r.adopted.length + r.newSidecar.length + r.forced.length + r.removed.length + r.migration.movedViews.length + r.pointers.length;
136
+ const changed = r.refreshed.length + r.created.length + r.adopted.length + r.newSidecar.length + r.forced.length + r.removed.length + r.migration.movedViews.length + r.pointers.appended.length + r.pointers.repointed.length + r.migration.repointedUserFiles.length + (r.migration.renamedBrain ? 1 : 0);
137
137
  const row = (sym, label, list, painter, note) => {
138
138
  if (!list.length) return;
139
139
  const n = c.dim(String(list.length).padStart(2));
@@ -150,7 +150,9 @@ async function update() {
150
150
  row(c.y('!'), '.new', r.newSidecar, c.y, 'you edited these — new version saved as *.new, merge by hand (or re-run with --force)');
151
151
  row(c.dim('−'), 'removed', r.removed, c.dim, 'no longer part of the kit (the dashboard lives in the spectoflow package now)');
152
152
  row(c.y('!'), 'kept', r.kept, c.y, 'you modified these and they are no longer part of the kit — delete them yourself when ready');
153
- row(c.cy('+'), 'linked', r.pointers, c.cy, `${r.pointers.join(', ')} existed without a pointer — a spectoflow section was appended`);
153
+ if (r.migration.renamedBrain) console.log(` ${c.cy('→')} ${c.cy('renamed'.padEnd(9))} ${c.dim(' 1')} ${c.dim('.spectoflow/AGENTS.md → .spectoflow/SPECTOFLOW.md (the brain; your edits, if any, came along)')}`);
154
+ row(c.cy('→'), 'repointed', [...r.pointers.repointed, ...r.migration.repointedUserFiles.map((f) => '.spectoflow/' + f)], c.cy, null);
155
+ row(c.cy('+'), 'linked', r.pointers.appended, c.cy, `${r.pointers.appended.join(', ')} existed without a pointer — a spectoflow section was appended`);
154
156
  if (r.migration.movedViews.length) console.log(` ${c.cy('→')} ${c.cy('views'.padEnd(9))} ${c.dim(String(r.migration.movedViews.length).padStart(2))} ${c.dim('custom views moved to .spectoflow/dashboards/')}`);
155
157
  r.migration.conflicts.forEach((f) => console.log(` ${c.y('!')} ${c.y('conflict'.padEnd(9))} ${c.dim(`dashboards/${f} already exists — the old copy stays in dashboard/custom/ for you to merge`)}`));
156
158
  if (r.legacyLeftovers.length) console.log(` ${c.y('!')} ${c.dim('this project has no install manifest, so nothing was deleted. Safe to remove by hand: ' + r.legacyLeftovers.map((p) => '.spectoflow/' + p).join(', '))}`);
@@ -616,7 +618,7 @@ const HELP = {
616
618
  ${c.dim('an existing AGENTS.md/GEMINI.md is kept and gets a spectoflow pointer section appended.')}
617
619
  ${c.dim('Full list with docs links: the dashboard\'s Documentation tab, or the README.')}`,
618
620
  update: `${c.bold('spectoflow update')} ${c.dim('[--dry-run] [--force|-f]')}\n
619
- Refresh framework-owned files (engine, default agents & skills, AGENTS.md, policy…)
621
+ Refresh framework-owned files (engine, default agents & skills, SPECTOFLOW.md, policy…)
620
622
  to this CLI's version, ${c.bold('preserving your work')}: config.json, workflow.md, specs/, plans/
621
623
  and any agent/skill you edited are never overwritten (an edited file's new version lands as
622
624
  ${c.dim('*.new')} for you to merge). ${c.g('--dry-run')} previews without writing.
package/lib/adapters.js CHANGED
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
  /*
3
- * Per-agent adapters. The canonical brain is .spectoflow/AGENTS.md. Each agent needs a thin entry
3
+ * Per-agent adapters. The canonical brain is .spectoflow/SPECTOFLOW.md. Each agent needs a thin entry
4
4
  * file in the place IT reads, pointing back to the canonical brain — so the framework stays in
5
5
  * .spectoflow/ (swappable, no pollution) while every agent still finds its native entry point.
6
6
  * (Same pattern OpenSpec uses to support claude/codex/cursor/gemini/…)
@@ -14,7 +14,7 @@ const path = require('path');
14
14
 
15
15
  const CLAUDE_MD = `# CLAUDE.md — spectoflow
16
16
 
17
- This project uses **spectoflow**. **Read \`.spectoflow/AGENTS.md\` and follow it** as your operating
17
+ This project uses **spectoflow**. **Read \`.spectoflow/SPECTOFLOW.md\` and follow it** as your operating
18
18
  instructions (intent router, workflow, standing rules).
19
19
 
20
20
  > If \`CLAUDE.md.tomerge\` exists next to this file, it is your previous CLAUDE.md, preserved on
@@ -23,7 +23,7 @@ instructions (intent router, workflow, standing rules).
23
23
 
24
24
  **Be an expert analyst, not an order-taker.** When a request is ambiguous, **clarify before acting**:
25
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\`.
26
+ is clear — then execute. See the Clarify reflex in \`.spectoflow/SPECTOFLOW.md\`.
27
27
 
28
28
  - Command: \`/spectoflow\` (\`init\` / \`status\` / or just a request).
29
29
  - Dashboard: \`spectoflow dashboard\` → http://localhost:4319
@@ -32,30 +32,30 @@ is clear — then execute. See the Clarify reflex in \`.spectoflow/AGENTS.md\`.
32
32
 
33
33
  const ROOT_AGENTS_MD = `# AGENTS.md — spectoflow
34
34
 
35
- This project uses **spectoflow**. **Read \`.spectoflow/AGENTS.md\` and follow it** as your operating
35
+ This project uses **spectoflow**. **Read \`.spectoflow/SPECTOFLOW.md\` and follow it** as your operating
36
36
  instructions. Artifacts are markdown in \`specs/\` and \`plans/\`; the workflow is \`.spectoflow/workflow.md\`.
37
37
 
38
38
  **Be an expert analyst, not an order-taker.** When a request is ambiguous, **clarify before acting**:
39
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\`.
40
+ is clear — then execute. See the Clarify reflex in \`.spectoflow/SPECTOFLOW.md\`.
41
41
  `;
42
42
 
43
43
  const GEMINI_MD = `# GEMINI.md — spectoflow
44
44
 
45
- This project uses **spectoflow**. **Read \`.spectoflow/AGENTS.md\` and follow it** as your operating
45
+ This project uses **spectoflow**. **Read \`.spectoflow/SPECTOFLOW.md\` and follow it** as your operating
46
46
  instructions (intent router, workflow, standing rules). Artifacts are markdown in \`specs/\` and
47
47
  \`plans/\`; the workflow is \`.spectoflow/workflow.md\`.
48
48
 
49
49
  **Be an expert analyst, not an order-taker.** When a request is ambiguous, **clarify before acting**:
50
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\`.
51
+ is clear — then execute. See the Clarify reflex in \`.spectoflow/SPECTOFLOW.md\`.
52
52
  `;
53
53
 
54
54
  const SLASH_CMD = `---
55
55
  description: spectoflow — spec-driven control (init / status / or just a request)
56
56
  ---
57
57
 
58
- Read \`.spectoflow/AGENTS.md\` and \`.spectoflow/config.json\` first.
58
+ Read \`.spectoflow/SPECTOFLOW.md\` and \`.spectoflow/config.json\` first.
59
59
 
60
60
  Argument: \`$ARGUMENTS\`
61
61
 
@@ -63,7 +63,7 @@ Argument: \`$ARGUMENTS\`
63
63
  \`specs/\` and \`plans/\` are empty, greet me, state the mode, and start Intake (brainstorm → analysis
64
64
  → spec → plan) by asking what I want to build.
65
65
  - \`status\`: summarize progress from \`plans/*.md\` and \`.spectoflow/runtime.json\`.
66
- - otherwise: treat \`$ARGUMENTS\` as a request and run the Router in \`.spectoflow/AGENTS.md\`.
66
+ - otherwise: treat \`$ARGUMENTS\` as a request and run the Router in \`.spectoflow/SPECTOFLOW.md\`.
67
67
  `;
68
68
 
69
69
  // Priority order = which agent becomes the default when several are detected.
@@ -232,43 +232,60 @@ const POINTER_START = '<!-- spectoflow:start -->';
232
232
  const POINTER_END = '<!-- spectoflow:end -->';
233
233
  const POINTER_BLOCK = `${POINTER_START}\n${ROOT_AGENTS_MD.replace(/^# .*\n/, '## spectoflow\n').trimEnd()}\n${POINTER_END}\n`;
234
234
 
235
- const hasPointer = (text) => text.includes('.spectoflow/AGENTS.md');
235
+ // The brain was `.spectoflow/AGENTS.md` before 0.28 — renamed so it can't be mistaken for the
236
+ // project-root AGENTS.md. Pointers written back then are rewritten in place, never duplicated.
237
+ const BRAIN = '.spectoflow/SPECTOFLOW.md';
238
+ const LEGACY_BRAIN = '.spectoflow/AGENTS.md';
239
+ const ENTRY_FILES = [...MEMORY_FILES, '.claude/commands/spectoflow.md'];
236
240
 
237
- function appendPointer(fp, dryRun) {
238
- const text = fs.readFileSync(fp, 'utf8');
239
- if (hasPointer(text)) return false;
240
- if (!dryRun) fs.writeFileSync(fp, text.replace(/\s*$/, '') + '\n\n' + POINTER_BLOCK);
241
- return true;
241
+ // Bring one existing entry file up to date: rewrite a legacy pointer, then (memory files only)
242
+ // append the pointer section if it still has none. Returns what it did (or would do, on dryRun).
243
+ function linkEntry(fp, rel, dryRun) {
244
+ const before = fs.readFileSync(fp, 'utf8');
245
+ let text = before.split(LEGACY_BRAIN).join(BRAIN);
246
+ const repointed = text !== before;
247
+ const appended = MEMORY_FILES.includes(rel) && !text.includes(BRAIN);
248
+ if (appended) text = text.replace(/\s*$/, '') + '\n\n' + POINTER_BLOCK;
249
+ if ((repointed || appended) && !dryRun) fs.writeFileSync(fp, text);
250
+ return { repointed, appended };
242
251
  }
243
252
 
244
253
  // Write the native entry-file shims for each selected agent. Shared files (AGENTS.md across
245
- // codex/cursor) are handled once. A memory file that already exists gets the pointer appended.
254
+ // codex/cursor) are handled once. An entry file that already exists is linked instead (linkEntry).
246
255
  function generate(projectRoot, agents) {
247
256
  const list = (agents && agents.length ? agents : ['claude', 'codex']).map(byId).filter(Boolean);
248
- const written = [], appended = [];
257
+ const written = [], appended = [], repointed = [], seen = new Set();
249
258
  for (const a of list) {
250
259
  for (const e of a.entries) {
251
- if (written.includes(e.path) || appended.includes(e.path)) continue;
260
+ if (seen.has(e.path)) continue;
261
+ seen.add(e.path);
252
262
  const fp = path.join(projectRoot, e.path);
253
263
  if (!fs.existsSync(fp)) {
254
264
  fs.mkdirSync(path.dirname(fp), { recursive: true });
255
265
  fs.writeFileSync(fp, e.content);
256
266
  written.push(e.path);
257
- } else if (MEMORY_FILES.includes(e.path) && appendPointer(fp)) {
258
- appended.push(e.path);
267
+ continue;
259
268
  }
269
+ const r = linkEntry(fp, e.path, false);
270
+ if (r.appended) appended.push(e.path);
271
+ if (r.repointed) repointed.push(e.path);
260
272
  }
261
273
  }
262
- return { written, appended };
274
+ return { written, appended, repointed };
263
275
  }
264
276
 
265
- // For `update`: an already-installed project whose existing memory files never got the pointer.
266
- // Only repairs files that exist — never creates one for an agent the project may not use.
277
+ // For `update`: link every entry file that exists — never creates one for an agent the project may
278
+ // not use.
267
279
  function ensurePointers(projectRoot, dryRun = false) {
268
- return MEMORY_FILES.filter((rel) => {
280
+ const out = { appended: [], repointed: [] };
281
+ for (const rel of ENTRY_FILES) {
269
282
  const fp = path.join(projectRoot, rel);
270
- return fs.existsSync(fp) && appendPointer(fp, dryRun);
271
- });
283
+ if (!fs.existsSync(fp)) continue;
284
+ const r = linkEntry(fp, rel, dryRun);
285
+ if (r.appended) out.appended.push(rel);
286
+ if (r.repointed) out.repointed.push(rel);
287
+ }
288
+ return out;
272
289
  }
273
290
 
274
291
  // { id: runner } defaults for the given agents — used to seed config.runners at init.
@@ -287,4 +304,4 @@ function knownAgents() {
287
304
  return REGISTRY.map((a) => ({ id: a.id, label: a.label, bin: a.detect.bin, dirs: a.detect.dirs || [], runner: a.runner, headless: a.headless, docsUrl: a.docsUrl }));
288
305
  }
289
306
 
290
- module.exports = { generate, ensurePointers, defaultRunners, REGISTRY, knownAgents };
307
+ module.exports = { generate, ensurePointers, defaultRunners, REGISTRY, knownAgents, BRAIN, LEGACY_BRAIN };
@@ -1354,7 +1354,7 @@ function renderCustomDashboards(){
1354
1354
 
1355
1355
  // ---- Settings → Customize: add dashboards/skills/agents by description, or "Auto" -------------
1356
1356
  // Generation itself is real agent work (research, clarify, write a file) — this UI never does it
1357
- // client-side. It just constructs a plain-language prompt (recognized by AGENTS.md's Router, which
1357
+ // client-side. It just constructs a plain-language prompt (recognized by SPECTOFLOW.md's Router, which
1358
1358
  // hands it to framework-curator) and sends it through the SAME /api/run + group-chat pipeline every
1359
1359
  // other "Run" already uses, then jumps to Chat so the requester watches it happen and can answer any
1360
1360
  // clarifying question there — no separate conversational UI to build or keep in sync.
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
  /*
3
3
  * Dashboard UI translations. `config.json`'s `language` governs BOTH the agent's own output (specs,
4
- * plans, code comments — per AGENTS.md) AND, from here, this dashboard's chrome — so picking a
4
+ * plans, code comments — per SPECTOFLOW.md) AND, from here, this dashboard's chrome — so picking a
5
5
  * language actually changes what you see, not just what the agent writes.
6
6
  *
7
7
  * t(key, vars?) looks up I18N[currentLang][key], falls back to I18N.en[key], then to the key itself
@@ -93,7 +93,7 @@ en: {
93
93
  'meeting.sub':'One dated note per day for this project — write it yourself, or have the active agent draft it from recent tasks and chat activity.','meeting.history':'History','meeting.today':'today','meeting.generate':'Generate','meeting.generateTitle':'Generate today\'s note from recent activity','meeting.overwriteWarn':'This will overwrite today\'s note.','meeting.generateAnyway':'Generate anyway',
94
94
  'chat.widgetTitle':'Run an agent','chat.widgetSub':'Quick access · full view in the Chat tab',
95
95
  'chat.tabSub':'Full conversation with the runner — the same run as the widget, more room to read it.',
96
- 'chat.idle':'Type a request — the agent runs headless in this project with full memory (<code>CLAUDE.md → AGENTS.md</code>) and updates the board live.',
96
+ 'chat.idle':'Type a request — the agent runs headless in this project with full memory (<code>CLAUDE.md / AGENTS.md → .spectoflow/SPECTOFLOW.md</code>) and updates the board live.',
97
97
  'chat.inputPlaceholder':'e.g. Add a login feature with email + password',
98
98
  'chat.orchestrateTitle':'Walk the enabled workflow','chat.summarizeTitle':'Condense the recent activity into a summary','chat.clearTitle':'Clear the chat log',
99
99
  'chat.warn':'⚠ Launches a real agent (<code>config.json → runners</code>) that can modify files &amp; run commands.','chat.running':'Agent running…',
@@ -212,7 +212,7 @@ fr: {
212
212
  'meeting.sub':'Une note datée par jour pour ce projet — rédigez-la vous-même, ou laissez l’agent actif la rédiger à partir des tâches récentes et du chat.','meeting.history':'Historique','meeting.today':'aujourd’hui','meeting.generate':'Générer','meeting.generateTitle':'Générer la note du jour à partir de l’activité récente','meeting.overwriteWarn':'Cela va écraser la note d’aujourd’hui.','meeting.generateAnyway':'Générer quand même',
213
213
  'chat.widgetTitle':'Lancer un agent','chat.widgetSub':'Accès rapide · vue complète dans l’onglet Chat',
214
214
  'chat.tabSub':'Conversation complète avec l’exécuteur — la même exécution que le widget, avec plus de place pour la lire.',
215
- 'chat.idle':'Tapez une demande — l’agent s’exécute sans supervision dans ce projet avec toute sa mémoire (<code>CLAUDE.md → AGENTS.md</code>) et met le tableau à jour en direct.',
215
+ 'chat.idle':'Tapez une demande — l’agent s’exécute sans supervision dans ce projet avec toute sa mémoire (<code>CLAUDE.md / AGENTS.md → .spectoflow/SPECTOFLOW.md</code>) et met le tableau à jour en direct.',
216
216
  'chat.inputPlaceholder':'ex. Ajouter une fonctionnalité de connexion par email + mot de passe',
217
217
  'chat.orchestrateTitle':'Parcourir le workflow activé','chat.summarizeTitle':'Condenser l’activité récente en un résumé','chat.clearTitle':'Effacer le journal de discussion',
218
218
  'chat.warn':'⚠ Lance un agent réel (<code>config.json → runners</code>) qui peut modifier des fichiers et exécuter des commandes.','chat.running':'Agent en cours d’exécution…',
@@ -331,7 +331,7 @@ es: {
331
331
  'meeting.sub':'Una nota fechada por día para este proyecto — escríbela tú mismo, o deja que el agente activo la redacte a partir de las tareas y el chat recientes.','meeting.history':'Historial','meeting.today':'hoy','meeting.generate':'Generar','meeting.generateTitle':'Generar la nota de hoy a partir de la actividad reciente','meeting.overwriteWarn':'Esto sobrescribirá la nota de hoy.','meeting.generateAnyway':'Generar de todos modos',
332
332
  'chat.widgetTitle':'Ejecutar un agente','chat.widgetSub':'Acceso rápido · vista completa en la pestaña Chat',
333
333
  'chat.tabSub':'Conversación completa con el ejecutor — la misma ejecución que el widget, con más espacio para leerla.',
334
- 'chat.idle':'Escribe una solicitud — el agente se ejecuta sin supervisión en este proyecto con toda su memoria (<code>CLAUDE.md → AGENTS.md</code>) y actualiza el tablero en vivo.',
334
+ 'chat.idle':'Escribe una solicitud — el agente se ejecuta sin supervisión en este proyecto con toda su memoria (<code>CLAUDE.md / AGENTS.md → .spectoflow/SPECTOFLOW.md</code>) y actualiza el tablero en vivo.',
335
335
  'chat.inputPlaceholder':'p. ej. Añadir un inicio de sesión con email + contraseña',
336
336
  'chat.orchestrateTitle':'Recorrer el workflow activado','chat.summarizeTitle':'Condensar la actividad reciente en un resumen','chat.clearTitle':'Borrar el registro del chat',
337
337
  'chat.warn':'⚠ Lanza un agente real (<code>config.json → runners</code>) que puede modificar archivos y ejecutar comandos.','chat.running':'Agente en ejecución…',
@@ -450,7 +450,7 @@ de: {
450
450
  'meeting.sub':'Eine datierte Notiz pro Tag für dieses Projekt — schreibe sie selbst, oder lass sie vom aktiven Agenten aus den letzten Aufgaben und dem Chat entwerfen.','meeting.history':'Verlauf','meeting.today':'heute','meeting.generate':'Generieren','meeting.generateTitle':'Die heutige Notiz aus der letzten Aktivität generieren','meeting.overwriteWarn':'Dies überschreibt die heutige Notiz.','meeting.generateAnyway':'Trotzdem generieren',
451
451
  'chat.widgetTitle':'Agenten ausführen','chat.widgetSub':'Schnellzugriff · vollständige Ansicht im Chat-Tab',
452
452
  'chat.tabSub':'Vollständiges Gespräch mit dem Runner — derselbe Lauf wie im Widget, mit mehr Platz zum Lesen.',
453
- 'chat.idle':'Geben Sie eine Anfrage ein — der Agent läuft eigenständig in diesem Projekt mit vollem Gedächtnis (<code>CLAUDE.md → AGENTS.md</code>) und aktualisiert das Board live.',
453
+ 'chat.idle':'Geben Sie eine Anfrage ein — der Agent läuft eigenständig in diesem Projekt mit vollem Gedächtnis (<code>CLAUDE.md / AGENTS.md → .spectoflow/SPECTOFLOW.md</code>) und aktualisiert das Board live.',
454
454
  'chat.inputPlaceholder':'z. B. Login mit E-Mail + Passwort hinzufügen',
455
455
  'chat.orchestrateTitle':'Den aktivierten Workflow durchlaufen','chat.summarizeTitle':'Die letzten Aktivitäten zu einer Zusammenfassung verdichten','chat.clearTitle':'Chat-Verlauf löschen',
456
456
  'chat.warn':'⚠ Startet einen echten Agenten (<code>config.json → runners</code>), der Dateien ändern und Befehle ausführen kann.','chat.running':'Agent läuft…',
@@ -569,7 +569,7 @@ pt: {
569
569
  'meeting.sub':'Uma nota datada por dia para este projeto — escreva-a você mesmo, ou deixe o agente ativo redigi-la a partir das tarefas e do chat recentes.','meeting.history':'Histórico','meeting.today':'hoje','meeting.generate':'Gerar','meeting.generateTitle':'Gerar a nota de hoje a partir da atividade recente','meeting.overwriteWarn':'Isto vai substituir a nota de hoje.','meeting.generateAnyway':'Gerar mesmo assim',
570
570
  'chat.widgetTitle':'Executar um agente','chat.widgetSub':'Acesso rápido · vista completa no separador Chat',
571
571
  'chat.tabSub':'Conversa completa com o executor — a mesma execução do widget, com mais espaço para ler.',
572
- 'chat.idle':'Escreva um pedido — o agente corre sem supervisão neste projeto com toda a sua memória (<code>CLAUDE.md → AGENTS.md</code>) e atualiza o painel em direto.',
572
+ 'chat.idle':'Escreva um pedido — o agente corre sem supervisão neste projeto com toda a sua memória (<code>CLAUDE.md / AGENTS.md → .spectoflow/SPECTOFLOW.md</code>) e atualiza o painel em direto.',
573
573
  'chat.inputPlaceholder':'ex. Adicionar login com email + palavra-passe',
574
574
  'chat.orchestrateTitle':'Percorrer o workflow ativado','chat.summarizeTitle':'Condensar a atividade recente num resumo','chat.clearTitle':'Limpar o registo do chat',
575
575
  'chat.warn':'⚠ Inicia um agente real (<code>config.json → runners</code>) que pode alterar ficheiros e executar comandos.','chat.running':'Agente em execução…',
@@ -688,7 +688,7 @@ it: {
688
688
  'meeting.sub':'Una nota datata al giorno per questo progetto — scrivila tu stesso, oppure lascia che l’agente attivo la scriva a partire dalle attività e dalla chat recenti.','meeting.history':'Cronologia','meeting.today':'oggi','meeting.generate':'Genera','meeting.generateTitle':'Genera la nota di oggi dall’attività recente','meeting.overwriteWarn':'Questo sovrascriverà la nota di oggi.','meeting.generateAnyway':'Genera comunque',
689
689
  'chat.widgetTitle':'Avvia un agente','chat.widgetSub':'Accesso rapido · vista completa nella scheda Chat',
690
690
  'chat.tabSub':'Conversazione completa con l’esecutore — la stessa esecuzione del widget, con più spazio per leggerla.',
691
- 'chat.idle':'Digita una richiesta — l’agente viene eseguito senza supervisione in questo progetto con tutta la sua memoria (<code>CLAUDE.md → AGENTS.md</code>) e aggiorna la bacheca in diretta.',
691
+ 'chat.idle':'Digita una richiesta — l’agente viene eseguito senza supervisione in questo progetto con tutta la sua memoria (<code>CLAUDE.md / AGENTS.md → .spectoflow/SPECTOFLOW.md</code>) e aggiorna la bacheca in diretta.',
692
692
  'chat.inputPlaceholder':'es. Aggiungi un login con email + password',
693
693
  'chat.orchestrateTitle':'Percorri il workflow attivato','chat.summarizeTitle':'Condensa l’attività recente in un riassunto','chat.clearTitle':'Cancella il registro della chat',
694
694
  'chat.warn':'⚠ Avvia un agente reale (<code>config.json → runners</code>) che può modificare file ed eseguire comandi.','chat.running':'Agente in esecuzione…',
@@ -318,7 +318,7 @@
318
318
  </div>
319
319
  <div class="chat-tab-log" id="chatTabLog">
320
320
  <div class="chat-idle" data-i18n-html="chat.idle">Type a request — the agent runs headless in this project with full memory
321
- (<code>CLAUDE.md → AGENTS.md</code>) and updates the board live.</div>
321
+ (<code>CLAUDE.md / AGENTS.md → .spectoflow/SPECTOFLOW.md</code>) and updates the board live.</div>
322
322
  </div>
323
323
  <!-- Toolbar right above the input, not up in the header — this is where you're looking
324
324
  when you reach for Summarize/Clear or switch which agent the next message runs on. -->
@@ -455,7 +455,7 @@
455
455
  </div>
456
456
  <div class="chat-log" id="chatLog">
457
457
  <div class="chat-idle" data-i18n-html="chat.idle">Type a request — the agent runs headless in this project with full memory
458
- (<code>CLAUDE.md → AGENTS.md</code>) and updates the board live.</div>
458
+ (<code>CLAUDE.md / AGENTS.md → .spectoflow/SPECTOFLOW.md</code>) and updates the board live.</div>
459
459
  </div>
460
460
  <div class="chat-toolbar">
461
461
  <button id="widgetSummarizeBtn" class="mini-btn" data-i18n-title="chat.summarizeTitle" data-i18n="action.summarize" title="Condense the recent activity into a summary">Summarize</button>
package/lib/init.js CHANGED
@@ -99,8 +99,9 @@ function runInit({ target, templatesDir, version, agentsArg, defaults }) {
99
99
  const added = normalizePlans(target, cfg);
100
100
  if (added) notes.push(`Normalized ${added} existing task(s) with stable ids.`);
101
101
 
102
- const { written, appended } = adapters.generate(target, agents);
103
- appended.forEach((f) => notes.push(`Existing ${f} kept — a spectoflow section was appended to it so your agent finds .spectoflow/AGENTS.md.`));
102
+ const { written, appended, repointed } = adapters.generate(target, agents);
103
+ appended.forEach((f) => notes.push(`Existing ${f} kept — a spectoflow section was appended to it so your agent finds ${adapters.BRAIN}.`));
104
+ repointed.forEach((f) => notes.push(`${f} pointed to ${adapters.LEGACY_BRAIN} — now ${adapters.BRAIN}.`));
104
105
 
105
106
  const mcpTargets = [path.join(target, '.mcp.json')];
106
107
  if (agents.includes('cursor')) mcpTargets.push(path.join(target, '.cursor', 'mcp.json'));
package/lib/update.js CHANGED
@@ -30,7 +30,7 @@ const LEGACY_LEFTOVERS = ['dashboard', 'lib/store.js', 'lib/agents-registry.js',
30
30
  // Data migration (0.23 → 0.24): custom views out of the old dashboard folder, the per-project lock
31
31
  // and its .gitignore line gone. Runs before any removal, is idempotent, and never overwrites.
32
32
  function migrateProjectData(projectRoot, sf, dryRun) {
33
- const r = { movedViews: [], conflicts: [], removedLock: false, gitignoreCleaned: false };
33
+ const r = { movedViews: [], conflicts: [], removedLock: false, gitignoreCleaned: false, renamedBrain: false, repointedUserFiles: [] };
34
34
  const oldDir = path.join(sf, 'dashboard', 'custom'), newDir = path.join(sf, 'dashboards');
35
35
  if (fs.existsSync(oldDir)) {
36
36
  for (const f of fs.readdirSync(oldDir).filter((x) => x.endsWith('.json')).sort()) {
@@ -54,6 +54,42 @@ function migrateProjectData(projectRoot, sf, dryRun) {
54
54
  return r;
55
55
  }
56
56
 
57
+ // 0.28: the brain `AGENTS.md` became `SPECTOFLOW.md`. Treated as a move, not "retire + create": the
58
+ // file (edits included) and its manifest baseline follow the new name, so the normal matrix below
59
+ // then refreshes it if untouched or offers a .new if edited. In dry-run nothing moves; `readFrom`
60
+ // tells the matrix to read the brain from its old place instead.
61
+ const BRAIN = 'SPECTOFLOW.md', LEGACY_BRAIN = 'AGENTS.md';
62
+ function moveBrain(sf, templatesDir, baseline, dryRun) {
63
+ const kitHasBrain = fs.existsSync(toDisk(templatesDir, BRAIN)) && !fs.existsSync(toDisk(templatesDir, LEGACY_BRAIN));
64
+ const from = toDisk(sf, LEGACY_BRAIN), to = toDisk(sf, BRAIN);
65
+ if (!kitHasBrain || !fs.existsSync(from) || fs.existsSync(to)) return null;
66
+ if (baseline[LEGACY_BRAIN] !== undefined) { baseline[BRAIN] = baseline[LEGACY_BRAIN]; delete baseline[LEGACY_BRAIN]; }
67
+ if (!dryRun) fs.renameSync(from, to);
68
+ return { readFrom: dryRun ? from : to };
69
+ }
70
+
71
+ // Agent-read markdown the user owns (generated agents/skills) may name the old brain path. Kit files
72
+ // are skipped: the matrix owns them, and rewriting one would make it look user-edited.
73
+ function repointUserFiles(sf, templatesDir, dryRun) {
74
+ const kit = new Set(ownership.listFrameworkFiles(templatesDir));
75
+ const changed = [];
76
+ const walk = (rel) => {
77
+ const abs = toDisk(sf, rel);
78
+ if (!fs.existsSync(abs)) return;
79
+ for (const e of fs.readdirSync(abs, { withFileTypes: true })) {
80
+ const child = rel + '/' + e.name;
81
+ if (e.isDirectory()) { walk(child); continue; }
82
+ if (!e.name.endsWith('.md') || kit.has(child)) continue;
83
+ const fp = toDisk(sf, child), text = fs.readFileSync(fp, 'utf8');
84
+ if (!text.includes('.spectoflow/' + LEGACY_BRAIN)) continue;
85
+ changed.push(child);
86
+ if (!dryRun) fs.writeFileSync(fp, text.split('.spectoflow/' + LEGACY_BRAIN).join('.spectoflow/' + BRAIN));
87
+ }
88
+ };
89
+ walk('agents'); walk('skills');
90
+ return changed;
91
+ }
92
+
57
93
  // Remove `fp`, then every now-empty parent up to (not including) `stop`.
58
94
  function removeAndPrune(fp, stop) {
59
95
  fs.unlinkSync(fp);
@@ -84,9 +120,11 @@ function runUpdate({ projectRoot, templatesDir, version, dryRun = false, force =
84
120
  legacyLeftovers: [],
85
121
  pointers: [],
86
122
  };
87
- const baseline = (prev && prev.files) || {};
123
+ const baseline = { ...((prev && prev.files) || {}) };
88
124
  const nextFiles = {}; // manifest to write after this run
89
125
  report.migration = migrateProjectData(projectRoot, sf, dryRun);
126
+ const brainMove = moveBrain(sf, templatesDir, baseline, dryRun);
127
+ report.migration.renamedBrain = !!brainMove;
90
128
 
91
129
  const write = (fp, buf) => {
92
130
  if (dryRun) return;
@@ -97,7 +135,7 @@ function runUpdate({ projectRoot, templatesDir, version, dryRun = false, force =
97
135
  for (const rel of ownership.listFrameworkFiles(templatesDir)) {
98
136
  const newBuf = fs.readFileSync(toDisk(templatesDir, rel));
99
137
  const newHash = manifest.sha256(newBuf);
100
- const diskPath = toDisk(sf, rel);
138
+ const diskPath = rel === BRAIN && brainMove ? brainMove.readFrom : toDisk(sf, rel);
101
139
  const base = baseline[rel]; // undefined for legacy / brand-new files
102
140
 
103
141
  if (!fs.existsSync(diskPath)) {
@@ -148,6 +186,7 @@ function runUpdate({ projectRoot, templatesDir, version, dryRun = false, force =
148
186
  }
149
187
 
150
188
  report.pointers = adapters.ensurePointers(projectRoot, dryRun);
189
+ report.migration.repointedUserFiles = repointUserFiles(sf, templatesDir, dryRun);
151
190
 
152
191
  if (!dryRun) manifest.writeManifest(sf, { version, files: nextFiles });
153
192
  return report;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spectoflow",
3
- "version": "0.27.2",
3
+ "version": "0.28.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",
@@ -7,17 +7,17 @@ workflow, and tracks everything as **markdown artifacts** you can diff and own.
7
7
 
8
8
  Everything the framework needs lives here in `.spectoflow/`, so your project root stays clean and the
9
9
  framework is swappable/updatable. Your per-agent entry files (`CLAUDE.md`, `AGENTS.md`, `GEMINI.md`)
10
- sit at the project root and just point back here.
10
+ sit at the project root and just point back to `SPECTOFLOW.md`, here.
11
11
 
12
12
  ## How you use it
13
13
 
14
14
  - **Just say what you want** to your agent ("add a login feature", "fix T-042"). The router in
15
- `AGENTS.md` classifies it (quick / standard / major), gates it by your **mode** and **policy**, and
15
+ `SPECTOFLOW.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
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
- (skill `clarify`, wired into the agent's memory in `AGENTS.md`).
20
+ (skill `clarify`, wired into the agent's memory in `SPECTOFLOW.md`).
21
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
@@ -58,7 +58,7 @@ Your **artifacts are markdown, and they live at the project root, not in here**:
58
58
 
59
59
  | Path | What it is |
60
60
  |------|------------|
61
- | `AGENTS.md` | **The brain** — the intent router, the modes, and the standing rules your agent follows. |
61
+ | `SPECTOFLOW.md` | **The brain** — the intent router, the modes, and the standing rules your agent follows. |
62
62
  | `workflow.md` | The **single** workflow definition (the pipeline steps and their capability/skill). |
63
63
  | `capabilities.md` | The capability palette (intake, analysis, planning, implementation, testing, quality, security, governance…) and how it adapts to the project type. |
64
64
  | `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). |
@@ -1,7 +1,7 @@
1
1
  # spectoflow — project brain (read fully at session start)
2
2
 
3
- > Agent-agnostic. Any agent reading this — Claude Code (`CLAUDE.md` points here), Codex/Cursor
4
- > (`AGENTS.md`), etc. — knows how to behave. Keep it lean; details live in the files it points to.
3
+ > Agent-agnostic. Any agent reading this — Claude Code (`CLAUDE.md` points here), Codex/Cursor and most
4
+ > others (the project-root `AGENTS.md` points here), etc. — knows how to behave. Keep it lean; details live in the files it points to.
5
5
 
6
6
  ## What spectoflow is
7
7
 
@@ -12,7 +12,7 @@ surfaces drift to the Attention tab; it gates only at `done`/Major (see `policy.
12
12
  `clarify` is a **reflex under `intake`, not a workflow step** either: on *any* ambiguous request the
13
13
  agent reflects it back and asks **one targeted question at a time** (each with a recommendation) until
14
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`.
15
+ the Clarify step in `SPECTOFLOW.md`.
16
16
 
17
17
  `customization` is also **not a workflow step** — it is triggered explicitly, either from the
18
18
  dashboard's Settings → Customize page or by a direct request ("add a dashboard for…", "create a skill
@@ -10,7 +10,7 @@ standard: requirements elicitation
10
10
 
11
11
  Turn a vague request into a crisp, agreed need **before** classifying or acting — the way a good
12
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*
13
+ agent's memory (see the Clarify step in `SPECTOFLOW.md`), not a workflow stage — it fires on *any*
14
14
  request, including bug reports and change requests on an existing project ("the login page doesn't
15
15
  display well, users can't sign in").
16
16
 
@@ -96,7 +96,7 @@ list.
96
96
 
97
97
  ### 6. Resolve capability collisions explicitly
98
98
 
99
- `.spectoflow/AGENTS.md`'s routing assumes one agent per capability unless a `priority` is set (see the
99
+ `.spectoflow/SPECTOFLOW.md`'s routing assumes one agent per capability unless a `priority` is set (see the
100
100
  front-matter rules in `docs/agents-skills-standard.md`). If the chosen capability already has an
101
101
  agent, either pick a different, more precise capability for this role, or set `priority` deliberately
102
102
  and tell the user which agent now wins ties — never leave two agents silently competing for the same