@harness-engineering/cli 1.24.1 → 1.24.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{agents-md-MMVRF77L.js → agents-md-PBKKTSQY.js} +1 -1
- package/dist/{architecture-JAEDPYQZ.js → architecture-FBSLURIB.js} +3 -3
- package/dist/{assess-project-7JZCVM7D.js → assess-project-74UVWPMB.js} +1 -1
- package/dist/bin/harness-mcp.js +13 -13
- package/dist/bin/harness.js +18 -17
- package/dist/{check-phase-gate-YQHAH4LL.js → check-phase-gate-WY6UICCL.js} +3 -3
- package/dist/{chunk-MS6KDQW7.js → chunk-3VYWO6QN.js} +6 -6
- package/dist/chunk-3XAPHB2Z.js +43 -0
- package/dist/{chunk-AVVJ5EUU.js → chunk-5PMRARB5.js} +1 -1
- package/dist/{chunk-NU3BPEDR.js → chunk-A737JDL4.js} +2 -2
- package/dist/{chunk-IDZNPTYD.js → chunk-EPUKTTJZ.js} +6 -1
- package/dist/{chunk-2DAMN7ED.js → chunk-FJYP32IV.js} +6 -6
- package/dist/{chunk-PCWYYFBP.js → chunk-H4U2QNY2.js} +32 -6
- package/dist/{chunk-C5FTJ3EU.js → chunk-L5UONZ53.js} +58 -1
- package/dist/{chunk-LY3YVKXL.js → chunk-O6UF33QH.js} +351 -104
- package/dist/{chunk-W2OZK3KC.js → chunk-SPTKLCKC.js} +1 -1
- package/dist/{chunk-TUJAHI22.js → chunk-TB427QOK.js} +5 -5
- package/dist/{chunk-HVFCCARH.js → chunk-UFQQBGC3.js} +1 -1
- package/dist/{chunk-JGMCHJ6B.js → chunk-V2FGX2KD.js} +3 -3
- package/dist/{chunk-JT732X6S.js → chunk-WEN5Z7CL.js} +2 -2
- package/dist/{chunk-BWWNQPPO.js → chunk-YPKKEP3O.js} +4 -4
- package/dist/{chunk-ZKVLBOYA.js → chunk-ZAMT24QN.js} +426 -383
- package/dist/{ci-workflow-HDH4LCOF.js → ci-workflow-QZRHAIO2.js} +1 -1
- package/dist/{dist-TZQUURSP.js → dist-7EBSGAHX.js} +3 -1
- package/dist/{docs-6SPJYTRR.js → docs-H34GBVRS.js} +4 -4
- package/dist/{engine-CSITRE3J.js → engine-VUQEAJFZ.js} +1 -1
- package/dist/{entropy-FZP643BK.js → entropy-ZAY73R6A.js} +3 -3
- package/dist/{feedback-RSPUJCJJ.js → feedback-TMEGYMWU.js} +2 -2
- package/dist/{generate-agent-definitions-OLYWXTYH.js → generate-agent-definitions-PQPG6SX5.js} +1 -1
- package/dist/index.d.ts +8 -8
- package/dist/index.js +23 -21
- package/dist/{loader-P2P7CV2A.js → loader-Y6A42WBD.js} +1 -1
- package/dist/{mcp-JZG22CYT.js → mcp-LCHC4NZ5.js} +13 -13
- package/dist/{performance-VVWOIJTE.js → performance-N67YJJDG.js} +4 -4
- package/dist/{review-pipeline-XSRO7HYZ.js → review-pipeline-YXF5ITL2.js} +4 -1
- package/dist/{runtime-VMOIIP6B.js → runtime-XNJUJCSG.js} +1 -1
- package/dist/scan-U67OKDRS.js +8 -0
- package/dist/{security-4AQ46P3H.js → security-L2YN3CTI.js} +1 -1
- package/dist/{validate-T7UTXLEQ.js → validate-MNE25KLZ.js} +2 -2
- package/dist/{validate-cross-check-M4NP2UF5.js → validate-cross-check-Y4PDR63C.js} +1 -1
- package/package.json +5 -5
|
@@ -1,6 +1,12 @@
|
|
|
1
|
+
import {
|
|
2
|
+
generateAgentsMd
|
|
3
|
+
} from "./chunk-I4QR3HNH.js";
|
|
1
4
|
import {
|
|
2
5
|
generateCIWorkflow
|
|
3
6
|
} from "./chunk-LOUH2LIC.js";
|
|
7
|
+
import {
|
|
8
|
+
TemplateEngine
|
|
9
|
+
} from "./chunk-FP53DDB5.js";
|
|
4
10
|
import {
|
|
5
11
|
generate,
|
|
6
12
|
validate
|
|
@@ -8,9 +14,6 @@ import {
|
|
|
8
14
|
import {
|
|
9
15
|
generateRuntime
|
|
10
16
|
} from "./chunk-LM5Z2WCA.js";
|
|
11
|
-
import {
|
|
12
|
-
generateAgentsMd
|
|
13
|
-
} from "./chunk-I4QR3HNH.js";
|
|
14
17
|
import {
|
|
15
18
|
runPersona
|
|
16
19
|
} from "./chunk-GISMXMVL.js";
|
|
@@ -25,7 +28,7 @@ import {
|
|
|
25
28
|
OutputMode,
|
|
26
29
|
createCheckPhaseGateCommand,
|
|
27
30
|
findFiles
|
|
28
|
-
} from "./chunk-
|
|
31
|
+
} from "./chunk-SPTKLCKC.js";
|
|
29
32
|
import {
|
|
30
33
|
createGenerateAgentDefinitionsCommand,
|
|
31
34
|
generateAgentDefinitions
|
|
@@ -38,8 +41,8 @@ import {
|
|
|
38
41
|
loadPersona
|
|
39
42
|
} from "./chunk-YDRB55Q4.js";
|
|
40
43
|
import {
|
|
41
|
-
|
|
42
|
-
} from "./chunk-
|
|
44
|
+
createScanCommand
|
|
45
|
+
} from "./chunk-3XAPHB2Z.js";
|
|
43
46
|
import {
|
|
44
47
|
appendFrameworkAgents,
|
|
45
48
|
captureHealthSnapshot,
|
|
@@ -53,11 +56,11 @@ import {
|
|
|
53
56
|
loadOrRebuildIndex,
|
|
54
57
|
persistToolingConfig,
|
|
55
58
|
recommend
|
|
56
|
-
} from "./chunk-
|
|
59
|
+
} from "./chunk-O6UF33QH.js";
|
|
57
60
|
import {
|
|
58
61
|
findConfigFile,
|
|
59
62
|
resolveConfig
|
|
60
|
-
} from "./chunk-
|
|
63
|
+
} from "./chunk-5PMRARB5.js";
|
|
61
64
|
import {
|
|
62
65
|
VALID_PLATFORMS
|
|
63
66
|
} from "./chunk-3ISINLYT.js";
|
|
@@ -137,7 +140,7 @@ import {
|
|
|
137
140
|
validateKnowledgeMap,
|
|
138
141
|
writeConfig,
|
|
139
142
|
writeLockfile
|
|
140
|
-
} from "./chunk-
|
|
143
|
+
} from "./chunk-H4U2QNY2.js";
|
|
141
144
|
import {
|
|
142
145
|
Err,
|
|
143
146
|
Ok
|
|
@@ -158,7 +161,7 @@ import {
|
|
|
158
161
|
} from "./chunk-3WGJMBKH.js";
|
|
159
162
|
|
|
160
163
|
// src/index.ts
|
|
161
|
-
import { Command as
|
|
164
|
+
import { Command as Command79 } from "commander";
|
|
162
165
|
|
|
163
166
|
// src/commands/add.ts
|
|
164
167
|
import { Command } from "commander";
|
|
@@ -346,7 +349,7 @@ function registerSkillsCommand(adoption) {
|
|
|
346
349
|
const globalOpts = cmd.optsWithGlobals();
|
|
347
350
|
const limit = Math.max(parseInt(opts.limit, 10) || 20, 1);
|
|
348
351
|
const cwd = process.cwd();
|
|
349
|
-
const { readAdoptionRecords, aggregateBySkill } = await import("./dist-
|
|
352
|
+
const { readAdoptionRecords, aggregateBySkill } = await import("./dist-7EBSGAHX.js");
|
|
350
353
|
const records = readAdoptionRecords(cwd);
|
|
351
354
|
if (records.length === 0) {
|
|
352
355
|
if (globalOpts.json) {
|
|
@@ -382,7 +385,7 @@ function registerRecentCommand(adoption) {
|
|
|
382
385
|
const globalOpts = cmd.optsWithGlobals();
|
|
383
386
|
const limit = Math.max(parseInt(opts.limit, 10) || 20, 1);
|
|
384
387
|
const cwd = process.cwd();
|
|
385
|
-
const { readAdoptionRecords } = await import("./dist-
|
|
388
|
+
const { readAdoptionRecords } = await import("./dist-7EBSGAHX.js");
|
|
386
389
|
const records = readAdoptionRecords(cwd);
|
|
387
390
|
if (records.length === 0) {
|
|
388
391
|
if (globalOpts.json) {
|
|
@@ -419,7 +422,7 @@ function registerSkillCommand(adoption) {
|
|
|
419
422
|
adoption.command("skill <name>").description("Show detail for a specific skill").action(async (name, _opts, cmd) => {
|
|
420
423
|
const globalOpts = cmd.optsWithGlobals();
|
|
421
424
|
const cwd = process.cwd();
|
|
422
|
-
const { readAdoptionRecords, aggregateBySkill } = await import("./dist-
|
|
425
|
+
const { readAdoptionRecords, aggregateBySkill } = await import("./dist-7EBSGAHX.js");
|
|
423
426
|
const records = readAdoptionRecords(cwd);
|
|
424
427
|
const skillRecords = records.filter((r) => r.skill === name);
|
|
425
428
|
if (skillRecords.length === 0) {
|
|
@@ -1899,7 +1902,7 @@ function runDashboard(opts) {
|
|
|
1899
1902
|
const clientPort = Number(opts.port ?? DEFAULT_CLIENT_PORT);
|
|
1900
1903
|
const apiPort = Number(opts.apiPort ?? DEFAULT_API_PORT);
|
|
1901
1904
|
const projectPath = resolve10(opts.cwd ?? process.cwd());
|
|
1902
|
-
const url = `http://localhost:${
|
|
1905
|
+
const url = `http://localhost:${apiPort}`;
|
|
1903
1906
|
const server = resolveServerScript();
|
|
1904
1907
|
if (!server) {
|
|
1905
1908
|
console.error("Could not locate the dashboard server. Run `pnpm build` in packages/dashboard.");
|
|
@@ -2495,7 +2498,7 @@ function createGenerateCommand() {
|
|
|
2495
2498
|
}
|
|
2496
2499
|
|
|
2497
2500
|
// src/commands/graph/index.ts
|
|
2498
|
-
import { Command as
|
|
2501
|
+
import { Command as Command23 } from "commander";
|
|
2499
2502
|
|
|
2500
2503
|
// src/commands/graph/status.ts
|
|
2501
2504
|
import * as path14 from "path";
|
|
@@ -2570,54 +2573,15 @@ async function runGraphExport(projectPath, format) {
|
|
|
2570
2573
|
}
|
|
2571
2574
|
|
|
2572
2575
|
// src/commands/graph/index.ts
|
|
2573
|
-
import * as
|
|
2576
|
+
import * as path18 from "path";
|
|
2574
2577
|
|
|
2575
|
-
// src/commands/graph/
|
|
2578
|
+
// src/commands/graph/query.ts
|
|
2576
2579
|
import { Command as Command21 } from "commander";
|
|
2577
2580
|
import * as path16 from "path";
|
|
2578
|
-
async function runScan(projectPath) {
|
|
2579
|
-
const { GraphStore, CodeIngestor, TopologicalLinker, KnowledgeIngestor, GitIngestor } = await import("./dist-QW5G3GX3.js");
|
|
2580
|
-
const store = new GraphStore();
|
|
2581
|
-
const start = Date.now();
|
|
2582
|
-
await new CodeIngestor(store).ingest(projectPath);
|
|
2583
|
-
new TopologicalLinker(store).link();
|
|
2584
|
-
const knowledgeIngestor = new KnowledgeIngestor(store);
|
|
2585
|
-
await knowledgeIngestor.ingestAll(projectPath);
|
|
2586
|
-
try {
|
|
2587
|
-
await new GitIngestor(store).ingest(projectPath);
|
|
2588
|
-
} catch {
|
|
2589
|
-
}
|
|
2590
|
-
const graphDir = path16.join(projectPath, ".harness", "graph");
|
|
2591
|
-
await store.save(graphDir);
|
|
2592
|
-
return { nodeCount: store.nodeCount, edgeCount: store.edgeCount, durationMs: Date.now() - start };
|
|
2593
|
-
}
|
|
2594
|
-
function createScanCommand() {
|
|
2595
|
-
return new Command21("scan").description("Scan project and build knowledge graph").argument("[path]", "Project root path", ".").action(async (inputPath, _opts, cmd) => {
|
|
2596
|
-
const projectPath = path16.resolve(inputPath);
|
|
2597
|
-
const globalOpts = cmd.optsWithGlobals();
|
|
2598
|
-
try {
|
|
2599
|
-
const result = await runScan(projectPath);
|
|
2600
|
-
if (globalOpts.json) {
|
|
2601
|
-
console.log(JSON.stringify(result));
|
|
2602
|
-
} else {
|
|
2603
|
-
console.log(
|
|
2604
|
-
`Graph built: ${result.nodeCount} nodes, ${result.edgeCount} edges (${result.durationMs}ms)`
|
|
2605
|
-
);
|
|
2606
|
-
}
|
|
2607
|
-
} catch (err) {
|
|
2608
|
-
console.error("Scan failed:", err instanceof Error ? err.message : err);
|
|
2609
|
-
process.exit(2);
|
|
2610
|
-
}
|
|
2611
|
-
});
|
|
2612
|
-
}
|
|
2613
|
-
|
|
2614
|
-
// src/commands/graph/query.ts
|
|
2615
|
-
import { Command as Command22 } from "commander";
|
|
2616
|
-
import * as path17 from "path";
|
|
2617
2581
|
async function runQuery(projectPath, rootNodeId, opts) {
|
|
2618
2582
|
const { GraphStore, ContextQL } = await import("./dist-QW5G3GX3.js");
|
|
2619
2583
|
const store = new GraphStore();
|
|
2620
|
-
const graphDir =
|
|
2584
|
+
const graphDir = path16.join(projectPath, ".harness", "graph");
|
|
2621
2585
|
const loaded = await store.load(graphDir);
|
|
2622
2586
|
if (!loaded) throw new Error("No graph found. Run `harness scan` first.");
|
|
2623
2587
|
const params = {
|
|
@@ -2639,7 +2603,7 @@ function printQueryResult(result) {
|
|
|
2639
2603
|
}
|
|
2640
2604
|
}
|
|
2641
2605
|
async function runQueryAction(rootNodeId, opts, globalOpts) {
|
|
2642
|
-
const projectPath =
|
|
2606
|
+
const projectPath = path16.resolve(globalOpts.config ? path16.dirname(globalOpts.config) : ".");
|
|
2643
2607
|
try {
|
|
2644
2608
|
const result = await runQuery(projectPath, rootNodeId, {
|
|
2645
2609
|
depth: parseInt(opts.depth),
|
|
@@ -2658,18 +2622,18 @@ async function runQueryAction(rootNodeId, opts, globalOpts) {
|
|
|
2658
2622
|
}
|
|
2659
2623
|
}
|
|
2660
2624
|
function createQueryCommand() {
|
|
2661
|
-
return new
|
|
2625
|
+
return new Command21("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) => {
|
|
2662
2626
|
await runQueryAction(rootNodeId, opts, cmd.optsWithGlobals());
|
|
2663
2627
|
});
|
|
2664
2628
|
}
|
|
2665
2629
|
|
|
2666
2630
|
// src/commands/graph/ingest.ts
|
|
2667
|
-
import { Command as
|
|
2668
|
-
import * as
|
|
2631
|
+
import { Command as Command22 } from "commander";
|
|
2632
|
+
import * as path17 from "path";
|
|
2669
2633
|
async function loadConnectorConfig(projectPath, source) {
|
|
2670
2634
|
try {
|
|
2671
2635
|
const fs36 = await import("fs/promises");
|
|
2672
|
-
const configPath =
|
|
2636
|
+
const configPath = path17.join(projectPath, "harness.config.json");
|
|
2673
2637
|
const config = JSON.parse(await fs36.readFile(configPath, "utf-8"));
|
|
2674
2638
|
const connector = config.graph?.connectors?.[source];
|
|
2675
2639
|
return connector ?? {};
|
|
@@ -2710,7 +2674,7 @@ async function runIngest(projectPath, source, opts) {
|
|
|
2710
2674
|
CIConnector,
|
|
2711
2675
|
ConfluenceConnector
|
|
2712
2676
|
} = await import("./dist-QW5G3GX3.js");
|
|
2713
|
-
const graphDir =
|
|
2677
|
+
const graphDir = path17.join(projectPath, ".harness", "graph");
|
|
2714
2678
|
const store = new GraphStore();
|
|
2715
2679
|
await store.load(graphDir);
|
|
2716
2680
|
if (opts?.all) {
|
|
@@ -2777,7 +2741,7 @@ async function runIngest(projectPath, source, opts) {
|
|
|
2777
2741
|
return result;
|
|
2778
2742
|
}
|
|
2779
2743
|
function createIngestCommand() {
|
|
2780
|
-
return new
|
|
2744
|
+
return new Command22("ingest").description("Ingest data into the knowledge graph").option(
|
|
2781
2745
|
"--source <name>",
|
|
2782
2746
|
"Source to ingest (code, knowledge, git, jira, slack, ci, confluence)"
|
|
2783
2747
|
).option("--all", "Run all sources (code, knowledge, git, and configured connectors)").option("--full", "Force full re-ingestion").action(async (opts, cmd) => {
|
|
@@ -2786,7 +2750,7 @@ function createIngestCommand() {
|
|
|
2786
2750
|
process.exit(1);
|
|
2787
2751
|
}
|
|
2788
2752
|
const globalOpts = cmd.optsWithGlobals();
|
|
2789
|
-
const projectPath =
|
|
2753
|
+
const projectPath = path17.resolve(globalOpts.config ? path17.dirname(globalOpts.config) : ".");
|
|
2790
2754
|
try {
|
|
2791
2755
|
const result = await runIngest(projectPath, opts.source ?? "", {
|
|
2792
2756
|
full: opts.full,
|
|
@@ -2809,7 +2773,7 @@ function createIngestCommand() {
|
|
|
2809
2773
|
|
|
2810
2774
|
// src/commands/graph/index.ts
|
|
2811
2775
|
function resolveProjectPath(globalOpts) {
|
|
2812
|
-
return
|
|
2776
|
+
return path18.resolve(globalOpts.config ? path18.dirname(globalOpts.config) : ".");
|
|
2813
2777
|
}
|
|
2814
2778
|
function printGraphStatus(result) {
|
|
2815
2779
|
if (result.status === "no_graph") {
|
|
@@ -2853,19 +2817,19 @@ async function runExportAction(opts, cmd) {
|
|
|
2853
2817
|
}
|
|
2854
2818
|
}
|
|
2855
2819
|
function createGraphCommand() {
|
|
2856
|
-
const graph = new
|
|
2820
|
+
const graph = new Command23("graph").description("Knowledge graph management");
|
|
2857
2821
|
graph.command("status").description("Show graph statistics").action(runStatusAction);
|
|
2858
2822
|
graph.command("export").description("Export graph").requiredOption("--format <format>", "Output format (json, mermaid)").action(runExportAction);
|
|
2859
2823
|
return graph;
|
|
2860
2824
|
}
|
|
2861
2825
|
|
|
2862
2826
|
// src/commands/hooks/index.ts
|
|
2863
|
-
import { Command as
|
|
2827
|
+
import { Command as Command28 } from "commander";
|
|
2864
2828
|
|
|
2865
2829
|
// src/commands/hooks/init.ts
|
|
2866
|
-
import { Command as
|
|
2830
|
+
import { Command as Command24 } from "commander";
|
|
2867
2831
|
import * as fs6 from "fs";
|
|
2868
|
-
import * as
|
|
2832
|
+
import * as path19 from "path";
|
|
2869
2833
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
2870
2834
|
|
|
2871
2835
|
// src/hooks/profiles.ts
|
|
@@ -2895,10 +2859,10 @@ var PROFILES = {
|
|
|
2895
2859
|
|
|
2896
2860
|
// src/commands/hooks/init.ts
|
|
2897
2861
|
var __filename2 = fileURLToPath2(import.meta.url);
|
|
2898
|
-
var __dirname2 =
|
|
2862
|
+
var __dirname2 = path19.dirname(__filename2);
|
|
2899
2863
|
var VALID_PROFILES = ["minimal", "standard", "strict"];
|
|
2900
2864
|
function resolveHookSourceDir() {
|
|
2901
|
-
const candidate =
|
|
2865
|
+
const candidate = path19.resolve(__dirname2, "..", "..", "hooks");
|
|
2902
2866
|
if (fs6.existsSync(candidate)) {
|
|
2903
2867
|
return candidate;
|
|
2904
2868
|
}
|
|
@@ -2927,12 +2891,12 @@ function mergeSettings(existing, hooksConfig) {
|
|
|
2927
2891
|
}
|
|
2928
2892
|
function initHooks(options) {
|
|
2929
2893
|
const { profile, projectDir } = options;
|
|
2930
|
-
const hooksDestDir =
|
|
2894
|
+
const hooksDestDir = path19.join(projectDir, ".harness", "hooks");
|
|
2931
2895
|
fs6.mkdirSync(hooksDestDir, { recursive: true });
|
|
2932
2896
|
if (fs6.existsSync(hooksDestDir)) {
|
|
2933
2897
|
for (const entry of fs6.readdirSync(hooksDestDir)) {
|
|
2934
2898
|
if (entry.endsWith(".js")) {
|
|
2935
|
-
fs6.unlinkSync(
|
|
2899
|
+
fs6.unlinkSync(path19.join(hooksDestDir, entry));
|
|
2936
2900
|
}
|
|
2937
2901
|
}
|
|
2938
2902
|
}
|
|
@@ -2941,18 +2905,18 @@ function initHooks(options) {
|
|
|
2941
2905
|
const activeNames = PROFILES[profile];
|
|
2942
2906
|
const activeScripts = HOOK_SCRIPTS.filter((h) => activeNames.includes(h.name));
|
|
2943
2907
|
for (const script of activeScripts) {
|
|
2944
|
-
const srcFile =
|
|
2945
|
-
const destFile =
|
|
2908
|
+
const srcFile = path19.join(sourceDir, `${script.name}.js`);
|
|
2909
|
+
const destFile = path19.join(hooksDestDir, `${script.name}.js`);
|
|
2946
2910
|
if (fs6.existsSync(srcFile)) {
|
|
2947
2911
|
fs6.copyFileSync(srcFile, destFile);
|
|
2948
2912
|
copiedScripts.push(script.name);
|
|
2949
2913
|
}
|
|
2950
2914
|
}
|
|
2951
|
-
const profilePath =
|
|
2915
|
+
const profilePath = path19.join(hooksDestDir, "profile.json");
|
|
2952
2916
|
fs6.writeFileSync(profilePath, JSON.stringify({ profile }, null, 2) + "\n");
|
|
2953
|
-
const claudeDir =
|
|
2917
|
+
const claudeDir = path19.join(projectDir, ".claude");
|
|
2954
2918
|
fs6.mkdirSync(claudeDir, { recursive: true });
|
|
2955
|
-
const settingsPath =
|
|
2919
|
+
const settingsPath = path19.join(claudeDir, "settings.json");
|
|
2956
2920
|
let existing = {};
|
|
2957
2921
|
if (fs6.existsSync(settingsPath)) {
|
|
2958
2922
|
try {
|
|
@@ -2970,7 +2934,7 @@ function initHooks(options) {
|
|
|
2970
2934
|
return { copiedScripts, settingsPath, profilePath };
|
|
2971
2935
|
}
|
|
2972
2936
|
function createInitCommand2() {
|
|
2973
|
-
return new
|
|
2937
|
+
return new Command24("init").description("Install Claude Code hook configurations into the current project").option("--profile <profile>", "Hook profile: minimal, standard, or strict", "standard").action(async (opts, cmd) => {
|
|
2974
2938
|
const globalOpts = cmd.optsWithGlobals();
|
|
2975
2939
|
const profile = opts.profile;
|
|
2976
2940
|
if (!VALID_PROFILES.includes(profile)) {
|
|
@@ -2994,7 +2958,7 @@ function createInitCommand2() {
|
|
|
2994
2958
|
`Installed ${result.copiedScripts.length} hook scripts to .harness/hooks/`
|
|
2995
2959
|
);
|
|
2996
2960
|
logger.info(`Profile: ${profile}`);
|
|
2997
|
-
logger.info(`Settings: ${
|
|
2961
|
+
logger.info(`Settings: ${path19.relative(projectDir, result.settingsPath)}`);
|
|
2998
2962
|
logger.dim("Run 'harness hooks list' to see installed hooks");
|
|
2999
2963
|
}
|
|
3000
2964
|
} catch (err) {
|
|
@@ -3006,12 +2970,12 @@ function createInitCommand2() {
|
|
|
3006
2970
|
}
|
|
3007
2971
|
|
|
3008
2972
|
// src/commands/hooks/list.ts
|
|
3009
|
-
import { Command as
|
|
2973
|
+
import { Command as Command25 } from "commander";
|
|
3010
2974
|
import * as fs7 from "fs";
|
|
3011
|
-
import * as
|
|
2975
|
+
import * as path20 from "path";
|
|
3012
2976
|
function listHooks(projectDir) {
|
|
3013
|
-
const hooksDir =
|
|
3014
|
-
const profilePath =
|
|
2977
|
+
const hooksDir = path20.join(projectDir, ".harness", "hooks");
|
|
2978
|
+
const profilePath = path20.join(hooksDir, "profile.json");
|
|
3015
2979
|
if (!fs7.existsSync(profilePath)) {
|
|
3016
2980
|
return { installed: false, profile: null, hooks: [] };
|
|
3017
2981
|
}
|
|
@@ -3030,7 +2994,7 @@ function listHooks(projectDir) {
|
|
|
3030
2994
|
name: h.name,
|
|
3031
2995
|
event: h.event,
|
|
3032
2996
|
matcher: h.matcher,
|
|
3033
|
-
scriptPath:
|
|
2997
|
+
scriptPath: path20.join(".harness", "hooks", `${h.name}.js`)
|
|
3034
2998
|
}));
|
|
3035
2999
|
const result = { installed: true, profile, hooks };
|
|
3036
3000
|
if (warning) {
|
|
@@ -3039,7 +3003,7 @@ function listHooks(projectDir) {
|
|
|
3039
3003
|
return result;
|
|
3040
3004
|
}
|
|
3041
3005
|
function createListCommand() {
|
|
3042
|
-
return new
|
|
3006
|
+
return new Command25("list").description("Show installed hooks and active profile").action(async (_opts, cmd) => {
|
|
3043
3007
|
const globalOpts = cmd.optsWithGlobals();
|
|
3044
3008
|
const projectDir = process.cwd();
|
|
3045
3009
|
const result = listHooks(projectDir);
|
|
@@ -3060,12 +3024,12 @@ function createListCommand() {
|
|
|
3060
3024
|
}
|
|
3061
3025
|
|
|
3062
3026
|
// src/commands/hooks/remove.ts
|
|
3063
|
-
import { Command as
|
|
3027
|
+
import { Command as Command26 } from "commander";
|
|
3064
3028
|
import * as fs8 from "fs";
|
|
3065
|
-
import * as
|
|
3029
|
+
import * as path21 from "path";
|
|
3066
3030
|
function removeHooks(projectDir) {
|
|
3067
|
-
const hooksDir =
|
|
3068
|
-
const settingsPath =
|
|
3031
|
+
const hooksDir = path21.join(projectDir, ".harness", "hooks");
|
|
3032
|
+
const settingsPath = path21.join(projectDir, ".claude", "settings.json");
|
|
3069
3033
|
let removed = false;
|
|
3070
3034
|
let settingsCleaned = false;
|
|
3071
3035
|
if (fs8.existsSync(hooksDir)) {
|
|
@@ -3090,7 +3054,7 @@ function removeHooks(projectDir) {
|
|
|
3090
3054
|
return { removed, hooksDir, settingsCleaned };
|
|
3091
3055
|
}
|
|
3092
3056
|
function createRemoveCommand() {
|
|
3093
|
-
return new
|
|
3057
|
+
return new Command26("remove").description("Remove harness-managed hooks from the current project").action(async (_opts, cmd) => {
|
|
3094
3058
|
const globalOpts = cmd.optsWithGlobals();
|
|
3095
3059
|
const projectDir = process.cwd();
|
|
3096
3060
|
const result = removeHooks(projectDir);
|
|
@@ -3112,16 +3076,16 @@ function createRemoveCommand() {
|
|
|
3112
3076
|
}
|
|
3113
3077
|
|
|
3114
3078
|
// src/commands/hooks/add.ts
|
|
3115
|
-
import { Command as
|
|
3079
|
+
import { Command as Command27 } from "commander";
|
|
3116
3080
|
import * as fs9 from "fs";
|
|
3117
|
-
import * as
|
|
3081
|
+
import * as path22 from "path";
|
|
3118
3082
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
3119
|
-
var __dirname3 =
|
|
3083
|
+
var __dirname3 = path22.dirname(fileURLToPath3(import.meta.url));
|
|
3120
3084
|
var ALIASES = {
|
|
3121
3085
|
sentinel: ["sentinel-pre", "sentinel-post"]
|
|
3122
3086
|
};
|
|
3123
3087
|
function hookSourceDir() {
|
|
3124
|
-
const d =
|
|
3088
|
+
const d = path22.resolve(__dirname3, "..", "..", "hooks");
|
|
3125
3089
|
if (fs9.existsSync(d)) return d;
|
|
3126
3090
|
throw new Error(`Hook scripts not found: ${d}`);
|
|
3127
3091
|
}
|
|
@@ -3139,11 +3103,11 @@ function addHooks(hookName, projectDir) {
|
|
|
3139
3103
|
const names = ALIASES[hookName] ?? [hookName];
|
|
3140
3104
|
const result = { added: [], alreadyInstalled: [], notFound: [] };
|
|
3141
3105
|
const srcDir = hookSourceDir();
|
|
3142
|
-
const destDir =
|
|
3106
|
+
const destDir = path22.join(projectDir, ".harness", "hooks");
|
|
3143
3107
|
fs9.mkdirSync(destDir, { recursive: true });
|
|
3144
|
-
const claudeDir =
|
|
3108
|
+
const claudeDir = path22.join(projectDir, ".claude");
|
|
3145
3109
|
fs9.mkdirSync(claudeDir, { recursive: true });
|
|
3146
|
-
const settingsPath =
|
|
3110
|
+
const settingsPath = path22.join(claudeDir, "settings.json");
|
|
3147
3111
|
const settings = readJson(settingsPath);
|
|
3148
3112
|
if (!settings.hooks) settings.hooks = {};
|
|
3149
3113
|
for (const name of names) {
|
|
@@ -3152,8 +3116,8 @@ function addHooks(hookName, projectDir) {
|
|
|
3152
3116
|
result.notFound.push(name);
|
|
3153
3117
|
continue;
|
|
3154
3118
|
}
|
|
3155
|
-
const src =
|
|
3156
|
-
const dest =
|
|
3119
|
+
const src = path22.join(srcDir, `${name}.js`);
|
|
3120
|
+
const dest = path22.join(destDir, `${name}.js`);
|
|
3157
3121
|
if (fs9.existsSync(dest)) {
|
|
3158
3122
|
result.alreadyInstalled.push(name);
|
|
3159
3123
|
} else if (!fs9.existsSync(src)) {
|
|
@@ -3169,7 +3133,7 @@ function addHooks(hookName, projectDir) {
|
|
|
3169
3133
|
return result;
|
|
3170
3134
|
}
|
|
3171
3135
|
function createAddCommand2() {
|
|
3172
|
-
return new
|
|
3136
|
+
return new Command27("add").argument("<hook-name>", "Hook name or alias (e.g., sentinel)").description("Add a hook without changing the profile").action(async (hookName, _opts, cmd) => {
|
|
3173
3137
|
const projectDir = process.cwd();
|
|
3174
3138
|
try {
|
|
3175
3139
|
const res = addHooks(hookName, projectDir);
|
|
@@ -3194,7 +3158,7 @@ function createAddCommand2() {
|
|
|
3194
3158
|
|
|
3195
3159
|
// src/commands/hooks/index.ts
|
|
3196
3160
|
function createHooksCommand() {
|
|
3197
|
-
const command = new
|
|
3161
|
+
const command = new Command28("hooks").description("Manage Claude Code hook configurations");
|
|
3198
3162
|
command.addCommand(createInitCommand2());
|
|
3199
3163
|
command.addCommand(createListCommand());
|
|
3200
3164
|
command.addCommand(createRemoveCommand());
|
|
@@ -3203,9 +3167,9 @@ function createHooksCommand() {
|
|
|
3203
3167
|
}
|
|
3204
3168
|
|
|
3205
3169
|
// src/commands/impact-preview.ts
|
|
3206
|
-
import { Command as
|
|
3170
|
+
import { Command as Command29 } from "commander";
|
|
3207
3171
|
import { execSync as execSync4 } from "child_process";
|
|
3208
|
-
import * as
|
|
3172
|
+
import * as path23 from "path";
|
|
3209
3173
|
import * as fs10 from "fs";
|
|
3210
3174
|
function getStagedFiles(cwd) {
|
|
3211
3175
|
try {
|
|
@@ -3220,7 +3184,7 @@ function getStagedFiles(cwd) {
|
|
|
3220
3184
|
}
|
|
3221
3185
|
function graphExists(projectPath) {
|
|
3222
3186
|
try {
|
|
3223
|
-
return fs10.existsSync(
|
|
3187
|
+
return fs10.existsSync(path23.join(projectPath, ".harness", "graph", "graph.json"));
|
|
3224
3188
|
} catch {
|
|
3225
3189
|
return false;
|
|
3226
3190
|
}
|
|
@@ -3229,7 +3193,7 @@ function extractNodeName(id) {
|
|
|
3229
3193
|
const parts = id.split(":");
|
|
3230
3194
|
if (parts.length > 1) {
|
|
3231
3195
|
const fullPath = parts.slice(1).join(":");
|
|
3232
|
-
return
|
|
3196
|
+
return path23.basename(fullPath);
|
|
3233
3197
|
}
|
|
3234
3198
|
return id;
|
|
3235
3199
|
}
|
|
@@ -3384,7 +3348,7 @@ function formatImpactOutput(stagedFiles, acc, options) {
|
|
|
3384
3348
|
return options.detailed ? formatDetailed(stagedFiles.length, merged, acc.aggregateCounts) : formatCompact(stagedFiles.length, merged, acc.aggregateCounts);
|
|
3385
3349
|
}
|
|
3386
3350
|
async function runImpactPreview(options) {
|
|
3387
|
-
const projectPath =
|
|
3351
|
+
const projectPath = path23.resolve(options.path ?? process.cwd());
|
|
3388
3352
|
const stagedFiles = getStagedFiles(projectPath);
|
|
3389
3353
|
if (stagedFiles.length === 0) return "Impact Preview: no staged changes";
|
|
3390
3354
|
if (!graphExists(projectPath)) {
|
|
@@ -3402,7 +3366,7 @@ async function runImpactPreview(options) {
|
|
|
3402
3366
|
return formatImpactOutput(stagedFiles, acc, options);
|
|
3403
3367
|
}
|
|
3404
3368
|
function createImpactPreviewCommand() {
|
|
3405
|
-
const command = new
|
|
3369
|
+
const command = new Command29("impact-preview").description("Show blast radius of staged changes using the knowledge graph").option("--detailed", "Show all affected files instead of top items").option("--per-file", "Show impact per staged file instead of aggregate").option("--path <dir>", "Project root (default: cwd)").action(async (opts) => {
|
|
3406
3370
|
const output = await runImpactPreview({
|
|
3407
3371
|
detailed: opts.detailed,
|
|
3408
3372
|
perFile: opts.perFile,
|
|
@@ -3415,24 +3379,24 @@ function createImpactPreviewCommand() {
|
|
|
3415
3379
|
}
|
|
3416
3380
|
|
|
3417
3381
|
// src/commands/init.ts
|
|
3418
|
-
import { Command as
|
|
3382
|
+
import { Command as Command31 } from "commander";
|
|
3419
3383
|
import chalk3 from "chalk";
|
|
3420
3384
|
import * as fs13 from "fs";
|
|
3421
|
-
import * as
|
|
3385
|
+
import * as path26 from "path";
|
|
3422
3386
|
|
|
3423
3387
|
// src/commands/setup-mcp.ts
|
|
3424
|
-
import { Command as
|
|
3388
|
+
import { Command as Command30 } from "commander";
|
|
3425
3389
|
import * as fs12 from "fs";
|
|
3426
|
-
import * as
|
|
3390
|
+
import * as path25 from "path";
|
|
3427
3391
|
import * as os2 from "os";
|
|
3428
3392
|
import chalk2 from "chalk";
|
|
3429
3393
|
import * as clack from "@clack/prompts";
|
|
3430
3394
|
|
|
3431
3395
|
// src/integrations/toml.ts
|
|
3432
3396
|
import * as fs11 from "fs";
|
|
3433
|
-
import * as
|
|
3397
|
+
import * as path24 from "path";
|
|
3434
3398
|
function writeTomlMcpEntry(filePath, name, entry) {
|
|
3435
|
-
const dir =
|
|
3399
|
+
const dir = path24.dirname(filePath);
|
|
3436
3400
|
if (!fs11.existsSync(dir)) {
|
|
3437
3401
|
fs11.mkdirSync(dir, { recursive: true });
|
|
3438
3402
|
}
|
|
@@ -3613,7 +3577,7 @@ function readJsonFile(filePath) {
|
|
|
3613
3577
|
}
|
|
3614
3578
|
}
|
|
3615
3579
|
function writeJsonFile(filePath, data) {
|
|
3616
|
-
const dir =
|
|
3580
|
+
const dir = path25.dirname(filePath);
|
|
3617
3581
|
if (!fs12.existsSync(dir)) {
|
|
3618
3582
|
fs12.mkdirSync(dir, { recursive: true });
|
|
3619
3583
|
}
|
|
@@ -3632,7 +3596,7 @@ function configureMcpServer(configPath) {
|
|
|
3632
3596
|
return true;
|
|
3633
3597
|
}
|
|
3634
3598
|
function addGeminiTrustedFolder(cwd) {
|
|
3635
|
-
const trustedPath =
|
|
3599
|
+
const trustedPath = path25.join(os2.homedir(), ".gemini", "trustedFolders.json");
|
|
3636
3600
|
const folders = readJsonFile(trustedPath) ?? {};
|
|
3637
3601
|
if (folders[cwd] === "TRUST_FOLDER") {
|
|
3638
3602
|
return false;
|
|
@@ -3646,7 +3610,7 @@ function setupMcp(cwd, client) {
|
|
|
3646
3610
|
const skipped = [];
|
|
3647
3611
|
let trustedFolder = false;
|
|
3648
3612
|
if (client === "all" || client === "claude") {
|
|
3649
|
-
const configPath =
|
|
3613
|
+
const configPath = path25.join(cwd, ".mcp.json");
|
|
3650
3614
|
if (configureMcpServer(configPath)) {
|
|
3651
3615
|
configured.push("Claude Code");
|
|
3652
3616
|
} else {
|
|
@@ -3654,7 +3618,7 @@ function setupMcp(cwd, client) {
|
|
|
3654
3618
|
}
|
|
3655
3619
|
}
|
|
3656
3620
|
if (client === "all" || client === "gemini") {
|
|
3657
|
-
const configPath =
|
|
3621
|
+
const configPath = path25.join(cwd, ".gemini", "settings.json");
|
|
3658
3622
|
if (configureMcpServer(configPath)) {
|
|
3659
3623
|
configured.push("Gemini CLI");
|
|
3660
3624
|
} else {
|
|
@@ -3663,7 +3627,7 @@ function setupMcp(cwd, client) {
|
|
|
3663
3627
|
trustedFolder = addGeminiTrustedFolder(cwd);
|
|
3664
3628
|
}
|
|
3665
3629
|
if (client === "all" || client === "codex") {
|
|
3666
|
-
const configPath =
|
|
3630
|
+
const configPath = path25.join(cwd, ".codex", "config.toml");
|
|
3667
3631
|
const alreadyConfigured = (() => {
|
|
3668
3632
|
if (!fs12.existsSync(configPath)) return false;
|
|
3669
3633
|
const content = fs12.readFileSync(configPath, "utf-8");
|
|
@@ -3681,7 +3645,7 @@ function setupMcp(cwd, client) {
|
|
|
3681
3645
|
}
|
|
3682
3646
|
}
|
|
3683
3647
|
if (client === "all" || client === "cursor") {
|
|
3684
|
-
const configPath =
|
|
3648
|
+
const configPath = path25.join(cwd, ".cursor", "mcp.json");
|
|
3685
3649
|
const existing = readJsonFile(configPath);
|
|
3686
3650
|
if (existing?.mcpServers?.["harness"]) {
|
|
3687
3651
|
skipped.push("Cursor");
|
|
@@ -3695,7 +3659,7 @@ function setupMcp(cwd, client) {
|
|
|
3695
3659
|
async function resolveCursorWithPicker(cwd, pick) {
|
|
3696
3660
|
const configured = [];
|
|
3697
3661
|
const skipped = [];
|
|
3698
|
-
const cursorConfigPath =
|
|
3662
|
+
const cursorConfigPath = path25.join(cwd, ".cursor", "mcp.json");
|
|
3699
3663
|
const existing = readJsonFile(cursorConfigPath);
|
|
3700
3664
|
if (existing?.mcpServers?.["harness"] && !pick) {
|
|
3701
3665
|
skipped.push("Cursor");
|
|
@@ -3739,7 +3703,7 @@ function printMcpResult(configured, skipped, trustedFolder) {
|
|
|
3739
3703
|
console.log("");
|
|
3740
3704
|
}
|
|
3741
3705
|
function createSetupMcpCommand() {
|
|
3742
|
-
return new
|
|
3706
|
+
return new Command30("setup-mcp").description("Configure MCP server for AI agent integration").option("--client <client>", "Client to configure (claude, gemini, codex, cursor, all)", "all").option("--pick", "Launch interactive tool picker (Cursor only)").option("--yes", "Bypass interactive picker and use curated 25-tool set (Cursor only)").action(async (opts, cmd) => {
|
|
3743
3707
|
const globalOpts = cmd.optsWithGlobals();
|
|
3744
3708
|
const cwd = process.cwd();
|
|
3745
3709
|
let configured;
|
|
@@ -3766,11 +3730,11 @@ function loadEngineAndTemplates(_options) {
|
|
|
3766
3730
|
}
|
|
3767
3731
|
function resolveInitDefaults(options) {
|
|
3768
3732
|
const cwd = options.cwd ?? process.cwd();
|
|
3769
|
-
return { cwd, name: options.name ??
|
|
3733
|
+
return { cwd, name: options.name ?? path26.basename(cwd), force: options.force ?? false };
|
|
3770
3734
|
}
|
|
3771
3735
|
async function runInit(options) {
|
|
3772
3736
|
const { cwd, name, force } = resolveInitDefaults(options);
|
|
3773
|
-
const configPath =
|
|
3737
|
+
const configPath = path26.join(cwd, "harness.config.json");
|
|
3774
3738
|
if (!force && fs13.existsSync(configPath)) {
|
|
3775
3739
|
return Err(
|
|
3776
3740
|
new CLIError("Project already initialized. Use --force to overwrite.", ExitCode.ERROR)
|
|
@@ -3879,7 +3843,7 @@ async function runInitAction2(opts) {
|
|
|
3879
3843
|
process.exit(ExitCode.SUCCESS);
|
|
3880
3844
|
}
|
|
3881
3845
|
function createInitCommand3() {
|
|
3882
|
-
const command = new
|
|
3846
|
+
const command = new Command31("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("--language <language>", "Target language (typescript, python, go, rust, java)").option("-f, --force", "Overwrite existing files").option("-y, --yes", "Use defaults without prompting").action(async (opts, cmd) => {
|
|
3883
3847
|
const globalOpts = cmd.optsWithGlobals();
|
|
3884
3848
|
await runInitAction2({
|
|
3885
3849
|
name: opts.name,
|
|
@@ -3896,14 +3860,14 @@ function createInitCommand3() {
|
|
|
3896
3860
|
// src/commands/install.ts
|
|
3897
3861
|
import * as fs18 from "fs";
|
|
3898
3862
|
import * as os5 from "os";
|
|
3899
|
-
import * as
|
|
3863
|
+
import * as path31 from "path";
|
|
3900
3864
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
3901
|
-
import { Command as
|
|
3865
|
+
import { Command as Command32 } from "commander";
|
|
3902
3866
|
import { parse as yamlParse } from "yaml";
|
|
3903
3867
|
|
|
3904
3868
|
// src/registry/npm-client.ts
|
|
3905
3869
|
import * as fs14 from "fs";
|
|
3906
|
-
import * as
|
|
3870
|
+
import * as path27 from "path";
|
|
3907
3871
|
import * as os3 from "os";
|
|
3908
3872
|
var NPM_REGISTRY = "https://registry.npmjs.org";
|
|
3909
3873
|
var FETCH_TIMEOUT_MS = 3e4;
|
|
@@ -3926,7 +3890,7 @@ function extractSkillName(packageName) {
|
|
|
3926
3890
|
function readNpmrcToken(registryUrl) {
|
|
3927
3891
|
const { hostname, pathname } = new URL(registryUrl);
|
|
3928
3892
|
const registryPath = `//${hostname}${pathname.replace(/\/$/, "")}/:_authToken=`;
|
|
3929
|
-
const candidates = [
|
|
3893
|
+
const candidates = [path27.join(process.cwd(), ".npmrc"), path27.join(os3.homedir(), ".npmrc")];
|
|
3930
3894
|
for (const npmrcPath of candidates) {
|
|
3931
3895
|
try {
|
|
3932
3896
|
const content = fs14.readFileSync(npmrcPath, "utf-8");
|
|
@@ -4025,12 +3989,12 @@ async function searchNpmRegistry(query, registryUrl) {
|
|
|
4025
3989
|
|
|
4026
3990
|
// src/registry/tarball.ts
|
|
4027
3991
|
import * as fs15 from "fs";
|
|
4028
|
-
import * as
|
|
3992
|
+
import * as path28 from "path";
|
|
4029
3993
|
import * as os4 from "os";
|
|
4030
3994
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
4031
3995
|
function extractTarball(tarballBuffer) {
|
|
4032
|
-
const tmpDir = fs15.mkdtempSync(
|
|
4033
|
-
const tarballPath =
|
|
3996
|
+
const tmpDir = fs15.mkdtempSync(path28.join(os4.tmpdir(), "harness-skill-install-"));
|
|
3997
|
+
const tarballPath = path28.join(tmpDir, "package.tgz");
|
|
4034
3998
|
try {
|
|
4035
3999
|
fs15.writeFileSync(tarballPath, tarballBuffer);
|
|
4036
4000
|
execFileSync2("tar", ["-xzf", tarballPath, "-C", tmpDir], {
|
|
@@ -4049,15 +4013,15 @@ function extractTarball(tarballBuffer) {
|
|
|
4049
4013
|
function placeSkillContent(extractedPkgDir, communityBaseDir, skillName, platforms) {
|
|
4050
4014
|
const files = fs15.readdirSync(extractedPkgDir);
|
|
4051
4015
|
for (const platform of platforms) {
|
|
4052
|
-
const targetDir =
|
|
4016
|
+
const targetDir = path28.join(communityBaseDir, platform, skillName);
|
|
4053
4017
|
if (fs15.existsSync(targetDir)) {
|
|
4054
4018
|
fs15.rmSync(targetDir, { recursive: true, force: true });
|
|
4055
4019
|
}
|
|
4056
4020
|
fs15.mkdirSync(targetDir, { recursive: true });
|
|
4057
4021
|
for (const file of files) {
|
|
4058
4022
|
if (file === "package.json" || file === "node_modules") continue;
|
|
4059
|
-
const srcPath =
|
|
4060
|
-
const destPath =
|
|
4023
|
+
const srcPath = path28.join(extractedPkgDir, file);
|
|
4024
|
+
const destPath = path28.join(targetDir, file);
|
|
4061
4025
|
const stat = fs15.statSync(srcPath);
|
|
4062
4026
|
if (stat.isDirectory()) {
|
|
4063
4027
|
fs15.cpSync(srcPath, destPath, { recursive: true });
|
|
@@ -4069,7 +4033,7 @@ function placeSkillContent(extractedPkgDir, communityBaseDir, skillName, platfor
|
|
|
4069
4033
|
}
|
|
4070
4034
|
function removeSkillContent(communityBaseDir, skillName, platforms) {
|
|
4071
4035
|
for (const platform of platforms) {
|
|
4072
|
-
const targetDir =
|
|
4036
|
+
const targetDir = path28.join(communityBaseDir, platform, skillName);
|
|
4073
4037
|
if (fs15.existsSync(targetDir)) {
|
|
4074
4038
|
fs15.rmSync(targetDir, { recursive: true, force: true });
|
|
4075
4039
|
}
|
|
@@ -4120,7 +4084,7 @@ function findDependentsOf(lockfile, targetPackageName) {
|
|
|
4120
4084
|
|
|
4121
4085
|
// src/registry/lockfile.ts
|
|
4122
4086
|
import * as fs16 from "fs";
|
|
4123
|
-
import * as
|
|
4087
|
+
import * as path29 from "path";
|
|
4124
4088
|
function createEmptyLockfile() {
|
|
4125
4089
|
return { version: 1, skills: {} };
|
|
4126
4090
|
}
|
|
@@ -4160,7 +4124,7 @@ function readLockfile2(filePath) {
|
|
|
4160
4124
|
return parsed;
|
|
4161
4125
|
}
|
|
4162
4126
|
function writeLockfile2(filePath, lockfile) {
|
|
4163
|
-
const dir =
|
|
4127
|
+
const dir = path29.dirname(filePath);
|
|
4164
4128
|
fs16.mkdirSync(dir, { recursive: true });
|
|
4165
4129
|
fs16.writeFileSync(filePath, sortedStringify(lockfile) + "\n", "utf-8");
|
|
4166
4130
|
}
|
|
@@ -4186,7 +4150,7 @@ function removeLockfileEntry(lockfile, name) {
|
|
|
4186
4150
|
|
|
4187
4151
|
// src/registry/bundled-skills.ts
|
|
4188
4152
|
import * as fs17 from "fs";
|
|
4189
|
-
import * as
|
|
4153
|
+
import * as path30 from "path";
|
|
4190
4154
|
function getBundledSkillNames(bundledSkillsDir) {
|
|
4191
4155
|
if (!fs17.existsSync(bundledSkillsDir)) {
|
|
4192
4156
|
return /* @__PURE__ */ new Set();
|
|
@@ -4195,7 +4159,7 @@ function getBundledSkillNames(bundledSkillsDir) {
|
|
|
4195
4159
|
const names = /* @__PURE__ */ new Set();
|
|
4196
4160
|
for (const entry of entries) {
|
|
4197
4161
|
try {
|
|
4198
|
-
const stat = fs17.statSync(
|
|
4162
|
+
const stat = fs17.statSync(path30.join(bundledSkillsDir, String(entry)));
|
|
4199
4163
|
if (stat.isDirectory()) {
|
|
4200
4164
|
names.add(String(entry));
|
|
4201
4165
|
}
|
|
@@ -4222,12 +4186,12 @@ function validateSkillYaml(parsed) {
|
|
|
4222
4186
|
function resolveCommunityBase(global) {
|
|
4223
4187
|
if (global) {
|
|
4224
4188
|
const communityBase2 = resolveGlobalCommunityBaseDir();
|
|
4225
|
-
return { communityBase: communityBase2, lockfilePath:
|
|
4189
|
+
return { communityBase: communityBase2, lockfilePath: path31.join(communityBase2, "skills-lock.json") };
|
|
4226
4190
|
}
|
|
4227
4191
|
const globalDir = resolveGlobalSkillsDir();
|
|
4228
|
-
const skillsDir =
|
|
4229
|
-
const communityBase =
|
|
4230
|
-
return { communityBase, lockfilePath:
|
|
4192
|
+
const skillsDir = path31.dirname(globalDir);
|
|
4193
|
+
const communityBase = path31.join(skillsDir, "community");
|
|
4194
|
+
return { communityBase, lockfilePath: path31.join(communityBase, "skills-lock.json") };
|
|
4231
4195
|
}
|
|
4232
4196
|
function parseGitHubRef(from) {
|
|
4233
4197
|
const ghPrefix = from.match(/^github:([^/]+)\/([^#]+?)(?:#(.+))?$/);
|
|
@@ -4243,7 +4207,7 @@ function parseGitHubRef(from) {
|
|
|
4243
4207
|
return null;
|
|
4244
4208
|
}
|
|
4245
4209
|
function cloneGitHubRepo(owner, repo, ref) {
|
|
4246
|
-
const tmpDir = fs18.mkdtempSync(
|
|
4210
|
+
const tmpDir = fs18.mkdtempSync(path31.join(os5.tmpdir(), "harness-gh-install-"));
|
|
4247
4211
|
const url = `https://github.com/${owner}/${repo}.git`;
|
|
4248
4212
|
try {
|
|
4249
4213
|
const cloneArgs = ["clone", "--depth", "1"];
|
|
@@ -4265,7 +4229,7 @@ function discoverSkillDirs(rootDir) {
|
|
|
4265
4229
|
function scan(dir, depth) {
|
|
4266
4230
|
if (depth > 3) return;
|
|
4267
4231
|
if (!fs18.existsSync(dir)) return;
|
|
4268
|
-
if (fs18.existsSync(
|
|
4232
|
+
if (fs18.existsSync(path31.join(dir, "skill.yaml"))) {
|
|
4269
4233
|
skillDirs.push(dir);
|
|
4270
4234
|
return;
|
|
4271
4235
|
}
|
|
@@ -4273,14 +4237,14 @@ function discoverSkillDirs(rootDir) {
|
|
|
4273
4237
|
for (const entry of entries) {
|
|
4274
4238
|
if (!entry.isDirectory()) continue;
|
|
4275
4239
|
if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
|
|
4276
|
-
scan(
|
|
4240
|
+
scan(path31.join(dir, entry.name), depth + 1);
|
|
4277
4241
|
}
|
|
4278
4242
|
}
|
|
4279
4243
|
scan(rootDir, 0);
|
|
4280
4244
|
return skillDirs;
|
|
4281
4245
|
}
|
|
4282
4246
|
function resolveLocalPkgDir(fromPath) {
|
|
4283
|
-
const resolvedPath =
|
|
4247
|
+
const resolvedPath = path31.resolve(fromPath);
|
|
4284
4248
|
if (!fs18.existsSync(resolvedPath)) {
|
|
4285
4249
|
throw new Error(`--from path does not exist: ${resolvedPath}`);
|
|
4286
4250
|
}
|
|
@@ -4291,12 +4255,12 @@ function resolveLocalPkgDir(fromPath) {
|
|
|
4291
4255
|
if (resolvedPath.endsWith(".tgz") || resolvedPath.endsWith(".tar.gz")) {
|
|
4292
4256
|
const tarballBuffer = fs18.readFileSync(resolvedPath);
|
|
4293
4257
|
const extractDir = extractTarball(tarballBuffer);
|
|
4294
|
-
return { pkgDir:
|
|
4258
|
+
return { pkgDir: path31.join(extractDir, "package"), extractDir };
|
|
4295
4259
|
}
|
|
4296
4260
|
throw new Error(`--from path must be a directory or .tgz file. Got: ${resolvedPath}`);
|
|
4297
4261
|
}
|
|
4298
4262
|
function installSkillDir(pkgDir, resolvedPath, options) {
|
|
4299
|
-
const skillYamlPath =
|
|
4263
|
+
const skillYamlPath = path31.join(pkgDir, "skill.yaml");
|
|
4300
4264
|
if (!fs18.existsSync(skillYamlPath)) {
|
|
4301
4265
|
throw new Error(`No skill.yaml found at ${skillYamlPath}`);
|
|
4302
4266
|
}
|
|
@@ -4328,7 +4292,7 @@ function installSkillDir(pkgDir, resolvedPath, options) {
|
|
|
4328
4292
|
async function runLocalInstall(fromPath, options) {
|
|
4329
4293
|
const { pkgDir, extractDir } = resolveLocalPkgDir(fromPath);
|
|
4330
4294
|
try {
|
|
4331
|
-
return installSkillDir(pkgDir,
|
|
4295
|
+
return installSkillDir(pkgDir, path31.resolve(fromPath), options);
|
|
4332
4296
|
} finally {
|
|
4333
4297
|
if (extractDir) cleanupTempDir(extractDir);
|
|
4334
4298
|
}
|
|
@@ -4371,8 +4335,8 @@ async function runInstall(skillName, options) {
|
|
|
4371
4335
|
version: installed.map((r) => r.version).join(", ")
|
|
4372
4336
|
};
|
|
4373
4337
|
}
|
|
4374
|
-
const resolvedFrom =
|
|
4375
|
-
if (fs18.existsSync(resolvedFrom) && fs18.statSync(resolvedFrom).isDirectory() && !fs18.existsSync(
|
|
4338
|
+
const resolvedFrom = path31.resolve(options.from);
|
|
4339
|
+
if (fs18.existsSync(resolvedFrom) && fs18.statSync(resolvedFrom).isDirectory() && !fs18.existsSync(path31.join(resolvedFrom, "skill.yaml"))) {
|
|
4376
4340
|
const results = await runBulkInstall(resolvedFrom, options);
|
|
4377
4341
|
const installed = results.filter((r) => r.installed);
|
|
4378
4342
|
return {
|
|
@@ -4413,8 +4377,8 @@ async function runInstall(skillName, options) {
|
|
|
4413
4377
|
const extractDir = extractTarball(tarballBuffer);
|
|
4414
4378
|
let skillYaml;
|
|
4415
4379
|
try {
|
|
4416
|
-
const extractedPkgDir =
|
|
4417
|
-
const skillYamlPath =
|
|
4380
|
+
const extractedPkgDir = path31.join(extractDir, "package");
|
|
4381
|
+
const skillYamlPath = path31.join(extractedPkgDir, "skill.yaml");
|
|
4418
4382
|
if (!fs18.existsSync(skillYamlPath)) {
|
|
4419
4383
|
throw new Error(`contains invalid skill.yaml: file not found in package`);
|
|
4420
4384
|
}
|
|
@@ -4458,7 +4422,7 @@ async function runInstall(skillName, options) {
|
|
|
4458
4422
|
return result;
|
|
4459
4423
|
}
|
|
4460
4424
|
function createInstallCommand() {
|
|
4461
|
-
const cmd = new
|
|
4425
|
+
const cmd = new Command32("install");
|
|
4462
4426
|
cmd.description("Install skills from npm registry, local directory, or GitHub repository").argument("<skill>", 'Skill name, @harness-skills/scoped package, or "." for bulk install').option("--version <range>", "Semver range or exact version to install").option("--force", "Force reinstall even if same version is already installed").option(
|
|
4463
4427
|
"--from <source>",
|
|
4464
4428
|
"Install from local path, directory, or GitHub (github:owner/repo, https://github.com/owner/repo)"
|
|
@@ -4492,8 +4456,8 @@ function createInstallCommand() {
|
|
|
4492
4456
|
|
|
4493
4457
|
// src/commands/install-constraints.ts
|
|
4494
4458
|
import * as fs19 from "fs/promises";
|
|
4495
|
-
import * as
|
|
4496
|
-
import { Command as
|
|
4459
|
+
import * as path32 from "path";
|
|
4460
|
+
import { Command as Command33 } from "commander";
|
|
4497
4461
|
import semver3 from "semver";
|
|
4498
4462
|
async function runInstallConstraints(options) {
|
|
4499
4463
|
const { source, configPath, lockfilePath } = options;
|
|
@@ -4690,7 +4654,7 @@ function isNodeError(err) {
|
|
|
4690
4654
|
return err instanceof Error && "code" in err;
|
|
4691
4655
|
}
|
|
4692
4656
|
function resolveConfigPath(opts) {
|
|
4693
|
-
if (opts.config) return
|
|
4657
|
+
if (opts.config) return path32.resolve(opts.config);
|
|
4694
4658
|
const found = findConfigFile();
|
|
4695
4659
|
if (!found.ok) {
|
|
4696
4660
|
logger.error(found.error.message);
|
|
@@ -4725,9 +4689,9 @@ function logInstallResult(val, opts) {
|
|
|
4725
4689
|
}
|
|
4726
4690
|
async function handleInstallConstraints(source, opts) {
|
|
4727
4691
|
const configPath = resolveConfigPath(opts);
|
|
4728
|
-
const projectRoot =
|
|
4729
|
-
const lockfilePath =
|
|
4730
|
-
const resolvedSource =
|
|
4692
|
+
const projectRoot = path32.dirname(configPath);
|
|
4693
|
+
const lockfilePath = path32.join(projectRoot, ".harness", "constraints.lock.json");
|
|
4694
|
+
const resolvedSource = path32.resolve(source);
|
|
4731
4695
|
if (opts.forceLocal && opts.forcePackage) {
|
|
4732
4696
|
logger.error("Cannot use both --force-local and --force-package.");
|
|
4733
4697
|
process.exit(1);
|
|
@@ -4747,18 +4711,18 @@ async function handleInstallConstraints(source, opts) {
|
|
|
4747
4711
|
logInstallResult(result.value, opts);
|
|
4748
4712
|
}
|
|
4749
4713
|
function createInstallConstraintsCommand() {
|
|
4750
|
-
const cmd = new
|
|
4714
|
+
const cmd = new Command33("install-constraints");
|
|
4751
4715
|
cmd.description("Install a constraints bundle into the local harness config").argument("<source>", "Path to a .harness-constraints.json bundle file").option("--force-local", "Resolve all conflicts by keeping local values").option("--force-package", "Resolve all conflicts by using package values").option("--dry-run", "Show what would change without writing files").option("-c, --config <path>", "Path to harness.config.json").action(handleInstallConstraints);
|
|
4752
4716
|
return cmd;
|
|
4753
4717
|
}
|
|
4754
4718
|
|
|
4755
4719
|
// src/commands/integrations/index.ts
|
|
4756
|
-
import { Command as
|
|
4720
|
+
import { Command as Command38 } from "commander";
|
|
4757
4721
|
|
|
4758
4722
|
// src/commands/integrations/add.ts
|
|
4759
|
-
import { Command as
|
|
4723
|
+
import { Command as Command34 } from "commander";
|
|
4760
4724
|
import * as fs20 from "fs";
|
|
4761
|
-
import * as
|
|
4725
|
+
import * as path33 from "path";
|
|
4762
4726
|
import chalk4 from "chalk";
|
|
4763
4727
|
function buildMcpEntry(def) {
|
|
4764
4728
|
const entry = { command: def.mcpConfig.command };
|
|
@@ -4767,14 +4731,14 @@ function buildMcpEntry(def) {
|
|
|
4767
4731
|
return entry;
|
|
4768
4732
|
}
|
|
4769
4733
|
function writeMcpEntries(cwd, defName, mcpEntry) {
|
|
4770
|
-
writeMcpEntry(
|
|
4771
|
-
const geminiDir =
|
|
4734
|
+
writeMcpEntry(path33.join(cwd, ".mcp.json"), defName, mcpEntry);
|
|
4735
|
+
const geminiDir = path33.join(cwd, ".gemini");
|
|
4772
4736
|
if (fs20.existsSync(geminiDir)) {
|
|
4773
|
-
writeMcpEntry(
|
|
4737
|
+
writeMcpEntry(path33.join(geminiDir, "settings.json"), defName, mcpEntry);
|
|
4774
4738
|
}
|
|
4775
4739
|
}
|
|
4776
4740
|
function updateIntegrationsConfig(cwd, defName) {
|
|
4777
|
-
const configPath =
|
|
4741
|
+
const configPath = path33.join(cwd, "harness.config.json");
|
|
4778
4742
|
const integConfig = readIntegrationsConfig(configPath);
|
|
4779
4743
|
if (!integConfig.enabled.includes(defName)) integConfig.enabled.push(defName);
|
|
4780
4744
|
integConfig.dismissed = integConfig.dismissed.filter((d) => d !== defName);
|
|
@@ -4820,7 +4784,7 @@ function printAddSuccess(value) {
|
|
|
4820
4784
|
}
|
|
4821
4785
|
}
|
|
4822
4786
|
function createAddIntegrationCommand() {
|
|
4823
|
-
return new
|
|
4787
|
+
return new Command34("add").description("Enable an MCP integration").argument("<name>", "Integration name (e.g. perplexity, augment-code)").action(async (name, _opts, cmd) => {
|
|
4824
4788
|
const globalOpts = cmd.optsWithGlobals();
|
|
4825
4789
|
const result = addIntegration(process.cwd(), name);
|
|
4826
4790
|
if (!result.ok) {
|
|
@@ -4834,8 +4798,8 @@ function createAddIntegrationCommand() {
|
|
|
4834
4798
|
}
|
|
4835
4799
|
|
|
4836
4800
|
// src/commands/integrations/list.ts
|
|
4837
|
-
import { Command as
|
|
4838
|
-
import * as
|
|
4801
|
+
import { Command as Command35 } from "commander";
|
|
4802
|
+
import * as path34 from "path";
|
|
4839
4803
|
import chalk5 from "chalk";
|
|
4840
4804
|
function printTier0Integrations(tier0, mcpServers) {
|
|
4841
4805
|
console.log(" Tier 0 (zero-config):");
|
|
@@ -4859,8 +4823,8 @@ function printTier1Integrations(tier1, mcpServers, dismissed) {
|
|
|
4859
4823
|
}
|
|
4860
4824
|
async function runListIntegrations(globalOpts) {
|
|
4861
4825
|
const cwd = process.cwd();
|
|
4862
|
-
const mcpConfig = readMcpConfig(
|
|
4863
|
-
const integConfig = readIntegrationsConfig(
|
|
4826
|
+
const mcpConfig = readMcpConfig(path34.join(cwd, ".mcp.json"));
|
|
4827
|
+
const integConfig = readIntegrationsConfig(path34.join(cwd, "harness.config.json"));
|
|
4864
4828
|
const mcpServers = mcpConfig.mcpServers ?? {};
|
|
4865
4829
|
if (globalOpts.json) {
|
|
4866
4830
|
const entries = INTEGRATION_REGISTRY.map((i) => ({
|
|
@@ -4891,13 +4855,13 @@ async function runListIntegrations(globalOpts) {
|
|
|
4891
4855
|
process.exit(ExitCode.SUCCESS);
|
|
4892
4856
|
}
|
|
4893
4857
|
function createListIntegrationsCommand() {
|
|
4894
|
-
return new
|
|
4858
|
+
return new Command35("list").description("Show all MCP integrations with status").action(async (_opts, cmd) => runListIntegrations(cmd.optsWithGlobals()));
|
|
4895
4859
|
}
|
|
4896
4860
|
|
|
4897
4861
|
// src/commands/integrations/remove.ts
|
|
4898
|
-
import { Command as
|
|
4862
|
+
import { Command as Command36 } from "commander";
|
|
4899
4863
|
import * as fs21 from "fs";
|
|
4900
|
-
import * as
|
|
4864
|
+
import * as path35 from "path";
|
|
4901
4865
|
function removeIntegration(cwd, name) {
|
|
4902
4866
|
const def = INTEGRATION_REGISTRY.find((i) => i.name === name);
|
|
4903
4867
|
if (!def) {
|
|
@@ -4908,21 +4872,21 @@ function removeIntegration(cwd, name) {
|
|
|
4908
4872
|
)
|
|
4909
4873
|
);
|
|
4910
4874
|
}
|
|
4911
|
-
const mcpPath =
|
|
4875
|
+
const mcpPath = path35.join(cwd, ".mcp.json");
|
|
4912
4876
|
removeMcpEntry(mcpPath, def.name);
|
|
4913
|
-
const geminiDir =
|
|
4877
|
+
const geminiDir = path35.join(cwd, ".gemini");
|
|
4914
4878
|
if (fs21.existsSync(geminiDir)) {
|
|
4915
|
-
const geminiPath =
|
|
4879
|
+
const geminiPath = path35.join(geminiDir, "settings.json");
|
|
4916
4880
|
removeMcpEntry(geminiPath, def.name);
|
|
4917
4881
|
}
|
|
4918
|
-
const configPath =
|
|
4882
|
+
const configPath = path35.join(cwd, "harness.config.json");
|
|
4919
4883
|
const integConfig = readIntegrationsConfig(configPath);
|
|
4920
4884
|
integConfig.enabled = integConfig.enabled.filter((e) => e !== def.name);
|
|
4921
4885
|
writeIntegrationsConfig(configPath, integConfig);
|
|
4922
4886
|
return Ok(def.displayName);
|
|
4923
4887
|
}
|
|
4924
4888
|
function createRemoveIntegrationCommand() {
|
|
4925
|
-
return new
|
|
4889
|
+
return new Command36("remove").description("Remove an MCP integration").argument("<name>", "Integration name (e.g. perplexity, augment-code)").action(async (name, _opts, cmd) => {
|
|
4926
4890
|
const globalOpts = cmd.optsWithGlobals();
|
|
4927
4891
|
const cwd = process.cwd();
|
|
4928
4892
|
const result = removeIntegration(cwd, name);
|
|
@@ -4943,8 +4907,8 @@ function createRemoveIntegrationCommand() {
|
|
|
4943
4907
|
}
|
|
4944
4908
|
|
|
4945
4909
|
// src/commands/integrations/dismiss.ts
|
|
4946
|
-
import { Command as
|
|
4947
|
-
import * as
|
|
4910
|
+
import { Command as Command37 } from "commander";
|
|
4911
|
+
import * as path36 from "path";
|
|
4948
4912
|
function dismissIntegration(cwd, name) {
|
|
4949
4913
|
const def = INTEGRATION_REGISTRY.find((i) => i.name === name);
|
|
4950
4914
|
if (!def) {
|
|
@@ -4955,7 +4919,7 @@ function dismissIntegration(cwd, name) {
|
|
|
4955
4919
|
)
|
|
4956
4920
|
);
|
|
4957
4921
|
}
|
|
4958
|
-
const configPath =
|
|
4922
|
+
const configPath = path36.join(cwd, "harness.config.json");
|
|
4959
4923
|
const integConfig = readIntegrationsConfig(configPath);
|
|
4960
4924
|
if (!integConfig.dismissed.includes(def.name)) {
|
|
4961
4925
|
integConfig.dismissed.push(def.name);
|
|
@@ -4965,7 +4929,7 @@ function dismissIntegration(cwd, name) {
|
|
|
4965
4929
|
return Ok(def.displayName);
|
|
4966
4930
|
}
|
|
4967
4931
|
function createDismissIntegrationCommand() {
|
|
4968
|
-
return new
|
|
4932
|
+
return new Command37("dismiss").description("Suppress doctor recommendations for an integration").argument("<name>", "Integration name (e.g. perplexity, augment-code)").action(async (name, _opts, cmd) => {
|
|
4969
4933
|
const globalOpts = cmd.optsWithGlobals();
|
|
4970
4934
|
const cwd = process.cwd();
|
|
4971
4935
|
const result = dismissIntegration(cwd, name);
|
|
@@ -4989,7 +4953,7 @@ function createDismissIntegrationCommand() {
|
|
|
4989
4953
|
|
|
4990
4954
|
// src/commands/integrations/index.ts
|
|
4991
4955
|
function createIntegrationsCommand() {
|
|
4992
|
-
const command = new
|
|
4956
|
+
const command = new Command38("integrations").description(
|
|
4993
4957
|
"Manage MCP peer integrations (add, list, remove, dismiss)"
|
|
4994
4958
|
);
|
|
4995
4959
|
command.addCommand(createListIntegrationsCommand());
|
|
@@ -5000,13 +4964,13 @@ function createIntegrationsCommand() {
|
|
|
5000
4964
|
}
|
|
5001
4965
|
|
|
5002
4966
|
// src/commands/learnings/index.ts
|
|
5003
|
-
import { Command as
|
|
4967
|
+
import { Command as Command40 } from "commander";
|
|
5004
4968
|
|
|
5005
4969
|
// src/commands/learnings/prune.ts
|
|
5006
|
-
import { Command as
|
|
5007
|
-
import * as
|
|
4970
|
+
import { Command as Command39 } from "commander";
|
|
4971
|
+
import * as path37 from "path";
|
|
5008
4972
|
async function handlePrune(opts) {
|
|
5009
|
-
const projectPath =
|
|
4973
|
+
const projectPath = path37.resolve(opts.path);
|
|
5010
4974
|
const result = await pruneLearnings(projectPath, opts.stream);
|
|
5011
4975
|
if (!result.ok) {
|
|
5012
4976
|
logger.error(result.error.message);
|
|
@@ -5045,23 +5009,23 @@ function printPatternProposals(patterns) {
|
|
|
5045
5009
|
);
|
|
5046
5010
|
}
|
|
5047
5011
|
function createPruneCommand() {
|
|
5048
|
-
return new
|
|
5012
|
+
return new Command39("prune").description(
|
|
5049
5013
|
"Analyze global learnings for patterns, present improvement proposals, and archive old entries"
|
|
5050
5014
|
).option("--path <path>", "Project root path", ".").option("--stream <name>", "Target a specific stream").action(handlePrune);
|
|
5051
5015
|
}
|
|
5052
5016
|
|
|
5053
5017
|
// src/commands/learnings/index.ts
|
|
5054
5018
|
function createLearningsCommand() {
|
|
5055
|
-
const command = new
|
|
5019
|
+
const command = new Command40("learnings").description("Learnings management commands");
|
|
5056
5020
|
command.addCommand(createPruneCommand());
|
|
5057
5021
|
return command;
|
|
5058
5022
|
}
|
|
5059
5023
|
|
|
5060
5024
|
// src/commands/linter/index.ts
|
|
5061
|
-
import { Command as
|
|
5025
|
+
import { Command as Command43 } from "commander";
|
|
5062
5026
|
|
|
5063
5027
|
// src/commands/linter/generate.ts
|
|
5064
|
-
import { Command as
|
|
5028
|
+
import { Command as Command41 } from "commander";
|
|
5065
5029
|
function formatGenerateError(e) {
|
|
5066
5030
|
switch (e.type) {
|
|
5067
5031
|
case "parse":
|
|
@@ -5105,7 +5069,7 @@ function handleSuccess(result, useJson) {
|
|
|
5105
5069
|
Generated ${result.rulesGenerated.length} rules to ${result.outputDir}`);
|
|
5106
5070
|
}
|
|
5107
5071
|
function createGenerateCommand2() {
|
|
5108
|
-
return new
|
|
5072
|
+
return new Command41("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) => {
|
|
5109
5073
|
try {
|
|
5110
5074
|
if (options.verbose) logger.info(`Parsing config: ${options.config}`);
|
|
5111
5075
|
const result = await generate({
|
|
@@ -5127,9 +5091,9 @@ function createGenerateCommand2() {
|
|
|
5127
5091
|
}
|
|
5128
5092
|
|
|
5129
5093
|
// src/commands/linter/validate.ts
|
|
5130
|
-
import { Command as
|
|
5094
|
+
import { Command as Command42 } from "commander";
|
|
5131
5095
|
function createValidateCommand() {
|
|
5132
|
-
return new
|
|
5096
|
+
return new Command42("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) => {
|
|
5133
5097
|
try {
|
|
5134
5098
|
const result = await validate({ configPath: options.config });
|
|
5135
5099
|
if (options.json) {
|
|
@@ -5148,7 +5112,7 @@ function createValidateCommand() {
|
|
|
5148
5112
|
|
|
5149
5113
|
// src/commands/linter/index.ts
|
|
5150
5114
|
function createLinterCommand() {
|
|
5151
|
-
const linter = new
|
|
5115
|
+
const linter = new Command43("linter").description(
|
|
5152
5116
|
"Generate and validate ESLint rules from YAML config"
|
|
5153
5117
|
);
|
|
5154
5118
|
linter.addCommand(createGenerateCommand2());
|
|
@@ -5157,22 +5121,22 @@ function createLinterCommand() {
|
|
|
5157
5121
|
}
|
|
5158
5122
|
|
|
5159
5123
|
// src/commands/mcp.ts
|
|
5160
|
-
import { Command as
|
|
5124
|
+
import { Command as Command44 } from "commander";
|
|
5161
5125
|
function createMcpCommand() {
|
|
5162
|
-
return new
|
|
5163
|
-
const { startServer: startServer2 } = await import("./mcp-
|
|
5126
|
+
return new Command44("mcp").description("Start the MCP (Model Context Protocol) server on stdio").option("--tools <tools...>", "Only register the specified tools (used by Cursor integration)").action(async (opts) => {
|
|
5127
|
+
const { startServer: startServer2 } = await import("./mcp-LCHC4NZ5.js");
|
|
5164
5128
|
await startServer2(opts.tools);
|
|
5165
5129
|
});
|
|
5166
5130
|
}
|
|
5167
5131
|
|
|
5168
5132
|
// src/commands/orchestrator.ts
|
|
5169
|
-
import { Command as
|
|
5170
|
-
import * as
|
|
5133
|
+
import { Command as Command45 } from "commander";
|
|
5134
|
+
import * as path38 from "path";
|
|
5171
5135
|
import { Orchestrator, WorkflowLoader, launchTUI } from "@harness-engineering/orchestrator";
|
|
5172
5136
|
function createOrchestratorCommand() {
|
|
5173
|
-
const orchestrator = new
|
|
5137
|
+
const orchestrator = new Command45("orchestrator");
|
|
5174
5138
|
orchestrator.command("run").description("Run the orchestrator daemon").option("-w, --workflow <path>", "Path to WORKFLOW.md", "WORKFLOW.md").action(async (opts) => {
|
|
5175
|
-
const workflowPath =
|
|
5139
|
+
const workflowPath = path38.resolve(process.cwd(), opts.workflow);
|
|
5176
5140
|
const loader = new WorkflowLoader();
|
|
5177
5141
|
const result = await loader.loadWorkflow(workflowPath);
|
|
5178
5142
|
if (!result.ok) {
|
|
@@ -5196,13 +5160,13 @@ function createOrchestratorCommand() {
|
|
|
5196
5160
|
}
|
|
5197
5161
|
|
|
5198
5162
|
// src/commands/perf.ts
|
|
5199
|
-
import { Command as
|
|
5200
|
-
import * as
|
|
5163
|
+
import { Command as Command46 } from "commander";
|
|
5164
|
+
import * as path39 from "path";
|
|
5201
5165
|
function registerBenchCommand(perf) {
|
|
5202
5166
|
perf.command("bench [glob]").description("Run benchmarks via vitest bench").action(async (glob, _opts, cmd) => {
|
|
5203
5167
|
const globalOpts = cmd.optsWithGlobals();
|
|
5204
5168
|
const cwd = process.cwd();
|
|
5205
|
-
const { BenchmarkRunner } = await import("./dist-
|
|
5169
|
+
const { BenchmarkRunner } = await import("./dist-7EBSGAHX.js");
|
|
5206
5170
|
const runner = new BenchmarkRunner();
|
|
5207
5171
|
const benchFiles = runner.discover(cwd, glob);
|
|
5208
5172
|
if (benchFiles.length === 0) {
|
|
@@ -5266,7 +5230,7 @@ async function runBaselinesShow(globalOpts) {
|
|
|
5266
5230
|
}
|
|
5267
5231
|
async function runBaselinesUpdate(globalOpts) {
|
|
5268
5232
|
const cwd = process.cwd();
|
|
5269
|
-
const { BenchmarkRunner } = await import("./dist-
|
|
5233
|
+
const { BenchmarkRunner } = await import("./dist-7EBSGAHX.js");
|
|
5270
5234
|
const runner = new BenchmarkRunner();
|
|
5271
5235
|
const manager = new BaselineManager(cwd);
|
|
5272
5236
|
logger.info("Running benchmarks to update baselines...");
|
|
@@ -5307,9 +5271,9 @@ function registerReportCommand(perf) {
|
|
|
5307
5271
|
perf.command("report").description("Full performance report with metrics, trends, and hotspots").action(async (_opts, cmd) => {
|
|
5308
5272
|
const globalOpts = cmd.optsWithGlobals();
|
|
5309
5273
|
const cwd = process.cwd();
|
|
5310
|
-
const { EntropyAnalyzer: EntropyAnalyzer2 } = await import("./dist-
|
|
5274
|
+
const { EntropyAnalyzer: EntropyAnalyzer2 } = await import("./dist-7EBSGAHX.js");
|
|
5311
5275
|
const analyzer = new EntropyAnalyzer2({
|
|
5312
|
-
rootDir:
|
|
5276
|
+
rootDir: path39.resolve(cwd),
|
|
5313
5277
|
analyze: { complexity: true, coupling: true }
|
|
5314
5278
|
});
|
|
5315
5279
|
const result = await analyzer.analyze();
|
|
@@ -5368,7 +5332,7 @@ function registerCriticalPathsCommand(perf) {
|
|
|
5368
5332
|
});
|
|
5369
5333
|
}
|
|
5370
5334
|
function createPerfCommand() {
|
|
5371
|
-
const perf = new
|
|
5335
|
+
const perf = new Command46("perf").description("Performance benchmark and baseline management");
|
|
5372
5336
|
registerBenchCommand(perf);
|
|
5373
5337
|
registerBaselinesCommands(perf);
|
|
5374
5338
|
registerReportCommand(perf);
|
|
@@ -5377,10 +5341,10 @@ function createPerfCommand() {
|
|
|
5377
5341
|
}
|
|
5378
5342
|
|
|
5379
5343
|
// src/commands/persona/index.ts
|
|
5380
|
-
import { Command as
|
|
5344
|
+
import { Command as Command49 } from "commander";
|
|
5381
5345
|
|
|
5382
5346
|
// src/commands/persona/list.ts
|
|
5383
|
-
import { Command as
|
|
5347
|
+
import { Command as Command47 } from "commander";
|
|
5384
5348
|
function printPersonaList(personas) {
|
|
5385
5349
|
if (personas.length === 0) {
|
|
5386
5350
|
logger.info("No personas found.");
|
|
@@ -5394,7 +5358,7 @@ function printPersonaList(personas) {
|
|
|
5394
5358
|
}
|
|
5395
5359
|
}
|
|
5396
5360
|
function createListCommand2() {
|
|
5397
|
-
return new
|
|
5361
|
+
return new Command47("list").description("List available agent personas").action(async (_opts, cmd) => {
|
|
5398
5362
|
const globalOpts = cmd.optsWithGlobals();
|
|
5399
5363
|
const personasDir = resolvePersonasDir();
|
|
5400
5364
|
const result = listPersonas(personasDir);
|
|
@@ -5414,17 +5378,17 @@ function createListCommand2() {
|
|
|
5414
5378
|
}
|
|
5415
5379
|
|
|
5416
5380
|
// src/commands/persona/generate.ts
|
|
5417
|
-
import { Command as
|
|
5381
|
+
import { Command as Command48 } from "commander";
|
|
5418
5382
|
import * as fs22 from "fs";
|
|
5419
|
-
import * as
|
|
5383
|
+
import * as path40 from "path";
|
|
5420
5384
|
function generatePersonaArtifacts(persona, outputDir, only) {
|
|
5421
5385
|
const slug = toKebabCase(persona.name);
|
|
5422
5386
|
const generated = [];
|
|
5423
5387
|
if (!only || only === "runtime") {
|
|
5424
5388
|
const result = generateRuntime(persona);
|
|
5425
5389
|
if (result.ok) {
|
|
5426
|
-
const outPath =
|
|
5427
|
-
fs22.mkdirSync(
|
|
5390
|
+
const outPath = path40.join(outputDir, `${slug}.runtime.json`);
|
|
5391
|
+
fs22.mkdirSync(path40.dirname(outPath), { recursive: true });
|
|
5428
5392
|
fs22.writeFileSync(outPath, result.value);
|
|
5429
5393
|
generated.push(outPath);
|
|
5430
5394
|
}
|
|
@@ -5432,7 +5396,7 @@ function generatePersonaArtifacts(persona, outputDir, only) {
|
|
|
5432
5396
|
if (!only || only === "agents-md") {
|
|
5433
5397
|
const result = generateAgentsMd(persona);
|
|
5434
5398
|
if (result.ok) {
|
|
5435
|
-
const outPath =
|
|
5399
|
+
const outPath = path40.join(outputDir, `${slug}.agents.md`);
|
|
5436
5400
|
fs22.writeFileSync(outPath, result.value);
|
|
5437
5401
|
generated.push(outPath);
|
|
5438
5402
|
}
|
|
@@ -5440,8 +5404,8 @@ function generatePersonaArtifacts(persona, outputDir, only) {
|
|
|
5440
5404
|
if (!only || only === "ci") {
|
|
5441
5405
|
const result = generateCIWorkflow(persona, "github");
|
|
5442
5406
|
if (result.ok) {
|
|
5443
|
-
const outPath =
|
|
5444
|
-
fs22.mkdirSync(
|
|
5407
|
+
const outPath = path40.join(outputDir, ".github", "workflows", `${slug}.yml`);
|
|
5408
|
+
fs22.mkdirSync(path40.dirname(outPath), { recursive: true });
|
|
5445
5409
|
fs22.writeFileSync(outPath, result.value);
|
|
5446
5410
|
generated.push(outPath);
|
|
5447
5411
|
}
|
|
@@ -5449,16 +5413,16 @@ function generatePersonaArtifacts(persona, outputDir, only) {
|
|
|
5449
5413
|
return generated;
|
|
5450
5414
|
}
|
|
5451
5415
|
function createGenerateCommand3() {
|
|
5452
|
-
return new
|
|
5416
|
+
return new Command48("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) => {
|
|
5453
5417
|
const globalOpts = cmd.optsWithGlobals();
|
|
5454
|
-
const personaResult = loadPersona(
|
|
5418
|
+
const personaResult = loadPersona(path40.join(resolvePersonasDir(), `${name}.yaml`));
|
|
5455
5419
|
if (!personaResult.ok) {
|
|
5456
5420
|
logger.error(personaResult.error.message);
|
|
5457
5421
|
process.exit(ExitCode.ERROR);
|
|
5458
5422
|
}
|
|
5459
5423
|
const generated = generatePersonaArtifacts(
|
|
5460
5424
|
personaResult.value,
|
|
5461
|
-
|
|
5425
|
+
path40.resolve(opts.outputDir),
|
|
5462
5426
|
opts.only
|
|
5463
5427
|
);
|
|
5464
5428
|
if (!globalOpts.quiet) {
|
|
@@ -5471,14 +5435,14 @@ function createGenerateCommand3() {
|
|
|
5471
5435
|
|
|
5472
5436
|
// src/commands/persona/index.ts
|
|
5473
5437
|
function createPersonaCommand() {
|
|
5474
|
-
const command = new
|
|
5438
|
+
const command = new Command49("persona").description("Agent persona management commands");
|
|
5475
5439
|
command.addCommand(createListCommand2());
|
|
5476
5440
|
command.addCommand(createGenerateCommand3());
|
|
5477
5441
|
return command;
|
|
5478
5442
|
}
|
|
5479
5443
|
|
|
5480
5444
|
// src/commands/predict.ts
|
|
5481
|
-
import { Command as
|
|
5445
|
+
import { Command as Command50 } from "commander";
|
|
5482
5446
|
import chalk6 from "chalk";
|
|
5483
5447
|
var CATEGORY_ORDER = [
|
|
5484
5448
|
"circular-deps",
|
|
@@ -5584,7 +5548,7 @@ function handlePredictError(err, mode) {
|
|
|
5584
5548
|
process.exit(ExitCode.ERROR);
|
|
5585
5549
|
}
|
|
5586
5550
|
function createPredictCommand() {
|
|
5587
|
-
const command = new
|
|
5551
|
+
const command = new Command50("predict").description("Predict which architectural constraints will break and when").option("--category <name>", "Filter to a single metric category").option("--no-roadmap", "Baseline only \u2014 skip roadmap spec impact").option("--horizon <weeks>", "Forecast horizon in weeks (default: 12)", "12").action(async (opts, cmd) => {
|
|
5588
5552
|
const globalOpts = cmd.optsWithGlobals();
|
|
5589
5553
|
const mode = globalOpts.json ? OutputMode.JSON : OutputMode.TEXT;
|
|
5590
5554
|
try {
|
|
@@ -5607,7 +5571,7 @@ function createPredictCommand() {
|
|
|
5607
5571
|
}
|
|
5608
5572
|
|
|
5609
5573
|
// src/commands/recommend.ts
|
|
5610
|
-
import { Command as
|
|
5574
|
+
import { Command as Command51 } from "commander";
|
|
5611
5575
|
import chalk7 from "chalk";
|
|
5612
5576
|
async function resolveSnapshot(cwd, noCache) {
|
|
5613
5577
|
if (!noCache) {
|
|
@@ -5671,7 +5635,7 @@ function printRecommendations(result) {
|
|
|
5671
5635
|
console.log("");
|
|
5672
5636
|
}
|
|
5673
5637
|
function createRecommendCommand() {
|
|
5674
|
-
const command = new
|
|
5638
|
+
const command = new Command51("recommend").description("Recommend skills based on codebase health analysis").option("--no-cache", "Force fresh health snapshot").option("--top <n>", "Max recommendations (default 5)", "5").action(async (opts, cmd) => {
|
|
5675
5639
|
const globalOpts = cmd.optsWithGlobals();
|
|
5676
5640
|
const mode = globalOpts.json ? OutputMode.JSON : OutputMode.TEXT;
|
|
5677
5641
|
try {
|
|
@@ -5707,9 +5671,9 @@ function createRecommendCommand() {
|
|
|
5707
5671
|
}
|
|
5708
5672
|
|
|
5709
5673
|
// src/commands/scan-config.ts
|
|
5710
|
-
import { Command as
|
|
5674
|
+
import { Command as Command52 } from "commander";
|
|
5711
5675
|
import { existsSync as existsSync21, readFileSync as readFileSync11, writeFileSync as writeFileSync12 } from "fs";
|
|
5712
|
-
import { join as
|
|
5676
|
+
import { join as join31, relative as relative2 } from "path";
|
|
5713
5677
|
var CONFIG_FILES = ["CLAUDE.md", "AGENTS.md", ".gemini/settings.json", "skill.yaml"];
|
|
5714
5678
|
function stripHighSeverityPatterns(content, injectionFindings) {
|
|
5715
5679
|
const highLines = /* @__PURE__ */ new Set();
|
|
@@ -5766,7 +5730,7 @@ async function runScanConfig(targetDir, options) {
|
|
|
5766
5730
|
const scanner = new SecurityScanner(parseSecurityConfig({}));
|
|
5767
5731
|
const results = [];
|
|
5768
5732
|
for (const configFile of CONFIG_FILES) {
|
|
5769
|
-
const result = scanSingleFile(
|
|
5733
|
+
const result = scanSingleFile(join31(targetDir, configFile), targetDir, scanner, options);
|
|
5770
5734
|
if (result) results.push(result);
|
|
5771
5735
|
}
|
|
5772
5736
|
return { exitCode: computeScanExitCode(results), results };
|
|
@@ -5796,7 +5760,7 @@ function formatTextOutput(result) {
|
|
|
5796
5760
|
}
|
|
5797
5761
|
}
|
|
5798
5762
|
function createScanConfigCommand() {
|
|
5799
|
-
const command = new
|
|
5763
|
+
const command = new Command52("scan-config").description(
|
|
5800
5764
|
"Scan CLAUDE.md, AGENTS.md, .gemini/settings.json, and skill.yaml for prompt injection patterns"
|
|
5801
5765
|
).option("--path <dir>", "Target directory to scan (default: cwd)").option("--fix", "Strip high-severity patterns from files in-place").action(async (opts, cmd) => {
|
|
5802
5766
|
const globalOpts = cmd.optsWithGlobals();
|
|
@@ -5813,18 +5777,18 @@ function createScanConfigCommand() {
|
|
|
5813
5777
|
}
|
|
5814
5778
|
|
|
5815
5779
|
// src/commands/setup.ts
|
|
5816
|
-
import { Command as
|
|
5780
|
+
import { Command as Command53 } from "commander";
|
|
5817
5781
|
import * as fs24 from "fs";
|
|
5818
5782
|
import * as os7 from "os";
|
|
5819
|
-
import * as
|
|
5783
|
+
import * as path42 from "path";
|
|
5820
5784
|
import chalk8 from "chalk";
|
|
5821
5785
|
|
|
5822
5786
|
// src/utils/first-run.ts
|
|
5823
5787
|
import * as fs23 from "fs";
|
|
5824
5788
|
import * as os6 from "os";
|
|
5825
|
-
import * as
|
|
5826
|
-
var HARNESS_DIR =
|
|
5827
|
-
var MARKER_FILE =
|
|
5789
|
+
import * as path41 from "path";
|
|
5790
|
+
var HARNESS_DIR = path41.join(os6.homedir(), ".harness");
|
|
5791
|
+
var MARKER_FILE = path41.join(HARNESS_DIR, ".setup-complete");
|
|
5828
5792
|
function isFirstRun() {
|
|
5829
5793
|
return !fs23.existsSync(MARKER_FILE);
|
|
5830
5794
|
}
|
|
@@ -5868,7 +5832,7 @@ function runSlashCommandGeneration() {
|
|
|
5868
5832
|
}
|
|
5869
5833
|
}
|
|
5870
5834
|
function detectClient(dirName) {
|
|
5871
|
-
return fs24.existsSync(
|
|
5835
|
+
return fs24.existsSync(path42.join(os7.homedir(), dirName));
|
|
5872
5836
|
}
|
|
5873
5837
|
async function runMcpSetup(cwd) {
|
|
5874
5838
|
const results = [];
|
|
@@ -5910,7 +5874,7 @@ function formatStep(result) {
|
|
|
5910
5874
|
}
|
|
5911
5875
|
function configureTier0Integrations(cwd) {
|
|
5912
5876
|
try {
|
|
5913
|
-
const mcpPath =
|
|
5877
|
+
const mcpPath = path42.join(cwd, ".mcp.json");
|
|
5914
5878
|
const config = readMcpConfig(mcpPath);
|
|
5915
5879
|
const tier0 = INTEGRATION_REGISTRY.filter((i) => i.tier === 0);
|
|
5916
5880
|
const added = [];
|
|
@@ -5919,9 +5883,9 @@ function configureTier0Integrations(cwd) {
|
|
|
5919
5883
|
writeMcpEntry(mcpPath, integration.name, integration.mcpConfig);
|
|
5920
5884
|
added.push(integration.displayName);
|
|
5921
5885
|
}
|
|
5922
|
-
const geminiDir =
|
|
5886
|
+
const geminiDir = path42.join(cwd, ".gemini");
|
|
5923
5887
|
if (fs24.existsSync(geminiDir)) {
|
|
5924
|
-
const geminiPath =
|
|
5888
|
+
const geminiPath = path42.join(geminiDir, "settings.json");
|
|
5925
5889
|
const geminiConfig = readMcpConfig(geminiPath);
|
|
5926
5890
|
for (const integration of tier0) {
|
|
5927
5891
|
if (!geminiConfig.mcpServers[integration.name]) {
|
|
@@ -5941,6 +5905,44 @@ function configureTier0Integrations(cwd) {
|
|
|
5941
5905
|
return { status: "fail", message: `Tier 0 integration configuration failed \u2014 ${msg}` };
|
|
5942
5906
|
}
|
|
5943
5907
|
}
|
|
5908
|
+
function ensureHooks(cwd) {
|
|
5909
|
+
const configPath = path42.join(cwd, "harness.config.json");
|
|
5910
|
+
if (!fs24.existsSync(configPath)) {
|
|
5911
|
+
return { status: "warn", message: "Not a harness project \u2014 skipped hook installation" };
|
|
5912
|
+
}
|
|
5913
|
+
let profile = "standard";
|
|
5914
|
+
const profilePath = path42.join(cwd, ".harness", "hooks", "profile.json");
|
|
5915
|
+
try {
|
|
5916
|
+
const data = JSON.parse(fs24.readFileSync(profilePath, "utf-8"));
|
|
5917
|
+
if (data.profile && ["minimal", "standard", "strict"].includes(data.profile)) {
|
|
5918
|
+
profile = data.profile;
|
|
5919
|
+
}
|
|
5920
|
+
} catch {
|
|
5921
|
+
}
|
|
5922
|
+
try {
|
|
5923
|
+
const result = initHooks({ profile, projectDir: cwd });
|
|
5924
|
+
return {
|
|
5925
|
+
status: "pass",
|
|
5926
|
+
message: `Installed ${result.copiedScripts.length} hooks (${profile} profile)`
|
|
5927
|
+
};
|
|
5928
|
+
} catch (error) {
|
|
5929
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
5930
|
+
return { status: "warn", message: `Hook installation skipped \u2014 ${msg}` };
|
|
5931
|
+
}
|
|
5932
|
+
}
|
|
5933
|
+
async function runInitialGraphScan(cwd) {
|
|
5934
|
+
try {
|
|
5935
|
+
const { runScan: runScan2 } = await import("./scan-U67OKDRS.js");
|
|
5936
|
+
const result = await runScan2(cwd);
|
|
5937
|
+
return {
|
|
5938
|
+
status: "pass",
|
|
5939
|
+
message: `Built knowledge graph: ${result.nodeCount} nodes, ${result.edgeCount} edges`
|
|
5940
|
+
};
|
|
5941
|
+
} catch (error) {
|
|
5942
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
5943
|
+
return { status: "warn", message: `Knowledge graph creation skipped \u2014 ${msg}` };
|
|
5944
|
+
}
|
|
5945
|
+
}
|
|
5944
5946
|
async function runSetup(cwd) {
|
|
5945
5947
|
const steps = [];
|
|
5946
5948
|
const nodeResult = checkNodeVersion3();
|
|
@@ -5954,6 +5956,10 @@ async function runSetup(cwd) {
|
|
|
5954
5956
|
steps.push(...mcpResults);
|
|
5955
5957
|
const tier0Result = configureTier0Integrations(cwd);
|
|
5956
5958
|
steps.push(tier0Result);
|
|
5959
|
+
const hooksResult = ensureHooks(cwd);
|
|
5960
|
+
steps.push(hooksResult);
|
|
5961
|
+
const graphResult = await runInitialGraphScan(cwd);
|
|
5962
|
+
steps.push(graphResult);
|
|
5957
5963
|
const success = steps.every((s) => s.status !== "fail");
|
|
5958
5964
|
if (success) {
|
|
5959
5965
|
markSetupComplete();
|
|
@@ -5961,7 +5967,7 @@ async function runSetup(cwd) {
|
|
|
5961
5967
|
return { steps, success };
|
|
5962
5968
|
}
|
|
5963
5969
|
function createSetupCommand() {
|
|
5964
|
-
return new
|
|
5970
|
+
return new Command53("setup").description("Configure harness environment: slash commands, MCP, and more").action(async () => {
|
|
5965
5971
|
const cwd = process.cwd();
|
|
5966
5972
|
console.log("");
|
|
5967
5973
|
console.log(` ${chalk8.bold("harness setup")}`);
|
|
@@ -5983,14 +5989,14 @@ function createSetupCommand() {
|
|
|
5983
5989
|
}
|
|
5984
5990
|
|
|
5985
5991
|
// src/commands/share.ts
|
|
5986
|
-
import { Command as
|
|
5992
|
+
import { Command as Command54 } from "commander";
|
|
5987
5993
|
import * as fs25 from "fs";
|
|
5988
|
-
import * as
|
|
5994
|
+
import * as path43 from "path";
|
|
5989
5995
|
import { parse as parseYaml } from "yaml";
|
|
5990
5996
|
var MANIFEST_FILENAME = "constraints.yaml";
|
|
5991
5997
|
async function runShareAction(projectPath, options) {
|
|
5992
|
-
const rootDir =
|
|
5993
|
-
const manifestPath =
|
|
5998
|
+
const rootDir = path43.resolve(projectPath);
|
|
5999
|
+
const manifestPath = path43.join(rootDir, MANIFEST_FILENAME);
|
|
5994
6000
|
if (!fs25.existsSync(manifestPath)) {
|
|
5995
6001
|
logger.error(
|
|
5996
6002
|
`No ${MANIFEST_FILENAME} found at ${manifestPath}.
|
|
@@ -6014,7 +6020,7 @@ Create a constraints.yaml in your project root to define what to share.`
|
|
|
6014
6020
|
process.exit(1);
|
|
6015
6021
|
}
|
|
6016
6022
|
const manifest = manifestResult.value;
|
|
6017
|
-
const configResult = resolveConfig(
|
|
6023
|
+
const configResult = resolveConfig(path43.join(rootDir, "harness.config.json"));
|
|
6018
6024
|
if (!configResult.ok) {
|
|
6019
6025
|
logger.error(configResult.error.message);
|
|
6020
6026
|
process.exit(1);
|
|
@@ -6032,8 +6038,8 @@ Create a constraints.yaml in your project root to define what to share.`
|
|
|
6032
6038
|
);
|
|
6033
6039
|
process.exit(1);
|
|
6034
6040
|
}
|
|
6035
|
-
const outputDir =
|
|
6036
|
-
const outputPath =
|
|
6041
|
+
const outputDir = path43.resolve(options.output);
|
|
6042
|
+
const outputPath = path43.join(outputDir, `${manifest.name}.harness-constraints.json`);
|
|
6037
6043
|
const writeResult = await writeConfig(outputPath, bundle);
|
|
6038
6044
|
if (!writeResult.ok) {
|
|
6039
6045
|
logger.error(`Failed to write bundle: ${writeResult.error.message}`);
|
|
@@ -6042,25 +6048,25 @@ Create a constraints.yaml in your project root to define what to share.`
|
|
|
6042
6048
|
logger.success(`Bundle written to ${outputPath}`);
|
|
6043
6049
|
}
|
|
6044
6050
|
function createShareCommand() {
|
|
6045
|
-
return new
|
|
6051
|
+
return new Command54("share").description("Extract and publish a constraints bundle from constraints.yaml").argument("[path]", "Path to the project root", ".").option("-o, --output <dir>", "Output directory for the bundle", ".").action(async (projectPath, options) => {
|
|
6046
6052
|
await runShareAction(projectPath, options);
|
|
6047
6053
|
});
|
|
6048
6054
|
}
|
|
6049
6055
|
|
|
6050
6056
|
// src/commands/skill/index.ts
|
|
6051
|
-
import { Command as
|
|
6057
|
+
import { Command as Command62 } from "commander";
|
|
6052
6058
|
|
|
6053
6059
|
// src/commands/skill/list.ts
|
|
6054
|
-
import { Command as
|
|
6060
|
+
import { Command as Command55 } from "commander";
|
|
6055
6061
|
import * as fs26 from "fs";
|
|
6056
|
-
import * as
|
|
6062
|
+
import * as path44 from "path";
|
|
6057
6063
|
import { parse } from "yaml";
|
|
6058
6064
|
function scanDirectory(dirPath, source) {
|
|
6059
6065
|
if (!fs26.existsSync(dirPath)) return [];
|
|
6060
6066
|
const entries = fs26.readdirSync(dirPath, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
|
|
6061
6067
|
const skills = [];
|
|
6062
6068
|
for (const name of entries) {
|
|
6063
|
-
const yamlPath =
|
|
6069
|
+
const yamlPath = path44.join(dirPath, name, "skill.yaml");
|
|
6064
6070
|
if (!fs26.existsSync(yamlPath)) continue;
|
|
6065
6071
|
try {
|
|
6066
6072
|
const raw = fs26.readFileSync(yamlPath, "utf-8");
|
|
@@ -6081,10 +6087,10 @@ function scanDirectory(dirPath, source) {
|
|
|
6081
6087
|
}
|
|
6082
6088
|
function collectCommunitySkills(seen, allSkills) {
|
|
6083
6089
|
const globalDir = resolveGlobalSkillsDir();
|
|
6084
|
-
const skillsDir =
|
|
6085
|
-
const communityBase =
|
|
6086
|
-
const communityPlatformDir =
|
|
6087
|
-
const lockfilePath =
|
|
6090
|
+
const skillsDir = path44.dirname(globalDir);
|
|
6091
|
+
const communityBase = path44.join(skillsDir, "community");
|
|
6092
|
+
const communityPlatformDir = path44.join(communityBase, "claude-code");
|
|
6093
|
+
const lockfilePath = path44.join(communityBase, "skills-lock.json");
|
|
6088
6094
|
const lockfile = readLockfile2(lockfilePath);
|
|
6089
6095
|
const communitySkills = scanDirectory(communityPlatformDir, "community");
|
|
6090
6096
|
for (const skill of communitySkills) {
|
|
@@ -6154,7 +6160,7 @@ function printSkillsVerbose(skills) {
|
|
|
6154
6160
|
for (const s of skills) printSkillEntry(s);
|
|
6155
6161
|
}
|
|
6156
6162
|
function createListCommand3() {
|
|
6157
|
-
return new
|
|
6163
|
+
return new Command55("list").description("List available skills").option("--installed", "Show only community-installed skills").option("--local", "Show only project-local skills").option("--all", "Show all skills (default)").action(async (opts, cmd) => {
|
|
6158
6164
|
const globalOpts = cmd.optsWithGlobals();
|
|
6159
6165
|
const skills = collectSkills({ filter: resolveFilter(opts) });
|
|
6160
6166
|
if (globalOpts.json) {
|
|
@@ -6169,9 +6175,9 @@ function createListCommand3() {
|
|
|
6169
6175
|
}
|
|
6170
6176
|
|
|
6171
6177
|
// src/commands/skill/run.ts
|
|
6172
|
-
import { Command as
|
|
6178
|
+
import { Command as Command56 } from "commander";
|
|
6173
6179
|
import * as fs27 from "fs";
|
|
6174
|
-
import * as
|
|
6180
|
+
import * as path45 from "path";
|
|
6175
6181
|
import { parse as parse2 } from "yaml";
|
|
6176
6182
|
|
|
6177
6183
|
// src/skill/complexity.ts
|
|
@@ -6267,7 +6273,7 @@ ${options.principles}`);
|
|
|
6267
6273
|
|
|
6268
6274
|
// src/commands/skill/run.ts
|
|
6269
6275
|
function loadSkillMetadata(skillDir) {
|
|
6270
|
-
const yamlPath =
|
|
6276
|
+
const yamlPath = path45.join(skillDir, "skill.yaml");
|
|
6271
6277
|
if (!fs27.existsSync(yamlPath)) return null;
|
|
6272
6278
|
try {
|
|
6273
6279
|
const result = SkillMetadataSchema.safeParse(parse2(fs27.readFileSync(yamlPath, "utf-8")));
|
|
@@ -6282,18 +6288,18 @@ function resolveComplexity(metadata, requested, projectPath) {
|
|
|
6282
6288
|
return requested;
|
|
6283
6289
|
}
|
|
6284
6290
|
function loadPrinciples(projectPath) {
|
|
6285
|
-
const principlesPath =
|
|
6291
|
+
const principlesPath = path45.join(projectPath, "docs", "principles.md");
|
|
6286
6292
|
return fs27.existsSync(principlesPath) ? fs27.readFileSync(principlesPath, "utf-8") : void 0;
|
|
6287
6293
|
}
|
|
6288
6294
|
function readMostRecentFileInDir(dirPath) {
|
|
6289
|
-
const files = fs27.readdirSync(dirPath).map((f) => ({ name: f, mtime: fs27.statSync(
|
|
6290
|
-
if (files.length > 0) return fs27.readFileSync(
|
|
6295
|
+
const files = fs27.readdirSync(dirPath).map((f) => ({ name: f, mtime: fs27.statSync(path45.join(dirPath, f)).mtimeMs })).sort((a, b) => b.mtime - a.mtime);
|
|
6296
|
+
if (files.length > 0) return fs27.readFileSync(path45.join(dirPath, files[0].name), "utf-8");
|
|
6291
6297
|
return void 0;
|
|
6292
6298
|
}
|
|
6293
6299
|
function loadPriorState(metadata, projectPath) {
|
|
6294
6300
|
if (!metadata?.state.persistent || metadata.state.files.length === 0) return void 0;
|
|
6295
6301
|
for (const stateFilePath of metadata.state.files) {
|
|
6296
|
-
const fullPath =
|
|
6302
|
+
const fullPath = path45.join(projectPath, stateFilePath);
|
|
6297
6303
|
if (!fs27.existsSync(fullPath)) continue;
|
|
6298
6304
|
const stat = fs27.statSync(fullPath);
|
|
6299
6305
|
if (stat.isDirectory()) return readMostRecentFileInDir(fullPath);
|
|
@@ -6317,7 +6323,7 @@ function resolvePhaseState(metadata, projectPath, phase) {
|
|
|
6317
6323
|
}
|
|
6318
6324
|
function appendProjectState(content, metadata, projectPath, hasPathOpt) {
|
|
6319
6325
|
if (!metadata?.state.persistent || !hasPathOpt) return content;
|
|
6320
|
-
const stateFile =
|
|
6326
|
+
const stateFile = path45.join(projectPath, ".harness", "state.json");
|
|
6321
6327
|
if (!fs27.existsSync(stateFile)) return content;
|
|
6322
6328
|
const stateContent = fs27.readFileSync(stateFile, "utf-8");
|
|
6323
6329
|
return content + `
|
|
@@ -6331,14 +6337,14 @@ ${stateContent}
|
|
|
6331
6337
|
}
|
|
6332
6338
|
async function runSkill(name, opts) {
|
|
6333
6339
|
const skillsDir = resolveSkillsDir();
|
|
6334
|
-
const skillDir =
|
|
6340
|
+
const skillDir = path45.join(skillsDir, name);
|
|
6335
6341
|
if (!fs27.existsSync(skillDir)) {
|
|
6336
6342
|
logger.error(`Skill not found: ${name}`);
|
|
6337
6343
|
process.exit(ExitCode.ERROR);
|
|
6338
6344
|
return;
|
|
6339
6345
|
}
|
|
6340
6346
|
const metadata = loadSkillMetadata(skillDir);
|
|
6341
|
-
const projectPath = opts.path ?
|
|
6347
|
+
const projectPath = opts.path ? path45.resolve(opts.path) : process.cwd();
|
|
6342
6348
|
const complexity = resolveComplexity(
|
|
6343
6349
|
metadata,
|
|
6344
6350
|
opts.complexity ?? "standard",
|
|
@@ -6365,7 +6371,7 @@ async function runSkill(name, opts) {
|
|
|
6365
6371
|
...stateWarning !== void 0 && { stateWarning },
|
|
6366
6372
|
...opts.party !== void 0 && { party: opts.party }
|
|
6367
6373
|
});
|
|
6368
|
-
const skillMdPath =
|
|
6374
|
+
const skillMdPath = path45.join(skillDir, "SKILL.md");
|
|
6369
6375
|
if (!fs27.existsSync(skillMdPath)) {
|
|
6370
6376
|
logger.error(`SKILL.md not found for skill: ${name}`);
|
|
6371
6377
|
process.exit(ExitCode.ERROR);
|
|
@@ -6381,13 +6387,13 @@ async function runSkill(name, opts) {
|
|
|
6381
6387
|
process.exit(ExitCode.SUCCESS);
|
|
6382
6388
|
}
|
|
6383
6389
|
function createRunCommand2() {
|
|
6384
|
-
return new
|
|
6390
|
+
return new Command56("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>", "Rigor level: fast, standard, thorough", "standard").option("--phase <name>", "Start at a specific phase (for re-entry)").option("--party", "Enable multi-perspective evaluation").action(async (name, opts) => runSkill(name, opts));
|
|
6385
6391
|
}
|
|
6386
6392
|
|
|
6387
6393
|
// src/commands/skill/validate.ts
|
|
6388
|
-
import { Command as
|
|
6394
|
+
import { Command as Command57 } from "commander";
|
|
6389
6395
|
import * as fs28 from "fs";
|
|
6390
|
-
import * as
|
|
6396
|
+
import * as path46 from "path";
|
|
6391
6397
|
import { parse as parse3 } from "yaml";
|
|
6392
6398
|
var BEHAVIORAL_REQUIRED_SECTIONS = [
|
|
6393
6399
|
"## When to Use",
|
|
@@ -6428,8 +6434,8 @@ function validateSkillMd(name, skillMdPath, skillType, errors) {
|
|
|
6428
6434
|
}
|
|
6429
6435
|
}
|
|
6430
6436
|
function validateSkillEntry(name, skillsDir, errors) {
|
|
6431
|
-
const skillDir =
|
|
6432
|
-
const yamlPath =
|
|
6437
|
+
const skillDir = path46.join(skillsDir, name);
|
|
6438
|
+
const yamlPath = path46.join(skillDir, "skill.yaml");
|
|
6433
6439
|
if (!fs28.existsSync(yamlPath)) {
|
|
6434
6440
|
errors.push(`${name}: missing skill.yaml`);
|
|
6435
6441
|
return false;
|
|
@@ -6441,7 +6447,7 @@ function validateSkillEntry(name, skillsDir, errors) {
|
|
|
6441
6447
|
errors.push(`${name}/skill.yaml: ${result.error.message}`);
|
|
6442
6448
|
return false;
|
|
6443
6449
|
}
|
|
6444
|
-
validateSkillMd(name,
|
|
6450
|
+
validateSkillMd(name, path46.join(skillDir, "SKILL.md"), result.data.type, errors);
|
|
6445
6451
|
return true;
|
|
6446
6452
|
} catch (e) {
|
|
6447
6453
|
errors.push(`${name}: parse error \u2014 ${e instanceof Error ? e.message : String(e)}`);
|
|
@@ -6449,7 +6455,7 @@ function validateSkillEntry(name, skillsDir, errors) {
|
|
|
6449
6455
|
}
|
|
6450
6456
|
}
|
|
6451
6457
|
function createValidateCommand2() {
|
|
6452
|
-
return new
|
|
6458
|
+
return new Command57("validate").description("Validate all skill.yaml files and SKILL.md structure").action(async (_opts, cmd) => {
|
|
6453
6459
|
const globalOpts = cmd.optsWithGlobals();
|
|
6454
6460
|
const skillsDir = resolveSkillsDir();
|
|
6455
6461
|
if (!fs28.existsSync(skillsDir)) {
|
|
@@ -6479,18 +6485,18 @@ function createValidateCommand2() {
|
|
|
6479
6485
|
}
|
|
6480
6486
|
|
|
6481
6487
|
// src/commands/skill/info.ts
|
|
6482
|
-
import { Command as
|
|
6488
|
+
import { Command as Command58 } from "commander";
|
|
6483
6489
|
import * as fs29 from "fs";
|
|
6484
|
-
import * as
|
|
6490
|
+
import * as path47 from "path";
|
|
6485
6491
|
import { parse as parse4 } from "yaml";
|
|
6486
6492
|
function loadSkillMetadata2(name) {
|
|
6487
6493
|
const skillsDir = resolveSkillsDir();
|
|
6488
|
-
const skillDir =
|
|
6494
|
+
const skillDir = path47.join(skillsDir, name);
|
|
6489
6495
|
if (!fs29.existsSync(skillDir)) {
|
|
6490
6496
|
logger.error(`Skill not found: ${name}`);
|
|
6491
6497
|
return { ok: false, exitCode: ExitCode.ERROR };
|
|
6492
6498
|
}
|
|
6493
|
-
const yamlPath =
|
|
6499
|
+
const yamlPath = path47.join(skillDir, "skill.yaml");
|
|
6494
6500
|
if (!fs29.existsSync(yamlPath)) {
|
|
6495
6501
|
logger.error(`skill.yaml not found for skill: ${name}`);
|
|
6496
6502
|
return { ok: false, exitCode: ExitCode.ERROR };
|
|
@@ -6525,7 +6531,7 @@ function printSkillInfo(skill) {
|
|
|
6525
6531
|
console.log(`Persistent: ${skill.state.persistent}`);
|
|
6526
6532
|
}
|
|
6527
6533
|
function createInfoCommand() {
|
|
6528
|
-
return new
|
|
6534
|
+
return new Command58("info").description("Show metadata for a skill").argument("<name>", "Skill name (e.g., harness-tdd)").action(async (name, _opts, cmd) => {
|
|
6529
6535
|
const globalOpts = cmd.optsWithGlobals();
|
|
6530
6536
|
const loaded = loadSkillMetadata2(name);
|
|
6531
6537
|
if (!loaded.ok) {
|
|
@@ -6542,7 +6548,7 @@ function createInfoCommand() {
|
|
|
6542
6548
|
}
|
|
6543
6549
|
|
|
6544
6550
|
// src/commands/skill/search.ts
|
|
6545
|
-
import { Command as
|
|
6551
|
+
import { Command as Command59 } from "commander";
|
|
6546
6552
|
async function runSearch(query, opts) {
|
|
6547
6553
|
const results = await searchNpmRegistry(query, opts.registry);
|
|
6548
6554
|
return results.filter((r) => {
|
|
@@ -6587,14 +6593,14 @@ async function runSearchAction(query, opts, globalOpts) {
|
|
|
6587
6593
|
}
|
|
6588
6594
|
}
|
|
6589
6595
|
function createSearchCommand() {
|
|
6590
|
-
return new
|
|
6596
|
+
return new Command59("search").description("Search for community skills on the @harness-skills registry").argument("<query>", "Search query").option("--platform <platform>", "Filter by platform (e.g., claude-code)").option("--trigger <trigger>", "Filter by trigger type (e.g., manual, automatic)").option("--registry <url>", "Use a custom npm registry URL").action(async (query, opts, cmd) => {
|
|
6591
6597
|
await runSearchAction(query, opts, cmd.optsWithGlobals());
|
|
6592
6598
|
});
|
|
6593
6599
|
}
|
|
6594
6600
|
|
|
6595
6601
|
// src/commands/skill/create.ts
|
|
6596
|
-
import { Command as
|
|
6597
|
-
import * as
|
|
6602
|
+
import { Command as Command60 } from "commander";
|
|
6603
|
+
import * as path48 from "path";
|
|
6598
6604
|
import * as fs30 from "fs";
|
|
6599
6605
|
import YAML from "yaml";
|
|
6600
6606
|
var KEBAB_CASE_RE = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/;
|
|
@@ -6679,21 +6685,21 @@ function runCreate(name, opts) {
|
|
|
6679
6685
|
if (!KEBAB_CASE_RE.test(name)) {
|
|
6680
6686
|
throw new Error(`Invalid skill name "${name}". Must be kebab-case (e.g., my-skill).`);
|
|
6681
6687
|
}
|
|
6682
|
-
const baseDir = opts.outputDir ??
|
|
6683
|
-
const skillDir =
|
|
6688
|
+
const baseDir = opts.outputDir ?? path48.join(process.cwd(), "agents", "skills", "claude-code");
|
|
6689
|
+
const skillDir = path48.join(baseDir, name);
|
|
6684
6690
|
if (fs30.existsSync(skillDir)) {
|
|
6685
6691
|
throw new Error(`Skill directory already exists: ${skillDir}`);
|
|
6686
6692
|
}
|
|
6687
6693
|
fs30.mkdirSync(skillDir, { recursive: true });
|
|
6688
6694
|
const description = opts.description || `A community skill: ${name}`;
|
|
6689
6695
|
const skillYaml = buildSkillYaml(name, opts);
|
|
6690
|
-
const skillYamlPath =
|
|
6696
|
+
const skillYamlPath = path48.join(skillDir, "skill.yaml");
|
|
6691
6697
|
fs30.writeFileSync(skillYamlPath, YAML.stringify(skillYaml));
|
|
6692
6698
|
const skillMd = buildSkillMd(name, description);
|
|
6693
|
-
const skillMdPath =
|
|
6699
|
+
const skillMdPath = path48.join(skillDir, "SKILL.md");
|
|
6694
6700
|
fs30.writeFileSync(skillMdPath, skillMd);
|
|
6695
6701
|
const readme = buildReadme(name, description);
|
|
6696
|
-
const readmePath =
|
|
6702
|
+
const readmePath = path48.join(skillDir, "README.md");
|
|
6697
6703
|
fs30.writeFileSync(readmePath, readme);
|
|
6698
6704
|
return {
|
|
6699
6705
|
name,
|
|
@@ -6708,12 +6714,12 @@ function printCreateResult(name, result) {
|
|
|
6708
6714
|
}
|
|
6709
6715
|
logger.info(`
|
|
6710
6716
|
Next steps:`);
|
|
6711
|
-
logger.info(` 1. Edit ${
|
|
6717
|
+
logger.info(` 1. Edit ${path48.join(result.directory, "SKILL.md")} with your skill content`);
|
|
6712
6718
|
logger.info(` 2. Run: harness skill validate ${name}`);
|
|
6713
6719
|
logger.info(` 3. Run: harness skills publish`);
|
|
6714
6720
|
}
|
|
6715
6721
|
function createCreateCommand() {
|
|
6716
|
-
return new
|
|
6722
|
+
return new Command60("create").description("Scaffold a new community skill").argument("<name>", "Skill name (kebab-case)").option("--description <desc>", "Skill description").option("--type <type>", "Skill type: rigid or flexible", "flexible").option("--platforms <platforms>", "Comma-separated platforms (default: claude-code)").option("--triggers <triggers>", "Comma-separated triggers (default: manual)").option("--output-dir <dir>", "Output directory (default: agents/skills/claude-code/)").action(async (name, opts, cmd) => {
|
|
6717
6723
|
const globalOpts = cmd.optsWithGlobals();
|
|
6718
6724
|
try {
|
|
6719
6725
|
const result = runCreate(name, {
|
|
@@ -6736,19 +6742,19 @@ function createCreateCommand() {
|
|
|
6736
6742
|
}
|
|
6737
6743
|
|
|
6738
6744
|
// src/commands/skill/publish.ts
|
|
6739
|
-
import { Command as
|
|
6745
|
+
import { Command as Command61 } from "commander";
|
|
6740
6746
|
import * as fs32 from "fs";
|
|
6741
|
-
import * as
|
|
6747
|
+
import * as path50 from "path";
|
|
6742
6748
|
import { execFileSync as execFileSync5 } from "child_process";
|
|
6743
6749
|
|
|
6744
6750
|
// src/registry/validator.ts
|
|
6745
6751
|
import * as fs31 from "fs";
|
|
6746
|
-
import * as
|
|
6752
|
+
import * as path49 from "path";
|
|
6747
6753
|
import { parse as parse5 } from "yaml";
|
|
6748
6754
|
import semver4 from "semver";
|
|
6749
6755
|
async function validateForPublish(skillDir, registryUrl) {
|
|
6750
6756
|
const errors = [];
|
|
6751
|
-
const skillYamlPath =
|
|
6757
|
+
const skillYamlPath = path49.join(skillDir, "skill.yaml");
|
|
6752
6758
|
if (!fs31.existsSync(skillYamlPath)) {
|
|
6753
6759
|
errors.push("skill.yaml not found. Create one with: harness skill create <name>");
|
|
6754
6760
|
return { valid: false, errors };
|
|
@@ -6777,7 +6783,7 @@ async function validateForPublish(skillDir, registryUrl) {
|
|
|
6777
6783
|
if (!skillMeta.triggers || skillMeta.triggers.length === 0) {
|
|
6778
6784
|
errors.push("At least one trigger is required. Add triggers to skill.yaml.");
|
|
6779
6785
|
}
|
|
6780
|
-
const skillMdPath =
|
|
6786
|
+
const skillMdPath = path49.join(skillDir, "SKILL.md");
|
|
6781
6787
|
if (!fs31.existsSync(skillMdPath)) {
|
|
6782
6788
|
errors.push("SKILL.md not found. Create it with content describing your skill.");
|
|
6783
6789
|
} else {
|
|
@@ -6861,11 +6867,11 @@ ${errorList}`);
|
|
|
6861
6867
|
}
|
|
6862
6868
|
const meta = validation.skillMeta;
|
|
6863
6869
|
const pkg = derivePackageJson(meta);
|
|
6864
|
-
const pkgPath =
|
|
6870
|
+
const pkgPath = path50.join(skillDir, "package.json");
|
|
6865
6871
|
fs32.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
|
|
6866
|
-
const readmePath =
|
|
6872
|
+
const readmePath = path50.join(skillDir, "README.md");
|
|
6867
6873
|
if (!fs32.existsSync(readmePath)) {
|
|
6868
|
-
const skillMdContent = fs32.readFileSync(
|
|
6874
|
+
const skillMdContent = fs32.readFileSync(path50.join(skillDir, "SKILL.md"), "utf-8");
|
|
6869
6875
|
const readme = `# ${pkg.name}
|
|
6870
6876
|
|
|
6871
6877
|
${meta.description}
|
|
@@ -6905,7 +6911,7 @@ ${skillMdContent}`;
|
|
|
6905
6911
|
};
|
|
6906
6912
|
}
|
|
6907
6913
|
function createPublishCommand() {
|
|
6908
|
-
return new
|
|
6914
|
+
return new Command61("publish").description("Validate and publish a skill to @harness-skills on npm").option("--dry-run", "Run validation and generate package.json without publishing").option("--dir <dir>", "Skill directory (default: current directory)").option("--registry <url>", "Use a custom npm registry URL").action(async (opts, cmd) => {
|
|
6909
6915
|
const globalOpts = cmd.optsWithGlobals();
|
|
6910
6916
|
const skillDir = opts.dir || process.cwd();
|
|
6911
6917
|
try {
|
|
@@ -6930,7 +6936,7 @@ function createPublishCommand() {
|
|
|
6930
6936
|
|
|
6931
6937
|
// src/commands/skill/index.ts
|
|
6932
6938
|
function createSkillCommand() {
|
|
6933
|
-
const command = new
|
|
6939
|
+
const command = new Command62("skill").description("Skill management commands");
|
|
6934
6940
|
command.addCommand(createListCommand3());
|
|
6935
6941
|
command.addCommand(createRunCommand2());
|
|
6936
6942
|
command.addCommand(createValidateCommand2());
|
|
@@ -6942,7 +6948,7 @@ function createSkillCommand() {
|
|
|
6942
6948
|
}
|
|
6943
6949
|
|
|
6944
6950
|
// src/commands/snapshot.ts
|
|
6945
|
-
import { Command as
|
|
6951
|
+
import { Command as Command63 } from "commander";
|
|
6946
6952
|
import { execSync as execSync5 } from "child_process";
|
|
6947
6953
|
import chalk9 from "chalk";
|
|
6948
6954
|
function getCommitHash3(cwd) {
|
|
@@ -7124,7 +7130,7 @@ function registerListCommand(parent) {
|
|
|
7124
7130
|
});
|
|
7125
7131
|
}
|
|
7126
7132
|
function createSnapshotCommand() {
|
|
7127
|
-
const command = new
|
|
7133
|
+
const command = new Command63("snapshot").description("Architecture timeline snapshot commands");
|
|
7128
7134
|
registerCaptureCommand(command);
|
|
7129
7135
|
registerTrendsCommand(command);
|
|
7130
7136
|
registerListCommand(command);
|
|
@@ -7132,11 +7138,11 @@ function createSnapshotCommand() {
|
|
|
7132
7138
|
}
|
|
7133
7139
|
|
|
7134
7140
|
// src/commands/state/index.ts
|
|
7135
|
-
import { Command as
|
|
7141
|
+
import { Command as Command68 } from "commander";
|
|
7136
7142
|
|
|
7137
7143
|
// src/commands/state/show.ts
|
|
7138
|
-
import { Command as
|
|
7139
|
-
import * as
|
|
7144
|
+
import { Command as Command64 } from "commander";
|
|
7145
|
+
import * as path51 from "path";
|
|
7140
7146
|
function printStateText(state, stream) {
|
|
7141
7147
|
if (stream) console.log(`Stream: ${stream}`);
|
|
7142
7148
|
console.log(`Schema Version: ${state.schemaVersion}`);
|
|
@@ -7160,9 +7166,9 @@ function printStateProgress(progress) {
|
|
|
7160
7166
|
}
|
|
7161
7167
|
}
|
|
7162
7168
|
function createShowCommand() {
|
|
7163
|
-
return new
|
|
7169
|
+
return new Command64("show").description("Show current project state").option("--path <path>", "Project root path", ".").option("--stream <name>", "Target a specific stream").action(async (opts, cmd) => {
|
|
7164
7170
|
const globalOpts = cmd.optsWithGlobals();
|
|
7165
|
-
const result = await loadState(
|
|
7171
|
+
const result = await loadState(path51.resolve(opts.path), opts.stream);
|
|
7166
7172
|
if (!result.ok) {
|
|
7167
7173
|
logger.error(result.error.message);
|
|
7168
7174
|
process.exit(ExitCode.ERROR);
|
|
@@ -7177,9 +7183,9 @@ function createShowCommand() {
|
|
|
7177
7183
|
}
|
|
7178
7184
|
|
|
7179
7185
|
// src/commands/state/reset.ts
|
|
7180
|
-
import { Command as
|
|
7186
|
+
import { Command as Command65 } from "commander";
|
|
7181
7187
|
import * as fs33 from "fs";
|
|
7182
|
-
import * as
|
|
7188
|
+
import * as path52 from "path";
|
|
7183
7189
|
import * as readline from "readline";
|
|
7184
7190
|
async function resolveStatePath(projectPath, stream) {
|
|
7185
7191
|
if (stream) {
|
|
@@ -7188,20 +7194,20 @@ async function resolveStatePath(projectPath, stream) {
|
|
|
7188
7194
|
logger.error(streamResult.error.message);
|
|
7189
7195
|
process.exit(ExitCode.ERROR);
|
|
7190
7196
|
}
|
|
7191
|
-
return
|
|
7197
|
+
return path52.join(streamResult.value, "state.json");
|
|
7192
7198
|
}
|
|
7193
|
-
return
|
|
7199
|
+
return path52.join(projectPath, ".harness", "state.json");
|
|
7194
7200
|
}
|
|
7195
7201
|
async function confirmReset() {
|
|
7196
7202
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
7197
|
-
const answer = await new Promise((
|
|
7198
|
-
rl.question("Reset project state? This cannot be undone. [y/N] ",
|
|
7203
|
+
const answer = await new Promise((resolve32) => {
|
|
7204
|
+
rl.question("Reset project state? This cannot be undone. [y/N] ", resolve32);
|
|
7199
7205
|
});
|
|
7200
7206
|
rl.close();
|
|
7201
7207
|
return answer.toLowerCase() === "y" || answer.toLowerCase() === "yes";
|
|
7202
7208
|
}
|
|
7203
7209
|
async function runReset(opts) {
|
|
7204
|
-
const projectPath =
|
|
7210
|
+
const projectPath = path52.resolve(opts.path);
|
|
7205
7211
|
const statePath = await resolveStatePath(projectPath, opts.stream);
|
|
7206
7212
|
if (!fs33.existsSync(statePath)) {
|
|
7207
7213
|
logger.info("No state file found. Nothing to reset.");
|
|
@@ -7221,15 +7227,15 @@ async function runReset(opts) {
|
|
|
7221
7227
|
process.exit(ExitCode.SUCCESS);
|
|
7222
7228
|
}
|
|
7223
7229
|
function createResetCommand() {
|
|
7224
|
-
return new
|
|
7230
|
+
return new Command65("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) => runReset(opts));
|
|
7225
7231
|
}
|
|
7226
7232
|
|
|
7227
7233
|
// src/commands/state/learn.ts
|
|
7228
|
-
import { Command as
|
|
7229
|
-
import * as
|
|
7234
|
+
import { Command as Command66 } from "commander";
|
|
7235
|
+
import * as path53 from "path";
|
|
7230
7236
|
function createLearnCommand() {
|
|
7231
|
-
return new
|
|
7232
|
-
const projectPath =
|
|
7237
|
+
return new Command66("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) => {
|
|
7238
|
+
const projectPath = path53.resolve(opts.path);
|
|
7233
7239
|
const result = await appendLearning(projectPath, message, void 0, void 0, opts.stream);
|
|
7234
7240
|
if (!result.ok) {
|
|
7235
7241
|
logger.error(result.error.message);
|
|
@@ -7242,10 +7248,10 @@ function createLearnCommand() {
|
|
|
7242
7248
|
}
|
|
7243
7249
|
|
|
7244
7250
|
// src/commands/state/streams.ts
|
|
7245
|
-
import { Command as
|
|
7246
|
-
import * as
|
|
7251
|
+
import { Command as Command67 } from "commander";
|
|
7252
|
+
import * as path54 from "path";
|
|
7247
7253
|
async function runListStreams(opts, globalOpts) {
|
|
7248
|
-
const projectPath =
|
|
7254
|
+
const projectPath = path54.resolve(opts.path);
|
|
7249
7255
|
const indexResult = await loadStreamIndex(projectPath);
|
|
7250
7256
|
const result = await listStreams(projectPath);
|
|
7251
7257
|
if (!result.ok) {
|
|
@@ -7266,7 +7272,7 @@ async function runListStreams(opts, globalOpts) {
|
|
|
7266
7272
|
process.exit(ExitCode.SUCCESS);
|
|
7267
7273
|
}
|
|
7268
7274
|
async function runCreateStream(name, opts) {
|
|
7269
|
-
const result = await createStream(
|
|
7275
|
+
const result = await createStream(path54.resolve(opts.path), name, opts.branch);
|
|
7270
7276
|
if (!result.ok) {
|
|
7271
7277
|
logger.error(result.error.message);
|
|
7272
7278
|
process.exit(ExitCode.ERROR);
|
|
@@ -7275,7 +7281,7 @@ async function runCreateStream(name, opts) {
|
|
|
7275
7281
|
process.exit(ExitCode.SUCCESS);
|
|
7276
7282
|
}
|
|
7277
7283
|
async function runArchiveStream(name, opts) {
|
|
7278
|
-
const result = await archiveStream(
|
|
7284
|
+
const result = await archiveStream(path54.resolve(opts.path), name);
|
|
7279
7285
|
if (!result.ok) {
|
|
7280
7286
|
logger.error(result.error.message);
|
|
7281
7287
|
process.exit(ExitCode.ERROR);
|
|
@@ -7284,7 +7290,7 @@ async function runArchiveStream(name, opts) {
|
|
|
7284
7290
|
process.exit(ExitCode.SUCCESS);
|
|
7285
7291
|
}
|
|
7286
7292
|
async function runActivateStream(name, opts) {
|
|
7287
|
-
const result = await setActiveStream(
|
|
7293
|
+
const result = await setActiveStream(path54.resolve(opts.path), name);
|
|
7288
7294
|
if (!result.ok) {
|
|
7289
7295
|
logger.error(result.error.message);
|
|
7290
7296
|
process.exit(ExitCode.ERROR);
|
|
@@ -7293,7 +7299,7 @@ async function runActivateStream(name, opts) {
|
|
|
7293
7299
|
process.exit(ExitCode.SUCCESS);
|
|
7294
7300
|
}
|
|
7295
7301
|
function createStreamsCommand() {
|
|
7296
|
-
const command = new
|
|
7302
|
+
const command = new Command67("streams").description("Manage state streams");
|
|
7297
7303
|
command.command("list").description("List all known streams").option("--path <path>", "Project root path", ".").action(async (opts, cmd) => runListStreams(opts, cmd.optsWithGlobals()));
|
|
7298
7304
|
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) => runCreateStream(name, opts));
|
|
7299
7305
|
command.command("archive <name>").description("Archive a stream").option("--path <path>", "Project root path", ".").action(async (name, opts) => runArchiveStream(name, opts));
|
|
@@ -7303,7 +7309,7 @@ function createStreamsCommand() {
|
|
|
7303
7309
|
|
|
7304
7310
|
// src/commands/state/index.ts
|
|
7305
7311
|
function createStateCommand() {
|
|
7306
|
-
const command = new
|
|
7312
|
+
const command = new Command68("state").description("Project state management commands");
|
|
7307
7313
|
command.addCommand(createShowCommand());
|
|
7308
7314
|
command.addCommand(createResetCommand());
|
|
7309
7315
|
command.addCommand(createLearnCommand());
|
|
@@ -7312,7 +7318,7 @@ function createStateCommand() {
|
|
|
7312
7318
|
}
|
|
7313
7319
|
|
|
7314
7320
|
// src/commands/taint.ts
|
|
7315
|
-
import { Command as
|
|
7321
|
+
import { Command as Command69 } from "commander";
|
|
7316
7322
|
function getProjectRoot() {
|
|
7317
7323
|
return process.cwd();
|
|
7318
7324
|
}
|
|
@@ -7384,25 +7390,25 @@ function registerStatusCommand(taint) {
|
|
|
7384
7390
|
});
|
|
7385
7391
|
}
|
|
7386
7392
|
function createTaintCommand() {
|
|
7387
|
-
const taint = new
|
|
7393
|
+
const taint = new Command69("taint").description("Manage sentinel session taint state");
|
|
7388
7394
|
registerClearCommand(taint);
|
|
7389
7395
|
registerStatusCommand(taint);
|
|
7390
7396
|
return taint;
|
|
7391
7397
|
}
|
|
7392
7398
|
|
|
7393
7399
|
// src/commands/telemetry/index.ts
|
|
7394
|
-
import { Command as
|
|
7400
|
+
import { Command as Command72 } from "commander";
|
|
7395
7401
|
|
|
7396
7402
|
// src/commands/telemetry/identify.ts
|
|
7397
|
-
import { Command as
|
|
7403
|
+
import { Command as Command70 } from "commander";
|
|
7398
7404
|
import * as fs34 from "fs";
|
|
7399
|
-
import * as
|
|
7405
|
+
import * as path55 from "path";
|
|
7400
7406
|
function telemetryFilePath(cwd) {
|
|
7401
|
-
return
|
|
7407
|
+
return path55.join(cwd, ".harness", "telemetry.json");
|
|
7402
7408
|
}
|
|
7403
7409
|
function writeTelemetryFile(cwd, data) {
|
|
7404
7410
|
const filePath = telemetryFilePath(cwd);
|
|
7405
|
-
const dir =
|
|
7411
|
+
const dir = path55.dirname(filePath);
|
|
7406
7412
|
fs34.mkdirSync(dir, { recursive: true });
|
|
7407
7413
|
fs34.writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n", "utf-8");
|
|
7408
7414
|
}
|
|
@@ -7418,7 +7424,7 @@ function printIdentity(identity) {
|
|
|
7418
7424
|
}
|
|
7419
7425
|
}
|
|
7420
7426
|
function createIdentifyCommand() {
|
|
7421
|
-
const cmd = new
|
|
7427
|
+
const cmd = new Command70("identify").description("Set or clear telemetry identity fields in .harness/telemetry.json").option("--project <name>", "Project name").option("--team <name>", "Team name").option("--alias <name>", "User alias").option("--clear", "Remove all identity fields").action((opts) => {
|
|
7422
7428
|
const cwd = process.cwd();
|
|
7423
7429
|
if (opts.clear) {
|
|
7424
7430
|
writeTelemetryFile(cwd, { identity: {} });
|
|
@@ -7442,7 +7448,7 @@ function createIdentifyCommand() {
|
|
|
7442
7448
|
}
|
|
7443
7449
|
|
|
7444
7450
|
// src/commands/telemetry/status.ts
|
|
7445
|
-
import { Command as
|
|
7451
|
+
import { Command as Command71 } from "commander";
|
|
7446
7452
|
function gatherEnvOverrides() {
|
|
7447
7453
|
const overrides = {};
|
|
7448
7454
|
if (process.env.DO_NOT_TRACK) overrides.DO_NOT_TRACK = process.env.DO_NOT_TRACK;
|
|
@@ -7480,7 +7486,7 @@ function printHumanStatus(result) {
|
|
|
7480
7486
|
}
|
|
7481
7487
|
}
|
|
7482
7488
|
function createStatusCommand() {
|
|
7483
|
-
const cmd = new
|
|
7489
|
+
const cmd = new Command71("status").description("Show current telemetry consent state, install ID, and identity").option("--json", "Output as JSON").action((opts) => {
|
|
7484
7490
|
const cwd = process.cwd();
|
|
7485
7491
|
const envOverrides = gatherEnvOverrides();
|
|
7486
7492
|
const consent = resolveConsent(cwd, void 0);
|
|
@@ -7513,14 +7519,14 @@ function createStatusCommand() {
|
|
|
7513
7519
|
|
|
7514
7520
|
// src/commands/telemetry/index.ts
|
|
7515
7521
|
function createTelemetryCommand() {
|
|
7516
|
-
const command = new
|
|
7522
|
+
const command = new Command72("telemetry").description("Telemetry identity and status management");
|
|
7517
7523
|
command.addCommand(createIdentifyCommand());
|
|
7518
7524
|
command.addCommand(createStatusCommand());
|
|
7519
7525
|
return command;
|
|
7520
7526
|
}
|
|
7521
7527
|
|
|
7522
7528
|
// src/commands/traceability.ts
|
|
7523
|
-
import { Command as
|
|
7529
|
+
import { Command as Command73 } from "commander";
|
|
7524
7530
|
import chalk10 from "chalk";
|
|
7525
7531
|
function confidenceLabel(maxConfidence) {
|
|
7526
7532
|
if (maxConfidence >= 0.8) return "explicit";
|
|
@@ -7651,7 +7657,7 @@ async function runTraceability(options) {
|
|
|
7651
7657
|
process.exit(ExitCode.SUCCESS);
|
|
7652
7658
|
}
|
|
7653
7659
|
function createTraceabilityCommand() {
|
|
7654
|
-
return new
|
|
7660
|
+
return new Command73("traceability").description("Show spec-to-implementation traceability from the knowledge graph").option("--spec <path>", "Filter by spec file path").option("--feature <name>", "Filter by feature name").action(async (opts, cmd) => {
|
|
7655
7661
|
const globalOpts = cmd.optsWithGlobals();
|
|
7656
7662
|
await runTraceability({
|
|
7657
7663
|
spec: opts.spec,
|
|
@@ -7664,15 +7670,15 @@ function createTraceabilityCommand() {
|
|
|
7664
7670
|
}
|
|
7665
7671
|
|
|
7666
7672
|
// src/commands/uninstall.ts
|
|
7667
|
-
import * as
|
|
7668
|
-
import { Command as
|
|
7673
|
+
import * as path56 from "path";
|
|
7674
|
+
import { Command as Command74 } from "commander";
|
|
7669
7675
|
async function runUninstall(skillName, options) {
|
|
7670
7676
|
const packageName = resolvePackageName(skillName);
|
|
7671
7677
|
const shortName = extractSkillName(packageName);
|
|
7672
7678
|
const globalDir = resolveGlobalSkillsDir();
|
|
7673
|
-
const skillsDir =
|
|
7674
|
-
const communityBase =
|
|
7675
|
-
const lockfilePath =
|
|
7679
|
+
const skillsDir = path56.dirname(globalDir);
|
|
7680
|
+
const communityBase = path56.join(skillsDir, "community");
|
|
7681
|
+
const lockfilePath = path56.join(communityBase, "skills-lock.json");
|
|
7676
7682
|
const lockfile = readLockfile2(lockfilePath);
|
|
7677
7683
|
const entry = lockfile.skills[packageName];
|
|
7678
7684
|
if (!entry) {
|
|
@@ -7702,7 +7708,7 @@ async function runUninstall(skillName, options) {
|
|
|
7702
7708
|
return result;
|
|
7703
7709
|
}
|
|
7704
7710
|
function createUninstallCommand() {
|
|
7705
|
-
const cmd = new
|
|
7711
|
+
const cmd = new Command74("uninstall");
|
|
7706
7712
|
cmd.description("Uninstall a community skill").argument("<skill>", "Skill name or @harness-skills/scoped package name").option("--force", "Remove even if other skills depend on this one").action(async (skill, opts) => {
|
|
7707
7713
|
try {
|
|
7708
7714
|
const result = await runUninstall(skill, opts);
|
|
@@ -7722,8 +7728,8 @@ function createUninstallCommand() {
|
|
|
7722
7728
|
|
|
7723
7729
|
// src/commands/uninstall-constraints.ts
|
|
7724
7730
|
import * as fs35 from "fs/promises";
|
|
7725
|
-
import * as
|
|
7726
|
-
import { Command as
|
|
7731
|
+
import * as path57 from "path";
|
|
7732
|
+
import { Command as Command75 } from "commander";
|
|
7727
7733
|
async function runUninstallConstraints(options) {
|
|
7728
7734
|
const { packageName, configPath, lockfilePath } = options;
|
|
7729
7735
|
const lockfileResult = await readLockfile(lockfilePath);
|
|
@@ -7780,11 +7786,11 @@ async function runUninstallConstraints(options) {
|
|
|
7780
7786
|
};
|
|
7781
7787
|
}
|
|
7782
7788
|
function createUninstallConstraintsCommand() {
|
|
7783
|
-
const cmd = new
|
|
7789
|
+
const cmd = new Command75("uninstall-constraints");
|
|
7784
7790
|
cmd.description("Remove a previously installed constraints package").argument("<name>", "Name of the constraint package to uninstall").option("-c, --config <path>", "Path to harness.config.json").action(async (name, opts) => {
|
|
7785
7791
|
let configPath;
|
|
7786
7792
|
if (opts.config) {
|
|
7787
|
-
configPath =
|
|
7793
|
+
configPath = path57.resolve(opts.config);
|
|
7788
7794
|
} else {
|
|
7789
7795
|
const found = findConfigFile();
|
|
7790
7796
|
if (!found.ok) {
|
|
@@ -7793,8 +7799,8 @@ function createUninstallConstraintsCommand() {
|
|
|
7793
7799
|
}
|
|
7794
7800
|
configPath = found.value;
|
|
7795
7801
|
}
|
|
7796
|
-
const projectRoot =
|
|
7797
|
-
const lockfilePath =
|
|
7802
|
+
const projectRoot = path57.dirname(configPath);
|
|
7803
|
+
const lockfilePath = path57.join(projectRoot, ".harness", "constraints.lock.json");
|
|
7798
7804
|
const result = await runUninstallConstraints({
|
|
7799
7805
|
packageName: name,
|
|
7800
7806
|
configPath,
|
|
@@ -7819,9 +7825,10 @@ function createUninstallConstraintsCommand() {
|
|
|
7819
7825
|
}
|
|
7820
7826
|
|
|
7821
7827
|
// src/commands/update.ts
|
|
7822
|
-
import { Command as
|
|
7828
|
+
import { Command as Command76 } from "commander";
|
|
7823
7829
|
import { execFile, execFileSync as execFileSync6 } from "child_process";
|
|
7824
|
-
import { realpathSync } from "fs";
|
|
7830
|
+
import { realpathSync, existsSync as existsSync33, readFileSync as readFileSync20 } from "fs";
|
|
7831
|
+
import { join as join46 } from "path";
|
|
7825
7832
|
import { promisify } from "util";
|
|
7826
7833
|
import readline2 from "readline";
|
|
7827
7834
|
import chalk11 from "chalk";
|
|
@@ -7887,16 +7894,48 @@ function prompt(question) {
|
|
|
7887
7894
|
input: process.stdin,
|
|
7888
7895
|
output: process.stdout
|
|
7889
7896
|
});
|
|
7890
|
-
return new Promise((
|
|
7897
|
+
return new Promise((resolve32) => {
|
|
7891
7898
|
rl.question(question, (answer) => {
|
|
7892
7899
|
rl.close();
|
|
7893
|
-
|
|
7900
|
+
resolve32(answer.trim().toLowerCase());
|
|
7894
7901
|
});
|
|
7895
7902
|
});
|
|
7896
7903
|
}
|
|
7904
|
+
function refreshHooks() {
|
|
7905
|
+
const cwd = process.cwd();
|
|
7906
|
+
const configPath = join46(cwd, "harness.config.json");
|
|
7907
|
+
if (!existsSync33(configPath)) return;
|
|
7908
|
+
let profile = "standard";
|
|
7909
|
+
const profilePath = join46(cwd, ".harness", "hooks", "profile.json");
|
|
7910
|
+
try {
|
|
7911
|
+
const data = JSON.parse(readFileSync20(profilePath, "utf-8"));
|
|
7912
|
+
if (data.profile && ["minimal", "standard", "strict"].includes(data.profile)) {
|
|
7913
|
+
profile = data.profile;
|
|
7914
|
+
}
|
|
7915
|
+
} catch {
|
|
7916
|
+
}
|
|
7917
|
+
try {
|
|
7918
|
+
const result = initHooks({ profile, projectDir: cwd });
|
|
7919
|
+
logger.success(`Refreshed ${result.copiedScripts.length} hooks (${profile} profile)`);
|
|
7920
|
+
} catch (err) {
|
|
7921
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
7922
|
+
logger.warn(`Hook refresh failed: ${msg}`);
|
|
7923
|
+
}
|
|
7924
|
+
}
|
|
7925
|
+
function runLocalGraphScan() {
|
|
7926
|
+
try {
|
|
7927
|
+
logger.info("Scanning codebase to rebuild knowledge graph...");
|
|
7928
|
+
execFileSync6("harness", ["graph", "scan", "."], { stdio: "inherit" });
|
|
7929
|
+
} catch {
|
|
7930
|
+
logger.warn("Graph scan failed. Run manually:");
|
|
7931
|
+
console.log(` ${chalk11.cyan("harness graph scan .")}`);
|
|
7932
|
+
}
|
|
7933
|
+
}
|
|
7897
7934
|
async function offerRegeneration() {
|
|
7898
7935
|
console.log("");
|
|
7899
|
-
const regenAnswer = await prompt(
|
|
7936
|
+
const regenAnswer = await prompt(
|
|
7937
|
+
"Regenerate slash commands, agent definitions, and knowledge graph? (Y/n) "
|
|
7938
|
+
);
|
|
7900
7939
|
if (regenAnswer === "n" || regenAnswer === "no") return;
|
|
7901
7940
|
const scopeAnswer = await prompt("Generate for (G)lobal or (l)ocal project? (G/l) ");
|
|
7902
7941
|
const isGlobal = scopeAnswer !== "l" && scopeAnswer !== "local";
|
|
@@ -7908,6 +7947,9 @@ async function offerRegeneration() {
|
|
|
7908
7947
|
logger.warn("Generation failed. Run manually:");
|
|
7909
7948
|
console.log(` ${chalk11.cyan(`harness generate${isGlobal ? " --global" : ""}`)}`);
|
|
7910
7949
|
}
|
|
7950
|
+
if (!isGlobal) {
|
|
7951
|
+
runLocalGraphScan();
|
|
7952
|
+
}
|
|
7911
7953
|
}
|
|
7912
7954
|
async function checkAllPackages(packages, installedVersions) {
|
|
7913
7955
|
logger.info("Checking for updates...");
|
|
@@ -7958,6 +8000,7 @@ async function runUpdateAction(opts, globalOpts) {
|
|
|
7958
8000
|
const { hasUpdates, outdated } = await checkAllPackages(packages, installedVersions);
|
|
7959
8001
|
if (!hasUpdates) {
|
|
7960
8002
|
logger.success("All packages are up to date");
|
|
8003
|
+
refreshHooks();
|
|
7961
8004
|
await offerRegeneration();
|
|
7962
8005
|
process.exit(ExitCode.SUCCESS);
|
|
7963
8006
|
}
|
|
@@ -7984,11 +8027,12 @@ async function runUpdateAction(opts, globalOpts) {
|
|
|
7984
8027
|
console.log(` ${chalk11.cyan(installCmd)}`);
|
|
7985
8028
|
process.exit(ExitCode.ERROR);
|
|
7986
8029
|
}
|
|
8030
|
+
refreshHooks();
|
|
7987
8031
|
await offerRegeneration();
|
|
7988
8032
|
process.exit(ExitCode.SUCCESS);
|
|
7989
8033
|
}
|
|
7990
8034
|
function createUpdateCommand() {
|
|
7991
|
-
return new
|
|
8035
|
+
return new Command76("update").description("Update all @harness-engineering packages to the latest version").option("--version <semver>", "Pin @harness-engineering/cli to a specific version").option("--force", "Force update even if versions match").option(
|
|
7992
8036
|
"--regenerate",
|
|
7993
8037
|
"Only regenerate slash commands and agent definitions (skip package updates)"
|
|
7994
8038
|
).action(async (opts, cmd) => {
|
|
@@ -7998,9 +8042,9 @@ function createUpdateCommand() {
|
|
|
7998
8042
|
}
|
|
7999
8043
|
|
|
8000
8044
|
// src/commands/usage.ts
|
|
8001
|
-
import { Command as
|
|
8045
|
+
import { Command as Command77 } from "commander";
|
|
8002
8046
|
async function loadAndPriceRecords(cwd, includeClaudeSessions = false) {
|
|
8003
|
-
const { readCostRecords, loadPricingData, calculateCost, parseCCRecords } = await import("./dist-
|
|
8047
|
+
const { readCostRecords, loadPricingData, calculateCost, parseCCRecords } = await import("./dist-7EBSGAHX.js");
|
|
8004
8048
|
const records = readCostRecords(cwd);
|
|
8005
8049
|
if (includeClaudeSessions) {
|
|
8006
8050
|
const ccRecords = parseCCRecords();
|
|
@@ -8051,7 +8095,7 @@ function registerDailyCommand(usage) {
|
|
|
8051
8095
|
}
|
|
8052
8096
|
return;
|
|
8053
8097
|
}
|
|
8054
|
-
const { aggregateByDay } = await import("./dist-
|
|
8098
|
+
const { aggregateByDay } = await import("./dist-7EBSGAHX.js");
|
|
8055
8099
|
const dailyData = aggregateByDay(records);
|
|
8056
8100
|
const limited = dailyData.slice(0, days);
|
|
8057
8101
|
if (globalOpts.json) {
|
|
@@ -8103,7 +8147,7 @@ function registerSessionsCommand(usage) {
|
|
|
8103
8147
|
}
|
|
8104
8148
|
return;
|
|
8105
8149
|
}
|
|
8106
|
-
const { aggregateBySession } = await import("./dist-
|
|
8150
|
+
const { aggregateBySession } = await import("./dist-7EBSGAHX.js");
|
|
8107
8151
|
const sessionData = aggregateBySession(records);
|
|
8108
8152
|
const limited = sessionData.slice(0, limit);
|
|
8109
8153
|
if (globalOpts.json) {
|
|
@@ -8176,7 +8220,7 @@ function registerSessionCommand(usage) {
|
|
|
8176
8220
|
const globalOpts = cmd.optsWithGlobals();
|
|
8177
8221
|
const cwd = process.cwd();
|
|
8178
8222
|
const records = await loadAndPriceRecords(cwd, globalOpts.includeClaudeSessions);
|
|
8179
|
-
const { aggregateBySession } = await import("./dist-
|
|
8223
|
+
const { aggregateBySession } = await import("./dist-7EBSGAHX.js");
|
|
8180
8224
|
const sessionData = aggregateBySession(records);
|
|
8181
8225
|
const match = sessionData.find((s) => s.sessionId === id);
|
|
8182
8226
|
if (!match) {
|
|
@@ -8209,7 +8253,7 @@ function registerLatestCommand(usage) {
|
|
|
8209
8253
|
}
|
|
8210
8254
|
return;
|
|
8211
8255
|
}
|
|
8212
|
-
const { aggregateBySession } = await import("./dist-
|
|
8256
|
+
const { aggregateBySession } = await import("./dist-7EBSGAHX.js");
|
|
8213
8257
|
const sessionData = aggregateBySession(records);
|
|
8214
8258
|
const latest = sessionData[0];
|
|
8215
8259
|
if (!latest) {
|
|
@@ -8235,7 +8279,7 @@ function registerLatestCommand(usage) {
|
|
|
8235
8279
|
});
|
|
8236
8280
|
}
|
|
8237
8281
|
function createUsageCommand() {
|
|
8238
|
-
const usage = new
|
|
8282
|
+
const usage = new Command77("usage").description("Token usage and cost tracking");
|
|
8239
8283
|
usage.option(
|
|
8240
8284
|
"--include-claude-sessions",
|
|
8241
8285
|
"Include Claude Code session data from ~/.claude/projects/"
|
|
@@ -8248,15 +8292,15 @@ function createUsageCommand() {
|
|
|
8248
8292
|
}
|
|
8249
8293
|
|
|
8250
8294
|
// src/commands/validate.ts
|
|
8251
|
-
import { Command as
|
|
8252
|
-
import * as
|
|
8295
|
+
import { Command as Command78 } from "commander";
|
|
8296
|
+
import * as path58 from "path";
|
|
8253
8297
|
async function runValidate(options) {
|
|
8254
8298
|
const configResult = resolveConfig(options.configPath);
|
|
8255
8299
|
if (!configResult.ok) {
|
|
8256
8300
|
return configResult;
|
|
8257
8301
|
}
|
|
8258
8302
|
const config = configResult.value;
|
|
8259
|
-
const cwd = options.cwd ?? (options.configPath ?
|
|
8303
|
+
const cwd = options.cwd ?? (options.configPath ? path58.dirname(path58.resolve(options.configPath)) : process.cwd());
|
|
8260
8304
|
const result = {
|
|
8261
8305
|
valid: true,
|
|
8262
8306
|
checks: {
|
|
@@ -8266,7 +8310,7 @@ async function runValidate(options) {
|
|
|
8266
8310
|
},
|
|
8267
8311
|
issues: []
|
|
8268
8312
|
};
|
|
8269
|
-
const agentsMapPath =
|
|
8313
|
+
const agentsMapPath = path58.resolve(cwd, config.agentsMapPath);
|
|
8270
8314
|
const agentsResult = await validateAgentsMap(agentsMapPath);
|
|
8271
8315
|
if (agentsResult.ok) {
|
|
8272
8316
|
result.checks.agentsMap = true;
|
|
@@ -8311,11 +8355,11 @@ function resolveValidateMode(globalOpts) {
|
|
|
8311
8355
|
return OutputMode.TEXT;
|
|
8312
8356
|
}
|
|
8313
8357
|
async function printCrossCheckWarnings(mode) {
|
|
8314
|
-
const { runCrossCheck: runCrossCheck2 } = await import("./validate-cross-check-
|
|
8358
|
+
const { runCrossCheck: runCrossCheck2 } = await import("./validate-cross-check-Y4PDR63C.js");
|
|
8315
8359
|
const cwd = process.cwd();
|
|
8316
8360
|
const crossResult = await runCrossCheck2({
|
|
8317
|
-
specsDir:
|
|
8318
|
-
plansDir:
|
|
8361
|
+
specsDir: path58.join(cwd, "docs", "specs"),
|
|
8362
|
+
plansDir: path58.join(cwd, "docs", "plans"),
|
|
8319
8363
|
projectPath: cwd
|
|
8320
8364
|
});
|
|
8321
8365
|
if (!crossResult.ok || crossResult.value.warnings === 0) return;
|
|
@@ -8327,7 +8371,7 @@ async function printCrossCheckWarnings(mode) {
|
|
|
8327
8371
|
${crossResult.value.warnings} warnings`);
|
|
8328
8372
|
}
|
|
8329
8373
|
function createValidateCommand3() {
|
|
8330
|
-
const command = new
|
|
8374
|
+
const command = new Command78("validate").description("Run all validation checks").option("--cross-check", "Run cross-artifact consistency validation").action(async (opts, cmd) => {
|
|
8331
8375
|
const globalOpts = cmd.optsWithGlobals();
|
|
8332
8376
|
const mode = resolveValidateMode(globalOpts);
|
|
8333
8377
|
const formatter = new OutputFormatter(mode);
|
|
@@ -8412,7 +8456,7 @@ var commandCreators = [
|
|
|
8412
8456
|
|
|
8413
8457
|
// src/index.ts
|
|
8414
8458
|
function createProgram() {
|
|
8415
|
-
const program = new
|
|
8459
|
+
const program = new Command79();
|
|
8416
8460
|
program.name("harness").description("CLI for Harness Engineering toolkit").version(CLI_VERSION).option("-c, --config <path>", "Path to config file").option("--json", "Output as JSON").option("--verbose", "Verbose output").option("--quiet", "Minimal output");
|
|
8417
8461
|
for (const creator of commandCreators) {
|
|
8418
8462
|
program.addCommand(creator());
|
|
@@ -8424,7 +8468,6 @@ export {
|
|
|
8424
8468
|
runCheckArch,
|
|
8425
8469
|
runGraphStatus,
|
|
8426
8470
|
runGraphExport,
|
|
8427
|
-
runScan,
|
|
8428
8471
|
runQuery,
|
|
8429
8472
|
runIngest,
|
|
8430
8473
|
runImpactPreview,
|