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.
Files changed (146) hide show
  1. package/.github/agents/jumpstart-adversary.agent.md +2 -1
  2. package/.github/agents/jumpstart-architect.agent.md +5 -6
  3. package/.github/agents/jumpstart-challenger.agent.md +2 -1
  4. package/.github/agents/jumpstart-devops.agent.md +2 -2
  5. package/.github/agents/jumpstart-diagram-verifier.agent.md +2 -1
  6. package/.github/agents/jumpstart-maintenance.agent.md +1 -0
  7. package/.github/agents/jumpstart-performance.agent.md +1 -0
  8. package/.github/agents/jumpstart-pm.agent.md +1 -1
  9. package/.github/agents/jumpstart-refactor.agent.md +1 -0
  10. package/.github/agents/jumpstart-requirements-extractor.agent.md +1 -0
  11. package/.github/agents/jumpstart-researcher.agent.md +1 -0
  12. package/.github/agents/jumpstart-retrospective.agent.md +1 -0
  13. package/.github/agents/jumpstart-reviewer.agent.md +2 -0
  14. package/.github/agents/jumpstart-scout.agent.md +1 -1
  15. package/.github/agents/jumpstart-scrum-master.agent.md +1 -0
  16. package/.github/agents/jumpstart-security.agent.md +2 -1
  17. package/.github/agents/jumpstart-tech-writer.agent.md +1 -0
  18. package/.github/workflows/quality.yml +19 -2
  19. package/.jumpstart/agents/analyst.md +38 -0
  20. package/.jumpstart/agents/architect.md +38 -0
  21. package/.jumpstart/agents/challenger.md +38 -0
  22. package/.jumpstart/agents/developer.md +41 -0
  23. package/.jumpstart/agents/pm.md +38 -0
  24. package/.jumpstart/agents/scout.md +33 -0
  25. package/.jumpstart/agents/ux-designer.md +4 -0
  26. package/.jumpstart/config.yaml +24 -0
  27. package/.jumpstart/schemas/timeline.schema.json +1 -0
  28. package/.jumpstart/skills/skill-creator/SKILL.md +485 -357
  29. package/.jumpstart/skills/skill-creator/agents/analyzer.md +274 -0
  30. package/.jumpstart/skills/skill-creator/agents/comparator.md +202 -0
  31. package/.jumpstart/skills/skill-creator/agents/grader.md +223 -0
  32. package/.jumpstart/skills/skill-creator/assets/eval_review.html +146 -0
  33. package/.jumpstart/skills/skill-creator/eval-viewer/generate_review.py +471 -0
  34. package/.jumpstart/skills/skill-creator/eval-viewer/viewer.html +1325 -0
  35. package/.jumpstart/skills/skill-creator/references/schemas.md +430 -0
  36. package/.jumpstart/skills/skill-creator/scripts/__init__.py +0 -0
  37. package/.jumpstart/skills/skill-creator/scripts/aggregate_benchmark.py +401 -0
  38. package/.jumpstart/skills/skill-creator/scripts/generate_report.py +326 -0
  39. package/.jumpstart/skills/skill-creator/scripts/improve_description.py +247 -0
  40. package/.jumpstart/skills/skill-creator/scripts/package_skill.py +136 -110
  41. package/.jumpstart/skills/skill-creator/scripts/run_eval.py +310 -0
  42. package/.jumpstart/skills/skill-creator/scripts/run_loop.py +328 -0
  43. package/.jumpstart/skills/skill-creator/scripts/utils.py +47 -0
  44. package/.jumpstart/state/timeline.json +659 -0
  45. package/.jumpstart/usage-log.json +74 -3
  46. package/README.md +62 -1
  47. package/bin/cli.js +3217 -1
  48. package/bin/headless-runner.js +62 -2
  49. package/bin/lib/agent-checkpoint.js +168 -0
  50. package/bin/lib/ai-evaluation.js +104 -0
  51. package/bin/lib/ai-intake.js +152 -0
  52. package/bin/lib/ambiguity-heatmap.js +152 -0
  53. package/bin/lib/artifact-comparison.js +104 -0
  54. package/bin/lib/ast-edit-engine.js +157 -0
  55. package/bin/lib/backlog-sync.js +338 -0
  56. package/bin/lib/bcdr-planning.js +158 -0
  57. package/bin/lib/bidirectional-trace.js +199 -0
  58. package/bin/lib/branch-workflow.js +266 -0
  59. package/bin/lib/cab-output.js +119 -0
  60. package/bin/lib/chat-integration.js +122 -0
  61. package/bin/lib/ci-cd-integration.js +208 -0
  62. package/bin/lib/codebase-retrieval.js +125 -0
  63. package/bin/lib/collaboration.js +168 -0
  64. package/bin/lib/compliance-packs.js +213 -0
  65. package/bin/lib/context-chunker.js +128 -0
  66. package/bin/lib/context-onboarding.js +122 -0
  67. package/bin/lib/contract-first.js +124 -0
  68. package/bin/lib/cost-router.js +148 -0
  69. package/bin/lib/credential-boundary.js +155 -0
  70. package/bin/lib/data-classification.js +180 -0
  71. package/bin/lib/data-contracts.js +129 -0
  72. package/bin/lib/db-evolution.js +158 -0
  73. package/bin/lib/decision-conflicts.js +299 -0
  74. package/bin/lib/delivery-confidence.js +361 -0
  75. package/bin/lib/dependency-upgrade.js +153 -0
  76. package/bin/lib/design-system.js +133 -0
  77. package/bin/lib/deterministic-artifacts.js +151 -0
  78. package/bin/lib/diagram-studio.js +115 -0
  79. package/bin/lib/domain-ontology.js +140 -0
  80. package/bin/lib/ea-review-packet.js +151 -0
  81. package/bin/lib/enterprise-search.js +123 -0
  82. package/bin/lib/enterprise-templates.js +140 -0
  83. package/bin/lib/environment-promotion.js +220 -0
  84. package/bin/lib/estimation-studio.js +130 -0
  85. package/bin/lib/event-modeling.js +133 -0
  86. package/bin/lib/evidence-collector.js +179 -0
  87. package/bin/lib/finops-planner.js +182 -0
  88. package/bin/lib/fitness-functions.js +279 -0
  89. package/bin/lib/focus.js +448 -0
  90. package/bin/lib/governance-dashboard.js +165 -0
  91. package/bin/lib/guided-handoff.js +120 -0
  92. package/bin/lib/impact-analysis.js +190 -0
  93. package/bin/lib/incident-feedback.js +157 -0
  94. package/bin/lib/integrate.js +1 -1
  95. package/bin/lib/knowledge-graph.js +122 -0
  96. package/bin/lib/legacy-modernizer.js +160 -0
  97. package/bin/lib/migration-planner.js +144 -0
  98. package/bin/lib/model-governance.js +185 -0
  99. package/bin/lib/model-router.js +144 -0
  100. package/bin/lib/multi-repo.js +272 -0
  101. package/bin/lib/next-phase.js +53 -8
  102. package/bin/lib/ops-ownership.js +152 -0
  103. package/bin/lib/parallel-agents.js +257 -0
  104. package/bin/lib/pattern-library.js +115 -0
  105. package/bin/lib/persona-packs.js +99 -0
  106. package/bin/lib/plan-executor.js +366 -0
  107. package/bin/lib/platform-engineering.js +119 -0
  108. package/bin/lib/playback-summaries.js +126 -0
  109. package/bin/lib/policy-engine.js +240 -0
  110. package/bin/lib/portfolio-reporting.js +357 -0
  111. package/bin/lib/pr-package.js +197 -0
  112. package/bin/lib/project-memory.js +235 -0
  113. package/bin/lib/prompt-governance.js +130 -0
  114. package/bin/lib/promptless-mode.js +128 -0
  115. package/bin/lib/quality-graph.js +193 -0
  116. package/bin/lib/raci-matrix.js +188 -0
  117. package/bin/lib/refactor-planner.js +167 -0
  118. package/bin/lib/reference-architectures.js +304 -0
  119. package/bin/lib/release-readiness.js +171 -0
  120. package/bin/lib/repo-graph.js +262 -0
  121. package/bin/lib/requirements-baseline.js +358 -0
  122. package/bin/lib/risk-register.js +211 -0
  123. package/bin/lib/role-approval.js +249 -0
  124. package/bin/lib/role-views.js +142 -0
  125. package/bin/lib/root-cause-analysis.js +132 -0
  126. package/bin/lib/runtime-debugger.js +154 -0
  127. package/bin/lib/safe-rename.js +135 -0
  128. package/bin/lib/semantic-diff.js +335 -0
  129. package/bin/lib/sla-slo.js +210 -0
  130. package/bin/lib/spec-comments.js +147 -0
  131. package/bin/lib/spec-maturity.js +287 -0
  132. package/bin/lib/sre-integration.js +154 -0
  133. package/bin/lib/structured-elicitation.js +174 -0
  134. package/bin/lib/telemetry-feedback.js +118 -0
  135. package/bin/lib/test-generator.js +146 -0
  136. package/bin/lib/timeline.js +2 -1
  137. package/bin/lib/tool-bridge.js +107 -0
  138. package/bin/lib/tool-guardrails.js +139 -0
  139. package/bin/lib/tool-schemas.js +172 -3
  140. package/bin/lib/transcript-ingestion.js +150 -0
  141. package/bin/lib/vendor-risk.js +173 -0
  142. package/bin/lib/waiver-workflow.js +174 -0
  143. package/bin/lib/web-dashboard.js +126 -0
  144. package/bin/lib/workshop-mode.js +165 -0
  145. package/bin/lib/workstream-ownership.js +104 -0
  146. 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
+ };