@harness-engineering/cli 1.6.0 → 1.6.2
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/personas/code-reviewer.yaml +2 -0
- package/dist/agents/personas/codebase-health-analyst.yaml +5 -0
- package/dist/agents/personas/performance-guardian.yaml +26 -0
- package/dist/agents/personas/security-reviewer.yaml +35 -0
- package/dist/agents/skills/claude-code/harness-autopilot/SKILL.md +494 -0
- package/dist/agents/skills/claude-code/harness-autopilot/skill.yaml +52 -0
- package/dist/agents/skills/claude-code/harness-code-review/SKILL.md +15 -0
- package/dist/agents/skills/claude-code/harness-integrity/SKILL.md +20 -6
- package/dist/agents/skills/claude-code/harness-perf/SKILL.md +231 -0
- package/dist/agents/skills/claude-code/harness-perf/skill.yaml +47 -0
- package/dist/agents/skills/claude-code/harness-perf-tdd/SKILL.md +236 -0
- package/dist/agents/skills/claude-code/harness-perf-tdd/skill.yaml +47 -0
- package/dist/agents/skills/claude-code/harness-pre-commit-review/SKILL.md +27 -2
- package/dist/agents/skills/claude-code/harness-release-readiness/SKILL.md +657 -0
- package/dist/agents/skills/claude-code/harness-release-readiness/skill.yaml +57 -0
- package/dist/agents/skills/claude-code/harness-security-review/SKILL.md +206 -0
- package/dist/agents/skills/claude-code/harness-security-review/skill.yaml +50 -0
- package/dist/agents/skills/claude-code/harness-security-scan/SKILL.md +102 -0
- package/dist/agents/skills/claude-code/harness-security-scan/skill.yaml +41 -0
- package/dist/agents/skills/claude-code/harness-state-management/SKILL.md +22 -8
- package/dist/agents/skills/gemini-cli/harness-autopilot/SKILL.md +494 -0
- package/dist/agents/skills/gemini-cli/harness-autopilot/skill.yaml +52 -0
- package/dist/agents/skills/gemini-cli/harness-perf/SKILL.md +231 -0
- package/dist/agents/skills/gemini-cli/harness-perf/skill.yaml +47 -0
- package/dist/agents/skills/gemini-cli/harness-perf-tdd/SKILL.md +236 -0
- package/dist/agents/skills/gemini-cli/harness-perf-tdd/skill.yaml +47 -0
- package/dist/agents/skills/gemini-cli/harness-release-readiness/SKILL.md +657 -0
- package/dist/agents/skills/gemini-cli/harness-release-readiness/skill.yaml +57 -0
- package/dist/agents/skills/gemini-cli/harness-security-review/skill.yaml +50 -0
- package/dist/agents/skills/gemini-cli/harness-security-scan/SKILL.md +102 -0
- package/dist/agents/skills/gemini-cli/harness-security-scan/skill.yaml +41 -0
- package/dist/bin/harness.js +3 -2
- package/dist/{chunk-VS4OTOKZ.js → chunk-IUFFBBYV.js} +1271 -461
- package/dist/{chunk-3U5VZYR7.js → chunk-UDWGSL3T.js} +4 -1
- package/dist/chunk-USEYPS7F.js +6150 -0
- package/dist/dist-4MYPT3OE.js +2528 -0
- package/dist/dist-RBZXXJHG.js +242 -0
- package/dist/index.js +3 -2
- package/dist/validate-cross-check-CPEPNLOD.js +7 -0
- package/package.json +12 -8
- package/dist/validate-cross-check-LNIZ7KGZ.js +0 -6
|
@@ -1,3 +1,38 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BaselineManager,
|
|
3
|
+
CriticalPathResolver,
|
|
4
|
+
EntropyAnalyzer,
|
|
5
|
+
Err,
|
|
6
|
+
Ok,
|
|
7
|
+
SecurityScanner,
|
|
8
|
+
TypeScriptParser,
|
|
9
|
+
VERSION,
|
|
10
|
+
appendLearning,
|
|
11
|
+
applyFixes,
|
|
12
|
+
archiveStream,
|
|
13
|
+
buildSnapshot,
|
|
14
|
+
checkDocCoverage,
|
|
15
|
+
createFixes,
|
|
16
|
+
createSelfReview,
|
|
17
|
+
createStream,
|
|
18
|
+
defineLayer,
|
|
19
|
+
detectCircularDepsInFiles,
|
|
20
|
+
detectDeadCode,
|
|
21
|
+
detectDocDrift,
|
|
22
|
+
generateSuggestions,
|
|
23
|
+
listStreams,
|
|
24
|
+
loadState,
|
|
25
|
+
loadStreamIndex,
|
|
26
|
+
parseDiff,
|
|
27
|
+
parseSecurityConfig,
|
|
28
|
+
requestPeerReview,
|
|
29
|
+
resolveStreamPath,
|
|
30
|
+
runCIChecks,
|
|
31
|
+
setActiveStream,
|
|
32
|
+
validateAgentsMap,
|
|
33
|
+
validateDependencies,
|
|
34
|
+
validateKnowledgeMap
|
|
35
|
+
} from "./chunk-USEYPS7F.js";
|
|
1
36
|
import {
|
|
2
37
|
CLIError,
|
|
3
38
|
ExitCode,
|
|
@@ -8,19 +43,15 @@ import {
|
|
|
8
43
|
} from "./chunk-ACMDUQJG.js";
|
|
9
44
|
|
|
10
45
|
// src/index.ts
|
|
11
|
-
import { Command as
|
|
12
|
-
import { VERSION } from "@harness-engineering/core";
|
|
46
|
+
import { Command as Command43 } from "commander";
|
|
13
47
|
|
|
14
48
|
// src/commands/validate.ts
|
|
15
49
|
import { Command } from "commander";
|
|
16
50
|
import * as path2 from "path";
|
|
17
|
-
import { Ok as Ok2 } from "@harness-engineering/core";
|
|
18
|
-
import { validateAgentsMap, validateKnowledgeMap } from "@harness-engineering/core";
|
|
19
51
|
|
|
20
52
|
// src/config/loader.ts
|
|
21
53
|
import * as fs from "fs";
|
|
22
54
|
import * as path from "path";
|
|
23
|
-
import { Ok, Err } from "@harness-engineering/core";
|
|
24
55
|
|
|
25
56
|
// src/config/schema.ts
|
|
26
57
|
import { z } from "zod";
|
|
@@ -241,7 +272,7 @@ async function runValidate(options) {
|
|
|
241
272
|
});
|
|
242
273
|
}
|
|
243
274
|
result.checks.fileStructure = true;
|
|
244
|
-
return
|
|
275
|
+
return Ok(result);
|
|
245
276
|
}
|
|
246
277
|
function createValidateCommand() {
|
|
247
278
|
const command = new Command("validate").description("Run all validation checks").option("--cross-check", "Run cross-artifact consistency validation").action(async (opts, cmd) => {
|
|
@@ -263,7 +294,7 @@ function createValidateCommand() {
|
|
|
263
294
|
process.exit(result.error.exitCode);
|
|
264
295
|
}
|
|
265
296
|
if (opts.crossCheck) {
|
|
266
|
-
const { runCrossCheck: runCrossCheck2 } = await import("./validate-cross-check-
|
|
297
|
+
const { runCrossCheck: runCrossCheck2 } = await import("./validate-cross-check-CPEPNLOD.js");
|
|
267
298
|
const cwd = process.cwd();
|
|
268
299
|
const specsDir = path2.join(cwd, "docs", "specs");
|
|
269
300
|
const plansDir = path2.join(cwd, "docs", "plans");
|
|
@@ -291,13 +322,6 @@ function createValidateCommand() {
|
|
|
291
322
|
// src/commands/check-deps.ts
|
|
292
323
|
import { Command as Command2 } from "commander";
|
|
293
324
|
import * as path3 from "path";
|
|
294
|
-
import { Ok as Ok3 } from "@harness-engineering/core";
|
|
295
|
-
import {
|
|
296
|
-
validateDependencies,
|
|
297
|
-
detectCircularDepsInFiles,
|
|
298
|
-
defineLayer,
|
|
299
|
-
TypeScriptParser
|
|
300
|
-
} from "@harness-engineering/core";
|
|
301
325
|
|
|
302
326
|
// src/utils/files.ts
|
|
303
327
|
import { glob } from "glob";
|
|
@@ -319,7 +343,7 @@ async function runCheckDeps(options) {
|
|
|
319
343
|
circularDeps: []
|
|
320
344
|
};
|
|
321
345
|
if (!config.layers || config.layers.length === 0) {
|
|
322
|
-
return
|
|
346
|
+
return Ok(result);
|
|
323
347
|
}
|
|
324
348
|
const rootDir = path3.resolve(cwd, config.rootDir);
|
|
325
349
|
const parser = new TypeScriptParser();
|
|
@@ -358,7 +382,7 @@ async function runCheckDeps(options) {
|
|
|
358
382
|
}
|
|
359
383
|
}
|
|
360
384
|
}
|
|
361
|
-
return
|
|
385
|
+
return Ok(result);
|
|
362
386
|
}
|
|
363
387
|
function createCheckDepsCommand() {
|
|
364
388
|
const command = new Command2("check-deps").description("Validate dependency layers and detect circular dependencies").action(async (_opts, cmd) => {
|
|
@@ -400,11 +424,400 @@ function createCheckDepsCommand() {
|
|
|
400
424
|
return command;
|
|
401
425
|
}
|
|
402
426
|
|
|
403
|
-
// src/commands/check-
|
|
427
|
+
// src/commands/check-perf.ts
|
|
404
428
|
import { Command as Command3 } from "commander";
|
|
405
429
|
import * as path4 from "path";
|
|
406
|
-
|
|
407
|
-
|
|
430
|
+
async function runCheckPerf(cwd, options) {
|
|
431
|
+
const runAll = !options.structural && !options.size && !options.coupling;
|
|
432
|
+
const analyzer = new EntropyAnalyzer({
|
|
433
|
+
rootDir: path4.resolve(cwd),
|
|
434
|
+
analyze: {
|
|
435
|
+
complexity: runAll || !!options.structural,
|
|
436
|
+
coupling: runAll || !!options.coupling,
|
|
437
|
+
sizeBudget: runAll || !!options.size
|
|
438
|
+
}
|
|
439
|
+
});
|
|
440
|
+
const analysisResult = await analyzer.analyze();
|
|
441
|
+
if (!analysisResult.ok) {
|
|
442
|
+
return Ok({
|
|
443
|
+
valid: false,
|
|
444
|
+
violations: [
|
|
445
|
+
{
|
|
446
|
+
tier: 1,
|
|
447
|
+
severity: "error",
|
|
448
|
+
metric: "analysis-error",
|
|
449
|
+
file: "",
|
|
450
|
+
value: 0,
|
|
451
|
+
threshold: 0,
|
|
452
|
+
message: `Analysis failed: ${analysisResult.error.message}`
|
|
453
|
+
}
|
|
454
|
+
],
|
|
455
|
+
stats: { filesAnalyzed: 0, violationCount: 1, errorCount: 1, warningCount: 0, infoCount: 0 }
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
const report = analysisResult.value;
|
|
459
|
+
const violations = [];
|
|
460
|
+
if (report.complexity) {
|
|
461
|
+
for (const v of report.complexity.violations) {
|
|
462
|
+
violations.push({
|
|
463
|
+
tier: v.tier,
|
|
464
|
+
severity: v.severity,
|
|
465
|
+
metric: v.metric,
|
|
466
|
+
file: v.file,
|
|
467
|
+
value: v.value,
|
|
468
|
+
threshold: v.threshold,
|
|
469
|
+
message: v.message || `[Tier ${v.tier}] ${v.metric}: ${v.function} (${v.value} > ${v.threshold})`
|
|
470
|
+
});
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
if (report.coupling) {
|
|
474
|
+
for (const v of report.coupling.violations) {
|
|
475
|
+
violations.push({
|
|
476
|
+
tier: v.tier,
|
|
477
|
+
severity: v.severity,
|
|
478
|
+
metric: v.metric,
|
|
479
|
+
file: v.file,
|
|
480
|
+
value: v.value,
|
|
481
|
+
threshold: v.threshold,
|
|
482
|
+
message: v.message || `[Tier ${v.tier}] ${v.metric}: ${v.file} (${v.value} > ${v.threshold})`
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
if (report.sizeBudget) {
|
|
487
|
+
for (const v of report.sizeBudget.violations) {
|
|
488
|
+
violations.push({
|
|
489
|
+
tier: v.tier,
|
|
490
|
+
severity: v.severity,
|
|
491
|
+
metric: "sizeBudget",
|
|
492
|
+
file: v.package,
|
|
493
|
+
value: v.currentSize,
|
|
494
|
+
threshold: v.budgetSize,
|
|
495
|
+
message: `[Tier ${v.tier}] Size: ${v.package} (${v.currentSize}B > ${v.budgetSize}B)`
|
|
496
|
+
});
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
const hasErrors = violations.some((v) => v.severity === "error");
|
|
500
|
+
const errorCount = violations.filter((v) => v.severity === "error").length;
|
|
501
|
+
const warningCount = violations.filter((v) => v.severity === "warning").length;
|
|
502
|
+
const infoCount = violations.filter((v) => v.severity === "info").length;
|
|
503
|
+
return Ok({
|
|
504
|
+
valid: !hasErrors,
|
|
505
|
+
violations,
|
|
506
|
+
stats: {
|
|
507
|
+
filesAnalyzed: report.complexity?.stats.filesAnalyzed ?? 0,
|
|
508
|
+
violationCount: violations.length,
|
|
509
|
+
errorCount,
|
|
510
|
+
warningCount,
|
|
511
|
+
infoCount
|
|
512
|
+
}
|
|
513
|
+
});
|
|
514
|
+
}
|
|
515
|
+
function createCheckPerfCommand() {
|
|
516
|
+
const command = new Command3("check-perf").description("Run performance checks: structural complexity, coupling, and size budgets").option("--structural", "Run structural complexity checks only").option("--coupling", "Run coupling metric checks only").option("--size", "Run size budget checks only").action(async (opts, cmd) => {
|
|
517
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
518
|
+
const mode = globalOpts.json ? OutputMode.JSON : globalOpts.quiet ? OutputMode.QUIET : globalOpts.verbose ? OutputMode.VERBOSE : OutputMode.TEXT;
|
|
519
|
+
const formatter = new OutputFormatter(mode);
|
|
520
|
+
const result = await runCheckPerf(process.cwd(), {
|
|
521
|
+
structural: opts.structural,
|
|
522
|
+
coupling: opts.coupling,
|
|
523
|
+
size: opts.size
|
|
524
|
+
});
|
|
525
|
+
if (!result.ok) {
|
|
526
|
+
if (mode === OutputMode.JSON) {
|
|
527
|
+
console.log(JSON.stringify({ error: result.error.message }));
|
|
528
|
+
} else {
|
|
529
|
+
logger.error(result.error.message);
|
|
530
|
+
}
|
|
531
|
+
process.exit(ExitCode.ERROR);
|
|
532
|
+
}
|
|
533
|
+
const issues = result.value.violations.map((v) => ({
|
|
534
|
+
file: v.file,
|
|
535
|
+
message: v.message
|
|
536
|
+
}));
|
|
537
|
+
const output = formatter.formatValidation({
|
|
538
|
+
valid: result.value.valid,
|
|
539
|
+
issues
|
|
540
|
+
});
|
|
541
|
+
if (output) {
|
|
542
|
+
console.log(output);
|
|
543
|
+
}
|
|
544
|
+
process.exit(result.value.valid ? ExitCode.SUCCESS : ExitCode.VALIDATION_FAILED);
|
|
545
|
+
});
|
|
546
|
+
return command;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
// src/commands/check-security.ts
|
|
550
|
+
import { Command as Command4 } from "commander";
|
|
551
|
+
import * as path5 from "path";
|
|
552
|
+
import { execSync } from "child_process";
|
|
553
|
+
var SEVERITY_RANK = {
|
|
554
|
+
error: 3,
|
|
555
|
+
warning: 2,
|
|
556
|
+
info: 1
|
|
557
|
+
};
|
|
558
|
+
function getChangedFiles(cwd) {
|
|
559
|
+
try {
|
|
560
|
+
const output = execSync("git diff --name-only HEAD~1", {
|
|
561
|
+
cwd,
|
|
562
|
+
encoding: "utf-8"
|
|
563
|
+
});
|
|
564
|
+
return output.trim().split("\n").filter((f) => f.length > 0).map((f) => path5.resolve(cwd, f));
|
|
565
|
+
} catch {
|
|
566
|
+
return [];
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
async function runCheckSecurity(cwd, options) {
|
|
570
|
+
const projectRoot = path5.resolve(cwd);
|
|
571
|
+
let configData = {};
|
|
572
|
+
try {
|
|
573
|
+
const fs25 = await import("fs");
|
|
574
|
+
const configPath = path5.join(projectRoot, "harness.config.json");
|
|
575
|
+
if (fs25.existsSync(configPath)) {
|
|
576
|
+
const raw = fs25.readFileSync(configPath, "utf-8");
|
|
577
|
+
const parsed = JSON.parse(raw);
|
|
578
|
+
configData = parsed.security ?? {};
|
|
579
|
+
}
|
|
580
|
+
} catch {
|
|
581
|
+
}
|
|
582
|
+
const securityConfig = parseSecurityConfig(configData);
|
|
583
|
+
const scanner = new SecurityScanner(securityConfig);
|
|
584
|
+
scanner.configureForProject(projectRoot);
|
|
585
|
+
let filesToScan;
|
|
586
|
+
if (options.changedOnly) {
|
|
587
|
+
filesToScan = getChangedFiles(projectRoot);
|
|
588
|
+
} else {
|
|
589
|
+
const { glob: glob2 } = await import("glob");
|
|
590
|
+
const pattern = "**/*.{ts,tsx,js,jsx,go,py,java,rb}";
|
|
591
|
+
const ignore = securityConfig.exclude ?? [
|
|
592
|
+
"**/node_modules/**",
|
|
593
|
+
"**/dist/**",
|
|
594
|
+
"**/*.test.ts",
|
|
595
|
+
"**/fixtures/**"
|
|
596
|
+
];
|
|
597
|
+
filesToScan = await glob2(pattern, { cwd: projectRoot, absolute: true, ignore });
|
|
598
|
+
}
|
|
599
|
+
const result = await scanner.scanFiles(filesToScan);
|
|
600
|
+
const threshold = options.severity ?? "warning";
|
|
601
|
+
const thresholdRank = SEVERITY_RANK[threshold];
|
|
602
|
+
const filtered = result.findings.filter((f) => SEVERITY_RANK[f.severity] >= thresholdRank);
|
|
603
|
+
const hasErrors = filtered.some((f) => f.severity === "error");
|
|
604
|
+
return Ok({
|
|
605
|
+
valid: !hasErrors,
|
|
606
|
+
findings: filtered,
|
|
607
|
+
stats: {
|
|
608
|
+
filesScanned: result.scannedFiles,
|
|
609
|
+
rulesApplied: result.rulesApplied,
|
|
610
|
+
errorCount: filtered.filter((f) => f.severity === "error").length,
|
|
611
|
+
warningCount: filtered.filter((f) => f.severity === "warning").length,
|
|
612
|
+
infoCount: filtered.filter((f) => f.severity === "info").length
|
|
613
|
+
}
|
|
614
|
+
});
|
|
615
|
+
}
|
|
616
|
+
function createCheckSecurityCommand() {
|
|
617
|
+
const command = new Command4("check-security").description("Run lightweight security scan: secrets, injection, XSS, weak crypto").option("--severity <level>", "Minimum severity threshold", "warning").hook("preAction", (thisCommand) => {
|
|
618
|
+
const severity = thisCommand.opts().severity;
|
|
619
|
+
if (!["error", "warning", "info"].includes(severity)) {
|
|
620
|
+
logger.error(`Invalid severity: "${severity}". Must be one of: error, warning, info`);
|
|
621
|
+
process.exit(ExitCode.ERROR);
|
|
622
|
+
}
|
|
623
|
+
}).option("--changed-only", "Only scan git-changed files").action(async (opts, cmd) => {
|
|
624
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
625
|
+
const mode = globalOpts.json ? OutputMode.JSON : globalOpts.quiet ? OutputMode.QUIET : globalOpts.verbose ? OutputMode.VERBOSE : OutputMode.TEXT;
|
|
626
|
+
const formatter = new OutputFormatter(mode);
|
|
627
|
+
const result = await runCheckSecurity(process.cwd(), {
|
|
628
|
+
severity: opts.severity,
|
|
629
|
+
changedOnly: opts.changedOnly
|
|
630
|
+
});
|
|
631
|
+
if (!result.ok) {
|
|
632
|
+
if (mode === OutputMode.JSON) {
|
|
633
|
+
console.log(JSON.stringify({ error: result.error.message }));
|
|
634
|
+
} else {
|
|
635
|
+
logger.error(result.error.message);
|
|
636
|
+
}
|
|
637
|
+
process.exit(ExitCode.ERROR);
|
|
638
|
+
}
|
|
639
|
+
const issues = result.value.findings.map((f) => ({
|
|
640
|
+
file: `${f.file}:${f.line}`,
|
|
641
|
+
message: `[${f.ruleId}] ${f.severity.toUpperCase()} ${f.message}`
|
|
642
|
+
}));
|
|
643
|
+
const output = formatter.formatValidation({
|
|
644
|
+
valid: result.value.valid,
|
|
645
|
+
issues
|
|
646
|
+
});
|
|
647
|
+
if (output) {
|
|
648
|
+
console.log(output);
|
|
649
|
+
}
|
|
650
|
+
process.exit(result.value.valid ? ExitCode.SUCCESS : ExitCode.VALIDATION_FAILED);
|
|
651
|
+
});
|
|
652
|
+
return command;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
// src/commands/perf.ts
|
|
656
|
+
import { Command as Command5 } from "commander";
|
|
657
|
+
import * as path6 from "path";
|
|
658
|
+
function createPerfCommand() {
|
|
659
|
+
const perf = new Command5("perf").description("Performance benchmark and baseline management");
|
|
660
|
+
perf.command("bench [glob]").description("Run benchmarks via vitest bench").action(async (glob2, _opts, cmd) => {
|
|
661
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
662
|
+
const cwd = process.cwd();
|
|
663
|
+
const { BenchmarkRunner } = await import("./dist-RBZXXJHG.js");
|
|
664
|
+
const runner = new BenchmarkRunner();
|
|
665
|
+
const benchFiles = runner.discover(cwd, glob2);
|
|
666
|
+
if (benchFiles.length === 0) {
|
|
667
|
+
if (globalOpts.json) {
|
|
668
|
+
console.log(JSON.stringify({ benchFiles: [], message: "No .bench.ts files found" }));
|
|
669
|
+
} else {
|
|
670
|
+
logger.info("No .bench.ts files found. Create *.bench.ts files to add benchmarks.");
|
|
671
|
+
}
|
|
672
|
+
return;
|
|
673
|
+
}
|
|
674
|
+
if (globalOpts.json) {
|
|
675
|
+
logger.info(`Found ${benchFiles.length} benchmark file(s). Running...`);
|
|
676
|
+
} else {
|
|
677
|
+
logger.info(`Found ${benchFiles.length} benchmark file(s):`);
|
|
678
|
+
for (const f of benchFiles) {
|
|
679
|
+
logger.info(` ${f}`);
|
|
680
|
+
}
|
|
681
|
+
logger.info("Running benchmarks...");
|
|
682
|
+
}
|
|
683
|
+
const result = await runner.run(glob2 ? { cwd, glob: glob2 } : { cwd });
|
|
684
|
+
if (globalOpts.json) {
|
|
685
|
+
console.log(JSON.stringify({ results: result.results, success: result.success }));
|
|
686
|
+
} else {
|
|
687
|
+
if (result.success && result.results.length > 0) {
|
|
688
|
+
logger.info(`
|
|
689
|
+
Results (${result.results.length} benchmarks):`);
|
|
690
|
+
for (const r of result.results) {
|
|
691
|
+
logger.info(
|
|
692
|
+
` ${r.file}::${r.name}: ${r.opsPerSec} ops/s (mean: ${r.meanMs.toFixed(2)}ms)`
|
|
693
|
+
);
|
|
694
|
+
}
|
|
695
|
+
logger.info("\nTo save as baselines: harness perf baselines update");
|
|
696
|
+
} else {
|
|
697
|
+
logger.info("Benchmark run completed. Check output above for details.");
|
|
698
|
+
if (result.rawOutput) {
|
|
699
|
+
console.log(result.rawOutput);
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
});
|
|
704
|
+
const baselines = perf.command("baselines").description("Manage performance baselines");
|
|
705
|
+
baselines.command("show").description("Display current baselines").action(async (_opts, cmd) => {
|
|
706
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
707
|
+
const cwd = process.cwd();
|
|
708
|
+
const manager = new BaselineManager(cwd);
|
|
709
|
+
const data = manager.load();
|
|
710
|
+
if (!data) {
|
|
711
|
+
if (globalOpts.json) {
|
|
712
|
+
console.log(JSON.stringify({ baselines: null, message: "No baselines file found" }));
|
|
713
|
+
} else {
|
|
714
|
+
logger.info("No baselines file found at .harness/perf/baselines.json");
|
|
715
|
+
}
|
|
716
|
+
return;
|
|
717
|
+
}
|
|
718
|
+
if (globalOpts.json) {
|
|
719
|
+
console.log(JSON.stringify(data, null, 2));
|
|
720
|
+
} else {
|
|
721
|
+
logger.info(`Baselines (updated: ${data.updatedAt}, from: ${data.updatedFrom})`);
|
|
722
|
+
for (const [name, baseline] of Object.entries(data.benchmarks)) {
|
|
723
|
+
logger.info(
|
|
724
|
+
` ${name}: ${baseline.opsPerSec} ops/s (mean: ${baseline.meanMs}ms, p99: ${baseline.p99Ms}ms)`
|
|
725
|
+
);
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
});
|
|
729
|
+
baselines.command("update").description("Update baselines from latest benchmark run").action(async (_opts, cmd) => {
|
|
730
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
731
|
+
const cwd = process.cwd();
|
|
732
|
+
const { BenchmarkRunner } = await import("./dist-RBZXXJHG.js");
|
|
733
|
+
const runner = new BenchmarkRunner();
|
|
734
|
+
const manager = new BaselineManager(cwd);
|
|
735
|
+
logger.info("Running benchmarks to update baselines...");
|
|
736
|
+
const benchResult = await runner.run({ cwd });
|
|
737
|
+
if (!benchResult.success || benchResult.results.length === 0) {
|
|
738
|
+
logger.error(
|
|
739
|
+
"No benchmark results to save. Run `harness perf bench` first to verify benchmarks work."
|
|
740
|
+
);
|
|
741
|
+
return;
|
|
742
|
+
}
|
|
743
|
+
let commitHash = "unknown";
|
|
744
|
+
try {
|
|
745
|
+
const { execSync: execSync5 } = await import("child_process");
|
|
746
|
+
commitHash = execSync5("git rev-parse --short HEAD", { cwd, encoding: "utf-8" }).trim();
|
|
747
|
+
} catch {
|
|
748
|
+
}
|
|
749
|
+
manager.save(benchResult.results, commitHash);
|
|
750
|
+
if (globalOpts.json) {
|
|
751
|
+
console.log(JSON.stringify({ updated: benchResult.results.length, commitHash }));
|
|
752
|
+
} else {
|
|
753
|
+
logger.info(`Updated ${benchResult.results.length} baseline(s) from commit ${commitHash}`);
|
|
754
|
+
logger.info("Baselines saved to .harness/perf/baselines.json");
|
|
755
|
+
}
|
|
756
|
+
});
|
|
757
|
+
perf.command("report").description("Full performance report with metrics, trends, and hotspots").action(async (_opts, cmd) => {
|
|
758
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
759
|
+
const cwd = process.cwd();
|
|
760
|
+
const { EntropyAnalyzer: EntropyAnalyzer2 } = await import("./dist-RBZXXJHG.js");
|
|
761
|
+
const analyzer = new EntropyAnalyzer2({
|
|
762
|
+
rootDir: path6.resolve(cwd),
|
|
763
|
+
analyze: { complexity: true, coupling: true }
|
|
764
|
+
});
|
|
765
|
+
const result = await analyzer.analyze();
|
|
766
|
+
if (!result.ok) {
|
|
767
|
+
logger.error(result.error.message);
|
|
768
|
+
return;
|
|
769
|
+
}
|
|
770
|
+
const report = result.value;
|
|
771
|
+
if (globalOpts.json) {
|
|
772
|
+
console.log(
|
|
773
|
+
JSON.stringify(
|
|
774
|
+
{
|
|
775
|
+
complexity: report.complexity,
|
|
776
|
+
coupling: report.coupling,
|
|
777
|
+
sizeBudget: report.sizeBudget
|
|
778
|
+
},
|
|
779
|
+
null,
|
|
780
|
+
2
|
|
781
|
+
)
|
|
782
|
+
);
|
|
783
|
+
} else {
|
|
784
|
+
logger.info("=== Performance Report ===");
|
|
785
|
+
if (report.complexity) {
|
|
786
|
+
logger.info(
|
|
787
|
+
`Complexity: ${report.complexity.stats.violationCount} violations (${report.complexity.stats.errorCount} errors, ${report.complexity.stats.warningCount} warnings)`
|
|
788
|
+
);
|
|
789
|
+
}
|
|
790
|
+
if (report.coupling) {
|
|
791
|
+
logger.info(
|
|
792
|
+
`Coupling: ${report.coupling.stats.violationCount} violations (${report.coupling.stats.warningCount} warnings)`
|
|
793
|
+
);
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
});
|
|
797
|
+
perf.command("critical-paths").description("Show resolved critical path set (annotations + graph inference)").action(async (_opts, cmd) => {
|
|
798
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
799
|
+
const cwd = process.cwd();
|
|
800
|
+
const resolver = new CriticalPathResolver(cwd);
|
|
801
|
+
const result = await resolver.resolve();
|
|
802
|
+
if (globalOpts.json) {
|
|
803
|
+
console.log(JSON.stringify(result, null, 2));
|
|
804
|
+
} else {
|
|
805
|
+
logger.info(
|
|
806
|
+
`Critical paths: ${result.stats.total} (${result.stats.annotated} annotated, ${result.stats.graphInferred} graph-inferred)`
|
|
807
|
+
);
|
|
808
|
+
for (const entry of result.entries) {
|
|
809
|
+
logger.info(
|
|
810
|
+
` ${entry.file}::${entry.function} [${entry.source}]${entry.fanIn ? ` (fan-in: ${entry.fanIn})` : ""}`
|
|
811
|
+
);
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
});
|
|
815
|
+
return perf;
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
// src/commands/check-docs.ts
|
|
819
|
+
import { Command as Command6 } from "commander";
|
|
820
|
+
import * as path7 from "path";
|
|
408
821
|
async function runCheckDocs(options) {
|
|
409
822
|
const cwd = options.cwd ?? process.cwd();
|
|
410
823
|
const minCoverage = options.minCoverage ?? 80;
|
|
@@ -413,22 +826,22 @@ async function runCheckDocs(options) {
|
|
|
413
826
|
return configResult;
|
|
414
827
|
}
|
|
415
828
|
const config = configResult.value;
|
|
416
|
-
const docsDir =
|
|
417
|
-
const sourceDir =
|
|
829
|
+
const docsDir = path7.resolve(cwd, config.docsDir);
|
|
830
|
+
const sourceDir = path7.resolve(cwd, config.rootDir);
|
|
418
831
|
const coverageResult = await checkDocCoverage("project", {
|
|
419
832
|
docsDir,
|
|
420
833
|
sourceDir,
|
|
421
834
|
excludePatterns: ["**/*.test.ts", "**/*.spec.ts", "**/node_modules/**"]
|
|
422
835
|
});
|
|
423
836
|
if (!coverageResult.ok) {
|
|
424
|
-
return
|
|
837
|
+
return Err(
|
|
425
838
|
new CLIError(
|
|
426
839
|
`Documentation coverage check failed: ${coverageResult.error.message}`,
|
|
427
840
|
ExitCode.ERROR
|
|
428
841
|
)
|
|
429
842
|
);
|
|
430
843
|
}
|
|
431
|
-
const knowledgeResult = await
|
|
844
|
+
const knowledgeResult = await validateKnowledgeMap(cwd);
|
|
432
845
|
let brokenLinks = [];
|
|
433
846
|
if (knowledgeResult.ok) {
|
|
434
847
|
brokenLinks = knowledgeResult.value.brokenLinks.map((b) => b.path);
|
|
@@ -443,10 +856,10 @@ async function runCheckDocs(options) {
|
|
|
443
856
|
undocumented: coverageResult.value.undocumented,
|
|
444
857
|
brokenLinks
|
|
445
858
|
};
|
|
446
|
-
return
|
|
859
|
+
return Ok(result);
|
|
447
860
|
}
|
|
448
861
|
function createCheckDocsCommand() {
|
|
449
|
-
const command = new
|
|
862
|
+
const command = new Command6("check-docs").description("Check documentation coverage").option("--min-coverage <percent>", "Minimum coverage percentage", "80").action(async (opts, cmd) => {
|
|
450
863
|
const globalOpts = cmd.optsWithGlobals();
|
|
451
864
|
const mode = globalOpts.json ? OutputMode.JSON : globalOpts.quiet ? OutputMode.QUIET : globalOpts.verbose ? OutputMode.VERBOSE : OutputMode.TEXT;
|
|
452
865
|
const formatter = new OutputFormatter(mode);
|
|
@@ -498,17 +911,15 @@ function createCheckDocsCommand() {
|
|
|
498
911
|
}
|
|
499
912
|
|
|
500
913
|
// src/commands/init.ts
|
|
501
|
-
import { Command as
|
|
914
|
+
import { Command as Command8 } from "commander";
|
|
502
915
|
import chalk3 from "chalk";
|
|
503
916
|
import * as fs5 from "fs";
|
|
504
|
-
import * as
|
|
505
|
-
import { Ok as Ok6, Err as Err4 } from "@harness-engineering/core";
|
|
917
|
+
import * as path11 from "path";
|
|
506
918
|
|
|
507
919
|
// src/templates/engine.ts
|
|
508
920
|
import * as fs2 from "fs";
|
|
509
|
-
import * as
|
|
921
|
+
import * as path8 from "path";
|
|
510
922
|
import Handlebars from "handlebars";
|
|
511
|
-
import { Ok as Ok5, Err as Err3 } from "@harness-engineering/core";
|
|
512
923
|
|
|
513
924
|
// src/templates/schema.ts
|
|
514
925
|
import { z as z2 } from "zod";
|
|
@@ -576,15 +987,15 @@ var TemplateEngine = class {
|
|
|
576
987
|
const templates = [];
|
|
577
988
|
for (const entry of entries) {
|
|
578
989
|
if (!entry.isDirectory()) continue;
|
|
579
|
-
const metaPath =
|
|
990
|
+
const metaPath = path8.join(this.templatesDir, entry.name, "template.json");
|
|
580
991
|
if (!fs2.existsSync(metaPath)) continue;
|
|
581
992
|
const raw = JSON.parse(fs2.readFileSync(metaPath, "utf-8"));
|
|
582
993
|
const parsed = TemplateMetadataSchema.safeParse(raw);
|
|
583
994
|
if (parsed.success) templates.push(parsed.data);
|
|
584
995
|
}
|
|
585
|
-
return
|
|
996
|
+
return Ok(templates);
|
|
586
997
|
} catch (error) {
|
|
587
|
-
return
|
|
998
|
+
return Err(
|
|
588
999
|
new Error(
|
|
589
1000
|
`Failed to list templates: ${error instanceof Error ? error.message : String(error)}`
|
|
590
1001
|
)
|
|
@@ -593,16 +1004,16 @@ var TemplateEngine = class {
|
|
|
593
1004
|
}
|
|
594
1005
|
resolveTemplate(level, framework) {
|
|
595
1006
|
const levelDir = this.findTemplateDir(level, "level");
|
|
596
|
-
if (!levelDir) return
|
|
597
|
-
const metaPath =
|
|
1007
|
+
if (!levelDir) return Err(new Error(`Template not found for level: ${level}`));
|
|
1008
|
+
const metaPath = path8.join(levelDir, "template.json");
|
|
598
1009
|
const metaRaw = JSON.parse(fs2.readFileSync(metaPath, "utf-8"));
|
|
599
1010
|
const metaResult = TemplateMetadataSchema.safeParse(metaRaw);
|
|
600
1011
|
if (!metaResult.success)
|
|
601
|
-
return
|
|
1012
|
+
return Err(new Error(`Invalid template.json in ${level}: ${metaResult.error.message}`));
|
|
602
1013
|
const metadata = metaResult.data;
|
|
603
1014
|
let files = [];
|
|
604
1015
|
if (metadata.extends) {
|
|
605
|
-
const baseDir =
|
|
1016
|
+
const baseDir = path8.join(this.templatesDir, metadata.extends);
|
|
606
1017
|
if (fs2.existsSync(baseDir)) files = this.collectFiles(baseDir, metadata.extends);
|
|
607
1018
|
}
|
|
608
1019
|
const levelFiles = this.collectFiles(levelDir, level);
|
|
@@ -610,8 +1021,8 @@ var TemplateEngine = class {
|
|
|
610
1021
|
let overlayMetadata;
|
|
611
1022
|
if (framework) {
|
|
612
1023
|
const frameworkDir = this.findTemplateDir(framework, "framework");
|
|
613
|
-
if (!frameworkDir) return
|
|
614
|
-
const fMetaPath =
|
|
1024
|
+
if (!frameworkDir) return Err(new Error(`Framework template not found: ${framework}`));
|
|
1025
|
+
const fMetaPath = path8.join(frameworkDir, "template.json");
|
|
615
1026
|
const fMetaRaw = JSON.parse(fs2.readFileSync(fMetaPath, "utf-8"));
|
|
616
1027
|
const fMetaResult = TemplateMetadataSchema.safeParse(fMetaRaw);
|
|
617
1028
|
if (fMetaResult.success) overlayMetadata = fMetaResult.data;
|
|
@@ -621,7 +1032,7 @@ var TemplateEngine = class {
|
|
|
621
1032
|
files = files.filter((f) => f.relativePath !== "template.json");
|
|
622
1033
|
const resolved = { metadata, files };
|
|
623
1034
|
if (overlayMetadata !== void 0) resolved.overlayMetadata = overlayMetadata;
|
|
624
|
-
return
|
|
1035
|
+
return Ok(resolved);
|
|
625
1036
|
}
|
|
626
1037
|
render(template, context) {
|
|
627
1038
|
const rendered = [];
|
|
@@ -641,7 +1052,7 @@ var TemplateEngine = class {
|
|
|
641
1052
|
}
|
|
642
1053
|
} catch (error) {
|
|
643
1054
|
const msg = error instanceof Error ? error.message : String(error);
|
|
644
|
-
return
|
|
1055
|
+
return Err(
|
|
645
1056
|
new Error(
|
|
646
1057
|
`Template render failed in ${file.sourceTemplate}/${file.relativePath}: ${msg}`
|
|
647
1058
|
)
|
|
@@ -653,7 +1064,7 @@ var TemplateEngine = class {
|
|
|
653
1064
|
rendered.push({ relativePath: file.relativePath, content });
|
|
654
1065
|
} catch (error) {
|
|
655
1066
|
const msg = error instanceof Error ? error.message : String(error);
|
|
656
|
-
return
|
|
1067
|
+
return Err(
|
|
657
1068
|
new Error(
|
|
658
1069
|
`Template render failed in ${file.sourceTemplate}/${file.relativePath}: ${msg}`
|
|
659
1070
|
)
|
|
@@ -671,24 +1082,24 @@ var TemplateEngine = class {
|
|
|
671
1082
|
}
|
|
672
1083
|
} catch (error) {
|
|
673
1084
|
const msg = error instanceof Error ? error.message : String(error);
|
|
674
|
-
return
|
|
1085
|
+
return Err(new Error(`JSON merge failed: ${msg}`));
|
|
675
1086
|
}
|
|
676
|
-
return
|
|
1087
|
+
return Ok({ files: rendered });
|
|
677
1088
|
}
|
|
678
1089
|
write(files, targetDir, options) {
|
|
679
1090
|
try {
|
|
680
1091
|
const written = [];
|
|
681
1092
|
for (const file of files.files) {
|
|
682
|
-
const targetPath =
|
|
683
|
-
const dir =
|
|
1093
|
+
const targetPath = path8.join(targetDir, file.relativePath);
|
|
1094
|
+
const dir = path8.dirname(targetPath);
|
|
684
1095
|
if (!options.overwrite && fs2.existsSync(targetPath)) continue;
|
|
685
1096
|
fs2.mkdirSync(dir, { recursive: true });
|
|
686
1097
|
fs2.writeFileSync(targetPath, file.content);
|
|
687
1098
|
written.push(file.relativePath);
|
|
688
1099
|
}
|
|
689
|
-
return
|
|
1100
|
+
return Ok(written);
|
|
690
1101
|
} catch (error) {
|
|
691
|
-
return
|
|
1102
|
+
return Err(
|
|
692
1103
|
new Error(
|
|
693
1104
|
`Failed to write files: ${error instanceof Error ? error.message : String(error)}`
|
|
694
1105
|
)
|
|
@@ -699,16 +1110,16 @@ var TemplateEngine = class {
|
|
|
699
1110
|
const entries = fs2.readdirSync(this.templatesDir, { withFileTypes: true });
|
|
700
1111
|
for (const entry of entries) {
|
|
701
1112
|
if (!entry.isDirectory()) continue;
|
|
702
|
-
const metaPath =
|
|
1113
|
+
const metaPath = path8.join(this.templatesDir, entry.name, "template.json");
|
|
703
1114
|
if (!fs2.existsSync(metaPath)) continue;
|
|
704
1115
|
const raw = JSON.parse(fs2.readFileSync(metaPath, "utf-8"));
|
|
705
1116
|
const parsed = TemplateMetadataSchema.safeParse(raw);
|
|
706
1117
|
if (!parsed.success) continue;
|
|
707
1118
|
if (type === "level" && parsed.data.level === name)
|
|
708
|
-
return
|
|
1119
|
+
return path8.join(this.templatesDir, entry.name);
|
|
709
1120
|
if (type === "framework" && parsed.data.framework === name)
|
|
710
|
-
return
|
|
711
|
-
if (parsed.data.name === name) return
|
|
1121
|
+
return path8.join(this.templatesDir, entry.name);
|
|
1122
|
+
if (parsed.data.name === name) return path8.join(this.templatesDir, entry.name);
|
|
712
1123
|
}
|
|
713
1124
|
return null;
|
|
714
1125
|
}
|
|
@@ -717,12 +1128,12 @@ var TemplateEngine = class {
|
|
|
717
1128
|
const walk = (currentDir) => {
|
|
718
1129
|
const entries = fs2.readdirSync(currentDir, { withFileTypes: true });
|
|
719
1130
|
for (const entry of entries) {
|
|
720
|
-
const fullPath =
|
|
1131
|
+
const fullPath = path8.join(currentDir, entry.name);
|
|
721
1132
|
if (entry.isDirectory()) {
|
|
722
1133
|
walk(fullPath);
|
|
723
1134
|
} else {
|
|
724
1135
|
files.push({
|
|
725
|
-
relativePath:
|
|
1136
|
+
relativePath: path8.relative(dir, fullPath),
|
|
726
1137
|
absolutePath: fullPath,
|
|
727
1138
|
isHandlebars: entry.name.endsWith(".hbs"),
|
|
728
1139
|
sourceTemplate: sourceName
|
|
@@ -754,51 +1165,51 @@ var TemplateEngine = class {
|
|
|
754
1165
|
|
|
755
1166
|
// src/utils/paths.ts
|
|
756
1167
|
import * as fs3 from "fs";
|
|
757
|
-
import * as
|
|
1168
|
+
import * as path9 from "path";
|
|
758
1169
|
import { fileURLToPath } from "url";
|
|
759
1170
|
var __filename = fileURLToPath(import.meta.url);
|
|
760
|
-
var __dirname =
|
|
1171
|
+
var __dirname = path9.dirname(__filename);
|
|
761
1172
|
function findUpDir(targetName, marker, maxLevels = 8) {
|
|
762
1173
|
let dir = __dirname;
|
|
763
1174
|
for (let i = 0; i < maxLevels; i++) {
|
|
764
|
-
const candidate =
|
|
1175
|
+
const candidate = path9.join(dir, targetName);
|
|
765
1176
|
if (fs3.existsSync(candidate) && fs3.statSync(candidate).isDirectory()) {
|
|
766
|
-
if (fs3.existsSync(
|
|
1177
|
+
if (fs3.existsSync(path9.join(candidate, marker))) {
|
|
767
1178
|
return candidate;
|
|
768
1179
|
}
|
|
769
1180
|
}
|
|
770
|
-
dir =
|
|
1181
|
+
dir = path9.dirname(dir);
|
|
771
1182
|
}
|
|
772
1183
|
return null;
|
|
773
1184
|
}
|
|
774
1185
|
function resolveTemplatesDir() {
|
|
775
|
-
return findUpDir("templates", "base") ??
|
|
1186
|
+
return findUpDir("templates", "base") ?? path9.join(__dirname, "templates");
|
|
776
1187
|
}
|
|
777
1188
|
function resolvePersonasDir() {
|
|
778
1189
|
const agentsDir = findUpDir("agents", "personas");
|
|
779
1190
|
if (agentsDir) {
|
|
780
|
-
return
|
|
1191
|
+
return path9.join(agentsDir, "personas");
|
|
781
1192
|
}
|
|
782
|
-
return
|
|
1193
|
+
return path9.join(__dirname, "agents", "personas");
|
|
783
1194
|
}
|
|
784
1195
|
function resolveSkillsDir() {
|
|
785
1196
|
const agentsDir = findUpDir("agents", "skills");
|
|
786
1197
|
if (agentsDir) {
|
|
787
|
-
return
|
|
1198
|
+
return path9.join(agentsDir, "skills", "claude-code");
|
|
788
1199
|
}
|
|
789
|
-
return
|
|
1200
|
+
return path9.join(__dirname, "agents", "skills", "claude-code");
|
|
790
1201
|
}
|
|
791
1202
|
function resolveProjectSkillsDir(cwd) {
|
|
792
1203
|
let dir = cwd ?? process.cwd();
|
|
793
1204
|
for (let i = 0; i < 8; i++) {
|
|
794
|
-
const candidate =
|
|
1205
|
+
const candidate = path9.join(dir, "agents", "skills", "claude-code");
|
|
795
1206
|
if (fs3.existsSync(candidate) && fs3.statSync(candidate).isDirectory()) {
|
|
796
|
-
const agentsDir =
|
|
797
|
-
if (fs3.existsSync(
|
|
1207
|
+
const agentsDir = path9.join(dir, "agents");
|
|
1208
|
+
if (fs3.existsSync(path9.join(agentsDir, "skills"))) {
|
|
798
1209
|
return candidate;
|
|
799
1210
|
}
|
|
800
1211
|
}
|
|
801
|
-
const parent =
|
|
1212
|
+
const parent = path9.dirname(dir);
|
|
802
1213
|
if (parent === dir) break;
|
|
803
1214
|
dir = parent;
|
|
804
1215
|
}
|
|
@@ -807,15 +1218,15 @@ function resolveProjectSkillsDir(cwd) {
|
|
|
807
1218
|
function resolveGlobalSkillsDir() {
|
|
808
1219
|
const agentsDir = findUpDir("agents", "skills");
|
|
809
1220
|
if (agentsDir) {
|
|
810
|
-
return
|
|
1221
|
+
return path9.join(agentsDir, "skills", "claude-code");
|
|
811
1222
|
}
|
|
812
|
-
return
|
|
1223
|
+
return path9.join(__dirname, "agents", "skills", "claude-code");
|
|
813
1224
|
}
|
|
814
1225
|
|
|
815
1226
|
// src/commands/setup-mcp.ts
|
|
816
|
-
import { Command as
|
|
1227
|
+
import { Command as Command7 } from "commander";
|
|
817
1228
|
import * as fs4 from "fs";
|
|
818
|
-
import * as
|
|
1229
|
+
import * as path10 from "path";
|
|
819
1230
|
import * as os from "os";
|
|
820
1231
|
import chalk2 from "chalk";
|
|
821
1232
|
var HARNESS_MCP_ENTRY = {
|
|
@@ -832,7 +1243,7 @@ function readJsonFile(filePath) {
|
|
|
832
1243
|
}
|
|
833
1244
|
}
|
|
834
1245
|
function writeJsonFile(filePath, data) {
|
|
835
|
-
const dir =
|
|
1246
|
+
const dir = path10.dirname(filePath);
|
|
836
1247
|
if (!fs4.existsSync(dir)) {
|
|
837
1248
|
fs4.mkdirSync(dir, { recursive: true });
|
|
838
1249
|
}
|
|
@@ -851,7 +1262,7 @@ function configureMcpServer(configPath) {
|
|
|
851
1262
|
return true;
|
|
852
1263
|
}
|
|
853
1264
|
function addGeminiTrustedFolder(cwd) {
|
|
854
|
-
const trustedPath =
|
|
1265
|
+
const trustedPath = path10.join(os.homedir(), ".gemini", "trustedFolders.json");
|
|
855
1266
|
const folders = readJsonFile(trustedPath) ?? {};
|
|
856
1267
|
if (folders[cwd] === "TRUST_FOLDER") {
|
|
857
1268
|
return false;
|
|
@@ -865,7 +1276,7 @@ function setupMcp(cwd, client) {
|
|
|
865
1276
|
const skipped = [];
|
|
866
1277
|
let trustedFolder = false;
|
|
867
1278
|
if (client === "all" || client === "claude") {
|
|
868
|
-
const configPath =
|
|
1279
|
+
const configPath = path10.join(cwd, ".mcp.json");
|
|
869
1280
|
if (configureMcpServer(configPath)) {
|
|
870
1281
|
configured.push("Claude Code");
|
|
871
1282
|
} else {
|
|
@@ -873,7 +1284,7 @@ function setupMcp(cwd, client) {
|
|
|
873
1284
|
}
|
|
874
1285
|
}
|
|
875
1286
|
if (client === "all" || client === "gemini") {
|
|
876
|
-
const configPath =
|
|
1287
|
+
const configPath = path10.join(cwd, ".gemini", "settings.json");
|
|
877
1288
|
if (configureMcpServer(configPath)) {
|
|
878
1289
|
configured.push("Gemini CLI");
|
|
879
1290
|
} else {
|
|
@@ -884,7 +1295,7 @@ function setupMcp(cwd, client) {
|
|
|
884
1295
|
return { configured, skipped, trustedFolder };
|
|
885
1296
|
}
|
|
886
1297
|
function createSetupMcpCommand() {
|
|
887
|
-
return new
|
|
1298
|
+
return new Command7("setup-mcp").description("Configure MCP server for AI agent integration").option("--client <client>", "Client to configure (claude, gemini, all)", "all").action(async (opts, cmd) => {
|
|
888
1299
|
const globalOpts = cmd.optsWithGlobals();
|
|
889
1300
|
const cwd = process.cwd();
|
|
890
1301
|
const { configured, skipped, trustedFolder } = setupMcp(cwd, opts.client);
|
|
@@ -910,8 +1321,12 @@ function createSetupMcpCommand() {
|
|
|
910
1321
|
}
|
|
911
1322
|
console.log("");
|
|
912
1323
|
console.log(chalk2.bold("The harness MCP server provides:"));
|
|
913
|
-
console.log(
|
|
914
|
-
|
|
1324
|
+
console.log(
|
|
1325
|
+
" - 31 tools for validation, entropy detection, skill execution, graph querying, and more"
|
|
1326
|
+
);
|
|
1327
|
+
console.log(
|
|
1328
|
+
" - 8 resources for project context, skills, rules, learnings, state, and graph data"
|
|
1329
|
+
);
|
|
915
1330
|
console.log("");
|
|
916
1331
|
console.log(`Run ${chalk2.cyan("harness skill list")} to see available skills.`);
|
|
917
1332
|
console.log("");
|
|
@@ -923,12 +1338,12 @@ function createSetupMcpCommand() {
|
|
|
923
1338
|
// src/commands/init.ts
|
|
924
1339
|
async function runInit(options) {
|
|
925
1340
|
const cwd = options.cwd ?? process.cwd();
|
|
926
|
-
const name = options.name ??
|
|
1341
|
+
const name = options.name ?? path11.basename(cwd);
|
|
927
1342
|
const level = options.level ?? "basic";
|
|
928
1343
|
const force = options.force ?? false;
|
|
929
|
-
const configPath =
|
|
1344
|
+
const configPath = path11.join(cwd, "harness.config.json");
|
|
930
1345
|
if (!force && fs5.existsSync(configPath)) {
|
|
931
|
-
return
|
|
1346
|
+
return Err(
|
|
932
1347
|
new CLIError("Project already initialized. Use --force to overwrite.", ExitCode.ERROR)
|
|
933
1348
|
);
|
|
934
1349
|
}
|
|
@@ -936,7 +1351,7 @@ async function runInit(options) {
|
|
|
936
1351
|
const engine = new TemplateEngine(templatesDir);
|
|
937
1352
|
const resolveResult = engine.resolveTemplate(level, options.framework);
|
|
938
1353
|
if (!resolveResult.ok) {
|
|
939
|
-
return
|
|
1354
|
+
return Err(new CLIError(resolveResult.error.message, ExitCode.ERROR));
|
|
940
1355
|
}
|
|
941
1356
|
const renderResult = engine.render(resolveResult.value, {
|
|
942
1357
|
projectName: name,
|
|
@@ -944,16 +1359,16 @@ async function runInit(options) {
|
|
|
944
1359
|
...options.framework !== void 0 && { framework: options.framework }
|
|
945
1360
|
});
|
|
946
1361
|
if (!renderResult.ok) {
|
|
947
|
-
return
|
|
1362
|
+
return Err(new CLIError(renderResult.error.message, ExitCode.ERROR));
|
|
948
1363
|
}
|
|
949
1364
|
const writeResult = engine.write(renderResult.value, cwd, { overwrite: force });
|
|
950
1365
|
if (!writeResult.ok) {
|
|
951
|
-
return
|
|
1366
|
+
return Err(new CLIError(writeResult.error.message, ExitCode.ERROR));
|
|
952
1367
|
}
|
|
953
|
-
return
|
|
1368
|
+
return Ok({ filesCreated: writeResult.value });
|
|
954
1369
|
}
|
|
955
1370
|
function createInitCommand() {
|
|
956
|
-
const command = new
|
|
1371
|
+
const command = new Command8("init").description("Initialize a new harness-engineering project").option("-n, --name <name>", "Project name").option("-l, --level <level>", "Adoption level (basic, intermediate, advanced)", "basic").option("--framework <framework>", "Framework overlay (nextjs)").option("-f, --force", "Overwrite existing files").option("-y, --yes", "Use defaults without prompting").action(async (opts, cmd) => {
|
|
957
1372
|
const globalOpts = cmd.optsWithGlobals();
|
|
958
1373
|
const result = await runInit({
|
|
959
1374
|
name: opts.name,
|
|
@@ -995,15 +1410,14 @@ function createInitCommand() {
|
|
|
995
1410
|
}
|
|
996
1411
|
|
|
997
1412
|
// src/commands/cleanup.ts
|
|
998
|
-
import { Command as
|
|
999
|
-
import * as
|
|
1000
|
-
import { Ok as Ok7, Err as Err5, EntropyAnalyzer } from "@harness-engineering/core";
|
|
1413
|
+
import { Command as Command9 } from "commander";
|
|
1414
|
+
import * as path12 from "path";
|
|
1001
1415
|
async function runCleanup(options) {
|
|
1002
1416
|
const cwd = options.cwd ?? process.cwd();
|
|
1003
1417
|
const type = options.type ?? "all";
|
|
1004
1418
|
const configResult = resolveConfig(options.configPath);
|
|
1005
1419
|
if (!configResult.ok) {
|
|
1006
|
-
return
|
|
1420
|
+
return Err(configResult.error);
|
|
1007
1421
|
}
|
|
1008
1422
|
const config = configResult.value;
|
|
1009
1423
|
const result = {
|
|
@@ -1012,11 +1426,11 @@ async function runCleanup(options) {
|
|
|
1012
1426
|
patternViolations: [],
|
|
1013
1427
|
totalIssues: 0
|
|
1014
1428
|
};
|
|
1015
|
-
const rootDir =
|
|
1016
|
-
const docsDir =
|
|
1429
|
+
const rootDir = path12.resolve(cwd, config.rootDir);
|
|
1430
|
+
const docsDir = path12.resolve(cwd, config.docsDir);
|
|
1017
1431
|
const entropyConfig = {
|
|
1018
1432
|
rootDir,
|
|
1019
|
-
entryPoints: [
|
|
1433
|
+
entryPoints: [path12.join(rootDir, "src/index.ts")],
|
|
1020
1434
|
docPaths: [docsDir],
|
|
1021
1435
|
analyze: {
|
|
1022
1436
|
drift: type === "all" || type === "drift",
|
|
@@ -1028,7 +1442,7 @@ async function runCleanup(options) {
|
|
|
1028
1442
|
const analyzer = new EntropyAnalyzer(entropyConfig);
|
|
1029
1443
|
const analysisResult = await analyzer.analyze();
|
|
1030
1444
|
if (!analysisResult.ok) {
|
|
1031
|
-
return
|
|
1445
|
+
return Err(
|
|
1032
1446
|
new CLIError(`Entropy analysis failed: ${analysisResult.error.message}`, ExitCode.ERROR)
|
|
1033
1447
|
);
|
|
1034
1448
|
}
|
|
@@ -1053,10 +1467,10 @@ async function runCleanup(options) {
|
|
|
1053
1467
|
}));
|
|
1054
1468
|
}
|
|
1055
1469
|
result.totalIssues = result.driftIssues.length + result.deadCode.length + result.patternViolations.length;
|
|
1056
|
-
return
|
|
1470
|
+
return Ok(result);
|
|
1057
1471
|
}
|
|
1058
1472
|
function createCleanupCommand() {
|
|
1059
|
-
const command = new
|
|
1473
|
+
const command = new Command9("cleanup").description("Detect entropy issues (doc drift, dead code, patterns)").option("-t, --type <type>", "Issue type: drift, dead-code, patterns, all", "all").action(async (opts, cmd) => {
|
|
1060
1474
|
const globalOpts = cmd.optsWithGlobals();
|
|
1061
1475
|
const mode = globalOpts.json ? OutputMode.JSON : globalOpts.quiet ? OutputMode.QUIET : globalOpts.verbose ? OutputMode.VERBOSE : OutputMode.TEXT;
|
|
1062
1476
|
const formatter = new OutputFormatter(mode);
|
|
@@ -1116,31 +1530,21 @@ function createCleanupCommand() {
|
|
|
1116
1530
|
}
|
|
1117
1531
|
|
|
1118
1532
|
// src/commands/fix-drift.ts
|
|
1119
|
-
import { Command as
|
|
1120
|
-
import * as
|
|
1121
|
-
import {
|
|
1122
|
-
Ok as Ok8,
|
|
1123
|
-
Err as Err6,
|
|
1124
|
-
buildSnapshot,
|
|
1125
|
-
detectDocDrift,
|
|
1126
|
-
detectDeadCode,
|
|
1127
|
-
createFixes,
|
|
1128
|
-
applyFixes,
|
|
1129
|
-
generateSuggestions
|
|
1130
|
-
} from "@harness-engineering/core";
|
|
1533
|
+
import { Command as Command10 } from "commander";
|
|
1534
|
+
import * as path13 from "path";
|
|
1131
1535
|
async function runFixDrift(options) {
|
|
1132
1536
|
const cwd = options.cwd ?? process.cwd();
|
|
1133
1537
|
const dryRun = options.dryRun !== false;
|
|
1134
1538
|
const configResult = resolveConfig(options.configPath);
|
|
1135
1539
|
if (!configResult.ok) {
|
|
1136
|
-
return
|
|
1540
|
+
return Err(configResult.error);
|
|
1137
1541
|
}
|
|
1138
1542
|
const config = configResult.value;
|
|
1139
|
-
const rootDir =
|
|
1140
|
-
const docsDir =
|
|
1543
|
+
const rootDir = path13.resolve(cwd, config.rootDir);
|
|
1544
|
+
const docsDir = path13.resolve(cwd, config.docsDir);
|
|
1141
1545
|
const entropyConfig = {
|
|
1142
1546
|
rootDir,
|
|
1143
|
-
entryPoints: [
|
|
1547
|
+
entryPoints: [path13.join(rootDir, "src/index.ts")],
|
|
1144
1548
|
docPaths: [docsDir],
|
|
1145
1549
|
analyze: {
|
|
1146
1550
|
drift: true,
|
|
@@ -1151,21 +1555,21 @@ async function runFixDrift(options) {
|
|
|
1151
1555
|
};
|
|
1152
1556
|
const snapshotResult = await buildSnapshot(entropyConfig);
|
|
1153
1557
|
if (!snapshotResult.ok) {
|
|
1154
|
-
return
|
|
1558
|
+
return Err(
|
|
1155
1559
|
new CLIError(`Failed to build snapshot: ${snapshotResult.error.message}`, ExitCode.ERROR)
|
|
1156
1560
|
);
|
|
1157
1561
|
}
|
|
1158
1562
|
const snapshot = snapshotResult.value;
|
|
1159
1563
|
const driftResult = await detectDocDrift(snapshot);
|
|
1160
1564
|
if (!driftResult.ok) {
|
|
1161
|
-
return
|
|
1565
|
+
return Err(
|
|
1162
1566
|
new CLIError(`Failed to detect drift: ${driftResult.error.message}`, ExitCode.ERROR)
|
|
1163
1567
|
);
|
|
1164
1568
|
}
|
|
1165
1569
|
const driftReport = driftResult.value;
|
|
1166
1570
|
const deadCodeResult = await detectDeadCode(snapshot);
|
|
1167
1571
|
if (!deadCodeResult.ok) {
|
|
1168
|
-
return
|
|
1572
|
+
return Err(
|
|
1169
1573
|
new CLIError(`Failed to detect dead code: ${deadCodeResult.error.message}`, ExitCode.ERROR)
|
|
1170
1574
|
);
|
|
1171
1575
|
}
|
|
@@ -1175,7 +1579,7 @@ async function runFixDrift(options) {
|
|
|
1175
1579
|
if (!dryRun && fixes.length > 0) {
|
|
1176
1580
|
const applyResult = await applyFixes(fixes, { dryRun: false });
|
|
1177
1581
|
if (!applyResult.ok) {
|
|
1178
|
-
return
|
|
1582
|
+
return Err(
|
|
1179
1583
|
new CLIError(`Failed to apply fixes: ${applyResult.error.message}`, ExitCode.ERROR)
|
|
1180
1584
|
);
|
|
1181
1585
|
}
|
|
@@ -1224,10 +1628,10 @@ async function runFixDrift(options) {
|
|
|
1224
1628
|
fixes: appliedFixes,
|
|
1225
1629
|
suggestions
|
|
1226
1630
|
};
|
|
1227
|
-
return
|
|
1631
|
+
return Ok(result);
|
|
1228
1632
|
}
|
|
1229
1633
|
function createFixDriftCommand() {
|
|
1230
|
-
const command = new
|
|
1634
|
+
const command = new Command10("fix-drift").description("Auto-fix entropy issues (doc drift, dead code)").option("--no-dry-run", "Actually apply fixes (default is dry-run mode)").action(async (opts, cmd) => {
|
|
1231
1635
|
const globalOpts = cmd.optsWithGlobals();
|
|
1232
1636
|
const mode = globalOpts.json ? OutputMode.JSON : globalOpts.quiet ? OutputMode.QUIET : globalOpts.verbose ? OutputMode.VERBOSE : OutputMode.TEXT;
|
|
1233
1637
|
const formatter = new OutputFormatter(mode);
|
|
@@ -1287,20 +1691,17 @@ function createFixDriftCommand() {
|
|
|
1287
1691
|
}
|
|
1288
1692
|
|
|
1289
1693
|
// src/commands/agent/index.ts
|
|
1290
|
-
import { Command as
|
|
1694
|
+
import { Command as Command13 } from "commander";
|
|
1291
1695
|
|
|
1292
1696
|
// src/commands/agent/run.ts
|
|
1293
|
-
import { Command as
|
|
1294
|
-
import * as
|
|
1697
|
+
import { Command as Command11 } from "commander";
|
|
1698
|
+
import * as path17 from "path";
|
|
1295
1699
|
import * as childProcess from "child_process";
|
|
1296
|
-
import { Ok as Ok10, Err as Err8 } from "@harness-engineering/core";
|
|
1297
|
-
import { requestPeerReview } from "@harness-engineering/core";
|
|
1298
1700
|
|
|
1299
1701
|
// src/persona/loader.ts
|
|
1300
1702
|
import * as fs6 from "fs";
|
|
1301
|
-
import * as
|
|
1703
|
+
import * as path14 from "path";
|
|
1302
1704
|
import YAML from "yaml";
|
|
1303
|
-
import { Ok as Ok9, Err as Err7 } from "@harness-engineering/core";
|
|
1304
1705
|
|
|
1305
1706
|
// src/persona/schema.ts
|
|
1306
1707
|
import { z as z3 } from "zod";
|
|
@@ -1386,36 +1787,36 @@ function normalizePersona(raw) {
|
|
|
1386
1787
|
function loadPersona(filePath) {
|
|
1387
1788
|
try {
|
|
1388
1789
|
if (!fs6.existsSync(filePath)) {
|
|
1389
|
-
return
|
|
1790
|
+
return Err(new Error(`Persona file not found: ${filePath}`));
|
|
1390
1791
|
}
|
|
1391
1792
|
const raw = fs6.readFileSync(filePath, "utf-8");
|
|
1392
1793
|
const parsed = YAML.parse(raw);
|
|
1393
1794
|
const result = PersonaSchema.safeParse(parsed);
|
|
1394
1795
|
if (!result.success) {
|
|
1395
|
-
return
|
|
1796
|
+
return Err(new Error(`Invalid persona ${filePath}: ${result.error.message}`));
|
|
1396
1797
|
}
|
|
1397
|
-
return
|
|
1798
|
+
return Ok(normalizePersona(result.data));
|
|
1398
1799
|
} catch (error) {
|
|
1399
|
-
return
|
|
1800
|
+
return Err(
|
|
1400
1801
|
new Error(`Failed to load persona: ${error instanceof Error ? error.message : String(error)}`)
|
|
1401
1802
|
);
|
|
1402
1803
|
}
|
|
1403
1804
|
}
|
|
1404
1805
|
function listPersonas(dir) {
|
|
1405
1806
|
try {
|
|
1406
|
-
if (!fs6.existsSync(dir)) return
|
|
1807
|
+
if (!fs6.existsSync(dir)) return Ok([]);
|
|
1407
1808
|
const entries = fs6.readdirSync(dir).filter((f) => f.endsWith(".yaml") || f.endsWith(".yml"));
|
|
1408
1809
|
const personas = [];
|
|
1409
1810
|
for (const entry of entries) {
|
|
1410
|
-
const filePath =
|
|
1811
|
+
const filePath = path14.join(dir, entry);
|
|
1411
1812
|
const result = loadPersona(filePath);
|
|
1412
1813
|
if (result.ok) {
|
|
1413
1814
|
personas.push({ name: result.value.name, description: result.value.description, filePath });
|
|
1414
1815
|
}
|
|
1415
1816
|
}
|
|
1416
|
-
return
|
|
1817
|
+
return Ok(personas);
|
|
1417
1818
|
} catch (error) {
|
|
1418
|
-
return
|
|
1819
|
+
return Err(
|
|
1419
1820
|
new Error(
|
|
1420
1821
|
`Failed to list personas: ${error instanceof Error ? error.message : String(error)}`
|
|
1421
1822
|
)
|
|
@@ -1425,9 +1826,9 @@ function listPersonas(dir) {
|
|
|
1425
1826
|
|
|
1426
1827
|
// src/persona/trigger-detector.ts
|
|
1427
1828
|
import * as fs7 from "fs";
|
|
1428
|
-
import * as
|
|
1829
|
+
import * as path15 from "path";
|
|
1429
1830
|
function detectTrigger(projectPath) {
|
|
1430
|
-
const handoffPath =
|
|
1831
|
+
const handoffPath = path15.join(projectPath, ".harness", "handoff.json");
|
|
1431
1832
|
if (!fs7.existsSync(handoffPath)) {
|
|
1432
1833
|
return { trigger: "manual" };
|
|
1433
1834
|
}
|
|
@@ -1506,8 +1907,8 @@ async function runPersona(persona, context) {
|
|
|
1506
1907
|
const result = await Promise.race([
|
|
1507
1908
|
context.commandExecutor(step.command),
|
|
1508
1909
|
new Promise(
|
|
1509
|
-
(
|
|
1510
|
-
() =>
|
|
1910
|
+
(resolve24) => setTimeout(
|
|
1911
|
+
() => resolve24({
|
|
1511
1912
|
ok: false,
|
|
1512
1913
|
error: new Error(TIMEOUT_ERROR_MESSAGE)
|
|
1513
1914
|
}),
|
|
@@ -1562,7 +1963,7 @@ async function runPersona(persona, context) {
|
|
|
1562
1963
|
const result = await Promise.race([
|
|
1563
1964
|
context.skillExecutor(step.skill, skillContext),
|
|
1564
1965
|
new Promise(
|
|
1565
|
-
(
|
|
1966
|
+
(resolve24) => setTimeout(() => resolve24(SKILL_TIMEOUT_RESULT), remainingTime)
|
|
1566
1967
|
)
|
|
1567
1968
|
]);
|
|
1568
1969
|
const durationMs = Date.now() - stepStart;
|
|
@@ -1606,7 +2007,7 @@ async function runPersona(persona, context) {
|
|
|
1606
2007
|
|
|
1607
2008
|
// src/persona/skill-executor.ts
|
|
1608
2009
|
import * as fs8 from "fs";
|
|
1609
|
-
import * as
|
|
2010
|
+
import * as path16 from "path";
|
|
1610
2011
|
import { parse as parse2 } from "yaml";
|
|
1611
2012
|
function resolveOutputMode(mode, trigger) {
|
|
1612
2013
|
if (mode !== "auto") return mode;
|
|
@@ -1615,7 +2016,7 @@ function resolveOutputMode(mode, trigger) {
|
|
|
1615
2016
|
function buildArtifactPath(projectPath, headSha) {
|
|
1616
2017
|
const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
1617
2018
|
const sha = headSha?.slice(0, 7) ?? "unknown";
|
|
1618
|
-
return
|
|
2019
|
+
return path16.join(projectPath, ".harness", "reviews", `${date}-${sha}.md`);
|
|
1619
2020
|
}
|
|
1620
2021
|
function buildArtifactContent(skillName, trigger, headSha) {
|
|
1621
2022
|
const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
@@ -1661,7 +2062,7 @@ function buildArtifactContent(skillName, trigger, headSha) {
|
|
|
1661
2062
|
async function executeSkill(skillName, context) {
|
|
1662
2063
|
const startTime = Date.now();
|
|
1663
2064
|
const skillsDir = resolveSkillsDir();
|
|
1664
|
-
const skillDir =
|
|
2065
|
+
const skillDir = path16.join(skillsDir, skillName);
|
|
1665
2066
|
if (!fs8.existsSync(skillDir)) {
|
|
1666
2067
|
return {
|
|
1667
2068
|
status: "fail",
|
|
@@ -1669,7 +2070,7 @@ async function executeSkill(skillName, context) {
|
|
|
1669
2070
|
durationMs: Date.now() - startTime
|
|
1670
2071
|
};
|
|
1671
2072
|
}
|
|
1672
|
-
const yamlPath =
|
|
2073
|
+
const yamlPath = path16.join(skillDir, "skill.yaml");
|
|
1673
2074
|
if (!fs8.existsSync(yamlPath)) {
|
|
1674
2075
|
return {
|
|
1675
2076
|
status: "fail",
|
|
@@ -1687,7 +2088,7 @@ async function executeSkill(skillName, context) {
|
|
|
1687
2088
|
durationMs: Date.now() - startTime
|
|
1688
2089
|
};
|
|
1689
2090
|
}
|
|
1690
|
-
const skillMdPath =
|
|
2091
|
+
const skillMdPath = path16.join(skillDir, "SKILL.md");
|
|
1691
2092
|
if (!fs8.existsSync(skillMdPath)) {
|
|
1692
2093
|
return {
|
|
1693
2094
|
status: "fail",
|
|
@@ -1707,7 +2108,7 @@ Trigger: ${context.trigger}
|
|
|
1707
2108
|
if (resolvedMode === "artifact") {
|
|
1708
2109
|
artifactPath = buildArtifactPath(context.projectPath, context.headSha);
|
|
1709
2110
|
const artifactContent = buildArtifactContent(skillName, context.trigger, context.headSha);
|
|
1710
|
-
const dir =
|
|
2111
|
+
const dir = path16.dirname(artifactPath);
|
|
1711
2112
|
fs8.mkdirSync(dir, { recursive: true });
|
|
1712
2113
|
fs8.writeFileSync(artifactPath, artifactContent, "utf-8");
|
|
1713
2114
|
}
|
|
@@ -1724,6 +2125,8 @@ var ALLOWED_PERSONA_COMMANDS = /* @__PURE__ */ new Set([
|
|
|
1724
2125
|
"validate",
|
|
1725
2126
|
"check-deps",
|
|
1726
2127
|
"check-docs",
|
|
2128
|
+
"check-perf",
|
|
2129
|
+
"check-security",
|
|
1727
2130
|
"cleanup",
|
|
1728
2131
|
"fix-drift",
|
|
1729
2132
|
"add"
|
|
@@ -1742,7 +2145,7 @@ async function runAgentTask(task, options) {
|
|
|
1742
2145
|
};
|
|
1743
2146
|
const agentType = agentTypeMap[task];
|
|
1744
2147
|
if (!agentType) {
|
|
1745
|
-
return
|
|
2148
|
+
return Err(
|
|
1746
2149
|
new CLIError(
|
|
1747
2150
|
`Unknown task: ${task}. Available: ${Object.keys(agentTypeMap).join(", ")}`,
|
|
1748
2151
|
ExitCode.ERROR
|
|
@@ -1762,10 +2165,10 @@ async function runAgentTask(task, options) {
|
|
|
1762
2165
|
{ timeout }
|
|
1763
2166
|
);
|
|
1764
2167
|
if (!reviewResult.ok) {
|
|
1765
|
-
return
|
|
2168
|
+
return Err(new CLIError(`Agent task failed: ${reviewResult.error.message}`, ExitCode.ERROR));
|
|
1766
2169
|
}
|
|
1767
2170
|
const review = reviewResult.value;
|
|
1768
|
-
return
|
|
2171
|
+
return Ok({
|
|
1769
2172
|
success: review.approved,
|
|
1770
2173
|
output: review.approved ? `Agent task '${task}' completed successfully` : `Agent task '${task}' found issues:
|
|
1771
2174
|
${review.comments.map((c) => ` - ${c.message}`).join("\n")}`
|
|
@@ -1782,11 +2185,11 @@ var VALID_TRIGGERS = /* @__PURE__ */ new Set([
|
|
|
1782
2185
|
"auto"
|
|
1783
2186
|
]);
|
|
1784
2187
|
function createRunCommand() {
|
|
1785
|
-
return new
|
|
2188
|
+
return new Command11("run").description("Run an agent task").argument("[task]", "Task to run (review, doc-review, test-review)").option("--timeout <ms>", "Timeout in milliseconds", "300000").option("--persona <name>", "Run a persona by name").option("--trigger <context>", "Trigger context (auto, on_pr, on_commit, manual)", "auto").action(async (task, opts, cmd) => {
|
|
1786
2189
|
const globalOpts = cmd.optsWithGlobals();
|
|
1787
2190
|
if (opts.persona) {
|
|
1788
2191
|
const personasDir = resolvePersonasDir();
|
|
1789
|
-
const filePath =
|
|
2192
|
+
const filePath = path17.join(personasDir, `${opts.persona}.yaml`);
|
|
1790
2193
|
const personaResult = loadPersona(filePath);
|
|
1791
2194
|
if (!personaResult.ok) {
|
|
1792
2195
|
logger.error(personaResult.error.message);
|
|
@@ -1797,13 +2200,13 @@ function createRunCommand() {
|
|
|
1797
2200
|
const trigger = opts.trigger === "auto" ? "auto" : VALID_TRIGGERS.has(opts.trigger) ? opts.trigger : "manual";
|
|
1798
2201
|
const commandExecutor = async (command) => {
|
|
1799
2202
|
if (!ALLOWED_PERSONA_COMMANDS.has(command)) {
|
|
1800
|
-
return
|
|
2203
|
+
return Err(new Error(`Unknown harness command: ${command}`));
|
|
1801
2204
|
}
|
|
1802
2205
|
try {
|
|
1803
2206
|
childProcess.execFileSync("npx", ["harness", command], { stdio: "inherit" });
|
|
1804
|
-
return
|
|
2207
|
+
return Ok(null);
|
|
1805
2208
|
} catch (error) {
|
|
1806
|
-
return
|
|
2209
|
+
return Err(new Error(error instanceof Error ? error.message : String(error)));
|
|
1807
2210
|
}
|
|
1808
2211
|
};
|
|
1809
2212
|
const report = await runPersona(persona, {
|
|
@@ -1843,10 +2246,8 @@ function createRunCommand() {
|
|
|
1843
2246
|
}
|
|
1844
2247
|
|
|
1845
2248
|
// src/commands/agent/review.ts
|
|
1846
|
-
import { Command as
|
|
1847
|
-
import { execSync } from "child_process";
|
|
1848
|
-
import { Ok as Ok11, Err as Err9 } from "@harness-engineering/core";
|
|
1849
|
-
import { createSelfReview, parseDiff } from "@harness-engineering/core";
|
|
2249
|
+
import { Command as Command12 } from "commander";
|
|
2250
|
+
import { execSync as execSync2 } from "child_process";
|
|
1850
2251
|
async function runAgentReview(options) {
|
|
1851
2252
|
const configResult = resolveConfig(options.configPath);
|
|
1852
2253
|
if (!configResult.ok) {
|
|
@@ -1855,22 +2256,22 @@ async function runAgentReview(options) {
|
|
|
1855
2256
|
const config = configResult.value;
|
|
1856
2257
|
let diff;
|
|
1857
2258
|
try {
|
|
1858
|
-
diff =
|
|
2259
|
+
diff = execSync2("git diff --cached", { encoding: "utf-8" });
|
|
1859
2260
|
if (!diff) {
|
|
1860
|
-
diff =
|
|
2261
|
+
diff = execSync2("git diff", { encoding: "utf-8" });
|
|
1861
2262
|
}
|
|
1862
2263
|
} catch {
|
|
1863
|
-
return
|
|
2264
|
+
return Err(new CLIError("Failed to get git diff", ExitCode.ERROR));
|
|
1864
2265
|
}
|
|
1865
2266
|
if (!diff) {
|
|
1866
|
-
return
|
|
2267
|
+
return Ok({
|
|
1867
2268
|
passed: true,
|
|
1868
2269
|
checklist: [{ check: "No changes to review", passed: true }]
|
|
1869
2270
|
});
|
|
1870
2271
|
}
|
|
1871
2272
|
const parsedDiffResult = parseDiff(diff);
|
|
1872
2273
|
if (!parsedDiffResult.ok) {
|
|
1873
|
-
return
|
|
2274
|
+
return Err(new CLIError(parsedDiffResult.error.message, ExitCode.ERROR));
|
|
1874
2275
|
}
|
|
1875
2276
|
const codeChanges = parsedDiffResult.value;
|
|
1876
2277
|
const review = await createSelfReview(codeChanges, {
|
|
@@ -1881,9 +2282,9 @@ async function runAgentReview(options) {
|
|
|
1881
2282
|
}
|
|
1882
2283
|
});
|
|
1883
2284
|
if (!review.ok) {
|
|
1884
|
-
return
|
|
2285
|
+
return Err(new CLIError(review.error.message, ExitCode.ERROR));
|
|
1885
2286
|
}
|
|
1886
|
-
return
|
|
2287
|
+
return Ok({
|
|
1887
2288
|
passed: review.value.passed,
|
|
1888
2289
|
checklist: review.value.items.map((item) => ({
|
|
1889
2290
|
check: item.check,
|
|
@@ -1893,7 +2294,7 @@ async function runAgentReview(options) {
|
|
|
1893
2294
|
});
|
|
1894
2295
|
}
|
|
1895
2296
|
function createReviewCommand() {
|
|
1896
|
-
return new
|
|
2297
|
+
return new Command12("review").description("Run self-review on current changes").action(async (_opts, cmd) => {
|
|
1897
2298
|
const globalOpts = cmd.optsWithGlobals();
|
|
1898
2299
|
const mode = globalOpts.json ? OutputMode.JSON : globalOpts.quiet ? OutputMode.QUIET : OutputMode.TEXT;
|
|
1899
2300
|
const result = await runAgentReview({
|
|
@@ -1929,17 +2330,16 @@ function createReviewCommand() {
|
|
|
1929
2330
|
|
|
1930
2331
|
// src/commands/agent/index.ts
|
|
1931
2332
|
function createAgentCommand() {
|
|
1932
|
-
const command = new
|
|
2333
|
+
const command = new Command13("agent").description("Agent orchestration commands");
|
|
1933
2334
|
command.addCommand(createRunCommand());
|
|
1934
2335
|
command.addCommand(createReviewCommand());
|
|
1935
2336
|
return command;
|
|
1936
2337
|
}
|
|
1937
2338
|
|
|
1938
2339
|
// src/commands/add.ts
|
|
1939
|
-
import { Command as
|
|
2340
|
+
import { Command as Command14 } from "commander";
|
|
1940
2341
|
import * as fs9 from "fs";
|
|
1941
|
-
import * as
|
|
1942
|
-
import { Ok as Ok12, Err as Err10 } from "@harness-engineering/core";
|
|
2342
|
+
import * as path18 from "path";
|
|
1943
2343
|
var LAYER_INDEX_TEMPLATE = (name) => `// ${name} layer
|
|
1944
2344
|
// Add your ${name} exports here
|
|
1945
2345
|
|
|
@@ -1969,7 +2369,7 @@ async function runAdd(componentType, name, options) {
|
|
|
1969
2369
|
const cwd = options.cwd ?? process.cwd();
|
|
1970
2370
|
const NAME_PATTERN = /^[a-zA-Z][a-zA-Z0-9_-]*$/;
|
|
1971
2371
|
if (!name || !NAME_PATTERN.test(name)) {
|
|
1972
|
-
return
|
|
2372
|
+
return Err(
|
|
1973
2373
|
new CLIError(
|
|
1974
2374
|
"Invalid name. Must start with a letter and contain only alphanumeric characters, hyphens, and underscores.",
|
|
1975
2375
|
ExitCode.ERROR
|
|
@@ -1983,12 +2383,12 @@ async function runAdd(componentType, name, options) {
|
|
|
1983
2383
|
try {
|
|
1984
2384
|
switch (componentType) {
|
|
1985
2385
|
case "layer": {
|
|
1986
|
-
const layerDir =
|
|
2386
|
+
const layerDir = path18.join(cwd, "src", name);
|
|
1987
2387
|
if (!fs9.existsSync(layerDir)) {
|
|
1988
2388
|
fs9.mkdirSync(layerDir, { recursive: true });
|
|
1989
2389
|
created.push(`src/${name}/`);
|
|
1990
2390
|
}
|
|
1991
|
-
const indexPath =
|
|
2391
|
+
const indexPath = path18.join(layerDir, "index.ts");
|
|
1992
2392
|
if (!fs9.existsSync(indexPath)) {
|
|
1993
2393
|
fs9.writeFileSync(indexPath, LAYER_INDEX_TEMPLATE(name));
|
|
1994
2394
|
created.push(`src/${name}/index.ts`);
|
|
@@ -1996,22 +2396,22 @@ async function runAdd(componentType, name, options) {
|
|
|
1996
2396
|
break;
|
|
1997
2397
|
}
|
|
1998
2398
|
case "module": {
|
|
1999
|
-
const modulePath =
|
|
2399
|
+
const modulePath = path18.join(cwd, "src", `${name}.ts`);
|
|
2000
2400
|
if (fs9.existsSync(modulePath)) {
|
|
2001
|
-
return
|
|
2401
|
+
return Err(new CLIError(`Module ${name} already exists`, ExitCode.ERROR));
|
|
2002
2402
|
}
|
|
2003
2403
|
fs9.writeFileSync(modulePath, MODULE_TEMPLATE(name));
|
|
2004
2404
|
created.push(`src/${name}.ts`);
|
|
2005
2405
|
break;
|
|
2006
2406
|
}
|
|
2007
2407
|
case "doc": {
|
|
2008
|
-
const docsDir =
|
|
2408
|
+
const docsDir = path18.join(cwd, "docs");
|
|
2009
2409
|
if (!fs9.existsSync(docsDir)) {
|
|
2010
2410
|
fs9.mkdirSync(docsDir, { recursive: true });
|
|
2011
2411
|
}
|
|
2012
|
-
const docPath =
|
|
2412
|
+
const docPath = path18.join(docsDir, `${name}.md`);
|
|
2013
2413
|
if (fs9.existsSync(docPath)) {
|
|
2014
|
-
return
|
|
2414
|
+
return Err(new CLIError(`Doc ${name} already exists`, ExitCode.ERROR));
|
|
2015
2415
|
}
|
|
2016
2416
|
fs9.writeFileSync(docPath, DOC_TEMPLATE(name));
|
|
2017
2417
|
created.push(`docs/${name}.md`);
|
|
@@ -2022,20 +2422,20 @@ async function runAdd(componentType, name, options) {
|
|
|
2022
2422
|
generateSkillFiles2({
|
|
2023
2423
|
name,
|
|
2024
2424
|
description: `${name} skill`,
|
|
2025
|
-
outputDir:
|
|
2425
|
+
outputDir: path18.join(cwd, "agents", "skills", "claude-code")
|
|
2026
2426
|
});
|
|
2027
2427
|
created.push(`agents/skills/claude-code/${name}/skill.yaml`);
|
|
2028
2428
|
created.push(`agents/skills/claude-code/${name}/SKILL.md`);
|
|
2029
2429
|
break;
|
|
2030
2430
|
}
|
|
2031
2431
|
case "persona": {
|
|
2032
|
-
const personasDir =
|
|
2432
|
+
const personasDir = path18.join(cwd, "agents", "personas");
|
|
2033
2433
|
if (!fs9.existsSync(personasDir)) {
|
|
2034
2434
|
fs9.mkdirSync(personasDir, { recursive: true });
|
|
2035
2435
|
}
|
|
2036
|
-
const personaPath =
|
|
2436
|
+
const personaPath = path18.join(personasDir, `${name}.yaml`);
|
|
2037
2437
|
if (fs9.existsSync(personaPath)) {
|
|
2038
|
-
return
|
|
2438
|
+
return Err(new CLIError(`Persona ${name} already exists`, ExitCode.ERROR));
|
|
2039
2439
|
}
|
|
2040
2440
|
fs9.writeFileSync(
|
|
2041
2441
|
personaPath,
|
|
@@ -2051,7 +2451,7 @@ focus_areas: []
|
|
|
2051
2451
|
}
|
|
2052
2452
|
default: {
|
|
2053
2453
|
const _exhaustive = componentType;
|
|
2054
|
-
return
|
|
2454
|
+
return Err(
|
|
2055
2455
|
new CLIError(
|
|
2056
2456
|
`Unknown component type: ${String(_exhaustive)}. Use: layer, module, doc, skill, persona`,
|
|
2057
2457
|
ExitCode.ERROR
|
|
@@ -2059,9 +2459,9 @@ focus_areas: []
|
|
|
2059
2459
|
);
|
|
2060
2460
|
}
|
|
2061
2461
|
}
|
|
2062
|
-
return
|
|
2462
|
+
return Ok({ created });
|
|
2063
2463
|
} catch (error) {
|
|
2064
|
-
return
|
|
2464
|
+
return Err(
|
|
2065
2465
|
new CLIError(
|
|
2066
2466
|
`Failed to add ${componentType}: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
2067
2467
|
ExitCode.ERROR
|
|
@@ -2070,7 +2470,7 @@ focus_areas: []
|
|
|
2070
2470
|
}
|
|
2071
2471
|
}
|
|
2072
2472
|
function createAddCommand() {
|
|
2073
|
-
const command = new
|
|
2473
|
+
const command = new Command14("add").description("Add a component to the project").argument("<type>", "Component type (layer, module, doc, skill, persona)").argument("<name>", "Component name").action(async (type, name, _opts, cmd) => {
|
|
2074
2474
|
const globalOpts = cmd.optsWithGlobals();
|
|
2075
2475
|
const result = await runAdd(type, name, {
|
|
2076
2476
|
...globalOpts.config !== void 0 && { configPath: globalOpts.config }
|
|
@@ -2091,13 +2491,352 @@ function createAddCommand() {
|
|
|
2091
2491
|
}
|
|
2092
2492
|
|
|
2093
2493
|
// src/commands/linter/index.ts
|
|
2094
|
-
import { Command as
|
|
2494
|
+
import { Command as Command17 } from "commander";
|
|
2495
|
+
|
|
2496
|
+
// src/commands/linter/generate.ts
|
|
2497
|
+
import { Command as Command15 } from "commander";
|
|
2498
|
+
|
|
2499
|
+
// ../linter-gen/dist/generator/orchestrator.js
|
|
2500
|
+
import * as fs12 from "fs/promises";
|
|
2501
|
+
import * as path21 from "path";
|
|
2502
|
+
|
|
2503
|
+
// ../linter-gen/dist/parser/config-parser.js
|
|
2504
|
+
import * as fs10 from "fs/promises";
|
|
2505
|
+
import * as yaml from "yaml";
|
|
2506
|
+
|
|
2507
|
+
// ../linter-gen/dist/schema/linter-config.js
|
|
2508
|
+
import { z as z4 } from "zod";
|
|
2509
|
+
var RuleConfigSchema = z4.object({
|
|
2510
|
+
/** Rule name in kebab-case (e.g., 'no-ui-in-services') */
|
|
2511
|
+
name: z4.string().regex(/^[a-z][a-z0-9]*(-[a-z0-9]+)*$/, "Rule name must be kebab-case"),
|
|
2512
|
+
/** Rule type - determines which template to use */
|
|
2513
|
+
type: z4.string().min(1),
|
|
2514
|
+
/** ESLint severity level */
|
|
2515
|
+
severity: z4.enum(["error", "warn", "off"]).default("error"),
|
|
2516
|
+
/** Template-specific configuration */
|
|
2517
|
+
config: z4.record(z4.unknown())
|
|
2518
|
+
});
|
|
2519
|
+
var LinterConfigSchema = z4.object({
|
|
2520
|
+
/** Config version - currently only 1 is supported */
|
|
2521
|
+
version: z4.literal(1),
|
|
2522
|
+
/** Output directory for generated rules */
|
|
2523
|
+
output: z4.string().min(1),
|
|
2524
|
+
/** Optional explicit template path mappings (type → path) */
|
|
2525
|
+
templates: z4.record(z4.string()).optional(),
|
|
2526
|
+
/** Rules to generate */
|
|
2527
|
+
rules: z4.array(RuleConfigSchema).min(1, "At least one rule is required")
|
|
2528
|
+
});
|
|
2529
|
+
|
|
2530
|
+
// ../linter-gen/dist/parser/config-parser.js
|
|
2531
|
+
var ParseError = class extends Error {
|
|
2532
|
+
code;
|
|
2533
|
+
cause;
|
|
2534
|
+
constructor(message, code, cause) {
|
|
2535
|
+
super(message);
|
|
2536
|
+
this.code = code;
|
|
2537
|
+
this.cause = cause;
|
|
2538
|
+
this.name = "ParseError";
|
|
2539
|
+
}
|
|
2540
|
+
};
|
|
2541
|
+
async function parseConfig(configPath) {
|
|
2542
|
+
let content;
|
|
2543
|
+
try {
|
|
2544
|
+
content = await fs10.readFile(configPath, "utf-8");
|
|
2545
|
+
} catch (err) {
|
|
2546
|
+
if (err.code === "ENOENT") {
|
|
2547
|
+
return {
|
|
2548
|
+
success: false,
|
|
2549
|
+
error: new ParseError(`Config file not found: ${configPath}`, "FILE_NOT_FOUND", err)
|
|
2550
|
+
};
|
|
2551
|
+
}
|
|
2552
|
+
return {
|
|
2553
|
+
success: false,
|
|
2554
|
+
error: new ParseError(`Failed to read config file: ${configPath}`, "FILE_READ_ERROR", err)
|
|
2555
|
+
};
|
|
2556
|
+
}
|
|
2557
|
+
let parsed;
|
|
2558
|
+
try {
|
|
2559
|
+
parsed = yaml.parse(content);
|
|
2560
|
+
} catch (err) {
|
|
2561
|
+
return {
|
|
2562
|
+
success: false,
|
|
2563
|
+
error: new ParseError(`Invalid YAML syntax in ${configPath}: ${err.message}`, "YAML_PARSE_ERROR", err)
|
|
2564
|
+
};
|
|
2565
|
+
}
|
|
2566
|
+
const result = LinterConfigSchema.safeParse(parsed);
|
|
2567
|
+
if (!result.success) {
|
|
2568
|
+
const issues = result.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
|
|
2569
|
+
return {
|
|
2570
|
+
success: false,
|
|
2571
|
+
error: new ParseError(`Invalid config: ${issues}`, "VALIDATION_ERROR", result.error)
|
|
2572
|
+
};
|
|
2573
|
+
}
|
|
2574
|
+
return {
|
|
2575
|
+
success: true,
|
|
2576
|
+
data: result.data,
|
|
2577
|
+
configPath
|
|
2578
|
+
};
|
|
2579
|
+
}
|
|
2580
|
+
|
|
2581
|
+
// ../linter-gen/dist/engine/template-loader.js
|
|
2582
|
+
import * as fs11 from "fs/promises";
|
|
2583
|
+
import * as path19 from "path";
|
|
2584
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
2585
|
+
var __filename2 = fileURLToPath2(import.meta.url);
|
|
2586
|
+
var __dirname2 = path19.dirname(__filename2);
|
|
2587
|
+
var TemplateLoadError = class extends Error {
|
|
2588
|
+
code;
|
|
2589
|
+
cause;
|
|
2590
|
+
constructor(message, code, cause) {
|
|
2591
|
+
super(message);
|
|
2592
|
+
this.code = code;
|
|
2593
|
+
this.cause = cause;
|
|
2594
|
+
this.name = "TemplateLoadError";
|
|
2595
|
+
}
|
|
2596
|
+
};
|
|
2597
|
+
var BUILTIN_TEMPLATES = ["import-restriction", "boundary-validation", "dependency-graph"];
|
|
2598
|
+
async function fileExists(filePath) {
|
|
2599
|
+
try {
|
|
2600
|
+
await fs11.access(filePath);
|
|
2601
|
+
return true;
|
|
2602
|
+
} catch {
|
|
2603
|
+
return false;
|
|
2604
|
+
}
|
|
2605
|
+
}
|
|
2606
|
+
async function loadTemplateFile(filePath, type) {
|
|
2607
|
+
try {
|
|
2608
|
+
const content = await fs11.readFile(filePath, "utf-8");
|
|
2609
|
+
return {
|
|
2610
|
+
success: true,
|
|
2611
|
+
source: { type, path: filePath, content }
|
|
2612
|
+
};
|
|
2613
|
+
} catch (err) {
|
|
2614
|
+
return {
|
|
2615
|
+
success: false,
|
|
2616
|
+
error: new TemplateLoadError(`Failed to read template: ${filePath}`, "TEMPLATE_READ_ERROR", err)
|
|
2617
|
+
};
|
|
2618
|
+
}
|
|
2619
|
+
}
|
|
2620
|
+
async function loadTemplate(ruleType, templatesConfig, configDir) {
|
|
2621
|
+
if (templatesConfig?.[ruleType]) {
|
|
2622
|
+
const explicitPath = path19.resolve(configDir, templatesConfig[ruleType]);
|
|
2623
|
+
if (await fileExists(explicitPath)) {
|
|
2624
|
+
return loadTemplateFile(explicitPath, "explicit");
|
|
2625
|
+
}
|
|
2626
|
+
return {
|
|
2627
|
+
success: false,
|
|
2628
|
+
error: new TemplateLoadError(`Explicit template not found: ${explicitPath}`, "TEMPLATE_NOT_FOUND")
|
|
2629
|
+
};
|
|
2630
|
+
}
|
|
2631
|
+
const conventionPath = path19.join(configDir, "templates", `${ruleType}.ts.hbs`);
|
|
2632
|
+
if (await fileExists(conventionPath)) {
|
|
2633
|
+
return loadTemplateFile(conventionPath, "convention");
|
|
2634
|
+
}
|
|
2635
|
+
if (BUILTIN_TEMPLATES.includes(ruleType)) {
|
|
2636
|
+
const builtinPath = path19.join(__dirname2, "..", "templates", `${ruleType}.ts.hbs`);
|
|
2637
|
+
if (await fileExists(builtinPath)) {
|
|
2638
|
+
return loadTemplateFile(builtinPath, "builtin");
|
|
2639
|
+
}
|
|
2640
|
+
}
|
|
2641
|
+
return {
|
|
2642
|
+
success: false,
|
|
2643
|
+
error: new TemplateLoadError(`Template not found for type '${ruleType}'. Checked: explicit config, ./templates/${ruleType}.ts.hbs, built-in templates.`, "TEMPLATE_NOT_FOUND")
|
|
2644
|
+
};
|
|
2645
|
+
}
|
|
2646
|
+
|
|
2647
|
+
// ../linter-gen/dist/generator/rule-generator.js
|
|
2648
|
+
import * as path20 from "path";
|
|
2649
|
+
|
|
2650
|
+
// ../linter-gen/dist/engine/context-builder.js
|
|
2651
|
+
var GENERATOR_VERSION = "0.1.0";
|
|
2652
|
+
function toCamelCase(str) {
|
|
2653
|
+
return str.replace(/-([a-z0-9])/g, (_, char) => char.toUpperCase());
|
|
2654
|
+
}
|
|
2655
|
+
function toPascalCase(str) {
|
|
2656
|
+
const camel = toCamelCase(str);
|
|
2657
|
+
return camel.charAt(0).toUpperCase() + camel.slice(1);
|
|
2658
|
+
}
|
|
2659
|
+
function buildRuleContext(rule, configPath) {
|
|
2660
|
+
return {
|
|
2661
|
+
name: rule.name,
|
|
2662
|
+
nameCamel: toCamelCase(rule.name),
|
|
2663
|
+
namePascal: toPascalCase(rule.name),
|
|
2664
|
+
severity: rule.severity,
|
|
2665
|
+
config: rule.config,
|
|
2666
|
+
meta: {
|
|
2667
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2668
|
+
generatorVersion: GENERATOR_VERSION,
|
|
2669
|
+
configPath
|
|
2670
|
+
}
|
|
2671
|
+
};
|
|
2672
|
+
}
|
|
2673
|
+
|
|
2674
|
+
// ../linter-gen/dist/engine/template-renderer.js
|
|
2675
|
+
import Handlebars2 from "handlebars";
|
|
2676
|
+
var TemplateError = class extends Error {
|
|
2677
|
+
cause;
|
|
2678
|
+
constructor(message, cause) {
|
|
2679
|
+
super(message);
|
|
2680
|
+
this.cause = cause;
|
|
2681
|
+
this.name = "TemplateError";
|
|
2682
|
+
}
|
|
2683
|
+
};
|
|
2684
|
+
function toCamelCase2(str) {
|
|
2685
|
+
return str.replace(/-([a-z0-9])/g, (_, char) => char.toUpperCase());
|
|
2686
|
+
}
|
|
2687
|
+
function toPascalCase2(str) {
|
|
2688
|
+
const camel = toCamelCase2(str);
|
|
2689
|
+
return camel.charAt(0).toUpperCase() + camel.slice(1);
|
|
2690
|
+
}
|
|
2691
|
+
Handlebars2.registerHelper("json", (obj) => JSON.stringify(obj));
|
|
2692
|
+
Handlebars2.registerHelper("jsonPretty", (obj) => JSON.stringify(obj, null, 2));
|
|
2693
|
+
Handlebars2.registerHelper("camelCase", (str) => toCamelCase2(str));
|
|
2694
|
+
Handlebars2.registerHelper("pascalCase", (str) => toPascalCase2(str));
|
|
2695
|
+
function renderTemplate(templateSource, context) {
|
|
2696
|
+
try {
|
|
2697
|
+
const compiled = Handlebars2.compile(templateSource, { strict: true });
|
|
2698
|
+
const output = compiled(context);
|
|
2699
|
+
return { success: true, output };
|
|
2700
|
+
} catch (err) {
|
|
2701
|
+
return {
|
|
2702
|
+
success: false,
|
|
2703
|
+
error: new TemplateError(`Template rendering failed: ${err.message}`, err)
|
|
2704
|
+
};
|
|
2705
|
+
}
|
|
2706
|
+
}
|
|
2707
|
+
|
|
2708
|
+
// ../linter-gen/dist/generator/rule-generator.js
|
|
2709
|
+
function generateRule(rule, template, outputDir, configPath) {
|
|
2710
|
+
const context = buildRuleContext(rule, configPath);
|
|
2711
|
+
const renderResult = renderTemplate(template.content, context);
|
|
2712
|
+
if (!renderResult.success) {
|
|
2713
|
+
return {
|
|
2714
|
+
success: false,
|
|
2715
|
+
error: renderResult.error,
|
|
2716
|
+
ruleName: rule.name
|
|
2717
|
+
};
|
|
2718
|
+
}
|
|
2719
|
+
const outputPath = path20.join(outputDir, `${rule.name}.ts`);
|
|
2720
|
+
return {
|
|
2721
|
+
success: true,
|
|
2722
|
+
rule: {
|
|
2723
|
+
name: rule.name,
|
|
2724
|
+
outputPath,
|
|
2725
|
+
content: renderResult.output
|
|
2726
|
+
}
|
|
2727
|
+
};
|
|
2728
|
+
}
|
|
2729
|
+
|
|
2730
|
+
// ../linter-gen/dist/generator/index-generator.js
|
|
2731
|
+
function toCamelCase3(str) {
|
|
2732
|
+
return str.replace(/-([a-z0-9])/g, (_, char) => char.toUpperCase());
|
|
2733
|
+
}
|
|
2734
|
+
function generateIndex(ruleNames) {
|
|
2735
|
+
const imports = ruleNames.map((name) => {
|
|
2736
|
+
const camel = toCamelCase3(name);
|
|
2737
|
+
return `import ${camel} from './${name}';`;
|
|
2738
|
+
}).join("\n");
|
|
2739
|
+
const rulesObject = ruleNames.map((name) => {
|
|
2740
|
+
const camel = toCamelCase3(name);
|
|
2741
|
+
return ` '${name}': ${camel},`;
|
|
2742
|
+
}).join("\n");
|
|
2743
|
+
const namedExports = ruleNames.map(toCamelCase3).join(", ");
|
|
2744
|
+
return `// Generated by @harness-engineering/linter-gen
|
|
2745
|
+
// Do not edit manually - regenerate from harness-linter.yml
|
|
2746
|
+
|
|
2747
|
+
${imports}
|
|
2748
|
+
|
|
2749
|
+
export const rules = {
|
|
2750
|
+
${rulesObject}
|
|
2751
|
+
};
|
|
2752
|
+
|
|
2753
|
+
export { ${namedExports} };
|
|
2754
|
+
`;
|
|
2755
|
+
}
|
|
2756
|
+
|
|
2757
|
+
// ../linter-gen/dist/generator/orchestrator.js
|
|
2758
|
+
async function validate(options) {
|
|
2759
|
+
const parseResult = await parseConfig(options.configPath);
|
|
2760
|
+
if (!parseResult.success) {
|
|
2761
|
+
return { success: false, error: parseResult.error };
|
|
2762
|
+
}
|
|
2763
|
+
return { success: true, ruleCount: parseResult.data.rules.length };
|
|
2764
|
+
}
|
|
2765
|
+
async function generate(options) {
|
|
2766
|
+
const errors = [];
|
|
2767
|
+
const parseResult = await parseConfig(options.configPath);
|
|
2768
|
+
if (!parseResult.success) {
|
|
2769
|
+
return { success: false, errors: [{ type: "parse", error: parseResult.error }] };
|
|
2770
|
+
}
|
|
2771
|
+
const config = parseResult.data;
|
|
2772
|
+
const configDir = path21.dirname(path21.resolve(options.configPath));
|
|
2773
|
+
const outputDir = options.outputDir ? path21.resolve(options.outputDir) : path21.resolve(configDir, config.output);
|
|
2774
|
+
if (options.clean && !options.dryRun) {
|
|
2775
|
+
try {
|
|
2776
|
+
await fs12.rm(outputDir, { recursive: true, force: true });
|
|
2777
|
+
} catch {
|
|
2778
|
+
}
|
|
2779
|
+
}
|
|
2780
|
+
if (!options.dryRun) {
|
|
2781
|
+
await fs12.mkdir(outputDir, { recursive: true });
|
|
2782
|
+
}
|
|
2783
|
+
const generatedRules = [];
|
|
2784
|
+
for (const rule of config.rules) {
|
|
2785
|
+
const templateResult = await loadTemplate(rule.type, config.templates, configDir);
|
|
2786
|
+
if (!templateResult.success) {
|
|
2787
|
+
errors.push({
|
|
2788
|
+
type: "template",
|
|
2789
|
+
error: templateResult.error,
|
|
2790
|
+
ruleName: rule.name
|
|
2791
|
+
});
|
|
2792
|
+
continue;
|
|
2793
|
+
}
|
|
2794
|
+
const ruleResult = generateRule(rule, templateResult.source, outputDir, options.configPath);
|
|
2795
|
+
if (!ruleResult.success) {
|
|
2796
|
+
errors.push({
|
|
2797
|
+
type: "render",
|
|
2798
|
+
error: ruleResult.error,
|
|
2799
|
+
ruleName: ruleResult.ruleName
|
|
2800
|
+
});
|
|
2801
|
+
continue;
|
|
2802
|
+
}
|
|
2803
|
+
if (!options.dryRun) {
|
|
2804
|
+
try {
|
|
2805
|
+
await fs12.writeFile(ruleResult.rule.outputPath, ruleResult.rule.content, "utf-8");
|
|
2806
|
+
} catch (err) {
|
|
2807
|
+
errors.push({
|
|
2808
|
+
type: "write",
|
|
2809
|
+
error: err,
|
|
2810
|
+
path: ruleResult.rule.outputPath
|
|
2811
|
+
});
|
|
2812
|
+
continue;
|
|
2813
|
+
}
|
|
2814
|
+
}
|
|
2815
|
+
generatedRules.push(rule.name);
|
|
2816
|
+
}
|
|
2817
|
+
if (generatedRules.length > 0 && !options.dryRun) {
|
|
2818
|
+
const indexContent = generateIndex(generatedRules);
|
|
2819
|
+
const indexPath = path21.join(outputDir, "index.ts");
|
|
2820
|
+
try {
|
|
2821
|
+
await fs12.writeFile(indexPath, indexContent, "utf-8");
|
|
2822
|
+
} catch (err) {
|
|
2823
|
+
errors.push({ type: "write", error: err, path: indexPath });
|
|
2824
|
+
}
|
|
2825
|
+
}
|
|
2826
|
+
if (errors.length > 0) {
|
|
2827
|
+
return { success: false, errors };
|
|
2828
|
+
}
|
|
2829
|
+
return {
|
|
2830
|
+
success: true,
|
|
2831
|
+
rulesGenerated: generatedRules,
|
|
2832
|
+
outputDir,
|
|
2833
|
+
dryRun: options.dryRun ?? false
|
|
2834
|
+
};
|
|
2835
|
+
}
|
|
2095
2836
|
|
|
2096
2837
|
// src/commands/linter/generate.ts
|
|
2097
|
-
import { Command as Command12 } from "commander";
|
|
2098
|
-
import { generate } from "@harness-engineering/linter-gen";
|
|
2099
2838
|
function createGenerateCommand() {
|
|
2100
|
-
return new
|
|
2839
|
+
return new Command15("generate").description("Generate ESLint rules from harness-linter.yml").option("-c, --config <path>", "Path to harness-linter.yml", "./harness-linter.yml").option("-o, --output <dir>", "Override output directory").option("--clean", "Remove existing files before generating").option("--dry-run", "Preview without writing files").option("--json", "Output as JSON").option("--verbose", "Show detailed output").action(async (options) => {
|
|
2101
2840
|
try {
|
|
2102
2841
|
if (options.verbose) {
|
|
2103
2842
|
logger.info(`Parsing config: ${options.config}`);
|
|
@@ -2159,10 +2898,9 @@ Generated ${result.rulesGenerated.length} rules to ${result.outputDir}`);
|
|
|
2159
2898
|
}
|
|
2160
2899
|
|
|
2161
2900
|
// src/commands/linter/validate.ts
|
|
2162
|
-
import { Command as
|
|
2163
|
-
import { validate } from "@harness-engineering/linter-gen";
|
|
2901
|
+
import { Command as Command16 } from "commander";
|
|
2164
2902
|
function createValidateCommand2() {
|
|
2165
|
-
return new
|
|
2903
|
+
return new Command16("validate").description("Validate harness-linter.yml config").option("-c, --config <path>", "Path to harness-linter.yml", "./harness-linter.yml").option("--json", "Output as JSON").action(async (options) => {
|
|
2166
2904
|
try {
|
|
2167
2905
|
const result = await validate({ configPath: options.config });
|
|
2168
2906
|
if (options.json) {
|
|
@@ -2181,7 +2919,7 @@ function createValidateCommand2() {
|
|
|
2181
2919
|
|
|
2182
2920
|
// src/commands/linter/index.ts
|
|
2183
2921
|
function createLinterCommand() {
|
|
2184
|
-
const linter = new
|
|
2922
|
+
const linter = new Command17("linter").description(
|
|
2185
2923
|
"Generate and validate ESLint rules from YAML config"
|
|
2186
2924
|
);
|
|
2187
2925
|
linter.addCommand(createGenerateCommand());
|
|
@@ -2190,12 +2928,12 @@ function createLinterCommand() {
|
|
|
2190
2928
|
}
|
|
2191
2929
|
|
|
2192
2930
|
// src/commands/persona/index.ts
|
|
2193
|
-
import { Command as
|
|
2931
|
+
import { Command as Command20 } from "commander";
|
|
2194
2932
|
|
|
2195
2933
|
// src/commands/persona/list.ts
|
|
2196
|
-
import { Command as
|
|
2934
|
+
import { Command as Command18 } from "commander";
|
|
2197
2935
|
function createListCommand() {
|
|
2198
|
-
return new
|
|
2936
|
+
return new Command18("list").description("List available agent personas").action(async (_opts, cmd) => {
|
|
2199
2937
|
const globalOpts = cmd.optsWithGlobals();
|
|
2200
2938
|
const personasDir = resolvePersonasDir();
|
|
2201
2939
|
const result = listPersonas(personasDir);
|
|
@@ -2224,12 +2962,9 @@ function createListCommand() {
|
|
|
2224
2962
|
}
|
|
2225
2963
|
|
|
2226
2964
|
// src/commands/persona/generate.ts
|
|
2227
|
-
import { Command as
|
|
2228
|
-
import * as
|
|
2229
|
-
import * as
|
|
2230
|
-
|
|
2231
|
-
// src/persona/generators/runtime.ts
|
|
2232
|
-
import { Ok as Ok13, Err as Err11 } from "@harness-engineering/core";
|
|
2965
|
+
import { Command as Command19 } from "commander";
|
|
2966
|
+
import * as fs13 from "fs";
|
|
2967
|
+
import * as path22 from "path";
|
|
2233
2968
|
|
|
2234
2969
|
// src/utils/string.ts
|
|
2235
2970
|
function toKebabCase(name) {
|
|
@@ -2246,9 +2981,9 @@ function generateRuntime(persona) {
|
|
|
2246
2981
|
timeout: persona.config.timeout,
|
|
2247
2982
|
severity: persona.config.severity
|
|
2248
2983
|
};
|
|
2249
|
-
return
|
|
2984
|
+
return Ok(JSON.stringify(config, null, 2));
|
|
2250
2985
|
} catch (error) {
|
|
2251
|
-
return
|
|
2986
|
+
return Err(
|
|
2252
2987
|
new Error(
|
|
2253
2988
|
`Failed to generate runtime config: ${error instanceof Error ? error.message : String(error)}`
|
|
2254
2989
|
)
|
|
@@ -2257,7 +2992,6 @@ function generateRuntime(persona) {
|
|
|
2257
2992
|
}
|
|
2258
2993
|
|
|
2259
2994
|
// src/persona/generators/agents-md.ts
|
|
2260
|
-
import { Ok as Ok14, Err as Err12 } from "@harness-engineering/core";
|
|
2261
2995
|
function formatTrigger(trigger) {
|
|
2262
2996
|
switch (trigger.event) {
|
|
2263
2997
|
case "on_pr": {
|
|
@@ -2291,9 +3025,9 @@ function generateAgentsMd(persona) {
|
|
|
2291
3025
|
|
|
2292
3026
|
**When this agent flags an issue:** Fix violations before merging. Run ${allCommands} locally to validate.
|
|
2293
3027
|
`;
|
|
2294
|
-
return
|
|
3028
|
+
return Ok(fragment);
|
|
2295
3029
|
} catch (error) {
|
|
2296
|
-
return
|
|
3030
|
+
return Err(
|
|
2297
3031
|
new Error(
|
|
2298
3032
|
`Failed to generate AGENTS.md fragment: ${error instanceof Error ? error.message : String(error)}`
|
|
2299
3033
|
)
|
|
@@ -2303,7 +3037,6 @@ function generateAgentsMd(persona) {
|
|
|
2303
3037
|
|
|
2304
3038
|
// src/persona/generators/ci-workflow.ts
|
|
2305
3039
|
import YAML2 from "yaml";
|
|
2306
|
-
import { Ok as Ok15, Err as Err13 } from "@harness-engineering/core";
|
|
2307
3040
|
function buildGitHubTriggers(triggers) {
|
|
2308
3041
|
const on = {};
|
|
2309
3042
|
for (const trigger of triggers) {
|
|
@@ -2329,7 +3062,7 @@ function buildGitHubTriggers(triggers) {
|
|
|
2329
3062
|
}
|
|
2330
3063
|
function generateCIWorkflow(persona, platform) {
|
|
2331
3064
|
try {
|
|
2332
|
-
if (platform === "gitlab") return
|
|
3065
|
+
if (platform === "gitlab") return Err(new Error("GitLab CI generation is not yet supported"));
|
|
2333
3066
|
const severity = persona.config.severity;
|
|
2334
3067
|
const steps = [
|
|
2335
3068
|
{ uses: "actions/checkout@v4" },
|
|
@@ -2351,9 +3084,9 @@ function generateCIWorkflow(persona, platform) {
|
|
|
2351
3084
|
}
|
|
2352
3085
|
}
|
|
2353
3086
|
};
|
|
2354
|
-
return
|
|
3087
|
+
return Ok(YAML2.stringify(workflow, { lineWidth: 0 }));
|
|
2355
3088
|
} catch (error) {
|
|
2356
|
-
return
|
|
3089
|
+
return Err(
|
|
2357
3090
|
new Error(
|
|
2358
3091
|
`Failed to generate CI workflow: ${error instanceof Error ? error.message : String(error)}`
|
|
2359
3092
|
)
|
|
@@ -2363,43 +3096,43 @@ function generateCIWorkflow(persona, platform) {
|
|
|
2363
3096
|
|
|
2364
3097
|
// src/commands/persona/generate.ts
|
|
2365
3098
|
function createGenerateCommand2() {
|
|
2366
|
-
return new
|
|
3099
|
+
return new Command19("generate").description("Generate artifacts from a persona config").argument("<name>", "Persona name (e.g., architecture-enforcer)").option("--output-dir <dir>", "Output directory", ".").option("--only <type>", "Generate only: ci, agents-md, runtime").action(async (name, opts, cmd) => {
|
|
2367
3100
|
const globalOpts = cmd.optsWithGlobals();
|
|
2368
3101
|
const personasDir = resolvePersonasDir();
|
|
2369
|
-
const filePath =
|
|
3102
|
+
const filePath = path22.join(personasDir, `${name}.yaml`);
|
|
2370
3103
|
const personaResult = loadPersona(filePath);
|
|
2371
3104
|
if (!personaResult.ok) {
|
|
2372
3105
|
logger.error(personaResult.error.message);
|
|
2373
3106
|
process.exit(ExitCode.ERROR);
|
|
2374
3107
|
}
|
|
2375
3108
|
const persona = personaResult.value;
|
|
2376
|
-
const outputDir =
|
|
3109
|
+
const outputDir = path22.resolve(opts.outputDir);
|
|
2377
3110
|
const slug = toKebabCase(persona.name);
|
|
2378
3111
|
const only = opts.only;
|
|
2379
3112
|
const generated = [];
|
|
2380
3113
|
if (!only || only === "runtime") {
|
|
2381
3114
|
const result = generateRuntime(persona);
|
|
2382
3115
|
if (result.ok) {
|
|
2383
|
-
const outPath =
|
|
2384
|
-
|
|
2385
|
-
|
|
3116
|
+
const outPath = path22.join(outputDir, `${slug}.runtime.json`);
|
|
3117
|
+
fs13.mkdirSync(path22.dirname(outPath), { recursive: true });
|
|
3118
|
+
fs13.writeFileSync(outPath, result.value);
|
|
2386
3119
|
generated.push(outPath);
|
|
2387
3120
|
}
|
|
2388
3121
|
}
|
|
2389
3122
|
if (!only || only === "agents-md") {
|
|
2390
3123
|
const result = generateAgentsMd(persona);
|
|
2391
3124
|
if (result.ok) {
|
|
2392
|
-
const outPath =
|
|
2393
|
-
|
|
3125
|
+
const outPath = path22.join(outputDir, `${slug}.agents.md`);
|
|
3126
|
+
fs13.writeFileSync(outPath, result.value);
|
|
2394
3127
|
generated.push(outPath);
|
|
2395
3128
|
}
|
|
2396
3129
|
}
|
|
2397
3130
|
if (!only || only === "ci") {
|
|
2398
3131
|
const result = generateCIWorkflow(persona, "github");
|
|
2399
3132
|
if (result.ok) {
|
|
2400
|
-
const outPath =
|
|
2401
|
-
|
|
2402
|
-
|
|
3133
|
+
const outPath = path22.join(outputDir, ".github", "workflows", `${slug}.yml`);
|
|
3134
|
+
fs13.mkdirSync(path22.dirname(outPath), { recursive: true });
|
|
3135
|
+
fs13.writeFileSync(outPath, result.value);
|
|
2403
3136
|
generated.push(outPath);
|
|
2404
3137
|
}
|
|
2405
3138
|
}
|
|
@@ -2413,37 +3146,37 @@ function createGenerateCommand2() {
|
|
|
2413
3146
|
|
|
2414
3147
|
// src/commands/persona/index.ts
|
|
2415
3148
|
function createPersonaCommand() {
|
|
2416
|
-
const command = new
|
|
3149
|
+
const command = new Command20("persona").description("Agent persona management commands");
|
|
2417
3150
|
command.addCommand(createListCommand());
|
|
2418
3151
|
command.addCommand(createGenerateCommand2());
|
|
2419
3152
|
return command;
|
|
2420
3153
|
}
|
|
2421
3154
|
|
|
2422
3155
|
// src/commands/skill/index.ts
|
|
2423
|
-
import { Command as
|
|
3156
|
+
import { Command as Command25 } from "commander";
|
|
2424
3157
|
|
|
2425
3158
|
// src/commands/skill/list.ts
|
|
2426
|
-
import { Command as
|
|
2427
|
-
import * as
|
|
2428
|
-
import * as
|
|
2429
|
-
import { parse as
|
|
3159
|
+
import { Command as Command21 } from "commander";
|
|
3160
|
+
import * as fs14 from "fs";
|
|
3161
|
+
import * as path23 from "path";
|
|
3162
|
+
import { parse as parse4 } from "yaml";
|
|
2430
3163
|
function createListCommand2() {
|
|
2431
|
-
return new
|
|
3164
|
+
return new Command21("list").description("List available skills").action(async (_opts, cmd) => {
|
|
2432
3165
|
const globalOpts = cmd.optsWithGlobals();
|
|
2433
3166
|
const skillsDir = resolveSkillsDir();
|
|
2434
|
-
if (!
|
|
3167
|
+
if (!fs14.existsSync(skillsDir)) {
|
|
2435
3168
|
logger.info("No skills directory found.");
|
|
2436
3169
|
process.exit(ExitCode.SUCCESS);
|
|
2437
3170
|
return;
|
|
2438
3171
|
}
|
|
2439
|
-
const entries =
|
|
3172
|
+
const entries = fs14.readdirSync(skillsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
|
|
2440
3173
|
const skills = [];
|
|
2441
3174
|
for (const name of entries) {
|
|
2442
|
-
const yamlPath =
|
|
2443
|
-
if (!
|
|
3175
|
+
const yamlPath = path23.join(skillsDir, name, "skill.yaml");
|
|
3176
|
+
if (!fs14.existsSync(yamlPath)) continue;
|
|
2444
3177
|
try {
|
|
2445
|
-
const raw =
|
|
2446
|
-
const parsed =
|
|
3178
|
+
const raw = fs14.readFileSync(yamlPath, "utf-8");
|
|
3179
|
+
const parsed = parse4(raw);
|
|
2447
3180
|
const result = SkillMetadataSchema.safeParse(parsed);
|
|
2448
3181
|
if (result.success) {
|
|
2449
3182
|
skills.push(result.data);
|
|
@@ -2472,13 +3205,13 @@ function createListCommand2() {
|
|
|
2472
3205
|
}
|
|
2473
3206
|
|
|
2474
3207
|
// src/commands/skill/run.ts
|
|
2475
|
-
import { Command as
|
|
2476
|
-
import * as
|
|
2477
|
-
import * as
|
|
2478
|
-
import { parse as
|
|
3208
|
+
import { Command as Command22 } from "commander";
|
|
3209
|
+
import * as fs15 from "fs";
|
|
3210
|
+
import * as path24 from "path";
|
|
3211
|
+
import { parse as parse5 } from "yaml";
|
|
2479
3212
|
|
|
2480
3213
|
// src/skill/complexity.ts
|
|
2481
|
-
import { execSync as
|
|
3214
|
+
import { execSync as execSync3 } from "child_process";
|
|
2482
3215
|
function evaluateSignals(signals) {
|
|
2483
3216
|
if (signals.fileCount >= 3) return "full";
|
|
2484
3217
|
if (signals.newDir) return "full";
|
|
@@ -2490,17 +3223,17 @@ function evaluateSignals(signals) {
|
|
|
2490
3223
|
}
|
|
2491
3224
|
function detectComplexity(projectPath) {
|
|
2492
3225
|
try {
|
|
2493
|
-
const base =
|
|
3226
|
+
const base = execSync3("git merge-base HEAD main", {
|
|
2494
3227
|
cwd: projectPath,
|
|
2495
3228
|
encoding: "utf-8",
|
|
2496
3229
|
stdio: ["pipe", "pipe", "pipe"]
|
|
2497
3230
|
}).trim();
|
|
2498
|
-
const diffFiles =
|
|
3231
|
+
const diffFiles = execSync3(`git diff --name-only ${base}`, {
|
|
2499
3232
|
cwd: projectPath,
|
|
2500
3233
|
encoding: "utf-8",
|
|
2501
3234
|
stdio: ["pipe", "pipe", "pipe"]
|
|
2502
3235
|
}).trim().split("\n").filter(Boolean);
|
|
2503
|
-
const diffStat =
|
|
3236
|
+
const diffStat = execSync3(`git diff --stat ${base}`, {
|
|
2504
3237
|
cwd: projectPath,
|
|
2505
3238
|
encoding: "utf-8",
|
|
2506
3239
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -2562,20 +3295,20 @@ ${options.priorState}`);
|
|
|
2562
3295
|
|
|
2563
3296
|
// src/commands/skill/run.ts
|
|
2564
3297
|
function createRunCommand2() {
|
|
2565
|
-
return new
|
|
3298
|
+
return new Command22("run").description("Run a skill (outputs SKILL.md content with context preamble)").argument("<name>", "Skill name (e.g., harness-tdd)").option("--path <path>", "Project root path for context injection").option("--complexity <level>", "Complexity: auto, light, full", "auto").option("--phase <name>", "Start at a specific phase (for re-entry)").option("--party", "Enable multi-perspective evaluation").action(async (name, opts, _cmd) => {
|
|
2566
3299
|
const skillsDir = resolveSkillsDir();
|
|
2567
|
-
const skillDir =
|
|
2568
|
-
if (!
|
|
3300
|
+
const skillDir = path24.join(skillsDir, name);
|
|
3301
|
+
if (!fs15.existsSync(skillDir)) {
|
|
2569
3302
|
logger.error(`Skill not found: ${name}`);
|
|
2570
3303
|
process.exit(ExitCode.ERROR);
|
|
2571
3304
|
return;
|
|
2572
3305
|
}
|
|
2573
|
-
const yamlPath =
|
|
3306
|
+
const yamlPath = path24.join(skillDir, "skill.yaml");
|
|
2574
3307
|
let metadata = null;
|
|
2575
|
-
if (
|
|
3308
|
+
if (fs15.existsSync(yamlPath)) {
|
|
2576
3309
|
try {
|
|
2577
|
-
const raw =
|
|
2578
|
-
const parsed =
|
|
3310
|
+
const raw = fs15.readFileSync(yamlPath, "utf-8");
|
|
3311
|
+
const parsed = parse5(raw);
|
|
2579
3312
|
const result = SkillMetadataSchema.safeParse(parsed);
|
|
2580
3313
|
if (result.success) metadata = result.data;
|
|
2581
3314
|
} catch {
|
|
@@ -2585,17 +3318,17 @@ function createRunCommand2() {
|
|
|
2585
3318
|
if (metadata?.phases && metadata.phases.length > 0) {
|
|
2586
3319
|
const requested = opts.complexity ?? "auto";
|
|
2587
3320
|
if (requested === "auto") {
|
|
2588
|
-
const projectPath2 = opts.path ?
|
|
3321
|
+
const projectPath2 = opts.path ? path24.resolve(opts.path) : process.cwd();
|
|
2589
3322
|
complexity = detectComplexity(projectPath2);
|
|
2590
3323
|
} else {
|
|
2591
3324
|
complexity = requested;
|
|
2592
3325
|
}
|
|
2593
3326
|
}
|
|
2594
3327
|
let principles;
|
|
2595
|
-
const projectPath = opts.path ?
|
|
2596
|
-
const principlesPath =
|
|
2597
|
-
if (
|
|
2598
|
-
principles =
|
|
3328
|
+
const projectPath = opts.path ? path24.resolve(opts.path) : process.cwd();
|
|
3329
|
+
const principlesPath = path24.join(projectPath, "docs", "principles.md");
|
|
3330
|
+
if (fs15.existsSync(principlesPath)) {
|
|
3331
|
+
principles = fs15.readFileSync(principlesPath, "utf-8");
|
|
2599
3332
|
}
|
|
2600
3333
|
let priorState;
|
|
2601
3334
|
let stateWarning;
|
|
@@ -2610,16 +3343,16 @@ function createRunCommand2() {
|
|
|
2610
3343
|
}
|
|
2611
3344
|
if (metadata?.state.persistent && metadata.state.files.length > 0) {
|
|
2612
3345
|
for (const stateFilePath of metadata.state.files) {
|
|
2613
|
-
const fullPath =
|
|
2614
|
-
if (
|
|
2615
|
-
const stat =
|
|
3346
|
+
const fullPath = path24.join(projectPath, stateFilePath);
|
|
3347
|
+
if (fs15.existsSync(fullPath)) {
|
|
3348
|
+
const stat = fs15.statSync(fullPath);
|
|
2616
3349
|
if (stat.isDirectory()) {
|
|
2617
|
-
const files =
|
|
3350
|
+
const files = fs15.readdirSync(fullPath).map((f) => ({ name: f, mtime: fs15.statSync(path24.join(fullPath, f)).mtimeMs })).sort((a, b) => b.mtime - a.mtime);
|
|
2618
3351
|
if (files.length > 0) {
|
|
2619
|
-
priorState =
|
|
3352
|
+
priorState = fs15.readFileSync(path24.join(fullPath, files[0].name), "utf-8");
|
|
2620
3353
|
}
|
|
2621
3354
|
} else {
|
|
2622
|
-
priorState =
|
|
3355
|
+
priorState = fs15.readFileSync(fullPath, "utf-8");
|
|
2623
3356
|
}
|
|
2624
3357
|
break;
|
|
2625
3358
|
}
|
|
@@ -2638,17 +3371,17 @@ function createRunCommand2() {
|
|
|
2638
3371
|
...stateWarning !== void 0 && { stateWarning },
|
|
2639
3372
|
party: opts.party
|
|
2640
3373
|
});
|
|
2641
|
-
const skillMdPath =
|
|
2642
|
-
if (!
|
|
3374
|
+
const skillMdPath = path24.join(skillDir, "SKILL.md");
|
|
3375
|
+
if (!fs15.existsSync(skillMdPath)) {
|
|
2643
3376
|
logger.error(`SKILL.md not found for skill: ${name}`);
|
|
2644
3377
|
process.exit(ExitCode.ERROR);
|
|
2645
3378
|
return;
|
|
2646
3379
|
}
|
|
2647
|
-
let content =
|
|
3380
|
+
let content = fs15.readFileSync(skillMdPath, "utf-8");
|
|
2648
3381
|
if (metadata?.state.persistent && opts.path) {
|
|
2649
|
-
const stateFile =
|
|
2650
|
-
if (
|
|
2651
|
-
const stateContent =
|
|
3382
|
+
const stateFile = path24.join(projectPath, ".harness", "state.json");
|
|
3383
|
+
if (fs15.existsSync(stateFile)) {
|
|
3384
|
+
const stateContent = fs15.readFileSync(stateFile, "utf-8");
|
|
2652
3385
|
content += `
|
|
2653
3386
|
|
|
2654
3387
|
---
|
|
@@ -2665,10 +3398,10 @@ ${stateContent}
|
|
|
2665
3398
|
}
|
|
2666
3399
|
|
|
2667
3400
|
// src/commands/skill/validate.ts
|
|
2668
|
-
import { Command as
|
|
2669
|
-
import * as
|
|
2670
|
-
import * as
|
|
2671
|
-
import { parse as
|
|
3401
|
+
import { Command as Command23 } from "commander";
|
|
3402
|
+
import * as fs16 from "fs";
|
|
3403
|
+
import * as path25 from "path";
|
|
3404
|
+
import { parse as parse6 } from "yaml";
|
|
2672
3405
|
var REQUIRED_SECTIONS = [
|
|
2673
3406
|
"## When to Use",
|
|
2674
3407
|
"## Process",
|
|
@@ -2677,35 +3410,35 @@ var REQUIRED_SECTIONS = [
|
|
|
2677
3410
|
"## Examples"
|
|
2678
3411
|
];
|
|
2679
3412
|
function createValidateCommand3() {
|
|
2680
|
-
return new
|
|
3413
|
+
return new Command23("validate").description("Validate all skill.yaml files and SKILL.md structure").action(async (_opts, cmd) => {
|
|
2681
3414
|
const globalOpts = cmd.optsWithGlobals();
|
|
2682
3415
|
const skillsDir = resolveSkillsDir();
|
|
2683
|
-
if (!
|
|
3416
|
+
if (!fs16.existsSync(skillsDir)) {
|
|
2684
3417
|
logger.info("No skills directory found.");
|
|
2685
3418
|
process.exit(ExitCode.SUCCESS);
|
|
2686
3419
|
return;
|
|
2687
3420
|
}
|
|
2688
|
-
const entries =
|
|
3421
|
+
const entries = fs16.readdirSync(skillsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
|
|
2689
3422
|
const errors = [];
|
|
2690
3423
|
let validated = 0;
|
|
2691
3424
|
for (const name of entries) {
|
|
2692
|
-
const skillDir =
|
|
2693
|
-
const yamlPath =
|
|
2694
|
-
const skillMdPath =
|
|
2695
|
-
if (!
|
|
3425
|
+
const skillDir = path25.join(skillsDir, name);
|
|
3426
|
+
const yamlPath = path25.join(skillDir, "skill.yaml");
|
|
3427
|
+
const skillMdPath = path25.join(skillDir, "SKILL.md");
|
|
3428
|
+
if (!fs16.existsSync(yamlPath)) {
|
|
2696
3429
|
errors.push(`${name}: missing skill.yaml`);
|
|
2697
3430
|
continue;
|
|
2698
3431
|
}
|
|
2699
3432
|
try {
|
|
2700
|
-
const raw =
|
|
2701
|
-
const parsed =
|
|
3433
|
+
const raw = fs16.readFileSync(yamlPath, "utf-8");
|
|
3434
|
+
const parsed = parse6(raw);
|
|
2702
3435
|
const result = SkillMetadataSchema.safeParse(parsed);
|
|
2703
3436
|
if (!result.success) {
|
|
2704
3437
|
errors.push(`${name}/skill.yaml: ${result.error.message}`);
|
|
2705
3438
|
continue;
|
|
2706
3439
|
}
|
|
2707
|
-
if (
|
|
2708
|
-
const mdContent =
|
|
3440
|
+
if (fs16.existsSync(skillMdPath)) {
|
|
3441
|
+
const mdContent = fs16.readFileSync(skillMdPath, "utf-8");
|
|
2709
3442
|
for (const section of REQUIRED_SECTIONS) {
|
|
2710
3443
|
if (!mdContent.includes(section)) {
|
|
2711
3444
|
errors.push(`${name}/SKILL.md: missing section "${section}"`);
|
|
@@ -2746,29 +3479,29 @@ function createValidateCommand3() {
|
|
|
2746
3479
|
}
|
|
2747
3480
|
|
|
2748
3481
|
// src/commands/skill/info.ts
|
|
2749
|
-
import { Command as
|
|
2750
|
-
import * as
|
|
2751
|
-
import * as
|
|
2752
|
-
import { parse as
|
|
3482
|
+
import { Command as Command24 } from "commander";
|
|
3483
|
+
import * as fs17 from "fs";
|
|
3484
|
+
import * as path26 from "path";
|
|
3485
|
+
import { parse as parse7 } from "yaml";
|
|
2753
3486
|
function createInfoCommand() {
|
|
2754
|
-
return new
|
|
3487
|
+
return new Command24("info").description("Show metadata for a skill").argument("<name>", "Skill name (e.g., harness-tdd)").action(async (name, _opts, cmd) => {
|
|
2755
3488
|
const globalOpts = cmd.optsWithGlobals();
|
|
2756
3489
|
const skillsDir = resolveSkillsDir();
|
|
2757
|
-
const skillDir =
|
|
2758
|
-
if (!
|
|
3490
|
+
const skillDir = path26.join(skillsDir, name);
|
|
3491
|
+
if (!fs17.existsSync(skillDir)) {
|
|
2759
3492
|
logger.error(`Skill not found: ${name}`);
|
|
2760
3493
|
process.exit(ExitCode.ERROR);
|
|
2761
3494
|
return;
|
|
2762
3495
|
}
|
|
2763
|
-
const yamlPath =
|
|
2764
|
-
if (!
|
|
3496
|
+
const yamlPath = path26.join(skillDir, "skill.yaml");
|
|
3497
|
+
if (!fs17.existsSync(yamlPath)) {
|
|
2765
3498
|
logger.error(`skill.yaml not found for skill: ${name}`);
|
|
2766
3499
|
process.exit(ExitCode.ERROR);
|
|
2767
3500
|
return;
|
|
2768
3501
|
}
|
|
2769
3502
|
try {
|
|
2770
|
-
const raw =
|
|
2771
|
-
const parsed =
|
|
3503
|
+
const raw = fs17.readFileSync(yamlPath, "utf-8");
|
|
3504
|
+
const parsed = parse7(raw);
|
|
2772
3505
|
const result = SkillMetadataSchema.safeParse(parsed);
|
|
2773
3506
|
if (!result.success) {
|
|
2774
3507
|
logger.error(`Invalid skill.yaml: ${result.error.message}`);
|
|
@@ -2808,7 +3541,7 @@ function createInfoCommand() {
|
|
|
2808
3541
|
|
|
2809
3542
|
// src/commands/skill/index.ts
|
|
2810
3543
|
function createSkillCommand() {
|
|
2811
|
-
const command = new
|
|
3544
|
+
const command = new Command25("skill").description("Skill management commands");
|
|
2812
3545
|
command.addCommand(createListCommand2());
|
|
2813
3546
|
command.addCommand(createRunCommand2());
|
|
2814
3547
|
command.addCommand(createValidateCommand3());
|
|
@@ -2817,17 +3550,16 @@ function createSkillCommand() {
|
|
|
2817
3550
|
}
|
|
2818
3551
|
|
|
2819
3552
|
// src/commands/state/index.ts
|
|
2820
|
-
import { Command as
|
|
3553
|
+
import { Command as Command30 } from "commander";
|
|
2821
3554
|
|
|
2822
3555
|
// src/commands/state/show.ts
|
|
2823
|
-
import { Command as
|
|
2824
|
-
import * as
|
|
2825
|
-
import { loadState } from "@harness-engineering/core";
|
|
3556
|
+
import { Command as Command26 } from "commander";
|
|
3557
|
+
import * as path27 from "path";
|
|
2826
3558
|
function createShowCommand() {
|
|
2827
|
-
return new
|
|
3559
|
+
return new Command26("show").description("Show current project state").option("--path <path>", "Project root path", ".").option("--stream <name>", "Target a specific stream").action(async (opts, cmd) => {
|
|
2828
3560
|
const globalOpts = cmd.optsWithGlobals();
|
|
2829
|
-
const projectPath =
|
|
2830
|
-
const result = await loadState(projectPath);
|
|
3561
|
+
const projectPath = path27.resolve(opts.path);
|
|
3562
|
+
const result = await loadState(projectPath, opts.stream);
|
|
2831
3563
|
if (!result.ok) {
|
|
2832
3564
|
logger.error(result.error.message);
|
|
2833
3565
|
process.exit(ExitCode.ERROR);
|
|
@@ -2839,6 +3571,7 @@ function createShowCommand() {
|
|
|
2839
3571
|
} else if (globalOpts.quiet) {
|
|
2840
3572
|
console.log(JSON.stringify(state));
|
|
2841
3573
|
} else {
|
|
3574
|
+
if (opts.stream) console.log(`Stream: ${opts.stream}`);
|
|
2842
3575
|
console.log(`Schema Version: ${state.schemaVersion}`);
|
|
2843
3576
|
if (state.position.phase) console.log(`Phase: ${state.position.phase}`);
|
|
2844
3577
|
if (state.position.task) console.log(`Task: ${state.position.task}`);
|
|
@@ -2865,23 +3598,34 @@ Decisions: ${state.decisions.length}`);
|
|
|
2865
3598
|
}
|
|
2866
3599
|
|
|
2867
3600
|
// src/commands/state/reset.ts
|
|
2868
|
-
import { Command as
|
|
2869
|
-
import * as
|
|
2870
|
-
import * as
|
|
3601
|
+
import { Command as Command27 } from "commander";
|
|
3602
|
+
import * as fs18 from "fs";
|
|
3603
|
+
import * as path28 from "path";
|
|
2871
3604
|
import * as readline from "readline";
|
|
2872
3605
|
function createResetCommand() {
|
|
2873
|
-
return new
|
|
2874
|
-
const projectPath =
|
|
2875
|
-
|
|
2876
|
-
if (
|
|
3606
|
+
return new Command27("reset").description("Reset project state (deletes .harness/state.json)").option("--path <path>", "Project root path", ".").option("--stream <name>", "Target a specific stream").option("--yes", "Skip confirmation prompt").action(async (opts, _cmd) => {
|
|
3607
|
+
const projectPath = path28.resolve(opts.path);
|
|
3608
|
+
let statePath;
|
|
3609
|
+
if (opts.stream) {
|
|
3610
|
+
const streamResult = await resolveStreamPath(projectPath, { stream: opts.stream });
|
|
3611
|
+
if (!streamResult.ok) {
|
|
3612
|
+
logger.error(streamResult.error.message);
|
|
3613
|
+
process.exit(ExitCode.ERROR);
|
|
3614
|
+
return;
|
|
3615
|
+
}
|
|
3616
|
+
statePath = path28.join(streamResult.value, "state.json");
|
|
3617
|
+
} else {
|
|
3618
|
+
statePath = path28.join(projectPath, ".harness", "state.json");
|
|
3619
|
+
}
|
|
3620
|
+
if (!fs18.existsSync(statePath)) {
|
|
2877
3621
|
logger.info("No state file found. Nothing to reset.");
|
|
2878
3622
|
process.exit(ExitCode.SUCCESS);
|
|
2879
3623
|
return;
|
|
2880
3624
|
}
|
|
2881
3625
|
if (!opts.yes) {
|
|
2882
3626
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
2883
|
-
const answer = await new Promise((
|
|
2884
|
-
rl.question("Reset project state? This cannot be undone. [y/N] ",
|
|
3627
|
+
const answer = await new Promise((resolve24) => {
|
|
3628
|
+
rl.question("Reset project state? This cannot be undone. [y/N] ", resolve24);
|
|
2885
3629
|
});
|
|
2886
3630
|
rl.close();
|
|
2887
3631
|
if (answer.toLowerCase() !== "y" && answer.toLowerCase() !== "yes") {
|
|
@@ -2891,7 +3635,7 @@ function createResetCommand() {
|
|
|
2891
3635
|
}
|
|
2892
3636
|
}
|
|
2893
3637
|
try {
|
|
2894
|
-
|
|
3638
|
+
fs18.unlinkSync(statePath);
|
|
2895
3639
|
logger.success("Project state reset.");
|
|
2896
3640
|
} catch (e) {
|
|
2897
3641
|
logger.error(`Failed to reset state: ${e instanceof Error ? e.message : String(e)}`);
|
|
@@ -2903,13 +3647,12 @@ function createResetCommand() {
|
|
|
2903
3647
|
}
|
|
2904
3648
|
|
|
2905
3649
|
// src/commands/state/learn.ts
|
|
2906
|
-
import { Command as
|
|
2907
|
-
import * as
|
|
2908
|
-
import { appendLearning } from "@harness-engineering/core";
|
|
3650
|
+
import { Command as Command28 } from "commander";
|
|
3651
|
+
import * as path29 from "path";
|
|
2909
3652
|
function createLearnCommand() {
|
|
2910
|
-
return new
|
|
2911
|
-
const projectPath =
|
|
2912
|
-
const result = await appendLearning(projectPath, message);
|
|
3653
|
+
return new Command28("learn").description("Append a learning to .harness/learnings.md").argument("<message>", "The learning to record").option("--path <path>", "Project root path", ".").option("--stream <name>", "Target a specific stream").action(async (message, opts, _cmd) => {
|
|
3654
|
+
const projectPath = path29.resolve(opts.path);
|
|
3655
|
+
const result = await appendLearning(projectPath, message, void 0, void 0, opts.stream);
|
|
2913
3656
|
if (!result.ok) {
|
|
2914
3657
|
logger.error(result.error.message);
|
|
2915
3658
|
process.exit(ExitCode.ERROR);
|
|
@@ -2920,29 +3663,95 @@ function createLearnCommand() {
|
|
|
2920
3663
|
});
|
|
2921
3664
|
}
|
|
2922
3665
|
|
|
3666
|
+
// src/commands/state/streams.ts
|
|
3667
|
+
import { Command as Command29 } from "commander";
|
|
3668
|
+
import * as path30 from "path";
|
|
3669
|
+
function createStreamsCommand() {
|
|
3670
|
+
const command = new Command29("streams").description("Manage state streams");
|
|
3671
|
+
command.command("list").description("List all known streams").option("--path <path>", "Project root path", ".").action(async (opts, cmd) => {
|
|
3672
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
3673
|
+
const projectPath = path30.resolve(opts.path);
|
|
3674
|
+
const indexResult = await loadStreamIndex(projectPath);
|
|
3675
|
+
const result = await listStreams(projectPath);
|
|
3676
|
+
if (!result.ok) {
|
|
3677
|
+
logger.error(result.error.message);
|
|
3678
|
+
process.exit(ExitCode.ERROR);
|
|
3679
|
+
return;
|
|
3680
|
+
}
|
|
3681
|
+
const active = indexResult.ok ? indexResult.value.activeStream : null;
|
|
3682
|
+
if (globalOpts.json) {
|
|
3683
|
+
logger.raw({ activeStream: active, streams: result.value });
|
|
3684
|
+
} else {
|
|
3685
|
+
if (result.value.length === 0) {
|
|
3686
|
+
console.log("No streams found.");
|
|
3687
|
+
}
|
|
3688
|
+
for (const s of result.value) {
|
|
3689
|
+
const marker = s.name === active ? " (active)" : "";
|
|
3690
|
+
const branch = s.branch ? ` [${s.branch}]` : "";
|
|
3691
|
+
console.log(` ${s.name}${marker}${branch} \u2014 last active: ${s.lastActiveAt}`);
|
|
3692
|
+
}
|
|
3693
|
+
}
|
|
3694
|
+
process.exit(ExitCode.SUCCESS);
|
|
3695
|
+
});
|
|
3696
|
+
command.command("create <name>").description("Create a new stream").option("--path <path>", "Project root path", ".").option("--branch <branch>", "Associate with a git branch").action(async (name, opts) => {
|
|
3697
|
+
const projectPath = path30.resolve(opts.path);
|
|
3698
|
+
const result = await createStream(projectPath, name, opts.branch);
|
|
3699
|
+
if (!result.ok) {
|
|
3700
|
+
logger.error(result.error.message);
|
|
3701
|
+
process.exit(ExitCode.ERROR);
|
|
3702
|
+
return;
|
|
3703
|
+
}
|
|
3704
|
+
logger.success(`Stream '${name}' created.`);
|
|
3705
|
+
process.exit(ExitCode.SUCCESS);
|
|
3706
|
+
});
|
|
3707
|
+
command.command("archive <name>").description("Archive a stream").option("--path <path>", "Project root path", ".").action(async (name, opts) => {
|
|
3708
|
+
const projectPath = path30.resolve(opts.path);
|
|
3709
|
+
const result = await archiveStream(projectPath, name);
|
|
3710
|
+
if (!result.ok) {
|
|
3711
|
+
logger.error(result.error.message);
|
|
3712
|
+
process.exit(ExitCode.ERROR);
|
|
3713
|
+
return;
|
|
3714
|
+
}
|
|
3715
|
+
logger.success(`Stream '${name}' archived.`);
|
|
3716
|
+
process.exit(ExitCode.SUCCESS);
|
|
3717
|
+
});
|
|
3718
|
+
command.command("activate <name>").description("Set the active stream").option("--path <path>", "Project root path", ".").action(async (name, opts) => {
|
|
3719
|
+
const projectPath = path30.resolve(opts.path);
|
|
3720
|
+
const result = await setActiveStream(projectPath, name);
|
|
3721
|
+
if (!result.ok) {
|
|
3722
|
+
logger.error(result.error.message);
|
|
3723
|
+
process.exit(ExitCode.ERROR);
|
|
3724
|
+
return;
|
|
3725
|
+
}
|
|
3726
|
+
logger.success(`Active stream set to '${name}'.`);
|
|
3727
|
+
process.exit(ExitCode.SUCCESS);
|
|
3728
|
+
});
|
|
3729
|
+
return command;
|
|
3730
|
+
}
|
|
3731
|
+
|
|
2923
3732
|
// src/commands/state/index.ts
|
|
2924
3733
|
function createStateCommand() {
|
|
2925
|
-
const command = new
|
|
3734
|
+
const command = new Command30("state").description("Project state management commands");
|
|
2926
3735
|
command.addCommand(createShowCommand());
|
|
2927
3736
|
command.addCommand(createResetCommand());
|
|
2928
3737
|
command.addCommand(createLearnCommand());
|
|
3738
|
+
command.addCommand(createStreamsCommand());
|
|
2929
3739
|
return command;
|
|
2930
3740
|
}
|
|
2931
3741
|
|
|
2932
3742
|
// src/commands/check-phase-gate.ts
|
|
2933
|
-
import { Command as
|
|
2934
|
-
import * as
|
|
2935
|
-
import * as
|
|
2936
|
-
import { Ok as Ok16 } from "@harness-engineering/core";
|
|
3743
|
+
import { Command as Command31 } from "commander";
|
|
3744
|
+
import * as path31 from "path";
|
|
3745
|
+
import * as fs19 from "fs";
|
|
2937
3746
|
function resolveSpecPath(implFile, implPattern, specPattern, cwd) {
|
|
2938
|
-
const relImpl =
|
|
3747
|
+
const relImpl = path31.relative(cwd, implFile);
|
|
2939
3748
|
const implBase = (implPattern.split("*")[0] ?? "").replace(/\/+$/, "");
|
|
2940
3749
|
const afterBase = relImpl.startsWith(implBase + "/") ? relImpl.slice(implBase.length + 1) : relImpl;
|
|
2941
3750
|
const segments = afterBase.split("/");
|
|
2942
3751
|
const firstSegment = segments[0] ?? "";
|
|
2943
|
-
const feature = segments.length > 1 ? firstSegment :
|
|
3752
|
+
const feature = segments.length > 1 ? firstSegment : path31.basename(firstSegment, path31.extname(firstSegment));
|
|
2944
3753
|
const specRelative = specPattern.replace("{feature}", feature);
|
|
2945
|
-
return
|
|
3754
|
+
return path31.resolve(cwd, specRelative);
|
|
2946
3755
|
}
|
|
2947
3756
|
async function runCheckPhaseGate(options) {
|
|
2948
3757
|
const configResult = resolveConfig(options.configPath);
|
|
@@ -2950,9 +3759,9 @@ async function runCheckPhaseGate(options) {
|
|
|
2950
3759
|
return configResult;
|
|
2951
3760
|
}
|
|
2952
3761
|
const config = configResult.value;
|
|
2953
|
-
const cwd = options.cwd ?? (options.configPath ?
|
|
3762
|
+
const cwd = options.cwd ?? (options.configPath ? path31.dirname(path31.resolve(options.configPath)) : process.cwd());
|
|
2954
3763
|
if (!config.phaseGates?.enabled) {
|
|
2955
|
-
return
|
|
3764
|
+
return Ok({
|
|
2956
3765
|
pass: true,
|
|
2957
3766
|
skipped: true,
|
|
2958
3767
|
missingSpecs: [],
|
|
@@ -2967,16 +3776,16 @@ async function runCheckPhaseGate(options) {
|
|
|
2967
3776
|
for (const implFile of implFiles) {
|
|
2968
3777
|
checkedFiles++;
|
|
2969
3778
|
const expectedSpec = resolveSpecPath(implFile, mapping.implPattern, mapping.specPattern, cwd);
|
|
2970
|
-
if (!
|
|
3779
|
+
if (!fs19.existsSync(expectedSpec)) {
|
|
2971
3780
|
missingSpecs.push({
|
|
2972
|
-
implFile:
|
|
2973
|
-
expectedSpec:
|
|
3781
|
+
implFile: path31.relative(cwd, implFile),
|
|
3782
|
+
expectedSpec: path31.relative(cwd, expectedSpec)
|
|
2974
3783
|
});
|
|
2975
3784
|
}
|
|
2976
3785
|
}
|
|
2977
3786
|
}
|
|
2978
3787
|
const pass = missingSpecs.length === 0;
|
|
2979
|
-
return
|
|
3788
|
+
return Ok({
|
|
2980
3789
|
pass,
|
|
2981
3790
|
skipped: false,
|
|
2982
3791
|
severity: phaseGates.severity,
|
|
@@ -2985,7 +3794,7 @@ async function runCheckPhaseGate(options) {
|
|
|
2985
3794
|
});
|
|
2986
3795
|
}
|
|
2987
3796
|
function createCheckPhaseGateCommand() {
|
|
2988
|
-
const command = new
|
|
3797
|
+
const command = new Command31("check-phase-gate").description("Verify that implementation files have matching spec documents").action(async (_opts, cmd) => {
|
|
2989
3798
|
const globalOpts = cmd.optsWithGlobals();
|
|
2990
3799
|
const mode = globalOpts.json ? OutputMode.JSON : globalOpts.quiet ? OutputMode.QUIET : globalOpts.verbose ? OutputMode.VERBOSE : OutputMode.TEXT;
|
|
2991
3800
|
const formatter = new OutputFormatter(mode);
|
|
@@ -3039,16 +3848,16 @@ function createCheckPhaseGateCommand() {
|
|
|
3039
3848
|
}
|
|
3040
3849
|
|
|
3041
3850
|
// src/commands/generate-slash-commands.ts
|
|
3042
|
-
import { Command as
|
|
3043
|
-
import
|
|
3044
|
-
import
|
|
3851
|
+
import { Command as Command32 } from "commander";
|
|
3852
|
+
import fs22 from "fs";
|
|
3853
|
+
import path34 from "path";
|
|
3045
3854
|
import os2 from "os";
|
|
3046
3855
|
import readline2 from "readline";
|
|
3047
3856
|
|
|
3048
3857
|
// src/slash-commands/normalize.ts
|
|
3049
|
-
import
|
|
3050
|
-
import
|
|
3051
|
-
import { parse as
|
|
3858
|
+
import fs20 from "fs";
|
|
3859
|
+
import path32 from "path";
|
|
3860
|
+
import { parse as parse8 } from "yaml";
|
|
3052
3861
|
|
|
3053
3862
|
// src/slash-commands/normalize-name.ts
|
|
3054
3863
|
function normalizeName(skillName) {
|
|
@@ -3068,18 +3877,18 @@ function normalizeSkills(skillSources, platforms) {
|
|
|
3068
3877
|
const specs = [];
|
|
3069
3878
|
const nameMap = /* @__PURE__ */ new Map();
|
|
3070
3879
|
for (const { dir: skillsDir, source } of skillSources) {
|
|
3071
|
-
if (!
|
|
3072
|
-
const entries =
|
|
3880
|
+
if (!fs20.existsSync(skillsDir)) continue;
|
|
3881
|
+
const entries = fs20.readdirSync(skillsDir, { withFileTypes: true }).filter((d) => d.isDirectory());
|
|
3073
3882
|
for (const entry of entries) {
|
|
3074
|
-
const yamlPath =
|
|
3075
|
-
if (!
|
|
3883
|
+
const yamlPath = path32.join(skillsDir, entry.name, "skill.yaml");
|
|
3884
|
+
if (!fs20.existsSync(yamlPath)) continue;
|
|
3076
3885
|
let raw;
|
|
3077
3886
|
try {
|
|
3078
|
-
raw =
|
|
3887
|
+
raw = fs20.readFileSync(yamlPath, "utf-8");
|
|
3079
3888
|
} catch {
|
|
3080
3889
|
continue;
|
|
3081
3890
|
}
|
|
3082
|
-
const parsed =
|
|
3891
|
+
const parsed = parse8(raw);
|
|
3083
3892
|
const result = SkillMetadataSchema.safeParse(parsed);
|
|
3084
3893
|
if (!result.success) {
|
|
3085
3894
|
console.warn(`Skipping ${entry.name}: invalid skill.yaml`);
|
|
@@ -3099,15 +3908,15 @@ function normalizeSkills(skillSources, platforms) {
|
|
|
3099
3908
|
continue;
|
|
3100
3909
|
}
|
|
3101
3910
|
nameMap.set(normalized, { skillName: meta.name, source });
|
|
3102
|
-
const skillMdPath =
|
|
3103
|
-
const skillMdContent =
|
|
3104
|
-
const skillMdRelative =
|
|
3911
|
+
const skillMdPath = path32.join(skillsDir, entry.name, "SKILL.md");
|
|
3912
|
+
const skillMdContent = fs20.existsSync(skillMdPath) ? fs20.readFileSync(skillMdPath, "utf-8") : "";
|
|
3913
|
+
const skillMdRelative = path32.relative(
|
|
3105
3914
|
process.cwd(),
|
|
3106
|
-
|
|
3915
|
+
path32.join(skillsDir, entry.name, "SKILL.md")
|
|
3107
3916
|
);
|
|
3108
|
-
const skillYamlRelative =
|
|
3917
|
+
const skillYamlRelative = path32.relative(
|
|
3109
3918
|
process.cwd(),
|
|
3110
|
-
|
|
3919
|
+
path32.join(skillsDir, entry.name, "skill.yaml")
|
|
3111
3920
|
);
|
|
3112
3921
|
const args = (meta.cli?.args ?? []).map((a) => ({
|
|
3113
3922
|
name: a.name,
|
|
@@ -3276,8 +4085,8 @@ function renderGemini(spec, skillMdContent, skillYamlContent) {
|
|
|
3276
4085
|
}
|
|
3277
4086
|
|
|
3278
4087
|
// src/slash-commands/sync.ts
|
|
3279
|
-
import
|
|
3280
|
-
import
|
|
4088
|
+
import fs21 from "fs";
|
|
4089
|
+
import path33 from "path";
|
|
3281
4090
|
|
|
3282
4091
|
// src/agent-definitions/constants.ts
|
|
3283
4092
|
var GENERATED_HEADER_AGENT = "<!-- Generated by harness generate-agent-definitions. Do not edit. -->";
|
|
@@ -3289,11 +4098,11 @@ function computeSyncPlan(outputDir, rendered) {
|
|
|
3289
4098
|
const removed = [];
|
|
3290
4099
|
const unchanged = [];
|
|
3291
4100
|
for (const [filename, content] of rendered) {
|
|
3292
|
-
const filePath =
|
|
3293
|
-
if (!
|
|
4101
|
+
const filePath = path33.join(outputDir, filename);
|
|
4102
|
+
if (!fs21.existsSync(filePath)) {
|
|
3294
4103
|
added.push(filename);
|
|
3295
4104
|
} else {
|
|
3296
|
-
const existing =
|
|
4105
|
+
const existing = fs21.readFileSync(filePath, "utf-8");
|
|
3297
4106
|
if (existing === content) {
|
|
3298
4107
|
unchanged.push(filename);
|
|
3299
4108
|
} else {
|
|
@@ -3301,14 +4110,14 @@ function computeSyncPlan(outputDir, rendered) {
|
|
|
3301
4110
|
}
|
|
3302
4111
|
}
|
|
3303
4112
|
}
|
|
3304
|
-
if (
|
|
3305
|
-
const existing =
|
|
3306
|
-
const stat =
|
|
4113
|
+
if (fs21.existsSync(outputDir)) {
|
|
4114
|
+
const existing = fs21.readdirSync(outputDir).filter((f) => {
|
|
4115
|
+
const stat = fs21.statSync(path33.join(outputDir, f));
|
|
3307
4116
|
return stat.isFile();
|
|
3308
4117
|
});
|
|
3309
4118
|
for (const filename of existing) {
|
|
3310
4119
|
if (rendered.has(filename)) continue;
|
|
3311
|
-
const content =
|
|
4120
|
+
const content = fs21.readFileSync(path33.join(outputDir, filename), "utf-8");
|
|
3312
4121
|
if (content.includes(GENERATED_HEADER_CLAUDE) || content.includes(GENERATED_HEADER_GEMINI) || content.includes(GENERATED_HEADER_AGENT)) {
|
|
3313
4122
|
removed.push(filename);
|
|
3314
4123
|
}
|
|
@@ -3317,18 +4126,18 @@ function computeSyncPlan(outputDir, rendered) {
|
|
|
3317
4126
|
return { added, updated, removed, unchanged };
|
|
3318
4127
|
}
|
|
3319
4128
|
function applySyncPlan(outputDir, rendered, plan, deleteOrphans) {
|
|
3320
|
-
|
|
4129
|
+
fs21.mkdirSync(outputDir, { recursive: true });
|
|
3321
4130
|
for (const filename of [...plan.added, ...plan.updated]) {
|
|
3322
4131
|
const content = rendered.get(filename);
|
|
3323
4132
|
if (content !== void 0) {
|
|
3324
|
-
|
|
4133
|
+
fs21.writeFileSync(path33.join(outputDir, filename), content);
|
|
3325
4134
|
}
|
|
3326
4135
|
}
|
|
3327
4136
|
if (deleteOrphans) {
|
|
3328
4137
|
for (const filename of plan.removed) {
|
|
3329
|
-
const filePath =
|
|
3330
|
-
if (
|
|
3331
|
-
|
|
4138
|
+
const filePath = path33.join(outputDir, filename);
|
|
4139
|
+
if (fs21.existsSync(filePath)) {
|
|
4140
|
+
fs21.unlinkSync(filePath);
|
|
3332
4141
|
}
|
|
3333
4142
|
}
|
|
3334
4143
|
}
|
|
@@ -3337,24 +4146,24 @@ function applySyncPlan(outputDir, rendered, plan, deleteOrphans) {
|
|
|
3337
4146
|
// src/commands/generate-slash-commands.ts
|
|
3338
4147
|
function resolveOutputDir(platform, opts) {
|
|
3339
4148
|
if (opts.output) {
|
|
3340
|
-
return
|
|
4149
|
+
return path34.join(opts.output, "harness");
|
|
3341
4150
|
}
|
|
3342
4151
|
if (opts.global) {
|
|
3343
4152
|
const home = os2.homedir();
|
|
3344
|
-
return platform === "claude-code" ?
|
|
4153
|
+
return platform === "claude-code" ? path34.join(home, ".claude", "commands", "harness") : path34.join(home, ".gemini", "commands", "harness");
|
|
3345
4154
|
}
|
|
3346
|
-
return platform === "claude-code" ?
|
|
4155
|
+
return platform === "claude-code" ? path34.join("agents", "commands", "claude-code", "harness") : path34.join("agents", "commands", "gemini-cli", "harness");
|
|
3347
4156
|
}
|
|
3348
4157
|
function fileExtension(platform) {
|
|
3349
4158
|
return platform === "claude-code" ? ".md" : ".toml";
|
|
3350
4159
|
}
|
|
3351
4160
|
async function confirmDeletion(files) {
|
|
3352
4161
|
const rl = readline2.createInterface({ input: process.stdin, output: process.stdout });
|
|
3353
|
-
return new Promise((
|
|
4162
|
+
return new Promise((resolve24) => {
|
|
3354
4163
|
rl.question(`
|
|
3355
4164
|
Remove ${files.length} orphaned command(s)? (y/N) `, (answer) => {
|
|
3356
4165
|
rl.close();
|
|
3357
|
-
|
|
4166
|
+
resolve24(answer.toLowerCase() === "y");
|
|
3358
4167
|
});
|
|
3359
4168
|
});
|
|
3360
4169
|
}
|
|
@@ -3369,7 +4178,7 @@ function generateSlashCommands(opts) {
|
|
|
3369
4178
|
}
|
|
3370
4179
|
if (opts.includeGlobal || skillSources.length === 0) {
|
|
3371
4180
|
const globalDir = resolveGlobalSkillsDir();
|
|
3372
|
-
if (!projectDir ||
|
|
4181
|
+
if (!projectDir || path34.resolve(globalDir) !== path34.resolve(projectDir)) {
|
|
3373
4182
|
skillSources.push({ dir: globalDir, source: "global" });
|
|
3374
4183
|
}
|
|
3375
4184
|
}
|
|
@@ -3391,7 +4200,7 @@ function generateSlashCommands(opts) {
|
|
|
3391
4200
|
executionContext: spec.prompt.executionContext.split("\n").map((line) => {
|
|
3392
4201
|
if (line.startsWith("@")) {
|
|
3393
4202
|
const relPath = line.slice(1);
|
|
3394
|
-
return `@${
|
|
4203
|
+
return `@${path34.resolve(relPath)}`;
|
|
3395
4204
|
}
|
|
3396
4205
|
return line;
|
|
3397
4206
|
}).join("\n")
|
|
@@ -3399,10 +4208,10 @@ function generateSlashCommands(opts) {
|
|
|
3399
4208
|
} : spec;
|
|
3400
4209
|
rendered.set(filename, renderClaudeCode(renderSpec));
|
|
3401
4210
|
} else {
|
|
3402
|
-
const mdPath =
|
|
3403
|
-
const yamlPath =
|
|
3404
|
-
const mdContent =
|
|
3405
|
-
const yamlContent =
|
|
4211
|
+
const mdPath = path34.join(spec.skillsBaseDir, spec.sourceDir, "SKILL.md");
|
|
4212
|
+
const yamlPath = path34.join(spec.skillsBaseDir, spec.sourceDir, "skill.yaml");
|
|
4213
|
+
const mdContent = fs22.existsSync(mdPath) ? fs22.readFileSync(mdPath, "utf-8") : "";
|
|
4214
|
+
const yamlContent = fs22.existsSync(yamlPath) ? fs22.readFileSync(yamlPath, "utf-8") : "";
|
|
3406
4215
|
rendered.set(filename, renderGemini(spec, mdContent, yamlContent));
|
|
3407
4216
|
}
|
|
3408
4217
|
}
|
|
@@ -3428,16 +4237,16 @@ async function handleOrphanDeletion(results, opts) {
|
|
|
3428
4237
|
const shouldDelete = opts.yes || await confirmDeletion(result.removed);
|
|
3429
4238
|
if (shouldDelete) {
|
|
3430
4239
|
for (const filename of result.removed) {
|
|
3431
|
-
const filePath =
|
|
3432
|
-
if (
|
|
3433
|
-
|
|
4240
|
+
const filePath = path34.join(result.outputDir, filename);
|
|
4241
|
+
if (fs22.existsSync(filePath)) {
|
|
4242
|
+
fs22.unlinkSync(filePath);
|
|
3434
4243
|
}
|
|
3435
4244
|
}
|
|
3436
4245
|
}
|
|
3437
4246
|
}
|
|
3438
4247
|
}
|
|
3439
4248
|
function createGenerateSlashCommandsCommand() {
|
|
3440
|
-
return new
|
|
4249
|
+
return new Command32("generate-slash-commands").description(
|
|
3441
4250
|
"Generate native slash commands for Claude Code and Gemini CLI from skill metadata"
|
|
3442
4251
|
).option("--platforms <list>", "Target platforms (comma-separated)", "claude-code,gemini-cli").option("--global", "Write to global config directories", false).option("--include-global", "Include built-in global skills alongside project skills", false).option("--output <dir>", "Custom output directory").option("--skills-dir <path>", "Skills directory to scan").option("--dry-run", "Show what would change without writing", false).option("--yes", "Skip deletion confirmation prompts", false).action(async (opts, cmd) => {
|
|
3443
4252
|
const globalOpts = cmd.optsWithGlobals();
|
|
@@ -3502,11 +4311,10 @@ ${result.platform} \u2192 ${result.outputDir}`);
|
|
|
3502
4311
|
}
|
|
3503
4312
|
|
|
3504
4313
|
// src/commands/ci/index.ts
|
|
3505
|
-
import { Command as
|
|
4314
|
+
import { Command as Command35 } from "commander";
|
|
3506
4315
|
|
|
3507
4316
|
// src/commands/ci/check.ts
|
|
3508
|
-
import { Command as
|
|
3509
|
-
import { runCIChecks } from "@harness-engineering/core";
|
|
4317
|
+
import { Command as Command33 } from "commander";
|
|
3510
4318
|
var VALID_CHECKS = ["validate", "deps", "docs", "entropy", "phase-gate"];
|
|
3511
4319
|
async function runCICheck(options) {
|
|
3512
4320
|
const configResult = resolveConfig(options.configPath);
|
|
@@ -3537,7 +4345,7 @@ function parseFailOn(failOn) {
|
|
|
3537
4345
|
return "error";
|
|
3538
4346
|
}
|
|
3539
4347
|
function createCheckCommand() {
|
|
3540
|
-
return new
|
|
4348
|
+
return new Command33("check").description("Run all harness checks for CI (validate, deps, docs, entropy, phase-gate)").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) => {
|
|
3541
4349
|
const globalOpts = cmd.optsWithGlobals();
|
|
3542
4350
|
const mode = globalOpts.json ? OutputMode.JSON : globalOpts.quiet ? OutputMode.QUIET : globalOpts.verbose ? OutputMode.VERBOSE : OutputMode.TEXT;
|
|
3543
4351
|
const skip = parseSkip(opts.skip);
|
|
@@ -3581,10 +4389,9 @@ function createCheckCommand() {
|
|
|
3581
4389
|
}
|
|
3582
4390
|
|
|
3583
4391
|
// src/commands/ci/init.ts
|
|
3584
|
-
import { Command as
|
|
3585
|
-
import * as
|
|
3586
|
-
import * as
|
|
3587
|
-
import { Ok as Ok17, Err as Err14 } from "@harness-engineering/core";
|
|
4392
|
+
import { Command as Command34 } from "commander";
|
|
4393
|
+
import * as fs23 from "fs";
|
|
4394
|
+
import * as path35 from "path";
|
|
3588
4395
|
var ALL_CHECKS = ["validate", "deps", "docs", "entropy", "phase-gate"];
|
|
3589
4396
|
function buildSkipFlag(checks) {
|
|
3590
4397
|
if (!checks) return "";
|
|
@@ -3668,20 +4475,20 @@ function generateCIConfig(options) {
|
|
|
3668
4475
|
};
|
|
3669
4476
|
const entry = generators[platform];
|
|
3670
4477
|
if (!entry) {
|
|
3671
|
-
return
|
|
4478
|
+
return Err(new CLIError(`Unknown platform: ${platform}`, ExitCode.ERROR));
|
|
3672
4479
|
}
|
|
3673
|
-
return
|
|
4480
|
+
return Ok({
|
|
3674
4481
|
filename: entry.filename,
|
|
3675
4482
|
content: entry.generate(skipFlag)
|
|
3676
4483
|
});
|
|
3677
4484
|
}
|
|
3678
4485
|
function detectPlatform() {
|
|
3679
|
-
if (
|
|
3680
|
-
if (
|
|
4486
|
+
if (fs23.existsSync(".github")) return "github";
|
|
4487
|
+
if (fs23.existsSync(".gitlab-ci.yml")) return "gitlab";
|
|
3681
4488
|
return null;
|
|
3682
4489
|
}
|
|
3683
4490
|
function createInitCommand2() {
|
|
3684
|
-
return new
|
|
4491
|
+
return new Command34("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) => {
|
|
3685
4492
|
const globalOpts = cmd.optsWithGlobals();
|
|
3686
4493
|
const platform = opts.platform ?? detectPlatform() ?? "generic";
|
|
3687
4494
|
const checks = opts.checks ? opts.checks.split(",").map((s) => s.trim()) : void 0;
|
|
@@ -3693,12 +4500,12 @@ function createInitCommand2() {
|
|
|
3693
4500
|
process.exit(result.error.exitCode);
|
|
3694
4501
|
}
|
|
3695
4502
|
const { filename, content } = result.value;
|
|
3696
|
-
const targetPath =
|
|
3697
|
-
const dir =
|
|
3698
|
-
|
|
3699
|
-
|
|
4503
|
+
const targetPath = path35.resolve(filename);
|
|
4504
|
+
const dir = path35.dirname(targetPath);
|
|
4505
|
+
fs23.mkdirSync(dir, { recursive: true });
|
|
4506
|
+
fs23.writeFileSync(targetPath, content);
|
|
3700
4507
|
if (platform === "generic") {
|
|
3701
|
-
|
|
4508
|
+
fs23.chmodSync(targetPath, "755");
|
|
3702
4509
|
}
|
|
3703
4510
|
if (globalOpts.json) {
|
|
3704
4511
|
console.log(JSON.stringify({ file: filename, platform }));
|
|
@@ -3711,15 +4518,15 @@ function createInitCommand2() {
|
|
|
3711
4518
|
|
|
3712
4519
|
// src/commands/ci/index.ts
|
|
3713
4520
|
function createCICommand() {
|
|
3714
|
-
const command = new
|
|
4521
|
+
const command = new Command35("ci").description("CI/CD integration commands");
|
|
3715
4522
|
command.addCommand(createCheckCommand());
|
|
3716
4523
|
command.addCommand(createInitCommand2());
|
|
3717
4524
|
return command;
|
|
3718
4525
|
}
|
|
3719
4526
|
|
|
3720
4527
|
// src/commands/update.ts
|
|
3721
|
-
import { Command as
|
|
3722
|
-
import { execSync as
|
|
4528
|
+
import { Command as Command36 } from "commander";
|
|
4529
|
+
import { execSync as execSync4 } from "child_process";
|
|
3723
4530
|
import { realpathSync } from "fs";
|
|
3724
4531
|
import readline3 from "readline";
|
|
3725
4532
|
import chalk4 from "chalk";
|
|
@@ -3739,7 +4546,7 @@ function detectPackageManager() {
|
|
|
3739
4546
|
return "npm";
|
|
3740
4547
|
}
|
|
3741
4548
|
function getLatestVersion(pkg = "@harness-engineering/cli") {
|
|
3742
|
-
const output =
|
|
4549
|
+
const output = execSync4(`npm view ${pkg} dist-tags.latest`, {
|
|
3743
4550
|
encoding: "utf-8",
|
|
3744
4551
|
timeout: 15e3
|
|
3745
4552
|
});
|
|
@@ -3747,7 +4554,7 @@ function getLatestVersion(pkg = "@harness-engineering/cli") {
|
|
|
3747
4554
|
}
|
|
3748
4555
|
function getInstalledVersion(pm) {
|
|
3749
4556
|
try {
|
|
3750
|
-
const output =
|
|
4557
|
+
const output = execSync4(`${pm} list -g @harness-engineering/cli --json`, {
|
|
3751
4558
|
encoding: "utf-8",
|
|
3752
4559
|
timeout: 15e3
|
|
3753
4560
|
});
|
|
@@ -3760,7 +4567,7 @@ function getInstalledVersion(pm) {
|
|
|
3760
4567
|
}
|
|
3761
4568
|
function getInstalledPackages(pm) {
|
|
3762
4569
|
try {
|
|
3763
|
-
const output =
|
|
4570
|
+
const output = execSync4(`${pm} list -g --json`, {
|
|
3764
4571
|
encoding: "utf-8",
|
|
3765
4572
|
timeout: 15e3
|
|
3766
4573
|
});
|
|
@@ -3776,15 +4583,15 @@ function prompt(question) {
|
|
|
3776
4583
|
input: process.stdin,
|
|
3777
4584
|
output: process.stdout
|
|
3778
4585
|
});
|
|
3779
|
-
return new Promise((
|
|
4586
|
+
return new Promise((resolve24) => {
|
|
3780
4587
|
rl.question(question, (answer) => {
|
|
3781
4588
|
rl.close();
|
|
3782
|
-
|
|
4589
|
+
resolve24(answer.trim().toLowerCase());
|
|
3783
4590
|
});
|
|
3784
4591
|
});
|
|
3785
4592
|
}
|
|
3786
4593
|
function createUpdateCommand() {
|
|
3787
|
-
return new
|
|
4594
|
+
return new Command36("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) => {
|
|
3788
4595
|
const globalOpts = cmd.optsWithGlobals();
|
|
3789
4596
|
const pm = detectPackageManager();
|
|
3790
4597
|
if (globalOpts.verbose) {
|
|
@@ -3827,7 +4634,7 @@ function createUpdateCommand() {
|
|
|
3827
4634
|
}
|
|
3828
4635
|
try {
|
|
3829
4636
|
logger.info("Updating packages...");
|
|
3830
|
-
|
|
4637
|
+
execSync4(installCmd, { stdio: "inherit", timeout: 12e4 });
|
|
3831
4638
|
console.log("");
|
|
3832
4639
|
logger.success("Update complete");
|
|
3833
4640
|
} catch {
|
|
@@ -3837,15 +4644,15 @@ function createUpdateCommand() {
|
|
|
3837
4644
|
process.exit(ExitCode.ERROR);
|
|
3838
4645
|
}
|
|
3839
4646
|
console.log("");
|
|
3840
|
-
const regenAnswer = await prompt("Regenerate slash commands? (y/N) ");
|
|
4647
|
+
const regenAnswer = await prompt("Regenerate slash commands and agent definitions? (y/N) ");
|
|
3841
4648
|
if (regenAnswer === "y" || regenAnswer === "yes") {
|
|
3842
4649
|
const scopeAnswer = await prompt("Generate for (g)lobal or (l)ocal project? (g/l) ");
|
|
3843
4650
|
const globalFlag = scopeAnswer === "g" || scopeAnswer === "global" ? " --global" : "";
|
|
3844
4651
|
try {
|
|
3845
|
-
|
|
4652
|
+
execSync4(`harness generate${globalFlag}`, { stdio: "inherit" });
|
|
3846
4653
|
} catch {
|
|
3847
|
-
logger.warn("
|
|
3848
|
-
console.log(` ${chalk4.cyan(`harness generate
|
|
4654
|
+
logger.warn("Generation failed. Run manually:");
|
|
4655
|
+
console.log(` ${chalk4.cyan(`harness generate${globalFlag}`)}`);
|
|
3849
4656
|
}
|
|
3850
4657
|
}
|
|
3851
4658
|
process.exit(ExitCode.SUCCESS);
|
|
@@ -3853,9 +4660,9 @@ function createUpdateCommand() {
|
|
|
3853
4660
|
}
|
|
3854
4661
|
|
|
3855
4662
|
// src/commands/generate-agent-definitions.ts
|
|
3856
|
-
import { Command as
|
|
3857
|
-
import * as
|
|
3858
|
-
import * as
|
|
4663
|
+
import { Command as Command37 } from "commander";
|
|
4664
|
+
import * as fs24 from "fs";
|
|
4665
|
+
import * as path36 from "path";
|
|
3859
4666
|
import * as os3 from "os";
|
|
3860
4667
|
|
|
3861
4668
|
// src/agent-definitions/generator.ts
|
|
@@ -3991,19 +4798,19 @@ function renderGeminiAgent(def) {
|
|
|
3991
4798
|
// src/commands/generate-agent-definitions.ts
|
|
3992
4799
|
function resolveOutputDir2(platform, opts) {
|
|
3993
4800
|
if (opts.output) {
|
|
3994
|
-
return platform === "claude-code" ?
|
|
4801
|
+
return platform === "claude-code" ? path36.join(opts.output, "claude-code") : path36.join(opts.output, "gemini-cli");
|
|
3995
4802
|
}
|
|
3996
4803
|
if (opts.global) {
|
|
3997
4804
|
const home = os3.homedir();
|
|
3998
|
-
return platform === "claude-code" ?
|
|
4805
|
+
return platform === "claude-code" ? path36.join(home, ".claude", "agents") : path36.join(home, ".gemini", "agents");
|
|
3999
4806
|
}
|
|
4000
|
-
return platform === "claude-code" ?
|
|
4807
|
+
return platform === "claude-code" ? path36.join("agents", "agents", "claude-code") : path36.join("agents", "agents", "gemini-cli");
|
|
4001
4808
|
}
|
|
4002
4809
|
function loadSkillContent(skillName) {
|
|
4003
4810
|
const skillsDir = resolveSkillsDir();
|
|
4004
|
-
const skillMdPath =
|
|
4005
|
-
if (!
|
|
4006
|
-
return
|
|
4811
|
+
const skillMdPath = path36.join(skillsDir, skillName, "SKILL.md");
|
|
4812
|
+
if (!fs24.existsSync(skillMdPath)) return null;
|
|
4813
|
+
return fs24.readFileSync(skillMdPath, "utf-8");
|
|
4007
4814
|
}
|
|
4008
4815
|
function getRenderer(platform) {
|
|
4009
4816
|
return platform === "claude-code" ? renderClaudeCodeAgent : renderGeminiAgent;
|
|
@@ -4047,7 +4854,7 @@ function generateAgentDefinitions(opts) {
|
|
|
4047
4854
|
return results;
|
|
4048
4855
|
}
|
|
4049
4856
|
function createGenerateAgentDefinitionsCommand() {
|
|
4050
|
-
return new
|
|
4857
|
+
return new Command37("generate-agent-definitions").description("Generate agent definition files from personas for Claude Code and Gemini CLI").option("--platforms <list>", "Target platforms (comma-separated)", "claude-code,gemini-cli").option("--global", "Write to global agent directories", false).option("--output <dir>", "Custom output directory").option("--dry-run", "Show what would change without writing", false).action(async (opts, cmd) => {
|
|
4051
4858
|
const globalOpts = cmd.optsWithGlobals();
|
|
4052
4859
|
const platforms = opts.platforms.split(",").map((p) => p.trim());
|
|
4053
4860
|
for (const p of platforms) {
|
|
@@ -4095,9 +4902,9 @@ ${result.platform} \u2192 ${result.outputDir}`);
|
|
|
4095
4902
|
}
|
|
4096
4903
|
|
|
4097
4904
|
// src/commands/generate.ts
|
|
4098
|
-
import { Command as
|
|
4905
|
+
import { Command as Command38 } from "commander";
|
|
4099
4906
|
function createGenerateCommand3() {
|
|
4100
|
-
return new
|
|
4907
|
+
return new Command38("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) => {
|
|
4101
4908
|
const globalOpts = cmd.optsWithGlobals();
|
|
4102
4909
|
const platforms = opts.platforms.split(",").map((p) => p.trim());
|
|
4103
4910
|
for (const p of platforms) {
|
|
@@ -4156,10 +4963,10 @@ function createGenerateCommand3() {
|
|
|
4156
4963
|
}
|
|
4157
4964
|
|
|
4158
4965
|
// src/commands/graph/scan.ts
|
|
4159
|
-
import { Command as
|
|
4160
|
-
import * as
|
|
4966
|
+
import { Command as Command39 } from "commander";
|
|
4967
|
+
import * as path37 from "path";
|
|
4161
4968
|
async function runScan(projectPath) {
|
|
4162
|
-
const { GraphStore, CodeIngestor, TopologicalLinker, KnowledgeIngestor, GitIngestor } = await import("
|
|
4969
|
+
const { GraphStore, CodeIngestor, TopologicalLinker, KnowledgeIngestor, GitIngestor } = await import("./dist-4MYPT3OE.js");
|
|
4163
4970
|
const store = new GraphStore();
|
|
4164
4971
|
const start = Date.now();
|
|
4165
4972
|
await new CodeIngestor(store).ingest(projectPath);
|
|
@@ -4170,13 +4977,13 @@ async function runScan(projectPath) {
|
|
|
4170
4977
|
await new GitIngestor(store).ingest(projectPath);
|
|
4171
4978
|
} catch {
|
|
4172
4979
|
}
|
|
4173
|
-
const graphDir =
|
|
4980
|
+
const graphDir = path37.join(projectPath, ".harness", "graph");
|
|
4174
4981
|
await store.save(graphDir);
|
|
4175
4982
|
return { nodeCount: store.nodeCount, edgeCount: store.edgeCount, durationMs: Date.now() - start };
|
|
4176
4983
|
}
|
|
4177
4984
|
function createScanCommand() {
|
|
4178
|
-
return new
|
|
4179
|
-
const projectPath =
|
|
4985
|
+
return new Command39("scan").description("Scan project and build knowledge graph").argument("[path]", "Project root path", ".").action(async (inputPath, _opts, cmd) => {
|
|
4986
|
+
const projectPath = path37.resolve(inputPath);
|
|
4180
4987
|
const globalOpts = cmd.optsWithGlobals();
|
|
4181
4988
|
try {
|
|
4182
4989
|
const result = await runScan(projectPath);
|
|
@@ -4195,13 +5002,13 @@ function createScanCommand() {
|
|
|
4195
5002
|
}
|
|
4196
5003
|
|
|
4197
5004
|
// src/commands/graph/ingest.ts
|
|
4198
|
-
import { Command as
|
|
4199
|
-
import * as
|
|
5005
|
+
import { Command as Command40 } from "commander";
|
|
5006
|
+
import * as path38 from "path";
|
|
4200
5007
|
async function loadConnectorConfig(projectPath, source) {
|
|
4201
5008
|
try {
|
|
4202
|
-
const
|
|
4203
|
-
const configPath =
|
|
4204
|
-
const config = JSON.parse(await
|
|
5009
|
+
const fs25 = await import("fs/promises");
|
|
5010
|
+
const configPath = path38.join(projectPath, "harness.config.json");
|
|
5011
|
+
const config = JSON.parse(await fs25.readFile(configPath, "utf-8"));
|
|
4205
5012
|
const connector = config.graph?.connectors?.find(
|
|
4206
5013
|
(c) => c.source === source
|
|
4207
5014
|
);
|
|
@@ -4240,8 +5047,8 @@ async function runIngest(projectPath, source, opts) {
|
|
|
4240
5047
|
SyncManager,
|
|
4241
5048
|
JiraConnector,
|
|
4242
5049
|
SlackConnector
|
|
4243
|
-
} = await import("
|
|
4244
|
-
const graphDir =
|
|
5050
|
+
} = await import("./dist-4MYPT3OE.js");
|
|
5051
|
+
const graphDir = path38.join(projectPath, ".harness", "graph");
|
|
4245
5052
|
const store = new GraphStore();
|
|
4246
5053
|
await store.load(graphDir);
|
|
4247
5054
|
if (opts?.all) {
|
|
@@ -4302,13 +5109,13 @@ async function runIngest(projectPath, source, opts) {
|
|
|
4302
5109
|
return result;
|
|
4303
5110
|
}
|
|
4304
5111
|
function createIngestCommand() {
|
|
4305
|
-
return new
|
|
5112
|
+
return new Command40("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) => {
|
|
4306
5113
|
if (!opts.source && !opts.all) {
|
|
4307
5114
|
console.error("Error: --source or --all is required");
|
|
4308
5115
|
process.exit(1);
|
|
4309
5116
|
}
|
|
4310
5117
|
const globalOpts = cmd.optsWithGlobals();
|
|
4311
|
-
const projectPath =
|
|
5118
|
+
const projectPath = path38.resolve(globalOpts.config ? path38.dirname(globalOpts.config) : ".");
|
|
4312
5119
|
try {
|
|
4313
5120
|
const result = await runIngest(projectPath, opts.source ?? "", {
|
|
4314
5121
|
full: opts.full,
|
|
@@ -4330,12 +5137,12 @@ function createIngestCommand() {
|
|
|
4330
5137
|
}
|
|
4331
5138
|
|
|
4332
5139
|
// src/commands/graph/query.ts
|
|
4333
|
-
import { Command as
|
|
4334
|
-
import * as
|
|
5140
|
+
import { Command as Command41 } from "commander";
|
|
5141
|
+
import * as path39 from "path";
|
|
4335
5142
|
async function runQuery(projectPath, rootNodeId, opts) {
|
|
4336
|
-
const { GraphStore, ContextQL } = await import("
|
|
5143
|
+
const { GraphStore, ContextQL } = await import("./dist-4MYPT3OE.js");
|
|
4337
5144
|
const store = new GraphStore();
|
|
4338
|
-
const graphDir =
|
|
5145
|
+
const graphDir = path39.join(projectPath, ".harness", "graph");
|
|
4339
5146
|
const loaded = await store.load(graphDir);
|
|
4340
5147
|
if (!loaded) throw new Error("No graph found. Run `harness scan` first.");
|
|
4341
5148
|
const params = {
|
|
@@ -4349,9 +5156,9 @@ async function runQuery(projectPath, rootNodeId, opts) {
|
|
|
4349
5156
|
return cql.execute(params);
|
|
4350
5157
|
}
|
|
4351
5158
|
function createQueryCommand() {
|
|
4352
|
-
return new
|
|
5159
|
+
return new Command41("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) => {
|
|
4353
5160
|
const globalOpts = cmd.optsWithGlobals();
|
|
4354
|
-
const projectPath =
|
|
5161
|
+
const projectPath = path39.resolve(globalOpts.config ? path39.dirname(globalOpts.config) : ".");
|
|
4355
5162
|
try {
|
|
4356
5163
|
const result = await runQuery(projectPath, rootNodeId, {
|
|
4357
5164
|
depth: parseInt(opts.depth),
|
|
@@ -4377,21 +5184,21 @@ function createQueryCommand() {
|
|
|
4377
5184
|
}
|
|
4378
5185
|
|
|
4379
5186
|
// src/commands/graph/index.ts
|
|
4380
|
-
import { Command as
|
|
5187
|
+
import { Command as Command42 } from "commander";
|
|
4381
5188
|
|
|
4382
5189
|
// src/commands/graph/status.ts
|
|
4383
|
-
import * as
|
|
5190
|
+
import * as path40 from "path";
|
|
4384
5191
|
async function runGraphStatus(projectPath) {
|
|
4385
|
-
const { GraphStore } = await import("
|
|
4386
|
-
const graphDir =
|
|
5192
|
+
const { GraphStore } = await import("./dist-4MYPT3OE.js");
|
|
5193
|
+
const graphDir = path40.join(projectPath, ".harness", "graph");
|
|
4387
5194
|
const store = new GraphStore();
|
|
4388
5195
|
const loaded = await store.load(graphDir);
|
|
4389
5196
|
if (!loaded) return { status: "no_graph", message: "No graph found. Run `harness scan` first." };
|
|
4390
|
-
const
|
|
4391
|
-
const metaPath =
|
|
5197
|
+
const fs25 = await import("fs/promises");
|
|
5198
|
+
const metaPath = path40.join(graphDir, "metadata.json");
|
|
4392
5199
|
let lastScan = "unknown";
|
|
4393
5200
|
try {
|
|
4394
|
-
const meta = JSON.parse(await
|
|
5201
|
+
const meta = JSON.parse(await fs25.readFile(metaPath, "utf-8"));
|
|
4395
5202
|
lastScan = meta.lastScanTimestamp;
|
|
4396
5203
|
} catch {
|
|
4397
5204
|
}
|
|
@@ -4402,8 +5209,8 @@ async function runGraphStatus(projectPath) {
|
|
|
4402
5209
|
}
|
|
4403
5210
|
let connectorSyncStatus = {};
|
|
4404
5211
|
try {
|
|
4405
|
-
const syncMetaPath =
|
|
4406
|
-
const syncMeta = JSON.parse(await
|
|
5212
|
+
const syncMetaPath = path40.join(graphDir, "sync-metadata.json");
|
|
5213
|
+
const syncMeta = JSON.parse(await fs25.readFile(syncMetaPath, "utf-8"));
|
|
4407
5214
|
for (const [name, data] of Object.entries(syncMeta.connectors ?? {})) {
|
|
4408
5215
|
connectorSyncStatus[name] = data.lastSyncTimestamp;
|
|
4409
5216
|
}
|
|
@@ -4420,10 +5227,10 @@ async function runGraphStatus(projectPath) {
|
|
|
4420
5227
|
}
|
|
4421
5228
|
|
|
4422
5229
|
// src/commands/graph/export.ts
|
|
4423
|
-
import * as
|
|
5230
|
+
import * as path41 from "path";
|
|
4424
5231
|
async function runGraphExport(projectPath, format) {
|
|
4425
|
-
const { GraphStore } = await import("
|
|
4426
|
-
const graphDir =
|
|
5232
|
+
const { GraphStore } = await import("./dist-4MYPT3OE.js");
|
|
5233
|
+
const graphDir = path41.join(projectPath, ".harness", "graph");
|
|
4427
5234
|
const store = new GraphStore();
|
|
4428
5235
|
const loaded = await store.load(graphDir);
|
|
4429
5236
|
if (!loaded) throw new Error("No graph found. Run `harness scan` first.");
|
|
@@ -4452,13 +5259,13 @@ async function runGraphExport(projectPath, format) {
|
|
|
4452
5259
|
}
|
|
4453
5260
|
|
|
4454
5261
|
// src/commands/graph/index.ts
|
|
4455
|
-
import * as
|
|
5262
|
+
import * as path42 from "path";
|
|
4456
5263
|
function createGraphCommand() {
|
|
4457
|
-
const graph = new
|
|
5264
|
+
const graph = new Command42("graph").description("Knowledge graph management");
|
|
4458
5265
|
graph.command("status").description("Show graph statistics").action(async (_opts, cmd) => {
|
|
4459
5266
|
try {
|
|
4460
5267
|
const globalOpts = cmd.optsWithGlobals();
|
|
4461
|
-
const projectPath =
|
|
5268
|
+
const projectPath = path42.resolve(globalOpts.config ? path42.dirname(globalOpts.config) : ".");
|
|
4462
5269
|
const result = await runGraphStatus(projectPath);
|
|
4463
5270
|
if (globalOpts.json) {
|
|
4464
5271
|
console.log(JSON.stringify(result, null, 2));
|
|
@@ -4485,7 +5292,7 @@ function createGraphCommand() {
|
|
|
4485
5292
|
});
|
|
4486
5293
|
graph.command("export").description("Export graph").requiredOption("--format <format>", "Output format (json, mermaid)").action(async (opts, cmd) => {
|
|
4487
5294
|
const globalOpts = cmd.optsWithGlobals();
|
|
4488
|
-
const projectPath =
|
|
5295
|
+
const projectPath = path42.resolve(globalOpts.config ? path42.dirname(globalOpts.config) : ".");
|
|
4489
5296
|
try {
|
|
4490
5297
|
const output = await runGraphExport(projectPath, opts.format);
|
|
4491
5298
|
console.log(output);
|
|
@@ -4499,11 +5306,14 @@ function createGraphCommand() {
|
|
|
4499
5306
|
|
|
4500
5307
|
// src/index.ts
|
|
4501
5308
|
function createProgram() {
|
|
4502
|
-
const program = new
|
|
5309
|
+
const program = new Command43();
|
|
4503
5310
|
program.name("harness").description("CLI for Harness Engineering toolkit").version(VERSION).option("-c, --config <path>", "Path to config file").option("--json", "Output as JSON").option("--verbose", "Verbose output").option("--quiet", "Minimal output");
|
|
4504
5311
|
program.addCommand(createValidateCommand());
|
|
4505
5312
|
program.addCommand(createCheckDepsCommand());
|
|
4506
5313
|
program.addCommand(createCheckDocsCommand());
|
|
5314
|
+
program.addCommand(createCheckPerfCommand());
|
|
5315
|
+
program.addCommand(createCheckSecurityCommand());
|
|
5316
|
+
program.addCommand(createPerfCommand());
|
|
4507
5317
|
program.addCommand(createInitCommand());
|
|
4508
5318
|
program.addCommand(createCleanupCommand());
|
|
4509
5319
|
program.addCommand(createFixDriftCommand());
|