@su-record/vibe 3.2.0 → 3.2.1

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.
Files changed (64) hide show
  1. package/README.en.md +3 -2
  2. package/README.md +4 -1
  3. package/dist/cli/design/design-md-parser.test.js +2 -2
  4. package/dist/cli/design/design-md-parser.test.js.map +1 -1
  5. package/dist/cli/setup/Provisioner.d.ts.map +1 -1
  6. package/dist/cli/setup/Provisioner.js +23 -7
  7. package/dist/cli/setup/Provisioner.js.map +1 -1
  8. package/dist/cli/utils/cli-detector.d.ts +3 -3
  9. package/dist/cli/utils/cli-detector.d.ts.map +1 -1
  10. package/dist/cli/utils/cli-detector.js +7 -27
  11. package/dist/cli/utils/cli-detector.js.map +1 -1
  12. package/dist/cli/utils/cli-detector.test.js +3 -2
  13. package/dist/cli/utils/cli-detector.test.js.map +1 -1
  14. package/dist/infra/lib/llm-availability.d.ts.map +1 -1
  15. package/dist/infra/lib/llm-availability.js +2 -10
  16. package/dist/infra/lib/llm-availability.js.map +1 -1
  17. package/dist/infra/lib/llm-availability.test.js +3 -2
  18. package/dist/infra/lib/llm-availability.test.js.map +1 -1
  19. package/dist/infra/lib/utils.d.ts +6 -0
  20. package/dist/infra/lib/utils.d.ts.map +1 -1
  21. package/dist/infra/lib/utils.js +33 -0
  22. package/dist/infra/lib/utils.js.map +1 -1
  23. package/dist/tools/convention/validateCodeQuality.d.ts +1 -0
  24. package/dist/tools/convention/validateCodeQuality.d.ts.map +1 -1
  25. package/dist/tools/convention/validateCodeQuality.js +3 -2
  26. package/dist/tools/convention/validateCodeQuality.js.map +1 -1
  27. package/dist/tools/index.d.ts +2 -2
  28. package/dist/tools/index.d.ts.map +1 -1
  29. package/dist/tools/index.js +2 -0
  30. package/dist/tools/index.js.map +1 -1
  31. package/dist/tools/spec/executionPacket.d.ts +101 -0
  32. package/dist/tools/spec/executionPacket.d.ts.map +1 -0
  33. package/dist/tools/spec/executionPacket.js +394 -0
  34. package/dist/tools/spec/executionPacket.js.map +1 -0
  35. package/dist/tools/spec/executionPacket.test.d.ts +2 -0
  36. package/dist/tools/spec/executionPacket.test.d.ts.map +1 -0
  37. package/dist/tools/spec/executionPacket.test.js +360 -0
  38. package/dist/tools/spec/executionPacket.test.js.map +1 -0
  39. package/dist/tools/spec/index.d.ts +2 -0
  40. package/dist/tools/spec/index.d.ts.map +1 -1
  41. package/dist/tools/spec/index.js +2 -0
  42. package/dist/tools/spec/index.js.map +1 -1
  43. package/dist/tools/spec/specGenerator.d.ts +12 -0
  44. package/dist/tools/spec/specGenerator.d.ts.map +1 -1
  45. package/dist/tools/spec/specGenerator.js +96 -14
  46. package/dist/tools/spec/specGenerator.js.map +1 -1
  47. package/dist/tools/spec/specGenerator.test.d.ts +2 -0
  48. package/dist/tools/spec/specGenerator.test.d.ts.map +1 -0
  49. package/dist/tools/spec/specGenerator.test.js +130 -0
  50. package/dist/tools/spec/specGenerator.test.js.map +1 -0
  51. package/hooks/scripts/__tests__/.vibe/command-log.txt +3 -3
  52. package/hooks/scripts/__tests__/llm-orchestrate-antigravity.test.js +5 -2
  53. package/hooks/scripts/__tests__/run-ledger-verify-required.test.js +7 -2
  54. package/hooks/scripts/__tests__/run-ledger.test.js +161 -10
  55. package/hooks/scripts/__tests__/utils-npm-root.test.js +5 -4
  56. package/hooks/scripts/lib/run-ledger.js +123 -21
  57. package/hooks/scripts/verify-ledger.js +23 -3
  58. package/package.json +1 -1
  59. package/skills/spec/SKILL.md +7 -0
  60. package/skills/vibe.run/SKILL.md +29 -6
  61. package/skills/vibe.trace/SKILL.md +3 -3
  62. package/skills/vibe.verify/SKILL.md +10 -4
  63. package/vibe/rules/loop-contract.md +7 -3
  64. package/vibe/templates/spec-template.md +32 -5
