@kodevibe/harness 0.11.1 → 0.11.2
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 +28 -4
- package/package.json +11 -2
- package/src/dependency-scan.js +194 -0
- package/src/guard.js +717 -0
- package/src/llm-bench.js +323 -0
- package/src/pack-check.js +47 -0
package/README.ko.md
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
> **AI 코딩 에이전트는 세션이 끝나면 모든 것을 잊습니다. kode:harness는 목표, 결정, 실패, 프로젝트 방향을 기억하게 합니다.**
|
|
13
13
|
|
|
14
|
-
AI 코딩 에이전트를 위한
|
|
14
|
+
AI 코딩 에이전트를 위한 pre-1.0 가드레일입니다. 실제 프로젝트 파일럿과 팀 사용을 기준으로 방향, proof, 세션 간 메모리를 유지합니다. 컨텍스트 부패를 방지하고, 프로젝트 방향을 강제하며, 세션 간 상태를 유지합니다. **Copilot, Claude, Cursor, Codex, Windsurf, Gemini** 지원. 의존성 제로.
|
|
15
15
|
|
|
16
16
|
> **에코시스템 내 위치.** kode:harness는 **kode:vibe** 에코시스템의 *실행(execution)* 레이어입니다 — 계획 레이어(PRD / 아키텍처 / ARB)와 인프라 레이어(CI / 런타임) 사이에 위치하며, 코딩 중 AI의 방향을 잡아주는 역할을 합니다. 다른 레이어는 선택이며, kode:harness만 독립적으로 쓸 수 있습니다.
|
|
17
17
|
|
|
@@ -32,6 +32,8 @@ npx @kodevibe/harness init # IDE 선택
|
|
|
32
32
|
|
|
33
33
|
끝입니다. 이제 AI는 영속적인 메모리, 방향 가드레일, 자기 교정 루프를 갖게 됩니다.
|
|
34
34
|
|
|
35
|
+
처음이라면 [docs/GETTING_STARTED.md](docs/GETTING_STARTED.md)부터 보세요. proof-first 흐름은 [docs/PROOF_LEDGER_WALKTHROUGH.md](docs/PROOF_LEDGER_WALKTHROUGH.md), 팀 도입은 [docs/TEAM_ONBOARDING.md](docs/TEAM_ONBOARDING.md), 설치/제거 안전 경계는 [docs/SAFETY_GUIDE.md](docs/SAFETY_GUIDE.md)에 정리했습니다.
|
|
36
|
+
|
|
35
37
|
v0.11부터는 **Proof-First Enforcement**가 Common Mode Confidence Loop 위에 추가됩니다. pm은 실행 가능한 proof를 계획해야 하고, lead는 증거 없이 Story를 완료 처리하지 않으며, reviewer는 proof가 없거나 실패하면 커밋 안내 전에 멈추고, state-check는 Proof Ledger 누락을 점검합니다.
|
|
36
38
|
|
|
37
39
|
<details>
|
|
@@ -170,6 +172,27 @@ npx @kodevibe/harness validate # state 파일에 실제 내용 확인
|
|
|
170
172
|
npx @kodevibe/harness uninstall --dry-run --ide vscode # 안전 제거 미리보기
|
|
171
173
|
```
|
|
172
174
|
|
|
175
|
+
소스 repo maintainer는 결정론 guard와 model-tier 증거 체크도 함께 실행할 수 있습니다:
|
|
176
|
+
|
|
177
|
+
```bash
|
|
178
|
+
npm run harness:check-pack
|
|
179
|
+
npm run harness:guard:all
|
|
180
|
+
npm run harness:guard:wrap-up -- --quiet
|
|
181
|
+
npm run harness:state-sync
|
|
182
|
+
npm run harness:dependency-scan
|
|
183
|
+
npm run harness:llm-bench:smoke
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
릴리즈 증거로 인정하려면 예시 fixture 대신 실제 3개 이상 모델 tier의 sealed result를 `docs/llm-bench-results.json`에 기록한 뒤 실행하세요. 각 run은 `docs/llm-bench-scenarios.json`의 표준 시나리오를 사용해야 하며, `capturedAt`, 일치하는 `promptHash`, `outputHash` 또는 `transcriptHash`가 필요합니다.
|
|
187
|
+
|
|
188
|
+
```bash
|
|
189
|
+
npm run harness:llm-bench:export
|
|
190
|
+
node scripts/llm-bench.js --collect-results --model-id local-small-2026-06-08 --tier constrained --outputs bench/r10/local-small-2026-06-08 --out docs/llm-bench-results.json --append
|
|
191
|
+
npm run harness:llm-bench:real
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
전체 3-model 증거 생성 절차는 `docs/LLM_BENCH_GUIDE.md`를 참고하세요.
|
|
195
|
+
|
|
173
196
|
---
|
|
174
197
|
|
|
175
198
|
## 지원 IDE
|
|
@@ -329,6 +352,12 @@ npx @kodevibe/harness init --team
|
|
|
329
352
|
|
|
330
353
|
---
|
|
331
354
|
|
|
355
|
+
## 문서
|
|
356
|
+
|
|
357
|
+
패키지 사용자는 이 README에서 시작하면 됩니다. 저장소 maintainer는 GitHub 전용 [docs/wiki-index.md](https://github.com/AIDD-Projects/harness/blob/main/docs/wiki-index.md)를 source repo 문서 지도로 사용합니다. 기여 가이드, 아키텍처 노트, 릴리스 기록, local docs hub 경계를 연결하되, source repo 안에 active harness instruction을 다시 설치하지 않습니다.
|
|
358
|
+
|
|
359
|
+
---
|
|
360
|
+
|
|
332
361
|
## 왜 만들었나
|
|
333
362
|
|
|
334
363
|
기존 AI 코딩 프레임워크는 **AI가 무엇을 하는지** — 코드 생성, 테스트 실행, 배포에 집중합니다. 하지만 진짜 문제는 능력이 아닙니다. **방향**입니다.
|
|
@@ -380,7 +409,7 @@ Bootstrap이 `docs/crew/`, `docs/PM/`, `docs/Analyst/`, `docs/ARB/`에서 crew
|
|
|
380
409
|
|
|
381
410
|
## 로드맵
|
|
382
411
|
|
|
383
|
-
kode:harness는 현재 **v0.11.
|
|
412
|
+
kode:harness는 현재 **v0.11.2** — v0.11의 proof-first와 uninstall safety 기반 위에 deterministic source-repo guardrail과 manifest-sealed R10 model benchmark workflow를 추가했습니다.
|
|
384
413
|
|
|
385
414
|
| 단계 | 버전 | 상태 | 초점 |
|
|
386
415
|
|------|------|------|------|
|
|
@@ -395,7 +424,8 @@ kode:harness는 현재 **v0.11.1** — manifest 기반 안전 제거를 추가
|
|
|
395
424
|
| **Drift Guard & Positioning** | v0.9.7 | ✅ 완료 | `harness/`↔`.github/` drift 가드, reviewer working-proof 게이트, kode:vibe 위치 안내, IDE 선택 가이드, project-brief 예시 |
|
|
396
425
|
| **Confidence Loop** | v0.10.0 | ✅ 완료 | Goal Card, Quiet Navigator, Evidence-Gated Progress Board, Proof Ledger, QA/content 회귀 테스트 |
|
|
397
426
|
| **Proof-First Enforcement** | v0.11.0 | ✅ 완료 | Mandatory Proof Plan, lead proof blocker, reviewer proof blocker, state-check Proof Ledger coverage |
|
|
398
|
-
| **Uninstall Safety** | v0.11.1 | ✅
|
|
427
|
+
| **Uninstall Safety** | v0.11.1 | ✅ 완료 | Manifest 기반 uninstall, state 기본 보존, shared owner 복원, purge cleanup |
|
|
428
|
+
| **Deterministic Release Guard** | v0.11.2 | ✅ 현재 | R1-R10 guard scripts, package-boundary scan, dependency-map scan, R10 manifest-sealed bench workflow |
|
|
399
429
|
| **Docs Bridge** | v0.11.1 | 🧪 Experimental | Project Docs Hub Index, docs-bridge 스킬, visibility 경계를 가진 로컬 docs hub 인덱스 |
|
|
400
430
|
| **Safety & Branding** | v0.9.6 | ✅ 완료 | init overwrite 백업, 배포 파일 pm 네이밍 정리, LICENSE 브랜딩 정리 |
|
|
401
431
|
| **Validation** | v1.0 | 🔜 다음 | 실사용 검증, 사용자 피드백 수집 |
|
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
> **Your AI coding agent forgets everything between sessions. kode:harness makes it remember — goals, decisions, failures, and project direction.**
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
Pre-1.0 guardrails for AI coding agents, designed for real project pilots and teams that need direction, proof, and memory across sessions. Prevents context rot, enforces project direction, and persists state across sessions. Works with **Copilot, Claude, Cursor, Codex, Windsurf, and Gemini**. Zero dependencies.
|
|
15
15
|
|
|
16
16
|
> **Where this fits.** kode:harness is the *execution* layer of the **kode:vibe** ecosystem — it sits between a planning layer (PRD / architecture / ARB) and an infrastructure layer (CI / runtime). kode:harness keeps the AI on direction while you code; the other layers are optional. You can use kode:harness alone.
|
|
17
17
|
|
|
@@ -32,6 +32,8 @@ npx @kodevibe/harness init # pick your IDE
|
|
|
32
32
|
|
|
33
33
|
That's it. Your AI now has persistent memory, direction guardrails, and self-correction loops.
|
|
34
34
|
|
|
35
|
+
New to kode:harness? Start with [docs/GETTING_STARTED.md](docs/GETTING_STARTED.md), then read [docs/PROOF_LEDGER_WALKTHROUGH.md](docs/PROOF_LEDGER_WALKTHROUGH.md) for the proof-first loop. Teams should also read [docs/TEAM_ONBOARDING.md](docs/TEAM_ONBOARDING.md). For install and uninstall trust boundaries, see [docs/SAFETY_GUIDE.md](docs/SAFETY_GUIDE.md).
|
|
36
|
+
|
|
35
37
|
v0.11 adds **Proof-First Enforcement** on top of the Common Mode Confidence Loop: pm must define runnable proof, lead cannot mark a Story done without passing evidence, reviewer blocks commit guidance when proof is missing or failing, and state-check audits Proof Ledger coverage.
|
|
36
38
|
|
|
37
39
|
<details>
|
|
@@ -182,6 +184,27 @@ npx @kodevibe/harness validate # verify state files have real content
|
|
|
182
184
|
npx @kodevibe/harness uninstall --dry-run --ide vscode # preview safe removal
|
|
183
185
|
```
|
|
184
186
|
|
|
187
|
+
Source repo maintainers can also run the deterministic guard and model-tier evidence checks:
|
|
188
|
+
|
|
189
|
+
```bash
|
|
190
|
+
npm run harness:check-pack
|
|
191
|
+
npm run harness:guard:all
|
|
192
|
+
npm run harness:guard:wrap-up -- --quiet
|
|
193
|
+
npm run harness:state-sync
|
|
194
|
+
npm run harness:dependency-scan
|
|
195
|
+
npm run harness:llm-bench:smoke
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
For release evidence, replace the example fixture with sealed results from at least three real model tiers. Each run must use the standard scenarios in `docs/llm-bench-scenarios.json` and include `capturedAt`, a matching `promptHash`, and `outputHash` or `transcriptHash`.
|
|
199
|
+
|
|
200
|
+
```bash
|
|
201
|
+
npm run harness:llm-bench:export
|
|
202
|
+
node scripts/llm-bench.js --collect-results --model-id local-small-2026-06-08 --tier constrained --outputs bench/r10/local-small-2026-06-08 --out docs/llm-bench-results.json --append
|
|
203
|
+
npm run harness:llm-bench:real
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
See `docs/LLM_BENCH_GUIDE.md` for the full three-model evidence workflow.
|
|
207
|
+
|
|
185
208
|
## Supported IDEs
|
|
186
209
|
|
|
187
210
|
Not sure which to pick? Use the IDE you already code in — each install path is generated from the same `harness/` source, so the underlying skills/agents are identical:
|
|
@@ -315,7 +338,7 @@ These 11 rules are enforced across all skills and agents. They form the quality
|
|
|
315
338
|
|
|
316
339
|
## Documentation
|
|
317
340
|
|
|
318
|
-
|
|
341
|
+
Package users can start with this README. Repository maintainers can use the GitHub-only [docs/wiki-index.md](https://github.com/AIDD-Projects/harness/blob/main/docs/wiki-index.md) as the source repo documentation map; it links the contribution guide, architecture notes, release history, and local docs hub boundaries without installing active harness instructions into this repo.
|
|
319
342
|
|
|
320
343
|
## Why We Built This
|
|
321
344
|
|
|
@@ -366,7 +389,7 @@ It adds a Project Docs Hub Index to `project-brief.md` with each local source, r
|
|
|
366
389
|
|
|
367
390
|
## Roadmap
|
|
368
391
|
|
|
369
|
-
kode:harness is at **v0.11.
|
|
392
|
+
kode:harness is at **v0.11.2** — adds deterministic source-repo guardrails and a manifest-sealed R10 model benchmark workflow on top of the v0.11 proof-first and uninstall safety foundation.
|
|
370
393
|
|
|
371
394
|
| Phase | Version | Status | Focus |
|
|
372
395
|
|---|---|---|---|
|
|
@@ -381,7 +404,8 @@ kode:harness is at **v0.11.1** — adds manifest-based safe uninstall so generat
|
|
|
381
404
|
| **Drift Guard & Positioning** | v0.9.7 | ✅ Done | `harness/`↔`.github/` drift detector, reviewer working-proof gate, kode:vibe positioning, IDE selection guide, project-brief example |
|
|
382
405
|
| **Confidence Loop** | v0.10.0 | ✅ Done | Goal Card, Quiet Navigator, Evidence-Gated Progress Board, Proof Ledger, QA/content regression tests |
|
|
383
406
|
| **Proof-First Enforcement** | v0.11.0 | ✅ Complete | Mandatory Proof Plan, lead proof blockers, reviewer proof blockers, state-check Proof Ledger coverage |
|
|
384
|
-
| **Uninstall Safety** | v0.11.1 | ✅
|
|
407
|
+
| **Uninstall Safety** | v0.11.1 | ✅ Complete | Manifest-based uninstall, default state preservation, shared owner restore, purge cleanup |
|
|
408
|
+
| **Deterministic Release Guard** | v0.11.2 | ✅ Current | R1-R10 guard scripts, package-boundary scan, dependency-map scan, R10 manifest-sealed bench workflow |
|
|
385
409
|
| **Docs Bridge** | v0.11.1 | 🧪 Experimental | Project Docs Hub Index, docs-bridge skill, local docs hub index with visibility boundaries |
|
|
386
410
|
| **Safety & Branding** | v0.9.6 | ✅ Done | init overwrite backups, shipped pm naming cleanup, LICENSE branding cleanup |
|
|
387
411
|
| **Validation** | v1.0 | 🔜 Next | Real-world project adoption, user feedback collection |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kodevibe/harness",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.2",
|
|
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
|
+
};
|