okstra 0.96.1 → 0.97.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (121) hide show
  1. package/README.kr.md +1 -1
  2. package/README.md +1 -1
  3. package/bin/okstra +1 -1
  4. package/docs/contributor-change-matrix.md +1 -1
  5. package/docs/kr/architecture/storage-model.md +273 -0
  6. package/docs/kr/architecture.md +6 -277
  7. package/docs/kr/cli.md +1 -1
  8. package/docs/project-structure-overview.md +26 -22
  9. package/docs/superpowers/plans/2026-06-20-error-feedback-loop.md +1019 -0
  10. package/docs/superpowers/plans/2026-06-20-run-index-row-normalization.md +583 -0
  11. package/docs/superpowers/plans/2026-06-20-stage-auto-integrate-teardown.md +819 -0
  12. package/docs/superpowers/plans/2026-06-21-release-handoff-local-checkout.md +348 -0
  13. package/docs/superpowers/specs/2026-06-06-stage-worktree-isolation-design.md +2 -2
  14. package/docs/superpowers/specs/2026-06-20-error-feedback-loop-design.md +171 -0
  15. package/docs/superpowers/specs/2026-06-20-run-index-row-normalization-design.md +112 -0
  16. package/docs/superpowers/specs/2026-06-20-stage-auto-integrate-teardown-design.md +145 -0
  17. package/docs/superpowers/specs/2026-06-21-release-handoff-local-checkout-design.md +113 -0
  18. package/docs/task-process/release-handoff.md +3 -3
  19. package/package.json +1 -1
  20. package/runtime/BUILD.json +2 -2
  21. package/runtime/bin/lib/okstra/cli.sh +1 -1
  22. package/runtime/bin/lib/okstra/interactive.sh +30 -4
  23. package/runtime/bin/lib/okstra-ctl/cmd-tail.sh +18 -5
  24. package/runtime/bin/okstra-render-final-report.py +4 -15
  25. package/runtime/bin/okstra.sh +5 -2
  26. package/runtime/prompts/launch.template.md +1 -0
  27. package/runtime/prompts/lead/report-writer.md +1 -1
  28. package/runtime/prompts/lead/team-contract.md +1 -1
  29. package/runtime/prompts/profiles/final-verification.md +1 -0
  30. package/runtime/prompts/profiles/forbidden-actions.json +1 -1
  31. package/runtime/prompts/profiles/release-handoff.md +10 -4
  32. package/runtime/python/okstra_ctl/__init__.py +3 -2
  33. package/runtime/python/okstra_ctl/backfill.py +12 -21
  34. package/runtime/python/okstra_ctl/codex_dispatch.py +7 -2
  35. package/runtime/python/okstra_ctl/conformance.py +21 -3
  36. package/runtime/python/okstra_ctl/consumers.py +7 -1
  37. package/runtime/python/okstra_ctl/dispatch_core.py +10 -2
  38. package/runtime/python/okstra_ctl/doctor.py +29 -12
  39. package/runtime/python/okstra_ctl/error_log_core.py +72 -0
  40. package/runtime/python/okstra_ctl/error_report.py +8 -58
  41. package/runtime/python/okstra_ctl/error_zip.py +319 -0
  42. package/runtime/python/okstra_ctl/fanout.py +5 -2
  43. package/runtime/python/okstra_ctl/fix_cycles.py +14 -2
  44. package/runtime/python/okstra_ctl/git_reconcile.py +7 -1
  45. package/runtime/python/okstra_ctl/handoff.py +125 -26
  46. package/runtime/python/okstra_ctl/implementation_stage.py +9 -2
  47. package/runtime/python/okstra_ctl/improvement_lenses.py +2 -0
  48. package/runtime/python/okstra_ctl/index.py +34 -91
  49. package/runtime/python/okstra_ctl/jsonl.py +29 -18
  50. package/runtime/python/okstra_ctl/listing.py +30 -5
  51. package/runtime/python/okstra_ctl/md_table.py +1 -1
  52. package/runtime/python/okstra_ctl/migrate.py +17 -7
  53. package/runtime/python/okstra_ctl/paths.py +1 -1
  54. package/runtime/python/okstra_ctl/recap.py +12 -3
  55. package/runtime/python/okstra_ctl/reconcile.py +13 -11
  56. package/runtime/python/okstra_ctl/render.py +49 -12
  57. package/runtime/python/okstra_ctl/render_final_report.py +4 -0
  58. package/runtime/python/okstra_ctl/report_views.py +47 -6
  59. package/runtime/python/okstra_ctl/run.py +128 -100
  60. package/runtime/python/okstra_ctl/run_index_row.py +118 -0
  61. package/runtime/python/okstra_ctl/schema_excerpt.py +8 -2
  62. package/runtime/python/okstra_ctl/sequence.py +2 -2
  63. package/runtime/python/okstra_ctl/stage_integrate.py +193 -0
  64. package/runtime/python/okstra_ctl/stage_targets.py +2 -8
  65. package/runtime/python/okstra_ctl/task_target.py +4 -1
  66. package/runtime/python/okstra_ctl/team_reconcile.py +6 -1
  67. package/runtime/python/okstra_ctl/wizard.py +27 -12
  68. package/runtime/python/okstra_ctl/workflow.py +9 -1
  69. package/runtime/python/okstra_ctl/worktree.py +93 -4
  70. package/runtime/python/okstra_ctl/worktree_registry.py +57 -8
  71. package/runtime/python/okstra_token_usage/collect.py +43 -38
  72. package/runtime/skills/okstra-brief/SKILL.md +43 -12
  73. package/runtime/skills/okstra-inspect/SKILL.md +44 -1
  74. package/runtime/templates/reports/final-report.template.md +2 -2
  75. package/runtime/templates/reports/release-handoff-input.template.md +1 -1
  76. package/runtime/validators/forbidden_actions.py +8 -1
  77. package/runtime/validators/validate-brief.py +13 -2
  78. package/runtime/validators/validate-run.py +27 -6
  79. package/runtime/validators/validate-schedule.py +1 -2
  80. package/runtime/validators/validate_improvement_report.py +14 -0
  81. package/src/cli-registry.mjs +45 -31
  82. package/src/{codex-dispatch.mjs → commands/execute/codex-dispatch.mjs} +3 -3
  83. package/src/{codex-run.mjs → commands/execute/codex-run.mjs} +3 -3
  84. package/src/{error-log.mjs → commands/execute/error-log.mjs} +1 -1
  85. package/src/{git-reconcile.mjs → commands/execute/git-reconcile.mjs} +1 -1
  86. package/src/{handoff.mjs → commands/execute/handoff.mjs} +3 -1
  87. package/src/commands/execute/integrate-stages.mjs +25 -0
  88. package/src/{plan-validate.mjs → commands/execute/plan-validate.mjs} +1 -1
  89. package/src/{render-bundle.mjs → commands/execute/render-bundle.mjs} +4 -4
  90. package/src/{run.mjs → commands/execute/run.mjs} +4 -4
  91. package/src/{spawn-followups.mjs → commands/execute/spawn-followups.mjs} +1 -1
  92. package/src/{team.mjs → commands/execute/team.mjs} +3 -3
  93. package/src/{token-usage.mjs → commands/execute/token-usage.mjs} +1 -1
  94. package/src/{wizard.mjs → commands/execute/wizard.mjs} +15 -6
  95. package/src/{worktree-lookup.mjs → commands/execute/worktree-lookup.mjs} +1 -1
  96. package/src/{context-cost.mjs → commands/inspect/context-cost.mjs} +2 -2
  97. package/src/{error-report.mjs → commands/inspect/error-report.mjs} +2 -2
  98. package/src/commands/inspect/error-zip.mjs +25 -0
  99. package/src/{recap.mjs → commands/inspect/recap.mjs} +2 -2
  100. package/src/{task-list.mjs → commands/inspect/task-list.mjs} +1 -1
  101. package/src/{task-show.mjs → commands/inspect/task-show.mjs} +1 -1
  102. package/src/{check-project.mjs → commands/lifecycle/check-project.mjs} +2 -2
  103. package/src/{config.mjs → commands/lifecycle/config.mjs} +3 -3
  104. package/src/{doctor.mjs → commands/lifecycle/doctor.mjs} +4 -4
  105. package/src/{install.mjs → commands/lifecycle/install.mjs} +18 -13
  106. package/src/{migrate.mjs → commands/lifecycle/migrate.mjs} +1 -1
  107. package/src/{paths.mjs → commands/lifecycle/paths.mjs} +1 -60
  108. package/src/{setup.mjs → commands/lifecycle/setup.mjs} +4 -4
  109. package/src/{uninstall.mjs → commands/lifecycle/uninstall.mjs} +20 -32
  110. package/src/{memory.mjs → commands/memory/memory.mjs} +32 -7
  111. package/src/{inject-report-index.mjs → commands/report/inject-report-index.mjs} +1 -1
  112. package/src/{render-final-report.mjs → commands/report/render-final-report.mjs} +1 -1
  113. package/src/{render-views.mjs → commands/report/render-views.mjs} +1 -1
  114. package/src/lib/paths.mjs +60 -0
  115. package/src/{_python-helper.mjs → lib/python-helper.mjs} +35 -13
  116. package/src/{version.mjs → lib/version.mjs} +2 -2
  117. /package/src/{okstra-dirs.mjs → lib/okstra-dirs.mjs} +0 -0
  118. /package/src/{_proc.mjs → lib/proc.mjs} +0 -0
  119. /package/src/{runtime-manifest.mjs → lib/runtime-manifest.mjs} +0 -0
  120. /package/src/{runtime-resolver.mjs → lib/runtime-resolver.mjs} +0 -0
  121. /package/src/{skill-catalog.mjs → lib/skill-catalog.mjs} +0 -0
