okstra 0.225.0 → 0.226.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.
Files changed (75) hide show
  1. package/dist/cli-registry.mjs +6 -0
  2. package/dist/cli-registry.mjs.map +1 -1
  3. package/dist/commands/execute/render-bundle.mjs +5 -0
  4. package/dist/commands/execute/render-bundle.mjs.map +1 -1
  5. package/dist/commands/pr/pr.mjs +42 -13
  6. package/dist/commands/pr/pr.mjs.map +1 -1
  7. package/docs/cli.md +19 -3
  8. package/docs/project-structure-overview.md +2 -0
  9. package/package.json +1 -1
  10. package/runtime/BUILD.json +2 -2
  11. package/runtime/bin/lib/okstra/cli.sh +5 -1
  12. package/runtime/bin/lib/okstra/globals.sh +1 -0
  13. package/runtime/bin/lib/okstra/usage.sh +3 -0
  14. package/runtime/bin/okstra.sh +2 -0
  15. package/runtime/prompts/duties/code-reviewer.json +7 -4
  16. package/runtime/prompts/duties/technical-verification-worker.json +2 -1
  17. package/runtime/prompts/lead/convergence.md +4 -1
  18. package/runtime/prompts/lead/phase-routing.md +7 -3
  19. package/runtime/prompts/lead/team-contract.md +1 -1
  20. package/runtime/prompts/wizard/prompts.ko.json +15 -0
  21. package/runtime/python/okstra_ctl/adapters/providers/codex/adapter.py +5 -0
  22. package/runtime/python/okstra_ctl/analysis_packet.py +108 -2
  23. package/runtime/python/okstra_ctl/approved_plan_stage_extract.py +206 -0
  24. package/runtime/python/okstra_ctl/brief_sources.py +200 -0
  25. package/runtime/python/okstra_ctl/business_flow/store.py +15 -2
  26. package/runtime/python/okstra_ctl/cmux.py +22 -5
  27. package/runtime/python/okstra_ctl/code_review_coverage.py +26 -2
  28. package/runtime/python/okstra_ctl/convergence_critic_prompt.py +4 -1
  29. package/runtime/python/okstra_ctl/design_surfaces.py +19 -7
  30. package/runtime/python/okstra_ctl/execution_mutation_audit.py +20 -18
  31. package/runtime/python/okstra_ctl/implementation_direction.py +10 -0
  32. package/runtime/python/okstra_ctl/initial_prompt_materialization.py +41 -0
  33. package/runtime/python/okstra_ctl/manager_split.py +185 -27
  34. package/runtime/python/okstra_ctl/next_phase.py +3 -1
  35. package/runtime/python/okstra_ctl/phases/final_verification/prior_blockers.py +128 -0
  36. package/runtime/python/okstra_ctl/phases/final_verification/profile.md +8 -5
  37. package/runtime/python/okstra_ctl/phases/final_verification/report_assets/final-verification.template.html +1 -1
  38. package/runtime/python/okstra_ctl/phases/final_verification/validation.py +84 -0
  39. package/runtime/python/okstra_ctl/phases/implementation/instructions/_implementation-executor.md +1 -1
  40. package/runtime/python/okstra_ctl/phases/implementation_option_selection/authoring.py +13 -5
  41. package/runtime/python/okstra_ctl/phases/implementation_option_selection/boundary.json +1 -1
  42. package/runtime/python/okstra_ctl/phases/implementation_option_selection/entry.py +67 -0
  43. package/runtime/python/okstra_ctl/phases/implementation_option_selection/profile.md +4 -2
  44. package/runtime/python/okstra_ctl/phases/implementation_option_selection/report_assets/implementation-option-selection.template.html +2 -1
  45. package/runtime/python/okstra_ctl/phases/implementation_option_selection/validation.py +127 -18
  46. package/runtime/python/okstra_ctl/phases/implementation_planning/profile.md +2 -1
  47. package/runtime/python/okstra_ctl/phases/technical_verification/profile.md +2 -2
  48. package/runtime/python/okstra_ctl/report_assembly.py +23 -2
  49. package/runtime/python/okstra_ctl/report_views.py +2 -1
  50. package/runtime/python/okstra_ctl/run.py +46 -1
  51. package/runtime/python/okstra_ctl/run_context.py +13 -8
  52. package/runtime/python/okstra_ctl/wizard/confirmation.py +6 -0
  53. package/runtime/python/okstra_ctl/wizard/ids.py +6 -0
  54. package/runtime/python/okstra_ctl/wizard/registry.py +21 -0
  55. package/runtime/python/okstra_ctl/wizard/render.py +4 -0
  56. package/runtime/python/okstra_ctl/wizard/sources.py +33 -11
  57. package/runtime/python/okstra_ctl/wizard/state.py +4 -0
  58. package/runtime/python/okstra_ctl/wizard/steps_options.py +74 -9
  59. package/runtime/python/okstra_ctl/worker_prompt_body.py +21 -1
  60. package/runtime/python/okstra_ctl/worker_prompt_headers.py +11 -4
  61. package/runtime/python/okstra_ctl/worker_prompt_policy.py +3 -0
  62. package/runtime/schemas/final-report-v2.0.schema.json +74 -0
  63. package/runtime/schemas/final-report-v3.0.schema.json +99 -2
  64. package/runtime/skills/okstra-brief-gen/SKILL.md +26 -0
  65. package/runtime/skills/okstra-code-review/SKILL.md +2 -0
  66. package/runtime/skills/okstra-code-review/references/census-rules.md +16 -2
  67. package/runtime/skills/okstra-code-review/references/review-calibration.md +7 -1
  68. package/runtime/skills/okstra-manager/SKILL.md +15 -2
  69. package/runtime/skills/okstra-pr-gen/SKILL.md +15 -1
  70. package/runtime/templates/reports/brief.template.md +10 -1
  71. package/runtime/templates/reports/html/i18n/en.json +8 -0
  72. package/runtime/templates/reports/html/i18n/ko.json +8 -0
  73. package/runtime/templates/worker-prompt-preamble.md +1 -1
  74. package/runtime/validators/validate-brief.py +39 -1
  75. package/runtime/validators/validate-run.py +5 -0
