okstra 0.34.1 → 0.36.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.kr.md +27 -19
- package/README.md +27 -19
- 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 +353 -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/plans/2026-05-24-implementation-lead-context-slimming.md +1700 -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 +30 -7
- package/runtime/agents/workers/claude-worker.md +31 -6
- package/runtime/agents/workers/codex-worker.md +37 -10
- package/runtime/agents/workers/gemini-worker.md +34 -7
- package/runtime/agents/workers/report-writer-worker.md +19 -10
- 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/launch.template.md +1 -0
- package/runtime/prompts/profiles/_common-contract.md +11 -6
- package/runtime/prompts/profiles/_implementation-deliverable.md +53 -0
- package/runtime/prompts/profiles/_implementation-executor.md +60 -0
- package/runtime/prompts/profiles/_implementation-verifier.md +76 -0
- 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 -118
- package/runtime/prompts/profiles/improvement-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 +26 -20
- package/runtime/python/okstra_ctl/render.py +166 -207
- package/runtime/python/okstra_ctl/render_final_report.py +53 -10
- package/runtime/python/okstra_ctl/run.py +299 -108
- package/runtime/python/okstra_ctl/run_context.py +22 -0
- package/runtime/python/okstra_ctl/seeding.py +186 -0
- package/runtime/python/okstra_ctl/session.py +65 -7
- 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 +102 -218
- package/runtime/skills/okstra-convergence/SKILL.md +2 -3
- package/runtime/skills/okstra-inspect/SKILL.md +581 -0
- package/runtime/skills/okstra-report-writer/SKILL.md +35 -15
- package/runtime/skills/okstra-run/SKILL.md +8 -7
- package/runtime/skills/okstra-schedule/SKILL.md +14 -157
- package/runtime/skills/okstra-setup/SKILL.md +28 -1
- package/runtime/skills/okstra-team-contract/SKILL.md +16 -107
- package/runtime/templates/okstra.CLAUDE.md +104 -0
- package/runtime/templates/reports/brief.template.md +204 -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/schedule.template.md +12 -3
- package/runtime/templates/reports/task-brief.template.md +2 -2
- package/runtime/templates/worker-prompt-preamble.md +108 -0
- 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 +27 -3
- package/runtime/skills/okstra-history/SKILL.md +0 -165
- package/runtime/skills/okstra-logs/SKILL.md +0 -173
- package/runtime/skills/okstra-report-finder/SKILL.md +0 -111
- package/runtime/skills/okstra-status/SKILL.md +0 -246
- package/runtime/skills/okstra-time-summary/SKILL.md +0 -172
|
@@ -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.
|
|
@@ -55,14 +55,23 @@ taskType: "{{FM_TASK_TYPE}}"
|
|
|
55
55
|
## Task Dependency Graph
|
|
56
56
|
|
|
57
57
|
<!-- Use ONE of:
|
|
58
|
-
(A) literal `_의존 정보 없음_` when no edges exist
|
|
59
|
-
(B) plain ``` fenced adjacency-list block
|
|
58
|
+
(A) literal `_의존 정보 없음_` when no edges exist (single-task scope or no extracted edges).
|
|
59
|
+
(B) plain ``` fenced adjacency-list block. Each non-empty line is one of:
|
|
60
|
+
- adjacency: `<TASK-ID> -> <TASK-ID>[, <TASK-ID>]*`
|
|
61
|
+
- comment: `# <free prose>` (max one per group)
|
|
62
|
+
- blank: group separator
|
|
60
63
|
Example (B):
|
|
61
64
|
```
|
|
62
65
|
DEV-1 -> DEV-2, DEV-3
|
|
63
66
|
DEV-2 -> DEV-4
|
|
67
|
+
# Phase 3 fan-out
|
|
68
|
+
DEV-5 -> DEV-6
|
|
64
69
|
```
|
|
65
|
-
|
|
70
|
+
Rules:
|
|
71
|
+
- ASCII arrow `->` only; Unicode `→` is rejected so downstream tooling does not handle both.
|
|
72
|
+
- Both endpoints MUST be `TASK-ID` literals (same identifiers as the At a Glance table). Free text is forbidden.
|
|
73
|
+
- Fence MUST be plain ``` with NO language tag. ```mermaid / ```plantuml / ```graphviz / ```dot are all rejected.
|
|
74
|
+
- If only one task is in scope (no dependencies), use Shape A — do NOT emit an empty fence. -->
|
|
66
75
|
_의존 정보 없음_
|
|
67
76
|
|
|
68
77
|
---
|
|
@@ -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
|
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# Worker Prompt Preamble (canonical)
|
|
2
|
+
|
|
3
|
+
This file is the single source of truth for the boilerplate that every okstra analysis worker, verifier, and report-writer worker must honour. The lead injects ONE anchor line into each dispatched worker prompt — `**Worker Preamble Path:** <abs-path>` — pointing here. Workers Read this file end-to-end before producing any output.
|
|
4
|
+
|
|
5
|
+
It replaces the previous practice of inlining ~80 lines of identical boilerplate into every worker prompt body.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Required reading (analysis workers + report-writer worker)
|
|
10
|
+
|
|
11
|
+
You are required to read every input file enumerated by the dispatcher (the lead's prompt lists them under `[Required reading]`) from the very first character to the very last character before you produce any analysis output. Skimming, partial reads, jumping to a single section, or relying on prior knowledge of a similar file's structure is not acceptable. Each file may contain decisive context that is not surfaced in its summary or first page.
|
|
12
|
+
|
|
13
|
+
### Audience-scoped enumeration (BLOCKING — performance optimization)
|
|
14
|
+
|
|
15
|
+
Different recipients need different files. Do NOT include `final-report-template.md` in analysis worker prompts: analysis workers produce findings (not the final report), and forcing them to read the template inflates token usage without changing finding quality.
|
|
16
|
+
|
|
17
|
+
| Recipient | Files included in `[Required reading]` |
|
|
18
|
+
|---|---|
|
|
19
|
+
| Claude / Codex / Gemini analysis workers | task-brief, analysis-profile, analysis-material (if present), reference-expectations, clarification-response (if carry-in) |
|
|
20
|
+
| Report writer worker (Phase 6) | all of the above **plus** `final-report-template.md` |
|
|
21
|
+
| Reverify dispatches (Phase 5.5, lightweight mode) | **do NOT inject `[Required reading]` at all** — see [okstra-convergence](../skills/okstra-convergence/SKILL.md) "Reverify prompt: required-reading suppression". |
|
|
22
|
+
|
|
23
|
+
### Reading rules
|
|
24
|
+
|
|
25
|
+
- Use a single `Read` tool call per file with no `offset` and no `limit`. If a file is genuinely too large for one read, page through with explicit `offset` / `limit` covering the entire file, and state the page boundaries in your Findings.
|
|
26
|
+
- For the carry-in clarification response, walk every row of `## 5. Clarification Items` (`C-001`, `C-002`, ...) in full, including rows whose `User input` cell is blank — a blank `User input` with `Status=open` is itself a signal you must surface. The structural similarity between the prior final report and the upcoming output is NOT a license to skim.
|
|
27
|
+
- Write the Reading Confirmation block to your **audit sidecar** at `runs/<task-type>/worker-results/<worker>-audit-<task-type>-<seq>.md` (sibling to the main worker-results file). One short line per input file confirming end-to-end reading. Do NOT include a `## 0. Reading Confirmation` heading in the main worker-results file — the validator fails worker-results that contain one. If you cannot truthfully confirm a file end-to-end, record a `tool-failure` in the errors sidecar instead of fabricating Findings.
|
|
28
|
+
- Do not collapse multiple input files into a single mental summary before reading them all individually. Each file has its own canonical role: brief = the user's request, profile = the lead's rules for this phase, reference-expectations = ground-truth config/deployment values, clarification-response = prior run's open questions and the user's answers, final-report-template = the structure your eventual writeup must conform to. Conflating them loses signal.
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## Error reporting (all workers)
|
|
33
|
+
|
|
34
|
+
If any tool call you make (Bash, Read, Edit, MCP, etc.) returns a non-zero exit code, raises an exception, or otherwise fails its intended effect, append a single entry to your worker errors sidecar at the absolute path the lead provided as `**Errors sidecar path:** <abs-path>`.
|
|
35
|
+
|
|
36
|
+
If the sidecar file does not exist, create it with `{"schemaVersion": 1, "errors": []}` then append.
|
|
37
|
+
|
|
38
|
+
### Entry schema
|
|
39
|
+
|
|
40
|
+
```json
|
|
41
|
+
{
|
|
42
|
+
"ts": "<ISO 8601 UTC>",
|
|
43
|
+
"phase": "<current okstra phase>",
|
|
44
|
+
"errorType": "tool-failure",
|
|
45
|
+
"command": "<failed command/tool signature>",
|
|
46
|
+
"commandKind": "bash | tool:Read | tool:Edit | mcp | ...",
|
|
47
|
+
"exitCode": <int or null>,
|
|
48
|
+
"durationMs": <int or null>,
|
|
49
|
+
"message": "<one-line human summary>",
|
|
50
|
+
"stderrExcerpt": "<first ~2KB of stderr, or null>",
|
|
51
|
+
"context": { ... or null }
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### Rules
|
|
56
|
+
|
|
57
|
+
- Do NOT include `source` / `recordedAt` / `agent` / `agentRole` / `model` / `taskKey` — the lead fills those in when dumping the sidecar to the run-level errors log via `okstra-error-log.py append-from-worker`.
|
|
58
|
+
- Do NOT use `errorType` values other than `"tool-failure"` in the sidecar. `cli-failure` events are recorded by CLI wrapper agents (codex / gemini) directly to the run-level errors log, not via the sidecar. `contract-violation` events are recorded by the lead after inspecting worker outputs.
|
|
59
|
+
- Continue your task after recording; do not abort unless the failure makes the task impossible.
|
|
60
|
+
|
|
61
|
+
### Path extraction (BLOCKING)
|
|
62
|
+
|
|
63
|
+
Before recording anything, extract the absolute paths verbatim from the lead's dispatch prompt body:
|
|
64
|
+
|
|
65
|
+
- `**Errors log path:** <abs-path>` — the run-level errors JSONL. Used by CLI wrapper agents via `okstra-error-log.py append-observed` for `cli-failure` events.
|
|
66
|
+
- `**Errors sidecar path:** <abs-path>` — this worker's per-run sidecar JSON. Used by all workers for `tool-failure` events.
|
|
67
|
+
|
|
68
|
+
If a required header line is absent from the dispatch prompt, return `<SENTINEL_PREFIX>_ERRORS_PATH_MISSING: lead prompt did not include **Errors log path:** / **Errors sidecar path:** headers` (substitute the worker's sentinel prefix — `CLAUDE_WORKER`, `CODEX`, `GEMINI`, or `REPORT_WRITER`) without proceeding. Do NOT synthesize the path from `<runDir>/logs/...` — that template syntax is documentation only; historical failure mode produced silently empty run-level error logs.
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## Anchor headers (lead-injected, BLOCKING)
|
|
73
|
+
|
|
74
|
+
Every worker prompt MUST begin with these anchor headers, in this exact order, before any other content. Workers extract them verbatim.
|
|
75
|
+
|
|
76
|
+
1. `**Project Root:** <absolute-path>` — required so the worker can self-anchor without relying on inherited cwd.
|
|
77
|
+
2. `**Prompt History Path:** <project-relative-path>`
|
|
78
|
+
3. `**Result Path:** <project-relative-path>` — canonical destination for the worker's result file.
|
|
79
|
+
4. `Assigned worker prompt history path: <absolute-path>` — same as the prompt-history path but resolved against `Project Root`. Codex / Gemini wrapper subagents extract this exact line.
|
|
80
|
+
5. `**Worker Preamble Path:** <absolute-path>` — points to THIS file. Workers Read it end-to-end before doing anything else.
|
|
81
|
+
6. `**Errors log path:** <absolute-path>` — run-level JSONL (see Error reporting above).
|
|
82
|
+
7. `**Errors sidecar path:** <absolute-path>` — per-worker JSON (see Error reporting above).
|
|
83
|
+
|
|
84
|
+
For the **implementation phase** specifically, the dispatched prompt MUST also include:
|
|
85
|
+
|
|
86
|
+
- `**Worktree:** <absolute-path>` — the task worktree path.
|
|
87
|
+
- `cwd for every mutating command: <absolute-path>` — same as Worktree path; used by codex / gemini wrappers as `--add-dir` / `--include-directories`.
|
|
88
|
+
|
|
89
|
+
When a worker reads any project-relative path from the prompt, it MUST resolve it against `Project Root` (e.g. `<Project Root>/<Result Path>`) — never use bare relative paths that depend on cwd.
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## Worker output sections (analysis workers)
|
|
94
|
+
|
|
95
|
+
Every analysis worker (Claude / Codex / Gemini) produces sections 1–5 with the same dimensions. The lead does NOT bias worker prompts with per-worker emphasis — sections 1–5 are the common core, and specialization lives only in optional Section 6.
|
|
96
|
+
|
|
97
|
+
1. **Findings** — what you identified
|
|
98
|
+
2. **Missing Information or Assumptions** — gaps in the analysis
|
|
99
|
+
3. **Safe or Reasonable Areas** — parts that look correct
|
|
100
|
+
4. **Uncertain Points** — areas needing further review
|
|
101
|
+
5. **Recommended Next Actions** — suggested follow-ups
|
|
102
|
+
6. **Specialization Lens (optional)** — additive content from your specialization lens. Items here are NOT subject to convergence cross-verification.
|
|
103
|
+
|
|
104
|
+
Every item in sections 1–5 (and any section 6) MUST carry a worker-internal item ID unique within the file. Lead synthesis preserves these as `<worker>:<item-id>` in `Source items (worker:item)` columns.
|
|
105
|
+
|
|
106
|
+
For task types `requirements-discovery`, `error-analysis`, `implementation-planning`, or `implementation`, every item in sections 1–5 MUST carry a ticket identifier. Use the `Ticket ID` column in table-form items and the `[TICKETID: <id>]` prefix in bullet/numbered items.
|
|
107
|
+
|
|
108
|
+
See `skills/okstra-team-contract/SKILL.md` "Worker Output Contract" for the full frontmatter schema and section ordering rules. This preamble is consistent with that contract; the team-contract document is authoritative if the two ever diverge.
|
|
@@ -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:]))
|