@@ -3,35 +3,35 @@ const SUMMARY_COLUMN = 25;
3
3
  export const COMMAND_REGISTRY = [
4
4
  {
5
5
  name: "install",
6
- module: "./install.mjs",
6
+ module: "./commands/lifecycle/install.mjs",
7
7
  export: "runInstall",
8
8
  category: "admin",
9
9
  summary: ["Install runtime; seed skills into existing agent homes"],
10
10
  },
11
11
  {
12
12
  name: "ensure-installed",
13
- module: "./install.mjs",
13
+ module: "./commands/lifecycle/install.mjs",
14
14
  export: "runEnsureInstalled",
15
15
  category: "admin",
16
16
  summary: ["Verify install is fresh; reinstall if stale (idempotent)"],
17
17
  },
18
18
  {
19
19
  name: "uninstall",
20
- module: "./uninstall.mjs",
20
+ module: "./commands/lifecycle/uninstall.mjs",
21
21
  export: "runUninstall",
22
22
  category: "admin",
23
23
  summary: ["Remove runtime + skills (user data preserved by default)"],
24
24
  },
25
25
  {
26
26
  name: "setup",
27
- module: "./setup.mjs",
27
+ module: "./commands/lifecycle/setup.mjs",
28
28
  export: "run",
29
29
  category: "admin",
30
30
  summary: ["Register the current project (.okstra/project.json)"],
31
31
  },
32
32
  {
33
33
  name: "doctor",
34
- module: "./doctor.mjs",
34
+ module: "./commands/lifecycle/doctor.mjs",
35
35
  export: "run",
36
36
  category: "admin",
37
37
  summary: [
@@ -40,42 +40,49 @@ export const COMMAND_REGISTRY = [
40
40
  },
41
41
  {
42
42
  name: "check-project",
43
- module: "./check-project.mjs",
43
+ module: "./commands/lifecycle/check-project.mjs",
44
44
  export: "run",
45
45
  category: "admin",
46
46
  summary: ["Verify the current project has been registered with setup"],
47
47
  },
48
48
  {
49
49
  name: "config",
50
- module: "./config.mjs",
50
+ module: "./commands/lifecycle/config.mjs",
51
51
  export: "run",
52
52
  category: "admin",
53
53
  summary: ["Read / write okstra settings (e.g. PR template path)"],
54
54
  },
55
55
  {
56
56
  name: "migrate",
57
- module: "./migrate.mjs",
57
+ module: "./commands/lifecycle/migrate.mjs",
58
58
  export: "run",
59
59
  category: "admin",
60
60
  summary: ["Move legacy .project-docs/okstra/ to .okstra/ (one-shot)"],
61
61
  },
62
62
  {
63
63
  name: "git-reconcile",
64
- module: "./git-reconcile.mjs",
64
+ module: "./commands/execute/git-reconcile.mjs",
65
65
  export: "run",
66
66
  category: "admin",
67
67
  summary: ["Reconcile stale stage SHAs after external git history changes"],
68
68
  },
69
69
  {
70
70
  name: "handoff",
71
- module: "./handoff.mjs",
71
+ module: "./commands/execute/handoff.mjs",
72
72
  export: "run",
73
73
  category: "admin",
74
74
  summary: ["Stage-group release-handoff helpers (eligible/assemble/record)"],
75
75
  },
76
+ {
77
+ name: "integrate-stages",
78
+ module: "./commands/execute/integrate-stages.mjs",
79
+ export: "run",
80
+ category: "admin",
81
+ summary: ["stage 들을 task 브랜치에 통합 머지하고 stage worktree 를 정리"],
82
+ },
76
83
  {
77
84
  name: "paths",
78
- module: "./paths.mjs",
85
+ module: "./commands/lifecycle/paths.mjs",
79
86
  export: "run",
80
87
  category: "admin",
81
88
  summary: [
@@ -84,56 +91,63 @@ export const COMMAND_REGISTRY = [
84
91
  },
85
92
  {
86
93
  name: "task-list",
87
- module: "./task-list.mjs",
94
+ module: "./commands/inspect/task-list.mjs",
88
95
  export: "run",
89
96
  category: "introspection",
90
97
  summary: ["List tasks registered in the current project"],
91
98
  },
92
99
  {
93
100
  name: "task-show",
94
- module: "./task-show.mjs",
101
+ module: "./commands/inspect/task-show.mjs",
95
102
  export: "run",
96
103
  category: "introspection",
97
104
  summary: ["Summarize a task's manifest + workflow phase state"],
98
105
  },
99
106
  {
100
107
  name: "context-cost",
101
- module: "./context-cost.mjs",
108
+ module: "./commands/inspect/context-cost.mjs",
102
109
  export: "run",
103
110
  category: "introspection",
104
111
  summary: ["Estimate file/read context cost for a task bundle"],
105
112
  },
106
113
  {
107
114
  name: "error-report",
108
- module: "./error-report.mjs",
115
+ module: "./commands/inspect/error-report.mjs",
109
116
  export: "run",
110
117
  category: "introspection",
111
118
  summary: ["Aggregate okstra-run error logs into a task-level report"],
112
119
  },
120
+ {
121
+ name: "error-zip",
122
+ module: "./commands/inspect/error-zip.mjs",
123
+ export: "run",
124
+ category: "introspection",
125
+ summary: ["Bundle cross-project okstra error logs into a zip"],
126
+ },
113
127
  {
114
128
  name: "recap",
115
- module: "./recap.mjs",
129
+ module: "./commands/inspect/recap.mjs",
116
130
  export: "run",
117
131
  category: "introspection",
118
132
  summary: ["Assemble a task's run-to-run phase recap or append a recap log entry"],
119
133
  },
120
134
  {
121
135
  name: "worktree-lookup",
122
- module: "./worktree-lookup.mjs",
136
+ module: "./commands/execute/worktree-lookup.mjs",
123
137
  export: "run",
124
138
  category: "introspection",
125
139
  summary: ["Look up registered worktree for a task-key"],
126
140
  },
127
141
  {
128
142
  name: "plan-validate",
129
- module: "./plan-validate.mjs",
143
+ module: "./commands/execute/plan-validate.mjs",
130
144
  export: "run",
131
145
  category: "introspection",
132
146
  summary: ["Check an approved-plan file for the approval marker"],
133
147
  },
134
148
  {
135
149
  name: "render-bundle",
136
- module: "./render-bundle.mjs",
150
+ module: "./commands/execute/render-bundle.mjs",
137
151
  export: "run",
138
152
  category: "introspection",
139
153
  summary: [
@@ -143,14 +157,14 @@ export const COMMAND_REGISTRY = [
143
157
  },
144
158
  {
145
159
  name: "run",
146
- module: "./run.mjs",
160
+ module: "./commands/execute/run.mjs",
147
161
  export: "run",
148
162
  category: "introspection",
149
163
  summary: ["Host-aware auto execution front door"],
150
164
  },
151
165
  {
152
166
  name: "codex-run",
153
- module: "./codex-run.mjs",
167
+ module: "./commands/execute/codex-run.mjs",
154
168
  export: "run",
155
169
  category: "introspection",
156
170
  summary: [
@@ -160,21 +174,21 @@ export const COMMAND_REGISTRY = [
160
174
  },
161
175
  {
162
176
  name: "codex-dispatch",
163
- module: "./codex-dispatch.mjs",
177
+ module: "./commands/execute/codex-dispatch.mjs",
164
178
  export: "run",
165
179
  category: "introspection",
166
180
  summary: ["Dispatch CLI-backed workers for a prepared Codex run"],
167
181
  },
168
182
  {
169
183
  name: "team",
170
- module: "./team.mjs",
184
+ module: "./commands/execute/team.mjs",
171
185
  export: "run",
172
186
  category: "introspection",
173
187
  summary: ["Dispatch and await tmux-pane workers for external leads"],
174
188
  },
175
189
  {
176
190
  name: "wizard",
177
- module: "./wizard.mjs",
191
+ module: "./commands/execute/wizard.mjs",
178
192
  export: "run",
179
193
  category: "introspection",
180
194
  summary: [
@@ -184,7 +198,7 @@ export const COMMAND_REGISTRY = [
184
198
  },
185
199
  {
186
200
  name: "token-usage",
187
- module: "./token-usage.mjs",
201
+ module: "./commands/execute/token-usage.mjs",
188
202
  export: "run",
189
203
  category: "introspection",
190
204
  summary: [
@@ -195,42 +209,42 @@ export const COMMAND_REGISTRY = [
195
209
  },
196
210
  {
197
211
  name: "render-views",
198
- module: "./render-views.mjs",
212
+ module: "./commands/report/render-views.mjs",
199
213
  export: "run",
200
214
  category: "introspection",
201
215
  summary: ["Render slim AI + self-contained HTML views of a final report"],
202
216
  },
203
217
  {
204
218
  name: "render-final-report",
205
- module: "./render-final-report.mjs",
219
+ module: "./commands/report/render-final-report.mjs",
206
220
  export: "run",
207
221
  category: "introspection",
208
222
  summary: ["Render the markdown sibling of a final-report data.json"],
209
223
  },
210
224
  {
211
225
  name: "inject-report-index",
212
- module: "./inject-report-index.mjs",
226
+ module: "./commands/report/inject-report-index.mjs",
213
227
  export: "run",
214
228
  category: "introspection",
215
229
  summary: ["Add the top-of-report Index + scroll anchors to a report"],
216
230
  },
217
231
  {
218
232
  name: "spawn-followups",
219
- module: "./spawn-followups.mjs",
233
+ module: "./commands/execute/spawn-followups.mjs",
220
234
  export: "run",
221
235
  category: "introspection",
222
236
  summary: ["Create follow-up task bundles from a final report"],
223
237
  },
224
238
  {
225
239
  name: "error-log",
226
- module: "./error-log.mjs",
240
+ module: "./commands/execute/error-log.mjs",
227
241
  export: "run",
228
242
  category: "introspection",
229
243
  summary: ["Append run error events to the run error log"],
230
244
  },
231
245
  {
232
246
  name: "memory",
233
- module: "./memory.mjs",
247
+ module: "./commands/memory/memory.mjs",
234
248
  export: "run",
235
249
  category: "introspection",
236
250
  summary: [
@@ -1,5 +1,5 @@
1
- import { runPythonModule } from "./_python-helper.mjs";
2
- import { resolvePaths } from "./paths.mjs";
1
+ import { runPythonModule } from "../../lib/python-helper.mjs";
2
+ import { resolvePaths } from "../../lib/paths.mjs";
3
3
 
4
4
  const USAGE = `okstra codex-dispatch — dispatch CLI-backed workers for a prepared Codex run
5
5
 
@@ -66,5 +66,5 @@ export async function run(args) {
66
66
  args: buildCodexDispatchArgs(args, paths),
67
67
  stdio: "inherit-stdout",
68
68
  });
69
- return result.code ?? 0;
69
+ return result.code;
70
70
  }
@@ -1,5 +1,5 @@
1
- import { runPythonModule } from "./_python-helper.mjs";
2
- import { resolvePaths } from "./paths.mjs";
1
+ import { runPythonModule } from "../../lib/python-helper.mjs";
2
+ import { resolvePaths } from "../../lib/paths.mjs";
3
3
 
4
4
  const USAGE = `okstra codex-run — prepare a render-only Codex lead task bundle
5
5
 
@@ -64,5 +64,5 @@ export async function run(args) {
64
64
  args: buildCodexRunArgs(args, paths.workspace),
65
65
  stdio: "inherit-stdout",
66
66
  });
67
- return result.code ?? 0;
67
+ return result.code;
68
68
  }
@@ -1,4 +1,4 @@
1
- import { runInstalledScript } from "./_python-helper.mjs";
1
+ import { runInstalledScript } from "../../lib/python-helper.mjs";
2
2
 
3
3
  const USAGE = `okstra error-log — append okstra run error events to the run error log
4
4
 
@@ -1,4 +1,4 @@
1
- import { runPythonModule } from "./_python-helper.mjs";
1
+ import { runPythonModule } from "../../lib/python-helper.mjs";
2
2
 
3
3
  const USAGE = `okstra git-reconcile — okstra 밖 git 히스토리 변경 후 기록 화해
4
4
 
@@ -1,4 +1,4 @@
1
- import { runPythonModule } from "./_python-helper.mjs";
1
+ import { runPythonModule } from "../../lib/python-helper.mjs";
2
2
 
3
3
  const USAGE = `okstra handoff — release-handoff stage-group 보조 (자격/수집/기록)
4
4
 
@@ -13,6 +13,8 @@ Usage:
13
13
  --report-path <md> --data-json <json>
14
14
  okstra handoff record-pr --plan-run-root <dir> --stages 2,3 \\
15
15
  --branch <b> --url <u>
16
+ okstra handoff local-checkout --project-root <dir> --project-id <id> \\
17
+ --task-group <g> --task-id <t>
16
18
 
17
19
  Exit codes: 0 ok / 1 자격·전제 위반 / 2 stage 간 merge 충돌(conflicts 동봉)
18
20
  `;
@@ -0,0 +1,25 @@
1
+ import { runPythonModule } from "../../lib/python-helper.mjs";
2
+
3
+ const USAGE = `okstra integrate-stages — stage 들을 task 브랜치에 통합 머지 + worktree 정리
4
+
5
+ A thin shim over \`python3 -m okstra_ctl.stage_integrate\`.
6
+
7
+ Usage:
8
+ okstra integrate-stages --project-id <id> \\
9
+ --task-group <g> --task-id <t> --plan-run-root <dir> \\
10
+ --task-worktree-path <dir> [--stage-map-json '<json>']
11
+
12
+ Exit codes: 0 ok / 2 stage 간 merge 충돌 / 1 python 프로세스 비정상 종료(폴백)
13
+ `;
14
+
15
+ export async function run(args) {
16
+ if (args.includes("--help") || args.includes("-h")) {
17
+ process.stdout.write(USAGE);
18
+ return 0;
19
+ }
20
+ const { code } = await runPythonModule({
21
+ module: "okstra_ctl.stage_integrate",
22
+ args,
23
+ });
24
+ return code ?? 1;
25
+ }
@@ -1,4 +1,4 @@
1
- import { runPythonSnippet, emitJsonError } from "./_python-helper.mjs";
1
+ import { runPythonSnippet, emitJsonError } from "../../lib/python-helper.mjs";
2
2
 
3
3
  const USAGE = `okstra plan-validate — verify an approved-plan file has a recognised approval marker
4
4
 
@@ -1,6 +1,6 @@
1
- import { runPythonModule } from "./_python-helper.mjs";
2
- import { resolvePaths } from "./paths.mjs";
3
- import { resolveRuntime } from "./runtime-resolver.mjs";
1
+ import { runPythonModule } from "../../lib/python-helper.mjs";
2
+ import { resolvePaths } from "../../lib/paths.mjs";
3
+ import { resolveRuntime } from "../../lib/runtime-resolver.mjs";
4
4
 
5
5
  const USAGE = `okstra render-bundle — preview the task bundle without launching claude
6
6
 
@@ -107,5 +107,5 @@ export async function run(args) {
107
107
  args: finalArgs,
108
108
  stdio: "inherit-stdout",
109
109
  });
110
- return result.code ?? 0;
110
+ return result.code;
111
111
  }
@@ -1,7 +1,7 @@
1
- import { runEnsureInstalled } from "./install.mjs";
2
- import { runPythonModule } from "./_python-helper.mjs";
3
- import { resolvePaths } from "./paths.mjs";
4
- import { resolveRuntime } from "./runtime-resolver.mjs";
1
+ import { runEnsureInstalled } from "../lifecycle/install.mjs";
2
+ import { runPythonModule } from "../../lib/python-helper.mjs";
3
+ import { resolvePaths } from "../../lib/paths.mjs";
4
+ import { resolveRuntime } from "../../lib/runtime-resolver.mjs";
5
5
 
6
6
  const USAGE = `okstra run — host-aware okstra execution front door
7
7
 
@@ -1,4 +1,4 @@
1
- import { runInstalledScript } from "./_python-helper.mjs";
1
+ import { runInstalledScript } from "../../lib/python-helper.mjs";
2
2
 
3
3
  const USAGE = `okstra spawn-followups — create follow-up task bundles from a final report
4
4
 
@@ -1,5 +1,5 @@
1
- import { runPythonModule } from "./_python-helper.mjs";
2
- import { resolvePaths } from "./paths.mjs";
1
+ import { runPythonModule } from "../../lib/python-helper.mjs";
2
+ import { resolvePaths } from "../../lib/paths.mjs";
3
3
 
4
4
  const USAGE = `okstra team — dispatch and await tmux-pane workers for external leads
5
5
 
@@ -59,5 +59,5 @@ export async function run(args) {
59
59
  args: buildTeamArgs(args, paths),
60
60
  stdio: "inherit-stdout",
61
61
  });
62
- return result.code ?? 0;
62
+ return result.code;
63
63
  }
@@ -1,4 +1,4 @@
1
- import { runInstalledScript } from "./_python-helper.mjs";
1
+ import { runInstalledScript } from "../../lib/python-helper.mjs";
2
2
 
3
3
  const USAGE = `okstra token-usage — collect token usage for a run
4
4
 
@@ -2,8 +2,8 @@ import { mkdtempSync } from "node:fs";
2
2
  import { tmpdir } from "node:os";
3
3
  import { join } from "node:path";
4
4
 
5
- import { runPythonModule } from "./_python-helper.mjs";
6
- import { resolvePaths } from "./paths.mjs";
5
+ import { runPythonModule } from "../../lib/python-helper.mjs";
6
+ import { resolvePaths } from "../../lib/paths.mjs";
7
7
 
8
8
  const USAGE = `okstra wizard — interactive okstra-run input collector
9
9
 
@@ -31,19 +31,28 @@ All subcommands emit a single JSON object on stdout. On validation failure
31
31
  'step' returns {ok:false, error, current} so the skill can re-prompt.
32
32
  `;
33
33
 
34
- function parseFlags(args) {
34
+ // Every wizard flag takes a value (there are no boolean flags), so the token
35
+ // after a flag is always its value — even free-text answers that begin with
36
+ // "--". A heuristic that rejects "--"-prefixed values would make legitimate
37
+ // directive/base-ref answers unsubmittable. The inline "--key=value" form lets
38
+ // callers pass any value (including empty) unambiguously.
39
+ export function parseFlags(args) {
35
40
  const out = {};
36
41
  for (let i = 0; i < args.length; i += 1) {
37
42
  const a = args[i];
38
43
  if (!a.startsWith("--")) {
39
44
  throw new Error(`unexpected positional argument: ${a}`);
40
45
  }
46
+ const eq = a.indexOf("=");
47
+ if (eq !== -1) {
48
+ out[a.slice(2, eq)] = a.slice(eq + 1);
49
+ continue;
50
+ }
41
51
  const key = a.slice(2);
42
- const next = args[i + 1];
43
- if (next === undefined || next.startsWith("--")) {
52
+ if (i + 1 >= args.length) {
44
53
  throw new Error(`flag --${key} requires a value`);
45
54
  }
46
- out[key] = next;
55
+ out[key] = args[i + 1];
47
56
  i += 1;
48
57
  }
49
58
  return out;
@@ -1,4 +1,4 @@
1
- import { runPythonSnippet, emitJsonError } from "./_python-helper.mjs";
1
+ import { runPythonSnippet, emitJsonError } from "../../lib/python-helper.mjs";
2
2
 
3
3
  const USAGE = `okstra worktree-lookup — look up registered worktree for a task-key
4
4
 
@@ -1,4 +1,4 @@
1
- import { runPythonModule } from "./_python-helper.mjs";
1
+ import { runPythonModule } from "../../lib/python-helper.mjs";
2
2
 
3
3
  const USAGE = `okstra context-cost — estimate context/read cost for a task bundle
4
4
 
@@ -23,5 +23,5 @@ export async function run(args) {
23
23
  args,
24
24
  stdio: "inherit-stdout",
25
25
  });
26
- return result.code ?? 0;
26
+ return result.code;
27
27
  }
@@ -1,4 +1,4 @@
1
- import { runPythonModule } from "./_python-helper.mjs";
1
+ import { runPythonModule } from "../../lib/python-helper.mjs";
2
2
 
3
3
  const USAGE = `okstra error-report — aggregate okstra-run error logs into a task report
4
4
 
@@ -23,5 +23,5 @@ export async function run(args) {
23
23
  args,
24
24
  stdio: "inherit-stdout",
25
25
  });
26
- return result.code ?? 0;
26
+ return result.code;
27
27
  }
@@ -0,0 +1,25 @@
1
+ import { runPythonModule } from "../../lib/python-helper.mjs";
2
+
3
+ const USAGE = `okstra error-zip — bundle cross-project okstra error logs into a zip
4
+
5
+ Usage:
6
+ okstra error-zip --out <path>
7
+
8
+ Walks the global run-index (~/.okstra/recent.jsonl + active.jsonl), collects
9
+ every target project's runs/*/logs/errors-*.jsonl, anonymizes records, and
10
+ writes a .zip (report.md + errors/anonymized.jsonl) to <path>. Read-only
11
+ against every target's .okstra/. Prints a JSON summary to stdout.
12
+ `;
13
+
14
+ export async function run(args) {
15
+ if (args.includes("--help") || args.includes("-h")) {
16
+ process.stdout.write(USAGE);
17
+ return 0;
18
+ }
19
+ const result = await runPythonModule({
20
+ module: "okstra_ctl.error_zip",
21
+ args,
22
+ stdio: "inherit-stdout",
23
+ });
24
+ return result.code;
25
+ }
@@ -1,4 +1,4 @@
1
- import { runPythonModule } from "./_python-helper.mjs";
1
+ import { runPythonModule } from "../../lib/python-helper.mjs";
2
2
 
3
3
  const USAGE = `okstra recap — assemble a task's run-to-run phase recap, or append a recap log entry
4
4
 
@@ -23,5 +23,5 @@ export async function run(args) {
23
23
  args,
24
24
  stdio: "inherit-stdout",
25
25
  });
26
- return result.code ?? 0;
26
+ return result.code;
27
27
  }
@@ -1,4 +1,4 @@
1
- import { runPythonSnippet, emitJsonError } from "./_python-helper.mjs";
1
+ import { runPythonSnippet, emitJsonError } from "../../lib/python-helper.mjs";
2
2
 
3
3
  const USAGE = `okstra task-list — list okstra tasks registered in this project
4
4
 
@@ -1,4 +1,4 @@
1
- import { runPythonSnippet, emitJsonError } from "./_python-helper.mjs";
1
+ import { runPythonSnippet, emitJsonError } from "../../lib/python-helper.mjs";
2
2
 
3
3
  const USAGE = `okstra task-show — summarize a task's manifest and workflow state
4
4
 
@@ -1,7 +1,7 @@
1
1
  import { promises as fs } from "node:fs";
2
2
  import { join } from "node:path";
3
- import { buildPythonpath, resolvePaths } from "./paths.mjs";
4
- import { fileExists, runProcess } from "./_proc.mjs";
3
+ import { buildPythonpath, resolvePaths } from "../../lib/paths.mjs";
4
+ import { fileExists, runProcess } from "../../lib/proc.mjs";
5
5
 
6
6
  const USAGE = `okstra check-project — verify that the current project has okstra setup
7
7
 
@@ -1,9 +1,9 @@
1
1
  import { promises as fs } from "node:fs";
2
2
  import { homedir } from "node:os";
3
3
  import { dirname, join, resolve, isAbsolute } from "node:path";
4
- import { buildPythonpath, resolvePaths } from "./paths.mjs";
5
- import { OKSTRA_DIR, projectJsonPath } from "./okstra-dirs.mjs";
6
- import { fileExists, runProcess } from "./_proc.mjs";
4
+ import { buildPythonpath, resolvePaths } from "../../lib/paths.mjs";
5
+ import { OKSTRA_DIR, projectJsonPath } from "../../lib/okstra-dirs.mjs";
6
+ import { fileExists, runProcess } from "../../lib/proc.mjs";
7
7
 
8
8
  const USAGE = `okstra config — read / write okstra settings (project + global scopes)
9
9
 
@@ -1,11 +1,11 @@
1
1
  import { promises as fs } from "node:fs";
2
2
  import { homedir } from "node:os";
3
3
  import { join } from "node:path";
4
- import { resolvePaths } from "./paths.mjs";
5
- import { fileExists, runProcess } from "./_proc.mjs";
6
- import { runPythonSnippet } from "./_python-helper.mjs";
4
+ import { resolvePaths } from "../../lib/paths.mjs";
5
+ import { fileExists, runProcess } from "../../lib/proc.mjs";
6
+ import { runPythonSnippet } from "../../lib/python-helper.mjs";
7
7
  import { runtimeIncludesClaudeAssets, validateInstallRuntime } from "./install.mjs";
8
- import { resolveRuntime } from "./runtime-resolver.mjs";
8
+ import { resolveRuntime } from "../../lib/runtime-resolver.mjs";
9
9
 
10
10
  const CLAUDE_SKILLS_DIR = join(homedir(), ".claude", "skills");
11
11
  const REQUIRED_SKILLS = ["okstra-setup", "okstra-run"];
@@ -2,12 +2,12 @@ import { promises as fs } from "node:fs";
2
2
  import { createHash } from "node:crypto";
3
3
  import { homedir } from "node:os";
4
4
  import { join, relative, resolve as resolveAbs } from "node:path";
5
- import { getPackageRoot } from "./version.mjs";
6
- import { resolvePaths } from "./paths.mjs";
7
- import { fileExists } from "./_proc.mjs";
8
- import { buildRuntimeManifest, RUNTIMES_MANIFEST_REL } from "./runtime-manifest.mjs";
9
- import { normalizeRuntimeRequest, resolveRuntime } from "./runtime-resolver.mjs";
10
- import { OBSOLETE_INTERNAL_SKILL_NAMES, USER_SKILL_NAMES } from "./skill-catalog.mjs";
5
+ import { getPackageRoot } from "../../lib/version.mjs";
6
+ import { resolvePaths } from "../../lib/paths.mjs";
7
+ import { fileExists } from "../../lib/proc.mjs";
8
+ import { buildRuntimeManifest, RUNTIMES_MANIFEST_REL } from "../../lib/runtime-manifest.mjs";
9
+ import { normalizeRuntimeRequest, resolveRuntime } from "../../lib/runtime-resolver.mjs";
10
+ import { OBSOLETE_INTERNAL_SKILL_NAMES, USER_SKILL_NAMES } from "../../lib/skill-catalog.mjs";
11
11
 
12
12
  const SKILLS_MANIFEST_REL = "installed-skills.json";
13
13
  const AGENTS_MANIFEST_REL = "installed-agents.json";
@@ -284,16 +284,21 @@ async function ensureSymlink(target, linkPath, opts) {
284
284
  return "linked";
285
285
  }
286
286
  await fs.mkdir(join(linkPath, ".."), { recursive: true });
287
+ // readlink returns EINVAL for both a missing link and an existing non-symlink
288
+ // file, so unlink alone would silently destroy a real file. lstat lets us
289
+ // refuse to clobber any existing entry that is not a symlink.
290
+ let existing;
287
291
  try {
288
- await fs.unlink(linkPath);
292
+ existing = await fs.lstat(linkPath);
289
293
  } catch (err) {
290
- if (err.code !== "ENOENT") {
291
- // Existing directory or regular file — refuse to clobber automatically.
292
- throw new Error(
293
- `refuse to replace ${linkPath} (not a symlink). Remove it manually and retry, or run 'okstra uninstall' first.`,
294
- );
295
- }
294
+ if (err.code !== "ENOENT") throw err;
295
+ }
296
+ if (existing && !existing.isSymbolicLink()) {
297
+ throw new Error(
298
+ `refuse to replace ${linkPath} (not a symlink). Remove it manually and retry, or run 'okstra uninstall' first.`,
299
+ );
296
300
  }
301
+ if (existing) await fs.unlink(linkPath);
297
302
  await fs.symlink(target, linkPath);
298
303
  return "linked";
299
304
  }
@@ -1,4 +1,4 @@
1
- import { runPythonModule } from "./_python-helper.mjs";
1
+ import { runPythonModule } from "../../lib/python-helper.mjs";
2
2
 
3
3
  const USAGE = `okstra migrate — move legacy .project-docs/okstra/ to .okstra/ (one-shot)
4
4