okstra 0.92.0 → 0.93.0

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.
@@ -0,0 +1,476 @@
1
+ # 에이전트 작업 자세(Operating Standard) 주입 — 구현 계획
2
+
3
+ > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
4
+
5
+ **Goal:** 전 okstra 에이전트에 공통 "작업 자세" 행동 규약을 주입하고, 코드 작성 워커(executor)에 완료 전 자기검증 게이트를 추가한다.
6
+
7
+ **Architecture:** 공통 코어를 `templates/operating-standard.md` SSOT 한 곳에 두고, 워커 preamble·리드 contract 가 그 코어를 verbatim 인라인한다(앵커 추가-read 없음 — 토큰 절약). 단위 테스트가 인라인 사본이 SSOT 와 일치하는지 검증해 drift 를 막는다. 코드 워커 완료 전 게이트는 별도 sidecar `_implementation-self-check.md` 로 신설하고, 기존 preflight 와 동일한 전달 경로(Claude executor 직접 read / codex·antigravity executor 는 lead 가 dispatch 프롬프트에 body append)를 탄다.
8
+
9
+ **Tech Stack:** Markdown 프롬프트/계약 파일, Python(`scripts/okstra_ctl/codex_dispatch.py`), pytest, `tools/build.mjs`(소스→runtime 복사).
10
+
11
+ ## Global Constraints
12
+
13
+ - 공통 코어는 **영어**, 6줄 이내(헤더 1문장 + 불릿 5개). 톤은 기존 preamble 의 간결 명령형.
14
+ - 단일 참조점: SSOT 는 `templates/operating-standard.md`. preamble·contract 인라인은 SSOT 의 코어 블록과 **문자 그대로 일치**해야 한다(테스트로 강제).
15
+ - 앵커 추가-read 금지(워커가 파일을 하나 더 읽게 만들지 않는다).
16
+ - `runtime/` 직접 수정 금지 — `npm run build` 로만 동기화.
17
+ - 새/수정 함수는 50 effective lines 이하.
18
+ - 정체성/경력 서사형 페르소나 문구 금지(형용사를 행동 동사로만).
19
+ - 문서/CHANGES 산문은 한국어, 코드 식별자·경로·CLI 플래그는 영어.
20
+ - 테스트 repo 루트 helper: `REPO = pathlib.Path(__file__).resolve().parents[1]`.
21
+
22
+ ---
23
+
24
+ ### Task 1: 공통 자세 SSOT 파일 신설
25
+
26
+ **Files:**
27
+ - Create: `templates/operating-standard.md`
28
+ - Test: `tests/test_operating_standard.py`
29
+
30
+ **Interfaces:**
31
+ - Produces: SSOT 파일. 이후 Task 2·3 가 이 파일의 "## Common core" 블록을 verbatim 인라인하고, Task 4 테스트가 일치를 검증한다.
32
+
33
+ - [ ] **Step 1: 실패하는 테스트 작성**
34
+
35
+ `tests/test_operating_standard.py`:
36
+ ```python
37
+ import pathlib
38
+
39
+ REPO = pathlib.Path(__file__).resolve().parents[1]
40
+ SSOT = REPO / "templates" / "operating-standard.md"
41
+
42
+
43
+ def _core_block(text: str) -> str:
44
+ """## Common core 헤딩 다음부터 다음 ## 헤딩 전까지의 본문(앞뒤 공백 제거)."""
45
+ after = text.split("## Common core", 1)[1]
46
+ return after.split("\n## ", 1)[0].strip()
47
+
48
+
49
+ def test_ssot_exists_with_core_and_addenda():
50
+ text = SSOT.read_text(encoding="utf-8")
51
+ core = _core_block(text)
52
+ assert "Evidence over assertion" in core
53
+ assert "Shortest sound path" in core
54
+ assert "Fit what's here" in core
55
+ assert "Hold your own line" in text # 워커 독립성 addendum
56
+ assert "Own the synthesis" in text # 리드 종합 addendum
57
+ ```
58
+
59
+ - [ ] **Step 2: 테스트 실패 확인**
60
+
61
+ Run: `python3 -m pytest tests/test_operating_standard.py::test_ssot_exists_with_core_and_addenda -v`
62
+ Expected: FAIL — `FileNotFoundError` (SSOT 파일 없음)
63
+
64
+ - [ ] **Step 3: SSOT 파일 작성**
65
+
66
+ `templates/operating-standard.md`:
67
+ ```markdown
68
+ # Operating Standard (canonical)
69
+
70
+ This file is the single source of truth for the operating standard injected into
71
+ every okstra agent. The "## Common core" block below is inlined verbatim into
72
+ `templates/worker-prompt-preamble.md` (workers) and
73
+ `prompts/lead/okstra-lead-contract.md` (lead). A unit test asserts both inlined
74
+ copies match this core — update all three together.
75
+
76
+ ## Common core
77
+
78
+ Work like a senior engineer who owns this result, not a commentator on it.
79
+ - Evidence over assertion — back every claim with a file:line, or mark it an explicit assumption. Never state the unverified as fact.
80
+ - Read before you reason — read each required input end to end; when you lack basis, write "insufficient evidence" instead of filling the gap plausibly.
81
+ - Shortest sound path — chase the most likely cause first; don't re-verify what is settled or pad with restatement.
82
+ - Decide, don't survey — when options exist, give the trade-off and one recommendation, not an exhaustive list.
83
+ - Fit what's here — match the surrounding code and prose; size the response to the request.
84
+
85
+ ## Role addendum
86
+
87
+ - Analysis / verification workers add this line: `Hold your own line — reason from your independent angle; do not drift toward the other workers' likely answers or echo the user. Triangulation fails if your view isn't genuinely yours.`
88
+ - Lead adds this line: `Own the synthesis — weigh worker outputs on evidence, not consensus; a better-grounded dissent outranks the majority.`
89
+ - Report-writer worker: common core only (no independence addendum — it authors, it does not analyze).
90
+ ```
91
+
92
+ - [ ] **Step 4: 테스트 통과 확인**
93
+
94
+ Run: `python3 -m pytest tests/test_operating_standard.py::test_ssot_exists_with_core_and_addenda -v`
95
+ Expected: PASS
96
+
97
+ - [ ] **Step 5: 커밋**
98
+
99
+ ```bash
100
+ git add templates/operating-standard.md tests/test_operating_standard.py
101
+ git commit -m "feat(agents): operating-standard SSOT 신설"
102
+ ```
103
+
104
+ ---
105
+
106
+ ### Task 2: 워커 preamble 에 공통 코어 + 독립성 인라인
107
+
108
+ **Files:**
109
+ - Modify: `templates/worker-prompt-preamble.md` (헤더 직후, `## Required reading` 앞)
110
+ - Test: `tests/test_operating_standard.py`
111
+
112
+ **Interfaces:**
113
+ - Consumes: Task 1 의 `## Common core` 블록(verbatim).
114
+ - Produces: 워커가 출력 전 마주치는 자세 헤더. 모든 분석/검증 워커에 적용.
115
+
116
+ - [ ] **Step 1: 실패하는 테스트 추가**
117
+
118
+ `tests/test_operating_standard.py` 에 추가:
119
+ ```python
120
+ PREAMBLE = REPO / "templates" / "worker-prompt-preamble.md"
121
+
122
+
123
+ def test_core_inlined_in_preamble():
124
+ core = _core_block(SSOT.read_text(encoding="utf-8"))
125
+ preamble = PREAMBLE.read_text(encoding="utf-8")
126
+ assert core in preamble # 코어 verbatim 일치(drift 차단)
127
+ assert "Hold your own line" in preamble # 워커 독립성 addendum
128
+ assert "Own the synthesis" not in preamble # 리드 전용 줄은 없어야
129
+ ```
130
+
131
+ - [ ] **Step 2: 테스트 실패 확인**
132
+
133
+ Run: `python3 -m pytest tests/test_operating_standard.py::test_core_inlined_in_preamble -v`
134
+ Expected: FAIL — `assert core in preamble` (아직 인라인 안 됨)
135
+
136
+ - [ ] **Step 3: preamble 수정**
137
+
138
+ `templates/worker-prompt-preamble.md` 의 5번째 줄(`It replaces the previous practice ...` 문단) 다음, `---` 구분선 앞에 아래 섹션을 삽입한다. `## Common core` 의 본문(Step 3 Task 1 의 `Work like a senior engineer ...` ~ `... size the response to the request.` 5불릿)을 verbatim 복사한다:
139
+
140
+ ```markdown
141
+
142
+ ## Operating standard (read before any output)
143
+
144
+ Work like a senior engineer who owns this result, not a commentator on it.
145
+ - Evidence over assertion — back every claim with a file:line, or mark it an explicit assumption. Never state the unverified as fact.
146
+ - Read before you reason — read each required input end to end; when you lack basis, write "insufficient evidence" instead of filling the gap plausibly.
147
+ - Shortest sound path — chase the most likely cause first; don't re-verify what is settled or pad with restatement.
148
+ - Decide, don't survey — when options exist, give the trade-off and one recommendation, not an exhaustive list.
149
+ - Fit what's here — match the surrounding code and prose; size the response to the request.
150
+ - Hold your own line — reason from your independent angle; do not drift toward the other workers' likely answers or echo the user. Triangulation fails if your view isn't genuinely yours.
151
+ ```
152
+
153
+ > 주의: `_core_block` 비교는 SSOT 의 코어 본문(5불릿)이 substring 으로 들어있는지 보므로, 여기서 5불릿은 SSOT 와 한 글자도 다르면 안 된다. 독립성 줄은 코어 다음에 별도 불릿으로 붙인다.
154
+
155
+ - [ ] **Step 4: 테스트 통과 확인**
156
+
157
+ Run: `python3 -m pytest tests/test_operating_standard.py -v`
158
+ Expected: PASS (Task 1·2 테스트 모두)
159
+
160
+ - [ ] **Step 5: 커밋**
161
+
162
+ ```bash
163
+ git add templates/worker-prompt-preamble.md tests/test_operating_standard.py
164
+ git commit -m "feat(agents): 워커 preamble 에 작업 자세 + 독립성 인라인"
165
+ ```
166
+
167
+ ---
168
+
169
+ ### Task 3: 리드 contract 에 공통 코어 + 종합 인라인
170
+
171
+ **Files:**
172
+ - Modify: `prompts/lead/okstra-lead-contract.md` (`# Okstra Lead Contract` 직후, `## Overview` 앞)
173
+ - Test: `tests/test_operating_standard.py`
174
+
175
+ **Interfaces:**
176
+ - Consumes: Task 1 의 `## Common core` 블록(verbatim).
177
+ - Produces: 리드가 오케스트레이션 전 마주치는 자세 헤더.
178
+
179
+ - [ ] **Step 1: 실패하는 테스트 추가**
180
+
181
+ ```python
182
+ CONTRACT = REPO / "prompts" / "lead" / "okstra-lead-contract.md"
183
+
184
+
185
+ def test_core_inlined_in_lead_contract():
186
+ core = _core_block(SSOT.read_text(encoding="utf-8"))
187
+ contract = CONTRACT.read_text(encoding="utf-8")
188
+ assert core in contract
189
+ assert "Own the synthesis" in contract # 리드 종합 addendum
190
+ assert "Hold your own line" not in contract # 워커 전용 줄은 없어야
191
+ ```
192
+
193
+ - [ ] **Step 2: 테스트 실패 확인**
194
+
195
+ Run: `python3 -m pytest tests/test_operating_standard.py::test_core_inlined_in_lead_contract -v`
196
+ Expected: FAIL — `assert core in contract`
197
+
198
+ - [ ] **Step 3: contract 수정**
199
+
200
+ `prompts/lead/okstra-lead-contract.md` 의 `# Okstra Lead Contract` 줄 다음, `## Overview` 앞에 삽입(코어 5불릿 verbatim + 리드 종합 줄):
201
+
202
+ ```markdown
203
+
204
+ ## Operating standard
205
+
206
+ Work like a senior engineer who owns this result, not a commentator on it.
207
+ - Evidence over assertion — back every claim with a file:line, or mark it an explicit assumption. Never state the unverified as fact.
208
+ - Read before you reason — read each required input end to end; when you lack basis, write "insufficient evidence" instead of filling the gap plausibly.
209
+ - Shortest sound path — chase the most likely cause first; don't re-verify what is settled or pad with restatement.
210
+ - Decide, don't survey — when options exist, give the trade-off and one recommendation, not an exhaustive list.
211
+ - Fit what's here — match the surrounding code and prose; size the response to the request.
212
+ - Own the synthesis — weigh worker outputs on evidence, not consensus; a better-grounded dissent outranks the majority.
213
+ ```
214
+
215
+ - [ ] **Step 4: 테스트 통과 확인**
216
+
217
+ Run: `python3 -m pytest tests/test_operating_standard.py -v`
218
+ Expected: PASS
219
+
220
+ - [ ] **Step 5: 커밋**
221
+
222
+ ```bash
223
+ git add prompts/lead/okstra-lead-contract.md tests/test_operating_standard.py
224
+ git commit -m "feat(agents): 리드 contract 에 작업 자세 + 종합 인라인"
225
+ ```
226
+
227
+ ---
228
+
229
+ ### Task 4: 완료 전 자기검증 게이트 sidecar 신설
230
+
231
+ **Files:**
232
+ - Create: `prompts/profiles/_implementation-self-check.md`
233
+ - Test: `tests/test_operating_standard.py`
234
+
235
+ **Interfaces:**
236
+ - Produces: 완료 전 게이트 본문. Task 5(executor/verifier 참조)·Task 6(codex_dispatch append)이 이 파일을 사용한다.
237
+
238
+ - [ ] **Step 1: 실패하는 테스트 추가**
239
+
240
+ ```python
241
+ SELFCHECK = REPO / "prompts" / "profiles" / "_implementation-self-check.md"
242
+
243
+
244
+ def test_self_check_gate_has_four_items():
245
+ text = SELFCHECK.read_text(encoding="utf-8")
246
+ assert "Implementation self-check" in text
247
+ for item in ("functions:", "conventions:", "names & comments:", "verification:"):
248
+ assert item in text
249
+ ```
250
+
251
+ - [ ] **Step 2: 테스트 실패 확인**
252
+
253
+ Run: `python3 -m pytest tests/test_operating_standard.py::test_self_check_gate_has_four_items -v`
254
+ Expected: FAIL — `FileNotFoundError`
255
+
256
+ - [ ] **Step 3: sidecar 작성**
257
+
258
+ `prompts/profiles/_implementation-self-check.md`:
259
+ ```markdown
260
+ <!--
261
+ Completion-time counterpart to _coding-conventions-preflight.md. The preflight
262
+ gate runs BEFORE the first Edit/Write; this gate runs BEFORE the executor claims
263
+ done. Same delivery paths: Claude executor reads this file directly; codex /
264
+ antigravity executor cannot read it (outside the CLI sandbox), so the lead
265
+ appends this file's body into the persisted executor prompt at dispatch time
266
+ (see codex_dispatch.py `_implementation_executor_tail`).
267
+ -->
268
+
269
+ # Implementation self-check (BLOCKING — before you claim done)
270
+
271
+ Before declaring the change complete, write one confirming line per item to your
272
+ audit sidecar. If any item fails, fix it or surface the violation — do not claim
273
+ done on a failing item.
274
+
275
+ - functions: every new/edited function ≤50 effective lines, single purpose
276
+ - conventions: applied the routed pack + project patterns (name which ones)
277
+ - names & comments: names say what, comments say why; no obvious-restatement comments; no truthful-name violations
278
+ - verification: ran the actual build/test (paste the command + its result); no dead or commented-out code left behind
279
+ ```
280
+
281
+ - [ ] **Step 4: 테스트 통과 확인**
282
+
283
+ Run: `python3 -m pytest tests/test_operating_standard.py::test_self_check_gate_has_four_items -v`
284
+ Expected: PASS
285
+
286
+ - [ ] **Step 5: 커밋**
287
+
288
+ ```bash
289
+ git add prompts/profiles/_implementation-self-check.md tests/test_operating_standard.py
290
+ git commit -m "feat(agents): 완료 전 자기검증 게이트 sidecar 신설"
291
+ ```
292
+
293
+ ---
294
+
295
+ ### Task 5: executor/verifier 프로필에 self-check 참조 배선
296
+
297
+ **Files:**
298
+ - Modify: `prompts/profiles/_implementation-executor.md` (preflight 불릿 다음에 self-check 불릿 추가)
299
+ - Modify: `prompts/profiles/_implementation-verifier.md` (코드 수정 시 동일 게이트 적용 declaration)
300
+ - Test: `tests/test_operating_standard.py`
301
+
302
+ **Interfaces:**
303
+ - Consumes: Task 4 의 `_implementation-self-check.md`.
304
+ - Produces: executor 가 done 전 게이트를 read/append 하도록 지시하는 계약. codex/antigravity 경로는 Task 6 가 물리적으로 append.
305
+
306
+ - [ ] **Step 1: 실패하는 테스트 추가**
307
+
308
+ ```python
309
+ EXECUTOR = REPO / "prompts" / "profiles" / "_implementation-executor.md"
310
+
311
+
312
+ def test_executor_references_self_check_gate():
313
+ text = EXECUTOR.read_text(encoding="utf-8")
314
+ assert "_implementation-self-check.md" in text
315
+ assert "before" in text.lower() and "done" in text.lower()
316
+ ```
317
+
318
+ - [ ] **Step 2: 테스트 실패 확인**
319
+
320
+ Run: `python3 -m pytest tests/test_operating_standard.py::test_executor_references_self_check_gate -v`
321
+ Expected: FAIL — `assert "_implementation-self-check.md" in text`
322
+
323
+ - [ ] **Step 3: executor 프로필 수정**
324
+
325
+ `prompts/profiles/_implementation-executor.md` 의 Coding-conventions preflight 불릿(line 22~24 블록) **다음**에 아래 불릿을 추가한다. 문구는 preflight 불릿의 전달-경로 표현을 그대로 따른다:
326
+
327
+ ```markdown
328
+ - **Completion self-check (BLOCKING — runs BEFORE you claim the stage done).** The gate body is a single source at `prompts/profiles/_implementation-self-check.md` (sibling of this sidecar): the four-item completion gate (functions ≤50 lines, conventions applied, truthful names & why-comments, real build/test run). Do NOT re-type it from memory — deliver it by file so it cannot drift.
329
+ - **Claude executor:** Read `_implementation-self-check.md` end-to-end before appending the `status:"done"` row, then write one confirming line per item to your audit sidecar.
330
+ - **CLI executor (codex / antigravity):** the CLI process cannot Read this path (outside its sandbox). The lead appends this file's body into the persisted executor prompt at dispatch time, immediately after the preflight body (see `codex_dispatch.py` `_implementation_executor_tail`). The head-less executor honours both gates from the single prompt.
331
+ ```
332
+
333
+ - [ ] **Step 4: verifier 프로필 수정**
334
+
335
+ `prompts/profiles/_implementation-verifier.md` 끝부분에 declaration 한 줄 추가(verifier 는 보통 코드를 수정하지 않으나, 수정하는 경우 동일 게이트가 바인딩됨을 명시):
336
+
337
+ ```markdown
338
+ - **Completion self-check (declaration).** If this verifier edits project code (not just inspects it), the same completion gate at `prompts/profiles/_implementation-self-check.md` binds before claiming done — same four items, recorded in the audit sidecar.
339
+ ```
340
+
341
+ - [ ] **Step 5: 테스트 통과 확인**
342
+
343
+ Run: `python3 -m pytest tests/test_operating_standard.py::test_executor_references_self_check_gate -v`
344
+ Expected: PASS
345
+
346
+ - [ ] **Step 6: 커밋**
347
+
348
+ ```bash
349
+ git add prompts/profiles/_implementation-executor.md prompts/profiles/_implementation-verifier.md tests/test_operating_standard.py
350
+ git commit -m "feat(agents): executor/verifier 에 완료 전 self-check 배선"
351
+ ```
352
+
353
+ ---
354
+
355
+ ### Task 6: codex_dispatch 가 self-check body 를 CLI 프롬프트에 append
356
+
357
+ **Files:**
358
+ - Modify: `scripts/okstra_ctl/codex_dispatch.py:802-812` (`_implementation_executor_tail`)
359
+ - Test: `tests/test_operating_standard.py`
360
+
361
+ **Interfaces:**
362
+ - Consumes: Task 4 의 `_implementation-self-check.md`.
363
+ - Produces: codex/antigravity executor 의 persisted 프롬프트 tail 에 preflight body + self-check body 가 순서대로 포함.
364
+
365
+ - [ ] **Step 1: 실패하는 테스트 추가**
366
+
367
+ `_implementation_executor_tail` 은 `workspace_root` 의 `prompts/profiles/` 를 읽으므로, repo 자체를 workspace_root 로 준다. 최소 manifest 는 `taskType` 만 필요(`_require_string(manifest, "taskType")`).
368
+ ```python
369
+ from scripts.okstra_ctl import codex_dispatch
370
+
371
+
372
+ def test_executor_tail_includes_both_gates():
373
+ tail = codex_dispatch._implementation_executor_tail(
374
+ {"taskType": "implementation"}, REPO, "executor"
375
+ )
376
+ assert "Coding-conventions preflight" in tail # 기존 preflight 유지
377
+ assert "Implementation self-check" in tail # 신규 self-check 추가
378
+ ```
379
+ > import 경로는 기존 codex_dispatch 테스트(`tests/test_*dispatch*.py`)의 import 방식을 따른다. 없으면 `importlib`로 `scripts/okstra_ctl/codex_dispatch.py`를 로드한다.
380
+
381
+ - [ ] **Step 2: 테스트 실패 확인**
382
+
383
+ Run: `python3 -m pytest tests/test_operating_standard.py::test_executor_tail_includes_both_gates -v`
384
+ Expected: FAIL — `assert "Implementation self-check" in tail` (아직 self-check 미append)
385
+
386
+ - [ ] **Step 3: `_implementation_executor_tail` 수정**
387
+
388
+ `scripts/okstra_ctl/codex_dispatch.py` 의 함수를 아래로 교체(preflight 뒤에 self-check body 를 이어붙임, 50줄 이내 유지):
389
+ ```python
390
+ def _implementation_executor_tail(
391
+ manifest: Mapping[str, Any],
392
+ workspace_root: Path,
393
+ role: str,
394
+ ) -> str:
395
+ if role != "executor" or _require_string(manifest, "taskType") != "implementation":
396
+ return ""
397
+ profiles = workspace_root / "prompts" / "profiles"
398
+ parts: list[str] = []
399
+ preflight_path = profiles / "_coding-conventions-preflight.md"
400
+ if preflight_path.is_file():
401
+ parts.append(preflight_path.read_text(encoding="utf-8"))
402
+ else:
403
+ parts.append("# Coding-conventions preflight\n\nApply project-local conventions before editing.")
404
+ selfcheck_path = profiles / "_implementation-self-check.md"
405
+ if selfcheck_path.is_file():
406
+ parts.append(selfcheck_path.read_text(encoding="utf-8"))
407
+ return "\n\n".join(parts)
408
+ ```
409
+
410
+ - [ ] **Step 4: 테스트 통과 확인**
411
+
412
+ Run: `python3 -m pytest tests/test_operating_standard.py::test_executor_tail_includes_both_gates -v`
413
+ Expected: PASS
414
+
415
+ - [ ] **Step 5: 커밋**
416
+
417
+ ```bash
418
+ git add scripts/okstra_ctl/codex_dispatch.py tests/test_operating_standard.py
419
+ git commit -m "feat(agents): CLI executor 프롬프트에 self-check body append"
420
+ ```
421
+
422
+ ---
423
+
424
+ ### Task 7: 빌드·전체 검증·CHANGES 기록
425
+
426
+ **Files:**
427
+ - Modify: `CHANGES.md` (최상단 날짜 섹션)
428
+ - (build output) `runtime/`
429
+
430
+ - [ ] **Step 1: 빌드(소스→runtime 동기화)**
431
+
432
+ Run: `npm run build`
433
+ Expected: `BUILD OK` 류 출력, 에러 없음. 새 파일(`operating-standard.md`, `_implementation-self-check.md`)이 `runtime/` 로 복사됨.
434
+
435
+ - [ ] **Step 2: runtime 동기화 확인**
436
+
437
+ ```bash
438
+ grep -rl "Operating Standard (canonical)" runtime/ | head
439
+ grep -rl "Implementation self-check" runtime/ | head
440
+ ```
441
+ Expected: 각각 runtime 하위 경로가 출력됨.
442
+
443
+ - [ ] **Step 3: 전체 테스트 + 워크플로 검증**
444
+
445
+ Run:
446
+ ```bash
447
+ python3 -m pytest tests/ -q
448
+ bash validators/validate-workflow.sh
449
+ ```
450
+ Expected: 전체 PASS, validator 통과.
451
+
452
+ - [ ] **Step 4: CHANGES.md 기록**
453
+
454
+ `CHANGES.md` 최상단에 `## 2026-06-19` 섹션이 이미 있으면 그 아래, 없으면 새로 추가하여 항목 작성:
455
+ ```markdown
456
+ ### feat(agents): 전 에이전트 공통 '작업 자세' + 코드 워커 완료 전 자기검증 게이트
457
+
458
+ - **배경**: 모든 okstra 에이전트의 출력 품질·태도·일관성·코드 작성 품질을 끌어올리고 싶었다. 코드 품질 규약(함수 50줄·정직한 이름·패턴 적용·증거 기반 완료)은 이미 존재했으므로, 문제는 규약 부재가 아니라 이행이었다.
459
+ - **해결**: 형용사를 검증 가능한 행동으로 번역한 공통 '작업 자세' 코어를 `templates/operating-standard.md` SSOT 로 두고, 워커 preamble·리드 contract 에 verbatim 인라인했다(테스트로 drift 차단). 워커는 독립성 한 줄, 리드는 근거 기반 종합 한 줄을 덧붙여 cross-verification 의 관점 다양성을 보존한다. 코드 워커에는 시작 전 preflight 의 짝인 완료 전 자기검증 게이트(`_implementation-self-check.md`)를 추가해, executor 가 done 선언 전 함수 길이·패턴·네이밍·실제 검증을 audit 사이드카에 한 줄씩 확정하게 했다(codex/antigravity 는 lead 가 프롬프트에 append).
460
+ - 사용자 영향: okstra-run 의 분석·구현·검증 전 단계에서 에이전트가 더 일관되게 증거 기반으로 답하고, 코드 작성 결과가 '검증 없이 완료 선언'·'함수 비대'·'패턴 무시' 없이 나오도록 자기검증을 강제한다. (설치 환경은 `okstra install` 로 런타임 갱신 후 적용.)
461
+ ```
462
+
463
+ - [ ] **Step 5: 커밋**
464
+
465
+ ```bash
466
+ git add CHANGES.md runtime/
467
+ git commit -m "chore(agents): operating-standard 빌드 동기화 + CHANGES 기록"
468
+ ```
469
+
470
+ ---
471
+
472
+ ## Self-Review (작성자 체크)
473
+
474
+ - **Spec coverage**: §4.1 공통 헤더→Task 1·2·3 / §4.2 역할 분기→Task 2(워커)·3(리드)·1(report-writer 명시) / §4.3 완료 전 게이트→Task 4·5·6 / §4.4 단일 참조점·주입 지점→Task 1(SSOT)·2·3(인라인)·6(append) / §5 검증→각 Task 테스트 + Task 7. 누락 없음.
475
+ - **Placeholder scan**: 모든 코드/문구 블록은 실제 내용. TBD/TODO 없음.
476
+ - **Type consistency**: 테스트 helper `_core_block`·상수(`SSOT`/`PREAMBLE`/`CONTRACT`/`SELFCHECK`/`EXECUTOR`)는 Task 1 에서 정의 후 재사용, 함수 시그니처 `_implementation_executor_tail(manifest, workspace_root, role)` 는 현재 코드와 일치.
@@ -0,0 +1,120 @@
1
+ # 에이전트 작업 자세(Operating Standard) 주입 설계
2
+
3
+ - 날짜: 2026-06-19
4
+ - 상태: 설계 승인 대기
5
+ - 범위: okstra 전 에이전트(분석 워커 / executor·verifier / report-writer / 리드)에 공통 "작업 자세" 행동 규약을 주입하고, 코드 작성 워커에 완료 전 자기검증 게이트를 추가한다.
6
+
7
+ ## 1. 배경과 목적
8
+
9
+ 사용자가 모든 okstra 에이전트에 "노련하고 효율적이고 성실하고 전문가스러운" 태도를 주입하길 원했다. 출력에서 아쉬웠던 점은 깊이·정확도, 태도·성의, 일관성, 장황함, 그리고 특히 **코드 작성 품질**(함수 길이/복잡, 프로젝트 패턴 무시, 주석·네이밍 부실, 검증 없이 완료 선언) 전반이었다.
10
+
11
+ ## 2. 핵심 통찰 — "규약 부재"가 아니라 "이행 부재"
12
+
13
+ 사용자가 지목한 코드 품질 4개는 **이미 규약에 전부 존재**한다:
14
+
15
+ - 함수 50줄 상한 → 글로벌 `CLAUDE.md` 4.5, [_coding-conventions-preflight.md](../../../prompts/profiles/_coding-conventions-preflight.md) "single-purpose functions ≤50 effective lines"
16
+ - 프로젝트 패턴 따르기 → `CLAUDE.md` 3.1, preflight 의 routed pack + project review packs
17
+ - 이름=무엇 / 주석=왜 → `CLAUDE.md` 4.1, preflight 의 "truthful names"
18
+ - 증거 기반 완료 → `CLAUDE.md` 2.6·7장, preflight 의 "lint/test green 은 필요조건일 뿐"
19
+
20
+ 따라서 규약 텍스트를 늘리는 것은 역효과다(안 읽히는 글만 길어진다). 설계는 **"얼마나 자주 마주치고, 출력 전에 스스로 검증하게 만드느냐"**(이행률)로 향한다. 막연한 정체성 페르소나("당신은 20년 경력 전문가다")는 현대 모델에서 추론 능력을 끌어올리지 못하고 장황함·과신·아첨을 유발하므로 채택하지 않는다 — 형용사를 **검증 가능한 동사**로 번역한다.
21
+
22
+ ## 3. 범위
23
+
24
+ ### 포함
25
+ - 전 에이전트 공통 "작업 자세" 헤더(형용사 → 행동 규약).
26
+ - 역할별 한 줄 분기(워커 독립성 / 리드 종합 / report-writer 제외).
27
+ - 코드 작성 워커(executor·verifier)의 **완료 전 자기검증 게이트** — 기존 시작 전 preflight 의 짝.
28
+
29
+ ### 비포함 (NOT)
30
+ - 정체성/경력 서사형 페르소나 문구.
31
+ - validator 기반 기계 강제(C안) — 정적으로 잡히는 항목이 제한적이라 본 작업에서 제외. 자기보고(self-check) 방식으로 간다.
32
+ - 워커별로 서로 다른 페르소나(관점 다양성은 독립성 조항으로 보존하되, 자세 규약 자체는 통일).
33
+
34
+ ## 4. 설계
35
+
36
+ ### 4.1 공통 작업 자세 헤더 (SSOT, 영어)
37
+
38
+ 기존 preamble·contract 가 영어이고 워커 출력 언어와 무관하므로 영어로 둔다. 톤은 기존 preamble 의 간결 명령형을 따른다.
39
+
40
+ ```
41
+ ## Operating standard
42
+
43
+ Work like a senior engineer who owns this result, not a commentator on it.
44
+ - Evidence over assertion — back every claim with a file:line, or mark it an
45
+ explicit assumption. Never state the unverified as fact.
46
+ - Read before you reason — read each required input end to end; when you lack
47
+ basis, write "insufficient evidence" instead of filling the gap plausibly.
48
+ - Shortest sound path — chase the most likely cause first; don't re-verify what
49
+ is settled or pad with restatement.
50
+ - Decide, don't survey — when options exist, give the trade-off and one
51
+ recommendation, not an exhaustive list.
52
+ - Fit what's here — match the surrounding code and prose; size the response to
53
+ the request.
54
+ ```
55
+
56
+ ### 4.2 역할별 한 줄 분기 (동질화 방지)
57
+
58
+ cross-verification 은 세 워커의 관점이 **독립적**이어야 triangulation 이 성립한다. 자세 규약은 통일하되 관점은 보존한다.
59
+
60
+ - **분석/검증 워커 전용** 추가 한 줄:
61
+ `Hold your own line — reason from your independent angle; do not drift toward the other workers' likely answers or echo the user. Triangulation fails if your view isn't genuinely yours.`
62
+ - **리드 전용** 추가 한 줄:
63
+ `Own the synthesis — weigh worker outputs on evidence, not consensus; a better-grounded dissent outranks the majority.`
64
+ - **report-writer**: 독립성 조항 없음(독립 분석을 하지 않는 작성자). 공통 코어만 적용.
65
+
66
+ ### 4.3 코드 워커 완료 전 자기검증 게이트
67
+
68
+ [_coding-conventions-preflight.md](../../../prompts/profiles/_coding-conventions-preflight.md)(시작 전 BLOCKING)에 대응하는 **완료 전 BLOCKING** 게이트. executor·verifier 가 "done" 을 선언하기 직전, audit 사이드카에 항목별 한 줄 확정을 쓴다. 항목은 사용자가 지목한 4개에 1:1 대응한다.
69
+
70
+ ```
71
+ ## Implementation self-check (BLOCKING — before you claim done)
72
+
73
+ Before declaring the change complete, write one confirming line per item to your
74
+ audit sidecar. If any fails, fix it or surface the violation — do not claim done.
75
+ - functions: every new/edited function ≤50 effective lines, single purpose
76
+ - conventions: applied the routed pack + project patterns (name them)
77
+ - names & comments: names say what, comments say why; no obvious-restatement
78
+ comments; no truthful-name violations
79
+ - verification: ran the actual build/test (paste command + result); no
80
+ dead/commented-out code left
81
+ ```
82
+
83
+ 이 게이트는 시작 전 preflight 와 **별개 관심사**(완료 전 검증)이므로 별도 sidecar `prompts/profiles/_implementation-self-check.md` 로 신설한다. 전달 경로는 preflight 와 동일하다: Claude executor 는 직접 read, codex/antigravity executor 는 sidecar 를 read 할 수 없으므로 lead 가 dispatch 시 프롬프트에 본문을 append 한다. [_implementation-executor.md](../../../prompts/profiles/_implementation-executor.md)·[_implementation-verifier.md](../../../prompts/profiles/_implementation-verifier.md) 가 이 sidecar 를 완료 전 단계에서 참조하도록 배선한다.
84
+
85
+ ### 4.4 주입 지점과 단일 참조점
86
+
87
+ build.mjs 는 단순 복사이고(파일 include 합성 없음), 워커는 `**Worker Preamble Path:**` 앵커로 [worker-prompt-preamble.md](../../../templates/worker-prompt-preamble.md)를 통째로 read 한다. 리드는 [okstra-lead-contract.md](../../../prompts/lead/okstra-lead-contract.md)·[team-contract.md](../../../prompts/lead/team-contract.md)를 읽는다.
88
+
89
+ 공통 코어(4.1)는 두 호출자(워커 preamble·리드 contract)를 가지므로 추출 대상이다. 실현 방식:
90
+
91
+ - **채택**: `templates/operating-standard.md` 를 SSOT 로 신설(4.1 코어 + 역할 분기 규칙 명시). 워커 preamble 과 리드 contract 는 코어 문장을 **인라인**한다(앵커 추가-read 없음 — okstra 는 audience-scoped enumeration 을 성능 최적화로 명시할 만큼 워커 토큰에 민감하므로, 5줄짜리 코어를 위해 매 워커 +1 파일 read 를 추가하지 않는다). 인라인 중복은 **단위 테스트로 drift 를 차단**한다(세 위치의 코어 문장이 SSOT 와 일치하는지 grep 검증).
92
+ - 코드 워커 게이트(4.3)는 별도 sidecar `prompts/profiles/_implementation-self-check.md` 로 신설한다(preflight 와 별개 관심사).
93
+
94
+ 주입 대상 파일:
95
+
96
+ | 파일 | 추가 내용 |
97
+ |---|---|
98
+ | `templates/operating-standard.md` (신설) | 공통 코어 + 역할 분기 규칙 (SSOT) |
99
+ | [templates/worker-prompt-preamble.md](../../../templates/worker-prompt-preamble.md) | 공통 코어 인라인 + 워커 독립성 한 줄 |
100
+ | [prompts/lead/okstra-lead-contract.md](../../../prompts/lead/okstra-lead-contract.md) | 공통 코어 인라인 + 리드 종합 한 줄 |
101
+ | `prompts/profiles/_implementation-self-check.md` (신설) | 완료 전 자기검증 게이트 |
102
+ | [_implementation-executor.md](../../../prompts/profiles/_implementation-executor.md) · [_implementation-verifier.md](../../../prompts/profiles/_implementation-verifier.md) | 완료 전 self-check sidecar 참조 배선 |
103
+
104
+ ## 5. 검증 / 테스트
105
+
106
+ - **단위 테스트(신규)**: `operating-standard.md` 의 코어 문장이 worker-prompt-preamble.md 와 okstra-lead-contract.md 에 모두 존재하는지(동기화), 워커 독립성 한 줄이 preamble 에만·리드 종합 한 줄이 contract 에만 있는지 검증.
107
+ - **단위 테스트(신규)**: 코드 워커 자기검증 게이트의 4개 항목 헤딩이 preflight 전달 경로에 포함되는지 검증.
108
+ - `npm run build` 후 `runtime/` 동기화 확인.
109
+ - `bash validators/validate-workflow.sh` 통과.
110
+ - `python3 -m pytest tests/` 전체 통과.
111
+
112
+ ## 6. 사용자 영향 (CHANGES.md 예정)
113
+
114
+ okstra 의 모든 에이전트가 출력 전 공통 "작업 자세"(증거 우선·끝까지 읽기·최단 경로·결정 후 추천·주변에 맞추기)를 마주치고, 코드 작성 워커는 "done" 선언 전 함수 길이·패턴·네이밍·실제 검증을 audit 사이드카에 자기보고한다. 워커 독립성 조항으로 cross-verification 의 관점 다양성은 보존된다.
115
+
116
+ ## 7. 리스크 / 미해결
117
+
118
+ - 자기검증은 self-check 이므로 100% 보장이 아니다(설계상 수용 — 기계 강제는 비포함). 다만 audit 사이드카 자기보고는 "조용히 건너뛰기"를 어렵게 만든다.
119
+ - 공통 코어 인라인 중복은 테스트로 drift 를 막지만, 테스트가 코어 문장을 문자열로 들고 있어야 한다(SSOT 변경 시 테스트도 갱신). 수용 가능한 트레이드오프.
120
+ - 헤더가 기존 preamble/contract 의 분량을 늘린다(코어 ~6줄 + 분기 1줄). 토큰 영향은 미미하나, 기존 "장황함 줄이기" 목표와 상충하지 않도록 코어를 6줄 이내로 유지한다.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "okstra",
3
- "version": "0.92.0",
3
+ "version": "0.93.0",
4
4
  "description": "Multi-agent cross-verification orchestrator runtime + Claude Code skills.",
