@kodevibe/harness 0.11.3 → 0.11.5

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 CHANGED
@@ -201,7 +201,7 @@ npm run harness:llm-bench:real
201
201
 
202
202
  | IDE | 이럴 때 고르세요 | 디스패처 (always-on) | 스킬 | 에이전트 |
203
203
  |-----|--------------------|---------------------|------|----------|
204
- | **VS Code Copilot** | VS Code를 주로 쓰고 GitHub Copilot Chat 사용. | `.github/copilot-instructions.md` | `.github/skills/*/SKILL.md` | `.github/agents/*.agent.md` |
204
+ | **VS Code Copilot** | VS Code를 주로 쓰고 GitHub Copilot Chat 사용. | `.github/copilot-instructions.md` (+ 짧은 `AGENTS.md` anchor) | `.github/skills/*/SKILL.md` | `.github/agents/*.agent.md` |
205
205
  | **Claude Code** | 터미널/Claude Code CLI 선호. | `CLAUDE.md` (+ `.claude/rules/core.md`) | `.claude/skills/*/SKILL.md` | `.claude/agents/*.md` |
206
206
  | **Cursor** | Cursor 에디터 사용. | `.cursor/rules/core.mdc` (+ `AGENTS.md`) | `.agents/skills/*/SKILL.md` (cross-tool) | `.cursor/rules/<agent>.mdc` |
207
207
  | **Codex** | OpenAI Codex CLI 서브에이전트 사용. | `AGENTS.md` | `.agents/skills/*/SKILL.md` | `.codex/agents/*.toml` |
@@ -409,7 +409,7 @@ Bootstrap이 `docs/crew/`, `docs/PM/`, `docs/Analyst/`, `docs/ARB/`에서 crew
409
409
 
410
410
  ## 로드맵
411
411
 
412
- kode:harness는 현재 **v0.11.2** — v0.11의 proof-first와 uninstall safety 기반 위에 deterministic source-repo guardrail과 manifest-sealed R10 model benchmark workflow를 추가했습니다.
412
+ kode:harness는 현재 **v0.11.5** — R16 recovery guard 기반 위에 R17 governance hardening(Crew Validation Tracker drift, dependency-map interface log drift, VS Code root instruction anchoring)을 추가했습니다.
413
413
 
414
414
  | 단계 | 버전 | 상태 | 초점 |
415
415
  |------|------|------|------|
@@ -425,7 +425,10 @@ kode:harness는 현재 **v0.11.2** — v0.11의 proof-first와 uninstall safety
425
425
  | **Confidence Loop** | v0.10.0 | ✅ 완료 | Goal Card, Quiet Navigator, Evidence-Gated Progress Board, Proof Ledger, QA/content 회귀 테스트 |
426
426
  | **Proof-First Enforcement** | v0.11.0 | ✅ 완료 | Mandatory Proof Plan, lead proof blocker, reviewer proof blocker, state-check Proof Ledger coverage |
427
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 |
428
+ | **Deterministic Release Guard** | v0.11.2 | ✅ 완료 | R1-R10 guard scripts, package-boundary scan, dependency-map scan, R10 manifest-sealed bench workflow |
429
+ | **Experiment Hardening** | v0.11.3 | ✅ 완료 | R15 Recent Changes integrity, Wave Scope boundary drift checks, enum/filter coverage honesty |
430
+ | **Recovery Hardening** | v0.11.4 | ✅ 완료 | R16 false PASS claim guard, surface-specific Story Contract checks, reviewer dependency evidence, dirty wrap-up guard |
431
+ | **Governance Hardening** | v0.11.5 | ✅ 현재 | R17 Crew Validation Tracker sync, dependency-map interface log guard, VS Code AGENTS.md instruction anchor |
429
432
  | **Docs Bridge** | v0.11.1 | 🧪 Experimental | Project Docs Hub Index, docs-bridge 스킬, visibility 경계를 가진 로컬 docs hub 인덱스 |
430
433
  | **Safety & Branding** | v0.9.6 | ✅ 완료 | init overwrite 백업, 배포 파일 pm 네이밍 정리, LICENSE 브랜딩 정리 |
431
434
  | **Validation** | v1.0 | 🔜 다음 | 실사용 검증, 사용자 피드백 수집 |
package/README.md CHANGED
@@ -211,7 +211,7 @@ Not sure which to pick? Use the IDE you already code in — each install path is
211
211
 
212
212
  | IDE | Pick this if… | Dispatcher (always-on) | Skills | Agents |
213
213
  |-----|---------------|----------------------|--------|--------|
214
- | **VS Code Copilot** | You use VS Code daily and have GitHub Copilot Chat. | `.github/copilot-instructions.md` | `.github/skills/*/SKILL.md` | `.github/agents/*.agent.md` |
214
+ | **VS Code Copilot** | You use VS Code daily and have GitHub Copilot Chat. | `.github/copilot-instructions.md` (+ short `AGENTS.md` anchor) | `.github/skills/*/SKILL.md` | `.github/agents/*.agent.md` |
215
215
  | **Claude Code** | You prefer Claude in the terminal / Claude Code CLI. | `CLAUDE.md` (+ `.claude/rules/core.md`) | `.claude/skills/*/SKILL.md` | `.claude/agents/*.md` |
216
216
  | **Cursor** | You use Cursor as your editor. | `.cursor/rules/core.mdc` (+ `AGENTS.md`) | `.agents/skills/*/SKILL.md` (cross-tool) | `.cursor/rules/<agent>.mdc` |
217
217
  | **Codex** | You use OpenAI Codex CLI subagents. | `AGENTS.md` | `.agents/skills/*/SKILL.md` | `.codex/agents/*.toml` |
@@ -389,7 +389,7 @@ It adds a Project Docs Hub Index to `project-brief.md` with each local source, r
389
389
 
390
390
  ## Roadmap
391
391
 
392
- kode:harness is at **v0.11.3** — adds R15 experiment hardening for section integrity, Wave Scope drift, and filter coverage honesty on top of the v0.11 proof-first and deterministic release guard foundation.
392
+ kode:harness is at **v0.11.5** — adds R17 governance hardening for Crew Validation Tracker drift, dependency-map interface log drift, and VS Code root instruction anchoring on top of the R16 recovery guard foundation.
393
393
 
