@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,265 @@
|
|
|
1
|
+
// src/commands/create-skill.ts
|
|
2
|
+
import { Command } from "commander";
|
|
3
|
+
import * as path from "path";
|
|
4
|
+
import * as fs from "fs";
|
|
5
|
+
import YAML from "yaml";
|
|
6
|
+
|
|
7
|
+
// src/skill/schema.ts
|
|
8
|
+
import { z } from "zod";
|
|
9
|
+
var SkillPhaseSchema = z.object({
|
|
10
|
+
name: z.string(),
|
|
11
|
+
description: z.string(),
|
|
12
|
+
required: z.boolean().default(true)
|
|
13
|
+
});
|
|
14
|
+
var SkillCliSchema = z.object({
|
|
15
|
+
command: z.string(),
|
|
16
|
+
args: z.array(
|
|
17
|
+
z.object({
|
|
18
|
+
name: z.string(),
|
|
19
|
+
description: z.string(),
|
|
20
|
+
required: z.boolean().default(false)
|
|
21
|
+
})
|
|
22
|
+
).default([])
|
|
23
|
+
});
|
|
24
|
+
var SkillMcpSchema = z.object({
|
|
25
|
+
tool: z.string(),
|
|
26
|
+
input: z.record(z.string())
|
|
27
|
+
});
|
|
28
|
+
var SkillStateSchema = z.object({
|
|
29
|
+
persistent: z.boolean().default(false),
|
|
30
|
+
files: z.array(z.string()).default([])
|
|
31
|
+
});
|
|
32
|
+
var ALLOWED_TRIGGERS = [
|
|
33
|
+
"manual",
|
|
34
|
+
"on_pr",
|
|
35
|
+
"on_commit",
|
|
36
|
+
"on_new_feature",
|
|
37
|
+
"on_bug_fix",
|
|
38
|
+
"on_refactor",
|
|
39
|
+
"on_project_init",
|
|
40
|
+
"on_review"
|
|
41
|
+
];
|
|
42
|
+
var ALLOWED_PLATFORMS = ["claude-code", "gemini-cli"];
|
|
43
|
+
var ALLOWED_COGNITIVE_MODES = [
|
|
44
|
+
"adversarial-reviewer",
|
|
45
|
+
"constructive-architect",
|
|
46
|
+
"meticulous-implementer",
|
|
47
|
+
"diagnostic-investigator",
|
|
48
|
+
"advisory-guide",
|
|
49
|
+
"meticulous-verifier"
|
|
50
|
+
];
|
|
51
|
+
var SkillMetadataSchema = z.object({
|
|
52
|
+
name: z.string().regex(/^[a-z][a-z0-9-]*$/, "Name must be lowercase with hyphens"),
|
|
53
|
+
version: z.string().regex(/^\d+\.\d+\.\d+$/, "Version must be semver format"),
|
|
54
|
+
description: z.string(),
|
|
55
|
+
cognitive_mode: z.string().regex(/^[a-z][a-z0-9]*(-[a-z0-9]+)*$/, "Cognitive mode must be kebab-case").optional(),
|
|
56
|
+
triggers: z.array(z.enum(ALLOWED_TRIGGERS)),
|
|
57
|
+
platforms: z.array(z.enum(ALLOWED_PLATFORMS)),
|
|
58
|
+
tools: z.array(z.string()),
|
|
59
|
+
cli: SkillCliSchema.optional(),
|
|
60
|
+
mcp: SkillMcpSchema.optional(),
|
|
61
|
+
type: z.enum(["rigid", "flexible"]),
|
|
62
|
+
phases: z.array(SkillPhaseSchema).optional(),
|
|
63
|
+
state: SkillStateSchema.default({}),
|
|
64
|
+
depends_on: z.array(z.string()).default([])
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// src/output/logger.ts
|
|
68
|
+
import chalk from "chalk";
|
|
69
|
+
var logger = {
|
|
70
|
+
info: (message) => console.log(chalk.blue("i"), message),
|
|
71
|
+
success: (message) => console.log(chalk.green("v"), message),
|
|
72
|
+
warn: (message) => console.log(chalk.yellow("!"), message),
|
|
73
|
+
error: (message) => console.error(chalk.red("x"), message),
|
|
74
|
+
dim: (message) => console.log(chalk.dim(message)),
|
|
75
|
+
// For JSON output mode
|
|
76
|
+
raw: (data) => console.log(JSON.stringify(data, null, 2))
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
// src/utils/errors.ts
|
|
80
|
+
var ExitCode = {
|
|
81
|
+
SUCCESS: 0,
|
|
82
|
+
VALIDATION_FAILED: 1,
|
|
83
|
+
ERROR: 2
|
|
84
|
+
};
|
|
85
|
+
var CLIError = class extends Error {
|
|
86
|
+
exitCode;
|
|
87
|
+
constructor(message, exitCode = ExitCode.ERROR) {
|
|
88
|
+
super(message);
|
|
89
|
+
this.name = "CLIError";
|
|
90
|
+
this.exitCode = exitCode;
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
function formatError(error) {
|
|
94
|
+
if (error instanceof CLIError) {
|
|
95
|
+
return `Error: ${error.message}`;
|
|
96
|
+
}
|
|
97
|
+
if (error instanceof Error) {
|
|
98
|
+
return `Error: ${error.message}`;
|
|
99
|
+
}
|
|
100
|
+
return `Error: ${String(error)}`;
|
|
101
|
+
}
|
|
102
|
+
function handleError(error) {
|
|
103
|
+
const message = formatError(error);
|
|
104
|
+
console.error(message);
|
|
105
|
+
const exitCode = error instanceof CLIError ? error.exitCode : ExitCode.ERROR;
|
|
106
|
+
process.exit(exitCode);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// src/commands/create-skill.ts
|
|
110
|
+
var KEBAB_CASE_RE = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/;
|
|
111
|
+
function buildSkillYaml(opts) {
|
|
112
|
+
const doc = {
|
|
113
|
+
name: opts.name,
|
|
114
|
+
version: "0.1.0",
|
|
115
|
+
description: opts.description,
|
|
116
|
+
cognitive_mode: opts.cognitiveMode ?? "constructive-architect",
|
|
117
|
+
triggers: ["manual"],
|
|
118
|
+
platforms: ["claude-code"],
|
|
119
|
+
tools: ["Read", "Grep", "Glob", "Edit", "Write", "Bash"],
|
|
120
|
+
type: "flexible",
|
|
121
|
+
state: {
|
|
122
|
+
persistent: false,
|
|
123
|
+
files: []
|
|
124
|
+
},
|
|
125
|
+
depends_on: []
|
|
126
|
+
};
|
|
127
|
+
return doc;
|
|
128
|
+
}
|
|
129
|
+
function buildSkillMd(opts) {
|
|
130
|
+
const mode = opts.cognitiveMode ?? "constructive-architect";
|
|
131
|
+
const readsSection = opts.reads && opts.reads.length > 0 ? opts.reads.map((r) => `- \`${r}\``).join("\n") : "- _No read patterns specified_";
|
|
132
|
+
const producesLine = opts.produces ? `- \`${opts.produces}\`` : "- _No output specified_";
|
|
133
|
+
const preChecksSection = opts.preChecks && opts.preChecks.length > 0 ? opts.preChecks.map((c) => `- \`${c}\``).join("\n") : "- _None_";
|
|
134
|
+
const postChecksSection = opts.postChecks && opts.postChecks.length > 0 ? opts.postChecks.map((c) => `- \`${c}\``).join("\n") : "- _None_";
|
|
135
|
+
return `# ${opts.name}
|
|
136
|
+
|
|
137
|
+
> Cognitive Mode: ${mode}
|
|
138
|
+
|
|
139
|
+
${opts.description}
|
|
140
|
+
|
|
141
|
+
## When to Use
|
|
142
|
+
|
|
143
|
+
- [Describe when this skill should be invoked]
|
|
144
|
+
- [Describe the trigger conditions]
|
|
145
|
+
|
|
146
|
+
## Context Assembly
|
|
147
|
+
|
|
148
|
+
### Reads
|
|
149
|
+
${readsSection}
|
|
150
|
+
|
|
151
|
+
### Produces
|
|
152
|
+
${producesLine}
|
|
153
|
+
|
|
154
|
+
## Deterministic Checks
|
|
155
|
+
|
|
156
|
+
### Pre-checks
|
|
157
|
+
${preChecksSection}
|
|
158
|
+
|
|
159
|
+
### Post-checks
|
|
160
|
+
${postChecksSection}
|
|
161
|
+
|
|
162
|
+
## Process
|
|
163
|
+
|
|
164
|
+
1. [Describe the step-by-step process]
|
|
165
|
+
2. [Add additional steps as needed]
|
|
166
|
+
|
|
167
|
+
## Harness Integration
|
|
168
|
+
|
|
169
|
+
This skill integrates with the harness engineering workflow. It can be invoked via:
|
|
170
|
+
|
|
171
|
+
\`\`\`bash
|
|
172
|
+
harness skill run ${opts.name}
|
|
173
|
+
\`\`\`
|
|
174
|
+
|
|
175
|
+
## Success Criteria
|
|
176
|
+
|
|
177
|
+
- [Define what a successful execution looks like]
|
|
178
|
+
- [Add measurable criteria]
|
|
179
|
+
|
|
180
|
+
## Examples
|
|
181
|
+
|
|
182
|
+
\`\`\`bash
|
|
183
|
+
harness skill run ${opts.name}
|
|
184
|
+
\`\`\`
|
|
185
|
+
`;
|
|
186
|
+
}
|
|
187
|
+
function generateSkillFiles(opts) {
|
|
188
|
+
if (!KEBAB_CASE_RE.test(opts.name)) {
|
|
189
|
+
throw new CLIError(
|
|
190
|
+
`Invalid skill name "${opts.name}". Must be kebab-case (e.g., my-skill).`,
|
|
191
|
+
ExitCode.VALIDATION_FAILED
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
if (opts.cognitiveMode && !ALLOWED_COGNITIVE_MODES.includes(opts.cognitiveMode)) {
|
|
195
|
+
throw new CLIError(
|
|
196
|
+
`Invalid cognitive mode "${opts.cognitiveMode}". Allowed: ${ALLOWED_COGNITIVE_MODES.join(", ")}`,
|
|
197
|
+
ExitCode.VALIDATION_FAILED
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
const baseDir = opts.outputDir ?? path.join(process.cwd(), "agents", "skills", "claude-code");
|
|
201
|
+
const skillDir = path.join(baseDir, opts.name);
|
|
202
|
+
if (fs.existsSync(skillDir)) {
|
|
203
|
+
throw new CLIError(`Skill directory already exists: ${skillDir}`, ExitCode.VALIDATION_FAILED);
|
|
204
|
+
}
|
|
205
|
+
fs.mkdirSync(skillDir, { recursive: true });
|
|
206
|
+
const skillYaml = buildSkillYaml(opts);
|
|
207
|
+
const skillYamlPath = path.join(skillDir, "skill.yaml");
|
|
208
|
+
fs.writeFileSync(skillYamlPath, YAML.stringify(skillYaml));
|
|
209
|
+
const skillMd = buildSkillMd(opts);
|
|
210
|
+
const skillMdPath = path.join(skillDir, "SKILL.md");
|
|
211
|
+
fs.writeFileSync(skillMdPath, skillMd);
|
|
212
|
+
return { skillYamlPath, skillMdPath };
|
|
213
|
+
}
|
|
214
|
+
function createCreateSkillCommand() {
|
|
215
|
+
const command = new Command("create-skill").description("Scaffold a new skill with skill.yaml and SKILL.md").requiredOption("--name <name>", "Skill name (kebab-case)").requiredOption("--description <desc>", "Skill description").option(
|
|
216
|
+
"--cognitive-mode <mode>",
|
|
217
|
+
`Cognitive mode (${ALLOWED_COGNITIVE_MODES.join(", ")})`,
|
|
218
|
+
"constructive-architect"
|
|
219
|
+
).option("--reads <patterns...>", "File patterns the skill reads").option("--produces <output>", "What the skill produces").option("--pre-checks <commands...>", "Pre-check commands").option("--post-checks <commands...>", "Post-check commands").action(async (opts, cmd) => {
|
|
220
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
221
|
+
try {
|
|
222
|
+
const result = generateSkillFiles({
|
|
223
|
+
name: opts.name,
|
|
224
|
+
description: opts.description,
|
|
225
|
+
cognitiveMode: opts.cognitiveMode,
|
|
226
|
+
reads: opts.reads,
|
|
227
|
+
produces: opts.produces,
|
|
228
|
+
preChecks: opts.preChecks,
|
|
229
|
+
postChecks: opts.postChecks
|
|
230
|
+
});
|
|
231
|
+
if (!globalOpts.quiet) {
|
|
232
|
+
logger.success(`Created skill "${opts.name}"`);
|
|
233
|
+
logger.info(` ${result.skillYamlPath}`);
|
|
234
|
+
logger.info(` ${result.skillMdPath}`);
|
|
235
|
+
}
|
|
236
|
+
if (globalOpts.json) {
|
|
237
|
+
logger.raw({
|
|
238
|
+
name: opts.name,
|
|
239
|
+
files: [result.skillYamlPath, result.skillMdPath]
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
} catch (error) {
|
|
243
|
+
if (error instanceof CLIError) {
|
|
244
|
+
if (globalOpts.json) {
|
|
245
|
+
console.log(JSON.stringify({ error: error.message }));
|
|
246
|
+
} else {
|
|
247
|
+
logger.error(error.message);
|
|
248
|
+
}
|
|
249
|
+
process.exit(error.exitCode);
|
|
250
|
+
}
|
|
251
|
+
throw error;
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
return command;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
export {
|
|
258
|
+
ExitCode,
|
|
259
|
+
CLIError,
|
|
260
|
+
handleError,
|
|
261
|
+
logger,
|
|
262
|
+
SkillMetadataSchema,
|
|
263
|
+
generateSkillFiles,
|
|
264
|
+
createCreateSkillCommand
|
|
265
|
+
};
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import { Command } from "commander";
|
|
3
|
+
import { VERSION } from "@harness-engineering/core";
|
|
4
|
+
|
|
5
|
+
// src/utils/errors.ts
|
|
6
|
+
var ExitCode = {
|
|
7
|
+
SUCCESS: 0,
|
|
8
|
+
VALIDATION_FAILED: 1,
|
|
9
|
+
ERROR: 2
|
|
10
|
+
};
|
|
11
|
+
var CLIError = class extends Error {
|
|
12
|
+
exitCode;
|
|
13
|
+
constructor(message, exitCode = ExitCode.ERROR) {
|
|
14
|
+
super(message);
|
|
15
|
+
this.name = "CLIError";
|
|
16
|
+
this.exitCode = exitCode;
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
function formatError(error) {
|
|
20
|
+
if (error instanceof CLIError) {
|
|
21
|
+
return `Error: ${error.message}`;
|
|
22
|
+
}
|
|
23
|
+
if (error instanceof Error) {
|
|
24
|
+
return `Error: ${error.message}`;
|
|
25
|
+
}
|
|
26
|
+
return `Error: ${String(error)}`;
|
|
27
|
+
}
|
|
28
|
+
function handleError(error) {
|
|
29
|
+
const message = formatError(error);
|
|
30
|
+
console.error(message);
|
|
31
|
+
const exitCode = error instanceof CLIError ? error.exitCode : ExitCode.ERROR;
|
|
32
|
+
process.exit(exitCode);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// src/output/formatter.ts
|
|
36
|
+
import chalk from "chalk";
|
|
37
|
+
var OutputMode = {
|
|
38
|
+
JSON: "json",
|
|
39
|
+
TEXT: "text",
|
|
40
|
+
QUIET: "quiet",
|
|
41
|
+
VERBOSE: "verbose"
|
|
42
|
+
};
|
|
43
|
+
var OutputFormatter = class {
|
|
44
|
+
constructor(mode = OutputMode.TEXT) {
|
|
45
|
+
this.mode = mode;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Format raw data (for JSON mode)
|
|
49
|
+
*/
|
|
50
|
+
format(data) {
|
|
51
|
+
if (this.mode === OutputMode.JSON) {
|
|
52
|
+
return JSON.stringify(data, null, 2);
|
|
53
|
+
}
|
|
54
|
+
return String(data);
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Format validation result
|
|
58
|
+
*/
|
|
59
|
+
formatValidation(result) {
|
|
60
|
+
if (this.mode === OutputMode.JSON) {
|
|
61
|
+
return JSON.stringify(result, null, 2);
|
|
62
|
+
}
|
|
63
|
+
if (this.mode === OutputMode.QUIET) {
|
|
64
|
+
if (result.valid) return "";
|
|
65
|
+
return result.issues.map((i) => `${i.file ?? ""}: ${i.message}`).join("\n");
|
|
66
|
+
}
|
|
67
|
+
const lines = [];
|
|
68
|
+
if (result.valid) {
|
|
69
|
+
lines.push(chalk.green("v validation passed"));
|
|
70
|
+
} else {
|
|
71
|
+
lines.push(chalk.red(`x Validation failed (${result.issues.length} issues)`));
|
|
72
|
+
lines.push("");
|
|
73
|
+
for (const issue of result.issues) {
|
|
74
|
+
const location = issue.file ? issue.line ? `${issue.file}:${issue.line}` : issue.file : "unknown";
|
|
75
|
+
lines.push(` ${chalk.yellow("*")} ${chalk.dim(location)}`);
|
|
76
|
+
lines.push(` ${issue.message}`);
|
|
77
|
+
if (issue.suggestion && this.mode === OutputMode.VERBOSE) {
|
|
78
|
+
lines.push(` ${chalk.dim("->")} ${issue.suggestion}`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return lines.join("\n");
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Format a summary line
|
|
86
|
+
*/
|
|
87
|
+
formatSummary(label, value, success) {
|
|
88
|
+
if (this.mode === OutputMode.JSON || this.mode === OutputMode.QUIET) {
|
|
89
|
+
return "";
|
|
90
|
+
}
|
|
91
|
+
const icon = success ? chalk.green("v") : chalk.red("x");
|
|
92
|
+
return `${icon} ${label}: ${value}`;
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
// src/output/logger.ts
|
|
97
|
+
import chalk2 from "chalk";
|
|
98
|
+
var logger = {
|
|
99
|
+
info: (message) => console.log(chalk2.blue("i"), message),
|
|
100
|
+
success: (message) => console.log(chalk2.green("v"), message),
|
|
101
|
+
warn: (message) => console.log(chalk2.yellow("!"), message),
|
|
102
|
+
error: (message) => console.error(chalk2.red("x"), message),
|
|
103
|
+
dim: (message) => console.log(chalk2.dim(message)),
|
|
104
|
+
// For JSON output mode
|
|
105
|
+
raw: (data) => console.log(JSON.stringify(data, null, 2))
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
// src/config/loader.ts
|
|
109
|
+
import * as fs from "fs";
|
|
110
|
+
import * as path from "path";
|
|
111
|
+
import { Ok, Err } from "@harness-engineering/core";
|
|
112
|
+
|
|
113
|
+
// src/config/schema.ts
|
|
114
|
+
import { z } from "zod";
|
|
115
|
+
var LayerSchema = z.object({
|
|
116
|
+
name: z.string(),
|
|
117
|
+
pattern: z.string(),
|
|
118
|
+
allowedDependencies: z.array(z.string())
|
|
119
|
+
});
|
|
120
|
+
var ForbiddenImportSchema = z.object({
|
|
121
|
+
from: z.string(),
|
|
122
|
+
disallow: z.array(z.string()),
|
|
123
|
+
message: z.string().optional()
|
|
124
|
+
});
|
|
125
|
+
var BoundaryConfigSchema = z.object({
|
|
126
|
+
requireSchema: z.array(z.string())
|
|
127
|
+
});
|
|
128
|
+
var AgentConfigSchema = z.object({
|
|
129
|
+
executor: z.enum(["subprocess", "cloud", "noop"]).default("subprocess"),
|
|
130
|
+
timeout: z.number().default(3e5),
|
|
131
|
+
skills: z.array(z.string()).optional()
|
|
132
|
+
});
|
|
133
|
+
var EntropyConfigSchema = z.object({
|
|
134
|
+
excludePatterns: z.array(z.string()).default(["**/node_modules/**", "**/*.test.ts"]),
|
|
135
|
+
autoFix: z.boolean().default(false)
|
|
136
|
+
});
|
|
137
|
+
var HarnessConfigSchema = z.object({
|
|
138
|
+
version: z.literal(1),
|
|
139
|
+
name: z.string().optional(),
|
|
140
|
+
rootDir: z.string().default("."),
|
|
141
|
+
layers: z.array(LayerSchema).optional(),
|
|
142
|
+
forbiddenImports: z.array(ForbiddenImportSchema).optional(),
|
|
143
|
+
boundaries: BoundaryConfigSchema.optional(),
|
|
144
|
+
agentsMapPath: z.string().default("./AGENTS.md"),
|
|
145
|
+
docsDir: z.string().default("./docs"),
|
|
146
|
+
agent: AgentConfigSchema.optional(),
|
|
147
|
+
entropy: EntropyConfigSchema.optional()
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
// src/config/loader.ts
|
|
151
|
+
var CONFIG_FILENAMES = ["harness.config.json"];
|
|
152
|
+
function findConfigFile(startDir = process.cwd()) {
|
|
153
|
+
let currentDir = path.resolve(startDir);
|
|
154
|
+
const root = path.parse(currentDir).root;
|
|
155
|
+
while (currentDir !== root) {
|
|
156
|
+
for (const filename of CONFIG_FILENAMES) {
|
|
157
|
+
const configPath = path.join(currentDir, filename);
|
|
158
|
+
if (fs.existsSync(configPath)) {
|
|
159
|
+
return Ok(configPath);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
currentDir = path.dirname(currentDir);
|
|
163
|
+
}
|
|
164
|
+
return Err(new CLIError(
|
|
165
|
+
'No harness.config.json found. Run "harness init" to create one.',
|
|
166
|
+
ExitCode.ERROR
|
|
167
|
+
));
|
|
168
|
+
}
|
|
169
|
+
function loadConfig(configPath) {
|
|
170
|
+
if (!fs.existsSync(configPath)) {
|
|
171
|
+
return Err(new CLIError(
|
|
172
|
+
`Config file not found: ${configPath}`,
|
|
173
|
+
ExitCode.ERROR
|
|
174
|
+
));
|
|
175
|
+
}
|
|
176
|
+
let rawConfig;
|
|
177
|
+
try {
|
|
178
|
+
const content = fs.readFileSync(configPath, "utf-8");
|
|
179
|
+
rawConfig = JSON.parse(content);
|
|
180
|
+
} catch (error) {
|
|
181
|
+
return Err(new CLIError(
|
|
182
|
+
`Failed to parse config: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
183
|
+
ExitCode.ERROR
|
|
184
|
+
));
|
|
185
|
+
}
|
|
186
|
+
const parsed = HarnessConfigSchema.safeParse(rawConfig);
|
|
187
|
+
if (!parsed.success) {
|
|
188
|
+
const issues = parsed.error.issues.map((i) => ` - ${i.path.join(".")}: ${i.message}`).join("\n");
|
|
189
|
+
return Err(new CLIError(
|
|
190
|
+
`Invalid config:
|
|
191
|
+
${issues}`,
|
|
192
|
+
ExitCode.ERROR
|
|
193
|
+
));
|
|
194
|
+
}
|
|
195
|
+
return Ok(parsed.data);
|
|
196
|
+
}
|
|
197
|
+
function resolveConfig(configPath) {
|
|
198
|
+
if (configPath) {
|
|
199
|
+
return loadConfig(configPath);
|
|
200
|
+
}
|
|
201
|
+
const findResult = findConfigFile();
|
|
202
|
+
if (!findResult.ok) {
|
|
203
|
+
return findResult;
|
|
204
|
+
}
|
|
205
|
+
return loadConfig(findResult.value);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// src/index.ts
|
|
209
|
+
function createProgram() {
|
|
210
|
+
const program = new Command();
|
|
211
|
+
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");
|
|
212
|
+
return program;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export {
|
|
216
|
+
ExitCode,
|
|
217
|
+
CLIError,
|
|
218
|
+
handleError,
|
|
219
|
+
OutputMode,
|
|
220
|
+
OutputFormatter,
|
|
221
|
+
logger,
|
|
222
|
+
findConfigFile,
|
|
223
|
+
loadConfig,
|
|
224
|
+
resolveConfig,
|
|
225
|
+
createProgram
|
|
226
|
+
};
|