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.
Files changed (188) hide show
  1. package/.github/agents/jumpstart-adversary.agent.md +2 -1
  2. package/.github/agents/jumpstart-architect.agent.md +6 -7
  3. package/.github/agents/jumpstart-challenger.agent.md +2 -1
  4. package/.github/agents/jumpstart-developer.agent.md +1 -1
  5. package/.github/agents/jumpstart-devops.agent.md +2 -2
  6. package/.github/agents/jumpstart-diagram-verifier.agent.md +2 -1
  7. package/.github/agents/jumpstart-maintenance.agent.md +1 -0
  8. package/.github/agents/jumpstart-performance.agent.md +1 -0
  9. package/.github/agents/jumpstart-pm.agent.md +1 -1
  10. package/.github/agents/jumpstart-refactor.agent.md +1 -0
  11. package/.github/agents/jumpstart-requirements-extractor.agent.md +1 -0
  12. package/.github/agents/jumpstart-researcher.agent.md +1 -0
  13. package/.github/agents/jumpstart-retrospective.agent.md +1 -0
  14. package/.github/agents/jumpstart-reviewer.agent.md +2 -0
  15. package/.github/agents/jumpstart-scout.agent.md +1 -1
  16. package/.github/agents/jumpstart-scrum-master.agent.md +1 -0
  17. package/.github/agents/jumpstart-security.agent.md +2 -1
  18. package/.github/agents/jumpstart-tech-writer.agent.md +1 -0
  19. package/.github/agents/jumpstart-uiux-designer.agent.md +66 -0
  20. package/.github/workflows/quality.yml +19 -2
  21. package/.jumpstart/agents/analyst.md +38 -0
  22. package/.jumpstart/agents/architect.md +39 -1
  23. package/.jumpstart/agents/challenger.md +38 -0
  24. package/.jumpstart/agents/developer.md +41 -0
  25. package/.jumpstart/agents/pm.md +38 -0
  26. package/.jumpstart/agents/scout.md +33 -0
  27. package/.jumpstart/agents/ux-designer.md +29 -9
  28. package/.jumpstart/commands/commands.md +6 -5
  29. package/.jumpstart/config.yaml +25 -1
  30. package/.jumpstart/roadmap.md +1 -1
  31. package/.jumpstart/schemas/timeline.schema.json +1 -0
  32. package/.jumpstart/skills/README.md +1 -0
  33. package/.jumpstart/skills/quality-gates/SKILL.md +126 -0
  34. package/.jumpstart/skills/skill-creator/SKILL.md +485 -357
  35. package/.jumpstart/skills/skill-creator/agents/analyzer.md +274 -0
  36. package/.jumpstart/skills/skill-creator/agents/comparator.md +202 -0
  37. package/.jumpstart/skills/skill-creator/agents/grader.md +223 -0
  38. package/.jumpstart/skills/skill-creator/assets/eval_review.html +146 -0
  39. package/.jumpstart/skills/skill-creator/eval-viewer/generate_review.py +471 -0
  40. package/.jumpstart/skills/skill-creator/eval-viewer/viewer.html +1325 -0
  41. package/.jumpstart/skills/skill-creator/references/schemas.md +430 -0
  42. package/.jumpstart/skills/skill-creator/scripts/__init__.py +0 -0
  43. package/.jumpstart/skills/skill-creator/scripts/aggregate_benchmark.py +401 -0
  44. package/.jumpstart/skills/skill-creator/scripts/generate_report.py +326 -0
  45. package/.jumpstart/skills/skill-creator/scripts/improve_description.py +247 -0
  46. package/.jumpstart/skills/skill-creator/scripts/package_skill.py +136 -110
  47. package/.jumpstart/skills/skill-creator/scripts/run_eval.py +310 -0
  48. package/.jumpstart/skills/skill-creator/scripts/run_loop.py +328 -0
  49. package/.jumpstart/skills/skill-creator/scripts/utils.py +47 -0
  50. package/.jumpstart/skills/ui-ux-pro-max/SKILL.md +266 -0
  51. package/.jumpstart/skills/ui-ux-pro-max/data/charts.csv +26 -0
  52. package/.jumpstart/skills/ui-ux-pro-max/data/colors.csv +97 -0
  53. package/.jumpstart/skills/ui-ux-pro-max/data/icons.csv +101 -0
  54. package/.jumpstart/skills/ui-ux-pro-max/data/landing.csv +31 -0
  55. package/.jumpstart/skills/ui-ux-pro-max/data/products.csv +97 -0
  56. package/.jumpstart/skills/ui-ux-pro-max/data/react-performance.csv +45 -0
  57. package/.jumpstart/skills/ui-ux-pro-max/data/stacks/astro.csv +54 -0
  58. package/.jumpstart/skills/ui-ux-pro-max/data/stacks/flutter.csv +53 -0
  59. package/.jumpstart/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -0
  60. package/.jumpstart/skills/ui-ux-pro-max/data/stacks/jetpack-compose.csv +53 -0
  61. package/.jumpstart/skills/ui-ux-pro-max/data/stacks/nextjs.csv +53 -0
  62. package/.jumpstart/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -0
  63. package/.jumpstart/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -0
  64. package/.jumpstart/skills/ui-ux-pro-max/data/stacks/react-native.csv +52 -0
  65. package/.jumpstart/skills/ui-ux-pro-max/data/stacks/react.csv +54 -0
  66. package/.jumpstart/skills/ui-ux-pro-max/data/stacks/shadcn.csv +61 -0
  67. package/.jumpstart/skills/ui-ux-pro-max/data/stacks/svelte.csv +54 -0
  68. package/.jumpstart/skills/ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
  69. package/.jumpstart/skills/ui-ux-pro-max/data/stacks/vue.csv +50 -0
  70. package/.jumpstart/skills/ui-ux-pro-max/data/styles.csv +68 -0
  71. package/.jumpstart/skills/ui-ux-pro-max/data/typography.csv +58 -0
  72. package/.jumpstart/skills/ui-ux-pro-max/data/ui-reasoning.csv +101 -0
  73. package/.jumpstart/skills/ui-ux-pro-max/data/ux-guidelines.csv +100 -0
  74. package/.jumpstart/skills/ui-ux-pro-max/data/web-interface.csv +31 -0
  75. package/.jumpstart/skills/ui-ux-pro-max/scripts/core.py +253 -0
  76. package/.jumpstart/skills/ui-ux-pro-max/scripts/design_system.py +1067 -0
  77. package/.jumpstart/skills/ui-ux-pro-max/scripts/search.py +114 -0
  78. package/.jumpstart/state/timeline.json +659 -0
  79. package/.jumpstart/templates/model-map.md +1 -1
  80. package/.jumpstart/templates/ux-design.md +3 -3
  81. package/.jumpstart/usage-log.json +74 -3
  82. package/AGENTS.md +1 -1
  83. package/README.md +64 -3
  84. package/bin/cli.js +3217 -1
  85. package/bin/headless-runner.js +62 -2
  86. package/bin/lib/agent-checkpoint.js +168 -0
  87. package/bin/lib/ai-evaluation.js +104 -0
  88. package/bin/lib/ai-intake.js +152 -0
  89. package/bin/lib/ambiguity-heatmap.js +152 -0
  90. package/bin/lib/artifact-comparison.js +104 -0
  91. package/bin/lib/ast-edit-engine.js +157 -0
  92. package/bin/lib/backlog-sync.js +338 -0
  93. package/bin/lib/bcdr-planning.js +158 -0
  94. package/bin/lib/bidirectional-trace.js +199 -0
  95. package/bin/lib/branch-workflow.js +266 -0
  96. package/bin/lib/cab-output.js +119 -0
  97. package/bin/lib/chat-integration.js +122 -0
  98. package/bin/lib/ci-cd-integration.js +208 -0
  99. package/bin/lib/codebase-retrieval.js +125 -0
  100. package/bin/lib/collaboration.js +168 -0
  101. package/bin/lib/compliance-packs.js +213 -0
  102. package/bin/lib/context-chunker.js +128 -0
  103. package/bin/lib/context-onboarding.js +122 -0
  104. package/bin/lib/contract-first.js +124 -0
  105. package/bin/lib/cost-router.js +148 -0
  106. package/bin/lib/credential-boundary.js +155 -0
  107. package/bin/lib/data-classification.js +180 -0
  108. package/bin/lib/data-contracts.js +129 -0
  109. package/bin/lib/db-evolution.js +158 -0
  110. package/bin/lib/decision-conflicts.js +299 -0
  111. package/bin/lib/delivery-confidence.js +361 -0
  112. package/bin/lib/dependency-upgrade.js +153 -0
  113. package/bin/lib/design-system.js +133 -0
  114. package/bin/lib/deterministic-artifacts.js +151 -0
  115. package/bin/lib/diagram-studio.js +115 -0
  116. package/bin/lib/domain-ontology.js +140 -0
  117. package/bin/lib/ea-review-packet.js +151 -0
  118. package/bin/lib/enterprise-search.js +123 -0
  119. package/bin/lib/enterprise-templates.js +140 -0
  120. package/bin/lib/environment-promotion.js +220 -0
  121. package/bin/lib/estimation-studio.js +130 -0
  122. package/bin/lib/event-modeling.js +133 -0
  123. package/bin/lib/evidence-collector.js +179 -0
  124. package/bin/lib/finops-planner.js +182 -0
  125. package/bin/lib/fitness-functions.js +279 -0
  126. package/bin/lib/focus.js +448 -0
  127. package/bin/lib/governance-dashboard.js +165 -0
  128. package/bin/lib/guided-handoff.js +120 -0
  129. package/bin/lib/impact-analysis.js +190 -0
  130. package/bin/lib/incident-feedback.js +157 -0
  131. package/bin/lib/integrate.js +1 -1
  132. package/bin/lib/knowledge-graph.js +122 -0
  133. package/bin/lib/legacy-modernizer.js +160 -0
  134. package/bin/lib/migration-planner.js +144 -0
  135. package/bin/lib/model-governance.js +185 -0
  136. package/bin/lib/model-router.js +144 -0
  137. package/bin/lib/multi-repo.js +272 -0
  138. package/bin/lib/next-phase.js +53 -8
  139. package/bin/lib/ops-ownership.js +152 -0
  140. package/bin/lib/parallel-agents.js +257 -0
  141. package/bin/lib/pattern-library.js +115 -0
  142. package/bin/lib/persona-packs.js +99 -0
  143. package/bin/lib/plan-executor.js +366 -0
  144. package/bin/lib/platform-engineering.js +119 -0
  145. package/bin/lib/playback-summaries.js +126 -0
  146. package/bin/lib/policy-engine.js +240 -0
  147. package/bin/lib/portfolio-reporting.js +357 -0
  148. package/bin/lib/pr-package.js +197 -0
  149. package/bin/lib/project-memory.js +235 -0
  150. package/bin/lib/prompt-governance.js +130 -0
  151. package/bin/lib/promptless-mode.js +128 -0
  152. package/bin/lib/quality-graph.js +193 -0
  153. package/bin/lib/raci-matrix.js +188 -0
  154. package/bin/lib/refactor-planner.js +167 -0
  155. package/bin/lib/reference-architectures.js +304 -0
  156. package/bin/lib/release-readiness.js +171 -0
  157. package/bin/lib/repo-graph.js +262 -0
  158. package/bin/lib/requirements-baseline.js +358 -0
  159. package/bin/lib/risk-register.js +211 -0
  160. package/bin/lib/role-approval.js +249 -0
  161. package/bin/lib/role-views.js +142 -0
  162. package/bin/lib/root-cause-analysis.js +132 -0
  163. package/bin/lib/runtime-debugger.js +154 -0
  164. package/bin/lib/safe-rename.js +135 -0
  165. package/bin/lib/secret-scanner.js +313 -0
  166. package/bin/lib/semantic-diff.js +335 -0
  167. package/bin/lib/sla-slo.js +210 -0
  168. package/bin/lib/smoke-tester.js +344 -0
  169. package/bin/lib/spec-comments.js +147 -0
  170. package/bin/lib/spec-maturity.js +287 -0
  171. package/bin/lib/sre-integration.js +154 -0
  172. package/bin/lib/structured-elicitation.js +174 -0
  173. package/bin/lib/telemetry-feedback.js +118 -0
  174. package/bin/lib/test-generator.js +146 -0
  175. package/bin/lib/timeline.js +2 -1
  176. package/bin/lib/tool-bridge.js +159 -0
  177. package/bin/lib/tool-guardrails.js +139 -0
  178. package/bin/lib/tool-schemas.js +281 -3
  179. package/bin/lib/transcript-ingestion.js +150 -0
  180. package/bin/lib/type-checker.js +261 -0
  181. package/bin/lib/uat-coverage.js +411 -0
  182. package/bin/lib/vendor-risk.js +173 -0
  183. package/bin/lib/waiver-workflow.js +174 -0
  184. package/bin/lib/web-dashboard.js +126 -0
  185. package/bin/lib/workshop-mode.js +165 -0
  186. package/bin/lib/workstream-ownership.js +104 -0
  187. package/package.json +1 -1
  188. package/.github/agents/jumpstart-ux-designer.agent.md +0 -45
