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.
- package/LICENSE +21 -0
- package/README.md +133 -0
- package/bin/spectoflow.js +170 -0
- package/lib/adapters.js +120 -0
- package/lib/detect.js +34 -0
- package/lib/manifest.js +36 -0
- package/lib/ownership.js +34 -0
- package/lib/update.js +78 -0
- package/package.json +30 -0
- package/templates/AGENTS.md +66 -0
- package/templates/agents/architect.md +54 -0
- package/templates/agents/business-analyst.md +53 -0
- package/templates/agents/code-reviewer.md +58 -0
- package/templates/agents/developer.md +73 -0
- package/templates/agents/devops.md +59 -0
- package/templates/agents/product-manager.md +53 -0
- package/templates/agents/qa-engineer.md +63 -0
- package/templates/agents/security-engineer.md +59 -0
- package/templates/agents/tech-lead.md +52 -0
- package/templates/agents/ux-designer.md +50 -0
- package/templates/capabilities.md +15 -0
- package/templates/config.json +10 -0
- package/templates/dashboard/orchestrator.js +117 -0
- package/templates/dashboard/public/app.js +747 -0
- package/templates/dashboard/public/charts.js +192 -0
- package/templates/dashboard/public/icons.js +28 -0
- package/templates/dashboard/public/index.html +226 -0
- package/templates/dashboard/public/stats.js +32 -0
- package/templates/dashboard/public/styles.css +426 -0
- package/templates/dashboard/runner.js +77 -0
- package/templates/dashboard/server.js +115 -0
- package/templates/lib/store.js +289 -0
- package/templates/policy.md +11 -0
- package/templates/skills/analyze-requirements/SKILL.md +67 -0
- package/templates/skills/brainstorm/SKILL.md +58 -0
- package/templates/skills/code-review/SKILL.md +67 -0
- package/templates/skills/implement/SKILL.md +80 -0
- package/templates/skills/security-review/SKILL.md +70 -0
- package/templates/skills/write-adr/SKILL.md +61 -0
- package/templates/skills/write-e2e-tests/SKILL.md +99 -0
- package/templates/skills/write-plan/SKILL.md +66 -0
- package/templates/skills/write-spec/SKILL.md +66 -0
- package/templates/skills/write-tests/SKILL.md +80 -0
- 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,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 };
|