spectoflow 0.17.5 → 0.18.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 +11 -0
- package/package.json +1 -1
- package/templates/AGENTS.md +29 -3
- package/templates/README.md +5 -1
- package/templates/agents/framework-curator.md +94 -0
- package/templates/capabilities.md +10 -1
- package/templates/dashboard/custom/.gitkeep +3 -0
- package/templates/dashboard/public/app.js +183 -4
- package/templates/dashboard/public/i18n.js +30 -0
- package/templates/dashboard/public/index.html +25 -17
- package/templates/dashboard/public/styles.css +27 -0
- package/templates/dashboard/server.js +7 -1
- package/templates/lib/custom-dashboard.js +76 -0
- package/templates/lib/store.js +29 -5
- package/templates/skills/generate-agent/SKILL.md +135 -0
- package/templates/skills/generate-dashboard/SKILL.md +152 -0
- package/templates/skills/generate-skill/SKILL.md +153 -0
- package/templates/skills/propose-customizations/SKILL.md +72 -0
|
@@ -70,7 +70,13 @@ function promoteAttention(item){
|
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
function watch(dir){ try{ fs.watch(dir,{recursive:false},()=>emit({type:'change'})); }catch(_){} }
|
|
73
|
-
|
|
73
|
+
// Custom dashboards (Customize page) live in their own subdirectory of .spectoflow, which the
|
|
74
|
+
// top-level `.spectoflow` watch below does NOT cover — fs.watch here is non-recursive on purpose
|
|
75
|
+
// (a recursive watch on the whole .spectoflow tree would also fire on every runtime.json write).
|
|
76
|
+
// Ensure the directory exists before watching it: a project that hasn't used Customize yet won't
|
|
77
|
+
// have it on disk, and `spectoflow init` on an older install won't have created it either.
|
|
78
|
+
try { fs.mkdirSync(path.join(ROOT,'.spectoflow','dashboard','custom'), { recursive: true }); } catch (_) {}
|
|
79
|
+
['plans','specs','.spectoflow','.spectoflow/dashboard/custom'].forEach(d=>{ const p=path.join(ROOT,d); if(fs.existsSync(p)) watch(p); });
|
|
74
80
|
|
|
75
81
|
// A process restart loses any in-flight orchestration; without this, a stale 'running' or
|
|
76
82
|
// 'awaiting_approval' status wedges the /api/orchestrate 409 guard forever. Not a real
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
/*
|
|
3
|
+
* Pure helpers for user-generated custom dashboards (.spectoflow/dashboard/custom/<id>.json).
|
|
4
|
+
*
|
|
5
|
+
* A custom dashboard is a DECLARATIVE block spec, never raw HTML/CSS/JS: the generating agent picks
|
|
6
|
+
* blocks from a fixed vocabulary (BLOCK_TYPES) that the dashboard already knows how to render, using
|
|
7
|
+
* the exact same token-driven components (kpi cards, bars, donut, tables…) the built-in Board uses.
|
|
8
|
+
* That is what guarantees a custom dashboard always matches the active design — including any design
|
|
9
|
+
* the user switches to later — with zero per-dashboard styling to keep in sync, and no arbitrary code
|
|
10
|
+
* ever running in the dashboard.
|
|
11
|
+
*
|
|
12
|
+
* Zero dependency; consumed by templates/dashboard/server.js (Node) via readCustomDashboards() in
|
|
13
|
+
* store.js. The browser-side renderer (dashboard/public/app.js) re-implements the tiny `resolveBind`
|
|
14
|
+
* walk independently — sharing code across the Node/browser boundary would need a build step, which
|
|
15
|
+
* this project avoids on purpose (see CLAUDE.md's zero-runtime-dependency invariant).
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
// Every block a generated dashboard may use. Adding a new type here is how the vocabulary grows —
|
|
19
|
+
// pair it with a matching case in app.js's renderCustomBlock().
|
|
20
|
+
const BLOCK_TYPES = new Set([
|
|
21
|
+
'markdown', // rendered prose — a spec excerpt, an explanation, a summary
|
|
22
|
+
'kpi-row', // a row of big-number stat cards, each optionally live-bound
|
|
23
|
+
'chart-bars', // horizontal progress/comparison bars
|
|
24
|
+
'chart-donut', // a status/category breakdown donut + legend
|
|
25
|
+
'table', // a simple column/row data table
|
|
26
|
+
'list', // a flat bullet list
|
|
27
|
+
'stat-tile-row', // a row of compact stat tiles (value/label/sub)
|
|
28
|
+
]);
|
|
29
|
+
|
|
30
|
+
// A small, explicit allow-list of live data paths a block may `bind` to, resolved against the same
|
|
31
|
+
// stats object SpectoStats.stats(P) already computes for the built-in Board — never an arbitrary
|
|
32
|
+
// expression, just a dotted property walk, so there is nothing to sandbox or evaluate.
|
|
33
|
+
// Mirrors the exact shape SpectoStats.stats(P) returns (dashboard/public/stats.js):
|
|
34
|
+
// { total, done, pct, byStatus, phases, toAsk, running, statuses }.
|
|
35
|
+
const BIND_ROOTS = new Set(['pct', 'done', 'total', 'byStatus', 'phases', 'toAsk', 'running', 'statuses']);
|
|
36
|
+
|
|
37
|
+
const ID_RE = /^[a-z][a-z0-9-]{0,39}$/;
|
|
38
|
+
|
|
39
|
+
// A conservative, curated icon key set — the same ICON map the rest of the dashboard already uses
|
|
40
|
+
// (icons.js), so a custom dashboard's tab never introduces a one-off, unstyled icon.
|
|
41
|
+
const ICON_KEYS = new Set(['board', 'requests', 'backlog', 'workflow', 'agents', 'chat', 'info', 'attention', 'settings']);
|
|
42
|
+
|
|
43
|
+
function isPlainObject(v) { return v != null && typeof v === 'object' && !Array.isArray(v); }
|
|
44
|
+
|
|
45
|
+
// Validates one block. Returns a list of error strings (empty = valid). Deliberately permissive on
|
|
46
|
+
// the *content* fields (labels, values, markdown text are free text) — it only enforces the block's
|
|
47
|
+
// *shape* (a known type, and that any `bind` path starts from an allowed root) so a slightly unusual
|
|
48
|
+
// but well-typed spec still renders rather than being rejected outright.
|
|
49
|
+
function validateBlock(b, i) {
|
|
50
|
+
const errs = [];
|
|
51
|
+
const at = `blocks[${i}]`;
|
|
52
|
+
if (!isPlainObject(b)) { errs.push(`${at} is not an object`); return errs; }
|
|
53
|
+
if (!BLOCK_TYPES.has(b.type)) errs.push(`${at}.type "${b.type}" is not a known block type (${[...BLOCK_TYPES].join(', ')})`);
|
|
54
|
+
const binds = [];
|
|
55
|
+
if (typeof b.bind === 'string') binds.push(b.bind);
|
|
56
|
+
if (Array.isArray(b.items)) b.items.forEach((it) => { if (it && typeof it.bind === 'string') binds.push(it.bind); });
|
|
57
|
+
if (Array.isArray(b.rows)) b.rows.forEach((r) => { if (r && typeof r.bind === 'string') binds.push(r.bind); });
|
|
58
|
+
if (Array.isArray(b.segments)) b.segments.forEach((s) => { if (s && typeof s.bind === 'string') binds.push(s.bind); });
|
|
59
|
+
binds.forEach((p) => { const root = String(p).split('.')[0]; if (!BIND_ROOTS.has(root)) errs.push(`${at} has an unbound bind path "${p}" (must start with one of: ${[...BIND_ROOTS].join(', ')})`); });
|
|
60
|
+
return errs;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Validates a whole dashboard spec as read from disk. Never throws — callers (store.js) should skip
|
|
64
|
+
// an invalid file rather than let one bad custom dashboard break the whole /api/project response.
|
|
65
|
+
function validateSpec(spec) {
|
|
66
|
+
const errors = [];
|
|
67
|
+
if (!isPlainObject(spec)) return { valid: false, errors: ['not an object'] };
|
|
68
|
+
if (!ID_RE.test(String(spec.id || ''))) errors.push('id must be lowercase kebab-case, starting with a letter, 1-40 chars');
|
|
69
|
+
if (!spec.title || typeof spec.title !== 'string') errors.push('title is required (a short display name)');
|
|
70
|
+
if (spec.icon != null && !ICON_KEYS.has(spec.icon)) errors.push(`icon "${spec.icon}" is not one of: ${[...ICON_KEYS].join(', ')}`);
|
|
71
|
+
if (!Array.isArray(spec.blocks) || !spec.blocks.length) errors.push('blocks must be a non-empty array');
|
|
72
|
+
else spec.blocks.forEach((b, i) => errors.push(...validateBlock(b, i)));
|
|
73
|
+
return { valid: errors.length === 0, errors };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
module.exports = { BLOCK_TYPES, BIND_ROOTS, ICON_KEYS, validateSpec, validateBlock };
|
package/templates/lib/store.js
CHANGED
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
*/
|
|
18
18
|
const fs = require('fs');
|
|
19
19
|
const path = require('path');
|
|
20
|
+
const { validateSpec } = require('./custom-dashboard');
|
|
20
21
|
|
|
21
22
|
// ---- task line parsing -------------------------------------------------------
|
|
22
23
|
// - [ ] T-012 Title here @owner ~level %status
|
|
@@ -226,6 +227,23 @@ function readWorkflow(projectRoot) {
|
|
|
226
227
|
} catch { return []; }
|
|
227
228
|
}
|
|
228
229
|
|
|
230
|
+
// ---- user-generated custom dashboards (.spectoflow/dashboard/custom/<id>.json) --------------
|
|
231
|
+
// One JSON file per custom dashboard page (see lib/custom-dashboard.js for the block schema this
|
|
232
|
+
// validates against). A malformed file is skipped, never thrown — one bad custom dashboard must
|
|
233
|
+
// never take down the whole /api/project response.
|
|
234
|
+
function readCustomDashboards(projectRoot) {
|
|
235
|
+
const dir = path.join(projectRoot, '.spectoflow', 'dashboard', 'custom');
|
|
236
|
+
if (!fs.existsSync(dir)) return [];
|
|
237
|
+
const out = [];
|
|
238
|
+
for (const f of fs.readdirSync(dir).filter((x) => x.endsWith('.json')).sort()) {
|
|
239
|
+
try {
|
|
240
|
+
const spec = JSON.parse(fs.readFileSync(path.join(dir, f), 'utf8'));
|
|
241
|
+
if (validateSpec(spec).valid) out.push(spec);
|
|
242
|
+
} catch { /* skip malformed */ }
|
|
243
|
+
}
|
|
244
|
+
return out;
|
|
245
|
+
}
|
|
246
|
+
|
|
229
247
|
// ---- unified read for the dashboard -----------------------------------------
|
|
230
248
|
function readProject(projectRoot) {
|
|
231
249
|
const config = readConfig(projectRoot);
|
|
@@ -235,6 +253,7 @@ function readProject(projectRoot) {
|
|
|
235
253
|
const specs = readSpecs(projectRoot);
|
|
236
254
|
const agents = listMd(path.join(projectRoot, '.spectoflow', 'agents'));
|
|
237
255
|
const skills = listSkills(path.join(projectRoot, '.spectoflow', 'skills'));
|
|
256
|
+
const customDashboards = readCustomDashboards(projectRoot);
|
|
238
257
|
|
|
239
258
|
// Write-guarded snapshot: readProject is polled continuously by the dashboard (and reacts to
|
|
240
259
|
// fs.watch on .spectoflow). Recording unconditionally on every read would rewrite runtime.json
|
|
@@ -260,7 +279,7 @@ function readProject(projectRoot) {
|
|
|
260
279
|
runtime = writeRuntime(projectRoot, cur);
|
|
261
280
|
}
|
|
262
281
|
|
|
263
|
-
return { config, plans, specs, workflow, agents, skills, runtime };
|
|
282
|
+
return { config, plans, specs, workflow, agents, skills, runtime, customDashboards };
|
|
264
283
|
}
|
|
265
284
|
function frontmatter(text) {
|
|
266
285
|
const m = String(text).replace(/\r\n?/g, '\n').match(/^---\n([\s\S]*?)\n---/);
|
|
@@ -274,12 +293,17 @@ function parseFlatList(raw) {
|
|
|
274
293
|
if (!raw) return [];
|
|
275
294
|
return String(raw).replace(/[[\]]/g, '').split(',').map((s) => s.trim()).filter(Boolean);
|
|
276
295
|
}
|
|
296
|
+
// `origin: user-generated` in a file's front-matter (written by generate-skill/generate-agent, see
|
|
297
|
+
// templates/skills/generate-skill and generate-agent) marks it as created through the Customize page
|
|
298
|
+
// rather than shipped by the framework — the dashboard's Customize section uses this `custom` flag to
|
|
299
|
+
// list only the user's own additions, distinct from the framework-shipped roster.
|
|
300
|
+
const isCustomOrigin = (fm) => fm.origin === 'user-generated';
|
|
277
301
|
function listMd(dir) {
|
|
278
302
|
if (!fs.existsSync(dir)) return [];
|
|
279
303
|
return fs.readdirSync(dir).filter((f) => f.endsWith('.md')).map((f) => {
|
|
280
304
|
const fm = frontmatter(fs.readFileSync(path.join(dir, f), 'utf8'));
|
|
281
305
|
return { file: f, name: fm.name || f.replace(/\.md$/, ''), title: fm.title || fm.name || f, capability: fm.capability || '', description: fm.description || '',
|
|
282
|
-
standards: parseFlatList(fm.standards), uses: parseFlatList(fm.uses) };
|
|
306
|
+
standards: parseFlatList(fm.standards), uses: parseFlatList(fm.uses), custom: isCustomOrigin(fm) };
|
|
283
307
|
});
|
|
284
308
|
}
|
|
285
309
|
function listSkills(dir) {
|
|
@@ -288,7 +312,7 @@ function listSkills(dir) {
|
|
|
288
312
|
const sk = path.join(dir, e.name, 'SKILL.md');
|
|
289
313
|
const fm = fs.existsSync(sk) ? frontmatter(fs.readFileSync(sk, 'utf8')) : {};
|
|
290
314
|
return { name: fm.name || e.name, description: fm.description || '', capability: fm.capability || '',
|
|
291
|
-
inputs: fm.inputs || '', outputs: fm.outputs || '', standard: fm.standard || '' };
|
|
315
|
+
inputs: fm.inputs || '', outputs: fm.outputs || '', standard: fm.standard || '', custom: isCustomOrigin(fm) };
|
|
292
316
|
});
|
|
293
317
|
}
|
|
294
318
|
function readAgents(projectRoot) {
|
|
@@ -298,7 +322,7 @@ function readAgents(projectRoot) {
|
|
|
298
322
|
const fm = frontmatter(fs.readFileSync(path.join(dir, f), 'utf8'));
|
|
299
323
|
return { name: fm.name || f.replace(/\.md$/, ''), capability: fm.capability || null,
|
|
300
324
|
title: fm.title || '', description: fm.description || '',
|
|
301
|
-
standards: parseFlatList(fm.standards), uses: parseFlatList(fm.uses) };
|
|
325
|
+
standards: parseFlatList(fm.standards), uses: parseFlatList(fm.uses), custom: isCustomOrigin(fm) };
|
|
302
326
|
});
|
|
303
327
|
}
|
|
304
328
|
function readSkills(projectRoot) {
|
|
@@ -308,5 +332,5 @@ function readSkills(projectRoot) {
|
|
|
308
332
|
module.exports = {
|
|
309
333
|
parseTaskLine, buildTaskLine, parsePlan, readPlans, readSpecs, updateTaskLine, addTaskComment,
|
|
310
334
|
readRuntime, writeRuntime, parseAgentLine, appendMessage, readConfig, readWorkflow, readProject,
|
|
311
|
-
readAgents, readSkills, recordSnapshot, resolvePlansDir, resolveSpecsDir,
|
|
335
|
+
readAgents, readSkills, readCustomDashboards, recordSnapshot, resolvePlansDir, resolveSpecsDir,
|
|
312
336
|
};
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: generate-agent
|
|
3
|
+
description: Turn a description (or an auto-analysis) into a new agent persona, grounded in real named methods and matching the framework's gold-standard shape.
|
|
4
|
+
capability: customization
|
|
5
|
+
inputs: A description of the role needed (from the Customize page or chat), or a chosen candidate from propose-customizations; the project's existing agents as worked examples.
|
|
6
|
+
outputs: A new .spectoflow/agents/<slug>.md matching docs/agents-skills-standard.md's shape, listed in the dashboard's Agents & Skills tab on the next tick.
|
|
7
|
+
standard: docs/agents-skills-standard.md gold-standard shape
|
|
8
|
+
---
|
|
9
|
+
# Generate agent
|
|
10
|
+
|
|
11
|
+
Turn a described role into a real agent persona — a stable team member with a clear mandate, named
|
|
12
|
+
operating standards, and guardrails, that reads like it shipped with the framework's own roster.
|
|
13
|
+
|
|
14
|
+
## When to use
|
|
15
|
+
|
|
16
|
+
Whenever the user asks (from the Customize page, or directly in chat) to **add an agent** — "I want a
|
|
17
|
+
data-migration specialist", "add an accessibility reviewer", "create an agent for API contract
|
|
18
|
+
reviews" — or when `propose-customizations` proposed an agent candidate the user picked.
|
|
19
|
+
|
|
20
|
+
## Method
|
|
21
|
+
|
|
22
|
+
### 1. Clarify before generating
|
|
23
|
+
|
|
24
|
+
A one-line ask ("add a data agent") is under-specified. Use `.spectoflow/skills/clarify`'s reflex —
|
|
25
|
+
one targeted question at a time, each with a recommended default — until you know:
|
|
26
|
+
- **What this role owns that no existing agent already owns.** Read `.spectoflow/agents/*.md` first —
|
|
27
|
+
a new agent for a capability an existing one already covers is redundant; either the existing agent
|
|
28
|
+
should gain a skill instead (see `generate-skill`), or this really is a distinct capability.
|
|
29
|
+
- **Which capability it serves.** Pick the closest match from `.spectoflow/capabilities.md`'s palette,
|
|
30
|
+
or note that this genuinely needs a new capability name (rare — most real needs fit the existing
|
|
31
|
+
palette; propose adding to the palette only when nothing fits).
|
|
32
|
+
- **What skill(s) it runs.** An agent without at least one skill in `uses` has no procedure to
|
|
33
|
+
execute — either an existing skill fits, or this request also needs `generate-skill` (sequence the
|
|
34
|
+
two: skill first, so the agent's `uses` list is accurate from the start).
|
|
35
|
+
|
|
36
|
+
### 2. Remember the agent/skill split
|
|
37
|
+
|
|
38
|
+
Per the framework's own core invariant: **agents are stable personas (the who); skills are the
|
|
39
|
+
evolving procedures (the how).** This agent's file should describe *who* the role is and what it's
|
|
40
|
+
accountable for — the actual step-by-step method belongs in its skill(s), referenced via `uses`, not
|
|
41
|
+
duplicated here. An agent file heavy with procedural detail has blurred the split; move that content
|
|
42
|
+
into a skill instead.
|
|
43
|
+
|
|
44
|
+
### 3. Ground the operating standards in named, real methods
|
|
45
|
+
|
|
46
|
+
Per `docs/agents-skills-standard.md`, `## Operating standards` names **cited methods**, each with a
|
|
47
|
+
one-line *why* — the same discipline every shipped agent already follows (open a couple as worked
|
|
48
|
+
examples: `qa-engineer` cites Kent Beck's TDD and Meszaros's xUnit Test Patterns; `security-engineer`
|
|
49
|
+
cites OWASP ASVS and the Top 10; `architect` cites C4 and ADRs). Identify the real, current, named
|
|
50
|
+
authority for this role's domain the same way `generate-skill`'s Method (step 2 there) describes —
|
|
51
|
+
verify it with your environment's research tools rather than relying purely on memory for a
|
|
52
|
+
fast-moving domain, and if no real standard exists for the role's specific angle, say so explicitly
|
|
53
|
+
and reason from first principles instead of fabricating a citation.
|
|
54
|
+
|
|
55
|
+
### 4. Write the agent in the gold-standard shape
|
|
56
|
+
|
|
57
|
+
Follow `docs/agents-skills-standard.md`'s agent shape exactly:
|
|
58
|
+
|
|
59
|
+
```yaml
|
|
60
|
+
---
|
|
61
|
+
name: <slug>
|
|
62
|
+
title: <Team title>
|
|
63
|
+
capability: <the palette capability chosen in step 1>
|
|
64
|
+
uses: [<skill-slug>, ...]
|
|
65
|
+
description: <one line>
|
|
66
|
+
standards: [<named method or source>, ...]
|
|
67
|
+
---
|
|
68
|
+
# <Title>
|
|
69
|
+
<1-2 line intro naming the persona and the capability it serves>
|
|
70
|
+
|
|
71
|
+
## Mandate
|
|
72
|
+
<who/why, 1-2 lines — what this role owns>
|
|
73
|
+
|
|
74
|
+
## Operating standards
|
|
75
|
+
<named, cited methods this role applies, each with a one-line why — from step 3>
|
|
76
|
+
|
|
77
|
+
## Definition of done
|
|
78
|
+
<concrete, checkable exit criteria for this role's contribution>
|
|
79
|
+
|
|
80
|
+
## Handoff
|
|
81
|
+
<what it produces and to whom — feeds the group-chat identity + orchestrator>
|
|
82
|
+
|
|
83
|
+
## Guardrails
|
|
84
|
+
<what it must never do — ties to .spectoflow/policy.md where relevant>
|
|
85
|
+
|
|
86
|
+
## References
|
|
87
|
+
<the real, verified sources from step 3, as titled links>
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### 5. Mark it as user-generated
|
|
91
|
+
|
|
92
|
+
Add `origin: user-generated` to the front-matter (an extra key — never remove or rename the required
|
|
93
|
+
ones: `name`, `title`, `capability`, `uses`, `description`). This is how the dashboard's Customize
|
|
94
|
+
page distinguishes what the user added from the shipped roster; omitting it hides the agent from that
|
|
95
|
+
list.
|
|
96
|
+
|
|
97
|
+
### 6. Resolve capability collisions explicitly
|
|
98
|
+
|
|
99
|
+
`.spectoflow/AGENTS.md`'s routing assumes one agent per capability unless a `priority` is set (see the
|
|
100
|
+
front-matter rules in `docs/agents-skills-standard.md`). If the chosen capability already has an
|
|
101
|
+
agent, either pick a different, more precise capability for this role, or set `priority` deliberately
|
|
102
|
+
and tell the user which agent now wins ties — never leave two agents silently competing for the same
|
|
103
|
+
capability with no way to tell which runs.
|
|
104
|
+
|
|
105
|
+
## Output contract
|
|
106
|
+
|
|
107
|
+
- One file: `.spectoflow/agents/<slug>.md`, matching the gold-standard shape, with
|
|
108
|
+
`origin: user-generated` in its front-matter.
|
|
109
|
+
- Progress and completion reported to the orchestrator and group chat:
|
|
110
|
+
|
|
111
|
+
```
|
|
112
|
+
::spectoflow role=customization kind=progress msg=Drafting agent "<title>" (capability <capability>)
|
|
113
|
+
::spectoflow role=customization kind=need msg=<what's missing, e.g. no skill yet for this agent to use>
|
|
114
|
+
::spectoflow role=customization kind=done msg=Agent "<title>" added — see it in Agents & Skills
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## Quality bar
|
|
118
|
+
|
|
119
|
+
- [ ] Front-matter matches the gold-standard shape exactly, plus `origin: user-generated`.
|
|
120
|
+
- [ ] Body has exactly the five required `##` headings, in order.
|
|
121
|
+
- [ ] `uses` lists at least one real, existing (or just-generated) skill — never an empty list.
|
|
122
|
+
- [ ] `## Operating standards` names real, verified, cited methods — or explicitly says none exist for
|
|
123
|
+
this angle and reasons from first principles instead. Never a fabricated citation.
|
|
124
|
+
- [ ] No capability collision left unresolved (step 6) — or the `priority` tie-break is explicit and
|
|
125
|
+
explained to the user.
|
|
126
|
+
- [ ] The role is genuinely distinct from every existing agent — not a duplicate the user could have
|
|
127
|
+
gotten by adding a skill to one that already exists.
|
|
128
|
+
- [ ] If the ask was ambiguous, it was clarified one question at a time before any file was written.
|
|
129
|
+
|
|
130
|
+
## References
|
|
131
|
+
|
|
132
|
+
- `docs/agents-skills-standard.md` — the gold-standard shape this agent's output must match exactly.
|
|
133
|
+
- Any shipped agent under `.spectoflow/agents/` (e.g. `qa-engineer`, `security-engineer`,
|
|
134
|
+
`spec-source-guardian`) — worked examples of real citation density in `## Operating standards`.
|
|
135
|
+
- `.spectoflow/capabilities.md` — the capability palette a new agent's `capability` must fit.
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: generate-dashboard
|
|
3
|
+
description: Turn a description (or an auto-analysis) into a new custom dashboard page, as a declarative block spec that automatically matches every design the dashboard ships.
|
|
4
|
+
capability: customization
|
|
5
|
+
inputs: A description of what the dashboard should show (from the Customize page or chat), or a chosen candidate from propose-customizations; the project's specs/plans/code as source material.
|
|
6
|
+
outputs: A validated block-spec JSON file at .spectoflow/dashboard/custom/<id>.json, live in the dashboard's nav on the next tick.
|
|
7
|
+
standard: declarative UI generation; Few's dashboard design principles
|
|
8
|
+
---
|
|
9
|
+
# Generate dashboard
|
|
10
|
+
|
|
11
|
+
Turn a described need into a new dashboard page for *this* project — added to the dashboard's own
|
|
12
|
+
navigation, rendered by the dashboard's own components, so it looks and behaves like it shipped with
|
|
13
|
+
the framework, not like a plugin bolted on.
|
|
14
|
+
|
|
15
|
+
## When to use
|
|
16
|
+
|
|
17
|
+
Whenever the user asks (from the Customize page, or directly in chat) to **add a dashboard** —
|
|
18
|
+
"I want a dashboard that shows my architecture", "add a page tracking API endpoint coverage", "show me
|
|
19
|
+
a dashboard of open security findings" — or when `propose-customizations` proposed a dashboard
|
|
20
|
+
candidate the user picked.
|
|
21
|
+
|
|
22
|
+
## Method
|
|
23
|
+
|
|
24
|
+
### 1. Never generate raw markup — only the declarative block vocabulary
|
|
25
|
+
|
|
26
|
+
The dashboard renders a custom page from a **JSON block spec**, using the exact same components the
|
|
27
|
+
built-in Board uses (`kpiCard`, `ocard`, `bars`, `donut`, a table builder, `mdLite` for markdown —
|
|
28
|
+
see `dashboard/public/app.js`). This is not a stylistic preference: it is the mechanism that makes the
|
|
29
|
+
result correct.
|
|
30
|
+
|
|
31
|
+
- Every block type is styled entirely through the active design's CSS custom properties
|
|
32
|
+
(`--signal`, `--surface`, `--line`, `--s-done`, …). Nothing in a block spec ever sets a literal
|
|
33
|
+
color, font, radius, or shadow.
|
|
34
|
+
- Because of that, a dashboard generated under one design (say, the default Spectral Console) renders
|
|
35
|
+
correctly, unmodified, under every other shipped design (Orbit, Control Room, Obsidian Ops, Neon
|
|
36
|
+
Command, Mission Control) — including any the user switches to **after** this dashboard was
|
|
37
|
+
generated. There is nothing design-specific to regenerate or maintain.
|
|
38
|
+
- It is also the safety boundary: a block spec is data, never executable code, so nothing this skill
|
|
39
|
+
writes can run arbitrary script in the user's dashboard.
|
|
40
|
+
|
|
41
|
+
**Never** write HTML, CSS, or JS for a custom dashboard, and never suggest doing so "for more
|
|
42
|
+
flexibility" — if the vocabulary genuinely can't express what's needed, say so explicitly (raise a
|
|
43
|
+
`need`) rather than stepping outside it.
|
|
44
|
+
|
|
45
|
+
### 2. Clarify before generating
|
|
46
|
+
|
|
47
|
+
A one-line ask ("add a dashboard for my project") is under-specified — you don't yet know what it
|
|
48
|
+
should show. Use `.spectoflow/skills/clarify`'s reflex: reflect the ask back, then ask one targeted
|
|
49
|
+
question at a time, each with a recommended default, until you know:
|
|
50
|
+
- **What it should show** (which data/content — an architecture overview, security posture, a
|
|
51
|
+
specific spec's status, custom KPIs…).
|
|
52
|
+
- **Static or live.** Does it need to update as the project changes (task counts, phase progress), or
|
|
53
|
+
is a point-in-time snapshot the actual intent (e.g. "show my chosen architecture" — a design
|
|
54
|
+
decision doesn't change every time a task ships)?
|
|
55
|
+
|
|
56
|
+
Skip clarification only when the ask is already unambiguous, or the user picked a fully-specified
|
|
57
|
+
candidate from `propose-customizations`.
|
|
58
|
+
|
|
59
|
+
### 3. Gather the source material
|
|
60
|
+
|
|
61
|
+
Read what the dashboard needs to show from the project itself — `specs/*.md`, `plans/*.md`, ADRs,
|
|
62
|
+
`.spectoflow/agents/`, `.spectoflow/skills/`, or the codebase, as the ask requires. For "my
|
|
63
|
+
architecture", that typically means reading a spec/ADR that documents it and turning its structure
|
|
64
|
+
into `markdown` blocks (rendered as-is) plus maybe a `list`/`table` block for components or decisions.
|
|
65
|
+
For "task/security/coverage tracking", that typically means **live-bound** blocks reading the same
|
|
66
|
+
computed stats the Board already uses (see step 5).
|
|
67
|
+
|
|
68
|
+
### 4. Choose blocks — the vocabulary
|
|
69
|
+
|
|
70
|
+
Pick from exactly these block types (anything else is invisible to the renderer — see
|
|
71
|
+
`.spectoflow/lib/custom-dashboard.js` for the enforced schema):
|
|
72
|
+
|
|
73
|
+
| `type` | Shape | Use for |
|
|
74
|
+
|---|---|---|
|
|
75
|
+
| `markdown` | `{ type, content }` — content is the markdown text to render (via the dashboard's own light markdown renderer: headings, lists, `code`, **bold**) | Explaining, documenting, an architecture write-up, a decision summary |
|
|
76
|
+
| `kpi-row` | `{ type, items: [{ label, value?, bind?, sub?, color? }] }` | A row of big-number stat cards (mirrors the Board's own KPI row) |
|
|
77
|
+
| `chart-bars` | `{ type, title, rows: [{ label, pct?, bind?, sub? }] }` | Progress or comparison bars (mirrors "Phase progress") |
|
|
78
|
+
| `chart-donut` | `{ type, title, segments: [{ key, value?, bind?, colorVar }] }` | A status/category breakdown + legend (mirrors "Status distribution") — `colorVar` is a design token name, e.g. `--s-done`, never a literal color |
|
|
79
|
+
| `table` | `{ type, title, columns: [string], rows: [[cell, ...]] }` | Structured tabular data |
|
|
80
|
+
| `list` | `{ type, title, items: [string] }` | A flat bullet list |
|
|
81
|
+
| `stat-tile-row` | `{ type, items: [{ value?, bind?, label, sub? }] }` | Compact stat tiles (mirrors the Info tab's counts) |
|
|
82
|
+
|
|
83
|
+
Apply Stephen Few's information-dashboard discipline while choosing: **one dashboard, one purpose** —
|
|
84
|
+
don't cram unrelated concerns onto the same page just because they were mentioned in the same request
|
|
85
|
+
(propose two dashboards instead, or ask which matters more); prefer the plainest block that carries
|
|
86
|
+
the point (a `stat-tile-row` over a `chart-donut` when there's nothing to compare); keep it scannable
|
|
87
|
+
in one screen — 4-8 blocks is a healthy page, not 20.
|
|
88
|
+
|
|
89
|
+
### 5. Static content vs. live bindings
|
|
90
|
+
|
|
91
|
+
- **Static**: give the block its content directly (`content`, `rows`, `items`, `value` fields) —
|
|
92
|
+
baked in at generation time. This is the default, and the right choice whenever the ask is about a
|
|
93
|
+
point-in-time view (architecture, a decision record, a written summary).
|
|
94
|
+
- **Live**: instead of `value`/`pct`, set `bind` to a dotted path into the same stats object the Board
|
|
95
|
+
already computes (`SpectoStats.stats(P)` — see `dashboard/public/stats.js`). Allowed roots only:
|
|
96
|
+
`pct`, `done`, `total`, `byStatus` (per-status counts, e.g. `byStatus.done`), `phases` (per-phase
|
|
97
|
+
`{title,done,total,pct}`), `toAsk` (tasks awaiting review), `running` (active agents/orchestration),
|
|
98
|
+
`statuses` (the status key list). Anything else is rejected — there is no free-form expression, only
|
|
99
|
+
this fixed, safe set of paths. Use `bind` whenever the ask is explicitly about *tracking* something
|
|
100
|
+
over time ("show my task progress", "how many findings are open").
|
|
101
|
+
|
|
102
|
+
### 6. Pick an id, a title, an icon
|
|
103
|
+
|
|
104
|
+
- `id`: lowercase kebab-case, unique among existing custom dashboards (list
|
|
105
|
+
`.spectoflow/dashboard/custom/*.json` first) — this becomes the URL segment and the file name.
|
|
106
|
+
- `title`: short, a few words, shown as the nav tab label.
|
|
107
|
+
- `icon`: one of `board`, `requests`, `backlog`, `workflow`, `agents`, `chat`, `info`, `attention`,
|
|
108
|
+
`settings` (the same set the rest of the dashboard uses — pick the closest match; default to `info`
|
|
109
|
+
when nothing fits well). An icon outside this set fails validation.
|
|
110
|
+
|
|
111
|
+
### 7. Write and verify
|
|
112
|
+
|
|
113
|
+
Write the spec to `.spectoflow/dashboard/custom/<id>.json` (pretty-printed, 2-space indent). Then
|
|
114
|
+
**verify it, don't assume it's valid** — run:
|
|
115
|
+
```
|
|
116
|
+
node -e "console.log(JSON.stringify(require('./.spectoflow/lib/custom-dashboard').validateSpec(JSON.parse(require('fs').readFileSync('./.spectoflow/dashboard/custom/<id>.json','utf8')))))"
|
|
117
|
+
```
|
|
118
|
+
If `valid` is `false`, fix the reported errors and re-run before reporting done — a spec the
|
|
119
|
+
dashboard's own validator rejects is never a finished deliverable, it would simply be skipped and the
|
|
120
|
+
user would see nothing.
|
|
121
|
+
|
|
122
|
+
## Output contract
|
|
123
|
+
|
|
124
|
+
- One file: `.spectoflow/dashboard/custom/<id>.json`, valid against
|
|
125
|
+
`.spectoflow/lib/custom-dashboard.js`'s `validateSpec` (verified per step 7, not assumed).
|
|
126
|
+
- Progress and completion reported to the orchestrator and group chat:
|
|
127
|
+
|
|
128
|
+
```
|
|
129
|
+
::spectoflow role=customization kind=progress msg=Drafting dashboard "<title>" — <n> blocks
|
|
130
|
+
::spectoflow role=customization kind=need msg=<what's missing and why generation can't proceed>
|
|
131
|
+
::spectoflow role=customization kind=done msg=Dashboard "<title>" added — open it from the nav (<id>)
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## Quality bar
|
|
135
|
+
|
|
136
|
+
- [ ] Every block's `type` is one of the seven documented types — nothing else.
|
|
137
|
+
- [ ] No block, anywhere, sets a literal color/font/size — `colorVar` values are design-token names
|
|
138
|
+
(`--s-*`, `--signal`, `--cool`, …), never hex/rgb literals.
|
|
139
|
+
- [ ] Every `bind` path's root is one of `pct`/`done`/`total`/`byStatus`/`phases`/`toAsk`/`running`/`statuses`.
|
|
140
|
+
- [ ] The generated file passes `validateSpec` — actually run, not assumed (step 7).
|
|
141
|
+
- [ ] `id` is unique, kebab-case; `icon` is one of the nine allowed keys.
|
|
142
|
+
- [ ] The page has a clear, single purpose — not an unrelated grab-bag of blocks.
|
|
143
|
+
- [ ] If the ask was ambiguous, it was clarified one question at a time before any file was written.
|
|
144
|
+
|
|
145
|
+
## References
|
|
146
|
+
|
|
147
|
+
- Stephen Few, *Information Dashboard Design* (O'Reilly/Analytics Press) — one purpose per dashboard,
|
|
148
|
+
the plainest chart that carries the point, single-screen legibility.
|
|
149
|
+
https://www.perceptualedge.com/library.php
|
|
150
|
+
- `.spectoflow/lib/custom-dashboard.js` — the enforced block schema and bind allow-list (source of
|
|
151
|
+
truth; this document summarizes it, the code is authoritative).
|
|
152
|
+
- `dashboard/public/stats.js` — the exact shape of the live stats object bindable via `bind`.
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: generate-skill
|
|
3
|
+
description: Turn a description (or an auto-analysis) into a new skill file, grounded in a real, cited domain standard and matching the framework's gold-standard shape.
|
|
4
|
+
capability: customization
|
|
5
|
+
inputs: A description of the procedure needed (from the Customize page or chat), or a chosen candidate from propose-customizations; the project's existing skills as worked examples.
|
|
6
|
+
outputs: A new .spectoflow/skills/<slug>/SKILL.md matching docs/agents-skills-standard.md's shape, listed in the dashboard's Agents & Skills tab on the next tick.
|
|
7
|
+
standard: docs/agents-skills-standard.md gold-standard shape
|
|
8
|
+
---
|
|
9
|
+
# Generate skill
|
|
10
|
+
|
|
11
|
+
Turn a described need for a new procedure into a real `SKILL.md` — one that reads like it shipped
|
|
12
|
+
with the framework: grounded in a named, current, cited standard for its domain, not generic advice
|
|
13
|
+
dressed up as a procedure.
|
|
14
|
+
|
|
15
|
+
## When to use
|
|
16
|
+
|
|
17
|
+
Whenever the user asks (from the Customize page, or directly in chat) to **add a skill** — "create a
|
|
18
|
+
skill for security review grounded in OWASP", "I want a skill for accessibility audits", "add a skill
|
|
19
|
+
for database migration reviews" — or when `propose-customizations` proposed a skill candidate the
|
|
20
|
+
user picked.
|
|
21
|
+
|
|
22
|
+
## Method
|
|
23
|
+
|
|
24
|
+
### 1. Clarify before generating
|
|
25
|
+
|
|
26
|
+
A one-line ask ("add a security skill") is under-specified. Use `.spectoflow/skills/clarify`'s
|
|
27
|
+
reflex — one targeted question at a time, each with a recommended default — until you know:
|
|
28
|
+
- **The exact procedure's scope.** "Security review" could mean a dozen different things (dependency
|
|
29
|
+
vulnerabilities? authn/authz review? secrets scanning? infra hardening?) — narrow it before writing
|
|
30
|
+
a method for the wrong one.
|
|
31
|
+
- **Which capability it belongs to** — pick the closest match from `.spectoflow/capabilities.md`'s
|
|
32
|
+
palette (security, quality, architecture, testing, operations, …); propose one, don't leave it open.
|
|
33
|
+
- **Who runs it** — an existing agent whose capability matches, or does this need a new agent too
|
|
34
|
+
(if so, this request also needs `generate-agent` — say so and sequence the two).
|
|
35
|
+
|
|
36
|
+
### 2. Identify the real domain standard — don't skip this, and don't fabricate it
|
|
37
|
+
|
|
38
|
+
This is the step that separates a real skill from a plausible-sounding one. For the procedure's
|
|
39
|
+
domain, identify the **actual, named, current authority** practitioners in that field defer to, the
|
|
40
|
+
same way the framework's own shipped skills already do (open a couple as worked examples —
|
|
41
|
+
`write-e2e-tests` cites Playwright's own docs page-by-page; `security-review` cites OWASP ASVS and the
|
|
42
|
+
OWASP Top 10; `write-adr` cites the ADR/C4 literature). Concretely:
|
|
43
|
+
|
|
44
|
+
| Domain | Look for | 2026-current example anchors |
|
|
45
|
+
|---|---|---|
|
|
46
|
+
| Security (general) | OWASP's current flagship guides | OWASP Top 10, OWASP ASVS, OWASP Cheat Sheet Series |
|
|
47
|
+
| Web app auth/session | OWASP-specific cheat sheets | Authentication/Session-Management Cheat Sheets |
|
|
48
|
+
| Accessibility | The current W3C recommendation | WCAG (check the current version — 2.2 at last knowledge, verify) |
|
|
49
|
+
| Architecture / ADRs | The established literature | C4 model (Simon Brown), Michael Nygard's ADR format |
|
|
50
|
+
| API design | A widely-adopted style guide | Google API Design Guide, Microsoft REST API Guidelines |
|
|
51
|
+
| Testing (any level) | The tool's own official docs | e.g. Playwright's own best-practices pages, not a blog summary of them |
|
|
52
|
+
| Performance | Vendor/W3C measurement standards | Core Web Vitals (web.dev), the relevant runtime's own profiling docs |
|
|
53
|
+
| Database/migrations | The database's own official docs + a recognized migration-safety pattern | e.g. "expand/contract" schema migration pattern |
|
|
54
|
+
| Accessibility, i18n, privacy, or any domain not listed here | Whatever is genuinely the field's own authority — never a generic listicle | — |
|
|
55
|
+
|
|
56
|
+
**Verify the standard is real and current before citing it** — use whatever research tool your
|
|
57
|
+
environment provides (web search/fetch) to confirm the standard's name, current version, and a real
|
|
58
|
+
URL; do not rely purely on training-time memory for a fast-moving domain (security guidance, W3C
|
|
59
|
+
specs, and vendor docs all revise). If you cannot verify a specific standard exists for the requested
|
|
60
|
+
domain, **say so explicitly and generate the method from first principles instead**, clearly flagged
|
|
61
|
+
in the skill's own body as "no single authoritative standard identified; method reasoned from
|
|
62
|
+
[whatever sound engineering principles apply]" — never invent a citation to fill the References
|
|
63
|
+
section. A skill honestly grounded in reasoned first principles is worth more than one dressed up with
|
|
64
|
+
a fabricated source.
|
|
65
|
+
|
|
66
|
+
### 3. Write the skill in the gold-standard shape
|
|
67
|
+
|
|
68
|
+
Follow `docs/agents-skills-standard.md`'s skill shape exactly:
|
|
69
|
+
|
|
70
|
+
```yaml
|
|
71
|
+
---
|
|
72
|
+
name: <slug>
|
|
73
|
+
description: <one line — reads as a trigger>
|
|
74
|
+
capability: <the palette capability chosen in step 1>
|
|
75
|
+
inputs: <what it needs>
|
|
76
|
+
outputs: <what it produces>
|
|
77
|
+
standard: <the named standard from step 2>
|
|
78
|
+
---
|
|
79
|
+
# <Skill name>
|
|
80
|
+
<1-line purpose>
|
|
81
|
+
|
|
82
|
+
## When to use
|
|
83
|
+
<the trigger — when does the workflow, or a direct request, reach for this>
|
|
84
|
+
|
|
85
|
+
## Method
|
|
86
|
+
<opinionated, numbered, SOURCED procedure — this is where the domain standard actually lives, applied
|
|
87
|
+
step by step, not just name-dropped in the References section>
|
|
88
|
+
|
|
89
|
+
## Output contract
|
|
90
|
+
<the exact artifact produced and where it's written; how progress is reported — see step 4>
|
|
91
|
+
|
|
92
|
+
## Quality bar
|
|
93
|
+
<a checkable checklist of what "good" looks like for this skill's output>
|
|
94
|
+
|
|
95
|
+
## References
|
|
96
|
+
<the real, verified sources from step 2, as titled links>
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Match the depth and citation density of the framework's own shipped skills — a Method section that
|
|
100
|
+
just says "follow best practices" has failed this step; one that names the specific technique
|
|
101
|
+
(e.g. "apply OWASP ASVS V2 Authentication requirements, specifically…") has succeeded.
|
|
102
|
+
|
|
103
|
+
### 4. Own the `::spectoflow` sentinel
|
|
104
|
+
|
|
105
|
+
Per the gold standard's conventions: **the skill owns the exact reporting syntax** — write out real
|
|
106
|
+
`::spectoflow role=<capability> kind=progress|need|done msg=…` lines in the new skill's Output
|
|
107
|
+
contract, matching the pattern every other skill uses (see any shipped skill for the exact grammar).
|
|
108
|
+
Do not leave this section vague or reference "the standard sentinel format" without spelling it out.
|
|
109
|
+
|
|
110
|
+
### 5. Mark it as user-generated
|
|
111
|
+
|
|
112
|
+
Add `origin: user-generated` to the front-matter (an extra key — never remove or rename the required
|
|
113
|
+
ones). This is how the dashboard's Customize page distinguishes what the user added from what shipped
|
|
114
|
+
with the framework; omitting it hides the skill from that list.
|
|
115
|
+
|
|
116
|
+
### 6. Wire it up if it belongs to the workflow
|
|
117
|
+
|
|
118
|
+
If the new skill is meant to run as part of the delivery pipeline (not just on-demand), tell the user
|
|
119
|
+
it can be added as a step in `.spectoflow/workflow.md` (or from the dashboard's Workflow tab) — but do
|
|
120
|
+
**not** edit `workflow.md` yourself without being asked; a new skill existing is not the same decision
|
|
121
|
+
as it being wired into every request's pipeline.
|
|
122
|
+
|
|
123
|
+
## Output contract
|
|
124
|
+
|
|
125
|
+
- One file: `.spectoflow/skills/<slug>/SKILL.md`, matching the gold-standard shape, with
|
|
126
|
+
`origin: user-generated` in its front-matter.
|
|
127
|
+
- Progress and completion reported to the orchestrator and group chat:
|
|
128
|
+
|
|
129
|
+
```
|
|
130
|
+
::spectoflow role=customization kind=progress msg=Researching the standard for "<domain>" — checking <source>
|
|
131
|
+
::spectoflow role=customization kind=need msg=<what's missing, e.g. no authoritative standard found for X>
|
|
132
|
+
::spectoflow role=customization kind=done msg=Skill "<slug>" added (capability <capability>) — grounded in <standard>
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## Quality bar
|
|
136
|
+
|
|
137
|
+
- [ ] Front-matter matches the gold-standard shape exactly, plus `origin: user-generated`.
|
|
138
|
+
- [ ] Body has exactly the five required `##` headings, in order.
|
|
139
|
+
- [ ] `## Method` names and applies a real, verified, current standard — or explicitly says none was
|
|
140
|
+
found and reasons from first principles instead. Never a fabricated citation.
|
|
141
|
+
- [ ] `## References` links are real and were verified (not assumed from memory) when the domain is
|
|
142
|
+
fast-moving (security, web standards, vendor APIs).
|
|
143
|
+
- [ ] The `::spectoflow` sentinel syntax is spelled out in full in the Output contract, not just
|
|
144
|
+
referenced.
|
|
145
|
+
- [ ] If the ask was ambiguous, it was clarified one question at a time before any file was written.
|
|
146
|
+
- [ ] `.spectoflow/workflow.md` was left untouched unless the user explicitly asked to enable this
|
|
147
|
+
skill as a pipeline step.
|
|
148
|
+
|
|
149
|
+
## References
|
|
150
|
+
|
|
151
|
+
- `docs/agents-skills-standard.md` — the gold-standard shape this skill's output must match exactly.
|
|
152
|
+
- Any shipped skill under `.spectoflow/skills/` (e.g. `security-review`, `write-e2e-tests`,
|
|
153
|
+
`write-adr`) — worked examples of real citation density and Method-section depth to match.
|