@kodevibe/harness 0.11.0 → 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.
@@ -138,6 +138,15 @@ For each issue/error that occurred in this session:
138
138
 
139
139
  > **Self-check**: `docs/dependency-map.md`에 이 세션에서 새로 추가한 모듈이 모두 등록되었는지 확인. 누락 시 즉시 추가. state-check가 PASS 또는 WARN을 반환해야 다음 단계로 진행합니다.
140
140
 
141
+ ### Step 5.55: Refresh Project Docs Hub Index (if applicable)
142
+
143
+ Run only if user used/requested `docs-bridge`, or Project Docs Hub Index has real rows.
144
+
145
+ 1. For changed docs (`README*`, `docs/`, ADR/design/runbook/API specs), refresh indexed review/status.
146
+ 2. For new docs, add `proposed` rows or recommend `docs-bridge`.
147
+ 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.*`.
149
+
141
150
  ### Step 5.6: Resolve STATE-AUDIT Flags (if applicable)
142
151
 
143
152
  If the `reviewer` agent was run in this session and produced `[STATE-AUDIT]` flags:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kodevibe/harness",
3
- "version": "0.11.0",
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: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
+ };