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
|
@@ -1,583 +0,0 @@
|
|
|
1
|
-
# run-index row 정규화 (runId SSOT화) Implementation Plan
|
|
2
|
-
|
|
3
|
-
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
|
4
|
-
|
|
5
|
-
**Goal:** run-index row(`recent.jsonl`/`active.jsonl`/`projects/<id>/index.jsonl`)에서 runId로부터 무손실 복원 가능한 5개 파생 필드를 제거해 디스크 포맷을 slim(18→13필드)으로 만들고, read 지점 hydrate로 reader 로직을 무변경 유지한다.
|
|
6
|
-
|
|
7
|
-
**Architecture:** 신규 모듈 `run_index_row.py`에 생성(`build_run_index_row`)·축약(`slim_run_row`)·복원(`hydrate_run_row`)·읽기(`read_run_index`)·일괄 변환(`reindex_slim`)을 집중한다. writer는 `build_run_index_row`로 slim row만 기록하고, reader 3곳은 `read_run_index`로 읽어 full row를 받는다. reconcile은 읽을 때 hydrate, 다시 쓸 때 slim한다.
|
|
8
|
-
|
|
9
|
-
**Tech Stack:** Python 3 (4-space indent, snake_case), pytest (`tests/run/`, conftest.py가 OKSTRA_HOME 격리), `npm run build`로 runtime 동기화.
|
|
10
|
-
|
|
11
|
-
## Global Constraints
|
|
12
|
-
|
|
13
|
-
- 디스크 row에서 제거하는 필드(정확히 5개): `projectId`, `taskType`, `runSeq`, `taskKey`, `invocationFile`.
|
|
14
|
-
- 유지하는 필드(13개): `runId`, `projectRoot`, `taskGroup`, `taskId`, `status`, `startedAt`, `finishedAt`, `workers`, `leadModel`, `validation`, `finalReportRel`, `finalStatusRel`, `runDirRel`.
|
|
15
|
-
- `taskGroup`/`taskId`는 raw 대소문자를 보존한다 (runId 세그먼트는 slug라 다를 수 있음 — 예: row `DEV-9388` vs runId `dev-9388`). 절대 runId에서 복원하지 않는다.
|
|
16
|
-
- `runId` 포맷 `<projectId>/<taskGroup>/<taskId>/<taskType>/r<NN>`은 fs-safe 슬러그 불변식이라 변경 금지.
|
|
17
|
-
- 모든 row 키는 camelCase. JSON 직렬화는 기존과 동일하게 `separators=(",", ":")`.
|
|
18
|
-
- `runtime/`는 빌드 산출물 — 직접 편집 금지. 소스 수정 후 `npm run build`.
|
|
19
|
-
- 커밋은 Conventional Commits, AI 트레일러 금지.
|
|
20
|
-
|
|
21
|
-
---
|
|
22
|
-
|
|
23
|
-
### Task 1: 코어 헬퍼 — build / slim / hydrate
|
|
24
|
-
|
|
25
|
-
**Files:**
|
|
26
|
-
- Create: `scripts/okstra_ctl/run_index_row.py`
|
|
27
|
-
- Test: `tests/run/test_run_index_row.py`
|
|
28
|
-
|
|
29
|
-
**Interfaces:**
|
|
30
|
-
- Consumes: `parse_run_id`, `build_run_id` from [`scripts/okstra_ctl/ids.py`](../../../scripts/okstra_ctl/ids.py); `invocation_path` from [`scripts/okstra_ctl/invocation.py`](../../../scripts/okstra_ctl/invocation.py).
|
|
31
|
-
- Produces:
|
|
32
|
-
- `DERIVED_FIELDS: tuple = ("projectId", "taskType", "runSeq", "taskKey", "invocationFile")`
|
|
33
|
-
- `build_run_index_row(*, project_id, project_root, task_group, task_id, task_type, run_seq, status, started_at, finished_at, workers, lead_model, validation, run_dir_rel, final_report_rel, final_status_rel) -> dict` — 13필드 slim row 반환.
|
|
34
|
-
- `slim_run_row(row: dict) -> dict` — `DERIVED_FIELDS`를 제거한 새 dict (멱등).
|
|
35
|
-
- `hydrate_run_row(row: dict) -> dict` — slim/full row를 받아 5개 파생 필드를 채운 full dict (멱등).
|
|
36
|
-
|
|
37
|
-
- [ ] **Step 1: Write the failing test**
|
|
38
|
-
|
|
39
|
-
```python
|
|
40
|
-
# tests/run/test_run_index_row.py
|
|
41
|
-
from pathlib import Path
|
|
42
|
-
|
|
43
|
-
from okstra_ctl.run_index_row import (
|
|
44
|
-
DERIVED_FIELDS, build_run_index_row, slim_run_row, hydrate_run_row,
|
|
45
|
-
)
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
def _sample_kwargs():
|
|
49
|
-
return dict(
|
|
50
|
-
project_id="fontradar-v2-api",
|
|
51
|
-
project_root="/Volumes/x/fontradar-v2-api",
|
|
52
|
-
task_group="uploadfont",
|
|
53
|
-
task_id="dev-6827-bug-de-redis-lupload",
|
|
54
|
-
task_type="final-verification",
|
|
55
|
-
run_seq=5,
|
|
56
|
-
status="prepared",
|
|
57
|
-
started_at="2026-06-18T20:24:00Z",
|
|
58
|
-
finished_at="2026-06-18T20:24:00Z",
|
|
59
|
-
workers=["claude", "codex"],
|
|
60
|
-
lead_model="opus",
|
|
61
|
-
validation="not-run",
|
|
62
|
-
run_dir_rel=".okstra/tasks/uploadfont/dev-6827-bug-de-redis-lupload/runs/final-verification",
|
|
63
|
-
final_report_rel=".okstra/tasks/uploadfont/dev-6827-bug-de-redis-lupload/runs/final-verification/reports/final-report-final-verification-001.md",
|
|
64
|
-
final_status_rel=".okstra/tasks/uploadfont/dev-6827-bug-de-redis-lupload/runs/final-verification/status/final-final-verification-001.status",
|
|
65
|
-
)
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
def test_build_run_index_row_omits_derived():
|
|
69
|
-
row = build_run_index_row(**_sample_kwargs())
|
|
70
|
-
for f in DERIVED_FIELDS:
|
|
71
|
-
assert f not in row, f"slim row 에 파생 필드 {f} 가 남았다"
|
|
72
|
-
assert row["runId"] == ("fontradar-v2-api/uploadfont/"
|
|
73
|
-
"dev-6827-bug-de-redis-lupload/final-verification/r05")
|
|
74
|
-
assert row["taskGroup"] == "uploadfont"
|
|
75
|
-
assert len(row) == 13
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
def test_hydrate_restores_derived():
|
|
79
|
-
slim = build_run_index_row(**_sample_kwargs())
|
|
80
|
-
full = hydrate_run_row(slim)
|
|
81
|
-
assert full["projectId"] == "fontradar-v2-api"
|
|
82
|
-
assert full["taskType"] == "final-verification"
|
|
83
|
-
assert full["runSeq"] == 5
|
|
84
|
-
assert full["taskKey"] == "uploadfont/dev-6827-bug-de-redis-lupload"
|
|
85
|
-
assert full["invocationFile"] == (
|
|
86
|
-
"projects/fontradar-v2-api/invocations/uploadfont/"
|
|
87
|
-
"dev-6827-bug-de-redis-lupload/final-verification/r05.json")
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
def test_hydrate_preserves_raw_case():
|
|
91
|
-
# row 의 raw 대소문자(DEV-9388)는 runId(slug dev-9388)에서 복원하면 안 된다
|
|
92
|
-
kwargs = _sample_kwargs()
|
|
93
|
-
kwargs["task_group"] = "DEV-9388"
|
|
94
|
-
kwargs["task_id"] = "dev-9436"
|
|
95
|
-
slim = build_run_index_row(**kwargs)
|
|
96
|
-
full = hydrate_run_row(slim)
|
|
97
|
-
assert full["taskGroup"] == "DEV-9388"
|
|
98
|
-
assert full["taskKey"] == "DEV-9388/dev-9436"
|
|
99
|
-
# invocationFile 은 slug 기반
|
|
100
|
-
assert "/dev-9388/dev-9436/" in full["invocationFile"]
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
def test_hydrate_idempotent_on_full():
|
|
104
|
-
slim = build_run_index_row(**_sample_kwargs())
|
|
105
|
-
once = hydrate_run_row(slim)
|
|
106
|
-
twice = hydrate_run_row(once)
|
|
107
|
-
assert once == twice
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
def test_slim_run_row_idempotent():
|
|
111
|
-
slim = build_run_index_row(**_sample_kwargs())
|
|
112
|
-
full = hydrate_run_row(slim)
|
|
113
|
-
assert slim_run_row(full) == slim
|
|
114
|
-
assert slim_run_row(slim) == slim
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
- [ ] **Step 2: Run test to verify it fails**
|
|
118
|
-
|
|
119
|
-
Run: `python3 -m pytest tests/run/test_run_index_row.py -v`
|
|
120
|
-
Expected: FAIL — `ModuleNotFoundError: No module named 'okstra_ctl.run_index_row'`
|
|
121
|
-
|
|
122
|
-
- [ ] **Step 3: Write minimal implementation**
|
|
123
|
-
|
|
124
|
-
```python
|
|
125
|
-
# scripts/okstra_ctl/run_index_row.py
|
|
126
|
-
"""run-index row 의 생성/축약/복원 단일 참조점.
|
|
127
|
-
|
|
128
|
-
디스크에는 slim(13필드)로 저장하고, reader 는 hydrate 로 full(18필드)을 받는다.
|
|
129
|
-
파생 5필드(projectId/taskType/runSeq/taskKey/invocationFile)는 runId 와 평면
|
|
130
|
-
taskGroup/taskId 로부터 무손실 복원 가능하다(2026-06-20 전수 검증 근거).
|
|
131
|
-
"""
|
|
132
|
-
from __future__ import annotations
|
|
133
|
-
|
|
134
|
-
from pathlib import Path
|
|
135
|
-
|
|
136
|
-
from .ids import build_run_id, parse_run_id
|
|
137
|
-
from .invocation import invocation_path
|
|
138
|
-
|
|
139
|
-
DERIVED_FIELDS = ("projectId", "taskType", "runSeq", "taskKey", "invocationFile")
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
def build_run_index_row(*, project_id: str, project_root: str,
|
|
143
|
-
task_group: str, task_id: str, task_type: str,
|
|
144
|
-
run_seq: int, status: str, started_at: str,
|
|
145
|
-
finished_at, workers: list, lead_model: str,
|
|
146
|
-
validation: str, run_dir_rel: str,
|
|
147
|
-
final_report_rel: str, final_status_rel: str) -> dict:
|
|
148
|
-
"""slim run-index row(13필드)를 만든다. 파생 5필드는 포함하지 않는다."""
|
|
149
|
-
return {
|
|
150
|
-
"runId": build_run_id(project_id, task_group, task_id,
|
|
151
|
-
task_type, run_seq),
|
|
152
|
-
"projectRoot": project_root,
|
|
153
|
-
"taskGroup": task_group,
|
|
154
|
-
"taskId": task_id,
|
|
155
|
-
"status": status,
|
|
156
|
-
"startedAt": started_at,
|
|
157
|
-
"finishedAt": finished_at,
|
|
158
|
-
"workers": workers,
|
|
159
|
-
"leadModel": lead_model,
|
|
160
|
-
"validation": validation,
|
|
161
|
-
"finalReportRel": final_report_rel,
|
|
162
|
-
"finalStatusRel": final_status_rel,
|
|
163
|
-
"runDirRel": run_dir_rel,
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
def slim_run_row(row: dict) -> dict:
|
|
168
|
-
"""파생 5필드를 제거한 새 dict. 이미 slim 이면 동일(멱등)."""
|
|
169
|
-
return {k: v for k, v in row.items() if k not in DERIVED_FIELDS}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
def hydrate_run_row(row: dict) -> dict:
|
|
173
|
-
"""slim/full row 를 받아 파생 5필드를 채운 full dict. 멱등.
|
|
174
|
-
taskKey 는 runId(slug)가 아닌 평면 raw taskGroup/taskId 로 조합한다.
|
|
175
|
-
"""
|
|
176
|
-
parsed = parse_run_id(row["runId"])
|
|
177
|
-
out = dict(row)
|
|
178
|
-
out["projectId"] = parsed["projectId"]
|
|
179
|
-
out["taskType"] = parsed["taskType"]
|
|
180
|
-
out["runSeq"] = parsed["runSeq"]
|
|
181
|
-
out["taskKey"] = f"{row.get('taskGroup', '')}/{row.get('taskId', '')}"
|
|
182
|
-
out["invocationFile"] = str(invocation_path(
|
|
183
|
-
Path(""), parsed["projectId"], parsed["taskGroup"], parsed["taskId"],
|
|
184
|
-
parsed["taskType"], parsed["runSeq"]))
|
|
185
|
-
return out
|
|
186
|
-
```
|
|
187
|
-
|
|
188
|
-
- [ ] **Step 4: Run test to verify it passes**
|
|
189
|
-
|
|
190
|
-
Run: `python3 -m pytest tests/run/test_run_index_row.py -v`
|
|
191
|
-
Expected: PASS (5 tests)
|
|
192
|
-
|
|
193
|
-
- [ ] **Step 5: Commit**
|
|
194
|
-
|
|
195
|
-
```bash
|
|
196
|
-
git add scripts/okstra_ctl/run_index_row.py tests/run/test_run_index_row.py
|
|
197
|
-
git commit -m "feat(index): run-index row 생성/축약/복원 코어 헬퍼 추가"
|
|
198
|
-
```
|
|
199
|
-
|
|
200
|
-
---
|
|
201
|
-
|
|
202
|
-
### Task 2: read_run_index + reindex_slim 일괄 변환
|
|
203
|
-
|
|
204
|
-
**Files:**
|
|
205
|
-
- Modify: `scripts/okstra_ctl/run_index_row.py` (함수 추가)
|
|
206
|
-
- Test: `tests/run/test_run_index_row.py` (테스트 추가)
|
|
207
|
-
|
|
208
|
-
**Interfaces:**
|
|
209
|
-
- Consumes: `read_jsonl` from [`scripts/okstra_ctl/jsonl.py`](../../../scripts/okstra_ctl/jsonl.py); `hydrate_run_row`, `slim_run_row` (Task 1).
|
|
210
|
-
- Produces:
|
|
211
|
-
- `read_run_index(path: Path) -> list[dict]` — jsonl을 읽어 각 row를 `hydrate_run_row`로 통과시킨 full row 목록.
|
|
212
|
-
- `reindex_slim(home: Path) -> int` — `recent.jsonl`/`active.jsonl`/`projects/*/index.jsonl`을 slim으로 원자적 재작성. 변환한 row 총수 반환. 멱등.
|
|
213
|
-
|
|
214
|
-
- [ ] **Step 1: Write the failing test**
|
|
215
|
-
|
|
216
|
-
```python
|
|
217
|
-
# tests/run/test_run_index_row.py (추가)
|
|
218
|
-
import json
|
|
219
|
-
|
|
220
|
-
from okstra_ctl.run_index_row import read_run_index, reindex_slim
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
def _write_full_row(path, kwargs_overrides=None):
|
|
224
|
-
# 마이그레이션 전 full row 를 디스크에 직접 기록
|
|
225
|
-
from okstra_ctl.run_index_row import hydrate_run_row, build_run_index_row
|
|
226
|
-
kw = _sample_kwargs()
|
|
227
|
-
if kwargs_overrides:
|
|
228
|
-
kw.update(kwargs_overrides)
|
|
229
|
-
full = hydrate_run_row(build_run_index_row(**kw))
|
|
230
|
-
path.parent.mkdir(parents=True, exist_ok=True)
|
|
231
|
-
with path.open("a") as f:
|
|
232
|
-
f.write(json.dumps(full, separators=(",", ":")) + "\n")
|
|
233
|
-
return full
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
def test_read_run_index_hydrates(tmp_path):
|
|
237
|
-
recent = tmp_path / "recent.jsonl"
|
|
238
|
-
# slim row 를 디스크에 기록
|
|
239
|
-
from okstra_ctl.run_index_row import build_run_index_row
|
|
240
|
-
slim = build_run_index_row(**_sample_kwargs())
|
|
241
|
-
recent.write_text(json.dumps(slim, separators=(",", ":")) + "\n")
|
|
242
|
-
rows = read_run_index(recent)
|
|
243
|
-
assert rows[0]["projectId"] == "fontradar-v2-api"
|
|
244
|
-
assert rows[0]["invocationFile"].startswith("projects/")
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
def test_reindex_slim_strips_and_is_idempotent(tmp_path):
|
|
248
|
-
home = tmp_path
|
|
249
|
-
recent = home / "recent.jsonl"
|
|
250
|
-
idx = home / "projects" / "fontradar-v2-api" / "index.jsonl"
|
|
251
|
-
full_recent = _write_full_row(recent)
|
|
252
|
-
_write_full_row(idx)
|
|
253
|
-
n1 = reindex_slim(home)
|
|
254
|
-
assert n1 == 2
|
|
255
|
-
# 디스크에서 파생 필드가 사라졌는지 확인
|
|
256
|
-
on_disk = json.loads(recent.read_text().splitlines()[0])
|
|
257
|
-
for f in DERIVED_FIELDS:
|
|
258
|
-
assert f not in on_disk
|
|
259
|
-
# 값 보존: hydrate 하면 원래 full 과 동일
|
|
260
|
-
assert hydrate_run_row(on_disk) == full_recent
|
|
261
|
-
# 멱등: 두 번째 실행도 같은 결과
|
|
262
|
-
n2 = reindex_slim(home)
|
|
263
|
-
assert json.loads(recent.read_text().splitlines()[0]) == on_disk
|
|
264
|
-
```
|
|
265
|
-
|
|
266
|
-
- [ ] **Step 2: Run test to verify it fails**
|
|
267
|
-
|
|
268
|
-
Run: `python3 -m pytest tests/run/test_run_index_row.py::test_reindex_slim_strips_and_is_idempotent -v`
|
|
269
|
-
Expected: FAIL — `ImportError: cannot import name 'reindex_slim'`
|
|
270
|
-
|
|
271
|
-
- [ ] **Step 3: Write minimal implementation**
|
|
272
|
-
|
|
273
|
-
```python
|
|
274
|
-
# scripts/okstra_ctl/run_index_row.py (추가)
|
|
275
|
-
import json
|
|
276
|
-
import os
|
|
277
|
-
|
|
278
|
-
from .jsonl import read_jsonl
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
def read_run_index(path: Path) -> list:
|
|
282
|
-
"""run-index jsonl 을 읽어 각 row 를 hydrate 한 full row 목록 반환."""
|
|
283
|
-
return [hydrate_run_row(r) for r in read_jsonl(path)]
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
def _rewrite_slim(path: Path) -> int:
|
|
287
|
-
rows = read_jsonl(path)
|
|
288
|
-
if not rows:
|
|
289
|
-
return 0
|
|
290
|
-
tmp = path.with_suffix(path.suffix + ".tmp")
|
|
291
|
-
with tmp.open("w") as f:
|
|
292
|
-
for r in rows:
|
|
293
|
-
f.write(json.dumps(slim_run_row(r), separators=(",", ":")) + "\n")
|
|
294
|
-
os.replace(tmp, path)
|
|
295
|
-
return len(rows)
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
def reindex_slim(home: Path) -> int:
|
|
299
|
-
"""기존 run-index 파일들을 slim 으로 일괄 재작성. 멱등. 변환 row 총수 반환."""
|
|
300
|
-
total = 0
|
|
301
|
-
targets = [home / "recent.jsonl", home / "active.jsonl"]
|
|
302
|
-
projects = home / "projects"
|
|
303
|
-
if projects.is_dir():
|
|
304
|
-
targets += sorted(projects.glob("*/index.jsonl"))
|
|
305
|
-
for path in targets:
|
|
306
|
-
if path.is_file():
|
|
307
|
-
total += _rewrite_slim(path)
|
|
308
|
-
return total
|
|
309
|
-
```
|
|
310
|
-
|
|
311
|
-
- [ ] **Step 4: Run test to verify it passes**
|
|
312
|
-
|
|
313
|
-
Run: `python3 -m pytest tests/run/test_run_index_row.py -v`
|
|
314
|
-
Expected: PASS (7 tests)
|
|
315
|
-
|
|
316
|
-
- [ ] **Step 5: Commit**
|
|
317
|
-
|
|
318
|
-
```bash
|
|
319
|
-
git add scripts/okstra_ctl/run_index_row.py tests/run/test_run_index_row.py
|
|
320
|
-
git commit -m "feat(index): read_run_index hydrate 와 reindex_slim 일괄 변환 추가"
|
|
321
|
-
```
|
|
322
|
-
|
|
323
|
-
---
|
|
324
|
-
|
|
325
|
-
### Task 3: writer 배선 — index.py / backfill.py
|
|
326
|
-
|
|
327
|
-
**Files:**
|
|
328
|
-
- Modify: `scripts/okstra_ctl/index.py:74-97` (record_start), `scripts/okstra_ctl/index.py:162-183` (reserve_run_in_active)
|
|
329
|
-
- Modify: `scripts/okstra_ctl/backfill.py:219-235` (row 생성)
|
|
330
|
-
- Test: 기존 `tests/run/` 회귀 (`test_okstra_index*.py`, `test_okstra_backfill*.py` 가 있으면 그 전체)
|
|
331
|
-
|
|
332
|
-
**Interfaces:**
|
|
333
|
-
- Consumes: `build_run_index_row` (Task 1).
|
|
334
|
-
- Produces: writer 가 기록하는 모든 row 가 slim(13필드). runId 값과 인덱스 invariant는 불변.
|
|
335
|
-
|
|
336
|
-
- [ ] **Step 1: Modify record_start to use build_run_index_row**
|
|
337
|
-
|
|
338
|
-
[`scripts/okstra_ctl/index.py:74-97`](../../../scripts/okstra_ctl/index.py:74)의 `row = { ... }` 리터럴 전체를 다음으로 치환:
|
|
339
|
-
|
|
340
|
-
```python
|
|
341
|
-
row = build_run_index_row(
|
|
342
|
-
project_id=project_id, project_root=project_root,
|
|
343
|
-
task_group=task_group, task_id=task_id, task_type=task_type,
|
|
344
|
-
run_seq=run_seq, status=initial_status, started_at=when,
|
|
345
|
-
finished_at=when if is_terminal else None,
|
|
346
|
-
workers=workers, lead_model=lead_model, validation="not-run",
|
|
347
|
-
run_dir_rel=run_dir_rel, final_report_rel=final_report_rel,
|
|
348
|
-
final_status_rel=final_status_rel)
|
|
349
|
-
```
|
|
350
|
-
|
|
351
|
-
파일 상단 import에 추가: `from .run_index_row import build_run_index_row`
|
|
352
|
-
(`run_id`/`inv_path_abs`/`inv_path_rel` 계산 라인 67-70, save_invocation 호출은 그대로 둔다 — `row["runId"]` 대신 기존 `run_id` 변수를 계속 사용.)
|
|
353
|
-
|
|
354
|
-
- [ ] **Step 2: Modify reserve_run_in_active to use build_run_index_row**
|
|
355
|
-
|
|
356
|
-
[`scripts/okstra_ctl/index.py:162-183`](../../../scripts/okstra_ctl/index.py:162)의 `row = { ... }` 리터럴 전체를 치환:
|
|
357
|
-
|
|
358
|
-
```python
|
|
359
|
-
row = build_run_index_row(
|
|
360
|
-
project_id=project_id, project_root=project_root,
|
|
361
|
-
task_group=task_group, task_id=task_id, task_type=task_type,
|
|
362
|
-
run_seq=run_seq, status="reserving", started_at=when,
|
|
363
|
-
finished_at=None, workers=[], lead_model="", validation="not-run",
|
|
364
|
-
run_dir_rel=run_dir_rel, final_report_rel=final_report_rel,
|
|
365
|
-
final_status_rel=final_status_rel)
|
|
366
|
-
```
|
|
367
|
-
|
|
368
|
-
(`inv_rel` 계산 라인 160-161은 더 이상 row에 쓰이지 않으므로 제거한다.)
|
|
369
|
-
|
|
370
|
-
- [ ] **Step 3: Modify backfill row to use build_run_index_row**
|
|
371
|
-
|
|
372
|
-
[`scripts/okstra_ctl/backfill.py:219-235`](../../../scripts/okstra_ctl/backfill.py:219)의 `row = { ... }` 리터럴 전체를 치환:
|
|
373
|
-
|
|
374
|
-
```python
|
|
375
|
-
row = build_run_index_row(
|
|
376
|
-
project_id=project_id, project_root=str(project_root),
|
|
377
|
-
task_group=actual_group, task_id=actual_task,
|
|
378
|
-
task_type=actual_type, run_seq=seq,
|
|
379
|
-
status=normalized_status, started_at=started_at,
|
|
380
|
-
finished_at=None if is_non_terminal else finished_at,
|
|
381
|
-
workers=raw_workers, lead_model=raw_lead_model,
|
|
382
|
-
validation=validation_status, run_dir_rel=run_dir_rel,
|
|
383
|
-
final_report_rel=final_rel,
|
|
384
|
-
final_status_rel=final_status_rel)
|
|
385
|
-
```
|
|
386
|
-
|
|
387
|
-
backfill.py 상단 import에 추가: `from .run_index_row import build_run_index_row`
|
|
388
|
-
(라인 199-201의 `inv_rel` 계산은 row에서 더 이상 안 쓰이지만 save_invocation 에는 무관 — `inv_rel` 변수가 다른 곳에서 안 쓰이면 제거. 라인 244의 save_invocation은 `actual_group/actual_task/actual_type/seq`를 직접 넘기므로 영향 없음.)
|
|
389
|
-
|
|
390
|
-
- [ ] **Step 4: Run regression tests**
|
|
391
|
-
|
|
392
|
-
Run: `python3 -m pytest tests/run/ -v`
|
|
393
|
-
Expected: PASS. 만약 기존 테스트가 디스크 row에서 `projectId`/`taskType`/`runSeq`/`taskKey`/`invocationFile`을 직접 단언한다면, 그 테스트의 read를 `read_run_index`로 바꾸거나 단언을 `hydrate_run_row(row)[...]` 기준으로 수정한다(assertion을 약화하지 말 것 — 동일 값 검증 유지).
|
|
394
|
-
|
|
395
|
-
- [ ] **Step 5: Commit**
|
|
396
|
-
|
|
397
|
-
```bash
|
|
398
|
-
git add scripts/okstra_ctl/index.py scripts/okstra_ctl/backfill.py tests/run/
|
|
399
|
-
git commit -m "refactor(index): writer 가 build_run_index_row 로 slim row 만 기록"
|
|
400
|
-
```
|
|
401
|
-
|
|
402
|
-
---
|
|
403
|
-
|
|
404
|
-
### Task 4: reader 배선 — sequence.py / listing.py
|
|
405
|
-
|
|
406
|
-
**Files:**
|
|
407
|
-
- Modify: `scripts/okstra_ctl/sequence.py:43-44`
|
|
408
|
-
- Modify: `scripts/okstra_ctl/listing.py:84,88-89`
|
|
409
|
-
- Test: 기존 `tests/run/` 회귀
|
|
410
|
-
|
|
411
|
-
**Interfaces:**
|
|
412
|
-
- Consumes: `read_run_index` (Task 2).
|
|
413
|
-
- Produces: sequence/listing 의 row 소비 로직 무변경, 디스크 slim → 메모리 full.
|
|
414
|
-
|
|
415
|
-
- [ ] **Step 1: sequence.py — read_jsonl → read_run_index**
|
|
416
|
-
|
|
417
|
-
[`scripts/okstra_ctl/sequence.py:8-9`](../../../scripts/okstra_ctl/sequence.py:8) import 교체:
|
|
418
|
-
`from .jsonl import read_jsonl` → `from .run_index_row import read_run_index`
|
|
419
|
-
|
|
420
|
-
[`scripts/okstra_ctl/sequence.py:43-44`](../../../scripts/okstra_ctl/sequence.py:43):
|
|
421
|
-
```python
|
|
422
|
-
for src in ("active.jsonl", "recent.jsonl"):
|
|
423
|
-
for r in read_run_index(home / src):
|
|
424
|
-
```
|
|
425
|
-
(아래 `r.get("projectId")`/`taskGroup`/`taskId`/`taskType`/`runSeq` 비교 로직은 그대로 — hydrate된 full row라 모두 존재.)
|
|
426
|
-
|
|
427
|
-
- [ ] **Step 2: listing.py — list_runs 의 read 를 read_run_index 로**
|
|
428
|
-
|
|
429
|
-
[`scripts/okstra_ctl/listing.py:10`](../../../scripts/okstra_ctl/listing.py:10) import 추가:
|
|
430
|
-
`from .run_index_row import read_run_index` (기존 `from .jsonl import read_jsonl`는 다른 곳에서 안 쓰이면 제거).
|
|
431
|
-
|
|
432
|
-
[`scripts/okstra_ctl/listing.py:84`](../../../scripts/okstra_ctl/listing.py:84):
|
|
433
|
-
```python
|
|
434
|
-
rows = read_run_index(home / "active.jsonl") + read_run_index(home / "recent.jsonl")
|
|
435
|
-
```
|
|
436
|
-
|
|
437
|
-
[`scripts/okstra_ctl/listing.py:88-89`](../../../scripts/okstra_ctl/listing.py:88) (archive도 hydrate):
|
|
438
|
-
```python
|
|
439
|
-
for f in sorted(archive_dir.glob("*/*.jsonl")):
|
|
440
|
-
rows.extend(read_run_index(f))
|
|
441
|
-
```
|
|
442
|
-
(`format_show`/`format_runs_table`/필터의 `projectId`/`taskType`/`invocationFile` 접근은 hydrate된 row라 그대로 동작.)
|
|
443
|
-
|
|
444
|
-
- [ ] **Step 3: Run regression tests**
|
|
445
|
-
|
|
446
|
-
Run: `python3 -m pytest tests/run/ -v`
|
|
447
|
-
Expected: PASS. listing/sequence 테스트가 status/show 출력을 검증한다면 동일 출력 유지(파생 필드가 hydrate로 채워짐).
|
|
448
|
-
|
|
449
|
-
- [ ] **Step 4: Commit**
|
|
450
|
-
|
|
451
|
-
```bash
|
|
452
|
-
git add scripts/okstra_ctl/sequence.py scripts/okstra_ctl/listing.py
|
|
453
|
-
git commit -m "refactor(index): sequence/listing 이 read_run_index 로 hydrate 후 소비"
|
|
454
|
-
```
|
|
455
|
-
|
|
456
|
-
---
|
|
457
|
-
|
|
458
|
-
### Task 5: reconcile.py 배선 — read hydrate + write slim
|
|
459
|
-
|
|
460
|
-
**Files:**
|
|
461
|
-
- Modify: `scripts/okstra_ctl/reconcile.py` (read 4곳, write 4곳)
|
|
462
|
-
- Test: 기존 `tests/run/test_*reconcile*.py` 회귀
|
|
463
|
-
|
|
464
|
-
**Interfaces:**
|
|
465
|
-
- Consumes: `read_run_index`, `slim_run_row` (Task 1·2).
|
|
466
|
-
- Produces: reconcile 가 in-place 재작성 시 slim 유지. 종결 추론/promote 로직 무변경.
|
|
467
|
-
|
|
468
|
-
- [ ] **Step 1: import 교체**
|
|
469
|
-
|
|
470
|
-
[`scripts/okstra_ctl/reconcile.py:10`](../../../scripts/okstra_ctl/reconcile.py:10):
|
|
471
|
-
```python
|
|
472
|
-
from .jsonl import append_jsonl, rotate_recent_if_needed
|
|
473
|
-
from .run_index_row import read_run_index, slim_run_row
|
|
474
|
-
```
|
|
475
|
-
(`read_jsonl` 제거 — 아래에서 모두 교체.)
|
|
476
|
-
|
|
477
|
-
- [ ] **Step 2: read 지점 3곳 → read_run_index**
|
|
478
|
-
|
|
479
|
-
- [`reconcile.py:89`](../../../scripts/okstra_ctl/reconcile.py:89) (`_sync_project_index`): `existing = read_jsonl(proj_index)` → `existing = read_run_index(proj_index)`
|
|
480
|
-
- [`reconcile.py:121`](../../../scripts/okstra_ctl/reconcile.py:121): `rows = read_jsonl(active)` → `rows = read_run_index(active)`
|
|
481
|
-
- [`reconcile.py:198`](../../../scripts/okstra_ctl/reconcile.py:198): `rows = read_jsonl(recent)` → `rows = read_run_index(recent)`
|
|
482
|
-
|
|
483
|
-
(`row.get("projectId")`/`taskType`/`runSeq`/`runDirRel`/`projectRoot` 소비는 hydrate된 row라 그대로 동작.)
|
|
484
|
-
|
|
485
|
-
- [ ] **Step 3: write 지점 4곳 → slim_run_row 적용**
|
|
486
|
-
|
|
487
|
-
- [`reconcile.py:96-97`](../../../scripts/okstra_ctl/reconcile.py:96) (`_sync_project_index` 재작성):
|
|
488
|
-
```python
|
|
489
|
-
for er in existing:
|
|
490
|
-
f.write(json.dumps(slim_run_row(er), separators=(",", ":")) + "\n")
|
|
491
|
-
```
|
|
492
|
-
- [`reconcile.py:170-171`](../../../scripts/okstra_ctl/reconcile.py:170) (active survivors 재작성):
|
|
493
|
-
```python
|
|
494
|
-
for row in survivors:
|
|
495
|
-
f.write(json.dumps(slim_run_row(row), separators=(",", ":")) + "\n")
|
|
496
|
-
```
|
|
497
|
-
- [`reconcile.py:174`](../../../scripts/okstra_ctl/reconcile.py:174) (recent append):
|
|
498
|
-
```python
|
|
499
|
-
append_jsonl(home / "recent.jsonl", slim_run_row(row))
|
|
500
|
-
```
|
|
501
|
-
- [`reconcile.py:223-225`](../../../scripts/okstra_ctl/reconcile.py:223) (recent in-place 재작성):
|
|
502
|
-
```python
|
|
503
|
-
for row in rows:
|
|
504
|
-
f.write(json.dumps(slim_run_row(row), separators=(",", ":")) + "\n")
|
|
505
|
-
```
|
|
506
|
-
|
|
507
|
-
(라인 175·177의 `row["projectId"]`/`row["projectRoot"]`는 hydrate된 row라 존재 — 그대로.)
|
|
508
|
-
|
|
509
|
-
- [ ] **Step 4: Run regression tests**
|
|
510
|
-
|
|
511
|
-
Run: `python3 -m pytest tests/run/ -v`
|
|
512
|
-
Expected: PASS (reconcile_active promote, reconcile_recent in-place, _sync_project_index 시나리오).
|
|
513
|
-
|
|
514
|
-
- [ ] **Step 5: Commit**
|
|
515
|
-
|
|
516
|
-
```bash
|
|
517
|
-
git add scripts/okstra_ctl/reconcile.py
|
|
518
|
-
git commit -m "refactor(index): reconcile read hydrate + write slim 로 정합 유지"
|
|
519
|
-
```
|
|
520
|
-
|
|
521
|
-
---
|
|
522
|
-
|
|
523
|
-
### Task 6: 일괄 마이그레이션 실행 + 빌드 + 전체 검증
|
|
524
|
-
|
|
525
|
-
**Files:**
|
|
526
|
-
- 변경 없음 (실행/검증 task)
|
|
527
|
-
|
|
528
|
-
**Interfaces:**
|
|
529
|
-
- Consumes: `reindex_slim` (Task 2).
|
|
530
|
-
|
|
531
|
-
- [ ] **Step 1: 전체 단위 테스트**
|
|
532
|
-
|
|
533
|
-
Run: `python3 -m pytest tests/`
|
|
534
|
-
Expected: PASS (전 도메인).
|
|
535
|
-
|
|
536
|
-
- [ ] **Step 2: 실 데이터 백업 후 마이그레이션 dry 확인**
|
|
537
|
-
|
|
538
|
-
```bash
|
|
539
|
-
cp -R ~/.okstra/recent.jsonl ~/.okstra/active.jsonl /tmp/okstra-index-backup/ 2>/dev/null; mkdir -p /tmp/okstra-index-backup
|
|
540
|
-
cp ~/.okstra/recent.jsonl /tmp/okstra-index-backup/recent.jsonl
|
|
541
|
-
python3 -c "import sys; sys.path.insert(0,'scripts'); from pathlib import Path; from okstra_ctl.run_index_row import reindex_slim, read_run_index; before=read_run_index(Path.home()/'.okstra'/'recent.jsonl'); print('rows', len(before))"
|
|
542
|
-
```
|
|
543
|
-
Expected: `rows 124` (또는 현재 수) — 에러 없이 hydrate 성공.
|
|
544
|
-
|
|
545
|
-
- [ ] **Step 3: 마이그레이션 실행**
|
|
546
|
-
|
|
547
|
-
```bash
|
|
548
|
-
python3 -c "import sys; sys.path.insert(0,'scripts'); from pathlib import Path; from okstra_ctl.run_index_row import reindex_slim; print('migrated', reindex_slim(Path.home()/'.okstra'))"
|
|
549
|
-
```
|
|
550
|
-
Expected: `migrated <N>` (recent + active + 6 index 의 총 row 수, 약 246).
|
|
551
|
-
|
|
552
|
-
- [ ] **Step 4: 마이그레이션 후 정합 검증**
|
|
553
|
-
|
|
554
|
-
```bash
|
|
555
|
-
python3 -c "import sys,json; sys.path.insert(0,'scripts'); from pathlib import Path; from okstra_ctl.run_index_row import read_run_index; bak=[json.loads(l) for l in open('/tmp/okstra-index-backup/recent.jsonl') if l.strip()]; cur=read_run_index(Path.home()/'.okstra'/'recent.jsonl'); bak_by={r['runId']:r for r in bak}; assert all(cur_r['invocationFile']==bak_by[cur_r['runId']].get('invocationFile') and cur_r['taskKey']==bak_by[cur_r['runId']].get('taskKey') for cur_r in cur if cur_r['runId'] in bak_by), 'mismatch'; print('OK: hydrate 가 원본 파생필드 재현')"
|
|
556
|
-
```
|
|
557
|
-
Expected: `OK: hydrate 가 원본 파생필드 재현` — 마이그레이션 전 full row의 파생 필드와 hydrate 결과가 일치.
|
|
558
|
-
|
|
559
|
-
- [ ] **Step 5: 멱등 재실행 + CLI 스모크**
|
|
560
|
-
|
|
561
|
-
```bash
|
|
562
|
-
python3 -c "import sys; sys.path.insert(0,'scripts'); from pathlib import Path; from okstra_ctl.run_index_row import reindex_slim; print('reidempotent', reindex_slim(Path.home()/'.okstra'))"
|
|
563
|
-
node bin/okstra --version
|
|
564
|
-
```
|
|
565
|
-
Expected: 두 번째 reindex_slim도 정상, CLI 정상.
|
|
566
|
-
|
|
567
|
-
- [ ] **Step 6: runtime 빌드 + 커밋**
|
|
568
|
-
|
|
569
|
-
```bash
|
|
570
|
-
npm run build
|
|
571
|
-
git add -A
|
|
572
|
-
git commit -m "chore(index): runtime 동기화 (run-index row slim 포맷)"
|
|
573
|
-
```
|
|
574
|
-
Expected: `runtime/` 가 새 소스로 갱신.
|
|
575
|
-
|
|
576
|
-
---
|
|
577
|
-
|
|
578
|
-
## Self-Review
|
|
579
|
-
|
|
580
|
-
- **Spec coverage:** §3.1 제거 5필드 → Task 1 `DERIVED_FIELDS`/`build_run_index_row`. §3.2 유지 13필드 → Task 1 row 구성 + 테스트. §4.1 두 헬퍼 → Task 1·2. §4.2 read hydrate(6곳 후보 → 검증으로 3곳 확정) → Task 4·5 (pane_reclaim/recap/wizard는 run-index row 비소비로 제외, 본문에 근거 명시). §5 마이그레이션(migrate.py 분리) → Task 2 `reindex_slim` + Task 6 실행. §6 테스트 5종 → Task 1·2 테스트. §7 비범위 → 계획에 포함 안 함.
|
|
581
|
-
- **Placeholder scan:** 모든 코드 step에 실제 코드/명령/기대출력 포함. TBD 없음.
|
|
582
|
-
- **Type consistency:** `build_run_index_row`/`slim_run_row`/`hydrate_run_row`/`read_run_index`/`reindex_slim`/`DERIVED_FIELDS` 시그니처가 Task 1→6 전반에서 일관. row 키 camelCase 일관.
|
|
583
|
-
- **주의(실행 중 확인):** 기존 `tests/run/`의 일부 테스트가 디스크 row에서 파생 필드를 직접 단언할 수 있다. Task 3·4·5의 회귀 step에서 발견 시, read를 `read_run_index`로 바꾸거나 단언을 동일 값 기준으로 갱신한다 — assertion 약화 금지.
|