@@ -10014,6 +10014,80 @@
10014
10014
  "implementation-option-selection",
10015
10015
  "implementation-planning"
10016
10016
  ]
10017
+ },
10018
+ "defectClass": {
10019
+ "$ref": "#/$defs/DefectClass"
10020
+ }
10021
+ }
10022
+ },
10023
+ "DefectClass": {
10024
+ "type": "object",
10025
+ "description": "The defect class a blocker belongs to: the shared mechanism, every location x case cell swept for it, and whether an earlier final-verification run of the same target already blocked on it. Required on every final-verification acceptance blocker by `phases/final_verification/validation.py`.",
10026
+ "required": [
10027
+ "mechanism",
10028
+ "cells",
10029
+ "recurrence"
10030
+ ],
10031
+ "additionalProperties": false,
10032
+ "properties": {
10033
+ "mechanism": {
10034
+ "type": "string",
10035
+ "minLength": 1
10036
+ },
10037
+ "cells": {
10038
+ "type": "array",
10039
+ "minItems": 1,
10040
+ "items": {
10041
+ "$ref": "#/$defs/DefectClassCell"
10042
+ }
10043
+ },
10044
+ "recurrence": {
10045
+ "enum": [
10046
+ "new",
10047
+ "recurring"
10048
+ ]
10049
+ },
10050
+ "priorBlockers": {
10051
+ "type": "array",
10052
+ "items": {
10053
+ "type": "string",
10054
+ "pattern": "^final-verification-\\d{3,} AB-\\d{3,}$"
10055
+ }
10056
+ }
10057
+ }
10058
+ },
10059
+ "DefectClassCell": {
10060
+ "type": "object",
10061
+ "required": [
10062
+ "location",
10063
+ "case",
10064
+ "verdict",
10065
+ "evidence"
10066
+ ],
10067
+ "additionalProperties": false,
10068
+ "properties": {
10069
+ "location": {
10070
+ "type": "string",
10071
+ "minLength": 1
10072
+ },
10073
+ "case": {
10074
+ "type": "string",
10075
+ "minLength": 1
10076
+ },
10077
+ "verdict": {
10078
+ "enum": [
10079
+ "defective",
10080
+ "sound",
10081
+ "unverified",
10082
+ "not-applicable"
10083
+ ]
10084
+ },
10085
+ "evidence": {
10086
+ "type": "string",
10087
+ "minLength": 1
10088
+ },
10089
+ "regressionTest": {
10090
+ "type": "string"
10017
10091
  }
10018
10092
  }
10019
10093
  },
@@ -3510,6 +3510,9 @@
3510
3510
  "$ref": "#/$defs/ImplementationOptionSafetyBlocker"
3511
3511
  }
3512
3512
  },
3513
+ "splitAcceptance": {
3514
+ "$ref": "#/$defs/ImplementationOptionSplitAcceptance"
3515
+ },
3513
3516
  "unresolvedFeasibilityFacts": {
3514
3517
  "type": "array",
3515
3518
  "items": {
@@ -3608,6 +3611,9 @@
3608
3611
  "$ref": "#/$defs/ImplementationOptionSafetyBlocker"
3609
3612
  }
3610
3613
  },
3614
+ "splitAcceptance": {
3615
+ "$ref": "#/$defs/ImplementationOptionSplitAcceptance"
3616
+ },
3611
3617
  "unresolvedFeasibilityFacts": {
3612
3618
  "type": "array",
3613
3619
  "items": {
@@ -3936,6 +3942,18 @@
3936
3942
  }
3937
3943
  }
3938
3944
  },
