@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.
@@ -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 this skill **once at session end**, not after each individual skill. It aggregates the entire session's work into state files.
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 (Quick Summary update) and Step 6 (Agent Memory) if an agent was used
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: "Consider running `pivot` skill to formally update project direction. You can run it AFTER this wrap-up session completes."
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**: Step 2.5λ₯Ό μ™„λ£Œν•΄μ•Ό Step 3 이후λ₯Ό μ§„ν–‰ν•  수 μžˆμŠ΅λ‹ˆλ‹€. Validation Trackerκ°€ μ‘΄μž¬ν•˜λ©΄ λ°˜λ“œμ‹œ κ°±μ‹  ν›„ λ‹€μŒ λ‹¨κ³„λ‘œ μ§„ν–‰ν•˜μ„Έμš”.
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**: Validation Tracker의 KPI/FR/ARB μƒνƒœκ°€ 이 μ„Έμ…˜μ˜ μ™„λ£Œ Storyλ₯Ό μ •ν™•νžˆ λ°˜μ˜ν•˜λŠ”μ§€ 확인. λ―Έκ°±μ‹  ν•­λͺ©μ΄ 있으면 μ¦‰μ‹œ κ°±μ‹  ν›„ λ‹€μŒ λ‹¨κ³„λ‘œ μ§„ν–‰.
73
+ 5. **Self-check**: KPI/FR/ARB μƒνƒœκ°€ μ™„λ£Œ Story와 μΌμΉ˜ν•΄μ•Ό ν•©λ‹ˆλ‹€. λˆ„λ½ μ‹œ μ¦‰μ‹œ κ°±μ‹ .
74
74
 
75
- > β›” Validation Tracker κ°±μ‹  없이 Step 3으둜 μ§„ν–‰ν•˜μ§€ λ§ˆμ„Έμš”. 이 단계λ₯Ό κ±΄λ„ˆλ›°λ©΄ FR/KPI Coverageκ°€ μ‹€μ œ μ§„ν–‰ 상황과 λΆˆμΌμΉ˜ν•©λ‹ˆλ‹€.
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 β€” do not double-count. Check `docs/project-state.md` Recent Changes for debug entries from this session to determine if a pattern was already recorded.
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**: Step 3 μ™„λ£Œ μ‹œ `docs/failure-patterns.md`에 μ΅œμ†Œ ν•˜λ‚˜μ˜ FP ν•­λͺ©μ΄ μžˆμ–΄μ•Ό ν•©λ‹ˆλ‹€ (이 μ„Έμ…˜μ—μ„œ μ‹€νŒ¨κ°€ μ—†μ—ˆμœΌλ©΄ κΈ°μ‘΄ ν•­λͺ© ν™•μΈλ§Œ).
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`에 이 μ„Έμ…˜μ—μ„œ μƒˆλ‘œ μΆ”κ°€ν•œ λͺ¨λ“ˆμ΄ λͺ¨λ‘ λ“±λ‘λ˜μ—ˆλŠ”μ§€ 확인. λˆ„λ½ μ‹œ μ¦‰μ‹œ μΆ”κ°€. state-checkκ°€ PASS λ˜λŠ” WARN을 λ°˜ν™˜ν•΄μ•Ό λ‹€μŒ λ‹¨κ³„λ‘œ μ§„ν–‰ν•©λ‹ˆλ‹€.
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 filesystem paths, vault names, page IDs, and resolver details out of tracked state; use `.harness/docs-bridge.local.*`.
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. If no proof exists, write `[PROOF-GAP] Proof missing` in the wrap-up report and recommend returning to `reviewer`; do not claim the Story is complete.
164
- 5. If `[PROOF-GAP]` exists, STOP before Step 5.65. Do not auto-commit state files that mark a Story done/reviewed without passing proof. Revert the Story to Proof Pending or return to `reviewer`.
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 commit fails (nothing to commit), skip β€” state files were already committed
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"` to find unpushed commits
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 β€” unpushed work is invisible to teammates
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 "원격 μ €μž₯μ†Œκ°€ μ„€μ •λ˜μ§€ μ•Šμ•˜μŠ΅λ‹ˆλ‹€. νŒ€ ν˜‘μ—… μ‹œ remote 섀정이 ν•„μš”ν•©λ‹ˆλ‹€."
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 already exist alongside placeholders, APPEND new entries and remove only the placeholder comments. Do not overwrite existing real data.
203
- - **When does initialization happen?**: On the FIRST session where the agent is used AND wrap-up is invoked. If an agent is never used, its memory stays as a placeholder indefinitely β€” this is expected.
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 found, update statistics (total reviews +1, auto-fixes, escalations)
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 β€” concise enough for the next session to scan instantly
285
+ - Quick Summary must be exactly 3 lines
279
286
 
280
287
  ## Enforced Rules
281
288
 
282
- - **Session Handoff**: Before ending a session, docs/project-state.md Quick Summary MUST be updated. Never leave the project in an undocumented state.
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 (per project-brief.md β†’ Key Technical Decisions; default: main)
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 (per project-brief.md; default: team lead) or the Owner of the conflicting rows.
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 failure pattern (FP-NNN in .harness/failure-patterns.md) is likely to affect other developers:
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 (team wiki, PR description) so others can add it to their personal .harness/failure-patterns.md
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.1",
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:sync": "node bin/cli.js init --ide vscode --batch --dir . --overwrite"
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
+ };