@harness-engineering/cli 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +115 -0
- package/dist/bin/harness.d.ts +1 -0
- package/dist/bin/harness.js +18 -0
- package/dist/chunk-4RCIE5YB.js +526 -0
- package/dist/chunk-5JDJNUEO.js +2639 -0
- package/dist/chunk-6I25KNGR.js +1209 -0
- package/dist/chunk-77B7VOJM.js +1304 -0
- package/dist/chunk-ATN2MXAI.js +2798 -0
- package/dist/chunk-CJ2ZAYCV.js +2639 -0
- package/dist/chunk-EFZOLZFB.js +265 -0
- package/dist/chunk-EQKDZSPA.js +226 -0
- package/dist/chunk-FQB2ZTRA.js +1209 -0
- package/dist/chunk-G2SHRCBP.js +935 -0
- package/dist/chunk-GPYYJN6Z.js +2634 -0
- package/dist/chunk-H2LQ7ELQ.js +2795 -0
- package/dist/chunk-L64MEJOI.js +2512 -0
- package/dist/chunk-LFA7JNFB.js +2633 -0
- package/dist/chunk-NDZWBEZS.js +317 -0
- package/dist/chunk-RZHIR5XA.js +640 -0
- package/dist/chunk-SJJ37KLV.js +317 -0
- package/dist/chunk-SPR56MPD.js +2798 -0
- package/dist/chunk-TLZO4QIN.js +2850 -0
- package/dist/chunk-TUMCTRNV.js +2637 -0
- package/dist/chunk-Z7MYWXIH.js +2852 -0
- package/dist/chunk-ZOOWDP6S.js +2857 -0
- package/dist/create-skill-4GKJZB5R.js +8 -0
- package/dist/index.d.ts +543 -0
- package/dist/index.js +42 -0
- package/dist/validate-cross-check-N75UV2CO.js +69 -0
- package/dist/validate-cross-check-ZB2OZDOK.js +69 -0
- package/package.json +59 -0
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import { Command as Command2 } from "commander";
|
|
3
|
+
import { VERSION } from "@harness-engineering/core";
|
|
4
|
+
|
|
5
|
+
// src/commands/validate.ts
|
|
6
|
+
import { Command } from "commander";
|
|
7
|
+
import * as path2 from "path";
|
|
8
|
+
import { Ok as Ok2 } from "@harness-engineering/core";
|
|
9
|
+
import { validateAgentsMap, validateKnowledgeMap } from "@harness-engineering/core";
|
|
10
|
+
|
|
11
|
+
// src/config/loader.ts
|
|
12
|
+
import * as fs from "fs";
|
|
13
|
+
import * as path from "path";
|
|
14
|
+
import { Ok, Err } from "@harness-engineering/core";
|
|
15
|
+
|
|
16
|
+
// src/config/schema.ts
|
|
17
|
+
import { z } from "zod";
|
|
18
|
+
var LayerSchema = z.object({
|
|
19
|
+
name: z.string(),
|
|
20
|
+
pattern: z.string(),
|
|
21
|
+
allowedDependencies: z.array(z.string())
|
|
22
|
+
});
|
|
23
|
+
var ForbiddenImportSchema = z.object({
|
|
24
|
+
from: z.string(),
|
|
25
|
+
disallow: z.array(z.string()),
|
|
26
|
+
message: z.string().optional()
|
|
27
|
+
});
|
|
28
|
+
var BoundaryConfigSchema = z.object({
|
|
29
|
+
requireSchema: z.array(z.string())
|
|
30
|
+
});
|
|
31
|
+
var AgentConfigSchema = z.object({
|
|
32
|
+
executor: z.enum(["subprocess", "cloud", "noop"]).default("subprocess"),
|
|
33
|
+
timeout: z.number().default(3e5),
|
|
34
|
+
skills: z.array(z.string()).optional()
|
|
35
|
+
});
|
|
36
|
+
var EntropyConfigSchema = z.object({
|
|
37
|
+
excludePatterns: z.array(z.string()).default(["**/node_modules/**", "**/*.test.ts"]),
|
|
38
|
+
autoFix: z.boolean().default(false)
|
|
39
|
+
});
|
|
40
|
+
var HarnessConfigSchema = z.object({
|
|
41
|
+
version: z.literal(1),
|
|
42
|
+
name: z.string().optional(),
|
|
43
|
+
rootDir: z.string().default("."),
|
|
44
|
+
layers: z.array(LayerSchema).optional(),
|
|
45
|
+
forbiddenImports: z.array(ForbiddenImportSchema).optional(),
|
|
46
|
+
boundaries: BoundaryConfigSchema.optional(),
|
|
47
|
+
agentsMapPath: z.string().default("./AGENTS.md"),
|
|
48
|
+
docsDir: z.string().default("./docs"),
|
|
49
|
+
agent: AgentConfigSchema.optional(),
|
|
50
|
+
entropy: EntropyConfigSchema.optional()
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// src/utils/errors.ts
|
|
54
|
+
var ExitCode = {
|
|
55
|
+
SUCCESS: 0,
|
|
56
|
+
VALIDATION_FAILED: 1,
|
|
57
|
+
ERROR: 2
|
|
58
|
+
};
|
|
59
|
+
var CLIError = class extends Error {
|
|
60
|
+
exitCode;
|
|
61
|
+
constructor(message, exitCode = ExitCode.ERROR) {
|
|
62
|
+
super(message);
|
|
63
|
+
this.name = "CLIError";
|
|
64
|
+
this.exitCode = exitCode;
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
function formatError(error) {
|
|
68
|
+
if (error instanceof CLIError) {
|
|
69
|
+
return `Error: ${error.message}`;
|
|
70
|
+
}
|
|
71
|
+
if (error instanceof Error) {
|
|
72
|
+
return `Error: ${error.message}`;
|
|
73
|
+
}
|
|
74
|
+
return `Error: ${String(error)}`;
|
|
75
|
+
}
|
|
76
|
+
function handleError(error) {
|
|
77
|
+
const message = formatError(error);
|
|
78
|
+
console.error(message);
|
|
79
|
+
const exitCode = error instanceof CLIError ? error.exitCode : ExitCode.ERROR;
|
|
80
|
+
process.exit(exitCode);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// src/config/loader.ts
|
|
84
|
+
var CONFIG_FILENAMES = ["harness.config.json"];
|
|
85
|
+
function findConfigFile(startDir = process.cwd()) {
|
|
86
|
+
let currentDir = path.resolve(startDir);
|
|
87
|
+
const root = path.parse(currentDir).root;
|
|
88
|
+
while (currentDir !== root) {
|
|
89
|
+
for (const filename of CONFIG_FILENAMES) {
|
|
90
|
+
const configPath = path.join(currentDir, filename);
|
|
91
|
+
if (fs.existsSync(configPath)) {
|
|
92
|
+
return Ok(configPath);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
currentDir = path.dirname(currentDir);
|
|
96
|
+
}
|
|
97
|
+
return Err(new CLIError(
|
|
98
|
+
'No harness.config.json found. Run "harness init" to create one.',
|
|
99
|
+
ExitCode.ERROR
|
|
100
|
+
));
|
|
101
|
+
}
|
|
102
|
+
function loadConfig(configPath) {
|
|
103
|
+
if (!fs.existsSync(configPath)) {
|
|
104
|
+
return Err(new CLIError(
|
|
105
|
+
`Config file not found: ${configPath}`,
|
|
106
|
+
ExitCode.ERROR
|
|
107
|
+
));
|
|
108
|
+
}
|
|
109
|
+
let rawConfig;
|
|
110
|
+
try {
|
|
111
|
+
const content = fs.readFileSync(configPath, "utf-8");
|
|
112
|
+
rawConfig = JSON.parse(content);
|
|
113
|
+
} catch (error) {
|
|
114
|
+
return Err(new CLIError(
|
|
115
|
+
`Failed to parse config: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
116
|
+
ExitCode.ERROR
|
|
117
|
+
));
|
|
118
|
+
}
|
|
119
|
+
const parsed = HarnessConfigSchema.safeParse(rawConfig);
|
|
120
|
+
if (!parsed.success) {
|
|
121
|
+
const issues = parsed.error.issues.map((i) => ` - ${i.path.join(".")}: ${i.message}`).join("\n");
|
|
122
|
+
return Err(new CLIError(
|
|
123
|
+
`Invalid config:
|
|
124
|
+
${issues}`,
|
|
125
|
+
ExitCode.ERROR
|
|
126
|
+
));
|
|
127
|
+
}
|
|
128
|
+
return Ok(parsed.data);
|
|
129
|
+
}
|
|
130
|
+
function resolveConfig(configPath) {
|
|
131
|
+
if (configPath) {
|
|
132
|
+
return loadConfig(configPath);
|
|
133
|
+
}
|
|
134
|
+
const findResult = findConfigFile();
|
|
135
|
+
if (!findResult.ok) {
|
|
136
|
+
return findResult;
|
|
137
|
+
}
|
|
138
|
+
return loadConfig(findResult.value);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// src/output/formatter.ts
|
|
142
|
+
import chalk from "chalk";
|
|
143
|
+
var OutputMode = {
|
|
144
|
+
JSON: "json",
|
|
145
|
+
TEXT: "text",
|
|
146
|
+
QUIET: "quiet",
|
|
147
|
+
VERBOSE: "verbose"
|
|
148
|
+
};
|
|
149
|
+
var OutputFormatter = class {
|
|
150
|
+
constructor(mode = OutputMode.TEXT) {
|
|
151
|
+
this.mode = mode;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Format raw data (for JSON mode)
|
|
155
|
+
*/
|
|
156
|
+
format(data) {
|
|
157
|
+
if (this.mode === OutputMode.JSON) {
|
|
158
|
+
return JSON.stringify(data, null, 2);
|
|
159
|
+
}
|
|
160
|
+
return String(data);
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Format validation result
|
|
164
|
+
*/
|
|
165
|
+
formatValidation(result) {
|
|
166
|
+
if (this.mode === OutputMode.JSON) {
|
|
167
|
+
return JSON.stringify(result, null, 2);
|
|
168
|
+
}
|
|
169
|
+
if (this.mode === OutputMode.QUIET) {
|
|
170
|
+
if (result.valid) return "";
|
|
171
|
+
return result.issues.map((i) => `${i.file ?? ""}: ${i.message}`).join("\n");
|
|
172
|
+
}
|
|
173
|
+
const lines = [];
|
|
174
|
+
if (result.valid) {
|
|
175
|
+
lines.push(chalk.green("v validation passed"));
|
|
176
|
+
} else {
|
|
177
|
+
lines.push(chalk.red(`x Validation failed (${result.issues.length} issues)`));
|
|
178
|
+
lines.push("");
|
|
179
|
+
for (const issue of result.issues) {
|
|
180
|
+
const location = issue.file ? issue.line ? `${issue.file}:${issue.line}` : issue.file : "unknown";
|
|
181
|
+
lines.push(` ${chalk.yellow("*")} ${chalk.dim(location)}`);
|
|
182
|
+
lines.push(` ${issue.message}`);
|
|
183
|
+
if (issue.suggestion && this.mode === OutputMode.VERBOSE) {
|
|
184
|
+
lines.push(` ${chalk.dim("->")} ${issue.suggestion}`);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return lines.join("\n");
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Format a summary line
|
|
192
|
+
*/
|
|
193
|
+
formatSummary(label, value, success) {
|
|
194
|
+
if (this.mode === OutputMode.JSON || this.mode === OutputMode.QUIET) {
|
|
195
|
+
return "";
|
|
196
|
+
}
|
|
197
|
+
const icon = success ? chalk.green("v") : chalk.red("x");
|
|
198
|
+
return `${icon} ${label}: ${value}`;
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
// src/output/logger.ts
|
|
203
|
+
import chalk2 from "chalk";
|
|
204
|
+
var logger = {
|
|
205
|
+
info: (message) => console.log(chalk2.blue("i"), message),
|
|
206
|
+
success: (message) => console.log(chalk2.green("v"), message),
|
|
207
|
+
warn: (message) => console.log(chalk2.yellow("!"), message),
|
|
208
|
+
error: (message) => console.error(chalk2.red("x"), message),
|
|
209
|
+
dim: (message) => console.log(chalk2.dim(message)),
|
|
210
|
+
// For JSON output mode
|
|
211
|
+
raw: (data) => console.log(JSON.stringify(data, null, 2))
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
// src/commands/validate.ts
|
|
215
|
+
async function runValidate(options) {
|
|
216
|
+
const configResult = resolveConfig(options.configPath);
|
|
217
|
+
if (!configResult.ok) {
|
|
218
|
+
return configResult;
|
|
219
|
+
}
|
|
220
|
+
const config = configResult.value;
|
|
221
|
+
const cwd = options.cwd ?? (options.configPath ? path2.dirname(path2.resolve(options.configPath)) : process.cwd());
|
|
222
|
+
const result = {
|
|
223
|
+
valid: true,
|
|
224
|
+
checks: {
|
|
225
|
+
agentsMap: false,
|
|
226
|
+
fileStructure: false,
|
|
227
|
+
knowledgeMap: false
|
|
228
|
+
},
|
|
229
|
+
issues: []
|
|
230
|
+
};
|
|
231
|
+
const agentsMapPath = path2.resolve(cwd, config.agentsMapPath);
|
|
232
|
+
const agentsResult = await validateAgentsMap(agentsMapPath);
|
|
233
|
+
if (agentsResult.ok) {
|
|
234
|
+
result.checks.agentsMap = true;
|
|
235
|
+
} else {
|
|
236
|
+
result.valid = false;
|
|
237
|
+
result.issues.push({
|
|
238
|
+
check: "agentsMap",
|
|
239
|
+
file: config.agentsMapPath,
|
|
240
|
+
message: agentsResult.error.message,
|
|
241
|
+
suggestion: agentsResult.error.suggestions?.[0]
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
const knowledgeResult = await validateKnowledgeMap(cwd);
|
|
245
|
+
if (knowledgeResult.ok && knowledgeResult.value.brokenLinks.length === 0) {
|
|
246
|
+
result.checks.knowledgeMap = true;
|
|
247
|
+
} else if (knowledgeResult.ok) {
|
|
248
|
+
result.valid = false;
|
|
249
|
+
for (const broken of knowledgeResult.value.brokenLinks) {
|
|
250
|
+
result.issues.push({
|
|
251
|
+
check: "knowledgeMap",
|
|
252
|
+
file: broken.path,
|
|
253
|
+
message: `Broken link: ${broken.path}`,
|
|
254
|
+
suggestion: broken.suggestion || "Remove or fix the broken link"
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
} else {
|
|
258
|
+
result.valid = false;
|
|
259
|
+
result.issues.push({
|
|
260
|
+
check: "knowledgeMap",
|
|
261
|
+
message: knowledgeResult.error.message
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
result.checks.fileStructure = true;
|
|
265
|
+
return Ok2(result);
|
|
266
|
+
}
|
|
267
|
+
function createValidateCommand() {
|
|
268
|
+
const command = new Command("validate").description("Run all validation checks").action(async (opts, cmd) => {
|
|
269
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
270
|
+
const mode = globalOpts.json ? OutputMode.JSON : globalOpts.quiet ? OutputMode.QUIET : globalOpts.verbose ? OutputMode.VERBOSE : OutputMode.TEXT;
|
|
271
|
+
const formatter = new OutputFormatter(mode);
|
|
272
|
+
const result = await runValidate({
|
|
273
|
+
configPath: globalOpts.config,
|
|
274
|
+
json: globalOpts.json,
|
|
275
|
+
verbose: globalOpts.verbose,
|
|
276
|
+
quiet: globalOpts.quiet
|
|
277
|
+
});
|
|
278
|
+
if (!result.ok) {
|
|
279
|
+
if (mode === OutputMode.JSON) {
|
|
280
|
+
console.log(JSON.stringify({ error: result.error.message }));
|
|
281
|
+
} else {
|
|
282
|
+
logger.error(result.error.message);
|
|
283
|
+
}
|
|
284
|
+
process.exit(result.error.exitCode);
|
|
285
|
+
}
|
|
286
|
+
const output = formatter.formatValidation({
|
|
287
|
+
valid: result.value.valid,
|
|
288
|
+
issues: result.value.issues
|
|
289
|
+
});
|
|
290
|
+
if (output) {
|
|
291
|
+
console.log(output);
|
|
292
|
+
}
|
|
293
|
+
process.exit(result.value.valid ? ExitCode.SUCCESS : ExitCode.VALIDATION_FAILED);
|
|
294
|
+
});
|
|
295
|
+
return command;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// src/index.ts
|
|
299
|
+
function createProgram() {
|
|
300
|
+
const program = new Command2();
|
|
301
|
+
program.name("harness").description("CLI for Harness Engineering toolkit").version(VERSION).option("-c, --config <path>", "Path to config file").option("--json", "Output as JSON").option("--verbose", "Verbose output").option("--quiet", "Minimal output");
|
|
302
|
+
program.addCommand(createValidateCommand());
|
|
303
|
+
return program;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
export {
|
|
307
|
+
ExitCode,
|
|
308
|
+
CLIError,
|
|
309
|
+
handleError,
|
|
310
|
+
findConfigFile,
|
|
311
|
+
loadConfig,
|
|
312
|
+
resolveConfig,
|
|
313
|
+
OutputMode,
|
|
314
|
+
OutputFormatter,
|
|
315
|
+
logger,
|
|
316
|
+
createProgram
|
|
317
|
+
};
|