@boyingliu01/xp-gate 0.12.9 → 0.12.12
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/adapter-common.sh +13 -18
- package/adapters/typescript.sh +87 -10
- package/gate-3.sh +1 -1
- package/hooks/adapter-common.sh +13 -18
- package/hooks/pre-commit +166 -27
- package/hooks/pre-push +231 -18
- package/lib/__tests__/doctor.test.js +65 -38
- package/lib/__tests__/sprint-discovery.test.ts +5 -4
- package/lib/__tests__/sprint-status.test.ts +5 -4
- package/lib/__tests__/tsconfig.json +8 -0
- package/lib/check-version.js +19 -4
- package/lib/init.js +24 -0
- package/lib/upgrade.js +64 -0
- package/mock-policy/AGENTS.md +3 -3
- package/mutation/AGENTS.md +3 -3
- package/mutation/runners/stryker-runner.ts +7 -5
- package/package.json +1 -1
- package/plugins/claude-code/.claude-plugin/plugin.json +1 -1
- package/plugins/claude-code/skills/delphi-review/AGENTS.md +3 -3
- package/plugins/claude-code/skills/sprint-flow/AGENTS.md +3 -3
- package/plugins/claude-code/skills/sprint-flow/SKILL.md +117 -14
- package/plugins/claude-code/skills/sprint-flow/__tests__/sprint-flow.test.cjs +444 -0
- package/plugins/claude-code/skills/sprint-flow/references/phase-2-build.md +104 -0
- package/plugins/claude-code/skills/test-specification-alignment/AGENTS.md +3 -3
- package/plugins/opencode/package.json +1 -1
- package/plugins/opencode/skills/delphi-review/AGENTS.md +3 -3
- package/plugins/opencode/skills/sprint-flow/AGENTS.md +3 -3
- package/plugins/opencode/skills/sprint-flow/SKILL.md +117 -14
- package/plugins/opencode/skills/sprint-flow/__tests__/sprint-flow.test.cjs +444 -0
- package/plugins/opencode/skills/sprint-flow/references/phase-2-build.md +104 -0
- package/plugins/opencode/skills/test-specification-alignment/AGENTS.md +3 -3
- package/plugins/qoder/plugin.json +1 -1
- package/plugins/qoder/skills/delphi-review/AGENTS.md +3 -3
- package/plugins/qoder/skills/sprint-flow/AGENTS.md +3 -3
- package/plugins/qoder/skills/test-specification-alignment/AGENTS.md +3 -3
- package/principles/AGENTS.md +3 -3
- package/principles/__tests__/baseline-storage.test.ts +3 -2
- package/principles/__tests__/baseline.test.ts +16 -14
- package/principles/__tests__/config.test.ts +1 -1
- package/principles/__tests__/types.test.ts +1 -0
- package/principles/adapters/__tests__/base.test.ts +8 -8
- package/principles/adapters/__tests__/cpp.test.ts +26 -26
- package/principles/adapters/__tests__/dart.test.ts +11 -11
- package/principles/adapters/__tests__/go.test.ts +9 -9
- package/principles/adapters/__tests__/java.test.ts +9 -9
- package/principles/adapters/__tests__/kotlin.test.ts +10 -10
- package/principles/adapters/__tests__/objectivec.test.ts +27 -27
- package/principles/adapters/__tests__/python.test.ts +11 -11
- package/principles/adapters/__tests__/swift.test.ts +9 -9
- package/principles/adapters/__tests__/typescript.test.ts +13 -13
- package/principles/config.ts +1 -1
- package/principles/rules/__tests__/clean-code/large-file.test.ts +10 -9
- package/skills/delphi-review/AGENTS.md +3 -3
- package/skills/sprint-flow/AGENTS.md +3 -3
- package/skills/sprint-flow/SKILL.md +117 -14
- package/skills/sprint-flow/__tests__/sprint-flow.test.cjs +444 -0
- package/skills/sprint-flow/references/phase-2-build.md +104 -0
- package/skills/test-specification-alignment/AGENTS.md +3 -3
|
@@ -20,6 +20,65 @@
|
|
|
20
20
|
|
|
21
21
|
---
|
|
22
22
|
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Uncommitted Changes Gate
|
|
26
|
+
|
|
27
|
+
**Purpose**: Prevent entering BUILD with uncommitted changes that could mix with sprint work.
|
|
28
|
+
|
|
29
|
+
**Execution**: Before entering Phase 2 BUILD, the orchestrator MUST check for uncommitted changes in the worktree.
|
|
30
|
+
|
|
31
|
+
### Gate Logic
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
# Check for uncommitted changes
|
|
35
|
+
UNCOMMITTED=$(git status --porcelain 2>/dev/null | wc -l | tr -d ' ')
|
|
36
|
+
|
|
37
|
+
if [ "$SKIP_UNCOMMITTED_GATE" = "1" ]; then
|
|
38
|
+
echo "[UNCOMMITTED-GATE] Skipped (SKIP_UNCOMMITTED_GATE=1)"
|
|
39
|
+
echo "{\"skipped\":true,\"reason\":\"SKIP_UNCOMMITTED_GATE=1\",\"timestamp\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"}" > .sprint-state/uncommitted-gate-log.json
|
|
40
|
+
elif [ "$UNCOMMITTED" -gt 0 ]; then
|
|
41
|
+
echo "⚠️ [UNCOMMITTED-GATE] Found ${UNCOMMITTED} uncommitted files in worktree:"
|
|
42
|
+
git status --short 2>/dev/null | head -20
|
|
43
|
+
echo ""
|
|
44
|
+
echo "Uncommitted changes may conflict with sprint work. Recommended actions:"
|
|
45
|
+
echo " 1. Commit changes: git add -A && git commit -m 'pre-sprint: save work before BUILD'"
|
|
46
|
+
echo " 2. Stash changes: git stash push -m 'pre-sprint stash'"
|
|
47
|
+
echo " 3. Skip gate: export SKIP_UNCOMMITTED_GATE=1 (not recommended)"
|
|
48
|
+
echo ""
|
|
49
|
+
echo "Logging to .sprint-state/uncommitted-gate-log.json"
|
|
50
|
+
echo "{\"blocked\":true,\"uncommitted_files\":${UNCOMMITTED},\"timestamp\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"}" > .sprint-state/uncommitted-gate-log.json
|
|
51
|
+
echo "[BLOCK] Uncommitted changes detected. Please commit, stash, or set SKIP_UNCOMMITTED_GATE=1 to continue."
|
|
52
|
+
exit 1
|
|
53
|
+
else
|
|
54
|
+
echo "✅ [UNCOMMITTED-GATE] Worktree clean. Proceeding to BUILD."
|
|
55
|
+
echo "{\"clean\":true,\"timestamp\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"}" > .sprint-state/uncommitted-gate-log.json
|
|
56
|
+
fi
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Escape Valve
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
# Skip the uncommitted changes gate (use with caution)
|
|
63
|
+
SKIP_UNCOMMITTED_GATE=1
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Log Format (`.sprint-state/uncommitted-gate-log.json`)
|
|
67
|
+
|
|
68
|
+
```json
|
|
69
|
+
{
|
|
70
|
+
"clean": true,
|
|
71
|
+
"blocked": false,
|
|
72
|
+
"skipped": false,
|
|
73
|
+
"uncommitted_files": 0,
|
|
74
|
+
"timestamp": "2026-07-05T10:00:00Z"
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
**Log location**: `.sprint-state/uncommitted-gate-log.json` — written on every gate execution for audit trail.
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
23
82
|
## TDD 强制执行
|
|
24
83
|
|
|
25
84
|
### Gate 5a-BLOCK: 新增文件测试强制
|
|
@@ -41,3 +100,48 @@
|
|
|
41
100
|
# 非 main/master 分支临时跳过 Gate 5a-BLOCK
|
|
42
101
|
SKIP_GATE_5A_BLOCK=1 git commit -m "message"
|
|
43
102
|
```
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## Timing & Stability
|
|
107
|
+
|
|
108
|
+
This section documents expected execution times and timeout handling for each Phase 2 sub-step to reduce execution timing stddev and prevent pipeline stalls.
|
|
109
|
+
|
|
110
|
+
### Expected Execution Times
|
|
111
|
+
|
|
112
|
+
| Step | Description | Expected Time | Timeout | On Timeout |
|
|
113
|
+
|------|-------------|--------------|---------|------------|
|
|
114
|
+
| DELPHI-GATE | Verify delphi-reviewed.json exists | 1-2s | 10s | BLOCK (critical gate) |
|
|
115
|
+
| ralph-loop (per REQ) | TDD + verification per requirement | 5-15 min | 30 min/REQ | Mark REQ as `timeout`, continue next REQ |
|
|
116
|
+
| parallel dispatch | Multi-agent parallel build | 10-30 min | 45 min | Collect partial results, continue |
|
|
117
|
+
| TDD cycle (per unit) | RED → GREEN → REFACTOR | 2-5 min | 10 min | Skip unit, log failure |
|
|
118
|
+
| freeze + blind-review | Code review in isolation | 5-10 min | 20 min | WARNING, continue |
|
|
119
|
+
| verification | Test suite + coverage check | 2-5 min | 15 min | Retry once, then BLOCK |
|
|
120
|
+
| cost monitor | Token cost accounting | <1s | 5s | Skip, log warning |
|
|
121
|
+
| Phase 2 total (lightweight) | ≤3 REQs | 15-30 min | 45 min | — |
|
|
122
|
+
| Phase 2 total (standard) | 4-10 REQs | 30-120 min | 150 min | — |
|
|
123
|
+
| Phase 2 total (complex) | >10 REQs | 60-240 min | 300 min | — |
|
|
124
|
+
|
|
125
|
+
### Stability Guidelines
|
|
126
|
+
|
|
127
|
+
1. **Timeout handling**: All Phase 2 sub-steps MUST have explicit timeouts. If a step times out, log the failure to `.sprint-state/phase-outputs/phase-2-errors.json` and continue to the next step (except DELPHI-GATE which is a hard BLOCK).
|
|
128
|
+
|
|
129
|
+
2. **Retry strategy**: For recoverable failures (verification, TDD cycle):
|
|
130
|
+
- First failure: log warning, retry once
|
|
131
|
+
- Second failure: log error, BLOCK and prompt user decision
|
|
132
|
+
- Do NOT retry more than twice for any single sub-step
|
|
133
|
+
|
|
134
|
+
3. **Parallel dispatch stability**: When using `--mode parallel`, if any agent fails:
|
|
135
|
+
- Collect partial results from successful agents
|
|
136
|
+
- Rerun failed agent(s) individually with `--mode ralph-loop`
|
|
137
|
+
- Do NOT rerun the entire parallel batch
|
|
138
|
+
|
|
139
|
+
4. **Cost monitor thresholds**:
|
|
140
|
+
- Token cost per REQ > 50,000 → WARNING (review REQ scope)
|
|
141
|
+
- Token cost per REQ > 100,000 → BLOCK (REQ too large, split into smaller units)
|
|
142
|
+
|
|
143
|
+
5. **StdDev reduction**: To reduce execution timing variability:
|
|
144
|
+
- Cache dependency installation results between REQs
|
|
145
|
+
- Reuse TDD scaffolding across similar REQ types
|
|
146
|
+
- Pre-compute code structure analysis once at Phase start
|
|
147
|
+
- Batch lint/format operations at the end, not per REQ
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# SKILLS/TEST-SPECIFICATION-ALIGNMENT KNOWLEDGE BASE
|
|
2
2
|
|
|
3
|
-
**Generated:** 2026-07-
|
|
4
|
-
**Commit:**
|
|
3
|
+
**Generated:** 2026-07-06
|
|
4
|
+
**Commit:** 132a12e
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.12.
|
|
6
|
+
**Version:** 0.12.12.0
|
|
7
7
|
|
|
8
8
|
## OVERVIEW
|
|
9
9
|
Test-Specification Alignment Engine — two-stage validation ensuring tests accurately reflect requirements and design specs.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "xp-gate",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.12",
|
|
4
4
|
"displayName": "XP-Gate",
|
|
5
5
|
"description": "Extreme Programming quality gates + AI workflow skills for Qoder. Includes 10 quality gates (Gate 0-9), Sprint Flow (11 phases), and Delphi multi-expert review (>=90% consensus).",
|
|
6
6
|
"author": {
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# SKILLS/DELPHI-REVIEW KNOWLEDGE BASE
|
|
2
2
|
|
|
3
|
-
**Generated:** 2026-07-
|
|
4
|
-
**Commit:**
|
|
3
|
+
**Generated:** 2026-07-06
|
|
4
|
+
**Commit:** 132a12e
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.12.
|
|
6
|
+
**Version:** 0.12.12.0
|
|
7
7
|
|
|
8
8
|
## OVERVIEW
|
|
9
9
|
Delphi Consensus Review — multi-round anonymous expert review (≥90% threshold, 3 experts from ≥2 providers, domestic models only). Supports design + code-walkthrough modes.
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# SKILLS/SPRINT-FLOW KNOWLEDGE BASE
|
|
2
2
|
|
|
3
|
-
**Generated:** 2026-07-
|
|
4
|
-
**Commit:**
|
|
3
|
+
**Generated:** 2026-07-06
|
|
4
|
+
**Commit:** 132a12e
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.12.
|
|
6
|
+
**Version:** 0.12.12.0
|
|
7
7
|
|
|
8
8
|
## OVERVIEW
|
|
9
9
|
**11-phase** development pipeline: ISOLATE → AUTO-ESTIMATE → THINK → PLAN → BUILD → REVIEW → USER ACCEPTANCE → FEEDBACK → SHIP → LAND → CLEANUP. Phase 2 default build mode is **ralph-loop** (REQ-level iteration, 40-67% token savings vs parallel). HARD-GATE in Phase 1: design must pass Delphi review (≥90% consensus) before any coding.
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# SKILLS/TEST-SPECIFICATION-ALIGNMENT KNOWLEDGE BASE
|
|
2
2
|
|
|
3
|
-
**Generated:** 2026-07-
|
|
4
|
-
**Commit:**
|
|
3
|
+
**Generated:** 2026-07-06
|
|
4
|
+
**Commit:** 132a12e
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.12.
|
|
6
|
+
**Version:** 0.12.12.0
|
|
7
7
|
|
|
8
8
|
## OVERVIEW
|
|
9
9
|
Test-Specification Alignment Engine — two-stage validation ensuring tests accurately reflect requirements and design specs.
|
package/principles/AGENTS.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# PRINCIPLES CHECKER MODULE
|
|
2
2
|
|
|
3
|
-
**Generated:** 2026-07-
|
|
4
|
-
**Commit:**
|
|
3
|
+
**Generated:** 2026-07-06
|
|
4
|
+
**Commit:** 132a12e
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.12.
|
|
6
|
+
**Version:** 0.12.12.0
|
|
7
7
|
|
|
8
8
|
## OVERVIEW
|
|
9
9
|
Clean Code & SOLID principles checker — **Gate 4** of pre-commit. 14 rules × 9 language adapters, SARIF 2.1.0 output. Houses the **Boy Scout Rule** enforcement engine (Gate 6) and warning-baseline storage.
|
|
@@ -154,8 +154,9 @@ describe('Baseline Storage', () => {
|
|
|
154
154
|
expect(stats.totalFiles).toBe(2);
|
|
155
155
|
expect(stats.totalWarnings).toBe(7);
|
|
156
156
|
expect(stats.averageWarningsPerFile).toBe(3.5);
|
|
157
|
-
|
|
158
|
-
expect(
|
|
157
|
+
const richStats = stats as { totalFiles: number; totalWarnings: number; averageWarningsPerFile: number; eslint: { totalWarnings: number; totalErrors: number } };
|
|
158
|
+
expect(richStats.eslint.totalWarnings).toBe(6);
|
|
159
|
+
expect(richStats.eslint.totalErrors).toBe(1);
|
|
159
160
|
});
|
|
160
161
|
|
|
161
162
|
it('creates baseline from files with warning data', async () => {
|
|
@@ -19,7 +19,7 @@ describe('BaselineEntry - lint tool fields (ruff, golangci, shellcheck)', () =>
|
|
|
19
19
|
lastAnalyzed: new Date().toISOString(),
|
|
20
20
|
},
|
|
21
21
|
};
|
|
22
|
-
expect(() => storage.validate(entry as Record<string, import('../baseline').BaselineEntry>)).not.toThrow();
|
|
22
|
+
expect(() => storage.validate(entry as unknown as Record<string, import('../baseline').BaselineEntry>)).not.toThrow();
|
|
23
23
|
});
|
|
24
24
|
|
|
25
25
|
it('accepts golangci field in BaselineEntry', () => {
|
|
@@ -31,7 +31,7 @@ describe('BaselineEntry - lint tool fields (ruff, golangci, shellcheck)', () =>
|
|
|
31
31
|
lastAnalyzed: new Date().toISOString(),
|
|
32
32
|
},
|
|
33
33
|
};
|
|
34
|
-
expect(() => storage.validate(entry as Record<string, import('../baseline').BaselineEntry>)).not.toThrow();
|
|
34
|
+
expect(() => storage.validate(entry as unknown as Record<string, import('../baseline').BaselineEntry>)).not.toThrow();
|
|
35
35
|
});
|
|
36
36
|
|
|
37
37
|
it('accepts shellcheck field in BaselineEntry', () => {
|
|
@@ -43,7 +43,7 @@ describe('BaselineEntry - lint tool fields (ruff, golangci, shellcheck)', () =>
|
|
|
43
43
|
lastAnalyzed: new Date().toISOString(),
|
|
44
44
|
},
|
|
45
45
|
};
|
|
46
|
-
expect(() => storage.validate(entry as Record<string, import('../baseline').BaselineEntry>)).not.toThrow();
|
|
46
|
+
expect(() => storage.validate(entry as unknown as Record<string, import('../baseline').BaselineEntry>)).not.toThrow();
|
|
47
47
|
});
|
|
48
48
|
|
|
49
49
|
it('rejects ruff entry with non-numeric warnings', () => {
|
|
@@ -55,7 +55,7 @@ describe('BaselineEntry - lint tool fields (ruff, golangci, shellcheck)', () =>
|
|
|
55
55
|
lastAnalyzed: new Date().toISOString(),
|
|
56
56
|
},
|
|
57
57
|
};
|
|
58
|
-
expect(() => storage.validate(entry as Record<string, import('../baseline').BaselineEntry>)).toThrow(/Invalid ruff properties/);
|
|
58
|
+
expect(() => storage.validate(entry as unknown as Record<string, import('../baseline').BaselineEntry>)).toThrow(/Invalid ruff properties/);
|
|
59
59
|
});
|
|
60
60
|
|
|
61
61
|
it('combines all lint tools in getSummaryStatistics', () => {
|
|
@@ -82,12 +82,13 @@ describe('BaselineEntry - lint tool fields (ruff, golangci, shellcheck)', () =>
|
|
|
82
82
|
|
|
83
83
|
expect(stats.totalFiles).toBe(3);
|
|
84
84
|
expect(stats.totalWarnings).toBe(10);
|
|
85
|
-
|
|
86
|
-
expect(
|
|
87
|
-
expect(
|
|
88
|
-
expect(
|
|
89
|
-
expect(
|
|
90
|
-
expect(
|
|
85
|
+
const richStats = stats as typeof stats & { ruff: { totalWarnings: number; totalErrors: number }; golangci: { totalWarnings: number; totalErrors: number }; shellcheck: { totalWarnings: number; totalErrors: number } };
|
|
86
|
+
expect(richStats.ruff.totalWarnings).toBe(5);
|
|
87
|
+
expect(richStats.ruff.totalErrors).toBe(2);
|
|
88
|
+
expect(richStats.golangci.totalWarnings).toBe(3);
|
|
89
|
+
expect(richStats.golangci.totalErrors).toBe(1);
|
|
90
|
+
expect(richStats.shellcheck.totalWarnings).toBe(2);
|
|
91
|
+
expect(richStats.shellcheck.totalErrors).toBe(0);
|
|
91
92
|
});
|
|
92
93
|
});
|
|
93
94
|
|
|
@@ -121,11 +122,12 @@ describe('BaselineStorage - extended coverage', () => {
|
|
|
121
122
|
},
|
|
122
123
|
};
|
|
123
124
|
|
|
124
|
-
const
|
|
125
|
+
const rawStats = storage.getSummaryStatistics(baseline);
|
|
126
|
+
const stats = rawStats as { totalFiles: number; totalWarnings: number; averageWarningsPerFile: number; ccn: { totalWarnings: number; totalMax: number } };
|
|
125
127
|
|
|
126
128
|
expect(stats.totalFiles).toBe(2);
|
|
127
|
-
expect(stats.ccn
|
|
128
|
-
expect(stats.ccn
|
|
129
|
+
expect(stats.ccn.totalWarnings).toBe(8);
|
|
130
|
+
expect(stats.ccn.totalMax).toBe(30);
|
|
129
131
|
});
|
|
130
132
|
|
|
131
133
|
it('returns averageWarningsPerFile of 0 for empty baseline', () => {
|
|
@@ -135,7 +137,7 @@ describe('BaselineStorage - extended coverage', () => {
|
|
|
135
137
|
expect(stats.totalFiles).toBe(0);
|
|
136
138
|
expect(stats.totalWarnings).toBe(0);
|
|
137
139
|
expect(stats.averageWarningsPerFile).toBe(0);
|
|
138
|
-
expect(stats.ccn).toBeUndefined();
|
|
140
|
+
expect('ccn' in stats ? (stats as Record<string, unknown>).ccn : undefined).toBeUndefined();
|
|
139
141
|
});
|
|
140
142
|
});
|
|
141
143
|
|
|
@@ -14,7 +14,7 @@ describe('config.ts - Configuration Loader', () => {
|
|
|
14
14
|
|
|
15
15
|
// Clean code thresholds from design Section 6
|
|
16
16
|
expect(config.rules['clean-code']['long-function'].threshold).toBe(50);
|
|
17
|
-
expect(config.rules['clean-code']['large-file'].threshold).toBe(
|
|
17
|
+
expect(config.rules['clean-code']['large-file'].threshold).toBe(1000);
|
|
18
18
|
expect(config.rules['clean-code']['god-class'].threshold).toBe(15);
|
|
19
19
|
expect(config.rules['clean-code']['deep-nesting'].threshold).toBe(4);
|
|
20
20
|
expect(config.rules['clean-code']['too-many-params'].threshold).toBe(7);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { describe, it, expect, vi } from 'vitest'
|
|
1
|
+
import { describe, it, expect, vi, type Mock } from 'vitest';;
|
|
2
2
|
import type { Adapter } from '../../types';
|
|
3
3
|
import { BaseAdapter } from '../base';
|
|
4
4
|
|
|
@@ -24,7 +24,7 @@ import { readFileSync } from 'fs';
|
|
|
24
24
|
describe('BaseAdapter - Default Implementation', () => {
|
|
25
25
|
|
|
26
26
|
it('should implement the Adapter interface', () => {
|
|
27
|
-
(readFileSync as
|
|
27
|
+
(readFileSync as Mock).mockReturnValue('mock file content\ntest line 2');
|
|
28
28
|
const adapter = new MockAdapter('./test.ts');
|
|
29
29
|
const implemented: Adapter = adapter as unknown as Adapter;
|
|
30
30
|
|
|
@@ -37,7 +37,7 @@ describe('BaseAdapter - Default Implementation', () => {
|
|
|
37
37
|
});
|
|
38
38
|
|
|
39
39
|
it('should detect language based on file extension', () => {
|
|
40
|
-
(readFileSync as
|
|
40
|
+
(readFileSync as Mock).mockReturnValue('file content');
|
|
41
41
|
|
|
42
42
|
const adapterTs = new MockAdapter('./test.ts');
|
|
43
43
|
expect(adapterTs.detectLanguage()).toBe('typescript');
|
|
@@ -56,35 +56,35 @@ describe('BaseAdapter - Default Implementation', () => {
|
|
|
56
56
|
});
|
|
57
57
|
|
|
58
58
|
it('should parse file AST correctly', () => {
|
|
59
|
-
(readFileSync as
|
|
59
|
+
(readFileSync as Mock).mockReturnValue('mock file content\ntest line 2');
|
|
60
60
|
const adapter = new MockAdapter('./test.ts');
|
|
61
61
|
const ast = adapter.parseAST();
|
|
62
62
|
expect(ast).toEqual({ content: 'mock file content\ntest line 2', astType: 'mock' });
|
|
63
63
|
});
|
|
64
64
|
|
|
65
65
|
it('should extract functions from AST', () => {
|
|
66
|
-
(readFileSync as
|
|
66
|
+
(readFileSync as Mock).mockReturnValue('');
|
|
67
67
|
const adapter = new MockAdapter('./test.ts');
|
|
68
68
|
const functions = adapter.extractFunctions();
|
|
69
69
|
expect(Array.isArray(functions)).toBe(true);
|
|
70
70
|
});
|
|
71
71
|
|
|
72
72
|
it('should extract classes from AST', () => {
|
|
73
|
-
(readFileSync as
|
|
73
|
+
(readFileSync as Mock).mockReturnValue('');
|
|
74
74
|
const adapter = new MockAdapter('./test.ts');
|
|
75
75
|
const classes = adapter.extractClasses();
|
|
76
76
|
expect(Array.isArray(classes)).toBe(true);
|
|
77
77
|
});
|
|
78
78
|
|
|
79
79
|
it('should count physical lines correctly', () => {
|
|
80
|
-
(readFileSync as
|
|
80
|
+
(readFileSync as Mock).mockReturnValue('line 1\nline 2\nline 3');
|
|
81
81
|
const adapter = new MockAdapter('./test.ts');
|
|
82
82
|
const lineCount = adapter.countLines();
|
|
83
83
|
expect(lineCount).toBe(3);
|
|
84
84
|
});
|
|
85
85
|
|
|
86
86
|
it('should throw error for unsupported file operations', () => {
|
|
87
|
-
(readFileSync as
|
|
87
|
+
(readFileSync as Mock).mockImplementation(() => {
|
|
88
88
|
throw new Error('Could not read file');
|
|
89
89
|
});
|
|
90
90
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { describe, it, expect, vi } from 'vitest'
|
|
1
|
+
import { describe, it, expect, vi, type Mock } from 'vitest';;
|
|
2
2
|
import { CppAdapter } from '../cpp';
|
|
3
3
|
|
|
4
4
|
type MockFunction = Record<string, unknown>;
|
|
@@ -13,7 +13,7 @@ import { readFileSync } from 'fs';
|
|
|
13
13
|
describe('CppAdapter', () => {
|
|
14
14
|
|
|
15
15
|
it('should implement the Adapter interface', () => {
|
|
16
|
-
(readFileSync as
|
|
16
|
+
(readFileSync as Mock).mockReturnValue('int main() { return 0; }');
|
|
17
17
|
const adapter = new CppAdapter('test.cpp');
|
|
18
18
|
|
|
19
19
|
expect(adapter).toHaveProperty('detectLanguage');
|
|
@@ -24,59 +24,59 @@ describe('CppAdapter', () => {
|
|
|
24
24
|
});
|
|
25
25
|
|
|
26
26
|
it('should detect language as cpp for .cpp files', () => {
|
|
27
|
-
(readFileSync as
|
|
27
|
+
(readFileSync as Mock).mockReturnValue('int main() { return 0; }');
|
|
28
28
|
const adapter = new CppAdapter('test.cpp');
|
|
29
29
|
const detected = adapter.detectLanguage();
|
|
30
30
|
expect(detected).toBe('cpp');
|
|
31
31
|
});
|
|
32
32
|
|
|
33
33
|
it('should detect language as cpp for .cxx files', () => {
|
|
34
|
-
(readFileSync as
|
|
34
|
+
(readFileSync as Mock).mockReturnValue('int main() { return 0; }');
|
|
35
35
|
const adapter = new CppAdapter('test.cxx');
|
|
36
36
|
const detected = adapter.detectLanguage();
|
|
37
37
|
expect(detected).toBe('cpp');
|
|
38
38
|
});
|
|
39
39
|
|
|
40
40
|
it('should detect language as cpp for .cc files', () => {
|
|
41
|
-
(readFileSync as
|
|
41
|
+
(readFileSync as Mock).mockReturnValue('int main() { return 0; }');
|
|
42
42
|
const adapter = new CppAdapter('test.cc');
|
|
43
43
|
const detected = adapter.detectLanguage();
|
|
44
44
|
expect(detected).toBe('cpp');
|
|
45
45
|
});
|
|
46
46
|
|
|
47
47
|
it('should detect language as cpp for .c files', () => {
|
|
48
|
-
(readFileSync as
|
|
48
|
+
(readFileSync as Mock).mockReturnValue('int main() { return 0; }');
|
|
49
49
|
const adapter = new CppAdapter('test.c');
|
|
50
50
|
const detected = adapter.detectLanguage();
|
|
51
51
|
expect(detected).toBe('cpp');
|
|
52
52
|
});
|
|
53
53
|
|
|
54
54
|
it('should detect language as cpp for .hpp files', () => {
|
|
55
|
-
(readFileSync as
|
|
55
|
+
(readFileSync as Mock).mockReturnValue('#pragma once');
|
|
56
56
|
const adapter = new CppAdapter('test.hpp');
|
|
57
57
|
const detected = adapter.detectLanguage();
|
|
58
58
|
expect(detected).toBe('cpp');
|
|
59
59
|
});
|
|
60
60
|
|
|
61
61
|
it('should detect language as cpp for .h files', () => {
|
|
62
|
-
(readFileSync as
|
|
62
|
+
(readFileSync as Mock).mockReturnValue('#pragma once');
|
|
63
63
|
const adapter = new CppAdapter('test.h');
|
|
64
64
|
const detected = adapter.detectLanguage();
|
|
65
65
|
expect(detected).toBe('cpp');
|
|
66
66
|
});
|
|
67
67
|
|
|
68
68
|
it('should parse C++ file AST correctly', () => {
|
|
69
|
-
(readFileSync as
|
|
69
|
+
(readFileSync as Mock).mockReturnValue('int main() { return 0; }');
|
|
70
70
|
const adapter = new CppAdapter('test.cpp');
|
|
71
71
|
const ast = adapter.parseAST();
|
|
72
72
|
expect(ast).toHaveProperty('content');
|
|
73
73
|
expect(ast).toHaveProperty('language');
|
|
74
74
|
expect(ast).toHaveProperty('filePath');
|
|
75
|
-
expect(ast.language).toBe('cpp');
|
|
75
|
+
expect((ast as { language: unknown }).language).toBe('cpp');
|
|
76
76
|
});
|
|
77
77
|
|
|
78
78
|
it('should extract functions from C++ code', () => {
|
|
79
|
-
(readFileSync as
|
|
79
|
+
(readFileSync as Mock).mockReturnValue(`
|
|
80
80
|
int add(int a, int b) {
|
|
81
81
|
return a + b;
|
|
82
82
|
}
|
|
@@ -92,7 +92,7 @@ int main() {
|
|
|
92
92
|
});
|
|
93
93
|
|
|
94
94
|
it('should handle C++ strings with special characters', () => {
|
|
95
|
-
(readFileSync as
|
|
95
|
+
(readFileSync as Mock).mockReturnValue(`
|
|
96
96
|
int main() {
|
|
97
97
|
const char* str = "Hello \\"World\\"";
|
|
98
98
|
char c = '\\'';
|
|
@@ -107,7 +107,7 @@ int main() {
|
|
|
107
107
|
});
|
|
108
108
|
|
|
109
109
|
it('should throw error when file cannot be read', () => {
|
|
110
|
-
(readFileSync as
|
|
110
|
+
(readFileSync as Mock).mockImplementation(() => {
|
|
111
111
|
throw new Error('Could not read file');
|
|
112
112
|
});
|
|
113
113
|
|
|
@@ -125,7 +125,7 @@ int main() {
|
|
|
125
125
|
describe('CppAdapter - extractCodeBlock edge cases', () => {
|
|
126
126
|
it('should skip single-line block comment before the opening brace', () => {
|
|
127
127
|
const src = `int foo() /* inline comment */ { return 1; }`;
|
|
128
|
-
(readFileSync as
|
|
128
|
+
(readFileSync as Mock).mockReturnValue(src);
|
|
129
129
|
const adapter = new CppAdapter('test.cpp');
|
|
130
130
|
const block = adapter.extractCodeBlock(0);
|
|
131
131
|
expect(block).toContain('{ return 1; }');
|
|
@@ -142,7 +142,7 @@ describe('CppAdapter - extractCodeBlock edge cases', () => {
|
|
|
142
142
|
' return 42;',
|
|
143
143
|
'}',
|
|
144
144
|
].join('\n');
|
|
145
|
-
(readFileSync as
|
|
145
|
+
(readFileSync as Mock).mockReturnValue(src);
|
|
146
146
|
const adapter = new CppAdapter('test.cpp');
|
|
147
147
|
const block = adapter.extractCodeBlock(0);
|
|
148
148
|
expect(block).toContain('return 42;');
|
|
@@ -151,7 +151,7 @@ describe('CppAdapter - extractCodeBlock edge cases', () => {
|
|
|
151
151
|
|
|
152
152
|
it('should ignore braces inside block comments', () => {
|
|
153
153
|
const src = `int foo() /* fake { brace } here */ { return 7; }`;
|
|
154
|
-
(readFileSync as
|
|
154
|
+
(readFileSync as Mock).mockReturnValue(src);
|
|
155
155
|
const adapter = new CppAdapter('test.cpp');
|
|
156
156
|
const block = adapter.extractCodeBlock(0);
|
|
157
157
|
expect(block).toContain('return 7;');
|
|
@@ -160,7 +160,7 @@ describe('CppAdapter - extractCodeBlock edge cases', () => {
|
|
|
160
160
|
|
|
161
161
|
it('should count nested braces correctly via braceCount++ path', () => {
|
|
162
162
|
const src = `void foo() { if (x) { return; } }`;
|
|
163
|
-
(readFileSync as
|
|
163
|
+
(readFileSync as Mock).mockReturnValue(src);
|
|
164
164
|
const adapter = new CppAdapter('test.cpp');
|
|
165
165
|
const block = adapter.extractCodeBlock(0);
|
|
166
166
|
expect(block).toBe('void foo() { if (x) { return; } }');
|
|
@@ -168,7 +168,7 @@ describe('CppAdapter - extractCodeBlock edge cases', () => {
|
|
|
168
168
|
|
|
169
169
|
it('should handle deeply nested braces (triple nesting)', () => {
|
|
170
170
|
const src = `int bar() { if (a) { while (b) { c++; } } return 0; }`;
|
|
171
|
-
(readFileSync as
|
|
171
|
+
(readFileSync as Mock).mockReturnValue(src);
|
|
172
172
|
const adapter = new CppAdapter('test.cpp');
|
|
173
173
|
const block = adapter.extractCodeBlock(0);
|
|
174
174
|
expect(block.trim().endsWith('}')).toBe(true);
|
|
@@ -190,7 +190,7 @@ describe('CppAdapter - extractCodeBlock edge cases', () => {
|
|
|
190
190
|
' return 0;',
|
|
191
191
|
'}',
|
|
192
192
|
].join('\n');
|
|
193
|
-
(readFileSync as
|
|
193
|
+
(readFileSync as Mock).mockReturnValue(src);
|
|
194
194
|
const adapter = new CppAdapter('test.cpp');
|
|
195
195
|
const block = adapter.extractCodeBlock(0);
|
|
196
196
|
expect(block.trim().endsWith('}')).toBe(true);
|
|
@@ -205,7 +205,7 @@ describe('CppAdapter - extractCodeBlock edge cases', () => {
|
|
|
205
205
|
' return 5;',
|
|
206
206
|
'}',
|
|
207
207
|
].join('\n');
|
|
208
|
-
(readFileSync as
|
|
208
|
+
(readFileSync as Mock).mockReturnValue(src);
|
|
209
209
|
const adapter = new CppAdapter('test.cpp');
|
|
210
210
|
const block = adapter.extractCodeBlock(0);
|
|
211
211
|
expect(block).toContain('return 5;');
|
|
@@ -223,7 +223,7 @@ describe('CppAdapter - extractCodeBlock edge cases', () => {
|
|
|
223
223
|
' int value_;',
|
|
224
224
|
'};',
|
|
225
225
|
].join('\n');
|
|
226
|
-
(readFileSync as
|
|
226
|
+
(readFileSync as Mock).mockReturnValue(src);
|
|
227
227
|
const adapter = new CppAdapter('test.cpp');
|
|
228
228
|
const functions = adapter.extractFunctions();
|
|
229
229
|
expect(Array.isArray(functions)).toBe(true);
|
|
@@ -240,7 +240,7 @@ describe('CppAdapter - extractCodeBlock edge cases', () => {
|
|
|
240
240
|
' int x;',
|
|
241
241
|
'};',
|
|
242
242
|
].join('\n');
|
|
243
|
-
(readFileSync as
|
|
243
|
+
(readFileSync as Mock).mockReturnValue(src);
|
|
244
244
|
const adapter = new CppAdapter('test.cpp');
|
|
245
245
|
const classes = adapter.extractClasses();
|
|
246
246
|
expect(Array.isArray(classes)).toBe(true);
|
|
@@ -250,13 +250,13 @@ describe('CppAdapter - extractCodeBlock edge cases', () => {
|
|
|
250
250
|
});
|
|
251
251
|
|
|
252
252
|
it('should count lines correctly', () => {
|
|
253
|
-
(readFileSync as
|
|
253
|
+
(readFileSync as Mock).mockReturnValue('line1\nline2\nline3');
|
|
254
254
|
const adapter = new CppAdapter('test.cpp');
|
|
255
255
|
expect(adapter.countLines()).toBe(3);
|
|
256
256
|
});
|
|
257
257
|
|
|
258
258
|
it('should fall back to super.detectLanguage for non-cpp extensions', () => {
|
|
259
|
-
(readFileSync as
|
|
259
|
+
(readFileSync as Mock).mockReturnValue('content');
|
|
260
260
|
const adapter = new CppAdapter('test.txt');
|
|
261
261
|
expect(adapter.detectLanguage()).toBe('unknown');
|
|
262
262
|
});
|
|
@@ -272,7 +272,7 @@ describe('CppAdapter - extractCodeBlock edge cases', () => {
|
|
|
272
272
|
' return -x;',
|
|
273
273
|
'}',
|
|
274
274
|
].join('\n');
|
|
275
|
-
(readFileSync as
|
|
275
|
+
(readFileSync as Mock).mockReturnValue(src);
|
|
276
276
|
const adapter = new CppAdapter('test.cpp');
|
|
277
277
|
const functions = adapter.extractFunctions();
|
|
278
278
|
expect(Array.isArray(functions)).toBe(true);
|
|
@@ -281,7 +281,7 @@ describe('CppAdapter - extractCodeBlock edge cases', () => {
|
|
|
281
281
|
|
|
282
282
|
it('should extract functions with scope prefix (e.g. MyClass::method)', () => {
|
|
283
283
|
const src = 'void MyClass::method() { return; }';
|
|
284
|
-
(readFileSync as
|
|
284
|
+
(readFileSync as Mock).mockReturnValue(src);
|
|
285
285
|
const adapter = new CppAdapter('test.cpp');
|
|
286
286
|
const functions = adapter.extractFunctions();
|
|
287
287
|
expect(Array.isArray(functions)).toBe(true);
|