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
package/bin/headless-runner.js
CHANGED
|
@@ -37,6 +37,10 @@ const { getToolsForPhase } = require('./lib/tool-schemas');
|
|
|
37
37
|
const { createMockRegistry, createPersonaRegistry } = require('./lib/mock-responses');
|
|
38
38
|
const { SimulationTracer } = require('./lib/simulation-tracer');
|
|
39
39
|
|
|
40
|
+
// Usage & timeline logging (ESM — loaded dynamically)
|
|
41
|
+
let _usageMod = null;
|
|
42
|
+
const _usageReady = import('./lib/usage.js').then(mod => { _usageMod = mod; }).catch(() => {});
|
|
43
|
+
|
|
40
44
|
// ─── Configuration ───────────────────────────────────────────────────────────
|
|
41
45
|
|
|
42
46
|
const ROOT_DIR = path.join(__dirname, '..');
|
|
@@ -182,6 +186,9 @@ class HeadlessRunner {
|
|
|
182
186
|
// Initialize tracer
|
|
183
187
|
this.tracer = new SimulationTracer(this.workspaceDir, options.scenario || 'headless');
|
|
184
188
|
|
|
189
|
+
// Usage log path
|
|
190
|
+
this.usageLogPath = path.join(this.workspaceDir, '.jumpstart', 'usage-log.json');
|
|
191
|
+
|
|
185
192
|
// Initialize timeline for event recording
|
|
186
193
|
this.timeline = null;
|
|
187
194
|
try {
|
|
@@ -199,6 +206,10 @@ class HeadlessRunner {
|
|
|
199
206
|
captureSubagents: true,
|
|
200
207
|
captureResearch: true
|
|
201
208
|
});
|
|
209
|
+
// Connect timeline to usage logger so usage events appear in timeline
|
|
210
|
+
if (_usageMod && typeof _usageMod.setUsageTimelineHook === 'function') {
|
|
211
|
+
_usageMod.setUsageTimelineHook(this.timeline);
|
|
212
|
+
}
|
|
202
213
|
}).catch(() => { /* timeline module not available — ok */ });
|
|
203
214
|
} catch {
|
|
204
215
|
this._timelineReady = Promise.resolve();
|
|
@@ -234,8 +245,9 @@ class HeadlessRunner {
|
|
|
234
245
|
}
|
|
235
246
|
|
|
236
247
|
async setup() {
|
|
237
|
-
// Ensure timeline
|
|
248
|
+
// Ensure timeline and usage modules are ready
|
|
238
249
|
if (this._timelineReady) await this._timelineReady;
|
|
250
|
+
await _usageReady;
|
|
239
251
|
|
|
240
252
|
// Create workspace directory structure
|
|
241
253
|
const dirs = [
|
|
@@ -518,6 +530,20 @@ Be brief and supportive.`;
|
|
|
518
530
|
const agentPrompt = this.loadAgentPrompt(agentName);
|
|
519
531
|
const personaPrompt = this.loadPersonaPrompt();
|
|
520
532
|
|
|
533
|
+
// Log agent system prompt to timeline
|
|
534
|
+
if (this.timeline) {
|
|
535
|
+
this.timeline.recordEvent({
|
|
536
|
+
event_type: 'prompt_logged',
|
|
537
|
+
action: `System prompt loaded for ${agentName} (${agentPrompt.length} chars)`,
|
|
538
|
+
metadata: {
|
|
539
|
+
prompt_type: 'system',
|
|
540
|
+
agent: agentName,
|
|
541
|
+
prompt_length: agentPrompt.length,
|
|
542
|
+
prompt_preview: agentPrompt.substring(0, 200) + (agentPrompt.length > 200 ? '…' : '')
|
|
543
|
+
}
|
|
544
|
+
});
|
|
545
|
+
}
|
|
546
|
+
|
|
521
547
|
// Initialize conversation histories
|
|
522
548
|
this.conversationHistory = [
|
|
523
549
|
{ role: 'system', content: agentPrompt }
|
|
@@ -534,6 +560,20 @@ Be brief and supportive.`;
|
|
|
534
560
|
const startMessage = this.getAgentStartMessage(agentName);
|
|
535
561
|
this.conversationHistory.push({ role: 'user', content: startMessage });
|
|
536
562
|
|
|
563
|
+
// Log activation prompt to timeline
|
|
564
|
+
if (this.timeline) {
|
|
565
|
+
this.timeline.recordEvent({
|
|
566
|
+
event_type: 'prompt_logged',
|
|
567
|
+
action: `Activation prompt sent to ${agentName}`,
|
|
568
|
+
metadata: {
|
|
569
|
+
prompt_type: 'activation',
|
|
570
|
+
agent: agentName,
|
|
571
|
+
prompt_length: startMessage.length,
|
|
572
|
+
prompt_content: startMessage
|
|
573
|
+
}
|
|
574
|
+
});
|
|
575
|
+
}
|
|
576
|
+
|
|
537
577
|
// Main conversation loop
|
|
538
578
|
this.turnCount = 0;
|
|
539
579
|
let sessionActive = true;
|
|
@@ -620,10 +660,30 @@ Be brief and supportive.`;
|
|
|
620
660
|
this.timeline.flush();
|
|
621
661
|
}
|
|
622
662
|
|
|
623
|
-
// Log usage
|
|
663
|
+
// Log usage to usage-log.json and console
|
|
624
664
|
const usage = this.agentProvider.getUsage();
|
|
625
665
|
this.log(`Usage: ${usage.totalTokens} tokens, ${usage.calls} calls`, 'info');
|
|
626
666
|
|
|
667
|
+
if (_usageMod && typeof _usageMod.logUsage === 'function') {
|
|
668
|
+
try {
|
|
669
|
+
_usageMod.logUsage(this.usageLogPath, {
|
|
670
|
+
phase: agentName,
|
|
671
|
+
agent: agentName.charAt(0).toUpperCase() + agentName.slice(1),
|
|
672
|
+
action: 'generation',
|
|
673
|
+
estimated_tokens: usage.totalTokens || 0,
|
|
674
|
+
estimated_cost_usd: (usage.totalTokens || 0) * 0.000002,
|
|
675
|
+
model: this.options.model || 'unknown',
|
|
676
|
+
metadata: {
|
|
677
|
+
turns: this.turnCount,
|
|
678
|
+
calls: usage.calls || 0,
|
|
679
|
+
status: finalStatus
|
|
680
|
+
}
|
|
681
|
+
});
|
|
682
|
+
} catch (err) {
|
|
683
|
+
this.log(`Warning: Failed to write usage log: ${err.message}`, 'warn');
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
|
|
627
687
|
return finalStatus;
|
|
628
688
|
}
|
|
629
689
|
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* agent-checkpoint.js — Agent Self-Checkpoint & Resume (Item 57)
|
|
3
|
+
*
|
|
4
|
+
* Recover gracefully from interrupted runs or model failures.
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* node bin/lib/agent-checkpoint.js save|restore|list|clean [options]
|
|
8
|
+
*
|
|
9
|
+
* State file: .jumpstart/state/agent-checkpoints.json
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
'use strict';
|
|
13
|
+
|
|
14
|
+
const fs = require('fs');
|
|
15
|
+
const path = require('path');
|
|
16
|
+
|
|
17
|
+
const DEFAULT_STATE_FILE = path.join('.jumpstart', 'state', 'agent-checkpoints.json');
|
|
18
|
+
|
|
19
|
+
const CHECKPOINT_TYPES = ['phase-start', 'phase-end', 'task-start', 'task-end', 'error-recovery', 'manual'];
|
|
20
|
+
|
|
21
|
+
function defaultState() {
|
|
22
|
+
return {
|
|
23
|
+
version: '1.0.0',
|
|
24
|
+
created_at: new Date().toISOString(),
|
|
25
|
+
last_updated: null,
|
|
26
|
+
checkpoints: [],
|
|
27
|
+
recovery_log: []
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function loadState(stateFile) {
|
|
32
|
+
const filePath = stateFile || DEFAULT_STATE_FILE;
|
|
33
|
+
if (!fs.existsSync(filePath)) return defaultState();
|
|
34
|
+
try { return JSON.parse(fs.readFileSync(filePath, 'utf8')); }
|
|
35
|
+
catch { return defaultState(); }
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function saveState(state, stateFile) {
|
|
39
|
+
const filePath = stateFile || DEFAULT_STATE_FILE;
|
|
40
|
+
const dir = path.dirname(filePath);
|
|
41
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
42
|
+
state.last_updated = new Date().toISOString();
|
|
43
|
+
fs.writeFileSync(filePath, JSON.stringify(state, null, 2) + '\n', 'utf8');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Save a checkpoint.
|
|
48
|
+
*
|
|
49
|
+
* @param {object} checkpoint - { agent, phase?, task?, context, type? }
|
|
50
|
+
* @param {object} [options]
|
|
51
|
+
* @returns {object}
|
|
52
|
+
*/
|
|
53
|
+
function saveCheckpoint(checkpoint, options = {}) {
|
|
54
|
+
if (!checkpoint || !checkpoint.agent) {
|
|
55
|
+
return { success: false, error: 'agent is required' };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const stateFile = options.stateFile || DEFAULT_STATE_FILE;
|
|
59
|
+
const state = loadState(stateFile);
|
|
60
|
+
|
|
61
|
+
const cp = {
|
|
62
|
+
id: `CP-${Date.now().toString(36).toUpperCase()}`,
|
|
63
|
+
agent: checkpoint.agent,
|
|
64
|
+
phase: checkpoint.phase || null,
|
|
65
|
+
task: checkpoint.task || null,
|
|
66
|
+
type: checkpoint.type || 'manual',
|
|
67
|
+
context: checkpoint.context || {},
|
|
68
|
+
files_snapshot: checkpoint.files_snapshot || [],
|
|
69
|
+
saved_at: new Date().toISOString()
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
state.checkpoints.push(cp);
|
|
73
|
+
|
|
74
|
+
// Keep only last 50 checkpoints
|
|
75
|
+
if (state.checkpoints.length > 50) {
|
|
76
|
+
state.checkpoints = state.checkpoints.slice(-50);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
saveState(state, stateFile);
|
|
80
|
+
|
|
81
|
+
return { success: true, checkpoint: cp };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Restore from a checkpoint.
|
|
86
|
+
*
|
|
87
|
+
* @param {string} [checkpointId] - If omitted, restores from latest.
|
|
88
|
+
* @param {object} [options]
|
|
89
|
+
* @returns {object}
|
|
90
|
+
*/
|
|
91
|
+
function restoreCheckpoint(checkpointId, options = {}) {
|
|
92
|
+
const stateFile = options.stateFile || DEFAULT_STATE_FILE;
|
|
93
|
+
const state = loadState(stateFile);
|
|
94
|
+
|
|
95
|
+
let checkpoint;
|
|
96
|
+
if (checkpointId) {
|
|
97
|
+
checkpoint = state.checkpoints.find(c => c.id === checkpointId);
|
|
98
|
+
} else {
|
|
99
|
+
checkpoint = state.checkpoints[state.checkpoints.length - 1];
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (!checkpoint) {
|
|
103
|
+
return { success: false, error: checkpointId ? `Checkpoint not found: ${checkpointId}` : 'No checkpoints available' };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
state.recovery_log.push({
|
|
107
|
+
checkpoint_id: checkpoint.id,
|
|
108
|
+
restored_at: new Date().toISOString()
|
|
109
|
+
});
|
|
110
|
+
saveState(state, stateFile);
|
|
111
|
+
|
|
112
|
+
return {
|
|
113
|
+
success: true,
|
|
114
|
+
checkpoint,
|
|
115
|
+
agent: checkpoint.agent,
|
|
116
|
+
phase: checkpoint.phase,
|
|
117
|
+
task: checkpoint.task,
|
|
118
|
+
context: checkpoint.context
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* List available checkpoints.
|
|
124
|
+
*
|
|
125
|
+
* @param {object} [filter] - { agent?, phase?, type? }
|
|
126
|
+
* @param {object} [options]
|
|
127
|
+
* @returns {object}
|
|
128
|
+
*/
|
|
129
|
+
function listCheckpoints(filter = {}, options = {}) {
|
|
130
|
+
const stateFile = options.stateFile || DEFAULT_STATE_FILE;
|
|
131
|
+
const state = loadState(stateFile);
|
|
132
|
+
let checkpoints = state.checkpoints;
|
|
133
|
+
|
|
134
|
+
if (filter.agent) checkpoints = checkpoints.filter(c => c.agent === filter.agent);
|
|
135
|
+
if (filter.phase) checkpoints = checkpoints.filter(c => c.phase === filter.phase);
|
|
136
|
+
if (filter.type) checkpoints = checkpoints.filter(c => c.type === filter.type);
|
|
137
|
+
|
|
138
|
+
return { success: true, checkpoints, total: checkpoints.length };
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Clean old checkpoints.
|
|
143
|
+
*
|
|
144
|
+
* @param {object} [options] - { keep? }
|
|
145
|
+
* @returns {object}
|
|
146
|
+
*/
|
|
147
|
+
function cleanCheckpoints(options = {}) {
|
|
148
|
+
const stateFile = options.stateFile || DEFAULT_STATE_FILE;
|
|
149
|
+
const state = loadState(stateFile);
|
|
150
|
+
const keep = options.keep || 10;
|
|
151
|
+
const removed = Math.max(0, state.checkpoints.length - keep);
|
|
152
|
+
|
|
153
|
+
state.checkpoints = state.checkpoints.slice(-keep);
|
|
154
|
+
saveState(state, stateFile);
|
|
155
|
+
|
|
156
|
+
return { success: true, removed, remaining: state.checkpoints.length };
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
module.exports = {
|
|
160
|
+
defaultState,
|
|
161
|
+
loadState,
|
|
162
|
+
saveState,
|
|
163
|
+
saveCheckpoint,
|
|
164
|
+
restoreCheckpoint,
|
|
165
|
+
listCheckpoints,
|
|
166
|
+
cleanCheckpoints,
|
|
167
|
+
CHECKPOINT_TYPES
|
|
168
|
+
};
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ai-evaluation.js — Evaluation Framework for AI Systems (Item 90)
|
|
3
|
+
*
|
|
4
|
+
* Groundedness, hallucination, safety, latency, cost,
|
|
5
|
+
* and business KPI eval packs.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* node bin/lib/ai-evaluation.js evaluate|report|configure [options]
|
|
9
|
+
*
|
|
10
|
+
* State file: .jumpstart/state/ai-evaluation.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', 'ai-evaluation.json');
|
|
19
|
+
|
|
20
|
+
const EVAL_DIMENSIONS = ['groundedness', 'hallucination', 'safety', 'latency', 'cost', 'relevance', 'coherence'];
|
|
21
|
+
|
|
22
|
+
function defaultState() {
|
|
23
|
+
return { version: '1.0.0', evaluations: [], benchmarks: [], last_updated: null };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function loadState(stateFile) {
|
|
27
|
+
const fp = stateFile || DEFAULT_STATE_FILE;
|
|
28
|
+
if (!fs.existsSync(fp)) return defaultState();
|
|
29
|
+
try { return JSON.parse(fs.readFileSync(fp, 'utf8')); }
|
|
30
|
+
catch { return defaultState(); }
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function saveState(state, stateFile) {
|
|
34
|
+
const fp = stateFile || DEFAULT_STATE_FILE;
|
|
35
|
+
const dir = path.dirname(fp);
|
|
36
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
37
|
+
state.last_updated = new Date().toISOString();
|
|
38
|
+
fs.writeFileSync(fp, JSON.stringify(state, null, 2) + '\n', 'utf8');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function evaluate(name, scores, options = {}) {
|
|
42
|
+
if (!name || !scores) return { success: false, error: 'name and scores are required' };
|
|
43
|
+
|
|
44
|
+
const stateFile = options.stateFile || DEFAULT_STATE_FILE;
|
|
45
|
+
const state = loadState(stateFile);
|
|
46
|
+
|
|
47
|
+
const evaluation = {
|
|
48
|
+
id: `EVAL-${Date.now()}`,
|
|
49
|
+
name,
|
|
50
|
+
scores,
|
|
51
|
+
overall: Math.round(Object.values(scores).reduce((a, b) => a + b, 0) / Object.keys(scores).length),
|
|
52
|
+
model: options.model || null,
|
|
53
|
+
use_case: options.use_case || null,
|
|
54
|
+
evaluated_at: new Date().toISOString()
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
state.evaluations.push(evaluation);
|
|
58
|
+
saveState(state, stateFile);
|
|
59
|
+
|
|
60
|
+
return { success: true, evaluation };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function generateReport(options = {}) {
|
|
64
|
+
const stateFile = options.stateFile || DEFAULT_STATE_FILE;
|
|
65
|
+
const state = loadState(stateFile);
|
|
66
|
+
|
|
67
|
+
const avgScores = {};
|
|
68
|
+
for (const dim of EVAL_DIMENSIONS) {
|
|
69
|
+
const vals = state.evaluations.filter(e => e.scores[dim] !== undefined).map(e => e.scores[dim]);
|
|
70
|
+
avgScores[dim] = vals.length > 0 ? Math.round(vals.reduce((a, b) => a + b, 0) / vals.length) : null;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
success: true,
|
|
75
|
+
total_evaluations: state.evaluations.length,
|
|
76
|
+
average_scores: avgScores,
|
|
77
|
+
evaluations: state.evaluations
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function configureBenchmark(name, thresholds, options = {}) {
|
|
82
|
+
if (!name || !thresholds) return { success: false, error: 'name and thresholds are required' };
|
|
83
|
+
|
|
84
|
+
const stateFile = options.stateFile || DEFAULT_STATE_FILE;
|
|
85
|
+
const state = loadState(stateFile);
|
|
86
|
+
|
|
87
|
+
const benchmark = {
|
|
88
|
+
id: `BENCH-${Date.now()}`,
|
|
89
|
+
name,
|
|
90
|
+
thresholds,
|
|
91
|
+
created_at: new Date().toISOString()
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
state.benchmarks.push(benchmark);
|
|
95
|
+
saveState(state, stateFile);
|
|
96
|
+
|
|
97
|
+
return { success: true, benchmark };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
module.exports = {
|
|
101
|
+
evaluate, generateReport, configureBenchmark,
|
|
102
|
+
loadState, saveState, defaultState,
|
|
103
|
+
EVAL_DIMENSIONS
|
|
104
|
+
};
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ai-intake.js — AI Use Case Intake Templates (Item 34)
|
|
3
|
+
*
|
|
4
|
+
* Standardize enterprise intake for business value, data sensitivity,
|
|
5
|
+
* model risk, and operating model.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* node bin/lib/ai-intake.js create|list|assess [options]
|
|
9
|
+
*
|
|
10
|
+
* State file: .jumpstart/state/ai-intake.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', 'ai-intake.json');
|
|
19
|
+
|
|
20
|
+
const INTAKE_SECTIONS = ['business-value', 'data-sensitivity', 'model-risk', 'operating-model', 'ethical-review', 'compliance-requirements'];
|
|
21
|
+
|
|
22
|
+
const RISK_TIERS = [
|
|
23
|
+
{ tier: 1, label: 'Low Risk', description: 'No PII, internal tools, low business impact' },
|
|
24
|
+
{ tier: 2, label: 'Medium Risk', description: 'Some PII, customer-facing, moderate impact' },
|
|
25
|
+
{ tier: 3, label: 'High Risk', description: 'Sensitive data, critical decisions, high impact' },
|
|
26
|
+
{ tier: 4, label: 'Critical Risk', description: 'Regulated domain, autonomous decisions, severe impact' }
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
function defaultState() {
|
|
30
|
+
return {
|
|
31
|
+
version: '1.0.0',
|
|
32
|
+
created_at: new Date().toISOString(),
|
|
33
|
+
last_updated: null,
|
|
34
|
+
intakes: []
|
|
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
|
+
* Create a new AI use case intake.
|
|
55
|
+
*
|
|
56
|
+
* @param {object} intake - { name, description, sponsor, business_value, data_types[], model_type? }
|
|
57
|
+
* @param {object} [options]
|
|
58
|
+
* @returns {object}
|
|
59
|
+
*/
|
|
60
|
+
function createIntake(intake, options = {}) {
|
|
61
|
+
if (!intake || !intake.name || !intake.description) {
|
|
62
|
+
return { success: false, error: 'name and description are required' };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const stateFile = options.stateFile || DEFAULT_STATE_FILE;
|
|
66
|
+
const state = loadState(stateFile);
|
|
67
|
+
|
|
68
|
+
// Auto-assess risk tier
|
|
69
|
+
const dataTypes = intake.data_types || [];
|
|
70
|
+
let riskTier = 1;
|
|
71
|
+
if (dataTypes.some(dt => ['PHI', 'PCI'].includes(dt))) riskTier = 4;
|
|
72
|
+
else if (dataTypes.some(dt => ['PII', 'credentials'].includes(dt))) riskTier = 3;
|
|
73
|
+
else if (dataTypes.some(dt => ['business-sensitive', 'internal'].includes(dt))) riskTier = 2;
|
|
74
|
+
|
|
75
|
+
const newIntake = {
|
|
76
|
+
id: `AI-${(state.intakes.length + 1).toString().padStart(3, '0')}`,
|
|
77
|
+
name: intake.name,
|
|
78
|
+
description: intake.description,
|
|
79
|
+
sponsor: intake.sponsor || null,
|
|
80
|
+
business_value: intake.business_value || '',
|
|
81
|
+
data_types: dataTypes,
|
|
82
|
+
model_type: intake.model_type || null,
|
|
83
|
+
risk_tier: riskTier,
|
|
84
|
+
risk_label: RISK_TIERS[riskTier - 1].label,
|
|
85
|
+
status: 'draft',
|
|
86
|
+
sections_completed: [],
|
|
87
|
+
created_at: new Date().toISOString()
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
state.intakes.push(newIntake);
|
|
91
|
+
saveState(state, stateFile);
|
|
92
|
+
|
|
93
|
+
return { success: true, intake: newIntake };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* List intakes with optional filter.
|
|
98
|
+
*
|
|
99
|
+
* @param {object} [filter]
|
|
100
|
+
* @param {object} [options]
|
|
101
|
+
* @returns {object}
|
|
102
|
+
*/
|
|
103
|
+
function listIntakes(filter = {}, options = {}) {
|
|
104
|
+
const stateFile = options.stateFile || DEFAULT_STATE_FILE;
|
|
105
|
+
const state = loadState(stateFile);
|
|
106
|
+
let intakes = state.intakes;
|
|
107
|
+
|
|
108
|
+
if (filter.status) intakes = intakes.filter(i => i.status === filter.status);
|
|
109
|
+
if (filter.risk_tier) intakes = intakes.filter(i => i.risk_tier === filter.risk_tier);
|
|
110
|
+
|
|
111
|
+
return { success: true, intakes, total: intakes.length };
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Assess intake completeness.
|
|
116
|
+
*
|
|
117
|
+
* @param {string} intakeId
|
|
118
|
+
* @param {object} [options]
|
|
119
|
+
* @returns {object}
|
|
120
|
+
*/
|
|
121
|
+
function assessIntake(intakeId, options = {}) {
|
|
122
|
+
const stateFile = options.stateFile || DEFAULT_STATE_FILE;
|
|
123
|
+
const state = loadState(stateFile);
|
|
124
|
+
|
|
125
|
+
const intake = state.intakes.find(i => i.id === intakeId);
|
|
126
|
+
if (!intake) return { success: false, error: `Intake not found: ${intakeId}` };
|
|
127
|
+
|
|
128
|
+
const completedSections = intake.sections_completed || [];
|
|
129
|
+
const missingSections = INTAKE_SECTIONS.filter(s => !completedSections.includes(s));
|
|
130
|
+
const completeness = Math.round((completedSections.length / INTAKE_SECTIONS.length) * 100);
|
|
131
|
+
|
|
132
|
+
return {
|
|
133
|
+
success: true,
|
|
134
|
+
intake_id: intakeId,
|
|
135
|
+
completeness,
|
|
136
|
+
completed_sections: completedSections,
|
|
137
|
+
missing_sections: missingSections,
|
|
138
|
+
risk_tier: intake.risk_tier,
|
|
139
|
+
ready_for_review: completeness >= 80
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
module.exports = {
|
|
144
|
+
defaultState,
|
|
145
|
+
loadState,
|
|
146
|
+
saveState,
|
|
147
|
+
createIntake,
|
|
148
|
+
listIntakes,
|
|
149
|
+
assessIntake,
|
|
150
|
+
INTAKE_SECTIONS,
|
|
151
|
+
RISK_TIERS
|
|
152
|
+
};
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ambiguity-heatmap.js — Requirement Ambiguity Heatmap (Item 71)
|
|
3
|
+
*
|
|
4
|
+
* Highlight vague language, missing constraints, undefined terms,
|
|
5
|
+
* and assumption density.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* node bin/lib/ambiguity-heatmap.js scan|report [options]
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
'use strict';
|
|
12
|
+
|
|
13
|
+
const fs = require('fs');
|
|
14
|
+
const path = require('path');
|
|
15
|
+
|
|
16
|
+
const VAGUE_TERMS = [
|
|
17
|
+
'should', 'could', 'might', 'possibly', 'maybe', 'approximately',
|
|
18
|
+
'reasonable', 'appropriate', 'adequate', 'sufficient', 'as needed',
|
|
19
|
+
'etc', 'and so on', 'as appropriate', 'in a timely manner',
|
|
20
|
+
'user-friendly', 'intuitive', 'seamless', 'robust', 'scalable',
|
|
21
|
+
'performant', 'efficient', 'flexible', 'simple', 'easy'
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
const MISSING_CONSTRAINT_PATTERNS = [
|
|
25
|
+
{ pattern: /\bfast\b/gi, suggestion: 'Define specific latency target (e.g., <200ms p95)' },
|
|
26
|
+
{ pattern: /\bsecure\b/gi, suggestion: 'Specify security controls (encryption, auth, audit)' },
|
|
27
|
+
{ pattern: /\bhigh availability\b/gi, suggestion: 'Define uptime SLA (e.g., 99.9%)' },
|
|
28
|
+
{ pattern: /\blarge scale\b/gi, suggestion: 'Quantify expected load (users, requests/sec)' },
|
|
29
|
+
{ pattern: /\breal[- ]?time\b/gi, suggestion: 'Define latency requirement (e.g., <1s, <100ms)' }
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Scan text for ambiguity indicators.
|
|
34
|
+
*/
|
|
35
|
+
function scanAmbiguity(text, options = {}) {
|
|
36
|
+
if (!text) return { success: false, error: 'Text content is required' };
|
|
37
|
+
|
|
38
|
+
const lines = text.split('\n');
|
|
39
|
+
const findings = [];
|
|
40
|
+
|
|
41
|
+
for (let i = 0; i < lines.length; i++) {
|
|
42
|
+
const line = lines[i];
|
|
43
|
+
const lineNum = i + 1;
|
|
44
|
+
|
|
45
|
+
// Check vague terms
|
|
46
|
+
for (const term of VAGUE_TERMS) {
|
|
47
|
+
const regex = new RegExp(`\\b${term}\\b`, 'gi');
|
|
48
|
+
const matches = line.match(regex);
|
|
49
|
+
if (matches) {
|
|
50
|
+
findings.push({
|
|
51
|
+
type: 'vague_language',
|
|
52
|
+
term,
|
|
53
|
+
line: lineNum,
|
|
54
|
+
severity: 'medium',
|
|
55
|
+
context: line.trim().substring(0, 100)
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Check missing constraints
|
|
61
|
+
for (const { pattern, suggestion } of MISSING_CONSTRAINT_PATTERNS) {
|
|
62
|
+
if (pattern.test(line)) {
|
|
63
|
+
findings.push({
|
|
64
|
+
type: 'missing_constraint',
|
|
65
|
+
line: lineNum,
|
|
66
|
+
severity: 'high',
|
|
67
|
+
suggestion,
|
|
68
|
+
context: line.trim().substring(0, 100)
|
|
69
|
+
});
|
|
70
|
+
// Reset regex lastIndex
|
|
71
|
+
pattern.lastIndex = 0;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Check for undefined terms (capitalized terms that may be domain concepts)
|
|
77
|
+
const definedTerms = new Set();
|
|
78
|
+
const usedTerms = new Set();
|
|
79
|
+
for (const line of lines) {
|
|
80
|
+
const defs = line.match(/^#+\s+(.+)/);
|
|
81
|
+
if (defs) definedTerms.add(defs[1].trim().toLowerCase());
|
|
82
|
+
|
|
83
|
+
const caps = line.match(/\b[A-Z][a-z]+(?:\s+[A-Z][a-z]+)+\b/g);
|
|
84
|
+
if (caps) caps.forEach(t => usedTerms.add(t.toLowerCase()));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Assumption density
|
|
88
|
+
const assumptions = (text.match(/\bassume[ds]?\b|\bassuming\b|\bassumption/gi) || []).length;
|
|
89
|
+
|
|
90
|
+
const totalLines = lines.filter(l => l.trim().length > 0).length;
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
success: true,
|
|
94
|
+
total_findings: findings.length,
|
|
95
|
+
findings: findings.slice(0, options.limit || 50),
|
|
96
|
+
metrics: {
|
|
97
|
+
vague_terms: findings.filter(f => f.type === 'vague_language').length,
|
|
98
|
+
missing_constraints: findings.filter(f => f.type === 'missing_constraint').length,
|
|
99
|
+
assumption_count: assumptions,
|
|
100
|
+
ambiguity_density: totalLines > 0 ? Math.round((findings.length / totalLines) * 100) : 0
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Scan a file for ambiguity.
|
|
107
|
+
*/
|
|
108
|
+
function scanFile(filePath, options = {}) {
|
|
109
|
+
if (!fs.existsSync(filePath)) {
|
|
110
|
+
return { success: false, error: `File not found: ${filePath}` };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
114
|
+
const result = scanAmbiguity(content, options);
|
|
115
|
+
result.file = filePath;
|
|
116
|
+
return result;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Generate ambiguity heatmap report.
|
|
121
|
+
*/
|
|
122
|
+
function generateHeatmap(root, options = {}) {
|
|
123
|
+
const specsDir = path.join(root, 'specs');
|
|
124
|
+
const results = [];
|
|
125
|
+
|
|
126
|
+
if (fs.existsSync(specsDir)) {
|
|
127
|
+
for (const f of fs.readdirSync(specsDir).filter(f => f.endsWith('.md'))) {
|
|
128
|
+
const fp = path.join(specsDir, f);
|
|
129
|
+
const result = scanFile(fp, options);
|
|
130
|
+
if (result.success) {
|
|
131
|
+
results.push({ file: f, ...result.metrics, total_findings: result.total_findings });
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
results.sort((a, b) => b.ambiguity_density - a.ambiguity_density);
|
|
137
|
+
|
|
138
|
+
return {
|
|
139
|
+
success: true,
|
|
140
|
+
files_scanned: results.length,
|
|
141
|
+
results,
|
|
142
|
+
overall: {
|
|
143
|
+
total_findings: results.reduce((s, r) => s + r.total_findings, 0),
|
|
144
|
+
highest_density_file: results.length > 0 ? results[0].file : null
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
module.exports = {
|
|
150
|
+
scanAmbiguity, scanFile, generateHeatmap,
|
|
151
|
+
VAGUE_TERMS, MISSING_CONSTRAINT_PATTERNS
|
|
152
|
+
};
|