harnessed 3.9.25 → 4.0.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/README.md +7 -2
- package/dist/cli.mjs +1811 -1425
- package/dist/cli.mjs.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/cli.mjs
CHANGED
|
@@ -3,22 +3,22 @@ import { spawnSync, spawn, execSync } from 'child_process';
|
|
|
3
3
|
import { existsSync, mkdirSync, renameSync, writeFileSync, readFileSync, readdirSync } from 'fs';
|
|
4
4
|
import { join, dirname, resolve, sep, relative } from 'path';
|
|
5
5
|
import { homedir } from 'os';
|
|
6
|
-
import { readFile, readdir, unlink, writeFile, stat, rm, cp, mkdir, access, rename } from 'fs/promises';
|
|
7
6
|
import { Type } from '@sinclair/typebox';
|
|
7
|
+
import { readFile, readdir, unlink, writeFile, stat, rm, cp, mkdir, access, rename } from 'fs/promises';
|
|
8
8
|
import { Value } from '@sinclair/typebox/value';
|
|
9
|
-
import { LineCounter, parseDocument, parse, isSeq, isScalar } from 'yaml';
|
|
10
9
|
import lockfile from 'proper-lockfile';
|
|
10
|
+
import { parse, LineCounter, parseDocument, isSeq, isScalar } from 'yaml';
|
|
11
11
|
import * as p from '@clack/prompts';
|
|
12
12
|
import { Command } from 'commander';
|
|
13
13
|
import { Ajv } from 'ajv';
|
|
14
14
|
import * as ajvFormatsNs from 'ajv-formats';
|
|
15
15
|
import { fileURLToPath } from 'url';
|
|
16
16
|
import { createHash } from 'crypto';
|
|
17
|
+
import { Parser } from 'expr-eval';
|
|
17
18
|
import { createPatch } from 'diff';
|
|
18
19
|
import pc from 'picocolors';
|
|
19
20
|
import { stdout, stdin } from 'process';
|
|
20
21
|
import * as readline from 'readline/promises';
|
|
21
|
-
import { Parser } from 'expr-eval';
|
|
22
22
|
import { query } from '@anthropic-ai/claude-agent-sdk';
|
|
23
23
|
|
|
24
24
|
var __defProp = Object.defineProperty;
|
|
@@ -119,158 +119,6 @@ var init_harnessedRoot = __esm({
|
|
|
119
119
|
"src/installers/lib/harnessedRoot.ts"() {
|
|
120
120
|
}
|
|
121
121
|
});
|
|
122
|
-
function checkNodeVersion() {
|
|
123
|
-
const v = process.versions.node;
|
|
124
|
-
const major = Number.parseInt(v.split(".")[0] ?? "0", 10);
|
|
125
|
-
return major >= 22 ? { name: "node \u2265 22", status: "pass", message: `node ${v}` } : {
|
|
126
|
-
name: "node \u2265 22",
|
|
127
|
-
status: "fail",
|
|
128
|
-
message: `node ${v} (need \u2265 22)`,
|
|
129
|
-
fix: "nvm install 22 && nvm use 22"
|
|
130
|
-
};
|
|
131
|
-
}
|
|
132
|
-
async function checkMcpScope() {
|
|
133
|
-
const projectMcp = join(process.cwd(), ".mcp.json");
|
|
134
|
-
const userClaude = join(homedir(), ".claude.json");
|
|
135
|
-
let projectExists = false;
|
|
136
|
-
try {
|
|
137
|
-
await readFile(projectMcp, "utf8");
|
|
138
|
-
projectExists = true;
|
|
139
|
-
} catch {
|
|
140
|
-
}
|
|
141
|
-
let userHasMcp = false;
|
|
142
|
-
try {
|
|
143
|
-
const raw = await readFile(userClaude, "utf8");
|
|
144
|
-
const parsed = JSON.parse(raw);
|
|
145
|
-
userHasMcp = !!parsed.mcpServers && Object.keys(parsed.mcpServers).length > 0;
|
|
146
|
-
} catch {
|
|
147
|
-
}
|
|
148
|
-
if (userHasMcp) {
|
|
149
|
-
return {
|
|
150
|
-
name: "mcp scope = project",
|
|
151
|
-
status: "fail",
|
|
152
|
-
message: `~/.claude.json has user-scope mcpServers (CC #54803 risk)`,
|
|
153
|
-
fix: "remove user-scope entries; re-add via `claude mcp add --scope project ...`"
|
|
154
|
-
};
|
|
155
|
-
}
|
|
156
|
-
return {
|
|
157
|
-
name: "mcp scope = project",
|
|
158
|
-
status: "pass",
|
|
159
|
-
message: projectExists ? "project .mcp.json present" : "no MCP servers installed"
|
|
160
|
-
};
|
|
161
|
-
}
|
|
162
|
-
function checkJq() {
|
|
163
|
-
const finder = process.platform === "win32" ? "where" : "which";
|
|
164
|
-
const r = spawnSync(finder, ["jq"], { encoding: "utf8" });
|
|
165
|
-
if (r.status === 0 && r.stdout.trim().length > 0) {
|
|
166
|
-
return {
|
|
167
|
-
name: "jq present",
|
|
168
|
-
status: "pass",
|
|
169
|
-
message: r.stdout.split(/\r?\n/)[0]?.trim() ?? "jq found"
|
|
170
|
-
};
|
|
171
|
-
}
|
|
172
|
-
const fix = process.platform === "win32" ? "winget install jqlang.jq (or: scoop install jq)" : process.platform === "darwin" ? "brew install jq" : "apt-get install jq (or: dnf install jq)";
|
|
173
|
-
return { name: "jq present", status: "fail", message: "jq not found in PATH", fix };
|
|
174
|
-
}
|
|
175
|
-
function checkWinBash() {
|
|
176
|
-
if (process.platform !== "win32") {
|
|
177
|
-
return { name: "bash flavor (win)", status: "pass", message: "skipped (non-Windows)" };
|
|
178
|
-
}
|
|
179
|
-
const where = spawnSync("where", ["bash"], { encoding: "utf8" });
|
|
180
|
-
const firstBash = (where.stdout ?? "").split(/\r?\n/)[0]?.trim() ?? "(not found)";
|
|
181
|
-
if (where.status !== 0 || !firstBash || firstBash === "(not found)") {
|
|
182
|
-
return {
|
|
183
|
-
name: "bash flavor (win)",
|
|
184
|
-
status: "fail",
|
|
185
|
-
message: "no bash on PATH",
|
|
186
|
-
fix: "install Git for Windows (Git Bash) and ensure it is on PATH"
|
|
187
|
-
};
|
|
188
|
-
}
|
|
189
|
-
const probe = spawnSync("bash", ["-c", "echo $WSL_DISTRO_NAME"], { encoding: "utf8" });
|
|
190
|
-
const distro = (probe.stdout ?? "").trim();
|
|
191
|
-
if (distro.length > 0) {
|
|
192
|
-
return {
|
|
193
|
-
name: "bash flavor (win)",
|
|
194
|
-
status: "fail",
|
|
195
|
-
message: `WSL bash (${distro}) \u2014 ralph-loop subagent fork breaks under WSL`,
|
|
196
|
-
fix: "reorder PATH so Git Bash precedes WSL bash.exe (Settings \u2192 System \u2192 Environment Variables)"
|
|
197
|
-
};
|
|
198
|
-
}
|
|
199
|
-
return { name: "bash flavor (win)", status: "pass", message: `${firstBash} (Git Bash / native)` };
|
|
200
|
-
}
|
|
201
|
-
var init_check_builtin = __esm({
|
|
202
|
-
"src/cli/lib/check-builtin.ts"() {
|
|
203
|
-
}
|
|
204
|
-
});
|
|
205
|
-
|
|
206
|
-
// src/cli/lib/probe-gstack.ts
|
|
207
|
-
var probe_gstack_exports = {};
|
|
208
|
-
__export(probe_gstack_exports, {
|
|
209
|
-
probeGstackPrefix: () => probeGstackPrefix
|
|
210
|
-
});
|
|
211
|
-
function probeOne(cmd) {
|
|
212
|
-
const finder = process.platform === "win32" ? "where" : "which";
|
|
213
|
-
const r = spawnSync(finder, [cmd], { encoding: "utf8" });
|
|
214
|
-
return r.status === 0 && (r.stdout?.trim().length ?? 0) > 0;
|
|
215
|
-
}
|
|
216
|
-
function probeGstackPrefix() {
|
|
217
|
-
const hasGstack = probeOne("gstack-office-hours");
|
|
218
|
-
const hasBare = probeOne("office-hours");
|
|
219
|
-
if (hasGstack && !hasBare) {
|
|
220
|
-
return { status: "pass", prefix: "gstack-", detail: "gstack-office-hours found" };
|
|
221
|
-
}
|
|
222
|
-
if (!hasGstack && hasBare) {
|
|
223
|
-
return { status: "pass", prefix: "", detail: "office-hours found (--no-prefix mode)" };
|
|
224
|
-
}
|
|
225
|
-
if (hasGstack && hasBare) {
|
|
226
|
-
return {
|
|
227
|
-
status: "fail",
|
|
228
|
-
detail: "both gstack-office-hours AND office-hours found \u2014 ambiguous",
|
|
229
|
-
fix: `edit .harnessed/config.json manually: '{"gstack_prefix":"gstack-"}' OR '{"gstack_prefix":""}'`
|
|
230
|
-
};
|
|
231
|
-
}
|
|
232
|
-
return {
|
|
233
|
-
status: "fail",
|
|
234
|
-
detail: "neither gstack-office-hours nor office-hours found in PATH",
|
|
235
|
-
fix: "install gstack: `npm i -g @gstack/cli` (or your preferred install method)"
|
|
236
|
-
};
|
|
237
|
-
}
|
|
238
|
-
var init_probe_gstack = __esm({
|
|
239
|
-
"src/cli/lib/probe-gstack.ts"() {
|
|
240
|
-
}
|
|
241
|
-
});
|
|
242
|
-
|
|
243
|
-
// src/manifest/lib/path-guard.ts
|
|
244
|
-
function checkPathSafe(input) {
|
|
245
|
-
for (const re of PATH_TRAVERSAL_PATTERNS) {
|
|
246
|
-
if (re.test(input)) throw new PathTraversalError();
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
var PATH_TRAVERSAL_PATTERNS, PathTraversalError;
|
|
250
|
-
var init_path_guard = __esm({
|
|
251
|
-
"src/manifest/lib/path-guard.ts"() {
|
|
252
|
-
PATH_TRAVERSAL_PATTERNS = [
|
|
253
|
-
/\.\.\//,
|
|
254
|
-
// (1) Unix dot-dot-slash: ../../etc/passwd
|
|
255
|
-
/\.\.\\/,
|
|
256
|
-
// (2) Windows backslash: ..\windows\system32
|
|
257
|
-
// biome-ignore lint/suspicious/noControlCharactersInRegex: intentional null-byte injection detection (R10.4 D-03 OWASP A1 vector 3)
|
|
258
|
-
/\x00/,
|
|
259
|
-
// (3) Null byte injection: path\x00attack
|
|
260
|
-
/%2[eE]%2[eE]/,
|
|
261
|
-
// (4) URL-encoded dot-dot: %2e%2e%2fetc
|
|
262
|
-
/%25[2][eE]%25[2][eE]/
|
|
263
|
-
// (5) Double-encoded: %252e%252e%252f
|
|
264
|
-
];
|
|
265
|
-
PathTraversalError = class _PathTraversalError extends Error {
|
|
266
|
-
constructor() {
|
|
267
|
-
super("path traversal attempt detected");
|
|
268
|
-
this.name = "PathTraversalError";
|
|
269
|
-
Object.setPrototypeOf(this, _PathTraversalError.prototype);
|
|
270
|
-
}
|
|
271
|
-
};
|
|
272
|
-
}
|
|
273
|
-
});
|
|
274
122
|
function branchOnSchemaVersion(v, handlers) {
|
|
275
123
|
const isKnownVersion = Object.values(SCHEMA_VERSIONS).includes(v);
|
|
276
124
|
return isKnownVersion ? handlers.v1() : handlers.unknown();
|
|
@@ -338,171 +186,168 @@ var init_schemaVersion = __esm({
|
|
|
338
186
|
]);
|
|
339
187
|
}
|
|
340
188
|
});
|
|
341
|
-
var
|
|
342
|
-
var
|
|
343
|
-
"src/
|
|
189
|
+
var CheckpointStatus, CheckpointV1;
|
|
190
|
+
var init_checkpoint_v1 = __esm({
|
|
191
|
+
"src/checkpoint/schema/checkpoint.v1.ts"() {
|
|
344
192
|
init_schemaVersion();
|
|
345
|
-
|
|
193
|
+
CheckpointStatus = Type.Union([
|
|
194
|
+
Type.Literal("active"),
|
|
195
|
+
Type.Literal("paused"),
|
|
196
|
+
Type.Literal("complete")
|
|
197
|
+
]);
|
|
198
|
+
CheckpointV1 = Type.Object(
|
|
346
199
|
{
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
200
|
+
schemaVersion: Type.Literal(SCHEMA_VERSIONS.checkpoint),
|
|
201
|
+
phase: Type.String({ minLength: 1 }),
|
|
202
|
+
status: CheckpointStatus,
|
|
203
|
+
last_task: Type.String(),
|
|
204
|
+
key_decisions: Type.Array(Type.String()),
|
|
205
|
+
canonical_refs: Type.Array(Type.String()),
|
|
206
|
+
/** D-04 WIRE-IN: optional SDK session_id captured via `sdkSpawn`
|
|
207
|
+
* `onSessionId` callback (CD-4 closure-ready) for future `--resume`. */
|
|
208
|
+
session_id: Type.Optional(Type.String()),
|
|
209
|
+
/** RESEARCH § 1.3 critical constraint — SDK session resume requires cwd
|
|
210
|
+
* match; we capture and validate at restore time. */
|
|
211
|
+
cwd: Type.String({ minLength: 1 }),
|
|
212
|
+
timestamp: Type.String({ minLength: 1 }),
|
|
213
|
+
// ISO-8601 by convention (TypeBox `format` requires Ajv-style format registry; we keep shape-check only — drift caught in writeCheckpoint path)
|
|
214
|
+
archive_path: Type.String({ minLength: 1 })
|
|
356
215
|
},
|
|
357
216
|
{ additionalProperties: false }
|
|
358
217
|
);
|
|
359
|
-
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
var WorkflowStatus, CurrentWorkflowV1;
|
|
221
|
+
var init_currentWorkflow_v1 = __esm({
|
|
222
|
+
"src/checkpoint/schema/currentWorkflow.v1.ts"() {
|
|
223
|
+
init_schemaVersion();
|
|
224
|
+
WorkflowStatus = Type.Union([
|
|
225
|
+
Type.Literal("active"),
|
|
226
|
+
Type.Literal("paused"),
|
|
227
|
+
Type.Literal("complete")
|
|
228
|
+
]);
|
|
229
|
+
CurrentWorkflowV1 = Type.Object(
|
|
360
230
|
{
|
|
361
|
-
schemaVersion: Type.Literal(SCHEMA_VERSIONS.
|
|
362
|
-
|
|
363
|
-
|
|
231
|
+
schemaVersion: Type.Literal(SCHEMA_VERSIONS.currentWorkflow),
|
|
232
|
+
phase: Type.String({ minLength: 1 }),
|
|
233
|
+
status: WorkflowStatus,
|
|
234
|
+
last_checkpoint_path: Type.Union([Type.String(), Type.Null()]),
|
|
235
|
+
// ISO-8601 by convention (TypeBox `format` requires Ajv-style registry; shape-check only here, drift surfaces in state.ts writer).
|
|
236
|
+
started_at: Type.String({ minLength: 1 }),
|
|
237
|
+
paused_at: Type.Optional(Type.String({ minLength: 1 })),
|
|
238
|
+
completed_at: Type.Optional(Type.String({ minLength: 1 }))
|
|
364
239
|
},
|
|
365
240
|
{ additionalProperties: false }
|
|
366
241
|
);
|
|
367
242
|
}
|
|
368
243
|
});
|
|
369
244
|
|
|
370
|
-
// src/
|
|
371
|
-
var
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
resolveAlias: () => resolveAlias
|
|
376
|
-
});
|
|
377
|
-
function loadAliases() {
|
|
378
|
-
if (_cached) return _cached;
|
|
379
|
-
if (!existsSync(ALIASES_PATH)) return null;
|
|
380
|
-
const raw = readFileSync(ALIASES_PATH, "utf8");
|
|
381
|
-
const parsed = parse(raw);
|
|
382
|
-
if (!Value.Check(AliasesV1, parsed)) {
|
|
383
|
-
const errs = [...Value.Errors(AliasesV1, parsed)].slice(0, 3);
|
|
384
|
-
throw new Error(
|
|
385
|
-
`aliases.yaml schema invalid: ${errs.map((e) => `${e.path} ${e.message}`).join("; ")}`
|
|
386
|
-
);
|
|
245
|
+
// src/checkpoint/schema/index.ts
|
|
246
|
+
var init_schema = __esm({
|
|
247
|
+
"src/checkpoint/schema/index.ts"() {
|
|
248
|
+
init_checkpoint_v1();
|
|
249
|
+
init_currentWorkflow_v1();
|
|
387
250
|
}
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
function resolveAlias(name) {
|
|
392
|
-
checkPathSafe(name);
|
|
393
|
-
return loadAliases()?.aliases?.[name]?.redirect ?? null;
|
|
251
|
+
});
|
|
252
|
+
function statePath() {
|
|
253
|
+
return harnessedFile("current-workflow.json");
|
|
394
254
|
}
|
|
395
|
-
function
|
|
396
|
-
|
|
397
|
-
return a ? Object.entries(a.aliases).map(([old, entry]) => ({ old, entry })) : [];
|
|
255
|
+
function lockTarget() {
|
|
256
|
+
return getHarnessedRoot();
|
|
398
257
|
}
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
var check_deprecations_exports = {};
|
|
411
|
-
__export(check_deprecations_exports, {
|
|
412
|
-
checkDeprecations: () => checkDeprecations
|
|
413
|
-
});
|
|
414
|
-
function checkDeprecations() {
|
|
258
|
+
function lockOpts() {
|
|
259
|
+
return {
|
|
260
|
+
stale: 1e4,
|
|
261
|
+
retries: { retries: 3, factor: 2, minTimeout: 100 },
|
|
262
|
+
lockfilePath: harnessedFile(".lock")
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
async function withLock(fn) {
|
|
266
|
+
const target = lockTarget();
|
|
267
|
+
await mkdir(target, { recursive: true });
|
|
268
|
+
let release;
|
|
415
269
|
try {
|
|
416
|
-
|
|
417
|
-
if (deprecations.length === 0) {
|
|
418
|
-
return { name: "deprecated manifests", status: "pass", message: "no deprecated manifests" };
|
|
419
|
-
}
|
|
420
|
-
const lines = deprecations.map(({ old, entry }) => {
|
|
421
|
-
const removal = entry.removal_date ? `, removes ${entry.removal_date}` : "";
|
|
422
|
-
return ` '${old}' \u2192 '${entry.redirect}' (since ${entry.since_version}, ${entry.deprecation_date}${removal}; ${entry.reason})`;
|
|
423
|
-
});
|
|
424
|
-
return {
|
|
425
|
-
name: "deprecated manifests",
|
|
426
|
-
status: "warn",
|
|
427
|
-
message: `${deprecations.length} deprecated manifest(s):
|
|
428
|
-
${lines.join("\n")}`,
|
|
429
|
-
fix: "install paths auto-redirect; consider migrating manifest references to new names"
|
|
430
|
-
};
|
|
270
|
+
release = await lockfile.lock(target, lockOpts());
|
|
431
271
|
} catch (e) {
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
272
|
+
if (e.code === "ELOCKED") throw new LockHeldError();
|
|
273
|
+
throw e;
|
|
274
|
+
}
|
|
275
|
+
try {
|
|
276
|
+
return await fn();
|
|
277
|
+
} finally {
|
|
278
|
+
await release?.();
|
|
438
279
|
}
|
|
439
280
|
}
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
281
|
+
async function readCurrentWorkflow() {
|
|
282
|
+
let raw;
|
|
283
|
+
try {
|
|
284
|
+
raw = await readFile(statePath(), "utf8");
|
|
285
|
+
} catch {
|
|
286
|
+
return null;
|
|
443
287
|
}
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
CheckpointStatus = Type.Union([
|
|
450
|
-
Type.Literal("active"),
|
|
451
|
-
Type.Literal("paused"),
|
|
452
|
-
Type.Literal("complete")
|
|
453
|
-
]);
|
|
454
|
-
CheckpointV1 = Type.Object(
|
|
455
|
-
{
|
|
456
|
-
schemaVersion: Type.Literal(SCHEMA_VERSIONS.checkpoint),
|
|
457
|
-
phase: Type.String({ minLength: 1 }),
|
|
458
|
-
status: CheckpointStatus,
|
|
459
|
-
last_task: Type.String(),
|
|
460
|
-
key_decisions: Type.Array(Type.String()),
|
|
461
|
-
canonical_refs: Type.Array(Type.String()),
|
|
462
|
-
/** D-04 WIRE-IN: optional SDK session_id captured via `sdkSpawn`
|
|
463
|
-
* `onSessionId` callback (CD-4 closure-ready) for future `--resume`. */
|
|
464
|
-
session_id: Type.Optional(Type.String()),
|
|
465
|
-
/** RESEARCH § 1.3 critical constraint — SDK session resume requires cwd
|
|
466
|
-
* match; we capture and validate at restore time. */
|
|
467
|
-
cwd: Type.String({ minLength: 1 }),
|
|
468
|
-
timestamp: Type.String({ minLength: 1 }),
|
|
469
|
-
// ISO-8601 by convention (TypeBox `format` requires Ajv-style format registry; we keep shape-check only — drift caught in writeCheckpoint path)
|
|
470
|
-
archive_path: Type.String({ minLength: 1 })
|
|
471
|
-
},
|
|
472
|
-
{ additionalProperties: false }
|
|
473
|
-
);
|
|
288
|
+
let parsed;
|
|
289
|
+
try {
|
|
290
|
+
parsed = JSON.parse(raw);
|
|
291
|
+
} catch {
|
|
292
|
+
return null;
|
|
474
293
|
}
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
CurrentWorkflowV1 = Type.Object(
|
|
486
|
-
{
|
|
487
|
-
schemaVersion: Type.Literal(SCHEMA_VERSIONS.currentWorkflow),
|
|
488
|
-
phase: Type.String({ minLength: 1 }),
|
|
489
|
-
status: WorkflowStatus,
|
|
490
|
-
last_checkpoint_path: Type.Union([Type.String(), Type.Null()]),
|
|
491
|
-
// ISO-8601 by convention (TypeBox `format` requires Ajv-style registry; shape-check only here, drift surfaces in state.ts writer).
|
|
492
|
-
started_at: Type.String({ minLength: 1 }),
|
|
493
|
-
paused_at: Type.Optional(Type.String({ minLength: 1 })),
|
|
494
|
-
completed_at: Type.Optional(Type.String({ minLength: 1 }))
|
|
495
|
-
},
|
|
496
|
-
{ additionalProperties: false }
|
|
497
|
-
);
|
|
294
|
+
const v = parsed.schemaVersion ?? "";
|
|
295
|
+
return branchOnSchemaVersion(v, {
|
|
296
|
+
v1: () => Value.Check(CurrentWorkflowV1, parsed) ? parsed : null,
|
|
297
|
+
unknown: () => null
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
async function writeCurrentWorkflow(s) {
|
|
301
|
+
if (!Value.Check(CurrentWorkflowV1, s)) {
|
|
302
|
+
const errs = [...Value.Errors(CurrentWorkflowV1, s)].map((e) => e.message).join("; ");
|
|
303
|
+
throw new WorkflowStateError(`current-workflow schema validation failed: ${errs}`);
|
|
498
304
|
}
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
305
|
+
const path = statePath();
|
|
306
|
+
await mkdir(dirname(path), { recursive: true });
|
|
307
|
+
await withLock(async () => {
|
|
308
|
+
await writeFile(path, JSON.stringify(s, null, 2), "utf8");
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
async function activate(phase, checkpointPath = null) {
|
|
312
|
+
await writeCurrentWorkflow({
|
|
313
|
+
schemaVersion: SCHEMA_VERSIONS.currentWorkflow,
|
|
314
|
+
phase,
|
|
315
|
+
status: "active",
|
|
316
|
+
last_checkpoint_path: checkpointPath,
|
|
317
|
+
started_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
async function pause() {
|
|
321
|
+
const s = await readCurrentWorkflow();
|
|
322
|
+
if (!s) return;
|
|
323
|
+
await writeCurrentWorkflow({ ...s, status: "paused", paused_at: (/* @__PURE__ */ new Date()).toISOString() });
|
|
324
|
+
}
|
|
325
|
+
async function complete() {
|
|
326
|
+
const s = await readCurrentWorkflow();
|
|
327
|
+
if (!s) return;
|
|
328
|
+
await writeCurrentWorkflow({ ...s, status: "complete", completed_at: (/* @__PURE__ */ new Date()).toISOString() });
|
|
329
|
+
}
|
|
330
|
+
var WorkflowStateError, LockHeldError;
|
|
331
|
+
var init_state = __esm({
|
|
332
|
+
"src/checkpoint/state.ts"() {
|
|
333
|
+
init_harnessedRoot();
|
|
334
|
+
init_schemaVersion();
|
|
335
|
+
init_schema();
|
|
336
|
+
WorkflowStateError = class extends Error {
|
|
337
|
+
constructor(message) {
|
|
338
|
+
super(message);
|
|
339
|
+
this.name = "WorkflowStateError";
|
|
340
|
+
}
|
|
341
|
+
};
|
|
342
|
+
LockHeldError = class _LockHeldError extends Error {
|
|
343
|
+
constructor() {
|
|
344
|
+
super(
|
|
345
|
+
`another harnessed process holds the lock at ${harnessedFile(".lock")} \u2014 wait or kill stale process (try: harnessed status)`
|
|
346
|
+
);
|
|
347
|
+
this.name = "LockHeldError";
|
|
348
|
+
Object.setPrototypeOf(this, _LockHeldError.prototype);
|
|
349
|
+
}
|
|
350
|
+
};
|
|
506
351
|
}
|
|
507
352
|
});
|
|
508
353
|
function estimateTokens(s) {
|
|
@@ -554,754 +399,949 @@ var init_template = __esm({
|
|
|
554
399
|
}
|
|
555
400
|
});
|
|
556
401
|
|
|
557
|
-
// src/
|
|
558
|
-
var
|
|
559
|
-
__export(
|
|
560
|
-
|
|
402
|
+
// src/checkpoint/engineHook.ts
|
|
403
|
+
var engineHook_exports = {};
|
|
404
|
+
__export(engineHook_exports, {
|
|
405
|
+
activatePhase: () => activatePhase,
|
|
406
|
+
completePhase: () => completePhase
|
|
561
407
|
});
|
|
562
|
-
function
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
if (!existsSync(md)) return [];
|
|
567
|
-
const fm = readFileSync(md, "utf8").match(/^---\n([\s\S]*?)\n---/)?.[1] ?? "";
|
|
568
|
-
const desc = fm.match(/^description:\s*(.+)$/m)?.[1] ?? "";
|
|
569
|
-
return [{ name, tokens: estimateTokens(desc) }];
|
|
570
|
-
});
|
|
408
|
+
async function activatePhase(phaseId) {
|
|
409
|
+
const checkpointPath = join(harnessedSubdir("checkpoints"), `${phaseId}.json`);
|
|
410
|
+
await activate(phaseId, checkpointPath);
|
|
411
|
+
return { checkpointPath };
|
|
571
412
|
}
|
|
572
|
-
function
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
if (total <= TOTAL_THRESHOLD && over === 0) {
|
|
578
|
-
const msg = `${items.length} skill(s) total ${total} tokens (under 1% / 2000 threshold)`;
|
|
579
|
-
return { name: "token budget", status: "pass", message: msg };
|
|
413
|
+
async function completePhase(ctx) {
|
|
414
|
+
if (ctx.phaseId === "unknown") {
|
|
415
|
+
console.error(
|
|
416
|
+
`[harnessed] WARN engineHook: phaseId="unknown" \u2014 checkpoint paths fall back to ${join(harnessedSubdir("checkpoints"), "unknown.json")} (Karpathy fail-loud non-blocking; W-04 mitigation)`
|
|
417
|
+
);
|
|
580
418
|
}
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
419
|
+
writeCheckpoint({
|
|
420
|
+
schemaVersion: SCHEMA_VERSIONS.checkpoint,
|
|
421
|
+
phase: ctx.phaseId,
|
|
422
|
+
status: "complete",
|
|
423
|
+
last_task: ctx.lastTask ?? "engine.runRouting complete",
|
|
424
|
+
key_decisions: ctx.keyDecisions ?? [],
|
|
425
|
+
canonical_refs: ctx.canonicalRefs ?? [],
|
|
426
|
+
...ctx.sessionId ? { session_id: ctx.sessionId } : {},
|
|
427
|
+
cwd: process.cwd(),
|
|
428
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
429
|
+
archive_path: `${join(harnessedSubdir("archive"), `phase-${ctx.phaseId}`)}/`
|
|
430
|
+
});
|
|
431
|
+
await complete();
|
|
590
432
|
}
|
|
591
|
-
var
|
|
592
|
-
|
|
593
|
-
|
|
433
|
+
var init_engineHook = __esm({
|
|
434
|
+
"src/checkpoint/engineHook.ts"() {
|
|
435
|
+
init_harnessedRoot();
|
|
436
|
+
init_schemaVersion();
|
|
437
|
+
init_state();
|
|
594
438
|
init_template();
|
|
595
|
-
CONTEXT_WINDOW_TOKENS = 2e5;
|
|
596
|
-
TOTAL_THRESHOLD = 2e3;
|
|
597
|
-
PER_SKILL_THRESHOLD = 5e3;
|
|
598
439
|
}
|
|
599
440
|
});
|
|
600
|
-
|
|
601
|
-
const
|
|
602
|
-
const
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
const data = JSON.parse(raw);
|
|
609
|
-
settingsValue = data.env?.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS;
|
|
610
|
-
settingsOn = settingsValue === "1";
|
|
611
|
-
} catch {
|
|
612
|
-
}
|
|
613
|
-
const detected = { env: envOn, settingsJson: settingsOn };
|
|
614
|
-
if (envOn || settingsOn) {
|
|
615
|
-
return { status: "pass", detected, envValue, settingsValue };
|
|
616
|
-
}
|
|
617
|
-
return {
|
|
618
|
-
status: "missing",
|
|
619
|
-
detected,
|
|
620
|
-
envValue,
|
|
621
|
-
settingsValue,
|
|
622
|
-
remediation: 'Agent Teams not enabled. Add to ~/.claude/settings.json:\n "env": { "CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS": "1" }\nOR run: claude config set env.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS 1\nOR export env var:\n export CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1\nThen restart Claude Code (CC >= 2.1.133 required).'
|
|
441
|
+
function checkNodeVersion() {
|
|
442
|
+
const v = process.versions.node;
|
|
443
|
+
const major = Number.parseInt(v.split(".")[0] ?? "0", 10);
|
|
444
|
+
return major >= 22 ? { name: "node \u2265 22", status: "pass", message: `node ${v}` } : {
|
|
445
|
+
name: "node \u2265 22",
|
|
446
|
+
status: "fail",
|
|
447
|
+
message: `node ${v} (need \u2265 22)`,
|
|
448
|
+
fix: "nvm install 22 && nvm use 22"
|
|
623
449
|
};
|
|
624
450
|
}
|
|
625
|
-
|
|
626
|
-
"
|
|
451
|
+
async function checkMcpScope() {
|
|
452
|
+
const projectMcp = join(process.cwd(), ".mcp.json");
|
|
453
|
+
const userClaude = join(homedir(), ".claude.json");
|
|
454
|
+
let projectExists = false;
|
|
455
|
+
try {
|
|
456
|
+
await readFile(projectMcp, "utf8");
|
|
457
|
+
projectExists = true;
|
|
458
|
+
} catch {
|
|
627
459
|
}
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
}
|
|
635
|
-
|
|
636
|
-
const r = await checkAgentTeams();
|
|
637
|
-
if (r.status === "pass") {
|
|
638
|
-
const source = r.detected.env ? "env var" : "settings.json";
|
|
460
|
+
let userHasMcp = false;
|
|
461
|
+
try {
|
|
462
|
+
const raw = await readFile(userClaude, "utf8");
|
|
463
|
+
const parsed = JSON.parse(raw);
|
|
464
|
+
userHasMcp = !!parsed.mcpServers && Object.keys(parsed.mcpServers).length > 0;
|
|
465
|
+
} catch {
|
|
466
|
+
}
|
|
467
|
+
if (userHasMcp) {
|
|
639
468
|
return {
|
|
640
|
-
name: "
|
|
641
|
-
status: "
|
|
642
|
-
message:
|
|
469
|
+
name: "mcp scope = project",
|
|
470
|
+
status: "fail",
|
|
471
|
+
message: `~/.claude.json has user-scope mcpServers (CC #54803 risk)`,
|
|
472
|
+
fix: "remove user-scope entries; re-add via `claude mcp add --scope project ...`"
|
|
643
473
|
};
|
|
644
474
|
}
|
|
645
475
|
return {
|
|
646
|
-
name: "
|
|
647
|
-
status: "
|
|
648
|
-
message: "
|
|
649
|
-
fix: r.remediation
|
|
476
|
+
name: "mcp scope = project",
|
|
477
|
+
status: "pass",
|
|
478
|
+
message: projectExists ? "project .mcp.json present" : "no MCP servers installed"
|
|
650
479
|
};
|
|
651
480
|
}
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
481
|
+
function checkJq() {
|
|
482
|
+
const finder = process.platform === "win32" ? "where" : "which";
|
|
483
|
+
const r = spawnSync(finder, ["jq"], { encoding: "utf8" });
|
|
484
|
+
if (r.status === 0 && r.stdout.trim().length > 0) {
|
|
485
|
+
return {
|
|
486
|
+
name: "jq present",
|
|
487
|
+
status: "pass",
|
|
488
|
+
message: r.stdout.split(/\r?\n/)[0]?.trim() ?? "jq found"
|
|
489
|
+
};
|
|
655
490
|
}
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
}
|
|
663
|
-
|
|
664
|
-
const
|
|
665
|
-
|
|
666
|
-
".claude",
|
|
667
|
-
"plugins",
|
|
668
|
-
"cache",
|
|
669
|
-
"planning-with-files",
|
|
670
|
-
"planning-with-files"
|
|
671
|
-
);
|
|
672
|
-
try {
|
|
673
|
-
const entries = await readdir(root);
|
|
674
|
-
const versions = entries.filter((e) => /^\d+\.\d+/.test(e));
|
|
675
|
-
if (versions.length > 0) {
|
|
676
|
-
return {
|
|
677
|
-
name: "planning-with-files plugin",
|
|
678
|
-
status: "pass",
|
|
679
|
-
message: `installed (version ${versions.join(", ")})`
|
|
680
|
-
};
|
|
681
|
-
}
|
|
491
|
+
const fix = process.platform === "win32" ? "winget install jqlang.jq (or: scoop install jq)" : process.platform === "darwin" ? "brew install jq" : "apt-get install jq (or: dnf install jq)";
|
|
492
|
+
return { name: "jq present", status: "fail", message: "jq not found in PATH", fix };
|
|
493
|
+
}
|
|
494
|
+
function checkWinBash() {
|
|
495
|
+
if (process.platform !== "win32") {
|
|
496
|
+
return { name: "bash flavor (win)", status: "pass", message: "skipped (non-Windows)" };
|
|
497
|
+
}
|
|
498
|
+
const where = spawnSync("where", ["bash"], { encoding: "utf8" });
|
|
499
|
+
const firstBash = (where.stdout ?? "").split(/\r?\n/)[0]?.trim() ?? "(not found)";
|
|
500
|
+
if (where.status !== 0 || !firstBash || firstBash === "(not found)") {
|
|
682
501
|
return {
|
|
683
|
-
name: "
|
|
684
|
-
status: "
|
|
685
|
-
message: "
|
|
686
|
-
fix:
|
|
687
|
-
install_commands: INSTALL_COMMANDS
|
|
502
|
+
name: "bash flavor (win)",
|
|
503
|
+
status: "fail",
|
|
504
|
+
message: "no bash on PATH",
|
|
505
|
+
fix: "install Git for Windows (Git Bash) and ensure it is on PATH"
|
|
688
506
|
};
|
|
689
|
-
}
|
|
507
|
+
}
|
|
508
|
+
const probe = spawnSync("bash", ["-c", "echo $WSL_DISTRO_NAME"], { encoding: "utf8" });
|
|
509
|
+
const distro = (probe.stdout ?? "").trim();
|
|
510
|
+
if (distro.length > 0) {
|
|
690
511
|
return {
|
|
691
|
-
name: "
|
|
692
|
-
status: "
|
|
693
|
-
message:
|
|
694
|
-
fix:
|
|
695
|
-
install_commands: INSTALL_COMMANDS
|
|
512
|
+
name: "bash flavor (win)",
|
|
513
|
+
status: "fail",
|
|
514
|
+
message: `WSL bash (${distro}) \u2014 ralph-loop subagent fork breaks under WSL`,
|
|
515
|
+
fix: "reorder PATH so Git Bash precedes WSL bash.exe (Settings \u2192 System \u2192 Environment Variables)"
|
|
696
516
|
};
|
|
697
517
|
}
|
|
518
|
+
return { name: "bash flavor (win)", status: "pass", message: `${firstBash} (Git Bash / native)` };
|
|
698
519
|
}
|
|
699
|
-
var
|
|
700
|
-
|
|
701
|
-
"src/cli/lib/check-planning-with-files.ts"() {
|
|
702
|
-
REMEDIATION = "install via `claude plugin marketplace add OthmanAdi/planning-with-files && claude plugin install planning-with-files` (requires >=2.2.0 per R20.15 + D-15)";
|
|
703
|
-
INSTALL_COMMANDS = [
|
|
704
|
-
"claude plugin marketplace add OthmanAdi/planning-with-files",
|
|
705
|
-
"claude plugin install planning-with-files"
|
|
706
|
-
];
|
|
520
|
+
var init_check_builtin = __esm({
|
|
521
|
+
"src/cli/lib/check-builtin.ts"() {
|
|
707
522
|
}
|
|
708
523
|
});
|
|
709
524
|
|
|
710
|
-
// src/cli/lib/
|
|
711
|
-
var
|
|
712
|
-
__export(
|
|
713
|
-
|
|
525
|
+
// src/cli/lib/probe-gstack.ts
|
|
526
|
+
var probe_gstack_exports = {};
|
|
527
|
+
__export(probe_gstack_exports, {
|
|
528
|
+
probeGstackPrefix: () => probeGstackPrefix
|
|
714
529
|
});
|
|
715
|
-
|
|
716
|
-
const
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
)
|
|
724
|
-
|
|
725
|
-
try {
|
|
726
|
-
const entries = await readdir(pluginRoot);
|
|
727
|
-
const versions = entries.filter((e) => /^\d+\.\d+/.test(e));
|
|
728
|
-
if (versions.length > 0) {
|
|
729
|
-
return {
|
|
730
|
-
name: "mattpocock-skills",
|
|
731
|
-
status: "pass",
|
|
732
|
-
message: `installed as plugin (version ${versions.join(", ")})`
|
|
733
|
-
};
|
|
734
|
-
}
|
|
735
|
-
} catch {
|
|
530
|
+
function probeOne(cmd) {
|
|
531
|
+
const finder = process.platform === "win32" ? "where" : "which";
|
|
532
|
+
const r = spawnSync(finder, [cmd], { encoding: "utf8" });
|
|
533
|
+
return r.status === 0 && (r.stdout?.trim().length ?? 0) > 0;
|
|
534
|
+
}
|
|
535
|
+
function probeGstackPrefix() {
|
|
536
|
+
const hasGstack = probeOne("gstack-office-hours");
|
|
537
|
+
const hasBare = probeOne("office-hours");
|
|
538
|
+
if (hasGstack && !hasBare) {
|
|
539
|
+
return { status: "pass", prefix: "gstack-", detail: "gstack-office-hours found" };
|
|
736
540
|
}
|
|
737
|
-
|
|
738
|
-
|
|
541
|
+
if (!hasGstack && hasBare) {
|
|
542
|
+
return { status: "pass", prefix: "", detail: "office-hours found (--no-prefix mode)" };
|
|
543
|
+
}
|
|
544
|
+
if (hasGstack && hasBare) {
|
|
739
545
|
return {
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
546
|
+
status: "fail",
|
|
547
|
+
detail: "both gstack-office-hours AND office-hours found \u2014 ambiguous",
|
|
548
|
+
fix: `edit .harnessed/config.json manually: '{"gstack_prefix":"gstack-"}' OR '{"gstack_prefix":""}'`
|
|
743
549
|
};
|
|
744
|
-
} catch {
|
|
745
|
-
}
|
|
746
|
-
const indicators = ["diagnose", "tdd", "zoom-out"];
|
|
747
|
-
for (const ind of indicators) {
|
|
748
|
-
const indPath = join(homedir(), ".claude", "skills", ind);
|
|
749
|
-
try {
|
|
750
|
-
await stat(indPath);
|
|
751
|
-
return {
|
|
752
|
-
name: "mattpocock-skills",
|
|
753
|
-
status: "pass",
|
|
754
|
-
message: `installed as individual skills (found ${ind}/)`
|
|
755
|
-
};
|
|
756
|
-
} catch {
|
|
757
|
-
}
|
|
758
550
|
}
|
|
759
551
|
return {
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
fix: REMEDIATION2,
|
|
764
|
-
install_commands: INSTALL_COMMANDS2
|
|
552
|
+
status: "fail",
|
|
553
|
+
detail: "neither gstack-office-hours nor office-hours found in PATH",
|
|
554
|
+
fix: "install gstack: `npm i -g @gstack/cli` (or your preferred install method)"
|
|
765
555
|
};
|
|
766
556
|
}
|
|
767
|
-
var
|
|
768
|
-
|
|
769
|
-
"src/cli/lib/check-mattpocock-skills.ts"() {
|
|
770
|
-
REMEDIATION2 = "install via `npx skills@latest add mattpocock/skills` (or git clone https://github.com/mattpocock/skills ~/.claude/skills/mattpocock-skills); methodology fallback already inline in role-prompts.yaml per v3.6.0 Phase 1 \u2014 install is optional but enables /grill-with-docs /zoom-out etc. SlashCommand acceleration";
|
|
771
|
-
INSTALL_COMMANDS2 = ["npx skills@latest add mattpocock/skills"];
|
|
557
|
+
var init_probe_gstack = __esm({
|
|
558
|
+
"src/cli/lib/probe-gstack.ts"() {
|
|
772
559
|
}
|
|
773
560
|
});
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
let raw;
|
|
780
|
-
try {
|
|
781
|
-
raw = await readFile(path, "utf8");
|
|
782
|
-
} catch (err2) {
|
|
783
|
-
if (err2.code === "ENOENT") return {};
|
|
784
|
-
throw err2;
|
|
785
|
-
}
|
|
786
|
-
try {
|
|
787
|
-
const parsed = JSON.parse(raw);
|
|
788
|
-
if (parsed === null || typeof parsed !== "object") return {};
|
|
789
|
-
return parsed;
|
|
790
|
-
} catch {
|
|
791
|
-
return {};
|
|
561
|
+
|
|
562
|
+
// src/manifest/lib/path-guard.ts
|
|
563
|
+
function checkPathSafe(input) {
|
|
564
|
+
for (const re of PATH_TRAVERSAL_PATTERNS) {
|
|
565
|
+
if (re.test(input)) throw new PathTraversalError();
|
|
792
566
|
}
|
|
793
567
|
}
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
]) {
|
|
816
|
-
try {
|
|
817
|
-
const raw = await readFile(path, "utf8");
|
|
818
|
-
const parsed = JSON.parse(raw);
|
|
819
|
-
const plugins = parsed.enabledPlugins;
|
|
820
|
-
if (plugins && typeof plugins === "object") {
|
|
821
|
-
if (Object.hasOwn(plugins, pluginName)) return true;
|
|
822
|
-
if (Object.keys(plugins).some((k) => k.split("@")[0] === pluginName)) return true;
|
|
568
|
+
var PATH_TRAVERSAL_PATTERNS, PathTraversalError;
|
|
569
|
+
var init_path_guard = __esm({
|
|
570
|
+
"src/manifest/lib/path-guard.ts"() {
|
|
571
|
+
PATH_TRAVERSAL_PATTERNS = [
|
|
572
|
+
/\.\.\//,
|
|
573
|
+
// (1) Unix dot-dot-slash: ../../etc/passwd
|
|
574
|
+
/\.\.\\/,
|
|
575
|
+
// (2) Windows backslash: ..\windows\system32
|
|
576
|
+
// biome-ignore lint/suspicious/noControlCharactersInRegex: intentional null-byte injection detection (R10.4 D-03 OWASP A1 vector 3)
|
|
577
|
+
/\x00/,
|
|
578
|
+
// (3) Null byte injection: path\x00attack
|
|
579
|
+
/%2[eE]%2[eE]/,
|
|
580
|
+
// (4) URL-encoded dot-dot: %2e%2e%2fetc
|
|
581
|
+
/%25[2][eE]%25[2][eE]/
|
|
582
|
+
// (5) Double-encoded: %252e%252e%252f
|
|
583
|
+
];
|
|
584
|
+
PathTraversalError = class _PathTraversalError extends Error {
|
|
585
|
+
constructor() {
|
|
586
|
+
super("path traversal attempt detected");
|
|
587
|
+
this.name = "PathTraversalError";
|
|
588
|
+
Object.setPrototypeOf(this, _PathTraversalError.prototype);
|
|
823
589
|
}
|
|
824
|
-
} catch {
|
|
825
|
-
}
|
|
826
|
-
}
|
|
827
|
-
return false;
|
|
828
|
-
}
|
|
829
|
-
var init_readClaudeConfig = __esm({
|
|
830
|
-
"src/installers/lib/readClaudeConfig.ts"() {
|
|
831
|
-
}
|
|
832
|
-
});
|
|
833
|
-
|
|
834
|
-
// src/cli/lib/check-mcp-availability.ts
|
|
835
|
-
var check_mcp_availability_exports = {};
|
|
836
|
-
__export(check_mcp_availability_exports, {
|
|
837
|
-
checkMcpAvailability: () => checkMcpAvailability
|
|
838
|
-
});
|
|
839
|
-
async function checkMcpAvailability() {
|
|
840
|
-
const installed = [];
|
|
841
|
-
const missing = [];
|
|
842
|
-
for (const s of TARGET_SERVERS) {
|
|
843
|
-
const present = await isMcpServerRegistered(s);
|
|
844
|
-
if (present) {
|
|
845
|
-
installed.push(s);
|
|
846
|
-
} else {
|
|
847
|
-
missing.push(s);
|
|
848
|
-
}
|
|
849
|
-
}
|
|
850
|
-
if (missing.length === 0) {
|
|
851
|
-
return {
|
|
852
|
-
name: "MCP servers (tavily/exa/chrome-devtools)",
|
|
853
|
-
status: "pass",
|
|
854
|
-
message: `all 3 installed: ${installed.join(", ")}`
|
|
855
|
-
};
|
|
856
|
-
}
|
|
857
|
-
if (installed.length === 0) {
|
|
858
|
-
return {
|
|
859
|
-
name: "MCP servers (tavily/exa/chrome-devtools)",
|
|
860
|
-
status: "warn",
|
|
861
|
-
message: "none of 3 target MCP servers registered in ~/.claude.json",
|
|
862
|
-
fix: "run `harnessed setup` to install via Step B (manifests/tools/{tavily,exa,chrome-devtools}-mcp.yaml)"
|
|
863
590
|
};
|
|
864
591
|
}
|
|
865
|
-
return {
|
|
866
|
-
name: "MCP servers (tavily/exa/chrome-devtools)",
|
|
867
|
-
status: "warn",
|
|
868
|
-
message: `${installed.length}/3 installed: ${installed.join(", ")}; missing: ${missing.join(", ")}`,
|
|
869
|
-
fix: "run `harnessed setup` to install missing MCPs via Step B"
|
|
870
|
-
};
|
|
871
|
-
}
|
|
872
|
-
var TARGET_SERVERS;
|
|
873
|
-
var init_check_mcp_availability = __esm({
|
|
874
|
-
"src/cli/lib/check-mcp-availability.ts"() {
|
|
875
|
-
init_readClaudeConfig();
|
|
876
|
-
TARGET_SERVERS = ["tavily-mcp", "exa-mcp", "chrome-devtools-mcp"];
|
|
877
|
-
}
|
|
878
|
-
});
|
|
879
|
-
|
|
880
|
-
// src/cli/lib/doctor-registry.ts
|
|
881
|
-
var CHECKS;
|
|
882
|
-
var init_doctor_registry = __esm({
|
|
883
|
-
"src/cli/lib/doctor-registry.ts"() {
|
|
884
|
-
init_check_builtin();
|
|
885
|
-
CHECKS = [
|
|
886
|
-
async () => checkNodeVersion(),
|
|
887
|
-
checkMcpScope,
|
|
888
|
-
async () => checkJq(),
|
|
889
|
-
async () => checkWinBash(),
|
|
890
|
-
async () => {
|
|
891
|
-
const { checkOrigin: checkOrigin2 } = await Promise.resolve().then(() => (init_origin_check(), origin_check_exports));
|
|
892
|
-
const r = checkOrigin2(process.cwd(), { allowFork: true });
|
|
893
|
-
return { name: "origin URL", status: r.status, message: r.detail, fix: r.fix };
|
|
894
|
-
},
|
|
895
|
-
async () => {
|
|
896
|
-
const { probeGstackPrefix: probeGstackPrefix2 } = await Promise.resolve().then(() => (init_probe_gstack(), probe_gstack_exports));
|
|
897
|
-
const r = probeGstackPrefix2();
|
|
898
|
-
return { name: "gstack prefix", status: r.status, message: r.detail, fix: r.fix };
|
|
899
|
-
},
|
|
900
|
-
async () => (await Promise.resolve().then(() => (init_check_deprecations(), check_deprecations_exports))).checkDeprecations(),
|
|
901
|
-
async () => (await Promise.resolve().then(() => (init_check_token_budget(), check_token_budget_exports))).checkTokenBudget(),
|
|
902
|
-
async () => (await Promise.resolve().then(() => (init_check_agent_teams_doctor(), check_agent_teams_doctor_exports))).checkAgentTeamsDoctor(),
|
|
903
|
-
async () => (await Promise.resolve().then(() => (init_check_planning_with_files(), check_planning_with_files_exports))).checkPlanningWithFiles(),
|
|
904
|
-
async () => (await Promise.resolve().then(() => (init_check_mattpocock_skills(), check_mattpocock_skills_exports))).checkMattpocockSkills(),
|
|
905
|
-
async () => (await Promise.resolve().then(() => (init_check_mcp_availability(), check_mcp_availability_exports))).checkMcpAvailability()
|
|
906
|
-
];
|
|
907
|
-
}
|
|
908
592
|
});
|
|
909
|
-
var
|
|
910
|
-
var
|
|
911
|
-
"src/manifest/schema/
|
|
593
|
+
var AliasEntryV1, AliasesV1;
|
|
594
|
+
var init_aliases_v1 = __esm({
|
|
595
|
+
"src/manifest/schema/aliases.v1.ts"() {
|
|
912
596
|
init_schemaVersion();
|
|
913
|
-
|
|
597
|
+
AliasEntryV1 = Type.Object(
|
|
914
598
|
{
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
599
|
+
redirect: Type.String({ minLength: 1 }),
|
|
600
|
+
reason: Type.String({ minLength: 1, maxLength: 500 }),
|
|
601
|
+
// DOS cap sister governance.ts
|
|
602
|
+
since_version: Type.String({ pattern: "^\\d+\\.\\d+\\.\\d+$" }),
|
|
603
|
+
// semver strict
|
|
604
|
+
deprecation_date: Type.String({ pattern: "^\\d{4}-\\d{2}-\\d{2}$" }),
|
|
605
|
+
// ISO-date Phase 3.2 W2 Rule 1
|
|
606
|
+
removal_date: Type.Optional(Type.String({ pattern: "^\\d{4}-\\d{2}-\\d{2}$" }))
|
|
607
|
+
// optional long-tail window
|
|
919
608
|
},
|
|
920
609
|
{ additionalProperties: false }
|
|
921
610
|
);
|
|
922
|
-
|
|
611
|
+
AliasesV1 = Type.Object(
|
|
923
612
|
{
|
|
924
|
-
schemaVersion: Type.Literal(SCHEMA_VERSIONS.
|
|
925
|
-
// 'harnessed.
|
|
926
|
-
|
|
927
|
-
// semver strict
|
|
928
|
-
e2e_verified_at: Type.String({ pattern: "^\\d{4}-\\d{2}-\\d{2}$" }),
|
|
929
|
-
// ISO date pattern
|
|
930
|
-
upstreams: Type.Array(PinnedUpstream)
|
|
613
|
+
schemaVersion: Type.Literal(SCHEMA_VERSIONS.aliases),
|
|
614
|
+
// 'harnessed.aliases.v1'
|
|
615
|
+
aliases: Type.Record(Type.String({ minLength: 1 }), AliasEntryV1)
|
|
931
616
|
},
|
|
932
617
|
{ additionalProperties: false }
|
|
933
618
|
);
|
|
934
619
|
}
|
|
935
620
|
});
|
|
936
621
|
|
|
937
|
-
// src/manifest/
|
|
938
|
-
var
|
|
939
|
-
__export(
|
|
940
|
-
|
|
941
|
-
|
|
622
|
+
// src/manifest/aliases.ts
|
|
623
|
+
var aliases_exports = {};
|
|
624
|
+
__export(aliases_exports, {
|
|
625
|
+
listDeprecations: () => listDeprecations,
|
|
626
|
+
loadAliases: () => loadAliases,
|
|
627
|
+
resolveAlias: () => resolveAlias
|
|
942
628
|
});
|
|
943
|
-
function
|
|
944
|
-
if (
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
_cache.set(harnessedVer, null);
|
|
948
|
-
return null;
|
|
949
|
-
}
|
|
950
|
-
const raw = readFileSync(path, "utf8");
|
|
629
|
+
function loadAliases() {
|
|
630
|
+
if (_cached) return _cached;
|
|
631
|
+
if (!existsSync(ALIASES_PATH)) return null;
|
|
632
|
+
const raw = readFileSync(ALIASES_PATH, "utf8");
|
|
951
633
|
const parsed = parse(raw);
|
|
952
|
-
if (!Value.Check(
|
|
953
|
-
const errs = [...Value.Errors(
|
|
634
|
+
if (!Value.Check(AliasesV1, parsed)) {
|
|
635
|
+
const errs = [...Value.Errors(AliasesV1, parsed)].slice(0, 3);
|
|
954
636
|
throw new Error(
|
|
955
|
-
|
|
637
|
+
`aliases.yaml schema invalid: ${errs.map((e) => `${e.path} ${e.message}`).join("; ")}`
|
|
956
638
|
);
|
|
957
639
|
}
|
|
958
|
-
|
|
640
|
+
_cached = parsed;
|
|
959
641
|
return parsed;
|
|
960
642
|
}
|
|
961
|
-
function
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
const entry = kg.upstreams.find((u) => u.name === upstreamName);
|
|
965
|
-
return entry?.version ?? null;
|
|
643
|
+
function resolveAlias(name) {
|
|
644
|
+
checkPathSafe(name);
|
|
645
|
+
return loadAliases()?.aliases?.[name]?.redirect ?? null;
|
|
966
646
|
}
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
647
|
+
function listDeprecations() {
|
|
648
|
+
const a = loadAliases();
|
|
649
|
+
return a ? Object.entries(a.aliases).map(([old, entry]) => ({ old, entry })) : [];
|
|
650
|
+
}
|
|
651
|
+
var ALIASES_PATH, _cached;
|
|
652
|
+
var init_aliases = __esm({
|
|
653
|
+
"src/manifest/aliases.ts"() {
|
|
654
|
+
init_path_guard();
|
|
655
|
+
init_aliases_v1();
|
|
656
|
+
ALIASES_PATH = join(process.cwd(), "manifests", "aliases.yaml");
|
|
657
|
+
_cached = null;
|
|
973
658
|
}
|
|
974
659
|
});
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
}
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
}
|
|
981
|
-
function
|
|
982
|
-
return {
|
|
983
|
-
stale: 1e4,
|
|
984
|
-
retries: { retries: 3, factor: 2, minTimeout: 100 },
|
|
985
|
-
lockfilePath: harnessedFile(".lock")
|
|
986
|
-
};
|
|
987
|
-
}
|
|
988
|
-
async function withLock(fn) {
|
|
989
|
-
const target = lockTarget();
|
|
990
|
-
await mkdir(target, { recursive: true });
|
|
991
|
-
let release;
|
|
660
|
+
|
|
661
|
+
// src/cli/lib/check-deprecations.ts
|
|
662
|
+
var check_deprecations_exports = {};
|
|
663
|
+
__export(check_deprecations_exports, {
|
|
664
|
+
checkDeprecations: () => checkDeprecations
|
|
665
|
+
});
|
|
666
|
+
function checkDeprecations() {
|
|
992
667
|
try {
|
|
993
|
-
|
|
668
|
+
const deprecations = listDeprecations();
|
|
669
|
+
if (deprecations.length === 0) {
|
|
670
|
+
return { name: "deprecated manifests", status: "pass", message: "no deprecated manifests" };
|
|
671
|
+
}
|
|
672
|
+
const lines = deprecations.map(({ old, entry }) => {
|
|
673
|
+
const removal = entry.removal_date ? `, removes ${entry.removal_date}` : "";
|
|
674
|
+
return ` '${old}' \u2192 '${entry.redirect}' (since ${entry.since_version}, ${entry.deprecation_date}${removal}; ${entry.reason})`;
|
|
675
|
+
});
|
|
676
|
+
return {
|
|
677
|
+
name: "deprecated manifests",
|
|
678
|
+
status: "warn",
|
|
679
|
+
message: `${deprecations.length} deprecated manifest(s):
|
|
680
|
+
${lines.join("\n")}`,
|
|
681
|
+
fix: "install paths auto-redirect; consider migrating manifest references to new names"
|
|
682
|
+
};
|
|
994
683
|
} catch (e) {
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
await release?.();
|
|
684
|
+
return {
|
|
685
|
+
name: "deprecated manifests",
|
|
686
|
+
status: "fail",
|
|
687
|
+
message: `aliases.yaml load error: ${e.message}`,
|
|
688
|
+
fix: "verify manifests/aliases.yaml schema (see docs/PROJECT-SPEC.md)"
|
|
689
|
+
};
|
|
1002
690
|
}
|
|
1003
691
|
}
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
raw = await readFile(statePath2(), "utf8");
|
|
1008
|
-
} catch {
|
|
1009
|
-
return null;
|
|
1010
|
-
}
|
|
1011
|
-
let parsed;
|
|
1012
|
-
try {
|
|
1013
|
-
parsed = JSON.parse(raw);
|
|
1014
|
-
} catch {
|
|
1015
|
-
return null;
|
|
692
|
+
var init_check_deprecations = __esm({
|
|
693
|
+
"src/cli/lib/check-deprecations.ts"() {
|
|
694
|
+
init_aliases();
|
|
1016
695
|
}
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
696
|
+
});
|
|
697
|
+
|
|
698
|
+
// src/cli/lib/check-token-budget.ts
|
|
699
|
+
var check_token_budget_exports = {};
|
|
700
|
+
__export(check_token_budget_exports, {
|
|
701
|
+
checkTokenBudget: () => checkTokenBudget
|
|
702
|
+
});
|
|
703
|
+
function scanSkillsDir(root) {
|
|
704
|
+
if (!existsSync(root)) return [];
|
|
705
|
+
return readdirSync(root).flatMap((name) => {
|
|
706
|
+
const md = join(root, name, "SKILL.md");
|
|
707
|
+
if (!existsSync(md)) return [];
|
|
708
|
+
const fm = readFileSync(md, "utf8").match(/^---\n([\s\S]*?)\n---/)?.[1] ?? "";
|
|
709
|
+
const desc = fm.match(/^description:\s*(.+)$/m)?.[1] ?? "";
|
|
710
|
+
return [{ name, tokens: estimateTokens(desc) }];
|
|
1021
711
|
});
|
|
1022
712
|
}
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
713
|
+
function checkTokenBudget() {
|
|
714
|
+
const roots = [join(homedir(), ".claude", "skills"), join(process.cwd(), "skills")];
|
|
715
|
+
const items = roots.flatMap(scanSkillsDir);
|
|
716
|
+
const total = items.reduce((s, i) => s + i.tokens, 0);
|
|
717
|
+
const over = items.filter((i) => i.tokens > PER_SKILL_THRESHOLD).length;
|
|
718
|
+
if (total <= TOTAL_THRESHOLD && over === 0) {
|
|
719
|
+
const msg = `${items.length} skill(s) total ${total} tokens (under 1% / 2000 threshold)`;
|
|
720
|
+
return { name: "token budget", status: "pass", message: msg };
|
|
1027
721
|
}
|
|
1028
|
-
const
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
phase,
|
|
1038
|
-
status: "active",
|
|
1039
|
-
last_checkpoint_path: checkpointPath,
|
|
1040
|
-
started_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
1041
|
-
});
|
|
1042
|
-
}
|
|
1043
|
-
async function pause() {
|
|
1044
|
-
const s = await readCurrentWorkflow();
|
|
1045
|
-
if (!s) return;
|
|
1046
|
-
await writeCurrentWorkflow({ ...s, status: "paused", paused_at: (/* @__PURE__ */ new Date()).toISOString() });
|
|
722
|
+
const top = [...items].sort((a, b) => b.tokens - a.tokens).slice(0, 3).map((t2) => `${t2.name}:${t2.tokens}`).join(", ");
|
|
723
|
+
const pct = (total / CONTEXT_WINDOW_TOKENS * 100).toFixed(2);
|
|
724
|
+
const message = `${items.length} skill(s) total ${total} tokens (${pct}% of 200000) \u2014 top: ${top}`;
|
|
725
|
+
return {
|
|
726
|
+
name: "token budget",
|
|
727
|
+
status: "warn",
|
|
728
|
+
message,
|
|
729
|
+
fix: "shorten verbose skill descriptions OR review per-skill > 5000 tokens"
|
|
730
|
+
};
|
|
1047
731
|
}
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
732
|
+
var CONTEXT_WINDOW_TOKENS, TOTAL_THRESHOLD, PER_SKILL_THRESHOLD;
|
|
733
|
+
var init_check_token_budget = __esm({
|
|
734
|
+
"src/cli/lib/check-token-budget.ts"() {
|
|
735
|
+
init_template();
|
|
736
|
+
CONTEXT_WINDOW_TOKENS = 2e5;
|
|
737
|
+
TOTAL_THRESHOLD = 2e3;
|
|
738
|
+
PER_SKILL_THRESHOLD = 5e3;
|
|
739
|
+
}
|
|
740
|
+
});
|
|
741
|
+
async function checkAgentTeams() {
|
|
742
|
+
const envValue = process.env.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS;
|
|
743
|
+
const envOn = envValue === "1";
|
|
744
|
+
let settingsValue;
|
|
745
|
+
let settingsOn = false;
|
|
746
|
+
try {
|
|
747
|
+
const path = resolve(homedir(), ".claude", "settings.json");
|
|
748
|
+
const raw = await readFile(path, "utf8");
|
|
749
|
+
const data = JSON.parse(raw);
|
|
750
|
+
settingsValue = data.env?.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS;
|
|
751
|
+
settingsOn = settingsValue === "1";
|
|
752
|
+
} catch {
|
|
753
|
+
}
|
|
754
|
+
const detected = { env: envOn, settingsJson: settingsOn };
|
|
755
|
+
if (envOn || settingsOn) {
|
|
756
|
+
return { status: "pass", detected, envValue, settingsValue };
|
|
757
|
+
}
|
|
758
|
+
return {
|
|
759
|
+
status: "missing",
|
|
760
|
+
detected,
|
|
761
|
+
envValue,
|
|
762
|
+
settingsValue,
|
|
763
|
+
remediation: 'Agent Teams not enabled. Add to ~/.claude/settings.json:\n "env": { "CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS": "1" }\nOR run: claude config set env.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS 1\nOR export env var:\n export CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1\nThen restart Claude Code (CC >= 2.1.133 required).'
|
|
764
|
+
};
|
|
1052
765
|
}
|
|
1053
|
-
var
|
|
1054
|
-
|
|
1055
|
-
"src/checkpoint/state.ts"() {
|
|
1056
|
-
init_harnessedRoot();
|
|
1057
|
-
init_schemaVersion();
|
|
1058
|
-
init_schema();
|
|
1059
|
-
WorkflowStateError = class extends Error {
|
|
1060
|
-
constructor(message) {
|
|
1061
|
-
super(message);
|
|
1062
|
-
this.name = "WorkflowStateError";
|
|
1063
|
-
}
|
|
1064
|
-
};
|
|
1065
|
-
LockHeldError = class _LockHeldError extends Error {
|
|
1066
|
-
constructor() {
|
|
1067
|
-
super(
|
|
1068
|
-
`another harnessed process holds the lock at ${harnessedFile(".lock")} \u2014 wait or kill stale process (try: harnessed status)`
|
|
1069
|
-
);
|
|
1070
|
-
this.name = "LockHeldError";
|
|
1071
|
-
Object.setPrototypeOf(this, _LockHeldError.prototype);
|
|
1072
|
-
}
|
|
1073
|
-
};
|
|
766
|
+
var init_checkAgentTeams = __esm({
|
|
767
|
+
"src/cli/lib/checkAgentTeams.ts"() {
|
|
1074
768
|
}
|
|
1075
769
|
});
|
|
1076
770
|
|
|
1077
|
-
// src/
|
|
1078
|
-
var
|
|
1079
|
-
__export(
|
|
1080
|
-
|
|
771
|
+
// src/cli/lib/check-agent-teams-doctor.ts
|
|
772
|
+
var check_agent_teams_doctor_exports = {};
|
|
773
|
+
__export(check_agent_teams_doctor_exports, {
|
|
774
|
+
checkAgentTeamsDoctor: () => checkAgentTeamsDoctor
|
|
1081
775
|
});
|
|
1082
|
-
async function
|
|
1083
|
-
const
|
|
1084
|
-
if (
|
|
1085
|
-
|
|
1086
|
-
status: "no-paused-phase",
|
|
1087
|
-
error: "no current-workflow.json found under <harnessed-root>"
|
|
1088
|
-
};
|
|
1089
|
-
}
|
|
1090
|
-
if (current.status !== "paused") {
|
|
776
|
+
async function checkAgentTeamsDoctor() {
|
|
777
|
+
const r = await checkAgentTeams();
|
|
778
|
+
if (r.status === "pass") {
|
|
779
|
+
const source = r.detected.env ? "env var" : "settings.json";
|
|
1091
780
|
return {
|
|
1092
|
-
|
|
1093
|
-
|
|
781
|
+
name: "Agent Teams env",
|
|
782
|
+
status: "pass",
|
|
783
|
+
message: `CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1 (${source})`
|
|
1094
784
|
};
|
|
1095
785
|
}
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
786
|
+
return {
|
|
787
|
+
name: "Agent Teams env",
|
|
788
|
+
status: "warn",
|
|
789
|
+
message: "CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS not set (Agent Teams disabled)",
|
|
790
|
+
fix: r.remediation
|
|
791
|
+
};
|
|
792
|
+
}
|
|
793
|
+
var init_check_agent_teams_doctor = __esm({
|
|
794
|
+
"src/cli/lib/check-agent-teams-doctor.ts"() {
|
|
795
|
+
init_checkAgentTeams();
|
|
1105
796
|
}
|
|
1106
|
-
|
|
797
|
+
});
|
|
798
|
+
|
|
799
|
+
// src/cli/lib/check-planning-with-files.ts
|
|
800
|
+
var check_planning_with_files_exports = {};
|
|
801
|
+
__export(check_planning_with_files_exports, {
|
|
802
|
+
checkPlanningWithFiles: () => checkPlanningWithFiles
|
|
803
|
+
});
|
|
804
|
+
async function checkPlanningWithFiles() {
|
|
805
|
+
const root = join(
|
|
806
|
+
homedir(),
|
|
807
|
+
".claude",
|
|
808
|
+
"plugins",
|
|
809
|
+
"cache",
|
|
810
|
+
"planning-with-files",
|
|
811
|
+
"planning-with-files"
|
|
812
|
+
);
|
|
1107
813
|
try {
|
|
1108
|
-
|
|
1109
|
-
|
|
814
|
+
const entries = await readdir(root);
|
|
815
|
+
const versions = entries.filter((e) => /^\d+\.\d+/.test(e));
|
|
816
|
+
if (versions.length > 0) {
|
|
817
|
+
return {
|
|
818
|
+
name: "planning-with-files plugin",
|
|
819
|
+
status: "pass",
|
|
820
|
+
message: `installed (version ${versions.join(", ")})`
|
|
821
|
+
};
|
|
822
|
+
}
|
|
1110
823
|
return {
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
824
|
+
name: "planning-with-files plugin",
|
|
825
|
+
status: "warn",
|
|
826
|
+
message: "plugin directory exists but no version subdir found",
|
|
827
|
+
fix: REMEDIATION,
|
|
828
|
+
install_commands: INSTALL_COMMANDS
|
|
829
|
+
};
|
|
830
|
+
} catch {
|
|
831
|
+
return {
|
|
832
|
+
name: "planning-with-files plugin",
|
|
833
|
+
status: "warn",
|
|
834
|
+
message: "not installed (plugin cache path missing)",
|
|
835
|
+
fix: REMEDIATION,
|
|
836
|
+
install_commands: INSTALL_COMMANDS
|
|
1114
837
|
};
|
|
1115
838
|
}
|
|
1116
|
-
const v = parsed.schemaVersion ?? "";
|
|
1117
|
-
const validated = branchOnSchemaVersion(v, {
|
|
1118
|
-
v1: () => Value.Check(CheckpointV1, parsed) ? parsed : null,
|
|
1119
|
-
unknown: () => null
|
|
1120
|
-
});
|
|
1121
|
-
if (!validated) {
|
|
1122
|
-
const errs = [...Value.Errors(CheckpointV1, parsed)].map((e) => e.message).join("; ");
|
|
1123
|
-
return { status: "corrupt", error: `checkpoint schema validation failed: ${errs}`, path };
|
|
1124
|
-
}
|
|
1125
|
-
const cwd = process.cwd();
|
|
1126
|
-
const cwdWarn = validated.cwd !== cwd ? `\u26A0 checkpoint cwd '${validated.cwd}' \u2260 current cwd '${cwd}' \u2014 SDK session resume may fail (\xA7 1.3); fresh-session fallback` : void 0;
|
|
1127
|
-
const sidHint = validated.session_id ? ` (session_id: ${validated.session_id} \u2014 SDK will redirect to original session)` : " (fresh session \u2014 context reloaded from checkpoint)";
|
|
1128
|
-
const resumeHint = `\u2192 in Claude Code: /gsd-execute-phase ${validated.phase}${sidHint}`;
|
|
1129
|
-
return { status: "ok", checkpoint: validated, ...cwdWarn ? { cwdWarn } : {}, resumeHint };
|
|
1130
839
|
}
|
|
1131
|
-
var
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
840
|
+
var REMEDIATION, INSTALL_COMMANDS;
|
|
841
|
+
var init_check_planning_with_files = __esm({
|
|
842
|
+
"src/cli/lib/check-planning-with-files.ts"() {
|
|
843
|
+
REMEDIATION = "install via `claude plugin marketplace add OthmanAdi/planning-with-files && claude plugin install planning-with-files` (requires >=2.2.0 per R20.15 + D-15)";
|
|
844
|
+
INSTALL_COMMANDS = [
|
|
845
|
+
"claude plugin marketplace add OthmanAdi/planning-with-files",
|
|
846
|
+
"claude plugin install planning-with-files"
|
|
847
|
+
];
|
|
1136
848
|
}
|
|
1137
849
|
});
|
|
1138
850
|
|
|
1139
|
-
// src/cli/lib/
|
|
1140
|
-
var
|
|
1141
|
-
__export(
|
|
1142
|
-
|
|
851
|
+
// src/cli/lib/check-mattpocock-skills.ts
|
|
852
|
+
var check_mattpocock_skills_exports = {};
|
|
853
|
+
__export(check_mattpocock_skills_exports, {
|
|
854
|
+
checkMattpocockSkills: () => checkMattpocockSkills
|
|
1143
855
|
});
|
|
1144
|
-
async function
|
|
1145
|
-
const
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
);
|
|
1153
|
-
if (installables.length === 0) {
|
|
1154
|
-
return out;
|
|
1155
|
-
}
|
|
1156
|
-
console.log(
|
|
1157
|
-
`
|
|
1158
|
-
\u{1F4A1} ${installables.length} optional check(s) installable \u2014 harnessed can run install commands now:`
|
|
856
|
+
async function checkMattpocockSkills() {
|
|
857
|
+
const pluginRoot = join(
|
|
858
|
+
homedir(),
|
|
859
|
+
".claude",
|
|
860
|
+
"plugins",
|
|
861
|
+
"cache",
|
|
862
|
+
"mattpocock-skills",
|
|
863
|
+
"mattpocock-skills"
|
|
1159
864
|
);
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
const ans = await p.confirm({
|
|
1171
|
-
message: `Run ${commands.length} install command(s) for "${check.name}"?`,
|
|
1172
|
-
initialValue: true
|
|
1173
|
-
});
|
|
1174
|
-
if (p.isCancel(ans) || ans !== true) {
|
|
1175
|
-
out.skipped.push(check.name);
|
|
1176
|
-
continue;
|
|
1177
|
-
}
|
|
1178
|
-
const failureReasons = [];
|
|
1179
|
-
for (const cmd of commands) {
|
|
1180
|
-
const tokens = cmd.split(/\s+/).filter((t2) => t2.length > 0);
|
|
1181
|
-
const exe = tokens[0];
|
|
1182
|
-
const args = tokens.slice(1);
|
|
1183
|
-
if (exe === void 0) {
|
|
1184
|
-
failureReasons.push(`empty command in install_commands`);
|
|
1185
|
-
continue;
|
|
1186
|
-
}
|
|
1187
|
-
const r = spawnSync(exe, args, {
|
|
1188
|
-
encoding: "utf8",
|
|
1189
|
-
stdio: "inherit",
|
|
1190
|
-
// Windows needs shell for `.cmd` / `.bat` exes (npx.cmd / claude.cmd
|
|
1191
|
-
// shims); Unix is fine either way. Pass-through to OS shell handles
|
|
1192
|
-
// PATH resolution + extension lookup.
|
|
1193
|
-
shell: true
|
|
1194
|
-
});
|
|
1195
|
-
if (r.status !== 0) {
|
|
1196
|
-
const reason = r.error !== void 0 ? `spawn error on \`${cmd}\`: ${r.error.message}` : `exit ${r.status ?? "<unknown>"} on \`${cmd}\``;
|
|
1197
|
-
failureReasons.push(reason);
|
|
1198
|
-
console.error(` \u2717 ${cmd} \u2014 ${reason}`);
|
|
1199
|
-
}
|
|
865
|
+
const skillRoot = join(homedir(), ".claude", "skills", "mattpocock-skills");
|
|
866
|
+
try {
|
|
867
|
+
const entries = await readdir(pluginRoot);
|
|
868
|
+
const versions = entries.filter((e) => /^\d+\.\d+/.test(e));
|
|
869
|
+
if (versions.length > 0) {
|
|
870
|
+
return {
|
|
871
|
+
name: "mattpocock-skills",
|
|
872
|
+
status: "pass",
|
|
873
|
+
message: `installed as plugin (version ${versions.join(", ")})`
|
|
874
|
+
};
|
|
1200
875
|
}
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
})
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
876
|
+
} catch {
|
|
877
|
+
}
|
|
878
|
+
try {
|
|
879
|
+
await stat(skillRoot);
|
|
880
|
+
return {
|
|
881
|
+
name: "mattpocock-skills",
|
|
882
|
+
status: "pass",
|
|
883
|
+
message: `installed as user-skill (${skillRoot})`
|
|
884
|
+
};
|
|
885
|
+
} catch {
|
|
886
|
+
}
|
|
887
|
+
const indicators = ["diagnose", "tdd", "zoom-out"];
|
|
888
|
+
for (const ind of indicators) {
|
|
889
|
+
const indPath = join(homedir(), ".claude", "skills", ind);
|
|
890
|
+
try {
|
|
891
|
+
await stat(indPath);
|
|
892
|
+
return {
|
|
893
|
+
name: "mattpocock-skills",
|
|
894
|
+
status: "pass",
|
|
895
|
+
message: `installed as individual skills (found ${ind}/)`
|
|
896
|
+
};
|
|
897
|
+
} catch {
|
|
1218
898
|
}
|
|
1219
899
|
}
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
900
|
+
return {
|
|
901
|
+
name: "mattpocock-skills",
|
|
902
|
+
status: "warn",
|
|
903
|
+
message: "not installed (plugin cache + user-skill paths both missing)",
|
|
904
|
+
fix: REMEDIATION2,
|
|
905
|
+
install_commands: INSTALL_COMMANDS2
|
|
906
|
+
};
|
|
1225
907
|
}
|
|
1226
|
-
var
|
|
1227
|
-
|
|
1228
|
-
|
|
908
|
+
var REMEDIATION2, INSTALL_COMMANDS2;
|
|
909
|
+
var init_check_mattpocock_skills = __esm({
|
|
910
|
+
"src/cli/lib/check-mattpocock-skills.ts"() {
|
|
911
|
+
REMEDIATION2 = "install via `npx skills@latest add mattpocock/skills` (or git clone https://github.com/mattpocock/skills ~/.claude/skills/mattpocock-skills); methodology fallback already inline in role-prompts.yaml per v3.6.0 Phase 1 \u2014 install is optional but enables /grill-with-docs /zoom-out etc. SlashCommand acceleration";
|
|
912
|
+
INSTALL_COMMANDS2 = ["npx skills@latest add mattpocock/skills"];
|
|
1229
913
|
}
|
|
1230
914
|
});
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
var package_default = {
|
|
1234
|
-
version: "3.9.25"};
|
|
1235
|
-
|
|
1236
|
-
// src/manifest/errors.ts
|
|
1237
|
-
function instancePathToKeyPath(instancePath) {
|
|
1238
|
-
if (!instancePath || instancePath === "/") return [];
|
|
1239
|
-
return instancePath.split("/").filter(Boolean).map((seg) => {
|
|
1240
|
-
const n = Number(seg);
|
|
1241
|
-
return Number.isInteger(n) && String(n) === seg ? n : seg;
|
|
1242
|
-
});
|
|
915
|
+
function getUserClaudeJsonPath() {
|
|
916
|
+
return join(homedir(), ".claude.json");
|
|
1243
917
|
}
|
|
1244
|
-
function
|
|
1245
|
-
const path =
|
|
1246
|
-
let
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
}
|
|
1250
|
-
|
|
918
|
+
async function readUserClaudeJson() {
|
|
919
|
+
const path = getUserClaudeJsonPath();
|
|
920
|
+
let raw;
|
|
921
|
+
try {
|
|
922
|
+
raw = await readFile(path, "utf8");
|
|
923
|
+
} catch (err2) {
|
|
924
|
+
if (err2.code === "ENOENT") return {};
|
|
925
|
+
throw err2;
|
|
926
|
+
}
|
|
927
|
+
try {
|
|
928
|
+
const parsed = JSON.parse(raw);
|
|
929
|
+
if (parsed === null || typeof parsed !== "object") return {};
|
|
930
|
+
return parsed;
|
|
931
|
+
} catch {
|
|
932
|
+
return {};
|
|
1251
933
|
}
|
|
1252
|
-
if (!node || typeof node !== "object") return { line: null, column: null };
|
|
1253
|
-
const range = node.range;
|
|
1254
|
-
if (!range) return { line: null, column: null };
|
|
1255
|
-
const offset = range[0];
|
|
1256
|
-
const pos = lineCounter.linePos(offset);
|
|
1257
|
-
return { line: pos.line, column: pos.col };
|
|
1258
934
|
}
|
|
1259
|
-
function
|
|
1260
|
-
|
|
1261
|
-
const
|
|
1262
|
-
if (
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
935
|
+
async function isMcpServerRegistered(name) {
|
|
936
|
+
const config = await readUserClaudeJson();
|
|
937
|
+
const servers = config.mcpServers;
|
|
938
|
+
if (!servers || typeof servers !== "object") return false;
|
|
939
|
+
return Object.hasOwn(servers, name);
|
|
940
|
+
}
|
|
941
|
+
async function isPluginRegistered(pluginName) {
|
|
942
|
+
try {
|
|
943
|
+
const path = join(homedir(), ".claude", "plugins", "installed_plugins.json");
|
|
944
|
+
const raw = await readFile(path, "utf8");
|
|
945
|
+
const parsed = JSON.parse(raw);
|
|
946
|
+
const plugins = parsed.plugins;
|
|
947
|
+
if (plugins && typeof plugins === "object") {
|
|
948
|
+
if (Object.hasOwn(plugins, pluginName)) return true;
|
|
949
|
+
if (Object.keys(plugins).some((k) => k.split("@")[0] === pluginName)) return true;
|
|
950
|
+
}
|
|
951
|
+
} catch {
|
|
1266
952
|
}
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
953
|
+
for (const path of [
|
|
954
|
+
join(homedir(), ".claude", "settings.json"),
|
|
955
|
+
join(homedir(), ".claude.json")
|
|
956
|
+
]) {
|
|
957
|
+
try {
|
|
958
|
+
const raw = await readFile(path, "utf8");
|
|
959
|
+
const parsed = JSON.parse(raw);
|
|
960
|
+
const plugins = parsed.enabledPlugins;
|
|
961
|
+
if (plugins && typeof plugins === "object") {
|
|
962
|
+
if (Object.hasOwn(plugins, pluginName)) return true;
|
|
963
|
+
if (Object.keys(plugins).some((k) => k.split("@")[0] === pluginName)) return true;
|
|
964
|
+
}
|
|
965
|
+
} catch {
|
|
966
|
+
}
|
|
1274
967
|
}
|
|
1275
|
-
return
|
|
1276
|
-
file,
|
|
1277
|
-
path,
|
|
1278
|
-
message: err2.message ?? "unknown error",
|
|
1279
|
-
line,
|
|
1280
|
-
column,
|
|
1281
|
-
keyword: err2.keyword
|
|
1282
|
-
};
|
|
1283
|
-
}
|
|
1284
|
-
function yamlParseErrorToFriendly(err2, file) {
|
|
1285
|
-
return {
|
|
1286
|
-
file,
|
|
1287
|
-
path: "/",
|
|
1288
|
-
message: err2.message,
|
|
1289
|
-
line: err2.linePos?.[0]?.line ?? null,
|
|
1290
|
-
column: err2.linePos?.[0]?.col ?? null,
|
|
1291
|
-
keyword: "yaml-parse"
|
|
1292
|
-
};
|
|
968
|
+
return false;
|
|
1293
969
|
}
|
|
1294
|
-
var
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
970
|
+
var init_readClaudeConfig = __esm({
|
|
971
|
+
"src/installers/lib/readClaudeConfig.ts"() {
|
|
972
|
+
}
|
|
973
|
+
});
|
|
974
|
+
|
|
975
|
+
// src/cli/lib/check-mcp-availability.ts
|
|
976
|
+
var check_mcp_availability_exports = {};
|
|
977
|
+
__export(check_mcp_availability_exports, {
|
|
978
|
+
checkMcpAvailability: () => checkMcpAvailability
|
|
979
|
+
});
|
|
980
|
+
async function checkMcpAvailability() {
|
|
981
|
+
const installed = [];
|
|
982
|
+
const missing = [];
|
|
983
|
+
for (const s of TARGET_SERVERS) {
|
|
984
|
+
const present = await isMcpServerRegistered(s);
|
|
985
|
+
if (present) {
|
|
986
|
+
installed.push(s);
|
|
987
|
+
} else {
|
|
988
|
+
missing.push(s);
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
if (missing.length === 0) {
|
|
992
|
+
return {
|
|
993
|
+
name: "MCP servers (tavily/exa/chrome-devtools)",
|
|
994
|
+
status: "pass",
|
|
995
|
+
message: `all 3 installed: ${installed.join(", ")}`
|
|
996
|
+
};
|
|
997
|
+
}
|
|
998
|
+
if (installed.length === 0) {
|
|
999
|
+
return {
|
|
1000
|
+
name: "MCP servers (tavily/exa/chrome-devtools)",
|
|
1001
|
+
status: "warn",
|
|
1002
|
+
message: "none of 3 target MCP servers registered in ~/.claude.json",
|
|
1003
|
+
fix: "run `harnessed setup` to install via Step B (manifests/tools/{tavily,exa,chrome-devtools}-mcp.yaml)"
|
|
1004
|
+
};
|
|
1005
|
+
}
|
|
1006
|
+
return {
|
|
1007
|
+
name: "MCP servers (tavily/exa/chrome-devtools)",
|
|
1008
|
+
status: "warn",
|
|
1009
|
+
message: `${installed.length}/3 installed: ${installed.join(", ")}; missing: ${missing.join(", ")}`,
|
|
1010
|
+
fix: "run `harnessed setup` to install missing MCPs via Step B"
|
|
1011
|
+
};
|
|
1012
|
+
}
|
|
1013
|
+
var TARGET_SERVERS;
|
|
1014
|
+
var init_check_mcp_availability = __esm({
|
|
1015
|
+
"src/cli/lib/check-mcp-availability.ts"() {
|
|
1016
|
+
init_readClaudeConfig();
|
|
1017
|
+
TARGET_SERVERS = ["tavily-mcp", "exa-mcp", "chrome-devtools-mcp"];
|
|
1018
|
+
}
|
|
1019
|
+
});
|
|
1020
|
+
|
|
1021
|
+
// src/cli/lib/doctor-registry.ts
|
|
1022
|
+
var CHECKS;
|
|
1023
|
+
var init_doctor_registry = __esm({
|
|
1024
|
+
"src/cli/lib/doctor-registry.ts"() {
|
|
1025
|
+
init_check_builtin();
|
|
1026
|
+
CHECKS = [
|
|
1027
|
+
async () => checkNodeVersion(),
|
|
1028
|
+
checkMcpScope,
|
|
1029
|
+
async () => checkJq(),
|
|
1030
|
+
async () => checkWinBash(),
|
|
1031
|
+
async () => {
|
|
1032
|
+
const { checkOrigin: checkOrigin2 } = await Promise.resolve().then(() => (init_origin_check(), origin_check_exports));
|
|
1033
|
+
const r = checkOrigin2(process.cwd(), { allowFork: true });
|
|
1034
|
+
return { name: "origin URL", status: r.status, message: r.detail, fix: r.fix };
|
|
1035
|
+
},
|
|
1036
|
+
async () => {
|
|
1037
|
+
const { probeGstackPrefix: probeGstackPrefix2 } = await Promise.resolve().then(() => (init_probe_gstack(), probe_gstack_exports));
|
|
1038
|
+
const r = probeGstackPrefix2();
|
|
1039
|
+
return { name: "gstack prefix", status: r.status, message: r.detail, fix: r.fix };
|
|
1040
|
+
},
|
|
1041
|
+
async () => (await Promise.resolve().then(() => (init_check_deprecations(), check_deprecations_exports))).checkDeprecations(),
|
|
1042
|
+
async () => (await Promise.resolve().then(() => (init_check_token_budget(), check_token_budget_exports))).checkTokenBudget(),
|
|
1043
|
+
async () => (await Promise.resolve().then(() => (init_check_agent_teams_doctor(), check_agent_teams_doctor_exports))).checkAgentTeamsDoctor(),
|
|
1044
|
+
async () => (await Promise.resolve().then(() => (init_check_planning_with_files(), check_planning_with_files_exports))).checkPlanningWithFiles(),
|
|
1045
|
+
async () => (await Promise.resolve().then(() => (init_check_mattpocock_skills(), check_mattpocock_skills_exports))).checkMattpocockSkills(),
|
|
1046
|
+
async () => (await Promise.resolve().then(() => (init_check_mcp_availability(), check_mcp_availability_exports))).checkMcpAvailability()
|
|
1047
|
+
];
|
|
1048
|
+
}
|
|
1049
|
+
});
|
|
1050
|
+
var PinnedUpstream, KnownGoodV1;
|
|
1051
|
+
var init_known_good_v1 = __esm({
|
|
1052
|
+
"src/manifest/schema/known-good.v1.ts"() {
|
|
1053
|
+
init_schemaVersion();
|
|
1054
|
+
PinnedUpstream = Type.Object(
|
|
1055
|
+
{
|
|
1056
|
+
name: Type.String({ minLength: 1 }),
|
|
1057
|
+
version: Type.String({ minLength: 1 }),
|
|
1058
|
+
install_method: Type.String({ minLength: 1 })
|
|
1059
|
+
// npm-cli / mcp-stdio-add / etc per Phase 2.X
|
|
1060
|
+
},
|
|
1061
|
+
{ additionalProperties: false }
|
|
1062
|
+
);
|
|
1063
|
+
KnownGoodV1 = Type.Object(
|
|
1064
|
+
{
|
|
1065
|
+
schemaVersion: Type.Literal(SCHEMA_VERSIONS.knownGood),
|
|
1066
|
+
// 'harnessed.known-good.v1'
|
|
1067
|
+
harnessed_version: Type.String({ pattern: "^\\d+\\.\\d+\\.\\d+$" }),
|
|
1068
|
+
// semver strict
|
|
1069
|
+
e2e_verified_at: Type.String({ pattern: "^\\d{4}-\\d{2}-\\d{2}$" }),
|
|
1070
|
+
// ISO date pattern
|
|
1071
|
+
upstreams: Type.Array(PinnedUpstream)
|
|
1072
|
+
},
|
|
1073
|
+
{ additionalProperties: false }
|
|
1074
|
+
);
|
|
1075
|
+
}
|
|
1076
|
+
});
|
|
1077
|
+
|
|
1078
|
+
// src/manifest/knownGood.ts
|
|
1079
|
+
var knownGood_exports = {};
|
|
1080
|
+
__export(knownGood_exports, {
|
|
1081
|
+
getPinnedVersion: () => getPinnedVersion,
|
|
1082
|
+
loadKnownGood: () => loadKnownGood
|
|
1083
|
+
});
|
|
1084
|
+
function loadKnownGood(harnessedVer) {
|
|
1085
|
+
if (_cache.has(harnessedVer)) return _cache.get(harnessedVer) ?? null;
|
|
1086
|
+
const path = join(versionsDir(), `${harnessedVer}-known-good.yaml`);
|
|
1087
|
+
if (!existsSync(path)) {
|
|
1088
|
+
_cache.set(harnessedVer, null);
|
|
1089
|
+
return null;
|
|
1090
|
+
}
|
|
1091
|
+
const raw = readFileSync(path, "utf8");
|
|
1092
|
+
const parsed = parse(raw);
|
|
1093
|
+
if (!Value.Check(KnownGoodV1, parsed)) {
|
|
1094
|
+
const errs = [...Value.Errors(KnownGoodV1, parsed)].slice(0, 3);
|
|
1095
|
+
throw new Error(
|
|
1096
|
+
`${path} schema invalid: ${errs.map((e) => `${e.path} ${e.message}`).join("; ")}`
|
|
1097
|
+
);
|
|
1098
|
+
}
|
|
1099
|
+
_cache.set(harnessedVer, parsed);
|
|
1100
|
+
return parsed;
|
|
1101
|
+
}
|
|
1102
|
+
function getPinnedVersion(upstreamName, harnessedVer) {
|
|
1103
|
+
const kg = loadKnownGood(harnessedVer);
|
|
1104
|
+
if (!kg) return null;
|
|
1105
|
+
const entry = kg.upstreams.find((u) => u.name === upstreamName);
|
|
1106
|
+
return entry?.version ?? null;
|
|
1107
|
+
}
|
|
1108
|
+
var versionsDir, _cache;
|
|
1109
|
+
var init_knownGood = __esm({
|
|
1110
|
+
"src/manifest/knownGood.ts"() {
|
|
1111
|
+
init_known_good_v1();
|
|
1112
|
+
versionsDir = () => join(process.cwd(), "versions");
|
|
1113
|
+
_cache = /* @__PURE__ */ new Map();
|
|
1114
|
+
}
|
|
1115
|
+
});
|
|
1116
|
+
|
|
1117
|
+
// src/checkpoint/resume.ts
|
|
1118
|
+
var resume_exports = {};
|
|
1119
|
+
__export(resume_exports, {
|
|
1120
|
+
runResume: () => runResume
|
|
1121
|
+
});
|
|
1122
|
+
async function runResume() {
|
|
1123
|
+
const current = await readCurrentWorkflow();
|
|
1124
|
+
if (!current) {
|
|
1125
|
+
return {
|
|
1126
|
+
status: "no-paused-phase",
|
|
1127
|
+
error: "no current-workflow.json found under <harnessed-root>"
|
|
1128
|
+
};
|
|
1129
|
+
}
|
|
1130
|
+
if (current.status !== "paused") {
|
|
1131
|
+
return {
|
|
1132
|
+
status: "no-paused-phase",
|
|
1133
|
+
error: `workflow status is '${current.status}', not 'paused'`
|
|
1134
|
+
};
|
|
1135
|
+
}
|
|
1136
|
+
if (!current.last_checkpoint_path) {
|
|
1137
|
+
return { status: "corrupt", error: "last_checkpoint_path missing", path: "" };
|
|
1138
|
+
}
|
|
1139
|
+
const path = current.last_checkpoint_path;
|
|
1140
|
+
let raw;
|
|
1141
|
+
try {
|
|
1142
|
+
raw = await readFile(path, "utf8");
|
|
1143
|
+
} catch (e) {
|
|
1144
|
+
return { status: "corrupt", error: `checkpoint missing: ${e.message}`, path };
|
|
1145
|
+
}
|
|
1146
|
+
let parsed;
|
|
1147
|
+
try {
|
|
1148
|
+
parsed = JSON.parse(raw);
|
|
1149
|
+
} catch (e) {
|
|
1150
|
+
return {
|
|
1151
|
+
status: "corrupt",
|
|
1152
|
+
error: `checkpoint JSON parse failed: ${e.message}`,
|
|
1153
|
+
path
|
|
1154
|
+
};
|
|
1155
|
+
}
|
|
1156
|
+
const v = parsed.schemaVersion ?? "";
|
|
1157
|
+
const validated = branchOnSchemaVersion(v, {
|
|
1158
|
+
v1: () => Value.Check(CheckpointV1, parsed) ? parsed : null,
|
|
1159
|
+
unknown: () => null
|
|
1160
|
+
});
|
|
1161
|
+
if (!validated) {
|
|
1162
|
+
const errs = [...Value.Errors(CheckpointV1, parsed)].map((e) => e.message).join("; ");
|
|
1163
|
+
return { status: "corrupt", error: `checkpoint schema validation failed: ${errs}`, path };
|
|
1164
|
+
}
|
|
1165
|
+
const cwd = process.cwd();
|
|
1166
|
+
const cwdWarn = validated.cwd !== cwd ? `\u26A0 checkpoint cwd '${validated.cwd}' \u2260 current cwd '${cwd}' \u2014 SDK session resume may fail (\xA7 1.3); fresh-session fallback` : void 0;
|
|
1167
|
+
const sidHint = validated.session_id ? ` (session_id: ${validated.session_id} \u2014 SDK will redirect to original session)` : " (fresh session \u2014 context reloaded from checkpoint)";
|
|
1168
|
+
const resumeHint = `\u2192 in Claude Code: /gsd-execute-phase ${validated.phase}${sidHint}`;
|
|
1169
|
+
return { status: "ok", checkpoint: validated, ...cwdWarn ? { cwdWarn } : {}, resumeHint };
|
|
1170
|
+
}
|
|
1171
|
+
var init_resume = __esm({
|
|
1172
|
+
"src/checkpoint/resume.ts"() {
|
|
1173
|
+
init_schemaVersion();
|
|
1174
|
+
init_schema();
|
|
1175
|
+
init_state();
|
|
1176
|
+
}
|
|
1177
|
+
});
|
|
1178
|
+
|
|
1179
|
+
// src/cli/lib/auto-install.ts
|
|
1180
|
+
var auto_install_exports = {};
|
|
1181
|
+
__export(auto_install_exports, {
|
|
1182
|
+
runAutoInstall: () => runAutoInstall
|
|
1183
|
+
});
|
|
1184
|
+
async function runAutoInstall(opts) {
|
|
1185
|
+
const out = { installed: [], skipped: [], failed: [] };
|
|
1186
|
+
if (!opts.autoInstall) {
|
|
1187
|
+
return out;
|
|
1188
|
+
}
|
|
1189
|
+
const results = await Promise.all(CHECKS.map((c) => c()));
|
|
1190
|
+
const installables = results.filter(
|
|
1191
|
+
(r) => r.status === "warn" && Array.isArray(r.install_commands) && r.install_commands.length > 0
|
|
1192
|
+
);
|
|
1193
|
+
if (installables.length === 0) {
|
|
1194
|
+
return out;
|
|
1195
|
+
}
|
|
1196
|
+
console.log(
|
|
1197
|
+
`
|
|
1198
|
+
\u{1F4A1} ${installables.length} optional check(s) installable \u2014 harnessed can run install commands now:`
|
|
1199
|
+
);
|
|
1200
|
+
for (const check of installables) {
|
|
1201
|
+
const commands = check.install_commands;
|
|
1202
|
+
if (opts.nonInteractive) {
|
|
1203
|
+
out.skipped.push(check.name);
|
|
1204
|
+
continue;
|
|
1205
|
+
}
|
|
1206
|
+
const preview = commands.map((c) => ` $ ${c}`).join("\n");
|
|
1207
|
+
console.log(`
|
|
1208
|
+
${check.name}:`);
|
|
1209
|
+
console.log(preview);
|
|
1210
|
+
const ans = await p.confirm({
|
|
1211
|
+
message: `Run ${commands.length} install command(s) for "${check.name}"?`,
|
|
1212
|
+
initialValue: true
|
|
1213
|
+
});
|
|
1214
|
+
if (p.isCancel(ans) || ans !== true) {
|
|
1215
|
+
out.skipped.push(check.name);
|
|
1216
|
+
continue;
|
|
1217
|
+
}
|
|
1218
|
+
const failureReasons = [];
|
|
1219
|
+
for (const cmd of commands) {
|
|
1220
|
+
const tokens = cmd.split(/\s+/).filter((t2) => t2.length > 0);
|
|
1221
|
+
const exe = tokens[0];
|
|
1222
|
+
const args = tokens.slice(1);
|
|
1223
|
+
if (exe === void 0) {
|
|
1224
|
+
failureReasons.push(`empty command in install_commands`);
|
|
1225
|
+
continue;
|
|
1226
|
+
}
|
|
1227
|
+
const r = spawnSync(exe, args, {
|
|
1228
|
+
encoding: "utf8",
|
|
1229
|
+
stdio: "inherit",
|
|
1230
|
+
// Windows needs shell for `.cmd` / `.bat` exes (npx.cmd / claude.cmd
|
|
1231
|
+
// shims); Unix is fine either way. Pass-through to OS shell handles
|
|
1232
|
+
// PATH resolution + extension lookup.
|
|
1233
|
+
shell: true
|
|
1234
|
+
});
|
|
1235
|
+
if (r.status !== 0) {
|
|
1236
|
+
const reason = r.error !== void 0 ? `spawn error on \`${cmd}\`: ${r.error.message}` : `exit ${r.status ?? "<unknown>"} on \`${cmd}\``;
|
|
1237
|
+
failureReasons.push(reason);
|
|
1238
|
+
console.error(` \u2717 ${cmd} \u2014 ${reason}`);
|
|
1239
|
+
}
|
|
1240
|
+
}
|
|
1241
|
+
if (failureReasons.length === 0) {
|
|
1242
|
+
out.installed.push(check.name);
|
|
1243
|
+
console.log(` \u2713 installed ${check.name}`);
|
|
1244
|
+
} else if (failureReasons.length === commands.length) {
|
|
1245
|
+
out.failed.push({
|
|
1246
|
+
name: check.name,
|
|
1247
|
+
reason: `all commands failed (${failureReasons.length})`
|
|
1248
|
+
});
|
|
1249
|
+
console.error(` \u2717 failed ${check.name} \u2014 all ${commands.length} commands failed`);
|
|
1250
|
+
} else {
|
|
1251
|
+
out.failed.push({
|
|
1252
|
+
name: check.name,
|
|
1253
|
+
reason: `partial: ${failureReasons.length}/${commands.length} commands failed`
|
|
1254
|
+
});
|
|
1255
|
+
console.error(
|
|
1256
|
+
` \u26A0 ${check.name} \u2014 partial: ${failureReasons.length}/${commands.length} commands failed (others succeeded)`
|
|
1257
|
+
);
|
|
1258
|
+
}
|
|
1259
|
+
}
|
|
1260
|
+
console.log(
|
|
1261
|
+
`
|
|
1262
|
+
Auto-install summary: ${out.installed.length} installed / ${out.skipped.length} skipped / ${out.failed.length} failed`
|
|
1263
|
+
);
|
|
1264
|
+
return out;
|
|
1265
|
+
}
|
|
1266
|
+
var init_auto_install = __esm({
|
|
1267
|
+
"src/cli/lib/auto-install.ts"() {
|
|
1268
|
+
init_doctor_registry();
|
|
1269
|
+
}
|
|
1270
|
+
});
|
|
1271
|
+
|
|
1272
|
+
// package.json
|
|
1273
|
+
var package_default = {
|
|
1274
|
+
version: "4.0.0"};
|
|
1275
|
+
|
|
1276
|
+
// src/manifest/errors.ts
|
|
1277
|
+
function instancePathToKeyPath(instancePath) {
|
|
1278
|
+
if (!instancePath || instancePath === "/") return [];
|
|
1279
|
+
return instancePath.split("/").filter(Boolean).map((seg) => {
|
|
1280
|
+
const n = Number(seg);
|
|
1281
|
+
return Number.isInteger(n) && String(n) === seg ? n : seg;
|
|
1282
|
+
});
|
|
1283
|
+
}
|
|
1284
|
+
function locateLineFromDoc(doc, lineCounter, instancePath) {
|
|
1285
|
+
const path = instancePathToKeyPath(instancePath);
|
|
1286
|
+
let node;
|
|
1287
|
+
if (path.length === 0) {
|
|
1288
|
+
node = doc.contents;
|
|
1289
|
+
} else {
|
|
1290
|
+
node = doc.getIn(path, true);
|
|
1291
|
+
}
|
|
1292
|
+
if (!node || typeof node !== "object") return { line: null, column: null };
|
|
1293
|
+
const range = node.range;
|
|
1294
|
+
if (!range) return { line: null, column: null };
|
|
1295
|
+
const offset = range[0];
|
|
1296
|
+
const pos = lineCounter.linePos(offset);
|
|
1297
|
+
return { line: pos.line, column: pos.col };
|
|
1298
|
+
}
|
|
1299
|
+
function ajvErrorToFriendly(err2, file, doc, lineCounter) {
|
|
1300
|
+
let path = err2.instancePath || "/";
|
|
1301
|
+
const params = err2.params;
|
|
1302
|
+
if (err2.keyword === "additionalProperties" && params && typeof params.additionalProperty === "string") {
|
|
1303
|
+
path = `${path}/${params.additionalProperty}`;
|
|
1304
|
+
} else if (err2.keyword === "required" && params && typeof params.missingProperty === "string") {
|
|
1305
|
+
path = `${path}/${params.missingProperty}`;
|
|
1306
|
+
}
|
|
1307
|
+
let line = null;
|
|
1308
|
+
let column = null;
|
|
1309
|
+
if (doc && lineCounter) {
|
|
1310
|
+
const lookupPath = err2.keyword === "required" ? err2.instancePath || "/" : path;
|
|
1311
|
+
const loc = locateLineFromDoc(doc, lineCounter, lookupPath);
|
|
1312
|
+
line = loc.line;
|
|
1313
|
+
column = loc.column;
|
|
1314
|
+
}
|
|
1315
|
+
return {
|
|
1316
|
+
file,
|
|
1317
|
+
path,
|
|
1318
|
+
message: err2.message ?? "unknown error",
|
|
1319
|
+
line,
|
|
1320
|
+
column,
|
|
1321
|
+
keyword: err2.keyword
|
|
1322
|
+
};
|
|
1323
|
+
}
|
|
1324
|
+
function yamlParseErrorToFriendly(err2, file) {
|
|
1325
|
+
return {
|
|
1326
|
+
file,
|
|
1327
|
+
path: "/",
|
|
1328
|
+
message: err2.message,
|
|
1329
|
+
line: err2.linePos?.[0]?.line ?? null,
|
|
1330
|
+
column: err2.linePos?.[0]?.col ?? null,
|
|
1331
|
+
keyword: "yaml-parse"
|
|
1332
|
+
};
|
|
1333
|
+
}
|
|
1334
|
+
var ApiVersion = Type.Literal("harnessed/v1");
|
|
1335
|
+
var Kind = Type.Literal("Manifest");
|
|
1336
|
+
var SpdxLicense = Type.Union([
|
|
1337
|
+
Type.Literal("MIT"),
|
|
1338
|
+
Type.Literal("Apache-2.0"),
|
|
1339
|
+
Type.Literal("BSD-3-Clause"),
|
|
1340
|
+
Type.Literal("ISC"),
|
|
1341
|
+
Type.Literal("0BSD"),
|
|
1342
|
+
Type.Literal("MIT-0"),
|
|
1343
|
+
Type.Literal("anthropics-official")
|
|
1344
|
+
]);
|
|
1305
1345
|
var LicenseSource = Type.Union([
|
|
1306
1346
|
Type.Literal("README"),
|
|
1307
1347
|
Type.Literal("registry"),
|
|
@@ -2082,7 +2122,7 @@ function renderHumanTable(records) {
|
|
|
2082
2122
|
}
|
|
2083
2123
|
}
|
|
2084
2124
|
function pipeToJq(filterExpr, lines) {
|
|
2085
|
-
return new Promise((
|
|
2125
|
+
return new Promise((resolve16, reject) => {
|
|
2086
2126
|
const child = spawn("jq", [filterExpr], {
|
|
2087
2127
|
stdio: ["pipe", "inherit", "inherit"],
|
|
2088
2128
|
windowsHide: true
|
|
@@ -2091,249 +2131,610 @@ function pipeToJq(filterExpr, lines) {
|
|
|
2091
2131
|
const e = err2;
|
|
2092
2132
|
if (e.code === "ENOENT") {
|
|
2093
2133
|
console.error(t("audit_log.jq_missing"));
|
|
2094
|
-
|
|
2134
|
+
resolve16(1);
|
|
2095
2135
|
} else {
|
|
2096
2136
|
reject(err2);
|
|
2097
2137
|
}
|
|
2098
2138
|
});
|
|
2099
|
-
child.on("close", (code) =>
|
|
2100
|
-
child.stdin.write(lines.join("\n"));
|
|
2101
|
-
child.stdin.end();
|
|
2139
|
+
child.on("close", (code) => resolve16(code ?? 0));
|
|
2140
|
+
child.stdin.write(lines.join("\n"));
|
|
2141
|
+
child.stdin.end();
|
|
2142
|
+
});
|
|
2143
|
+
}
|
|
2144
|
+
function registerAuditLog(program2) {
|
|
2145
|
+
program2.command("audit-log").description(
|
|
2146
|
+
"Query routing audit log (<harnessed-root>/audit.log) with optional jq filter (R10.1)"
|
|
2147
|
+
).option("--filter <expr>", `jq filter expression (e.g. '.category=="engineering"')`).option("--tail <n>", "show N most recent records (default 50)", "50").option("--head <n>", "show N oldest records (--head takes priority over --tail)").option("--reverse", "flip output order").option("--json", "output full 12-field JSON instead of human table").action(
|
|
2148
|
+
async (opts) => {
|
|
2149
|
+
const tailN = opts.tail !== void 0 ? Number(opts.tail) : 50;
|
|
2150
|
+
if (Number.isNaN(tailN) || tailN < 1) {
|
|
2151
|
+
console.error(t("audit_log.tail_invalid"));
|
|
2152
|
+
process.exit(2);
|
|
2153
|
+
}
|
|
2154
|
+
const headN = opts.head !== void 0 ? Number(opts.head) : void 0;
|
|
2155
|
+
if (headN !== void 0 && (Number.isNaN(headN) || headN < 1)) {
|
|
2156
|
+
console.error(t("audit_log.head_invalid"));
|
|
2157
|
+
process.exit(2);
|
|
2158
|
+
}
|
|
2159
|
+
const path = auditPath();
|
|
2160
|
+
if (!existsSync(path)) {
|
|
2161
|
+
console.log(t("audit_log.no_records_file", { path }));
|
|
2162
|
+
process.exit(0);
|
|
2163
|
+
}
|
|
2164
|
+
const raw = readFileSync(path, "utf8");
|
|
2165
|
+
const lines = raw.split("\n").map((l) => l.trim()).filter((l) => l.length > 0);
|
|
2166
|
+
if (lines.length === 0) {
|
|
2167
|
+
console.log(t("audit_log.no_records_empty"));
|
|
2168
|
+
process.exit(0);
|
|
2169
|
+
}
|
|
2170
|
+
let records = [];
|
|
2171
|
+
for (const line of lines) {
|
|
2172
|
+
try {
|
|
2173
|
+
records.push(JSON.parse(line));
|
|
2174
|
+
} catch {
|
|
2175
|
+
}
|
|
2176
|
+
}
|
|
2177
|
+
if (headN !== void 0) {
|
|
2178
|
+
records = records.slice(0, headN);
|
|
2179
|
+
} else {
|
|
2180
|
+
records = records.slice(-tailN);
|
|
2181
|
+
}
|
|
2182
|
+
if (opts.reverse) records = records.reverse();
|
|
2183
|
+
records = records.map(redactRecord);
|
|
2184
|
+
if (opts.filter) {
|
|
2185
|
+
const redactedLines = records.map((r) => JSON.stringify(r));
|
|
2186
|
+
const exitCode = await pipeToJq(opts.filter, redactedLines);
|
|
2187
|
+
process.exit(exitCode);
|
|
2188
|
+
}
|
|
2189
|
+
if (opts.json) {
|
|
2190
|
+
for (const r of records) {
|
|
2191
|
+
console.log(JSON.stringify(r, null, 2));
|
|
2192
|
+
}
|
|
2193
|
+
} else {
|
|
2194
|
+
renderHumanTable(records);
|
|
2195
|
+
}
|
|
2196
|
+
process.exit(0);
|
|
2197
|
+
}
|
|
2198
|
+
);
|
|
2199
|
+
}
|
|
2200
|
+
|
|
2201
|
+
// src/installers/lib/backup.ts
|
|
2202
|
+
init_harnessedRoot();
|
|
2203
|
+
function getBackupRoot() {
|
|
2204
|
+
return harnessedSubdir("backups");
|
|
2205
|
+
}
|
|
2206
|
+
var HOME_DIR = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
2207
|
+
function mirrorPath(target, scope, backupDir) {
|
|
2208
|
+
const root = scope === "HOME" ? HOME_DIR : ".";
|
|
2209
|
+
const rel = root ? relative(root, target) : target;
|
|
2210
|
+
if (!rel || rel.startsWith("..")) {
|
|
2211
|
+
const flat = createHash("sha1").update(target).digest("hex").slice(0, 16);
|
|
2212
|
+
return join(backupDir, scope, flat);
|
|
2213
|
+
}
|
|
2214
|
+
return join(backupDir, scope, rel);
|
|
2215
|
+
}
|
|
2216
|
+
function detectEol(buf) {
|
|
2217
|
+
return buf.includes("\r\n") ? "crlf" : "lf";
|
|
2218
|
+
}
|
|
2219
|
+
async function backup(plan, ctx) {
|
|
2220
|
+
const filename = ctx.manifest.metadata.name;
|
|
2221
|
+
const backupId = (/* @__PURE__ */ new Date()).toISOString().replace(/:/g, "-");
|
|
2222
|
+
const backupDir = join(getBackupRoot(), backupId);
|
|
2223
|
+
try {
|
|
2224
|
+
await mkdir(backupDir, { recursive: true });
|
|
2225
|
+
} catch (err2) {
|
|
2226
|
+
return {
|
|
2227
|
+
ok: false,
|
|
2228
|
+
error: {
|
|
2229
|
+
file: filename,
|
|
2230
|
+
path: "/",
|
|
2231
|
+
message: `failed to create backup dir ${backupDir}: ${err2.message}`,
|
|
2232
|
+
line: null,
|
|
2233
|
+
column: null,
|
|
2234
|
+
keyword: "backup-mkdir-failed"
|
|
2235
|
+
}
|
|
2236
|
+
};
|
|
2237
|
+
}
|
|
2238
|
+
const entries = [];
|
|
2239
|
+
for (const file of plan.files) {
|
|
2240
|
+
let buf;
|
|
2241
|
+
try {
|
|
2242
|
+
buf = await readFile(file.target);
|
|
2243
|
+
} catch (err2) {
|
|
2244
|
+
const code = err2.code;
|
|
2245
|
+
if (code === "ENOENT" && file.oldText === "") {
|
|
2246
|
+
entries.push({
|
|
2247
|
+
target: file.target,
|
|
2248
|
+
backup: "",
|
|
2249
|
+
// sentinel: no backup written; rollback should unlink target
|
|
2250
|
+
sha1: "",
|
|
2251
|
+
eol: "lf"
|
|
2252
|
+
// moot for non-existent file; default to lf
|
|
2253
|
+
});
|
|
2254
|
+
continue;
|
|
2255
|
+
}
|
|
2256
|
+
return {
|
|
2257
|
+
ok: false,
|
|
2258
|
+
error: {
|
|
2259
|
+
file: filename,
|
|
2260
|
+
path: file.target,
|
|
2261
|
+
message: `failed to read original file for backup: ${err2.message}`,
|
|
2262
|
+
line: null,
|
|
2263
|
+
column: null,
|
|
2264
|
+
keyword: "backup-read-failed"
|
|
2265
|
+
}
|
|
2266
|
+
};
|
|
2267
|
+
}
|
|
2268
|
+
const sha1 = createHash("sha1").update(buf).digest("hex");
|
|
2269
|
+
const eol = detectEol(buf);
|
|
2270
|
+
const dest = mirrorPath(file.target, file.scope, backupDir);
|
|
2271
|
+
try {
|
|
2272
|
+
await mkdir(dirname(dest), { recursive: true });
|
|
2273
|
+
await writeFile(dest, buf);
|
|
2274
|
+
} catch (err2) {
|
|
2275
|
+
return {
|
|
2276
|
+
ok: false,
|
|
2277
|
+
error: {
|
|
2278
|
+
file: filename,
|
|
2279
|
+
path: dest,
|
|
2280
|
+
message: `failed to write backup copy: ${err2.message}`,
|
|
2281
|
+
line: null,
|
|
2282
|
+
column: null,
|
|
2283
|
+
keyword: "backup-write-failed"
|
|
2284
|
+
}
|
|
2285
|
+
};
|
|
2286
|
+
}
|
|
2287
|
+
entries.push({ target: file.target, backup: dest, sha1, eol });
|
|
2288
|
+
}
|
|
2289
|
+
const metadata = {
|
|
2290
|
+
installer: filename,
|
|
2291
|
+
manifest: filename,
|
|
2292
|
+
timestamp: backupId,
|
|
2293
|
+
files: entries
|
|
2294
|
+
};
|
|
2295
|
+
const metadataPath = join(backupDir, "metadata.json");
|
|
2296
|
+
try {
|
|
2297
|
+
await writeFile(metadataPath, `${JSON.stringify(metadata, null, 2)}
|
|
2298
|
+
`, "utf8");
|
|
2299
|
+
} catch (err2) {
|
|
2300
|
+
return {
|
|
2301
|
+
ok: false,
|
|
2302
|
+
error: {
|
|
2303
|
+
file: filename,
|
|
2304
|
+
path: metadataPath,
|
|
2305
|
+
message: `failed to write metadata.json: ${err2.message}`,
|
|
2306
|
+
line: null,
|
|
2307
|
+
column: null,
|
|
2308
|
+
keyword: "backup-metadata-failed"
|
|
2309
|
+
}
|
|
2310
|
+
};
|
|
2311
|
+
}
|
|
2312
|
+
return { ok: true, backupId, backupDir };
|
|
2313
|
+
}
|
|
2314
|
+
|
|
2315
|
+
// src/cli/backup-list.ts
|
|
2316
|
+
function registerBackupList(program2) {
|
|
2317
|
+
const backup2 = program2.command("backup").description("Backup snapshot operations");
|
|
2318
|
+
backup2.command("list").description("List backup snapshots under .harnessed-backup/").action(async () => {
|
|
2319
|
+
const root = getBackupRoot();
|
|
2320
|
+
let dirs;
|
|
2321
|
+
try {
|
|
2322
|
+
dirs = (await readdir(root, { withFileTypes: true })).filter((e) => e.isDirectory()).map((e) => e.name).sort();
|
|
2323
|
+
} catch {
|
|
2324
|
+
console.log(t("backup.no_backups", { root }));
|
|
2325
|
+
return;
|
|
2326
|
+
}
|
|
2327
|
+
if (dirs.length === 0) {
|
|
2328
|
+
console.log(t("backup.no_backups_empty", { root }));
|
|
2329
|
+
return;
|
|
2330
|
+
}
|
|
2331
|
+
for (const ts of dirs) {
|
|
2332
|
+
try {
|
|
2333
|
+
const meta = JSON.parse(
|
|
2334
|
+
await readFile(join(root, ts, "metadata.json"), "utf8")
|
|
2335
|
+
);
|
|
2336
|
+
console.log(
|
|
2337
|
+
`${ts} ${meta.manifest} (${meta.files.length} file${meta.files.length === 1 ? "" : "s"})`
|
|
2338
|
+
);
|
|
2339
|
+
} catch {
|
|
2340
|
+
console.log(`${ts} (metadata.json missing or unreadable)`);
|
|
2341
|
+
}
|
|
2342
|
+
}
|
|
2343
|
+
console.log(t("backup.total_snapshots", { count: dirs.length }));
|
|
2344
|
+
});
|
|
2345
|
+
}
|
|
2346
|
+
|
|
2347
|
+
// src/cli/checkpoint.ts
|
|
2348
|
+
var ACTIONS = ["start", "complete", "fail"];
|
|
2349
|
+
function isAction(a) {
|
|
2350
|
+
return ACTIONS.includes(a);
|
|
2351
|
+
}
|
|
2352
|
+
function registerCheckpoint(program2) {
|
|
2353
|
+
program2.command("checkpoint <action> <sub>").description(
|
|
2354
|
+
"Record workflow progress: start | complete | fail <sub-workflow> (writes to ~/.claude/harnessed/checkpoints/)"
|
|
2355
|
+
).option("--summary <text>", "short summary stored as the checkpoint lastTask").action(async (action, sub, opts) => {
|
|
2356
|
+
if (!isAction(action)) {
|
|
2357
|
+
console.error(
|
|
2358
|
+
`[harnessed] checkpoint: unknown action "${action}" \u2014 expected one of ${ACTIONS.join(", ")}`
|
|
2359
|
+
);
|
|
2360
|
+
process.exit(1);
|
|
2361
|
+
return;
|
|
2362
|
+
}
|
|
2363
|
+
const { activatePhase: activatePhase2, completePhase: completePhase2 } = await Promise.resolve().then(() => (init_engineHook(), engineHook_exports));
|
|
2364
|
+
if (action === "start") {
|
|
2365
|
+
const { checkpointPath } = await activatePhase2(sub);
|
|
2366
|
+
console.log(`[harnessed] checkpoint started: ${sub} \u2192 ${checkpointPath}`);
|
|
2367
|
+
process.exit(0);
|
|
2368
|
+
return;
|
|
2369
|
+
}
|
|
2370
|
+
if (action === "complete") {
|
|
2371
|
+
await completePhase2({
|
|
2372
|
+
phaseId: sub,
|
|
2373
|
+
status: "complete",
|
|
2374
|
+
lastTask: opts.summary ?? `phase ${sub} complete`
|
|
2375
|
+
});
|
|
2376
|
+
console.log(`[harnessed] checkpoint complete: ${sub}`);
|
|
2377
|
+
process.exit(0);
|
|
2378
|
+
return;
|
|
2379
|
+
}
|
|
2380
|
+
await completePhase2({
|
|
2381
|
+
phaseId: sub,
|
|
2382
|
+
status: "complete",
|
|
2383
|
+
lastTask: `FAILED: ${opts.summary ?? ""}`
|
|
2384
|
+
});
|
|
2385
|
+
console.error(
|
|
2386
|
+
`[harnessed] checkpoint FAILED recorded: ${sub}${opts.summary ? ` \u2014 ${opts.summary}` : ""}`
|
|
2387
|
+
);
|
|
2388
|
+
process.exit(1);
|
|
2102
2389
|
});
|
|
2103
2390
|
}
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
console.log(t("audit_log.no_records_empty"));
|
|
2128
|
-
process.exit(0);
|
|
2129
|
-
}
|
|
2130
|
-
let records = [];
|
|
2131
|
-
for (const line of lines) {
|
|
2132
|
-
try {
|
|
2133
|
-
records.push(JSON.parse(line));
|
|
2134
|
-
} catch {
|
|
2135
|
-
}
|
|
2136
|
-
}
|
|
2137
|
-
if (headN !== void 0) {
|
|
2138
|
-
records = records.slice(0, headN);
|
|
2139
|
-
} else {
|
|
2140
|
-
records = records.slice(-tailN);
|
|
2141
|
-
}
|
|
2142
|
-
if (opts.reverse) records = records.reverse();
|
|
2143
|
-
records = records.map(redactRecord);
|
|
2144
|
-
if (opts.filter) {
|
|
2145
|
-
const redactedLines = records.map((r) => JSON.stringify(r));
|
|
2146
|
-
const exitCode = await pipeToJq(opts.filter, redactedLines);
|
|
2147
|
-
process.exit(exitCode);
|
|
2148
|
-
}
|
|
2149
|
-
if (opts.json) {
|
|
2150
|
-
for (const r of records) {
|
|
2151
|
-
console.log(JSON.stringify(r, null, 2));
|
|
2152
|
-
}
|
|
2153
|
-
} else {
|
|
2154
|
-
renderHumanTable(records);
|
|
2391
|
+
|
|
2392
|
+
// src/cli/doctor.ts
|
|
2393
|
+
init_doctor_registry();
|
|
2394
|
+
function registerDoctor(program2) {
|
|
2395
|
+
program2.command("doctor").description(
|
|
2396
|
+
"Preflight checks (Node / MCP scope / jq / Win bash / origin URL / gstack prefix / deprecations / token budget / Agent Teams / planning-with-files / mattpocock-skills / MCP availability)"
|
|
2397
|
+
).option("--json", "output JSON instead of human-readable").action(async (opts) => {
|
|
2398
|
+
const results = await Promise.all(CHECKS.map((c) => c()));
|
|
2399
|
+
const hasFail = results.some((r) => r.status === "fail");
|
|
2400
|
+
const hasWarn = results.some((r) => r.status === "warn");
|
|
2401
|
+
if (opts.json) {
|
|
2402
|
+
console.log(
|
|
2403
|
+
JSON.stringify(
|
|
2404
|
+
{ checks: results, summary: hasFail ? "fail" : hasWarn ? "warn" : "pass" },
|
|
2405
|
+
null,
|
|
2406
|
+
2
|
|
2407
|
+
)
|
|
2408
|
+
);
|
|
2409
|
+
} else {
|
|
2410
|
+
for (const r of results) {
|
|
2411
|
+
const mark = r.status === "pass" ? "\u2713" : r.status === "warn" ? "\u26A0" : "\u2717";
|
|
2412
|
+
console.log(`${mark} ${r.name} \u2014 ${r.message}`);
|
|
2413
|
+
if (r.status !== "pass" && r.fix) console.log(` fix: ${r.fix}`);
|
|
2155
2414
|
}
|
|
2156
|
-
|
|
2415
|
+
console.log(
|
|
2416
|
+
hasFail ? t("doctor.summary.fail") : hasWarn ? t("doctor.summary.warn") : t("doctor.summary.pass")
|
|
2417
|
+
);
|
|
2157
2418
|
}
|
|
2158
|
-
|
|
2419
|
+
process.exit(hasFail ? 1 : 0);
|
|
2420
|
+
});
|
|
2421
|
+
}
|
|
2422
|
+
var PARSER_OPTIONS = {
|
|
2423
|
+
operators: {
|
|
2424
|
+
add: false,
|
|
2425
|
+
subtract: false,
|
|
2426
|
+
multiply: false,
|
|
2427
|
+
divide: false,
|
|
2428
|
+
logical: true,
|
|
2429
|
+
comparison: true,
|
|
2430
|
+
in: true,
|
|
2431
|
+
assignment: false
|
|
2432
|
+
}
|
|
2433
|
+
};
|
|
2434
|
+
var _parserSingleton = new Parser(PARSER_OPTIONS);
|
|
2435
|
+
var GateEvalError = class extends Error {
|
|
2436
|
+
constructor(message, expression) {
|
|
2437
|
+
super(message);
|
|
2438
|
+
this.expression = expression;
|
|
2439
|
+
this.name = "GateEvalError";
|
|
2440
|
+
}
|
|
2441
|
+
expression;
|
|
2442
|
+
};
|
|
2443
|
+
function evalGate(expression, context) {
|
|
2444
|
+
try {
|
|
2445
|
+
const parsed = _parserSingleton.parse(expression);
|
|
2446
|
+
const result = parsed.evaluate(context);
|
|
2447
|
+
if (typeof result !== "boolean") {
|
|
2448
|
+
throw new GateEvalError(
|
|
2449
|
+
`Expression must evaluate to boolean, got ${typeof result}`,
|
|
2450
|
+
expression
|
|
2451
|
+
);
|
|
2452
|
+
}
|
|
2453
|
+
return result;
|
|
2454
|
+
} catch (err2) {
|
|
2455
|
+
if (err2 instanceof GateEvalError) throw err2;
|
|
2456
|
+
throw new GateEvalError(`Gate eval failed: ${err2.message}`, expression);
|
|
2457
|
+
}
|
|
2159
2458
|
}
|
|
2160
2459
|
|
|
2161
|
-
// src/
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2460
|
+
// src/workflow/schema/judgment.ts
|
|
2461
|
+
init_schemaVersion();
|
|
2462
|
+
var TriggerInvocation = Type.Object(
|
|
2463
|
+
{
|
|
2464
|
+
capability: Type.String()
|
|
2465
|
+
},
|
|
2466
|
+
{ additionalProperties: false }
|
|
2467
|
+
);
|
|
2468
|
+
var RequiresCapabilities = Type.Object(
|
|
2469
|
+
{
|
|
2470
|
+
capabilities: Type.Array(Type.String())
|
|
2471
|
+
},
|
|
2472
|
+
{ additionalProperties: false }
|
|
2473
|
+
);
|
|
2474
|
+
var JudgmentTrigger = Type.Object(
|
|
2475
|
+
{
|
|
2476
|
+
description: Type.Optional(Type.String()),
|
|
2477
|
+
fires_when: Type.Optional(Type.String()),
|
|
2478
|
+
skips_when: Type.Optional(Type.String()),
|
|
2479
|
+
invokes: Type.Optional(Type.Array(TriggerInvocation)),
|
|
2480
|
+
requires: Type.Optional(RequiresCapabilities),
|
|
2481
|
+
wraps: Type.Optional(Type.Array(Type.String()))
|
|
2482
|
+
},
|
|
2483
|
+
{ additionalProperties: false }
|
|
2484
|
+
);
|
|
2485
|
+
var FallbackRule = Type.Object(
|
|
2486
|
+
{
|
|
2487
|
+
description: Type.Optional(Type.String()),
|
|
2488
|
+
fallback_action: Type.Optional(Type.String()),
|
|
2489
|
+
message_template: Type.Optional(Type.String()),
|
|
2490
|
+
override_signal: Type.Optional(Type.Array(Type.String())),
|
|
2491
|
+
chain_isolation: Type.Optional(Type.Boolean())
|
|
2492
|
+
},
|
|
2493
|
+
{ additionalProperties: false }
|
|
2494
|
+
);
|
|
2495
|
+
var JudgmentTriggersFile = Type.Object(
|
|
2496
|
+
{
|
|
2497
|
+
schema_version: Type.Literal(SCHEMA_VERSIONS.judgment),
|
|
2498
|
+
triggers: Type.Record(Type.String(), JudgmentTrigger)
|
|
2499
|
+
},
|
|
2500
|
+
{ additionalProperties: false }
|
|
2501
|
+
);
|
|
2502
|
+
var JudgmentRulesFile = Type.Object(
|
|
2503
|
+
{
|
|
2504
|
+
schema_version: Type.Literal(SCHEMA_VERSIONS.judgment),
|
|
2505
|
+
rules: Type.Record(Type.String(), FallbackRule)
|
|
2506
|
+
},
|
|
2507
|
+
{ additionalProperties: false }
|
|
2508
|
+
);
|
|
2509
|
+
Type.Union([JudgmentTriggersFile, JudgmentRulesFile]);
|
|
2510
|
+
var UserOverrideEntry = Type.Object(
|
|
2511
|
+
{
|
|
2512
|
+
id: Type.String({ minLength: 1 }),
|
|
2513
|
+
// kebab-case (e.g. 'brainstorm', 'arch-review')
|
|
2514
|
+
keywords: Type.Array(Type.String({ minLength: 1 }), { minItems: 1 }),
|
|
2515
|
+
triggers: Type.Array(Type.String({ minLength: 1 }), { minItems: 1 })
|
|
2516
|
+
},
|
|
2517
|
+
{ additionalProperties: false }
|
|
2518
|
+
);
|
|
2519
|
+
var UserOverridesFile = Type.Object(
|
|
2520
|
+
{
|
|
2521
|
+
schema_version: Type.Literal("harnessed.user-overrides.v1"),
|
|
2522
|
+
overrides: Type.Array(UserOverrideEntry, { minItems: 1 })
|
|
2523
|
+
},
|
|
2524
|
+
{ additionalProperties: false }
|
|
2525
|
+
);
|
|
2526
|
+
|
|
2527
|
+
// src/workflow/judgmentResolver.ts
|
|
2528
|
+
var TriggerNotFoundError = class extends Error {
|
|
2529
|
+
constructor(trigger, fileName) {
|
|
2530
|
+
super(`Trigger '${trigger}' not found in judgments/${fileName}.yaml`);
|
|
2531
|
+
this.trigger = trigger;
|
|
2532
|
+
this.fileName = fileName;
|
|
2533
|
+
this.name = "TriggerNotFoundError";
|
|
2534
|
+
}
|
|
2535
|
+
trigger;
|
|
2536
|
+
fileName;
|
|
2537
|
+
};
|
|
2538
|
+
var _fileCache = /* @__PURE__ */ new Map();
|
|
2539
|
+
async function resolveJudgmentGate(gateRef, context, packageRoot) {
|
|
2540
|
+
const userOverrides = context.user_overrides;
|
|
2541
|
+
if (Array.isArray(userOverrides) && userOverrides.includes(gateRef)) {
|
|
2542
|
+
return true;
|
|
2543
|
+
}
|
|
2544
|
+
const parts = gateRef.split(".");
|
|
2545
|
+
if (parts.length !== 4 || parts[0] !== "judgments") {
|
|
2546
|
+
throw new Error(`Invalid gate ref: ${gateRef}`);
|
|
2547
|
+
}
|
|
2548
|
+
const [, fileName, triggerName, fieldName] = parts;
|
|
2549
|
+
let parsed = _fileCache.get(fileName);
|
|
2550
|
+
if (!parsed) {
|
|
2551
|
+
const yamlPath = resolve(packageRoot, "workflows", "judgments", `${fileName}.yaml`);
|
|
2552
|
+
const raw = await readFile(yamlPath, "utf8");
|
|
2553
|
+
const parsedRaw = parse(raw);
|
|
2554
|
+
const schema = fileName === "fallback" ? JudgmentRulesFile : JudgmentTriggersFile;
|
|
2555
|
+
if (!Value.Check(schema, parsedRaw)) {
|
|
2556
|
+
const errors = [...Value.Errors(schema, parsedRaw)].slice(0, 3).map((e) => `${e.path} ${e.message}`).join("; ");
|
|
2557
|
+
throw new Error(`Invalid judgment file ${fileName}.yaml: ${errors}`);
|
|
2558
|
+
}
|
|
2559
|
+
parsed = parsedRaw;
|
|
2560
|
+
_fileCache.set(fileName, parsed);
|
|
2561
|
+
}
|
|
2562
|
+
const entries = "triggers" in parsed ? parsed.triggers : parsed.rules;
|
|
2563
|
+
const trigger = entries[triggerName];
|
|
2564
|
+
if (!trigger) {
|
|
2565
|
+
throw new TriggerNotFoundError(triggerName, fileName);
|
|
2566
|
+
}
|
|
2567
|
+
const expr = fieldName === "fires" ? trigger.fires_when : fieldName === "skips" ? trigger.skips_when : void 0;
|
|
2568
|
+
if (!expr) {
|
|
2569
|
+
throw new Error(
|
|
2570
|
+
`Field '${fieldName}' has no expression in trigger '${triggerName}' of ${fileName}.yaml`
|
|
2571
|
+
);
|
|
2572
|
+
}
|
|
2573
|
+
return evalGate(expr, context);
|
|
2165
2574
|
}
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
const
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
const flat = createHash("sha1").update(target).digest("hex").slice(0, 16);
|
|
2172
|
-
return join(backupDir, scope, flat);
|
|
2575
|
+
function getPackageRoot() {
|
|
2576
|
+
const thisFile = fileURLToPath(import.meta.url);
|
|
2577
|
+
const thisDir = dirname(thisFile);
|
|
2578
|
+
if (thisDir.endsWith("dist") || thisDir.replace(/\\/g, "/").endsWith("/dist")) {
|
|
2579
|
+
return resolve(thisDir, "..");
|
|
2173
2580
|
}
|
|
2174
|
-
return
|
|
2581
|
+
return resolve(thisDir, "..", "..", "..");
|
|
2175
2582
|
}
|
|
2176
|
-
|
|
2177
|
-
|
|
2583
|
+
|
|
2584
|
+
// src/cli/gates.ts
|
|
2585
|
+
var VALID_MASTERS = /* @__PURE__ */ new Set(["auto", "discuss", "plan", "task", "verify"]);
|
|
2586
|
+
var PARALLELISM_GATE = "judgments.parallelism-gate.agent-teams-upgrade.fires";
|
|
2587
|
+
function defaultContext(task, stage) {
|
|
2588
|
+
return {
|
|
2589
|
+
task,
|
|
2590
|
+
user_understanding_unclear: false,
|
|
2591
|
+
phase: {
|
|
2592
|
+
stage,
|
|
2593
|
+
is_critical_module: true,
|
|
2594
|
+
is_final_step: true,
|
|
2595
|
+
is_major_release: false,
|
|
2596
|
+
has_auth_or_secrets: false,
|
|
2597
|
+
has_design_changes: false,
|
|
2598
|
+
has_ui_changes: false,
|
|
2599
|
+
requires_creative_polish: false,
|
|
2600
|
+
is_complex_architecture: true,
|
|
2601
|
+
has_cross_phase_data_flow: true,
|
|
2602
|
+
open_decisions: 2,
|
|
2603
|
+
scope_days: 2,
|
|
2604
|
+
scope_locked_in_history: false,
|
|
2605
|
+
single_task: false,
|
|
2606
|
+
type: "general"
|
|
2607
|
+
},
|
|
2608
|
+
subtask: {
|
|
2609
|
+
approaches: 2,
|
|
2610
|
+
core_algorithm: true,
|
|
2611
|
+
has_api_contract: true,
|
|
2612
|
+
error_cost: "high",
|
|
2613
|
+
lines: 50,
|
|
2614
|
+
type: "general",
|
|
2615
|
+
is_core_business_logic: true,
|
|
2616
|
+
is_algorithm: true,
|
|
2617
|
+
is_data_processing: true,
|
|
2618
|
+
regression_risk: "high",
|
|
2619
|
+
reliability_required: true,
|
|
2620
|
+
communication_needed: false,
|
|
2621
|
+
needs_lib_docs: false,
|
|
2622
|
+
needs_web_search: false,
|
|
2623
|
+
parallel_count: 1,
|
|
2624
|
+
search_type: "general",
|
|
2625
|
+
test_type: "general"
|
|
2626
|
+
}
|
|
2627
|
+
};
|
|
2178
2628
|
}
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
line: null,
|
|
2193
|
-
column: null,
|
|
2194
|
-
keyword: "backup-mkdir-failed"
|
|
2195
|
-
}
|
|
2196
|
-
};
|
|
2197
|
-
}
|
|
2198
|
-
const entries = [];
|
|
2199
|
-
for (const file of plan.files) {
|
|
2200
|
-
let buf;
|
|
2201
|
-
try {
|
|
2202
|
-
buf = await readFile(file.target);
|
|
2203
|
-
} catch (err2) {
|
|
2204
|
-
const code = err2.code;
|
|
2205
|
-
if (code === "ENOENT" && file.oldText === "") {
|
|
2206
|
-
entries.push({
|
|
2207
|
-
target: file.target,
|
|
2208
|
-
backup: "",
|
|
2209
|
-
// sentinel: no backup written; rollback should unlink target
|
|
2210
|
-
sha1: "",
|
|
2211
|
-
eol: "lf"
|
|
2212
|
-
// moot for non-existent file; default to lf
|
|
2213
|
-
});
|
|
2214
|
-
continue;
|
|
2215
|
-
}
|
|
2216
|
-
return {
|
|
2217
|
-
ok: false,
|
|
2218
|
-
error: {
|
|
2219
|
-
file: filename,
|
|
2220
|
-
path: file.target,
|
|
2221
|
-
message: `failed to read original file for backup: ${err2.message}`,
|
|
2222
|
-
line: null,
|
|
2223
|
-
column: null,
|
|
2224
|
-
keyword: "backup-read-failed"
|
|
2225
|
-
}
|
|
2226
|
-
};
|
|
2629
|
+
function resolveMasterYamlPath(master, packageRoot) {
|
|
2630
|
+
return master === "auto" ? resolve(packageRoot, "workflows", "auto", "workflow.yaml") : resolve(packageRoot, "workflows", master, "auto", "workflow.yaml");
|
|
2631
|
+
}
|
|
2632
|
+
function registerGates(program2) {
|
|
2633
|
+
program2.command("gates").description(
|
|
2634
|
+
"Evaluate which sub-workflows fire for a master orchestrator (JSON plan, no spawn)."
|
|
2635
|
+
).argument("<master>", "master name: auto | discuss | plan | task | verify").option("--task <text>", "task description (injected as gateContext.task)").option("--skip-sub <names>", "comma-separated sub names to skip without gate eval").option("--context <json>", "JSON object merged over the default gate context").action(async (master, raw) => {
|
|
2636
|
+
if (!VALID_MASTERS.has(master)) {
|
|
2637
|
+
console.error(
|
|
2638
|
+
`error: unknown master '${master}'. Expected one of: auto, discuss, plan, task, verify.`
|
|
2639
|
+
);
|
|
2640
|
+
process.exit(1);
|
|
2641
|
+
return;
|
|
2227
2642
|
}
|
|
2228
|
-
const
|
|
2229
|
-
const
|
|
2230
|
-
|
|
2643
|
+
const packageRoot = getPackageRoot();
|
|
2644
|
+
const yamlPath = resolveMasterYamlPath(master, packageRoot);
|
|
2645
|
+
let raw_yaml;
|
|
2231
2646
|
try {
|
|
2232
|
-
await
|
|
2233
|
-
await writeFile(dest, buf);
|
|
2647
|
+
raw_yaml = await readFile(yamlPath, "utf8");
|
|
2234
2648
|
} catch (err2) {
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
message: `failed to write backup copy: ${err2.message}`,
|
|
2241
|
-
line: null,
|
|
2242
|
-
column: null,
|
|
2243
|
-
keyword: "backup-write-failed"
|
|
2244
|
-
}
|
|
2245
|
-
};
|
|
2649
|
+
console.error(
|
|
2650
|
+
`error: failed to read master workflow.yaml at ${yamlPath} \u2014 ${err2.message}`
|
|
2651
|
+
);
|
|
2652
|
+
process.exit(1);
|
|
2653
|
+
return;
|
|
2246
2654
|
}
|
|
2247
|
-
|
|
2248
|
-
}
|
|
2249
|
-
const metadata = {
|
|
2250
|
-
installer: filename,
|
|
2251
|
-
manifest: filename,
|
|
2252
|
-
timestamp: backupId,
|
|
2253
|
-
files: entries
|
|
2254
|
-
};
|
|
2255
|
-
const metadataPath = join(backupDir, "metadata.json");
|
|
2256
|
-
try {
|
|
2257
|
-
await writeFile(metadataPath, `${JSON.stringify(metadata, null, 2)}
|
|
2258
|
-
`, "utf8");
|
|
2259
|
-
} catch (err2) {
|
|
2260
|
-
return {
|
|
2261
|
-
ok: false,
|
|
2262
|
-
error: {
|
|
2263
|
-
file: filename,
|
|
2264
|
-
path: metadataPath,
|
|
2265
|
-
message: `failed to write metadata.json: ${err2.message}`,
|
|
2266
|
-
line: null,
|
|
2267
|
-
column: null,
|
|
2268
|
-
keyword: "backup-metadata-failed"
|
|
2269
|
-
}
|
|
2270
|
-
};
|
|
2271
|
-
}
|
|
2272
|
-
return { ok: true, backupId, backupDir };
|
|
2273
|
-
}
|
|
2274
|
-
|
|
2275
|
-
// src/cli/backup-list.ts
|
|
2276
|
-
function registerBackupList(program2) {
|
|
2277
|
-
const backup2 = program2.command("backup").description("Backup snapshot operations");
|
|
2278
|
-
backup2.command("list").description("List backup snapshots under .harnessed-backup/").action(async () => {
|
|
2279
|
-
const root = getBackupRoot();
|
|
2280
|
-
let dirs;
|
|
2655
|
+
let parsed;
|
|
2281
2656
|
try {
|
|
2282
|
-
|
|
2283
|
-
} catch {
|
|
2284
|
-
console.
|
|
2657
|
+
parsed = parse(raw_yaml);
|
|
2658
|
+
} catch (err2) {
|
|
2659
|
+
console.error(`error: failed to parse ${yamlPath} \u2014 ${err2.message}`);
|
|
2660
|
+
process.exit(1);
|
|
2285
2661
|
return;
|
|
2286
2662
|
}
|
|
2287
|
-
|
|
2288
|
-
|
|
2663
|
+
const delegates = parsed?.delegates_to;
|
|
2664
|
+
if (!Array.isArray(delegates) || delegates.length === 0) {
|
|
2665
|
+
console.error(`error: master '${master}' workflow.yaml missing delegates_to[]`);
|
|
2666
|
+
process.exit(1);
|
|
2289
2667
|
return;
|
|
2290
2668
|
}
|
|
2291
|
-
|
|
2669
|
+
const clauses = delegates;
|
|
2670
|
+
const task = typeof raw.task === "string" ? raw.task : "";
|
|
2671
|
+
const stage = master;
|
|
2672
|
+
const ctx = defaultContext(task, stage);
|
|
2673
|
+
if (typeof raw.context === "string" && raw.context.length > 0) {
|
|
2674
|
+
let extra;
|
|
2292
2675
|
try {
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
);
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2676
|
+
extra = JSON.parse(raw.context);
|
|
2677
|
+
} catch (err2) {
|
|
2678
|
+
console.error(`error: --context is not valid JSON \u2014 ${err2.message}`);
|
|
2679
|
+
process.exit(1);
|
|
2680
|
+
return;
|
|
2681
|
+
}
|
|
2682
|
+
if (extra && typeof extra === "object") {
|
|
2683
|
+
Object.assign(ctx, extra);
|
|
2301
2684
|
}
|
|
2302
2685
|
}
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2686
|
+
const skipSubs = new Set(
|
|
2687
|
+
typeof raw.skipSub === "string" && raw.skipSub.length > 0 ? raw.skipSub.split(",").map((s) => s.trim()).filter((s) => s.length > 0) : []
|
|
2688
|
+
);
|
|
2689
|
+
ctx.skip_subs = [...skipSubs];
|
|
2690
|
+
ctx.task = task;
|
|
2691
|
+
const fire = [];
|
|
2692
|
+
const skip = [];
|
|
2693
|
+
for (const clause of clauses) {
|
|
2694
|
+
if (skipSubs.has(clause.sub)) {
|
|
2695
|
+
skip.push({
|
|
2696
|
+
sub: clause.sub,
|
|
2697
|
+
reason: "skipped via skip_subs (done interactively in main session)"
|
|
2698
|
+
});
|
|
2699
|
+
continue;
|
|
2700
|
+
}
|
|
2701
|
+
if (!clause.gate) {
|
|
2702
|
+
fire.push(fireEntry(clause));
|
|
2703
|
+
continue;
|
|
2704
|
+
}
|
|
2705
|
+
try {
|
|
2706
|
+
const passes = await resolveJudgmentGate(clause.gate, ctx, packageRoot);
|
|
2707
|
+
if (passes) {
|
|
2708
|
+
fire.push(fireEntry(clause));
|
|
2709
|
+
} else {
|
|
2710
|
+
skip.push({ sub: clause.sub, reason: `gate ${clause.gate} = false` });
|
|
2711
|
+
}
|
|
2712
|
+
} catch (e) {
|
|
2713
|
+
console.warn(
|
|
2714
|
+
`\u26A0\uFE0F master ${master} sub ${clause.sub} gate ${clause.gate} eval failed (${e.message}); firing sub as if gate=true (ADR 0029 fail-soft).`
|
|
2715
|
+
);
|
|
2716
|
+
fire.push(fireEntry(clause));
|
|
2329
2717
|
}
|
|
2330
|
-
console.log(
|
|
2331
|
-
hasFail ? t("doctor.summary.fail") : hasWarn ? t("doctor.summary.warn") : t("doctor.summary.pass")
|
|
2332
|
-
);
|
|
2333
2718
|
}
|
|
2334
|
-
|
|
2719
|
+
let parallelism = { escalate_to_teams: false, reason: null };
|
|
2720
|
+
try {
|
|
2721
|
+
const escalate = await resolveJudgmentGate(PARALLELISM_GATE, ctx, packageRoot);
|
|
2722
|
+
parallelism = escalate ? { escalate_to_teams: true, reason: "agent-teams-upgrade gate fired" } : { escalate_to_teams: false, reason: null };
|
|
2723
|
+
} catch {
|
|
2724
|
+
parallelism = { escalate_to_teams: false, reason: null };
|
|
2725
|
+
}
|
|
2726
|
+
const result = { master, fire, skip, parallelism };
|
|
2727
|
+
console.log(JSON.stringify(result, null, 2));
|
|
2728
|
+
process.exit(0);
|
|
2335
2729
|
});
|
|
2336
2730
|
}
|
|
2731
|
+
function fireEntry(clause) {
|
|
2732
|
+
const entry = { sub: clause.sub };
|
|
2733
|
+
if (clause.order !== void 0) entry.order = clause.order;
|
|
2734
|
+
if (clause.mode !== void 0) entry.mode = clause.mode;
|
|
2735
|
+
if (clause.gate !== void 0) entry.gate = clause.gate;
|
|
2736
|
+
return entry;
|
|
2737
|
+
}
|
|
2337
2738
|
var DURATION_RE = /^(\d+)([dhmw])$/;
|
|
2338
2739
|
function parseDuration(s) {
|
|
2339
2740
|
const m = DURATION_RE.exec(s);
|
|
@@ -2561,11 +2962,11 @@ function preflight(ctx) {
|
|
|
2561
2962
|
// src/installers/lib/state.ts
|
|
2562
2963
|
init_harnessedRoot();
|
|
2563
2964
|
var DEFAULT_STATE = { version: "1", installed: {} };
|
|
2564
|
-
function
|
|
2965
|
+
function statePath2(_cwd) {
|
|
2565
2966
|
return harnessedFile("state.json");
|
|
2566
2967
|
}
|
|
2567
2968
|
async function readState(cwd) {
|
|
2568
|
-
const path =
|
|
2969
|
+
const path = statePath2();
|
|
2569
2970
|
let raw;
|
|
2570
2971
|
try {
|
|
2571
2972
|
raw = await readFile(path, "utf8");
|
|
@@ -2586,7 +2987,7 @@ async function readState(cwd) {
|
|
|
2586
2987
|
}
|
|
2587
2988
|
}
|
|
2588
2989
|
async function writeState(cwd, state) {
|
|
2589
|
-
const path =
|
|
2990
|
+
const path = statePath2();
|
|
2590
2991
|
const tmp = `${path}.tmp`;
|
|
2591
2992
|
await mkdir(dirname(path), { recursive: true });
|
|
2592
2993
|
await writeFile(tmp, `${JSON.stringify(state, null, 2)}
|
|
@@ -2753,10 +3154,10 @@ async function spawnCmd(ctx, cmd, args, timeoutMs) {
|
|
|
2753
3154
|
child.stderr?.setEncoding("utf8").on("data", (chunk) => {
|
|
2754
3155
|
stderr += chunk;
|
|
2755
3156
|
});
|
|
2756
|
-
return await new Promise((
|
|
3157
|
+
return await new Promise((resolve16) => {
|
|
2757
3158
|
const timer = setTimeout(() => {
|
|
2758
3159
|
child.kill("SIGKILL");
|
|
2759
|
-
|
|
3160
|
+
resolve16({
|
|
2760
3161
|
ok: false,
|
|
2761
3162
|
phase: "spawn",
|
|
2762
3163
|
error: {
|
|
@@ -2771,7 +3172,7 @@ async function spawnCmd(ctx, cmd, args, timeoutMs) {
|
|
|
2771
3172
|
}, effectiveTimeoutMs);
|
|
2772
3173
|
child.on("error", (err2) => {
|
|
2773
3174
|
clearTimeout(timer);
|
|
2774
|
-
|
|
3175
|
+
resolve16({
|
|
2775
3176
|
ok: false,
|
|
2776
3177
|
phase: "spawn",
|
|
2777
3178
|
error: {
|
|
@@ -2786,7 +3187,7 @@ async function spawnCmd(ctx, cmd, args, timeoutMs) {
|
|
|
2786
3187
|
});
|
|
2787
3188
|
child.on("close", (code) => {
|
|
2788
3189
|
clearTimeout(timer);
|
|
2789
|
-
|
|
3190
|
+
resolve16({ ok: true, exitCode: code ?? -1, stdout: stdout2, stderr });
|
|
2790
3191
|
});
|
|
2791
3192
|
});
|
|
2792
3193
|
}
|
|
@@ -2899,7 +3300,7 @@ async function isAlreadyInstalled(ctx, opts = {}) {
|
|
|
2899
3300
|
// src/installers/ccPluginMarketplace.ts
|
|
2900
3301
|
init_readClaudeConfig();
|
|
2901
3302
|
function runArgs(claudeArgs, cwd, timeoutMs = 15e3) {
|
|
2902
|
-
return new Promise((
|
|
3303
|
+
return new Promise((resolve16) => {
|
|
2903
3304
|
const isWin = process.platform === "win32";
|
|
2904
3305
|
const child = isWin ? spawn("cmd.exe", ["/c", "claude", ...claudeArgs], { cwd, windowsHide: true }) : spawn("claude", claudeArgs, { cwd, shell: false });
|
|
2905
3306
|
let stderr = "";
|
|
@@ -2908,15 +3309,15 @@ function runArgs(claudeArgs, cwd, timeoutMs = 15e3) {
|
|
|
2908
3309
|
});
|
|
2909
3310
|
const timer = setTimeout(() => {
|
|
2910
3311
|
child.kill("SIGKILL");
|
|
2911
|
-
|
|
3312
|
+
resolve16({ exitCode: -1, stderr: `${stderr}[timeout after ${timeoutMs}ms]` });
|
|
2912
3313
|
}, timeoutMs);
|
|
2913
3314
|
child.on("error", (e) => {
|
|
2914
3315
|
clearTimeout(timer);
|
|
2915
|
-
|
|
3316
|
+
resolve16({ exitCode: -1, stderr: `${stderr}${e.message}` });
|
|
2916
3317
|
});
|
|
2917
3318
|
child.on("close", (code) => {
|
|
2918
3319
|
clearTimeout(timer);
|
|
2919
|
-
|
|
3320
|
+
resolve16({ exitCode: code ?? -1, stderr });
|
|
2920
3321
|
});
|
|
2921
3322
|
});
|
|
2922
3323
|
}
|
|
@@ -3062,7 +3463,7 @@ ${newEntry}
|
|
|
3062
3463
|
return { ok: true, backupId: bk.backupId, appliedFiles: [settingsFile] };
|
|
3063
3464
|
};
|
|
3064
3465
|
function gitRevParseHead(cwd, timeoutMs = 1e4) {
|
|
3065
|
-
return new Promise((
|
|
3466
|
+
return new Promise((resolve16) => {
|
|
3066
3467
|
const isWin = process.platform === "win32";
|
|
3067
3468
|
const child = isWin ? spawn("cmd.exe", ["/c", "git", "rev-parse", "HEAD"], { cwd, windowsHide: true }) : spawn("git", ["rev-parse", "HEAD"], { cwd, shell: false });
|
|
3068
3469
|
let stdout2 = "";
|
|
@@ -3071,15 +3472,15 @@ function gitRevParseHead(cwd, timeoutMs = 1e4) {
|
|
|
3071
3472
|
});
|
|
3072
3473
|
const timer = setTimeout(() => {
|
|
3073
3474
|
child.kill("SIGKILL");
|
|
3074
|
-
|
|
3475
|
+
resolve16({ sha: "", exit: -1 });
|
|
3075
3476
|
}, timeoutMs);
|
|
3076
3477
|
child.on("error", () => {
|
|
3077
3478
|
clearTimeout(timer);
|
|
3078
|
-
|
|
3479
|
+
resolve16({ sha: "", exit: -1 });
|
|
3079
3480
|
});
|
|
3080
3481
|
child.on("close", (code) => {
|
|
3081
3482
|
clearTimeout(timer);
|
|
3082
|
-
|
|
3483
|
+
resolve16({ sha: stdout2.trim(), exit: code ?? -1 });
|
|
3083
3484
|
});
|
|
3084
3485
|
});
|
|
3085
3486
|
}
|
|
@@ -3786,16 +4187,6 @@ async function runInstall(manifest, opts) {
|
|
|
3786
4187
|
|
|
3787
4188
|
// src/cli/install.ts
|
|
3788
4189
|
init_path_guard();
|
|
3789
|
-
function getPackageRoot() {
|
|
3790
|
-
const thisFile = fileURLToPath(import.meta.url);
|
|
3791
|
-
const thisDir = dirname(thisFile);
|
|
3792
|
-
if (thisDir.endsWith("dist") || thisDir.replace(/\\/g, "/").endsWith("/dist")) {
|
|
3793
|
-
return resolve(thisDir, "..");
|
|
3794
|
-
}
|
|
3795
|
-
return resolve(thisDir, "..", "..", "..");
|
|
3796
|
-
}
|
|
3797
|
-
|
|
3798
|
-
// src/cli/install.ts
|
|
3799
4190
|
function formatError(e) {
|
|
3800
4191
|
const head = `error: ${e.message}`;
|
|
3801
4192
|
const where = e.path && e.path !== "/" ? `
|
|
@@ -3954,67 +4345,37 @@ function registerManifestAdd(program2) {
|
|
|
3954
4345
|
process.exit(0);
|
|
3955
4346
|
}
|
|
3956
4347
|
const rl = readline.createInterface({ input: stdin, output: stdout });
|
|
3957
|
-
const payload = {
|
|
3958
|
-
upstream,
|
|
3959
|
-
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3960
|
-
author: process.env.USER ?? process.env.USERNAME ?? "unknown"
|
|
3961
|
-
};
|
|
3962
|
-
for (const { q, f } of QA) {
|
|
3963
|
-
const a = (await rl.question(`${q}
|
|
3964
|
-
> `)).trim();
|
|
3965
|
-
if (!a) {
|
|
3966
|
-
console.error(t("manifest_add.empty_answer"));
|
|
3967
|
-
rl.close();
|
|
3968
|
-
process.exit(1);
|
|
3969
|
-
}
|
|
3970
|
-
payload[f] = a;
|
|
3971
|
-
}
|
|
3972
|
-
rl.close();
|
|
3973
|
-
const dryRun = raw.dryRun === true;
|
|
3974
|
-
if (!dryRun) {
|
|
3975
|
-
writeFileSync(outPath, `${JSON.stringify(payload, null, 2)}
|
|
3976
|
-
`, "utf8");
|
|
3977
|
-
console.log(t("manifest_add.gate_passed_wrote", { path: outPath }));
|
|
3978
|
-
} else {
|
|
3979
|
-
console.log(t("manifest_add.gate_passed_dry_run", { path: outPath }));
|
|
3980
|
-
console.log(JSON.stringify(payload, null, 2));
|
|
3981
|
-
}
|
|
3982
|
-
process.exit(0);
|
|
3983
|
-
});
|
|
3984
|
-
}
|
|
3985
|
-
|
|
3986
|
-
// src/checkpoint/engineHook.ts
|
|
3987
|
-
init_harnessedRoot();
|
|
3988
|
-
init_schemaVersion();
|
|
3989
|
-
init_state();
|
|
3990
|
-
init_template();
|
|
3991
|
-
async function activatePhase(phaseId) {
|
|
3992
|
-
const checkpointPath = join(harnessedSubdir("checkpoints"), `${phaseId}.json`);
|
|
3993
|
-
await activate(phaseId, checkpointPath);
|
|
3994
|
-
return { checkpointPath };
|
|
3995
|
-
}
|
|
3996
|
-
async function completePhase(ctx) {
|
|
3997
|
-
if (ctx.phaseId === "unknown") {
|
|
3998
|
-
console.error(
|
|
3999
|
-
`[harnessed] WARN engineHook: phaseId="unknown" \u2014 checkpoint paths fall back to ${join(harnessedSubdir("checkpoints"), "unknown.json")} (Karpathy fail-loud non-blocking; W-04 mitigation)`
|
|
4000
|
-
);
|
|
4001
|
-
}
|
|
4002
|
-
writeCheckpoint({
|
|
4003
|
-
schemaVersion: SCHEMA_VERSIONS.checkpoint,
|
|
4004
|
-
phase: ctx.phaseId,
|
|
4005
|
-
status: "complete",
|
|
4006
|
-
last_task: ctx.lastTask ?? "engine.runRouting complete",
|
|
4007
|
-
key_decisions: ctx.keyDecisions ?? [],
|
|
4008
|
-
canonical_refs: ctx.canonicalRefs ?? [],
|
|
4009
|
-
...ctx.sessionId ? { session_id: ctx.sessionId } : {},
|
|
4010
|
-
cwd: process.cwd(),
|
|
4011
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4012
|
-
archive_path: `${join(harnessedSubdir("archive"), `phase-${ctx.phaseId}`)}/`
|
|
4348
|
+
const payload = {
|
|
4349
|
+
upstream,
|
|
4350
|
+
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4351
|
+
author: process.env.USER ?? process.env.USERNAME ?? "unknown"
|
|
4352
|
+
};
|
|
4353
|
+
for (const { q, f } of QA) {
|
|
4354
|
+
const a = (await rl.question(`${q}
|
|
4355
|
+
> `)).trim();
|
|
4356
|
+
if (!a) {
|
|
4357
|
+
console.error(t("manifest_add.empty_answer"));
|
|
4358
|
+
rl.close();
|
|
4359
|
+
process.exit(1);
|
|
4360
|
+
}
|
|
4361
|
+
payload[f] = a;
|
|
4362
|
+
}
|
|
4363
|
+
rl.close();
|
|
4364
|
+
const dryRun = raw.dryRun === true;
|
|
4365
|
+
if (!dryRun) {
|
|
4366
|
+
writeFileSync(outPath, `${JSON.stringify(payload, null, 2)}
|
|
4367
|
+
`, "utf8");
|
|
4368
|
+
console.log(t("manifest_add.gate_passed_wrote", { path: outPath }));
|
|
4369
|
+
} else {
|
|
4370
|
+
console.log(t("manifest_add.gate_passed_dry_run", { path: outPath }));
|
|
4371
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
4372
|
+
}
|
|
4373
|
+
process.exit(0);
|
|
4013
4374
|
});
|
|
4014
|
-
await complete();
|
|
4015
4375
|
}
|
|
4016
4376
|
|
|
4017
4377
|
// src/workflow/run.ts
|
|
4378
|
+
init_engineHook();
|
|
4018
4379
|
init_state();
|
|
4019
4380
|
async function loadRolePrompts(workflowsDir) {
|
|
4020
4381
|
const path = join(workflowsDir, "role-prompts.yaml");
|
|
@@ -4027,36 +4388,123 @@ async function loadRolePrompts(workflowsDir) {
|
|
|
4027
4388
|
const doc = parse(raw);
|
|
4028
4389
|
return doc?.prompts ?? {};
|
|
4029
4390
|
}
|
|
4030
|
-
|
|
4031
|
-
|
|
4032
|
-
|
|
4033
|
-
|
|
4034
|
-
|
|
4391
|
+
var INTERACTIVE_COMMANDS = /* @__PURE__ */ new Set([
|
|
4392
|
+
"discuss",
|
|
4393
|
+
"discuss-strategic",
|
|
4394
|
+
"discuss-phase",
|
|
4395
|
+
"discuss-subtask",
|
|
4396
|
+
"task-clarify"
|
|
4397
|
+
]);
|
|
4398
|
+
var ORCHESTRATOR_COMMANDS = /* @__PURE__ */ new Set(["auto", "plan", "task", "verify"]);
|
|
4399
|
+
var MARKER = `<!-- harnessed-generated:v3.4.4 -->`;
|
|
4400
|
+
function spawnLoopSteps(indent) {
|
|
4401
|
+
const i = indent;
|
|
4402
|
+
return [
|
|
4403
|
+
`${i}a. Bash: \`harnessed prompt <sub> --task "<spec>" --json\` \u2192 parse \`{prompt, max_iterations, model}\`.`,
|
|
4404
|
+
`${i}b. Spawn a CC-native subagent (Task / Agent tool) with that \`prompt\` and \`model\`. Wrap in the ralph-loop plugin for completion-promise enforcement:`,
|
|
4405
|
+
`${i} \`/ralph-loop "<prompt>" --max-iterations <max_iterations> --completion-promise "COMPLETE"\``,
|
|
4406
|
+
`${i} If the ralph-loop plugin is not installed, self-loop: spawn \u2192 check output for \`<promise>COMPLETE</promise>\` \u2192 if absent, re-spawn with the prior output appended (up to max_iterations).`,
|
|
4407
|
+
`${i}c. If the subagent output contains \`STATUS: NEEDS_CLARIFICATION\` + a question list: STOP. Use AskUserQuestion to relay those exact questions to the user. Append the user's answers to the spec, then re-spawn the same sub. (This is the round-trip headless spawn cannot do.)`,
|
|
4408
|
+
`${i}d. On \`<promise>COMPLETE</promise>\`: Bash \`harnessed checkpoint complete <sub> --summary "<one-line>"\`.`
|
|
4409
|
+
];
|
|
4410
|
+
}
|
|
4411
|
+
function buildInteractiveBody(name, prompt) {
|
|
4412
|
+
return [
|
|
4413
|
+
`# /${name}`,
|
|
4414
|
+
``,
|
|
4415
|
+
prompt.description,
|
|
4416
|
+
``,
|
|
4417
|
+
`## How to run (interactive \u2014 in THIS session)`,
|
|
4418
|
+
``,
|
|
4419
|
+
`Clarification requires real user dialogue. Spawned subagents cannot ask the user questions, so run this stage directly in this session \u2014 do NOT spawn it.`,
|
|
4420
|
+
``,
|
|
4421
|
+
`1. Evaluate the clarification criteria for "$ARGUMENTS":`,
|
|
4422
|
+
` - Strategic layer: new feature / new milestone / unclear business scope \u2192 run gstack \`/office-hours\` + \`/plan-ceo-review\``,
|
|
4423
|
+
` - Phase layer: \u22652 open implementation decisions / unclear cross-phase API contract \u2192 run GSD \`/gsd-discuss-phase\``,
|
|
4424
|
+
` - Subtask layer: \u22652 distinct approaches / core algorithm / API contract design / high error cost \u2192 run superpowers brainstorming`,
|
|
4425
|
+
`2. For each layer that fires, hold the dialogue with the user (use AskUserQuestion for option-style decisions). Lock every open decision.`,
|
|
4426
|
+
`3. Skip layers that don't fire \u2014 state which were skipped and why (transparent skip).`,
|
|
4427
|
+
`4. Persist locked decisions to \`.planning/\` via planning-with-files (\`findings.md\` / \`task_plan.md\`).`,
|
|
4428
|
+
``,
|
|
4429
|
+
`Output: a locked spec the execution stages can consume without further user input. Then run \`/plan\` \u2192 \`/task\` \u2192 \`/verify\` with that spec.`,
|
|
4430
|
+
``,
|
|
4431
|
+
`## Notes`,
|
|
4432
|
+
``,
|
|
4433
|
+
`- Generated by \`harnessed setup\`. Re-run after a harnessed upgrade to refresh.`,
|
|
4434
|
+
``,
|
|
4435
|
+
MARKER,
|
|
4436
|
+
``
|
|
4437
|
+
].join("\n");
|
|
4438
|
+
}
|
|
4439
|
+
function buildOrchestratorBody(name, prompt) {
|
|
4440
|
+
const autoDiscussStep = name === "auto" ? [
|
|
4441
|
+
`1. FIRST run the discuss stage interactively in THIS session (spawned subagents cannot ask the user questions). Evaluate strategic / phase / subtask clarification criteria for "$ARGUMENTS"; for each that fires, dialogue with the user (AskUserQuestion) and lock decisions; transparent-skip the rest. Produce a locked spec.`
|
|
4442
|
+
] : [
|
|
4443
|
+
`1. If the clarification criteria fire for "$ARGUMENTS" (\u22652 approaches / core algorithm / API contract / high error cost), clarify interactively in THIS session first (AskUserQuestion) and lock decisions. Otherwise transparent-skip. Produce a locked spec.`
|
|
4444
|
+
];
|
|
4445
|
+
return [
|
|
4035
4446
|
`# /${name}`,
|
|
4036
4447
|
``,
|
|
4037
4448
|
prompt.description,
|
|
4038
4449
|
``,
|
|
4039
|
-
`## How to
|
|
4450
|
+
`## How to run (orchestrator \u2014 clarify in THIS session, then drive native subagent spawns)`,
|
|
4451
|
+
``,
|
|
4452
|
+
`harnessed is the orchestration brain: \`harnessed gates\` tells you which subs fire, \`harnessed prompt\` gives you each sub's spawn-ready prompt. YOU (the main session) do the spawning with CC-native Task / Agent tools \u2014 keeping the session responsive, enabling Agent Teams, and letting clarification round-trips reach the user.`,
|
|
4453
|
+
``,
|
|
4454
|
+
...autoDiscussStep,
|
|
4455
|
+
`2. Bash: \`harnessed gates ${name} --task "<locked spec>" --skip-sub clarify\` \u2192 parse the JSON \`{fire: [{sub, order, mode}], skip, parallelism: {escalate_to_teams}}\`.`,
|
|
4456
|
+
`3. If \`parallelism.escalate_to_teams === true\`: this stage needs multiple subagents to coordinate (SendMessage / shared contract). Read \`~/.claude/rules/agent-teams.md\`, then \`TeamCreate\` \u2192 \`Agent(name, team_name, ...)\` per fired sub (prompt from \`harnessed prompt <sub>\`) \u2192 coordinate via SendMessage \u2192 \`SendMessage shutdown_request\` + \`TeamDelete\`.`,
|
|
4457
|
+
`4. Otherwise, for each fired sub in \`order\` (serial subs sequentially, parallel subs concurrently via parallel Task calls):`,
|
|
4458
|
+
...spawnLoopSteps(" "),
|
|
4459
|
+
`5. Report a per-sub fired/skipped summary to the user. ${name === "auto" ? "Then run the `retro` stage to capture lessons." : ""}`,
|
|
4460
|
+
``,
|
|
4461
|
+
`Do NOT pipe to \`harnessed run ${name}\` \u2014 that is the CI/headless path (SDK spawn, blocks the session, no Agent Teams, no clarification round-trip).`,
|
|
4462
|
+
``,
|
|
4463
|
+
`## Notes`,
|
|
4464
|
+
``,
|
|
4465
|
+
`- Generated by \`harnessed setup\`. Re-run after a harnessed upgrade to refresh.`,
|
|
4466
|
+
`- gate/discipline SoT: \`workflows/${nameToYamlHintPath(name)}\` + \`workflows/judgments/\` \u2014 consumed by \`harnessed gates\` + \`harnessed prompt\`.`,
|
|
4467
|
+
``,
|
|
4468
|
+
MARKER,
|
|
4469
|
+
``
|
|
4470
|
+
].join("\n");
|
|
4471
|
+
}
|
|
4472
|
+
function buildExecutionBody(name, prompt) {
|
|
4473
|
+
return [
|
|
4474
|
+
`# /${name}`,
|
|
4475
|
+
``,
|
|
4476
|
+
prompt.description,
|
|
4040
4477
|
``,
|
|
4041
|
-
|
|
4478
|
+
`## How to run (execution \u2014 native subagent spawn)`,
|
|
4042
4479
|
``,
|
|
4043
|
-
|
|
4044
|
-
`echo "$ARGUMENTS" | harnessed run ${name} --task-stdin`,
|
|
4045
|
-
"```",
|
|
4480
|
+
`harnessed gives you the spawn-ready prompt; YOU spawn the subagent with a CC-native Task / Agent tool (keeps the session responsive + lets clarification round-trips reach the user).`,
|
|
4046
4481
|
``,
|
|
4047
|
-
|
|
4482
|
+
...spawnLoopSteps("").map(
|
|
4483
|
+
(s) => s.replace("<sub>", name).replace('--task "<spec>"', '--task "$ARGUMENTS"')
|
|
4484
|
+
),
|
|
4048
4485
|
``,
|
|
4049
|
-
`
|
|
4486
|
+
`Do NOT pipe to \`harnessed run ${name}\` \u2014 that is the CI/headless path (SDK spawn).`,
|
|
4050
4487
|
``,
|
|
4051
4488
|
`## Notes`,
|
|
4052
4489
|
``,
|
|
4053
|
-
`-
|
|
4054
|
-
`- The sister \`~/.claude/skills/${name}/SKILL.md\` is the Skill-tool entry point (Claude loads it when triggers match
|
|
4055
|
-
`- Workflow runtime: \`src/workflow/run.ts\` walks \`workflows/${nameToYamlHintPath(name)}\` with disciplines + judgments + master orchestration applied per the yaml \`delegates_to[]\` + \`gate\` clauses.`,
|
|
4490
|
+
`- Generated by \`harnessed setup\`. Re-run after a harnessed upgrade to refresh.`,
|
|
4491
|
+
`- The sister \`~/.claude/skills/${name}/SKILL.md\` is the Skill-tool entry point (Claude loads it when triggers match). gate/discipline SoT: \`workflows/${nameToYamlHintPath(name)}\`.`,
|
|
4056
4492
|
``,
|
|
4057
|
-
|
|
4493
|
+
MARKER,
|
|
4058
4494
|
``
|
|
4059
4495
|
].join("\n");
|
|
4496
|
+
}
|
|
4497
|
+
function generateCommandFile(name, prompt, _capabilities, _installedPlugins, _installedUserSkills) {
|
|
4498
|
+
const isMaster = prompt.is_master === true;
|
|
4499
|
+
const argHint = isMaster ? "[task description]" : "[requirement text or omit]";
|
|
4500
|
+
let body;
|
|
4501
|
+
if (INTERACTIVE_COMMANDS.has(name)) {
|
|
4502
|
+
body = buildInteractiveBody(name, prompt);
|
|
4503
|
+
} else if (ORCHESTRATOR_COMMANDS.has(name)) {
|
|
4504
|
+
body = buildOrchestratorBody(name, prompt);
|
|
4505
|
+
} else {
|
|
4506
|
+
body = buildExecutionBody(name, prompt);
|
|
4507
|
+
}
|
|
4060
4508
|
const warnings = [];
|
|
4061
4509
|
const frontmatter = [
|
|
4062
4510
|
"---",
|
|
@@ -4344,159 +4792,6 @@ async function readGovernance() {
|
|
|
4344
4792
|
async function isVetoed() {
|
|
4345
4793
|
return (await readGovernance())?.status === "vetoed";
|
|
4346
4794
|
}
|
|
4347
|
-
var PARSER_OPTIONS = {
|
|
4348
|
-
operators: {
|
|
4349
|
-
add: false,
|
|
4350
|
-
subtract: false,
|
|
4351
|
-
multiply: false,
|
|
4352
|
-
divide: false,
|
|
4353
|
-
logical: true,
|
|
4354
|
-
comparison: true,
|
|
4355
|
-
in: true,
|
|
4356
|
-
assignment: false
|
|
4357
|
-
}
|
|
4358
|
-
};
|
|
4359
|
-
var _parserSingleton = new Parser(PARSER_OPTIONS);
|
|
4360
|
-
var GateEvalError = class extends Error {
|
|
4361
|
-
constructor(message, expression) {
|
|
4362
|
-
super(message);
|
|
4363
|
-
this.expression = expression;
|
|
4364
|
-
this.name = "GateEvalError";
|
|
4365
|
-
}
|
|
4366
|
-
expression;
|
|
4367
|
-
};
|
|
4368
|
-
function evalGate(expression, context) {
|
|
4369
|
-
try {
|
|
4370
|
-
const parsed = _parserSingleton.parse(expression);
|
|
4371
|
-
const result = parsed.evaluate(context);
|
|
4372
|
-
if (typeof result !== "boolean") {
|
|
4373
|
-
throw new GateEvalError(
|
|
4374
|
-
`Expression must evaluate to boolean, got ${typeof result}`,
|
|
4375
|
-
expression
|
|
4376
|
-
);
|
|
4377
|
-
}
|
|
4378
|
-
return result;
|
|
4379
|
-
} catch (err2) {
|
|
4380
|
-
if (err2 instanceof GateEvalError) throw err2;
|
|
4381
|
-
throw new GateEvalError(`Gate eval failed: ${err2.message}`, expression);
|
|
4382
|
-
}
|
|
4383
|
-
}
|
|
4384
|
-
|
|
4385
|
-
// src/workflow/schema/judgment.ts
|
|
4386
|
-
init_schemaVersion();
|
|
4387
|
-
var TriggerInvocation = Type.Object(
|
|
4388
|
-
{
|
|
4389
|
-
capability: Type.String()
|
|
4390
|
-
},
|
|
4391
|
-
{ additionalProperties: false }
|
|
4392
|
-
);
|
|
4393
|
-
var RequiresCapabilities = Type.Object(
|
|
4394
|
-
{
|
|
4395
|
-
capabilities: Type.Array(Type.String())
|
|
4396
|
-
},
|
|
4397
|
-
{ additionalProperties: false }
|
|
4398
|
-
);
|
|
4399
|
-
var JudgmentTrigger = Type.Object(
|
|
4400
|
-
{
|
|
4401
|
-
description: Type.Optional(Type.String()),
|
|
4402
|
-
fires_when: Type.Optional(Type.String()),
|
|
4403
|
-
skips_when: Type.Optional(Type.String()),
|
|
4404
|
-
invokes: Type.Optional(Type.Array(TriggerInvocation)),
|
|
4405
|
-
requires: Type.Optional(RequiresCapabilities),
|
|
4406
|
-
wraps: Type.Optional(Type.Array(Type.String()))
|
|
4407
|
-
},
|
|
4408
|
-
{ additionalProperties: false }
|
|
4409
|
-
);
|
|
4410
|
-
var FallbackRule = Type.Object(
|
|
4411
|
-
{
|
|
4412
|
-
description: Type.Optional(Type.String()),
|
|
4413
|
-
fallback_action: Type.Optional(Type.String()),
|
|
4414
|
-
message_template: Type.Optional(Type.String()),
|
|
4415
|
-
override_signal: Type.Optional(Type.Array(Type.String())),
|
|
4416
|
-
chain_isolation: Type.Optional(Type.Boolean())
|
|
4417
|
-
},
|
|
4418
|
-
{ additionalProperties: false }
|
|
4419
|
-
);
|
|
4420
|
-
var JudgmentTriggersFile = Type.Object(
|
|
4421
|
-
{
|
|
4422
|
-
schema_version: Type.Literal(SCHEMA_VERSIONS.judgment),
|
|
4423
|
-
triggers: Type.Record(Type.String(), JudgmentTrigger)
|
|
4424
|
-
},
|
|
4425
|
-
{ additionalProperties: false }
|
|
4426
|
-
);
|
|
4427
|
-
var JudgmentRulesFile = Type.Object(
|
|
4428
|
-
{
|
|
4429
|
-
schema_version: Type.Literal(SCHEMA_VERSIONS.judgment),
|
|
4430
|
-
rules: Type.Record(Type.String(), FallbackRule)
|
|
4431
|
-
},
|
|
4432
|
-
{ additionalProperties: false }
|
|
4433
|
-
);
|
|
4434
|
-
Type.Union([JudgmentTriggersFile, JudgmentRulesFile]);
|
|
4435
|
-
var UserOverrideEntry = Type.Object(
|
|
4436
|
-
{
|
|
4437
|
-
id: Type.String({ minLength: 1 }),
|
|
4438
|
-
// kebab-case (e.g. 'brainstorm', 'arch-review')
|
|
4439
|
-
keywords: Type.Array(Type.String({ minLength: 1 }), { minItems: 1 }),
|
|
4440
|
-
triggers: Type.Array(Type.String({ minLength: 1 }), { minItems: 1 })
|
|
4441
|
-
},
|
|
4442
|
-
{ additionalProperties: false }
|
|
4443
|
-
);
|
|
4444
|
-
var UserOverridesFile = Type.Object(
|
|
4445
|
-
{
|
|
4446
|
-
schema_version: Type.Literal("harnessed.user-overrides.v1"),
|
|
4447
|
-
overrides: Type.Array(UserOverrideEntry, { minItems: 1 })
|
|
4448
|
-
},
|
|
4449
|
-
{ additionalProperties: false }
|
|
4450
|
-
);
|
|
4451
|
-
|
|
4452
|
-
// src/workflow/judgmentResolver.ts
|
|
4453
|
-
var TriggerNotFoundError = class extends Error {
|
|
4454
|
-
constructor(trigger, fileName) {
|
|
4455
|
-
super(`Trigger '${trigger}' not found in judgments/${fileName}.yaml`);
|
|
4456
|
-
this.trigger = trigger;
|
|
4457
|
-
this.fileName = fileName;
|
|
4458
|
-
this.name = "TriggerNotFoundError";
|
|
4459
|
-
}
|
|
4460
|
-
trigger;
|
|
4461
|
-
fileName;
|
|
4462
|
-
};
|
|
4463
|
-
var _fileCache = /* @__PURE__ */ new Map();
|
|
4464
|
-
async function resolveJudgmentGate(gateRef, context, packageRoot) {
|
|
4465
|
-
const userOverrides = context.user_overrides;
|
|
4466
|
-
if (Array.isArray(userOverrides) && userOverrides.includes(gateRef)) {
|
|
4467
|
-
return true;
|
|
4468
|
-
}
|
|
4469
|
-
const parts = gateRef.split(".");
|
|
4470
|
-
if (parts.length !== 4 || parts[0] !== "judgments") {
|
|
4471
|
-
throw new Error(`Invalid gate ref: ${gateRef}`);
|
|
4472
|
-
}
|
|
4473
|
-
const [, fileName, triggerName, fieldName] = parts;
|
|
4474
|
-
let parsed = _fileCache.get(fileName);
|
|
4475
|
-
if (!parsed) {
|
|
4476
|
-
const yamlPath = resolve(packageRoot, "workflows", "judgments", `${fileName}.yaml`);
|
|
4477
|
-
const raw = await readFile(yamlPath, "utf8");
|
|
4478
|
-
const parsedRaw = parse(raw);
|
|
4479
|
-
const schema = fileName === "fallback" ? JudgmentRulesFile : JudgmentTriggersFile;
|
|
4480
|
-
if (!Value.Check(schema, parsedRaw)) {
|
|
4481
|
-
const errors = [...Value.Errors(schema, parsedRaw)].slice(0, 3).map((e) => `${e.path} ${e.message}`).join("; ");
|
|
4482
|
-
throw new Error(`Invalid judgment file ${fileName}.yaml: ${errors}`);
|
|
4483
|
-
}
|
|
4484
|
-
parsed = parsedRaw;
|
|
4485
|
-
_fileCache.set(fileName, parsed);
|
|
4486
|
-
}
|
|
4487
|
-
const entries = "triggers" in parsed ? parsed.triggers : parsed.rules;
|
|
4488
|
-
const trigger = entries[triggerName];
|
|
4489
|
-
if (!trigger) {
|
|
4490
|
-
throw new TriggerNotFoundError(triggerName, fileName);
|
|
4491
|
-
}
|
|
4492
|
-
const expr = fieldName === "fires" ? trigger.fires_when : fieldName === "skips" ? trigger.skips_when : void 0;
|
|
4493
|
-
if (!expr) {
|
|
4494
|
-
throw new Error(
|
|
4495
|
-
`Field '${fieldName}' has no expression in trigger '${triggerName}' of ${fileName}.yaml`
|
|
4496
|
-
);
|
|
4497
|
-
}
|
|
4498
|
-
return evalGate(expr, context);
|
|
4499
|
-
}
|
|
4500
4795
|
|
|
4501
4796
|
// src/workflow/lib/promiseExtract.ts
|
|
4502
4797
|
var PROMISE_PATTERN = /<promise>([^<]+)<\/promise>/;
|
|
@@ -4917,7 +5212,7 @@ function loadPhases(yamlPath, vars) {
|
|
|
4917
5212
|
}
|
|
4918
5213
|
return validated;
|
|
4919
5214
|
}
|
|
4920
|
-
function
|
|
5215
|
+
function resolveMasterYamlPath2(masterName, packageRoot) {
|
|
4921
5216
|
return masterName === "auto" ? resolve(packageRoot, "workflows", "auto", "workflow.yaml") : resolve(packageRoot, "workflows", masterName, "auto", "workflow.yaml");
|
|
4922
5217
|
}
|
|
4923
5218
|
function resolveSubYamlPath(masterName, subName, packageRoot) {
|
|
@@ -5021,7 +5316,7 @@ async function maybeArbitrate(firedClauses, packageRoot) {
|
|
|
5021
5316
|
|
|
5022
5317
|
// src/workflow/masterOrchestrator.ts
|
|
5023
5318
|
async function runMasterOrchestrator(masterName, context, packageRoot, spawnDriver = defaultSpawnDriver, opts = {}) {
|
|
5024
|
-
const yamlPath =
|
|
5319
|
+
const yamlPath = resolveMasterYamlPath2(masterName, packageRoot);
|
|
5025
5320
|
const raw = await readFile(yamlPath, "utf8");
|
|
5026
5321
|
const parsed = parse(raw);
|
|
5027
5322
|
if (!Value.Check(WorkflowSchemaV3, parsed)) {
|
|
@@ -5042,8 +5337,17 @@ async function runMasterOrchestrator(masterName, context, packageRoot, spawnDriv
|
|
|
5042
5337
|
}
|
|
5043
5338
|
effectiveOpts = pre.opts;
|
|
5044
5339
|
}
|
|
5340
|
+
const skipSubs = new Set(Array.isArray(context.skip_subs) ? context.skip_subs : []);
|
|
5045
5341
|
const gateEvalled = [];
|
|
5046
5342
|
for (const clause of master.delegates_to) {
|
|
5343
|
+
if (skipSubs.has(clause.sub)) {
|
|
5344
|
+
gateEvalled.push({
|
|
5345
|
+
clause,
|
|
5346
|
+
passes: false,
|
|
5347
|
+
reason: `skipped via skip_subs (done interactively in main session)`
|
|
5348
|
+
});
|
|
5349
|
+
continue;
|
|
5350
|
+
}
|
|
5047
5351
|
if (!clause.gate) {
|
|
5048
5352
|
gateEvalled.push({ clause, passes: true });
|
|
5049
5353
|
continue;
|
|
@@ -5395,6 +5699,78 @@ async function runWorkflow(yamlPath, vars, opts = {}) {
|
|
|
5395
5699
|
...skippedPhases.length > 0 ? { skippedPhases } : {}
|
|
5396
5700
|
};
|
|
5397
5701
|
}
|
|
5702
|
+
|
|
5703
|
+
// src/cli/prompt.ts
|
|
5704
|
+
var DEFAULT_MAX_ITERATIONS = 20;
|
|
5705
|
+
var DEFAULT_MODEL = "sonnet";
|
|
5706
|
+
var DEFAULT_SPECIALIST = "Implementation Engineer";
|
|
5707
|
+
var PROTOCOLS = `
|
|
5708
|
+
## Completion protocol
|
|
5709
|
+
When done emit: <promise>COMPLETE</promise>
|
|
5710
|
+
|
|
5711
|
+
## Clarification protocol
|
|
5712
|
+
If you hit a gray area you cannot decide (\u22652 reasonable options, missing key context, ambiguous requirement), do NOT self-decide. Emit:
|
|
5713
|
+
STATUS: NEEDS_CLARIFICATION
|
|
5714
|
+
1. <question>
|
|
5715
|
+
2. <question>
|
|
5716
|
+
The main session will relay these to the user and re-spawn you with answers.
|
|
5717
|
+
`;
|
|
5718
|
+
async function resolveMaxIterations2(sub, packageRoot) {
|
|
5719
|
+
try {
|
|
5720
|
+
const path = resolve(packageRoot, "workflows", "defaults.yaml");
|
|
5721
|
+
const raw = await readFile(path, "utf8");
|
|
5722
|
+
const doc = parse(raw);
|
|
5723
|
+
const entry = doc?.ralph_max_iterations?.[sub];
|
|
5724
|
+
if (typeof entry === "number" && Number.isFinite(entry)) {
|
|
5725
|
+
return entry;
|
|
5726
|
+
}
|
|
5727
|
+
if (entry && typeof entry === "object") {
|
|
5728
|
+
for (const v of Object.values(entry)) {
|
|
5729
|
+
if (typeof v === "number" && Number.isFinite(v)) {
|
|
5730
|
+
return v;
|
|
5731
|
+
}
|
|
5732
|
+
}
|
|
5733
|
+
}
|
|
5734
|
+
return DEFAULT_MAX_ITERATIONS;
|
|
5735
|
+
} catch {
|
|
5736
|
+
return DEFAULT_MAX_ITERATIONS;
|
|
5737
|
+
}
|
|
5738
|
+
}
|
|
5739
|
+
function registerPrompt(program2) {
|
|
5740
|
+
program2.command("prompt").description(
|
|
5741
|
+
"Print a spawn-ready prompt for a sub-workflow (role + checklist + protocols, no spawn)."
|
|
5742
|
+
).argument("<sub>", "sub-workflow name (e.g. task-code, verify-paranoid)").option("--task <text>", "task description prepended as a ## Task section").option("--json", "emit JSON {prompt, max_iterations, model, specialist} instead of text").action(async (sub, raw) => {
|
|
5743
|
+
const packageRoot = getPackageRoot();
|
|
5744
|
+
const workflowsDir = resolve(packageRoot, "workflows");
|
|
5745
|
+
const rolePrompts = await loadRolePrompts(workflowsDir);
|
|
5746
|
+
const def = buildAgentDef(sub, rolePrompts, void 0, void 0, void 0);
|
|
5747
|
+
const body = def.prompt;
|
|
5748
|
+
const taskSection = typeof raw.task === "string" && raw.task.length > 0 ? `## Task
|
|
5749
|
+
${raw.task}
|
|
5750
|
+
|
|
5751
|
+
` : "";
|
|
5752
|
+
const fullPrompt = `${taskSection}${body}
|
|
5753
|
+
${PROTOCOLS}`;
|
|
5754
|
+
if (raw.json) {
|
|
5755
|
+
const maxIterations = await resolveMaxIterations2(sub, packageRoot);
|
|
5756
|
+
const rp = rolePrompts[sub];
|
|
5757
|
+
const model = def.model ?? DEFAULT_MODEL;
|
|
5758
|
+
const specialist = rp?.specialist ?? DEFAULT_SPECIALIST;
|
|
5759
|
+
console.log(
|
|
5760
|
+
JSON.stringify({
|
|
5761
|
+
prompt: fullPrompt,
|
|
5762
|
+
max_iterations: maxIterations,
|
|
5763
|
+
model,
|
|
5764
|
+
specialist
|
|
5765
|
+
})
|
|
5766
|
+
);
|
|
5767
|
+
process.exit(0);
|
|
5768
|
+
return;
|
|
5769
|
+
}
|
|
5770
|
+
console.log(fullPrompt);
|
|
5771
|
+
process.exit(0);
|
|
5772
|
+
});
|
|
5773
|
+
}
|
|
5398
5774
|
async function loadUserOverrides(packageRoot) {
|
|
5399
5775
|
const yamlPath = resolve(packageRoot, "workflows", "judgments", "user-overrides.yaml");
|
|
5400
5776
|
let raw;
|
|
@@ -5441,7 +5817,7 @@ var _autoChainCache = null;
|
|
|
5441
5817
|
var _autoChainLoadFailed = false;
|
|
5442
5818
|
function registerRun(program2) {
|
|
5443
5819
|
program2.command("run").description(
|
|
5444
|
-
"Run a harnessed workflow (
|
|
5820
|
+
"Run a harnessed workflow via in-process SDK spawn (CI/headless mode \u2014 blocks, no Agent Teams, no clarification round-trip). Slash commands use CC-native spawn via gates/prompt/checkpoint instead."
|
|
5445
5821
|
).argument("[name]", "workflow name (e.g. discuss, verify-paranoid, research, auto)").option("--task <text>", "task description (passed as workflow gateContext.task)").option("--task-stdin", "read task description from stdin until EOF (avoids shell-escape)").option(
|
|
5446
5822
|
"--max-iterations <n>",
|
|
5447
5823
|
"ralph-loop max iter (default 20; honored Phase 3 onward)",
|
|
@@ -5449,7 +5825,10 @@ function registerRun(program2) {
|
|
|
5449
5825
|
).option("--model <model>", "subagent model: 'haiku' | 'sonnet' | 'opus'").option(
|
|
5450
5826
|
"--dry-run",
|
|
5451
5827
|
"validate yaml load + walk runtime without spawning (Phase 1 default for verification)"
|
|
5452
|
-
).option("--staged", "/auto super-master: pause between stages for user review").option(
|
|
5828
|
+
).option("--staged", "/auto super-master: pause between stages for user review").option(
|
|
5829
|
+
"--skip-sub <names>",
|
|
5830
|
+
"comma-separated sub names to skip (work already done interactively in main session, e.g. clarify)"
|
|
5831
|
+
).option("--list", "print all known workflow names and exit").action(async (name, raw) => {
|
|
5453
5832
|
if (raw.list) {
|
|
5454
5833
|
const names = await listWorkflowNames(WORKFLOWS_DIR);
|
|
5455
5834
|
for (const n of names) console.log(n);
|
|
@@ -5541,6 +5920,10 @@ function registerRun(program2) {
|
|
|
5541
5920
|
...raw.model ? { modelOverride: raw.model } : {},
|
|
5542
5921
|
...raw.maxIterations ? { maxIterations: raw.maxIterations } : {},
|
|
5543
5922
|
...raw.staged ? { staged: true } : {},
|
|
5923
|
+
// v3.9.26 Option A — skip subs done interactively in main session.
|
|
5924
|
+
...typeof raw.skipSub === "string" && raw.skipSub.length > 0 ? {
|
|
5925
|
+
skip_subs: raw.skipSub.split(",").map((s) => s.trim()).filter((s) => s.length > 0)
|
|
5926
|
+
} : {},
|
|
5544
5927
|
...matchedTriggers.length > 0 ? { user_overrides: matchedTriggers } : {}
|
|
5545
5928
|
};
|
|
5546
5929
|
if (raw.dryRun) {
|
|
@@ -6279,20 +6662,19 @@ function printGrouped(b, prefix = "") {
|
|
|
6279
6662
|
const GROUP_ORDER = ["mcp-tool", "command", "cli-binary", "other"];
|
|
6280
6663
|
const groupOf = (n) => b.componentTypes[n] ?? "other";
|
|
6281
6664
|
const buckets = {};
|
|
6665
|
+
const push = (group, entry) => {
|
|
6666
|
+
if (!buckets[group]) buckets[group] = [];
|
|
6667
|
+
buckets[group].push(entry);
|
|
6668
|
+
};
|
|
6282
6669
|
for (const n of b.failed) {
|
|
6283
6670
|
const m = n.match(/^([^:]+):/);
|
|
6284
6671
|
const baseName = m?.[1] ?? n;
|
|
6285
|
-
(
|
|
6672
|
+
push(groupOf(baseName), { status: "failed", name: n });
|
|
6286
6673
|
}
|
|
6287
|
-
for (const n of b.installed) (
|
|
6674
|
+
for (const n of b.installed) push(groupOf(n), { status: "installed", name: n });
|
|
6288
6675
|
for (const s of b.skipped)
|
|
6289
|
-
(
|
|
6290
|
-
|
|
6291
|
-
name: s.name,
|
|
6292
|
-
suffix: ` \u2014 ${s.reason}`
|
|
6293
|
-
});
|
|
6294
|
-
for (const n of b.alreadyInstalled)
|
|
6295
|
-
(buckets[groupOf(n)] ??= []).push({ status: "already-installed", name: n });
|
|
6676
|
+
push(groupOf(s.name), { status: "skipped", name: s.name, suffix: ` \u2014 ${s.reason}` });
|
|
6677
|
+
for (const n of b.alreadyInstalled) push(groupOf(n), { status: "already-installed", name: n });
|
|
6296
6678
|
for (const g of GROUP_ORDER) {
|
|
6297
6679
|
const entries = buckets[g];
|
|
6298
6680
|
if (!entries || entries.length === 0) continue;
|
|
@@ -6682,7 +7064,7 @@ var uninstallNpmCli = async (ctx) => {
|
|
|
6682
7064
|
const m = install.cmd.match(/npm\s+(?:install|i)\s+(?:-g\s+)?(\S+)/);
|
|
6683
7065
|
const pkg = m?.[1] ?? ctx.manifest.metadata.upstream.source;
|
|
6684
7066
|
const isWin = process.platform === "win32";
|
|
6685
|
-
const result = await new Promise((
|
|
7067
|
+
const result = await new Promise((resolve16) => {
|
|
6686
7068
|
const child = isWin ? spawn("cmd.exe", ["/c", "npm", "uninstall", "-g", pkg], { windowsHide: true }) : spawn("npm", ["uninstall", "-g", pkg], { shell: false });
|
|
6687
7069
|
let stderr = "";
|
|
6688
7070
|
child.stderr?.setEncoding("utf8").on("data", (c) => {
|
|
@@ -6690,15 +7072,15 @@ var uninstallNpmCli = async (ctx) => {
|
|
|
6690
7072
|
});
|
|
6691
7073
|
const timer = setTimeout(() => {
|
|
6692
7074
|
child.kill("SIGKILL");
|
|
6693
|
-
|
|
7075
|
+
resolve16({ exitCode: -1, stderr: `${stderr}[timeout]` });
|
|
6694
7076
|
}, 3e4);
|
|
6695
7077
|
child.on("error", (e) => {
|
|
6696
7078
|
clearTimeout(timer);
|
|
6697
|
-
|
|
7079
|
+
resolve16({ exitCode: -1, stderr: e.message });
|
|
6698
7080
|
});
|
|
6699
7081
|
child.on("close", (code) => {
|
|
6700
7082
|
clearTimeout(timer);
|
|
6701
|
-
|
|
7083
|
+
resolve16({ exitCode: code ?? -1, stderr });
|
|
6702
7084
|
});
|
|
6703
7085
|
});
|
|
6704
7086
|
if (result.exitCode !== 0) {
|
|
@@ -6788,7 +7170,8 @@ async function removeSettingsEnv(settingsPath3) {
|
|
|
6788
7170
|
if (!changed) return false;
|
|
6789
7171
|
if (Object.keys(env).length === 0) delete data.env;
|
|
6790
7172
|
else data.env = env;
|
|
6791
|
-
await writeFile(settingsPath3, JSON.stringify(data, null, 2)
|
|
7173
|
+
await writeFile(settingsPath3, `${JSON.stringify(data, null, 2)}
|
|
7174
|
+
`, "utf8");
|
|
6792
7175
|
return true;
|
|
6793
7176
|
}
|
|
6794
7177
|
async function runUnifiedUninstall(home, dryRun) {
|
|
@@ -6996,6 +7379,9 @@ registerResume(program);
|
|
|
6996
7379
|
registerUninstall(program);
|
|
6997
7380
|
registerSetup(program);
|
|
6998
7381
|
registerRun(program);
|
|
7382
|
+
registerGates(program);
|
|
7383
|
+
registerPrompt(program);
|
|
7384
|
+
registerCheckpoint(program);
|
|
6999
7385
|
program.parse(process.argv);
|
|
7000
7386
|
//# sourceMappingURL=cli.mjs.map
|
|
7001
7387
|
//# sourceMappingURL=cli.mjs.map
|