394
394
  | Phase | Version | Status | Focus |
395
395
  |---|---|---|---|
@@ -406,7 +406,9 @@ kode:harness is at **v0.11.3** — adds R15 experiment hardening for section int
406
406
  | **Proof-First Enforcement** | v0.11.0 | ✅ Complete | Mandatory Proof Plan, lead proof blockers, reviewer proof blockers, state-check Proof Ledger coverage |
407
407
  | **Uninstall Safety** | v0.11.1 | ✅ Complete | Manifest-based uninstall, default state preservation, shared owner restore, purge cleanup |
408
408
  | **Deterministic Release Guard** | v0.11.2 | ✅ Complete | R1-R10 guard scripts, package-boundary scan, dependency-map scan, R10 manifest-sealed bench workflow |
409
- | **Experiment Hardening** | v0.11.3 | ✅ Current | R15 Recent Changes integrity, Wave Scope boundary drift checks, enum/filter coverage honesty, R15 bench scenarios |
409
+ | **Experiment Hardening** | v0.11.3 | ✅ Complete | R15 Recent Changes integrity, Wave Scope boundary drift checks, enum/filter coverage honesty, R15 bench scenarios |
410
+ | **Recovery Hardening** | v0.11.4 | ✅ Complete | R16 false PASS claim guard, surface-specific Story Contract checks, reviewer dependency evidence, dirty wrap-up guard |
411
+ | **Governance Hardening** | v0.11.5 | ✅ Current | R17 Crew Validation Tracker sync, dependency-map interface log guard, VS Code AGENTS.md instruction anchor |
410
412
  | **Docs Bridge** | v0.11.1 | 🧪 Experimental | Project Docs Hub Index, docs-bridge skill, local docs hub index with visibility boundaries |
411
413
  | **Safety & Branding** | v0.9.6 | ✅ Done | init overwrite backups, shipped pm naming cleanup, LICENSE branding cleanup |
412
414
  | **Validation** | v1.0 | 🔜 Next | Real-world project adoption, user feedback collection |
@@ -157,6 +157,7 @@ When a Story contains multiple Tasks/Waves (from breakdown):
157
157
  - Only allowed files changed → continue.
158
158
  - Extra files changed → output `[SCOPE-DRIFT: WAVE_BOUNDARY]`, record the extra files, and ask whether the Wave should be collapsed/approved before proceeding.
159
159
  - Record a mini Proof Ledger row inline: Evidence, Result, Command / Observation
160
+ - For semantic contracts with "always/every/all/항상", include public surfaces in the Wave proof target (for example: `create/list/get/resolve` return paths). A test that covers only one return path is partial proof.
160
161
  - Only after verification passes, prompt: "Wave {N} 완료 (tests pass). Wave {N+1}로 넘어갈까요?"
161
162
  - If tests fail → output `[BLOCKER: WAVE_PROOF_FAILING]`, fix within the current Wave, and do NOT advance.
162
163
  - This prevents context overload from modifying too many modules simultaneously
@@ -42,24 +42,22 @@ One of:
42
42
 
43
43
  ### Step 0: State File Readiness
44
44
 
45
- Before proceeding, verify that required state files have content (not just TODO placeholders):
45
+ Before proceeding, verify required state files have content:
46
46
  - `docs/project-brief.md` — Must have Vision and Goals filled
47
47
  - `docs/features.md` — Must have at least one feature row
48
48
  - `docs/dependency-map.md` — Must have at least one module row (for existing projects)
49
49
 
50
- If ALL files are empty/placeholder-only → **Stop and run the `setup` skill first.** Report: "State files are empty. Running setup to onboard this project."
51
- If `docs/project-brief.md` alone is empty → **Stop.** Without Vision/Goals, pm cannot check Non-Goals or provide direction guard. Run `setup` first.
50
+ If ALL files are empty/placeholder-only → **Stop and run `setup` first.**
51
+ If `docs/project-brief.md` alone is empty → **Stop.** Without Vision/Goals, pm cannot provide direction guard.
52
52
 
53
53
  > Step 0 runs BEFORE Step 1. If Step 0 stops (empty brief), Step 1 never executes. When Step 0 passes, Step 1 reads the now-confirmed non-empty project-brief.md for detailed content.
54
54
 
55
55
  ### Step 0.5: Load Agent Memory
56
56
 
57
57
  Read `docs/agent-memory/pm.md` for past learnings:
58
- - Estimation accuracy from previous sprints (did Wave estimates match reality?)
59
- - Architecture patterns that worked or failed in this project
60
- - Repeated planning mistakes to avoid
58
+ - estimation accuracy, architecture patterns, repeated planning mistakes
61
59
 
62
- Apply these insights when creating the implementation plan. If the memory file is empty or contains only placeholders, skip this step.
60
+ Apply these when planning. If memory is empty/placeholders only, skip.
63
61
 
64
62
  ### Step 0.7: Roadmap Draft
65
63
 
@@ -36,17 +36,15 @@ Before reviewing, verify that required state files exist and are not empty:
36
36
  - `docs/failure-patterns.md` — Must exist (needed for Step 5 cross-check)
37
37
  - `docs/project-state.md` — Must have current Sprint info (needed for scope check)
38
38
 
39
- If state files are empty/placeholder-only → Warn: "State files are not filled. Review will proceed but scope check and failure pattern cross-check will be limited. Consider running `setup` skill."
40
- If `docs/failure-patterns.md` is empty, FP-cross-check (Step 5) will be skipped. This increases risk of recurring bugs.
39
+ If state files are empty/placeholder-only → warn that scope and FP checks are limited; suggest `setup`.
40
+ If `docs/failure-patterns.md` is empty, skip FP cross-check.
41
41
 
42
42
  ### Step 0.5: Load Agent Memory
43
43
 
44
44
  Read `docs/agent-memory/reviewer.md` for past learnings:
45
- - Frequently missed review items in this project
46
- - Common code patterns that caused issues
47
- - Review statistics (pass rate, common failure categories)
45
+ - missed review items, risky code patterns, review statistics
48
46
 
