okstra 0.55.0 → 0.56.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 (45) hide show
  1. package/bin/okstra +24 -7
  2. package/docs/kr/architecture.md +2 -2
  3. package/docs/project-structure-overview.md +0 -1
  4. package/docs/superpowers/plans/2026-05-25-okstra-project-root-rename.md +0 -1
  5. package/docs/superpowers/plans/2026-06-07-stage-conformance-qa-phase2.md +275 -0
  6. package/docs/superpowers/plans/2026-06-07-stage-conformance-qa-phase3.md +282 -0
  7. package/docs/superpowers/plans/2026-06-07-stage-conformance-qa-phase4a.md +147 -0
  8. package/docs/superpowers/plans/2026-06-07-stage-conformance-qa-phase4b.md +262 -0
  9. package/docs/superpowers/plans/2026-06-07-stage-conformance-qa-phase4c.md +184 -0
  10. package/docs/superpowers/plans/2026-06-07-stage-conformance-qa-phase4d.md +88 -0
  11. package/docs/superpowers/plans/2026-06-07-stage-conformance-qa-phase4e.md +250 -0
  12. package/docs/superpowers/plans/2026-06-07-stage-conformance-qa.md +409 -0
  13. package/docs/superpowers/specs/2026-06-07-stage-conformance-qa-design.md +169 -0
  14. package/package.json +1 -1
  15. package/runtime/BUILD.json +2 -2
  16. package/runtime/bin/lib/okstra/cli.sh +5 -1
  17. package/runtime/bin/lib/okstra/usage.sh +5 -0
  18. package/runtime/bin/okstra.sh +1 -0
  19. package/runtime/prompts/profiles/_common-contract.md +4 -4
  20. package/runtime/prompts/profiles/_implementation-deliverable.md +4 -4
  21. package/runtime/prompts/profiles/_implementation-executor.md +1 -4
  22. package/runtime/prompts/profiles/_implementation-verifier.md +23 -2
  23. package/runtime/prompts/profiles/final-verification.md +2 -1
  24. package/runtime/prompts/profiles/implementation-planning.md +9 -5
  25. package/runtime/prompts/profiles/implementation.md +6 -6
  26. package/runtime/prompts/profiles/improvement-discovery.md +1 -0
  27. package/runtime/prompts/profiles/release-handoff.md +4 -4
  28. package/runtime/python/okstra_ctl/conformance.py +270 -0
  29. package/runtime/python/okstra_ctl/paths.py +2 -0
  30. package/runtime/python/okstra_ctl/run.py +29 -0
  31. package/runtime/schemas/final-report-v1.0.schema.json +127 -10
  32. package/runtime/skills/okstra-coding-preflight/SKILL.md +8 -0
  33. package/runtime/skills/okstra-coding-preflight/clean-code.md +6 -0
  34. package/runtime/skills/okstra-run/SKILL.md +12 -0
  35. package/runtime/skills/okstra-run/templates/pr-body.template.md +12 -12
  36. package/runtime/skills/okstra-setup/SKILL.md +35 -0
  37. package/runtime/templates/reports/final-report.template.md +63 -19
  38. package/runtime/templates/reports/i18n/en.json +1 -1
  39. package/runtime/templates/reports/i18n/ko.json +1 -1
  40. package/runtime/templates/reports/implementation-input.template.md +1 -1
  41. package/runtime/templates/reports/implementation-planning-input.template.md +3 -3
  42. package/runtime/validators/validate-implementation-plan-stages.py +28 -3
  43. package/runtime/validators/validate-run.py +98 -0
  44. package/src/okstra-dirs.mjs +1 -1
  45. package/src/migrate.mjs +0 -146
@@ -276,6 +276,9 @@ class PrepareInputs:
276
276
  work_category: str = ""
277
277
  base_ref: str = ""
278
278
  approved_plan_path: str = ""
279
+ # implementation 전용: `--qa-waiver "<stageKey>:<reason>"` 사용자 확인형 우회.
280
+ # prepare-time 에 task-level conformance 매니페스트 entry.waiver 를 채운다.
281
+ qa_waiver: str = ""
279
282
  stage: str = "auto"
280
283
  clarification_response_path: str = "" # absolute or empty
281
284
  # release-handoff 전용: PR 본문 템플릿 1회성 override. 빈 문자열이면
@@ -1092,6 +1095,28 @@ def _validate_prepare_inputs(project_root: Path, inp: PrepareInputs) -> list:
1092
1095
  return ctx_stage_map
1093
1096
 
1094
1097
 
1098
+ def _apply_qa_waiver_if_requested(inp: "PrepareInputs", project_root: Path) -> None:
1099
+ """`--qa-waiver` 가 있으면 task-level 매니페스트 entry 의 waiver 를 채운다."""
1100
+ if not inp.qa_waiver:
1101
+ return
1102
+ from .conformance import apply_qa_waiver, parse_qa_waiver_arg
1103
+ from .paths import task_dir
1104
+ parsed = parse_qa_waiver_arg(inp.qa_waiver)
1105
+ if parsed is None:
1106
+ raise PrepareError(
1107
+ f'--qa-waiver must be "<stageKey>:<reason>", got {inp.qa_waiver!r}'
1108
+ )
1109
+ stage_key, reason = parsed
1110
+ manifest_path = task_dir(project_root, inp.task_group, inp.task_id) / "qa" / "conformance-manifest.json"
1111
+ if not manifest_path.is_file():
1112
+ raise PrepareError(f"--qa-waiver: conformance manifest not found at {manifest_path}")
1113
+ manifest = json.loads(manifest_path.read_text())
1114
+ when = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
1115
+ if not apply_qa_waiver(manifest, stage_key, reason, at=when):
1116
+ raise PrepareError(f"--qa-waiver: stageKey {stage_key!r} not in manifest {manifest_path}")
1117
+ manifest_path.write_text(json.dumps(manifest, indent=2, ensure_ascii=False) + "\n")
1118
+
1119
+
1095
1120
  def _register_and_check_project(project_root: Path, inp: PrepareInputs) -> None:
1096
1121
  """project.json self-registration + (implementation 한정) qaCommands gate 검증."""
1097
1122
  from okstra_project import ResolverError
@@ -1120,6 +1145,7 @@ def _register_and_check_project(project_root: Path, inp: PrepareInputs) -> None:
1120
1145
  qa_errors = validate_qa_commands(project_meta.get("qaCommands"))
1121
1146
  if qa_errors:
1122
1147
  raise PrepareError(_format_qa_errors(qa_errors))
1148
+ _apply_qa_waiver_if_requested(inp, project_root)
1123
1149
 
1124
1150
 
1125
1151
  def _resolve_roster(inp: PrepareInputs, profile_file: Path) -> tuple[list[str], str]:
@@ -1860,6 +1886,8 @@ def main(argv: list[str]) -> int:
1860
1886
  p.add_argument("--critic", default="")
1861
1887
  p.add_argument("--related-tasks", default="", dest="related_tasks_raw")
1862
1888
  p.add_argument("--approved-plan", default="", dest="approved_plan_path")
1889
+ p.add_argument("--qa-waiver", default="", dest="qa_waiver",
1890
+ help='Stage conformance 우회: "<stageKey>:<reason>" (사용자 확인형, 매니페스트 entry.waiver 기록)')
1863
1891
  p.add_argument(
1864
1892
  "--stage", default="auto", dest="stage",
1865
1893
  help=(
@@ -1975,6 +2003,7 @@ def main(argv: list[str]) -> int:
1975
2003
  work_category=args.work_category,
1976
2004
  base_ref=args.base_ref,
1977
2005
  approved_plan_path=args.approved_plan_path,
2006
+ qa_waiver=args.qa_waiver,
1978
2007
  stage=args.stage,
1979
2008
  clarification_response_path=clarification_abs,
1980
2009
  pr_template_path=args.pr_template_path,
@@ -330,12 +330,13 @@
330
330
 
331
331
  "implementationPlanning": {
332
332
  "type": "object",
333
- "description": "RENDER_IF taskType == implementation-planning. §4.5 deliverables.",
333
+ "description": "RENDER_IF taskType == implementation-planning. §5.5 deliverables.",
334
334
  "required": [
335
335
  "optionCandidates",
336
336
  "tradeoffMatrix",
337
337
  "recommendedOption",
338
- "stepwiseExecution",
338
+ "stageMap",
339
+ "stages",
339
340
  "dependencyMigrationRisk",
340
341
  "validationChecklist",
341
342
  "rollbackStrategy",
@@ -355,7 +356,18 @@
355
356
  "items": { "$ref": "#/$defs/TradeoffRow" }
356
357
  },
357
358
  "recommendedOption": { "$ref": "#/$defs/RecommendedOption" },
359
+ "stageMap": {
360
+ "type": "array",
361
+ "minItems": 1,
362
+ "items": { "$ref": "#/$defs/StageMapRow" }
363
+ },
364
+ "stages": {
365
+ "type": "array",
366
+ "minItems": 1,
367
+ "items": { "$ref": "#/$defs/ImplementationPlanStage" }
368
+ },
358
369
  "stepwiseExecution": {
370
+ "description": "Legacy flat summary kept for compatibility only. New reports use stageMap/stages.",
359
371
  "type": "array",
360
372
  "minItems": 1,
361
373
  "items": { "$ref": "#/$defs/StepRow" }
@@ -385,7 +397,7 @@
385
397
 
386
398
  "releaseHandoff": {
387
399
  "type": "object",
388
- "description": "RENDER_IF taskType == release-handoff. §4.6 deliverables.",
400
+ "description": "RENDER_IF taskType == release-handoff. §5.6 deliverables.",
389
401
  "required": [
390
402
  "sourceVerificationReport",
391
403
  "featureBranchState",
@@ -419,11 +431,14 @@
419
431
  },
420
432
  "userSelections": {
421
433
  "type": "object",
422
- "required": ["h1", "h3"],
434
+ "required": ["h1", "h2b", "h3"],
423
435
  "additionalProperties": false,
424
436
  "properties": {
425
437
  "h1": { "enum": ["local only", "push + PR", "skip"] },
426
438
  "h2": { "type": "string" },
439
+ "h2b": {
440
+ "enum": ["not-run", "clean", "proceed anyway", "change base branch", "cancel"]
441
+ },
427
442
  "h3": { "enum": ["use as-is", "edit then proceed", "cancel"] }
428
443
  }
429
444
  },
@@ -482,12 +497,13 @@
482
497
 
483
498
  "implementation": {
484
499
  "type": "object",
485
- "description": "RENDER_IF taskType == implementation. §4.7 deliverables.",
500
+ "description": "RENDER_IF taskType == implementation. §5.7 deliverables.",
486
501
  "required": [
487
502
  "approvedPlanReference",
488
503
  "commitList",
489
504
  "diffSummary",
490
505
  "outOfPlanEdits",
506
+ "stageSidecarEvidence",
491
507
  "validationEvidence",
492
508
  "verifierResults",
493
509
  "rollbackVerification",
@@ -526,6 +542,7 @@
526
542
  "type": "array",
527
543
  "items": { "$ref": "#/$defs/OutOfPlanEditRow" }
528
544
  },
545
+ "stageSidecarEvidence": { "$ref": "#/$defs/StageSidecarEvidence" },
529
546
  "validationEvidence": {
530
547
  "type": "array",
531
548
  "minItems": 1,
@@ -547,7 +564,7 @@
547
564
 
548
565
  "finalVerification": {
549
566
  "type": "object",
550
- "description": "RENDER_IF taskType == final-verification. §4.8 deliverables.",
567
+ "description": "RENDER_IF taskType == final-verification. §5.8 deliverables.",
551
568
  "required": [
552
569
  "sourceImplementationReport",
553
570
  "acceptanceBlockers",
@@ -624,7 +641,7 @@
624
641
 
625
642
  "allOf": [
626
643
  {
627
- "description": "implementation-planning task-type requires §4.5 block.",
644
+ "description": "implementation-planning task-type requires §5.5 block.",
628
645
  "if": {
629
646
  "properties": { "header": { "properties": { "taskType": { "const": "implementation-planning" } } } },
630
647
  "required": ["header"]
@@ -634,7 +651,7 @@
634
651
  }
635
652
  },
636
653
  {
637
- "description": "release-handoff task-type requires §4.6 block.",
654
+ "description": "release-handoff task-type requires §5.6 block.",
638
655
  "if": {
639
656
  "properties": { "header": { "properties": { "taskType": { "const": "release-handoff" } } } },
640
657
  "required": ["header"]
@@ -644,7 +661,7 @@
644
661
  }
645
662
  },
646
663
  {
647
- "description": "implementation task-type requires §4.7 block.",
664
+ "description": "implementation task-type requires §5.7 block.",
648
665
  "if": {
649
666
  "properties": { "header": { "properties": { "taskType": { "const": "implementation" } } } },
650
667
  "required": ["header"]
@@ -654,7 +671,7 @@
654
671
  }
655
672
  },
656
673
  {
657
- "description": "final-verification task-type requires §4.8 block.",
674
+ "description": "final-verification task-type requires §5.8 block.",
658
675
  "if": {
659
676
  "properties": { "header": { "properties": { "taskType": { "const": "final-verification" } } } },
660
677
  "required": ["header"]
@@ -1066,6 +1083,90 @@
1066
1083
  }
1067
1084
  },
1068
1085
 
1086
+ "StageMapRow": {
1087
+ "type": "object",
1088
+ "required": ["stage", "title", "dependsOn", "stepCount", "exitContractSummary"],
1089
+ "additionalProperties": false,
1090
+ "properties": {
1091
+ "stage": { "type": "integer", "minimum": 1 },
1092
+ "title": { "type": "string", "minLength": 1 },
1093
+ "dependsOn": {
1094
+ "type": "string",
1095
+ "minLength": 1,
1096
+ "description": "Literal Stage Map depends-on cell: `(none)` or a comma-separated stage number list."
1097
+ },
1098
+ "stepCount": { "type": "integer", "minimum": 0, "maximum": 6 },
1099
+ "exitContractSummary": { "type": "string", "minLength": 1 }
1100
+ }
1101
+ },
1102
+
1103
+ "ImplementationPlanStage": {
1104
+ "type": "object",
1105
+ "required": [
1106
+ "stage",
1107
+ "title",
1108
+ "sliceValue",
1109
+ "acceptance",
1110
+ "carryIn",
1111
+ "stepwiseExecution",
1112
+ "exitContract",
1113
+ "stageValidation"
1114
+ ],
1115
+ "additionalProperties": false,
1116
+ "oneOf": [
1117
+ {
1118
+ "required": ["conformanceTests"],
1119
+ "not": { "required": ["conformanceExemption"] }
1120
+ },
1121
+ {
1122
+ "required": ["conformanceExemption"],
1123
+ "not": { "required": ["conformanceTests"] }
1124
+ }
1125
+ ],
1126
+ "properties": {
1127
+ "stage": { "type": "integer", "minimum": 1 },
1128
+ "title": { "type": "string", "minLength": 1 },
1129
+ "sliceValue": { "type": "string", "minLength": 1 },
1130
+ "acceptance": { "type": "string", "minLength": 1 },
1131
+ "conformanceTests": {
1132
+ "type": "string",
1133
+ "minLength": 1,
1134
+ "description": "Text after the `Conformance tests: stage-N — ...` prefix."
1135
+ },
1136
+ "conformanceExemption": {
1137
+ "type": "string",
1138
+ "minLength": 1,
1139
+ "description": "Text after the `Conformance exemption:` prefix."
1140
+ },
1141
+ "tddExemption": {
1142
+ "type": "string",
1143
+ "description": "Optional text after the `TDD exemption:` prefix."
1144
+ },
1145
+ "carryIn": { "type": "string", "minLength": 1 },
1146
+ "stepwiseExecution": {
1147
+ "type": "array",
1148
+ "minItems": 1,
1149
+ "maxItems": 6,
1150
+ "items": { "$ref": "#/$defs/StageStepRow" }
1151
+ },
1152
+ "exitContract": { "type": "string", "minLength": 1 },
1153
+ "stageValidation": { "type": "string", "minLength": 1 }
1154
+ }
1155
+ },
1156
+
1157
+ "StageStepRow": {
1158
+ "type": "object",
1159
+ "required": ["step", "action", "files", "command", "expected"],
1160
+ "additionalProperties": false,
1161
+ "properties": {
1162
+ "step": { "type": "integer", "minimum": 1 },
1163
+ "action": { "type": "string", "minLength": 1 },
1164
+ "files": { "type": "string", "minLength": 1 },
1165
+ "command": { "type": "string", "minLength": 1 },
1166
+ "expected": { "type": "string", "minLength": 1 }
1167
+ }
1168
+ },
1169
+
1069
1170
  "StepRow": {
1070
1171
  "type": "object",
1071
1172
  "required": ["step", "ticketId", "action", "files", "commandOrTest", "expectedOutcome"],
@@ -1080,6 +1181,22 @@
1080
1181
  }
1081
1182
  },
1082
1183
 
1184
+ "StageSidecarEvidence": {
1185
+ "type": "object",
1186
+ "required": ["stageNumber", "stageTitle", "carryJson", "consumerRows"],
1187
+ "additionalProperties": false,
1188
+ "properties": {
1189
+ "stageNumber": { "type": "integer", "minimum": 1 },
1190
+ "stageTitle": { "type": "string", "minLength": 1 },
1191
+ "carryJson": { "type": "string", "minLength": 1 },
1192
+ "consumerRows": {
1193
+ "type": "array",
1194
+ "minItems": 1,
1195
+ "items": { "type": "string", "minLength": 1 }
1196
+ }
1197
+ }
1198
+ },
1199
+
1083
1200
  "DependencyRow": {
1084
1201
  "type": "object",
1085
1202
  "required": ["id", "kind", "item", "impact", "mitigation"],
@@ -62,6 +62,14 @@ If you suspect an overlay applies but the layout is non-standard, ask one questi
62
62
  - [ ] Existing code searched: `grep` for the symbol / file / identifier you are about to add. Do not duplicate.
63
63
  - [ ] Project conventions checked: `.editorconfig`, `CONTRIBUTING.md`, formatter config (`.prettierrc`, `rustfmt.toml`, `ktlint`, `google-java-format`, etc.). **Project rules override this skill on conflict.**
64
64
 
65
+ ## Completion sweep (before declaring a multi-file change done)
66
+
67
+ Per-file checks miss cross-cutting issues; each commit can be individually clean while the sum violates DRY. Before saying "done":
68
+
69
+ - [ ] **Domain-literal sweep:** `grep -rn` every domain enum value / predicate you added or touched in WHERE clauses, filters, or branches. The same literal at 2+ I/O sites is a *candidate* scattered decision — ask: would these sites change together when the business rule changes? Same decision → consolidate into one named constant or query builder in the domain layer and make every site reference it. Different decisions that merely share a value → leave them separate; coupling incidental duplication is worse than the repetition. (The identifier grep above does NOT catch this — sweep *values*, not just names.)
70
+ - [ ] **Stand-alone name test for exports:** for each exported identifier, look at its siblings — can a caller pick the right one from the names alone? If a comment must explain which to use, the name fails; encode the distinguishing fact in it (e.g., the input shape: `parseRows` vs `parseRowsFromFlatItems`).
71
+ - [ ] **No documented forks:** two deliberate variants of one capability must not survive as parallel implementations with a comment explaining the delta. Re-read both bodies and check the deltas are genuinely parametric: if they reduce to a few orthogonal options, collapse into one implementation taking explicit option parameters that encode them. If encoding the delta would take more than ~3 options, or add more branching than the duplication it removes, they are two capabilities — keep two implementations with distinct honest names and delete the "variant of" framing. Either way the comment-documented fork dies. "The divergence is documented" stays a refused rationalization, and a two-capabilities verdict must come from reading the bodies, not from reluctance to refactor.
72
+
65
73
  ## Boundaries
66
74
 
67
75
  - This skill does **not** auto-format. Run the project's formatter yourself.
@@ -22,6 +22,11 @@ const sum = (arr) => arr.reduce((acc, x) => acc + x, 0);
22
22
 
23
23
  Caveat: do not extract until the duplication is real (rule of three). Premature abstraction is also a code smell.
24
24
 
25
+ Two more forms of duplication that hide from symbol-level grep:
26
+
27
+ - **Scattered domain literals** — the same enum value / predicate repeated across queries or filters *may* be one business decision duplicated. The test: would the sites change together when the rule changes? If yes, consolidate to a named constant or builder at a single reference point; if they merely share a value, leave them apart — coupling incidental duplication is worse than repetition.
28
+ - **Documented forks** — two deliberate variants of one capability kept as parallel implementations "because the delta is commented". If the delta is genuinely parametric (a few orthogonal options), collapse into one implementation with explicit option parameters; if not, split into two distinctly named capabilities. The commented fork itself is never the end state.
29
+
25
30
  ## KISS — Keep It Simple
26
31
 
27
32
  The simplest solution that meets the requirement wins. Cleverness has a maintenance cost.
@@ -104,6 +109,7 @@ The test: if the name appeared in a stacktrace, an autocomplete list, or a grep
104
109
  - Generic verbs with no information: `handle`, `process`, `execute`, `doStuff`, `manage`. If the function deletes-then-inserts, name it `replace`, not `update`.
105
110
  - Constants that will collide with siblings later: `BUCKET` → `FONTS_BUCKET`, `TIMEOUT` → `HTTP_READ_TIMEOUT_MS`, `URL` → `AUTH_SERVICE_URL`.
106
111
  - Repository / port methods named after the rule they enforce, not the data they fetch: `findValid*` / `findActive*` / `findEligible*` — the adjective encodes a business rule the caller can't inspect from the name. Prefer `findDesktopLibrariesForUser(userId)` + a domain predicate applied by the caller.
112
+ - Near-twin siblings a caller can't tell apart from the names alone (`parseRows` vs `parseRowsFromItems`): if a comment must explain which to pick, rename — encode the distinguishing fact (e.g., input shape: `parseRowsFromFlatItems`). Matching the existing sibling's style does not excuse the ambiguity.
107
113
 
108
114
  This rule applies most strongly to **names crossing module boundaries**: public methods, exported constants, port methods. Private helpers in a tight local scope get more slack.
109
115
 
@@ -184,6 +184,18 @@ The python function underneath is mutex-protected (`~/.okstra/.locks/<task-key>.
184
184
 
185
185
  You can delete the literal state-file path after this point — its job is done. Invoke `rm` with the literal path (e.g. `rm /var/folders/.../okstra-wizard.AbCd.json`), not a shell variable.
186
186
 
187
+ ### Step 5.1 (implementation only): conformance waiver offer
188
+
189
+ `render-bundle` accepts an optional `--qa-waiver "<stageKey>:<reason>"` flag (implementation only). It records a **user-acknowledged** waiver into the task-level conformance manifest entry (`entry.waiver`), letting the run proceed when a stage's Tier 3 conformance script genuinely cannot run (e.g. the replica DB is unreachable). The waiver records the user's reason **verbatim**.
190
+
191
+ This is **never** a lead/worker self-exemption — only the user may waive. Offer it **only** when conformance BLOCKING is expected (the chosen stage declares a conformance entry whose script you cannot run in this environment). Surface it as a 3-option recommendation picker (per the run-prompt recommendation rule):
192
+
193
+ 1. (recommended) Run the conformance script — no waiver.
194
+ 2. Waive this stage — ask the user for the exact `<stageKey>` and reason, then pass `--qa-waiver "<stageKey>:<reason>"` to `render-bundle` (reason = the user's words, unedited).
195
+ 3. 직접 입력 — the user types the full `<stageKey>:<reason>` value.
196
+
197
+ When the user picks a waiver, append `--qa-waiver "<stageKey>:<reason>"` to the `render-bundle` invocation above. Omit the flag entirely otherwise (do **not** pass `--qa-waiver ""`). A malformed value or unknown `<stageKey>` aborts `render-bundle` with a `PrepareError`.
198
+
187
199
  ## Step 6: Take over as Claude lead
188
200
 
189
201
  Read `<INSTRUCTION_SET_PATH>/claude-execution-prompt.md` verbatim and enter `Claude lead` mode. The lead prompt now points to compact intake artifacts first (`active-run-context`, `analysis-profile.md`, and `analysis-packet.md`); full source files such as `analysis-material.md`, `reference-expectations.md`, and `final-report-template.md` are lazy/fallback inputs. Follow the rendered prompt order, do not preempt it.
@@ -17,25 +17,25 @@ okstra release-handoff 기본 PR 본문 템플릿.
17
17
  - `git log --oneline <base>..HEAD` 의 commit 범위
18
18
  - `git diff <base>..HEAD --stat` 의 변경 파일 통계
19
19
 
20
- 빈 섹션은 그대로 두지 말고 통째로 삭제합니다. HTML 주석은 PR 생성 전에
21
- 모두 제거됩니다.
22
20
  -->
23
21
 
24
- ## Summary
22
+ ## **Please check if the PR fulfills these requirements**
23
+ - [ ] Commits have a single intent
24
+ - [ ] Tests for the changes have been added (for bug fixes / features)
25
+ - [ ] I reviewed my own code
26
+ - [ ] I tested the changes (if not, explain why in the "Other information" section)
27
+ - [ ] Docs have been added / updated
25
28
 
26
- <!-- 1–3 bullets. 변경의 동기(WHY)와 결과(WHAT changed at a high level). -->
29
+ ## **What kind of change does this PR introduce?** (Bug fix, feature, docs update, ...)
27
30
 
28
- ## Changes
29
31
 
30
- <!-- 영역별로 묶은 구체적 변경 목록. 파일/모듈 단위 그룹화 권장. -->
32
+ ## **What is the current behavior?** (You can also link to an open issue here)
31
33
 
32
- ## Test plan
33
34
 
34
- <!-- 리뷰어가 따라 있는 검증 절차. 자동/수동 모두 가능. -->
35
+ ## **What is the new behavior (if this is a feature change)?**
35
36
 
36
- - [ ] <검증 항목>
37
- - [ ] <검증 항목>
38
37
 
39
- ## Linked issues
38
+ ## **Does this PR introduce a breaking change?** (What changes might users need to make in their application due to this PR?)
40
39
 
41
- <!-- `Refs: TICKET-123`, `Closes #N`, 관련 PR 링크 등. 없으면 섹션 통째로 삭제. -->
40
+
41
+ ## **Other information**:
@@ -181,6 +181,41 @@ The field is preserved across the runtime's auto-upserts of
181
181
  `updatedAt` are runtime-owned, so manual edits to `qaCommands`
182
182
  survive every subsequent `okstra setup` / `okstra run` invocation.
183
183
 
184
+ ### Step 4.6.1 (optional): `qaEnv` — Tier 3 conformance environment
185
+
186
+ `implementation` / `final-verification` verifiers run **stage
187
+ conformance scripts** (Tier 3) that may need to reach a database or an
188
+ HTTP endpoint to prove the diff satisfies upstream requirements. Declare
189
+ the environment those scripts are allowed to touch under `qaEnv`. Every
190
+ field is optional; declare only what your conformance scripts use.
191
+
192
+ ```json
193
+ "qaEnv": {
194
+ "replicaDbDsn": "<replica/test DB DSN — never shared/staging/prod>",
195
+ "appBaseUrl": "http://localhost:3000",
196
+ "envFile": ".okstra/qa.env",
197
+ "surfacePatterns": { "db": ["*.sql", "*repository*"], "http": ["*controller*"] }
198
+ }
199
+ ```
200
+
201
+ - `replicaDbDsn` — DSN the conformance script connects to. MUST be a
202
+ replica / disposable test DB, **never** a shared, staging, or
203
+ production database (conformance scripts may write).
204
+ - `appBaseUrl` — base URL for endpoint-level conformance checks
205
+ (local app only).
206
+ - `envFile` — path (under `.okstra/`) to an env file the verifier
207
+ sources before running conformance scripts.
208
+ - `surfacePatterns` — per-project **override** of the diff-surface
209
+ cross-check map (`capability → glob list`). The validator maps each
210
+ changed file to a capability surface (`db` / `http` / `io`) and fails
211
+ the run when the diff touches a surface no stage `requires`. The
212
+ built-in patterns (e.g. `*router*` for `http`, `*storage*` for `io`)
213
+ are broad and match many front-end files, so front-end-heavy repos
214
+ should override with narrower globs to avoid false BLOCKING verdicts
215
+ (Phase 4b review note). An over-broad pattern over-blocks; an
216
+ over-narrow one lets an undeclared surface through — tune to the
217
+ repo's real db/http/io file naming.
218
+
184
219
  ## Step 4.7 (automatic): project-local Claude settings symlink
185
220
 
186
221
  `okstra setup` (and `okstra run` on its first invocation per project)
@@ -140,7 +140,7 @@ implementation-option: {{ frontmatter.implementationOption | yaml_scalar }}
140
140
  {% if header.taskType == 'implementation-planning' %}
141
141
  ## 5.5 Implementation Plan Deliverables
142
142
 
143
- ### 5.5.1 Option Candidates{% if t("sectionAside.optionCandidates") != "Option Candidates" %} ({{ t("sectionAside.optionCandidates") }}){% endif %}
143
+ ### Option Candidates{% if t("sectionAside.optionCandidates") != "Option Candidates" %} ({{ t("sectionAside.optionCandidates") }}){% endif %}
144
144
 
145
145
  {% for opt in implementationPlanning.optionCandidates %}
146
146
  **{{ opt.name }}**
@@ -158,7 +158,7 @@ implementation-option: {{ frontmatter.implementationOption | yaml_scalar }}
158
158
 
159
159
  {% endfor %}
160
160
 
161
- ### 5.5.2 Trade-off Matrix{% if t("sectionAside.tradeOffMatrix") != "Trade-off Matrix" %} ({{ t("sectionAside.tradeOffMatrix") }}){% endif %}
161
+ ### Trade-off Matrix{% if t("sectionAside.tradeOffMatrix") != "Trade-off Matrix" %} ({{ t("sectionAside.tradeOffMatrix") }}){% endif %}
162
162
 
163
163
  | Option | Complexity | Risk | Reversibility | Test Coverage Cost | Rollout Cost |
164
164
  |--------|-----------|------|---------------|--------------------|--------------|
@@ -166,7 +166,7 @@ implementation-option: {{ frontmatter.implementationOption | yaml_scalar }}
166
166
  | {{ row.option }} | {{ row.complexity }} | {{ row.risk }} | {{ row.reversibility }} | {{ row.testCoverageCost }} | {{ row.rolloutCost }} |
167
167
  {% endfor %}
168
168
 
169
- ### 5.5.3 Recommended Option{% if t("sectionAside.recommendedOption") != "Recommended Option" %} ({{ t("sectionAside.recommendedOption") }}){% endif %}
169
+ ### Recommended Option{% if t("sectionAside.recommendedOption") != "Recommended Option" %} ({{ t("sectionAside.recommendedOption") }}){% endif %}
170
170
 
171
171
  | {{ t("implementationPlanning.recommendedTableHeaderLabel") }} | {{ t("implementationPlanning.recommendedTableHeaderValue") }} |
172
172
  |------|----|
@@ -175,15 +175,7 @@ implementation-option: {{ frontmatter.implementationOption | yaml_scalar }}
175
175
  | {{ t("implementationPlanning.rationaleLabel") }} | {{ implementationPlanning.recommendedOption.rationale }} |
176
176
  | {{ t("implementationPlanning.rejectedSummaryLabel") }} | {{ implementationPlanning.recommendedOption.rejectedSummary }} |
177
177
 
178
- ### 5.5.4 Stepwise Execution Order{% if t("sectionAside.stepwiseExecutionOrder") != "Stepwise Execution Order" %} ({{ t("sectionAside.stepwiseExecutionOrder") }}){% endif %}
179
-
180
- | Step | Ticket ID | Action (≤ 5min) | Files | Command / Test | Expected outcome |
181
- |------|-----------|------------------|-------|----------------|-------------------|
182
- {% for row in implementationPlanning.stepwiseExecution -%}
183
- | {{ row.step }} | `{{ row.ticketId }}` | {{ row.action }} | `{{ row.files }}` | `{{ row.commandOrTest }}` | {{ row.expectedOutcome }} |
184
- {% endfor %}
185
-
186
- ### 5.5.5 Dependency / Migration Risk{% if t("sectionAside.dependencyRisk") != "Dependency / Migration Risk" %} ({{ t("sectionAside.dependencyRisk") }}){% endif %}
178
+ ### Dependency / Migration Risk{% if t("sectionAside.dependencyRisk") != "Dependency / Migration Risk" %} ({{ t("sectionAside.dependencyRisk") }}){% endif %}
187
179
 
188
180
  {% if implementationPlanning.dependencyMigrationRisk | length == 0 -%}
189
181
  {{ t("emptyState.dependencyRisk") }}
@@ -195,7 +187,7 @@ implementation-option: {{ frontmatter.implementationOption | yaml_scalar }}
195
187
  {% endfor %}
196
188
  {%- endif %}
197
189
 
198
- ### 5.5.6 Validation Checklist{% if t("sectionAside.validationChecklist") != "Validation Checklist" %} ({{ t("sectionAside.validationChecklist") }}){% endif %}
190
+ ### Validation Checklist{% if t("sectionAside.validationChecklist") != "Validation Checklist" %} ({{ t("sectionAside.validationChecklist") }}){% endif %}
199
191
 
200
192
  | Phase | Ticket ID | Check | Command / Observation | Expected outcome |
201
193
  |-------|-----------|-------|------------------------|-------------------|
@@ -203,7 +195,7 @@ implementation-option: {{ frontmatter.implementationOption | yaml_scalar }}
203
195
  | {{ row.phase }} | `{{ row.ticketId }}` | {{ row.check }} | `{{ row.commandOrObservation }}` | {{ row.expectedOutcome }} |
204
196
  {% endfor %}
205
197
 
206
- ### 5.5.7 Rollback Strategy{% if t("sectionAside.rollbackStrategy") != "Rollback Strategy" %} ({{ t("sectionAside.rollbackStrategy") }}){% endif %}
198
+ ### Rollback Strategy{% if t("sectionAside.rollbackStrategy") != "Rollback Strategy" %} ({{ t("sectionAside.rollbackStrategy") }}){% endif %}
207
199
 
208
200
  | ID | Step | Action | Trigger signal | {{ t("columns.checkMethod") }} |
209
201
  |----|------|--------|----------------|-----------|
@@ -211,7 +203,7 @@ implementation-option: {{ frontmatter.implementationOption | yaml_scalar }}
211
203
  | {{ row.id }} | {{ row.step }} | `{{ row.action }}` | {{ row.triggerSignal }} | `{{ row.verificationMethod }}` |
212
204
  {% endfor %}
213
205
 
214
- ### 5.5.8 Requirement Coverage
206
+ ### Requirement Coverage
215
207
 
216
208
  | ID | Source | Requirement | Covered by option / stage / step | Status |
217
209
  |----|--------|-------------|-----------------------------------|--------|
@@ -219,6 +211,45 @@ implementation-option: {{ frontmatter.implementationOption | yaml_scalar }}
219
211
  | {{ row.id }} | `{{ row.source }}` | {{ row.requirement }} | {{ row.coveredBy }} | `{{ row.status }}` |
220
212
  {% endfor %}
221
213
 
214
+ ## 5.5 Stage Map
215
+
216
+ | stage | title | depends-on | step-count | exit-contract-summary |
217
+ |-------|-------|------------|------------|-----------------------|
218
+ {% for row in implementationPlanning.stageMap -%}
219
+ | {{ row.stage }} | {{ row.title }} | {{ row.dependsOn }} | {{ row.stepCount }} | {{ row.exitContractSummary }} |
220
+ {% endfor %}
221
+
222
+ {% for stage in implementationPlanning.stages %}
223
+ ## 5.5.{{ stage.stage }} Stage {{ stage.stage }}: {{ stage.title }}
224
+
225
+ Slice value: {{ stage.sliceValue }}
226
+ Acceptance: {{ stage.acceptance }}
227
+ {% if stage.conformanceTests %}Conformance tests: stage-{{ stage.stage }} — {{ stage.conformanceTests }}
228
+ {% else %}Conformance exemption: {{ stage.conformanceExemption }}
229
+ {% endif %}{% if stage.tddExemption %}TDD exemption: {{ stage.tddExemption }}
230
+ {% endif %}
231
+ ### Carry-In
232
+
233
+ {{ stage.carryIn }}
234
+
235
+ ### Stepwise Execution Order
236
+
237
+ | step | action | files | command | expected |
238
+ |------|--------|-------|---------|----------|
239
+ {% for row in stage.stepwiseExecution -%}
240
+ | {{ row.step }} | {{ row.action }} | `{{ row.files }}` | `{{ row.command }}` | {{ row.expected }} |
241
+ {% endfor %}
242
+
243
+ ### Stage Exit Contract
244
+
245
+ {{ stage.exitContract }}
246
+
247
+ ### Stage Validation
248
+
249
+ {{ stage.stageValidation }}
250
+
251
+ {% endfor %}
252
+
222
253
  ### 5.5.9 Plan Body Verification{% if t("sectionAside.planBodyVerification") != "Plan Body Verification" %} ({{ t("sectionAside.planBodyVerification") }}){% endif %}
223
254
 
224
255
  {{ t("sectionIntro.planBodyVerification") }}
@@ -271,6 +302,7 @@ implementation-option: {{ frontmatter.implementationOption | yaml_scalar }}
271
302
  |---------|-----------|--------------------|--------------------|
272
303
  | H1 | {{ t("releaseHandoff.h1Body") }} | `{{ releaseHandoff.userSelections.h1 }}` | `local only` / `push + PR` / `skip` |
273
304
  | H2 | {{ t("releaseHandoff.h2Body") }} | `{{ releaseHandoff.userSelections.h2 or t("releaseHandoff.h2DefaultLabel") }}` | {{ t("releaseHandoff.h2OptionsLabel") }} |
305
+ | H2b | Merge conflict probe | `{{ releaseHandoff.userSelections.h2b or releaseHandoff.mergeConflictProbe.kind }}` | `not-run` / `clean` / `proceed anyway` / `change base branch` / `cancel` |
274
306
  | H3 | {{ t("releaseHandoff.h3Body") }} | `{{ releaseHandoff.userSelections.h3 }}` | `use as-is` / `edit then proceed` / `cancel` |
275
307
 
276
308
  ### 5.6.4 Executed Commands
@@ -371,7 +403,19 @@ implementation-option: {{ frontmatter.implementationOption | yaml_scalar }}
371
403
  {% endfor %}
372
404
  {%- endif %}
373
405
 
374
- ### 5.7.5 Validation Evidence
406
+ ### 5.7.5 Stage Sidecar Evidence
407
+
408
+ - Stage: `{{ implementation.stageSidecarEvidence.stageNumber }}` — {{ implementation.stageSidecarEvidence.stageTitle }}
409
+ - Carry sidecar JSON:
410
+ ```json
411
+ {{ implementation.stageSidecarEvidence.carryJson }}
412
+ ```
413
+ - Appended `consumers.jsonl` rows:
414
+ {% for row in implementation.stageSidecarEvidence.consumerRows -%}
415
+ > {{ row }}
416
+ {% endfor %}
417
+
418
+ ### 5.7.6 Validation Evidence
375
419
 
376
420
  | Phase | Command | Exit code | Output tail | TDD evidence |
377
421
  |-------|---------|-----------|-------------|--------------|
@@ -379,7 +423,7 @@ implementation-option: {{ frontmatter.implementationOption | yaml_scalar }}
379
423
  | {{ row.phase }} | `{{ row.command }}` | `{{ row.exitCode }}` | {{ row.outputTail }} | {{ row.tddEvidence or '--' }} |
380
424
  {% endfor %}
381
425
 
382
- ### 5.7.6 Verifier Results
426
+ ### 5.7.7 Verifier Results
383
427
 
384
428
  {% for block in implementation.verifierResults %}
385
429
  - **{{ block.verifier }}** — Verdict: `{{ block.verdict }}`
@@ -390,7 +434,7 @@ implementation-option: {{ frontmatter.implementationOption | yaml_scalar }}
390
434
  - Discrepancy: {{ block.discrepancy or t("emptyState.discrepancy") }}
391
435
  {% endfor %}
392
436
 
393
- ### 5.7.7 Rollback Verification
437
+ ### 5.7.8 Rollback Verification
394
438
 
395
439
  | Category | Rollback command | Verification | Result |
396
440
  |----------|-------------------|---------------|--------|
@@ -398,7 +442,7 @@ implementation-option: {{ frontmatter.implementationOption | yaml_scalar }}
398
442
  | {{ row.category }} | `{{ row.rollbackCommand }}` | {{ row.verification }} | `{{ row.result }}` |
399
443
  {% endfor %}
400
444
 
401
- ### 5.7.8 Routing Recommendation
445
+ ### 5.7.9 Routing Recommendation
402
446
 
403
447
  {{ implementation.routingRecommendation }}
404
448
 
@@ -105,7 +105,7 @@
105
105
  "h2Body": "PR base branch (when H1 = `push + PR`)",
106
106
  "h3Body": "How should the PR title/body draft be handled?",
107
107
  "h2DefaultLabel": "(n/a)",
108
- "h2OptionsLabel": "staging / preprod / prod / main / dev / user input",
108
+ "h2OptionsLabel": "staging / preprod / main / user input",
109
109
  "noMutationNote": "(no mutating command — H1=`skip` or H3=`cancel`)",
110
110
  "commandsTableHeader": {
111
111
  "outputSummary": "stdout/stderr summary"
@@ -105,7 +105,7 @@
105
105
  "h2Body": "PR base 브랜치 (H1=`push + PR` 인 경우)",
106
106
  "h3Body": "PR title/body 초안 처리",
107
107
  "h2DefaultLabel": "(n/a)",
108
- "h2OptionsLabel": "staging / preprod / prod / main / dev / 사용자 입력",
108
+ "h2OptionsLabel": "staging / preprod / main / 사용자 입력",
109
109
  "noMutationNote": "(mutating 명령 미실행 — H1=`skip` 또는 H3=`cancel`)",
110
110
  "commandsTableHeader": {
111
111
  "outputSummary": "stdout/stderr 요약"
@@ -27,7 +27,7 @@ taskType: "{{FM_TASK_TYPE}}"
27
27
  ## Approved Plan Reference (mandatory)
28
28
 
29
29
  - Approved plan path: `runs/implementation-planning/<run-id>/reports/final-report.md`
30
- - Approval evidence (quoted exactly from the plan's `User Approval Request` resolution):
30
+ - Approval evidence (quoted exactly from the plan's YAML frontmatter, e.g. `approved: true`):
31
31
  - Recommended option name selected from the plan:
32
32
  - Plan's bite-sized step list (paste or reference by anchor):
33
33