harnessed 3.9.26 → 4.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -2
- package/dist/cli.mjs +893 -592
- 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,6 +119,325 @@ var init_harnessedRoot = __esm({
|
|
|
119
119
|
"src/installers/lib/harnessedRoot.ts"() {
|
|
120
120
|
}
|
|
121
121
|
});
|
|
122
|
+
function branchOnSchemaVersion(v, handlers) {
|
|
123
|
+
const isKnownVersion = Object.values(SCHEMA_VERSIONS).includes(v);
|
|
124
|
+
return isKnownVersion ? handlers.v1() : handlers.unknown();
|
|
125
|
+
}
|
|
126
|
+
var SCHEMA_VERSIONS;
|
|
127
|
+
var init_schemaVersion = __esm({
|
|
128
|
+
"src/types/schemaVersion.ts"() {
|
|
129
|
+
SCHEMA_VERSIONS = {
|
|
130
|
+
routingSnapshot: "harnessed.routing-snapshot.v1",
|
|
131
|
+
handoffDoc: "harnessed.handoff-doc.v1",
|
|
132
|
+
phasesYaml: "harnessed.phases-yaml.v1",
|
|
133
|
+
manifestState: "harnessed.manifest-state.v1",
|
|
134
|
+
installerState: "harnessed.installer-state.v1",
|
|
135
|
+
routeDecisionLog: "harnessed.route-decision-log.v1",
|
|
136
|
+
checkpoint: "harnessed.checkpoint.v1",
|
|
137
|
+
currentWorkflow: "harnessed.current-workflow.v1",
|
|
138
|
+
// ← Phase 3.1 W1 T1.1 ADD (D-02 KARPATHY 3-state)
|
|
139
|
+
config: "harnessed.config.v1",
|
|
140
|
+
// ← Phase 3.2 W1 T1.1 ADD (D-01 PROBE gstack_prefix store)
|
|
141
|
+
governance: "harnessed.governance.v1",
|
|
142
|
+
// ← Phase 3.2 W1 T1.1 ADD (D-04 PUSH veto status)
|
|
143
|
+
aliases: "harnessed.aliases.v1",
|
|
144
|
+
// ← Phase 3.3 W1 T1.1 ADD (D-01 RICH)
|
|
145
|
+
knownGood: "harnessed.known-good.v1",
|
|
146
|
+
// ← Phase 3.3 W1 T1.1 ADD (D-03 YAML manifest)
|
|
147
|
+
capabilities: "harnessed.capabilities.v1",
|
|
148
|
+
// ← Phase v2.0-2.3 W0 ADD (R20.2 flat yaml capabilities manifest validate)
|
|
149
|
+
judgment: "harnessed.judgment.v1",
|
|
150
|
+
// ← Phase v2.0-2.3 W0 ADD (R20.4 multi-file judgments validate)
|
|
151
|
+
workflow: "harnessed.workflow.v2",
|
|
152
|
+
// ← Phase v2.0-2.4 W0 T2.4.W0.1 ADD (R20.1+R20.2+R20.9 workflow.yaml v2)
|
|
153
|
+
workflow_v3: "harnessed.workflow.v3",
|
|
154
|
+
// ← Phase v3.0-3.3 W0 T3.3.W0.11 ADD (D-09 disciplines_applied + D-05 tools_available + master delegates_to per Pattern A B.1 LOCK)
|
|
155
|
+
discipline: "harnessed.discipline.v1"
|
|
156
|
+
// ← Phase v3.0-3.3 W0 T3.3.W0.11 ADD (D-09 L0 Discipline Substrate, sister judgment.v1 multi-file pattern)
|
|
157
|
+
};
|
|
158
|
+
Type.Union([
|
|
159
|
+
Type.Literal(SCHEMA_VERSIONS.routingSnapshot),
|
|
160
|
+
Type.Literal(SCHEMA_VERSIONS.handoffDoc),
|
|
161
|
+
Type.Literal(SCHEMA_VERSIONS.phasesYaml),
|
|
162
|
+
Type.Literal(SCHEMA_VERSIONS.manifestState),
|
|
163
|
+
Type.Literal(SCHEMA_VERSIONS.installerState),
|
|
164
|
+
Type.Literal(SCHEMA_VERSIONS.routeDecisionLog),
|
|
165
|
+
Type.Literal(SCHEMA_VERSIONS.checkpoint),
|
|
166
|
+
Type.Literal(SCHEMA_VERSIONS.currentWorkflow),
|
|
167
|
+
// ← Phase 3.1 W1 T1.1 ADD
|
|
168
|
+
Type.Literal(SCHEMA_VERSIONS.config),
|
|
169
|
+
// ← Phase 3.2 W1 T1.1 ADD
|
|
170
|
+
Type.Literal(SCHEMA_VERSIONS.governance),
|
|
171
|
+
// ← Phase 3.2 W1 T1.1 ADD
|
|
172
|
+
Type.Literal(SCHEMA_VERSIONS.aliases),
|
|
173
|
+
// ← Phase 3.3 W1 T1.1 ADD
|
|
174
|
+
Type.Literal(SCHEMA_VERSIONS.knownGood),
|
|
175
|
+
// ← Phase 3.3 W1 T1.1 ADD
|
|
176
|
+
Type.Literal(SCHEMA_VERSIONS.capabilities),
|
|
177
|
+
// ← Phase v2.0-2.3 W0 ADD
|
|
178
|
+
Type.Literal(SCHEMA_VERSIONS.judgment),
|
|
179
|
+
// ← Phase v2.0-2.3 W0 ADD
|
|
180
|
+
Type.Literal(SCHEMA_VERSIONS.workflow),
|
|
181
|
+
// ← Phase v2.0-2.4 W0 T2.4.W0.1 ADD (first .v2 in union)
|
|
182
|
+
Type.Literal(SCHEMA_VERSIONS.workflow_v3),
|
|
183
|
+
// ← Phase v3.0-3.3 W0 T3.3.W0.11 ADD (first .v3 in union)
|
|
184
|
+
Type.Literal(SCHEMA_VERSIONS.discipline)
|
|
185
|
+
// ← Phase v3.0-3.3 W0 T3.3.W0.11 ADD
|
|
186
|
+
]);
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
var CheckpointStatus, CheckpointV1;
|
|
190
|
+
var init_checkpoint_v1 = __esm({
|
|
191
|
+
"src/checkpoint/schema/checkpoint.v1.ts"() {
|
|
192
|
+
init_schemaVersion();
|
|
193
|
+
CheckpointStatus = Type.Union([
|
|
194
|
+
Type.Literal("active"),
|
|
195
|
+
Type.Literal("paused"),
|
|
196
|
+
Type.Literal("complete")
|
|
197
|
+
]);
|
|
198
|
+
CheckpointV1 = Type.Object(
|
|
199
|
+
{
|
|
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 })
|
|
215
|
+
},
|
|
216
|
+
{ additionalProperties: false }
|
|
217
|
+
);
|
|
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(
|
|
230
|
+
{
|
|
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 }))
|
|
239
|
+
},
|
|
240
|
+
{ additionalProperties: false }
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
|
|
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();
|
|
250
|
+
}
|
|
251
|
+
});
|
|
252
|
+
function statePath() {
|
|
253
|
+
return harnessedFile("current-workflow.json");
|
|
254
|
+
}
|
|
255
|
+
function lockTarget() {
|
|
256
|
+
return getHarnessedRoot();
|
|
257
|
+
}
|
|
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;
|
|
269
|
+
try {
|
|
270
|
+
release = await lockfile.lock(target, lockOpts());
|
|
271
|
+
} catch (e) {
|
|
272
|
+
if (e.code === "ELOCKED") throw new LockHeldError();
|
|
273
|
+
throw e;
|
|
274
|
+
}
|
|
275
|
+
try {
|
|
276
|
+
return await fn();
|
|
277
|
+
} finally {
|
|
278
|
+
await release?.();
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
async function readCurrentWorkflow() {
|
|
282
|
+
let raw;
|
|
283
|
+
try {
|
|
284
|
+
raw = await readFile(statePath(), "utf8");
|
|
285
|
+
} catch {
|
|
286
|
+
return null;
|
|
287
|
+
}
|
|
288
|
+
let parsed;
|
|
289
|
+
try {
|
|
290
|
+
parsed = JSON.parse(raw);
|
|
291
|
+
} catch {
|
|
292
|
+
return null;
|
|
293
|
+
}
|
|
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}`);
|
|
304
|
+
}
|
|
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
|
+
};
|
|
351
|
+
}
|
|
352
|
+
});
|
|
353
|
+
function estimateTokens(s) {
|
|
354
|
+
return Math.ceil(Buffer.byteLength(s, "utf8") / 4);
|
|
355
|
+
}
|
|
356
|
+
function enforceBudget(c, budget = BUDGET_TOKEN) {
|
|
357
|
+
let candidate = c;
|
|
358
|
+
let tokens = estimateTokens(JSON.stringify(candidate));
|
|
359
|
+
if (tokens <= budget) return candidate;
|
|
360
|
+
candidate = { ...candidate, last_task: candidate.last_task.slice(0, 200) };
|
|
361
|
+
tokens = estimateTokens(JSON.stringify(candidate));
|
|
362
|
+
if (tokens <= budget) return candidate;
|
|
363
|
+
candidate = { ...candidate, key_decisions: candidate.key_decisions.slice(0, 5) };
|
|
364
|
+
tokens = estimateTokens(JSON.stringify(candidate));
|
|
365
|
+
if (tokens <= budget) return candidate;
|
|
366
|
+
throw new CheckpointTooLargeError(
|
|
367
|
+
`Checkpoint exceeds ${budget}-token budget even after truncation (estimated ${tokens})`
|
|
368
|
+
);
|
|
369
|
+
}
|
|
370
|
+
function writeCheckpoint(c, customPath) {
|
|
371
|
+
if (!Value.Check(CheckpointV1, c)) {
|
|
372
|
+
const errs = [...Value.Errors(CheckpointV1, c)].map((e) => e.message).join("; ");
|
|
373
|
+
throw new CheckpointWriteError(`Schema validation failed: ${errs}`);
|
|
374
|
+
}
|
|
375
|
+
const enforced = enforceBudget(c);
|
|
376
|
+
const path = join(harnessedSubdir("checkpoints"), `${enforced.phase}.json`);
|
|
377
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
378
|
+
writeFileSync(path, JSON.stringify(enforced, null, 2), "utf8");
|
|
379
|
+
return path;
|
|
380
|
+
}
|
|
381
|
+
var BUDGET_TOKEN, CheckpointTooLargeError, CheckpointWriteError;
|
|
382
|
+
var init_template = __esm({
|
|
383
|
+
"src/checkpoint/template.ts"() {
|
|
384
|
+
init_harnessedRoot();
|
|
385
|
+
init_schema();
|
|
386
|
+
BUDGET_TOKEN = 1e3;
|
|
387
|
+
CheckpointTooLargeError = class extends Error {
|
|
388
|
+
constructor(message) {
|
|
389
|
+
super(message);
|
|
390
|
+
this.name = "CheckpointTooLargeError";
|
|
391
|
+
}
|
|
392
|
+
};
|
|
393
|
+
CheckpointWriteError = class extends Error {
|
|
394
|
+
constructor(message) {
|
|
395
|
+
super(message);
|
|
396
|
+
this.name = "CheckpointWriteError";
|
|
397
|
+
}
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
// src/checkpoint/engineHook.ts
|
|
403
|
+
var engineHook_exports = {};
|
|
404
|
+
__export(engineHook_exports, {
|
|
405
|
+
activatePhase: () => activatePhase,
|
|
406
|
+
completePhase: () => completePhase
|
|
407
|
+
});
|
|
408
|
+
async function activatePhase(phaseId) {
|
|
409
|
+
const checkpointPath = join(harnessedSubdir("checkpoints"), `${phaseId}.json`);
|
|
410
|
+
await activate(phaseId, checkpointPath);
|
|
411
|
+
return { checkpointPath };
|
|
412
|
+
}
|
|
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
|
+
);
|
|
418
|
+
}
|
|
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();
|
|
432
|
+
}
|
|
433
|
+
var init_engineHook = __esm({
|
|
434
|
+
"src/checkpoint/engineHook.ts"() {
|
|
435
|
+
init_harnessedRoot();
|
|
436
|
+
init_schemaVersion();
|
|
437
|
+
init_state();
|
|
438
|
+
init_template();
|
|
439
|
+
}
|
|
440
|
+
});
|
|
122
441
|
function checkNodeVersion() {
|
|
123
442
|
const v = process.versions.node;
|
|
124
443
|
const major = Number.parseInt(v.split(".")[0] ?? "0", 10);
|
|
@@ -244,98 +563,31 @@ var init_probe_gstack = __esm({
|
|
|
244
563
|
function checkPathSafe(input) {
|
|
245
564
|
for (const re of PATH_TRAVERSAL_PATTERNS) {
|
|
246
565
|
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
|
-
function branchOnSchemaVersion(v, handlers) {
|
|
275
|
-
const isKnownVersion = Object.values(SCHEMA_VERSIONS).includes(v);
|
|
276
|
-
return isKnownVersion ? handlers.v1() : handlers.unknown();
|
|
277
|
-
}
|
|
278
|
-
var SCHEMA_VERSIONS;
|
|
279
|
-
var init_schemaVersion = __esm({
|
|
280
|
-
"src/types/schemaVersion.ts"() {
|
|
281
|
-
SCHEMA_VERSIONS = {
|
|
282
|
-
routingSnapshot: "harnessed.routing-snapshot.v1",
|
|
283
|
-
handoffDoc: "harnessed.handoff-doc.v1",
|
|
284
|
-
phasesYaml: "harnessed.phases-yaml.v1",
|
|
285
|
-
manifestState: "harnessed.manifest-state.v1",
|
|
286
|
-
installerState: "harnessed.installer-state.v1",
|
|
287
|
-
routeDecisionLog: "harnessed.route-decision-log.v1",
|
|
288
|
-
checkpoint: "harnessed.checkpoint.v1",
|
|
289
|
-
currentWorkflow: "harnessed.current-workflow.v1",
|
|
290
|
-
// ← Phase 3.1 W1 T1.1 ADD (D-02 KARPATHY 3-state)
|
|
291
|
-
config: "harnessed.config.v1",
|
|
292
|
-
// ← Phase 3.2 W1 T1.1 ADD (D-01 PROBE gstack_prefix store)
|
|
293
|
-
governance: "harnessed.governance.v1",
|
|
294
|
-
// ← Phase 3.2 W1 T1.1 ADD (D-04 PUSH veto status)
|
|
295
|
-
aliases: "harnessed.aliases.v1",
|
|
296
|
-
// ← Phase 3.3 W1 T1.1 ADD (D-01 RICH)
|
|
297
|
-
knownGood: "harnessed.known-good.v1",
|
|
298
|
-
// ← Phase 3.3 W1 T1.1 ADD (D-03 YAML manifest)
|
|
299
|
-
capabilities: "harnessed.capabilities.v1",
|
|
300
|
-
// ← Phase v2.0-2.3 W0 ADD (R20.2 flat yaml capabilities manifest validate)
|
|
301
|
-
judgment: "harnessed.judgment.v1",
|
|
302
|
-
// ← Phase v2.0-2.3 W0 ADD (R20.4 multi-file judgments validate)
|
|
303
|
-
workflow: "harnessed.workflow.v2",
|
|
304
|
-
// ← Phase v2.0-2.4 W0 T2.4.W0.1 ADD (R20.1+R20.2+R20.9 workflow.yaml v2)
|
|
305
|
-
workflow_v3: "harnessed.workflow.v3",
|
|
306
|
-
// ← Phase v3.0-3.3 W0 T3.3.W0.11 ADD (D-09 disciplines_applied + D-05 tools_available + master delegates_to per Pattern A B.1 LOCK)
|
|
307
|
-
discipline: "harnessed.discipline.v1"
|
|
308
|
-
// ← Phase v3.0-3.3 W0 T3.3.W0.11 ADD (D-09 L0 Discipline Substrate, sister judgment.v1 multi-file pattern)
|
|
566
|
+
}
|
|
567
|
+
}
|
|
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);
|
|
589
|
+
}
|
|
309
590
|
};
|
|
310
|
-
Type.Union([
|
|
311
|
-
Type.Literal(SCHEMA_VERSIONS.routingSnapshot),
|
|
312
|
-
Type.Literal(SCHEMA_VERSIONS.handoffDoc),
|
|
313
|
-
Type.Literal(SCHEMA_VERSIONS.phasesYaml),
|
|
314
|
-
Type.Literal(SCHEMA_VERSIONS.manifestState),
|
|
315
|
-
Type.Literal(SCHEMA_VERSIONS.installerState),
|
|
316
|
-
Type.Literal(SCHEMA_VERSIONS.routeDecisionLog),
|
|
317
|
-
Type.Literal(SCHEMA_VERSIONS.checkpoint),
|
|
318
|
-
Type.Literal(SCHEMA_VERSIONS.currentWorkflow),
|
|
319
|
-
// ← Phase 3.1 W1 T1.1 ADD
|
|
320
|
-
Type.Literal(SCHEMA_VERSIONS.config),
|
|
321
|
-
// ← Phase 3.2 W1 T1.1 ADD
|
|
322
|
-
Type.Literal(SCHEMA_VERSIONS.governance),
|
|
323
|
-
// ← Phase 3.2 W1 T1.1 ADD
|
|
324
|
-
Type.Literal(SCHEMA_VERSIONS.aliases),
|
|
325
|
-
// ← Phase 3.3 W1 T1.1 ADD
|
|
326
|
-
Type.Literal(SCHEMA_VERSIONS.knownGood),
|
|
327
|
-
// ← Phase 3.3 W1 T1.1 ADD
|
|
328
|
-
Type.Literal(SCHEMA_VERSIONS.capabilities),
|
|
329
|
-
// ← Phase v2.0-2.3 W0 ADD
|
|
330
|
-
Type.Literal(SCHEMA_VERSIONS.judgment),
|
|
331
|
-
// ← Phase v2.0-2.3 W0 ADD
|
|
332
|
-
Type.Literal(SCHEMA_VERSIONS.workflow),
|
|
333
|
-
// ← Phase v2.0-2.4 W0 T2.4.W0.1 ADD (first .v2 in union)
|
|
334
|
-
Type.Literal(SCHEMA_VERSIONS.workflow_v3),
|
|
335
|
-
// ← Phase v3.0-3.3 W0 T3.3.W0.11 ADD (first .v3 in union)
|
|
336
|
-
Type.Literal(SCHEMA_VERSIONS.discipline)
|
|
337
|
-
// ← Phase v3.0-3.3 W0 T3.3.W0.11 ADD
|
|
338
|
-
]);
|
|
339
591
|
}
|
|
340
592
|
});
|
|
341
593
|
var AliasEntryV1, AliasesV1;
|
|
@@ -442,117 +694,6 @@ var init_check_deprecations = __esm({
|
|
|
442
694
|
init_aliases();
|
|
443
695
|
}
|
|
444
696
|
});
|
|
445
|
-
var CheckpointStatus, CheckpointV1;
|
|
446
|
-
var init_checkpoint_v1 = __esm({
|
|
447
|
-
"src/checkpoint/schema/checkpoint.v1.ts"() {
|
|
448
|
-
init_schemaVersion();
|
|
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
|
-
);
|
|
474
|
-
}
|
|
475
|
-
});
|
|
476
|
-
var WorkflowStatus, CurrentWorkflowV1;
|
|
477
|
-
var init_currentWorkflow_v1 = __esm({
|
|
478
|
-
"src/checkpoint/schema/currentWorkflow.v1.ts"() {
|
|
479
|
-
init_schemaVersion();
|
|
480
|
-
WorkflowStatus = Type.Union([
|
|
481
|
-
Type.Literal("active"),
|
|
482
|
-
Type.Literal("paused"),
|
|
483
|
-
Type.Literal("complete")
|
|
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
|
-
);
|
|
498
|
-
}
|
|
499
|
-
});
|
|
500
|
-
|
|
501
|
-
// src/checkpoint/schema/index.ts
|
|
502
|
-
var init_schema = __esm({
|
|
503
|
-
"src/checkpoint/schema/index.ts"() {
|
|
504
|
-
init_checkpoint_v1();
|
|
505
|
-
init_currentWorkflow_v1();
|
|
506
|
-
}
|
|
507
|
-
});
|
|
508
|
-
function estimateTokens(s) {
|
|
509
|
-
return Math.ceil(Buffer.byteLength(s, "utf8") / 4);
|
|
510
|
-
}
|
|
511
|
-
function enforceBudget(c, budget = BUDGET_TOKEN) {
|
|
512
|
-
let candidate = c;
|
|
513
|
-
let tokens = estimateTokens(JSON.stringify(candidate));
|
|
514
|
-
if (tokens <= budget) return candidate;
|
|
515
|
-
candidate = { ...candidate, last_task: candidate.last_task.slice(0, 200) };
|
|
516
|
-
tokens = estimateTokens(JSON.stringify(candidate));
|
|
517
|
-
if (tokens <= budget) return candidate;
|
|
518
|
-
candidate = { ...candidate, key_decisions: candidate.key_decisions.slice(0, 5) };
|
|
519
|
-
tokens = estimateTokens(JSON.stringify(candidate));
|
|
520
|
-
if (tokens <= budget) return candidate;
|
|
521
|
-
throw new CheckpointTooLargeError(
|
|
522
|
-
`Checkpoint exceeds ${budget}-token budget even after truncation (estimated ${tokens})`
|
|
523
|
-
);
|
|
524
|
-
}
|
|
525
|
-
function writeCheckpoint(c, customPath) {
|
|
526
|
-
if (!Value.Check(CheckpointV1, c)) {
|
|
527
|
-
const errs = [...Value.Errors(CheckpointV1, c)].map((e) => e.message).join("; ");
|
|
528
|
-
throw new CheckpointWriteError(`Schema validation failed: ${errs}`);
|
|
529
|
-
}
|
|
530
|
-
const enforced = enforceBudget(c);
|
|
531
|
-
const path = join(harnessedSubdir("checkpoints"), `${enforced.phase}.json`);
|
|
532
|
-
mkdirSync(dirname(path), { recursive: true });
|
|
533
|
-
writeFileSync(path, JSON.stringify(enforced, null, 2), "utf8");
|
|
534
|
-
return path;
|
|
535
|
-
}
|
|
536
|
-
var BUDGET_TOKEN, CheckpointTooLargeError, CheckpointWriteError;
|
|
537
|
-
var init_template = __esm({
|
|
538
|
-
"src/checkpoint/template.ts"() {
|
|
539
|
-
init_harnessedRoot();
|
|
540
|
-
init_schema();
|
|
541
|
-
BUDGET_TOKEN = 1e3;
|
|
542
|
-
CheckpointTooLargeError = class extends Error {
|
|
543
|
-
constructor(message) {
|
|
544
|
-
super(message);
|
|
545
|
-
this.name = "CheckpointTooLargeError";
|
|
546
|
-
}
|
|
547
|
-
};
|
|
548
|
-
CheckpointWriteError = class extends Error {
|
|
549
|
-
constructor(message) {
|
|
550
|
-
super(message);
|
|
551
|
-
this.name = "CheckpointWriteError";
|
|
552
|
-
}
|
|
553
|
-
};
|
|
554
|
-
}
|
|
555
|
-
});
|
|
556
697
|
|
|
557
698
|
// src/cli/lib/check-token-budget.ts
|
|
558
699
|
var check_token_budget_exports = {};
|
|
@@ -955,122 +1096,21 @@ function loadKnownGood(harnessedVer) {
|
|
|
955
1096
|
`${path} schema invalid: ${errs.map((e) => `${e.path} ${e.message}`).join("; ")}`
|
|
956
1097
|
);
|
|
957
1098
|
}
|
|
958
|
-
_cache.set(harnessedVer, parsed);
|
|
959
|
-
return parsed;
|
|
960
|
-
}
|
|
961
|
-
function getPinnedVersion(upstreamName, harnessedVer) {
|
|
962
|
-
const kg = loadKnownGood(harnessedVer);
|
|
963
|
-
if (!kg) return null;
|
|
964
|
-
const entry = kg.upstreams.find((u) => u.name === upstreamName);
|
|
965
|
-
return entry?.version ?? null;
|
|
966
|
-
}
|
|
967
|
-
var versionsDir, _cache;
|
|
968
|
-
var init_knownGood = __esm({
|
|
969
|
-
"src/manifest/knownGood.ts"() {
|
|
970
|
-
init_known_good_v1();
|
|
971
|
-
versionsDir = () => join(process.cwd(), "versions");
|
|
972
|
-
_cache = /* @__PURE__ */ new Map();
|
|
973
|
-
}
|
|
974
|
-
});
|
|
975
|
-
function statePath2() {
|
|
976
|
-
return harnessedFile("current-workflow.json");
|
|
977
|
-
}
|
|
978
|
-
function lockTarget() {
|
|
979
|
-
return getHarnessedRoot();
|
|
980
|
-
}
|
|
981
|
-
function lockOpts() {
|
|
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;
|
|
992
|
-
try {
|
|
993
|
-
release = await lockfile.lock(target, lockOpts());
|
|
994
|
-
} catch (e) {
|
|
995
|
-
if (e.code === "ELOCKED") throw new LockHeldError();
|
|
996
|
-
throw e;
|
|
997
|
-
}
|
|
998
|
-
try {
|
|
999
|
-
return await fn();
|
|
1000
|
-
} finally {
|
|
1001
|
-
await release?.();
|
|
1002
|
-
}
|
|
1003
|
-
}
|
|
1004
|
-
async function readCurrentWorkflow() {
|
|
1005
|
-
let raw;
|
|
1006
|
-
try {
|
|
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;
|
|
1016
|
-
}
|
|
1017
|
-
const v = parsed.schemaVersion ?? "";
|
|
1018
|
-
return branchOnSchemaVersion(v, {
|
|
1019
|
-
v1: () => Value.Check(CurrentWorkflowV1, parsed) ? parsed : null,
|
|
1020
|
-
unknown: () => null
|
|
1021
|
-
});
|
|
1022
|
-
}
|
|
1023
|
-
async function writeCurrentWorkflow(s) {
|
|
1024
|
-
if (!Value.Check(CurrentWorkflowV1, s)) {
|
|
1025
|
-
const errs = [...Value.Errors(CurrentWorkflowV1, s)].map((e) => e.message).join("; ");
|
|
1026
|
-
throw new WorkflowStateError(`current-workflow schema validation failed: ${errs}`);
|
|
1027
|
-
}
|
|
1028
|
-
const path = statePath2();
|
|
1029
|
-
await mkdir(dirname(path), { recursive: true });
|
|
1030
|
-
await withLock(async () => {
|
|
1031
|
-
await writeFile(path, JSON.stringify(s, null, 2), "utf8");
|
|
1032
|
-
});
|
|
1033
|
-
}
|
|
1034
|
-
async function activate(phase, checkpointPath = null) {
|
|
1035
|
-
await writeCurrentWorkflow({
|
|
1036
|
-
schemaVersion: SCHEMA_VERSIONS.currentWorkflow,
|
|
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() });
|
|
1047
|
-
}
|
|
1048
|
-
async function complete() {
|
|
1049
|
-
const s = await readCurrentWorkflow();
|
|
1050
|
-
if (!s) return;
|
|
1051
|
-
await writeCurrentWorkflow({ ...s, status: "complete", completed_at: (/* @__PURE__ */ new Date()).toISOString() });
|
|
1099
|
+
_cache.set(harnessedVer, parsed);
|
|
1100
|
+
return parsed;
|
|
1052
1101
|
}
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
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
|
-
};
|
|
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();
|
|
1074
1114
|
}
|
|
1075
1115
|
});
|
|
1076
1116
|
|
|
@@ -1231,7 +1271,7 @@ var init_auto_install = __esm({
|
|
|
1231
1271
|
|
|
1232
1272
|
// package.json
|
|
1233
1273
|
var package_default = {
|
|
1234
|
-
version: "
|
|
1274
|
+
version: "4.0.1"};
|
|
1235
1275
|
|
|
1236
1276
|
// src/manifest/errors.ts
|
|
1237
1277
|
function instancePathToKeyPath(instancePath) {
|
|
@@ -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,12 +2131,12 @@ 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) =>
|
|
2139
|
+
child.on("close", (code) => resolve16(code ?? 0));
|
|
2100
2140
|
child.stdin.write(lines.join("\n"));
|
|
2101
2141
|
child.stdin.end();
|
|
2102
2142
|
});
|
|
@@ -2304,6 +2344,51 @@ function registerBackupList(program2) {
|
|
|
2304
2344
|
});
|
|
2305
2345
|
}
|
|
2306
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);
|
|
2389
|
+
});
|
|
2390
|
+
}
|
|
2391
|
+
|
|
2307
2392
|
// src/cli/doctor.ts
|
|
2308
2393
|
init_doctor_registry();
|
|
2309
2394
|
function registerDoctor(program2) {
|
|
@@ -2327,13 +2412,329 @@ function registerDoctor(program2) {
|
|
|
2327
2412
|
console.log(`${mark} ${r.name} \u2014 ${r.message}`);
|
|
2328
2413
|
if (r.status !== "pass" && r.fix) console.log(` fix: ${r.fix}`);
|
|
2329
2414
|
}
|
|
2330
|
-
console.log(
|
|
2331
|
-
hasFail ? t("doctor.summary.fail") : hasWarn ? t("doctor.summary.warn") : t("doctor.summary.pass")
|
|
2332
|
-
);
|
|
2415
|
+
console.log(
|
|
2416
|
+
hasFail ? t("doctor.summary.fail") : hasWarn ? t("doctor.summary.warn") : t("doctor.summary.pass")
|
|
2417
|
+
);
|
|
2418
|
+
}
|
|
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
|
+
}
|
|
2458
|
+
}
|
|
2459
|
+
|
|
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);
|
|
2574
|
+
}
|
|
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, "..");
|
|
2580
|
+
}
|
|
2581
|
+
return resolve(thisDir, "..", "..", "..");
|
|
2582
|
+
}
|
|
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
|
+
};
|
|
2628
|
+
}
|
|
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;
|
|
2642
|
+
}
|
|
2643
|
+
const packageRoot = getPackageRoot();
|
|
2644
|
+
const yamlPath = resolveMasterYamlPath(master, packageRoot);
|
|
2645
|
+
let raw_yaml;
|
|
2646
|
+
try {
|
|
2647
|
+
raw_yaml = await readFile(yamlPath, "utf8");
|
|
2648
|
+
} catch (err2) {
|
|
2649
|
+
console.error(
|
|
2650
|
+
`error: failed to read master workflow.yaml at ${yamlPath} \u2014 ${err2.message}`
|
|
2651
|
+
);
|
|
2652
|
+
process.exit(1);
|
|
2653
|
+
return;
|
|
2654
|
+
}
|
|
2655
|
+
let parsed;
|
|
2656
|
+
try {
|
|
2657
|
+
parsed = parse(raw_yaml);
|
|
2658
|
+
} catch (err2) {
|
|
2659
|
+
console.error(`error: failed to parse ${yamlPath} \u2014 ${err2.message}`);
|
|
2660
|
+
process.exit(1);
|
|
2661
|
+
return;
|
|
2662
|
+
}
|
|
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);
|
|
2667
|
+
return;
|
|
2668
|
+
}
|
|
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;
|
|
2675
|
+
try {
|
|
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);
|
|
2684
|
+
}
|
|
2333
2685
|
}
|
|
2334
|
-
|
|
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));
|
|
2717
|
+
}
|
|
2718
|
+
}
|
|
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 !== "/" ? `
|
|
@@ -3983,38 +4374,8 @@ function registerManifestAdd(program2) {
|
|
|
3983
4374
|
});
|
|
3984
4375
|
}
|
|
3985
4376
|
|
|
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}`)}/`
|
|
4013
|
-
});
|
|
4014
|
-
await complete();
|
|
4015
|
-
}
|
|
4016
|
-
|
|
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");
|
|
@@ -4034,17 +4395,31 @@ var INTERACTIVE_COMMANDS = /* @__PURE__ */ new Set([
|
|
|
4034
4395
|
"discuss-subtask",
|
|
4035
4396
|
"task-clarify"
|
|
4036
4397
|
]);
|
|
4037
|
-
var
|
|
4398
|
+
var ORCHESTRATOR_COMMANDS = /* @__PURE__ */ new Set(["auto", "plan", "task", "verify"]);
|
|
4038
4399
|
var MARKER = `<!-- harnessed-generated:v3.4.4 -->`;
|
|
4400
|
+
var LANG_DIRECTIVE = `> **Language**: respond to the user in the language set by \`env.HARNESSED_USER_LANG\` (e.g. \`zh-Hans\` \u2192 \u7B80\u4F53\u4E2D\u6587, \`en\` \u2192 English). If unset, mirror the user's input language. Keep code / commands / identifiers / error messages / URLs verbatim.`;
|
|
4401
|
+
function spawnLoopSteps(indent) {
|
|
4402
|
+
const i = indent;
|
|
4403
|
+
return [
|
|
4404
|
+
`${i}a. Bash: \`harnessed prompt <sub> --task "<spec>" --json\` \u2192 parse \`{prompt, max_iterations, model}\`.`,
|
|
4405
|
+
`${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:`,
|
|
4406
|
+
`${i} \`/ralph-loop "<prompt>" --max-iterations <max_iterations> --completion-promise "COMPLETE"\``,
|
|
4407
|
+
`${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).`,
|
|
4408
|
+
`${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.)`,
|
|
4409
|
+
`${i}d. On \`<promise>COMPLETE</promise>\`: Bash \`harnessed checkpoint complete <sub> --summary "<one-line>"\`.`
|
|
4410
|
+
];
|
|
4411
|
+
}
|
|
4039
4412
|
function buildInteractiveBody(name, prompt) {
|
|
4040
4413
|
return [
|
|
4041
4414
|
`# /${name}`,
|
|
4042
4415
|
``,
|
|
4043
4416
|
prompt.description,
|
|
4044
4417
|
``,
|
|
4418
|
+
LANG_DIRECTIVE,
|
|
4419
|
+
``,
|
|
4045
4420
|
`## How to run (interactive \u2014 in THIS session)`,
|
|
4046
4421
|
``,
|
|
4047
|
-
`Clarification requires real user dialogue. Spawned subagents cannot ask the user questions, so run this stage directly in this session \u2014 do NOT spawn
|
|
4422
|
+
`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.`,
|
|
4048
4423
|
``,
|
|
4049
4424
|
`1. Evaluate the clarification criteria for "$ARGUMENTS":`,
|
|
4050
4425
|
` - Strategic layer: new feature / new milestone / unclear business scope \u2192 run gstack \`/office-hours\` + \`/plan-ceo-review\``,
|
|
@@ -4054,85 +4429,73 @@ function buildInteractiveBody(name, prompt) {
|
|
|
4054
4429
|
`3. Skip layers that don't fire \u2014 state which were skipped and why (transparent skip).`,
|
|
4055
4430
|
`4. Persist locked decisions to \`.planning/\` via planning-with-files (\`findings.md\` / \`task_plan.md\`).`,
|
|
4056
4431
|
``,
|
|
4057
|
-
`Output: a locked spec the execution stages can consume without further user input.`,
|
|
4432
|
+
`Output: a locked spec the execution stages can consume without further user input. Then run \`/plan\` \u2192 \`/task\` \u2192 \`/verify\` with that spec.`,
|
|
4058
4433
|
``,
|
|
4059
4434
|
`## Notes`,
|
|
4060
4435
|
``,
|
|
4061
|
-
`-
|
|
4062
|
-
`- The downstream execution command (\`/plan\` \u2192 \`/task\` \u2192 \`/verify\`) embeds the locked spec in its task text.`,
|
|
4436
|
+
`- Generated by \`harnessed setup\`. Re-run after a harnessed upgrade to refresh.`,
|
|
4063
4437
|
``,
|
|
4064
4438
|
MARKER,
|
|
4065
4439
|
``
|
|
4066
4440
|
].join("\n");
|
|
4067
4441
|
}
|
|
4068
|
-
function
|
|
4069
|
-
const
|
|
4070
|
-
`
|
|
4071
|
-
``,
|
|
4072
|
-
"```bash",
|
|
4073
|
-
`echo "<locked spec + $ARGUMENTS>" | harnessed run plan --task-stdin`,
|
|
4074
|
-
`echo "<locked spec + plan output>" | harnessed run task --task-stdin --skip-sub clarify`,
|
|
4075
|
-
`echo "<locked spec + task output>" | harnessed run verify --task-stdin`,
|
|
4076
|
-
`echo "<summary of all stages>" | harnessed run retro --task-stdin`,
|
|
4077
|
-
"```",
|
|
4078
|
-
``,
|
|
4079
|
-
` Do NOT run \`harnessed run auto\` \u2014 that would re-spawn the discuss stage headlessly, defeating the interactive clarification you just did.`,
|
|
4080
|
-
` \`--skip-sub clarify\` tells the task master that clarification is already done.`
|
|
4442
|
+
function buildOrchestratorBody(name, prompt) {
|
|
4443
|
+
const autoDiscussStep = name === "auto" ? [
|
|
4444
|
+
`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.`
|
|
4081
4445
|
] : [
|
|
4082
|
-
`
|
|
4083
|
-
``,
|
|
4084
|
-
"```bash",
|
|
4085
|
-
`echo "<locked spec + $ARGUMENTS>" | harnessed run task --task-stdin --skip-sub clarify`,
|
|
4086
|
-
"```",
|
|
4087
|
-
``,
|
|
4088
|
-
` \`--skip-sub clarify\` tells the task master that clarification is already done in this session.`
|
|
4446
|
+
`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.`
|
|
4089
4447
|
];
|
|
4090
4448
|
return [
|
|
4091
4449
|
`# /${name}`,
|
|
4092
4450
|
``,
|
|
4093
4451
|
prompt.description,
|
|
4094
4452
|
``,
|
|
4095
|
-
|
|
4453
|
+
LANG_DIRECTIVE,
|
|
4454
|
+
``,
|
|
4455
|
+
`## How to run (orchestrator \u2014 clarify in THIS session, then drive native subagent spawns)`,
|
|
4456
|
+
``,
|
|
4457
|
+
`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.`,
|
|
4096
4458
|
``,
|
|
4097
|
-
|
|
4098
|
-
`
|
|
4099
|
-
`
|
|
4100
|
-
`
|
|
4101
|
-
...
|
|
4459
|
+
...autoDiscussStep,
|
|
4460
|
+
`2. Bash: \`harnessed gates ${name} --task "<locked spec>" --skip-sub clarify\` \u2192 parse the JSON \`{fire: [{sub, order, mode}], skip, parallelism: {escalate_to_teams}}\`.`,
|
|
4461
|
+
`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\`.`,
|
|
4462
|
+
`4. Otherwise, for each fired sub in \`order\` (serial subs sequentially, parallel subs concurrently via parallel Task calls):`,
|
|
4463
|
+
...spawnLoopSteps(" "),
|
|
4464
|
+
`5. Report a per-sub fired/skipped summary to the user. ${name === "auto" ? "Then run the `retro` stage to capture lessons." : ""}`,
|
|
4465
|
+
``,
|
|
4466
|
+
`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).`,
|
|
4102
4467
|
``,
|
|
4103
4468
|
`## Notes`,
|
|
4104
4469
|
``,
|
|
4105
|
-
`-
|
|
4106
|
-
`-
|
|
4470
|
+
`- Generated by \`harnessed setup\`. Re-run after a harnessed upgrade to refresh.`,
|
|
4471
|
+
`- gate/discipline SoT: \`workflows/${nameToYamlHintPath(name)}\` + \`workflows/judgments/\` \u2014 consumed by \`harnessed gates\` + \`harnessed prompt\`.`,
|
|
4107
4472
|
``,
|
|
4108
4473
|
MARKER,
|
|
4109
4474
|
``
|
|
4110
4475
|
].join("\n");
|
|
4111
4476
|
}
|
|
4112
|
-
function
|
|
4113
|
-
const stagedNote = name === "auto" ? "\n- For stage-by-stage review, append `--staged` (pauses between stages for user review)." : "";
|
|
4477
|
+
function buildExecutionBody(name, prompt) {
|
|
4114
4478
|
return [
|
|
4115
4479
|
`# /${name}`,
|
|
4116
4480
|
``,
|
|
4117
4481
|
prompt.description,
|
|
4118
4482
|
``,
|
|
4119
|
-
|
|
4483
|
+
LANG_DIRECTIVE,
|
|
4120
4484
|
``,
|
|
4121
|
-
|
|
4485
|
+
`## How to run (execution \u2014 native subagent spawn)`,
|
|
4122
4486
|
``,
|
|
4123
|
-
|
|
4124
|
-
`echo "$ARGUMENTS" | harnessed run ${name} --task-stdin`,
|
|
4125
|
-
"```",
|
|
4487
|
+
`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).`,
|
|
4126
4488
|
``,
|
|
4127
|
-
|
|
4489
|
+
...spawnLoopSteps("").map(
|
|
4490
|
+
(s) => s.replace("<sub>", name).replace('--task "<spec>"', '--task "$ARGUMENTS"')
|
|
4491
|
+
),
|
|
4128
4492
|
``,
|
|
4129
|
-
`
|
|
4493
|
+
`Do NOT pipe to \`harnessed run ${name}\` \u2014 that is the CI/headless path (SDK spawn).`,
|
|
4130
4494
|
``,
|
|
4131
4495
|
`## Notes`,
|
|
4132
4496
|
``,
|
|
4133
|
-
`-
|
|
4134
|
-
`- The sister \`~/.claude/skills/${name}/SKILL.md\` is the Skill-tool entry point (Claude loads it when triggers match
|
|
4135
|
-
`- Workflow runtime: \`src/workflow/run.ts\` walks \`workflows/${nameToYamlHintPath(name)}\` with disciplines + judgments + master orchestration applied per the yaml \`delegates_to[]\` + \`gate\` clauses.`,
|
|
4497
|
+
`- Generated by \`harnessed setup\`. Re-run after a harnessed upgrade to refresh.`,
|
|
4498
|
+
`- 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)}\`.`,
|
|
4136
4499
|
``,
|
|
4137
4500
|
MARKER,
|
|
4138
4501
|
``
|
|
@@ -4144,10 +4507,10 @@ function generateCommandFile(name, prompt, _capabilities, _installedPlugins, _in
|
|
|
4144
4507
|
let body;
|
|
4145
4508
|
if (INTERACTIVE_COMMANDS.has(name)) {
|
|
4146
4509
|
body = buildInteractiveBody(name, prompt);
|
|
4147
|
-
} else if (
|
|
4148
|
-
body =
|
|
4510
|
+
} else if (ORCHESTRATOR_COMMANDS.has(name)) {
|
|
4511
|
+
body = buildOrchestratorBody(name, prompt);
|
|
4149
4512
|
} else {
|
|
4150
|
-
body =
|
|
4513
|
+
body = buildExecutionBody(name, prompt);
|
|
4151
4514
|
}
|
|
4152
4515
|
const warnings = [];
|
|
4153
4516
|
const frontmatter = [
|
|
@@ -4436,159 +4799,6 @@ async function readGovernance() {
|
|
|
4436
4799
|
async function isVetoed() {
|
|
4437
4800
|
return (await readGovernance())?.status === "vetoed";
|
|
4438
4801
|
}
|
|
4439
|
-
var PARSER_OPTIONS = {
|
|
4440
|
-
operators: {
|
|
4441
|
-
add: false,
|
|
4442
|
-
subtract: false,
|
|
4443
|
-
multiply: false,
|
|
4444
|
-
divide: false,
|
|
4445
|
-
logical: true,
|
|
4446
|
-
comparison: true,
|
|
4447
|
-
in: true,
|
|
4448
|
-
assignment: false
|
|
4449
|
-
}
|
|
4450
|
-
};
|
|
4451
|
-
var _parserSingleton = new Parser(PARSER_OPTIONS);
|
|
4452
|
-
var GateEvalError = class extends Error {
|
|
4453
|
-
constructor(message, expression) {
|
|
4454
|
-
super(message);
|
|
4455
|
-
this.expression = expression;
|
|
4456
|
-
this.name = "GateEvalError";
|
|
4457
|
-
}
|
|
4458
|
-
expression;
|
|
4459
|
-
};
|
|
4460
|
-
function evalGate(expression, context) {
|
|
4461
|
-
try {
|
|
4462
|
-
const parsed = _parserSingleton.parse(expression);
|
|
4463
|
-
const result = parsed.evaluate(context);
|
|
4464
|
-
if (typeof result !== "boolean") {
|
|
4465
|
-
throw new GateEvalError(
|
|
4466
|
-
`Expression must evaluate to boolean, got ${typeof result}`,
|
|
4467
|
-
expression
|
|
4468
|
-
);
|
|
4469
|
-
}
|
|
4470
|
-
return result;
|
|
4471
|
-
} catch (err2) {
|
|
4472
|
-
if (err2 instanceof GateEvalError) throw err2;
|
|
4473
|
-
throw new GateEvalError(`Gate eval failed: ${err2.message}`, expression);
|
|
4474
|
-
}
|
|
4475
|
-
}
|
|
4476
|
-
|
|
4477
|
-
// src/workflow/schema/judgment.ts
|
|
4478
|
-
init_schemaVersion();
|
|
4479
|
-
var TriggerInvocation = Type.Object(
|
|
4480
|
-
{
|
|
4481
|
-
capability: Type.String()
|
|
4482
|
-
},
|
|
4483
|
-
{ additionalProperties: false }
|
|
4484
|
-
);
|
|
4485
|
-
var RequiresCapabilities = Type.Object(
|
|
4486
|
-
{
|
|
4487
|
-
capabilities: Type.Array(Type.String())
|
|
4488
|
-
},
|
|
4489
|
-
{ additionalProperties: false }
|
|
4490
|
-
);
|
|
4491
|
-
var JudgmentTrigger = Type.Object(
|
|
4492
|
-
{
|
|
4493
|
-
description: Type.Optional(Type.String()),
|
|
4494
|
-
fires_when: Type.Optional(Type.String()),
|
|
4495
|
-
skips_when: Type.Optional(Type.String()),
|
|
4496
|
-
invokes: Type.Optional(Type.Array(TriggerInvocation)),
|
|
4497
|
-
requires: Type.Optional(RequiresCapabilities),
|
|
4498
|
-
wraps: Type.Optional(Type.Array(Type.String()))
|
|
4499
|
-
},
|
|
4500
|
-
{ additionalProperties: false }
|
|
4501
|
-
);
|
|
4502
|
-
var FallbackRule = Type.Object(
|
|
4503
|
-
{
|
|
4504
|
-
description: Type.Optional(Type.String()),
|
|
4505
|
-
fallback_action: Type.Optional(Type.String()),
|
|
4506
|
-
message_template: Type.Optional(Type.String()),
|
|
4507
|
-
override_signal: Type.Optional(Type.Array(Type.String())),
|
|
4508
|
-
chain_isolation: Type.Optional(Type.Boolean())
|
|
4509
|
-
},
|
|
4510
|
-
{ additionalProperties: false }
|
|
4511
|
-
);
|
|
4512
|
-
var JudgmentTriggersFile = Type.Object(
|
|
4513
|
-
{
|
|
4514
|
-
schema_version: Type.Literal(SCHEMA_VERSIONS.judgment),
|
|
4515
|
-
triggers: Type.Record(Type.String(), JudgmentTrigger)
|
|
4516
|
-
},
|
|
4517
|
-
{ additionalProperties: false }
|
|
4518
|
-
);
|
|
4519
|
-
var JudgmentRulesFile = Type.Object(
|
|
4520
|
-
{
|
|
4521
|
-
schema_version: Type.Literal(SCHEMA_VERSIONS.judgment),
|
|
4522
|
-
rules: Type.Record(Type.String(), FallbackRule)
|
|
4523
|
-
},
|
|
4524
|
-
{ additionalProperties: false }
|
|
4525
|
-
);
|
|
4526
|
-
Type.Union([JudgmentTriggersFile, JudgmentRulesFile]);
|
|
4527
|
-
var UserOverrideEntry = Type.Object(
|
|
4528
|
-
{
|
|
4529
|
-
id: Type.String({ minLength: 1 }),
|
|
4530
|
-
// kebab-case (e.g. 'brainstorm', 'arch-review')
|
|
4531
|
-
keywords: Type.Array(Type.String({ minLength: 1 }), { minItems: 1 }),
|
|
4532
|
-
triggers: Type.Array(Type.String({ minLength: 1 }), { minItems: 1 })
|
|
4533
|
-
},
|
|
4534
|
-
{ additionalProperties: false }
|
|
4535
|
-
);
|
|
4536
|
-
var UserOverridesFile = Type.Object(
|
|
4537
|
-
{
|
|
4538
|
-
schema_version: Type.Literal("harnessed.user-overrides.v1"),
|
|
4539
|
-
overrides: Type.Array(UserOverrideEntry, { minItems: 1 })
|
|
4540
|
-
},
|
|
4541
|
-
{ additionalProperties: false }
|
|
4542
|
-
);
|
|
4543
|
-
|
|
4544
|
-
// src/workflow/judgmentResolver.ts
|
|
4545
|
-
var TriggerNotFoundError = class extends Error {
|
|
4546
|
-
constructor(trigger, fileName) {
|
|
4547
|
-
super(`Trigger '${trigger}' not found in judgments/${fileName}.yaml`);
|
|
4548
|
-
this.trigger = trigger;
|
|
4549
|
-
this.fileName = fileName;
|
|
4550
|
-
this.name = "TriggerNotFoundError";
|
|
4551
|
-
}
|
|
4552
|
-
trigger;
|
|
4553
|
-
fileName;
|
|
4554
|
-
};
|
|
4555
|
-
var _fileCache = /* @__PURE__ */ new Map();
|
|
4556
|
-
async function resolveJudgmentGate(gateRef, context, packageRoot) {
|
|
4557
|
-
const userOverrides = context.user_overrides;
|
|
4558
|
-
if (Array.isArray(userOverrides) && userOverrides.includes(gateRef)) {
|
|
4559
|
-
return true;
|
|
4560
|
-
}
|
|
4561
|
-
const parts = gateRef.split(".");
|
|
4562
|
-
if (parts.length !== 4 || parts[0] !== "judgments") {
|
|
4563
|
-
throw new Error(`Invalid gate ref: ${gateRef}`);
|
|
4564
|
-
}
|
|
4565
|
-
const [, fileName, triggerName, fieldName] = parts;
|
|
4566
|
-
let parsed = _fileCache.get(fileName);
|
|
4567
|
-
if (!parsed) {
|
|
4568
|
-
const yamlPath = resolve(packageRoot, "workflows", "judgments", `${fileName}.yaml`);
|
|
4569
|
-
const raw = await readFile(yamlPath, "utf8");
|
|
4570
|
-
const parsedRaw = parse(raw);
|
|
4571
|
-
const schema = fileName === "fallback" ? JudgmentRulesFile : JudgmentTriggersFile;
|
|
4572
|
-
if (!Value.Check(schema, parsedRaw)) {
|
|
4573
|
-
const errors = [...Value.Errors(schema, parsedRaw)].slice(0, 3).map((e) => `${e.path} ${e.message}`).join("; ");
|
|
4574
|
-
throw new Error(`Invalid judgment file ${fileName}.yaml: ${errors}`);
|
|
4575
|
-
}
|
|
4576
|
-
parsed = parsedRaw;
|
|
4577
|
-
_fileCache.set(fileName, parsed);
|
|
4578
|
-
}
|
|
4579
|
-
const entries = "triggers" in parsed ? parsed.triggers : parsed.rules;
|
|
4580
|
-
const trigger = entries[triggerName];
|
|
4581
|
-
if (!trigger) {
|
|
4582
|
-
throw new TriggerNotFoundError(triggerName, fileName);
|
|
4583
|
-
}
|
|
4584
|
-
const expr = fieldName === "fires" ? trigger.fires_when : fieldName === "skips" ? trigger.skips_when : void 0;
|
|
4585
|
-
if (!expr) {
|
|
4586
|
-
throw new Error(
|
|
4587
|
-
`Field '${fieldName}' has no expression in trigger '${triggerName}' of ${fileName}.yaml`
|
|
4588
|
-
);
|
|
4589
|
-
}
|
|
4590
|
-
return evalGate(expr, context);
|
|
4591
|
-
}
|
|
4592
4802
|
|
|
4593
4803
|
// src/workflow/lib/promiseExtract.ts
|
|
4594
4804
|
var PROMISE_PATTERN = /<promise>([^<]+)<\/promise>/;
|
|
@@ -5009,7 +5219,7 @@ function loadPhases(yamlPath, vars) {
|
|
|
5009
5219
|
}
|
|
5010
5220
|
return validated;
|
|
5011
5221
|
}
|
|
5012
|
-
function
|
|
5222
|
+
function resolveMasterYamlPath2(masterName, packageRoot) {
|
|
5013
5223
|
return masterName === "auto" ? resolve(packageRoot, "workflows", "auto", "workflow.yaml") : resolve(packageRoot, "workflows", masterName, "auto", "workflow.yaml");
|
|
5014
5224
|
}
|
|
5015
5225
|
function resolveSubYamlPath(masterName, subName, packageRoot) {
|
|
@@ -5113,7 +5323,7 @@ async function maybeArbitrate(firedClauses, packageRoot) {
|
|
|
5113
5323
|
|
|
5114
5324
|
// src/workflow/masterOrchestrator.ts
|
|
5115
5325
|
async function runMasterOrchestrator(masterName, context, packageRoot, spawnDriver = defaultSpawnDriver, opts = {}) {
|
|
5116
|
-
const yamlPath =
|
|
5326
|
+
const yamlPath = resolveMasterYamlPath2(masterName, packageRoot);
|
|
5117
5327
|
const raw = await readFile(yamlPath, "utf8");
|
|
5118
5328
|
const parsed = parse(raw);
|
|
5119
5329
|
if (!Value.Check(WorkflowSchemaV3, parsed)) {
|
|
@@ -5496,6 +5706,94 @@ async function runWorkflow(yamlPath, vars, opts = {}) {
|
|
|
5496
5706
|
...skippedPhases.length > 0 ? { skippedPhases } : {}
|
|
5497
5707
|
};
|
|
5498
5708
|
}
|
|
5709
|
+
|
|
5710
|
+
// src/cli/prompt.ts
|
|
5711
|
+
var DEFAULT_MAX_ITERATIONS = 20;
|
|
5712
|
+
var DEFAULT_MODEL = "sonnet";
|
|
5713
|
+
var DEFAULT_SPECIALIST = "Implementation Engineer";
|
|
5714
|
+
var LANG_NAMES = {
|
|
5715
|
+
en: "English",
|
|
5716
|
+
"zh-Hans": "\u7B80\u4F53\u4E2D\u6587 (Simplified Chinese)",
|
|
5717
|
+
"zh-CN": "\u7B80\u4F53\u4E2D\u6587 (Simplified Chinese)",
|
|
5718
|
+
"zh-Hant": "\u7E41\u9AD4\u4E2D\u6587 (Traditional Chinese)",
|
|
5719
|
+
"zh-TW": "\u7E41\u9AD4\u4E2D\u6587 (Traditional Chinese)"
|
|
5720
|
+
};
|
|
5721
|
+
function buildLanguageSection() {
|
|
5722
|
+
const code = process.env.HARNESSED_USER_LANG;
|
|
5723
|
+
if (!code) return "";
|
|
5724
|
+
const name = LANG_NAMES[code] ?? code;
|
|
5725
|
+
return `
|
|
5726
|
+
## Language
|
|
5727
|
+
Respond in ${name}. Keep code, commands, file/identifier/API names, error messages, stack traces, URLs, commit hashes, and version numbers in their original form (do not translate or transliterate them).
|
|
5728
|
+
`;
|
|
5729
|
+
}
|
|
5730
|
+
var PROTOCOLS = `
|
|
5731
|
+
## Completion protocol
|
|
5732
|
+
When done emit: <promise>COMPLETE</promise>
|
|
5733
|
+
|
|
5734
|
+
## Clarification protocol
|
|
5735
|
+
If you hit a gray area you cannot decide (\u22652 reasonable options, missing key context, ambiguous requirement), do NOT self-decide. Emit:
|
|
5736
|
+
STATUS: NEEDS_CLARIFICATION
|
|
5737
|
+
1. <question>
|
|
5738
|
+
2. <question>
|
|
5739
|
+
The main session will relay these to the user and re-spawn you with answers.
|
|
5740
|
+
`;
|
|
5741
|
+
async function resolveMaxIterations2(sub, packageRoot) {
|
|
5742
|
+
try {
|
|
5743
|
+
const path = resolve(packageRoot, "workflows", "defaults.yaml");
|
|
5744
|
+
const raw = await readFile(path, "utf8");
|
|
5745
|
+
const doc = parse(raw);
|
|
5746
|
+
const entry = doc?.ralph_max_iterations?.[sub];
|
|
5747
|
+
if (typeof entry === "number" && Number.isFinite(entry)) {
|
|
5748
|
+
return entry;
|
|
5749
|
+
}
|
|
5750
|
+
if (entry && typeof entry === "object") {
|
|
5751
|
+
for (const v of Object.values(entry)) {
|
|
5752
|
+
if (typeof v === "number" && Number.isFinite(v)) {
|
|
5753
|
+
return v;
|
|
5754
|
+
}
|
|
5755
|
+
}
|
|
5756
|
+
}
|
|
5757
|
+
return DEFAULT_MAX_ITERATIONS;
|
|
5758
|
+
} catch {
|
|
5759
|
+
return DEFAULT_MAX_ITERATIONS;
|
|
5760
|
+
}
|
|
5761
|
+
}
|
|
5762
|
+
function registerPrompt(program2) {
|
|
5763
|
+
program2.command("prompt").description(
|
|
5764
|
+
"Print a spawn-ready prompt for a sub-workflow (role + checklist + protocols, no spawn)."
|
|
5765
|
+
).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) => {
|
|
5766
|
+
const packageRoot = getPackageRoot();
|
|
5767
|
+
const workflowsDir = resolve(packageRoot, "workflows");
|
|
5768
|
+
const rolePrompts = await loadRolePrompts(workflowsDir);
|
|
5769
|
+
const def = buildAgentDef(sub, rolePrompts, void 0, void 0, void 0);
|
|
5770
|
+
const body = def.prompt;
|
|
5771
|
+
const taskSection = typeof raw.task === "string" && raw.task.length > 0 ? `## Task
|
|
5772
|
+
${raw.task}
|
|
5773
|
+
|
|
5774
|
+
` : "";
|
|
5775
|
+
const fullPrompt = `${taskSection}${body}
|
|
5776
|
+
${PROTOCOLS}${buildLanguageSection()}`;
|
|
5777
|
+
if (raw.json) {
|
|
5778
|
+
const maxIterations = await resolveMaxIterations2(sub, packageRoot);
|
|
5779
|
+
const rp = rolePrompts[sub];
|
|
5780
|
+
const model = def.model ?? DEFAULT_MODEL;
|
|
5781
|
+
const specialist = rp?.specialist ?? DEFAULT_SPECIALIST;
|
|
5782
|
+
console.log(
|
|
5783
|
+
JSON.stringify({
|
|
5784
|
+
prompt: fullPrompt,
|
|
5785
|
+
max_iterations: maxIterations,
|
|
5786
|
+
model,
|
|
5787
|
+
specialist
|
|
5788
|
+
})
|
|
5789
|
+
);
|
|
5790
|
+
process.exit(0);
|
|
5791
|
+
return;
|
|
5792
|
+
}
|
|
5793
|
+
console.log(fullPrompt);
|
|
5794
|
+
process.exit(0);
|
|
5795
|
+
});
|
|
5796
|
+
}
|
|
5499
5797
|
async function loadUserOverrides(packageRoot) {
|
|
5500
5798
|
const yamlPath = resolve(packageRoot, "workflows", "judgments", "user-overrides.yaml");
|
|
5501
5799
|
let raw;
|
|
@@ -5542,7 +5840,7 @@ var _autoChainCache = null;
|
|
|
5542
5840
|
var _autoChainLoadFailed = false;
|
|
5543
5841
|
function registerRun(program2) {
|
|
5544
5842
|
program2.command("run").description(
|
|
5545
|
-
"Run a harnessed workflow (
|
|
5843
|
+
"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."
|
|
5546
5844
|
).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(
|
|
5547
5845
|
"--max-iterations <n>",
|
|
5548
5846
|
"ralph-loop max iter (default 20; honored Phase 3 onward)",
|
|
@@ -6789,7 +7087,7 @@ var uninstallNpmCli = async (ctx) => {
|
|
|
6789
7087
|
const m = install.cmd.match(/npm\s+(?:install|i)\s+(?:-g\s+)?(\S+)/);
|
|
6790
7088
|
const pkg = m?.[1] ?? ctx.manifest.metadata.upstream.source;
|
|
6791
7089
|
const isWin = process.platform === "win32";
|
|
6792
|
-
const result = await new Promise((
|
|
7090
|
+
const result = await new Promise((resolve16) => {
|
|
6793
7091
|
const child = isWin ? spawn("cmd.exe", ["/c", "npm", "uninstall", "-g", pkg], { windowsHide: true }) : spawn("npm", ["uninstall", "-g", pkg], { shell: false });
|
|
6794
7092
|
let stderr = "";
|
|
6795
7093
|
child.stderr?.setEncoding("utf8").on("data", (c) => {
|
|
@@ -6797,15 +7095,15 @@ var uninstallNpmCli = async (ctx) => {
|
|
|
6797
7095
|
});
|
|
6798
7096
|
const timer = setTimeout(() => {
|
|
6799
7097
|
child.kill("SIGKILL");
|
|
6800
|
-
|
|
7098
|
+
resolve16({ exitCode: -1, stderr: `${stderr}[timeout]` });
|
|
6801
7099
|
}, 3e4);
|
|
6802
7100
|
child.on("error", (e) => {
|
|
6803
7101
|
clearTimeout(timer);
|
|
6804
|
-
|
|
7102
|
+
resolve16({ exitCode: -1, stderr: e.message });
|
|
6805
7103
|
});
|
|
6806
7104
|
child.on("close", (code) => {
|
|
6807
7105
|
clearTimeout(timer);
|
|
6808
|
-
|
|
7106
|
+
resolve16({ exitCode: code ?? -1, stderr });
|
|
6809
7107
|
});
|
|
6810
7108
|
});
|
|
6811
7109
|
if (result.exitCode !== 0) {
|
|
@@ -7104,6 +7402,9 @@ registerResume(program);
|
|
|
7104
7402
|
registerUninstall(program);
|
|
7105
7403
|
registerSetup(program);
|
|
7106
7404
|
registerRun(program);
|
|
7405
|
+
registerGates(program);
|
|
7406
|
+
registerPrompt(program);
|
|
7407
|
+
registerCheckpoint(program);
|
|
7107
7408
|
program.parse(process.argv);
|
|
7108
7409
|
//# sourceMappingURL=cli.mjs.map
|
|
7109
7410
|
//# sourceMappingURL=cli.mjs.map
|