@kodevibe/harness 0.11.1 β 0.11.3
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/README.ko.md +33 -3
- package/README.md +29 -4
- package/harness/agents/lead.md +12 -4
- package/harness/agents/pm.md +20 -12
- package/harness/agents/reviewer.md +26 -29
- package/harness/project-state.md +11 -0
- package/harness/skills/breakdown.md +2 -1
- package/harness/skills/pr-review.md +16 -0
- package/harness/skills/setup.md +1 -0
- package/harness/skills/state-check.md +55 -0
- package/harness/skills/wrap-up.md +32 -25
- package/package.json +11 -2
- package/src/dependency-scan.js +194 -0
- package/src/guard.js +1017 -0
- package/src/llm-bench.js +323 -0
- package/src/pack-check.js +47 -0
|
@@ -20,7 +20,7 @@ This is kode:harness's memory mechanism β without it, the same mistakes repeat
|
|
|
20
20
|
- After a review revealed a repeated mistake
|
|
21
21
|
- When the user explicitly asks to record a lesson
|
|
22
22
|
|
|
23
|
-
> **Timing**: Invoke
|
|
23
|
+
> **Timing**: Invoke once at session end; aggregate the whole session into state files.
|
|
24
24
|
|
|
25
25
|
## Procedure
|
|
26
26
|
|
|
@@ -35,7 +35,7 @@ If `git diff --stat` shows no changes and `git log` shows no new commits this se
|
|
|
35
35
|
- Report: "π Quiet session β status checks only, no code changes."
|
|
36
36
|
- Skip Step 3 (Failure Pattern Detection) β no code changes means no new failure patterns
|
|
37
37
|
- Skip Step 5 (features.md update) and Step 5.5 (dependency-map.md verify) β nothing changed
|
|
38
|
-
- Still execute Step 4
|
|
38
|
+
- Still execute Step 4 and Step 6 if an agent was used
|
|
39
39
|
- Still execute Step 2 (Direction Drift Check) β discussion-only drift is possible
|
|
40
40
|
|
|
41
41
|
### Step 2: Direction Drift Check
|
|
@@ -49,14 +49,14 @@ Before recording failures, verify that the session's work stayed aligned with pr
|
|
|
49
49
|
- Did the user explicitly change direction during this session? β Note for pivot recommendation
|
|
50
50
|
3. **If drift detected**:
|
|
51
51
|
- Add a warning to the Step 7 Report: `β οΈ Direction drift: [description of misalignment]`
|
|
52
|
-
- Recommend
|
|
52
|
+
- Recommend `pivot` after wrap-up
|
|
53
53
|
- Do NOT block β the wrap-up skill always completes
|
|
54
54
|
4. **If no drift**: Proceed silently (no output for this step)
|
|
55
55
|
|
|
56
56
|
<!-- CREW_MODE_START -->
|
|
57
57
|
#### Step 2.5: Validation Tracker Update (π£ Pipeline only) β οΈ MANDATORY
|
|
58
58
|
|
|
59
|
-
> **β Completion Gate**:
|
|
59
|
+
> **β Completion Gate**: Validation Trackerκ° μμΌλ©΄ κ°±μ ν Step 3μΌλ‘ μ§ννμΈμ.
|
|
60
60
|
|
|
61
61
|
If `docs/project-brief.md` contains a `## Validation Tracker` section with data:
|
|
62
62
|
|
|
@@ -70,9 +70,9 @@ If `docs/project-brief.md` contains a `## Validation Tracker` section with data:
|
|
|
70
70
|
- Did this session produce Stories/code that don't map to any FR or KPI? β warn
|
|
71
71
|
- Are there KPIs/FRs with no Stories after 2+ sprints? β warn as "β οΈ Unplanned KPI/FR risk"
|
|
72
72
|
- Include warnings in Step 7 Report
|
|
73
|
-
5. **Self-check**:
|
|
73
|
+
5. **Self-check**: KPI/FR/ARB μνκ° μλ£ Storyμ μΌμΉν΄μΌ ν©λλ€. λλ½ μ μ¦μ κ°±μ .
|
|
74
74
|
|
|
75
|
-
> β
|
|
75
|
+
> β Tracker κ°±μ μμ΄ Step 3μΌλ‘ μ§ννμ§ λ§μΈμ.
|
|
76
76
|
|
|
77
77
|
If no Validation Tracker β skip this step entirely.
|
|
78
78
|
<!-- CREW_MODE_END -->
|
|
@@ -83,7 +83,7 @@ For each issue/error that occurred in this session:
|
|
|
83
83
|
|
|
84
84
|
1. Read `docs/failure-patterns.md`
|
|
85
85
|
2. Check if this matches an existing pattern (FP-NNN):
|
|
86
|
-
- **If match found AND already incremented by `debug` in this session**: Skip
|
|
86
|
+
- **If match found AND already incremented by `debug` in this session**: Skip. Check Recent Changes to avoid double-count.
|
|
87
87
|
- **If match found AND NOT already incremented this session**: Increment the Frequency counter, add the Sprint/Story to "Occurred"
|
|
88
88
|
- **If new pattern**: Assign next FP-NNN number, create a new entry using this format:
|
|
89
89
|
|
|
@@ -100,7 +100,7 @@ For each issue/error that occurred in this session:
|
|
|
100
100
|
|
|
101
101
|
3. If the failure relates to a specific skill or agent, note it for that skill's checklist
|
|
102
102
|
|
|
103
|
-
> **Self-check**:
|
|
103
|
+
> **Self-check**: `docs/failure-patterns.md` has at least one FP entry; if no new failure, existing entries are enough.
|
|
104
104
|
|
|
105
105
|
### Step 4: Update docs/project-state.md β οΈ MANDATORY
|
|
106
106
|
|
|
@@ -116,6 +116,9 @@ For each issue/error that occurred in this session:
|
|
|
116
116
|
```
|
|
117
117
|
- [YYYY-MM-DD] S{N}-{M}: {what was done} (STATUS: DONE)
|
|
118
118
|
```
|
|
119
|
+
- Append compact changelog rows only. If creating the section, place it before `## Session Handoff Protocol` or EOF.
|
|
120
|
+
- Never insert it inside proof/evidence sections (`Proof Ledger`, durable UI evidence, smoke/manual proof).
|
|
121
|
+
- Self-check: no nested headings or UI/proof checklist bullets under `## Recent Changes`.
|
|
119
122
|
|
|
120
123
|
### Step 5: Update docs/features.md (if applicable)
|
|
121
124
|
|
|
@@ -136,7 +139,7 @@ For each issue/error that occurred in this session:
|
|
|
136
139
|
- WARN β include warnings in the final wrap-up report, then proceed
|
|
137
140
|
- FAIL β fix the listed drift (update affected state files), then re-run state-check until PASS or WARN
|
|
138
141
|
|
|
139
|
-
> **Self-check**: `docs/dependency-map.md
|
|
142
|
+
> **Self-check**: New modules are registered in `docs/dependency-map.md`; state-check is PASS/WARN.
|
|
140
143
|
|
|
141
144
|
### Step 5.55: Refresh Project Docs Hub Index (if applicable)
|
|
142
145
|
|
|
@@ -145,7 +148,7 @@ Run only if user used/requested `docs-bridge`, or Project Docs Hub Index has rea
|
|
|
145
148
|
1. For changed docs (`README*`, `docs/`, ADR/design/runbook/API specs), refresh indexed review/status.
|
|
146
149
|
2. For new docs, add `proposed` rows or recommend `docs-bridge`.
|
|
147
150
|
3. Never write external hubs, invent targets, change visibility, or convert `local-only` / `pending` without explicit request.
|
|
148
|
-
4. Keep
|
|
151
|
+
4. Keep private paths/IDs/resolvers out of tracked state; use `.harness/docs-bridge.local.*`.
|
|
149
152
|
|
|
150
153
|
### Step 5.6: Resolve STATE-AUDIT Flags (if applicable)
|
|
151
154
|
|
|
@@ -160,8 +163,12 @@ Before session end, record the working proof that justified completion:
|
|
|
160
163
|
1. Read reviewer output or recent terminal evidence for passing tests/smoke proof.
|
|
161
164
|
2. Add one compact row to `docs/project-state.md` β `## Proof Ledger` for each completed Story.
|
|
162
165
|
3. Cross-check completed Stories against `## Evidence Summary` / `## Proof Ledger`.
|
|
163
|
-
4.
|
|
164
|
-
5.
|
|
166
|
+
4. Check `## Story Contracts`: completed Story rows must be `β
pass`/`proven`; otherwise `[CONTRACT-GAP]`.
|
|
167
|
+
5. UI/manual/smoke proof needs artifact/checklist or URL + exact counts/elements.
|
|
168
|
+
6. Keep durable UI evidence in its own section, never under `## Recent Changes`.
|
|
169
|
+
7. Split FR/KPI/ARB mappings need `Scope split approved: <reason>`.
|
|
170
|
+
8. If proof is missing, write `[PROOF-GAP]` and return to `reviewer`.
|
|
171
|
+
9. If `[PROOF-GAP]` or `[CONTRACT-GAP]` exists, STOP before Step 5.65.
|
|
165
172
|
|
|
166
173
|
Proof rows must stay short: Date, Story, Evidence, Result, Command / Observation. Do not paste long logs.
|
|
167
174
|
|
|
@@ -171,7 +178,7 @@ State file λ³κ²½μ¬νμ 컀λ°ν©λλ€. Learn μ€ν κ²°κ³Όκ° μ»€λ°λμ§
|
|
|
171
178
|
|
|
172
179
|
1. Stage state files: `git add docs/project-state.md docs/failure-patterns.md docs/features.md docs/dependency-map.md docs/agent-memory/`
|
|
173
180
|
2. Commit: `git commit -m "wrap-up: session lessons captured"`
|
|
174
|
-
3. If
|
|
181
|
+
3. If nothing to commit, skip.
|
|
175
182
|
|
|
176
183
|
> **Self-check**: `git status`μ docs/ μλ unstaged νμΌμ΄ μμ΄μΌ ν©λλ€.
|
|
177
184
|
|
|
@@ -179,16 +186,16 @@ State file λ³κ²½μ¬νμ 컀λ°ν©λλ€. Learn μ€ν κ²°κ³Όκ° μ»€λ°λμ§
|
|
|
179
186
|
|
|
180
187
|
Before ending the session, check for unpushed commits:
|
|
181
188
|
|
|
182
|
-
1. Run `git log --oneline @{u}..HEAD 2>/dev/null || echo "no upstream"`
|
|
189
|
+
1. Run `git log --oneline @{u}..HEAD 2>/dev/null || echo "no upstream"`
|
|
183
190
|
2. **If unpushed commits exist**:
|
|
184
191
|
- List the commits: `git log --oneline @{u}..HEAD`
|
|
185
192
|
- Solo mode: Recommend push β `git push origin {branch}`
|
|
186
|
-
- Team mode: **Strongly recommend** push
|
|
193
|
+
- Team mode: **Strongly recommend** push
|
|
187
194
|
- Warn: "β οΈ {N}κ°μ 컀λ°μ΄ pushλμ§ μμμ΅λλ€. μμ
λ¬Όμ μ격μ λ°±μ
νμΈμ."
|
|
188
195
|
3. **If no upstream configured** (`no upstream`):
|
|
189
196
|
- Check if remote exists: `git remote -v`
|
|
190
197
|
- If remote exists: Suggest `git push -u origin {branch}` (first push)
|
|
191
|
-
- If no remote: Note "μ격 μ μ₯μκ° μ€μ λμ§ μμμ΅λλ€.
|
|
198
|
+
- If no remote: Note "μ격 μ μ₯μκ° μ€μ λμ§ μμμ΅λλ€."
|
|
192
199
|
4. **If all commits are pushed**: Skip (no output)
|
|
193
200
|
|
|
194
201
|
### Step 6: Update Agent Memory (if applicable)
|
|
@@ -199,15 +206,15 @@ If an agent (reviewer, pm, lead, architect) was used in this session, update its
|
|
|
199
206
|
2. **Auto-initialize if needed**: If the file only contains `<!-- Example entries` placeholder comments and no real data:
|
|
200
207
|
- Replace the placeholder block with actual entries from this session
|
|
201
208
|
- Initialize statistics counters with real values
|
|
202
|
-
- If real entries
|
|
203
|
-
-
|
|
209
|
+
- If real entries exist, APPEND and remove only placeholder comments.
|
|
210
|
+
- Initialize on the first wrap-up session where the agent was used.
|
|
204
211
|
- Example transformation:
|
|
205
212
|
```
|
|
206
213
|
Before: <!-- Example entries (replace with real findings after first review):
|
|
207
214
|
After: - [S1-1] Mock sync missed for UserService interface change
|
|
208
215
|
```
|
|
209
216
|
3. **Append session learnings** to the appropriate section:
|
|
210
|
-
- **reviewer.md**: Add review patterns
|
|
217
|
+
- **reviewer.md**: Add review patterns and update statistics
|
|
211
218
|
- **pm.md**: Record estimation accuracy (planned vs actual), note architecture discoveries
|
|
212
219
|
- **lead.md**: Update velocity (stories done/planned), record any scope drift incidents
|
|
213
220
|
- **architect.md**: Record design decisions made, module boundary insights, anti-patterns observed
|
|
@@ -275,11 +282,11 @@ If crew artifacts were used this session (π£ pipeline), also append:
|
|
|
275
282
|
- Keep failure pattern descriptions concise (1-2 sentences for Cause and Prevention)
|
|
276
283
|
- If no failures occurred, skip Step 2 and just update state files
|
|
277
284
|
- Do not modify source code β this skill only updates state files
|
|
278
|
-
- Quick Summary must be exactly 3 lines
|
|
285
|
+
- Quick Summary must be exactly 3 lines
|
|
279
286
|
|
|
280
287
|
## Enforced Rules
|
|
281
288
|
|
|
282
|
-
- **Session Handoff**: Before ending
|
|
289
|
+
- **Session Handoff**: Before ending, update docs/project-state.md Quick Summary.
|
|
283
290
|
- **State File Size Limits**: Keep state files compact for LLM context windows:
|
|
284
291
|
- docs/project-brief.md: Max 200 lines
|
|
285
292
|
- docs/project-state.md: Max 300 lines (archive completed sprints)
|
|
@@ -302,7 +309,7 @@ If crew artifacts were used this session (π£ pipeline), also append:
|
|
|
302
309
|
## Team Mode: Session Wrap-up
|
|
303
310
|
|
|
304
311
|
### Pre-Pull (mandatory before any shared file edit)
|
|
305
|
-
1. Run `git pull` on the default branch before updating docs/features.md or docs/dependency-map.md (
|
|
312
|
+
1. Run `git pull` on the default branch before updating docs/features.md or docs/dependency-map.md (default: main)
|
|
306
313
|
2. If merge conflicts occur, follow the **Merge Conflict SOP** below
|
|
307
314
|
|
|
308
315
|
### Merge Conflict SOP
|
|
@@ -319,7 +326,7 @@ When `git pull` causes merge conflicts in shared state files:
|
|
|
319
326
|
| `docs/failure-patterns.md` | Keep BOTH entries, deduplicate by FP-NNN number |
|
|
320
327
|
3. **After resolving**: `git add <resolved-files> && git commit`
|
|
321
328
|
4. **Verify**: Re-read the resolved file and confirm no data was lost
|
|
322
|
-
5. **If unsure**: Do NOT force-resolve. Ask the designated authority
|
|
329
|
+
5. **If unsure**: Do NOT force-resolve. Ask the designated authority or row Owner.
|
|
323
330
|
|
|
324
331
|
### Owner-Scoped Updates
|
|
325
332
|
- **docs/features.md**: only update rows where Owner = you
|
|
@@ -327,7 +334,7 @@ When `git pull` causes merge conflicts in shared state files:
|
|
|
327
334
|
- **Personal files** (.harness/project-state.md, .harness/failure-patterns.md, .harness/agent-memory/): update freely β no coordination needed
|
|
328
335
|
|
|
329
336
|
### Failure Pattern Promotion
|
|
330
|
-
If a personal
|
|
337
|
+
If a personal FP in `.harness/failure-patterns.md` may affect others:
|
|
331
338
|
1. Discuss with the team (Slack, PR comment, etc.)
|
|
332
|
-
2. If agreed, add it to a shared location
|
|
339
|
+
2. If agreed, add it to a shared location so others can copy it.
|
|
333
340
|
<!-- TEAM_MODE_END -->
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kodevibe/harness",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.3",
|
|
4
4
|
"description": "kode:harness β harness engineering for keeping every developer's AI aligned on one project direction.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"llm",
|
|
@@ -46,8 +46,17 @@
|
|
|
46
46
|
},
|
|
47
47
|
"scripts": {
|
|
48
48
|
"test": "node --test tests/*.test.js",
|
|
49
|
+
"harness:check-pack": "node scripts/check-public-pack.js",
|
|
49
50
|
"harness:check-drift": "node scripts/check-harness-drift.js",
|
|
50
|
-
"harness:
|
|
51
|
+
"harness:dependency-scan": "node scripts/check-dependency-map.js",
|
|
52
|
+
"harness:guard": "node scripts/harness-guard.js",
|
|
53
|
+
"harness:guard:all": "node scripts/harness-guard.js --all",
|
|
54
|
+
"harness:guard:wrap-up": "node scripts/harness-guard.js --wrap-up",
|
|
55
|
+
"harness:llm-bench": "node scripts/llm-bench.js",
|
|
56
|
+
"harness:llm-bench:export": "node scripts/llm-bench.js --export-prompts --scenarios docs/llm-bench-scenarios.json --out bench/r10",
|
|
57
|
+
"harness:llm-bench:smoke": "node scripts/llm-bench.js docs/llm-bench-results.example.json",
|
|
58
|
+
"harness:llm-bench:real": "node scripts/llm-bench.js docs/llm-bench-results.json --require-real",
|
|
59
|
+
"harness:state-sync": "node scripts/harness-guard.js --state-sync"
|
|
51
60
|
},
|
|
52
61
|
"publishConfig": {
|
|
53
62
|
"access": "public"
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
const SOURCE_EXTENSIONS = new Set(['.js', '.jsx', '.ts', '.tsx', '.mjs', '.cjs']);
|
|
7
|
+
const DEFAULT_ROOTS = ['src', 'app', 'lib', 'packages', 'services'];
|
|
8
|
+
const SKIP_DIRS = new Set(['.git', '.harness', 'node_modules', 'dist', 'build', 'coverage', '.next', '.turbo']);
|
|
9
|
+
|
|
10
|
+
function toPosix(file) {
|
|
11
|
+
return String(file || '').replace(/\\/g, '/');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function stripCodeComments(content) {
|
|
15
|
+
return String(content || '')
|
|
16
|
+
.replace(/\/\*[\s\S]*?\*\//g, '')
|
|
17
|
+
.replace(/(^|[^:])\/\/.*$/gm, '$1');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function isSourceFile(file) {
|
|
21
|
+
return SOURCE_EXTENSIONS.has(path.extname(file));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function walkSourceFiles(cwd, roots = DEFAULT_ROOTS) {
|
|
25
|
+
const files = [];
|
|
26
|
+
|
|
27
|
+
function walk(dir) {
|
|
28
|
+
if (!fs.existsSync(dir)) return;
|
|
29
|
+
for (const name of fs.readdirSync(dir)) {
|
|
30
|
+
if (SKIP_DIRS.has(name)) continue;
|
|
31
|
+
const full = path.join(dir, name);
|
|
32
|
+
const st = fs.statSync(full);
|
|
33
|
+
if (st.isDirectory()) walk(full);
|
|
34
|
+
else if (st.isFile() && isSourceFile(full)) files.push(toPosix(path.relative(cwd, full)));
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
for (const root of roots) walk(path.join(cwd, root));
|
|
39
|
+
return files.sort();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function moduleKeyForFile(file) {
|
|
43
|
+
const parts = toPosix(file).split('/').filter(Boolean);
|
|
44
|
+
if (parts.length === 0) return '';
|
|
45
|
+
if (parts[0] === 'packages' || parts[0] === 'services') {
|
|
46
|
+
return parts.length >= 2 ? `${parts[0]}/${parts[1]}` : parts[0];
|
|
47
|
+
}
|
|
48
|
+
if (DEFAULT_ROOTS.includes(parts[0])) {
|
|
49
|
+
return parts.length >= 2 ? `${parts[0]}/${parts[1]}` : parts[0];
|
|
50
|
+
}
|
|
51
|
+
return parts[0];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function extractLocalSpecifiers(content) {
|
|
55
|
+
const clean = stripCodeComments(content);
|
|
56
|
+
const specs = [];
|
|
57
|
+
const patterns = [
|
|
58
|
+
/\bimport\s+(?:[^'"]+\s+from\s+)?['"]([^'"]+)['"]/g,
|
|
59
|
+
/\bexport\s+[^'"]+\s+from\s+['"]([^'"]+)['"]/g,
|
|
60
|
+
/\brequire\s*\(\s*['"]([^'"]+)['"]\s*\)/g,
|
|
61
|
+
/\bimport\s*\(\s*['"]([^'"]+)['"]\s*\)/g,
|
|
62
|
+
];
|
|
63
|
+
for (const re of patterns) {
|
|
64
|
+
for (const match of clean.matchAll(re)) {
|
|
65
|
+
const spec = match[1];
|
|
66
|
+
if (spec.startsWith('.')) specs.push(spec);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return specs;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function normalizeRelativeTarget(fromFile, specifier) {
|
|
73
|
+
const fromDir = path.posix.dirname(toPosix(fromFile));
|
|
74
|
+
return path.posix.normalize(path.posix.join(fromDir, specifier));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function scanDependencyGraph({ cwd = process.cwd(), files = null, roots = DEFAULT_ROOTS } = {}) {
|
|
78
|
+
const sourceFiles = files ? files.filter(isSourceFile).map(toPosix).sort() : walkSourceFiles(cwd, roots);
|
|
79
|
+
const modules = new Map();
|
|
80
|
+
|
|
81
|
+
for (const file of sourceFiles) {
|
|
82
|
+
const module = moduleKeyForFile(file);
|
|
83
|
+
if (!module) continue;
|
|
84
|
+
if (!modules.has(module)) modules.set(module, { module, files: new Set(), dependsOn: new Set() });
|
|
85
|
+
modules.get(module).files.add(file);
|
|
86
|
+
|
|
87
|
+
const abs = path.join(cwd, file);
|
|
88
|
+
if (!fs.existsSync(abs)) continue;
|
|
89
|
+
const content = fs.readFileSync(abs, 'utf8');
|
|
90
|
+
for (const spec of extractLocalSpecifiers(content)) {
|
|
91
|
+
const target = normalizeRelativeTarget(file, spec);
|
|
92
|
+
const dep = moduleKeyForFile(target);
|
|
93
|
+
if (dep && dep !== module) modules.get(module).dependsOn.add(dep);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return [...modules.values()]
|
|
98
|
+
.map((entry) => ({
|
|
99
|
+
module: entry.module,
|
|
100
|
+
files: [...entry.files].sort(),
|
|
101
|
+
dependsOn: [...entry.dependsOn].sort(),
|
|
102
|
+
}))
|
|
103
|
+
.sort((a, b) => a.module.localeCompare(b.module));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function markdownTableRows(section) {
|
|
107
|
+
const lines = String(section || '').split('\n').map((line) => line.trim()).filter((line) => line.startsWith('|'));
|
|
108
|
+
if (lines.length < 2) return [];
|
|
109
|
+
const headers = lines[0].replace(/^\|/, '').replace(/\|$/, '').split('|').map((cell) => cell.trim());
|
|
110
|
+
return lines.slice(2)
|
|
111
|
+
.filter((line) => !/^\|\s*[-:|\s]+\|?$/.test(line))
|
|
112
|
+
.map((line) => {
|
|
113
|
+
const cells = line.replace(/^\|/, '').replace(/\|$/, '').split('|').map((cell) => cell.trim());
|
|
114
|
+
const row = {};
|
|
115
|
+
headers.forEach((header, i) => { row[header] = cells[i] || ''; });
|
|
116
|
+
row.__raw = line;
|
|
117
|
+
return row;
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function section(content, header) {
|
|
122
|
+
const re = new RegExp(`^##\\s+${header}\\s*$`, 'm');
|
|
123
|
+
const match = re.exec(content);
|
|
124
|
+
if (!match) return '';
|
|
125
|
+
const rest = content.slice(match.index + match[0].length);
|
|
126
|
+
const next = /^##\s+/m.exec(rest);
|
|
127
|
+
return next ? rest.slice(0, next.index) : rest;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function splitDepends(value) {
|
|
131
|
+
return String(value || '')
|
|
132
|
+
.replace(/`/g, '')
|
|
133
|
+
.split(/(?:<br\s*\/?>|[,;]|\s{2,})/i)
|
|
134
|
+
.map((item) => item.trim())
|
|
135
|
+
.filter((item) => item && !/^[-ββ]$/.test(item) && !/^n\/a$/i.test(item) && !/^none$/i.test(item));
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function parseDependencyMap(content) {
|
|
139
|
+
const body = section(content, 'Module Registry')
|
|
140
|
+
|| section(content, 'Module Dependency Map')
|
|
141
|
+
|| section(content, 'Module Map')
|
|
142
|
+
|| content;
|
|
143
|
+
return markdownTableRows(body)
|
|
144
|
+
.map((row) => ({
|
|
145
|
+
module: row.Module || row.module || '',
|
|
146
|
+
dependsOn: splitDepends(row['Depends On'] || row.Depends || row.Dependencies || ''),
|
|
147
|
+
raw: row.__raw,
|
|
148
|
+
}))
|
|
149
|
+
.filter((row) => row.module);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function checkDependencyMapCoverage({ graph = [], dependencyMap = '' } = {}) {
|
|
153
|
+
const violations = [];
|
|
154
|
+
const rows = parseDependencyMap(dependencyMap);
|
|
155
|
+
const rowByModule = new Map(rows.map((row) => [row.module, row]));
|
|
156
|
+
|
|
157
|
+
for (const item of graph) {
|
|
158
|
+
const row = rowByModule.get(item.module);
|
|
159
|
+
if (!row) {
|
|
160
|
+
violations.push({
|
|
161
|
+
check: 'dependency-scan',
|
|
162
|
+
severity: 'error',
|
|
163
|
+
line: 0,
|
|
164
|
+
message: `Module ${item.module} exists in source files but is missing from dependency-map.md (R7 source dependency scan).`,
|
|
165
|
+
});
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
const mapped = row.dependsOn.join(' ');
|
|
169
|
+
for (const dep of item.dependsOn) {
|
|
170
|
+
if (!mapped.includes(dep)) {
|
|
171
|
+
violations.push({
|
|
172
|
+
check: 'dependency-scan',
|
|
173
|
+
severity: 'error',
|
|
174
|
+
line: 0,
|
|
175
|
+
message: `Module ${item.module} imports ${dep}, but dependency-map.md does not list it in Depends On (R7 source dependency scan).`,
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return violations;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
module.exports = {
|
|
185
|
+
DEFAULT_ROOTS,
|
|
186
|
+
isSourceFile,
|
|
187
|
+
walkSourceFiles,
|
|
188
|
+
moduleKeyForFile,
|
|
189
|
+
extractLocalSpecifiers,
|
|
190
|
+
normalizeRelativeTarget,
|
|
191
|
+
scanDependencyGraph,
|
|
192
|
+
parseDependencyMap,
|
|
193
|
+
checkDependencyMapCoverage,
|
|
194
|
+
};
|