5
5
  "license": "MIT",
6
6
  "author": "devonshin",
@@ -1,5 +1,5 @@
1
1
  {
2
- "package": "0.92.0",
3
- "builtAt": "2026-06-18T07:29:46.716Z",
2
+ "package": "0.93.0",
3
+ "builtAt": "2026-06-18T18:22:39.034Z",
4
4
  "repoRoot": "/home/runner/work/okstra/okstra"
5
5
  }
@@ -1,5 +1,15 @@
1
1
  # Okstra Lead Contract
2
2
 
3
+ ## Operating standard
4
+
5
+ Work like a senior engineer who owns this result, not a commentator on it.
6
+ - Evidence over assertion — back every claim with a file:line, or mark it an explicit assumption. Never state the unverified as fact.
7
+ - Read before you reason — read each required input end to end; when you lack basis, write "insufficient evidence" instead of filling the gap plausibly.
8
+ - Shortest sound path — chase the most likely cause first; don't re-verify what is settled or pad with restatement.
9
+ - Decide, don't survey — when options exist, give the trade-off and one recommendation, not an exhaustive list.
10
+ - Fit what's here — match the surrounding code and prose; size the response to the request.
11
+ - Own the synthesis — weigh worker outputs on evidence, not consensus; a better-grounded dissent outranks the majority.
12
+
3
13
  ## Overview
