spectoflow 0.12.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.
Files changed (44) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +133 -0
  3. package/bin/spectoflow.js +170 -0
  4. package/lib/adapters.js +120 -0
  5. package/lib/detect.js +34 -0
  6. package/lib/manifest.js +36 -0
  7. package/lib/ownership.js +34 -0
  8. package/lib/update.js +78 -0
  9. package/package.json +30 -0
  10. package/templates/AGENTS.md +66 -0
  11. package/templates/agents/architect.md +54 -0
  12. package/templates/agents/business-analyst.md +53 -0
  13. package/templates/agents/code-reviewer.md +58 -0
  14. package/templates/agents/developer.md +73 -0
  15. package/templates/agents/devops.md +59 -0
  16. package/templates/agents/product-manager.md +53 -0
  17. package/templates/agents/qa-engineer.md +63 -0
  18. package/templates/agents/security-engineer.md +59 -0
  19. package/templates/agents/tech-lead.md +52 -0
  20. package/templates/agents/ux-designer.md +50 -0
  21. package/templates/capabilities.md +15 -0
  22. package/templates/config.json +10 -0
  23. package/templates/dashboard/orchestrator.js +117 -0
  24. package/templates/dashboard/public/app.js +747 -0
  25. package/templates/dashboard/public/charts.js +192 -0
  26. package/templates/dashboard/public/icons.js +28 -0
  27. package/templates/dashboard/public/index.html +226 -0
  28. package/templates/dashboard/public/stats.js +32 -0
  29. package/templates/dashboard/public/styles.css +426 -0
  30. package/templates/dashboard/runner.js +77 -0
  31. package/templates/dashboard/server.js +115 -0
  32. package/templates/lib/store.js +289 -0
  33. package/templates/policy.md +11 -0
  34. package/templates/skills/analyze-requirements/SKILL.md +67 -0
  35. package/templates/skills/brainstorm/SKILL.md +58 -0
  36. package/templates/skills/code-review/SKILL.md +67 -0
  37. package/templates/skills/implement/SKILL.md +80 -0
  38. package/templates/skills/security-review/SKILL.md +70 -0
  39. package/templates/skills/write-adr/SKILL.md +61 -0
  40. package/templates/skills/write-e2e-tests/SKILL.md +99 -0
  41. package/templates/skills/write-plan/SKILL.md +66 -0
  42. package/templates/skills/write-spec/SKILL.md +66 -0
  43. package/templates/skills/write-tests/SKILL.md +80 -0
  44. package/templates/workflow.md +15 -0
