okstra 0.102.2 → 0.102.4
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 +8 -3
- package/docs/kr/architecture.md +12 -13
- 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/lib/okstra/interactive.sh +12 -0
- 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 +6 -13
- 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 +8 -13
- 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_outcome.py +271 -0
- package/runtime/python/okstra_ctl/implementation_stage.py +7 -11
- package/runtime/python/okstra_ctl/path_hints.py +665 -0
- package/runtime/python/okstra_ctl/render.py +4 -3
- 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/run_context.py +4 -2
- package/runtime/python/okstra_ctl/stage_targets.py +18 -0
- package/runtime/python/okstra_ctl/wizard.py +37 -26
- package/runtime/python/okstra_ctl/wizard_stage_intent.py +54 -0
- package/runtime/python/okstra_project/state.py +40 -2
- 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
|
@@ -31,6 +31,7 @@ from okstra_project.dirs import OKSTRA_DIR_NAME, project_json_path
|
|
|
31
31
|
from . import fix_cycles
|
|
32
32
|
from .paths import okstra_home
|
|
33
33
|
from .lead_runtime import lead_runtime_info
|
|
34
|
+
from .path_hints import compact_active_run_context, hydrate_run_context
|
|
34
35
|
from .workflow import DEFAULT_NEXT_PHASE, PHASE_SEQUENCE
|
|
35
36
|
|
|
36
37
|
|
|
@@ -56,8 +57,8 @@ def _load_ctx(ctx_arg: str) -> dict:
|
|
|
56
57
|
"""
|
|
57
58
|
s = ctx_arg.lstrip()
|
|
58
59
|
if s.startswith("{"):
|
|
59
|
-
return json.loads(ctx_arg)
|
|
60
|
-
return json.loads(Path(ctx_arg).read_text(encoding="utf-8"))
|
|
60
|
+
return hydrate_run_context(json.loads(ctx_arg))
|
|
61
|
+
return hydrate_run_context(json.loads(Path(ctx_arg).read_text(encoding="utf-8")))
|
|
61
62
|
|
|
62
63
|
|
|
63
64
|
def _write_text(path: Path, text: str) -> None:
|
|
@@ -434,7 +435,7 @@ def render_active_run_context(active_context_path: str, ctx: dict) -> None:
|
|
|
434
435
|
"sourceArtifacts": _active_source_artifacts(ctx),
|
|
435
436
|
"lazyReadPlan": _active_lazy_read_plan(),
|
|
436
437
|
}
|
|
437
|
-
_write_json(Path(active_context_path), payload)
|
|
438
|
+
_write_json(Path(active_context_path), compact_active_run_context(ctx, payload))
|
|
438
439
|
|
|
439
440
|
|
|
440
441
|
def render_team_state(team_state_path: str, ctx: dict) -> None:
|
|
@@ -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)
|
|
@@ -25,6 +25,7 @@ from typing import Iterator, Optional
|
|
|
25
25
|
|
|
26
26
|
from okstra_project.dirs import okstra_home
|
|
27
27
|
|
|
28
|
+
from .path_hints import compact_run_context, hydrate_run_context
|
|
28
29
|
from .paths import compute_run_paths, task_runs_dir
|
|
29
30
|
|
|
30
31
|
|
|
@@ -188,7 +189,7 @@ def compute_and_write_run_context(
|
|
|
188
189
|
str(ctx_path.relative_to(project_root.resolve()))
|
|
189
190
|
if ctx_path.resolve().is_relative_to(project_root.resolve())
|
|
190
191
|
else str(ctx_path))
|
|
191
|
-
_atomic_write_json(ctx_path, ctx)
|
|
192
|
+
_atomic_write_json(ctx_path, compact_run_context(ctx))
|
|
192
193
|
return ctx
|
|
193
194
|
|
|
194
195
|
|
|
@@ -225,7 +226,8 @@ def read_run_context(run_manifests_dir: Path, task_type_segment: str,
|
|
|
225
226
|
path = Path(run_manifests_dir) / _run_context_filename(task_type_segment, seq)
|
|
226
227
|
if not path.is_file():
|
|
227
228
|
return None
|
|
228
|
-
|
|
229
|
+
payload = json.loads(path.read_text(encoding="utf-8"))
|
|
230
|
+
return hydrate_run_context(payload)
|
|
229
231
|
|
|
230
232
|
|
|
231
233
|
def read_run_inputs(run_manifests_dir: Path, task_type_segment: str,
|
|
@@ -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:
|
|
@@ -1046,6 +1053,18 @@ def _opt(value: str, label: str = "", description: str = "") -> Option:
|
|
|
1046
1053
|
|
|
1047
1054
|
# --- builders ---
|
|
1048
1055
|
|
|
1056
|
+
def _contract_outcome_suffix(entry: dict) -> str:
|
|
1057
|
+
outcome = entry.get("phaseOutcome")
|
|
1058
|
+
implementation = outcome.get("implementation") if isinstance(outcome, dict) else {}
|
|
1059
|
+
if not isinstance(implementation, dict):
|
|
1060
|
+
return ""
|
|
1061
|
+
if implementation.get("state") != "completed":
|
|
1062
|
+
return ""
|
|
1063
|
+
if entry.get("currentStatus") == "contract-violated":
|
|
1064
|
+
return " · implementation complete; contract warnings"
|
|
1065
|
+
return ""
|
|
1066
|
+
|
|
1067
|
+
|
|
1049
1068
|
def _build_task_pick(state: WizardState) -> Prompt:
|
|
1050
1069
|
t = _p(state.workspace_root, "task_pick")
|
|
1051
1070
|
project_root = Path(state.project_root)
|
|
@@ -1063,7 +1082,7 @@ def _build_task_pick(state: WizardState) -> Prompt:
|
|
|
1063
1082
|
phase = entry.get("currentPhase") or ttype
|
|
1064
1083
|
nxt = entry.get("nextRecommendedPhase") or ""
|
|
1065
1084
|
suffix = latest_suffix if key == latest_key else ""
|
|
1066
|
-
label = f"{key} · {phase} · next: {nxt}{suffix}"
|
|
1085
|
+
label = f"{key} · {phase} · next: {nxt}{_contract_outcome_suffix(entry)}{suffix}"
|
|
1067
1086
|
options.append(_opt(value=key, label=label))
|
|
1068
1087
|
for value, label in _static_options(t):
|
|
1069
1088
|
options.append(_opt(value=value, label=label))
|
|
@@ -1652,7 +1671,6 @@ _REUSE_LAST_TOKEN = "__reuse_last__"
|
|
|
1652
1671
|
_SIBLINGS_TOKEN = "__siblings__"
|
|
1653
1672
|
_LATEST_REPORT_TOKEN = "__latest_report__"
|
|
1654
1673
|
_PROJECT_DEFAULT_TOKEN = "__project_default__"
|
|
1655
|
-
WHOLE_TASK_STAGE = "__whole_task__"
|
|
1656
1674
|
ALL_STAGES = "__all_stages__"
|
|
1657
1675
|
|
|
1658
1676
|
|
|
@@ -3488,21 +3506,14 @@ def render_args(state: WizardState) -> dict[str, str]:
|
|
|
3488
3506
|
if state.reuse_worktree or state.task_type == "final-verification"
|
|
3489
3507
|
else state.base_ref
|
|
3490
3508
|
)
|
|
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 = ""
|
|
3509
|
+
try:
|
|
3510
|
+
stage_intent = resolve_wizard_stage_intent(
|
|
3511
|
+
task_type=state.task_type,
|
|
3512
|
+
selected_stage=state.selected_stage,
|
|
3513
|
+
selected_stages=state.selected_stages,
|
|
3514
|
+
)
|
|
3515
|
+
except WizardStageIntentError as exc:
|
|
3516
|
+
raise WizardError(str(exc)) from exc
|
|
3506
3517
|
pr_template = (
|
|
3507
3518
|
state.pr_template_path
|
|
3508
3519
|
if state.task_type == "release-handoff"
|
|
@@ -3518,9 +3529,9 @@ def render_args(state: WizardState) -> dict[str, str]:
|
|
|
3518
3529
|
"executor": state.executor,
|
|
3519
3530
|
"critic": state.critic,
|
|
3520
3531
|
"approved-plan": state.approved_plan_path,
|
|
3521
|
-
"stage": stage,
|
|
3532
|
+
"stage": stage_intent.stage,
|
|
3522
3533
|
"stages": state.handoff_stages,
|
|
3523
|
-
"chain-stages": chain_stages,
|
|
3534
|
+
"chain-stages": stage_intent.chain_stages,
|
|
3524
3535
|
"base-ref": base_ref,
|
|
3525
3536
|
"workers": workers,
|
|
3526
3537
|
"directive": state.directive,
|
|
@@ -3581,12 +3592,12 @@ def confirmation_block(state: WizardState) -> str:
|
|
|
3581
3592
|
lines.append(f" critic : {state.critic or '(off)'}")
|
|
3582
3593
|
if state.task_type in _STAGE_SCOPED_TASK_TYPES:
|
|
3583
3594
|
lines.append(f" approved-plan : {state.approved_plan_path}")
|
|
3584
|
-
stage = (
|
|
3585
|
-
|
|
3586
|
-
|
|
3587
|
-
|
|
3588
|
-
|
|
3589
|
-
|
|
3595
|
+
stage = wizard_stage_confirmation_label(
|
|
3596
|
+
task_type=state.task_type,
|
|
3597
|
+
selected_stage=state.selected_stage,
|
|
3598
|
+
whole_task_label=_msg(
|
|
3599
|
+
state.workspace_root, "confirmation", "stage_whole_task"
|
|
3600
|
+
),
|
|
3590
3601
|
)
|
|
3591
3602
|
lines.append(f" stage : {stage}")
|
|
3592
3603
|
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)"
|
|
@@ -1,10 +1,13 @@
|
|
|
1
|
-
"""Read-
|
|
1
|
+
"""Read-side state accessors over okstra's on-disk authority files.
|
|
2
2
|
|
|
3
3
|
Skills, okstra.sh, and okstra-ctl all read the same files through this module
|
|
4
4
|
so identity / discovery values are derived from disk on every call instead of
|
|
5
5
|
being passed via process environment. This keeps concurrent claude-code skill
|
|
6
6
|
invocations isolated — each call reads the authoritative state at the moment
|
|
7
7
|
it runs and never sees stale snapshots inherited from a parent process.
|
|
8
|
+
Some accessors also run best-effort reconciliation before returning derived
|
|
9
|
+
phase pointers, so stale catalog entries can be healed from authoritative
|
|
10
|
+
task artifacts.
|
|
8
11
|
|
|
9
12
|
권위 파일 매핑 (okstra root = <PROJECT_ROOT>/<OKSTRA_DIR_NAME>):
|
|
10
13
|
- <okstra root>/project.json
|
|
@@ -78,6 +81,38 @@ def read_task_manifest(task_root: Path) -> Optional[dict]:
|
|
|
78
81
|
return _load_json(Path(task_root) / "task-manifest.json")
|
|
79
82
|
|
|
80
83
|
|
|
84
|
+
def _reconcile_task_root_best_effort(project_root: Path, task_root: Path) -> None:
|
|
85
|
+
try:
|
|
86
|
+
from okstra_ctl.implementation_outcome import reconcile_implementation_outcome
|
|
87
|
+
|
|
88
|
+
reconcile_implementation_outcome(project_root, task_root)
|
|
89
|
+
except Exception:
|
|
90
|
+
return
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _entry_with_manifest_state(entry: dict, task_root: Path) -> dict:
|
|
94
|
+
out = {**entry}
|
|
95
|
+
manifest = read_task_manifest(task_root) or {}
|
|
96
|
+
workflow = manifest.get("workflow") if isinstance(manifest.get("workflow"), dict) else {}
|
|
97
|
+
for source, target in (
|
|
98
|
+
("currentPhase", "currentPhase"),
|
|
99
|
+
("currentPhaseState", "currentPhaseState"),
|
|
100
|
+
("lastCompletedPhase", "lastCompletedPhase"),
|
|
101
|
+
("nextRecommendedPhase", "nextRecommendedPhase"),
|
|
102
|
+
):
|
|
103
|
+
value = workflow.get(source)
|
|
104
|
+
if isinstance(value, str):
|
|
105
|
+
out[target] = value
|
|
106
|
+
for field in ("currentStatus", "latestRunStatus", "latestReportPath"):
|
|
107
|
+
value = manifest.get(field)
|
|
108
|
+
if isinstance(value, str):
|
|
109
|
+
out[field] = value
|
|
110
|
+
phase_outcome = manifest.get("phaseOutcome")
|
|
111
|
+
if isinstance(phase_outcome, dict):
|
|
112
|
+
out["phaseOutcome"] = phase_outcome
|
|
113
|
+
return out
|
|
114
|
+
|
|
115
|
+
|
|
81
116
|
def parse_task_key(task_key: str) -> tuple[str, str, str]:
|
|
82
117
|
"""`project-id:task-group:task-id` 를 3-tuple 로 분해.
|
|
83
118
|
|
|
@@ -111,10 +146,12 @@ def find_task_root(project_root: Path, task_key: str) -> Optional[Path]:
|
|
|
111
146
|
if isinstance(rel, str) and rel:
|
|
112
147
|
abs_path = project_root / rel if not Path(rel).is_absolute() else Path(rel)
|
|
113
148
|
if abs_path.is_dir():
|
|
149
|
+
_reconcile_task_root_best_effort(project_root, abs_path)
|
|
114
150
|
return abs_path
|
|
115
151
|
|
|
116
152
|
slug_path = project_root / TASKS_RELATIVE / slugify(task_group) / slugify(task_id)
|
|
117
153
|
if slug_path.is_dir():
|
|
154
|
+
_reconcile_task_root_best_effort(project_root, slug_path)
|
|
118
155
|
return slug_path
|
|
119
156
|
return None
|
|
120
157
|
|
|
@@ -180,7 +217,8 @@ def list_project_tasks(
|
|
|
180
217
|
root = find_task_root(project_root, entry_key)
|
|
181
218
|
if root is None:
|
|
182
219
|
continue
|
|
183
|
-
|
|
220
|
+
reconciled_entry = _entry_with_manifest_state(entry, root)
|
|
221
|
+
out.append({**reconciled_entry, "_resolvedTaskRoot": str(root)})
|
|
184
222
|
if limit is not None and limit >= 0:
|
|
185
223
|
out = out[:limit]
|
|
186
224
|
return out
|
|
@@ -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
|