@harness-engineering/cli 1.24.3 → 1.25.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agents/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-TDLOFNNQ.js → chunk-BOQRTARD.js} +1 -1
- package/dist/{chunk-ZAMT24QN.js → chunk-CJXVNDZZ.js} +1537 -737
- 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/hooks/adoption-tracker.js +189 -0
- package/dist/hooks/block-no-verify.js +50 -0
- package/dist/hooks/cost-tracker.js +66 -0
- package/dist/hooks/pre-compact-state.js +115 -0
- package/dist/hooks/profiles.ts +48 -0
- package/dist/hooks/protect-config.js +75 -0
- package/dist/hooks/quality-gate.js +131 -0
- package/dist/hooks/sentinel-post.js +159 -0
- package/dist/hooks/sentinel-pre.js +244 -0
- package/dist/hooks/telemetry-reporter.js +248 -0
- 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,14 +3108,24 @@ 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
|
|
2866
|
-
|
|
2867
|
-
|
|
3114
|
+
const candidates = [
|
|
3115
|
+
// Dev layout: src/commands/hooks/ → ../../hooks/
|
|
3116
|
+
path20.resolve(__dirname2, "..", "..", "hooks"),
|
|
3117
|
+
// Bundled layout: dist/ → ./hooks/ (copied by copy-assets.mjs)
|
|
3118
|
+
path20.resolve(__dirname2, "hooks")
|
|
3119
|
+
];
|
|
3120
|
+
for (const candidate of candidates) {
|
|
3121
|
+
if (fs8.existsSync(candidate)) {
|
|
3122
|
+
return candidate;
|
|
3123
|
+
}
|
|
2868
3124
|
}
|
|
2869
|
-
throw new Error(
|
|
3125
|
+
throw new Error(
|
|
3126
|
+
`Cannot locate hook scripts directory. Searched:
|
|
3127
|
+
${candidates.map((c) => ` - ${c}`).join("\n")}`
|
|
3128
|
+
);
|
|
2870
3129
|
}
|
|
2871
3130
|
function buildSettingsHooks(profile) {
|
|
2872
3131
|
const activeHookNames = PROFILES[profile];
|
|
@@ -2891,12 +3150,12 @@ function mergeSettings(existing, hooksConfig) {
|
|
|
2891
3150
|
}
|
|
2892
3151
|
function initHooks(options) {
|
|
2893
3152
|
const { profile, projectDir } = options;
|
|
2894
|
-
const hooksDestDir =
|
|
2895
|
-
|
|
2896
|
-
if (
|
|
2897
|
-
for (const entry of
|
|
3153
|
+
const hooksDestDir = path20.join(projectDir, ".harness", "hooks");
|
|
3154
|
+
fs8.mkdirSync(hooksDestDir, { recursive: true });
|
|
3155
|
+
if (fs8.existsSync(hooksDestDir)) {
|
|
3156
|
+
for (const entry of fs8.readdirSync(hooksDestDir)) {
|
|
2898
3157
|
if (entry.endsWith(".js")) {
|
|
2899
|
-
|
|
3158
|
+
fs8.unlinkSync(path20.join(hooksDestDir, entry));
|
|
2900
3159
|
}
|
|
2901
3160
|
}
|
|
2902
3161
|
}
|
|
@@ -2905,22 +3164,22 @@ function initHooks(options) {
|
|
|
2905
3164
|
const activeNames = PROFILES[profile];
|
|
2906
3165
|
const activeScripts = HOOK_SCRIPTS.filter((h) => activeNames.includes(h.name));
|
|
2907
3166
|
for (const script of activeScripts) {
|
|
2908
|
-
const srcFile =
|
|
2909
|
-
const destFile =
|
|
2910
|
-
if (
|
|
2911
|
-
|
|
3167
|
+
const srcFile = path20.join(sourceDir, `${script.name}.js`);
|
|
3168
|
+
const destFile = path20.join(hooksDestDir, `${script.name}.js`);
|
|
3169
|
+
if (fs8.existsSync(srcFile)) {
|
|
3170
|
+
fs8.copyFileSync(srcFile, destFile);
|
|
2912
3171
|
copiedScripts.push(script.name);
|
|
2913
3172
|
}
|
|
2914
3173
|
}
|
|
2915
|
-
const profilePath =
|
|
2916
|
-
|
|
2917
|
-
const claudeDir =
|
|
2918
|
-
|
|
2919
|
-
const settingsPath =
|
|
3174
|
+
const profilePath = path20.join(hooksDestDir, "profile.json");
|
|
3175
|
+
fs8.writeFileSync(profilePath, JSON.stringify({ profile }, null, 2) + "\n");
|
|
3176
|
+
const claudeDir = path20.join(projectDir, ".claude");
|
|
3177
|
+
fs8.mkdirSync(claudeDir, { recursive: true });
|
|
3178
|
+
const settingsPath = path20.join(claudeDir, "settings.json");
|
|
2920
3179
|
let existing = {};
|
|
2921
|
-
if (
|
|
3180
|
+
if (fs8.existsSync(settingsPath)) {
|
|
2922
3181
|
try {
|
|
2923
|
-
existing = JSON.parse(
|
|
3182
|
+
existing = JSON.parse(fs8.readFileSync(settingsPath, "utf-8"));
|
|
2924
3183
|
} catch (e) {
|
|
2925
3184
|
throw new Error(
|
|
2926
3185
|
`Malformed .claude/settings.json \u2014 fix the JSON syntax before running hooks init. Parse error: ${e instanceof Error ? e.message : String(e)}`,
|
|
@@ -2930,11 +3189,11 @@ function initHooks(options) {
|
|
|
2930
3189
|
}
|
|
2931
3190
|
const hooksConfig = buildSettingsHooks(profile);
|
|
2932
3191
|
const merged = mergeSettings(existing, hooksConfig);
|
|
2933
|
-
|
|
3192
|
+
fs8.writeFileSync(settingsPath, JSON.stringify(merged, null, 2) + "\n");
|
|
2934
3193
|
return { copiedScripts, settingsPath, profilePath };
|
|
2935
3194
|
}
|
|
2936
3195
|
function createInitCommand2() {
|
|
2937
|
-
return new
|
|
3196
|
+
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
3197
|
const globalOpts = cmd.optsWithGlobals();
|
|
2939
3198
|
const profile = opts.profile;
|
|
2940
3199
|
if (!VALID_PROFILES.includes(profile)) {
|
|
@@ -2958,7 +3217,9 @@ function createInitCommand2() {
|
|
|
2958
3217
|
`Installed ${result.copiedScripts.length} hook scripts to .harness/hooks/`
|
|
2959
3218
|
);
|
|
2960
3219
|
logger.info(`Profile: ${profile}`);
|
|
2961
|
-
logger.info(
|
|
3220
|
+
logger.info(
|
|
3221
|
+
`Settings: ${path20.relative(projectDir, result.settingsPath).replaceAll("\\", "/")}`
|
|
3222
|
+
);
|
|
2962
3223
|
logger.dim("Run 'harness hooks list' to see installed hooks");
|
|
2963
3224
|
}
|
|
2964
3225
|
} catch (err) {
|
|
@@ -2970,19 +3231,19 @@ function createInitCommand2() {
|
|
|
2970
3231
|
}
|
|
2971
3232
|
|
|
2972
3233
|
// src/commands/hooks/list.ts
|
|
2973
|
-
import { Command as
|
|
2974
|
-
import * as
|
|
2975
|
-
import * as
|
|
3234
|
+
import { Command as Command27 } from "commander";
|
|
3235
|
+
import * as fs9 from "fs";
|
|
3236
|
+
import * as path21 from "path";
|
|
2976
3237
|
function listHooks(projectDir) {
|
|
2977
|
-
const hooksDir =
|
|
2978
|
-
const profilePath =
|
|
2979
|
-
if (!
|
|
3238
|
+
const hooksDir = path21.join(projectDir, ".harness", "hooks");
|
|
3239
|
+
const profilePath = path21.join(hooksDir, "profile.json");
|
|
3240
|
+
if (!fs9.existsSync(profilePath)) {
|
|
2980
3241
|
return { installed: false, profile: null, hooks: [] };
|
|
2981
3242
|
}
|
|
2982
3243
|
let profile = "standard";
|
|
2983
3244
|
let warning;
|
|
2984
3245
|
try {
|
|
2985
|
-
const data = JSON.parse(
|
|
3246
|
+
const data = JSON.parse(fs9.readFileSync(profilePath, "utf-8"));
|
|
2986
3247
|
if (data.profile && ["minimal", "standard", "strict"].includes(data.profile)) {
|
|
2987
3248
|
profile = data.profile;
|
|
2988
3249
|
}
|
|
@@ -2994,7 +3255,7 @@ function listHooks(projectDir) {
|
|
|
2994
3255
|
name: h.name,
|
|
2995
3256
|
event: h.event,
|
|
2996
3257
|
matcher: h.matcher,
|
|
2997
|
-
scriptPath:
|
|
3258
|
+
scriptPath: path21.join(".harness", "hooks", `${h.name}.js`)
|
|
2998
3259
|
}));
|
|
2999
3260
|
const result = { installed: true, profile, hooks };
|
|
3000
3261
|
if (warning) {
|
|
@@ -3003,7 +3264,7 @@ function listHooks(projectDir) {
|
|
|
3003
3264
|
return result;
|
|
3004
3265
|
}
|
|
3005
3266
|
function createListCommand() {
|
|
3006
|
-
return new
|
|
3267
|
+
return new Command27("list").description("Show installed hooks and active profile").action(async (_opts, cmd) => {
|
|
3007
3268
|
const globalOpts = cmd.optsWithGlobals();
|
|
3008
3269
|
const projectDir = process.cwd();
|
|
3009
3270
|
const result = listHooks(projectDir);
|
|
@@ -3024,28 +3285,28 @@ function createListCommand() {
|
|
|
3024
3285
|
}
|
|
3025
3286
|
|
|
3026
3287
|
// src/commands/hooks/remove.ts
|
|
3027
|
-
import { Command as
|
|
3028
|
-
import * as
|
|
3029
|
-
import * as
|
|
3288
|
+
import { Command as Command28 } from "commander";
|
|
3289
|
+
import * as fs10 from "fs";
|
|
3290
|
+
import * as path22 from "path";
|
|
3030
3291
|
function removeHooks(projectDir) {
|
|
3031
|
-
const hooksDir =
|
|
3032
|
-
const settingsPath =
|
|
3292
|
+
const hooksDir = path22.join(projectDir, ".harness", "hooks");
|
|
3293
|
+
const settingsPath = path22.join(projectDir, ".claude", "settings.json");
|
|
3033
3294
|
let removed = false;
|
|
3034
3295
|
let settingsCleaned = false;
|
|
3035
|
-
if (
|
|
3036
|
-
|
|
3296
|
+
if (fs10.existsSync(hooksDir)) {
|
|
3297
|
+
fs10.rmSync(hooksDir, { recursive: true, force: true });
|
|
3037
3298
|
removed = true;
|
|
3038
3299
|
}
|
|
3039
|
-
if (
|
|
3300
|
+
if (fs10.existsSync(settingsPath)) {
|
|
3040
3301
|
try {
|
|
3041
|
-
const settings = JSON.parse(
|
|
3302
|
+
const settings = JSON.parse(fs10.readFileSync(settingsPath, "utf-8"));
|
|
3042
3303
|
if (settings.hooks !== void 0) {
|
|
3043
3304
|
delete settings.hooks;
|
|
3044
3305
|
settingsCleaned = true;
|
|
3045
3306
|
if (Object.keys(settings).length === 0) {
|
|
3046
|
-
|
|
3307
|
+
fs10.unlinkSync(settingsPath);
|
|
3047
3308
|
} else {
|
|
3048
|
-
|
|
3309
|
+
fs10.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
3049
3310
|
}
|
|
3050
3311
|
}
|
|
3051
3312
|
} catch {
|
|
@@ -3054,7 +3315,7 @@ function removeHooks(projectDir) {
|
|
|
3054
3315
|
return { removed, hooksDir, settingsCleaned };
|
|
3055
3316
|
}
|
|
3056
3317
|
function createRemoveCommand() {
|
|
3057
|
-
return new
|
|
3318
|
+
return new Command28("remove").description("Remove harness-managed hooks from the current project").action(async (_opts, cmd) => {
|
|
3058
3319
|
const globalOpts = cmd.optsWithGlobals();
|
|
3059
3320
|
const projectDir = process.cwd();
|
|
3060
3321
|
const result = removeHooks(projectDir);
|
|
@@ -3076,21 +3337,21 @@ function createRemoveCommand() {
|
|
|
3076
3337
|
}
|
|
3077
3338
|
|
|
3078
3339
|
// src/commands/hooks/add.ts
|
|
3079
|
-
import { Command as
|
|
3080
|
-
import * as
|
|
3081
|
-
import * as
|
|
3340
|
+
import { Command as Command29 } from "commander";
|
|
3341
|
+
import * as fs11 from "fs";
|
|
3342
|
+
import * as path23 from "path";
|
|
3082
3343
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
3083
|
-
var __dirname3 =
|
|
3344
|
+
var __dirname3 = path23.dirname(fileURLToPath3(import.meta.url));
|
|
3084
3345
|
var ALIASES = {
|
|
3085
3346
|
sentinel: ["sentinel-pre", "sentinel-post"]
|
|
3086
3347
|
};
|
|
3087
3348
|
function hookSourceDir() {
|
|
3088
|
-
const d =
|
|
3089
|
-
if (
|
|
3349
|
+
const d = path23.resolve(__dirname3, "..", "..", "hooks");
|
|
3350
|
+
if (fs11.existsSync(d)) return d;
|
|
3090
3351
|
throw new Error(`Hook scripts not found: ${d}`);
|
|
3091
3352
|
}
|
|
3092
3353
|
function readJson(p) {
|
|
3093
|
-
return
|
|
3354
|
+
return fs11.existsSync(p) ? JSON.parse(fs11.readFileSync(p, "utf-8")) : {};
|
|
3094
3355
|
}
|
|
3095
3356
|
function registerHook(s, ev, matcher, name) {
|
|
3096
3357
|
if (!s.hooks[ev]) s.hooks[ev] = [];
|
|
@@ -3103,11 +3364,11 @@ function addHooks(hookName, projectDir) {
|
|
|
3103
3364
|
const names = ALIASES[hookName] ?? [hookName];
|
|
3104
3365
|
const result = { added: [], alreadyInstalled: [], notFound: [] };
|
|
3105
3366
|
const srcDir = hookSourceDir();
|
|
3106
|
-
const destDir =
|
|
3107
|
-
|
|
3108
|
-
const claudeDir =
|
|
3109
|
-
|
|
3110
|
-
const settingsPath =
|
|
3367
|
+
const destDir = path23.join(projectDir, ".harness", "hooks");
|
|
3368
|
+
fs11.mkdirSync(destDir, { recursive: true });
|
|
3369
|
+
const claudeDir = path23.join(projectDir, ".claude");
|
|
3370
|
+
fs11.mkdirSync(claudeDir, { recursive: true });
|
|
3371
|
+
const settingsPath = path23.join(claudeDir, "settings.json");
|
|
3111
3372
|
const settings = readJson(settingsPath);
|
|
3112
3373
|
if (!settings.hooks) settings.hooks = {};
|
|
3113
3374
|
for (const name of names) {
|
|
@@ -3116,24 +3377,24 @@ function addHooks(hookName, projectDir) {
|
|
|
3116
3377
|
result.notFound.push(name);
|
|
3117
3378
|
continue;
|
|
3118
3379
|
}
|
|
3119
|
-
const src =
|
|
3120
|
-
const dest =
|
|
3121
|
-
if (
|
|
3380
|
+
const src = path23.join(srcDir, `${name}.js`);
|
|
3381
|
+
const dest = path23.join(destDir, `${name}.js`);
|
|
3382
|
+
if (fs11.existsSync(dest)) {
|
|
3122
3383
|
result.alreadyInstalled.push(name);
|
|
3123
|
-
} else if (!
|
|
3384
|
+
} else if (!fs11.existsSync(src)) {
|
|
3124
3385
|
result.notFound.push(name);
|
|
3125
3386
|
continue;
|
|
3126
3387
|
} else {
|
|
3127
|
-
|
|
3388
|
+
fs11.copyFileSync(src, dest);
|
|
3128
3389
|
result.added.push(name);
|
|
3129
3390
|
}
|
|
3130
3391
|
registerHook(settings, def.event, def.matcher, name);
|
|
3131
3392
|
}
|
|
3132
|
-
|
|
3393
|
+
fs11.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
3133
3394
|
return result;
|
|
3134
3395
|
}
|
|
3135
3396
|
function createAddCommand2() {
|
|
3136
|
-
return new
|
|
3397
|
+
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
3398
|
const projectDir = process.cwd();
|
|
3138
3399
|
try {
|
|
3139
3400
|
const res = addHooks(hookName, projectDir);
|
|
@@ -3158,7 +3419,7 @@ function createAddCommand2() {
|
|
|
3158
3419
|
|
|
3159
3420
|
// src/commands/hooks/index.ts
|
|
3160
3421
|
function createHooksCommand() {
|
|
3161
|
-
const command = new
|
|
3422
|
+
const command = new Command30("hooks").description("Manage Claude Code hook configurations");
|
|
3162
3423
|
command.addCommand(createInitCommand2());
|
|
3163
3424
|
command.addCommand(createListCommand());
|
|
3164
3425
|
command.addCommand(createRemoveCommand());
|
|
@@ -3167,10 +3428,10 @@ function createHooksCommand() {
|
|
|
3167
3428
|
}
|
|
3168
3429
|
|
|
3169
3430
|
// src/commands/impact-preview.ts
|
|
3170
|
-
import { Command as
|
|
3431
|
+
import { Command as Command31 } from "commander";
|
|
3171
3432
|
import { execSync as execSync4 } from "child_process";
|
|
3172
|
-
import * as
|
|
3173
|
-
import * as
|
|
3433
|
+
import * as path24 from "path";
|
|
3434
|
+
import * as fs12 from "fs";
|
|
3174
3435
|
function getStagedFiles(cwd) {
|
|
3175
3436
|
try {
|
|
3176
3437
|
const output = execSync4("git diff --cached --name-only", {
|
|
@@ -3184,7 +3445,7 @@ function getStagedFiles(cwd) {
|
|
|
3184
3445
|
}
|
|
3185
3446
|
function graphExists(projectPath) {
|
|
3186
3447
|
try {
|
|
3187
|
-
return
|
|
3448
|
+
return fs12.existsSync(path24.join(projectPath, ".harness", "graph", "graph.json"));
|
|
3188
3449
|
} catch {
|
|
3189
3450
|
return false;
|
|
3190
3451
|
}
|
|
@@ -3193,7 +3454,7 @@ function extractNodeName(id) {
|
|
|
3193
3454
|
const parts = id.split(":");
|
|
3194
3455
|
if (parts.length > 1) {
|
|
3195
3456
|
const fullPath = parts.slice(1).join(":");
|
|
3196
|
-
return
|
|
3457
|
+
return path24.basename(fullPath);
|
|
3197
3458
|
}
|
|
3198
3459
|
return id;
|
|
3199
3460
|
}
|
|
@@ -3348,7 +3609,7 @@ function formatImpactOutput(stagedFiles, acc, options) {
|
|
|
3348
3609
|
return options.detailed ? formatDetailed(stagedFiles.length, merged, acc.aggregateCounts) : formatCompact(stagedFiles.length, merged, acc.aggregateCounts);
|
|
3349
3610
|
}
|
|
3350
3611
|
async function runImpactPreview(options) {
|
|
3351
|
-
const projectPath =
|
|
3612
|
+
const projectPath = path24.resolve(options.path ?? process.cwd());
|
|
3352
3613
|
const stagedFiles = getStagedFiles(projectPath);
|
|
3353
3614
|
if (stagedFiles.length === 0) return "Impact Preview: no staged changes";
|
|
3354
3615
|
if (!graphExists(projectPath)) {
|
|
@@ -3366,7 +3627,7 @@ async function runImpactPreview(options) {
|
|
|
3366
3627
|
return formatImpactOutput(stagedFiles, acc, options);
|
|
3367
3628
|
}
|
|
3368
3629
|
function createImpactPreviewCommand() {
|
|
3369
|
-
const command = new
|
|
3630
|
+
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
3631
|
const output = await runImpactPreview({
|
|
3371
3632
|
detailed: opts.detailed,
|
|
3372
3633
|
perFile: opts.perFile,
|
|
@@ -3379,28 +3640,29 @@ function createImpactPreviewCommand() {
|
|
|
3379
3640
|
}
|
|
3380
3641
|
|
|
3381
3642
|
// src/commands/init.ts
|
|
3382
|
-
|
|
3643
|
+
init_dist();
|
|
3644
|
+
import { Command as Command33 } from "commander";
|
|
3383
3645
|
import chalk3 from "chalk";
|
|
3384
|
-
import * as
|
|
3385
|
-
import * as
|
|
3646
|
+
import * as fs15 from "fs";
|
|
3647
|
+
import * as path27 from "path";
|
|
3386
3648
|
|
|
3387
3649
|
// src/commands/setup-mcp.ts
|
|
3388
|
-
import { Command as
|
|
3389
|
-
import * as
|
|
3390
|
-
import * as
|
|
3650
|
+
import { Command as Command32 } from "commander";
|
|
3651
|
+
import * as fs14 from "fs";
|
|
3652
|
+
import * as path26 from "path";
|
|
3391
3653
|
import * as os2 from "os";
|
|
3392
3654
|
import chalk2 from "chalk";
|
|
3393
3655
|
import * as clack from "@clack/prompts";
|
|
3394
3656
|
|
|
3395
3657
|
// src/integrations/toml.ts
|
|
3396
|
-
import * as
|
|
3397
|
-
import * as
|
|
3658
|
+
import * as fs13 from "fs";
|
|
3659
|
+
import * as path25 from "path";
|
|
3398
3660
|
function writeTomlMcpEntry(filePath, name, entry) {
|
|
3399
|
-
const dir =
|
|
3400
|
-
if (!
|
|
3401
|
-
|
|
3661
|
+
const dir = path25.dirname(filePath);
|
|
3662
|
+
if (!fs13.existsSync(dir)) {
|
|
3663
|
+
fs13.mkdirSync(dir, { recursive: true });
|
|
3402
3664
|
}
|
|
3403
|
-
const existing =
|
|
3665
|
+
const existing = fs13.existsSync(filePath) ? fs13.readFileSync(filePath, "utf-8") : "";
|
|
3404
3666
|
const blockHeader = `[mcp_servers.${name}]`;
|
|
3405
3667
|
const newBlock = serializeTomlMcpBlock(name, entry);
|
|
3406
3668
|
let updated;
|
|
@@ -3411,8 +3673,8 @@ function writeTomlMcpEntry(filePath, name, entry) {
|
|
|
3411
3673
|
updated = existing + separator + newBlock;
|
|
3412
3674
|
}
|
|
3413
3675
|
const tmp = filePath + ".tmp";
|
|
3414
|
-
|
|
3415
|
-
|
|
3676
|
+
fs13.writeFileSync(tmp, updated);
|
|
3677
|
+
fs13.renameSync(tmp, filePath);
|
|
3416
3678
|
}
|
|
3417
3679
|
function serializeTomlMcpBlock(name, entry) {
|
|
3418
3680
|
const lines = [`[mcp_servers.${name}]`];
|
|
@@ -3470,6 +3732,7 @@ var CURSOR_CURATED_TOOLS = [
|
|
|
3470
3732
|
"get_impact",
|
|
3471
3733
|
"detect_entropy",
|
|
3472
3734
|
"run_security_scan",
|
|
3735
|
+
"get_security_trends",
|
|
3473
3736
|
"assess_project",
|
|
3474
3737
|
"manage_state",
|
|
3475
3738
|
"create_self_review",
|
|
@@ -3514,6 +3777,7 @@ var ALL_MCP_TOOLS = [
|
|
|
3514
3777
|
"ingest_source",
|
|
3515
3778
|
"generate_agent_definitions",
|
|
3516
3779
|
"run_security_scan",
|
|
3780
|
+
"get_security_trends",
|
|
3517
3781
|
"check_performance",
|
|
3518
3782
|
"get_perf_baselines",
|
|
3519
3783
|
"update_perf_baselines",
|
|
@@ -3540,7 +3804,8 @@ var ALL_MCP_TOOLS = [
|
|
|
3540
3804
|
"recommend_skills",
|
|
3541
3805
|
"compute_blast_radius",
|
|
3542
3806
|
"dispatch_skills",
|
|
3543
|
-
"compact"
|
|
3807
|
+
"compact",
|
|
3808
|
+
"detect_constraint_emergence"
|
|
3544
3809
|
];
|
|
3545
3810
|
async function runCursorToolPicker() {
|
|
3546
3811
|
try {
|
|
@@ -3568,20 +3833,20 @@ function writeCursorMcpEntryWithTools(configPath, tools) {
|
|
|
3568
3833
|
});
|
|
3569
3834
|
}
|
|
3570
3835
|
function readJsonFile(filePath) {
|
|
3571
|
-
if (!
|
|
3836
|
+
if (!fs14.existsSync(filePath)) return null;
|
|
3572
3837
|
try {
|
|
3573
|
-
return JSON.parse(
|
|
3838
|
+
return JSON.parse(fs14.readFileSync(filePath, "utf-8"));
|
|
3574
3839
|
} catch {
|
|
3575
|
-
|
|
3840
|
+
fs14.copyFileSync(filePath, filePath + ".bak");
|
|
3576
3841
|
return null;
|
|
3577
3842
|
}
|
|
3578
3843
|
}
|
|
3579
3844
|
function writeJsonFile(filePath, data) {
|
|
3580
|
-
const dir =
|
|
3581
|
-
if (!
|
|
3582
|
-
|
|
3845
|
+
const dir = path26.dirname(filePath);
|
|
3846
|
+
if (!fs14.existsSync(dir)) {
|
|
3847
|
+
fs14.mkdirSync(dir, { recursive: true });
|
|
3583
3848
|
}
|
|
3584
|
-
|
|
3849
|
+
fs14.writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n");
|
|
3585
3850
|
}
|
|
3586
3851
|
function configureMcpServer(configPath) {
|
|
3587
3852
|
const config = readJsonFile(configPath) ?? {};
|
|
@@ -3596,7 +3861,7 @@ function configureMcpServer(configPath) {
|
|
|
3596
3861
|
return true;
|
|
3597
3862
|
}
|
|
3598
3863
|
function addGeminiTrustedFolder(cwd) {
|
|
3599
|
-
const trustedPath =
|
|
3864
|
+
const trustedPath = path26.join(os2.homedir(), ".gemini", "trustedFolders.json");
|
|
3600
3865
|
const folders = readJsonFile(trustedPath) ?? {};
|
|
3601
3866
|
if (folders[cwd] === "TRUST_FOLDER") {
|
|
3602
3867
|
return false;
|
|
@@ -3610,7 +3875,7 @@ function setupMcp(cwd, client) {
|
|
|
3610
3875
|
const skipped = [];
|
|
3611
3876
|
let trustedFolder = false;
|
|
3612
3877
|
if (client === "all" || client === "claude") {
|
|
3613
|
-
const configPath =
|
|
3878
|
+
const configPath = path26.join(cwd, ".mcp.json");
|
|
3614
3879
|
if (configureMcpServer(configPath)) {
|
|
3615
3880
|
configured.push("Claude Code");
|
|
3616
3881
|
} else {
|
|
@@ -3618,7 +3883,7 @@ function setupMcp(cwd, client) {
|
|
|
3618
3883
|
}
|
|
3619
3884
|
}
|
|
3620
3885
|
if (client === "all" || client === "gemini") {
|
|
3621
|
-
const configPath =
|
|
3886
|
+
const configPath = path26.join(cwd, ".gemini", "settings.json");
|
|
3622
3887
|
if (configureMcpServer(configPath)) {
|
|
3623
3888
|
configured.push("Gemini CLI");
|
|
3624
3889
|
} else {
|
|
@@ -3627,10 +3892,10 @@ function setupMcp(cwd, client) {
|
|
|
3627
3892
|
trustedFolder = addGeminiTrustedFolder(cwd);
|
|
3628
3893
|
}
|
|
3629
3894
|
if (client === "all" || client === "codex") {
|
|
3630
|
-
const configPath =
|
|
3895
|
+
const configPath = path26.join(cwd, ".codex", "config.toml");
|
|
3631
3896
|
const alreadyConfigured = (() => {
|
|
3632
|
-
if (!
|
|
3633
|
-
const content =
|
|
3897
|
+
if (!fs14.existsSync(configPath)) return false;
|
|
3898
|
+
const content = fs14.readFileSync(configPath, "utf-8");
|
|
3634
3899
|
return content.includes("[mcp_servers.harness]");
|
|
3635
3900
|
})();
|
|
3636
3901
|
if (alreadyConfigured) {
|
|
@@ -3645,7 +3910,7 @@ function setupMcp(cwd, client) {
|
|
|
3645
3910
|
}
|
|
3646
3911
|
}
|
|
3647
3912
|
if (client === "all" || client === "cursor") {
|
|
3648
|
-
const configPath =
|
|
3913
|
+
const configPath = path26.join(cwd, ".cursor", "mcp.json");
|
|
3649
3914
|
const existing = readJsonFile(configPath);
|
|
3650
3915
|
if (existing?.mcpServers?.["harness"]) {
|
|
3651
3916
|
skipped.push("Cursor");
|
|
@@ -3659,7 +3924,7 @@ function setupMcp(cwd, client) {
|
|
|
3659
3924
|
async function resolveCursorWithPicker(cwd, pick) {
|
|
3660
3925
|
const configured = [];
|
|
3661
3926
|
const skipped = [];
|
|
3662
|
-
const cursorConfigPath =
|
|
3927
|
+
const cursorConfigPath = path26.join(cwd, ".cursor", "mcp.json");
|
|
3663
3928
|
const existing = readJsonFile(cursorConfigPath);
|
|
3664
3929
|
if (existing?.mcpServers?.["harness"] && !pick) {
|
|
3665
3930
|
skipped.push("Cursor");
|
|
@@ -3703,7 +3968,7 @@ function printMcpResult(configured, skipped, trustedFolder) {
|
|
|
3703
3968
|
console.log("");
|
|
3704
3969
|
}
|
|
3705
3970
|
function createSetupMcpCommand() {
|
|
3706
|
-
return new
|
|
3971
|
+
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
3972
|
const globalOpts = cmd.optsWithGlobals();
|
|
3708
3973
|
const cwd = process.cwd();
|
|
3709
3974
|
let configured;
|
|
@@ -3730,12 +3995,12 @@ function loadEngineAndTemplates(_options) {
|
|
|
3730
3995
|
}
|
|
3731
3996
|
function resolveInitDefaults(options) {
|
|
3732
3997
|
const cwd = options.cwd ?? process.cwd();
|
|
3733
|
-
return { cwd, name: options.name ??
|
|
3998
|
+
return { cwd, name: options.name ?? path27.basename(cwd), force: options.force ?? false };
|
|
3734
3999
|
}
|
|
3735
4000
|
async function runInit(options) {
|
|
3736
4001
|
const { cwd, name, force } = resolveInitDefaults(options);
|
|
3737
|
-
const configPath =
|
|
3738
|
-
if (!force &&
|
|
4002
|
+
const configPath = path27.join(cwd, "harness.config.json");
|
|
4003
|
+
if (!force && fs15.existsSync(configPath)) {
|
|
3739
4004
|
return Err(
|
|
3740
4005
|
new CLIError("Project already initialized. Use --force to overwrite.", ExitCode.ERROR)
|
|
3741
4006
|
);
|
|
@@ -3843,11 +4108,11 @@ async function runInitAction2(opts) {
|
|
|
3843
4108
|
process.exit(ExitCode.SUCCESS);
|
|
3844
4109
|
}
|
|
3845
4110
|
function createInitCommand3() {
|
|
3846
|
-
const command = new
|
|
4111
|
+
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
4112
|
const globalOpts = cmd.optsWithGlobals();
|
|
3848
4113
|
await runInitAction2({
|
|
3849
4114
|
name: opts.name,
|
|
3850
|
-
level: opts.level,
|
|
4115
|
+
level: opts.template ?? opts.level,
|
|
3851
4116
|
framework: opts.framework,
|
|
3852
4117
|
language: opts.language,
|
|
3853
4118
|
force: opts.force,
|
|
@@ -3858,16 +4123,16 @@ function createInitCommand3() {
|
|
|
3858
4123
|
}
|
|
3859
4124
|
|
|
3860
4125
|
// src/commands/install.ts
|
|
3861
|
-
import * as
|
|
4126
|
+
import * as fs20 from "fs";
|
|
3862
4127
|
import * as os5 from "os";
|
|
3863
|
-
import * as
|
|
4128
|
+
import * as path32 from "path";
|
|
3864
4129
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
3865
|
-
import { Command as
|
|
4130
|
+
import { Command as Command34 } from "commander";
|
|
3866
4131
|
import { parse as yamlParse } from "yaml";
|
|
3867
4132
|
|
|
3868
4133
|
// src/registry/npm-client.ts
|
|
3869
|
-
import * as
|
|
3870
|
-
import * as
|
|
4134
|
+
import * as fs16 from "fs";
|
|
4135
|
+
import * as path28 from "path";
|
|
3871
4136
|
import * as os3 from "os";
|
|
3872
4137
|
var NPM_REGISTRY = "https://registry.npmjs.org";
|
|
3873
4138
|
var FETCH_TIMEOUT_MS = 3e4;
|
|
@@ -3890,10 +4155,10 @@ function extractSkillName(packageName) {
|
|
|
3890
4155
|
function readNpmrcToken(registryUrl) {
|
|
3891
4156
|
const { hostname, pathname } = new URL(registryUrl);
|
|
3892
4157
|
const registryPath = `//${hostname}${pathname.replace(/\/$/, "")}/:_authToken=`;
|
|
3893
|
-
const candidates = [
|
|
4158
|
+
const candidates = [path28.join(process.cwd(), ".npmrc"), path28.join(os3.homedir(), ".npmrc")];
|
|
3894
4159
|
for (const npmrcPath of candidates) {
|
|
3895
4160
|
try {
|
|
3896
|
-
const content =
|
|
4161
|
+
const content = fs16.readFileSync(npmrcPath, "utf-8");
|
|
3897
4162
|
for (const line of content.split("\n")) {
|
|
3898
4163
|
const trimmed = line.trim();
|
|
3899
4164
|
if (trimmed.startsWith(registryPath)) {
|
|
@@ -3988,19 +4253,19 @@ async function searchNpmRegistry(query, registryUrl) {
|
|
|
3988
4253
|
}
|
|
3989
4254
|
|
|
3990
4255
|
// src/registry/tarball.ts
|
|
3991
|
-
import * as
|
|
3992
|
-
import * as
|
|
4256
|
+
import * as fs17 from "fs";
|
|
4257
|
+
import * as path29 from "path";
|
|
3993
4258
|
import * as os4 from "os";
|
|
3994
4259
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
3995
4260
|
function extractTarball(tarballBuffer) {
|
|
3996
|
-
const tmpDir =
|
|
3997
|
-
const tarballPath =
|
|
4261
|
+
const tmpDir = fs17.mkdtempSync(path29.join(os4.tmpdir(), "harness-skill-install-"));
|
|
4262
|
+
const tarballPath = path29.join(tmpDir, "package.tgz");
|
|
3998
4263
|
try {
|
|
3999
|
-
|
|
4264
|
+
fs17.writeFileSync(tarballPath, tarballBuffer);
|
|
4000
4265
|
execFileSync2("tar", ["-xzf", tarballPath, "-C", tmpDir], {
|
|
4001
4266
|
timeout: 3e4
|
|
4002
4267
|
});
|
|
4003
|
-
|
|
4268
|
+
fs17.unlinkSync(tarballPath);
|
|
4004
4269
|
} catch (err) {
|
|
4005
4270
|
cleanupTempDir(tmpDir);
|
|
4006
4271
|
throw new Error(
|
|
@@ -4011,37 +4276,37 @@ function extractTarball(tarballBuffer) {
|
|
|
4011
4276
|
return tmpDir;
|
|
4012
4277
|
}
|
|
4013
4278
|
function placeSkillContent(extractedPkgDir, communityBaseDir, skillName, platforms) {
|
|
4014
|
-
const files =
|
|
4279
|
+
const files = fs17.readdirSync(extractedPkgDir);
|
|
4015
4280
|
for (const platform of platforms) {
|
|
4016
|
-
const targetDir =
|
|
4017
|
-
if (
|
|
4018
|
-
|
|
4281
|
+
const targetDir = path29.join(communityBaseDir, platform, skillName);
|
|
4282
|
+
if (fs17.existsSync(targetDir)) {
|
|
4283
|
+
fs17.rmSync(targetDir, { recursive: true, force: true });
|
|
4019
4284
|
}
|
|
4020
|
-
|
|
4285
|
+
fs17.mkdirSync(targetDir, { recursive: true });
|
|
4021
4286
|
for (const file of files) {
|
|
4022
4287
|
if (file === "package.json" || file === "node_modules") continue;
|
|
4023
|
-
const srcPath =
|
|
4024
|
-
const destPath =
|
|
4025
|
-
const stat =
|
|
4288
|
+
const srcPath = path29.join(extractedPkgDir, file);
|
|
4289
|
+
const destPath = path29.join(targetDir, file);
|
|
4290
|
+
const stat = fs17.statSync(srcPath);
|
|
4026
4291
|
if (stat.isDirectory()) {
|
|
4027
|
-
|
|
4292
|
+
fs17.cpSync(srcPath, destPath, { recursive: true });
|
|
4028
4293
|
} else {
|
|
4029
|
-
|
|
4294
|
+
fs17.copyFileSync(srcPath, destPath);
|
|
4030
4295
|
}
|
|
4031
4296
|
}
|
|
4032
4297
|
}
|
|
4033
4298
|
}
|
|
4034
4299
|
function removeSkillContent(communityBaseDir, skillName, platforms) {
|
|
4035
4300
|
for (const platform of platforms) {
|
|
4036
|
-
const targetDir =
|
|
4037
|
-
if (
|
|
4038
|
-
|
|
4301
|
+
const targetDir = path29.join(communityBaseDir, platform, skillName);
|
|
4302
|
+
if (fs17.existsSync(targetDir)) {
|
|
4303
|
+
fs17.rmSync(targetDir, { recursive: true, force: true });
|
|
4039
4304
|
}
|
|
4040
4305
|
}
|
|
4041
4306
|
}
|
|
4042
4307
|
function cleanupTempDir(dirPath) {
|
|
4043
4308
|
try {
|
|
4044
|
-
|
|
4309
|
+
fs17.rmSync(dirPath, { recursive: true, force: true });
|
|
4045
4310
|
} catch {
|
|
4046
4311
|
}
|
|
4047
4312
|
}
|
|
@@ -4083,8 +4348,8 @@ function findDependentsOf(lockfile, targetPackageName) {
|
|
|
4083
4348
|
}
|
|
4084
4349
|
|
|
4085
4350
|
// src/registry/lockfile.ts
|
|
4086
|
-
import * as
|
|
4087
|
-
import * as
|
|
4351
|
+
import * as fs18 from "fs";
|
|
4352
|
+
import * as path30 from "path";
|
|
4088
4353
|
function createEmptyLockfile() {
|
|
4089
4354
|
return { version: 1, skills: {} };
|
|
4090
4355
|
}
|
|
@@ -4104,10 +4369,10 @@ function sortedStringify(obj) {
|
|
|
4104
4369
|
);
|
|
4105
4370
|
}
|
|
4106
4371
|
function readLockfile2(filePath) {
|
|
4107
|
-
if (!
|
|
4372
|
+
if (!fs18.existsSync(filePath)) {
|
|
4108
4373
|
return createEmptyLockfile();
|
|
4109
4374
|
}
|
|
4110
|
-
const raw =
|
|
4375
|
+
const raw = fs18.readFileSync(filePath, "utf-8");
|
|
4111
4376
|
let parsed;
|
|
4112
4377
|
try {
|
|
4113
4378
|
parsed = JSON.parse(raw);
|
|
@@ -4124,9 +4389,9 @@ function readLockfile2(filePath) {
|
|
|
4124
4389
|
return parsed;
|
|
4125
4390
|
}
|
|
4126
4391
|
function writeLockfile2(filePath, lockfile) {
|
|
4127
|
-
const dir =
|
|
4128
|
-
|
|
4129
|
-
|
|
4392
|
+
const dir = path30.dirname(filePath);
|
|
4393
|
+
fs18.mkdirSync(dir, { recursive: true });
|
|
4394
|
+
fs18.writeFileSync(filePath, sortedStringify(lockfile) + "\n", "utf-8");
|
|
4130
4395
|
}
|
|
4131
4396
|
function updateLockfileEntry(lockfile, name, entry) {
|
|
4132
4397
|
return {
|
|
@@ -4149,17 +4414,17 @@ function removeLockfileEntry(lockfile, name) {
|
|
|
4149
4414
|
}
|
|
4150
4415
|
|
|
4151
4416
|
// src/registry/bundled-skills.ts
|
|
4152
|
-
import * as
|
|
4153
|
-
import * as
|
|
4417
|
+
import * as fs19 from "fs";
|
|
4418
|
+
import * as path31 from "path";
|
|
4154
4419
|
function getBundledSkillNames(bundledSkillsDir) {
|
|
4155
|
-
if (!
|
|
4420
|
+
if (!fs19.existsSync(bundledSkillsDir)) {
|
|
4156
4421
|
return /* @__PURE__ */ new Set();
|
|
4157
4422
|
}
|
|
4158
|
-
const entries =
|
|
4423
|
+
const entries = fs19.readdirSync(bundledSkillsDir);
|
|
4159
4424
|
const names = /* @__PURE__ */ new Set();
|
|
4160
4425
|
for (const entry of entries) {
|
|
4161
4426
|
try {
|
|
4162
|
-
const stat =
|
|
4427
|
+
const stat = fs19.statSync(path31.join(bundledSkillsDir, String(entry)));
|
|
4163
4428
|
if (stat.isDirectory()) {
|
|
4164
4429
|
names.add(String(entry));
|
|
4165
4430
|
}
|
|
@@ -4186,12 +4451,12 @@ function validateSkillYaml(parsed) {
|
|
|
4186
4451
|
function resolveCommunityBase(global) {
|
|
4187
4452
|
if (global) {
|
|
4188
4453
|
const communityBase2 = resolveGlobalCommunityBaseDir();
|
|
4189
|
-
return { communityBase: communityBase2, lockfilePath:
|
|
4454
|
+
return { communityBase: communityBase2, lockfilePath: path32.join(communityBase2, "skills-lock.json") };
|
|
4190
4455
|
}
|
|
4191
4456
|
const globalDir = resolveGlobalSkillsDir();
|
|
4192
|
-
const skillsDir =
|
|
4193
|
-
const communityBase =
|
|
4194
|
-
return { communityBase, lockfilePath:
|
|
4457
|
+
const skillsDir = path32.dirname(globalDir);
|
|
4458
|
+
const communityBase = path32.join(skillsDir, "community");
|
|
4459
|
+
return { communityBase, lockfilePath: path32.join(communityBase, "skills-lock.json") };
|
|
4195
4460
|
}
|
|
4196
4461
|
function parseGitHubRef(from) {
|
|
4197
4462
|
const ghPrefix = from.match(/^github:([^/]+)\/([^#]+?)(?:#(.+))?$/);
|
|
@@ -4207,7 +4472,7 @@ function parseGitHubRef(from) {
|
|
|
4207
4472
|
return null;
|
|
4208
4473
|
}
|
|
4209
4474
|
function cloneGitHubRepo(owner, repo, ref) {
|
|
4210
|
-
const tmpDir =
|
|
4475
|
+
const tmpDir = fs20.mkdtempSync(path32.join(os5.tmpdir(), "harness-gh-install-"));
|
|
4211
4476
|
const url = `https://github.com/${owner}/${repo}.git`;
|
|
4212
4477
|
try {
|
|
4213
4478
|
const cloneArgs = ["clone", "--depth", "1"];
|
|
@@ -4228,43 +4493,43 @@ function discoverSkillDirs(rootDir) {
|
|
|
4228
4493
|
const skillDirs = [];
|
|
4229
4494
|
function scan(dir, depth) {
|
|
4230
4495
|
if (depth > 3) return;
|
|
4231
|
-
if (!
|
|
4232
|
-
if (
|
|
4496
|
+
if (!fs20.existsSync(dir)) return;
|
|
4497
|
+
if (fs20.existsSync(path32.join(dir, "skill.yaml"))) {
|
|
4233
4498
|
skillDirs.push(dir);
|
|
4234
4499
|
return;
|
|
4235
4500
|
}
|
|
4236
|
-
const entries =
|
|
4501
|
+
const entries = fs20.readdirSync(dir, { withFileTypes: true });
|
|
4237
4502
|
for (const entry of entries) {
|
|
4238
4503
|
if (!entry.isDirectory()) continue;
|
|
4239
4504
|
if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
|
|
4240
|
-
scan(
|
|
4505
|
+
scan(path32.join(dir, entry.name), depth + 1);
|
|
4241
4506
|
}
|
|
4242
4507
|
}
|
|
4243
4508
|
scan(rootDir, 0);
|
|
4244
4509
|
return skillDirs;
|
|
4245
4510
|
}
|
|
4246
4511
|
function resolveLocalPkgDir(fromPath) {
|
|
4247
|
-
const resolvedPath =
|
|
4248
|
-
if (!
|
|
4512
|
+
const resolvedPath = path32.resolve(fromPath);
|
|
4513
|
+
if (!fs20.existsSync(resolvedPath)) {
|
|
4249
4514
|
throw new Error(`--from path does not exist: ${resolvedPath}`);
|
|
4250
4515
|
}
|
|
4251
|
-
const stat =
|
|
4516
|
+
const stat = fs20.statSync(resolvedPath);
|
|
4252
4517
|
if (stat.isDirectory()) {
|
|
4253
4518
|
return { pkgDir: resolvedPath, extractDir: null };
|
|
4254
4519
|
}
|
|
4255
4520
|
if (resolvedPath.endsWith(".tgz") || resolvedPath.endsWith(".tar.gz")) {
|
|
4256
|
-
const tarballBuffer =
|
|
4521
|
+
const tarballBuffer = fs20.readFileSync(resolvedPath);
|
|
4257
4522
|
const extractDir = extractTarball(tarballBuffer);
|
|
4258
|
-
return { pkgDir:
|
|
4523
|
+
return { pkgDir: path32.join(extractDir, "package"), extractDir };
|
|
4259
4524
|
}
|
|
4260
4525
|
throw new Error(`--from path must be a directory or .tgz file. Got: ${resolvedPath}`);
|
|
4261
4526
|
}
|
|
4262
4527
|
function installSkillDir(pkgDir, resolvedPath, options) {
|
|
4263
|
-
const skillYamlPath =
|
|
4264
|
-
if (!
|
|
4528
|
+
const skillYamlPath = path32.join(pkgDir, "skill.yaml");
|
|
4529
|
+
if (!fs20.existsSync(skillYamlPath)) {
|
|
4265
4530
|
throw new Error(`No skill.yaml found at ${skillYamlPath}`);
|
|
4266
4531
|
}
|
|
4267
|
-
const skillYaml = validateSkillYaml(yamlParse(
|
|
4532
|
+
const skillYaml = validateSkillYaml(yamlParse(fs20.readFileSync(skillYamlPath, "utf-8")));
|
|
4268
4533
|
const shortName = skillYaml.name;
|
|
4269
4534
|
const { communityBase, lockfilePath } = resolveCommunityBase(options.global ?? false);
|
|
4270
4535
|
if (!options.global) {
|
|
@@ -4292,7 +4557,7 @@ function installSkillDir(pkgDir, resolvedPath, options) {
|
|
|
4292
4557
|
async function runLocalInstall(fromPath, options) {
|
|
4293
4558
|
const { pkgDir, extractDir } = resolveLocalPkgDir(fromPath);
|
|
4294
4559
|
try {
|
|
4295
|
-
return installSkillDir(pkgDir,
|
|
4560
|
+
return installSkillDir(pkgDir, path32.resolve(fromPath), options);
|
|
4296
4561
|
} finally {
|
|
4297
4562
|
if (extractDir) cleanupTempDir(extractDir);
|
|
4298
4563
|
}
|
|
@@ -4335,8 +4600,8 @@ async function runInstall(skillName, options) {
|
|
|
4335
4600
|
version: installed.map((r) => r.version).join(", ")
|
|
4336
4601
|
};
|
|
4337
4602
|
}
|
|
4338
|
-
const resolvedFrom =
|
|
4339
|
-
if (
|
|
4603
|
+
const resolvedFrom = path32.resolve(options.from);
|
|
4604
|
+
if (fs20.existsSync(resolvedFrom) && fs20.statSync(resolvedFrom).isDirectory() && !fs20.existsSync(path32.join(resolvedFrom, "skill.yaml"))) {
|
|
4340
4605
|
const results = await runBulkInstall(resolvedFrom, options);
|
|
4341
4606
|
const installed = results.filter((r) => r.installed);
|
|
4342
4607
|
return {
|
|
@@ -4377,12 +4642,12 @@ async function runInstall(skillName, options) {
|
|
|
4377
4642
|
const extractDir = extractTarball(tarballBuffer);
|
|
4378
4643
|
let skillYaml;
|
|
4379
4644
|
try {
|
|
4380
|
-
const extractedPkgDir =
|
|
4381
|
-
const skillYamlPath =
|
|
4382
|
-
if (!
|
|
4645
|
+
const extractedPkgDir = path32.join(extractDir, "package");
|
|
4646
|
+
const skillYamlPath = path32.join(extractedPkgDir, "skill.yaml");
|
|
4647
|
+
if (!fs20.existsSync(skillYamlPath)) {
|
|
4383
4648
|
throw new Error(`contains invalid skill.yaml: file not found in package`);
|
|
4384
4649
|
}
|
|
4385
|
-
const rawYaml =
|
|
4650
|
+
const rawYaml = fs20.readFileSync(skillYamlPath, "utf-8");
|
|
4386
4651
|
const parsed = yamlParse(rawYaml);
|
|
4387
4652
|
skillYaml = validateSkillYaml(parsed);
|
|
4388
4653
|
placeSkillContent(extractedPkgDir, communityBase, shortName, skillYaml.platforms);
|
|
@@ -4422,7 +4687,7 @@ async function runInstall(skillName, options) {
|
|
|
4422
4687
|
return result;
|
|
4423
4688
|
}
|
|
4424
4689
|
function createInstallCommand() {
|
|
4425
|
-
const cmd = new
|
|
4690
|
+
const cmd = new Command34("install");
|
|
4426
4691
|
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
4692
|
"--from <source>",
|
|
4428
4693
|
"Install from local path, directory, or GitHub (github:owner/repo, https://github.com/owner/repo)"
|
|
@@ -4455,15 +4720,16 @@ function createInstallCommand() {
|
|
|
4455
4720
|
}
|
|
4456
4721
|
|
|
4457
4722
|
// src/commands/install-constraints.ts
|
|
4458
|
-
|
|
4459
|
-
import * as
|
|
4460
|
-
import
|
|
4723
|
+
init_dist();
|
|
4724
|
+
import * as fs21 from "fs/promises";
|
|
4725
|
+
import * as path33 from "path";
|
|
4726
|
+
import { Command as Command35 } from "commander";
|
|
4461
4727
|
import semver3 from "semver";
|
|
4462
4728
|
async function runInstallConstraints(options) {
|
|
4463
4729
|
const { source, configPath, lockfilePath } = options;
|
|
4464
4730
|
let rawBundle;
|
|
4465
4731
|
try {
|
|
4466
|
-
rawBundle = await
|
|
4732
|
+
rawBundle = await fs21.readFile(source, "utf-8");
|
|
4467
4733
|
} catch (err) {
|
|
4468
4734
|
if (isNodeError(err) && err.code === "ENOENT") {
|
|
4469
4735
|
return { ok: false, error: `Bundle file not found: ${source}` };
|
|
@@ -4506,7 +4772,7 @@ async function runInstallConstraints(options) {
|
|
|
4506
4772
|
}
|
|
4507
4773
|
let localConfig;
|
|
4508
4774
|
try {
|
|
4509
|
-
const raw = await
|
|
4775
|
+
const raw = await fs21.readFile(configPath, "utf-8");
|
|
4510
4776
|
localConfig = JSON.parse(raw);
|
|
4511
4777
|
} catch (err) {
|
|
4512
4778
|
return {
|
|
@@ -4654,7 +4920,7 @@ function isNodeError(err) {
|
|
|
4654
4920
|
return err instanceof Error && "code" in err;
|
|
4655
4921
|
}
|
|
4656
4922
|
function resolveConfigPath(opts) {
|
|
4657
|
-
if (opts.config) return
|
|
4923
|
+
if (opts.config) return path33.resolve(opts.config);
|
|
4658
4924
|
const found = findConfigFile();
|
|
4659
4925
|
if (!found.ok) {
|
|
4660
4926
|
logger.error(found.error.message);
|
|
@@ -4689,9 +4955,9 @@ function logInstallResult(val, opts) {
|
|
|
4689
4955
|
}
|
|
4690
4956
|
async function handleInstallConstraints(source, opts) {
|
|
4691
4957
|
const configPath = resolveConfigPath(opts);
|
|
4692
|
-
const projectRoot =
|
|
4693
|
-
const lockfilePath =
|
|
4694
|
-
const resolvedSource =
|
|
4958
|
+
const projectRoot = path33.dirname(configPath);
|
|
4959
|
+
const lockfilePath = path33.join(projectRoot, ".harness", "constraints.lock.json");
|
|
4960
|
+
const resolvedSource = path33.resolve(source);
|
|
4695
4961
|
if (opts.forceLocal && opts.forcePackage) {
|
|
4696
4962
|
logger.error("Cannot use both --force-local and --force-package.");
|
|
4697
4963
|
process.exit(1);
|
|
@@ -4711,18 +4977,19 @@ async function handleInstallConstraints(source, opts) {
|
|
|
4711
4977
|
logInstallResult(result.value, opts);
|
|
4712
4978
|
}
|
|
4713
4979
|
function createInstallConstraintsCommand() {
|
|
4714
|
-
const cmd = new
|
|
4980
|
+
const cmd = new Command35("install-constraints");
|
|
4715
4981
|
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
4982
|
return cmd;
|
|
4717
4983
|
}
|
|
4718
4984
|
|
|
4719
4985
|
// src/commands/integrations/index.ts
|
|
4720
|
-
import { Command as
|
|
4986
|
+
import { Command as Command40 } from "commander";
|
|
4721
4987
|
|
|
4722
4988
|
// src/commands/integrations/add.ts
|
|
4723
|
-
|
|
4724
|
-
import
|
|
4725
|
-
import * as
|
|
4989
|
+
init_dist();
|
|
4990
|
+
import { Command as Command36 } from "commander";
|
|
4991
|
+
import * as fs22 from "fs";
|
|
4992
|
+
import * as path34 from "path";
|
|
4726
4993
|
import chalk4 from "chalk";
|
|
4727
4994
|
function buildMcpEntry(def) {
|
|
4728
4995
|
const entry = { command: def.mcpConfig.command };
|
|
@@ -4731,14 +4998,14 @@ function buildMcpEntry(def) {
|
|
|
4731
4998
|
return entry;
|
|
4732
4999
|
}
|
|
4733
5000
|
function writeMcpEntries(cwd, defName, mcpEntry) {
|
|
4734
|
-
writeMcpEntry(
|
|
4735
|
-
const geminiDir =
|
|
4736
|
-
if (
|
|
4737
|
-
writeMcpEntry(
|
|
5001
|
+
writeMcpEntry(path34.join(cwd, ".mcp.json"), defName, mcpEntry);
|
|
5002
|
+
const geminiDir = path34.join(cwd, ".gemini");
|
|
5003
|
+
if (fs22.existsSync(geminiDir)) {
|
|
5004
|
+
writeMcpEntry(path34.join(geminiDir, "settings.json"), defName, mcpEntry);
|
|
4738
5005
|
}
|
|
4739
5006
|
}
|
|
4740
5007
|
function updateIntegrationsConfig(cwd, defName) {
|
|
4741
|
-
const configPath =
|
|
5008
|
+
const configPath = path34.join(cwd, "harness.config.json");
|
|
4742
5009
|
const integConfig = readIntegrationsConfig(configPath);
|
|
4743
5010
|
if (!integConfig.enabled.includes(defName)) integConfig.enabled.push(defName);
|
|
4744
5011
|
integConfig.dismissed = integConfig.dismissed.filter((d) => d !== defName);
|
|
@@ -4784,7 +5051,7 @@ function printAddSuccess(value) {
|
|
|
4784
5051
|
}
|
|
4785
5052
|
}
|
|
4786
5053
|
function createAddIntegrationCommand() {
|
|
4787
|
-
return new
|
|
5054
|
+
return new Command36("add").description("Enable an MCP integration").argument("<name>", "Integration name (e.g. perplexity, augment-code)").action(async (name, _opts, cmd) => {
|
|
4788
5055
|
const globalOpts = cmd.optsWithGlobals();
|
|
4789
5056
|
const result = addIntegration(process.cwd(), name);
|
|
4790
5057
|
if (!result.ok) {
|
|
@@ -4798,8 +5065,8 @@ function createAddIntegrationCommand() {
|
|
|
4798
5065
|
}
|
|
4799
5066
|
|
|
4800
5067
|
// src/commands/integrations/list.ts
|
|
4801
|
-
import { Command as
|
|
4802
|
-
import * as
|
|
5068
|
+
import { Command as Command37 } from "commander";
|
|
5069
|
+
import * as path35 from "path";
|
|
4803
5070
|
import chalk5 from "chalk";
|
|
4804
5071
|
function printTier0Integrations(tier0, mcpServers) {
|
|
4805
5072
|
console.log(" Tier 0 (zero-config):");
|
|
@@ -4823,8 +5090,8 @@ function printTier1Integrations(tier1, mcpServers, dismissed) {
|
|
|
4823
5090
|
}
|
|
4824
5091
|
async function runListIntegrations(globalOpts) {
|
|
4825
5092
|
const cwd = process.cwd();
|
|
4826
|
-
const mcpConfig = readMcpConfig(
|
|
4827
|
-
const integConfig = readIntegrationsConfig(
|
|
5093
|
+
const mcpConfig = readMcpConfig(path35.join(cwd, ".mcp.json"));
|
|
5094
|
+
const integConfig = readIntegrationsConfig(path35.join(cwd, "harness.config.json"));
|
|
4828
5095
|
const mcpServers = mcpConfig.mcpServers ?? {};
|
|
4829
5096
|
if (globalOpts.json) {
|
|
4830
5097
|
const entries = INTEGRATION_REGISTRY.map((i) => ({
|
|
@@ -4855,13 +5122,14 @@ async function runListIntegrations(globalOpts) {
|
|
|
4855
5122
|
process.exit(ExitCode.SUCCESS);
|
|
4856
5123
|
}
|
|
4857
5124
|
function createListIntegrationsCommand() {
|
|
4858
|
-
return new
|
|
5125
|
+
return new Command37("list").description("Show all MCP integrations with status").action(async (_opts, cmd) => runListIntegrations(cmd.optsWithGlobals()));
|
|
4859
5126
|
}
|
|
4860
5127
|
|
|
4861
5128
|
// src/commands/integrations/remove.ts
|
|
4862
|
-
|
|
4863
|
-
import
|
|
4864
|
-
import * as
|
|
5129
|
+
init_dist();
|
|
5130
|
+
import { Command as Command38 } from "commander";
|
|
5131
|
+
import * as fs23 from "fs";
|
|
5132
|
+
import * as path36 from "path";
|
|
4865
5133
|
function removeIntegration(cwd, name) {
|
|
4866
5134
|
const def = INTEGRATION_REGISTRY.find((i) => i.name === name);
|
|
4867
5135
|
if (!def) {
|
|
@@ -4872,21 +5140,21 @@ function removeIntegration(cwd, name) {
|
|
|
4872
5140
|
)
|
|
4873
5141
|
);
|
|
4874
5142
|
}
|
|
4875
|
-
const mcpPath =
|
|
5143
|
+
const mcpPath = path36.join(cwd, ".mcp.json");
|
|
4876
5144
|
removeMcpEntry(mcpPath, def.name);
|
|
4877
|
-
const geminiDir =
|
|
4878
|
-
if (
|
|
4879
|
-
const geminiPath =
|
|
5145
|
+
const geminiDir = path36.join(cwd, ".gemini");
|
|
5146
|
+
if (fs23.existsSync(geminiDir)) {
|
|
5147
|
+
const geminiPath = path36.join(geminiDir, "settings.json");
|
|
4880
5148
|
removeMcpEntry(geminiPath, def.name);
|
|
4881
5149
|
}
|
|
4882
|
-
const configPath =
|
|
5150
|
+
const configPath = path36.join(cwd, "harness.config.json");
|
|
4883
5151
|
const integConfig = readIntegrationsConfig(configPath);
|
|
4884
5152
|
integConfig.enabled = integConfig.enabled.filter((e) => e !== def.name);
|
|
4885
5153
|
writeIntegrationsConfig(configPath, integConfig);
|
|
4886
5154
|
return Ok(def.displayName);
|
|
4887
5155
|
}
|
|
4888
5156
|
function createRemoveIntegrationCommand() {
|
|
4889
|
-
return new
|
|
5157
|
+
return new Command38("remove").description("Remove an MCP integration").argument("<name>", "Integration name (e.g. perplexity, augment-code)").action(async (name, _opts, cmd) => {
|
|
4890
5158
|
const globalOpts = cmd.optsWithGlobals();
|
|
4891
5159
|
const cwd = process.cwd();
|
|
4892
5160
|
const result = removeIntegration(cwd, name);
|
|
@@ -4907,8 +5175,9 @@ function createRemoveIntegrationCommand() {
|
|
|
4907
5175
|
}
|
|
4908
5176
|
|
|
4909
5177
|
// src/commands/integrations/dismiss.ts
|
|
4910
|
-
|
|
4911
|
-
import
|
|
5178
|
+
init_dist();
|
|
5179
|
+
import { Command as Command39 } from "commander";
|
|
5180
|
+
import * as path37 from "path";
|
|
4912
5181
|
function dismissIntegration(cwd, name) {
|
|
4913
5182
|
const def = INTEGRATION_REGISTRY.find((i) => i.name === name);
|
|
4914
5183
|
if (!def) {
|
|
@@ -4919,7 +5188,7 @@ function dismissIntegration(cwd, name) {
|
|
|
4919
5188
|
)
|
|
4920
5189
|
);
|
|
4921
5190
|
}
|
|
4922
|
-
const configPath =
|
|
5191
|
+
const configPath = path37.join(cwd, "harness.config.json");
|
|
4923
5192
|
const integConfig = readIntegrationsConfig(configPath);
|
|
4924
5193
|
if (!integConfig.dismissed.includes(def.name)) {
|
|
4925
5194
|
integConfig.dismissed.push(def.name);
|
|
@@ -4929,7 +5198,7 @@ function dismissIntegration(cwd, name) {
|
|
|
4929
5198
|
return Ok(def.displayName);
|
|
4930
5199
|
}
|
|
4931
5200
|
function createDismissIntegrationCommand() {
|
|
4932
|
-
return new
|
|
5201
|
+
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
5202
|
const globalOpts = cmd.optsWithGlobals();
|
|
4934
5203
|
const cwd = process.cwd();
|
|
4935
5204
|
const result = dismissIntegration(cwd, name);
|
|
@@ -4953,7 +5222,7 @@ function createDismissIntegrationCommand() {
|
|
|
4953
5222
|
|
|
4954
5223
|
// src/commands/integrations/index.ts
|
|
4955
5224
|
function createIntegrationsCommand() {
|
|
4956
|
-
const command = new
|
|
5225
|
+
const command = new Command40("integrations").description(
|
|
4957
5226
|
"Manage MCP peer integrations (add, list, remove, dismiss)"
|
|
4958
5227
|
);
|
|
4959
5228
|
command.addCommand(createListIntegrationsCommand());
|
|
@@ -4964,13 +5233,14 @@ function createIntegrationsCommand() {
|
|
|
4964
5233
|
}
|
|
4965
5234
|
|
|
4966
5235
|
// src/commands/learnings/index.ts
|
|
4967
|
-
import { Command as
|
|
5236
|
+
import { Command as Command42 } from "commander";
|
|
4968
5237
|
|
|
4969
5238
|
// src/commands/learnings/prune.ts
|
|
4970
|
-
|
|
4971
|
-
import
|
|
5239
|
+
init_dist();
|
|
5240
|
+
import { Command as Command41 } from "commander";
|
|
5241
|
+
import * as path38 from "path";
|
|
4972
5242
|
async function handlePrune(opts) {
|
|
4973
|
-
const projectPath =
|
|
5243
|
+
const projectPath = path38.resolve(opts.path);
|
|
4974
5244
|
const result = await pruneLearnings(projectPath, opts.stream);
|
|
4975
5245
|
if (!result.ok) {
|
|
4976
5246
|
logger.error(result.error.message);
|
|
@@ -5009,23 +5279,23 @@ function printPatternProposals(patterns) {
|
|
|
5009
5279
|
);
|
|
5010
5280
|
}
|
|
5011
5281
|
function createPruneCommand() {
|
|
5012
|
-
return new
|
|
5282
|
+
return new Command41("prune").description(
|
|
5013
5283
|
"Analyze global learnings for patterns, present improvement proposals, and archive old entries"
|
|
5014
5284
|
).option("--path <path>", "Project root path", ".").option("--stream <name>", "Target a specific stream").action(handlePrune);
|
|
5015
5285
|
}
|
|
5016
5286
|
|
|
5017
5287
|
// src/commands/learnings/index.ts
|
|
5018
5288
|
function createLearningsCommand() {
|
|
5019
|
-
const command = new
|
|
5289
|
+
const command = new Command42("learnings").description("Learnings management commands");
|
|
5020
5290
|
command.addCommand(createPruneCommand());
|
|
5021
5291
|
return command;
|
|
5022
5292
|
}
|
|
5023
5293
|
|
|
5024
5294
|
// src/commands/linter/index.ts
|
|
5025
|
-
import { Command as
|
|
5295
|
+
import { Command as Command45 } from "commander";
|
|
5026
5296
|
|
|
5027
5297
|
// src/commands/linter/generate.ts
|
|
5028
|
-
import { Command as
|
|
5298
|
+
import { Command as Command43 } from "commander";
|
|
5029
5299
|
function formatGenerateError(e) {
|
|
5030
5300
|
switch (e.type) {
|
|
5031
5301
|
case "parse":
|
|
@@ -5069,7 +5339,7 @@ function handleSuccess(result, useJson) {
|
|
|
5069
5339
|
Generated ${result.rulesGenerated.length} rules to ${result.outputDir}`);
|
|
5070
5340
|
}
|
|
5071
5341
|
function createGenerateCommand2() {
|
|
5072
|
-
return new
|
|
5342
|
+
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
5343
|
try {
|
|
5074
5344
|
if (options.verbose) logger.info(`Parsing config: ${options.config}`);
|
|
5075
5345
|
const result = await generate({
|
|
@@ -5091,9 +5361,9 @@ function createGenerateCommand2() {
|
|
|
5091
5361
|
}
|
|
5092
5362
|
|
|
5093
5363
|
// src/commands/linter/validate.ts
|
|
5094
|
-
import { Command as
|
|
5364
|
+
import { Command as Command44 } from "commander";
|
|
5095
5365
|
function createValidateCommand() {
|
|
5096
|
-
return new
|
|
5366
|
+
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
5367
|
try {
|
|
5098
5368
|
const result = await validate({ configPath: options.config });
|
|
5099
5369
|
if (options.json) {
|
|
@@ -5112,7 +5382,7 @@ function createValidateCommand() {
|
|
|
5112
5382
|
|
|
5113
5383
|
// src/commands/linter/index.ts
|
|
5114
5384
|
function createLinterCommand() {
|
|
5115
|
-
const linter = new
|
|
5385
|
+
const linter = new Command45("linter").description(
|
|
5116
5386
|
"Generate and validate ESLint rules from YAML config"
|
|
5117
5387
|
);
|
|
5118
5388
|
linter.addCommand(createGenerateCommand2());
|
|
@@ -5121,22 +5391,61 @@ function createLinterCommand() {
|
|
|
5121
5391
|
}
|
|
5122
5392
|
|
|
5123
5393
|
// src/commands/mcp.ts
|
|
5124
|
-
import { Command as
|
|
5394
|
+
import { Command as Command46 } from "commander";
|
|
5395
|
+
var VALID_TIERS = /* @__PURE__ */ new Set(["core", "standard", "full"]);
|
|
5396
|
+
function parseTier(value) {
|
|
5397
|
+
const lower = value.toLowerCase();
|
|
5398
|
+
if (!VALID_TIERS.has(lower)) {
|
|
5399
|
+
throw new Error(`Invalid tier "${value}". Expected one of: core, standard, full.`);
|
|
5400
|
+
}
|
|
5401
|
+
return lower;
|
|
5402
|
+
}
|
|
5403
|
+
function parseBudget(value) {
|
|
5404
|
+
const n = Number.parseInt(value, 10);
|
|
5405
|
+
if (!Number.isFinite(n) || n < 0) {
|
|
5406
|
+
throw new Error(`Invalid --budget-tokens "${value}". Expected a non-negative integer.`);
|
|
5407
|
+
}
|
|
5408
|
+
return n;
|
|
5409
|
+
}
|
|
5125
5410
|
function createMcpCommand() {
|
|
5126
|
-
return new
|
|
5127
|
-
|
|
5128
|
-
|
|
5411
|
+
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(
|
|
5412
|
+
"--tier <core|standard|full>",
|
|
5413
|
+
"Load a preset tool tier instead of all tools",
|
|
5414
|
+
parseTier
|
|
5415
|
+
).option(
|
|
5416
|
+
"--budget-tokens <n>",
|
|
5417
|
+
"Auto-select tier to fit this baseline token budget",
|
|
5418
|
+
parseBudget
|
|
5419
|
+
).action(async (opts) => {
|
|
5420
|
+
const [{ startServer: startServer2, getToolDefinitions: getToolDefinitions2 }, { selectTier }] = await Promise.all([
|
|
5421
|
+
import("./mcp-2553PNUC.js"),
|
|
5422
|
+
import("./tool-tiers-7QGZ3FKY.js")
|
|
5423
|
+
]);
|
|
5424
|
+
if (opts.tools && opts.tools.length > 0) {
|
|
5425
|
+
await startServer2(opts.tools);
|
|
5426
|
+
return;
|
|
5427
|
+
}
|
|
5428
|
+
if (opts.tier || opts.budgetTokens !== void 0) {
|
|
5429
|
+
const definitions = getToolDefinitions2();
|
|
5430
|
+
const selection = selectTier(definitions, {
|
|
5431
|
+
...opts.tier ? { overrideTier: opts.tier } : {},
|
|
5432
|
+
...opts.budgetTokens !== void 0 ? { tokenBudget: opts.budgetTokens } : {}
|
|
5433
|
+
});
|
|
5434
|
+
await startServer2(selection.filter);
|
|
5435
|
+
return;
|
|
5436
|
+
}
|
|
5437
|
+
await startServer2();
|
|
5129
5438
|
});
|
|
5130
5439
|
}
|
|
5131
5440
|
|
|
5132
5441
|
// src/commands/orchestrator.ts
|
|
5133
|
-
import { Command as
|
|
5134
|
-
import * as
|
|
5442
|
+
import { Command as Command47 } from "commander";
|
|
5443
|
+
import * as path39 from "path";
|
|
5135
5444
|
import { Orchestrator, WorkflowLoader, launchTUI } from "@harness-engineering/orchestrator";
|
|
5136
5445
|
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 =
|
|
5446
|
+
const orchestrator = new Command47("orchestrator");
|
|
5447
|
+
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) => {
|
|
5448
|
+
const workflowPath = path39.resolve(process.cwd(), opts.workflow);
|
|
5140
5449
|
const loader = new WorkflowLoader();
|
|
5141
5450
|
const result = await loader.loadWorkflow(workflowPath);
|
|
5142
5451
|
if (!result.ok) {
|
|
@@ -5152,23 +5461,32 @@ function createOrchestratorCommand() {
|
|
|
5152
5461
|
process.on("SIGINT", shutdown);
|
|
5153
5462
|
process.on("SIGTERM", shutdown);
|
|
5154
5463
|
daemon.start();
|
|
5155
|
-
|
|
5156
|
-
|
|
5464
|
+
if (opts.headless) {
|
|
5465
|
+
logger.info(
|
|
5466
|
+
"Orchestrator running in headless mode (no TUI). Use the web dashboard or Ctrl+C to stop."
|
|
5467
|
+
);
|
|
5468
|
+
await new Promise(() => {
|
|
5469
|
+
});
|
|
5470
|
+
} else {
|
|
5471
|
+
const { waitUntilExit } = launchTUI(daemon);
|
|
5472
|
+
await waitUntilExit();
|
|
5473
|
+
}
|
|
5157
5474
|
process.exit(ExitCode.SUCCESS);
|
|
5158
5475
|
});
|
|
5159
5476
|
return orchestrator;
|
|
5160
5477
|
}
|
|
5161
5478
|
|
|
5162
5479
|
// src/commands/perf.ts
|
|
5163
|
-
|
|
5164
|
-
import
|
|
5480
|
+
init_dist();
|
|
5481
|
+
import { Command as Command48 } from "commander";
|
|
5482
|
+
import * as path40 from "path";
|
|
5165
5483
|
function registerBenchCommand(perf) {
|
|
5166
|
-
perf.command("bench [glob]").description("Run benchmarks via vitest bench").action(async (
|
|
5484
|
+
perf.command("bench [glob]").description("Run benchmarks via vitest bench").action(async (glob2, _opts, cmd) => {
|
|
5167
5485
|
const globalOpts = cmd.optsWithGlobals();
|
|
5168
5486
|
const cwd = process.cwd();
|
|
5169
|
-
const { BenchmarkRunner } = await import("./dist-
|
|
5487
|
+
const { BenchmarkRunner } = await import("./dist-WCSJHQPK.js");
|
|
5170
5488
|
const runner = new BenchmarkRunner();
|
|
5171
|
-
const benchFiles = runner.discover(cwd,
|
|
5489
|
+
const benchFiles = runner.discover(cwd, glob2);
|
|
5172
5490
|
if (benchFiles.length === 0) {
|
|
5173
5491
|
if (globalOpts.json) {
|
|
5174
5492
|
console.log(JSON.stringify({ benchFiles: [], message: "No .bench.ts files found" }));
|
|
@@ -5178,7 +5496,7 @@ function registerBenchCommand(perf) {
|
|
|
5178
5496
|
return;
|
|
5179
5497
|
}
|
|
5180
5498
|
logBenchDiscovery(globalOpts.json, benchFiles);
|
|
5181
|
-
const result = await runner.run(
|
|
5499
|
+
const result = await runner.run(glob2 ? { cwd, glob: glob2 } : { cwd });
|
|
5182
5500
|
outputBenchResults(globalOpts.json, result);
|
|
5183
5501
|
});
|
|
5184
5502
|
}
|
|
@@ -5230,7 +5548,7 @@ async function runBaselinesShow(globalOpts) {
|
|
|
5230
5548
|
}
|
|
5231
5549
|
async function runBaselinesUpdate(globalOpts) {
|
|
5232
5550
|
const cwd = process.cwd();
|
|
5233
|
-
const { BenchmarkRunner } = await import("./dist-
|
|
5551
|
+
const { BenchmarkRunner } = await import("./dist-WCSJHQPK.js");
|
|
5234
5552
|
const runner = new BenchmarkRunner();
|
|
5235
5553
|
const manager = new BaselineManager(cwd);
|
|
5236
5554
|
logger.info("Running benchmarks to update baselines...");
|
|
@@ -5271,9 +5589,9 @@ function registerReportCommand(perf) {
|
|
|
5271
5589
|
perf.command("report").description("Full performance report with metrics, trends, and hotspots").action(async (_opts, cmd) => {
|
|
5272
5590
|
const globalOpts = cmd.optsWithGlobals();
|
|
5273
5591
|
const cwd = process.cwd();
|
|
5274
|
-
const { EntropyAnalyzer: EntropyAnalyzer2 } = await import("./dist-
|
|
5592
|
+
const { EntropyAnalyzer: EntropyAnalyzer2 } = await import("./dist-WCSJHQPK.js");
|
|
5275
5593
|
const analyzer = new EntropyAnalyzer2({
|
|
5276
|
-
rootDir:
|
|
5594
|
+
rootDir: path40.resolve(cwd),
|
|
5277
5595
|
analyze: { complexity: true, coupling: true }
|
|
5278
5596
|
});
|
|
5279
5597
|
const result = await analyzer.analyze();
|
|
@@ -5332,7 +5650,7 @@ function registerCriticalPathsCommand(perf) {
|
|
|
5332
5650
|
});
|
|
5333
5651
|
}
|
|
5334
5652
|
function createPerfCommand() {
|
|
5335
|
-
const perf = new
|
|
5653
|
+
const perf = new Command48("perf").description("Performance benchmark and baseline management");
|
|
5336
5654
|
registerBenchCommand(perf);
|
|
5337
5655
|
registerBaselinesCommands(perf);
|
|
5338
5656
|
registerReportCommand(perf);
|
|
@@ -5341,10 +5659,10 @@ function createPerfCommand() {
|
|
|
5341
5659
|
}
|
|
5342
5660
|
|
|
5343
5661
|
// src/commands/persona/index.ts
|
|
5344
|
-
import { Command as
|
|
5662
|
+
import { Command as Command51 } from "commander";
|
|
5345
5663
|
|
|
5346
5664
|
// src/commands/persona/list.ts
|
|
5347
|
-
import { Command as
|
|
5665
|
+
import { Command as Command49 } from "commander";
|
|
5348
5666
|
function printPersonaList(personas) {
|
|
5349
5667
|
if (personas.length === 0) {
|
|
5350
5668
|
logger.info("No personas found.");
|
|
@@ -5358,7 +5676,7 @@ function printPersonaList(personas) {
|
|
|
5358
5676
|
}
|
|
5359
5677
|
}
|
|
5360
5678
|
function createListCommand2() {
|
|
5361
|
-
return new
|
|
5679
|
+
return new Command49("list").description("List available agent personas").action(async (_opts, cmd) => {
|
|
5362
5680
|
const globalOpts = cmd.optsWithGlobals();
|
|
5363
5681
|
const personasDir = resolvePersonasDir();
|
|
5364
5682
|
const result = listPersonas(personasDir);
|
|
@@ -5378,51 +5696,51 @@ function createListCommand2() {
|
|
|
5378
5696
|
}
|
|
5379
5697
|
|
|
5380
5698
|
// src/commands/persona/generate.ts
|
|
5381
|
-
import { Command as
|
|
5382
|
-
import * as
|
|
5383
|
-
import * as
|
|
5699
|
+
import { Command as Command50 } from "commander";
|
|
5700
|
+
import * as fs24 from "fs";
|
|
5701
|
+
import * as path41 from "path";
|
|
5384
5702
|
function generatePersonaArtifacts(persona, outputDir, only) {
|
|
5385
5703
|
const slug = toKebabCase(persona.name);
|
|
5386
5704
|
const generated = [];
|
|
5387
5705
|
if (!only || only === "runtime") {
|
|
5388
5706
|
const result = generateRuntime(persona);
|
|
5389
5707
|
if (result.ok) {
|
|
5390
|
-
const outPath =
|
|
5391
|
-
|
|
5392
|
-
|
|
5708
|
+
const outPath = path41.join(outputDir, `${slug}.runtime.json`);
|
|
5709
|
+
fs24.mkdirSync(path41.dirname(outPath), { recursive: true });
|
|
5710
|
+
fs24.writeFileSync(outPath, result.value);
|
|
5393
5711
|
generated.push(outPath);
|
|
5394
5712
|
}
|
|
5395
5713
|
}
|
|
5396
5714
|
if (!only || only === "agents-md") {
|
|
5397
5715
|
const result = generateAgentsMd(persona);
|
|
5398
5716
|
if (result.ok) {
|
|
5399
|
-
const outPath =
|
|
5400
|
-
|
|
5717
|
+
const outPath = path41.join(outputDir, `${slug}.agents.md`);
|
|
5718
|
+
fs24.writeFileSync(outPath, result.value);
|
|
5401
5719
|
generated.push(outPath);
|
|
5402
5720
|
}
|
|
5403
5721
|
}
|
|
5404
5722
|
if (!only || only === "ci") {
|
|
5405
5723
|
const result = generateCIWorkflow(persona, "github");
|
|
5406
5724
|
if (result.ok) {
|
|
5407
|
-
const outPath =
|
|
5408
|
-
|
|
5409
|
-
|
|
5725
|
+
const outPath = path41.join(outputDir, ".github", "workflows", `${slug}.yml`);
|
|
5726
|
+
fs24.mkdirSync(path41.dirname(outPath), { recursive: true });
|
|
5727
|
+
fs24.writeFileSync(outPath, result.value);
|
|
5410
5728
|
generated.push(outPath);
|
|
5411
5729
|
}
|
|
5412
5730
|
}
|
|
5413
5731
|
return generated;
|
|
5414
5732
|
}
|
|
5415
5733
|
function createGenerateCommand3() {
|
|
5416
|
-
return new
|
|
5734
|
+
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
5735
|
const globalOpts = cmd.optsWithGlobals();
|
|
5418
|
-
const personaResult = loadPersona(
|
|
5736
|
+
const personaResult = loadPersona(path41.join(resolvePersonasDir(), `${name}.yaml`));
|
|
5419
5737
|
if (!personaResult.ok) {
|
|
5420
5738
|
logger.error(personaResult.error.message);
|
|
5421
5739
|
process.exit(ExitCode.ERROR);
|
|
5422
5740
|
}
|
|
5423
5741
|
const generated = generatePersonaArtifacts(
|
|
5424
5742
|
personaResult.value,
|
|
5425
|
-
|
|
5743
|
+
path41.resolve(opts.outputDir),
|
|
5426
5744
|
opts.only
|
|
5427
5745
|
);
|
|
5428
5746
|
if (!globalOpts.quiet) {
|
|
@@ -5435,14 +5753,15 @@ function createGenerateCommand3() {
|
|
|
5435
5753
|
|
|
5436
5754
|
// src/commands/persona/index.ts
|
|
5437
5755
|
function createPersonaCommand() {
|
|
5438
|
-
const command = new
|
|
5756
|
+
const command = new Command51("persona").description("Agent persona management commands");
|
|
5439
5757
|
command.addCommand(createListCommand2());
|
|
5440
5758
|
command.addCommand(createGenerateCommand3());
|
|
5441
5759
|
return command;
|
|
5442
5760
|
}
|
|
5443
5761
|
|
|
5444
5762
|
// src/commands/predict.ts
|
|
5445
|
-
|
|
5763
|
+
init_dist();
|
|
5764
|
+
import { Command as Command52 } from "commander";
|
|
5446
5765
|
import chalk6 from "chalk";
|
|
5447
5766
|
var CATEGORY_ORDER = [
|
|
5448
5767
|
"circular-deps",
|
|
@@ -5548,7 +5867,7 @@ function handlePredictError(err, mode) {
|
|
|
5548
5867
|
process.exit(ExitCode.ERROR);
|
|
5549
5868
|
}
|
|
5550
5869
|
function createPredictCommand() {
|
|
5551
|
-
const command = new
|
|
5870
|
+
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
5871
|
const globalOpts = cmd.optsWithGlobals();
|
|
5553
5872
|
const mode = globalOpts.json ? OutputMode.JSON : OutputMode.TEXT;
|
|
5554
5873
|
try {
|
|
@@ -5570,8 +5889,137 @@ function createPredictCommand() {
|
|
|
5570
5889
|
return command;
|
|
5571
5890
|
}
|
|
5572
5891
|
|
|
5892
|
+
// src/commands/publish-analyses.ts
|
|
5893
|
+
import * as fs25 from "fs";
|
|
5894
|
+
import * as path42 from "path";
|
|
5895
|
+
import { Command as Command53 } from "commander";
|
|
5896
|
+
init_dist();
|
|
5897
|
+
import {
|
|
5898
|
+
renderAnalysisComment,
|
|
5899
|
+
loadPublishedIndex,
|
|
5900
|
+
savePublishedIndex
|
|
5901
|
+
} from "@harness-engineering/orchestrator";
|
|
5902
|
+
function bootstrapTrackerCommand(opts, verb) {
|
|
5903
|
+
const projectPath = path42.resolve(opts.dir);
|
|
5904
|
+
const trackerConfig = loadTrackerSyncConfig(projectPath);
|
|
5905
|
+
if (!trackerConfig) {
|
|
5906
|
+
logger.error(`No tracker config found in harness.config.json. Cannot ${verb}.`);
|
|
5907
|
+
return null;
|
|
5908
|
+
}
|
|
5909
|
+
if (trackerConfig.kind !== "github") {
|
|
5910
|
+
logger.error(
|
|
5911
|
+
`${verb} currently only supports 'github' tracker kind. Found: ${String(trackerConfig.kind)}`
|
|
5912
|
+
);
|
|
5913
|
+
return null;
|
|
5914
|
+
}
|
|
5915
|
+
const projectEnvPath = path42.join(projectPath, ".env");
|
|
5916
|
+
if (fs25.existsSync(projectEnvPath) && !process.env.GITHUB_TOKEN) {
|
|
5917
|
+
__require("dotenv").config({ path: projectEnvPath });
|
|
5918
|
+
}
|
|
5919
|
+
const token = process.env.GITHUB_TOKEN;
|
|
5920
|
+
if (!token) {
|
|
5921
|
+
logger.error("No GITHUB_TOKEN environment variable found.");
|
|
5922
|
+
return null;
|
|
5923
|
+
}
|
|
5924
|
+
return { token, projectPath, trackerConfig };
|
|
5925
|
+
}
|
|
5926
|
+
function buildNameToExternalIdMap(projectPath) {
|
|
5927
|
+
const roadmapFile = path42.join(projectPath, "docs", "roadmap.md");
|
|
5928
|
+
if (!fs25.existsSync(roadmapFile)) {
|
|
5929
|
+
logger.error("No docs/roadmap.md found. Cannot map issue hashes to external IDs.");
|
|
5930
|
+
return null;
|
|
5931
|
+
}
|
|
5932
|
+
const { parseRoadmap } = (init_dist(), __toCommonJS(dist_exports));
|
|
5933
|
+
const roadmapParsed = parseRoadmap(fs25.readFileSync(roadmapFile, "utf-8"));
|
|
5934
|
+
if (!roadmapParsed.ok) {
|
|
5935
|
+
logger.error("Failed to parse docs/roadmap.md");
|
|
5936
|
+
return null;
|
|
5937
|
+
}
|
|
5938
|
+
const map = /* @__PURE__ */ new Map();
|
|
5939
|
+
for (const milestone of roadmapParsed.value.milestones) {
|
|
5940
|
+
for (const feature of milestone.features) {
|
|
5941
|
+
if (feature.externalId) {
|
|
5942
|
+
map.set(feature.name.toLowerCase(), feature.externalId);
|
|
5943
|
+
}
|
|
5944
|
+
}
|
|
5945
|
+
}
|
|
5946
|
+
return map;
|
|
5947
|
+
}
|
|
5948
|
+
function resolveExternalId(record, nameToExternalId) {
|
|
5949
|
+
if (record.externalId) return record.externalId;
|
|
5950
|
+
for (const [featName, extId] of nameToExternalId) {
|
|
5951
|
+
const prefix = featName.toLowerCase().replace(/[^a-z0-9]+/g, "-").slice(0, 20);
|
|
5952
|
+
if (record.identifier.startsWith(prefix)) return extId;
|
|
5953
|
+
}
|
|
5954
|
+
return null;
|
|
5955
|
+
}
|
|
5956
|
+
async function publishUnpublishedAnalyses(analyses, adapter, nameToExternalId, publishedIndex) {
|
|
5957
|
+
let publishedCount = 0;
|
|
5958
|
+
for (const record of analyses) {
|
|
5959
|
+
if (publishedIndex[record.issueId]) continue;
|
|
5960
|
+
const externalId = resolveExternalId(record, nameToExternalId);
|
|
5961
|
+
if (!externalId) {
|
|
5962
|
+
logger.dim(`Skipping ${record.identifier}: could not resolve externalId from roadmap`);
|
|
5963
|
+
continue;
|
|
5964
|
+
}
|
|
5965
|
+
logger.info(`Publishing analysis for ${record.identifier} to ${externalId}...`);
|
|
5966
|
+
const result = await adapter.addComment(externalId, renderAnalysisComment(record));
|
|
5967
|
+
if (result.ok) {
|
|
5968
|
+
publishedIndex[record.issueId] = (/* @__PURE__ */ new Date()).toISOString();
|
|
5969
|
+
publishedCount++;
|
|
5970
|
+
} else {
|
|
5971
|
+
logger.error(`Failed to publish ${record.identifier}: ${result.error.message}`);
|
|
5972
|
+
}
|
|
5973
|
+
}
|
|
5974
|
+
return publishedCount;
|
|
5975
|
+
}
|
|
5976
|
+
async function runPublishAnalyses(opts) {
|
|
5977
|
+
const bootstrap = bootstrapTrackerCommand(opts, "publish");
|
|
5978
|
+
if (!bootstrap) return process.exit(1);
|
|
5979
|
+
const { token, projectPath, trackerConfig } = bootstrap;
|
|
5980
|
+
const nameToExternalId = buildNameToExternalIdMap(projectPath);
|
|
5981
|
+
if (!nameToExternalId) return process.exit(1);
|
|
5982
|
+
const { AnalysisArchive } = await import("@harness-engineering/orchestrator");
|
|
5983
|
+
const { GitHubIssuesSyncAdapter: GitHubIssuesSyncAdapter2 } = await import("./dist-WCSJHQPK.js");
|
|
5984
|
+
const archive = new AnalysisArchive(path42.join(projectPath, ".harness", "analyses"));
|
|
5985
|
+
const analyses = await archive.list();
|
|
5986
|
+
if (analyses.length === 0) {
|
|
5987
|
+
logger.info("No analyses found to publish.");
|
|
5988
|
+
return;
|
|
5989
|
+
}
|
|
5990
|
+
const publishedIndex = loadPublishedIndex(projectPath);
|
|
5991
|
+
const adapter = new GitHubIssuesSyncAdapter2({ token, config: trackerConfig });
|
|
5992
|
+
const publishedCount = await publishUnpublishedAnalyses(
|
|
5993
|
+
analyses,
|
|
5994
|
+
adapter,
|
|
5995
|
+
nameToExternalId,
|
|
5996
|
+
publishedIndex
|
|
5997
|
+
);
|
|
5998
|
+
if (publishedCount > 0) {
|
|
5999
|
+
savePublishedIndex(projectPath, publishedIndex);
|
|
6000
|
+
logger.success(`Successfully published ${publishedCount} analyses.`);
|
|
6001
|
+
} else {
|
|
6002
|
+
logger.info("All analyses are already up to date on external tracker.");
|
|
6003
|
+
}
|
|
6004
|
+
}
|
|
6005
|
+
function createPublishAnalysesCommand() {
|
|
6006
|
+
const command = new Command53("publish-analyses").description(
|
|
6007
|
+
"Publishes locally generated intelligence analyses to the external issue tracker (e.g., GitHub)"
|
|
6008
|
+
).option("-d, --dir <path>", "Workspace directory", process.cwd()).action(async (opts) => {
|
|
6009
|
+
try {
|
|
6010
|
+
await runPublishAnalyses(opts);
|
|
6011
|
+
} catch (err) {
|
|
6012
|
+
logger.error(
|
|
6013
|
+
`Error publishing analyses: ${err instanceof Error ? err.message : String(err)}`
|
|
6014
|
+
);
|
|
6015
|
+
process.exit(1);
|
|
6016
|
+
}
|
|
6017
|
+
});
|
|
6018
|
+
return command;
|
|
6019
|
+
}
|
|
6020
|
+
|
|
5573
6021
|
// src/commands/recommend.ts
|
|
5574
|
-
import { Command as
|
|
6022
|
+
import { Command as Command54 } from "commander";
|
|
5575
6023
|
import chalk7 from "chalk";
|
|
5576
6024
|
async function resolveSnapshot(cwd, noCache) {
|
|
5577
6025
|
if (!noCache) {
|
|
@@ -5635,7 +6083,7 @@ function printRecommendations(result) {
|
|
|
5635
6083
|
console.log("");
|
|
5636
6084
|
}
|
|
5637
6085
|
function createRecommendCommand() {
|
|
5638
|
-
const command = new
|
|
6086
|
+
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
6087
|
const globalOpts = cmd.optsWithGlobals();
|
|
5640
6088
|
const mode = globalOpts.json ? OutputMode.JSON : OutputMode.TEXT;
|
|
5641
6089
|
try {
|
|
@@ -5671,9 +6119,10 @@ function createRecommendCommand() {
|
|
|
5671
6119
|
}
|
|
5672
6120
|
|
|
5673
6121
|
// src/commands/scan-config.ts
|
|
5674
|
-
|
|
5675
|
-
import {
|
|
5676
|
-
import {
|
|
6122
|
+
init_dist();
|
|
6123
|
+
import { Command as Command55 } from "commander";
|
|
6124
|
+
import { existsSync as existsSync22, readFileSync as readFileSync14, writeFileSync as writeFileSync12 } from "fs";
|
|
6125
|
+
import { join as join30, relative as relative2 } from "path";
|
|
5677
6126
|
var CONFIG_FILES = ["CLAUDE.md", "AGENTS.md", ".gemini/settings.json", "skill.yaml"];
|
|
5678
6127
|
function stripHighSeverityPatterns(content, injectionFindings) {
|
|
5679
6128
|
const highLines = /* @__PURE__ */ new Set();
|
|
@@ -5706,10 +6155,10 @@ function applyFix(filePath, targetDir, content, injectionFindings) {
|
|
|
5706
6155
|
}
|
|
5707
6156
|
}
|
|
5708
6157
|
function scanSingleFile(filePath, targetDir, scanner, options) {
|
|
5709
|
-
if (!
|
|
6158
|
+
if (!existsSync22(filePath)) return null;
|
|
5710
6159
|
let content;
|
|
5711
6160
|
try {
|
|
5712
|
-
content =
|
|
6161
|
+
content = readFileSync14(filePath, "utf8");
|
|
5713
6162
|
} catch {
|
|
5714
6163
|
return null;
|
|
5715
6164
|
}
|
|
@@ -5730,7 +6179,7 @@ async function runScanConfig(targetDir, options) {
|
|
|
5730
6179
|
const scanner = new SecurityScanner(parseSecurityConfig({}));
|
|
5731
6180
|
const results = [];
|
|
5732
6181
|
for (const configFile of CONFIG_FILES) {
|
|
5733
|
-
const result = scanSingleFile(
|
|
6182
|
+
const result = scanSingleFile(join30(targetDir, configFile), targetDir, scanner, options);
|
|
5734
6183
|
if (result) results.push(result);
|
|
5735
6184
|
}
|
|
5736
6185
|
return { exitCode: computeScanExitCode(results), results };
|
|
@@ -5760,7 +6209,7 @@ function formatTextOutput(result) {
|
|
|
5760
6209
|
}
|
|
5761
6210
|
}
|
|
5762
6211
|
function createScanConfigCommand() {
|
|
5763
|
-
const command = new
|
|
6212
|
+
const command = new Command55("scan-config").description(
|
|
5764
6213
|
"Scan CLAUDE.md, AGENTS.md, .gemini/settings.json, and skill.yaml for prompt injection patterns"
|
|
5765
6214
|
).option("--path <dir>", "Target directory to scan (default: cwd)").option("--fix", "Strip high-severity patterns from files in-place").action(async (opts, cmd) => {
|
|
5766
6215
|
const globalOpts = cmd.optsWithGlobals();
|
|
@@ -5777,24 +6226,24 @@ function createScanConfigCommand() {
|
|
|
5777
6226
|
}
|
|
5778
6227
|
|
|
5779
6228
|
// src/commands/setup.ts
|
|
5780
|
-
import { Command as
|
|
5781
|
-
import * as
|
|
6229
|
+
import { Command as Command56 } from "commander";
|
|
6230
|
+
import * as fs28 from "fs";
|
|
5782
6231
|
import * as os7 from "os";
|
|
5783
|
-
import * as
|
|
5784
|
-
import
|
|
6232
|
+
import * as path45 from "path";
|
|
6233
|
+
import chalk9 from "chalk";
|
|
5785
6234
|
|
|
5786
6235
|
// src/utils/first-run.ts
|
|
5787
|
-
import * as
|
|
6236
|
+
import * as fs26 from "fs";
|
|
5788
6237
|
import * as os6 from "os";
|
|
5789
|
-
import * as
|
|
5790
|
-
var HARNESS_DIR =
|
|
5791
|
-
var MARKER_FILE =
|
|
6238
|
+
import * as path43 from "path";
|
|
6239
|
+
var HARNESS_DIR = path43.join(os6.homedir(), ".harness");
|
|
6240
|
+
var MARKER_FILE = path43.join(HARNESS_DIR, ".setup-complete");
|
|
5792
6241
|
function isFirstRun() {
|
|
5793
|
-
return !
|
|
6242
|
+
return !fs26.existsSync(MARKER_FILE);
|
|
5794
6243
|
}
|
|
5795
6244
|
function markSetupComplete() {
|
|
5796
|
-
|
|
5797
|
-
|
|
6245
|
+
fs26.mkdirSync(HARNESS_DIR, { recursive: true });
|
|
6246
|
+
fs26.writeFileSync(MARKER_FILE, "", "utf-8");
|
|
5798
6247
|
}
|
|
5799
6248
|
function printFirstRunWelcome() {
|
|
5800
6249
|
try {
|
|
@@ -5806,6 +6255,119 @@ function printFirstRunWelcome() {
|
|
|
5806
6255
|
}
|
|
5807
6256
|
}
|
|
5808
6257
|
|
|
6258
|
+
// src/commands/telemetry-wizard.ts
|
|
6259
|
+
import * as fs27 from "fs";
|
|
6260
|
+
import * as path44 from "path";
|
|
6261
|
+
import readline from "readline";
|
|
6262
|
+
import chalk8 from "chalk";
|
|
6263
|
+
function prompt(question) {
|
|
6264
|
+
const rl = readline.createInterface({
|
|
6265
|
+
input: process.stdin,
|
|
6266
|
+
output: process.stdout
|
|
6267
|
+
});
|
|
6268
|
+
return new Promise((resolve35) => {
|
|
6269
|
+
rl.question(question, (answer) => {
|
|
6270
|
+
rl.close();
|
|
6271
|
+
resolve35(answer.trim().toLowerCase());
|
|
6272
|
+
});
|
|
6273
|
+
});
|
|
6274
|
+
}
|
|
6275
|
+
function isNo(answer) {
|
|
6276
|
+
return answer === "n" || answer === "no";
|
|
6277
|
+
}
|
|
6278
|
+
function isTelemetryConfigured(cwd) {
|
|
6279
|
+
const configPath = path44.join(cwd, "harness.config.json");
|
|
6280
|
+
try {
|
|
6281
|
+
const raw = JSON.parse(fs27.readFileSync(configPath, "utf-8"));
|
|
6282
|
+
return raw.telemetry !== void 0 && raw.adoption !== void 0;
|
|
6283
|
+
} catch {
|
|
6284
|
+
return false;
|
|
6285
|
+
}
|
|
6286
|
+
}
|
|
6287
|
+
function writeTelemetryConfig(cwd, result) {
|
|
6288
|
+
const configPath = path44.join(cwd, "harness.config.json");
|
|
6289
|
+
try {
|
|
6290
|
+
const raw = JSON.parse(fs27.readFileSync(configPath, "utf-8"));
|
|
6291
|
+
raw.telemetry = { enabled: result.telemetryEnabled };
|
|
6292
|
+
raw.adoption = { enabled: result.adoptionEnabled };
|
|
6293
|
+
fs27.writeFileSync(configPath, JSON.stringify(raw, null, 2) + "\n");
|
|
6294
|
+
} catch (err) {
|
|
6295
|
+
console.warn(
|
|
6296
|
+
`\u26A0 Could not update ${configPath}: ${err instanceof Error ? err.message : String(err)}`
|
|
6297
|
+
);
|
|
6298
|
+
}
|
|
6299
|
+
const hasIdentity = result.identity.project || result.identity.team || result.identity.alias;
|
|
6300
|
+
if (hasIdentity) {
|
|
6301
|
+
const harnessDir = path44.join(cwd, ".harness");
|
|
6302
|
+
fs27.mkdirSync(harnessDir, { recursive: true });
|
|
6303
|
+
const telemetryFile = path44.join(harnessDir, "telemetry.json");
|
|
6304
|
+
fs27.writeFileSync(telemetryFile, JSON.stringify({ identity: result.identity }, null, 2) + "\n");
|
|
6305
|
+
}
|
|
6306
|
+
}
|
|
6307
|
+
async function promptIdentity() {
|
|
6308
|
+
console.log(
|
|
6309
|
+
` ${chalk8.cyan("Identity")} (optional) \u2014 tag telemetry with project/team/alias for filtering.`
|
|
6310
|
+
);
|
|
6311
|
+
console.log(" Leave blank to keep anonymous.");
|
|
6312
|
+
const answer = await prompt(" Set identity fields? (y/N) ");
|
|
6313
|
+
const identity = {};
|
|
6314
|
+
if (answer === "y" || answer === "yes") {
|
|
6315
|
+
const projectName = await prompt(" Project name: ");
|
|
6316
|
+
if (projectName) identity.project = projectName;
|
|
6317
|
+
const teamName = await prompt(" Team name: ");
|
|
6318
|
+
if (teamName) identity.team = teamName;
|
|
6319
|
+
const alias = await prompt(" Alias: ");
|
|
6320
|
+
if (alias) identity.alias = alias;
|
|
6321
|
+
}
|
|
6322
|
+
return identity;
|
|
6323
|
+
}
|
|
6324
|
+
async function runTelemetryWizard() {
|
|
6325
|
+
if (!process.stdin.isTTY) return null;
|
|
6326
|
+
console.log("");
|
|
6327
|
+
console.log(` ${chalk8.bold("Telemetry Configuration")}`);
|
|
6328
|
+
console.log("");
|
|
6329
|
+
console.log(" Harness can collect anonymous usage data to help improve the tool.");
|
|
6330
|
+
console.log(" You can change these settings anytime in harness.config.json.");
|
|
6331
|
+
console.log("");
|
|
6332
|
+
console.log(
|
|
6333
|
+
` ${chalk8.cyan("Anonymous telemetry")} \u2014 error rates, command usage, performance metrics.`
|
|
6334
|
+
);
|
|
6335
|
+
console.log(" No code, file contents, or personally identifiable info is sent.");
|
|
6336
|
+
const telemetryAnswer = await prompt(" Enable anonymous telemetry? (Y/n) ");
|
|
6337
|
+
const telemetryEnabled = !isNo(telemetryAnswer);
|
|
6338
|
+
console.log("");
|
|
6339
|
+
console.log(
|
|
6340
|
+
` ${chalk8.cyan("Adoption tracking")} \u2014 which skills are used, how often, and success rates.`
|
|
6341
|
+
);
|
|
6342
|
+
console.log(" Stored locally in .harness/adoption/. Never sent externally.");
|
|
6343
|
+
const adoptionAnswer = await prompt(" Enable local adoption tracking? (Y/n) ");
|
|
6344
|
+
const adoptionEnabled = !isNo(adoptionAnswer);
|
|
6345
|
+
console.log("");
|
|
6346
|
+
const identity = await promptIdentity();
|
|
6347
|
+
return { telemetryEnabled, adoptionEnabled, identity };
|
|
6348
|
+
}
|
|
6349
|
+
async function ensureTelemetryConfigured(cwd) {
|
|
6350
|
+
const configPath = path44.join(cwd, "harness.config.json");
|
|
6351
|
+
if (!fs27.existsSync(configPath)) {
|
|
6352
|
+
return { status: "warn", message: "Not a harness project \u2014 skipped telemetry configuration" };
|
|
6353
|
+
}
|
|
6354
|
+
if (isTelemetryConfigured(cwd)) {
|
|
6355
|
+
return { status: "pass", message: "Telemetry already configured" };
|
|
6356
|
+
}
|
|
6357
|
+
const result = await runTelemetryWizard();
|
|
6358
|
+
if (!result) {
|
|
6359
|
+
return { status: "warn", message: "Non-interactive mode \u2014 skipped telemetry configuration" };
|
|
6360
|
+
}
|
|
6361
|
+
writeTelemetryConfig(cwd, result);
|
|
6362
|
+
const parts = [];
|
|
6363
|
+
parts.push(`telemetry ${result.telemetryEnabled ? "enabled" : "disabled"}`);
|
|
6364
|
+
parts.push(`adoption ${result.adoptionEnabled ? "enabled" : "disabled"}`);
|
|
6365
|
+
if (result.identity.project || result.identity.team || result.identity.alias) {
|
|
6366
|
+
parts.push("identity set");
|
|
6367
|
+
}
|
|
6368
|
+
return { status: "pass", message: `Telemetry configured: ${parts.join(", ")}` };
|
|
6369
|
+
}
|
|
6370
|
+
|
|
5809
6371
|
// src/commands/setup.ts
|
|
5810
6372
|
function checkNodeVersion3() {
|
|
5811
6373
|
const result = checkNodeVersion();
|
|
@@ -5832,7 +6394,7 @@ function runSlashCommandGeneration() {
|
|
|
5832
6394
|
}
|
|
5833
6395
|
}
|
|
5834
6396
|
function detectClient(dirName) {
|
|
5835
|
-
return
|
|
6397
|
+
return fs28.existsSync(path45.join(os7.homedir(), dirName));
|
|
5836
6398
|
}
|
|
5837
6399
|
async function runMcpSetup(cwd) {
|
|
5838
6400
|
const results = [];
|
|
@@ -5869,12 +6431,12 @@ async function runMcpSetup(cwd) {
|
|
|
5869
6431
|
return results;
|
|
5870
6432
|
}
|
|
5871
6433
|
function formatStep(result) {
|
|
5872
|
-
const icon = result.status === "pass" ?
|
|
6434
|
+
const icon = result.status === "pass" ? chalk9.green("\u2713") : result.status === "warn" ? chalk9.yellow("\u26A0") : chalk9.red("\u2717");
|
|
5873
6435
|
return ` ${icon} ${result.message}`;
|
|
5874
6436
|
}
|
|
5875
6437
|
function configureTier0Integrations(cwd) {
|
|
5876
6438
|
try {
|
|
5877
|
-
const mcpPath =
|
|
6439
|
+
const mcpPath = path45.join(cwd, ".mcp.json");
|
|
5878
6440
|
const config = readMcpConfig(mcpPath);
|
|
5879
6441
|
const tier0 = INTEGRATION_REGISTRY.filter((i) => i.tier === 0);
|
|
5880
6442
|
const added = [];
|
|
@@ -5883,9 +6445,9 @@ function configureTier0Integrations(cwd) {
|
|
|
5883
6445
|
writeMcpEntry(mcpPath, integration.name, integration.mcpConfig);
|
|
5884
6446
|
added.push(integration.displayName);
|
|
5885
6447
|
}
|
|
5886
|
-
const geminiDir =
|
|
5887
|
-
if (
|
|
5888
|
-
const geminiPath =
|
|
6448
|
+
const geminiDir = path45.join(cwd, ".gemini");
|
|
6449
|
+
if (fs28.existsSync(geminiDir)) {
|
|
6450
|
+
const geminiPath = path45.join(geminiDir, "settings.json");
|
|
5889
6451
|
const geminiConfig = readMcpConfig(geminiPath);
|
|
5890
6452
|
for (const integration of tier0) {
|
|
5891
6453
|
if (!geminiConfig.mcpServers[integration.name]) {
|
|
@@ -5906,14 +6468,14 @@ function configureTier0Integrations(cwd) {
|
|
|
5906
6468
|
}
|
|
5907
6469
|
}
|
|
5908
6470
|
function ensureHooks(cwd) {
|
|
5909
|
-
const configPath =
|
|
5910
|
-
if (!
|
|
6471
|
+
const configPath = path45.join(cwd, "harness.config.json");
|
|
6472
|
+
if (!fs28.existsSync(configPath)) {
|
|
5911
6473
|
return { status: "warn", message: "Not a harness project \u2014 skipped hook installation" };
|
|
5912
6474
|
}
|
|
5913
6475
|
let profile = "standard";
|
|
5914
|
-
const profilePath =
|
|
6476
|
+
const profilePath = path45.join(cwd, ".harness", "hooks", "profile.json");
|
|
5915
6477
|
try {
|
|
5916
|
-
const data = JSON.parse(
|
|
6478
|
+
const data = JSON.parse(fs28.readFileSync(profilePath, "utf-8"));
|
|
5917
6479
|
if (data.profile && ["minimal", "standard", "strict"].includes(data.profile)) {
|
|
5918
6480
|
profile = data.profile;
|
|
5919
6481
|
}
|
|
@@ -5932,7 +6494,7 @@ function ensureHooks(cwd) {
|
|
|
5932
6494
|
}
|
|
5933
6495
|
async function runInitialGraphScan(cwd) {
|
|
5934
6496
|
try {
|
|
5935
|
-
const { runScan: runScan2 } = await import("./scan-
|
|
6497
|
+
const { runScan: runScan2 } = await import("./scan-MPJ6JHUY.js");
|
|
5936
6498
|
const result = await runScan2(cwd);
|
|
5937
6499
|
return {
|
|
5938
6500
|
status: "pass",
|
|
@@ -5958,6 +6520,8 @@ async function runSetup(cwd) {
|
|
|
5958
6520
|
steps.push(tier0Result);
|
|
5959
6521
|
const hooksResult = ensureHooks(cwd);
|
|
5960
6522
|
steps.push(hooksResult);
|
|
6523
|
+
const telemetryResult = await ensureTelemetryConfigured(cwd);
|
|
6524
|
+
steps.push(telemetryResult);
|
|
5961
6525
|
const graphResult = await runInitialGraphScan(cwd);
|
|
5962
6526
|
steps.push(graphResult);
|
|
5963
6527
|
const success = steps.every((s) => s.status !== "fail");
|
|
@@ -5967,10 +6531,10 @@ async function runSetup(cwd) {
|
|
|
5967
6531
|
return { steps, success };
|
|
5968
6532
|
}
|
|
5969
6533
|
function createSetupCommand() {
|
|
5970
|
-
return new
|
|
6534
|
+
return new Command56("setup").description("Configure harness environment: slash commands, MCP, and more").action(async () => {
|
|
5971
6535
|
const cwd = process.cwd();
|
|
5972
6536
|
console.log("");
|
|
5973
|
-
console.log(` ${
|
|
6537
|
+
console.log(` ${chalk9.bold("harness setup")}`);
|
|
5974
6538
|
console.log("");
|
|
5975
6539
|
const { steps, success } = await runSetup(cwd);
|
|
5976
6540
|
for (const step of steps) {
|
|
@@ -5989,15 +6553,16 @@ function createSetupCommand() {
|
|
|
5989
6553
|
}
|
|
5990
6554
|
|
|
5991
6555
|
// src/commands/share.ts
|
|
5992
|
-
|
|
5993
|
-
import
|
|
5994
|
-
import * as
|
|
6556
|
+
init_dist();
|
|
6557
|
+
import { Command as Command57 } from "commander";
|
|
6558
|
+
import * as fs29 from "fs";
|
|
6559
|
+
import * as path46 from "path";
|
|
5995
6560
|
import { parse as parseYaml } from "yaml";
|
|
5996
6561
|
var MANIFEST_FILENAME = "constraints.yaml";
|
|
5997
6562
|
async function runShareAction(projectPath, options) {
|
|
5998
|
-
const rootDir =
|
|
5999
|
-
const manifestPath =
|
|
6000
|
-
if (!
|
|
6563
|
+
const rootDir = path46.resolve(projectPath);
|
|
6564
|
+
const manifestPath = path46.join(rootDir, MANIFEST_FILENAME);
|
|
6565
|
+
if (!fs29.existsSync(manifestPath)) {
|
|
6001
6566
|
logger.error(
|
|
6002
6567
|
`No ${MANIFEST_FILENAME} found at ${manifestPath}.
|
|
6003
6568
|
Create a constraints.yaml in your project root to define what to share.`
|
|
@@ -6006,7 +6571,7 @@ Create a constraints.yaml in your project root to define what to share.`
|
|
|
6006
6571
|
}
|
|
6007
6572
|
let parsed;
|
|
6008
6573
|
try {
|
|
6009
|
-
const raw =
|
|
6574
|
+
const raw = fs29.readFileSync(manifestPath, "utf-8");
|
|
6010
6575
|
parsed = parseYaml(raw);
|
|
6011
6576
|
} catch (err) {
|
|
6012
6577
|
logger.error(
|
|
@@ -6020,7 +6585,7 @@ Create a constraints.yaml in your project root to define what to share.`
|
|
|
6020
6585
|
process.exit(1);
|
|
6021
6586
|
}
|
|
6022
6587
|
const manifest = manifestResult.value;
|
|
6023
|
-
const configResult = resolveConfig(
|
|
6588
|
+
const configResult = resolveConfig(path46.join(rootDir, "harness.config.json"));
|
|
6024
6589
|
if (!configResult.ok) {
|
|
6025
6590
|
logger.error(configResult.error.message);
|
|
6026
6591
|
process.exit(1);
|
|
@@ -6038,8 +6603,8 @@ Create a constraints.yaml in your project root to define what to share.`
|
|
|
6038
6603
|
);
|
|
6039
6604
|
process.exit(1);
|
|
6040
6605
|
}
|
|
6041
|
-
const outputDir =
|
|
6042
|
-
const outputPath =
|
|
6606
|
+
const outputDir = path46.resolve(options.output);
|
|
6607
|
+
const outputPath = path46.join(outputDir, `${manifest.name}.harness-constraints.json`);
|
|
6043
6608
|
const writeResult = await writeConfig(outputPath, bundle);
|
|
6044
6609
|
if (!writeResult.ok) {
|
|
6045
6610
|
logger.error(`Failed to write bundle: ${writeResult.error.message}`);
|
|
@@ -6048,28 +6613,28 @@ Create a constraints.yaml in your project root to define what to share.`
|
|
|
6048
6613
|
logger.success(`Bundle written to ${outputPath}`);
|
|
6049
6614
|
}
|
|
6050
6615
|
function createShareCommand() {
|
|
6051
|
-
return new
|
|
6616
|
+
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
6617
|
await runShareAction(projectPath, options);
|
|
6053
6618
|
});
|
|
6054
6619
|
}
|
|
6055
6620
|
|
|
6056
6621
|
// src/commands/skill/index.ts
|
|
6057
|
-
import { Command as
|
|
6622
|
+
import { Command as Command65 } from "commander";
|
|
6058
6623
|
|
|
6059
6624
|
// src/commands/skill/list.ts
|
|
6060
|
-
import { Command as
|
|
6061
|
-
import * as
|
|
6062
|
-
import * as
|
|
6625
|
+
import { Command as Command58 } from "commander";
|
|
6626
|
+
import * as fs30 from "fs";
|
|
6627
|
+
import * as path47 from "path";
|
|
6063
6628
|
import { parse } from "yaml";
|
|
6064
6629
|
function scanDirectory(dirPath, source) {
|
|
6065
|
-
if (!
|
|
6066
|
-
const entries =
|
|
6630
|
+
if (!fs30.existsSync(dirPath)) return [];
|
|
6631
|
+
const entries = fs30.readdirSync(dirPath, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
|
|
6067
6632
|
const skills = [];
|
|
6068
6633
|
for (const name of entries) {
|
|
6069
|
-
const yamlPath =
|
|
6070
|
-
if (!
|
|
6634
|
+
const yamlPath = path47.join(dirPath, name, "skill.yaml");
|
|
6635
|
+
if (!fs30.existsSync(yamlPath)) continue;
|
|
6071
6636
|
try {
|
|
6072
|
-
const raw =
|
|
6637
|
+
const raw = fs30.readFileSync(yamlPath, "utf-8");
|
|
6073
6638
|
const parsed = parse(raw);
|
|
6074
6639
|
const result = SkillMetadataSchema.safeParse(parsed);
|
|
6075
6640
|
if (result.success) {
|
|
@@ -6087,10 +6652,10 @@ function scanDirectory(dirPath, source) {
|
|
|
6087
6652
|
}
|
|
6088
6653
|
function collectCommunitySkills(seen, allSkills) {
|
|
6089
6654
|
const globalDir = resolveGlobalSkillsDir();
|
|
6090
|
-
const skillsDir =
|
|
6091
|
-
const communityBase =
|
|
6092
|
-
const communityPlatformDir =
|
|
6093
|
-
const lockfilePath =
|
|
6655
|
+
const skillsDir = path47.dirname(globalDir);
|
|
6656
|
+
const communityBase = path47.join(skillsDir, "community");
|
|
6657
|
+
const communityPlatformDir = path47.join(communityBase, "claude-code");
|
|
6658
|
+
const lockfilePath = path47.join(communityBase, "skills-lock.json");
|
|
6094
6659
|
const lockfile = readLockfile2(lockfilePath);
|
|
6095
6660
|
const communitySkills = scanDirectory(communityPlatformDir, "community");
|
|
6096
6661
|
for (const skill of communitySkills) {
|
|
@@ -6160,7 +6725,7 @@ function printSkillsVerbose(skills) {
|
|
|
6160
6725
|
for (const s of skills) printSkillEntry(s);
|
|
6161
6726
|
}
|
|
6162
6727
|
function createListCommand3() {
|
|
6163
|
-
return new
|
|
6728
|
+
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
6729
|
const globalOpts = cmd.optsWithGlobals();
|
|
6165
6730
|
const skills = collectSkills({ filter: resolveFilter(opts) });
|
|
6166
6731
|
if (globalOpts.json) {
|
|
@@ -6175,9 +6740,9 @@ function createListCommand3() {
|
|
|
6175
6740
|
}
|
|
6176
6741
|
|
|
6177
6742
|
// src/commands/skill/run.ts
|
|
6178
|
-
import { Command as
|
|
6179
|
-
import * as
|
|
6180
|
-
import * as
|
|
6743
|
+
import { Command as Command59 } from "commander";
|
|
6744
|
+
import * as fs31 from "fs";
|
|
6745
|
+
import * as path48 from "path";
|
|
6181
6746
|
import { parse as parse2 } from "yaml";
|
|
6182
6747
|
|
|
6183
6748
|
// src/skill/complexity.ts
|
|
@@ -6273,10 +6838,10 @@ ${options.principles}`);
|
|
|
6273
6838
|
|
|
6274
6839
|
// src/commands/skill/run.ts
|
|
6275
6840
|
function loadSkillMetadata(skillDir) {
|
|
6276
|
-
const yamlPath =
|
|
6277
|
-
if (!
|
|
6841
|
+
const yamlPath = path48.join(skillDir, "skill.yaml");
|
|
6842
|
+
if (!fs31.existsSync(yamlPath)) return null;
|
|
6278
6843
|
try {
|
|
6279
|
-
const result = SkillMetadataSchema.safeParse(parse2(
|
|
6844
|
+
const result = SkillMetadataSchema.safeParse(parse2(fs31.readFileSync(yamlPath, "utf-8")));
|
|
6280
6845
|
return result.success ? result.data : null;
|
|
6281
6846
|
} catch {
|
|
6282
6847
|
return null;
|
|
@@ -6288,22 +6853,22 @@ function resolveComplexity(metadata, requested, projectPath) {
|
|
|
6288
6853
|
return requested;
|
|
6289
6854
|
}
|
|
6290
6855
|
function loadPrinciples(projectPath) {
|
|
6291
|
-
const principlesPath =
|
|
6292
|
-
return
|
|
6856
|
+
const principlesPath = path48.join(projectPath, "docs", "principles.md");
|
|
6857
|
+
return fs31.existsSync(principlesPath) ? fs31.readFileSync(principlesPath, "utf-8") : void 0;
|
|
6293
6858
|
}
|
|
6294
6859
|
function readMostRecentFileInDir(dirPath) {
|
|
6295
|
-
const files =
|
|
6296
|
-
if (files.length > 0) return
|
|
6860
|
+
const files = fs31.readdirSync(dirPath).map((f) => ({ name: f, mtime: fs31.statSync(path48.join(dirPath, f)).mtimeMs })).sort((a, b) => b.mtime - a.mtime);
|
|
6861
|
+
if (files.length > 0) return fs31.readFileSync(path48.join(dirPath, files[0].name), "utf-8");
|
|
6297
6862
|
return void 0;
|
|
6298
6863
|
}
|
|
6299
6864
|
function loadPriorState(metadata, projectPath) {
|
|
6300
6865
|
if (!metadata?.state.persistent || metadata.state.files.length === 0) return void 0;
|
|
6301
6866
|
for (const stateFilePath of metadata.state.files) {
|
|
6302
|
-
const fullPath =
|
|
6303
|
-
if (!
|
|
6304
|
-
const stat =
|
|
6867
|
+
const fullPath = path48.join(projectPath, stateFilePath);
|
|
6868
|
+
if (!fs31.existsSync(fullPath)) continue;
|
|
6869
|
+
const stat = fs31.statSync(fullPath);
|
|
6305
6870
|
if (stat.isDirectory()) return readMostRecentFileInDir(fullPath);
|
|
6306
|
-
return
|
|
6871
|
+
return fs31.readFileSync(fullPath, "utf-8");
|
|
6307
6872
|
}
|
|
6308
6873
|
return void 0;
|
|
6309
6874
|
}
|
|
@@ -6323,9 +6888,9 @@ function resolvePhaseState(metadata, projectPath, phase) {
|
|
|
6323
6888
|
}
|
|
6324
6889
|
function appendProjectState(content, metadata, projectPath, hasPathOpt) {
|
|
6325
6890
|
if (!metadata?.state.persistent || !hasPathOpt) return content;
|
|
6326
|
-
const stateFile =
|
|
6327
|
-
if (!
|
|
6328
|
-
const stateContent =
|
|
6891
|
+
const stateFile = path48.join(projectPath, ".harness", "state.json");
|
|
6892
|
+
if (!fs31.existsSync(stateFile)) return content;
|
|
6893
|
+
const stateContent = fs31.readFileSync(stateFile, "utf-8");
|
|
6329
6894
|
return content + `
|
|
6330
6895
|
|
|
6331
6896
|
---
|
|
@@ -6337,14 +6902,14 @@ ${stateContent}
|
|
|
6337
6902
|
}
|
|
6338
6903
|
async function runSkill(name, opts) {
|
|
6339
6904
|
const skillsDir = resolveSkillsDir();
|
|
6340
|
-
const skillDir =
|
|
6341
|
-
if (!
|
|
6905
|
+
const skillDir = path48.join(skillsDir, name);
|
|
6906
|
+
if (!fs31.existsSync(skillDir)) {
|
|
6342
6907
|
logger.error(`Skill not found: ${name}`);
|
|
6343
6908
|
process.exit(ExitCode.ERROR);
|
|
6344
6909
|
return;
|
|
6345
6910
|
}
|
|
6346
6911
|
const metadata = loadSkillMetadata(skillDir);
|
|
6347
|
-
const projectPath = opts.path ?
|
|
6912
|
+
const projectPath = opts.path ? path48.resolve(opts.path) : process.cwd();
|
|
6348
6913
|
const complexity = resolveComplexity(
|
|
6349
6914
|
metadata,
|
|
6350
6915
|
opts.complexity ?? "standard",
|
|
@@ -6371,14 +6936,14 @@ async function runSkill(name, opts) {
|
|
|
6371
6936
|
...stateWarning !== void 0 && { stateWarning },
|
|
6372
6937
|
...opts.party !== void 0 && { party: opts.party }
|
|
6373
6938
|
});
|
|
6374
|
-
const skillMdPath =
|
|
6375
|
-
if (!
|
|
6939
|
+
const skillMdPath = path48.join(skillDir, "SKILL.md");
|
|
6940
|
+
if (!fs31.existsSync(skillMdPath)) {
|
|
6376
6941
|
logger.error(`SKILL.md not found for skill: ${name}`);
|
|
6377
6942
|
process.exit(ExitCode.ERROR);
|
|
6378
6943
|
return;
|
|
6379
6944
|
}
|
|
6380
6945
|
const content = appendProjectState(
|
|
6381
|
-
|
|
6946
|
+
fs31.readFileSync(skillMdPath, "utf-8"),
|
|
6382
6947
|
metadata,
|
|
6383
6948
|
projectPath,
|
|
6384
6949
|
!!opts.path
|
|
@@ -6387,13 +6952,13 @@ async function runSkill(name, opts) {
|
|
|
6387
6952
|
process.exit(ExitCode.SUCCESS);
|
|
6388
6953
|
}
|
|
6389
6954
|
function createRunCommand2() {
|
|
6390
|
-
return new
|
|
6955
|
+
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
6956
|
}
|
|
6392
6957
|
|
|
6393
6958
|
// src/commands/skill/validate.ts
|
|
6394
|
-
import { Command as
|
|
6395
|
-
import * as
|
|
6396
|
-
import * as
|
|
6959
|
+
import { Command as Command60 } from "commander";
|
|
6960
|
+
import * as fs32 from "fs";
|
|
6961
|
+
import * as path49 from "path";
|
|
6397
6962
|
import { parse as parse3 } from "yaml";
|
|
6398
6963
|
var BEHAVIORAL_REQUIRED_SECTIONS = [
|
|
6399
6964
|
"## When to Use",
|
|
@@ -6405,11 +6970,11 @@ var BEHAVIORAL_REQUIRED_SECTIONS = [
|
|
|
6405
6970
|
];
|
|
6406
6971
|
var KNOWLEDGE_REQUIRED_SECTIONS = ["## Instructions"];
|
|
6407
6972
|
function validateSkillMd(name, skillMdPath, skillType, errors) {
|
|
6408
|
-
if (!
|
|
6973
|
+
if (!fs32.existsSync(skillMdPath)) {
|
|
6409
6974
|
errors.push(`${name}: missing SKILL.md`);
|
|
6410
6975
|
return;
|
|
6411
6976
|
}
|
|
6412
|
-
const mdContent =
|
|
6977
|
+
const mdContent = fs32.readFileSync(skillMdPath, "utf-8");
|
|
6413
6978
|
if (!mdContent.trim().startsWith("# ")) {
|
|
6414
6979
|
errors.push(`${name}/SKILL.md: must start with an h1 heading`);
|
|
6415
6980
|
}
|
|
@@ -6434,20 +6999,20 @@ function validateSkillMd(name, skillMdPath, skillType, errors) {
|
|
|
6434
6999
|
}
|
|
6435
7000
|
}
|
|
6436
7001
|
function validateSkillEntry(name, skillsDir, errors) {
|
|
6437
|
-
const skillDir =
|
|
6438
|
-
const yamlPath =
|
|
6439
|
-
if (!
|
|
7002
|
+
const skillDir = path49.join(skillsDir, name);
|
|
7003
|
+
const yamlPath = path49.join(skillDir, "skill.yaml");
|
|
7004
|
+
if (!fs32.existsSync(yamlPath)) {
|
|
6440
7005
|
errors.push(`${name}: missing skill.yaml`);
|
|
6441
7006
|
return false;
|
|
6442
7007
|
}
|
|
6443
7008
|
try {
|
|
6444
|
-
const raw =
|
|
7009
|
+
const raw = fs32.readFileSync(yamlPath, "utf-8");
|
|
6445
7010
|
const result = SkillMetadataSchema.safeParse(parse3(raw));
|
|
6446
7011
|
if (!result.success) {
|
|
6447
7012
|
errors.push(`${name}/skill.yaml: ${result.error.message}`);
|
|
6448
7013
|
return false;
|
|
6449
7014
|
}
|
|
6450
|
-
validateSkillMd(name,
|
|
7015
|
+
validateSkillMd(name, path49.join(skillDir, "SKILL.md"), result.data.type, errors);
|
|
6451
7016
|
return true;
|
|
6452
7017
|
} catch (e) {
|
|
6453
7018
|
errors.push(`${name}: parse error \u2014 ${e instanceof Error ? e.message : String(e)}`);
|
|
@@ -6455,15 +7020,15 @@ function validateSkillEntry(name, skillsDir, errors) {
|
|
|
6455
7020
|
}
|
|
6456
7021
|
}
|
|
6457
7022
|
function createValidateCommand2() {
|
|
6458
|
-
return new
|
|
7023
|
+
return new Command60("validate").description("Validate all skill.yaml files and SKILL.md structure").action(async (_opts, cmd) => {
|
|
6459
7024
|
const globalOpts = cmd.optsWithGlobals();
|
|
6460
7025
|
const skillsDir = resolveSkillsDir();
|
|
6461
|
-
if (!
|
|
7026
|
+
if (!fs32.existsSync(skillsDir)) {
|
|
6462
7027
|
logger.info("No skills directory found.");
|
|
6463
7028
|
process.exit(ExitCode.SUCCESS);
|
|
6464
7029
|
return;
|
|
6465
7030
|
}
|
|
6466
|
-
const entries =
|
|
7031
|
+
const entries = fs32.readdirSync(skillsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
|
|
6467
7032
|
const errors = [];
|
|
6468
7033
|
let validated = 0;
|
|
6469
7034
|
for (const name of entries) {
|
|
@@ -6485,24 +7050,24 @@ function createValidateCommand2() {
|
|
|
6485
7050
|
}
|
|
6486
7051
|
|
|
6487
7052
|
// src/commands/skill/info.ts
|
|
6488
|
-
import { Command as
|
|
6489
|
-
import * as
|
|
6490
|
-
import * as
|
|
7053
|
+
import { Command as Command61 } from "commander";
|
|
7054
|
+
import * as fs33 from "fs";
|
|
7055
|
+
import * as path50 from "path";
|
|
6491
7056
|
import { parse as parse4 } from "yaml";
|
|
6492
7057
|
function loadSkillMetadata2(name) {
|
|
6493
7058
|
const skillsDir = resolveSkillsDir();
|
|
6494
|
-
const skillDir =
|
|
6495
|
-
if (!
|
|
7059
|
+
const skillDir = path50.join(skillsDir, name);
|
|
7060
|
+
if (!fs33.existsSync(skillDir)) {
|
|
6496
7061
|
logger.error(`Skill not found: ${name}`);
|
|
6497
7062
|
return { ok: false, exitCode: ExitCode.ERROR };
|
|
6498
7063
|
}
|
|
6499
|
-
const yamlPath =
|
|
6500
|
-
if (!
|
|
7064
|
+
const yamlPath = path50.join(skillDir, "skill.yaml");
|
|
7065
|
+
if (!fs33.existsSync(yamlPath)) {
|
|
6501
7066
|
logger.error(`skill.yaml not found for skill: ${name}`);
|
|
6502
7067
|
return { ok: false, exitCode: ExitCode.ERROR };
|
|
6503
7068
|
}
|
|
6504
7069
|
try {
|
|
6505
|
-
const raw =
|
|
7070
|
+
const raw = fs33.readFileSync(yamlPath, "utf-8");
|
|
6506
7071
|
const parsed = parse4(raw);
|
|
6507
7072
|
const result = SkillMetadataSchema.safeParse(parsed);
|
|
6508
7073
|
if (!result.success) {
|
|
@@ -6531,7 +7096,7 @@ function printSkillInfo(skill) {
|
|
|
6531
7096
|
console.log(`Persistent: ${skill.state.persistent}`);
|
|
6532
7097
|
}
|
|
6533
7098
|
function createInfoCommand() {
|
|
6534
|
-
return new
|
|
7099
|
+
return new Command61("info").description("Show metadata for a skill").argument("<name>", "Skill name (e.g., harness-tdd)").action(async (name, _opts, cmd) => {
|
|
6535
7100
|
const globalOpts = cmd.optsWithGlobals();
|
|
6536
7101
|
const loaded = loadSkillMetadata2(name);
|
|
6537
7102
|
if (!loaded.ok) {
|
|
@@ -6548,7 +7113,7 @@ function createInfoCommand() {
|
|
|
6548
7113
|
}
|
|
6549
7114
|
|
|
6550
7115
|
// src/commands/skill/search.ts
|
|
6551
|
-
import { Command as
|
|
7116
|
+
import { Command as Command62 } from "commander";
|
|
6552
7117
|
async function runSearch(query, opts) {
|
|
6553
7118
|
const results = await searchNpmRegistry(query, opts.registry);
|
|
6554
7119
|
return results.filter((r) => {
|
|
@@ -6593,15 +7158,15 @@ async function runSearchAction(query, opts, globalOpts) {
|
|
|
6593
7158
|
}
|
|
6594
7159
|
}
|
|
6595
7160
|
function createSearchCommand() {
|
|
6596
|
-
return new
|
|
7161
|
+
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
7162
|
await runSearchAction(query, opts, cmd.optsWithGlobals());
|
|
6598
7163
|
});
|
|
6599
7164
|
}
|
|
6600
7165
|
|
|
6601
7166
|
// src/commands/skill/create.ts
|
|
6602
|
-
import { Command as
|
|
6603
|
-
import * as
|
|
6604
|
-
import * as
|
|
7167
|
+
import { Command as Command63 } from "commander";
|
|
7168
|
+
import * as path51 from "path";
|
|
7169
|
+
import * as fs34 from "fs";
|
|
6605
7170
|
import YAML from "yaml";
|
|
6606
7171
|
var KEBAB_CASE_RE = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/;
|
|
6607
7172
|
function buildReadme(name, description) {
|
|
@@ -6685,22 +7250,22 @@ function runCreate(name, opts) {
|
|
|
6685
7250
|
if (!KEBAB_CASE_RE.test(name)) {
|
|
6686
7251
|
throw new Error(`Invalid skill name "${name}". Must be kebab-case (e.g., my-skill).`);
|
|
6687
7252
|
}
|
|
6688
|
-
const baseDir = opts.outputDir ??
|
|
6689
|
-
const skillDir =
|
|
6690
|
-
if (
|
|
7253
|
+
const baseDir = opts.outputDir ?? path51.join(process.cwd(), "agents", "skills", "claude-code");
|
|
7254
|
+
const skillDir = path51.join(baseDir, name);
|
|
7255
|
+
if (fs34.existsSync(skillDir)) {
|
|
6691
7256
|
throw new Error(`Skill directory already exists: ${skillDir}`);
|
|
6692
7257
|
}
|
|
6693
|
-
|
|
7258
|
+
fs34.mkdirSync(skillDir, { recursive: true });
|
|
6694
7259
|
const description = opts.description || `A community skill: ${name}`;
|
|
6695
7260
|
const skillYaml = buildSkillYaml(name, opts);
|
|
6696
|
-
const skillYamlPath =
|
|
6697
|
-
|
|
7261
|
+
const skillYamlPath = path51.join(skillDir, "skill.yaml");
|
|
7262
|
+
fs34.writeFileSync(skillYamlPath, YAML.stringify(skillYaml));
|
|
6698
7263
|
const skillMd = buildSkillMd(name, description);
|
|
6699
|
-
const skillMdPath =
|
|
6700
|
-
|
|
7264
|
+
const skillMdPath = path51.join(skillDir, "SKILL.md");
|
|
7265
|
+
fs34.writeFileSync(skillMdPath, skillMd);
|
|
6701
7266
|
const readme = buildReadme(name, description);
|
|
6702
|
-
const readmePath =
|
|
6703
|
-
|
|
7267
|
+
const readmePath = path51.join(skillDir, "README.md");
|
|
7268
|
+
fs34.writeFileSync(readmePath, readme);
|
|
6704
7269
|
return {
|
|
6705
7270
|
name,
|
|
6706
7271
|
directory: skillDir,
|
|
@@ -6714,12 +7279,12 @@ function printCreateResult(name, result) {
|
|
|
6714
7279
|
}
|
|
6715
7280
|
logger.info(`
|
|
6716
7281
|
Next steps:`);
|
|
6717
|
-
logger.info(` 1. Edit ${
|
|
7282
|
+
logger.info(` 1. Edit ${path51.join(result.directory, "SKILL.md")} with your skill content`);
|
|
6718
7283
|
logger.info(` 2. Run: harness skill validate ${name}`);
|
|
6719
7284
|
logger.info(` 3. Run: harness skills publish`);
|
|
6720
7285
|
}
|
|
6721
7286
|
function createCreateCommand() {
|
|
6722
|
-
return new
|
|
7287
|
+
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
7288
|
const globalOpts = cmd.optsWithGlobals();
|
|
6724
7289
|
try {
|
|
6725
7290
|
const result = runCreate(name, {
|
|
@@ -6742,26 +7307,26 @@ function createCreateCommand() {
|
|
|
6742
7307
|
}
|
|
6743
7308
|
|
|
6744
7309
|
// src/commands/skill/publish.ts
|
|
6745
|
-
import { Command as
|
|
6746
|
-
import * as
|
|
6747
|
-
import * as
|
|
7310
|
+
import { Command as Command64 } from "commander";
|
|
7311
|
+
import * as fs36 from "fs";
|
|
7312
|
+
import * as path53 from "path";
|
|
6748
7313
|
import { execFileSync as execFileSync5 } from "child_process";
|
|
6749
7314
|
|
|
6750
7315
|
// src/registry/validator.ts
|
|
6751
|
-
import * as
|
|
6752
|
-
import * as
|
|
7316
|
+
import * as fs35 from "fs";
|
|
7317
|
+
import * as path52 from "path";
|
|
6753
7318
|
import { parse as parse5 } from "yaml";
|
|
6754
7319
|
import semver4 from "semver";
|
|
6755
7320
|
async function validateForPublish(skillDir, registryUrl) {
|
|
6756
7321
|
const errors = [];
|
|
6757
|
-
const skillYamlPath =
|
|
6758
|
-
if (!
|
|
7322
|
+
const skillYamlPath = path52.join(skillDir, "skill.yaml");
|
|
7323
|
+
if (!fs35.existsSync(skillYamlPath)) {
|
|
6759
7324
|
errors.push("skill.yaml not found. Create one with: harness skill create <name>");
|
|
6760
7325
|
return { valid: false, errors };
|
|
6761
7326
|
}
|
|
6762
7327
|
let skillMeta;
|
|
6763
7328
|
try {
|
|
6764
|
-
const raw =
|
|
7329
|
+
const raw = fs35.readFileSync(skillYamlPath, "utf-8");
|
|
6765
7330
|
const parsed = parse5(raw);
|
|
6766
7331
|
const result = SkillMetadataSchema.safeParse(parsed);
|
|
6767
7332
|
if (!result.success) {
|
|
@@ -6783,11 +7348,11 @@ async function validateForPublish(skillDir, registryUrl) {
|
|
|
6783
7348
|
if (!skillMeta.triggers || skillMeta.triggers.length === 0) {
|
|
6784
7349
|
errors.push("At least one trigger is required. Add triggers to skill.yaml.");
|
|
6785
7350
|
}
|
|
6786
|
-
const skillMdPath =
|
|
6787
|
-
if (!
|
|
7351
|
+
const skillMdPath = path52.join(skillDir, "SKILL.md");
|
|
7352
|
+
if (!fs35.existsSync(skillMdPath)) {
|
|
6788
7353
|
errors.push("SKILL.md not found. Create it with content describing your skill.");
|
|
6789
7354
|
} else {
|
|
6790
|
-
const content =
|
|
7355
|
+
const content = fs35.readFileSync(skillMdPath, "utf-8");
|
|
6791
7356
|
if (!content.includes("## When to Use")) {
|
|
6792
7357
|
errors.push('SKILL.md must contain a "## When to Use" section.');
|
|
6793
7358
|
}
|
|
@@ -6867,11 +7432,11 @@ ${errorList}`);
|
|
|
6867
7432
|
}
|
|
6868
7433
|
const meta = validation.skillMeta;
|
|
6869
7434
|
const pkg = derivePackageJson(meta);
|
|
6870
|
-
const pkgPath =
|
|
6871
|
-
|
|
6872
|
-
const readmePath =
|
|
6873
|
-
if (!
|
|
6874
|
-
const skillMdContent =
|
|
7435
|
+
const pkgPath = path53.join(skillDir, "package.json");
|
|
7436
|
+
fs36.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
|
|
7437
|
+
const readmePath = path53.join(skillDir, "README.md");
|
|
7438
|
+
if (!fs36.existsSync(readmePath)) {
|
|
7439
|
+
const skillMdContent = fs36.readFileSync(path53.join(skillDir, "SKILL.md"), "utf-8");
|
|
6875
7440
|
const readme = `# ${pkg.name}
|
|
6876
7441
|
|
|
6877
7442
|
${meta.description}
|
|
@@ -6885,7 +7450,7 @@ harness install ${meta.name}
|
|
|
6885
7450
|
---
|
|
6886
7451
|
|
|
6887
7452
|
${skillMdContent}`;
|
|
6888
|
-
|
|
7453
|
+
fs36.writeFileSync(readmePath, readme);
|
|
6889
7454
|
}
|
|
6890
7455
|
if (opts.dryRun) {
|
|
6891
7456
|
return {
|
|
@@ -6911,7 +7476,7 @@ ${skillMdContent}`;
|
|
|
6911
7476
|
};
|
|
6912
7477
|
}
|
|
6913
7478
|
function createPublishCommand() {
|
|
6914
|
-
return new
|
|
7479
|
+
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
7480
|
const globalOpts = cmd.optsWithGlobals();
|
|
6916
7481
|
const skillDir = opts.dir || process.cwd();
|
|
6917
7482
|
try {
|
|
@@ -6936,7 +7501,7 @@ function createPublishCommand() {
|
|
|
6936
7501
|
|
|
6937
7502
|
// src/commands/skill/index.ts
|
|
6938
7503
|
function createSkillCommand() {
|
|
6939
|
-
const command = new
|
|
7504
|
+
const command = new Command65("skill").description("Skill management commands");
|
|
6940
7505
|
command.addCommand(createListCommand3());
|
|
6941
7506
|
command.addCommand(createRunCommand2());
|
|
6942
7507
|
command.addCommand(createValidateCommand2());
|
|
@@ -6948,9 +7513,10 @@ function createSkillCommand() {
|
|
|
6948
7513
|
}
|
|
6949
7514
|
|
|
6950
7515
|
// src/commands/snapshot.ts
|
|
6951
|
-
|
|
7516
|
+
init_dist();
|
|
7517
|
+
import { Command as Command66 } from "commander";
|
|
6952
7518
|
import { execSync as execSync5 } from "child_process";
|
|
6953
|
-
import
|
|
7519
|
+
import chalk10 from "chalk";
|
|
6954
7520
|
function getCommitHash3(cwd) {
|
|
6955
7521
|
try {
|
|
6956
7522
|
return execSync5("git rev-parse --short HEAD", { cwd, encoding: "utf-8" }).toString().trim();
|
|
@@ -6975,9 +7541,9 @@ function formatDelta(delta) {
|
|
|
6975
7541
|
function directionSymbol(direction) {
|
|
6976
7542
|
switch (direction) {
|
|
6977
7543
|
case "improving":
|
|
6978
|
-
return
|
|
7544
|
+
return chalk10.green("improving");
|
|
6979
7545
|
case "declining":
|
|
6980
|
-
return
|
|
7546
|
+
return chalk10.red("declining");
|
|
6981
7547
|
case "stable":
|
|
6982
7548
|
return "=";
|
|
6983
7549
|
}
|
|
@@ -7130,7 +7696,7 @@ function registerListCommand(parent) {
|
|
|
7130
7696
|
});
|
|
7131
7697
|
}
|
|
7132
7698
|
function createSnapshotCommand() {
|
|
7133
|
-
const command = new
|
|
7699
|
+
const command = new Command66("snapshot").description("Architecture timeline snapshot commands");
|
|
7134
7700
|
registerCaptureCommand(command);
|
|
7135
7701
|
registerTrendsCommand(command);
|
|
7136
7702
|
registerListCommand(command);
|
|
@@ -7138,11 +7704,12 @@ function createSnapshotCommand() {
|
|
|
7138
7704
|
}
|
|
7139
7705
|
|
|
7140
7706
|
// src/commands/state/index.ts
|
|
7141
|
-
import { Command as
|
|
7707
|
+
import { Command as Command71 } from "commander";
|
|
7142
7708
|
|
|
7143
7709
|
// src/commands/state/show.ts
|
|
7144
|
-
|
|
7145
|
-
import
|
|
7710
|
+
init_dist();
|
|
7711
|
+
import { Command as Command67 } from "commander";
|
|
7712
|
+
import * as path54 from "path";
|
|
7146
7713
|
function printStateText(state, stream) {
|
|
7147
7714
|
if (stream) console.log(`Stream: ${stream}`);
|
|
7148
7715
|
console.log(`Schema Version: ${state.schemaVersion}`);
|
|
@@ -7166,9 +7733,9 @@ function printStateProgress(progress) {
|
|
|
7166
7733
|
}
|
|
7167
7734
|
}
|
|
7168
7735
|
function createShowCommand() {
|
|
7169
|
-
return new
|
|
7736
|
+
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
7737
|
const globalOpts = cmd.optsWithGlobals();
|
|
7171
|
-
const result = await loadState(
|
|
7738
|
+
const result = await loadState(path54.resolve(opts.path), opts.stream);
|
|
7172
7739
|
if (!result.ok) {
|
|
7173
7740
|
logger.error(result.error.message);
|
|
7174
7741
|
process.exit(ExitCode.ERROR);
|
|
@@ -7183,10 +7750,11 @@ function createShowCommand() {
|
|
|
7183
7750
|
}
|
|
7184
7751
|
|
|
7185
7752
|
// src/commands/state/reset.ts
|
|
7186
|
-
|
|
7187
|
-
import
|
|
7188
|
-
import * as
|
|
7189
|
-
import * as
|
|
7753
|
+
init_dist();
|
|
7754
|
+
import { Command as Command68 } from "commander";
|
|
7755
|
+
import * as fs37 from "fs";
|
|
7756
|
+
import * as path55 from "path";
|
|
7757
|
+
import * as readline2 from "readline";
|
|
7190
7758
|
async function resolveStatePath(projectPath, stream) {
|
|
7191
7759
|
if (stream) {
|
|
7192
7760
|
const streamResult = await resolveStreamPath(projectPath, { stream });
|
|
@@ -7194,22 +7762,22 @@ async function resolveStatePath(projectPath, stream) {
|
|
|
7194
7762
|
logger.error(streamResult.error.message);
|
|
7195
7763
|
process.exit(ExitCode.ERROR);
|
|
7196
7764
|
}
|
|
7197
|
-
return
|
|
7765
|
+
return path55.join(streamResult.value, "state.json");
|
|
7198
7766
|
}
|
|
7199
|
-
return
|
|
7767
|
+
return path55.join(projectPath, ".harness", "state.json");
|
|
7200
7768
|
}
|
|
7201
7769
|
async function confirmReset() {
|
|
7202
|
-
const rl =
|
|
7203
|
-
const answer = await new Promise((
|
|
7204
|
-
rl.question("Reset project state? This cannot be undone. [y/N] ",
|
|
7770
|
+
const rl = readline2.createInterface({ input: process.stdin, output: process.stdout });
|
|
7771
|
+
const answer = await new Promise((resolve35) => {
|
|
7772
|
+
rl.question("Reset project state? This cannot be undone. [y/N] ", resolve35);
|
|
7205
7773
|
});
|
|
7206
7774
|
rl.close();
|
|
7207
7775
|
return answer.toLowerCase() === "y" || answer.toLowerCase() === "yes";
|
|
7208
7776
|
}
|
|
7209
7777
|
async function runReset(opts) {
|
|
7210
|
-
const projectPath =
|
|
7778
|
+
const projectPath = path55.resolve(opts.path);
|
|
7211
7779
|
const statePath = await resolveStatePath(projectPath, opts.stream);
|
|
7212
|
-
if (!
|
|
7780
|
+
if (!fs37.existsSync(statePath)) {
|
|
7213
7781
|
logger.info("No state file found. Nothing to reset.");
|
|
7214
7782
|
process.exit(ExitCode.SUCCESS);
|
|
7215
7783
|
}
|
|
@@ -7218,7 +7786,7 @@ async function runReset(opts) {
|
|
|
7218
7786
|
process.exit(ExitCode.SUCCESS);
|
|
7219
7787
|
}
|
|
7220
7788
|
try {
|
|
7221
|
-
|
|
7789
|
+
fs37.unlinkSync(statePath);
|
|
7222
7790
|
logger.success("Project state reset.");
|
|
7223
7791
|
} catch (e) {
|
|
7224
7792
|
logger.error(`Failed to reset state: ${e instanceof Error ? e.message : String(e)}`);
|
|
@@ -7227,15 +7795,16 @@ async function runReset(opts) {
|
|
|
7227
7795
|
process.exit(ExitCode.SUCCESS);
|
|
7228
7796
|
}
|
|
7229
7797
|
function createResetCommand() {
|
|
7230
|
-
return new
|
|
7798
|
+
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
7799
|
}
|
|
7232
7800
|
|
|
7233
7801
|
// src/commands/state/learn.ts
|
|
7234
|
-
|
|
7235
|
-
import
|
|
7802
|
+
init_dist();
|
|
7803
|
+
import { Command as Command69 } from "commander";
|
|
7804
|
+
import * as path56 from "path";
|
|
7236
7805
|
function createLearnCommand() {
|
|
7237
|
-
return new
|
|
7238
|
-
const projectPath =
|
|
7806
|
+
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) => {
|
|
7807
|
+
const projectPath = path56.resolve(opts.path);
|
|
7239
7808
|
const result = await appendLearning(projectPath, message, void 0, void 0, opts.stream);
|
|
7240
7809
|
if (!result.ok) {
|
|
7241
7810
|
logger.error(result.error.message);
|
|
@@ -7248,10 +7817,11 @@ function createLearnCommand() {
|
|
|
7248
7817
|
}
|
|
7249
7818
|
|
|
7250
7819
|
// src/commands/state/streams.ts
|
|
7251
|
-
|
|
7252
|
-
import
|
|
7820
|
+
init_dist();
|
|
7821
|
+
import { Command as Command70 } from "commander";
|
|
7822
|
+
import * as path57 from "path";
|
|
7253
7823
|
async function runListStreams(opts, globalOpts) {
|
|
7254
|
-
const projectPath =
|
|
7824
|
+
const projectPath = path57.resolve(opts.path);
|
|
7255
7825
|
const indexResult = await loadStreamIndex(projectPath);
|
|
7256
7826
|
const result = await listStreams(projectPath);
|
|
7257
7827
|
if (!result.ok) {
|
|
@@ -7272,7 +7842,7 @@ async function runListStreams(opts, globalOpts) {
|
|
|
7272
7842
|
process.exit(ExitCode.SUCCESS);
|
|
7273
7843
|
}
|
|
7274
7844
|
async function runCreateStream(name, opts) {
|
|
7275
|
-
const result = await createStream(
|
|
7845
|
+
const result = await createStream(path57.resolve(opts.path), name, opts.branch);
|
|
7276
7846
|
if (!result.ok) {
|
|
7277
7847
|
logger.error(result.error.message);
|
|
7278
7848
|
process.exit(ExitCode.ERROR);
|
|
@@ -7281,7 +7851,7 @@ async function runCreateStream(name, opts) {
|
|
|
7281
7851
|
process.exit(ExitCode.SUCCESS);
|
|
7282
7852
|
}
|
|
7283
7853
|
async function runArchiveStream(name, opts) {
|
|
7284
|
-
const result = await archiveStream(
|
|
7854
|
+
const result = await archiveStream(path57.resolve(opts.path), name);
|
|
7285
7855
|
if (!result.ok) {
|
|
7286
7856
|
logger.error(result.error.message);
|
|
7287
7857
|
process.exit(ExitCode.ERROR);
|
|
@@ -7290,7 +7860,7 @@ async function runArchiveStream(name, opts) {
|
|
|
7290
7860
|
process.exit(ExitCode.SUCCESS);
|
|
7291
7861
|
}
|
|
7292
7862
|
async function runActivateStream(name, opts) {
|
|
7293
|
-
const result = await setActiveStream(
|
|
7863
|
+
const result = await setActiveStream(path57.resolve(opts.path), name);
|
|
7294
7864
|
if (!result.ok) {
|
|
7295
7865
|
logger.error(result.error.message);
|
|
7296
7866
|
process.exit(ExitCode.ERROR);
|
|
@@ -7299,7 +7869,7 @@ async function runActivateStream(name, opts) {
|
|
|
7299
7869
|
process.exit(ExitCode.SUCCESS);
|
|
7300
7870
|
}
|
|
7301
7871
|
function createStreamsCommand() {
|
|
7302
|
-
const command = new
|
|
7872
|
+
const command = new Command70("streams").description("Manage state streams");
|
|
7303
7873
|
command.command("list").description("List all known streams").option("--path <path>", "Project root path", ".").action(async (opts, cmd) => runListStreams(opts, cmd.optsWithGlobals()));
|
|
7304
7874
|
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
7875
|
command.command("archive <name>").description("Archive a stream").option("--path <path>", "Project root path", ".").action(async (name, opts) => runArchiveStream(name, opts));
|
|
@@ -7309,7 +7879,7 @@ function createStreamsCommand() {
|
|
|
7309
7879
|
|
|
7310
7880
|
// src/commands/state/index.ts
|
|
7311
7881
|
function createStateCommand() {
|
|
7312
|
-
const command = new
|
|
7882
|
+
const command = new Command71("state").description("Project state management commands");
|
|
7313
7883
|
command.addCommand(createShowCommand());
|
|
7314
7884
|
command.addCommand(createResetCommand());
|
|
7315
7885
|
command.addCommand(createLearnCommand());
|
|
@@ -7317,8 +7887,154 @@ function createStateCommand() {
|
|
|
7317
7887
|
return command;
|
|
7318
7888
|
}
|
|
7319
7889
|
|
|
7890
|
+
// src/commands/sync-analyses.ts
|
|
7891
|
+
import * as fs38 from "fs";
|
|
7892
|
+
import * as path58 from "path";
|
|
7893
|
+
import { Command as Command72 } from "commander";
|
|
7894
|
+
init_dist();
|
|
7895
|
+
function isValidRecord(record) {
|
|
7896
|
+
return typeof record.issueId === "string" && !!record.issueId && typeof record.identifier === "string" && !!record.identifier && typeof record.analyzedAt === "string" && !/[/\\]/.test(record.issueId);
|
|
7897
|
+
}
|
|
7898
|
+
function extractAnalysisFromComments(comments) {
|
|
7899
|
+
const sorted = [...comments].sort(
|
|
7900
|
+
(a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
|
|
7901
|
+
);
|
|
7902
|
+
for (const comment of sorted) {
|
|
7903
|
+
const fenceRegex = /```json\s*\r?\n([\s\S]*?)\r?\n```/g;
|
|
7904
|
+
let match;
|
|
7905
|
+
while ((match = fenceRegex.exec(comment.body)) !== null) {
|
|
7906
|
+
try {
|
|
7907
|
+
const parsed = JSON.parse(match[1]);
|
|
7908
|
+
if (parsed._harness_analysis !== true) continue;
|
|
7909
|
+
const { _harness_analysis, _version, ...record } = parsed;
|
|
7910
|
+
if (isValidRecord(record)) return record;
|
|
7911
|
+
} catch {
|
|
7912
|
+
continue;
|
|
7913
|
+
}
|
|
7914
|
+
}
|
|
7915
|
+
}
|
|
7916
|
+
return null;
|
|
7917
|
+
}
|
|
7918
|
+
function bootstrapTrackerCommand2(opts, verb) {
|
|
7919
|
+
const projectPath = path58.resolve(opts.dir);
|
|
7920
|
+
const trackerConfig = loadTrackerSyncConfig(projectPath);
|
|
7921
|
+
if (!trackerConfig) {
|
|
7922
|
+
logger.error(`No tracker config found in harness.config.json. Cannot ${verb}.`);
|
|
7923
|
+
return null;
|
|
7924
|
+
}
|
|
7925
|
+
if (trackerConfig.kind !== "github") {
|
|
7926
|
+
logger.error(
|
|
7927
|
+
`${verb} currently only supports 'github' tracker kind. Found: ${String(trackerConfig.kind)}`
|
|
7928
|
+
);
|
|
7929
|
+
return null;
|
|
7930
|
+
}
|
|
7931
|
+
const projectEnvPath = path58.join(projectPath, ".env");
|
|
7932
|
+
if (fs38.existsSync(projectEnvPath) && !process.env.GITHUB_TOKEN) {
|
|
7933
|
+
__require("dotenv").config({ path: projectEnvPath });
|
|
7934
|
+
}
|
|
7935
|
+
const token = process.env.GITHUB_TOKEN;
|
|
7936
|
+
if (!token) {
|
|
7937
|
+
logger.error("No GITHUB_TOKEN environment variable found.");
|
|
7938
|
+
return null;
|
|
7939
|
+
}
|
|
7940
|
+
return { token, projectPath, trackerConfig };
|
|
7941
|
+
}
|
|
7942
|
+
function loadRoadmapFeatures(projectPath) {
|
|
7943
|
+
const roadmapFile = path58.join(projectPath, "docs", "roadmap.md");
|
|
7944
|
+
if (!fs38.existsSync(roadmapFile)) {
|
|
7945
|
+
logger.error("No docs/roadmap.md found. Cannot discover features with externalIds.");
|
|
7946
|
+
return null;
|
|
7947
|
+
}
|
|
7948
|
+
const { parseRoadmap } = (init_dist(), __toCommonJS(dist_exports));
|
|
7949
|
+
const roadmapParsed = parseRoadmap(fs38.readFileSync(roadmapFile, "utf-8"));
|
|
7950
|
+
if (!roadmapParsed.ok) {
|
|
7951
|
+
logger.error("Failed to parse docs/roadmap.md");
|
|
7952
|
+
return null;
|
|
7953
|
+
}
|
|
7954
|
+
const features = [];
|
|
7955
|
+
for (const milestone of roadmapParsed.value.milestones) {
|
|
7956
|
+
for (const feature of milestone.features) {
|
|
7957
|
+
if (feature.externalId) {
|
|
7958
|
+
features.push({ name: feature.name, externalId: feature.externalId });
|
|
7959
|
+
}
|
|
7960
|
+
}
|
|
7961
|
+
}
|
|
7962
|
+
return features;
|
|
7963
|
+
}
|
|
7964
|
+
async function syncFeatureAnalyses(features, adapter, archive, publishedIndex) {
|
|
7965
|
+
let syncedCount = 0;
|
|
7966
|
+
let skippedCount = 0;
|
|
7967
|
+
let warnCount = 0;
|
|
7968
|
+
for (const { name, externalId } of features) {
|
|
7969
|
+
const commentsResult = await adapter.fetchComments(externalId);
|
|
7970
|
+
if (!commentsResult.ok) {
|
|
7971
|
+
logger.warn(
|
|
7972
|
+
`Failed to fetch comments for "${name}" (${externalId}): ${commentsResult.error.message}`
|
|
7973
|
+
);
|
|
7974
|
+
warnCount++;
|
|
7975
|
+
continue;
|
|
7976
|
+
}
|
|
7977
|
+
const record = extractAnalysisFromComments(commentsResult.value);
|
|
7978
|
+
if (!record) {
|
|
7979
|
+
skippedCount++;
|
|
7980
|
+
continue;
|
|
7981
|
+
}
|
|
7982
|
+
await archive.save(record);
|
|
7983
|
+
publishedIndex[record.issueId] = record.analyzedAt;
|
|
7984
|
+
syncedCount++;
|
|
7985
|
+
logger.info(`Synced analysis for "${name}" (${record.identifier})`);
|
|
7986
|
+
}
|
|
7987
|
+
return { syncedCount, skippedCount, warnCount };
|
|
7988
|
+
}
|
|
7989
|
+
async function runSyncAnalyses(opts) {
|
|
7990
|
+
const bootstrap = bootstrapTrackerCommand2(opts, "sync");
|
|
7991
|
+
if (!bootstrap) return process.exit(1);
|
|
7992
|
+
const { token, projectPath, trackerConfig } = bootstrap;
|
|
7993
|
+
const features = loadRoadmapFeatures(projectPath);
|
|
7994
|
+
if (!features) return process.exit(1);
|
|
7995
|
+
if (features.length === 0) {
|
|
7996
|
+
logger.info("No roadmap features have externalIds. Nothing to sync.");
|
|
7997
|
+
return;
|
|
7998
|
+
}
|
|
7999
|
+
const { AnalysisArchive, loadPublishedIndex: loadPublishedIndex2, savePublishedIndex: savePublishedIndex2 } = await import("@harness-engineering/orchestrator");
|
|
8000
|
+
const { GitHubIssuesSyncAdapter: GitHubIssuesSyncAdapter2 } = await import("./dist-WCSJHQPK.js");
|
|
8001
|
+
const adapter = new GitHubIssuesSyncAdapter2({ token, config: trackerConfig });
|
|
8002
|
+
const archive = new AnalysisArchive(path58.join(projectPath, ".harness", "analyses"));
|
|
8003
|
+
const publishedIndex = loadPublishedIndex2(projectPath);
|
|
8004
|
+
const { syncedCount, skippedCount, warnCount } = await syncFeatureAnalyses(
|
|
8005
|
+
features,
|
|
8006
|
+
adapter,
|
|
8007
|
+
archive,
|
|
8008
|
+
publishedIndex
|
|
8009
|
+
);
|
|
8010
|
+
if (syncedCount > 0) {
|
|
8011
|
+
savePublishedIndex2(projectPath, publishedIndex);
|
|
8012
|
+
logger.success(
|
|
8013
|
+
`Synced ${syncedCount} analysis record(s). Skipped ${skippedCount} (no analysis). Warnings: ${warnCount}.`
|
|
8014
|
+
);
|
|
8015
|
+
} else {
|
|
8016
|
+
logger.info(
|
|
8017
|
+
`No new analyses found on tracker. Skipped ${skippedCount} feature(s) with no analysis comments.`
|
|
8018
|
+
);
|
|
8019
|
+
}
|
|
8020
|
+
}
|
|
8021
|
+
function createSyncAnalysesCommand() {
|
|
8022
|
+
const command = new Command72("sync-analyses").description(
|
|
8023
|
+
"Pull published intelligence analyses from the external issue tracker into the local .harness/analyses/ directory"
|
|
8024
|
+
).option("-d, --dir <path>", "Workspace directory", process.cwd()).action(async (opts) => {
|
|
8025
|
+
try {
|
|
8026
|
+
await runSyncAnalyses(opts);
|
|
8027
|
+
} catch (err) {
|
|
8028
|
+
logger.error(`Error syncing analyses: ${err instanceof Error ? err.message : String(err)}`);
|
|
8029
|
+
process.exit(1);
|
|
8030
|
+
}
|
|
8031
|
+
});
|
|
8032
|
+
return command;
|
|
8033
|
+
}
|
|
8034
|
+
|
|
7320
8035
|
// src/commands/taint.ts
|
|
7321
|
-
|
|
8036
|
+
init_dist();
|
|
8037
|
+
import { Command as Command73 } from "commander";
|
|
7322
8038
|
function getProjectRoot() {
|
|
7323
8039
|
return process.cwd();
|
|
7324
8040
|
}
|
|
@@ -7390,27 +8106,28 @@ function registerStatusCommand(taint) {
|
|
|
7390
8106
|
});
|
|
7391
8107
|
}
|
|
7392
8108
|
function createTaintCommand() {
|
|
7393
|
-
const taint = new
|
|
8109
|
+
const taint = new Command73("taint").description("Manage sentinel session taint state");
|
|
7394
8110
|
registerClearCommand(taint);
|
|
7395
8111
|
registerStatusCommand(taint);
|
|
7396
8112
|
return taint;
|
|
7397
8113
|
}
|
|
7398
8114
|
|
|
7399
8115
|
// src/commands/telemetry/index.ts
|
|
7400
|
-
import { Command as
|
|
8116
|
+
import { Command as Command76 } from "commander";
|
|
7401
8117
|
|
|
7402
8118
|
// src/commands/telemetry/identify.ts
|
|
7403
|
-
|
|
7404
|
-
import
|
|
7405
|
-
import * as
|
|
8119
|
+
init_dist();
|
|
8120
|
+
import { Command as Command74 } from "commander";
|
|
8121
|
+
import * as fs39 from "fs";
|
|
8122
|
+
import * as path59 from "path";
|
|
7406
8123
|
function telemetryFilePath(cwd) {
|
|
7407
|
-
return
|
|
8124
|
+
return path59.join(cwd, ".harness", "telemetry.json");
|
|
7408
8125
|
}
|
|
7409
8126
|
function writeTelemetryFile(cwd, data) {
|
|
7410
8127
|
const filePath = telemetryFilePath(cwd);
|
|
7411
|
-
const dir =
|
|
7412
|
-
|
|
7413
|
-
|
|
8128
|
+
const dir = path59.dirname(filePath);
|
|
8129
|
+
fs39.mkdirSync(dir, { recursive: true });
|
|
8130
|
+
fs39.writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n", "utf-8");
|
|
7414
8131
|
}
|
|
7415
8132
|
var IDENTITY_FIELDS = ["project", "team", "alias"];
|
|
7416
8133
|
function applyIdentityFields(opts, identity) {
|
|
@@ -7424,7 +8141,7 @@ function printIdentity(identity) {
|
|
|
7424
8141
|
}
|
|
7425
8142
|
}
|
|
7426
8143
|
function createIdentifyCommand() {
|
|
7427
|
-
const cmd = new
|
|
8144
|
+
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
8145
|
const cwd = process.cwd();
|
|
7429
8146
|
if (opts.clear) {
|
|
7430
8147
|
writeTelemetryFile(cwd, { identity: {} });
|
|
@@ -7448,7 +8165,8 @@ function createIdentifyCommand() {
|
|
|
7448
8165
|
}
|
|
7449
8166
|
|
|
7450
8167
|
// src/commands/telemetry/status.ts
|
|
7451
|
-
|
|
8168
|
+
init_dist();
|
|
8169
|
+
import { Command as Command75 } from "commander";
|
|
7452
8170
|
function gatherEnvOverrides() {
|
|
7453
8171
|
const overrides = {};
|
|
7454
8172
|
if (process.env.DO_NOT_TRACK) overrides.DO_NOT_TRACK = process.env.DO_NOT_TRACK;
|
|
@@ -7486,7 +8204,7 @@ function printHumanStatus(result) {
|
|
|
7486
8204
|
}
|
|
7487
8205
|
}
|
|
7488
8206
|
function createStatusCommand() {
|
|
7489
|
-
const cmd = new
|
|
8207
|
+
const cmd = new Command75("status").description("Show current telemetry consent state, install ID, and identity").option("--json", "Output as JSON").action((opts) => {
|
|
7490
8208
|
const cwd = process.cwd();
|
|
7491
8209
|
const envOverrides = gatherEnvOverrides();
|
|
7492
8210
|
const consent = resolveConsent(cwd, void 0);
|
|
@@ -7519,15 +8237,15 @@ function createStatusCommand() {
|
|
|
7519
8237
|
|
|
7520
8238
|
// src/commands/telemetry/index.ts
|
|
7521
8239
|
function createTelemetryCommand() {
|
|
7522
|
-
const command = new
|
|
8240
|
+
const command = new Command76("telemetry").description("Telemetry identity and status management");
|
|
7523
8241
|
command.addCommand(createIdentifyCommand());
|
|
7524
8242
|
command.addCommand(createStatusCommand());
|
|
7525
8243
|
return command;
|
|
7526
8244
|
}
|
|
7527
8245
|
|
|
7528
8246
|
// src/commands/traceability.ts
|
|
7529
|
-
import { Command as
|
|
7530
|
-
import
|
|
8247
|
+
import { Command as Command77 } from "commander";
|
|
8248
|
+
import chalk11 from "chalk";
|
|
7531
8249
|
function confidenceLabel(maxConfidence) {
|
|
7532
8250
|
if (maxConfidence >= 0.8) return "explicit";
|
|
7533
8251
|
if (maxConfidence > 0) return "inferred";
|
|
@@ -7536,13 +8254,13 @@ function confidenceLabel(maxConfidence) {
|
|
|
7536
8254
|
function statusIcon(status) {
|
|
7537
8255
|
switch (status) {
|
|
7538
8256
|
case "full":
|
|
7539
|
-
return
|
|
8257
|
+
return chalk11.green("\u2713 full");
|
|
7540
8258
|
case "code-only":
|
|
7541
|
-
return
|
|
8259
|
+
return chalk11.yellow("\u25D0 code-only");
|
|
7542
8260
|
case "test-only":
|
|
7543
|
-
return
|
|
8261
|
+
return chalk11.yellow("\u25D0 test-only");
|
|
7544
8262
|
case "none":
|
|
7545
|
-
return
|
|
8263
|
+
return chalk11.red("\u2717 none");
|
|
7546
8264
|
default:
|
|
7547
8265
|
return status;
|
|
7548
8266
|
}
|
|
@@ -7584,14 +8302,14 @@ function handleEmptyResults(mode) {
|
|
|
7584
8302
|
}
|
|
7585
8303
|
function printResultTable(result, mode) {
|
|
7586
8304
|
const specLabel = result.specPath || result.featureName || "Unknown";
|
|
7587
|
-
console.log(` ${
|
|
8305
|
+
console.log(` ${chalk11.cyan(specLabel)} ${chalk11.dim(`(${result.summary.total} requirements)`)}`);
|
|
7588
8306
|
console.log("");
|
|
7589
8307
|
const numWidth = 4;
|
|
7590
8308
|
const nameWidth = mode === OutputMode.VERBOSE ? 44 : 36;
|
|
7591
8309
|
const codeWidth = 6;
|
|
7592
8310
|
const testWidth = 7;
|
|
7593
8311
|
const confWidth = 12;
|
|
7594
|
-
const header =
|
|
8312
|
+
const header = chalk11.dim(
|
|
7595
8313
|
` ${pad("#", numWidth)}${pad("Requirement", nameWidth)}${pad("Code", codeWidth)}${pad("Tests", testWidth)}${pad("Confidence", confWidth)}Status`
|
|
7596
8314
|
);
|
|
7597
8315
|
console.log(header);
|
|
@@ -7620,10 +8338,10 @@ function printRequirementRows(requirements, mode, cols) {
|
|
|
7620
8338
|
);
|
|
7621
8339
|
if (mode === OutputMode.VERBOSE) {
|
|
7622
8340
|
for (const f of req.codeFiles) {
|
|
7623
|
-
console.log(
|
|
8341
|
+
console.log(chalk11.dim(` code: ${f.path} (${f.method})`));
|
|
7624
8342
|
}
|
|
7625
8343
|
for (const f of req.testFiles) {
|
|
7626
|
-
console.log(
|
|
8344
|
+
console.log(chalk11.dim(` test: ${f.path} (${f.method})`));
|
|
7627
8345
|
}
|
|
7628
8346
|
}
|
|
7629
8347
|
}
|
|
@@ -7634,7 +8352,7 @@ function printCoverageSummary(summary) {
|
|
|
7634
8352
|
const codePct = s.total > 0 ? Math.round(s.withCode / s.total * 100) : 0;
|
|
7635
8353
|
const testPct = s.total > 0 ? Math.round(s.withTests / s.total * 100) : 0;
|
|
7636
8354
|
console.log(
|
|
7637
|
-
` ${
|
|
8355
|
+
` ${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
8356
|
);
|
|
7639
8357
|
}
|
|
7640
8358
|
async function runTraceability(options) {
|
|
@@ -7642,7 +8360,7 @@ async function runTraceability(options) {
|
|
|
7642
8360
|
const projectPath = process.cwd();
|
|
7643
8361
|
const store = await loadGraphStore(projectPath);
|
|
7644
8362
|
if (!store) handleNoStore(mode);
|
|
7645
|
-
const graphModule = await import("./dist-
|
|
8363
|
+
const graphModule = await import("./dist-RADHOOXG.js");
|
|
7646
8364
|
const filterOptions = buildFilterOptions(options);
|
|
7647
8365
|
const results = graphModule.queryTraceability(store, filterOptions);
|
|
7648
8366
|
if (results.length === 0) handleEmptyResults(mode);
|
|
@@ -7650,14 +8368,14 @@ async function runTraceability(options) {
|
|
|
7650
8368
|
console.log(JSON.stringify(results, null, 2));
|
|
7651
8369
|
} else {
|
|
7652
8370
|
console.log("");
|
|
7653
|
-
console.log(
|
|
8371
|
+
console.log(chalk11.bold("Spec-to-Implementation Traceability"));
|
|
7654
8372
|
console.log("");
|
|
7655
8373
|
for (const result of results) printResultTable(result, mode);
|
|
7656
8374
|
}
|
|
7657
8375
|
process.exit(ExitCode.SUCCESS);
|
|
7658
8376
|
}
|
|
7659
8377
|
function createTraceabilityCommand() {
|
|
7660
|
-
return new
|
|
8378
|
+
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
8379
|
const globalOpts = cmd.optsWithGlobals();
|
|
7662
8380
|
await runTraceability({
|
|
7663
8381
|
spec: opts.spec,
|
|
@@ -7670,15 +8388,15 @@ function createTraceabilityCommand() {
|
|
|
7670
8388
|
}
|
|
7671
8389
|
|
|
7672
8390
|
// src/commands/uninstall.ts
|
|
7673
|
-
import * as
|
|
7674
|
-
import { Command as
|
|
8391
|
+
import * as path60 from "path";
|
|
8392
|
+
import { Command as Command78 } from "commander";
|
|
7675
8393
|
async function runUninstall(skillName, options) {
|
|
7676
8394
|
const packageName = resolvePackageName(skillName);
|
|
7677
8395
|
const shortName = extractSkillName(packageName);
|
|
7678
8396
|
const globalDir = resolveGlobalSkillsDir();
|
|
7679
|
-
const skillsDir =
|
|
7680
|
-
const communityBase =
|
|
7681
|
-
const lockfilePath =
|
|
8397
|
+
const skillsDir = path60.dirname(globalDir);
|
|
8398
|
+
const communityBase = path60.join(skillsDir, "community");
|
|
8399
|
+
const lockfilePath = path60.join(communityBase, "skills-lock.json");
|
|
7682
8400
|
const lockfile = readLockfile2(lockfilePath);
|
|
7683
8401
|
const entry = lockfile.skills[packageName];
|
|
7684
8402
|
if (!entry) {
|
|
@@ -7708,7 +8426,7 @@ async function runUninstall(skillName, options) {
|
|
|
7708
8426
|
return result;
|
|
7709
8427
|
}
|
|
7710
8428
|
function createUninstallCommand() {
|
|
7711
|
-
const cmd = new
|
|
8429
|
+
const cmd = new Command78("uninstall");
|
|
7712
8430
|
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
8431
|
try {
|
|
7714
8432
|
const result = await runUninstall(skill, opts);
|
|
@@ -7727,9 +8445,10 @@ function createUninstallCommand() {
|
|
|
7727
8445
|
}
|
|
7728
8446
|
|
|
7729
8447
|
// src/commands/uninstall-constraints.ts
|
|
7730
|
-
|
|
7731
|
-
import * as
|
|
7732
|
-
import
|
|
8448
|
+
init_dist();
|
|
8449
|
+
import * as fs40 from "fs/promises";
|
|
8450
|
+
import * as path61 from "path";
|
|
8451
|
+
import { Command as Command79 } from "commander";
|
|
7733
8452
|
async function runUninstallConstraints(options) {
|
|
7734
8453
|
const { packageName, configPath, lockfilePath } = options;
|
|
7735
8454
|
const lockfileResult = await readLockfile(lockfilePath);
|
|
@@ -7749,7 +8468,7 @@ async function runUninstallConstraints(options) {
|
|
|
7749
8468
|
}
|
|
7750
8469
|
let localConfig;
|
|
7751
8470
|
try {
|
|
7752
|
-
const raw = await
|
|
8471
|
+
const raw = await fs40.readFile(configPath, "utf-8");
|
|
7753
8472
|
localConfig = JSON.parse(raw);
|
|
7754
8473
|
} catch (err) {
|
|
7755
8474
|
return {
|
|
@@ -7786,11 +8505,11 @@ async function runUninstallConstraints(options) {
|
|
|
7786
8505
|
};
|
|
7787
8506
|
}
|
|
7788
8507
|
function createUninstallConstraintsCommand() {
|
|
7789
|
-
const cmd = new
|
|
8508
|
+
const cmd = new Command79("uninstall-constraints");
|
|
7790
8509
|
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
8510
|
let configPath;
|
|
7792
8511
|
if (opts.config) {
|
|
7793
|
-
configPath =
|
|
8512
|
+
configPath = path61.resolve(opts.config);
|
|
7794
8513
|
} else {
|
|
7795
8514
|
const found = findConfigFile();
|
|
7796
8515
|
if (!found.ok) {
|
|
@@ -7799,8 +8518,8 @@ function createUninstallConstraintsCommand() {
|
|
|
7799
8518
|
}
|
|
7800
8519
|
configPath = found.value;
|
|
7801
8520
|
}
|
|
7802
|
-
const projectRoot =
|
|
7803
|
-
const lockfilePath =
|
|
8521
|
+
const projectRoot = path61.dirname(configPath);
|
|
8522
|
+
const lockfilePath = path61.join(projectRoot, ".harness", "constraints.lock.json");
|
|
7804
8523
|
const result = await runUninstallConstraints({
|
|
7805
8524
|
packageName: name,
|
|
7806
8525
|
configPath,
|
|
@@ -7825,13 +8544,13 @@ function createUninstallConstraintsCommand() {
|
|
|
7825
8544
|
}
|
|
7826
8545
|
|
|
7827
8546
|
// src/commands/update.ts
|
|
7828
|
-
import { Command as
|
|
8547
|
+
import { Command as Command80 } from "commander";
|
|
7829
8548
|
import { execFile, execFileSync as execFileSync6 } from "child_process";
|
|
7830
|
-
import { realpathSync, existsSync as
|
|
7831
|
-
import { join as
|
|
8549
|
+
import { realpathSync, existsSync as existsSync36, readFileSync as readFileSync25 } from "fs";
|
|
8550
|
+
import { join as join47 } from "path";
|
|
7832
8551
|
import { promisify } from "util";
|
|
7833
|
-
import
|
|
7834
|
-
import
|
|
8552
|
+
import readline3 from "readline";
|
|
8553
|
+
import chalk12 from "chalk";
|
|
7835
8554
|
function detectPackageManager() {
|
|
7836
8555
|
try {
|
|
7837
8556
|
const argv1 = process.argv[1];
|
|
@@ -7889,26 +8608,35 @@ function getInstalledPackages(pm) {
|
|
|
7889
8608
|
return ["@harness-engineering/cli", "@harness-engineering/core"];
|
|
7890
8609
|
}
|
|
7891
8610
|
}
|
|
7892
|
-
function
|
|
7893
|
-
const rl =
|
|
8611
|
+
function prompt2(question) {
|
|
8612
|
+
const rl = readline3.createInterface({
|
|
7894
8613
|
input: process.stdin,
|
|
7895
8614
|
output: process.stdout
|
|
7896
8615
|
});
|
|
7897
|
-
return new Promise((
|
|
8616
|
+
return new Promise((resolve35) => {
|
|
7898
8617
|
rl.question(question, (answer) => {
|
|
7899
8618
|
rl.close();
|
|
7900
|
-
|
|
8619
|
+
resolve35(answer.trim().toLowerCase());
|
|
7901
8620
|
});
|
|
7902
8621
|
});
|
|
7903
8622
|
}
|
|
8623
|
+
async function ensureTelemetryIfNeeded() {
|
|
8624
|
+
const cwd = process.cwd();
|
|
8625
|
+
const result = await ensureTelemetryConfigured(cwd);
|
|
8626
|
+
if (result.status === "pass") {
|
|
8627
|
+
logger.success(result.message);
|
|
8628
|
+
} else if (result.status === "warn") {
|
|
8629
|
+
logger.warn(result.message);
|
|
8630
|
+
}
|
|
8631
|
+
}
|
|
7904
8632
|
function refreshHooks() {
|
|
7905
8633
|
const cwd = process.cwd();
|
|
7906
|
-
const configPath =
|
|
7907
|
-
if (!
|
|
8634
|
+
const configPath = join47(cwd, "harness.config.json");
|
|
8635
|
+
if (!existsSync36(configPath)) return;
|
|
7908
8636
|
let profile = "standard";
|
|
7909
|
-
const profilePath =
|
|
8637
|
+
const profilePath = join47(cwd, ".harness", "hooks", "profile.json");
|
|
7910
8638
|
try {
|
|
7911
|
-
const data = JSON.parse(
|
|
8639
|
+
const data = JSON.parse(readFileSync25(profilePath, "utf-8"));
|
|
7912
8640
|
if (data.profile && ["minimal", "standard", "strict"].includes(data.profile)) {
|
|
7913
8641
|
profile = data.profile;
|
|
7914
8642
|
}
|
|
@@ -7928,16 +8656,16 @@ function runLocalGraphScan() {
|
|
|
7928
8656
|
execFileSync6("harness", ["graph", "scan", "."], { stdio: "inherit" });
|
|
7929
8657
|
} catch {
|
|
7930
8658
|
logger.warn("Graph scan failed. Run manually:");
|
|
7931
|
-
console.log(` ${
|
|
8659
|
+
console.log(` ${chalk12.cyan("harness graph scan .")}`);
|
|
7932
8660
|
}
|
|
7933
8661
|
}
|
|
7934
8662
|
async function offerRegeneration() {
|
|
7935
8663
|
console.log("");
|
|
7936
|
-
const regenAnswer = await
|
|
8664
|
+
const regenAnswer = await prompt2(
|
|
7937
8665
|
"Regenerate slash commands, agent definitions, and knowledge graph? (Y/n) "
|
|
7938
8666
|
);
|
|
7939
8667
|
if (regenAnswer === "n" || regenAnswer === "no") return;
|
|
7940
|
-
const scopeAnswer = await
|
|
8668
|
+
const scopeAnswer = await prompt2("Generate for (G)lobal or (l)ocal project? (G/l) ");
|
|
7941
8669
|
const isGlobal = scopeAnswer !== "l" && scopeAnswer !== "local";
|
|
7942
8670
|
try {
|
|
7943
8671
|
execFileSync6("harness", ["generate", ...isGlobal ? ["--global"] : []], {
|
|
@@ -7945,7 +8673,7 @@ async function offerRegeneration() {
|
|
|
7945
8673
|
});
|
|
7946
8674
|
} catch {
|
|
7947
8675
|
logger.warn("Generation failed. Run manually:");
|
|
7948
|
-
console.log(` ${
|
|
8676
|
+
console.log(` ${chalk12.cyan(`harness generate${isGlobal ? " --global" : ""}`)}`);
|
|
7949
8677
|
}
|
|
7950
8678
|
if (!isGlobal) {
|
|
7951
8679
|
runLocalGraphScan();
|
|
@@ -8001,14 +8729,15 @@ async function runUpdateAction(opts, globalOpts) {
|
|
|
8001
8729
|
if (!hasUpdates) {
|
|
8002
8730
|
logger.success("All packages are up to date");
|
|
8003
8731
|
refreshHooks();
|
|
8732
|
+
await ensureTelemetryIfNeeded();
|
|
8004
8733
|
await offerRegeneration();
|
|
8005
8734
|
process.exit(ExitCode.SUCCESS);
|
|
8006
8735
|
}
|
|
8007
8736
|
console.log("");
|
|
8008
8737
|
for (const { pkg, current, latest } of outdated) {
|
|
8009
8738
|
const shortName = pkg.replace("@harness-engineering/", "");
|
|
8010
|
-
const currentStr = current ?
|
|
8011
|
-
logger.info(`${shortName}: ${currentStr} \u2192 ${
|
|
8739
|
+
const currentStr = current ? chalk12.dim(`v${current}`) : chalk12.dim("not installed");
|
|
8740
|
+
logger.info(`${shortName}: ${currentStr} \u2192 ${chalk12.green(`v${latest}`)}`);
|
|
8012
8741
|
}
|
|
8013
8742
|
console.log("");
|
|
8014
8743
|
}
|
|
@@ -8024,15 +8753,16 @@ async function runUpdateAction(opts, globalOpts) {
|
|
|
8024
8753
|
} catch {
|
|
8025
8754
|
console.log("");
|
|
8026
8755
|
logger.error("Update failed. You can try manually:");
|
|
8027
|
-
console.log(` ${
|
|
8756
|
+
console.log(` ${chalk12.cyan(installCmd)}`);
|
|
8028
8757
|
process.exit(ExitCode.ERROR);
|
|
8029
8758
|
}
|
|
8030
8759
|
refreshHooks();
|
|
8760
|
+
await ensureTelemetryIfNeeded();
|
|
8031
8761
|
await offerRegeneration();
|
|
8032
8762
|
process.exit(ExitCode.SUCCESS);
|
|
8033
8763
|
}
|
|
8034
8764
|
function createUpdateCommand() {
|
|
8035
|
-
return new
|
|
8765
|
+
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
8766
|
"--regenerate",
|
|
8037
8767
|
"Only regenerate slash commands and agent definitions (skip package updates)"
|
|
8038
8768
|
).action(async (opts, cmd) => {
|
|
@@ -8042,15 +8772,15 @@ function createUpdateCommand() {
|
|
|
8042
8772
|
}
|
|
8043
8773
|
|
|
8044
8774
|
// src/commands/usage.ts
|
|
8045
|
-
import { Command as
|
|
8775
|
+
import { Command as Command81 } from "commander";
|
|
8046
8776
|
async function loadAndPriceRecords(cwd, includeClaudeSessions = false) {
|
|
8047
|
-
const { readCostRecords, loadPricingData, calculateCost, parseCCRecords } = await import("./dist-
|
|
8777
|
+
const { readCostRecords, loadPricingData, calculateCost, parseCCRecords } = await import("./dist-WCSJHQPK.js");
|
|
8048
8778
|
const records = readCostRecords(cwd);
|
|
8049
8779
|
if (includeClaudeSessions) {
|
|
8050
8780
|
const ccRecords = parseCCRecords();
|
|
8051
8781
|
records.push(...ccRecords);
|
|
8052
8782
|
}
|
|
8053
|
-
if (records.length === 0) return records;
|
|
8783
|
+
if (records.length === 0) return { records, pricingData: null };
|
|
8054
8784
|
const pricingData = await loadPricingData(cwd);
|
|
8055
8785
|
for (const record of records) {
|
|
8056
8786
|
if (record.model && record.costMicroUSD == null) {
|
|
@@ -8058,7 +8788,14 @@ async function loadAndPriceRecords(cwd, includeClaudeSessions = false) {
|
|
|
8058
8788
|
if (cost != null) record.costMicroUSD = cost;
|
|
8059
8789
|
}
|
|
8060
8790
|
}
|
|
8061
|
-
return records;
|
|
8791
|
+
return { records, pricingData };
|
|
8792
|
+
}
|
|
8793
|
+
function computeCacheSavings(cacheReadTokens, model, pricingData) {
|
|
8794
|
+
if (!cacheReadTokens || !model || !pricingData) return null;
|
|
8795
|
+
const pricing = pricingData.get(model);
|
|
8796
|
+
if (!pricing || pricing.cacheReadPer1M == null) return null;
|
|
8797
|
+
const savingsUSD = cacheReadTokens / 1e6 * (pricing.inputPer1M - pricing.cacheReadPer1M);
|
|
8798
|
+
return Math.round(savingsUSD * 1e6);
|
|
8062
8799
|
}
|
|
8063
8800
|
function formatMicroUSD(microUSD) {
|
|
8064
8801
|
if (microUSD == null) return "N/A";
|
|
@@ -8086,7 +8823,10 @@ function registerDailyCommand(usage) {
|
|
|
8086
8823
|
const globalOpts = cmd.optsWithGlobals();
|
|
8087
8824
|
const days = Math.min(Math.max(parseInt(opts.days, 10) || 7, 1), 90);
|
|
8088
8825
|
const cwd = process.cwd();
|
|
8089
|
-
const records = await loadAndPriceRecords(
|
|
8826
|
+
const { records, pricingData } = await loadAndPriceRecords(
|
|
8827
|
+
cwd,
|
|
8828
|
+
globalOpts.includeClaudeSessions
|
|
8829
|
+
);
|
|
8090
8830
|
if (records.length === 0) {
|
|
8091
8831
|
if (globalOpts.json) {
|
|
8092
8832
|
console.log(JSON.stringify([]));
|
|
@@ -8095,23 +8835,25 @@ function registerDailyCommand(usage) {
|
|
|
8095
8835
|
}
|
|
8096
8836
|
return;
|
|
8097
8837
|
}
|
|
8098
|
-
const { aggregateByDay } = await import("./dist-
|
|
8838
|
+
const { aggregateByDay } = await import("./dist-WCSJHQPK.js");
|
|
8099
8839
|
const dailyData = aggregateByDay(records);
|
|
8100
8840
|
const limited = dailyData.slice(0, days);
|
|
8101
8841
|
if (globalOpts.json) {
|
|
8102
8842
|
const enriched = limited.map((day) => {
|
|
8103
8843
|
const hitRate = computeCacheHitRate(day.cacheReadTokens, day.tokens.inputTokens);
|
|
8844
|
+
const savings = computeCacheSavings(day.cacheReadTokens, day.models[0], pricingData);
|
|
8104
8845
|
return {
|
|
8105
8846
|
...day,
|
|
8106
|
-
...hitRate != null ? { cacheHitRate: Math.round(hitRate * 1e3) / 1e3 } : {}
|
|
8847
|
+
...hitRate != null ? { cacheHitRate: Math.round(hitRate * 1e3) / 1e3 } : {},
|
|
8848
|
+
...savings != null ? { cacheSavingsMicroUSD: savings } : {}
|
|
8107
8849
|
};
|
|
8108
8850
|
});
|
|
8109
8851
|
console.log(JSON.stringify(enriched, null, 2));
|
|
8110
8852
|
return;
|
|
8111
8853
|
}
|
|
8112
8854
|
const hasCacheData = limited.some((d) => d.cacheReadTokens != null && d.cacheReadTokens > 0);
|
|
8113
|
-
const cacheHeader = hasCacheData ? " | Cache " : "";
|
|
8114
|
-
const cacheDivider = hasCacheData ? " | ------" : "";
|
|
8855
|
+
const cacheHeader = hasCacheData ? " | Cache | Saved " : "";
|
|
8856
|
+
const cacheDivider = hasCacheData ? " | ------ | -------" : "";
|
|
8115
8857
|
const header = "Date | Sessions | Input | Output | Model(s) | Cost " + cacheHeader;
|
|
8116
8858
|
const divider = "-------------|----------|-----------|-----------|------------------------------|-------" + cacheDivider;
|
|
8117
8859
|
logger.info(header);
|
|
@@ -8125,7 +8867,10 @@ function registerDailyCommand(usage) {
|
|
|
8125
8867
|
const cost = formatMicroUSD(day.costMicroUSD);
|
|
8126
8868
|
const cacheCol = hasCacheData ? (() => {
|
|
8127
8869
|
const rate = computeCacheHitRate(day.cacheReadTokens, day.tokens.inputTokens);
|
|
8128
|
-
|
|
8870
|
+
const savings = computeCacheSavings(day.cacheReadTokens, day.models[0], pricingData);
|
|
8871
|
+
const rateStr = rate != null ? formatPercent(rate).padStart(5) : " -";
|
|
8872
|
+
const savingsStr = savings != null ? formatMicroUSD(savings).padStart(7) : " N/A";
|
|
8873
|
+
return " | " + rateStr + " | " + savingsStr;
|
|
8129
8874
|
})() : "";
|
|
8130
8875
|
logger.info(
|
|
8131
8876
|
`${date} | ${sessions} | ${input} | ${output} | ${models} | ${cost}${cacheCol}`
|
|
@@ -8138,7 +8883,7 @@ function registerSessionsCommand(usage) {
|
|
|
8138
8883
|
const globalOpts = cmd.optsWithGlobals();
|
|
8139
8884
|
const limit = Math.min(Math.max(parseInt(opts.limit, 10) || 10, 1), 100);
|
|
8140
8885
|
const cwd = process.cwd();
|
|
8141
|
-
const records = await loadAndPriceRecords(cwd, globalOpts.includeClaudeSessions);
|
|
8886
|
+
const { records } = await loadAndPriceRecords(cwd, globalOpts.includeClaudeSessions);
|
|
8142
8887
|
if (records.length === 0) {
|
|
8143
8888
|
if (globalOpts.json) {
|
|
8144
8889
|
console.log(JSON.stringify([]));
|
|
@@ -8147,7 +8892,7 @@ function registerSessionsCommand(usage) {
|
|
|
8147
8892
|
}
|
|
8148
8893
|
return;
|
|
8149
8894
|
}
|
|
8150
|
-
const { aggregateBySession } = await import("./dist-
|
|
8895
|
+
const { aggregateBySession } = await import("./dist-WCSJHQPK.js");
|
|
8151
8896
|
const sessionData = aggregateBySession(records);
|
|
8152
8897
|
const limited = sessionData.slice(0, limit);
|
|
8153
8898
|
if (globalOpts.json) {
|
|
@@ -8189,7 +8934,7 @@ function reportSessionNotFound(id, sessionData, useJson) {
|
|
|
8189
8934
|
}
|
|
8190
8935
|
}
|
|
8191
8936
|
}
|
|
8192
|
-
function printSessionDetail(match) {
|
|
8937
|
+
function printSessionDetail(match, pricingData) {
|
|
8193
8938
|
logger.info(`Session: ${match.sessionId}`);
|
|
8194
8939
|
logger.info(`Started: ${match.firstTimestamp}`);
|
|
8195
8940
|
logger.info(`Ended: ${match.lastTimestamp}`);
|
|
@@ -8208,9 +8953,13 @@ function printSessionDetail(match) {
|
|
|
8208
8953
|
}
|
|
8209
8954
|
const hitRate = computeCacheHitRate(match.cacheReadTokens, match.tokens.inputTokens);
|
|
8210
8955
|
if (hitRate != null) {
|
|
8956
|
+
const savings = computeCacheSavings(match.cacheReadTokens, match.model, pricingData);
|
|
8211
8957
|
logger.info("");
|
|
8212
8958
|
logger.info("Cache Performance:");
|
|
8213
8959
|
logger.info(` Cache hit rate: ${formatPercent(hitRate)}`);
|
|
8960
|
+
if (savings != null) {
|
|
8961
|
+
logger.info(` Estimated savings: ${formatMicroUSD(savings)}`);
|
|
8962
|
+
}
|
|
8214
8963
|
}
|
|
8215
8964
|
logger.info("");
|
|
8216
8965
|
logger.info(`Cost: ${formatMicroUSD(match.costMicroUSD)}`);
|
|
@@ -8219,8 +8968,11 @@ function registerSessionCommand(usage) {
|
|
|
8219
8968
|
usage.command("session <id>").description("Show detailed token breakdown for a specific session").action(async (id, _opts, cmd) => {
|
|
8220
8969
|
const globalOpts = cmd.optsWithGlobals();
|
|
8221
8970
|
const cwd = process.cwd();
|
|
8222
|
-
const records = await loadAndPriceRecords(
|
|
8223
|
-
|
|
8971
|
+
const { records, pricingData } = await loadAndPriceRecords(
|
|
8972
|
+
cwd,
|
|
8973
|
+
globalOpts.includeClaudeSessions
|
|
8974
|
+
);
|
|
8975
|
+
const { aggregateBySession } = await import("./dist-WCSJHQPK.js");
|
|
8224
8976
|
const sessionData = aggregateBySession(records);
|
|
8225
8977
|
const match = sessionData.find((s) => s.sessionId === id);
|
|
8226
8978
|
if (!match) {
|
|
@@ -8230,21 +8982,23 @@ function registerSessionCommand(usage) {
|
|
|
8230
8982
|
}
|
|
8231
8983
|
if (globalOpts.json) {
|
|
8232
8984
|
const hitRate = computeCacheHitRate(match.cacheReadTokens, match.tokens.inputTokens);
|
|
8985
|
+
const savings = computeCacheSavings(match.cacheReadTokens, match.model, pricingData);
|
|
8233
8986
|
const enriched = {
|
|
8234
8987
|
...match,
|
|
8235
|
-
...hitRate != null ? { cacheHitRate: Math.round(hitRate * 1e3) / 1e3 } : {}
|
|
8988
|
+
...hitRate != null ? { cacheHitRate: Math.round(hitRate * 1e3) / 1e3 } : {},
|
|
8989
|
+
...savings != null ? { cacheSavingsMicroUSD: savings } : {}
|
|
8236
8990
|
};
|
|
8237
8991
|
console.log(JSON.stringify(enriched, null, 2));
|
|
8238
8992
|
return;
|
|
8239
8993
|
}
|
|
8240
|
-
printSessionDetail(match);
|
|
8994
|
+
printSessionDetail(match, pricingData);
|
|
8241
8995
|
});
|
|
8242
8996
|
}
|
|
8243
8997
|
function registerLatestCommand(usage) {
|
|
8244
8998
|
usage.command("latest").description("Show the most recently completed session cost summary").action(async (_opts, cmd) => {
|
|
8245
8999
|
const globalOpts = cmd.optsWithGlobals();
|
|
8246
9000
|
const cwd = process.cwd();
|
|
8247
|
-
const records = await loadAndPriceRecords(cwd, globalOpts.includeClaudeSessions);
|
|
9001
|
+
const { records } = await loadAndPriceRecords(cwd, globalOpts.includeClaudeSessions);
|
|
8248
9002
|
if (records.length === 0) {
|
|
8249
9003
|
if (globalOpts.json) {
|
|
8250
9004
|
console.log(JSON.stringify({ error: "No usage data found" }));
|
|
@@ -8253,7 +9007,7 @@ function registerLatestCommand(usage) {
|
|
|
8253
9007
|
}
|
|
8254
9008
|
return;
|
|
8255
9009
|
}
|
|
8256
|
-
const { aggregateBySession } = await import("./dist-
|
|
9010
|
+
const { aggregateBySession } = await import("./dist-WCSJHQPK.js");
|
|
8257
9011
|
const sessionData = aggregateBySession(records);
|
|
8258
9012
|
const latest = sessionData[0];
|
|
8259
9013
|
if (!latest) {
|
|
@@ -8279,7 +9033,7 @@ function registerLatestCommand(usage) {
|
|
|
8279
9033
|
});
|
|
8280
9034
|
}
|
|
8281
9035
|
function createUsageCommand() {
|
|
8282
|
-
const usage = new
|
|
9036
|
+
const usage = new Command81("usage").description("Token usage and cost tracking");
|
|
8283
9037
|
usage.option(
|
|
8284
9038
|
"--include-claude-sessions",
|
|
8285
9039
|
"Include Claude Code session data from ~/.claude/projects/"
|
|
@@ -8292,15 +9046,17 @@ function createUsageCommand() {
|
|
|
8292
9046
|
}
|
|
8293
9047
|
|
|
8294
9048
|
// src/commands/validate.ts
|
|
8295
|
-
|
|
8296
|
-
|
|
9049
|
+
init_dist();
|
|
9050
|
+
init_dist();
|
|
9051
|
+
import { Command as Command82 } from "commander";
|
|
9052
|
+
import * as path62 from "path";
|
|
8297
9053
|
async function runValidate(options) {
|
|
8298
9054
|
const configResult = resolveConfig(options.configPath);
|
|
8299
9055
|
if (!configResult.ok) {
|
|
8300
9056
|
return configResult;
|
|
8301
9057
|
}
|
|
8302
9058
|
const config = configResult.value;
|
|
8303
|
-
const cwd = options.cwd ?? (options.configPath ?
|
|
9059
|
+
const cwd = options.cwd ?? (options.configPath ? path62.dirname(path62.resolve(options.configPath)) : process.cwd());
|
|
8304
9060
|
const result = {
|
|
8305
9061
|
valid: true,
|
|
8306
9062
|
checks: {
|
|
@@ -8310,7 +9066,7 @@ async function runValidate(options) {
|
|
|
8310
9066
|
},
|
|
8311
9067
|
issues: []
|
|
8312
9068
|
};
|
|
8313
|
-
const agentsMapPath =
|
|
9069
|
+
const agentsMapPath = path62.resolve(cwd, config.agentsMapPath);
|
|
8314
9070
|
const agentsResult = await validateAgentsMap(agentsMapPath);
|
|
8315
9071
|
if (agentsResult.ok) {
|
|
8316
9072
|
result.checks.agentsMap = true;
|
|
@@ -8346,6 +9102,26 @@ async function runValidate(options) {
|
|
|
8346
9102
|
});
|
|
8347
9103
|
}
|
|
8348
9104
|
result.checks.fileStructure = true;
|
|
9105
|
+
if (options.agentConfigs) {
|
|
9106
|
+
const agentCfg = await validateAgentConfigs(cwd, {
|
|
9107
|
+
strict: options.strict === true,
|
|
9108
|
+
...options.agnixBin !== void 0 && { agnixBin: options.agnixBin }
|
|
9109
|
+
});
|
|
9110
|
+
result.agentConfigs = agentCfg;
|
|
9111
|
+
result.checks.agentConfigs = agentCfg.valid;
|
|
9112
|
+
if (!agentCfg.valid) result.valid = false;
|
|
9113
|
+
for (const finding of agentCfg.issues) {
|
|
9114
|
+
result.issues.push({
|
|
9115
|
+
check: "agentConfigs",
|
|
9116
|
+
file: finding.file,
|
|
9117
|
+
...finding.line !== void 0 && { line: finding.line },
|
|
9118
|
+
ruleId: finding.ruleId,
|
|
9119
|
+
severity: finding.severity,
|
|
9120
|
+
message: finding.message,
|
|
9121
|
+
...finding.suggestion !== void 0 && { suggestion: finding.suggestion }
|
|
9122
|
+
});
|
|
9123
|
+
}
|
|
9124
|
+
}
|
|
8349
9125
|
return Ok(result);
|
|
8350
9126
|
}
|
|
8351
9127
|
function resolveValidateMode(globalOpts) {
|
|
@@ -8355,11 +9131,11 @@ function resolveValidateMode(globalOpts) {
|
|
|
8355
9131
|
return OutputMode.TEXT;
|
|
8356
9132
|
}
|
|
8357
9133
|
async function printCrossCheckWarnings(mode) {
|
|
8358
|
-
const { runCrossCheck: runCrossCheck2 } = await import("./validate-cross-check-
|
|
9134
|
+
const { runCrossCheck: runCrossCheck2 } = await import("./validate-cross-check-ZOWFA3DB.js");
|
|
8359
9135
|
const cwd = process.cwd();
|
|
8360
9136
|
const crossResult = await runCrossCheck2({
|
|
8361
|
-
specsDir:
|
|
8362
|
-
plansDir:
|
|
9137
|
+
specsDir: path62.join(cwd, "docs", "specs"),
|
|
9138
|
+
plansDir: path62.join(cwd, "docs", "plans"),
|
|
8363
9139
|
projectPath: cwd
|
|
8364
9140
|
});
|
|
8365
9141
|
if (!crossResult.ok || crossResult.value.warnings === 0) return;
|
|
@@ -8371,37 +9147,59 @@ async function printCrossCheckWarnings(mode) {
|
|
|
8371
9147
|
${crossResult.value.warnings} warnings`);
|
|
8372
9148
|
}
|
|
8373
9149
|
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
|
-
});
|
|
9150
|
+
const command = new Command82("validate").description("Run all validation checks").option("--cross-check", "Run cross-artifact consistency validation").option(
|
|
9151
|
+
"--agent-configs",
|
|
9152
|
+
"Validate agent configs (CLAUDE.md, hooks, skills) via agnix or built-in fallback rules"
|
|
9153
|
+
).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
9154
|
return command;
|
|
8398
9155
|
}
|
|
9156
|
+
async function runValidateAction(opts, globalOpts) {
|
|
9157
|
+
const mode = resolveValidateMode(globalOpts);
|
|
9158
|
+
const formatter = new OutputFormatter(mode);
|
|
9159
|
+
const result = await runValidate({
|
|
9160
|
+
...typeof globalOpts.config === "string" && { configPath: globalOpts.config },
|
|
9161
|
+
json: globalOpts.json === true,
|
|
9162
|
+
verbose: globalOpts.verbose === true,
|
|
9163
|
+
quiet: globalOpts.quiet === true,
|
|
9164
|
+
agentConfigs: opts.agentConfigs === true,
|
|
9165
|
+
strict: opts.strict === true,
|
|
9166
|
+
...typeof opts.agnixBin === "string" && { agnixBin: opts.agnixBin }
|
|
9167
|
+
});
|
|
9168
|
+
if (!result.ok) {
|
|
9169
|
+
if (mode === OutputMode.JSON) console.log(JSON.stringify({ error: result.error.message }));
|
|
9170
|
+
else logger.error(result.error.message);
|
|
9171
|
+
process.exit(result.error.exitCode);
|
|
9172
|
+
}
|
|
9173
|
+
if (opts.crossCheck) await printCrossCheckWarnings(mode);
|
|
9174
|
+
emitValidateOutput(result.value, mode, formatter);
|
|
9175
|
+
process.exit(result.value.valid ? ExitCode.SUCCESS : ExitCode.VALIDATION_FAILED);
|
|
9176
|
+
}
|
|
9177
|
+
function emitValidateOutput(value, mode, formatter) {
|
|
9178
|
+
if (mode === OutputMode.JSON) {
|
|
9179
|
+
console.log(JSON.stringify(value, null, 2));
|
|
9180
|
+
return;
|
|
9181
|
+
}
|
|
9182
|
+
const output = formatter.formatValidation({ valid: value.valid, issues: value.issues });
|
|
9183
|
+
if (output) console.log(output);
|
|
9184
|
+
if (value.agentConfigs) printAgentConfigSummary(value.agentConfigs, mode);
|
|
9185
|
+
}
|
|
9186
|
+
function printAgentConfigSummary(cfg, mode) {
|
|
9187
|
+
if (mode === OutputMode.QUIET) return;
|
|
9188
|
+
const engineLabel = cfg.engine === "agnix" ? "agnix" : "built-in fallback rules";
|
|
9189
|
+
const note = cfg.fellBackBecause ? ` (${cfg.fellBackBecause})` : "";
|
|
9190
|
+
console.log(`
|
|
9191
|
+
Agent configs checked via ${engineLabel}${note}`);
|
|
9192
|
+
if (cfg.engine === "fallback" && cfg.fellBackBecause === "binary-not-found") {
|
|
9193
|
+
console.log(" Install agnix for 385+ rule coverage: https://github.com/agent-sh/agnix");
|
|
9194
|
+
}
|
|
9195
|
+
}
|
|
8399
9196
|
|
|
8400
9197
|
// src/commands/_registry.ts
|
|
8401
9198
|
var commandCreators = [
|
|
8402
9199
|
createAddCommand,
|
|
8403
9200
|
createAdoptionCommand,
|
|
8404
9201
|
createAgentCommand,
|
|
9202
|
+
createAuditProtectedCommand,
|
|
8405
9203
|
createBlueprintCommand,
|
|
8406
9204
|
createCheckArchCommand,
|
|
8407
9205
|
createCheckDepsCommand,
|
|
@@ -8434,6 +9232,7 @@ var commandCreators = [
|
|
|
8434
9232
|
createPerfCommand,
|
|
8435
9233
|
createPersonaCommand,
|
|
8436
9234
|
createPredictCommand,
|
|
9235
|
+
createPublishAnalysesCommand,
|
|
8437
9236
|
createQueryCommand,
|
|
8438
9237
|
createRecommendCommand,
|
|
8439
9238
|
createScanCommand,
|
|
@@ -8444,6 +9243,7 @@ var commandCreators = [
|
|
|
8444
9243
|
createSkillCommand,
|
|
8445
9244
|
createSnapshotCommand,
|
|
8446
9245
|
createStateCommand,
|
|
9246
|
+
createSyncAnalysesCommand,
|
|
8447
9247
|
createTaintCommand,
|
|
8448
9248
|
createTelemetryCommand,
|
|
8449
9249
|
createTraceabilityCommand,
|
|
@@ -8456,7 +9256,7 @@ var commandCreators = [
|
|
|
8456
9256
|
|
|
8457
9257
|
// src/index.ts
|
|
8458
9258
|
function createProgram() {
|
|
8459
|
-
const program = new
|
|
9259
|
+
const program = new Command83();
|
|
8460
9260
|
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
9261
|
for (const creator of commandCreators) {
|
|
8462
9262
|
program.addCommand(creator());
|