3945
+ "ImplementationOptionSplitAcceptance": {
3946
+ "type": "object",
3947
+ "description": "The user's accept-risk answer that settles a split feasibility vote. Report assembly checks that clarificationRef names a blocks: approval row answered with accept-risk.",
3948
+ "required": ["clarificationRef"],
3949
+ "additionalProperties": false,
3950
+ "properties": {
3951
+ "clarificationRef": {
3952
+ "type": "string",
3953
+ "pattern": "^C-[0-9]{3}$"
3954
+ }
3955
+ }
3956
+ },
3939
3957
  "ImplementationOptionUnresolvedFact": {
3940
3958
  "type": "object",
3941
3959
  "required": [
@@ -3947,8 +3965,13 @@
3947
3965
  "properties": {
3948
3966
  "resolutionKind": {
3949
3967
  "type": "string",
3950
- "enum": ["user-decision", "technical-verification"],
3951
- "description": "Optional for historical reads; blocked report publication requires explicit classification."
3968
+ "enum": ["user-decision", "technical-verification", "deferred-gate"],
3969
+ "description": "Optional for historical reads; blocked report publication requires explicit classification. deferred-gate marks a fact only a later gate can close (for example a deployed-environment measurement); it requires gate and does not block candidate validity."
3970
+ },
3971
+ "gate": {
3972
+ "type": "string",
3973
+ "minLength": 1,
3974
+ "description": "For deferred-gate facts: the gate that closes the fact, such as a brief External Gates entry or final-verification user-owned external QA."
3952
3975
  },
3953
3976
  "clarificationRefs": {
3954
3977
  "type": "array",
@@ -10480,6 +10503,80 @@
10480
10503
  "implementation-option-selection",
10481
10504
  "implementation-planning"
10482
10505
  ]
10506
+ },
10507
+ "defectClass": {
10508
+ "$ref": "#/$defs/DefectClass"
10509
+ }
10510
+ }
10511
+ },
10512
+ "DefectClass": {
10513
+ "type": "object",
10514
+ "description": "The defect class a blocker belongs to: the shared mechanism, every location x case cell swept for it, and whether an earlier final-verification run of the same target already blocked on it. Required on every final-verification acceptance blocker by `phases/final_verification/validation.py`.",
10515
+ "required": [
10516
+ "mechanism",
10517
+ "cells",
10518
+ "recurrence"
10519
+ ],
10520
+ "additionalProperties": false,
10521
+ "properties": {
10522
+ "mechanism": {
10523
+ "type": "string",
10524
+ "minLength": 1
10525
+ },
10526
+ "cells": {
10527
+ "type": "array",
10528
+ "minItems": 1,
10529
+ "items": {
10530
+ "$ref": "#/$defs/DefectClassCell"
10531
+ }
10532
+ },
10533
+ "recurrence": {
10534
+ "enum": [
10535
+ "new",
10536
+ "recurring"
10537
+ ]
10538
+ },
10539
+ "priorBlockers": {
10540
+ "type": "array",
10541
+ "items": {
10542
+ "type": "string",
10543
+ "pattern": "^final-verification-\\d{3,} AB-\\d{3,}$"
10544
+ }
10545
+ }
10546
+ }
10547
+ },
10548
+ "DefectClassCell": {
10549
+ "type": "object",
10550
+ "required": [
10551
+ "location",
10552
+ "case",
10553
+ "verdict",
10554
+ "evidence"
10555
+ ],
10556
+ "additionalProperties": false,
10557
+ "properties": {
10558
+ "location": {
10559
+ "type": "string",
10560
+ "minLength": 1
10561
+ },
10562
+ "case": {
10563
+ "type": "string",
10564
+ "minLength": 1
10565
+ },
10566
+ "verdict": {
10567
+ "enum": [
10568
+ "defective",
10569
+ "sound",
10570
+ "unverified",
10571
+ "not-applicable"
10572
+ ]
10573
+ },
10574
+ "evidence": {
10575
+ "type": "string",
10576
+ "minLength": 1
10577
+ },
10578
+ "regressionTest": {
10579
+ "type": "string"
10483
10580
  }
10484
10581
  }
10485
10582
  },
@@ -185,6 +185,23 @@ for every variant and every source type below.
185
185
  missing and where it lives. A bracketed note inside the captured block
186
186
  helps the reader, but it does not replace that row: the row is what a later
187
187
  phase sees when it asks whether the source arrived complete.
188
+ 4. **Every local document the brief is derived from is its own Source.** A
189
+ spec, plan or note under the project root that you read while drafting —
190
+ one the reporter named, or one you opened because a ticket or the
191
+ conversation pointed at it — gets its own `### Source N — file` entry,
192
+ not only a mention in Context. When git does not track the file (a
193
+ `.project-docs/` spec, for example), write the entry with
194
+ `- format: by-reference`, a project-relative `ref`, and an empty fence:
195
+ the task worktree is a checkout without that file, and prepare copies it
196
+ read-only into the analysis packet's `## Cited Local Documents` on every
197
+ run, so later revisions of the document reach the next phase. A tracked
198
+ file is in the worktree; capture it as-is per rule 1.
199
+
200
+ **Enforced:** `validators/validate-brief.py`
201
+ (`check_cited_local_documents`) refuses a brief that names a git-untracked
202
+ document under the project root outside `## Source Material` without such
203
+ an entry. `okstra brief-sources attach --project-root <PROJECT_ROOT>
204
+ --brief <path>` appends the missing entries by reference.
188
205
 
189
206
  ### 1.0. brief variant
190
207
 
@@ -235,6 +252,9 @@ each source's raw content **separately** under the `Source Material
235
252
  Take an absolute or project-relative path via free-text. Read the entire
236
253
  file. Re-ask if it doesn't exist. Captured content is preserved **verbatim**
237
254
  in Source Material.
255
+ When the file is under the project root and git does not track it, record
256
+ it by reference instead (capture contract rule 4): the entry's `ref` is the
257
+ project-relative path, `format: by-reference`, and the fence stays empty.
238
258
 
239
259
  ### 1b. `Issue tracker ticket`
240
260
 
@@ -1047,6 +1067,12 @@ Lenses` rules.
1047
1067
  2. If it exits non-zero, **fix the offending brief(s) and re-run**. Do NOT
1048
1068
  print the Step 7 message until the validator passes.
1049
1069
 
1070
+ A `local document ... has no ### Source N — file entry` failure is fixed
1071
+ by `okstra brief-sources attach --project-root <PROJECT_ROOT> --brief
1072
+ <path>`, which appends the entry and changes no other line. For a brief
1073
+ other than the one you just wrote, show the user the listed path and ask
1074
+ before running it.
1075
+
1050
1076
  3. If neither validator copy is present, fall back to a manual checklist:
1051
1077
  confirm each brief has all ten required frontmatter keys (`type`,
1052
1078
  `brief-id`, `parent-id`, `ticket-id`, `source-type`, `task-group`,
@@ -195,6 +195,8 @@ The command reuses the existing completion verification boundary and refuses mix
195
195
 
196
196
  For missing checks, dispatch one gap-fill invocation per reviewer on that reviewer's original slot, carrying only its incomplete grouped cells, their shared catalog entries, the same range and rule paths. Ask for one complete row for each such cell, not separate replies per missing rule. Use a new invocation ID and repeat the full materialize → verify → dispatch → materialize-result → complete → verify-completion boundary. Re-run the audit with that reviewer's initial and gap-fill markers. Earlier usable verdicts remain unchanged. An invocation with unknown IDs is invalid; record its rejection and request a corrected new invocation before auditing the retained valid completions. Do not silently relabel IDs.
197
197
 
198
+ Before accepting a clean general cell, open its G2/G3/G5 observations and verify the result-meaning trace, the downstream workload and pre-work bound, and the applicable documented convention checks. If opened code contradicts a clean observation, record the semantic rejection and exclude that completion from the final coverage audit. Request a full corrected invocation on the same reviewer slot and audit the retained valid completion only; otherwise first-usable preservation would keep the disproved claim. Keep the rejected artifacts immutable for the audit trail. Ordinary missing-row or formatting gaps still use targeted gap-fill with retained valid completions. The parser enforces presence and source locations; the lead checks truth. Keep these observations inside the existing Evidence column, without adding verdict rows.
199
+
198
200
  The Checked/N/A/Findings fields must account for every expected rule ID exactly once; a generic clean or all-checked statement never covers an omitted rule. Do not start Step 3.6 until every reviewer passes the audit. If evidence remains unavailable, report the specific blocker and incomplete coverage instead of writing a completed review.
199
201
 
200
202
  ## Step 3.6 — Check every citation
@@ -19,11 +19,11 @@ Use these named review criteria. Each criterion includes the relevant subconditi
19
19
  | Group | Rule IDs and scope |
20
20
  |---|---|
21
21
  | `structural` | `S1` reuse/DRY; `S2` simplicity/KISS; `S3` responsibilities/SOLID; `S4` current scope/YAGNI; when an architecture pack is routed: `S5` thin ports, `S6` I/O-only adapters, `S7` domain-object placement, `S8` dependency direction, `S9` boundary ports, `S10` domain-owned business rules |
22
- | `semantic` | `M1` truthful and specific names; `M2` single-purpose readable flow, including size and nesting; `M3` comments and explanatory values |
22
+ | `semantic` | `M1` truthful and specific names, including result tags and diagnostic values; `M2` single-purpose readable flow, including size and nesting; `M3` comments and explanatory values |
23
23
  | `state-and-tests` | `T1` state and mutation boundaries; `T2` no self-mocking; `T3` behavioral outcome assertions; `T4` discriminating fixtures, test titles and branch coverage |
24
24
  | `general` | `G1` correctness and regressions; `G2` error handling and third-party wrappers; `G3` untrusted-input and resource bounds; `G4` performance; `G5` routed language/framework and project conventions |
25
25
 
26
- Write the selected IDs with their absolute pack/section references under `## Rules`. Multiple references for a criterion go in the same Reference field, separated by semicolons. Do not turn every heading, bullet, formatter instruction or example into another rule. Read the original references rather than restating their bodies.
26
+ Write the selected IDs with their absolute pack/section references under `## Rules`. Multiple references for a criterion go in the same Reference field, separated by semicolons. Include the observed-fact error guidance with G2, and list the routed normative clause locations together under G5 instead of citing only a pack's first line. Do not turn every heading, bullet, formatter instruction or example into another rule. Read the original references rather than restating their bodies.
27
27
 
28
28
  Write targets under `## Cells`; each has a stable `C-CR-...` ID, the target and its group. A function target includes its path and position, e.g. `src/orders/pricing.py:42 calculate_price`. Distinguish same-line anonymous functions with their columns. Targets are repository-relative; no absolute or parent-traversal paths.
29
29
 
@@ -45,6 +45,20 @@ The example is abbreviated; the actual catalog includes the named criteria above
45
45
 
46
46
  Do not omit a cell because a rule seems inapplicable. Record that criterion under `N/A` with its reason in the compact row. The normal case lists checked rule IDs once with one inspected source anchor for the cell. This preserves rule-level omission checks without repeating paths and explanations for every rule.
47
47
 
48
+ ## Inspection before compression
49
+
50
+ Keep discovery separate from output accounting. Read the changed constructs and applicable canonical clauses, trace their dependencies, identify findings, then compress the result into the existing grouped rows. The table is the output format, not the inspection procedure.
51
+
52
+ Use these inspection guides for general cells:
53
+
54
+ | Criterion | Inspection guide |
55
+ |---|---|
56
+ | G2 | Enumerate the dependency's possible outcomes and compare their meanings with the returned tags, diagnostic values and error text. Trace existing callees and types when the changed mapping depends on them. |
57
+ | G3 | Identify each input dimension and the work performed inside changed loops or repeated calls. Follow existing callees to account for scans, parsing and allocation; a type-only callee diff does not remove newly amplified work at its caller. Locate the enforcing bound before the expensive work, or report the unbounded path. |
58
+ | G5 | Inspect every applicable normative clause of the routed language, framework and project policy for the changed constructs. Name those clauses and observed conformance. Evaluate explicit conventions independently of formatter, linter and behavioral-test results. |
59
+
60
+ For checked G2/G3/G5, retain a short source-cited observation in the existing Evidence column as defined by `review-calibration.md`. Format and source existence are enforced by `code_review_coverage.py::_checked_rule_evidence` and the standalone completion tests. The truth and completeness of the observation remain review judgments; the lead checks them before accepting clean coverage.
61
+
48
62
  ## The one principle
49
63
 
50
64
  **Membership is mechanical; judgment only ever decides a cell's verdict.**
@@ -61,7 +61,13 @@ Return exactly one row per assigned cell under `## Coverage Verdicts`. Keep deta
61
61
 
62
62
  The example uses the base structural and state-and-tests catalogs. `Checked` means inspected and no actionable finding for those rules. A criterion includes all applicable subconditions in its references; one inapplicable subcondition does not make the entire criterion N/A. Explicitly list the IDs; `all`, counts and ID ranges are not verdicts. `N/A` assigns `rule=reason`; `Findings` assigns `rule=finding-id`. Separate assignments with semicolons and multiple finding IDs for one rule with commas. Use `-` for an empty field. Every expected rule appears exactly once across the three status fields. Never infer an omitted rule as clean.
63
63
 
64
- Every row supplies one literal repository-relative source `path:line` for that cell's target file. Do not substitute a rule-pack citation such as `cc:5`, a global alias such as `E01`, or another target's path. The checker verifies that the file and line exist inside the project. Additional definitions and evidence remain in finding bodies.
64
+ Every row supplies one literal repository-relative source `path:line` for that cell's target file. The citation may be backtick-quoted and followed by ` — ` and a short observation before any ` :: ` rule observations. Do not substitute a rule-pack citation such as `cc:5`, a global alias such as `E01`, or another target's path. The checker verifies that the file and line exist inside the project. Additional definitions and evidence remain in finding bodies.
65
+
66
+ For `general` cells, checked G2, G3 and G5 each carry a compact observation in that same Evidence field: `target/path:line :: G2=source/path:line observed outcome mapping; G3=source/path:line observed workload and bound; G5=source/path:line applicable clauses and observed conformance`. Each observation begins with an existing project-source citation; it may cite a relevant callee. Keep rule-pack references in the catalog or the observation text, not in the project-source citation slot. Separate assignments with semicolons; use commas within an observation. Findings and reasoned N/A already explain their criteria and need no duplicate clean observation.
67
+
68
+ For example, `jobs/dispatch.py:18 :: G2=jobs/client.py:25 returned states preserve the dependency outcomes; G3=jobs/dispatch.py:9 validated batch limit bounds work before parsing; G5=jobs/dispatch.py:18 public typing and exception-chaining clauses hold` records three observations in one row. `checked`, `clean` and `no issues` alone are verdict labels, not observations. The checker rejects missing observations, notes for unchecked criteria, aliases and invalid source locations. It cannot establish whether a written observation is true.
69
+
70
+ Complete discovery before producing clean rows. Follow dependency results and diagnostic meanings for G2, downstream workload and pre-work bounds for G3, and each applicable documented convention for G5. A documented convention violation can be a `nit` with a concrete fix even when outputs are correct. Trace the shared predicates and algorithms before treating similar orchestration as duplicated rules.
65
71
 
66
72
  A finding ID names a body outside this section, in this reviewer's original or gap-fill result. N/A reasons are short inline applicability explanations, not aliases or rule references. Missing sources, unread rules and blocked inspection remain unaccounted checks and name their blocker. A partial row is rejected with the missing rule IDs; gap-fill rechecks that grouped cell, not every individual rule as a separate output row.
67
73
 
@@ -69,9 +69,11 @@ Use this when one Linear project, or one parent issue, covers work in several re
69
69
  3. For each issue, propose target projects from evidence: labels, team, repository names or paths in the body. Confirm with `prompt_user` (multi-select, recommended projects first, reason in each description).
70
70
  4. For each (issue, project) pair, draft the scope: the part of the issue this project implements. Confirm it with `prompt_user` offering one or two drafts plus a custom-input option. A pair without scope is rejected by the CLI.
71
71
  5. Author each pair's brief fields under the okstra-brief-gen brief contract: bodies verbatim, `EB-NNN` / `PB-NNN` / `EO-NNN` items as `<id> <observable condition> — verify: <how>`, anything without an observation method under `externalGates`, and `openQuestions` rows prefixed `general:`, `terminology:`, `intent-check:`, `conversion-block:` or `adr-candidate:`.
72
+ List in `localSources` every local document under that project's root you read to author the pair (a spec, a plan, a note), as a path relative to the project root. Each becomes a `### Source N — file` entry with `format: by-reference`, so every run of the child copies the current file into its analysis packet; a task worktree is a checkout and lacks an untracked `.project-docs/` file. A path outside the project root or a missing file rejects the plan, and a brief whose text names an untracked document that `localSources` does not list fails `validate-brief`.
72
73
  6. Write the plan JSON below with the Write tool to a scratch path outside the project roots, then run `okstra manager task split ... --plan <path>`.
73
74
  - `split plan rejected` or `rendered briefs failed validate-brief`: fix the named fields and run it again. Nothing was written.
74
- - `briefs already exist with different content`: show the listed paths and ask before adding `--overwrite`, which replaces hand edits. Adding issues to an earlier split changes every brief's Related Task Graph, so existing briefs are listed too.
75
+ - To add issues or projects to an earlier split, add them to the kept `split-plan.json` content and run the split again without `--overwrite`. Each brief's status is `created`, `unchanged`, `refreshed` or `overwritten`. The CLI renders each existing brief from the kept plan and from the new plan, both with the brief's original `created` date. When the two differ only in `### Out of scope`, `## Related Artifacts` and `## Related Task Graph`, it removes and appends just the changed lines in those sections (`refreshed`) and keeps every other line, hand edits included. An edit to the pair's own `outOfScope` list is applied the same way. A brief the new plan does not change stays `unchanged`, even with hand edits.
76
+ - `briefs already exist with content this plan would change`: the plan changes a listed brief outside those sections, for example its own scope or Context. Show the listed paths and ask before adding `--overwrite`, which re-renders only those briefs and replaces their hand edits. A brief with no kept plan to compare against is also listed when its text differs.
75
77
  7. Each `Brief N task key` is `<project-id>:<task-group>:<child-task-id>`. Start one with `okstra manager task run ... --project-id <project-id> --child-task-id <child-task-id>` and follow the Child Launch Rule. The packet already carries `--task-brief`, and `--task-type` until the child task exists.
76
78
 
77
79
  Plan JSON (`schemaVersion` 1). `source.kind` is `project` or `issue`; `recommendedPhase` is `requirements-discovery` (default), `error-analysis` or `improvement-discovery`; `relations[].relation` uses the brief Related Task Graph values; list fields may be omitted.
@@ -90,7 +92,8 @@ Plan JSON (`schemaVersion` 1). `source.kind` is `project` or `issue`; `recommend
90
92
  "recommendedPhase": "requirements-discovery",
91
93
  "context": "<text>", "problem": "<text>", "desiredOutcome": "<text>",
92
94
  "expectedBehavior": [], "preservedBehavior": [], "expectedOutcome": [],
93
- "externalGates": [], "constraints": [], "openQuestions": []
95
+ "externalGates": [], "constraints": [], "openQuestions": [],
96
+ "localSources": [".project-docs/specs/<spec>.md"]
94
97
  }
95
98
  ]
96
99
  }
@@ -100,6 +103,16 @@ Plan JSON (`schemaVersion` 1). `source.kind` is `project` or `issue`; `recommend
100
103
 
101
104
  The CLI writes each brief to `<projectRoot>/.okstra/briefs/<group-slug>/<ticketId>-<file-title>.md` (`<group-slug>` is the task group lowercased, with each run of characters other than `a-z` and `0-9` replaced by `-`, so `Upload V2` becomes `upload-v2`) with a `## Project Scope` section: this project's scope, `outOfScope`, and the scope of every other project the same issue went to. It validates every brief with the brief validator before writing any file, registers the child tasks, and keeps the plan at `split-plan.json` in the manager task directory.
102
105
 
106
+ ## Upgrading an Existing Brief
107
+
108
+ A brief written before `localSources` existed may name a local document only in Context; `validate-brief` and every prepare of that child then refuse it. Do not hand-edit the brief and do not re-split to fix it: `--overwrite` re-renders the brief and replaces later edits. Run
109
+
110
+ ```bash
111
+ okstra brief-sources attach --project-root <project-root> --brief <brief-path>
112
+ ```
113
+
114
+ It appends one `### Source N — file` entry (`format: by-reference`) per cited untracked document at the end of Source Material and changes no other line, so `EB-` / `PB-` / `EO-` ids, External Gates and later edits stay. Show the user the printed paths. Add the same paths to the pair's `localSources` in the kept `split-plan.json` before any later re-split, so the re-rendered brief carries them.
115
+
103
116
  ## Overview Page
104
117
 
105
118
  When the user wants to see a manager at a glance, run `okstra manager task sync ...` for each task listed by `okstra manager list tasks ...` whose snapshot must be current, then run `okstra manager view --manager-id <manager-id>` and give the user the returned `View URL`. The page reads manager files only, so each task shows the state of its last sync and prints its own sync command.
@@ -12,7 +12,7 @@ For authored questions, `prompt_user` means the current host's available questio
12
12
  Register PR body templates and generate PR descriptions. Templates live in the
13
13
  user home at `~/.okstra/template/pr/`. This skill is **global** — it does not
14
14
  require `<PROJECT_ROOT>/.okstra/project.json`. PR generation additionally
15
- requires the current directory to be a git repository.
15
+ requires a git repository: the current directory, or the one `--repo` names.
16
16
 
17
17
  ## Step 0: Check CLI availability
18
18
 
@@ -31,6 +31,13 @@ If `okstra` is not on PATH, tell the user:
31
31
  `$(...)`, leading `VAR=`, or wrap in `if`/`eval`/`||`/`&&` — keep arguments literal and follow the current host's
32
32
  execution-permission guidance. Paste literal values emitted by a prior call.
33
33
 
34
+ **Target repository:** when the PR's repository is not the session's working
35
+ directory — a sibling repository or another worktree — never `cd` into it. Add
36
+ `--repo <absolute path>` to every `okstra pr branches`, `okstra pr gen` and
37
+ `okstra pr check` call instead; omit it when the working directory is the PR's
38
+ repository. `--template-file` and `--body-file` paths still resolve against the
39
+ working directory, so pass them as absolute paths.
40
+
34
41
  ## Step 1: Pick the mode (always first)
35
42
 
36
43
  Present a 3-option picker (`prompt_user`):
@@ -78,6 +85,8 @@ Carry the chosen template name as `<template>` (`default` for the bundled one).
78
85
  okstra pr branches
79
86
  ```
80
87
 
88
+ With a target repository: `okstra pr branches --repo <absolute path>`.
89
+
81
90
  It prints the recommended base branches one per line, current branch excluded, or `(no branches)`.
82
91
  Present a 3-option base picker from the first two printed branches plus `Enter directly` last; with
83
92
  `(no branches)`, ask for the base directly. Carry the choice as `<base>`.
@@ -88,6 +97,8 @@ Present a 3-option base picker from the first two printed branches plus `Enter d
88
97
  okstra pr gen --base <base> --template <template>
89
98
  ```
90
99
 
100
+ With a target repository, add `--repo <absolute path>`.
101
+
91
102
  To describe a branch other than the checkout — a stage branch whose worktree is gone — add
92
103
  `--head <branch>`; nothing is checked out.
93
104
 
@@ -105,6 +116,9 @@ Write the title and body to a temp file and check them:
105
116
  okstra pr check --body-file <path-to-body>
106
117
  ```
107
118
 
119
+ With a target repository, add `--repo <absolute path>`: the check skips terms that
120
+ repository's tracked files use.
121
+
108
122
  Each printed `L<line>: <pattern>: <match>` row is an okstra identifier a reviewer cannot open:
109
123
  restate that line in the reviewer's terms and check again. `clean` means none is left.
110
124
 
@@ -43,6 +43,15 @@ attachment returned as a link, an opaque embed node) gets a
43
43
  `conversion-block:` row in Open Questions naming what is missing. Heading was