49
- Pay extra attention to items flagged in past reviews. If the memory file is empty or contains only placeholders, skip this step.
47
+ If memory is empty/placeholders only, skip.
50
48
 
51
49
  ### Input
52
50
 
@@ -68,11 +66,11 @@ Changed file list (user-provided or from `git diff --name-only`)
68
66
  **Step 2.2: Acceptance Contract Gate**
69
67
 
70
68
  If `docs/project-state.md` has `## Story Contracts` rows for the Story:
71
- 1. Review each row before code-quality review.
72
- 2. Compare assertion vs code, tests, API/UI output, and proof.
73
- 3. Output **Story Contract Review**: `Contract | Status | Evidence`.
74
- 4. `FAIL`, `NOT_PROVEN`, blank Proof Status, or `needs-user-confirmation` blocks `DONE` and commit guidance.
75
- 5. Wrong-contract tests fail.
69
+ 1. Compare each row against code, tests, API/UI, and proof.
70
+ 2. Output **Story Contract Review**: `Contract | Status | Evidence`.
71
+ 3. `FAIL`, `NOT_PROVEN`, blank Proof Status, or `needs-user-confirmation` blocks `DONE`.
72
+ 4. Wrong-contract tests fail.
73
+ 5. **R16 surface rule**: `always/every/all/항상` contracts must name/prove relevant public paths, e.g. `create/list/get/resolve`. Missing surfaces → `[CONTRACT-GAP: SURFACE_UNSPECIFIED]`.
76
74
 
77
75
  <!-- CREW_MODE_START -->
78
76
  **Step 2.5: CI Standards Compliance (🟣 Pipeline only)**
@@ -123,10 +121,9 @@ Record the result as a **Proof Ledger** entry. Keep it short:
123
121
  If state files are in scope, write/request Proof Ledger / Evidence Summary immediately after proof passes.
124
122
 
125
123
  **Step 4: Security Check (secure skill)**
126
- - [ ] No credentials, .env, or temp files in staging (FP-004)
127
- - [ ] No hardcoded API keys or passwords
128
- - [ ] No injection vulnerabilities (SQL, XSS)
124
+ - [ ] No credentials, hardcoded secrets, injection risks, or temp files
129
125
  - [ ] Evaluator artifacts require approval (`harness-owner: evaluator` → `harness-edit-approved`)
126
+ - [ ] **R16 scope/dependency evidence**: For "no external deps/auth/persistence", cite `package.json` and actual `require`/`import` lines. Do not name absent modules; hallucinated deps block `DONE`.
130
127
 
131
128
  **Step 5: Failure Pattern Cross-Check**
132
129
  - Compare current changes against all FP-NNN items in docs/failure-patterns.md
@@ -176,6 +173,8 @@ After running state-check, also verify:
176
173
  - [ ] **docs/failure-patterns.md**: If a bug was fixed that matched a pattern, was frequency incremented?
177
174
  - [ ] **docs/project-brief.md**: If a technology or architectural decision was made, is it in Decision Log?
