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,15 @@
1
+ # Capabilities — palette + project-type adaptation
2
+
3
+ A capability is a role; an agent implements it; a skill is the procedure it runs. Workflows ask for a
4
+ capability, never a named agent — this keeps spectoflow agent-agnostic.
5
+
6
+ Palette: intake · research · analysis · architecture · planning · testing · implementation · security · quality · design · operations.
7
+
8
+ | Project type | Active capabilities |
9
+ |---|---|
10
+ | app / web / API | all |
11
+ | infra / IaC | intake, research, analysis, architecture, planning, security, quality, implementation, operations |
12
+ | data / ETL | intake, analysis, architecture, planning, testing (data quality), implementation, quality |
13
+ | study / content | intake, research, analysis, planning, quality |
14
+
15
+ For non-code projects the code-specific capabilities simply stay inactive; the workflow adapts.
@@ -0,0 +1,10 @@
1
+ {
2
+ "mode": "semi",
3
+ "language": "en",
4
+ "agent": "claude",
5
+ "projectType": "app",
6
+ "runners": {
7
+ "claude": "claude -p --permission-mode acceptEdits",
8
+ "codex": "codex exec"
9
+ }
10
+ }
@@ -0,0 +1,117 @@
1
+ 'use strict';
2
+ const fs = require('fs');
3
+ const path = require('path');
4
+ const store = require('../lib/store');
5
+ const { startRun } = require('./runner');
6
+
7
+ // step (from store.readWorkflow) -> { agent, skill } or { error }
8
+ function resolveStep(root, step) {
9
+ if (!step.cap) return { error: `step "${step.name}" has no capability annotation` };
10
+ const agent = (store.readAgents(root).find((a) => a.capability === step.cap) || {}).name;
11
+ if (!agent) return { error: `step "${step.name}": no agent for capability "${step.cap}"` };
12
+ if (step.skill) {
13
+ const sp = path.join(root, '.spectoflow', 'skills', step.skill, 'SKILL.md');
14
+ if (!fs.existsSync(sp)) return { error: `step "${step.name}": skill "${step.skill}" not found` };
15
+ }
16
+ return { agent, skill: step.skill || null };
17
+ }
18
+
19
+ function saveState(root, o, emit) {
20
+ const rt = store.readRuntime(root); rt.orchestration = o; store.writeRuntime(root, rt);
21
+ emit({ type: 'change' });
22
+ }
23
+ function post(root, role, kind, text, emit) {
24
+ const m = store.appendMessage(root, { role, agent: role, kind, text });
25
+ emit({ type: 'message', message: m });
26
+ }
27
+
28
+ async function runOrchestration({ root, request, mode, runStep, confirm, resume }, emit) {
29
+ const enabled = store.readWorkflow(root).filter((s) => s.enabled);
30
+ let o, startAt = 0;
31
+ if (resume) {
32
+ const prev = store.readRuntime(root).orchestration;
33
+ if (!prev || ['done', 'cancelled'].includes(prev.status)) return prev || null;
34
+ o = prev; o.status = 'running'; mode = o.mode;
35
+ startAt = o.steps.findIndex((s) => s.status !== 'done'); // first not-done step
36
+ if (startAt < 0) startAt = enabled.length;
37
+ } else {
38
+ o = {
39
+ id: 'o' + Date.now().toString(36), request, mode, status: 'running', currentStep: 0,
40
+ startedAt: new Date().toISOString(),
41
+ steps: enabled.map((s) => ({ name: s.name, cap: s.cap, skill: s.skill, policy: !!s.policy, agent: null, status: 'pending' })),
42
+ };
43
+ }
44
+ saveState(root, o, emit);
45
+
46
+ for (let i = startAt; i < enabled.length; i++) {
47
+ o.currentStep = i; const step = enabled[i], st = o.steps[i];
48
+ const r = resolveStep(root, step);
49
+ if (r.error) { st.status = 'failed'; o.status = 'failed'; saveState(root, o, emit); post(root, 'orchestrator', 'status', '⚠ ' + r.error, emit); return o; }
50
+ st.agent = r.agent;
51
+
52
+ const policyGated = !!step.policy || step.cap === 'security';
53
+ const needConfirm = mode === 'manual' || policyGated; // v1: semi == autopilot + policy (spec O2)
54
+ if (needConfirm) {
55
+ st.status = 'awaiting_approval'; o.status = 'awaiting_approval'; saveState(root, o, emit);
56
+ post(root, 'orchestrator', 'question', `Approve step "${step.name}" (${r.agent})${policyGated ? ' — policy gate' : ''}?`, emit);
57
+ const dec = await confirm(step, { policy: policyGated });
58
+ post(root, 'orchestrator', 'status', `decision: ${dec.decision}${dec.note ? ' — ' + dec.note : ''}`, emit);
59
+ if (dec.decision === 'cancel') { st.status = 'skipped'; o.status = 'cancelled'; saveState(root, o, emit); return o; }
60
+ o.status = 'running'; saveState(root, o, emit);
61
+ }
62
+
63
+ st.status = 'running'; saveState(root, o, emit);
64
+ post(root, 'orchestrator', 'status', `→ ${step.name} (${r.agent})`, emit);
65
+ const exit = await runStep({ root, step, agent: r.agent, skill: r.skill, request }, emit);
66
+ if (exit !== 0) { st.status = 'failed'; o.status = 'failed'; saveState(root, o, emit); post(root, 'orchestrator', 'status', `⚠ ${step.name} failed (exit ${exit})`, emit); return o; }
67
+ st.status = 'done'; saveState(root, o, emit);
68
+ }
69
+ o.currentStep = enabled.length; o.status = 'done'; saveState(root, o, emit);
70
+ post(root, 'orchestrator', 'status', '■ workflow complete', emit);
71
+ return o;
72
+ }
73
+
74
+ function buildPrompt({ step, agent, skill, request }) {
75
+ const skillLine = skill
76
+ ? `Run the "${skill}" skill (.spectoflow/skills/${skill}/SKILL.md) for this request.`
77
+ : `Apply your role's mandate for this request.`;
78
+ return [
79
+ `You are the ${agent} (capability: ${step.cap}). ${skillLine}`,
80
+ `Request: ${request}`,
81
+ `Context: the current specs/ and plans/ in this project.`,
82
+ `Work to the project standard and post progress as ::spectoflow role=${step.cap} kind=… msg=… lines.`,
83
+ ].join('\n');
84
+ }
85
+
86
+ function defaultRunStep({ root, step, agent, skill, request }, emit) {
87
+ return new Promise((resolve) => {
88
+ const prompt = buildPrompt({ step, agent, skill, request });
89
+ const tool = store.readConfig(root).agent;
90
+ const r = startRun(root, { prompt, agent: tool }, (e) => { emit(e); if (e.type === 'run-end') resolve(e.code); });
91
+ if (r.error) { emit({ type: 'message', message: { role: 'orchestrator', kind: 'status', text: r.error } }); resolve(1); }
92
+ });
93
+ }
94
+
95
+ let pending = null;
96
+ function defaultConfirm(step, reason) { return new Promise((resolve) => { pending = { resolve }; }); }
97
+ function submitDecision(decision, note) {
98
+ if (!pending) return false;
99
+ const p = pending; pending = null; p.resolve({ decision, note }); return true;
100
+ }
101
+
102
+ // Boot-time reconcile: a process restart loses any in-flight orchestration (the runOrchestration
103
+ // call stack, and the in-memory `pending` approval) even though runtime.json still records it as
104
+ // 'running' or 'awaiting_approval'. That stale non-terminal status wedges the /api/orchestrate 409
105
+ // guard forever. This does NOT resume execution — it just marks the stale run (and any of its
106
+ // in-flight steps) 'failed' so the dashboard is unwedged and the user can start a fresh run.
107
+ function reconcileOnBoot(root) {
108
+ const rt = store.readRuntime(root);
109
+ const o = rt.orchestration;
110
+ if (!o || !['running', 'awaiting_approval'].includes(o.status)) return false;
111
+ o.status = 'failed';
112
+ (o.steps || []).forEach((s) => { if (['running', 'awaiting_approval'].includes(s.status)) s.status = 'failed'; });
113
+ store.writeRuntime(root, rt);
114
+ return true;
115
+ }
116
+
117
+ module.exports = { resolveStep, runOrchestration, defaultRunStep, defaultConfirm, submitDecision, reconcileOnBoot };