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,261 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* type-checker.js — Automated Type Checking Gate
|
|
3
|
+
*
|
|
4
|
+
* Detects and runs type checkers (TypeScript tsc, Python mypy/pyright)
|
|
5
|
+
* after agent writes to src/. Provides structured output for the
|
|
6
|
+
* Developer agent during Phase 4 implementation.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* echo '{"files":["src/index.ts"],"root":"."}' | node bin/lib/type-checker.js
|
|
10
|
+
*
|
|
11
|
+
* Input (stdin JSON):
|
|
12
|
+
* {
|
|
13
|
+
* "files": ["src/index.ts", "src/utils.ts"],
|
|
14
|
+
* "root": ".",
|
|
15
|
+
* "config": {
|
|
16
|
+
* "type_command": "npx tsc --noEmit",
|
|
17
|
+
* "strict": true
|
|
18
|
+
* }
|
|
19
|
+
* }
|
|
20
|
+
*
|
|
21
|
+
* Output (stdout JSON):
|
|
22
|
+
* {
|
|
23
|
+
* "files_checked": 2,
|
|
24
|
+
* "errors": 3,
|
|
25
|
+
* "warnings": 0,
|
|
26
|
+
* "findings": [...],
|
|
27
|
+
* "pass": false,
|
|
28
|
+
* "checker": "TypeScript"
|
|
29
|
+
* }
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
import { createRequire } from 'module';
|
|
33
|
+
const require = createRequire(import.meta.url);
|
|
34
|
+
const fs = require('fs');
|
|
35
|
+
const path = require('path');
|
|
36
|
+
const { execSync } = require('child_process');
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Detect the project's type checker from configuration files.
|
|
40
|
+
*
|
|
41
|
+
* @param {string} root - Project root path.
|
|
42
|
+
* @returns {{ command: string, name: string } | null}
|
|
43
|
+
*/
|
|
44
|
+
function detectTypeChecker(root) {
|
|
45
|
+
const checks = [
|
|
46
|
+
{ file: 'tsconfig.json', command: 'npx tsc --noEmit', name: 'TypeScript' },
|
|
47
|
+
{ file: 'jsconfig.json', command: 'npx tsc --noEmit --allowJs', name: 'TypeScript (JS)' },
|
|
48
|
+
{ file: 'pyrightconfig.json', command: 'npx pyright', name: 'Pyright' },
|
|
49
|
+
{ file: 'mypy.ini', command: 'python -m mypy', name: 'mypy' },
|
|
50
|
+
{ file: '.mypy.ini', command: 'python -m mypy', name: 'mypy' }
|
|
51
|
+
];
|
|
52
|
+
|
|
53
|
+
// Check pyproject.toml for mypy or pyright config
|
|
54
|
+
const pyprojectPath = path.join(root, 'pyproject.toml');
|
|
55
|
+
if (fs.existsSync(pyprojectPath)) {
|
|
56
|
+
try {
|
|
57
|
+
const content = fs.readFileSync(pyprojectPath, 'utf8');
|
|
58
|
+
if (content.includes('[tool.mypy]')) {
|
|
59
|
+
return { command: 'python -m mypy', name: 'mypy' };
|
|
60
|
+
}
|
|
61
|
+
if (content.includes('[tool.pyright]')) {
|
|
62
|
+
return { command: 'npx pyright', name: 'Pyright' };
|
|
63
|
+
}
|
|
64
|
+
} catch {
|
|
65
|
+
// ignore
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
for (const check of checks) {
|
|
70
|
+
if (fs.existsSync(path.join(root, check.file))) {
|
|
71
|
+
return { command: check.command, name: check.name };
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Parse type checker output into structured findings.
|
|
80
|
+
* Handles TypeScript, Pyright, and mypy output formats.
|
|
81
|
+
*
|
|
82
|
+
* @param {string} output - Raw type checker output.
|
|
83
|
+
* @param {string} checkerName - Name of the type checker.
|
|
84
|
+
* @returns {Array<{ file: string, line: number|null, severity: string, message: string, code: string|null }>}
|
|
85
|
+
*/
|
|
86
|
+
function parseTypeErrors(output, checkerName) {
|
|
87
|
+
const findings = [];
|
|
88
|
+
const lines = output.split('\n');
|
|
89
|
+
|
|
90
|
+
for (const line of lines) {
|
|
91
|
+
// TypeScript format: src/index.ts(10,5): error TS2322: Type 'string' is not assignable...
|
|
92
|
+
const tsMatch = line.match(/^(.+?)\((\d+),\d+\):\s+(error|warning)\s+(TS\d+):\s+(.+)/);
|
|
93
|
+
if (tsMatch) {
|
|
94
|
+
findings.push({
|
|
95
|
+
file: tsMatch[1],
|
|
96
|
+
line: parseInt(tsMatch[2], 10),
|
|
97
|
+
severity: tsMatch[3],
|
|
98
|
+
code: tsMatch[4],
|
|
99
|
+
message: tsMatch[5].trim()
|
|
100
|
+
});
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// TypeScript alternative format: src/index.ts:10:5 - error TS2322: Type 'string'...
|
|
105
|
+
const tsAltMatch = line.match(/^(.+?):(\d+):\d+\s+-\s+(error|warning)\s+(TS\d+):\s+(.+)/);
|
|
106
|
+
if (tsAltMatch) {
|
|
107
|
+
findings.push({
|
|
108
|
+
file: tsAltMatch[1],
|
|
109
|
+
line: parseInt(tsAltMatch[2], 10),
|
|
110
|
+
severity: tsAltMatch[3],
|
|
111
|
+
code: tsAltMatch[4],
|
|
112
|
+
message: tsAltMatch[5].trim()
|
|
113
|
+
});
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// mypy format: src/main.py:10: error: Incompatible types... [assignment]
|
|
118
|
+
const mypyMatch = line.match(/^(.+?):(\d+):\s+(error|warning|note):\s+(.+?)(?:\s+\[(.+?)\])?$/);
|
|
119
|
+
if (mypyMatch && !line.startsWith(' ')) {
|
|
120
|
+
findings.push({
|
|
121
|
+
file: mypyMatch[1],
|
|
122
|
+
line: parseInt(mypyMatch[2], 10),
|
|
123
|
+
severity: mypyMatch[3] === 'note' ? 'warning' : mypyMatch[3],
|
|
124
|
+
code: mypyMatch[5] || null,
|
|
125
|
+
message: mypyMatch[4].trim()
|
|
126
|
+
});
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Pyright format: src/main.py:10:5 - error: Cannot assign... (reportGeneralClassIssues)
|
|
131
|
+
const pyrightMatch = line.match(/^(.+?):(\d+):\d+\s+-\s+(error|warning|information):\s+(.+?)(?:\s+\((.+?)\))?$/);
|
|
132
|
+
if (pyrightMatch) {
|
|
133
|
+
findings.push({
|
|
134
|
+
file: pyrightMatch[1],
|
|
135
|
+
line: parseInt(pyrightMatch[2], 10),
|
|
136
|
+
severity: pyrightMatch[3] === 'information' ? 'warning' : pyrightMatch[3],
|
|
137
|
+
code: pyrightMatch[5] || null,
|
|
138
|
+
message: pyrightMatch[4].trim()
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return findings;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Run type checking on the project.
|
|
148
|
+
*
|
|
149
|
+
* @param {object} input - Type check options.
|
|
150
|
+
* @param {string[]} [input.files] - Specific files (used for filtering results).
|
|
151
|
+
* @param {string} [input.root] - Project root.
|
|
152
|
+
* @param {object} [input.config] - Override config.
|
|
153
|
+
* @param {string} [input.config.type_command] - Custom type check command.
|
|
154
|
+
* @param {boolean} [input.config.strict] - Enable strict mode.
|
|
155
|
+
* @returns {object} Type check results.
|
|
156
|
+
*/
|
|
157
|
+
function runTypeCheck(input) {
|
|
158
|
+
const { files = [], root = '.', config = {} } = input;
|
|
159
|
+
const resolvedRoot = path.resolve(root);
|
|
160
|
+
|
|
161
|
+
// Determine type checker
|
|
162
|
+
let checkerInfo;
|
|
163
|
+
if (config.type_command) {
|
|
164
|
+
checkerInfo = { command: config.type_command, name: 'custom' };
|
|
165
|
+
} else {
|
|
166
|
+
checkerInfo = detectTypeChecker(resolvedRoot);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (!checkerInfo) {
|
|
170
|
+
return {
|
|
171
|
+
files_checked: files.length,
|
|
172
|
+
errors: 0,
|
|
173
|
+
warnings: 0,
|
|
174
|
+
findings: [],
|
|
175
|
+
pass: true,
|
|
176
|
+
checker: null,
|
|
177
|
+
message: 'No type checker detected. Consider adding tsconfig.json (TypeScript) or mypy.ini (Python).'
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Build command
|
|
182
|
+
let cmd = checkerInfo.command;
|
|
183
|
+
if (config.strict && checkerInfo.name === 'TypeScript') {
|
|
184
|
+
cmd += ' --strict';
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
let output = '';
|
|
188
|
+
let exitCode = 0;
|
|
189
|
+
|
|
190
|
+
try {
|
|
191
|
+
output = execSync(cmd, {
|
|
192
|
+
cwd: resolvedRoot,
|
|
193
|
+
encoding: 'utf8',
|
|
194
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
195
|
+
timeout: 120000
|
|
196
|
+
});
|
|
197
|
+
} catch (err) {
|
|
198
|
+
output = (err.stdout || '') + (err.stderr || '');
|
|
199
|
+
exitCode = err.status || 1;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
let findings = parseTypeErrors(output, checkerInfo.name);
|
|
203
|
+
|
|
204
|
+
// If specific files provided, filter findings to those files
|
|
205
|
+
if (files.length > 0) {
|
|
206
|
+
const normalizedFiles = new Set(files.map(f => {
|
|
207
|
+
if (path.isAbsolute(f)) return path.relative(resolvedRoot, f);
|
|
208
|
+
return f;
|
|
209
|
+
}));
|
|
210
|
+
|
|
211
|
+
findings = findings.filter(f => {
|
|
212
|
+
const normalized = path.isAbsolute(f.file)
|
|
213
|
+
? path.relative(resolvedRoot, f.file)
|
|
214
|
+
: f.file;
|
|
215
|
+
return normalizedFiles.has(normalized);
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const errors = findings.filter(f => f.severity === 'error').length;
|
|
220
|
+
const warnings = findings.filter(f => f.severity === 'warning').length;
|
|
221
|
+
|
|
222
|
+
return {
|
|
223
|
+
files_checked: files.length || '(project-wide)',
|
|
224
|
+
errors,
|
|
225
|
+
warnings,
|
|
226
|
+
findings,
|
|
227
|
+
pass: errors === 0,
|
|
228
|
+
checker: checkerInfo.name,
|
|
229
|
+
exit_code: exitCode
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// ─── CLI Entry Point ──────────────────────────────────────────────────────────
|
|
234
|
+
|
|
235
|
+
if (process.argv[1] && (
|
|
236
|
+
process.argv[1].endsWith('type-checker.js') ||
|
|
237
|
+
process.argv[1].endsWith('type-checker')
|
|
238
|
+
)) {
|
|
239
|
+
let input = '';
|
|
240
|
+
process.stdin.setEncoding('utf8');
|
|
241
|
+
process.stdin.on('data', chunk => { input += chunk; });
|
|
242
|
+
process.stdin.on('end', () => {
|
|
243
|
+
try {
|
|
244
|
+
const parsed = input.trim() ? JSON.parse(input) : {};
|
|
245
|
+
const result = runTypeCheck(parsed);
|
|
246
|
+
process.stdout.write(JSON.stringify(result, null, 2) + '\n');
|
|
247
|
+
process.exit(result.pass ? 0 : 1);
|
|
248
|
+
} catch (err) {
|
|
249
|
+
process.stdout.write(JSON.stringify({ error: err.message }) + '\n');
|
|
250
|
+
process.exit(2);
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
if (process.stdin.isTTY) {
|
|
255
|
+
const result = runTypeCheck({});
|
|
256
|
+
process.stdout.write(JSON.stringify(result, null, 2) + '\n');
|
|
257
|
+
process.exit(result.pass ? 0 : 1);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
export { runTypeCheck, detectTypeChecker, parseTypeErrors };
|
|
@@ -0,0 +1,411 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* uat-coverage.js — Automated User Acceptance Testing (UAT) Alignment
|
|
5
|
+
*
|
|
6
|
+
* Part of Jump Start Framework.
|
|
7
|
+
*
|
|
8
|
+
* Extends the coverage.js concept to verify that PRD acceptance criteria
|
|
9
|
+
* (Gherkin-style Given/When/Then or plain-text AC) are actually covered
|
|
10
|
+
* by the generated test suite. Bridges Phase 2 (Planning) and Phase 4
|
|
11
|
+
* (Implementing) by ensuring tests fulfil business needs.
|
|
12
|
+
*
|
|
13
|
+
* Usage:
|
|
14
|
+
* node bin/lib/uat-coverage.js specs/prd.md tests/
|
|
15
|
+
*
|
|
16
|
+
* Output: JSON or Markdown report showing AC → test mapping.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
'use strict';
|
|
20
|
+
|
|
21
|
+
const fs = require('fs');
|
|
22
|
+
const path = require('path');
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Extract acceptance criteria from a PRD document.
|
|
26
|
+
* Supports both Gherkin (Given/When/Then) and bullet-list AC formats.
|
|
27
|
+
*
|
|
28
|
+
* @param {string} prdContent - PRD markdown content.
|
|
29
|
+
* @returns {Array<{ story_id: string, criteria: string[], gherkin: string[] }>}
|
|
30
|
+
*/
|
|
31
|
+
function extractAcceptanceCriteria(prdContent) {
|
|
32
|
+
const stories = [];
|
|
33
|
+
const storyPattern = /\b(E\d+-S\d+)\b/g;
|
|
34
|
+
const storyIds = [...new Set((prdContent.match(storyPattern) || []))];
|
|
35
|
+
|
|
36
|
+
for (const storyId of storyIds) {
|
|
37
|
+
// Find the section for this story
|
|
38
|
+
const escapedId = storyId.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
39
|
+
const sectionRegex = new RegExp(
|
|
40
|
+
`${escapedId}[\\s\\S]*?(?=E\\d+-S\\d+|## |$)`,
|
|
41
|
+
'g'
|
|
42
|
+
);
|
|
43
|
+
const section = sectionRegex.exec(prdContent);
|
|
44
|
+
|
|
45
|
+
if (!section) {
|
|
46
|
+
stories.push({ story_id: storyId, criteria: [], gherkin: [] });
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const sectionText = section[0];
|
|
51
|
+
|
|
52
|
+
// Extract Gherkin blocks (Given/When/Then)
|
|
53
|
+
const gherkinLines = [];
|
|
54
|
+
const gherkinPattern = /^\s*(Given|When|Then|And|But)\s+(.+)/gm;
|
|
55
|
+
let gherkinMatch;
|
|
56
|
+
while ((gherkinMatch = gherkinPattern.exec(sectionText)) !== null) {
|
|
57
|
+
gherkinLines.push(`${gherkinMatch[1]} ${gherkinMatch[2].trim()}`);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Extract bullet-point acceptance criteria
|
|
61
|
+
const criteria = [];
|
|
62
|
+
// Look for AC section markers
|
|
63
|
+
const acSection = sectionText.match(
|
|
64
|
+
/(?:acceptance\s+criteria|AC|criteria)[:\s]*\n([\s\S]*?)(?=\n(?:#{1,3}\s|\n\n)|$)/i
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
if (acSection) {
|
|
68
|
+
const bulletPattern = /^\s*[-*]\s+(.+)/gm;
|
|
69
|
+
let bulletMatch;
|
|
70
|
+
while ((bulletMatch = bulletPattern.exec(acSection[1])) !== null) {
|
|
71
|
+
criteria.push(bulletMatch[1].trim());
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Also extract standalone bullet criteria near the story ID
|
|
76
|
+
if (criteria.length === 0) {
|
|
77
|
+
const bulletPattern = /^\s*[-*]\s+(.+)/gm;
|
|
78
|
+
let bulletMatch;
|
|
79
|
+
while ((bulletMatch = bulletPattern.exec(sectionText)) !== null) {
|
|
80
|
+
const text = bulletMatch[1].trim();
|
|
81
|
+
// Filter out non-AC items (headers, metadata, etc.)
|
|
82
|
+
if (text.length > 10 && !text.startsWith('#') && !text.startsWith('|')) {
|
|
83
|
+
criteria.push(text);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
stories.push({
|
|
89
|
+
story_id: storyId,
|
|
90
|
+
criteria,
|
|
91
|
+
gherkin: gherkinLines
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return stories;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Scan test files for references to story IDs and acceptance criteria.
|
|
100
|
+
*
|
|
101
|
+
* @param {string} testDir - Path to the test directory.
|
|
102
|
+
* @param {string[]} storyIds - Story IDs to look for.
|
|
103
|
+
* @returns {Map<string, { files: string[], keywords: string[] }>}
|
|
104
|
+
*/
|
|
105
|
+
function scanTestCoverage(testDir, storyIds) {
|
|
106
|
+
const coverage = new Map();
|
|
107
|
+
for (const id of storyIds) {
|
|
108
|
+
coverage.set(id, { files: [], keywords: [] });
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (!fs.existsSync(testDir)) {
|
|
112
|
+
return coverage;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const testFiles = walkTestFiles(testDir);
|
|
116
|
+
|
|
117
|
+
for (const testFile of testFiles) {
|
|
118
|
+
let content;
|
|
119
|
+
try {
|
|
120
|
+
content = fs.readFileSync(testFile, 'utf8');
|
|
121
|
+
} catch {
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
for (const storyId of storyIds) {
|
|
126
|
+
// Check for direct story ID reference
|
|
127
|
+
if (content.includes(storyId)) {
|
|
128
|
+
coverage.get(storyId).files.push(path.relative(testDir, testFile));
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Check for describe/it blocks that reference the story semantically
|
|
132
|
+
const escapedId = storyId.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
133
|
+
const testBlockPattern = new RegExp(
|
|
134
|
+
`(?:describe|it|test)\\s*\\(\\s*['"\`].*${escapedId}.*['"\`]`,
|
|
135
|
+
'i'
|
|
136
|
+
);
|
|
137
|
+
if (testBlockPattern.test(content)) {
|
|
138
|
+
const entry = coverage.get(storyId);
|
|
139
|
+
const relFile = path.relative(testDir, testFile);
|
|
140
|
+
if (!entry.files.includes(relFile)) {
|
|
141
|
+
entry.files.push(relFile);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return coverage;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Walk a directory tree and collect test file paths.
|
|
152
|
+
*
|
|
153
|
+
* @param {string} dir - Directory to walk.
|
|
154
|
+
* @returns {string[]}
|
|
155
|
+
*/
|
|
156
|
+
function walkTestFiles(dir) {
|
|
157
|
+
const results = [];
|
|
158
|
+
const testPatterns = ['.test.', '.spec.', '_test.', '_spec.', '.feature'];
|
|
159
|
+
|
|
160
|
+
function walk(currentDir) {
|
|
161
|
+
let entries;
|
|
162
|
+
try {
|
|
163
|
+
entries = fs.readdirSync(currentDir, { withFileTypes: true });
|
|
164
|
+
} catch {
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
for (const entry of entries) {
|
|
168
|
+
const fullPath = path.join(currentDir, entry.name);
|
|
169
|
+
if (entry.isDirectory()) {
|
|
170
|
+
if (entry.name !== 'node_modules' && entry.name !== '.git') {
|
|
171
|
+
walk(fullPath);
|
|
172
|
+
}
|
|
173
|
+
} else {
|
|
174
|
+
const isTestFile = testPatterns.some(p => entry.name.includes(p));
|
|
175
|
+
if (isTestFile) {
|
|
176
|
+
results.push(fullPath);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
walk(dir);
|
|
183
|
+
return results;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Match acceptance criteria text against test file content for semantic coverage.
|
|
188
|
+
*
|
|
189
|
+
* @param {Array<{ story_id: string, criteria: string[], gherkin: string[] }>} storyCriteria
|
|
190
|
+
* @param {string} testDir - Path to test directory.
|
|
191
|
+
* @returns {Array<{ story_id: string, criterion: string, covered: boolean, test_files: string[] }>}
|
|
192
|
+
*/
|
|
193
|
+
function matchCriteriaToTests(storyCriteria, testDir) {
|
|
194
|
+
const results = [];
|
|
195
|
+
|
|
196
|
+
if (!fs.existsSync(testDir)) {
|
|
197
|
+
for (const story of storyCriteria) {
|
|
198
|
+
const allCriteria = [...story.criteria, ...story.gherkin];
|
|
199
|
+
for (const criterion of allCriteria) {
|
|
200
|
+
results.push({
|
|
201
|
+
story_id: story.story_id,
|
|
202
|
+
criterion,
|
|
203
|
+
covered: false,
|
|
204
|
+
test_files: []
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
return results;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const testFiles = walkTestFiles(testDir);
|
|
212
|
+
const testContents = new Map();
|
|
213
|
+
for (const file of testFiles) {
|
|
214
|
+
try {
|
|
215
|
+
testContents.set(file, fs.readFileSync(file, 'utf8').toLowerCase());
|
|
216
|
+
} catch {
|
|
217
|
+
// skip
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
for (const story of storyCriteria) {
|
|
222
|
+
const allCriteria = [...story.criteria, ...story.gherkin];
|
|
223
|
+
|
|
224
|
+
for (const criterion of allCriteria) {
|
|
225
|
+
// Extract key terms from the criterion (words > 3 chars, not stopwords)
|
|
226
|
+
const keywords = extractKeywords(criterion);
|
|
227
|
+
const matchingFiles = [];
|
|
228
|
+
|
|
229
|
+
for (const [file, content] of testContents) {
|
|
230
|
+
// Check if test file mentions the story ID
|
|
231
|
+
const hasStoryRef = content.includes(story.story_id.toLowerCase());
|
|
232
|
+
|
|
233
|
+
// Check keyword overlap (at least 50% of keywords present)
|
|
234
|
+
const keywordHits = keywords.filter(k => content.includes(k.toLowerCase()));
|
|
235
|
+
const keywordCoverage = keywords.length > 0
|
|
236
|
+
? keywordHits.length / keywords.length
|
|
237
|
+
: 0;
|
|
238
|
+
|
|
239
|
+
if (hasStoryRef || keywordCoverage >= 0.5) {
|
|
240
|
+
matchingFiles.push(path.relative(testDir, file));
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
results.push({
|
|
245
|
+
story_id: story.story_id,
|
|
246
|
+
criterion,
|
|
247
|
+
covered: matchingFiles.length > 0,
|
|
248
|
+
test_files: [...new Set(matchingFiles)]
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
return results;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Extract meaningful keywords from a text string.
|
|
258
|
+
*
|
|
259
|
+
* @param {string} text - Input text.
|
|
260
|
+
* @returns {string[]}
|
|
261
|
+
*/
|
|
262
|
+
function extractKeywords(text) {
|
|
263
|
+
const stopwords = new Set([
|
|
264
|
+
'the', 'a', 'an', 'is', 'are', 'was', 'were', 'be', 'been', 'being',
|
|
265
|
+
'have', 'has', 'had', 'do', 'does', 'did', 'will', 'would', 'shall',
|
|
266
|
+
'should', 'may', 'might', 'must', 'can', 'could', 'and', 'but', 'or',
|
|
267
|
+
'nor', 'not', 'so', 'yet', 'both', 'either', 'neither', 'each',
|
|
268
|
+
'every', 'all', 'any', 'few', 'more', 'most', 'other', 'some',
|
|
269
|
+
'such', 'than', 'too', 'very', 'just', 'that', 'this', 'these',
|
|
270
|
+
'those', 'with', 'from', 'into', 'for', 'about', 'given', 'when',
|
|
271
|
+
'then', 'user', 'system'
|
|
272
|
+
]);
|
|
273
|
+
|
|
274
|
+
return text
|
|
275
|
+
.toLowerCase()
|
|
276
|
+
.replace(/[^a-z0-9\s]/g, ' ')
|
|
277
|
+
.split(/\s+/)
|
|
278
|
+
.filter(w => w.length > 3 && !stopwords.has(w));
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Compute UAT coverage statistics.
|
|
283
|
+
*
|
|
284
|
+
* @param {string} prdPath - Path to the PRD file.
|
|
285
|
+
* @param {string} testDir - Path to the test directory.
|
|
286
|
+
* @returns {object} Coverage results.
|
|
287
|
+
*/
|
|
288
|
+
function computeUATCoverage(prdPath, testDir) {
|
|
289
|
+
if (!fs.existsSync(prdPath)) {
|
|
290
|
+
throw new Error(`PRD not found: ${prdPath}`);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const prdContent = fs.readFileSync(prdPath, 'utf8');
|
|
294
|
+
const storyCriteria = extractAcceptanceCriteria(prdContent);
|
|
295
|
+
const storyIds = storyCriteria.map(s => s.story_id);
|
|
296
|
+
|
|
297
|
+
// Story-level coverage (does the test suite reference this story?)
|
|
298
|
+
const storyCoverage = scanTestCoverage(testDir, storyIds);
|
|
299
|
+
|
|
300
|
+
// Criteria-level coverage (are individual AC items addressed?)
|
|
301
|
+
const criteriaResults = matchCriteriaToTests(storyCriteria, testDir);
|
|
302
|
+
|
|
303
|
+
const totalCriteria = criteriaResults.length;
|
|
304
|
+
const coveredCriteria = criteriaResults.filter(r => r.covered).length;
|
|
305
|
+
const criteriaCoveragePct = totalCriteria > 0
|
|
306
|
+
? Math.round((coveredCriteria / totalCriteria) * 100)
|
|
307
|
+
: 100;
|
|
308
|
+
|
|
309
|
+
const totalStories = storyIds.length;
|
|
310
|
+
const coveredStories = storyIds.filter(id => {
|
|
311
|
+
const entry = storyCoverage.get(id);
|
|
312
|
+
return entry && entry.files.length > 0;
|
|
313
|
+
});
|
|
314
|
+
const storyCoveragePct = totalStories > 0
|
|
315
|
+
? Math.round((coveredStories.length / totalStories) * 100)
|
|
316
|
+
: 100;
|
|
317
|
+
|
|
318
|
+
return {
|
|
319
|
+
total_stories: totalStories,
|
|
320
|
+
covered_stories: coveredStories.length,
|
|
321
|
+
story_coverage_pct: storyCoveragePct,
|
|
322
|
+
total_criteria: totalCriteria,
|
|
323
|
+
covered_criteria: coveredCriteria,
|
|
324
|
+
criteria_coverage_pct: criteriaCoveragePct,
|
|
325
|
+
story_details: storyCriteria.map(s => ({
|
|
326
|
+
story_id: s.story_id,
|
|
327
|
+
criteria_count: s.criteria.length + s.gherkin.length,
|
|
328
|
+
test_files: (storyCoverage.get(s.story_id) || { files: [] }).files
|
|
329
|
+
})),
|
|
330
|
+
criteria_details: criteriaResults,
|
|
331
|
+
pass: criteriaCoveragePct >= 80
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Generate a UAT coverage report in markdown format.
|
|
337
|
+
*
|
|
338
|
+
* @param {string} prdPath - Path to the PRD file.
|
|
339
|
+
* @param {string} testDir - Path to the test directory.
|
|
340
|
+
* @returns {string} Markdown report.
|
|
341
|
+
*/
|
|
342
|
+
function generateUATReport(prdPath, testDir) {
|
|
343
|
+
const result = computeUATCoverage(prdPath, testDir);
|
|
344
|
+
|
|
345
|
+
let report = `# UAT Coverage Report: Acceptance Criteria → Tests\n\n`;
|
|
346
|
+
report += `**Story Coverage:** ${result.story_coverage_pct}% (${result.covered_stories}/${result.total_stories} stories)\n`;
|
|
347
|
+
report += `**Criteria Coverage:** ${result.criteria_coverage_pct}% (${result.covered_criteria}/${result.total_criteria} criteria)\n`;
|
|
348
|
+
report += `**Status:** ${result.pass ? '✅ PASS' : '❌ FAIL'} (threshold: 80%)\n\n`;
|
|
349
|
+
|
|
350
|
+
report += `## Story Summary\n\n`;
|
|
351
|
+
report += `| Story | Criteria | Test Files | Status |\n`;
|
|
352
|
+
report += `|-------|----------|------------|--------|\n`;
|
|
353
|
+
|
|
354
|
+
for (const story of result.story_details) {
|
|
355
|
+
const status = story.test_files.length > 0 ? '✅' : '❌';
|
|
356
|
+
const files = story.test_files.length > 0
|
|
357
|
+
? story.test_files.join(', ')
|
|
358
|
+
: '_none_';
|
|
359
|
+
report += `| ${story.story_id} | ${story.criteria_count} | ${files} | ${status} |\n`;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// Show uncovered criteria
|
|
363
|
+
const uncovered = result.criteria_details.filter(c => !c.covered);
|
|
364
|
+
if (uncovered.length > 0) {
|
|
365
|
+
report += `\n## Uncovered Acceptance Criteria\n\n`;
|
|
366
|
+
for (const item of uncovered) {
|
|
367
|
+
report += `- ❌ **${item.story_id}**: ${item.criterion}\n`;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// Show covered criteria
|
|
372
|
+
const covered = result.criteria_details.filter(c => c.covered);
|
|
373
|
+
if (covered.length > 0) {
|
|
374
|
+
report += `\n## Covered Acceptance Criteria\n\n`;
|
|
375
|
+
for (const item of covered) {
|
|
376
|
+
report += `- ✅ **${item.story_id}**: ${item.criterion} → ${item.test_files.join(', ')}\n`;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
return report;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// ─── CLI Entry Point ──────────────────────────────────────────────────────────
|
|
384
|
+
|
|
385
|
+
if (require.main === module) {
|
|
386
|
+
const args = process.argv.slice(2);
|
|
387
|
+
if (args.length < 2) {
|
|
388
|
+
console.error('Usage: node bin/lib/uat-coverage.js <prd-path> <test-dir>');
|
|
389
|
+
process.exit(2);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
try {
|
|
393
|
+
const report = generateUATReport(args[0], args[1]);
|
|
394
|
+
process.stdout.write(report + '\n');
|
|
395
|
+
const result = computeUATCoverage(args[0], args[1]);
|
|
396
|
+
process.exit(result.pass ? 0 : 1);
|
|
397
|
+
} catch (err) {
|
|
398
|
+
console.error(`Error: ${err.message}`);
|
|
399
|
+
process.exit(2);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
module.exports = {
|
|
404
|
+
extractAcceptanceCriteria,
|
|
405
|
+
scanTestCoverage,
|
|
406
|
+
matchCriteriaToTests,
|
|
407
|
+
extractKeywords,
|
|
408
|
+
computeUATCoverage,
|
|
409
|
+
generateUATReport,
|
|
410
|
+
walkTestFiles
|
|
411
|
+
};
|