okstra 0.64.1 → 0.65.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/okstra +1 -0
- package/docs/kr/architecture.md +2 -0
- package/docs/kr/cli.md +11 -3
- package/docs/kr/performance-improvement-plan-v2.md +2 -1
- package/docs/project-structure-overview.md +1 -0
- package/docs/superpowers/plans/2026-06-10-p6-token-usage-incremental.md +1029 -0
- package/docs/superpowers/specs/2026-06-10-blocking-contract-posthoc-conformance-design.md +168 -0
- package/package.json +1 -1
- package/runtime/BUILD.json +2 -2
- package/runtime/agents/SKILL.md +3 -1
- package/runtime/agents/workers/claude-worker.md +1 -1
- package/runtime/agents/workers/codex-worker.md +1 -0
- package/runtime/agents/workers/gemini-worker.md +1 -0
- package/runtime/bin/lib/okstra/cli.sh +4 -0
- package/runtime/bin/lib/okstra/globals.sh +1 -0
- package/runtime/bin/lib/okstra/usage.sh +4 -1
- package/runtime/bin/okstra.sh +1 -0
- package/runtime/prompts/profiles/_implementation-executor.md +1 -0
- package/runtime/python/okstra_ctl/clarification_items.py +96 -37
- package/runtime/python/okstra_ctl/context_cost.py +86 -8
- package/runtime/python/okstra_ctl/locks.py +32 -0
- package/runtime/python/okstra_ctl/migrate.py +45 -6
- package/runtime/python/okstra_ctl/pr_template.py +2 -7
- package/runtime/python/okstra_ctl/run.py +58 -44
- package/runtime/python/okstra_ctl/run_context.py +3 -8
- package/runtime/python/okstra_ctl/seeding.py +25 -18
- package/runtime/python/okstra_ctl/wizard.py +8 -10
- package/runtime/python/okstra_ctl/worktree.py +13 -0
- package/runtime/python/okstra_project/dirs.py +10 -1
- package/runtime/python/okstra_token_usage/claude.py +226 -61
- package/runtime/python/okstra_token_usage/cli.py +10 -1
- package/runtime/python/okstra_token_usage/collect.py +34 -27
- package/runtime/python/okstra_token_usage/cursor.py +93 -0
- package/runtime/python/okstra_token_usage/paths.py +29 -2
- package/runtime/skills/okstra-coding-preflight/clean-code.md +15 -0
- package/runtime/skills/okstra-inspect/SKILL.md +16 -11
- package/runtime/skills/okstra-run/templates/pr-body.template.md +13 -16
- package/runtime/validators/lib/fixtures.sh +73 -10
- package/runtime/validators/lib/runners.sh +4 -0
- package/runtime/validators/validate-run.py +53 -0
- package/runtime/validators/validate_session_conformance.py +430 -0
- package/src/migrate.mjs +31 -0
|
@@ -524,25 +524,29 @@ Parse the JSON and report these fields:
|
|
|
524
524
|
| Current run | `totals.currentRunFileCount`, `totals.currentRunBytes`, `currentRunPath` |
|
|
525
525
|
| Legacy timestamp artifacts | `totals.legacyTimestampFileCount` |
|
|
526
526
|
| Instruction set | `instructionSet.fileCount`, `instructionSet.bytes`, `instructionSet.analysisPacketBytes`, `instructionSet.legacyTaskPacketBytes` |
|
|
527
|
-
| Lead Phase 1 | `leadPhase1.mode`, `leadPhase1.fileCount`, `leadPhase1.bytes` |
|
|
528
|
-
| Analysis worker | `analysisWorker.mode`, `analysisWorker.fileCount`, `analysisWorker.bytesPerWorker`, `analysisWorker.legacyFullContractBytesPerWorker`, `analysisWorker.estimatedPacketModeBytesPerWorker`, `analysisWorker.estimatedReductionPercent` |
|
|
529
|
-
| Report writer | `reportWriter.fileCount`, `reportWriter.bytes` |
|
|
527
|
+
| Lead Phase 1 | `leadPhase1.mode`, `leadPhase1.fileCount`, `leadPhase1.bytes`, `leadPhase1.estimatedTokens` |
|
|
528
|
+
| Analysis worker | `analysisWorker.mode`, `analysisWorker.fileCount`, `analysisWorker.bytesPerWorker`, `analysisWorker.estimatedTokensPerWorker`, `analysisWorker.legacyFullContractBytesPerWorker`, `analysisWorker.estimatedPacketModeBytesPerWorker`, `analysisWorker.estimatedReductionPercent` |
|
|
529
|
+
| Report writer | `reportWriter.fileCount`, `reportWriter.bytes`, `reportWriter.estimatedTokens` |
|
|
530
|
+
| Skill assets (hot path) | `skillAssets.fileCount`, `skillAssets.bytes`, `skillAssets.estimatedTokens`, top entries of `skillAssets.files[]` |
|
|
530
531
|
|
|
531
532
|
Format bytes as both raw bytes and rounded KB/MB where useful. Use `analysisWorker.estimatedReductionPercent` for the worker-input reduction. Do not recompute it from `bytesPerWorker` when `analysisWorker.mode == "analysis-packet-primary"` because `bytesPerWorker` is already the packet-primary cost.
|
|
532
533
|
|
|
534
|
+
`estimatedTokens*` fields are a static heuristic (~4 ASCII chars/token, non-ASCII ≈ 1 token/char) for ranking instruction surfaces — actual billable cost is the token-usage collector's domain; never present these as billing numbers. `skillAssets` measures the per-run hot-path instruction assets loaded OUTSIDE the task bundle (lifecycle skill bodies + worker agent specs, installed copy preferred) — the prompt-diet target list, sorted by size descending.
|
|
535
|
+
|
|
533
536
|
### cost.4 — Output template
|
|
534
537
|
|
|
535
538
|
```markdown
|
|
536
539
|
## okstra Context Cost — <task-key>
|
|
537
540
|
|
|
538
|
-
| Surface | Files | Size |
|
|
539
|
-
|
|
540
|
-
| Task bundle | <N> | <bytes> (<human>) |
|
|
541
|
-
| Current run | <N> | <bytes> (<human>) |
|
|
542
|
-
| Instruction set | <N> | <bytes> (<human>) |
|
|
543
|
-
| Lead Phase 1 (`<mode>`) | <N> | <bytes> (<human>) |
|
|
544
|
-
| Analysis worker / worker (`<mode>`) | <N> | <bytes> (<human>) |
|
|
545
|
-
| Report writer synthesis | <N> | <bytes> (<human>) |
|
|
541
|
+
| Surface | Files | Size | ~Tokens |
|
|
542
|
+
|---|---:|---:|---:|
|
|
543
|
+
| Task bundle | <N> | <bytes> (<human>) | - |
|
|
544
|
+
| Current run | <N> | <bytes> (<human>) | - |
|
|
545
|
+
| Instruction set | <N> | <bytes> (<human>) | <estimatedTokens> |
|
|
546
|
+
| Lead Phase 1 (`<mode>`) | <N> | <bytes> (<human>) | <estimatedTokens> |
|
|
547
|
+
| Analysis worker / worker (`<mode>`) | <N> | <bytes> (<human>) | <estimatedTokensPerWorker> |
|
|
548
|
+
| Report writer synthesis | <N> | <bytes> (<human>) | <estimatedTokens> |
|
|
549
|
+
| Skill assets (hot path) | <N> | <bytes> (<human>) | <estimatedTokens> |
|
|
546
550
|
|
|
547
551
|
- Current run: `<currentRunPath-or-->`
|
|
548
552
|
- Legacy timestamp artifacts: `<N>`
|
|
@@ -562,6 +566,7 @@ Interpretation rules:
|
|
|
562
566
|
- `analysisWorker.mode == "analysis-packet-primary"` means new workers should read `analysis-packet.md` first and open full source inputs only for evidence checks or missing detail.
|
|
563
567
|
- If `analysisWorker.mode == "full-input-contract"` and `estimatedReductionPercent` is low, the next target is worker prompt/input contract slimming.
|
|
564
568
|
- If `reportWriter.bytes` dominates, the next target is a compact `synthesis-input` artifact.
|
|
569
|
+
- If `skillAssets.estimatedTokens` dominates the per-run fixed cost, the next target is slimming the largest `skillAssets.files[]` entries (prompt diet — perf plan v2 P2), e.g. thin-core + lazy-sidecar split.
|
|
565
570
|
- If `legacyTimestampFileCount` is high, recommend current-view/cold-artifact separation or retention cleanup, not destructive deletion by default.
|
|
566
571
|
|
|
567
572
|
---
|
|
@@ -1,28 +1,25 @@
|
|
|
1
1
|
<!--
|
|
2
|
-
okstra release-handoff
|
|
2
|
+
okstra release-handoff default PR body template.
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
4
|
+
This file is used when there is no custom PR template. Priority:
|
|
5
|
+
1. The per-run override path entered in okstra-run Step 6
|
|
6
|
+
2. `prTemplatePath` of <project-root>/.okstra/project.json
|
|
7
|
+
3. `prTemplatePath` of ~/.okstra/config.json
|
|
8
|
+
4. This default file
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
`prTemplatePath` 키를 추가하세요. (절대경로 또는 project_root 기준 상대경로)
|
|
12
|
-
|
|
13
|
-
플레이스홀더는 release-handoff 의 Claude lead 가 다음 입력을 근거로
|
|
14
|
-
직접 채웁니다:
|
|
15
|
-
- run brief 의 의도/스코프
|
|
16
|
-
- 인용된 final-verification 리포트의 verdict 근거
|
|
17
|
-
- `git log --oneline <base>..HEAD` 의 commit 범위
|
|
18
|
-
- `git diff <base>..HEAD --stat` 의 변경 파일 통계
|
|
10
|
+
To use your own templates in project or global settings, add the `prTemplatePath` key to one of the paths above. (Absolute path or relative path based on `project_root`)
|
|
19
11
|
|
|
12
|
+
The placeholder is filled in directly by the Claude lead for the release-handoff based on the following inputs:
|
|
13
|
+
- The intent and scope of the run brief
|
|
14
|
+
- The grounds for the verdict in the referenced final-verification report
|
|
15
|
+
- The commit range from `git log --oneline <base>..HEAD`
|
|
16
|
+
- The file change statistics from `git diff <base>..HEAD --stat`
|
|
20
17
|
-->
|
|
21
18
|
|
|
22
19
|
## **Please check if the PR fulfills these requirements**
|
|
23
20
|
- [ ] Commits have a single intent
|
|
24
21
|
- [ ] Tests for the changes have been added (for bug fixes / features)
|
|
25
|
-
- [
|
|
22
|
+
- [x] I reviewed my own code
|
|
26
23
|
- [ ] I tested the changes (if not, explain why in the "Other information" section)
|
|
27
24
|
- [ ] Docs have been added / updated
|
|
28
25
|
|
|
@@ -147,6 +147,7 @@ prepare_run_validator_fixture() {
|
|
|
147
147
|
expected_task_manifest_relative_path="$(task_manifest_relative_path "$task_group" "$task_id")"
|
|
148
148
|
|
|
149
149
|
python3 - "$PROJECT_ROOT" "$expected_task_manifest_relative_path" "$omitted_worker_id" <<'PY'
|
|
150
|
+
from datetime import datetime, timezone
|
|
150
151
|
from pathlib import Path
|
|
151
152
|
import json
|
|
152
153
|
import sys
|
|
@@ -258,20 +259,33 @@ for worker in team_state.get("workers", []):
|
|
|
258
259
|
result_stem = result_path.stem # e.g. claude-worker-error-analysis-001
|
|
259
260
|
audit_stem = result_stem.replace("-worker-", "-worker-audit-", 1)
|
|
260
261
|
audit_path = result_path.with_name(f"{audit_stem}{result_path.suffix}")
|
|
261
|
-
|
|
262
|
-
"
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
262
|
+
audit_lines = [
|
|
263
|
+
f"# {worker.get('role', worker_id)} Audit",
|
|
264
|
+
"",
|
|
265
|
+
"- Read task-brief.md end-to-end (validation fixture).",
|
|
266
|
+
]
|
|
267
|
+
if worker_id == "claude":
|
|
268
|
+
# Heartbeat 계약 (agents/workers/claude-worker.md "Heartbeat") —
|
|
269
|
+
# validate_session_conformance 가 claude-worker audit 사이드카의
|
|
270
|
+
# `- PROGRESS:` cadence 를 검사하므로 fixture 도 계약을 준수한다.
|
|
271
|
+
hb_ts = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
272
|
+
audit_lines += [
|
|
273
|
+
"",
|
|
274
|
+
f"- PROGRESS: started {hb_ts}",
|
|
275
|
+
f"- PROGRESS: read-task-brief.md {hb_ts}",
|
|
276
|
+
f"- PROGRESS: analysis-start {hb_ts}",
|
|
277
|
+
f"- PROGRESS: findings-draft-complete {hb_ts}",
|
|
278
|
+
f"- PROGRESS: write-result-start {hb_ts}",
|
|
279
|
+
]
|
|
280
|
+
audit_path.write_text("\n".join(audit_lines) + "\n")
|
|
271
281
|
|
|
272
282
|
lead = team_state.get("lead")
|
|
273
283
|
if isinstance(lead, dict):
|
|
274
284
|
lead["status"] = "completed"
|
|
285
|
+
# render-only fixture 에는 실 Claude 세션이 없어 sessionId 가 비어 있을 수
|
|
286
|
+
# 있다 — session-conformance fixture jsonl 의 파일명과 맞춘 고정 id 를 부여.
|
|
287
|
+
if not str(lead.get("sessionId") or "").strip():
|
|
288
|
+
lead["sessionId"] = "fixture-lead-session-0001"
|
|
275
289
|
team_state["workflowState"] = "worker-results-collected"
|
|
276
290
|
|
|
277
291
|
# validate-run.py requires team-state.teamCreate.attempted=true with a
|
|
@@ -436,6 +450,55 @@ if WORKSPACE_ROOT:
|
|
|
436
450
|
if final_status_path.exists():
|
|
437
451
|
final_status_path.unlink()
|
|
438
452
|
|
|
453
|
+
# session-conformance fixture — validate_session_conformance 는 lead 세션
|
|
454
|
+
# jsonl 에서 run 윈도우 내 PROGRESS 체크포인트를 스캔한다. 계약을 준수한
|
|
455
|
+
# 합성 jsonl 을 주입 시드 디렉터리(.claude-projects-fixture)에 만들어 두고,
|
|
456
|
+
# 러너(run_validator_expectation)가 --claude-projects-dir 로 넘긴다.
|
|
457
|
+
lead_sid = str((team_state.get("lead") or {}).get("sessionId") or "").strip()
|
|
458
|
+
if lead_sid:
|
|
459
|
+
now_iso = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%S.000Z")
|
|
460
|
+
progress_lines = [
|
|
461
|
+
"PROGRESS: phase-1-intake reading task bundle",
|
|
462
|
+
"PROGRESS: phase-1-intake complete",
|
|
463
|
+
f"PROGRESS: phase-2-prompts preparing {len(team_state.get('workers') or [])} worker prompts",
|
|
464
|
+
"PROGRESS: phase-3-team-create attempting TeamCreate",
|
|
465
|
+
]
|
|
466
|
+
for worker in team_state.get("workers", []):
|
|
467
|
+
if not isinstance(worker, dict):
|
|
468
|
+
continue
|
|
469
|
+
worker_id = str(worker.get("workerId", "")).strip()
|
|
470
|
+
status = str(worker.get("status", "")).strip()
|
|
471
|
+
if not worker_id or worker_id == "report-writer":
|
|
472
|
+
continue
|
|
473
|
+
if status in ("completed", "timeout", "error"):
|
|
474
|
+
progress_lines.append(
|
|
475
|
+
f"PROGRESS: phase-4-dispatch worker={worker_id}-worker model=fixture"
|
|
476
|
+
)
|
|
477
|
+
if status == "completed":
|
|
478
|
+
progress_lines.append(
|
|
479
|
+
f"PROGRESS: phase-5-collect worker={worker_id}-worker status=completed"
|
|
480
|
+
)
|
|
481
|
+
progress_lines.append("PROGRESS: phase-6-synthesis dispatching report-writer-worker")
|
|
482
|
+
progress_lines.append("PROGRESS: phase-7-persist updating manifests")
|
|
483
|
+
records = [
|
|
484
|
+
{
|
|
485
|
+
"type": "assistant",
|
|
486
|
+
"timestamp": now_iso,
|
|
487
|
+
"message": {"content": [{"type": "text", "text": line}]},
|
|
488
|
+
}
|
|
489
|
+
for line in progress_lines
|
|
490
|
+
]
|
|
491
|
+
# 인코딩 기준은 validator 가 쓰는 task-manifest.projectRoot — macOS 의
|
|
492
|
+
# /tmp 심링크 때문에 셸의 $PROJECT_ROOT(/tmp/...)와 manifest 의
|
|
493
|
+
# projectRoot(/private/tmp/...)가 다른 문자열일 수 있다.
|
|
494
|
+
manifest_project_root = str(task_manifest.get("projectRoot") or project_root)
|
|
495
|
+
encoded_cwd = "-" + manifest_project_root.strip("/").replace("/", "-")
|
|
496
|
+
session_dir = project_root / ".claude-projects-fixture" / encoded_cwd
|
|
497
|
+
session_dir.mkdir(parents=True, exist_ok=True)
|
|
498
|
+
(session_dir / f"{lead_sid}.jsonl").write_text(
|
|
499
|
+
"".join(json.dumps(r, ensure_ascii=False) + "\n" for r in records)
|
|
500
|
+
)
|
|
501
|
+
|
|
439
502
|
write_json(team_state_path, team_state)
|
|
440
503
|
write_json(run_manifest_path, run_manifest)
|
|
441
504
|
write_json(task_manifest_path, task_manifest)
|
|
@@ -87,6 +87,10 @@ process = subprocess.run(
|
|
|
87
87
|
str(task_manifest_path),
|
|
88
88
|
"--final-status",
|
|
89
89
|
str(final_status_path),
|
|
90
|
+
# session-conformance 주입 시드 — prepare_run_validator_fixture 가
|
|
91
|
+
# 만든 합성 lead jsonl 디렉터리 (실제 ~/.claude/projects 미오염).
|
|
92
|
+
"--claude-projects-dir",
|
|
93
|
+
str(project_root / ".claude-projects-fixture"),
|
|
90
94
|
],
|
|
91
95
|
capture_output=True,
|
|
92
96
|
text=True,
|
|
@@ -1681,6 +1681,41 @@ def _validate_improvement_discovery(
|
|
|
1681
1681
|
failures.append(f"improvement-discovery: {err}")
|
|
1682
1682
|
|
|
1683
1683
|
|
|
1684
|
+
def _validate_session_conformance(
|
|
1685
|
+
team_state: dict,
|
|
1686
|
+
team_state_path: Path,
|
|
1687
|
+
project_root: Path,
|
|
1688
|
+
report_path: Path,
|
|
1689
|
+
task_type: str,
|
|
1690
|
+
claude_projects_dir: str | None,
|
|
1691
|
+
failures: list[str],
|
|
1692
|
+
) -> None:
|
|
1693
|
+
"""agents/SKILL.md BLOCKING 계약 3종(PROGRESS 체크포인트 / claude-worker
|
|
1694
|
+
heartbeat / implementation entry guard)의 post-hoc 검사를 위임하고 실패를
|
|
1695
|
+
``session-conformance: `` 접두로 folding 한다. 설계:
|
|
1696
|
+
docs/superpowers/specs/2026-06-10-blocking-contract-posthoc-conformance-design.md
|
|
1697
|
+
"""
|
|
1698
|
+
_validators_dir = Path(__file__).resolve().parent
|
|
1699
|
+
if str(_validators_dir) not in sys.path:
|
|
1700
|
+
sys.path.insert(0, str(_validators_dir))
|
|
1701
|
+
try:
|
|
1702
|
+
from validate_session_conformance import validate_session_conformance # noqa: E402
|
|
1703
|
+
except ImportError as exc:
|
|
1704
|
+
failures.append(
|
|
1705
|
+
f"session-conformance: validate_session_conformance import failed — {exc}"
|
|
1706
|
+
)
|
|
1707
|
+
return
|
|
1708
|
+
result = validate_session_conformance(
|
|
1709
|
+
team_state=team_state,
|
|
1710
|
+
team_state_path=team_state_path,
|
|
1711
|
+
project_root=project_root,
|
|
1712
|
+
report_path=report_path,
|
|
1713
|
+
task_type=task_type,
|
|
1714
|
+
claude_projects_dir=Path(claude_projects_dir) if claude_projects_dir else None,
|
|
1715
|
+
)
|
|
1716
|
+
failures.extend(f"session-conformance: {err}" for err in result.errors)
|
|
1717
|
+
|
|
1718
|
+
|
|
1684
1719
|
def _validate_requirements_discovery_fanout(run_dir, failures) -> None:
|
|
1685
1720
|
"""requirements-discovery run 에 fan-out/ 이 있으면 packet+index 를 검증해
|
|
1686
1721
|
실패를 ``requirements-discovery: `` 접두로 folding 한다. fan-out 이 없으면 no-op.
|
|
@@ -1993,6 +2028,15 @@ def main() -> int:
|
|
|
1993
2028
|
parser.add_argument(
|
|
1994
2029
|
"--final-status", required=False, help="Optional final status file to write."
|
|
1995
2030
|
)
|
|
2031
|
+
parser.add_argument(
|
|
2032
|
+
"--claude-projects-dir",
|
|
2033
|
+
required=False,
|
|
2034
|
+
default=None,
|
|
2035
|
+
help=(
|
|
2036
|
+
"Override the Claude Code projects root used for session-conformance "
|
|
2037
|
+
"jsonl lookup (test/diagnostic seam; default: ~/.claude/projects)."
|
|
2038
|
+
),
|
|
2039
|
+
)
|
|
1996
2040
|
args = parser.parse_args()
|
|
1997
2041
|
|
|
1998
2042
|
run_manifest_path = Path(args.run_manifest).resolve()
|
|
@@ -2043,6 +2087,15 @@ def main() -> int:
|
|
|
2043
2087
|
validate_phase_boundary(task_type, report_path, failures)
|
|
2044
2088
|
if task_type:
|
|
2045
2089
|
validate_worker_results_audit(report_path, task_type, failures)
|
|
2090
|
+
_validate_session_conformance(
|
|
2091
|
+
team_state,
|
|
2092
|
+
team_state_path,
|
|
2093
|
+
project_root,
|
|
2094
|
+
report_path,
|
|
2095
|
+
task_type,
|
|
2096
|
+
args.claude_projects_dir,
|
|
2097
|
+
failures,
|
|
2098
|
+
)
|
|
2046
2099
|
if task_type in ("implementation", "final-verification"):
|
|
2047
2100
|
_sp = None
|
|
2048
2101
|
_pj = project_json_path(project_root)
|