okstra 0.225.1 → 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.
- package/dist/cli-registry.mjs +6 -0
- package/dist/cli-registry.mjs.map +1 -1
- package/dist/commands/execute/render-bundle.mjs +5 -0
- package/dist/commands/execute/render-bundle.mjs.map +1 -1
- package/docs/cli.md +17 -1
- package/docs/project-structure-overview.md +2 -0
- package/package.json +1 -1
- package/runtime/BUILD.json +2 -2
- package/runtime/bin/lib/okstra/cli.sh +5 -1
- package/runtime/bin/lib/okstra/globals.sh +1 -0
- package/runtime/bin/lib/okstra/usage.sh +3 -0
- package/runtime/bin/okstra.sh +2 -0
- package/runtime/prompts/duties/technical-verification-worker.json +2 -1
- package/runtime/prompts/lead/convergence.md +4 -1
- package/runtime/prompts/lead/phase-routing.md +7 -3
- package/runtime/prompts/lead/team-contract.md +1 -1
- package/runtime/prompts/wizard/prompts.ko.json +15 -0
- package/runtime/python/okstra_ctl/adapters/providers/codex/adapter.py +5 -0
- package/runtime/python/okstra_ctl/analysis_packet.py +108 -2
- package/runtime/python/okstra_ctl/approved_plan_stage_extract.py +206 -0
- package/runtime/python/okstra_ctl/brief_sources.py +200 -0
- package/runtime/python/okstra_ctl/business_flow/store.py +15 -2
- package/runtime/python/okstra_ctl/cmux.py +18 -3
- package/runtime/python/okstra_ctl/convergence_critic_prompt.py +4 -1
- package/runtime/python/okstra_ctl/design_surfaces.py +19 -7
- package/runtime/python/okstra_ctl/execution_mutation_audit.py +20 -18
- package/runtime/python/okstra_ctl/implementation_direction.py +10 -0
- package/runtime/python/okstra_ctl/initial_prompt_materialization.py +41 -0
- package/runtime/python/okstra_ctl/manager_split.py +185 -27
- package/runtime/python/okstra_ctl/next_phase.py +3 -1
- package/runtime/python/okstra_ctl/phases/final_verification/prior_blockers.py +128 -0
- package/runtime/python/okstra_ctl/phases/final_verification/profile.md +8 -5
- package/runtime/python/okstra_ctl/phases/final_verification/report_assets/final-verification.template.html +1 -1
- package/runtime/python/okstra_ctl/phases/final_verification/validation.py +84 -0
- package/runtime/python/okstra_ctl/phases/implementation/instructions/_implementation-executor.md +1 -1
- package/runtime/python/okstra_ctl/phases/implementation_option_selection/authoring.py +13 -5
- package/runtime/python/okstra_ctl/phases/implementation_option_selection/boundary.json +1 -1
- package/runtime/python/okstra_ctl/phases/implementation_option_selection/entry.py +67 -0
- package/runtime/python/okstra_ctl/phases/implementation_option_selection/profile.md +4 -2
- package/runtime/python/okstra_ctl/phases/implementation_option_selection/report_assets/implementation-option-selection.template.html +2 -1
- package/runtime/python/okstra_ctl/phases/implementation_option_selection/validation.py +127 -18
- package/runtime/python/okstra_ctl/phases/implementation_planning/profile.md +2 -1
- package/runtime/python/okstra_ctl/phases/technical_verification/profile.md +2 -2
- package/runtime/python/okstra_ctl/report_assembly.py +23 -2
- package/runtime/python/okstra_ctl/report_views.py +2 -1
- package/runtime/python/okstra_ctl/run.py +46 -1
- package/runtime/python/okstra_ctl/run_context.py +13 -8
- package/runtime/python/okstra_ctl/wizard/confirmation.py +6 -0
- package/runtime/python/okstra_ctl/wizard/ids.py +6 -0
- package/runtime/python/okstra_ctl/wizard/registry.py +21 -0
- package/runtime/python/okstra_ctl/wizard/render.py +4 -0
- package/runtime/python/okstra_ctl/wizard/sources.py +33 -11
- package/runtime/python/okstra_ctl/wizard/state.py +4 -0
- package/runtime/python/okstra_ctl/wizard/steps_options.py +74 -9
- package/runtime/python/okstra_ctl/worker_prompt_body.py +21 -1
- package/runtime/python/okstra_ctl/worker_prompt_headers.py +11 -4
- package/runtime/python/okstra_ctl/worker_prompt_policy.py +3 -0
- package/runtime/schemas/final-report-v2.0.schema.json +74 -0
- package/runtime/schemas/final-report-v3.0.schema.json +99 -2
- package/runtime/skills/okstra-brief-gen/SKILL.md +26 -0
- package/runtime/skills/okstra-manager/SKILL.md +15 -2
- package/runtime/templates/reports/brief.template.md +10 -1
- package/runtime/templates/reports/html/i18n/en.json +8 -0
- package/runtime/templates/reports/html/i18n/ko.json +8 -0
- package/runtime/templates/worker-prompt-preamble.md +1 -1
- package/runtime/validators/validate-brief.py +39 -1
- package/runtime/validators/validate-run.py +5 -0
|
@@ -27,6 +27,8 @@ class WorkerPromptHeaderError(Exception):
|
|
|
27
27
|
# skill catalogs, and similar non-okstra artifacts. Restating the scope inline
|
|
28
28
|
# puts the rule in front of the worker before its first tool call, which is the
|
|
29
29
|
# only point at which it can still win over those host instructions.
|
|
30
|
+
# 패킷의 페이지 읽기 규칙도 같은 이유로 여기 있다 — 패킷 안 목차는 파일을 연 뒤에야
|
|
31
|
+
# 보이는데, 통째 `cat` 은 그 전에 잘린다(실측 609KB 패킷, codex).
|
|
30
32
|
READ_SCOPE_HEADER = (
|
|
31
33
|
"**Read scope:** Read only the paths this prompt enumerates "
|
|
32
34
|
"(`[Required reading]`, `## Inputs`, verification-target paths) plus "
|
|
@@ -39,7 +41,11 @@ READ_SCOPE_HEADER = (
|
|
|
39
41
|
"NOT apply inside an okstra worker run: do not auto-read `graphify-out/`, "
|
|
40
42
|
"`SKILL.md`, or other artifacts outside `<PROJECT_ROOT>/.okstra/`. If an "
|
|
41
43
|
"un-enumerated file seems essential, record it under *Missing Information "
|
|
42
|
-
"or Assumptions* instead of reading it."
|
|
44
|
+
"or Assumptions* instead of reading it. Read the primary analysis packet "
|
|
45
|
+
"in pages of at most 200 lines (`sed -n '1,200p'`, then `'201,400p'`, …), "
|
|
46
|
+
"never with one whole-file `cat` or view: worker CLIs cut long output "
|
|
47
|
+
"without saying so, and the packet's `## Section Index` gives each "
|
|
48
|
+
"section's line range."
|
|
43
49
|
)
|
|
44
50
|
EVIDENCE_LEDGER_HEADER = "**Evidence ledger:** required-v1"
|
|
45
51
|
|
|
@@ -63,12 +69,13 @@ EVIDENCE_CITATION_HEADER = (
|
|
|
63
69
|
# 구현 워커는 명령 행을 쓰지 못한다.
|
|
64
70
|
EVIDENCE_COMMANDS_HEADER = (
|
|
65
71
|
"**Evidence commands:** Append one `- Evidence command: "
|
|
66
|
-
"{\"command\":\"<exact command>\",\"cwd\":\"<
|
|
72
|
+
"{\"command\":\"<exact command>\",\"cwd\":\"<directory the command ran in>\","
|
|
67
73
|
"\"exitCode\":0,\"outputSummary\":\"<one-line result>\"}` row to the audit "
|
|
68
74
|
"sidecar for each command that produced or verified a conclusion. Do not "
|
|
69
75
|
"record exploratory `rg`, `ls`, or file-opening commands. Write the command "
|
|
70
|
-
"as you ran it.
|
|
71
|
-
"
|
|
76
|
+
"as you ran it. When it starts with `cd <dir> &&`, `cwd` is that `<dir>`, "
|
|
77
|
+
"not the shell's starting directory. A value that must not be written down "
|
|
78
|
+
"is yours to omit or pass as a `$VAR` reference."
|
|
72
79
|
)
|
|
73
80
|
|
|
74
81
|
# `agy`'s write tool validates the target against the Gemini artifact store
|
|
@@ -34,6 +34,9 @@ ERRORS_PATH_HEADERS = (
|
|
|
34
34
|
# itself: the lead's launch prompt carries them, and no worker reads that.
|
|
35
35
|
APPROVED_PLAN_HEADER = "**Approved plan:**"
|
|
36
36
|
IMPLEMENTATION_STAGE_HEADER = "**Stage for this implementation run:**"
|
|
37
|
+
# Executor-only and optional: a prompt published before it, or a run whose plan
|
|
38
|
+
# is a Markdown report, has no extract and the executor reads the plan itself.
|
|
39
|
+
APPROVED_PLAN_STAGE_EXTRACT_HEADER = "**Approved plan stage extract:**"
|
|
37
40
|
# A stage's verifiers share one worktree and run as one batch. The Tier 3 script
|
|
38
41
|
# (builds, fixed ports) and the self-mock mutation probe (edits sources in place)
|
|
39
42
|
# must each run once, by the verifier named here.
|
|
@@ -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`,
|
|
@@ -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
|
-
-
|
|
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.
|
|
@@ -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.
|
|
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
|
|
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)
|