harnessed 3.0.2 → 3.1.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 +75 -36
- package/dist/cli.mjs +174 -133
- 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/workflows/auto/SKILL.md +99 -0
- package/workflows/auto/workflow.yaml +47 -0
package/dist/cli.mjs
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { execSync, spawnSync, spawn } from 'child_process';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { existsSync, mkdirSync, renameSync, writeFileSync, readFileSync, appendFileSync, readdirSync } from 'fs';
|
|
4
|
+
import { join, resolve, dirname, relative } from 'path';
|
|
5
|
+
import { homedir } from 'os';
|
|
5
6
|
import { Type } from '@sinclair/typebox';
|
|
6
7
|
import { Value } from '@sinclair/typebox/value';
|
|
7
8
|
import { LineCounter, parseDocument, parse, isSeq, isScalar } from 'yaml';
|
|
8
|
-
import { homedir } from 'os';
|
|
9
9
|
import { readFile, readdir, unlink, writeFile, stat, rm, cp, access, mkdir, rename } from 'fs/promises';
|
|
10
10
|
import lockfile from 'proper-lockfile';
|
|
11
11
|
import { Command } from 'commander';
|
|
@@ -78,6 +78,46 @@ var init_origin_check = __esm({
|
|
|
78
78
|
"src/cli/lib/origin-check.ts"() {
|
|
79
79
|
}
|
|
80
80
|
});
|
|
81
|
+
function getHarnessedRoot() {
|
|
82
|
+
const override = process.env.HARNESSED_ROOT_OVERRIDE;
|
|
83
|
+
if (override !== void 0 && override !== "") return override;
|
|
84
|
+
return join(homedir(), ".claude", "harnessed");
|
|
85
|
+
}
|
|
86
|
+
function harnessedSubdir(name) {
|
|
87
|
+
return join(getHarnessedRoot(), name);
|
|
88
|
+
}
|
|
89
|
+
function harnessedFile(name) {
|
|
90
|
+
return join(getHarnessedRoot(), name);
|
|
91
|
+
}
|
|
92
|
+
function migrateLegacyHarnessedRoot() {
|
|
93
|
+
const legacyRoot = join(homedir(), ".harnessed");
|
|
94
|
+
const newRoot = join(homedir(), ".claude", "harnessed");
|
|
95
|
+
const claudeParent = join(homedir(), ".claude");
|
|
96
|
+
if (!existsSync(legacyRoot)) return;
|
|
97
|
+
if (!existsSync(newRoot)) {
|
|
98
|
+
mkdirSync(claudeParent, { recursive: true });
|
|
99
|
+
renameSync(legacyRoot, newRoot);
|
|
100
|
+
console.error(
|
|
101
|
+
`[harnessed] migrated legacy state directory ${legacyRoot} \u2192 ${newRoot} (v3.0.3 path change)`
|
|
102
|
+
);
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
const safetyBak = join(homedir(), ".harnessed.legacy-bak");
|
|
106
|
+
if (existsSync(safetyBak)) {
|
|
107
|
+
console.error(
|
|
108
|
+
`[harnessed] WARN: ${legacyRoot} reappeared after a prior migration (existing bak at ${safetyBak}); leaving in place \u2014 inspect manually if needed`
|
|
109
|
+
);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
renameSync(legacyRoot, safetyBak);
|
|
113
|
+
console.error(
|
|
114
|
+
`[harnessed] both ${legacyRoot} and ${newRoot} existed \u2014 legacy directory preserved at ${safetyBak} (review manually if you need data from it; v3.0.3 path change)`
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
var init_harnessedRoot = __esm({
|
|
118
|
+
"src/installers/lib/harnessedRoot.ts"() {
|
|
119
|
+
}
|
|
120
|
+
});
|
|
81
121
|
|
|
82
122
|
// src/cli/lib/probe-gstack.ts
|
|
83
123
|
var probe_gstack_exports = {};
|
|
@@ -408,7 +448,7 @@ function writeCheckpoint(c, customPath) {
|
|
|
408
448
|
throw new CheckpointWriteError(`Schema validation failed: ${errs}`);
|
|
409
449
|
}
|
|
410
450
|
const enforced = enforceBudget(c);
|
|
411
|
-
const path =
|
|
451
|
+
const path = join(harnessedSubdir("checkpoints"), `${enforced.phase}.json`);
|
|
412
452
|
mkdirSync(dirname(path), { recursive: true });
|
|
413
453
|
writeFileSync(path, JSON.stringify(enforced, null, 2), "utf8");
|
|
414
454
|
return path;
|
|
@@ -416,6 +456,7 @@ function writeCheckpoint(c, customPath) {
|
|
|
416
456
|
var BUDGET_TOKEN, CheckpointTooLargeError, CheckpointWriteError;
|
|
417
457
|
var init_template = __esm({
|
|
418
458
|
"src/checkpoint/template.ts"() {
|
|
459
|
+
init_harnessedRoot();
|
|
419
460
|
init_schema();
|
|
420
461
|
BUDGET_TOKEN = 1e3;
|
|
421
462
|
CheckpointTooLargeError = class extends Error {
|
|
@@ -579,10 +620,25 @@ var init_check_planning_with_files = __esm({
|
|
|
579
620
|
REMEDIATION = "install via Claude Code plugin marketplace: `claude plugin install planning-with-files` (requires >=2.2.0 per R20.15 + D-15)";
|
|
580
621
|
}
|
|
581
622
|
});
|
|
623
|
+
function statePath() {
|
|
624
|
+
return harnessedFile("current-workflow.json");
|
|
625
|
+
}
|
|
626
|
+
function lockTarget() {
|
|
627
|
+
return getHarnessedRoot();
|
|
628
|
+
}
|
|
629
|
+
function lockOpts() {
|
|
630
|
+
return {
|
|
631
|
+
stale: 1e4,
|
|
632
|
+
retries: { retries: 3, factor: 2, minTimeout: 100 },
|
|
633
|
+
lockfilePath: harnessedFile(".lock")
|
|
634
|
+
};
|
|
635
|
+
}
|
|
582
636
|
async function withLock(fn) {
|
|
637
|
+
const target = lockTarget();
|
|
638
|
+
await mkdir(target, { recursive: true });
|
|
583
639
|
let release;
|
|
584
640
|
try {
|
|
585
|
-
release = await lockfile.lock(
|
|
641
|
+
release = await lockfile.lock(target, lockOpts());
|
|
586
642
|
} catch (e) {
|
|
587
643
|
if (e.code === "ELOCKED") throw new LockHeldError();
|
|
588
644
|
throw e;
|
|
@@ -596,7 +652,7 @@ async function withLock(fn) {
|
|
|
596
652
|
async function readCurrentWorkflow() {
|
|
597
653
|
let raw;
|
|
598
654
|
try {
|
|
599
|
-
raw = await readFile(
|
|
655
|
+
raw = await readFile(statePath(), "utf8");
|
|
600
656
|
} catch {
|
|
601
657
|
return null;
|
|
602
658
|
}
|
|
@@ -617,9 +673,10 @@ async function writeCurrentWorkflow(s) {
|
|
|
617
673
|
const errs = [...Value.Errors(CurrentWorkflowV1, s)].map((e) => e.message).join("; ");
|
|
618
674
|
throw new WorkflowStateError(`current-workflow schema validation failed: ${errs}`);
|
|
619
675
|
}
|
|
620
|
-
|
|
676
|
+
const path = statePath();
|
|
677
|
+
await mkdir(dirname(path), { recursive: true });
|
|
621
678
|
await withLock(async () => {
|
|
622
|
-
await writeFile(
|
|
679
|
+
await writeFile(path, JSON.stringify(s, null, 2), "utf8");
|
|
623
680
|
});
|
|
624
681
|
}
|
|
625
682
|
async function activate(phase, checkpointPath = null) {
|
|
@@ -636,18 +693,12 @@ async function complete() {
|
|
|
636
693
|
if (!s) return;
|
|
637
694
|
await writeCurrentWorkflow({ ...s, status: "complete", completed_at: (/* @__PURE__ */ new Date()).toISOString() });
|
|
638
695
|
}
|
|
639
|
-
var
|
|
696
|
+
var WorkflowStateError, LockHeldError;
|
|
640
697
|
var init_state = __esm({
|
|
641
698
|
"src/checkpoint/state.ts"() {
|
|
699
|
+
init_harnessedRoot();
|
|
642
700
|
init_schemaVersion();
|
|
643
701
|
init_schema();
|
|
644
|
-
STATE_PATH = ".harnessed/current-workflow.json";
|
|
645
|
-
LOCK_TARGET = ".harnessed";
|
|
646
|
-
LOCK_OPTS = {
|
|
647
|
-
stale: 1e4,
|
|
648
|
-
retries: { retries: 3, factor: 2, minTimeout: 100 },
|
|
649
|
-
lockfilePath: ".harnessed/.lock"
|
|
650
|
-
};
|
|
651
702
|
WorkflowStateError = class extends Error {
|
|
652
703
|
constructor(message) {
|
|
653
704
|
super(message);
|
|
@@ -657,7 +708,7 @@ var init_state = __esm({
|
|
|
657
708
|
LockHeldError = class _LockHeldError extends Error {
|
|
658
709
|
constructor() {
|
|
659
710
|
super(
|
|
660
|
-
|
|
711
|
+
`another harnessed process holds the lock at ${harnessedFile(".lock")} \u2014 wait or kill stale process (try: harnessed status)`
|
|
661
712
|
);
|
|
662
713
|
this.name = "LockHeldError";
|
|
663
714
|
Object.setPrototypeOf(this, _LockHeldError.prototype);
|
|
@@ -740,7 +791,10 @@ __export(resume_exports, {
|
|
|
740
791
|
async function runResume() {
|
|
741
792
|
const current = await readCurrentWorkflow();
|
|
742
793
|
if (!current) {
|
|
743
|
-
return {
|
|
794
|
+
return {
|
|
795
|
+
status: "no-paused-phase",
|
|
796
|
+
error: "no current-workflow.json found under <harnessed-root>"
|
|
797
|
+
};
|
|
744
798
|
}
|
|
745
799
|
if (current.status !== "paused") {
|
|
746
800
|
return {
|
|
@@ -793,7 +847,7 @@ var init_resume = __esm({
|
|
|
793
847
|
|
|
794
848
|
// package.json
|
|
795
849
|
var package_default = {
|
|
796
|
-
version: "3.0
|
|
850
|
+
version: "3.1.0"};
|
|
797
851
|
|
|
798
852
|
// src/manifest/errors.ts
|
|
799
853
|
function instancePathToKeyPath(instancePath) {
|
|
@@ -1546,7 +1600,12 @@ audited ${yamls.length} manifest${yamls.length === 1 ? "" : "s"} \u2014 ${findin
|
|
|
1546
1600
|
process.exit(errorCount > 0 ? 1 : 0);
|
|
1547
1601
|
});
|
|
1548
1602
|
}
|
|
1549
|
-
|
|
1603
|
+
|
|
1604
|
+
// src/cli/audit-log.ts
|
|
1605
|
+
init_harnessedRoot();
|
|
1606
|
+
function auditPath() {
|
|
1607
|
+
return harnessedFile("audit.log");
|
|
1608
|
+
}
|
|
1550
1609
|
var REDACT_PATTERNS = [
|
|
1551
1610
|
[/api[_-]?key\s*[:=]\s*\S+/gi, "api_key=[REDACTED]"],
|
|
1552
1611
|
[/\btoken\s*[:=]\s*\S+/gi, "token=[REDACTED]"],
|
|
@@ -1594,7 +1653,9 @@ function pipeToJq(filterExpr, lines) {
|
|
|
1594
1653
|
});
|
|
1595
1654
|
}
|
|
1596
1655
|
function registerAuditLog(program2) {
|
|
1597
|
-
program2.command("audit-log").description(
|
|
1656
|
+
program2.command("audit-log").description(
|
|
1657
|
+
"Query routing audit log (<harnessed-root>/audit.log) with optional jq filter (R10.1)"
|
|
1658
|
+
).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(
|
|
1598
1659
|
async (opts) => {
|
|
1599
1660
|
const tailN = opts.tail !== void 0 ? Number(opts.tail) : 50;
|
|
1600
1661
|
if (Number.isNaN(tailN) || tailN < 1) {
|
|
@@ -1606,11 +1667,12 @@ function registerAuditLog(program2) {
|
|
|
1606
1667
|
console.error("\u2717 --head must be a positive integer");
|
|
1607
1668
|
process.exit(2);
|
|
1608
1669
|
}
|
|
1609
|
-
|
|
1610
|
-
|
|
1670
|
+
const path = auditPath();
|
|
1671
|
+
if (!existsSync(path)) {
|
|
1672
|
+
console.log(`no audit records found (${path} does not exist)`);
|
|
1611
1673
|
process.exit(0);
|
|
1612
1674
|
}
|
|
1613
|
-
const raw = readFileSync(
|
|
1675
|
+
const raw = readFileSync(path, "utf8");
|
|
1614
1676
|
const lines = raw.split("\n").map((l) => l.trim()).filter((l) => l.length > 0);
|
|
1615
1677
|
if (lines.length === 0) {
|
|
1616
1678
|
console.log("no audit records found (audit.log is empty)");
|
|
@@ -1646,8 +1708,11 @@ function registerAuditLog(program2) {
|
|
|
1646
1708
|
}
|
|
1647
1709
|
);
|
|
1648
1710
|
}
|
|
1711
|
+
|
|
1712
|
+
// src/installers/lib/backup.ts
|
|
1713
|
+
init_harnessedRoot();
|
|
1649
1714
|
function getBackupRoot() {
|
|
1650
|
-
return
|
|
1715
|
+
return harnessedSubdir("backups");
|
|
1651
1716
|
}
|
|
1652
1717
|
var HOME_DIR = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
1653
1718
|
function mirrorPath(target, scope, backupDir) {
|
|
@@ -2317,7 +2382,12 @@ function matchesWhen(when, task) {
|
|
|
2317
2382
|
}
|
|
2318
2383
|
return true;
|
|
2319
2384
|
}
|
|
2320
|
-
|
|
2385
|
+
|
|
2386
|
+
// src/audit/log.ts
|
|
2387
|
+
init_harnessedRoot();
|
|
2388
|
+
function auditPath2() {
|
|
2389
|
+
return harnessedFile("audit.log");
|
|
2390
|
+
}
|
|
2321
2391
|
Type.Object(
|
|
2322
2392
|
{
|
|
2323
2393
|
ts: Type.String(),
|
|
@@ -2352,8 +2422,9 @@ function buildAuditRecord(task, decision, matched, ctx) {
|
|
|
2352
2422
|
};
|
|
2353
2423
|
}
|
|
2354
2424
|
function emitAuditRecord(record) {
|
|
2355
|
-
|
|
2356
|
-
|
|
2425
|
+
const path = auditPath2();
|
|
2426
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
2427
|
+
appendFileSync(path, `${JSON.stringify(record)}
|
|
2357
2428
|
`);
|
|
2358
2429
|
}
|
|
2359
2430
|
|
|
@@ -2371,18 +2442,19 @@ function emitAudit(task, decision, matched, outcome, sessionId) {
|
|
|
2371
2442
|
}
|
|
2372
2443
|
|
|
2373
2444
|
// src/checkpoint/engineHook.ts
|
|
2445
|
+
init_harnessedRoot();
|
|
2374
2446
|
init_schemaVersion();
|
|
2375
2447
|
init_state();
|
|
2376
2448
|
init_template();
|
|
2377
2449
|
async function activatePhase(phaseId) {
|
|
2378
|
-
const checkpointPath =
|
|
2450
|
+
const checkpointPath = join(harnessedSubdir("checkpoints"), `${phaseId}.json`);
|
|
2379
2451
|
await activate(phaseId, checkpointPath);
|
|
2380
2452
|
return { checkpointPath };
|
|
2381
2453
|
}
|
|
2382
2454
|
async function completePhase(ctx) {
|
|
2383
2455
|
if (ctx.phaseId === "unknown") {
|
|
2384
2456
|
console.error(
|
|
2385
|
-
|
|
2457
|
+
`[harnessed] WARN engineHook: phaseId="unknown" \u2014 checkpoint paths fall back to ${join(harnessedSubdir("checkpoints"), "unknown.json")} (Karpathy fail-loud non-blocking; W-04 mitigation)`
|
|
2386
2458
|
);
|
|
2387
2459
|
}
|
|
2388
2460
|
writeCheckpoint({
|
|
@@ -2395,7 +2467,7 @@ async function completePhase(ctx) {
|
|
|
2395
2467
|
...ctx.sessionId ? { session_id: ctx.sessionId } : {},
|
|
2396
2468
|
cwd: process.cwd(),
|
|
2397
2469
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2398
|
-
archive_path:
|
|
2470
|
+
archive_path: `${join(harnessedSubdir("archive"), `phase-${ctx.phaseId}`)}/`
|
|
2399
2471
|
});
|
|
2400
2472
|
await complete();
|
|
2401
2473
|
}
|
|
@@ -2479,11 +2551,11 @@ var VerbatimCompleteFailError = class extends Error {
|
|
|
2479
2551
|
}
|
|
2480
2552
|
lastMessage;
|
|
2481
2553
|
};
|
|
2482
|
-
async function ralphLoopWrap(
|
|
2554
|
+
async function ralphLoopWrap(spawn6, maxIter) {
|
|
2483
2555
|
let last = "";
|
|
2484
2556
|
let sessionId;
|
|
2485
2557
|
for (let i = 0; i < maxIter; i++) {
|
|
2486
|
-
last = await
|
|
2558
|
+
last = await spawn6(sessionId, (id) => {
|
|
2487
2559
|
sessionId = id;
|
|
2488
2560
|
});
|
|
2489
2561
|
if (isComplete(last)) return last;
|
|
@@ -3239,12 +3311,15 @@ function preflight(ctx) {
|
|
|
3239
3311
|
}
|
|
3240
3312
|
return { ok: errors.length === 0, errors };
|
|
3241
3313
|
}
|
|
3314
|
+
|
|
3315
|
+
// src/installers/lib/state.ts
|
|
3316
|
+
init_harnessedRoot();
|
|
3242
3317
|
var DEFAULT_STATE = { version: "1", installed: {} };
|
|
3243
|
-
function
|
|
3244
|
-
return
|
|
3318
|
+
function statePath2(_cwd) {
|
|
3319
|
+
return harnessedFile("state.json");
|
|
3245
3320
|
}
|
|
3246
3321
|
async function readState(cwd) {
|
|
3247
|
-
const path =
|
|
3322
|
+
const path = statePath2();
|
|
3248
3323
|
let raw;
|
|
3249
3324
|
try {
|
|
3250
3325
|
raw = await readFile(path, "utf8");
|
|
@@ -3265,7 +3340,7 @@ async function readState(cwd) {
|
|
|
3265
3340
|
}
|
|
3266
3341
|
}
|
|
3267
3342
|
async function writeState(cwd, state) {
|
|
3268
|
-
const path =
|
|
3343
|
+
const path = statePath2();
|
|
3269
3344
|
const tmp = `${path}.tmp`;
|
|
3270
3345
|
await mkdir(dirname(path), { recursive: true });
|
|
3271
3346
|
await writeFile(tmp, `${JSON.stringify(state, null, 2)}
|
|
@@ -3273,7 +3348,7 @@ async function writeState(cwd, state) {
|
|
|
3273
3348
|
await rename(tmp, path);
|
|
3274
3349
|
}
|
|
3275
3350
|
async function updateInstalled(cwd, name, version, manifestSha1) {
|
|
3276
|
-
const state = await readState(
|
|
3351
|
+
const state = await readState();
|
|
3277
3352
|
state.installed[name] = {
|
|
3278
3353
|
version,
|
|
3279
3354
|
installedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -3382,6 +3457,39 @@ var installCcHookAdd = async (ctx) => {
|
|
|
3382
3457
|
await updateInstalled(ctx.cwd, ctx.manifest.metadata.name, "", "");
|
|
3383
3458
|
return { ok: true, backupId: bk.backupId, appliedFiles: [settingsPath] };
|
|
3384
3459
|
};
|
|
3460
|
+
function getUserClaudeJsonPath() {
|
|
3461
|
+
return join(homedir(), ".claude.json");
|
|
3462
|
+
}
|
|
3463
|
+
async function readUserClaudeJson() {
|
|
3464
|
+
const path = getUserClaudeJsonPath();
|
|
3465
|
+
let raw;
|
|
3466
|
+
try {
|
|
3467
|
+
raw = await readFile(path, "utf8");
|
|
3468
|
+
} catch (err2) {
|
|
3469
|
+
if (err2.code === "ENOENT") return {};
|
|
3470
|
+
throw err2;
|
|
3471
|
+
}
|
|
3472
|
+
try {
|
|
3473
|
+
const parsed = JSON.parse(raw);
|
|
3474
|
+
if (parsed === null || typeof parsed !== "object") return {};
|
|
3475
|
+
return parsed;
|
|
3476
|
+
} catch {
|
|
3477
|
+
return {};
|
|
3478
|
+
}
|
|
3479
|
+
}
|
|
3480
|
+
async function isMcpServerRegistered(name) {
|
|
3481
|
+
const config = await readUserClaudeJson();
|
|
3482
|
+
const servers = config.mcpServers;
|
|
3483
|
+
if (!servers || typeof servers !== "object") return false;
|
|
3484
|
+
return Object.hasOwn(servers, name);
|
|
3485
|
+
}
|
|
3486
|
+
async function isPluginRegistered(pluginName) {
|
|
3487
|
+
const config = await readUserClaudeJson();
|
|
3488
|
+
const plugins = config.enabledPlugins;
|
|
3489
|
+
if (!plugins || typeof plugins !== "object") return false;
|
|
3490
|
+
if (Object.hasOwn(plugins, pluginName)) return true;
|
|
3491
|
+
return Object.keys(plugins).some((k) => k.split("@")[0] === pluginName);
|
|
3492
|
+
}
|
|
3385
3493
|
function runArgs(claudeArgs, cwd, timeoutMs = 15e3) {
|
|
3386
3494
|
return new Promise((resolve9) => {
|
|
3387
3495
|
const isWin = process.platform === "win32";
|
|
@@ -3525,34 +3633,8 @@ ${newEntry}
|
|
|
3525
3633
|
)
|
|
3526
3634
|
};
|
|
3527
3635
|
}
|
|
3528
|
-
const
|
|
3529
|
-
|
|
3530
|
-
cwd: spawnCwd,
|
|
3531
|
-
shell: process.platform === "win32",
|
|
3532
|
-
windowsHide: true
|
|
3533
|
-
});
|
|
3534
|
-
let stdout2 = "";
|
|
3535
|
-
let stderr = "";
|
|
3536
|
-
child.stdout?.setEncoding("utf8").on("data", (c) => {
|
|
3537
|
-
stdout2 += c;
|
|
3538
|
-
});
|
|
3539
|
-
child.stderr?.setEncoding("utf8").on("data", (c) => {
|
|
3540
|
-
stderr += c;
|
|
3541
|
-
});
|
|
3542
|
-
const timer = setTimeout(() => {
|
|
3543
|
-
child.kill("SIGKILL");
|
|
3544
|
-
resolve9({ exitCode: -1, stderr: `${stderr}[timeout]`, stdout: stdout2 });
|
|
3545
|
-
}, 15e3);
|
|
3546
|
-
child.on("error", (e) => {
|
|
3547
|
-
clearTimeout(timer);
|
|
3548
|
-
resolve9({ exitCode: -1, stderr: e.message, stdout: stdout2 });
|
|
3549
|
-
});
|
|
3550
|
-
child.on("close", (code) => {
|
|
3551
|
-
clearTimeout(timer);
|
|
3552
|
-
resolve9({ exitCode: code ?? -1, stderr, stdout: stdout2 });
|
|
3553
|
-
});
|
|
3554
|
-
});
|
|
3555
|
-
if (vr.exitCode !== 0 || !vr.stdout.includes(pluginName)) {
|
|
3636
|
+
const registered = await isPluginRegistered(pluginName);
|
|
3637
|
+
if (!registered) {
|
|
3556
3638
|
return {
|
|
3557
3639
|
ok: false,
|
|
3558
3640
|
phase: "verify",
|
|
@@ -3560,7 +3642,7 @@ ${newEntry}
|
|
|
3560
3642
|
error: err(
|
|
3561
3643
|
ctx,
|
|
3562
3644
|
"/spec/verify/cmd",
|
|
3563
|
-
`verify
|
|
3645
|
+
`verify: plugin '${pluginName}' not found in enabledPlugins map of ~/.claude.json after install spawn exit 0 (file may have been overwritten, or claude plugin install wrote to a non-default location)`,
|
|
3564
3646
|
"verify-failed"
|
|
3565
3647
|
)
|
|
3566
3648
|
};
|
|
@@ -3828,6 +3910,8 @@ var installGitCloneWithSetup = async (ctx) => {
|
|
|
3828
3910
|
await updateInstalled(ctx.cwd, name, install.git_ref, "");
|
|
3829
3911
|
return { ok: true, backupId: bk.backupId, appliedFiles: [cloneTarget] };
|
|
3830
3912
|
};
|
|
3913
|
+
|
|
3914
|
+
// src/installers/mcpHttpAdd.ts
|
|
3831
3915
|
function resolveEnvVars(value) {
|
|
3832
3916
|
const pattern = /\$\{([A-Z_][A-Z0-9_]*)\}/g;
|
|
3833
3917
|
let resolved = value;
|
|
@@ -3977,34 +4061,8 @@ ${newEntry}
|
|
|
3977
4061
|
)
|
|
3978
4062
|
};
|
|
3979
4063
|
}
|
|
3980
|
-
const
|
|
3981
|
-
|
|
3982
|
-
cwd: spawnCwd,
|
|
3983
|
-
shell: process.platform === "win32",
|
|
3984
|
-
windowsHide: true
|
|
3985
|
-
});
|
|
3986
|
-
let stdout2 = "";
|
|
3987
|
-
let stderr = "";
|
|
3988
|
-
child.stdout?.setEncoding("utf8").on("data", (c) => {
|
|
3989
|
-
stdout2 += c;
|
|
3990
|
-
});
|
|
3991
|
-
child.stderr?.setEncoding("utf8").on("data", (c) => {
|
|
3992
|
-
stderr += c;
|
|
3993
|
-
});
|
|
3994
|
-
const timer = setTimeout(() => {
|
|
3995
|
-
child.kill("SIGKILL");
|
|
3996
|
-
resolve9({ exitCode: -1, stderr: `${stderr}[timeout]`, stdout: stdout2 });
|
|
3997
|
-
}, 15e3);
|
|
3998
|
-
child.on("error", (e) => {
|
|
3999
|
-
clearTimeout(timer);
|
|
4000
|
-
resolve9({ exitCode: -1, stderr: e.message, stdout: stdout2 });
|
|
4001
|
-
});
|
|
4002
|
-
child.on("close", (code) => {
|
|
4003
|
-
clearTimeout(timer);
|
|
4004
|
-
resolve9({ exitCode: code ?? -1, stderr, stdout: stdout2 });
|
|
4005
|
-
});
|
|
4006
|
-
});
|
|
4007
|
-
if (vr.exitCode !== 0 || !vr.stdout.includes(name)) {
|
|
4064
|
+
const registered = await isMcpServerRegistered(name);
|
|
4065
|
+
if (!registered) {
|
|
4008
4066
|
return {
|
|
4009
4067
|
ok: false,
|
|
4010
4068
|
phase: "verify",
|
|
@@ -4012,7 +4070,7 @@ ${newEntry}
|
|
|
4012
4070
|
error: err(
|
|
4013
4071
|
ctx,
|
|
4014
4072
|
"/spec/verify/cmd",
|
|
4015
|
-
`verify
|
|
4073
|
+
`verify: '${name}' not found in mcpServers map of ~/.claude.json after install spawn exit 0 (file may have been overwritten, or claude mcp add wrote to a non-default location)`,
|
|
4016
4074
|
"verify-failed"
|
|
4017
4075
|
)
|
|
4018
4076
|
};
|
|
@@ -4020,6 +4078,8 @@ ${newEntry}
|
|
|
4020
4078
|
await updateInstalled(ctx.cwd, name, install.npm_version, "");
|
|
4021
4079
|
return { ok: true, backupId: bk.backupId, appliedFiles: [mcpFile] };
|
|
4022
4080
|
};
|
|
4081
|
+
|
|
4082
|
+
// src/installers/mcpStdioAdd.ts
|
|
4023
4083
|
var installMcpStdioAdd = async (ctx) => {
|
|
4024
4084
|
const install = ctx.manifest.spec.install;
|
|
4025
4085
|
if (install.method !== "mcp-stdio-add") {
|
|
@@ -4117,34 +4177,8 @@ ${newEntry}
|
|
|
4117
4177
|
)
|
|
4118
4178
|
};
|
|
4119
4179
|
}
|
|
4120
|
-
const
|
|
4121
|
-
|
|
4122
|
-
cwd: spawnCwd,
|
|
4123
|
-
shell: process.platform === "win32",
|
|
4124
|
-
windowsHide: true
|
|
4125
|
-
});
|
|
4126
|
-
let stdout2 = "";
|
|
4127
|
-
let stderr = "";
|
|
4128
|
-
child.stdout?.setEncoding("utf8").on("data", (c) => {
|
|
4129
|
-
stdout2 += c;
|
|
4130
|
-
});
|
|
4131
|
-
child.stderr?.setEncoding("utf8").on("data", (c) => {
|
|
4132
|
-
stderr += c;
|
|
4133
|
-
});
|
|
4134
|
-
const timer = setTimeout(() => {
|
|
4135
|
-
child.kill("SIGKILL");
|
|
4136
|
-
resolve9({ exitCode: -1, stderr: `${stderr}[timeout]`, stdout: stdout2 });
|
|
4137
|
-
}, 15e3);
|
|
4138
|
-
child.on("error", (e) => {
|
|
4139
|
-
clearTimeout(timer);
|
|
4140
|
-
resolve9({ exitCode: -1, stderr: e.message, stdout: stdout2 });
|
|
4141
|
-
});
|
|
4142
|
-
child.on("close", (code) => {
|
|
4143
|
-
clearTimeout(timer);
|
|
4144
|
-
resolve9({ exitCode: code ?? -1, stderr, stdout: stdout2 });
|
|
4145
|
-
});
|
|
4146
|
-
});
|
|
4147
|
-
if (vr.exitCode !== 0 || !vr.stdout.includes(name)) {
|
|
4180
|
+
const registered = await isMcpServerRegistered(name);
|
|
4181
|
+
if (!registered) {
|
|
4148
4182
|
return {
|
|
4149
4183
|
ok: false,
|
|
4150
4184
|
phase: "verify",
|
|
@@ -4152,7 +4186,7 @@ ${newEntry}
|
|
|
4152
4186
|
error: err(
|
|
4153
4187
|
ctx,
|
|
4154
4188
|
"/spec/verify/cmd",
|
|
4155
|
-
`verify
|
|
4189
|
+
`verify: '${name}' not found in mcpServers map of ~/.claude.json after install spawn exit 0 (file may have been overwritten, or claude mcp add wrote to a non-default location)`,
|
|
4156
4190
|
"verify-failed"
|
|
4157
4191
|
)
|
|
4158
4192
|
};
|
|
@@ -4778,7 +4812,8 @@ function registerRollback(program2) {
|
|
|
4778
4812
|
}
|
|
4779
4813
|
init_checkAgentTeams();
|
|
4780
4814
|
var FLAT_LEGACY_DEPRECATED = /* @__PURE__ */ new Set(["plan-feature", "execute-task", "verify-work"]);
|
|
4781
|
-
var FLAT_LEGACY_KEEP = /* @__PURE__ */ new Set(["research", "retro"]);
|
|
4815
|
+
var FLAT_LEGACY_KEEP = /* @__PURE__ */ new Set(["research", "retro", "auto"]);
|
|
4816
|
+
var FLAT_TOP_LEVEL_MASTERS = /* @__PURE__ */ new Set(["auto"]);
|
|
4782
4817
|
var NON_WORKFLOW_DIRS = /* @__PURE__ */ new Set(["disciplines", "judgments"]);
|
|
4783
4818
|
async function scanWorkflowsNested(workflowsDir, entries) {
|
|
4784
4819
|
const workflows = [];
|
|
@@ -4806,7 +4841,7 @@ async function scanWorkflowsNested(workflowsDir, entries) {
|
|
|
4806
4841
|
continue;
|
|
4807
4842
|
}
|
|
4808
4843
|
if (FLAT_LEGACY_KEEP.has(entry)) {
|
|
4809
|
-
workflows.push({ name: entry, relPath: entry, isMaster:
|
|
4844
|
+
workflows.push({ name: entry, relPath: entry, isMaster: FLAT_TOP_LEVEL_MASTERS.has(entry) });
|
|
4810
4845
|
continue;
|
|
4811
4846
|
}
|
|
4812
4847
|
workflows.push({ name: entry, relPath: entry, isMaster: false });
|
|
@@ -5029,12 +5064,15 @@ MCP servers configured. Run \`/mcp\` in Claude Code to verify each server's conn
|
|
|
5029
5064
|
process.exit(0);
|
|
5030
5065
|
});
|
|
5031
5066
|
}
|
|
5067
|
+
|
|
5068
|
+
// src/cli/status.ts
|
|
5069
|
+
init_harnessedRoot();
|
|
5032
5070
|
function registerStatus(program2) {
|
|
5033
|
-
program2.command("status").description("Show installed upstreams (from
|
|
5071
|
+
program2.command("status").description("Show installed upstreams (from <harnessed-root>/state.json)").action(async () => {
|
|
5034
5072
|
const state = await readState(process.cwd());
|
|
5035
5073
|
const names = Object.keys(state.installed).sort();
|
|
5036
5074
|
if (names.length === 0) {
|
|
5037
|
-
console.log(
|
|
5075
|
+
console.log(`no installs recorded (${harnessedFile("state.json")} absent or empty)`);
|
|
5038
5076
|
} else {
|
|
5039
5077
|
for (const n of names) {
|
|
5040
5078
|
const e = state.installed[n];
|
|
@@ -5044,18 +5082,19 @@ function registerStatus(program2) {
|
|
|
5044
5082
|
console.log(`
|
|
5045
5083
|
${names.length} install${names.length === 1 ? "" : "s"} recorded`);
|
|
5046
5084
|
}
|
|
5085
|
+
const lockPath = harnessedFile(".lock");
|
|
5047
5086
|
try {
|
|
5048
|
-
const isLocked = await lockfile.check(
|
|
5049
|
-
lockfilePath:
|
|
5087
|
+
const isLocked = await lockfile.check(getHarnessedRoot(), {
|
|
5088
|
+
lockfilePath: lockPath,
|
|
5050
5089
|
stale: 1e4
|
|
5051
5090
|
});
|
|
5052
5091
|
if (isLocked) {
|
|
5053
|
-
const s = await stat(
|
|
5092
|
+
const s = await stat(lockPath);
|
|
5054
5093
|
const ageMs = Date.now() - s.mtime.getTime();
|
|
5055
5094
|
const stale = ageMs > 1e4;
|
|
5056
5095
|
console.log(`
|
|
5057
5096
|
lock: held (since ${s.mtime.toISOString()})${stale ? " \u2014 STALE" : ""}`);
|
|
5058
|
-
console.log(
|
|
5097
|
+
console.log(` to release: wait for process to finish or delete ${lockPath}`);
|
|
5059
5098
|
} else {
|
|
5060
5099
|
console.log("\nlock: free");
|
|
5061
5100
|
}
|
|
@@ -5384,6 +5423,8 @@ function registerUninstall(program2) {
|
|
|
5384
5423
|
}
|
|
5385
5424
|
|
|
5386
5425
|
// src/cli.ts
|
|
5426
|
+
init_harnessedRoot();
|
|
5427
|
+
migrateLegacyHarnessedRoot();
|
|
5387
5428
|
var program = new Command();
|
|
5388
5429
|
program.name("harnessed").description("AI coding harness package manager + composition orchestrator").version(package_default.version);
|
|
5389
5430
|
registerInstall(program);
|