okstra 0.206.0 → 0.206.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.md +2 -2
- package/dist/commands/lifecycle/install.mjs +1 -1
- package/dist/commands/lifecycle/install.mjs.map +1 -1
- package/docs/architecture.md +12 -12
- package/docs/cli.md +4 -4
- package/docs/contributor-change-matrix.md +3 -2
- package/docs/performance-improvement-plan-v2.md +1 -1
- package/docs/project-structure-overview.md +39 -18
- package/package.json +1 -1
- package/runtime/BUILD.json +2 -2
- package/runtime/bin/okstra-spawn-followups.py +2 -2
- package/runtime/prompts/launch.template.md +1 -1
- package/runtime/prompts/lead/context-loader.md +1 -1
- package/runtime/prompts/lead/convergence.md +3 -3
- package/runtime/prompts/lead/okstra-lead-contract.md +13 -54
- package/runtime/prompts/lead/phase-routing.md +64 -0
- package/runtime/prompts/lead/report-writer.md +2 -2
- package/runtime/prompts/lead/team-contract.md +1 -1
- package/runtime/prompts/profiles/_coding-conventions-preflight.md +1 -1
- package/runtime/prompts/profiles/_common-contract.md +1 -1
- package/runtime/prompts/profiles/_coverage-critic.md +1 -1
- package/runtime/prompts/profiles/forbidden-actions.json +0 -94
- package/runtime/python/okstra_ctl/agent/prompt_cli/corrections.py +1 -1
- package/runtime/python/okstra_ctl/agent/prompt_cli/materialize.py +10 -1
- package/runtime/python/okstra_ctl/analysis_inputs.py +0 -39
- package/runtime/python/okstra_ctl/analysis_scope.py +31 -0
- package/runtime/python/okstra_ctl/asset_roots.py +19 -0
- package/runtime/python/okstra_ctl/consumers.py +12 -0
- package/runtime/python/okstra_ctl/dispatch_state.py +22 -0
- package/runtime/python/okstra_ctl/doctor.py +2 -1
- package/runtime/python/okstra_ctl/execution_mutation_audit.py +27 -1
- package/runtime/python/okstra_ctl/handoff.py +11 -466
- package/runtime/python/okstra_ctl/handoff_error.py +5 -0
- package/runtime/python/okstra_ctl/implementation_direction.py +0 -477
- package/runtime/python/okstra_ctl/initial_prompt_materialization.py +8 -1
- package/runtime/python/okstra_ctl/option_comparison.py +3 -165
- package/runtime/python/okstra_ctl/option_votes.py +3 -191
- package/runtime/python/okstra_ctl/paths.py +8 -6
- package/runtime/python/okstra_ctl/phases/catalog.py +56 -12
- package/runtime/python/okstra_ctl/phases/change_impact_analysis/boundary.json +11 -0
- package/runtime/python/okstra_ctl/phases/change_impact_analysis/entry.py +39 -0
- package/runtime/python/okstra_ctl/{report_html/view_models/change_impact_analysis.py → phases/change_impact_analysis/report.py} +3 -3
- package/runtime/python/okstra_ctl/phases/change_impact_analysis/spec.md +26 -0
- package/runtime/python/okstra_ctl/phases/change_impact_analysis/validation.py +23 -0
- package/runtime/python/okstra_ctl/phases/error_analysis/__init__.py +1 -0
- package/runtime/python/okstra_ctl/phases/error_analysis/boundary.json +9 -0
- package/runtime/{prompts/profiles/error-analysis.md → python/okstra_ctl/phases/error_analysis/profile.md} +2 -2
- package/runtime/python/okstra_ctl/{report_html/view_models/error_analysis.py → phases/error_analysis/report.py} +9 -8
- package/runtime/{templates/reports → python/okstra_ctl/phases/error_analysis/report_assets}/error-analysis-input.template.md +1 -1
- package/runtime/python/okstra_ctl/phases/error_analysis/spec.md +118 -0
- package/runtime/python/okstra_ctl/phases/error_analysis/validation.py +241 -0
- package/runtime/python/okstra_ctl/phases/feature_analysis/__init__.py +1 -0
- package/runtime/python/okstra_ctl/phases/feature_analysis/boundary.json +8 -0
- package/runtime/python/okstra_ctl/phases/feature_analysis/entry.py +63 -0
- package/runtime/python/okstra_ctl/{report_html/view_models/feature_analysis.py → phases/feature_analysis/report.py} +12 -5
- package/runtime/python/okstra_ctl/phases/feature_analysis/spec.md +22 -0
- package/runtime/python/okstra_ctl/phases/feature_analysis/validation.py +27 -0
- package/runtime/python/okstra_ctl/phases/feature_analysis/wizard.py +95 -0
- package/runtime/python/okstra_ctl/phases/final_verification/boundary.json +8 -0
- package/runtime/python/okstra_ctl/phases/final_verification/profile.md +1 -1
- package/runtime/{templates/reports → python/okstra_ctl/phases/final_verification/report_assets}/final-verification-input.template.md +1 -1
- package/runtime/python/okstra_ctl/phases/final_verification/spec.md +1 -1
- package/runtime/python/okstra_ctl/phases/implementation/__init__.py +1 -0
- package/runtime/python/okstra_ctl/phases/implementation/boundary.json +17 -0
- package/runtime/python/okstra_ctl/{implementation_stage.py → phases/implementation/entry.py} +22 -10
- package/runtime/{prompts/host-orchestration/implementation.md → python/okstra_ctl/phases/implementation/host-rules.md} +1 -1
- package/runtime/{prompts/profiles → python/okstra_ctl/phases/implementation/instructions}/_implementation-deliverable.md +1 -1
- package/runtime/{prompts/profiles → python/okstra_ctl/phases/implementation/instructions}/_implementation-executor.md +4 -3
- package/runtime/{prompts/profiles → python/okstra_ctl/phases/implementation/instructions}/_implementation-verifier.md +4 -4
- package/runtime/{prompts/profiles/implementation.md → python/okstra_ctl/phases/implementation/profile.md} +5 -5
- package/runtime/python/okstra_ctl/{report_html/view_models/implementation.py → phases/implementation/report.py} +3 -3
- package/runtime/{templates/reports → python/okstra_ctl/phases/implementation/report_assets}/implementation-input.template.md +1 -1
- package/runtime/python/okstra_ctl/phases/implementation/spec.md +238 -0
- package/runtime/python/okstra_ctl/phases/implementation/validation.py +205 -0
- package/runtime/python/okstra_ctl/phases/implementation/wizard.py +39 -0
- package/runtime/python/okstra_ctl/phases/implementation_option_selection/__init__.py +1 -0
- package/runtime/python/okstra_ctl/phases/implementation_option_selection/authoring.py +80 -0
- package/runtime/python/okstra_ctl/phases/implementation_option_selection/boundary.json +10 -0
- package/runtime/python/okstra_ctl/phases/implementation_option_selection/comparison.py +168 -0
- package/runtime/python/okstra_ctl/phases/implementation_option_selection/entry.py +27 -0
- package/runtime/{prompts/profiles/implementation-option-selection.md → python/okstra_ctl/phases/implementation_option_selection/profile.md} +2 -2
- package/runtime/python/okstra_ctl/{report_html/view_models/implementation_option_selection.py → phases/implementation_option_selection/report.py} +2 -2
- package/runtime/python/okstra_ctl/phases/implementation_option_selection/spec.md +83 -0
- package/runtime/python/okstra_ctl/{implementation_options.py → phases/implementation_option_selection/validation.py} +3 -3
- package/runtime/python/okstra_ctl/phases/implementation_option_selection/votes.py +194 -0
- package/runtime/python/okstra_ctl/phases/implementation_planning/__init__.py +1 -0
- package/runtime/python/okstra_ctl/phases/implementation_planning/authoring.py +2345 -0
- package/runtime/python/okstra_ctl/phases/implementation_planning/boundary.json +12 -0
- package/runtime/python/okstra_ctl/phases/implementation_planning/entry.py +161 -0
- package/runtime/python/okstra_ctl/phases/implementation_planning/guidance.py +178 -0
- package/runtime/{prompts/lead → python/okstra_ctl/phases/implementation_planning/instructions}/plan-body-verification.md +61 -51
- package/runtime/python/okstra_ctl/phases/implementation_planning/plan_body.py +3295 -0
- package/runtime/{prompts/profiles/implementation-planning.md → python/okstra_ctl/phases/implementation_planning/profile.md} +74 -25
- package/runtime/python/okstra_ctl/phases/implementation_planning/report.py +237 -0
- package/runtime/{templates/reports → python/okstra_ctl/phases/implementation_planning/report_assets}/implementation-planning-input.template.md +2 -2
- package/runtime/python/okstra_ctl/phases/implementation_planning/spec.md +204 -0
- package/runtime/python/okstra_ctl/phases/implementation_planning/validation.py +597 -0
- package/runtime/python/okstra_ctl/phases/implementation_planning/wizard.py +166 -0
- package/runtime/python/okstra_ctl/phases/improvement_discovery/boundary.json +12 -0
- package/runtime/python/okstra_ctl/{improvement_lenses.py → phases/improvement_discovery/lenses.py} +1 -6
- package/runtime/{prompts/profiles/improvement-discovery.md → python/okstra_ctl/phases/improvement_discovery/profile.md} +5 -5
- package/runtime/python/okstra_ctl/{report_html/view_models/improvement_discovery.py → phases/improvement_discovery/report.py} +3 -3
- package/runtime/{templates/reports → python/okstra_ctl/phases/improvement_discovery/report_assets}/improvement-discovery-input.template.md +1 -2
- package/runtime/python/okstra_ctl/phases/improvement_discovery/spec.md +29 -0
- package/runtime/{validators/validate_improvement_report.py → python/okstra_ctl/phases/improvement_discovery/validation.py} +5 -14
- package/runtime/python/okstra_ctl/phases/project_analysis/__init__.py +1 -0
- package/runtime/python/okstra_ctl/phases/project_analysis/boundary.json +8 -0
- package/runtime/python/okstra_ctl/phases/project_analysis/entry.py +11 -0
- package/runtime/python/okstra_ctl/{report_html/view_models/project_analysis.py → phases/project_analysis/report.py} +3 -3
- package/runtime/python/okstra_ctl/phases/project_analysis/spec.md +33 -0
- package/runtime/python/okstra_ctl/phases/project_analysis/validation.py +55 -0
- package/runtime/python/okstra_ctl/phases/release_handoff/__init__.py +1 -0
- package/runtime/python/okstra_ctl/phases/release_handoff/boundary.json +17 -0
- package/runtime/python/okstra_ctl/phases/release_handoff/entry.py +147 -0
- package/runtime/python/okstra_ctl/phases/release_handoff/operations.py +446 -0
- package/runtime/{prompts/profiles/release-handoff.md → python/okstra_ctl/phases/release_handoff/profile.md} +3 -3
- package/runtime/python/okstra_ctl/{report_html/view_models/release_handoff.py → phases/release_handoff/report.py} +3 -3
- package/runtime/{templates/reports → python/okstra_ctl/phases/release_handoff/report_assets}/release-handoff-input.template.md +1 -1
- package/runtime/python/okstra_ctl/phases/release_handoff/spec.md +233 -0
- package/runtime/python/okstra_ctl/phases/release_handoff/wizard.py +84 -0
- package/runtime/python/okstra_ctl/phases/requirements_discovery/__init__.py +1 -0
- package/runtime/python/okstra_ctl/phases/requirements_discovery/boundary.json +9 -0
- package/runtime/{prompts/profiles/requirements-discovery.md → python/okstra_ctl/phases/requirements_discovery/profile.md} +2 -3
- package/runtime/python/okstra_ctl/{report_html/view_models/requirements_discovery.py → phases/requirements_discovery/report.py} +3 -3
- package/runtime/python/okstra_ctl/phases/requirements_discovery/spec.md +132 -0
- package/runtime/{validators/validate_fanout.py → python/okstra_ctl/phases/requirements_discovery/validation.py} +11 -12
- package/runtime/python/okstra_ctl/phases/technical_verification/__init__.py +1 -0
- package/runtime/python/okstra_ctl/phases/technical_verification/boundary.json +9 -0
- package/runtime/python/okstra_ctl/phases/technical_verification/entry.py +100 -0
- package/runtime/{prompts/profiles/technical-verification.md → python/okstra_ctl/phases/technical_verification/profile.md} +1 -1
- package/runtime/python/okstra_ctl/{report_html/view_models/technical_verification.py → phases/technical_verification/report.py} +2 -2
- package/runtime/python/okstra_ctl/phases/technical_verification/spec.md +37 -0
- package/runtime/python/okstra_ctl/phases/technical_verification/validation.py +90 -0
- package/runtime/python/okstra_ctl/plan_approval.py +70 -0
- package/runtime/python/okstra_ctl/plan_items_cli.py +2 -2130
- package/runtime/python/okstra_ctl/profile_show.py +3 -3
- package/runtime/python/okstra_ctl/render.py +9 -2
- package/runtime/python/okstra_ctl/report_assembly.py +11 -90
- package/runtime/python/okstra_ctl/report_html/context_links.py +1 -1
- package/runtime/python/okstra_ctl/report_projections.py +1 -36
- package/runtime/python/okstra_ctl/report_routing.py +23 -0
- package/runtime/python/okstra_ctl/report_synthesis_packet.py +4 -73
- package/runtime/python/okstra_ctl/report_validation_identity.py +38 -0
- package/runtime/python/okstra_ctl/report_views.py +1 -1
- package/runtime/python/okstra_ctl/run.py +67 -349
- package/runtime/python/okstra_ctl/stage_map.py +13 -0
- package/runtime/python/okstra_ctl/technical_verification_facts.py +52 -0
- package/runtime/python/okstra_ctl/wizard/__init__.py +31 -31
- package/runtime/python/okstra_ctl/wizard/api.py +18 -0
- package/runtime/python/okstra_ctl/wizard/outcome.py +3 -12
- package/runtime/python/okstra_ctl/wizard/registry.py +20 -12
- package/runtime/python/okstra_ctl/wizard/steps_analysis.py +0 -97
- package/runtime/python/okstra_ctl/wizard/steps_plan.py +10 -263
- package/runtime/python/okstra_ctl/wizard/steps_roles.py +2 -1
- package/runtime/python/okstra_ctl/work_categories.py +1 -1
- package/runtime/python/okstra_ctl/worker_prompt_contract.py +36 -0
- package/runtime/python/okstra_ctl/workflow.py +26 -143
- package/runtime/skills/okstra-brief-gen/SKILL.md +3 -3
- package/runtime/skills/okstra-run/SKILL.md +1 -1
- package/runtime/templates/reports/quick-input.template.md +1 -1
- package/runtime/templates/reports/task-brief.template.md +1 -1
- package/runtime/validators/validate-brief.py +2 -2
- package/runtime/validators/validate-run.py +299 -3940
- package/runtime/validators/validate_analysis_report.py +14 -126
- package/runtime/python/okstra_ctl/report_html/view_models/implementation_planning.py +0 -147
- package/runtime/python/okstra_ctl/technical_verification.py +0 -195
- /package/runtime/{prompts/profiles/change-impact-analysis.json → python/okstra_ctl/phases/change_impact_analysis/profile.json} +0 -0
- /package/runtime/{prompts/profiles/change-impact-analysis.md → python/okstra_ctl/phases/change_impact_analysis/profile.md} +0 -0
- /package/runtime/{templates/reports → python/okstra_ctl/phases/change_impact_analysis/report_assets}/change-impact-analysis-input.template.md +0 -0
- /package/runtime/{templates/reports/html/tasks → python/okstra_ctl/phases/change_impact_analysis/report_assets}/change-impact-analysis.template.html +0 -0
- /package/runtime/{templates/reports/md/tasks → python/okstra_ctl/phases/change_impact_analysis/report_assets}/change-impact-analysis.template.md +0 -0
- /package/runtime/{prompts/profiles/error-analysis.json → python/okstra_ctl/phases/error_analysis/profile.json} +0 -0
- /package/runtime/{templates/reports/html/tasks → python/okstra_ctl/phases/error_analysis/report_assets}/error-analysis.template.html +0 -0
- /package/runtime/{templates/reports/md/tasks → python/okstra_ctl/phases/error_analysis/report_assets}/error-analysis.template.md +0 -0
- /package/runtime/{prompts/profiles/feature-analysis.json → python/okstra_ctl/phases/feature_analysis/profile.json} +0 -0
- /package/runtime/{prompts/profiles/feature-analysis.md → python/okstra_ctl/phases/feature_analysis/profile.md} +0 -0
- /package/runtime/{templates/reports → python/okstra_ctl/phases/feature_analysis/report_assets}/feature-analysis-input.template.md +0 -0
- /package/runtime/{templates/reports/html/tasks → python/okstra_ctl/phases/feature_analysis/report_assets}/feature-analysis.template.html +0 -0
- /package/runtime/{templates/reports/md/tasks → python/okstra_ctl/phases/feature_analysis/report_assets}/feature-analysis.template.md +0 -0
- /package/runtime/{prompts/profiles → python/okstra_ctl/phases/implementation/instructions}/_implementation-diff-review.md +0 -0
- /package/runtime/{prompts/profiles → python/okstra_ctl/phases/implementation/instructions}/_implementation-self-check.md +0 -0
- /package/runtime/{prompts/profiles/implementation.json → python/okstra_ctl/phases/implementation/profile.json} +0 -0
- /package/runtime/{templates/reports/html/tasks → python/okstra_ctl/phases/implementation/report_assets}/implementation.template.html +0 -0
- /package/runtime/{templates/reports/md/tasks → python/okstra_ctl/phases/implementation/report_assets}/implementation.template.md +0 -0
- /package/runtime/{prompts/profiles/implementation-option-selection.json → python/okstra_ctl/phases/implementation_option_selection/profile.json} +0 -0
- /package/runtime/{templates/reports/html/tasks → python/okstra_ctl/phases/implementation_option_selection/report_assets}/implementation-option-selection.template.html +0 -0
- /package/runtime/{templates/reports/md/tasks → python/okstra_ctl/phases/implementation_option_selection/report_assets}/implementation-option-selection.template.md +0 -0
- /package/runtime/{prompts/host-orchestration/implementation-planning.md → python/okstra_ctl/phases/implementation_planning/host-rules.md} +0 -0
- /package/runtime/{prompts/profiles/implementation-planning.json → python/okstra_ctl/phases/implementation_planning/profile.json} +0 -0
- /package/runtime/{templates/reports/html/tasks → python/okstra_ctl/phases/implementation_planning/report_assets}/implementation-planning.template.html +0 -0
- /package/runtime/{templates/reports/md/tasks → python/okstra_ctl/phases/implementation_planning/report_assets}/implementation-planning.template.md +0 -0
- /package/runtime/{prompts/profiles/improvement-discovery.json → python/okstra_ctl/phases/improvement_discovery/profile.json} +0 -0
- /package/runtime/{templates/reports/html/tasks → python/okstra_ctl/phases/improvement_discovery/report_assets}/improvement-discovery.template.html +0 -0
- /package/runtime/{templates/reports/md/tasks → python/okstra_ctl/phases/improvement_discovery/report_assets}/improvement-discovery.template.md +0 -0
- /package/runtime/{prompts/profiles/project-analysis.json → python/okstra_ctl/phases/project_analysis/profile.json} +0 -0
- /package/runtime/{prompts/profiles/project-analysis.md → python/okstra_ctl/phases/project_analysis/profile.md} +0 -0
- /package/runtime/{templates/reports → python/okstra_ctl/phases/project_analysis/report_assets}/project-analysis-input.template.md +0 -0
- /package/runtime/{templates/reports/html/tasks → python/okstra_ctl/phases/project_analysis/report_assets}/project-analysis.template.html +0 -0
- /package/runtime/{templates/reports/md/tasks → python/okstra_ctl/phases/project_analysis/report_assets}/project-analysis.template.md +0 -0
- /package/runtime/{prompts/profiles/release-handoff.json → python/okstra_ctl/phases/release_handoff/profile.json} +0 -0
- /package/runtime/{templates/reports/html/tasks → python/okstra_ctl/phases/release_handoff/report_assets}/release-handoff.template.html +0 -0
- /package/runtime/{templates/reports/md/tasks → python/okstra_ctl/phases/release_handoff/report_assets}/release-handoff.template.md +0 -0
- /package/runtime/python/okstra_ctl/{fanout.py → phases/requirements_discovery/fanout.py} +0 -0
- /package/runtime/{prompts/profiles/requirements-discovery.json → python/okstra_ctl/phases/requirements_discovery/profile.json} +0 -0
- /package/runtime/{templates/reports → python/okstra_ctl/phases/requirements_discovery/report_assets}/fan-out-unit.template.md +0 -0
- /package/runtime/{templates/reports/html/tasks → python/okstra_ctl/phases/requirements_discovery/report_assets}/requirements-discovery.template.html +0 -0
- /package/runtime/{templates/reports/md/tasks → python/okstra_ctl/phases/requirements_discovery/report_assets}/requirements-discovery.template.md +0 -0
- /package/runtime/{prompts/profiles/technical-verification.json → python/okstra_ctl/phases/technical_verification/profile.json} +0 -0
- /package/runtime/{templates/reports/html/tasks → python/okstra_ctl/phases/technical_verification/report_assets}/technical-verification.template.html +0 -0
- /package/runtime/{templates/reports/md/tasks → python/okstra_ctl/phases/technical_verification/report_assets}/technical-verification.template.md +0 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"allowed": " - pre-planning context exploration notes (files/interfaces inspected, recent commits scanned, ambiguities flagged)\n - selected-direction planning: realize the one validated selected direction without reopening candidate comparison, with a File Structure list (Create/Modify/Delete with one-line responsibility per file), affected interfaces, and blast-radius estimate\n - legacy candidate-comparison planning only: at least two implementation option candidates, each with a File Structure list (Create/Modify/Delete with one-line responsibility per file), affected interfaces, and blast-radius estimate\n - legacy candidate-comparison planning only: a trade-off matrix across options (complexity, risk, reversibility, test cost, rollout cost) and recommended option with rationale tied to isolation / single-responsibility / YAGNI principles\n - bite-sized stepwise execution order for the selected direction or legacy recommended option (each step ~2-5 min, exact file paths and commands, TDD ordering when applicable, no placeholders)\n - dependency / migration risk assessment, validation checklist (pre / mid / post with exact commands), rollback strategy with revert path and trigger signal\n - every unresolved ambiguity registered as a `Blocks=approval` row in the `## 1. Clarification Items` table (do NOT create a separate `Open Questions` block under `5.5.x` — the unified table is the single home)\n - report record `frontmatter.approved: false` awaiting `--approve` or the in-session wizard\n - self-review confirmation (spec coverage, placeholder scan, internal consistency, ambiguity, scope)\n - one endStateCoverage row per brief end-state id, each mapped to the R-NNN row that carries it",
|
|
3
|
+
"forbidden": [
|
|
4
|
+
"source code edits of any kind (Edit/Write on project source files is forbidden)",
|
|
5
|
+
"file writes outside the run`s artifact directories (`reports/`, `prompts/`, `state/`, `manifests/`, `worker-results/`, `status/`, `sessions/`), including task-root QA scripts, manifest, and tsconfig (planning declares conformance commands and required dependencies; implementation writes these files); in particular, do not write to `docs/superpowers/specs/` or `docs/superpowers/plans/`",
|
|
6
|
+
"executing builds, migrations, deployments, or any state-mutating command",
|
|
7
|
+
"starting `implementation` inside this run (must be a separate run authorised by an approved deliverable from this phase), even if the user says \"다음 단계 진행해\"",
|
|
8
|
+
"dispatching parallel sub-agents beyond the required worker roster (okstra owns worker fan-out)",
|
|
9
|
+
"leaving placeholders such as TBD / TODO / \"handle edge cases\" / \"similar to Option N\" in the report",
|
|
10
|
+
"delegating the self-review pass — the Okstra lead must run it"
|
|
11
|
+
]
|
|
12
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
"""구현 계획의 신규 진입·재실행 입력을 검증한다."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Protocol
|
|
8
|
+
|
|
9
|
+
from okstra_ctl.prepare_error import PrepareError
|
|
10
|
+
from okstra_ctl.incremental_scope import ReverifyScopeError, parse_user_reverify_scope
|
|
11
|
+
from okstra_ctl.implementation_direction import (
|
|
12
|
+
DirectionSelectionError,
|
|
13
|
+
SelectedDirection,
|
|
14
|
+
lexical_absolute_path,
|
|
15
|
+
resolve_selected_direction,
|
|
16
|
+
validate_task_artifact_path,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
class PlanningInputs(Protocol):
|
|
20
|
+
"""공통 준비 입력 중 계획 진입 정책이 읽는 필드."""
|
|
21
|
+
|
|
22
|
+
project_root: str | Path
|
|
23
|
+
project_id: str
|
|
24
|
+
task_group: str
|
|
25
|
+
task_id: str
|
|
26
|
+
task_type: str
|
|
27
|
+
selected_direction_path: str
|
|
28
|
+
clarification_response_path: str
|
|
29
|
+
reverify_scope: str
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
_PLANNING_REPORT_RE = re.compile(
|
|
33
|
+
r"^final-report-implementation-planning-\d{3,}\.(?:md|data\.json)$"
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _resolve_planning_input_path(path_value: str, project_root: Path) -> Path | None:
|
|
38
|
+
"""Resolve cwd-first planning inputs without following symbolic links."""
|
|
39
|
+
raw_path = Path(path_value).expanduser()
|
|
40
|
+
cwd_candidate = lexical_absolute_path(raw_path)
|
|
41
|
+
if cwd_candidate.is_file():
|
|
42
|
+
return cwd_candidate
|
|
43
|
+
if not raw_path.is_absolute():
|
|
44
|
+
project_candidate = lexical_absolute_path(project_root / raw_path)
|
|
45
|
+
if project_candidate.is_file():
|
|
46
|
+
return project_candidate
|
|
47
|
+
return None
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _is_existing_planning_report(
|
|
51
|
+
path_value: str, project_root: Path, inp: PlanningInputs
|
|
52
|
+
) -> bool:
|
|
53
|
+
from okstra_ctl.paths import task_dir
|
|
54
|
+
|
|
55
|
+
try:
|
|
56
|
+
report = lexical_absolute_path(Path(path_value))
|
|
57
|
+
task_root = lexical_absolute_path(
|
|
58
|
+
task_dir(project_root, inp.task_group, inp.task_id)
|
|
59
|
+
)
|
|
60
|
+
relative = report.relative_to(task_root)
|
|
61
|
+
except ValueError:
|
|
62
|
+
return False
|
|
63
|
+
has_planning_layout = (
|
|
64
|
+
_PLANNING_REPORT_RE.fullmatch(report.name) is not None
|
|
65
|
+
and relative.parts[:3]
|
|
66
|
+
== (
|
|
67
|
+
"runs",
|
|
68
|
+
"implementation-planning",
|
|
69
|
+
"reports",
|
|
70
|
+
)
|
|
71
|
+
and len(relative.parts) == 4
|
|
72
|
+
)
|
|
73
|
+
if not has_planning_layout:
|
|
74
|
+
return False
|
|
75
|
+
try:
|
|
76
|
+
validate_task_artifact_path(report, task_root, "planning report")
|
|
77
|
+
except DirectionSelectionError:
|
|
78
|
+
return False
|
|
79
|
+
return True
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _validate_planning_entry_inputs(project_root: Path, inp: PlanningInputs) -> None:
|
|
83
|
+
if inp.task_type != "implementation-planning":
|
|
84
|
+
if inp.selected_direction_path:
|
|
85
|
+
raise PrepareError(
|
|
86
|
+
"--selected-direction is only meaningful with --task-type "
|
|
87
|
+
f"implementation-planning; got {inp.task_type}"
|
|
88
|
+
)
|
|
89
|
+
return
|
|
90
|
+
if inp.selected_direction_path and inp.clarification_response_path:
|
|
91
|
+
raise PrepareError(
|
|
92
|
+
"implementation-planning accepts either --selected-direction for a "
|
|
93
|
+
"new plan or --clarification-response for a rerun, not both"
|
|
94
|
+
)
|
|
95
|
+
if inp.selected_direction_path:
|
|
96
|
+
return
|
|
97
|
+
if inp.clarification_response_path:
|
|
98
|
+
if not _is_existing_planning_report(
|
|
99
|
+
inp.clarification_response_path, project_root, inp
|
|
100
|
+
):
|
|
101
|
+
raise PrepareError(
|
|
102
|
+
"implementation-planning rerun --clarification-response must point "
|
|
103
|
+
"to an existing implementation-planning report for the same task"
|
|
104
|
+
)
|
|
105
|
+
return
|
|
106
|
+
raise PrepareError(
|
|
107
|
+
"implementation-planning requires --selected-direction for a new plan or "
|
|
108
|
+
"--clarification-response to an existing implementation-planning report"
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def _resolve_planning_direction(inp: PlanningInputs) -> SelectedDirection | None:
|
|
113
|
+
if inp.task_type != "implementation-planning" or not inp.selected_direction_path:
|
|
114
|
+
return None
|
|
115
|
+
from okstra_ctl.paths import task_dir
|
|
116
|
+
|
|
117
|
+
try:
|
|
118
|
+
report = lexical_absolute_path(Path(inp.selected_direction_path))
|
|
119
|
+
expected_task_root = lexical_absolute_path(
|
|
120
|
+
task_dir(Path(inp.project_root), inp.task_group, inp.task_id)
|
|
121
|
+
)
|
|
122
|
+
report.relative_to(expected_task_root)
|
|
123
|
+
except ValueError as exc:
|
|
124
|
+
raise PrepareError(
|
|
125
|
+
"selected direction report must belong to the same task"
|
|
126
|
+
) from exc
|
|
127
|
+
try:
|
|
128
|
+
validate_task_artifact_path(
|
|
129
|
+
report, expected_task_root, "selected direction report"
|
|
130
|
+
)
|
|
131
|
+
return resolve_selected_direction(
|
|
132
|
+
report,
|
|
133
|
+
expected_task_key=(f"{inp.project_id}:{inp.task_group}:{inp.task_id}"),
|
|
134
|
+
)
|
|
135
|
+
except DirectionSelectionError as exc:
|
|
136
|
+
raise PrepareError(str(exc)) from exc
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def _validate_reverify_scope(inp: PlanningInputs) -> None:
|
|
140
|
+
"""A pinned re-verification scope is only actionable on a planning re-run.
|
|
141
|
+
|
|
142
|
+
Every other phase renders the tokens too (the template always reads them),
|
|
143
|
+
but nothing consumes them there — so a value outside the one phase that
|
|
144
|
+
acts on it is a caller mistake, not a preference to honour silently.
|
|
145
|
+
"""
|
|
146
|
+
if not (inp.reverify_scope or "").strip():
|
|
147
|
+
return
|
|
148
|
+
if inp.task_type != "implementation-planning":
|
|
149
|
+
raise PrepareError(
|
|
150
|
+
"--reverify-scope is only meaningful with --task-type "
|
|
151
|
+
f"implementation-planning; got {inp.task_type}"
|
|
152
|
+
)
|
|
153
|
+
if not inp.clarification_response_path:
|
|
154
|
+
raise PrepareError(
|
|
155
|
+
"--reverify-scope needs --clarification-response: there is no prior "
|
|
156
|
+
"report to narrow re-verification against"
|
|
157
|
+
)
|
|
158
|
+
try:
|
|
159
|
+
parse_user_reverify_scope(inp.reverify_scope)
|
|
160
|
+
except ReverifyScopeError as exc:
|
|
161
|
+
raise PrepareError(str(exc)) from exc
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
"""구현 계획의 의존·결정 기록과 사용자 승인 안내 규칙."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
|
|
7
|
+
from okstra_ctl.clarification_items import APPROVAL_BLOCKS, progress_blocking_ids
|
|
8
|
+
from okstra_ctl.plan_approval import plan_is_approved
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _validate_implementation_planning_cross_project(
|
|
12
|
+
data: dict, failures: list[str]
|
|
13
|
+
) -> None:
|
|
14
|
+
"""타 프로젝트 의존을 DM 행(`kind == 'cross-project'`)으로 선언했다면
|
|
15
|
+
`crossProjectDependencies` 에 `direction == 'upstream-precondition'` 행이
|
|
16
|
+
반드시 있어야 한다 — 실제 선행 필수 의존이 soft `recommendedNextSteps`
|
|
17
|
+
추천으로 새어나가지 못하게 강제한다. 각 XP 행의 필드 비-빈은 스키마가
|
|
18
|
+
보장하고, 이 검사는 cross-project DM 신호가 있을 때 XP 행이 *존재*하는지를 본다.
|
|
19
|
+
"""
|
|
20
|
+
ip = data.get("implementationPlanning")
|
|
21
|
+
if not isinstance(ip, dict):
|
|
22
|
+
return
|
|
23
|
+
dm_rows = ip.get("dependencyMigrationRisk") or []
|
|
24
|
+
has_cross_project_dm = any(
|
|
25
|
+
isinstance(r, dict) and r.get("kind") == "cross-project" for r in dm_rows
|
|
26
|
+
)
|
|
27
|
+
if not has_cross_project_dm:
|
|
28
|
+
return
|
|
29
|
+
xp_rows = ip.get("crossProjectDependencies") or []
|
|
30
|
+
has_upstream = any(
|
|
31
|
+
isinstance(r, dict) and r.get("direction") == "upstream-precondition"
|
|
32
|
+
for r in xp_rows
|
|
33
|
+
)
|
|
34
|
+
if not has_upstream:
|
|
35
|
+
failures.append(
|
|
36
|
+
"final-report data.json: a dependencyMigrationRisk row has "
|
|
37
|
+
"kind='cross-project' but crossProjectDependencies carries no "
|
|
38
|
+
"direction='upstream-precondition' entry. Record the cross-project "
|
|
39
|
+
"dependency as a mandatory precondition (concrete requiredWork / "
|
|
40
|
+
"verificationSignal / howToStart) — not a soft Recommended Next Step."
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _validate_implementation_planning_decision_drafts(
|
|
45
|
+
data: dict, failures: list[str]
|
|
46
|
+
) -> None:
|
|
47
|
+
"""`decisionDrafts` 가 비어있지 않으면 어느 stage 의 stepwiseExecution step 이
|
|
48
|
+
`.okstra/decisions/` 파일을 생성하는 materialization step 을 포함해야 한다.
|
|
49
|
+
프로파일 Decision-record evaluation 의 "approved plan stepwise MUST include
|
|
50
|
+
Create .okstra/decisions/<NNNN>-<slug>.md" 를 실제 강제한다. draft 존재 자체가
|
|
51
|
+
trigger 이므로 self-contained 하다.
|
|
52
|
+
"""
|
|
53
|
+
ip = data.get("implementationPlanning")
|
|
54
|
+
if not isinstance(ip, dict):
|
|
55
|
+
return
|
|
56
|
+
if not (ip.get("decisionDrafts") or []):
|
|
57
|
+
return
|
|
58
|
+
has_materialization = any(
|
|
59
|
+
".okstra/decisions/" in (step.get("files") or "")
|
|
60
|
+
or ".okstra/decisions/" in (step.get("action") or "")
|
|
61
|
+
for stage in (ip.get("stages") or [])
|
|
62
|
+
if isinstance(stage, dict)
|
|
63
|
+
for step in (stage.get("stepwiseExecution") or [])
|
|
64
|
+
if isinstance(step, dict)
|
|
65
|
+
)
|
|
66
|
+
if not has_materialization:
|
|
67
|
+
failures.append(
|
|
68
|
+
"final-report data.json: implementationPlanning.decisionDrafts is "
|
|
69
|
+
"non-empty but no stage's stepwiseExecution creates a "
|
|
70
|
+
"`.okstra/decisions/<NNNN>-<slug>.md` file. The approved plan must "
|
|
71
|
+
"materialize each decision draft via a stepwise step (profile "
|
|
72
|
+
"Decision-record evaluation)."
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
_RERUN_FLAG = "--answered-clarifications"
|
|
77
|
+
_USER_RESPONSE_HINT = re.compile(r"okstra-user-response", re.IGNORECASE)
|
|
78
|
+
_APPROVE_HINT = re.compile(r"--approve|\bapprov", re.IGNORECASE)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _next_step_texts(steps: object) -> list[str]:
|
|
82
|
+
"""Every reader-visible string in `recommendedNextSteps`, prose and command."""
|
|
83
|
+
texts: list[str] = []
|
|
84
|
+
for step in steps if isinstance(steps, list) else []:
|
|
85
|
+
if not isinstance(step, dict):
|
|
86
|
+
continue
|
|
87
|
+
texts.append(str(step.get("text") or ""))
|
|
88
|
+
for command in step.get("commands") or []:
|
|
89
|
+
if isinstance(command, dict):
|
|
90
|
+
texts.append(str(command.get("claudeCode") or ""))
|
|
91
|
+
texts.append(str(command.get("terminal") or ""))
|
|
92
|
+
return texts
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _has_unresolved_approval_blocker(data: dict) -> bool:
|
|
96
|
+
return bool(
|
|
97
|
+
progress_blocking_ids(
|
|
98
|
+
data.get("clarificationItems"),
|
|
99
|
+
APPROVAL_BLOCKS,
|
|
100
|
+
report_data=data,
|
|
101
|
+
)
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _planning_gate_blocks_approval(data: dict) -> bool:
|
|
106
|
+
planning = data.get("implementationPlanning")
|
|
107
|
+
if not isinstance(planning, dict):
|
|
108
|
+
return False
|
|
109
|
+
verification = planning.get("planBodyVerification")
|
|
110
|
+
if not isinstance(verification, dict):
|
|
111
|
+
return False
|
|
112
|
+
gate = str(verification.get("gateResult") or "").strip().lower()
|
|
113
|
+
if gate == "aborted-non-result":
|
|
114
|
+
return True
|
|
115
|
+
if gate != "blocked-by-disagreement":
|
|
116
|
+
return False
|
|
117
|
+
return _has_unresolved_approval_blocker(data) or not any(
|
|
118
|
+
isinstance(row, dict) and row.get("blocks") == "approval"
|
|
119
|
+
for row in data.get("clarificationItems") or []
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def _validate_rerun_guidance(data: dict, failures: list[str]) -> None:
|
|
124
|
+
"""A report that withholds approval must say how to come back from it.
|
|
125
|
+
|
|
126
|
+
The way forward — answer the blockers, then re-run carrying those ids —
|
|
127
|
+
lived only in the lead prompt, which is read *after* the next run has
|
|
128
|
+
already started. The person who has to act reads the report instead, and it
|
|
129
|
+
told them nothing about the next command. Requiring the flag by name is a
|
|
130
|
+
low bar deliberately: it does not check that the rest of the step is right,
|
|
131
|
+
only that the report stops leaving the reader to work the mechanics out.
|
|
132
|
+
"""
|
|
133
|
+
if (data.get("header") or {}).get("taskType") != "implementation-planning":
|
|
134
|
+
return
|
|
135
|
+
if not _has_unresolved_approval_blocker(data):
|
|
136
|
+
return
|
|
137
|
+
texts = _next_step_texts(data.get("recommendedNextSteps"))
|
|
138
|
+
if any(_RERUN_FLAG in text for text in texts) and any(
|
|
139
|
+
_USER_RESPONSE_HINT.search(text) for text in texts
|
|
140
|
+
):
|
|
141
|
+
return
|
|
142
|
+
failures.append(
|
|
143
|
+
"final-report data.json: this plan withholds approval on an unresolved "
|
|
144
|
+
"`blocks: approval` clarification, but no `recommendedNextSteps` entry "
|
|
145
|
+
"tells the reader the command to run now — name `/okstra-user-response` "
|
|
146
|
+
f"and the `{_RERUN_FLAG}` re-run in a step's `text` or one of its "
|
|
147
|
+
"`commands`. `okstra recap assemble` prints the exact ids and flag "
|
|
148
|
+
"value once the answers are recorded."
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def _validate_approval_guidance(data: dict, failures: list[str]) -> None:
|
|
153
|
+
"""승인 가능한 plan-ready 는 사용자에게 승인하라고 말해야 한다.
|
|
154
|
+
|
|
155
|
+
포인터가 implementation/ready 여도 승인은 사용자만 뒤집는다. 다음 단계
|
|
156
|
+
안내가 계획 재실행이면 승인 칸을 건너뛰고 같은 단계를 다시 돈다.
|
|
157
|
+
"""
|
|
158
|
+
if (data.get("header") or {}).get("taskType") != "implementation-planning":
|
|
159
|
+
return
|
|
160
|
+
planning = data.get("implementationPlanning")
|
|
161
|
+
if not isinstance(planning, dict) or planning.get("outcome") != "plan-ready":
|
|
162
|
+
return
|
|
163
|
+
if _has_unresolved_approval_blocker(data) or _planning_gate_blocks_approval(data):
|
|
164
|
+
return
|
|
165
|
+
if plan_is_approved(data):
|
|
166
|
+
return
|
|
167
|
+
if any(
|
|
168
|
+
_APPROVE_HINT.search(text)
|
|
169
|
+
for text in _next_step_texts(data.get("recommendedNextSteps"))
|
|
170
|
+
):
|
|
171
|
+
return
|
|
172
|
+
failures.append(
|
|
173
|
+
"final-report data.json: this plan is ready for the user to approve, "
|
|
174
|
+
"but no `recommendedNextSteps` entry tells the reader to approve — "
|
|
175
|
+
"name `--approve` or the in-session wizard in a step's `text` or "
|
|
176
|
+
"one of its `commands`. Do not recommend another "
|
|
177
|
+
"implementation-planning run."
|
|
178
|
+
)
|