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,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* enterprise-templates.js — Guided Enterprise Templates (Item 67)
|
|
3
|
+
*
|
|
4
|
+
* Industry-specific templates for healthcare, insurance, banking,
|
|
5
|
+
* manufacturing, retail, public sector, and internal platform engineering.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* node bin/lib/enterprise-templates.js list|get|apply [options]
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
'use strict';
|
|
12
|
+
|
|
13
|
+
const fs = require('fs');
|
|
14
|
+
const path = require('path');
|
|
15
|
+
|
|
16
|
+
const VERTICALS = [
|
|
17
|
+
'healthcare', 'insurance', 'banking', 'manufacturing',
|
|
18
|
+
'retail', 'public-sector', 'platform-engineering'
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
const TEMPLATE_CATALOG = {
|
|
22
|
+
healthcare: {
|
|
23
|
+
label: 'Healthcare',
|
|
24
|
+
compliance: ['HIPAA', 'HITECH', 'FDA-21-CFR-Part-11'],
|
|
25
|
+
data_concerns: ['PHI', 'patient-consent', 'de-identification'],
|
|
26
|
+
nfrs: ['audit-trail', 'data-encryption-at-rest', 'access-control'],
|
|
27
|
+
personas: ['clinician', 'patient', 'admin', 'compliance-officer']
|
|
28
|
+
},
|
|
29
|
+
insurance: {
|
|
30
|
+
label: 'Insurance',
|
|
31
|
+
compliance: ['SOC2', 'state-regulations', 'NAIC'],
|
|
32
|
+
data_concerns: ['PII', 'claims-data', 'underwriting-models'],
|
|
33
|
+
nfrs: ['audit-trail', 'data-retention', 'fraud-detection'],
|
|
34
|
+
personas: ['policyholder', 'agent', 'underwriter', 'claims-adjuster']
|
|
35
|
+
},
|
|
36
|
+
banking: {
|
|
37
|
+
label: 'Banking',
|
|
38
|
+
compliance: ['PCI-DSS', 'SOX', 'GDPR', 'AML-KYC'],
|
|
39
|
+
data_concerns: ['PII', 'transaction-data', 'account-info'],
|
|
40
|
+
nfrs: ['encryption', 'audit-trail', 'multi-factor-auth'],
|
|
41
|
+
personas: ['customer', 'teller', 'relationship-manager', 'risk-officer']
|
|
42
|
+
},
|
|
43
|
+
manufacturing: {
|
|
44
|
+
label: 'Manufacturing',
|
|
45
|
+
compliance: ['ISO-9001', 'ISO-27001'],
|
|
46
|
+
data_concerns: ['IoT-sensor-data', 'supply-chain', 'quality-metrics'],
|
|
47
|
+
nfrs: ['real-time-processing', 'edge-computing', 'uptime-sla'],
|
|
48
|
+
personas: ['plant-manager', 'operator', 'quality-engineer', 'supply-chain-manager']
|
|
49
|
+
},
|
|
50
|
+
retail: {
|
|
51
|
+
label: 'Retail',
|
|
52
|
+
compliance: ['PCI-DSS', 'CCPA', 'GDPR'],
|
|
53
|
+
data_concerns: ['customer-data', 'payment-info', 'inventory'],
|
|
54
|
+
nfrs: ['scalability', 'low-latency', 'high-availability'],
|
|
55
|
+
personas: ['shopper', 'store-manager', 'merchandiser', 'support-agent']
|
|
56
|
+
},
|
|
57
|
+
'public-sector': {
|
|
58
|
+
label: 'Public Sector',
|
|
59
|
+
compliance: ['FedRAMP', 'FISMA', 'Section-508', 'WCAG'],
|
|
60
|
+
data_concerns: ['citizen-data', 'classified-info', 'FOIA'],
|
|
61
|
+
nfrs: ['accessibility', 'audit-trail', 'data-sovereignty'],
|
|
62
|
+
personas: ['citizen', 'case-worker', 'agency-admin', 'auditor']
|
|
63
|
+
},
|
|
64
|
+
'platform-engineering': {
|
|
65
|
+
label: 'Internal Platform Engineering',
|
|
66
|
+
compliance: ['SOC2', 'internal-governance'],
|
|
67
|
+
data_concerns: ['service-configs', 'deployment-state', 'metrics'],
|
|
68
|
+
nfrs: ['self-service', 'golden-paths', 'developer-experience'],
|
|
69
|
+
personas: ['platform-engineer', 'app-developer', 'sre', 'security-engineer']
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* List available enterprise templates.
|
|
75
|
+
*/
|
|
76
|
+
function listTemplates() {
|
|
77
|
+
return {
|
|
78
|
+
success: true,
|
|
79
|
+
verticals: VERTICALS,
|
|
80
|
+
templates: VERTICALS.map(v => ({
|
|
81
|
+
id: v,
|
|
82
|
+
label: TEMPLATE_CATALOG[v].label,
|
|
83
|
+
compliance_count: TEMPLATE_CATALOG[v].compliance.length,
|
|
84
|
+
persona_count: TEMPLATE_CATALOG[v].personas.length
|
|
85
|
+
}))
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Get detailed template for a vertical.
|
|
91
|
+
*/
|
|
92
|
+
function getTemplate(vertical) {
|
|
93
|
+
if (!VERTICALS.includes(vertical)) {
|
|
94
|
+
return { success: false, error: `Unknown vertical: ${vertical}. Valid: ${VERTICALS.join(', ')}` };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return {
|
|
98
|
+
success: true,
|
|
99
|
+
vertical,
|
|
100
|
+
template: TEMPLATE_CATALOG[vertical]
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Apply template to project configuration.
|
|
106
|
+
*/
|
|
107
|
+
function applyTemplate(root, vertical, options = {}) {
|
|
108
|
+
if (!VERTICALS.includes(vertical)) {
|
|
109
|
+
return { success: false, error: `Unknown vertical: ${vertical}. Valid: ${VERTICALS.join(', ')}` };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const template = TEMPLATE_CATALOG[vertical];
|
|
113
|
+
const applied = {
|
|
114
|
+
vertical,
|
|
115
|
+
label: template.label,
|
|
116
|
+
compliance_frameworks: template.compliance,
|
|
117
|
+
data_concerns: template.data_concerns,
|
|
118
|
+
nfr_requirements: template.nfrs,
|
|
119
|
+
personas: template.personas,
|
|
120
|
+
applied_at: new Date().toISOString()
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
// Save applied template state
|
|
124
|
+
const stateDir = path.join(root, '.jumpstart', 'state');
|
|
125
|
+
if (!fs.existsSync(stateDir)) fs.mkdirSync(stateDir, { recursive: true });
|
|
126
|
+
fs.writeFileSync(
|
|
127
|
+
path.join(stateDir, 'enterprise-template.json'),
|
|
128
|
+
JSON.stringify(applied, null, 2) + '\n', 'utf8'
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
return { success: true, applied };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
module.exports = {
|
|
135
|
+
listTemplates,
|
|
136
|
+
getTemplate,
|
|
137
|
+
applyTemplate,
|
|
138
|
+
VERTICALS,
|
|
139
|
+
TEMPLATE_CATALOG
|
|
140
|
+
};
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* environment-promotion.js — Environment Promotion Governance (Item 22)
|
|
3
|
+
*
|
|
4
|
+
* Track readiness from dev to test to stage to prod with
|
|
5
|
+
* quality gates tied to environments.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* node bin/lib/environment-promotion.js promote|status|gate [options]
|
|
9
|
+
*
|
|
10
|
+
* State file: .jumpstart/state/environment-promotion.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', 'environment-promotion.json');
|
|
19
|
+
|
|
20
|
+
const ENVIRONMENTS = ['dev', 'test', 'staging', 'prod'];
|
|
21
|
+
|
|
22
|
+
const DEFAULT_GATES = {
|
|
23
|
+
dev: ['unit-tests', 'lint', 'build'],
|
|
24
|
+
test: ['integration-tests', 'coverage-threshold', 'security-scan'],
|
|
25
|
+
staging: ['e2e-tests', 'performance-tests', 'approval-required'],
|
|
26
|
+
prod: ['release-readiness', 'change-advisory', 'final-approval']
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Default promotion state.
|
|
31
|
+
* @returns {object}
|
|
32
|
+
*/
|
|
33
|
+
function defaultState() {
|
|
34
|
+
return {
|
|
35
|
+
version: '1.0.0',
|
|
36
|
+
created_at: new Date().toISOString(),
|
|
37
|
+
last_updated: null,
|
|
38
|
+
current_environment: 'dev',
|
|
39
|
+
environments: ENVIRONMENTS.map(env => ({
|
|
40
|
+
name: env,
|
|
41
|
+
status: env === 'dev' ? 'active' : 'pending',
|
|
42
|
+
gates: (DEFAULT_GATES[env] || []).map(g => ({ name: g, passed: false, checked_at: null })),
|
|
43
|
+
promoted_at: null,
|
|
44
|
+
promoted_by: null
|
|
45
|
+
})),
|
|
46
|
+
promotion_history: []
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function loadState(stateFile) {
|
|
51
|
+
const filePath = stateFile || DEFAULT_STATE_FILE;
|
|
52
|
+
if (!fs.existsSync(filePath)) return defaultState();
|
|
53
|
+
try { return JSON.parse(fs.readFileSync(filePath, 'utf8')); }
|
|
54
|
+
catch { return defaultState(); }
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function saveState(state, stateFile) {
|
|
58
|
+
const filePath = stateFile || DEFAULT_STATE_FILE;
|
|
59
|
+
const dir = path.dirname(filePath);
|
|
60
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
61
|
+
state.last_updated = new Date().toISOString();
|
|
62
|
+
fs.writeFileSync(filePath, JSON.stringify(state, null, 2) + '\n', 'utf8');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Check if an environment's gates are all passed.
|
|
67
|
+
*
|
|
68
|
+
* @param {string} environment
|
|
69
|
+
* @param {object} [options]
|
|
70
|
+
* @returns {object}
|
|
71
|
+
*/
|
|
72
|
+
function checkGates(environment, options = {}) {
|
|
73
|
+
if (!ENVIRONMENTS.includes(environment)) {
|
|
74
|
+
return { success: false, error: `Invalid environment: ${environment}. Must be one of: ${ENVIRONMENTS.join(', ')}` };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const stateFile = options.stateFile || DEFAULT_STATE_FILE;
|
|
78
|
+
const state = loadState(stateFile);
|
|
79
|
+
const env = state.environments.find(e => e.name === environment);
|
|
80
|
+
if (!env) return { success: false, error: `Environment not found: ${environment}` };
|
|
81
|
+
|
|
82
|
+
const passed = env.gates.filter(g => g.passed);
|
|
83
|
+
const failed = env.gates.filter(g => !g.passed);
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
success: true,
|
|
87
|
+
environment,
|
|
88
|
+
all_passed: failed.length === 0,
|
|
89
|
+
passed: passed.map(g => g.name),
|
|
90
|
+
pending: failed.map(g => g.name),
|
|
91
|
+
total: env.gates.length,
|
|
92
|
+
ready_to_promote: failed.length === 0
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Record a gate check result.
|
|
98
|
+
*
|
|
99
|
+
* @param {string} environment
|
|
100
|
+
* @param {string} gateName
|
|
101
|
+
* @param {boolean} passed
|
|
102
|
+
* @param {object} [options]
|
|
103
|
+
* @returns {object}
|
|
104
|
+
*/
|
|
105
|
+
function recordGateResult(environment, gateName, passed, options = {}) {
|
|
106
|
+
if (!ENVIRONMENTS.includes(environment)) {
|
|
107
|
+
return { success: false, error: `Invalid environment: ${environment}` };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const stateFile = options.stateFile || DEFAULT_STATE_FILE;
|
|
111
|
+
const state = loadState(stateFile);
|
|
112
|
+
const env = state.environments.find(e => e.name === environment);
|
|
113
|
+
if (!env) return { success: false, error: `Environment not found: ${environment}` };
|
|
114
|
+
|
|
115
|
+
const gate = env.gates.find(g => g.name === gateName);
|
|
116
|
+
if (!gate) return { success: false, error: `Gate not found: ${gateName}` };
|
|
117
|
+
|
|
118
|
+
gate.passed = passed;
|
|
119
|
+
gate.checked_at = new Date().toISOString();
|
|
120
|
+
saveState(state, stateFile);
|
|
121
|
+
|
|
122
|
+
return { success: true, environment, gate: gateName, passed, checked_at: gate.checked_at };
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Promote to the next environment.
|
|
127
|
+
*
|
|
128
|
+
* @param {string} targetEnv
|
|
129
|
+
* @param {object} [options]
|
|
130
|
+
* @returns {object}
|
|
131
|
+
*/
|
|
132
|
+
function promote(targetEnv, options = {}) {
|
|
133
|
+
if (!ENVIRONMENTS.includes(targetEnv)) {
|
|
134
|
+
return { success: false, error: `Invalid environment: ${targetEnv}` };
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const stateFile = options.stateFile || DEFAULT_STATE_FILE;
|
|
138
|
+
const state = loadState(stateFile);
|
|
139
|
+
|
|
140
|
+
const targetIdx = ENVIRONMENTS.indexOf(targetEnv);
|
|
141
|
+
const currentIdx = ENVIRONMENTS.indexOf(state.current_environment);
|
|
142
|
+
|
|
143
|
+
if (targetIdx <= currentIdx) {
|
|
144
|
+
return { success: false, error: `Cannot promote backward from ${state.current_environment} to ${targetEnv}` };
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Check all intermediate gates
|
|
148
|
+
for (let i = currentIdx; i < targetIdx; i++) {
|
|
149
|
+
const env = state.environments.find(e => e.name === ENVIRONMENTS[i]);
|
|
150
|
+
if (env) {
|
|
151
|
+
const pending = env.gates.filter(g => !g.passed);
|
|
152
|
+
if (pending.length > 0) {
|
|
153
|
+
return {
|
|
154
|
+
success: false,
|
|
155
|
+
error: `Gates not passed for ${ENVIRONMENTS[i]}: ${pending.map(g => g.name).join(', ')}`
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
state.current_environment = targetEnv;
|
|
162
|
+
const envObj = state.environments.find(e => e.name === targetEnv);
|
|
163
|
+
if (envObj) {
|
|
164
|
+
envObj.status = 'active';
|
|
165
|
+
envObj.promoted_at = new Date().toISOString();
|
|
166
|
+
envObj.promoted_by = options.promotedBy || null;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
state.promotion_history.push({
|
|
170
|
+
from: ENVIRONMENTS[currentIdx],
|
|
171
|
+
to: targetEnv,
|
|
172
|
+
promoted_at: new Date().toISOString(),
|
|
173
|
+
promoted_by: options.promotedBy || null
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
saveState(state, stateFile);
|
|
177
|
+
|
|
178
|
+
return {
|
|
179
|
+
success: true,
|
|
180
|
+
from: ENVIRONMENTS[currentIdx],
|
|
181
|
+
to: targetEnv,
|
|
182
|
+
current_environment: state.current_environment
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Get promotion status.
|
|
188
|
+
*
|
|
189
|
+
* @param {object} [options]
|
|
190
|
+
* @returns {object}
|
|
191
|
+
*/
|
|
192
|
+
function getStatus(options = {}) {
|
|
193
|
+
const stateFile = options.stateFile || DEFAULT_STATE_FILE;
|
|
194
|
+
const state = loadState(stateFile);
|
|
195
|
+
|
|
196
|
+
return {
|
|
197
|
+
success: true,
|
|
198
|
+
current_environment: state.current_environment,
|
|
199
|
+
environments: state.environments.map(e => ({
|
|
200
|
+
name: e.name,
|
|
201
|
+
status: e.status,
|
|
202
|
+
gates_passed: e.gates.filter(g => g.passed).length,
|
|
203
|
+
gates_total: e.gates.length,
|
|
204
|
+
ready: e.gates.every(g => g.passed)
|
|
205
|
+
})),
|
|
206
|
+
promotion_history: state.promotion_history
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
module.exports = {
|
|
211
|
+
defaultState,
|
|
212
|
+
loadState,
|
|
213
|
+
saveState,
|
|
214
|
+
checkGates,
|
|
215
|
+
recordGateResult,
|
|
216
|
+
promote,
|
|
217
|
+
getStatus,
|
|
218
|
+
ENVIRONMENTS,
|
|
219
|
+
DEFAULT_GATES
|
|
220
|
+
};
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* estimation-studio.js — Feature Estimation Studio (Item 72)
|
|
3
|
+
*
|
|
4
|
+
* T-shirt sizing, story points, ideal days, ROM cost,
|
|
5
|
+
* and confidence ranges for feature estimation.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* node bin/lib/estimation-studio.js estimate|report|calibrate [options]
|
|
9
|
+
*
|
|
10
|
+
* State file: .jumpstart/state/estimations.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', 'estimations.json');
|
|
19
|
+
|
|
20
|
+
const TSHIRT_SIZES = ['XS', 'S', 'M', 'L', 'XL', 'XXL'];
|
|
21
|
+
const TSHIRT_TO_POINTS = { XS: 1, S: 2, M: 3, L: 5, XL: 8, XXL: 13 };
|
|
22
|
+
const TSHIRT_TO_DAYS = { XS: 0.5, S: 1, M: 2, L: 5, XL: 10, XXL: 20 };
|
|
23
|
+
|
|
24
|
+
const CONFIDENCE_LEVELS = ['low', 'medium', 'high'];
|
|
25
|
+
const CONFIDENCE_RANGES = {
|
|
26
|
+
low: { min_multiplier: 0.5, max_multiplier: 3.0 },
|
|
27
|
+
medium: { min_multiplier: 0.75, max_multiplier: 1.5 },
|
|
28
|
+
high: { min_multiplier: 0.9, max_multiplier: 1.2 }
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
function defaultState() {
|
|
32
|
+
return { version: '1.0.0', estimates: [], calibration: { velocity: null }, last_updated: null };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function loadState(stateFile) {
|
|
36
|
+
const fp = stateFile || DEFAULT_STATE_FILE;
|
|
37
|
+
if (!fs.existsSync(fp)) return defaultState();
|
|
38
|
+
try { return JSON.parse(fs.readFileSync(fp, 'utf8')); }
|
|
39
|
+
catch { return defaultState(); }
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function saveState(state, stateFile) {
|
|
43
|
+
const fp = stateFile || DEFAULT_STATE_FILE;
|
|
44
|
+
const dir = path.dirname(fp);
|
|
45
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
46
|
+
state.last_updated = new Date().toISOString();
|
|
47
|
+
fs.writeFileSync(fp, JSON.stringify(state, null, 2) + '\n', 'utf8');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Estimate a feature.
|
|
52
|
+
*/
|
|
53
|
+
function estimateFeature(name, tshirtSize, options = {}) {
|
|
54
|
+
if (!name || !tshirtSize) return { success: false, error: 'name and tshirtSize are required' };
|
|
55
|
+
if (!TSHIRT_SIZES.includes(tshirtSize)) {
|
|
56
|
+
return { success: false, error: `Invalid size: ${tshirtSize}. Valid: ${TSHIRT_SIZES.join(', ')}` };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const confidence = options.confidence || 'medium';
|
|
60
|
+
const range = CONFIDENCE_RANGES[confidence] || CONFIDENCE_RANGES.medium;
|
|
61
|
+
const points = TSHIRT_TO_POINTS[tshirtSize];
|
|
62
|
+
const days = TSHIRT_TO_DAYS[tshirtSize];
|
|
63
|
+
const dailyRate = options.dailyRate || 800;
|
|
64
|
+
|
|
65
|
+
const estimate = {
|
|
66
|
+
id: `EST-${Date.now()}`,
|
|
67
|
+
name,
|
|
68
|
+
tshirt_size: tshirtSize,
|
|
69
|
+
story_points: points,
|
|
70
|
+
ideal_days: days,
|
|
71
|
+
confidence,
|
|
72
|
+
rom_cost: {
|
|
73
|
+
min: Math.round(days * dailyRate * range.min_multiplier),
|
|
74
|
+
expected: Math.round(days * dailyRate),
|
|
75
|
+
max: Math.round(days * dailyRate * range.max_multiplier)
|
|
76
|
+
},
|
|
77
|
+
created_at: new Date().toISOString()
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const stateFile = options.stateFile || DEFAULT_STATE_FILE;
|
|
81
|
+
const state = loadState(stateFile);
|
|
82
|
+
state.estimates.push(estimate);
|
|
83
|
+
saveState(state, stateFile);
|
|
84
|
+
|
|
85
|
+
return { success: true, estimate };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Generate estimation report.
|
|
90
|
+
*/
|
|
91
|
+
function generateReport(options = {}) {
|
|
92
|
+
const stateFile = options.stateFile || DEFAULT_STATE_FILE;
|
|
93
|
+
const state = loadState(stateFile);
|
|
94
|
+
|
|
95
|
+
const totalPoints = state.estimates.reduce((s, e) => s + e.story_points, 0);
|
|
96
|
+
const totalDays = state.estimates.reduce((s, e) => s + e.ideal_days, 0);
|
|
97
|
+
const totalCostMin = state.estimates.reduce((s, e) => s + e.rom_cost.min, 0);
|
|
98
|
+
const totalCostMax = state.estimates.reduce((s, e) => s + e.rom_cost.max, 0);
|
|
99
|
+
|
|
100
|
+
return {
|
|
101
|
+
success: true,
|
|
102
|
+
total_features: state.estimates.length,
|
|
103
|
+
total_story_points: totalPoints,
|
|
104
|
+
total_ideal_days: totalDays,
|
|
105
|
+
total_rom_cost: { min: totalCostMin, max: totalCostMax },
|
|
106
|
+
by_size: TSHIRT_SIZES.reduce((acc, size) => {
|
|
107
|
+
acc[size] = state.estimates.filter(e => e.tshirt_size === size).length;
|
|
108
|
+
return acc;
|
|
109
|
+
}, {}),
|
|
110
|
+
estimates: state.estimates
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Set calibration data.
|
|
116
|
+
*/
|
|
117
|
+
function calibrate(velocity, options = {}) {
|
|
118
|
+
const stateFile = options.stateFile || DEFAULT_STATE_FILE;
|
|
119
|
+
const state = loadState(stateFile);
|
|
120
|
+
state.calibration.velocity = velocity;
|
|
121
|
+
state.calibration.updated_at = new Date().toISOString();
|
|
122
|
+
saveState(state, stateFile);
|
|
123
|
+
return { success: true, velocity };
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
module.exports = {
|
|
127
|
+
estimateFeature, generateReport, calibrate,
|
|
128
|
+
loadState, saveState, defaultState,
|
|
129
|
+
TSHIRT_SIZES, TSHIRT_TO_POINTS, TSHIRT_TO_DAYS, CONFIDENCE_LEVELS, CONFIDENCE_RANGES
|
|
130
|
+
};
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* event-modeling.js — Event-Driven Architecture Modeling (Item 85)
|
|
3
|
+
*
|
|
4
|
+
* Topics, events, idempotency, retries, DLQs, sagas,
|
|
5
|
+
* and observability patterns.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* node bin/lib/event-modeling.js define|validate|report [options]
|
|
9
|
+
*
|
|
10
|
+
* State file: .jumpstart/state/event-modeling.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', 'event-modeling.json');
|
|
19
|
+
|
|
20
|
+
const EVENT_TYPES = ['domain-event', 'integration-event', 'command', 'query'];
|
|
21
|
+
const PATTERNS = ['saga', 'choreography', 'orchestration', 'cqrs', 'event-sourcing'];
|
|
22
|
+
|
|
23
|
+
function defaultState() {
|
|
24
|
+
return { version: '1.0.0', topics: [], events: [], sagas: [], 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 defineTopic(name, options = {}) {
|
|
43
|
+
if (!name) return { success: false, error: 'Topic name is required' };
|
|
44
|
+
|
|
45
|
+
const stateFile = options.stateFile || DEFAULT_STATE_FILE;
|
|
46
|
+
const state = loadState(stateFile);
|
|
47
|
+
|
|
48
|
+
const topic = {
|
|
49
|
+
id: `TOPIC-${Date.now()}`,
|
|
50
|
+
name,
|
|
51
|
+
partitions: options.partitions || 1,
|
|
52
|
+
retention: options.retention || '7d',
|
|
53
|
+
dlq: options.dlq || false,
|
|
54
|
+
created_at: new Date().toISOString()
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
state.topics.push(topic);
|
|
58
|
+
saveState(state, stateFile);
|
|
59
|
+
|
|
60
|
+
return { success: true, topic };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function defineEvent(name, topicId, options = {}) {
|
|
64
|
+
if (!name || !topicId) return { success: false, error: 'name and topicId are required' };
|
|
65
|
+
|
|
66
|
+
const stateFile = options.stateFile || DEFAULT_STATE_FILE;
|
|
67
|
+
const state = loadState(stateFile);
|
|
68
|
+
|
|
69
|
+
const event = {
|
|
70
|
+
id: `EVT-${Date.now()}`,
|
|
71
|
+
name,
|
|
72
|
+
topic: topicId,
|
|
73
|
+
type: options.type || 'domain-event',
|
|
74
|
+
schema: options.schema || {},
|
|
75
|
+
idempotency_key: options.idempotency_key || null,
|
|
76
|
+
retry_policy: options.retry_policy || { max_retries: 3, backoff: 'exponential' },
|
|
77
|
+
created_at: new Date().toISOString()
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
if (!EVENT_TYPES.includes(event.type)) {
|
|
81
|
+
return { success: false, error: `Unknown type: ${event.type}. Valid: ${EVENT_TYPES.join(', ')}` };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
state.events.push(event);
|
|
85
|
+
saveState(state, stateFile);
|
|
86
|
+
|
|
87
|
+
return { success: true, event };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function defineSaga(name, steps, options = {}) {
|
|
91
|
+
if (!name || !steps || !Array.isArray(steps)) {
|
|
92
|
+
return { success: false, error: 'name and steps array are required' };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const stateFile = options.stateFile || DEFAULT_STATE_FILE;
|
|
96
|
+
const state = loadState(stateFile);
|
|
97
|
+
|
|
98
|
+
const saga = {
|
|
99
|
+
id: `SAGA-${Date.now()}`,
|
|
100
|
+
name,
|
|
101
|
+
steps: steps.map((s, i) => ({ order: i + 1, ...s })),
|
|
102
|
+
compensation: options.compensation || 'manual',
|
|
103
|
+
created_at: new Date().toISOString()
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
state.sagas.push(saga);
|
|
107
|
+
saveState(state, stateFile);
|
|
108
|
+
|
|
109
|
+
return { success: true, saga };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function generateReport(options = {}) {
|
|
113
|
+
const stateFile = options.stateFile || DEFAULT_STATE_FILE;
|
|
114
|
+
const state = loadState(stateFile);
|
|
115
|
+
|
|
116
|
+
return {
|
|
117
|
+
success: true,
|
|
118
|
+
total_topics: state.topics.length,
|
|
119
|
+
total_events: state.events.length,
|
|
120
|
+
total_sagas: state.sagas.length,
|
|
121
|
+
topics_with_dlq: state.topics.filter(t => t.dlq).length,
|
|
122
|
+
events_by_type: state.events.reduce((acc, e) => { acc[e.type] = (acc[e.type] || 0) + 1; return acc; }, {}),
|
|
123
|
+
topics: state.topics,
|
|
124
|
+
events: state.events,
|
|
125
|
+
sagas: state.sagas
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
module.exports = {
|
|
130
|
+
defineTopic, defineEvent, defineSaga, generateReport,
|
|
131
|
+
loadState, saveState, defaultState,
|
|
132
|
+
EVENT_TYPES, PATTERNS
|
|
133
|
+
};
|