okstra 0.102.2 → 0.102.3
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 +5 -5
- package/README.md +5 -5
- package/docs/kr/architecture/storage-model.md +1 -1
- package/docs/kr/architecture.md +7 -11
- package/docs/kr/cli.md +4 -4
- package/docs/kr/container.md +1 -1
- package/docs/kr/performance-improvement-plan-v2.md +2 -2
- package/docs/project-structure-overview.md +2 -2
- package/package.json +7 -2
- package/runtime/BUILD.json +2 -2
- package/runtime/bin/okstra-antigravity-exec.sh +1 -2
- package/runtime/bin/okstra-codex-exec.sh +1 -2
- package/runtime/bin/okstra-render-final-report.py +2 -9
- package/runtime/bin/okstra-spawn-followups.py +4 -6
- package/runtime/bin/okstra-trace-cleanup.sh +24 -4
- package/runtime/prompts/lead/okstra-lead-contract.md +2 -2
- package/runtime/prompts/lead/report-writer.md +1 -1
- package/runtime/prompts/profiles/_common-contract.md +9 -11
- package/runtime/python/okstra_ctl/codex_dispatch.py +4 -12
- package/runtime/python/okstra_ctl/consumers.py +35 -0
- package/runtime/python/okstra_ctl/container.py +4 -6
- package/runtime/python/okstra_ctl/dispatch_core.py +4 -12
- package/runtime/python/okstra_ctl/final_report_paths.py +24 -0
- package/runtime/python/okstra_ctl/handoff.py +2 -1
- package/runtime/python/okstra_ctl/implementation_stage.py +7 -11
- package/runtime/python/okstra_ctl/report_view_artifacts.py +14 -0
- package/runtime/python/okstra_ctl/report_views.py +7 -4
- package/runtime/python/okstra_ctl/run.py +3 -10
- package/runtime/python/okstra_ctl/stage_targets.py +18 -0
- package/runtime/python/okstra_ctl/wizard.py +24 -25
- package/runtime/python/okstra_ctl/wizard_stage_intent.py +54 -0
- package/runtime/python/okstra_token_usage/report.py +2 -5
- package/runtime/validators/validate-report-views.py +2 -1
- package/runtime/validators/validate-run.py +1 -11
- package/src/commands/lifecycle/install.mjs +5 -5
- package/src/commands/lifecycle/uninstall.mjs +4 -4
- package/docs/superpowers/plans/2026-05-12-ticket-id-in-reports.md +0 -638
- package/docs/superpowers/plans/2026-05-14-convergence-queue-pruning.md +0 -1568
- package/docs/superpowers/plans/2026-05-17-dual-format-final-report.md +0 -167
- package/docs/superpowers/plans/2026-05-20-final-report-language.md +0 -1501
- package/docs/superpowers/plans/2026-05-20-implementation-planning-multi-stage.md +0 -1267
- package/docs/superpowers/plans/2026-05-20-okstra-run-prompt-sot-b1.md +0 -1007
- package/docs/superpowers/plans/2026-05-20-wizard-messages-json-sot.md +0 -720
- package/docs/superpowers/plans/2026-05-20-wizard-prompt-json-sot-a1.md +0 -681
- package/docs/superpowers/plans/2026-05-21-improvement-discovery-task-type.md +0 -1691
- package/docs/superpowers/plans/2026-05-24-implementation-lead-context-slimming.md +0 -1700
- package/docs/superpowers/plans/2026-05-25-okstra-project-root-rename.md +0 -158
- package/docs/superpowers/plans/2026-05-26-wizard-3-option-picker.md +0 -860
- package/docs/superpowers/plans/2026-06-02-final-verification-protocol-hardening.md +0 -326
- package/docs/superpowers/plans/2026-06-02-okstra-run-branch-confirm-step.md +0 -337
- package/docs/superpowers/plans/2026-06-02-okstra-run-phase-pane-cleanup.md +0 -410
- package/docs/superpowers/plans/2026-06-02-requirements-discovery-fanout.md +0 -728
- package/docs/superpowers/plans/2026-06-04-adversarial-implementation-planning.md +0 -294
- package/docs/superpowers/plans/2026-06-04-adversarial-verification.md +0 -570
- package/docs/superpowers/plans/2026-06-04-coverage-critic.md +0 -516
- package/docs/superpowers/plans/2026-06-04-stage-cohesion-planner.md +0 -351
- package/docs/superpowers/plans/2026-06-04-stage-run-batching.md +0 -457
- package/docs/superpowers/plans/2026-06-05-acceptance-critic.md +0 -251
- package/docs/superpowers/plans/2026-06-05-compact-markdown-report-tables.md +0 -323
- package/docs/superpowers/plans/2026-06-05-wizard-batch-prompts.md +0 -559
- package/docs/superpowers/plans/2026-06-06-final-verification-whole-task-gate.md +0 -993
- package/docs/superpowers/plans/2026-06-06-stage-parallel-and-pending-fixes.md +0 -93
- package/docs/superpowers/plans/2026-06-06-stage-worktree-isolation-p1.md +0 -447
- package/docs/superpowers/plans/2026-06-06-stage-worktree-isolation-p2.md +0 -289
- package/docs/superpowers/plans/2026-06-06-stage-worktree-isolation-p3.md +0 -774
- package/docs/superpowers/plans/2026-06-06-stage-worktree-isolation-p4.md +0 -303
- package/docs/superpowers/plans/2026-06-06-stage-worktree-isolation-p5-multidep-base.md +0 -387
- package/docs/superpowers/plans/2026-06-07-stage-conformance-qa-phase2.md +0 -275
- package/docs/superpowers/plans/2026-06-07-stage-conformance-qa-phase3.md +0 -282
- package/docs/superpowers/plans/2026-06-07-stage-conformance-qa-phase4a.md +0 -147
- package/docs/superpowers/plans/2026-06-07-stage-conformance-qa-phase4b.md +0 -262
- package/docs/superpowers/plans/2026-06-07-stage-conformance-qa-phase4c.md +0 -184
- package/docs/superpowers/plans/2026-06-07-stage-conformance-qa-phase4d.md +0 -88
- package/docs/superpowers/plans/2026-06-07-stage-conformance-qa-phase4e.md +0 -250
- package/docs/superpowers/plans/2026-06-07-stage-conformance-qa.md +0 -409
- package/docs/superpowers/plans/2026-06-09-implementation-run-artifact-stage-isolation.md +0 -320
- package/docs/superpowers/plans/2026-06-10-concurrent-run-team-guard.md +0 -456
- package/docs/superpowers/plans/2026-06-10-git-reconcile-stale-sha-recovery.md +0 -1408
- package/docs/superpowers/plans/2026-06-10-lead-worker-completion-polling-PROBE.md +0 -42
- package/docs/superpowers/plans/2026-06-10-lead-worker-completion-polling.md +0 -337
- package/docs/superpowers/plans/2026-06-10-p6-token-usage-incremental.md +0 -1029
- package/docs/superpowers/plans/2026-06-10-stage-group-handoff.md +0 -1572
- package/docs/superpowers/plans/2026-06-11-fix-cycle.md +0 -1290
- package/docs/superpowers/plans/2026-06-11-wizard-whole-task-final-verification.md +0 -526
- package/docs/superpowers/plans/2026-06-12-html-plan-approval.md +0 -1000
- package/docs/superpowers/plans/2026-06-13-repo-risk-hardening.md +0 -493
- package/docs/superpowers/plans/2026-06-14-host-runtime-auto-execution.md +0 -1775
- package/docs/superpowers/plans/2026-06-15-coding-preflight-pack-dispatch-path.md +0 -504
- package/docs/superpowers/plans/2026-06-15-internal-skill-migration-final-fixups.md +0 -342
- package/docs/superpowers/plans/2026-06-15-internal-skill-migration-fixups.md +0 -258
- package/docs/superpowers/plans/2026-06-15-internal-skill-migration-remaining-fixups.md +0 -387
- package/docs/superpowers/plans/2026-06-15-internal-skill-resource-migration.md +0 -749
- package/docs/superpowers/plans/2026-06-15-worker-prompt-anchor-final-fixups.md +0 -828
- package/docs/superpowers/plans/2026-06-15-worker-prompt-header-error-contract.md +0 -490
- package/docs/superpowers/plans/2026-06-17-okstra-error-report.md +0 -724
- package/docs/superpowers/plans/2026-06-18-subagent-pane-reclaim.md +0 -483
- package/docs/superpowers/plans/2026-06-19-agent-operating-standard.md +0 -476
- package/docs/superpowers/plans/2026-06-19-okstra-inspect-recap-facet.md +0 -647
- package/docs/superpowers/plans/2026-06-20-error-feedback-loop.md +0 -1019
- package/docs/superpowers/plans/2026-06-20-run-index-row-normalization.md +0 -583
- package/docs/superpowers/plans/2026-06-20-stage-auto-integrate-teardown.md +0 -819
- package/docs/superpowers/plans/2026-06-21-okstra-container-local-user-test.md +0 -714
- package/docs/superpowers/plans/2026-06-21-release-handoff-local-checkout.md +0 -348
- package/docs/superpowers/plans/2026-06-23-implementation-stage-multiselect-chain.md +0 -630
- package/docs/superpowers/plans/2026-06-23-implementation-step-commit-granularity.md +0 -274
- package/docs/superpowers/plans/2026-06-23-phase-batch-cleanup-enforcement.md +0 -409
- package/docs/superpowers/plans/2026-06-24-cross-project-precondition.md +0 -542
- package/docs/superpowers/plans/2026-06-24-decision-drafts-render-gap.md +0 -568
- package/docs/superpowers/plans/2026-06-24-manual-user-test-section.md +0 -203
- package/docs/superpowers/plans/2026-06-24-okstra-container-build-rename.md +0 -525
- package/docs/superpowers/specs/2026-05-12-ticket-id-in-reports-design.md +0 -131
- package/docs/superpowers/specs/2026-05-15-implementation-plan-verification-design.md +0 -254
- package/docs/superpowers/specs/2026-05-20-final-report-language-design.md +0 -383
- package/docs/superpowers/specs/2026-05-20-implementation-planning-multi-stage-design.md +0 -322
- package/docs/superpowers/specs/2026-05-20-okstra-run-prompt-sot-design.md +0 -299
- package/docs/superpowers/specs/2026-05-21-improvement-discovery-task-type-design.md +0 -335
- package/docs/superpowers/specs/2026-06-02-okstra-run-branch-confirm-step-design.md +0 -113
- package/docs/superpowers/specs/2026-06-02-okstra-run-phase-pane-cleanup-design.md +0 -173
- package/docs/superpowers/specs/2026-06-02-requirements-discovery-fanout-design.md +0 -154
- package/docs/superpowers/specs/2026-06-04-adversarial-implementation-planning-design.md +0 -90
- package/docs/superpowers/specs/2026-06-04-adversarial-verification-design.md +0 -176
- package/docs/superpowers/specs/2026-06-04-coverage-critic-design.md +0 -99
- package/docs/superpowers/specs/2026-06-04-stage-splitting-cost-aware-design.md +0 -98
- package/docs/superpowers/specs/2026-06-05-acceptance-critic-design.md +0 -90
- package/docs/superpowers/specs/2026-06-05-compact-markdown-report-tables-design.md +0 -87
- package/docs/superpowers/specs/2026-06-05-wizard-batch-prompts-design.md +0 -121
- package/docs/superpowers/specs/2026-06-06-final-verification-whole-task-gate-design.md +0 -126
- package/docs/superpowers/specs/2026-06-06-stage-worktree-isolation-design.md +0 -180
- package/docs/superpowers/specs/2026-06-06-vertical-slice-tdd-planning-design.md +0 -179
- package/docs/superpowers/specs/2026-06-07-stage-conformance-qa-design.md +0 -169
- package/docs/superpowers/specs/2026-06-09-executor-model-custom-id-cascade-design.md +0 -66
- package/docs/superpowers/specs/2026-06-09-implementation-run-artifact-stage-isolation-design.md +0 -87
- package/docs/superpowers/specs/2026-06-10-blocking-contract-posthoc-conformance-design.md +0 -168
- package/docs/superpowers/specs/2026-06-10-concurrent-run-team-guard-design.md +0 -107
- package/docs/superpowers/specs/2026-06-10-git-reconcile-stale-sha-recovery-design.md +0 -105
- package/docs/superpowers/specs/2026-06-10-lead-worker-completion-polling-design.md +0 -113
- package/docs/superpowers/specs/2026-06-10-stage-group-handoff-design.md +0 -156
- package/docs/superpowers/specs/2026-06-11-brief-entry-only-handoff-stage-entry-design.md +0 -158
- package/docs/superpowers/specs/2026-06-11-fix-cycle-design.md +0 -94
- package/docs/superpowers/specs/2026-06-11-wizard-whole-task-final-verification-design.md +0 -89
- package/docs/superpowers/specs/2026-06-12-codex-lead-adapter-design.md +0 -358
- package/docs/superpowers/specs/2026-06-12-html-plan-approval-design.md +0 -85
- package/docs/superpowers/specs/2026-06-12-stage-discipline-rule-design.md +0 -108
- package/docs/superpowers/specs/2026-06-13-forbidden-actions-ssot-design.md +0 -134
- package/docs/superpowers/specs/2026-06-13-neutral-tmux-lead-adapter-design.md +0 -284
- package/docs/superpowers/specs/2026-06-14-host-runtime-auto-execution-design.md +0 -404
- package/docs/superpowers/specs/2026-06-17-okstra-error-report-design.md +0 -123
- package/docs/superpowers/specs/2026-06-18-subagent-pane-reclaim-design.md +0 -150
- package/docs/superpowers/specs/2026-06-19-agent-operating-standard-design.md +0 -120
- package/docs/superpowers/specs/2026-06-19-okstra-inspect-recap-facet-design.md +0 -96
- package/docs/superpowers/specs/2026-06-20-error-feedback-loop-design.md +0 -171
- package/docs/superpowers/specs/2026-06-20-run-index-row-normalization-design.md +0 -112
- package/docs/superpowers/specs/2026-06-20-stage-auto-integrate-teardown-design.md +0 -145
- package/docs/superpowers/specs/2026-06-21-okstra-container-local-user-test-design.md +0 -125
- package/docs/superpowers/specs/2026-06-21-release-handoff-local-checkout-design.md +0 -113
- package/docs/superpowers/specs/2026-06-23-implementation-stage-multiselect-chain-design.md +0 -138
- package/docs/superpowers/specs/2026-06-23-implementation-step-commit-granularity-design.md +0 -120
- package/docs/superpowers/specs/2026-06-24-cross-project-precondition-design.md +0 -129
- package/docs/superpowers/specs/2026-06-24-decision-drafts-render-gap-design.md +0 -185
- package/docs/superpowers/specs/2026-06-24-manual-user-test-section-design.md +0 -121
- package/docs/superpowers/specs/2026-06-24-okstra-container-build-rename-design.md +0 -110
|
@@ -18,6 +18,10 @@ from datetime import datetime, timezone
|
|
|
18
18
|
from pathlib import Path
|
|
19
19
|
from typing import Any, Mapping, Sequence
|
|
20
20
|
|
|
21
|
+
from .final_report_paths import (
|
|
22
|
+
final_report_data_path as _final_report_data_path,
|
|
23
|
+
final_report_markdown_path as _final_report_markdown_path,
|
|
24
|
+
)
|
|
21
25
|
from .lead_events import LeadEvent, append_lead_event
|
|
22
26
|
from .wrapper_status import status_path_for_prompt
|
|
23
27
|
from .paths import task_dir
|
|
@@ -1450,18 +1454,6 @@ def _failure_reason(exit_code: int, missing_paths: Sequence[Path]) -> str:
|
|
|
1450
1454
|
return "worker dispatch failed"
|
|
1451
1455
|
|
|
1452
1456
|
|
|
1453
|
-
def _final_report_data_path(markdown_path: Path) -> Path:
|
|
1454
|
-
if markdown_path.name.endswith(".md"):
|
|
1455
|
-
return markdown_path.with_name(markdown_path.name[:-3] + ".data.json")
|
|
1456
|
-
return markdown_path.with_suffix(".data.json")
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
def _final_report_markdown_path(data_json_path: Path) -> Path:
|
|
1460
|
-
if data_json_path.name.endswith(".data.json"):
|
|
1461
|
-
return data_json_path.with_name(data_json_path.name[:-10] + ".md")
|
|
1462
|
-
return data_json_path.with_suffix(".md")
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
1457
|
def _replace_report_writer_model_line(
|
|
1466
1458
|
prompt_path: Path,
|
|
1467
1459
|
model_execution_value: str,
|
|
@@ -9,6 +9,7 @@ from __future__ import annotations
|
|
|
9
9
|
|
|
10
10
|
import json
|
|
11
11
|
import re
|
|
12
|
+
from dataclasses import dataclass
|
|
12
13
|
from pathlib import Path
|
|
13
14
|
from typing import Any, Dict, List, Optional
|
|
14
15
|
|
|
@@ -17,6 +18,17 @@ from .run_context import consumers_mutex
|
|
|
17
18
|
CONSUMERS_FILENAME = "consumers.jsonl"
|
|
18
19
|
|
|
19
20
|
|
|
21
|
+
@dataclass(frozen=True)
|
|
22
|
+
class StageConsumerState:
|
|
23
|
+
rows: List[Dict[str, Any]]
|
|
24
|
+
done_rows: List[Dict[str, Any]]
|
|
25
|
+
done_by_stage: Dict[int, Dict[str, Any]]
|
|
26
|
+
done_stages: set[int]
|
|
27
|
+
started_stages: set[int]
|
|
28
|
+
verified_accepted_stages: set[int]
|
|
29
|
+
pr_covered_stages: set[int]
|
|
30
|
+
|
|
31
|
+
|
|
20
32
|
def _path(plan_run_root: Path) -> Path:
|
|
21
33
|
return plan_run_root / CONSUMERS_FILENAME
|
|
22
34
|
|
|
@@ -50,6 +62,29 @@ def latest_done_by_stage(rows: List[Dict[str, Any]]) -> Dict[int, Dict[str, Any]
|
|
|
50
62
|
return out
|
|
51
63
|
|
|
52
64
|
|
|
65
|
+
def read_stage_consumer_state(
|
|
66
|
+
plan_run_root: Path, *, recover_from_carry: bool = False,
|
|
67
|
+
) -> StageConsumerState:
|
|
68
|
+
if recover_from_carry:
|
|
69
|
+
backfill_done_from_carry(plan_run_root)
|
|
70
|
+
rows = read_consumers(plan_run_root)
|
|
71
|
+
done_rows = [r for r in rows if r.get("status") == "done"]
|
|
72
|
+
done_by_stage = latest_done_by_stage(rows)
|
|
73
|
+
started = {
|
|
74
|
+
r["stage"] for r in rows
|
|
75
|
+
if r.get("status") == "started" and isinstance(r.get("stage"), int)
|
|
76
|
+
}
|
|
77
|
+
return StageConsumerState(
|
|
78
|
+
rows=rows,
|
|
79
|
+
done_rows=done_rows,
|
|
80
|
+
done_by_stage=done_by_stage,
|
|
81
|
+
done_stages=set(done_by_stage.keys()),
|
|
82
|
+
started_stages=started,
|
|
83
|
+
verified_accepted_stages=verified_accepted_stages(rows),
|
|
84
|
+
pr_covered_stages=pr_covered_stages(rows),
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
|
|
53
88
|
def append_consumer(plan_run_root: Path, *, impl_task_key: str, stage: int,
|
|
54
89
|
status: str, force_reappend: bool = False,
|
|
55
90
|
**fields: Any) -> None:
|
|
@@ -506,7 +506,7 @@ def _split_task_key(task_key: str) -> tuple[str, str, str]:
|
|
|
506
506
|
def _resolve_up_inputs(project_root: Path, task_key: str) -> dict:
|
|
507
507
|
"""task-key 로 up 에 필요한 입력(worktree/stage_map/done_rows/anchor)을 모은다."""
|
|
508
508
|
from . import worktree_registry as _reg
|
|
509
|
-
from .consumers import
|
|
509
|
+
from .consumers import read_stage_consumer_state
|
|
510
510
|
from .plan_run_root import resolve_plan_run_root_by_task_key
|
|
511
511
|
|
|
512
512
|
project_id, group, task_id = _split_task_key(task_key)
|
|
@@ -517,11 +517,9 @@ def _resolve_up_inputs(project_root: Path, task_key: str) -> dict:
|
|
|
517
517
|
"없습니다. 먼저 해당 task 의 phase 를 한 번 실행해 worktree 를 만드세요.")
|
|
518
518
|
plan = resolve_plan_run_root_by_task_key(
|
|
519
519
|
project_root=project_root, task_group=group, task_id=task_id)
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
backfill_done_from_carry(plan.run_root)
|
|
524
|
-
done_rows = [r for r in read_consumers(plan.run_root) if r.get("status") == "done"]
|
|
520
|
+
done_rows = read_stage_consumer_state(
|
|
521
|
+
plan.run_root, recover_from_carry=True,
|
|
522
|
+
).done_rows
|
|
525
523
|
stage_map = _plan_stage_map(plan.approved_plan_path)
|
|
526
524
|
anchor = _reg.get_implementation_base(project_id, group, task_id) or ""
|
|
527
525
|
return {
|
|
@@ -11,6 +11,10 @@ from pathlib import Path
|
|
|
11
11
|
from typing import Any, Mapping, Sequence
|
|
12
12
|
|
|
13
13
|
from . import tmux
|
|
14
|
+
from .final_report_paths import (
|
|
15
|
+
final_report_data_path as _final_report_data_path,
|
|
16
|
+
final_report_markdown_path as _final_report_markdown_path,
|
|
17
|
+
)
|
|
14
18
|
from .lead_events import LeadEvent, append_lead_event
|
|
15
19
|
from .worker_prompt_headers import WorkerPromptHeaderError, worker_prompt_headers
|
|
16
20
|
from .wrapper_status import read_wrapper_status, status_path_for_prompt
|
|
@@ -823,18 +827,6 @@ def _completion_paths(worker_id: str, result_path: Path, manifest: Mapping[str,
|
|
|
823
827
|
return (data_json, _final_report_markdown_path(data_json), result_path)
|
|
824
828
|
|
|
825
829
|
|
|
826
|
-
def _final_report_data_path(markdown_path: Path) -> Path:
|
|
827
|
-
if markdown_path.name.endswith(".md"):
|
|
828
|
-
return markdown_path.with_name(markdown_path.name[:-3] + ".data.json")
|
|
829
|
-
return markdown_path.with_suffix(".data.json")
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
def _final_report_markdown_path(data_json_path: Path) -> Path:
|
|
833
|
-
if data_json_path.name.endswith(".data.json"):
|
|
834
|
-
return data_json_path.with_name(data_json_path.name[:-10] + ".md")
|
|
835
|
-
return data_json_path.with_suffix(".md")
|
|
836
|
-
|
|
837
|
-
|
|
838
830
|
def _run_dir(plan: DispatchPlan) -> Path:
|
|
839
831
|
value = _string_value(plan.manifest.get("runDirectoryPath"))
|
|
840
832
|
return _resolve_project_path(plan.project_root, value) if value else plan.team_state_path.parent.parent
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""Path helpers for the final-report markdown/data.json pair."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
DATA_JSON_SUFFIX = ".data.json"
|
|
8
|
+
MARKDOWN_SUFFIX = ".md"
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def final_report_data_path(report_path: Path) -> Path:
|
|
12
|
+
"""Return the data.json sibling for a final-report markdown path."""
|
|
13
|
+
name = report_path.name
|
|
14
|
+
if name.endswith(MARKDOWN_SUFFIX):
|
|
15
|
+
return report_path.with_name(name[: -len(MARKDOWN_SUFFIX)] + DATA_JSON_SUFFIX)
|
|
16
|
+
return report_path.with_suffix(DATA_JSON_SUFFIX)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def final_report_markdown_path(data_path: Path) -> Path:
|
|
20
|
+
"""Return the markdown sibling for a final-report data.json path."""
|
|
21
|
+
name = data_path.name
|
|
22
|
+
if name.endswith(DATA_JSON_SUFFIX):
|
|
23
|
+
return data_path.with_name(name[: -len(DATA_JSON_SUFFIX)] + MARKDOWN_SUFFIX)
|
|
24
|
+
return data_path.with_suffix(MARKDOWN_SUFFIX)
|
|
@@ -13,6 +13,7 @@ from pathlib import Path
|
|
|
13
13
|
from typing import Any, Callable, Dict, List, Optional, Tuple
|
|
14
14
|
|
|
15
15
|
from . import consumers, worktree_registry
|
|
16
|
+
from .final_report_paths import final_report_markdown_path
|
|
16
17
|
from .worktree import (compute_branch_name, compute_worktree_path,
|
|
17
18
|
main_worktree_path, is_dirty_excluding_okstra,
|
|
18
19
|
nested_worktree_excludes, is_ancestor, merge_branch,
|
|
@@ -91,7 +92,7 @@ def latest_whole_task_fv_accepted(project_root, project_id: str,
|
|
|
91
92
|
if ((data.get("header") or {}).get("taskType") == "final-verification"
|
|
92
93
|
and data.get("verificationScope") == "whole-task"
|
|
93
94
|
and token == "accepted"):
|
|
94
|
-
md =
|
|
95
|
+
md = final_report_markdown_path(dj)
|
|
95
96
|
return str(md if md.is_file() else dj)
|
|
96
97
|
return ""
|
|
97
98
|
|
|
@@ -66,16 +66,14 @@ def select_and_provision_implementation_stage(
|
|
|
66
66
|
"""
|
|
67
67
|
del task_key # The registry uses the split identity fields.
|
|
68
68
|
|
|
69
|
-
from .consumers import
|
|
69
|
+
from .consumers import backfill_done_from_carry, read_stage_consumer_state
|
|
70
70
|
from . import worktree as _worktree
|
|
71
71
|
from . import worktree_registry as _reg
|
|
72
72
|
|
|
73
73
|
plan_run_root = Path(inp.approved_plan_path).resolve().parents[1]
|
|
74
74
|
backfill_done_from_carry(plan_run_root)
|
|
75
75
|
auto_reconcile_best_effort(inp, plan_run_root)
|
|
76
|
-
|
|
77
|
-
done_stages = {r["stage"] for r in consumed if r.get("status") == "done"}
|
|
78
|
-
started_stages = {r["stage"] for r in consumed if r.get("status") == "started"}
|
|
76
|
+
consumers = read_stage_consumer_state(plan_run_root)
|
|
79
77
|
reserved_stages = _reg.list_active_stage_numbers(
|
|
80
78
|
inp.project_id,
|
|
81
79
|
inp.task_group,
|
|
@@ -83,18 +81,17 @@ def select_and_provision_implementation_stage(
|
|
|
83
81
|
)
|
|
84
82
|
|
|
85
83
|
try:
|
|
86
|
-
|
|
84
|
+
selected = stage_targets.resolve_implementation_stage(
|
|
87
85
|
ctx_stage_map,
|
|
88
|
-
done_stages,
|
|
86
|
+
consumers.done_stages,
|
|
89
87
|
inp.stage,
|
|
90
|
-
started_stages=started_stages,
|
|
88
|
+
started_stages=consumers.started_stages,
|
|
91
89
|
reserved_stages=reserved_stages,
|
|
92
90
|
)
|
|
93
91
|
except stage_targets.StageTargetError as exc:
|
|
94
92
|
raise _as_implementation_stage_error(exc) from exc
|
|
95
93
|
|
|
96
|
-
|
|
97
|
-
concurrent_stages = sorted(reserved_stages - done_stages - {selected})
|
|
94
|
+
concurrent_stages = sorted(reserved_stages - consumers.done_stages - {selected})
|
|
98
95
|
|
|
99
96
|
if executor_worktree_status.startswith("skipped"):
|
|
100
97
|
head = _git_out(inp.project_root, "rev-parse", "HEAD")
|
|
@@ -133,11 +130,10 @@ def select_and_provision_implementation_stage(
|
|
|
133
130
|
selected_stage = next(
|
|
134
131
|
s for s in ctx_stage_map if s["stage_number"] == selected
|
|
135
132
|
)
|
|
136
|
-
consumer_done_rows = [r for r in consumed if r.get("status") == "done"]
|
|
137
133
|
try:
|
|
138
134
|
stage_base = stage_targets.resolve_stage_base_commit(
|
|
139
135
|
selected_stage,
|
|
140
|
-
|
|
136
|
+
consumers.done_rows,
|
|
141
137
|
anchor_base_commit=anchor,
|
|
142
138
|
candidate_base=head_sha,
|
|
143
139
|
project_root=Path(task_worktree_path),
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""Path helpers for generated final-report view artifacts."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def html_view_path(report_path: Path) -> Path:
|
|
8
|
+
"""Return the self-contained HTML view path for a final-report markdown."""
|
|
9
|
+
return report_path.with_suffix(".html")
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def user_responses_dir_for_report(report_path: Path) -> Path:
|
|
13
|
+
"""Return the sidecar directory used by the report's exported responses."""
|
|
14
|
+
return report_path.parent.parent / "user-responses"
|
|
@@ -28,6 +28,9 @@ from dataclasses import dataclass
|
|
|
28
28
|
from pathlib import Path
|
|
29
29
|
from typing import Iterable, Optional
|
|
30
30
|
|
|
31
|
+
from .final_report_paths import final_report_data_path
|
|
32
|
+
from .report_view_artifacts import html_view_path, user_responses_dir_for_report
|
|
33
|
+
|
|
31
34
|
|
|
32
35
|
def source_digest(src_md: str) -> str:
|
|
33
36
|
"""Stable identifier for a final-report markdown body. The HTML
|
|
@@ -904,7 +907,7 @@ def plan_approval_context(
|
|
|
904
907
|
validator 가 같은 판정을 공유한다. recommendedOption 이 비거나 후보에 없으면
|
|
905
908
|
첫 후보로 폴백한다 — 항상 정확히 한 옵션이 selected 가 되어 브라우저 자동선택분의
|
|
906
909
|
묵시 Export 를 차단한다."""
|
|
907
|
-
data_path =
|
|
910
|
+
data_path = final_report_data_path(src_md_path)
|
|
908
911
|
if not data_path.is_file():
|
|
909
912
|
return None
|
|
910
913
|
try:
|
|
@@ -1020,14 +1023,14 @@ def render_html_view(
|
|
|
1020
1023
|
css: str,
|
|
1021
1024
|
js: str,
|
|
1022
1025
|
) -> Path | None:
|
|
1023
|
-
"""Write
|
|
1026
|
+
"""Write the HTML view artifact for ``src_md_path`` and return its path,
|
|
1024
1027
|
or return ``None`` when the report needs no interactive view — §1
|
|
1025
1028
|
clarification rows 도 없고 Plan Approval 위젯 대상도 아닐 때
|
|
1026
1029
|
(``report_has_clarification_items`` / ``plan_approval_context``).
|
|
1027
1030
|
Idempotent — overwrites an existing html sibling, and removes a stale
|
|
1028
1031
|
one when the report no longer needs a view."""
|
|
1029
1032
|
src_text = src_md_path.read_text(encoding="utf-8")
|
|
1030
|
-
html_path =
|
|
1033
|
+
html_path = html_view_path(src_md_path)
|
|
1031
1034
|
# Fail-closed on §1 heading drift: when a `## 1. Clarification Items`
|
|
1032
1035
|
# heading exists but its strict form does not parse,
|
|
1033
1036
|
# `report_has_clarification_items` returns False (lenient parse), so a
|
|
@@ -1052,5 +1055,5 @@ def render_html_view(
|
|
|
1052
1055
|
html_path.write_text(html_text, encoding="utf-8")
|
|
1053
1056
|
# 사용자 확인(폼/승인)이 필요한 보고서 — Export 파일의 저장 위치를 미리
|
|
1054
1057
|
# 만들어 사용자가 디렉토리를 만들 필요가 없게 한다 (reports/ 의 sibling).
|
|
1055
|
-
(src_md_path
|
|
1058
|
+
user_responses_dir_for_report(src_md_path).mkdir(parents=True, exist_ok=True)
|
|
1056
1059
|
return html_path
|
|
@@ -43,6 +43,7 @@ from .material import (
|
|
|
43
43
|
resolve_related_tasks,
|
|
44
44
|
)
|
|
45
45
|
from .final_report_schema import load_schema
|
|
46
|
+
from .final_report_paths import final_report_data_path as _final_report_data_path
|
|
46
47
|
from .lead_events import LeadEvent, append_lead_event
|
|
47
48
|
from .lead_runtime import ALLOWED_LEAD_RUNTIMES
|
|
48
49
|
from .models import ModelAssignment, resolve_model_metadata
|
|
@@ -146,13 +147,6 @@ def _extract_frontmatter_block(body: str) -> str | None:
|
|
|
146
147
|
return m.group(1) if m else None
|
|
147
148
|
|
|
148
149
|
|
|
149
|
-
def _final_report_data_path(report_path: Path) -> Path:
|
|
150
|
-
name = report_path.name
|
|
151
|
-
if name.endswith(".md"):
|
|
152
|
-
return report_path.with_name(name[:-3] + ".data.json")
|
|
153
|
-
return report_path.with_suffix(".data.json")
|
|
154
|
-
|
|
155
|
-
|
|
156
150
|
def _load_final_report_data_if_present(report_path: Path) -> tuple[Path, dict] | None:
|
|
157
151
|
data_path = _final_report_data_path(report_path)
|
|
158
152
|
if not data_path.is_file():
|
|
@@ -1514,7 +1508,7 @@ def _reserve_final_verification_target(
|
|
|
1514
1508
|
"""final-verification 의 검증 target 을 registry/consumers/git 에서
|
|
1515
1509
|
해소하고 gate 를 강제한다. 위반 시 PrepareError. 결과를 ctx 의
|
|
1516
1510
|
VERIFICATION_* 키로 주입한다."""
|
|
1517
|
-
from .consumers import
|
|
1511
|
+
from .consumers import backfill_done_from_carry, read_stage_consumer_state
|
|
1518
1512
|
from . import worktree_registry as _reg
|
|
1519
1513
|
|
|
1520
1514
|
plan_run_root = plan_run_root_from_approved_plan(inp.approved_plan_path)
|
|
@@ -1522,8 +1516,7 @@ def _reserve_final_verification_target(
|
|
|
1522
1516
|
# rows before the whole-task gate checks every stage.
|
|
1523
1517
|
backfill_done_from_carry(plan_run_root)
|
|
1524
1518
|
_auto_reconcile_best_effort(inp, plan_run_root)
|
|
1525
|
-
done_rows =
|
|
1526
|
-
if r.get("status") == "done"]
|
|
1519
|
+
done_rows = read_stage_consumer_state(plan_run_root).done_rows
|
|
1527
1520
|
|
|
1528
1521
|
if inp.stage and inp.stage != "auto":
|
|
1529
1522
|
n = int(inp.stage)
|
|
@@ -98,6 +98,24 @@ def resolve_effective_stages(
|
|
|
98
98
|
return batch
|
|
99
99
|
|
|
100
100
|
|
|
101
|
+
def resolve_implementation_stage(
|
|
102
|
+
stages: list[dict[str, Any]],
|
|
103
|
+
done_stages: set[int],
|
|
104
|
+
requested: str,
|
|
105
|
+
*,
|
|
106
|
+
started_stages: set[int] | None = None,
|
|
107
|
+
reserved_stages: set[int] | None = None,
|
|
108
|
+
) -> int:
|
|
109
|
+
"""Return the single Stage Map stage selected for one implementation run."""
|
|
110
|
+
return resolve_effective_stages(
|
|
111
|
+
stages,
|
|
112
|
+
done_stages,
|
|
113
|
+
requested,
|
|
114
|
+
started_stages=started_stages,
|
|
115
|
+
reserved_stages=reserved_stages,
|
|
116
|
+
)[0]
|
|
117
|
+
|
|
118
|
+
|
|
101
119
|
def order_stage_closure(
|
|
102
120
|
stages, selected: set[int], done: set[int],
|
|
103
121
|
) -> list[int]:
|
|
@@ -34,6 +34,7 @@ from okstra_ctl.models import (
|
|
|
34
34
|
resolve_model_metadata,
|
|
35
35
|
)
|
|
36
36
|
from okstra_ctl.clarification_items import scan_approval_gate, user_response_sidecars
|
|
37
|
+
from okstra_ctl.final_report_paths import final_report_data_path
|
|
37
38
|
from okstra_ctl.pr_template import PrTemplateError, resolve_pr_template_path
|
|
38
39
|
from okstra_ctl.run import (
|
|
39
40
|
APPROVED_FRONTMATTER_PATTERN,
|
|
@@ -60,6 +61,12 @@ from okstra_ctl.workers import (
|
|
|
60
61
|
validate_workers_against_profile,
|
|
61
62
|
)
|
|
62
63
|
from okstra_ctl.workflow import PHASE_SEQUENCE
|
|
64
|
+
from okstra_ctl.wizard_stage_intent import (
|
|
65
|
+
WHOLE_TASK_STAGE,
|
|
66
|
+
WizardStageIntentError,
|
|
67
|
+
resolve_wizard_stage_intent,
|
|
68
|
+
wizard_stage_confirmation_label,
|
|
69
|
+
)
|
|
63
70
|
from okstra_ctl import fix_cycles, worktree_registry
|
|
64
71
|
from okstra_ctl.worktree import (
|
|
65
72
|
compute_worktree_path,
|
|
@@ -607,7 +614,7 @@ def _find_html_approval_sidecar(
|
|
|
607
614
|
def _validate_sidecar_option(plan_path: Path, option_name: str, errors_t: dict) -> None:
|
|
608
615
|
"""sidecar 의 옵션 이름이 plan data.json 의 optionCandidates 에 있는지
|
|
609
616
|
검증한다. 없으면 유효 후보를 나열하며 거부한다 (fail-closed)."""
|
|
610
|
-
data_path =
|
|
617
|
+
data_path = final_report_data_path(plan_path)
|
|
611
618
|
candidates: list[str] = []
|
|
612
619
|
if data_path.is_file():
|
|
613
620
|
try:
|
|
@@ -1652,7 +1659,6 @@ _REUSE_LAST_TOKEN = "__reuse_last__"
|
|
|
1652
1659
|
_SIBLINGS_TOKEN = "__siblings__"
|
|
1653
1660
|
_LATEST_REPORT_TOKEN = "__latest_report__"
|
|
1654
1661
|
_PROJECT_DEFAULT_TOKEN = "__project_default__"
|
|
1655
|
-
WHOLE_TASK_STAGE = "__whole_task__"
|
|
1656
1662
|
ALL_STAGES = "__all_stages__"
|
|
1657
1663
|
|
|
1658
1664
|
|
|
@@ -3488,21 +3494,14 @@ def render_args(state: WizardState) -> dict[str, str]:
|
|
|
3488
3494
|
if state.reuse_worktree or state.task_type == "final-verification"
|
|
3489
3495
|
else state.base_ref
|
|
3490
3496
|
)
|
|
3491
|
-
|
|
3492
|
-
|
|
3493
|
-
|
|
3494
|
-
|
|
3495
|
-
|
|
3496
|
-
|
|
3497
|
-
|
|
3498
|
-
|
|
3499
|
-
raise WizardError(
|
|
3500
|
-
"final-verification requires an explicit stage number"
|
|
3501
|
-
)
|
|
3502
|
-
else:
|
|
3503
|
-
stage = state.selected_stage
|
|
3504
|
-
else:
|
|
3505
|
-
stage = ""
|
|
3497
|
+
try:
|
|
3498
|
+
stage_intent = resolve_wizard_stage_intent(
|
|
3499
|
+
task_type=state.task_type,
|
|
3500
|
+
selected_stage=state.selected_stage,
|
|
3501
|
+
selected_stages=state.selected_stages,
|
|
3502
|
+
)
|
|
3503
|
+
except WizardStageIntentError as exc:
|
|
3504
|
+
raise WizardError(str(exc)) from exc
|
|
3506
3505
|
pr_template = (
|
|
3507
3506
|
state.pr_template_path
|
|
3508
3507
|
if state.task_type == "release-handoff"
|
|
@@ -3518,9 +3517,9 @@ def render_args(state: WizardState) -> dict[str, str]:
|
|
|
3518
3517
|
"executor": state.executor,
|
|
3519
3518
|
"critic": state.critic,
|
|
3520
3519
|
"approved-plan": state.approved_plan_path,
|
|
3521
|
-
"stage": stage,
|
|
3520
|
+
"stage": stage_intent.stage,
|
|
3522
3521
|
"stages": state.handoff_stages,
|
|
3523
|
-
"chain-stages": chain_stages,
|
|
3522
|
+
"chain-stages": stage_intent.chain_stages,
|
|
3524
3523
|
"base-ref": base_ref,
|
|
3525
3524
|
"workers": workers,
|
|
3526
3525
|
"directive": state.directive,
|
|
@@ -3581,12 +3580,12 @@ def confirmation_block(state: WizardState) -> str:
|
|
|
3581
3580
|
lines.append(f" critic : {state.critic or '(off)'}")
|
|
3582
3581
|
if state.task_type in _STAGE_SCOPED_TASK_TYPES:
|
|
3583
3582
|
lines.append(f" approved-plan : {state.approved_plan_path}")
|
|
3584
|
-
stage = (
|
|
3585
|
-
|
|
3586
|
-
|
|
3587
|
-
|
|
3588
|
-
|
|
3589
|
-
|
|
3583
|
+
stage = wizard_stage_confirmation_label(
|
|
3584
|
+
task_type=state.task_type,
|
|
3585
|
+
selected_stage=state.selected_stage,
|
|
3586
|
+
whole_task_label=_msg(
|
|
3587
|
+
state.workspace_root, "confirmation", "stage_whole_task"
|
|
3588
|
+
),
|
|
3590
3589
|
)
|
|
3591
3590
|
lines.append(f" stage : {stage}")
|
|
3592
3591
|
if state.clarification_response_path:
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""Stage-related intent projection for okstra-run wizard output."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
WHOLE_TASK_STAGE = "__whole_task__"
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class WizardStageIntentError(ValueError):
|
|
11
|
+
"""Raised when wizard state cannot produce stage-related render args."""
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass(frozen=True)
|
|
15
|
+
class WizardStageIntent:
|
|
16
|
+
stage: str
|
|
17
|
+
chain_stages: str
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def resolve_wizard_stage_intent(
|
|
21
|
+
*,
|
|
22
|
+
task_type: str,
|
|
23
|
+
selected_stage: str,
|
|
24
|
+
selected_stages: str,
|
|
25
|
+
) -> WizardStageIntent:
|
|
26
|
+
"""Return the stage-related ``okstra render-bundle`` arguments."""
|
|
27
|
+
if task_type == "implementation":
|
|
28
|
+
stage = selected_stage or "auto"
|
|
29
|
+
return WizardStageIntent(stage=stage, chain_stages=selected_stages or stage)
|
|
30
|
+
|
|
31
|
+
if task_type == "final-verification":
|
|
32
|
+
if selected_stage == WHOLE_TASK_STAGE:
|
|
33
|
+
return WizardStageIntent(stage="", chain_stages="")
|
|
34
|
+
if not selected_stage or selected_stage == "auto":
|
|
35
|
+
raise WizardStageIntentError(
|
|
36
|
+
"final-verification requires an explicit stage number"
|
|
37
|
+
)
|
|
38
|
+
return WizardStageIntent(stage=selected_stage, chain_stages="")
|
|
39
|
+
|
|
40
|
+
return WizardStageIntent(stage="", chain_stages="")
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def wizard_stage_confirmation_label(
|
|
44
|
+
*,
|
|
45
|
+
task_type: str,
|
|
46
|
+
selected_stage: str,
|
|
47
|
+
whole_task_label: str,
|
|
48
|
+
) -> str:
|
|
49
|
+
"""Return the human-readable stage value for the confirmation block."""
|
|
50
|
+
if selected_stage == WHOLE_TASK_STAGE:
|
|
51
|
+
return whole_task_label
|
|
52
|
+
if selected_stage:
|
|
53
|
+
return selected_stage
|
|
54
|
+
return "auto" if task_type == "implementation" else "(not selected)"
|
|
@@ -14,6 +14,7 @@ _HERE = Path(__file__).resolve().parent.parent
|
|
|
14
14
|
if str(_HERE) not in sys.path:
|
|
15
15
|
sys.path.insert(0, str(_HERE))
|
|
16
16
|
|
|
17
|
+
from okstra_ctl.final_report_paths import final_report_markdown_path # noqa: E402
|
|
17
18
|
from okstra_ctl.render_final_report import ( # noqa: E402
|
|
18
19
|
FinalReportRenderError,
|
|
19
20
|
render_to_file,
|
|
@@ -209,11 +210,7 @@ def populate_and_render(
|
|
|
209
210
|
changes = populate_data_token_cells(data_path, team_state)
|
|
210
211
|
|
|
211
212
|
if output_path is None:
|
|
212
|
-
|
|
213
|
-
if name.endswith(".data.json"):
|
|
214
|
-
output_path = data_path.with_name(name[: -len(".data.json")] + ".md")
|
|
215
|
-
else:
|
|
216
|
-
output_path = data_path.with_suffix(".md")
|
|
213
|
+
output_path = final_report_markdown_path(data_path)
|
|
217
214
|
|
|
218
215
|
try:
|
|
219
216
|
bytes_written = render_to_file(data_path, output_path)
|
|
@@ -38,6 +38,7 @@ from okstra_ctl.report_views import ( # noqa: E402
|
|
|
38
38
|
plan_approval_context,
|
|
39
39
|
source_digest,
|
|
40
40
|
)
|
|
41
|
+
from okstra_ctl.report_view_artifacts import html_view_path # noqa: E402
|
|
41
42
|
|
|
42
43
|
|
|
43
44
|
_EXTERNAL_URL_RE = re.compile(
|
|
@@ -83,7 +84,7 @@ def validate(report_path: Path) -> list[str]:
|
|
|
83
84
|
return [f"final-report not found: {report_path}"]
|
|
84
85
|
|
|
85
86
|
md = report_path.read_text(encoding="utf-8")
|
|
86
|
-
html_path =
|
|
87
|
+
html_path = html_view_path(report_path)
|
|
87
88
|
# §1 헤딩이 있는데 파싱이 실패하면 md_ids 가 빈 []이 되어 "clarification 없음
|
|
88
89
|
# → skip" 으로 흘러 HTML form parity 게이트가 조용히 열린다. fail-closed.
|
|
89
90
|
if section_1_present_but_unparsed(md):
|
|
@@ -44,6 +44,7 @@ from okstra_ctl.md_table import ( # noqa: E402
|
|
|
44
44
|
is_separator_row as _is_markdown_separator,
|
|
45
45
|
split_pipe_row as _split_pipe_row,
|
|
46
46
|
)
|
|
47
|
+
from okstra_ctl.final_report_paths import final_report_data_path as _data_path_for # noqa: E402
|
|
47
48
|
|
|
48
49
|
TERMINAL_STATUSES = {"completed", "timeout", "error", "not-run"}
|
|
49
50
|
ATTEMPTED_STATUSES = {"completed", "timeout", "error"}
|
|
@@ -1414,17 +1415,6 @@ def _validate_verdict_card_consistency(content: str, failures: list[str]) -> Non
|
|
|
1414
1415
|
)
|
|
1415
1416
|
|
|
1416
1417
|
|
|
1417
|
-
def _data_path_for(report_path: Path) -> Path:
|
|
1418
|
-
"""Derive the final-report data.json sibling from the markdown path.
|
|
1419
|
-
``foo.md`` → ``foo.data.json``. The data.json is the canonical JSON
|
|
1420
|
-
SSOT; the markdown is rendered from it.
|
|
1421
|
-
"""
|
|
1422
|
-
name = report_path.name
|
|
1423
|
-
if name.endswith(".md"):
|
|
1424
|
-
return report_path.with_name(name[:-3] + ".data.json")
|
|
1425
|
-
return report_path.with_suffix(".data.json")
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
1418
|
def _load_final_report_data(report_path: Path) -> dict:
|
|
1429
1419
|
"""Best-effort parse of the final-report data.json sibling. Returns {} when
|
|
1430
1420
|
absent or unparseable — those conditions are already surfaced as failures by
|
|
@@ -13,9 +13,9 @@ const SKILLS_MANIFEST_REL = "installed-skills.json";
|
|
|
13
13
|
const AGENTS_MANIFEST_REL = "installed-agents.json";
|
|
14
14
|
const USER_HOME = homedir();
|
|
15
15
|
const CLAUDE_HOME = join(USER_HOME, ".claude");
|
|
16
|
-
const
|
|
16
|
+
const AGENTS_HOME = join(USER_HOME, ".agents");
|
|
17
17
|
const CLAUDE_SKILLS_DIR = join(CLAUDE_HOME, "skills");
|
|
18
|
-
const
|
|
18
|
+
const AGENTS_SKILLS_DIR = join(AGENTS_HOME, "skills");
|
|
19
19
|
const CLAUDE_AGENTS_DIR = join(CLAUDE_HOME, "agents");
|
|
20
20
|
|
|
21
21
|
// Source template (relative to runtime root in copy mode, or repo root in link mode).
|
|
@@ -80,7 +80,7 @@ Effect (copy mode):
|
|
|
80
80
|
${"$HOME"}/.okstra/prompts <- runtime/prompts (lead contracts + coding-preflight pack)
|
|
81
81
|
${"$HOME"}/.okstra/templates/settings.local.json <- runtime/templates/reports/settings.template.json
|
|
82
82
|
${"$HOME"}/.claude/skills/<name> <- skills when ${"$HOME"}/.claude exists
|
|
83
|
-
${"$HOME"}/.
|
|
83
|
+
${"$HOME"}/.agents/skills/<name> <- skills (created when needed)
|
|
84
84
|
${"$HOME"}/.claude/agents/<worker>.md <- worker agents when ${"$HOME"}/.claude exists
|
|
85
85
|
${"$HOME"}/.okstra/installed-runtimes.json <- install request/resolution manifest
|
|
86
86
|
${"$HOME"}/.okstra/installed-skills.json <- target-aware manifest of installed skills
|
|
@@ -93,7 +93,7 @@ Effect (link mode):
|
|
|
93
93
|
${"$HOME"}/.okstra/prompts -> <repo>/prompts
|
|
94
94
|
${"$HOME"}/.okstra/templates/settings.local.json -> <repo>/templates/reports/settings.template.json
|
|
95
95
|
${"$HOME"}/.claude/skills/<name> -> <repo>/skills/<name> when ${"$HOME"}/.claude exists
|
|
96
|
-
${"$HOME"}/.
|
|
96
|
+
${"$HOME"}/.agents/skills/<name> -> <repo>/skills/<name> (created when needed)
|
|
97
97
|
${"$HOME"}/.claude/agents/<worker>.md -> <repo>/agents/workers/<worker>.md when ${"$HOME"}/.claude exists
|
|
98
98
|
${"$HOME"}/.okstra/dev-link <- <repo> path stamp
|
|
99
99
|
${"$HOME"}/.okstra/version <- installed package version stamp
|
|
@@ -401,7 +401,7 @@ async function detectSkillTargets() {
|
|
|
401
401
|
if (hasClaude) {
|
|
402
402
|
targets.push({ provider: "claude", home: CLAUDE_HOME, skillsDir: CLAUDE_SKILLS_DIR });
|
|
403
403
|
}
|
|
404
|
-
targets.push({ provider: "agent", home:
|
|
404
|
+
targets.push({ provider: "agent", home: AGENTS_HOME, skillsDir: AGENTS_SKILLS_DIR });
|
|
405
405
|
return targets;
|
|
406
406
|
}
|
|
407
407
|
|
|
@@ -40,13 +40,13 @@ const FALLBACK_AGENT_NAMES = [
|
|
|
40
40
|
|
|
41
41
|
const USER_HOME = homedir();
|
|
42
42
|
const CLAUDE_HOME = join(USER_HOME, ".claude");
|
|
43
|
-
const
|
|
43
|
+
const AGENTS_HOME = join(USER_HOME, ".agents");
|
|
44
44
|
const CLAUDE_SKILLS_DIR = join(CLAUDE_HOME, "skills");
|
|
45
|
-
const
|
|
45
|
+
const AGENTS_SKILLS_DIR = join(AGENTS_HOME, "skills");
|
|
46
46
|
const CLAUDE_AGENTS_DIR = join(CLAUDE_HOME, "agents");
|
|
47
47
|
const FALLBACK_SKILL_ROOTS = [
|
|
48
48
|
CLAUDE_SKILLS_DIR,
|
|
49
|
-
|
|
49
|
+
AGENTS_SKILLS_DIR,
|
|
50
50
|
];
|
|
51
51
|
const SKILLS_MANIFEST_REL = "installed-skills.json";
|
|
52
52
|
const AGENTS_MANIFEST_REL = "installed-agents.json";
|
|
@@ -60,7 +60,7 @@ Usage:
|
|
|
60
60
|
installed-skills.json, installed-agents.json} AND
|
|
61
61
|
every skill target recorded in
|
|
62
62
|
installed-skills.json
|
|
63
|
-
(~/.claude/skills or ~/.
|
|
63
|
+
(~/.claude/skills or ~/.agents/skills).
|
|
64
64
|
Claude worker agents are
|
|
65
65
|
removed from ~/.claude/agents when installed.
|
|
66
66
|
Missing manifests fall back to hard-coded
|