okstra 0.34.0 → 0.36.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/README.kr.md +26 -16
- package/README.md +26 -16
- package/docs/kr/architecture.md +59 -45
- package/docs/kr/cli.md +61 -18
- package/docs/pr-template-usage.md +65 -0
- package/docs/project-structure-overview.md +358 -354
- package/docs/superpowers/plans/2026-05-12-ticket-id-in-reports.md +1 -1
- package/docs/superpowers/plans/2026-05-14-convergence-queue-pruning.md +1 -1
- package/docs/superpowers/plans/2026-05-17-dual-format-final-report.md +1 -1
- package/docs/superpowers/plans/2026-05-20-final-report-language.md +1501 -0
- package/docs/superpowers/plans/2026-05-20-implementation-planning-multi-stage.md +1267 -0
- package/docs/superpowers/plans/2026-05-20-okstra-run-prompt-sot-b1.md +1007 -0
- package/docs/superpowers/plans/2026-05-20-wizard-messages-json-sot.md +720 -0
- package/docs/superpowers/plans/2026-05-20-wizard-prompt-json-sot-a1.md +681 -0
- package/docs/superpowers/plans/2026-05-21-improvement-discovery-task-type.md +1691 -0
- package/docs/superpowers/specs/2026-05-20-final-report-language-design.md +383 -0
- package/docs/superpowers/specs/2026-05-20-implementation-planning-multi-stage-design.md +320 -0
- package/docs/superpowers/specs/2026-05-20-okstra-run-prompt-sot-design.md +299 -0
- package/docs/superpowers/specs/2026-05-21-improvement-discovery-task-type-design.md +335 -0
- package/docs/task-process/README.md +74 -0
- package/docs/task-process/common-flow.md +166 -0
- package/docs/task-process/error-analysis.md +101 -0
- package/docs/task-process/final-verification.md +167 -0
- package/docs/task-process/implementation-planning.md +128 -0
- package/docs/task-process/implementation.md +149 -0
- package/docs/task-process/release-handoff.md +206 -0
- package/docs/task-process/requirements-discovery.md +115 -0
- package/package.json +1 -1
- package/runtime/BUILD.json +2 -2
- package/runtime/agents/SKILL.md +29 -13
- package/runtime/agents/workers/claude-worker.md +26 -0
- package/runtime/agents/workers/codex-worker.md +27 -1
- package/runtime/agents/workers/gemini-worker.md +27 -1
- package/runtime/agents/workers/report-writer-worker.md +8 -1
- package/runtime/bin/okstra-central.sh +6 -6
- package/runtime/bin/okstra-codex-exec.sh +49 -28
- package/runtime/bin/okstra-gemini-exec.sh +39 -21
- package/runtime/bin/okstra-render-final-report.py +13 -2
- package/runtime/bin/okstra-wrapper-status.py +155 -0
- package/runtime/bin/okstra.sh +2 -2
- package/runtime/prompts/profiles/_common-contract.md +11 -6
- package/runtime/prompts/profiles/error-analysis.md +3 -7
- package/runtime/prompts/profiles/implementation-planning.md +22 -21
- package/runtime/prompts/profiles/implementation.md +28 -11
- package/runtime/prompts/profiles/improvement-discovery.md +42 -0
- package/runtime/prompts/profiles/kr/_common-contract.md +92 -0
- package/runtime/prompts/profiles/kr/error-analysis.md +36 -0
- package/runtime/prompts/profiles/kr/final-verification.md +48 -0
- package/runtime/prompts/profiles/kr/implementation-planning.md +90 -0
- package/runtime/prompts/profiles/kr/implementation.md +144 -0
- package/runtime/prompts/profiles/kr/improvement-discovery.md +42 -0
- package/runtime/prompts/profiles/kr/release-handoff.md +104 -0
- package/runtime/prompts/profiles/kr/requirements-discovery.md +42 -0
- package/runtime/prompts/profiles/release-handoff.md +1 -1
- package/runtime/prompts/profiles/requirements-discovery.md +8 -12
- package/runtime/prompts/wizard/prompts.ko.json +230 -0
- package/runtime/python/lib/okstra/cli.sh +2 -49
- package/runtime/python/lib/okstra/globals.sh +21 -21
- package/runtime/python/lib/okstra/interactive.sh +7 -7
- package/runtime/python/okstra_ctl/clarification_items.py +3 -9
- package/runtime/python/okstra_ctl/consumers.py +53 -0
- package/runtime/python/okstra_ctl/final_report_schema.py +0 -7
- package/runtime/python/okstra_ctl/i18n.py +73 -0
- package/runtime/python/okstra_ctl/improvement_lenses.py +44 -0
- package/runtime/python/okstra_ctl/index.py +1 -1
- package/runtime/python/okstra_ctl/paths.py +23 -20
- package/runtime/python/okstra_ctl/render.py +147 -202
- package/runtime/python/okstra_ctl/render_final_report.py +53 -10
- package/runtime/python/okstra_ctl/run.py +292 -107
- package/runtime/python/okstra_ctl/run_context.py +22 -0
- package/runtime/python/okstra_ctl/seeding.py +186 -0
- package/runtime/python/okstra_ctl/wizard.py +348 -127
- package/runtime/python/okstra_ctl/workflow.py +21 -2
- package/runtime/python/okstra_ctl/worktree.py +54 -1
- package/runtime/python/okstra_project/resolver.py +4 -3
- package/runtime/python/okstra_token_usage/report.py +2 -2
- package/runtime/schemas/final-report-v1.0.schema.json +22 -16
- package/runtime/skills/okstra-brief/SKILL.md +124 -31
- package/runtime/skills/okstra-convergence/SKILL.md +2 -3
- package/runtime/skills/okstra-report-writer/SKILL.md +35 -15
- package/runtime/skills/okstra-run/SKILL.md +5 -4
- package/runtime/skills/okstra-schedule/SKILL.md +4 -4
- package/runtime/skills/okstra-setup/SKILL.md +27 -0
- package/runtime/skills/okstra-team-contract/SKILL.md +1 -1
- package/runtime/templates/okstra.CLAUDE.md +104 -0
- package/runtime/templates/reports/final-report.template.md +93 -98
- package/runtime/templates/reports/i18n/en.json +135 -0
- package/runtime/templates/reports/i18n/ko.json +135 -0
- package/runtime/templates/reports/implementation-planning-input.template.md +18 -0
- package/runtime/templates/reports/improvement-discovery-input.template.md +78 -0
- package/runtime/templates/reports/task-brief.template.md +2 -2
- package/runtime/validators/lib/fixtures.sh +30 -0
- package/runtime/validators/lib/runners.sh +1 -1
- package/runtime/validators/validate-implementation-plan-stages.py +211 -0
- package/runtime/validators/validate-run.py +121 -26
- package/runtime/validators/validate-workflow.sh +2 -2
- package/runtime/validators/validate_improvement_report.py +275 -0
- package/src/config.mjs +18 -0
- package/src/install.mjs +41 -14
- package/src/setup.mjs +133 -1
- package/src/uninstall.mjs +21 -1
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_meta": {
|
|
3
|
+
"lang": "ko",
|
|
4
|
+
"note": "okstra final-report 고정 문자열 (한국어). 키는 en.json 과 정확히 일치해야 한다. 키를 추가하면 en.json 도 같은 커밋에서 추가한다."
|
|
5
|
+
},
|
|
6
|
+
"emptyState": {
|
|
7
|
+
"consensusItems": "- 합의 항목 없음.",
|
|
8
|
+
"differences": "- 유의미한 차이 없음. 1.1 Consensus 가 그대로 유효합니다.",
|
|
9
|
+
"primaryEvidence": "- 주 증거 없음.",
|
|
10
|
+
"secondaryEvidence": "- 보조 증거 또는 대안 해석 없음.",
|
|
11
|
+
"risks": "- 누락된 정보·위험 없음.",
|
|
12
|
+
"dependencyRisk": "- 의존성·마이그레이션 위험 없음.",
|
|
13
|
+
"dissent": "- 반대 의견 없음.",
|
|
14
|
+
"outOfPlanEdits": "- 계획 외 편집 없음.",
|
|
15
|
+
"declinedFixRecommendations": "- 없음.",
|
|
16
|
+
"discrepancy": "- 없음.",
|
|
17
|
+
"lingeringRisks": "- 추적 대상 잔존 위험 없음.",
|
|
18
|
+
"noClarification": "- 추가 정보 요청 없음. Section 2 의 최종 판단이 그대로 유효합니다.",
|
|
19
|
+
"noFollowUp": "- 후속 작업 없음. 본 run 의 다음 phase 는 §6 (Recommended Next Steps) 참고."
|
|
20
|
+
},
|
|
21
|
+
"columns": {
|
|
22
|
+
"summary": "한 줄 요약",
|
|
23
|
+
"source": "출처 (brief/source/worker)",
|
|
24
|
+
"rawTokens": "처리 토큰",
|
|
25
|
+
"billableTokens": "환산 토큰",
|
|
26
|
+
"billableTokensInputEquiv": "환산 토큰 (input 기준)",
|
|
27
|
+
"cost": "비용 (USD)",
|
|
28
|
+
"checkMethod": "확인 방법"
|
|
29
|
+
},
|
|
30
|
+
"sectionAside": {
|
|
31
|
+
"dependencyRisk": "의존성·마이그레이션 위험",
|
|
32
|
+
"validationChecklist": "검증 체크리스트",
|
|
33
|
+
"rollbackStrategy": "롤백 전략",
|
|
34
|
+
"planBodyVerification": "계획 본문 검증",
|
|
35
|
+
"recommendedOption": "권장 옵션",
|
|
36
|
+
"optionCandidates": "옵션 후보",
|
|
37
|
+
"tradeOffMatrix": "트레이드오프 매트릭스",
|
|
38
|
+
"stepwiseExecutionOrder": "단계별 실행 순서"
|
|
39
|
+
},
|
|
40
|
+
"sectionIntro": {
|
|
41
|
+
"verdictCard": "한눈에 보는 결과 카드. 본 표의 모든 값은 `## 2. Final Verdict` 및 `## 6. Recommended Next Steps` 의 권위 있는 값과 정확히 일치해야 합니다.",
|
|
42
|
+
"clarificationCarryIn": "이전 보고서의 `## 5. Clarification Items` 표 매 행(`C-001`, `C-002`, …) 을 새 증거에 비추어 검토하고, 각 행의 `Status` 를 `resolved` 또는 `obsolete` 로 갱신한 뒤 본 run 의 `## 5.` 표에 carry-in 합니다. 해소 근거(파일:라인 / 로그 / 워커 결과) 를 함께 인용합니다.",
|
|
43
|
+
"ticketCoverage": "3~5 개 row 로 핵심 문제·요구사항·검증 대상을 표로 정리합니다. brief, 소스 자료, worker 결과를 근거로 작성합니다.",
|
|
44
|
+
"executionStatus": "각 worker 의 status, 배정 모델, key finding 을 한 표에 모읍니다. worker 산출물을 근거 없는 주장으로 대체하지 않습니다.",
|
|
45
|
+
"sourceItemsRule": "`Source items` 규칙: 본 합의 row 가 어느 워커의 어느 항목들에서 합성됐는지를 `<worker>:<item-id>` 페어 콤마-리스트로 적습니다. 자세한 정책은 `prompts/profiles/_common-contract.md` \"Cross-worker traceability\" SSOT.",
|
|
46
|
+
"stepRule": "규칙: 한 step 은 약 2~5 분. 모든 step 은 정확한 파일 경로와 명령어 포함.",
|
|
47
|
+
"planBodyVerification": "Phase 6 에서 report-writer 가 합성한 4.5 본문을 lead 가 plan-item 단위로 워커들에게 다시 던지고 평결을 수집한 결과.",
|
|
48
|
+
"clarificationItems": "다음 run 으로 넘어가기 전에 사용자가 답하거나 자료를 첨부해야 하는 항목을 **한 표 안에서** 추적합니다."
|
|
49
|
+
},
|
|
50
|
+
"tokenSummary": {
|
|
51
|
+
"heading": "토큰 사용량 요약",
|
|
52
|
+
"tableHeaderItem": "항목",
|
|
53
|
+
"rowLead": "Lead",
|
|
54
|
+
"rowWorkerTotal": "Worker 합계",
|
|
55
|
+
"rowGrandTotal": "**전체 합계**",
|
|
56
|
+
"rowCliExtra": "Codex/Gemini CLI 추가 비용"
|
|
57
|
+
},
|
|
58
|
+
"verdictCard": {
|
|
59
|
+
"tableHeaderLabel": "항목",
|
|
60
|
+
"tableHeaderValue": "값",
|
|
61
|
+
"approvalRequiredSuffix": "frontmatter `approved` 가 `true` 여야 `implementation` 진입 가능",
|
|
62
|
+
"rationaleLabel": "근거 요약",
|
|
63
|
+
"nextStepLabel": "다음 단계"
|
|
64
|
+
},
|
|
65
|
+
"ticketCoverage": {
|
|
66
|
+
"intro": "본 run 이 다룬 ticket 의 역방향 인덱스. 본문 항목들은 모두 `Ticket ID` 컬럼 또는 `[TICKETID: <id>]` 태그로 ticket 과 묶여 있습니다.",
|
|
67
|
+
"columnSections": "등장 섹션",
|
|
68
|
+
"columnRelatedIds": "관련 항목 IDs",
|
|
69
|
+
"ruleNote": "규칙: `Ticket ID` 는 본문에서 등장한 ticket 키와 정확히 동일 문자열. `Issue / Ticket` 이 비어 폴백된 경우 `Task ID` 값을 prefix 없이 그대로 (예: `8852`). 식별 불가는 `unknown`."
|
|
70
|
+
},
|
|
71
|
+
"finalVerdict": {
|
|
72
|
+
"intro": "최종 결론과 권장 방향을 한 표로 명시합니다. `Direction` ∈ `continue-investigation / begin-implementation / approve / reject / hold`. `task-type` 이 `final-verification` 이면 `Verdict Token` 은 `accepted / conditional-accept / blocked` 중 하나여야 하며, `release-handoff` 는 이 값을 진입 게이트로 사용합니다. 다른 task-type 에서는 `not-applicable`."
|
|
73
|
+
},
|
|
74
|
+
"evidence": {
|
|
75
|
+
"sourceItemsColumnNote": "`Source items` 컬럼 규칙은 §1.1 과 동일."
|
|
76
|
+
},
|
|
77
|
+
"roundHistory": {
|
|
78
|
+
"round2SkippedReasonNote": "값은 `queue-empty | max-rounds-1 | all-reverify-non-result | not-skipped | convergence-disabled | single-analyser-only` 중 하나."
|
|
79
|
+
},
|
|
80
|
+
"implementationPlanning": {
|
|
81
|
+
"optionInterfacesLabel": "영향 인터페이스 / 공개 계약 / 다운스트림 소비자",
|
|
82
|
+
"optionBlastRadiusLabel": "폭발 반경 추정",
|
|
83
|
+
"recommendedTableHeaderLabel": "항목",
|
|
84
|
+
"recommendedTableHeaderValue": "값",
|
|
85
|
+
"coreReasonLabel": "핵심 이유",
|
|
86
|
+
"rationaleLabel": "근거 (Trade-off 행 / 원칙)",
|
|
87
|
+
"rejectedSummaryLabel": "채택되지 않은 옵션 요약",
|
|
88
|
+
"columnImpact": "영향",
|
|
89
|
+
"columnMitigation": "완화 / 선행 작업"
|
|
90
|
+
},
|
|
91
|
+
"releaseHandoff": {
|
|
92
|
+
"auditNote": "git/gh mutating 명령이 실행된 phase 의 감사 기록.",
|
|
93
|
+
"branchStateAside": "run 시작 시점",
|
|
94
|
+
"gitStatusShortLabel": "`git status --short` 출력",
|
|
95
|
+
"existingPrLabel": "기존 PR 존재 여부",
|
|
96
|
+
"userSelectionsAside": "메뉴 응답 기록",
|
|
97
|
+
"questionsTableHeader": {
|
|
98
|
+
"questionId": "질문 ID",
|
|
99
|
+
"questionBody": "질문 본문",
|
|
100
|
+
"userResponse": "사용자 응답 (원문)",
|
|
101
|
+
"allowedOptions": "응답이 가능한 보기"
|
|
102
|
+
},
|
|
103
|
+
"h1Body": "어떤 작업을 실행할까요?",
|
|
104
|
+
"h2Body": "PR base 브랜치 (H1=`push + PR` 인 경우)",
|
|
105
|
+
"h3Body": "PR title/body 초안 처리",
|
|
106
|
+
"h2DefaultLabel": "(n/a)",
|
|
107
|
+
"h2OptionsLabel": "staging / preprod / prod / main / dev / 사용자 입력",
|
|
108
|
+
"noMutationNote": "(mutating 명령 미실행 — H1=`skip` 또는 H3=`cancel`)",
|
|
109
|
+
"commandsTableHeader": {
|
|
110
|
+
"outputSummary": "stdout/stderr 요약"
|
|
111
|
+
}
|
|
112
|
+
},
|
|
113
|
+
"executionMeta": {
|
|
114
|
+
"runExecutorWorktreePath": "본 run 의 `EXECUTOR_WORKTREE_PATH`",
|
|
115
|
+
"runBaseRef": "본 run 의 base ref"
|
|
116
|
+
},
|
|
117
|
+
"evidenceMeta": {
|
|
118
|
+
"commitListSummary": "인용된 commit list / diff summary 요약",
|
|
119
|
+
"targetWorktreePath": "검증 대상 worktree path",
|
|
120
|
+
"capturedHeadBaseSha": "run 시작 시 capture 한 head/base SHA",
|
|
121
|
+
"gitStatusAtRunStart": "`git status --short` (run 시작 시점)"
|
|
122
|
+
},
|
|
123
|
+
"clarification": {
|
|
124
|
+
"fillAndRerun": "답을 채우신 뒤 같은 phase 를 다시 실행:",
|
|
125
|
+
"separateTerminalLabel": "별도 터미널",
|
|
126
|
+
"columnGuide": "컬럼 가이드 (전체 정의는 `prompts/profiles/_common-contract.md §Clarification request policy` SSOT 참조):"
|
|
127
|
+
},
|
|
128
|
+
"followUpTasks": {
|
|
129
|
+
"headingAside": "후속 작업"
|
|
130
|
+
},
|
|
131
|
+
"finalVerification": {
|
|
132
|
+
"validationEvidenceAside": "요구사항 커버리지",
|
|
133
|
+
"columnRequirement": "Requirement (plan/brief 인용)"
|
|
134
|
+
}
|
|
135
|
+
}
|
|
@@ -105,3 +105,21 @@ The final report of an `implementation-planning` run MUST contain every section
|
|
|
105
105
|
|
|
106
106
|
- This input can be used as a planning draft before creating `okstra-task-brief.md`.
|
|
107
107
|
- Reuse the same `Task Group` and `Task ID` if this plan belongs to the same long-lived task.
|
|
108
|
+
|
|
109
|
+
## Stage Output Shape (reference)
|
|
110
|
+
|
|
111
|
+
This run's final report MUST emit `## 4.5 Stage Map` and `## 4.5.<i> Stage <i>` sections per the implementation-planning profile §"Required deliverable shape". Two illustrative shapes:
|
|
112
|
+
|
|
113
|
+
### Shape A — single stage (small work)
|
|
114
|
+
| stage | title | depends-on | step-count | exit-contract-summary |
|
|
115
|
+
|-------|-------|-------|-------|-------|
|
|
116
|
+
| 1 | tiny rename | (none) | 2 | src/foo.ts:renamedFoo |
|
|
117
|
+
|
|
118
|
+
### Shape B — three stages, two parallel
|
|
119
|
+
| stage | title | depends-on | step-count | exit-contract-summary |
|
|
120
|
+
|-------|-------|-------|-------|-------|
|
|
121
|
+
| 1 | foo API skeleton | (none) | 4 | src/foo/api.ts:exportedFoo |
|
|
122
|
+
| 2 | baz settings split | (none) | 2 | src/baz/settings.ts, env BAZ_MODE |
|
|
123
|
+
| 3 | bar integration | 1, 2 | 3 | src/bar/use-foo.ts, GET /bar |
|
|
124
|
+
|
|
125
|
+
Stages 1 and 2 in Shape B are `depends-on (none)` → can be run by two parallel `implementation` runs.
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: OKSTRA Improvement Discovery Input - {{TASK_KEY}}
|
|
3
|
+
id: {{FM_ID}}
|
|
4
|
+
tags: {{FM_TAGS}}
|
|
5
|
+
status: ready-for-agent
|
|
6
|
+
aliases: {{FM_ALIASES}}
|
|
7
|
+
date: {{TASK_DATE}}
|
|
8
|
+
task-id: "{{TASK_ID}}"
|
|
9
|
+
task-group: "{{TASK_GROUP}}"
|
|
10
|
+
project-id: "{{PROJECT_ID}}"
|
|
11
|
+
taskType: "{{FM_TASK_TYPE}}"
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# OKSTRA Improvement Discovery Input
|
|
15
|
+
|
|
16
|
+
## Identity
|
|
17
|
+
|
|
18
|
+
- Project ID:
|
|
19
|
+
- Task Group:
|
|
20
|
+
- Task ID:
|
|
21
|
+
- Related Tasks:
|
|
22
|
+
- Issue / Ticket:
|
|
23
|
+
- 값이 비면 워커는 `Task ID` 로 폴백한다.
|
|
24
|
+
- Task Type: `improvement-discovery`
|
|
25
|
+
- Requested Outcome:
|
|
26
|
+
|
|
27
|
+
## Scope (from brief frontmatter)
|
|
28
|
+
|
|
29
|
+
- scan-scope:
|
|
30
|
+
- out-of-scope:
|
|
31
|
+
- priority-lenses:
|
|
32
|
+
- candidate-cap (1..12, default 8):
|
|
33
|
+
|
|
34
|
+
## Context
|
|
35
|
+
|
|
36
|
+
- Why this scope is being scanned now:
|
|
37
|
+
- Recent change context (last N commits to scan-scope):
|
|
38
|
+
- Stakeholders or owners of the scope:
|
|
39
|
+
|
|
40
|
+
## Desired Outcome
|
|
41
|
+
|
|
42
|
+
- What kinds of improvements do you want surfaced?
|
|
43
|
+
- Anti-goals (improvements you do NOT want this run to propose):
|
|
44
|
+
|
|
45
|
+
## Constraints
|
|
46
|
+
|
|
47
|
+
- Untouchable areas:
|
|
48
|
+
- Compatibility / deadline constraints:
|
|
49
|
+
- Performance / regression budget (if applicable):
|
|
50
|
+
|
|
51
|
+
## Phase 1.5 — Lead Reflect-Back Grilling
|
|
52
|
+
|
|
53
|
+
This section is filled in by the lead during Phase 1.5 before worker dispatch.
|
|
54
|
+
Workers MUST read the resolved values from `runs/improvement-discovery/<seq>/state/phase-1.5-grilling.md`
|
|
55
|
+
rather than the unresolved brief.
|
|
56
|
+
|
|
57
|
+
- Reflect-back summary:
|
|
58
|
+
- Open questions (Q1..QN):
|
|
59
|
+
- Resolved scope:
|
|
60
|
+
- Resolved lenses:
|
|
61
|
+
|
|
62
|
+
## Improvement Candidates (workers populate this)
|
|
63
|
+
|
|
64
|
+
| Cand ID | Lens | Title | Scope | Severity | Effort | Consensus | Source workers | Recommended next-phase | Evidence |
|
|
65
|
+
|---------|------|-------|-------|----------|--------|-----------|----------------|------------------------|----------|
|
|
66
|
+
|
|
67
|
+
## Questions for Analysers
|
|
68
|
+
|
|
69
|
+
1. Within the resolved scope and priority lenses, what are the highest-impact improvement candidates?
|
|
70
|
+
2. Which candidates have full cross-worker consensus, and which are worker-unique?
|
|
71
|
+
3. For each candidate, what is the safest next phase (requirements-discovery / implementation-planning / error-analysis)?
|
|
72
|
+
4. Which candidates would you intentionally exclude despite being technically valid, and why?
|
|
73
|
+
5. Are there any signals that the scope itself is mis-defined (and should be re-narrowed before discovery proceeds)?
|
|
74
|
+
|
|
75
|
+
## Conversion Note
|
|
76
|
+
|
|
77
|
+
- Each candidate the user picks becomes a new okstra task. Suggested task-key: `<task-group>/imp-<Cand-ID>`.
|
|
78
|
+
- The candidate row's Recommended next-phase determines which `--task-type` to launch with.
|
|
@@ -65,7 +65,7 @@ taskType: "{{FM_TASK_TYPE}}"
|
|
|
65
65
|
- What trade-offs, dependencies, or migrations matter?
|
|
66
66
|
- What validation and rollback approach is expected?
|
|
67
67
|
- If `Task Type` is `implementation`:
|
|
68
|
-
- Which approved `implementation-planning` final report authorises this run, and
|
|
68
|
+
- Which approved `implementation-planning` final report authorises this run, and is its frontmatter `approved: true` cited verbatim?
|
|
69
69
|
- What is the authoritative file list and step order copied from that plan?
|
|
70
70
|
- Which validation, TDD, and rollback commands must be executed and recorded with actual output?
|
|
71
71
|
- If `Task Type` is `final-verification`:
|
|
@@ -141,7 +141,7 @@ taskType: "{{FM_TASK_TYPE}}"
|
|
|
141
141
|
- Allowed and forbidden actions for each task type are listed in `Lifecycle Phase Boundaries` of the okstra skill (`agents/SKILL.md`). The lead and every worker stay inside that boundary.
|
|
142
142
|
- "다음 단계 진행해" or any equivalent user phrase is interpreted as "complete the remaining outputs of the current phase," never as "start the next lifecycle phase." The next phase begins only via a fresh okstra invocation with the new `--task-type`.
|
|
143
143
|
- For `implementation-planning` specifically: produce a plan document with the sections listed in `okstra-implementation-planning-input.template.md` `## Required Plan Deliverable`. Do not edit project source code, run builds/migrations/deployments, or write artifacts outside the run's own directories.
|
|
144
|
-
- For `implementation` specifically: edits are bounded by the approved plan's file list (the `--approved-plan` reference). The run MUST refuse to start if the approved plan path is missing or
|
|
144
|
+
- For `implementation` specifically: edits are bounded by the approved plan's file list (the `--approved-plan` reference). The run MUST refuse to start if the approved plan path is missing or its frontmatter `approved` field is not `true`. `git push`, publish, deploy, real migrations, and any third-party write API call remain forbidden; only local `git add`/`git commit` are allowed. Verifier roles stay read-only — they record fix recommendations rather than applying edits — and acceptance verdicts belong to `final-verification`, not this phase.
|
|
145
145
|
|
|
146
146
|
## Available MCP Servers
|
|
147
147
|
|
|
@@ -374,6 +374,36 @@ report_path.write_text("\n".join(report_lines) + "\n")
|
|
|
374
374
|
import os
|
|
375
375
|
WORKSPACE_ROOT = os.environ.get("OKSTRA_WORKSPACE_ROOT_FOR_FIXTURE", "")
|
|
376
376
|
if WORKSPACE_ROOT:
|
|
377
|
+
# Write final-report .data.json SSOT next to the markdown. The validator's
|
|
378
|
+
# validate_final_report_data() reads this via _data_path_for(report_path)
|
|
379
|
+
# and the renderer treats it as the canonical source — the markdown alone
|
|
380
|
+
# is no longer a valid run artifact (dual-format final-report rollout).
|
|
381
|
+
# Sample bundled in tests/fixtures is patched with this run's task identity.
|
|
382
|
+
task_type = str(task_manifest.get("taskType", ""))
|
|
383
|
+
sample_path = (
|
|
384
|
+
Path(WORKSPACE_ROOT)
|
|
385
|
+
/ "tests" / "fixtures" / "final-report-data"
|
|
386
|
+
/ f"{task_type}-001.data.json"
|
|
387
|
+
)
|
|
388
|
+
if sample_path.is_file():
|
|
389
|
+
sample = json.loads(sample_path.read_text(encoding="utf-8"))
|
|
390
|
+
sample["frontmatter"]["taskGroup"] = str(task_manifest.get("taskGroup", ""))
|
|
391
|
+
sample["frontmatter"]["taskId"] = str(task_manifest.get("taskId", ""))
|
|
392
|
+
sample["frontmatter"]["taskType"] = task_type
|
|
393
|
+
sample["frontmatter"]["projectId"] = str(task_manifest.get("projectId", ""))
|
|
394
|
+
sample["header"]["taskKey"] = str(task_manifest.get("taskKey", ""))
|
|
395
|
+
sample["header"]["taskType"] = task_type
|
|
396
|
+
name = report_path.name
|
|
397
|
+
data_path = (
|
|
398
|
+
report_path.with_name(name[:-3] + ".data.json")
|
|
399
|
+
if name.endswith(".md")
|
|
400
|
+
else report_path.with_suffix(".data.json")
|
|
401
|
+
)
|
|
402
|
+
data_path.write_text(
|
|
403
|
+
json.dumps(sample, indent=2, ensure_ascii=False) + "\n",
|
|
404
|
+
encoding="utf-8",
|
|
405
|
+
)
|
|
406
|
+
|
|
377
407
|
import sys as _sys
|
|
378
408
|
_sys.path.insert(0, str(Path(WORKSPACE_ROOT) / "scripts"))
|
|
379
409
|
try:
|
|
@@ -41,7 +41,7 @@ run_validator_expectation() {
|
|
|
41
41
|
|
|
42
42
|
expected_task_manifest_relative_path="$(task_manifest_relative_path "$task_group" "$task_id")"
|
|
43
43
|
|
|
44
|
-
python3 - "$PROJECT_ROOT" "$expected_task_manifest_relative_path" "$
|
|
44
|
+
python3 - "$PROJECT_ROOT" "$expected_task_manifest_relative_path" "$RUN_VALIDATOR_PATH" "$expected_status" "$expected_failure_substring" <<'PY'
|
|
45
45
|
from pathlib import Path
|
|
46
46
|
import json
|
|
47
47
|
import subprocess
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""S1–S8 checks for the Stage Map structure of an approved
|
|
3
|
+
implementation-planning final-report.md. Run from prepare_task_bundle
|
|
4
|
+
of `implementation` task or standalone."""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import argparse
|
|
9
|
+
import re
|
|
10
|
+
import sys
|
|
11
|
+
from dataclasses import dataclass
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import List, Tuple
|
|
14
|
+
|
|
15
|
+
STAGE_MAP_HEADING = re.compile(r"^##\s+4\.5\s+Stage\s+Map\b", re.M)
|
|
16
|
+
STAGE_SECTION = re.compile(
|
|
17
|
+
r"^##\s+4\.5\.(\d+)\s+Stage\s+\1\s*:\s*(.+)$", re.M
|
|
18
|
+
)
|
|
19
|
+
REQUIRED_SUBSECTIONS = (
|
|
20
|
+
"Carry-In",
|
|
21
|
+
"Stepwise Execution Order",
|
|
22
|
+
"Stage Exit Contract",
|
|
23
|
+
"Stage Validation",
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass
|
|
28
|
+
class StageMeta:
|
|
29
|
+
stage_number: int
|
|
30
|
+
title: str
|
|
31
|
+
depends_on: List[int]
|
|
32
|
+
step_count: int
|
|
33
|
+
exit_contract_summary: str
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclass
|
|
37
|
+
class ValidationError:
|
|
38
|
+
code: str # S1..S8
|
|
39
|
+
stage: int # 0 = global
|
|
40
|
+
message: str
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _check_stage_map_present(text: str) -> List[ValidationError]:
|
|
44
|
+
if not STAGE_MAP_HEADING.search(text):
|
|
45
|
+
return [ValidationError("S1", 0,
|
|
46
|
+
"section '## 4.5 Stage Map' is missing")]
|
|
47
|
+
return []
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _parse_stage_map(text: str) -> Tuple[List[StageMeta], List[ValidationError]]:
|
|
51
|
+
m = STAGE_MAP_HEADING.search(text)
|
|
52
|
+
if not m:
|
|
53
|
+
return [], [] # S1 already reported
|
|
54
|
+
body = text[m.end():]
|
|
55
|
+
rows = []
|
|
56
|
+
for line in body.splitlines():
|
|
57
|
+
if line.startswith("##"):
|
|
58
|
+
break
|
|
59
|
+
if not line.strip().startswith("|"):
|
|
60
|
+
continue
|
|
61
|
+
cells = [c.strip() for c in line.strip().strip("|").split("|")]
|
|
62
|
+
if len(cells) != 5:
|
|
63
|
+
continue
|
|
64
|
+
# skip header and separator rows (all-dash of any length is covered by set check)
|
|
65
|
+
if cells[0] == "stage" or set(cells[0]) <= set("-"):
|
|
66
|
+
continue
|
|
67
|
+
try:
|
|
68
|
+
n = int(cells[0])
|
|
69
|
+
except ValueError:
|
|
70
|
+
continue
|
|
71
|
+
depends_raw = cells[2].strip()
|
|
72
|
+
depends = [] if depends_raw in ("(none)", "") else [
|
|
73
|
+
int(x.strip()) for x in depends_raw.split(",") if x.strip()
|
|
74
|
+
]
|
|
75
|
+
try:
|
|
76
|
+
step_count = int(cells[3])
|
|
77
|
+
except ValueError:
|
|
78
|
+
step_count = -1
|
|
79
|
+
rows.append(StageMeta(n, cells[1], depends, step_count, cells[4]))
|
|
80
|
+
errors: List[ValidationError] = []
|
|
81
|
+
for i, r in enumerate(rows, start=1):
|
|
82
|
+
if r.stage_number != i:
|
|
83
|
+
errors.append(ValidationError("S2", r.stage_number,
|
|
84
|
+
f"stage numbers must be 1..N monotonic, got {r.stage_number} at row {i}"))
|
|
85
|
+
return rows, errors
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _count_effective_steps(section: str) -> int:
|
|
89
|
+
m = re.search(r"^###\s+Stepwise Execution Order\b", section, re.M)
|
|
90
|
+
if not m:
|
|
91
|
+
return 0
|
|
92
|
+
body = section[m.end():]
|
|
93
|
+
nxt = re.search(r"^###\s+\w", body, re.M)
|
|
94
|
+
if nxt:
|
|
95
|
+
body = body[: nxt.start()]
|
|
96
|
+
count = 0
|
|
97
|
+
for line in body.splitlines():
|
|
98
|
+
s = line.strip()
|
|
99
|
+
if not s or s.startswith("<!--"):
|
|
100
|
+
continue
|
|
101
|
+
if not s.startswith("|"):
|
|
102
|
+
continue
|
|
103
|
+
# Reuse the same header/divider detection as _parse_stage_map:
|
|
104
|
+
# split on `|`, inspect first non-empty cell.
|
|
105
|
+
first_cell = s.strip("|").split("|")[0].strip()
|
|
106
|
+
if first_cell.lower() == "step":
|
|
107
|
+
continue
|
|
108
|
+
if set(first_cell) <= set("-: "):
|
|
109
|
+
continue
|
|
110
|
+
count += 1
|
|
111
|
+
return count
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def _check_each_stage_section(text: str, stages: List[StageMeta]) -> List[ValidationError]:
|
|
115
|
+
errs: List[ValidationError] = []
|
|
116
|
+
for s in stages:
|
|
117
|
+
pattern = rf"^##\s+4\.5\.{s.stage_number}\s+Stage\s+{s.stage_number}\s*:"
|
|
118
|
+
start_m = re.search(pattern, text, re.M)
|
|
119
|
+
if not start_m:
|
|
120
|
+
errs.append(ValidationError("S3", s.stage_number,
|
|
121
|
+
f"stage section '## 4.5.{s.stage_number} Stage {s.stage_number}:' missing"))
|
|
122
|
+
continue
|
|
123
|
+
# Slice the stage's section body
|
|
124
|
+
start = start_m.end()
|
|
125
|
+
nxt = re.search(
|
|
126
|
+
rf"^##\s+4\.5\.{s.stage_number + 1}\s+Stage\s+",
|
|
127
|
+
text[start:], re.M,
|
|
128
|
+
)
|
|
129
|
+
section = text[start: start + nxt.start()] if nxt else text[start:]
|
|
130
|
+
|
|
131
|
+
for sub in REQUIRED_SUBSECTIONS:
|
|
132
|
+
if not re.search(rf"^###\s+{re.escape(sub)}\b", section, re.M):
|
|
133
|
+
errs.append(ValidationError("S4", s.stage_number,
|
|
134
|
+
f"required subsection '### {sub}' missing"))
|
|
135
|
+
|
|
136
|
+
# S5: effective step count
|
|
137
|
+
steps = _count_effective_steps(section)
|
|
138
|
+
if steps > 6:
|
|
139
|
+
errs.append(ValidationError("S5", s.stage_number,
|
|
140
|
+
f"effective step count {steps} exceeds 6"))
|
|
141
|
+
|
|
142
|
+
# S7: step-count cell vs. real count
|
|
143
|
+
if s.step_count >= 0 and s.step_count != steps:
|
|
144
|
+
errs.append(ValidationError("S7", s.stage_number,
|
|
145
|
+
f"Stage Map step-count={s.step_count} but real count={steps}"))
|
|
146
|
+
return errs
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def _check_depends_on(stages: List[StageMeta]) -> List[ValidationError]:
|
|
150
|
+
errs: List[ValidationError] = []
|
|
151
|
+
valid = {s.stage_number for s in stages}
|
|
152
|
+
for s in stages:
|
|
153
|
+
for d in s.depends_on:
|
|
154
|
+
if d == s.stage_number:
|
|
155
|
+
errs.append(ValidationError("S8", s.stage_number, "self depends-on"))
|
|
156
|
+
elif d not in valid:
|
|
157
|
+
errs.append(ValidationError("S6", s.stage_number,
|
|
158
|
+
f"depends-on {d} does not exist"))
|
|
159
|
+
|
|
160
|
+
# DAG cycle detection via Kahn's algorithm
|
|
161
|
+
# Build a graph only from valid edges (S6 errors already reported above)
|
|
162
|
+
indeg = {s.stage_number: 0 for s in stages}
|
|
163
|
+
graph: dict[int, list[int]] = {s.stage_number: [] for s in stages}
|
|
164
|
+
for s in stages:
|
|
165
|
+
for d in s.depends_on:
|
|
166
|
+
if d in graph and d != s.stage_number:
|
|
167
|
+
graph[d].append(s.stage_number)
|
|
168
|
+
indeg[s.stage_number] += 1
|
|
169
|
+
|
|
170
|
+
queue = [n for n, k in indeg.items() if k == 0]
|
|
171
|
+
visited = 0
|
|
172
|
+
while queue:
|
|
173
|
+
n = queue.pop()
|
|
174
|
+
visited += 1
|
|
175
|
+
for m in graph[n]:
|
|
176
|
+
indeg[m] -= 1
|
|
177
|
+
if indeg[m] == 0:
|
|
178
|
+
queue.append(m)
|
|
179
|
+
if visited != len(stages):
|
|
180
|
+
errs.append(ValidationError("S8", 0, "depends-on graph has a cycle"))
|
|
181
|
+
return errs
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def main(argv: List[str]) -> int:
|
|
185
|
+
p = argparse.ArgumentParser()
|
|
186
|
+
p.add_argument("--plan", required=True)
|
|
187
|
+
args = p.parse_args(argv)
|
|
188
|
+
text = Path(args.plan).read_text(encoding="utf-8")
|
|
189
|
+
|
|
190
|
+
errors: List[ValidationError] = []
|
|
191
|
+
errors.extend(_check_stage_map_present(text))
|
|
192
|
+
if errors:
|
|
193
|
+
for e in errors:
|
|
194
|
+
print(f"{e.code} stage={e.stage}: {e.message}", file=sys.stderr)
|
|
195
|
+
return 1
|
|
196
|
+
|
|
197
|
+
stages, s2_errs = _parse_stage_map(text)
|
|
198
|
+
errors.extend(s2_errs)
|
|
199
|
+
if stages:
|
|
200
|
+
errors.extend(_check_each_stage_section(text, stages))
|
|
201
|
+
errors.extend(_check_depends_on(stages))
|
|
202
|
+
|
|
203
|
+
if errors:
|
|
204
|
+
for e in errors:
|
|
205
|
+
print(f"{e.code} stage={e.stage}: {e.message}", file=sys.stderr)
|
|
206
|
+
return 1
|
|
207
|
+
return 0
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
if __name__ == "__main__":
|
|
211
|
+
sys.exit(main(sys.argv[1:]))
|