@harness-engineering/cli 1.6.0 → 1.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/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 +1 -1
- package/dist/{chunk-VS4OTOKZ.js → chunk-O6NEKDYP.js} +789 -299
- package/dist/index.js +1 -1
- package/package.json +2 -2
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
} from "./chunk-ACMDUQJG.js";
|
|
9
9
|
|
|
10
10
|
// src/index.ts
|
|
11
|
-
import { Command as
|
|
11
|
+
import { Command as Command43 } from "commander";
|
|
12
12
|
import { VERSION } from "@harness-engineering/core";
|
|
13
13
|
|
|
14
14
|
// src/commands/validate.ts
|
|
@@ -400,10 +400,404 @@ function createCheckDepsCommand() {
|
|
|
400
400
|
return command;
|
|
401
401
|
}
|
|
402
402
|
|
|
403
|
-
// src/commands/check-
|
|
403
|
+
// src/commands/check-perf.ts
|
|
404
404
|
import { Command as Command3 } from "commander";
|
|
405
405
|
import * as path4 from "path";
|
|
406
|
-
import { Ok as Ok4,
|
|
406
|
+
import { Ok as Ok4, EntropyAnalyzer } from "@harness-engineering/core";
|
|
407
|
+
async function runCheckPerf(cwd, options) {
|
|
408
|
+
const runAll = !options.structural && !options.size && !options.coupling;
|
|
409
|
+
const analyzer = new EntropyAnalyzer({
|
|
410
|
+
rootDir: path4.resolve(cwd),
|
|
411
|
+
analyze: {
|
|
412
|
+
complexity: runAll || !!options.structural,
|
|
413
|
+
coupling: runAll || !!options.coupling,
|
|
414
|
+
sizeBudget: runAll || !!options.size
|
|
415
|
+
}
|
|
416
|
+
});
|
|
417
|
+
const analysisResult = await analyzer.analyze();
|
|
418
|
+
if (!analysisResult.ok) {
|
|
419
|
+
return Ok4({
|
|
420
|
+
valid: false,
|
|
421
|
+
violations: [
|
|
422
|
+
{
|
|
423
|
+
tier: 1,
|
|
424
|
+
severity: "error",
|
|
425
|
+
metric: "analysis-error",
|
|
426
|
+
file: "",
|
|
427
|
+
value: 0,
|
|
428
|
+
threshold: 0,
|
|
429
|
+
message: `Analysis failed: ${analysisResult.error.message}`
|
|
430
|
+
}
|
|
431
|
+
],
|
|
432
|
+
stats: { filesAnalyzed: 0, violationCount: 1, errorCount: 1, warningCount: 0, infoCount: 0 }
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
const report = analysisResult.value;
|
|
436
|
+
const violations = [];
|
|
437
|
+
if (report.complexity) {
|
|
438
|
+
for (const v of report.complexity.violations) {
|
|
439
|
+
violations.push({
|
|
440
|
+
tier: v.tier,
|
|
441
|
+
severity: v.severity,
|
|
442
|
+
metric: v.metric,
|
|
443
|
+
file: v.file,
|
|
444
|
+
value: v.value,
|
|
445
|
+
threshold: v.threshold,
|
|
446
|
+
message: v.message || `[Tier ${v.tier}] ${v.metric}: ${v.function} (${v.value} > ${v.threshold})`
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
if (report.coupling) {
|
|
451
|
+
for (const v of report.coupling.violations) {
|
|
452
|
+
violations.push({
|
|
453
|
+
tier: v.tier,
|
|
454
|
+
severity: v.severity,
|
|
455
|
+
metric: v.metric,
|
|
456
|
+
file: v.file,
|
|
457
|
+
value: v.value,
|
|
458
|
+
threshold: v.threshold,
|
|
459
|
+
message: v.message || `[Tier ${v.tier}] ${v.metric}: ${v.file} (${v.value} > ${v.threshold})`
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
if (report.sizeBudget) {
|
|
464
|
+
for (const v of report.sizeBudget.violations) {
|
|
465
|
+
violations.push({
|
|
466
|
+
tier: v.tier,
|
|
467
|
+
severity: v.severity,
|
|
468
|
+
metric: "sizeBudget",
|
|
469
|
+
file: v.package,
|
|
470
|
+
value: v.currentSize,
|
|
471
|
+
threshold: v.budgetSize,
|
|
472
|
+
message: `[Tier ${v.tier}] Size: ${v.package} (${v.currentSize}B > ${v.budgetSize}B)`
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
const hasErrors = violations.some((v) => v.severity === "error");
|
|
477
|
+
const errorCount = violations.filter((v) => v.severity === "error").length;
|
|
478
|
+
const warningCount = violations.filter((v) => v.severity === "warning").length;
|
|
479
|
+
const infoCount = violations.filter((v) => v.severity === "info").length;
|
|
480
|
+
return Ok4({
|
|
481
|
+
valid: !hasErrors,
|
|
482
|
+
violations,
|
|
483
|
+
stats: {
|
|
484
|
+
filesAnalyzed: report.complexity?.stats.filesAnalyzed ?? 0,
|
|
485
|
+
violationCount: violations.length,
|
|
486
|
+
errorCount,
|
|
487
|
+
warningCount,
|
|
488
|
+
infoCount
|
|
489
|
+
}
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
function createCheckPerfCommand() {
|
|
493
|
+
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) => {
|
|
494
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
495
|
+
const mode = globalOpts.json ? OutputMode.JSON : globalOpts.quiet ? OutputMode.QUIET : globalOpts.verbose ? OutputMode.VERBOSE : OutputMode.TEXT;
|
|
496
|
+
const formatter = new OutputFormatter(mode);
|
|
497
|
+
const result = await runCheckPerf(process.cwd(), {
|
|
498
|
+
structural: opts.structural,
|
|
499
|
+
coupling: opts.coupling,
|
|
500
|
+
size: opts.size
|
|
501
|
+
});
|
|
502
|
+
if (!result.ok) {
|
|
503
|
+
if (mode === OutputMode.JSON) {
|
|
504
|
+
console.log(JSON.stringify({ error: result.error.message }));
|
|
505
|
+
} else {
|
|
506
|
+
logger.error(result.error.message);
|
|
507
|
+
}
|
|
508
|
+
process.exit(ExitCode.ERROR);
|
|
509
|
+
}
|
|
510
|
+
const issues = result.value.violations.map((v) => ({
|
|
511
|
+
file: v.file,
|
|
512
|
+
message: v.message
|
|
513
|
+
}));
|
|
514
|
+
const output = formatter.formatValidation({
|
|
515
|
+
valid: result.value.valid,
|
|
516
|
+
issues
|
|
517
|
+
});
|
|
518
|
+
if (output) {
|
|
519
|
+
console.log(output);
|
|
520
|
+
}
|
|
521
|
+
process.exit(result.value.valid ? ExitCode.SUCCESS : ExitCode.VALIDATION_FAILED);
|
|
522
|
+
});
|
|
523
|
+
return command;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
// src/commands/check-security.ts
|
|
527
|
+
import { Command as Command4 } from "commander";
|
|
528
|
+
import * as path5 from "path";
|
|
529
|
+
import { execSync } from "child_process";
|
|
530
|
+
import { Ok as Ok5, SecurityScanner, parseSecurityConfig } from "@harness-engineering/core";
|
|
531
|
+
var SEVERITY_RANK = {
|
|
532
|
+
error: 3,
|
|
533
|
+
warning: 2,
|
|
534
|
+
info: 1
|
|
535
|
+
};
|
|
536
|
+
function getChangedFiles(cwd) {
|
|
537
|
+
try {
|
|
538
|
+
const output = execSync("git diff --name-only HEAD~1", {
|
|
539
|
+
cwd,
|
|
540
|
+
encoding: "utf-8"
|
|
541
|
+
});
|
|
542
|
+
return output.trim().split("\n").filter((f) => f.length > 0).map((f) => path5.resolve(cwd, f));
|
|
543
|
+
} catch {
|
|
544
|
+
return [];
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
async function runCheckSecurity(cwd, options) {
|
|
548
|
+
const projectRoot = path5.resolve(cwd);
|
|
549
|
+
let configData = {};
|
|
550
|
+
try {
|
|
551
|
+
const fs22 = await import("fs");
|
|
552
|
+
const configPath = path5.join(projectRoot, "harness.config.json");
|
|
553
|
+
if (fs22.existsSync(configPath)) {
|
|
554
|
+
const raw = fs22.readFileSync(configPath, "utf-8");
|
|
555
|
+
const parsed = JSON.parse(raw);
|
|
556
|
+
configData = parsed.security ?? {};
|
|
557
|
+
}
|
|
558
|
+
} catch {
|
|
559
|
+
}
|
|
560
|
+
const securityConfig = parseSecurityConfig(configData);
|
|
561
|
+
const scanner = new SecurityScanner(securityConfig);
|
|
562
|
+
scanner.configureForProject(projectRoot);
|
|
563
|
+
let filesToScan;
|
|
564
|
+
if (options.changedOnly) {
|
|
565
|
+
filesToScan = getChangedFiles(projectRoot);
|
|
566
|
+
} else {
|
|
567
|
+
const { glob: glob2 } = await import("glob");
|
|
568
|
+
const pattern = "**/*.{ts,tsx,js,jsx,go,py,java,rb}";
|
|
569
|
+
const ignore = securityConfig.exclude ?? [
|
|
570
|
+
"**/node_modules/**",
|
|
571
|
+
"**/dist/**",
|
|
572
|
+
"**/*.test.ts",
|
|
573
|
+
"**/fixtures/**"
|
|
574
|
+
];
|
|
575
|
+
filesToScan = await glob2(pattern, { cwd: projectRoot, absolute: true, ignore });
|
|
576
|
+
}
|
|
577
|
+
const result = await scanner.scanFiles(filesToScan);
|
|
578
|
+
const threshold = options.severity ?? "warning";
|
|
579
|
+
const thresholdRank = SEVERITY_RANK[threshold];
|
|
580
|
+
const filtered = result.findings.filter((f) => SEVERITY_RANK[f.severity] >= thresholdRank);
|
|
581
|
+
const hasErrors = filtered.some((f) => f.severity === "error");
|
|
582
|
+
return Ok5({
|
|
583
|
+
valid: !hasErrors,
|
|
584
|
+
findings: filtered,
|
|
585
|
+
stats: {
|
|
586
|
+
filesScanned: result.scannedFiles,
|
|
587
|
+
rulesApplied: result.rulesApplied,
|
|
588
|
+
errorCount: filtered.filter((f) => f.severity === "error").length,
|
|
589
|
+
warningCount: filtered.filter((f) => f.severity === "warning").length,
|
|
590
|
+
infoCount: filtered.filter((f) => f.severity === "info").length
|
|
591
|
+
}
|
|
592
|
+
});
|
|
593
|
+
}
|
|
594
|
+
function createCheckSecurityCommand() {
|
|
595
|
+
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) => {
|
|
596
|
+
const severity = thisCommand.opts().severity;
|
|
597
|
+
if (!["error", "warning", "info"].includes(severity)) {
|
|
598
|
+
logger.error(`Invalid severity: "${severity}". Must be one of: error, warning, info`);
|
|
599
|
+
process.exit(ExitCode.ERROR);
|
|
600
|
+
}
|
|
601
|
+
}).option("--changed-only", "Only scan git-changed files").action(async (opts, cmd) => {
|
|
602
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
603
|
+
const mode = globalOpts.json ? OutputMode.JSON : globalOpts.quiet ? OutputMode.QUIET : globalOpts.verbose ? OutputMode.VERBOSE : OutputMode.TEXT;
|
|
604
|
+
const formatter = new OutputFormatter(mode);
|
|
605
|
+
const result = await runCheckSecurity(process.cwd(), {
|
|
606
|
+
severity: opts.severity,
|
|
607
|
+
changedOnly: opts.changedOnly
|
|
608
|
+
});
|
|
609
|
+
if (!result.ok) {
|
|
610
|
+
if (mode === OutputMode.JSON) {
|
|
611
|
+
console.log(JSON.stringify({ error: result.error.message }));
|
|
612
|
+
} else {
|
|
613
|
+
logger.error(result.error.message);
|
|
614
|
+
}
|
|
615
|
+
process.exit(ExitCode.ERROR);
|
|
616
|
+
}
|
|
617
|
+
const issues = result.value.findings.map((f) => ({
|
|
618
|
+
file: `${f.file}:${f.line}`,
|
|
619
|
+
message: `[${f.ruleId}] ${f.severity.toUpperCase()} ${f.message}`
|
|
620
|
+
}));
|
|
621
|
+
const output = formatter.formatValidation({
|
|
622
|
+
valid: result.value.valid,
|
|
623
|
+
issues
|
|
624
|
+
});
|
|
625
|
+
if (output) {
|
|
626
|
+
console.log(output);
|
|
627
|
+
}
|
|
628
|
+
process.exit(result.value.valid ? ExitCode.SUCCESS : ExitCode.VALIDATION_FAILED);
|
|
629
|
+
});
|
|
630
|
+
return command;
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
// src/commands/perf.ts
|
|
634
|
+
import { Command as Command5 } from "commander";
|
|
635
|
+
import * as path6 from "path";
|
|
636
|
+
import { BaselineManager, CriticalPathResolver } from "@harness-engineering/core";
|
|
637
|
+
function createPerfCommand() {
|
|
638
|
+
const perf = new Command5("perf").description("Performance benchmark and baseline management");
|
|
639
|
+
perf.command("bench [glob]").description("Run benchmarks via vitest bench").action(async (glob2, _opts, cmd) => {
|
|
640
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
641
|
+
const cwd = process.cwd();
|
|
642
|
+
const { BenchmarkRunner } = await import("@harness-engineering/core");
|
|
643
|
+
const runner = new BenchmarkRunner();
|
|
644
|
+
const benchFiles = runner.discover(cwd, glob2);
|
|
645
|
+
if (benchFiles.length === 0) {
|
|
646
|
+
if (globalOpts.json) {
|
|
647
|
+
console.log(JSON.stringify({ benchFiles: [], message: "No .bench.ts files found" }));
|
|
648
|
+
} else {
|
|
649
|
+
logger.info("No .bench.ts files found. Create *.bench.ts files to add benchmarks.");
|
|
650
|
+
}
|
|
651
|
+
return;
|
|
652
|
+
}
|
|
653
|
+
if (globalOpts.json) {
|
|
654
|
+
logger.info(`Found ${benchFiles.length} benchmark file(s). Running...`);
|
|
655
|
+
} else {
|
|
656
|
+
logger.info(`Found ${benchFiles.length} benchmark file(s):`);
|
|
657
|
+
for (const f of benchFiles) {
|
|
658
|
+
logger.info(` ${f}`);
|
|
659
|
+
}
|
|
660
|
+
logger.info("Running benchmarks...");
|
|
661
|
+
}
|
|
662
|
+
const result = await runner.run(glob2 ? { cwd, glob: glob2 } : { cwd });
|
|
663
|
+
if (globalOpts.json) {
|
|
664
|
+
console.log(JSON.stringify({ results: result.results, success: result.success }));
|
|
665
|
+
} else {
|
|
666
|
+
if (result.success && result.results.length > 0) {
|
|
667
|
+
logger.info(`
|
|
668
|
+
Results (${result.results.length} benchmarks):`);
|
|
669
|
+
for (const r of result.results) {
|
|
670
|
+
logger.info(
|
|
671
|
+
` ${r.file}::${r.name}: ${r.opsPerSec} ops/s (mean: ${r.meanMs.toFixed(2)}ms)`
|
|
672
|
+
);
|
|
673
|
+
}
|
|
674
|
+
logger.info("\nTo save as baselines: harness perf baselines update");
|
|
675
|
+
} else {
|
|
676
|
+
logger.info("Benchmark run completed. Check output above for details.");
|
|
677
|
+
if (result.rawOutput) {
|
|
678
|
+
console.log(result.rawOutput);
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
});
|
|
683
|
+
const baselines = perf.command("baselines").description("Manage performance baselines");
|
|
684
|
+
baselines.command("show").description("Display current baselines").action(async (_opts, cmd) => {
|
|
685
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
686
|
+
const cwd = process.cwd();
|
|
687
|
+
const manager = new BaselineManager(cwd);
|
|
688
|
+
const data = manager.load();
|
|
689
|
+
if (!data) {
|
|
690
|
+
if (globalOpts.json) {
|
|
691
|
+
console.log(JSON.stringify({ baselines: null, message: "No baselines file found" }));
|
|
692
|
+
} else {
|
|
693
|
+
logger.info("No baselines file found at .harness/perf/baselines.json");
|
|
694
|
+
}
|
|
695
|
+
return;
|
|
696
|
+
}
|
|
697
|
+
if (globalOpts.json) {
|
|
698
|
+
console.log(JSON.stringify(data, null, 2));
|
|
699
|
+
} else {
|
|
700
|
+
logger.info(`Baselines (updated: ${data.updatedAt}, from: ${data.updatedFrom})`);
|
|
701
|
+
for (const [name, baseline] of Object.entries(data.benchmarks)) {
|
|
702
|
+
logger.info(
|
|
703
|
+
` ${name}: ${baseline.opsPerSec} ops/s (mean: ${baseline.meanMs}ms, p99: ${baseline.p99Ms}ms)`
|
|
704
|
+
);
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
});
|
|
708
|
+
baselines.command("update").description("Update baselines from latest benchmark run").action(async (_opts, cmd) => {
|
|
709
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
710
|
+
const cwd = process.cwd();
|
|
711
|
+
const { BenchmarkRunner } = await import("@harness-engineering/core");
|
|
712
|
+
const runner = new BenchmarkRunner();
|
|
713
|
+
const manager = new BaselineManager(cwd);
|
|
714
|
+
logger.info("Running benchmarks to update baselines...");
|
|
715
|
+
const benchResult = await runner.run({ cwd });
|
|
716
|
+
if (!benchResult.success || benchResult.results.length === 0) {
|
|
717
|
+
logger.error(
|
|
718
|
+
"No benchmark results to save. Run `harness perf bench` first to verify benchmarks work."
|
|
719
|
+
);
|
|
720
|
+
return;
|
|
721
|
+
}
|
|
722
|
+
let commitHash = "unknown";
|
|
723
|
+
try {
|
|
724
|
+
const { execSync: execSync5 } = await import("child_process");
|
|
725
|
+
commitHash = execSync5("git rev-parse --short HEAD", { cwd, encoding: "utf-8" }).trim();
|
|
726
|
+
} catch {
|
|
727
|
+
}
|
|
728
|
+
manager.save(benchResult.results, commitHash);
|
|
729
|
+
if (globalOpts.json) {
|
|
730
|
+
console.log(JSON.stringify({ updated: benchResult.results.length, commitHash }));
|
|
731
|
+
} else {
|
|
732
|
+
logger.info(`Updated ${benchResult.results.length} baseline(s) from commit ${commitHash}`);
|
|
733
|
+
logger.info("Baselines saved to .harness/perf/baselines.json");
|
|
734
|
+
}
|
|
735
|
+
});
|
|
736
|
+
perf.command("report").description("Full performance report with metrics, trends, and hotspots").action(async (_opts, cmd) => {
|
|
737
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
738
|
+
const cwd = process.cwd();
|
|
739
|
+
const { EntropyAnalyzer: EntropyAnalyzer3 } = await import("@harness-engineering/core");
|
|
740
|
+
const analyzer = new EntropyAnalyzer3({
|
|
741
|
+
rootDir: path6.resolve(cwd),
|
|
742
|
+
analyze: { complexity: true, coupling: true }
|
|
743
|
+
});
|
|
744
|
+
const result = await analyzer.analyze();
|
|
745
|
+
if (!result.ok) {
|
|
746
|
+
logger.error(result.error.message);
|
|
747
|
+
return;
|
|
748
|
+
}
|
|
749
|
+
const report = result.value;
|
|
750
|
+
if (globalOpts.json) {
|
|
751
|
+
console.log(
|
|
752
|
+
JSON.stringify(
|
|
753
|
+
{
|
|
754
|
+
complexity: report.complexity,
|
|
755
|
+
coupling: report.coupling,
|
|
756
|
+
sizeBudget: report.sizeBudget
|
|
757
|
+
},
|
|
758
|
+
null,
|
|
759
|
+
2
|
|
760
|
+
)
|
|
761
|
+
);
|
|
762
|
+
} else {
|
|
763
|
+
logger.info("=== Performance Report ===");
|
|
764
|
+
if (report.complexity) {
|
|
765
|
+
logger.info(
|
|
766
|
+
`Complexity: ${report.complexity.stats.violationCount} violations (${report.complexity.stats.errorCount} errors, ${report.complexity.stats.warningCount} warnings)`
|
|
767
|
+
);
|
|
768
|
+
}
|
|
769
|
+
if (report.coupling) {
|
|
770
|
+
logger.info(
|
|
771
|
+
`Coupling: ${report.coupling.stats.violationCount} violations (${report.coupling.stats.warningCount} warnings)`
|
|
772
|
+
);
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
});
|
|
776
|
+
perf.command("critical-paths").description("Show resolved critical path set (annotations + graph inference)").action(async (_opts, cmd) => {
|
|
777
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
778
|
+
const cwd = process.cwd();
|
|
779
|
+
const resolver = new CriticalPathResolver(cwd);
|
|
780
|
+
const result = await resolver.resolve();
|
|
781
|
+
if (globalOpts.json) {
|
|
782
|
+
console.log(JSON.stringify(result, null, 2));
|
|
783
|
+
} else {
|
|
784
|
+
logger.info(
|
|
785
|
+
`Critical paths: ${result.stats.total} (${result.stats.annotated} annotated, ${result.stats.graphInferred} graph-inferred)`
|
|
786
|
+
);
|
|
787
|
+
for (const entry of result.entries) {
|
|
788
|
+
logger.info(
|
|
789
|
+
` ${entry.file}::${entry.function} [${entry.source}]${entry.fanIn ? ` (fan-in: ${entry.fanIn})` : ""}`
|
|
790
|
+
);
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
});
|
|
794
|
+
return perf;
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
// src/commands/check-docs.ts
|
|
798
|
+
import { Command as Command6 } from "commander";
|
|
799
|
+
import * as path7 from "path";
|
|
800
|
+
import { Ok as Ok6, Err as Err2 } from "@harness-engineering/core";
|
|
407
801
|
import { checkDocCoverage, validateKnowledgeMap as validateKnowledgeMap2 } from "@harness-engineering/core";
|
|
408
802
|
async function runCheckDocs(options) {
|
|
409
803
|
const cwd = options.cwd ?? process.cwd();
|
|
@@ -413,8 +807,8 @@ async function runCheckDocs(options) {
|
|
|
413
807
|
return configResult;
|
|
414
808
|
}
|
|
415
809
|
const config = configResult.value;
|
|
416
|
-
const docsDir =
|
|
417
|
-
const sourceDir =
|
|
810
|
+
const docsDir = path7.resolve(cwd, config.docsDir);
|
|
811
|
+
const sourceDir = path7.resolve(cwd, config.rootDir);
|
|
418
812
|
const coverageResult = await checkDocCoverage("project", {
|
|
419
813
|
docsDir,
|
|
420
814
|
sourceDir,
|
|
@@ -443,10 +837,10 @@ async function runCheckDocs(options) {
|
|
|
443
837
|
undocumented: coverageResult.value.undocumented,
|
|
444
838
|
brokenLinks
|
|
445
839
|
};
|
|
446
|
-
return
|
|
840
|
+
return Ok6(result);
|
|
447
841
|
}
|
|
448
842
|
function createCheckDocsCommand() {
|
|
449
|
-
const command = new
|
|
843
|
+
const command = new Command6("check-docs").description("Check documentation coverage").option("--min-coverage <percent>", "Minimum coverage percentage", "80").action(async (opts, cmd) => {
|
|
450
844
|
const globalOpts = cmd.optsWithGlobals();
|
|
451
845
|
const mode = globalOpts.json ? OutputMode.JSON : globalOpts.quiet ? OutputMode.QUIET : globalOpts.verbose ? OutputMode.VERBOSE : OutputMode.TEXT;
|
|
452
846
|
const formatter = new OutputFormatter(mode);
|
|
@@ -498,17 +892,17 @@ function createCheckDocsCommand() {
|
|
|
498
892
|
}
|
|
499
893
|
|
|
500
894
|
// src/commands/init.ts
|
|
501
|
-
import { Command as
|
|
895
|
+
import { Command as Command8 } from "commander";
|
|
502
896
|
import chalk3 from "chalk";
|
|
503
897
|
import * as fs5 from "fs";
|
|
504
|
-
import * as
|
|
505
|
-
import { Ok as
|
|
898
|
+
import * as path11 from "path";
|
|
899
|
+
import { Ok as Ok8, Err as Err4 } from "@harness-engineering/core";
|
|
506
900
|
|
|
507
901
|
// src/templates/engine.ts
|
|
508
902
|
import * as fs2 from "fs";
|
|
509
|
-
import * as
|
|
903
|
+
import * as path8 from "path";
|
|
510
904
|
import Handlebars from "handlebars";
|
|
511
|
-
import { Ok as
|
|
905
|
+
import { Ok as Ok7, Err as Err3 } from "@harness-engineering/core";
|
|
512
906
|
|
|
513
907
|
// src/templates/schema.ts
|
|
514
908
|
import { z as z2 } from "zod";
|
|
@@ -576,13 +970,13 @@ var TemplateEngine = class {
|
|
|
576
970
|
const templates = [];
|
|
577
971
|
for (const entry of entries) {
|
|
578
972
|
if (!entry.isDirectory()) continue;
|
|
579
|
-
const metaPath =
|
|
973
|
+
const metaPath = path8.join(this.templatesDir, entry.name, "template.json");
|
|
580
974
|
if (!fs2.existsSync(metaPath)) continue;
|
|
581
975
|
const raw = JSON.parse(fs2.readFileSync(metaPath, "utf-8"));
|
|
582
976
|
const parsed = TemplateMetadataSchema.safeParse(raw);
|
|
583
977
|
if (parsed.success) templates.push(parsed.data);
|
|
584
978
|
}
|
|
585
|
-
return
|
|
979
|
+
return Ok7(templates);
|
|
586
980
|
} catch (error) {
|
|
587
981
|
return Err3(
|
|
588
982
|
new Error(
|
|
@@ -594,7 +988,7 @@ var TemplateEngine = class {
|
|
|
594
988
|
resolveTemplate(level, framework) {
|
|
595
989
|
const levelDir = this.findTemplateDir(level, "level");
|
|
596
990
|
if (!levelDir) return Err3(new Error(`Template not found for level: ${level}`));
|
|
597
|
-
const metaPath =
|
|
991
|
+
const metaPath = path8.join(levelDir, "template.json");
|
|
598
992
|
const metaRaw = JSON.parse(fs2.readFileSync(metaPath, "utf-8"));
|
|
599
993
|
const metaResult = TemplateMetadataSchema.safeParse(metaRaw);
|
|
600
994
|
if (!metaResult.success)
|
|
@@ -602,7 +996,7 @@ var TemplateEngine = class {
|
|
|
602
996
|
const metadata = metaResult.data;
|
|
603
997
|
let files = [];
|
|
604
998
|
if (metadata.extends) {
|
|
605
|
-
const baseDir =
|
|
999
|
+
const baseDir = path8.join(this.templatesDir, metadata.extends);
|
|
606
1000
|
if (fs2.existsSync(baseDir)) files = this.collectFiles(baseDir, metadata.extends);
|
|
607
1001
|
}
|
|
608
1002
|
const levelFiles = this.collectFiles(levelDir, level);
|
|
@@ -611,7 +1005,7 @@ var TemplateEngine = class {
|
|
|
611
1005
|
if (framework) {
|
|
612
1006
|
const frameworkDir = this.findTemplateDir(framework, "framework");
|
|
613
1007
|
if (!frameworkDir) return Err3(new Error(`Framework template not found: ${framework}`));
|
|
614
|
-
const fMetaPath =
|
|
1008
|
+
const fMetaPath = path8.join(frameworkDir, "template.json");
|
|
615
1009
|
const fMetaRaw = JSON.parse(fs2.readFileSync(fMetaPath, "utf-8"));
|
|
616
1010
|
const fMetaResult = TemplateMetadataSchema.safeParse(fMetaRaw);
|
|
617
1011
|
if (fMetaResult.success) overlayMetadata = fMetaResult.data;
|
|
@@ -621,7 +1015,7 @@ var TemplateEngine = class {
|
|
|
621
1015
|
files = files.filter((f) => f.relativePath !== "template.json");
|
|
622
1016
|
const resolved = { metadata, files };
|
|
623
1017
|
if (overlayMetadata !== void 0) resolved.overlayMetadata = overlayMetadata;
|
|
624
|
-
return
|
|
1018
|
+
return Ok7(resolved);
|
|
625
1019
|
}
|
|
626
1020
|
render(template, context) {
|
|
627
1021
|
const rendered = [];
|
|
@@ -673,20 +1067,20 @@ var TemplateEngine = class {
|
|
|
673
1067
|
const msg = error instanceof Error ? error.message : String(error);
|
|
674
1068
|
return Err3(new Error(`JSON merge failed: ${msg}`));
|
|
675
1069
|
}
|
|
676
|
-
return
|
|
1070
|
+
return Ok7({ files: rendered });
|
|
677
1071
|
}
|
|
678
1072
|
write(files, targetDir, options) {
|
|
679
1073
|
try {
|
|
680
1074
|
const written = [];
|
|
681
1075
|
for (const file of files.files) {
|
|
682
|
-
const targetPath =
|
|
683
|
-
const dir =
|
|
1076
|
+
const targetPath = path8.join(targetDir, file.relativePath);
|
|
1077
|
+
const dir = path8.dirname(targetPath);
|
|
684
1078
|
if (!options.overwrite && fs2.existsSync(targetPath)) continue;
|
|
685
1079
|
fs2.mkdirSync(dir, { recursive: true });
|
|
686
1080
|
fs2.writeFileSync(targetPath, file.content);
|
|
687
1081
|
written.push(file.relativePath);
|
|
688
1082
|
}
|
|
689
|
-
return
|
|
1083
|
+
return Ok7(written);
|
|
690
1084
|
} catch (error) {
|
|
691
1085
|
return Err3(
|
|
692
1086
|
new Error(
|
|
@@ -699,16 +1093,16 @@ var TemplateEngine = class {
|
|
|
699
1093
|
const entries = fs2.readdirSync(this.templatesDir, { withFileTypes: true });
|
|
700
1094
|
for (const entry of entries) {
|
|
701
1095
|
if (!entry.isDirectory()) continue;
|
|
702
|
-
const metaPath =
|
|
1096
|
+
const metaPath = path8.join(this.templatesDir, entry.name, "template.json");
|
|
703
1097
|
if (!fs2.existsSync(metaPath)) continue;
|
|
704
1098
|
const raw = JSON.parse(fs2.readFileSync(metaPath, "utf-8"));
|
|
705
1099
|
const parsed = TemplateMetadataSchema.safeParse(raw);
|
|
706
1100
|
if (!parsed.success) continue;
|
|
707
1101
|
if (type === "level" && parsed.data.level === name)
|
|
708
|
-
return
|
|
1102
|
+
return path8.join(this.templatesDir, entry.name);
|
|
709
1103
|
if (type === "framework" && parsed.data.framework === name)
|
|
710
|
-
return
|
|
711
|
-
if (parsed.data.name === name) return
|
|
1104
|
+
return path8.join(this.templatesDir, entry.name);
|
|
1105
|
+
if (parsed.data.name === name) return path8.join(this.templatesDir, entry.name);
|
|
712
1106
|
}
|
|
713
1107
|
return null;
|
|
714
1108
|
}
|
|
@@ -717,12 +1111,12 @@ var TemplateEngine = class {
|
|
|
717
1111
|
const walk = (currentDir) => {
|
|
718
1112
|
const entries = fs2.readdirSync(currentDir, { withFileTypes: true });
|
|
719
1113
|
for (const entry of entries) {
|
|
720
|
-
const fullPath =
|
|
1114
|
+
const fullPath = path8.join(currentDir, entry.name);
|
|
721
1115
|
if (entry.isDirectory()) {
|
|
722
1116
|
walk(fullPath);
|
|
723
1117
|
} else {
|
|
724
1118
|
files.push({
|
|
725
|
-
relativePath:
|
|
1119
|
+
relativePath: path8.relative(dir, fullPath),
|
|
726
1120
|
absolutePath: fullPath,
|
|
727
1121
|
isHandlebars: entry.name.endsWith(".hbs"),
|
|
728
1122
|
sourceTemplate: sourceName
|
|
@@ -754,51 +1148,51 @@ var TemplateEngine = class {
|
|
|
754
1148
|
|
|
755
1149
|
// src/utils/paths.ts
|
|
756
1150
|
import * as fs3 from "fs";
|
|
757
|
-
import * as
|
|
1151
|
+
import * as path9 from "path";
|
|
758
1152
|
import { fileURLToPath } from "url";
|
|
759
1153
|
var __filename = fileURLToPath(import.meta.url);
|
|
760
|
-
var __dirname =
|
|
1154
|
+
var __dirname = path9.dirname(__filename);
|
|
761
1155
|
function findUpDir(targetName, marker, maxLevels = 8) {
|
|
762
1156
|
let dir = __dirname;
|
|
763
1157
|
for (let i = 0; i < maxLevels; i++) {
|
|
764
|
-
const candidate =
|
|
1158
|
+
const candidate = path9.join(dir, targetName);
|
|
765
1159
|
if (fs3.existsSync(candidate) && fs3.statSync(candidate).isDirectory()) {
|
|
766
|
-
if (fs3.existsSync(
|
|
1160
|
+
if (fs3.existsSync(path9.join(candidate, marker))) {
|
|
767
1161
|
return candidate;
|
|
768
1162
|
}
|
|
769
1163
|
}
|
|
770
|
-
dir =
|
|
1164
|
+
dir = path9.dirname(dir);
|
|
771
1165
|
}
|
|
772
1166
|
return null;
|
|
773
1167
|
}
|
|
774
1168
|
function resolveTemplatesDir() {
|
|
775
|
-
return findUpDir("templates", "base") ??
|
|
1169
|
+
return findUpDir("templates", "base") ?? path9.join(__dirname, "templates");
|
|
776
1170
|
}
|
|
777
1171
|
function resolvePersonasDir() {
|
|
778
1172
|
const agentsDir = findUpDir("agents", "personas");
|
|
779
1173
|
if (agentsDir) {
|
|
780
|
-
return
|
|
1174
|
+
return path9.join(agentsDir, "personas");
|
|
781
1175
|
}
|
|
782
|
-
return
|
|
1176
|
+
return path9.join(__dirname, "agents", "personas");
|
|
783
1177
|
}
|
|
784
1178
|
function resolveSkillsDir() {
|
|
785
1179
|
const agentsDir = findUpDir("agents", "skills");
|
|
786
1180
|
if (agentsDir) {
|
|
787
|
-
return
|
|
1181
|
+
return path9.join(agentsDir, "skills", "claude-code");
|
|
788
1182
|
}
|
|
789
|
-
return
|
|
1183
|
+
return path9.join(__dirname, "agents", "skills", "claude-code");
|
|
790
1184
|
}
|
|
791
1185
|
function resolveProjectSkillsDir(cwd) {
|
|
792
1186
|
let dir = cwd ?? process.cwd();
|
|
793
1187
|
for (let i = 0; i < 8; i++) {
|
|
794
|
-
const candidate =
|
|
1188
|
+
const candidate = path9.join(dir, "agents", "skills", "claude-code");
|
|
795
1189
|
if (fs3.existsSync(candidate) && fs3.statSync(candidate).isDirectory()) {
|
|
796
|
-
const agentsDir =
|
|
797
|
-
if (fs3.existsSync(
|
|
1190
|
+
const agentsDir = path9.join(dir, "agents");
|
|
1191
|
+
if (fs3.existsSync(path9.join(agentsDir, "skills"))) {
|
|
798
1192
|
return candidate;
|
|
799
1193
|
}
|
|
800
1194
|
}
|
|
801
|
-
const parent =
|
|
1195
|
+
const parent = path9.dirname(dir);
|
|
802
1196
|
if (parent === dir) break;
|
|
803
1197
|
dir = parent;
|
|
804
1198
|
}
|
|
@@ -807,15 +1201,15 @@ function resolveProjectSkillsDir(cwd) {
|
|
|
807
1201
|
function resolveGlobalSkillsDir() {
|
|
808
1202
|
const agentsDir = findUpDir("agents", "skills");
|
|
809
1203
|
if (agentsDir) {
|
|
810
|
-
return
|
|
1204
|
+
return path9.join(agentsDir, "skills", "claude-code");
|
|
811
1205
|
}
|
|
812
|
-
return
|
|
1206
|
+
return path9.join(__dirname, "agents", "skills", "claude-code");
|
|
813
1207
|
}
|
|
814
1208
|
|
|
815
1209
|
// src/commands/setup-mcp.ts
|
|
816
|
-
import { Command as
|
|
1210
|
+
import { Command as Command7 } from "commander";
|
|
817
1211
|
import * as fs4 from "fs";
|
|
818
|
-
import * as
|
|
1212
|
+
import * as path10 from "path";
|
|
819
1213
|
import * as os from "os";
|
|
820
1214
|
import chalk2 from "chalk";
|
|
821
1215
|
var HARNESS_MCP_ENTRY = {
|
|
@@ -832,7 +1226,7 @@ function readJsonFile(filePath) {
|
|
|
832
1226
|
}
|
|
833
1227
|
}
|
|
834
1228
|
function writeJsonFile(filePath, data) {
|
|
835
|
-
const dir =
|
|
1229
|
+
const dir = path10.dirname(filePath);
|
|
836
1230
|
if (!fs4.existsSync(dir)) {
|
|
837
1231
|
fs4.mkdirSync(dir, { recursive: true });
|
|
838
1232
|
}
|
|
@@ -851,7 +1245,7 @@ function configureMcpServer(configPath) {
|
|
|
851
1245
|
return true;
|
|
852
1246
|
}
|
|
853
1247
|
function addGeminiTrustedFolder(cwd) {
|
|
854
|
-
const trustedPath =
|
|
1248
|
+
const trustedPath = path10.join(os.homedir(), ".gemini", "trustedFolders.json");
|
|
855
1249
|
const folders = readJsonFile(trustedPath) ?? {};
|
|
856
1250
|
if (folders[cwd] === "TRUST_FOLDER") {
|
|
857
1251
|
return false;
|
|
@@ -865,7 +1259,7 @@ function setupMcp(cwd, client) {
|
|
|
865
1259
|
const skipped = [];
|
|
866
1260
|
let trustedFolder = false;
|
|
867
1261
|
if (client === "all" || client === "claude") {
|
|
868
|
-
const configPath =
|
|
1262
|
+
const configPath = path10.join(cwd, ".mcp.json");
|
|
869
1263
|
if (configureMcpServer(configPath)) {
|
|
870
1264
|
configured.push("Claude Code");
|
|
871
1265
|
} else {
|
|
@@ -873,7 +1267,7 @@ function setupMcp(cwd, client) {
|
|
|
873
1267
|
}
|
|
874
1268
|
}
|
|
875
1269
|
if (client === "all" || client === "gemini") {
|
|
876
|
-
const configPath =
|
|
1270
|
+
const configPath = path10.join(cwd, ".gemini", "settings.json");
|
|
877
1271
|
if (configureMcpServer(configPath)) {
|
|
878
1272
|
configured.push("Gemini CLI");
|
|
879
1273
|
} else {
|
|
@@ -884,7 +1278,7 @@ function setupMcp(cwd, client) {
|
|
|
884
1278
|
return { configured, skipped, trustedFolder };
|
|
885
1279
|
}
|
|
886
1280
|
function createSetupMcpCommand() {
|
|
887
|
-
return new
|
|
1281
|
+
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
1282
|
const globalOpts = cmd.optsWithGlobals();
|
|
889
1283
|
const cwd = process.cwd();
|
|
890
1284
|
const { configured, skipped, trustedFolder } = setupMcp(cwd, opts.client);
|
|
@@ -910,8 +1304,12 @@ function createSetupMcpCommand() {
|
|
|
910
1304
|
}
|
|
911
1305
|
console.log("");
|
|
912
1306
|
console.log(chalk2.bold("The harness MCP server provides:"));
|
|
913
|
-
console.log(
|
|
914
|
-
|
|
1307
|
+
console.log(
|
|
1308
|
+
" - 31 tools for validation, entropy detection, skill execution, graph querying, and more"
|
|
1309
|
+
);
|
|
1310
|
+
console.log(
|
|
1311
|
+
" - 8 resources for project context, skills, rules, learnings, state, and graph data"
|
|
1312
|
+
);
|
|
915
1313
|
console.log("");
|
|
916
1314
|
console.log(`Run ${chalk2.cyan("harness skill list")} to see available skills.`);
|
|
917
1315
|
console.log("");
|
|
@@ -923,10 +1321,10 @@ function createSetupMcpCommand() {
|
|
|
923
1321
|
// src/commands/init.ts
|
|
924
1322
|
async function runInit(options) {
|
|
925
1323
|
const cwd = options.cwd ?? process.cwd();
|
|
926
|
-
const name = options.name ??
|
|
1324
|
+
const name = options.name ?? path11.basename(cwd);
|
|
927
1325
|
const level = options.level ?? "basic";
|
|
928
1326
|
const force = options.force ?? false;
|
|
929
|
-
const configPath =
|
|
1327
|
+
const configPath = path11.join(cwd, "harness.config.json");
|
|
930
1328
|
if (!force && fs5.existsSync(configPath)) {
|
|
931
1329
|
return Err4(
|
|
932
1330
|
new CLIError("Project already initialized. Use --force to overwrite.", ExitCode.ERROR)
|
|
@@ -950,10 +1348,10 @@ async function runInit(options) {
|
|
|
950
1348
|
if (!writeResult.ok) {
|
|
951
1349
|
return Err4(new CLIError(writeResult.error.message, ExitCode.ERROR));
|
|
952
1350
|
}
|
|
953
|
-
return
|
|
1351
|
+
return Ok8({ filesCreated: writeResult.value });
|
|
954
1352
|
}
|
|
955
1353
|
function createInitCommand() {
|
|
956
|
-
const command = new
|
|
1354
|
+
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
1355
|
const globalOpts = cmd.optsWithGlobals();
|
|
958
1356
|
const result = await runInit({
|
|
959
1357
|
name: opts.name,
|
|
@@ -995,9 +1393,9 @@ function createInitCommand() {
|
|
|
995
1393
|
}
|
|
996
1394
|
|
|
997
1395
|
// src/commands/cleanup.ts
|
|
998
|
-
import { Command as
|
|
999
|
-
import * as
|
|
1000
|
-
import { Ok as
|
|
1396
|
+
import { Command as Command9 } from "commander";
|
|
1397
|
+
import * as path12 from "path";
|
|
1398
|
+
import { Ok as Ok9, Err as Err5, EntropyAnalyzer as EntropyAnalyzer2 } from "@harness-engineering/core";
|
|
1001
1399
|
async function runCleanup(options) {
|
|
1002
1400
|
const cwd = options.cwd ?? process.cwd();
|
|
1003
1401
|
const type = options.type ?? "all";
|
|
@@ -1012,11 +1410,11 @@ async function runCleanup(options) {
|
|
|
1012
1410
|
patternViolations: [],
|
|
1013
1411
|
totalIssues: 0
|
|
1014
1412
|
};
|
|
1015
|
-
const rootDir =
|
|
1016
|
-
const docsDir =
|
|
1413
|
+
const rootDir = path12.resolve(cwd, config.rootDir);
|
|
1414
|
+
const docsDir = path12.resolve(cwd, config.docsDir);
|
|
1017
1415
|
const entropyConfig = {
|
|
1018
1416
|
rootDir,
|
|
1019
|
-
entryPoints: [
|
|
1417
|
+
entryPoints: [path12.join(rootDir, "src/index.ts")],
|
|
1020
1418
|
docPaths: [docsDir],
|
|
1021
1419
|
analyze: {
|
|
1022
1420
|
drift: type === "all" || type === "drift",
|
|
@@ -1025,7 +1423,7 @@ async function runCleanup(options) {
|
|
|
1025
1423
|
},
|
|
1026
1424
|
exclude: config.entropy?.excludePatterns ?? ["**/node_modules/**", "**/*.test.ts"]
|
|
1027
1425
|
};
|
|
1028
|
-
const analyzer = new
|
|
1426
|
+
const analyzer = new EntropyAnalyzer2(entropyConfig);
|
|
1029
1427
|
const analysisResult = await analyzer.analyze();
|
|
1030
1428
|
if (!analysisResult.ok) {
|
|
1031
1429
|
return Err5(
|
|
@@ -1053,10 +1451,10 @@ async function runCleanup(options) {
|
|
|
1053
1451
|
}));
|
|
1054
1452
|
}
|
|
1055
1453
|
result.totalIssues = result.driftIssues.length + result.deadCode.length + result.patternViolations.length;
|
|
1056
|
-
return
|
|
1454
|
+
return Ok9(result);
|
|
1057
1455
|
}
|
|
1058
1456
|
function createCleanupCommand() {
|
|
1059
|
-
const command = new
|
|
1457
|
+
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
1458
|
const globalOpts = cmd.optsWithGlobals();
|
|
1061
1459
|
const mode = globalOpts.json ? OutputMode.JSON : globalOpts.quiet ? OutputMode.QUIET : globalOpts.verbose ? OutputMode.VERBOSE : OutputMode.TEXT;
|
|
1062
1460
|
const formatter = new OutputFormatter(mode);
|
|
@@ -1116,10 +1514,10 @@ function createCleanupCommand() {
|
|
|
1116
1514
|
}
|
|
1117
1515
|
|
|
1118
1516
|
// src/commands/fix-drift.ts
|
|
1119
|
-
import { Command as
|
|
1120
|
-
import * as
|
|
1517
|
+
import { Command as Command10 } from "commander";
|
|
1518
|
+
import * as path13 from "path";
|
|
1121
1519
|
import {
|
|
1122
|
-
Ok as
|
|
1520
|
+
Ok as Ok10,
|
|
1123
1521
|
Err as Err6,
|
|
1124
1522
|
buildSnapshot,
|
|
1125
1523
|
detectDocDrift,
|
|
@@ -1136,11 +1534,11 @@ async function runFixDrift(options) {
|
|
|
1136
1534
|
return Err6(configResult.error);
|
|
1137
1535
|
}
|
|
1138
1536
|
const config = configResult.value;
|
|
1139
|
-
const rootDir =
|
|
1140
|
-
const docsDir =
|
|
1537
|
+
const rootDir = path13.resolve(cwd, config.rootDir);
|
|
1538
|
+
const docsDir = path13.resolve(cwd, config.docsDir);
|
|
1141
1539
|
const entropyConfig = {
|
|
1142
1540
|
rootDir,
|
|
1143
|
-
entryPoints: [
|
|
1541
|
+
entryPoints: [path13.join(rootDir, "src/index.ts")],
|
|
1144
1542
|
docPaths: [docsDir],
|
|
1145
1543
|
analyze: {
|
|
1146
1544
|
drift: true,
|
|
@@ -1224,10 +1622,10 @@ async function runFixDrift(options) {
|
|
|
1224
1622
|
fixes: appliedFixes,
|
|
1225
1623
|
suggestions
|
|
1226
1624
|
};
|
|
1227
|
-
return
|
|
1625
|
+
return Ok10(result);
|
|
1228
1626
|
}
|
|
1229
1627
|
function createFixDriftCommand() {
|
|
1230
|
-
const command = new
|
|
1628
|
+
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
1629
|
const globalOpts = cmd.optsWithGlobals();
|
|
1232
1630
|
const mode = globalOpts.json ? OutputMode.JSON : globalOpts.quiet ? OutputMode.QUIET : globalOpts.verbose ? OutputMode.VERBOSE : OutputMode.TEXT;
|
|
1233
1631
|
const formatter = new OutputFormatter(mode);
|
|
@@ -1287,20 +1685,20 @@ function createFixDriftCommand() {
|
|
|
1287
1685
|
}
|
|
1288
1686
|
|
|
1289
1687
|
// src/commands/agent/index.ts
|
|
1290
|
-
import { Command as
|
|
1688
|
+
import { Command as Command13 } from "commander";
|
|
1291
1689
|
|
|
1292
1690
|
// src/commands/agent/run.ts
|
|
1293
|
-
import { Command as
|
|
1294
|
-
import * as
|
|
1691
|
+
import { Command as Command11 } from "commander";
|
|
1692
|
+
import * as path17 from "path";
|
|
1295
1693
|
import * as childProcess from "child_process";
|
|
1296
|
-
import { Ok as
|
|
1694
|
+
import { Ok as Ok12, Err as Err8 } from "@harness-engineering/core";
|
|
1297
1695
|
import { requestPeerReview } from "@harness-engineering/core";
|
|
1298
1696
|
|
|
1299
1697
|
// src/persona/loader.ts
|
|
1300
1698
|
import * as fs6 from "fs";
|
|
1301
|
-
import * as
|
|
1699
|
+
import * as path14 from "path";
|
|
1302
1700
|
import YAML from "yaml";
|
|
1303
|
-
import { Ok as
|
|
1701
|
+
import { Ok as Ok11, Err as Err7 } from "@harness-engineering/core";
|
|
1304
1702
|
|
|
1305
1703
|
// src/persona/schema.ts
|
|
1306
1704
|
import { z as z3 } from "zod";
|
|
@@ -1394,7 +1792,7 @@ function loadPersona(filePath) {
|
|
|
1394
1792
|
if (!result.success) {
|
|
1395
1793
|
return Err7(new Error(`Invalid persona ${filePath}: ${result.error.message}`));
|
|
1396
1794
|
}
|
|
1397
|
-
return
|
|
1795
|
+
return Ok11(normalizePersona(result.data));
|
|
1398
1796
|
} catch (error) {
|
|
1399
1797
|
return Err7(
|
|
1400
1798
|
new Error(`Failed to load persona: ${error instanceof Error ? error.message : String(error)}`)
|
|
@@ -1403,17 +1801,17 @@ function loadPersona(filePath) {
|
|
|
1403
1801
|
}
|
|
1404
1802
|
function listPersonas(dir) {
|
|
1405
1803
|
try {
|
|
1406
|
-
if (!fs6.existsSync(dir)) return
|
|
1804
|
+
if (!fs6.existsSync(dir)) return Ok11([]);
|
|
1407
1805
|
const entries = fs6.readdirSync(dir).filter((f) => f.endsWith(".yaml") || f.endsWith(".yml"));
|
|
1408
1806
|
const personas = [];
|
|
1409
1807
|
for (const entry of entries) {
|
|
1410
|
-
const filePath =
|
|
1808
|
+
const filePath = path14.join(dir, entry);
|
|
1411
1809
|
const result = loadPersona(filePath);
|
|
1412
1810
|
if (result.ok) {
|
|
1413
1811
|
personas.push({ name: result.value.name, description: result.value.description, filePath });
|
|
1414
1812
|
}
|
|
1415
1813
|
}
|
|
1416
|
-
return
|
|
1814
|
+
return Ok11(personas);
|
|
1417
1815
|
} catch (error) {
|
|
1418
1816
|
return Err7(
|
|
1419
1817
|
new Error(
|
|
@@ -1425,9 +1823,9 @@ function listPersonas(dir) {
|
|
|
1425
1823
|
|
|
1426
1824
|
// src/persona/trigger-detector.ts
|
|
1427
1825
|
import * as fs7 from "fs";
|
|
1428
|
-
import * as
|
|
1826
|
+
import * as path15 from "path";
|
|
1429
1827
|
function detectTrigger(projectPath) {
|
|
1430
|
-
const handoffPath =
|
|
1828
|
+
const handoffPath = path15.join(projectPath, ".harness", "handoff.json");
|
|
1431
1829
|
if (!fs7.existsSync(handoffPath)) {
|
|
1432
1830
|
return { trigger: "manual" };
|
|
1433
1831
|
}
|
|
@@ -1506,8 +1904,8 @@ async function runPersona(persona, context) {
|
|
|
1506
1904
|
const result = await Promise.race([
|
|
1507
1905
|
context.commandExecutor(step.command),
|
|
1508
1906
|
new Promise(
|
|
1509
|
-
(
|
|
1510
|
-
() =>
|
|
1907
|
+
(resolve22) => setTimeout(
|
|
1908
|
+
() => resolve22({
|
|
1511
1909
|
ok: false,
|
|
1512
1910
|
error: new Error(TIMEOUT_ERROR_MESSAGE)
|
|
1513
1911
|
}),
|
|
@@ -1562,7 +1960,7 @@ async function runPersona(persona, context) {
|
|
|
1562
1960
|
const result = await Promise.race([
|
|
1563
1961
|
context.skillExecutor(step.skill, skillContext),
|
|
1564
1962
|
new Promise(
|
|
1565
|
-
(
|
|
1963
|
+
(resolve22) => setTimeout(() => resolve22(SKILL_TIMEOUT_RESULT), remainingTime)
|
|
1566
1964
|
)
|
|
1567
1965
|
]);
|
|
1568
1966
|
const durationMs = Date.now() - stepStart;
|
|
@@ -1606,7 +2004,7 @@ async function runPersona(persona, context) {
|
|
|
1606
2004
|
|
|
1607
2005
|
// src/persona/skill-executor.ts
|
|
1608
2006
|
import * as fs8 from "fs";
|
|
1609
|
-
import * as
|
|
2007
|
+
import * as path16 from "path";
|
|
1610
2008
|
import { parse as parse2 } from "yaml";
|
|
1611
2009
|
function resolveOutputMode(mode, trigger) {
|
|
1612
2010
|
if (mode !== "auto") return mode;
|
|
@@ -1615,7 +2013,7 @@ function resolveOutputMode(mode, trigger) {
|
|
|
1615
2013
|
function buildArtifactPath(projectPath, headSha) {
|
|
1616
2014
|
const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
1617
2015
|
const sha = headSha?.slice(0, 7) ?? "unknown";
|
|
1618
|
-
return
|
|
2016
|
+
return path16.join(projectPath, ".harness", "reviews", `${date}-${sha}.md`);
|
|
1619
2017
|
}
|
|
1620
2018
|
function buildArtifactContent(skillName, trigger, headSha) {
|
|
1621
2019
|
const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
@@ -1661,7 +2059,7 @@ function buildArtifactContent(skillName, trigger, headSha) {
|
|
|
1661
2059
|
async function executeSkill(skillName, context) {
|
|
1662
2060
|
const startTime = Date.now();
|
|
1663
2061
|
const skillsDir = resolveSkillsDir();
|
|
1664
|
-
const skillDir =
|
|
2062
|
+
const skillDir = path16.join(skillsDir, skillName);
|
|
1665
2063
|
if (!fs8.existsSync(skillDir)) {
|
|
1666
2064
|
return {
|
|
1667
2065
|
status: "fail",
|
|
@@ -1669,7 +2067,7 @@ async function executeSkill(skillName, context) {
|
|
|
1669
2067
|
durationMs: Date.now() - startTime
|
|
1670
2068
|
};
|
|
1671
2069
|
}
|
|
1672
|
-
const yamlPath =
|
|
2070
|
+
const yamlPath = path16.join(skillDir, "skill.yaml");
|
|
1673
2071
|
if (!fs8.existsSync(yamlPath)) {
|
|
1674
2072
|
return {
|
|
1675
2073
|
status: "fail",
|
|
@@ -1687,7 +2085,7 @@ async function executeSkill(skillName, context) {
|
|
|
1687
2085
|
durationMs: Date.now() - startTime
|
|
1688
2086
|
};
|
|
1689
2087
|
}
|
|
1690
|
-
const skillMdPath =
|
|
2088
|
+
const skillMdPath = path16.join(skillDir, "SKILL.md");
|
|
1691
2089
|
if (!fs8.existsSync(skillMdPath)) {
|
|
1692
2090
|
return {
|
|
1693
2091
|
status: "fail",
|
|
@@ -1707,7 +2105,7 @@ Trigger: ${context.trigger}
|
|
|
1707
2105
|
if (resolvedMode === "artifact") {
|
|
1708
2106
|
artifactPath = buildArtifactPath(context.projectPath, context.headSha);
|
|
1709
2107
|
const artifactContent = buildArtifactContent(skillName, context.trigger, context.headSha);
|
|
1710
|
-
const dir =
|
|
2108
|
+
const dir = path16.dirname(artifactPath);
|
|
1711
2109
|
fs8.mkdirSync(dir, { recursive: true });
|
|
1712
2110
|
fs8.writeFileSync(artifactPath, artifactContent, "utf-8");
|
|
1713
2111
|
}
|
|
@@ -1724,6 +2122,8 @@ var ALLOWED_PERSONA_COMMANDS = /* @__PURE__ */ new Set([
|
|
|
1724
2122
|
"validate",
|
|
1725
2123
|
"check-deps",
|
|
1726
2124
|
"check-docs",
|
|
2125
|
+
"check-perf",
|
|
2126
|
+
"check-security",
|
|
1727
2127
|
"cleanup",
|
|
1728
2128
|
"fix-drift",
|
|
1729
2129
|
"add"
|
|
@@ -1765,7 +2165,7 @@ async function runAgentTask(task, options) {
|
|
|
1765
2165
|
return Err8(new CLIError(`Agent task failed: ${reviewResult.error.message}`, ExitCode.ERROR));
|
|
1766
2166
|
}
|
|
1767
2167
|
const review = reviewResult.value;
|
|
1768
|
-
return
|
|
2168
|
+
return Ok12({
|
|
1769
2169
|
success: review.approved,
|
|
1770
2170
|
output: review.approved ? `Agent task '${task}' completed successfully` : `Agent task '${task}' found issues:
|
|
1771
2171
|
${review.comments.map((c) => ` - ${c.message}`).join("\n")}`
|
|
@@ -1782,11 +2182,11 @@ var VALID_TRIGGERS = /* @__PURE__ */ new Set([
|
|
|
1782
2182
|
"auto"
|
|
1783
2183
|
]);
|
|
1784
2184
|
function createRunCommand() {
|
|
1785
|
-
return new
|
|
2185
|
+
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
2186
|
const globalOpts = cmd.optsWithGlobals();
|
|
1787
2187
|
if (opts.persona) {
|
|
1788
2188
|
const personasDir = resolvePersonasDir();
|
|
1789
|
-
const filePath =
|
|
2189
|
+
const filePath = path17.join(personasDir, `${opts.persona}.yaml`);
|
|
1790
2190
|
const personaResult = loadPersona(filePath);
|
|
1791
2191
|
if (!personaResult.ok) {
|
|
1792
2192
|
logger.error(personaResult.error.message);
|
|
@@ -1801,7 +2201,7 @@ function createRunCommand() {
|
|
|
1801
2201
|
}
|
|
1802
2202
|
try {
|
|
1803
2203
|
childProcess.execFileSync("npx", ["harness", command], { stdio: "inherit" });
|
|
1804
|
-
return
|
|
2204
|
+
return Ok12(null);
|
|
1805
2205
|
} catch (error) {
|
|
1806
2206
|
return Err8(new Error(error instanceof Error ? error.message : String(error)));
|
|
1807
2207
|
}
|
|
@@ -1843,9 +2243,9 @@ function createRunCommand() {
|
|
|
1843
2243
|
}
|
|
1844
2244
|
|
|
1845
2245
|
// src/commands/agent/review.ts
|
|
1846
|
-
import { Command as
|
|
1847
|
-
import { execSync } from "child_process";
|
|
1848
|
-
import { Ok as
|
|
2246
|
+
import { Command as Command12 } from "commander";
|
|
2247
|
+
import { execSync as execSync2 } from "child_process";
|
|
2248
|
+
import { Ok as Ok13, Err as Err9 } from "@harness-engineering/core";
|
|
1849
2249
|
import { createSelfReview, parseDiff } from "@harness-engineering/core";
|
|
1850
2250
|
async function runAgentReview(options) {
|
|
1851
2251
|
const configResult = resolveConfig(options.configPath);
|
|
@@ -1855,15 +2255,15 @@ async function runAgentReview(options) {
|
|
|
1855
2255
|
const config = configResult.value;
|
|
1856
2256
|
let diff;
|
|
1857
2257
|
try {
|
|
1858
|
-
diff =
|
|
2258
|
+
diff = execSync2("git diff --cached", { encoding: "utf-8" });
|
|
1859
2259
|
if (!diff) {
|
|
1860
|
-
diff =
|
|
2260
|
+
diff = execSync2("git diff", { encoding: "utf-8" });
|
|
1861
2261
|
}
|
|
1862
2262
|
} catch {
|
|
1863
2263
|
return Err9(new CLIError("Failed to get git diff", ExitCode.ERROR));
|
|
1864
2264
|
}
|
|
1865
2265
|
if (!diff) {
|
|
1866
|
-
return
|
|
2266
|
+
return Ok13({
|
|
1867
2267
|
passed: true,
|
|
1868
2268
|
checklist: [{ check: "No changes to review", passed: true }]
|
|
1869
2269
|
});
|
|
@@ -1883,7 +2283,7 @@ async function runAgentReview(options) {
|
|
|
1883
2283
|
if (!review.ok) {
|
|
1884
2284
|
return Err9(new CLIError(review.error.message, ExitCode.ERROR));
|
|
1885
2285
|
}
|
|
1886
|
-
return
|
|
2286
|
+
return Ok13({
|
|
1887
2287
|
passed: review.value.passed,
|
|
1888
2288
|
checklist: review.value.items.map((item) => ({
|
|
1889
2289
|
check: item.check,
|
|
@@ -1893,7 +2293,7 @@ async function runAgentReview(options) {
|
|
|
1893
2293
|
});
|
|
1894
2294
|
}
|
|
1895
2295
|
function createReviewCommand() {
|
|
1896
|
-
return new
|
|
2296
|
+
return new Command12("review").description("Run self-review on current changes").action(async (_opts, cmd) => {
|
|
1897
2297
|
const globalOpts = cmd.optsWithGlobals();
|
|
1898
2298
|
const mode = globalOpts.json ? OutputMode.JSON : globalOpts.quiet ? OutputMode.QUIET : OutputMode.TEXT;
|
|
1899
2299
|
const result = await runAgentReview({
|
|
@@ -1929,17 +2329,17 @@ function createReviewCommand() {
|
|
|
1929
2329
|
|
|
1930
2330
|
// src/commands/agent/index.ts
|
|
1931
2331
|
function createAgentCommand() {
|
|
1932
|
-
const command = new
|
|
2332
|
+
const command = new Command13("agent").description("Agent orchestration commands");
|
|
1933
2333
|
command.addCommand(createRunCommand());
|
|
1934
2334
|
command.addCommand(createReviewCommand());
|
|
1935
2335
|
return command;
|
|
1936
2336
|
}
|
|
1937
2337
|
|
|
1938
2338
|
// src/commands/add.ts
|
|
1939
|
-
import { Command as
|
|
2339
|
+
import { Command as Command14 } from "commander";
|
|
1940
2340
|
import * as fs9 from "fs";
|
|
1941
|
-
import * as
|
|
1942
|
-
import { Ok as
|
|
2341
|
+
import * as path18 from "path";
|
|
2342
|
+
import { Ok as Ok14, Err as Err10 } from "@harness-engineering/core";
|
|
1943
2343
|
var LAYER_INDEX_TEMPLATE = (name) => `// ${name} layer
|
|
1944
2344
|
// Add your ${name} exports here
|
|
1945
2345
|
|
|
@@ -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,7 +2396,7 @@ 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
2401
|
return Err10(new CLIError(`Module ${name} already exists`, ExitCode.ERROR));
|
|
2002
2402
|
}
|
|
@@ -2005,11 +2405,11 @@ async function runAdd(componentType, name, options) {
|
|
|
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
2414
|
return Err10(new CLIError(`Doc ${name} already exists`, ExitCode.ERROR));
|
|
2015
2415
|
}
|
|
@@ -2022,18 +2422,18 @@ 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
2438
|
return Err10(new CLIError(`Persona ${name} already exists`, ExitCode.ERROR));
|
|
2039
2439
|
}
|
|
@@ -2059,7 +2459,7 @@ focus_areas: []
|
|
|
2059
2459
|
);
|
|
2060
2460
|
}
|
|
2061
2461
|
}
|
|
2062
|
-
return
|
|
2462
|
+
return Ok14({ created });
|
|
2063
2463
|
} catch (error) {
|
|
2064
2464
|
return Err10(
|
|
2065
2465
|
new CLIError(
|
|
@@ -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,13 @@ function createAddCommand() {
|
|
|
2091
2491
|
}
|
|
2092
2492
|
|
|
2093
2493
|
// src/commands/linter/index.ts
|
|
2094
|
-
import { Command as
|
|
2494
|
+
import { Command as Command17 } from "commander";
|
|
2095
2495
|
|
|
2096
2496
|
// src/commands/linter/generate.ts
|
|
2097
|
-
import { Command as
|
|
2497
|
+
import { Command as Command15 } from "commander";
|
|
2098
2498
|
import { generate } from "@harness-engineering/linter-gen";
|
|
2099
2499
|
function createGenerateCommand() {
|
|
2100
|
-
return new
|
|
2500
|
+
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
2501
|
try {
|
|
2102
2502
|
if (options.verbose) {
|
|
2103
2503
|
logger.info(`Parsing config: ${options.config}`);
|
|
@@ -2159,10 +2559,10 @@ Generated ${result.rulesGenerated.length} rules to ${result.outputDir}`);
|
|
|
2159
2559
|
}
|
|
2160
2560
|
|
|
2161
2561
|
// src/commands/linter/validate.ts
|
|
2162
|
-
import { Command as
|
|
2562
|
+
import { Command as Command16 } from "commander";
|
|
2163
2563
|
import { validate } from "@harness-engineering/linter-gen";
|
|
2164
2564
|
function createValidateCommand2() {
|
|
2165
|
-
return new
|
|
2565
|
+
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
2566
|
try {
|
|
2167
2567
|
const result = await validate({ configPath: options.config });
|
|
2168
2568
|
if (options.json) {
|
|
@@ -2181,7 +2581,7 @@ function createValidateCommand2() {
|
|
|
2181
2581
|
|
|
2182
2582
|
// src/commands/linter/index.ts
|
|
2183
2583
|
function createLinterCommand() {
|
|
2184
|
-
const linter = new
|
|
2584
|
+
const linter = new Command17("linter").description(
|
|
2185
2585
|
"Generate and validate ESLint rules from YAML config"
|
|
2186
2586
|
);
|
|
2187
2587
|
linter.addCommand(createGenerateCommand());
|
|
@@ -2190,12 +2590,12 @@ function createLinterCommand() {
|
|
|
2190
2590
|
}
|
|
2191
2591
|
|
|
2192
2592
|
// src/commands/persona/index.ts
|
|
2193
|
-
import { Command as
|
|
2593
|
+
import { Command as Command20 } from "commander";
|
|
2194
2594
|
|
|
2195
2595
|
// src/commands/persona/list.ts
|
|
2196
|
-
import { Command as
|
|
2596
|
+
import { Command as Command18 } from "commander";
|
|
2197
2597
|
function createListCommand() {
|
|
2198
|
-
return new
|
|
2598
|
+
return new Command18("list").description("List available agent personas").action(async (_opts, cmd) => {
|
|
2199
2599
|
const globalOpts = cmd.optsWithGlobals();
|
|
2200
2600
|
const personasDir = resolvePersonasDir();
|
|
2201
2601
|
const result = listPersonas(personasDir);
|
|
@@ -2224,12 +2624,12 @@ function createListCommand() {
|
|
|
2224
2624
|
}
|
|
2225
2625
|
|
|
2226
2626
|
// src/commands/persona/generate.ts
|
|
2227
|
-
import { Command as
|
|
2627
|
+
import { Command as Command19 } from "commander";
|
|
2228
2628
|
import * as fs10 from "fs";
|
|
2229
|
-
import * as
|
|
2629
|
+
import * as path19 from "path";
|
|
2230
2630
|
|
|
2231
2631
|
// src/persona/generators/runtime.ts
|
|
2232
|
-
import { Ok as
|
|
2632
|
+
import { Ok as Ok15, Err as Err11 } from "@harness-engineering/core";
|
|
2233
2633
|
|
|
2234
2634
|
// src/utils/string.ts
|
|
2235
2635
|
function toKebabCase(name) {
|
|
@@ -2246,7 +2646,7 @@ function generateRuntime(persona) {
|
|
|
2246
2646
|
timeout: persona.config.timeout,
|
|
2247
2647
|
severity: persona.config.severity
|
|
2248
2648
|
};
|
|
2249
|
-
return
|
|
2649
|
+
return Ok15(JSON.stringify(config, null, 2));
|
|
2250
2650
|
} catch (error) {
|
|
2251
2651
|
return Err11(
|
|
2252
2652
|
new Error(
|
|
@@ -2257,7 +2657,7 @@ function generateRuntime(persona) {
|
|
|
2257
2657
|
}
|
|
2258
2658
|
|
|
2259
2659
|
// src/persona/generators/agents-md.ts
|
|
2260
|
-
import { Ok as
|
|
2660
|
+
import { Ok as Ok16, Err as Err12 } from "@harness-engineering/core";
|
|
2261
2661
|
function formatTrigger(trigger) {
|
|
2262
2662
|
switch (trigger.event) {
|
|
2263
2663
|
case "on_pr": {
|
|
@@ -2291,7 +2691,7 @@ function generateAgentsMd(persona) {
|
|
|
2291
2691
|
|
|
2292
2692
|
**When this agent flags an issue:** Fix violations before merging. Run ${allCommands} locally to validate.
|
|
2293
2693
|
`;
|
|
2294
|
-
return
|
|
2694
|
+
return Ok16(fragment);
|
|
2295
2695
|
} catch (error) {
|
|
2296
2696
|
return Err12(
|
|
2297
2697
|
new Error(
|
|
@@ -2303,7 +2703,7 @@ function generateAgentsMd(persona) {
|
|
|
2303
2703
|
|
|
2304
2704
|
// src/persona/generators/ci-workflow.ts
|
|
2305
2705
|
import YAML2 from "yaml";
|
|
2306
|
-
import { Ok as
|
|
2706
|
+
import { Ok as Ok17, Err as Err13 } from "@harness-engineering/core";
|
|
2307
2707
|
function buildGitHubTriggers(triggers) {
|
|
2308
2708
|
const on = {};
|
|
2309
2709
|
for (const trigger of triggers) {
|
|
@@ -2351,7 +2751,7 @@ function generateCIWorkflow(persona, platform) {
|
|
|
2351
2751
|
}
|
|
2352
2752
|
}
|
|
2353
2753
|
};
|
|
2354
|
-
return
|
|
2754
|
+
return Ok17(YAML2.stringify(workflow, { lineWidth: 0 }));
|
|
2355
2755
|
} catch (error) {
|
|
2356
2756
|
return Err13(
|
|
2357
2757
|
new Error(
|
|
@@ -2363,25 +2763,25 @@ function generateCIWorkflow(persona, platform) {
|
|
|
2363
2763
|
|
|
2364
2764
|
// src/commands/persona/generate.ts
|
|
2365
2765
|
function createGenerateCommand2() {
|
|
2366
|
-
return new
|
|
2766
|
+
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
2767
|
const globalOpts = cmd.optsWithGlobals();
|
|
2368
2768
|
const personasDir = resolvePersonasDir();
|
|
2369
|
-
const filePath =
|
|
2769
|
+
const filePath = path19.join(personasDir, `${name}.yaml`);
|
|
2370
2770
|
const personaResult = loadPersona(filePath);
|
|
2371
2771
|
if (!personaResult.ok) {
|
|
2372
2772
|
logger.error(personaResult.error.message);
|
|
2373
2773
|
process.exit(ExitCode.ERROR);
|
|
2374
2774
|
}
|
|
2375
2775
|
const persona = personaResult.value;
|
|
2376
|
-
const outputDir =
|
|
2776
|
+
const outputDir = path19.resolve(opts.outputDir);
|
|
2377
2777
|
const slug = toKebabCase(persona.name);
|
|
2378
2778
|
const only = opts.only;
|
|
2379
2779
|
const generated = [];
|
|
2380
2780
|
if (!only || only === "runtime") {
|
|
2381
2781
|
const result = generateRuntime(persona);
|
|
2382
2782
|
if (result.ok) {
|
|
2383
|
-
const outPath =
|
|
2384
|
-
fs10.mkdirSync(
|
|
2783
|
+
const outPath = path19.join(outputDir, `${slug}.runtime.json`);
|
|
2784
|
+
fs10.mkdirSync(path19.dirname(outPath), { recursive: true });
|
|
2385
2785
|
fs10.writeFileSync(outPath, result.value);
|
|
2386
2786
|
generated.push(outPath);
|
|
2387
2787
|
}
|
|
@@ -2389,7 +2789,7 @@ function createGenerateCommand2() {
|
|
|
2389
2789
|
if (!only || only === "agents-md") {
|
|
2390
2790
|
const result = generateAgentsMd(persona);
|
|
2391
2791
|
if (result.ok) {
|
|
2392
|
-
const outPath =
|
|
2792
|
+
const outPath = path19.join(outputDir, `${slug}.agents.md`);
|
|
2393
2793
|
fs10.writeFileSync(outPath, result.value);
|
|
2394
2794
|
generated.push(outPath);
|
|
2395
2795
|
}
|
|
@@ -2397,8 +2797,8 @@ function createGenerateCommand2() {
|
|
|
2397
2797
|
if (!only || only === "ci") {
|
|
2398
2798
|
const result = generateCIWorkflow(persona, "github");
|
|
2399
2799
|
if (result.ok) {
|
|
2400
|
-
const outPath =
|
|
2401
|
-
fs10.mkdirSync(
|
|
2800
|
+
const outPath = path19.join(outputDir, ".github", "workflows", `${slug}.yml`);
|
|
2801
|
+
fs10.mkdirSync(path19.dirname(outPath), { recursive: true });
|
|
2402
2802
|
fs10.writeFileSync(outPath, result.value);
|
|
2403
2803
|
generated.push(outPath);
|
|
2404
2804
|
}
|
|
@@ -2413,22 +2813,22 @@ function createGenerateCommand2() {
|
|
|
2413
2813
|
|
|
2414
2814
|
// src/commands/persona/index.ts
|
|
2415
2815
|
function createPersonaCommand() {
|
|
2416
|
-
const command = new
|
|
2816
|
+
const command = new Command20("persona").description("Agent persona management commands");
|
|
2417
2817
|
command.addCommand(createListCommand());
|
|
2418
2818
|
command.addCommand(createGenerateCommand2());
|
|
2419
2819
|
return command;
|
|
2420
2820
|
}
|
|
2421
2821
|
|
|
2422
2822
|
// src/commands/skill/index.ts
|
|
2423
|
-
import { Command as
|
|
2823
|
+
import { Command as Command25 } from "commander";
|
|
2424
2824
|
|
|
2425
2825
|
// src/commands/skill/list.ts
|
|
2426
|
-
import { Command as
|
|
2826
|
+
import { Command as Command21 } from "commander";
|
|
2427
2827
|
import * as fs11 from "fs";
|
|
2428
|
-
import * as
|
|
2828
|
+
import * as path20 from "path";
|
|
2429
2829
|
import { parse as parse3 } from "yaml";
|
|
2430
2830
|
function createListCommand2() {
|
|
2431
|
-
return new
|
|
2831
|
+
return new Command21("list").description("List available skills").action(async (_opts, cmd) => {
|
|
2432
2832
|
const globalOpts = cmd.optsWithGlobals();
|
|
2433
2833
|
const skillsDir = resolveSkillsDir();
|
|
2434
2834
|
if (!fs11.existsSync(skillsDir)) {
|
|
@@ -2439,7 +2839,7 @@ function createListCommand2() {
|
|
|
2439
2839
|
const entries = fs11.readdirSync(skillsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
|
|
2440
2840
|
const skills = [];
|
|
2441
2841
|
for (const name of entries) {
|
|
2442
|
-
const yamlPath =
|
|
2842
|
+
const yamlPath = path20.join(skillsDir, name, "skill.yaml");
|
|
2443
2843
|
if (!fs11.existsSync(yamlPath)) continue;
|
|
2444
2844
|
try {
|
|
2445
2845
|
const raw = fs11.readFileSync(yamlPath, "utf-8");
|
|
@@ -2472,13 +2872,13 @@ function createListCommand2() {
|
|
|
2472
2872
|
}
|
|
2473
2873
|
|
|
2474
2874
|
// src/commands/skill/run.ts
|
|
2475
|
-
import { Command as
|
|
2875
|
+
import { Command as Command22 } from "commander";
|
|
2476
2876
|
import * as fs12 from "fs";
|
|
2477
|
-
import * as
|
|
2877
|
+
import * as path21 from "path";
|
|
2478
2878
|
import { parse as parse4 } from "yaml";
|
|
2479
2879
|
|
|
2480
2880
|
// src/skill/complexity.ts
|
|
2481
|
-
import { execSync as
|
|
2881
|
+
import { execSync as execSync3 } from "child_process";
|
|
2482
2882
|
function evaluateSignals(signals) {
|
|
2483
2883
|
if (signals.fileCount >= 3) return "full";
|
|
2484
2884
|
if (signals.newDir) return "full";
|
|
@@ -2490,17 +2890,17 @@ function evaluateSignals(signals) {
|
|
|
2490
2890
|
}
|
|
2491
2891
|
function detectComplexity(projectPath) {
|
|
2492
2892
|
try {
|
|
2493
|
-
const base =
|
|
2893
|
+
const base = execSync3("git merge-base HEAD main", {
|
|
2494
2894
|
cwd: projectPath,
|
|
2495
2895
|
encoding: "utf-8",
|
|
2496
2896
|
stdio: ["pipe", "pipe", "pipe"]
|
|
2497
2897
|
}).trim();
|
|
2498
|
-
const diffFiles =
|
|
2898
|
+
const diffFiles = execSync3(`git diff --name-only ${base}`, {
|
|
2499
2899
|
cwd: projectPath,
|
|
2500
2900
|
encoding: "utf-8",
|
|
2501
2901
|
stdio: ["pipe", "pipe", "pipe"]
|
|
2502
2902
|
}).trim().split("\n").filter(Boolean);
|
|
2503
|
-
const diffStat =
|
|
2903
|
+
const diffStat = execSync3(`git diff --stat ${base}`, {
|
|
2504
2904
|
cwd: projectPath,
|
|
2505
2905
|
encoding: "utf-8",
|
|
2506
2906
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -2562,15 +2962,15 @@ ${options.priorState}`);
|
|
|
2562
2962
|
|
|
2563
2963
|
// src/commands/skill/run.ts
|
|
2564
2964
|
function createRunCommand2() {
|
|
2565
|
-
return new
|
|
2965
|
+
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
2966
|
const skillsDir = resolveSkillsDir();
|
|
2567
|
-
const skillDir =
|
|
2967
|
+
const skillDir = path21.join(skillsDir, name);
|
|
2568
2968
|
if (!fs12.existsSync(skillDir)) {
|
|
2569
2969
|
logger.error(`Skill not found: ${name}`);
|
|
2570
2970
|
process.exit(ExitCode.ERROR);
|
|
2571
2971
|
return;
|
|
2572
2972
|
}
|
|
2573
|
-
const yamlPath =
|
|
2973
|
+
const yamlPath = path21.join(skillDir, "skill.yaml");
|
|
2574
2974
|
let metadata = null;
|
|
2575
2975
|
if (fs12.existsSync(yamlPath)) {
|
|
2576
2976
|
try {
|
|
@@ -2585,15 +2985,15 @@ function createRunCommand2() {
|
|
|
2585
2985
|
if (metadata?.phases && metadata.phases.length > 0) {
|
|
2586
2986
|
const requested = opts.complexity ?? "auto";
|
|
2587
2987
|
if (requested === "auto") {
|
|
2588
|
-
const projectPath2 = opts.path ?
|
|
2988
|
+
const projectPath2 = opts.path ? path21.resolve(opts.path) : process.cwd();
|
|
2589
2989
|
complexity = detectComplexity(projectPath2);
|
|
2590
2990
|
} else {
|
|
2591
2991
|
complexity = requested;
|
|
2592
2992
|
}
|
|
2593
2993
|
}
|
|
2594
2994
|
let principles;
|
|
2595
|
-
const projectPath = opts.path ?
|
|
2596
|
-
const principlesPath =
|
|
2995
|
+
const projectPath = opts.path ? path21.resolve(opts.path) : process.cwd();
|
|
2996
|
+
const principlesPath = path21.join(projectPath, "docs", "principles.md");
|
|
2597
2997
|
if (fs12.existsSync(principlesPath)) {
|
|
2598
2998
|
principles = fs12.readFileSync(principlesPath, "utf-8");
|
|
2599
2999
|
}
|
|
@@ -2610,13 +3010,13 @@ function createRunCommand2() {
|
|
|
2610
3010
|
}
|
|
2611
3011
|
if (metadata?.state.persistent && metadata.state.files.length > 0) {
|
|
2612
3012
|
for (const stateFilePath of metadata.state.files) {
|
|
2613
|
-
const fullPath =
|
|
3013
|
+
const fullPath = path21.join(projectPath, stateFilePath);
|
|
2614
3014
|
if (fs12.existsSync(fullPath)) {
|
|
2615
3015
|
const stat = fs12.statSync(fullPath);
|
|
2616
3016
|
if (stat.isDirectory()) {
|
|
2617
|
-
const files = fs12.readdirSync(fullPath).map((f) => ({ name: f, mtime: fs12.statSync(
|
|
3017
|
+
const files = fs12.readdirSync(fullPath).map((f) => ({ name: f, mtime: fs12.statSync(path21.join(fullPath, f)).mtimeMs })).sort((a, b) => b.mtime - a.mtime);
|
|
2618
3018
|
if (files.length > 0) {
|
|
2619
|
-
priorState = fs12.readFileSync(
|
|
3019
|
+
priorState = fs12.readFileSync(path21.join(fullPath, files[0].name), "utf-8");
|
|
2620
3020
|
}
|
|
2621
3021
|
} else {
|
|
2622
3022
|
priorState = fs12.readFileSync(fullPath, "utf-8");
|
|
@@ -2638,7 +3038,7 @@ function createRunCommand2() {
|
|
|
2638
3038
|
...stateWarning !== void 0 && { stateWarning },
|
|
2639
3039
|
party: opts.party
|
|
2640
3040
|
});
|
|
2641
|
-
const skillMdPath =
|
|
3041
|
+
const skillMdPath = path21.join(skillDir, "SKILL.md");
|
|
2642
3042
|
if (!fs12.existsSync(skillMdPath)) {
|
|
2643
3043
|
logger.error(`SKILL.md not found for skill: ${name}`);
|
|
2644
3044
|
process.exit(ExitCode.ERROR);
|
|
@@ -2646,7 +3046,7 @@ function createRunCommand2() {
|
|
|
2646
3046
|
}
|
|
2647
3047
|
let content = fs12.readFileSync(skillMdPath, "utf-8");
|
|
2648
3048
|
if (metadata?.state.persistent && opts.path) {
|
|
2649
|
-
const stateFile =
|
|
3049
|
+
const stateFile = path21.join(projectPath, ".harness", "state.json");
|
|
2650
3050
|
if (fs12.existsSync(stateFile)) {
|
|
2651
3051
|
const stateContent = fs12.readFileSync(stateFile, "utf-8");
|
|
2652
3052
|
content += `
|
|
@@ -2665,9 +3065,9 @@ ${stateContent}
|
|
|
2665
3065
|
}
|
|
2666
3066
|
|
|
2667
3067
|
// src/commands/skill/validate.ts
|
|
2668
|
-
import { Command as
|
|
3068
|
+
import { Command as Command23 } from "commander";
|
|
2669
3069
|
import * as fs13 from "fs";
|
|
2670
|
-
import * as
|
|
3070
|
+
import * as path22 from "path";
|
|
2671
3071
|
import { parse as parse5 } from "yaml";
|
|
2672
3072
|
var REQUIRED_SECTIONS = [
|
|
2673
3073
|
"## When to Use",
|
|
@@ -2677,7 +3077,7 @@ var REQUIRED_SECTIONS = [
|
|
|
2677
3077
|
"## Examples"
|
|
2678
3078
|
];
|
|
2679
3079
|
function createValidateCommand3() {
|
|
2680
|
-
return new
|
|
3080
|
+
return new Command23("validate").description("Validate all skill.yaml files and SKILL.md structure").action(async (_opts, cmd) => {
|
|
2681
3081
|
const globalOpts = cmd.optsWithGlobals();
|
|
2682
3082
|
const skillsDir = resolveSkillsDir();
|
|
2683
3083
|
if (!fs13.existsSync(skillsDir)) {
|
|
@@ -2689,9 +3089,9 @@ function createValidateCommand3() {
|
|
|
2689
3089
|
const errors = [];
|
|
2690
3090
|
let validated = 0;
|
|
2691
3091
|
for (const name of entries) {
|
|
2692
|
-
const skillDir =
|
|
2693
|
-
const yamlPath =
|
|
2694
|
-
const skillMdPath =
|
|
3092
|
+
const skillDir = path22.join(skillsDir, name);
|
|
3093
|
+
const yamlPath = path22.join(skillDir, "skill.yaml");
|
|
3094
|
+
const skillMdPath = path22.join(skillDir, "SKILL.md");
|
|
2695
3095
|
if (!fs13.existsSync(yamlPath)) {
|
|
2696
3096
|
errors.push(`${name}: missing skill.yaml`);
|
|
2697
3097
|
continue;
|
|
@@ -2746,21 +3146,21 @@ function createValidateCommand3() {
|
|
|
2746
3146
|
}
|
|
2747
3147
|
|
|
2748
3148
|
// src/commands/skill/info.ts
|
|
2749
|
-
import { Command as
|
|
3149
|
+
import { Command as Command24 } from "commander";
|
|
2750
3150
|
import * as fs14 from "fs";
|
|
2751
|
-
import * as
|
|
3151
|
+
import * as path23 from "path";
|
|
2752
3152
|
import { parse as parse6 } from "yaml";
|
|
2753
3153
|
function createInfoCommand() {
|
|
2754
|
-
return new
|
|
3154
|
+
return new Command24("info").description("Show metadata for a skill").argument("<name>", "Skill name (e.g., harness-tdd)").action(async (name, _opts, cmd) => {
|
|
2755
3155
|
const globalOpts = cmd.optsWithGlobals();
|
|
2756
3156
|
const skillsDir = resolveSkillsDir();
|
|
2757
|
-
const skillDir =
|
|
3157
|
+
const skillDir = path23.join(skillsDir, name);
|
|
2758
3158
|
if (!fs14.existsSync(skillDir)) {
|
|
2759
3159
|
logger.error(`Skill not found: ${name}`);
|
|
2760
3160
|
process.exit(ExitCode.ERROR);
|
|
2761
3161
|
return;
|
|
2762
3162
|
}
|
|
2763
|
-
const yamlPath =
|
|
3163
|
+
const yamlPath = path23.join(skillDir, "skill.yaml");
|
|
2764
3164
|
if (!fs14.existsSync(yamlPath)) {
|
|
2765
3165
|
logger.error(`skill.yaml not found for skill: ${name}`);
|
|
2766
3166
|
process.exit(ExitCode.ERROR);
|
|
@@ -2808,7 +3208,7 @@ function createInfoCommand() {
|
|
|
2808
3208
|
|
|
2809
3209
|
// src/commands/skill/index.ts
|
|
2810
3210
|
function createSkillCommand() {
|
|
2811
|
-
const command = new
|
|
3211
|
+
const command = new Command25("skill").description("Skill management commands");
|
|
2812
3212
|
command.addCommand(createListCommand2());
|
|
2813
3213
|
command.addCommand(createRunCommand2());
|
|
2814
3214
|
command.addCommand(createValidateCommand3());
|
|
@@ -2817,17 +3217,17 @@ function createSkillCommand() {
|
|
|
2817
3217
|
}
|
|
2818
3218
|
|
|
2819
3219
|
// src/commands/state/index.ts
|
|
2820
|
-
import { Command as
|
|
3220
|
+
import { Command as Command30 } from "commander";
|
|
2821
3221
|
|
|
2822
3222
|
// src/commands/state/show.ts
|
|
2823
|
-
import { Command as
|
|
2824
|
-
import * as
|
|
3223
|
+
import { Command as Command26 } from "commander";
|
|
3224
|
+
import * as path24 from "path";
|
|
2825
3225
|
import { loadState } from "@harness-engineering/core";
|
|
2826
3226
|
function createShowCommand() {
|
|
2827
|
-
return new
|
|
3227
|
+
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
3228
|
const globalOpts = cmd.optsWithGlobals();
|
|
2829
|
-
const projectPath =
|
|
2830
|
-
const result = await loadState(projectPath);
|
|
3229
|
+
const projectPath = path24.resolve(opts.path);
|
|
3230
|
+
const result = await loadState(projectPath, opts.stream);
|
|
2831
3231
|
if (!result.ok) {
|
|
2832
3232
|
logger.error(result.error.message);
|
|
2833
3233
|
process.exit(ExitCode.ERROR);
|
|
@@ -2839,6 +3239,7 @@ function createShowCommand() {
|
|
|
2839
3239
|
} else if (globalOpts.quiet) {
|
|
2840
3240
|
console.log(JSON.stringify(state));
|
|
2841
3241
|
} else {
|
|
3242
|
+
if (opts.stream) console.log(`Stream: ${opts.stream}`);
|
|
2842
3243
|
console.log(`Schema Version: ${state.schemaVersion}`);
|
|
2843
3244
|
if (state.position.phase) console.log(`Phase: ${state.position.phase}`);
|
|
2844
3245
|
if (state.position.task) console.log(`Task: ${state.position.task}`);
|
|
@@ -2865,14 +3266,26 @@ Decisions: ${state.decisions.length}`);
|
|
|
2865
3266
|
}
|
|
2866
3267
|
|
|
2867
3268
|
// src/commands/state/reset.ts
|
|
2868
|
-
import { Command as
|
|
3269
|
+
import { Command as Command27 } from "commander";
|
|
2869
3270
|
import * as fs15 from "fs";
|
|
2870
|
-
import * as
|
|
3271
|
+
import * as path25 from "path";
|
|
2871
3272
|
import * as readline from "readline";
|
|
3273
|
+
import { resolveStreamPath } from "@harness-engineering/core";
|
|
2872
3274
|
function createResetCommand() {
|
|
2873
|
-
return new
|
|
2874
|
-
const projectPath =
|
|
2875
|
-
|
|
3275
|
+
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) => {
|
|
3276
|
+
const projectPath = path25.resolve(opts.path);
|
|
3277
|
+
let statePath;
|
|
3278
|
+
if (opts.stream) {
|
|
3279
|
+
const streamResult = await resolveStreamPath(projectPath, { stream: opts.stream });
|
|
3280
|
+
if (!streamResult.ok) {
|
|
3281
|
+
logger.error(streamResult.error.message);
|
|
3282
|
+
process.exit(ExitCode.ERROR);
|
|
3283
|
+
return;
|
|
3284
|
+
}
|
|
3285
|
+
statePath = path25.join(streamResult.value, "state.json");
|
|
3286
|
+
} else {
|
|
3287
|
+
statePath = path25.join(projectPath, ".harness", "state.json");
|
|
3288
|
+
}
|
|
2876
3289
|
if (!fs15.existsSync(statePath)) {
|
|
2877
3290
|
logger.info("No state file found. Nothing to reset.");
|
|
2878
3291
|
process.exit(ExitCode.SUCCESS);
|
|
@@ -2880,8 +3293,8 @@ function createResetCommand() {
|
|
|
2880
3293
|
}
|
|
2881
3294
|
if (!opts.yes) {
|
|
2882
3295
|
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] ",
|
|
3296
|
+
const answer = await new Promise((resolve22) => {
|
|
3297
|
+
rl.question("Reset project state? This cannot be undone. [y/N] ", resolve22);
|
|
2885
3298
|
});
|
|
2886
3299
|
rl.close();
|
|
2887
3300
|
if (answer.toLowerCase() !== "y" && answer.toLowerCase() !== "yes") {
|
|
@@ -2903,13 +3316,13 @@ function createResetCommand() {
|
|
|
2903
3316
|
}
|
|
2904
3317
|
|
|
2905
3318
|
// src/commands/state/learn.ts
|
|
2906
|
-
import { Command as
|
|
2907
|
-
import * as
|
|
3319
|
+
import { Command as Command28 } from "commander";
|
|
3320
|
+
import * as path26 from "path";
|
|
2908
3321
|
import { appendLearning } from "@harness-engineering/core";
|
|
2909
3322
|
function createLearnCommand() {
|
|
2910
|
-
return new
|
|
2911
|
-
const projectPath =
|
|
2912
|
-
const result = await appendLearning(projectPath, message);
|
|
3323
|
+
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) => {
|
|
3324
|
+
const projectPath = path26.resolve(opts.path);
|
|
3325
|
+
const result = await appendLearning(projectPath, message, void 0, void 0, opts.stream);
|
|
2913
3326
|
if (!result.ok) {
|
|
2914
3327
|
logger.error(result.error.message);
|
|
2915
3328
|
process.exit(ExitCode.ERROR);
|
|
@@ -2920,29 +3333,103 @@ function createLearnCommand() {
|
|
|
2920
3333
|
});
|
|
2921
3334
|
}
|
|
2922
3335
|
|
|
3336
|
+
// src/commands/state/streams.ts
|
|
3337
|
+
import { Command as Command29 } from "commander";
|
|
3338
|
+
import * as path27 from "path";
|
|
3339
|
+
import {
|
|
3340
|
+
createStream,
|
|
3341
|
+
listStreams,
|
|
3342
|
+
archiveStream,
|
|
3343
|
+
setActiveStream,
|
|
3344
|
+
loadStreamIndex
|
|
3345
|
+
} from "@harness-engineering/core";
|
|
3346
|
+
function createStreamsCommand() {
|
|
3347
|
+
const command = new Command29("streams").description("Manage state streams");
|
|
3348
|
+
command.command("list").description("List all known streams").option("--path <path>", "Project root path", ".").action(async (opts, cmd) => {
|
|
3349
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
3350
|
+
const projectPath = path27.resolve(opts.path);
|
|
3351
|
+
const indexResult = await loadStreamIndex(projectPath);
|
|
3352
|
+
const result = await listStreams(projectPath);
|
|
3353
|
+
if (!result.ok) {
|
|
3354
|
+
logger.error(result.error.message);
|
|
3355
|
+
process.exit(ExitCode.ERROR);
|
|
3356
|
+
return;
|
|
3357
|
+
}
|
|
3358
|
+
const active = indexResult.ok ? indexResult.value.activeStream : null;
|
|
3359
|
+
if (globalOpts.json) {
|
|
3360
|
+
logger.raw({ activeStream: active, streams: result.value });
|
|
3361
|
+
} else {
|
|
3362
|
+
if (result.value.length === 0) {
|
|
3363
|
+
console.log("No streams found.");
|
|
3364
|
+
}
|
|
3365
|
+
for (const s of result.value) {
|
|
3366
|
+
const marker = s.name === active ? " (active)" : "";
|
|
3367
|
+
const branch = s.branch ? ` [${s.branch}]` : "";
|
|
3368
|
+
console.log(` ${s.name}${marker}${branch} \u2014 last active: ${s.lastActiveAt}`);
|
|
3369
|
+
}
|
|
3370
|
+
}
|
|
3371
|
+
process.exit(ExitCode.SUCCESS);
|
|
3372
|
+
});
|
|
3373
|
+
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) => {
|
|
3374
|
+
const projectPath = path27.resolve(opts.path);
|
|
3375
|
+
const result = await createStream(projectPath, name, opts.branch);
|
|
3376
|
+
if (!result.ok) {
|
|
3377
|
+
logger.error(result.error.message);
|
|
3378
|
+
process.exit(ExitCode.ERROR);
|
|
3379
|
+
return;
|
|
3380
|
+
}
|
|
3381
|
+
logger.success(`Stream '${name}' created.`);
|
|
3382
|
+
process.exit(ExitCode.SUCCESS);
|
|
3383
|
+
});
|
|
3384
|
+
command.command("archive <name>").description("Archive a stream").option("--path <path>", "Project root path", ".").action(async (name, opts) => {
|
|
3385
|
+
const projectPath = path27.resolve(opts.path);
|
|
3386
|
+
const result = await archiveStream(projectPath, name);
|
|
3387
|
+
if (!result.ok) {
|
|
3388
|
+
logger.error(result.error.message);
|
|
3389
|
+
process.exit(ExitCode.ERROR);
|
|
3390
|
+
return;
|
|
3391
|
+
}
|
|
3392
|
+
logger.success(`Stream '${name}' archived.`);
|
|
3393
|
+
process.exit(ExitCode.SUCCESS);
|
|
3394
|
+
});
|
|
3395
|
+
command.command("activate <name>").description("Set the active stream").option("--path <path>", "Project root path", ".").action(async (name, opts) => {
|
|
3396
|
+
const projectPath = path27.resolve(opts.path);
|
|
3397
|
+
const result = await setActiveStream(projectPath, name);
|
|
3398
|
+
if (!result.ok) {
|
|
3399
|
+
logger.error(result.error.message);
|
|
3400
|
+
process.exit(ExitCode.ERROR);
|
|
3401
|
+
return;
|
|
3402
|
+
}
|
|
3403
|
+
logger.success(`Active stream set to '${name}'.`);
|
|
3404
|
+
process.exit(ExitCode.SUCCESS);
|
|
3405
|
+
});
|
|
3406
|
+
return command;
|
|
3407
|
+
}
|
|
3408
|
+
|
|
2923
3409
|
// src/commands/state/index.ts
|
|
2924
3410
|
function createStateCommand() {
|
|
2925
|
-
const command = new
|
|
3411
|
+
const command = new Command30("state").description("Project state management commands");
|
|
2926
3412
|
command.addCommand(createShowCommand());
|
|
2927
3413
|
command.addCommand(createResetCommand());
|
|
2928
3414
|
command.addCommand(createLearnCommand());
|
|
3415
|
+
command.addCommand(createStreamsCommand());
|
|
2929
3416
|
return command;
|
|
2930
3417
|
}
|
|
2931
3418
|
|
|
2932
3419
|
// src/commands/check-phase-gate.ts
|
|
2933
|
-
import { Command as
|
|
2934
|
-
import * as
|
|
3420
|
+
import { Command as Command31 } from "commander";
|
|
3421
|
+
import * as path28 from "path";
|
|
2935
3422
|
import * as fs16 from "fs";
|
|
2936
|
-
import { Ok as
|
|
3423
|
+
import { Ok as Ok18 } from "@harness-engineering/core";
|
|
2937
3424
|
function resolveSpecPath(implFile, implPattern, specPattern, cwd) {
|
|
2938
|
-
const relImpl =
|
|
3425
|
+
const relImpl = path28.relative(cwd, implFile);
|
|
2939
3426
|
const implBase = (implPattern.split("*")[0] ?? "").replace(/\/+$/, "");
|
|
2940
3427
|
const afterBase = relImpl.startsWith(implBase + "/") ? relImpl.slice(implBase.length + 1) : relImpl;
|
|
2941
3428
|
const segments = afterBase.split("/");
|
|
2942
3429
|
const firstSegment = segments[0] ?? "";
|
|
2943
|
-
const feature = segments.length > 1 ? firstSegment :
|
|
3430
|
+
const feature = segments.length > 1 ? firstSegment : path28.basename(firstSegment, path28.extname(firstSegment));
|
|
2944
3431
|
const specRelative = specPattern.replace("{feature}", feature);
|
|
2945
|
-
return
|
|
3432
|
+
return path28.resolve(cwd, specRelative);
|
|
2946
3433
|
}
|
|
2947
3434
|
async function runCheckPhaseGate(options) {
|
|
2948
3435
|
const configResult = resolveConfig(options.configPath);
|
|
@@ -2950,9 +3437,9 @@ async function runCheckPhaseGate(options) {
|
|
|
2950
3437
|
return configResult;
|
|
2951
3438
|
}
|
|
2952
3439
|
const config = configResult.value;
|
|
2953
|
-
const cwd = options.cwd ?? (options.configPath ?
|
|
3440
|
+
const cwd = options.cwd ?? (options.configPath ? path28.dirname(path28.resolve(options.configPath)) : process.cwd());
|
|
2954
3441
|
if (!config.phaseGates?.enabled) {
|
|
2955
|
-
return
|
|
3442
|
+
return Ok18({
|
|
2956
3443
|
pass: true,
|
|
2957
3444
|
skipped: true,
|
|
2958
3445
|
missingSpecs: [],
|
|
@@ -2969,14 +3456,14 @@ async function runCheckPhaseGate(options) {
|
|
|
2969
3456
|
const expectedSpec = resolveSpecPath(implFile, mapping.implPattern, mapping.specPattern, cwd);
|
|
2970
3457
|
if (!fs16.existsSync(expectedSpec)) {
|
|
2971
3458
|
missingSpecs.push({
|
|
2972
|
-
implFile:
|
|
2973
|
-
expectedSpec:
|
|
3459
|
+
implFile: path28.relative(cwd, implFile),
|
|
3460
|
+
expectedSpec: path28.relative(cwd, expectedSpec)
|
|
2974
3461
|
});
|
|
2975
3462
|
}
|
|
2976
3463
|
}
|
|
2977
3464
|
}
|
|
2978
3465
|
const pass = missingSpecs.length === 0;
|
|
2979
|
-
return
|
|
3466
|
+
return Ok18({
|
|
2980
3467
|
pass,
|
|
2981
3468
|
skipped: false,
|
|
2982
3469
|
severity: phaseGates.severity,
|
|
@@ -2985,7 +3472,7 @@ async function runCheckPhaseGate(options) {
|
|
|
2985
3472
|
});
|
|
2986
3473
|
}
|
|
2987
3474
|
function createCheckPhaseGateCommand() {
|
|
2988
|
-
const command = new
|
|
3475
|
+
const command = new Command31("check-phase-gate").description("Verify that implementation files have matching spec documents").action(async (_opts, cmd) => {
|
|
2989
3476
|
const globalOpts = cmd.optsWithGlobals();
|
|
2990
3477
|
const mode = globalOpts.json ? OutputMode.JSON : globalOpts.quiet ? OutputMode.QUIET : globalOpts.verbose ? OutputMode.VERBOSE : OutputMode.TEXT;
|
|
2991
3478
|
const formatter = new OutputFormatter(mode);
|
|
@@ -3039,15 +3526,15 @@ function createCheckPhaseGateCommand() {
|
|
|
3039
3526
|
}
|
|
3040
3527
|
|
|
3041
3528
|
// src/commands/generate-slash-commands.ts
|
|
3042
|
-
import { Command as
|
|
3529
|
+
import { Command as Command32 } from "commander";
|
|
3043
3530
|
import fs19 from "fs";
|
|
3044
|
-
import
|
|
3531
|
+
import path31 from "path";
|
|
3045
3532
|
import os2 from "os";
|
|
3046
3533
|
import readline2 from "readline";
|
|
3047
3534
|
|
|
3048
3535
|
// src/slash-commands/normalize.ts
|
|
3049
3536
|
import fs17 from "fs";
|
|
3050
|
-
import
|
|
3537
|
+
import path29 from "path";
|
|
3051
3538
|
import { parse as parse7 } from "yaml";
|
|
3052
3539
|
|
|
3053
3540
|
// src/slash-commands/normalize-name.ts
|
|
@@ -3071,7 +3558,7 @@ function normalizeSkills(skillSources, platforms) {
|
|
|
3071
3558
|
if (!fs17.existsSync(skillsDir)) continue;
|
|
3072
3559
|
const entries = fs17.readdirSync(skillsDir, { withFileTypes: true }).filter((d) => d.isDirectory());
|
|
3073
3560
|
for (const entry of entries) {
|
|
3074
|
-
const yamlPath =
|
|
3561
|
+
const yamlPath = path29.join(skillsDir, entry.name, "skill.yaml");
|
|
3075
3562
|
if (!fs17.existsSync(yamlPath)) continue;
|
|
3076
3563
|
let raw;
|
|
3077
3564
|
try {
|
|
@@ -3099,15 +3586,15 @@ function normalizeSkills(skillSources, platforms) {
|
|
|
3099
3586
|
continue;
|
|
3100
3587
|
}
|
|
3101
3588
|
nameMap.set(normalized, { skillName: meta.name, source });
|
|
3102
|
-
const skillMdPath =
|
|
3589
|
+
const skillMdPath = path29.join(skillsDir, entry.name, "SKILL.md");
|
|
3103
3590
|
const skillMdContent = fs17.existsSync(skillMdPath) ? fs17.readFileSync(skillMdPath, "utf-8") : "";
|
|
3104
|
-
const skillMdRelative =
|
|
3591
|
+
const skillMdRelative = path29.relative(
|
|
3105
3592
|
process.cwd(),
|
|
3106
|
-
|
|
3593
|
+
path29.join(skillsDir, entry.name, "SKILL.md")
|
|
3107
3594
|
);
|
|
3108
|
-
const skillYamlRelative =
|
|
3595
|
+
const skillYamlRelative = path29.relative(
|
|
3109
3596
|
process.cwd(),
|
|
3110
|
-
|
|
3597
|
+
path29.join(skillsDir, entry.name, "skill.yaml")
|
|
3111
3598
|
);
|
|
3112
3599
|
const args = (meta.cli?.args ?? []).map((a) => ({
|
|
3113
3600
|
name: a.name,
|
|
@@ -3277,7 +3764,7 @@ function renderGemini(spec, skillMdContent, skillYamlContent) {
|
|
|
3277
3764
|
|
|
3278
3765
|
// src/slash-commands/sync.ts
|
|
3279
3766
|
import fs18 from "fs";
|
|
3280
|
-
import
|
|
3767
|
+
import path30 from "path";
|
|
3281
3768
|
|
|
3282
3769
|
// src/agent-definitions/constants.ts
|
|
3283
3770
|
var GENERATED_HEADER_AGENT = "<!-- Generated by harness generate-agent-definitions. Do not edit. -->";
|
|
@@ -3289,7 +3776,7 @@ function computeSyncPlan(outputDir, rendered) {
|
|
|
3289
3776
|
const removed = [];
|
|
3290
3777
|
const unchanged = [];
|
|
3291
3778
|
for (const [filename, content] of rendered) {
|
|
3292
|
-
const filePath =
|
|
3779
|
+
const filePath = path30.join(outputDir, filename);
|
|
3293
3780
|
if (!fs18.existsSync(filePath)) {
|
|
3294
3781
|
added.push(filename);
|
|
3295
3782
|
} else {
|
|
@@ -3303,12 +3790,12 @@ function computeSyncPlan(outputDir, rendered) {
|
|
|
3303
3790
|
}
|
|
3304
3791
|
if (fs18.existsSync(outputDir)) {
|
|
3305
3792
|
const existing = fs18.readdirSync(outputDir).filter((f) => {
|
|
3306
|
-
const stat = fs18.statSync(
|
|
3793
|
+
const stat = fs18.statSync(path30.join(outputDir, f));
|
|
3307
3794
|
return stat.isFile();
|
|
3308
3795
|
});
|
|
3309
3796
|
for (const filename of existing) {
|
|
3310
3797
|
if (rendered.has(filename)) continue;
|
|
3311
|
-
const content = fs18.readFileSync(
|
|
3798
|
+
const content = fs18.readFileSync(path30.join(outputDir, filename), "utf-8");
|
|
3312
3799
|
if (content.includes(GENERATED_HEADER_CLAUDE) || content.includes(GENERATED_HEADER_GEMINI) || content.includes(GENERATED_HEADER_AGENT)) {
|
|
3313
3800
|
removed.push(filename);
|
|
3314
3801
|
}
|
|
@@ -3321,12 +3808,12 @@ function applySyncPlan(outputDir, rendered, plan, deleteOrphans) {
|
|
|
3321
3808
|
for (const filename of [...plan.added, ...plan.updated]) {
|
|
3322
3809
|
const content = rendered.get(filename);
|
|
3323
3810
|
if (content !== void 0) {
|
|
3324
|
-
fs18.writeFileSync(
|
|
3811
|
+
fs18.writeFileSync(path30.join(outputDir, filename), content);
|
|
3325
3812
|
}
|
|
3326
3813
|
}
|
|
3327
3814
|
if (deleteOrphans) {
|
|
3328
3815
|
for (const filename of plan.removed) {
|
|
3329
|
-
const filePath =
|
|
3816
|
+
const filePath = path30.join(outputDir, filename);
|
|
3330
3817
|
if (fs18.existsSync(filePath)) {
|
|
3331
3818
|
fs18.unlinkSync(filePath);
|
|
3332
3819
|
}
|
|
@@ -3337,24 +3824,24 @@ function applySyncPlan(outputDir, rendered, plan, deleteOrphans) {
|
|
|
3337
3824
|
// src/commands/generate-slash-commands.ts
|
|
3338
3825
|
function resolveOutputDir(platform, opts) {
|
|
3339
3826
|
if (opts.output) {
|
|
3340
|
-
return
|
|
3827
|
+
return path31.join(opts.output, "harness");
|
|
3341
3828
|
}
|
|
3342
3829
|
if (opts.global) {
|
|
3343
3830
|
const home = os2.homedir();
|
|
3344
|
-
return platform === "claude-code" ?
|
|
3831
|
+
return platform === "claude-code" ? path31.join(home, ".claude", "commands", "harness") : path31.join(home, ".gemini", "commands", "harness");
|
|
3345
3832
|
}
|
|
3346
|
-
return platform === "claude-code" ?
|
|
3833
|
+
return platform === "claude-code" ? path31.join("agents", "commands", "claude-code", "harness") : path31.join("agents", "commands", "gemini-cli", "harness");
|
|
3347
3834
|
}
|
|
3348
3835
|
function fileExtension(platform) {
|
|
3349
3836
|
return platform === "claude-code" ? ".md" : ".toml";
|
|
3350
3837
|
}
|
|
3351
3838
|
async function confirmDeletion(files) {
|
|
3352
3839
|
const rl = readline2.createInterface({ input: process.stdin, output: process.stdout });
|
|
3353
|
-
return new Promise((
|
|
3840
|
+
return new Promise((resolve22) => {
|
|
3354
3841
|
rl.question(`
|
|
3355
3842
|
Remove ${files.length} orphaned command(s)? (y/N) `, (answer) => {
|
|
3356
3843
|
rl.close();
|
|
3357
|
-
|
|
3844
|
+
resolve22(answer.toLowerCase() === "y");
|
|
3358
3845
|
});
|
|
3359
3846
|
});
|
|
3360
3847
|
}
|
|
@@ -3369,7 +3856,7 @@ function generateSlashCommands(opts) {
|
|
|
3369
3856
|
}
|
|
3370
3857
|
if (opts.includeGlobal || skillSources.length === 0) {
|
|
3371
3858
|
const globalDir = resolveGlobalSkillsDir();
|
|
3372
|
-
if (!projectDir ||
|
|
3859
|
+
if (!projectDir || path31.resolve(globalDir) !== path31.resolve(projectDir)) {
|
|
3373
3860
|
skillSources.push({ dir: globalDir, source: "global" });
|
|
3374
3861
|
}
|
|
3375
3862
|
}
|
|
@@ -3391,7 +3878,7 @@ function generateSlashCommands(opts) {
|
|
|
3391
3878
|
executionContext: spec.prompt.executionContext.split("\n").map((line) => {
|
|
3392
3879
|
if (line.startsWith("@")) {
|
|
3393
3880
|
const relPath = line.slice(1);
|
|
3394
|
-
return `@${
|
|
3881
|
+
return `@${path31.resolve(relPath)}`;
|
|
3395
3882
|
}
|
|
3396
3883
|
return line;
|
|
3397
3884
|
}).join("\n")
|
|
@@ -3399,8 +3886,8 @@ function generateSlashCommands(opts) {
|
|
|
3399
3886
|
} : spec;
|
|
3400
3887
|
rendered.set(filename, renderClaudeCode(renderSpec));
|
|
3401
3888
|
} else {
|
|
3402
|
-
const mdPath =
|
|
3403
|
-
const yamlPath =
|
|
3889
|
+
const mdPath = path31.join(spec.skillsBaseDir, spec.sourceDir, "SKILL.md");
|
|
3890
|
+
const yamlPath = path31.join(spec.skillsBaseDir, spec.sourceDir, "skill.yaml");
|
|
3404
3891
|
const mdContent = fs19.existsSync(mdPath) ? fs19.readFileSync(mdPath, "utf-8") : "";
|
|
3405
3892
|
const yamlContent = fs19.existsSync(yamlPath) ? fs19.readFileSync(yamlPath, "utf-8") : "";
|
|
3406
3893
|
rendered.set(filename, renderGemini(spec, mdContent, yamlContent));
|
|
@@ -3428,7 +3915,7 @@ async function handleOrphanDeletion(results, opts) {
|
|
|
3428
3915
|
const shouldDelete = opts.yes || await confirmDeletion(result.removed);
|
|
3429
3916
|
if (shouldDelete) {
|
|
3430
3917
|
for (const filename of result.removed) {
|
|
3431
|
-
const filePath =
|
|
3918
|
+
const filePath = path31.join(result.outputDir, filename);
|
|
3432
3919
|
if (fs19.existsSync(filePath)) {
|
|
3433
3920
|
fs19.unlinkSync(filePath);
|
|
3434
3921
|
}
|
|
@@ -3437,7 +3924,7 @@ async function handleOrphanDeletion(results, opts) {
|
|
|
3437
3924
|
}
|
|
3438
3925
|
}
|
|
3439
3926
|
function createGenerateSlashCommandsCommand() {
|
|
3440
|
-
return new
|
|
3927
|
+
return new Command32("generate-slash-commands").description(
|
|
3441
3928
|
"Generate native slash commands for Claude Code and Gemini CLI from skill metadata"
|
|
3442
3929
|
).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
3930
|
const globalOpts = cmd.optsWithGlobals();
|
|
@@ -3502,10 +3989,10 @@ ${result.platform} \u2192 ${result.outputDir}`);
|
|
|
3502
3989
|
}
|
|
3503
3990
|
|
|
3504
3991
|
// src/commands/ci/index.ts
|
|
3505
|
-
import { Command as
|
|
3992
|
+
import { Command as Command35 } from "commander";
|
|
3506
3993
|
|
|
3507
3994
|
// src/commands/ci/check.ts
|
|
3508
|
-
import { Command as
|
|
3995
|
+
import { Command as Command33 } from "commander";
|
|
3509
3996
|
import { runCIChecks } from "@harness-engineering/core";
|
|
3510
3997
|
var VALID_CHECKS = ["validate", "deps", "docs", "entropy", "phase-gate"];
|
|
3511
3998
|
async function runCICheck(options) {
|
|
@@ -3537,7 +4024,7 @@ function parseFailOn(failOn) {
|
|
|
3537
4024
|
return "error";
|
|
3538
4025
|
}
|
|
3539
4026
|
function createCheckCommand() {
|
|
3540
|
-
return new
|
|
4027
|
+
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
4028
|
const globalOpts = cmd.optsWithGlobals();
|
|
3542
4029
|
const mode = globalOpts.json ? OutputMode.JSON : globalOpts.quiet ? OutputMode.QUIET : globalOpts.verbose ? OutputMode.VERBOSE : OutputMode.TEXT;
|
|
3543
4030
|
const skip = parseSkip(opts.skip);
|
|
@@ -3581,10 +4068,10 @@ function createCheckCommand() {
|
|
|
3581
4068
|
}
|
|
3582
4069
|
|
|
3583
4070
|
// src/commands/ci/init.ts
|
|
3584
|
-
import { Command as
|
|
4071
|
+
import { Command as Command34 } from "commander";
|
|
3585
4072
|
import * as fs20 from "fs";
|
|
3586
|
-
import * as
|
|
3587
|
-
import { Ok as
|
|
4073
|
+
import * as path32 from "path";
|
|
4074
|
+
import { Ok as Ok19, Err as Err14 } from "@harness-engineering/core";
|
|
3588
4075
|
var ALL_CHECKS = ["validate", "deps", "docs", "entropy", "phase-gate"];
|
|
3589
4076
|
function buildSkipFlag(checks) {
|
|
3590
4077
|
if (!checks) return "";
|
|
@@ -3670,7 +4157,7 @@ function generateCIConfig(options) {
|
|
|
3670
4157
|
if (!entry) {
|
|
3671
4158
|
return Err14(new CLIError(`Unknown platform: ${platform}`, ExitCode.ERROR));
|
|
3672
4159
|
}
|
|
3673
|
-
return
|
|
4160
|
+
return Ok19({
|
|
3674
4161
|
filename: entry.filename,
|
|
3675
4162
|
content: entry.generate(skipFlag)
|
|
3676
4163
|
});
|
|
@@ -3681,7 +4168,7 @@ function detectPlatform() {
|
|
|
3681
4168
|
return null;
|
|
3682
4169
|
}
|
|
3683
4170
|
function createInitCommand2() {
|
|
3684
|
-
return new
|
|
4171
|
+
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
4172
|
const globalOpts = cmd.optsWithGlobals();
|
|
3686
4173
|
const platform = opts.platform ?? detectPlatform() ?? "generic";
|
|
3687
4174
|
const checks = opts.checks ? opts.checks.split(",").map((s) => s.trim()) : void 0;
|
|
@@ -3693,8 +4180,8 @@ function createInitCommand2() {
|
|
|
3693
4180
|
process.exit(result.error.exitCode);
|
|
3694
4181
|
}
|
|
3695
4182
|
const { filename, content } = result.value;
|
|
3696
|
-
const targetPath =
|
|
3697
|
-
const dir =
|
|
4183
|
+
const targetPath = path32.resolve(filename);
|
|
4184
|
+
const dir = path32.dirname(targetPath);
|
|
3698
4185
|
fs20.mkdirSync(dir, { recursive: true });
|
|
3699
4186
|
fs20.writeFileSync(targetPath, content);
|
|
3700
4187
|
if (platform === "generic") {
|
|
@@ -3711,15 +4198,15 @@ function createInitCommand2() {
|
|
|
3711
4198
|
|
|
3712
4199
|
// src/commands/ci/index.ts
|
|
3713
4200
|
function createCICommand() {
|
|
3714
|
-
const command = new
|
|
4201
|
+
const command = new Command35("ci").description("CI/CD integration commands");
|
|
3715
4202
|
command.addCommand(createCheckCommand());
|
|
3716
4203
|
command.addCommand(createInitCommand2());
|
|
3717
4204
|
return command;
|
|
3718
4205
|
}
|
|
3719
4206
|
|
|
3720
4207
|
// src/commands/update.ts
|
|
3721
|
-
import { Command as
|
|
3722
|
-
import { execSync as
|
|
4208
|
+
import { Command as Command36 } from "commander";
|
|
4209
|
+
import { execSync as execSync4 } from "child_process";
|
|
3723
4210
|
import { realpathSync } from "fs";
|
|
3724
4211
|
import readline3 from "readline";
|
|
3725
4212
|
import chalk4 from "chalk";
|
|
@@ -3739,7 +4226,7 @@ function detectPackageManager() {
|
|
|
3739
4226
|
return "npm";
|
|
3740
4227
|
}
|
|
3741
4228
|
function getLatestVersion(pkg = "@harness-engineering/cli") {
|
|
3742
|
-
const output =
|
|
4229
|
+
const output = execSync4(`npm view ${pkg} dist-tags.latest`, {
|
|
3743
4230
|
encoding: "utf-8",
|
|
3744
4231
|
timeout: 15e3
|
|
3745
4232
|
});
|
|
@@ -3747,7 +4234,7 @@ function getLatestVersion(pkg = "@harness-engineering/cli") {
|
|
|
3747
4234
|
}
|
|
3748
4235
|
function getInstalledVersion(pm) {
|
|
3749
4236
|
try {
|
|
3750
|
-
const output =
|
|
4237
|
+
const output = execSync4(`${pm} list -g @harness-engineering/cli --json`, {
|
|
3751
4238
|
encoding: "utf-8",
|
|
3752
4239
|
timeout: 15e3
|
|
3753
4240
|
});
|
|
@@ -3760,7 +4247,7 @@ function getInstalledVersion(pm) {
|
|
|
3760
4247
|
}
|
|
3761
4248
|
function getInstalledPackages(pm) {
|
|
3762
4249
|
try {
|
|
3763
|
-
const output =
|
|
4250
|
+
const output = execSync4(`${pm} list -g --json`, {
|
|
3764
4251
|
encoding: "utf-8",
|
|
3765
4252
|
timeout: 15e3
|
|
3766
4253
|
});
|
|
@@ -3776,15 +4263,15 @@ function prompt(question) {
|
|
|
3776
4263
|
input: process.stdin,
|
|
3777
4264
|
output: process.stdout
|
|
3778
4265
|
});
|
|
3779
|
-
return new Promise((
|
|
4266
|
+
return new Promise((resolve22) => {
|
|
3780
4267
|
rl.question(question, (answer) => {
|
|
3781
4268
|
rl.close();
|
|
3782
|
-
|
|
4269
|
+
resolve22(answer.trim().toLowerCase());
|
|
3783
4270
|
});
|
|
3784
4271
|
});
|
|
3785
4272
|
}
|
|
3786
4273
|
function createUpdateCommand() {
|
|
3787
|
-
return new
|
|
4274
|
+
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
4275
|
const globalOpts = cmd.optsWithGlobals();
|
|
3789
4276
|
const pm = detectPackageManager();
|
|
3790
4277
|
if (globalOpts.verbose) {
|
|
@@ -3827,7 +4314,7 @@ function createUpdateCommand() {
|
|
|
3827
4314
|
}
|
|
3828
4315
|
try {
|
|
3829
4316
|
logger.info("Updating packages...");
|
|
3830
|
-
|
|
4317
|
+
execSync4(installCmd, { stdio: "inherit", timeout: 12e4 });
|
|
3831
4318
|
console.log("");
|
|
3832
4319
|
logger.success("Update complete");
|
|
3833
4320
|
} catch {
|
|
@@ -3837,15 +4324,15 @@ function createUpdateCommand() {
|
|
|
3837
4324
|
process.exit(ExitCode.ERROR);
|
|
3838
4325
|
}
|
|
3839
4326
|
console.log("");
|
|
3840
|
-
const regenAnswer = await prompt("Regenerate slash commands? (y/N) ");
|
|
4327
|
+
const regenAnswer = await prompt("Regenerate slash commands and agent definitions? (y/N) ");
|
|
3841
4328
|
if (regenAnswer === "y" || regenAnswer === "yes") {
|
|
3842
4329
|
const scopeAnswer = await prompt("Generate for (g)lobal or (l)ocal project? (g/l) ");
|
|
3843
4330
|
const globalFlag = scopeAnswer === "g" || scopeAnswer === "global" ? " --global" : "";
|
|
3844
4331
|
try {
|
|
3845
|
-
|
|
4332
|
+
execSync4(`harness generate${globalFlag}`, { stdio: "inherit" });
|
|
3846
4333
|
} catch {
|
|
3847
|
-
logger.warn("
|
|
3848
|
-
console.log(` ${chalk4.cyan(`harness generate
|
|
4334
|
+
logger.warn("Generation failed. Run manually:");
|
|
4335
|
+
console.log(` ${chalk4.cyan(`harness generate${globalFlag}`)}`);
|
|
3849
4336
|
}
|
|
3850
4337
|
}
|
|
3851
4338
|
process.exit(ExitCode.SUCCESS);
|
|
@@ -3853,9 +4340,9 @@ function createUpdateCommand() {
|
|
|
3853
4340
|
}
|
|
3854
4341
|
|
|
3855
4342
|
// src/commands/generate-agent-definitions.ts
|
|
3856
|
-
import { Command as
|
|
4343
|
+
import { Command as Command37 } from "commander";
|
|
3857
4344
|
import * as fs21 from "fs";
|
|
3858
|
-
import * as
|
|
4345
|
+
import * as path33 from "path";
|
|
3859
4346
|
import * as os3 from "os";
|
|
3860
4347
|
|
|
3861
4348
|
// src/agent-definitions/generator.ts
|
|
@@ -3991,17 +4478,17 @@ function renderGeminiAgent(def) {
|
|
|
3991
4478
|
// src/commands/generate-agent-definitions.ts
|
|
3992
4479
|
function resolveOutputDir2(platform, opts) {
|
|
3993
4480
|
if (opts.output) {
|
|
3994
|
-
return platform === "claude-code" ?
|
|
4481
|
+
return platform === "claude-code" ? path33.join(opts.output, "claude-code") : path33.join(opts.output, "gemini-cli");
|
|
3995
4482
|
}
|
|
3996
4483
|
if (opts.global) {
|
|
3997
4484
|
const home = os3.homedir();
|
|
3998
|
-
return platform === "claude-code" ?
|
|
4485
|
+
return platform === "claude-code" ? path33.join(home, ".claude", "agents") : path33.join(home, ".gemini", "agents");
|
|
3999
4486
|
}
|
|
4000
|
-
return platform === "claude-code" ?
|
|
4487
|
+
return platform === "claude-code" ? path33.join("agents", "agents", "claude-code") : path33.join("agents", "agents", "gemini-cli");
|
|
4001
4488
|
}
|
|
4002
4489
|
function loadSkillContent(skillName) {
|
|
4003
4490
|
const skillsDir = resolveSkillsDir();
|
|
4004
|
-
const skillMdPath =
|
|
4491
|
+
const skillMdPath = path33.join(skillsDir, skillName, "SKILL.md");
|
|
4005
4492
|
if (!fs21.existsSync(skillMdPath)) return null;
|
|
4006
4493
|
return fs21.readFileSync(skillMdPath, "utf-8");
|
|
4007
4494
|
}
|
|
@@ -4047,7 +4534,7 @@ function generateAgentDefinitions(opts) {
|
|
|
4047
4534
|
return results;
|
|
4048
4535
|
}
|
|
4049
4536
|
function createGenerateAgentDefinitionsCommand() {
|
|
4050
|
-
return new
|
|
4537
|
+
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
4538
|
const globalOpts = cmd.optsWithGlobals();
|
|
4052
4539
|
const platforms = opts.platforms.split(",").map((p) => p.trim());
|
|
4053
4540
|
for (const p of platforms) {
|
|
@@ -4095,9 +4582,9 @@ ${result.platform} \u2192 ${result.outputDir}`);
|
|
|
4095
4582
|
}
|
|
4096
4583
|
|
|
4097
4584
|
// src/commands/generate.ts
|
|
4098
|
-
import { Command as
|
|
4585
|
+
import { Command as Command38 } from "commander";
|
|
4099
4586
|
function createGenerateCommand3() {
|
|
4100
|
-
return new
|
|
4587
|
+
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
4588
|
const globalOpts = cmd.optsWithGlobals();
|
|
4102
4589
|
const platforms = opts.platforms.split(",").map((p) => p.trim());
|
|
4103
4590
|
for (const p of platforms) {
|
|
@@ -4156,8 +4643,8 @@ function createGenerateCommand3() {
|
|
|
4156
4643
|
}
|
|
4157
4644
|
|
|
4158
4645
|
// src/commands/graph/scan.ts
|
|
4159
|
-
import { Command as
|
|
4160
|
-
import * as
|
|
4646
|
+
import { Command as Command39 } from "commander";
|
|
4647
|
+
import * as path34 from "path";
|
|
4161
4648
|
async function runScan(projectPath) {
|
|
4162
4649
|
const { GraphStore, CodeIngestor, TopologicalLinker, KnowledgeIngestor, GitIngestor } = await import("@harness-engineering/graph");
|
|
4163
4650
|
const store = new GraphStore();
|
|
@@ -4170,13 +4657,13 @@ async function runScan(projectPath) {
|
|
|
4170
4657
|
await new GitIngestor(store).ingest(projectPath);
|
|
4171
4658
|
} catch {
|
|
4172
4659
|
}
|
|
4173
|
-
const graphDir =
|
|
4660
|
+
const graphDir = path34.join(projectPath, ".harness", "graph");
|
|
4174
4661
|
await store.save(graphDir);
|
|
4175
4662
|
return { nodeCount: store.nodeCount, edgeCount: store.edgeCount, durationMs: Date.now() - start };
|
|
4176
4663
|
}
|
|
4177
4664
|
function createScanCommand() {
|
|
4178
|
-
return new
|
|
4179
|
-
const projectPath =
|
|
4665
|
+
return new Command39("scan").description("Scan project and build knowledge graph").argument("[path]", "Project root path", ".").action(async (inputPath, _opts, cmd) => {
|
|
4666
|
+
const projectPath = path34.resolve(inputPath);
|
|
4180
4667
|
const globalOpts = cmd.optsWithGlobals();
|
|
4181
4668
|
try {
|
|
4182
4669
|
const result = await runScan(projectPath);
|
|
@@ -4195,12 +4682,12 @@ function createScanCommand() {
|
|
|
4195
4682
|
}
|
|
4196
4683
|
|
|
4197
4684
|
// src/commands/graph/ingest.ts
|
|
4198
|
-
import { Command as
|
|
4199
|
-
import * as
|
|
4685
|
+
import { Command as Command40 } from "commander";
|
|
4686
|
+
import * as path35 from "path";
|
|
4200
4687
|
async function loadConnectorConfig(projectPath, source) {
|
|
4201
4688
|
try {
|
|
4202
4689
|
const fs22 = await import("fs/promises");
|
|
4203
|
-
const configPath =
|
|
4690
|
+
const configPath = path35.join(projectPath, "harness.config.json");
|
|
4204
4691
|
const config = JSON.parse(await fs22.readFile(configPath, "utf-8"));
|
|
4205
4692
|
const connector = config.graph?.connectors?.find(
|
|
4206
4693
|
(c) => c.source === source
|
|
@@ -4241,7 +4728,7 @@ async function runIngest(projectPath, source, opts) {
|
|
|
4241
4728
|
JiraConnector,
|
|
4242
4729
|
SlackConnector
|
|
4243
4730
|
} = await import("@harness-engineering/graph");
|
|
4244
|
-
const graphDir =
|
|
4731
|
+
const graphDir = path35.join(projectPath, ".harness", "graph");
|
|
4245
4732
|
const store = new GraphStore();
|
|
4246
4733
|
await store.load(graphDir);
|
|
4247
4734
|
if (opts?.all) {
|
|
@@ -4302,13 +4789,13 @@ async function runIngest(projectPath, source, opts) {
|
|
|
4302
4789
|
return result;
|
|
4303
4790
|
}
|
|
4304
4791
|
function createIngestCommand() {
|
|
4305
|
-
return new
|
|
4792
|
+
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
4793
|
if (!opts.source && !opts.all) {
|
|
4307
4794
|
console.error("Error: --source or --all is required");
|
|
4308
4795
|
process.exit(1);
|
|
4309
4796
|
}
|
|
4310
4797
|
const globalOpts = cmd.optsWithGlobals();
|
|
4311
|
-
const projectPath =
|
|
4798
|
+
const projectPath = path35.resolve(globalOpts.config ? path35.dirname(globalOpts.config) : ".");
|
|
4312
4799
|
try {
|
|
4313
4800
|
const result = await runIngest(projectPath, opts.source ?? "", {
|
|
4314
4801
|
full: opts.full,
|
|
@@ -4330,12 +4817,12 @@ function createIngestCommand() {
|
|
|
4330
4817
|
}
|
|
4331
4818
|
|
|
4332
4819
|
// src/commands/graph/query.ts
|
|
4333
|
-
import { Command as
|
|
4334
|
-
import * as
|
|
4820
|
+
import { Command as Command41 } from "commander";
|
|
4821
|
+
import * as path36 from "path";
|
|
4335
4822
|
async function runQuery(projectPath, rootNodeId, opts) {
|
|
4336
4823
|
const { GraphStore, ContextQL } = await import("@harness-engineering/graph");
|
|
4337
4824
|
const store = new GraphStore();
|
|
4338
|
-
const graphDir =
|
|
4825
|
+
const graphDir = path36.join(projectPath, ".harness", "graph");
|
|
4339
4826
|
const loaded = await store.load(graphDir);
|
|
4340
4827
|
if (!loaded) throw new Error("No graph found. Run `harness scan` first.");
|
|
4341
4828
|
const params = {
|
|
@@ -4349,9 +4836,9 @@ async function runQuery(projectPath, rootNodeId, opts) {
|
|
|
4349
4836
|
return cql.execute(params);
|
|
4350
4837
|
}
|
|
4351
4838
|
function createQueryCommand() {
|
|
4352
|
-
return new
|
|
4839
|
+
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
4840
|
const globalOpts = cmd.optsWithGlobals();
|
|
4354
|
-
const projectPath =
|
|
4841
|
+
const projectPath = path36.resolve(globalOpts.config ? path36.dirname(globalOpts.config) : ".");
|
|
4355
4842
|
try {
|
|
4356
4843
|
const result = await runQuery(projectPath, rootNodeId, {
|
|
4357
4844
|
depth: parseInt(opts.depth),
|
|
@@ -4377,18 +4864,18 @@ function createQueryCommand() {
|
|
|
4377
4864
|
}
|
|
4378
4865
|
|
|
4379
4866
|
// src/commands/graph/index.ts
|
|
4380
|
-
import { Command as
|
|
4867
|
+
import { Command as Command42 } from "commander";
|
|
4381
4868
|
|
|
4382
4869
|
// src/commands/graph/status.ts
|
|
4383
|
-
import * as
|
|
4870
|
+
import * as path37 from "path";
|
|
4384
4871
|
async function runGraphStatus(projectPath) {
|
|
4385
4872
|
const { GraphStore } = await import("@harness-engineering/graph");
|
|
4386
|
-
const graphDir =
|
|
4873
|
+
const graphDir = path37.join(projectPath, ".harness", "graph");
|
|
4387
4874
|
const store = new GraphStore();
|
|
4388
4875
|
const loaded = await store.load(graphDir);
|
|
4389
4876
|
if (!loaded) return { status: "no_graph", message: "No graph found. Run `harness scan` first." };
|
|
4390
4877
|
const fs22 = await import("fs/promises");
|
|
4391
|
-
const metaPath =
|
|
4878
|
+
const metaPath = path37.join(graphDir, "metadata.json");
|
|
4392
4879
|
let lastScan = "unknown";
|
|
4393
4880
|
try {
|
|
4394
4881
|
const meta = JSON.parse(await fs22.readFile(metaPath, "utf-8"));
|
|
@@ -4402,7 +4889,7 @@ async function runGraphStatus(projectPath) {
|
|
|
4402
4889
|
}
|
|
4403
4890
|
let connectorSyncStatus = {};
|
|
4404
4891
|
try {
|
|
4405
|
-
const syncMetaPath =
|
|
4892
|
+
const syncMetaPath = path37.join(graphDir, "sync-metadata.json");
|
|
4406
4893
|
const syncMeta = JSON.parse(await fs22.readFile(syncMetaPath, "utf-8"));
|
|
4407
4894
|
for (const [name, data] of Object.entries(syncMeta.connectors ?? {})) {
|
|
4408
4895
|
connectorSyncStatus[name] = data.lastSyncTimestamp;
|
|
@@ -4420,10 +4907,10 @@ async function runGraphStatus(projectPath) {
|
|
|
4420
4907
|
}
|
|
4421
4908
|
|
|
4422
4909
|
// src/commands/graph/export.ts
|
|
4423
|
-
import * as
|
|
4910
|
+
import * as path38 from "path";
|
|
4424
4911
|
async function runGraphExport(projectPath, format) {
|
|
4425
4912
|
const { GraphStore } = await import("@harness-engineering/graph");
|
|
4426
|
-
const graphDir =
|
|
4913
|
+
const graphDir = path38.join(projectPath, ".harness", "graph");
|
|
4427
4914
|
const store = new GraphStore();
|
|
4428
4915
|
const loaded = await store.load(graphDir);
|
|
4429
4916
|
if (!loaded) throw new Error("No graph found. Run `harness scan` first.");
|
|
@@ -4452,13 +4939,13 @@ async function runGraphExport(projectPath, format) {
|
|
|
4452
4939
|
}
|
|
4453
4940
|
|
|
4454
4941
|
// src/commands/graph/index.ts
|
|
4455
|
-
import * as
|
|
4942
|
+
import * as path39 from "path";
|
|
4456
4943
|
function createGraphCommand() {
|
|
4457
|
-
const graph = new
|
|
4944
|
+
const graph = new Command42("graph").description("Knowledge graph management");
|
|
4458
4945
|
graph.command("status").description("Show graph statistics").action(async (_opts, cmd) => {
|
|
4459
4946
|
try {
|
|
4460
4947
|
const globalOpts = cmd.optsWithGlobals();
|
|
4461
|
-
const projectPath =
|
|
4948
|
+
const projectPath = path39.resolve(globalOpts.config ? path39.dirname(globalOpts.config) : ".");
|
|
4462
4949
|
const result = await runGraphStatus(projectPath);
|
|
4463
4950
|
if (globalOpts.json) {
|
|
4464
4951
|
console.log(JSON.stringify(result, null, 2));
|
|
@@ -4485,7 +4972,7 @@ function createGraphCommand() {
|
|
|
4485
4972
|
});
|
|
4486
4973
|
graph.command("export").description("Export graph").requiredOption("--format <format>", "Output format (json, mermaid)").action(async (opts, cmd) => {
|
|
4487
4974
|
const globalOpts = cmd.optsWithGlobals();
|
|
4488
|
-
const projectPath =
|
|
4975
|
+
const projectPath = path39.resolve(globalOpts.config ? path39.dirname(globalOpts.config) : ".");
|
|
4489
4976
|
try {
|
|
4490
4977
|
const output = await runGraphExport(projectPath, opts.format);
|
|
4491
4978
|
console.log(output);
|
|
@@ -4499,11 +4986,14 @@ function createGraphCommand() {
|
|
|
4499
4986
|
|
|
4500
4987
|
// src/index.ts
|
|
4501
4988
|
function createProgram() {
|
|
4502
|
-
const program = new
|
|
4989
|
+
const program = new Command43();
|
|
4503
4990
|
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
4991
|
program.addCommand(createValidateCommand());
|
|
4505
4992
|
program.addCommand(createCheckDepsCommand());
|
|
4506
4993
|
program.addCommand(createCheckDocsCommand());
|
|
4994
|
+
program.addCommand(createCheckPerfCommand());
|
|
4995
|
+
program.addCommand(createCheckSecurityCommand());
|
|
4996
|
+
program.addCommand(createPerfCommand());
|
|
4507
4997
|
program.addCommand(createInitCommand());
|
|
4508
4998
|
program.addCommand(createCleanupCommand());
|
|
4509
4999
|
program.addCommand(createFixDriftCommand());
|