@ps-neko/nekowork 0.1.0-alpha.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.
Files changed (203) hide show
  1. package/AGENTS.md +112 -0
  2. package/CLAUDE.md +81 -0
  3. package/LICENSE +21 -0
  4. package/README.md +283 -0
  5. package/REVIEW.md +96 -0
  6. package/RULES.md +51 -0
  7. package/SOUL.md +21 -0
  8. package/WORKING-CONTEXT.md +52 -0
  9. package/agent.yaml +219 -0
  10. package/agents/architect.md +57 -0
  11. package/agents/code-reviewer.md +60 -0
  12. package/agents/codex-challenger.md +53 -0
  13. package/agents/codex-reviewer.md +56 -0
  14. package/agents/debugger.md +33 -0
  15. package/agents/doc-writer.md +51 -0
  16. package/agents/executor.md +41 -0
  17. package/agents/planner.md +49 -0
  18. package/agents/research.md +50 -0
  19. package/agents/security-reviewer.md +47 -0
  20. package/agents/test-engineer.md +41 -0
  21. package/bridge/mcp-server.js +301 -0
  22. package/commands/claude-led-codex-review.md +29 -0
  23. package/docs/ADVANCED.md +321 -0
  24. package/docs/AI-DEVELOPMENT-LIFECYCLE.md +105 -0
  25. package/docs/ARCHITECTURE.md +205 -0
  26. package/docs/AUDIT.md +114 -0
  27. package/docs/AUTH-MIGRATION.md +282 -0
  28. package/docs/CHANGELOG.md +97 -0
  29. package/docs/CLI-STAGES.md +89 -0
  30. package/docs/CODEMAPS/README.md +15 -0
  31. package/docs/CODEMAPS/agents.md +22 -0
  32. package/docs/CODEMAPS/bridge.md +18 -0
  33. package/docs/CODEMAPS/hooks.md +28 -0
  34. package/docs/CODEMAPS/manifests.md +14 -0
  35. package/docs/CODEMAPS/rules.md +22 -0
  36. package/docs/CODEMAPS/schemas.md +21 -0
  37. package/docs/CODEMAPS/scripts.md +158 -0
  38. package/docs/CODEMAPS/skills.md +29 -0
  39. package/docs/CODEMAPS/tests.md +98 -0
  40. package/docs/CORE-INVARIANTS.md +38 -0
  41. package/docs/DEMO.md +110 -0
  42. package/docs/EXAMPLE-PROJECT.md +92 -0
  43. package/docs/PORTING.md +154 -0
  44. package/docs/PRODUCT-PRINCIPLES.md +303 -0
  45. package/docs/PUBLISH-ALPHA.md +106 -0
  46. package/docs/QUICKSTART.md +344 -0
  47. package/docs/RELEASE-READINESS.md +140 -0
  48. package/docs/RISK-CLASSIFIER.md +50 -0
  49. package/docs/RUNBOOK.md +146 -0
  50. package/docs/SECURITY.md +79 -0
  51. package/docs/SETUP.md +142 -0
  52. package/docs/WHY-NEKOWORK.md +64 -0
  53. package/docs/case-studies/README.md +16 -0
  54. package/docs/case-studies/SINDRESORHUS-IS-PLAIN-OBJ.md +141 -0
  55. package/docs/dev-log/2026-04-29-p1-recovery.md +142 -0
  56. package/docs/dev-log/2026-04-29-week1-4.md +81 -0
  57. package/docs/examples/GITHUB-ACTIONS-HARDENING.md +86 -0
  58. package/docs/examples/QUALITY-LIFECYCLE-SMOKE.md +32 -0
  59. package/docs/examples/TRADING-DASHBOARD-MOCK.md +65 -0
  60. package/docs/workflows-stash/README.md +32 -0
  61. package/docs/workflows-stash/harness-review.yml +166 -0
  62. package/docs/workflows-stash/harness-validate.yml +48 -0
  63. package/examples/github-actions-hardening/.github/workflows/hardened-validate.yml +38 -0
  64. package/examples/github-actions-hardening/README.md +31 -0
  65. package/examples/github-actions-hardening/case-study/ASK.md +26 -0
  66. package/examples/github-actions-hardening/case-study/GATE_STATUS.md +28 -0
  67. package/examples/github-actions-hardening/case-study/PLAN.md +25 -0
  68. package/examples/github-actions-hardening/case-study/SHIP_READY.md +21 -0
  69. package/examples/github-actions-hardening/case-study/TASK.md +30 -0
  70. package/examples/github-actions-hardening/case-study/TEAM_HANDOFFS.md +37 -0
  71. package/examples/github-actions-hardening/case-study/VERIFY_SUMMARY.md +35 -0
  72. package/examples/github-actions-hardening/case-study/WORK_SUMMARY.md +24 -0
  73. package/examples/github-actions-hardening/package.json +12 -0
  74. package/examples/github-actions-hardening/scripts/check.mjs +43 -0
  75. package/examples/quality-lifecycle-smoke/README.md +30 -0
  76. package/examples/quality-lifecycle-smoke/case-study/ASK.md +24 -0
  77. package/examples/quality-lifecycle-smoke/case-study/GATE_STATUS.md +10 -0
  78. package/examples/quality-lifecycle-smoke/case-study/PLAN.md +19 -0
  79. package/examples/quality-lifecycle-smoke/case-study/SHIP_READY.md +11 -0
  80. package/examples/quality-lifecycle-smoke/case-study/TASK.md +19 -0
  81. package/examples/quality-lifecycle-smoke/case-study/TEAM_HANDOFFS.md +21 -0
  82. package/examples/quality-lifecycle-smoke/case-study/VERIFY_SUMMARY.md +44 -0
  83. package/examples/quality-lifecycle-smoke/case-study/WORK_SUMMARY.md +19 -0
  84. package/examples/quality-lifecycle-smoke/package.json +8 -0
  85. package/examples/quality-lifecycle-smoke/scripts/check.mjs +44 -0
  86. package/examples/trading-dashboard-mock/README.md +33 -0
  87. package/examples/trading-dashboard-mock/case-study/ASK.md +24 -0
  88. package/examples/trading-dashboard-mock/case-study/GATE_STATUS.md +28 -0
  89. package/examples/trading-dashboard-mock/case-study/PLAN.md +23 -0
  90. package/examples/trading-dashboard-mock/case-study/SHIP_READY.md +21 -0
  91. package/examples/trading-dashboard-mock/case-study/TASK.md +29 -0
  92. package/examples/trading-dashboard-mock/case-study/TEAM_HANDOFFS.md +49 -0
  93. package/examples/trading-dashboard-mock/case-study/VERIFY_SUMMARY.md +35 -0
  94. package/examples/trading-dashboard-mock/case-study/WORK_SUMMARY.md +27 -0
  95. package/examples/trading-dashboard-mock/fixtures/market.json +9 -0
  96. package/examples/trading-dashboard-mock/index.html +76 -0
  97. package/examples/trading-dashboard-mock/package.json +9 -0
  98. package/examples/trading-dashboard-mock/scripts/check.mjs +54 -0
  99. package/examples/trading-dashboard-mock/src/app.js +83 -0
  100. package/examples/trading-dashboard-mock/src/styles.css +227 -0
  101. package/hooks/hooks.json +44 -0
  102. package/hooks/scripts/config-protection.js +34 -0
  103. package/hooks/scripts/gateguard-fact-force.js +146 -0
  104. package/hooks/scripts/persistent-mode.mjs +27 -0
  105. package/hooks/scripts/pre-bash-dispatcher.js +63 -0
  106. package/hooks/scripts/quality-gate.js +106 -0
  107. package/manifests/install-components.json +195 -0
  108. package/manifests/install-modules.json +101 -0
  109. package/manifests/install-profiles.json +134 -0
  110. package/package.json +96 -0
  111. package/rules/common/coding-style.md +71 -0
  112. package/rules/common/security.md +69 -0
  113. package/rules/common/testing.md +58 -0
  114. package/rules/python/coding-style.md +80 -0
  115. package/rules/python/testing.md +86 -0
  116. package/rules/typescript/coding-style.md +97 -0
  117. package/rules/typescript/security.md +67 -0
  118. package/rules/typescript/testing.md +78 -0
  119. package/schemas/agent-yaml.schema.json +168 -0
  120. package/schemas/agent.schema.json +32 -0
  121. package/schemas/handoff.schema.json +105 -0
  122. package/schemas/hooks.schema.json +35 -0
  123. package/schemas/install-components.schema.json +46 -0
  124. package/schemas/install-modules.schema.json +39 -0
  125. package/schemas/install-profiles.schema.json +32 -0
  126. package/schemas/install-state.schema.json +42 -0
  127. package/schemas/routing.schema.json +42 -0
  128. package/schemas/skill.schema.json +19 -0
  129. package/scripts/agents/dispatch.js +144 -0
  130. package/scripts/agents/runners/claude.js +214 -0
  131. package/scripts/agents/runners/codex.js +233 -0
  132. package/scripts/agents/runners/gemini.js +92 -0
  133. package/scripts/agents/runners/mock.js +107 -0
  134. package/scripts/auth/github-import-gh.js +52 -0
  135. package/scripts/auth/github-login.js +79 -0
  136. package/scripts/auth/github-logout.js +21 -0
  137. package/scripts/auth/github-status.js +46 -0
  138. package/scripts/build-claude.js +101 -0
  139. package/scripts/build-codemaps.js +286 -0
  140. package/scripts/build-codex.js +93 -0
  141. package/scripts/build-cursor.js +132 -0
  142. package/scripts/build-gemini.js +117 -0
  143. package/scripts/build-opencode.js +117 -0
  144. package/scripts/ci/catalog.js +120 -0
  145. package/scripts/ci/check-markers.js +48 -0
  146. package/scripts/ci/security-hardening.js +270 -0
  147. package/scripts/ci/validate-agents.js +88 -0
  148. package/scripts/ci/validate-hooks.js +99 -0
  149. package/scripts/ci/validate-manifests.js +128 -0
  150. package/scripts/ci/validate-skills.js +93 -0
  151. package/scripts/cli.js +1134 -0
  152. package/scripts/core/auth-guard.js +22 -0
  153. package/scripts/core/build-roots.js +11 -0
  154. package/scripts/core/cli-resolver.js +64 -0
  155. package/scripts/core/execution-workspace.js +84 -0
  156. package/scripts/core/git-mutation-guard.js +79 -0
  157. package/scripts/core/install-state.js +125 -0
  158. package/scripts/core/json-extractor.js +32 -0
  159. package/scripts/core/subprocess.js +74 -0
  160. package/scripts/daemon/wait.js +278 -0
  161. package/scripts/demo-external-project.js +222 -0
  162. package/scripts/demo-quick-run.js +193 -0
  163. package/scripts/demo-review.js +204 -0
  164. package/scripts/doctor.js +296 -0
  165. package/scripts/install-apply.js +185 -0
  166. package/scripts/install-plan.js +411 -0
  167. package/scripts/lib/acceptance-criteria.js +105 -0
  168. package/scripts/lib/costs.js +82 -0
  169. package/scripts/lib/instincts.js +194 -0
  170. package/scripts/lib/keychain.js +85 -0
  171. package/scripts/lib/profile-policy.js +134 -0
  172. package/scripts/lib/profile-safety.js +81 -0
  173. package/scripts/lib/risk-classifier.js +145 -0
  174. package/scripts/lib/router.js +138 -0
  175. package/scripts/lib/severity.js +99 -0
  176. package/scripts/lib/token-vault.js +136 -0
  177. package/scripts/orchestrators/apply.js +225 -0
  178. package/scripts/orchestrators/ask.js +143 -0
  179. package/scripts/orchestrators/gate.js +179 -0
  180. package/scripts/orchestrators/ralph.js +179 -0
  181. package/scripts/orchestrators/review.js +452 -0
  182. package/scripts/orchestrators/run.js +151 -0
  183. package/scripts/orchestrators/ship.js +339 -0
  184. package/scripts/orchestrators/team-lite.js +270 -0
  185. package/scripts/orchestrators/team.js +244 -0
  186. package/scripts/orchestrators/verify.js +306 -0
  187. package/scripts/orchestrators/work.js +207 -0
  188. package/scripts/portability/simulate-port.js +220 -0
  189. package/scripts/repair.js +184 -0
  190. package/scripts/sync-claude-md.js +220 -0
  191. package/scripts/verify/claude-live.js +30 -0
  192. package/scripts/verify/codex-live.js +60 -0
  193. package/scripts/verify/gemini-live.js +48 -0
  194. package/scripts/verify/runtime.js +105 -0
  195. package/skills/claude-led-codex-review/SKILL.md +133 -0
  196. package/skills/plan-eng-review/SKILL.md +51 -0
  197. package/skills/porting/SKILL.md +69 -0
  198. package/skills/ralph/SKILL.md +48 -0
  199. package/skills/release-readiness/SKILL.md +62 -0
  200. package/skills/review/SKILL.md +42 -0
  201. package/skills/security-hardening/SKILL.md +59 -0
  202. package/skills/ship/SKILL.md +44 -0
  203. package/skills/tdd-workflow/SKILL.md +42 -0