4
14
 
5
15
  Claude lead orchestrates multiple AI workers (Claude, Codex, Antigravity) against a prepared task bundle, collects their independent outputs, supervises convergence, and ensures the final report is produced. When `Report writer worker` is in the selected roster, the final report file is **authored by the Report writer worker** — Claude lead reviews and approves but does not write the file itself. Claude lead never substitutes its own reasoning for a worker result, and never bypasses a rostered Report writer worker by writing the report directly.
@@ -22,6 +22,9 @@ until Phase 5 ends, then drop from active context for Phase 6/7.
22
22
  - **Coding-conventions preflight (BLOCKING — runs before the first `Edit` / `Write`, and binds the TDD loop below).** The gate body is a single source at `prompts/profiles/_coding-conventions-preflight.md` (sibling of this sidecar): resolved `Coding preflight pack:` runtime-resource path usage, project review rule packs, the always-binding language-agnostic principles, and graceful degradation. Do NOT re-type that content from memory — deliver it by file so it cannot drift or be dropped:
23
23
  - **Claude executor:** Read `_coding-conventions-preflight.md` end-to-end before the first `Edit` / `Write`, then state in ONE line which conventions apply (e.g. `Applying TS + hexagonal overlay; domain at src/domains/*/domain/`).
24
24
  - **CLI executor (BLOCKING when the executor provider is `codex` or `antigravity`):** the executor CLI process does NOT share the lead's context, and it cannot Read this sidecar's directory — that path sits outside the CLI sandbox and the CLI only sees its stdin prompt, so a file reference never reaches it. The lead MUST physically append the **body** of `_coding-conventions-preflight.md` into the persisted executor prompt at dispatch time: Read the file from the same absolute directory you read this sidecar from, then `Write`/`cat` its body into the persisted prompt. Never hand-retype it. Enforcement: the CLI wrapper agents refuse an implementation-Executor dispatch whose persisted prompt lacks the literal heading `Coding-conventions preflight`, returning `<SENTINEL_PREFIX>_PREFLIGHT_MISSING` (see `agents/workers/_cli-wrapper-template.md` → Prompt Composition).
