jumpstart-mode 1.1.12 → 1.1.13
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/.github/agents/jumpstart-adversary.agent.md +2 -1
- package/.github/agents/jumpstart-architect.agent.md +5 -6
- package/.github/agents/jumpstart-challenger.agent.md +2 -1
- package/.github/agents/jumpstart-devops.agent.md +2 -2
- package/.github/agents/jumpstart-diagram-verifier.agent.md +2 -1
- package/.github/agents/jumpstart-maintenance.agent.md +1 -0
- package/.github/agents/jumpstart-performance.agent.md +1 -0
- package/.github/agents/jumpstart-pm.agent.md +1 -1
- package/.github/agents/jumpstart-refactor.agent.md +1 -0
- package/.github/agents/jumpstart-requirements-extractor.agent.md +1 -0
- package/.github/agents/jumpstart-researcher.agent.md +1 -0
- package/.github/agents/jumpstart-retrospective.agent.md +1 -0
- package/.github/agents/jumpstart-reviewer.agent.md +2 -0
- package/.github/agents/jumpstart-scout.agent.md +1 -1
- package/.github/agents/jumpstart-scrum-master.agent.md +1 -0
- package/.github/agents/jumpstart-security.agent.md +2 -1
- package/.github/agents/jumpstart-tech-writer.agent.md +1 -0
- package/.github/workflows/quality.yml +19 -2
- package/.jumpstart/agents/analyst.md +38 -0
- package/.jumpstart/agents/architect.md +38 -0
- package/.jumpstart/agents/challenger.md +38 -0
- package/.jumpstart/agents/developer.md +41 -0
- package/.jumpstart/agents/pm.md +38 -0
- package/.jumpstart/agents/scout.md +33 -0
- package/.jumpstart/agents/ux-designer.md +4 -0
- package/.jumpstart/config.yaml +24 -0
- package/.jumpstart/schemas/timeline.schema.json +1 -0
- package/.jumpstart/skills/skill-creator/SKILL.md +485 -357
- package/.jumpstart/skills/skill-creator/agents/analyzer.md +274 -0
- package/.jumpstart/skills/skill-creator/agents/comparator.md +202 -0
- package/.jumpstart/skills/skill-creator/agents/grader.md +223 -0
- package/.jumpstart/skills/skill-creator/assets/eval_review.html +146 -0
- package/.jumpstart/skills/skill-creator/eval-viewer/generate_review.py +471 -0
- package/.jumpstart/skills/skill-creator/eval-viewer/viewer.html +1325 -0
- package/.jumpstart/skills/skill-creator/references/schemas.md +430 -0
- package/.jumpstart/skills/skill-creator/scripts/__init__.py +0 -0
- package/.jumpstart/skills/skill-creator/scripts/aggregate_benchmark.py +401 -0
- package/.jumpstart/skills/skill-creator/scripts/generate_report.py +326 -0
- package/.jumpstart/skills/skill-creator/scripts/improve_description.py +247 -0
- package/.jumpstart/skills/skill-creator/scripts/package_skill.py +136 -110
- package/.jumpstart/skills/skill-creator/scripts/run_eval.py +310 -0
- package/.jumpstart/skills/skill-creator/scripts/run_loop.py +328 -0
- package/.jumpstart/skills/skill-creator/scripts/utils.py +47 -0
- package/.jumpstart/state/timeline.json +659 -0
- package/.jumpstart/usage-log.json +74 -3
- package/README.md +62 -1
- package/bin/cli.js +3217 -1
- package/bin/headless-runner.js +62 -2
- package/bin/lib/agent-checkpoint.js +168 -0
- package/bin/lib/ai-evaluation.js +104 -0
- package/bin/lib/ai-intake.js +152 -0
- package/bin/lib/ambiguity-heatmap.js +152 -0
- package/bin/lib/artifact-comparison.js +104 -0
- package/bin/lib/ast-edit-engine.js +157 -0
- package/bin/lib/backlog-sync.js +338 -0
- package/bin/lib/bcdr-planning.js +158 -0
- package/bin/lib/bidirectional-trace.js +199 -0
- package/bin/lib/branch-workflow.js +266 -0
- package/bin/lib/cab-output.js +119 -0
- package/bin/lib/chat-integration.js +122 -0
- package/bin/lib/ci-cd-integration.js +208 -0
- package/bin/lib/codebase-retrieval.js +125 -0
- package/bin/lib/collaboration.js +168 -0
- package/bin/lib/compliance-packs.js +213 -0
- package/bin/lib/context-chunker.js +128 -0
- package/bin/lib/context-onboarding.js +122 -0
- package/bin/lib/contract-first.js +124 -0
- package/bin/lib/cost-router.js +148 -0
- package/bin/lib/credential-boundary.js +155 -0
- package/bin/lib/data-classification.js +180 -0
- package/bin/lib/data-contracts.js +129 -0
- package/bin/lib/db-evolution.js +158 -0
- package/bin/lib/decision-conflicts.js +299 -0
- package/bin/lib/delivery-confidence.js +361 -0
- package/bin/lib/dependency-upgrade.js +153 -0
- package/bin/lib/design-system.js +133 -0
- package/bin/lib/deterministic-artifacts.js +151 -0
- package/bin/lib/diagram-studio.js +115 -0
- package/bin/lib/domain-ontology.js +140 -0
- package/bin/lib/ea-review-packet.js +151 -0
- package/bin/lib/enterprise-search.js +123 -0
- package/bin/lib/enterprise-templates.js +140 -0
- package/bin/lib/environment-promotion.js +220 -0
- package/bin/lib/estimation-studio.js +130 -0
- package/bin/lib/event-modeling.js +133 -0
- package/bin/lib/evidence-collector.js +179 -0
- package/bin/lib/finops-planner.js +182 -0
- package/bin/lib/fitness-functions.js +279 -0
- package/bin/lib/focus.js +448 -0
- package/bin/lib/governance-dashboard.js +165 -0
- package/bin/lib/guided-handoff.js +120 -0
- package/bin/lib/impact-analysis.js +190 -0
- package/bin/lib/incident-feedback.js +157 -0
- package/bin/lib/integrate.js +1 -1
- package/bin/lib/knowledge-graph.js +122 -0
- package/bin/lib/legacy-modernizer.js +160 -0
- package/bin/lib/migration-planner.js +144 -0
- package/bin/lib/model-governance.js +185 -0
- package/bin/lib/model-router.js +144 -0
- package/bin/lib/multi-repo.js +272 -0
- package/bin/lib/next-phase.js +53 -8
- package/bin/lib/ops-ownership.js +152 -0
- package/bin/lib/parallel-agents.js +257 -0
- package/bin/lib/pattern-library.js +115 -0
- package/bin/lib/persona-packs.js +99 -0
- package/bin/lib/plan-executor.js +366 -0
- package/bin/lib/platform-engineering.js +119 -0
- package/bin/lib/playback-summaries.js +126 -0
- package/bin/lib/policy-engine.js +240 -0
- package/bin/lib/portfolio-reporting.js +357 -0
- package/bin/lib/pr-package.js +197 -0
- package/bin/lib/project-memory.js +235 -0
- package/bin/lib/prompt-governance.js +130 -0
- package/bin/lib/promptless-mode.js +128 -0
- package/bin/lib/quality-graph.js +193 -0
- package/bin/lib/raci-matrix.js +188 -0
- package/bin/lib/refactor-planner.js +167 -0
- package/bin/lib/reference-architectures.js +304 -0
- package/bin/lib/release-readiness.js +171 -0
- package/bin/lib/repo-graph.js +262 -0
- package/bin/lib/requirements-baseline.js +358 -0
- package/bin/lib/risk-register.js +211 -0
- package/bin/lib/role-approval.js +249 -0
- package/bin/lib/role-views.js +142 -0
- package/bin/lib/root-cause-analysis.js +132 -0
- package/bin/lib/runtime-debugger.js +154 -0
- package/bin/lib/safe-rename.js +135 -0
- package/bin/lib/semantic-diff.js +335 -0
- package/bin/lib/sla-slo.js +210 -0
- package/bin/lib/spec-comments.js +147 -0
- package/bin/lib/spec-maturity.js +287 -0
- package/bin/lib/sre-integration.js +154 -0
- package/bin/lib/structured-elicitation.js +174 -0
- package/bin/lib/telemetry-feedback.js +118 -0
- package/bin/lib/test-generator.js +146 -0
- package/bin/lib/timeline.js +2 -1
- package/bin/lib/tool-bridge.js +107 -0
- package/bin/lib/tool-guardrails.js +139 -0
- package/bin/lib/tool-schemas.js +172 -3
- package/bin/lib/transcript-ingestion.js +150 -0
- package/bin/lib/vendor-risk.js +173 -0
- package/bin/lib/waiver-workflow.js +174 -0
- package/bin/lib/web-dashboard.js +126 -0
- package/bin/lib/workshop-mode.js +165 -0
- package/bin/lib/workstream-ownership.js +104 -0
- package/package.json +1 -1
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* plan-executor.js — Rich Plan Execution Engine
|
|
3
|
+
*
|
|
4
|
+
* Turn implementation-plan tasks into executable, resumable,
|
|
5
|
+
* verifiable agent jobs.
|
|
6
|
+
*
|
|
7
|
+
* State file: .jumpstart/state/plan-execution.json
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* node bin/lib/plan-executor.js start|status|resume|verify|reset [options]
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
'use strict';
|
|
14
|
+
|
|
15
|
+
const fs = require('fs');
|
|
16
|
+
const path = require('path');
|
|
17
|
+
|
|
18
|
+
const DEFAULT_EXECUTION_FILE = path.join('.jumpstart', 'state', 'plan-execution.json');
|
|
19
|
+
|
|
20
|
+
const TASK_STATUSES = ['pending', 'in_progress', 'completed', 'failed', 'skipped', 'blocked'];
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Default execution state.
|
|
24
|
+
* @returns {object}
|
|
25
|
+
*/
|
|
26
|
+
function defaultExecutionState() {
|
|
27
|
+
return {
|
|
28
|
+
version: '1.0.0',
|
|
29
|
+
created_at: new Date().toISOString(),
|
|
30
|
+
last_updated: null,
|
|
31
|
+
plan_source: null,
|
|
32
|
+
jobs: [],
|
|
33
|
+
execution_log: []
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Load execution state from disk.
|
|
39
|
+
* @param {string} [stateFile]
|
|
40
|
+
* @returns {object}
|
|
41
|
+
*/
|
|
42
|
+
function loadExecutionState(stateFile) {
|
|
43
|
+
const filePath = stateFile || DEFAULT_EXECUTION_FILE;
|
|
44
|
+
if (!fs.existsSync(filePath)) {
|
|
45
|
+
return defaultExecutionState();
|
|
46
|
+
}
|
|
47
|
+
try {
|
|
48
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
49
|
+
} catch {
|
|
50
|
+
return defaultExecutionState();
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Save execution state to disk.
|
|
56
|
+
* @param {object} state
|
|
57
|
+
* @param {string} [stateFile]
|
|
58
|
+
*/
|
|
59
|
+
function saveExecutionState(state, stateFile) {
|
|
60
|
+
const filePath = stateFile || DEFAULT_EXECUTION_FILE;
|
|
61
|
+
const dir = path.dirname(filePath);
|
|
62
|
+
if (!fs.existsSync(dir)) {
|
|
63
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
64
|
+
}
|
|
65
|
+
state.last_updated = new Date().toISOString();
|
|
66
|
+
fs.writeFileSync(filePath, JSON.stringify(state, null, 2) + '\n', 'utf8');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Parse implementation plan into executable jobs.
|
|
71
|
+
*
|
|
72
|
+
* @param {string} planContent - Implementation plan markdown content.
|
|
73
|
+
* @returns {object[]}
|
|
74
|
+
*/
|
|
75
|
+
function parsePlanToJobs(planContent) {
|
|
76
|
+
const jobs = [];
|
|
77
|
+
const lines = planContent.split('\n');
|
|
78
|
+
let currentMilestone = null;
|
|
79
|
+
|
|
80
|
+
for (const line of lines) {
|
|
81
|
+
// Detect milestones like "## Milestone 1: ...", "## M01: ..."
|
|
82
|
+
const milestoneMatch = line.match(/^#{2,3}\s+(?:Milestone\s+)?(\d+|M\d+)[:\s—–-]+\s*(.+)$/i);
|
|
83
|
+
if (milestoneMatch) {
|
|
84
|
+
currentMilestone = milestoneMatch[1].startsWith('M') ? milestoneMatch[1] : `M${milestoneMatch[1].padStart(2, '0')}`;
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Detect tasks like "- M01-T01: ...", "### M01-T01 — ..."
|
|
89
|
+
const taskMatch = line.match(/(?:^#{2,4}\s+|^[-*]\s+\*{0,2})(M\d+-T\d+)(?:\*{0,2})[:\s—–-]+\s*(.+)/i);
|
|
90
|
+
if (taskMatch) {
|
|
91
|
+
const taskId = taskMatch[1];
|
|
92
|
+
const title = taskMatch[2].trim().replace(/\*{1,2}/g, '');
|
|
93
|
+
|
|
94
|
+
// Extract story refs
|
|
95
|
+
const storyRefs = line.match(/E\d+-S\d+/g) || [];
|
|
96
|
+
|
|
97
|
+
// Extract dependency refs (depends on: M01-T02)
|
|
98
|
+
const depMatch = line.match(/depends?\s+on[:\s]+([^.]+)/i);
|
|
99
|
+
const dependencies = depMatch
|
|
100
|
+
? (depMatch[1].match(/M\d+-T\d+/g) || [])
|
|
101
|
+
: [];
|
|
102
|
+
|
|
103
|
+
jobs.push({
|
|
104
|
+
id: taskId,
|
|
105
|
+
title,
|
|
106
|
+
milestone: currentMilestone || taskId.split('-')[0],
|
|
107
|
+
status: 'pending',
|
|
108
|
+
story_refs: [...new Set(storyRefs)],
|
|
109
|
+
dependencies,
|
|
110
|
+
started_at: null,
|
|
111
|
+
completed_at: null,
|
|
112
|
+
verification: null,
|
|
113
|
+
output_files: [],
|
|
114
|
+
error: null
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return jobs;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Initialize execution from implementation plan.
|
|
124
|
+
*
|
|
125
|
+
* @param {string} root - Project root.
|
|
126
|
+
* @param {object} [options]
|
|
127
|
+
* @returns {object}
|
|
128
|
+
*/
|
|
129
|
+
function initializeExecution(root, options = {}) {
|
|
130
|
+
const planPath = options.planPath || path.join(root, 'specs', 'implementation-plan.md');
|
|
131
|
+
const stateFile = options.stateFile || path.join(root, DEFAULT_EXECUTION_FILE);
|
|
132
|
+
|
|
133
|
+
if (!fs.existsSync(planPath)) {
|
|
134
|
+
return { success: false, error: `Implementation plan not found: ${planPath}` };
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const planContent = fs.readFileSync(planPath, 'utf8');
|
|
138
|
+
const jobs = parsePlanToJobs(planContent);
|
|
139
|
+
|
|
140
|
+
if (jobs.length === 0) {
|
|
141
|
+
return { success: false, error: 'No tasks found in implementation plan' };
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const state = defaultExecutionState();
|
|
145
|
+
state.plan_source = path.relative(root, planPath);
|
|
146
|
+
state.jobs = jobs;
|
|
147
|
+
state.execution_log.push({
|
|
148
|
+
event: 'initialized',
|
|
149
|
+
timestamp: new Date().toISOString(),
|
|
150
|
+
total_jobs: jobs.length
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
saveExecutionState(state, stateFile);
|
|
154
|
+
|
|
155
|
+
return {
|
|
156
|
+
success: true,
|
|
157
|
+
total_jobs: jobs.length,
|
|
158
|
+
milestones: [...new Set(jobs.map(j => j.milestone))],
|
|
159
|
+
jobs: jobs.map(j => ({ id: j.id, title: j.title, milestone: j.milestone, dependencies: j.dependencies }))
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Get execution status.
|
|
165
|
+
*
|
|
166
|
+
* @param {object} [options]
|
|
167
|
+
* @returns {object}
|
|
168
|
+
*/
|
|
169
|
+
function getExecutionStatus(options = {}) {
|
|
170
|
+
const stateFile = options.stateFile || DEFAULT_EXECUTION_FILE;
|
|
171
|
+
const state = loadExecutionState(stateFile);
|
|
172
|
+
|
|
173
|
+
if (state.jobs.length === 0) {
|
|
174
|
+
return { success: true, initialized: false, message: 'No execution plan loaded' };
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const statusCounts = {};
|
|
178
|
+
for (const status of TASK_STATUSES) {
|
|
179
|
+
statusCounts[status] = state.jobs.filter(j => j.status === status).length;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const completedPct = state.jobs.length > 0
|
|
183
|
+
? Math.round((statusCounts.completed / state.jobs.length) * 100)
|
|
184
|
+
: 0;
|
|
185
|
+
|
|
186
|
+
// Find next actionable tasks (pending with all dependencies completed)
|
|
187
|
+
const completedIds = new Set(state.jobs.filter(j => j.status === 'completed').map(j => j.id));
|
|
188
|
+
const nextTasks = state.jobs.filter(j =>
|
|
189
|
+
j.status === 'pending' &&
|
|
190
|
+
j.dependencies.every(dep => completedIds.has(dep))
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
return {
|
|
194
|
+
success: true,
|
|
195
|
+
initialized: true,
|
|
196
|
+
plan_source: state.plan_source,
|
|
197
|
+
total_jobs: state.jobs.length,
|
|
198
|
+
progress: completedPct,
|
|
199
|
+
status_counts: statusCounts,
|
|
200
|
+
next_tasks: nextTasks.map(j => ({ id: j.id, title: j.title, milestone: j.milestone })),
|
|
201
|
+
jobs: state.jobs.map(j => ({
|
|
202
|
+
id: j.id,
|
|
203
|
+
title: j.title,
|
|
204
|
+
status: j.status,
|
|
205
|
+
milestone: j.milestone
|
|
206
|
+
}))
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Update a job's status.
|
|
212
|
+
*
|
|
213
|
+
* @param {string} jobId - Task ID (e.g., M01-T01).
|
|
214
|
+
* @param {string} status - New status.
|
|
215
|
+
* @param {object} [options]
|
|
216
|
+
* @returns {object}
|
|
217
|
+
*/
|
|
218
|
+
function updateJobStatus(jobId, status, options = {}) {
|
|
219
|
+
if (!TASK_STATUSES.includes(status)) {
|
|
220
|
+
return { success: false, error: `Invalid status: ${status}. Must be one of: ${TASK_STATUSES.join(', ')}` };
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const stateFile = options.stateFile || DEFAULT_EXECUTION_FILE;
|
|
224
|
+
const state = loadExecutionState(stateFile);
|
|
225
|
+
const job = state.jobs.find(j => j.id === jobId);
|
|
226
|
+
|
|
227
|
+
if (!job) {
|
|
228
|
+
return { success: false, error: `Job not found: ${jobId}` };
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const previousStatus = job.status;
|
|
232
|
+
job.status = status;
|
|
233
|
+
|
|
234
|
+
if (status === 'in_progress' && !job.started_at) {
|
|
235
|
+
job.started_at = new Date().toISOString();
|
|
236
|
+
}
|
|
237
|
+
if (status === 'completed') {
|
|
238
|
+
job.completed_at = new Date().toISOString();
|
|
239
|
+
}
|
|
240
|
+
if (status === 'failed' && options.error) {
|
|
241
|
+
job.error = options.error;
|
|
242
|
+
}
|
|
243
|
+
if (options.output_files) {
|
|
244
|
+
job.output_files = options.output_files;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
state.execution_log.push({
|
|
248
|
+
event: 'status_change',
|
|
249
|
+
job_id: jobId,
|
|
250
|
+
from: previousStatus,
|
|
251
|
+
to: status,
|
|
252
|
+
timestamp: new Date().toISOString()
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
saveExecutionState(state, stateFile);
|
|
256
|
+
|
|
257
|
+
return {
|
|
258
|
+
success: true,
|
|
259
|
+
job_id: jobId,
|
|
260
|
+
previous_status: previousStatus,
|
|
261
|
+
new_status: status,
|
|
262
|
+
started_at: job.started_at,
|
|
263
|
+
completed_at: job.completed_at
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Verify a completed job.
|
|
269
|
+
*
|
|
270
|
+
* @param {string} jobId - Task ID.
|
|
271
|
+
* @param {string} root - Project root.
|
|
272
|
+
* @param {object} [options]
|
|
273
|
+
* @returns {object}
|
|
274
|
+
*/
|
|
275
|
+
function verifyJob(jobId, root, options = {}) {
|
|
276
|
+
const stateFile = options.stateFile || path.join(root, DEFAULT_EXECUTION_FILE);
|
|
277
|
+
const state = loadExecutionState(stateFile);
|
|
278
|
+
const job = state.jobs.find(j => j.id === jobId);
|
|
279
|
+
|
|
280
|
+
if (!job) {
|
|
281
|
+
return { success: false, error: `Job not found: ${jobId}` };
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const checks = [];
|
|
285
|
+
|
|
286
|
+
// Check if job has output files
|
|
287
|
+
if (job.output_files && job.output_files.length > 0) {
|
|
288
|
+
for (const file of job.output_files) {
|
|
289
|
+
const exists = fs.existsSync(path.join(root, file));
|
|
290
|
+
checks.push({ check: `file_exists: ${file}`, passed: exists });
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Check status
|
|
295
|
+
checks.push({ check: 'status_completed', passed: job.status === 'completed' });
|
|
296
|
+
checks.push({ check: 'has_completion_time', passed: !!job.completed_at });
|
|
297
|
+
checks.push({ check: 'no_errors', passed: !job.error });
|
|
298
|
+
|
|
299
|
+
const allPassed = checks.every(c => c.passed);
|
|
300
|
+
|
|
301
|
+
job.verification = {
|
|
302
|
+
verified_at: new Date().toISOString(),
|
|
303
|
+
passed: allPassed,
|
|
304
|
+
checks
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
saveExecutionState(state, stateFile);
|
|
308
|
+
|
|
309
|
+
return {
|
|
310
|
+
success: true,
|
|
311
|
+
job_id: jobId,
|
|
312
|
+
verified: allPassed,
|
|
313
|
+
checks,
|
|
314
|
+
summary: {
|
|
315
|
+
total_checks: checks.length,
|
|
316
|
+
passed: checks.filter(c => c.passed).length,
|
|
317
|
+
failed: checks.filter(c => !c.passed).length
|
|
318
|
+
}
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Reset execution state.
|
|
324
|
+
*
|
|
325
|
+
* @param {object} [options]
|
|
326
|
+
* @returns {object}
|
|
327
|
+
*/
|
|
328
|
+
function resetExecution(options = {}) {
|
|
329
|
+
const stateFile = options.stateFile || DEFAULT_EXECUTION_FILE;
|
|
330
|
+
const state = loadExecutionState(stateFile);
|
|
331
|
+
const previousCount = state.jobs.length;
|
|
332
|
+
|
|
333
|
+
for (const job of state.jobs) {
|
|
334
|
+
job.status = 'pending';
|
|
335
|
+
job.started_at = null;
|
|
336
|
+
job.completed_at = null;
|
|
337
|
+
job.verification = null;
|
|
338
|
+
job.error = null;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
state.execution_log.push({
|
|
342
|
+
event: 'reset',
|
|
343
|
+
timestamp: new Date().toISOString(),
|
|
344
|
+
jobs_reset: previousCount
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
saveExecutionState(state, stateFile);
|
|
348
|
+
|
|
349
|
+
return {
|
|
350
|
+
success: true,
|
|
351
|
+
jobs_reset: previousCount
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
module.exports = {
|
|
356
|
+
defaultExecutionState,
|
|
357
|
+
loadExecutionState,
|
|
358
|
+
saveExecutionState,
|
|
359
|
+
parsePlanToJobs,
|
|
360
|
+
initializeExecution,
|
|
361
|
+
getExecutionStatus,
|
|
362
|
+
updateJobStatus,
|
|
363
|
+
verifyJob,
|
|
364
|
+
resetExecution,
|
|
365
|
+
TASK_STATUSES
|
|
366
|
+
};
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* platform-engineering.js — Platform Engineering Integration (Item 86)
|
|
3
|
+
*
|
|
4
|
+
* Connect to golden paths, service templates, IDP portals,
|
|
5
|
+
* and paved-road tooling.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* node bin/lib/platform-engineering.js register|list|instantiate|report [options]
|
|
9
|
+
*
|
|
10
|
+
* State file: .jumpstart/state/platform-engineering.json
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
'use strict';
|
|
14
|
+
|
|
15
|
+
const fs = require('fs');
|
|
16
|
+
const path = require('path');
|
|
17
|
+
|
|
18
|
+
const DEFAULT_STATE_FILE = path.join('.jumpstart', 'state', 'platform-engineering.json');
|
|
19
|
+
|
|
20
|
+
const TEMPLATE_TYPES = ['service', 'library', 'worker', 'api-gateway', 'frontend'];
|
|
21
|
+
const GOLDEN_PATH_STAGES = ['scaffold', 'ci-cd', 'observability', 'security', 'deployment'];
|
|
22
|
+
|
|
23
|
+
function defaultState() {
|
|
24
|
+
return { version: '1.0.0', templates: [], golden_paths: [], instances: [], last_updated: null };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function loadState(stateFile) {
|
|
28
|
+
const fp = stateFile || DEFAULT_STATE_FILE;
|
|
29
|
+
if (!fs.existsSync(fp)) return defaultState();
|
|
30
|
+
try { return JSON.parse(fs.readFileSync(fp, 'utf8')); }
|
|
31
|
+
catch { return defaultState(); }
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function saveState(state, stateFile) {
|
|
35
|
+
const fp = stateFile || DEFAULT_STATE_FILE;
|
|
36
|
+
const dir = path.dirname(fp);
|
|
37
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
38
|
+
state.last_updated = new Date().toISOString();
|
|
39
|
+
fs.writeFileSync(fp, JSON.stringify(state, null, 2) + '\n', 'utf8');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function registerTemplate(name, type, options = {}) {
|
|
43
|
+
if (!name || !type) return { success: false, error: 'name and type are required' };
|
|
44
|
+
if (!TEMPLATE_TYPES.includes(type)) {
|
|
45
|
+
return { success: false, error: `Unknown type: ${type}. Valid: ${TEMPLATE_TYPES.join(', ')}` };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const stateFile = options.stateFile || DEFAULT_STATE_FILE;
|
|
49
|
+
const state = loadState(stateFile);
|
|
50
|
+
|
|
51
|
+
const template = {
|
|
52
|
+
id: `PLAT-${Date.now()}`,
|
|
53
|
+
name,
|
|
54
|
+
type,
|
|
55
|
+
tech_stack: options.tech_stack || [],
|
|
56
|
+
golden_path_stages: options.stages || GOLDEN_PATH_STAGES,
|
|
57
|
+
version: options.version || '1.0.0',
|
|
58
|
+
created_at: new Date().toISOString()
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
state.templates.push(template);
|
|
62
|
+
saveState(state, stateFile);
|
|
63
|
+
|
|
64
|
+
return { success: true, template };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function listTemplates(options = {}) {
|
|
68
|
+
const stateFile = options.stateFile || DEFAULT_STATE_FILE;
|
|
69
|
+
const state = loadState(stateFile);
|
|
70
|
+
|
|
71
|
+
let templates = state.templates;
|
|
72
|
+
if (options.type) templates = templates.filter(t => t.type === options.type);
|
|
73
|
+
|
|
74
|
+
return { success: true, total: templates.length, templates };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function instantiateTemplate(templateId, projectName, options = {}) {
|
|
78
|
+
if (!templateId || !projectName) return { success: false, error: 'templateId and projectName are required' };
|
|
79
|
+
|
|
80
|
+
const stateFile = options.stateFile || DEFAULT_STATE_FILE;
|
|
81
|
+
const state = loadState(stateFile);
|
|
82
|
+
|
|
83
|
+
const template = state.templates.find(t => t.id === templateId);
|
|
84
|
+
if (!template) return { success: false, error: `Template ${templateId} not found` };
|
|
85
|
+
|
|
86
|
+
const instance = {
|
|
87
|
+
id: `INST-${Date.now()}`,
|
|
88
|
+
template_id: templateId,
|
|
89
|
+
template_name: template.name,
|
|
90
|
+
project_name: projectName,
|
|
91
|
+
status: 'created',
|
|
92
|
+
created_at: new Date().toISOString()
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
state.instances.push(instance);
|
|
96
|
+
saveState(state, stateFile);
|
|
97
|
+
|
|
98
|
+
return { success: true, instance };
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function generateReport(options = {}) {
|
|
102
|
+
const stateFile = options.stateFile || DEFAULT_STATE_FILE;
|
|
103
|
+
const state = loadState(stateFile);
|
|
104
|
+
|
|
105
|
+
return {
|
|
106
|
+
success: true,
|
|
107
|
+
total_templates: state.templates.length,
|
|
108
|
+
total_instances: state.instances.length,
|
|
109
|
+
by_type: state.templates.reduce((acc, t) => { acc[t.type] = (acc[t.type] || 0) + 1; return acc; }, {}),
|
|
110
|
+
templates: state.templates,
|
|
111
|
+
instances: state.instances
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
module.exports = {
|
|
116
|
+
registerTemplate, listTemplates, instantiateTemplate, generateReport,
|
|
117
|
+
loadState, saveState, defaultState,
|
|
118
|
+
TEMPLATE_TYPES, GOLDEN_PATH_STAGES
|
|
119
|
+
};
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* playback-summaries.js — Stakeholder Playback Summaries (Item 68)
|
|
3
|
+
*
|
|
4
|
+
* Translate technical outputs into business-language summaries
|
|
5
|
+
* for each audience.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* node bin/lib/playback-summaries.js generate|list [options]
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
'use strict';
|
|
12
|
+
|
|
13
|
+
const fs = require('fs');
|
|
14
|
+
const path = require('path');
|
|
15
|
+
|
|
16
|
+
const AUDIENCES = ['executive', 'technical', 'product', 'operations', 'compliance'];
|
|
17
|
+
|
|
18
|
+
const AUDIENCE_CONFIG = {
|
|
19
|
+
executive: {
|
|
20
|
+
label: 'Executive Summary',
|
|
21
|
+
focus: ['business_value', 'timeline', 'budget', 'risks', 'decisions'],
|
|
22
|
+
tone: 'strategic',
|
|
23
|
+
max_length: 500
|
|
24
|
+
},
|
|
25
|
+
technical: {
|
|
26
|
+
label: 'Technical Summary',
|
|
27
|
+
focus: ['architecture', 'tech_stack', 'api_design', 'data_model', 'nfrs'],
|
|
28
|
+
tone: 'technical',
|
|
29
|
+
max_length: 1000
|
|
30
|
+
},
|
|
31
|
+
product: {
|
|
32
|
+
label: 'Product Summary',
|
|
33
|
+
focus: ['user_stories', 'acceptance_criteria', 'personas', 'scope', 'prioritization'],
|
|
34
|
+
tone: 'user-centric',
|
|
35
|
+
max_length: 800
|
|
36
|
+
},
|
|
37
|
+
operations: {
|
|
38
|
+
label: 'Operations Summary',
|
|
39
|
+
focus: ['deployment', 'monitoring', 'runbooks', 'sla_slo', 'incident_response'],
|
|
40
|
+
tone: 'operational',
|
|
41
|
+
max_length: 700
|
|
42
|
+
},
|
|
43
|
+
compliance: {
|
|
44
|
+
label: 'Compliance Summary',
|
|
45
|
+
focus: ['regulations', 'controls', 'evidence', 'audit_trail', 'data_classification'],
|
|
46
|
+
tone: 'regulatory',
|
|
47
|
+
max_length: 600
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Generate a playback summary for a target audience.
|
|
53
|
+
*/
|
|
54
|
+
function generateSummary(root, audience, options = {}) {
|
|
55
|
+
if (!AUDIENCES.includes(audience)) {
|
|
56
|
+
return { success: false, error: `Unknown audience: ${audience}. Valid: ${AUDIENCES.join(', ')}` };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const config = AUDIENCE_CONFIG[audience];
|
|
60
|
+
const summary = {
|
|
61
|
+
audience,
|
|
62
|
+
label: config.label,
|
|
63
|
+
tone: config.tone,
|
|
64
|
+
focus_areas: config.focus,
|
|
65
|
+
generated_at: new Date().toISOString(),
|
|
66
|
+
sections: {}
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
// Gather data from available specs
|
|
70
|
+
const specsDir = path.join(root, 'specs');
|
|
71
|
+
const availableSpecs = [];
|
|
72
|
+
if (fs.existsSync(specsDir)) {
|
|
73
|
+
for (const f of fs.readdirSync(specsDir).filter(f => f.endsWith('.md'))) {
|
|
74
|
+
availableSpecs.push(f);
|
|
75
|
+
try {
|
|
76
|
+
const content = fs.readFileSync(path.join(specsDir, f), 'utf8');
|
|
77
|
+
summary.sections[f] = {
|
|
78
|
+
available: true,
|
|
79
|
+
size: content.length,
|
|
80
|
+
has_approval: content.includes('Phase Gate Approval')
|
|
81
|
+
};
|
|
82
|
+
} catch { summary.sections[f] = { available: true, size: 0 }; }
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Phase status
|
|
87
|
+
const stateFile = path.join(root, '.jumpstart', 'state', 'state.json');
|
|
88
|
+
if (fs.existsSync(stateFile)) {
|
|
89
|
+
try {
|
|
90
|
+
const state = JSON.parse(fs.readFileSync(stateFile, 'utf8'));
|
|
91
|
+
summary.project_status = {
|
|
92
|
+
current_phase: state.current_phase || 0,
|
|
93
|
+
last_agent: state.current_agent || null
|
|
94
|
+
};
|
|
95
|
+
} catch { summary.project_status = { current_phase: 0 }; }
|
|
96
|
+
} else {
|
|
97
|
+
summary.project_status = { current_phase: 0 };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
summary.specs_available = availableSpecs;
|
|
101
|
+
summary.max_length = config.max_length;
|
|
102
|
+
|
|
103
|
+
return { success: true, summary };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* List available audience types.
|
|
108
|
+
*/
|
|
109
|
+
function listAudiences() {
|
|
110
|
+
return {
|
|
111
|
+
success: true,
|
|
112
|
+
audiences: AUDIENCES.map(a => ({
|
|
113
|
+
id: a,
|
|
114
|
+
label: AUDIENCE_CONFIG[a].label,
|
|
115
|
+
tone: AUDIENCE_CONFIG[a].tone,
|
|
116
|
+
focus: AUDIENCE_CONFIG[a].focus
|
|
117
|
+
}))
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
module.exports = {
|
|
122
|
+
generateSummary,
|
|
123
|
+
listAudiences,
|
|
124
|
+
AUDIENCES,
|
|
125
|
+
AUDIENCE_CONFIG
|
|
126
|
+
};
|