package/scripts/cli.js ADDED
@@ -0,0 +1,1134 @@
1
+ #!/usr/bin/env node
2
+ // NEKOWORK/HARNESS CLI entrypoint.
3
+ // Public verbs: doctor, ask, plan, team, work, verify, gate, ship, apply, run, review, review-cycle, install, validate, version.
4
+ // Advanced verbs: self-review, codex-review, ralph, wait, sessions, costs, instincts.
5
+
6
+ import { spawnSync } from 'node:child_process';
7
+ import fs from 'node:fs';
8
+ import path from 'node:path';
9
+ import { fileURLToPath } from 'node:url';
10
+
11
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
12
+ const ROOT = path.resolve(__dirname, '..');
13
+
14
+ const verb = process.argv[2];
15
+ const rest = process.argv.slice(3);
16
+
17
+ function run(script, args) {
18
+ const r = spawnSync(process.execPath, [path.join(__dirname, script), ...args], { stdio: 'inherit' });
19
+ process.exit(r.status ?? 1);
20
+ }
21
+
22
+ function resolveProjectRoot(value) {
23
+ return path.resolve(value || process.env.HARNESS_PROJECT_ROOT || process.cwd());
24
+ }
25
+
26
+ function help() {
27
+ console.log(`
28
+ harness <verb> [args]
29
+
30
+ Install / verify
31
+ install --plan [--profile <name>] [--target <name>] [--module <id>] [--component <id>] [--project-root <dir>]
32
+ selective manifest dry-run
33
+ install --plan --list [--json] list profile/module/component/target catalog
34
+ install --apply [--profile <name>] [--project-root <dir>]
35
+ apply generated harness outputs and state
36
+ validate validate catalog and core profile
37
+ doctor [--project-root <dir>] [--quick] [--gemini-smoke] [--json]
38
+ local environment health check
39
+ version
40
+
41
+ Review loop
42
+ ask "<task>" [--profile quality|product|security] [--session <id>] [--project-root <dir>] [--json]
43
+ question gate only; no provider calls or project mutation
44
+ team "<task>" [--workers planner,research,product,security,test] [--no-write] [--session <id>] [--project-root <dir>] [--live] [--json]
45
+ read-only multi-worker handoffs; no project mutation
46
+ work "<task>" [--profile quality|security] [--single-executor] [--session <id>] [--project-root <dir>] [--live] [--json]
47
+ single executor implement handoff; live mode captures isolated diff
48
+ verify "<task>" --session <id> [--profile quality|security] [--strict-quality] [--secure] [--project-root <dir>] [--live] [--json]
49
+ Codex-only verification of a prior work handoff
50
+ gate status --session <id> [--project-root <dir>] [--json]
51
+ inspect HUMAN_GATE / approval / block state
52
+ gate approve --session <id> --reason <text> [--project-root <dir>] [--json]
53
+ record explicit human approval for an open gate
54
+ gate block --session <id> --reason <text> [--project-root <dir>] [--json]
55
+ record explicit human block for a session
56
+ ship "<task>" --session <id> [--require-clean-gates] [--project-root <dir>] [--live] [--json]
57
+ ship/no-ship readiness handoff; blocked by HUMAN_GATE
58
+ apply --session <id> [--project-root <dir>] [--allow-dirty] [--force] [--json]
59
+ apply a verified SHIP_READY live-work diff to the target project
60
+ run "<task>" [--session <id>] [--profile quality|security] [--strict-quality] [--secure] [--live] [--apply] [--allow-dirty] [--force] [--project-root <dir>] [--json]
61
+ decomposed wrapper: work -> verify -> ship, optional apply
62
+ review "<task>" [--secure] [--fast] [--no-ship] [--no-codex] [--live] [--session <id>] [--project-root <dir>]
63
+ legacy full claude-led-codex-review workflow
64
+ review-cycle "<task>" [--secure] [--fast] [--no-ship] [--no-codex] [--live] [--session <id>] [--project-root <dir>]
65
+ explicit compatibility alias for the legacy full workflow
66
+ plan "<task>" [--project-root <dir>] ideate + plan only
67
+ self-review reserved; use review for now
68
+ codex-review reserved; use review for now
69
+
70
+ Options:
71
+ --live use local CLI sessions. Claude uses claude auth, Codex uses codex login.
72
+ API keys are not needed for the default delegated CLI path.
73
+ --project-root <dir>
74
+ target project root for session/state/git work. Agents/schemas are read
75
+ from the HARNESS install root.
76
+ default mock provider; no API keys or provider CLIs required
77
+
78
+ Advanced
79
+ ralph "<task>" [--max-iter 5] [--engine review|run] [--secure] [--live] [--project-root <dir>]
80
+ repeat until PRD acceptance criteria pass
81
+ team-lite "<task>" [--live] [--session <id>] [--project-root <dir>]
82
+ OMC-style staged team pipeline
83
+ wait start start persistent daemon
84
+ wait stop stop persistent daemon
85
+ wait status daemon status
86
+
87
+ Sessions / cost / learning
88
+ sessions list sessions
89
+ costs --since=7d [--rows] [--json] summarize cost estimates
90
+ instincts list [--kind <k>] [--min-confidence <n>] [--json]
91
+ instincts show <id>
92
+ instincts ready [--max-stale-days N] [--min-diversity X] [--blocked]
93
+ list promotion candidates; human confirmation required
94
+ instincts promote <id> promote only at confidence 1.0
95
+ instincts prune [--older-days N] [--dry-run]
96
+
97
+ Other
98
+ validate, doctor, version, help
99
+ `);
100
+ }
101
+
102
+ async function dynamicReview(opts) {
103
+ const { reviewCycle } = await import('./orchestrators/review.js');
104
+ const result = await reviewCycle({
105
+ ...opts,
106
+ harnessRoot: ROOT,
107
+ projectRoot: resolveProjectRoot(opts.projectRoot),
108
+ });
109
+
110
+ console.log('');
111
+ console.log('=== result ===');
112
+ console.log(' session : ' + result.sessionId);
113
+ console.log(' mode : ' + (result.mode || 'legacy-full-review-cycle'));
114
+ console.log(' handoffs : ' + result.handoffs.length);
115
+ console.log(' human gate : ' + (result.humanGate ? `YES (${result.reason})` : 'no'));
116
+ console.log(' secure : ' + (result.secureActive ? 'active' : 'off'));
117
+ if (result.humanGate) process.exit(3);
118
+ }
119
+
120
+ function usageError(message) {
121
+ const err = new Error(message);
122
+ err.cliUsage = true;
123
+ return err;
124
+ }
125
+
126
+ function parseReviewArgs(argv) {
127
+ const opts = {
128
+ task: '',
129
+ live: false,
130
+ secure: false,
131
+ fast: false,
132
+ noShip: false,
133
+ noCodex: false,
134
+ sessionId: null,
135
+ projectRoot: null,
136
+ };
137
+ const unknown = [];
138
+
139
+ for (let i = 0; i < argv.length; i++) {
140
+ const a = argv[i];
141
+ if (a === '--live') opts.live = true;
142
+ else if (a === '--secure') opts.secure = true;
143
+ else if (a === '--fast') opts.fast = true;
144
+ else if (a === '--no-ship') opts.noShip = true;
145
+ else if (a === '--no-codex') opts.noCodex = true;
146
+ else if (a === '--session') {
147
+ const value = argv[++i];
148
+ if (!value || value.startsWith('--')) throw usageError('--session requires a value');
149
+ opts.sessionId = value;
150
+ } else if (a === '--project-root') {
151
+ const value = argv[++i];
152
+ if (!value || value.startsWith('--')) throw usageError('--project-root requires a value');
153
+ opts.projectRoot = value;
154
+ } else if (a.startsWith('--project-root=')) {
155
+ opts.projectRoot = a.slice('--project-root='.length);
156
+ } else if (a === '--max-iter') {
157
+ const value = argv[++i];
158
+ if (!value || value.startsWith('--')) throw usageError('--max-iter requires a value');
159
+ opts.maxIter = Number(value);
160
+ } else if (a.startsWith('--max-iter=')) {
161
+ opts.maxIter = Number(a.slice('--max-iter='.length));
162
+ } else if (a.startsWith('--')) {
163
+ unknown.push(a);
164
+ } else if (!opts.task) {
165
+ opts.task = a;
166
+ } else {
167
+ opts.task += ' ' + a;
168
+ }
169
+ }
170
+
171
+ if (unknown.length) throw usageError(`unknown flag: ${unknown.join(', ')}`);
172
+ if (opts.secure && opts.fast) throw usageError('--secure and --fast cannot be used together');
173
+ if (opts.noCodex && opts.secure) throw usageError('--no-codex and --secure cannot be used together');
174
+ if (opts.maxIter != null && (!Number.isFinite(opts.maxIter) || opts.maxIter < 1)) {
175
+ throw usageError('--max-iter must be a number >= 1');
176
+ }
177
+
178
+ return opts;
179
+ }
180
+
181
+ function parseRalphArgs(argv) {
182
+ const reviewArgv = [];
183
+ let engine = null;
184
+
185
+ for (let i = 0; i < argv.length; i++) {
186
+ const a = argv[i];
187
+ if (a === '--engine') {
188
+ const value = argv[++i];
189
+ if (!value || value.startsWith('--')) throw usageError('--engine requires a value');
190
+ engine = value;
191
+ } else if (a.startsWith('--engine=')) {
192
+ engine = a.slice('--engine='.length);
193
+ } else {
194
+ reviewArgv.push(a);
195
+ }
196
+ }
197
+
198
+ const opts = parseReviewArgs(reviewArgv);
199
+ if (engine != null) {
200
+ if (!['review', 'legacy-review', 'run'].includes(engine)) {
201
+ throw usageError('--engine must be review or run');
202
+ }
203
+ opts.engine = engine;
204
+ }
205
+ return opts;
206
+ }
207
+
208
+ function parseAskArgs(argv) {
209
+ const opts = {
210
+ task: '',
211
+ sessionId: null,
212
+ projectRoot: null,
213
+ profile: null,
214
+ json: false,
215
+ };
216
+ const unknown = [];
217
+
218
+ for (let i = 0; i < argv.length; i++) {
219
+ const a = argv[i];
220
+ if (a === '--json') opts.json = true;
221
+ else if (a === '--profile') {
222
+ const value = argv[++i];
223
+ if (!value || value.startsWith('--')) throw usageError('--profile requires a value');
224
+ opts.profile = value;
225
+ } else if (a.startsWith('--profile=')) {
226
+ opts.profile = a.slice('--profile='.length);
227
+ }
228
+ else if (a === '--session') {
229
+ const value = argv[++i];
230
+ if (!value || value.startsWith('--')) throw usageError('--session requires a value');
231
+ opts.sessionId = value;
232
+ } else if (a === '--project-root') {
233
+ const value = argv[++i];
234
+ if (!value || value.startsWith('--')) throw usageError('--project-root requires a value');
235
+ opts.projectRoot = value;
236
+ } else if (a.startsWith('--project-root=')) {
237
+ opts.projectRoot = a.slice('--project-root='.length);
238
+ } else if (a.startsWith('--session=')) {
239
+ opts.sessionId = a.slice('--session='.length);
240
+ } else if (a.startsWith('--')) {
241
+ unknown.push(a);
242
+ } else if (!opts.task) {
243
+ opts.task = a;
244
+ } else {
245
+ opts.task += ' ' + a;
246
+ }
247
+ }
248
+
249
+ if (unknown.length) throw usageError(`unknown flag: ${unknown.join(', ')}`);
250
+ return opts;
251
+ }
252
+
253
+ function parseTeamArgs(argv) {
254
+ const opts = {
255
+ task: '',
256
+ workers: null,
257
+ noWrite: false,
258
+ sessionId: null,
259
+ projectRoot: null,
260
+ live: false,
261
+ json: false,
262
+ };
263
+ const unknown = [];
264
+
265
+ for (let i = 0; i < argv.length; i++) {
266
+ const a = argv[i];
267
+ if (a === '--json') opts.json = true;
268
+ else if (a === '--live') opts.live = true;
269
+ else if (a === '--no-write') opts.noWrite = true;
270
+ else if (a === '--workers') {
271
+ const value = argv[++i];
272
+ if (!value || value.startsWith('--')) throw usageError('--workers requires a comma-separated value');
273
+ opts.workers = value;
274
+ } else if (a.startsWith('--workers=')) {
275
+ opts.workers = a.slice('--workers='.length);
276
+ } else if (a === '--session') {
277
+ const value = argv[++i];
278
+ if (!value || value.startsWith('--')) throw usageError('--session requires a value');
279
+ opts.sessionId = value;
280
+ } else if (a.startsWith('--session=')) {
281
+ opts.sessionId = a.slice('--session='.length);
282
+ } else if (a === '--project-root') {
283
+ const value = argv[++i];
284
+ if (!value || value.startsWith('--')) throw usageError('--project-root requires a value');
285
+ opts.projectRoot = value;
286
+ } else if (a.startsWith('--project-root=')) {
287
+ opts.projectRoot = a.slice('--project-root='.length);
288
+ } else if (a.startsWith('--')) {
289
+ unknown.push(a);
290
+ } else if (!opts.task) {
291
+ opts.task = a;
292
+ } else {
293
+ opts.task += ' ' + a;
294
+ }
295
+ }
296
+
297
+ if (unknown.length) throw usageError(`unknown flag: ${unknown.join(', ')}`);
298
+ return opts;
299
+ }
300
+
301
+ function parseWorkArgs(argv) {
302
+ const opts = {
303
+ task: '',
304
+ singleExecutor: false,
305
+ sessionId: null,
306
+ projectRoot: null,
307
+ profile: null,
308
+ live: false,
309
+ json: false,
310
+ };
311
+ const unknown = [];
312
+
313
+ for (let i = 0; i < argv.length; i++) {
314
+ const a = argv[i];
315
+ if (a === '--json') opts.json = true;
316
+ else if (a === '--live') opts.live = true;
317
+ else if (a === '--profile') {
318
+ const value = argv[++i];
319
+ if (!value || value.startsWith('--')) throw usageError('--profile requires a value');
320
+ opts.profile = value;
321
+ } else if (a.startsWith('--profile=')) {
322
+ opts.profile = a.slice('--profile='.length);
323
+ } else if (a === '--single-executor') opts.singleExecutor = true;
324
+ else if (a === '--session') {
325
+ const value = argv[++i];
326
+ if (!value || value.startsWith('--')) throw usageError('--session requires a value');
327
+ opts.sessionId = value;
328
+ } else if (a.startsWith('--session=')) {
329
+ opts.sessionId = a.slice('--session='.length);
330
+ } else if (a === '--project-root') {
331
+ const value = argv[++i];
332
+ if (!value || value.startsWith('--')) throw usageError('--project-root requires a value');
333
+ opts.projectRoot = value;
334
+ } else if (a.startsWith('--project-root=')) {
335
+ opts.projectRoot = a.slice('--project-root='.length);
336
+ } else if (a.startsWith('--')) {
337
+ unknown.push(a);
338
+ } else if (!opts.task) {
339
+ opts.task = a;
340
+ } else {
341
+ opts.task += ' ' + a;
342
+ }
343
+ }
344
+
345
+ if (unknown.length) throw usageError(`unknown flag: ${unknown.join(', ')}`);
346
+ return opts;
347
+ }
348
+
349
+ function parseVerifyArgs(argv) {
350
+ const opts = {
351
+ task: '',
352
+ requireCleanGates: false,
353
+ sessionId: null,
354
+ projectRoot: null,
355
+ profile: null,
356
+ live: false,
357
+ secure: false,
358
+ strictQuality: false,
359
+ json: false,
360
+ };
361
+ const unknown = [];
362
+
363
+ for (let i = 0; i < argv.length; i++) {
364
+ const a = argv[i];
365
+ if (a === '--json') opts.json = true;
366
+ else if (a === '--live') opts.live = true;
367
+ else if (a === '--secure') opts.secure = true;
368
+ else if (a === '--strict-quality') opts.strictQuality = true;
369
+ else if (a === '--profile') {
370
+ const value = argv[++i];
371
+ if (!value || value.startsWith('--')) throw usageError('--profile requires a value');
372
+ opts.profile = value;
373
+ } else if (a.startsWith('--profile=')) {
374
+ opts.profile = a.slice('--profile='.length);
375
+ } else if (a === '--session') {
376
+ const value = argv[++i];
377
+ if (!value || value.startsWith('--')) throw usageError('--session requires a value');
378
+ opts.sessionId = value;
379
+ } else if (a.startsWith('--session=')) {
380
+ opts.sessionId = a.slice('--session='.length);
381
+ } else if (a === '--project-root') {
382
+ const value = argv[++i];
383
+ if (!value || value.startsWith('--')) throw usageError('--project-root requires a value');
384
+ opts.projectRoot = value;
385
+ } else if (a.startsWith('--project-root=')) {
386
+ opts.projectRoot = a.slice('--project-root='.length);
387
+ } else if (a.startsWith('--')) {
388
+ unknown.push(a);
389
+ } else if (!opts.task) {
390
+ opts.task = a;
391
+ } else {
392
+ opts.task += ' ' + a;
393
+ }
394
+ }
395
+
396
+ if (unknown.length) throw usageError(`unknown flag: ${unknown.join(', ')}`);
397
+ return opts;
398
+ }
399
+
400
+ function parseShipArgs(argv) {
401
+ const opts = {
402
+ task: '',
403
+ sessionId: null,
404
+ projectRoot: null,
405
+ live: false,
406
+ json: false,
407
+ };
408
+ const unknown = [];
409
+
410
+ for (let i = 0; i < argv.length; i++) {
411
+ const a = argv[i];
412
+ if (a === '--json') opts.json = true;
413
+ else if (a === '--live') opts.live = true;
414
+ else if (a === '--require-clean-gates') opts.requireCleanGates = true;
415
+ else if (a === '--session') {
416
+ const value = argv[++i];
417
+ if (!value || value.startsWith('--')) throw usageError('--session requires a value');
418
+ opts.sessionId = value;
419
+ } else if (a.startsWith('--session=')) {
420
+ opts.sessionId = a.slice('--session='.length);
421
+ } else if (a === '--project-root') {
422
+ const value = argv[++i];
423
+ if (!value || value.startsWith('--')) throw usageError('--project-root requires a value');
424
+ opts.projectRoot = value;
425
+ } else if (a.startsWith('--project-root=')) {
426
+ opts.projectRoot = a.slice('--project-root='.length);
427
+ } else if (a.startsWith('--')) {
428
+ unknown.push(a);
429
+ } else if (!opts.task) {
430
+ opts.task = a;
431
+ } else {
432
+ opts.task += ' ' + a;
433
+ }
434
+ }
435
+
436
+ if (unknown.length) throw usageError(`unknown flag: ${unknown.join(', ')}`);
437
+ return opts;
438
+ }
439
+
440
+ function parseGateArgs(argv) {
441
+ const opts = {
442
+ action: argv[0] && !argv[0].startsWith('--') ? argv[0] : 'status',
443
+ sessionId: null,
444
+ projectRoot: null,
445
+ reason: '',
446
+ json: false,
447
+ };
448
+ const unknown = [];
449
+ const start = opts.action === argv[0] ? 1 : 0;
450
+
451
+ for (let i = start; i < argv.length; i++) {
452
+ const a = argv[i];
453
+ if (a === '--json') opts.json = true;
454
+ else if (a === '--session') {
455
+ const value = argv[++i];
456
+ if (!value || value.startsWith('--')) throw usageError('--session requires a value');
457
+ opts.sessionId = value;
458
+ } else if (a.startsWith('--session=')) {
459
+ opts.sessionId = a.slice('--session='.length);
460
+ } else if (a === '--project-root') {
461
+ const value = argv[++i];
462
+ if (!value || value.startsWith('--')) throw usageError('--project-root requires a value');
463
+ opts.projectRoot = value;
464
+ } else if (a.startsWith('--project-root=')) {
465
+ opts.projectRoot = a.slice('--project-root='.length);
466
+ } else if (a === '--reason') {
467
+ const value = argv[++i];
468
+ if (!value || value.startsWith('--')) throw usageError('--reason requires a value');
469
+ opts.reason = value;
470
+ } else if (a.startsWith('--reason=')) {
471
+ opts.reason = a.slice('--reason='.length);
472
+ } else if (a.startsWith('--')) {
473
+ unknown.push(a);
474
+ } else if (opts.action === 'approve' || opts.action === 'block') {
475
+ opts.reason = opts.reason ? `${opts.reason} ${a}` : a;
476
+ } else {
477
+ unknown.push(a);
478
+ }
479
+ }
480
+
481
+ if (!['status', 'approve', 'block'].includes(opts.action)) {
482
+ throw usageError(`unknown gate action: ${opts.action}`);
483
+ }
484
+ if (unknown.length) throw usageError(`unknown flag: ${unknown.join(', ')}`);
485
+ if ((opts.action === 'approve' || opts.action === 'block') && !opts.reason) {
486
+ throw usageError(`gate ${opts.action} requires --reason <text>`);
487
+ }
488
+ return opts;
489
+ }
490
+
491
+ function parseApplyArgs(argv) {
492
+ const opts = {
493
+ sessionId: null,
494
+ projectRoot: null,
495
+ allowDirty: false,
496
+ force: false,
497
+ json: false,
498
+ };
499
+ const unknown = [];
500
+
501
+ for (let i = 0; i < argv.length; i++) {
502
+ const a = argv[i];
503
+ if (a === '--json') opts.json = true;
504
+ else if (a === '--allow-dirty') opts.allowDirty = true;
505
+ else if (a === '--force') opts.force = true;
506
+ else if (a === '--session') {
507
+ const value = argv[++i];
508
+ if (!value || value.startsWith('--')) throw usageError('--session requires a value');
509
+ opts.sessionId = value;
510
+ } else if (a.startsWith('--session=')) {
511
+ opts.sessionId = a.slice('--session='.length);
512
+ } else if (a === '--project-root') {
513
+ const value = argv[++i];
514
+ if (!value || value.startsWith('--')) throw usageError('--project-root requires a value');
515
+ opts.projectRoot = value;
516
+ } else if (a.startsWith('--project-root=')) {
517
+ opts.projectRoot = a.slice('--project-root='.length);
518
+ } else if (a.startsWith('--')) {
519
+ unknown.push(a);
520
+ } else {
521
+ unknown.push(a);
522
+ }
523
+ }
524
+
525
+ if (unknown.length) throw usageError(`unknown flag: ${unknown.join(', ')}`);
526
+ return opts;
527
+ }
528
+
529
+ function parseRunArgs(argv) {
530
+ const opts = {
531
+ task: '',
532
+ sessionId: null,
533
+ projectRoot: null,
534
+ profile: null,
535
+ live: false,
536
+ secure: false,
537
+ strictQuality: false,
538
+ apply: false,
539
+ allowDirty: false,
540
+ force: false,
541
+ json: false,
542
+ };
543
+ const unknown = [];
544
+
545
+ for (let i = 0; i < argv.length; i++) {
546
+ const a = argv[i];
547
+ if (a === '--json') opts.json = true;
548
+ else if (a === '--live') opts.live = true;
549
+ else if (a === '--secure') opts.secure = true;
550
+ else if (a === '--strict-quality') opts.strictQuality = true;
551
+ else if (a === '--profile') {
552
+ const value = argv[++i];
553
+ if (!value || value.startsWith('--')) throw usageError('--profile requires a value');
554
+ opts.profile = value;
555
+ } else if (a.startsWith('--profile=')) {
556
+ opts.profile = a.slice('--profile='.length);
557
+ } else if (a === '--apply') opts.apply = true;
558
+ else if (a === '--allow-dirty') opts.allowDirty = true;
559
+ else if (a === '--force') opts.force = true;
560
+ else if (a === '--session') {
561
+ const value = argv[++i];
562
+ if (!value || value.startsWith('--')) throw usageError('--session requires a value');
563
+ opts.sessionId = value;
564
+ } else if (a.startsWith('--session=')) {
565
+ opts.sessionId = a.slice('--session='.length);
566
+ } else if (a === '--project-root') {
567
+ const value = argv[++i];
568
+ if (!value || value.startsWith('--')) throw usageError('--project-root requires a value');
569
+ opts.projectRoot = value;
570
+ } else if (a.startsWith('--project-root=')) {
571
+ opts.projectRoot = a.slice('--project-root='.length);
572
+ } else if (a.startsWith('--')) {
573
+ unknown.push(a);
574
+ } else if (!opts.task) {
575
+ opts.task = a;
576
+ } else {
577
+ opts.task += ' ' + a;
578
+ }
579
+ }
580
+
581
+ if (unknown.length) throw usageError(`unknown flag: ${unknown.join(', ')}`);
582
+ return opts;
583
+ }
584
+
585
+ function optionValue(argv, flag, fallback = undefined) {
586
+ const i = argv.indexOf(flag);
587
+ if (i >= 0 && argv[i + 1] && !argv[i + 1].startsWith('--')) return argv[i + 1];
588
+ for (const a of argv) {
589
+ if (a.startsWith(`${flag}=`)) return a.slice(flag.length + 1);
590
+ }
591
+ return fallback;
592
+ }
593
+
594
+ function optionNumber(argv, flag, fallback = undefined) {
595
+ const value = optionValue(argv, flag, undefined);
596
+ return value == null ? fallback : Number(value);
597
+ }
598
+
599
+ (async () => {
600
+ switch (verb) {
601
+ case 'install': {
602
+ const mode = rest.includes('--apply') ? 'apply' : 'plan';
603
+ const filtered = rest.filter(a => a !== '--apply' && a !== '--plan');
604
+ run(`install-${mode}.js`, filtered);
605
+ break;
606
+ }
607
+
608
+ case 'validate':
609
+ run('install-plan.js', ['--profile', 'core', '--verbose']);
610
+ break;
611
+
612
+ case 'doctor':
613
+ run('doctor.js', rest);
614
+ break;
615
+
616
+ case 'ask': {
617
+ const opts = parseAskArgs(rest);
618
+ if (!opts.task) {
619
+ console.error('task is required. Example: harness ask "trading dashboard mockup"');
620
+ process.exit(2);
621
+ }
622
+ const { askGate } = await import('./orchestrators/ask.js');
623
+ const result = await askGate({
624
+ ...opts,
625
+ harnessRoot: ROOT,
626
+ projectRoot: resolveProjectRoot(opts.projectRoot),
627
+ });
628
+ if (opts.json) {
629
+ console.log(JSON.stringify(result.handoff, null, 2));
630
+ } else {
631
+ console.log('=== ask ===');
632
+ console.log(' session : ' + result.sessionId);
633
+ console.log(' risk : ' + result.handoff.risk_level);
634
+ console.log(' human gate : ' + (result.handoff.requires_human_gate ? 'required-if-continuing' : 'no'));
635
+ console.log(' questions : ' + result.handoff.questions.length);
636
+ console.log(' handoff : ' + path.relative(process.cwd(), path.join(result.sessionDir, 'handoffs', '00-question-gate.md')).replace(/\\/g, '/'));
637
+ }
638
+ break;
639
+ }
640
+
641
+ case 'team': {
642
+ const opts = parseTeamArgs(rest);
643
+ if (!opts.task) {
644
+ console.error('task is required. Example: harness team "trading dashboard mockup"');
645
+ process.exit(2);
646
+ }
647
+ const { teamCycle } = await import('./orchestrators/team.js');
648
+ const result = await teamCycle({
649
+ ...opts,
650
+ harnessRoot: ROOT,
651
+ projectRoot: resolveProjectRoot(opts.projectRoot),
652
+ });
653
+ if (opts.json) {
654
+ console.log(JSON.stringify({
655
+ sessionId: result.sessionId,
656
+ workers: result.workers,
657
+ tasks: result.tasks,
658
+ handoffs: result.handoffs.length,
659
+ recommendedNextStep: result.recommendedNextStep,
660
+ }, null, 2));
661
+ } else {
662
+ console.log('=== team ===');
663
+ console.log(' session : ' + result.sessionId);
664
+ console.log(' mode : read-only');
665
+ console.log(' workers : ' + result.workers.join(', '));
666
+ console.log(' handoffs : ' + result.handoffs.length);
667
+ console.log(' next step : ' + result.recommendedNextStep);
668
+ console.log(' summary : ' + path.relative(process.cwd(), path.join(result.sessionDir, 'team-summary.json')).replace(/\\/g, '/'));
669
+ }
670
+ break;
671
+ }
672
+
673
+ case 'work': {
674
+ const opts = parseWorkArgs(rest);
675
+ if (!opts.task) {
676
+ console.error('task is required. Example: harness work "implement trading dashboard mockup"');
677
+ process.exit(2);
678
+ }
679
+ const { workCycle } = await import('./orchestrators/work.js');
680
+ const result = await workCycle({
681
+ ...opts,
682
+ harnessRoot: ROOT,
683
+ projectRoot: resolveProjectRoot(opts.projectRoot),
684
+ });
685
+ if (opts.json) {
686
+ console.log(JSON.stringify({
687
+ sessionId: result.sessionId,
688
+ stage: result.handoff.stage,
689
+ agent: result.handoff.agent,
690
+ round: result.round,
691
+ files: result.files,
692
+ diffPath: result.diffPath,
693
+ live: result.live,
694
+ }, null, 2));
695
+ } else {
696
+ console.log('=== work ===');
697
+ console.log(' session : ' + result.sessionId);
698
+ console.log(' executor : ' + result.handoff.agent);
699
+ console.log(' round : ' + result.round);
700
+ console.log(' files : ' + result.files.length);
701
+ console.log(' diff : ' + (result.diffPath || '(none)'));
702
+ console.log(' codex : not run');
703
+ console.log(' ship : not run');
704
+ }
705
+ break;
706
+ }
707
+
708
+ case 'verify': {
709
+ const opts = parseVerifyArgs(rest);
710
+ if (!opts.task) {
711
+ console.error('task is required. Example: harness verify "verify implemented dashboard" --session work-123');
712
+ process.exit(2);
713
+ }
714
+ if (!opts.sessionId) {
715
+ console.error('--session is required for verify so NEKOWORK can read the prior work handoff');
716
+ process.exit(2);
717
+ }
718
+ const { verifyCycle } = await import('./orchestrators/verify.js');
719
+ let result;
720
+ try {
721
+ result = await verifyCycle({
722
+ ...opts,
723
+ harnessRoot: ROOT,
724
+ projectRoot: resolveProjectRoot(opts.projectRoot),
725
+ });
726
+ } catch (e) {
727
+ if (/^verify requires/.test(e?.message || '')) throw usageError(e.message);
728
+ throw e;
729
+ }
730
+ if (opts.json) {
731
+ console.log(JSON.stringify({
732
+ sessionId: result.sessionId,
733
+ verdict: result.verdict,
734
+ secureActive: result.secureActive,
735
+ humanGate: result.humanGate,
736
+ reason: result.reason,
737
+ codexChallenge: Boolean(result.codexChallenge),
738
+ strictQuality: result.strictQuality,
739
+ strictQualityBlocked: result.strictQualityBlocked,
740
+ qualityWarnings: result.qualityWarnings || [],
741
+ }, null, 2));
742
+ } else {
743
+ console.log('=== verify ===');
744
+ console.log(' session : ' + result.sessionId);
745
+ console.log(' verdict : ' + result.verdict);
746
+ console.log(' secure : ' + (result.secureActive ? 'active' : 'off'));
747
+ console.log(' challenge : ' + (result.codexChallenge ? 'yes' : 'no'));
748
+ console.log(' strict : ' + (result.strictQuality ? (result.strictQualityBlocked ? 'blocked' : 'passed') : 'off'));
749
+ console.log(' warnings : ' + (result.qualityWarnings?.length || 0));
750
+ console.log(' human gate : ' + (result.humanGate ? `YES (${result.reason})` : 'no'));
751
+ console.log(' ship : not run');
752
+ }
753
+ if (result.humanGate) process.exit(3);
754
+ break;
755
+ }
756
+
757
+ case 'ship': {
758
+ const opts = parseShipArgs(rest);
759
+ if (!opts.sessionId) {
760
+ console.error('--session is required for ship so NEKOWORK can read prior work and verify handoffs');
761
+ process.exit(2);
762
+ }
763
+ const { shipCycle } = await import('./orchestrators/ship.js');
764
+ let result;
765
+ try {
766
+ result = await shipCycle({
767
+ ...opts,
768
+ harnessRoot: ROOT,
769
+ projectRoot: resolveProjectRoot(opts.projectRoot),
770
+ });
771
+ } catch (e) {
772
+ if (/^ship requires/.test(e?.message || '')) throw usageError(e.message);
773
+ throw e;
774
+ }
775
+ if (opts.json) {
776
+ console.log(JSON.stringify({
777
+ sessionId: result.sessionId,
778
+ shipReady: result.shipReady,
779
+ noShip: result.noShip,
780
+ humanGate: result.humanGate,
781
+ verdict: result.verdict,
782
+ reason: result.reason,
783
+ shipHandoff: Boolean(result.shipHandoff),
784
+ }, null, 2));
785
+ } else {
786
+ console.log('=== ship ===');
787
+ console.log(' session : ' + result.sessionId);
788
+ console.log(' status : ' + (result.shipReady ? 'ready' : 'no-ship'));
789
+ console.log(' verdict : ' + result.verdict);
790
+ console.log(' handoff : ' + (result.shipHandoff ? 'written' : 'not written'));
791
+ console.log(' human gate : ' + (result.humanGate ? `YES (${result.reason})` : 'no'));
792
+ console.log(' mutation : target project not mutated');
793
+ }
794
+ if (result.humanGate) process.exit(3);
795
+ break;
796
+ }
797
+
798
+ case 'gate': {
799
+ const opts = parseGateArgs(rest);
800
+ if (!opts.sessionId) {
801
+ console.error('--session is required for gate');
802
+ process.exit(2);
803
+ }
804
+ const { gateCommand } = await import('./orchestrators/gate.js');
805
+ let result;
806
+ try {
807
+ result = gateCommand({
808
+ ...opts,
809
+ projectRoot: resolveProjectRoot(opts.projectRoot),
810
+ });
811
+ } catch (e) {
812
+ if (/^gate /.test(e?.message || '') || /^unknown gate action/.test(e?.message || '')) throw usageError(e.message);
813
+ throw e;
814
+ }
815
+ if (opts.json) {
816
+ console.log(JSON.stringify({
817
+ sessionId: result.sessionId,
818
+ status: result.status,
819
+ humanGate: result.humanGate,
820
+ approved: result.approved,
821
+ blocked: result.blocked,
822
+ reason: result.reason,
823
+ humanGateReason: result.humanGateReason,
824
+ approvalReason: result.approvalReason,
825
+ blockReason: result.blockReason,
826
+ }, null, 2));
827
+ } else {
828
+ console.log('=== gate ===');
829
+ console.log(' session : ' + result.sessionId);
830
+ console.log(' status : ' + result.status);
831
+ console.log(' human gate : ' + (result.humanGate ? `YES (${result.humanGateReason || result.reason})` : 'no'));
832
+ console.log(' approved : ' + (result.approved ? `yes (${result.approvalReason || result.reason})` : 'no'));
833
+ console.log(' blocked : ' + (result.blocked ? `YES (${result.blockReason || result.reason})` : 'no'));
834
+ }
835
+ if (result.blocked || result.status === 'open') process.exit(3);
836
+ break;
837
+ }
838
+
839
+ case 'apply': {
840
+ const opts = parseApplyArgs(rest);
841
+ if (!opts.sessionId) {
842
+ console.error('--session is required for apply');
843
+ process.exit(2);
844
+ }
845
+ const { applyCycle } = await import('./orchestrators/apply.js');
846
+ let result;
847
+ try {
848
+ result = applyCycle({
849
+ ...opts,
850
+ projectRoot: resolveProjectRoot(opts.projectRoot),
851
+ });
852
+ } catch (e) {
853
+ if (/^(apply requires|git apply failed)/.test(e?.message || '')) throw usageError(e.message);
854
+ throw e;
855
+ }
856
+ if (opts.json) {
857
+ console.log(JSON.stringify({
858
+ sessionId: result.sessionId,
859
+ applied: result.applied,
860
+ alreadyApplied: result.alreadyApplied,
861
+ humanGate: result.humanGate,
862
+ noShip: result.noShip,
863
+ reason: result.reason,
864
+ diffPath: result.diffPath,
865
+ files: result.files,
866
+ }, null, 2));
867
+ } else {
868
+ console.log('=== apply ===');
869
+ console.log(' session : ' + result.sessionId);
870
+ console.log(' applied : ' + (result.applied ? 'yes' : 'no'));
871
+ console.log(' already : ' + (result.alreadyApplied ? 'yes' : 'no'));
872
+ console.log(' human gate : ' + (result.humanGate ? 'YES' : 'no'));
873
+ console.log(' no ship : ' + (result.noShip ? 'YES' : 'no'));
874
+ console.log(' diff : ' + (result.diffPath || '(none)'));
875
+ if (result.reason) console.log(' reason : ' + result.reason);
876
+ }
877
+ if (result.humanGate || result.noShip) process.exit(3);
878
+ break;
879
+ }
880
+
881
+ case 'run': {
882
+ const opts = parseRunArgs(rest);
883
+ if (!opts.task) {
884
+ console.error('task is required. Example: harness run "implement and verify dashboard"');
885
+ process.exit(2);
886
+ }
887
+ const { runCycle } = await import('./orchestrators/run.js');
888
+ let result;
889
+ try {
890
+ result = await runCycle({
891
+ ...opts,
892
+ harnessRoot: ROOT,
893
+ projectRoot: resolveProjectRoot(opts.projectRoot),
894
+ });
895
+ } catch (e) {
896
+ if (/^(run requires|verify requires|ship requires|apply requires|git apply failed)/.test(e?.message || '')) throw usageError(e.message);
897
+ throw e;
898
+ }
899
+ if (opts.json) {
900
+ console.log(JSON.stringify({
901
+ sessionId: result.sessionId,
902
+ stoppedAt: result.stoppedAt,
903
+ verdict: result.verdict,
904
+ humanGate: result.humanGate,
905
+ noShip: result.noShip,
906
+ shipReady: result.shipReady,
907
+ applyRequested: result.applyRequested,
908
+ applySkippedReason: result.applySkippedReason,
909
+ applied: result.applied,
910
+ strictQuality: result.verify?.strictQuality,
911
+ strictQualityBlocked: result.verify?.strictQualityBlocked,
912
+ }, null, 2));
913
+ } else {
914
+ console.log('=== run ===');
915
+ console.log(' session : ' + result.sessionId);
916
+ console.log(' stopped at : ' + result.stoppedAt);
917
+ console.log(' verdict : ' + result.verdict);
918
+ console.log(' human gate : ' + (result.humanGate ? 'YES' : 'no'));
919
+ console.log(' no ship : ' + (result.noShip ? 'YES' : 'no'));
920
+ console.log(' ship ready : ' + (result.shipReady ? 'yes' : 'no'));
921
+ console.log(' strict : ' + (result.verify?.strictQuality ? (result.verify?.strictQualityBlocked ? 'blocked' : 'passed') : 'off'));
922
+ console.log(' apply : ' + (result.applied ? 'applied' : result.applyRequested ? `skipped (${result.applySkippedReason || 'not needed'})` : 'not requested'));
923
+ }
924
+ if (result.humanGate || (opts.apply && (result.noShip || result.applySkippedReason))) process.exit(3);
925
+ break;
926
+ }
927
+
928
+ case 'review':
929
+ case 'review-cycle': {
930
+ const opts = parseReviewArgs(rest);
931
+ if (!opts.task) {
932
+ console.error(`task is required. Example: harness ${verb} "add JWT validation"`);
933
+ process.exit(2);
934
+ }
935
+ await dynamicReview(opts);
936
+ break;
937
+ }
938
+
939
+ case 'ralph': {
940
+ const opts = parseRalphArgs(rest);
941
+ if (!opts.task) {
942
+ console.error('task is required. Example: harness ralph "feature X" --max-iter 5');
943
+ process.exit(2);
944
+ }
945
+ const { ralphLoop } = await import('./orchestrators/ralph.js');
946
+ const r = await ralphLoop({
947
+ ...opts,
948
+ harnessRoot: ROOT,
949
+ projectRoot: resolveProjectRoot(opts.projectRoot),
950
+ });
951
+ console.log('=== ralph done ===');
952
+ console.log(JSON.stringify(r, null, 2));
953
+ if (r.reason === 'human_gate') process.exit(3);
954
+ break;
955
+ }
956
+
957
+ case 'team-lite': {
958
+ const opts = parseReviewArgs(rest);
959
+ if (!opts.task) {
960
+ console.error('task is required. Example: harness team-lite "refactor auth guard"');
961
+ process.exit(2);
962
+ }
963
+ const { teamLiteCycle } = await import('./orchestrators/team-lite.js');
964
+ const r = await teamLiteCycle({
965
+ ...opts,
966
+ harnessRoot: ROOT,
967
+ projectRoot: resolveProjectRoot(opts.projectRoot),
968
+ });
969
+ console.log('=== team-lite done ===');
970
+ console.log(' session : ' + r.sessionId);
971
+ console.log(' tasks : ' + r.tasks.map(t => `${t.id}:${t.status}`).join(', '));
972
+ console.log(' handoffs : ' + r.handoffs.length);
973
+ console.log(' verdict : ' + r.verdict);
974
+ break;
975
+ }
976
+
977
+ case 'wait':
978
+ run('daemon/wait.js', rest.length ? rest : ['status']);
979
+ break;
980
+
981
+ case 'plan': {
982
+ const opts = parseReviewArgs(rest);
983
+ opts.fast = false;
984
+ opts.noShip = true;
985
+ const { reviewCycle } = await import('./orchestrators/review.js');
986
+ const result = await reviewCycle({
987
+ ...opts,
988
+ harnessRoot: ROOT,
989
+ projectRoot: resolveProjectRoot(opts.projectRoot),
990
+ stopAfter: 'plan',
991
+ });
992
+ console.log('handoffs:', result.handoffs.map(h => h.stage).join(' -> '));
993
+ break;
994
+ }
995
+
996
+ case 'self-review':
997
+ case 'codex-review':
998
+ console.error(`${verb} is reserved. Use the review workflow for now.`);
999
+ process.exit(2);
1000
+
1001
+ case 'instincts': {
1002
+ const sub = rest[0] || 'list';
1003
+ const { list: iList, get: iGet, promote: iPromote, prune: iPrune } = await import('./lib/instincts.js');
1004
+
1005
+ if (sub === 'list') {
1006
+ const minConfidence = optionNumber(rest, '--min-confidence', 0);
1007
+ const kind = optionValue(rest, '--kind', undefined);
1008
+ const rows = iList({ kind, minConfidence });
1009
+
1010
+ if (rest.includes('--json')) {
1011
+ console.log(JSON.stringify(rows, null, 2));
1012
+ } else {
1013
+ console.log(`total=${rows.length} (kind=${kind || 'any'}, min-confidence=${minConfidence})`);
1014
+ for (const r of rows) {
1015
+ const mark = r.promoted ? '[PROMOTED]' : (r.confidence >= 1 ? '[READY]' : '');
1016
+ console.log(` ${r.id} ${r.kind.padEnd(15)} count=${String(r.count).padStart(3)} conf=${r.confidence.toFixed(2)} ${mark} ${r.key}`);
1017
+ }
1018
+ }
1019
+ } else if (sub === 'show') {
1020
+ const id = rest[1];
1021
+ if (!id) {
1022
+ console.error('id is required');
1023
+ process.exit(2);
1024
+ }
1025
+ const inst = iGet(id);
1026
+ if (!inst) {
1027
+ console.error('not found');
1028
+ process.exit(1);
1029
+ }
1030
+ console.log(JSON.stringify(inst, null, 2));
1031
+ } else if (sub === 'ready') {
1032
+ const { ready: iReady } = await import('./lib/instincts.js');
1033
+ const maxStaleDays = optionNumber(rest, '--max-stale-days', 14);
1034
+ const minDiversity = optionNumber(rest, '--min-diversity', 0.5);
1035
+ const r = iReady({ maxStaleDays, minDiversity });
1036
+
1037
+ if (rest.includes('--json')) {
1038
+ console.log(JSON.stringify(r, null, 2));
1039
+ } else {
1040
+ console.log(`promotion candidates=${r.ready.length} (max-stale-days=${maxStaleDays}, min-diversity=${minDiversity})`);
1041
+ for (const x of r.ready) {
1042
+ console.log(` ${x.id} ${x.kind.padEnd(15)} count=${x.count} div=${x.diversity} ${x.key}`);
1043
+ }
1044
+ if (rest.includes('--blocked')) {
1045
+ console.log(`\nblocked=${r.blocked.length}`);
1046
+ for (const x of r.blocked) console.log(` ${x.id} ${x.reason} ${x.key}`);
1047
+ }
1048
+ console.log('\nPromotion requires explicit command: harness instincts promote <id>');
1049
+ }
1050
+ } else if (sub === 'promote') {
1051
+ const id = rest[1];
1052
+ if (!id) {
1053
+ console.error('id is required');
1054
+ process.exit(2);
1055
+ }
1056
+ const r = iPromote(id);
1057
+ console.log(`promoted: ${r.id} (${r.key})`);
1058
+ } else if (sub === 'prune') {
1059
+ const dryRun = rest.includes('--dry-run');
1060
+ const olderDays = optionNumber(rest, '--older-days', undefined);
1061
+ const r = iPrune({ olderDays, dryRun });
1062
+ console.log(`removed=${r.removed.length}, kept=${r.kept}, dry_run=${r.dry_run}`);
1063
+ if (rest.includes('--rows')) {
1064
+ for (const x of r.removed) console.log(` - ${x.id} ${x.kind} ${x.key}`);
1065
+ }
1066
+ } else {
1067
+ console.error(`unknown subverb: ${sub}. list | show <id> | ready | promote <id> | prune`);
1068
+ process.exit(2);
1069
+ }
1070
+ break;
1071
+ }
1072
+
1073
+ case 'costs': {
1074
+ const since = optionValue(rest, '--since', '7d');
1075
+ const { list, summarize } = await import('./lib/costs.js');
1076
+ const rows = list({ since });
1077
+ const sum = summarize(rows);
1078
+ console.log(`since=${since}, rows=${sum.rows}, total=$${sum.total_usd}`);
1079
+ console.log('by_provider:', JSON.stringify(sum.by_provider));
1080
+ console.log('by_model :', JSON.stringify(sum.by_model));
1081
+ if (rest.includes('--json')) {
1082
+ console.log(JSON.stringify({ since, summary: sum, rows }, null, 2));
1083
+ } else if (rest.includes('--rows')) {
1084
+ for (const r of rows.slice(-20)) console.log(' ' + JSON.stringify(r));
1085
+ }
1086
+ break;
1087
+ }
1088
+
1089
+ case 'sessions': {
1090
+ const sessionsProjectRoot = optionValue(rest, '--project-root', null);
1091
+ const dir = path.join(resolveProjectRoot(sessionsProjectRoot), '.harness', 'state', 'sessions');
1092
+ if (!fs.existsSync(dir)) {
1093
+ console.log('(no sessions)');
1094
+ break;
1095
+ }
1096
+ for (const s of fs.readdirSync(dir)) {
1097
+ const sd = path.join(dir, s);
1098
+ const handoffs = fs.existsSync(path.join(sd, 'handoffs'))
1099
+ ? fs.readdirSync(path.join(sd, 'handoffs')).filter(f => f.endsWith('.md')).length
1100
+ : 0;
1101
+ const gate = fs.existsSync(path.join(sd, 'HUMAN_GATE')) ? ' [HUMAN_GATE]' : '';
1102
+ console.log(` ${s} handoffs=${handoffs}${gate}`);
1103
+ }
1104
+ break;
1105
+ }
1106
+
1107
+ case 'version':
1108
+ case '--version':
1109
+ case '-v': {
1110
+ const pkg = JSON.parse(fs.readFileSync(path.join(ROOT, 'package.json'), 'utf8'));
1111
+ console.log(`harness ${pkg.version}`);
1112
+ break;
1113
+ }
1114
+
1115
+ case undefined:
1116
+ case 'help':
1117
+ case '--help':
1118
+ case '-h':
1119
+ help();
1120
+ break;
1121
+
1122
+ default:
1123
+ console.error(`unknown verb: ${verb}`);
1124
+ help();
1125
+ process.exit(2);
1126
+ }
1127
+ })().catch((e) => {
1128
+ if (e?.cliUsage) {
1129
+ console.error(e.message);
1130
+ process.exit(2);
1131
+ }
1132
+ console.error('UNEXPECTED:', e?.stack || e);
1133
+ process.exit(1);
1134
+ });