44
44
  originally "Source Material (verbatim — do not modify)" — the parenthetical
45
45
  is a reviewer note, not body text.
46
+ `format: by-reference` applies only to a `file` source whose `ref` is a path
47
+ under the project root (absolute, or relative to the project root): leave
48
+ the fence empty, and prepare copies the file read-only into the analysis
49
+ packet's `## Cited Local Documents` on every run. Use it for an untracked
50
+ local document such as a `.project-docs/` spec, which the task worktree does
51
+ not contain. A file only mentioned in Context is not read, and
52
+ `validators/validate-brief.py` refuses a brief that names an untracked
53
+ document outside Source Material without its own `file` entry;
54
+ `okstra brief-sources attach` appends the missing entries.
46
55
  -->
47
56
 
48
57
  ### Source 1 — <type: file | linear | jira | github | notion | url | user-input>
@@ -50,7 +59,7 @@ is a reviewer note, not body text.
50
59
  - ref: <abs file path | LIN-1234 | https://... | "conversation synthesis">
51
60
  - fetched-via: <Read | mcp__linear__getIssue | mcp__notion__... | gh issue view | WebFetch | user-paste>
52
61
  - fetched-at: <YYYY-MM-DD HH:MM>
53
- - format: <as-is | "Jira ADF → Markdown (semantics preserved)" | "tool-truncated — missing body requested from reporter">
62
+ - format: <as-is | by-reference | "Jira ADF → Markdown (semantics preserved)" | "tool-truncated — missing body requested from reporter">
54
63
 
