@harness-engineering/cli 1.24.3 → 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-FBSLURIB.js → architecture-HNIO6AUX.js} +6 -5
- package/dist/{assess-project-74UVWPMB.js → assess-project-6MV5TNY3.js} +2 -1
- package/dist/bin/harness-mcp.js +18 -17
- package/dist/bin/harness.js +47 -31
- package/dist/{check-phase-gate-WY6UICCL.js → check-phase-gate-VCBQHQAC.js} +6 -5
- package/dist/{chunk-Q3XYV5UC.js → chunk-47N6R2F4.js} +5 -1
- package/dist/{chunk-3XAPHB2Z.js → chunk-4NK7Z3BP.js} +1 -1
- 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-ZAMT24QN.js → chunk-AZT567I6.js} +1525 -735
- package/dist/{chunk-TDLOFNNQ.js → chunk-BOQRTARD.js} +1 -1
- package/dist/{chunk-A737JDL4.js → chunk-CXJTVICF.js} +4 -4
- package/dist/{chunk-LOUH2LIC.js → chunk-EHRZMOQ2.js} +5 -1
- package/dist/{chunk-L5UONZ53.js → chunk-F23H3U5U.js} +6 -2
- package/dist/{chunk-YPKKEP3O.js → chunk-FES2YEQU.js} +9 -9
- package/dist/{chunk-SPTKLCKC.js → chunk-FIQL2HND.js} +6 -2
- package/dist/{chunk-FP53DDB5.js → chunk-FSNPBT74.js} +5 -1
- package/dist/{chunk-TB427QOK.js → chunk-GEEYCQDS.js} +13 -9
- 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-V2FGX2KD.js → chunk-M55DGGF3.js} +9 -5
- package/dist/{chunk-H4U2QNY2.js → chunk-MI6MA6OP.js} +16888 -13143
- 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-O6UF33QH.js → chunk-RX7TUMBR.js} +320 -136
- package/dist/{chunk-3VYWO6QN.js → chunk-TYV4EUAD.js} +12 -8
- package/dist/{chunk-FJYP32IV.js → chunk-UQZBZINS.js} +6 -6
- package/dist/{chunk-UFQQBGC3.js → chunk-UV3BZMGT.js} +64 -3
- package/dist/{chunk-5PMRARB5.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-WEN5Z7CL.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-7EBSGAHX.js → dist-WCSJHQPK.js} +106 -3
- package/dist/{docs-H34GBVRS.js → docs-UBOGGHTY.js} +6 -5
- package/dist/engine-MJJAP5CH.js +10 -0
- package/dist/{entropy-ZAY73R6A.js → entropy-EMSXF2PX.js} +5 -4
- package/dist/{feedback-TMEGYMWU.js → feedback-ZLUX72HD.js} +2 -1
- package/dist/{generate-agent-definitions-PQPG6SX5.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 +13 -7
- package/dist/index.js +30 -29
- package/dist/loader-JVSJZSWZ.js +12 -0
- package/dist/mcp-2553PNUC.js +38 -0
- package/dist/{performance-N67YJJDG.js → performance-7AGWJUY4.js} +6 -5
- 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-U67OKDRS.js → scan-MPJ6JHUY.js} +2 -1
- 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 +4 -4
- package/dist/agents-md-PBKKTSQY.js +0 -9
- package/dist/chunk-5LMZA5LZ.js +0 -38
- package/dist/chunk-OZIHPMA7.js +0 -5171
- package/dist/ci-workflow-QZRHAIO2.js +0 -9
- package/dist/engine-VUQEAJFZ.js +0 -9
- package/dist/loader-Y6A42WBD.js +0 -11
- package/dist/mcp-LCHC4NZ5.js +0 -37
- package/dist/review-pipeline-YXF5ITL2.js +0 -12
- package/dist/runtime-XNJUJCSG.js +0 -10
- package/dist/security-L2YN3CTI.js +0 -9
- package/dist/skill-executor-GA7BDX3F.js +0 -8
- package/dist/validate-MNE25KLZ.js +0 -13
- package/dist/validate-cross-check-Y4PDR63C.js +0 -9
|
@@ -1,25 +1,25 @@
|
|
|
1
1
|
import {
|
|
2
2
|
generateAgentsMd
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-5BQ5BOJL.js";
|
|
4
4
|
import {
|
|
5
5
|
generateCIWorkflow
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-EHRZMOQ2.js";
|
|
7
7
|
import {
|
|
8
8
|
TemplateEngine
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-FSNPBT74.js";
|
|
10
10
|
import {
|
|
11
11
|
generate,
|
|
12
12
|
validate
|
|
13
13
|
} from "./chunk-CQ553GZO.js";
|
|
14
14
|
import {
|
|
15
15
|
generateRuntime
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-6VZQJ5CX.js";
|
|
17
17
|
import {
|
|
18
18
|
runPersona
|
|
19
19
|
} from "./chunk-GISMXMVL.js";
|
|
20
20
|
import {
|
|
21
21
|
executeSkill
|
|
22
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-WWACIIBA.js";
|
|
23
23
|
import {
|
|
24
24
|
ALLOWED_PERSONA_COMMANDS
|
|
25
25
|
} from "./chunk-TEFCFC4H.js";
|
|
@@ -28,21 +28,21 @@ import {
|
|
|
28
28
|
OutputMode,
|
|
29
29
|
createCheckPhaseGateCommand,
|
|
30
30
|
findFiles
|
|
31
|
-
} from "./chunk-
|
|
31
|
+
} from "./chunk-FIQL2HND.js";
|
|
32
32
|
import {
|
|
33
33
|
createGenerateAgentDefinitionsCommand,
|
|
34
34
|
generateAgentDefinitions
|
|
35
|
-
} from "./chunk-
|
|
35
|
+
} from "./chunk-AT74HEQM.js";
|
|
36
36
|
import {
|
|
37
37
|
toKebabCase
|
|
38
38
|
} from "./chunk-KET4QQZB.js";
|
|
39
39
|
import {
|
|
40
40
|
listPersonas,
|
|
41
41
|
loadPersona
|
|
42
|
-
} from "./chunk-
|
|
42
|
+
} from "./chunk-P7PANON5.js";
|
|
43
43
|
import {
|
|
44
44
|
createScanCommand
|
|
45
|
-
} from "./chunk-
|
|
45
|
+
} from "./chunk-4NK7Z3BP.js";
|
|
46
46
|
import {
|
|
47
47
|
appendFrameworkAgents,
|
|
48
48
|
captureHealthSnapshot,
|
|
@@ -56,11 +56,11 @@ import {
|
|
|
56
56
|
loadOrRebuildIndex,
|
|
57
57
|
persistToolingConfig,
|
|
58
58
|
recommend
|
|
59
|
-
} from "./chunk-
|
|
59
|
+
} from "./chunk-RX7TUMBR.js";
|
|
60
60
|
import {
|
|
61
61
|
findConfigFile,
|
|
62
62
|
resolveConfig
|
|
63
|
-
} from "./chunk-
|
|
63
|
+
} from "./chunk-WIQA4BSH.js";
|
|
64
64
|
import {
|
|
65
65
|
VALID_PLATFORMS
|
|
66
66
|
} from "./chunk-3ISINLYT.js";
|
|
@@ -74,7 +74,7 @@ import {
|
|
|
74
74
|
} from "./chunk-6B6UN6SG.js";
|
|
75
75
|
import {
|
|
76
76
|
loadGraphStore
|
|
77
|
-
} from "./chunk-
|
|
77
|
+
} from "./chunk-BOQRTARD.js";
|
|
78
78
|
import {
|
|
79
79
|
CLI_VERSION
|
|
80
80
|
} from "./chunk-BM3PWGXQ.js";
|
|
@@ -84,11 +84,14 @@ import {
|
|
|
84
84
|
BaselineManager,
|
|
85
85
|
BlueprintGenerator,
|
|
86
86
|
BundleSchema,
|
|
87
|
+
CINotifier,
|
|
87
88
|
CriticalPathResolver,
|
|
88
89
|
EntropyAnalyzer,
|
|
90
|
+
GitHubIssuesSyncAdapter,
|
|
89
91
|
PredictionEngine,
|
|
90
92
|
ProjectScanner,
|
|
91
93
|
SecurityScanner,
|
|
94
|
+
SecurityTimelineManager,
|
|
92
95
|
SpecImpactEstimator,
|
|
93
96
|
TimelineManager,
|
|
94
97
|
TypeScriptParser,
|
|
@@ -110,16 +113,20 @@ import {
|
|
|
110
113
|
detectDeadCode,
|
|
111
114
|
detectDocDrift,
|
|
112
115
|
diff,
|
|
116
|
+
dist_exports,
|
|
113
117
|
extractBundle,
|
|
114
118
|
generateSuggestions,
|
|
115
119
|
getOrCreateInstallId,
|
|
120
|
+
init_dist,
|
|
116
121
|
listStreams,
|
|
117
122
|
listTaintedSessions,
|
|
118
123
|
loadState,
|
|
119
124
|
loadStreamIndex,
|
|
125
|
+
loadTrackerSyncConfig,
|
|
120
126
|
mapInjectionFindings,
|
|
121
127
|
mapSecurityFindings,
|
|
122
128
|
parseDiff,
|
|
129
|
+
parseFileRegions,
|
|
123
130
|
parseManifest,
|
|
124
131
|
parseSecurityConfig,
|
|
125
132
|
pruneLearnings,
|
|
@@ -135,35 +142,41 @@ import {
|
|
|
135
142
|
runReviewPipeline,
|
|
136
143
|
scanForInjection,
|
|
137
144
|
setActiveStream,
|
|
145
|
+
validateAgentConfigs,
|
|
138
146
|
validateAgentsMap,
|
|
139
147
|
validateDependencies,
|
|
140
148
|
validateKnowledgeMap,
|
|
141
149
|
writeConfig,
|
|
142
150
|
writeLockfile
|
|
143
|
-
} from "./chunk-
|
|
151
|
+
} from "./chunk-MI6MA6OP.js";
|
|
144
152
|
import {
|
|
145
153
|
Err,
|
|
146
154
|
Ok
|
|
147
|
-
} from "./chunk-
|
|
155
|
+
} from "./chunk-RL4VMEXL.js";
|
|
148
156
|
import {
|
|
149
157
|
createCreateSkillCommand
|
|
150
|
-
} from "./chunk-
|
|
158
|
+
} from "./chunk-RRHUCDRD.js";
|
|
151
159
|
import {
|
|
152
160
|
logger
|
|
153
161
|
} from "./chunk-EBJQ6N4M.js";
|
|
154
162
|
import {
|
|
155
163
|
SkillMetadataSchema
|
|
156
|
-
} from "./chunk-
|
|
164
|
+
} from "./chunk-HIK6OEUF.js";
|
|
157
165
|
import {
|
|
158
166
|
CLIError,
|
|
159
167
|
ExitCode,
|
|
160
168
|
handleError
|
|
161
169
|
} from "./chunk-3WGJMBKH.js";
|
|
170
|
+
import {
|
|
171
|
+
__require,
|
|
172
|
+
__toCommonJS
|
|
173
|
+
} from "./chunk-KFQGP6VL.js";
|
|
162
174
|
|
|
163
175
|
// src/index.ts
|
|
164
|
-
import { Command as
|
|
176
|
+
import { Command as Command83 } from "commander";
|
|
165
177
|
|
|
166
178
|
// src/commands/add.ts
|
|
179
|
+
init_dist();
|
|
167
180
|
import { Command } from "commander";
|
|
168
181
|
import * as fs from "fs";
|
|
169
182
|
import * as path from "path";
|
|
@@ -251,7 +264,7 @@ function addDoc(name, cwd, configResult, created) {
|
|
|
251
264
|
return Ok(void 0);
|
|
252
265
|
}
|
|
253
266
|
async function addSkill(name, cwd, created) {
|
|
254
|
-
const { generateSkillFiles: generateSkillFiles2 } = await import("./create-skill-
|
|
267
|
+
const { generateSkillFiles: generateSkillFiles2 } = await import("./create-skill-6QWJHQYS.js");
|
|
255
268
|
generateSkillFiles2({
|
|
256
269
|
name,
|
|
257
270
|
description: `${name} skill`,
|
|
@@ -349,7 +362,7 @@ function registerSkillsCommand(adoption) {
|
|
|
349
362
|
const globalOpts = cmd.optsWithGlobals();
|
|
350
363
|
const limit = Math.max(parseInt(opts.limit, 10) || 20, 1);
|
|
351
364
|
const cwd = process.cwd();
|
|
352
|
-
const { readAdoptionRecords, aggregateBySkill } = await import("./dist-
|
|
365
|
+
const { readAdoptionRecords, aggregateBySkill } = await import("./dist-WCSJHQPK.js");
|
|
353
366
|
const records = readAdoptionRecords(cwd);
|
|
354
367
|
if (records.length === 0) {
|
|
355
368
|
if (globalOpts.json) {
|
|
@@ -385,7 +398,7 @@ function registerRecentCommand(adoption) {
|
|
|
385
398
|
const globalOpts = cmd.optsWithGlobals();
|
|
386
399
|
const limit = Math.max(parseInt(opts.limit, 10) || 20, 1);
|
|
387
400
|
const cwd = process.cwd();
|
|
388
|
-
const { readAdoptionRecords } = await import("./dist-
|
|
401
|
+
const { readAdoptionRecords } = await import("./dist-WCSJHQPK.js");
|
|
389
402
|
const records = readAdoptionRecords(cwd);
|
|
390
403
|
if (records.length === 0) {
|
|
391
404
|
if (globalOpts.json) {
|
|
@@ -422,7 +435,7 @@ function registerSkillCommand(adoption) {
|
|
|
422
435
|
adoption.command("skill <name>").description("Show detail for a specific skill").action(async (name, _opts, cmd) => {
|
|
423
436
|
const globalOpts = cmd.optsWithGlobals();
|
|
424
437
|
const cwd = process.cwd();
|
|
425
|
-
const { readAdoptionRecords, aggregateBySkill } = await import("./dist-
|
|
438
|
+
const { readAdoptionRecords, aggregateBySkill } = await import("./dist-WCSJHQPK.js");
|
|
426
439
|
const records = readAdoptionRecords(cwd);
|
|
427
440
|
const skillRecords = records.filter((r) => r.skill === name);
|
|
428
441
|
if (skillRecords.length === 0) {
|
|
@@ -488,6 +501,8 @@ function createAdoptionCommand() {
|
|
|
488
501
|
import { Command as Command5 } from "commander";
|
|
489
502
|
|
|
490
503
|
// src/commands/agent/run.ts
|
|
504
|
+
init_dist();
|
|
505
|
+
init_dist();
|
|
491
506
|
import { Command as Command3 } from "commander";
|
|
492
507
|
import * as path2 from "path";
|
|
493
508
|
import * as childProcess from "child_process";
|
|
@@ -609,6 +624,7 @@ function createRunCommand() {
|
|
|
609
624
|
}
|
|
610
625
|
|
|
611
626
|
// src/commands/agent/review.ts
|
|
627
|
+
init_dist();
|
|
612
628
|
import { Command as Command4 } from "commander";
|
|
613
629
|
import { execSync } from "child_process";
|
|
614
630
|
async function runAgentReview(options) {
|
|
@@ -657,7 +673,9 @@ async function runAgentReview(options) {
|
|
|
657
673
|
comment: options.comment ?? false,
|
|
658
674
|
ci: options.ci ?? false,
|
|
659
675
|
deep: options.deep ?? false,
|
|
660
|
-
noMechanical: options.noMechanical ?? false
|
|
676
|
+
noMechanical: options.noMechanical ?? false,
|
|
677
|
+
...options.thorough ? { thorough: true } : {},
|
|
678
|
+
...options.isolated ? { isolated: true } : {}
|
|
661
679
|
},
|
|
662
680
|
config
|
|
663
681
|
});
|
|
@@ -703,7 +721,10 @@ function printReviewResult(result, mode) {
|
|
|
703
721
|
}
|
|
704
722
|
}
|
|
705
723
|
function createReviewCommand() {
|
|
706
|
-
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) => {
|
|
707
728
|
const globalOpts = cmd.optsWithGlobals();
|
|
708
729
|
const mode = resolveReviewMode(globalOpts);
|
|
709
730
|
const result = await runAgentReview({
|
|
@@ -714,7 +735,9 @@ function createReviewCommand() {
|
|
|
714
735
|
comment: opts.comment,
|
|
715
736
|
ci: opts.ci,
|
|
716
737
|
deep: opts.deep,
|
|
717
|
-
noMechanical: opts.mechanical === false
|
|
738
|
+
noMechanical: opts.mechanical === false,
|
|
739
|
+
thorough: opts.thorough,
|
|
740
|
+
isolated: opts.isolated
|
|
718
741
|
});
|
|
719
742
|
if (!result.ok) {
|
|
720
743
|
if (mode === OutputMode.JSON) console.log(JSON.stringify({ error: result.error.message }));
|
|
@@ -737,21 +760,130 @@ function createAgentCommand() {
|
|
|
737
760
|
return command;
|
|
738
761
|
}
|
|
739
762
|
|
|
740
|
-
// src/commands/
|
|
763
|
+
// src/commands/audit-protected.ts
|
|
764
|
+
init_dist();
|
|
741
765
|
import { Command as Command6 } from "commander";
|
|
742
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";
|
|
743
875
|
function createBlueprintCommand() {
|
|
744
|
-
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) => {
|
|
745
877
|
try {
|
|
746
|
-
const rootDir =
|
|
747
|
-
const outputDir =
|
|
878
|
+
const rootDir = path4.resolve(projectPath);
|
|
879
|
+
const outputDir = path4.resolve(options.output);
|
|
748
880
|
logger.info(`Scanning project at ${rootDir}...`);
|
|
749
881
|
const scanner = new ProjectScanner(rootDir);
|
|
750
882
|
const data = await scanner.scan();
|
|
751
883
|
logger.info(`Generating blueprint to ${outputDir}...`);
|
|
752
884
|
const generator = new BlueprintGenerator();
|
|
753
885
|
await generator.generate(data, { outputDir });
|
|
754
|
-
logger.success(`Blueprint generated successfully at ${
|
|
886
|
+
logger.success(`Blueprint generated successfully at ${path4.join(outputDir, "index.html")}`);
|
|
755
887
|
} catch (error) {
|
|
756
888
|
logger.error(
|
|
757
889
|
`Failed to generate blueprint: ${error instanceof Error ? error.message : String(error)}`
|
|
@@ -762,7 +894,8 @@ function createBlueprintCommand() {
|
|
|
762
894
|
}
|
|
763
895
|
|
|
764
896
|
// src/commands/check-arch.ts
|
|
765
|
-
|
|
897
|
+
init_dist();
|
|
898
|
+
import { Command as Command8 } from "commander";
|
|
766
899
|
import { execSync as execSync2 } from "child_process";
|
|
767
900
|
function getCommitHash(cwd) {
|
|
768
901
|
try {
|
|
@@ -856,7 +989,7 @@ async function runCheckArch(options) {
|
|
|
856
989
|
thresholdViolations
|
|
857
990
|
});
|
|
858
991
|
}
|
|
859
|
-
function
|
|
992
|
+
function resolveOutputMode2(globalOpts) {
|
|
860
993
|
if (globalOpts.json) return OutputMode.JSON;
|
|
861
994
|
if (globalOpts.quiet) return OutputMode.QUIET;
|
|
862
995
|
if (globalOpts.verbose) return OutputMode.VERBOSE;
|
|
@@ -892,9 +1025,9 @@ function printArchResult(value, mode, formatter) {
|
|
|
892
1025
|
if (output) console.log(output);
|
|
893
1026
|
}
|
|
894
1027
|
function createCheckArchCommand() {
|
|
895
|
-
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) => {
|
|
896
1029
|
const globalOpts = cmd.optsWithGlobals();
|
|
897
|
-
const mode =
|
|
1030
|
+
const mode = resolveOutputMode2(globalOpts);
|
|
898
1031
|
const formatter = new OutputFormatter(mode);
|
|
899
1032
|
const result = await runCheckArch({
|
|
900
1033
|
configPath: globalOpts.config,
|
|
@@ -922,8 +1055,10 @@ function createCheckArchCommand() {
|
|
|
922
1055
|
}
|
|
923
1056
|
|
|
924
1057
|
// src/commands/check-deps.ts
|
|
925
|
-
|
|
926
|
-
|
|
1058
|
+
init_dist();
|
|
1059
|
+
init_dist();
|
|
1060
|
+
import { Command as Command9 } from "commander";
|
|
1061
|
+
import * as path5 from "path";
|
|
927
1062
|
async function runCheckDeps(options) {
|
|
928
1063
|
const cwd = options.cwd ?? process.cwd();
|
|
929
1064
|
const configResult = resolveConfig(options.configPath);
|
|
@@ -939,7 +1074,7 @@ async function runCheckDeps(options) {
|
|
|
939
1074
|
if (!config.layers || config.layers.length === 0) {
|
|
940
1075
|
return Ok(result);
|
|
941
1076
|
}
|
|
942
|
-
const rootDir =
|
|
1077
|
+
const rootDir = path5.resolve(cwd, config.rootDir);
|
|
943
1078
|
const parser = new TypeScriptParser();
|
|
944
1079
|
const layers = config.layers.map((l) => defineLayer(l.name, [l.pattern], l.allowedDependencies));
|
|
945
1080
|
const layerConfig = {
|
|
@@ -1014,25 +1149,17 @@ async function runCheckDepsAction(globalOpts) {
|
|
|
1014
1149
|
process.exit(result.value.valid ? ExitCode.SUCCESS : ExitCode.VALIDATION_FAILED);
|
|
1015
1150
|
}
|
|
1016
1151
|
function createCheckDepsCommand() {
|
|
1017
|
-
const command = new
|
|
1152
|
+
const command = new Command9("check-deps").description("Validate dependency layers and detect circular dependencies").action(async (_opts, cmd) => {
|
|
1018
1153
|
await runCheckDepsAction(cmd.optsWithGlobals());
|
|
1019
1154
|
});
|
|
1020
1155
|
return command;
|
|
1021
1156
|
}
|
|
1022
1157
|
|
|
1023
1158
|
// src/commands/check-docs.ts
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
function resolveOutputMode2(globalOpts) {
|
|
1029
|
-
if (globalOpts.json) return OutputMode.JSON;
|
|
1030
|
-
if (globalOpts.quiet) return OutputMode.QUIET;
|
|
1031
|
-
if (globalOpts.verbose) return OutputMode.VERBOSE;
|
|
1032
|
-
return OutputMode.TEXT;
|
|
1033
|
-
}
|
|
1034
|
-
|
|
1035
|
-
// src/commands/check-docs.ts
|
|
1159
|
+
init_dist();
|
|
1160
|
+
init_dist();
|
|
1161
|
+
import { Command as Command10 } from "commander";
|
|
1162
|
+
import * as path6 from "path";
|
|
1036
1163
|
async function runCheckDocs(options) {
|
|
1037
1164
|
const cwd = options.cwd ?? process.cwd();
|
|
1038
1165
|
const minCoverage = options.minCoverage ?? 80;
|
|
@@ -1041,8 +1168,8 @@ async function runCheckDocs(options) {
|
|
|
1041
1168
|
return configResult;
|
|
1042
1169
|
}
|
|
1043
1170
|
const config = configResult.value;
|
|
1044
|
-
const docsDir =
|
|
1045
|
-
const sourceDir =
|
|
1171
|
+
const docsDir = path6.resolve(cwd, config.docsDir);
|
|
1172
|
+
const sourceDir = path6.resolve(cwd, config.rootDir);
|
|
1046
1173
|
const coverageResult = await checkDocCoverage("project", {
|
|
1047
1174
|
docsDir,
|
|
1048
1175
|
sourceDir,
|
|
@@ -1109,9 +1236,9 @@ function printCheckDocsResult(value, mode, formatter) {
|
|
|
1109
1236
|
}
|
|
1110
1237
|
}
|
|
1111
1238
|
function createCheckDocsCommand() {
|
|
1112
|
-
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) => {
|
|
1113
1240
|
const globalOpts = cmd.optsWithGlobals();
|
|
1114
|
-
const mode =
|
|
1241
|
+
const mode = resolveOutputMode(globalOpts);
|
|
1115
1242
|
const formatter = new OutputFormatter(mode);
|
|
1116
1243
|
const result = await runCheckDocs({
|
|
1117
1244
|
configPath: globalOpts.config,
|
|
@@ -1139,12 +1266,13 @@ function createCheckDocsCommand() {
|
|
|
1139
1266
|
}
|
|
1140
1267
|
|
|
1141
1268
|
// src/commands/check-perf.ts
|
|
1142
|
-
|
|
1143
|
-
import
|
|
1269
|
+
init_dist();
|
|
1270
|
+
import { Command as Command11 } from "commander";
|
|
1271
|
+
import * as path7 from "path";
|
|
1144
1272
|
async function runCheckPerf(cwd, options) {
|
|
1145
1273
|
const runAll2 = !options.structural && !options.size && !options.coupling;
|
|
1146
1274
|
const analyzer = new EntropyAnalyzer({
|
|
1147
|
-
rootDir:
|
|
1275
|
+
rootDir: path7.resolve(cwd),
|
|
1148
1276
|
analyze: {
|
|
1149
1277
|
complexity: runAll2 || !!options.structural,
|
|
1150
1278
|
coupling: runAll2 || !!options.coupling,
|
|
@@ -1256,15 +1384,16 @@ async function runCheckPerfAction(opts, globalOpts) {
|
|
|
1256
1384
|
process.exit(result.value.valid ? ExitCode.SUCCESS : ExitCode.VALIDATION_FAILED);
|
|
1257
1385
|
}
|
|
1258
1386
|
function createCheckPerfCommand() {
|
|
1259
|
-
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) => {
|
|
1260
1388
|
await runCheckPerfAction(opts, cmd.optsWithGlobals());
|
|
1261
1389
|
});
|
|
1262
1390
|
return command;
|
|
1263
1391
|
}
|
|
1264
1392
|
|
|
1265
1393
|
// src/commands/check-security.ts
|
|
1266
|
-
|
|
1267
|
-
import
|
|
1394
|
+
init_dist();
|
|
1395
|
+
import { Command as Command12 } from "commander";
|
|
1396
|
+
import * as path8 from "path";
|
|
1268
1397
|
import { execSync as execSync3 } from "child_process";
|
|
1269
1398
|
var SEVERITY_RANK = {
|
|
1270
1399
|
error: 3,
|
|
@@ -1277,19 +1406,19 @@ function getChangedFiles(cwd) {
|
|
|
1277
1406
|
cwd,
|
|
1278
1407
|
encoding: "utf-8"
|
|
1279
1408
|
});
|
|
1280
|
-
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));
|
|
1281
1410
|
} catch {
|
|
1282
1411
|
return [];
|
|
1283
1412
|
}
|
|
1284
1413
|
}
|
|
1285
1414
|
async function runCheckSecurity(cwd, options) {
|
|
1286
|
-
const projectRoot =
|
|
1415
|
+
const projectRoot = path8.resolve(cwd);
|
|
1287
1416
|
let configData = {};
|
|
1288
1417
|
try {
|
|
1289
|
-
const
|
|
1290
|
-
const configPath =
|
|
1291
|
-
if (
|
|
1292
|
-
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");
|
|
1293
1422
|
const parsed = JSON.parse(raw);
|
|
1294
1423
|
configData = parsed.security ?? {};
|
|
1295
1424
|
}
|
|
@@ -1302,7 +1431,7 @@ async function runCheckSecurity(cwd, options) {
|
|
|
1302
1431
|
if (options.changedOnly) {
|
|
1303
1432
|
filesToScan = getChangedFiles(projectRoot);
|
|
1304
1433
|
} else {
|
|
1305
|
-
const { glob } = await import("glob");
|
|
1434
|
+
const { glob: glob2 } = await import("glob");
|
|
1306
1435
|
const pattern = "**/*.{ts,tsx,js,jsx,go,py,java,rb}";
|
|
1307
1436
|
const ignore = securityConfig.exclude ?? [
|
|
1308
1437
|
"**/node_modules/**",
|
|
@@ -1310,9 +1439,19 @@ async function runCheckSecurity(cwd, options) {
|
|
|
1310
1439
|
"**/*.test.ts",
|
|
1311
1440
|
"**/fixtures/**"
|
|
1312
1441
|
];
|
|
1313
|
-
filesToScan = await
|
|
1442
|
+
filesToScan = await glob2(pattern, { cwd: projectRoot, absolute: true, ignore });
|
|
1314
1443
|
}
|
|
1315
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
|
+
}
|
|
1316
1455
|
const threshold = options.severity ?? "warning";
|
|
1317
1456
|
const thresholdRank = SEVERITY_RANK[threshold];
|
|
1318
1457
|
const filtered = result.findings.filter((f) => SEVERITY_RANK[f.severity] >= thresholdRank);
|
|
@@ -1358,7 +1497,7 @@ async function runCheckSecurityAction(opts, globalOpts) {
|
|
|
1358
1497
|
process.exit(result.value.valid ? ExitCode.SUCCESS : ExitCode.VALIDATION_FAILED);
|
|
1359
1498
|
}
|
|
1360
1499
|
function createCheckSecurityCommand() {
|
|
1361
|
-
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) => {
|
|
1362
1501
|
const severity = thisCommand.opts().severity;
|
|
1363
1502
|
if (!["error", "warning", "info"].includes(severity)) {
|
|
1364
1503
|
logger.error(`Invalid severity: "${severity}". Must be one of: error, warning, info`);
|
|
@@ -1371,10 +1510,11 @@ function createCheckSecurityCommand() {
|
|
|
1371
1510
|
}
|
|
1372
1511
|
|
|
1373
1512
|
// src/commands/ci/index.ts
|
|
1374
|
-
import { Command as
|
|
1513
|
+
import { Command as Command16 } from "commander";
|
|
1375
1514
|
|
|
1376
1515
|
// src/commands/ci/check.ts
|
|
1377
|
-
|
|
1516
|
+
init_dist();
|
|
1517
|
+
import { Command as Command13 } from "commander";
|
|
1378
1518
|
var VALID_CHECKS = [
|
|
1379
1519
|
"validate",
|
|
1380
1520
|
"deps",
|
|
@@ -1438,7 +1578,7 @@ function printCheckReport(report) {
|
|
|
1438
1578
|
}
|
|
1439
1579
|
}
|
|
1440
1580
|
async function runCheckAction(opts, globalOpts) {
|
|
1441
|
-
const mode =
|
|
1581
|
+
const mode = resolveOutputMode(globalOpts);
|
|
1442
1582
|
const skip = parseSkip(opts.skip);
|
|
1443
1583
|
const failOn = parseFailOn(opts.failOn);
|
|
1444
1584
|
const opts2 = {
|
|
@@ -1464,15 +1604,16 @@ async function runCheckAction(opts, globalOpts) {
|
|
|
1464
1604
|
process.exit(report.exitCode);
|
|
1465
1605
|
}
|
|
1466
1606
|
function createCheckCommand() {
|
|
1467
|
-
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) => {
|
|
1468
1608
|
await runCheckAction(opts, cmd.optsWithGlobals());
|
|
1469
1609
|
});
|
|
1470
1610
|
}
|
|
1471
1611
|
|
|
1472
1612
|
// src/commands/ci/init.ts
|
|
1473
|
-
|
|
1474
|
-
import
|
|
1475
|
-
import * as
|
|
1613
|
+
init_dist();
|
|
1614
|
+
import { Command as Command14 } from "commander";
|
|
1615
|
+
import * as fs3 from "fs";
|
|
1616
|
+
import * as path9 from "path";
|
|
1476
1617
|
var ALL_CHECKS = [
|
|
1477
1618
|
"validate",
|
|
1478
1619
|
"deps",
|
|
@@ -1574,17 +1715,17 @@ function generateCIConfig(options) {
|
|
|
1574
1715
|
});
|
|
1575
1716
|
}
|
|
1576
1717
|
function detectPlatform() {
|
|
1577
|
-
if (
|
|
1578
|
-
if (
|
|
1718
|
+
if (fs3.existsSync(".github")) return "github";
|
|
1719
|
+
if (fs3.existsSync(".gitlab-ci.yml")) return "gitlab";
|
|
1579
1720
|
return null;
|
|
1580
1721
|
}
|
|
1581
1722
|
function writeGeneratedConfig(filename, content, platform) {
|
|
1582
|
-
const targetPath =
|
|
1583
|
-
const dir =
|
|
1584
|
-
|
|
1585
|
-
|
|
1723
|
+
const targetPath = path9.resolve(filename);
|
|
1724
|
+
const dir = path9.dirname(targetPath);
|
|
1725
|
+
fs3.mkdirSync(dir, { recursive: true });
|
|
1726
|
+
fs3.writeFileSync(targetPath, content);
|
|
1586
1727
|
if (platform === "generic" && process.platform !== "win32") {
|
|
1587
|
-
|
|
1728
|
+
fs3.chmodSync(targetPath, "755");
|
|
1588
1729
|
}
|
|
1589
1730
|
}
|
|
1590
1731
|
async function runInitAction(opts, globalOpts) {
|
|
@@ -1607,22 +1748,128 @@ async function runInitAction(opts, globalOpts) {
|
|
|
1607
1748
|
}
|
|
1608
1749
|
}
|
|
1609
1750
|
function createInitCommand() {
|
|
1610
|
-
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) => {
|
|
1611
1752
|
await runInitAction(opts, cmd.optsWithGlobals());
|
|
1612
1753
|
});
|
|
1613
1754
|
}
|
|
1614
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
|
+
|
|
1615
1860
|
// src/commands/ci/index.ts
|
|
1616
1861
|
function createCICommand() {
|
|
1617
|
-
const command = new
|
|
1862
|
+
const command = new Command16("ci").description("CI/CD integration commands");
|
|
1618
1863
|
command.addCommand(createCheckCommand());
|
|
1619
1864
|
command.addCommand(createInitCommand());
|
|
1865
|
+
command.addCommand(createNotifyCommand());
|
|
1620
1866
|
return command;
|
|
1621
1867
|
}
|
|
1622
1868
|
|
|
1623
1869
|
// src/commands/cleanup.ts
|
|
1624
|
-
|
|
1625
|
-
import
|
|
1870
|
+
init_dist();
|
|
1871
|
+
import { Command as Command17 } from "commander";
|
|
1872
|
+
import * as path10 from "path";
|
|
1626
1873
|
async function runCleanup(options) {
|
|
1627
1874
|
const cwd = options.cwd ?? process.cwd();
|
|
1628
1875
|
const type = options.type ?? "all";
|
|
@@ -1637,11 +1884,11 @@ async function runCleanup(options) {
|
|
|
1637
1884
|
patternViolations: [],
|
|
1638
1885
|
totalIssues: 0
|
|
1639
1886
|
};
|
|
1640
|
-
const rootDir =
|
|
1641
|
-
const docsDir =
|
|
1887
|
+
const rootDir = path10.resolve(cwd, config.rootDir);
|
|
1888
|
+
const docsDir = path10.resolve(cwd, config.docsDir);
|
|
1642
1889
|
const entropyConfig = {
|
|
1643
1890
|
rootDir,
|
|
1644
|
-
entryPoints:
|
|
1891
|
+
...config.entropy?.entryPoints && { entryPoints: config.entropy.entryPoints },
|
|
1645
1892
|
docPaths: [docsDir],
|
|
1646
1893
|
analyze: {
|
|
1647
1894
|
drift: type === "all" || type === "drift",
|
|
@@ -1710,9 +1957,9 @@ function printCleanupResult(value, formatter) {
|
|
|
1710
1957
|
}
|
|
1711
1958
|
}
|
|
1712
1959
|
function createCleanupCommand() {
|
|
1713
|
-
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) => {
|
|
1714
1961
|
const globalOpts = cmd.optsWithGlobals();
|
|
1715
|
-
const mode =
|
|
1962
|
+
const mode = resolveOutputMode(globalOpts);
|
|
1716
1963
|
const formatter = new OutputFormatter(mode);
|
|
1717
1964
|
const result = await runCleanup({
|
|
1718
1965
|
configPath: globalOpts.config,
|
|
@@ -1740,20 +1987,21 @@ function createCleanupCommand() {
|
|
|
1740
1987
|
}
|
|
1741
1988
|
|
|
1742
1989
|
// src/commands/cleanup-sessions.ts
|
|
1743
|
-
|
|
1744
|
-
import
|
|
1745
|
-
import * as
|
|
1990
|
+
init_dist();
|
|
1991
|
+
import { Command as Command18 } from "commander";
|
|
1992
|
+
import * as fs5 from "fs";
|
|
1993
|
+
import * as path11 from "path";
|
|
1746
1994
|
var STALE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
1747
1995
|
function getMostRecentMtime(dirPath) {
|
|
1748
1996
|
let latest = 0;
|
|
1749
1997
|
try {
|
|
1750
|
-
const entries =
|
|
1998
|
+
const entries = fs5.readdirSync(dirPath, { withFileTypes: true });
|
|
1751
1999
|
for (const entry of entries) {
|
|
1752
|
-
const fullPath =
|
|
1753
|
-
const stat =
|
|
2000
|
+
const fullPath = path11.join(dirPath, entry.name);
|
|
2001
|
+
const stat = fs5.statSync(fullPath);
|
|
1754
2002
|
if (stat.mtimeMs > latest) latest = stat.mtimeMs;
|
|
1755
2003
|
}
|
|
1756
|
-
const dirStat =
|
|
2004
|
+
const dirStat = fs5.statSync(dirPath);
|
|
1757
2005
|
if (dirStat.mtimeMs > latest) latest = dirStat.mtimeMs;
|
|
1758
2006
|
} catch {
|
|
1759
2007
|
}
|
|
@@ -1762,14 +2010,14 @@ function getMostRecentMtime(dirPath) {
|
|
|
1762
2010
|
async function runCleanupSessions(options) {
|
|
1763
2011
|
const cwd = options.cwd ?? process.cwd();
|
|
1764
2012
|
const dryRun = options.dryRun ?? false;
|
|
1765
|
-
const sessionsDir =
|
|
2013
|
+
const sessionsDir = path11.join(cwd, ".harness", "sessions");
|
|
1766
2014
|
const result = { removed: [], kept: [] };
|
|
1767
|
-
if (!
|
|
2015
|
+
if (!fs5.existsSync(sessionsDir)) {
|
|
1768
2016
|
return Ok(result);
|
|
1769
2017
|
}
|
|
1770
2018
|
let entries;
|
|
1771
2019
|
try {
|
|
1772
|
-
entries =
|
|
2020
|
+
entries = fs5.readdirSync(sessionsDir, { withFileTypes: true });
|
|
1773
2021
|
} catch (err) {
|
|
1774
2022
|
return Err(
|
|
1775
2023
|
new CLIError(
|
|
@@ -1781,14 +2029,14 @@ async function runCleanupSessions(options) {
|
|
|
1781
2029
|
const now = Date.now();
|
|
1782
2030
|
for (const entry of entries) {
|
|
1783
2031
|
if (!entry.isDirectory()) continue;
|
|
1784
|
-
const sessionPath =
|
|
2032
|
+
const sessionPath = path11.join(sessionsDir, entry.name);
|
|
1785
2033
|
const mostRecent = getMostRecentMtime(sessionPath);
|
|
1786
2034
|
const ageMs = now - mostRecent;
|
|
1787
2035
|
if (ageMs > STALE_TTL_MS) {
|
|
1788
2036
|
result.removed.push(entry.name);
|
|
1789
2037
|
if (!dryRun) {
|
|
1790
2038
|
try {
|
|
1791
|
-
|
|
2039
|
+
fs5.rmSync(sessionPath, { recursive: true, force: true });
|
|
1792
2040
|
} catch (err) {
|
|
1793
2041
|
return Err(
|
|
1794
2042
|
new CLIError(
|
|
@@ -1831,9 +2079,9 @@ Cleaned up ${removed.length} stale session(s).`);
|
|
|
1831
2079
|
}
|
|
1832
2080
|
}
|
|
1833
2081
|
function createCleanupSessionsCommand() {
|
|
1834
|
-
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) => {
|
|
1835
2083
|
const globalOpts = cmd.optsWithGlobals();
|
|
1836
|
-
const cwd =
|
|
2084
|
+
const cwd = path11.resolve(opts.path);
|
|
1837
2085
|
const result = await runCleanupSessions({ cwd, dryRun: opts.dryRun });
|
|
1838
2086
|
if (!result.ok) {
|
|
1839
2087
|
logger.error(result.error.message);
|
|
@@ -1847,11 +2095,11 @@ function createCleanupSessionsCommand() {
|
|
|
1847
2095
|
}
|
|
1848
2096
|
|
|
1849
2097
|
// src/commands/dashboard.ts
|
|
1850
|
-
import { Command as
|
|
2098
|
+
import { Command as Command19 } from "commander";
|
|
1851
2099
|
import { spawn } from "child_process";
|
|
1852
2100
|
import { existsSync as existsSync4 } from "fs";
|
|
1853
2101
|
import { createRequire } from "module";
|
|
1854
|
-
import { dirname as dirname2, join as
|
|
2102
|
+
import { dirname as dirname2, join as join6, resolve as resolve11 } from "path";
|
|
1855
2103
|
import { setTimeout } from "timers";
|
|
1856
2104
|
import { fileURLToPath } from "url";
|
|
1857
2105
|
var __filename = fileURLToPath(import.meta.url);
|
|
@@ -1867,20 +2115,20 @@ function resolveServerScript() {
|
|
|
1867
2115
|
try {
|
|
1868
2116
|
const pkgPath = require2.resolve("@harness-engineering/dashboard/package.json");
|
|
1869
2117
|
const pkgDir = dirname2(pkgPath);
|
|
1870
|
-
const built2 =
|
|
2118
|
+
const built2 = join6(pkgDir, "dist", "server", "serve.js");
|
|
1871
2119
|
if (existsSync4(built2)) return { script: built2, dev: false };
|
|
1872
|
-
const dev2 =
|
|
2120
|
+
const dev2 = join6(pkgDir, "src", "server", "serve.ts");
|
|
1873
2121
|
if (existsSync4(dev2)) return { script: dev2, dev: true };
|
|
1874
2122
|
} catch {
|
|
1875
2123
|
}
|
|
1876
2124
|
const built = [
|
|
1877
|
-
|
|
1878
|
-
|
|
2125
|
+
join6(__dirname, "..", "..", "..", "dashboard", "dist", "server", "serve.js"),
|
|
2126
|
+
join6(__dirname, "..", "..", "..", "..", "packages", "dashboard", "dist", "server", "serve.js")
|
|
1879
2127
|
].find((p) => existsSync4(p));
|
|
1880
2128
|
if (built) return { script: built, dev: false };
|
|
1881
2129
|
const dev = [
|
|
1882
|
-
|
|
1883
|
-
|
|
2130
|
+
join6(__dirname, "..", "..", "..", "dashboard", "src", "server", "serve.ts"),
|
|
2131
|
+
join6(__dirname, "..", "..", "..", "..", "packages", "dashboard", "src", "server", "serve.ts")
|
|
1884
2132
|
].find((p) => existsSync4(p));
|
|
1885
2133
|
return dev ? { script: dev, dev: true } : null;
|
|
1886
2134
|
}
|
|
@@ -1901,7 +2149,7 @@ function spawnDashboardServer(server, env) {
|
|
|
1901
2149
|
function runDashboard(opts) {
|
|
1902
2150
|
const clientPort = Number(opts.port ?? DEFAULT_CLIENT_PORT);
|
|
1903
2151
|
const apiPort = Number(opts.apiPort ?? DEFAULT_API_PORT);
|
|
1904
|
-
const projectPath =
|
|
2152
|
+
const projectPath = resolve11(opts.cwd ?? process.cwd());
|
|
1905
2153
|
const url = `http://localhost:${apiPort}`;
|
|
1906
2154
|
const server = resolveServerScript();
|
|
1907
2155
|
if (!server) {
|
|
@@ -1922,14 +2170,14 @@ function runDashboard(opts) {
|
|
|
1922
2170
|
}
|
|
1923
2171
|
}
|
|
1924
2172
|
function createDashboardCommand() {
|
|
1925
|
-
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));
|
|
1926
2174
|
}
|
|
1927
2175
|
|
|
1928
2176
|
// src/commands/doctor.ts
|
|
1929
|
-
import { Command as
|
|
1930
|
-
import * as
|
|
2177
|
+
import { Command as Command20 } from "commander";
|
|
2178
|
+
import * as fs7 from "fs";
|
|
1931
2179
|
import * as os from "os";
|
|
1932
|
-
import * as
|
|
2180
|
+
import * as path13 from "path";
|
|
1933
2181
|
import chalk from "chalk";
|
|
1934
2182
|
|
|
1935
2183
|
// src/utils/node-version.ts
|
|
@@ -2012,24 +2260,24 @@ var INTEGRATION_REGISTRY = [
|
|
|
2012
2260
|
];
|
|
2013
2261
|
|
|
2014
2262
|
// src/integrations/config.ts
|
|
2015
|
-
import * as
|
|
2016
|
-
import * as
|
|
2263
|
+
import * as fs6 from "fs";
|
|
2264
|
+
import * as path12 from "path";
|
|
2017
2265
|
function readJsonSafe(filePath) {
|
|
2018
|
-
if (!
|
|
2266
|
+
if (!fs6.existsSync(filePath)) return null;
|
|
2019
2267
|
try {
|
|
2020
|
-
return JSON.parse(
|
|
2268
|
+
return JSON.parse(fs6.readFileSync(filePath, "utf-8"));
|
|
2021
2269
|
} catch {
|
|
2022
2270
|
return null;
|
|
2023
2271
|
}
|
|
2024
2272
|
}
|
|
2025
2273
|
function writeJson(filePath, data) {
|
|
2026
|
-
const dir =
|
|
2027
|
-
if (!
|
|
2028
|
-
|
|
2274
|
+
const dir = path12.dirname(filePath);
|
|
2275
|
+
if (!fs6.existsSync(dir)) {
|
|
2276
|
+
fs6.mkdirSync(dir, { recursive: true });
|
|
2029
2277
|
}
|
|
2030
2278
|
const tmp = filePath + ".tmp";
|
|
2031
|
-
|
|
2032
|
-
|
|
2279
|
+
fs6.writeFileSync(tmp, JSON.stringify(data, null, 2) + "\n");
|
|
2280
|
+
fs6.renameSync(tmp, filePath);
|
|
2033
2281
|
}
|
|
2034
2282
|
function readMcpConfig(filePath) {
|
|
2035
2283
|
const config = readJsonSafe(filePath);
|
|
@@ -2043,7 +2291,7 @@ function writeMcpEntry(filePath, name, entry) {
|
|
|
2043
2291
|
writeJson(filePath, config);
|
|
2044
2292
|
}
|
|
2045
2293
|
function removeMcpEntry(filePath, name) {
|
|
2046
|
-
if (!
|
|
2294
|
+
if (!fs6.existsSync(filePath)) return;
|
|
2047
2295
|
const config = readMcpConfig(filePath);
|
|
2048
2296
|
delete config.mcpServers[name];
|
|
2049
2297
|
writeJson(filePath, config);
|
|
@@ -2082,7 +2330,7 @@ function checkNodeVersion2() {
|
|
|
2082
2330
|
}
|
|
2083
2331
|
function countCommandFiles(dir, ext) {
|
|
2084
2332
|
try {
|
|
2085
|
-
return
|
|
2333
|
+
return fs7.readdirSync(dir).filter((f) => f.endsWith(ext)).length;
|
|
2086
2334
|
} catch {
|
|
2087
2335
|
return 0;
|
|
2088
2336
|
}
|
|
@@ -2091,13 +2339,13 @@ function checkSlashCommands() {
|
|
|
2091
2339
|
const platforms = [
|
|
2092
2340
|
{
|
|
2093
2341
|
name: "Claude Code",
|
|
2094
|
-
dir:
|
|
2342
|
+
dir: path13.join(os.homedir(), ".claude", "commands", "harness"),
|
|
2095
2343
|
ext: ".md",
|
|
2096
2344
|
client: "claude-code"
|
|
2097
2345
|
},
|
|
2098
2346
|
{
|
|
2099
2347
|
name: "Gemini CLI",
|
|
2100
|
-
dir:
|
|
2348
|
+
dir: path13.join(os.homedir(), ".gemini", "commands", "harness"),
|
|
2101
2349
|
ext: ".toml",
|
|
2102
2350
|
client: "gemini-cli"
|
|
2103
2351
|
}
|
|
@@ -2121,7 +2369,7 @@ function checkSlashCommands() {
|
|
|
2121
2369
|
}
|
|
2122
2370
|
function checkMcpConfig(cwd) {
|
|
2123
2371
|
const results = [];
|
|
2124
|
-
const claudeConfig = readMcpConfig(
|
|
2372
|
+
const claudeConfig = readMcpConfig(path13.join(cwd, ".mcp.json"));
|
|
2125
2373
|
if (claudeConfig.mcpServers?.["harness"]) {
|
|
2126
2374
|
results.push({
|
|
2127
2375
|
name: "mcp-claude",
|
|
@@ -2136,9 +2384,9 @@ function checkMcpConfig(cwd) {
|
|
|
2136
2384
|
fix: "Run: harness setup-mcp --client claude"
|
|
2137
2385
|
});
|
|
2138
2386
|
}
|
|
2139
|
-
const geminiDir =
|
|
2140
|
-
if (
|
|
2141
|
-
const geminiConfig = readMcpConfig(
|
|
2387
|
+
const geminiDir = path13.join(cwd, ".gemini");
|
|
2388
|
+
if (fs7.existsSync(geminiDir)) {
|
|
2389
|
+
const geminiConfig = readMcpConfig(path13.join(geminiDir, "settings.json"));
|
|
2142
2390
|
if (geminiConfig.mcpServers?.["harness"]) {
|
|
2143
2391
|
results.push({
|
|
2144
2392
|
name: "mcp-gemini",
|
|
@@ -2157,12 +2405,12 @@ function checkMcpConfig(cwd) {
|
|
|
2157
2405
|
return results;
|
|
2158
2406
|
}
|
|
2159
2407
|
function loadMcpPresence(cwd) {
|
|
2160
|
-
const mcpPath =
|
|
2161
|
-
const geminiDir =
|
|
2162
|
-
const hasGemini =
|
|
2408
|
+
const mcpPath = path13.join(cwd, ".mcp.json");
|
|
2409
|
+
const geminiDir = path13.join(cwd, ".gemini");
|
|
2410
|
+
const hasGemini = fs7.existsSync(geminiDir);
|
|
2163
2411
|
return {
|
|
2164
2412
|
mcpConfig: readMcpConfig(mcpPath),
|
|
2165
|
-
geminiConfig: hasGemini ? readMcpConfig(
|
|
2413
|
+
geminiConfig: hasGemini ? readMcpConfig(path13.join(geminiDir, "settings.json")) : null,
|
|
2166
2414
|
hasGemini
|
|
2167
2415
|
};
|
|
2168
2416
|
}
|
|
@@ -2194,7 +2442,7 @@ function checkTier0Presence(def, presence) {
|
|
|
2194
2442
|
function checkIntegrations(cwd) {
|
|
2195
2443
|
const results = [];
|
|
2196
2444
|
const presence = loadMcpPresence(cwd);
|
|
2197
|
-
const configPath =
|
|
2445
|
+
const configPath = path13.join(cwd, "harness.config.json");
|
|
2198
2446
|
const integrationsConfig = readIntegrationsConfig(configPath);
|
|
2199
2447
|
for (const def of INTEGRATION_REGISTRY.filter((d) => d.tier === 0)) {
|
|
2200
2448
|
results.push(checkTier0Presence(def, presence));
|
|
@@ -2244,7 +2492,7 @@ function formatCheck(check) {
|
|
|
2244
2492
|
return line;
|
|
2245
2493
|
}
|
|
2246
2494
|
function createDoctorCommand() {
|
|
2247
|
-
return new
|
|
2495
|
+
return new Command20("doctor").description("Check environment health: Node version, slash commands, MCP configuration").action((_opts, cmd) => {
|
|
2248
2496
|
const globalOpts = cmd.optsWithGlobals();
|
|
2249
2497
|
const cwd = process.cwd();
|
|
2250
2498
|
const useJson = globalOpts.json;
|
|
@@ -2269,8 +2517,9 @@ function createDoctorCommand() {
|
|
|
2269
2517
|
}
|
|
2270
2518
|
|
|
2271
2519
|
// src/commands/fix-drift.ts
|
|
2272
|
-
|
|
2273
|
-
import
|
|
2520
|
+
init_dist();
|
|
2521
|
+
import { Command as Command21 } from "commander";
|
|
2522
|
+
import * as path14 from "path";
|
|
2274
2523
|
async function runFixDrift(options) {
|
|
2275
2524
|
const cwd = options.cwd ?? process.cwd();
|
|
2276
2525
|
const dryRun = options.dryRun !== false;
|
|
@@ -2279,11 +2528,11 @@ async function runFixDrift(options) {
|
|
|
2279
2528
|
return Err(configResult.error);
|
|
2280
2529
|
}
|
|
2281
2530
|
const config = configResult.value;
|
|
2282
|
-
const rootDir =
|
|
2283
|
-
const docsDir =
|
|
2531
|
+
const rootDir = path14.resolve(cwd, config.rootDir);
|
|
2532
|
+
const docsDir = path14.resolve(cwd, config.docsDir);
|
|
2284
2533
|
const entropyConfig = {
|
|
2285
2534
|
rootDir,
|
|
2286
|
-
entryPoints:
|
|
2535
|
+
...config.entropy?.entryPoints && { entryPoints: config.entropy.entryPoints },
|
|
2287
2536
|
docPaths: [docsDir],
|
|
2288
2537
|
analyze: {
|
|
2289
2538
|
drift: true,
|
|
@@ -2402,9 +2651,9 @@ function printFixDriftResult(value, mode, formatter) {
|
|
|
2402
2651
|
}
|
|
2403
2652
|
}
|
|
2404
2653
|
function createFixDriftCommand() {
|
|
2405
|
-
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) => {
|
|
2406
2655
|
const globalOpts = cmd.optsWithGlobals();
|
|
2407
|
-
const mode =
|
|
2656
|
+
const mode = resolveOutputMode(globalOpts);
|
|
2408
2657
|
const formatter = new OutputFormatter(mode);
|
|
2409
2658
|
const result = await runFixDrift({
|
|
2410
2659
|
configPath: globalOpts.config,
|
|
@@ -2432,7 +2681,7 @@ function createFixDriftCommand() {
|
|
|
2432
2681
|
}
|
|
2433
2682
|
|
|
2434
2683
|
// src/commands/generate.ts
|
|
2435
|
-
import { Command as
|
|
2684
|
+
import { Command as Command22 } from "commander";
|
|
2436
2685
|
function validatePlatforms(platforms) {
|
|
2437
2686
|
for (const p of platforms) {
|
|
2438
2687
|
if (!VALID_PLATFORMS.includes(p)) {
|
|
@@ -2492,27 +2741,27 @@ async function runGenerateAction(opts, globalOpts) {
|
|
|
2492
2741
|
}
|
|
2493
2742
|
}
|
|
2494
2743
|
function createGenerateCommand() {
|
|
2495
|
-
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) => {
|
|
2496
2745
|
await runGenerateAction(opts, cmd.optsWithGlobals());
|
|
2497
2746
|
});
|
|
2498
2747
|
}
|
|
2499
2748
|
|
|
2500
2749
|
// src/commands/graph/index.ts
|
|
2501
|
-
import { Command as
|
|
2750
|
+
import { Command as Command25 } from "commander";
|
|
2502
2751
|
|
|
2503
2752
|
// src/commands/graph/status.ts
|
|
2504
|
-
import * as
|
|
2753
|
+
import * as path15 from "path";
|
|
2505
2754
|
async function runGraphStatus(projectPath) {
|
|
2506
|
-
const { GraphStore } = await import("./dist-
|
|
2507
|
-
const graphDir =
|
|
2755
|
+
const { GraphStore } = await import("./dist-RADHOOXG.js");
|
|
2756
|
+
const graphDir = path15.join(projectPath, ".harness", "graph");
|
|
2508
2757
|
const store = new GraphStore();
|
|
2509
2758
|
const loaded = await store.load(graphDir);
|
|
2510
2759
|
if (!loaded) return { status: "no_graph", message: "No graph found. Run `harness scan` first." };
|
|
2511
|
-
const
|
|
2512
|
-
const metaPath =
|
|
2760
|
+
const fs41 = await import("fs/promises");
|
|
2761
|
+
const metaPath = path15.join(graphDir, "metadata.json");
|
|
2513
2762
|
let lastScan = "unknown";
|
|
2514
2763
|
try {
|
|
2515
|
-
const meta = JSON.parse(await
|
|
2764
|
+
const meta = JSON.parse(await fs41.readFile(metaPath, "utf-8"));
|
|
2516
2765
|
lastScan = meta.lastScanTimestamp;
|
|
2517
2766
|
} catch {
|
|
2518
2767
|
}
|
|
@@ -2523,8 +2772,8 @@ async function runGraphStatus(projectPath) {
|
|
|
2523
2772
|
}
|
|
2524
2773
|
let connectorSyncStatus = {};
|
|
2525
2774
|
try {
|
|
2526
|
-
const syncMetaPath =
|
|
2527
|
-
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"));
|
|
2528
2777
|
for (const [name, data] of Object.entries(syncMeta.connectors ?? {})) {
|
|
2529
2778
|
connectorSyncStatus[name] = data.lastSyncTimestamp;
|
|
2530
2779
|
}
|
|
@@ -2541,10 +2790,10 @@ async function runGraphStatus(projectPath) {
|
|
|
2541
2790
|
}
|
|
2542
2791
|
|
|
2543
2792
|
// src/commands/graph/export.ts
|
|
2544
|
-
import * as
|
|
2793
|
+
import * as path16 from "path";
|
|
2545
2794
|
async function runGraphExport(projectPath, format) {
|
|
2546
|
-
const { GraphStore } = await import("./dist-
|
|
2547
|
-
const graphDir =
|
|
2795
|
+
const { GraphStore } = await import("./dist-RADHOOXG.js");
|
|
2796
|
+
const graphDir = path16.join(projectPath, ".harness", "graph");
|
|
2548
2797
|
const store = new GraphStore();
|
|
2549
2798
|
const loaded = await store.load(graphDir);
|
|
2550
2799
|
if (!loaded) throw new Error("No graph found. Run `harness scan` first.");
|
|
@@ -2573,15 +2822,15 @@ async function runGraphExport(projectPath, format) {
|
|
|
2573
2822
|
}
|
|
2574
2823
|
|
|
2575
2824
|
// src/commands/graph/index.ts
|
|
2576
|
-
import * as
|
|
2825
|
+
import * as path19 from "path";
|
|
2577
2826
|
|
|
2578
2827
|
// src/commands/graph/query.ts
|
|
2579
|
-
import { Command as
|
|
2580
|
-
import * as
|
|
2828
|
+
import { Command as Command23 } from "commander";
|
|
2829
|
+
import * as path17 from "path";
|
|
2581
2830
|
async function runQuery(projectPath, rootNodeId, opts) {
|
|
2582
|
-
const { GraphStore, ContextQL } = await import("./dist-
|
|
2831
|
+
const { GraphStore, ContextQL } = await import("./dist-RADHOOXG.js");
|
|
2583
2832
|
const store = new GraphStore();
|
|
2584
|
-
const graphDir =
|
|
2833
|
+
const graphDir = path17.join(projectPath, ".harness", "graph");
|
|
2585
2834
|
const loaded = await store.load(graphDir);
|
|
2586
2835
|
if (!loaded) throw new Error("No graph found. Run `harness scan` first.");
|
|
2587
2836
|
const params = {
|
|
@@ -2603,7 +2852,7 @@ function printQueryResult(result) {
|
|
|
2603
2852
|
}
|
|
2604
2853
|
}
|
|
2605
2854
|
async function runQueryAction(rootNodeId, opts, globalOpts) {
|
|
2606
|
-
const projectPath =
|
|
2855
|
+
const projectPath = path17.resolve(globalOpts.config ? path17.dirname(globalOpts.config) : ".");
|
|
2607
2856
|
try {
|
|
2608
2857
|
const result = await runQuery(projectPath, rootNodeId, {
|
|
2609
2858
|
depth: parseInt(opts.depth),
|
|
@@ -2622,19 +2871,19 @@ async function runQueryAction(rootNodeId, opts, globalOpts) {
|
|
|
2622
2871
|
}
|
|
2623
2872
|
}
|
|
2624
2873
|
function createQueryCommand() {
|
|
2625
|
-
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) => {
|
|
2626
2875
|
await runQueryAction(rootNodeId, opts, cmd.optsWithGlobals());
|
|
2627
2876
|
});
|
|
2628
2877
|
}
|
|
2629
2878
|
|
|
2630
2879
|
// src/commands/graph/ingest.ts
|
|
2631
|
-
import { Command as
|
|
2632
|
-
import * as
|
|
2880
|
+
import { Command as Command24 } from "commander";
|
|
2881
|
+
import * as path18 from "path";
|
|
2633
2882
|
async function loadConnectorConfig(projectPath, source) {
|
|
2634
2883
|
try {
|
|
2635
|
-
const
|
|
2636
|
-
const configPath =
|
|
2637
|
-
const config = JSON.parse(await
|
|
2884
|
+
const fs41 = await import("fs/promises");
|
|
2885
|
+
const configPath = path18.join(projectPath, "harness.config.json");
|
|
2886
|
+
const config = JSON.parse(await fs41.readFile(configPath, "utf-8"));
|
|
2638
2887
|
const connector = config.graph?.connectors?.[source];
|
|
2639
2888
|
return connector ?? {};
|
|
2640
2889
|
} catch {
|
|
@@ -2673,8 +2922,8 @@ async function runIngest(projectPath, source, opts) {
|
|
|
2673
2922
|
SlackConnector,
|
|
2674
2923
|
CIConnector,
|
|
2675
2924
|
ConfluenceConnector
|
|
2676
|
-
} = await import("./dist-
|
|
2677
|
-
const graphDir =
|
|
2925
|
+
} = await import("./dist-RADHOOXG.js");
|
|
2926
|
+
const graphDir = path18.join(projectPath, ".harness", "graph");
|
|
2678
2927
|
const store = new GraphStore();
|
|
2679
2928
|
await store.load(graphDir);
|
|
2680
2929
|
if (opts?.all) {
|
|
@@ -2741,7 +2990,7 @@ async function runIngest(projectPath, source, opts) {
|
|
|
2741
2990
|
return result;
|
|
2742
2991
|
}
|
|
2743
2992
|
function createIngestCommand() {
|
|
2744
|
-
return new
|
|
2993
|
+
return new Command24("ingest").description("Ingest data into the knowledge graph").option(
|
|
2745
2994
|
"--source <name>",
|
|
2746
2995
|
"Source to ingest (code, knowledge, git, jira, slack, ci, confluence)"
|
|
2747
2996
|
).option("--all", "Run all sources (code, knowledge, git, and configured connectors)").option("--full", "Force full re-ingestion").action(async (opts, cmd) => {
|
|
@@ -2750,7 +2999,7 @@ function createIngestCommand() {
|
|
|
2750
2999
|
process.exit(1);
|
|
2751
3000
|
}
|
|
2752
3001
|
const globalOpts = cmd.optsWithGlobals();
|
|
2753
|
-
const projectPath =
|
|
3002
|
+
const projectPath = path18.resolve(globalOpts.config ? path18.dirname(globalOpts.config) : ".");
|
|
2754
3003
|
try {
|
|
2755
3004
|
const result = await runIngest(projectPath, opts.source ?? "", {
|
|
2756
3005
|
full: opts.full,
|
|
@@ -2773,7 +3022,7 @@ function createIngestCommand() {
|
|
|
2773
3022
|
|
|
2774
3023
|
// src/commands/graph/index.ts
|
|
2775
3024
|
function resolveProjectPath(globalOpts) {
|
|
2776
|
-
return
|
|
3025
|
+
return path19.resolve(globalOpts.config ? path19.dirname(globalOpts.config) : ".");
|
|
2777
3026
|
}
|
|
2778
3027
|
function printGraphStatus(result) {
|
|
2779
3028
|
if (result.status === "no_graph") {
|
|
@@ -2817,19 +3066,19 @@ async function runExportAction(opts, cmd) {
|
|
|
2817
3066
|
}
|
|
2818
3067
|
}
|
|
2819
3068
|
function createGraphCommand() {
|
|
2820
|
-
const graph = new
|
|
3069
|
+
const graph = new Command25("graph").description("Knowledge graph management");
|
|
2821
3070
|
graph.command("status").description("Show graph statistics").action(runStatusAction);
|
|
2822
3071
|
graph.command("export").description("Export graph").requiredOption("--format <format>", "Output format (json, mermaid)").action(runExportAction);
|
|
2823
3072
|
return graph;
|
|
2824
3073
|
}
|
|
2825
3074
|
|
|
2826
3075
|
// src/commands/hooks/index.ts
|
|
2827
|
-
import { Command as
|
|
3076
|
+
import { Command as Command30 } from "commander";
|
|
2828
3077
|
|
|
2829
3078
|
// src/commands/hooks/init.ts
|
|
2830
|
-
import { Command as
|
|
2831
|
-
import * as
|
|
2832
|
-
import * as
|
|
3079
|
+
import { Command as Command26 } from "commander";
|
|
3080
|
+
import * as fs8 from "fs";
|
|
3081
|
+
import * as path20 from "path";
|
|
2833
3082
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
2834
3083
|
|
|
2835
3084
|
// src/hooks/profiles.ts
|
|
@@ -2859,11 +3108,11 @@ var PROFILES = {
|
|
|
2859
3108
|
|
|
2860
3109
|
// src/commands/hooks/init.ts
|
|
2861
3110
|
var __filename2 = fileURLToPath2(import.meta.url);
|
|
2862
|
-
var __dirname2 =
|
|
3111
|
+
var __dirname2 = path20.dirname(__filename2);
|
|
2863
3112
|
var VALID_PROFILES = ["minimal", "standard", "strict"];
|
|
2864
3113
|
function resolveHookSourceDir() {
|
|
2865
|
-
const candidate =
|
|
2866
|
-
if (
|
|
3114
|
+
const candidate = path20.resolve(__dirname2, "..", "..", "hooks");
|
|
3115
|
+
if (fs8.existsSync(candidate)) {
|
|
2867
3116
|
return candidate;
|
|
2868
3117
|
}
|
|
2869
3118
|
throw new Error(`Cannot locate hook scripts directory. Expected at: ${candidate}`);
|
|
@@ -2891,12 +3140,12 @@ function mergeSettings(existing, hooksConfig) {
|
|
|
2891
3140
|
}
|
|
2892
3141
|
function initHooks(options) {
|
|
2893
3142
|
const { profile, projectDir } = options;
|
|
2894
|
-
const hooksDestDir =
|
|
2895
|
-
|
|
2896
|
-
if (
|
|
2897
|
-
for (const entry of
|
|
3143
|
+
const hooksDestDir = path20.join(projectDir, ".harness", "hooks");
|
|
3144
|
+
fs8.mkdirSync(hooksDestDir, { recursive: true });
|
|
3145
|
+
if (fs8.existsSync(hooksDestDir)) {
|
|
3146
|
+
for (const entry of fs8.readdirSync(hooksDestDir)) {
|
|
2898
3147
|
if (entry.endsWith(".js")) {
|
|
2899
|
-
|
|
3148
|
+
fs8.unlinkSync(path20.join(hooksDestDir, entry));
|
|
2900
3149
|
}
|
|
2901
3150
|
}
|
|
2902
3151
|
}
|
|
@@ -2905,22 +3154,22 @@ function initHooks(options) {
|
|
|
2905
3154
|
const activeNames = PROFILES[profile];
|
|
2906
3155
|
const activeScripts = HOOK_SCRIPTS.filter((h) => activeNames.includes(h.name));
|
|
2907
3156
|
for (const script of activeScripts) {
|
|
2908
|
-
const srcFile =
|
|
2909
|
-
const destFile =
|
|
2910
|
-
if (
|
|
2911
|
-
|
|
3157
|
+
const srcFile = path20.join(sourceDir, `${script.name}.js`);
|
|
3158
|
+
const destFile = path20.join(hooksDestDir, `${script.name}.js`);
|
|
3159
|
+
if (fs8.existsSync(srcFile)) {
|
|
3160
|
+
fs8.copyFileSync(srcFile, destFile);
|
|
2912
3161
|
copiedScripts.push(script.name);
|
|
2913
3162
|
}
|
|
2914
3163
|
}
|
|
2915
|
-
const profilePath =
|
|
2916
|
-
|
|
2917
|
-
const claudeDir =
|
|
2918
|
-
|
|
2919
|
-
const settingsPath =
|
|
3164
|
+
const profilePath = path20.join(hooksDestDir, "profile.json");
|
|
3165
|
+
fs8.writeFileSync(profilePath, JSON.stringify({ profile }, null, 2) + "\n");
|
|
3166
|
+
const claudeDir = path20.join(projectDir, ".claude");
|
|
3167
|
+
fs8.mkdirSync(claudeDir, { recursive: true });
|
|
3168
|
+
const settingsPath = path20.join(claudeDir, "settings.json");
|
|
2920
3169
|
let existing = {};
|
|
2921
|
-
if (
|
|
3170
|
+
if (fs8.existsSync(settingsPath)) {
|
|
2922
3171
|
try {
|
|
2923
|
-
existing = JSON.parse(
|
|
3172
|
+
existing = JSON.parse(fs8.readFileSync(settingsPath, "utf-8"));
|
|
2924
3173
|
} catch (e) {
|
|
2925
3174
|
throw new Error(
|
|
2926
3175
|
`Malformed .claude/settings.json \u2014 fix the JSON syntax before running hooks init. Parse error: ${e instanceof Error ? e.message : String(e)}`,
|
|
@@ -2930,11 +3179,11 @@ function initHooks(options) {
|
|
|
2930
3179
|
}
|
|
2931
3180
|
const hooksConfig = buildSettingsHooks(profile);
|
|
2932
3181
|
const merged = mergeSettings(existing, hooksConfig);
|
|
2933
|
-
|
|
3182
|
+
fs8.writeFileSync(settingsPath, JSON.stringify(merged, null, 2) + "\n");
|
|
2934
3183
|
return { copiedScripts, settingsPath, profilePath };
|
|
2935
3184
|
}
|
|
2936
3185
|
function createInitCommand2() {
|
|
2937
|
-
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) => {
|
|
2938
3187
|
const globalOpts = cmd.optsWithGlobals();
|
|
2939
3188
|
const profile = opts.profile;
|
|
2940
3189
|
if (!VALID_PROFILES.includes(profile)) {
|
|
@@ -2958,7 +3207,9 @@ function createInitCommand2() {
|
|
|
2958
3207
|
`Installed ${result.copiedScripts.length} hook scripts to .harness/hooks/`
|
|
2959
3208
|
);
|
|
2960
3209
|
logger.info(`Profile: ${profile}`);
|
|
2961
|
-
logger.info(
|
|
3210
|
+
logger.info(
|
|
3211
|
+
`Settings: ${path20.relative(projectDir, result.settingsPath).replaceAll("\\", "/")}`
|
|
3212
|
+
);
|
|
2962
3213
|
logger.dim("Run 'harness hooks list' to see installed hooks");
|
|
2963
3214
|
}
|
|
2964
3215
|
} catch (err) {
|
|
@@ -2970,19 +3221,19 @@ function createInitCommand2() {
|
|
|
2970
3221
|
}
|
|
2971
3222
|
|
|
2972
3223
|
// src/commands/hooks/list.ts
|
|
2973
|
-
import { Command as
|
|
2974
|
-
import * as
|
|
2975
|
-
import * as
|
|
3224
|
+
import { Command as Command27 } from "commander";
|
|
3225
|
+
import * as fs9 from "fs";
|
|
3226
|
+
import * as path21 from "path";
|
|
2976
3227
|
function listHooks(projectDir) {
|
|
2977
|
-
const hooksDir =
|
|
2978
|
-
const profilePath =
|
|
2979
|
-
if (!
|
|
3228
|
+
const hooksDir = path21.join(projectDir, ".harness", "hooks");
|
|
3229
|
+
const profilePath = path21.join(hooksDir, "profile.json");
|
|
3230
|
+
if (!fs9.existsSync(profilePath)) {
|
|
2980
3231
|
return { installed: false, profile: null, hooks: [] };
|
|
2981
3232
|
}
|
|
2982
3233
|
let profile = "standard";
|
|
2983
3234
|
let warning;
|
|
2984
3235
|
try {
|
|
2985
|
-
const data = JSON.parse(
|
|
3236
|
+
const data = JSON.parse(fs9.readFileSync(profilePath, "utf-8"));
|
|
2986
3237
|
if (data.profile && ["minimal", "standard", "strict"].includes(data.profile)) {
|
|
2987
3238
|
profile = data.profile;
|
|
2988
3239
|
}
|
|
@@ -2994,7 +3245,7 @@ function listHooks(projectDir) {
|
|
|
2994
3245
|
name: h.name,
|
|
2995
3246
|
event: h.event,
|
|
2996
3247
|
matcher: h.matcher,
|
|
2997
|
-
scriptPath:
|
|
3248
|
+
scriptPath: path21.join(".harness", "hooks", `${h.name}.js`)
|
|
2998
3249
|
}));
|
|
2999
3250
|
const result = { installed: true, profile, hooks };
|
|
3000
3251
|
if (warning) {
|
|
@@ -3003,7 +3254,7 @@ function listHooks(projectDir) {
|
|
|
3003
3254
|
return result;
|
|
3004
3255
|
}
|
|
3005
3256
|
function createListCommand() {
|
|
3006
|
-
return new
|
|
3257
|
+
return new Command27("list").description("Show installed hooks and active profile").action(async (_opts, cmd) => {
|
|
3007
3258
|
const globalOpts = cmd.optsWithGlobals();
|
|
3008
3259
|
const projectDir = process.cwd();
|
|
3009
3260
|
const result = listHooks(projectDir);
|
|
@@ -3024,28 +3275,28 @@ function createListCommand() {
|
|
|
3024
3275
|
}
|
|
3025
3276
|
|
|
3026
3277
|
// src/commands/hooks/remove.ts
|
|
3027
|
-
import { Command as
|
|
3028
|
-
import * as
|
|
3029
|
-
import * as
|
|
3278
|
+
import { Command as Command28 } from "commander";
|
|
3279
|
+
import * as fs10 from "fs";
|
|
3280
|
+
import * as path22 from "path";
|
|
3030
3281
|
function removeHooks(projectDir) {
|
|
3031
|
-
const hooksDir =
|
|
3032
|
-
const settingsPath =
|
|
3282
|
+
const hooksDir = path22.join(projectDir, ".harness", "hooks");
|
|
3283
|
+
const settingsPath = path22.join(projectDir, ".claude", "settings.json");
|
|
3033
3284
|
let removed = false;
|
|
3034
3285
|
let settingsCleaned = false;
|
|
3035
|
-
if (
|
|
3036
|
-
|
|
3286
|
+
if (fs10.existsSync(hooksDir)) {
|
|
3287
|
+
fs10.rmSync(hooksDir, { recursive: true, force: true });
|
|
3037
3288
|
removed = true;
|
|
3038
3289
|
}
|
|
3039
|
-
if (
|
|
3290
|
+
if (fs10.existsSync(settingsPath)) {
|
|
3040
3291
|
try {
|
|
3041
|
-
const settings = JSON.parse(
|
|
3292
|
+
const settings = JSON.parse(fs10.readFileSync(settingsPath, "utf-8"));
|
|
3042
3293
|
if (settings.hooks !== void 0) {
|
|
3043
3294
|
delete settings.hooks;
|
|
3044
3295
|
settingsCleaned = true;
|
|
3045
3296
|
if (Object.keys(settings).length === 0) {
|
|
3046
|
-
|
|
3297
|
+
fs10.unlinkSync(settingsPath);
|
|
3047
3298
|
} else {
|
|
3048
|
-
|
|
3299
|
+
fs10.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
3049
3300
|
}
|
|
3050
3301
|
}
|
|
3051
3302
|
} catch {
|
|
@@ -3054,7 +3305,7 @@ function removeHooks(projectDir) {
|
|
|
3054
3305
|
return { removed, hooksDir, settingsCleaned };
|
|
3055
3306
|
}
|
|
3056
3307
|
function createRemoveCommand() {
|
|
3057
|
-
return new
|
|
3308
|
+
return new Command28("remove").description("Remove harness-managed hooks from the current project").action(async (_opts, cmd) => {
|
|
3058
3309
|
const globalOpts = cmd.optsWithGlobals();
|
|
3059
3310
|
const projectDir = process.cwd();
|
|
3060
3311
|
const result = removeHooks(projectDir);
|
|
@@ -3076,21 +3327,21 @@ function createRemoveCommand() {
|
|
|
3076
3327
|
}
|
|
3077
3328
|
|
|
3078
3329
|
// src/commands/hooks/add.ts
|
|
3079
|
-
import { Command as
|
|
3080
|
-
import * as
|
|
3081
|
-
import * as
|
|
3330
|
+
import { Command as Command29 } from "commander";
|
|
3331
|
+
import * as fs11 from "fs";
|
|
3332
|
+
import * as path23 from "path";
|
|
3082
3333
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
3083
|
-
var __dirname3 =
|
|
3334
|
+
var __dirname3 = path23.dirname(fileURLToPath3(import.meta.url));
|
|
3084
3335
|
var ALIASES = {
|
|
3085
3336
|
sentinel: ["sentinel-pre", "sentinel-post"]
|
|
3086
3337
|
};
|
|
3087
3338
|
function hookSourceDir() {
|
|
3088
|
-
const d =
|
|
3089
|
-
if (
|
|
3339
|
+
const d = path23.resolve(__dirname3, "..", "..", "hooks");
|
|
3340
|
+
if (fs11.existsSync(d)) return d;
|
|
3090
3341
|
throw new Error(`Hook scripts not found: ${d}`);
|
|
3091
3342
|
}
|
|
3092
3343
|
function readJson(p) {
|
|
3093
|
-
return
|
|
3344
|
+
return fs11.existsSync(p) ? JSON.parse(fs11.readFileSync(p, "utf-8")) : {};
|
|
3094
3345
|
}
|
|
3095
3346
|
function registerHook(s, ev, matcher, name) {
|
|
3096
3347
|
if (!s.hooks[ev]) s.hooks[ev] = [];
|
|
@@ -3103,11 +3354,11 @@ function addHooks(hookName, projectDir) {
|
|
|
3103
3354
|
const names = ALIASES[hookName] ?? [hookName];
|
|
3104
3355
|
const result = { added: [], alreadyInstalled: [], notFound: [] };
|
|
3105
3356
|
const srcDir = hookSourceDir();
|
|
3106
|
-
const destDir =
|
|
3107
|
-
|
|
3108
|
-
const claudeDir =
|
|
3109
|
-
|
|
3110
|
-
const settingsPath =
|
|
3357
|
+
const destDir = path23.join(projectDir, ".harness", "hooks");
|
|
3358
|
+
fs11.mkdirSync(destDir, { recursive: true });
|
|
3359
|
+
const claudeDir = path23.join(projectDir, ".claude");
|
|
3360
|
+
fs11.mkdirSync(claudeDir, { recursive: true });
|
|
3361
|
+
const settingsPath = path23.join(claudeDir, "settings.json");
|
|
3111
3362
|
const settings = readJson(settingsPath);
|
|
3112
3363
|
if (!settings.hooks) settings.hooks = {};
|
|
3113
3364
|
for (const name of names) {
|
|
@@ -3116,24 +3367,24 @@ function addHooks(hookName, projectDir) {
|
|
|
3116
3367
|
result.notFound.push(name);
|
|
3117
3368
|
continue;
|
|
3118
3369
|
}
|
|
3119
|
-
const src =
|
|
3120
|
-
const dest =
|
|
3121
|
-
if (
|
|
3370
|
+
const src = path23.join(srcDir, `${name}.js`);
|
|
3371
|
+
const dest = path23.join(destDir, `${name}.js`);
|
|
3372
|
+
if (fs11.existsSync(dest)) {
|
|
3122
3373
|
result.alreadyInstalled.push(name);
|
|
3123
|
-
} else if (!
|
|
3374
|
+
} else if (!fs11.existsSync(src)) {
|
|
3124
3375
|
result.notFound.push(name);
|
|
3125
3376
|
continue;
|
|
3126
3377
|
} else {
|
|
3127
|
-
|
|
3378
|
+
fs11.copyFileSync(src, dest);
|
|
3128
3379
|
result.added.push(name);
|
|
3129
3380
|
}
|
|
3130
3381
|
registerHook(settings, def.event, def.matcher, name);
|
|
3131
3382
|
}
|
|
3132
|
-
|
|
3383
|
+
fs11.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
3133
3384
|
return result;
|
|
3134
3385
|
}
|
|
3135
3386
|
function createAddCommand2() {
|
|
3136
|
-
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) => {
|
|
3137
3388
|
const projectDir = process.cwd();
|
|
3138
3389
|
try {
|
|
3139
3390
|
const res = addHooks(hookName, projectDir);
|
|
@@ -3158,7 +3409,7 @@ function createAddCommand2() {
|
|
|
3158
3409
|
|
|
3159
3410
|
// src/commands/hooks/index.ts
|
|
3160
3411
|
function createHooksCommand() {
|
|
3161
|
-
const command = new
|
|
3412
|
+
const command = new Command30("hooks").description("Manage Claude Code hook configurations");
|
|
3162
3413
|
command.addCommand(createInitCommand2());
|
|
3163
3414
|
command.addCommand(createListCommand());
|
|
3164
3415
|
command.addCommand(createRemoveCommand());
|
|
@@ -3167,10 +3418,10 @@ function createHooksCommand() {
|
|
|
3167
3418
|
}
|
|
3168
3419
|
|
|
3169
3420
|
// src/commands/impact-preview.ts
|
|
3170
|
-
import { Command as
|
|
3421
|
+
import { Command as Command31 } from "commander";
|
|
3171
3422
|
import { execSync as execSync4 } from "child_process";
|
|
3172
|
-
import * as
|
|
3173
|
-
import * as
|
|
3423
|
+
import * as path24 from "path";
|
|
3424
|
+
import * as fs12 from "fs";
|
|
3174
3425
|
function getStagedFiles(cwd) {
|
|
3175
3426
|
try {
|
|
3176
3427
|
const output = execSync4("git diff --cached --name-only", {
|
|
@@ -3184,7 +3435,7 @@ function getStagedFiles(cwd) {
|
|
|
3184
3435
|
}
|
|
3185
3436
|
function graphExists(projectPath) {
|
|
3186
3437
|
try {
|
|
3187
|
-
return
|
|
3438
|
+
return fs12.existsSync(path24.join(projectPath, ".harness", "graph", "graph.json"));
|
|
3188
3439
|
} catch {
|
|
3189
3440
|
return false;
|
|
3190
3441
|
}
|
|
@@ -3193,7 +3444,7 @@ function extractNodeName(id) {
|
|
|
3193
3444
|
const parts = id.split(":");
|
|
3194
3445
|
if (parts.length > 1) {
|
|
3195
3446
|
const fullPath = parts.slice(1).join(":");
|
|
3196
|
-
return
|
|
3447
|
+
return path24.basename(fullPath);
|
|
3197
3448
|
}
|
|
3198
3449
|
return id;
|
|
3199
3450
|
}
|
|
@@ -3348,7 +3599,7 @@ function formatImpactOutput(stagedFiles, acc, options) {
|
|
|
3348
3599
|
return options.detailed ? formatDetailed(stagedFiles.length, merged, acc.aggregateCounts) : formatCompact(stagedFiles.length, merged, acc.aggregateCounts);
|
|
3349
3600
|
}
|
|
3350
3601
|
async function runImpactPreview(options) {
|
|
3351
|
-
const projectPath =
|
|
3602
|
+
const projectPath = path24.resolve(options.path ?? process.cwd());
|
|
3352
3603
|
const stagedFiles = getStagedFiles(projectPath);
|
|
3353
3604
|
if (stagedFiles.length === 0) return "Impact Preview: no staged changes";
|
|
3354
3605
|
if (!graphExists(projectPath)) {
|
|
@@ -3366,7 +3617,7 @@ async function runImpactPreview(options) {
|
|
|
3366
3617
|
return formatImpactOutput(stagedFiles, acc, options);
|
|
3367
3618
|
}
|
|
3368
3619
|
function createImpactPreviewCommand() {
|
|
3369
|
-
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) => {
|
|
3370
3621
|
const output = await runImpactPreview({
|
|
3371
3622
|
detailed: opts.detailed,
|
|
3372
3623
|
perFile: opts.perFile,
|
|
@@ -3379,28 +3630,29 @@ function createImpactPreviewCommand() {
|
|
|
3379
3630
|
}
|
|
3380
3631
|
|
|
3381
3632
|
// src/commands/init.ts
|
|
3382
|
-
|
|
3633
|
+
init_dist();
|
|
3634
|
+
import { Command as Command33 } from "commander";
|
|
3383
3635
|
import chalk3 from "chalk";
|
|
3384
|
-
import * as
|
|
3385
|
-
import * as
|
|
3636
|
+
import * as fs15 from "fs";
|
|
3637
|
+
import * as path27 from "path";
|
|
3386
3638
|
|
|
3387
3639
|
// src/commands/setup-mcp.ts
|
|
3388
|
-
import { Command as
|
|
3389
|
-
import * as
|
|
3390
|
-
import * as
|
|
3640
|
+
import { Command as Command32 } from "commander";
|
|
3641
|
+
import * as fs14 from "fs";
|
|
3642
|
+
import * as path26 from "path";
|
|
3391
3643
|
import * as os2 from "os";
|
|
3392
3644
|
import chalk2 from "chalk";
|
|
3393
3645
|
import * as clack from "@clack/prompts";
|
|
3394
3646
|
|
|
3395
3647
|
// src/integrations/toml.ts
|
|
3396
|
-
import * as
|
|
3397
|
-
import * as
|
|
3648
|
+
import * as fs13 from "fs";
|
|
3649
|
+
import * as path25 from "path";
|
|
3398
3650
|
function writeTomlMcpEntry(filePath, name, entry) {
|
|
3399
|
-
const dir =
|
|
3400
|
-
if (!
|
|
3401
|
-
|
|
3651
|
+
const dir = path25.dirname(filePath);
|
|
3652
|
+
if (!fs13.existsSync(dir)) {
|
|
3653
|
+
fs13.mkdirSync(dir, { recursive: true });
|
|
3402
3654
|
}
|
|
3403
|
-
const existing =
|
|
3655
|
+
const existing = fs13.existsSync(filePath) ? fs13.readFileSync(filePath, "utf-8") : "";
|
|
3404
3656
|
const blockHeader = `[mcp_servers.${name}]`;
|
|
3405
3657
|
const newBlock = serializeTomlMcpBlock(name, entry);
|
|
3406
3658
|
let updated;
|
|
@@ -3411,8 +3663,8 @@ function writeTomlMcpEntry(filePath, name, entry) {
|
|
|
3411
3663
|
updated = existing + separator + newBlock;
|
|
3412
3664
|
}
|
|
3413
3665
|
const tmp = filePath + ".tmp";
|
|
3414
|
-
|
|
3415
|
-
|
|
3666
|
+
fs13.writeFileSync(tmp, updated);
|
|
3667
|
+
fs13.renameSync(tmp, filePath);
|
|
3416
3668
|
}
|
|
3417
3669
|
function serializeTomlMcpBlock(name, entry) {
|
|
3418
3670
|
const lines = [`[mcp_servers.${name}]`];
|
|
@@ -3470,6 +3722,7 @@ var CURSOR_CURATED_TOOLS = [
|
|
|
3470
3722
|
"get_impact",
|
|
3471
3723
|
"detect_entropy",
|
|
3472
3724
|
"run_security_scan",
|
|
3725
|
+
"get_security_trends",
|
|
3473
3726
|
"assess_project",
|
|
3474
3727
|
"manage_state",
|
|
3475
3728
|
"create_self_review",
|
|
@@ -3514,6 +3767,7 @@ var ALL_MCP_TOOLS = [
|
|
|
3514
3767
|
"ingest_source",
|
|
3515
3768
|
"generate_agent_definitions",
|
|
3516
3769
|
"run_security_scan",
|
|
3770
|
+
"get_security_trends",
|
|
3517
3771
|
"check_performance",
|
|
3518
3772
|
"get_perf_baselines",
|
|
3519
3773
|
"update_perf_baselines",
|
|
@@ -3540,7 +3794,8 @@ var ALL_MCP_TOOLS = [
|
|
|
3540
3794
|
"recommend_skills",
|
|
3541
3795
|
"compute_blast_radius",
|
|
3542
3796
|
"dispatch_skills",
|
|
3543
|
-
"compact"
|
|
3797
|
+
"compact",
|
|
3798
|
+
"detect_constraint_emergence"
|
|
3544
3799
|
];
|
|
3545
3800
|
async function runCursorToolPicker() {
|
|
3546
3801
|
try {
|
|
@@ -3568,20 +3823,20 @@ function writeCursorMcpEntryWithTools(configPath, tools) {
|
|
|
3568
3823
|
});
|
|
3569
3824
|
}
|
|
3570
3825
|
function readJsonFile(filePath) {
|
|
3571
|
-
if (!
|
|
3826
|
+
if (!fs14.existsSync(filePath)) return null;
|
|
3572
3827
|
try {
|
|
3573
|
-
return JSON.parse(
|
|
3828
|
+
return JSON.parse(fs14.readFileSync(filePath, "utf-8"));
|
|
3574
3829
|
} catch {
|
|
3575
|
-
|
|
3830
|
+
fs14.copyFileSync(filePath, filePath + ".bak");
|
|
3576
3831
|
return null;
|
|
3577
3832
|
}
|
|
3578
3833
|
}
|
|
3579
3834
|
function writeJsonFile(filePath, data) {
|
|
3580
|
-
const dir =
|
|
3581
|
-
if (!
|
|
3582
|
-
|
|
3835
|
+
const dir = path26.dirname(filePath);
|
|
3836
|
+
if (!fs14.existsSync(dir)) {
|
|
3837
|
+
fs14.mkdirSync(dir, { recursive: true });
|
|
3583
3838
|
}
|
|
3584
|
-
|
|
3839
|
+
fs14.writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n");
|
|
3585
3840
|
}
|
|
3586
3841
|
function configureMcpServer(configPath) {
|
|
3587
3842
|
const config = readJsonFile(configPath) ?? {};
|
|
@@ -3596,7 +3851,7 @@ function configureMcpServer(configPath) {
|
|
|
3596
3851
|
return true;
|
|
3597
3852
|
}
|
|
3598
3853
|
function addGeminiTrustedFolder(cwd) {
|
|
3599
|
-
const trustedPath =
|
|
3854
|
+
const trustedPath = path26.join(os2.homedir(), ".gemini", "trustedFolders.json");
|
|
3600
3855
|
const folders = readJsonFile(trustedPath) ?? {};
|
|
3601
3856
|
if (folders[cwd] === "TRUST_FOLDER") {
|
|
3602
3857
|
return false;
|
|
@@ -3610,7 +3865,7 @@ function setupMcp(cwd, client) {
|
|
|
3610
3865
|
const skipped = [];
|
|
3611
3866
|
let trustedFolder = false;
|
|
3612
3867
|
if (client === "all" || client === "claude") {
|
|
3613
|
-
const configPath =
|
|
3868
|
+
const configPath = path26.join(cwd, ".mcp.json");
|
|
3614
3869
|
if (configureMcpServer(configPath)) {
|
|
3615
3870
|
configured.push("Claude Code");
|
|
3616
3871
|
} else {
|
|
@@ -3618,7 +3873,7 @@ function setupMcp(cwd, client) {
|
|
|
3618
3873
|
}
|
|
3619
3874
|
}
|
|
3620
3875
|
if (client === "all" || client === "gemini") {
|
|
3621
|
-
const configPath =
|
|
3876
|
+
const configPath = path26.join(cwd, ".gemini", "settings.json");
|
|
3622
3877
|
if (configureMcpServer(configPath)) {
|
|
3623
3878
|
configured.push("Gemini CLI");
|
|
3624
3879
|
} else {
|
|
@@ -3627,10 +3882,10 @@ function setupMcp(cwd, client) {
|
|
|
3627
3882
|
trustedFolder = addGeminiTrustedFolder(cwd);
|
|
3628
3883
|
}
|
|
3629
3884
|
if (client === "all" || client === "codex") {
|
|
3630
|
-
const configPath =
|
|
3885
|
+
const configPath = path26.join(cwd, ".codex", "config.toml");
|
|
3631
3886
|
const alreadyConfigured = (() => {
|
|
3632
|
-
if (!
|
|
3633
|
-
const content =
|
|
3887
|
+
if (!fs14.existsSync(configPath)) return false;
|
|
3888
|
+
const content = fs14.readFileSync(configPath, "utf-8");
|
|
3634
3889
|
return content.includes("[mcp_servers.harness]");
|
|
3635
3890
|
})();
|
|
3636
3891
|
if (alreadyConfigured) {
|
|
@@ -3645,7 +3900,7 @@ function setupMcp(cwd, client) {
|
|
|
3645
3900
|
}
|
|
3646
3901
|
}
|
|
3647
3902
|
if (client === "all" || client === "cursor") {
|
|
3648
|
-
const configPath =
|
|
3903
|
+
const configPath = path26.join(cwd, ".cursor", "mcp.json");
|
|
3649
3904
|
const existing = readJsonFile(configPath);
|
|
3650
3905
|
if (existing?.mcpServers?.["harness"]) {
|
|
3651
3906
|
skipped.push("Cursor");
|
|
@@ -3659,7 +3914,7 @@ function setupMcp(cwd, client) {
|
|
|
3659
3914
|
async function resolveCursorWithPicker(cwd, pick) {
|
|
3660
3915
|
const configured = [];
|
|
3661
3916
|
const skipped = [];
|
|
3662
|
-
const cursorConfigPath =
|
|
3917
|
+
const cursorConfigPath = path26.join(cwd, ".cursor", "mcp.json");
|
|
3663
3918
|
const existing = readJsonFile(cursorConfigPath);
|
|
3664
3919
|
if (existing?.mcpServers?.["harness"] && !pick) {
|
|
3665
3920
|
skipped.push("Cursor");
|
|
@@ -3703,7 +3958,7 @@ function printMcpResult(configured, skipped, trustedFolder) {
|
|
|
3703
3958
|
console.log("");
|
|
3704
3959
|
}
|
|
3705
3960
|
function createSetupMcpCommand() {
|
|
3706
|
-
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) => {
|
|
3707
3962
|
const globalOpts = cmd.optsWithGlobals();
|
|
3708
3963
|
const cwd = process.cwd();
|
|
3709
3964
|
let configured;
|
|
@@ -3730,12 +3985,12 @@ function loadEngineAndTemplates(_options) {
|
|
|
3730
3985
|
}
|
|
3731
3986
|
function resolveInitDefaults(options) {
|
|
3732
3987
|
const cwd = options.cwd ?? process.cwd();
|
|
3733
|
-
return { cwd, name: options.name ??
|
|
3988
|
+
return { cwd, name: options.name ?? path27.basename(cwd), force: options.force ?? false };
|
|
3734
3989
|
}
|
|
3735
3990
|
async function runInit(options) {
|
|
3736
3991
|
const { cwd, name, force } = resolveInitDefaults(options);
|
|
3737
|
-
const configPath =
|
|
3738
|
-
if (!force &&
|
|
3992
|
+
const configPath = path27.join(cwd, "harness.config.json");
|
|
3993
|
+
if (!force && fs15.existsSync(configPath)) {
|
|
3739
3994
|
return Err(
|
|
3740
3995
|
new CLIError("Project already initialized. Use --force to overwrite.", ExitCode.ERROR)
|
|
3741
3996
|
);
|
|
@@ -3843,11 +4098,11 @@ async function runInitAction2(opts) {
|
|
|
3843
4098
|
process.exit(ExitCode.SUCCESS);
|
|
3844
4099
|
}
|
|
3845
4100
|
function createInitCommand3() {
|
|
3846
|
-
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) => {
|
|
3847
4102
|
const globalOpts = cmd.optsWithGlobals();
|
|
3848
4103
|
await runInitAction2({
|
|
3849
4104
|
name: opts.name,
|
|
3850
|
-
level: opts.level,
|
|
4105
|
+
level: opts.template ?? opts.level,
|
|
3851
4106
|
framework: opts.framework,
|
|
3852
4107
|
language: opts.language,
|
|
3853
4108
|
force: opts.force,
|
|
@@ -3858,16 +4113,16 @@ function createInitCommand3() {
|
|
|
3858
4113
|
}
|
|
3859
4114
|
|
|
3860
4115
|
// src/commands/install.ts
|
|
3861
|
-
import * as
|
|
4116
|
+
import * as fs20 from "fs";
|
|
3862
4117
|
import * as os5 from "os";
|
|
3863
|
-
import * as
|
|
4118
|
+
import * as path32 from "path";
|
|
3864
4119
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
3865
|
-
import { Command as
|
|
4120
|
+
import { Command as Command34 } from "commander";
|
|
3866
4121
|
import { parse as yamlParse } from "yaml";
|
|
3867
4122
|
|
|
3868
4123
|
// src/registry/npm-client.ts
|
|
3869
|
-
import * as
|
|
3870
|
-
import * as
|
|
4124
|
+
import * as fs16 from "fs";
|
|
4125
|
+
import * as path28 from "path";
|
|
3871
4126
|
import * as os3 from "os";
|
|
3872
4127
|
var NPM_REGISTRY = "https://registry.npmjs.org";
|
|
3873
4128
|
var FETCH_TIMEOUT_MS = 3e4;
|
|
@@ -3890,10 +4145,10 @@ function extractSkillName(packageName) {
|
|
|
3890
4145
|
function readNpmrcToken(registryUrl) {
|
|
3891
4146
|
const { hostname, pathname } = new URL(registryUrl);
|
|
3892
4147
|
const registryPath = `//${hostname}${pathname.replace(/\/$/, "")}/:_authToken=`;
|
|
3893
|
-
const candidates = [
|
|
4148
|
+
const candidates = [path28.join(process.cwd(), ".npmrc"), path28.join(os3.homedir(), ".npmrc")];
|
|
3894
4149
|
for (const npmrcPath of candidates) {
|
|
3895
4150
|
try {
|
|
3896
|
-
const content =
|
|
4151
|
+
const content = fs16.readFileSync(npmrcPath, "utf-8");
|
|
3897
4152
|
for (const line of content.split("\n")) {
|
|
3898
4153
|
const trimmed = line.trim();
|
|
3899
4154
|
if (trimmed.startsWith(registryPath)) {
|
|
@@ -3988,19 +4243,19 @@ async function searchNpmRegistry(query, registryUrl) {
|
|
|
3988
4243
|
}
|
|
3989
4244
|
|
|
3990
4245
|
// src/registry/tarball.ts
|
|
3991
|
-
import * as
|
|
3992
|
-
import * as
|
|
4246
|
+
import * as fs17 from "fs";
|
|
4247
|
+
import * as path29 from "path";
|
|
3993
4248
|
import * as os4 from "os";
|
|
3994
4249
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
3995
4250
|
function extractTarball(tarballBuffer) {
|
|
3996
|
-
const tmpDir =
|
|
3997
|
-
const tarballPath =
|
|
4251
|
+
const tmpDir = fs17.mkdtempSync(path29.join(os4.tmpdir(), "harness-skill-install-"));
|
|
4252
|
+
const tarballPath = path29.join(tmpDir, "package.tgz");
|
|
3998
4253
|
try {
|
|
3999
|
-
|
|
4254
|
+
fs17.writeFileSync(tarballPath, tarballBuffer);
|
|
4000
4255
|
execFileSync2("tar", ["-xzf", tarballPath, "-C", tmpDir], {
|
|
4001
4256
|
timeout: 3e4
|
|
4002
4257
|
});
|
|
4003
|
-
|
|
4258
|
+
fs17.unlinkSync(tarballPath);
|
|
4004
4259
|
} catch (err) {
|
|
4005
4260
|
cleanupTempDir(tmpDir);
|
|
4006
4261
|
throw new Error(
|
|
@@ -4011,37 +4266,37 @@ function extractTarball(tarballBuffer) {
|
|
|
4011
4266
|
return tmpDir;
|
|
4012
4267
|
}
|
|
4013
4268
|
function placeSkillContent(extractedPkgDir, communityBaseDir, skillName, platforms) {
|
|
4014
|
-
const files =
|
|
4269
|
+
const files = fs17.readdirSync(extractedPkgDir);
|
|
4015
4270
|
for (const platform of platforms) {
|
|
4016
|
-
const targetDir =
|
|
4017
|
-
if (
|
|
4018
|
-
|
|
4271
|
+
const targetDir = path29.join(communityBaseDir, platform, skillName);
|
|
4272
|
+
if (fs17.existsSync(targetDir)) {
|
|
4273
|
+
fs17.rmSync(targetDir, { recursive: true, force: true });
|
|
4019
4274
|
}
|
|
4020
|
-
|
|
4275
|
+
fs17.mkdirSync(targetDir, { recursive: true });
|
|
4021
4276
|
for (const file of files) {
|
|
4022
4277
|
if (file === "package.json" || file === "node_modules") continue;
|
|
4023
|
-
const srcPath =
|
|
4024
|
-
const destPath =
|
|
4025
|
-
const stat =
|
|
4278
|
+
const srcPath = path29.join(extractedPkgDir, file);
|
|
4279
|
+
const destPath = path29.join(targetDir, file);
|
|
4280
|
+
const stat = fs17.statSync(srcPath);
|
|
4026
4281
|
if (stat.isDirectory()) {
|
|
4027
|
-
|
|
4282
|
+
fs17.cpSync(srcPath, destPath, { recursive: true });
|
|
4028
4283
|
} else {
|
|
4029
|
-
|
|
4284
|
+
fs17.copyFileSync(srcPath, destPath);
|
|
4030
4285
|
}
|
|
4031
4286
|
}
|
|
4032
4287
|
}
|
|
4033
4288
|
}
|
|
4034
4289
|
function removeSkillContent(communityBaseDir, skillName, platforms) {
|
|
4035
4290
|
for (const platform of platforms) {
|
|
4036
|
-
const targetDir =
|
|
4037
|
-
if (
|
|
4038
|
-
|
|
4291
|
+
const targetDir = path29.join(communityBaseDir, platform, skillName);
|
|
4292
|
+
if (fs17.existsSync(targetDir)) {
|
|
4293
|
+
fs17.rmSync(targetDir, { recursive: true, force: true });
|
|
4039
4294
|
}
|
|
4040
4295
|
}
|
|
4041
4296
|
}
|
|
4042
4297
|
function cleanupTempDir(dirPath) {
|
|
4043
4298
|
try {
|
|
4044
|
-
|
|
4299
|
+
fs17.rmSync(dirPath, { recursive: true, force: true });
|
|
4045
4300
|
} catch {
|
|
4046
4301
|
}
|
|
4047
4302
|
}
|
|
@@ -4083,8 +4338,8 @@ function findDependentsOf(lockfile, targetPackageName) {
|
|
|
4083
4338
|
}
|
|
4084
4339
|
|
|
4085
4340
|
// src/registry/lockfile.ts
|
|
4086
|
-
import * as
|
|
4087
|
-
import * as
|
|
4341
|
+
import * as fs18 from "fs";
|
|
4342
|
+
import * as path30 from "path";
|
|
4088
4343
|
function createEmptyLockfile() {
|
|
4089
4344
|
return { version: 1, skills: {} };
|
|
4090
4345
|
}
|
|
@@ -4104,10 +4359,10 @@ function sortedStringify(obj) {
|
|
|
4104
4359
|
);
|
|
4105
4360
|
}
|
|
4106
4361
|
function readLockfile2(filePath) {
|
|
4107
|
-
if (!
|
|
4362
|
+
if (!fs18.existsSync(filePath)) {
|
|
4108
4363
|
return createEmptyLockfile();
|
|
4109
4364
|
}
|
|
4110
|
-
const raw =
|
|
4365
|
+
const raw = fs18.readFileSync(filePath, "utf-8");
|
|
4111
4366
|
let parsed;
|
|
4112
4367
|
try {
|
|
4113
4368
|
parsed = JSON.parse(raw);
|
|
@@ -4124,9 +4379,9 @@ function readLockfile2(filePath) {
|
|
|
4124
4379
|
return parsed;
|
|
4125
4380
|
}
|
|
4126
4381
|
function writeLockfile2(filePath, lockfile) {
|
|
4127
|
-
const dir =
|
|
4128
|
-
|
|
4129
|
-
|
|
4382
|
+
const dir = path30.dirname(filePath);
|
|
4383
|
+
fs18.mkdirSync(dir, { recursive: true });
|
|
4384
|
+
fs18.writeFileSync(filePath, sortedStringify(lockfile) + "\n", "utf-8");
|
|
4130
4385
|
}
|
|
4131
4386
|
function updateLockfileEntry(lockfile, name, entry) {
|
|
4132
4387
|
return {
|
|
@@ -4149,17 +4404,17 @@ function removeLockfileEntry(lockfile, name) {
|
|
|
4149
4404
|
}
|
|
4150
4405
|
|
|
4151
4406
|
// src/registry/bundled-skills.ts
|
|
4152
|
-
import * as
|
|
4153
|
-
import * as
|
|
4407
|
+
import * as fs19 from "fs";
|
|
4408
|
+
import * as path31 from "path";
|
|
4154
4409
|
function getBundledSkillNames(bundledSkillsDir) {
|
|
4155
|
-
if (!
|
|
4410
|
+
if (!fs19.existsSync(bundledSkillsDir)) {
|
|
4156
4411
|
return /* @__PURE__ */ new Set();
|
|
4157
4412
|
}
|
|
4158
|
-
const entries =
|
|
4413
|
+
const entries = fs19.readdirSync(bundledSkillsDir);
|
|
4159
4414
|
const names = /* @__PURE__ */ new Set();
|
|
4160
4415
|
for (const entry of entries) {
|
|
4161
4416
|
try {
|
|
4162
|
-
const stat =
|
|
4417
|
+
const stat = fs19.statSync(path31.join(bundledSkillsDir, String(entry)));
|
|
4163
4418
|
if (stat.isDirectory()) {
|
|
4164
4419
|
names.add(String(entry));
|
|
4165
4420
|
}
|
|
@@ -4186,12 +4441,12 @@ function validateSkillYaml(parsed) {
|
|
|
4186
4441
|
function resolveCommunityBase(global) {
|
|
4187
4442
|
if (global) {
|
|
4188
4443
|
const communityBase2 = resolveGlobalCommunityBaseDir();
|
|
4189
|
-
return { communityBase: communityBase2, lockfilePath:
|
|
4444
|
+
return { communityBase: communityBase2, lockfilePath: path32.join(communityBase2, "skills-lock.json") };
|
|
4190
4445
|
}
|
|
4191
4446
|
const globalDir = resolveGlobalSkillsDir();
|
|
4192
|
-
const skillsDir =
|
|
4193
|
-
const communityBase =
|
|
4194
|
-
return { communityBase, lockfilePath:
|
|
4447
|
+
const skillsDir = path32.dirname(globalDir);
|
|
4448
|
+
const communityBase = path32.join(skillsDir, "community");
|
|
4449
|
+
return { communityBase, lockfilePath: path32.join(communityBase, "skills-lock.json") };
|
|
4195
4450
|
}
|
|
4196
4451
|
function parseGitHubRef(from) {
|
|
4197
4452
|
const ghPrefix = from.match(/^github:([^/]+)\/([^#]+?)(?:#(.+))?$/);
|
|
@@ -4207,7 +4462,7 @@ function parseGitHubRef(from) {
|
|
|
4207
4462
|
return null;
|
|
4208
4463
|
}
|
|
4209
4464
|
function cloneGitHubRepo(owner, repo, ref) {
|
|
4210
|
-
const tmpDir =
|
|
4465
|
+
const tmpDir = fs20.mkdtempSync(path32.join(os5.tmpdir(), "harness-gh-install-"));
|
|
4211
4466
|
const url = `https://github.com/${owner}/${repo}.git`;
|
|
4212
4467
|
try {
|
|
4213
4468
|
const cloneArgs = ["clone", "--depth", "1"];
|
|
@@ -4228,43 +4483,43 @@ function discoverSkillDirs(rootDir) {
|
|
|
4228
4483
|
const skillDirs = [];
|
|
4229
4484
|
function scan(dir, depth) {
|
|
4230
4485
|
if (depth > 3) return;
|
|
4231
|
-
if (!
|
|
4232
|
-
if (
|
|
4486
|
+
if (!fs20.existsSync(dir)) return;
|
|
4487
|
+
if (fs20.existsSync(path32.join(dir, "skill.yaml"))) {
|
|
4233
4488
|
skillDirs.push(dir);
|
|
4234
4489
|
return;
|
|
4235
4490
|
}
|
|
4236
|
-
const entries =
|
|
4491
|
+
const entries = fs20.readdirSync(dir, { withFileTypes: true });
|
|
4237
4492
|
for (const entry of entries) {
|
|
4238
4493
|
if (!entry.isDirectory()) continue;
|
|
4239
4494
|
if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
|
|
4240
|
-
scan(
|
|
4495
|
+
scan(path32.join(dir, entry.name), depth + 1);
|
|
4241
4496
|
}
|
|
4242
4497
|
}
|
|
4243
4498
|
scan(rootDir, 0);
|
|
4244
4499
|
return skillDirs;
|
|
4245
4500
|
}
|
|
4246
4501
|
function resolveLocalPkgDir(fromPath) {
|
|
4247
|
-
const resolvedPath =
|
|
4248
|
-
if (!
|
|
4502
|
+
const resolvedPath = path32.resolve(fromPath);
|
|
4503
|
+
if (!fs20.existsSync(resolvedPath)) {
|
|
4249
4504
|
throw new Error(`--from path does not exist: ${resolvedPath}`);
|
|
4250
4505
|
}
|
|
4251
|
-
const stat =
|
|
4506
|
+
const stat = fs20.statSync(resolvedPath);
|
|
4252
4507
|
if (stat.isDirectory()) {
|
|
4253
4508
|
return { pkgDir: resolvedPath, extractDir: null };
|
|
4254
4509
|
}
|
|
4255
4510
|
if (resolvedPath.endsWith(".tgz") || resolvedPath.endsWith(".tar.gz")) {
|
|
4256
|
-
const tarballBuffer =
|
|
4511
|
+
const tarballBuffer = fs20.readFileSync(resolvedPath);
|
|
4257
4512
|
const extractDir = extractTarball(tarballBuffer);
|
|
4258
|
-
return { pkgDir:
|
|
4513
|
+
return { pkgDir: path32.join(extractDir, "package"), extractDir };
|
|
4259
4514
|
}
|
|
4260
4515
|
throw new Error(`--from path must be a directory or .tgz file. Got: ${resolvedPath}`);
|
|
4261
4516
|
}
|
|
4262
4517
|
function installSkillDir(pkgDir, resolvedPath, options) {
|
|
4263
|
-
const skillYamlPath =
|
|
4264
|
-
if (!
|
|
4518
|
+
const skillYamlPath = path32.join(pkgDir, "skill.yaml");
|
|
4519
|
+
if (!fs20.existsSync(skillYamlPath)) {
|
|
4265
4520
|
throw new Error(`No skill.yaml found at ${skillYamlPath}`);
|
|
4266
4521
|
}
|
|
4267
|
-
const skillYaml = validateSkillYaml(yamlParse(
|
|
4522
|
+
const skillYaml = validateSkillYaml(yamlParse(fs20.readFileSync(skillYamlPath, "utf-8")));
|
|
4268
4523
|
const shortName = skillYaml.name;
|
|
4269
4524
|
const { communityBase, lockfilePath } = resolveCommunityBase(options.global ?? false);
|
|
4270
4525
|
if (!options.global) {
|
|
@@ -4292,7 +4547,7 @@ function installSkillDir(pkgDir, resolvedPath, options) {
|
|
|
4292
4547
|
async function runLocalInstall(fromPath, options) {
|
|
4293
4548
|
const { pkgDir, extractDir } = resolveLocalPkgDir(fromPath);
|
|
4294
4549
|
try {
|
|
4295
|
-
return installSkillDir(pkgDir,
|
|
4550
|
+
return installSkillDir(pkgDir, path32.resolve(fromPath), options);
|
|
4296
4551
|
} finally {
|
|
4297
4552
|
if (extractDir) cleanupTempDir(extractDir);
|
|
4298
4553
|
}
|
|
@@ -4335,8 +4590,8 @@ async function runInstall(skillName, options) {
|
|
|
4335
4590
|
version: installed.map((r) => r.version).join(", ")
|
|
4336
4591
|
};
|
|
4337
4592
|
}
|
|
4338
|
-
const resolvedFrom =
|
|
4339
|
-
if (
|
|
4593
|
+
const resolvedFrom = path32.resolve(options.from);
|
|
4594
|
+
if (fs20.existsSync(resolvedFrom) && fs20.statSync(resolvedFrom).isDirectory() && !fs20.existsSync(path32.join(resolvedFrom, "skill.yaml"))) {
|
|
4340
4595
|
const results = await runBulkInstall(resolvedFrom, options);
|
|
4341
4596
|
const installed = results.filter((r) => r.installed);
|
|
4342
4597
|
return {
|
|
@@ -4377,12 +4632,12 @@ async function runInstall(skillName, options) {
|
|
|
4377
4632
|
const extractDir = extractTarball(tarballBuffer);
|
|
4378
4633
|
let skillYaml;
|
|
4379
4634
|
try {
|
|
4380
|
-
const extractedPkgDir =
|
|
4381
|
-
const skillYamlPath =
|
|
4382
|
-
if (!
|
|
4635
|
+
const extractedPkgDir = path32.join(extractDir, "package");
|
|
4636
|
+
const skillYamlPath = path32.join(extractedPkgDir, "skill.yaml");
|
|
4637
|
+
if (!fs20.existsSync(skillYamlPath)) {
|
|
4383
4638
|
throw new Error(`contains invalid skill.yaml: file not found in package`);
|
|
4384
4639
|
}
|
|
4385
|
-
const rawYaml =
|
|
4640
|
+
const rawYaml = fs20.readFileSync(skillYamlPath, "utf-8");
|
|
4386
4641
|
const parsed = yamlParse(rawYaml);
|
|
4387
4642
|
skillYaml = validateSkillYaml(parsed);
|
|
4388
4643
|
placeSkillContent(extractedPkgDir, communityBase, shortName, skillYaml.platforms);
|
|
@@ -4422,7 +4677,7 @@ async function runInstall(skillName, options) {
|
|
|
4422
4677
|
return result;
|
|
4423
4678
|
}
|
|
4424
4679
|
function createInstallCommand() {
|
|
4425
|
-
const cmd = new
|
|
4680
|
+
const cmd = new Command34("install");
|
|
4426
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(
|
|
4427
4682
|
"--from <source>",
|
|
4428
4683
|
"Install from local path, directory, or GitHub (github:owner/repo, https://github.com/owner/repo)"
|
|
@@ -4455,15 +4710,16 @@ function createInstallCommand() {
|
|
|
4455
4710
|
}
|
|
4456
4711
|
|
|
4457
4712
|
// src/commands/install-constraints.ts
|
|
4458
|
-
|
|
4459
|
-
import * as
|
|
4460
|
-
import
|
|
4713
|
+
init_dist();
|
|
4714
|
+
import * as fs21 from "fs/promises";
|
|
4715
|
+
import * as path33 from "path";
|
|
4716
|
+
import { Command as Command35 } from "commander";
|
|
4461
4717
|
import semver3 from "semver";
|
|
4462
4718
|
async function runInstallConstraints(options) {
|
|
4463
4719
|
const { source, configPath, lockfilePath } = options;
|
|
4464
4720
|
let rawBundle;
|
|
4465
4721
|
try {
|
|
4466
|
-
rawBundle = await
|
|
4722
|
+
rawBundle = await fs21.readFile(source, "utf-8");
|
|
4467
4723
|
} catch (err) {
|
|
4468
4724
|
if (isNodeError(err) && err.code === "ENOENT") {
|
|
4469
4725
|
return { ok: false, error: `Bundle file not found: ${source}` };
|
|
@@ -4506,7 +4762,7 @@ async function runInstallConstraints(options) {
|
|
|
4506
4762
|
}
|
|
4507
4763
|
let localConfig;
|
|
4508
4764
|
try {
|
|
4509
|
-
const raw = await
|
|
4765
|
+
const raw = await fs21.readFile(configPath, "utf-8");
|
|
4510
4766
|
localConfig = JSON.parse(raw);
|
|
4511
4767
|
} catch (err) {
|
|
4512
4768
|
return {
|
|
@@ -4654,7 +4910,7 @@ function isNodeError(err) {
|
|
|
4654
4910
|
return err instanceof Error && "code" in err;
|
|
4655
4911
|
}
|
|
4656
4912
|
function resolveConfigPath(opts) {
|
|
4657
|
-
if (opts.config) return
|
|
4913
|
+
if (opts.config) return path33.resolve(opts.config);
|
|
4658
4914
|
const found = findConfigFile();
|
|
4659
4915
|
if (!found.ok) {
|
|
4660
4916
|
logger.error(found.error.message);
|
|
@@ -4689,9 +4945,9 @@ function logInstallResult(val, opts) {
|
|
|
4689
4945
|
}
|
|
4690
4946
|
async function handleInstallConstraints(source, opts) {
|
|
4691
4947
|
const configPath = resolveConfigPath(opts);
|
|
4692
|
-
const projectRoot =
|
|
4693
|
-
const lockfilePath =
|
|
4694
|
-
const resolvedSource =
|
|
4948
|
+
const projectRoot = path33.dirname(configPath);
|
|
4949
|
+
const lockfilePath = path33.join(projectRoot, ".harness", "constraints.lock.json");
|
|
4950
|
+
const resolvedSource = path33.resolve(source);
|
|
4695
4951
|
if (opts.forceLocal && opts.forcePackage) {
|
|
4696
4952
|
logger.error("Cannot use both --force-local and --force-package.");
|
|
4697
4953
|
process.exit(1);
|
|
@@ -4711,18 +4967,19 @@ async function handleInstallConstraints(source, opts) {
|
|
|
4711
4967
|
logInstallResult(result.value, opts);
|
|
4712
4968
|
}
|
|
4713
4969
|
function createInstallConstraintsCommand() {
|
|
4714
|
-
const cmd = new
|
|
4970
|
+
const cmd = new Command35("install-constraints");
|
|
4715
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);
|
|
4716
4972
|
return cmd;
|
|
4717
4973
|
}
|
|
4718
4974
|
|
|
4719
4975
|
// src/commands/integrations/index.ts
|
|
4720
|
-
import { Command as
|
|
4976
|
+
import { Command as Command40 } from "commander";
|
|
4721
4977
|
|
|
4722
4978
|
// src/commands/integrations/add.ts
|
|
4723
|
-
|
|
4724
|
-
import
|
|
4725
|
-
import * as
|
|
4979
|
+
init_dist();
|
|
4980
|
+
import { Command as Command36 } from "commander";
|
|
4981
|
+
import * as fs22 from "fs";
|
|
4982
|
+
import * as path34 from "path";
|
|
4726
4983
|
import chalk4 from "chalk";
|
|
4727
4984
|
function buildMcpEntry(def) {
|
|
4728
4985
|
const entry = { command: def.mcpConfig.command };
|
|
@@ -4731,14 +4988,14 @@ function buildMcpEntry(def) {
|
|
|
4731
4988
|
return entry;
|
|
4732
4989
|
}
|
|
4733
4990
|
function writeMcpEntries(cwd, defName, mcpEntry) {
|
|
4734
|
-
writeMcpEntry(
|
|
4735
|
-
const geminiDir =
|
|
4736
|
-
if (
|
|
4737
|
-
writeMcpEntry(
|
|
4991
|
+
writeMcpEntry(path34.join(cwd, ".mcp.json"), defName, mcpEntry);
|
|
4992
|
+
const geminiDir = path34.join(cwd, ".gemini");
|
|
4993
|
+
if (fs22.existsSync(geminiDir)) {
|
|
4994
|
+
writeMcpEntry(path34.join(geminiDir, "settings.json"), defName, mcpEntry);
|
|
4738
4995
|
}
|
|
4739
4996
|
}
|
|
4740
4997
|
function updateIntegrationsConfig(cwd, defName) {
|
|
4741
|
-
const configPath =
|
|
4998
|
+
const configPath = path34.join(cwd, "harness.config.json");
|
|
4742
4999
|
const integConfig = readIntegrationsConfig(configPath);
|
|
4743
5000
|
if (!integConfig.enabled.includes(defName)) integConfig.enabled.push(defName);
|
|
4744
5001
|
integConfig.dismissed = integConfig.dismissed.filter((d) => d !== defName);
|
|
@@ -4784,7 +5041,7 @@ function printAddSuccess(value) {
|
|
|
4784
5041
|
}
|
|
4785
5042
|
}
|
|
4786
5043
|
function createAddIntegrationCommand() {
|
|
4787
|
-
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) => {
|
|
4788
5045
|
const globalOpts = cmd.optsWithGlobals();
|
|
4789
5046
|
const result = addIntegration(process.cwd(), name);
|
|
4790
5047
|
if (!result.ok) {
|
|
@@ -4798,8 +5055,8 @@ function createAddIntegrationCommand() {
|
|
|
4798
5055
|
}
|
|
4799
5056
|
|
|
4800
5057
|
// src/commands/integrations/list.ts
|
|
4801
|
-
import { Command as
|
|
4802
|
-
import * as
|
|
5058
|
+
import { Command as Command37 } from "commander";
|
|
5059
|
+
import * as path35 from "path";
|
|
4803
5060
|
import chalk5 from "chalk";
|
|
4804
5061
|
function printTier0Integrations(tier0, mcpServers) {
|
|
4805
5062
|
console.log(" Tier 0 (zero-config):");
|
|
@@ -4823,8 +5080,8 @@ function printTier1Integrations(tier1, mcpServers, dismissed) {
|
|
|
4823
5080
|
}
|
|
4824
5081
|
async function runListIntegrations(globalOpts) {
|
|
4825
5082
|
const cwd = process.cwd();
|
|
4826
|
-
const mcpConfig = readMcpConfig(
|
|
4827
|
-
const integConfig = readIntegrationsConfig(
|
|
5083
|
+
const mcpConfig = readMcpConfig(path35.join(cwd, ".mcp.json"));
|
|
5084
|
+
const integConfig = readIntegrationsConfig(path35.join(cwd, "harness.config.json"));
|
|
4828
5085
|
const mcpServers = mcpConfig.mcpServers ?? {};
|
|
4829
5086
|
if (globalOpts.json) {
|
|
4830
5087
|
const entries = INTEGRATION_REGISTRY.map((i) => ({
|
|
@@ -4855,13 +5112,14 @@ async function runListIntegrations(globalOpts) {
|
|
|
4855
5112
|
process.exit(ExitCode.SUCCESS);
|
|
4856
5113
|
}
|
|
4857
5114
|
function createListIntegrationsCommand() {
|
|
4858
|
-
return new
|
|
5115
|
+
return new Command37("list").description("Show all MCP integrations with status").action(async (_opts, cmd) => runListIntegrations(cmd.optsWithGlobals()));
|
|
4859
5116
|
}
|
|
4860
5117
|
|
|
4861
5118
|
// src/commands/integrations/remove.ts
|
|
4862
|
-
|
|
4863
|
-
import
|
|
4864
|
-
import * as
|
|
5119
|
+
init_dist();
|
|
5120
|
+
import { Command as Command38 } from "commander";
|
|
5121
|
+
import * as fs23 from "fs";
|
|
5122
|
+
import * as path36 from "path";
|
|
4865
5123
|
function removeIntegration(cwd, name) {
|
|
4866
5124
|
const def = INTEGRATION_REGISTRY.find((i) => i.name === name);
|
|
4867
5125
|
if (!def) {
|
|
@@ -4872,21 +5130,21 @@ function removeIntegration(cwd, name) {
|
|
|
4872
5130
|
)
|
|
4873
5131
|
);
|
|
4874
5132
|
}
|
|
4875
|
-
const mcpPath =
|
|
5133
|
+
const mcpPath = path36.join(cwd, ".mcp.json");
|
|
4876
5134
|
removeMcpEntry(mcpPath, def.name);
|
|
4877
|
-
const geminiDir =
|
|
4878
|
-
if (
|
|
4879
|
-
const geminiPath =
|
|
5135
|
+
const geminiDir = path36.join(cwd, ".gemini");
|
|
5136
|
+
if (fs23.existsSync(geminiDir)) {
|
|
5137
|
+
const geminiPath = path36.join(geminiDir, "settings.json");
|
|
4880
5138
|
removeMcpEntry(geminiPath, def.name);
|
|
4881
5139
|
}
|
|
4882
|
-
const configPath =
|
|
5140
|
+
const configPath = path36.join(cwd, "harness.config.json");
|
|
4883
5141
|
const integConfig = readIntegrationsConfig(configPath);
|
|
4884
5142
|
integConfig.enabled = integConfig.enabled.filter((e) => e !== def.name);
|
|
4885
5143
|
writeIntegrationsConfig(configPath, integConfig);
|
|
4886
5144
|
return Ok(def.displayName);
|
|
4887
5145
|
}
|
|
4888
5146
|
function createRemoveIntegrationCommand() {
|
|
4889
|
-
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) => {
|
|
4890
5148
|
const globalOpts = cmd.optsWithGlobals();
|
|
4891
5149
|
const cwd = process.cwd();
|
|
4892
5150
|
const result = removeIntegration(cwd, name);
|
|
@@ -4907,8 +5165,9 @@ function createRemoveIntegrationCommand() {
|
|
|
4907
5165
|
}
|
|
4908
5166
|
|
|
4909
5167
|
// src/commands/integrations/dismiss.ts
|
|
4910
|
-
|
|
4911
|
-
import
|
|
5168
|
+
init_dist();
|
|
5169
|
+
import { Command as Command39 } from "commander";
|
|
5170
|
+
import * as path37 from "path";
|
|
4912
5171
|
function dismissIntegration(cwd, name) {
|
|
4913
5172
|
const def = INTEGRATION_REGISTRY.find((i) => i.name === name);
|
|
4914
5173
|
if (!def) {
|
|
@@ -4919,7 +5178,7 @@ function dismissIntegration(cwd, name) {
|
|
|
4919
5178
|
)
|
|
4920
5179
|
);
|
|
4921
5180
|
}
|
|
4922
|
-
const configPath =
|
|
5181
|
+
const configPath = path37.join(cwd, "harness.config.json");
|
|
4923
5182
|
const integConfig = readIntegrationsConfig(configPath);
|
|
4924
5183
|
if (!integConfig.dismissed.includes(def.name)) {
|
|
4925
5184
|
integConfig.dismissed.push(def.name);
|
|
@@ -4929,7 +5188,7 @@ function dismissIntegration(cwd, name) {
|
|
|
4929
5188
|
return Ok(def.displayName);
|
|
4930
5189
|
}
|
|
4931
5190
|
function createDismissIntegrationCommand() {
|
|
4932
|
-
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) => {
|
|
4933
5192
|
const globalOpts = cmd.optsWithGlobals();
|
|
4934
5193
|
const cwd = process.cwd();
|
|
4935
5194
|
const result = dismissIntegration(cwd, name);
|
|
@@ -4953,7 +5212,7 @@ function createDismissIntegrationCommand() {
|
|
|
4953
5212
|
|
|
4954
5213
|
// src/commands/integrations/index.ts
|
|
4955
5214
|
function createIntegrationsCommand() {
|
|
4956
|
-
const command = new
|
|
5215
|
+
const command = new Command40("integrations").description(
|
|
4957
5216
|
"Manage MCP peer integrations (add, list, remove, dismiss)"
|
|
4958
5217
|
);
|
|
4959
5218
|
command.addCommand(createListIntegrationsCommand());
|
|
@@ -4964,13 +5223,14 @@ function createIntegrationsCommand() {
|
|
|
4964
5223
|
}
|
|
4965
5224
|
|
|
4966
5225
|
// src/commands/learnings/index.ts
|
|
4967
|
-
import { Command as
|
|
5226
|
+
import { Command as Command42 } from "commander";
|
|
4968
5227
|
|
|
4969
5228
|
// src/commands/learnings/prune.ts
|
|
4970
|
-
|
|
4971
|
-
import
|
|
5229
|
+
init_dist();
|
|
5230
|
+
import { Command as Command41 } from "commander";
|
|
5231
|
+
import * as path38 from "path";
|
|
4972
5232
|
async function handlePrune(opts) {
|
|
4973
|
-
const projectPath =
|
|
5233
|
+
const projectPath = path38.resolve(opts.path);
|
|
4974
5234
|
const result = await pruneLearnings(projectPath, opts.stream);
|
|
4975
5235
|
if (!result.ok) {
|
|
4976
5236
|
logger.error(result.error.message);
|
|
@@ -5009,23 +5269,23 @@ function printPatternProposals(patterns) {
|
|
|
5009
5269
|
);
|
|
5010
5270
|
}
|
|
5011
5271
|
function createPruneCommand() {
|
|
5012
|
-
return new
|
|
5272
|
+
return new Command41("prune").description(
|
|
5013
5273
|
"Analyze global learnings for patterns, present improvement proposals, and archive old entries"
|
|
5014
5274
|
).option("--path <path>", "Project root path", ".").option("--stream <name>", "Target a specific stream").action(handlePrune);
|
|
5015
5275
|
}
|
|
5016
5276
|
|
|
5017
5277
|
// src/commands/learnings/index.ts
|
|
5018
5278
|
function createLearningsCommand() {
|
|
5019
|
-
const command = new
|
|
5279
|
+
const command = new Command42("learnings").description("Learnings management commands");
|
|
5020
5280
|
command.addCommand(createPruneCommand());
|
|
5021
5281
|
return command;
|
|
5022
5282
|
}
|
|
5023
5283
|
|
|
5024
5284
|
// src/commands/linter/index.ts
|
|
5025
|
-
import { Command as
|
|
5285
|
+
import { Command as Command45 } from "commander";
|
|
5026
5286
|
|
|
5027
5287
|
// src/commands/linter/generate.ts
|
|
5028
|
-
import { Command as
|
|
5288
|
+
import { Command as Command43 } from "commander";
|
|
5029
5289
|
function formatGenerateError(e) {
|
|
5030
5290
|
switch (e.type) {
|
|
5031
5291
|
case "parse":
|
|
@@ -5069,7 +5329,7 @@ function handleSuccess(result, useJson) {
|
|
|
5069
5329
|
Generated ${result.rulesGenerated.length} rules to ${result.outputDir}`);
|
|
5070
5330
|
}
|
|
5071
5331
|
function createGenerateCommand2() {
|
|
5072
|
-
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) => {
|
|
5073
5333
|
try {
|
|
5074
5334
|
if (options.verbose) logger.info(`Parsing config: ${options.config}`);
|
|
5075
5335
|
const result = await generate({
|
|
@@ -5091,9 +5351,9 @@ function createGenerateCommand2() {
|
|
|
5091
5351
|
}
|
|
5092
5352
|
|
|
5093
5353
|
// src/commands/linter/validate.ts
|
|
5094
|
-
import { Command as
|
|
5354
|
+
import { Command as Command44 } from "commander";
|
|
5095
5355
|
function createValidateCommand() {
|
|
5096
|
-
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) => {
|
|
5097
5357
|
try {
|
|
5098
5358
|
const result = await validate({ configPath: options.config });
|
|
5099
5359
|
if (options.json) {
|
|
@@ -5112,7 +5372,7 @@ function createValidateCommand() {
|
|
|
5112
5372
|
|
|
5113
5373
|
// src/commands/linter/index.ts
|
|
5114
5374
|
function createLinterCommand() {
|
|
5115
|
-
const linter = new
|
|
5375
|
+
const linter = new Command45("linter").description(
|
|
5116
5376
|
"Generate and validate ESLint rules from YAML config"
|
|
5117
5377
|
);
|
|
5118
5378
|
linter.addCommand(createGenerateCommand2());
|
|
@@ -5121,22 +5381,61 @@ function createLinterCommand() {
|
|
|
5121
5381
|
}
|
|
5122
5382
|
|
|
5123
5383
|
// src/commands/mcp.ts
|
|
5124
|
-
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
|
+
}
|
|
5125
5400
|
function createMcpCommand() {
|
|
5126
|
-
return new
|
|
5127
|
-
|
|
5128
|
-
|
|
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();
|
|
5129
5428
|
});
|
|
5130
5429
|
}
|
|
5131
5430
|
|
|
5132
5431
|
// src/commands/orchestrator.ts
|
|
5133
|
-
import { Command as
|
|
5134
|
-
import * as
|
|
5432
|
+
import { Command as Command47 } from "commander";
|
|
5433
|
+
import * as path39 from "path";
|
|
5135
5434
|
import { Orchestrator, WorkflowLoader, launchTUI } from "@harness-engineering/orchestrator";
|
|
5136
5435
|
function createOrchestratorCommand() {
|
|
5137
|
-
const orchestrator = new
|
|
5138
|
-
orchestrator.command("run").description("Run the orchestrator daemon").option("-w, --workflow <path>", "Path to WORKFLOW.md", "WORKFLOW.md").action(async (opts) => {
|
|
5139
|
-
const workflowPath =
|
|
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) => {
|
|
5438
|
+
const workflowPath = path39.resolve(process.cwd(), opts.workflow);
|
|
5140
5439
|
const loader = new WorkflowLoader();
|
|
5141
5440
|
const result = await loader.loadWorkflow(workflowPath);
|
|
5142
5441
|
if (!result.ok) {
|
|
@@ -5152,23 +5451,32 @@ function createOrchestratorCommand() {
|
|
|
5152
5451
|
process.on("SIGINT", shutdown);
|
|
5153
5452
|
process.on("SIGTERM", shutdown);
|
|
5154
5453
|
daemon.start();
|
|
5155
|
-
|
|
5156
|
-
|
|
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
|
+
}
|
|
5157
5464
|
process.exit(ExitCode.SUCCESS);
|
|
5158
5465
|
});
|
|
5159
5466
|
return orchestrator;
|
|
5160
5467
|
}
|
|
5161
5468
|
|
|
5162
5469
|
// src/commands/perf.ts
|
|
5163
|
-
|
|
5164
|
-
import
|
|
5470
|
+
init_dist();
|
|
5471
|
+
import { Command as Command48 } from "commander";
|
|
5472
|
+
import * as path40 from "path";
|
|
5165
5473
|
function registerBenchCommand(perf) {
|
|
5166
|
-
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) => {
|
|
5167
5475
|
const globalOpts = cmd.optsWithGlobals();
|
|
5168
5476
|
const cwd = process.cwd();
|
|
5169
|
-
const { BenchmarkRunner } = await import("./dist-
|
|
5477
|
+
const { BenchmarkRunner } = await import("./dist-WCSJHQPK.js");
|
|
5170
5478
|
const runner = new BenchmarkRunner();
|
|
5171
|
-
const benchFiles = runner.discover(cwd,
|
|
5479
|
+
const benchFiles = runner.discover(cwd, glob2);
|
|
5172
5480
|
if (benchFiles.length === 0) {
|
|
5173
5481
|
if (globalOpts.json) {
|
|
5174
5482
|
console.log(JSON.stringify({ benchFiles: [], message: "No .bench.ts files found" }));
|
|
@@ -5178,7 +5486,7 @@ function registerBenchCommand(perf) {
|
|
|
5178
5486
|
return;
|
|
5179
5487
|
}
|
|
5180
5488
|
logBenchDiscovery(globalOpts.json, benchFiles);
|
|
5181
|
-
const result = await runner.run(
|
|
5489
|
+
const result = await runner.run(glob2 ? { cwd, glob: glob2 } : { cwd });
|
|
5182
5490
|
outputBenchResults(globalOpts.json, result);
|
|
5183
5491
|
});
|
|
5184
5492
|
}
|
|
@@ -5230,7 +5538,7 @@ async function runBaselinesShow(globalOpts) {
|
|
|
5230
5538
|
}
|
|
5231
5539
|
async function runBaselinesUpdate(globalOpts) {
|
|
5232
5540
|
const cwd = process.cwd();
|
|
5233
|
-
const { BenchmarkRunner } = await import("./dist-
|
|
5541
|
+
const { BenchmarkRunner } = await import("./dist-WCSJHQPK.js");
|
|
5234
5542
|
const runner = new BenchmarkRunner();
|
|
5235
5543
|
const manager = new BaselineManager(cwd);
|
|
5236
5544
|
logger.info("Running benchmarks to update baselines...");
|
|
@@ -5271,9 +5579,9 @@ function registerReportCommand(perf) {
|
|
|
5271
5579
|
perf.command("report").description("Full performance report with metrics, trends, and hotspots").action(async (_opts, cmd) => {
|
|
5272
5580
|
const globalOpts = cmd.optsWithGlobals();
|
|
5273
5581
|
const cwd = process.cwd();
|
|
5274
|
-
const { EntropyAnalyzer: EntropyAnalyzer2 } = await import("./dist-
|
|
5582
|
+
const { EntropyAnalyzer: EntropyAnalyzer2 } = await import("./dist-WCSJHQPK.js");
|
|
5275
5583
|
const analyzer = new EntropyAnalyzer2({
|
|
5276
|
-
rootDir:
|
|
5584
|
+
rootDir: path40.resolve(cwd),
|
|
5277
5585
|
analyze: { complexity: true, coupling: true }
|
|
5278
5586
|
});
|
|
5279
5587
|
const result = await analyzer.analyze();
|
|
@@ -5332,7 +5640,7 @@ function registerCriticalPathsCommand(perf) {
|
|
|
5332
5640
|
});
|
|
5333
5641
|
}
|
|
5334
5642
|
function createPerfCommand() {
|
|
5335
|
-
const perf = new
|
|
5643
|
+
const perf = new Command48("perf").description("Performance benchmark and baseline management");
|
|
5336
5644
|
registerBenchCommand(perf);
|
|
5337
5645
|
registerBaselinesCommands(perf);
|
|
5338
5646
|
registerReportCommand(perf);
|
|
@@ -5341,10 +5649,10 @@ function createPerfCommand() {
|
|
|
5341
5649
|
}
|
|
5342
5650
|
|
|
5343
5651
|
// src/commands/persona/index.ts
|
|
5344
|
-
import { Command as
|
|
5652
|
+
import { Command as Command51 } from "commander";
|
|
5345
5653
|
|
|
5346
5654
|
// src/commands/persona/list.ts
|
|
5347
|
-
import { Command as
|
|
5655
|
+
import { Command as Command49 } from "commander";
|
|
5348
5656
|
function printPersonaList(personas) {
|
|
5349
5657
|
if (personas.length === 0) {
|
|
5350
5658
|
logger.info("No personas found.");
|
|
@@ -5358,7 +5666,7 @@ function printPersonaList(personas) {
|
|
|
5358
5666
|
}
|
|
5359
5667
|
}
|
|
5360
5668
|
function createListCommand2() {
|
|
5361
|
-
return new
|
|
5669
|
+
return new Command49("list").description("List available agent personas").action(async (_opts, cmd) => {
|
|
5362
5670
|
const globalOpts = cmd.optsWithGlobals();
|
|
5363
5671
|
const personasDir = resolvePersonasDir();
|
|
5364
5672
|
const result = listPersonas(personasDir);
|
|
@@ -5378,51 +5686,51 @@ function createListCommand2() {
|
|
|
5378
5686
|
}
|
|
5379
5687
|
|
|
5380
5688
|
// src/commands/persona/generate.ts
|
|
5381
|
-
import { Command as
|
|
5382
|
-
import * as
|
|
5383
|
-
import * as
|
|
5689
|
+
import { Command as Command50 } from "commander";
|
|
5690
|
+
import * as fs24 from "fs";
|
|
5691
|
+
import * as path41 from "path";
|
|
5384
5692
|
function generatePersonaArtifacts(persona, outputDir, only) {
|
|
5385
5693
|
const slug = toKebabCase(persona.name);
|
|
5386
5694
|
const generated = [];
|
|
5387
5695
|
if (!only || only === "runtime") {
|
|
5388
5696
|
const result = generateRuntime(persona);
|
|
5389
5697
|
if (result.ok) {
|
|
5390
|
-
const outPath =
|
|
5391
|
-
|
|
5392
|
-
|
|
5698
|
+
const outPath = path41.join(outputDir, `${slug}.runtime.json`);
|
|
5699
|
+
fs24.mkdirSync(path41.dirname(outPath), { recursive: true });
|
|
5700
|
+
fs24.writeFileSync(outPath, result.value);
|
|
5393
5701
|
generated.push(outPath);
|
|
5394
5702
|
}
|
|
5395
5703
|
}
|
|
5396
5704
|
if (!only || only === "agents-md") {
|
|
5397
5705
|
const result = generateAgentsMd(persona);
|
|
5398
5706
|
if (result.ok) {
|
|
5399
|
-
const outPath =
|
|
5400
|
-
|
|
5707
|
+
const outPath = path41.join(outputDir, `${slug}.agents.md`);
|
|
5708
|
+
fs24.writeFileSync(outPath, result.value);
|
|
5401
5709
|
generated.push(outPath);
|
|
5402
5710
|
}
|
|
5403
5711
|
}
|
|
5404
5712
|
if (!only || only === "ci") {
|
|
5405
5713
|
const result = generateCIWorkflow(persona, "github");
|
|
5406
5714
|
if (result.ok) {
|
|
5407
|
-
const outPath =
|
|
5408
|
-
|
|
5409
|
-
|
|
5715
|
+
const outPath = path41.join(outputDir, ".github", "workflows", `${slug}.yml`);
|
|
5716
|
+
fs24.mkdirSync(path41.dirname(outPath), { recursive: true });
|
|
5717
|
+
fs24.writeFileSync(outPath, result.value);
|
|
5410
5718
|
generated.push(outPath);
|
|
5411
5719
|
}
|
|
5412
5720
|
}
|
|
5413
5721
|
return generated;
|
|
5414
5722
|
}
|
|
5415
5723
|
function createGenerateCommand3() {
|
|
5416
|
-
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) => {
|
|
5417
5725
|
const globalOpts = cmd.optsWithGlobals();
|
|
5418
|
-
const personaResult = loadPersona(
|
|
5726
|
+
const personaResult = loadPersona(path41.join(resolvePersonasDir(), `${name}.yaml`));
|
|
5419
5727
|
if (!personaResult.ok) {
|
|
5420
5728
|
logger.error(personaResult.error.message);
|
|
5421
5729
|
process.exit(ExitCode.ERROR);
|
|
5422
5730
|
}
|
|
5423
5731
|
const generated = generatePersonaArtifacts(
|
|
5424
5732
|
personaResult.value,
|
|
5425
|
-
|
|
5733
|
+
path41.resolve(opts.outputDir),
|
|
5426
5734
|
opts.only
|
|
5427
5735
|
);
|
|
5428
5736
|
if (!globalOpts.quiet) {
|
|
@@ -5435,14 +5743,15 @@ function createGenerateCommand3() {
|
|
|
5435
5743
|
|
|
5436
5744
|
// src/commands/persona/index.ts
|
|
5437
5745
|
function createPersonaCommand() {
|
|
5438
|
-
const command = new
|
|
5746
|
+
const command = new Command51("persona").description("Agent persona management commands");
|
|
5439
5747
|
command.addCommand(createListCommand2());
|
|
5440
5748
|
command.addCommand(createGenerateCommand3());
|
|
5441
5749
|
return command;
|
|
5442
5750
|
}
|
|
5443
5751
|
|
|
5444
5752
|
// src/commands/predict.ts
|
|
5445
|
-
|
|
5753
|
+
init_dist();
|
|
5754
|
+
import { Command as Command52 } from "commander";
|
|
5446
5755
|
import chalk6 from "chalk";
|
|
5447
5756
|
var CATEGORY_ORDER = [
|
|
5448
5757
|
"circular-deps",
|
|
@@ -5548,7 +5857,7 @@ function handlePredictError(err, mode) {
|
|
|
5548
5857
|
process.exit(ExitCode.ERROR);
|
|
5549
5858
|
}
|
|
5550
5859
|
function createPredictCommand() {
|
|
5551
|
-
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) => {
|
|
5552
5861
|
const globalOpts = cmd.optsWithGlobals();
|
|
5553
5862
|
const mode = globalOpts.json ? OutputMode.JSON : OutputMode.TEXT;
|
|
5554
5863
|
try {
|
|
@@ -5570,8 +5879,137 @@ function createPredictCommand() {
|
|
|
5570
5879
|
return command;
|
|
5571
5880
|
}
|
|
5572
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
|
+
|
|
5573
6011
|
// src/commands/recommend.ts
|
|
5574
|
-
import { Command as
|
|
6012
|
+
import { Command as Command54 } from "commander";
|
|
5575
6013
|
import chalk7 from "chalk";
|
|
5576
6014
|
async function resolveSnapshot(cwd, noCache) {
|
|
5577
6015
|
if (!noCache) {
|
|
@@ -5635,7 +6073,7 @@ function printRecommendations(result) {
|
|
|
5635
6073
|
console.log("");
|
|
5636
6074
|
}
|
|
5637
6075
|
function createRecommendCommand() {
|
|
5638
|
-
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) => {
|
|
5639
6077
|
const globalOpts = cmd.optsWithGlobals();
|
|
5640
6078
|
const mode = globalOpts.json ? OutputMode.JSON : OutputMode.TEXT;
|
|
5641
6079
|
try {
|
|
@@ -5671,9 +6109,10 @@ function createRecommendCommand() {
|
|
|
5671
6109
|
}
|
|
5672
6110
|
|
|
5673
6111
|
// src/commands/scan-config.ts
|
|
5674
|
-
|
|
5675
|
-
import {
|
|
5676
|
-
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";
|
|
5677
6116
|
var CONFIG_FILES = ["CLAUDE.md", "AGENTS.md", ".gemini/settings.json", "skill.yaml"];
|
|
5678
6117
|
function stripHighSeverityPatterns(content, injectionFindings) {
|
|
5679
6118
|
const highLines = /* @__PURE__ */ new Set();
|
|
@@ -5706,10 +6145,10 @@ function applyFix(filePath, targetDir, content, injectionFindings) {
|
|
|
5706
6145
|
}
|
|
5707
6146
|
}
|
|
5708
6147
|
function scanSingleFile(filePath, targetDir, scanner, options) {
|
|
5709
|
-
if (!
|
|
6148
|
+
if (!existsSync22(filePath)) return null;
|
|
5710
6149
|
let content;
|
|
5711
6150
|
try {
|
|
5712
|
-
content =
|
|
6151
|
+
content = readFileSync14(filePath, "utf8");
|
|
5713
6152
|
} catch {
|
|
5714
6153
|
return null;
|
|
5715
6154
|
}
|
|
@@ -5730,7 +6169,7 @@ async function runScanConfig(targetDir, options) {
|
|
|
5730
6169
|
const scanner = new SecurityScanner(parseSecurityConfig({}));
|
|
5731
6170
|
const results = [];
|
|
5732
6171
|
for (const configFile of CONFIG_FILES) {
|
|
5733
|
-
const result = scanSingleFile(
|
|
6172
|
+
const result = scanSingleFile(join30(targetDir, configFile), targetDir, scanner, options);
|
|
5734
6173
|
if (result) results.push(result);
|
|
5735
6174
|
}
|
|
5736
6175
|
return { exitCode: computeScanExitCode(results), results };
|
|
@@ -5760,7 +6199,7 @@ function formatTextOutput(result) {
|
|
|
5760
6199
|
}
|
|
5761
6200
|
}
|
|
5762
6201
|
function createScanConfigCommand() {
|
|
5763
|
-
const command = new
|
|
6202
|
+
const command = new Command55("scan-config").description(
|
|
5764
6203
|
"Scan CLAUDE.md, AGENTS.md, .gemini/settings.json, and skill.yaml for prompt injection patterns"
|
|
5765
6204
|
).option("--path <dir>", "Target directory to scan (default: cwd)").option("--fix", "Strip high-severity patterns from files in-place").action(async (opts, cmd) => {
|
|
5766
6205
|
const globalOpts = cmd.optsWithGlobals();
|
|
@@ -5777,24 +6216,24 @@ function createScanConfigCommand() {
|
|
|
5777
6216
|
}
|
|
5778
6217
|
|
|
5779
6218
|
// src/commands/setup.ts
|
|
5780
|
-
import { Command as
|
|
5781
|
-
import * as
|
|
6219
|
+
import { Command as Command56 } from "commander";
|
|
6220
|
+
import * as fs28 from "fs";
|
|
5782
6221
|
import * as os7 from "os";
|
|
5783
|
-
import * as
|
|
5784
|
-
import
|
|
6222
|
+
import * as path45 from "path";
|
|
6223
|
+
import chalk9 from "chalk";
|
|
5785
6224
|
|
|
5786
6225
|
// src/utils/first-run.ts
|
|
5787
|
-
import * as
|
|
6226
|
+
import * as fs26 from "fs";
|
|
5788
6227
|
import * as os6 from "os";
|
|
5789
|
-
import * as
|
|
5790
|
-
var HARNESS_DIR =
|
|
5791
|
-
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");
|
|
5792
6231
|
function isFirstRun() {
|
|
5793
|
-
return !
|
|
6232
|
+
return !fs26.existsSync(MARKER_FILE);
|
|
5794
6233
|
}
|
|
5795
6234
|
function markSetupComplete() {
|
|
5796
|
-
|
|
5797
|
-
|
|
6235
|
+
fs26.mkdirSync(HARNESS_DIR, { recursive: true });
|
|
6236
|
+
fs26.writeFileSync(MARKER_FILE, "", "utf-8");
|
|
5798
6237
|
}
|
|
5799
6238
|
function printFirstRunWelcome() {
|
|
5800
6239
|
try {
|
|
@@ -5806,6 +6245,119 @@ function printFirstRunWelcome() {
|
|
|
5806
6245
|
}
|
|
5807
6246
|
}
|
|
5808
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
|
+
|
|
5809
6361
|
// src/commands/setup.ts
|
|
5810
6362
|
function checkNodeVersion3() {
|
|
5811
6363
|
const result = checkNodeVersion();
|
|
@@ -5832,7 +6384,7 @@ function runSlashCommandGeneration() {
|
|
|
5832
6384
|
}
|
|
5833
6385
|
}
|
|
5834
6386
|
function detectClient(dirName) {
|
|
5835
|
-
return
|
|
6387
|
+
return fs28.existsSync(path45.join(os7.homedir(), dirName));
|
|
5836
6388
|
}
|
|
5837
6389
|
async function runMcpSetup(cwd) {
|
|
5838
6390
|
const results = [];
|
|
@@ -5869,12 +6421,12 @@ async function runMcpSetup(cwd) {
|
|
|
5869
6421
|
return results;
|
|
5870
6422
|
}
|
|
5871
6423
|
function formatStep(result) {
|
|
5872
|
-
const icon = result.status === "pass" ?
|
|
6424
|
+
const icon = result.status === "pass" ? chalk9.green("\u2713") : result.status === "warn" ? chalk9.yellow("\u26A0") : chalk9.red("\u2717");
|
|
5873
6425
|
return ` ${icon} ${result.message}`;
|
|
5874
6426
|
}
|
|
5875
6427
|
function configureTier0Integrations(cwd) {
|
|
5876
6428
|
try {
|
|
5877
|
-
const mcpPath =
|
|
6429
|
+
const mcpPath = path45.join(cwd, ".mcp.json");
|
|
5878
6430
|
const config = readMcpConfig(mcpPath);
|
|
5879
6431
|
const tier0 = INTEGRATION_REGISTRY.filter((i) => i.tier === 0);
|
|
5880
6432
|
const added = [];
|
|
@@ -5883,9 +6435,9 @@ function configureTier0Integrations(cwd) {
|
|
|
5883
6435
|
writeMcpEntry(mcpPath, integration.name, integration.mcpConfig);
|
|
5884
6436
|
added.push(integration.displayName);
|
|
5885
6437
|
}
|
|
5886
|
-
const geminiDir =
|
|
5887
|
-
if (
|
|
5888
|
-
const geminiPath =
|
|
6438
|
+
const geminiDir = path45.join(cwd, ".gemini");
|
|
6439
|
+
if (fs28.existsSync(geminiDir)) {
|
|
6440
|
+
const geminiPath = path45.join(geminiDir, "settings.json");
|
|
5889
6441
|
const geminiConfig = readMcpConfig(geminiPath);
|
|
5890
6442
|
for (const integration of tier0) {
|
|
5891
6443
|
if (!geminiConfig.mcpServers[integration.name]) {
|
|
@@ -5906,14 +6458,14 @@ function configureTier0Integrations(cwd) {
|
|
|
5906
6458
|
}
|
|
5907
6459
|
}
|
|
5908
6460
|
function ensureHooks(cwd) {
|
|
5909
|
-
const configPath =
|
|
5910
|
-
if (!
|
|
6461
|
+
const configPath = path45.join(cwd, "harness.config.json");
|
|
6462
|
+
if (!fs28.existsSync(configPath)) {
|
|
5911
6463
|
return { status: "warn", message: "Not a harness project \u2014 skipped hook installation" };
|
|
5912
6464
|
}
|
|
5913
6465
|
let profile = "standard";
|
|
5914
|
-
const profilePath =
|
|
6466
|
+
const profilePath = path45.join(cwd, ".harness", "hooks", "profile.json");
|
|
5915
6467
|
try {
|
|
5916
|
-
const data = JSON.parse(
|
|
6468
|
+
const data = JSON.parse(fs28.readFileSync(profilePath, "utf-8"));
|
|
5917
6469
|
if (data.profile && ["minimal", "standard", "strict"].includes(data.profile)) {
|
|
5918
6470
|
profile = data.profile;
|
|
5919
6471
|
}
|
|
@@ -5932,7 +6484,7 @@ function ensureHooks(cwd) {
|
|
|
5932
6484
|
}
|
|
5933
6485
|
async function runInitialGraphScan(cwd) {
|
|
5934
6486
|
try {
|
|
5935
|
-
const { runScan: runScan2 } = await import("./scan-
|
|
6487
|
+
const { runScan: runScan2 } = await import("./scan-MPJ6JHUY.js");
|
|
5936
6488
|
const result = await runScan2(cwd);
|
|
5937
6489
|
return {
|
|
5938
6490
|
status: "pass",
|
|
@@ -5958,6 +6510,8 @@ async function runSetup(cwd) {
|
|
|
5958
6510
|
steps.push(tier0Result);
|
|
5959
6511
|
const hooksResult = ensureHooks(cwd);
|
|
5960
6512
|
steps.push(hooksResult);
|
|
6513
|
+
const telemetryResult = await ensureTelemetryConfigured(cwd);
|
|
6514
|
+
steps.push(telemetryResult);
|
|
5961
6515
|
const graphResult = await runInitialGraphScan(cwd);
|
|
5962
6516
|
steps.push(graphResult);
|
|
5963
6517
|
const success = steps.every((s) => s.status !== "fail");
|
|
@@ -5967,10 +6521,10 @@ async function runSetup(cwd) {
|
|
|
5967
6521
|
return { steps, success };
|
|
5968
6522
|
}
|
|
5969
6523
|
function createSetupCommand() {
|
|
5970
|
-
return new
|
|
6524
|
+
return new Command56("setup").description("Configure harness environment: slash commands, MCP, and more").action(async () => {
|
|
5971
6525
|
const cwd = process.cwd();
|
|
5972
6526
|
console.log("");
|
|
5973
|
-
console.log(` ${
|
|
6527
|
+
console.log(` ${chalk9.bold("harness setup")}`);
|
|
5974
6528
|
console.log("");
|
|
5975
6529
|
const { steps, success } = await runSetup(cwd);
|
|
5976
6530
|
for (const step of steps) {
|
|
@@ -5989,15 +6543,16 @@ function createSetupCommand() {
|
|
|
5989
6543
|
}
|
|
5990
6544
|
|
|
5991
6545
|
// src/commands/share.ts
|
|
5992
|
-
|
|
5993
|
-
import
|
|
5994
|
-
import * as
|
|
6546
|
+
init_dist();
|
|
6547
|
+
import { Command as Command57 } from "commander";
|
|
6548
|
+
import * as fs29 from "fs";
|
|
6549
|
+
import * as path46 from "path";
|
|
5995
6550
|
import { parse as parseYaml } from "yaml";
|
|
5996
6551
|
var MANIFEST_FILENAME = "constraints.yaml";
|
|
5997
6552
|
async function runShareAction(projectPath, options) {
|
|
5998
|
-
const rootDir =
|
|
5999
|
-
const manifestPath =
|
|
6000
|
-
if (!
|
|
6553
|
+
const rootDir = path46.resolve(projectPath);
|
|
6554
|
+
const manifestPath = path46.join(rootDir, MANIFEST_FILENAME);
|
|
6555
|
+
if (!fs29.existsSync(manifestPath)) {
|
|
6001
6556
|
logger.error(
|
|
6002
6557
|
`No ${MANIFEST_FILENAME} found at ${manifestPath}.
|
|
6003
6558
|
Create a constraints.yaml in your project root to define what to share.`
|
|
@@ -6006,7 +6561,7 @@ Create a constraints.yaml in your project root to define what to share.`
|
|
|
6006
6561
|
}
|
|
6007
6562
|
let parsed;
|
|
6008
6563
|
try {
|
|
6009
|
-
const raw =
|
|
6564
|
+
const raw = fs29.readFileSync(manifestPath, "utf-8");
|
|
6010
6565
|
parsed = parseYaml(raw);
|
|
6011
6566
|
} catch (err) {
|
|
6012
6567
|
logger.error(
|
|
@@ -6020,7 +6575,7 @@ Create a constraints.yaml in your project root to define what to share.`
|
|
|
6020
6575
|
process.exit(1);
|
|
6021
6576
|
}
|
|
6022
6577
|
const manifest = manifestResult.value;
|
|
6023
|
-
const configResult = resolveConfig(
|
|
6578
|
+
const configResult = resolveConfig(path46.join(rootDir, "harness.config.json"));
|
|
6024
6579
|
if (!configResult.ok) {
|
|
6025
6580
|
logger.error(configResult.error.message);
|
|
6026
6581
|
process.exit(1);
|
|
@@ -6038,8 +6593,8 @@ Create a constraints.yaml in your project root to define what to share.`
|
|
|
6038
6593
|
);
|
|
6039
6594
|
process.exit(1);
|
|
6040
6595
|
}
|
|
6041
|
-
const outputDir =
|
|
6042
|
-
const outputPath =
|
|
6596
|
+
const outputDir = path46.resolve(options.output);
|
|
6597
|
+
const outputPath = path46.join(outputDir, `${manifest.name}.harness-constraints.json`);
|
|
6043
6598
|
const writeResult = await writeConfig(outputPath, bundle);
|
|
6044
6599
|
if (!writeResult.ok) {
|
|
6045
6600
|
logger.error(`Failed to write bundle: ${writeResult.error.message}`);
|
|
@@ -6048,28 +6603,28 @@ Create a constraints.yaml in your project root to define what to share.`
|
|
|
6048
6603
|
logger.success(`Bundle written to ${outputPath}`);
|
|
6049
6604
|
}
|
|
6050
6605
|
function createShareCommand() {
|
|
6051
|
-
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) => {
|
|
6052
6607
|
await runShareAction(projectPath, options);
|
|
6053
6608
|
});
|
|
6054
6609
|
}
|
|
6055
6610
|
|
|
6056
6611
|
// src/commands/skill/index.ts
|
|
6057
|
-
import { Command as
|
|
6612
|
+
import { Command as Command65 } from "commander";
|
|
6058
6613
|
|
|
6059
6614
|
// src/commands/skill/list.ts
|
|
6060
|
-
import { Command as
|
|
6061
|
-
import * as
|
|
6062
|
-
import * as
|
|
6615
|
+
import { Command as Command58 } from "commander";
|
|
6616
|
+
import * as fs30 from "fs";
|
|
6617
|
+
import * as path47 from "path";
|
|
6063
6618
|
import { parse } from "yaml";
|
|
6064
6619
|
function scanDirectory(dirPath, source) {
|
|
6065
|
-
if (!
|
|
6066
|
-
const entries =
|
|
6620
|
+
if (!fs30.existsSync(dirPath)) return [];
|
|
6621
|
+
const entries = fs30.readdirSync(dirPath, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
|
|
6067
6622
|
const skills = [];
|
|
6068
6623
|
for (const name of entries) {
|
|
6069
|
-
const yamlPath =
|
|
6070
|
-
if (!
|
|
6624
|
+
const yamlPath = path47.join(dirPath, name, "skill.yaml");
|
|
6625
|
+
if (!fs30.existsSync(yamlPath)) continue;
|
|
6071
6626
|
try {
|
|
6072
|
-
const raw =
|
|
6627
|
+
const raw = fs30.readFileSync(yamlPath, "utf-8");
|
|
6073
6628
|
const parsed = parse(raw);
|
|
6074
6629
|
const result = SkillMetadataSchema.safeParse(parsed);
|
|
6075
6630
|
if (result.success) {
|
|
@@ -6087,10 +6642,10 @@ function scanDirectory(dirPath, source) {
|
|
|
6087
6642
|
}
|
|
6088
6643
|
function collectCommunitySkills(seen, allSkills) {
|
|
6089
6644
|
const globalDir = resolveGlobalSkillsDir();
|
|
6090
|
-
const skillsDir =
|
|
6091
|
-
const communityBase =
|
|
6092
|
-
const communityPlatformDir =
|
|
6093
|
-
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");
|
|
6094
6649
|
const lockfile = readLockfile2(lockfilePath);
|
|
6095
6650
|
const communitySkills = scanDirectory(communityPlatformDir, "community");
|
|
6096
6651
|
for (const skill of communitySkills) {
|
|
@@ -6160,7 +6715,7 @@ function printSkillsVerbose(skills) {
|
|
|
6160
6715
|
for (const s of skills) printSkillEntry(s);
|
|
6161
6716
|
}
|
|
6162
6717
|
function createListCommand3() {
|
|
6163
|
-
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) => {
|
|
6164
6719
|
const globalOpts = cmd.optsWithGlobals();
|
|
6165
6720
|
const skills = collectSkills({ filter: resolveFilter(opts) });
|
|
6166
6721
|
if (globalOpts.json) {
|
|
@@ -6175,9 +6730,9 @@ function createListCommand3() {
|
|
|
6175
6730
|
}
|
|
6176
6731
|
|
|
6177
6732
|
// src/commands/skill/run.ts
|
|
6178
|
-
import { Command as
|
|
6179
|
-
import * as
|
|
6180
|
-
import * as
|
|
6733
|
+
import { Command as Command59 } from "commander";
|
|
6734
|
+
import * as fs31 from "fs";
|
|
6735
|
+
import * as path48 from "path";
|
|
6181
6736
|
import { parse as parse2 } from "yaml";
|
|
6182
6737
|
|
|
6183
6738
|
// src/skill/complexity.ts
|
|
@@ -6273,10 +6828,10 @@ ${options.principles}`);
|
|
|
6273
6828
|
|
|
6274
6829
|
// src/commands/skill/run.ts
|
|
6275
6830
|
function loadSkillMetadata(skillDir) {
|
|
6276
|
-
const yamlPath =
|
|
6277
|
-
if (!
|
|
6831
|
+
const yamlPath = path48.join(skillDir, "skill.yaml");
|
|
6832
|
+
if (!fs31.existsSync(yamlPath)) return null;
|
|
6278
6833
|
try {
|
|
6279
|
-
const result = SkillMetadataSchema.safeParse(parse2(
|
|
6834
|
+
const result = SkillMetadataSchema.safeParse(parse2(fs31.readFileSync(yamlPath, "utf-8")));
|
|
6280
6835
|
return result.success ? result.data : null;
|
|
6281
6836
|
} catch {
|
|
6282
6837
|
return null;
|
|
@@ -6288,22 +6843,22 @@ function resolveComplexity(metadata, requested, projectPath) {
|
|
|
6288
6843
|
return requested;
|
|
6289
6844
|
}
|
|
6290
6845
|
function loadPrinciples(projectPath) {
|
|
6291
|
-
const principlesPath =
|
|
6292
|
-
return
|
|
6846
|
+
const principlesPath = path48.join(projectPath, "docs", "principles.md");
|
|
6847
|
+
return fs31.existsSync(principlesPath) ? fs31.readFileSync(principlesPath, "utf-8") : void 0;
|
|
6293
6848
|
}
|
|
6294
6849
|
function readMostRecentFileInDir(dirPath) {
|
|
6295
|
-
const files =
|
|
6296
|
-
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");
|
|
6297
6852
|
return void 0;
|
|
6298
6853
|
}
|
|
6299
6854
|
function loadPriorState(metadata, projectPath) {
|
|
6300
6855
|
if (!metadata?.state.persistent || metadata.state.files.length === 0) return void 0;
|
|
6301
6856
|
for (const stateFilePath of metadata.state.files) {
|
|
6302
|
-
const fullPath =
|
|
6303
|
-
if (!
|
|
6304
|
-
const stat =
|
|
6857
|
+
const fullPath = path48.join(projectPath, stateFilePath);
|
|
6858
|
+
if (!fs31.existsSync(fullPath)) continue;
|
|
6859
|
+
const stat = fs31.statSync(fullPath);
|
|
6305
6860
|
if (stat.isDirectory()) return readMostRecentFileInDir(fullPath);
|
|
6306
|
-
return
|
|
6861
|
+
return fs31.readFileSync(fullPath, "utf-8");
|
|
6307
6862
|
}
|
|
6308
6863
|
return void 0;
|
|
6309
6864
|
}
|
|
@@ -6323,9 +6878,9 @@ function resolvePhaseState(metadata, projectPath, phase) {
|
|
|
6323
6878
|
}
|
|
6324
6879
|
function appendProjectState(content, metadata, projectPath, hasPathOpt) {
|
|
6325
6880
|
if (!metadata?.state.persistent || !hasPathOpt) return content;
|
|
6326
|
-
const stateFile =
|
|
6327
|
-
if (!
|
|
6328
|
-
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");
|
|
6329
6884
|
return content + `
|
|
6330
6885
|
|
|
6331
6886
|
---
|
|
@@ -6337,14 +6892,14 @@ ${stateContent}
|
|
|
6337
6892
|
}
|
|
6338
6893
|
async function runSkill(name, opts) {
|
|
6339
6894
|
const skillsDir = resolveSkillsDir();
|
|
6340
|
-
const skillDir =
|
|
6341
|
-
if (!
|
|
6895
|
+
const skillDir = path48.join(skillsDir, name);
|
|
6896
|
+
if (!fs31.existsSync(skillDir)) {
|
|
6342
6897
|
logger.error(`Skill not found: ${name}`);
|
|
6343
6898
|
process.exit(ExitCode.ERROR);
|
|
6344
6899
|
return;
|
|
6345
6900
|
}
|
|
6346
6901
|
const metadata = loadSkillMetadata(skillDir);
|
|
6347
|
-
const projectPath = opts.path ?
|
|
6902
|
+
const projectPath = opts.path ? path48.resolve(opts.path) : process.cwd();
|
|
6348
6903
|
const complexity = resolveComplexity(
|
|
6349
6904
|
metadata,
|
|
6350
6905
|
opts.complexity ?? "standard",
|
|
@@ -6371,14 +6926,14 @@ async function runSkill(name, opts) {
|
|
|
6371
6926
|
...stateWarning !== void 0 && { stateWarning },
|
|
6372
6927
|
...opts.party !== void 0 && { party: opts.party }
|
|
6373
6928
|
});
|
|
6374
|
-
const skillMdPath =
|
|
6375
|
-
if (!
|
|
6929
|
+
const skillMdPath = path48.join(skillDir, "SKILL.md");
|
|
6930
|
+
if (!fs31.existsSync(skillMdPath)) {
|
|
6376
6931
|
logger.error(`SKILL.md not found for skill: ${name}`);
|
|
6377
6932
|
process.exit(ExitCode.ERROR);
|
|
6378
6933
|
return;
|
|
6379
6934
|
}
|
|
6380
6935
|
const content = appendProjectState(
|
|
6381
|
-
|
|
6936
|
+
fs31.readFileSync(skillMdPath, "utf-8"),
|
|
6382
6937
|
metadata,
|
|
6383
6938
|
projectPath,
|
|
6384
6939
|
!!opts.path
|
|
@@ -6387,13 +6942,13 @@ async function runSkill(name, opts) {
|
|
|
6387
6942
|
process.exit(ExitCode.SUCCESS);
|
|
6388
6943
|
}
|
|
6389
6944
|
function createRunCommand2() {
|
|
6390
|
-
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));
|
|
6391
6946
|
}
|
|
6392
6947
|
|
|
6393
6948
|
// src/commands/skill/validate.ts
|
|
6394
|
-
import { Command as
|
|
6395
|
-
import * as
|
|
6396
|
-
import * as
|
|
6949
|
+
import { Command as Command60 } from "commander";
|
|
6950
|
+
import * as fs32 from "fs";
|
|
6951
|
+
import * as path49 from "path";
|
|
6397
6952
|
import { parse as parse3 } from "yaml";
|
|
6398
6953
|
var BEHAVIORAL_REQUIRED_SECTIONS = [
|
|
6399
6954
|
"## When to Use",
|
|
@@ -6405,11 +6960,11 @@ var BEHAVIORAL_REQUIRED_SECTIONS = [
|
|
|
6405
6960
|
];
|
|
6406
6961
|
var KNOWLEDGE_REQUIRED_SECTIONS = ["## Instructions"];
|
|
6407
6962
|
function validateSkillMd(name, skillMdPath, skillType, errors) {
|
|
6408
|
-
if (!
|
|
6963
|
+
if (!fs32.existsSync(skillMdPath)) {
|
|
6409
6964
|
errors.push(`${name}: missing SKILL.md`);
|
|
6410
6965
|
return;
|
|
6411
6966
|
}
|
|
6412
|
-
const mdContent =
|
|
6967
|
+
const mdContent = fs32.readFileSync(skillMdPath, "utf-8");
|
|
6413
6968
|
if (!mdContent.trim().startsWith("# ")) {
|
|
6414
6969
|
errors.push(`${name}/SKILL.md: must start with an h1 heading`);
|
|
6415
6970
|
}
|
|
@@ -6434,20 +6989,20 @@ function validateSkillMd(name, skillMdPath, skillType, errors) {
|
|
|
6434
6989
|
}
|
|
6435
6990
|
}
|
|
6436
6991
|
function validateSkillEntry(name, skillsDir, errors) {
|
|
6437
|
-
const skillDir =
|
|
6438
|
-
const yamlPath =
|
|
6439
|
-
if (!
|
|
6992
|
+
const skillDir = path49.join(skillsDir, name);
|
|
6993
|
+
const yamlPath = path49.join(skillDir, "skill.yaml");
|
|
6994
|
+
if (!fs32.existsSync(yamlPath)) {
|
|
6440
6995
|
errors.push(`${name}: missing skill.yaml`);
|
|
6441
6996
|
return false;
|
|
6442
6997
|
}
|
|
6443
6998
|
try {
|
|
6444
|
-
const raw =
|
|
6999
|
+
const raw = fs32.readFileSync(yamlPath, "utf-8");
|
|
6445
7000
|
const result = SkillMetadataSchema.safeParse(parse3(raw));
|
|
6446
7001
|
if (!result.success) {
|
|
6447
7002
|
errors.push(`${name}/skill.yaml: ${result.error.message}`);
|
|
6448
7003
|
return false;
|
|
6449
7004
|
}
|
|
6450
|
-
validateSkillMd(name,
|
|
7005
|
+
validateSkillMd(name, path49.join(skillDir, "SKILL.md"), result.data.type, errors);
|
|
6451
7006
|
return true;
|
|
6452
7007
|
} catch (e) {
|
|
6453
7008
|
errors.push(`${name}: parse error \u2014 ${e instanceof Error ? e.message : String(e)}`);
|
|
@@ -6455,15 +7010,15 @@ function validateSkillEntry(name, skillsDir, errors) {
|
|
|
6455
7010
|
}
|
|
6456
7011
|
}
|
|
6457
7012
|
function createValidateCommand2() {
|
|
6458
|
-
return new
|
|
7013
|
+
return new Command60("validate").description("Validate all skill.yaml files and SKILL.md structure").action(async (_opts, cmd) => {
|
|
6459
7014
|
const globalOpts = cmd.optsWithGlobals();
|
|
6460
7015
|
const skillsDir = resolveSkillsDir();
|
|
6461
|
-
if (!
|
|
7016
|
+
if (!fs32.existsSync(skillsDir)) {
|
|
6462
7017
|
logger.info("No skills directory found.");
|
|
6463
7018
|
process.exit(ExitCode.SUCCESS);
|
|
6464
7019
|
return;
|
|
6465
7020
|
}
|
|
6466
|
-
const entries =
|
|
7021
|
+
const entries = fs32.readdirSync(skillsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
|
|
6467
7022
|
const errors = [];
|
|
6468
7023
|
let validated = 0;
|
|
6469
7024
|
for (const name of entries) {
|
|
@@ -6485,24 +7040,24 @@ function createValidateCommand2() {
|
|
|
6485
7040
|
}
|
|
6486
7041
|
|
|
6487
7042
|
// src/commands/skill/info.ts
|
|
6488
|
-
import { Command as
|
|
6489
|
-
import * as
|
|
6490
|
-
import * as
|
|
7043
|
+
import { Command as Command61 } from "commander";
|
|
7044
|
+
import * as fs33 from "fs";
|
|
7045
|
+
import * as path50 from "path";
|
|
6491
7046
|
import { parse as parse4 } from "yaml";
|
|
6492
7047
|
function loadSkillMetadata2(name) {
|
|
6493
7048
|
const skillsDir = resolveSkillsDir();
|
|
6494
|
-
const skillDir =
|
|
6495
|
-
if (!
|
|
7049
|
+
const skillDir = path50.join(skillsDir, name);
|
|
7050
|
+
if (!fs33.existsSync(skillDir)) {
|
|
6496
7051
|
logger.error(`Skill not found: ${name}`);
|
|
6497
7052
|
return { ok: false, exitCode: ExitCode.ERROR };
|
|
6498
7053
|
}
|
|
6499
|
-
const yamlPath =
|
|
6500
|
-
if (!
|
|
7054
|
+
const yamlPath = path50.join(skillDir, "skill.yaml");
|
|
7055
|
+
if (!fs33.existsSync(yamlPath)) {
|
|
6501
7056
|
logger.error(`skill.yaml not found for skill: ${name}`);
|
|
6502
7057
|
return { ok: false, exitCode: ExitCode.ERROR };
|
|
6503
7058
|
}
|
|
6504
7059
|
try {
|
|
6505
|
-
const raw =
|
|
7060
|
+
const raw = fs33.readFileSync(yamlPath, "utf-8");
|
|
6506
7061
|
const parsed = parse4(raw);
|
|
6507
7062
|
const result = SkillMetadataSchema.safeParse(parsed);
|
|
6508
7063
|
if (!result.success) {
|
|
@@ -6531,7 +7086,7 @@ function printSkillInfo(skill) {
|
|
|
6531
7086
|
console.log(`Persistent: ${skill.state.persistent}`);
|
|
6532
7087
|
}
|
|
6533
7088
|
function createInfoCommand() {
|
|
6534
|
-
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) => {
|
|
6535
7090
|
const globalOpts = cmd.optsWithGlobals();
|
|
6536
7091
|
const loaded = loadSkillMetadata2(name);
|
|
6537
7092
|
if (!loaded.ok) {
|
|
@@ -6548,7 +7103,7 @@ function createInfoCommand() {
|
|
|
6548
7103
|
}
|
|
6549
7104
|
|
|
6550
7105
|
// src/commands/skill/search.ts
|
|
6551
|
-
import { Command as
|
|
7106
|
+
import { Command as Command62 } from "commander";
|
|
6552
7107
|
async function runSearch(query, opts) {
|
|
6553
7108
|
const results = await searchNpmRegistry(query, opts.registry);
|
|
6554
7109
|
return results.filter((r) => {
|
|
@@ -6593,15 +7148,15 @@ async function runSearchAction(query, opts, globalOpts) {
|
|
|
6593
7148
|
}
|
|
6594
7149
|
}
|
|
6595
7150
|
function createSearchCommand() {
|
|
6596
|
-
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) => {
|
|
6597
7152
|
await runSearchAction(query, opts, cmd.optsWithGlobals());
|
|
6598
7153
|
});
|
|
6599
7154
|
}
|
|
6600
7155
|
|
|
6601
7156
|
// src/commands/skill/create.ts
|
|
6602
|
-
import { Command as
|
|
6603
|
-
import * as
|
|
6604
|
-
import * as
|
|
7157
|
+
import { Command as Command63 } from "commander";
|
|
7158
|
+
import * as path51 from "path";
|
|
7159
|
+
import * as fs34 from "fs";
|
|
6605
7160
|
import YAML from "yaml";
|
|
6606
7161
|
var KEBAB_CASE_RE = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/;
|
|
6607
7162
|
function buildReadme(name, description) {
|
|
@@ -6685,22 +7240,22 @@ function runCreate(name, opts) {
|
|
|
6685
7240
|
if (!KEBAB_CASE_RE.test(name)) {
|
|
6686
7241
|
throw new Error(`Invalid skill name "${name}". Must be kebab-case (e.g., my-skill).`);
|
|
6687
7242
|
}
|
|
6688
|
-
const baseDir = opts.outputDir ??
|
|
6689
|
-
const skillDir =
|
|
6690
|
-
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)) {
|
|
6691
7246
|
throw new Error(`Skill directory already exists: ${skillDir}`);
|
|
6692
7247
|
}
|
|
6693
|
-
|
|
7248
|
+
fs34.mkdirSync(skillDir, { recursive: true });
|
|
6694
7249
|
const description = opts.description || `A community skill: ${name}`;
|
|
6695
7250
|
const skillYaml = buildSkillYaml(name, opts);
|
|
6696
|
-
const skillYamlPath =
|
|
6697
|
-
|
|
7251
|
+
const skillYamlPath = path51.join(skillDir, "skill.yaml");
|
|
7252
|
+
fs34.writeFileSync(skillYamlPath, YAML.stringify(skillYaml));
|
|
6698
7253
|
const skillMd = buildSkillMd(name, description);
|
|
6699
|
-
const skillMdPath =
|
|
6700
|
-
|
|
7254
|
+
const skillMdPath = path51.join(skillDir, "SKILL.md");
|
|
7255
|
+
fs34.writeFileSync(skillMdPath, skillMd);
|
|
6701
7256
|
const readme = buildReadme(name, description);
|
|
6702
|
-
const readmePath =
|
|
6703
|
-
|
|
7257
|
+
const readmePath = path51.join(skillDir, "README.md");
|
|
7258
|
+
fs34.writeFileSync(readmePath, readme);
|
|
6704
7259
|
return {
|
|
6705
7260
|
name,
|
|
6706
7261
|
directory: skillDir,
|
|
@@ -6714,12 +7269,12 @@ function printCreateResult(name, result) {
|
|
|
6714
7269
|
}
|
|
6715
7270
|
logger.info(`
|
|
6716
7271
|
Next steps:`);
|
|
6717
|
-
logger.info(` 1. Edit ${
|
|
7272
|
+
logger.info(` 1. Edit ${path51.join(result.directory, "SKILL.md")} with your skill content`);
|
|
6718
7273
|
logger.info(` 2. Run: harness skill validate ${name}`);
|
|
6719
7274
|
logger.info(` 3. Run: harness skills publish`);
|
|
6720
7275
|
}
|
|
6721
7276
|
function createCreateCommand() {
|
|
6722
|
-
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) => {
|
|
6723
7278
|
const globalOpts = cmd.optsWithGlobals();
|
|
6724
7279
|
try {
|
|
6725
7280
|
const result = runCreate(name, {
|
|
@@ -6742,26 +7297,26 @@ function createCreateCommand() {
|
|
|
6742
7297
|
}
|
|
6743
7298
|
|
|
6744
7299
|
// src/commands/skill/publish.ts
|
|
6745
|
-
import { Command as
|
|
6746
|
-
import * as
|
|
6747
|
-
import * as
|
|
7300
|
+
import { Command as Command64 } from "commander";
|
|
7301
|
+
import * as fs36 from "fs";
|
|
7302
|
+
import * as path53 from "path";
|
|
6748
7303
|
import { execFileSync as execFileSync5 } from "child_process";
|
|
6749
7304
|
|
|
6750
7305
|
// src/registry/validator.ts
|
|
6751
|
-
import * as
|
|
6752
|
-
import * as
|
|
7306
|
+
import * as fs35 from "fs";
|
|
7307
|
+
import * as path52 from "path";
|
|
6753
7308
|
import { parse as parse5 } from "yaml";
|
|
6754
7309
|
import semver4 from "semver";
|
|
6755
7310
|
async function validateForPublish(skillDir, registryUrl) {
|
|
6756
7311
|
const errors = [];
|
|
6757
|
-
const skillYamlPath =
|
|
6758
|
-
if (!
|
|
7312
|
+
const skillYamlPath = path52.join(skillDir, "skill.yaml");
|
|
7313
|
+
if (!fs35.existsSync(skillYamlPath)) {
|
|
6759
7314
|
errors.push("skill.yaml not found. Create one with: harness skill create <name>");
|
|
6760
7315
|
return { valid: false, errors };
|
|
6761
7316
|
}
|
|
6762
7317
|
let skillMeta;
|
|
6763
7318
|
try {
|
|
6764
|
-
const raw =
|
|
7319
|
+
const raw = fs35.readFileSync(skillYamlPath, "utf-8");
|
|
6765
7320
|
const parsed = parse5(raw);
|
|
6766
7321
|
const result = SkillMetadataSchema.safeParse(parsed);
|
|
6767
7322
|
if (!result.success) {
|
|
@@ -6783,11 +7338,11 @@ async function validateForPublish(skillDir, registryUrl) {
|
|
|
6783
7338
|
if (!skillMeta.triggers || skillMeta.triggers.length === 0) {
|
|
6784
7339
|
errors.push("At least one trigger is required. Add triggers to skill.yaml.");
|
|
6785
7340
|
}
|
|
6786
|
-
const skillMdPath =
|
|
6787
|
-
if (!
|
|
7341
|
+
const skillMdPath = path52.join(skillDir, "SKILL.md");
|
|
7342
|
+
if (!fs35.existsSync(skillMdPath)) {
|
|
6788
7343
|
errors.push("SKILL.md not found. Create it with content describing your skill.");
|
|
6789
7344
|
} else {
|
|
6790
|
-
const content =
|
|
7345
|
+
const content = fs35.readFileSync(skillMdPath, "utf-8");
|
|
6791
7346
|
if (!content.includes("## When to Use")) {
|
|
6792
7347
|
errors.push('SKILL.md must contain a "## When to Use" section.');
|
|
6793
7348
|
}
|
|
@@ -6867,11 +7422,11 @@ ${errorList}`);
|
|
|
6867
7422
|
}
|
|
6868
7423
|
const meta = validation.skillMeta;
|
|
6869
7424
|
const pkg = derivePackageJson(meta);
|
|
6870
|
-
const pkgPath =
|
|
6871
|
-
|
|
6872
|
-
const readmePath =
|
|
6873
|
-
if (!
|
|
6874
|
-
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");
|
|
6875
7430
|
const readme = `# ${pkg.name}
|
|
6876
7431
|
|
|
6877
7432
|
${meta.description}
|
|
@@ -6885,7 +7440,7 @@ harness install ${meta.name}
|
|
|
6885
7440
|
---
|
|
6886
7441
|
|
|
6887
7442
|
${skillMdContent}`;
|
|
6888
|
-
|
|
7443
|
+
fs36.writeFileSync(readmePath, readme);
|
|
6889
7444
|
}
|
|
6890
7445
|
if (opts.dryRun) {
|
|
6891
7446
|
return {
|
|
@@ -6911,7 +7466,7 @@ ${skillMdContent}`;
|
|
|
6911
7466
|
};
|
|
6912
7467
|
}
|
|
6913
7468
|
function createPublishCommand() {
|
|
6914
|
-
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) => {
|
|
6915
7470
|
const globalOpts = cmd.optsWithGlobals();
|
|
6916
7471
|
const skillDir = opts.dir || process.cwd();
|
|
6917
7472
|
try {
|
|
@@ -6936,7 +7491,7 @@ function createPublishCommand() {
|
|
|
6936
7491
|
|
|
6937
7492
|
// src/commands/skill/index.ts
|
|
6938
7493
|
function createSkillCommand() {
|
|
6939
|
-
const command = new
|
|
7494
|
+
const command = new Command65("skill").description("Skill management commands");
|
|
6940
7495
|
command.addCommand(createListCommand3());
|
|
6941
7496
|
command.addCommand(createRunCommand2());
|
|
6942
7497
|
command.addCommand(createValidateCommand2());
|
|
@@ -6948,9 +7503,10 @@ function createSkillCommand() {
|
|
|
6948
7503
|
}
|
|
6949
7504
|
|
|
6950
7505
|
// src/commands/snapshot.ts
|
|
6951
|
-
|
|
7506
|
+
init_dist();
|
|
7507
|
+
import { Command as Command66 } from "commander";
|
|
6952
7508
|
import { execSync as execSync5 } from "child_process";
|
|
6953
|
-
import
|
|
7509
|
+
import chalk10 from "chalk";
|
|
6954
7510
|
function getCommitHash3(cwd) {
|
|
6955
7511
|
try {
|
|
6956
7512
|
return execSync5("git rev-parse --short HEAD", { cwd, encoding: "utf-8" }).toString().trim();
|
|
@@ -6975,9 +7531,9 @@ function formatDelta(delta) {
|
|
|
6975
7531
|
function directionSymbol(direction) {
|
|
6976
7532
|
switch (direction) {
|
|
6977
7533
|
case "improving":
|
|
6978
|
-
return
|
|
7534
|
+
return chalk10.green("improving");
|
|
6979
7535
|
case "declining":
|
|
6980
|
-
return
|
|
7536
|
+
return chalk10.red("declining");
|
|
6981
7537
|
case "stable":
|
|
6982
7538
|
return "=";
|
|
6983
7539
|
}
|
|
@@ -7130,7 +7686,7 @@ function registerListCommand(parent) {
|
|
|
7130
7686
|
});
|
|
7131
7687
|
}
|
|
7132
7688
|
function createSnapshotCommand() {
|
|
7133
|
-
const command = new
|
|
7689
|
+
const command = new Command66("snapshot").description("Architecture timeline snapshot commands");
|
|
7134
7690
|
registerCaptureCommand(command);
|
|
7135
7691
|
registerTrendsCommand(command);
|
|
7136
7692
|
registerListCommand(command);
|
|
@@ -7138,11 +7694,12 @@ function createSnapshotCommand() {
|
|
|
7138
7694
|
}
|
|
7139
7695
|
|
|
7140
7696
|
// src/commands/state/index.ts
|
|
7141
|
-
import { Command as
|
|
7697
|
+
import { Command as Command71 } from "commander";
|
|
7142
7698
|
|
|
7143
7699
|
// src/commands/state/show.ts
|
|
7144
|
-
|
|
7145
|
-
import
|
|
7700
|
+
init_dist();
|
|
7701
|
+
import { Command as Command67 } from "commander";
|
|
7702
|
+
import * as path54 from "path";
|
|
7146
7703
|
function printStateText(state, stream) {
|
|
7147
7704
|
if (stream) console.log(`Stream: ${stream}`);
|
|
7148
7705
|
console.log(`Schema Version: ${state.schemaVersion}`);
|
|
@@ -7166,9 +7723,9 @@ function printStateProgress(progress) {
|
|
|
7166
7723
|
}
|
|
7167
7724
|
}
|
|
7168
7725
|
function createShowCommand() {
|
|
7169
|
-
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) => {
|
|
7170
7727
|
const globalOpts = cmd.optsWithGlobals();
|
|
7171
|
-
const result = await loadState(
|
|
7728
|
+
const result = await loadState(path54.resolve(opts.path), opts.stream);
|
|
7172
7729
|
if (!result.ok) {
|
|
7173
7730
|
logger.error(result.error.message);
|
|
7174
7731
|
process.exit(ExitCode.ERROR);
|
|
@@ -7183,10 +7740,11 @@ function createShowCommand() {
|
|
|
7183
7740
|
}
|
|
7184
7741
|
|
|
7185
7742
|
// src/commands/state/reset.ts
|
|
7186
|
-
|
|
7187
|
-
import
|
|
7188
|
-
import * as
|
|
7189
|
-
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";
|
|
7190
7748
|
async function resolveStatePath(projectPath, stream) {
|
|
7191
7749
|
if (stream) {
|
|
7192
7750
|
const streamResult = await resolveStreamPath(projectPath, { stream });
|
|
@@ -7194,22 +7752,22 @@ async function resolveStatePath(projectPath, stream) {
|
|
|
7194
7752
|
logger.error(streamResult.error.message);
|
|
7195
7753
|
process.exit(ExitCode.ERROR);
|
|
7196
7754
|
}
|
|
7197
|
-
return
|
|
7755
|
+
return path55.join(streamResult.value, "state.json");
|
|
7198
7756
|
}
|
|
7199
|
-
return
|
|
7757
|
+
return path55.join(projectPath, ".harness", "state.json");
|
|
7200
7758
|
}
|
|
7201
7759
|
async function confirmReset() {
|
|
7202
|
-
const rl =
|
|
7203
|
-
const answer = await new Promise((
|
|
7204
|
-
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);
|
|
7205
7763
|
});
|
|
7206
7764
|
rl.close();
|
|
7207
7765
|
return answer.toLowerCase() === "y" || answer.toLowerCase() === "yes";
|
|
7208
7766
|
}
|
|
7209
7767
|
async function runReset(opts) {
|
|
7210
|
-
const projectPath =
|
|
7768
|
+
const projectPath = path55.resolve(opts.path);
|
|
7211
7769
|
const statePath = await resolveStatePath(projectPath, opts.stream);
|
|
7212
|
-
if (!
|
|
7770
|
+
if (!fs37.existsSync(statePath)) {
|
|
7213
7771
|
logger.info("No state file found. Nothing to reset.");
|
|
7214
7772
|
process.exit(ExitCode.SUCCESS);
|
|
7215
7773
|
}
|
|
@@ -7218,7 +7776,7 @@ async function runReset(opts) {
|
|
|
7218
7776
|
process.exit(ExitCode.SUCCESS);
|
|
7219
7777
|
}
|
|
7220
7778
|
try {
|
|
7221
|
-
|
|
7779
|
+
fs37.unlinkSync(statePath);
|
|
7222
7780
|
logger.success("Project state reset.");
|
|
7223
7781
|
} catch (e) {
|
|
7224
7782
|
logger.error(`Failed to reset state: ${e instanceof Error ? e.message : String(e)}`);
|
|
@@ -7227,15 +7785,16 @@ async function runReset(opts) {
|
|
|
7227
7785
|
process.exit(ExitCode.SUCCESS);
|
|
7228
7786
|
}
|
|
7229
7787
|
function createResetCommand() {
|
|
7230
|
-
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));
|
|
7231
7789
|
}
|
|
7232
7790
|
|
|
7233
7791
|
// src/commands/state/learn.ts
|
|
7234
|
-
|
|
7235
|
-
import
|
|
7792
|
+
init_dist();
|
|
7793
|
+
import { Command as Command69 } from "commander";
|
|
7794
|
+
import * as path56 from "path";
|
|
7236
7795
|
function createLearnCommand() {
|
|
7237
|
-
return new
|
|
7238
|
-
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);
|
|
7239
7798
|
const result = await appendLearning(projectPath, message, void 0, void 0, opts.stream);
|
|
7240
7799
|
if (!result.ok) {
|
|
7241
7800
|
logger.error(result.error.message);
|
|
@@ -7248,10 +7807,11 @@ function createLearnCommand() {
|
|
|
7248
7807
|
}
|
|
7249
7808
|
|
|
7250
7809
|
// src/commands/state/streams.ts
|
|
7251
|
-
|
|
7252
|
-
import
|
|
7810
|
+
init_dist();
|
|
7811
|
+
import { Command as Command70 } from "commander";
|
|
7812
|
+
import * as path57 from "path";
|
|
7253
7813
|
async function runListStreams(opts, globalOpts) {
|
|
7254
|
-
const projectPath =
|
|
7814
|
+
const projectPath = path57.resolve(opts.path);
|
|
7255
7815
|
const indexResult = await loadStreamIndex(projectPath);
|
|
7256
7816
|
const result = await listStreams(projectPath);
|
|
7257
7817
|
if (!result.ok) {
|
|
@@ -7272,7 +7832,7 @@ async function runListStreams(opts, globalOpts) {
|
|
|
7272
7832
|
process.exit(ExitCode.SUCCESS);
|
|
7273
7833
|
}
|
|
7274
7834
|
async function runCreateStream(name, opts) {
|
|
7275
|
-
const result = await createStream(
|
|
7835
|
+
const result = await createStream(path57.resolve(opts.path), name, opts.branch);
|
|
7276
7836
|
if (!result.ok) {
|
|
7277
7837
|
logger.error(result.error.message);
|
|
7278
7838
|
process.exit(ExitCode.ERROR);
|
|
@@ -7281,7 +7841,7 @@ async function runCreateStream(name, opts) {
|
|
|
7281
7841
|
process.exit(ExitCode.SUCCESS);
|
|
7282
7842
|
}
|
|
7283
7843
|
async function runArchiveStream(name, opts) {
|
|
7284
|
-
const result = await archiveStream(
|
|
7844
|
+
const result = await archiveStream(path57.resolve(opts.path), name);
|
|
7285
7845
|
if (!result.ok) {
|
|
7286
7846
|
logger.error(result.error.message);
|
|
7287
7847
|
process.exit(ExitCode.ERROR);
|
|
@@ -7290,7 +7850,7 @@ async function runArchiveStream(name, opts) {
|
|
|
7290
7850
|
process.exit(ExitCode.SUCCESS);
|
|
7291
7851
|
}
|
|
7292
7852
|
async function runActivateStream(name, opts) {
|
|
7293
|
-
const result = await setActiveStream(
|
|
7853
|
+
const result = await setActiveStream(path57.resolve(opts.path), name);
|
|
7294
7854
|
if (!result.ok) {
|
|
7295
7855
|
logger.error(result.error.message);
|
|
7296
7856
|
process.exit(ExitCode.ERROR);
|
|
@@ -7299,7 +7859,7 @@ async function runActivateStream(name, opts) {
|
|
|
7299
7859
|
process.exit(ExitCode.SUCCESS);
|
|
7300
7860
|
}
|
|
7301
7861
|
function createStreamsCommand() {
|
|
7302
|
-
const command = new
|
|
7862
|
+
const command = new Command70("streams").description("Manage state streams");
|
|
7303
7863
|
command.command("list").description("List all known streams").option("--path <path>", "Project root path", ".").action(async (opts, cmd) => runListStreams(opts, cmd.optsWithGlobals()));
|
|
7304
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));
|
|
7305
7865
|
command.command("archive <name>").description("Archive a stream").option("--path <path>", "Project root path", ".").action(async (name, opts) => runArchiveStream(name, opts));
|
|
@@ -7309,7 +7869,7 @@ function createStreamsCommand() {
|
|
|
7309
7869
|
|
|
7310
7870
|
// src/commands/state/index.ts
|
|
7311
7871
|
function createStateCommand() {
|
|
7312
|
-
const command = new
|
|
7872
|
+
const command = new Command71("state").description("Project state management commands");
|
|
7313
7873
|
command.addCommand(createShowCommand());
|
|
7314
7874
|
command.addCommand(createResetCommand());
|
|
7315
7875
|
command.addCommand(createLearnCommand());
|
|
@@ -7317,8 +7877,154 @@ function createStateCommand() {
|
|
|
7317
7877
|
return command;
|
|
7318
7878
|
}
|
|
7319
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
|
+
|
|
7320
8025
|
// src/commands/taint.ts
|
|
7321
|
-
|
|
8026
|
+
init_dist();
|
|
8027
|
+
import { Command as Command73 } from "commander";
|
|
7322
8028
|
function getProjectRoot() {
|
|
7323
8029
|
return process.cwd();
|
|
7324
8030
|
}
|
|
@@ -7390,27 +8096,28 @@ function registerStatusCommand(taint) {
|
|
|
7390
8096
|
});
|
|
7391
8097
|
}
|
|
7392
8098
|
function createTaintCommand() {
|
|
7393
|
-
const taint = new
|
|
8099
|
+
const taint = new Command73("taint").description("Manage sentinel session taint state");
|
|
7394
8100
|
registerClearCommand(taint);
|
|
7395
8101
|
registerStatusCommand(taint);
|
|
7396
8102
|
return taint;
|
|
7397
8103
|
}
|
|
7398
8104
|
|
|
7399
8105
|
// src/commands/telemetry/index.ts
|
|
7400
|
-
import { Command as
|
|
8106
|
+
import { Command as Command76 } from "commander";
|
|
7401
8107
|
|
|
7402
8108
|
// src/commands/telemetry/identify.ts
|
|
7403
|
-
|
|
7404
|
-
import
|
|
7405
|
-
import * as
|
|
8109
|
+
init_dist();
|
|
8110
|
+
import { Command as Command74 } from "commander";
|
|
8111
|
+
import * as fs39 from "fs";
|
|
8112
|
+
import * as path59 from "path";
|
|
7406
8113
|
function telemetryFilePath(cwd) {
|
|
7407
|
-
return
|
|
8114
|
+
return path59.join(cwd, ".harness", "telemetry.json");
|
|
7408
8115
|
}
|
|
7409
8116
|
function writeTelemetryFile(cwd, data) {
|
|
7410
8117
|
const filePath = telemetryFilePath(cwd);
|
|
7411
|
-
const dir =
|
|
7412
|
-
|
|
7413
|
-
|
|
8118
|
+
const dir = path59.dirname(filePath);
|
|
8119
|
+
fs39.mkdirSync(dir, { recursive: true });
|
|
8120
|
+
fs39.writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n", "utf-8");
|
|
7414
8121
|
}
|
|
7415
8122
|
var IDENTITY_FIELDS = ["project", "team", "alias"];
|
|
7416
8123
|
function applyIdentityFields(opts, identity) {
|
|
@@ -7424,7 +8131,7 @@ function printIdentity(identity) {
|
|
|
7424
8131
|
}
|
|
7425
8132
|
}
|
|
7426
8133
|
function createIdentifyCommand() {
|
|
7427
|
-
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) => {
|
|
7428
8135
|
const cwd = process.cwd();
|
|
7429
8136
|
if (opts.clear) {
|
|
7430
8137
|
writeTelemetryFile(cwd, { identity: {} });
|
|
@@ -7448,7 +8155,8 @@ function createIdentifyCommand() {
|
|
|
7448
8155
|
}
|
|
7449
8156
|
|
|
7450
8157
|
// src/commands/telemetry/status.ts
|
|
7451
|
-
|
|
8158
|
+
init_dist();
|
|
8159
|
+
import { Command as Command75 } from "commander";
|
|
7452
8160
|
function gatherEnvOverrides() {
|
|
7453
8161
|
const overrides = {};
|
|
7454
8162
|
if (process.env.DO_NOT_TRACK) overrides.DO_NOT_TRACK = process.env.DO_NOT_TRACK;
|
|
@@ -7486,7 +8194,7 @@ function printHumanStatus(result) {
|
|
|
7486
8194
|
}
|
|
7487
8195
|
}
|
|
7488
8196
|
function createStatusCommand() {
|
|
7489
|
-
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) => {
|
|
7490
8198
|
const cwd = process.cwd();
|
|
7491
8199
|
const envOverrides = gatherEnvOverrides();
|
|
7492
8200
|
const consent = resolveConsent(cwd, void 0);
|
|
@@ -7519,15 +8227,15 @@ function createStatusCommand() {
|
|
|
7519
8227
|
|
|
7520
8228
|
// src/commands/telemetry/index.ts
|
|
7521
8229
|
function createTelemetryCommand() {
|
|
7522
|
-
const command = new
|
|
8230
|
+
const command = new Command76("telemetry").description("Telemetry identity and status management");
|
|
7523
8231
|
command.addCommand(createIdentifyCommand());
|
|
7524
8232
|
command.addCommand(createStatusCommand());
|
|
7525
8233
|
return command;
|
|
7526
8234
|
}
|
|
7527
8235
|
|
|
7528
8236
|
// src/commands/traceability.ts
|
|
7529
|
-
import { Command as
|
|
7530
|
-
import
|
|
8237
|
+
import { Command as Command77 } from "commander";
|
|
8238
|
+
import chalk11 from "chalk";
|
|
7531
8239
|
function confidenceLabel(maxConfidence) {
|
|
7532
8240
|
if (maxConfidence >= 0.8) return "explicit";
|
|
7533
8241
|
if (maxConfidence > 0) return "inferred";
|
|
@@ -7536,13 +8244,13 @@ function confidenceLabel(maxConfidence) {
|
|
|
7536
8244
|
function statusIcon(status) {
|
|
7537
8245
|
switch (status) {
|
|
7538
8246
|
case "full":
|
|
7539
|
-
return
|
|
8247
|
+
return chalk11.green("\u2713 full");
|
|
7540
8248
|
case "code-only":
|
|
7541
|
-
return
|
|
8249
|
+
return chalk11.yellow("\u25D0 code-only");
|
|
7542
8250
|
case "test-only":
|
|
7543
|
-
return
|
|
8251
|
+
return chalk11.yellow("\u25D0 test-only");
|
|
7544
8252
|
case "none":
|
|
7545
|
-
return
|
|
8253
|
+
return chalk11.red("\u2717 none");
|
|
7546
8254
|
default:
|
|
7547
8255
|
return status;
|
|
7548
8256
|
}
|
|
@@ -7584,14 +8292,14 @@ function handleEmptyResults(mode) {
|
|
|
7584
8292
|
}
|
|
7585
8293
|
function printResultTable(result, mode) {
|
|
7586
8294
|
const specLabel = result.specPath || result.featureName || "Unknown";
|
|
7587
|
-
console.log(` ${
|
|
8295
|
+
console.log(` ${chalk11.cyan(specLabel)} ${chalk11.dim(`(${result.summary.total} requirements)`)}`);
|
|
7588
8296
|
console.log("");
|
|
7589
8297
|
const numWidth = 4;
|
|
7590
8298
|
const nameWidth = mode === OutputMode.VERBOSE ? 44 : 36;
|
|
7591
8299
|
const codeWidth = 6;
|
|
7592
8300
|
const testWidth = 7;
|
|
7593
8301
|
const confWidth = 12;
|
|
7594
|
-
const header =
|
|
8302
|
+
const header = chalk11.dim(
|
|
7595
8303
|
` ${pad("#", numWidth)}${pad("Requirement", nameWidth)}${pad("Code", codeWidth)}${pad("Tests", testWidth)}${pad("Confidence", confWidth)}Status`
|
|
7596
8304
|
);
|
|
7597
8305
|
console.log(header);
|
|
@@ -7620,10 +8328,10 @@ function printRequirementRows(requirements, mode, cols) {
|
|
|
7620
8328
|
);
|
|
7621
8329
|
if (mode === OutputMode.VERBOSE) {
|
|
7622
8330
|
for (const f of req.codeFiles) {
|
|
7623
|
-
console.log(
|
|
8331
|
+
console.log(chalk11.dim(` code: ${f.path} (${f.method})`));
|
|
7624
8332
|
}
|
|
7625
8333
|
for (const f of req.testFiles) {
|
|
7626
|
-
console.log(
|
|
8334
|
+
console.log(chalk11.dim(` test: ${f.path} (${f.method})`));
|
|
7627
8335
|
}
|
|
7628
8336
|
}
|
|
7629
8337
|
}
|
|
@@ -7634,7 +8342,7 @@ function printCoverageSummary(summary) {
|
|
|
7634
8342
|
const codePct = s.total > 0 ? Math.round(s.withCode / s.total * 100) : 0;
|
|
7635
8343
|
const testPct = s.total > 0 ? Math.round(s.withTests / s.total * 100) : 0;
|
|
7636
8344
|
console.log(
|
|
7637
|
-
` ${
|
|
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})`
|
|
7638
8346
|
);
|
|
7639
8347
|
}
|
|
7640
8348
|
async function runTraceability(options) {
|
|
@@ -7642,7 +8350,7 @@ async function runTraceability(options) {
|
|
|
7642
8350
|
const projectPath = process.cwd();
|
|
7643
8351
|
const store = await loadGraphStore(projectPath);
|
|
7644
8352
|
if (!store) handleNoStore(mode);
|
|
7645
|
-
const graphModule = await import("./dist-
|
|
8353
|
+
const graphModule = await import("./dist-RADHOOXG.js");
|
|
7646
8354
|
const filterOptions = buildFilterOptions(options);
|
|
7647
8355
|
const results = graphModule.queryTraceability(store, filterOptions);
|
|
7648
8356
|
if (results.length === 0) handleEmptyResults(mode);
|
|
@@ -7650,14 +8358,14 @@ async function runTraceability(options) {
|
|
|
7650
8358
|
console.log(JSON.stringify(results, null, 2));
|
|
7651
8359
|
} else {
|
|
7652
8360
|
console.log("");
|
|
7653
|
-
console.log(
|
|
8361
|
+
console.log(chalk11.bold("Spec-to-Implementation Traceability"));
|
|
7654
8362
|
console.log("");
|
|
7655
8363
|
for (const result of results) printResultTable(result, mode);
|
|
7656
8364
|
}
|
|
7657
8365
|
process.exit(ExitCode.SUCCESS);
|
|
7658
8366
|
}
|
|
7659
8367
|
function createTraceabilityCommand() {
|
|
7660
|
-
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) => {
|
|
7661
8369
|
const globalOpts = cmd.optsWithGlobals();
|
|
7662
8370
|
await runTraceability({
|
|
7663
8371
|
spec: opts.spec,
|
|
@@ -7670,15 +8378,15 @@ function createTraceabilityCommand() {
|
|
|
7670
8378
|
}
|
|
7671
8379
|
|
|
7672
8380
|
// src/commands/uninstall.ts
|
|
7673
|
-
import * as
|
|
7674
|
-
import { Command as
|
|
8381
|
+
import * as path60 from "path";
|
|
8382
|
+
import { Command as Command78 } from "commander";
|
|
7675
8383
|
async function runUninstall(skillName, options) {
|
|
7676
8384
|
const packageName = resolvePackageName(skillName);
|
|
7677
8385
|
const shortName = extractSkillName(packageName);
|
|
7678
8386
|
const globalDir = resolveGlobalSkillsDir();
|
|
7679
|
-
const skillsDir =
|
|
7680
|
-
const communityBase =
|
|
7681
|
-
const lockfilePath =
|
|
8387
|
+
const skillsDir = path60.dirname(globalDir);
|
|
8388
|
+
const communityBase = path60.join(skillsDir, "community");
|
|
8389
|
+
const lockfilePath = path60.join(communityBase, "skills-lock.json");
|
|
7682
8390
|
const lockfile = readLockfile2(lockfilePath);
|
|
7683
8391
|
const entry = lockfile.skills[packageName];
|
|
7684
8392
|
if (!entry) {
|
|
@@ -7708,7 +8416,7 @@ async function runUninstall(skillName, options) {
|
|
|
7708
8416
|
return result;
|
|
7709
8417
|
}
|
|
7710
8418
|
function createUninstallCommand() {
|
|
7711
|
-
const cmd = new
|
|
8419
|
+
const cmd = new Command78("uninstall");
|
|
7712
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) => {
|
|
7713
8421
|
try {
|
|
7714
8422
|
const result = await runUninstall(skill, opts);
|
|
@@ -7727,9 +8435,10 @@ function createUninstallCommand() {
|
|
|
7727
8435
|
}
|
|
7728
8436
|
|
|
7729
8437
|
// src/commands/uninstall-constraints.ts
|
|
7730
|
-
|
|
7731
|
-
import * as
|
|
7732
|
-
import
|
|
8438
|
+
init_dist();
|
|
8439
|
+
import * as fs40 from "fs/promises";
|
|
8440
|
+
import * as path61 from "path";
|
|
8441
|
+
import { Command as Command79 } from "commander";
|
|
7733
8442
|
async function runUninstallConstraints(options) {
|
|
7734
8443
|
const { packageName, configPath, lockfilePath } = options;
|
|
7735
8444
|
const lockfileResult = await readLockfile(lockfilePath);
|
|
@@ -7749,7 +8458,7 @@ async function runUninstallConstraints(options) {
|
|
|
7749
8458
|
}
|
|
7750
8459
|
let localConfig;
|
|
7751
8460
|
try {
|
|
7752
|
-
const raw = await
|
|
8461
|
+
const raw = await fs40.readFile(configPath, "utf-8");
|
|
7753
8462
|
localConfig = JSON.parse(raw);
|
|
7754
8463
|
} catch (err) {
|
|
7755
8464
|
return {
|
|
@@ -7786,11 +8495,11 @@ async function runUninstallConstraints(options) {
|
|
|
7786
8495
|
};
|
|
7787
8496
|
}
|
|
7788
8497
|
function createUninstallConstraintsCommand() {
|
|
7789
|
-
const cmd = new
|
|
8498
|
+
const cmd = new Command79("uninstall-constraints");
|
|
7790
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) => {
|
|
7791
8500
|
let configPath;
|
|
7792
8501
|
if (opts.config) {
|
|
7793
|
-
configPath =
|
|
8502
|
+
configPath = path61.resolve(opts.config);
|
|
7794
8503
|
} else {
|
|
7795
8504
|
const found = findConfigFile();
|
|
7796
8505
|
if (!found.ok) {
|
|
@@ -7799,8 +8508,8 @@ function createUninstallConstraintsCommand() {
|
|
|
7799
8508
|
}
|
|
7800
8509
|
configPath = found.value;
|
|
7801
8510
|
}
|
|
7802
|
-
const projectRoot =
|
|
7803
|
-
const lockfilePath =
|
|
8511
|
+
const projectRoot = path61.dirname(configPath);
|
|
8512
|
+
const lockfilePath = path61.join(projectRoot, ".harness", "constraints.lock.json");
|
|
7804
8513
|
const result = await runUninstallConstraints({
|
|
7805
8514
|
packageName: name,
|
|
7806
8515
|
configPath,
|
|
@@ -7825,13 +8534,13 @@ function createUninstallConstraintsCommand() {
|
|
|
7825
8534
|
}
|
|
7826
8535
|
|
|
7827
8536
|
// src/commands/update.ts
|
|
7828
|
-
import { Command as
|
|
8537
|
+
import { Command as Command80 } from "commander";
|
|
7829
8538
|
import { execFile, execFileSync as execFileSync6 } from "child_process";
|
|
7830
|
-
import { realpathSync, existsSync as
|
|
7831
|
-
import { join as
|
|
8539
|
+
import { realpathSync, existsSync as existsSync36, readFileSync as readFileSync25 } from "fs";
|
|
8540
|
+
import { join as join47 } from "path";
|
|
7832
8541
|
import { promisify } from "util";
|
|
7833
|
-
import
|
|
7834
|
-
import
|
|
8542
|
+
import readline3 from "readline";
|
|
8543
|
+
import chalk12 from "chalk";
|
|
7835
8544
|
function detectPackageManager() {
|
|
7836
8545
|
try {
|
|
7837
8546
|
const argv1 = process.argv[1];
|
|
@@ -7889,26 +8598,35 @@ function getInstalledPackages(pm) {
|
|
|
7889
8598
|
return ["@harness-engineering/cli", "@harness-engineering/core"];
|
|
7890
8599
|
}
|
|
7891
8600
|
}
|
|
7892
|
-
function
|
|
7893
|
-
const rl =
|
|
8601
|
+
function prompt2(question) {
|
|
8602
|
+
const rl = readline3.createInterface({
|
|
7894
8603
|
input: process.stdin,
|
|
7895
8604
|
output: process.stdout
|
|
7896
8605
|
});
|
|
7897
|
-
return new Promise((
|
|
8606
|
+
return new Promise((resolve35) => {
|
|
7898
8607
|
rl.question(question, (answer) => {
|
|
7899
8608
|
rl.close();
|
|
7900
|
-
|
|
8609
|
+
resolve35(answer.trim().toLowerCase());
|
|
7901
8610
|
});
|
|
7902
8611
|
});
|
|
7903
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
|
+
}
|
|
7904
8622
|
function refreshHooks() {
|
|
7905
8623
|
const cwd = process.cwd();
|
|
7906
|
-
const configPath =
|
|
7907
|
-
if (!
|
|
8624
|
+
const configPath = join47(cwd, "harness.config.json");
|
|
8625
|
+
if (!existsSync36(configPath)) return;
|
|
7908
8626
|
let profile = "standard";
|
|
7909
|
-
const profilePath =
|
|
8627
|
+
const profilePath = join47(cwd, ".harness", "hooks", "profile.json");
|
|
7910
8628
|
try {
|
|
7911
|
-
const data = JSON.parse(
|
|
8629
|
+
const data = JSON.parse(readFileSync25(profilePath, "utf-8"));
|
|
7912
8630
|
if (data.profile && ["minimal", "standard", "strict"].includes(data.profile)) {
|
|
7913
8631
|
profile = data.profile;
|
|
7914
8632
|
}
|
|
@@ -7928,16 +8646,16 @@ function runLocalGraphScan() {
|
|
|
7928
8646
|
execFileSync6("harness", ["graph", "scan", "."], { stdio: "inherit" });
|
|
7929
8647
|
} catch {
|
|
7930
8648
|
logger.warn("Graph scan failed. Run manually:");
|
|
7931
|
-
console.log(` ${
|
|
8649
|
+
console.log(` ${chalk12.cyan("harness graph scan .")}`);
|
|
7932
8650
|
}
|
|
7933
8651
|
}
|
|
7934
8652
|
async function offerRegeneration() {
|
|
7935
8653
|
console.log("");
|
|
7936
|
-
const regenAnswer = await
|
|
8654
|
+
const regenAnswer = await prompt2(
|
|
7937
8655
|
"Regenerate slash commands, agent definitions, and knowledge graph? (Y/n) "
|
|
7938
8656
|
);
|
|
7939
8657
|
if (regenAnswer === "n" || regenAnswer === "no") return;
|
|
7940
|
-
const scopeAnswer = await
|
|
8658
|
+
const scopeAnswer = await prompt2("Generate for (G)lobal or (l)ocal project? (G/l) ");
|
|
7941
8659
|
const isGlobal = scopeAnswer !== "l" && scopeAnswer !== "local";
|
|
7942
8660
|
try {
|
|
7943
8661
|
execFileSync6("harness", ["generate", ...isGlobal ? ["--global"] : []], {
|
|
@@ -7945,7 +8663,7 @@ async function offerRegeneration() {
|
|
|
7945
8663
|
});
|
|
7946
8664
|
} catch {
|
|
7947
8665
|
logger.warn("Generation failed. Run manually:");
|
|
7948
|
-
console.log(` ${
|
|
8666
|
+
console.log(` ${chalk12.cyan(`harness generate${isGlobal ? " --global" : ""}`)}`);
|
|
7949
8667
|
}
|
|
7950
8668
|
if (!isGlobal) {
|
|
7951
8669
|
runLocalGraphScan();
|
|
@@ -8001,14 +8719,15 @@ async function runUpdateAction(opts, globalOpts) {
|
|
|
8001
8719
|
if (!hasUpdates) {
|
|
8002
8720
|
logger.success("All packages are up to date");
|
|
8003
8721
|
refreshHooks();
|
|
8722
|
+
await ensureTelemetryIfNeeded();
|
|
8004
8723
|
await offerRegeneration();
|
|
8005
8724
|
process.exit(ExitCode.SUCCESS);
|
|
8006
8725
|
}
|
|
8007
8726
|
console.log("");
|
|
8008
8727
|
for (const { pkg, current, latest } of outdated) {
|
|
8009
8728
|
const shortName = pkg.replace("@harness-engineering/", "");
|
|
8010
|
-
const currentStr = current ?
|
|
8011
|
-
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}`)}`);
|
|
8012
8731
|
}
|
|
8013
8732
|
console.log("");
|
|
8014
8733
|
}
|
|
@@ -8024,15 +8743,16 @@ async function runUpdateAction(opts, globalOpts) {
|
|
|
8024
8743
|
} catch {
|
|
8025
8744
|
console.log("");
|
|
8026
8745
|
logger.error("Update failed. You can try manually:");
|
|
8027
|
-
console.log(` ${
|
|
8746
|
+
console.log(` ${chalk12.cyan(installCmd)}`);
|
|
8028
8747
|
process.exit(ExitCode.ERROR);
|
|
8029
8748
|
}
|
|
8030
8749
|
refreshHooks();
|
|
8750
|
+
await ensureTelemetryIfNeeded();
|
|
8031
8751
|
await offerRegeneration();
|
|
8032
8752
|
process.exit(ExitCode.SUCCESS);
|
|
8033
8753
|
}
|
|
8034
8754
|
function createUpdateCommand() {
|
|
8035
|
-
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(
|
|
8036
8756
|
"--regenerate",
|
|
8037
8757
|
"Only regenerate slash commands and agent definitions (skip package updates)"
|
|
8038
8758
|
).action(async (opts, cmd) => {
|
|
@@ -8042,15 +8762,15 @@ function createUpdateCommand() {
|
|
|
8042
8762
|
}
|
|
8043
8763
|
|
|
8044
8764
|
// src/commands/usage.ts
|
|
8045
|
-
import { Command as
|
|
8765
|
+
import { Command as Command81 } from "commander";
|
|
8046
8766
|
async function loadAndPriceRecords(cwd, includeClaudeSessions = false) {
|
|
8047
|
-
const { readCostRecords, loadPricingData, calculateCost, parseCCRecords } = await import("./dist-
|
|
8767
|
+
const { readCostRecords, loadPricingData, calculateCost, parseCCRecords } = await import("./dist-WCSJHQPK.js");
|
|
8048
8768
|
const records = readCostRecords(cwd);
|
|
8049
8769
|
if (includeClaudeSessions) {
|
|
8050
8770
|
const ccRecords = parseCCRecords();
|
|
8051
8771
|
records.push(...ccRecords);
|
|
8052
8772
|
}
|
|
8053
|
-
if (records.length === 0) return records;
|
|
8773
|
+
if (records.length === 0) return { records, pricingData: null };
|
|
8054
8774
|
const pricingData = await loadPricingData(cwd);
|
|
8055
8775
|
for (const record of records) {
|
|
8056
8776
|
if (record.model && record.costMicroUSD == null) {
|
|
@@ -8058,7 +8778,14 @@ async function loadAndPriceRecords(cwd, includeClaudeSessions = false) {
|
|
|
8058
8778
|
if (cost != null) record.costMicroUSD = cost;
|
|
8059
8779
|
}
|
|
8060
8780
|
}
|
|
8061
|
-
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);
|
|
8062
8789
|
}
|
|
8063
8790
|
function formatMicroUSD(microUSD) {
|
|
8064
8791
|
if (microUSD == null) return "N/A";
|
|
@@ -8086,7 +8813,10 @@ function registerDailyCommand(usage) {
|
|
|
8086
8813
|
const globalOpts = cmd.optsWithGlobals();
|
|
8087
8814
|
const days = Math.min(Math.max(parseInt(opts.days, 10) || 7, 1), 90);
|
|
8088
8815
|
const cwd = process.cwd();
|
|
8089
|
-
const records = await loadAndPriceRecords(
|
|
8816
|
+
const { records, pricingData } = await loadAndPriceRecords(
|
|
8817
|
+
cwd,
|
|
8818
|
+
globalOpts.includeClaudeSessions
|
|
8819
|
+
);
|
|
8090
8820
|
if (records.length === 0) {
|
|
8091
8821
|
if (globalOpts.json) {
|
|
8092
8822
|
console.log(JSON.stringify([]));
|
|
@@ -8095,23 +8825,25 @@ function registerDailyCommand(usage) {
|
|
|
8095
8825
|
}
|
|
8096
8826
|
return;
|
|
8097
8827
|
}
|
|
8098
|
-
const { aggregateByDay } = await import("./dist-
|
|
8828
|
+
const { aggregateByDay } = await import("./dist-WCSJHQPK.js");
|
|
8099
8829
|
const dailyData = aggregateByDay(records);
|
|
8100
8830
|
const limited = dailyData.slice(0, days);
|
|
8101
8831
|
if (globalOpts.json) {
|
|
8102
8832
|
const enriched = limited.map((day) => {
|
|
8103
8833
|
const hitRate = computeCacheHitRate(day.cacheReadTokens, day.tokens.inputTokens);
|
|
8834
|
+
const savings = computeCacheSavings(day.cacheReadTokens, day.models[0], pricingData);
|
|
8104
8835
|
return {
|
|
8105
8836
|
...day,
|
|
8106
|
-
...hitRate != null ? { cacheHitRate: Math.round(hitRate * 1e3) / 1e3 } : {}
|
|
8837
|
+
...hitRate != null ? { cacheHitRate: Math.round(hitRate * 1e3) / 1e3 } : {},
|
|
8838
|
+
...savings != null ? { cacheSavingsMicroUSD: savings } : {}
|
|
8107
8839
|
};
|
|
8108
8840
|
});
|
|
8109
8841
|
console.log(JSON.stringify(enriched, null, 2));
|
|
8110
8842
|
return;
|
|
8111
8843
|
}
|
|
8112
8844
|
const hasCacheData = limited.some((d) => d.cacheReadTokens != null && d.cacheReadTokens > 0);
|
|
8113
|
-
const cacheHeader = hasCacheData ? " | Cache " : "";
|
|
8114
|
-
const cacheDivider = hasCacheData ? " | ------" : "";
|
|
8845
|
+
const cacheHeader = hasCacheData ? " | Cache | Saved " : "";
|
|
8846
|
+
const cacheDivider = hasCacheData ? " | ------ | -------" : "";
|
|
8115
8847
|
const header = "Date | Sessions | Input | Output | Model(s) | Cost " + cacheHeader;
|
|
8116
8848
|
const divider = "-------------|----------|-----------|-----------|------------------------------|-------" + cacheDivider;
|
|
8117
8849
|
logger.info(header);
|
|
@@ -8125,7 +8857,10 @@ function registerDailyCommand(usage) {
|
|
|
8125
8857
|
const cost = formatMicroUSD(day.costMicroUSD);
|
|
8126
8858
|
const cacheCol = hasCacheData ? (() => {
|
|
8127
8859
|
const rate = computeCacheHitRate(day.cacheReadTokens, day.tokens.inputTokens);
|
|
8128
|
-
|
|
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;
|
|
8129
8864
|
})() : "";
|
|
8130
8865
|
logger.info(
|
|
8131
8866
|
`${date} | ${sessions} | ${input} | ${output} | ${models} | ${cost}${cacheCol}`
|
|
@@ -8138,7 +8873,7 @@ function registerSessionsCommand(usage) {
|
|
|
8138
8873
|
const globalOpts = cmd.optsWithGlobals();
|
|
8139
8874
|
const limit = Math.min(Math.max(parseInt(opts.limit, 10) || 10, 1), 100);
|
|
8140
8875
|
const cwd = process.cwd();
|
|
8141
|
-
const records = await loadAndPriceRecords(cwd, globalOpts.includeClaudeSessions);
|
|
8876
|
+
const { records } = await loadAndPriceRecords(cwd, globalOpts.includeClaudeSessions);
|
|
8142
8877
|
if (records.length === 0) {
|
|
8143
8878
|
if (globalOpts.json) {
|
|
8144
8879
|
console.log(JSON.stringify([]));
|
|
@@ -8147,7 +8882,7 @@ function registerSessionsCommand(usage) {
|
|
|
8147
8882
|
}
|
|
8148
8883
|
return;
|
|
8149
8884
|
}
|
|
8150
|
-
const { aggregateBySession } = await import("./dist-
|
|
8885
|
+
const { aggregateBySession } = await import("./dist-WCSJHQPK.js");
|
|
8151
8886
|
const sessionData = aggregateBySession(records);
|
|
8152
8887
|
const limited = sessionData.slice(0, limit);
|
|
8153
8888
|
if (globalOpts.json) {
|
|
@@ -8189,7 +8924,7 @@ function reportSessionNotFound(id, sessionData, useJson) {
|
|
|
8189
8924
|
}
|
|
8190
8925
|
}
|
|
8191
8926
|
}
|
|
8192
|
-
function printSessionDetail(match) {
|
|
8927
|
+
function printSessionDetail(match, pricingData) {
|
|
8193
8928
|
logger.info(`Session: ${match.sessionId}`);
|
|
8194
8929
|
logger.info(`Started: ${match.firstTimestamp}`);
|
|
8195
8930
|
logger.info(`Ended: ${match.lastTimestamp}`);
|
|
@@ -8208,9 +8943,13 @@ function printSessionDetail(match) {
|
|
|
8208
8943
|
}
|
|
8209
8944
|
const hitRate = computeCacheHitRate(match.cacheReadTokens, match.tokens.inputTokens);
|
|
8210
8945
|
if (hitRate != null) {
|
|
8946
|
+
const savings = computeCacheSavings(match.cacheReadTokens, match.model, pricingData);
|
|
8211
8947
|
logger.info("");
|
|
8212
8948
|
logger.info("Cache Performance:");
|
|
8213
8949
|
logger.info(` Cache hit rate: ${formatPercent(hitRate)}`);
|
|
8950
|
+
if (savings != null) {
|
|
8951
|
+
logger.info(` Estimated savings: ${formatMicroUSD(savings)}`);
|
|
8952
|
+
}
|
|
8214
8953
|
}
|
|
8215
8954
|
logger.info("");
|
|
8216
8955
|
logger.info(`Cost: ${formatMicroUSD(match.costMicroUSD)}`);
|
|
@@ -8219,8 +8958,11 @@ function registerSessionCommand(usage) {
|
|
|
8219
8958
|
usage.command("session <id>").description("Show detailed token breakdown for a specific session").action(async (id, _opts, cmd) => {
|
|
8220
8959
|
const globalOpts = cmd.optsWithGlobals();
|
|
8221
8960
|
const cwd = process.cwd();
|
|
8222
|
-
const records = await loadAndPriceRecords(
|
|
8223
|
-
|
|
8961
|
+
const { records, pricingData } = await loadAndPriceRecords(
|
|
8962
|
+
cwd,
|
|
8963
|
+
globalOpts.includeClaudeSessions
|
|
8964
|
+
);
|
|
8965
|
+
const { aggregateBySession } = await import("./dist-WCSJHQPK.js");
|
|
8224
8966
|
const sessionData = aggregateBySession(records);
|
|
8225
8967
|
const match = sessionData.find((s) => s.sessionId === id);
|
|
8226
8968
|
if (!match) {
|
|
@@ -8230,21 +8972,23 @@ function registerSessionCommand(usage) {
|
|
|
8230
8972
|
}
|
|
8231
8973
|
if (globalOpts.json) {
|
|
8232
8974
|
const hitRate = computeCacheHitRate(match.cacheReadTokens, match.tokens.inputTokens);
|
|
8975
|
+
const savings = computeCacheSavings(match.cacheReadTokens, match.model, pricingData);
|
|
8233
8976
|
const enriched = {
|
|
8234
8977
|
...match,
|
|
8235
|
-
...hitRate != null ? { cacheHitRate: Math.round(hitRate * 1e3) / 1e3 } : {}
|
|
8978
|
+
...hitRate != null ? { cacheHitRate: Math.round(hitRate * 1e3) / 1e3 } : {},
|
|
8979
|
+
...savings != null ? { cacheSavingsMicroUSD: savings } : {}
|
|
8236
8980
|
};
|
|
8237
8981
|
console.log(JSON.stringify(enriched, null, 2));
|
|
8238
8982
|
return;
|
|
8239
8983
|
}
|
|
8240
|
-
printSessionDetail(match);
|
|
8984
|
+
printSessionDetail(match, pricingData);
|
|
8241
8985
|
});
|
|
8242
8986
|
}
|
|
8243
8987
|
function registerLatestCommand(usage) {
|
|
8244
8988
|
usage.command("latest").description("Show the most recently completed session cost summary").action(async (_opts, cmd) => {
|
|
8245
8989
|
const globalOpts = cmd.optsWithGlobals();
|
|
8246
8990
|
const cwd = process.cwd();
|
|
8247
|
-
const records = await loadAndPriceRecords(cwd, globalOpts.includeClaudeSessions);
|
|
8991
|
+
const { records } = await loadAndPriceRecords(cwd, globalOpts.includeClaudeSessions);
|
|
8248
8992
|
if (records.length === 0) {
|
|
8249
8993
|
if (globalOpts.json) {
|
|
8250
8994
|
console.log(JSON.stringify({ error: "No usage data found" }));
|
|
@@ -8253,7 +8997,7 @@ function registerLatestCommand(usage) {
|
|
|
8253
8997
|
}
|
|
8254
8998
|
return;
|
|
8255
8999
|
}
|
|
8256
|
-
const { aggregateBySession } = await import("./dist-
|
|
9000
|
+
const { aggregateBySession } = await import("./dist-WCSJHQPK.js");
|
|
8257
9001
|
const sessionData = aggregateBySession(records);
|
|
8258
9002
|
const latest = sessionData[0];
|
|
8259
9003
|
if (!latest) {
|
|
@@ -8279,7 +9023,7 @@ function registerLatestCommand(usage) {
|
|
|
8279
9023
|
});
|
|
8280
9024
|
}
|
|
8281
9025
|
function createUsageCommand() {
|
|
8282
|
-
const usage = new
|
|
9026
|
+
const usage = new Command81("usage").description("Token usage and cost tracking");
|
|
8283
9027
|
usage.option(
|
|
8284
9028
|
"--include-claude-sessions",
|
|
8285
9029
|
"Include Claude Code session data from ~/.claude/projects/"
|
|
@@ -8292,15 +9036,17 @@ function createUsageCommand() {
|
|
|
8292
9036
|
}
|
|
8293
9037
|
|
|
8294
9038
|
// src/commands/validate.ts
|
|
8295
|
-
|
|
8296
|
-
|
|
9039
|
+
init_dist();
|
|
9040
|
+
init_dist();
|
|
9041
|
+
import { Command as Command82 } from "commander";
|
|
9042
|
+
import * as path62 from "path";
|
|
8297
9043
|
async function runValidate(options) {
|
|
8298
9044
|
const configResult = resolveConfig(options.configPath);
|
|
8299
9045
|
if (!configResult.ok) {
|
|
8300
9046
|
return configResult;
|
|
8301
9047
|
}
|
|
8302
9048
|
const config = configResult.value;
|
|
8303
|
-
const cwd = options.cwd ?? (options.configPath ?
|
|
9049
|
+
const cwd = options.cwd ?? (options.configPath ? path62.dirname(path62.resolve(options.configPath)) : process.cwd());
|
|
8304
9050
|
const result = {
|
|
8305
9051
|
valid: true,
|
|
8306
9052
|
checks: {
|
|
@@ -8310,7 +9056,7 @@ async function runValidate(options) {
|
|
|
8310
9056
|
},
|
|
8311
9057
|
issues: []
|
|
8312
9058
|
};
|
|
8313
|
-
const agentsMapPath =
|
|
9059
|
+
const agentsMapPath = path62.resolve(cwd, config.agentsMapPath);
|
|
8314
9060
|
const agentsResult = await validateAgentsMap(agentsMapPath);
|
|
8315
9061
|
if (agentsResult.ok) {
|
|
8316
9062
|
result.checks.agentsMap = true;
|
|
@@ -8346,6 +9092,26 @@ async function runValidate(options) {
|
|
|
8346
9092
|
});
|
|
8347
9093
|
}
|
|
8348
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
|
+
}
|
|
8349
9115
|
return Ok(result);
|
|
8350
9116
|
}
|
|
8351
9117
|
function resolveValidateMode(globalOpts) {
|
|
@@ -8355,11 +9121,11 @@ function resolveValidateMode(globalOpts) {
|
|
|
8355
9121
|
return OutputMode.TEXT;
|
|
8356
9122
|
}
|
|
8357
9123
|
async function printCrossCheckWarnings(mode) {
|
|
8358
|
-
const { runCrossCheck: runCrossCheck2 } = await import("./validate-cross-check-
|
|
9124
|
+
const { runCrossCheck: runCrossCheck2 } = await import("./validate-cross-check-ZOWFA3DB.js");
|
|
8359
9125
|
const cwd = process.cwd();
|
|
8360
9126
|
const crossResult = await runCrossCheck2({
|
|
8361
|
-
specsDir:
|
|
8362
|
-
plansDir:
|
|
9127
|
+
specsDir: path62.join(cwd, "docs", "specs"),
|
|
9128
|
+
plansDir: path62.join(cwd, "docs", "plans"),
|
|
8363
9129
|
projectPath: cwd
|
|
8364
9130
|
});
|
|
8365
9131
|
if (!crossResult.ok || crossResult.value.warnings === 0) return;
|
|
@@ -8371,37 +9137,59 @@ async function printCrossCheckWarnings(mode) {
|
|
|
8371
9137
|
${crossResult.value.warnings} warnings`);
|
|
8372
9138
|
}
|
|
8373
9139
|
function createValidateCommand3() {
|
|
8374
|
-
const command = new
|
|
8375
|
-
|
|
8376
|
-
|
|
8377
|
-
|
|
8378
|
-
const result = await runValidate({
|
|
8379
|
-
configPath: globalOpts.config,
|
|
8380
|
-
json: globalOpts.json,
|
|
8381
|
-
verbose: globalOpts.verbose,
|
|
8382
|
-
quiet: globalOpts.quiet
|
|
8383
|
-
});
|
|
8384
|
-
if (!result.ok) {
|
|
8385
|
-
if (mode === OutputMode.JSON) console.log(JSON.stringify({ error: result.error.message }));
|
|
8386
|
-
else logger.error(result.error.message);
|
|
8387
|
-
process.exit(result.error.exitCode);
|
|
8388
|
-
}
|
|
8389
|
-
if (opts.crossCheck) await printCrossCheckWarnings(mode);
|
|
8390
|
-
const output = formatter.formatValidation({
|
|
8391
|
-
valid: result.value.valid,
|
|
8392
|
-
issues: result.value.issues
|
|
8393
|
-
});
|
|
8394
|
-
if (output) console.log(output);
|
|
8395
|
-
process.exit(result.value.valid ? ExitCode.SUCCESS : ExitCode.VALIDATION_FAILED);
|
|
8396
|
-
});
|
|
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()));
|
|
8397
9144
|
return command;
|
|
8398
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
|
+
}
|
|
8399
9186
|
|
|
8400
9187
|
// src/commands/_registry.ts
|
|
8401
9188
|
var commandCreators = [
|
|
8402
9189
|
createAddCommand,
|
|
8403
9190
|
createAdoptionCommand,
|
|
8404
9191
|
createAgentCommand,
|
|
9192
|
+
createAuditProtectedCommand,
|
|
8405
9193
|
createBlueprintCommand,
|
|
8406
9194
|
createCheckArchCommand,
|
|
8407
9195
|
createCheckDepsCommand,
|
|
@@ -8434,6 +9222,7 @@ var commandCreators = [
|
|
|
8434
9222
|
createPerfCommand,
|
|
8435
9223
|
createPersonaCommand,
|
|
8436
9224
|
createPredictCommand,
|
|
9225
|
+
createPublishAnalysesCommand,
|
|
8437
9226
|
createQueryCommand,
|
|
8438
9227
|
createRecommendCommand,
|
|
8439
9228
|
createScanCommand,
|
|
@@ -8444,6 +9233,7 @@ var commandCreators = [
|
|
|
8444
9233
|
createSkillCommand,
|
|
8445
9234
|
createSnapshotCommand,
|
|
8446
9235
|
createStateCommand,
|
|
9236
|
+
createSyncAnalysesCommand,
|
|
8447
9237
|
createTaintCommand,
|
|
8448
9238
|
createTelemetryCommand,
|
|
8449
9239
|
createTraceabilityCommand,
|
|
@@ -8456,7 +9246,7 @@ var commandCreators = [
|
|
|
8456
9246
|
|
|
8457
9247
|
// src/index.ts
|
|
8458
9248
|
function createProgram() {
|
|
8459
|
-
const program = new
|
|
9249
|
+
const program = new Command83();
|
|
8460
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");
|
|
8461
9251
|
for (const creator of commandCreators) {
|
|
8462
9252
|
program.addCommand(creator());
|