@harness-engineering/cli 1.9.0 → 1.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agents/skills/claude-code/harness-autopilot/SKILL.md +7 -2
- package/dist/agents/skills/claude-code/harness-brainstorming/SKILL.md +10 -1
- package/dist/agents/skills/claude-code/harness-execution/SKILL.md +2 -2
- package/dist/agents/skills/gemini-cli/harness-autopilot/SKILL.md +7 -2
- package/dist/agents/skills/gemini-cli/harness-brainstorming/SKILL.md +10 -1
- package/dist/agents/skills/gemini-cli/harness-execution/SKILL.md +2 -2
- package/dist/agents-md-EMRFLNBC.js +8 -0
- package/dist/architecture-5JNN5L3M.js +13 -0
- package/dist/bin/harness-mcp.d.ts +1 -0
- package/dist/bin/harness-mcp.js +28 -0
- package/dist/bin/harness.js +42 -8
- package/dist/check-phase-gate-WOKIYGAM.js +12 -0
- package/dist/chunk-46YA6FI3.js +293 -0
- package/dist/chunk-4PFMY3H7.js +248 -0
- package/dist/{chunk-6JIT7CEM.js → chunk-72GHBOL2.js} +1 -1
- package/dist/chunk-7X7ZAYMY.js +373 -0
- package/dist/chunk-B7HFEHWP.js +35 -0
- package/dist/chunk-BM3PWGXQ.js +14 -0
- package/dist/chunk-C2ERUR3L.js +255 -0
- package/dist/chunk-CWZ4Y2PO.js +189 -0
- package/dist/{chunk-ULSRSP53.js → chunk-ECUJQS3B.js} +11 -112
- package/dist/chunk-EOLRW32Q.js +72 -0
- package/dist/chunk-F3YDAJFQ.js +125 -0
- package/dist/chunk-F4PTVZWA.js +116 -0
- package/dist/chunk-FPIPT36X.js +187 -0
- package/dist/chunk-FX7SQHGD.js +103 -0
- package/dist/chunk-HIOXKZYF.js +15 -0
- package/dist/chunk-IDZNPTYD.js +16 -0
- package/dist/chunk-JSTQ3AWB.js +31 -0
- package/dist/chunk-K6XAPGML.js +27 -0
- package/dist/chunk-KET4QQZB.js +8 -0
- package/dist/chunk-LXU5M77O.js +4028 -0
- package/dist/chunk-MDUK2J2O.js +67 -0
- package/dist/chunk-MHBMTPW7.js +29 -0
- package/dist/chunk-MO4YQOMB.js +85 -0
- package/dist/chunk-NKDM3FMH.js +52 -0
- package/dist/{chunk-CGSHUJES.js → chunk-NX6DSZSM.js} +7 -26
- package/dist/chunk-OPXH4CQN.js +62 -0
- package/dist/{chunk-RTPHUDZS.js → chunk-PAHHT2IK.js} +466 -2714
- package/dist/chunk-PMTFPOCT.js +122 -0
- package/dist/chunk-PSXF277V.js +89 -0
- package/dist/chunk-Q6AB7W5Z.js +135 -0
- package/dist/chunk-QPEH2QPG.js +347 -0
- package/dist/chunk-TEFCFC4H.js +15 -0
- package/dist/chunk-TRAPF4IX.js +185 -0
- package/dist/chunk-VUCPTQ6G.js +67 -0
- package/dist/chunk-W6Y7ZW3Y.js +13 -0
- package/dist/chunk-ZOAWBDWU.js +72 -0
- package/dist/ci-workflow-ZBBUNTHQ.js +8 -0
- package/dist/constants-5JGUXPEK.js +6 -0
- package/dist/create-skill-LUWO46WF.js +11 -0
- package/dist/dist-D4RYGUZE.js +14 -0
- package/dist/dist-L7LAAQAS.js +18 -0
- package/dist/{dist-C5PYIQPF.js → dist-PBTNVK6K.js} +8 -6
- package/dist/docs-PTJGD6XI.js +12 -0
- package/dist/engine-SCMZ3G3E.js +8 -0
- package/dist/entropy-YIUBGKY7.js +12 -0
- package/dist/feedback-WEVQSLAA.js +18 -0
- package/dist/generate-agent-definitions-BU5LOJTI.js +15 -0
- package/dist/glob-helper-5OHBUQAI.js +52 -0
- package/dist/graph-loader-RLO3KRIX.js +8 -0
- package/dist/index.d.ts +11 -1
- package/dist/index.js +84 -33
- package/dist/loader-6S6PVGSF.js +10 -0
- package/dist/mcp-BNLBTCXZ.js +34 -0
- package/dist/performance-5TVW6SA6.js +24 -0
- package/dist/review-pipeline-4JTQAWKW.js +9 -0
- package/dist/runner-VMYLHWOC.js +6 -0
- package/dist/runtime-PXIM7UV6.js +9 -0
- package/dist/security-URYTKLGK.js +9 -0
- package/dist/skill-executor-KVS47DAU.js +8 -0
- package/dist/validate-KSDUUK2M.js +12 -0
- package/dist/validate-cross-check-WZAX357V.js +8 -0
- package/dist/version-KFFPOQAX.js +6 -0
- package/package.json +6 -4
- package/dist/create-skill-UZOHMXRU.js +0 -8
- package/dist/validate-cross-check-VG573VZO.js +0 -7
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import {
|
|
2
|
+
sanitizePath
|
|
3
|
+
} from "./chunk-W6Y7ZW3Y.js";
|
|
4
|
+
|
|
5
|
+
// src/mcp/tools/review-pipeline.ts
|
|
6
|
+
var runCodeReviewDefinition = {
|
|
7
|
+
name: "run_code_review",
|
|
8
|
+
description: "Run the unified 7-phase code review pipeline: gate, mechanical checks, context scoping, parallel agents, validation, deduplication, and output.",
|
|
9
|
+
inputSchema: {
|
|
10
|
+
type: "object",
|
|
11
|
+
properties: {
|
|
12
|
+
path: { type: "string", description: "Path to project root" },
|
|
13
|
+
diff: { type: "string", description: "Git diff string to review" },
|
|
14
|
+
commitMessage: {
|
|
15
|
+
type: "string",
|
|
16
|
+
description: "Most recent commit message (for change-type detection)"
|
|
17
|
+
},
|
|
18
|
+
comment: {
|
|
19
|
+
type: "boolean",
|
|
20
|
+
description: "Post inline comments to GitHub PR (requires prNumber and repo)"
|
|
21
|
+
},
|
|
22
|
+
ci: {
|
|
23
|
+
type: "boolean",
|
|
24
|
+
description: "Enable eligibility gate and non-interactive output"
|
|
25
|
+
},
|
|
26
|
+
deep: {
|
|
27
|
+
type: "boolean",
|
|
28
|
+
description: "Add threat modeling pass to security agent"
|
|
29
|
+
},
|
|
30
|
+
noMechanical: {
|
|
31
|
+
type: "boolean",
|
|
32
|
+
description: "Skip mechanical checks (useful if already run)"
|
|
33
|
+
},
|
|
34
|
+
prNumber: {
|
|
35
|
+
type: "number",
|
|
36
|
+
description: "PR number (required for --comment and CI gate)"
|
|
37
|
+
},
|
|
38
|
+
repo: {
|
|
39
|
+
type: "string",
|
|
40
|
+
description: "Repository in owner/repo format (required for --comment)"
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
required: ["path", "diff"]
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
async function handleRunCodeReview(input) {
|
|
47
|
+
try {
|
|
48
|
+
const { parseDiff, runReviewPipeline } = await import("./dist-PBTNVK6K.js");
|
|
49
|
+
const parseResult = parseDiff(input.diff);
|
|
50
|
+
if (!parseResult.ok) {
|
|
51
|
+
return {
|
|
52
|
+
content: [
|
|
53
|
+
{
|
|
54
|
+
type: "text",
|
|
55
|
+
text: `Error parsing diff: ${parseResult.error.message}`
|
|
56
|
+
}
|
|
57
|
+
],
|
|
58
|
+
isError: true
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
const codeChanges = parseResult.value;
|
|
62
|
+
const projectRoot = sanitizePath(input.path);
|
|
63
|
+
const diffInfo = {
|
|
64
|
+
changedFiles: codeChanges.files.map((f) => f.path),
|
|
65
|
+
newFiles: codeChanges.files.filter((f) => f.status === "added").map((f) => f.path),
|
|
66
|
+
deletedFiles: codeChanges.files.filter((f) => f.status === "deleted").map((f) => f.path),
|
|
67
|
+
totalDiffLines: input.diff.split("\n").length,
|
|
68
|
+
fileDiffs: new Map(
|
|
69
|
+
codeChanges.files.map((f) => [f.path, f.diff ?? ""])
|
|
70
|
+
)
|
|
71
|
+
};
|
|
72
|
+
const result = await runReviewPipeline({
|
|
73
|
+
projectRoot,
|
|
74
|
+
diff: diffInfo,
|
|
75
|
+
commitMessage: input.commitMessage ?? "",
|
|
76
|
+
flags: {
|
|
77
|
+
comment: input.comment ?? false,
|
|
78
|
+
ci: input.ci ?? false,
|
|
79
|
+
deep: input.deep ?? false,
|
|
80
|
+
noMechanical: input.noMechanical ?? false
|
|
81
|
+
},
|
|
82
|
+
...input.repo != null ? { repo: input.repo } : {}
|
|
83
|
+
});
|
|
84
|
+
return {
|
|
85
|
+
content: [
|
|
86
|
+
{
|
|
87
|
+
type: "text",
|
|
88
|
+
text: JSON.stringify(
|
|
89
|
+
{
|
|
90
|
+
skipped: result.skipped,
|
|
91
|
+
skipReason: result.skipReason,
|
|
92
|
+
stoppedByMechanical: result.stoppedByMechanical,
|
|
93
|
+
assessment: result.assessment,
|
|
94
|
+
findingCount: result.findings.length,
|
|
95
|
+
terminalOutput: result.terminalOutput,
|
|
96
|
+
githubCommentCount: result.githubComments.length,
|
|
97
|
+
exitCode: result.exitCode
|
|
98
|
+
},
|
|
99
|
+
null,
|
|
100
|
+
2
|
|
101
|
+
)
|
|
102
|
+
}
|
|
103
|
+
],
|
|
104
|
+
isError: false
|
|
105
|
+
};
|
|
106
|
+
} catch (error) {
|
|
107
|
+
return {
|
|
108
|
+
content: [
|
|
109
|
+
{
|
|
110
|
+
type: "text",
|
|
111
|
+
text: `Error: ${error instanceof Error ? error.message : String(error)}`
|
|
112
|
+
}
|
|
113
|
+
],
|
|
114
|
+
isError: true
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export {
|
|
120
|
+
runCodeReviewDefinition,
|
|
121
|
+
handleRunCodeReview
|
|
122
|
+
};
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import {
|
|
2
|
+
sanitizePath
|
|
3
|
+
} from "./chunk-W6Y7ZW3Y.js";
|
|
4
|
+
|
|
5
|
+
// src/mcp/tools/security.ts
|
|
6
|
+
import * as path from "path";
|
|
7
|
+
var runSecurityScanDefinition = {
|
|
8
|
+
name: "run_security_scan",
|
|
9
|
+
description: "Run the built-in security scanner on a project or specific files. Detects secrets, injection, XSS, weak crypto, and other vulnerabilities.",
|
|
10
|
+
inputSchema: {
|
|
11
|
+
type: "object",
|
|
12
|
+
properties: {
|
|
13
|
+
path: { type: "string", description: "Path to project root" },
|
|
14
|
+
files: {
|
|
15
|
+
type: "array",
|
|
16
|
+
items: { type: "string" },
|
|
17
|
+
description: "Optional list of specific files to scan. If omitted, scans all source files."
|
|
18
|
+
},
|
|
19
|
+
strict: {
|
|
20
|
+
type: "boolean",
|
|
21
|
+
description: "Override strict mode \u2014 promotes all warnings to errors"
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
required: ["path"]
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
async function handleRunSecurityScan(input) {
|
|
28
|
+
try {
|
|
29
|
+
const core = await import("./dist-PBTNVK6K.js");
|
|
30
|
+
const projectRoot = sanitizePath(input.path);
|
|
31
|
+
let configData = {};
|
|
32
|
+
try {
|
|
33
|
+
const fs = await import("fs");
|
|
34
|
+
const configPath = path.join(projectRoot, "harness.config.json");
|
|
35
|
+
if (fs.existsSync(configPath)) {
|
|
36
|
+
const raw = fs.readFileSync(configPath, "utf-8");
|
|
37
|
+
const parsed = JSON.parse(raw);
|
|
38
|
+
configData = parsed.security ?? {};
|
|
39
|
+
}
|
|
40
|
+
} catch {
|
|
41
|
+
}
|
|
42
|
+
if (input.strict !== void 0) {
|
|
43
|
+
configData.strict = input.strict;
|
|
44
|
+
}
|
|
45
|
+
const securityConfig = core.parseSecurityConfig(configData);
|
|
46
|
+
const scanner = new core.SecurityScanner(securityConfig);
|
|
47
|
+
scanner.configureForProject(projectRoot);
|
|
48
|
+
let filesToScan;
|
|
49
|
+
if (input.files && input.files.length > 0) {
|
|
50
|
+
filesToScan = input.files.map((f) => path.resolve(projectRoot, f));
|
|
51
|
+
} else {
|
|
52
|
+
const { globFiles } = await import("./glob-helper-5OHBUQAI.js");
|
|
53
|
+
filesToScan = await globFiles(projectRoot, securityConfig.exclude);
|
|
54
|
+
}
|
|
55
|
+
const result = await scanner.scanFiles(filesToScan);
|
|
56
|
+
return {
|
|
57
|
+
content: [
|
|
58
|
+
{
|
|
59
|
+
type: "text",
|
|
60
|
+
text: JSON.stringify({
|
|
61
|
+
...result,
|
|
62
|
+
summary: {
|
|
63
|
+
errors: result.findings.filter((f) => f.severity === "error").length,
|
|
64
|
+
warnings: result.findings.filter(
|
|
65
|
+
(f) => f.severity === "warning"
|
|
66
|
+
).length,
|
|
67
|
+
info: result.findings.filter((f) => f.severity === "info").length
|
|
68
|
+
}
|
|
69
|
+
})
|
|
70
|
+
}
|
|
71
|
+
]
|
|
72
|
+
};
|
|
73
|
+
} catch (error) {
|
|
74
|
+
return {
|
|
75
|
+
content: [
|
|
76
|
+
{
|
|
77
|
+
type: "text",
|
|
78
|
+
text: `Error: ${error instanceof Error ? error.message : String(error)}`
|
|
79
|
+
}
|
|
80
|
+
],
|
|
81
|
+
isError: true
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export {
|
|
87
|
+
runSecurityScanDefinition,
|
|
88
|
+
handleRunSecurityScan
|
|
89
|
+
};
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Err,
|
|
3
|
+
Ok
|
|
4
|
+
} from "./chunk-MHBMTPW7.js";
|
|
5
|
+
|
|
6
|
+
// src/persona/loader.ts
|
|
7
|
+
import * as fs from "fs";
|
|
8
|
+
import * as path from "path";
|
|
9
|
+
import YAML from "yaml";
|
|
10
|
+
|
|
11
|
+
// src/persona/schema.ts
|
|
12
|
+
import { z } from "zod";
|
|
13
|
+
var TriggerContextSchema = z.enum(["always", "on_pr", "on_commit", "on_review", "scheduled", "manual", "on_plan_approved"]).default("always");
|
|
14
|
+
var CommandStepSchema = z.object({
|
|
15
|
+
command: z.string(),
|
|
16
|
+
when: TriggerContextSchema
|
|
17
|
+
});
|
|
18
|
+
var SkillStepSchema = z.object({
|
|
19
|
+
skill: z.string(),
|
|
20
|
+
when: TriggerContextSchema,
|
|
21
|
+
output: z.enum(["inline", "artifact", "auto"]).default("auto")
|
|
22
|
+
});
|
|
23
|
+
var StepSchema = z.union([CommandStepSchema, SkillStepSchema]);
|
|
24
|
+
var PersonaTriggerSchema = z.discriminatedUnion("event", [
|
|
25
|
+
z.object({
|
|
26
|
+
event: z.literal("on_pr"),
|
|
27
|
+
conditions: z.object({
|
|
28
|
+
paths: z.array(z.string()).optional(),
|
|
29
|
+
min_files: z.number().optional()
|
|
30
|
+
}).optional()
|
|
31
|
+
}),
|
|
32
|
+
z.object({
|
|
33
|
+
event: z.literal("on_commit"),
|
|
34
|
+
conditions: z.object({ branches: z.array(z.string()).optional() }).optional()
|
|
35
|
+
}),
|
|
36
|
+
z.object({
|
|
37
|
+
event: z.literal("scheduled"),
|
|
38
|
+
cron: z.string()
|
|
39
|
+
}),
|
|
40
|
+
z.object({
|
|
41
|
+
event: z.literal("manual")
|
|
42
|
+
})
|
|
43
|
+
]);
|
|
44
|
+
var PersonaConfigSchema = z.object({
|
|
45
|
+
severity: z.enum(["error", "warning"]).default("error"),
|
|
46
|
+
autoFix: z.boolean().default(false),
|
|
47
|
+
timeout: z.number().default(3e5)
|
|
48
|
+
});
|
|
49
|
+
var PersonaOutputsSchema = z.object({
|
|
50
|
+
"agents-md": z.boolean().default(true),
|
|
51
|
+
"ci-workflow": z.boolean().default(true),
|
|
52
|
+
"runtime-config": z.boolean().default(true)
|
|
53
|
+
});
|
|
54
|
+
var PersonaSchemaV1 = z.object({
|
|
55
|
+
version: z.literal(1),
|
|
56
|
+
name: z.string(),
|
|
57
|
+
description: z.string(),
|
|
58
|
+
role: z.string(),
|
|
59
|
+
skills: z.array(z.string()),
|
|
60
|
+
commands: z.array(z.string()),
|
|
61
|
+
triggers: z.array(PersonaTriggerSchema),
|
|
62
|
+
config: PersonaConfigSchema.default({}),
|
|
63
|
+
outputs: PersonaOutputsSchema.default({})
|
|
64
|
+
});
|
|
65
|
+
var PersonaSchemaV2 = z.object({
|
|
66
|
+
version: z.literal(2),
|
|
67
|
+
name: z.string(),
|
|
68
|
+
description: z.string(),
|
|
69
|
+
role: z.string(),
|
|
70
|
+
skills: z.array(z.string()),
|
|
71
|
+
steps: z.array(StepSchema),
|
|
72
|
+
triggers: z.array(PersonaTriggerSchema),
|
|
73
|
+
config: PersonaConfigSchema.default({}),
|
|
74
|
+
outputs: PersonaOutputsSchema.default({})
|
|
75
|
+
});
|
|
76
|
+
var PersonaSchema = z.union([PersonaSchemaV1, PersonaSchemaV2]);
|
|
77
|
+
|
|
78
|
+
// src/persona/loader.ts
|
|
79
|
+
function normalizePersona(raw) {
|
|
80
|
+
if (raw.version === 1 && Array.isArray(raw.commands)) {
|
|
81
|
+
const { commands, ...rest } = raw;
|
|
82
|
+
return {
|
|
83
|
+
...rest,
|
|
84
|
+
steps: commands.map((cmd) => ({
|
|
85
|
+
command: cmd,
|
|
86
|
+
when: "always"
|
|
87
|
+
}))
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
return raw;
|
|
91
|
+
}
|
|
92
|
+
function loadPersona(filePath) {
|
|
93
|
+
try {
|
|
94
|
+
if (!fs.existsSync(filePath)) {
|
|
95
|
+
return Err(new Error(`Persona file not found: ${filePath}`));
|
|
96
|
+
}
|
|
97
|
+
const raw = fs.readFileSync(filePath, "utf-8");
|
|
98
|
+
const parsed = YAML.parse(raw);
|
|
99
|
+
const result = PersonaSchema.safeParse(parsed);
|
|
100
|
+
if (!result.success) {
|
|
101
|
+
return Err(new Error(`Invalid persona ${filePath}: ${result.error.message}`));
|
|
102
|
+
}
|
|
103
|
+
return Ok(normalizePersona(result.data));
|
|
104
|
+
} catch (error) {
|
|
105
|
+
return Err(
|
|
106
|
+
new Error(`Failed to load persona: ${error instanceof Error ? error.message : String(error)}`)
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
function listPersonas(dir) {
|
|
111
|
+
try {
|
|
112
|
+
if (!fs.existsSync(dir)) return Ok([]);
|
|
113
|
+
const entries = fs.readdirSync(dir).filter((f) => f.endsWith(".yaml") || f.endsWith(".yml"));
|
|
114
|
+
const personas = [];
|
|
115
|
+
for (const entry of entries) {
|
|
116
|
+
const filePath = path.join(dir, entry);
|
|
117
|
+
const result = loadPersona(filePath);
|
|
118
|
+
if (result.ok) {
|
|
119
|
+
personas.push({ name: result.value.name, description: result.value.description, filePath });
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return Ok(personas);
|
|
123
|
+
} catch (error) {
|
|
124
|
+
return Err(
|
|
125
|
+
new Error(
|
|
126
|
+
`Failed to list personas: ${error instanceof Error ? error.message : String(error)}`
|
|
127
|
+
)
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export {
|
|
133
|
+
loadPersona,
|
|
134
|
+
listPersonas
|
|
135
|
+
};
|
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
// ../linter-gen/dist/generator/orchestrator.js
|
|
2
|
+
import * as fs3 from "fs/promises";
|
|
3
|
+
import * as path3 from "path";
|
|
4
|
+
|
|
5
|
+
// ../linter-gen/dist/parser/config-parser.js
|
|
6
|
+
import * as fs from "fs/promises";
|
|
7
|
+
import * as yaml from "yaml";
|
|
8
|
+
|
|
9
|
+
// ../linter-gen/dist/schema/linter-config.js
|
|
10
|
+
import { z } from "zod";
|
|
11
|
+
var RuleConfigSchema = z.object({
|
|
12
|
+
/** Rule name in kebab-case (e.g., 'no-ui-in-services') */
|
|
13
|
+
name: z.string().regex(/^[a-z][a-z0-9]*(-[a-z0-9]+)*$/, "Rule name must be kebab-case"),
|
|
14
|
+
/** Rule type - determines which template to use */
|
|
15
|
+
type: z.string().min(1),
|
|
16
|
+
/** ESLint severity level */
|
|
17
|
+
severity: z.enum(["error", "warn", "off"]).default("error"),
|
|
18
|
+
/** Template-specific configuration */
|
|
19
|
+
config: z.record(z.unknown())
|
|
20
|
+
});
|
|
21
|
+
var LinterConfigSchema = z.object({
|
|
22
|
+
/** Config version - currently only 1 is supported */
|
|
23
|
+
version: z.literal(1),
|
|
24
|
+
/** Output directory for generated rules */
|
|
25
|
+
output: z.string().min(1),
|
|
26
|
+
/** Optional explicit template path mappings (type → path) */
|
|
27
|
+
templates: z.record(z.string()).optional(),
|
|
28
|
+
/** Rules to generate */
|
|
29
|
+
rules: z.array(RuleConfigSchema).min(1, "At least one rule is required")
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
// ../linter-gen/dist/parser/config-parser.js
|
|
33
|
+
var ParseError = class extends Error {
|
|
34
|
+
code;
|
|
35
|
+
cause;
|
|
36
|
+
constructor(message, code, cause) {
|
|
37
|
+
super(message);
|
|
38
|
+
this.code = code;
|
|
39
|
+
this.cause = cause;
|
|
40
|
+
this.name = "ParseError";
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
async function parseConfig(configPath) {
|
|
44
|
+
let content;
|
|
45
|
+
try {
|
|
46
|
+
content = await fs.readFile(configPath, "utf-8");
|
|
47
|
+
} catch (err) {
|
|
48
|
+
if (err.code === "ENOENT") {
|
|
49
|
+
return {
|
|
50
|
+
success: false,
|
|
51
|
+
error: new ParseError(`Config file not found: ${configPath}`, "FILE_NOT_FOUND", err)
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
return {
|
|
55
|
+
success: false,
|
|
56
|
+
error: new ParseError(`Failed to read config file: ${configPath}`, "FILE_READ_ERROR", err)
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
let parsed;
|
|
60
|
+
try {
|
|
61
|
+
parsed = yaml.parse(content);
|
|
62
|
+
} catch (err) {
|
|
63
|
+
return {
|
|
64
|
+
success: false,
|
|
65
|
+
error: new ParseError(`Invalid YAML syntax in ${configPath}: ${err.message}`, "YAML_PARSE_ERROR", err)
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
const result = LinterConfigSchema.safeParse(parsed);
|
|
69
|
+
if (!result.success) {
|
|
70
|
+
const issues = result.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
|
|
71
|
+
return {
|
|
72
|
+
success: false,
|
|
73
|
+
error: new ParseError(`Invalid config: ${issues}`, "VALIDATION_ERROR", result.error)
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
return {
|
|
77
|
+
success: true,
|
|
78
|
+
data: result.data,
|
|
79
|
+
configPath
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// ../linter-gen/dist/engine/template-loader.js
|
|
84
|
+
import * as fs2 from "fs/promises";
|
|
85
|
+
import * as path from "path";
|
|
86
|
+
import { fileURLToPath } from "url";
|
|
87
|
+
var __filename = fileURLToPath(import.meta.url);
|
|
88
|
+
var __dirname = path.dirname(__filename);
|
|
89
|
+
var TemplateLoadError = class extends Error {
|
|
90
|
+
code;
|
|
91
|
+
cause;
|
|
92
|
+
constructor(message, code, cause) {
|
|
93
|
+
super(message);
|
|
94
|
+
this.code = code;
|
|
95
|
+
this.cause = cause;
|
|
96
|
+
this.name = "TemplateLoadError";
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
var BUILTIN_TEMPLATES = ["import-restriction", "boundary-validation", "dependency-graph"];
|
|
100
|
+
async function fileExists(filePath) {
|
|
101
|
+
try {
|
|
102
|
+
await fs2.access(filePath);
|
|
103
|
+
return true;
|
|
104
|
+
} catch {
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
async function loadTemplateFile(filePath, type) {
|
|
109
|
+
try {
|
|
110
|
+
const content = await fs2.readFile(filePath, "utf-8");
|
|
111
|
+
return {
|
|
112
|
+
success: true,
|
|
113
|
+
source: { type, path: filePath, content }
|
|
114
|
+
};
|
|
115
|
+
} catch (err) {
|
|
116
|
+
return {
|
|
117
|
+
success: false,
|
|
118
|
+
error: new TemplateLoadError(`Failed to read template: ${filePath}`, "TEMPLATE_READ_ERROR", err)
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
async function loadTemplate(ruleType, templatesConfig, configDir) {
|
|
123
|
+
if (templatesConfig?.[ruleType]) {
|
|
124
|
+
const explicitPath = path.resolve(configDir, templatesConfig[ruleType]);
|
|
125
|
+
if (await fileExists(explicitPath)) {
|
|
126
|
+
return loadTemplateFile(explicitPath, "explicit");
|
|
127
|
+
}
|
|
128
|
+
return {
|
|
129
|
+
success: false,
|
|
130
|
+
error: new TemplateLoadError(`Explicit template not found: ${explicitPath}`, "TEMPLATE_NOT_FOUND")
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
const conventionPath = path.join(configDir, "templates", `${ruleType}.ts.hbs`);
|
|
134
|
+
if (await fileExists(conventionPath)) {
|
|
135
|
+
return loadTemplateFile(conventionPath, "convention");
|
|
136
|
+
}
|
|
137
|
+
if (BUILTIN_TEMPLATES.includes(ruleType)) {
|
|
138
|
+
const builtinPath = path.join(__dirname, "..", "templates", `${ruleType}.ts.hbs`);
|
|
139
|
+
if (await fileExists(builtinPath)) {
|
|
140
|
+
return loadTemplateFile(builtinPath, "builtin");
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return {
|
|
144
|
+
success: false,
|
|
145
|
+
error: new TemplateLoadError(`Template not found for type '${ruleType}'. Checked: explicit config, ./templates/${ruleType}.ts.hbs, built-in templates.`, "TEMPLATE_NOT_FOUND")
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// ../linter-gen/dist/generator/rule-generator.js
|
|
150
|
+
import * as path2 from "path";
|
|
151
|
+
|
|
152
|
+
// ../linter-gen/dist/engine/context-builder.js
|
|
153
|
+
var GENERATOR_VERSION = "0.1.0";
|
|
154
|
+
function toCamelCase(str) {
|
|
155
|
+
return str.replace(/-([a-z0-9])/g, (_, char) => char.toUpperCase());
|
|
156
|
+
}
|
|
157
|
+
function toPascalCase(str) {
|
|
158
|
+
const camel = toCamelCase(str);
|
|
159
|
+
return camel.charAt(0).toUpperCase() + camel.slice(1);
|
|
160
|
+
}
|
|
161
|
+
function buildRuleContext(rule, configPath) {
|
|
162
|
+
return {
|
|
163
|
+
name: rule.name,
|
|
164
|
+
nameCamel: toCamelCase(rule.name),
|
|
165
|
+
namePascal: toPascalCase(rule.name),
|
|
166
|
+
severity: rule.severity,
|
|
167
|
+
config: rule.config,
|
|
168
|
+
meta: {
|
|
169
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
170
|
+
generatorVersion: GENERATOR_VERSION,
|
|
171
|
+
configPath
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// ../linter-gen/dist/engine/template-renderer.js
|
|
177
|
+
import Handlebars from "handlebars";
|
|
178
|
+
var TemplateError = class extends Error {
|
|
179
|
+
cause;
|
|
180
|
+
constructor(message, cause) {
|
|
181
|
+
super(message);
|
|
182
|
+
this.cause = cause;
|
|
183
|
+
this.name = "TemplateError";
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
function toCamelCase2(str) {
|
|
187
|
+
return str.replace(/-([a-z0-9])/g, (_, char) => char.toUpperCase());
|
|
188
|
+
}
|
|
189
|
+
function toPascalCase2(str) {
|
|
190
|
+
const camel = toCamelCase2(str);
|
|
191
|
+
return camel.charAt(0).toUpperCase() + camel.slice(1);
|
|
192
|
+
}
|
|
193
|
+
Handlebars.registerHelper("json", (obj) => JSON.stringify(obj));
|
|
194
|
+
Handlebars.registerHelper("jsonPretty", (obj) => JSON.stringify(obj, null, 2));
|
|
195
|
+
Handlebars.registerHelper("camelCase", (str) => toCamelCase2(str));
|
|
196
|
+
Handlebars.registerHelper("pascalCase", (str) => toPascalCase2(str));
|
|
197
|
+
function renderTemplate(templateSource, context) {
|
|
198
|
+
try {
|
|
199
|
+
const compiled = Handlebars.compile(templateSource, { strict: true });
|
|
200
|
+
const output = compiled(context);
|
|
201
|
+
return { success: true, output };
|
|
202
|
+
} catch (err) {
|
|
203
|
+
return {
|
|
204
|
+
success: false,
|
|
205
|
+
error: new TemplateError(`Template rendering failed: ${err.message}`, err)
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// ../linter-gen/dist/generator/rule-generator.js
|
|
211
|
+
function generateRule(rule, template, outputDir, configPath) {
|
|
212
|
+
const context = buildRuleContext(rule, configPath);
|
|
213
|
+
const renderResult = renderTemplate(template.content, context);
|
|
214
|
+
if (!renderResult.success) {
|
|
215
|
+
return {
|
|
216
|
+
success: false,
|
|
217
|
+
error: renderResult.error,
|
|
218
|
+
ruleName: rule.name
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
const outputPath = path2.join(outputDir, `${rule.name}.ts`);
|
|
222
|
+
return {
|
|
223
|
+
success: true,
|
|
224
|
+
rule: {
|
|
225
|
+
name: rule.name,
|
|
226
|
+
outputPath,
|
|
227
|
+
content: renderResult.output
|
|
228
|
+
}
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// ../linter-gen/dist/generator/index-generator.js
|
|
233
|
+
function toCamelCase3(str) {
|
|
234
|
+
return str.replace(/-([a-z0-9])/g, (_, char) => char.toUpperCase());
|
|
235
|
+
}
|
|
236
|
+
function generateIndex(ruleNames) {
|
|
237
|
+
const imports = ruleNames.map((name) => {
|
|
238
|
+
const camel = toCamelCase3(name);
|
|
239
|
+
return `import ${camel} from './${name}';`;
|
|
240
|
+
}).join("\n");
|
|
241
|
+
const rulesObject = ruleNames.map((name) => {
|
|
242
|
+
const camel = toCamelCase3(name);
|
|
243
|
+
return ` '${name}': ${camel},`;
|
|
244
|
+
}).join("\n");
|
|
245
|
+
const namedExports = ruleNames.map(toCamelCase3).join(", ");
|
|
246
|
+
return `// Generated by @harness-engineering/linter-gen
|
|
247
|
+
// Do not edit manually - regenerate from harness-linter.yml
|
|
248
|
+
|
|
249
|
+
${imports}
|
|
250
|
+
|
|
251
|
+
export const rules = {
|
|
252
|
+
${rulesObject}
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
export { ${namedExports} };
|
|
256
|
+
`;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// ../linter-gen/dist/generator/orchestrator.js
|
|
260
|
+
async function validate(options) {
|
|
261
|
+
const parseResult = await parseConfig(options.configPath);
|
|
262
|
+
if (!parseResult.success) {
|
|
263
|
+
return { success: false, error: parseResult.error };
|
|
264
|
+
}
|
|
265
|
+
return { success: true, ruleCount: parseResult.data.rules.length };
|
|
266
|
+
}
|
|
267
|
+
async function generate(options) {
|
|
268
|
+
const errors = [];
|
|
269
|
+
const parseResult = await parseConfig(options.configPath);
|
|
270
|
+
if (!parseResult.success) {
|
|
271
|
+
return { success: false, errors: [{ type: "parse", error: parseResult.error }] };
|
|
272
|
+
}
|
|
273
|
+
const config = parseResult.data;
|
|
274
|
+
const configDir = path3.dirname(path3.resolve(options.configPath));
|
|
275
|
+
const outputDir = options.outputDir ? path3.resolve(options.outputDir) : path3.resolve(configDir, config.output);
|
|
276
|
+
if (options.clean && !options.dryRun) {
|
|
277
|
+
try {
|
|
278
|
+
await fs3.rm(outputDir, { recursive: true, force: true });
|
|
279
|
+
} catch {
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
if (!options.dryRun) {
|
|
283
|
+
await fs3.mkdir(outputDir, { recursive: true });
|
|
284
|
+
}
|
|
285
|
+
const generatedRules = [];
|
|
286
|
+
for (const rule of config.rules) {
|
|
287
|
+
const templateResult = await loadTemplate(rule.type, config.templates, configDir);
|
|
288
|
+
if (!templateResult.success) {
|
|
289
|
+
errors.push({
|
|
290
|
+
type: "template",
|
|
291
|
+
error: templateResult.error,
|
|
292
|
+
ruleName: rule.name
|
|
293
|
+
});
|
|
294
|
+
continue;
|
|
295
|
+
}
|
|
296
|
+
const ruleResult = generateRule(rule, templateResult.source, outputDir, options.configPath);
|
|
297
|
+
if (!ruleResult.success) {
|
|
298
|
+
errors.push({
|
|
299
|
+
type: "render",
|
|
300
|
+
error: ruleResult.error,
|
|
301
|
+
ruleName: ruleResult.ruleName
|
|
302
|
+
});
|
|
303
|
+
continue;
|
|
304
|
+
}
|
|
305
|
+
if (!options.dryRun) {
|
|
306
|
+
try {
|
|
307
|
+
await fs3.writeFile(ruleResult.rule.outputPath, ruleResult.rule.content, "utf-8");
|
|
308
|
+
} catch (err) {
|
|
309
|
+
errors.push({
|
|
310
|
+
type: "write",
|
|
311
|
+
error: err,
|
|
312
|
+
path: ruleResult.rule.outputPath
|
|
313
|
+
});
|
|
314
|
+
continue;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
generatedRules.push(rule.name);
|
|
318
|
+
}
|
|
319
|
+
if (generatedRules.length > 0 && !options.dryRun) {
|
|
320
|
+
const indexContent = generateIndex(generatedRules);
|
|
321
|
+
const indexPath = path3.join(outputDir, "index.ts");
|
|
322
|
+
try {
|
|
323
|
+
await fs3.writeFile(indexPath, indexContent, "utf-8");
|
|
324
|
+
} catch (err) {
|
|
325
|
+
errors.push({ type: "write", error: err, path: indexPath });
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
if (errors.length > 0) {
|
|
329
|
+
return { success: false, errors };
|
|
330
|
+
}
|
|
331
|
+
return {
|
|
332
|
+
success: true,
|
|
333
|
+
rulesGenerated: generatedRules,
|
|
334
|
+
outputDir,
|
|
335
|
+
dryRun: options.dryRun ?? false
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
export {
|
|
340
|
+
RuleConfigSchema,
|
|
341
|
+
LinterConfigSchema,
|
|
342
|
+
ParseError,
|
|
343
|
+
TemplateLoadError,
|
|
344
|
+
TemplateError,
|
|
345
|
+
validate,
|
|
346
|
+
generate
|
|
347
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// src/persona/constants.ts
|
|
2
|
+
var ALLOWED_PERSONA_COMMANDS = /* @__PURE__ */ new Set([
|
|
3
|
+
"validate",
|
|
4
|
+
"check-deps",
|
|
5
|
+
"check-docs",
|
|
6
|
+
"check-perf",
|
|
7
|
+
"check-security",
|
|
8
|
+
"cleanup",
|
|
9
|
+
"fix-drift",
|
|
10
|
+
"add"
|
|
11
|
+
]);
|
|
12
|
+
|
|
13
|
+
export {
|
|
14
|
+
ALLOWED_PERSONA_COMMANDS
|
|
15
|
+
};
|