55
64
  ```
56
65
  <Paste the raw source here without changing a single character.>
@@ -451,6 +451,12 @@
451
451
  "what-blocks-acceptance": "What blocks acceptance",
452
452
  "nothing-is-holding-acceptance": "Nothing is holding acceptance.",
453
453
  "follow-up": "Follow-up:",
454
+ "defect-mechanism": "Mechanism:",
455
+ "recurring-class": "Recurring — an earlier run blocked on this class:",
456
+ "location": "Location",
457
+ "case": "Case",
458
+ "verdict": "Verdict",
459
+ "regression-test": "Regression test",
454
460
  "residual-risk": "Residual risk",
455
461
  "owner": "Owner:",
456
462
  "escalation-condition": "Escalation condition:",
@@ -521,6 +527,8 @@
521
527
  "safety-blockers": "Safety blockers",
522
528
  "technical-verification": "Technical verification pending",
523
529
  "user-decision": "User decision record",
530
+ "deferred-gate": "Closes at a later gate",
531
+ "split-acceptance": "Split vote accepted as a risk by the user",
524
532
  "classification-required": "Classification required before reassembly",
525
533
  "unresolved-facts": "Unresolved feasibility facts",
526
534
  "expected-outcome": "Expected outcome",
@@ -451,6 +451,12 @@
451
451
  "what-blocks-acceptance": "수용을 막는 것",
452
452
  "nothing-is-holding-acceptance": "수용을 막는 것이 없습니다.",
453
453
  "follow-up": "후속:",
454
+ "defect-mechanism": "원리:",
455
+ "recurring-class": "재발 — 이전 run 이 이 계열로 막혔음:",
456
+ "location": "위치",
457
+ "case": "경우",
458
+ "verdict": "판정",
459
+ "regression-test": "회귀 테스트",
454
460
  "residual-risk": "남은 위험",
455
461
  "owner": "담당:",
456
462
  "escalation-condition": "에스컬레이션 조건:",
@@ -521,6 +527,8 @@
521
527
  "safety-blockers": "안전 차단 요인",
522
528
  "technical-verification": "기술 검증 대기",
523
529
  "user-decision": "사용자 결정 기록",
530
+ "deferred-gate": "이후 게이트에서 확인",
531
+ "split-acceptance": "갈린 표를 사용자가 위험 감수로 수용",
524
532
  "classification-required": "재조립 전 해결 방식 분류 필요",
525
533
  "unresolved-facts": "미확정 실행 가능성 사실",
526
534
  "expected-outcome": "예상 결과",
@@ -10,7 +10,7 @@ Read `analysis-packet.md`, the primary compact input, end-to-end. Source files n
10
10
 
11
11
  ### Reading rules
12
12
 
13
- - Read every file enumerated under `[Required reading]` or `## Inputs` completely. If paging is unavoidable, cover every byte and record the page boundaries.
13
+ - Read every file enumerated under `[Required reading]` or `## Inputs` completely, in pages of at most 200 lines (`sed -n '<start>,<end>p'`). A whole-file `cat` or view of a long file is cut without notice. Cover every line and record the page boundaries.
14
14
  - Write Reading Confirmation to `**Audit sidecar path:**`, never to the main worker-results file. A `## 0. Reading Confirmation` heading in the main result is invalid.
