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,173 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vendor-risk.js — Vendor & Dependency Risk Scoring (Item 36)
|
|
3
|
+
*
|
|
4
|
+
* Evaluate OSS and SaaS dependencies for maintenance health,
|
|
5
|
+
* license risk, and supply chain concerns.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* node bin/lib/vendor-risk.js scan|assess|report [options]
|
|
9
|
+
*
|
|
10
|
+
* State file: .jumpstart/state/vendor-risk.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', 'vendor-risk.json');
|
|
19
|
+
|
|
20
|
+
const RISK_FACTORS = ['maintenance', 'license', 'security', 'popularity', 'supply-chain'];
|
|
21
|
+
|
|
22
|
+
const LICENSE_RISK = {
|
|
23
|
+
'MIT': 'low', 'ISC': 'low', 'BSD-2-Clause': 'low', 'BSD-3-Clause': 'low', 'Apache-2.0': 'low',
|
|
24
|
+
'LGPL-2.1': 'medium', 'LGPL-3.0': 'medium', 'MPL-2.0': 'medium',
|
|
25
|
+
'GPL-2.0': 'high', 'GPL-3.0': 'high', 'AGPL-3.0': 'high',
|
|
26
|
+
'SSPL-1.0': 'critical', 'BSL-1.1': 'high', 'UNLICENSED': 'critical', 'unknown': 'high'
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
function defaultState() {
|
|
30
|
+
return {
|
|
31
|
+
version: '1.0.0',
|
|
32
|
+
created_at: new Date().toISOString(),
|
|
33
|
+
last_updated: null,
|
|
34
|
+
assessments: [],
|
|
35
|
+
vendor_catalog: []
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function loadState(stateFile) {
|
|
40
|
+
const filePath = stateFile || DEFAULT_STATE_FILE;
|
|
41
|
+
if (!fs.existsSync(filePath)) return defaultState();
|
|
42
|
+
try { return JSON.parse(fs.readFileSync(filePath, 'utf8')); }
|
|
43
|
+
catch { return defaultState(); }
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function saveState(state, stateFile) {
|
|
47
|
+
const filePath = stateFile || DEFAULT_STATE_FILE;
|
|
48
|
+
const dir = path.dirname(filePath);
|
|
49
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
50
|
+
state.last_updated = new Date().toISOString();
|
|
51
|
+
fs.writeFileSync(filePath, JSON.stringify(state, null, 2) + '\n', 'utf8');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Scan project dependencies.
|
|
56
|
+
*
|
|
57
|
+
* @param {string} root - Project root.
|
|
58
|
+
* @param {object} [options]
|
|
59
|
+
* @returns {object}
|
|
60
|
+
*/
|
|
61
|
+
function scanDependencies(root, options = {}) {
|
|
62
|
+
const packageFile = path.join(root, 'package.json');
|
|
63
|
+
const dependencies = [];
|
|
64
|
+
|
|
65
|
+
if (fs.existsSync(packageFile)) {
|
|
66
|
+
try {
|
|
67
|
+
const pkg = JSON.parse(fs.readFileSync(packageFile, 'utf8'));
|
|
68
|
+
const deps = { ...pkg.dependencies };
|
|
69
|
+
const devDeps = options.includeDevDeps ? pkg.devDependencies || {} : {};
|
|
70
|
+
|
|
71
|
+
for (const [name, version] of Object.entries(deps)) {
|
|
72
|
+
dependencies.push({ name, version, type: 'production', ecosystem: 'npm' });
|
|
73
|
+
}
|
|
74
|
+
for (const [name, version] of Object.entries(devDeps)) {
|
|
75
|
+
dependencies.push({ name, version, type: 'development', ecosystem: 'npm' });
|
|
76
|
+
}
|
|
77
|
+
} catch { /* ignore */ }
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return { success: true, dependencies, total: dependencies.length };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Assess risk for a dependency.
|
|
85
|
+
*
|
|
86
|
+
* @param {object} dep - { name, version, license?, last_publish?, weekly_downloads? }
|
|
87
|
+
* @param {object} [options]
|
|
88
|
+
* @returns {object}
|
|
89
|
+
*/
|
|
90
|
+
function assessDependency(dep, options = {}) {
|
|
91
|
+
if (!dep || !dep.name) return { success: false, error: 'dep.name is required' };
|
|
92
|
+
|
|
93
|
+
const scores = {};
|
|
94
|
+
|
|
95
|
+
// License risk
|
|
96
|
+
const license = dep.license || 'unknown';
|
|
97
|
+
const licenseRisk = LICENSE_RISK[license] || 'high';
|
|
98
|
+
scores.license = licenseRisk === 'low' ? 90 : licenseRisk === 'medium' ? 60 : licenseRisk === 'high' ? 30 : 10;
|
|
99
|
+
|
|
100
|
+
// Maintenance (based on last publish date if available)
|
|
101
|
+
if (dep.last_publish) {
|
|
102
|
+
const daysSince = Math.floor((Date.now() - new Date(dep.last_publish).getTime()) / (1000 * 60 * 60 * 24));
|
|
103
|
+
scores.maintenance = daysSince < 90 ? 90 : daysSince < 365 ? 60 : daysSince < 730 ? 30 : 10;
|
|
104
|
+
} else {
|
|
105
|
+
scores.maintenance = 50; // unknown
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Popularity
|
|
109
|
+
const downloads = dep.weekly_downloads || 0;
|
|
110
|
+
scores.popularity = downloads > 1000000 ? 90 : downloads > 100000 ? 70 : downloads > 10000 ? 50 : 30;
|
|
111
|
+
|
|
112
|
+
// Security (default: no known issues)
|
|
113
|
+
scores.security = dep.known_vulnerabilities ? 20 : 80;
|
|
114
|
+
|
|
115
|
+
// Supply chain
|
|
116
|
+
scores['supply-chain'] = dep.has_lockfile ? 80 : 50;
|
|
117
|
+
|
|
118
|
+
const overall = Math.round(Object.values(scores).reduce((a, b) => a + b, 0) / Object.keys(scores).length);
|
|
119
|
+
|
|
120
|
+
const stateFile = options.stateFile || DEFAULT_STATE_FILE;
|
|
121
|
+
const state = loadState(stateFile);
|
|
122
|
+
|
|
123
|
+
const assessment = {
|
|
124
|
+
name: dep.name,
|
|
125
|
+
version: dep.version,
|
|
126
|
+
license,
|
|
127
|
+
scores,
|
|
128
|
+
overall,
|
|
129
|
+
risk_level: overall >= 70 ? 'low' : overall >= 50 ? 'medium' : overall >= 30 ? 'high' : 'critical',
|
|
130
|
+
assessed_at: new Date().toISOString()
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
state.assessments.push(assessment);
|
|
134
|
+
saveState(state, stateFile);
|
|
135
|
+
|
|
136
|
+
return { success: true, assessment };
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Generate vendor risk report.
|
|
141
|
+
*
|
|
142
|
+
* @param {object} [options]
|
|
143
|
+
* @returns {object}
|
|
144
|
+
*/
|
|
145
|
+
function generateReport(options = {}) {
|
|
146
|
+
const stateFile = options.stateFile || DEFAULT_STATE_FILE;
|
|
147
|
+
const state = loadState(stateFile);
|
|
148
|
+
|
|
149
|
+
const byRisk = { low: 0, medium: 0, high: 0, critical: 0 };
|
|
150
|
+
for (const a of state.assessments) byRisk[a.risk_level]++;
|
|
151
|
+
|
|
152
|
+
return {
|
|
153
|
+
success: true,
|
|
154
|
+
total_assessed: state.assessments.length,
|
|
155
|
+
by_risk: byRisk,
|
|
156
|
+
high_risk: state.assessments.filter(a => a.risk_level === 'high' || a.risk_level === 'critical'),
|
|
157
|
+
average_score: state.assessments.length > 0
|
|
158
|
+
? Math.round(state.assessments.reduce((s, a) => s + a.overall, 0) / state.assessments.length)
|
|
159
|
+
: 0,
|
|
160
|
+
assessments: state.assessments
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
module.exports = {
|
|
165
|
+
defaultState,
|
|
166
|
+
loadState,
|
|
167
|
+
saveState,
|
|
168
|
+
scanDependencies,
|
|
169
|
+
assessDependency,
|
|
170
|
+
generateReport,
|
|
171
|
+
RISK_FACTORS,
|
|
172
|
+
LICENSE_RISK
|
|
173
|
+
};
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* waiver-workflow.js — Exception & Waiver Workflow (Item 27)
|
|
3
|
+
*
|
|
4
|
+
* Allow formal approval of justified deviations from standards
|
|
5
|
+
* with expiration and owner.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* node bin/lib/waiver-workflow.js request|approve|list|expire [options]
|
|
9
|
+
*
|
|
10
|
+
* State file: .jumpstart/state/waivers.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', 'waivers.json');
|
|
19
|
+
|
|
20
|
+
const WAIVER_STATUSES = ['pending', 'approved', 'rejected', 'expired', 'revoked'];
|
|
21
|
+
const WAIVER_CATEGORIES = ['security', 'architecture', 'compliance', 'performance', 'testing', 'documentation', 'other'];
|
|
22
|
+
|
|
23
|
+
function defaultState() {
|
|
24
|
+
return {
|
|
25
|
+
version: '1.0.0',
|
|
26
|
+
created_at: new Date().toISOString(),
|
|
27
|
+
last_updated: null,
|
|
28
|
+
waivers: []
|
|
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
|
+
* Request a new waiver.
|
|
49
|
+
*
|
|
50
|
+
* @param {object} request - { title, category, justification, owner, expires_in_days? }
|
|
51
|
+
* @param {object} [options]
|
|
52
|
+
* @returns {object}
|
|
53
|
+
*/
|
|
54
|
+
function requestWaiver(request, options = {}) {
|
|
55
|
+
if (!request || !request.title || !request.justification || !request.owner) {
|
|
56
|
+
return { success: false, error: 'title, justification, and owner are required' };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const category = (request.category || 'other').toLowerCase();
|
|
60
|
+
if (!WAIVER_CATEGORIES.includes(category)) {
|
|
61
|
+
return { success: false, error: `Invalid category. Must be one of: ${WAIVER_CATEGORIES.join(', ')}` };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const stateFile = options.stateFile || DEFAULT_STATE_FILE;
|
|
65
|
+
const state = loadState(stateFile);
|
|
66
|
+
|
|
67
|
+
const expiresInDays = request.expires_in_days || 90;
|
|
68
|
+
const expiresAt = new Date();
|
|
69
|
+
expiresAt.setDate(expiresAt.getDate() + expiresInDays);
|
|
70
|
+
|
|
71
|
+
const waiver = {
|
|
72
|
+
id: `WVR-${Date.now().toString(36).toUpperCase()}`,
|
|
73
|
+
title: request.title,
|
|
74
|
+
category,
|
|
75
|
+
justification: request.justification,
|
|
76
|
+
owner: request.owner,
|
|
77
|
+
status: 'pending',
|
|
78
|
+
requested_at: new Date().toISOString(),
|
|
79
|
+
expires_at: expiresAt.toISOString(),
|
|
80
|
+
approved_by: null,
|
|
81
|
+
approved_at: null,
|
|
82
|
+
conditions: request.conditions || [],
|
|
83
|
+
affected_artifacts: request.affected_artifacts || []
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
state.waivers.push(waiver);
|
|
87
|
+
saveState(state, stateFile);
|
|
88
|
+
|
|
89
|
+
return { success: true, waiver };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Approve or reject a waiver.
|
|
94
|
+
*
|
|
95
|
+
* @param {string} waiverId
|
|
96
|
+
* @param {string} action - 'approve' or 'reject'
|
|
97
|
+
* @param {object} [options]
|
|
98
|
+
* @returns {object}
|
|
99
|
+
*/
|
|
100
|
+
function resolveWaiver(waiverId, action, options = {}) {
|
|
101
|
+
if (!['approve', 'reject'].includes(action)) {
|
|
102
|
+
return { success: false, error: 'action must be "approve" or "reject"' };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const stateFile = options.stateFile || DEFAULT_STATE_FILE;
|
|
106
|
+
const state = loadState(stateFile);
|
|
107
|
+
|
|
108
|
+
const waiver = state.waivers.find(w => w.id === waiverId);
|
|
109
|
+
if (!waiver) return { success: false, error: `Waiver not found: ${waiverId}` };
|
|
110
|
+
if (waiver.status !== 'pending') {
|
|
111
|
+
return { success: false, error: `Waiver is already ${waiver.status}` };
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
waiver.status = action === 'approve' ? 'approved' : 'rejected';
|
|
115
|
+
waiver.approved_by = options.approver || null;
|
|
116
|
+
waiver.approved_at = new Date().toISOString();
|
|
117
|
+
|
|
118
|
+
saveState(state, stateFile);
|
|
119
|
+
return { success: true, waiver };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Expire outdated waivers.
|
|
124
|
+
*
|
|
125
|
+
* @param {object} [options]
|
|
126
|
+
* @returns {object}
|
|
127
|
+
*/
|
|
128
|
+
function expireWaivers(options = {}) {
|
|
129
|
+
const stateFile = options.stateFile || DEFAULT_STATE_FILE;
|
|
130
|
+
const state = loadState(stateFile);
|
|
131
|
+
const now = new Date();
|
|
132
|
+
let expired = 0;
|
|
133
|
+
|
|
134
|
+
for (const waiver of state.waivers) {
|
|
135
|
+
if (waiver.status === 'approved' && new Date(waiver.expires_at) < now) {
|
|
136
|
+
waiver.status = 'expired';
|
|
137
|
+
expired++;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
saveState(state, stateFile);
|
|
142
|
+
return { success: true, expired, total_waivers: state.waivers.length };
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* List waivers with optional filter.
|
|
147
|
+
*
|
|
148
|
+
* @param {object} [filter] - { status?, category?, owner? }
|
|
149
|
+
* @param {object} [options]
|
|
150
|
+
* @returns {object}
|
|
151
|
+
*/
|
|
152
|
+
function listWaivers(filter = {}, options = {}) {
|
|
153
|
+
const stateFile = options.stateFile || DEFAULT_STATE_FILE;
|
|
154
|
+
const state = loadState(stateFile);
|
|
155
|
+
let waivers = state.waivers;
|
|
156
|
+
|
|
157
|
+
if (filter.status) waivers = waivers.filter(w => w.status === filter.status);
|
|
158
|
+
if (filter.category) waivers = waivers.filter(w => w.category === filter.category);
|
|
159
|
+
if (filter.owner) waivers = waivers.filter(w => w.owner === filter.owner);
|
|
160
|
+
|
|
161
|
+
return { success: true, waivers, total: waivers.length };
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
module.exports = {
|
|
165
|
+
defaultState,
|
|
166
|
+
loadState,
|
|
167
|
+
saveState,
|
|
168
|
+
requestWaiver,
|
|
169
|
+
resolveWaiver,
|
|
170
|
+
expireWaivers,
|
|
171
|
+
listWaivers,
|
|
172
|
+
WAIVER_STATUSES,
|
|
173
|
+
WAIVER_CATEGORIES
|
|
174
|
+
};
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* web-dashboard.js — Rich Web UI / Local Dashboard (Item 61)
|
|
3
|
+
*
|
|
4
|
+
* Serve a local web control center for project management,
|
|
5
|
+
* phase tracking, artifact browsing, and governance overview.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* node bin/lib/web-dashboard.js start|status|config [options]
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
'use strict';
|
|
12
|
+
|
|
13
|
+
const fs = require('fs');
|
|
14
|
+
const path = require('path');
|
|
15
|
+
|
|
16
|
+
const DEFAULT_PORT = 3000;
|
|
17
|
+
const DEFAULT_HOST = 'localhost';
|
|
18
|
+
|
|
19
|
+
const DASHBOARD_SECTIONS = ['phases', 'artifacts', 'governance', 'timeline', 'risks', 'metrics'];
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Generate dashboard configuration.
|
|
23
|
+
*/
|
|
24
|
+
function generateConfig(root, options = {}) {
|
|
25
|
+
const port = options.port || DEFAULT_PORT;
|
|
26
|
+
const host = options.host || DEFAULT_HOST;
|
|
27
|
+
|
|
28
|
+
const config = {
|
|
29
|
+
port,
|
|
30
|
+
host,
|
|
31
|
+
root: path.resolve(root),
|
|
32
|
+
sections: options.sections || DASHBOARD_SECTIONS,
|
|
33
|
+
theme: options.theme || 'default',
|
|
34
|
+
auth: options.auth || { enabled: false },
|
|
35
|
+
refresh_interval: options.refresh_interval || 30,
|
|
36
|
+
generated_at: new Date().toISOString()
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
return { success: true, config };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Gather dashboard data from project state.
|
|
44
|
+
*/
|
|
45
|
+
function gatherDashboardData(root, options = {}) {
|
|
46
|
+
const data = {
|
|
47
|
+
project_root: root,
|
|
48
|
+
generated_at: new Date().toISOString(),
|
|
49
|
+
sections: {}
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
// Phase status
|
|
53
|
+
const stateFile = path.join(root, '.jumpstart', 'state', 'state.json');
|
|
54
|
+
if (fs.existsSync(stateFile)) {
|
|
55
|
+
try {
|
|
56
|
+
const state = JSON.parse(fs.readFileSync(stateFile, 'utf8'));
|
|
57
|
+
data.sections.phases = {
|
|
58
|
+
current_phase: state.current_phase || 0,
|
|
59
|
+
current_agent: state.current_agent || null,
|
|
60
|
+
last_completed_step: state.last_completed_step || null
|
|
61
|
+
};
|
|
62
|
+
} catch { data.sections.phases = { current_phase: 0 }; }
|
|
63
|
+
} else {
|
|
64
|
+
data.sections.phases = { current_phase: 0 };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Artifacts
|
|
68
|
+
const specsDir = path.join(root, 'specs');
|
|
69
|
+
const artifacts = [];
|
|
70
|
+
if (fs.existsSync(specsDir)) {
|
|
71
|
+
for (const f of fs.readdirSync(specsDir).filter(f => f.endsWith('.md'))) {
|
|
72
|
+
const fp = path.join(specsDir, f);
|
|
73
|
+
const stat = fs.statSync(fp);
|
|
74
|
+
artifacts.push({ name: f, size: stat.size, modified: stat.mtime.toISOString() });
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
data.sections.artifacts = { total: artifacts.length, files: artifacts };
|
|
78
|
+
|
|
79
|
+
// Config summary
|
|
80
|
+
const configFile = path.join(root, '.jumpstart', 'config.yaml');
|
|
81
|
+
data.sections.config = { exists: fs.existsSync(configFile) };
|
|
82
|
+
|
|
83
|
+
return { success: true, ...data };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Generate static HTML dashboard.
|
|
88
|
+
*/
|
|
89
|
+
function generateStaticDashboard(data) {
|
|
90
|
+
const html = `<!DOCTYPE html>
|
|
91
|
+
<html lang="en">
|
|
92
|
+
<head><meta charset="UTF-8"><title>Jump Start Dashboard</title></head>
|
|
93
|
+
<body>
|
|
94
|
+
<h1>Jump Start Dashboard</h1>
|
|
95
|
+
<p>Generated: ${data.generated_at}</p>
|
|
96
|
+
<h2>Phase Status</h2>
|
|
97
|
+
<p>Current Phase: ${data.sections.phases.current_phase}</p>
|
|
98
|
+
<h2>Artifacts (${data.sections.artifacts.total})</h2>
|
|
99
|
+
<ul>${data.sections.artifacts.files.map(f => `<li>${f.name} (${f.size} bytes)</li>`).join('')}</ul>
|
|
100
|
+
</body>
|
|
101
|
+
</html>`;
|
|
102
|
+
return { success: true, html };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Get server status.
|
|
107
|
+
*/
|
|
108
|
+
function getServerStatus(options = {}) {
|
|
109
|
+
return {
|
|
110
|
+
success: true,
|
|
111
|
+
running: false,
|
|
112
|
+
port: options.port || DEFAULT_PORT,
|
|
113
|
+
host: options.host || DEFAULT_HOST,
|
|
114
|
+
uptime: null
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
module.exports = {
|
|
119
|
+
generateConfig,
|
|
120
|
+
gatherDashboardData,
|
|
121
|
+
generateStaticDashboard,
|
|
122
|
+
getServerStatus,
|
|
123
|
+
DASHBOARD_SECTIONS,
|
|
124
|
+
DEFAULT_PORT,
|
|
125
|
+
DEFAULT_HOST
|
|
126
|
+
};
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* workshop-mode.js — Live Workshop Mode (Item 64)
|
|
3
|
+
*
|
|
4
|
+
* Facilitate discovery sessions and convert outputs directly
|
|
5
|
+
* into challenger brief, product brief, and PRD.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* node bin/lib/workshop-mode.js start|capture|convert|status [options]
|
|
9
|
+
*
|
|
10
|
+
* State file: .jumpstart/state/workshop.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', 'workshop.json');
|
|
19
|
+
|
|
20
|
+
const WORKSHOP_TYPES = ['discovery', 'ideation', 'refinement', 'retrospective'];
|
|
21
|
+
const OUTPUT_ARTIFACTS = ['challenger-brief', 'product-brief', 'prd'];
|
|
22
|
+
|
|
23
|
+
function defaultState() {
|
|
24
|
+
return {
|
|
25
|
+
version: '1.0.0',
|
|
26
|
+
sessions: [],
|
|
27
|
+
last_updated: null
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function loadState(stateFile) {
|
|
32
|
+
const fp = stateFile || DEFAULT_STATE_FILE;
|
|
33
|
+
if (!fs.existsSync(fp)) return defaultState();
|
|
34
|
+
try { return JSON.parse(fs.readFileSync(fp, 'utf8')); }
|
|
35
|
+
catch { return defaultState(); }
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function saveState(state, stateFile) {
|
|
39
|
+
const fp = stateFile || DEFAULT_STATE_FILE;
|
|
40
|
+
const dir = path.dirname(fp);
|
|
41
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
42
|
+
state.last_updated = new Date().toISOString();
|
|
43
|
+
fs.writeFileSync(fp, JSON.stringify(state, null, 2) + '\n', 'utf8');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Start a new workshop session.
|
|
48
|
+
*/
|
|
49
|
+
function startSession(name, options = {}) {
|
|
50
|
+
if (!name) return { success: false, error: 'Session name is required' };
|
|
51
|
+
|
|
52
|
+
const stateFile = options.stateFile || DEFAULT_STATE_FILE;
|
|
53
|
+
const state = loadState(stateFile);
|
|
54
|
+
|
|
55
|
+
const session = {
|
|
56
|
+
id: `WS-${Date.now()}`,
|
|
57
|
+
name,
|
|
58
|
+
type: options.type || 'discovery',
|
|
59
|
+
status: 'active',
|
|
60
|
+
facilitator: options.facilitator || null,
|
|
61
|
+
participants: options.participants || [],
|
|
62
|
+
captures: [],
|
|
63
|
+
created_at: new Date().toISOString(),
|
|
64
|
+
ended_at: null
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
if (!WORKSHOP_TYPES.includes(session.type)) {
|
|
68
|
+
return { success: false, error: `Unknown type: ${session.type}. Valid: ${WORKSHOP_TYPES.join(', ')}` };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
state.sessions.push(session);
|
|
72
|
+
saveState(state, stateFile);
|
|
73
|
+
|
|
74
|
+
return { success: true, session };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Capture a workshop insight or decision.
|
|
79
|
+
*/
|
|
80
|
+
function captureInsight(sessionId, text, options = {}) {
|
|
81
|
+
if (!sessionId || !text) return { success: false, error: 'sessionId and text are required' };
|
|
82
|
+
|
|
83
|
+
const stateFile = options.stateFile || DEFAULT_STATE_FILE;
|
|
84
|
+
const state = loadState(stateFile);
|
|
85
|
+
|
|
86
|
+
const session = state.sessions.find(s => s.id === sessionId);
|
|
87
|
+
if (!session) return { success: false, error: `Session ${sessionId} not found` };
|
|
88
|
+
|
|
89
|
+
const capture = {
|
|
90
|
+
id: `CAP-${Date.now()}`,
|
|
91
|
+
text,
|
|
92
|
+
category: options.category || 'insight',
|
|
93
|
+
author: options.author || 'anonymous',
|
|
94
|
+
timestamp: new Date().toISOString()
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
session.captures.push(capture);
|
|
98
|
+
saveState(state, stateFile);
|
|
99
|
+
|
|
100
|
+
return { success: true, capture };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Convert session captures to artifact outline.
|
|
105
|
+
*/
|
|
106
|
+
function convertToArtifact(sessionId, artifactType, options = {}) {
|
|
107
|
+
if (!sessionId || !artifactType) return { success: false, error: 'sessionId and artifactType are required' };
|
|
108
|
+
if (!OUTPUT_ARTIFACTS.includes(artifactType)) {
|
|
109
|
+
return { success: false, error: `Unknown artifact type. Valid: ${OUTPUT_ARTIFACTS.join(', ')}` };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const stateFile = options.stateFile || DEFAULT_STATE_FILE;
|
|
113
|
+
const state = loadState(stateFile);
|
|
114
|
+
|
|
115
|
+
const session = state.sessions.find(s => s.id === sessionId);
|
|
116
|
+
if (!session) return { success: false, error: `Session ${sessionId} not found` };
|
|
117
|
+
|
|
118
|
+
const sections = session.captures.reduce((acc, cap) => {
|
|
119
|
+
const cat = cap.category || 'general';
|
|
120
|
+
if (!acc[cat]) acc[cat] = [];
|
|
121
|
+
acc[cat].push(cap.text);
|
|
122
|
+
return acc;
|
|
123
|
+
}, {});
|
|
124
|
+
|
|
125
|
+
return {
|
|
126
|
+
success: true,
|
|
127
|
+
artifact_type: artifactType,
|
|
128
|
+
session_name: session.name,
|
|
129
|
+
sections,
|
|
130
|
+
captures_used: session.captures.length
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Get session status.
|
|
136
|
+
*/
|
|
137
|
+
function getSessionStatus(options = {}) {
|
|
138
|
+
const stateFile = options.stateFile || DEFAULT_STATE_FILE;
|
|
139
|
+
const state = loadState(stateFile);
|
|
140
|
+
|
|
141
|
+
return {
|
|
142
|
+
success: true,
|
|
143
|
+
total_sessions: state.sessions.length,
|
|
144
|
+
active: state.sessions.filter(s => s.status === 'active').length,
|
|
145
|
+
sessions: state.sessions.map(s => ({
|
|
146
|
+
id: s.id,
|
|
147
|
+
name: s.name,
|
|
148
|
+
type: s.type,
|
|
149
|
+
status: s.status,
|
|
150
|
+
captures: s.captures.length
|
|
151
|
+
}))
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
module.exports = {
|
|
156
|
+
startSession,
|
|
157
|
+
captureInsight,
|
|
158
|
+
convertToArtifact,
|
|
159
|
+
getSessionStatus,
|
|
160
|
+
loadState,
|
|
161
|
+
saveState,
|
|
162
|
+
defaultState,
|
|
163
|
+
WORKSHOP_TYPES,
|
|
164
|
+
OUTPUT_ARTIFACTS
|
|
165
|
+
};
|