178
175
  - [ ] **docs/agent-memory/*.md**: If an agent (reviewer/pm/lead) was used this session, was its memory updated by the wrap-up skill?
176
+ - [ ] **R16 guard evidence**: Run/request the guard command and include its exact summary. Any guard error forbids `DONE`/`DONE_WITH_CONCERNS`:
177
+ `HARNESS_GUARD_ROOT="$PWD" node /path/to/k-harness/scripts/harness-guard.js docs/project-state.md`
179
178
 
180
179
  For each missing update: flag as `[STATE-AUDIT]` in the output and provide the exact update that should be made.
181
180
  **Severity**:
@@ -205,6 +204,8 @@ When review result is DONE or DONE_WITH_CONCERNS (no blockers):
205
204
 
206
205
  If review is BLOCKED → do NOT suggest commit. Fix first.
207
206
 
207
+ Before commit guidance, run `git status --short`; do not imply a commit exists unless `git log --oneline -1` confirms it.
208
+
208
209
  ### Output Format
209
210
 
210
211
  ```
@@ -61,10 +61,8 @@ Use `--overwrite` only to reset corrupted state after backup; then rerun setup t
61
61
  - `sprint-manager.md` → should be renamed to `lead.md`
62
62
  - `navigator.md` → should be renamed to `lead.md`
63
63
  - `builder.md` → should be renamed to `pm.md`
64
- - For each legacy file found:
65
- - If the new name does NOT exist offer to rename: `mv {legacy}.md {new}.md` (preserves history)
66
- - If BOTH exist → ask the user which to keep, or merge contents into the new name and delete the legacy
67
- - Confirm with the user before renaming. Record the migration in `docs/project-state.md` Recent Changes.
64
+ - For each legacy file: offer rename if the new name is absent; if both exist, ask whether to keep or merge.
65
+ - Confirm before renaming and record the migration in Recent Changes.
68
66
 
69
67
  **Do NOT modify any code files in this phase.**
70
68
 
@@ -174,6 +174,18 @@ If `docs/project-brief.md` maps one FR/KPI/ARB row to multiple Story IDs, requir
174
174
 
175
175
  This catches wrap-up corruption where `## Recent Changes` is inserted in the middle of `FR-008 Durable UI Evidence` and steals the remaining proof content.
176
176
 
177
+ ### Check 14: Self-Verify Claim Integrity (R16)
178
+
179
+ If `docs/project-state.md` or the caller output claims `state-check PASS`, `0 FAIL`, `0 WARN`, or `guard no issues`, the claim must be backed by deterministic evidence:
180
+
181
+ 1. Prefer running the installed guard command:
182
+ `HARNESS_GUARD_ROOT="$PWD" node /path/to/k-harness/scripts/harness-guard.js docs/project-state.md`
183
+ 2. If CLI execution is unavailable, do not claim `0 FAIL, 0 WARN`; say `manual state-check only`.
184
+ 3. FAIL if any markdown/state/contract/handoff/env-seal issue is visible while the file claims clean self-verify.
185
+ 4. FAIL if the guard output is summarized but not shown.
186
+
187
+ This catches reports such as "state-check PASS: 0 FAIL, 0 WARN" when a Proof Ledger table is malformed or Environment Seal is missing.
188
+
177
189
  ## Output Format
178
190
 
179
191
  ```
@@ -213,6 +225,10 @@ This catches wrap-up corruption where `## Recent Changes` is inserted in the mid
213
225
  ### Check 13: Recent Changes Section Integrity
214
226
  - Recent Changes contains only changelog entries / {M} misplaced evidence lines
215
227
 
228
+ ### Check 14: Self-Verify Claim Integrity
229
+ - Guard output: shown / missing
230
+ - Clean PASS claim matches deterministic result: yes/no
231
+
216
232
  <!-- CREW_MODE_START -->
217
233
  ### Check 6: Validation Tracker (🟣)
218
234
  - {N} FR references checked / {M} drifted
@@ -252,7 +268,7 @@ When invoked by another agent (pm/reviewer/wrap-up), control returns to the call
252
268
 
253
269
  - Do NOT invent data. Read the files and report exactly what you find.
254
270
  - Do NOT modify state files in this skill — diagnosis only. Caller decides remediation.
255
- - Do NOT run shell scripts. All checks are markdown-described file reads + comparisons.
271
+ - Do NOT invent deterministic results. If a guard CLI is available, run it; otherwise mark clean PASS claims as manual-only, not `0 FAIL, 0 WARN`.
256
272
  - If a check cannot be performed (e.g., `docs/` missing entirely), report it as FAIL and stop — further checks are meaningless.
257
273
 
258
274
  ## Anti-patterns
@@ -27,8 +27,7 @@ This is kode:harness's memory mechanism — without it, the same mistakes repeat
27
27
  ### Step 1: Review Session Activity
28
28
 
29
29
  1. Scan recent git changes: `git log --oneline -10` and `git diff --stat HEAD~3`
30
- 2. Identify what was accomplished in this session
31
- 3. Identify any errors, failures, or unexpected issues that occurred
30
+ 2. Identify accomplishments and unexpected issues
32
31
 
33
32
  **Edge Case: Zero-Change Session**
34
33
  If `git diff --stat` shows no changes and `git log` shows no new commits this session:
@@ -141,6 +140,22 @@ For each issue/error that occurred in this session:
141
140
 
142
141
  > **Self-check**: New modules are registered in `docs/dependency-map.md`; state-check is PASS/WARN.
143
142
 
143
+ #### Step 5.5b: Guard Evidence (R16) ⚠️ MANDATORY
144
+
145
+ Before saying `state-check PASS`, `0 FAIL`, `0 WARN`, `STATUS: DONE`, or `Session Learn Complete`, run and quote one guard summary:
146
+
147
+ ```bash
148
+ HARNESS_GUARD_ROOT="$PWD" node /path/to/k-harness/scripts/harness-guard.js docs/project-state.md
149
+ ```
150
+
151
+ or installed script:
152
+
153
+ ```bash
154
+ npm run harness:guard:wrap-up
155
+ ```
156
+
157
+ Rules: paste the exact guard summary. Errors block `STATUS: DONE`; warnings must be listed. Never write `0 FAIL, 0 WARN` unless guard says no issues.
158
+
144
159
  ### Step 5.55: Refresh Project Docs Hub Index (if applicable)
145
160
 
146
161
  Run only if user used/requested `docs-bridge`, or Project Docs Hub Index has real rows.
@@ -182,6 +197,16 @@ State file 변경사항을 커밋합니다. Learn 실행 결과가 커밋되지
182
197
 
183
198
  > **Self-check**: `git status`에 docs/ 아래 unstaged 파일이 없어야 합니다.
184
199
 
200
+ #### Step 5.65b: Dirty Worktree Truth (R16) ⚠️ MANDATORY
201
+
202
+ Run:
203
+
204
+ ```bash
205
+ git status --short
206
+ ```
207
+
208
+ Rules: paste exact `git status --short` or `clean`. Dirty `src/`, `test/`, `public/`, or app files mean work is not fully committed. If they remain by policy, report `Session End: DIRTY WORKTREE`.
209
+
185
210
  ### Step 5.7: Git Push Check (session end)
186
211
 
187
212
  Before ending the session, check for unpushed commits:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kodevibe/harness",
3
- "version": "0.11.3",
3
+ "version": "0.11.5",
4
4
  "description": "kode:harness — harness engineering for keeping every developer's AI aligned on one project direction.",
5
5
  "keywords": [
6
6
  "llm",
package/src/guard.js CHANGED
@@ -21,6 +21,10 @@
21
21
  // R13 checkSmokeEvidence — browser/manual proof must leave durable evidence
22
22
  // R14 checkScopeSplitApproval — FR/KPI/ARB split mappings need approval
23
23
  // R15 checkRecentChangesIntegrity — wrap-up must not corrupt state sections
24
+ // R16 checkSelfVerifyClaim — claimed PASS must match deterministic guard
25
+ // R16 checkReviewerAuditEvidence — scope audits must cite real deps/imports
26
+ // R17 checkCrewValidationSync — Crew Validation Tracker follows done work
27
+ // R17 checkDependencyInterfaceLog — interface-affecting features update deps
24
28
  //
25
29
  // Severity: 'error' blocks the commit (exit 1). 'warn' is informational.
26
30
 
@@ -285,6 +289,8 @@ function checkStateFile(content) {
285
289
 
286
290
  const STORY_CONTRACT_PASS = /✅|pass(?:ed)?|proven|verified|reviewed|done|ok/i;
287
291
  const STORY_CONTRACT_NOT_PROVEN = /❌|fail(?:ed)?|not[_ -]?proven|not[_ -]?verified|pending|todo|tbd|blank|needs[_ -]?user[_ -]?confirmation|needs[_ -]?confirmation|⬜|🚫|blocked/i;
292
+ const STORY_CONTRACT_ALWAYS = /\balways\b|\bevery\b|\ball\b|항상|모든|전체/i;
293
+ const STORY_CONTRACT_SURFACE = /\b(create|list|get|resolve|update|delete|api|ui|endpoint|route|public\s+surface|return\s+path)\b|생성|목록|조회|해결|수정|삭제|반환면|공개\s*표면/i;
288
294
 
289
295
  /**
290
296
  * Semantic Story Contract gate. This is intentionally project-agnostic:
@@ -363,12 +369,56 @@ function checkStoryContracts(input = {}) {
363
369
  message: `Story ${id} is done but Story Contract "${contract}" is not proven (status: ${status || 'blank'}). Prove every contract row before Done (R11).`,
364
370
  });
365
371
  }
372
+ const assertionText = Object.values(row).filter((v) => typeof v === 'string').join(' ');
373
+ if (STORY_CONTRACT_ALWAYS.test(assertionText)) {
374
+ const surfaceMatches = assertionText.match(new RegExp(STORY_CONTRACT_SURFACE.source, 'gi')) || [];
375
+ const uniqueSurfaces = new Set(surfaceMatches.map((s) => s.toLowerCase()));
376
+ if (uniqueSurfaces.size < 2) {
377
+ violations.push({
378
+ check: 'story-contract',
379
+ severity: 'error',
380
+ line: 0,
381
+ message: `Story ${id} contract "${contract}" uses an always/every assertion but does not enumerate public surfaces (e.g. create/list/get/resolve/API/UI). R16 requires surface-specific proof so one return path cannot drift.`,
382
+ });
383
+ }
384
+ }
366
385
  }
367
386
  }
368
387
 
369
388
  return violations;
370
389
  }
371
390
 
391
+ // ─── Self-Verify Claim Gate (R16) ───────────────────────────────────
392
+
393
+ const CLEAN_SELF_VERIFY_CLAIM = /\b(?:state-check|harness-guard|guard)\b[\s\S]{0,80}\bPASS\b|\b0\s+FAIL\b[\s,;/|]*\b0\s+WARN\b|\b0\s+error\(s\)\b[\s,;/|]*\b0\s+warning\(s\)\b/i;
394
+
395
+ /**
396
+ * Catch the Experiment #6 failure mode: a state file claims state-check/guard
397
+ * passed cleanly while deterministic checks still find errors or warnings.
398
+ *
399
+ * @param {string} content project-state.md
400
+ * @param {Array} deterministicViolations violations found for the same file
401
+ * @returns {Array}
402
+ */
403
+ function checkSelfVerifyClaim(content, deterministicViolations = []) {
404
+ const visible = stripHtmlComments(content);
405
+ if (!CLEAN_SELF_VERIFY_CLAIM.test(visible)) return [];
406
+
407
+ const relevant = deterministicViolations
408
+ .filter((v) => v.check !== 'self-verify-claim')
409
+ .filter((v) => v.severity === 'error' || v.severity === 'warn');
410
+ if (relevant.length === 0) return [];
411
+
412
+ const errors = relevant.filter((v) => v.severity === 'error').length;
413
+ const warnings = relevant.filter((v) => v.severity === 'warn').length;
414
+ return [{
415
+ check: 'self-verify-claim',
416
+ severity: 'error',
417
+ line: 0,
418
+ message: `State file claims clean self-verify/PASS, but deterministic guard found ${errors} error(s) and ${warnings} warning(s). Paste/fix the real guard output before reporting DONE (R16).`,
419
+ }];
420
+ }
421
+
372
422
  // ─── Reviewer Handoff Gate (R3) ──────────────────────────────────────