@@ -0,0 +1,179 @@
1
+ /**
2
+ * evidence-collector.js — Evidence Collection Automation (Item 25)
3
+ *
4
+ * Auto-package screenshots, logs, tests, policy checks,
5
+ * architecture diagrams, and approvals for audits.
6
+ *
7
+ * Usage:
8
+ * node bin/lib/evidence-collector.js collect|package|status [options]
9
+ *
10
+ * Output: .jumpstart/evidence/
11
+ */
12
+
13
+ 'use strict';
14
+
15
+ const fs = require('fs');
16
+ const path = require('path');
17
+
18
+ const DEFAULT_OUTPUT_DIR = path.join('.jumpstart', 'evidence');
19
+ const DEFAULT_STATE_FILE = path.join('.jumpstart', 'state', 'evidence.json');
20
+
21
+ const EVIDENCE_TYPES = ['test-results', 'approval-records', 'policy-checks', 'architecture-diagrams',
22
+ 'security-scans', 'coverage-reports', 'audit-logs', 'screenshots', 'compliance-checks'];
23
+
24
+ /**
25
+ * Default evidence state.
26
+ * @returns {object}
27
+ */
28
+ function defaultState() {
29
+ return {
30
+ version: '1.0.0',
31
+ created_at: new Date().toISOString(),
32
+ last_updated: null,
33
+ collections: [],
34
+ evidence_items: []
35
+ };
36
+ }
37
+
38
+ function loadState(stateFile) {
39
+ const filePath = stateFile || DEFAULT_STATE_FILE;
40
+ if (!fs.existsSync(filePath)) return defaultState();
41
+ try { return JSON.parse(fs.readFileSync(filePath, 'utf8')); }
42
+ catch { return defaultState(); }
43
+ }
44
+
45
+ function saveState(state, stateFile) {
46
+ const filePath = stateFile || DEFAULT_STATE_FILE;
47
+ const dir = path.dirname(filePath);
48
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
49
+ state.last_updated = new Date().toISOString();
50
+ fs.writeFileSync(filePath, JSON.stringify(state, null, 2) + '\n', 'utf8');
51
+ }
52
+
53
+ /**
54
+ * Collect evidence from the project.
55
+ *
56
+ * @param {string} root - Project root.
57
+ * @param {object} [options]
58
+ * @returns {object}
59
+ */
60
+ function collectEvidence(root, options = {}) {
61
+ const stateFile = options.stateFile || path.join(root, DEFAULT_STATE_FILE);
62
+ const state = loadState(stateFile);
63
+ const items = [];
64
+
65
+ // Collect approval records
66
+ const approvalFile = path.join(root, '.jumpstart', 'state', 'role-approvals.json');
67
+ if (fs.existsSync(approvalFile)) {
68
+ items.push({ type: 'approval-records', source: approvalFile, collected_at: new Date().toISOString() });
69
+ }
70
+
71
+ // Collect policy check results
72
+ const policyFile = path.join(root, '.jumpstart', 'policies.json');
73
+ if (fs.existsSync(policyFile)) {
74
+ items.push({ type: 'policy-checks', source: policyFile, collected_at: new Date().toISOString() });
75
+ }
76
+
77
+ // Collect spec artifacts as architecture evidence
78
+ const specsDir = path.join(root, 'specs');
79
+ if (fs.existsSync(specsDir)) {
80
+ const specs = fs.readdirSync(specsDir).filter(f => f.endsWith('.md'));
81
+ for (const spec of specs) {
82
+ items.push({ type: 'architecture-diagrams', source: path.join('specs', spec), collected_at: new Date().toISOString() });
83
+ }
84
+ }
85
+
86
+ // Collect test results if available
87
+ const testDirs = ['tests', 'test', '__tests__'];
88
+ for (const td of testDirs) {
89
+ const testDir = path.join(root, td);
90
+ if (fs.existsSync(testDir)) {
91
+ items.push({ type: 'test-results', source: td, collected_at: new Date().toISOString() });
92
+ break;
93
+ }
94
+ }
95
+
96
+ state.evidence_items.push(...items);
97
+ state.collections.push({
98
+ id: `ev-${Date.now()}`,
99
+ collected_at: new Date().toISOString(),
100
+ items_count: items.length,
101
+ types: [...new Set(items.map(i => i.type))]
102
+ });
103
+
104
+ saveState(state, stateFile);
105
+
106
+ return {
107
+ success: true,
108
+ items_collected: items.length,
109
+ types: [...new Set(items.map(i => i.type))],
110
+ collection_id: state.collections[state.collections.length - 1].id
111
+ };
112
+ }
113
+
114
+ /**
115
+ * Package evidence into an audit-ready bundle.
116
+ *
117
+ * @param {string} root - Project root.
118
+ * @param {object} [options]
119
+ * @returns {object}
120
+ */
121
+ function packageEvidence(root, options = {}) {
122
+ const stateFile = options.stateFile || path.join(root, DEFAULT_STATE_FILE);
123
+ const state = loadState(stateFile);
124
+ const outputDir = options.outputDir || path.join(root, DEFAULT_OUTPUT_DIR);
125
+
126
+ if (!fs.existsSync(outputDir)) fs.mkdirSync(outputDir, { recursive: true });
127
+
128
+ const manifest = {
129
+ package_id: `audit-${Date.now()}`,
130
+ created_at: new Date().toISOString(),
131
+ project_root: root,
132
+ total_items: state.evidence_items.length,
133
+ types: [...new Set(state.evidence_items.map(i => i.type))],
134
+ collections: state.collections.length,
135
+ items: state.evidence_items
136
+ };
137
+
138
+ const manifestPath = path.join(outputDir, 'audit-manifest.json');
139
+ fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + '\n', 'utf8');
140
+
141
+ return {
142
+ success: true,
143
+ package_id: manifest.package_id,
144
+ output: manifestPath,
145
+ total_items: manifest.total_items,
146
+ types: manifest.types
147
+ };
148
+ }
149
+
150
+ /**
151
+ * Get evidence collection status.
152
+ *
153
+ * @param {object} [options]
154
+ * @returns {object}
155
+ */
156
+ function getStatus(options = {}) {
157
+ const stateFile = options.stateFile || DEFAULT_STATE_FILE;
158
+ const state = loadState(stateFile);
159
+
160
+ return {
161
+ success: true,
162
+ total_items: state.evidence_items.length,
163
+ collections: state.collections.length,
164
+ types: [...new Set(state.evidence_items.map(i => i.type))],
165
+ last_collection: state.collections.length > 0
166
+ ? state.collections[state.collections.length - 1]
167
+ : null
168
+ };
169
+ }
170
+
171
+ module.exports = {
172
+ defaultState,
173
+ loadState,
174
+ saveState,
175
+ collectEvidence,
176
+ packageEvidence,
177
+ getStatus,
178
+ EVIDENCE_TYPES
179
+ };
@@ -0,0 +1,182 @@
1
+ /**
2
+ * finops-planner.js — FinOps-Aware Architecture Planning (Item 35)
3
+ *
4
+ * Produce estimated run costs and optimization recommendations
5
+ * before implementation.
6
+ *
7
+ * Usage:
8
+ * node bin/lib/finops-planner.js estimate|optimize|report [options]
9
+ *
10
+ * State file: .jumpstart/state/finops.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', 'finops.json');
19
+
20
+ const COST_CATEGORIES = ['compute', 'storage', 'network', 'database', 'ai-ml', 'monitoring', 'third-party', 'licensing'];
21
+
22
+ const CLOUD_PRICING_ESTIMATES = {
23
+ compute: { unit: 'vCPU-hour', low: 0.02, medium: 0.05, high: 0.10 },
24
+ storage: { unit: 'GB-month', low: 0.01, medium: 0.023, high: 0.10 },
25
+ network: { unit: 'GB-transfer', low: 0.01, medium: 0.08, high: 0.12 },
26
+ database: { unit: 'instance-hour', low: 0.02, medium: 0.10, high: 0.50 },
27
+ 'ai-ml': { unit: '1K-tokens', low: 0.001, medium: 0.01, high: 0.06 },
28
+ monitoring: { unit: 'GB-logs-month', low: 0.25, medium: 0.50, high: 1.00 }
29
+ };
30
+
31
+ function defaultState() {
32
+ return {
33
+ version: '1.0.0',
34
+ created_at: new Date().toISOString(),
35
+ last_updated: null,
36
+ estimates: [],
37
+ budgets: [],
38
+ optimizations: []
39
+ };
40
+ }
41
+
42
+ function loadState(stateFile) {
43
+ const filePath = stateFile || DEFAULT_STATE_FILE;
44
+ if (!fs.existsSync(filePath)) return defaultState();
45
+ try { return JSON.parse(fs.readFileSync(filePath, 'utf8')); }
46
+ catch { return defaultState(); }
47
+ }
48
+
49
+ function saveState(state, stateFile) {
50
+ const filePath = stateFile || DEFAULT_STATE_FILE;
51
+ const dir = path.dirname(filePath);
52
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
53
+ state.last_updated = new Date().toISOString();
54
+ fs.writeFileSync(filePath, JSON.stringify(state, null, 2) + '\n', 'utf8');
55
+ }
56
+
57
+ /**
58
+ * Create a cost estimate for a service/component.
59
+ *
60
+ * @param {object} estimate - { name, components[] }
61
+ * @param {object} [options]
62
+ * @returns {object}
63
+ */
64
+ function createEstimate(estimate, options = {}) {
65
+ if (!estimate || !estimate.name) {
66
+ return { success: false, error: 'estimate.name is required' };
67
+ }
68
+
69
+ const components = estimate.components || [];
70
+ let totalMonthly = 0;
71
+ const breakdown = [];
72
+
73
+ for (const comp of components) {
74
+ const category = comp.category || 'compute';
75
+ const pricing = CLOUD_PRICING_ESTIMATES[category];
76
+ const tier = comp.tier || 'medium';
77
+ const quantity = comp.quantity || 1;
78
+ const hours = comp.hours_per_month || 730; // ~24*30
79
+
80
+ let monthlyCost;
81
+ if (pricing) {
82
+ const rate = pricing[tier] || pricing.medium;
83
+ monthlyCost = rate * quantity * (category === 'storage' || category === 'monitoring' ? 1 : hours);
84
+ } else {
85
+ monthlyCost = comp.monthly_cost || 0;
86
+ }
87
+
88
+ breakdown.push({
89
+ name: comp.name || category,
90
+ category,
91
+ quantity,
92
+ monthly_cost: Math.round(monthlyCost * 100) / 100
93
+ });
94
+ totalMonthly += monthlyCost;
95
+ }
96
+
97
+ const stateFile = options.stateFile || DEFAULT_STATE_FILE;
98
+ const state = loadState(stateFile);
99
+
100
+ const est = {
101
+ id: `FIN-${Date.now().toString(36).toUpperCase()}`,
102
+ name: estimate.name,
103
+ breakdown,
104
+ monthly_total: Math.round(totalMonthly * 100) / 100,
105
+ annual_total: Math.round(totalMonthly * 12 * 100) / 100,
106
+ created_at: new Date().toISOString()
107
+ };
108
+
109
+ state.estimates.push(est);
110
+ saveState(state, stateFile);
111
+
112
+ return { success: true, estimate: est };
113
+ }
114
+
115
+ /**
116
+ * Get optimization recommendations.
117
+ *
118
+ * @param {object} [options]
119
+ * @returns {object}
120
+ */
121
+ function getOptimizations(options = {}) {
122
+ const stateFile = options.stateFile || DEFAULT_STATE_FILE;
123
+ const state = loadState(stateFile);
124
+
125
+ const recommendations = [];
126
+
127
+ for (const est of state.estimates) {
128
+ for (const comp of est.breakdown) {
129
+ if (comp.category === 'compute' && comp.monthly_cost > 500) {
130
+ recommendations.push({ estimate: est.name, component: comp.name, recommendation: 'Consider reserved instances or spot instances', potential_savings: '30-60%' });
131
+ }
132
+ if (comp.category === 'storage' && comp.monthly_cost > 100) {
133
+ recommendations.push({ estimate: est.name, component: comp.name, recommendation: 'Implement storage tiering (hot/warm/cold)', potential_savings: '20-40%' });
134
+ }
135
+ if (comp.category === 'ai-ml' && comp.monthly_cost > 200) {
136
+ recommendations.push({ estimate: est.name, component: comp.name, recommendation: 'Use smaller models for simple tasks, batch requests', potential_savings: '40-70%' });
137
+ }
138
+ }
139
+ }
140
+
141
+ return { success: true, recommendations, total: recommendations.length };
142
+ }
143
+
144
+ /**
145
+ * Generate FinOps report.
146
+ *
147
+ * @param {object} [options]
148
+ * @returns {object}
149
+ */
150
+ function generateReport(options = {}) {
151
+ const stateFile = options.stateFile || DEFAULT_STATE_FILE;
152
+ const state = loadState(stateFile);
153
+
154
+ const totalMonthly = state.estimates.reduce((sum, e) => sum + e.monthly_total, 0);
155
+ const byCategory = {};
156
+
157
+ for (const est of state.estimates) {
158
+ for (const comp of est.breakdown) {
159
+ byCategory[comp.category] = (byCategory[comp.category] || 0) + comp.monthly_cost;
160
+ }
161
+ }
162
+
163
+ return {
164
+ success: true,
165
+ total_estimates: state.estimates.length,
166
+ total_monthly: Math.round(totalMonthly * 100) / 100,
167
+ total_annual: Math.round(totalMonthly * 12 * 100) / 100,
168
+ by_category: byCategory,
169
+ estimates: state.estimates
170
+ };
171
+ }
172
+
173
+ module.exports = {
174
+ defaultState,
175
+ loadState,
176
+ saveState,
177
+ createEstimate,
178
+ getOptimizations,
179
+ generateReport,
180
+ COST_CATEGORIES,
181
+ CLOUD_PRICING_ESTIMATES
182
+ };
@@ -0,0 +1,279 @@
1
+ /**
2
+ * fitness-functions.js — Architectural Fitness Functions
3
+ *
4
+ * Continuously evaluate the implemented system against architecture
5
+ * constraints and NFRs.
6
+ *
7
+ * Config: .jumpstart/fitness-functions.json
8
+ *
9
+ * Usage:
10
+ * node bin/lib/fitness-functions.js evaluate|add|list|report [options]
11
+ */
12
+
13
+ 'use strict';
14
+
15
+ const fs = require('fs');
16
+ const path = require('path');
17
+
18
+ const DEFAULT_FITNESS_FILE = path.join('.jumpstart', 'fitness-functions.json');
19
+
20
+ const FITNESS_CATEGORIES = ['dependency', 'structure', 'complexity', 'naming', 'security', 'performance', 'testing'];
21
+
22
+ /**
23
+ * Default fitness function registry.
24
+ * @returns {object}
25
+ */
26
+ function defaultRegistry() {
27
+ return {
28
+ version: '1.0.0',
29
+ created_at: new Date().toISOString(),
30
+ last_evaluated: null,
31
+ functions: [],
32
+ evaluation_history: []
33
+ };
34
+ }
35
+
36
+ /**
37
+ * Load fitness function registry from disk.
38
+ * @param {string} [registryFile]
39
+ * @returns {object}
40
+ */
41
+ function loadRegistry(registryFile) {
42
+ const filePath = registryFile || DEFAULT_FITNESS_FILE;
43
+ if (!fs.existsSync(filePath)) {
44
+ return defaultRegistry();
45
+ }
46
+ try {
47
+ return JSON.parse(fs.readFileSync(filePath, 'utf8'));
48
+ } catch {
49
+ return defaultRegistry();
50
+ }
51
+ }
52
+
53
+ /**
54
+ * Save fitness function registry to disk.
55
+ * @param {object} registry
56
+ * @param {string} [registryFile]
57
+ */
58
+ function saveRegistry(registry, registryFile) {
59
+ const filePath = registryFile || DEFAULT_FITNESS_FILE;
60
+ const dir = path.dirname(filePath);
61
+ if (!fs.existsSync(dir)) {
62
+ fs.mkdirSync(dir, { recursive: true });
63
+ }
64
+ fs.writeFileSync(filePath, JSON.stringify(registry, null, 2) + '\n', 'utf8');
65
+ }
66
+
67
+ /**
68
+ * Add a fitness function.
69
+ *
70
+ * @param {object} func - { id, name, category, description, check_type, pattern?, threshold?, target_dirs? }
71
+ * @param {object} [options]
72
+ * @returns {object}
73
+ */
74
+ function addFitnessFunction(func, options = {}) {
75
+ if (!func || !func.name || !func.description) {
76
+ return { success: false, error: 'name and description are required' };
77
+ }
78
+
79
+ const category = (func.category || 'structure').toLowerCase();
80
+ if (!FITNESS_CATEGORIES.includes(category)) {
81
+ return { success: false, error: `category must be one of: ${FITNESS_CATEGORIES.join(', ')}` };
82
+ }
83
+
84
+ const registryFile = options.registryFile || DEFAULT_FITNESS_FILE;
85
+ const registry = loadRegistry(registryFile);
86
+
87
+ const id = func.id || `ff-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`;
88
+ if (registry.functions.find(f => f.id === id)) {
89
+ return { success: false, error: `Fitness function "${id}" already exists` };
90
+ }
91
+
92
+ const newFunc = {
93
+ id,
94
+ name: func.name.trim(),
95
+ category,
96
+ description: func.description.trim(),
97
+ check_type: func.check_type || 'pattern',
98
+ pattern: func.pattern || null,
99
+ threshold: func.threshold != null ? func.threshold : null,
100
+ target_dirs: func.target_dirs || ['src'],
101
+ enabled: func.enabled !== false,
102
+ created_at: new Date().toISOString()
103
+ };
104
+
105
+ registry.functions.push(newFunc);
106
+ saveRegistry(registry, registryFile);
107
+
108
+ return { success: true, function: newFunc, total: registry.functions.length };
109
+ }
110
+
111
+ /**
112
+ * Built-in fitness checks.
113
+ */
114
+ const BUILTIN_CHECKS = {
115
+ max_file_length: (content, threshold) => {
116
+ const lineCount = content.split('\n').length;
117
+ return { passed: lineCount <= (threshold || 500), value: lineCount, threshold: threshold || 500 };
118
+ },
119
+ no_circular_imports: (content) => {
120
+ const imports = (content.match(/(?:require|import)\s*\(?['"]([^'"]+)['"]\)?/g) || []);
121
+ return { passed: true, value: imports.length, note: 'static check only' };
122
+ },
123
+ max_function_params: (content, threshold) => {
124
+ const funcPattern = /function\s+\w+\s*\(([^)]*)\)/g;
125
+ const arrowPattern = /\(([^)]*)\)\s*(?:=>|{)/g;
126
+ let maxParams = 0;
127
+ let match;
128
+ while ((match = funcPattern.exec(content)) !== null) {
129
+ const params = match[1].split(',').filter(p => p.trim().length > 0).length;
130
+ if (params > maxParams) maxParams = params;
131
+ }
132
+ while ((match = arrowPattern.exec(content)) !== null) {
133
+ const params = match[1].split(',').filter(p => p.trim().length > 0).length;
134
+ if (params > maxParams) maxParams = params;
135
+ }
136
+ return { passed: maxParams <= (threshold || 5), value: maxParams, threshold: threshold || 5 };
137
+ },
138
+ pattern_match: (content, _threshold, pattern) => {
139
+ if (!pattern) return { passed: true, value: 0 };
140
+ try {
141
+ const regex = new RegExp(pattern, 'gi');
142
+ const matches = content.match(regex) || [];
143
+ return { passed: matches.length === 0, value: matches.length, pattern };
144
+ } catch {
145
+ return { passed: true, value: 0, error: 'invalid regex' };
146
+ }
147
+ }
148
+ };
149
+
150
+ /**
151
+ * Evaluate all fitness functions against the project.
152
+ *
153
+ * @param {string} root - Project root.
154
+ * @param {object} [options]
155
+ * @returns {object}
156
+ */
157
+ function evaluateFitness(root, options = {}) {
158
+ const registryFile = options.registryFile || path.join(root, DEFAULT_FITNESS_FILE);
159
+ const registry = loadRegistry(registryFile);
160
+
161
+ const enabledFuncs = registry.functions.filter(f => f.enabled !== false);
162
+ const results = [];
163
+
164
+ for (const func of enabledFuncs) {
165
+ const violations = [];
166
+ const targetDirs = func.target_dirs || ['src'];
167
+
168
+ for (const dir of targetDirs) {
169
+ const absDir = path.join(root, dir);
170
+ if (!fs.existsSync(absDir)) continue;
171
+
172
+ const walk = (d) => {
173
+ for (const entry of fs.readdirSync(d, { withFileTypes: true })) {
174
+ if (entry.name.startsWith('.') || entry.name === 'node_modules') continue;
175
+ const full = path.join(d, entry.name);
176
+ if (entry.isDirectory()) {
177
+ walk(full);
178
+ } else if (entry.isFile() && /\.(js|ts|jsx|tsx|py|go|java|rb|rs)$/.test(entry.name)) {
179
+ try {
180
+ const content = fs.readFileSync(full, 'utf8');
181
+ const rel = path.relative(root, full).replace(/\\/g, '/');
182
+ let checkResult;
183
+
184
+ if (func.check_type === 'max_file_length') {
185
+ checkResult = BUILTIN_CHECKS.max_file_length(content, func.threshold);
186
+ } else if (func.check_type === 'max_function_params') {
187
+ checkResult = BUILTIN_CHECKS.max_function_params(content, func.threshold);
188
+ } else if (func.check_type === 'pattern' && func.pattern) {
189
+ checkResult = BUILTIN_CHECKS.pattern_match(content, func.threshold, func.pattern);
190
+ } else {
191
+ checkResult = { passed: true, value: 0 };
192
+ }
193
+
194
+ if (!checkResult.passed) {
195
+ violations.push({
196
+ file: rel,
197
+ ...checkResult
198
+ });
199
+ }
200
+ } catch {
201
+ // skip unreadable files
202
+ }
203
+ }
204
+ }
205
+ };
206
+ walk(absDir);
207
+ }
208
+
209
+ results.push({
210
+ id: func.id,
211
+ name: func.name,
212
+ category: func.category,
213
+ passed: violations.length === 0,
214
+ violations: violations.length,
215
+ details: violations.slice(0, 10)
216
+ });
217
+ }
218
+
219
+ const allPassed = results.every(r => r.passed);
220
+ const evaluation = {
221
+ evaluated_at: new Date().toISOString(),
222
+ total_functions: results.length,
223
+ passed: results.filter(r => r.passed).length,
224
+ failed: results.filter(r => !r.passed).length,
225
+ all_passed: allPassed
226
+ };
227
+
228
+ registry.last_evaluated = evaluation.evaluated_at;
229
+ registry.evaluation_history.push(evaluation);
230
+ if (registry.evaluation_history.length > 50) {
231
+ registry.evaluation_history = registry.evaluation_history.slice(-50);
232
+ }
233
+ saveRegistry(registry, registryFile);
234
+
235
+ return {
236
+ success: true,
237
+ all_passed: allPassed,
238
+ results,
239
+ summary: evaluation
240
+ };
241
+ }
242
+
243
+ /**
244
+ * List registered fitness functions.
245
+ *
246
+ * @param {object} [filter]
247
+ * @param {object} [options]
248
+ * @returns {object}
249
+ */
250
+ function listFitnessFunctions(filter = {}, options = {}) {
251
+ const registryFile = options.registryFile || DEFAULT_FITNESS_FILE;
252
+ const registry = loadRegistry(registryFile);
253
+
254
+ let functions = registry.functions;
255
+ if (filter.category) {
256
+ functions = functions.filter(f => f.category === filter.category);
257
+ }
258
+ if (filter.enabled !== undefined) {
259
+ functions = functions.filter(f => (f.enabled !== false) === filter.enabled);
260
+ }
261
+
262
+ return {
263
+ success: true,
264
+ functions,
265
+ total: functions.length,
266
+ last_evaluated: registry.last_evaluated
267
+ };
268
+ }
269
+
270
+ module.exports = {
271
+ defaultRegistry,
272
+ loadRegistry,
273
+ saveRegistry,
274
+ addFitnessFunction,
275
+ evaluateFitness,
276
+ listFitnessFunctions,
277
+ BUILTIN_CHECKS,
278
+ FITNESS_CATEGORIES
279
+ };