@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.
- package/AGENTS.md +112 -0
- package/CLAUDE.md +81 -0
- package/LICENSE +21 -0
- package/README.md +283 -0
- package/REVIEW.md +96 -0
- package/RULES.md +51 -0
- package/SOUL.md +21 -0
- package/WORKING-CONTEXT.md +52 -0
- package/agent.yaml +219 -0
- package/agents/architect.md +57 -0
- package/agents/code-reviewer.md +60 -0
- package/agents/codex-challenger.md +53 -0
- package/agents/codex-reviewer.md +56 -0
- package/agents/debugger.md +33 -0
- package/agents/doc-writer.md +51 -0
- package/agents/executor.md +41 -0
- package/agents/planner.md +49 -0
- package/agents/research.md +50 -0
- package/agents/security-reviewer.md +47 -0
- package/agents/test-engineer.md +41 -0
- package/bridge/mcp-server.js +301 -0
- package/commands/claude-led-codex-review.md +29 -0
- package/docs/ADVANCED.md +321 -0
- package/docs/AI-DEVELOPMENT-LIFECYCLE.md +105 -0
- package/docs/ARCHITECTURE.md +205 -0
- package/docs/AUDIT.md +114 -0
- package/docs/AUTH-MIGRATION.md +282 -0
- package/docs/CHANGELOG.md +97 -0
- package/docs/CLI-STAGES.md +89 -0
- package/docs/CODEMAPS/README.md +15 -0
- package/docs/CODEMAPS/agents.md +22 -0
- package/docs/CODEMAPS/bridge.md +18 -0
- package/docs/CODEMAPS/hooks.md +28 -0
- package/docs/CODEMAPS/manifests.md +14 -0
- package/docs/CODEMAPS/rules.md +22 -0
- package/docs/CODEMAPS/schemas.md +21 -0
- package/docs/CODEMAPS/scripts.md +158 -0
- package/docs/CODEMAPS/skills.md +29 -0
- package/docs/CODEMAPS/tests.md +98 -0
- package/docs/CORE-INVARIANTS.md +38 -0
- package/docs/DEMO.md +110 -0
- package/docs/EXAMPLE-PROJECT.md +92 -0
- package/docs/PORTING.md +154 -0
- package/docs/PRODUCT-PRINCIPLES.md +303 -0
- package/docs/PUBLISH-ALPHA.md +106 -0
- package/docs/QUICKSTART.md +344 -0
- package/docs/RELEASE-READINESS.md +140 -0
- package/docs/RISK-CLASSIFIER.md +50 -0
- package/docs/RUNBOOK.md +146 -0
- package/docs/SECURITY.md +79 -0
- package/docs/SETUP.md +142 -0
- package/docs/WHY-NEKOWORK.md +64 -0
- package/docs/case-studies/README.md +16 -0
- package/docs/case-studies/SINDRESORHUS-IS-PLAIN-OBJ.md +141 -0
- package/docs/dev-log/2026-04-29-p1-recovery.md +142 -0
- package/docs/dev-log/2026-04-29-week1-4.md +81 -0
- package/docs/examples/GITHUB-ACTIONS-HARDENING.md +86 -0
- package/docs/examples/QUALITY-LIFECYCLE-SMOKE.md +32 -0
- package/docs/examples/TRADING-DASHBOARD-MOCK.md +65 -0
- package/docs/workflows-stash/README.md +32 -0
- package/docs/workflows-stash/harness-review.yml +166 -0
- package/docs/workflows-stash/harness-validate.yml +48 -0
- package/examples/github-actions-hardening/.github/workflows/hardened-validate.yml +38 -0
- package/examples/github-actions-hardening/README.md +31 -0
- package/examples/github-actions-hardening/case-study/ASK.md +26 -0
- package/examples/github-actions-hardening/case-study/GATE_STATUS.md +28 -0
- package/examples/github-actions-hardening/case-study/PLAN.md +25 -0
- package/examples/github-actions-hardening/case-study/SHIP_READY.md +21 -0
- package/examples/github-actions-hardening/case-study/TASK.md +30 -0
- package/examples/github-actions-hardening/case-study/TEAM_HANDOFFS.md +37 -0
- package/examples/github-actions-hardening/case-study/VERIFY_SUMMARY.md +35 -0
- package/examples/github-actions-hardening/case-study/WORK_SUMMARY.md +24 -0
- package/examples/github-actions-hardening/package.json +12 -0
- package/examples/github-actions-hardening/scripts/check.mjs +43 -0
- package/examples/quality-lifecycle-smoke/README.md +30 -0
- package/examples/quality-lifecycle-smoke/case-study/ASK.md +24 -0
- package/examples/quality-lifecycle-smoke/case-study/GATE_STATUS.md +10 -0
- package/examples/quality-lifecycle-smoke/case-study/PLAN.md +19 -0
- package/examples/quality-lifecycle-smoke/case-study/SHIP_READY.md +11 -0
- package/examples/quality-lifecycle-smoke/case-study/TASK.md +19 -0
- package/examples/quality-lifecycle-smoke/case-study/TEAM_HANDOFFS.md +21 -0
- package/examples/quality-lifecycle-smoke/case-study/VERIFY_SUMMARY.md +44 -0
- package/examples/quality-lifecycle-smoke/case-study/WORK_SUMMARY.md +19 -0
- package/examples/quality-lifecycle-smoke/package.json +8 -0
- package/examples/quality-lifecycle-smoke/scripts/check.mjs +44 -0
- package/examples/trading-dashboard-mock/README.md +33 -0
- package/examples/trading-dashboard-mock/case-study/ASK.md +24 -0
- package/examples/trading-dashboard-mock/case-study/GATE_STATUS.md +28 -0
- package/examples/trading-dashboard-mock/case-study/PLAN.md +23 -0
- package/examples/trading-dashboard-mock/case-study/SHIP_READY.md +21 -0
- package/examples/trading-dashboard-mock/case-study/TASK.md +29 -0
- package/examples/trading-dashboard-mock/case-study/TEAM_HANDOFFS.md +49 -0
- package/examples/trading-dashboard-mock/case-study/VERIFY_SUMMARY.md +35 -0
- package/examples/trading-dashboard-mock/case-study/WORK_SUMMARY.md +27 -0
- package/examples/trading-dashboard-mock/fixtures/market.json +9 -0
- package/examples/trading-dashboard-mock/index.html +76 -0
- package/examples/trading-dashboard-mock/package.json +9 -0
- package/examples/trading-dashboard-mock/scripts/check.mjs +54 -0
- package/examples/trading-dashboard-mock/src/app.js +83 -0
- package/examples/trading-dashboard-mock/src/styles.css +227 -0
- package/hooks/hooks.json +44 -0
- package/hooks/scripts/config-protection.js +34 -0
- package/hooks/scripts/gateguard-fact-force.js +146 -0
- package/hooks/scripts/persistent-mode.mjs +27 -0
- package/hooks/scripts/pre-bash-dispatcher.js +63 -0
- package/hooks/scripts/quality-gate.js +106 -0
- package/manifests/install-components.json +195 -0
- package/manifests/install-modules.json +101 -0
- package/manifests/install-profiles.json +134 -0
- package/package.json +96 -0
- package/rules/common/coding-style.md +71 -0
- package/rules/common/security.md +69 -0
- package/rules/common/testing.md +58 -0
- package/rules/python/coding-style.md +80 -0
- package/rules/python/testing.md +86 -0
- package/rules/typescript/coding-style.md +97 -0
- package/rules/typescript/security.md +67 -0
- package/rules/typescript/testing.md +78 -0
- package/schemas/agent-yaml.schema.json +168 -0
- package/schemas/agent.schema.json +32 -0
- package/schemas/handoff.schema.json +105 -0
- package/schemas/hooks.schema.json +35 -0
- package/schemas/install-components.schema.json +46 -0
- package/schemas/install-modules.schema.json +39 -0
- package/schemas/install-profiles.schema.json +32 -0
- package/schemas/install-state.schema.json +42 -0
- package/schemas/routing.schema.json +42 -0
- package/schemas/skill.schema.json +19 -0
- package/scripts/agents/dispatch.js +144 -0
- package/scripts/agents/runners/claude.js +214 -0
- package/scripts/agents/runners/codex.js +233 -0
- package/scripts/agents/runners/gemini.js +92 -0
- package/scripts/agents/runners/mock.js +107 -0
- package/scripts/auth/github-import-gh.js +52 -0
- package/scripts/auth/github-login.js +79 -0
- package/scripts/auth/github-logout.js +21 -0
- package/scripts/auth/github-status.js +46 -0
- package/scripts/build-claude.js +101 -0
- package/scripts/build-codemaps.js +286 -0
- package/scripts/build-codex.js +93 -0
- package/scripts/build-cursor.js +132 -0
- package/scripts/build-gemini.js +117 -0
- package/scripts/build-opencode.js +117 -0
- package/scripts/ci/catalog.js +120 -0
- package/scripts/ci/check-markers.js +48 -0
- package/scripts/ci/security-hardening.js +270 -0
- package/scripts/ci/validate-agents.js +88 -0
- package/scripts/ci/validate-hooks.js +99 -0
- package/scripts/ci/validate-manifests.js +128 -0
- package/scripts/ci/validate-skills.js +93 -0
- package/scripts/cli.js +1134 -0
- package/scripts/core/auth-guard.js +22 -0
- package/scripts/core/build-roots.js +11 -0
- package/scripts/core/cli-resolver.js +64 -0
- package/scripts/core/execution-workspace.js +84 -0
- package/scripts/core/git-mutation-guard.js +79 -0
- package/scripts/core/install-state.js +125 -0
- package/scripts/core/json-extractor.js +32 -0
- package/scripts/core/subprocess.js +74 -0
- package/scripts/daemon/wait.js +278 -0
- package/scripts/demo-external-project.js +222 -0
- package/scripts/demo-quick-run.js +193 -0
- package/scripts/demo-review.js +204 -0
- package/scripts/doctor.js +296 -0
- package/scripts/install-apply.js +185 -0
- package/scripts/install-plan.js +411 -0
- package/scripts/lib/acceptance-criteria.js +105 -0
- package/scripts/lib/costs.js +82 -0
- package/scripts/lib/instincts.js +194 -0
- package/scripts/lib/keychain.js +85 -0
- package/scripts/lib/profile-policy.js +134 -0
- package/scripts/lib/profile-safety.js +81 -0
- package/scripts/lib/risk-classifier.js +145 -0
- package/scripts/lib/router.js +138 -0
- package/scripts/lib/severity.js +99 -0
- package/scripts/lib/token-vault.js +136 -0
- package/scripts/orchestrators/apply.js +225 -0
- package/scripts/orchestrators/ask.js +143 -0
- package/scripts/orchestrators/gate.js +179 -0
- package/scripts/orchestrators/ralph.js +179 -0
- package/scripts/orchestrators/review.js +452 -0
- package/scripts/orchestrators/run.js +151 -0
- package/scripts/orchestrators/ship.js +339 -0
- package/scripts/orchestrators/team-lite.js +270 -0
- package/scripts/orchestrators/team.js +244 -0
- package/scripts/orchestrators/verify.js +306 -0
- package/scripts/orchestrators/work.js +207 -0
- package/scripts/portability/simulate-port.js +220 -0
- package/scripts/repair.js +184 -0
- package/scripts/sync-claude-md.js +220 -0
- package/scripts/verify/claude-live.js +30 -0
- package/scripts/verify/codex-live.js +60 -0
- package/scripts/verify/gemini-live.js +48 -0
- package/scripts/verify/runtime.js +105 -0
- package/skills/claude-led-codex-review/SKILL.md +133 -0
- package/skills/plan-eng-review/SKILL.md +51 -0
- package/skills/porting/SKILL.md +69 -0
- package/skills/ralph/SKILL.md +48 -0
- package/skills/release-readiness/SKILL.md +62 -0
- package/skills/review/SKILL.md +42 -0
- package/skills/security-hardening/SKILL.md +59 -0
- package/skills/ship/SKILL.md +44 -0
- 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
|
+
});
|