373
423
 
374
424
  /**
@@ -474,7 +524,7 @@ function checkLearnCompletion({ projectState = '', features = '', quiet = false
474
524
 
475
525
  function splitPathList(value) {
476
526
  return String(value || '')
477
- .split(/[,;<br>`]+|\s{2,}/)
527
+ .split(/(?:,|;|<br\s*\/?>|`)+|\s{2,}/i)
478
528
  .map((v) => v.trim())
479
529
  .filter((v) => v && !/^n\/a$/i.test(v) && !/^\(?none\)?$/i.test(v));
480
530
  }
@@ -536,6 +586,124 @@ function checkStateSync({ projectState = '', features = '', dependencyMap = '' }
536
586
  return violations;
537
587
  }
538
588
 
589
+ // ─── Crew Validation Tracker Sync Gate (R17) ────────────────────────
590
+
591
+ const COMPLETE_STATUS = /✅|done|proven|pass(?:ed)?|reviewed|complete|완료|통과/i;
592
+ const INCOMPLETE_STATUS = /planned|pending|todo|not[_ -]?proven|not[_ -]?verified|⬜|🟡|🔄|대기|계획|미완료/i;
593
+ const REQUIREMENT_ID_RE = /\b(?:FR|KPI|ARB|ARB-FAIL)[-_]?\d+\b/gi;
594
+ const BASELINE_REQUIREMENTS = new Set(['FR-001', 'FR-002']);
595
+
596
+ function normalizedRequirementId(value) {
597
+ return String(value || '').toUpperCase().replace('_', '-');
598
+ }
599
+
600
+ function extractRequirementIds(value) {
601
+ return [...new Set((String(value || '').match(REQUIREMENT_ID_RE) || [])
602
+ .map(normalizedRequirementId))];
603
+ }
604
+
605
+ function trackerRowsFromBrief(projectBrief = '') {
606
+ const visible = stripHtmlComments(projectBrief);
607
+ const section = getSection(visible, 'Validation Tracker') || '';
608
+ return parseMarkdownTable(section);
609
+ }
610
+
611
+ function trackerRequirement(row) {
612
+ return row.Requirement || row.FR || row.KPI || row.ARB || row.Item || row.Control || '';
613
+ }
614
+
615
+ function trackerStory(row) {
616
+ return row.Story || row.Stories || row['Story ID'] || '';
617
+ }
618
+
619
+ function trackerStatus(row) {
620
+ return row.Status || row.status || '';
621
+ }
622
+
623
+ /**
624
+ * Crew mode adds project-brief.md Validation Tracker as the FR/KPI/ARB source
625
+ * of truth. A recurring Qwen failure was marking features/project-state done
626
+ * while leaving tracker rows Planned. This gate makes that drift blocking.
627
+ *
628
+ * @param {{projectState?: string, features?: string, projectBrief?: string}} files
629
+ * @returns {Array}
630
+ */
631
+ function checkCrewValidationSync({ projectState = '', features = '', projectBrief = '' } = {}) {
632
+ const violations = [];
633
+ const trackerRows = trackerRowsFromBrief(projectBrief);
634
+ if (trackerRows.length === 0) return violations;
635
+
636
+ const doneStoryIds = parseMarkdownTable(getSection(stripHtmlComments(projectState), 'Story Status') || '')
637
+ .filter((row) => /✅\s*done/i.test(rowStatus(row)))
638
+ .map((row) => storyIdFromRow(row))
639
+ .filter(Boolean);
640
+ const doneStorySet = new Set(doneStoryIds);
641
+
642
+ const featureRows = parseMarkdownTable(getSection(stripHtmlComments(features), 'Feature Registry') || stripHtmlComments(features))
643
+ .filter((row) => COMPLETE_STATUS.test(row.Status || row.status || ''));
644
+ const doneRequirements = new Set();
645
+ for (const feature of featureRows) {
646
+ const raw = Object.values(feature).filter((v) => typeof v === 'string').join(' ');
647
+ for (const req of extractRequirementIds(raw)) doneRequirements.add(req);
648
+ }
649
+
650
+ for (const row of trackerRows) {
651
+ const req = normalizedRequirementId(trackerRequirement(row));
652
+ const story = trackerStory(row);
653
+ const status = trackerStatus(row);
654
+ const mappedToDoneStory = [...doneStorySet].some((id) => story.includes(id));
655
+ const doneByFeature = req && doneRequirements.has(req);
656
+ if ((mappedToDoneStory || doneByFeature) && INCOMPLETE_STATUS.test(status)) {
657
+ violations.push({
658
+ check: 'validation-tracker',
659
+ severity: 'error',
660
+ line: 0,
661
+ message: `Validation Tracker row ${req || '(unknown requirement)'} maps to completed work but still has status "${status || 'blank'}" (R17). Update project-brief.md to Proven/Done or keep the Story out of Done.`,
662
+ });
663
+ }
664
+ }
665
+
666
+ return violations;
667
+ }
668
+
669
+ // ─── Dependency Interface Log Gate (R17) ────────────────────────────
670
+
671
+ const INTERFACE_FEATURE_TERMS = /\b(FR-00[3-9]|FR-0[1-9]\d|sla|risk|filter|api|interface|contract|auth|login|board|control)\b/i;
672
+
673
+ function checkDependencyInterfaceLog({ features = '', dependencyMap = '' } = {}) {
674
+ const violations = [];
675
+ const depVisible = stripHtmlComments(dependencyMap);
676
+ const interfaceLog = getSection(depVisible, 'Interface Change Log');
677
+ if (interfaceLog === null) return violations;
678
+
679
+ const featureRows = parseMarkdownTable(getSection(stripHtmlComments(features), 'Feature Registry') || stripHtmlComments(features))
680
+ .filter((row) => COMPLETE_STATUS.test(row.Status || row.status || ''));
681
+
682
+ for (const row of featureRows) {
683
+ const raw = Object.values(row).filter((v) => typeof v === 'string').join(' ');
684
+ const requirements = extractRequirementIds(raw).filter((id) => !BASELINE_REQUIREMENTS.has(id));
685
+ const keyFiles = row['Key Files'] || row['Key files'] || row.Files || row.Scope || '';
686
+ const touchesSource = splitPathList(keyFiles).some((file) => /^(src|lib|app|public)\//.test(file) || /^(server|index)\.js$/.test(file));
687
+ const interfaceLike = requirements.length > 0 || INTERFACE_FEATURE_TERMS.test(raw);
688
+ if (!touchesSource || !interfaceLike) continue;
689
+
690
+ const coveredByRequirement = requirements.some((req) => interfaceLog.includes(req));
691
+ const featureName = row.Feature || row.Name || row.Title || '';
692
+ const tokens = String(featureName).toLowerCase().match(/[a-z0-9-]{4,}/g) || [];
693
+ const meaningfulMatches = tokens.filter((token) => interfaceLog.toLowerCase().includes(token));
694
+ if (!coveredByRequirement && meaningfulMatches.length < 2) {
695
+ violations.push({
696
+ check: 'dependency-interface-log',
697
+ severity: 'error',
698
+ line: 0,
699
+ message: `Completed feature "${featureName || '(unnamed feature)'}" changes source/API/UI surfaces but dependency-map.md Interface Change Log has no matching FR/feature entry (R17). Add an interface log row or explicitly record no interface change.`,
700
+ });
701
+ }
702
+ }
703
+
704
+ return violations;
705
+ }
706
+
539
707
  // ─── Scope Split Approval Gate (R14) ────────────────────────────────
540
708
 
541
709
  const STORY_ID_RE = /\bS\d+-\d+\b/g;
@@ -786,6 +954,109 @@ function checkEvaluatorArtifact(content, filename = '') {
786
954
  }];
