jumpstart-mode 1.1.11 → 1.1.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/agents/jumpstart-adversary.agent.md +2 -1
- package/.github/agents/jumpstart-architect.agent.md +6 -7
- package/.github/agents/jumpstart-challenger.agent.md +2 -1
- package/.github/agents/jumpstart-developer.agent.md +1 -1
- package/.github/agents/jumpstart-devops.agent.md +2 -2
- package/.github/agents/jumpstart-diagram-verifier.agent.md +2 -1
- package/.github/agents/jumpstart-maintenance.agent.md +1 -0
- package/.github/agents/jumpstart-performance.agent.md +1 -0
- package/.github/agents/jumpstart-pm.agent.md +1 -1
- package/.github/agents/jumpstart-refactor.agent.md +1 -0
- package/.github/agents/jumpstart-requirements-extractor.agent.md +1 -0
- package/.github/agents/jumpstart-researcher.agent.md +1 -0
- package/.github/agents/jumpstart-retrospective.agent.md +1 -0
- package/.github/agents/jumpstart-reviewer.agent.md +2 -0
- package/.github/agents/jumpstart-scout.agent.md +1 -1
- package/.github/agents/jumpstart-scrum-master.agent.md +1 -0
- package/.github/agents/jumpstart-security.agent.md +2 -1
- package/.github/agents/jumpstart-tech-writer.agent.md +1 -0
- package/.github/agents/jumpstart-uiux-designer.agent.md +66 -0
- package/.github/workflows/quality.yml +19 -2
- package/.jumpstart/agents/analyst.md +38 -0
- package/.jumpstart/agents/architect.md +39 -1
- package/.jumpstart/agents/challenger.md +38 -0
- package/.jumpstart/agents/developer.md +41 -0
- package/.jumpstart/agents/pm.md +38 -0
- package/.jumpstart/agents/scout.md +33 -0
- package/.jumpstart/agents/ux-designer.md +29 -9
- package/.jumpstart/commands/commands.md +6 -5
- package/.jumpstart/config.yaml +25 -1
- package/.jumpstart/roadmap.md +1 -1
- package/.jumpstart/schemas/timeline.schema.json +1 -0
- package/.jumpstart/skills/README.md +1 -0
- package/.jumpstart/skills/quality-gates/SKILL.md +126 -0
- package/.jumpstart/skills/skill-creator/SKILL.md +485 -357
- package/.jumpstart/skills/skill-creator/agents/analyzer.md +274 -0
- package/.jumpstart/skills/skill-creator/agents/comparator.md +202 -0
- package/.jumpstart/skills/skill-creator/agents/grader.md +223 -0
- package/.jumpstart/skills/skill-creator/assets/eval_review.html +146 -0
- package/.jumpstart/skills/skill-creator/eval-viewer/generate_review.py +471 -0
- package/.jumpstart/skills/skill-creator/eval-viewer/viewer.html +1325 -0
- package/.jumpstart/skills/skill-creator/references/schemas.md +430 -0
- package/.jumpstart/skills/skill-creator/scripts/__init__.py +0 -0
- package/.jumpstart/skills/skill-creator/scripts/aggregate_benchmark.py +401 -0
- package/.jumpstart/skills/skill-creator/scripts/generate_report.py +326 -0
- package/.jumpstart/skills/skill-creator/scripts/improve_description.py +247 -0
- package/.jumpstart/skills/skill-creator/scripts/package_skill.py +136 -110
- package/.jumpstart/skills/skill-creator/scripts/run_eval.py +310 -0
- package/.jumpstart/skills/skill-creator/scripts/run_loop.py +328 -0
- package/.jumpstart/skills/skill-creator/scripts/utils.py +47 -0
- package/.jumpstart/skills/ui-ux-pro-max/SKILL.md +266 -0
- package/.jumpstart/skills/ui-ux-pro-max/data/charts.csv +26 -0
- package/.jumpstart/skills/ui-ux-pro-max/data/colors.csv +97 -0
- package/.jumpstart/skills/ui-ux-pro-max/data/icons.csv +101 -0
- package/.jumpstart/skills/ui-ux-pro-max/data/landing.csv +31 -0
- package/.jumpstart/skills/ui-ux-pro-max/data/products.csv +97 -0
- package/.jumpstart/skills/ui-ux-pro-max/data/react-performance.csv +45 -0
- package/.jumpstart/skills/ui-ux-pro-max/data/stacks/astro.csv +54 -0
- package/.jumpstart/skills/ui-ux-pro-max/data/stacks/flutter.csv +53 -0
- package/.jumpstart/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -0
- package/.jumpstart/skills/ui-ux-pro-max/data/stacks/jetpack-compose.csv +53 -0
- package/.jumpstart/skills/ui-ux-pro-max/data/stacks/nextjs.csv +53 -0
- package/.jumpstart/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -0
- package/.jumpstart/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -0
- package/.jumpstart/skills/ui-ux-pro-max/data/stacks/react-native.csv +52 -0
- package/.jumpstart/skills/ui-ux-pro-max/data/stacks/react.csv +54 -0
- package/.jumpstart/skills/ui-ux-pro-max/data/stacks/shadcn.csv +61 -0
- package/.jumpstart/skills/ui-ux-pro-max/data/stacks/svelte.csv +54 -0
- package/.jumpstart/skills/ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
- package/.jumpstart/skills/ui-ux-pro-max/data/stacks/vue.csv +50 -0
- package/.jumpstart/skills/ui-ux-pro-max/data/styles.csv +68 -0
- package/.jumpstart/skills/ui-ux-pro-max/data/typography.csv +58 -0
- package/.jumpstart/skills/ui-ux-pro-max/data/ui-reasoning.csv +101 -0
- package/.jumpstart/skills/ui-ux-pro-max/data/ux-guidelines.csv +100 -0
- package/.jumpstart/skills/ui-ux-pro-max/data/web-interface.csv +31 -0
- package/.jumpstart/skills/ui-ux-pro-max/scripts/core.py +253 -0
- package/.jumpstart/skills/ui-ux-pro-max/scripts/design_system.py +1067 -0
- package/.jumpstart/skills/ui-ux-pro-max/scripts/search.py +114 -0
- package/.jumpstart/state/timeline.json +659 -0
- package/.jumpstart/templates/model-map.md +1 -1
- package/.jumpstart/templates/ux-design.md +3 -3
- package/.jumpstart/usage-log.json +74 -3
- package/AGENTS.md +1 -1
- package/README.md +64 -3
- package/bin/cli.js +3217 -1
- package/bin/headless-runner.js +62 -2
- package/bin/lib/agent-checkpoint.js +168 -0
- package/bin/lib/ai-evaluation.js +104 -0
- package/bin/lib/ai-intake.js +152 -0
- package/bin/lib/ambiguity-heatmap.js +152 -0
- package/bin/lib/artifact-comparison.js +104 -0
- package/bin/lib/ast-edit-engine.js +157 -0
- package/bin/lib/backlog-sync.js +338 -0
- package/bin/lib/bcdr-planning.js +158 -0
- package/bin/lib/bidirectional-trace.js +199 -0
- package/bin/lib/branch-workflow.js +266 -0
- package/bin/lib/cab-output.js +119 -0
- package/bin/lib/chat-integration.js +122 -0
- package/bin/lib/ci-cd-integration.js +208 -0
- package/bin/lib/codebase-retrieval.js +125 -0
- package/bin/lib/collaboration.js +168 -0
- package/bin/lib/compliance-packs.js +213 -0
- package/bin/lib/context-chunker.js +128 -0
- package/bin/lib/context-onboarding.js +122 -0
- package/bin/lib/contract-first.js +124 -0
- package/bin/lib/cost-router.js +148 -0
- package/bin/lib/credential-boundary.js +155 -0
- package/bin/lib/data-classification.js +180 -0
- package/bin/lib/data-contracts.js +129 -0
- package/bin/lib/db-evolution.js +158 -0
- package/bin/lib/decision-conflicts.js +299 -0
- package/bin/lib/delivery-confidence.js +361 -0
- package/bin/lib/dependency-upgrade.js +153 -0
- package/bin/lib/design-system.js +133 -0
- package/bin/lib/deterministic-artifacts.js +151 -0
- package/bin/lib/diagram-studio.js +115 -0
- package/bin/lib/domain-ontology.js +140 -0
- package/bin/lib/ea-review-packet.js +151 -0
- package/bin/lib/enterprise-search.js +123 -0
- package/bin/lib/enterprise-templates.js +140 -0
- package/bin/lib/environment-promotion.js +220 -0
- package/bin/lib/estimation-studio.js +130 -0
- package/bin/lib/event-modeling.js +133 -0
- package/bin/lib/evidence-collector.js +179 -0
- package/bin/lib/finops-planner.js +182 -0
- package/bin/lib/fitness-functions.js +279 -0
- package/bin/lib/focus.js +448 -0
- package/bin/lib/governance-dashboard.js +165 -0
- package/bin/lib/guided-handoff.js +120 -0
- package/bin/lib/impact-analysis.js +190 -0
- package/bin/lib/incident-feedback.js +157 -0
- package/bin/lib/integrate.js +1 -1
- package/bin/lib/knowledge-graph.js +122 -0
- package/bin/lib/legacy-modernizer.js +160 -0
- package/bin/lib/migration-planner.js +144 -0
- package/bin/lib/model-governance.js +185 -0
- package/bin/lib/model-router.js +144 -0
- package/bin/lib/multi-repo.js +272 -0
- package/bin/lib/next-phase.js +53 -8
- package/bin/lib/ops-ownership.js +152 -0
- package/bin/lib/parallel-agents.js +257 -0
- package/bin/lib/pattern-library.js +115 -0
- package/bin/lib/persona-packs.js +99 -0
- package/bin/lib/plan-executor.js +366 -0
- package/bin/lib/platform-engineering.js +119 -0
- package/bin/lib/playback-summaries.js +126 -0
- package/bin/lib/policy-engine.js +240 -0
- package/bin/lib/portfolio-reporting.js +357 -0
- package/bin/lib/pr-package.js +197 -0
- package/bin/lib/project-memory.js +235 -0
- package/bin/lib/prompt-governance.js +130 -0
- package/bin/lib/promptless-mode.js +128 -0
- package/bin/lib/quality-graph.js +193 -0
- package/bin/lib/raci-matrix.js +188 -0
- package/bin/lib/refactor-planner.js +167 -0
- package/bin/lib/reference-architectures.js +304 -0
- package/bin/lib/release-readiness.js +171 -0
- package/bin/lib/repo-graph.js +262 -0
- package/bin/lib/requirements-baseline.js +358 -0
- package/bin/lib/risk-register.js +211 -0
- package/bin/lib/role-approval.js +249 -0
- package/bin/lib/role-views.js +142 -0
- package/bin/lib/root-cause-analysis.js +132 -0
- package/bin/lib/runtime-debugger.js +154 -0
- package/bin/lib/safe-rename.js +135 -0
- package/bin/lib/secret-scanner.js +313 -0
- package/bin/lib/semantic-diff.js +335 -0
- package/bin/lib/sla-slo.js +210 -0
- package/bin/lib/smoke-tester.js +344 -0
- package/bin/lib/spec-comments.js +147 -0
- package/bin/lib/spec-maturity.js +287 -0
- package/bin/lib/sre-integration.js +154 -0
- package/bin/lib/structured-elicitation.js +174 -0
- package/bin/lib/telemetry-feedback.js +118 -0
- package/bin/lib/test-generator.js +146 -0
- package/bin/lib/timeline.js +2 -1
- package/bin/lib/tool-bridge.js +159 -0
- package/bin/lib/tool-guardrails.js +139 -0
- package/bin/lib/tool-schemas.js +281 -3
- package/bin/lib/transcript-ingestion.js +150 -0
- package/bin/lib/type-checker.js +261 -0
- package/bin/lib/uat-coverage.js +411 -0
- package/bin/lib/vendor-risk.js +173 -0
- package/bin/lib/waiver-workflow.js +174 -0
- package/bin/lib/web-dashboard.js +126 -0
- package/bin/lib/workshop-mode.js +165 -0
- package/bin/lib/workstream-ownership.js +104 -0
- package/package.json +1 -1
- package/.github/agents/jumpstart-ux-designer.agent.md +0 -45
|
@@ -0,0 +1,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
|
+
};
|