jumpstart-mode 1.1.11 → 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 +6 -7
- package/.github/agents/jumpstart-challenger.agent.md +2 -1
- package/.github/agents/jumpstart-developer.agent.md +1 -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/agents/jumpstart-uiux-designer.agent.md +66 -0
- package/.github/workflows/quality.yml +19 -2
- package/.jumpstart/agents/analyst.md +38 -0
- package/.jumpstart/agents/architect.md +39 -1
- 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 +29 -9
- package/.jumpstart/commands/commands.md +6 -5
- package/.jumpstart/config.yaml +25 -1
- package/.jumpstart/roadmap.md +1 -1
- package/.jumpstart/schemas/timeline.schema.json +1 -0
- package/.jumpstart/skills/README.md +1 -0
- package/.jumpstart/skills/quality-gates/SKILL.md +126 -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/skills/ui-ux-pro-max/SKILL.md +266 -0
- package/.jumpstart/skills/ui-ux-pro-max/data/charts.csv +26 -0
- package/.jumpstart/skills/ui-ux-pro-max/data/colors.csv +97 -0
- package/.jumpstart/skills/ui-ux-pro-max/data/icons.csv +101 -0
- package/.jumpstart/skills/ui-ux-pro-max/data/landing.csv +31 -0
- package/.jumpstart/skills/ui-ux-pro-max/data/products.csv +97 -0
- package/.jumpstart/skills/ui-ux-pro-max/data/react-performance.csv +45 -0
- package/.jumpstart/skills/ui-ux-pro-max/data/stacks/astro.csv +54 -0
- package/.jumpstart/skills/ui-ux-pro-max/data/stacks/flutter.csv +53 -0
- package/.jumpstart/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -0
- package/.jumpstart/skills/ui-ux-pro-max/data/stacks/jetpack-compose.csv +53 -0
- package/.jumpstart/skills/ui-ux-pro-max/data/stacks/nextjs.csv +53 -0
- package/.jumpstart/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -0
- package/.jumpstart/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -0
- package/.jumpstart/skills/ui-ux-pro-max/data/stacks/react-native.csv +52 -0
- package/.jumpstart/skills/ui-ux-pro-max/data/stacks/react.csv +54 -0
- package/.jumpstart/skills/ui-ux-pro-max/data/stacks/shadcn.csv +61 -0
- package/.jumpstart/skills/ui-ux-pro-max/data/stacks/svelte.csv +54 -0
- package/.jumpstart/skills/ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
- package/.jumpstart/skills/ui-ux-pro-max/data/stacks/vue.csv +50 -0
- package/.jumpstart/skills/ui-ux-pro-max/data/styles.csv +68 -0
- package/.jumpstart/skills/ui-ux-pro-max/data/typography.csv +58 -0
- package/.jumpstart/skills/ui-ux-pro-max/data/ui-reasoning.csv +101 -0
- package/.jumpstart/skills/ui-ux-pro-max/data/ux-guidelines.csv +100 -0
- package/.jumpstart/skills/ui-ux-pro-max/data/web-interface.csv +31 -0
- package/.jumpstart/skills/ui-ux-pro-max/scripts/core.py +253 -0
- package/.jumpstart/skills/ui-ux-pro-max/scripts/design_system.py +1067 -0
- package/.jumpstart/skills/ui-ux-pro-max/scripts/search.py +114 -0
- package/.jumpstart/state/timeline.json +659 -0
- package/.jumpstart/templates/model-map.md +1 -1
- package/.jumpstart/templates/ux-design.md +3 -3
- package/.jumpstart/usage-log.json +74 -3
- package/AGENTS.md +1 -1
- package/README.md +64 -3
- 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/secret-scanner.js +313 -0
- package/bin/lib/semantic-diff.js +335 -0
- package/bin/lib/sla-slo.js +210 -0
- package/bin/lib/smoke-tester.js +344 -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 +159 -0
- package/bin/lib/tool-guardrails.js +139 -0
- package/bin/lib/tool-schemas.js +281 -3
- package/bin/lib/transcript-ingestion.js +150 -0
- package/bin/lib/type-checker.js +261 -0
- package/bin/lib/uat-coverage.js +411 -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
- package/.github/agents/jumpstart-ux-designer.agent.md +0 -45
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* quality-graph.js — Code Quality Smell Graph (Item 60)
|
|
3
|
+
*
|
|
4
|
+
* Map hotspots across complexity, churn, bugs, ownership gaps,
|
|
5
|
+
* and requirement ambiguity.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* node bin/lib/quality-graph.js scan|report [options]
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
'use strict';
|
|
12
|
+
|
|
13
|
+
const fs = require('fs');
|
|
14
|
+
const path = require('path');
|
|
15
|
+
|
|
16
|
+
const QUALITY_DIMENSIONS = ['complexity', 'churn', 'test-coverage', 'ownership', 'documentation', 'dependencies'];
|
|
17
|
+
|
|
18
|
+
const COMPLEXITY_THRESHOLDS = {
|
|
19
|
+
low: { max_lines: 200, max_functions: 15 },
|
|
20
|
+
medium: { max_lines: 500, max_functions: 30 },
|
|
21
|
+
high: { max_lines: 1000, max_functions: 50 }
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Scan a project for quality hotspots.
|
|
26
|
+
*
|
|
27
|
+
* @param {string} root - Project root.
|
|
28
|
+
* @param {object} [options]
|
|
29
|
+
* @returns {object}
|
|
30
|
+
*/
|
|
31
|
+
function scanQuality(root, options = {}) {
|
|
32
|
+
const excludeDirs = options.excludeDirs || ['node_modules', '.git', 'dist', 'build', 'vendor'];
|
|
33
|
+
const extensions = options.extensions || ['.js', '.ts', '.py', '.java', '.go', '.rb'];
|
|
34
|
+
const hotspots = [];
|
|
35
|
+
|
|
36
|
+
function walk(dir) {
|
|
37
|
+
if (!fs.existsSync(dir)) return;
|
|
38
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
39
|
+
if (entry.isDirectory()) {
|
|
40
|
+
if (!excludeDirs.includes(entry.name)) walk(path.join(dir, entry.name));
|
|
41
|
+
} else if (entry.isFile()) {
|
|
42
|
+
const ext = path.extname(entry.name).toLowerCase();
|
|
43
|
+
if (extensions.includes(ext)) {
|
|
44
|
+
try {
|
|
45
|
+
const filePath = path.join(dir, entry.name);
|
|
46
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
47
|
+
const relPath = path.relative(root, filePath).replace(/\\/g, '/');
|
|
48
|
+
const metrics = analyzeFileMetrics(content, ext);
|
|
49
|
+
|
|
50
|
+
hotspots.push({
|
|
51
|
+
file: relPath,
|
|
52
|
+
...metrics,
|
|
53
|
+
overall_score: calculateOverallScore(metrics)
|
|
54
|
+
});
|
|
55
|
+
} catch { /* skip */ }
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
walk(root);
|
|
62
|
+
|
|
63
|
+
// Sort by score (lower = worse)
|
|
64
|
+
hotspots.sort((a, b) => a.overall_score - b.overall_score);
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
success: true,
|
|
68
|
+
total_files: hotspots.length,
|
|
69
|
+
hotspots: hotspots.slice(0, options.limit || 20),
|
|
70
|
+
all_files: hotspots,
|
|
71
|
+
summary: {
|
|
72
|
+
total_files: hotspots.length,
|
|
73
|
+
average_score: hotspots.length > 0 ? Math.round(hotspots.reduce((s, h) => s + h.overall_score, 0) / hotspots.length) : 0,
|
|
74
|
+
critical_hotspots: hotspots.filter(h => h.overall_score < 30).length,
|
|
75
|
+
high_risk: hotspots.filter(h => h.overall_score < 50).length
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Analyze file-level quality metrics.
|
|
82
|
+
*
|
|
83
|
+
* @param {string} content - File content.
|
|
84
|
+
* @param {string} ext - File extension.
|
|
85
|
+
* @returns {object}
|
|
86
|
+
*/
|
|
87
|
+
function analyzeFileMetrics(content, ext) {
|
|
88
|
+
const lines = content.split('\n');
|
|
89
|
+
const totalLines = lines.length;
|
|
90
|
+
const blankLines = lines.filter(l => l.trim() === '').length;
|
|
91
|
+
const commentLines = lines.filter(l => /^\s*(?:\/\/|#|\/\*|\*|""")/.test(l)).length;
|
|
92
|
+
const codeLines = totalLines - blankLines - commentLines;
|
|
93
|
+
|
|
94
|
+
// Function/method count
|
|
95
|
+
const functions = (content.match(/(?:function\s+\w+|(?:const|let|var)\s+\w+\s*=\s*(?:async\s+)?(?:\([^)]*\)|[^=])\s*=>|\bdef\s+\w+|\bfunc\s+\w+)/g) || []).length;
|
|
96
|
+
|
|
97
|
+
// Nested depth (max bracket nesting)
|
|
98
|
+
let maxDepth = 0;
|
|
99
|
+
let currentDepth = 0;
|
|
100
|
+
for (const char of content) {
|
|
101
|
+
if (char === '{' || char === '(') { currentDepth++; maxDepth = Math.max(maxDepth, currentDepth); }
|
|
102
|
+
if (char === '}' || char === ')') currentDepth--;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// TODO/FIXME count
|
|
106
|
+
const todos = (content.match(/\b(?:TODO|FIXME|HACK|XXX)\b/gi) || []).length;
|
|
107
|
+
|
|
108
|
+
// Long lines
|
|
109
|
+
const longLines = lines.filter(l => l.length > 120).length;
|
|
110
|
+
|
|
111
|
+
// Import count
|
|
112
|
+
const imports = (content.match(/(?:^import\s|^const\s.*=\s*require|^from\s)/gm) || []).length;
|
|
113
|
+
|
|
114
|
+
return {
|
|
115
|
+
total_lines: totalLines,
|
|
116
|
+
code_lines: codeLines,
|
|
117
|
+
comment_ratio: totalLines > 0 ? Math.round((commentLines / totalLines) * 100) : 0,
|
|
118
|
+
functions,
|
|
119
|
+
max_nesting_depth: maxDepth,
|
|
120
|
+
todos,
|
|
121
|
+
long_lines: longLines,
|
|
122
|
+
imports,
|
|
123
|
+
complexity_level: totalLines > COMPLEXITY_THRESHOLDS.high.max_lines ? 'critical'
|
|
124
|
+
: totalLines > COMPLEXITY_THRESHOLDS.medium.max_lines ? 'high'
|
|
125
|
+
: totalLines > COMPLEXITY_THRESHOLDS.low.max_lines ? 'medium' : 'low'
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Calculate overall quality score (0-100).
|
|
131
|
+
*
|
|
132
|
+
* @param {object} metrics
|
|
133
|
+
* @returns {number}
|
|
134
|
+
*/
|
|
135
|
+
function calculateOverallScore(metrics) {
|
|
136
|
+
let score = 100;
|
|
137
|
+
|
|
138
|
+
// Penalize large files
|
|
139
|
+
if (metrics.total_lines > 500) score -= 15;
|
|
140
|
+
if (metrics.total_lines > 1000) score -= 15;
|
|
141
|
+
|
|
142
|
+
// Penalize deep nesting
|
|
143
|
+
if (metrics.max_nesting_depth > 5) score -= 10;
|
|
144
|
+
if (metrics.max_nesting_depth > 8) score -= 10;
|
|
145
|
+
|
|
146
|
+
// Penalize TODOs
|
|
147
|
+
score -= metrics.todos * 3;
|
|
148
|
+
|
|
149
|
+
// Penalize low comment ratio
|
|
150
|
+
if (metrics.comment_ratio < 5) score -= 10;
|
|
151
|
+
|
|
152
|
+
// Penalize many long lines
|
|
153
|
+
if (metrics.long_lines > 10) score -= 10;
|
|
154
|
+
|
|
155
|
+
// Penalize too many functions
|
|
156
|
+
if (metrics.functions > 30) score -= 10;
|
|
157
|
+
|
|
158
|
+
return Math.max(0, Math.min(100, score));
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Generate quality report.
|
|
163
|
+
*
|
|
164
|
+
* @param {object} scanResult
|
|
165
|
+
* @returns {object}
|
|
166
|
+
*/
|
|
167
|
+
function generateReport(scanResult) {
|
|
168
|
+
const byComplexity = { low: 0, medium: 0, high: 0, critical: 0 };
|
|
169
|
+
for (const h of scanResult.all_files || []) {
|
|
170
|
+
byComplexity[h.complexity_level] = (byComplexity[h.complexity_level] || 0) + 1;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return {
|
|
174
|
+
success: true,
|
|
175
|
+
summary: scanResult.summary,
|
|
176
|
+
by_complexity: byComplexity,
|
|
177
|
+
top_hotspots: (scanResult.hotspots || []).slice(0, 10),
|
|
178
|
+
recommendations: [
|
|
179
|
+
...(scanResult.summary.critical_hotspots > 0 ? ['Refactor critical hotspots with high complexity'] : []),
|
|
180
|
+
...(scanResult.summary.average_score < 60 ? ['Consider code review standards and complexity limits'] : []),
|
|
181
|
+
'Add documentation to files with low comment ratios'
|
|
182
|
+
]
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
module.exports = {
|
|
187
|
+
scanQuality,
|
|
188
|
+
analyzeFileMetrics,
|
|
189
|
+
calculateOverallScore,
|
|
190
|
+
generateReport,
|
|
191
|
+
QUALITY_DIMENSIONS,
|
|
192
|
+
COMPLEXITY_THRESHOLDS
|
|
193
|
+
};
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* raci-matrix.js — RACI-Aware Approvals (Item 23)
|
|
3
|
+
*
|
|
4
|
+
* Define who is Responsible, Accountable, Consulted, Informed
|
|
5
|
+
* for each phase and artifact.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* node bin/lib/raci-matrix.js define|check|report [options]
|
|
9
|
+
*
|
|
10
|
+
* State file: .jumpstart/state/raci-matrix.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', 'raci-matrix.json');
|
|
19
|
+
|
|
20
|
+
const RACI_ROLES = ['responsible', 'accountable', 'consulted', 'informed'];
|
|
21
|
+
|
|
22
|
+
const DEFAULT_PHASES = ['scout', 'challenger', 'analyst', 'pm', 'architect', 'developer'];
|
|
23
|
+
|
|
24
|
+
const DEFAULT_ARTIFACTS = [
|
|
25
|
+
'specs/codebase-context.md',
|
|
26
|
+
'specs/challenger-brief.md',
|
|
27
|
+
'specs/product-brief.md',
|
|
28
|
+
'specs/prd.md',
|
|
29
|
+
'specs/architecture.md',
|
|
30
|
+
'specs/implementation-plan.md'
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Default RACI state.
|
|
35
|
+
* @returns {object}
|
|
36
|
+
*/
|
|
37
|
+
function defaultState() {
|
|
38
|
+
return {
|
|
39
|
+
version: '1.0.0',
|
|
40
|
+
created_at: new Date().toISOString(),
|
|
41
|
+
last_updated: null,
|
|
42
|
+
assignments: {},
|
|
43
|
+
stakeholders: []
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function loadState(stateFile) {
|
|
48
|
+
const filePath = stateFile || DEFAULT_STATE_FILE;
|
|
49
|
+
if (!fs.existsSync(filePath)) return defaultState();
|
|
50
|
+
try { return JSON.parse(fs.readFileSync(filePath, 'utf8')); }
|
|
51
|
+
catch { return defaultState(); }
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function saveState(state, stateFile) {
|
|
55
|
+
const filePath = stateFile || DEFAULT_STATE_FILE;
|
|
56
|
+
const dir = path.dirname(filePath);
|
|
57
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
58
|
+
state.last_updated = new Date().toISOString();
|
|
59
|
+
fs.writeFileSync(filePath, JSON.stringify(state, null, 2) + '\n', 'utf8');
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Define a RACI assignment for an artifact/phase.
|
|
64
|
+
*
|
|
65
|
+
* @param {string} artifact - Artifact or phase identifier.
|
|
66
|
+
* @param {object} assignment - { responsible, accountable, consulted[], informed[] }
|
|
67
|
+
* @param {object} [options]
|
|
68
|
+
* @returns {object}
|
|
69
|
+
*/
|
|
70
|
+
function defineAssignment(artifact, assignment, options = {}) {
|
|
71
|
+
if (!artifact) return { success: false, error: 'artifact is required' };
|
|
72
|
+
if (!assignment || !assignment.accountable) {
|
|
73
|
+
return { success: false, error: 'assignment.accountable is required' };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const stateFile = options.stateFile || DEFAULT_STATE_FILE;
|
|
77
|
+
const state = loadState(stateFile);
|
|
78
|
+
|
|
79
|
+
state.assignments[artifact] = {
|
|
80
|
+
artifact,
|
|
81
|
+
responsible: assignment.responsible || assignment.accountable,
|
|
82
|
+
accountable: assignment.accountable,
|
|
83
|
+
consulted: Array.isArray(assignment.consulted) ? assignment.consulted : [],
|
|
84
|
+
informed: Array.isArray(assignment.informed) ? assignment.informed : [],
|
|
85
|
+
defined_at: new Date().toISOString()
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
// Track stakeholders
|
|
89
|
+
const allPeople = [
|
|
90
|
+
assignment.responsible,
|
|
91
|
+
assignment.accountable,
|
|
92
|
+
...(assignment.consulted || []),
|
|
93
|
+
...(assignment.informed || [])
|
|
94
|
+
].filter(Boolean);
|
|
95
|
+
|
|
96
|
+
for (const person of allPeople) {
|
|
97
|
+
if (!state.stakeholders.includes(person)) {
|
|
98
|
+
state.stakeholders.push(person);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
saveState(state, stateFile);
|
|
103
|
+
|
|
104
|
+
return { success: true, artifact, assignment: state.assignments[artifact] };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Check RACI compliance for approval actions.
|
|
109
|
+
*
|
|
110
|
+
* @param {string} artifact
|
|
111
|
+
* @param {string} actor - Who is performing the action.
|
|
112
|
+
* @param {string} action - 'approve' | 'review' | 'inform'
|
|
113
|
+
* @param {object} [options]
|
|
114
|
+
* @returns {object}
|
|
115
|
+
*/
|
|
116
|
+
function checkPermission(artifact, actor, action, options = {}) {
|
|
117
|
+
const stateFile = options.stateFile || DEFAULT_STATE_FILE;
|
|
118
|
+
const state = loadState(stateFile);
|
|
119
|
+
|
|
120
|
+
const assignment = state.assignments[artifact];
|
|
121
|
+
if (!assignment) {
|
|
122
|
+
return { success: true, allowed: true, reason: 'No RACI assignment defined — unrestricted' };
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (action === 'approve') {
|
|
126
|
+
const allowed = assignment.accountable === actor || assignment.responsible === actor;
|
|
127
|
+
return {
|
|
128
|
+
success: true,
|
|
129
|
+
allowed,
|
|
130
|
+
reason: allowed
|
|
131
|
+
? `${actor} is ${assignment.accountable === actor ? 'Accountable' : 'Responsible'}`
|
|
132
|
+
: `${actor} is not Responsible or Accountable. Need: ${assignment.accountable}`
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (action === 'review') {
|
|
137
|
+
const allowed = assignment.consulted.includes(actor) ||
|
|
138
|
+
assignment.responsible === actor ||
|
|
139
|
+
assignment.accountable === actor;
|
|
140
|
+
return { success: true, allowed, reason: allowed ? 'Actor has review rights' : 'Actor is not in C/R/A' };
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return { success: true, allowed: true, reason: 'Action permitted' };
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Generate a RACI matrix report.
|
|
148
|
+
*
|
|
149
|
+
* @param {object} [options]
|
|
150
|
+
* @returns {object}
|
|
151
|
+
*/
|
|
152
|
+
function generateReport(options = {}) {
|
|
153
|
+
const stateFile = options.stateFile || DEFAULT_STATE_FILE;
|
|
154
|
+
const state = loadState(stateFile);
|
|
155
|
+
|
|
156
|
+
const matrix = Object.entries(state.assignments).map(([artifact, a]) => ({
|
|
157
|
+
artifact,
|
|
158
|
+
R: a.responsible,
|
|
159
|
+
A: a.accountable,
|
|
160
|
+
C: a.consulted.join(', '),
|
|
161
|
+
I: a.informed.join(', ')
|
|
162
|
+
}));
|
|
163
|
+
|
|
164
|
+
const gaps = DEFAULT_ARTIFACTS.filter(a => !state.assignments[a]);
|
|
165
|
+
|
|
166
|
+
return {
|
|
167
|
+
success: true,
|
|
168
|
+
matrix,
|
|
169
|
+
stakeholders: state.stakeholders,
|
|
170
|
+
total_assignments: matrix.length,
|
|
171
|
+
gaps,
|
|
172
|
+
coverage: DEFAULT_ARTIFACTS.length > 0
|
|
173
|
+
? Math.round(((DEFAULT_ARTIFACTS.length - gaps.length) / DEFAULT_ARTIFACTS.length) * 100)
|
|
174
|
+
: 100
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
module.exports = {
|
|
179
|
+
defaultState,
|
|
180
|
+
loadState,
|
|
181
|
+
saveState,
|
|
182
|
+
defineAssignment,
|
|
183
|
+
checkPermission,
|
|
184
|
+
generateReport,
|
|
185
|
+
RACI_ROLES,
|
|
186
|
+
DEFAULT_PHASES,
|
|
187
|
+
DEFAULT_ARTIFACTS
|
|
188
|
+
};
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* refactor-planner.js — Refactor Planner with Dependency Safety (Item 43)
|
|
3
|
+
*
|
|
4
|
+
* Model safe sequencing for large refactors and migrations.
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* node bin/lib/refactor-planner.js plan|validate|report [options]
|
|
8
|
+
*
|
|
9
|
+
* State file: .jumpstart/state/refactor-plan.json
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
'use strict';
|
|
13
|
+
|
|
14
|
+
const fs = require('fs');
|
|
15
|
+
const path = require('path');
|
|
16
|
+
|
|
17
|
+
const DEFAULT_STATE_FILE = path.join('.jumpstart', 'state', 'refactor-plan.json');
|
|
18
|
+
|
|
19
|
+
const REFACTOR_TYPES = ['rename', 'move', 'extract', 'inline', 'restructure', 'migrate', 'upgrade'];
|
|
20
|
+
const RISK_LEVELS = ['low', 'medium', 'high', 'critical'];
|
|
21
|
+
|
|
22
|
+
function defaultState() {
|
|
23
|
+
return {
|
|
24
|
+
version: '1.0.0',
|
|
25
|
+
created_at: new Date().toISOString(),
|
|
26
|
+
last_updated: null,
|
|
27
|
+
plans: [],
|
|
28
|
+
completed: []
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function loadState(stateFile) {
|
|
33
|
+
const filePath = stateFile || DEFAULT_STATE_FILE;
|
|
34
|
+
if (!fs.existsSync(filePath)) return defaultState();
|
|
35
|
+
try { return JSON.parse(fs.readFileSync(filePath, 'utf8')); }
|
|
36
|
+
catch { return defaultState(); }
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function saveState(state, stateFile) {
|
|
40
|
+
const filePath = stateFile || DEFAULT_STATE_FILE;
|
|
41
|
+
const dir = path.dirname(filePath);
|
|
42
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
43
|
+
state.last_updated = new Date().toISOString();
|
|
44
|
+
fs.writeFileSync(filePath, JSON.stringify(state, null, 2) + '\n', 'utf8');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Create a refactor plan.
|
|
49
|
+
*
|
|
50
|
+
* @param {object} plan - { name, type, description, steps[], affected_files[] }
|
|
51
|
+
* @param {object} [options]
|
|
52
|
+
* @returns {object}
|
|
53
|
+
*/
|
|
54
|
+
function createPlan(plan, options = {}) {
|
|
55
|
+
if (!plan || !plan.name || !plan.type) {
|
|
56
|
+
return { success: false, error: 'name and type are required' };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (!REFACTOR_TYPES.includes(plan.type)) {
|
|
60
|
+
return { success: false, error: `Invalid type. Must be one of: ${REFACTOR_TYPES.join(', ')}` };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const stateFile = options.stateFile || DEFAULT_STATE_FILE;
|
|
64
|
+
const state = loadState(stateFile);
|
|
65
|
+
|
|
66
|
+
const steps = (plan.steps || []).map((step, i) => ({
|
|
67
|
+
order: i + 1,
|
|
68
|
+
description: step.description || step,
|
|
69
|
+
status: 'pending',
|
|
70
|
+
dependencies: step.dependencies || [],
|
|
71
|
+
risk: step.risk || 'low',
|
|
72
|
+
rollback: step.rollback || null
|
|
73
|
+
}));
|
|
74
|
+
|
|
75
|
+
const newPlan = {
|
|
76
|
+
id: `REF-${(state.plans.length + 1).toString().padStart(3, '0')}`,
|
|
77
|
+
name: plan.name,
|
|
78
|
+
type: plan.type,
|
|
79
|
+
description: plan.description || '',
|
|
80
|
+
steps,
|
|
81
|
+
affected_files: plan.affected_files || [],
|
|
82
|
+
status: 'draft',
|
|
83
|
+
risk_level: steps.some(s => s.risk === 'critical') ? 'critical' : steps.some(s => s.risk === 'high') ? 'high' : 'medium',
|
|
84
|
+
created_at: new Date().toISOString()
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
state.plans.push(newPlan);
|
|
88
|
+
saveState(state, stateFile);
|
|
89
|
+
|
|
90
|
+
return { success: true, plan: newPlan };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Validate refactor plan dependencies.
|
|
95
|
+
*
|
|
96
|
+
* @param {string} planId
|
|
97
|
+
* @param {object} [options]
|
|
98
|
+
* @returns {object}
|
|
99
|
+
*/
|
|
100
|
+
function validatePlan(planId, options = {}) {
|
|
101
|
+
const stateFile = options.stateFile || DEFAULT_STATE_FILE;
|
|
102
|
+
const state = loadState(stateFile);
|
|
103
|
+
|
|
104
|
+
const plan = state.plans.find(p => p.id === planId);
|
|
105
|
+
if (!plan) return { success: false, error: `Plan not found: ${planId}` };
|
|
106
|
+
|
|
107
|
+
const issues = [];
|
|
108
|
+
|
|
109
|
+
// Check circular dependencies
|
|
110
|
+
for (const step of plan.steps) {
|
|
111
|
+
for (const dep of step.dependencies) {
|
|
112
|
+
const depStep = plan.steps.find(s => s.order === dep);
|
|
113
|
+
if (depStep && depStep.dependencies.includes(step.order)) {
|
|
114
|
+
issues.push({ type: 'circular-dependency', steps: [step.order, dep] });
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Check dependency ordering
|
|
120
|
+
for (const step of plan.steps) {
|
|
121
|
+
for (const dep of step.dependencies) {
|
|
122
|
+
if (dep >= step.order) {
|
|
123
|
+
issues.push({ type: 'invalid-order', step: step.order, depends_on: dep });
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return {
|
|
129
|
+
success: true,
|
|
130
|
+
plan_id: planId,
|
|
131
|
+
valid: issues.length === 0,
|
|
132
|
+
issues,
|
|
133
|
+
total_steps: plan.steps.length,
|
|
134
|
+
risk_level: plan.risk_level
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Generate refactor report.
|
|
140
|
+
*
|
|
141
|
+
* @param {object} [options]
|
|
142
|
+
* @returns {object}
|
|
143
|
+
*/
|
|
144
|
+
function generateReport(options = {}) {
|
|
145
|
+
const stateFile = options.stateFile || DEFAULT_STATE_FILE;
|
|
146
|
+
const state = loadState(stateFile);
|
|
147
|
+
|
|
148
|
+
return {
|
|
149
|
+
success: true,
|
|
150
|
+
total_plans: state.plans.length,
|
|
151
|
+
completed: state.completed.length,
|
|
152
|
+
active: state.plans.filter(p => p.status !== 'completed').length,
|
|
153
|
+
by_type: state.plans.reduce((acc, p) => { acc[p.type] = (acc[p.type] || 0) + 1; return acc; }, {}),
|
|
154
|
+
plans: state.plans
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
module.exports = {
|
|
159
|
+
defaultState,
|
|
160
|
+
loadState,
|
|
161
|
+
saveState,
|
|
162
|
+
createPlan,
|
|
163
|
+
validatePlan,
|
|
164
|
+
generateReport,
|
|
165
|
+
REFACTOR_TYPES,
|
|
166
|
+
RISK_LEVELS
|
|
167
|
+
};
|