787
955
  }
788
956
 
957
+ // ─── Reviewer Audit Evidence Gate (R16) ─────────────────────────────
958
+
959
+ const REVIEWER_AUDIT_MODULE_LINE = /^\s*[-*]?\s*(?:\*\*)?(?:Verified modules|Verified imports|Dependencies verified|검증(?:된)?\s*(?:모듈|의존성)|확인(?:된)?\s*(?:모듈|의존성))(?:\*\*)?\s*:/im;
960
+ const REVIEWER_AUDIT_IGNORE = new Set([
961
+ 'project',
962
+ 'project-local',
963
+ 'local',
964
+ 'internal',
965
+ 'built-in',
966
+ 'builtin',
967
+ 'node',
968
+ 'none',
969
+ 'n/a',
970
+ ]);
971
+
972
+ function packageNamesFromJson(packageJson = '') {
973
+ if (!packageJson.trim()) return new Set();
974
+ try {
975
+ const pkg = JSON.parse(packageJson);
976
+ return new Set([
977
+ ...Object.keys(pkg.dependencies || {}),
978
+ ...Object.keys(pkg.devDependencies || {}),
979
+ ...Object.keys(pkg.peerDependencies || {}),
980
+ ...Object.keys(pkg.optionalDependencies || {}),
981
+ ]);
982
+ } catch {
983
+ return new Set();
984
+ }
985
+ }
986
+
987
+ function moduleNamesFromSourceFiles(sourceFiles = []) {
988
+ const names = new Set();
989
+ for (const { file = '', content = '' } of sourceFiles) {
990
+ const base = file.split(/[\\/]/).pop() || '';
991
+ if (base.includes('.')) names.add(base.replace(/\.[^.]+$/, ''));
992
+ const requireRe = /\brequire\(\s*['"]([^'"]+)['"]\s*\)/g;
993
+ const importRe = /\bfrom\s+['"]([^'"]+)['"]/g;
994
+ for (const re of [requireRe, importRe]) {
995
+ let match;
996
+ while ((match = re.exec(content)) !== null) {
997
+ const spec = match[1];
998
+ if (spec.startsWith('.')) {
999
+ const local = spec.split('/').pop();
1000
+ if (local) names.add(local.replace(/\.[^.]+$/, ''));
1001
+ continue;
1002
+ }
1003
+ names.add(spec.split('/')[0]);
1004
+ }
1005
+ }
1006
+ }
1007
+ return names;
1008
+ }
1009
+
1010
+ function extractAuditModuleNames(content) {
1011
+ const names = new Set();
1012
+ const lines = content.split('\n');
1013
+ for (const line of lines) {
1014
+ if (!REVIEWER_AUDIT_MODULE_LINE.test(line)) continue;
1015
+ const codeNames = [...line.matchAll(/`([^`]+)`/g)].map((m) => m[1]);
1016
+ const source = codeNames.length > 0 ? codeNames.join(',') : line.split(':').slice(1).join(':');
1017
+ for (const raw of source.split(/[,\s/]+/)) {
1018
+ const token = raw.replace(/^[`"'(*\-\s]+|[`"',.)*\s]+$/g, '');
1019
+ if (!token || token.length < 2) continue;
1020
+ if (!/^[A-Za-z][A-Za-z0-9_.:-]*$/.test(token)) continue;
1021
+ if (REVIEWER_AUDIT_IGNORE.has(token.toLowerCase())) continue;
1022
+ names.add(token.replace(/^node:/, ''));
1023
+ }
1024
+ }
1025
+ return [...names];
1026
+ }
1027
+
1028
+ /**
1029
+ * Reviewer scope audits are allowed to be judgmental, but dependency evidence
1030
+ * must be grounded in package.json or actual require/import lines. This catches
1031
+ * hallucinated modules such as "express" in a Node http-only project.
1032
+ *
1033
+ * @param {string} content reviewer.md
1034
+ * @param {{packageJson?: string, sourceFiles?: Array<{file:string, content:string}>, filename?: string}} context
1035
+ * @returns {Array}
1036
+ */
1037
+ function checkReviewerAuditEvidence(content, { packageJson = '', sourceFiles = [], filename = '' } = {}) {
1038
+ const asserted = extractAuditModuleNames(content);
1039
+ if (asserted.length === 0) return [];
1040
+
1041
+ const builtins = new Set(require('module').builtinModules.map((name) => name.replace(/^node:/, '')));
1042
+ const deps = packageNamesFromJson(packageJson);
1043
+ const sourceNames = moduleNamesFromSourceFiles(sourceFiles);
1044
+ const allowed = new Set([...builtins, ...deps, ...sourceNames]);
1045
+
1046
+ const violations = [];
1047
+ for (const name of asserted) {
1048
+ const normalized = name.replace(/^node:/, '');
1049
+ if (allowed.has(normalized)) continue;
1050
+ violations.push({
1051
+ check: 'reviewer-audit',
1052
+ severity: 'error',
1053
+ line: 0,
1054
+ message: `${filename ? filename + ': ' : ''}reviewer audit cites "${name}" as a verified module/dependency, but it is not in package.json or actual require/import lines (R16). Cite exact evidence or remove it.`,
1055
+ });
1056
+ }
1057
+ return violations;
1058
+ }
1059
+
789
1060
  // ─── Markdown lint (R6 / L3-8) ───────────────────────────────────────
