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 +8 -6
- package/bin/spectoflow.js +5 -3
- package/lib/adapters.js +44 -27
- package/lib/dashboard/public/app.js +1 -1
- package/lib/dashboard/public/i18n.js +7 -7
- package/lib/dashboard/public/index.html +2 -2
- package/lib/init.js +3 -2
- package/lib/update.js +42 -3
- package/package.json +1 -1
- package/templates/README.md +4 -4
- package/templates/{AGENTS.md → SPECTOFLOW.md} +2 -2
- package/templates/capabilities.md +1 -1
- package/templates/skills/clarify/SKILL.md +1 -1
- package/templates/skills/generate-agent/SKILL.md +1 -1
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/
|
|
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
|
-
**
|
|
126
|
-
reads natively. The real brain (intent router, workflow, rules) is
|
|
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, `
|
|
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 (`
|
|
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
|
-
|
|
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,
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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
|
-
|
|
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
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
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.
|
|
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 (
|
|
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
|
-
|
|
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`:
|
|
266
|
-
//
|
|
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
|
-
|
|
280
|
+
const out = { appended: [], repointed: [] };
|
|
281
|
+
for (const rel of ENTRY_FILES) {
|
|
269
282
|
const fp = path.join(projectRoot, rel);
|
|
270
|
-
|
|
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
|
|
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
|
|
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
|
|
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 & 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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 .
|
|
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.
|
|
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",
|
package/templates/README.md
CHANGED
|
@@ -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
|
-
`
|
|
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 `
|
|
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
|
-
| `
|
|
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 `
|
|
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 `
|
|
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/
|
|
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
|