@@ -0,0 +1,289 @@
1
+ 'use strict';
2
+ /*
3
+ * spectoflow storage engine (zero dependency).
4
+ *
5
+ * Artifacts are MARKDOWN (human source of truth, versioned):
6
+ * plans/<name>.md — tasks as checkbox lines:
7
+ * ## Phase title
8
+ * - [ ] T-012 Add login form @dev ~standard %in_progress
9
+ * - note: some comment (indented sub-bullets = comments)
10
+ * specs/<name>.md — free-form spec markdown (listed, not parsed into tasks)
11
+ *
12
+ * Volatile execution state is JSON (gitignored, never read by humans):
13
+ * .spectoflow/runtime.json — running agents, heartbeats, test results
14
+ *
15
+ * Writes are GRANULAR: we locate a task's line by id and rewrite only that line (or insert a
16
+ * sub-bullet), leaving the rest of the file byte-for-byte intact.
17
+ */
18
+ const fs = require('fs');
19
+ const path = require('path');
20
+
21
+ // ---- task line parsing -------------------------------------------------------
22
+ // - [ ] T-012 Title here @owner ~level %status
23
+ const LINE_RE = /^(\s*)- \[( |x|X)\]\s+(\S+)\s*(.*)$/;
24
+
25
+ function parseTaskLine(line) {
26
+ const m = line.match(LINE_RE);
27
+ if (!m) return null;
28
+ const [, indent, check, id, rest0] = m;
29
+ let rest = rest0;
30
+ const owner = (rest.match(/(?:^|\s)@([\w-]+)/) || [])[1] || null;
31
+ const level = (rest.match(/(?:^|\s)~([\w-]+)/) || [])[1] || null;
32
+ const statusTag = (rest.match(/(?:^|\s)%([\w-]+)/) || [])[1] || null;
33
+ const title = rest.replace(/(?:^|\s)[@~%][\w-]+/g, '').replace(/\s+/g, ' ').trim();
34
+ const done = check.toLowerCase() === 'x';
35
+ const status = done ? 'done' : (statusTag || 'todo');
36
+ return { indent, id, title, owner, level: level || 'standard', status, done };
37
+ }
38
+
39
+ function buildTaskLine(t) {
40
+ const parts = [`${t.indent || ''}- [${t.status === 'done' ? 'x' : ' '}] ${t.id} ${t.title}`];
41
+ if (t.owner) parts.push(`@${t.owner}`);
42
+ if (t.level && t.level !== 'standard') parts.push(`~${t.level}`);
43
+ if (t.status !== 'done' && t.status !== 'todo') parts.push(`%${t.status}`);
44
+ return parts.join(' ');
45
+ }
46
+
47
+ // ---- plan file model ---------------------------------------------------------
48
+ function parsePlan(text) {
49
+ const lines = text.split('\n');
50
+ const phases = [];
51
+ let cur = { id: 'P0', title: 'Tasks', goal: '', tasks: [] };
52
+ let lastTask = null;
53
+ const pushPhase = () => { if (cur.tasks.length || cur.title !== 'Tasks') phases.push(cur); };
54
+ lines.forEach((line) => {
55
+ const h = line.match(/^##\s+(.*)$/);
56
+ if (h) { pushPhase(); cur = { id: 'P' + phases.length, title: h[1].trim(), goal: '', tasks: [] }; lastTask = null; return; }
57
+ const t = parseTaskLine(line);
58
+ if (t) { t.comments = []; cur.tasks.push(t); lastTask = t; return; }
59
+ const c = line.match(/^\s+- (?:note:|comment:)?\s*(.*)$/);
60
+ if (c && lastTask && c[1].trim()) lastTask.comments.push(c[1].trim());
61
+ });
62
+ pushPhase();
63
+ return phases;
64
+ }
65
+
66
+ function readPlans(projectRoot) {
67
+ const dir = path.join(projectRoot, 'plans');
68
+ if (!fs.existsSync(dir)) return [];
69
+ const out = [];
70
+ for (const f of fs.readdirSync(dir).filter((x) => x.endsWith('.md')).sort()) {
71
+ const text = fs.readFileSync(path.join(dir, f), 'utf8');
72
+ out.push({ file: f, phases: parsePlan(text) });
73
+ }
74
+ return out;
75
+ }
76
+
77
+ // ---- granular writes ---------------------------------------------------------
78
+ // Rewrite only the line whose task id matches, preserving everything else.
79
+ function updateTaskLine(projectRoot, file, id, patch) {
80
+ const fp = path.join(projectRoot, 'plans', file);
81
+ const lines = fs.readFileSync(fp, 'utf8').split('\n');
82
+ let changed = false;
83
+ for (let i = 0; i < lines.length; i++) {
84
+ const t = parseTaskLine(lines[i]);
85
+ if (t && t.id === id) {
86
+ const next = Object.assign(t, patch);
87
+ lines[i] = buildTaskLine(next);
88
+ changed = true;
89
+ break;
90
+ }
91
+ }
92
+ if (changed) writeAtomic(fp, lines.join('\n'));
93
+ return changed;
94
+ }
95
+
96
+ function addTaskComment(projectRoot, file, id, text, author) {
97
+ const fp = path.join(projectRoot, 'plans', file);
98
+ const lines = fs.readFileSync(fp, 'utf8').split('\n');
99
+ for (let i = 0; i < lines.length; i++) {
100
+ const t = parseTaskLine(lines[i]);
101
+ if (t && t.id === id) {
102
+ const indent = (t.indent || '') + ' ';
103
+ const who = author ? `@${author}: ` : '';
104
+ // insert after any existing comment sub-bullets
105
+ let j = i + 1;
106
+ while (j < lines.length && /^\s+- /.test(lines[j])) j++;
107
+ lines.splice(j, 0, `${indent}- note: ${who}${text}`);
108
+ writeAtomic(fp, lines.join('\n'));
109
+ return true;
110
+ }
111
+ }
112
+ return false;
113
+ }
114
+
115
+ function writeAtomic(fp, content) {
116
+ const tmp = fp + '.tmp';
117
+ fs.writeFileSync(tmp, content, 'utf8');
118
+ fs.renameSync(tmp, fp);
119
+ }
120
+
121
+ // ---- runtime sidecar (volatile) ---------------------------------------------
122
+ function runtimePath(projectRoot) { return path.join(projectRoot, '.spectoflow', 'runtime.json'); }
123
+ function readRuntime(projectRoot) {
124
+ try { return JSON.parse(fs.readFileSync(runtimePath(projectRoot), 'utf8')); }
125
+ catch { return { agents: [], tests: {}, messages: [], updatedAt: null }; }
126
+ }
127
+ function writeRuntime(projectRoot, rt) {
128
+ rt.updatedAt = new Date().toISOString();
129
+ writeAtomic(runtimePath(projectRoot), JSON.stringify(rt, null, 2) + '\n');
130
+ return rt;
131
+ }
132
+
133
+ // ---- progress history (volatile, kept inside runtime.json) ------------------
134
+ // Pure helper: mutates+returns `runtime.history` — one {date,total,done} point per calendar day,
135
+ // newest last, capped to 60 points. Same-day calls UPDATE the existing point rather than appending
136
+ // (so re-recording the same day never grows history). No filesystem access here; the caller
137
+ // decides whether the result is worth persisting (see readProject's write-guard).
138
+ function recordSnapshot(runtime, counts, date) {
139
+ runtime.history = runtime.history || [];
140
+ const d = date || new Date().toISOString().slice(0, 10);
141
+ const last = runtime.history[runtime.history.length - 1];
142
+ const snap = { date: d, total: counts.total | 0, done: counts.done | 0 };
143
+ if (last && last.date === d) runtime.history[runtime.history.length - 1] = snap;
144
+ else runtime.history.push(snap);
145
+ if (runtime.history.length > 60) runtime.history = runtime.history.slice(-60);
146
+ return runtime;
147
+ }
148
+
149
+ // ---- group-chat message log (volatile) --------------------------------------
150
+ // A running agent identifies itself by printing sentinel lines on stdout:
151
+ // ::spectoflow role=developer kind=status msg=finished T-023
152
+ // Everything after `msg=` is free text to end of line. Non-sentinel lines return null.
153
+ function parseAgentLine(line) {
154
+ const s = String(line);
155
+ if (!/^\s*::spectoflow\b/.test(s)) return null;
156
+ return {
157
+ role: (s.match(/\brole=(\S+)/) || [])[1] || 'agent',
158
+ kind: (s.match(/\bkind=(\S+)/) || [])[1] || 'message',
159
+ text: ((s.match(/\bmsg=([\s\S]*)$/) || [])[1] || '').trim(),
160
+ };
161
+ }
162
+
163
+ // Append one message to runtime.messages (id + at stamped here; caller can't override them).
164
+ function appendMessage(projectRoot, msg) {
165
+ const rt = readRuntime(projectRoot);
166
+ rt.messages = rt.messages || [];
167
+ const full = { kind: 'message', ...msg,
168
+ id: 'm' + Date.now().toString(36) + Math.random().toString(36).slice(2, 6),
169
+ at: new Date().toISOString() };
170
+ rt.messages.push(full);
171
+ writeRuntime(projectRoot, rt);
172
+ return full;
173
+ }
174
+
175
+ // ---- config & workflow -------------------------------------------------------
176
+ function readConfig(projectRoot) {
177
+ try { return JSON.parse(fs.readFileSync(path.join(projectRoot, '.spectoflow', 'config.json'), 'utf8')); }
178
+ catch { return { mode: 'semi', language: 'en', agent: 'claude' }; }
179
+ }
180
+ function readWorkflow(projectRoot) {
181
+ try {
182
+ const text = fs.readFileSync(path.join(projectRoot, '.spectoflow', 'workflow.md'), 'utf8').replace(/\r\n?/g, '\n');
183
+ const steps = [];
184
+ text.split('\n').forEach((l) => {
185
+ const m = l.match(/^\s*- \[( |x|X)\]\s+(.*?)\s*$/);
186
+ if (!m) return;
187
+ let rest = m[2], cap = null, skill = null, policy = false;
188
+ const ann = rest.match(/\{([^}]*)\}\s*$/);
189
+ if (ann) {
190
+ rest = rest.slice(0, ann.index).trim();
191
+ cap = (ann[1].match(/\bcap:(\S+)/) || [])[1] || null;
192
+ skill = (ann[1].match(/\bskill:(\S+)/) || [])[1] || null;
193
+ policy = /\bpolicy\b/.test(ann[1]);
194
+ }
195
+ const optional = /\(optional\)/i.test(rest);
196
+ const name = rest.replace(/\s*\(optional\)\s*$/i, '').trim();
197
+ steps.push({ name, enabled: m[1].toLowerCase() === 'x', optional, cap, skill, policy });
198
+ });
199
+ return steps;
200
+ } catch { return []; }
201
+ }
202
+
203
+ // ---- unified read for the dashboard -----------------------------------------
204
+ function readProject(projectRoot) {
205
+ const config = readConfig(projectRoot);
206
+ const plans = readPlans(projectRoot);
207
+ let runtime = readRuntime(projectRoot);
208
+ const workflow = readWorkflow(projectRoot);
209
+ const specs = (() => {
210
+ const d = path.join(projectRoot, 'specs');
211
+ return fs.existsSync(d) ? fs.readdirSync(d).filter((x) => x.endsWith('.md')) : [];
212
+ })();
213
+ const agents = listMd(path.join(projectRoot, '.spectoflow', 'agents'));
214
+ const skills = listSkills(path.join(projectRoot, '.spectoflow', 'skills'));
215
+
216
+ // Write-guarded snapshot: readProject is polled continuously by the dashboard (and reacts to
217
+ // fs.watch on .spectoflow). Recording unconditionally on every read would rewrite runtime.json
218
+ // on every poll → fs.watch fires → SSE 'change' → client re-reads → infinite loop / SSE storm.
219
+ // So we only persist when today's {total,done} actually differs from the last history entry
220
+ // (or history is empty and needs seeding) — a no-op read never touches the filesystem.
221
+ const today = new Date().toISOString().slice(0, 10);
222
+ let total = 0, done = 0;
223
+ for (const pl of plans) for (const ph of pl.phases) for (const t of ph.tasks) { total++; if (t.status === 'done') done++; }
224
+ const history = runtime.history || [];
225
+ const last = history[history.length - 1];
226
+ const changed = !last || last.date !== today || last.total !== total || last.done !== done;
227
+ if (changed) {
228
+ // Concurrency guard: readProject's own initial `readRuntime` above can be stale by the time
229
+ // we're ready to write — a concurrent writer (e.g. appendMessage, from a running agent) may
230
+ // have written runtime.json in between. Writing back our stale in-memory copy would silently
231
+ // clobber whatever that concurrent writer just persisted (messages, agent status, etc).
232
+ // So we re-read the freshest runtime immediately before writing and mutate ONLY its history
233
+ // in place; every other field (messages/agents/tests) comes from this fresh read, not from
234
+ // the possibly-stale `runtime` captured earlier in this function.
235
+ const cur = readRuntime(projectRoot);
236
+ recordSnapshot(cur, { total, done }, today);
237
+ runtime = writeRuntime(projectRoot, cur);
238
+ }
239
+
240
+ return { config, plans, specs, workflow, agents, skills, runtime };
241
+ }
242
+ function frontmatter(text) {
243
+ const m = String(text).replace(/\r\n?/g, '\n').match(/^---\n([\s\S]*?)\n---/);
244
+ const out = {};
245
+ if (m) m[1].split('\n').forEach((l) => { const kv = l.match(/^([\w-]+):\s*(.*)$/); if (kv) out[kv[1]] = kv[2].trim(); });
246
+ return out;
247
+ }
248
+ // Parse a flat inline-list front-matter value, e.g. "[analyze-requirements, write-spec]" →
249
+ // ['analyze-requirements', 'write-spec']. Returns [] for an absent/empty value.
250
+ function parseFlatList(raw) {
251
+ if (!raw) return [];
252
+ return String(raw).replace(/[[\]]/g, '').split(',').map((s) => s.trim()).filter(Boolean);
253
+ }
254
+ function listMd(dir) {
255
+ if (!fs.existsSync(dir)) return [];
256
+ return fs.readdirSync(dir).filter((f) => f.endsWith('.md')).map((f) => {
257
+ const fm = frontmatter(fs.readFileSync(path.join(dir, f), 'utf8'));
258
+ return { file: f, name: fm.name || f.replace(/\.md$/, ''), title: fm.title || fm.name || f, capability: fm.capability || '', description: fm.description || '',
259
+ standards: parseFlatList(fm.standards), uses: parseFlatList(fm.uses) };
260
+ });
261
+ }
262
+ function listSkills(dir) {
263
+ if (!fs.existsSync(dir)) return [];
264
+ return fs.readdirSync(dir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => {
265
+ const sk = path.join(dir, e.name, 'SKILL.md');
266
+ const fm = fs.existsSync(sk) ? frontmatter(fs.readFileSync(sk, 'utf8')) : {};
267
+ return { name: fm.name || e.name, description: fm.description || '', capability: fm.capability || '',
268
+ inputs: fm.inputs || '', outputs: fm.outputs || '', standard: fm.standard || '' };
269
+ });
270
+ }
271
+ function readAgents(projectRoot) {
272
+ const dir = path.join(projectRoot, '.spectoflow', 'agents');
273
+ if (!fs.existsSync(dir)) return [];
274
+ return fs.readdirSync(dir).filter((f) => f.endsWith('.md')).map((f) => {
275
+ const fm = frontmatter(fs.readFileSync(path.join(dir, f), 'utf8'));
276
+ return { name: fm.name || f.replace(/\.md$/, ''), capability: fm.capability || null,
277
+ title: fm.title || '', description: fm.description || '',
278
+ standards: parseFlatList(fm.standards), uses: parseFlatList(fm.uses) };
279
+ });
280
+ }
281
+ function readSkills(projectRoot) {
282
+ return listSkills(path.join(projectRoot, '.spectoflow', 'skills'));
283
+ }
284
+
285
+ module.exports = {
286
+ parseTaskLine, buildTaskLine, parsePlan, readPlans, updateTaskLine, addTaskComment,
287
+ readRuntime, writeRuntime, parseAgentLine, appendMessage, readConfig, readWorkflow, readProject,
288
+ readAgents, readSkills, recordSnapshot,
289
+ };
@@ -0,0 +1,11 @@
1
+ # Policy — non-negotiable gates
2
+
3
+ Orthogonal to mode. Even in autopilot, these require **explicit human approval** before execution.
4
+
5
+ - **Production deployment** (build/release/deploy to prod).
6
+ - **Destructive migration** (irreversible drop/alter, data deletion, purge).
7
+ - **Security change** (auth, permissions, secrets, network exposure, session lifetime).
8
+ - **Committing spend / external side effect** (payment, purchase, mass send).
9
+
10
+ When a step hits a gate: stop, explain the act and its risk in one line, ask [Approve / Cancel /
11
+ Modify], and record the decision in the runtime log. Overridable per project (add or relax gates).
@@ -0,0 +1,67 @@
1
+ ---
2
+ name: analyze-requirements
3
+ description: Turn a need into testable acceptance criteria in Given/When/Then, with edge cases enumerated.
4
+ capability: analysis
5
+ inputs: The raw need (a request, ticket, or user story) and any known constraints or existing spec.
6
+ outputs: A list of testable acceptance criteria (Given/When/Then) plus an edge-case checklist.
7
+ standard: BDD / acceptance criteria
8
+ ---
9
+ # Analyze requirements
10
+
11
+ Turn an ambiguous need into a set of unambiguous, testable acceptance criteria before design or code
12
+ starts.
13
+
14
+ ## When to use
15
+ Whenever a need arrives that isn't yet expressed as testable criteria — a new feature, a change
16
+ request, a bug that implies a missing behavior — or whenever the workflow reaches the Analysis step.
17
+
18
+ ## Method
19
+ Apply Behavior-Driven Development's Given/When/Then structure (Cucumber/Gherkin) to state each
20
+ criterion as a concrete example, then stress it with a standard edge-case taxonomy
21
+ (ISTQB equivalence partitioning + boundary value analysis):
22
+
23
+ 1. **Restate the need in one sentence.** If it takes more than one sentence, split it into multiple
24
+ needs — each gets its own criteria.
25
+ 2. **Write acceptance criteria as Given/When/Then.** For each distinct behavior:
26
+ - `Given` the initial context/state (3-5 steps max per scenario — more and it stops reading as a
27
+ spec and becomes an implementation).
28
+ - `When` the triggering action.
29
+ - `Then` the expected, observable outcome (behavior/contract level — no implementation detail).
30
+ One criterion = one behavior. If it needs "and" to describe, it is probably two criteria.
31
+ 3. **Enumerate edge cases per input**, using equivalence partitioning + boundary value analysis:
32
+ - Valid equivalence class(es) — one representative example, not every value in the class.
33
+ - Invalid equivalence class(es) — what must be rejected and how.
34
+ - Boundaries — min, max, min-1, max+1, empty, zero, null/missing.
35
+ - Error paths — what happens on failure (timeout, denial, malformed input), not just success.
36
+ 4. **Flag gaps as `need`s, don't fill them in.** Any requirement gap that depends on a third party, a
37
+ business decision, or information you don't have is raised as a `need` per `policy.md` — never
38
+ guessed at to keep moving.
39
+ 5. **Hand off** the criteria list to `write-spec` to be shaped into a reviewable spec document.
40
+
41
+ ## Output contract
42
+ Acceptance criteria (Given/When/Then) and the edge-case checklist are recorded in `specs/<feature>.md`
43
+ (or the plan, if no spec exists yet), written with granular, one-line-at-a-time updates. Report progress
44
+ with the `::spectoflow` sentinel:
45
+
46
+ ```
47
+ ::spectoflow role=analysis kind=progress msg=<N> acceptance criteria drafted for <feature>
48
+ ::spectoflow role=analysis kind=need msg=<what is missing and who must resolve it>
49
+ ::spectoflow role=analysis kind=report msg=<N> criteria, <M> edge cases enumerated, ready for write-spec
50
+ ```
51
+
52
+ ## Quality bar
53
+ - [ ] Every acceptance criterion is a concrete Given/When/Then example, not a vague statement.
54
+ - [ ] Each criterion covers exactly one behavior (splittable by "and" is a sign it's really two).
55
+ - [ ] Edge cases cover valid + invalid equivalence classes, boundaries, and error paths — not just the
56
+ happy path.
57
+ - [ ] No criterion states implementation detail (class names, frameworks, algorithms).
58
+ - [ ] Every gap that isn't this role's to answer is raised as a `need`, not silently assumed.
59
+
60
+ ## References
61
+ - Cucumber, "Gherkin Syntax" — https://cucumber.netlify.app/docs/gherkin/
62
+ - SmartBear, "Writing scenarios with Gherkin syntax" —
63
+ https://support.smartbear.com/cucumberstudio/docs/bdd/write-gherkin-scenarios.html
64
+ - ISTQB Foundation Level — Boundary Value Analysis white paper —
65
+ https://istqb.org/wp-content/uploads/2025/10/Boundary-Value-Analysis-white-paper.pdf
66
+ - SoftwareTestingHelp, "Boundary Value Analysis & Equivalence Partitioning Examples" —
67
+ https://www.softwaretestinghelp.com/what-is-boundary-value-analysis-and-equivalence-partitioning/
@@ -0,0 +1,58 @@
1
+ ---
2
+ name: brainstorm
3
+ description: Frame a need — problem, users, scope, risks, success metric — before committing to a spec.
4
+ capability: intake
5
+ inputs: The raw ask/request from the user or requester.
6
+ outputs: A framed brief (problem, users, scope, risks, success metric) ready for analysis.
7
+ standard: product discovery
8
+ ---
9
+ # Brainstorm
10
+
11
+ Frame a raw need into an agreed problem statement before any solution gets designed.
12
+
13
+ ## When to use
14
+ When a new ask arrives — a feature request, a bug report reframed as a need, or any item without an
15
+ agreed problem/scope yet — or whenever the workflow reaches an intake step.
16
+
17
+ ## Method
18
+ Frame the need *before* reaching for solutions, in this order:
19
+
20
+ 1. **Problem** — what user-facing or business problem is this, stated as an outcome, not a feature
21
+ ("users can't X" not "add a button").
22
+ 2. **Users** — who specifically is affected; which segment, not "everyone".
23
+ 3. **Scope** — what's in scope for a first useful version.
24
+ 4. **Out of scope** — what is explicitly excluded, so nobody assumes it's included later.
25
+ 5. **Risks** — name the risk(s) most likely to kill or derail this: value (will users want it),
26
+ usability (can they use it), feasibility (can it be built with what we have), business viability
27
+ (does it fit constraints/compliance/cost) — per SVPG's four big risks.
28
+ 6. **Success metric** — one metric that will tell us the outcome was achieved.
29
+
30
+ Offer 2-3 directions with trade-offs once the problem is framed; let the user react and converge on a
31
+ shared understanding. Do not write code or a full spec at this stage — that belongs to the next
32
+ capability.
33
+
34
+ ## Output contract
35
+ Write the framed brief as a granular note/task comment (one line at a time): problem statement,
36
+ users, scope, out-of-scope, top risk(s), success metric. Report to the orchestrator and group chat
37
+ with:
38
+
39
+ ```
40
+ ::spectoflow role=intake kind=brief msg=<one-line problem + scope summary>
41
+ ```
42
+
43
+ The brief feeds the next analysis/spec-writing step; it is not itself a spec or an implementation
44
+ plan.
45
+
46
+ ## Quality bar
47
+ - [ ] Problem is stated as an outcome/user pain, not pre-decided as a solution/feature.
48
+ - [ ] Users are named specifically, not "everyone" or left implicit.
49
+ - [ ] Scope and out-of-scope are both stated explicitly.
50
+ - [ ] At least one risk (value/usability/feasibility/business viability) is named.
51
+ - [ ] Exactly one success metric is stated and agreed with the requester.
52
+ - [ ] No code or full spec was written during this step.
53
+
54
+ ## References
55
+ - Teresa Torres, *Continuous Discovery Habits* (Product Talk, 2021) —
56
+ https://www.producttalk.org/continuous-discovery-habits-book/
57
+ - Marty Cagan, "The Four Big Risks" — Silicon Valley Product Group —
58
+ https://www.svpg.com/four-big-risks/
@@ -0,0 +1,67 @@
1
+ ---
2
+ name: code-review
3
+ description: Review a deliverable against its requirements, findings graded by severity.
4
+ capability: quality
5
+ inputs: The deliverable under review and its acceptance criteria/spec.
6
+ outputs: A severity-graded findings report with a ready / rework verdict.
7
+ standard: Google code-review guide
8
+ ---
9
+ # Code review
10
+
11
+ Scoped review of a deliverable against its requirements to catch defects before it is marked done.
12
+
13
+ ## When to use
14
+ When a `plans/*.md` task or deliverable is reported complete and needs an independent check before
15
+ its status flips to done — or whenever the workflow reaches a quality step.
16
+
17
+ ## Method
18
+ Read the deliverable and its acceptance criteria/spec first; review against them, not against
19
+ personal preference. Following Google's "How to do a code review", walk each category and read every
20
+ line the author expects reviewed:
21
+
22
+ 1. **Correctness / Functionality** — does the code behave as the spec and the author intended; are
23
+ edge cases and error paths handled.
24
+ 2. **Tests** — does the change have correct, well-designed automated tests covering the new behavior
25
+ (not just the happy path)?
26
+ 3. **Readability / Naming / Comments** — clear names, comments that explain *why* not *what*, no
27
+ dead code or leftover debug output.
28
+ 4. **Design / Complexity** — is the change well-designed for the system it lands in; could it be
29
+ simpler; would another developer understand and reuse it later?
30
+ 5. **Security** — obvious injection, auth/authz, secrets, or input-validation issues on the touched
31
+ surface (defer a full pass to the `security-review` skill when the change is security-sensitive).
32
+ 6. **Consistency / Documentation** — matches existing style/conventions; docs updated if behavior or
33
+ interface changed.
34
+
35
+ Grade each finding by severity: **Critical** (breaks correctness/security, blocks), **Important**
36
+ (real defect or gap, should block), **Minor** (worth fixing, not blocking), **Nit** (polish, author's
37
+ choice — the guide's own "Nit:" convention for non-blocking points). Favor approving once the change
38
+ demonstrably improves the codebase, even if imperfect; don't hold it to a standard of perfection.
39
+
40
+ ## Output contract
41
+ Write findings as a report / task comment alongside the deliverable (granular, one line at a time),
42
+ each carrying: severity, file:line, and the issue. End with an explicit verdict: **ready** or
43
+ **rework**. Report to the orchestrator and group chat with:
44
+
45
+ ```
46
+ ::spectoflow role=quality kind=review msg=<verdict + counts by severity>
47
+ ::spectoflow role=quality kind=finding msg=<severity> <file:line> — <issue>
48
+ ```
49
+
50
+ Do not modify the deliverable — report only. A discrepancy with the spec is raised as a `need`, not
51
+ silently patched.
52
+
53
+ ## Quality bar
54
+ - [ ] Reviewed against the deliverable's stated acceptance criteria/spec, not personal taste.
55
+ - [ ] Every finding has a severity (Critical/Important/Minor/Nit) and a file:line.
56
+ - [ ] Tests checked for real coverage of new behavior, not just the presence of a test file.
57
+ - [ ] Design/complexity and naming/readability both considered, not just correctness.
58
+ - [ ] Obvious security issues on the touched surface flagged (or routed to security-review).
59
+ - [ ] A clear verdict (ready/rework) is stated; no open Critical/Important left unacknowledged.
60
+
61
+ ## References
62
+ - Google Engineering Practices, "How to do a code review" —
63
+ https://google.github.io/eng-practices/review/reviewer/
64
+ - Google Engineering Practices, "What to look for in a code review" —
65
+ https://google.github.io/eng-practices/review/reviewer/looking-for.html
66
+ - Google Engineering Practices, "The Standard of Code Review" —
67
+ https://google.github.io/eng-practices/review/reviewer/standard.html
@@ -0,0 +1,80 @@
1
+ ---
2
+ name: implement
3
+ description: Implement a plan task as small, conventional commits — red-green when a test exists, boy-scout cleanup on touched code, no scope creep.
4
+ capability: implementation
5
+ inputs: A `plans/*.md` task (checkbox item) with its linked spec section, and any failing test the testing capability already wrote for it.
6
+ outputs: Working code for the task, committed as one or more Conventional Commits, plus the task's checkbox and status flipped in `plans/*.md`.
7
+ standard: Conventional Commits + YAGNI/DRY
8
+ ---
9
+ # Implement
10
+
11
+ Turn one plan task into shipped code through small, well-described commits — nothing more than the
12
+ task asks for, nothing left messier than it was found.
13
+
14
+ ## When to use
15
+ When the workflow reaches the `implementation` capability for a task in `plans/*.md` that is not yet
16
+ checked off, or when the group chat routes work to the developer persona.
17
+
18
+ ## Method
19
+ 1. **Re-read the task's contract first.** Open the linked spec section and the plan task's acceptance
20
+ criteria before writing any code. If a test already exists for this task (unit/integration/e2e),
21
+ run it — it should fail (**red**). If no test exists, note that in the report; do not silently skip
22
+ testing, escalate to the `testing` capability if the task needs one.
23
+ 2. **Build the smallest change that satisfies the criteria (YAGNI).** You Aren't Gonna Need It: implement
24
+ only what the current task requires — no speculative config, no unused abstraction, no extra endpoint
25
+ "while we're in here". Extra scope is a separate task, not a freebie.
26
+ 3. **Make it pass, then clean it up (green → refactor).** Get the existing/linked test green with the
27
+ simplest correct code, then refactor for clarity and to remove duplication (DRY — Don't Repeat
28
+ Yourself: extract only when a *third* real occurrence appears, not on the first hint of similarity).
29
+ 4. **Apply the boy-scout rule to code you touch.** Leave the lines you had to open cleaner than you found
30
+ them (naming, dead code, obvious lint issues) — but do not refactor unrelated files or modules just
31
+ because you passed through the repo; that belongs to its own task.
32
+ 5. **Commit in small, working, Conventional Commits.** Each commit:
33
+ - Builds and (if a test exists) passes on its own — no "WIP" or broken intermediate commits on the
34
+ shared branch (trunk-based hygiene: keep the branch always releasable).
35
+ - Follows the Conventional Commits grammar:
36
+ `<type>[optional scope]: <description>` header, optional body one blank line after, optional
37
+ footer(s) one blank line after that.
38
+ - Uses `feat:` for new capability, `fix:` for a bug fix, and other conventional types (`refactor:`,
39
+ `test:`, `docs:`, `chore:`, …) for everything else — pick the type that matches what the commit
40
+ actually does, not what the task was called.
41
+ - Marks a breaking change with `!` before the colon (e.g. `feat(api)!: ...`) or a `BREAKING CHANGE:`
42
+ footer — only when the task's contract says the change is breaking.
43
+ - Stays scoped to one logical change; split a task into several commits rather than bundling unrelated
44
+ edits into one.
45
+ 6. **Flip the task's status as you go**, not in one batch at the end — granular, one line at a time, so
46
+ the dashboard and other agents see live progress rather than a silent gap.
47
+
48
+ ## Output contract
49
+ - Code changes committed with Conventional Commits messages as described above.
50
+ - The corresponding checkbox/status line in `plans/*.md` updated via a granular, one-line write
51
+ (`- [ ]` → `- [x]`, or the task's status field) — never a full-file rewrite of the plan.
52
+ - Progress and completion reported to the orchestrator and group chat with:
53
+
54
+ ```
55
+ ::spectoflow role=implementation kind=progress msg=<task id> <what changed, one line>
56
+ ::spectoflow role=implementation kind=commit msg=<commit type>(<scope>): <description>
57
+ ::spectoflow role=implementation kind=done msg=<task id> done — <tests status: red→green | no test>
58
+ ```
59
+
60
+ ## Quality bar
61
+ - [ ] Task's acceptance criteria (from the spec/plan) are met — nothing more, nothing less.
62
+ - [ ] If a test existed for this task, it went red → green; if none existed, that is stated explicitly.
63
+ - [ ] No speculative code, config, or abstraction beyond what the task requires (YAGNI).
64
+ - [ ] No duplicated logic left behind that a third occurrence should have collapsed (DRY).
65
+ - [ ] Every commit builds/passes standalone and follows `<type>[scope]: <description>` grammar.
66
+ - [ ] Breaking changes are marked with `!` or a `BREAKING CHANGE:` footer, and only when real.
67
+ - [ ] Code the task touched is left cleaner (boy-scout), with no drive-by edits outside the task's scope.
68
+ - [ ] Plan status updated via a granular write, not a full-file rewrite.
69
+
70
+ ## References
71
+ - Conventional Commits v1.0.0 — https://www.conventionalcommits.org/en/v1.0.0/ (message grammar: type,
72
+ optional scope, description, body, footer; `feat`/`fix` as the baseline types; `!` and
73
+ `BREAKING CHANGE:` footer for breaking changes).
74
+ - Trunk-Based Development — https://trunkbaseddevelopment.com/ (small, short-lived changes committed
75
+ frequently to a shared branch that always stays releasable).
76
+ - Martin Fowler, "YAGNI" — https://martinfowler.com/bliki/Yagni.html (build capability only when it is
77
+ actually needed, not because it might be useful later).
78
+ - "Don't repeat yourself" — https://en.wikipedia.org/wiki/Don%27t_repeat_yourself (every piece of
79
+ knowledge should have a single, unambiguous representation; the classic "rule of three" for when to
80
+ extract).
@@ -0,0 +1,70 @@
1
+ ---
2
+ name: security-review
3
+ description: Review a change for secrets, authorization and attack surface against OWASP ASVS + Top 10.
4
+ capability: security
5
+ inputs: The change under review (diff/branch), the affected code, config, and dependency manifests.
6
+ outputs: A severity-ranked findings report with a Pass / Pass-with-follow-ups / Block verdict.
7
+ standard: OWASP ASVS + Top 10
8
+ ---
9
+ # Security review
10
+
11
+ Scoped, standards-based review of a change to catch security defects before merge or deploy.
12
+
13
+ ## When to use
14
+ When a change touches authentication, authorization, secrets, input handling, injection surfaces,
15
+ sensitive-data flows, outbound network calls, or dependencies — or whenever the workflow reaches a
16
+ security step or a `policy.md` security gate.
17
+
18
+ ## Method
19
+ Scope to the diff and its trust boundaries (new inputs, new auth/authz checks, new secrets, new outbound
20
+ calls, new dependencies). Walk each area, mapping findings to OWASP Top 10 (2021) and ASVS 5.0:
21
+
22
+ 1. **Authentication & session** (Top-10 A07; ASVS Authentication) — credential handling, MFA where
23
+ required, session lifetime/rotation/invalidation, no auth bypass introduced.
24
+ 2. **Authorization / access control** (A01) — every new endpoint/action enforces least privilege and
25
+ object-level checks; no IDOR, no missing server-side authz, no privilege escalation.
26
+ 3. **Secrets & cryptography** (A02) — no secrets in cleartext, source, logs, or fixtures; `.gitignore`
27
+ covers them and a `*.example` is provided; strong algorithms, no hard-coded keys, TLS in transit.
28
+ 4. **Input validation & injection** (A03) — untrusted input is validated/encoded/parameterised; check
29
+ SQL/NoSQL/command/LDAP injection and XSS on every new sink; no string-built queries.
30
+ 5. **Sensitive-data exposure & misconfiguration** (A02, A05) — PII minimised and protected, safe error
31
+ messages, secure defaults, no debug/verbose leakage, correct security headers/CORS.
32
+ 6. **Vulnerable & outdated dependencies** (A06) — new/updated packages checked for known CVEs and
33
+ maintenance; pin and justify additions.
34
+ 7. **Design, integrity & SSRF** (A04, A08, A10) — threat-model the change for insecure design, unsafe
35
+ deserialization / unsigned update or CI paths, and outbound requests reachable by user-controlled
36
+ URLs (SSRF).
37
+ 8. **Logging & monitoring** (A09) — security-relevant events are logged without leaking secrets/PII.
38
+
39
+ Assign each finding a severity (Critical / High / Medium / Low / Info) calibrated to the project's target
40
+ ASVS level, and a concrete remediation.
41
+
42
+ ## Output contract
43
+ Write findings as a report / task comments alongside the change (granular, one line at a time), each
44
+ finding carrying: severity, the Top-10 category, the ASVS requirement id where applicable, location
45
+ (file:line), and remediation. End with an explicit verdict: **Pass**, **Pass with follow-ups**, or
46
+ **Block**. Report to the orchestrator and group chat with:
47
+
48
+ ```
49
+ ::spectoflow role=security kind=review msg=<verdict + counts by severity>
50
+ ::spectoflow role=security kind=finding msg=<severity> <Top-10 cat> <file:line> — <issue>
51
+ ```
52
+
53
+ A security-sensitive change routes to the `policy.md` human-approval gate; the skill never self-approves.
54
+
55
+ ## Quality bar
56
+ - [ ] Every OWASP Top 10 (2021) category A01–A10 is explicitly marked **Considered** or **N/A**.
57
+ - [ ] Each finding has severity + Top-10 category + remediation (+ ASVS id where applicable).
58
+ - [ ] Secrets checked: none in cleartext/source/logs; gitignored; `*.example` present.
59
+ - [ ] Authz checked on every new endpoint/action (least privilege, object-level).
60
+ - [ ] Injection/XSS checked on every new sink; inputs validated/parameterised.
61
+ - [ ] New/updated dependencies screened for known vulnerabilities.
62
+ - [ ] A clear verdict is stated; no open Critical/High without a recorded human decision.
63
+
64
+ ## References
65
+ - OWASP Top 10:2021 — https://owasp.org/Top10/2021/ (A01 Broken Access Control, A02 Cryptographic
66
+ Failures, A03 Injection, A04 Insecure Design, A05 Security Misconfiguration, A06 Vulnerable and
67
+ Outdated Components, A07 Identification and Authentication Failures, A08 Software and Data Integrity
68
+ Failures, A09 Security Logging and Monitoring Failures, A10 Server-Side Request Forgery).
69
+ - OWASP Application Security Verification Standard (ASVS) 5.0.0 (2025-05-30) —
70
+ https://owasp.org/www-project-application-security-verification-standard/