okstra 0.142.0 → 0.143.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/docs/project-structure-overview.md +2 -0
- package/docs/task-process/error-analysis.md +9 -4
- package/package.json +1 -1
- package/runtime/BUILD.json +2 -2
- package/runtime/agents/workers/report-writer-worker.md +2 -1
- package/runtime/prompts/lead/context-loader.md +2 -2
- package/runtime/prompts/lead/convergence.md +5 -2
- package/runtime/prompts/lead/report-writer.md +4 -3
- package/runtime/prompts/profiles/_common-contract.md +3 -1
- package/runtime/prompts/profiles/error-analysis.md +5 -1
- package/runtime/python/okstra_ctl/analysis_packet.py +28 -3
- package/runtime/python/okstra_ctl/brief_frontmatter.py +56 -0
- package/runtime/python/okstra_ctl/convergence_engine.py +66 -15
- package/runtime/python/okstra_ctl/run.py +81 -33
- package/runtime/python/okstra_ctl/schema_excerpt.py +5 -3
- package/runtime/python/okstra_ctl/wizard.py +2 -44
- package/runtime/schemas/final-report-v1.0.schema.json +99 -0
- package/runtime/templates/reports/final-report.template.md +26 -0
- package/runtime/templates/reports/i18n/en.json +20 -2
- package/runtime/templates/reports/i18n/ko.json +20 -2
- package/runtime/validators/validate-brief.py +5 -1
- package/runtime/validators/validate-run.py +287 -36
|
@@ -102,6 +102,10 @@ from .worktree import (
|
|
|
102
102
|
WorktreeProvision,
|
|
103
103
|
provision_task_worktree,
|
|
104
104
|
)
|
|
105
|
+
from .brief_frontmatter import (
|
|
106
|
+
has_reporter_confirmation_contract,
|
|
107
|
+
read_brief_frontmatter,
|
|
108
|
+
)
|
|
105
109
|
|
|
106
110
|
# Frontmatter approval-flag matcher.
|
|
107
111
|
#
|
|
@@ -854,6 +858,7 @@ class _ResolvedAssets:
|
|
|
854
858
|
final_report_template: Path
|
|
855
859
|
lead_contract: Path
|
|
856
860
|
run_validator: Path
|
|
861
|
+
brief_validator: Path
|
|
857
862
|
|
|
858
863
|
|
|
859
864
|
def _resolve_runtime_assets(workspace_root: Path, inp: PrepareInputs) -> _ResolvedAssets:
|
|
@@ -877,7 +882,14 @@ def _resolve_runtime_assets(workspace_root: Path, inp: PrepareInputs) -> _Resolv
|
|
|
877
882
|
)
|
|
878
883
|
lead_contract = workspace_root / "prompts" / "lead" / "okstra-lead-contract.md"
|
|
879
884
|
run_validator = workspace_root / "validators" / "validate-run.py"
|
|
880
|
-
|
|
885
|
+
brief_validator = workspace_root / "validators" / "validate-brief.py"
|
|
886
|
+
for required in (
|
|
887
|
+
task_index_template,
|
|
888
|
+
final_report_template,
|
|
889
|
+
run_validator,
|
|
890
|
+
brief_validator,
|
|
891
|
+
lead_contract,
|
|
892
|
+
):
|
|
881
893
|
if not required.is_file():
|
|
882
894
|
raise PrepareError(
|
|
883
895
|
f"required okstra template or lead contract missing: {required}.{_INSTALL_HINT}"
|
|
@@ -889,12 +901,49 @@ def _resolve_runtime_assets(workspace_root: Path, inp: PrepareInputs) -> _Resolv
|
|
|
889
901
|
final_report_template=final_report_template,
|
|
890
902
|
lead_contract=lead_contract,
|
|
891
903
|
run_validator=run_validator,
|
|
904
|
+
brief_validator=brief_validator,
|
|
892
905
|
)
|
|
893
906
|
|
|
894
907
|
|
|
908
|
+
def _validate_task_brief_preflight(
|
|
909
|
+
project_root: Path,
|
|
910
|
+
brief_path: Path,
|
|
911
|
+
validator_path: Path,
|
|
912
|
+
) -> None:
|
|
913
|
+
"""Validate canonical briefs before any prepare-time side effects."""
|
|
914
|
+
frontmatter = read_brief_frontmatter(brief_path)
|
|
915
|
+
if not has_reporter_confirmation_contract(frontmatter):
|
|
916
|
+
return
|
|
917
|
+
|
|
918
|
+
proc = _subprocess.run(
|
|
919
|
+
[
|
|
920
|
+
sys.executable,
|
|
921
|
+
str(validator_path),
|
|
922
|
+
str(brief_path),
|
|
923
|
+
"--briefs-root",
|
|
924
|
+
str(project_root / ".okstra" / "briefs"),
|
|
925
|
+
],
|
|
926
|
+
capture_output=True,
|
|
927
|
+
text=True,
|
|
928
|
+
check=False,
|
|
929
|
+
)
|
|
930
|
+
if proc.returncode != 0:
|
|
931
|
+
detail = " ".join(
|
|
932
|
+
line.strip()
|
|
933
|
+
for output in (proc.stdout, proc.stderr)
|
|
934
|
+
for line in output.splitlines()
|
|
935
|
+
if line.strip()
|
|
936
|
+
)
|
|
937
|
+
raise PrepareError(f"task brief failed validation: {detail}")
|
|
938
|
+
if frontmatter["reporter-confirmations"] == "pending":
|
|
939
|
+
raise PrepareError(
|
|
940
|
+
"task brief reporter-confirmations is pending; rerun okstra-brief-gen "
|
|
941
|
+
"Step 6.5 before starting error-analysis"
|
|
942
|
+
)
|
|
943
|
+
|
|
944
|
+
|
|
895
945
|
def _validate_prepare_inputs(project_root: Path, inp: PrepareInputs) -> list:
|
|
896
|
-
"""
|
|
897
|
-
를 검증하고, implementation 일 때 stage map 을 파싱해 돌려준다 (그 외엔 빈 리스트)."""
|
|
946
|
+
"""Validate pure prepare inputs and return a final-verification stage map."""
|
|
898
947
|
if not project_root.is_dir():
|
|
899
948
|
raise PrepareError(f"project root not found: {project_root}")
|
|
900
949
|
if inp.stages and inp.task_type != "release-handoff":
|
|
@@ -911,41 +960,14 @@ def _validate_prepare_inputs(project_root: Path, inp: PrepareInputs) -> list:
|
|
|
911
960
|
raise PrepareError(f"task brief not found: {inp.brief_path}")
|
|
912
961
|
ctx_stage_map: list = []
|
|
913
962
|
# implementation 과 final-verification 은 둘 다 승인된 plan 의 Stage Map 을
|
|
914
|
-
# 입력으로 받는다(전자는 실행 scope, 후자는 검증 scope).
|
|
915
|
-
# stage-map 파싱은 공유하고, frontmatter 승인/option 주입/구조 검증 같은
|
|
916
|
-
# implementation 전용 단계만 따로 게이트한다.
|
|
963
|
+
# 입력으로 받는다(전자는 실행 scope, 후자는 검증 scope).
|
|
917
964
|
if inp.task_type in ("implementation", "final-verification"):
|
|
918
965
|
if not inp.approved_plan_path:
|
|
919
966
|
raise PrepareError(
|
|
920
967
|
f"task-type {inp.task_type} requires "
|
|
921
968
|
"--approved-plan <path-to-final-report.md>"
|
|
922
969
|
)
|
|
923
|
-
if inp.task_type == "
|
|
924
|
-
# --approve / --implementation-option 은 공유 approved-plan 파일에
|
|
925
|
-
# read-modify-write 한다. 같은 task-key 의 동시 stage run 둘이 둘 다
|
|
926
|
-
# 이 플래그를 주면 후발 write 가 선발의 frontmatter 변경을 덮어쓴다
|
|
927
|
-
# (lost update). 두 mutation 을 per-task-key 락으로 직렬화한다.
|
|
928
|
-
if inp.approve_plan_ack or inp.implementation_option:
|
|
929
|
-
with worktree_provision_mutex(
|
|
930
|
-
okstra_home(), inp.project_id,
|
|
931
|
-
slugify(inp.task_group), slugify(inp.task_id),
|
|
932
|
-
):
|
|
933
|
-
if inp.approve_plan_ack:
|
|
934
|
-
# 사용자가 직접 `--approve` 를 입력한 행위 자체를 승인 의사로
|
|
935
|
-
# 모델링한다. frontmatter approved 를 true 로 toggle 한 뒤
|
|
936
|
-
# 동일한 검증 경로(`_validate_approved_plan`)를 통과시킨다.
|
|
937
|
-
_apply_cli_approval(inp.approved_plan_path)
|
|
938
|
-
if inp.implementation_option:
|
|
939
|
-
# 유저가 고른 Option Candidate 이름을 approved-plan
|
|
940
|
-
# frontmatter 의 `implementation-option:` 라인에 기록한다.
|
|
941
|
-
# 빈 값이면 implementation 이 plan 의 `Recommended Option`
|
|
942
|
-
# 으로 폴백하므로 호출하지 않는다.
|
|
943
|
-
_apply_cli_implementation_option(
|
|
944
|
-
inp.approved_plan_path, inp.implementation_option
|
|
945
|
-
)
|
|
946
|
-
_validate_approved_plan(inp.approved_plan_path)
|
|
947
|
-
_validate_stage_structure(inp.approved_plan_path)
|
|
948
|
-
else:
|
|
970
|
+
if inp.task_type == "final-verification":
|
|
949
971
|
# final-verification 에서 --approve / --implementation-option 은
|
|
950
972
|
# 의미가 없다 (승인은 implementation 진입 시 이미 끝났다).
|
|
951
973
|
if inp.approve_plan_ack:
|
|
@@ -958,7 +980,7 @@ def _validate_prepare_inputs(project_root: Path, inp: PrepareInputs) -> list:
|
|
|
958
980
|
"--implementation-option is only meaningful with --task-type "
|
|
959
981
|
"implementation and --approved-plan <path>"
|
|
960
982
|
)
|
|
961
|
-
|
|
983
|
+
ctx_stage_map = _parse_stage_map_into_ctx(inp.approved_plan_path)
|
|
962
984
|
else:
|
|
963
985
|
if inp.approve_plan_ack:
|
|
964
986
|
# implementation 외 task-type 에서 `--approve` 는 의미가 없다. 사용자에게
|
|
@@ -988,6 +1010,24 @@ def _validate_prepare_inputs(project_root: Path, inp: PrepareInputs) -> list:
|
|
|
988
1010
|
return ctx_stage_map
|
|
989
1011
|
|
|
990
1012
|
|
|
1013
|
+
def _prepare_implementation_approved_plan(inp: PrepareInputs) -> list:
|
|
1014
|
+
"""Apply approved-plan inputs only after canonical brief preflight succeeds."""
|
|
1015
|
+
if inp.approve_plan_ack or inp.implementation_option:
|
|
1016
|
+
with worktree_provision_mutex(
|
|
1017
|
+
okstra_home(), inp.project_id,
|
|
1018
|
+
slugify(inp.task_group), slugify(inp.task_id),
|
|
1019
|
+
):
|
|
1020
|
+
if inp.approve_plan_ack:
|
|
1021
|
+
_apply_cli_approval(inp.approved_plan_path)
|
|
1022
|
+
if inp.implementation_option:
|
|
1023
|
+
_apply_cli_implementation_option(
|
|
1024
|
+
inp.approved_plan_path, inp.implementation_option
|
|
1025
|
+
)
|
|
1026
|
+
_validate_approved_plan(inp.approved_plan_path)
|
|
1027
|
+
_validate_stage_structure(inp.approved_plan_path)
|
|
1028
|
+
return _parse_stage_map_into_ctx(inp.approved_plan_path)
|
|
1029
|
+
|
|
1030
|
+
|
|
991
1031
|
def _collect_handoff_source_report_rows(
|
|
992
1032
|
rows: list, nums: list,
|
|
993
1033
|
) -> list:
|
|
@@ -2072,6 +2112,14 @@ def prepare_task_bundle(inp: PrepareInputs) -> PrepareOutputs:
|
|
|
2072
2112
|
task_index_template = assets.task_index_template
|
|
2073
2113
|
final_report_template = assets.final_report_template
|
|
2074
2114
|
ctx_stage_map = _validate_prepare_inputs(project_root, inp)
|
|
2115
|
+
if inp.task_type != "release-handoff":
|
|
2116
|
+
_validate_task_brief_preflight(
|
|
2117
|
+
project_root,
|
|
2118
|
+
inp.brief_path,
|
|
2119
|
+
assets.brief_validator,
|
|
2120
|
+
)
|
|
2121
|
+
if inp.task_type == "implementation":
|
|
2122
|
+
ctx_stage_map = _prepare_implementation_approved_plan(inp)
|
|
2075
2123
|
|
|
2076
2124
|
# release-handoff: 검증 보고서 인용 input 문서를 생성해 brief 자리에 채운다.
|
|
2077
2125
|
# 이후의 모든 brief 소비 경로(material/instruction-set 복사)는 그대로 동작한다.
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
"""Build a task-type-scoped excerpt of the final-report schema.
|
|
2
2
|
|
|
3
3
|
The full schema (``schemas/final-report-v1.0.schema.json``) carries the
|
|
4
|
-
deliverable property blocks for ALL task-types (``
|
|
5
|
-
``releaseHandoff``, ``implementation``,
|
|
4
|
+
deliverable property blocks for ALL task-types (``errorAnalysis``,
|
|
5
|
+
``implementationPlanning``, ``releaseHandoff``, ``implementation``,
|
|
6
|
+
``finalVerification``) plus a
|
|
6
7
|
``$defs`` library (~38% of the file) shared across them. A single run only
|
|
7
8
|
authors ONE task-type's data.json, so the report-writer worker only needs
|
|
8
9
|
the common structure + its own task-type's block + the ``$defs`` those
|
|
@@ -24,10 +25,11 @@ import json
|
|
|
24
25
|
import re
|
|
25
26
|
|
|
26
27
|
# task-type → the per-type deliverable property key it owns. task-types
|
|
27
|
-
# absent from this map (requirements-discovery,
|
|
28
|
+
# absent from this map (requirements-discovery,
|
|
28
29
|
# improvement-discovery) have no per-type block; their excerpt keeps only
|
|
29
30
|
# the common properties.
|
|
30
31
|
_TASK_TYPE_PROPERTY = {
|
|
32
|
+
"error-analysis": "errorAnalysis",
|
|
31
33
|
"implementation-planning": "implementationPlanning",
|
|
32
34
|
"release-handoff": "releaseHandoff",
|
|
33
35
|
"implementation": "implementation",
|
|
@@ -30,6 +30,7 @@ from pathlib import Path
|
|
|
30
30
|
from typing import Any, Callable, Optional
|
|
31
31
|
|
|
32
32
|
from okstra_ctl.ids import slugify_task_segment
|
|
33
|
+
from okstra_ctl.brief_frontmatter import read_brief_frontmatter
|
|
33
34
|
from okstra_ctl.models import (
|
|
34
35
|
PROVIDER_MAPPINGS,
|
|
35
36
|
UnknownModelError,
|
|
@@ -155,49 +156,6 @@ _RECENT_PREFIX = "__recent:"
|
|
|
155
156
|
_REPORT_PREFIX = "__report:"
|
|
156
157
|
_BRIEF_PREFIX = "__brief:"
|
|
157
158
|
|
|
158
|
-
# Lines of `key: value` we pull from a brief markdown frontmatter. The
|
|
159
|
-
# parser is intentionally lightweight (no yaml dep) and tolerant — a
|
|
160
|
-
# malformed brief returns an empty dict.
|
|
161
|
-
_BRIEF_FRONTMATTER_LINE_RE = re.compile(r"^([a-zA-Z0-9_\-]+)\s*:\s*(.*)$")
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
def _parse_brief_frontmatter(path: Path) -> dict[str, str]:
|
|
165
|
-
"""Read the YAML-style frontmatter at the top of a brief markdown file
|
|
166
|
-
and return a flat ``{key: value}`` map.
|
|
167
|
-
|
|
168
|
-
Returns ``{}`` if the file is unreadable, has no frontmatter, or the
|
|
169
|
-
frontmatter is malformed. Comments (``# ...``) and quoted values are
|
|
170
|
-
stripped. Placeholder values like ``<task-group>`` are kept verbatim;
|
|
171
|
-
callers decide whether to treat them as a real suggestion.
|
|
172
|
-
"""
|
|
173
|
-
try:
|
|
174
|
-
text = path.read_text(encoding="utf-8")
|
|
175
|
-
except OSError:
|
|
176
|
-
return {}
|
|
177
|
-
if not text.startswith("---"):
|
|
178
|
-
return {}
|
|
179
|
-
lines = text.splitlines()
|
|
180
|
-
if not lines or lines[0].strip() != "---":
|
|
181
|
-
return {}
|
|
182
|
-
out: dict[str, str] = {}
|
|
183
|
-
for line in lines[1:]:
|
|
184
|
-
if line.strip() == "---":
|
|
185
|
-
break
|
|
186
|
-
# strip trailing inline comment
|
|
187
|
-
comment_idx = line.find("#")
|
|
188
|
-
if comment_idx >= 0:
|
|
189
|
-
line = line[:comment_idx]
|
|
190
|
-
m = _BRIEF_FRONTMATTER_LINE_RE.match(line.strip())
|
|
191
|
-
if not m:
|
|
192
|
-
continue
|
|
193
|
-
key, val = m.group(1), m.group(2).strip()
|
|
194
|
-
# strip matching quotes
|
|
195
|
-
if (len(val) >= 2 and val[0] == val[-1] and val[0] in ("'", '"')):
|
|
196
|
-
val = val[1:-1]
|
|
197
|
-
out[key] = val
|
|
198
|
-
return out
|
|
199
|
-
|
|
200
|
-
|
|
201
159
|
def _looks_like_template_placeholder(value: str) -> bool:
|
|
202
160
|
"""Treat ``<task-group>``, ``<...>``, empty strings, and ``self`` as
|
|
203
161
|
non-suggestions. Anything else (a real slug-like value) is honored."""
|
|
@@ -223,7 +181,7 @@ def _brief_suggestions(path: Path) -> tuple[str, str]:
|
|
|
223
181
|
A brief without frontmatter, or with placeholder values, yields two
|
|
224
182
|
empty strings — callers fall back to plain-text input.
|
|
225
183
|
"""
|
|
226
|
-
fm =
|
|
184
|
+
fm = read_brief_frontmatter(path)
|
|
227
185
|
tg_raw = fm.get("task-group", "")
|
|
228
186
|
bid_raw = fm.get("brief-id", "")
|
|
229
187
|
tg = "" if _looks_like_template_placeholder(tg_raw) else tg_raw
|
|
@@ -384,6 +384,8 @@
|
|
|
384
384
|
"items": { "$ref": "#/$defs/RiskRow" }
|
|
385
385
|
},
|
|
386
386
|
|
|
387
|
+
"errorAnalysis": { "$ref": "#/$defs/ErrorAnalysis" },
|
|
388
|
+
|
|
387
389
|
"implementationPlanning": {
|
|
388
390
|
"type": "object",
|
|
389
391
|
"description": "RENDER_IF taskType == implementation-planning. §5.5 deliverables.",
|
|
@@ -814,6 +816,16 @@
|
|
|
814
816
|
},
|
|
815
817
|
|
|
816
818
|
"allOf": [
|
|
819
|
+
{
|
|
820
|
+
"description": "error-analysis task-type requires a structured diagnosis block.",
|
|
821
|
+
"if": {
|
|
822
|
+
"properties": { "header": { "properties": { "taskType": { "const": "error-analysis" } } } },
|
|
823
|
+
"required": ["header"]
|
|
824
|
+
},
|
|
825
|
+
"then": {
|
|
826
|
+
"required": ["errorAnalysis"]
|
|
827
|
+
}
|
|
828
|
+
},
|
|
817
829
|
{
|
|
818
830
|
"description": "implementation-planning task-type requires §5.5 block.",
|
|
819
831
|
"if": {
|
|
@@ -952,6 +964,7 @@
|
|
|
952
964
|
"Direction": {
|
|
953
965
|
"enum": [
|
|
954
966
|
"continue-investigation",
|
|
967
|
+
"begin-planning",
|
|
955
968
|
"begin-implementation",
|
|
956
969
|
"approve",
|
|
957
970
|
"reject",
|
|
@@ -959,6 +972,92 @@
|
|
|
959
972
|
]
|
|
960
973
|
},
|
|
961
974
|
|
|
975
|
+
"ErrorAnalysis": {
|
|
976
|
+
"type": "object",
|
|
977
|
+
"additionalProperties": false,
|
|
978
|
+
"required": [
|
|
979
|
+
"symptomVerbatim",
|
|
980
|
+
"observableFailure",
|
|
981
|
+
"reproduction",
|
|
982
|
+
"causeCandidates",
|
|
983
|
+
"nextDiagnostic",
|
|
984
|
+
"routing"
|
|
985
|
+
],
|
|
986
|
+
"properties": {
|
|
987
|
+
"symptomVerbatim": { "type": "string" },
|
|
988
|
+
"observableFailure": { "type": "string" },
|
|
989
|
+
"reproduction": {
|
|
990
|
+
"type": "object",
|
|
991
|
+
"additionalProperties": false,
|
|
992
|
+
"required": ["status", "evidence", "blockedReason"],
|
|
993
|
+
"properties": {
|
|
994
|
+
"status": {
|
|
995
|
+
"enum": ["reproduced", "not-reproduced", "blocked-before-repro"]
|
|
996
|
+
},
|
|
997
|
+
"evidence": {
|
|
998
|
+
"type": "array",
|
|
999
|
+
"minItems": 1,
|
|
1000
|
+
"items": { "type": "string" }
|
|
1001
|
+
},
|
|
1002
|
+
"blockedReason": { "type": "string" }
|
|
1003
|
+
}
|
|
1004
|
+
},
|
|
1005
|
+
"causeCandidates": {
|
|
1006
|
+
"type": "array",
|
|
1007
|
+
"items": {
|
|
1008
|
+
"type": "object",
|
|
1009
|
+
"additionalProperties": false,
|
|
1010
|
+
"required": [
|
|
1011
|
+
"id",
|
|
1012
|
+
"statement",
|
|
1013
|
+
"supportingEvidence",
|
|
1014
|
+
"falsifyingEvidenceChecked",
|
|
1015
|
+
"confidence",
|
|
1016
|
+
"disproveWith"
|
|
1017
|
+
],
|
|
1018
|
+
"properties": {
|
|
1019
|
+
"id": { "type": "string", "pattern": "^EA-\\d{3,}$" },
|
|
1020
|
+
"statement": { "type": "string" },
|
|
1021
|
+
"supportingEvidence": {
|
|
1022
|
+
"type": "array",
|
|
1023
|
+
"minItems": 1,
|
|
1024
|
+
"items": { "type": "string" }
|
|
1025
|
+
},
|
|
1026
|
+
"falsifyingEvidenceChecked": {
|
|
1027
|
+
"type": "array",
|
|
1028
|
+
"minItems": 1,
|
|
1029
|
+
"items": { "type": "string" }
|
|
1030
|
+
},
|
|
1031
|
+
"confidence": { "enum": ["low", "medium", "high"] },
|
|
1032
|
+
"disproveWith": { "type": "string", "minLength": 1 }
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
},
|
|
1036
|
+
"nextDiagnostic": {
|
|
1037
|
+
"type": "object",
|
|
1038
|
+
"additionalProperties": false,
|
|
1039
|
+
"required": ["action", "confirmingSignal", "rejectingSignal"],
|
|
1040
|
+
"properties": {
|
|
1041
|
+
"action": { "type": "string", "minLength": 1 },
|
|
1042
|
+
"confirmingSignal": { "type": "string", "minLength": 1 },
|
|
1043
|
+
"rejectingSignal": { "type": "string", "minLength": 1 }
|
|
1044
|
+
}
|
|
1045
|
+
},
|
|
1046
|
+
"routing": {
|
|
1047
|
+
"type": "object",
|
|
1048
|
+
"additionalProperties": false,
|
|
1049
|
+
"required": ["nextTaskType", "leadingCauseId", "rationale"],
|
|
1050
|
+
"properties": {
|
|
1051
|
+
"nextTaskType": {
|
|
1052
|
+
"enum": ["error-analysis", "implementation-planning"]
|
|
1053
|
+
},
|
|
1054
|
+
"leadingCauseId": { "type": "string" },
|
|
1055
|
+
"rationale": { "type": "string", "minLength": 1 }
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
},
|
|
1060
|
+
|
|
962
1061
|
"TicketId": {
|
|
963
1062
|
"type": "string",
|
|
964
1063
|
"minLength": 1,
|
|
@@ -171,6 +171,32 @@ Carried-forward plan items retain their prior verdicts verbatim; each such item
|
|
|
171
171
|
|
|
172
172
|
{% endif %}
|
|
173
173
|
|
|
174
|
+
{% if header.taskType == 'error-analysis' %}
|
|
175
|
+
### 2.4 Error Analysis Result{% if t("errorAnalysis.heading") != "Error Analysis Result" %} ({{ t("errorAnalysis.heading") }}){% endif %}
|
|
176
|
+
|
|
177
|
+
- **{{ t("errorAnalysis.symptomVerbatim") }}:** {{ errorAnalysis.symptomVerbatim }}
|
|
178
|
+
- **{{ t("errorAnalysis.observableFailure") }}:** {{ errorAnalysis.observableFailure }}
|
|
179
|
+
- **{{ t("errorAnalysis.reproductionStatus") }}:** `{{ errorAnalysis.reproduction.status }}`
|
|
180
|
+
- **{{ t("errorAnalysis.reproductionEvidence") }}:** {{ errorAnalysis.reproduction.evidence | join(", ") }}
|
|
181
|
+
{% if errorAnalysis.reproduction.blockedReason %}- **{{ t("errorAnalysis.blockedReason") }}:** {{ errorAnalysis.reproduction.blockedReason }}
|
|
182
|
+
{% endif %}
|
|
183
|
+
|
|
184
|
+
{% if errorAnalysis.causeCandidates | length == 0 -%}
|
|
185
|
+
{{ t("emptyState.errorAnalysisCauseCandidates") }}
|
|
186
|
+
{%- else %}
|
|
187
|
+
| ID | {{ t("errorAnalysis.candidate") }} | {{ t("errorAnalysis.supportingEvidence") }} | {{ t("errorAnalysis.falsifyingEvidence") }} | {{ t("errorAnalysis.confidence") }} | {{ t("errorAnalysis.disproveWith") }} |
|
|
188
|
+
|---|---|---|---|---|---|
|
|
189
|
+
{% for row in errorAnalysis.causeCandidates -%}
|
|
190
|
+
| {{ row.id | mdcell }} | {{ row.statement | mdcell }} | {{ row.supportingEvidence | join(", ") | mdcell }} | {{ row.falsifyingEvidenceChecked | join(", ") | mdcell }} | `{{ row.confidence | mdcell }}` | {{ row.disproveWith | mdcell }} |
|
|
191
|
+
{% endfor %}
|
|
192
|
+
{%- endif %}
|
|
193
|
+
|
|
194
|
+
- **{{ t("errorAnalysis.nextDiagnostic") }}:** {{ errorAnalysis.nextDiagnostic.action }}
|
|
195
|
+
- **{{ t("errorAnalysis.confirmingSignal") }}:** {{ errorAnalysis.nextDiagnostic.confirmingSignal }}
|
|
196
|
+
- **{{ t("errorAnalysis.rejectingSignal") }}:** {{ errorAnalysis.nextDiagnostic.rejectingSignal }}
|
|
197
|
+
- **{{ t("errorAnalysis.route") }}:** `{{ errorAnalysis.routing.nextTaskType }}`{% if errorAnalysis.routing.leadingCauseId %} — `{{ errorAnalysis.routing.leadingCauseId }}`{% endif %} — {{ errorAnalysis.routing.rationale }}
|
|
198
|
+
{% endif %}
|
|
199
|
+
|
|
174
200
|
## 3. Recommended Next Steps
|
|
175
201
|
|
|
176
202
|
{% if recommendedNextSteps | length == 0 -%}
|
|
@@ -19,7 +19,8 @@
|
|
|
19
19
|
"lingeringRisks": "- No tracked lingering risks.",
|
|
20
20
|
"noClarification": "- No additional information requested. The Section 7 verdict stands as-is.",
|
|
21
21
|
"noFollowUp": "- No follow-up tasks. The next phase for this run is in §3 (Recommended Next Steps).",
|
|
22
|
-
"endStateCoverage": "No end-state coverage recorded for this phase."
|
|
22
|
+
"endStateCoverage": "No end-state coverage recorded for this phase.",
|
|
23
|
+
"errorAnalysisCauseCandidates": "No evidence-backed cause candidate yet."
|
|
23
24
|
},
|
|
24
25
|
"columns": {
|
|
25
26
|
"recordMeta": "Record",
|
|
@@ -97,8 +98,25 @@
|
|
|
97
98
|
"columnSections": "Sections",
|
|
98
99
|
"columnRelatedIds": "Related item IDs"
|
|
99
100
|
},
|
|
101
|
+
"errorAnalysis": {
|
|
102
|
+
"heading": "Error Analysis Result",
|
|
103
|
+
"symptomVerbatim": "Symptom Verbatim",
|
|
104
|
+
"observableFailure": "Observable Failure",
|
|
105
|
+
"reproductionStatus": "Reproduction Status",
|
|
106
|
+
"reproductionEvidence": "Reproduction Evidence",
|
|
107
|
+
"blockedReason": "Blocked Reason",
|
|
108
|
+
"candidate": "Cause Candidate",
|
|
109
|
+
"supportingEvidence": "Supporting Evidence",
|
|
110
|
+
"falsifyingEvidence": "Falsifying Evidence",
|
|
111
|
+
"confidence": "Confidence",
|
|
112
|
+
"disproveWith": "Disprove With",
|
|
113
|
+
"nextDiagnostic": "Next Diagnostic",
|
|
114
|
+
"confirmingSignal": "Confirming Signal",
|
|
115
|
+
"rejectingSignal": "Rejecting Signal",
|
|
116
|
+
"route": "Route"
|
|
117
|
+
},
|
|
100
118
|
"finalVerdict": {
|
|
101
|
-
"intro": "This run's final conclusion and next action. **`Direction`** is the recommended next action — one of `continue-investigation`, `begin-implementation`, `approve`, `reject`, or `hold` — and is present for every task-type. **`Verdict Token`** is meaningful only for the `final-verification` task-type, where it is one of `accepted`, `conditional-accept`, or `blocked` and serves as the `release-handoff` entry gate. For every other task-type, `Verdict Token` is always `not-applicable`."
|
|
119
|
+
"intro": "This run's final conclusion and next action. **`Direction`** is the recommended next action — one of `continue-investigation`, `begin-planning`, `begin-implementation`, `approve`, `reject`, or `hold` — and is present for every task-type. `begin-planning` means the diagnosis is ready to enter `implementation-planning`. **`Verdict Token`** is meaningful only for the `final-verification` task-type, where it is one of `accepted`, `conditional-accept`, or `blocked` and serves as the `release-handoff` entry gate. For every other task-type, `Verdict Token` is always `not-applicable`."
|
|
102
120
|
},
|
|
103
121
|
"evidence": {
|
|
104
122
|
"sourceItemsColumnNote": "The `Source items` column is described in §6.1."
|
|
@@ -19,7 +19,8 @@
|
|
|
19
19
|
"lingeringRisks": "- 추적 대상 잔존 위험 없음.",
|
|
20
20
|
"noClarification": "- 추가 정보 요청 없음. Section 7 의 최종 판단이 그대로 유효합니다.",
|
|
21
21
|
"noFollowUp": "- 후속 작업 없음. 본 run 의 다음 phase 는 §3 (Recommended Next Steps) 참고.",
|
|
22
|
-
"endStateCoverage": "이번 phase 에 기록된 종료 상태 처리가 없습니다."
|
|
22
|
+
"endStateCoverage": "이번 phase 에 기록된 종료 상태 처리가 없습니다.",
|
|
23
|
+
"errorAnalysisCauseCandidates": "근거가 뒷받침하는 원인 후보가 아직 없습니다."
|
|
23
24
|
},
|
|
24
25
|
"columns": {
|
|
25
26
|
"recordMeta": "항목",
|
|
@@ -97,8 +98,25 @@
|
|
|
97
98
|
"columnSections": "등장 섹션",
|
|
98
99
|
"columnRelatedIds": "관련 항목 IDs"
|
|
99
100
|
},
|
|
101
|
+
"errorAnalysis": {
|
|
102
|
+
"heading": "오류 분석 결과",
|
|
103
|
+
"symptomVerbatim": "증상 원문",
|
|
104
|
+
"observableFailure": "관측된 실패",
|
|
105
|
+
"reproductionStatus": "재현 상태",
|
|
106
|
+
"reproductionEvidence": "재현 근거",
|
|
107
|
+
"blockedReason": "차단 사유",
|
|
108
|
+
"candidate": "원인 후보",
|
|
109
|
+
"supportingEvidence": "지지 근거",
|
|
110
|
+
"falsifyingEvidence": "반증 확인",
|
|
111
|
+
"confidence": "신뢰도",
|
|
112
|
+
"disproveWith": "반증 방법",
|
|
113
|
+
"nextDiagnostic": "다음 진단",
|
|
114
|
+
"confirmingSignal": "확인 신호",
|
|
115
|
+
"rejectingSignal": "기각 신호",
|
|
116
|
+
"route": "라우팅"
|
|
117
|
+
},
|
|
100
118
|
"finalVerdict": {
|
|
101
|
-
"intro": "이 run 의 최종 결론과 다음 행동입니다. **`Direction`** 은 권장 다음 행동으로 `continue-investigation`(조사 계속) · `begin-implementation`(구현 시작) · `approve`(승인) · `reject`(반려) · `hold`(보류) 중 하나이며, 모든 task-type 에 존재합니다. **`Verdict Token`** 은 `final-verification` task-type 에서만 의미를 가집니다 — `accepted` · `conditional-accept` · `blocked` 중 하나로 `release-handoff` 진입 게이트로 쓰입니다. 그 외 task-type 에서 `Verdict Token` 은 항상 `not-applicable`(해당 없음) 입니다."
|
|
119
|
+
"intro": "이 run 의 최종 결론과 다음 행동입니다. **`Direction`** 은 권장 다음 행동으로 `continue-investigation`(조사 계속) · `begin-planning`(계획 시작) · `begin-implementation`(구현 시작) · `approve`(승인) · `reject`(반려) · `hold`(보류) 중 하나이며, 모든 task-type 에 존재합니다. `begin-planning`은 진단이 `implementation-planning`에 진입할 준비가 됐음을 뜻합니다. **`Verdict Token`** 은 `final-verification` task-type 에서만 의미를 가집니다 — `accepted` · `conditional-accept` · `blocked` 중 하나로 `release-handoff` 진입 게이트로 쓰입니다. 그 외 task-type 에서 `Verdict Token` 은 항상 `not-applicable`(해당 없음) 입니다."
|
|
102
120
|
},
|
|
103
121
|
"evidence": {
|
|
104
122
|
"sourceItemsColumnNote": "`Source items` 열 설명은 §6.1 과 동일합니다."
|
|
@@ -841,6 +841,10 @@ def validate_brief(path: Path, briefs_root: Path) -> list[str]:
|
|
|
841
841
|
|
|
842
842
|
|
|
843
843
|
def find_briefs(root: Path) -> Iterable[Path]:
|
|
844
|
+
if root.is_file():
|
|
845
|
+
if root.suffix == ".md":
|
|
846
|
+
yield root
|
|
847
|
+
return
|
|
844
848
|
yield from root.rglob("*.md")
|
|
845
849
|
|
|
846
850
|
|
|
@@ -849,7 +853,7 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
849
853
|
parser.add_argument(
|
|
850
854
|
"briefs_dir",
|
|
851
855
|
type=Path,
|
|
852
|
-
help="
|
|
856
|
+
help="Brief markdown file or directory containing brief files.",
|
|
853
857
|
)
|
|
854
858
|
parser.add_argument(
|
|
855
859
|
"--briefs-root",
|