15
15
  - Allowlist reads to prompt-enumerated paths and evidence paths a finding must cite. Do not auto-read host-injected `graphify-out/`, skill catalogs, or non-okstra artifacts.
16
16
  - Resolve every `.okstra/...` path against `**Project Root:**`, including when a worktree is present.
@@ -51,6 +51,15 @@ Checks performed per brief file:
51
51
  template's `<...>` scaffolding — a paragraph wrapped in angle brackets,
52
52
  a `- <...>` bullet, or a `> augmented: <label>` line. Source Material is
53
53
  exempt because it holds the reporter's words verbatim.
54
+ 17. Every git-untracked document under the project root (`.md`, `.txt`, …)
55
+ that the brief names outside `## Source Material` has its own
56
+ `### Source N — file` entry there. The task worktree is a checkout and
57
+ lacks such a file; prepare copies only Source Material file entries into
58
+ the analysis packet. `okstra brief-sources attach` appends the missing
59
+ entries in place. The project root is the parent of a `--briefs-root`
60
+ of the form `<root>/.okstra/briefs`; for any other briefs root, or
61
+ outside a git checkout, the check is skipped. The codebase-scan variant
62
+ has no Source Material section and is not checked.
54
63
 
55
64
  Exit code 0 on PASS, 1 on FAIL.