25
+ - **Completion self-check (BLOCKING — runs BEFORE you claim the stage done).** The gate body is a single source at `prompts/profiles/_implementation-self-check.md` (sibling of this sidecar): the four-item completion gate (functions ≤50 lines, conventions applied, truthful names & why-comments, real build/test run). Do NOT re-type it from memory — deliver it by file so it cannot drift.
26
+ - **Claude executor:** Read `_implementation-self-check.md` end-to-end before appending the `status:"done"` row, then write one confirming line per item to your audit sidecar.
27
+ - **CLI executor (codex / antigravity):** the CLI process cannot Read this path (outside its sandbox). The lead appends this file's body into the persisted executor prompt at dispatch time, immediately after the preflight body (see `codex_dispatch.py` `_implementation_executor_tail`). The head-less executor honours both gates from the single prompt.
25
28
  - **Stage discipline transcription (when a preceding stage is `done`):** the lead MUST transcribe the `Stage discipline` rule (from this run's rendered profile — the INCLUDEd `_stage-discipline.md` body) verbatim into the dispatched CLI executor prompt, so a codex/antigravity executor honors the prior-stage behavior-freeze. Declaration-level — no wrapper sentinel.
26
29
  - **Non-interactive auto-execution (BLOCKING when the executor provider is `codex` or `antigravity`).** A CLI executor runs head-less (`codex exec` / antigravity equivalent) — there is no human at the keyboard. Skills loaded during the run (tdd, coding-preflight, and others) contain "get user approval", "state your plan to the user and wait", or "ask before proceeding" gates written for interactive sessions; in this run those gates are **already satisfied** by the upstream `implementation-planning` approval (the plan this stage executes was human-approved). The executor MUST NOT stop to request approval, MUST NOT end its turn after only producing a plan, and MUST carry the stage through end-to-end — RED → GREEN → refactor → per-step commits → `### Stage Carry Evidence`. The ONLY skill step to skip is the interactive user-approval prompt itself; every other skill rule (TDD discipline, conventions, real-IO isolation) still binds. The lead MUST transcribe this bullet verbatim into the dispatched CLI executor prompt (same reason as the preflight transcription rule above — the CLI process does not share lead context). Stopping early for approval in a head-less run is the observed empty-exit failure (exit 0, no diff): treat it as `contract-violated`.
27
30
  - **Mandatory TDD loop**: BEFORE the first `Edit` or `Write` call, the executor MUST apply a red-green-refactor loop for every code change in this run. This is required; skipping it is a `contract-violated` outcome. This governs HOW each step is executed (failing test first → minimal implementation → refactor); it does not override the approved plan's WHAT/file scope.
@@ -0,0 +1,19 @@
1
+ <!--
2
+ Completion-time counterpart to _coding-conventions-preflight.md. The preflight
3
+ gate runs BEFORE the first Edit/Write; this gate runs BEFORE the executor claims
4
+ done. Same delivery paths: Claude executor reads this file directly; codex /
5
+ antigravity executor cannot read it (outside the CLI sandbox), so the lead
6
+ appends this file's body into the persisted executor prompt at dispatch time
7
+ (see codex_dispatch.py `_implementation_executor_tail`).
8
+ -->
9
+
10
+ # Implementation self-check (BLOCKING — before you claim done)
11
+
12
+ Before declaring the change complete, write one confirming line per item to your
13
+ audit sidecar. If any item fails, fix it or surface the violation — do not claim
14
+ done on a failing item.
15
+
16
+ - functions: every new/edited function ≤50 effective lines, single purpose
17
+ - conventions: applied the routed pack + project patterns (name which ones)
18
+ - names & comments: names say what, comments say why; no obvious-restatement comments; no truthful-name violations
19
+ - verification: ran the actual build/test (paste the command + its result); no dead or commented-out code left behind
@@ -126,3 +126,7 @@ If every verifier present in the resolved roster (`Claude verifier`, `Codex veri
126
126
  - git state mutations — `git add`, `git commit`, `git stash`, `git checkout -- <file>`, `git restore`, `git reset`, `git rebase`, `git merge`, branch creation/deletion, tag creation. Only read-only git queries (`git status`, `git diff`, `git log`, `git show`, `git rev-parse`, `git blame`) are permitted for verifiers
127
127
  - running integration / end-to-end tests that produce non-local side effects (DB writes against a non-local datastore, external API writes, docker compose against a non-isolated environment) unless that exact command is listed in the approved plan's validation set
128
128
  - redirecting tool caches or output to paths outside the worktree — e.g. setting `CARGO_TARGET_DIR`, `PYTEST_CACHE_DIR`, `NODE_OPTIONS=--require=<external>`, or any env var that causes the verifier's command to write outside the worktree's normal build artifact paths
129
+
130
+ ## Completion self-check (declaration)
131
+
132
+ - **Completion self-check (declaration).** If this verifier edits project code (not just inspects it), the same completion gate at `prompts/profiles/_implementation-self-check.md` binds before claiming done — same four items, recorded in the audit sidecar.
@@ -217,9 +217,9 @@
217
217
  "echo_template": "approved-plan: {value}"
218
218
  },
219
219
  "approve_plan_confirm": {
220
- "label": " 플랜으로 구현을 진행할까요?\n {path}\n· 예 — 이 plan 대로 구현을 실행합니다. 플랜이 아직 승인 전이면 지금 data.json(정본) + 리포트를 함께 approved 로 처리한 뒤 진행합니다. (markdown 만 손으로 고치면 일관성 검증에서 거부되므로 이 경로로 승인하세요.)\n· 아니오 — 진행하지 않습니다.",
220
+ "label": "이대로 구현을 진행할까요?\n {path}\n· 예 — 이 plan 대로 구현을 실행합니다. 플랜이 아직 승인 전이면 지금 data.json(정본) + 리포트를 함께 approved 로 처리한 뒤 진행합니다. (markdown 만 손으로 고치면 일관성 검증에서 거부되므로 이 경로로 승인하세요.)\n· 아니오 — 진행하지 않습니다.",
221
221
  "label_final_verification": "이 plan 을 검증 기준으로 삼아 진행할까요?\n {path}\n· 예 — 선택한 stage 의 구현 결과를 위 plan 기준으로 검증합니다. (plan 이 아직 승인 전이면 data.json(정본) + 리포트를 함께 approved 로 처리한 뒤 검증합니다.)\n· 아니오 — 진행하지 않습니다.",
222
- "html_approval_note": "\n보고서에서 내보낸 승인 기록을 발견했습니다: {sidecar}\n 선택한 구현 옵션: {option}\n· 예(기록 적용) — 승인과 함께 위 옵션을 frontmatter 에 기록합니다.",
222
+ "html_approval_note": "\n지난 {plan_label} 의 보고서에서 사용자의 승인 기록을 발견했습니다.\n 선택된 방식: {option}",
223
223
  "html_approval_note_default_option": "(추천 옵션 그대로)",
224
224
  "echo_template": "approve-plan: {value}",
225
225
  "options": {
@@ -231,8 +231,8 @@
231
231
  "no": "아니오 — 진행하지 않음"
232
232
  },
233
233
  "options_html_approval": {
234
- "yes_apply": "예 — 내보낸 기록대로 승인 + 옵션 적용 (추천)",
235
- "yes": "예 — 승인만 (내보낸 기록 무시)",
234
+ "yes_apply": "예 — 지난 {plan_label} 보고서, 사용자의 승인 기록으로 진행 (추천)",
235
+ "yes": "예 — 내보낸 기록 무시 후 진행 — 진행 방식은 구현 중 다시 정함",
236
236
  "no": "아니오 — 진행하지 않음"
237
237
  },
238
238
  "echo_variants": {
@@ -358,7 +358,7 @@
358
358
  }
359
359
  },
360
360
  "defaults_or_custom": {
361
- "label": "역할별 모델·실행 옵션 단계입니다 (참여 워커 구성을 바꾸는 게 아닙니다).\n이번 run 워커: {workers}\n· 기본값으로 진행 — 모든 역할을 추천 모델로 둡니다. 추천 기본값: lead·report-writer=opus, claude=opus, codex=gpt-5.5, antigravity=auto (실제 값은 runtime 기본값으로 해소). directive·관련 task 없이 바로 진행.\n· 커스터마이즈 — 역할별 모델을 직접 고르고, 추가 directive·관련 task 도 지정합니다.",
361
+ "label": "역할별 모델 선택 단계입니다 (참여 워커 구성을 바꾸는 게 아닙니다).\n이번 run 에서 모델을 고를 역할:\n{role_models}\n· 기본값으로 진행 — 추천 모델을 그대로 쓰고, directive·관련 task 없이 바로 넘어갑니다.\n· 커스터마이즈 — 역할별 모델을 직접 고르고, 추가 directive·관련 task 도 지정합니다.\n(추천 값은 runtime 기본값으로 해소됩니다. 분석에 참여하지 않는 역할은 위 목록에 나오지 않습니다 — 예: antigravity 는 implementation 의 executor 로 선택했을 때만 표시됩니다.)",
362
362
  "echo_template": "customize: {value}",
363
363
  "options": {
364
364
  "defaults": "기본값으로 진행 (역할별 추천 모델 그대로)",
@@ -806,10 +806,17 @@ def _implementation_executor_tail(
806
806
  ) -> str:
807
807
  if role != "executor" or _require_string(manifest, "taskType") != "implementation":
808
808
  return ""
809
- preflight_path = workspace_root / "prompts" / "profiles" / "_coding-conventions-preflight.md"
810
- if not preflight_path.is_file():
811
- return "# Coding-conventions preflight\n\nApply project-local conventions before editing."
812
- return preflight_path.read_text(encoding="utf-8")
809
+ profiles = workspace_root / "prompts" / "profiles"
810
+ parts: list[str] = []
811
+ preflight_path = profiles / "_coding-conventions-preflight.md"
812
+ if preflight_path.is_file():
813
+ parts.append(preflight_path.read_text(encoding="utf-8"))
814
+ else:
815
+ parts.append("# Coding-conventions preflight\n\nApply project-local conventions before editing.")
816
+ selfcheck_path = profiles / "_implementation-self-check.md"
817
+ if selfcheck_path.is_file():
818
+ parts.append(selfcheck_path.read_text(encoding="utf-8"))
819
+ return "\n\n".join(parts)
813
820
 
814
821
 
815
822
  def _project_relative_path(project_root: Path, path: Path) -> str:
@@ -1315,33 +1315,42 @@ class _ModelBindings:
1315
1315
  executor_model_meta: ModelAssignment
1316
1316
 
1317
1317
 
1318
+ def recommended_role_models() -> dict[str, str]:
1319
+ """역할 → 추천 모델 display 값 (env override 반영). prepare 의 모델 해소와
1320
+ wizard 의 안내 표기가 공유하는 단일 기준점."""
1321
+ lead_default = _default("OKSTRA_DEFAULT_LEAD_MODEL", "opus")
1322
+ return {
1323
+ "lead": lead_default,
1324
+ "claude": _default("OKSTRA_DEFAULT_CLAUDE_MODEL", "opus"),
1325
+ "codex": _default("OKSTRA_DEFAULT_CODEX_MODEL", "gpt-5.5"),
1326
+ "antigravity": _default("OKSTRA_DEFAULT_ANTIGRAVITY_MODEL", "auto"),
1327
+ "report-writer": _default("OKSTRA_DEFAULT_REPORT_WRITER_MODEL", lead_default),
1328
+ }
1329
+
1330
+
1318
1331
  def _resolve_worker_models(inp: PrepareInputs) -> dict:
1319
1332
  """lead/claude/codex/antigravity/report-writer 모델을 alias → (display, execution) 로 해소."""
1320
- lead_default = _default("OKSTRA_DEFAULT_LEAD_MODEL", "opus")
1321
- claude_default = _default("OKSTRA_DEFAULT_CLAUDE_MODEL", "opus")
1322
- codex_default = _default("OKSTRA_DEFAULT_CODEX_MODEL", "gpt-5.5")
1323
- antigravity_default = _default("OKSTRA_DEFAULT_ANTIGRAVITY_MODEL", "auto")
1324
- report_writer_default = _default("OKSTRA_DEFAULT_REPORT_WRITER_MODEL", lead_default)
1333
+ rec = recommended_role_models()
1325
1334
  return {
1326
1335
  "lead": resolve_model_metadata(
1327
1336
  provider="claude", raw_value=inp.lead_model,
1328
- default_display=lead_default, default_execution=lead_default,
1337
+ default_display=rec["lead"], default_execution=rec["lead"],
1329
1338
  ),
1330
1339
  "cw": resolve_model_metadata(
1331
1340
  provider="claude", raw_value=inp.claude_model,
1332
- default_display=claude_default, default_execution=claude_default,
1341
+ default_display=rec["claude"], default_execution=rec["claude"],
1333
1342
  ),
1334
1343
  "co": resolve_model_metadata(
1335
1344
  provider="codex", raw_value=inp.codex_model,
1336
- default_display=codex_default, default_execution=codex_default,
1345
+ default_display=rec["codex"], default_execution=rec["codex"],
1337
1346
  ),
1338
1347
  "ge": resolve_model_metadata(
1339
1348
  provider="antigravity", raw_value=inp.antigravity_model,
1340
- default_display=antigravity_default, default_execution=antigravity_default,
1349
+ default_display=rec["antigravity"], default_execution=rec["antigravity"],
1341
1350
  ),
1342
1351
  "rw": resolve_model_metadata(
1343
1352
  provider="claude", raw_value=inp.report_writer_model,
1344
- default_display=report_writer_default, default_execution=report_writer_default,
1353
+ default_display=rec["report-writer"], default_execution=rec["report-writer"],
1345
1354
  ),
1346
1355
  }
1347
1356
 
@@ -44,6 +44,7 @@ from okstra_ctl.run import (
44
44
  _reject_blocking_plan_body_gate,
45
45
  _set_data_json_approved_true_if_present,
46
46
  _validate_data_json_approval_consistency,
47
+ recommended_role_models,
47
48
  )
48
49
  from okstra_ctl.user_response import (
49
50
  UserResponseApprovalRecord,
@@ -93,6 +94,15 @@ TASK_TYPE_VALUES = [tt for tt, _ in TASK_TYPES]
93
94
 
94
95
  EXECUTORS = ["claude", "codex", "antigravity"]
95
96
 
97
+ # 역할 한 줄 설명 — defaults_or_custom 안내에서 각 역할이 무슨 일을 하는지 보여준다.
98
+ ROLE_BLURBS = {
99
+ "lead": "Claude lead · 워커 dispatch·교차검증 수렴·최종 합성",
100
+ "claude": "독립 분석 워커 (Claude)",
101
+ "codex": "독립 분석 워커 (Codex)",
102
+ "antigravity": "독립 분석 워커 (Antigravity)",
103
+ "report-writer": "최종 리포트 작성 (분석은 하지 않음)",
104
+ }
105
+
96
106
  # Task types that consume an approved plan and need a stage-scope pick:
97
107
  # implementation executes a stage, final-verification verifies a stage
98
108
  # (or the whole task via `auto`). Both gate the approved-plan + stage steps.
@@ -618,6 +628,14 @@ def _validate_sidecar_option(plan_path: Path, option_name: str, errors_t: dict)
618
628
  )
619
629
 
620
630
 
631
+ def _plan_short_label(candidate: str) -> str:
632
+ """plan 파일명에서 사용자용 짧은 식별자를 뽑는다.
633
+ final-report-implementation-planning-002.md → implementation-planning-002"""
634
+ if not candidate:
635
+ return ""
636
+ return Path(candidate).stem.removeprefix("final-report-")
637
+
638
+
621
639
  def _stage_plan_for_confirmation(
622
640
  state: WizardState, path_str: str, *, suffix: str = ""
623
641
  ) -> Optional[str]:
@@ -1703,12 +1721,16 @@ def _build_approve_plan_confirm(state: WizardState) -> Prompt:
1703
1721
  label = (t["label_final_verification"] or t["label"]) if is_fv else t["label"]
1704
1722
  options_map = (t["options_final_verification"] or t["options"]) if is_fv else t["options"]
1705
1723
  if state.html_approval_sidecar:
1724
+ plan_label = _plan_short_label(state.approve_plan_candidate)
1706
1725
  label += t["html_approval_note"].format(
1707
- sidecar=state.html_approval_sidecar,
1726
+ plan_label=plan_label,
1708
1727
  option=state.html_approval_option
1709
1728
  or t["html_approval_note_default_option"],
1710
1729
  )
1711
- options_map = t["options_html_approval"]
1730
+ options_map = {
1731
+ k: v.format(plan_label=plan_label)
1732
+ for k, v in t["options_html_approval"].items()
1733
+ }
1712
1734
  return Prompt(
1713
1735
  step=S_APPROVE_PLAN_CONFIRM, kind="pick",
1714
1736
  label=label,
@@ -2357,11 +2379,34 @@ def _submit_reuse_previous(state: WizardState, value: str) -> Optional[str]:
2357
2379
  f"lead-model={state.lead_model or 'default'})")
2358
2380
 
2359
2381
 
2382
+ def _role_model_lines(state: WizardState) -> str:
2383
+ """이번 run 에서 실제로 모델을 고르게 되는 역할만, 추천 모델과 함께 나열한다.
2384
+ 뒤따르는 *_model 단계의 등장 조건과 1:1 로 맞춰 안내와 실제 화면이 어긋나지
2385
+ 않게 한다 (그래서 분석에 참여하지 않는 antigravity 는 executor 일 때만 나온다)."""
2386
+ rec = recommended_role_models()
2387
+ roster = _resolved_roster(state)
2388
+ impl = state.task_type == "implementation"
2389
+
2390
+ def line(role_label: str, blurb: str, model: str) -> str:
2391
+ return f" · {role_label} ({blurb}) — 추천 {model}"
2392
+
2393
+ lines = [line("lead", ROLE_BLURBS["lead"], rec["lead"])]
2394
+ if impl:
2395
+ ex = state.executor or "claude"
2396
+ lines.append(line(f"executor={ex}", "코드 구현 실행자", rec.get(ex, "auto")))
2397
+ else:
2398
+ for w in ("claude", "codex", "antigravity"):
2399
+ if w in roster:
2400
+ lines.append(line(w, ROLE_BLURBS[w], rec[w]))
2401
+ if impl or "report-writer" in roster:
2402
+ lines.append(line("report-writer", ROLE_BLURBS["report-writer"],
2403
+ rec["report-writer"]))
2404
+ return "\n".join(lines)
2405
+
2406
+
2360
2407
  def _build_defaults_or_custom(state: WizardState) -> Prompt:
2361
- roster = (state.workers_override
2362
- or ",".join(state.profile_workers)
2363
- or "(profile default)")
2364
- t = _p(state.workspace_root, "defaults_or_custom", workers=roster)
2408
+ t = _p(state.workspace_root, "defaults_or_custom",
2409
+ role_models=_role_model_lines(state))
2365
2410
  return Prompt(
2366
2411
  step=S_DEFAULTS_OR_CUSTOM, kind="pick",
2367
2412
  label=t["label"],
@@ -3306,11 +3351,16 @@ def _remaining_screens(state: WizardState) -> int:
3306
3351
  return screens
3307
3352
 
3308
3353
 
3309
- def _screen_progress(state: WizardState) -> dict[str, int]:
3354
+ def _screen_progress(state: WizardState) -> dict[str, Any]:
3310
3355
  passed = _passed_screens(state)
3311
3356
  total = passed + _remaining_screens(state)
3312
3357
  index = passed + 1
3313
- return {"index": index, "total": total, "remaining": max(0, total - index)}
3358
+ remaining = max(0, total - index)
3359
+ suffix = "마지막 단계" if remaining == 0 else f"앞으로 {remaining} 스텝 남음"
3360
+ return {
3361
+ "index": index, "total": total, "remaining": remaining,
3362
+ "label": f"Step {index}/{total} · {suffix}",
3363
+ }
3314
3364
 
3315
3365
 
3316
3366
  def prompt_payload(state: WizardState, prompt: Prompt) -> dict[str, Any]:
@@ -32,7 +32,7 @@ Every wizard call returns JSON. The two shapes you'll see:
32
32
  "progress": { "index": 5, "total": 11, "remaining": 6 } } }
33
33
  ```
34
34
 
35
- Every non-terminal `next` carries a `progress` object (`done` / `aborted` omit it). `index` is the 1-based number of the **current screen** (pick_group counts as one screen), `total` is the wizard's estimate of the full screen count, and `remaining` = `total index`. The total is a forward estimate from the current answers, so it may grow by a few when the user opens a branch (e.g. choosing *Customize* adds the model screen). **Always suffix the rendered prompt with the progress marker** — see Step 3.
35
+ Every non-terminal `next` carries a `progress` object (`done` / `aborted` omit it). It holds `index` (1-based number of the **current screen**; pick_group counts as one screen), `total` (the wizard's forward estimate of the full screen count may grow by a few when the user opens a branch, e.g. choosing *Customize* adds the model screen), `remaining` (`total index`), and **`label`** the ready-to-render Korean marker the wizard already composed (e.g. `Step 10/11 · 앞으로 1 스텝 남음`, or `Step 11/11 · 마지막 단계` on the final screen). **Always suffix the rendered prompt with `progress.label` verbatim** — see Step 3. Do not recompute the marker or decide "마지막 단계" yourself; only the wizard knows whether more screens follow.
36
36
 
37
37
  ```json
38
38
  { "ok": false, "error": "approved plan has no APPROVED marker: ...",
@@ -96,7 +96,7 @@ Output: the same `{ok, next}` JSON described above. The first `next` is always `
96
96
 
97
97
  Repeat until `next.kind == "done"` (or `"aborted"` — terminal cancel, see "How the wizard talks to you"):
98
98
 
99
- 1. **Render** the prompt according to `kind` (and `multi` for pick). **Always append the progress marker to the rendered question label** (the `AskUserQuestion` question text, or the `text`-prompt message): suffix `label` with ` (Step <index>/<total> · 남은 <remaining>단계)`, using `next.progress`. When `remaining == 0` (the final `confirm` screen), use `(Step <index>/<total> · 마지막 단계)` instead. Example: `모델을 선택하세요 (Step 8/11 · 남은 3단계)`. Re-prompts after `ok: false` reuse `current.progress` the same way. The progress marker is presentation-only — never send it back to the wizard as part of an answer.
99
+ 1. **Render** the prompt according to `kind` (and `multi` for pick). **Always append the progress marker to the rendered question label** (the `AskUserQuestion` question text, or the `text`-prompt message): suffix it with ` (<next.progress.label>)` render `progress.label` exactly as the wizard sent it, never recompute it. Example: label `Step 8/11 · 앞으로 3 스텝 남음` `모델을 선택하세요 (Step 8/11 · 앞으로 3 스텝 남음)`. Re-prompts after `ok: false` reuse `current.progress.label` the same way. The progress marker is presentation-only — never send it back to the wizard as part of an answer.
100
100
  - `pick` + `multi: false` → `AskUserQuestion` with `multiSelect: false`, `label`, and `options`. The user's chosen option's `value` is the answer string.
101
101
  - `pick` + `multi: true` → `AskUserQuestion` with `multiSelect: true`, `label`, and `options`. Join the selected `value`s with `,` into a single literal CSV string (e.g. `"claude,codex,antigravity"`) and submit it as a single `--answer "claude,codex,antigravity"`. Empty selection submits `--answer ""` and the wizard re-prompts.
102
102
  - `pick_group` → one `AskUserQuestion` with one question per `questions[]` entry (tab). Map each tab's selected `value` back by `questions[].step`, assemble a JSON object, and submit it as a single literal `--answer '<json>'`.
@@ -0,0 +1,22 @@
1
+ # Operating Standard (canonical)
2
+
3
+ This file is the single source of truth for the operating standard injected into
4
+ every okstra agent. The "Common core" block below is inlined verbatim into
5
+ `templates/worker-prompt-preamble.md` (workers) and
6
+ `prompts/lead/okstra-lead-contract.md` (lead). A unit test asserts both inlined
7
+ copies match this core — update all three together.
8
+
9
+ ## Common core
10
+
11
+ Work like a senior engineer who owns this result, not a commentator on it.
12
+ - Evidence over assertion — back every claim with a file:line, or mark it an explicit assumption. Never state the unverified as fact.
13
+ - Read before you reason — read each required input end to end; when you lack basis, write "insufficient evidence" instead of filling the gap plausibly.
14
+ - Shortest sound path — chase the most likely cause first; don't re-verify what is settled or pad with restatement.
15
+ - Decide, don't survey — when options exist, give the trade-off and one recommendation, not an exhaustive list.
16
+ - Fit what's here — match the surrounding code and prose; size the response to the request.
17
+
18
+ ## Role addendum
19
+
20
+ - Analysis / verification workers add this line: `Hold your own line — reason from your independent angle; do not drift toward the other workers' likely answers or echo the user. Triangulation fails if your view isn't genuinely yours.`
21
+ - Lead adds this line: `Own the synthesis — weigh worker outputs on evidence, not consensus; a better-grounded dissent outranks the majority.`
22
+ - Report-writer worker: common core only (no independence addendum — it authors, it does not analyze).
@@ -4,6 +4,16 @@ This file is the single source of truth for the boilerplate that every okstra an
4
4
 
5
5
  It replaces the previous practice of inlining ~80 lines of identical boilerplate into every worker prompt body.
6
6
 
7
+ ## Operating standard (read before any output)
8
+
9
+ Work like a senior engineer who owns this result, not a commentator on it.
10
+ - Evidence over assertion — back every claim with a file:line, or mark it an explicit assumption. Never state the unverified as fact.
11
+ - Read before you reason — read each required input end to end; when you lack basis, write "insufficient evidence" instead of filling the gap plausibly.
12
+ - Shortest sound path — chase the most likely cause first; don't re-verify what is settled or pad with restatement.
13
+ - Decide, don't survey — when options exist, give the trade-off and one recommendation, not an exhaustive list.
14
+ - Fit what's here — match the surrounding code and prose; size the response to the request.
15
+ - Hold your own line — reason from your independent angle; do not drift toward the other workers' likely answers or echo the user. Triangulation fails if your view isn't genuinely yours.
16
+
7
17
  ---
8
18
 
9
19
  ## Required reading (analysis workers + report-writer worker)