@harness-engineering/cli 1.24.1 → 1.25.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agents/skills/claude-code/harness-architecture-advisor/SKILL.md +10 -6
- package/dist/agents/skills/claude-code/harness-code-review/SKILL.md +40 -5
- package/dist/agents/skills/claude-code/harness-git-workflow/SKILL.md +32 -11
- package/dist/agents/skills/claude-code/harness-planning/SKILL.md +19 -0
- package/dist/agents/skills/claude-code/harness-skill-authoring/SKILL.md +42 -7
- package/dist/agents/skills/claude-code/harness-verification/SKILL.md +25 -0
- package/dist/agents/skills/codex/harness-architecture-advisor/SKILL.md +10 -6
- package/dist/agents/skills/codex/harness-code-review/SKILL.md +40 -5
- package/dist/agents/skills/codex/harness-git-workflow/SKILL.md +32 -11
- package/dist/agents/skills/codex/harness-planning/SKILL.md +19 -0
- package/dist/agents/skills/codex/harness-skill-authoring/SKILL.md +42 -7
- package/dist/agents/skills/codex/harness-verification/SKILL.md +25 -0
- package/dist/agents/skills/cursor/harness-architecture-advisor/SKILL.md +10 -6
- package/dist/agents/skills/cursor/harness-code-review/SKILL.md +40 -5
- package/dist/agents/skills/cursor/harness-git-workflow/SKILL.md +32 -11
- package/dist/agents/skills/cursor/harness-planning/SKILL.md +19 -0
- package/dist/agents/skills/cursor/harness-skill-authoring/SKILL.md +42 -7
- package/dist/agents/skills/cursor/harness-verification/SKILL.md +25 -0
- package/dist/agents/skills/gemini-cli/harness-architecture-advisor/SKILL.md +10 -6
- package/dist/agents/skills/gemini-cli/harness-code-review/SKILL.md +40 -5
- package/dist/agents/skills/gemini-cli/harness-git-workflow/SKILL.md +32 -11
- package/dist/agents/skills/gemini-cli/harness-planning/SKILL.md +19 -0
- package/dist/agents/skills/gemini-cli/harness-skill-authoring/SKILL.md +42 -7
- package/dist/agents/skills/gemini-cli/harness-verification/SKILL.md +25 -0
- package/dist/agents-md-MCUM4SIZ.js +10 -0
- package/dist/architecture-HNIO6AUX.js +15 -0
- package/dist/{assess-project-7JZCVM7D.js → assess-project-6MV5TNY3.js} +2 -1
- package/dist/bin/harness-mcp.js +19 -18
- package/dist/bin/harness.js +48 -31
- package/dist/{check-phase-gate-YQHAH4LL.js → check-phase-gate-VCBQHQAC.js} +6 -5
- package/dist/{chunk-Q3XYV5UC.js → chunk-47N6R2F4.js} +5 -1
- package/dist/chunk-4NK7Z3BP.js +43 -0
- package/dist/{chunk-I4QR3HNH.js → chunk-5BQ5BOJL.js} +5 -1
- package/dist/{chunk-LM5Z2WCA.js → chunk-6VZQJ5CX.js} +5 -1
- package/dist/{chunk-NV4FPO7J.js → chunk-AT74HEQM.js} +1 -1
- package/dist/{chunk-ZKVLBOYA.js → chunk-AZT567I6.js} +1495 -662
- package/dist/{chunk-TDLOFNNQ.js → chunk-BOQRTARD.js} +1 -1
- package/dist/{chunk-NU3BPEDR.js → chunk-CXJTVICF.js} +5 -5
- package/dist/{chunk-LOUH2LIC.js → chunk-EHRZMOQ2.js} +5 -1
- package/dist/{chunk-IDZNPTYD.js → chunk-EPUKTTJZ.js} +6 -1
- package/dist/{chunk-C5FTJ3EU.js → chunk-F23H3U5U.js} +62 -1
- package/dist/{chunk-BWWNQPPO.js → chunk-FES2YEQU.js} +10 -10
- package/dist/{chunk-W2OZK3KC.js → chunk-FIQL2HND.js} +6 -2
- package/dist/{chunk-FP53DDB5.js → chunk-FSNPBT74.js} +5 -1
- package/dist/{chunk-TUJAHI22.js → chunk-GEEYCQDS.js} +14 -10
- package/dist/{chunk-ZOVATVQC.js → chunk-HIK6OEUF.js} +6 -1
- package/dist/chunk-K2SON7TI.js +5382 -0
- package/dist/chunk-KFQGP6VL.js +33 -0
- package/dist/{chunk-JGMCHJ6B.js → chunk-M55DGGF3.js} +10 -6
- package/dist/{chunk-PCWYYFBP.js → chunk-MI6MA6OP.js} +16906 -13135
- package/dist/{chunk-YDRB55Q4.js → chunk-P7PANON5.js} +5 -1
- package/dist/chunk-RL4VMEXL.js +53 -0
- package/dist/{chunk-XNEMDKV5.js → chunk-RRHUCDRD.js} +1 -1
- package/dist/{chunk-LY3YVKXL.js → chunk-RX7TUMBR.js} +606 -175
- package/dist/{chunk-MS6KDQW7.js → chunk-TYV4EUAD.js} +13 -9
- package/dist/{chunk-2DAMN7ED.js → chunk-UQZBZINS.js} +6 -6
- package/dist/{chunk-HVFCCARH.js → chunk-UV3BZMGT.js} +64 -3
- package/dist/{chunk-AVVJ5EUU.js → chunk-WIQA4BSH.js} +12 -5
- package/dist/{chunk-UQEUYRBP.js → chunk-WWACIIBA.js} +1 -1
- package/dist/{chunk-LVYPPKZI.js → chunk-XTITAVUR.js} +5 -1
- package/dist/{chunk-JT732X6S.js → chunk-YTP2UDPV.js} +3 -3
- package/dist/ci-workflow-RTM7VVTD.js +10 -0
- package/dist/{constants-5JGUXPEK.js → constants-P4M3C2T3.js} +1 -0
- package/dist/{create-skill-QCXINA5Q.js → create-skill-6QWJHQYS.js} +3 -2
- package/dist/{dist-666AAZQ6.js → dist-3EWNRFFQ.js} +6 -1
- package/dist/{dist-RRMRZRV7.js → dist-ABTKRYZH.js} +1 -0
- package/dist/{dist-QW5G3GX3.js → dist-RADHOOXG.js} +4 -1
- package/dist/{dist-TZQUURSP.js → dist-WCSJHQPK.js} +108 -3
- package/dist/docs-UBOGGHTY.js +16 -0
- package/dist/engine-MJJAP5CH.js +10 -0
- package/dist/entropy-EMSXF2PX.js +14 -0
- package/dist/{feedback-RSPUJCJJ.js → feedback-ZLUX72HD.js} +3 -2
- package/dist/{generate-agent-definitions-OLYWXTYH.js → generate-agent-definitions-AWLPJ27C.js} +6 -5
- package/dist/{glob-helper-SRXMZPKM.js → glob-helper-VCQXK5XY.js} +2 -0
- package/dist/{graph-loader-M6FXJAKK.js → graph-loader-JHQVQRUS.js} +2 -1
- package/dist/index.d.ts +21 -15
- package/dist/index.js +36 -33
- package/dist/loader-JVSJZSWZ.js +12 -0
- package/dist/mcp-2553PNUC.js +38 -0
- package/dist/{performance-VVWOIJTE.js → performance-7AGWJUY4.js} +7 -6
- package/dist/review-pipeline-ZWVQJTJX.js +13 -0
- package/dist/{runner-TY7DJGQV.js → runner-AAEF2TXY.js} +1 -0
- package/dist/runtime-D6YUQPP2.js +11 -0
- package/dist/scan-MPJ6JHUY.js +9 -0
- package/dist/security-JLZUAQYT.js +14 -0
- package/dist/skill-executor-MOCUIAYS.js +9 -0
- package/dist/templates/base/.agnix.toml +16 -0
- package/dist/templates/orchestrator/WORKFLOW.md +55 -7
- package/dist/templates/orchestrator/template.json +1 -0
- package/dist/tool-tiers-7QGZ3FKY.js +98 -0
- package/dist/validate-TIIHRPMA.js +14 -0
- package/dist/validate-cross-check-ZOWFA3DB.js +10 -0
- package/dist/{version-KFFPOQAX.js → version-CUI434OP.js} +1 -0
- package/package.json +5 -5
- package/dist/agents-md-MMVRF77L.js +0 -9
- package/dist/architecture-JAEDPYQZ.js +0 -14
- package/dist/chunk-5LMZA5LZ.js +0 -38
- package/dist/chunk-OZIHPMA7.js +0 -5171
- package/dist/ci-workflow-HDH4LCOF.js +0 -9
- package/dist/docs-6SPJYTRR.js +0 -15
- package/dist/engine-CSITRE3J.js +0 -9
- package/dist/entropy-FZP643BK.js +0 -13
- package/dist/loader-P2P7CV2A.js +0 -11
- package/dist/mcp-JZG22CYT.js +0 -37
- package/dist/review-pipeline-XSRO7HYZ.js +0 -9
- package/dist/runtime-VMOIIP6B.js +0 -10
- package/dist/security-4AQ46P3H.js +0 -9
- package/dist/skill-executor-GA7BDX3F.js +0 -8
- package/dist/validate-T7UTXLEQ.js +0 -13
- package/dist/validate-cross-check-M4NP2UF5.js +0 -9
|
@@ -1,22 +1,25 @@
|
|
|
1
|
+
import {
|
|
2
|
+
generateAgentsMd
|
|
3
|
+
} from "./chunk-5BQ5BOJL.js";
|
|
1
4
|
import {
|
|
2
5
|
generateCIWorkflow
|
|
3
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-EHRZMOQ2.js";
|
|
7
|
+
import {
|
|
8
|
+
TemplateEngine
|
|
9
|
+
} from "./chunk-FSNPBT74.js";
|
|
4
10
|
import {
|
|
5
11
|
generate,
|
|
6
12
|
validate
|
|
7
13
|
} from "./chunk-CQ553GZO.js";
|
|
8
14
|
import {
|
|
9
15
|
generateRuntime
|
|
10
|
-
} from "./chunk-
|
|
11
|
-
import {
|
|
12
|
-
generateAgentsMd
|
|
13
|
-
} from "./chunk-I4QR3HNH.js";
|
|
16
|
+
} from "./chunk-6VZQJ5CX.js";
|
|
14
17
|
import {
|
|
15
18
|
runPersona
|
|
16
19
|
} from "./chunk-GISMXMVL.js";
|
|
17
20
|
import {
|
|
18
21
|
executeSkill
|
|
19
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-WWACIIBA.js";
|
|
20
23
|
import {
|
|
21
24
|
ALLOWED_PERSONA_COMMANDS
|
|
22
25
|
} from "./chunk-TEFCFC4H.js";
|
|
@@ -25,21 +28,21 @@ import {
|
|
|
25
28
|
OutputMode,
|
|
26
29
|
createCheckPhaseGateCommand,
|
|
27
30
|
findFiles
|
|
28
|
-
} from "./chunk-
|
|
31
|
+
} from "./chunk-FIQL2HND.js";
|
|
29
32
|
import {
|
|
30
33
|
createGenerateAgentDefinitionsCommand,
|
|
31
34
|
generateAgentDefinitions
|
|
32
|
-
} from "./chunk-
|
|
35
|
+
} from "./chunk-AT74HEQM.js";
|
|
33
36
|
import {
|
|
34
37
|
toKebabCase
|
|
35
38
|
} from "./chunk-KET4QQZB.js";
|
|
36
39
|
import {
|
|
37
40
|
listPersonas,
|
|
38
41
|
loadPersona
|
|
39
|
-
} from "./chunk-
|
|
42
|
+
} from "./chunk-P7PANON5.js";
|
|
40
43
|
import {
|
|
41
|
-
|
|
42
|
-
} from "./chunk-
|
|
44
|
+
createScanCommand
|
|
45
|
+
} from "./chunk-4NK7Z3BP.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-RX7TUMBR.js";
|
|
57
60
|
import {
|
|
58
61
|
findConfigFile,
|
|
59
62
|
resolveConfig
|
|
60
|
-
} from "./chunk-
|
|
63
|
+
} from "./chunk-WIQA4BSH.js";
|
|
61
64
|
import {
|
|
62
65
|
VALID_PLATFORMS
|
|
63
66
|
} from "./chunk-3ISINLYT.js";
|
|
@@ -71,7 +74,7 @@ import {
|
|
|
71
74
|
} from "./chunk-6B6UN6SG.js";
|
|
72
75
|
import {
|
|
73
76
|
loadGraphStore
|
|
74
|
-
} from "./chunk-
|
|
77
|
+
} from "./chunk-BOQRTARD.js";
|
|
75
78
|
import {
|
|
76
79
|
CLI_VERSION
|
|
77
80
|
} from "./chunk-BM3PWGXQ.js";
|
|
@@ -81,11 +84,14 @@ import {
|
|
|
81
84
|
BaselineManager,
|
|
82
85
|
BlueprintGenerator,
|
|
83
86
|
BundleSchema,
|
|
87
|
+
CINotifier,
|
|
84
88
|
CriticalPathResolver,
|
|
85
89
|
EntropyAnalyzer,
|
|
90
|
+
GitHubIssuesSyncAdapter,
|
|
86
91
|
PredictionEngine,
|
|
87
92
|
ProjectScanner,
|
|
88
93
|
SecurityScanner,
|
|
94
|
+
SecurityTimelineManager,
|
|
89
95
|
SpecImpactEstimator,
|
|
90
96
|
TimelineManager,
|
|
91
97
|
TypeScriptParser,
|
|
@@ -107,16 +113,20 @@ import {
|
|
|
107
113
|
detectDeadCode,
|
|
108
114
|
detectDocDrift,
|
|
109
115
|
diff,
|
|
116
|
+
dist_exports,
|
|
110
117
|
extractBundle,
|
|
111
118
|
generateSuggestions,
|
|
112
119
|
getOrCreateInstallId,
|
|
120
|
+
init_dist,
|
|
113
121
|
listStreams,
|
|
114
122
|
listTaintedSessions,
|
|
115
123
|
loadState,
|
|
116
124
|
loadStreamIndex,
|
|
125
|
+
loadTrackerSyncConfig,
|
|
117
126
|
mapInjectionFindings,
|
|
118
127
|
mapSecurityFindings,
|
|
119
128
|
parseDiff,
|
|
129
|
+
parseFileRegions,
|
|
120
130
|
parseManifest,
|
|
121
131
|
parseSecurityConfig,
|
|
122
132
|
pruneLearnings,
|
|
@@ -132,35 +142,41 @@ import {
|
|
|
132
142
|
runReviewPipeline,
|
|
133
143
|
scanForInjection,
|
|
134
144
|
setActiveStream,
|
|
145
|
+
validateAgentConfigs,
|
|
135
146
|
validateAgentsMap,
|
|
136
147
|
validateDependencies,
|
|
137
148
|
validateKnowledgeMap,
|
|
138
149
|
writeConfig,
|
|
139
150
|
writeLockfile
|
|
140
|
-
} from "./chunk-
|
|
151
|
+
} from "./chunk-MI6MA6OP.js";
|
|
141
152
|
import {
|
|
142
153
|
Err,
|
|
143
154
|
Ok
|
|
144
|
-
} from "./chunk-
|
|
155
|
+
} from "./chunk-RL4VMEXL.js";
|
|
145
156
|
import {
|
|
146
157
|
createCreateSkillCommand
|
|
147
|
-
} from "./chunk-
|
|
158
|
+
} from "./chunk-RRHUCDRD.js";
|
|
148
159
|
import {
|
|
149
160
|
logger
|
|
150
161
|
} from "./chunk-EBJQ6N4M.js";
|
|
151
162
|
import {
|
|
152
163
|
SkillMetadataSchema
|
|
153
|
-
} from "./chunk-
|
|
164
|
+
} from "./chunk-HIK6OEUF.js";
|
|
154
165
|
import {
|
|
155
166
|
CLIError,
|
|
156
167
|
ExitCode,
|
|
157
168
|
handleError
|
|
158
169
|
} from "./chunk-3WGJMBKH.js";
|
|
170
|
+
import {
|
|
171
|
+
__require,
|
|
172
|
+
__toCommonJS
|
|
173
|
+
} from "./chunk-KFQGP6VL.js";
|
|
159
174
|
|
|
160
175
|
// src/index.ts
|
|
161
|
-
import { Command as
|
|
176
|
+
import { Command as Command83 } from "commander";
|
|
162
177
|
|
|
163
178
|
// src/commands/add.ts
|
|
179
|
+
init_dist();
|
|
164
180
|
import { Command } from "commander";
|
|
165
181
|
import * as fs from "fs";
|
|
166
182
|
import * as path from "path";
|
|
@@ -248,7 +264,7 @@ function addDoc(name, cwd, configResult, created) {
|
|
|
248
264
|
return Ok(void 0);
|
|
249
265
|
}
|
|
250
266
|
async function addSkill(name, cwd, created) {
|
|
251
|
-
const { generateSkillFiles: generateSkillFiles2 } = await import("./create-skill-
|
|
267
|
+
const { generateSkillFiles: generateSkillFiles2 } = await import("./create-skill-6QWJHQYS.js");
|
|
252
268
|
generateSkillFiles2({
|
|
253
269
|
name,
|
|
254
270
|
description: `${name} skill`,
|
|
@@ -346,7 +362,7 @@ function registerSkillsCommand(adoption) {
|
|
|
346
362
|
const globalOpts = cmd.optsWithGlobals();
|
|
347
363
|
const limit = Math.max(parseInt(opts.limit, 10) || 20, 1);
|
|
348
364
|
const cwd = process.cwd();
|
|
349
|
-
const { readAdoptionRecords, aggregateBySkill } = await import("./dist-
|
|
365
|
+
const { readAdoptionRecords, aggregateBySkill } = await import("./dist-WCSJHQPK.js");
|
|
350
366
|
const records = readAdoptionRecords(cwd);
|
|
351
367
|
if (records.length === 0) {
|
|
352
368
|
if (globalOpts.json) {
|
|
@@ -382,7 +398,7 @@ function registerRecentCommand(adoption) {
|
|
|
382
398
|
const globalOpts = cmd.optsWithGlobals();
|
|
383
399
|
const limit = Math.max(parseInt(opts.limit, 10) || 20, 1);
|
|
384
400
|
const cwd = process.cwd();
|
|
385
|
-
const { readAdoptionRecords } = await import("./dist-
|
|
401
|
+
const { readAdoptionRecords } = await import("./dist-WCSJHQPK.js");
|
|
386
402
|
const records = readAdoptionRecords(cwd);
|
|
387
403
|
if (records.length === 0) {
|
|
388
404
|
if (globalOpts.json) {
|
|
@@ -419,7 +435,7 @@ function registerSkillCommand(adoption) {
|
|
|
419
435
|
adoption.command("skill <name>").description("Show detail for a specific skill").action(async (name, _opts, cmd) => {
|
|
420
436
|
const globalOpts = cmd.optsWithGlobals();
|
|
421
437
|
const cwd = process.cwd();
|
|
422
|
-
const { readAdoptionRecords, aggregateBySkill } = await import("./dist-
|
|
438
|
+
const { readAdoptionRecords, aggregateBySkill } = await import("./dist-WCSJHQPK.js");
|
|
423
439
|
const records = readAdoptionRecords(cwd);
|
|
424
440
|
const skillRecords = records.filter((r) => r.skill === name);
|
|
425
441
|
if (skillRecords.length === 0) {
|
|
@@ -485,6 +501,8 @@ function createAdoptionCommand() {
|
|
|
485
501
|
import { Command as Command5 } from "commander";
|
|
486
502
|
|
|
487
503
|
// src/commands/agent/run.ts
|
|
504
|
+
init_dist();
|
|
505
|
+
init_dist();
|
|
488
506
|
import { Command as Command3 } from "commander";
|
|
489
507
|
import * as path2 from "path";
|
|
490
508
|
import * as childProcess from "child_process";
|
|
@@ -606,6 +624,7 @@ function createRunCommand() {
|
|
|
606
624
|
}
|
|
607
625
|
|
|
608
626
|
// src/commands/agent/review.ts
|
|
627
|
+
init_dist();
|
|
609
628
|
import { Command as Command4 } from "commander";
|
|
610
629
|
import { execSync } from "child_process";
|
|
611
630
|
async function runAgentReview(options) {
|
|
@@ -654,7 +673,9 @@ async function runAgentReview(options) {
|
|
|
654
673
|
comment: options.comment ?? false,
|
|
655
674
|
ci: options.ci ?? false,
|
|
656
675
|
deep: options.deep ?? false,
|
|
657
|
-
noMechanical: options.noMechanical ?? false
|
|
676
|
+
noMechanical: options.noMechanical ?? false,
|
|
677
|
+
...options.thorough ? { thorough: true } : {},
|
|
678
|
+
...options.isolated ? { isolated: true } : {}
|
|
658
679
|
},
|
|
659
680
|
config
|
|
660
681
|
});
|
|
@@ -700,7 +721,10 @@ function printReviewResult(result, mode) {
|
|
|
700
721
|
}
|
|
701
722
|
}
|
|
702
723
|
function createReviewCommand() {
|
|
703
|
-
return new Command4("review").description("Run unified code review pipeline on current changes").option("--comment", "Post inline comments to GitHub PR").option("--ci", "Enable eligibility gate, non-interactive output").option("--deep", "Add threat modeling pass to security agent").option("--no-mechanical", "Skip mechanical checks").
|
|
724
|
+
return new Command4("review").description("Run unified code review pipeline on current changes").option("--comment", "Post inline comments to GitHub PR").option("--ci", "Enable eligibility gate, non-interactive output").option("--deep", "Add threat modeling pass to security agent").option("--no-mechanical", "Skip mechanical checks").option("--thorough", "Generate task-specific rubric before reading implementation").option(
|
|
725
|
+
"--isolated",
|
|
726
|
+
"Two-stage review: spec-compliance then code-quality with disjoint context"
|
|
727
|
+
).action(async (opts, cmd) => {
|
|
704
728
|
const globalOpts = cmd.optsWithGlobals();
|
|
705
729
|
const mode = resolveReviewMode(globalOpts);
|
|
706
730
|
const result = await runAgentReview({
|
|
@@ -711,7 +735,9 @@ function createReviewCommand() {
|
|
|
711
735
|
comment: opts.comment,
|
|
712
736
|
ci: opts.ci,
|
|
713
737
|
deep: opts.deep,
|
|
714
|
-
noMechanical: opts.mechanical === false
|
|
738
|
+
noMechanical: opts.mechanical === false,
|
|
739
|
+
thorough: opts.thorough,
|
|
740
|
+
isolated: opts.isolated
|
|
715
741
|
});
|
|
716
742
|
if (!result.ok) {
|
|
717
743
|
if (mode === OutputMode.JSON) console.log(JSON.stringify({ error: result.error.message }));
|
|
@@ -734,21 +760,130 @@ function createAgentCommand() {
|
|
|
734
760
|
return command;
|
|
735
761
|
}
|
|
736
762
|
|
|
737
|
-
// src/commands/
|
|
763
|
+
// src/commands/audit-protected.ts
|
|
764
|
+
init_dist();
|
|
738
765
|
import { Command as Command6 } from "commander";
|
|
739
766
|
import * as path3 from "path";
|
|
767
|
+
import * as fs2 from "fs";
|
|
768
|
+
|
|
769
|
+
// src/utils/output.ts
|
|
770
|
+
function resolveOutputMode(globalOpts) {
|
|
771
|
+
if (globalOpts.json) return OutputMode.JSON;
|
|
772
|
+
if (globalOpts.quiet) return OutputMode.QUIET;
|
|
773
|
+
if (globalOpts.verbose) return OutputMode.VERBOSE;
|
|
774
|
+
return OutputMode.TEXT;
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
// src/commands/audit-protected.ts
|
|
778
|
+
import { glob } from "glob";
|
|
779
|
+
async function runAuditProtected(options) {
|
|
780
|
+
const cwd = options.cwd ?? process.cwd();
|
|
781
|
+
const configResult = resolveConfig(options.configPath);
|
|
782
|
+
if (!configResult.ok) {
|
|
783
|
+
return Err(configResult.error);
|
|
784
|
+
}
|
|
785
|
+
const config = configResult.value;
|
|
786
|
+
const rootDir = path3.resolve(cwd, config.rootDir);
|
|
787
|
+
const excludePatterns = config.entropy?.excludePatterns ?? ["**/node_modules/**", "**/*.test.ts"];
|
|
788
|
+
let files;
|
|
789
|
+
try {
|
|
790
|
+
files = await glob("**/*.{ts,tsx,js,jsx,py,sh}", {
|
|
791
|
+
cwd: rootDir,
|
|
792
|
+
ignore: excludePatterns
|
|
793
|
+
});
|
|
794
|
+
} catch (e) {
|
|
795
|
+
return Err(new CLIError(`Failed to scan files: ${e.message}`, ExitCode.ERROR));
|
|
796
|
+
}
|
|
797
|
+
const allRegions = [];
|
|
798
|
+
const allIssues = [];
|
|
799
|
+
for (const relativePath of files) {
|
|
800
|
+
const absolutePath = path3.resolve(rootDir, relativePath);
|
|
801
|
+
let content;
|
|
802
|
+
try {
|
|
803
|
+
content = fs2.readFileSync(absolutePath, "utf-8");
|
|
804
|
+
} catch {
|
|
805
|
+
continue;
|
|
806
|
+
}
|
|
807
|
+
const { regions, issues } = parseFileRegions(relativePath, content);
|
|
808
|
+
allRegions.push(...regions);
|
|
809
|
+
allIssues.push(...issues);
|
|
810
|
+
}
|
|
811
|
+
return Ok({
|
|
812
|
+
regions: allRegions,
|
|
813
|
+
issues: allIssues,
|
|
814
|
+
fileCount: files.length
|
|
815
|
+
});
|
|
816
|
+
}
|
|
817
|
+
function formatRegion(region) {
|
|
818
|
+
const lineRange = region.startLine === region.endLine ? `${region.startLine}` : `${region.startLine}-${region.endLine}`;
|
|
819
|
+
const scopes = region.scopes.join(",");
|
|
820
|
+
const reason = region.reason ? ` ${region.reason}` : "";
|
|
821
|
+
return ` ${region.file}:${lineRange} [${scopes}]${reason}`;
|
|
822
|
+
}
|
|
823
|
+
function printAuditResult(result, formatter) {
|
|
824
|
+
const regionCount = result.regions.length;
|
|
825
|
+
const fileSet = new Set(result.regions.map((r) => r.file));
|
|
826
|
+
console.log(
|
|
827
|
+
formatter.formatSummary("Protected regions", `${regionCount} in ${fileSet.size} file(s)`, true)
|
|
828
|
+
);
|
|
829
|
+
if (regionCount > 0) {
|
|
830
|
+
console.log(`
|
|
831
|
+
Found ${regionCount} protected region(s) in ${fileSet.size} file(s):
|
|
832
|
+
`);
|
|
833
|
+
for (const region of result.regions) {
|
|
834
|
+
console.log(formatRegion(region));
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
if (result.issues.length > 0) {
|
|
838
|
+
console.log(`
|
|
839
|
+
Issues (${result.issues.length}):`);
|
|
840
|
+
for (const issue of result.issues) {
|
|
841
|
+
console.log(` ${issue.file}:${issue.line} \u2014 ${issue.message}`);
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
function createAuditProtectedCommand() {
|
|
846
|
+
const command = new Command6("audit-protected").description("Report all harness-ignore protected code regions").action(async (_opts, cmd) => {
|
|
847
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
848
|
+
const mode = resolveOutputMode(globalOpts);
|
|
849
|
+
const formatter = new OutputFormatter(mode);
|
|
850
|
+
const result = await runAuditProtected({
|
|
851
|
+
configPath: globalOpts.config
|
|
852
|
+
});
|
|
853
|
+
if (!result.ok) {
|
|
854
|
+
if (mode === OutputMode.JSON) {
|
|
855
|
+
console.log(JSON.stringify({ error: result.error.message }));
|
|
856
|
+
} else {
|
|
857
|
+
logger.error(result.error.message);
|
|
858
|
+
}
|
|
859
|
+
process.exit(result.error.exitCode);
|
|
860
|
+
}
|
|
861
|
+
if (mode === OutputMode.JSON) {
|
|
862
|
+
console.log(JSON.stringify(result.value, null, 2));
|
|
863
|
+
} else if (mode !== OutputMode.QUIET || result.value.regions.length > 0) {
|
|
864
|
+
printAuditResult(result.value, formatter);
|
|
865
|
+
}
|
|
866
|
+
process.exit(result.value.issues.length > 0 ? ExitCode.VALIDATION_FAILED : ExitCode.SUCCESS);
|
|
867
|
+
});
|
|
868
|
+
return command;
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
// src/commands/blueprint.ts
|
|
872
|
+
init_dist();
|
|
873
|
+
import { Command as Command7 } from "commander";
|
|
874
|
+
import * as path4 from "path";
|
|
740
875
|
function createBlueprintCommand() {
|
|
741
|
-
return new
|
|
876
|
+
return new Command7("blueprint").description("Generate a self-contained, interactive blueprint of the codebase").argument("[path]", "Path to the project root", ".").option("-o, --output <dir>", "Output directory", "docs/blueprint").action(async (projectPath, options) => {
|
|
742
877
|
try {
|
|
743
|
-
const rootDir =
|
|
744
|
-
const outputDir =
|
|
878
|
+
const rootDir = path4.resolve(projectPath);
|
|
879
|
+
const outputDir = path4.resolve(options.output);
|
|
745
880
|
logger.info(`Scanning project at ${rootDir}...`);
|
|
746
881
|
const scanner = new ProjectScanner(rootDir);
|
|
747
882
|
const data = await scanner.scan();
|
|
748
883
|
logger.info(`Generating blueprint to ${outputDir}...`);
|
|
749
884
|
const generator = new BlueprintGenerator();
|
|
750
885
|
await generator.generate(data, { outputDir });
|
|
751
|
-
logger.success(`Blueprint generated successfully at ${
|
|
886
|
+
logger.success(`Blueprint generated successfully at ${path4.join(outputDir, "index.html")}`);
|
|
752
887
|
} catch (error) {
|
|
753
888
|
logger.error(
|
|
754
889
|
`Failed to generate blueprint: ${error instanceof Error ? error.message : String(error)}`
|
|
@@ -759,7 +894,8 @@ function createBlueprintCommand() {
|
|
|
759
894
|
}
|
|
760
895
|
|
|
761
896
|
// src/commands/check-arch.ts
|
|
762
|
-
|
|
897
|
+
init_dist();
|
|
898
|
+
import { Command as Command8 } from "commander";
|
|
763
899
|
import { execSync as execSync2 } from "child_process";
|
|
764
900
|
function getCommitHash(cwd) {
|
|
765
901
|
try {
|
|
@@ -853,7 +989,7 @@ async function runCheckArch(options) {
|
|
|
853
989
|
thresholdViolations
|
|
854
990
|
});
|
|
855
991
|
}
|
|
856
|
-
function
|
|
992
|
+
function resolveOutputMode2(globalOpts) {
|
|
857
993
|
if (globalOpts.json) return OutputMode.JSON;
|
|
858
994
|
if (globalOpts.quiet) return OutputMode.QUIET;
|
|
859
995
|
if (globalOpts.verbose) return OutputMode.VERBOSE;
|
|
@@ -889,9 +1025,9 @@ function printArchResult(value, mode, formatter) {
|
|
|
889
1025
|
if (output) console.log(output);
|
|
890
1026
|
}
|
|
891
1027
|
function createCheckArchCommand() {
|
|
892
|
-
const command = new
|
|
1028
|
+
const command = new Command8("check-arch").description("Check architecture assertions against baseline and thresholds").option("--update-baseline", "Capture current state as new baseline").option("--module <path>", "Check a single module").action(async (opts, cmd) => {
|
|
893
1029
|
const globalOpts = cmd.optsWithGlobals();
|
|
894
|
-
const mode =
|
|
1030
|
+
const mode = resolveOutputMode2(globalOpts);
|
|
895
1031
|
const formatter = new OutputFormatter(mode);
|
|
896
1032
|
const result = await runCheckArch({
|
|
897
1033
|
configPath: globalOpts.config,
|
|
@@ -919,8 +1055,10 @@ function createCheckArchCommand() {
|
|
|
919
1055
|
}
|
|
920
1056
|
|
|
921
1057
|
// src/commands/check-deps.ts
|
|
922
|
-
|
|
923
|
-
|
|
1058
|
+
init_dist();
|
|
1059
|
+
init_dist();
|
|
1060
|
+
import { Command as Command9 } from "commander";
|
|
1061
|
+
import * as path5 from "path";
|
|
924
1062
|
async function runCheckDeps(options) {
|
|
925
1063
|
const cwd = options.cwd ?? process.cwd();
|
|
926
1064
|
const configResult = resolveConfig(options.configPath);
|
|
@@ -936,7 +1074,7 @@ async function runCheckDeps(options) {
|
|
|
936
1074
|
if (!config.layers || config.layers.length === 0) {
|
|
937
1075
|
return Ok(result);
|
|
938
1076
|
}
|
|
939
|
-
const rootDir =
|
|
1077
|
+
const rootDir = path5.resolve(cwd, config.rootDir);
|
|
940
1078
|
const parser = new TypeScriptParser();
|
|
941
1079
|
const layers = config.layers.map((l) => defineLayer(l.name, [l.pattern], l.allowedDependencies));
|
|
942
1080
|
const layerConfig = {
|
|
@@ -1011,25 +1149,17 @@ async function runCheckDepsAction(globalOpts) {
|
|
|
1011
1149
|
process.exit(result.value.valid ? ExitCode.SUCCESS : ExitCode.VALIDATION_FAILED);
|
|
1012
1150
|
}
|
|
1013
1151
|
function createCheckDepsCommand() {
|
|
1014
|
-
const command = new
|
|
1152
|
+
const command = new Command9("check-deps").description("Validate dependency layers and detect circular dependencies").action(async (_opts, cmd) => {
|
|
1015
1153
|
await runCheckDepsAction(cmd.optsWithGlobals());
|
|
1016
1154
|
});
|
|
1017
1155
|
return command;
|
|
1018
1156
|
}
|
|
1019
1157
|
|
|
1020
1158
|
// src/commands/check-docs.ts
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
function resolveOutputMode2(globalOpts) {
|
|
1026
|
-
if (globalOpts.json) return OutputMode.JSON;
|
|
1027
|
-
if (globalOpts.quiet) return OutputMode.QUIET;
|
|
1028
|
-
if (globalOpts.verbose) return OutputMode.VERBOSE;
|
|
1029
|
-
return OutputMode.TEXT;
|
|
1030
|
-
}
|
|
1031
|
-
|
|
1032
|
-
// src/commands/check-docs.ts
|
|
1159
|
+
init_dist();
|
|
1160
|
+
init_dist();
|
|
1161
|
+
import { Command as Command10 } from "commander";
|
|
1162
|
+
import * as path6 from "path";
|
|
1033
1163
|
async function runCheckDocs(options) {
|
|
1034
1164
|
const cwd = options.cwd ?? process.cwd();
|
|
1035
1165
|
const minCoverage = options.minCoverage ?? 80;
|
|
@@ -1038,8 +1168,8 @@ async function runCheckDocs(options) {
|
|
|
1038
1168
|
return configResult;
|
|
1039
1169
|
}
|
|
1040
1170
|
const config = configResult.value;
|
|
1041
|
-
const docsDir =
|
|
1042
|
-
const sourceDir =
|
|
1171
|
+
const docsDir = path6.resolve(cwd, config.docsDir);
|
|
1172
|
+
const sourceDir = path6.resolve(cwd, config.rootDir);
|
|
1043
1173
|
const coverageResult = await checkDocCoverage("project", {
|
|
1044
1174
|
docsDir,
|
|
1045
1175
|
sourceDir,
|
|
@@ -1106,9 +1236,9 @@ function printCheckDocsResult(value, mode, formatter) {
|
|
|
1106
1236
|
}
|
|
1107
1237
|
}
|
|
1108
1238
|
function createCheckDocsCommand() {
|
|
1109
|
-
const command = new
|
|
1239
|
+
const command = new Command10("check-docs").description("Check documentation coverage").option("--min-coverage <percent>", "Minimum coverage percentage", "80").action(async (opts, cmd) => {
|
|
1110
1240
|
const globalOpts = cmd.optsWithGlobals();
|
|
1111
|
-
const mode =
|
|
1241
|
+
const mode = resolveOutputMode(globalOpts);
|
|
1112
1242
|
const formatter = new OutputFormatter(mode);
|
|
1113
1243
|
const result = await runCheckDocs({
|
|
1114
1244
|
configPath: globalOpts.config,
|
|
@@ -1136,12 +1266,13 @@ function createCheckDocsCommand() {
|
|
|
1136
1266
|
}
|
|
1137
1267
|
|
|
1138
1268
|
// src/commands/check-perf.ts
|
|
1139
|
-
|
|
1140
|
-
import
|
|
1269
|
+
init_dist();
|
|
1270
|
+
import { Command as Command11 } from "commander";
|
|
1271
|
+
import * as path7 from "path";
|
|
1141
1272
|
async function runCheckPerf(cwd, options) {
|
|
1142
1273
|
const runAll2 = !options.structural && !options.size && !options.coupling;
|
|
1143
1274
|
const analyzer = new EntropyAnalyzer({
|
|
1144
|
-
rootDir:
|
|
1275
|
+
rootDir: path7.resolve(cwd),
|
|
1145
1276
|
analyze: {
|
|
1146
1277
|
complexity: runAll2 || !!options.structural,
|
|
1147
1278
|
coupling: runAll2 || !!options.coupling,
|
|
@@ -1253,15 +1384,16 @@ async function runCheckPerfAction(opts, globalOpts) {
|
|
|
1253
1384
|
process.exit(result.value.valid ? ExitCode.SUCCESS : ExitCode.VALIDATION_FAILED);
|
|
1254
1385
|
}
|
|
1255
1386
|
function createCheckPerfCommand() {
|
|
1256
|
-
const command = new
|
|
1387
|
+
const command = new Command11("check-perf").description("Run performance checks: structural complexity, coupling, and size budgets").option("--structural", "Run structural complexity checks only").option("--coupling", "Run coupling metric checks only").option("--size", "Run size budget checks only").action(async (opts, cmd) => {
|
|
1257
1388
|
await runCheckPerfAction(opts, cmd.optsWithGlobals());
|
|
1258
1389
|
});
|
|
1259
1390
|
return command;
|
|
1260
1391
|
}
|
|
1261
1392
|
|
|
1262
1393
|
// src/commands/check-security.ts
|
|
1263
|
-
|
|
1264
|
-
import
|
|
1394
|
+
init_dist();
|
|
1395
|
+
import { Command as Command12 } from "commander";
|
|
1396
|
+
import * as path8 from "path";
|
|
1265
1397
|
import { execSync as execSync3 } from "child_process";
|
|
1266
1398
|
var SEVERITY_RANK = {
|
|
1267
1399
|
error: 3,
|
|
@@ -1274,19 +1406,19 @@ function getChangedFiles(cwd) {
|
|
|
1274
1406
|
cwd,
|
|
1275
1407
|
encoding: "utf-8"
|
|
1276
1408
|
});
|
|
1277
|
-
return output.trim().split("\n").filter((f) => f.length > 0).map((f) =>
|
|
1409
|
+
return output.trim().split("\n").filter((f) => f.length > 0).map((f) => path8.resolve(cwd, f));
|
|
1278
1410
|
} catch {
|
|
1279
1411
|
return [];
|
|
1280
1412
|
}
|
|
1281
1413
|
}
|
|
1282
1414
|
async function runCheckSecurity(cwd, options) {
|
|
1283
|
-
const projectRoot =
|
|
1415
|
+
const projectRoot = path8.resolve(cwd);
|
|
1284
1416
|
let configData = {};
|
|
1285
1417
|
try {
|
|
1286
|
-
const
|
|
1287
|
-
const configPath =
|
|
1288
|
-
if (
|
|
1289
|
-
const raw =
|
|
1418
|
+
const fs41 = await import("fs");
|
|
1419
|
+
const configPath = path8.join(projectRoot, "harness.config.json");
|
|
1420
|
+
if (fs41.existsSync(configPath)) {
|
|
1421
|
+
const raw = fs41.readFileSync(configPath, "utf-8");
|
|
1290
1422
|
const parsed = JSON.parse(raw);
|
|
1291
1423
|
configData = parsed.security ?? {};
|
|
1292
1424
|
}
|
|
@@ -1299,7 +1431,7 @@ async function runCheckSecurity(cwd, options) {
|
|
|
1299
1431
|
if (options.changedOnly) {
|
|
1300
1432
|
filesToScan = getChangedFiles(projectRoot);
|
|
1301
1433
|
} else {
|
|
1302
|
-
const { glob } = await import("glob");
|
|
1434
|
+
const { glob: glob2 } = await import("glob");
|
|
1303
1435
|
const pattern = "**/*.{ts,tsx,js,jsx,go,py,java,rb}";
|
|
1304
1436
|
const ignore = securityConfig.exclude ?? [
|
|
1305
1437
|
"**/node_modules/**",
|
|
@@ -1307,9 +1439,19 @@ async function runCheckSecurity(cwd, options) {
|
|
|
1307
1439
|
"**/*.test.ts",
|
|
1308
1440
|
"**/fixtures/**"
|
|
1309
1441
|
];
|
|
1310
|
-
filesToScan = await
|
|
1442
|
+
filesToScan = await glob2(pattern, { cwd: projectRoot, absolute: true, ignore });
|
|
1311
1443
|
}
|
|
1312
1444
|
const result = await scanner.scanFiles(filesToScan);
|
|
1445
|
+
try {
|
|
1446
|
+
const commitHash = execSync3("git rev-parse HEAD", {
|
|
1447
|
+
cwd: projectRoot,
|
|
1448
|
+
encoding: "utf-8"
|
|
1449
|
+
}).trim();
|
|
1450
|
+
const timelineManager = new SecurityTimelineManager(projectRoot);
|
|
1451
|
+
timelineManager.capture(result, commitHash);
|
|
1452
|
+
timelineManager.updateLifecycles(result.findings, commitHash);
|
|
1453
|
+
} catch {
|
|
1454
|
+
}
|
|
1313
1455
|
const threshold = options.severity ?? "warning";
|
|
1314
1456
|
const thresholdRank = SEVERITY_RANK[threshold];
|
|
1315
1457
|
const filtered = result.findings.filter((f) => SEVERITY_RANK[f.severity] >= thresholdRank);
|
|
@@ -1355,7 +1497,7 @@ async function runCheckSecurityAction(opts, globalOpts) {
|
|
|
1355
1497
|
process.exit(result.value.valid ? ExitCode.SUCCESS : ExitCode.VALIDATION_FAILED);
|
|
1356
1498
|
}
|
|
1357
1499
|
function createCheckSecurityCommand() {
|
|
1358
|
-
const command = new
|
|
1500
|
+
const command = new Command12("check-security").description("Run lightweight security scan: secrets, injection, XSS, weak crypto").option("--severity <level>", "Minimum severity threshold", "warning").hook("preAction", (thisCommand) => {
|
|
1359
1501
|
const severity = thisCommand.opts().severity;
|
|
1360
1502
|
if (!["error", "warning", "info"].includes(severity)) {
|
|
1361
1503
|
logger.error(`Invalid severity: "${severity}". Must be one of: error, warning, info`);
|
|
@@ -1368,10 +1510,11 @@ function createCheckSecurityCommand() {
|
|
|
1368
1510
|
}
|
|
1369
1511
|
|
|
1370
1512
|
// src/commands/ci/index.ts
|
|
1371
|
-
import { Command as
|
|
1513
|
+
import { Command as Command16 } from "commander";
|
|
1372
1514
|
|
|
1373
1515
|
// src/commands/ci/check.ts
|
|
1374
|
-
|
|
1516
|
+
init_dist();
|
|
1517
|
+
import { Command as Command13 } from "commander";
|
|
1375
1518
|
var VALID_CHECKS = [
|
|
1376
1519
|
"validate",
|
|
1377
1520
|
"deps",
|
|
@@ -1435,7 +1578,7 @@ function printCheckReport(report) {
|
|
|
1435
1578
|
}
|
|
1436
1579
|
}
|
|
1437
1580
|
async function runCheckAction(opts, globalOpts) {
|
|
1438
|
-
const mode =
|
|
1581
|
+
const mode = resolveOutputMode(globalOpts);
|
|
1439
1582
|
const skip = parseSkip(opts.skip);
|
|
1440
1583
|
const failOn = parseFailOn(opts.failOn);
|
|
1441
1584
|
const opts2 = {
|
|
@@ -1461,15 +1604,16 @@ async function runCheckAction(opts, globalOpts) {
|
|
|
1461
1604
|
process.exit(report.exitCode);
|
|
1462
1605
|
}
|
|
1463
1606
|
function createCheckCommand() {
|
|
1464
|
-
return new
|
|
1607
|
+
return new Command13("check").description("Run all harness checks for CI (validate, deps, docs, entropy, phase-gate, arch)").option("--skip <checks>", "Comma-separated checks to skip (e.g., entropy,docs)").option("--fail-on <severity>", "Fail on severity level: error (default) or warning", "error").action(async (opts, cmd) => {
|
|
1465
1608
|
await runCheckAction(opts, cmd.optsWithGlobals());
|
|
1466
1609
|
});
|
|
1467
1610
|
}
|
|
1468
1611
|
|
|
1469
1612
|
// src/commands/ci/init.ts
|
|
1470
|
-
|
|
1471
|
-
import
|
|
1472
|
-
import * as
|
|
1613
|
+
init_dist();
|
|
1614
|
+
import { Command as Command14 } from "commander";
|
|
1615
|
+
import * as fs3 from "fs";
|
|
1616
|
+
import * as path9 from "path";
|
|
1473
1617
|
var ALL_CHECKS = [
|
|
1474
1618
|
"validate",
|
|
1475
1619
|
"deps",
|
|
@@ -1571,17 +1715,17 @@ function generateCIConfig(options) {
|
|
|
1571
1715
|
});
|
|
1572
1716
|
}
|
|
1573
1717
|
function detectPlatform() {
|
|
1574
|
-
if (
|
|
1575
|
-
if (
|
|
1718
|
+
if (fs3.existsSync(".github")) return "github";
|
|
1719
|
+
if (fs3.existsSync(".gitlab-ci.yml")) return "gitlab";
|
|
1576
1720
|
return null;
|
|
1577
1721
|
}
|
|
1578
1722
|
function writeGeneratedConfig(filename, content, platform) {
|
|
1579
|
-
const targetPath =
|
|
1580
|
-
const dir =
|
|
1581
|
-
|
|
1582
|
-
|
|
1723
|
+
const targetPath = path9.resolve(filename);
|
|
1724
|
+
const dir = path9.dirname(targetPath);
|
|
1725
|
+
fs3.mkdirSync(dir, { recursive: true });
|
|
1726
|
+
fs3.writeFileSync(targetPath, content);
|
|
1583
1727
|
if (platform === "generic" && process.platform !== "win32") {
|
|
1584
|
-
|
|
1728
|
+
fs3.chmodSync(targetPath, "755");
|
|
1585
1729
|
}
|
|
1586
1730
|
}
|
|
1587
1731
|
async function runInitAction(opts, globalOpts) {
|
|
@@ -1604,22 +1748,128 @@ async function runInitAction(opts, globalOpts) {
|
|
|
1604
1748
|
}
|
|
1605
1749
|
}
|
|
1606
1750
|
function createInitCommand() {
|
|
1607
|
-
return new
|
|
1751
|
+
return new Command14("init").description("Generate CI configuration for harness checks").option("--platform <platform>", "CI platform: github, gitlab, or generic").option("--checks <list>", "Comma-separated list of checks to include").action(async (opts, cmd) => {
|
|
1608
1752
|
await runInitAction(opts, cmd.optsWithGlobals());
|
|
1609
1753
|
});
|
|
1610
1754
|
}
|
|
1611
1755
|
|
|
1756
|
+
// src/commands/ci/notify.ts
|
|
1757
|
+
init_dist();
|
|
1758
|
+
import { Command as Command15 } from "commander";
|
|
1759
|
+
import * as fs4 from "fs";
|
|
1760
|
+
function loadReport(reportPath) {
|
|
1761
|
+
try {
|
|
1762
|
+
const raw = fs4.readFileSync(reportPath, "utf-8");
|
|
1763
|
+
return JSON.parse(raw);
|
|
1764
|
+
} catch {
|
|
1765
|
+
return null;
|
|
1766
|
+
}
|
|
1767
|
+
}
|
|
1768
|
+
function resolveToken() {
|
|
1769
|
+
return process.env.GITHUB_TOKEN ?? process.env.GH_TOKEN ?? null;
|
|
1770
|
+
}
|
|
1771
|
+
function resolveTrackerConfig(config) {
|
|
1772
|
+
const roadmap = config.roadmap;
|
|
1773
|
+
const tracker = roadmap?.tracker;
|
|
1774
|
+
if (!tracker || tracker.kind !== "github") return null;
|
|
1775
|
+
return tracker;
|
|
1776
|
+
}
|
|
1777
|
+
async function runNotifyAction(reportPath, opts, globalOpts) {
|
|
1778
|
+
const mode = resolveOutputMode(globalOpts);
|
|
1779
|
+
const report = loadReport(reportPath);
|
|
1780
|
+
if (!report) {
|
|
1781
|
+
logger.error(`Failed to read or parse report: ${reportPath}`);
|
|
1782
|
+
process.exit(ExitCode.ERROR);
|
|
1783
|
+
}
|
|
1784
|
+
const configResult = resolveConfig(
|
|
1785
|
+
typeof globalOpts.config === "string" ? globalOpts.config : void 0
|
|
1786
|
+
);
|
|
1787
|
+
if (!configResult.ok) {
|
|
1788
|
+
logger.error(configResult.error.message);
|
|
1789
|
+
process.exit(ExitCode.ERROR);
|
|
1790
|
+
}
|
|
1791
|
+
const config = configResult.value;
|
|
1792
|
+
const trackerConfig = resolveTrackerConfig(config);
|
|
1793
|
+
if (!trackerConfig || !trackerConfig.repo) {
|
|
1794
|
+
logger.error(
|
|
1795
|
+
'No GitHub tracker configured. Set roadmap.tracker in harness.config.json with kind: "github" and repo: "owner/repo".'
|
|
1796
|
+
);
|
|
1797
|
+
process.exit(ExitCode.ERROR);
|
|
1798
|
+
}
|
|
1799
|
+
const token = resolveToken();
|
|
1800
|
+
if (!token) {
|
|
1801
|
+
logger.error("No GitHub token found. Set GITHUB_TOKEN or GH_TOKEN environment variable.");
|
|
1802
|
+
process.exit(ExitCode.ERROR);
|
|
1803
|
+
}
|
|
1804
|
+
const adapter = new GitHubIssuesSyncAdapter({ token, config: trackerConfig });
|
|
1805
|
+
const notifier = new CINotifier(adapter, trackerConfig.repo);
|
|
1806
|
+
const target = opts.target;
|
|
1807
|
+
if (target === "pr-comment") {
|
|
1808
|
+
if (!opts.pr) {
|
|
1809
|
+
logger.error("--pr <number> is required when target is pr-comment");
|
|
1810
|
+
process.exit(ExitCode.ERROR);
|
|
1811
|
+
}
|
|
1812
|
+
const prNumber = parseInt(opts.pr, 10);
|
|
1813
|
+
if (isNaN(prNumber)) {
|
|
1814
|
+
logger.error(`Invalid PR number: ${opts.pr}`);
|
|
1815
|
+
process.exit(ExitCode.ERROR);
|
|
1816
|
+
}
|
|
1817
|
+
const result = await notifier.notifyPR(report, prNumber);
|
|
1818
|
+
if (!result.ok) {
|
|
1819
|
+
logger.error(`Failed to post PR comment: ${result.error.message}`);
|
|
1820
|
+
process.exit(ExitCode.ERROR);
|
|
1821
|
+
}
|
|
1822
|
+
if (mode === OutputMode.JSON) {
|
|
1823
|
+
console.log(JSON.stringify({ target, pr: prNumber, status: "posted" }));
|
|
1824
|
+
} else if (mode !== OutputMode.QUIET) {
|
|
1825
|
+
logger.success(`Posted CI report as comment on PR #${prNumber}`);
|
|
1826
|
+
}
|
|
1827
|
+
} else if (target === "issue") {
|
|
1828
|
+
if (report.exitCode === 0) {
|
|
1829
|
+
if (mode === OutputMode.JSON) {
|
|
1830
|
+
console.log(JSON.stringify({ target, status: "skipped", reason: "no failures" }));
|
|
1831
|
+
} else if (mode !== OutputMode.QUIET) {
|
|
1832
|
+
logger.dim("No failures \u2014 skipping issue creation");
|
|
1833
|
+
}
|
|
1834
|
+
return;
|
|
1835
|
+
}
|
|
1836
|
+
const issueOpts = {};
|
|
1837
|
+
if (opts.title) issueOpts.issueTitle = opts.title;
|
|
1838
|
+
if (opts.labels) issueOpts.labels = opts.labels.split(",").map((l) => l.trim());
|
|
1839
|
+
const result = await notifier.notifyIssue(report, issueOpts);
|
|
1840
|
+
if (!result.ok) {
|
|
1841
|
+
logger.error(`Failed to create issue: ${result.error.message}`);
|
|
1842
|
+
process.exit(ExitCode.ERROR);
|
|
1843
|
+
}
|
|
1844
|
+
if (mode === OutputMode.JSON) {
|
|
1845
|
+
console.log(JSON.stringify({ target, ...result.value, status: "created" }));
|
|
1846
|
+
} else if (mode !== OutputMode.QUIET) {
|
|
1847
|
+
logger.success(`Created issue: ${result.value.url}`);
|
|
1848
|
+
}
|
|
1849
|
+
} else {
|
|
1850
|
+
logger.error(`Unknown target: ${target}. Use "pr-comment" or "issue".`);
|
|
1851
|
+
process.exit(ExitCode.ERROR);
|
|
1852
|
+
}
|
|
1853
|
+
}
|
|
1854
|
+
function createNotifyCommand() {
|
|
1855
|
+
return new Command15("notify").description("Post CI check results to GitHub (PR comment or issue)").argument("<report>", "Path to CI check report JSON file (from harness ci check --json)").requiredOption("--target <target>", "Notification target: pr-comment or issue").option("--pr <number>", "PR number (required for pr-comment target)").option("--title <title>", "Custom issue title (for issue target)").option("--labels <labels>", "Comma-separated labels for created issues").action(async (reportPath, opts, cmd) => {
|
|
1856
|
+
await runNotifyAction(reportPath, opts, cmd.optsWithGlobals());
|
|
1857
|
+
});
|
|
1858
|
+
}
|
|
1859
|
+
|
|
1612
1860
|
// src/commands/ci/index.ts
|
|
1613
1861
|
function createCICommand() {
|
|
1614
|
-
const command = new
|
|
1862
|
+
const command = new Command16("ci").description("CI/CD integration commands");
|
|
1615
1863
|
command.addCommand(createCheckCommand());
|
|
1616
1864
|
command.addCommand(createInitCommand());
|
|
1865
|
+
command.addCommand(createNotifyCommand());
|
|
1617
1866
|
return command;
|
|
1618
1867
|
}
|
|
1619
1868
|
|
|
1620
1869
|
// src/commands/cleanup.ts
|
|
1621
|
-
|
|
1622
|
-
import
|
|
1870
|
+
init_dist();
|
|
1871
|
+
import { Command as Command17 } from "commander";
|
|
1872
|
+
import * as path10 from "path";
|
|
1623
1873
|
async function runCleanup(options) {
|
|
1624
1874
|
const cwd = options.cwd ?? process.cwd();
|
|
1625
1875
|
const type = options.type ?? "all";
|
|
@@ -1634,11 +1884,11 @@ async function runCleanup(options) {
|
|
|
1634
1884
|
patternViolations: [],
|
|
1635
1885
|
totalIssues: 0
|
|
1636
1886
|
};
|
|
1637
|
-
const rootDir =
|
|
1638
|
-
const docsDir =
|
|
1887
|
+
const rootDir = path10.resolve(cwd, config.rootDir);
|
|
1888
|
+
const docsDir = path10.resolve(cwd, config.docsDir);
|
|
1639
1889
|
const entropyConfig = {
|
|
1640
1890
|
rootDir,
|
|
1641
|
-
entryPoints:
|
|
1891
|
+
...config.entropy?.entryPoints && { entryPoints: config.entropy.entryPoints },
|
|
1642
1892
|
docPaths: [docsDir],
|
|
1643
1893
|
analyze: {
|
|
1644
1894
|
drift: type === "all" || type === "drift",
|
|
@@ -1707,9 +1957,9 @@ function printCleanupResult(value, formatter) {
|
|
|
1707
1957
|
}
|
|
1708
1958
|
}
|
|
1709
1959
|
function createCleanupCommand() {
|
|
1710
|
-
const command = new
|
|
1960
|
+
const command = new Command17("cleanup").description("Detect entropy issues (doc drift, dead code, patterns)").option("-t, --type <type>", "Issue type: drift, dead-code, patterns, all", "all").action(async (opts, cmd) => {
|
|
1711
1961
|
const globalOpts = cmd.optsWithGlobals();
|
|
1712
|
-
const mode =
|
|
1962
|
+
const mode = resolveOutputMode(globalOpts);
|
|
1713
1963
|
const formatter = new OutputFormatter(mode);
|
|
1714
1964
|
const result = await runCleanup({
|
|
1715
1965
|
configPath: globalOpts.config,
|
|
@@ -1737,20 +1987,21 @@ function createCleanupCommand() {
|
|
|
1737
1987
|
}
|
|
1738
1988
|
|
|
1739
1989
|
// src/commands/cleanup-sessions.ts
|
|
1740
|
-
|
|
1741
|
-
import
|
|
1742
|
-
import * as
|
|
1990
|
+
init_dist();
|
|
1991
|
+
import { Command as Command18 } from "commander";
|
|
1992
|
+
import * as fs5 from "fs";
|
|
1993
|
+
import * as path11 from "path";
|
|
1743
1994
|
var STALE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
1744
1995
|
function getMostRecentMtime(dirPath) {
|
|
1745
1996
|
let latest = 0;
|
|
1746
1997
|
try {
|
|
1747
|
-
const entries =
|
|
1998
|
+
const entries = fs5.readdirSync(dirPath, { withFileTypes: true });
|
|
1748
1999
|
for (const entry of entries) {
|
|
1749
|
-
const fullPath =
|
|
1750
|
-
const stat =
|
|
2000
|
+
const fullPath = path11.join(dirPath, entry.name);
|
|
2001
|
+
const stat = fs5.statSync(fullPath);
|
|
1751
2002
|
if (stat.mtimeMs > latest) latest = stat.mtimeMs;
|
|
1752
2003
|
}
|
|
1753
|
-
const dirStat =
|
|
2004
|
+
const dirStat = fs5.statSync(dirPath);
|
|
1754
2005
|
if (dirStat.mtimeMs > latest) latest = dirStat.mtimeMs;
|
|
1755
2006
|
} catch {
|
|
1756
2007
|
}
|
|
@@ -1759,14 +2010,14 @@ function getMostRecentMtime(dirPath) {
|
|
|
1759
2010
|
async function runCleanupSessions(options) {
|
|
1760
2011
|
const cwd = options.cwd ?? process.cwd();
|
|
1761
2012
|
const dryRun = options.dryRun ?? false;
|
|
1762
|
-
const sessionsDir =
|
|
2013
|
+
const sessionsDir = path11.join(cwd, ".harness", "sessions");
|
|
1763
2014
|
const result = { removed: [], kept: [] };
|
|
1764
|
-
if (!
|
|
2015
|
+
if (!fs5.existsSync(sessionsDir)) {
|
|
1765
2016
|
return Ok(result);
|
|
1766
2017
|
}
|
|
1767
2018
|
let entries;
|
|
1768
2019
|
try {
|
|
1769
|
-
entries =
|
|
2020
|
+
entries = fs5.readdirSync(sessionsDir, { withFileTypes: true });
|
|
1770
2021
|
} catch (err) {
|
|
1771
2022
|
return Err(
|
|
1772
2023
|
new CLIError(
|
|
@@ -1778,14 +2029,14 @@ async function runCleanupSessions(options) {
|
|
|
1778
2029
|
const now = Date.now();
|
|
1779
2030
|
for (const entry of entries) {
|
|
1780
2031
|
if (!entry.isDirectory()) continue;
|
|
1781
|
-
const sessionPath =
|
|
2032
|
+
const sessionPath = path11.join(sessionsDir, entry.name);
|
|
1782
2033
|
const mostRecent = getMostRecentMtime(sessionPath);
|
|
1783
2034
|
const ageMs = now - mostRecent;
|
|
1784
2035
|
if (ageMs > STALE_TTL_MS) {
|
|
1785
2036
|
result.removed.push(entry.name);
|
|
1786
2037
|
if (!dryRun) {
|
|
1787
2038
|
try {
|
|
1788
|
-
|
|
2039
|
+
fs5.rmSync(sessionPath, { recursive: true, force: true });
|
|
1789
2040
|
} catch (err) {
|
|
1790
2041
|
return Err(
|
|
1791
2042
|
new CLIError(
|
|
@@ -1828,9 +2079,9 @@ Cleaned up ${removed.length} stale session(s).`);
|
|
|
1828
2079
|
}
|
|
1829
2080
|
}
|
|
1830
2081
|
function createCleanupSessionsCommand() {
|
|
1831
|
-
const command = new
|
|
2082
|
+
const command = new Command18("cleanup-sessions").description("Remove stale session directories from .harness/sessions/ (no write in 24h)").option("--dry-run", "List stale sessions without deleting them", false).option("--path <path>", "Project root path", ".").action(async (opts, cmd) => {
|
|
1832
2083
|
const globalOpts = cmd.optsWithGlobals();
|
|
1833
|
-
const cwd =
|
|
2084
|
+
const cwd = path11.resolve(opts.path);
|
|
1834
2085
|
const result = await runCleanupSessions({ cwd, dryRun: opts.dryRun });
|
|
1835
2086
|
if (!result.ok) {
|
|
1836
2087
|
logger.error(result.error.message);
|
|
@@ -1844,11 +2095,11 @@ function createCleanupSessionsCommand() {
|
|
|
1844
2095
|
}
|
|
1845
2096
|
|
|
1846
2097
|
// src/commands/dashboard.ts
|
|
1847
|
-
import { Command as
|
|
2098
|
+
import { Command as Command19 } from "commander";
|
|
1848
2099
|
import { spawn } from "child_process";
|
|
1849
2100
|
import { existsSync as existsSync4 } from "fs";
|
|
1850
2101
|
import { createRequire } from "module";
|
|
1851
|
-
import { dirname as dirname2, join as
|
|
2102
|
+
import { dirname as dirname2, join as join6, resolve as resolve11 } from "path";
|
|
1852
2103
|
import { setTimeout } from "timers";
|
|
1853
2104
|
import { fileURLToPath } from "url";
|
|
1854
2105
|
var __filename = fileURLToPath(import.meta.url);
|
|
@@ -1864,20 +2115,20 @@ function resolveServerScript() {
|
|
|
1864
2115
|
try {
|
|
1865
2116
|
const pkgPath = require2.resolve("@harness-engineering/dashboard/package.json");
|
|
1866
2117
|
const pkgDir = dirname2(pkgPath);
|
|
1867
|
-
const built2 =
|
|
2118
|
+
const built2 = join6(pkgDir, "dist", "server", "serve.js");
|
|
1868
2119
|
if (existsSync4(built2)) return { script: built2, dev: false };
|
|
1869
|
-
const dev2 =
|
|
2120
|
+
const dev2 = join6(pkgDir, "src", "server", "serve.ts");
|
|
1870
2121
|
if (existsSync4(dev2)) return { script: dev2, dev: true };
|
|
1871
2122
|
} catch {
|
|
1872
2123
|
}
|
|
1873
2124
|
const built = [
|
|
1874
|
-
|
|
1875
|
-
|
|
2125
|
+
join6(__dirname, "..", "..", "..", "dashboard", "dist", "server", "serve.js"),
|
|
2126
|
+
join6(__dirname, "..", "..", "..", "..", "packages", "dashboard", "dist", "server", "serve.js")
|
|
1876
2127
|
].find((p) => existsSync4(p));
|
|
1877
2128
|
if (built) return { script: built, dev: false };
|
|
1878
2129
|
const dev = [
|
|
1879
|
-
|
|
1880
|
-
|
|
2130
|
+
join6(__dirname, "..", "..", "..", "dashboard", "src", "server", "serve.ts"),
|
|
2131
|
+
join6(__dirname, "..", "..", "..", "..", "packages", "dashboard", "src", "server", "serve.ts")
|
|
1881
2132
|
].find((p) => existsSync4(p));
|
|
1882
2133
|
return dev ? { script: dev, dev: true } : null;
|
|
1883
2134
|
}
|
|
@@ -1898,8 +2149,8 @@ function spawnDashboardServer(server, env) {
|
|
|
1898
2149
|
function runDashboard(opts) {
|
|
1899
2150
|
const clientPort = Number(opts.port ?? DEFAULT_CLIENT_PORT);
|
|
1900
2151
|
const apiPort = Number(opts.apiPort ?? DEFAULT_API_PORT);
|
|
1901
|
-
const projectPath =
|
|
1902
|
-
const url = `http://localhost:${
|
|
2152
|
+
const projectPath = resolve11(opts.cwd ?? process.cwd());
|
|
2153
|
+
const url = `http://localhost:${apiPort}`;
|
|
1903
2154
|
const server = resolveServerScript();
|
|
1904
2155
|
if (!server) {
|
|
1905
2156
|
console.error("Could not locate the dashboard server. Run `pnpm build` in packages/dashboard.");
|
|
@@ -1919,14 +2170,14 @@ function runDashboard(opts) {
|
|
|
1919
2170
|
}
|
|
1920
2171
|
}
|
|
1921
2172
|
function createDashboardCommand() {
|
|
1922
|
-
return new
|
|
2173
|
+
return new Command19("dashboard").description("Start the Harness local web dashboard").option("--port <port>", "Client dev server port", String(DEFAULT_CLIENT_PORT)).option("--api-port <port>", "API server port", String(DEFAULT_API_PORT)).option("--no-open", "Do not automatically open browser").option("--cwd <path>", "Project directory (defaults to cwd)").action((opts) => runDashboard(opts));
|
|
1923
2174
|
}
|
|
1924
2175
|
|
|
1925
2176
|
// src/commands/doctor.ts
|
|
1926
|
-
import { Command as
|
|
1927
|
-
import * as
|
|
2177
|
+
import { Command as Command20 } from "commander";
|
|
2178
|
+
import * as fs7 from "fs";
|
|
1928
2179
|
import * as os from "os";
|
|
1929
|
-
import * as
|
|
2180
|
+
import * as path13 from "path";
|
|
1930
2181
|
import chalk from "chalk";
|
|
1931
2182
|
|
|
1932
2183
|
// src/utils/node-version.ts
|
|
@@ -2009,24 +2260,24 @@ var INTEGRATION_REGISTRY = [
|
|
|
2009
2260
|
];
|
|
2010
2261
|
|
|
2011
2262
|
// src/integrations/config.ts
|
|
2012
|
-
import * as
|
|
2013
|
-
import * as
|
|
2263
|
+
import * as fs6 from "fs";
|
|
2264
|
+
import * as path12 from "path";
|
|
2014
2265
|
function readJsonSafe(filePath) {
|
|
2015
|
-
if (!
|
|
2266
|
+
if (!fs6.existsSync(filePath)) return null;
|
|
2016
2267
|
try {
|
|
2017
|
-
return JSON.parse(
|
|
2268
|
+
return JSON.parse(fs6.readFileSync(filePath, "utf-8"));
|
|
2018
2269
|
} catch {
|
|
2019
2270
|
return null;
|
|
2020
2271
|
}
|
|
2021
2272
|
}
|
|
2022
2273
|
function writeJson(filePath, data) {
|
|
2023
|
-
const dir =
|
|
2024
|
-
if (!
|
|
2025
|
-
|
|
2274
|
+
const dir = path12.dirname(filePath);
|
|
2275
|
+
if (!fs6.existsSync(dir)) {
|
|
2276
|
+
fs6.mkdirSync(dir, { recursive: true });
|
|
2026
2277
|
}
|
|
2027
2278
|
const tmp = filePath + ".tmp";
|
|
2028
|
-
|
|
2029
|
-
|
|
2279
|
+
fs6.writeFileSync(tmp, JSON.stringify(data, null, 2) + "\n");
|
|
2280
|
+
fs6.renameSync(tmp, filePath);
|
|
2030
2281
|
}
|
|
2031
2282
|
function readMcpConfig(filePath) {
|
|
2032
2283
|
const config = readJsonSafe(filePath);
|
|
@@ -2040,7 +2291,7 @@ function writeMcpEntry(filePath, name, entry) {
|
|
|
2040
2291
|
writeJson(filePath, config);
|
|
2041
2292
|
}
|
|
2042
2293
|
function removeMcpEntry(filePath, name) {
|
|
2043
|
-
if (!
|
|
2294
|
+
if (!fs6.existsSync(filePath)) return;
|
|
2044
2295
|
const config = readMcpConfig(filePath);
|
|
2045
2296
|
delete config.mcpServers[name];
|
|
2046
2297
|
writeJson(filePath, config);
|
|
@@ -2079,7 +2330,7 @@ function checkNodeVersion2() {
|
|
|
2079
2330
|
}
|
|
2080
2331
|
function countCommandFiles(dir, ext) {
|
|
2081
2332
|
try {
|
|
2082
|
-
return
|
|
2333
|
+
return fs7.readdirSync(dir).filter((f) => f.endsWith(ext)).length;
|
|
2083
2334
|
} catch {
|
|
2084
2335
|
return 0;
|
|
2085
2336
|
}
|
|
@@ -2088,13 +2339,13 @@ function checkSlashCommands() {
|
|
|
2088
2339
|
const platforms = [
|
|
2089
2340
|
{
|
|
2090
2341
|
name: "Claude Code",
|
|
2091
|
-
dir:
|
|
2342
|
+
dir: path13.join(os.homedir(), ".claude", "commands", "harness"),
|
|
2092
2343
|
ext: ".md",
|
|
2093
2344
|
client: "claude-code"
|
|
2094
2345
|
},
|
|
2095
2346
|
{
|
|
2096
2347
|
name: "Gemini CLI",
|
|
2097
|
-
dir:
|
|
2348
|
+
dir: path13.join(os.homedir(), ".gemini", "commands", "harness"),
|
|
2098
2349
|
ext: ".toml",
|
|
2099
2350
|
client: "gemini-cli"
|
|
2100
2351
|
}
|
|
@@ -2118,7 +2369,7 @@ function checkSlashCommands() {
|
|
|
2118
2369
|
}
|
|
2119
2370
|
function checkMcpConfig(cwd) {
|
|
2120
2371
|
const results = [];
|
|
2121
|
-
const claudeConfig = readMcpConfig(
|
|
2372
|
+
const claudeConfig = readMcpConfig(path13.join(cwd, ".mcp.json"));
|
|
2122
2373
|
if (claudeConfig.mcpServers?.["harness"]) {
|
|
2123
2374
|
results.push({
|
|
2124
2375
|
name: "mcp-claude",
|
|
@@ -2133,9 +2384,9 @@ function checkMcpConfig(cwd) {
|
|
|
2133
2384
|
fix: "Run: harness setup-mcp --client claude"
|
|
2134
2385
|
});
|
|
2135
2386
|
}
|
|
2136
|
-
const geminiDir =
|
|
2137
|
-
if (
|
|
2138
|
-
const geminiConfig = readMcpConfig(
|
|
2387
|
+
const geminiDir = path13.join(cwd, ".gemini");
|
|
2388
|
+
if (fs7.existsSync(geminiDir)) {
|
|
2389
|
+
const geminiConfig = readMcpConfig(path13.join(geminiDir, "settings.json"));
|
|
2139
2390
|
if (geminiConfig.mcpServers?.["harness"]) {
|
|
2140
2391
|
results.push({
|
|
2141
2392
|
name: "mcp-gemini",
|
|
@@ -2154,12 +2405,12 @@ function checkMcpConfig(cwd) {
|
|
|
2154
2405
|
return results;
|
|
2155
2406
|
}
|
|
2156
2407
|
function loadMcpPresence(cwd) {
|
|
2157
|
-
const mcpPath =
|
|
2158
|
-
const geminiDir =
|
|
2159
|
-
const hasGemini =
|
|
2408
|
+
const mcpPath = path13.join(cwd, ".mcp.json");
|
|
2409
|
+
const geminiDir = path13.join(cwd, ".gemini");
|
|
2410
|
+
const hasGemini = fs7.existsSync(geminiDir);
|
|
2160
2411
|
return {
|
|
2161
2412
|
mcpConfig: readMcpConfig(mcpPath),
|
|
2162
|
-
geminiConfig: hasGemini ? readMcpConfig(
|
|
2413
|
+
geminiConfig: hasGemini ? readMcpConfig(path13.join(geminiDir, "settings.json")) : null,
|
|
2163
2414
|
hasGemini
|
|
2164
2415
|
};
|
|
2165
2416
|
}
|
|
@@ -2191,7 +2442,7 @@ function checkTier0Presence(def, presence) {
|
|
|
2191
2442
|
function checkIntegrations(cwd) {
|
|
2192
2443
|
const results = [];
|
|
2193
2444
|
const presence = loadMcpPresence(cwd);
|
|
2194
|
-
const configPath =
|
|
2445
|
+
const configPath = path13.join(cwd, "harness.config.json");
|
|
2195
2446
|
const integrationsConfig = readIntegrationsConfig(configPath);
|
|
2196
2447
|
for (const def of INTEGRATION_REGISTRY.filter((d) => d.tier === 0)) {
|
|
2197
2448
|
results.push(checkTier0Presence(def, presence));
|
|
@@ -2241,7 +2492,7 @@ function formatCheck(check) {
|
|
|
2241
2492
|
return line;
|
|
2242
2493
|
}
|
|
2243
2494
|
function createDoctorCommand() {
|
|
2244
|
-
return new
|
|
2495
|
+
return new Command20("doctor").description("Check environment health: Node version, slash commands, MCP configuration").action((_opts, cmd) => {
|
|
2245
2496
|
const globalOpts = cmd.optsWithGlobals();
|
|
2246
2497
|
const cwd = process.cwd();
|
|
2247
2498
|
const useJson = globalOpts.json;
|
|
@@ -2266,8 +2517,9 @@ function createDoctorCommand() {
|
|
|
2266
2517
|
}
|
|
2267
2518
|
|
|
2268
2519
|
// src/commands/fix-drift.ts
|
|
2269
|
-
|
|
2270
|
-
import
|
|
2520
|
+
init_dist();
|
|
2521
|
+
import { Command as Command21 } from "commander";
|
|
2522
|
+
import * as path14 from "path";
|
|
2271
2523
|
async function runFixDrift(options) {
|
|
2272
2524
|
const cwd = options.cwd ?? process.cwd();
|
|
2273
2525
|
const dryRun = options.dryRun !== false;
|
|
@@ -2276,11 +2528,11 @@ async function runFixDrift(options) {
|
|
|
2276
2528
|
return Err(configResult.error);
|
|
2277
2529
|
}
|
|
2278
2530
|
const config = configResult.value;
|
|
2279
|
-
const rootDir =
|
|
2280
|
-
const docsDir =
|
|
2531
|
+
const rootDir = path14.resolve(cwd, config.rootDir);
|
|
2532
|
+
const docsDir = path14.resolve(cwd, config.docsDir);
|
|
2281
2533
|
const entropyConfig = {
|
|
2282
2534
|
rootDir,
|
|
2283
|
-
entryPoints:
|
|
2535
|
+
...config.entropy?.entryPoints && { entryPoints: config.entropy.entryPoints },
|
|
2284
2536
|
docPaths: [docsDir],
|
|
2285
2537
|
analyze: {
|
|
2286
2538
|
drift: true,
|
|
@@ -2399,9 +2651,9 @@ function printFixDriftResult(value, mode, formatter) {
|
|
|
2399
2651
|
}
|
|
2400
2652
|
}
|
|
2401
2653
|
function createFixDriftCommand() {
|
|
2402
|
-
const command = new
|
|
2654
|
+
const command = new Command21("fix-drift").description("Auto-fix entropy issues (doc drift, dead code)").option("--no-dry-run", "Actually apply fixes (default is dry-run mode)").action(async (opts, cmd) => {
|
|
2403
2655
|
const globalOpts = cmd.optsWithGlobals();
|
|
2404
|
-
const mode =
|
|
2656
|
+
const mode = resolveOutputMode(globalOpts);
|
|
2405
2657
|
const formatter = new OutputFormatter(mode);
|
|
2406
2658
|
const result = await runFixDrift({
|
|
2407
2659
|
configPath: globalOpts.config,
|
|
@@ -2429,7 +2681,7 @@ function createFixDriftCommand() {
|
|
|
2429
2681
|
}
|
|
2430
2682
|
|
|
2431
2683
|
// src/commands/generate.ts
|
|
2432
|
-
import { Command as
|
|
2684
|
+
import { Command as Command22 } from "commander";
|
|
2433
2685
|
function validatePlatforms(platforms) {
|
|
2434
2686
|
for (const p of platforms) {
|
|
2435
2687
|
if (!VALID_PLATFORMS.includes(p)) {
|
|
@@ -2489,27 +2741,27 @@ async function runGenerateAction(opts, globalOpts) {
|
|
|
2489
2741
|
}
|
|
2490
2742
|
}
|
|
2491
2743
|
function createGenerateCommand() {
|
|
2492
|
-
return new
|
|
2744
|
+
return new Command22("generate").description("Generate all platform integrations (slash commands + agent definitions)").option("--platforms <list>", "Target platforms (comma-separated)", "claude-code,gemini-cli").option("--global", "Write to global directories", false).option("--include-global", "Include built-in global skills", false).option("--output <dir>", "Custom output directory").option("--dry-run", "Show what would change without writing", false).option("--yes", "Skip deletion confirmation prompts", false).action(async (opts, cmd) => {
|
|
2493
2745
|
await runGenerateAction(opts, cmd.optsWithGlobals());
|
|
2494
2746
|
});
|
|
2495
2747
|
}
|
|
2496
2748
|
|
|
2497
2749
|
// src/commands/graph/index.ts
|
|
2498
|
-
import { Command as
|
|
2750
|
+
import { Command as Command25 } from "commander";
|
|
2499
2751
|
|
|
2500
2752
|
// src/commands/graph/status.ts
|
|
2501
|
-
import * as
|
|
2753
|
+
import * as path15 from "path";
|
|
2502
2754
|
async function runGraphStatus(projectPath) {
|
|
2503
|
-
const { GraphStore } = await import("./dist-
|
|
2504
|
-
const graphDir =
|
|
2755
|
+
const { GraphStore } = await import("./dist-RADHOOXG.js");
|
|
2756
|
+
const graphDir = path15.join(projectPath, ".harness", "graph");
|
|
2505
2757
|
const store = new GraphStore();
|
|
2506
2758
|
const loaded = await store.load(graphDir);
|
|
2507
2759
|
if (!loaded) return { status: "no_graph", message: "No graph found. Run `harness scan` first." };
|
|
2508
|
-
const
|
|
2509
|
-
const metaPath =
|
|
2760
|
+
const fs41 = await import("fs/promises");
|
|
2761
|
+
const metaPath = path15.join(graphDir, "metadata.json");
|
|
2510
2762
|
let lastScan = "unknown";
|
|
2511
2763
|
try {
|
|
2512
|
-
const meta = JSON.parse(await
|
|
2764
|
+
const meta = JSON.parse(await fs41.readFile(metaPath, "utf-8"));
|
|
2513
2765
|
lastScan = meta.lastScanTimestamp;
|
|
2514
2766
|
} catch {
|
|
2515
2767
|
}
|
|
@@ -2520,8 +2772,8 @@ async function runGraphStatus(projectPath) {
|
|
|
2520
2772
|
}
|
|
2521
2773
|
let connectorSyncStatus = {};
|
|
2522
2774
|
try {
|
|
2523
|
-
const syncMetaPath =
|
|
2524
|
-
const syncMeta = JSON.parse(await
|
|
2775
|
+
const syncMetaPath = path15.join(graphDir, "sync-metadata.json");
|
|
2776
|
+
const syncMeta = JSON.parse(await fs41.readFile(syncMetaPath, "utf-8"));
|
|
2525
2777
|
for (const [name, data] of Object.entries(syncMeta.connectors ?? {})) {
|
|
2526
2778
|
connectorSyncStatus[name] = data.lastSyncTimestamp;
|
|
2527
2779
|
}
|
|
@@ -2538,10 +2790,10 @@ async function runGraphStatus(projectPath) {
|
|
|
2538
2790
|
}
|
|
2539
2791
|
|
|
2540
2792
|
// src/commands/graph/export.ts
|
|
2541
|
-
import * as
|
|
2793
|
+
import * as path16 from "path";
|
|
2542
2794
|
async function runGraphExport(projectPath, format) {
|
|
2543
|
-
const { GraphStore } = await import("./dist-
|
|
2544
|
-
const graphDir =
|
|
2795
|
+
const { GraphStore } = await import("./dist-RADHOOXG.js");
|
|
2796
|
+
const graphDir = path16.join(projectPath, ".harness", "graph");
|
|
2545
2797
|
const store = new GraphStore();
|
|
2546
2798
|
const loaded = await store.load(graphDir);
|
|
2547
2799
|
if (!loaded) throw new Error("No graph found. Run `harness scan` first.");
|
|
@@ -2572,50 +2824,11 @@ async function runGraphExport(projectPath, format) {
|
|
|
2572
2824
|
// src/commands/graph/index.ts
|
|
2573
2825
|
import * as path19 from "path";
|
|
2574
2826
|
|
|
2575
|
-
// src/commands/graph/scan.ts
|
|
2576
|
-
import { Command as Command21 } from "commander";
|
|
2577
|
-
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
2827
|
// src/commands/graph/query.ts
|
|
2615
|
-
import { Command as
|
|
2828
|
+
import { Command as Command23 } from "commander";
|
|
2616
2829
|
import * as path17 from "path";
|
|
2617
2830
|
async function runQuery(projectPath, rootNodeId, opts) {
|
|
2618
|
-
const { GraphStore, ContextQL } = await import("./dist-
|
|
2831
|
+
const { GraphStore, ContextQL } = await import("./dist-RADHOOXG.js");
|
|
2619
2832
|
const store = new GraphStore();
|
|
2620
2833
|
const graphDir = path17.join(projectPath, ".harness", "graph");
|
|
2621
2834
|
const loaded = await store.load(graphDir);
|
|
@@ -2658,19 +2871,19 @@ async function runQueryAction(rootNodeId, opts, globalOpts) {
|
|
|
2658
2871
|
}
|
|
2659
2872
|
}
|
|
2660
2873
|
function createQueryCommand() {
|
|
2661
|
-
return new
|
|
2874
|
+
return new Command23("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
2875
|
await runQueryAction(rootNodeId, opts, cmd.optsWithGlobals());
|
|
2663
2876
|
});
|
|
2664
2877
|
}
|
|
2665
2878
|
|
|
2666
2879
|
// src/commands/graph/ingest.ts
|
|
2667
|
-
import { Command as
|
|
2880
|
+
import { Command as Command24 } from "commander";
|
|
2668
2881
|
import * as path18 from "path";
|
|
2669
2882
|
async function loadConnectorConfig(projectPath, source) {
|
|
2670
2883
|
try {
|
|
2671
|
-
const
|
|
2884
|
+
const fs41 = await import("fs/promises");
|
|
2672
2885
|
const configPath = path18.join(projectPath, "harness.config.json");
|
|
2673
|
-
const config = JSON.parse(await
|
|
2886
|
+
const config = JSON.parse(await fs41.readFile(configPath, "utf-8"));
|
|
2674
2887
|
const connector = config.graph?.connectors?.[source];
|
|
2675
2888
|
return connector ?? {};
|
|
2676
2889
|
} catch {
|
|
@@ -2709,7 +2922,7 @@ async function runIngest(projectPath, source, opts) {
|
|
|
2709
2922
|
SlackConnector,
|
|
2710
2923
|
CIConnector,
|
|
2711
2924
|
ConfluenceConnector
|
|
2712
|
-
} = await import("./dist-
|
|
2925
|
+
} = await import("./dist-RADHOOXG.js");
|
|
2713
2926
|
const graphDir = path18.join(projectPath, ".harness", "graph");
|
|
2714
2927
|
const store = new GraphStore();
|
|
2715
2928
|
await store.load(graphDir);
|
|
@@ -2777,7 +2990,7 @@ async function runIngest(projectPath, source, opts) {
|
|
|
2777
2990
|
return result;
|
|
2778
2991
|
}
|
|
2779
2992
|
function createIngestCommand() {
|
|
2780
|
-
return new
|
|
2993
|
+
return new Command24("ingest").description("Ingest data into the knowledge graph").option(
|
|
2781
2994
|
"--source <name>",
|
|
2782
2995
|
"Source to ingest (code, knowledge, git, jira, slack, ci, confluence)"
|
|
2783
2996
|
).option("--all", "Run all sources (code, knowledge, git, and configured connectors)").option("--full", "Force full re-ingestion").action(async (opts, cmd) => {
|
|
@@ -2853,18 +3066,18 @@ async function runExportAction(opts, cmd) {
|
|
|
2853
3066
|
}
|
|
2854
3067
|
}
|
|
2855
3068
|
function createGraphCommand() {
|
|
2856
|
-
const graph = new
|
|
3069
|
+
const graph = new Command25("graph").description("Knowledge graph management");
|
|
2857
3070
|
graph.command("status").description("Show graph statistics").action(runStatusAction);
|
|
2858
3071
|
graph.command("export").description("Export graph").requiredOption("--format <format>", "Output format (json, mermaid)").action(runExportAction);
|
|
2859
3072
|
return graph;
|
|
2860
3073
|
}
|
|
2861
3074
|
|
|
2862
3075
|
// src/commands/hooks/index.ts
|
|
2863
|
-
import { Command as
|
|
3076
|
+
import { Command as Command30 } from "commander";
|
|
2864
3077
|
|
|
2865
3078
|
// src/commands/hooks/init.ts
|
|
2866
|
-
import { Command as
|
|
2867
|
-
import * as
|
|
3079
|
+
import { Command as Command26 } from "commander";
|
|
3080
|
+
import * as fs8 from "fs";
|
|
2868
3081
|
import * as path20 from "path";
|
|
2869
3082
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
2870
3083
|
|
|
@@ -2899,7 +3112,7 @@ var __dirname2 = path20.dirname(__filename2);
|
|
|
2899
3112
|
var VALID_PROFILES = ["minimal", "standard", "strict"];
|
|
2900
3113
|
function resolveHookSourceDir() {
|
|
2901
3114
|
const candidate = path20.resolve(__dirname2, "..", "..", "hooks");
|
|
2902
|
-
if (
|
|
3115
|
+
if (fs8.existsSync(candidate)) {
|
|
2903
3116
|
return candidate;
|
|
2904
3117
|
}
|
|
2905
3118
|
throw new Error(`Cannot locate hook scripts directory. Expected at: ${candidate}`);
|
|
@@ -2928,11 +3141,11 @@ function mergeSettings(existing, hooksConfig) {
|
|
|
2928
3141
|
function initHooks(options) {
|
|
2929
3142
|
const { profile, projectDir } = options;
|
|
2930
3143
|
const hooksDestDir = path20.join(projectDir, ".harness", "hooks");
|
|
2931
|
-
|
|
2932
|
-
if (
|
|
2933
|
-
for (const entry of
|
|
3144
|
+
fs8.mkdirSync(hooksDestDir, { recursive: true });
|
|
3145
|
+
if (fs8.existsSync(hooksDestDir)) {
|
|
3146
|
+
for (const entry of fs8.readdirSync(hooksDestDir)) {
|
|
2934
3147
|
if (entry.endsWith(".js")) {
|
|
2935
|
-
|
|
3148
|
+
fs8.unlinkSync(path20.join(hooksDestDir, entry));
|
|
2936
3149
|
}
|
|
2937
3150
|
}
|
|
2938
3151
|
}
|
|
@@ -2943,20 +3156,20 @@ function initHooks(options) {
|
|
|
2943
3156
|
for (const script of activeScripts) {
|
|
2944
3157
|
const srcFile = path20.join(sourceDir, `${script.name}.js`);
|
|
2945
3158
|
const destFile = path20.join(hooksDestDir, `${script.name}.js`);
|
|
2946
|
-
if (
|
|
2947
|
-
|
|
3159
|
+
if (fs8.existsSync(srcFile)) {
|
|
3160
|
+
fs8.copyFileSync(srcFile, destFile);
|
|
2948
3161
|
copiedScripts.push(script.name);
|
|
2949
3162
|
}
|
|
2950
3163
|
}
|
|
2951
3164
|
const profilePath = path20.join(hooksDestDir, "profile.json");
|
|
2952
|
-
|
|
3165
|
+
fs8.writeFileSync(profilePath, JSON.stringify({ profile }, null, 2) + "\n");
|
|
2953
3166
|
const claudeDir = path20.join(projectDir, ".claude");
|
|
2954
|
-
|
|
3167
|
+
fs8.mkdirSync(claudeDir, { recursive: true });
|
|
2955
3168
|
const settingsPath = path20.join(claudeDir, "settings.json");
|
|
2956
3169
|
let existing = {};
|
|
2957
|
-
if (
|
|
3170
|
+
if (fs8.existsSync(settingsPath)) {
|
|
2958
3171
|
try {
|
|
2959
|
-
existing = JSON.parse(
|
|
3172
|
+
existing = JSON.parse(fs8.readFileSync(settingsPath, "utf-8"));
|
|
2960
3173
|
} catch (e) {
|
|
2961
3174
|
throw new Error(
|
|
2962
3175
|
`Malformed .claude/settings.json \u2014 fix the JSON syntax before running hooks init. Parse error: ${e instanceof Error ? e.message : String(e)}`,
|
|
@@ -2966,11 +3179,11 @@ function initHooks(options) {
|
|
|
2966
3179
|
}
|
|
2967
3180
|
const hooksConfig = buildSettingsHooks(profile);
|
|
2968
3181
|
const merged = mergeSettings(existing, hooksConfig);
|
|
2969
|
-
|
|
3182
|
+
fs8.writeFileSync(settingsPath, JSON.stringify(merged, null, 2) + "\n");
|
|
2970
3183
|
return { copiedScripts, settingsPath, profilePath };
|
|
2971
3184
|
}
|
|
2972
3185
|
function createInitCommand2() {
|
|
2973
|
-
return new
|
|
3186
|
+
return new Command26("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
3187
|
const globalOpts = cmd.optsWithGlobals();
|
|
2975
3188
|
const profile = opts.profile;
|
|
2976
3189
|
if (!VALID_PROFILES.includes(profile)) {
|
|
@@ -2994,7 +3207,9 @@ function createInitCommand2() {
|
|
|
2994
3207
|
`Installed ${result.copiedScripts.length} hook scripts to .harness/hooks/`
|
|
2995
3208
|
);
|
|
2996
3209
|
logger.info(`Profile: ${profile}`);
|
|
2997
|
-
logger.info(
|
|
3210
|
+
logger.info(
|
|
3211
|
+
`Settings: ${path20.relative(projectDir, result.settingsPath).replaceAll("\\", "/")}`
|
|
3212
|
+
);
|
|
2998
3213
|
logger.dim("Run 'harness hooks list' to see installed hooks");
|
|
2999
3214
|
}
|
|
3000
3215
|
} catch (err) {
|
|
@@ -3006,19 +3221,19 @@ function createInitCommand2() {
|
|
|
3006
3221
|
}
|
|
3007
3222
|
|
|
3008
3223
|
// src/commands/hooks/list.ts
|
|
3009
|
-
import { Command as
|
|
3010
|
-
import * as
|
|
3224
|
+
import { Command as Command27 } from "commander";
|
|
3225
|
+
import * as fs9 from "fs";
|
|
3011
3226
|
import * as path21 from "path";
|
|
3012
3227
|
function listHooks(projectDir) {
|
|
3013
3228
|
const hooksDir = path21.join(projectDir, ".harness", "hooks");
|
|
3014
3229
|
const profilePath = path21.join(hooksDir, "profile.json");
|
|
3015
|
-
if (!
|
|
3230
|
+
if (!fs9.existsSync(profilePath)) {
|
|
3016
3231
|
return { installed: false, profile: null, hooks: [] };
|
|
3017
3232
|
}
|
|
3018
3233
|
let profile = "standard";
|
|
3019
3234
|
let warning;
|
|
3020
3235
|
try {
|
|
3021
|
-
const data = JSON.parse(
|
|
3236
|
+
const data = JSON.parse(fs9.readFileSync(profilePath, "utf-8"));
|
|
3022
3237
|
if (data.profile && ["minimal", "standard", "strict"].includes(data.profile)) {
|
|
3023
3238
|
profile = data.profile;
|
|
3024
3239
|
}
|
|
@@ -3039,7 +3254,7 @@ function listHooks(projectDir) {
|
|
|
3039
3254
|
return result;
|
|
3040
3255
|
}
|
|
3041
3256
|
function createListCommand() {
|
|
3042
|
-
return new
|
|
3257
|
+
return new Command27("list").description("Show installed hooks and active profile").action(async (_opts, cmd) => {
|
|
3043
3258
|
const globalOpts = cmd.optsWithGlobals();
|
|
3044
3259
|
const projectDir = process.cwd();
|
|
3045
3260
|
const result = listHooks(projectDir);
|
|
@@ -3060,28 +3275,28 @@ function createListCommand() {
|
|
|
3060
3275
|
}
|
|
3061
3276
|
|
|
3062
3277
|
// src/commands/hooks/remove.ts
|
|
3063
|
-
import { Command as
|
|
3064
|
-
import * as
|
|
3278
|
+
import { Command as Command28 } from "commander";
|
|
3279
|
+
import * as fs10 from "fs";
|
|
3065
3280
|
import * as path22 from "path";
|
|
3066
3281
|
function removeHooks(projectDir) {
|
|
3067
3282
|
const hooksDir = path22.join(projectDir, ".harness", "hooks");
|
|
3068
3283
|
const settingsPath = path22.join(projectDir, ".claude", "settings.json");
|
|
3069
3284
|
let removed = false;
|
|
3070
3285
|
let settingsCleaned = false;
|
|
3071
|
-
if (
|
|
3072
|
-
|
|
3286
|
+
if (fs10.existsSync(hooksDir)) {
|
|
3287
|
+
fs10.rmSync(hooksDir, { recursive: true, force: true });
|
|
3073
3288
|
removed = true;
|
|
3074
3289
|
}
|
|
3075
|
-
if (
|
|
3290
|
+
if (fs10.existsSync(settingsPath)) {
|
|
3076
3291
|
try {
|
|
3077
|
-
const settings = JSON.parse(
|
|
3292
|
+
const settings = JSON.parse(fs10.readFileSync(settingsPath, "utf-8"));
|
|
3078
3293
|
if (settings.hooks !== void 0) {
|
|
3079
3294
|
delete settings.hooks;
|
|
3080
3295
|
settingsCleaned = true;
|
|
3081
3296
|
if (Object.keys(settings).length === 0) {
|
|
3082
|
-
|
|
3297
|
+
fs10.unlinkSync(settingsPath);
|
|
3083
3298
|
} else {
|
|
3084
|
-
|
|
3299
|
+
fs10.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
3085
3300
|
}
|
|
3086
3301
|
}
|
|
3087
3302
|
} catch {
|
|
@@ -3090,7 +3305,7 @@ function removeHooks(projectDir) {
|
|
|
3090
3305
|
return { removed, hooksDir, settingsCleaned };
|
|
3091
3306
|
}
|
|
3092
3307
|
function createRemoveCommand() {
|
|
3093
|
-
return new
|
|
3308
|
+
return new Command28("remove").description("Remove harness-managed hooks from the current project").action(async (_opts, cmd) => {
|
|
3094
3309
|
const globalOpts = cmd.optsWithGlobals();
|
|
3095
3310
|
const projectDir = process.cwd();
|
|
3096
3311
|
const result = removeHooks(projectDir);
|
|
@@ -3112,8 +3327,8 @@ function createRemoveCommand() {
|
|
|
3112
3327
|
}
|
|
3113
3328
|
|
|
3114
3329
|
// src/commands/hooks/add.ts
|
|
3115
|
-
import { Command as
|
|
3116
|
-
import * as
|
|
3330
|
+
import { Command as Command29 } from "commander";
|
|
3331
|
+
import * as fs11 from "fs";
|
|
3117
3332
|
import * as path23 from "path";
|
|
3118
3333
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
3119
3334
|
var __dirname3 = path23.dirname(fileURLToPath3(import.meta.url));
|
|
@@ -3122,11 +3337,11 @@ var ALIASES = {
|
|
|
3122
3337
|
};
|
|
3123
3338
|
function hookSourceDir() {
|
|
3124
3339
|
const d = path23.resolve(__dirname3, "..", "..", "hooks");
|
|
3125
|
-
if (
|
|
3340
|
+
if (fs11.existsSync(d)) return d;
|
|
3126
3341
|
throw new Error(`Hook scripts not found: ${d}`);
|
|
3127
3342
|
}
|
|
3128
3343
|
function readJson(p) {
|
|
3129
|
-
return
|
|
3344
|
+
return fs11.existsSync(p) ? JSON.parse(fs11.readFileSync(p, "utf-8")) : {};
|
|
3130
3345
|
}
|
|
3131
3346
|
function registerHook(s, ev, matcher, name) {
|
|
3132
3347
|
if (!s.hooks[ev]) s.hooks[ev] = [];
|
|
@@ -3140,9 +3355,9 @@ function addHooks(hookName, projectDir) {
|
|
|
3140
3355
|
const result = { added: [], alreadyInstalled: [], notFound: [] };
|
|
3141
3356
|
const srcDir = hookSourceDir();
|
|
3142
3357
|
const destDir = path23.join(projectDir, ".harness", "hooks");
|
|
3143
|
-
|
|
3358
|
+
fs11.mkdirSync(destDir, { recursive: true });
|
|
3144
3359
|
const claudeDir = path23.join(projectDir, ".claude");
|
|
3145
|
-
|
|
3360
|
+
fs11.mkdirSync(claudeDir, { recursive: true });
|
|
3146
3361
|
const settingsPath = path23.join(claudeDir, "settings.json");
|
|
3147
3362
|
const settings = readJson(settingsPath);
|
|
3148
3363
|
if (!settings.hooks) settings.hooks = {};
|
|
@@ -3154,22 +3369,22 @@ function addHooks(hookName, projectDir) {
|
|
|
3154
3369
|
}
|
|
3155
3370
|
const src = path23.join(srcDir, `${name}.js`);
|
|
3156
3371
|
const dest = path23.join(destDir, `${name}.js`);
|
|
3157
|
-
if (
|
|
3372
|
+
if (fs11.existsSync(dest)) {
|
|
3158
3373
|
result.alreadyInstalled.push(name);
|
|
3159
|
-
} else if (!
|
|
3374
|
+
} else if (!fs11.existsSync(src)) {
|
|
3160
3375
|
result.notFound.push(name);
|
|
3161
3376
|
continue;
|
|
3162
3377
|
} else {
|
|
3163
|
-
|
|
3378
|
+
fs11.copyFileSync(src, dest);
|
|
3164
3379
|
result.added.push(name);
|
|
3165
3380
|
}
|
|
3166
3381
|
registerHook(settings, def.event, def.matcher, name);
|
|
3167
3382
|
}
|
|
3168
|
-
|
|
3383
|
+
fs11.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
3169
3384
|
return result;
|
|
3170
3385
|
}
|
|
3171
3386
|
function createAddCommand2() {
|
|
3172
|
-
return new
|
|
3387
|
+
return new Command29("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
3388
|
const projectDir = process.cwd();
|
|
3174
3389
|
try {
|
|
3175
3390
|
const res = addHooks(hookName, projectDir);
|
|
@@ -3194,7 +3409,7 @@ function createAddCommand2() {
|
|
|
3194
3409
|
|
|
3195
3410
|
// src/commands/hooks/index.ts
|
|
3196
3411
|
function createHooksCommand() {
|
|
3197
|
-
const command = new
|
|
3412
|
+
const command = new Command30("hooks").description("Manage Claude Code hook configurations");
|
|
3198
3413
|
command.addCommand(createInitCommand2());
|
|
3199
3414
|
command.addCommand(createListCommand());
|
|
3200
3415
|
command.addCommand(createRemoveCommand());
|
|
@@ -3203,10 +3418,10 @@ function createHooksCommand() {
|
|
|
3203
3418
|
}
|
|
3204
3419
|
|
|
3205
3420
|
// src/commands/impact-preview.ts
|
|
3206
|
-
import { Command as
|
|
3421
|
+
import { Command as Command31 } from "commander";
|
|
3207
3422
|
import { execSync as execSync4 } from "child_process";
|
|
3208
3423
|
import * as path24 from "path";
|
|
3209
|
-
import * as
|
|
3424
|
+
import * as fs12 from "fs";
|
|
3210
3425
|
function getStagedFiles(cwd) {
|
|
3211
3426
|
try {
|
|
3212
3427
|
const output = execSync4("git diff --cached --name-only", {
|
|
@@ -3220,7 +3435,7 @@ function getStagedFiles(cwd) {
|
|
|
3220
3435
|
}
|
|
3221
3436
|
function graphExists(projectPath) {
|
|
3222
3437
|
try {
|
|
3223
|
-
return
|
|
3438
|
+
return fs12.existsSync(path24.join(projectPath, ".harness", "graph", "graph.json"));
|
|
3224
3439
|
} catch {
|
|
3225
3440
|
return false;
|
|
3226
3441
|
}
|
|
@@ -3402,7 +3617,7 @@ async function runImpactPreview(options) {
|
|
|
3402
3617
|
return formatImpactOutput(stagedFiles, acc, options);
|
|
3403
3618
|
}
|
|
3404
3619
|
function createImpactPreviewCommand() {
|
|
3405
|
-
const command = new
|
|
3620
|
+
const command = new Command31("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
3621
|
const output = await runImpactPreview({
|
|
3407
3622
|
detailed: opts.detailed,
|
|
3408
3623
|
perFile: opts.perFile,
|
|
@@ -3415,28 +3630,29 @@ function createImpactPreviewCommand() {
|
|
|
3415
3630
|
}
|
|
3416
3631
|
|
|
3417
3632
|
// src/commands/init.ts
|
|
3418
|
-
|
|
3633
|
+
init_dist();
|
|
3634
|
+
import { Command as Command33 } from "commander";
|
|
3419
3635
|
import chalk3 from "chalk";
|
|
3420
|
-
import * as
|
|
3636
|
+
import * as fs15 from "fs";
|
|
3421
3637
|
import * as path27 from "path";
|
|
3422
3638
|
|
|
3423
3639
|
// src/commands/setup-mcp.ts
|
|
3424
|
-
import { Command as
|
|
3425
|
-
import * as
|
|
3640
|
+
import { Command as Command32 } from "commander";
|
|
3641
|
+
import * as fs14 from "fs";
|
|
3426
3642
|
import * as path26 from "path";
|
|
3427
3643
|
import * as os2 from "os";
|
|
3428
3644
|
import chalk2 from "chalk";
|
|
3429
3645
|
import * as clack from "@clack/prompts";
|
|
3430
3646
|
|
|
3431
3647
|
// src/integrations/toml.ts
|
|
3432
|
-
import * as
|
|
3648
|
+
import * as fs13 from "fs";
|
|
3433
3649
|
import * as path25 from "path";
|
|
3434
3650
|
function writeTomlMcpEntry(filePath, name, entry) {
|
|
3435
3651
|
const dir = path25.dirname(filePath);
|
|
3436
|
-
if (!
|
|
3437
|
-
|
|
3652
|
+
if (!fs13.existsSync(dir)) {
|
|
3653
|
+
fs13.mkdirSync(dir, { recursive: true });
|
|
3438
3654
|
}
|
|
3439
|
-
const existing =
|
|
3655
|
+
const existing = fs13.existsSync(filePath) ? fs13.readFileSync(filePath, "utf-8") : "";
|
|
3440
3656
|
const blockHeader = `[mcp_servers.${name}]`;
|
|
3441
3657
|
const newBlock = serializeTomlMcpBlock(name, entry);
|
|
3442
3658
|
let updated;
|
|
@@ -3447,8 +3663,8 @@ function writeTomlMcpEntry(filePath, name, entry) {
|
|
|
3447
3663
|
updated = existing + separator + newBlock;
|
|
3448
3664
|
}
|
|
3449
3665
|
const tmp = filePath + ".tmp";
|
|
3450
|
-
|
|
3451
|
-
|
|
3666
|
+
fs13.writeFileSync(tmp, updated);
|
|
3667
|
+
fs13.renameSync(tmp, filePath);
|
|
3452
3668
|
}
|
|
3453
3669
|
function serializeTomlMcpBlock(name, entry) {
|
|
3454
3670
|
const lines = [`[mcp_servers.${name}]`];
|
|
@@ -3506,6 +3722,7 @@ var CURSOR_CURATED_TOOLS = [
|
|
|
3506
3722
|
"get_impact",
|
|
3507
3723
|
"detect_entropy",
|
|
3508
3724
|
"run_security_scan",
|
|
3725
|
+
"get_security_trends",
|
|
3509
3726
|
"assess_project",
|
|
3510
3727
|
"manage_state",
|
|
3511
3728
|
"create_self_review",
|
|
@@ -3550,6 +3767,7 @@ var ALL_MCP_TOOLS = [
|
|
|
3550
3767
|
"ingest_source",
|
|
3551
3768
|
"generate_agent_definitions",
|
|
3552
3769
|
"run_security_scan",
|
|
3770
|
+
"get_security_trends",
|
|
3553
3771
|
"check_performance",
|
|
3554
3772
|
"get_perf_baselines",
|
|
3555
3773
|
"update_perf_baselines",
|
|
@@ -3576,7 +3794,8 @@ var ALL_MCP_TOOLS = [
|
|
|
3576
3794
|
"recommend_skills",
|
|
3577
3795
|
"compute_blast_radius",
|
|
3578
3796
|
"dispatch_skills",
|
|
3579
|
-
"compact"
|
|
3797
|
+
"compact",
|
|
3798
|
+
"detect_constraint_emergence"
|
|
3580
3799
|
];
|
|
3581
3800
|
async function runCursorToolPicker() {
|
|
3582
3801
|
try {
|
|
@@ -3604,20 +3823,20 @@ function writeCursorMcpEntryWithTools(configPath, tools) {
|
|
|
3604
3823
|
});
|
|
3605
3824
|
}
|
|
3606
3825
|
function readJsonFile(filePath) {
|
|
3607
|
-
if (!
|
|
3826
|
+
if (!fs14.existsSync(filePath)) return null;
|
|
3608
3827
|
try {
|
|
3609
|
-
return JSON.parse(
|
|
3828
|
+
return JSON.parse(fs14.readFileSync(filePath, "utf-8"));
|
|
3610
3829
|
} catch {
|
|
3611
|
-
|
|
3830
|
+
fs14.copyFileSync(filePath, filePath + ".bak");
|
|
3612
3831
|
return null;
|
|
3613
3832
|
}
|
|
3614
3833
|
}
|
|
3615
3834
|
function writeJsonFile(filePath, data) {
|
|
3616
3835
|
const dir = path26.dirname(filePath);
|
|
3617
|
-
if (!
|
|
3618
|
-
|
|
3836
|
+
if (!fs14.existsSync(dir)) {
|
|
3837
|
+
fs14.mkdirSync(dir, { recursive: true });
|
|
3619
3838
|
}
|
|
3620
|
-
|
|
3839
|
+
fs14.writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n");
|
|
3621
3840
|
}
|
|
3622
3841
|
function configureMcpServer(configPath) {
|
|
3623
3842
|
const config = readJsonFile(configPath) ?? {};
|
|
@@ -3665,8 +3884,8 @@ function setupMcp(cwd, client) {
|
|
|
3665
3884
|
if (client === "all" || client === "codex") {
|
|
3666
3885
|
const configPath = path26.join(cwd, ".codex", "config.toml");
|
|
3667
3886
|
const alreadyConfigured = (() => {
|
|
3668
|
-
if (!
|
|
3669
|
-
const content =
|
|
3887
|
+
if (!fs14.existsSync(configPath)) return false;
|
|
3888
|
+
const content = fs14.readFileSync(configPath, "utf-8");
|
|
3670
3889
|
return content.includes("[mcp_servers.harness]");
|
|
3671
3890
|
})();
|
|
3672
3891
|
if (alreadyConfigured) {
|
|
@@ -3739,7 +3958,7 @@ function printMcpResult(configured, skipped, trustedFolder) {
|
|
|
3739
3958
|
console.log("");
|
|
3740
3959
|
}
|
|
3741
3960
|
function createSetupMcpCommand() {
|
|
3742
|
-
return new
|
|
3961
|
+
return new Command32("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
3962
|
const globalOpts = cmd.optsWithGlobals();
|
|
3744
3963
|
const cwd = process.cwd();
|
|
3745
3964
|
let configured;
|
|
@@ -3771,7 +3990,7 @@ function resolveInitDefaults(options) {
|
|
|
3771
3990
|
async function runInit(options) {
|
|
3772
3991
|
const { cwd, name, force } = resolveInitDefaults(options);
|
|
3773
3992
|
const configPath = path27.join(cwd, "harness.config.json");
|
|
3774
|
-
if (!force &&
|
|
3993
|
+
if (!force && fs15.existsSync(configPath)) {
|
|
3775
3994
|
return Err(
|
|
3776
3995
|
new CLIError("Project already initialized. Use --force to overwrite.", ExitCode.ERROR)
|
|
3777
3996
|
);
|
|
@@ -3879,11 +4098,11 @@ async function runInitAction2(opts) {
|
|
|
3879
4098
|
process.exit(ExitCode.SUCCESS);
|
|
3880
4099
|
}
|
|
3881
4100
|
function createInitCommand3() {
|
|
3882
|
-
const command = new
|
|
4101
|
+
const command = new Command33("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("-t, --template <template>", "Specific template name (e.g. orchestrator)").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
4102
|
const globalOpts = cmd.optsWithGlobals();
|
|
3884
4103
|
await runInitAction2({
|
|
3885
4104
|
name: opts.name,
|
|
3886
|
-
level: opts.level,
|
|
4105
|
+
level: opts.template ?? opts.level,
|
|
3887
4106
|
framework: opts.framework,
|
|
3888
4107
|
language: opts.language,
|
|
3889
4108
|
force: opts.force,
|
|
@@ -3894,15 +4113,15 @@ function createInitCommand3() {
|
|
|
3894
4113
|
}
|
|
3895
4114
|
|
|
3896
4115
|
// src/commands/install.ts
|
|
3897
|
-
import * as
|
|
4116
|
+
import * as fs20 from "fs";
|
|
3898
4117
|
import * as os5 from "os";
|
|
3899
4118
|
import * as path32 from "path";
|
|
3900
4119
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
3901
|
-
import { Command as
|
|
4120
|
+
import { Command as Command34 } from "commander";
|
|
3902
4121
|
import { parse as yamlParse } from "yaml";
|
|
3903
4122
|
|
|
3904
4123
|
// src/registry/npm-client.ts
|
|
3905
|
-
import * as
|
|
4124
|
+
import * as fs16 from "fs";
|
|
3906
4125
|
import * as path28 from "path";
|
|
3907
4126
|
import * as os3 from "os";
|
|
3908
4127
|
var NPM_REGISTRY = "https://registry.npmjs.org";
|
|
@@ -3929,7 +4148,7 @@ function readNpmrcToken(registryUrl) {
|
|
|
3929
4148
|
const candidates = [path28.join(process.cwd(), ".npmrc"), path28.join(os3.homedir(), ".npmrc")];
|
|
3930
4149
|
for (const npmrcPath of candidates) {
|
|
3931
4150
|
try {
|
|
3932
|
-
const content =
|
|
4151
|
+
const content = fs16.readFileSync(npmrcPath, "utf-8");
|
|
3933
4152
|
for (const line of content.split("\n")) {
|
|
3934
4153
|
const trimmed = line.trim();
|
|
3935
4154
|
if (trimmed.startsWith(registryPath)) {
|
|
@@ -4024,19 +4243,19 @@ async function searchNpmRegistry(query, registryUrl) {
|
|
|
4024
4243
|
}
|
|
4025
4244
|
|
|
4026
4245
|
// src/registry/tarball.ts
|
|
4027
|
-
import * as
|
|
4246
|
+
import * as fs17 from "fs";
|
|
4028
4247
|
import * as path29 from "path";
|
|
4029
4248
|
import * as os4 from "os";
|
|
4030
4249
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
4031
4250
|
function extractTarball(tarballBuffer) {
|
|
4032
|
-
const tmpDir =
|
|
4251
|
+
const tmpDir = fs17.mkdtempSync(path29.join(os4.tmpdir(), "harness-skill-install-"));
|
|
4033
4252
|
const tarballPath = path29.join(tmpDir, "package.tgz");
|
|
4034
4253
|
try {
|
|
4035
|
-
|
|
4254
|
+
fs17.writeFileSync(tarballPath, tarballBuffer);
|
|
4036
4255
|
execFileSync2("tar", ["-xzf", tarballPath, "-C", tmpDir], {
|
|
4037
4256
|
timeout: 3e4
|
|
4038
4257
|
});
|
|
4039
|
-
|
|
4258
|
+
fs17.unlinkSync(tarballPath);
|
|
4040
4259
|
} catch (err) {
|
|
4041
4260
|
cleanupTempDir(tmpDir);
|
|
4042
4261
|
throw new Error(
|
|
@@ -4047,22 +4266,22 @@ function extractTarball(tarballBuffer) {
|
|
|
4047
4266
|
return tmpDir;
|
|
4048
4267
|
}
|
|
4049
4268
|
function placeSkillContent(extractedPkgDir, communityBaseDir, skillName, platforms) {
|
|
4050
|
-
const files =
|
|
4269
|
+
const files = fs17.readdirSync(extractedPkgDir);
|
|
4051
4270
|
for (const platform of platforms) {
|
|
4052
4271
|
const targetDir = path29.join(communityBaseDir, platform, skillName);
|
|
4053
|
-
if (
|
|
4054
|
-
|
|
4272
|
+
if (fs17.existsSync(targetDir)) {
|
|
4273
|
+
fs17.rmSync(targetDir, { recursive: true, force: true });
|
|
4055
4274
|
}
|
|
4056
|
-
|
|
4275
|
+
fs17.mkdirSync(targetDir, { recursive: true });
|
|
4057
4276
|
for (const file of files) {
|
|
4058
4277
|
if (file === "package.json" || file === "node_modules") continue;
|
|
4059
4278
|
const srcPath = path29.join(extractedPkgDir, file);
|
|
4060
4279
|
const destPath = path29.join(targetDir, file);
|
|
4061
|
-
const stat =
|
|
4280
|
+
const stat = fs17.statSync(srcPath);
|
|
4062
4281
|
if (stat.isDirectory()) {
|
|
4063
|
-
|
|
4282
|
+
fs17.cpSync(srcPath, destPath, { recursive: true });
|
|
4064
4283
|
} else {
|
|
4065
|
-
|
|
4284
|
+
fs17.copyFileSync(srcPath, destPath);
|
|
4066
4285
|
}
|
|
4067
4286
|
}
|
|
4068
4287
|
}
|
|
@@ -4070,14 +4289,14 @@ function placeSkillContent(extractedPkgDir, communityBaseDir, skillName, platfor
|
|
|
4070
4289
|
function removeSkillContent(communityBaseDir, skillName, platforms) {
|
|
4071
4290
|
for (const platform of platforms) {
|
|
4072
4291
|
const targetDir = path29.join(communityBaseDir, platform, skillName);
|
|
4073
|
-
if (
|
|
4074
|
-
|
|
4292
|
+
if (fs17.existsSync(targetDir)) {
|
|
4293
|
+
fs17.rmSync(targetDir, { recursive: true, force: true });
|
|
4075
4294
|
}
|
|
4076
4295
|
}
|
|
4077
4296
|
}
|
|
4078
4297
|
function cleanupTempDir(dirPath) {
|
|
4079
4298
|
try {
|
|
4080
|
-
|
|
4299
|
+
fs17.rmSync(dirPath, { recursive: true, force: true });
|
|
4081
4300
|
} catch {
|
|
4082
4301
|
}
|
|
4083
4302
|
}
|
|
@@ -4119,7 +4338,7 @@ function findDependentsOf(lockfile, targetPackageName) {
|
|
|
4119
4338
|
}
|
|
4120
4339
|
|
|
4121
4340
|
// src/registry/lockfile.ts
|
|
4122
|
-
import * as
|
|
4341
|
+
import * as fs18 from "fs";
|
|
4123
4342
|
import * as path30 from "path";
|
|
4124
4343
|
function createEmptyLockfile() {
|
|
4125
4344
|
return { version: 1, skills: {} };
|
|
@@ -4140,10 +4359,10 @@ function sortedStringify(obj) {
|
|
|
4140
4359
|
);
|
|
4141
4360
|
}
|
|
4142
4361
|
function readLockfile2(filePath) {
|
|
4143
|
-
if (!
|
|
4362
|
+
if (!fs18.existsSync(filePath)) {
|
|
4144
4363
|
return createEmptyLockfile();
|
|
4145
4364
|
}
|
|
4146
|
-
const raw =
|
|
4365
|
+
const raw = fs18.readFileSync(filePath, "utf-8");
|
|
4147
4366
|
let parsed;
|
|
4148
4367
|
try {
|
|
4149
4368
|
parsed = JSON.parse(raw);
|
|
@@ -4161,8 +4380,8 @@ function readLockfile2(filePath) {
|
|
|
4161
4380
|
}
|
|
4162
4381
|
function writeLockfile2(filePath, lockfile) {
|
|
4163
4382
|
const dir = path30.dirname(filePath);
|
|
4164
|
-
|
|
4165
|
-
|
|
4383
|
+
fs18.mkdirSync(dir, { recursive: true });
|
|
4384
|
+
fs18.writeFileSync(filePath, sortedStringify(lockfile) + "\n", "utf-8");
|
|
4166
4385
|
}
|
|
4167
4386
|
function updateLockfileEntry(lockfile, name, entry) {
|
|
4168
4387
|
return {
|
|
@@ -4185,17 +4404,17 @@ function removeLockfileEntry(lockfile, name) {
|
|
|
4185
4404
|
}
|
|
4186
4405
|
|
|
4187
4406
|
// src/registry/bundled-skills.ts
|
|
4188
|
-
import * as
|
|
4407
|
+
import * as fs19 from "fs";
|
|
4189
4408
|
import * as path31 from "path";
|
|
4190
4409
|
function getBundledSkillNames(bundledSkillsDir) {
|
|
4191
|
-
if (!
|
|
4410
|
+
if (!fs19.existsSync(bundledSkillsDir)) {
|
|
4192
4411
|
return /* @__PURE__ */ new Set();
|
|
4193
4412
|
}
|
|
4194
|
-
const entries =
|
|
4413
|
+
const entries = fs19.readdirSync(bundledSkillsDir);
|
|
4195
4414
|
const names = /* @__PURE__ */ new Set();
|
|
4196
4415
|
for (const entry of entries) {
|
|
4197
4416
|
try {
|
|
4198
|
-
const stat =
|
|
4417
|
+
const stat = fs19.statSync(path31.join(bundledSkillsDir, String(entry)));
|
|
4199
4418
|
if (stat.isDirectory()) {
|
|
4200
4419
|
names.add(String(entry));
|
|
4201
4420
|
}
|
|
@@ -4243,7 +4462,7 @@ function parseGitHubRef(from) {
|
|
|
4243
4462
|
return null;
|
|
4244
4463
|
}
|
|
4245
4464
|
function cloneGitHubRepo(owner, repo, ref) {
|
|
4246
|
-
const tmpDir =
|
|
4465
|
+
const tmpDir = fs20.mkdtempSync(path32.join(os5.tmpdir(), "harness-gh-install-"));
|
|
4247
4466
|
const url = `https://github.com/${owner}/${repo}.git`;
|
|
4248
4467
|
try {
|
|
4249
4468
|
const cloneArgs = ["clone", "--depth", "1"];
|
|
@@ -4264,12 +4483,12 @@ function discoverSkillDirs(rootDir) {
|
|
|
4264
4483
|
const skillDirs = [];
|
|
4265
4484
|
function scan(dir, depth) {
|
|
4266
4485
|
if (depth > 3) return;
|
|
4267
|
-
if (!
|
|
4268
|
-
if (
|
|
4486
|
+
if (!fs20.existsSync(dir)) return;
|
|
4487
|
+
if (fs20.existsSync(path32.join(dir, "skill.yaml"))) {
|
|
4269
4488
|
skillDirs.push(dir);
|
|
4270
4489
|
return;
|
|
4271
4490
|
}
|
|
4272
|
-
const entries =
|
|
4491
|
+
const entries = fs20.readdirSync(dir, { withFileTypes: true });
|
|
4273
4492
|
for (const entry of entries) {
|
|
4274
4493
|
if (!entry.isDirectory()) continue;
|
|
4275
4494
|
if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
|
|
@@ -4281,15 +4500,15 @@ function discoverSkillDirs(rootDir) {
|
|
|
4281
4500
|
}
|
|
4282
4501
|
function resolveLocalPkgDir(fromPath) {
|
|
4283
4502
|
const resolvedPath = path32.resolve(fromPath);
|
|
4284
|
-
if (!
|
|
4503
|
+
if (!fs20.existsSync(resolvedPath)) {
|
|
4285
4504
|
throw new Error(`--from path does not exist: ${resolvedPath}`);
|
|
4286
4505
|
}
|
|
4287
|
-
const stat =
|
|
4506
|
+
const stat = fs20.statSync(resolvedPath);
|
|
4288
4507
|
if (stat.isDirectory()) {
|
|
4289
4508
|
return { pkgDir: resolvedPath, extractDir: null };
|
|
4290
4509
|
}
|
|
4291
4510
|
if (resolvedPath.endsWith(".tgz") || resolvedPath.endsWith(".tar.gz")) {
|
|
4292
|
-
const tarballBuffer =
|
|
4511
|
+
const tarballBuffer = fs20.readFileSync(resolvedPath);
|
|
4293
4512
|
const extractDir = extractTarball(tarballBuffer);
|
|
4294
4513
|
return { pkgDir: path32.join(extractDir, "package"), extractDir };
|
|
4295
4514
|
}
|
|
@@ -4297,10 +4516,10 @@ function resolveLocalPkgDir(fromPath) {
|
|
|
4297
4516
|
}
|
|
4298
4517
|
function installSkillDir(pkgDir, resolvedPath, options) {
|
|
4299
4518
|
const skillYamlPath = path32.join(pkgDir, "skill.yaml");
|
|
4300
|
-
if (!
|
|
4519
|
+
if (!fs20.existsSync(skillYamlPath)) {
|
|
4301
4520
|
throw new Error(`No skill.yaml found at ${skillYamlPath}`);
|
|
4302
4521
|
}
|
|
4303
|
-
const skillYaml = validateSkillYaml(yamlParse(
|
|
4522
|
+
const skillYaml = validateSkillYaml(yamlParse(fs20.readFileSync(skillYamlPath, "utf-8")));
|
|
4304
4523
|
const shortName = skillYaml.name;
|
|
4305
4524
|
const { communityBase, lockfilePath } = resolveCommunityBase(options.global ?? false);
|
|
4306
4525
|
if (!options.global) {
|
|
@@ -4372,7 +4591,7 @@ async function runInstall(skillName, options) {
|
|
|
4372
4591
|
};
|
|
4373
4592
|
}
|
|
4374
4593
|
const resolvedFrom = path32.resolve(options.from);
|
|
4375
|
-
if (
|
|
4594
|
+
if (fs20.existsSync(resolvedFrom) && fs20.statSync(resolvedFrom).isDirectory() && !fs20.existsSync(path32.join(resolvedFrom, "skill.yaml"))) {
|
|
4376
4595
|
const results = await runBulkInstall(resolvedFrom, options);
|
|
4377
4596
|
const installed = results.filter((r) => r.installed);
|
|
4378
4597
|
return {
|
|
@@ -4415,10 +4634,10 @@ async function runInstall(skillName, options) {
|
|
|
4415
4634
|
try {
|
|
4416
4635
|
const extractedPkgDir = path32.join(extractDir, "package");
|
|
4417
4636
|
const skillYamlPath = path32.join(extractedPkgDir, "skill.yaml");
|
|
4418
|
-
if (!
|
|
4637
|
+
if (!fs20.existsSync(skillYamlPath)) {
|
|
4419
4638
|
throw new Error(`contains invalid skill.yaml: file not found in package`);
|
|
4420
4639
|
}
|
|
4421
|
-
const rawYaml =
|
|
4640
|
+
const rawYaml = fs20.readFileSync(skillYamlPath, "utf-8");
|
|
4422
4641
|
const parsed = yamlParse(rawYaml);
|
|
4423
4642
|
skillYaml = validateSkillYaml(parsed);
|
|
4424
4643
|
placeSkillContent(extractedPkgDir, communityBase, shortName, skillYaml.platforms);
|
|
@@ -4458,7 +4677,7 @@ async function runInstall(skillName, options) {
|
|
|
4458
4677
|
return result;
|
|
4459
4678
|
}
|
|
4460
4679
|
function createInstallCommand() {
|
|
4461
|
-
const cmd = new
|
|
4680
|
+
const cmd = new Command34("install");
|
|
4462
4681
|
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
4682
|
"--from <source>",
|
|
4464
4683
|
"Install from local path, directory, or GitHub (github:owner/repo, https://github.com/owner/repo)"
|
|
@@ -4491,15 +4710,16 @@ function createInstallCommand() {
|
|
|
4491
4710
|
}
|
|
4492
4711
|
|
|
4493
4712
|
// src/commands/install-constraints.ts
|
|
4494
|
-
|
|
4713
|
+
init_dist();
|
|
4714
|
+
import * as fs21 from "fs/promises";
|
|
4495
4715
|
import * as path33 from "path";
|
|
4496
|
-
import { Command as
|
|
4716
|
+
import { Command as Command35 } from "commander";
|
|
4497
4717
|
import semver3 from "semver";
|
|
4498
4718
|
async function runInstallConstraints(options) {
|
|
4499
4719
|
const { source, configPath, lockfilePath } = options;
|
|
4500
4720
|
let rawBundle;
|
|
4501
4721
|
try {
|
|
4502
|
-
rawBundle = await
|
|
4722
|
+
rawBundle = await fs21.readFile(source, "utf-8");
|
|
4503
4723
|
} catch (err) {
|
|
4504
4724
|
if (isNodeError(err) && err.code === "ENOENT") {
|
|
4505
4725
|
return { ok: false, error: `Bundle file not found: ${source}` };
|
|
@@ -4542,7 +4762,7 @@ async function runInstallConstraints(options) {
|
|
|
4542
4762
|
}
|
|
4543
4763
|
let localConfig;
|
|
4544
4764
|
try {
|
|
4545
|
-
const raw = await
|
|
4765
|
+
const raw = await fs21.readFile(configPath, "utf-8");
|
|
4546
4766
|
localConfig = JSON.parse(raw);
|
|
4547
4767
|
} catch (err) {
|
|
4548
4768
|
return {
|
|
@@ -4747,17 +4967,18 @@ async function handleInstallConstraints(source, opts) {
|
|
|
4747
4967
|
logInstallResult(result.value, opts);
|
|
4748
4968
|
}
|
|
4749
4969
|
function createInstallConstraintsCommand() {
|
|
4750
|
-
const cmd = new
|
|
4970
|
+
const cmd = new Command35("install-constraints");
|
|
4751
4971
|
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
4972
|
return cmd;
|
|
4753
4973
|
}
|
|
4754
4974
|
|
|
4755
4975
|
// src/commands/integrations/index.ts
|
|
4756
|
-
import { Command as
|
|
4976
|
+
import { Command as Command40 } from "commander";
|
|
4757
4977
|
|
|
4758
4978
|
// src/commands/integrations/add.ts
|
|
4759
|
-
|
|
4760
|
-
import
|
|
4979
|
+
init_dist();
|
|
4980
|
+
import { Command as Command36 } from "commander";
|
|
4981
|
+
import * as fs22 from "fs";
|
|
4761
4982
|
import * as path34 from "path";
|
|
4762
4983
|
import chalk4 from "chalk";
|
|
4763
4984
|
function buildMcpEntry(def) {
|
|
@@ -4769,7 +4990,7 @@ function buildMcpEntry(def) {
|
|
|
4769
4990
|
function writeMcpEntries(cwd, defName, mcpEntry) {
|
|
4770
4991
|
writeMcpEntry(path34.join(cwd, ".mcp.json"), defName, mcpEntry);
|
|
4771
4992
|
const geminiDir = path34.join(cwd, ".gemini");
|
|
4772
|
-
if (
|
|
4993
|
+
if (fs22.existsSync(geminiDir)) {
|
|
4773
4994
|
writeMcpEntry(path34.join(geminiDir, "settings.json"), defName, mcpEntry);
|
|
4774
4995
|
}
|
|
4775
4996
|
}
|
|
@@ -4820,7 +5041,7 @@ function printAddSuccess(value) {
|
|
|
4820
5041
|
}
|
|
4821
5042
|
}
|
|
4822
5043
|
function createAddIntegrationCommand() {
|
|
4823
|
-
return new
|
|
5044
|
+
return new Command36("add").description("Enable an MCP integration").argument("<name>", "Integration name (e.g. perplexity, augment-code)").action(async (name, _opts, cmd) => {
|
|
4824
5045
|
const globalOpts = cmd.optsWithGlobals();
|
|
4825
5046
|
const result = addIntegration(process.cwd(), name);
|
|
4826
5047
|
if (!result.ok) {
|
|
@@ -4834,7 +5055,7 @@ function createAddIntegrationCommand() {
|
|
|
4834
5055
|
}
|
|
4835
5056
|
|
|
4836
5057
|
// src/commands/integrations/list.ts
|
|
4837
|
-
import { Command as
|
|
5058
|
+
import { Command as Command37 } from "commander";
|
|
4838
5059
|
import * as path35 from "path";
|
|
4839
5060
|
import chalk5 from "chalk";
|
|
4840
5061
|
function printTier0Integrations(tier0, mcpServers) {
|
|
@@ -4891,12 +5112,13 @@ async function runListIntegrations(globalOpts) {
|
|
|
4891
5112
|
process.exit(ExitCode.SUCCESS);
|
|
4892
5113
|
}
|
|
4893
5114
|
function createListIntegrationsCommand() {
|
|
4894
|
-
return new
|
|
5115
|
+
return new Command37("list").description("Show all MCP integrations with status").action(async (_opts, cmd) => runListIntegrations(cmd.optsWithGlobals()));
|
|
4895
5116
|
}
|
|
4896
5117
|
|
|
4897
5118
|
// src/commands/integrations/remove.ts
|
|
4898
|
-
|
|
4899
|
-
import
|
|
5119
|
+
init_dist();
|
|
5120
|
+
import { Command as Command38 } from "commander";
|
|
5121
|
+
import * as fs23 from "fs";
|
|
4900
5122
|
import * as path36 from "path";
|
|
4901
5123
|
function removeIntegration(cwd, name) {
|
|
4902
5124
|
const def = INTEGRATION_REGISTRY.find((i) => i.name === name);
|
|
@@ -4911,7 +5133,7 @@ function removeIntegration(cwd, name) {
|
|
|
4911
5133
|
const mcpPath = path36.join(cwd, ".mcp.json");
|
|
4912
5134
|
removeMcpEntry(mcpPath, def.name);
|
|
4913
5135
|
const geminiDir = path36.join(cwd, ".gemini");
|
|
4914
|
-
if (
|
|
5136
|
+
if (fs23.existsSync(geminiDir)) {
|
|
4915
5137
|
const geminiPath = path36.join(geminiDir, "settings.json");
|
|
4916
5138
|
removeMcpEntry(geminiPath, def.name);
|
|
4917
5139
|
}
|
|
@@ -4922,7 +5144,7 @@ function removeIntegration(cwd, name) {
|
|
|
4922
5144
|
return Ok(def.displayName);
|
|
4923
5145
|
}
|
|
4924
5146
|
function createRemoveIntegrationCommand() {
|
|
4925
|
-
return new
|
|
5147
|
+
return new Command38("remove").description("Remove an MCP integration").argument("<name>", "Integration name (e.g. perplexity, augment-code)").action(async (name, _opts, cmd) => {
|
|
4926
5148
|
const globalOpts = cmd.optsWithGlobals();
|
|
4927
5149
|
const cwd = process.cwd();
|
|
4928
5150
|
const result = removeIntegration(cwd, name);
|
|
@@ -4943,7 +5165,8 @@ function createRemoveIntegrationCommand() {
|
|
|
4943
5165
|
}
|
|
4944
5166
|
|
|
4945
5167
|
// src/commands/integrations/dismiss.ts
|
|
4946
|
-
|
|
5168
|
+
init_dist();
|
|
5169
|
+
import { Command as Command39 } from "commander";
|
|
4947
5170
|
import * as path37 from "path";
|
|
4948
5171
|
function dismissIntegration(cwd, name) {
|
|
4949
5172
|
const def = INTEGRATION_REGISTRY.find((i) => i.name === name);
|
|
@@ -4965,7 +5188,7 @@ function dismissIntegration(cwd, name) {
|
|
|
4965
5188
|
return Ok(def.displayName);
|
|
4966
5189
|
}
|
|
4967
5190
|
function createDismissIntegrationCommand() {
|
|
4968
|
-
return new
|
|
5191
|
+
return new Command39("dismiss").description("Suppress doctor recommendations for an integration").argument("<name>", "Integration name (e.g. perplexity, augment-code)").action(async (name, _opts, cmd) => {
|
|
4969
5192
|
const globalOpts = cmd.optsWithGlobals();
|
|
4970
5193
|
const cwd = process.cwd();
|
|
4971
5194
|
const result = dismissIntegration(cwd, name);
|
|
@@ -4989,7 +5212,7 @@ function createDismissIntegrationCommand() {
|
|
|
4989
5212
|
|
|
4990
5213
|
// src/commands/integrations/index.ts
|
|
4991
5214
|
function createIntegrationsCommand() {
|
|
4992
|
-
const command = new
|
|
5215
|
+
const command = new Command40("integrations").description(
|
|
4993
5216
|
"Manage MCP peer integrations (add, list, remove, dismiss)"
|
|
4994
5217
|
);
|
|
4995
5218
|
command.addCommand(createListIntegrationsCommand());
|
|
@@ -5000,10 +5223,11 @@ function createIntegrationsCommand() {
|
|
|
5000
5223
|
}
|
|
5001
5224
|
|
|
5002
5225
|
// src/commands/learnings/index.ts
|
|
5003
|
-
import { Command as
|
|
5226
|
+
import { Command as Command42 } from "commander";
|
|
5004
5227
|
|
|
5005
5228
|
// src/commands/learnings/prune.ts
|
|
5006
|
-
|
|
5229
|
+
init_dist();
|
|
5230
|
+
import { Command as Command41 } from "commander";
|
|
5007
5231
|
import * as path38 from "path";
|
|
5008
5232
|
async function handlePrune(opts) {
|
|
5009
5233
|
const projectPath = path38.resolve(opts.path);
|
|
@@ -5045,23 +5269,23 @@ function printPatternProposals(patterns) {
|
|
|
5045
5269
|
);
|
|
5046
5270
|
}
|
|
5047
5271
|
function createPruneCommand() {
|
|
5048
|
-
return new
|
|
5272
|
+
return new Command41("prune").description(
|
|
5049
5273
|
"Analyze global learnings for patterns, present improvement proposals, and archive old entries"
|
|
5050
5274
|
).option("--path <path>", "Project root path", ".").option("--stream <name>", "Target a specific stream").action(handlePrune);
|
|
5051
5275
|
}
|
|
5052
5276
|
|
|
5053
5277
|
// src/commands/learnings/index.ts
|
|
5054
5278
|
function createLearningsCommand() {
|
|
5055
|
-
const command = new
|
|
5279
|
+
const command = new Command42("learnings").description("Learnings management commands");
|
|
5056
5280
|
command.addCommand(createPruneCommand());
|
|
5057
5281
|
return command;
|
|
5058
5282
|
}
|
|
5059
5283
|
|
|
5060
5284
|
// src/commands/linter/index.ts
|
|
5061
|
-
import { Command as
|
|
5285
|
+
import { Command as Command45 } from "commander";
|
|
5062
5286
|
|
|
5063
5287
|
// src/commands/linter/generate.ts
|
|
5064
|
-
import { Command as
|
|
5288
|
+
import { Command as Command43 } from "commander";
|
|
5065
5289
|
function formatGenerateError(e) {
|
|
5066
5290
|
switch (e.type) {
|
|
5067
5291
|
case "parse":
|
|
@@ -5105,7 +5329,7 @@ function handleSuccess(result, useJson) {
|
|
|
5105
5329
|
Generated ${result.rulesGenerated.length} rules to ${result.outputDir}`);
|
|
5106
5330
|
}
|
|
5107
5331
|
function createGenerateCommand2() {
|
|
5108
|
-
return new
|
|
5332
|
+
return new Command43("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
5333
|
try {
|
|
5110
5334
|
if (options.verbose) logger.info(`Parsing config: ${options.config}`);
|
|
5111
5335
|
const result = await generate({
|
|
@@ -5127,9 +5351,9 @@ function createGenerateCommand2() {
|
|
|
5127
5351
|
}
|
|
5128
5352
|
|
|
5129
5353
|
// src/commands/linter/validate.ts
|
|
5130
|
-
import { Command as
|
|
5354
|
+
import { Command as Command44 } from "commander";
|
|
5131
5355
|
function createValidateCommand() {
|
|
5132
|
-
return new
|
|
5356
|
+
return new Command44("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
5357
|
try {
|
|
5134
5358
|
const result = await validate({ configPath: options.config });
|
|
5135
5359
|
if (options.json) {
|
|
@@ -5148,7 +5372,7 @@ function createValidateCommand() {
|
|
|
5148
5372
|
|
|
5149
5373
|
// src/commands/linter/index.ts
|
|
5150
5374
|
function createLinterCommand() {
|
|
5151
|
-
const linter = new
|
|
5375
|
+
const linter = new Command45("linter").description(
|
|
5152
5376
|
"Generate and validate ESLint rules from YAML config"
|
|
5153
5377
|
);
|
|
5154
5378
|
linter.addCommand(createGenerateCommand2());
|
|
@@ -5157,21 +5381,60 @@ function createLinterCommand() {
|
|
|
5157
5381
|
}
|
|
5158
5382
|
|
|
5159
5383
|
// src/commands/mcp.ts
|
|
5160
|
-
import { Command as
|
|
5384
|
+
import { Command as Command46 } from "commander";
|
|
5385
|
+
var VALID_TIERS = /* @__PURE__ */ new Set(["core", "standard", "full"]);
|
|
5386
|
+
function parseTier(value) {
|
|
5387
|
+
const lower = value.toLowerCase();
|
|
5388
|
+
if (!VALID_TIERS.has(lower)) {
|
|
5389
|
+
throw new Error(`Invalid tier "${value}". Expected one of: core, standard, full.`);
|
|
5390
|
+
}
|
|
5391
|
+
return lower;
|
|
5392
|
+
}
|
|
5393
|
+
function parseBudget(value) {
|
|
5394
|
+
const n = Number.parseInt(value, 10);
|
|
5395
|
+
if (!Number.isFinite(n) || n < 0) {
|
|
5396
|
+
throw new Error(`Invalid --budget-tokens "${value}". Expected a non-negative integer.`);
|
|
5397
|
+
}
|
|
5398
|
+
return n;
|
|
5399
|
+
}
|
|
5161
5400
|
function createMcpCommand() {
|
|
5162
|
-
return new
|
|
5163
|
-
|
|
5164
|
-
|
|
5401
|
+
return new Command46("mcp").description("Start the MCP (Model Context Protocol) server on stdio").option("--tools <tools...>", "Only register the specified tools (used by Cursor integration)").option(
|
|
5402
|
+
"--tier <core|standard|full>",
|
|
5403
|
+
"Load a preset tool tier instead of all tools",
|
|
5404
|
+
parseTier
|
|
5405
|
+
).option(
|
|
5406
|
+
"--budget-tokens <n>",
|
|
5407
|
+
"Auto-select tier to fit this baseline token budget",
|
|
5408
|
+
parseBudget
|
|
5409
|
+
).action(async (opts) => {
|
|
5410
|
+
const [{ startServer: startServer2, getToolDefinitions: getToolDefinitions2 }, { selectTier }] = await Promise.all([
|
|
5411
|
+
import("./mcp-2553PNUC.js"),
|
|
5412
|
+
import("./tool-tiers-7QGZ3FKY.js")
|
|
5413
|
+
]);
|
|
5414
|
+
if (opts.tools && opts.tools.length > 0) {
|
|
5415
|
+
await startServer2(opts.tools);
|
|
5416
|
+
return;
|
|
5417
|
+
}
|
|
5418
|
+
if (opts.tier || opts.budgetTokens !== void 0) {
|
|
5419
|
+
const definitions = getToolDefinitions2();
|
|
5420
|
+
const selection = selectTier(definitions, {
|
|
5421
|
+
...opts.tier ? { overrideTier: opts.tier } : {},
|
|
5422
|
+
...opts.budgetTokens !== void 0 ? { tokenBudget: opts.budgetTokens } : {}
|
|
5423
|
+
});
|
|
5424
|
+
await startServer2(selection.filter);
|
|
5425
|
+
return;
|
|
5426
|
+
}
|
|
5427
|
+
await startServer2();
|
|
5165
5428
|
});
|
|
5166
5429
|
}
|
|
5167
5430
|
|
|
5168
5431
|
// src/commands/orchestrator.ts
|
|
5169
|
-
import { Command as
|
|
5432
|
+
import { Command as Command47 } from "commander";
|
|
5170
5433
|
import * as path39 from "path";
|
|
5171
5434
|
import { Orchestrator, WorkflowLoader, launchTUI } from "@harness-engineering/orchestrator";
|
|
5172
5435
|
function createOrchestratorCommand() {
|
|
5173
|
-
const orchestrator = new
|
|
5174
|
-
orchestrator.command("run").description("Run the orchestrator daemon").option("-w, --workflow <path>", "Path to WORKFLOW.md", "WORKFLOW.md").action(async (opts) => {
|
|
5436
|
+
const orchestrator = new Command47("orchestrator");
|
|
5437
|
+
orchestrator.command("run").description("Run the orchestrator daemon").option("-w, --workflow <path>", "Path to WORKFLOW.md", "WORKFLOW.md").option("--headless", "Run without TUI (server-only mode for use with web dashboard)").action(async (opts) => {
|
|
5175
5438
|
const workflowPath = path39.resolve(process.cwd(), opts.workflow);
|
|
5176
5439
|
const loader = new WorkflowLoader();
|
|
5177
5440
|
const result = await loader.loadWorkflow(workflowPath);
|
|
@@ -5188,23 +5451,32 @@ function createOrchestratorCommand() {
|
|
|
5188
5451
|
process.on("SIGINT", shutdown);
|
|
5189
5452
|
process.on("SIGTERM", shutdown);
|
|
5190
5453
|
daemon.start();
|
|
5191
|
-
|
|
5192
|
-
|
|
5454
|
+
if (opts.headless) {
|
|
5455
|
+
logger.info(
|
|
5456
|
+
"Orchestrator running in headless mode (no TUI). Use the web dashboard or Ctrl+C to stop."
|
|
5457
|
+
);
|
|
5458
|
+
await new Promise(() => {
|
|
5459
|
+
});
|
|
5460
|
+
} else {
|
|
5461
|
+
const { waitUntilExit } = launchTUI(daemon);
|
|
5462
|
+
await waitUntilExit();
|
|
5463
|
+
}
|
|
5193
5464
|
process.exit(ExitCode.SUCCESS);
|
|
5194
5465
|
});
|
|
5195
5466
|
return orchestrator;
|
|
5196
5467
|
}
|
|
5197
5468
|
|
|
5198
5469
|
// src/commands/perf.ts
|
|
5199
|
-
|
|
5470
|
+
init_dist();
|
|
5471
|
+
import { Command as Command48 } from "commander";
|
|
5200
5472
|
import * as path40 from "path";
|
|
5201
5473
|
function registerBenchCommand(perf) {
|
|
5202
|
-
perf.command("bench [glob]").description("Run benchmarks via vitest bench").action(async (
|
|
5474
|
+
perf.command("bench [glob]").description("Run benchmarks via vitest bench").action(async (glob2, _opts, cmd) => {
|
|
5203
5475
|
const globalOpts = cmd.optsWithGlobals();
|
|
5204
5476
|
const cwd = process.cwd();
|
|
5205
|
-
const { BenchmarkRunner } = await import("./dist-
|
|
5477
|
+
const { BenchmarkRunner } = await import("./dist-WCSJHQPK.js");
|
|
5206
5478
|
const runner = new BenchmarkRunner();
|
|
5207
|
-
const benchFiles = runner.discover(cwd,
|
|
5479
|
+
const benchFiles = runner.discover(cwd, glob2);
|
|
5208
5480
|
if (benchFiles.length === 0) {
|
|
5209
5481
|
if (globalOpts.json) {
|
|
5210
5482
|
console.log(JSON.stringify({ benchFiles: [], message: "No .bench.ts files found" }));
|
|
@@ -5214,7 +5486,7 @@ function registerBenchCommand(perf) {
|
|
|
5214
5486
|
return;
|
|
5215
5487
|
}
|
|
5216
5488
|
logBenchDiscovery(globalOpts.json, benchFiles);
|
|
5217
|
-
const result = await runner.run(
|
|
5489
|
+
const result = await runner.run(glob2 ? { cwd, glob: glob2 } : { cwd });
|
|
5218
5490
|
outputBenchResults(globalOpts.json, result);
|
|
5219
5491
|
});
|
|
5220
5492
|
}
|
|
@@ -5266,7 +5538,7 @@ async function runBaselinesShow(globalOpts) {
|
|
|
5266
5538
|
}
|
|
5267
5539
|
async function runBaselinesUpdate(globalOpts) {
|
|
5268
5540
|
const cwd = process.cwd();
|
|
5269
|
-
const { BenchmarkRunner } = await import("./dist-
|
|
5541
|
+
const { BenchmarkRunner } = await import("./dist-WCSJHQPK.js");
|
|
5270
5542
|
const runner = new BenchmarkRunner();
|
|
5271
5543
|
const manager = new BaselineManager(cwd);
|
|
5272
5544
|
logger.info("Running benchmarks to update baselines...");
|
|
@@ -5307,7 +5579,7 @@ function registerReportCommand(perf) {
|
|
|
5307
5579
|
perf.command("report").description("Full performance report with metrics, trends, and hotspots").action(async (_opts, cmd) => {
|
|
5308
5580
|
const globalOpts = cmd.optsWithGlobals();
|
|
5309
5581
|
const cwd = process.cwd();
|
|
5310
|
-
const { EntropyAnalyzer: EntropyAnalyzer2 } = await import("./dist-
|
|
5582
|
+
const { EntropyAnalyzer: EntropyAnalyzer2 } = await import("./dist-WCSJHQPK.js");
|
|
5311
5583
|
const analyzer = new EntropyAnalyzer2({
|
|
5312
5584
|
rootDir: path40.resolve(cwd),
|
|
5313
5585
|
analyze: { complexity: true, coupling: true }
|
|
@@ -5368,7 +5640,7 @@ function registerCriticalPathsCommand(perf) {
|
|
|
5368
5640
|
});
|
|
5369
5641
|
}
|
|
5370
5642
|
function createPerfCommand() {
|
|
5371
|
-
const perf = new
|
|
5643
|
+
const perf = new Command48("perf").description("Performance benchmark and baseline management");
|
|
5372
5644
|
registerBenchCommand(perf);
|
|
5373
5645
|
registerBaselinesCommands(perf);
|
|
5374
5646
|
registerReportCommand(perf);
|
|
@@ -5377,10 +5649,10 @@ function createPerfCommand() {
|
|
|
5377
5649
|
}
|
|
5378
5650
|
|
|
5379
5651
|
// src/commands/persona/index.ts
|
|
5380
|
-
import { Command as
|
|
5652
|
+
import { Command as Command51 } from "commander";
|
|
5381
5653
|
|
|
5382
5654
|
// src/commands/persona/list.ts
|
|
5383
|
-
import { Command as
|
|
5655
|
+
import { Command as Command49 } from "commander";
|
|
5384
5656
|
function printPersonaList(personas) {
|
|
5385
5657
|
if (personas.length === 0) {
|
|
5386
5658
|
logger.info("No personas found.");
|
|
@@ -5394,7 +5666,7 @@ function printPersonaList(personas) {
|
|
|
5394
5666
|
}
|
|
5395
5667
|
}
|
|
5396
5668
|
function createListCommand2() {
|
|
5397
|
-
return new
|
|
5669
|
+
return new Command49("list").description("List available agent personas").action(async (_opts, cmd) => {
|
|
5398
5670
|
const globalOpts = cmd.optsWithGlobals();
|
|
5399
5671
|
const personasDir = resolvePersonasDir();
|
|
5400
5672
|
const result = listPersonas(personasDir);
|
|
@@ -5414,8 +5686,8 @@ function createListCommand2() {
|
|
|
5414
5686
|
}
|
|
5415
5687
|
|
|
5416
5688
|
// src/commands/persona/generate.ts
|
|
5417
|
-
import { Command as
|
|
5418
|
-
import * as
|
|
5689
|
+
import { Command as Command50 } from "commander";
|
|
5690
|
+
import * as fs24 from "fs";
|
|
5419
5691
|
import * as path41 from "path";
|
|
5420
5692
|
function generatePersonaArtifacts(persona, outputDir, only) {
|
|
5421
5693
|
const slug = toKebabCase(persona.name);
|
|
@@ -5424,8 +5696,8 @@ function generatePersonaArtifacts(persona, outputDir, only) {
|
|
|
5424
5696
|
const result = generateRuntime(persona);
|
|
5425
5697
|
if (result.ok) {
|
|
5426
5698
|
const outPath = path41.join(outputDir, `${slug}.runtime.json`);
|
|
5427
|
-
|
|
5428
|
-
|
|
5699
|
+
fs24.mkdirSync(path41.dirname(outPath), { recursive: true });
|
|
5700
|
+
fs24.writeFileSync(outPath, result.value);
|
|
5429
5701
|
generated.push(outPath);
|
|
5430
5702
|
}
|
|
5431
5703
|
}
|
|
@@ -5433,7 +5705,7 @@ function generatePersonaArtifacts(persona, outputDir, only) {
|
|
|
5433
5705
|
const result = generateAgentsMd(persona);
|
|
5434
5706
|
if (result.ok) {
|
|
5435
5707
|
const outPath = path41.join(outputDir, `${slug}.agents.md`);
|
|
5436
|
-
|
|
5708
|
+
fs24.writeFileSync(outPath, result.value);
|
|
5437
5709
|
generated.push(outPath);
|
|
5438
5710
|
}
|
|
5439
5711
|
}
|
|
@@ -5441,15 +5713,15 @@ function generatePersonaArtifacts(persona, outputDir, only) {
|
|
|
5441
5713
|
const result = generateCIWorkflow(persona, "github");
|
|
5442
5714
|
if (result.ok) {
|
|
5443
5715
|
const outPath = path41.join(outputDir, ".github", "workflows", `${slug}.yml`);
|
|
5444
|
-
|
|
5445
|
-
|
|
5716
|
+
fs24.mkdirSync(path41.dirname(outPath), { recursive: true });
|
|
5717
|
+
fs24.writeFileSync(outPath, result.value);
|
|
5446
5718
|
generated.push(outPath);
|
|
5447
5719
|
}
|
|
5448
5720
|
}
|
|
5449
5721
|
return generated;
|
|
5450
5722
|
}
|
|
5451
5723
|
function createGenerateCommand3() {
|
|
5452
|
-
return new
|
|
5724
|
+
return new Command50("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
5725
|
const globalOpts = cmd.optsWithGlobals();
|
|
5454
5726
|
const personaResult = loadPersona(path41.join(resolvePersonasDir(), `${name}.yaml`));
|
|
5455
5727
|
if (!personaResult.ok) {
|
|
@@ -5471,14 +5743,15 @@ function createGenerateCommand3() {
|
|
|
5471
5743
|
|
|
5472
5744
|
// src/commands/persona/index.ts
|
|
5473
5745
|
function createPersonaCommand() {
|
|
5474
|
-
const command = new
|
|
5746
|
+
const command = new Command51("persona").description("Agent persona management commands");
|
|
5475
5747
|
command.addCommand(createListCommand2());
|
|
5476
5748
|
command.addCommand(createGenerateCommand3());
|
|
5477
5749
|
return command;
|
|
5478
5750
|
}
|
|
5479
5751
|
|
|
5480
5752
|
// src/commands/predict.ts
|
|
5481
|
-
|
|
5753
|
+
init_dist();
|
|
5754
|
+
import { Command as Command52 } from "commander";
|
|
5482
5755
|
import chalk6 from "chalk";
|
|
5483
5756
|
var CATEGORY_ORDER = [
|
|
5484
5757
|
"circular-deps",
|
|
@@ -5584,7 +5857,7 @@ function handlePredictError(err, mode) {
|
|
|
5584
5857
|
process.exit(ExitCode.ERROR);
|
|
5585
5858
|
}
|
|
5586
5859
|
function createPredictCommand() {
|
|
5587
|
-
const command = new
|
|
5860
|
+
const command = new Command52("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
5861
|
const globalOpts = cmd.optsWithGlobals();
|
|
5589
5862
|
const mode = globalOpts.json ? OutputMode.JSON : OutputMode.TEXT;
|
|
5590
5863
|
try {
|
|
@@ -5606,8 +5879,137 @@ function createPredictCommand() {
|
|
|
5606
5879
|
return command;
|
|
5607
5880
|
}
|
|
5608
5881
|
|
|
5882
|
+
// src/commands/publish-analyses.ts
|
|
5883
|
+
import * as fs25 from "fs";
|
|
5884
|
+
import * as path42 from "path";
|
|
5885
|
+
import { Command as Command53 } from "commander";
|
|
5886
|
+
init_dist();
|
|
5887
|
+
import {
|
|
5888
|
+
renderAnalysisComment,
|
|
5889
|
+
loadPublishedIndex,
|
|
5890
|
+
savePublishedIndex
|
|
5891
|
+
} from "@harness-engineering/orchestrator";
|
|
5892
|
+
function bootstrapTrackerCommand(opts, verb) {
|
|
5893
|
+
const projectPath = path42.resolve(opts.dir);
|
|
5894
|
+
const trackerConfig = loadTrackerSyncConfig(projectPath);
|
|
5895
|
+
if (!trackerConfig) {
|
|
5896
|
+
logger.error(`No tracker config found in harness.config.json. Cannot ${verb}.`);
|
|
5897
|
+
return null;
|
|
5898
|
+
}
|
|
5899
|
+
if (trackerConfig.kind !== "github") {
|
|
5900
|
+
logger.error(
|
|
5901
|
+
`${verb} currently only supports 'github' tracker kind. Found: ${String(trackerConfig.kind)}`
|
|
5902
|
+
);
|
|
5903
|
+
return null;
|
|
5904
|
+
}
|
|
5905
|
+
const projectEnvPath = path42.join(projectPath, ".env");
|
|
5906
|
+
if (fs25.existsSync(projectEnvPath) && !process.env.GITHUB_TOKEN) {
|
|
5907
|
+
__require("dotenv").config({ path: projectEnvPath });
|
|
5908
|
+
}
|
|
5909
|
+
const token = process.env.GITHUB_TOKEN;
|
|
5910
|
+
if (!token) {
|
|
5911
|
+
logger.error("No GITHUB_TOKEN environment variable found.");
|
|
5912
|
+
return null;
|
|
5913
|
+
}
|
|
5914
|
+
return { token, projectPath, trackerConfig };
|
|
5915
|
+
}
|
|
5916
|
+
function buildNameToExternalIdMap(projectPath) {
|
|
5917
|
+
const roadmapFile = path42.join(projectPath, "docs", "roadmap.md");
|
|
5918
|
+
if (!fs25.existsSync(roadmapFile)) {
|
|
5919
|
+
logger.error("No docs/roadmap.md found. Cannot map issue hashes to external IDs.");
|
|
5920
|
+
return null;
|
|
5921
|
+
}
|
|
5922
|
+
const { parseRoadmap } = (init_dist(), __toCommonJS(dist_exports));
|
|
5923
|
+
const roadmapParsed = parseRoadmap(fs25.readFileSync(roadmapFile, "utf-8"));
|
|
5924
|
+
if (!roadmapParsed.ok) {
|
|
5925
|
+
logger.error("Failed to parse docs/roadmap.md");
|
|
5926
|
+
return null;
|
|
5927
|
+
}
|
|
5928
|
+
const map = /* @__PURE__ */ new Map();
|
|
5929
|
+
for (const milestone of roadmapParsed.value.milestones) {
|
|
5930
|
+
for (const feature of milestone.features) {
|
|
5931
|
+
if (feature.externalId) {
|
|
5932
|
+
map.set(feature.name.toLowerCase(), feature.externalId);
|
|
5933
|
+
}
|
|
5934
|
+
}
|
|
5935
|
+
}
|
|
5936
|
+
return map;
|
|
5937
|
+
}
|
|
5938
|
+
function resolveExternalId(record, nameToExternalId) {
|
|
5939
|
+
if (record.externalId) return record.externalId;
|
|
5940
|
+
for (const [featName, extId] of nameToExternalId) {
|
|
5941
|
+
const prefix = featName.toLowerCase().replace(/[^a-z0-9]+/g, "-").slice(0, 20);
|
|
5942
|
+
if (record.identifier.startsWith(prefix)) return extId;
|
|
5943
|
+
}
|
|
5944
|
+
return null;
|
|
5945
|
+
}
|
|
5946
|
+
async function publishUnpublishedAnalyses(analyses, adapter, nameToExternalId, publishedIndex) {
|
|
5947
|
+
let publishedCount = 0;
|
|
5948
|
+
for (const record of analyses) {
|
|
5949
|
+
if (publishedIndex[record.issueId]) continue;
|
|
5950
|
+
const externalId = resolveExternalId(record, nameToExternalId);
|
|
5951
|
+
if (!externalId) {
|
|
5952
|
+
logger.dim(`Skipping ${record.identifier}: could not resolve externalId from roadmap`);
|
|
5953
|
+
continue;
|
|
5954
|
+
}
|
|
5955
|
+
logger.info(`Publishing analysis for ${record.identifier} to ${externalId}...`);
|
|
5956
|
+
const result = await adapter.addComment(externalId, renderAnalysisComment(record));
|
|
5957
|
+
if (result.ok) {
|
|
5958
|
+
publishedIndex[record.issueId] = (/* @__PURE__ */ new Date()).toISOString();
|
|
5959
|
+
publishedCount++;
|
|
5960
|
+
} else {
|
|
5961
|
+
logger.error(`Failed to publish ${record.identifier}: ${result.error.message}`);
|
|
5962
|
+
}
|
|
5963
|
+
}
|
|
5964
|
+
return publishedCount;
|
|
5965
|
+
}
|
|
5966
|
+
async function runPublishAnalyses(opts) {
|
|
5967
|
+
const bootstrap = bootstrapTrackerCommand(opts, "publish");
|
|
5968
|
+
if (!bootstrap) return process.exit(1);
|
|
5969
|
+
const { token, projectPath, trackerConfig } = bootstrap;
|
|
5970
|
+
const nameToExternalId = buildNameToExternalIdMap(projectPath);
|
|
5971
|
+
if (!nameToExternalId) return process.exit(1);
|
|
5972
|
+
const { AnalysisArchive } = await import("@harness-engineering/orchestrator");
|
|
5973
|
+
const { GitHubIssuesSyncAdapter: GitHubIssuesSyncAdapter2 } = await import("./dist-WCSJHQPK.js");
|
|
5974
|
+
const archive = new AnalysisArchive(path42.join(projectPath, ".harness", "analyses"));
|
|
5975
|
+
const analyses = await archive.list();
|
|
5976
|
+
if (analyses.length === 0) {
|
|
5977
|
+
logger.info("No analyses found to publish.");
|
|
5978
|
+
return;
|
|
5979
|
+
}
|
|
5980
|
+
const publishedIndex = loadPublishedIndex(projectPath);
|
|
5981
|
+
const adapter = new GitHubIssuesSyncAdapter2({ token, config: trackerConfig });
|
|
5982
|
+
const publishedCount = await publishUnpublishedAnalyses(
|
|
5983
|
+
analyses,
|
|
5984
|
+
adapter,
|
|
5985
|
+
nameToExternalId,
|
|
5986
|
+
publishedIndex
|
|
5987
|
+
);
|
|
5988
|
+
if (publishedCount > 0) {
|
|
5989
|
+
savePublishedIndex(projectPath, publishedIndex);
|
|
5990
|
+
logger.success(`Successfully published ${publishedCount} analyses.`);
|
|
5991
|
+
} else {
|
|
5992
|
+
logger.info("All analyses are already up to date on external tracker.");
|
|
5993
|
+
}
|
|
5994
|
+
}
|
|
5995
|
+
function createPublishAnalysesCommand() {
|
|
5996
|
+
const command = new Command53("publish-analyses").description(
|
|
5997
|
+
"Publishes locally generated intelligence analyses to the external issue tracker (e.g., GitHub)"
|
|
5998
|
+
).option("-d, --dir <path>", "Workspace directory", process.cwd()).action(async (opts) => {
|
|
5999
|
+
try {
|
|
6000
|
+
await runPublishAnalyses(opts);
|
|
6001
|
+
} catch (err) {
|
|
6002
|
+
logger.error(
|
|
6003
|
+
`Error publishing analyses: ${err instanceof Error ? err.message : String(err)}`
|
|
6004
|
+
);
|
|
6005
|
+
process.exit(1);
|
|
6006
|
+
}
|
|
6007
|
+
});
|
|
6008
|
+
return command;
|
|
6009
|
+
}
|
|
6010
|
+
|
|
5609
6011
|
// src/commands/recommend.ts
|
|
5610
|
-
import { Command as
|
|
6012
|
+
import { Command as Command54 } from "commander";
|
|
5611
6013
|
import chalk7 from "chalk";
|
|
5612
6014
|
async function resolveSnapshot(cwd, noCache) {
|
|
5613
6015
|
if (!noCache) {
|
|
@@ -5671,7 +6073,7 @@ function printRecommendations(result) {
|
|
|
5671
6073
|
console.log("");
|
|
5672
6074
|
}
|
|
5673
6075
|
function createRecommendCommand() {
|
|
5674
|
-
const command = new
|
|
6076
|
+
const command = new Command54("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
6077
|
const globalOpts = cmd.optsWithGlobals();
|
|
5676
6078
|
const mode = globalOpts.json ? OutputMode.JSON : OutputMode.TEXT;
|
|
5677
6079
|
try {
|
|
@@ -5707,9 +6109,10 @@ function createRecommendCommand() {
|
|
|
5707
6109
|
}
|
|
5708
6110
|
|
|
5709
6111
|
// src/commands/scan-config.ts
|
|
5710
|
-
|
|
5711
|
-
import {
|
|
5712
|
-
import {
|
|
6112
|
+
init_dist();
|
|
6113
|
+
import { Command as Command55 } from "commander";
|
|
6114
|
+
import { existsSync as existsSync22, readFileSync as readFileSync14, writeFileSync as writeFileSync12 } from "fs";
|
|
6115
|
+
import { join as join30, relative as relative2 } from "path";
|
|
5713
6116
|
var CONFIG_FILES = ["CLAUDE.md", "AGENTS.md", ".gemini/settings.json", "skill.yaml"];
|
|
5714
6117
|
function stripHighSeverityPatterns(content, injectionFindings) {
|
|
5715
6118
|
const highLines = /* @__PURE__ */ new Set();
|
|
@@ -5742,10 +6145,10 @@ function applyFix(filePath, targetDir, content, injectionFindings) {
|
|
|
5742
6145
|
}
|
|
5743
6146
|
}
|
|
5744
6147
|
function scanSingleFile(filePath, targetDir, scanner, options) {
|
|
5745
|
-
if (!
|
|
6148
|
+
if (!existsSync22(filePath)) return null;
|
|
5746
6149
|
let content;
|
|
5747
6150
|
try {
|
|
5748
|
-
content =
|
|
6151
|
+
content = readFileSync14(filePath, "utf8");
|
|
5749
6152
|
} catch {
|
|
5750
6153
|
return null;
|
|
5751
6154
|
}
|
|
@@ -5766,7 +6169,7 @@ async function runScanConfig(targetDir, options) {
|
|
|
5766
6169
|
const scanner = new SecurityScanner(parseSecurityConfig({}));
|
|
5767
6170
|
const results = [];
|
|
5768
6171
|
for (const configFile of CONFIG_FILES) {
|
|
5769
|
-
const result = scanSingleFile(
|
|
6172
|
+
const result = scanSingleFile(join30(targetDir, configFile), targetDir, scanner, options);
|
|
5770
6173
|
if (result) results.push(result);
|
|
5771
6174
|
}
|
|
5772
6175
|
return { exitCode: computeScanExitCode(results), results };
|
|
@@ -5796,7 +6199,7 @@ function formatTextOutput(result) {
|
|
|
5796
6199
|
}
|
|
5797
6200
|
}
|
|
5798
6201
|
function createScanConfigCommand() {
|
|
5799
|
-
const command = new
|
|
6202
|
+
const command = new Command55("scan-config").description(
|
|
5800
6203
|
"Scan CLAUDE.md, AGENTS.md, .gemini/settings.json, and skill.yaml for prompt injection patterns"
|
|
5801
6204
|
).option("--path <dir>", "Target directory to scan (default: cwd)").option("--fix", "Strip high-severity patterns from files in-place").action(async (opts, cmd) => {
|
|
5802
6205
|
const globalOpts = cmd.optsWithGlobals();
|
|
@@ -5813,24 +6216,24 @@ function createScanConfigCommand() {
|
|
|
5813
6216
|
}
|
|
5814
6217
|
|
|
5815
6218
|
// src/commands/setup.ts
|
|
5816
|
-
import { Command as
|
|
5817
|
-
import * as
|
|
6219
|
+
import { Command as Command56 } from "commander";
|
|
6220
|
+
import * as fs28 from "fs";
|
|
5818
6221
|
import * as os7 from "os";
|
|
5819
|
-
import * as
|
|
5820
|
-
import
|
|
6222
|
+
import * as path45 from "path";
|
|
6223
|
+
import chalk9 from "chalk";
|
|
5821
6224
|
|
|
5822
6225
|
// src/utils/first-run.ts
|
|
5823
|
-
import * as
|
|
6226
|
+
import * as fs26 from "fs";
|
|
5824
6227
|
import * as os6 from "os";
|
|
5825
|
-
import * as
|
|
5826
|
-
var HARNESS_DIR =
|
|
5827
|
-
var MARKER_FILE =
|
|
6228
|
+
import * as path43 from "path";
|
|
6229
|
+
var HARNESS_DIR = path43.join(os6.homedir(), ".harness");
|
|
6230
|
+
var MARKER_FILE = path43.join(HARNESS_DIR, ".setup-complete");
|
|
5828
6231
|
function isFirstRun() {
|
|
5829
|
-
return !
|
|
6232
|
+
return !fs26.existsSync(MARKER_FILE);
|
|
5830
6233
|
}
|
|
5831
6234
|
function markSetupComplete() {
|
|
5832
|
-
|
|
5833
|
-
|
|
6235
|
+
fs26.mkdirSync(HARNESS_DIR, { recursive: true });
|
|
6236
|
+
fs26.writeFileSync(MARKER_FILE, "", "utf-8");
|
|
5834
6237
|
}
|
|
5835
6238
|
function printFirstRunWelcome() {
|
|
5836
6239
|
try {
|
|
@@ -5842,6 +6245,119 @@ function printFirstRunWelcome() {
|
|
|
5842
6245
|
}
|
|
5843
6246
|
}
|
|
5844
6247
|
|
|
6248
|
+
// src/commands/telemetry-wizard.ts
|
|
6249
|
+
import * as fs27 from "fs";
|
|
6250
|
+
import * as path44 from "path";
|
|
6251
|
+
import readline from "readline";
|
|
6252
|
+
import chalk8 from "chalk";
|
|
6253
|
+
function prompt(question) {
|
|
6254
|
+
const rl = readline.createInterface({
|
|
6255
|
+
input: process.stdin,
|
|
6256
|
+
output: process.stdout
|
|
6257
|
+
});
|
|
6258
|
+
return new Promise((resolve35) => {
|
|
6259
|
+
rl.question(question, (answer) => {
|
|
6260
|
+
rl.close();
|
|
6261
|
+
resolve35(answer.trim().toLowerCase());
|
|
6262
|
+
});
|
|
6263
|
+
});
|
|
6264
|
+
}
|
|
6265
|
+
function isNo(answer) {
|
|
6266
|
+
return answer === "n" || answer === "no";
|
|
6267
|
+
}
|
|
6268
|
+
function isTelemetryConfigured(cwd) {
|
|
6269
|
+
const configPath = path44.join(cwd, "harness.config.json");
|
|
6270
|
+
try {
|
|
6271
|
+
const raw = JSON.parse(fs27.readFileSync(configPath, "utf-8"));
|
|
6272
|
+
return raw.telemetry !== void 0 && raw.adoption !== void 0;
|
|
6273
|
+
} catch {
|
|
6274
|
+
return false;
|
|
6275
|
+
}
|
|
6276
|
+
}
|
|
6277
|
+
function writeTelemetryConfig(cwd, result) {
|
|
6278
|
+
const configPath = path44.join(cwd, "harness.config.json");
|
|
6279
|
+
try {
|
|
6280
|
+
const raw = JSON.parse(fs27.readFileSync(configPath, "utf-8"));
|
|
6281
|
+
raw.telemetry = { enabled: result.telemetryEnabled };
|
|
6282
|
+
raw.adoption = { enabled: result.adoptionEnabled };
|
|
6283
|
+
fs27.writeFileSync(configPath, JSON.stringify(raw, null, 2) + "\n");
|
|
6284
|
+
} catch (err) {
|
|
6285
|
+
console.warn(
|
|
6286
|
+
`\u26A0 Could not update ${configPath}: ${err instanceof Error ? err.message : String(err)}`
|
|
6287
|
+
);
|
|
6288
|
+
}
|
|
6289
|
+
const hasIdentity = result.identity.project || result.identity.team || result.identity.alias;
|
|
6290
|
+
if (hasIdentity) {
|
|
6291
|
+
const harnessDir = path44.join(cwd, ".harness");
|
|
6292
|
+
fs27.mkdirSync(harnessDir, { recursive: true });
|
|
6293
|
+
const telemetryFile = path44.join(harnessDir, "telemetry.json");
|
|
6294
|
+
fs27.writeFileSync(telemetryFile, JSON.stringify({ identity: result.identity }, null, 2) + "\n");
|
|
6295
|
+
}
|
|
6296
|
+
}
|
|
6297
|
+
async function promptIdentity() {
|
|
6298
|
+
console.log(
|
|
6299
|
+
` ${chalk8.cyan("Identity")} (optional) \u2014 tag telemetry with project/team/alias for filtering.`
|
|
6300
|
+
);
|
|
6301
|
+
console.log(" Leave blank to keep anonymous.");
|
|
6302
|
+
const answer = await prompt(" Set identity fields? (y/N) ");
|
|
6303
|
+
const identity = {};
|
|
6304
|
+
if (answer === "y" || answer === "yes") {
|
|
6305
|
+
const projectName = await prompt(" Project name: ");
|
|
6306
|
+
if (projectName) identity.project = projectName;
|
|
6307
|
+
const teamName = await prompt(" Team name: ");
|
|
6308
|
+
if (teamName) identity.team = teamName;
|
|
6309
|
+
const alias = await prompt(" Alias: ");
|
|
6310
|
+
if (alias) identity.alias = alias;
|
|
6311
|
+
}
|
|
6312
|
+
return identity;
|
|
6313
|
+
}
|
|
6314
|
+
async function runTelemetryWizard() {
|
|
6315
|
+
if (!process.stdin.isTTY) return null;
|
|
6316
|
+
console.log("");
|
|
6317
|
+
console.log(` ${chalk8.bold("Telemetry Configuration")}`);
|
|
6318
|
+
console.log("");
|
|
6319
|
+
console.log(" Harness can collect anonymous usage data to help improve the tool.");
|
|
6320
|
+
console.log(" You can change these settings anytime in harness.config.json.");
|
|
6321
|
+
console.log("");
|
|
6322
|
+
console.log(
|
|
6323
|
+
` ${chalk8.cyan("Anonymous telemetry")} \u2014 error rates, command usage, performance metrics.`
|
|
6324
|
+
);
|
|
6325
|
+
console.log(" No code, file contents, or personally identifiable info is sent.");
|
|
6326
|
+
const telemetryAnswer = await prompt(" Enable anonymous telemetry? (Y/n) ");
|
|
6327
|
+
const telemetryEnabled = !isNo(telemetryAnswer);
|
|
6328
|
+
console.log("");
|
|
6329
|
+
console.log(
|
|
6330
|
+
` ${chalk8.cyan("Adoption tracking")} \u2014 which skills are used, how often, and success rates.`
|
|
6331
|
+
);
|
|
6332
|
+
console.log(" Stored locally in .harness/adoption/. Never sent externally.");
|
|
6333
|
+
const adoptionAnswer = await prompt(" Enable local adoption tracking? (Y/n) ");
|
|
6334
|
+
const adoptionEnabled = !isNo(adoptionAnswer);
|
|
6335
|
+
console.log("");
|
|
6336
|
+
const identity = await promptIdentity();
|
|
6337
|
+
return { telemetryEnabled, adoptionEnabled, identity };
|
|
6338
|
+
}
|
|
6339
|
+
async function ensureTelemetryConfigured(cwd) {
|
|
6340
|
+
const configPath = path44.join(cwd, "harness.config.json");
|
|
6341
|
+
if (!fs27.existsSync(configPath)) {
|
|
6342
|
+
return { status: "warn", message: "Not a harness project \u2014 skipped telemetry configuration" };
|
|
6343
|
+
}
|
|
6344
|
+
if (isTelemetryConfigured(cwd)) {
|
|
6345
|
+
return { status: "pass", message: "Telemetry already configured" };
|
|
6346
|
+
}
|
|
6347
|
+
const result = await runTelemetryWizard();
|
|
6348
|
+
if (!result) {
|
|
6349
|
+
return { status: "warn", message: "Non-interactive mode \u2014 skipped telemetry configuration" };
|
|
6350
|
+
}
|
|
6351
|
+
writeTelemetryConfig(cwd, result);
|
|
6352
|
+
const parts = [];
|
|
6353
|
+
parts.push(`telemetry ${result.telemetryEnabled ? "enabled" : "disabled"}`);
|
|
6354
|
+
parts.push(`adoption ${result.adoptionEnabled ? "enabled" : "disabled"}`);
|
|
6355
|
+
if (result.identity.project || result.identity.team || result.identity.alias) {
|
|
6356
|
+
parts.push("identity set");
|
|
6357
|
+
}
|
|
6358
|
+
return { status: "pass", message: `Telemetry configured: ${parts.join(", ")}` };
|
|
6359
|
+
}
|
|
6360
|
+
|
|
5845
6361
|
// src/commands/setup.ts
|
|
5846
6362
|
function checkNodeVersion3() {
|
|
5847
6363
|
const result = checkNodeVersion();
|
|
@@ -5868,7 +6384,7 @@ function runSlashCommandGeneration() {
|
|
|
5868
6384
|
}
|
|
5869
6385
|
}
|
|
5870
6386
|
function detectClient(dirName) {
|
|
5871
|
-
return
|
|
6387
|
+
return fs28.existsSync(path45.join(os7.homedir(), dirName));
|
|
5872
6388
|
}
|
|
5873
6389
|
async function runMcpSetup(cwd) {
|
|
5874
6390
|
const results = [];
|
|
@@ -5905,12 +6421,12 @@ async function runMcpSetup(cwd) {
|
|
|
5905
6421
|
return results;
|
|
5906
6422
|
}
|
|
5907
6423
|
function formatStep(result) {
|
|
5908
|
-
const icon = result.status === "pass" ?
|
|
6424
|
+
const icon = result.status === "pass" ? chalk9.green("\u2713") : result.status === "warn" ? chalk9.yellow("\u26A0") : chalk9.red("\u2717");
|
|
5909
6425
|
return ` ${icon} ${result.message}`;
|
|
5910
6426
|
}
|
|
5911
6427
|
function configureTier0Integrations(cwd) {
|
|
5912
6428
|
try {
|
|
5913
|
-
const mcpPath =
|
|
6429
|
+
const mcpPath = path45.join(cwd, ".mcp.json");
|
|
5914
6430
|
const config = readMcpConfig(mcpPath);
|
|
5915
6431
|
const tier0 = INTEGRATION_REGISTRY.filter((i) => i.tier === 0);
|
|
5916
6432
|
const added = [];
|
|
@@ -5919,9 +6435,9 @@ function configureTier0Integrations(cwd) {
|
|
|
5919
6435
|
writeMcpEntry(mcpPath, integration.name, integration.mcpConfig);
|
|
5920
6436
|
added.push(integration.displayName);
|
|
5921
6437
|
}
|
|
5922
|
-
const geminiDir =
|
|
5923
|
-
if (
|
|
5924
|
-
const geminiPath =
|
|
6438
|
+
const geminiDir = path45.join(cwd, ".gemini");
|
|
6439
|
+
if (fs28.existsSync(geminiDir)) {
|
|
6440
|
+
const geminiPath = path45.join(geminiDir, "settings.json");
|
|
5925
6441
|
const geminiConfig = readMcpConfig(geminiPath);
|
|
5926
6442
|
for (const integration of tier0) {
|
|
5927
6443
|
if (!geminiConfig.mcpServers[integration.name]) {
|
|
@@ -5941,6 +6457,44 @@ function configureTier0Integrations(cwd) {
|
|
|
5941
6457
|
return { status: "fail", message: `Tier 0 integration configuration failed \u2014 ${msg}` };
|
|
5942
6458
|
}
|
|
5943
6459
|
}
|
|
6460
|
+
function ensureHooks(cwd) {
|
|
6461
|
+
const configPath = path45.join(cwd, "harness.config.json");
|
|
6462
|
+
if (!fs28.existsSync(configPath)) {
|
|
6463
|
+
return { status: "warn", message: "Not a harness project \u2014 skipped hook installation" };
|
|
6464
|
+
}
|
|
6465
|
+
let profile = "standard";
|
|
6466
|
+
const profilePath = path45.join(cwd, ".harness", "hooks", "profile.json");
|
|
6467
|
+
try {
|
|
6468
|
+
const data = JSON.parse(fs28.readFileSync(profilePath, "utf-8"));
|
|
6469
|
+
if (data.profile && ["minimal", "standard", "strict"].includes(data.profile)) {
|
|
6470
|
+
profile = data.profile;
|
|
6471
|
+
}
|
|
6472
|
+
} catch {
|
|
6473
|
+
}
|
|
6474
|
+
try {
|
|
6475
|
+
const result = initHooks({ profile, projectDir: cwd });
|
|
6476
|
+
return {
|
|
6477
|
+
status: "pass",
|
|
6478
|
+
message: `Installed ${result.copiedScripts.length} hooks (${profile} profile)`
|
|
6479
|
+
};
|
|
6480
|
+
} catch (error) {
|
|
6481
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
6482
|
+
return { status: "warn", message: `Hook installation skipped \u2014 ${msg}` };
|
|
6483
|
+
}
|
|
6484
|
+
}
|
|
6485
|
+
async function runInitialGraphScan(cwd) {
|
|
6486
|
+
try {
|
|
6487
|
+
const { runScan: runScan2 } = await import("./scan-MPJ6JHUY.js");
|
|
6488
|
+
const result = await runScan2(cwd);
|
|
6489
|
+
return {
|
|
6490
|
+
status: "pass",
|
|
6491
|
+
message: `Built knowledge graph: ${result.nodeCount} nodes, ${result.edgeCount} edges`
|
|
6492
|
+
};
|
|
6493
|
+
} catch (error) {
|
|
6494
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
6495
|
+
return { status: "warn", message: `Knowledge graph creation skipped \u2014 ${msg}` };
|
|
6496
|
+
}
|
|
6497
|
+
}
|
|
5944
6498
|
async function runSetup(cwd) {
|
|
5945
6499
|
const steps = [];
|
|
5946
6500
|
const nodeResult = checkNodeVersion3();
|
|
@@ -5954,6 +6508,12 @@ async function runSetup(cwd) {
|
|
|
5954
6508
|
steps.push(...mcpResults);
|
|
5955
6509
|
const tier0Result = configureTier0Integrations(cwd);
|
|
5956
6510
|
steps.push(tier0Result);
|
|
6511
|
+
const hooksResult = ensureHooks(cwd);
|
|
6512
|
+
steps.push(hooksResult);
|
|
6513
|
+
const telemetryResult = await ensureTelemetryConfigured(cwd);
|
|
6514
|
+
steps.push(telemetryResult);
|
|
6515
|
+
const graphResult = await runInitialGraphScan(cwd);
|
|
6516
|
+
steps.push(graphResult);
|
|
5957
6517
|
const success = steps.every((s) => s.status !== "fail");
|
|
5958
6518
|
if (success) {
|
|
5959
6519
|
markSetupComplete();
|
|
@@ -5961,10 +6521,10 @@ async function runSetup(cwd) {
|
|
|
5961
6521
|
return { steps, success };
|
|
5962
6522
|
}
|
|
5963
6523
|
function createSetupCommand() {
|
|
5964
|
-
return new
|
|
6524
|
+
return new Command56("setup").description("Configure harness environment: slash commands, MCP, and more").action(async () => {
|
|
5965
6525
|
const cwd = process.cwd();
|
|
5966
6526
|
console.log("");
|
|
5967
|
-
console.log(` ${
|
|
6527
|
+
console.log(` ${chalk9.bold("harness setup")}`);
|
|
5968
6528
|
console.log("");
|
|
5969
6529
|
const { steps, success } = await runSetup(cwd);
|
|
5970
6530
|
for (const step of steps) {
|
|
@@ -5983,15 +6543,16 @@ function createSetupCommand() {
|
|
|
5983
6543
|
}
|
|
5984
6544
|
|
|
5985
6545
|
// src/commands/share.ts
|
|
5986
|
-
|
|
5987
|
-
import
|
|
5988
|
-
import * as
|
|
6546
|
+
init_dist();
|
|
6547
|
+
import { Command as Command57 } from "commander";
|
|
6548
|
+
import * as fs29 from "fs";
|
|
6549
|
+
import * as path46 from "path";
|
|
5989
6550
|
import { parse as parseYaml } from "yaml";
|
|
5990
6551
|
var MANIFEST_FILENAME = "constraints.yaml";
|
|
5991
6552
|
async function runShareAction(projectPath, options) {
|
|
5992
|
-
const rootDir =
|
|
5993
|
-
const manifestPath =
|
|
5994
|
-
if (!
|
|
6553
|
+
const rootDir = path46.resolve(projectPath);
|
|
6554
|
+
const manifestPath = path46.join(rootDir, MANIFEST_FILENAME);
|
|
6555
|
+
if (!fs29.existsSync(manifestPath)) {
|
|
5995
6556
|
logger.error(
|
|
5996
6557
|
`No ${MANIFEST_FILENAME} found at ${manifestPath}.
|
|
5997
6558
|
Create a constraints.yaml in your project root to define what to share.`
|
|
@@ -6000,7 +6561,7 @@ Create a constraints.yaml in your project root to define what to share.`
|
|
|
6000
6561
|
}
|
|
6001
6562
|
let parsed;
|
|
6002
6563
|
try {
|
|
6003
|
-
const raw =
|
|
6564
|
+
const raw = fs29.readFileSync(manifestPath, "utf-8");
|
|
6004
6565
|
parsed = parseYaml(raw);
|
|
6005
6566
|
} catch (err) {
|
|
6006
6567
|
logger.error(
|
|
@@ -6014,7 +6575,7 @@ Create a constraints.yaml in your project root to define what to share.`
|
|
|
6014
6575
|
process.exit(1);
|
|
6015
6576
|
}
|
|
6016
6577
|
const manifest = manifestResult.value;
|
|
6017
|
-
const configResult = resolveConfig(
|
|
6578
|
+
const configResult = resolveConfig(path46.join(rootDir, "harness.config.json"));
|
|
6018
6579
|
if (!configResult.ok) {
|
|
6019
6580
|
logger.error(configResult.error.message);
|
|
6020
6581
|
process.exit(1);
|
|
@@ -6032,8 +6593,8 @@ Create a constraints.yaml in your project root to define what to share.`
|
|
|
6032
6593
|
);
|
|
6033
6594
|
process.exit(1);
|
|
6034
6595
|
}
|
|
6035
|
-
const outputDir =
|
|
6036
|
-
const outputPath =
|
|
6596
|
+
const outputDir = path46.resolve(options.output);
|
|
6597
|
+
const outputPath = path46.join(outputDir, `${manifest.name}.harness-constraints.json`);
|
|
6037
6598
|
const writeResult = await writeConfig(outputPath, bundle);
|
|
6038
6599
|
if (!writeResult.ok) {
|
|
6039
6600
|
logger.error(`Failed to write bundle: ${writeResult.error.message}`);
|
|
@@ -6042,28 +6603,28 @@ Create a constraints.yaml in your project root to define what to share.`
|
|
|
6042
6603
|
logger.success(`Bundle written to ${outputPath}`);
|
|
6043
6604
|
}
|
|
6044
6605
|
function createShareCommand() {
|
|
6045
|
-
return new
|
|
6606
|
+
return new Command57("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
6607
|
await runShareAction(projectPath, options);
|
|
6047
6608
|
});
|
|
6048
6609
|
}
|
|
6049
6610
|
|
|
6050
6611
|
// src/commands/skill/index.ts
|
|
6051
|
-
import { Command as
|
|
6612
|
+
import { Command as Command65 } from "commander";
|
|
6052
6613
|
|
|
6053
6614
|
// src/commands/skill/list.ts
|
|
6054
|
-
import { Command as
|
|
6055
|
-
import * as
|
|
6056
|
-
import * as
|
|
6615
|
+
import { Command as Command58 } from "commander";
|
|
6616
|
+
import * as fs30 from "fs";
|
|
6617
|
+
import * as path47 from "path";
|
|
6057
6618
|
import { parse } from "yaml";
|
|
6058
6619
|
function scanDirectory(dirPath, source) {
|
|
6059
|
-
if (!
|
|
6060
|
-
const entries =
|
|
6620
|
+
if (!fs30.existsSync(dirPath)) return [];
|
|
6621
|
+
const entries = fs30.readdirSync(dirPath, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
|
|
6061
6622
|
const skills = [];
|
|
6062
6623
|
for (const name of entries) {
|
|
6063
|
-
const yamlPath =
|
|
6064
|
-
if (!
|
|
6624
|
+
const yamlPath = path47.join(dirPath, name, "skill.yaml");
|
|
6625
|
+
if (!fs30.existsSync(yamlPath)) continue;
|
|
6065
6626
|
try {
|
|
6066
|
-
const raw =
|
|
6627
|
+
const raw = fs30.readFileSync(yamlPath, "utf-8");
|
|
6067
6628
|
const parsed = parse(raw);
|
|
6068
6629
|
const result = SkillMetadataSchema.safeParse(parsed);
|
|
6069
6630
|
if (result.success) {
|
|
@@ -6081,10 +6642,10 @@ function scanDirectory(dirPath, source) {
|
|
|
6081
6642
|
}
|
|
6082
6643
|
function collectCommunitySkills(seen, allSkills) {
|
|
6083
6644
|
const globalDir = resolveGlobalSkillsDir();
|
|
6084
|
-
const skillsDir =
|
|
6085
|
-
const communityBase =
|
|
6086
|
-
const communityPlatformDir =
|
|
6087
|
-
const lockfilePath =
|
|
6645
|
+
const skillsDir = path47.dirname(globalDir);
|
|
6646
|
+
const communityBase = path47.join(skillsDir, "community");
|
|
6647
|
+
const communityPlatformDir = path47.join(communityBase, "claude-code");
|
|
6648
|
+
const lockfilePath = path47.join(communityBase, "skills-lock.json");
|
|
6088
6649
|
const lockfile = readLockfile2(lockfilePath);
|
|
6089
6650
|
const communitySkills = scanDirectory(communityPlatformDir, "community");
|
|
6090
6651
|
for (const skill of communitySkills) {
|
|
@@ -6154,7 +6715,7 @@ function printSkillsVerbose(skills) {
|
|
|
6154
6715
|
for (const s of skills) printSkillEntry(s);
|
|
6155
6716
|
}
|
|
6156
6717
|
function createListCommand3() {
|
|
6157
|
-
return new
|
|
6718
|
+
return new Command58("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
6719
|
const globalOpts = cmd.optsWithGlobals();
|
|
6159
6720
|
const skills = collectSkills({ filter: resolveFilter(opts) });
|
|
6160
6721
|
if (globalOpts.json) {
|
|
@@ -6169,9 +6730,9 @@ function createListCommand3() {
|
|
|
6169
6730
|
}
|
|
6170
6731
|
|
|
6171
6732
|
// src/commands/skill/run.ts
|
|
6172
|
-
import { Command as
|
|
6173
|
-
import * as
|
|
6174
|
-
import * as
|
|
6733
|
+
import { Command as Command59 } from "commander";
|
|
6734
|
+
import * as fs31 from "fs";
|
|
6735
|
+
import * as path48 from "path";
|
|
6175
6736
|
import { parse as parse2 } from "yaml";
|
|
6176
6737
|
|
|
6177
6738
|
// src/skill/complexity.ts
|
|
@@ -6267,10 +6828,10 @@ ${options.principles}`);
|
|
|
6267
6828
|
|
|
6268
6829
|
// src/commands/skill/run.ts
|
|
6269
6830
|
function loadSkillMetadata(skillDir) {
|
|
6270
|
-
const yamlPath =
|
|
6271
|
-
if (!
|
|
6831
|
+
const yamlPath = path48.join(skillDir, "skill.yaml");
|
|
6832
|
+
if (!fs31.existsSync(yamlPath)) return null;
|
|
6272
6833
|
try {
|
|
6273
|
-
const result = SkillMetadataSchema.safeParse(parse2(
|
|
6834
|
+
const result = SkillMetadataSchema.safeParse(parse2(fs31.readFileSync(yamlPath, "utf-8")));
|
|
6274
6835
|
return result.success ? result.data : null;
|
|
6275
6836
|
} catch {
|
|
6276
6837
|
return null;
|
|
@@ -6282,22 +6843,22 @@ function resolveComplexity(metadata, requested, projectPath) {
|
|
|
6282
6843
|
return requested;
|
|
6283
6844
|
}
|
|
6284
6845
|
function loadPrinciples(projectPath) {
|
|
6285
|
-
const principlesPath =
|
|
6286
|
-
return
|
|
6846
|
+
const principlesPath = path48.join(projectPath, "docs", "principles.md");
|
|
6847
|
+
return fs31.existsSync(principlesPath) ? fs31.readFileSync(principlesPath, "utf-8") : void 0;
|
|
6287
6848
|
}
|
|
6288
6849
|
function readMostRecentFileInDir(dirPath) {
|
|
6289
|
-
const files =
|
|
6290
|
-
if (files.length > 0) return
|
|
6850
|
+
const files = fs31.readdirSync(dirPath).map((f) => ({ name: f, mtime: fs31.statSync(path48.join(dirPath, f)).mtimeMs })).sort((a, b) => b.mtime - a.mtime);
|
|
6851
|
+
if (files.length > 0) return fs31.readFileSync(path48.join(dirPath, files[0].name), "utf-8");
|
|
6291
6852
|
return void 0;
|
|
6292
6853
|
}
|
|
6293
6854
|
function loadPriorState(metadata, projectPath) {
|
|
6294
6855
|
if (!metadata?.state.persistent || metadata.state.files.length === 0) return void 0;
|
|
6295
6856
|
for (const stateFilePath of metadata.state.files) {
|
|
6296
|
-
const fullPath =
|
|
6297
|
-
if (!
|
|
6298
|
-
const stat =
|
|
6857
|
+
const fullPath = path48.join(projectPath, stateFilePath);
|
|
6858
|
+
if (!fs31.existsSync(fullPath)) continue;
|
|
6859
|
+
const stat = fs31.statSync(fullPath);
|
|
6299
6860
|
if (stat.isDirectory()) return readMostRecentFileInDir(fullPath);
|
|
6300
|
-
return
|
|
6861
|
+
return fs31.readFileSync(fullPath, "utf-8");
|
|
6301
6862
|
}
|
|
6302
6863
|
return void 0;
|
|
6303
6864
|
}
|
|
@@ -6317,9 +6878,9 @@ function resolvePhaseState(metadata, projectPath, phase) {
|
|
|
6317
6878
|
}
|
|
6318
6879
|
function appendProjectState(content, metadata, projectPath, hasPathOpt) {
|
|
6319
6880
|
if (!metadata?.state.persistent || !hasPathOpt) return content;
|
|
6320
|
-
const stateFile =
|
|
6321
|
-
if (!
|
|
6322
|
-
const stateContent =
|
|
6881
|
+
const stateFile = path48.join(projectPath, ".harness", "state.json");
|
|
6882
|
+
if (!fs31.existsSync(stateFile)) return content;
|
|
6883
|
+
const stateContent = fs31.readFileSync(stateFile, "utf-8");
|
|
6323
6884
|
return content + `
|
|
6324
6885
|
|
|
6325
6886
|
---
|
|
@@ -6331,14 +6892,14 @@ ${stateContent}
|
|
|
6331
6892
|
}
|
|
6332
6893
|
async function runSkill(name, opts) {
|
|
6333
6894
|
const skillsDir = resolveSkillsDir();
|
|
6334
|
-
const skillDir =
|
|
6335
|
-
if (!
|
|
6895
|
+
const skillDir = path48.join(skillsDir, name);
|
|
6896
|
+
if (!fs31.existsSync(skillDir)) {
|
|
6336
6897
|
logger.error(`Skill not found: ${name}`);
|
|
6337
6898
|
process.exit(ExitCode.ERROR);
|
|
6338
6899
|
return;
|
|
6339
6900
|
}
|
|
6340
6901
|
const metadata = loadSkillMetadata(skillDir);
|
|
6341
|
-
const projectPath = opts.path ?
|
|
6902
|
+
const projectPath = opts.path ? path48.resolve(opts.path) : process.cwd();
|
|
6342
6903
|
const complexity = resolveComplexity(
|
|
6343
6904
|
metadata,
|
|
6344
6905
|
opts.complexity ?? "standard",
|
|
@@ -6365,14 +6926,14 @@ async function runSkill(name, opts) {
|
|
|
6365
6926
|
...stateWarning !== void 0 && { stateWarning },
|
|
6366
6927
|
...opts.party !== void 0 && { party: opts.party }
|
|
6367
6928
|
});
|
|
6368
|
-
const skillMdPath =
|
|
6369
|
-
if (!
|
|
6929
|
+
const skillMdPath = path48.join(skillDir, "SKILL.md");
|
|
6930
|
+
if (!fs31.existsSync(skillMdPath)) {
|
|
6370
6931
|
logger.error(`SKILL.md not found for skill: ${name}`);
|
|
6371
6932
|
process.exit(ExitCode.ERROR);
|
|
6372
6933
|
return;
|
|
6373
6934
|
}
|
|
6374
6935
|
const content = appendProjectState(
|
|
6375
|
-
|
|
6936
|
+
fs31.readFileSync(skillMdPath, "utf-8"),
|
|
6376
6937
|
metadata,
|
|
6377
6938
|
projectPath,
|
|
6378
6939
|
!!opts.path
|
|
@@ -6381,13 +6942,13 @@ async function runSkill(name, opts) {
|
|
|
6381
6942
|
process.exit(ExitCode.SUCCESS);
|
|
6382
6943
|
}
|
|
6383
6944
|
function createRunCommand2() {
|
|
6384
|
-
return new
|
|
6945
|
+
return new Command59("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
6946
|
}
|
|
6386
6947
|
|
|
6387
6948
|
// src/commands/skill/validate.ts
|
|
6388
|
-
import { Command as
|
|
6389
|
-
import * as
|
|
6390
|
-
import * as
|
|
6949
|
+
import { Command as Command60 } from "commander";
|
|
6950
|
+
import * as fs32 from "fs";
|
|
6951
|
+
import * as path49 from "path";
|
|
6391
6952
|
import { parse as parse3 } from "yaml";
|
|
6392
6953
|
var BEHAVIORAL_REQUIRED_SECTIONS = [
|
|
6393
6954
|
"## When to Use",
|
|
@@ -6399,11 +6960,11 @@ var BEHAVIORAL_REQUIRED_SECTIONS = [
|
|
|
6399
6960
|
];
|
|
6400
6961
|
var KNOWLEDGE_REQUIRED_SECTIONS = ["## Instructions"];
|
|
6401
6962
|
function validateSkillMd(name, skillMdPath, skillType, errors) {
|
|
6402
|
-
if (!
|
|
6963
|
+
if (!fs32.existsSync(skillMdPath)) {
|
|
6403
6964
|
errors.push(`${name}: missing SKILL.md`);
|
|
6404
6965
|
return;
|
|
6405
6966
|
}
|
|
6406
|
-
const mdContent =
|
|
6967
|
+
const mdContent = fs32.readFileSync(skillMdPath, "utf-8");
|
|
6407
6968
|
if (!mdContent.trim().startsWith("# ")) {
|
|
6408
6969
|
errors.push(`${name}/SKILL.md: must start with an h1 heading`);
|
|
6409
6970
|
}
|
|
@@ -6428,20 +6989,20 @@ function validateSkillMd(name, skillMdPath, skillType, errors) {
|
|
|
6428
6989
|
}
|
|
6429
6990
|
}
|
|
6430
6991
|
function validateSkillEntry(name, skillsDir, errors) {
|
|
6431
|
-
const skillDir =
|
|
6432
|
-
const yamlPath =
|
|
6433
|
-
if (!
|
|
6992
|
+
const skillDir = path49.join(skillsDir, name);
|
|
6993
|
+
const yamlPath = path49.join(skillDir, "skill.yaml");
|
|
6994
|
+
if (!fs32.existsSync(yamlPath)) {
|
|
6434
6995
|
errors.push(`${name}: missing skill.yaml`);
|
|
6435
6996
|
return false;
|
|
6436
6997
|
}
|
|
6437
6998
|
try {
|
|
6438
|
-
const raw =
|
|
6999
|
+
const raw = fs32.readFileSync(yamlPath, "utf-8");
|
|
6439
7000
|
const result = SkillMetadataSchema.safeParse(parse3(raw));
|
|
6440
7001
|
if (!result.success) {
|
|
6441
7002
|
errors.push(`${name}/skill.yaml: ${result.error.message}`);
|
|
6442
7003
|
return false;
|
|
6443
7004
|
}
|
|
6444
|
-
validateSkillMd(name,
|
|
7005
|
+
validateSkillMd(name, path49.join(skillDir, "SKILL.md"), result.data.type, errors);
|
|
6445
7006
|
return true;
|
|
6446
7007
|
} catch (e) {
|
|
6447
7008
|
errors.push(`${name}: parse error \u2014 ${e instanceof Error ? e.message : String(e)}`);
|
|
@@ -6449,15 +7010,15 @@ function validateSkillEntry(name, skillsDir, errors) {
|
|
|
6449
7010
|
}
|
|
6450
7011
|
}
|
|
6451
7012
|
function createValidateCommand2() {
|
|
6452
|
-
return new
|
|
7013
|
+
return new Command60("validate").description("Validate all skill.yaml files and SKILL.md structure").action(async (_opts, cmd) => {
|
|
6453
7014
|
const globalOpts = cmd.optsWithGlobals();
|
|
6454
7015
|
const skillsDir = resolveSkillsDir();
|
|
6455
|
-
if (!
|
|
7016
|
+
if (!fs32.existsSync(skillsDir)) {
|
|
6456
7017
|
logger.info("No skills directory found.");
|
|
6457
7018
|
process.exit(ExitCode.SUCCESS);
|
|
6458
7019
|
return;
|
|
6459
7020
|
}
|
|
6460
|
-
const entries =
|
|
7021
|
+
const entries = fs32.readdirSync(skillsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
|
|
6461
7022
|
const errors = [];
|
|
6462
7023
|
let validated = 0;
|
|
6463
7024
|
for (const name of entries) {
|
|
@@ -6479,24 +7040,24 @@ function createValidateCommand2() {
|
|
|
6479
7040
|
}
|
|
6480
7041
|
|
|
6481
7042
|
// src/commands/skill/info.ts
|
|
6482
|
-
import { Command as
|
|
6483
|
-
import * as
|
|
6484
|
-
import * as
|
|
7043
|
+
import { Command as Command61 } from "commander";
|
|
7044
|
+
import * as fs33 from "fs";
|
|
7045
|
+
import * as path50 from "path";
|
|
6485
7046
|
import { parse as parse4 } from "yaml";
|
|
6486
7047
|
function loadSkillMetadata2(name) {
|
|
6487
7048
|
const skillsDir = resolveSkillsDir();
|
|
6488
|
-
const skillDir =
|
|
6489
|
-
if (!
|
|
7049
|
+
const skillDir = path50.join(skillsDir, name);
|
|
7050
|
+
if (!fs33.existsSync(skillDir)) {
|
|
6490
7051
|
logger.error(`Skill not found: ${name}`);
|
|
6491
7052
|
return { ok: false, exitCode: ExitCode.ERROR };
|
|
6492
7053
|
}
|
|
6493
|
-
const yamlPath =
|
|
6494
|
-
if (!
|
|
7054
|
+
const yamlPath = path50.join(skillDir, "skill.yaml");
|
|
7055
|
+
if (!fs33.existsSync(yamlPath)) {
|
|
6495
7056
|
logger.error(`skill.yaml not found for skill: ${name}`);
|
|
6496
7057
|
return { ok: false, exitCode: ExitCode.ERROR };
|
|
6497
7058
|
}
|
|
6498
7059
|
try {
|
|
6499
|
-
const raw =
|
|
7060
|
+
const raw = fs33.readFileSync(yamlPath, "utf-8");
|
|
6500
7061
|
const parsed = parse4(raw);
|
|
6501
7062
|
const result = SkillMetadataSchema.safeParse(parsed);
|
|
6502
7063
|
if (!result.success) {
|
|
@@ -6525,7 +7086,7 @@ function printSkillInfo(skill) {
|
|
|
6525
7086
|
console.log(`Persistent: ${skill.state.persistent}`);
|
|
6526
7087
|
}
|
|
6527
7088
|
function createInfoCommand() {
|
|
6528
|
-
return new
|
|
7089
|
+
return new Command61("info").description("Show metadata for a skill").argument("<name>", "Skill name (e.g., harness-tdd)").action(async (name, _opts, cmd) => {
|
|
6529
7090
|
const globalOpts = cmd.optsWithGlobals();
|
|
6530
7091
|
const loaded = loadSkillMetadata2(name);
|
|
6531
7092
|
if (!loaded.ok) {
|
|
@@ -6542,7 +7103,7 @@ function createInfoCommand() {
|
|
|
6542
7103
|
}
|
|
6543
7104
|
|
|
6544
7105
|
// src/commands/skill/search.ts
|
|
6545
|
-
import { Command as
|
|
7106
|
+
import { Command as Command62 } from "commander";
|
|
6546
7107
|
async function runSearch(query, opts) {
|
|
6547
7108
|
const results = await searchNpmRegistry(query, opts.registry);
|
|
6548
7109
|
return results.filter((r) => {
|
|
@@ -6587,15 +7148,15 @@ async function runSearchAction(query, opts, globalOpts) {
|
|
|
6587
7148
|
}
|
|
6588
7149
|
}
|
|
6589
7150
|
function createSearchCommand() {
|
|
6590
|
-
return new
|
|
7151
|
+
return new Command62("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
7152
|
await runSearchAction(query, opts, cmd.optsWithGlobals());
|
|
6592
7153
|
});
|
|
6593
7154
|
}
|
|
6594
7155
|
|
|
6595
7156
|
// src/commands/skill/create.ts
|
|
6596
|
-
import { Command as
|
|
6597
|
-
import * as
|
|
6598
|
-
import * as
|
|
7157
|
+
import { Command as Command63 } from "commander";
|
|
7158
|
+
import * as path51 from "path";
|
|
7159
|
+
import * as fs34 from "fs";
|
|
6599
7160
|
import YAML from "yaml";
|
|
6600
7161
|
var KEBAB_CASE_RE = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/;
|
|
6601
7162
|
function buildReadme(name, description) {
|
|
@@ -6679,22 +7240,22 @@ function runCreate(name, opts) {
|
|
|
6679
7240
|
if (!KEBAB_CASE_RE.test(name)) {
|
|
6680
7241
|
throw new Error(`Invalid skill name "${name}". Must be kebab-case (e.g., my-skill).`);
|
|
6681
7242
|
}
|
|
6682
|
-
const baseDir = opts.outputDir ??
|
|
6683
|
-
const skillDir =
|
|
6684
|
-
if (
|
|
7243
|
+
const baseDir = opts.outputDir ?? path51.join(process.cwd(), "agents", "skills", "claude-code");
|
|
7244
|
+
const skillDir = path51.join(baseDir, name);
|
|
7245
|
+
if (fs34.existsSync(skillDir)) {
|
|
6685
7246
|
throw new Error(`Skill directory already exists: ${skillDir}`);
|
|
6686
7247
|
}
|
|
6687
|
-
|
|
7248
|
+
fs34.mkdirSync(skillDir, { recursive: true });
|
|
6688
7249
|
const description = opts.description || `A community skill: ${name}`;
|
|
6689
7250
|
const skillYaml = buildSkillYaml(name, opts);
|
|
6690
|
-
const skillYamlPath =
|
|
6691
|
-
|
|
7251
|
+
const skillYamlPath = path51.join(skillDir, "skill.yaml");
|
|
7252
|
+
fs34.writeFileSync(skillYamlPath, YAML.stringify(skillYaml));
|
|
6692
7253
|
const skillMd = buildSkillMd(name, description);
|
|
6693
|
-
const skillMdPath =
|
|
6694
|
-
|
|
7254
|
+
const skillMdPath = path51.join(skillDir, "SKILL.md");
|
|
7255
|
+
fs34.writeFileSync(skillMdPath, skillMd);
|
|
6695
7256
|
const readme = buildReadme(name, description);
|
|
6696
|
-
const readmePath =
|
|
6697
|
-
|
|
7257
|
+
const readmePath = path51.join(skillDir, "README.md");
|
|
7258
|
+
fs34.writeFileSync(readmePath, readme);
|
|
6698
7259
|
return {
|
|
6699
7260
|
name,
|
|
6700
7261
|
directory: skillDir,
|
|
@@ -6708,12 +7269,12 @@ function printCreateResult(name, result) {
|
|
|
6708
7269
|
}
|
|
6709
7270
|
logger.info(`
|
|
6710
7271
|
Next steps:`);
|
|
6711
|
-
logger.info(` 1. Edit ${
|
|
7272
|
+
logger.info(` 1. Edit ${path51.join(result.directory, "SKILL.md")} with your skill content`);
|
|
6712
7273
|
logger.info(` 2. Run: harness skill validate ${name}`);
|
|
6713
7274
|
logger.info(` 3. Run: harness skills publish`);
|
|
6714
7275
|
}
|
|
6715
7276
|
function createCreateCommand() {
|
|
6716
|
-
return new
|
|
7277
|
+
return new Command63("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
7278
|
const globalOpts = cmd.optsWithGlobals();
|
|
6718
7279
|
try {
|
|
6719
7280
|
const result = runCreate(name, {
|
|
@@ -6736,26 +7297,26 @@ function createCreateCommand() {
|
|
|
6736
7297
|
}
|
|
6737
7298
|
|
|
6738
7299
|
// src/commands/skill/publish.ts
|
|
6739
|
-
import { Command as
|
|
6740
|
-
import * as
|
|
6741
|
-
import * as
|
|
7300
|
+
import { Command as Command64 } from "commander";
|
|
7301
|
+
import * as fs36 from "fs";
|
|
7302
|
+
import * as path53 from "path";
|
|
6742
7303
|
import { execFileSync as execFileSync5 } from "child_process";
|
|
6743
7304
|
|
|
6744
7305
|
// src/registry/validator.ts
|
|
6745
|
-
import * as
|
|
6746
|
-
import * as
|
|
7306
|
+
import * as fs35 from "fs";
|
|
7307
|
+
import * as path52 from "path";
|
|
6747
7308
|
import { parse as parse5 } from "yaml";
|
|
6748
7309
|
import semver4 from "semver";
|
|
6749
7310
|
async function validateForPublish(skillDir, registryUrl) {
|
|
6750
7311
|
const errors = [];
|
|
6751
|
-
const skillYamlPath =
|
|
6752
|
-
if (!
|
|
7312
|
+
const skillYamlPath = path52.join(skillDir, "skill.yaml");
|
|
7313
|
+
if (!fs35.existsSync(skillYamlPath)) {
|
|
6753
7314
|
errors.push("skill.yaml not found. Create one with: harness skill create <name>");
|
|
6754
7315
|
return { valid: false, errors };
|
|
6755
7316
|
}
|
|
6756
7317
|
let skillMeta;
|
|
6757
7318
|
try {
|
|
6758
|
-
const raw =
|
|
7319
|
+
const raw = fs35.readFileSync(skillYamlPath, "utf-8");
|
|
6759
7320
|
const parsed = parse5(raw);
|
|
6760
7321
|
const result = SkillMetadataSchema.safeParse(parsed);
|
|
6761
7322
|
if (!result.success) {
|
|
@@ -6777,11 +7338,11 @@ async function validateForPublish(skillDir, registryUrl) {
|
|
|
6777
7338
|
if (!skillMeta.triggers || skillMeta.triggers.length === 0) {
|
|
6778
7339
|
errors.push("At least one trigger is required. Add triggers to skill.yaml.");
|
|
6779
7340
|
}
|
|
6780
|
-
const skillMdPath =
|
|
6781
|
-
if (!
|
|
7341
|
+
const skillMdPath = path52.join(skillDir, "SKILL.md");
|
|
7342
|
+
if (!fs35.existsSync(skillMdPath)) {
|
|
6782
7343
|
errors.push("SKILL.md not found. Create it with content describing your skill.");
|
|
6783
7344
|
} else {
|
|
6784
|
-
const content =
|
|
7345
|
+
const content = fs35.readFileSync(skillMdPath, "utf-8");
|
|
6785
7346
|
if (!content.includes("## When to Use")) {
|
|
6786
7347
|
errors.push('SKILL.md must contain a "## When to Use" section.');
|
|
6787
7348
|
}
|
|
@@ -6861,11 +7422,11 @@ ${errorList}`);
|
|
|
6861
7422
|
}
|
|
6862
7423
|
const meta = validation.skillMeta;
|
|
6863
7424
|
const pkg = derivePackageJson(meta);
|
|
6864
|
-
const pkgPath =
|
|
6865
|
-
|
|
6866
|
-
const readmePath =
|
|
6867
|
-
if (!
|
|
6868
|
-
const skillMdContent =
|
|
7425
|
+
const pkgPath = path53.join(skillDir, "package.json");
|
|
7426
|
+
fs36.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
|
|
7427
|
+
const readmePath = path53.join(skillDir, "README.md");
|
|
7428
|
+
if (!fs36.existsSync(readmePath)) {
|
|
7429
|
+
const skillMdContent = fs36.readFileSync(path53.join(skillDir, "SKILL.md"), "utf-8");
|
|
6869
7430
|
const readme = `# ${pkg.name}
|
|
6870
7431
|
|
|
6871
7432
|
${meta.description}
|
|
@@ -6879,7 +7440,7 @@ harness install ${meta.name}
|
|
|
6879
7440
|
---
|
|
6880
7441
|
|
|
6881
7442
|
${skillMdContent}`;
|
|
6882
|
-
|
|
7443
|
+
fs36.writeFileSync(readmePath, readme);
|
|
6883
7444
|
}
|
|
6884
7445
|
if (opts.dryRun) {
|
|
6885
7446
|
return {
|
|
@@ -6905,7 +7466,7 @@ ${skillMdContent}`;
|
|
|
6905
7466
|
};
|
|
6906
7467
|
}
|
|
6907
7468
|
function createPublishCommand() {
|
|
6908
|
-
return new
|
|
7469
|
+
return new Command64("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
7470
|
const globalOpts = cmd.optsWithGlobals();
|
|
6910
7471
|
const skillDir = opts.dir || process.cwd();
|
|
6911
7472
|
try {
|
|
@@ -6930,7 +7491,7 @@ function createPublishCommand() {
|
|
|
6930
7491
|
|
|
6931
7492
|
// src/commands/skill/index.ts
|
|
6932
7493
|
function createSkillCommand() {
|
|
6933
|
-
const command = new
|
|
7494
|
+
const command = new Command65("skill").description("Skill management commands");
|
|
6934
7495
|
command.addCommand(createListCommand3());
|
|
6935
7496
|
command.addCommand(createRunCommand2());
|
|
6936
7497
|
command.addCommand(createValidateCommand2());
|
|
@@ -6942,9 +7503,10 @@ function createSkillCommand() {
|
|
|
6942
7503
|
}
|
|
6943
7504
|
|
|
6944
7505
|
// src/commands/snapshot.ts
|
|
6945
|
-
|
|
7506
|
+
init_dist();
|
|
7507
|
+
import { Command as Command66 } from "commander";
|
|
6946
7508
|
import { execSync as execSync5 } from "child_process";
|
|
6947
|
-
import
|
|
7509
|
+
import chalk10 from "chalk";
|
|
6948
7510
|
function getCommitHash3(cwd) {
|
|
6949
7511
|
try {
|
|
6950
7512
|
return execSync5("git rev-parse --short HEAD", { cwd, encoding: "utf-8" }).toString().trim();
|
|
@@ -6969,9 +7531,9 @@ function formatDelta(delta) {
|
|
|
6969
7531
|
function directionSymbol(direction) {
|
|
6970
7532
|
switch (direction) {
|
|
6971
7533
|
case "improving":
|
|
6972
|
-
return
|
|
7534
|
+
return chalk10.green("improving");
|
|
6973
7535
|
case "declining":
|
|
6974
|
-
return
|
|
7536
|
+
return chalk10.red("declining");
|
|
6975
7537
|
case "stable":
|
|
6976
7538
|
return "=";
|
|
6977
7539
|
}
|
|
@@ -7124,7 +7686,7 @@ function registerListCommand(parent) {
|
|
|
7124
7686
|
});
|
|
7125
7687
|
}
|
|
7126
7688
|
function createSnapshotCommand() {
|
|
7127
|
-
const command = new
|
|
7689
|
+
const command = new Command66("snapshot").description("Architecture timeline snapshot commands");
|
|
7128
7690
|
registerCaptureCommand(command);
|
|
7129
7691
|
registerTrendsCommand(command);
|
|
7130
7692
|
registerListCommand(command);
|
|
@@ -7132,11 +7694,12 @@ function createSnapshotCommand() {
|
|
|
7132
7694
|
}
|
|
7133
7695
|
|
|
7134
7696
|
// src/commands/state/index.ts
|
|
7135
|
-
import { Command as
|
|
7697
|
+
import { Command as Command71 } from "commander";
|
|
7136
7698
|
|
|
7137
7699
|
// src/commands/state/show.ts
|
|
7138
|
-
|
|
7139
|
-
import
|
|
7700
|
+
init_dist();
|
|
7701
|
+
import { Command as Command67 } from "commander";
|
|
7702
|
+
import * as path54 from "path";
|
|
7140
7703
|
function printStateText(state, stream) {
|
|
7141
7704
|
if (stream) console.log(`Stream: ${stream}`);
|
|
7142
7705
|
console.log(`Schema Version: ${state.schemaVersion}`);
|
|
@@ -7160,9 +7723,9 @@ function printStateProgress(progress) {
|
|
|
7160
7723
|
}
|
|
7161
7724
|
}
|
|
7162
7725
|
function createShowCommand() {
|
|
7163
|
-
return new
|
|
7726
|
+
return new Command67("show").description("Show current project state").option("--path <path>", "Project root path", ".").option("--stream <name>", "Target a specific stream").action(async (opts, cmd) => {
|
|
7164
7727
|
const globalOpts = cmd.optsWithGlobals();
|
|
7165
|
-
const result = await loadState(
|
|
7728
|
+
const result = await loadState(path54.resolve(opts.path), opts.stream);
|
|
7166
7729
|
if (!result.ok) {
|
|
7167
7730
|
logger.error(result.error.message);
|
|
7168
7731
|
process.exit(ExitCode.ERROR);
|
|
@@ -7177,10 +7740,11 @@ function createShowCommand() {
|
|
|
7177
7740
|
}
|
|
7178
7741
|
|
|
7179
7742
|
// src/commands/state/reset.ts
|
|
7180
|
-
|
|
7181
|
-
import
|
|
7182
|
-
import * as
|
|
7183
|
-
import * as
|
|
7743
|
+
init_dist();
|
|
7744
|
+
import { Command as Command68 } from "commander";
|
|
7745
|
+
import * as fs37 from "fs";
|
|
7746
|
+
import * as path55 from "path";
|
|
7747
|
+
import * as readline2 from "readline";
|
|
7184
7748
|
async function resolveStatePath(projectPath, stream) {
|
|
7185
7749
|
if (stream) {
|
|
7186
7750
|
const streamResult = await resolveStreamPath(projectPath, { stream });
|
|
@@ -7188,22 +7752,22 @@ async function resolveStatePath(projectPath, stream) {
|
|
|
7188
7752
|
logger.error(streamResult.error.message);
|
|
7189
7753
|
process.exit(ExitCode.ERROR);
|
|
7190
7754
|
}
|
|
7191
|
-
return
|
|
7755
|
+
return path55.join(streamResult.value, "state.json");
|
|
7192
7756
|
}
|
|
7193
|
-
return
|
|
7757
|
+
return path55.join(projectPath, ".harness", "state.json");
|
|
7194
7758
|
}
|
|
7195
7759
|
async function confirmReset() {
|
|
7196
|
-
const rl =
|
|
7197
|
-
const answer = await new Promise((
|
|
7198
|
-
rl.question("Reset project state? This cannot be undone. [y/N] ",
|
|
7760
|
+
const rl = readline2.createInterface({ input: process.stdin, output: process.stdout });
|
|
7761
|
+
const answer = await new Promise((resolve35) => {
|
|
7762
|
+
rl.question("Reset project state? This cannot be undone. [y/N] ", resolve35);
|
|
7199
7763
|
});
|
|
7200
7764
|
rl.close();
|
|
7201
7765
|
return answer.toLowerCase() === "y" || answer.toLowerCase() === "yes";
|
|
7202
7766
|
}
|
|
7203
7767
|
async function runReset(opts) {
|
|
7204
|
-
const projectPath =
|
|
7768
|
+
const projectPath = path55.resolve(opts.path);
|
|
7205
7769
|
const statePath = await resolveStatePath(projectPath, opts.stream);
|
|
7206
|
-
if (!
|
|
7770
|
+
if (!fs37.existsSync(statePath)) {
|
|
7207
7771
|
logger.info("No state file found. Nothing to reset.");
|
|
7208
7772
|
process.exit(ExitCode.SUCCESS);
|
|
7209
7773
|
}
|
|
@@ -7212,7 +7776,7 @@ async function runReset(opts) {
|
|
|
7212
7776
|
process.exit(ExitCode.SUCCESS);
|
|
7213
7777
|
}
|
|
7214
7778
|
try {
|
|
7215
|
-
|
|
7779
|
+
fs37.unlinkSync(statePath);
|
|
7216
7780
|
logger.success("Project state reset.");
|
|
7217
7781
|
} catch (e) {
|
|
7218
7782
|
logger.error(`Failed to reset state: ${e instanceof Error ? e.message : String(e)}`);
|
|
@@ -7221,15 +7785,16 @@ async function runReset(opts) {
|
|
|
7221
7785
|
process.exit(ExitCode.SUCCESS);
|
|
7222
7786
|
}
|
|
7223
7787
|
function createResetCommand() {
|
|
7224
|
-
return new
|
|
7788
|
+
return new Command68("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
7789
|
}
|
|
7226
7790
|
|
|
7227
7791
|
// src/commands/state/learn.ts
|
|
7228
|
-
|
|
7229
|
-
import
|
|
7792
|
+
init_dist();
|
|
7793
|
+
import { Command as Command69 } from "commander";
|
|
7794
|
+
import * as path56 from "path";
|
|
7230
7795
|
function createLearnCommand() {
|
|
7231
|
-
return new
|
|
7232
|
-
const projectPath =
|
|
7796
|
+
return new Command69("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) => {
|
|
7797
|
+
const projectPath = path56.resolve(opts.path);
|
|
7233
7798
|
const result = await appendLearning(projectPath, message, void 0, void 0, opts.stream);
|
|
7234
7799
|
if (!result.ok) {
|
|
7235
7800
|
logger.error(result.error.message);
|
|
@@ -7242,10 +7807,11 @@ function createLearnCommand() {
|
|
|
7242
7807
|
}
|
|
7243
7808
|
|
|
7244
7809
|
// src/commands/state/streams.ts
|
|
7245
|
-
|
|
7246
|
-
import
|
|
7810
|
+
init_dist();
|
|
7811
|
+
import { Command as Command70 } from "commander";
|
|
7812
|
+
import * as path57 from "path";
|
|
7247
7813
|
async function runListStreams(opts, globalOpts) {
|
|
7248
|
-
const projectPath =
|
|
7814
|
+
const projectPath = path57.resolve(opts.path);
|
|
7249
7815
|
const indexResult = await loadStreamIndex(projectPath);
|
|
7250
7816
|
const result = await listStreams(projectPath);
|
|
7251
7817
|
if (!result.ok) {
|
|
@@ -7266,7 +7832,7 @@ async function runListStreams(opts, globalOpts) {
|
|
|
7266
7832
|
process.exit(ExitCode.SUCCESS);
|
|
7267
7833
|
}
|
|
7268
7834
|
async function runCreateStream(name, opts) {
|
|
7269
|
-
const result = await createStream(
|
|
7835
|
+
const result = await createStream(path57.resolve(opts.path), name, opts.branch);
|
|
7270
7836
|
if (!result.ok) {
|
|
7271
7837
|
logger.error(result.error.message);
|
|
7272
7838
|
process.exit(ExitCode.ERROR);
|
|
@@ -7275,7 +7841,7 @@ async function runCreateStream(name, opts) {
|
|
|
7275
7841
|
process.exit(ExitCode.SUCCESS);
|
|
7276
7842
|
}
|
|
7277
7843
|
async function runArchiveStream(name, opts) {
|
|
7278
|
-
const result = await archiveStream(
|
|
7844
|
+
const result = await archiveStream(path57.resolve(opts.path), name);
|
|
7279
7845
|
if (!result.ok) {
|
|
7280
7846
|
logger.error(result.error.message);
|
|
7281
7847
|
process.exit(ExitCode.ERROR);
|
|
@@ -7284,7 +7850,7 @@ async function runArchiveStream(name, opts) {
|
|
|
7284
7850
|
process.exit(ExitCode.SUCCESS);
|
|
7285
7851
|
}
|
|
7286
7852
|
async function runActivateStream(name, opts) {
|
|
7287
|
-
const result = await setActiveStream(
|
|
7853
|
+
const result = await setActiveStream(path57.resolve(opts.path), name);
|
|
7288
7854
|
if (!result.ok) {
|
|
7289
7855
|
logger.error(result.error.message);
|
|
7290
7856
|
process.exit(ExitCode.ERROR);
|
|
@@ -7293,7 +7859,7 @@ async function runActivateStream(name, opts) {
|
|
|
7293
7859
|
process.exit(ExitCode.SUCCESS);
|
|
7294
7860
|
}
|
|
7295
7861
|
function createStreamsCommand() {
|
|
7296
|
-
const command = new
|
|
7862
|
+
const command = new Command70("streams").description("Manage state streams");
|
|
7297
7863
|
command.command("list").description("List all known streams").option("--path <path>", "Project root path", ".").action(async (opts, cmd) => runListStreams(opts, cmd.optsWithGlobals()));
|
|
7298
7864
|
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
7865
|
command.command("archive <name>").description("Archive a stream").option("--path <path>", "Project root path", ".").action(async (name, opts) => runArchiveStream(name, opts));
|
|
@@ -7303,7 +7869,7 @@ function createStreamsCommand() {
|
|
|
7303
7869
|
|
|
7304
7870
|
// src/commands/state/index.ts
|
|
7305
7871
|
function createStateCommand() {
|
|
7306
|
-
const command = new
|
|
7872
|
+
const command = new Command71("state").description("Project state management commands");
|
|
7307
7873
|
command.addCommand(createShowCommand());
|
|
7308
7874
|
command.addCommand(createResetCommand());
|
|
7309
7875
|
command.addCommand(createLearnCommand());
|
|
@@ -7311,8 +7877,154 @@ function createStateCommand() {
|
|
|
7311
7877
|
return command;
|
|
7312
7878
|
}
|
|
7313
7879
|
|
|
7880
|
+
// src/commands/sync-analyses.ts
|
|
7881
|
+
import * as fs38 from "fs";
|
|
7882
|
+
import * as path58 from "path";
|
|
7883
|
+
import { Command as Command72 } from "commander";
|
|
7884
|
+
init_dist();
|
|
7885
|
+
function isValidRecord(record) {
|
|
7886
|
+
return typeof record.issueId === "string" && !!record.issueId && typeof record.identifier === "string" && !!record.identifier && typeof record.analyzedAt === "string" && !/[/\\]/.test(record.issueId);
|
|
7887
|
+
}
|
|
7888
|
+
function extractAnalysisFromComments(comments) {
|
|
7889
|
+
const sorted = [...comments].sort(
|
|
7890
|
+
(a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
|
|
7891
|
+
);
|
|
7892
|
+
for (const comment of sorted) {
|
|
7893
|
+
const fenceRegex = /```json\s*\r?\n([\s\S]*?)\r?\n```/g;
|
|
7894
|
+
let match;
|
|
7895
|
+
while ((match = fenceRegex.exec(comment.body)) !== null) {
|
|
7896
|
+
try {
|
|
7897
|
+
const parsed = JSON.parse(match[1]);
|
|
7898
|
+
if (parsed._harness_analysis !== true) continue;
|
|
7899
|
+
const { _harness_analysis, _version, ...record } = parsed;
|
|
7900
|
+
if (isValidRecord(record)) return record;
|
|
7901
|
+
} catch {
|
|
7902
|
+
continue;
|
|
7903
|
+
}
|
|
7904
|
+
}
|
|
7905
|
+
}
|
|
7906
|
+
return null;
|
|
7907
|
+
}
|
|
7908
|
+
function bootstrapTrackerCommand2(opts, verb) {
|
|
7909
|
+
const projectPath = path58.resolve(opts.dir);
|
|
7910
|
+
const trackerConfig = loadTrackerSyncConfig(projectPath);
|
|
7911
|
+
if (!trackerConfig) {
|
|
7912
|
+
logger.error(`No tracker config found in harness.config.json. Cannot ${verb}.`);
|
|
7913
|
+
return null;
|
|
7914
|
+
}
|
|
7915
|
+
if (trackerConfig.kind !== "github") {
|
|
7916
|
+
logger.error(
|
|
7917
|
+
`${verb} currently only supports 'github' tracker kind. Found: ${String(trackerConfig.kind)}`
|
|
7918
|
+
);
|
|
7919
|
+
return null;
|
|
7920
|
+
}
|
|
7921
|
+
const projectEnvPath = path58.join(projectPath, ".env");
|
|
7922
|
+
if (fs38.existsSync(projectEnvPath) && !process.env.GITHUB_TOKEN) {
|
|
7923
|
+
__require("dotenv").config({ path: projectEnvPath });
|
|
7924
|
+
}
|
|
7925
|
+
const token = process.env.GITHUB_TOKEN;
|
|
7926
|
+
if (!token) {
|
|
7927
|
+
logger.error("No GITHUB_TOKEN environment variable found.");
|
|
7928
|
+
return null;
|
|
7929
|
+
}
|
|
7930
|
+
return { token, projectPath, trackerConfig };
|
|
7931
|
+
}
|
|
7932
|
+
function loadRoadmapFeatures(projectPath) {
|
|
7933
|
+
const roadmapFile = path58.join(projectPath, "docs", "roadmap.md");
|
|
7934
|
+
if (!fs38.existsSync(roadmapFile)) {
|
|
7935
|
+
logger.error("No docs/roadmap.md found. Cannot discover features with externalIds.");
|
|
7936
|
+
return null;
|
|
7937
|
+
}
|
|
7938
|
+
const { parseRoadmap } = (init_dist(), __toCommonJS(dist_exports));
|
|
7939
|
+
const roadmapParsed = parseRoadmap(fs38.readFileSync(roadmapFile, "utf-8"));
|
|
7940
|
+
if (!roadmapParsed.ok) {
|
|
7941
|
+
logger.error("Failed to parse docs/roadmap.md");
|
|
7942
|
+
return null;
|
|
7943
|
+
}
|
|
7944
|
+
const features = [];
|
|
7945
|
+
for (const milestone of roadmapParsed.value.milestones) {
|
|
7946
|
+
for (const feature of milestone.features) {
|
|
7947
|
+
if (feature.externalId) {
|
|
7948
|
+
features.push({ name: feature.name, externalId: feature.externalId });
|
|
7949
|
+
}
|
|
7950
|
+
}
|
|
7951
|
+
}
|
|
7952
|
+
return features;
|
|
7953
|
+
}
|
|
7954
|
+
async function syncFeatureAnalyses(features, adapter, archive, publishedIndex) {
|
|
7955
|
+
let syncedCount = 0;
|
|
7956
|
+
let skippedCount = 0;
|
|
7957
|
+
let warnCount = 0;
|
|
7958
|
+
for (const { name, externalId } of features) {
|
|
7959
|
+
const commentsResult = await adapter.fetchComments(externalId);
|
|
7960
|
+
if (!commentsResult.ok) {
|
|
7961
|
+
logger.warn(
|
|
7962
|
+
`Failed to fetch comments for "${name}" (${externalId}): ${commentsResult.error.message}`
|
|
7963
|
+
);
|
|
7964
|
+
warnCount++;
|
|
7965
|
+
continue;
|
|
7966
|
+
}
|
|
7967
|
+
const record = extractAnalysisFromComments(commentsResult.value);
|
|
7968
|
+
if (!record) {
|
|
7969
|
+
skippedCount++;
|
|
7970
|
+
continue;
|
|
7971
|
+
}
|
|
7972
|
+
await archive.save(record);
|
|
7973
|
+
publishedIndex[record.issueId] = record.analyzedAt;
|
|
7974
|
+
syncedCount++;
|
|
7975
|
+
logger.info(`Synced analysis for "${name}" (${record.identifier})`);
|
|
7976
|
+
}
|
|
7977
|
+
return { syncedCount, skippedCount, warnCount };
|
|
7978
|
+
}
|
|
7979
|
+
async function runSyncAnalyses(opts) {
|
|
7980
|
+
const bootstrap = bootstrapTrackerCommand2(opts, "sync");
|
|
7981
|
+
if (!bootstrap) return process.exit(1);
|
|
7982
|
+
const { token, projectPath, trackerConfig } = bootstrap;
|
|
7983
|
+
const features = loadRoadmapFeatures(projectPath);
|
|
7984
|
+
if (!features) return process.exit(1);
|
|
7985
|
+
if (features.length === 0) {
|
|
7986
|
+
logger.info("No roadmap features have externalIds. Nothing to sync.");
|
|
7987
|
+
return;
|
|
7988
|
+
}
|
|
7989
|
+
const { AnalysisArchive, loadPublishedIndex: loadPublishedIndex2, savePublishedIndex: savePublishedIndex2 } = await import("@harness-engineering/orchestrator");
|
|
7990
|
+
const { GitHubIssuesSyncAdapter: GitHubIssuesSyncAdapter2 } = await import("./dist-WCSJHQPK.js");
|
|
7991
|
+
const adapter = new GitHubIssuesSyncAdapter2({ token, config: trackerConfig });
|
|
7992
|
+
const archive = new AnalysisArchive(path58.join(projectPath, ".harness", "analyses"));
|
|
7993
|
+
const publishedIndex = loadPublishedIndex2(projectPath);
|
|
7994
|
+
const { syncedCount, skippedCount, warnCount } = await syncFeatureAnalyses(
|
|
7995
|
+
features,
|
|
7996
|
+
adapter,
|
|
7997
|
+
archive,
|
|
7998
|
+
publishedIndex
|
|
7999
|
+
);
|
|
8000
|
+
if (syncedCount > 0) {
|
|
8001
|
+
savePublishedIndex2(projectPath, publishedIndex);
|
|
8002
|
+
logger.success(
|
|
8003
|
+
`Synced ${syncedCount} analysis record(s). Skipped ${skippedCount} (no analysis). Warnings: ${warnCount}.`
|
|
8004
|
+
);
|
|
8005
|
+
} else {
|
|
8006
|
+
logger.info(
|
|
8007
|
+
`No new analyses found on tracker. Skipped ${skippedCount} feature(s) with no analysis comments.`
|
|
8008
|
+
);
|
|
8009
|
+
}
|
|
8010
|
+
}
|
|
8011
|
+
function createSyncAnalysesCommand() {
|
|
8012
|
+
const command = new Command72("sync-analyses").description(
|
|
8013
|
+
"Pull published intelligence analyses from the external issue tracker into the local .harness/analyses/ directory"
|
|
8014
|
+
).option("-d, --dir <path>", "Workspace directory", process.cwd()).action(async (opts) => {
|
|
8015
|
+
try {
|
|
8016
|
+
await runSyncAnalyses(opts);
|
|
8017
|
+
} catch (err) {
|
|
8018
|
+
logger.error(`Error syncing analyses: ${err instanceof Error ? err.message : String(err)}`);
|
|
8019
|
+
process.exit(1);
|
|
8020
|
+
}
|
|
8021
|
+
});
|
|
8022
|
+
return command;
|
|
8023
|
+
}
|
|
8024
|
+
|
|
7314
8025
|
// src/commands/taint.ts
|
|
7315
|
-
|
|
8026
|
+
init_dist();
|
|
8027
|
+
import { Command as Command73 } from "commander";
|
|
7316
8028
|
function getProjectRoot() {
|
|
7317
8029
|
return process.cwd();
|
|
7318
8030
|
}
|
|
@@ -7384,27 +8096,28 @@ function registerStatusCommand(taint) {
|
|
|
7384
8096
|
});
|
|
7385
8097
|
}
|
|
7386
8098
|
function createTaintCommand() {
|
|
7387
|
-
const taint = new
|
|
8099
|
+
const taint = new Command73("taint").description("Manage sentinel session taint state");
|
|
7388
8100
|
registerClearCommand(taint);
|
|
7389
8101
|
registerStatusCommand(taint);
|
|
7390
8102
|
return taint;
|
|
7391
8103
|
}
|
|
7392
8104
|
|
|
7393
8105
|
// src/commands/telemetry/index.ts
|
|
7394
|
-
import { Command as
|
|
8106
|
+
import { Command as Command76 } from "commander";
|
|
7395
8107
|
|
|
7396
8108
|
// src/commands/telemetry/identify.ts
|
|
7397
|
-
|
|
7398
|
-
import
|
|
7399
|
-
import * as
|
|
8109
|
+
init_dist();
|
|
8110
|
+
import { Command as Command74 } from "commander";
|
|
8111
|
+
import * as fs39 from "fs";
|
|
8112
|
+
import * as path59 from "path";
|
|
7400
8113
|
function telemetryFilePath(cwd) {
|
|
7401
|
-
return
|
|
8114
|
+
return path59.join(cwd, ".harness", "telemetry.json");
|
|
7402
8115
|
}
|
|
7403
8116
|
function writeTelemetryFile(cwd, data) {
|
|
7404
8117
|
const filePath = telemetryFilePath(cwd);
|
|
7405
|
-
const dir =
|
|
7406
|
-
|
|
7407
|
-
|
|
8118
|
+
const dir = path59.dirname(filePath);
|
|
8119
|
+
fs39.mkdirSync(dir, { recursive: true });
|
|
8120
|
+
fs39.writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n", "utf-8");
|
|
7408
8121
|
}
|
|
7409
8122
|
var IDENTITY_FIELDS = ["project", "team", "alias"];
|
|
7410
8123
|
function applyIdentityFields(opts, identity) {
|
|
@@ -7418,7 +8131,7 @@ function printIdentity(identity) {
|
|
|
7418
8131
|
}
|
|
7419
8132
|
}
|
|
7420
8133
|
function createIdentifyCommand() {
|
|
7421
|
-
const cmd = new
|
|
8134
|
+
const cmd = new Command74("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
8135
|
const cwd = process.cwd();
|
|
7423
8136
|
if (opts.clear) {
|
|
7424
8137
|
writeTelemetryFile(cwd, { identity: {} });
|
|
@@ -7442,7 +8155,8 @@ function createIdentifyCommand() {
|
|
|
7442
8155
|
}
|
|
7443
8156
|
|
|
7444
8157
|
// src/commands/telemetry/status.ts
|
|
7445
|
-
|
|
8158
|
+
init_dist();
|
|
8159
|
+
import { Command as Command75 } from "commander";
|
|
7446
8160
|
function gatherEnvOverrides() {
|
|
7447
8161
|
const overrides = {};
|
|
7448
8162
|
if (process.env.DO_NOT_TRACK) overrides.DO_NOT_TRACK = process.env.DO_NOT_TRACK;
|
|
@@ -7480,7 +8194,7 @@ function printHumanStatus(result) {
|
|
|
7480
8194
|
}
|
|
7481
8195
|
}
|
|
7482
8196
|
function createStatusCommand() {
|
|
7483
|
-
const cmd = new
|
|
8197
|
+
const cmd = new Command75("status").description("Show current telemetry consent state, install ID, and identity").option("--json", "Output as JSON").action((opts) => {
|
|
7484
8198
|
const cwd = process.cwd();
|
|
7485
8199
|
const envOverrides = gatherEnvOverrides();
|
|
7486
8200
|
const consent = resolveConsent(cwd, void 0);
|
|
@@ -7513,15 +8227,15 @@ function createStatusCommand() {
|
|
|
7513
8227
|
|
|
7514
8228
|
// src/commands/telemetry/index.ts
|
|
7515
8229
|
function createTelemetryCommand() {
|
|
7516
|
-
const command = new
|
|
8230
|
+
const command = new Command76("telemetry").description("Telemetry identity and status management");
|
|
7517
8231
|
command.addCommand(createIdentifyCommand());
|
|
7518
8232
|
command.addCommand(createStatusCommand());
|
|
7519
8233
|
return command;
|
|
7520
8234
|
}
|
|
7521
8235
|
|
|
7522
8236
|
// src/commands/traceability.ts
|
|
7523
|
-
import { Command as
|
|
7524
|
-
import
|
|
8237
|
+
import { Command as Command77 } from "commander";
|
|
8238
|
+
import chalk11 from "chalk";
|
|
7525
8239
|
function confidenceLabel(maxConfidence) {
|
|
7526
8240
|
if (maxConfidence >= 0.8) return "explicit";
|
|
7527
8241
|
if (maxConfidence > 0) return "inferred";
|
|
@@ -7530,13 +8244,13 @@ function confidenceLabel(maxConfidence) {
|
|
|
7530
8244
|
function statusIcon(status) {
|
|
7531
8245
|
switch (status) {
|
|
7532
8246
|
case "full":
|
|
7533
|
-
return
|
|
8247
|
+
return chalk11.green("\u2713 full");
|
|
7534
8248
|
case "code-only":
|
|
7535
|
-
return
|
|
8249
|
+
return chalk11.yellow("\u25D0 code-only");
|
|
7536
8250
|
case "test-only":
|
|
7537
|
-
return
|
|
8251
|
+
return chalk11.yellow("\u25D0 test-only");
|
|
7538
8252
|
case "none":
|
|
7539
|
-
return
|
|
8253
|
+
return chalk11.red("\u2717 none");
|
|
7540
8254
|
default:
|
|
7541
8255
|
return status;
|
|
7542
8256
|
}
|
|
@@ -7578,14 +8292,14 @@ function handleEmptyResults(mode) {
|
|
|
7578
8292
|
}
|
|
7579
8293
|
function printResultTable(result, mode) {
|
|
7580
8294
|
const specLabel = result.specPath || result.featureName || "Unknown";
|
|
7581
|
-
console.log(` ${
|
|
8295
|
+
console.log(` ${chalk11.cyan(specLabel)} ${chalk11.dim(`(${result.summary.total} requirements)`)}`);
|
|
7582
8296
|
console.log("");
|
|
7583
8297
|
const numWidth = 4;
|
|
7584
8298
|
const nameWidth = mode === OutputMode.VERBOSE ? 44 : 36;
|
|
7585
8299
|
const codeWidth = 6;
|
|
7586
8300
|
const testWidth = 7;
|
|
7587
8301
|
const confWidth = 12;
|
|
7588
|
-
const header =
|
|
8302
|
+
const header = chalk11.dim(
|
|
7589
8303
|
` ${pad("#", numWidth)}${pad("Requirement", nameWidth)}${pad("Code", codeWidth)}${pad("Tests", testWidth)}${pad("Confidence", confWidth)}Status`
|
|
7590
8304
|
);
|
|
7591
8305
|
console.log(header);
|
|
@@ -7614,10 +8328,10 @@ function printRequirementRows(requirements, mode, cols) {
|
|
|
7614
8328
|
);
|
|
7615
8329
|
if (mode === OutputMode.VERBOSE) {
|
|
7616
8330
|
for (const f of req.codeFiles) {
|
|
7617
|
-
console.log(
|
|
8331
|
+
console.log(chalk11.dim(` code: ${f.path} (${f.method})`));
|
|
7618
8332
|
}
|
|
7619
8333
|
for (const f of req.testFiles) {
|
|
7620
|
-
console.log(
|
|
8334
|
+
console.log(chalk11.dim(` test: ${f.path} (${f.method})`));
|
|
7621
8335
|
}
|
|
7622
8336
|
}
|
|
7623
8337
|
}
|
|
@@ -7628,7 +8342,7 @@ function printCoverageSummary(summary) {
|
|
|
7628
8342
|
const codePct = s.total > 0 ? Math.round(s.withCode / s.total * 100) : 0;
|
|
7629
8343
|
const testPct = s.total > 0 ? Math.round(s.withTests / s.total * 100) : 0;
|
|
7630
8344
|
console.log(
|
|
7631
|
-
` ${
|
|
8345
|
+
` ${chalk11.bold("Coverage:")} ${fullyPct}% fully traced (${s.fullyTraced}/${s.total}), ${codePct}% with code (${s.withCode}/${s.total}), ${testPct}% with tests (${s.withTests}/${s.total})`
|
|
7632
8346
|
);
|
|
7633
8347
|
}
|
|
7634
8348
|
async function runTraceability(options) {
|
|
@@ -7636,7 +8350,7 @@ async function runTraceability(options) {
|
|
|
7636
8350
|
const projectPath = process.cwd();
|
|
7637
8351
|
const store = await loadGraphStore(projectPath);
|
|
7638
8352
|
if (!store) handleNoStore(mode);
|
|
7639
|
-
const graphModule = await import("./dist-
|
|
8353
|
+
const graphModule = await import("./dist-RADHOOXG.js");
|
|
7640
8354
|
const filterOptions = buildFilterOptions(options);
|
|
7641
8355
|
const results = graphModule.queryTraceability(store, filterOptions);
|
|
7642
8356
|
if (results.length === 0) handleEmptyResults(mode);
|
|
@@ -7644,14 +8358,14 @@ async function runTraceability(options) {
|
|
|
7644
8358
|
console.log(JSON.stringify(results, null, 2));
|
|
7645
8359
|
} else {
|
|
7646
8360
|
console.log("");
|
|
7647
|
-
console.log(
|
|
8361
|
+
console.log(chalk11.bold("Spec-to-Implementation Traceability"));
|
|
7648
8362
|
console.log("");
|
|
7649
8363
|
for (const result of results) printResultTable(result, mode);
|
|
7650
8364
|
}
|
|
7651
8365
|
process.exit(ExitCode.SUCCESS);
|
|
7652
8366
|
}
|
|
7653
8367
|
function createTraceabilityCommand() {
|
|
7654
|
-
return new
|
|
8368
|
+
return new Command77("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
8369
|
const globalOpts = cmd.optsWithGlobals();
|
|
7656
8370
|
await runTraceability({
|
|
7657
8371
|
spec: opts.spec,
|
|
@@ -7664,15 +8378,15 @@ function createTraceabilityCommand() {
|
|
|
7664
8378
|
}
|
|
7665
8379
|
|
|
7666
8380
|
// src/commands/uninstall.ts
|
|
7667
|
-
import * as
|
|
7668
|
-
import { Command as
|
|
8381
|
+
import * as path60 from "path";
|
|
8382
|
+
import { Command as Command78 } from "commander";
|
|
7669
8383
|
async function runUninstall(skillName, options) {
|
|
7670
8384
|
const packageName = resolvePackageName(skillName);
|
|
7671
8385
|
const shortName = extractSkillName(packageName);
|
|
7672
8386
|
const globalDir = resolveGlobalSkillsDir();
|
|
7673
|
-
const skillsDir =
|
|
7674
|
-
const communityBase =
|
|
7675
|
-
const lockfilePath =
|
|
8387
|
+
const skillsDir = path60.dirname(globalDir);
|
|
8388
|
+
const communityBase = path60.join(skillsDir, "community");
|
|
8389
|
+
const lockfilePath = path60.join(communityBase, "skills-lock.json");
|
|
7676
8390
|
const lockfile = readLockfile2(lockfilePath);
|
|
7677
8391
|
const entry = lockfile.skills[packageName];
|
|
7678
8392
|
if (!entry) {
|
|
@@ -7702,7 +8416,7 @@ async function runUninstall(skillName, options) {
|
|
|
7702
8416
|
return result;
|
|
7703
8417
|
}
|
|
7704
8418
|
function createUninstallCommand() {
|
|
7705
|
-
const cmd = new
|
|
8419
|
+
const cmd = new Command78("uninstall");
|
|
7706
8420
|
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
8421
|
try {
|
|
7708
8422
|
const result = await runUninstall(skill, opts);
|
|
@@ -7721,9 +8435,10 @@ function createUninstallCommand() {
|
|
|
7721
8435
|
}
|
|
7722
8436
|
|
|
7723
8437
|
// src/commands/uninstall-constraints.ts
|
|
7724
|
-
|
|
7725
|
-
import * as
|
|
7726
|
-
import
|
|
8438
|
+
init_dist();
|
|
8439
|
+
import * as fs40 from "fs/promises";
|
|
8440
|
+
import * as path61 from "path";
|
|
8441
|
+
import { Command as Command79 } from "commander";
|
|
7727
8442
|
async function runUninstallConstraints(options) {
|
|
7728
8443
|
const { packageName, configPath, lockfilePath } = options;
|
|
7729
8444
|
const lockfileResult = await readLockfile(lockfilePath);
|
|
@@ -7743,7 +8458,7 @@ async function runUninstallConstraints(options) {
|
|
|
7743
8458
|
}
|
|
7744
8459
|
let localConfig;
|
|
7745
8460
|
try {
|
|
7746
|
-
const raw = await
|
|
8461
|
+
const raw = await fs40.readFile(configPath, "utf-8");
|
|
7747
8462
|
localConfig = JSON.parse(raw);
|
|
7748
8463
|
} catch (err) {
|
|
7749
8464
|
return {
|
|
@@ -7780,11 +8495,11 @@ async function runUninstallConstraints(options) {
|
|
|
7780
8495
|
};
|
|
7781
8496
|
}
|
|
7782
8497
|
function createUninstallConstraintsCommand() {
|
|
7783
|
-
const cmd = new
|
|
8498
|
+
const cmd = new Command79("uninstall-constraints");
|
|
7784
8499
|
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
8500
|
let configPath;
|
|
7786
8501
|
if (opts.config) {
|
|
7787
|
-
configPath =
|
|
8502
|
+
configPath = path61.resolve(opts.config);
|
|
7788
8503
|
} else {
|
|
7789
8504
|
const found = findConfigFile();
|
|
7790
8505
|
if (!found.ok) {
|
|
@@ -7793,8 +8508,8 @@ function createUninstallConstraintsCommand() {
|
|
|
7793
8508
|
}
|
|
7794
8509
|
configPath = found.value;
|
|
7795
8510
|
}
|
|
7796
|
-
const projectRoot =
|
|
7797
|
-
const lockfilePath =
|
|
8511
|
+
const projectRoot = path61.dirname(configPath);
|
|
8512
|
+
const lockfilePath = path61.join(projectRoot, ".harness", "constraints.lock.json");
|
|
7798
8513
|
const result = await runUninstallConstraints({
|
|
7799
8514
|
packageName: name,
|
|
7800
8515
|
configPath,
|
|
@@ -7819,12 +8534,13 @@ function createUninstallConstraintsCommand() {
|
|
|
7819
8534
|
}
|
|
7820
8535
|
|
|
7821
8536
|
// src/commands/update.ts
|
|
7822
|
-
import { Command as
|
|
8537
|
+
import { Command as Command80 } from "commander";
|
|
7823
8538
|
import { execFile, execFileSync as execFileSync6 } from "child_process";
|
|
7824
|
-
import { realpathSync } from "fs";
|
|
8539
|
+
import { realpathSync, existsSync as existsSync36, readFileSync as readFileSync25 } from "fs";
|
|
8540
|
+
import { join as join47 } from "path";
|
|
7825
8541
|
import { promisify } from "util";
|
|
7826
|
-
import
|
|
7827
|
-
import
|
|
8542
|
+
import readline3 from "readline";
|
|
8543
|
+
import chalk12 from "chalk";
|
|
7828
8544
|
function detectPackageManager() {
|
|
7829
8545
|
try {
|
|
7830
8546
|
const argv1 = process.argv[1];
|
|
@@ -7882,23 +8598,64 @@ function getInstalledPackages(pm) {
|
|
|
7882
8598
|
return ["@harness-engineering/cli", "@harness-engineering/core"];
|
|
7883
8599
|
}
|
|
7884
8600
|
}
|
|
7885
|
-
function
|
|
7886
|
-
const rl =
|
|
8601
|
+
function prompt2(question) {
|
|
8602
|
+
const rl = readline3.createInterface({
|
|
7887
8603
|
input: process.stdin,
|
|
7888
8604
|
output: process.stdout
|
|
7889
8605
|
});
|
|
7890
|
-
return new Promise((
|
|
8606
|
+
return new Promise((resolve35) => {
|
|
7891
8607
|
rl.question(question, (answer) => {
|
|
7892
8608
|
rl.close();
|
|
7893
|
-
|
|
8609
|
+
resolve35(answer.trim().toLowerCase());
|
|
7894
8610
|
});
|
|
7895
8611
|
});
|
|
7896
8612
|
}
|
|
8613
|
+
async function ensureTelemetryIfNeeded() {
|
|
8614
|
+
const cwd = process.cwd();
|
|
8615
|
+
const result = await ensureTelemetryConfigured(cwd);
|
|
8616
|
+
if (result.status === "pass") {
|
|
8617
|
+
logger.success(result.message);
|
|
8618
|
+
} else if (result.status === "warn") {
|
|
8619
|
+
logger.warn(result.message);
|
|
8620
|
+
}
|
|
8621
|
+
}
|
|
8622
|
+
function refreshHooks() {
|
|
8623
|
+
const cwd = process.cwd();
|
|
8624
|
+
const configPath = join47(cwd, "harness.config.json");
|
|
8625
|
+
if (!existsSync36(configPath)) return;
|
|
8626
|
+
let profile = "standard";
|
|
8627
|
+
const profilePath = join47(cwd, ".harness", "hooks", "profile.json");
|
|
8628
|
+
try {
|
|
8629
|
+
const data = JSON.parse(readFileSync25(profilePath, "utf-8"));
|
|
8630
|
+
if (data.profile && ["minimal", "standard", "strict"].includes(data.profile)) {
|
|
8631
|
+
profile = data.profile;
|
|
8632
|
+
}
|
|
8633
|
+
} catch {
|
|
8634
|
+
}
|
|
8635
|
+
try {
|
|
8636
|
+
const result = initHooks({ profile, projectDir: cwd });
|
|
8637
|
+
logger.success(`Refreshed ${result.copiedScripts.length} hooks (${profile} profile)`);
|
|
8638
|
+
} catch (err) {
|
|
8639
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
8640
|
+
logger.warn(`Hook refresh failed: ${msg}`);
|
|
8641
|
+
}
|
|
8642
|
+
}
|
|
8643
|
+
function runLocalGraphScan() {
|
|
8644
|
+
try {
|
|
8645
|
+
logger.info("Scanning codebase to rebuild knowledge graph...");
|
|
8646
|
+
execFileSync6("harness", ["graph", "scan", "."], { stdio: "inherit" });
|
|
8647
|
+
} catch {
|
|
8648
|
+
logger.warn("Graph scan failed. Run manually:");
|
|
8649
|
+
console.log(` ${chalk12.cyan("harness graph scan .")}`);
|
|
8650
|
+
}
|
|
8651
|
+
}
|
|
7897
8652
|
async function offerRegeneration() {
|
|
7898
8653
|
console.log("");
|
|
7899
|
-
const regenAnswer = await
|
|
8654
|
+
const regenAnswer = await prompt2(
|
|
8655
|
+
"Regenerate slash commands, agent definitions, and knowledge graph? (Y/n) "
|
|
8656
|
+
);
|
|
7900
8657
|
if (regenAnswer === "n" || regenAnswer === "no") return;
|
|
7901
|
-
const scopeAnswer = await
|
|
8658
|
+
const scopeAnswer = await prompt2("Generate for (G)lobal or (l)ocal project? (G/l) ");
|
|
7902
8659
|
const isGlobal = scopeAnswer !== "l" && scopeAnswer !== "local";
|
|
7903
8660
|
try {
|
|
7904
8661
|
execFileSync6("harness", ["generate", ...isGlobal ? ["--global"] : []], {
|
|
@@ -7906,7 +8663,10 @@ async function offerRegeneration() {
|
|
|
7906
8663
|
});
|
|
7907
8664
|
} catch {
|
|
7908
8665
|
logger.warn("Generation failed. Run manually:");
|
|
7909
|
-
console.log(` ${
|
|
8666
|
+
console.log(` ${chalk12.cyan(`harness generate${isGlobal ? " --global" : ""}`)}`);
|
|
8667
|
+
}
|
|
8668
|
+
if (!isGlobal) {
|
|
8669
|
+
runLocalGraphScan();
|
|
7910
8670
|
}
|
|
7911
8671
|
}
|
|
7912
8672
|
async function checkAllPackages(packages, installedVersions) {
|
|
@@ -7958,14 +8718,16 @@ async function runUpdateAction(opts, globalOpts) {
|
|
|
7958
8718
|
const { hasUpdates, outdated } = await checkAllPackages(packages, installedVersions);
|
|
7959
8719
|
if (!hasUpdates) {
|
|
7960
8720
|
logger.success("All packages are up to date");
|
|
8721
|
+
refreshHooks();
|
|
8722
|
+
await ensureTelemetryIfNeeded();
|
|
7961
8723
|
await offerRegeneration();
|
|
7962
8724
|
process.exit(ExitCode.SUCCESS);
|
|
7963
8725
|
}
|
|
7964
8726
|
console.log("");
|
|
7965
8727
|
for (const { pkg, current, latest } of outdated) {
|
|
7966
8728
|
const shortName = pkg.replace("@harness-engineering/", "");
|
|
7967
|
-
const currentStr = current ?
|
|
7968
|
-
logger.info(`${shortName}: ${currentStr} \u2192 ${
|
|
8729
|
+
const currentStr = current ? chalk12.dim(`v${current}`) : chalk12.dim("not installed");
|
|
8730
|
+
logger.info(`${shortName}: ${currentStr} \u2192 ${chalk12.green(`v${latest}`)}`);
|
|
7969
8731
|
}
|
|
7970
8732
|
console.log("");
|
|
7971
8733
|
}
|
|
@@ -7981,14 +8743,16 @@ async function runUpdateAction(opts, globalOpts) {
|
|
|
7981
8743
|
} catch {
|
|
7982
8744
|
console.log("");
|
|
7983
8745
|
logger.error("Update failed. You can try manually:");
|
|
7984
|
-
console.log(` ${
|
|
8746
|
+
console.log(` ${chalk12.cyan(installCmd)}`);
|
|
7985
8747
|
process.exit(ExitCode.ERROR);
|
|
7986
8748
|
}
|
|
8749
|
+
refreshHooks();
|
|
8750
|
+
await ensureTelemetryIfNeeded();
|
|
7987
8751
|
await offerRegeneration();
|
|
7988
8752
|
process.exit(ExitCode.SUCCESS);
|
|
7989
8753
|
}
|
|
7990
8754
|
function createUpdateCommand() {
|
|
7991
|
-
return new
|
|
8755
|
+
return new Command80("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
8756
|
"--regenerate",
|
|
7993
8757
|
"Only regenerate slash commands and agent definitions (skip package updates)"
|
|
7994
8758
|
).action(async (opts, cmd) => {
|
|
@@ -7998,15 +8762,15 @@ function createUpdateCommand() {
|
|
|
7998
8762
|
}
|
|
7999
8763
|
|
|
8000
8764
|
// src/commands/usage.ts
|
|
8001
|
-
import { Command as
|
|
8765
|
+
import { Command as Command81 } from "commander";
|
|
8002
8766
|
async function loadAndPriceRecords(cwd, includeClaudeSessions = false) {
|
|
8003
|
-
const { readCostRecords, loadPricingData, calculateCost, parseCCRecords } = await import("./dist-
|
|
8767
|
+
const { readCostRecords, loadPricingData, calculateCost, parseCCRecords } = await import("./dist-WCSJHQPK.js");
|
|
8004
8768
|
const records = readCostRecords(cwd);
|
|
8005
8769
|
if (includeClaudeSessions) {
|
|
8006
8770
|
const ccRecords = parseCCRecords();
|
|
8007
8771
|
records.push(...ccRecords);
|
|
8008
8772
|
}
|
|
8009
|
-
if (records.length === 0) return records;
|
|
8773
|
+
if (records.length === 0) return { records, pricingData: null };
|
|
8010
8774
|
const pricingData = await loadPricingData(cwd);
|
|
8011
8775
|
for (const record of records) {
|
|
8012
8776
|
if (record.model && record.costMicroUSD == null) {
|
|
@@ -8014,7 +8778,14 @@ async function loadAndPriceRecords(cwd, includeClaudeSessions = false) {
|
|
|
8014
8778
|
if (cost != null) record.costMicroUSD = cost;
|
|
8015
8779
|
}
|
|
8016
8780
|
}
|
|
8017
|
-
return records;
|
|
8781
|
+
return { records, pricingData };
|
|
8782
|
+
}
|
|
8783
|
+
function computeCacheSavings(cacheReadTokens, model, pricingData) {
|
|
8784
|
+
if (!cacheReadTokens || !model || !pricingData) return null;
|
|
8785
|
+
const pricing = pricingData.get(model);
|
|
8786
|
+
if (!pricing || pricing.cacheReadPer1M == null) return null;
|
|
8787
|
+
const savingsUSD = cacheReadTokens / 1e6 * (pricing.inputPer1M - pricing.cacheReadPer1M);
|
|
8788
|
+
return Math.round(savingsUSD * 1e6);
|
|
8018
8789
|
}
|
|
8019
8790
|
function formatMicroUSD(microUSD) {
|
|
8020
8791
|
if (microUSD == null) return "N/A";
|
|
@@ -8042,7 +8813,10 @@ function registerDailyCommand(usage) {
|
|
|
8042
8813
|
const globalOpts = cmd.optsWithGlobals();
|
|
8043
8814
|
const days = Math.min(Math.max(parseInt(opts.days, 10) || 7, 1), 90);
|
|
8044
8815
|
const cwd = process.cwd();
|
|
8045
|
-
const records = await loadAndPriceRecords(
|
|
8816
|
+
const { records, pricingData } = await loadAndPriceRecords(
|
|
8817
|
+
cwd,
|
|
8818
|
+
globalOpts.includeClaudeSessions
|
|
8819
|
+
);
|
|
8046
8820
|
if (records.length === 0) {
|
|
8047
8821
|
if (globalOpts.json) {
|
|
8048
8822
|
console.log(JSON.stringify([]));
|
|
@@ -8051,23 +8825,25 @@ function registerDailyCommand(usage) {
|
|
|
8051
8825
|
}
|
|
8052
8826
|
return;
|
|
8053
8827
|
}
|
|
8054
|
-
const { aggregateByDay } = await import("./dist-
|
|
8828
|
+
const { aggregateByDay } = await import("./dist-WCSJHQPK.js");
|
|
8055
8829
|
const dailyData = aggregateByDay(records);
|
|
8056
8830
|
const limited = dailyData.slice(0, days);
|
|
8057
8831
|
if (globalOpts.json) {
|
|
8058
8832
|
const enriched = limited.map((day) => {
|
|
8059
8833
|
const hitRate = computeCacheHitRate(day.cacheReadTokens, day.tokens.inputTokens);
|
|
8834
|
+
const savings = computeCacheSavings(day.cacheReadTokens, day.models[0], pricingData);
|
|
8060
8835
|
return {
|
|
8061
8836
|
...day,
|
|
8062
|
-
...hitRate != null ? { cacheHitRate: Math.round(hitRate * 1e3) / 1e3 } : {}
|
|
8837
|
+
...hitRate != null ? { cacheHitRate: Math.round(hitRate * 1e3) / 1e3 } : {},
|
|
8838
|
+
...savings != null ? { cacheSavingsMicroUSD: savings } : {}
|
|
8063
8839
|
};
|
|
8064
8840
|
});
|
|
8065
8841
|
console.log(JSON.stringify(enriched, null, 2));
|
|
8066
8842
|
return;
|
|
8067
8843
|
}
|
|
8068
8844
|
const hasCacheData = limited.some((d) => d.cacheReadTokens != null && d.cacheReadTokens > 0);
|
|
8069
|
-
const cacheHeader = hasCacheData ? " | Cache " : "";
|
|
8070
|
-
const cacheDivider = hasCacheData ? " | ------" : "";
|
|
8845
|
+
const cacheHeader = hasCacheData ? " | Cache | Saved " : "";
|
|
8846
|
+
const cacheDivider = hasCacheData ? " | ------ | -------" : "";
|
|
8071
8847
|
const header = "Date | Sessions | Input | Output | Model(s) | Cost " + cacheHeader;
|
|
8072
8848
|
const divider = "-------------|----------|-----------|-----------|------------------------------|-------" + cacheDivider;
|
|
8073
8849
|
logger.info(header);
|
|
@@ -8081,7 +8857,10 @@ function registerDailyCommand(usage) {
|
|
|
8081
8857
|
const cost = formatMicroUSD(day.costMicroUSD);
|
|
8082
8858
|
const cacheCol = hasCacheData ? (() => {
|
|
8083
8859
|
const rate = computeCacheHitRate(day.cacheReadTokens, day.tokens.inputTokens);
|
|
8084
|
-
|
|
8860
|
+
const savings = computeCacheSavings(day.cacheReadTokens, day.models[0], pricingData);
|
|
8861
|
+
const rateStr = rate != null ? formatPercent(rate).padStart(5) : " -";
|
|
8862
|
+
const savingsStr = savings != null ? formatMicroUSD(savings).padStart(7) : " N/A";
|
|
8863
|
+
return " | " + rateStr + " | " + savingsStr;
|
|
8085
8864
|
})() : "";
|
|
8086
8865
|
logger.info(
|
|
8087
8866
|
`${date} | ${sessions} | ${input} | ${output} | ${models} | ${cost}${cacheCol}`
|
|
@@ -8094,7 +8873,7 @@ function registerSessionsCommand(usage) {
|
|
|
8094
8873
|
const globalOpts = cmd.optsWithGlobals();
|
|
8095
8874
|
const limit = Math.min(Math.max(parseInt(opts.limit, 10) || 10, 1), 100);
|
|
8096
8875
|
const cwd = process.cwd();
|
|
8097
|
-
const records = await loadAndPriceRecords(cwd, globalOpts.includeClaudeSessions);
|
|
8876
|
+
const { records } = await loadAndPriceRecords(cwd, globalOpts.includeClaudeSessions);
|
|
8098
8877
|
if (records.length === 0) {
|
|
8099
8878
|
if (globalOpts.json) {
|
|
8100
8879
|
console.log(JSON.stringify([]));
|
|
@@ -8103,7 +8882,7 @@ function registerSessionsCommand(usage) {
|
|
|
8103
8882
|
}
|
|
8104
8883
|
return;
|
|
8105
8884
|
}
|
|
8106
|
-
const { aggregateBySession } = await import("./dist-
|
|
8885
|
+
const { aggregateBySession } = await import("./dist-WCSJHQPK.js");
|
|
8107
8886
|
const sessionData = aggregateBySession(records);
|
|
8108
8887
|
const limited = sessionData.slice(0, limit);
|
|
8109
8888
|
if (globalOpts.json) {
|
|
@@ -8145,7 +8924,7 @@ function reportSessionNotFound(id, sessionData, useJson) {
|
|
|
8145
8924
|
}
|
|
8146
8925
|
}
|
|
8147
8926
|
}
|
|
8148
|
-
function printSessionDetail(match) {
|
|
8927
|
+
function printSessionDetail(match, pricingData) {
|
|
8149
8928
|
logger.info(`Session: ${match.sessionId}`);
|
|
8150
8929
|
logger.info(`Started: ${match.firstTimestamp}`);
|
|
8151
8930
|
logger.info(`Ended: ${match.lastTimestamp}`);
|
|
@@ -8164,9 +8943,13 @@ function printSessionDetail(match) {
|
|
|
8164
8943
|
}
|
|
8165
8944
|
const hitRate = computeCacheHitRate(match.cacheReadTokens, match.tokens.inputTokens);
|
|
8166
8945
|
if (hitRate != null) {
|
|
8946
|
+
const savings = computeCacheSavings(match.cacheReadTokens, match.model, pricingData);
|
|
8167
8947
|
logger.info("");
|
|
8168
8948
|
logger.info("Cache Performance:");
|
|
8169
8949
|
logger.info(` Cache hit rate: ${formatPercent(hitRate)}`);
|
|
8950
|
+
if (savings != null) {
|
|
8951
|
+
logger.info(` Estimated savings: ${formatMicroUSD(savings)}`);
|
|
8952
|
+
}
|
|
8170
8953
|
}
|
|
8171
8954
|
logger.info("");
|
|
8172
8955
|
logger.info(`Cost: ${formatMicroUSD(match.costMicroUSD)}`);
|
|
@@ -8175,8 +8958,11 @@ function registerSessionCommand(usage) {
|
|
|
8175
8958
|
usage.command("session <id>").description("Show detailed token breakdown for a specific session").action(async (id, _opts, cmd) => {
|
|
8176
8959
|
const globalOpts = cmd.optsWithGlobals();
|
|
8177
8960
|
const cwd = process.cwd();
|
|
8178
|
-
const records = await loadAndPriceRecords(
|
|
8179
|
-
|
|
8961
|
+
const { records, pricingData } = await loadAndPriceRecords(
|
|
8962
|
+
cwd,
|
|
8963
|
+
globalOpts.includeClaudeSessions
|
|
8964
|
+
);
|
|
8965
|
+
const { aggregateBySession } = await import("./dist-WCSJHQPK.js");
|
|
8180
8966
|
const sessionData = aggregateBySession(records);
|
|
8181
8967
|
const match = sessionData.find((s) => s.sessionId === id);
|
|
8182
8968
|
if (!match) {
|
|
@@ -8186,21 +8972,23 @@ function registerSessionCommand(usage) {
|
|
|
8186
8972
|
}
|
|
8187
8973
|
if (globalOpts.json) {
|
|
8188
8974
|
const hitRate = computeCacheHitRate(match.cacheReadTokens, match.tokens.inputTokens);
|
|
8975
|
+
const savings = computeCacheSavings(match.cacheReadTokens, match.model, pricingData);
|
|
8189
8976
|
const enriched = {
|
|
8190
8977
|
...match,
|
|
8191
|
-
...hitRate != null ? { cacheHitRate: Math.round(hitRate * 1e3) / 1e3 } : {}
|
|
8978
|
+
...hitRate != null ? { cacheHitRate: Math.round(hitRate * 1e3) / 1e3 } : {},
|
|
8979
|
+
...savings != null ? { cacheSavingsMicroUSD: savings } : {}
|
|
8192
8980
|
};
|
|
8193
8981
|
console.log(JSON.stringify(enriched, null, 2));
|
|
8194
8982
|
return;
|
|
8195
8983
|
}
|
|
8196
|
-
printSessionDetail(match);
|
|
8984
|
+
printSessionDetail(match, pricingData);
|
|
8197
8985
|
});
|
|
8198
8986
|
}
|
|
8199
8987
|
function registerLatestCommand(usage) {
|
|
8200
8988
|
usage.command("latest").description("Show the most recently completed session cost summary").action(async (_opts, cmd) => {
|
|
8201
8989
|
const globalOpts = cmd.optsWithGlobals();
|
|
8202
8990
|
const cwd = process.cwd();
|
|
8203
|
-
const records = await loadAndPriceRecords(cwd, globalOpts.includeClaudeSessions);
|
|
8991
|
+
const { records } = await loadAndPriceRecords(cwd, globalOpts.includeClaudeSessions);
|
|
8204
8992
|
if (records.length === 0) {
|
|
8205
8993
|
if (globalOpts.json) {
|
|
8206
8994
|
console.log(JSON.stringify({ error: "No usage data found" }));
|
|
@@ -8209,7 +8997,7 @@ function registerLatestCommand(usage) {
|
|
|
8209
8997
|
}
|
|
8210
8998
|
return;
|
|
8211
8999
|
}
|
|
8212
|
-
const { aggregateBySession } = await import("./dist-
|
|
9000
|
+
const { aggregateBySession } = await import("./dist-WCSJHQPK.js");
|
|
8213
9001
|
const sessionData = aggregateBySession(records);
|
|
8214
9002
|
const latest = sessionData[0];
|
|
8215
9003
|
if (!latest) {
|
|
@@ -8235,7 +9023,7 @@ function registerLatestCommand(usage) {
|
|
|
8235
9023
|
});
|
|
8236
9024
|
}
|
|
8237
9025
|
function createUsageCommand() {
|
|
8238
|
-
const usage = new
|
|
9026
|
+
const usage = new Command81("usage").description("Token usage and cost tracking");
|
|
8239
9027
|
usage.option(
|
|
8240
9028
|
"--include-claude-sessions",
|
|
8241
9029
|
"Include Claude Code session data from ~/.claude/projects/"
|
|
@@ -8248,15 +9036,17 @@ function createUsageCommand() {
|
|
|
8248
9036
|
}
|
|
8249
9037
|
|
|
8250
9038
|
// src/commands/validate.ts
|
|
8251
|
-
|
|
8252
|
-
|
|
9039
|
+
init_dist();
|
|
9040
|
+
init_dist();
|
|
9041
|
+
import { Command as Command82 } from "commander";
|
|
9042
|
+
import * as path62 from "path";
|
|
8253
9043
|
async function runValidate(options) {
|
|
8254
9044
|
const configResult = resolveConfig(options.configPath);
|
|
8255
9045
|
if (!configResult.ok) {
|
|
8256
9046
|
return configResult;
|
|
8257
9047
|
}
|
|
8258
9048
|
const config = configResult.value;
|
|
8259
|
-
const cwd = options.cwd ?? (options.configPath ?
|
|
9049
|
+
const cwd = options.cwd ?? (options.configPath ? path62.dirname(path62.resolve(options.configPath)) : process.cwd());
|
|
8260
9050
|
const result = {
|
|
8261
9051
|
valid: true,
|
|
8262
9052
|
checks: {
|
|
@@ -8266,7 +9056,7 @@ async function runValidate(options) {
|
|
|
8266
9056
|
},
|
|
8267
9057
|
issues: []
|
|
8268
9058
|
};
|
|
8269
|
-
const agentsMapPath =
|
|
9059
|
+
const agentsMapPath = path62.resolve(cwd, config.agentsMapPath);
|
|
8270
9060
|
const agentsResult = await validateAgentsMap(agentsMapPath);
|
|
8271
9061
|
if (agentsResult.ok) {
|
|
8272
9062
|
result.checks.agentsMap = true;
|
|
@@ -8302,6 +9092,26 @@ async function runValidate(options) {
|
|
|
8302
9092
|
});
|
|
8303
9093
|
}
|
|
8304
9094
|
result.checks.fileStructure = true;
|
|
9095
|
+
if (options.agentConfigs) {
|
|
9096
|
+
const agentCfg = await validateAgentConfigs(cwd, {
|
|
9097
|
+
strict: options.strict === true,
|
|
9098
|
+
...options.agnixBin !== void 0 && { agnixBin: options.agnixBin }
|
|
9099
|
+
});
|
|
9100
|
+
result.agentConfigs = agentCfg;
|
|
9101
|
+
result.checks.agentConfigs = agentCfg.valid;
|
|
9102
|
+
if (!agentCfg.valid) result.valid = false;
|
|
9103
|
+
for (const finding of agentCfg.issues) {
|
|
9104
|
+
result.issues.push({
|
|
9105
|
+
check: "agentConfigs",
|
|
9106
|
+
file: finding.file,
|
|
9107
|
+
...finding.line !== void 0 && { line: finding.line },
|
|
9108
|
+
ruleId: finding.ruleId,
|
|
9109
|
+
severity: finding.severity,
|
|
9110
|
+
message: finding.message,
|
|
9111
|
+
...finding.suggestion !== void 0 && { suggestion: finding.suggestion }
|
|
9112
|
+
});
|
|
9113
|
+
}
|
|
9114
|
+
}
|
|
8305
9115
|
return Ok(result);
|
|
8306
9116
|
}
|
|
8307
9117
|
function resolveValidateMode(globalOpts) {
|
|
@@ -8311,11 +9121,11 @@ function resolveValidateMode(globalOpts) {
|
|
|
8311
9121
|
return OutputMode.TEXT;
|
|
8312
9122
|
}
|
|
8313
9123
|
async function printCrossCheckWarnings(mode) {
|
|
8314
|
-
const { runCrossCheck: runCrossCheck2 } = await import("./validate-cross-check-
|
|
9124
|
+
const { runCrossCheck: runCrossCheck2 } = await import("./validate-cross-check-ZOWFA3DB.js");
|
|
8315
9125
|
const cwd = process.cwd();
|
|
8316
9126
|
const crossResult = await runCrossCheck2({
|
|
8317
|
-
specsDir:
|
|
8318
|
-
plansDir:
|
|
9127
|
+
specsDir: path62.join(cwd, "docs", "specs"),
|
|
9128
|
+
plansDir: path62.join(cwd, "docs", "plans"),
|
|
8319
9129
|
projectPath: cwd
|
|
8320
9130
|
});
|
|
8321
9131
|
if (!crossResult.ok || crossResult.value.warnings === 0) return;
|
|
@@ -8327,37 +9137,59 @@ async function printCrossCheckWarnings(mode) {
|
|
|
8327
9137
|
${crossResult.value.warnings} warnings`);
|
|
8328
9138
|
}
|
|
8329
9139
|
function createValidateCommand3() {
|
|
8330
|
-
const command = new
|
|
8331
|
-
|
|
8332
|
-
|
|
8333
|
-
|
|
8334
|
-
const result = await runValidate({
|
|
8335
|
-
configPath: globalOpts.config,
|
|
8336
|
-
json: globalOpts.json,
|
|
8337
|
-
verbose: globalOpts.verbose,
|
|
8338
|
-
quiet: globalOpts.quiet
|
|
8339
|
-
});
|
|
8340
|
-
if (!result.ok) {
|
|
8341
|
-
if (mode === OutputMode.JSON) console.log(JSON.stringify({ error: result.error.message }));
|
|
8342
|
-
else logger.error(result.error.message);
|
|
8343
|
-
process.exit(result.error.exitCode);
|
|
8344
|
-
}
|
|
8345
|
-
if (opts.crossCheck) await printCrossCheckWarnings(mode);
|
|
8346
|
-
const output = formatter.formatValidation({
|
|
8347
|
-
valid: result.value.valid,
|
|
8348
|
-
issues: result.value.issues
|
|
8349
|
-
});
|
|
8350
|
-
if (output) console.log(output);
|
|
8351
|
-
process.exit(result.value.valid ? ExitCode.SUCCESS : ExitCode.VALIDATION_FAILED);
|
|
8352
|
-
});
|
|
9140
|
+
const command = new Command82("validate").description("Run all validation checks").option("--cross-check", "Run cross-artifact consistency validation").option(
|
|
9141
|
+
"--agent-configs",
|
|
9142
|
+
"Validate agent configs (CLAUDE.md, hooks, skills) via agnix or built-in fallback rules"
|
|
9143
|
+
).option("--strict", "Treat warnings as errors (applies to --agent-configs)").option("--agnix-bin <path>", "Override the agnix binary path discovered on PATH").action(async (opts, cmd) => runValidateAction(opts, cmd.optsWithGlobals()));
|
|
8353
9144
|
return command;
|
|
8354
9145
|
}
|
|
9146
|
+
async function runValidateAction(opts, globalOpts) {
|
|
9147
|
+
const mode = resolveValidateMode(globalOpts);
|
|
9148
|
+
const formatter = new OutputFormatter(mode);
|
|
9149
|
+
const result = await runValidate({
|
|
9150
|
+
...typeof globalOpts.config === "string" && { configPath: globalOpts.config },
|
|
9151
|
+
json: globalOpts.json === true,
|
|
9152
|
+
verbose: globalOpts.verbose === true,
|
|
9153
|
+
quiet: globalOpts.quiet === true,
|
|
9154
|
+
agentConfigs: opts.agentConfigs === true,
|
|
9155
|
+
strict: opts.strict === true,
|
|
9156
|
+
...typeof opts.agnixBin === "string" && { agnixBin: opts.agnixBin }
|
|
9157
|
+
});
|
|
9158
|
+
if (!result.ok) {
|
|
9159
|
+
if (mode === OutputMode.JSON) console.log(JSON.stringify({ error: result.error.message }));
|
|
9160
|
+
else logger.error(result.error.message);
|
|
9161
|
+
process.exit(result.error.exitCode);
|
|
9162
|
+
}
|
|
9163
|
+
if (opts.crossCheck) await printCrossCheckWarnings(mode);
|
|
9164
|
+
emitValidateOutput(result.value, mode, formatter);
|
|
9165
|
+
process.exit(result.value.valid ? ExitCode.SUCCESS : ExitCode.VALIDATION_FAILED);
|
|
9166
|
+
}
|
|
9167
|
+
function emitValidateOutput(value, mode, formatter) {
|
|
9168
|
+
if (mode === OutputMode.JSON) {
|
|
9169
|
+
console.log(JSON.stringify(value, null, 2));
|
|
9170
|
+
return;
|
|
9171
|
+
}
|
|
9172
|
+
const output = formatter.formatValidation({ valid: value.valid, issues: value.issues });
|
|
9173
|
+
if (output) console.log(output);
|
|
9174
|
+
if (value.agentConfigs) printAgentConfigSummary(value.agentConfigs, mode);
|
|
9175
|
+
}
|
|
9176
|
+
function printAgentConfigSummary(cfg, mode) {
|
|
9177
|
+
if (mode === OutputMode.QUIET) return;
|
|
9178
|
+
const engineLabel = cfg.engine === "agnix" ? "agnix" : "built-in fallback rules";
|
|
9179
|
+
const note = cfg.fellBackBecause ? ` (${cfg.fellBackBecause})` : "";
|
|
9180
|
+
console.log(`
|
|
9181
|
+
Agent configs checked via ${engineLabel}${note}`);
|
|
9182
|
+
if (cfg.engine === "fallback" && cfg.fellBackBecause === "binary-not-found") {
|
|
9183
|
+
console.log(" Install agnix for 385+ rule coverage: https://github.com/agent-sh/agnix");
|
|
9184
|
+
}
|
|
9185
|
+
}
|
|
8355
9186
|
|
|
8356
9187
|
// src/commands/_registry.ts
|
|
8357
9188
|
var commandCreators = [
|
|
8358
9189
|
createAddCommand,
|
|
8359
9190
|
createAdoptionCommand,
|
|
8360
9191
|
createAgentCommand,
|
|
9192
|
+
createAuditProtectedCommand,
|
|
8361
9193
|
createBlueprintCommand,
|
|
8362
9194
|
createCheckArchCommand,
|
|
8363
9195
|
createCheckDepsCommand,
|
|
@@ -8390,6 +9222,7 @@ var commandCreators = [
|
|
|
8390
9222
|
createPerfCommand,
|
|
8391
9223
|
createPersonaCommand,
|
|
8392
9224
|
createPredictCommand,
|
|
9225
|
+
createPublishAnalysesCommand,
|
|
8393
9226
|
createQueryCommand,
|
|
8394
9227
|
createRecommendCommand,
|
|
8395
9228
|
createScanCommand,
|
|
@@ -8400,6 +9233,7 @@ var commandCreators = [
|
|
|
8400
9233
|
createSkillCommand,
|
|
8401
9234
|
createSnapshotCommand,
|
|
8402
9235
|
createStateCommand,
|
|
9236
|
+
createSyncAnalysesCommand,
|
|
8403
9237
|
createTaintCommand,
|
|
8404
9238
|
createTelemetryCommand,
|
|
8405
9239
|
createTraceabilityCommand,
|
|
@@ -8412,7 +9246,7 @@ var commandCreators = [
|
|
|
8412
9246
|
|
|
8413
9247
|
// src/index.ts
|
|
8414
9248
|
function createProgram() {
|
|
8415
|
-
const program = new
|
|
9249
|
+
const program = new Command83();
|
|
8416
9250
|
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
9251
|
for (const creator of commandCreators) {
|
|
8418
9252
|
program.addCommand(creator());
|
|
@@ -8424,7 +9258,6 @@ export {
|
|
|
8424
9258
|
runCheckArch,
|
|
8425
9259
|
runGraphStatus,
|
|
8426
9260
|
runGraphExport,
|
|
8427
|
-
runScan,
|
|
8428
9261
|
runQuery,
|
|
8429
9262
|
runIngest,
|
|
8430
9263
|
runImpactPreview,
|