790
1061
 
791
1062
  /**
@@ -929,6 +1200,10 @@ function isProjectBriefFile(file) {
929
1200
  return /(?:^|\/)(?:docs|\.harness)\/project-brief\.md$/.test(file);
930
1201
  }
931
1202
 
1203
+ function isReviewerMemoryFile(file) {
1204
+ return /(?:^|\/)(?:docs|\.harness)\/agent-memory\/reviewer\.md$/.test(file);
1205
+ }
1206
+
932
1207
  function isScannableForSecrets(file) {
933
1208
  return /\.(js|ts|jsx|tsx|json|jsonc|ya?ml|env|sh|py|java|md|properties|toml)$/i.test(file)
934
1209
  && !/\.lock$/.test(file);
@@ -939,6 +1214,35 @@ function isPublicPackageFile(file) {
939
1214
  return PUBLIC_PACKAGE_PATHS.some((re) => re.test(normalized));
940
1215
  }
941
1216
 
1217
+ function sourceFilesForAudit(cwd) {
1218
+ const files = [];
1219
+ const roots = ['src', 'lib', 'app', 'server.js', 'index.js'];
1220
+ const addFile = (rel) => {
1221
+ const abs = path.join(cwd, rel);
1222
+ if (!fs.existsSync(abs) || !fs.statSync(abs).isFile()) return;
1223
+ if (!/\.(?:js|mjs|cjs|ts|tsx|jsx)$/.test(rel)) return;
1224
+ files.push({ file: rel, content: fs.readFileSync(abs, 'utf8') });
1225
+ };
1226
+ const walkSource = (relDir) => {
1227
+ const absDir = path.join(cwd, relDir);
1228
+ if (!fs.existsSync(absDir) || !fs.statSync(absDir).isDirectory()) return;
1229
+ for (const name of fs.readdirSync(absDir)) {
1230
+ if (name.startsWith('.') || name === 'node_modules') continue;
1231
+ const rel = path.join(relDir, name);
1232
+ const abs = path.join(cwd, rel);
1233
+ if (fs.statSync(abs).isDirectory()) walkSource(rel);
1234
+ else addFile(rel);
1235
+ }
1236
+ };
1237
+ for (const root of roots) {
1238
+ const abs = path.join(cwd, root);
1239
+ if (!fs.existsSync(abs)) continue;
1240
+ if (fs.statSync(abs).isDirectory()) walkSource(root);
1241
+ else addFile(root);
1242
+ }
1243
+ return files;
1244
+ }
1245
+
942
1246
  /**
943
1247
  * Run all guard checks over a set of files.
944
1248
  * @param {{files: string[], cwd?: string}} opts
@@ -947,6 +1251,7 @@ function isPublicPackageFile(file) {
947
1251
  function runGuard({ files, cwd = process.cwd() }) {
948
1252
  const all = [];
949
1253
  let scanned = 0;
1254
+ const stateContents = {};
950
1255
 
951
1256
  for (const file of files) {
952
1257
  const abs = path.isAbsolute(file) ? file : path.join(cwd, file);
@@ -954,6 +1259,12 @@ function runGuard({ files, cwd = process.cwd() }) {
954
1259
  const content = fs.readFileSync(abs, 'utf8');
955
1260
  const rel = path.relative(cwd, abs);
956
1261
  scanned++;
1262
+ const beforeFile = all.length;
1263
+ const normalizedRel = rel.replace(/\\/g, '/');
1264
+ if (/^(docs|\.harness)\/project-state\.md$/.test(normalizedRel)) stateContents.projectState = content;
1265
+ if (/^(docs|\.harness)\/features\.md$/.test(normalizedRel)) stateContents.features = content;
1266
+ if (/^(docs|\.harness)\/dependency-map\.md$/.test(normalizedRel)) stateContents.dependencyMap = content;
1267
+ if (/^(docs|\.harness)\/project-brief\.md$/.test(normalizedRel)) stateContents.projectBrief = content;
957
1268
 
958
1269
  if (isScannableForSecrets(file)) {
959
1270
  all.push(...scanSecrets(content, rel));
@@ -979,10 +1290,27 @@ function runGuard({ files, cwd = process.cwd() }) {
979
1290
  if (STATE_LINE_LIMITS[base]) {
980
1291
  all.push(...lintLineLimit(content, STATE_LINE_LIMITS[base], rel));
981
1292
  }
1293
+ all.push(...checkSelfVerifyClaim(content, all.slice(beforeFile)));
982
1294
  }
983
1295
  if (isProjectBriefFile(file)) {
984
1296
  all.push(...checkScopeSplitApproval({ projectBrief: content }));
985
1297
  }
1298
+ if (isReviewerMemoryFile(file)) {
1299
+ const pkgPath = path.join(cwd, 'package.json');
1300
+ all.push(...checkReviewerAuditEvidence(content, {
1301
+ filename: rel,
1302
+ packageJson: fs.existsSync(pkgPath) ? fs.readFileSync(pkgPath, 'utf8') : '',
1303
+ sourceFiles: sourceFilesForAudit(cwd),
1304
+ }));
1305
+ }
1306
+ }
1307
+
1308
+ if (stateContents.projectState && stateContents.features && stateContents.dependencyMap) {
1309
+ all.push(...checkStateSync(stateContents));
1310
+ all.push(...checkDependencyInterfaceLog(stateContents));
1311
+ }
1312
+ if (stateContents.projectState && stateContents.features && stateContents.projectBrief) {
1313
+ all.push(...checkCrewValidationSync(stateContents));
986
1314
  }
987
1315
 
988
1316
  const errorCount = all.filter((v) => v.severity === 'error').length;
@@ -997,13 +1325,17 @@ module.exports = {
997
1325
  checkStoryContracts,
998
1326
  checkLearnCompletion,
999
1327
  checkStateSync,
1328
+ checkCrewValidationSync,
1329
+ checkDependencyInterfaceLog,
1000
1330
  checkScopeSplitApproval,
1001
1331
  checkRecentChangesIntegrity,
1332
+ checkSelfVerifyClaim,
1002
1333
  checkIntegrationDoD,
1003
1334
  checkSmokeEvidence,
1004
1335
  checkEnvSeal,
1005
1336
  checkPublicBoundary,
1006
1337
  checkEvaluatorArtifact,
1338
+ checkReviewerAuditEvidence,
1007
1339
  lintMarkdownTables,
1008
1340
  lintLineLimit,
1009
1341
  checkInstructionBudget,
package/src/init.js CHANGED
@@ -338,6 +338,24 @@ function writeAgentsAsToml(targetDir, agentsDir, overwrite, mode = 'solo', crew
338
338
  }
339
339
  }
340
340
 
341
+ function vscodeAgentsMirror() {
342
+ return [
343
+ '# kode:harness VS Code Instruction Anchor',
344
+ '',
345
+ 'This project uses kode:harness. The canonical VS Code Copilot dispatcher is `.github/copilot-instructions.md` and must be followed.',
346
+ '',
347
+ 'Hard stops:',
348
+ '',
349
+ '- Read `docs/project-state.md` before planning or coding.',
350
+ '- Every response must end with a `🧭 Next Step` block.',
351
+ '- Do not mark a Story Done without Proof Ledger evidence.',
352
+ '- Do not claim state-check/guard PASS without real command output.',
353
+ '- Do not claim clean worktree, commit, push, publish, or policy compliance without checking the actual command result.',
354
+ '- Security, governance, dependency, CI/CD, and release rules are enforced by deterministic guards; if guard output conflicts with prose, guard output wins.',
355
+ '',
356
+ ].join('\n');
357
+ }
358
+
341
359
  // ─── IDE Generators ──────────────────────────────────────────
342
360
 
343
361
  function generateVscode(targetDir, overwrite, mode = 'solo', crew = false) {
@@ -345,6 +363,9 @@ function generateVscode(targetDir, overwrite, mode = 'solo', crew = false) {
345
363
 
346
364
  // Global instructions (dispatcher only — rules are embedded in skills)
347
365
  writeFile(targetDir, '.github/copilot-instructions.md', coreRules, true);
366
+ // Root AGENTS.md mirror — VS Code now supports AGENTS.md as an instruction
367
+ // surface. Keep it short to avoid conflicting with the canonical dispatcher.
368
+ writeFile(targetDir, 'AGENTS.md', vscodeAgentsMirror(), true);
348
369
 
349
370
  // Skills (.github/skills — VS Code default search path, SKILL.md with frontmatter)
350
371
  writeSkills(targetDir, '.github/skills', true, mode, crew);