@harness-engineering/cli 1.6.1 → 1.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/harness.js +3 -2
- package/dist/{chunk-O6NEKDYP.js → chunk-IUFFBBYV.js} +647 -327
- package/dist/{chunk-3U5VZYR7.js → chunk-UDWGSL3T.js} +4 -1
- package/dist/chunk-USEYPS7F.js +6150 -0
- package/dist/dist-4MYPT3OE.js +2528 -0
- package/dist/dist-RBZXXJHG.js +242 -0
- package/dist/index.js +3 -2
- package/dist/validate-cross-check-CPEPNLOD.js +7 -0
- package/package.json +12 -8
- package/dist/validate-cross-check-LNIZ7KGZ.js +0 -6
|
@@ -1,3 +1,38 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BaselineManager,
|
|
3
|
+
CriticalPathResolver,
|
|
4
|
+
EntropyAnalyzer,
|
|
5
|
+
Err,
|
|
6
|
+
Ok,
|
|
7
|
+
SecurityScanner,
|
|
8
|
+
TypeScriptParser,
|
|
9
|
+
VERSION,
|
|
10
|
+
appendLearning,
|
|
11
|
+
applyFixes,
|
|
12
|
+
archiveStream,
|
|
13
|
+
buildSnapshot,
|
|
14
|
+
checkDocCoverage,
|
|
15
|
+
createFixes,
|
|
16
|
+
createSelfReview,
|
|
17
|
+
createStream,
|
|
18
|
+
defineLayer,
|
|
19
|
+
detectCircularDepsInFiles,
|
|
20
|
+
detectDeadCode,
|
|
21
|
+
detectDocDrift,
|
|
22
|
+
generateSuggestions,
|
|
23
|
+
listStreams,
|
|
24
|
+
loadState,
|
|
25
|
+
loadStreamIndex,
|
|
26
|
+
parseDiff,
|
|
27
|
+
parseSecurityConfig,
|
|
28
|
+
requestPeerReview,
|
|
29
|
+
resolveStreamPath,
|
|
30
|
+
runCIChecks,
|
|
31
|
+
setActiveStream,
|
|
32
|
+
validateAgentsMap,
|
|
33
|
+
validateDependencies,
|
|
34
|
+
validateKnowledgeMap
|
|
35
|
+
} from "./chunk-USEYPS7F.js";
|
|
1
36
|
import {
|
|
2
37
|
CLIError,
|
|
3
38
|
ExitCode,
|
|
@@ -9,18 +44,14 @@ import {
|
|
|
9
44
|
|
|
10
45
|
// src/index.ts
|
|
11
46
|
import { Command as Command43 } from "commander";
|
|
12
|
-
import { VERSION } from "@harness-engineering/core";
|
|
13
47
|
|
|
14
48
|
// src/commands/validate.ts
|
|
15
49
|
import { Command } from "commander";
|
|
16
50
|
import * as path2 from "path";
|
|
17
|
-
import { Ok as Ok2 } from "@harness-engineering/core";
|
|
18
|
-
import { validateAgentsMap, validateKnowledgeMap } from "@harness-engineering/core";
|
|
19
51
|
|
|
20
52
|
// src/config/loader.ts
|
|
21
53
|
import * as fs from "fs";
|
|
22
54
|
import * as path from "path";
|
|
23
|
-
import { Ok, Err } from "@harness-engineering/core";
|
|
24
55
|
|
|
25
56
|
// src/config/schema.ts
|
|
26
57
|
import { z } from "zod";
|
|
@@ -241,7 +272,7 @@ async function runValidate(options) {
|
|
|
241
272
|
});
|
|
242
273
|
}
|
|
243
274
|
result.checks.fileStructure = true;
|
|
244
|
-
return
|
|
275
|
+
return Ok(result);
|
|
245
276
|
}
|
|
246
277
|
function createValidateCommand() {
|
|
247
278
|
const command = new Command("validate").description("Run all validation checks").option("--cross-check", "Run cross-artifact consistency validation").action(async (opts, cmd) => {
|
|
@@ -263,7 +294,7 @@ function createValidateCommand() {
|
|
|
263
294
|
process.exit(result.error.exitCode);
|
|
264
295
|
}
|
|
265
296
|
if (opts.crossCheck) {
|
|
266
|
-
const { runCrossCheck: runCrossCheck2 } = await import("./validate-cross-check-
|
|
297
|
+
const { runCrossCheck: runCrossCheck2 } = await import("./validate-cross-check-CPEPNLOD.js");
|
|
267
298
|
const cwd = process.cwd();
|
|
268
299
|
const specsDir = path2.join(cwd, "docs", "specs");
|
|
269
300
|
const plansDir = path2.join(cwd, "docs", "plans");
|
|
@@ -291,13 +322,6 @@ function createValidateCommand() {
|
|
|
291
322
|
// src/commands/check-deps.ts
|
|
292
323
|
import { Command as Command2 } from "commander";
|
|
293
324
|
import * as path3 from "path";
|
|
294
|
-
import { Ok as Ok3 } from "@harness-engineering/core";
|
|
295
|
-
import {
|
|
296
|
-
validateDependencies,
|
|
297
|
-
detectCircularDepsInFiles,
|
|
298
|
-
defineLayer,
|
|
299
|
-
TypeScriptParser
|
|
300
|
-
} from "@harness-engineering/core";
|
|
301
325
|
|
|
302
326
|
// src/utils/files.ts
|
|
303
327
|
import { glob } from "glob";
|
|
@@ -319,7 +343,7 @@ async function runCheckDeps(options) {
|
|
|
319
343
|
circularDeps: []
|
|
320
344
|
};
|
|
321
345
|
if (!config.layers || config.layers.length === 0) {
|
|
322
|
-
return
|
|
346
|
+
return Ok(result);
|
|
323
347
|
}
|
|
324
348
|
const rootDir = path3.resolve(cwd, config.rootDir);
|
|
325
349
|
const parser = new TypeScriptParser();
|
|
@@ -358,7 +382,7 @@ async function runCheckDeps(options) {
|
|
|
358
382
|
}
|
|
359
383
|
}
|
|
360
384
|
}
|
|
361
|
-
return
|
|
385
|
+
return Ok(result);
|
|
362
386
|
}
|
|
363
387
|
function createCheckDepsCommand() {
|
|
364
388
|
const command = new Command2("check-deps").description("Validate dependency layers and detect circular dependencies").action(async (_opts, cmd) => {
|
|
@@ -403,7 +427,6 @@ function createCheckDepsCommand() {
|
|
|
403
427
|
// src/commands/check-perf.ts
|
|
404
428
|
import { Command as Command3 } from "commander";
|
|
405
429
|
import * as path4 from "path";
|
|
406
|
-
import { Ok as Ok4, EntropyAnalyzer } from "@harness-engineering/core";
|
|
407
430
|
async function runCheckPerf(cwd, options) {
|
|
408
431
|
const runAll = !options.structural && !options.size && !options.coupling;
|
|
409
432
|
const analyzer = new EntropyAnalyzer({
|
|
@@ -416,7 +439,7 @@ async function runCheckPerf(cwd, options) {
|
|
|
416
439
|
});
|
|
417
440
|
const analysisResult = await analyzer.analyze();
|
|
418
441
|
if (!analysisResult.ok) {
|
|
419
|
-
return
|
|
442
|
+
return Ok({
|
|
420
443
|
valid: false,
|
|
421
444
|
violations: [
|
|
422
445
|
{
|
|
@@ -477,7 +500,7 @@ async function runCheckPerf(cwd, options) {
|
|
|
477
500
|
const errorCount = violations.filter((v) => v.severity === "error").length;
|
|
478
501
|
const warningCount = violations.filter((v) => v.severity === "warning").length;
|
|
479
502
|
const infoCount = violations.filter((v) => v.severity === "info").length;
|
|
480
|
-
return
|
|
503
|
+
return Ok({
|
|
481
504
|
valid: !hasErrors,
|
|
482
505
|
violations,
|
|
483
506
|
stats: {
|
|
@@ -527,7 +550,6 @@ function createCheckPerfCommand() {
|
|
|
527
550
|
import { Command as Command4 } from "commander";
|
|
528
551
|
import * as path5 from "path";
|
|
529
552
|
import { execSync } from "child_process";
|
|
530
|
-
import { Ok as Ok5, SecurityScanner, parseSecurityConfig } from "@harness-engineering/core";
|
|
531
553
|
var SEVERITY_RANK = {
|
|
532
554
|
error: 3,
|
|
533
555
|
warning: 2,
|
|
@@ -548,10 +570,10 @@ async function runCheckSecurity(cwd, options) {
|
|
|
548
570
|
const projectRoot = path5.resolve(cwd);
|
|
549
571
|
let configData = {};
|
|
550
572
|
try {
|
|
551
|
-
const
|
|
573
|
+
const fs25 = await import("fs");
|
|
552
574
|
const configPath = path5.join(projectRoot, "harness.config.json");
|
|
553
|
-
if (
|
|
554
|
-
const raw =
|
|
575
|
+
if (fs25.existsSync(configPath)) {
|
|
576
|
+
const raw = fs25.readFileSync(configPath, "utf-8");
|
|
555
577
|
const parsed = JSON.parse(raw);
|
|
556
578
|
configData = parsed.security ?? {};
|
|
557
579
|
}
|
|
@@ -579,7 +601,7 @@ async function runCheckSecurity(cwd, options) {
|
|
|
579
601
|
const thresholdRank = SEVERITY_RANK[threshold];
|
|
580
602
|
const filtered = result.findings.filter((f) => SEVERITY_RANK[f.severity] >= thresholdRank);
|
|
581
603
|
const hasErrors = filtered.some((f) => f.severity === "error");
|
|
582
|
-
return
|
|
604
|
+
return Ok({
|
|
583
605
|
valid: !hasErrors,
|
|
584
606
|
findings: filtered,
|
|
585
607
|
stats: {
|
|
@@ -633,13 +655,12 @@ function createCheckSecurityCommand() {
|
|
|
633
655
|
// src/commands/perf.ts
|
|
634
656
|
import { Command as Command5 } from "commander";
|
|
635
657
|
import * as path6 from "path";
|
|
636
|
-
import { BaselineManager, CriticalPathResolver } from "@harness-engineering/core";
|
|
637
658
|
function createPerfCommand() {
|
|
638
659
|
const perf = new Command5("perf").description("Performance benchmark and baseline management");
|
|
639
660
|
perf.command("bench [glob]").description("Run benchmarks via vitest bench").action(async (glob2, _opts, cmd) => {
|
|
640
661
|
const globalOpts = cmd.optsWithGlobals();
|
|
641
662
|
const cwd = process.cwd();
|
|
642
|
-
const { BenchmarkRunner } = await import("
|
|
663
|
+
const { BenchmarkRunner } = await import("./dist-RBZXXJHG.js");
|
|
643
664
|
const runner = new BenchmarkRunner();
|
|
644
665
|
const benchFiles = runner.discover(cwd, glob2);
|
|
645
666
|
if (benchFiles.length === 0) {
|
|
@@ -708,7 +729,7 @@ Results (${result.results.length} benchmarks):`);
|
|
|
708
729
|
baselines.command("update").description("Update baselines from latest benchmark run").action(async (_opts, cmd) => {
|
|
709
730
|
const globalOpts = cmd.optsWithGlobals();
|
|
710
731
|
const cwd = process.cwd();
|
|
711
|
-
const { BenchmarkRunner } = await import("
|
|
732
|
+
const { BenchmarkRunner } = await import("./dist-RBZXXJHG.js");
|
|
712
733
|
const runner = new BenchmarkRunner();
|
|
713
734
|
const manager = new BaselineManager(cwd);
|
|
714
735
|
logger.info("Running benchmarks to update baselines...");
|
|
@@ -736,8 +757,8 @@ Results (${result.results.length} benchmarks):`);
|
|
|
736
757
|
perf.command("report").description("Full performance report with metrics, trends, and hotspots").action(async (_opts, cmd) => {
|
|
737
758
|
const globalOpts = cmd.optsWithGlobals();
|
|
738
759
|
const cwd = process.cwd();
|
|
739
|
-
const { EntropyAnalyzer:
|
|
740
|
-
const analyzer = new
|
|
760
|
+
const { EntropyAnalyzer: EntropyAnalyzer2 } = await import("./dist-RBZXXJHG.js");
|
|
761
|
+
const analyzer = new EntropyAnalyzer2({
|
|
741
762
|
rootDir: path6.resolve(cwd),
|
|
742
763
|
analyze: { complexity: true, coupling: true }
|
|
743
764
|
});
|
|
@@ -797,8 +818,6 @@ Results (${result.results.length} benchmarks):`);
|
|
|
797
818
|
// src/commands/check-docs.ts
|
|
798
819
|
import { Command as Command6 } from "commander";
|
|
799
820
|
import * as path7 from "path";
|
|
800
|
-
import { Ok as Ok6, Err as Err2 } from "@harness-engineering/core";
|
|
801
|
-
import { checkDocCoverage, validateKnowledgeMap as validateKnowledgeMap2 } from "@harness-engineering/core";
|
|
802
821
|
async function runCheckDocs(options) {
|
|
803
822
|
const cwd = options.cwd ?? process.cwd();
|
|
804
823
|
const minCoverage = options.minCoverage ?? 80;
|
|
@@ -815,14 +834,14 @@ async function runCheckDocs(options) {
|
|
|
815
834
|
excludePatterns: ["**/*.test.ts", "**/*.spec.ts", "**/node_modules/**"]
|
|
816
835
|
});
|
|
817
836
|
if (!coverageResult.ok) {
|
|
818
|
-
return
|
|
837
|
+
return Err(
|
|
819
838
|
new CLIError(
|
|
820
839
|
`Documentation coverage check failed: ${coverageResult.error.message}`,
|
|
821
840
|
ExitCode.ERROR
|
|
822
841
|
)
|
|
823
842
|
);
|
|
824
843
|
}
|
|
825
|
-
const knowledgeResult = await
|
|
844
|
+
const knowledgeResult = await validateKnowledgeMap(cwd);
|
|
826
845
|
let brokenLinks = [];
|
|
827
846
|
if (knowledgeResult.ok) {
|
|
828
847
|
brokenLinks = knowledgeResult.value.brokenLinks.map((b) => b.path);
|
|
@@ -837,7 +856,7 @@ async function runCheckDocs(options) {
|
|
|
837
856
|
undocumented: coverageResult.value.undocumented,
|
|
838
857
|
brokenLinks
|
|
839
858
|
};
|
|
840
|
-
return
|
|
859
|
+
return Ok(result);
|
|
841
860
|
}
|
|
842
861
|
function createCheckDocsCommand() {
|
|
843
862
|
const command = new Command6("check-docs").description("Check documentation coverage").option("--min-coverage <percent>", "Minimum coverage percentage", "80").action(async (opts, cmd) => {
|
|
@@ -896,13 +915,11 @@ import { Command as Command8 } from "commander";
|
|
|
896
915
|
import chalk3 from "chalk";
|
|
897
916
|
import * as fs5 from "fs";
|
|
898
917
|
import * as path11 from "path";
|
|
899
|
-
import { Ok as Ok8, Err as Err4 } from "@harness-engineering/core";
|
|
900
918
|
|
|
901
919
|
// src/templates/engine.ts
|
|
902
920
|
import * as fs2 from "fs";
|
|
903
921
|
import * as path8 from "path";
|
|
904
922
|
import Handlebars from "handlebars";
|
|
905
|
-
import { Ok as Ok7, Err as Err3 } from "@harness-engineering/core";
|
|
906
923
|
|
|
907
924
|
// src/templates/schema.ts
|
|
908
925
|
import { z as z2 } from "zod";
|
|
@@ -976,9 +993,9 @@ var TemplateEngine = class {
|
|
|
976
993
|
const parsed = TemplateMetadataSchema.safeParse(raw);
|
|
977
994
|
if (parsed.success) templates.push(parsed.data);
|
|
978
995
|
}
|
|
979
|
-
return
|
|
996
|
+
return Ok(templates);
|
|
980
997
|
} catch (error) {
|
|
981
|
-
return
|
|
998
|
+
return Err(
|
|
982
999
|
new Error(
|
|
983
1000
|
`Failed to list templates: ${error instanceof Error ? error.message : String(error)}`
|
|
984
1001
|
)
|
|
@@ -987,12 +1004,12 @@ var TemplateEngine = class {
|
|
|
987
1004
|
}
|
|
988
1005
|
resolveTemplate(level, framework) {
|
|
989
1006
|
const levelDir = this.findTemplateDir(level, "level");
|
|
990
|
-
if (!levelDir) return
|
|
1007
|
+
if (!levelDir) return Err(new Error(`Template not found for level: ${level}`));
|
|
991
1008
|
const metaPath = path8.join(levelDir, "template.json");
|
|
992
1009
|
const metaRaw = JSON.parse(fs2.readFileSync(metaPath, "utf-8"));
|
|
993
1010
|
const metaResult = TemplateMetadataSchema.safeParse(metaRaw);
|
|
994
1011
|
if (!metaResult.success)
|
|
995
|
-
return
|
|
1012
|
+
return Err(new Error(`Invalid template.json in ${level}: ${metaResult.error.message}`));
|
|
996
1013
|
const metadata = metaResult.data;
|
|
997
1014
|
let files = [];
|
|
998
1015
|
if (metadata.extends) {
|
|
@@ -1004,7 +1021,7 @@ var TemplateEngine = class {
|
|
|
1004
1021
|
let overlayMetadata;
|
|
1005
1022
|
if (framework) {
|
|
1006
1023
|
const frameworkDir = this.findTemplateDir(framework, "framework");
|
|
1007
|
-
if (!frameworkDir) return
|
|
1024
|
+
if (!frameworkDir) return Err(new Error(`Framework template not found: ${framework}`));
|
|
1008
1025
|
const fMetaPath = path8.join(frameworkDir, "template.json");
|
|
1009
1026
|
const fMetaRaw = JSON.parse(fs2.readFileSync(fMetaPath, "utf-8"));
|
|
1010
1027
|
const fMetaResult = TemplateMetadataSchema.safeParse(fMetaRaw);
|
|
@@ -1015,7 +1032,7 @@ var TemplateEngine = class {
|
|
|
1015
1032
|
files = files.filter((f) => f.relativePath !== "template.json");
|
|
1016
1033
|
const resolved = { metadata, files };
|
|
1017
1034
|
if (overlayMetadata !== void 0) resolved.overlayMetadata = overlayMetadata;
|
|
1018
|
-
return
|
|
1035
|
+
return Ok(resolved);
|
|
1019
1036
|
}
|
|
1020
1037
|
render(template, context) {
|
|
1021
1038
|
const rendered = [];
|
|
@@ -1035,7 +1052,7 @@ var TemplateEngine = class {
|
|
|
1035
1052
|
}
|
|
1036
1053
|
} catch (error) {
|
|
1037
1054
|
const msg = error instanceof Error ? error.message : String(error);
|
|
1038
|
-
return
|
|
1055
|
+
return Err(
|
|
1039
1056
|
new Error(
|
|
1040
1057
|
`Template render failed in ${file.sourceTemplate}/${file.relativePath}: ${msg}`
|
|
1041
1058
|
)
|
|
@@ -1047,7 +1064,7 @@ var TemplateEngine = class {
|
|
|
1047
1064
|
rendered.push({ relativePath: file.relativePath, content });
|
|
1048
1065
|
} catch (error) {
|
|
1049
1066
|
const msg = error instanceof Error ? error.message : String(error);
|
|
1050
|
-
return
|
|
1067
|
+
return Err(
|
|
1051
1068
|
new Error(
|
|
1052
1069
|
`Template render failed in ${file.sourceTemplate}/${file.relativePath}: ${msg}`
|
|
1053
1070
|
)
|
|
@@ -1065,9 +1082,9 @@ var TemplateEngine = class {
|
|
|
1065
1082
|
}
|
|
1066
1083
|
} catch (error) {
|
|
1067
1084
|
const msg = error instanceof Error ? error.message : String(error);
|
|
1068
|
-
return
|
|
1085
|
+
return Err(new Error(`JSON merge failed: ${msg}`));
|
|
1069
1086
|
}
|
|
1070
|
-
return
|
|
1087
|
+
return Ok({ files: rendered });
|
|
1071
1088
|
}
|
|
1072
1089
|
write(files, targetDir, options) {
|
|
1073
1090
|
try {
|
|
@@ -1080,9 +1097,9 @@ var TemplateEngine = class {
|
|
|
1080
1097
|
fs2.writeFileSync(targetPath, file.content);
|
|
1081
1098
|
written.push(file.relativePath);
|
|
1082
1099
|
}
|
|
1083
|
-
return
|
|
1100
|
+
return Ok(written);
|
|
1084
1101
|
} catch (error) {
|
|
1085
|
-
return
|
|
1102
|
+
return Err(
|
|
1086
1103
|
new Error(
|
|
1087
1104
|
`Failed to write files: ${error instanceof Error ? error.message : String(error)}`
|
|
1088
1105
|
)
|
|
@@ -1326,7 +1343,7 @@ async function runInit(options) {
|
|
|
1326
1343
|
const force = options.force ?? false;
|
|
1327
1344
|
const configPath = path11.join(cwd, "harness.config.json");
|
|
1328
1345
|
if (!force && fs5.existsSync(configPath)) {
|
|
1329
|
-
return
|
|
1346
|
+
return Err(
|
|
1330
1347
|
new CLIError("Project already initialized. Use --force to overwrite.", ExitCode.ERROR)
|
|
1331
1348
|
);
|
|
1332
1349
|
}
|
|
@@ -1334,7 +1351,7 @@ async function runInit(options) {
|
|
|
1334
1351
|
const engine = new TemplateEngine(templatesDir);
|
|
1335
1352
|
const resolveResult = engine.resolveTemplate(level, options.framework);
|
|
1336
1353
|
if (!resolveResult.ok) {
|
|
1337
|
-
return
|
|
1354
|
+
return Err(new CLIError(resolveResult.error.message, ExitCode.ERROR));
|
|
1338
1355
|
}
|
|
1339
1356
|
const renderResult = engine.render(resolveResult.value, {
|
|
1340
1357
|
projectName: name,
|
|
@@ -1342,13 +1359,13 @@ async function runInit(options) {
|
|
|
1342
1359
|
...options.framework !== void 0 && { framework: options.framework }
|
|
1343
1360
|
});
|
|
1344
1361
|
if (!renderResult.ok) {
|
|
1345
|
-
return
|
|
1362
|
+
return Err(new CLIError(renderResult.error.message, ExitCode.ERROR));
|
|
1346
1363
|
}
|
|
1347
1364
|
const writeResult = engine.write(renderResult.value, cwd, { overwrite: force });
|
|
1348
1365
|
if (!writeResult.ok) {
|
|
1349
|
-
return
|
|
1366
|
+
return Err(new CLIError(writeResult.error.message, ExitCode.ERROR));
|
|
1350
1367
|
}
|
|
1351
|
-
return
|
|
1368
|
+
return Ok({ filesCreated: writeResult.value });
|
|
1352
1369
|
}
|
|
1353
1370
|
function createInitCommand() {
|
|
1354
1371
|
const command = new Command8("init").description("Initialize a new harness-engineering project").option("-n, --name <name>", "Project name").option("-l, --level <level>", "Adoption level (basic, intermediate, advanced)", "basic").option("--framework <framework>", "Framework overlay (nextjs)").option("-f, --force", "Overwrite existing files").option("-y, --yes", "Use defaults without prompting").action(async (opts, cmd) => {
|
|
@@ -1395,13 +1412,12 @@ function createInitCommand() {
|
|
|
1395
1412
|
// src/commands/cleanup.ts
|
|
1396
1413
|
import { Command as Command9 } from "commander";
|
|
1397
1414
|
import * as path12 from "path";
|
|
1398
|
-
import { Ok as Ok9, Err as Err5, EntropyAnalyzer as EntropyAnalyzer2 } from "@harness-engineering/core";
|
|
1399
1415
|
async function runCleanup(options) {
|
|
1400
1416
|
const cwd = options.cwd ?? process.cwd();
|
|
1401
1417
|
const type = options.type ?? "all";
|
|
1402
1418
|
const configResult = resolveConfig(options.configPath);
|
|
1403
1419
|
if (!configResult.ok) {
|
|
1404
|
-
return
|
|
1420
|
+
return Err(configResult.error);
|
|
1405
1421
|
}
|
|
1406
1422
|
const config = configResult.value;
|
|
1407
1423
|
const result = {
|
|
@@ -1423,10 +1439,10 @@ async function runCleanup(options) {
|
|
|
1423
1439
|
},
|
|
1424
1440
|
exclude: config.entropy?.excludePatterns ?? ["**/node_modules/**", "**/*.test.ts"]
|
|
1425
1441
|
};
|
|
1426
|
-
const analyzer = new
|
|
1442
|
+
const analyzer = new EntropyAnalyzer(entropyConfig);
|
|
1427
1443
|
const analysisResult = await analyzer.analyze();
|
|
1428
1444
|
if (!analysisResult.ok) {
|
|
1429
|
-
return
|
|
1445
|
+
return Err(
|
|
1430
1446
|
new CLIError(`Entropy analysis failed: ${analysisResult.error.message}`, ExitCode.ERROR)
|
|
1431
1447
|
);
|
|
1432
1448
|
}
|
|
@@ -1451,7 +1467,7 @@ async function runCleanup(options) {
|
|
|
1451
1467
|
}));
|
|
1452
1468
|
}
|
|
1453
1469
|
result.totalIssues = result.driftIssues.length + result.deadCode.length + result.patternViolations.length;
|
|
1454
|
-
return
|
|
1470
|
+
return Ok(result);
|
|
1455
1471
|
}
|
|
1456
1472
|
function createCleanupCommand() {
|
|
1457
1473
|
const command = new Command9("cleanup").description("Detect entropy issues (doc drift, dead code, patterns)").option("-t, --type <type>", "Issue type: drift, dead-code, patterns, all", "all").action(async (opts, cmd) => {
|
|
@@ -1516,22 +1532,12 @@ function createCleanupCommand() {
|
|
|
1516
1532
|
// src/commands/fix-drift.ts
|
|
1517
1533
|
import { Command as Command10 } from "commander";
|
|
1518
1534
|
import * as path13 from "path";
|
|
1519
|
-
import {
|
|
1520
|
-
Ok as Ok10,
|
|
1521
|
-
Err as Err6,
|
|
1522
|
-
buildSnapshot,
|
|
1523
|
-
detectDocDrift,
|
|
1524
|
-
detectDeadCode,
|
|
1525
|
-
createFixes,
|
|
1526
|
-
applyFixes,
|
|
1527
|
-
generateSuggestions
|
|
1528
|
-
} from "@harness-engineering/core";
|
|
1529
1535
|
async function runFixDrift(options) {
|
|
1530
1536
|
const cwd = options.cwd ?? process.cwd();
|
|
1531
1537
|
const dryRun = options.dryRun !== false;
|
|
1532
1538
|
const configResult = resolveConfig(options.configPath);
|
|
1533
1539
|
if (!configResult.ok) {
|
|
1534
|
-
return
|
|
1540
|
+
return Err(configResult.error);
|
|
1535
1541
|
}
|
|
1536
1542
|
const config = configResult.value;
|
|
1537
1543
|
const rootDir = path13.resolve(cwd, config.rootDir);
|
|
@@ -1549,21 +1555,21 @@ async function runFixDrift(options) {
|
|
|
1549
1555
|
};
|
|
1550
1556
|
const snapshotResult = await buildSnapshot(entropyConfig);
|
|
1551
1557
|
if (!snapshotResult.ok) {
|
|
1552
|
-
return
|
|
1558
|
+
return Err(
|
|
1553
1559
|
new CLIError(`Failed to build snapshot: ${snapshotResult.error.message}`, ExitCode.ERROR)
|
|
1554
1560
|
);
|
|
1555
1561
|
}
|
|
1556
1562
|
const snapshot = snapshotResult.value;
|
|
1557
1563
|
const driftResult = await detectDocDrift(snapshot);
|
|
1558
1564
|
if (!driftResult.ok) {
|
|
1559
|
-
return
|
|
1565
|
+
return Err(
|
|
1560
1566
|
new CLIError(`Failed to detect drift: ${driftResult.error.message}`, ExitCode.ERROR)
|
|
1561
1567
|
);
|
|
1562
1568
|
}
|
|
1563
1569
|
const driftReport = driftResult.value;
|
|
1564
1570
|
const deadCodeResult = await detectDeadCode(snapshot);
|
|
1565
1571
|
if (!deadCodeResult.ok) {
|
|
1566
|
-
return
|
|
1572
|
+
return Err(
|
|
1567
1573
|
new CLIError(`Failed to detect dead code: ${deadCodeResult.error.message}`, ExitCode.ERROR)
|
|
1568
1574
|
);
|
|
1569
1575
|
}
|
|
@@ -1573,7 +1579,7 @@ async function runFixDrift(options) {
|
|
|
1573
1579
|
if (!dryRun && fixes.length > 0) {
|
|
1574
1580
|
const applyResult = await applyFixes(fixes, { dryRun: false });
|
|
1575
1581
|
if (!applyResult.ok) {
|
|
1576
|
-
return
|
|
1582
|
+
return Err(
|
|
1577
1583
|
new CLIError(`Failed to apply fixes: ${applyResult.error.message}`, ExitCode.ERROR)
|
|
1578
1584
|
);
|
|
1579
1585
|
}
|
|
@@ -1622,7 +1628,7 @@ async function runFixDrift(options) {
|
|
|
1622
1628
|
fixes: appliedFixes,
|
|
1623
1629
|
suggestions
|
|
1624
1630
|
};
|
|
1625
|
-
return
|
|
1631
|
+
return Ok(result);
|
|
1626
1632
|
}
|
|
1627
1633
|
function createFixDriftCommand() {
|
|
1628
1634
|
const command = new Command10("fix-drift").description("Auto-fix entropy issues (doc drift, dead code)").option("--no-dry-run", "Actually apply fixes (default is dry-run mode)").action(async (opts, cmd) => {
|
|
@@ -1691,14 +1697,11 @@ import { Command as Command13 } from "commander";
|
|
|
1691
1697
|
import { Command as Command11 } from "commander";
|
|
1692
1698
|
import * as path17 from "path";
|
|
1693
1699
|
import * as childProcess from "child_process";
|
|
1694
|
-
import { Ok as Ok12, Err as Err8 } from "@harness-engineering/core";
|
|
1695
|
-
import { requestPeerReview } from "@harness-engineering/core";
|
|
1696
1700
|
|
|
1697
1701
|
// src/persona/loader.ts
|
|
1698
1702
|
import * as fs6 from "fs";
|
|
1699
1703
|
import * as path14 from "path";
|
|
1700
1704
|
import YAML from "yaml";
|
|
1701
|
-
import { Ok as Ok11, Err as Err7 } from "@harness-engineering/core";
|
|
1702
1705
|
|
|
1703
1706
|
// src/persona/schema.ts
|
|
1704
1707
|
import { z as z3 } from "zod";
|
|
@@ -1784,24 +1787,24 @@ function normalizePersona(raw) {
|
|
|
1784
1787
|
function loadPersona(filePath) {
|
|
1785
1788
|
try {
|
|
1786
1789
|
if (!fs6.existsSync(filePath)) {
|
|
1787
|
-
return
|
|
1790
|
+
return Err(new Error(`Persona file not found: ${filePath}`));
|
|
1788
1791
|
}
|
|
1789
1792
|
const raw = fs6.readFileSync(filePath, "utf-8");
|
|
1790
1793
|
const parsed = YAML.parse(raw);
|
|
1791
1794
|
const result = PersonaSchema.safeParse(parsed);
|
|
1792
1795
|
if (!result.success) {
|
|
1793
|
-
return
|
|
1796
|
+
return Err(new Error(`Invalid persona ${filePath}: ${result.error.message}`));
|
|
1794
1797
|
}
|
|
1795
|
-
return
|
|
1798
|
+
return Ok(normalizePersona(result.data));
|
|
1796
1799
|
} catch (error) {
|
|
1797
|
-
return
|
|
1800
|
+
return Err(
|
|
1798
1801
|
new Error(`Failed to load persona: ${error instanceof Error ? error.message : String(error)}`)
|
|
1799
1802
|
);
|
|
1800
1803
|
}
|
|
1801
1804
|
}
|
|
1802
1805
|
function listPersonas(dir) {
|
|
1803
1806
|
try {
|
|
1804
|
-
if (!fs6.existsSync(dir)) return
|
|
1807
|
+
if (!fs6.existsSync(dir)) return Ok([]);
|
|
1805
1808
|
const entries = fs6.readdirSync(dir).filter((f) => f.endsWith(".yaml") || f.endsWith(".yml"));
|
|
1806
1809
|
const personas = [];
|
|
1807
1810
|
for (const entry of entries) {
|
|
@@ -1811,9 +1814,9 @@ function listPersonas(dir) {
|
|
|
1811
1814
|
personas.push({ name: result.value.name, description: result.value.description, filePath });
|
|
1812
1815
|
}
|
|
1813
1816
|
}
|
|
1814
|
-
return
|
|
1817
|
+
return Ok(personas);
|
|
1815
1818
|
} catch (error) {
|
|
1816
|
-
return
|
|
1819
|
+
return Err(
|
|
1817
1820
|
new Error(
|
|
1818
1821
|
`Failed to list personas: ${error instanceof Error ? error.message : String(error)}`
|
|
1819
1822
|
)
|
|
@@ -1904,8 +1907,8 @@ async function runPersona(persona, context) {
|
|
|
1904
1907
|
const result = await Promise.race([
|
|
1905
1908
|
context.commandExecutor(step.command),
|
|
1906
1909
|
new Promise(
|
|
1907
|
-
(
|
|
1908
|
-
() =>
|
|
1910
|
+
(resolve24) => setTimeout(
|
|
1911
|
+
() => resolve24({
|
|
1909
1912
|
ok: false,
|
|
1910
1913
|
error: new Error(TIMEOUT_ERROR_MESSAGE)
|
|
1911
1914
|
}),
|
|
@@ -1960,7 +1963,7 @@ async function runPersona(persona, context) {
|
|
|
1960
1963
|
const result = await Promise.race([
|
|
1961
1964
|
context.skillExecutor(step.skill, skillContext),
|
|
1962
1965
|
new Promise(
|
|
1963
|
-
(
|
|
1966
|
+
(resolve24) => setTimeout(() => resolve24(SKILL_TIMEOUT_RESULT), remainingTime)
|
|
1964
1967
|
)
|
|
1965
1968
|
]);
|
|
1966
1969
|
const durationMs = Date.now() - stepStart;
|
|
@@ -2142,7 +2145,7 @@ async function runAgentTask(task, options) {
|
|
|
2142
2145
|
};
|
|
2143
2146
|
const agentType = agentTypeMap[task];
|
|
2144
2147
|
if (!agentType) {
|
|
2145
|
-
return
|
|
2148
|
+
return Err(
|
|
2146
2149
|
new CLIError(
|
|
2147
2150
|
`Unknown task: ${task}. Available: ${Object.keys(agentTypeMap).join(", ")}`,
|
|
2148
2151
|
ExitCode.ERROR
|
|
@@ -2162,10 +2165,10 @@ async function runAgentTask(task, options) {
|
|
|
2162
2165
|
{ timeout }
|
|
2163
2166
|
);
|
|
2164
2167
|
if (!reviewResult.ok) {
|
|
2165
|
-
return
|
|
2168
|
+
return Err(new CLIError(`Agent task failed: ${reviewResult.error.message}`, ExitCode.ERROR));
|
|
2166
2169
|
}
|
|
2167
2170
|
const review = reviewResult.value;
|
|
2168
|
-
return
|
|
2171
|
+
return Ok({
|
|
2169
2172
|
success: review.approved,
|
|
2170
2173
|
output: review.approved ? `Agent task '${task}' completed successfully` : `Agent task '${task}' found issues:
|
|
2171
2174
|
${review.comments.map((c) => ` - ${c.message}`).join("\n")}`
|
|
@@ -2197,13 +2200,13 @@ function createRunCommand() {
|
|
|
2197
2200
|
const trigger = opts.trigger === "auto" ? "auto" : VALID_TRIGGERS.has(opts.trigger) ? opts.trigger : "manual";
|
|
2198
2201
|
const commandExecutor = async (command) => {
|
|
2199
2202
|
if (!ALLOWED_PERSONA_COMMANDS.has(command)) {
|
|
2200
|
-
return
|
|
2203
|
+
return Err(new Error(`Unknown harness command: ${command}`));
|
|
2201
2204
|
}
|
|
2202
2205
|
try {
|
|
2203
2206
|
childProcess.execFileSync("npx", ["harness", command], { stdio: "inherit" });
|
|
2204
|
-
return
|
|
2207
|
+
return Ok(null);
|
|
2205
2208
|
} catch (error) {
|
|
2206
|
-
return
|
|
2209
|
+
return Err(new Error(error instanceof Error ? error.message : String(error)));
|
|
2207
2210
|
}
|
|
2208
2211
|
};
|
|
2209
2212
|
const report = await runPersona(persona, {
|
|
@@ -2245,8 +2248,6 @@ function createRunCommand() {
|
|
|
2245
2248
|
// src/commands/agent/review.ts
|
|
2246
2249
|
import { Command as Command12 } from "commander";
|
|
2247
2250
|
import { execSync as execSync2 } from "child_process";
|
|
2248
|
-
import { Ok as Ok13, Err as Err9 } from "@harness-engineering/core";
|
|
2249
|
-
import { createSelfReview, parseDiff } from "@harness-engineering/core";
|
|
2250
2251
|
async function runAgentReview(options) {
|
|
2251
2252
|
const configResult = resolveConfig(options.configPath);
|
|
2252
2253
|
if (!configResult.ok) {
|
|
@@ -2260,17 +2261,17 @@ async function runAgentReview(options) {
|
|
|
2260
2261
|
diff = execSync2("git diff", { encoding: "utf-8" });
|
|
2261
2262
|
}
|
|
2262
2263
|
} catch {
|
|
2263
|
-
return
|
|
2264
|
+
return Err(new CLIError("Failed to get git diff", ExitCode.ERROR));
|
|
2264
2265
|
}
|
|
2265
2266
|
if (!diff) {
|
|
2266
|
-
return
|
|
2267
|
+
return Ok({
|
|
2267
2268
|
passed: true,
|
|
2268
2269
|
checklist: [{ check: "No changes to review", passed: true }]
|
|
2269
2270
|
});
|
|
2270
2271
|
}
|
|
2271
2272
|
const parsedDiffResult = parseDiff(diff);
|
|
2272
2273
|
if (!parsedDiffResult.ok) {
|
|
2273
|
-
return
|
|
2274
|
+
return Err(new CLIError(parsedDiffResult.error.message, ExitCode.ERROR));
|
|
2274
2275
|
}
|
|
2275
2276
|
const codeChanges = parsedDiffResult.value;
|
|
2276
2277
|
const review = await createSelfReview(codeChanges, {
|
|
@@ -2281,9 +2282,9 @@ async function runAgentReview(options) {
|
|
|
2281
2282
|
}
|
|
2282
2283
|
});
|
|
2283
2284
|
if (!review.ok) {
|
|
2284
|
-
return
|
|
2285
|
+
return Err(new CLIError(review.error.message, ExitCode.ERROR));
|
|
2285
2286
|
}
|
|
2286
|
-
return
|
|
2287
|
+
return Ok({
|
|
2287
2288
|
passed: review.value.passed,
|
|
2288
2289
|
checklist: review.value.items.map((item) => ({
|
|
2289
2290
|
check: item.check,
|
|
@@ -2339,7 +2340,6 @@ function createAgentCommand() {
|
|
|
2339
2340
|
import { Command as Command14 } from "commander";
|
|
2340
2341
|
import * as fs9 from "fs";
|
|
2341
2342
|
import * as path18 from "path";
|
|
2342
|
-
import { Ok as Ok14, Err as Err10 } from "@harness-engineering/core";
|
|
2343
2343
|
var LAYER_INDEX_TEMPLATE = (name) => `// ${name} layer
|
|
2344
2344
|
// Add your ${name} exports here
|
|
2345
2345
|
|
|
@@ -2369,7 +2369,7 @@ async function runAdd(componentType, name, options) {
|
|
|
2369
2369
|
const cwd = options.cwd ?? process.cwd();
|
|
2370
2370
|
const NAME_PATTERN = /^[a-zA-Z][a-zA-Z0-9_-]*$/;
|
|
2371
2371
|
if (!name || !NAME_PATTERN.test(name)) {
|
|
2372
|
-
return
|
|
2372
|
+
return Err(
|
|
2373
2373
|
new CLIError(
|
|
2374
2374
|
"Invalid name. Must start with a letter and contain only alphanumeric characters, hyphens, and underscores.",
|
|
2375
2375
|
ExitCode.ERROR
|
|
@@ -2398,7 +2398,7 @@ async function runAdd(componentType, name, options) {
|
|
|
2398
2398
|
case "module": {
|
|
2399
2399
|
const modulePath = path18.join(cwd, "src", `${name}.ts`);
|
|
2400
2400
|
if (fs9.existsSync(modulePath)) {
|
|
2401
|
-
return
|
|
2401
|
+
return Err(new CLIError(`Module ${name} already exists`, ExitCode.ERROR));
|
|
2402
2402
|
}
|
|
2403
2403
|
fs9.writeFileSync(modulePath, MODULE_TEMPLATE(name));
|
|
2404
2404
|
created.push(`src/${name}.ts`);
|
|
@@ -2411,7 +2411,7 @@ async function runAdd(componentType, name, options) {
|
|
|
2411
2411
|
}
|
|
2412
2412
|
const docPath = path18.join(docsDir, `${name}.md`);
|
|
2413
2413
|
if (fs9.existsSync(docPath)) {
|
|
2414
|
-
return
|
|
2414
|
+
return Err(new CLIError(`Doc ${name} already exists`, ExitCode.ERROR));
|
|
2415
2415
|
}
|
|
2416
2416
|
fs9.writeFileSync(docPath, DOC_TEMPLATE(name));
|
|
2417
2417
|
created.push(`docs/${name}.md`);
|
|
@@ -2435,7 +2435,7 @@ async function runAdd(componentType, name, options) {
|
|
|
2435
2435
|
}
|
|
2436
2436
|
const personaPath = path18.join(personasDir, `${name}.yaml`);
|
|
2437
2437
|
if (fs9.existsSync(personaPath)) {
|
|
2438
|
-
return
|
|
2438
|
+
return Err(new CLIError(`Persona ${name} already exists`, ExitCode.ERROR));
|
|
2439
2439
|
}
|
|
2440
2440
|
fs9.writeFileSync(
|
|
2441
2441
|
personaPath,
|
|
@@ -2451,7 +2451,7 @@ focus_areas: []
|
|
|
2451
2451
|
}
|
|
2452
2452
|
default: {
|
|
2453
2453
|
const _exhaustive = componentType;
|
|
2454
|
-
return
|
|
2454
|
+
return Err(
|
|
2455
2455
|
new CLIError(
|
|
2456
2456
|
`Unknown component type: ${String(_exhaustive)}. Use: layer, module, doc, skill, persona`,
|
|
2457
2457
|
ExitCode.ERROR
|
|
@@ -2459,9 +2459,9 @@ focus_areas: []
|
|
|
2459
2459
|
);
|
|
2460
2460
|
}
|
|
2461
2461
|
}
|
|
2462
|
-
return
|
|
2462
|
+
return Ok({ created });
|
|
2463
2463
|
} catch (error) {
|
|
2464
|
-
return
|
|
2464
|
+
return Err(
|
|
2465
2465
|
new CLIError(
|
|
2466
2466
|
`Failed to add ${componentType}: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
2467
2467
|
ExitCode.ERROR
|
|
@@ -2495,7 +2495,346 @@ import { Command as Command17 } from "commander";
|
|
|
2495
2495
|
|
|
2496
2496
|
// src/commands/linter/generate.ts
|
|
2497
2497
|
import { Command as Command15 } from "commander";
|
|
2498
|
-
|
|
2498
|
+
|
|
2499
|
+
// ../linter-gen/dist/generator/orchestrator.js
|
|
2500
|
+
import * as fs12 from "fs/promises";
|
|
2501
|
+
import * as path21 from "path";
|
|
2502
|
+
|
|
2503
|
+
// ../linter-gen/dist/parser/config-parser.js
|
|
2504
|
+
import * as fs10 from "fs/promises";
|
|
2505
|
+
import * as yaml from "yaml";
|
|
2506
|
+
|
|
2507
|
+
// ../linter-gen/dist/schema/linter-config.js
|
|
2508
|
+
import { z as z4 } from "zod";
|
|
2509
|
+
var RuleConfigSchema = z4.object({
|
|
2510
|
+
/** Rule name in kebab-case (e.g., 'no-ui-in-services') */
|
|
2511
|
+
name: z4.string().regex(/^[a-z][a-z0-9]*(-[a-z0-9]+)*$/, "Rule name must be kebab-case"),
|
|
2512
|
+
/** Rule type - determines which template to use */
|
|
2513
|
+
type: z4.string().min(1),
|
|
2514
|
+
/** ESLint severity level */
|
|
2515
|
+
severity: z4.enum(["error", "warn", "off"]).default("error"),
|
|
2516
|
+
/** Template-specific configuration */
|
|
2517
|
+
config: z4.record(z4.unknown())
|
|
2518
|
+
});
|
|
2519
|
+
var LinterConfigSchema = z4.object({
|
|
2520
|
+
/** Config version - currently only 1 is supported */
|
|
2521
|
+
version: z4.literal(1),
|
|
2522
|
+
/** Output directory for generated rules */
|
|
2523
|
+
output: z4.string().min(1),
|
|
2524
|
+
/** Optional explicit template path mappings (type → path) */
|
|
2525
|
+
templates: z4.record(z4.string()).optional(),
|
|
2526
|
+
/** Rules to generate */
|
|
2527
|
+
rules: z4.array(RuleConfigSchema).min(1, "At least one rule is required")
|
|
2528
|
+
});
|
|
2529
|
+
|
|
2530
|
+
// ../linter-gen/dist/parser/config-parser.js
|
|
2531
|
+
var ParseError = class extends Error {
|
|
2532
|
+
code;
|
|
2533
|
+
cause;
|
|
2534
|
+
constructor(message, code, cause) {
|
|
2535
|
+
super(message);
|
|
2536
|
+
this.code = code;
|
|
2537
|
+
this.cause = cause;
|
|
2538
|
+
this.name = "ParseError";
|
|
2539
|
+
}
|
|
2540
|
+
};
|
|
2541
|
+
async function parseConfig(configPath) {
|
|
2542
|
+
let content;
|
|
2543
|
+
try {
|
|
2544
|
+
content = await fs10.readFile(configPath, "utf-8");
|
|
2545
|
+
} catch (err) {
|
|
2546
|
+
if (err.code === "ENOENT") {
|
|
2547
|
+
return {
|
|
2548
|
+
success: false,
|
|
2549
|
+
error: new ParseError(`Config file not found: ${configPath}`, "FILE_NOT_FOUND", err)
|
|
2550
|
+
};
|
|
2551
|
+
}
|
|
2552
|
+
return {
|
|
2553
|
+
success: false,
|
|
2554
|
+
error: new ParseError(`Failed to read config file: ${configPath}`, "FILE_READ_ERROR", err)
|
|
2555
|
+
};
|
|
2556
|
+
}
|
|
2557
|
+
let parsed;
|
|
2558
|
+
try {
|
|
2559
|
+
parsed = yaml.parse(content);
|
|
2560
|
+
} catch (err) {
|
|
2561
|
+
return {
|
|
2562
|
+
success: false,
|
|
2563
|
+
error: new ParseError(`Invalid YAML syntax in ${configPath}: ${err.message}`, "YAML_PARSE_ERROR", err)
|
|
2564
|
+
};
|
|
2565
|
+
}
|
|
2566
|
+
const result = LinterConfigSchema.safeParse(parsed);
|
|
2567
|
+
if (!result.success) {
|
|
2568
|
+
const issues = result.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
|
|
2569
|
+
return {
|
|
2570
|
+
success: false,
|
|
2571
|
+
error: new ParseError(`Invalid config: ${issues}`, "VALIDATION_ERROR", result.error)
|
|
2572
|
+
};
|
|
2573
|
+
}
|
|
2574
|
+
return {
|
|
2575
|
+
success: true,
|
|
2576
|
+
data: result.data,
|
|
2577
|
+
configPath
|
|
2578
|
+
};
|
|
2579
|
+
}
|
|
2580
|
+
|
|
2581
|
+
// ../linter-gen/dist/engine/template-loader.js
|
|
2582
|
+
import * as fs11 from "fs/promises";
|
|
2583
|
+
import * as path19 from "path";
|
|
2584
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
2585
|
+
var __filename2 = fileURLToPath2(import.meta.url);
|
|
2586
|
+
var __dirname2 = path19.dirname(__filename2);
|
|
2587
|
+
var TemplateLoadError = class extends Error {
|
|
2588
|
+
code;
|
|
2589
|
+
cause;
|
|
2590
|
+
constructor(message, code, cause) {
|
|
2591
|
+
super(message);
|
|
2592
|
+
this.code = code;
|
|
2593
|
+
this.cause = cause;
|
|
2594
|
+
this.name = "TemplateLoadError";
|
|
2595
|
+
}
|
|
2596
|
+
};
|
|
2597
|
+
var BUILTIN_TEMPLATES = ["import-restriction", "boundary-validation", "dependency-graph"];
|
|
2598
|
+
async function fileExists(filePath) {
|
|
2599
|
+
try {
|
|
2600
|
+
await fs11.access(filePath);
|
|
2601
|
+
return true;
|
|
2602
|
+
} catch {
|
|
2603
|
+
return false;
|
|
2604
|
+
}
|
|
2605
|
+
}
|
|
2606
|
+
async function loadTemplateFile(filePath, type) {
|
|
2607
|
+
try {
|
|
2608
|
+
const content = await fs11.readFile(filePath, "utf-8");
|
|
2609
|
+
return {
|
|
2610
|
+
success: true,
|
|
2611
|
+
source: { type, path: filePath, content }
|
|
2612
|
+
};
|
|
2613
|
+
} catch (err) {
|
|
2614
|
+
return {
|
|
2615
|
+
success: false,
|
|
2616
|
+
error: new TemplateLoadError(`Failed to read template: ${filePath}`, "TEMPLATE_READ_ERROR", err)
|
|
2617
|
+
};
|
|
2618
|
+
}
|
|
2619
|
+
}
|
|
2620
|
+
async function loadTemplate(ruleType, templatesConfig, configDir) {
|
|
2621
|
+
if (templatesConfig?.[ruleType]) {
|
|
2622
|
+
const explicitPath = path19.resolve(configDir, templatesConfig[ruleType]);
|
|
2623
|
+
if (await fileExists(explicitPath)) {
|
|
2624
|
+
return loadTemplateFile(explicitPath, "explicit");
|
|
2625
|
+
}
|
|
2626
|
+
return {
|
|
2627
|
+
success: false,
|
|
2628
|
+
error: new TemplateLoadError(`Explicit template not found: ${explicitPath}`, "TEMPLATE_NOT_FOUND")
|
|
2629
|
+
};
|
|
2630
|
+
}
|
|
2631
|
+
const conventionPath = path19.join(configDir, "templates", `${ruleType}.ts.hbs`);
|
|
2632
|
+
if (await fileExists(conventionPath)) {
|
|
2633
|
+
return loadTemplateFile(conventionPath, "convention");
|
|
2634
|
+
}
|
|
2635
|
+
if (BUILTIN_TEMPLATES.includes(ruleType)) {
|
|
2636
|
+
const builtinPath = path19.join(__dirname2, "..", "templates", `${ruleType}.ts.hbs`);
|
|
2637
|
+
if (await fileExists(builtinPath)) {
|
|
2638
|
+
return loadTemplateFile(builtinPath, "builtin");
|
|
2639
|
+
}
|
|
2640
|
+
}
|
|
2641
|
+
return {
|
|
2642
|
+
success: false,
|
|
2643
|
+
error: new TemplateLoadError(`Template not found for type '${ruleType}'. Checked: explicit config, ./templates/${ruleType}.ts.hbs, built-in templates.`, "TEMPLATE_NOT_FOUND")
|
|
2644
|
+
};
|
|
2645
|
+
}
|
|
2646
|
+
|
|
2647
|
+
// ../linter-gen/dist/generator/rule-generator.js
|
|
2648
|
+
import * as path20 from "path";
|
|
2649
|
+
|
|
2650
|
+
// ../linter-gen/dist/engine/context-builder.js
|
|
2651
|
+
var GENERATOR_VERSION = "0.1.0";
|
|
2652
|
+
function toCamelCase(str) {
|
|
2653
|
+
return str.replace(/-([a-z0-9])/g, (_, char) => char.toUpperCase());
|
|
2654
|
+
}
|
|
2655
|
+
function toPascalCase(str) {
|
|
2656
|
+
const camel = toCamelCase(str);
|
|
2657
|
+
return camel.charAt(0).toUpperCase() + camel.slice(1);
|
|
2658
|
+
}
|
|
2659
|
+
function buildRuleContext(rule, configPath) {
|
|
2660
|
+
return {
|
|
2661
|
+
name: rule.name,
|
|
2662
|
+
nameCamel: toCamelCase(rule.name),
|
|
2663
|
+
namePascal: toPascalCase(rule.name),
|
|
2664
|
+
severity: rule.severity,
|
|
2665
|
+
config: rule.config,
|
|
2666
|
+
meta: {
|
|
2667
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2668
|
+
generatorVersion: GENERATOR_VERSION,
|
|
2669
|
+
configPath
|
|
2670
|
+
}
|
|
2671
|
+
};
|
|
2672
|
+
}
|
|
2673
|
+
|
|
2674
|
+
// ../linter-gen/dist/engine/template-renderer.js
|
|
2675
|
+
import Handlebars2 from "handlebars";
|
|
2676
|
+
var TemplateError = class extends Error {
|
|
2677
|
+
cause;
|
|
2678
|
+
constructor(message, cause) {
|
|
2679
|
+
super(message);
|
|
2680
|
+
this.cause = cause;
|
|
2681
|
+
this.name = "TemplateError";
|
|
2682
|
+
}
|
|
2683
|
+
};
|
|
2684
|
+
function toCamelCase2(str) {
|
|
2685
|
+
return str.replace(/-([a-z0-9])/g, (_, char) => char.toUpperCase());
|
|
2686
|
+
}
|
|
2687
|
+
function toPascalCase2(str) {
|
|
2688
|
+
const camel = toCamelCase2(str);
|
|
2689
|
+
return camel.charAt(0).toUpperCase() + camel.slice(1);
|
|
2690
|
+
}
|
|
2691
|
+
Handlebars2.registerHelper("json", (obj) => JSON.stringify(obj));
|
|
2692
|
+
Handlebars2.registerHelper("jsonPretty", (obj) => JSON.stringify(obj, null, 2));
|
|
2693
|
+
Handlebars2.registerHelper("camelCase", (str) => toCamelCase2(str));
|
|
2694
|
+
Handlebars2.registerHelper("pascalCase", (str) => toPascalCase2(str));
|
|
2695
|
+
function renderTemplate(templateSource, context) {
|
|
2696
|
+
try {
|
|
2697
|
+
const compiled = Handlebars2.compile(templateSource, { strict: true });
|
|
2698
|
+
const output = compiled(context);
|
|
2699
|
+
return { success: true, output };
|
|
2700
|
+
} catch (err) {
|
|
2701
|
+
return {
|
|
2702
|
+
success: false,
|
|
2703
|
+
error: new TemplateError(`Template rendering failed: ${err.message}`, err)
|
|
2704
|
+
};
|
|
2705
|
+
}
|
|
2706
|
+
}
|
|
2707
|
+
|
|
2708
|
+
// ../linter-gen/dist/generator/rule-generator.js
|
|
2709
|
+
function generateRule(rule, template, outputDir, configPath) {
|
|
2710
|
+
const context = buildRuleContext(rule, configPath);
|
|
2711
|
+
const renderResult = renderTemplate(template.content, context);
|
|
2712
|
+
if (!renderResult.success) {
|
|
2713
|
+
return {
|
|
2714
|
+
success: false,
|
|
2715
|
+
error: renderResult.error,
|
|
2716
|
+
ruleName: rule.name
|
|
2717
|
+
};
|
|
2718
|
+
}
|
|
2719
|
+
const outputPath = path20.join(outputDir, `${rule.name}.ts`);
|
|
2720
|
+
return {
|
|
2721
|
+
success: true,
|
|
2722
|
+
rule: {
|
|
2723
|
+
name: rule.name,
|
|
2724
|
+
outputPath,
|
|
2725
|
+
content: renderResult.output
|
|
2726
|
+
}
|
|
2727
|
+
};
|
|
2728
|
+
}
|
|
2729
|
+
|
|
2730
|
+
// ../linter-gen/dist/generator/index-generator.js
|
|
2731
|
+
function toCamelCase3(str) {
|
|
2732
|
+
return str.replace(/-([a-z0-9])/g, (_, char) => char.toUpperCase());
|
|
2733
|
+
}
|
|
2734
|
+
function generateIndex(ruleNames) {
|
|
2735
|
+
const imports = ruleNames.map((name) => {
|
|
2736
|
+
const camel = toCamelCase3(name);
|
|
2737
|
+
return `import ${camel} from './${name}';`;
|
|
2738
|
+
}).join("\n");
|
|
2739
|
+
const rulesObject = ruleNames.map((name) => {
|
|
2740
|
+
const camel = toCamelCase3(name);
|
|
2741
|
+
return ` '${name}': ${camel},`;
|
|
2742
|
+
}).join("\n");
|
|
2743
|
+
const namedExports = ruleNames.map(toCamelCase3).join(", ");
|
|
2744
|
+
return `// Generated by @harness-engineering/linter-gen
|
|
2745
|
+
// Do not edit manually - regenerate from harness-linter.yml
|
|
2746
|
+
|
|
2747
|
+
${imports}
|
|
2748
|
+
|
|
2749
|
+
export const rules = {
|
|
2750
|
+
${rulesObject}
|
|
2751
|
+
};
|
|
2752
|
+
|
|
2753
|
+
export { ${namedExports} };
|
|
2754
|
+
`;
|
|
2755
|
+
}
|
|
2756
|
+
|
|
2757
|
+
// ../linter-gen/dist/generator/orchestrator.js
|
|
2758
|
+
async function validate(options) {
|
|
2759
|
+
const parseResult = await parseConfig(options.configPath);
|
|
2760
|
+
if (!parseResult.success) {
|
|
2761
|
+
return { success: false, error: parseResult.error };
|
|
2762
|
+
}
|
|
2763
|
+
return { success: true, ruleCount: parseResult.data.rules.length };
|
|
2764
|
+
}
|
|
2765
|
+
async function generate(options) {
|
|
2766
|
+
const errors = [];
|
|
2767
|
+
const parseResult = await parseConfig(options.configPath);
|
|
2768
|
+
if (!parseResult.success) {
|
|
2769
|
+
return { success: false, errors: [{ type: "parse", error: parseResult.error }] };
|
|
2770
|
+
}
|
|
2771
|
+
const config = parseResult.data;
|
|
2772
|
+
const configDir = path21.dirname(path21.resolve(options.configPath));
|
|
2773
|
+
const outputDir = options.outputDir ? path21.resolve(options.outputDir) : path21.resolve(configDir, config.output);
|
|
2774
|
+
if (options.clean && !options.dryRun) {
|
|
2775
|
+
try {
|
|
2776
|
+
await fs12.rm(outputDir, { recursive: true, force: true });
|
|
2777
|
+
} catch {
|
|
2778
|
+
}
|
|
2779
|
+
}
|
|
2780
|
+
if (!options.dryRun) {
|
|
2781
|
+
await fs12.mkdir(outputDir, { recursive: true });
|
|
2782
|
+
}
|
|
2783
|
+
const generatedRules = [];
|
|
2784
|
+
for (const rule of config.rules) {
|
|
2785
|
+
const templateResult = await loadTemplate(rule.type, config.templates, configDir);
|
|
2786
|
+
if (!templateResult.success) {
|
|
2787
|
+
errors.push({
|
|
2788
|
+
type: "template",
|
|
2789
|
+
error: templateResult.error,
|
|
2790
|
+
ruleName: rule.name
|
|
2791
|
+
});
|
|
2792
|
+
continue;
|
|
2793
|
+
}
|
|
2794
|
+
const ruleResult = generateRule(rule, templateResult.source, outputDir, options.configPath);
|
|
2795
|
+
if (!ruleResult.success) {
|
|
2796
|
+
errors.push({
|
|
2797
|
+
type: "render",
|
|
2798
|
+
error: ruleResult.error,
|
|
2799
|
+
ruleName: ruleResult.ruleName
|
|
2800
|
+
});
|
|
2801
|
+
continue;
|
|
2802
|
+
}
|
|
2803
|
+
if (!options.dryRun) {
|
|
2804
|
+
try {
|
|
2805
|
+
await fs12.writeFile(ruleResult.rule.outputPath, ruleResult.rule.content, "utf-8");
|
|
2806
|
+
} catch (err) {
|
|
2807
|
+
errors.push({
|
|
2808
|
+
type: "write",
|
|
2809
|
+
error: err,
|
|
2810
|
+
path: ruleResult.rule.outputPath
|
|
2811
|
+
});
|
|
2812
|
+
continue;
|
|
2813
|
+
}
|
|
2814
|
+
}
|
|
2815
|
+
generatedRules.push(rule.name);
|
|
2816
|
+
}
|
|
2817
|
+
if (generatedRules.length > 0 && !options.dryRun) {
|
|
2818
|
+
const indexContent = generateIndex(generatedRules);
|
|
2819
|
+
const indexPath = path21.join(outputDir, "index.ts");
|
|
2820
|
+
try {
|
|
2821
|
+
await fs12.writeFile(indexPath, indexContent, "utf-8");
|
|
2822
|
+
} catch (err) {
|
|
2823
|
+
errors.push({ type: "write", error: err, path: indexPath });
|
|
2824
|
+
}
|
|
2825
|
+
}
|
|
2826
|
+
if (errors.length > 0) {
|
|
2827
|
+
return { success: false, errors };
|
|
2828
|
+
}
|
|
2829
|
+
return {
|
|
2830
|
+
success: true,
|
|
2831
|
+
rulesGenerated: generatedRules,
|
|
2832
|
+
outputDir,
|
|
2833
|
+
dryRun: options.dryRun ?? false
|
|
2834
|
+
};
|
|
2835
|
+
}
|
|
2836
|
+
|
|
2837
|
+
// src/commands/linter/generate.ts
|
|
2499
2838
|
function createGenerateCommand() {
|
|
2500
2839
|
return new Command15("generate").description("Generate ESLint rules from harness-linter.yml").option("-c, --config <path>", "Path to harness-linter.yml", "./harness-linter.yml").option("-o, --output <dir>", "Override output directory").option("--clean", "Remove existing files before generating").option("--dry-run", "Preview without writing files").option("--json", "Output as JSON").option("--verbose", "Show detailed output").action(async (options) => {
|
|
2501
2840
|
try {
|
|
@@ -2560,7 +2899,6 @@ Generated ${result.rulesGenerated.length} rules to ${result.outputDir}`);
|
|
|
2560
2899
|
|
|
2561
2900
|
// src/commands/linter/validate.ts
|
|
2562
2901
|
import { Command as Command16 } from "commander";
|
|
2563
|
-
import { validate } from "@harness-engineering/linter-gen";
|
|
2564
2902
|
function createValidateCommand2() {
|
|
2565
2903
|
return new Command16("validate").description("Validate harness-linter.yml config").option("-c, --config <path>", "Path to harness-linter.yml", "./harness-linter.yml").option("--json", "Output as JSON").action(async (options) => {
|
|
2566
2904
|
try {
|
|
@@ -2625,11 +2963,8 @@ function createListCommand() {
|
|
|
2625
2963
|
|
|
2626
2964
|
// src/commands/persona/generate.ts
|
|
2627
2965
|
import { Command as Command19 } from "commander";
|
|
2628
|
-
import * as
|
|
2629
|
-
import * as
|
|
2630
|
-
|
|
2631
|
-
// src/persona/generators/runtime.ts
|
|
2632
|
-
import { Ok as Ok15, Err as Err11 } from "@harness-engineering/core";
|
|
2966
|
+
import * as fs13 from "fs";
|
|
2967
|
+
import * as path22 from "path";
|
|
2633
2968
|
|
|
2634
2969
|
// src/utils/string.ts
|
|
2635
2970
|
function toKebabCase(name) {
|
|
@@ -2646,9 +2981,9 @@ function generateRuntime(persona) {
|
|
|
2646
2981
|
timeout: persona.config.timeout,
|
|
2647
2982
|
severity: persona.config.severity
|
|
2648
2983
|
};
|
|
2649
|
-
return
|
|
2984
|
+
return Ok(JSON.stringify(config, null, 2));
|
|
2650
2985
|
} catch (error) {
|
|
2651
|
-
return
|
|
2986
|
+
return Err(
|
|
2652
2987
|
new Error(
|
|
2653
2988
|
`Failed to generate runtime config: ${error instanceof Error ? error.message : String(error)}`
|
|
2654
2989
|
)
|
|
@@ -2657,7 +2992,6 @@ function generateRuntime(persona) {
|
|
|
2657
2992
|
}
|
|
2658
2993
|
|
|
2659
2994
|
// src/persona/generators/agents-md.ts
|
|
2660
|
-
import { Ok as Ok16, Err as Err12 } from "@harness-engineering/core";
|
|
2661
2995
|
function formatTrigger(trigger) {
|
|
2662
2996
|
switch (trigger.event) {
|
|
2663
2997
|
case "on_pr": {
|
|
@@ -2691,9 +3025,9 @@ function generateAgentsMd(persona) {
|
|
|
2691
3025
|
|
|
2692
3026
|
**When this agent flags an issue:** Fix violations before merging. Run ${allCommands} locally to validate.
|
|
2693
3027
|
`;
|
|
2694
|
-
return
|
|
3028
|
+
return Ok(fragment);
|
|
2695
3029
|
} catch (error) {
|
|
2696
|
-
return
|
|
3030
|
+
return Err(
|
|
2697
3031
|
new Error(
|
|
2698
3032
|
`Failed to generate AGENTS.md fragment: ${error instanceof Error ? error.message : String(error)}`
|
|
2699
3033
|
)
|
|
@@ -2703,7 +3037,6 @@ function generateAgentsMd(persona) {
|
|
|
2703
3037
|
|
|
2704
3038
|
// src/persona/generators/ci-workflow.ts
|
|
2705
3039
|
import YAML2 from "yaml";
|
|
2706
|
-
import { Ok as Ok17, Err as Err13 } from "@harness-engineering/core";
|
|
2707
3040
|
function buildGitHubTriggers(triggers) {
|
|
2708
3041
|
const on = {};
|
|
2709
3042
|
for (const trigger of triggers) {
|
|
@@ -2729,7 +3062,7 @@ function buildGitHubTriggers(triggers) {
|
|
|
2729
3062
|
}
|
|
2730
3063
|
function generateCIWorkflow(persona, platform) {
|
|
2731
3064
|
try {
|
|
2732
|
-
if (platform === "gitlab") return
|
|
3065
|
+
if (platform === "gitlab") return Err(new Error("GitLab CI generation is not yet supported"));
|
|
2733
3066
|
const severity = persona.config.severity;
|
|
2734
3067
|
const steps = [
|
|
2735
3068
|
{ uses: "actions/checkout@v4" },
|
|
@@ -2751,9 +3084,9 @@ function generateCIWorkflow(persona, platform) {
|
|
|
2751
3084
|
}
|
|
2752
3085
|
}
|
|
2753
3086
|
};
|
|
2754
|
-
return
|
|
3087
|
+
return Ok(YAML2.stringify(workflow, { lineWidth: 0 }));
|
|
2755
3088
|
} catch (error) {
|
|
2756
|
-
return
|
|
3089
|
+
return Err(
|
|
2757
3090
|
new Error(
|
|
2758
3091
|
`Failed to generate CI workflow: ${error instanceof Error ? error.message : String(error)}`
|
|
2759
3092
|
)
|
|
@@ -2766,40 +3099,40 @@ function createGenerateCommand2() {
|
|
|
2766
3099
|
return new Command19("generate").description("Generate artifacts from a persona config").argument("<name>", "Persona name (e.g., architecture-enforcer)").option("--output-dir <dir>", "Output directory", ".").option("--only <type>", "Generate only: ci, agents-md, runtime").action(async (name, opts, cmd) => {
|
|
2767
3100
|
const globalOpts = cmd.optsWithGlobals();
|
|
2768
3101
|
const personasDir = resolvePersonasDir();
|
|
2769
|
-
const filePath =
|
|
3102
|
+
const filePath = path22.join(personasDir, `${name}.yaml`);
|
|
2770
3103
|
const personaResult = loadPersona(filePath);
|
|
2771
3104
|
if (!personaResult.ok) {
|
|
2772
3105
|
logger.error(personaResult.error.message);
|
|
2773
3106
|
process.exit(ExitCode.ERROR);
|
|
2774
3107
|
}
|
|
2775
3108
|
const persona = personaResult.value;
|
|
2776
|
-
const outputDir =
|
|
3109
|
+
const outputDir = path22.resolve(opts.outputDir);
|
|
2777
3110
|
const slug = toKebabCase(persona.name);
|
|
2778
3111
|
const only = opts.only;
|
|
2779
3112
|
const generated = [];
|
|
2780
3113
|
if (!only || only === "runtime") {
|
|
2781
3114
|
const result = generateRuntime(persona);
|
|
2782
3115
|
if (result.ok) {
|
|
2783
|
-
const outPath =
|
|
2784
|
-
|
|
2785
|
-
|
|
3116
|
+
const outPath = path22.join(outputDir, `${slug}.runtime.json`);
|
|
3117
|
+
fs13.mkdirSync(path22.dirname(outPath), { recursive: true });
|
|
3118
|
+
fs13.writeFileSync(outPath, result.value);
|
|
2786
3119
|
generated.push(outPath);
|
|
2787
3120
|
}
|
|
2788
3121
|
}
|
|
2789
3122
|
if (!only || only === "agents-md") {
|
|
2790
3123
|
const result = generateAgentsMd(persona);
|
|
2791
3124
|
if (result.ok) {
|
|
2792
|
-
const outPath =
|
|
2793
|
-
|
|
3125
|
+
const outPath = path22.join(outputDir, `${slug}.agents.md`);
|
|
3126
|
+
fs13.writeFileSync(outPath, result.value);
|
|
2794
3127
|
generated.push(outPath);
|
|
2795
3128
|
}
|
|
2796
3129
|
}
|
|
2797
3130
|
if (!only || only === "ci") {
|
|
2798
3131
|
const result = generateCIWorkflow(persona, "github");
|
|
2799
3132
|
if (result.ok) {
|
|
2800
|
-
const outPath =
|
|
2801
|
-
|
|
2802
|
-
|
|
3133
|
+
const outPath = path22.join(outputDir, ".github", "workflows", `${slug}.yml`);
|
|
3134
|
+
fs13.mkdirSync(path22.dirname(outPath), { recursive: true });
|
|
3135
|
+
fs13.writeFileSync(outPath, result.value);
|
|
2803
3136
|
generated.push(outPath);
|
|
2804
3137
|
}
|
|
2805
3138
|
}
|
|
@@ -2824,26 +3157,26 @@ import { Command as Command25 } from "commander";
|
|
|
2824
3157
|
|
|
2825
3158
|
// src/commands/skill/list.ts
|
|
2826
3159
|
import { Command as Command21 } from "commander";
|
|
2827
|
-
import * as
|
|
2828
|
-
import * as
|
|
2829
|
-
import { parse as
|
|
3160
|
+
import * as fs14 from "fs";
|
|
3161
|
+
import * as path23 from "path";
|
|
3162
|
+
import { parse as parse4 } from "yaml";
|
|
2830
3163
|
function createListCommand2() {
|
|
2831
3164
|
return new Command21("list").description("List available skills").action(async (_opts, cmd) => {
|
|
2832
3165
|
const globalOpts = cmd.optsWithGlobals();
|
|
2833
3166
|
const skillsDir = resolveSkillsDir();
|
|
2834
|
-
if (!
|
|
3167
|
+
if (!fs14.existsSync(skillsDir)) {
|
|
2835
3168
|
logger.info("No skills directory found.");
|
|
2836
3169
|
process.exit(ExitCode.SUCCESS);
|
|
2837
3170
|
return;
|
|
2838
3171
|
}
|
|
2839
|
-
const entries =
|
|
3172
|
+
const entries = fs14.readdirSync(skillsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
|
|
2840
3173
|
const skills = [];
|
|
2841
3174
|
for (const name of entries) {
|
|
2842
|
-
const yamlPath =
|
|
2843
|
-
if (!
|
|
3175
|
+
const yamlPath = path23.join(skillsDir, name, "skill.yaml");
|
|
3176
|
+
if (!fs14.existsSync(yamlPath)) continue;
|
|
2844
3177
|
try {
|
|
2845
|
-
const raw =
|
|
2846
|
-
const parsed =
|
|
3178
|
+
const raw = fs14.readFileSync(yamlPath, "utf-8");
|
|
3179
|
+
const parsed = parse4(raw);
|
|
2847
3180
|
const result = SkillMetadataSchema.safeParse(parsed);
|
|
2848
3181
|
if (result.success) {
|
|
2849
3182
|
skills.push(result.data);
|
|
@@ -2873,9 +3206,9 @@ function createListCommand2() {
|
|
|
2873
3206
|
|
|
2874
3207
|
// src/commands/skill/run.ts
|
|
2875
3208
|
import { Command as Command22 } from "commander";
|
|
2876
|
-
import * as
|
|
2877
|
-
import * as
|
|
2878
|
-
import { parse as
|
|
3209
|
+
import * as fs15 from "fs";
|
|
3210
|
+
import * as path24 from "path";
|
|
3211
|
+
import { parse as parse5 } from "yaml";
|
|
2879
3212
|
|
|
2880
3213
|
// src/skill/complexity.ts
|
|
2881
3214
|
import { execSync as execSync3 } from "child_process";
|
|
@@ -2964,18 +3297,18 @@ ${options.priorState}`);
|
|
|
2964
3297
|
function createRunCommand2() {
|
|
2965
3298
|
return new Command22("run").description("Run a skill (outputs SKILL.md content with context preamble)").argument("<name>", "Skill name (e.g., harness-tdd)").option("--path <path>", "Project root path for context injection").option("--complexity <level>", "Complexity: auto, light, full", "auto").option("--phase <name>", "Start at a specific phase (for re-entry)").option("--party", "Enable multi-perspective evaluation").action(async (name, opts, _cmd) => {
|
|
2966
3299
|
const skillsDir = resolveSkillsDir();
|
|
2967
|
-
const skillDir =
|
|
2968
|
-
if (!
|
|
3300
|
+
const skillDir = path24.join(skillsDir, name);
|
|
3301
|
+
if (!fs15.existsSync(skillDir)) {
|
|
2969
3302
|
logger.error(`Skill not found: ${name}`);
|
|
2970
3303
|
process.exit(ExitCode.ERROR);
|
|
2971
3304
|
return;
|
|
2972
3305
|
}
|
|
2973
|
-
const yamlPath =
|
|
3306
|
+
const yamlPath = path24.join(skillDir, "skill.yaml");
|
|
2974
3307
|
let metadata = null;
|
|
2975
|
-
if (
|
|
3308
|
+
if (fs15.existsSync(yamlPath)) {
|
|
2976
3309
|
try {
|
|
2977
|
-
const raw =
|
|
2978
|
-
const parsed =
|
|
3310
|
+
const raw = fs15.readFileSync(yamlPath, "utf-8");
|
|
3311
|
+
const parsed = parse5(raw);
|
|
2979
3312
|
const result = SkillMetadataSchema.safeParse(parsed);
|
|
2980
3313
|
if (result.success) metadata = result.data;
|
|
2981
3314
|
} catch {
|
|
@@ -2985,17 +3318,17 @@ function createRunCommand2() {
|
|
|
2985
3318
|
if (metadata?.phases && metadata.phases.length > 0) {
|
|
2986
3319
|
const requested = opts.complexity ?? "auto";
|
|
2987
3320
|
if (requested === "auto") {
|
|
2988
|
-
const projectPath2 = opts.path ?
|
|
3321
|
+
const projectPath2 = opts.path ? path24.resolve(opts.path) : process.cwd();
|
|
2989
3322
|
complexity = detectComplexity(projectPath2);
|
|
2990
3323
|
} else {
|
|
2991
3324
|
complexity = requested;
|
|
2992
3325
|
}
|
|
2993
3326
|
}
|
|
2994
3327
|
let principles;
|
|
2995
|
-
const projectPath = opts.path ?
|
|
2996
|
-
const principlesPath =
|
|
2997
|
-
if (
|
|
2998
|
-
principles =
|
|
3328
|
+
const projectPath = opts.path ? path24.resolve(opts.path) : process.cwd();
|
|
3329
|
+
const principlesPath = path24.join(projectPath, "docs", "principles.md");
|
|
3330
|
+
if (fs15.existsSync(principlesPath)) {
|
|
3331
|
+
principles = fs15.readFileSync(principlesPath, "utf-8");
|
|
2999
3332
|
}
|
|
3000
3333
|
let priorState;
|
|
3001
3334
|
let stateWarning;
|
|
@@ -3010,16 +3343,16 @@ function createRunCommand2() {
|
|
|
3010
3343
|
}
|
|
3011
3344
|
if (metadata?.state.persistent && metadata.state.files.length > 0) {
|
|
3012
3345
|
for (const stateFilePath of metadata.state.files) {
|
|
3013
|
-
const fullPath =
|
|
3014
|
-
if (
|
|
3015
|
-
const stat =
|
|
3346
|
+
const fullPath = path24.join(projectPath, stateFilePath);
|
|
3347
|
+
if (fs15.existsSync(fullPath)) {
|
|
3348
|
+
const stat = fs15.statSync(fullPath);
|
|
3016
3349
|
if (stat.isDirectory()) {
|
|
3017
|
-
const files =
|
|
3350
|
+
const files = fs15.readdirSync(fullPath).map((f) => ({ name: f, mtime: fs15.statSync(path24.join(fullPath, f)).mtimeMs })).sort((a, b) => b.mtime - a.mtime);
|
|
3018
3351
|
if (files.length > 0) {
|
|
3019
|
-
priorState =
|
|
3352
|
+
priorState = fs15.readFileSync(path24.join(fullPath, files[0].name), "utf-8");
|
|
3020
3353
|
}
|
|
3021
3354
|
} else {
|
|
3022
|
-
priorState =
|
|
3355
|
+
priorState = fs15.readFileSync(fullPath, "utf-8");
|
|
3023
3356
|
}
|
|
3024
3357
|
break;
|
|
3025
3358
|
}
|
|
@@ -3038,17 +3371,17 @@ function createRunCommand2() {
|
|
|
3038
3371
|
...stateWarning !== void 0 && { stateWarning },
|
|
3039
3372
|
party: opts.party
|
|
3040
3373
|
});
|
|
3041
|
-
const skillMdPath =
|
|
3042
|
-
if (!
|
|
3374
|
+
const skillMdPath = path24.join(skillDir, "SKILL.md");
|
|
3375
|
+
if (!fs15.existsSync(skillMdPath)) {
|
|
3043
3376
|
logger.error(`SKILL.md not found for skill: ${name}`);
|
|
3044
3377
|
process.exit(ExitCode.ERROR);
|
|
3045
3378
|
return;
|
|
3046
3379
|
}
|
|
3047
|
-
let content =
|
|
3380
|
+
let content = fs15.readFileSync(skillMdPath, "utf-8");
|
|
3048
3381
|
if (metadata?.state.persistent && opts.path) {
|
|
3049
|
-
const stateFile =
|
|
3050
|
-
if (
|
|
3051
|
-
const stateContent =
|
|
3382
|
+
const stateFile = path24.join(projectPath, ".harness", "state.json");
|
|
3383
|
+
if (fs15.existsSync(stateFile)) {
|
|
3384
|
+
const stateContent = fs15.readFileSync(stateFile, "utf-8");
|
|
3052
3385
|
content += `
|
|
3053
3386
|
|
|
3054
3387
|
---
|
|
@@ -3066,9 +3399,9 @@ ${stateContent}
|
|
|
3066
3399
|
|
|
3067
3400
|
// src/commands/skill/validate.ts
|
|
3068
3401
|
import { Command as Command23 } from "commander";
|
|
3069
|
-
import * as
|
|
3070
|
-
import * as
|
|
3071
|
-
import { parse as
|
|
3402
|
+
import * as fs16 from "fs";
|
|
3403
|
+
import * as path25 from "path";
|
|
3404
|
+
import { parse as parse6 } from "yaml";
|
|
3072
3405
|
var REQUIRED_SECTIONS = [
|
|
3073
3406
|
"## When to Use",
|
|
3074
3407
|
"## Process",
|
|
@@ -3080,32 +3413,32 @@ function createValidateCommand3() {
|
|
|
3080
3413
|
return new Command23("validate").description("Validate all skill.yaml files and SKILL.md structure").action(async (_opts, cmd) => {
|
|
3081
3414
|
const globalOpts = cmd.optsWithGlobals();
|
|
3082
3415
|
const skillsDir = resolveSkillsDir();
|
|
3083
|
-
if (!
|
|
3416
|
+
if (!fs16.existsSync(skillsDir)) {
|
|
3084
3417
|
logger.info("No skills directory found.");
|
|
3085
3418
|
process.exit(ExitCode.SUCCESS);
|
|
3086
3419
|
return;
|
|
3087
3420
|
}
|
|
3088
|
-
const entries =
|
|
3421
|
+
const entries = fs16.readdirSync(skillsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
|
|
3089
3422
|
const errors = [];
|
|
3090
3423
|
let validated = 0;
|
|
3091
3424
|
for (const name of entries) {
|
|
3092
|
-
const skillDir =
|
|
3093
|
-
const yamlPath =
|
|
3094
|
-
const skillMdPath =
|
|
3095
|
-
if (!
|
|
3425
|
+
const skillDir = path25.join(skillsDir, name);
|
|
3426
|
+
const yamlPath = path25.join(skillDir, "skill.yaml");
|
|
3427
|
+
const skillMdPath = path25.join(skillDir, "SKILL.md");
|
|
3428
|
+
if (!fs16.existsSync(yamlPath)) {
|
|
3096
3429
|
errors.push(`${name}: missing skill.yaml`);
|
|
3097
3430
|
continue;
|
|
3098
3431
|
}
|
|
3099
3432
|
try {
|
|
3100
|
-
const raw =
|
|
3101
|
-
const parsed =
|
|
3433
|
+
const raw = fs16.readFileSync(yamlPath, "utf-8");
|
|
3434
|
+
const parsed = parse6(raw);
|
|
3102
3435
|
const result = SkillMetadataSchema.safeParse(parsed);
|
|
3103
3436
|
if (!result.success) {
|
|
3104
3437
|
errors.push(`${name}/skill.yaml: ${result.error.message}`);
|
|
3105
3438
|
continue;
|
|
3106
3439
|
}
|
|
3107
|
-
if (
|
|
3108
|
-
const mdContent =
|
|
3440
|
+
if (fs16.existsSync(skillMdPath)) {
|
|
3441
|
+
const mdContent = fs16.readFileSync(skillMdPath, "utf-8");
|
|
3109
3442
|
for (const section of REQUIRED_SECTIONS) {
|
|
3110
3443
|
if (!mdContent.includes(section)) {
|
|
3111
3444
|
errors.push(`${name}/SKILL.md: missing section "${section}"`);
|
|
@@ -3147,28 +3480,28 @@ function createValidateCommand3() {
|
|
|
3147
3480
|
|
|
3148
3481
|
// src/commands/skill/info.ts
|
|
3149
3482
|
import { Command as Command24 } from "commander";
|
|
3150
|
-
import * as
|
|
3151
|
-
import * as
|
|
3152
|
-
import { parse as
|
|
3483
|
+
import * as fs17 from "fs";
|
|
3484
|
+
import * as path26 from "path";
|
|
3485
|
+
import { parse as parse7 } from "yaml";
|
|
3153
3486
|
function createInfoCommand() {
|
|
3154
3487
|
return new Command24("info").description("Show metadata for a skill").argument("<name>", "Skill name (e.g., harness-tdd)").action(async (name, _opts, cmd) => {
|
|
3155
3488
|
const globalOpts = cmd.optsWithGlobals();
|
|
3156
3489
|
const skillsDir = resolveSkillsDir();
|
|
3157
|
-
const skillDir =
|
|
3158
|
-
if (!
|
|
3490
|
+
const skillDir = path26.join(skillsDir, name);
|
|
3491
|
+
if (!fs17.existsSync(skillDir)) {
|
|
3159
3492
|
logger.error(`Skill not found: ${name}`);
|
|
3160
3493
|
process.exit(ExitCode.ERROR);
|
|
3161
3494
|
return;
|
|
3162
3495
|
}
|
|
3163
|
-
const yamlPath =
|
|
3164
|
-
if (!
|
|
3496
|
+
const yamlPath = path26.join(skillDir, "skill.yaml");
|
|
3497
|
+
if (!fs17.existsSync(yamlPath)) {
|
|
3165
3498
|
logger.error(`skill.yaml not found for skill: ${name}`);
|
|
3166
3499
|
process.exit(ExitCode.ERROR);
|
|
3167
3500
|
return;
|
|
3168
3501
|
}
|
|
3169
3502
|
try {
|
|
3170
|
-
const raw =
|
|
3171
|
-
const parsed =
|
|
3503
|
+
const raw = fs17.readFileSync(yamlPath, "utf-8");
|
|
3504
|
+
const parsed = parse7(raw);
|
|
3172
3505
|
const result = SkillMetadataSchema.safeParse(parsed);
|
|
3173
3506
|
if (!result.success) {
|
|
3174
3507
|
logger.error(`Invalid skill.yaml: ${result.error.message}`);
|
|
@@ -3221,12 +3554,11 @@ import { Command as Command30 } from "commander";
|
|
|
3221
3554
|
|
|
3222
3555
|
// src/commands/state/show.ts
|
|
3223
3556
|
import { Command as Command26 } from "commander";
|
|
3224
|
-
import * as
|
|
3225
|
-
import { loadState } from "@harness-engineering/core";
|
|
3557
|
+
import * as path27 from "path";
|
|
3226
3558
|
function createShowCommand() {
|
|
3227
3559
|
return new Command26("show").description("Show current project state").option("--path <path>", "Project root path", ".").option("--stream <name>", "Target a specific stream").action(async (opts, cmd) => {
|
|
3228
3560
|
const globalOpts = cmd.optsWithGlobals();
|
|
3229
|
-
const projectPath =
|
|
3561
|
+
const projectPath = path27.resolve(opts.path);
|
|
3230
3562
|
const result = await loadState(projectPath, opts.stream);
|
|
3231
3563
|
if (!result.ok) {
|
|
3232
3564
|
logger.error(result.error.message);
|
|
@@ -3267,13 +3599,12 @@ Decisions: ${state.decisions.length}`);
|
|
|
3267
3599
|
|
|
3268
3600
|
// src/commands/state/reset.ts
|
|
3269
3601
|
import { Command as Command27 } from "commander";
|
|
3270
|
-
import * as
|
|
3271
|
-
import * as
|
|
3602
|
+
import * as fs18 from "fs";
|
|
3603
|
+
import * as path28 from "path";
|
|
3272
3604
|
import * as readline from "readline";
|
|
3273
|
-
import { resolveStreamPath } from "@harness-engineering/core";
|
|
3274
3605
|
function createResetCommand() {
|
|
3275
3606
|
return new Command27("reset").description("Reset project state (deletes .harness/state.json)").option("--path <path>", "Project root path", ".").option("--stream <name>", "Target a specific stream").option("--yes", "Skip confirmation prompt").action(async (opts, _cmd) => {
|
|
3276
|
-
const projectPath =
|
|
3607
|
+
const projectPath = path28.resolve(opts.path);
|
|
3277
3608
|
let statePath;
|
|
3278
3609
|
if (opts.stream) {
|
|
3279
3610
|
const streamResult = await resolveStreamPath(projectPath, { stream: opts.stream });
|
|
@@ -3282,19 +3613,19 @@ function createResetCommand() {
|
|
|
3282
3613
|
process.exit(ExitCode.ERROR);
|
|
3283
3614
|
return;
|
|
3284
3615
|
}
|
|
3285
|
-
statePath =
|
|
3616
|
+
statePath = path28.join(streamResult.value, "state.json");
|
|
3286
3617
|
} else {
|
|
3287
|
-
statePath =
|
|
3618
|
+
statePath = path28.join(projectPath, ".harness", "state.json");
|
|
3288
3619
|
}
|
|
3289
|
-
if (!
|
|
3620
|
+
if (!fs18.existsSync(statePath)) {
|
|
3290
3621
|
logger.info("No state file found. Nothing to reset.");
|
|
3291
3622
|
process.exit(ExitCode.SUCCESS);
|
|
3292
3623
|
return;
|
|
3293
3624
|
}
|
|
3294
3625
|
if (!opts.yes) {
|
|
3295
3626
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
3296
|
-
const answer = await new Promise((
|
|
3297
|
-
rl.question("Reset project state? This cannot be undone. [y/N] ",
|
|
3627
|
+
const answer = await new Promise((resolve24) => {
|
|
3628
|
+
rl.question("Reset project state? This cannot be undone. [y/N] ", resolve24);
|
|
3298
3629
|
});
|
|
3299
3630
|
rl.close();
|
|
3300
3631
|
if (answer.toLowerCase() !== "y" && answer.toLowerCase() !== "yes") {
|
|
@@ -3304,7 +3635,7 @@ function createResetCommand() {
|
|
|
3304
3635
|
}
|
|
3305
3636
|
}
|
|
3306
3637
|
try {
|
|
3307
|
-
|
|
3638
|
+
fs18.unlinkSync(statePath);
|
|
3308
3639
|
logger.success("Project state reset.");
|
|
3309
3640
|
} catch (e) {
|
|
3310
3641
|
logger.error(`Failed to reset state: ${e instanceof Error ? e.message : String(e)}`);
|
|
@@ -3317,11 +3648,10 @@ function createResetCommand() {
|
|
|
3317
3648
|
|
|
3318
3649
|
// src/commands/state/learn.ts
|
|
3319
3650
|
import { Command as Command28 } from "commander";
|
|
3320
|
-
import * as
|
|
3321
|
-
import { appendLearning } from "@harness-engineering/core";
|
|
3651
|
+
import * as path29 from "path";
|
|
3322
3652
|
function createLearnCommand() {
|
|
3323
3653
|
return new Command28("learn").description("Append a learning to .harness/learnings.md").argument("<message>", "The learning to record").option("--path <path>", "Project root path", ".").option("--stream <name>", "Target a specific stream").action(async (message, opts, _cmd) => {
|
|
3324
|
-
const projectPath =
|
|
3654
|
+
const projectPath = path29.resolve(opts.path);
|
|
3325
3655
|
const result = await appendLearning(projectPath, message, void 0, void 0, opts.stream);
|
|
3326
3656
|
if (!result.ok) {
|
|
3327
3657
|
logger.error(result.error.message);
|
|
@@ -3335,19 +3665,12 @@ function createLearnCommand() {
|
|
|
3335
3665
|
|
|
3336
3666
|
// src/commands/state/streams.ts
|
|
3337
3667
|
import { Command as Command29 } from "commander";
|
|
3338
|
-
import * as
|
|
3339
|
-
import {
|
|
3340
|
-
createStream,
|
|
3341
|
-
listStreams,
|
|
3342
|
-
archiveStream,
|
|
3343
|
-
setActiveStream,
|
|
3344
|
-
loadStreamIndex
|
|
3345
|
-
} from "@harness-engineering/core";
|
|
3668
|
+
import * as path30 from "path";
|
|
3346
3669
|
function createStreamsCommand() {
|
|
3347
3670
|
const command = new Command29("streams").description("Manage state streams");
|
|
3348
3671
|
command.command("list").description("List all known streams").option("--path <path>", "Project root path", ".").action(async (opts, cmd) => {
|
|
3349
3672
|
const globalOpts = cmd.optsWithGlobals();
|
|
3350
|
-
const projectPath =
|
|
3673
|
+
const projectPath = path30.resolve(opts.path);
|
|
3351
3674
|
const indexResult = await loadStreamIndex(projectPath);
|
|
3352
3675
|
const result = await listStreams(projectPath);
|
|
3353
3676
|
if (!result.ok) {
|
|
@@ -3371,7 +3694,7 @@ function createStreamsCommand() {
|
|
|
3371
3694
|
process.exit(ExitCode.SUCCESS);
|
|
3372
3695
|
});
|
|
3373
3696
|
command.command("create <name>").description("Create a new stream").option("--path <path>", "Project root path", ".").option("--branch <branch>", "Associate with a git branch").action(async (name, opts) => {
|
|
3374
|
-
const projectPath =
|
|
3697
|
+
const projectPath = path30.resolve(opts.path);
|
|
3375
3698
|
const result = await createStream(projectPath, name, opts.branch);
|
|
3376
3699
|
if (!result.ok) {
|
|
3377
3700
|
logger.error(result.error.message);
|
|
@@ -3382,7 +3705,7 @@ function createStreamsCommand() {
|
|
|
3382
3705
|
process.exit(ExitCode.SUCCESS);
|
|
3383
3706
|
});
|
|
3384
3707
|
command.command("archive <name>").description("Archive a stream").option("--path <path>", "Project root path", ".").action(async (name, opts) => {
|
|
3385
|
-
const projectPath =
|
|
3708
|
+
const projectPath = path30.resolve(opts.path);
|
|
3386
3709
|
const result = await archiveStream(projectPath, name);
|
|
3387
3710
|
if (!result.ok) {
|
|
3388
3711
|
logger.error(result.error.message);
|
|
@@ -3393,7 +3716,7 @@ function createStreamsCommand() {
|
|
|
3393
3716
|
process.exit(ExitCode.SUCCESS);
|
|
3394
3717
|
});
|
|
3395
3718
|
command.command("activate <name>").description("Set the active stream").option("--path <path>", "Project root path", ".").action(async (name, opts) => {
|
|
3396
|
-
const projectPath =
|
|
3719
|
+
const projectPath = path30.resolve(opts.path);
|
|
3397
3720
|
const result = await setActiveStream(projectPath, name);
|
|
3398
3721
|
if (!result.ok) {
|
|
3399
3722
|
logger.error(result.error.message);
|
|
@@ -3418,18 +3741,17 @@ function createStateCommand() {
|
|
|
3418
3741
|
|
|
3419
3742
|
// src/commands/check-phase-gate.ts
|
|
3420
3743
|
import { Command as Command31 } from "commander";
|
|
3421
|
-
import * as
|
|
3422
|
-
import * as
|
|
3423
|
-
import { Ok as Ok18 } from "@harness-engineering/core";
|
|
3744
|
+
import * as path31 from "path";
|
|
3745
|
+
import * as fs19 from "fs";
|
|
3424
3746
|
function resolveSpecPath(implFile, implPattern, specPattern, cwd) {
|
|
3425
|
-
const relImpl =
|
|
3747
|
+
const relImpl = path31.relative(cwd, implFile);
|
|
3426
3748
|
const implBase = (implPattern.split("*")[0] ?? "").replace(/\/+$/, "");
|
|
3427
3749
|
const afterBase = relImpl.startsWith(implBase + "/") ? relImpl.slice(implBase.length + 1) : relImpl;
|
|
3428
3750
|
const segments = afterBase.split("/");
|
|
3429
3751
|
const firstSegment = segments[0] ?? "";
|
|
3430
|
-
const feature = segments.length > 1 ? firstSegment :
|
|
3752
|
+
const feature = segments.length > 1 ? firstSegment : path31.basename(firstSegment, path31.extname(firstSegment));
|
|
3431
3753
|
const specRelative = specPattern.replace("{feature}", feature);
|
|
3432
|
-
return
|
|
3754
|
+
return path31.resolve(cwd, specRelative);
|
|
3433
3755
|
}
|
|
3434
3756
|
async function runCheckPhaseGate(options) {
|
|
3435
3757
|
const configResult = resolveConfig(options.configPath);
|
|
@@ -3437,9 +3759,9 @@ async function runCheckPhaseGate(options) {
|
|
|
3437
3759
|
return configResult;
|
|
3438
3760
|
}
|
|
3439
3761
|
const config = configResult.value;
|
|
3440
|
-
const cwd = options.cwd ?? (options.configPath ?
|
|
3762
|
+
const cwd = options.cwd ?? (options.configPath ? path31.dirname(path31.resolve(options.configPath)) : process.cwd());
|
|
3441
3763
|
if (!config.phaseGates?.enabled) {
|
|
3442
|
-
return
|
|
3764
|
+
return Ok({
|
|
3443
3765
|
pass: true,
|
|
3444
3766
|
skipped: true,
|
|
3445
3767
|
missingSpecs: [],
|
|
@@ -3454,16 +3776,16 @@ async function runCheckPhaseGate(options) {
|
|
|
3454
3776
|
for (const implFile of implFiles) {
|
|
3455
3777
|
checkedFiles++;
|
|
3456
3778
|
const expectedSpec = resolveSpecPath(implFile, mapping.implPattern, mapping.specPattern, cwd);
|
|
3457
|
-
if (!
|
|
3779
|
+
if (!fs19.existsSync(expectedSpec)) {
|
|
3458
3780
|
missingSpecs.push({
|
|
3459
|
-
implFile:
|
|
3460
|
-
expectedSpec:
|
|
3781
|
+
implFile: path31.relative(cwd, implFile),
|
|
3782
|
+
expectedSpec: path31.relative(cwd, expectedSpec)
|
|
3461
3783
|
});
|
|
3462
3784
|
}
|
|
3463
3785
|
}
|
|
3464
3786
|
}
|
|
3465
3787
|
const pass = missingSpecs.length === 0;
|
|
3466
|
-
return
|
|
3788
|
+
return Ok({
|
|
3467
3789
|
pass,
|
|
3468
3790
|
skipped: false,
|
|
3469
3791
|
severity: phaseGates.severity,
|
|
@@ -3527,15 +3849,15 @@ function createCheckPhaseGateCommand() {
|
|
|
3527
3849
|
|
|
3528
3850
|
// src/commands/generate-slash-commands.ts
|
|
3529
3851
|
import { Command as Command32 } from "commander";
|
|
3530
|
-
import
|
|
3531
|
-
import
|
|
3852
|
+
import fs22 from "fs";
|
|
3853
|
+
import path34 from "path";
|
|
3532
3854
|
import os2 from "os";
|
|
3533
3855
|
import readline2 from "readline";
|
|
3534
3856
|
|
|
3535
3857
|
// src/slash-commands/normalize.ts
|
|
3536
|
-
import
|
|
3537
|
-
import
|
|
3538
|
-
import { parse as
|
|
3858
|
+
import fs20 from "fs";
|
|
3859
|
+
import path32 from "path";
|
|
3860
|
+
import { parse as parse8 } from "yaml";
|
|
3539
3861
|
|
|
3540
3862
|
// src/slash-commands/normalize-name.ts
|
|
3541
3863
|
function normalizeName(skillName) {
|
|
@@ -3555,18 +3877,18 @@ function normalizeSkills(skillSources, platforms) {
|
|
|
3555
3877
|
const specs = [];
|
|
3556
3878
|
const nameMap = /* @__PURE__ */ new Map();
|
|
3557
3879
|
for (const { dir: skillsDir, source } of skillSources) {
|
|
3558
|
-
if (!
|
|
3559
|
-
const entries =
|
|
3880
|
+
if (!fs20.existsSync(skillsDir)) continue;
|
|
3881
|
+
const entries = fs20.readdirSync(skillsDir, { withFileTypes: true }).filter((d) => d.isDirectory());
|
|
3560
3882
|
for (const entry of entries) {
|
|
3561
|
-
const yamlPath =
|
|
3562
|
-
if (!
|
|
3883
|
+
const yamlPath = path32.join(skillsDir, entry.name, "skill.yaml");
|
|
3884
|
+
if (!fs20.existsSync(yamlPath)) continue;
|
|
3563
3885
|
let raw;
|
|
3564
3886
|
try {
|
|
3565
|
-
raw =
|
|
3887
|
+
raw = fs20.readFileSync(yamlPath, "utf-8");
|
|
3566
3888
|
} catch {
|
|
3567
3889
|
continue;
|
|
3568
3890
|
}
|
|
3569
|
-
const parsed =
|
|
3891
|
+
const parsed = parse8(raw);
|
|
3570
3892
|
const result = SkillMetadataSchema.safeParse(parsed);
|
|
3571
3893
|
if (!result.success) {
|
|
3572
3894
|
console.warn(`Skipping ${entry.name}: invalid skill.yaml`);
|
|
@@ -3586,15 +3908,15 @@ function normalizeSkills(skillSources, platforms) {
|
|
|
3586
3908
|
continue;
|
|
3587
3909
|
}
|
|
3588
3910
|
nameMap.set(normalized, { skillName: meta.name, source });
|
|
3589
|
-
const skillMdPath =
|
|
3590
|
-
const skillMdContent =
|
|
3591
|
-
const skillMdRelative =
|
|
3911
|
+
const skillMdPath = path32.join(skillsDir, entry.name, "SKILL.md");
|
|
3912
|
+
const skillMdContent = fs20.existsSync(skillMdPath) ? fs20.readFileSync(skillMdPath, "utf-8") : "";
|
|
3913
|
+
const skillMdRelative = path32.relative(
|
|
3592
3914
|
process.cwd(),
|
|
3593
|
-
|
|
3915
|
+
path32.join(skillsDir, entry.name, "SKILL.md")
|
|
3594
3916
|
);
|
|
3595
|
-
const skillYamlRelative =
|
|
3917
|
+
const skillYamlRelative = path32.relative(
|
|
3596
3918
|
process.cwd(),
|
|
3597
|
-
|
|
3919
|
+
path32.join(skillsDir, entry.name, "skill.yaml")
|
|
3598
3920
|
);
|
|
3599
3921
|
const args = (meta.cli?.args ?? []).map((a) => ({
|
|
3600
3922
|
name: a.name,
|
|
@@ -3763,8 +4085,8 @@ function renderGemini(spec, skillMdContent, skillYamlContent) {
|
|
|
3763
4085
|
}
|
|
3764
4086
|
|
|
3765
4087
|
// src/slash-commands/sync.ts
|
|
3766
|
-
import
|
|
3767
|
-
import
|
|
4088
|
+
import fs21 from "fs";
|
|
4089
|
+
import path33 from "path";
|
|
3768
4090
|
|
|
3769
4091
|
// src/agent-definitions/constants.ts
|
|
3770
4092
|
var GENERATED_HEADER_AGENT = "<!-- Generated by harness generate-agent-definitions. Do not edit. -->";
|
|
@@ -3776,11 +4098,11 @@ function computeSyncPlan(outputDir, rendered) {
|
|
|
3776
4098
|
const removed = [];
|
|
3777
4099
|
const unchanged = [];
|
|
3778
4100
|
for (const [filename, content] of rendered) {
|
|
3779
|
-
const filePath =
|
|
3780
|
-
if (!
|
|
4101
|
+
const filePath = path33.join(outputDir, filename);
|
|
4102
|
+
if (!fs21.existsSync(filePath)) {
|
|
3781
4103
|
added.push(filename);
|
|
3782
4104
|
} else {
|
|
3783
|
-
const existing =
|
|
4105
|
+
const existing = fs21.readFileSync(filePath, "utf-8");
|
|
3784
4106
|
if (existing === content) {
|
|
3785
4107
|
unchanged.push(filename);
|
|
3786
4108
|
} else {
|
|
@@ -3788,14 +4110,14 @@ function computeSyncPlan(outputDir, rendered) {
|
|
|
3788
4110
|
}
|
|
3789
4111
|
}
|
|
3790
4112
|
}
|
|
3791
|
-
if (
|
|
3792
|
-
const existing =
|
|
3793
|
-
const stat =
|
|
4113
|
+
if (fs21.existsSync(outputDir)) {
|
|
4114
|
+
const existing = fs21.readdirSync(outputDir).filter((f) => {
|
|
4115
|
+
const stat = fs21.statSync(path33.join(outputDir, f));
|
|
3794
4116
|
return stat.isFile();
|
|
3795
4117
|
});
|
|
3796
4118
|
for (const filename of existing) {
|
|
3797
4119
|
if (rendered.has(filename)) continue;
|
|
3798
|
-
const content =
|
|
4120
|
+
const content = fs21.readFileSync(path33.join(outputDir, filename), "utf-8");
|
|
3799
4121
|
if (content.includes(GENERATED_HEADER_CLAUDE) || content.includes(GENERATED_HEADER_GEMINI) || content.includes(GENERATED_HEADER_AGENT)) {
|
|
3800
4122
|
removed.push(filename);
|
|
3801
4123
|
}
|
|
@@ -3804,18 +4126,18 @@ function computeSyncPlan(outputDir, rendered) {
|
|
|
3804
4126
|
return { added, updated, removed, unchanged };
|
|
3805
4127
|
}
|
|
3806
4128
|
function applySyncPlan(outputDir, rendered, plan, deleteOrphans) {
|
|
3807
|
-
|
|
4129
|
+
fs21.mkdirSync(outputDir, { recursive: true });
|
|
3808
4130
|
for (const filename of [...plan.added, ...plan.updated]) {
|
|
3809
4131
|
const content = rendered.get(filename);
|
|
3810
4132
|
if (content !== void 0) {
|
|
3811
|
-
|
|
4133
|
+
fs21.writeFileSync(path33.join(outputDir, filename), content);
|
|
3812
4134
|
}
|
|
3813
4135
|
}
|
|
3814
4136
|
if (deleteOrphans) {
|
|
3815
4137
|
for (const filename of plan.removed) {
|
|
3816
|
-
const filePath =
|
|
3817
|
-
if (
|
|
3818
|
-
|
|
4138
|
+
const filePath = path33.join(outputDir, filename);
|
|
4139
|
+
if (fs21.existsSync(filePath)) {
|
|
4140
|
+
fs21.unlinkSync(filePath);
|
|
3819
4141
|
}
|
|
3820
4142
|
}
|
|
3821
4143
|
}
|
|
@@ -3824,24 +4146,24 @@ function applySyncPlan(outputDir, rendered, plan, deleteOrphans) {
|
|
|
3824
4146
|
// src/commands/generate-slash-commands.ts
|
|
3825
4147
|
function resolveOutputDir(platform, opts) {
|
|
3826
4148
|
if (opts.output) {
|
|
3827
|
-
return
|
|
4149
|
+
return path34.join(opts.output, "harness");
|
|
3828
4150
|
}
|
|
3829
4151
|
if (opts.global) {
|
|
3830
4152
|
const home = os2.homedir();
|
|
3831
|
-
return platform === "claude-code" ?
|
|
4153
|
+
return platform === "claude-code" ? path34.join(home, ".claude", "commands", "harness") : path34.join(home, ".gemini", "commands", "harness");
|
|
3832
4154
|
}
|
|
3833
|
-
return platform === "claude-code" ?
|
|
4155
|
+
return platform === "claude-code" ? path34.join("agents", "commands", "claude-code", "harness") : path34.join("agents", "commands", "gemini-cli", "harness");
|
|
3834
4156
|
}
|
|
3835
4157
|
function fileExtension(platform) {
|
|
3836
4158
|
return platform === "claude-code" ? ".md" : ".toml";
|
|
3837
4159
|
}
|
|
3838
4160
|
async function confirmDeletion(files) {
|
|
3839
4161
|
const rl = readline2.createInterface({ input: process.stdin, output: process.stdout });
|
|
3840
|
-
return new Promise((
|
|
4162
|
+
return new Promise((resolve24) => {
|
|
3841
4163
|
rl.question(`
|
|
3842
4164
|
Remove ${files.length} orphaned command(s)? (y/N) `, (answer) => {
|
|
3843
4165
|
rl.close();
|
|
3844
|
-
|
|
4166
|
+
resolve24(answer.toLowerCase() === "y");
|
|
3845
4167
|
});
|
|
3846
4168
|
});
|
|
3847
4169
|
}
|
|
@@ -3856,7 +4178,7 @@ function generateSlashCommands(opts) {
|
|
|
3856
4178
|
}
|
|
3857
4179
|
if (opts.includeGlobal || skillSources.length === 0) {
|
|
3858
4180
|
const globalDir = resolveGlobalSkillsDir();
|
|
3859
|
-
if (!projectDir ||
|
|
4181
|
+
if (!projectDir || path34.resolve(globalDir) !== path34.resolve(projectDir)) {
|
|
3860
4182
|
skillSources.push({ dir: globalDir, source: "global" });
|
|
3861
4183
|
}
|
|
3862
4184
|
}
|
|
@@ -3878,7 +4200,7 @@ function generateSlashCommands(opts) {
|
|
|
3878
4200
|
executionContext: spec.prompt.executionContext.split("\n").map((line) => {
|
|
3879
4201
|
if (line.startsWith("@")) {
|
|
3880
4202
|
const relPath = line.slice(1);
|
|
3881
|
-
return `@${
|
|
4203
|
+
return `@${path34.resolve(relPath)}`;
|
|
3882
4204
|
}
|
|
3883
4205
|
return line;
|
|
3884
4206
|
}).join("\n")
|
|
@@ -3886,10 +4208,10 @@ function generateSlashCommands(opts) {
|
|
|
3886
4208
|
} : spec;
|
|
3887
4209
|
rendered.set(filename, renderClaudeCode(renderSpec));
|
|
3888
4210
|
} else {
|
|
3889
|
-
const mdPath =
|
|
3890
|
-
const yamlPath =
|
|
3891
|
-
const mdContent =
|
|
3892
|
-
const yamlContent =
|
|
4211
|
+
const mdPath = path34.join(spec.skillsBaseDir, spec.sourceDir, "SKILL.md");
|
|
4212
|
+
const yamlPath = path34.join(spec.skillsBaseDir, spec.sourceDir, "skill.yaml");
|
|
4213
|
+
const mdContent = fs22.existsSync(mdPath) ? fs22.readFileSync(mdPath, "utf-8") : "";
|
|
4214
|
+
const yamlContent = fs22.existsSync(yamlPath) ? fs22.readFileSync(yamlPath, "utf-8") : "";
|
|
3893
4215
|
rendered.set(filename, renderGemini(spec, mdContent, yamlContent));
|
|
3894
4216
|
}
|
|
3895
4217
|
}
|
|
@@ -3915,9 +4237,9 @@ async function handleOrphanDeletion(results, opts) {
|
|
|
3915
4237
|
const shouldDelete = opts.yes || await confirmDeletion(result.removed);
|
|
3916
4238
|
if (shouldDelete) {
|
|
3917
4239
|
for (const filename of result.removed) {
|
|
3918
|
-
const filePath =
|
|
3919
|
-
if (
|
|
3920
|
-
|
|
4240
|
+
const filePath = path34.join(result.outputDir, filename);
|
|
4241
|
+
if (fs22.existsSync(filePath)) {
|
|
4242
|
+
fs22.unlinkSync(filePath);
|
|
3921
4243
|
}
|
|
3922
4244
|
}
|
|
3923
4245
|
}
|
|
@@ -3993,7 +4315,6 @@ import { Command as Command35 } from "commander";
|
|
|
3993
4315
|
|
|
3994
4316
|
// src/commands/ci/check.ts
|
|
3995
4317
|
import { Command as Command33 } from "commander";
|
|
3996
|
-
import { runCIChecks } from "@harness-engineering/core";
|
|
3997
4318
|
var VALID_CHECKS = ["validate", "deps", "docs", "entropy", "phase-gate"];
|
|
3998
4319
|
async function runCICheck(options) {
|
|
3999
4320
|
const configResult = resolveConfig(options.configPath);
|
|
@@ -4069,9 +4390,8 @@ function createCheckCommand() {
|
|
|
4069
4390
|
|
|
4070
4391
|
// src/commands/ci/init.ts
|
|
4071
4392
|
import { Command as Command34 } from "commander";
|
|
4072
|
-
import * as
|
|
4073
|
-
import * as
|
|
4074
|
-
import { Ok as Ok19, Err as Err14 } from "@harness-engineering/core";
|
|
4393
|
+
import * as fs23 from "fs";
|
|
4394
|
+
import * as path35 from "path";
|
|
4075
4395
|
var ALL_CHECKS = ["validate", "deps", "docs", "entropy", "phase-gate"];
|
|
4076
4396
|
function buildSkipFlag(checks) {
|
|
4077
4397
|
if (!checks) return "";
|
|
@@ -4155,16 +4475,16 @@ function generateCIConfig(options) {
|
|
|
4155
4475
|
};
|
|
4156
4476
|
const entry = generators[platform];
|
|
4157
4477
|
if (!entry) {
|
|
4158
|
-
return
|
|
4478
|
+
return Err(new CLIError(`Unknown platform: ${platform}`, ExitCode.ERROR));
|
|
4159
4479
|
}
|
|
4160
|
-
return
|
|
4480
|
+
return Ok({
|
|
4161
4481
|
filename: entry.filename,
|
|
4162
4482
|
content: entry.generate(skipFlag)
|
|
4163
4483
|
});
|
|
4164
4484
|
}
|
|
4165
4485
|
function detectPlatform() {
|
|
4166
|
-
if (
|
|
4167
|
-
if (
|
|
4486
|
+
if (fs23.existsSync(".github")) return "github";
|
|
4487
|
+
if (fs23.existsSync(".gitlab-ci.yml")) return "gitlab";
|
|
4168
4488
|
return null;
|
|
4169
4489
|
}
|
|
4170
4490
|
function createInitCommand2() {
|
|
@@ -4180,12 +4500,12 @@ function createInitCommand2() {
|
|
|
4180
4500
|
process.exit(result.error.exitCode);
|
|
4181
4501
|
}
|
|
4182
4502
|
const { filename, content } = result.value;
|
|
4183
|
-
const targetPath =
|
|
4184
|
-
const dir =
|
|
4185
|
-
|
|
4186
|
-
|
|
4503
|
+
const targetPath = path35.resolve(filename);
|
|
4504
|
+
const dir = path35.dirname(targetPath);
|
|
4505
|
+
fs23.mkdirSync(dir, { recursive: true });
|
|
4506
|
+
fs23.writeFileSync(targetPath, content);
|
|
4187
4507
|
if (platform === "generic") {
|
|
4188
|
-
|
|
4508
|
+
fs23.chmodSync(targetPath, "755");
|
|
4189
4509
|
}
|
|
4190
4510
|
if (globalOpts.json) {
|
|
4191
4511
|
console.log(JSON.stringify({ file: filename, platform }));
|
|
@@ -4263,10 +4583,10 @@ function prompt(question) {
|
|
|
4263
4583
|
input: process.stdin,
|
|
4264
4584
|
output: process.stdout
|
|
4265
4585
|
});
|
|
4266
|
-
return new Promise((
|
|
4586
|
+
return new Promise((resolve24) => {
|
|
4267
4587
|
rl.question(question, (answer) => {
|
|
4268
4588
|
rl.close();
|
|
4269
|
-
|
|
4589
|
+
resolve24(answer.trim().toLowerCase());
|
|
4270
4590
|
});
|
|
4271
4591
|
});
|
|
4272
4592
|
}
|
|
@@ -4341,8 +4661,8 @@ function createUpdateCommand() {
|
|
|
4341
4661
|
|
|
4342
4662
|
// src/commands/generate-agent-definitions.ts
|
|
4343
4663
|
import { Command as Command37 } from "commander";
|
|
4344
|
-
import * as
|
|
4345
|
-
import * as
|
|
4664
|
+
import * as fs24 from "fs";
|
|
4665
|
+
import * as path36 from "path";
|
|
4346
4666
|
import * as os3 from "os";
|
|
4347
4667
|
|
|
4348
4668
|
// src/agent-definitions/generator.ts
|
|
@@ -4478,19 +4798,19 @@ function renderGeminiAgent(def) {
|
|
|
4478
4798
|
// src/commands/generate-agent-definitions.ts
|
|
4479
4799
|
function resolveOutputDir2(platform, opts) {
|
|
4480
4800
|
if (opts.output) {
|
|
4481
|
-
return platform === "claude-code" ?
|
|
4801
|
+
return platform === "claude-code" ? path36.join(opts.output, "claude-code") : path36.join(opts.output, "gemini-cli");
|
|
4482
4802
|
}
|
|
4483
4803
|
if (opts.global) {
|
|
4484
4804
|
const home = os3.homedir();
|
|
4485
|
-
return platform === "claude-code" ?
|
|
4805
|
+
return platform === "claude-code" ? path36.join(home, ".claude", "agents") : path36.join(home, ".gemini", "agents");
|
|
4486
4806
|
}
|
|
4487
|
-
return platform === "claude-code" ?
|
|
4807
|
+
return platform === "claude-code" ? path36.join("agents", "agents", "claude-code") : path36.join("agents", "agents", "gemini-cli");
|
|
4488
4808
|
}
|
|
4489
4809
|
function loadSkillContent(skillName) {
|
|
4490
4810
|
const skillsDir = resolveSkillsDir();
|
|
4491
|
-
const skillMdPath =
|
|
4492
|
-
if (!
|
|
4493
|
-
return
|
|
4811
|
+
const skillMdPath = path36.join(skillsDir, skillName, "SKILL.md");
|
|
4812
|
+
if (!fs24.existsSync(skillMdPath)) return null;
|
|
4813
|
+
return fs24.readFileSync(skillMdPath, "utf-8");
|
|
4494
4814
|
}
|
|
4495
4815
|
function getRenderer(platform) {
|
|
4496
4816
|
return platform === "claude-code" ? renderClaudeCodeAgent : renderGeminiAgent;
|
|
@@ -4644,9 +4964,9 @@ function createGenerateCommand3() {
|
|
|
4644
4964
|
|
|
4645
4965
|
// src/commands/graph/scan.ts
|
|
4646
4966
|
import { Command as Command39 } from "commander";
|
|
4647
|
-
import * as
|
|
4967
|
+
import * as path37 from "path";
|
|
4648
4968
|
async function runScan(projectPath) {
|
|
4649
|
-
const { GraphStore, CodeIngestor, TopologicalLinker, KnowledgeIngestor, GitIngestor } = await import("
|
|
4969
|
+
const { GraphStore, CodeIngestor, TopologicalLinker, KnowledgeIngestor, GitIngestor } = await import("./dist-4MYPT3OE.js");
|
|
4650
4970
|
const store = new GraphStore();
|
|
4651
4971
|
const start = Date.now();
|
|
4652
4972
|
await new CodeIngestor(store).ingest(projectPath);
|
|
@@ -4657,13 +4977,13 @@ async function runScan(projectPath) {
|
|
|
4657
4977
|
await new GitIngestor(store).ingest(projectPath);
|
|
4658
4978
|
} catch {
|
|
4659
4979
|
}
|
|
4660
|
-
const graphDir =
|
|
4980
|
+
const graphDir = path37.join(projectPath, ".harness", "graph");
|
|
4661
4981
|
await store.save(graphDir);
|
|
4662
4982
|
return { nodeCount: store.nodeCount, edgeCount: store.edgeCount, durationMs: Date.now() - start };
|
|
4663
4983
|
}
|
|
4664
4984
|
function createScanCommand() {
|
|
4665
4985
|
return new Command39("scan").description("Scan project and build knowledge graph").argument("[path]", "Project root path", ".").action(async (inputPath, _opts, cmd) => {
|
|
4666
|
-
const projectPath =
|
|
4986
|
+
const projectPath = path37.resolve(inputPath);
|
|
4667
4987
|
const globalOpts = cmd.optsWithGlobals();
|
|
4668
4988
|
try {
|
|
4669
4989
|
const result = await runScan(projectPath);
|
|
@@ -4683,12 +5003,12 @@ function createScanCommand() {
|
|
|
4683
5003
|
|
|
4684
5004
|
// src/commands/graph/ingest.ts
|
|
4685
5005
|
import { Command as Command40 } from "commander";
|
|
4686
|
-
import * as
|
|
5006
|
+
import * as path38 from "path";
|
|
4687
5007
|
async function loadConnectorConfig(projectPath, source) {
|
|
4688
5008
|
try {
|
|
4689
|
-
const
|
|
4690
|
-
const configPath =
|
|
4691
|
-
const config = JSON.parse(await
|
|
5009
|
+
const fs25 = await import("fs/promises");
|
|
5010
|
+
const configPath = path38.join(projectPath, "harness.config.json");
|
|
5011
|
+
const config = JSON.parse(await fs25.readFile(configPath, "utf-8"));
|
|
4692
5012
|
const connector = config.graph?.connectors?.find(
|
|
4693
5013
|
(c) => c.source === source
|
|
4694
5014
|
);
|
|
@@ -4727,8 +5047,8 @@ async function runIngest(projectPath, source, opts) {
|
|
|
4727
5047
|
SyncManager,
|
|
4728
5048
|
JiraConnector,
|
|
4729
5049
|
SlackConnector
|
|
4730
|
-
} = await import("
|
|
4731
|
-
const graphDir =
|
|
5050
|
+
} = await import("./dist-4MYPT3OE.js");
|
|
5051
|
+
const graphDir = path38.join(projectPath, ".harness", "graph");
|
|
4732
5052
|
const store = new GraphStore();
|
|
4733
5053
|
await store.load(graphDir);
|
|
4734
5054
|
if (opts?.all) {
|
|
@@ -4795,7 +5115,7 @@ function createIngestCommand() {
|
|
|
4795
5115
|
process.exit(1);
|
|
4796
5116
|
}
|
|
4797
5117
|
const globalOpts = cmd.optsWithGlobals();
|
|
4798
|
-
const projectPath =
|
|
5118
|
+
const projectPath = path38.resolve(globalOpts.config ? path38.dirname(globalOpts.config) : ".");
|
|
4799
5119
|
try {
|
|
4800
5120
|
const result = await runIngest(projectPath, opts.source ?? "", {
|
|
4801
5121
|
full: opts.full,
|
|
@@ -4818,11 +5138,11 @@ function createIngestCommand() {
|
|
|
4818
5138
|
|
|
4819
5139
|
// src/commands/graph/query.ts
|
|
4820
5140
|
import { Command as Command41 } from "commander";
|
|
4821
|
-
import * as
|
|
5141
|
+
import * as path39 from "path";
|
|
4822
5142
|
async function runQuery(projectPath, rootNodeId, opts) {
|
|
4823
|
-
const { GraphStore, ContextQL } = await import("
|
|
5143
|
+
const { GraphStore, ContextQL } = await import("./dist-4MYPT3OE.js");
|
|
4824
5144
|
const store = new GraphStore();
|
|
4825
|
-
const graphDir =
|
|
5145
|
+
const graphDir = path39.join(projectPath, ".harness", "graph");
|
|
4826
5146
|
const loaded = await store.load(graphDir);
|
|
4827
5147
|
if (!loaded) throw new Error("No graph found. Run `harness scan` first.");
|
|
4828
5148
|
const params = {
|
|
@@ -4838,7 +5158,7 @@ async function runQuery(projectPath, rootNodeId, opts) {
|
|
|
4838
5158
|
function createQueryCommand() {
|
|
4839
5159
|
return new Command41("query").description("Query the knowledge graph").argument("<rootNodeId>", "Starting node ID").option("--depth <n>", "Max traversal depth", "3").option("--types <types>", "Comma-separated node types to include").option("--edges <edges>", "Comma-separated edge types to include").option("--bidirectional", "Traverse both directions").action(async (rootNodeId, opts, cmd) => {
|
|
4840
5160
|
const globalOpts = cmd.optsWithGlobals();
|
|
4841
|
-
const projectPath =
|
|
5161
|
+
const projectPath = path39.resolve(globalOpts.config ? path39.dirname(globalOpts.config) : ".");
|
|
4842
5162
|
try {
|
|
4843
5163
|
const result = await runQuery(projectPath, rootNodeId, {
|
|
4844
5164
|
depth: parseInt(opts.depth),
|
|
@@ -4867,18 +5187,18 @@ function createQueryCommand() {
|
|
|
4867
5187
|
import { Command as Command42 } from "commander";
|
|
4868
5188
|
|
|
4869
5189
|
// src/commands/graph/status.ts
|
|
4870
|
-
import * as
|
|
5190
|
+
import * as path40 from "path";
|
|
4871
5191
|
async function runGraphStatus(projectPath) {
|
|
4872
|
-
const { GraphStore } = await import("
|
|
4873
|
-
const graphDir =
|
|
5192
|
+
const { GraphStore } = await import("./dist-4MYPT3OE.js");
|
|
5193
|
+
const graphDir = path40.join(projectPath, ".harness", "graph");
|
|
4874
5194
|
const store = new GraphStore();
|
|
4875
5195
|
const loaded = await store.load(graphDir);
|
|
4876
5196
|
if (!loaded) return { status: "no_graph", message: "No graph found. Run `harness scan` first." };
|
|
4877
|
-
const
|
|
4878
|
-
const metaPath =
|
|
5197
|
+
const fs25 = await import("fs/promises");
|
|
5198
|
+
const metaPath = path40.join(graphDir, "metadata.json");
|
|
4879
5199
|
let lastScan = "unknown";
|
|
4880
5200
|
try {
|
|
4881
|
-
const meta = JSON.parse(await
|
|
5201
|
+
const meta = JSON.parse(await fs25.readFile(metaPath, "utf-8"));
|
|
4882
5202
|
lastScan = meta.lastScanTimestamp;
|
|
4883
5203
|
} catch {
|
|
4884
5204
|
}
|
|
@@ -4889,8 +5209,8 @@ async function runGraphStatus(projectPath) {
|
|
|
4889
5209
|
}
|
|
4890
5210
|
let connectorSyncStatus = {};
|
|
4891
5211
|
try {
|
|
4892
|
-
const syncMetaPath =
|
|
4893
|
-
const syncMeta = JSON.parse(await
|
|
5212
|
+
const syncMetaPath = path40.join(graphDir, "sync-metadata.json");
|
|
5213
|
+
const syncMeta = JSON.parse(await fs25.readFile(syncMetaPath, "utf-8"));
|
|
4894
5214
|
for (const [name, data] of Object.entries(syncMeta.connectors ?? {})) {
|
|
4895
5215
|
connectorSyncStatus[name] = data.lastSyncTimestamp;
|
|
4896
5216
|
}
|
|
@@ -4907,10 +5227,10 @@ async function runGraphStatus(projectPath) {
|
|
|
4907
5227
|
}
|
|
4908
5228
|
|
|
4909
5229
|
// src/commands/graph/export.ts
|
|
4910
|
-
import * as
|
|
5230
|
+
import * as path41 from "path";
|
|
4911
5231
|
async function runGraphExport(projectPath, format) {
|
|
4912
|
-
const { GraphStore } = await import("
|
|
4913
|
-
const graphDir =
|
|
5232
|
+
const { GraphStore } = await import("./dist-4MYPT3OE.js");
|
|
5233
|
+
const graphDir = path41.join(projectPath, ".harness", "graph");
|
|
4914
5234
|
const store = new GraphStore();
|
|
4915
5235
|
const loaded = await store.load(graphDir);
|
|
4916
5236
|
if (!loaded) throw new Error("No graph found. Run `harness scan` first.");
|
|
@@ -4939,13 +5259,13 @@ async function runGraphExport(projectPath, format) {
|
|
|
4939
5259
|
}
|
|
4940
5260
|
|
|
4941
5261
|
// src/commands/graph/index.ts
|
|
4942
|
-
import * as
|
|
5262
|
+
import * as path42 from "path";
|
|
4943
5263
|
function createGraphCommand() {
|
|
4944
5264
|
const graph = new Command42("graph").description("Knowledge graph management");
|
|
4945
5265
|
graph.command("status").description("Show graph statistics").action(async (_opts, cmd) => {
|
|
4946
5266
|
try {
|
|
4947
5267
|
const globalOpts = cmd.optsWithGlobals();
|
|
4948
|
-
const projectPath =
|
|
5268
|
+
const projectPath = path42.resolve(globalOpts.config ? path42.dirname(globalOpts.config) : ".");
|
|
4949
5269
|
const result = await runGraphStatus(projectPath);
|
|
4950
5270
|
if (globalOpts.json) {
|
|
4951
5271
|
console.log(JSON.stringify(result, null, 2));
|
|
@@ -4972,7 +5292,7 @@ function createGraphCommand() {
|
|
|
4972
5292
|
});
|
|
4973
5293
|
graph.command("export").description("Export graph").requiredOption("--format <format>", "Output format (json, mermaid)").action(async (opts, cmd) => {
|
|
4974
5294
|
const globalOpts = cmd.optsWithGlobals();
|
|
4975
|
-
const projectPath =
|
|
5295
|
+
const projectPath = path42.resolve(globalOpts.config ? path42.dirname(globalOpts.config) : ".");
|
|
4976
5296
|
try {
|
|
4977
5297
|
const output = await runGraphExport(projectPath, opts.format);
|
|
4978
5298
|
console.log(output);
|