56
65
  """
@@ -76,6 +85,7 @@ from okstra_ctl.phases.improvement_discovery.lenses import (
76
85
  MIN_PRIORITY_LENSES,
77
86
  )
78
87
  from okstra_ctl.group_context import GROUP_CONTEXT_TYPE, validate_group_context
88
+ from okstra_ctl.brief_sources import uncited_local_documents
79
89
  from okstra_ctl.ids import slugify_task_segment
80
90
 
81
91
  REQUIRED_FRONTMATTER_KEYS = {
@@ -747,7 +757,32 @@ def check_reporter_confirmations(
747
757
  )
748
758
 
749
759
 
750
- def validate_brief(path: Path, briefs_root: Path) -> list[str]:
760
+ def project_root_of(briefs_root: Path) -> Path | None:
761
+ """`<root>/.okstra/briefs` 이면 `<root>`, 아니면 None."""
762
+ resolved = briefs_root.resolve()
763
+ if resolved.name == "briefs" and resolved.parent.name == ".okstra":
764
+ return resolved.parent.parent
765
+ return None
766
+
767
+
768
+ def check_cited_local_documents(
769
+ text: str, scope: str, project_root: Path | None, errors: list[str]
770
+ ) -> None:
771
+ if project_root is None or scope == "codebase":
772
+ return
773
+ for relative in uncited_local_documents(text, project_root):
774
+ errors.append(
775
+ f"local document {relative!r} is cited in the brief but has no "
776
+ "`### Source N — file` entry in Source Material; it is untracked, "
777
+ "so the task worktree lacks it and no run will show it to workers. "
778
+ f"Run `okstra brief-sources attach --project-root {project_root} "
779
+ "--brief <this brief>` to append it by reference"
780
+ )
781
+
782
+
783
+ def validate_brief(
784
+ path: Path, briefs_root: Path, project_root: Path | None = None
785
+ ) -> list[str]:
751
786
  text = strip_html_comments(path.read_text(encoding="utf-8"))
752
787
  errors: list[str] = []
753
788
 
@@ -795,6 +830,9 @@ def validate_brief(path: Path, briefs_root: Path) -> list[str]:
795
830
  check_end_state_sections(text, scope, errors)
796
831
  check_variant_required_sections(text, scope, errors)
797
832
  check_template_scaffold(text, scope, errors)
833
+ check_cited_local_documents(
834
+ text, scope, project_root or project_root_of(briefs_root), errors
835
+ )
798
836
 
799
837
  # 2. brief-id matches filename stem
800
838
  stem = path.stem
@@ -242,6 +242,7 @@ from okstra_ctl.report_routing import technical_verification_routing_errors
242
242
  from okstra_ctl.phases.implementation_option_selection.validation import ( # noqa: E402
243
243
  validate_blocked_answer_channel,
244
244
  validate_implementation_option_selection,
245
+ validate_split_acceptance,
245
246
  )
246
247
  from okstra_ctl.phases.implementation_planning.validation import (
247
248
  _validate_requirement_provenance, _validate_stage_has_requirement,
@@ -3197,6 +3198,10 @@ def validate_final_report_data(
3197
3198
  f"implementation-option-selection: {error}"
3198
3199
  for error in validate_blocked_answer_channel(data)
3199
3200
  )
3201
+ failures.extend(
3202
+ f"implementation-option-selection: {error}"
3203
+ for error in validate_split_acceptance(data)
3204
+ )
3200
3205
  elif task_type == "implementation":
3201
3206
  _validate_stage_carry_sidecar_exists(data, report_path, failures)
3202
3207
  _validate_lead_fixes(data, failures)