@harness-engineering/cli 1.14.0 → 1.15.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/dist/agents/skills/claude-code/harness-autopilot/SKILL.md +240 -39
- package/dist/agents/skills/claude-code/harness-autopilot/skill.yaml +6 -0
- package/dist/agents/skills/claude-code/harness-product-spec/SKILL.md +5 -5
- package/dist/agents/skills/gemini-cli/harness-autopilot/SKILL.md +240 -39
- package/dist/agents/skills/gemini-cli/harness-autopilot/skill.yaml +6 -0
- package/dist/agents/skills/gemini-cli/harness-product-spec/SKILL.md +5 -5
- package/dist/agents/skills/package.json +1 -0
- package/dist/agents/skills/vitest.config.mts +5 -0
- package/dist/{agents-md-YTYQDA3P.js → agents-md-ZGNIDWAF.js} +1 -1
- package/dist/{architecture-JQZYM4US.js → architecture-ZLIH5533.js} +2 -2
- package/dist/bin/harness-mcp.js +11 -11
- package/dist/bin/harness.js +16 -14
- package/dist/{check-phase-gate-L3RADYWO.js → check-phase-gate-ZOXVBDCN.js} +3 -3
- package/dist/{chunk-7IP4JIFL.js → chunk-2BKLWLY6.js} +4 -4
- package/dist/{chunk-OSXBPAMK.js → chunk-3ZZKVN62.js} +1 -1
- package/dist/{chunk-O5OJVPL6.js → chunk-B2HKP423.js} +1 -1
- package/dist/{chunk-YPYGXRDR.js → chunk-EDXIVMAP.js} +4 -4
- package/dist/{chunk-XKECDXJS.js → chunk-J4RAX7YB.js} +738 -186
- package/dist/{chunk-6KTUUFRN.js → chunk-LGYBN7Y6.js} +1 -1
- package/dist/{chunk-3C2MLBPJ.js → chunk-N25INEIX.js} +1 -1
- package/dist/{chunk-NLVUVUGD.js → chunk-ND2ENWDM.js} +1 -1
- package/dist/{chunk-YZD2MRNQ.js → chunk-NNHDDXYT.js} +379 -119
- package/dist/{chunk-S2FXOWOR.js → chunk-OFXQSFOW.js} +2 -2
- package/dist/{chunk-OXLLOSSR.js → chunk-VEPAJXBW.js} +2 -2
- package/dist/{chunk-TPOTOBR7.js → chunk-YLXFKVJE.js} +3 -3
- package/dist/{chunk-ABQHQ6I5.js → chunk-Z2OOPXJO.js} +1238 -133
- package/dist/{ci-workflow-EQZFVX3P.js → ci-workflow-765LSHRD.js} +1 -1
- package/dist/{dist-HWXF2C3R.js → dist-ALQDD67R.js} +47 -1
- package/dist/{docs-7ECGYMAV.js → docs-NRMQCOJ6.js} +3 -3
- package/dist/{engine-EG4EH4IX.js → engine-3RB7MXPP.js} +1 -1
- package/dist/{entropy-5USWKLVS.js → entropy-6AGX2ZUN.js} +2 -2
- package/dist/{feedback-UTBXZZHF.js → feedback-MY4QZIFD.js} +1 -1
- package/dist/{generate-agent-definitions-3PM5EU7V.js → generate-agent-definitions-ZAE726AU.js} +1 -1
- package/dist/index.d.ts +8 -8
- package/dist/index.js +13 -13
- package/dist/{loader-ZPALXIVR.js → loader-UUTVMQCC.js} +1 -1
- package/dist/{mcp-362EZHF4.js → mcp-VU5FMO52.js} +11 -11
- package/dist/{performance-OQAFMJUD.js → performance-2D7G6NMJ.js} +2 -2
- package/dist/{review-pipeline-C4GCFVGP.js → review-pipeline-RAQ55ISU.js} +1 -1
- package/dist/{runtime-7YLVK453.js → runtime-BCK5RRZQ.js} +1 -1
- package/dist/{security-PZOX7AQS.js → security-2RPQEN62.js} +1 -1
- package/dist/{validate-FD3Z6VJD.js → validate-KBYQAEWE.js} +2 -2
- package/dist/{validate-cross-check-WNJM6H2D.js → validate-cross-check-OABMREW4.js} +1 -1
- package/package.json +5 -3
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
OutputMode,
|
|
7
7
|
createCheckPhaseGateCommand,
|
|
8
8
|
findFiles
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-LGYBN7Y6.js";
|
|
10
10
|
import {
|
|
11
11
|
createGenerateAgentDefinitionsCommand,
|
|
12
12
|
generateAgentDefinitions
|
|
@@ -50,14 +50,14 @@ import {
|
|
|
50
50
|
handleGetImpact,
|
|
51
51
|
handleOrphanDeletion,
|
|
52
52
|
persistToolingConfig
|
|
53
|
-
} from "./chunk-
|
|
53
|
+
} from "./chunk-NNHDDXYT.js";
|
|
54
54
|
import {
|
|
55
55
|
VALID_PLATFORMS
|
|
56
56
|
} from "./chunk-ZOAWBDWU.js";
|
|
57
57
|
import {
|
|
58
58
|
findConfigFile,
|
|
59
59
|
resolveConfig
|
|
60
|
-
} from "./chunk-
|
|
60
|
+
} from "./chunk-B2HKP423.js";
|
|
61
61
|
import {
|
|
62
62
|
resolveGlobalSkillsDir,
|
|
63
63
|
resolvePersonasDir,
|
|
@@ -127,14 +127,14 @@ import {
|
|
|
127
127
|
validateKnowledgeMap,
|
|
128
128
|
writeConfig,
|
|
129
129
|
writeLockfile
|
|
130
|
-
} from "./chunk-
|
|
130
|
+
} from "./chunk-Z2OOPXJO.js";
|
|
131
131
|
import {
|
|
132
132
|
Err,
|
|
133
133
|
Ok
|
|
134
134
|
} from "./chunk-ERS5EVUZ.js";
|
|
135
135
|
|
|
136
136
|
// src/index.ts
|
|
137
|
-
import { Command as
|
|
137
|
+
import { Command as Command61 } from "commander";
|
|
138
138
|
|
|
139
139
|
// src/commands/validate.ts
|
|
140
140
|
import { Command } from "commander";
|
|
@@ -213,7 +213,7 @@ function createValidateCommand() {
|
|
|
213
213
|
process.exit(result.error.exitCode);
|
|
214
214
|
}
|
|
215
215
|
if (opts.crossCheck) {
|
|
216
|
-
const { runCrossCheck: runCrossCheck2 } = await import("./validate-cross-check-
|
|
216
|
+
const { runCrossCheck: runCrossCheck2 } = await import("./validate-cross-check-OABMREW4.js");
|
|
217
217
|
const cwd = process.cwd();
|
|
218
218
|
const specsDir = path.join(cwd, "docs", "specs");
|
|
219
219
|
const plansDir = path.join(cwd, "docs", "plans");
|
|
@@ -481,10 +481,10 @@ async function runCheckSecurity(cwd, options) {
|
|
|
481
481
|
const projectRoot = path4.resolve(cwd);
|
|
482
482
|
let configData = {};
|
|
483
483
|
try {
|
|
484
|
-
const
|
|
484
|
+
const fs29 = await import("fs");
|
|
485
485
|
const configPath = path4.join(projectRoot, "harness.config.json");
|
|
486
|
-
if (
|
|
487
|
-
const raw =
|
|
486
|
+
if (fs29.existsSync(configPath)) {
|
|
487
|
+
const raw = fs29.readFileSync(configPath, "utf-8");
|
|
488
488
|
const parsed = JSON.parse(raw);
|
|
489
489
|
configData = parsed.security ?? {};
|
|
490
490
|
}
|
|
@@ -570,7 +570,7 @@ function registerBenchCommand(perf) {
|
|
|
570
570
|
perf.command("bench [glob]").description("Run benchmarks via vitest bench").action(async (glob, _opts, cmd) => {
|
|
571
571
|
const globalOpts = cmd.optsWithGlobals();
|
|
572
572
|
const cwd = process.cwd();
|
|
573
|
-
const { BenchmarkRunner } = await import("./dist-
|
|
573
|
+
const { BenchmarkRunner } = await import("./dist-ALQDD67R.js");
|
|
574
574
|
const runner = new BenchmarkRunner();
|
|
575
575
|
const benchFiles = runner.discover(cwd, glob);
|
|
576
576
|
if (benchFiles.length === 0) {
|
|
@@ -638,7 +638,7 @@ function registerBaselinesCommands(perf) {
|
|
|
638
638
|
baselines.command("update").description("Update baselines from latest benchmark run").action(async (_opts, cmd) => {
|
|
639
639
|
const globalOpts = cmd.optsWithGlobals();
|
|
640
640
|
const cwd = process.cwd();
|
|
641
|
-
const { BenchmarkRunner } = await import("./dist-
|
|
641
|
+
const { BenchmarkRunner } = await import("./dist-ALQDD67R.js");
|
|
642
642
|
const runner = new BenchmarkRunner();
|
|
643
643
|
const manager = new BaselineManager(cwd);
|
|
644
644
|
logger.info("Running benchmarks to update baselines...");
|
|
@@ -671,7 +671,7 @@ function registerReportCommand(perf) {
|
|
|
671
671
|
perf.command("report").description("Full performance report with metrics, trends, and hotspots").action(async (_opts, cmd) => {
|
|
672
672
|
const globalOpts = cmd.optsWithGlobals();
|
|
673
673
|
const cwd = process.cwd();
|
|
674
|
-
const { EntropyAnalyzer: EntropyAnalyzer2 } = await import("./dist-
|
|
674
|
+
const { EntropyAnalyzer: EntropyAnalyzer2 } = await import("./dist-ALQDD67R.js");
|
|
675
675
|
const analyzer = new EntropyAnalyzer2({
|
|
676
676
|
rootDir: path5.resolve(cwd),
|
|
677
677
|
analyze: { complexity: true, coupling: true }
|
|
@@ -3125,8 +3125,8 @@ function createResetCommand() {
|
|
|
3125
3125
|
}
|
|
3126
3126
|
if (!opts.yes) {
|
|
3127
3127
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
3128
|
-
const answer = await new Promise((
|
|
3129
|
-
rl.question("Reset project state? This cannot be undone. [y/N] ",
|
|
3128
|
+
const answer = await new Promise((resolve30) => {
|
|
3129
|
+
rl.question("Reset project state? This cannot be undone. [y/N] ", resolve30);
|
|
3130
3130
|
});
|
|
3131
3131
|
rl.close();
|
|
3132
3132
|
if (answer.toLowerCase() !== "y" && answer.toLowerCase() !== "yes") {
|
|
@@ -3240,11 +3240,302 @@ function createStateCommand() {
|
|
|
3240
3240
|
return command;
|
|
3241
3241
|
}
|
|
3242
3242
|
|
|
3243
|
+
// src/commands/setup.ts
|
|
3244
|
+
import { Command as Command34 } from "commander";
|
|
3245
|
+
import * as fs17 from "fs";
|
|
3246
|
+
import * as os4 from "os";
|
|
3247
|
+
import * as path29 from "path";
|
|
3248
|
+
import chalk3 from "chalk";
|
|
3249
|
+
|
|
3250
|
+
// src/utils/first-run.ts
|
|
3251
|
+
import * as fs16 from "fs";
|
|
3252
|
+
import * as os3 from "os";
|
|
3253
|
+
import * as path28 from "path";
|
|
3254
|
+
var HARNESS_DIR = path28.join(os3.homedir(), ".harness");
|
|
3255
|
+
var MARKER_FILE = path28.join(HARNESS_DIR, ".setup-complete");
|
|
3256
|
+
function isFirstRun() {
|
|
3257
|
+
return !fs16.existsSync(MARKER_FILE);
|
|
3258
|
+
}
|
|
3259
|
+
function markSetupComplete() {
|
|
3260
|
+
fs16.mkdirSync(HARNESS_DIR, { recursive: true });
|
|
3261
|
+
fs16.writeFileSync(MARKER_FILE, "", "utf-8");
|
|
3262
|
+
}
|
|
3263
|
+
function printFirstRunWelcome() {
|
|
3264
|
+
try {
|
|
3265
|
+
if (!isFirstRun()) return;
|
|
3266
|
+
if (process.env.CI) return;
|
|
3267
|
+
if (process.argv.includes("--quiet")) return;
|
|
3268
|
+
process.stderr.write("Welcome to harness! Run `harness setup` to get started.\n");
|
|
3269
|
+
} catch {
|
|
3270
|
+
}
|
|
3271
|
+
}
|
|
3272
|
+
|
|
3273
|
+
// src/utils/node-version.ts
|
|
3274
|
+
import semver2 from "semver";
|
|
3275
|
+
var REQUIRED_NODE_VERSION = ">=22.0.0";
|
|
3276
|
+
function checkNodeVersion() {
|
|
3277
|
+
return {
|
|
3278
|
+
satisfies: semver2.satisfies(process.version, REQUIRED_NODE_VERSION),
|
|
3279
|
+
current: process.version,
|
|
3280
|
+
required: ">=22"
|
|
3281
|
+
};
|
|
3282
|
+
}
|
|
3283
|
+
|
|
3284
|
+
// src/commands/setup.ts
|
|
3285
|
+
function checkNodeVersion2() {
|
|
3286
|
+
const result = checkNodeVersion();
|
|
3287
|
+
if (result.satisfies) {
|
|
3288
|
+
return { status: "pass", message: `Node.js ${result.current} (requires ${result.required})` };
|
|
3289
|
+
}
|
|
3290
|
+
return { status: "fail", message: `Node.js ${result.current} \u2014 requires ${result.required}` };
|
|
3291
|
+
}
|
|
3292
|
+
function runSlashCommandGeneration() {
|
|
3293
|
+
try {
|
|
3294
|
+
const results = generateSlashCommands({
|
|
3295
|
+
global: true,
|
|
3296
|
+
platforms: ["claude-code", "gemini-cli"],
|
|
3297
|
+
yes: true,
|
|
3298
|
+
includeGlobal: false,
|
|
3299
|
+
skillsDir: "",
|
|
3300
|
+
dryRun: false
|
|
3301
|
+
});
|
|
3302
|
+
const outputDirs = results.map((r) => r.outputDir).join(", ");
|
|
3303
|
+
return { status: "pass", message: `Generated global slash commands -> ${outputDirs}` };
|
|
3304
|
+
} catch (error) {
|
|
3305
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
3306
|
+
return { status: "fail", message: `Slash command generation failed \u2014 ${msg}` };
|
|
3307
|
+
}
|
|
3308
|
+
}
|
|
3309
|
+
function detectClient(dirName) {
|
|
3310
|
+
return fs17.existsSync(path29.join(os4.homedir(), dirName));
|
|
3311
|
+
}
|
|
3312
|
+
function runMcpSetup(cwd) {
|
|
3313
|
+
const results = [];
|
|
3314
|
+
const clients = [
|
|
3315
|
+
{ name: "Claude Code", dir: ".claude", client: "claude", configTarget: ".mcp.json" },
|
|
3316
|
+
{
|
|
3317
|
+
name: "Gemini CLI",
|
|
3318
|
+
dir: ".gemini",
|
|
3319
|
+
client: "gemini",
|
|
3320
|
+
configTarget: ".gemini/settings.json"
|
|
3321
|
+
}
|
|
3322
|
+
];
|
|
3323
|
+
for (const { name, dir, client, configTarget } of clients) {
|
|
3324
|
+
if (!detectClient(dir)) {
|
|
3325
|
+
results.push({
|
|
3326
|
+
status: "warn",
|
|
3327
|
+
message: `${name} not detected \u2014 skipped MCP configuration`
|
|
3328
|
+
});
|
|
3329
|
+
continue;
|
|
3330
|
+
}
|
|
3331
|
+
try {
|
|
3332
|
+
setupMcp(cwd, client);
|
|
3333
|
+
results.push({ status: "pass", message: `Configured MCP for ${name} -> ${configTarget}` });
|
|
3334
|
+
} catch (error) {
|
|
3335
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
3336
|
+
results.push({
|
|
3337
|
+
status: "fail",
|
|
3338
|
+
message: `MCP configuration failed for ${name} \u2014 ${msg}`
|
|
3339
|
+
});
|
|
3340
|
+
}
|
|
3341
|
+
}
|
|
3342
|
+
return results;
|
|
3343
|
+
}
|
|
3344
|
+
function formatStep(result) {
|
|
3345
|
+
const icon = result.status === "pass" ? chalk3.green("\u2713") : result.status === "warn" ? chalk3.yellow("\u26A0") : chalk3.red("\u2717");
|
|
3346
|
+
return ` ${icon} ${result.message}`;
|
|
3347
|
+
}
|
|
3348
|
+
function runSetup(cwd) {
|
|
3349
|
+
const steps = [];
|
|
3350
|
+
const nodeResult = checkNodeVersion2();
|
|
3351
|
+
steps.push(nodeResult);
|
|
3352
|
+
if (nodeResult.status === "fail") {
|
|
3353
|
+
return { steps, success: false };
|
|
3354
|
+
}
|
|
3355
|
+
const slashResult = runSlashCommandGeneration();
|
|
3356
|
+
steps.push(slashResult);
|
|
3357
|
+
const mcpResults = runMcpSetup(cwd);
|
|
3358
|
+
steps.push(...mcpResults);
|
|
3359
|
+
const success = steps.every((s) => s.status !== "fail");
|
|
3360
|
+
if (success) {
|
|
3361
|
+
markSetupComplete();
|
|
3362
|
+
}
|
|
3363
|
+
return { steps, success };
|
|
3364
|
+
}
|
|
3365
|
+
function createSetupCommand() {
|
|
3366
|
+
return new Command34("setup").description("Configure harness environment: slash commands, MCP, and more").action(() => {
|
|
3367
|
+
const cwd = process.cwd();
|
|
3368
|
+
console.log("");
|
|
3369
|
+
console.log(` ${chalk3.bold("harness setup")}`);
|
|
3370
|
+
console.log("");
|
|
3371
|
+
const { steps, success } = runSetup(cwd);
|
|
3372
|
+
for (const step of steps) {
|
|
3373
|
+
console.log(formatStep(step));
|
|
3374
|
+
}
|
|
3375
|
+
console.log("");
|
|
3376
|
+
if (success) {
|
|
3377
|
+
console.log(" Setup complete. Next steps:");
|
|
3378
|
+
console.log(" - Open a project directory and run /harness:initialize-project");
|
|
3379
|
+
console.log(" - Or run harness init --name my-project to scaffold a new one");
|
|
3380
|
+
console.log(" - Run harness doctor anytime to check your environment");
|
|
3381
|
+
console.log("");
|
|
3382
|
+
}
|
|
3383
|
+
process.exit(success ? ExitCode.SUCCESS : ExitCode.VALIDATION_FAILED);
|
|
3384
|
+
});
|
|
3385
|
+
}
|
|
3386
|
+
|
|
3387
|
+
// src/commands/doctor.ts
|
|
3388
|
+
import { Command as Command35 } from "commander";
|
|
3389
|
+
import * as fs18 from "fs";
|
|
3390
|
+
import * as os5 from "os";
|
|
3391
|
+
import * as path30 from "path";
|
|
3392
|
+
import chalk4 from "chalk";
|
|
3393
|
+
function checkNodeVersion3() {
|
|
3394
|
+
const result = checkNodeVersion();
|
|
3395
|
+
if (result.satisfies) {
|
|
3396
|
+
return {
|
|
3397
|
+
name: "node",
|
|
3398
|
+
status: "pass",
|
|
3399
|
+
message: `Node.js ${result.current} (requires ${result.required})`
|
|
3400
|
+
};
|
|
3401
|
+
}
|
|
3402
|
+
return {
|
|
3403
|
+
name: "node",
|
|
3404
|
+
status: "fail",
|
|
3405
|
+
message: `Node.js ${result.current} (requires ${result.required})`,
|
|
3406
|
+
fix: "Install Node.js >= 22: https://nodejs.org/"
|
|
3407
|
+
};
|
|
3408
|
+
}
|
|
3409
|
+
function countCommandFiles(dir, ext) {
|
|
3410
|
+
try {
|
|
3411
|
+
return fs18.readdirSync(dir).filter((f) => f.endsWith(ext)).length;
|
|
3412
|
+
} catch {
|
|
3413
|
+
return 0;
|
|
3414
|
+
}
|
|
3415
|
+
}
|
|
3416
|
+
function checkSlashCommands() {
|
|
3417
|
+
const platforms = [
|
|
3418
|
+
{
|
|
3419
|
+
name: "Claude Code",
|
|
3420
|
+
dir: path30.join(os5.homedir(), ".claude", "commands", "harness"),
|
|
3421
|
+
ext: ".md",
|
|
3422
|
+
client: "claude-code"
|
|
3423
|
+
},
|
|
3424
|
+
{
|
|
3425
|
+
name: "Gemini CLI",
|
|
3426
|
+
dir: path30.join(os5.homedir(), ".gemini", "commands", "harness"),
|
|
3427
|
+
ext: ".toml",
|
|
3428
|
+
client: "gemini-cli"
|
|
3429
|
+
}
|
|
3430
|
+
];
|
|
3431
|
+
return platforms.map(({ name, dir, ext, client }) => {
|
|
3432
|
+
const count = countCommandFiles(dir, ext);
|
|
3433
|
+
if (count > 0) {
|
|
3434
|
+
return {
|
|
3435
|
+
name: `slash-commands-${client}`,
|
|
3436
|
+
status: "pass",
|
|
3437
|
+
message: `Slash commands installed -> ${dir} (${count} commands)`
|
|
3438
|
+
};
|
|
3439
|
+
}
|
|
3440
|
+
return {
|
|
3441
|
+
name: `slash-commands-${client}`,
|
|
3442
|
+
status: "fail",
|
|
3443
|
+
message: `No slash commands found for ${name}`,
|
|
3444
|
+
fix: "Run: harness setup"
|
|
3445
|
+
};
|
|
3446
|
+
});
|
|
3447
|
+
}
|
|
3448
|
+
function readJsonSafe(filePath) {
|
|
3449
|
+
try {
|
|
3450
|
+
if (!fs18.existsSync(filePath)) return null;
|
|
3451
|
+
return JSON.parse(fs18.readFileSync(filePath, "utf-8"));
|
|
3452
|
+
} catch {
|
|
3453
|
+
return null;
|
|
3454
|
+
}
|
|
3455
|
+
}
|
|
3456
|
+
function checkMcpConfig(cwd) {
|
|
3457
|
+
const results = [];
|
|
3458
|
+
const claudeConfigPath = path30.join(cwd, ".mcp.json");
|
|
3459
|
+
const claudeConfig = readJsonSafe(claudeConfigPath);
|
|
3460
|
+
if (claudeConfig?.mcpServers?.["harness"]) {
|
|
3461
|
+
results.push({
|
|
3462
|
+
name: "mcp-claude",
|
|
3463
|
+
status: "pass",
|
|
3464
|
+
message: "MCP configured for Claude Code"
|
|
3465
|
+
});
|
|
3466
|
+
} else {
|
|
3467
|
+
results.push({
|
|
3468
|
+
name: "mcp-claude",
|
|
3469
|
+
status: "fail",
|
|
3470
|
+
message: "MCP not configured for Claude Code",
|
|
3471
|
+
fix: "Run: harness setup-mcp --client claude"
|
|
3472
|
+
});
|
|
3473
|
+
}
|
|
3474
|
+
const geminiConfigPath = path30.join(os5.homedir(), ".gemini", "settings.json");
|
|
3475
|
+
const geminiConfig = readJsonSafe(geminiConfigPath);
|
|
3476
|
+
if (geminiConfig?.mcpServers?.["harness"]) {
|
|
3477
|
+
results.push({
|
|
3478
|
+
name: "mcp-gemini",
|
|
3479
|
+
status: "pass",
|
|
3480
|
+
message: "MCP configured for Gemini CLI"
|
|
3481
|
+
});
|
|
3482
|
+
} else {
|
|
3483
|
+
results.push({
|
|
3484
|
+
name: "mcp-gemini",
|
|
3485
|
+
status: "fail",
|
|
3486
|
+
message: "MCP not configured for Gemini CLI",
|
|
3487
|
+
fix: "Run: harness setup-mcp --client gemini"
|
|
3488
|
+
});
|
|
3489
|
+
}
|
|
3490
|
+
return results;
|
|
3491
|
+
}
|
|
3492
|
+
function runDoctor(cwd) {
|
|
3493
|
+
const checks = [];
|
|
3494
|
+
checks.push(checkNodeVersion3());
|
|
3495
|
+
checks.push(...checkSlashCommands());
|
|
3496
|
+
checks.push(...checkMcpConfig(cwd));
|
|
3497
|
+
const allPassed = checks.every((c) => c.status === "pass");
|
|
3498
|
+
return { checks, allPassed };
|
|
3499
|
+
}
|
|
3500
|
+
function formatCheck(check) {
|
|
3501
|
+
const icon = check.status === "pass" ? chalk4.green("\u2713") : chalk4.red("\u2717");
|
|
3502
|
+
let line = ` ${icon} ${check.message}`;
|
|
3503
|
+
if (check.status === "fail" && check.fix) {
|
|
3504
|
+
line += `
|
|
3505
|
+
-> ${check.fix}`;
|
|
3506
|
+
}
|
|
3507
|
+
return line;
|
|
3508
|
+
}
|
|
3509
|
+
function createDoctorCommand() {
|
|
3510
|
+
return new Command35("doctor").description("Check environment health: Node version, slash commands, MCP configuration").action((_opts, cmd) => {
|
|
3511
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
3512
|
+
const cwd = process.cwd();
|
|
3513
|
+
const useJson = globalOpts.json;
|
|
3514
|
+
const result = runDoctor(cwd);
|
|
3515
|
+
if (useJson) {
|
|
3516
|
+
console.log(JSON.stringify(result, null, 2));
|
|
3517
|
+
} else {
|
|
3518
|
+
console.log("");
|
|
3519
|
+
console.log(` ${chalk4.bold("harness doctor")}`);
|
|
3520
|
+
console.log("");
|
|
3521
|
+
for (const check of result.checks) {
|
|
3522
|
+
console.log(formatCheck(check));
|
|
3523
|
+
}
|
|
3524
|
+
console.log("");
|
|
3525
|
+
const passed = result.checks.filter((c) => c.status === "pass").length;
|
|
3526
|
+
const total = result.checks.length;
|
|
3527
|
+
console.log(` ${passed}/${total} checks passed`);
|
|
3528
|
+
console.log("");
|
|
3529
|
+
}
|
|
3530
|
+
process.exit(result.allPassed ? ExitCode.SUCCESS : ExitCode.VALIDATION_FAILED);
|
|
3531
|
+
});
|
|
3532
|
+
}
|
|
3533
|
+
|
|
3243
3534
|
// src/commands/ci/index.ts
|
|
3244
|
-
import { Command as
|
|
3535
|
+
import { Command as Command38 } from "commander";
|
|
3245
3536
|
|
|
3246
3537
|
// src/commands/ci/check.ts
|
|
3247
|
-
import { Command as
|
|
3538
|
+
import { Command as Command36 } from "commander";
|
|
3248
3539
|
var VALID_CHECKS = [
|
|
3249
3540
|
"validate",
|
|
3250
3541
|
"deps",
|
|
@@ -3284,7 +3575,7 @@ function parseFailOn(failOn) {
|
|
|
3284
3575
|
return "error";
|
|
3285
3576
|
}
|
|
3286
3577
|
function createCheckCommand() {
|
|
3287
|
-
return new
|
|
3578
|
+
return new Command36("check").description("Run all harness checks for CI (validate, deps, docs, entropy, phase-gate, arch)").option("--skip <checks>", "Comma-separated checks to skip (e.g., entropy,docs)").option("--fail-on <severity>", "Fail on severity level: error (default) or warning", "error").action(async (opts, cmd) => {
|
|
3288
3579
|
const globalOpts = cmd.optsWithGlobals();
|
|
3289
3580
|
const mode = resolveOutputMode(globalOpts);
|
|
3290
3581
|
const skip = parseSkip(opts.skip);
|
|
@@ -3328,9 +3619,9 @@ function createCheckCommand() {
|
|
|
3328
3619
|
}
|
|
3329
3620
|
|
|
3330
3621
|
// src/commands/ci/init.ts
|
|
3331
|
-
import { Command as
|
|
3332
|
-
import * as
|
|
3333
|
-
import * as
|
|
3622
|
+
import { Command as Command37 } from "commander";
|
|
3623
|
+
import * as fs19 from "fs";
|
|
3624
|
+
import * as path31 from "path";
|
|
3334
3625
|
var ALL_CHECKS = [
|
|
3335
3626
|
"validate",
|
|
3336
3627
|
"deps",
|
|
@@ -3431,12 +3722,12 @@ function generateCIConfig(options) {
|
|
|
3431
3722
|
});
|
|
3432
3723
|
}
|
|
3433
3724
|
function detectPlatform() {
|
|
3434
|
-
if (
|
|
3435
|
-
if (
|
|
3725
|
+
if (fs19.existsSync(".github")) return "github";
|
|
3726
|
+
if (fs19.existsSync(".gitlab-ci.yml")) return "gitlab";
|
|
3436
3727
|
return null;
|
|
3437
3728
|
}
|
|
3438
3729
|
function createInitCommand2() {
|
|
3439
|
-
return new
|
|
3730
|
+
return new Command37("init").description("Generate CI configuration for harness checks").option("--platform <platform>", "CI platform: github, gitlab, or generic").option("--checks <list>", "Comma-separated list of checks to include").action(async (opts, cmd) => {
|
|
3440
3731
|
const globalOpts = cmd.optsWithGlobals();
|
|
3441
3732
|
const platform = opts.platform ?? detectPlatform() ?? "generic";
|
|
3442
3733
|
const checks = opts.checks ? opts.checks.split(",").map((s) => s.trim()) : void 0;
|
|
@@ -3448,12 +3739,12 @@ function createInitCommand2() {
|
|
|
3448
3739
|
process.exit(result.error.exitCode);
|
|
3449
3740
|
}
|
|
3450
3741
|
const { filename, content } = result.value;
|
|
3451
|
-
const targetPath =
|
|
3452
|
-
const dir =
|
|
3453
|
-
|
|
3454
|
-
|
|
3742
|
+
const targetPath = path31.resolve(filename);
|
|
3743
|
+
const dir = path31.dirname(targetPath);
|
|
3744
|
+
fs19.mkdirSync(dir, { recursive: true });
|
|
3745
|
+
fs19.writeFileSync(targetPath, content);
|
|
3455
3746
|
if (platform === "generic" && process.platform !== "win32") {
|
|
3456
|
-
|
|
3747
|
+
fs19.chmodSync(targetPath, "755");
|
|
3457
3748
|
}
|
|
3458
3749
|
if (globalOpts.json) {
|
|
3459
3750
|
console.log(JSON.stringify({ file: filename, platform }));
|
|
@@ -3466,18 +3757,275 @@ function createInitCommand2() {
|
|
|
3466
3757
|
|
|
3467
3758
|
// src/commands/ci/index.ts
|
|
3468
3759
|
function createCICommand() {
|
|
3469
|
-
const command = new
|
|
3760
|
+
const command = new Command38("ci").description("CI/CD integration commands");
|
|
3470
3761
|
command.addCommand(createCheckCommand());
|
|
3471
3762
|
command.addCommand(createInitCommand2());
|
|
3472
3763
|
return command;
|
|
3473
3764
|
}
|
|
3474
3765
|
|
|
3766
|
+
// src/commands/hooks/index.ts
|
|
3767
|
+
import { Command as Command42 } from "commander";
|
|
3768
|
+
|
|
3769
|
+
// src/commands/hooks/init.ts
|
|
3770
|
+
import { Command as Command39 } from "commander";
|
|
3771
|
+
import * as fs20 from "fs";
|
|
3772
|
+
import * as path32 from "path";
|
|
3773
|
+
import { fileURLToPath } from "url";
|
|
3774
|
+
|
|
3775
|
+
// src/hooks/profiles.ts
|
|
3776
|
+
var HOOK_SCRIPTS = [
|
|
3777
|
+
{ name: "block-no-verify", event: "PreToolUse", matcher: "Bash", minProfile: "minimal" },
|
|
3778
|
+
{ name: "protect-config", event: "PreToolUse", matcher: "Write|Edit", minProfile: "standard" },
|
|
3779
|
+
{ name: "quality-gate", event: "PostToolUse", matcher: "Edit|Write", minProfile: "standard" },
|
|
3780
|
+
{ name: "pre-compact-state", event: "PreCompact", matcher: "*", minProfile: "standard" },
|
|
3781
|
+
{ name: "cost-tracker", event: "Stop", matcher: "*", minProfile: "strict" }
|
|
3782
|
+
];
|
|
3783
|
+
var PROFILE_ORDER = ["minimal", "standard", "strict"];
|
|
3784
|
+
function hooksForProfile(profile) {
|
|
3785
|
+
const profileIndex = PROFILE_ORDER.indexOf(profile);
|
|
3786
|
+
return HOOK_SCRIPTS.filter((h) => PROFILE_ORDER.indexOf(h.minProfile) <= profileIndex).map(
|
|
3787
|
+
(h) => h.name
|
|
3788
|
+
);
|
|
3789
|
+
}
|
|
3790
|
+
var PROFILES = {
|
|
3791
|
+
minimal: hooksForProfile("minimal"),
|
|
3792
|
+
standard: hooksForProfile("standard"),
|
|
3793
|
+
strict: hooksForProfile("strict")
|
|
3794
|
+
};
|
|
3795
|
+
|
|
3796
|
+
// src/commands/hooks/init.ts
|
|
3797
|
+
var __filename = fileURLToPath(import.meta.url);
|
|
3798
|
+
var __dirname = path32.dirname(__filename);
|
|
3799
|
+
var VALID_PROFILES = ["minimal", "standard", "strict"];
|
|
3800
|
+
function resolveHookSourceDir() {
|
|
3801
|
+
const candidate = path32.resolve(__dirname, "..", "..", "hooks");
|
|
3802
|
+
if (fs20.existsSync(candidate)) {
|
|
3803
|
+
return candidate;
|
|
3804
|
+
}
|
|
3805
|
+
throw new Error(`Cannot locate hook scripts directory. Expected at: ${candidate}`);
|
|
3806
|
+
}
|
|
3807
|
+
function buildSettingsHooks(profile) {
|
|
3808
|
+
const activeHookNames = PROFILES[profile];
|
|
3809
|
+
const activeScripts = HOOK_SCRIPTS.filter((h) => activeHookNames.includes(h.name));
|
|
3810
|
+
const hooks = {};
|
|
3811
|
+
for (const script of activeScripts) {
|
|
3812
|
+
if (!hooks[script.event]) {
|
|
3813
|
+
hooks[script.event] = [];
|
|
3814
|
+
}
|
|
3815
|
+
hooks[script.event].push({
|
|
3816
|
+
matcher: script.matcher,
|
|
3817
|
+
hooks: [{ type: "command", command: `node .harness/hooks/${script.name}.js` }]
|
|
3818
|
+
});
|
|
3819
|
+
}
|
|
3820
|
+
return hooks;
|
|
3821
|
+
}
|
|
3822
|
+
function mergeSettings(existing, hooksConfig) {
|
|
3823
|
+
return {
|
|
3824
|
+
...existing,
|
|
3825
|
+
hooks: hooksConfig
|
|
3826
|
+
};
|
|
3827
|
+
}
|
|
3828
|
+
function initHooks(options) {
|
|
3829
|
+
const { profile, projectDir } = options;
|
|
3830
|
+
const hooksDestDir = path32.join(projectDir, ".harness", "hooks");
|
|
3831
|
+
fs20.mkdirSync(hooksDestDir, { recursive: true });
|
|
3832
|
+
if (fs20.existsSync(hooksDestDir)) {
|
|
3833
|
+
for (const entry of fs20.readdirSync(hooksDestDir)) {
|
|
3834
|
+
if (entry.endsWith(".js")) {
|
|
3835
|
+
fs20.unlinkSync(path32.join(hooksDestDir, entry));
|
|
3836
|
+
}
|
|
3837
|
+
}
|
|
3838
|
+
}
|
|
3839
|
+
const sourceDir = resolveHookSourceDir();
|
|
3840
|
+
const copiedScripts = [];
|
|
3841
|
+
const activeNames = PROFILES[profile];
|
|
3842
|
+
const activeScripts = HOOK_SCRIPTS.filter((h) => activeNames.includes(h.name));
|
|
3843
|
+
for (const script of activeScripts) {
|
|
3844
|
+
const srcFile = path32.join(sourceDir, `${script.name}.js`);
|
|
3845
|
+
const destFile = path32.join(hooksDestDir, `${script.name}.js`);
|
|
3846
|
+
if (fs20.existsSync(srcFile)) {
|
|
3847
|
+
fs20.copyFileSync(srcFile, destFile);
|
|
3848
|
+
copiedScripts.push(script.name);
|
|
3849
|
+
}
|
|
3850
|
+
}
|
|
3851
|
+
const profilePath = path32.join(hooksDestDir, "profile.json");
|
|
3852
|
+
fs20.writeFileSync(profilePath, JSON.stringify({ profile }, null, 2) + "\n");
|
|
3853
|
+
const claudeDir = path32.join(projectDir, ".claude");
|
|
3854
|
+
fs20.mkdirSync(claudeDir, { recursive: true });
|
|
3855
|
+
const settingsPath = path32.join(claudeDir, "settings.json");
|
|
3856
|
+
let existing = {};
|
|
3857
|
+
if (fs20.existsSync(settingsPath)) {
|
|
3858
|
+
try {
|
|
3859
|
+
existing = JSON.parse(fs20.readFileSync(settingsPath, "utf-8"));
|
|
3860
|
+
} catch (e) {
|
|
3861
|
+
throw new Error(
|
|
3862
|
+
`Malformed .claude/settings.json \u2014 fix the JSON syntax before running hooks init. Parse error: ${e instanceof Error ? e.message : String(e)}`,
|
|
3863
|
+
{ cause: e }
|
|
3864
|
+
);
|
|
3865
|
+
}
|
|
3866
|
+
}
|
|
3867
|
+
const hooksConfig = buildSettingsHooks(profile);
|
|
3868
|
+
const merged = mergeSettings(existing, hooksConfig);
|
|
3869
|
+
fs20.writeFileSync(settingsPath, JSON.stringify(merged, null, 2) + "\n");
|
|
3870
|
+
return { copiedScripts, settingsPath, profilePath };
|
|
3871
|
+
}
|
|
3872
|
+
function createInitCommand3() {
|
|
3873
|
+
return new Command39("init").description("Install Claude Code hook configurations into the current project").option("--profile <profile>", "Hook profile: minimal, standard, or strict", "standard").action(async (opts, cmd) => {
|
|
3874
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
3875
|
+
const profile = opts.profile;
|
|
3876
|
+
if (!VALID_PROFILES.includes(profile)) {
|
|
3877
|
+
logger.error(`Invalid profile: ${profile}. Must be one of: ${VALID_PROFILES.join(", ")}`);
|
|
3878
|
+
process.exit(2);
|
|
3879
|
+
}
|
|
3880
|
+
const projectDir = process.cwd();
|
|
3881
|
+
try {
|
|
3882
|
+
const result = initHooks({ profile, projectDir });
|
|
3883
|
+
if (globalOpts.json) {
|
|
3884
|
+
console.log(
|
|
3885
|
+
JSON.stringify({
|
|
3886
|
+
profile,
|
|
3887
|
+
copiedScripts: result.copiedScripts,
|
|
3888
|
+
settingsPath: result.settingsPath,
|
|
3889
|
+
profilePath: result.profilePath
|
|
3890
|
+
})
|
|
3891
|
+
);
|
|
3892
|
+
} else {
|
|
3893
|
+
logger.success(
|
|
3894
|
+
`Installed ${result.copiedScripts.length} hook scripts to .harness/hooks/`
|
|
3895
|
+
);
|
|
3896
|
+
logger.info(`Profile: ${profile}`);
|
|
3897
|
+
logger.info(`Settings: ${path32.relative(projectDir, result.settingsPath)}`);
|
|
3898
|
+
logger.dim("Run 'harness hooks list' to see installed hooks");
|
|
3899
|
+
}
|
|
3900
|
+
} catch (err) {
|
|
3901
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
3902
|
+
logger.error(`Failed to initialize hooks: ${message}`);
|
|
3903
|
+
process.exit(2);
|
|
3904
|
+
}
|
|
3905
|
+
});
|
|
3906
|
+
}
|
|
3907
|
+
|
|
3908
|
+
// src/commands/hooks/list.ts
|
|
3909
|
+
import { Command as Command40 } from "commander";
|
|
3910
|
+
import * as fs21 from "fs";
|
|
3911
|
+
import * as path33 from "path";
|
|
3912
|
+
function listHooks(projectDir) {
|
|
3913
|
+
const hooksDir = path33.join(projectDir, ".harness", "hooks");
|
|
3914
|
+
const profilePath = path33.join(hooksDir, "profile.json");
|
|
3915
|
+
if (!fs21.existsSync(profilePath)) {
|
|
3916
|
+
return { installed: false, profile: null, hooks: [] };
|
|
3917
|
+
}
|
|
3918
|
+
let profile = "standard";
|
|
3919
|
+
let warning;
|
|
3920
|
+
try {
|
|
3921
|
+
const data = JSON.parse(fs21.readFileSync(profilePath, "utf-8"));
|
|
3922
|
+
if (data.profile && ["minimal", "standard", "strict"].includes(data.profile)) {
|
|
3923
|
+
profile = data.profile;
|
|
3924
|
+
}
|
|
3925
|
+
} catch {
|
|
3926
|
+
warning = "Malformed profile.json \u2014 defaulting to standard profile";
|
|
3927
|
+
}
|
|
3928
|
+
const activeNames = PROFILES[profile];
|
|
3929
|
+
const hooks = HOOK_SCRIPTS.filter((h) => activeNames.includes(h.name)).map((h) => ({
|
|
3930
|
+
name: h.name,
|
|
3931
|
+
event: h.event,
|
|
3932
|
+
matcher: h.matcher,
|
|
3933
|
+
scriptPath: path33.join(".harness", "hooks", `${h.name}.js`)
|
|
3934
|
+
}));
|
|
3935
|
+
const result = { installed: true, profile, hooks };
|
|
3936
|
+
if (warning) {
|
|
3937
|
+
result.warning = warning;
|
|
3938
|
+
}
|
|
3939
|
+
return result;
|
|
3940
|
+
}
|
|
3941
|
+
function createListCommand3() {
|
|
3942
|
+
return new Command40("list").description("Show installed hooks and active profile").action(async (_opts, cmd) => {
|
|
3943
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
3944
|
+
const projectDir = process.cwd();
|
|
3945
|
+
const result = listHooks(projectDir);
|
|
3946
|
+
if (globalOpts.json) {
|
|
3947
|
+
console.log(JSON.stringify(result, null, 2));
|
|
3948
|
+
return;
|
|
3949
|
+
}
|
|
3950
|
+
if (!result.installed) {
|
|
3951
|
+
logger.info("No harness hooks installed. Run 'harness hooks init' to set up hooks.");
|
|
3952
|
+
return;
|
|
3953
|
+
}
|
|
3954
|
+
logger.info(`Profile: ${result.profile}`);
|
|
3955
|
+
logger.info(`Hooks (${result.hooks.length}):`);
|
|
3956
|
+
for (const hook of result.hooks) {
|
|
3957
|
+
console.log(` ${hook.name} ${hook.event}:${hook.matcher} ${hook.scriptPath}`);
|
|
3958
|
+
}
|
|
3959
|
+
});
|
|
3960
|
+
}
|
|
3961
|
+
|
|
3962
|
+
// src/commands/hooks/remove.ts
|
|
3963
|
+
import { Command as Command41 } from "commander";
|
|
3964
|
+
import * as fs22 from "fs";
|
|
3965
|
+
import * as path34 from "path";
|
|
3966
|
+
function removeHooks(projectDir) {
|
|
3967
|
+
const hooksDir = path34.join(projectDir, ".harness", "hooks");
|
|
3968
|
+
const settingsPath = path34.join(projectDir, ".claude", "settings.json");
|
|
3969
|
+
let removed = false;
|
|
3970
|
+
let settingsCleaned = false;
|
|
3971
|
+
if (fs22.existsSync(hooksDir)) {
|
|
3972
|
+
fs22.rmSync(hooksDir, { recursive: true, force: true });
|
|
3973
|
+
removed = true;
|
|
3974
|
+
}
|
|
3975
|
+
if (fs22.existsSync(settingsPath)) {
|
|
3976
|
+
try {
|
|
3977
|
+
const settings = JSON.parse(fs22.readFileSync(settingsPath, "utf-8"));
|
|
3978
|
+
if (settings.hooks !== void 0) {
|
|
3979
|
+
delete settings.hooks;
|
|
3980
|
+
settingsCleaned = true;
|
|
3981
|
+
if (Object.keys(settings).length === 0) {
|
|
3982
|
+
fs22.unlinkSync(settingsPath);
|
|
3983
|
+
} else {
|
|
3984
|
+
fs22.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
3985
|
+
}
|
|
3986
|
+
}
|
|
3987
|
+
} catch {
|
|
3988
|
+
}
|
|
3989
|
+
}
|
|
3990
|
+
return { removed, hooksDir, settingsCleaned };
|
|
3991
|
+
}
|
|
3992
|
+
function createRemoveCommand() {
|
|
3993
|
+
return new Command41("remove").description("Remove harness-managed hooks from the current project").action(async (_opts, cmd) => {
|
|
3994
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
3995
|
+
const projectDir = process.cwd();
|
|
3996
|
+
const result = removeHooks(projectDir);
|
|
3997
|
+
if (globalOpts.json) {
|
|
3998
|
+
console.log(JSON.stringify(result));
|
|
3999
|
+
return;
|
|
4000
|
+
}
|
|
4001
|
+
if (!result.removed && !result.settingsCleaned) {
|
|
4002
|
+
logger.info("No harness hooks found to remove.");
|
|
4003
|
+
return;
|
|
4004
|
+
}
|
|
4005
|
+
if (result.removed) {
|
|
4006
|
+
logger.success("Removed .harness/hooks/ directory");
|
|
4007
|
+
}
|
|
4008
|
+
if (result.settingsCleaned) {
|
|
4009
|
+
logger.success("Cleaned hook entries from .claude/settings.json");
|
|
4010
|
+
}
|
|
4011
|
+
});
|
|
4012
|
+
}
|
|
4013
|
+
|
|
4014
|
+
// src/commands/hooks/index.ts
|
|
4015
|
+
function createHooksCommand() {
|
|
4016
|
+
const command = new Command42("hooks").description("Manage Claude Code hook configurations");
|
|
4017
|
+
command.addCommand(createInitCommand3());
|
|
4018
|
+
command.addCommand(createListCommand3());
|
|
4019
|
+
command.addCommand(createRemoveCommand());
|
|
4020
|
+
return command;
|
|
4021
|
+
}
|
|
4022
|
+
|
|
3475
4023
|
// src/commands/update.ts
|
|
3476
|
-
import { Command as
|
|
4024
|
+
import { Command as Command43 } from "commander";
|
|
3477
4025
|
import { execFileSync as execFileSync4 } from "child_process";
|
|
3478
4026
|
import { realpathSync } from "fs";
|
|
3479
4027
|
import readline2 from "readline";
|
|
3480
|
-
import
|
|
4028
|
+
import chalk5 from "chalk";
|
|
3481
4029
|
function detectPackageManager() {
|
|
3482
4030
|
try {
|
|
3483
4031
|
const argv1 = process.argv[1];
|
|
@@ -3533,10 +4081,10 @@ function prompt(question) {
|
|
|
3533
4081
|
input: process.stdin,
|
|
3534
4082
|
output: process.stdout
|
|
3535
4083
|
});
|
|
3536
|
-
return new Promise((
|
|
4084
|
+
return new Promise((resolve30) => {
|
|
3537
4085
|
rl.question(question, (answer) => {
|
|
3538
4086
|
rl.close();
|
|
3539
|
-
|
|
4087
|
+
resolve30(answer.trim().toLowerCase());
|
|
3540
4088
|
});
|
|
3541
4089
|
});
|
|
3542
4090
|
}
|
|
@@ -3552,11 +4100,11 @@ async function offerRegeneration() {
|
|
|
3552
4100
|
});
|
|
3553
4101
|
} catch {
|
|
3554
4102
|
logger.warn("Generation failed. Run manually:");
|
|
3555
|
-
console.log(` ${
|
|
4103
|
+
console.log(` ${chalk5.cyan(`harness generate${isGlobal ? " --global" : ""}`)}`);
|
|
3556
4104
|
}
|
|
3557
4105
|
}
|
|
3558
4106
|
function createUpdateCommand() {
|
|
3559
|
-
return new
|
|
4107
|
+
return new Command43("update").description("Update all @harness-engineering packages to the latest version").option("--version <semver>", "Pin @harness-engineering/cli to a specific version").action(async (opts, cmd) => {
|
|
3560
4108
|
const globalOpts = cmd.optsWithGlobals();
|
|
3561
4109
|
const pm = detectPackageManager();
|
|
3562
4110
|
if (globalOpts.verbose) {
|
|
@@ -3578,8 +4126,8 @@ function createUpdateCommand() {
|
|
|
3578
4126
|
}
|
|
3579
4127
|
if (currentVersion) {
|
|
3580
4128
|
console.log("");
|
|
3581
|
-
logger.info(`Current CLI version: ${
|
|
3582
|
-
logger.info(`Latest CLI version: ${
|
|
4129
|
+
logger.info(`Current CLI version: ${chalk5.dim(`v${currentVersion}`)}`);
|
|
4130
|
+
logger.info(`Latest CLI version: ${chalk5.green(`v${latestCliVersion}`)}`);
|
|
3583
4131
|
console.log("");
|
|
3584
4132
|
}
|
|
3585
4133
|
}
|
|
@@ -3605,7 +4153,7 @@ function createUpdateCommand() {
|
|
|
3605
4153
|
} catch {
|
|
3606
4154
|
console.log("");
|
|
3607
4155
|
logger.error("Update failed. You can try manually:");
|
|
3608
|
-
console.log(` ${
|
|
4156
|
+
console.log(` ${chalk5.cyan(installCmd)}`);
|
|
3609
4157
|
process.exit(ExitCode.ERROR);
|
|
3610
4158
|
}
|
|
3611
4159
|
await offerRegeneration();
|
|
@@ -3614,9 +4162,9 @@ function createUpdateCommand() {
|
|
|
3614
4162
|
}
|
|
3615
4163
|
|
|
3616
4164
|
// src/commands/generate.ts
|
|
3617
|
-
import { Command as
|
|
4165
|
+
import { Command as Command44 } from "commander";
|
|
3618
4166
|
function createGenerateCommand3() {
|
|
3619
|
-
return new
|
|
4167
|
+
return new Command44("generate").description("Generate all platform integrations (slash commands + agent definitions)").option("--platforms <list>", "Target platforms (comma-separated)", "claude-code,gemini-cli").option("--global", "Write to global directories", false).option("--include-global", "Include built-in global skills", false).option("--output <dir>", "Custom output directory").option("--dry-run", "Show what would change without writing", false).option("--yes", "Skip deletion confirmation prompts", false).action(async (opts, cmd) => {
|
|
3620
4168
|
const globalOpts = cmd.optsWithGlobals();
|
|
3621
4169
|
const platforms = opts.platforms.split(",").map((p) => p.trim());
|
|
3622
4170
|
for (const p of platforms) {
|
|
@@ -3675,8 +4223,8 @@ function createGenerateCommand3() {
|
|
|
3675
4223
|
}
|
|
3676
4224
|
|
|
3677
4225
|
// src/commands/graph/scan.ts
|
|
3678
|
-
import { Command as
|
|
3679
|
-
import * as
|
|
4226
|
+
import { Command as Command45 } from "commander";
|
|
4227
|
+
import * as path35 from "path";
|
|
3680
4228
|
async function runScan(projectPath) {
|
|
3681
4229
|
const { GraphStore, CodeIngestor, TopologicalLinker, KnowledgeIngestor, GitIngestor } = await import("./dist-B26DFXMP.js");
|
|
3682
4230
|
const store = new GraphStore();
|
|
@@ -3689,13 +4237,13 @@ async function runScan(projectPath) {
|
|
|
3689
4237
|
await new GitIngestor(store).ingest(projectPath);
|
|
3690
4238
|
} catch {
|
|
3691
4239
|
}
|
|
3692
|
-
const graphDir =
|
|
4240
|
+
const graphDir = path35.join(projectPath, ".harness", "graph");
|
|
3693
4241
|
await store.save(graphDir);
|
|
3694
4242
|
return { nodeCount: store.nodeCount, edgeCount: store.edgeCount, durationMs: Date.now() - start };
|
|
3695
4243
|
}
|
|
3696
4244
|
function createScanCommand() {
|
|
3697
|
-
return new
|
|
3698
|
-
const projectPath =
|
|
4245
|
+
return new Command45("scan").description("Scan project and build knowledge graph").argument("[path]", "Project root path", ".").action(async (inputPath, _opts, cmd) => {
|
|
4246
|
+
const projectPath = path35.resolve(inputPath);
|
|
3699
4247
|
const globalOpts = cmd.optsWithGlobals();
|
|
3700
4248
|
try {
|
|
3701
4249
|
const result = await runScan(projectPath);
|
|
@@ -3714,13 +4262,13 @@ function createScanCommand() {
|
|
|
3714
4262
|
}
|
|
3715
4263
|
|
|
3716
4264
|
// src/commands/graph/ingest.ts
|
|
3717
|
-
import { Command as
|
|
3718
|
-
import * as
|
|
4265
|
+
import { Command as Command46 } from "commander";
|
|
4266
|
+
import * as path36 from "path";
|
|
3719
4267
|
async function loadConnectorConfig(projectPath, source) {
|
|
3720
4268
|
try {
|
|
3721
|
-
const
|
|
3722
|
-
const configPath =
|
|
3723
|
-
const config = JSON.parse(await
|
|
4269
|
+
const fs29 = await import("fs/promises");
|
|
4270
|
+
const configPath = path36.join(projectPath, "harness.config.json");
|
|
4271
|
+
const config = JSON.parse(await fs29.readFile(configPath, "utf-8"));
|
|
3724
4272
|
const connector = config.graph?.connectors?.find(
|
|
3725
4273
|
(c) => c.source === source
|
|
3726
4274
|
);
|
|
@@ -3760,7 +4308,7 @@ async function runIngest(projectPath, source, opts) {
|
|
|
3760
4308
|
JiraConnector,
|
|
3761
4309
|
SlackConnector
|
|
3762
4310
|
} = await import("./dist-B26DFXMP.js");
|
|
3763
|
-
const graphDir =
|
|
4311
|
+
const graphDir = path36.join(projectPath, ".harness", "graph");
|
|
3764
4312
|
const store = new GraphStore();
|
|
3765
4313
|
await store.load(graphDir);
|
|
3766
4314
|
if (opts?.all) {
|
|
@@ -3821,13 +4369,13 @@ async function runIngest(projectPath, source, opts) {
|
|
|
3821
4369
|
return result;
|
|
3822
4370
|
}
|
|
3823
4371
|
function createIngestCommand() {
|
|
3824
|
-
return new
|
|
4372
|
+
return new Command46("ingest").description("Ingest data into the knowledge graph").option("--source <name>", "Source to ingest (code, knowledge, git, jira, slack)").option("--all", "Run all sources (code, knowledge, git, and configured connectors)").option("--full", "Force full re-ingestion").action(async (opts, cmd) => {
|
|
3825
4373
|
if (!opts.source && !opts.all) {
|
|
3826
4374
|
console.error("Error: --source or --all is required");
|
|
3827
4375
|
process.exit(1);
|
|
3828
4376
|
}
|
|
3829
4377
|
const globalOpts = cmd.optsWithGlobals();
|
|
3830
|
-
const projectPath =
|
|
4378
|
+
const projectPath = path36.resolve(globalOpts.config ? path36.dirname(globalOpts.config) : ".");
|
|
3831
4379
|
try {
|
|
3832
4380
|
const result = await runIngest(projectPath, opts.source ?? "", {
|
|
3833
4381
|
full: opts.full,
|
|
@@ -3849,12 +4397,12 @@ function createIngestCommand() {
|
|
|
3849
4397
|
}
|
|
3850
4398
|
|
|
3851
4399
|
// src/commands/graph/query.ts
|
|
3852
|
-
import { Command as
|
|
3853
|
-
import * as
|
|
4400
|
+
import { Command as Command47 } from "commander";
|
|
4401
|
+
import * as path37 from "path";
|
|
3854
4402
|
async function runQuery(projectPath, rootNodeId, opts) {
|
|
3855
4403
|
const { GraphStore, ContextQL } = await import("./dist-B26DFXMP.js");
|
|
3856
4404
|
const store = new GraphStore();
|
|
3857
|
-
const graphDir =
|
|
4405
|
+
const graphDir = path37.join(projectPath, ".harness", "graph");
|
|
3858
4406
|
const loaded = await store.load(graphDir);
|
|
3859
4407
|
if (!loaded) throw new Error("No graph found. Run `harness scan` first.");
|
|
3860
4408
|
const params = {
|
|
@@ -3868,9 +4416,9 @@ async function runQuery(projectPath, rootNodeId, opts) {
|
|
|
3868
4416
|
return cql.execute(params);
|
|
3869
4417
|
}
|
|
3870
4418
|
function createQueryCommand() {
|
|
3871
|
-
return new
|
|
4419
|
+
return new Command47("query").description("Query the knowledge graph").argument("<rootNodeId>", "Starting node ID").option("--depth <n>", "Max traversal depth", "3").option("--types <types>", "Comma-separated node types to include").option("--edges <edges>", "Comma-separated edge types to include").option("--bidirectional", "Traverse both directions").action(async (rootNodeId, opts, cmd) => {
|
|
3872
4420
|
const globalOpts = cmd.optsWithGlobals();
|
|
3873
|
-
const projectPath =
|
|
4421
|
+
const projectPath = path37.resolve(globalOpts.config ? path37.dirname(globalOpts.config) : ".");
|
|
3874
4422
|
try {
|
|
3875
4423
|
const result = await runQuery(projectPath, rootNodeId, {
|
|
3876
4424
|
depth: parseInt(opts.depth),
|
|
@@ -3896,21 +4444,21 @@ function createQueryCommand() {
|
|
|
3896
4444
|
}
|
|
3897
4445
|
|
|
3898
4446
|
// src/commands/graph/index.ts
|
|
3899
|
-
import { Command as
|
|
4447
|
+
import { Command as Command48 } from "commander";
|
|
3900
4448
|
|
|
3901
4449
|
// src/commands/graph/status.ts
|
|
3902
|
-
import * as
|
|
4450
|
+
import * as path38 from "path";
|
|
3903
4451
|
async function runGraphStatus(projectPath) {
|
|
3904
4452
|
const { GraphStore } = await import("./dist-B26DFXMP.js");
|
|
3905
|
-
const graphDir =
|
|
4453
|
+
const graphDir = path38.join(projectPath, ".harness", "graph");
|
|
3906
4454
|
const store = new GraphStore();
|
|
3907
4455
|
const loaded = await store.load(graphDir);
|
|
3908
4456
|
if (!loaded) return { status: "no_graph", message: "No graph found. Run `harness scan` first." };
|
|
3909
|
-
const
|
|
3910
|
-
const metaPath =
|
|
4457
|
+
const fs29 = await import("fs/promises");
|
|
4458
|
+
const metaPath = path38.join(graphDir, "metadata.json");
|
|
3911
4459
|
let lastScan = "unknown";
|
|
3912
4460
|
try {
|
|
3913
|
-
const meta = JSON.parse(await
|
|
4461
|
+
const meta = JSON.parse(await fs29.readFile(metaPath, "utf-8"));
|
|
3914
4462
|
lastScan = meta.lastScanTimestamp;
|
|
3915
4463
|
} catch {
|
|
3916
4464
|
}
|
|
@@ -3921,8 +4469,8 @@ async function runGraphStatus(projectPath) {
|
|
|
3921
4469
|
}
|
|
3922
4470
|
let connectorSyncStatus = {};
|
|
3923
4471
|
try {
|
|
3924
|
-
const syncMetaPath =
|
|
3925
|
-
const syncMeta = JSON.parse(await
|
|
4472
|
+
const syncMetaPath = path38.join(graphDir, "sync-metadata.json");
|
|
4473
|
+
const syncMeta = JSON.parse(await fs29.readFile(syncMetaPath, "utf-8"));
|
|
3926
4474
|
for (const [name, data] of Object.entries(syncMeta.connectors ?? {})) {
|
|
3927
4475
|
connectorSyncStatus[name] = data.lastSyncTimestamp;
|
|
3928
4476
|
}
|
|
@@ -3939,10 +4487,10 @@ async function runGraphStatus(projectPath) {
|
|
|
3939
4487
|
}
|
|
3940
4488
|
|
|
3941
4489
|
// src/commands/graph/export.ts
|
|
3942
|
-
import * as
|
|
4490
|
+
import * as path39 from "path";
|
|
3943
4491
|
async function runGraphExport(projectPath, format) {
|
|
3944
4492
|
const { GraphStore } = await import("./dist-B26DFXMP.js");
|
|
3945
|
-
const graphDir =
|
|
4493
|
+
const graphDir = path39.join(projectPath, ".harness", "graph");
|
|
3946
4494
|
const store = new GraphStore();
|
|
3947
4495
|
const loaded = await store.load(graphDir);
|
|
3948
4496
|
if (!loaded) throw new Error("No graph found. Run `harness scan` first.");
|
|
@@ -3971,13 +4519,13 @@ async function runGraphExport(projectPath, format) {
|
|
|
3971
4519
|
}
|
|
3972
4520
|
|
|
3973
4521
|
// src/commands/graph/index.ts
|
|
3974
|
-
import * as
|
|
4522
|
+
import * as path40 from "path";
|
|
3975
4523
|
function createGraphCommand() {
|
|
3976
|
-
const graph = new
|
|
4524
|
+
const graph = new Command48("graph").description("Knowledge graph management");
|
|
3977
4525
|
graph.command("status").description("Show graph statistics").action(async (_opts, cmd) => {
|
|
3978
4526
|
try {
|
|
3979
4527
|
const globalOpts = cmd.optsWithGlobals();
|
|
3980
|
-
const projectPath =
|
|
4528
|
+
const projectPath = path40.resolve(globalOpts.config ? path40.dirname(globalOpts.config) : ".");
|
|
3981
4529
|
const result = await runGraphStatus(projectPath);
|
|
3982
4530
|
if (globalOpts.json) {
|
|
3983
4531
|
console.log(JSON.stringify(result, null, 2));
|
|
@@ -4004,7 +4552,7 @@ function createGraphCommand() {
|
|
|
4004
4552
|
});
|
|
4005
4553
|
graph.command("export").description("Export graph").requiredOption("--format <format>", "Output format (json, mermaid)").action(async (opts, cmd) => {
|
|
4006
4554
|
const globalOpts = cmd.optsWithGlobals();
|
|
4007
|
-
const projectPath =
|
|
4555
|
+
const projectPath = path40.resolve(globalOpts.config ? path40.dirname(globalOpts.config) : ".");
|
|
4008
4556
|
try {
|
|
4009
4557
|
const output = await runGraphExport(projectPath, opts.format);
|
|
4010
4558
|
console.log(output);
|
|
@@ -4017,19 +4565,19 @@ function createGraphCommand() {
|
|
|
4017
4565
|
}
|
|
4018
4566
|
|
|
4019
4567
|
// src/commands/mcp.ts
|
|
4020
|
-
import { Command as
|
|
4568
|
+
import { Command as Command49 } from "commander";
|
|
4021
4569
|
function createMcpCommand() {
|
|
4022
|
-
return new
|
|
4023
|
-
const { startServer: startServer2 } = await import("./mcp-
|
|
4570
|
+
return new Command49("mcp").description("Start the MCP (Model Context Protocol) server on stdio").action(async () => {
|
|
4571
|
+
const { startServer: startServer2 } = await import("./mcp-VU5FMO52.js");
|
|
4024
4572
|
await startServer2();
|
|
4025
4573
|
});
|
|
4026
4574
|
}
|
|
4027
4575
|
|
|
4028
4576
|
// src/commands/impact-preview.ts
|
|
4029
|
-
import { Command as
|
|
4577
|
+
import { Command as Command50 } from "commander";
|
|
4030
4578
|
import { execSync as execSync3 } from "child_process";
|
|
4031
|
-
import * as
|
|
4032
|
-
import * as
|
|
4579
|
+
import * as path41 from "path";
|
|
4580
|
+
import * as fs23 from "fs";
|
|
4033
4581
|
function getStagedFiles(cwd) {
|
|
4034
4582
|
try {
|
|
4035
4583
|
const output = execSync3("git diff --cached --name-only", {
|
|
@@ -4043,7 +4591,7 @@ function getStagedFiles(cwd) {
|
|
|
4043
4591
|
}
|
|
4044
4592
|
function graphExists(projectPath) {
|
|
4045
4593
|
try {
|
|
4046
|
-
return
|
|
4594
|
+
return fs23.existsSync(path41.join(projectPath, ".harness", "graph", "graph.json"));
|
|
4047
4595
|
} catch {
|
|
4048
4596
|
return false;
|
|
4049
4597
|
}
|
|
@@ -4052,7 +4600,7 @@ function extractNodeName(id) {
|
|
|
4052
4600
|
const parts = id.split(":");
|
|
4053
4601
|
if (parts.length > 1) {
|
|
4054
4602
|
const fullPath = parts.slice(1).join(":");
|
|
4055
|
-
return
|
|
4603
|
+
return path41.basename(fullPath);
|
|
4056
4604
|
}
|
|
4057
4605
|
return id;
|
|
4058
4606
|
}
|
|
@@ -4175,7 +4723,7 @@ function formatPerFile(perFileResults) {
|
|
|
4175
4723
|
return lines.join("\n");
|
|
4176
4724
|
}
|
|
4177
4725
|
async function runImpactPreview(options) {
|
|
4178
|
-
const projectPath =
|
|
4726
|
+
const projectPath = path41.resolve(options.path ?? process.cwd());
|
|
4179
4727
|
const stagedFiles = getStagedFiles(projectPath);
|
|
4180
4728
|
if (stagedFiles.length === 0) {
|
|
4181
4729
|
return "Impact Preview: no staged changes";
|
|
@@ -4222,7 +4770,7 @@ async function runImpactPreview(options) {
|
|
|
4222
4770
|
return formatCompact(stagedFiles.length, merged, aggregateCounts);
|
|
4223
4771
|
}
|
|
4224
4772
|
function createImpactPreviewCommand() {
|
|
4225
|
-
const command = new
|
|
4773
|
+
const command = new Command50("impact-preview").description("Show blast radius of staged changes using the knowledge graph").option("--detailed", "Show all affected files instead of top items").option("--per-file", "Show impact per staged file instead of aggregate").option("--path <dir>", "Project root (default: cwd)").action(async (opts) => {
|
|
4226
4774
|
const output = await runImpactPreview({
|
|
4227
4775
|
detailed: opts.detailed,
|
|
4228
4776
|
perFile: opts.perFile,
|
|
@@ -4235,7 +4783,7 @@ function createImpactPreviewCommand() {
|
|
|
4235
4783
|
}
|
|
4236
4784
|
|
|
4237
4785
|
// src/commands/check-arch.ts
|
|
4238
|
-
import { Command as
|
|
4786
|
+
import { Command as Command51 } from "commander";
|
|
4239
4787
|
import { execSync as execSync4 } from "child_process";
|
|
4240
4788
|
function getCommitHash2(cwd) {
|
|
4241
4789
|
try {
|
|
@@ -4330,7 +4878,7 @@ async function runCheckArch(options) {
|
|
|
4330
4878
|
});
|
|
4331
4879
|
}
|
|
4332
4880
|
function createCheckArchCommand() {
|
|
4333
|
-
const command = new
|
|
4881
|
+
const command = new Command51("check-arch").description("Check architecture assertions against baseline and thresholds").option("--update-baseline", "Capture current state as new baseline").option("--module <path>", "Check a single module").action(async (opts, cmd) => {
|
|
4334
4882
|
const globalOpts = cmd.optsWithGlobals();
|
|
4335
4883
|
const mode = globalOpts.json ? OutputMode.JSON : globalOpts.quiet ? OutputMode.QUIET : globalOpts.verbose ? OutputMode.VERBOSE : OutputMode.TEXT;
|
|
4336
4884
|
const formatter = new OutputFormatter(mode);
|
|
@@ -4396,20 +4944,20 @@ function createCheckArchCommand() {
|
|
|
4396
4944
|
}
|
|
4397
4945
|
|
|
4398
4946
|
// src/commands/blueprint.ts
|
|
4399
|
-
import { Command as
|
|
4400
|
-
import * as
|
|
4947
|
+
import { Command as Command52 } from "commander";
|
|
4948
|
+
import * as path42 from "path";
|
|
4401
4949
|
function createBlueprintCommand() {
|
|
4402
|
-
return new
|
|
4950
|
+
return new Command52("blueprint").description("Generate a self-contained, interactive blueprint of the codebase").argument("[path]", "Path to the project root", ".").option("-o, --output <dir>", "Output directory", "docs/blueprint").action(async (projectPath, options) => {
|
|
4403
4951
|
try {
|
|
4404
|
-
const rootDir =
|
|
4405
|
-
const outputDir =
|
|
4952
|
+
const rootDir = path42.resolve(projectPath);
|
|
4953
|
+
const outputDir = path42.resolve(options.output);
|
|
4406
4954
|
logger.info(`Scanning project at ${rootDir}...`);
|
|
4407
4955
|
const scanner = new ProjectScanner(rootDir);
|
|
4408
4956
|
const data = await scanner.scan();
|
|
4409
4957
|
logger.info(`Generating blueprint to ${outputDir}...`);
|
|
4410
4958
|
const generator = new BlueprintGenerator();
|
|
4411
4959
|
await generator.generate(data, { outputDir });
|
|
4412
|
-
logger.success(`Blueprint generated successfully at ${
|
|
4960
|
+
logger.success(`Blueprint generated successfully at ${path42.join(outputDir, "index.html")}`);
|
|
4413
4961
|
} catch (error) {
|
|
4414
4962
|
logger.error(
|
|
4415
4963
|
`Failed to generate blueprint: ${error instanceof Error ? error.message : String(error)}`
|
|
@@ -4420,16 +4968,16 @@ function createBlueprintCommand() {
|
|
|
4420
4968
|
}
|
|
4421
4969
|
|
|
4422
4970
|
// src/commands/share.ts
|
|
4423
|
-
import { Command as
|
|
4424
|
-
import * as
|
|
4425
|
-
import * as
|
|
4971
|
+
import { Command as Command53 } from "commander";
|
|
4972
|
+
import * as fs24 from "fs";
|
|
4973
|
+
import * as path43 from "path";
|
|
4426
4974
|
import { parse as parseYaml } from "yaml";
|
|
4427
4975
|
var MANIFEST_FILENAME = "constraints.yaml";
|
|
4428
4976
|
function createShareCommand() {
|
|
4429
|
-
return new
|
|
4430
|
-
const rootDir =
|
|
4431
|
-
const manifestPath =
|
|
4432
|
-
if (!
|
|
4977
|
+
return new Command53("share").description("Extract and publish a constraints bundle from constraints.yaml").argument("[path]", "Path to the project root", ".").option("-o, --output <dir>", "Output directory for the bundle", ".").action(async (projectPath, options) => {
|
|
4978
|
+
const rootDir = path43.resolve(projectPath);
|
|
4979
|
+
const manifestPath = path43.join(rootDir, MANIFEST_FILENAME);
|
|
4980
|
+
if (!fs24.existsSync(manifestPath)) {
|
|
4433
4981
|
logger.error(
|
|
4434
4982
|
`No ${MANIFEST_FILENAME} found at ${manifestPath}.
|
|
4435
4983
|
Create a constraints.yaml in your project root to define what to share.`
|
|
@@ -4438,7 +4986,7 @@ Create a constraints.yaml in your project root to define what to share.`
|
|
|
4438
4986
|
}
|
|
4439
4987
|
let parsed;
|
|
4440
4988
|
try {
|
|
4441
|
-
const raw =
|
|
4989
|
+
const raw = fs24.readFileSync(manifestPath, "utf-8");
|
|
4442
4990
|
parsed = parseYaml(raw);
|
|
4443
4991
|
} catch (err) {
|
|
4444
4992
|
logger.error(
|
|
@@ -4452,7 +5000,7 @@ Create a constraints.yaml in your project root to define what to share.`
|
|
|
4452
5000
|
process.exit(1);
|
|
4453
5001
|
}
|
|
4454
5002
|
const manifest = manifestResult.value;
|
|
4455
|
-
const configResult = resolveConfig(
|
|
5003
|
+
const configResult = resolveConfig(path43.join(rootDir, "harness.config.json"));
|
|
4456
5004
|
if (!configResult.ok) {
|
|
4457
5005
|
logger.error(configResult.error.message);
|
|
4458
5006
|
process.exit(1);
|
|
@@ -4470,8 +5018,8 @@ Create a constraints.yaml in your project root to define what to share.`
|
|
|
4470
5018
|
);
|
|
4471
5019
|
process.exit(1);
|
|
4472
5020
|
}
|
|
4473
|
-
const outputDir =
|
|
4474
|
-
const outputPath =
|
|
5021
|
+
const outputDir = path43.resolve(options.output);
|
|
5022
|
+
const outputPath = path43.join(outputDir, `${manifest.name}.harness-constraints.json`);
|
|
4475
5023
|
const writeResult = await writeConfig(outputPath, bundle);
|
|
4476
5024
|
if (!writeResult.ok) {
|
|
4477
5025
|
logger.error(`Failed to write bundle: ${writeResult.error.message}`);
|
|
@@ -4482,25 +5030,25 @@ Create a constraints.yaml in your project root to define what to share.`
|
|
|
4482
5030
|
}
|
|
4483
5031
|
|
|
4484
5032
|
// src/commands/install.ts
|
|
4485
|
-
import * as
|
|
4486
|
-
import * as
|
|
4487
|
-
import { Command as
|
|
5033
|
+
import * as fs26 from "fs";
|
|
5034
|
+
import * as path45 from "path";
|
|
5035
|
+
import { Command as Command54 } from "commander";
|
|
4488
5036
|
import { parse as yamlParse } from "yaml";
|
|
4489
5037
|
|
|
4490
5038
|
// src/registry/tarball.ts
|
|
4491
|
-
import * as
|
|
4492
|
-
import * as
|
|
4493
|
-
import * as
|
|
5039
|
+
import * as fs25 from "fs";
|
|
5040
|
+
import * as path44 from "path";
|
|
5041
|
+
import * as os6 from "os";
|
|
4494
5042
|
import { execFileSync as execFileSync5 } from "child_process";
|
|
4495
5043
|
function extractTarball(tarballBuffer) {
|
|
4496
|
-
const tmpDir =
|
|
4497
|
-
const tarballPath =
|
|
5044
|
+
const tmpDir = fs25.mkdtempSync(path44.join(os6.tmpdir(), "harness-skill-install-"));
|
|
5045
|
+
const tarballPath = path44.join(tmpDir, "package.tgz");
|
|
4498
5046
|
try {
|
|
4499
|
-
|
|
5047
|
+
fs25.writeFileSync(tarballPath, tarballBuffer);
|
|
4500
5048
|
execFileSync5("tar", ["-xzf", tarballPath, "-C", tmpDir], {
|
|
4501
5049
|
timeout: 3e4
|
|
4502
5050
|
});
|
|
4503
|
-
|
|
5051
|
+
fs25.unlinkSync(tarballPath);
|
|
4504
5052
|
} catch (err) {
|
|
4505
5053
|
cleanupTempDir(tmpDir);
|
|
4506
5054
|
throw new Error(
|
|
@@ -4511,43 +5059,43 @@ function extractTarball(tarballBuffer) {
|
|
|
4511
5059
|
return tmpDir;
|
|
4512
5060
|
}
|
|
4513
5061
|
function placeSkillContent(extractedPkgDir, communityBaseDir, skillName, platforms) {
|
|
4514
|
-
const files =
|
|
5062
|
+
const files = fs25.readdirSync(extractedPkgDir);
|
|
4515
5063
|
for (const platform of platforms) {
|
|
4516
|
-
const targetDir =
|
|
4517
|
-
if (
|
|
4518
|
-
|
|
5064
|
+
const targetDir = path44.join(communityBaseDir, platform, skillName);
|
|
5065
|
+
if (fs25.existsSync(targetDir)) {
|
|
5066
|
+
fs25.rmSync(targetDir, { recursive: true, force: true });
|
|
4519
5067
|
}
|
|
4520
|
-
|
|
5068
|
+
fs25.mkdirSync(targetDir, { recursive: true });
|
|
4521
5069
|
for (const file of files) {
|
|
4522
5070
|
if (file === "package.json" || file === "node_modules") continue;
|
|
4523
|
-
const srcPath =
|
|
4524
|
-
const destPath =
|
|
4525
|
-
const stat =
|
|
5071
|
+
const srcPath = path44.join(extractedPkgDir, file);
|
|
5072
|
+
const destPath = path44.join(targetDir, file);
|
|
5073
|
+
const stat = fs25.statSync(srcPath);
|
|
4526
5074
|
if (stat.isDirectory()) {
|
|
4527
|
-
|
|
5075
|
+
fs25.cpSync(srcPath, destPath, { recursive: true });
|
|
4528
5076
|
} else {
|
|
4529
|
-
|
|
5077
|
+
fs25.copyFileSync(srcPath, destPath);
|
|
4530
5078
|
}
|
|
4531
5079
|
}
|
|
4532
5080
|
}
|
|
4533
5081
|
}
|
|
4534
5082
|
function removeSkillContent(communityBaseDir, skillName, platforms) {
|
|
4535
5083
|
for (const platform of platforms) {
|
|
4536
|
-
const targetDir =
|
|
4537
|
-
if (
|
|
4538
|
-
|
|
5084
|
+
const targetDir = path44.join(communityBaseDir, platform, skillName);
|
|
5085
|
+
if (fs25.existsSync(targetDir)) {
|
|
5086
|
+
fs25.rmSync(targetDir, { recursive: true, force: true });
|
|
4539
5087
|
}
|
|
4540
5088
|
}
|
|
4541
5089
|
}
|
|
4542
5090
|
function cleanupTempDir(dirPath) {
|
|
4543
5091
|
try {
|
|
4544
|
-
|
|
5092
|
+
fs25.rmSync(dirPath, { recursive: true, force: true });
|
|
4545
5093
|
} catch {
|
|
4546
5094
|
}
|
|
4547
5095
|
}
|
|
4548
5096
|
|
|
4549
5097
|
// src/registry/resolver.ts
|
|
4550
|
-
import
|
|
5098
|
+
import semver3 from "semver";
|
|
4551
5099
|
function resolveVersion(metadata, versionRange) {
|
|
4552
5100
|
const versions = Object.keys(metadata.versions);
|
|
4553
5101
|
if (versions.length === 0) {
|
|
@@ -4559,13 +5107,13 @@ function resolveVersion(metadata, versionRange) {
|
|
|
4559
5107
|
const latestInfo = metadata.versions[latestTag];
|
|
4560
5108
|
if (latestInfo) return latestInfo;
|
|
4561
5109
|
}
|
|
4562
|
-
const highest =
|
|
5110
|
+
const highest = semver3.maxSatisfying(versions, "*");
|
|
4563
5111
|
if (!highest || !metadata.versions[highest]) {
|
|
4564
5112
|
throw new Error(`No versions available for ${metadata.name}.`);
|
|
4565
5113
|
}
|
|
4566
5114
|
return metadata.versions[highest];
|
|
4567
5115
|
}
|
|
4568
|
-
const matched =
|
|
5116
|
+
const matched = semver3.maxSatisfying(versions, versionRange);
|
|
4569
5117
|
if (!matched || !metadata.versions[matched]) {
|
|
4570
5118
|
throw new Error(
|
|
4571
5119
|
`No version of ${metadata.name} matches range ${versionRange}. Available: ${versions.join(", ")}`
|
|
@@ -4597,35 +5145,35 @@ function validateSkillYaml(parsed) {
|
|
|
4597
5145
|
};
|
|
4598
5146
|
}
|
|
4599
5147
|
async function runLocalInstall(fromPath, options) {
|
|
4600
|
-
const resolvedPath =
|
|
4601
|
-
if (!
|
|
5148
|
+
const resolvedPath = path45.resolve(fromPath);
|
|
5149
|
+
if (!fs26.existsSync(resolvedPath)) {
|
|
4602
5150
|
throw new Error(`--from path does not exist: ${resolvedPath}`);
|
|
4603
5151
|
}
|
|
4604
|
-
const stat =
|
|
5152
|
+
const stat = fs26.statSync(resolvedPath);
|
|
4605
5153
|
let extractDir = null;
|
|
4606
5154
|
let pkgDir;
|
|
4607
5155
|
if (stat.isDirectory()) {
|
|
4608
5156
|
pkgDir = resolvedPath;
|
|
4609
5157
|
} else if (resolvedPath.endsWith(".tgz") || resolvedPath.endsWith(".tar.gz")) {
|
|
4610
|
-
const tarballBuffer =
|
|
5158
|
+
const tarballBuffer = fs26.readFileSync(resolvedPath);
|
|
4611
5159
|
extractDir = extractTarball(tarballBuffer);
|
|
4612
|
-
pkgDir =
|
|
5160
|
+
pkgDir = path45.join(extractDir, "package");
|
|
4613
5161
|
} else {
|
|
4614
5162
|
throw new Error(`--from path must be a directory or .tgz file. Got: ${resolvedPath}`);
|
|
4615
5163
|
}
|
|
4616
5164
|
try {
|
|
4617
|
-
const skillYamlPath =
|
|
4618
|
-
if (!
|
|
5165
|
+
const skillYamlPath = path45.join(pkgDir, "skill.yaml");
|
|
5166
|
+
if (!fs26.existsSync(skillYamlPath)) {
|
|
4619
5167
|
throw new Error(`No skill.yaml found at ${skillYamlPath}`);
|
|
4620
5168
|
}
|
|
4621
|
-
const rawYaml =
|
|
5169
|
+
const rawYaml = fs26.readFileSync(skillYamlPath, "utf-8");
|
|
4622
5170
|
const parsed = yamlParse(rawYaml);
|
|
4623
5171
|
const skillYaml = validateSkillYaml(parsed);
|
|
4624
5172
|
const shortName = skillYaml.name;
|
|
4625
5173
|
const globalDir = resolveGlobalSkillsDir();
|
|
4626
|
-
const skillsDir =
|
|
4627
|
-
const communityBase =
|
|
4628
|
-
const lockfilePath =
|
|
5174
|
+
const skillsDir = path45.dirname(globalDir);
|
|
5175
|
+
const communityBase = path45.join(skillsDir, "community");
|
|
5176
|
+
const lockfilePath = path45.join(communityBase, "skills-lock.json");
|
|
4629
5177
|
const bundledNames = getBundledSkillNames(globalDir);
|
|
4630
5178
|
if (bundledNames.has(shortName)) {
|
|
4631
5179
|
throw new Error(
|
|
@@ -4666,9 +5214,9 @@ async function runInstall(skillName, options) {
|
|
|
4666
5214
|
const packageName = resolvePackageName(skillName);
|
|
4667
5215
|
const shortName = extractSkillName(packageName);
|
|
4668
5216
|
const globalDir = resolveGlobalSkillsDir();
|
|
4669
|
-
const skillsDir =
|
|
4670
|
-
const communityBase =
|
|
4671
|
-
const lockfilePath =
|
|
5217
|
+
const skillsDir = path45.dirname(globalDir);
|
|
5218
|
+
const communityBase = path45.join(skillsDir, "community");
|
|
5219
|
+
const lockfilePath = path45.join(communityBase, "skills-lock.json");
|
|
4672
5220
|
const bundledNames = getBundledSkillNames(globalDir);
|
|
4673
5221
|
if (bundledNames.has(shortName)) {
|
|
4674
5222
|
throw new Error(
|
|
@@ -4694,12 +5242,12 @@ async function runInstall(skillName, options) {
|
|
|
4694
5242
|
const extractDir = extractTarball(tarballBuffer);
|
|
4695
5243
|
let skillYaml;
|
|
4696
5244
|
try {
|
|
4697
|
-
const extractedPkgDir =
|
|
4698
|
-
const skillYamlPath =
|
|
4699
|
-
if (!
|
|
5245
|
+
const extractedPkgDir = path45.join(extractDir, "package");
|
|
5246
|
+
const skillYamlPath = path45.join(extractedPkgDir, "skill.yaml");
|
|
5247
|
+
if (!fs26.existsSync(skillYamlPath)) {
|
|
4700
5248
|
throw new Error(`contains invalid skill.yaml: file not found in package`);
|
|
4701
5249
|
}
|
|
4702
|
-
const rawYaml =
|
|
5250
|
+
const rawYaml = fs26.readFileSync(skillYamlPath, "utf-8");
|
|
4703
5251
|
const parsed = yamlParse(rawYaml);
|
|
4704
5252
|
skillYaml = validateSkillYaml(parsed);
|
|
4705
5253
|
placeSkillContent(extractedPkgDir, communityBase, shortName, skillYaml.platforms);
|
|
@@ -4738,7 +5286,7 @@ async function runInstall(skillName, options) {
|
|
|
4738
5286
|
return result;
|
|
4739
5287
|
}
|
|
4740
5288
|
function createInstallCommand() {
|
|
4741
|
-
const cmd = new
|
|
5289
|
+
const cmd = new Command54("install");
|
|
4742
5290
|
cmd.description("Install a community skill from the @harness-skills registry").argument("<skill>", "Skill name or @harness-skills/scoped package name").option("--version <range>", "Semver range or exact version to install").option("--force", "Force reinstall even if same version is already installed").option("--from <path>", "Install from a local directory or .tgz file").option("--registry <url>", "Use a custom npm registry URL").action(async (skill, opts) => {
|
|
4743
5291
|
try {
|
|
4744
5292
|
const result = await runInstall(skill, opts);
|
|
@@ -4762,15 +5310,15 @@ function createInstallCommand() {
|
|
|
4762
5310
|
}
|
|
4763
5311
|
|
|
4764
5312
|
// src/commands/install-constraints.ts
|
|
4765
|
-
import * as
|
|
4766
|
-
import * as
|
|
4767
|
-
import { Command as
|
|
4768
|
-
import
|
|
5313
|
+
import * as fs27 from "fs/promises";
|
|
5314
|
+
import * as path46 from "path";
|
|
5315
|
+
import { Command as Command55 } from "commander";
|
|
5316
|
+
import semver4 from "semver";
|
|
4769
5317
|
async function runInstallConstraints(options) {
|
|
4770
5318
|
const { source, configPath, lockfilePath } = options;
|
|
4771
5319
|
let rawBundle;
|
|
4772
5320
|
try {
|
|
4773
|
-
rawBundle = await
|
|
5321
|
+
rawBundle = await fs27.readFile(source, "utf-8");
|
|
4774
5322
|
} catch (err) {
|
|
4775
5323
|
if (isNodeError(err) && err.code === "ENOENT") {
|
|
4776
5324
|
return { ok: false, error: `Bundle file not found: ${source}` };
|
|
@@ -4793,9 +5341,9 @@ async function runInstallConstraints(options) {
|
|
|
4793
5341
|
}
|
|
4794
5342
|
const bundle = bundleResult.data;
|
|
4795
5343
|
if (bundle.minHarnessVersion) {
|
|
4796
|
-
const installed =
|
|
4797
|
-
const required =
|
|
4798
|
-
if (installed && required &&
|
|
5344
|
+
const installed = semver4.valid(semver4.coerce(CLI_VERSION));
|
|
5345
|
+
const required = semver4.valid(semver4.coerce(bundle.minHarnessVersion));
|
|
5346
|
+
if (installed && required && semver4.lt(installed, required)) {
|
|
4799
5347
|
return {
|
|
4800
5348
|
ok: false,
|
|
4801
5349
|
error: `Bundle requires harness version >= ${bundle.minHarnessVersion}, but installed version is ${CLI_VERSION}. Please upgrade.`
|
|
@@ -4813,7 +5361,7 @@ async function runInstallConstraints(options) {
|
|
|
4813
5361
|
}
|
|
4814
5362
|
let localConfig;
|
|
4815
5363
|
try {
|
|
4816
|
-
const raw = await
|
|
5364
|
+
const raw = await fs27.readFile(configPath, "utf-8");
|
|
4817
5365
|
localConfig = JSON.parse(raw);
|
|
4818
5366
|
} catch (err) {
|
|
4819
5367
|
return {
|
|
@@ -4961,7 +5509,7 @@ function isNodeError(err) {
|
|
|
4961
5509
|
return err instanceof Error && "code" in err;
|
|
4962
5510
|
}
|
|
4963
5511
|
function resolveConfigPath(opts) {
|
|
4964
|
-
if (opts.config) return
|
|
5512
|
+
if (opts.config) return path46.resolve(opts.config);
|
|
4965
5513
|
const found = findConfigFile();
|
|
4966
5514
|
if (!found.ok) {
|
|
4967
5515
|
logger.error(found.error.message);
|
|
@@ -4996,9 +5544,9 @@ function logInstallResult(val, opts) {
|
|
|
4996
5544
|
}
|
|
4997
5545
|
async function handleInstallConstraints(source, opts) {
|
|
4998
5546
|
const configPath = resolveConfigPath(opts);
|
|
4999
|
-
const projectRoot =
|
|
5000
|
-
const lockfilePath =
|
|
5001
|
-
const resolvedSource =
|
|
5547
|
+
const projectRoot = path46.dirname(configPath);
|
|
5548
|
+
const lockfilePath = path46.join(projectRoot, ".harness", "constraints.lock.json");
|
|
5549
|
+
const resolvedSource = path46.resolve(source);
|
|
5002
5550
|
if (opts.forceLocal && opts.forcePackage) {
|
|
5003
5551
|
logger.error("Cannot use both --force-local and --force-package.");
|
|
5004
5552
|
process.exit(1);
|
|
@@ -5018,15 +5566,15 @@ async function handleInstallConstraints(source, opts) {
|
|
|
5018
5566
|
logInstallResult(result.value, opts);
|
|
5019
5567
|
}
|
|
5020
5568
|
function createInstallConstraintsCommand() {
|
|
5021
|
-
const cmd = new
|
|
5569
|
+
const cmd = new Command55("install-constraints");
|
|
5022
5570
|
cmd.description("Install a constraints bundle into the local harness config").argument("<source>", "Path to a .harness-constraints.json bundle file").option("--force-local", "Resolve all conflicts by keeping local values").option("--force-package", "Resolve all conflicts by using package values").option("--dry-run", "Show what would change without writing files").option("-c, --config <path>", "Path to harness.config.json").action(handleInstallConstraints);
|
|
5023
5571
|
return cmd;
|
|
5024
5572
|
}
|
|
5025
5573
|
|
|
5026
5574
|
// src/commands/uninstall-constraints.ts
|
|
5027
|
-
import * as
|
|
5028
|
-
import * as
|
|
5029
|
-
import { Command as
|
|
5575
|
+
import * as fs28 from "fs/promises";
|
|
5576
|
+
import * as path47 from "path";
|
|
5577
|
+
import { Command as Command56 } from "commander";
|
|
5030
5578
|
async function runUninstallConstraints(options) {
|
|
5031
5579
|
const { packageName, configPath, lockfilePath } = options;
|
|
5032
5580
|
const lockfileResult = await readLockfile(lockfilePath);
|
|
@@ -5046,7 +5594,7 @@ async function runUninstallConstraints(options) {
|
|
|
5046
5594
|
}
|
|
5047
5595
|
let localConfig;
|
|
5048
5596
|
try {
|
|
5049
|
-
const raw = await
|
|
5597
|
+
const raw = await fs28.readFile(configPath, "utf-8");
|
|
5050
5598
|
localConfig = JSON.parse(raw);
|
|
5051
5599
|
} catch (err) {
|
|
5052
5600
|
return {
|
|
@@ -5083,11 +5631,11 @@ async function runUninstallConstraints(options) {
|
|
|
5083
5631
|
};
|
|
5084
5632
|
}
|
|
5085
5633
|
function createUninstallConstraintsCommand() {
|
|
5086
|
-
const cmd = new
|
|
5634
|
+
const cmd = new Command56("uninstall-constraints");
|
|
5087
5635
|
cmd.description("Remove a previously installed constraints package").argument("<name>", "Name of the constraint package to uninstall").option("-c, --config <path>", "Path to harness.config.json").action(async (name, opts) => {
|
|
5088
5636
|
let configPath;
|
|
5089
5637
|
if (opts.config) {
|
|
5090
|
-
configPath =
|
|
5638
|
+
configPath = path47.resolve(opts.config);
|
|
5091
5639
|
} else {
|
|
5092
5640
|
const found = findConfigFile();
|
|
5093
5641
|
if (!found.ok) {
|
|
@@ -5096,8 +5644,8 @@ function createUninstallConstraintsCommand() {
|
|
|
5096
5644
|
}
|
|
5097
5645
|
configPath = found.value;
|
|
5098
5646
|
}
|
|
5099
|
-
const projectRoot =
|
|
5100
|
-
const lockfilePath =
|
|
5647
|
+
const projectRoot = path47.dirname(configPath);
|
|
5648
|
+
const lockfilePath = path47.join(projectRoot, ".harness", "constraints.lock.json");
|
|
5101
5649
|
const result = await runUninstallConstraints({
|
|
5102
5650
|
packageName: name,
|
|
5103
5651
|
configPath,
|
|
@@ -5122,15 +5670,15 @@ function createUninstallConstraintsCommand() {
|
|
|
5122
5670
|
}
|
|
5123
5671
|
|
|
5124
5672
|
// src/commands/uninstall.ts
|
|
5125
|
-
import * as
|
|
5126
|
-
import { Command as
|
|
5673
|
+
import * as path48 from "path";
|
|
5674
|
+
import { Command as Command57 } from "commander";
|
|
5127
5675
|
async function runUninstall(skillName, options) {
|
|
5128
5676
|
const packageName = resolvePackageName(skillName);
|
|
5129
5677
|
const shortName = extractSkillName(packageName);
|
|
5130
5678
|
const globalDir = resolveGlobalSkillsDir();
|
|
5131
|
-
const skillsDir =
|
|
5132
|
-
const communityBase =
|
|
5133
|
-
const lockfilePath =
|
|
5679
|
+
const skillsDir = path48.dirname(globalDir);
|
|
5680
|
+
const communityBase = path48.join(skillsDir, "community");
|
|
5681
|
+
const lockfilePath = path48.join(communityBase, "skills-lock.json");
|
|
5134
5682
|
const lockfile = readLockfile2(lockfilePath);
|
|
5135
5683
|
const entry = lockfile.skills[packageName];
|
|
5136
5684
|
if (!entry) {
|
|
@@ -5160,7 +5708,7 @@ async function runUninstall(skillName, options) {
|
|
|
5160
5708
|
return result;
|
|
5161
5709
|
}
|
|
5162
5710
|
function createUninstallCommand() {
|
|
5163
|
-
const cmd = new
|
|
5711
|
+
const cmd = new Command57("uninstall");
|
|
5164
5712
|
cmd.description("Uninstall a community skill").argument("<skill>", "Skill name or @harness-skills/scoped package name").option("--force", "Remove even if other skills depend on this one").action(async (skill, opts) => {
|
|
5165
5713
|
try {
|
|
5166
5714
|
const result = await runUninstall(skill, opts);
|
|
@@ -5179,13 +5727,13 @@ function createUninstallCommand() {
|
|
|
5179
5727
|
}
|
|
5180
5728
|
|
|
5181
5729
|
// src/commands/orchestrator.ts
|
|
5182
|
-
import { Command as
|
|
5183
|
-
import * as
|
|
5730
|
+
import { Command as Command58 } from "commander";
|
|
5731
|
+
import * as path49 from "path";
|
|
5184
5732
|
import { Orchestrator, WorkflowLoader, launchTUI } from "@harness-engineering/orchestrator";
|
|
5185
5733
|
function createOrchestratorCommand() {
|
|
5186
|
-
const orchestrator = new
|
|
5734
|
+
const orchestrator = new Command58("orchestrator");
|
|
5187
5735
|
orchestrator.command("run").description("Run the orchestrator daemon").option("-w, --workflow <path>", "Path to WORKFLOW.md", "WORKFLOW.md").action(async (opts) => {
|
|
5188
|
-
const workflowPath =
|
|
5736
|
+
const workflowPath = path49.resolve(process.cwd(), opts.workflow);
|
|
5189
5737
|
const loader = new WorkflowLoader();
|
|
5190
5738
|
const result = await loader.loadWorkflow(workflowPath);
|
|
5191
5739
|
if (!result.ok) {
|
|
@@ -5209,13 +5757,13 @@ function createOrchestratorCommand() {
|
|
|
5209
5757
|
}
|
|
5210
5758
|
|
|
5211
5759
|
// src/commands/learnings/index.ts
|
|
5212
|
-
import { Command as
|
|
5760
|
+
import { Command as Command60 } from "commander";
|
|
5213
5761
|
|
|
5214
5762
|
// src/commands/learnings/prune.ts
|
|
5215
|
-
import { Command as
|
|
5216
|
-
import * as
|
|
5763
|
+
import { Command as Command59 } from "commander";
|
|
5764
|
+
import * as path50 from "path";
|
|
5217
5765
|
async function handlePrune(opts) {
|
|
5218
|
-
const projectPath =
|
|
5766
|
+
const projectPath = path50.resolve(opts.path);
|
|
5219
5767
|
const result = await pruneLearnings(projectPath, opts.stream);
|
|
5220
5768
|
if (!result.ok) {
|
|
5221
5769
|
logger.error(result.error.message);
|
|
@@ -5254,21 +5802,21 @@ function printPatternProposals(patterns) {
|
|
|
5254
5802
|
);
|
|
5255
5803
|
}
|
|
5256
5804
|
function createPruneCommand() {
|
|
5257
|
-
return new
|
|
5805
|
+
return new Command59("prune").description(
|
|
5258
5806
|
"Analyze global learnings for patterns, present improvement proposals, and archive old entries"
|
|
5259
5807
|
).option("--path <path>", "Project root path", ".").option("--stream <name>", "Target a specific stream").action(handlePrune);
|
|
5260
5808
|
}
|
|
5261
5809
|
|
|
5262
5810
|
// src/commands/learnings/index.ts
|
|
5263
5811
|
function createLearningsCommand() {
|
|
5264
|
-
const command = new
|
|
5812
|
+
const command = new Command60("learnings").description("Learnings management commands");
|
|
5265
5813
|
command.addCommand(createPruneCommand());
|
|
5266
5814
|
return command;
|
|
5267
5815
|
}
|
|
5268
5816
|
|
|
5269
5817
|
// src/index.ts
|
|
5270
5818
|
function createProgram() {
|
|
5271
|
-
const program = new
|
|
5819
|
+
const program = new Command61();
|
|
5272
5820
|
program.name("harness").description("CLI for Harness Engineering toolkit").version(CLI_VERSION).option("-c, --config <path>", "Path to config file").option("--json", "Output as JSON").option("--verbose", "Verbose output").option("--quiet", "Minimal output");
|
|
5273
5821
|
program.addCommand(createValidateCommand());
|
|
5274
5822
|
program.addCommand(createCheckDepsCommand());
|
|
@@ -5289,10 +5837,13 @@ function createProgram() {
|
|
|
5289
5837
|
program.addCommand(createCheckPhaseGateCommand());
|
|
5290
5838
|
program.addCommand(createCreateSkillCommand());
|
|
5291
5839
|
program.addCommand(createSetupMcpCommand());
|
|
5840
|
+
program.addCommand(createSetupCommand());
|
|
5841
|
+
program.addCommand(createDoctorCommand());
|
|
5292
5842
|
program.addCommand(createGenerateSlashCommandsCommand());
|
|
5293
5843
|
program.addCommand(createGenerateAgentDefinitionsCommand());
|
|
5294
5844
|
program.addCommand(createGenerateCommand3());
|
|
5295
5845
|
program.addCommand(createCICommand());
|
|
5846
|
+
program.addCommand(createHooksCommand());
|
|
5296
5847
|
program.addCommand(createUpdateCommand());
|
|
5297
5848
|
program.addCommand(createScanCommand());
|
|
5298
5849
|
program.addCommand(createIngestCommand());
|
|
@@ -5313,6 +5864,7 @@ function createProgram() {
|
|
|
5313
5864
|
|
|
5314
5865
|
export {
|
|
5315
5866
|
buildPreamble,
|
|
5867
|
+
printFirstRunWelcome,
|
|
5316
5868
|
runScan,
|
|
5317
5869
|
runIngest,
|
|
5318
5870
|
runQuery,
|