@@ -2,7 +2,7 @@
2
2
  * Run Ledger — vibe.run 실행 및 vibe.verify 결과 추적.
3
3
  *
4
4
  * 파일 위치: <projectDir>/.vibe/metrics/run-ledger.json
5
- * 형식: { runStarted, runFeature, verifyPassed, verifyAt, stopWarned,
5
+ * 형식: { runId, runStarted, runFeature, verifyPassed, verifyAt, stopWarned,
6
6
  * verifyRequired, verifyRequiredReason }
7
7
  *
8
8
  * 모든 함수는 fail-open (try/catch, 오류 시 null/false 반환).
@@ -10,14 +10,56 @@
10
10
  */
11
11
 
12
12
  import fs from 'fs';
13
- import os from 'os';
13
+ import { randomUUID } from 'crypto';
14
14
  import path from 'path';
15
15
 
16
+ const EVIDENCE_SCHEMA_VERSION = '1.0.0';
17
+ const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
18
+
16
19
  /** 레저 파일 경로 */
17
20
  function ledgerPath(projectDir) {
18
21
  return path.join(projectDir, '.vibe', 'metrics', 'run-ledger.json');
19
22
  }
20
23
 
24
+ function evidencePath(projectDir, runId) {
25
+ if (!UUID_PATTERN.test(runId)) return null;
26
+ const runsDir = path.resolve(projectDir, '.vibe', 'runs');
27
+ const target = path.resolve(runsDir, runId, 'evidence.json');
28
+ return target.startsWith(`${runsDir}${path.sep}`) ? target : null;
29
+ }
30
+
31
+ function writeJsonAtomic(targetPath, data) {
32
+ if (!targetPath) return false;
33
+ const dir = path.dirname(targetPath);
34
+ fs.mkdirSync(dir, { recursive: true });
35
+ const tmp = path.join(dir, `.${path.basename(targetPath)}.${process.pid}.${Date.now()}.tmp`);
36
+ try {
37
+ fs.writeFileSync(tmp, JSON.stringify(data, null, 2), 'utf-8');
38
+ fs.renameSync(tmp, targetPath);
39
+ return true;
40
+ } catch {
41
+ try { fs.rmSync(tmp, { force: true }); } catch { /* fail-open */ }
42
+ return false;
43
+ }
44
+ }
45
+
46
+ function withLedgerLock(projectDir, operation) {
47
+ const lockPath = path.join(projectDir, '.vibe', 'metrics', 'run-ledger.lock');
48
+ let descriptor;
49
+ try {
50
+ fs.mkdirSync(path.dirname(lockPath), { recursive: true });
51
+ descriptor = fs.openSync(lockPath, 'wx');
52
+ return operation();
53
+ } catch {
54
+ return false;
55
+ } finally {
56
+ if (descriptor !== undefined) {
57
+ fs.closeSync(descriptor);
58
+ try { fs.rmSync(lockPath, { force: true }); } catch { /* fail-open */ }
59
+ }
60
+ }
61
+ }
62
+
21
63
  /**
22
64
  * 레저 파일 읽기.
23
65
  * @param {string} projectDir
@@ -41,12 +83,61 @@ export function readLedger(projectDir) {
41
83
  */
42
84
  function writeLedger(projectDir, data) {
43
85
  try {
44
- const p = ledgerPath(projectDir);
45
- fs.mkdirSync(path.dirname(p), { recursive: true });
46
- const tmp = path.join(os.tmpdir(), `run-ledger-${process.pid}-${Date.now()}.tmp`);
47
- fs.writeFileSync(tmp, JSON.stringify(data, null, 2), 'utf-8');
48
- fs.renameSync(tmp, p);
49
- return true;
86
+ return writeJsonAtomic(ledgerPath(projectDir), data);
87
+ } catch {
88
+ return false;
89
+ }
90
+ }
91
+
92
+ function verificationResults(results) {
93
+ if (Array.isArray(results)) {
94
+ return results
95
+ .filter(item => item
96
+ && typeof item === 'object'
97
+ && typeof item.command === 'string'
98
+ && Number.isInteger(item.exitCode))
99
+ .map(item => ({
100
+ command: item.command,
101
+ exitCode: item.exitCode,
102
+ }));
103
+ }
104
+ return [];
105
+ }
106
+
107
+ function resolveSpecPath(projectDir, feature) {
108
+ if (typeof feature !== 'string' || feature.includes('..') || /[\\/]/.test(feature)) {
109
+ return null;
110
+ }
111
+ const flatPath = `.vibe/specs/${feature}.md`;
112
+ if (fs.existsSync(path.join(projectDir, flatPath))) return flatPath;
113
+ const splitPath = `.vibe/specs/${feature}/_index.md`;
114
+ return fs.existsSync(path.join(projectDir, splitPath)) ? splitPath : null;
115
+ }
116
+
117
+ function buildEvidence(projectDir, ledger, passed, generatedAt) {
118
+ return {
119
+ schemaVersion: EVIDENCE_SCHEMA_VERSION,
120
+ runId: ledger.runId,
121
+ specPath: resolveSpecPath(projectDir, ledger.runFeature),
122
+ generatedAt,
123
+ judges: {
124
+ deterministic: {
125
+ authority: 'blocking',
126
+ verifyPassed: Boolean(passed),
127
+ verificationResults: verificationResults(ledger.verificationResults),
128
+ },
129
+ model: { authority: 'advisory-only', canComplete: false },
130
+ humanTaste: { authority: 'release-only', canComplete: false },
131
+ },
132
+ };
133
+ }
134
+
135
+ function writeEvidence(projectDir, ledger, passed, generatedAt) {
136
+ try {
137
+ return writeJsonAtomic(
138
+ evidencePath(projectDir, ledger.runId),
139
+ buildEvidence(projectDir, ledger, passed, generatedAt),
140
+ );
50
141
  } catch {
51
142
  return false;
52
143
  }
@@ -59,10 +150,12 @@ function writeLedger(projectDir, data) {
59
150
  * @returns {boolean} 성공 여부
60
151
  */
61
152
  export function recordRunStart(projectDir, feature) {
62
- try {
153
+ return withLedgerLock(projectDir, () => {
63
154
  const existing = readLedger(projectDir) || {};
155
+ const { verificationResults: _results, verificationCommands: _commands, ...retained } = existing;
64
156
  const next = {
65
- ...existing,
157
+ ...retained,
158
+ runId: randomUUID(),
66
159
  runStarted: new Date().toISOString(),
67
160
  runFeature: feature || null,
68
161
  verifyPassed: false,
@@ -70,9 +163,7 @@ export function recordRunStart(projectDir, feature) {
70
163
  stopWarned: false,
71
164
  };
72
165
  return writeLedger(projectDir, next);
73
- } catch {
74
- return false;
75
- }
166
+ });
76
167
  }
77
168
 
78
169
  /**
@@ -80,25 +171,36 @@ export function recordRunStart(projectDir, feature) {
80
171
  * pass 시 verifyRequired 상태를 클리어한다.
81
172
  * @param {string} projectDir
82
173
  * @param {boolean} passed - 검증 통과 여부
174
+ * @param {{runId?: string, verificationResults?: object[]}} options
83
175
  * @returns {boolean} 성공 여부
84
176
  */
85
- export function recordVerify(projectDir, passed) {
86
- try {
177
+ export function recordVerify(projectDir, passed, options = {}) {
178
+ return withLedgerLock(projectDir, () => {
87
179
  const existing = readLedger(projectDir) || {};
180
+ const existingRunId = UUID_PATTERN.test(existing.runId || '') ? existing.runId : null;
181
+ if (existing.runId !== undefined && !existingRunId) return false;
182
+ if (existingRunId && options.runId !== existingRunId) return false;
183
+ const runId = existingRunId || randomUUID();
184
+ const results = verificationResults(options.verificationResults);
185
+ if (passed && (results.length === 0 || results.some(result => result.exitCode !== 0))) {
186
+ return false;
187
+ }
188
+ const generatedAt = new Date().toISOString();
88
189
  const next = {
89
190
  ...existing,
191
+ runId,
90
192
  verifyPassed: Boolean(passed),
91
- verifyAt: new Date().toISOString(),
193
+ verifyAt: generatedAt,
194
+ verificationResults: results,
92
195
  };
93
196
  // pass 시 verifyRequired 클리어
94
197
  if (passed) {
95
198
  next.verifyRequired = false;
96
199
  next.verifyRequiredReason = null;
97
200
  }
201
+ if (!writeEvidence(projectDir, next, passed, generatedAt)) return false;
98
202
  return writeLedger(projectDir, next);
99
- } catch {
100
- return false;
101
- }
203
+ });
102
204
  }
103
205
 
104
206
  /**
@@ -144,9 +246,9 @@ export function markStopWarned(projectDir) {
144
246
  */
145
247
  export function extractRunFeature(prompt) {
146
248
  try {
147
- const m = prompt.match(/(?:\/|\$)vibe\.run\s+([^\s]+)/i);
249
+ const m = prompt.match(/(?:\/|\$)vibe\.run\s+(?:"([^"]+)"|'([^']+)'|([^\s]+))/i);
148
250
  if (!m) return null;
149
- const token = m[1];
251
+ const token = m[1] || m[2] || m[3];
150
252
  // 플래그(-- 시작)나 키워드는 기능명이 아님
151
253
  if (token.startsWith('-')) return null;
152
254
  return token;
@@ -2,7 +2,7 @@
2
2
  /**
3
3
  * verify-ledger CLI — vibe.verify 결과를 run-ledger에 기록.
4
4
  *
5
- * 사용법: node hooks/scripts/verify-ledger.js pass|fail
5
+ * 사용법: node hooks/scripts/verify-ledger.js pass|fail <run-id> <results-json-path>
6
6
  *
7
7
  * vibe.verify/SKILL.md 에서 검증 완료 시 호출.
8
8
  * stdout 출력은 에이전트가 Bash로 실행하는 컨텍스트이므로 허용.
@@ -10,13 +10,33 @@
10
10
  */
11
11
 
12
12
  import { recordVerify } from './lib/run-ledger.js';
13
+ import fs from 'fs';
13
14
 
14
15
  const arg = process.argv[2];
16
+ const runId = process.argv[3];
17
+ const resultsPath = process.argv[4];
15
18
  const passed = arg === 'pass';
16
19
  const projectDir = process.env.CLAUDE_PROJECT_DIR || process.cwd();
17
20
 
18
- recordVerify(projectDir, passed);
21
+ function readResults(filePath) {
22
+ try {
23
+ if (!filePath) return [];
24
+ const parsed = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
25
+ return Array.isArray(parsed) ? parsed : [];
26
+ } catch {
27
+ return [];
28
+ }
29
+ }
30
+
31
+ const recorded = recordVerify(projectDir, passed, {
32
+ runId,
33
+ verificationResults: readResults(resultsPath),
34
+ });
19
35
 
20
36
  const status = passed ? 'pass' : 'fail';
21
- process.stdout.write(`[verify-ledger] recorded: verifyPassed=${passed} (${status})\n`);
37
+ if (recorded) {
38
+ process.stdout.write(`[verify-ledger] recorded: verifyPassed=${passed} (${status})\n`);
39
+ } else {
40
+ process.stderr.write('[verify-ledger] WARNING: run-ledger or evidence.json write failed\n');
41
+ }
22
42
  process.exit(0);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@su-record/vibe",
3
- "version": "3.2.0",
3
+ "version": "3.2.1",
4
4
  "description": "AI Coding Framework for Claude Code — 7+ agents, 60 skills, multi-LLM orchestration",
5
5
  "type": "module",
6
6
  "main": "dist/cli/index.js",
@@ -54,10 +54,15 @@ Task(subagent_type="Explore",
54
54
  `vibe/templates/spec-template.md` 구조로 `.vibe/specs/{feature-name}.md` 를 작성한다. 핵심 요건:
55
55
 
56
56
  - **Overview / Goal** — 무엇을, 왜. 1-3 문장.
57
+ - **Context Sources** — 입력으로 사용한 파일·문서·URL·관측 상태. 출처 없는 추정은 Assumptions 로 분리.
58
+ - **Requirements** — `REQ-{feature}-NNN` ID와 연결된 Done Criteria를 표로 명시.
57
59
  - **Done Criteria** — 결정론적 게이트만. 각 항목은 "명령/관찰로 pass·fail 판정 가능"해야 한다 (테스트 exit code, 빌드 성공, 특정 동작 관찰). "잘 동작한다" 류 서술 금지 — 이것이 루프의 JUDGE 입력이 된다.
60
+ - **Evidence Required** — Done 을 증명할 명령 결과·테스트 리포트·로그·스크린샷·코드 위치.
61
+ - **Human Taste (Non-Blocking)** — UX·브랜드·제품 감각처럼 release 시 사람이 판단할 기준. 완료 게이트로 쓰지 않는다.
58
62
  - **Scenarios** — Given-When-Then. Happy path + 주요 edge case. 각 시나리오는 Done Criteria 중 하나에 매핑.
59
63
  - **Out of Scope** — 이번에 하지 않는 것을 명시 (비어 있으면 스코프 팽창 신호).
60
64
  - **Assumptions** — 3단계에서 채택한 기본값 전부.
65
+ - **Constraints** — 구현·보안·호환성 경계. execution packet으로 압축돼도 반드시 보존한다.
61
66
  - **API Contract** (해당 시에만) — 엔드포인트/요청/응답 형태. 이 섹션이 있으면 이후 `/vibe.contract` 가 drift 를 검사한다.
62
67
 
63
68
  이어서 `.vibe/features/{feature-name}.feature` 를 생성한다: 시나리오 섹션을 gherkin 으로 변환 (Done Criteria ↔ Scenario 매핑 유지). `/vibe.run` 이 이 파일을 구현·검증 단위로 사용한다.
@@ -71,6 +76,8 @@ Task(subagent_type="Explore",
71
76
  작성 직후, 아래 체크리스트로 자기 SPEC 을 **1회** 점검하고 걸리는 항목을 즉시 고친다. 외부 LLM 리뷰 없음, 수렴 루프 없음 — 한 번 고치면 끝.
72
77
 
73
78
  - [ ] 모든 Done Criteria 가 명령/관찰로 판정 가능한가 (모델 자기 보고가 아닌)
79
+ - [ ] Context Sources 와 Assumptions 가 분리됐고, 각 Done Criteria 의 Evidence Required 가 있는가
80
+ - [ ] Human Taste 가 결정론적 완료 게이트에 섞이지 않았는가
74
81
  - [ ] 모든 시나리오가 Done Criteria 에 매핑되는가 (고아 시나리오 없음)
75
82
  - [ ] 수치가 필요한 곳에 수치가 있는가 (제한·타임아웃·크기 — 없으면 기본값 + Assumptions)
76
83
  - [ ] Out of Scope 가 비어 있지 않은가
@@ -201,18 +201,41 @@ Step 3: If neither → Error: "Run /vibe.spec first"
201
201
 
202
202
  **Split structure:** Load `_index.md` first, then phase files in order. Execute phases sequentially (or per `--phase` flag).
203
203
 
204
+ ### 1-0. Compile + validate execution packet (MANDATORY)
205
+
206
+ For a monolithic SPEC, compile it after resolving the canonical path. For a split SPEC, do not compile `_index.md`; defer this step until each active phase file is loaded in Phase Isolation Step B. Compile with `writeExecutionPacket`, then immediately verify the saved artifact with `validateExecutionPacket`.
207
+
208
+ ```bash
209
+ node -e "import('file://{{VIBE_PATH}}/dist/tools/index.js').then(t => {
210
+ const projectPath=process.cwd(), specPath='.vibe/specs/{feature-name}.md';
211
+ const profile='{codex-or-claude-code}';
212
+ const written=t.writeExecutionPacket({projectPath,specPath,profile});
213
+ if(!written.ok){console.error(JSON.stringify(written.errors));process.exit(1)}
214
+ const checked=t.validateExecutionPacket({projectPath,specPath,packetPath:written.packetPath});
215
+ if(!checked.valid){console.error(checked.code);process.exit(1)}
216
+ console.log(written.packetPath);
217
+ })"
218
+ ```
219
+
220
+ - Codex uses profile `codex`; Claude Code uses `claude-code`.
221
+ - Split SPECs compile each active `phase-N-*.md` immediately before that phase runs; `_index.md` remains the overview ANCHOR and is not treated as a phase contract.
222
+ - Use the packet only when validation returns `valid: true`.
223
+ - `STALE_PACKET`, invalid packet, preservation-audit failure, or budget failure is blocking: recompile from the canonical SPEC and never silently fall back to an unvalidated packet.
224
+ - The packet is a derived execution view. The canonical SPEC remains the ANCHOR and source of truth.
225
+
204
226
  ### 1-1. Phase Isolation Protocol (Large SPEC Guard, MANDATORY for 3+ phases)
205
227
 
206
228
  ```
207
229
  Step A: Read _index.md (overview only — phase list, REQ IDs)
208
230
  Step B: For each Phase N:
209
231
  1. RE-READ Phase N SPEC section (every time, no memory)
210
- 2. RE-READ Phase N Feature scenarios
211
- 3. Extract Phase N scope: files, scenarios, requirements
212
- 4. Implement Phase N scenarios
213
- 5. Verify Phase N
214
- 6. Write Phase Checkpoint → .vibe/checkpoints/
215
- 7. DISCARD Phase N details from working memory
232
+ 2. Compile + validate Phase N execution packet using the phase file path
233
+ 3. RE-READ Phase N Feature scenarios
234
+ 4. Extract Phase N scope: files, scenarios, requirements
235
+ 5. Implement Phase N scenarios
236
+ 6. Verify Phase N
237
+ 7. Write Phase Checkpoint .vibe/checkpoints/
238
+ 8. DISCARD Phase N details from working memory
216
239
  Step C: Next Phase
217
240
  ```
218
241
 
@@ -213,18 +213,18 @@ RTM status === 'empty'
213
213
 
214
214
  ### Run-ledger flow
215
215
 
216
- `/vibe.verify` records its outcome via `hooks/scripts/verify-ledger.js pass|fail`. This writes `verifyPassed` and `verifyAt` into `.vibe/metrics/run-ledger.json`. Downstream gates consume this record:
216
+ `/vibe.verify` records its outcome through `hooks/scripts/verify-ledger.js`, binding the current run ID and command-result evidence. This writes `verifyPassed` and `verifyAt` into `.vibe/metrics/run-ledger.json`. Downstream gates consume this record:
217
217
 
218
218
  | Gate | Behavior |
219
219
  |------|----------|
220
220
  | `auto-commit` | Commits only when `verifyPassed === true` AND `verifyAt > runStarted` |
221
221
  | Stop hook | Warns when `runStarted && !verifyPassed`; blocks once if `verifyGate.mode === 'block'` |
222
222
 
223
- **To register a passing trace as verified**, run `/vibe.verify` after `/vibe.trace` reports acceptable coverage. The verify skill calls `verify-ledger.js pass` internally — you do not invoke it manually.
223
+ **To register a passing trace as verified**, run `/vibe.verify` after `/vibe.trace` reports acceptable coverage. The verify skill records the current run ID and command evidence internally — you do not invoke the ledger CLI manually.
224
224
 
225
225
  ```
226
226
  /vibe.trace "login" → RTM: 9/9 (100%)
227
- /vibe.verify "login" → runs checks → calls verify-ledger.js pass
227
+ /vibe.verify "login" → runs checks → records pass + run ID + command results
228
228
  → .vibe/metrics/run-ledger.json updated
229
229
  auto-commit / Stop gate → verifyPassed=true, gate clears
230
230
  ```
@@ -104,7 +104,7 @@ Load skill `contract` with: check "{feature}"
104
104
 
105
105
  ### 7. Metrics + Ledger update (MANDATORY final step)
106
106
 
107
- Record run metrics, then write the verify result to the run ledger. This is the machine-readable JUDGE record consumed by the Stop-hook verify gate, auto-commit verify gate, and loop-contract gates.
107
+ Record run metrics, then write the verify result to the run ledger. This is the machine-readable deterministic JUDGE record consumed by the Stop-hook verify gate, auto-commit verify gate, and loop-contract gates. `recordVerify` also writes `.vibe/runs/{run-id}/evidence.json`; Model Judge findings remain advisory-only and Human Taste remains release-only.
108
108
 
109
109
  ```bash
110
110
  # Append step-count history (ok if current-run.json missing)
@@ -113,15 +113,21 @@ const fs=require('fs'),p='.vibe/metrics';
113
113
  try{const c=JSON.parse(fs.readFileSync(p+'/current-run.json','utf-8'));
114
114
  fs.appendFileSync(p+'/history.jsonl',JSON.stringify({verifiedAt:new Date().toISOString(),feature:c.feature,startedAt:c.startedAt,steps:c.steps||0})+'\n');}catch{}"
115
115
 
116
- # Record verify result pass | fail (calls recordVerify on the run ledger)
116
+ # Write the exact commands run in steps 2-3 and their exit codes.
117
+ mkdir -p .vibe/metrics
118
+ # Write `.vibe/metrics/verification-results.json` as:
119
+ # [{"command":"npm test","exitCode":0}, ...]
120
+
121
+ # Bind the result to the current run and its command evidence.
117
122
  HOOKS_DIR="${VIBE_PATH:-$(npm root -g 2>/dev/null)/@su-record/vibe}/hooks/scripts"
118
- [ -f "$HOOKS_DIR/verify-ledger.js" ] && node "$HOOKS_DIR/verify-ledger.js" pass # or: fail
123
+ RUN_ID=$(node -p "JSON.parse(require('fs').readFileSync('.vibe/metrics/run-ledger.json','utf8')).runId")
124
+ [ -f "$HOOKS_DIR/verify-ledger.js" ] && node "$HOOKS_DIR/verify-ledger.js" pass "$RUN_ID" .vibe/metrics/verification-results.json # or: fail
119
125
 
120
126
  # Recipe extraction (best-effort, silent)
121
127
  [ -f "$HOOKS_DIR/recipe-extractor.js" ] && node "$HOOKS_DIR/recipe-extractor.js" 2>/dev/null || true
122
128
  ```
123
129
 
124
- Use `pass` only when the summary in step 4 is PASS; otherwise `fail`. Skipping this step leaves `verifyPassed` unset and downstream gates will treat the run as unverified.
130
+ Use `pass` only when the summary in step 4 is PASS; otherwise `fail`. A passing record requires at least one command result and every exit code must be zero. A stale run ID, missing evidence, or mismatched result leaves `verifyPassed` unset and downstream gates treat the run as unverified.
125
131
 
126
132
  ## Failure escalation (convergence-based, no retry cap)
127
133
 
@@ -17,15 +17,19 @@
17
17
  → 루프:
18
18
  ANCHOR 디스크에서 재고정: SPEC + run-ledger + scope.json (+ 직전 인박스)
19
19
  ACT 파이프라인 실행 (스킬 체인)
20
- JUDGE 결정론 판정만 인정: run-ledger verifyPassed │ 테스트 exit code │ RTM status
21
- 모델의 "완료했습니다" 자기 보고는 종료 조건이 될 수 없다
22
- RECORD run-ledger(현재 회전) + loop-history.jsonl(회전 이력, 스케줄 루프)
20
+ JUDGE Deterministic Judge(blocking): run-ledger verifyPassed │ 테스트 exit code │ RTM status
21
+ Model Judge(advisory-only): 발견을 제안하지만 완료 권한 없음
22
+ Human Taste(release-only): UX·브랜드·제품 감각을 판단하지만 루프 완료 권한 없음
23
+ RECORD run-ledger + `.vibe/runs/{run-id}/evidence.json` + loop-history.jsonl
23
24
  → 종료(EXIT): 게이트 전부 통과 │ stuck │ max_iterations │ 예산 상한
24
25
  ```
25
26
 
26
27
  ### ANCHOR가 컨텍스트 오염 방어인 이유
27
28
  루프 상태는 컨텍스트가 아니라 디스크에 산다. 매 회전이 아티팩트에서 다시 시작하므로 컨텍스트가 오염되거나 compact로 소실돼도 루프는 깨지지 않으며, 회전마다 fresh 컨텍스트(서브에이전트)로 돌려도 된다.
28
29
 
30
+ ### Judge 권한 경계
31
+ 종료 권한은 테스트 exit code·run-ledger·RTM 같은 **결정론적 Judge**에만 있다. Model Judge는 누락·모순·위험을 발견하는 보조 수단이며, 발견을 테스트나 관측 가능한 기준으로 내리기 전에는 차단 근거가 아니다. Human Taste는 공개·배포 시점의 사람 판단으로 남고 루프의 완료 상태를 변경하지 않는다.
32
+
29
33
  ### stuck (결정론)
30
34
  연속 2회 회전의 발견(discover/findings) 해시가 동일 → 중단하고 사람에게 (`loop-ledger.js check-stuck`이 판정·기록). "다시 해보면 될 것 같다"는 모델 판단으로 무시 금지.
31
35
 
@@ -10,13 +10,29 @@
10
10
 
11
11
  {What and why — 1-3 sentences.}
12
12
 
13
+ ### Context Sources
14
+
15
+ - {File, document, URL, or observed system state used as input}
16
+
13
17
  ### Assumptions
14
18
 
15
19
  - {Default adopted without asking — e.g., session expiry 24h}
16
20
 
21
+ ### Constraints
22
+
23
+ - {Invariant or implementation boundary that every execution packet must preserve}
24
+
25
+ ---
26
+
27
+ ## 2. Requirements
28
+
29
+ | ID | Requirement | Done Criteria |
30
+ |----|-------------|---------------|
31
+ | REQ-{feature}-001 | {Observable functional requirement} | D1, D2 |
32
+
17
33
  ---
18
34
 
19
- ## 2. Done Criteria (deterministic gates)
35
+ ## 3. Done Criteria (deterministic gates)
20
36
 
21
37
  > Each criterion must be judgeable by a command or observable behavior — never by self-report.
22
38
  > These are the JUDGE inputs of the loop (`vibe/rules/loop-contract.md`); `/vibe.verify` records the result in `.vibe/metrics/run-ledger.json`.
@@ -26,9 +42,18 @@
26
42
  | D1 | {e.g., all scenarios in the feature file pass} | {e.g., `npx vitest run` exit 0} |
27
43
  | D2 | {e.g., build succeeds with no type errors} | {e.g., `npm run build` exit 0} |
28
44
 
45
+ ### Evidence Required
46
+
47
+ - D1 → {Command result, test report, log, screenshot, or verified code location}
48
+ - D2 → {Evidence required for this specific criterion}
49
+
50
+ ### Human Taste (Non-Blocking)
51
+
52
+ - {UX, brand, or product-quality review reserved for the release decision; never a loop completion gate}
53
+
29
54
  ---
30
55
 
31
- ## 3. Scenarios
56
+ ## 4. Scenarios
32
57
 
33
58
  > Mirrored to `.vibe/features/{feature}.feature` (gherkin). Every scenario maps to a Done criterion.
34
59
 
@@ -46,13 +71,13 @@ Scenario: {Edge case title} # → D1
46
71
 
47
72
  ---
48
73
 
49
- ## 4. Out of Scope
74
+ ## 5. Out of Scope
50
75
 
51
76
  - {Explicitly not doing this time — must not be empty}
52
77
 
53
78
  ---
54
79
 
55
- ## 5. API Contract (only if the feature exposes an API)
80
+ ## 6. API Contract (only if the feature exposes an API)
56
81
 
57
82
  > Presence of this section enables `/vibe.contract` drift detection.
58
83
 
@@ -64,8 +89,10 @@ Response: 201 {...}
64
89
 
65
90
  ---
66
91
 
67
- ## 6. Verification
92
+ ## 7. Verification
68
93
 
69
94
  - `/vibe.run "{feature}"` implements scenario-by-scenario, verifying each immediately.
70
95
  - `/vibe.verify "{feature}"` judges the Done Criteria and sets `verifyPassed` in the run-ledger.
96
+ - Verification writes `.vibe/runs/{run-id}/evidence.json`; only deterministic Judge results can complete the loop.
97
+ - Model Judge findings are advisory-only. Human Taste is release-only.
71
98
  - Gate = all Done Criteria pass (exit codes / observed behavior) — loop continues until gates pass, stuck, or max iterations.