Package not found. Please check the package name and try again.
@harness-engineering/cli 1.8.2 → 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/cleanup-dead-code/SKILL.md +3 -3
- package/dist/agents/skills/claude-code/harness-autopilot/SKILL.md +20 -3
- package/dist/agents/skills/claude-code/harness-brainstorming/SKILL.md +55 -5
- package/dist/agents/skills/claude-code/harness-code-review/SKILL.md +36 -15
- package/dist/agents/skills/claude-code/harness-codebase-cleanup/SKILL.md +1 -1
- package/dist/agents/skills/claude-code/harness-execution/SKILL.md +70 -13
- package/dist/agents/skills/claude-code/harness-planning/SKILL.md +41 -3
- package/dist/agents/skills/claude-code/harness-pre-commit-review/SKILL.md +28 -3
- package/dist/agents/skills/claude-code/harness-release-readiness/SKILL.md +14 -2
- package/dist/agents/skills/claude-code/harness-verification/SKILL.md +18 -2
- package/dist/agents/skills/gemini-cli/cleanup-dead-code/SKILL.md +3 -3
- package/dist/agents/skills/gemini-cli/harness-autopilot/SKILL.md +20 -3
- package/dist/agents/skills/gemini-cli/harness-brainstorming/SKILL.md +55 -5
- package/dist/agents/skills/gemini-cli/harness-code-review/SKILL.md +36 -15
- package/dist/agents/skills/gemini-cli/harness-codebase-cleanup/SKILL.md +1 -1
- package/dist/agents/skills/gemini-cli/harness-execution/SKILL.md +70 -13
- package/dist/agents/skills/gemini-cli/harness-planning/SKILL.md +41 -3
- package/dist/agents/skills/gemini-cli/harness-pre-commit-review/SKILL.md +28 -3
- package/dist/agents/skills/gemini-cli/harness-release-readiness/SKILL.md +14 -2
- package/dist/agents/skills/gemini-cli/harness-verification/SKILL.md +18 -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-LB4GRDDV.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-SAB3VXOW.js → chunk-NX6DSZSM.js} +144 -111
- package/dist/chunk-OPXH4CQN.js +62 -0
- package/dist/{chunk-Y7U5AYAL.js → chunk-PAHHT2IK.js} +471 -2719
- 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-K6KTTN3I.js → dist-I7DB5VKB.js} +237 -0
- package/dist/dist-L7LAAQAS.js +18 -0
- package/dist/{dist-ZODQVGC4.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 +7 -5
- package/dist/create-skill-UZOHMXRU.js +0 -8
- package/dist/validate-cross-check-DLNK423G.js +0 -7
|
@@ -1,9 +1,80 @@
|
|
|
1
|
+
import {
|
|
2
|
+
generateCIWorkflow
|
|
3
|
+
} from "./chunk-VUCPTQ6G.js";
|
|
4
|
+
import {
|
|
5
|
+
OutputFormatter,
|
|
6
|
+
OutputMode,
|
|
7
|
+
createCheckPhaseGateCommand,
|
|
8
|
+
findFiles,
|
|
9
|
+
resolveConfig
|
|
10
|
+
} from "./chunk-7X7ZAYMY.js";
|
|
11
|
+
import {
|
|
12
|
+
createGenerateAgentDefinitionsCommand,
|
|
13
|
+
generateAgentDefinitions
|
|
14
|
+
} from "./chunk-46YA6FI3.js";
|
|
15
|
+
import {
|
|
16
|
+
listPersonas,
|
|
17
|
+
loadPersona
|
|
18
|
+
} from "./chunk-Q6AB7W5Z.js";
|
|
19
|
+
import {
|
|
20
|
+
runPersona
|
|
21
|
+
} from "./chunk-TRAPF4IX.js";
|
|
22
|
+
import {
|
|
23
|
+
executeSkill
|
|
24
|
+
} from "./chunk-F3YDAJFQ.js";
|
|
25
|
+
import {
|
|
26
|
+
ALLOWED_PERSONA_COMMANDS
|
|
27
|
+
} from "./chunk-TEFCFC4H.js";
|
|
28
|
+
import {
|
|
29
|
+
createCreateSkillCommand
|
|
30
|
+
} from "./chunk-ECUJQS3B.js";
|
|
31
|
+
import {
|
|
32
|
+
logger
|
|
33
|
+
} from "./chunk-HIOXKZYF.js";
|
|
34
|
+
import {
|
|
35
|
+
generate,
|
|
36
|
+
validate
|
|
37
|
+
} from "./chunk-QPEH2QPG.js";
|
|
38
|
+
import {
|
|
39
|
+
generateRuntime
|
|
40
|
+
} from "./chunk-JSTQ3AWB.js";
|
|
41
|
+
import {
|
|
42
|
+
toKebabCase
|
|
43
|
+
} from "./chunk-KET4QQZB.js";
|
|
44
|
+
import {
|
|
45
|
+
generateAgentsMd
|
|
46
|
+
} from "./chunk-NKDM3FMH.js";
|
|
47
|
+
import {
|
|
48
|
+
createGenerateSlashCommandsCommand,
|
|
49
|
+
generateSlashCommands,
|
|
50
|
+
handleOrphanDeletion
|
|
51
|
+
} from "./chunk-LXU5M77O.js";
|
|
52
|
+
import {
|
|
53
|
+
VALID_PLATFORMS
|
|
54
|
+
} from "./chunk-ZOAWBDWU.js";
|
|
55
|
+
import {
|
|
56
|
+
resolvePersonasDir,
|
|
57
|
+
resolveSkillsDir,
|
|
58
|
+
resolveTemplatesDir
|
|
59
|
+
} from "./chunk-EOLRW32Q.js";
|
|
60
|
+
import {
|
|
61
|
+
CLIError,
|
|
62
|
+
ExitCode,
|
|
63
|
+
handleError
|
|
64
|
+
} from "./chunk-B7HFEHWP.js";
|
|
65
|
+
import {
|
|
66
|
+
SkillMetadataSchema
|
|
67
|
+
} from "./chunk-MDUK2J2O.js";
|
|
68
|
+
import {
|
|
69
|
+
CLI_VERSION
|
|
70
|
+
} from "./chunk-BM3PWGXQ.js";
|
|
71
|
+
import {
|
|
72
|
+
TemplateEngine
|
|
73
|
+
} from "./chunk-C2ERUR3L.js";
|
|
1
74
|
import {
|
|
2
75
|
BaselineManager,
|
|
3
76
|
CriticalPathResolver,
|
|
4
77
|
EntropyAnalyzer,
|
|
5
|
-
Err,
|
|
6
|
-
Ok,
|
|
7
78
|
SecurityScanner,
|
|
8
79
|
TypeScriptParser,
|
|
9
80
|
appendLearning,
|
|
@@ -31,277 +102,25 @@ import {
|
|
|
31
102
|
validateAgentsMap,
|
|
32
103
|
validateDependencies,
|
|
33
104
|
validateKnowledgeMap
|
|
34
|
-
} from "./chunk-
|
|
105
|
+
} from "./chunk-NX6DSZSM.js";
|
|
35
106
|
import {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
createCreateSkillCommand,
|
|
40
|
-
handleError,
|
|
41
|
-
logger
|
|
42
|
-
} from "./chunk-ULSRSP53.js";
|
|
107
|
+
Err,
|
|
108
|
+
Ok
|
|
109
|
+
} from "./chunk-MHBMTPW7.js";
|
|
43
110
|
|
|
44
111
|
// src/index.ts
|
|
45
|
-
import { Command as
|
|
46
|
-
|
|
47
|
-
// src/version.ts
|
|
48
|
-
import { createRequire } from "module";
|
|
49
|
-
var require_ = createRequire(import.meta.url);
|
|
50
|
-
var resolved;
|
|
51
|
-
try {
|
|
52
|
-
resolved = require_("../package.json").version ?? "0.0.0";
|
|
53
|
-
} catch {
|
|
54
|
-
resolved = "0.0.0";
|
|
55
|
-
}
|
|
56
|
-
var CLI_VERSION = resolved;
|
|
112
|
+
import { Command as Command41 } from "commander";
|
|
57
113
|
|
|
58
114
|
// src/commands/validate.ts
|
|
59
115
|
import { Command } from "commander";
|
|
60
|
-
import * as path2 from "path";
|
|
61
|
-
|
|
62
|
-
// src/config/loader.ts
|
|
63
|
-
import * as fs from "fs";
|
|
64
116
|
import * as path from "path";
|
|
65
|
-
|
|
66
|
-
// src/config/schema.ts
|
|
67
|
-
import { z } from "zod";
|
|
68
|
-
var LayerSchema = z.object({
|
|
69
|
-
name: z.string(),
|
|
70
|
-
pattern: z.string(),
|
|
71
|
-
allowedDependencies: z.array(z.string())
|
|
72
|
-
});
|
|
73
|
-
var ForbiddenImportSchema = z.object({
|
|
74
|
-
from: z.string(),
|
|
75
|
-
disallow: z.array(z.string()),
|
|
76
|
-
message: z.string().optional()
|
|
77
|
-
});
|
|
78
|
-
var BoundaryConfigSchema = z.object({
|
|
79
|
-
requireSchema: z.array(z.string())
|
|
80
|
-
});
|
|
81
|
-
var AgentConfigSchema = z.object({
|
|
82
|
-
executor: z.enum(["subprocess", "cloud", "noop"]).default("subprocess"),
|
|
83
|
-
timeout: z.number().default(3e5),
|
|
84
|
-
skills: z.array(z.string()).optional()
|
|
85
|
-
});
|
|
86
|
-
var EntropyConfigSchema = z.object({
|
|
87
|
-
excludePatterns: z.array(z.string()).default(["**/node_modules/**", "**/*.test.ts"]),
|
|
88
|
-
autoFix: z.boolean().default(false)
|
|
89
|
-
});
|
|
90
|
-
var PhaseGateMappingSchema = z.object({
|
|
91
|
-
implPattern: z.string(),
|
|
92
|
-
specPattern: z.string()
|
|
93
|
-
});
|
|
94
|
-
var PhaseGatesConfigSchema = z.object({
|
|
95
|
-
enabled: z.boolean().default(false),
|
|
96
|
-
severity: z.enum(["error", "warning"]).default("error"),
|
|
97
|
-
mappings: z.array(PhaseGateMappingSchema).default([{ implPattern: "src/**/*.ts", specPattern: "docs/changes/{feature}/proposal.md" }])
|
|
98
|
-
});
|
|
99
|
-
var SecurityConfigSchema = z.object({
|
|
100
|
-
enabled: z.boolean().default(true),
|
|
101
|
-
strict: z.boolean().default(false),
|
|
102
|
-
rules: z.record(z.string(), z.enum(["off", "error", "warning", "info"])).optional(),
|
|
103
|
-
exclude: z.array(z.string()).optional()
|
|
104
|
-
}).passthrough();
|
|
105
|
-
var PerformanceConfigSchema = z.object({
|
|
106
|
-
complexity: z.record(z.unknown()).optional(),
|
|
107
|
-
coupling: z.record(z.unknown()).optional(),
|
|
108
|
-
sizeBudget: z.record(z.unknown()).optional()
|
|
109
|
-
}).passthrough();
|
|
110
|
-
var DesignConfigSchema = z.object({
|
|
111
|
-
strictness: z.enum(["strict", "standard", "permissive"]).default("standard"),
|
|
112
|
-
platforms: z.array(z.enum(["web", "mobile"])).default([]),
|
|
113
|
-
tokenPath: z.string().optional(),
|
|
114
|
-
aestheticIntent: z.string().optional()
|
|
115
|
-
});
|
|
116
|
-
var I18nCoverageConfigSchema = z.object({
|
|
117
|
-
minimumPercent: z.number().min(0).max(100).default(100),
|
|
118
|
-
requirePlurals: z.boolean().default(true),
|
|
119
|
-
detectUntranslated: z.boolean().default(true)
|
|
120
|
-
});
|
|
121
|
-
var I18nMcpConfigSchema = z.object({
|
|
122
|
-
server: z.string(),
|
|
123
|
-
projectId: z.string().optional()
|
|
124
|
-
});
|
|
125
|
-
var I18nConfigSchema = z.object({
|
|
126
|
-
enabled: z.boolean().default(false),
|
|
127
|
-
strictness: z.enum(["strict", "standard", "permissive"]).default("standard"),
|
|
128
|
-
sourceLocale: z.string().default("en"),
|
|
129
|
-
targetLocales: z.array(z.string()).default([]),
|
|
130
|
-
framework: z.enum([
|
|
131
|
-
"auto",
|
|
132
|
-
"i18next",
|
|
133
|
-
"react-intl",
|
|
134
|
-
"vue-i18n",
|
|
135
|
-
"flutter-intl",
|
|
136
|
-
"apple",
|
|
137
|
-
"android",
|
|
138
|
-
"custom"
|
|
139
|
-
]).default("auto"),
|
|
140
|
-
format: z.string().default("json"),
|
|
141
|
-
messageFormat: z.enum(["icu", "i18next", "custom"]).default("icu"),
|
|
142
|
-
keyConvention: z.enum(["dot-notation", "snake_case", "camelCase", "custom"]).default("dot-notation"),
|
|
143
|
-
translationPaths: z.record(z.string(), z.string()).optional(),
|
|
144
|
-
platforms: z.array(z.enum(["web", "mobile", "backend"])).default([]),
|
|
145
|
-
industry: z.string().optional(),
|
|
146
|
-
coverage: I18nCoverageConfigSchema.optional(),
|
|
147
|
-
pseudoLocale: z.string().optional(),
|
|
148
|
-
mcp: I18nMcpConfigSchema.optional()
|
|
149
|
-
});
|
|
150
|
-
var ModelTierConfigSchema = z.object({
|
|
151
|
-
fast: z.string().optional(),
|
|
152
|
-
standard: z.string().optional(),
|
|
153
|
-
strong: z.string().optional()
|
|
154
|
-
});
|
|
155
|
-
var ReviewConfigSchema = z.object({
|
|
156
|
-
model_tiers: ModelTierConfigSchema.optional()
|
|
157
|
-
});
|
|
158
|
-
var HarnessConfigSchema = z.object({
|
|
159
|
-
version: z.literal(1),
|
|
160
|
-
name: z.string().optional(),
|
|
161
|
-
rootDir: z.string().default("."),
|
|
162
|
-
layers: z.array(LayerSchema).optional(),
|
|
163
|
-
forbiddenImports: z.array(ForbiddenImportSchema).optional(),
|
|
164
|
-
boundaries: BoundaryConfigSchema.optional(),
|
|
165
|
-
agentsMapPath: z.string().default("./AGENTS.md"),
|
|
166
|
-
docsDir: z.string().default("./docs"),
|
|
167
|
-
agent: AgentConfigSchema.optional(),
|
|
168
|
-
entropy: EntropyConfigSchema.optional(),
|
|
169
|
-
security: SecurityConfigSchema.optional(),
|
|
170
|
-
performance: PerformanceConfigSchema.optional(),
|
|
171
|
-
template: z.object({
|
|
172
|
-
level: z.enum(["basic", "intermediate", "advanced"]),
|
|
173
|
-
framework: z.string().optional(),
|
|
174
|
-
version: z.number()
|
|
175
|
-
}).optional(),
|
|
176
|
-
phaseGates: PhaseGatesConfigSchema.optional(),
|
|
177
|
-
design: DesignConfigSchema.optional(),
|
|
178
|
-
i18n: I18nConfigSchema.optional(),
|
|
179
|
-
review: ReviewConfigSchema.optional(),
|
|
180
|
-
updateCheckInterval: z.number().int().min(0).optional()
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
// src/config/loader.ts
|
|
184
|
-
var CONFIG_FILENAMES = ["harness.config.json"];
|
|
185
|
-
function findConfigFile(startDir = process.cwd()) {
|
|
186
|
-
let currentDir = path.resolve(startDir);
|
|
187
|
-
const root = path.parse(currentDir).root;
|
|
188
|
-
while (currentDir !== root) {
|
|
189
|
-
for (const filename of CONFIG_FILENAMES) {
|
|
190
|
-
const configPath = path.join(currentDir, filename);
|
|
191
|
-
if (fs.existsSync(configPath)) {
|
|
192
|
-
return Ok(configPath);
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
currentDir = path.dirname(currentDir);
|
|
196
|
-
}
|
|
197
|
-
return Err(
|
|
198
|
-
new CLIError('No harness.config.json found. Run "harness init" to create one.', ExitCode.ERROR)
|
|
199
|
-
);
|
|
200
|
-
}
|
|
201
|
-
function loadConfig(configPath) {
|
|
202
|
-
if (!fs.existsSync(configPath)) {
|
|
203
|
-
return Err(new CLIError(`Config file not found: ${configPath}`, ExitCode.ERROR));
|
|
204
|
-
}
|
|
205
|
-
let rawConfig;
|
|
206
|
-
try {
|
|
207
|
-
const content = fs.readFileSync(configPath, "utf-8");
|
|
208
|
-
rawConfig = JSON.parse(content);
|
|
209
|
-
} catch (error) {
|
|
210
|
-
return Err(
|
|
211
|
-
new CLIError(
|
|
212
|
-
`Failed to parse config: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
213
|
-
ExitCode.ERROR
|
|
214
|
-
)
|
|
215
|
-
);
|
|
216
|
-
}
|
|
217
|
-
const parsed = HarnessConfigSchema.safeParse(rawConfig);
|
|
218
|
-
if (!parsed.success) {
|
|
219
|
-
const issues = parsed.error.issues.map((i) => ` - ${i.path.join(".")}: ${i.message}`).join("\n");
|
|
220
|
-
return Err(new CLIError(`Invalid config:
|
|
221
|
-
${issues}`, ExitCode.ERROR));
|
|
222
|
-
}
|
|
223
|
-
return Ok(parsed.data);
|
|
224
|
-
}
|
|
225
|
-
function resolveConfig(configPath) {
|
|
226
|
-
if (configPath) {
|
|
227
|
-
return loadConfig(configPath);
|
|
228
|
-
}
|
|
229
|
-
const findResult = findConfigFile();
|
|
230
|
-
if (!findResult.ok) {
|
|
231
|
-
return findResult;
|
|
232
|
-
}
|
|
233
|
-
return loadConfig(findResult.value);
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
// src/output/formatter.ts
|
|
237
|
-
import chalk from "chalk";
|
|
238
|
-
var OutputMode = {
|
|
239
|
-
JSON: "json",
|
|
240
|
-
TEXT: "text",
|
|
241
|
-
QUIET: "quiet",
|
|
242
|
-
VERBOSE: "verbose"
|
|
243
|
-
};
|
|
244
|
-
var OutputFormatter = class {
|
|
245
|
-
constructor(mode = OutputMode.TEXT) {
|
|
246
|
-
this.mode = mode;
|
|
247
|
-
}
|
|
248
|
-
/**
|
|
249
|
-
* Format raw data (for JSON mode)
|
|
250
|
-
*/
|
|
251
|
-
format(data) {
|
|
252
|
-
if (this.mode === OutputMode.JSON) {
|
|
253
|
-
return JSON.stringify(data, null, 2);
|
|
254
|
-
}
|
|
255
|
-
return String(data);
|
|
256
|
-
}
|
|
257
|
-
/**
|
|
258
|
-
* Format validation result
|
|
259
|
-
*/
|
|
260
|
-
formatValidation(result) {
|
|
261
|
-
if (this.mode === OutputMode.JSON) {
|
|
262
|
-
return JSON.stringify(result, null, 2);
|
|
263
|
-
}
|
|
264
|
-
if (this.mode === OutputMode.QUIET) {
|
|
265
|
-
if (result.valid) return "";
|
|
266
|
-
return result.issues.map((i) => `${i.file ?? ""}: ${i.message}`).join("\n");
|
|
267
|
-
}
|
|
268
|
-
const lines = [];
|
|
269
|
-
if (result.valid) {
|
|
270
|
-
lines.push(chalk.green("v validation passed"));
|
|
271
|
-
} else {
|
|
272
|
-
lines.push(chalk.red(`x Validation failed (${result.issues.length} issues)`));
|
|
273
|
-
lines.push("");
|
|
274
|
-
for (const issue of result.issues) {
|
|
275
|
-
const location = issue.file ? issue.line ? `${issue.file}:${issue.line}` : issue.file : "unknown";
|
|
276
|
-
lines.push(` ${chalk.yellow("*")} ${chalk.dim(location)}`);
|
|
277
|
-
lines.push(` ${issue.message}`);
|
|
278
|
-
if (issue.suggestion && this.mode === OutputMode.VERBOSE) {
|
|
279
|
-
lines.push(` ${chalk.dim("->")} ${issue.suggestion}`);
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
return lines.join("\n");
|
|
284
|
-
}
|
|
285
|
-
/**
|
|
286
|
-
* Format a summary line
|
|
287
|
-
*/
|
|
288
|
-
formatSummary(label, value, success) {
|
|
289
|
-
if (this.mode === OutputMode.JSON || this.mode === OutputMode.QUIET) {
|
|
290
|
-
return "";
|
|
291
|
-
}
|
|
292
|
-
const icon = success ? chalk.green("v") : chalk.red("x");
|
|
293
|
-
return `${icon} ${label}: ${value}`;
|
|
294
|
-
}
|
|
295
|
-
};
|
|
296
|
-
|
|
297
|
-
// src/commands/validate.ts
|
|
298
117
|
async function runValidate(options) {
|
|
299
118
|
const configResult = resolveConfig(options.configPath);
|
|
300
119
|
if (!configResult.ok) {
|
|
301
120
|
return configResult;
|
|
302
121
|
}
|
|
303
122
|
const config = configResult.value;
|
|
304
|
-
const cwd = options.cwd ?? (options.configPath ?
|
|
123
|
+
const cwd = options.cwd ?? (options.configPath ? path.dirname(path.resolve(options.configPath)) : process.cwd());
|
|
305
124
|
const result = {
|
|
306
125
|
valid: true,
|
|
307
126
|
checks: {
|
|
@@ -311,7 +130,7 @@ async function runValidate(options) {
|
|
|
311
130
|
},
|
|
312
131
|
issues: []
|
|
313
132
|
};
|
|
314
|
-
const agentsMapPath =
|
|
133
|
+
const agentsMapPath = path.resolve(cwd, config.agentsMapPath);
|
|
315
134
|
const agentsResult = await validateAgentsMap(agentsMapPath);
|
|
316
135
|
if (agentsResult.ok) {
|
|
317
136
|
result.checks.agentsMap = true;
|
|
@@ -369,10 +188,10 @@ function createValidateCommand() {
|
|
|
369
188
|
process.exit(result.error.exitCode);
|
|
370
189
|
}
|
|
371
190
|
if (opts.crossCheck) {
|
|
372
|
-
const { runCrossCheck: runCrossCheck2 } = await import("./validate-cross-check-
|
|
191
|
+
const { runCrossCheck: runCrossCheck2 } = await import("./validate-cross-check-WZAX357V.js");
|
|
373
192
|
const cwd = process.cwd();
|
|
374
|
-
const specsDir =
|
|
375
|
-
const plansDir =
|
|
193
|
+
const specsDir = path.join(cwd, "docs", "specs");
|
|
194
|
+
const plansDir = path.join(cwd, "docs", "plans");
|
|
376
195
|
const crossResult = await runCrossCheck2({ specsDir, plansDir, projectPath: cwd });
|
|
377
196
|
if (crossResult.ok && crossResult.value.warnings > 0) {
|
|
378
197
|
console.log("\nCross-artifact validation:");
|
|
@@ -396,15 +215,7 @@ function createValidateCommand() {
|
|
|
396
215
|
|
|
397
216
|
// src/commands/check-deps.ts
|
|
398
217
|
import { Command as Command2 } from "commander";
|
|
399
|
-
import * as
|
|
400
|
-
|
|
401
|
-
// src/utils/files.ts
|
|
402
|
-
import { glob } from "glob";
|
|
403
|
-
async function findFiles(pattern, cwd = process.cwd()) {
|
|
404
|
-
return glob(pattern, { cwd, absolute: true });
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
// src/commands/check-deps.ts
|
|
218
|
+
import * as path2 from "path";
|
|
408
219
|
async function runCheckDeps(options) {
|
|
409
220
|
const cwd = options.cwd ?? process.cwd();
|
|
410
221
|
const configResult = resolveConfig(options.configPath);
|
|
@@ -420,7 +231,7 @@ async function runCheckDeps(options) {
|
|
|
420
231
|
if (!config.layers || config.layers.length === 0) {
|
|
421
232
|
return Ok(result);
|
|
422
233
|
}
|
|
423
|
-
const rootDir =
|
|
234
|
+
const rootDir = path2.resolve(cwd, config.rootDir);
|
|
424
235
|
const parser = new TypeScriptParser();
|
|
425
236
|
const layers = config.layers.map((l) => defineLayer(l.name, [l.pattern], l.allowedDependencies));
|
|
426
237
|
const layerConfig = {
|
|
@@ -501,11 +312,11 @@ function createCheckDepsCommand() {
|
|
|
501
312
|
|
|
502
313
|
// src/commands/check-perf.ts
|
|
503
314
|
import { Command as Command3 } from "commander";
|
|
504
|
-
import * as
|
|
315
|
+
import * as path3 from "path";
|
|
505
316
|
async function runCheckPerf(cwd, options) {
|
|
506
317
|
const runAll = !options.structural && !options.size && !options.coupling;
|
|
507
318
|
const analyzer = new EntropyAnalyzer({
|
|
508
|
-
rootDir:
|
|
319
|
+
rootDir: path3.resolve(cwd),
|
|
509
320
|
analyze: {
|
|
510
321
|
complexity: runAll || !!options.structural,
|
|
511
322
|
coupling: runAll || !!options.coupling,
|
|
@@ -623,7 +434,7 @@ function createCheckPerfCommand() {
|
|
|
623
434
|
|
|
624
435
|
// src/commands/check-security.ts
|
|
625
436
|
import { Command as Command4 } from "commander";
|
|
626
|
-
import * as
|
|
437
|
+
import * as path4 from "path";
|
|
627
438
|
import { execSync } from "child_process";
|
|
628
439
|
var SEVERITY_RANK = {
|
|
629
440
|
error: 3,
|
|
@@ -636,19 +447,19 @@ function getChangedFiles(cwd) {
|
|
|
636
447
|
cwd,
|
|
637
448
|
encoding: "utf-8"
|
|
638
449
|
});
|
|
639
|
-
return output.trim().split("\n").filter((f) => f.length > 0).map((f) =>
|
|
450
|
+
return output.trim().split("\n").filter((f) => f.length > 0).map((f) => path4.resolve(cwd, f));
|
|
640
451
|
} catch {
|
|
641
452
|
return [];
|
|
642
453
|
}
|
|
643
454
|
}
|
|
644
455
|
async function runCheckSecurity(cwd, options) {
|
|
645
|
-
const projectRoot =
|
|
456
|
+
const projectRoot = path4.resolve(cwd);
|
|
646
457
|
let configData = {};
|
|
647
458
|
try {
|
|
648
|
-
const
|
|
649
|
-
const configPath =
|
|
650
|
-
if (
|
|
651
|
-
const raw =
|
|
459
|
+
const fs11 = await import("fs");
|
|
460
|
+
const configPath = path4.join(projectRoot, "harness.config.json");
|
|
461
|
+
if (fs11.existsSync(configPath)) {
|
|
462
|
+
const raw = fs11.readFileSync(configPath, "utf-8");
|
|
652
463
|
const parsed = JSON.parse(raw);
|
|
653
464
|
configData = parsed.security ?? {};
|
|
654
465
|
}
|
|
@@ -661,7 +472,7 @@ async function runCheckSecurity(cwd, options) {
|
|
|
661
472
|
if (options.changedOnly) {
|
|
662
473
|
filesToScan = getChangedFiles(projectRoot);
|
|
663
474
|
} else {
|
|
664
|
-
const { glob
|
|
475
|
+
const { glob } = await import("glob");
|
|
665
476
|
const pattern = "**/*.{ts,tsx,js,jsx,go,py,java,rb}";
|
|
666
477
|
const ignore = securityConfig.exclude ?? [
|
|
667
478
|
"**/node_modules/**",
|
|
@@ -669,7 +480,7 @@ async function runCheckSecurity(cwd, options) {
|
|
|
669
480
|
"**/*.test.ts",
|
|
670
481
|
"**/fixtures/**"
|
|
671
482
|
];
|
|
672
|
-
filesToScan = await
|
|
483
|
+
filesToScan = await glob(pattern, { cwd: projectRoot, absolute: true, ignore });
|
|
673
484
|
}
|
|
674
485
|
const result = await scanner.scanFiles(filesToScan);
|
|
675
486
|
const threshold = options.severity ?? "warning";
|
|
@@ -729,15 +540,15 @@ function createCheckSecurityCommand() {
|
|
|
729
540
|
|
|
730
541
|
// src/commands/perf.ts
|
|
731
542
|
import { Command as Command5 } from "commander";
|
|
732
|
-
import * as
|
|
543
|
+
import * as path5 from "path";
|
|
733
544
|
function createPerfCommand() {
|
|
734
545
|
const perf = new Command5("perf").description("Performance benchmark and baseline management");
|
|
735
|
-
perf.command("bench [glob]").description("Run benchmarks via vitest bench").action(async (
|
|
546
|
+
perf.command("bench [glob]").description("Run benchmarks via vitest bench").action(async (glob, _opts, cmd) => {
|
|
736
547
|
const globalOpts = cmd.optsWithGlobals();
|
|
737
548
|
const cwd = process.cwd();
|
|
738
|
-
const { BenchmarkRunner } = await import("./dist-
|
|
549
|
+
const { BenchmarkRunner } = await import("./dist-PBTNVK6K.js");
|
|
739
550
|
const runner = new BenchmarkRunner();
|
|
740
|
-
const benchFiles = runner.discover(cwd,
|
|
551
|
+
const benchFiles = runner.discover(cwd, glob);
|
|
741
552
|
if (benchFiles.length === 0) {
|
|
742
553
|
if (globalOpts.json) {
|
|
743
554
|
console.log(JSON.stringify({ benchFiles: [], message: "No .bench.ts files found" }));
|
|
@@ -755,7 +566,7 @@ function createPerfCommand() {
|
|
|
755
566
|
}
|
|
756
567
|
logger.info("Running benchmarks...");
|
|
757
568
|
}
|
|
758
|
-
const result = await runner.run(
|
|
569
|
+
const result = await runner.run(glob ? { cwd, glob } : { cwd });
|
|
759
570
|
if (globalOpts.json) {
|
|
760
571
|
console.log(JSON.stringify({ results: result.results, success: result.success }));
|
|
761
572
|
} else {
|
|
@@ -804,7 +615,7 @@ Results (${result.results.length} benchmarks):`);
|
|
|
804
615
|
baselines.command("update").description("Update baselines from latest benchmark run").action(async (_opts, cmd) => {
|
|
805
616
|
const globalOpts = cmd.optsWithGlobals();
|
|
806
617
|
const cwd = process.cwd();
|
|
807
|
-
const { BenchmarkRunner } = await import("./dist-
|
|
618
|
+
const { BenchmarkRunner } = await import("./dist-PBTNVK6K.js");
|
|
808
619
|
const runner = new BenchmarkRunner();
|
|
809
620
|
const manager = new BaselineManager(cwd);
|
|
810
621
|
logger.info("Running benchmarks to update baselines...");
|
|
@@ -832,9 +643,9 @@ Results (${result.results.length} benchmarks):`);
|
|
|
832
643
|
perf.command("report").description("Full performance report with metrics, trends, and hotspots").action(async (_opts, cmd) => {
|
|
833
644
|
const globalOpts = cmd.optsWithGlobals();
|
|
834
645
|
const cwd = process.cwd();
|
|
835
|
-
const { EntropyAnalyzer: EntropyAnalyzer2 } = await import("./dist-
|
|
646
|
+
const { EntropyAnalyzer: EntropyAnalyzer2 } = await import("./dist-PBTNVK6K.js");
|
|
836
647
|
const analyzer = new EntropyAnalyzer2({
|
|
837
|
-
rootDir:
|
|
648
|
+
rootDir: path5.resolve(cwd),
|
|
838
649
|
analyze: { complexity: true, coupling: true }
|
|
839
650
|
});
|
|
840
651
|
const result = await analyzer.analyze();
|
|
@@ -892,7 +703,7 @@ Results (${result.results.length} benchmarks):`);
|
|
|
892
703
|
|
|
893
704
|
// src/commands/check-docs.ts
|
|
894
705
|
import { Command as Command6 } from "commander";
|
|
895
|
-
import * as
|
|
706
|
+
import * as path6 from "path";
|
|
896
707
|
async function runCheckDocs(options) {
|
|
897
708
|
const cwd = options.cwd ?? process.cwd();
|
|
898
709
|
const minCoverage = options.minCoverage ?? 80;
|
|
@@ -901,8 +712,8 @@ async function runCheckDocs(options) {
|
|
|
901
712
|
return configResult;
|
|
902
713
|
}
|
|
903
714
|
const config = configResult.value;
|
|
904
|
-
const docsDir =
|
|
905
|
-
const sourceDir =
|
|
715
|
+
const docsDir = path6.resolve(cwd, config.docsDir);
|
|
716
|
+
const sourceDir = path6.resolve(cwd, config.rootDir);
|
|
906
717
|
const coverageResult = await checkDocCoverage("project", {
|
|
907
718
|
docsDir,
|
|
908
719
|
sourceDir,
|
|
@@ -987,341 +798,34 @@ function createCheckDocsCommand() {
|
|
|
987
798
|
|
|
988
799
|
// src/commands/init.ts
|
|
989
800
|
import { Command as Command8 } from "commander";
|
|
990
|
-
import
|
|
991
|
-
import * as fs5 from "fs";
|
|
992
|
-
import * as path11 from "path";
|
|
993
|
-
|
|
994
|
-
// src/templates/engine.ts
|
|
801
|
+
import chalk2 from "chalk";
|
|
995
802
|
import * as fs2 from "fs";
|
|
996
803
|
import * as path8 from "path";
|
|
997
|
-
import Handlebars from "handlebars";
|
|
998
|
-
|
|
999
|
-
// src/templates/schema.ts
|
|
1000
|
-
import { z as z2 } from "zod";
|
|
1001
|
-
var MergeStrategySchema = z2.object({
|
|
1002
|
-
json: z2.enum(["deep-merge", "overlay-wins"]).default("deep-merge"),
|
|
1003
|
-
files: z2.literal("overlay-wins").default("overlay-wins")
|
|
1004
|
-
});
|
|
1005
|
-
var TemplateMetadataSchema = z2.object({
|
|
1006
|
-
name: z2.string(),
|
|
1007
|
-
description: z2.string(),
|
|
1008
|
-
level: z2.enum(["basic", "intermediate", "advanced"]).optional(),
|
|
1009
|
-
framework: z2.string().optional(),
|
|
1010
|
-
extends: z2.string().optional(),
|
|
1011
|
-
mergeStrategy: MergeStrategySchema.default({}),
|
|
1012
|
-
version: z2.literal(1)
|
|
1013
|
-
});
|
|
1014
|
-
|
|
1015
|
-
// src/templates/merger.ts
|
|
1016
|
-
function isPlainObject(val) {
|
|
1017
|
-
return typeof val === "object" && val !== null && !Array.isArray(val);
|
|
1018
|
-
}
|
|
1019
|
-
function deepMergeJson(base, overlay) {
|
|
1020
|
-
const result = { ...base };
|
|
1021
|
-
for (const key of Object.keys(overlay)) {
|
|
1022
|
-
if (isPlainObject(result[key]) && isPlainObject(overlay[key])) {
|
|
1023
|
-
result[key] = deepMergeJson(
|
|
1024
|
-
result[key],
|
|
1025
|
-
overlay[key]
|
|
1026
|
-
);
|
|
1027
|
-
} else {
|
|
1028
|
-
result[key] = overlay[key];
|
|
1029
|
-
}
|
|
1030
|
-
}
|
|
1031
|
-
return result;
|
|
1032
|
-
}
|
|
1033
|
-
var CONCAT_KEYS = /* @__PURE__ */ new Set(["dependencies", "devDependencies", "peerDependencies"]);
|
|
1034
|
-
function mergePackageJson(base, overlay) {
|
|
1035
|
-
const result = { ...base };
|
|
1036
|
-
for (const key of Object.keys(overlay)) {
|
|
1037
|
-
if (CONCAT_KEYS.has(key) && isPlainObject(result[key]) && isPlainObject(overlay[key])) {
|
|
1038
|
-
result[key] = {
|
|
1039
|
-
...result[key],
|
|
1040
|
-
...overlay[key]
|
|
1041
|
-
};
|
|
1042
|
-
} else if (isPlainObject(result[key]) && isPlainObject(overlay[key])) {
|
|
1043
|
-
result[key] = deepMergeJson(
|
|
1044
|
-
result[key],
|
|
1045
|
-
overlay[key]
|
|
1046
|
-
);
|
|
1047
|
-
} else {
|
|
1048
|
-
result[key] = overlay[key];
|
|
1049
|
-
}
|
|
1050
|
-
}
|
|
1051
|
-
return result;
|
|
1052
|
-
}
|
|
1053
|
-
|
|
1054
|
-
// src/templates/engine.ts
|
|
1055
|
-
var TemplateEngine = class {
|
|
1056
|
-
constructor(templatesDir) {
|
|
1057
|
-
this.templatesDir = templatesDir;
|
|
1058
|
-
}
|
|
1059
|
-
listTemplates() {
|
|
1060
|
-
try {
|
|
1061
|
-
const entries = fs2.readdirSync(this.templatesDir, { withFileTypes: true });
|
|
1062
|
-
const templates = [];
|
|
1063
|
-
for (const entry of entries) {
|
|
1064
|
-
if (!entry.isDirectory()) continue;
|
|
1065
|
-
const metaPath = path8.join(this.templatesDir, entry.name, "template.json");
|
|
1066
|
-
if (!fs2.existsSync(metaPath)) continue;
|
|
1067
|
-
const raw = JSON.parse(fs2.readFileSync(metaPath, "utf-8"));
|
|
1068
|
-
const parsed = TemplateMetadataSchema.safeParse(raw);
|
|
1069
|
-
if (parsed.success) templates.push(parsed.data);
|
|
1070
|
-
}
|
|
1071
|
-
return Ok(templates);
|
|
1072
|
-
} catch (error) {
|
|
1073
|
-
return Err(
|
|
1074
|
-
new Error(
|
|
1075
|
-
`Failed to list templates: ${error instanceof Error ? error.message : String(error)}`
|
|
1076
|
-
)
|
|
1077
|
-
);
|
|
1078
|
-
}
|
|
1079
|
-
}
|
|
1080
|
-
resolveTemplate(level, framework) {
|
|
1081
|
-
const levelDir = this.findTemplateDir(level, "level");
|
|
1082
|
-
if (!levelDir) return Err(new Error(`Template not found for level: ${level}`));
|
|
1083
|
-
const metaPath = path8.join(levelDir, "template.json");
|
|
1084
|
-
const metaRaw = JSON.parse(fs2.readFileSync(metaPath, "utf-8"));
|
|
1085
|
-
const metaResult = TemplateMetadataSchema.safeParse(metaRaw);
|
|
1086
|
-
if (!metaResult.success)
|
|
1087
|
-
return Err(new Error(`Invalid template.json in ${level}: ${metaResult.error.message}`));
|
|
1088
|
-
const metadata = metaResult.data;
|
|
1089
|
-
let files = [];
|
|
1090
|
-
if (metadata.extends) {
|
|
1091
|
-
const baseDir = path8.join(this.templatesDir, metadata.extends);
|
|
1092
|
-
if (fs2.existsSync(baseDir)) files = this.collectFiles(baseDir, metadata.extends);
|
|
1093
|
-
}
|
|
1094
|
-
const levelFiles = this.collectFiles(levelDir, level);
|
|
1095
|
-
files = this.mergeFileLists(files, levelFiles);
|
|
1096
|
-
let overlayMetadata;
|
|
1097
|
-
if (framework) {
|
|
1098
|
-
const frameworkDir = this.findTemplateDir(framework, "framework");
|
|
1099
|
-
if (!frameworkDir) return Err(new Error(`Framework template not found: ${framework}`));
|
|
1100
|
-
const fMetaPath = path8.join(frameworkDir, "template.json");
|
|
1101
|
-
const fMetaRaw = JSON.parse(fs2.readFileSync(fMetaPath, "utf-8"));
|
|
1102
|
-
const fMetaResult = TemplateMetadataSchema.safeParse(fMetaRaw);
|
|
1103
|
-
if (fMetaResult.success) overlayMetadata = fMetaResult.data;
|
|
1104
|
-
const frameworkFiles = this.collectFiles(frameworkDir, framework);
|
|
1105
|
-
files = this.mergeFileLists(files, frameworkFiles);
|
|
1106
|
-
}
|
|
1107
|
-
files = files.filter((f) => f.relativePath !== "template.json");
|
|
1108
|
-
const resolved2 = { metadata, files };
|
|
1109
|
-
if (overlayMetadata !== void 0) resolved2.overlayMetadata = overlayMetadata;
|
|
1110
|
-
return Ok(resolved2);
|
|
1111
|
-
}
|
|
1112
|
-
render(template, context) {
|
|
1113
|
-
const rendered = [];
|
|
1114
|
-
const jsonBuffers = /* @__PURE__ */ new Map();
|
|
1115
|
-
for (const file of template.files) {
|
|
1116
|
-
const outputPath = file.relativePath.replace(/\.hbs$/, "");
|
|
1117
|
-
if (file.isHandlebars) {
|
|
1118
|
-
try {
|
|
1119
|
-
const raw = fs2.readFileSync(file.absolutePath, "utf-8");
|
|
1120
|
-
const compiled = Handlebars.compile(raw, { strict: true });
|
|
1121
|
-
const content = compiled(context);
|
|
1122
|
-
if (outputPath.endsWith(".json") && file.relativePath.endsWith(".json.hbs")) {
|
|
1123
|
-
if (!jsonBuffers.has(outputPath)) jsonBuffers.set(outputPath, []);
|
|
1124
|
-
jsonBuffers.get(outputPath).push(JSON.parse(content));
|
|
1125
|
-
} else {
|
|
1126
|
-
rendered.push({ relativePath: outputPath, content });
|
|
1127
|
-
}
|
|
1128
|
-
} catch (error) {
|
|
1129
|
-
const msg = error instanceof Error ? error.message : String(error);
|
|
1130
|
-
return Err(
|
|
1131
|
-
new Error(
|
|
1132
|
-
`Template render failed in ${file.sourceTemplate}/${file.relativePath}: ${msg}`
|
|
1133
|
-
)
|
|
1134
|
-
);
|
|
1135
|
-
}
|
|
1136
|
-
} else {
|
|
1137
|
-
try {
|
|
1138
|
-
const content = fs2.readFileSync(file.absolutePath, "utf-8");
|
|
1139
|
-
rendered.push({ relativePath: file.relativePath, content });
|
|
1140
|
-
} catch (error) {
|
|
1141
|
-
const msg = error instanceof Error ? error.message : String(error);
|
|
1142
|
-
return Err(
|
|
1143
|
-
new Error(
|
|
1144
|
-
`Template render failed in ${file.sourceTemplate}/${file.relativePath}: ${msg}`
|
|
1145
|
-
)
|
|
1146
|
-
);
|
|
1147
|
-
}
|
|
1148
|
-
}
|
|
1149
|
-
}
|
|
1150
|
-
try {
|
|
1151
|
-
for (const [outputPath, jsons] of jsonBuffers) {
|
|
1152
|
-
let merged = {};
|
|
1153
|
-
for (const json of jsons) {
|
|
1154
|
-
merged = outputPath === "package.json" ? mergePackageJson(merged, json) : deepMergeJson(merged, json);
|
|
1155
|
-
}
|
|
1156
|
-
rendered.push({ relativePath: outputPath, content: JSON.stringify(merged, null, 2) });
|
|
1157
|
-
}
|
|
1158
|
-
} catch (error) {
|
|
1159
|
-
const msg = error instanceof Error ? error.message : String(error);
|
|
1160
|
-
return Err(new Error(`JSON merge failed: ${msg}`));
|
|
1161
|
-
}
|
|
1162
|
-
return Ok({ files: rendered });
|
|
1163
|
-
}
|
|
1164
|
-
write(files, targetDir, options) {
|
|
1165
|
-
try {
|
|
1166
|
-
const written = [];
|
|
1167
|
-
for (const file of files.files) {
|
|
1168
|
-
const targetPath = path8.join(targetDir, file.relativePath);
|
|
1169
|
-
const dir = path8.dirname(targetPath);
|
|
1170
|
-
if (!options.overwrite && fs2.existsSync(targetPath)) continue;
|
|
1171
|
-
fs2.mkdirSync(dir, { recursive: true });
|
|
1172
|
-
fs2.writeFileSync(targetPath, file.content);
|
|
1173
|
-
written.push(file.relativePath);
|
|
1174
|
-
}
|
|
1175
|
-
return Ok(written);
|
|
1176
|
-
} catch (error) {
|
|
1177
|
-
return Err(
|
|
1178
|
-
new Error(
|
|
1179
|
-
`Failed to write files: ${error instanceof Error ? error.message : String(error)}`
|
|
1180
|
-
)
|
|
1181
|
-
);
|
|
1182
|
-
}
|
|
1183
|
-
}
|
|
1184
|
-
findTemplateDir(name, type) {
|
|
1185
|
-
const entries = fs2.readdirSync(this.templatesDir, { withFileTypes: true });
|
|
1186
|
-
for (const entry of entries) {
|
|
1187
|
-
if (!entry.isDirectory()) continue;
|
|
1188
|
-
const metaPath = path8.join(this.templatesDir, entry.name, "template.json");
|
|
1189
|
-
if (!fs2.existsSync(metaPath)) continue;
|
|
1190
|
-
const raw = JSON.parse(fs2.readFileSync(metaPath, "utf-8"));
|
|
1191
|
-
const parsed = TemplateMetadataSchema.safeParse(raw);
|
|
1192
|
-
if (!parsed.success) continue;
|
|
1193
|
-
if (type === "level" && parsed.data.level === name)
|
|
1194
|
-
return path8.join(this.templatesDir, entry.name);
|
|
1195
|
-
if (type === "framework" && parsed.data.framework === name)
|
|
1196
|
-
return path8.join(this.templatesDir, entry.name);
|
|
1197
|
-
if (parsed.data.name === name) return path8.join(this.templatesDir, entry.name);
|
|
1198
|
-
}
|
|
1199
|
-
return null;
|
|
1200
|
-
}
|
|
1201
|
-
collectFiles(dir, sourceName) {
|
|
1202
|
-
const files = [];
|
|
1203
|
-
const walk = (currentDir) => {
|
|
1204
|
-
const entries = fs2.readdirSync(currentDir, { withFileTypes: true });
|
|
1205
|
-
for (const entry of entries) {
|
|
1206
|
-
const fullPath = path8.join(currentDir, entry.name);
|
|
1207
|
-
if (entry.isDirectory()) {
|
|
1208
|
-
walk(fullPath);
|
|
1209
|
-
} else {
|
|
1210
|
-
files.push({
|
|
1211
|
-
relativePath: path8.relative(dir, fullPath).replace(/\\/g, "/"),
|
|
1212
|
-
absolutePath: fullPath,
|
|
1213
|
-
isHandlebars: entry.name.endsWith(".hbs"),
|
|
1214
|
-
sourceTemplate: sourceName
|
|
1215
|
-
});
|
|
1216
|
-
}
|
|
1217
|
-
}
|
|
1218
|
-
};
|
|
1219
|
-
walk(dir);
|
|
1220
|
-
return files;
|
|
1221
|
-
}
|
|
1222
|
-
mergeFileLists(base, overlay) {
|
|
1223
|
-
const map = /* @__PURE__ */ new Map();
|
|
1224
|
-
for (const file of base) map.set(file.relativePath, file);
|
|
1225
|
-
for (const file of overlay) {
|
|
1226
|
-
if (file.relativePath.endsWith(".json.hbs")) {
|
|
1227
|
-
const baseKey = base.find((f) => f.relativePath === file.relativePath);
|
|
1228
|
-
if (baseKey) {
|
|
1229
|
-
map.set(`__overlay__${file.relativePath}`, file);
|
|
1230
|
-
} else {
|
|
1231
|
-
map.set(file.relativePath, file);
|
|
1232
|
-
}
|
|
1233
|
-
} else {
|
|
1234
|
-
map.set(file.relativePath, file);
|
|
1235
|
-
}
|
|
1236
|
-
}
|
|
1237
|
-
return Array.from(map.values());
|
|
1238
|
-
}
|
|
1239
|
-
};
|
|
1240
|
-
|
|
1241
|
-
// src/utils/paths.ts
|
|
1242
|
-
import * as fs3 from "fs";
|
|
1243
|
-
import * as path9 from "path";
|
|
1244
|
-
import { fileURLToPath } from "url";
|
|
1245
|
-
var __filename = fileURLToPath(import.meta.url);
|
|
1246
|
-
var __dirname = path9.dirname(__filename);
|
|
1247
|
-
function findUpDir(targetName, marker, maxLevels = 8) {
|
|
1248
|
-
let dir = __dirname;
|
|
1249
|
-
for (let i = 0; i < maxLevels; i++) {
|
|
1250
|
-
const candidate = path9.join(dir, targetName);
|
|
1251
|
-
if (fs3.existsSync(candidate) && fs3.statSync(candidate).isDirectory()) {
|
|
1252
|
-
if (fs3.existsSync(path9.join(candidate, marker))) {
|
|
1253
|
-
return candidate;
|
|
1254
|
-
}
|
|
1255
|
-
}
|
|
1256
|
-
dir = path9.dirname(dir);
|
|
1257
|
-
}
|
|
1258
|
-
return null;
|
|
1259
|
-
}
|
|
1260
|
-
function resolveTemplatesDir() {
|
|
1261
|
-
return findUpDir("templates", "base") ?? path9.join(__dirname, "templates");
|
|
1262
|
-
}
|
|
1263
|
-
function resolvePersonasDir() {
|
|
1264
|
-
const agentsDir = findUpDir("agents", "personas");
|
|
1265
|
-
if (agentsDir) {
|
|
1266
|
-
return path9.join(agentsDir, "personas");
|
|
1267
|
-
}
|
|
1268
|
-
return path9.join(__dirname, "agents", "personas");
|
|
1269
|
-
}
|
|
1270
|
-
function resolveSkillsDir() {
|
|
1271
|
-
const agentsDir = findUpDir("agents", "skills");
|
|
1272
|
-
if (agentsDir) {
|
|
1273
|
-
return path9.join(agentsDir, "skills", "claude-code");
|
|
1274
|
-
}
|
|
1275
|
-
return path9.join(__dirname, "agents", "skills", "claude-code");
|
|
1276
|
-
}
|
|
1277
|
-
function resolveProjectSkillsDir(cwd) {
|
|
1278
|
-
let dir = cwd ?? process.cwd();
|
|
1279
|
-
for (let i = 0; i < 8; i++) {
|
|
1280
|
-
const candidate = path9.join(dir, "agents", "skills", "claude-code");
|
|
1281
|
-
if (fs3.existsSync(candidate) && fs3.statSync(candidate).isDirectory()) {
|
|
1282
|
-
const agentsDir = path9.join(dir, "agents");
|
|
1283
|
-
if (fs3.existsSync(path9.join(agentsDir, "skills"))) {
|
|
1284
|
-
return candidate;
|
|
1285
|
-
}
|
|
1286
|
-
}
|
|
1287
|
-
const parent = path9.dirname(dir);
|
|
1288
|
-
if (parent === dir) break;
|
|
1289
|
-
dir = parent;
|
|
1290
|
-
}
|
|
1291
|
-
return null;
|
|
1292
|
-
}
|
|
1293
|
-
function resolveGlobalSkillsDir() {
|
|
1294
|
-
const agentsDir = findUpDir("agents", "skills");
|
|
1295
|
-
if (agentsDir) {
|
|
1296
|
-
return path9.join(agentsDir, "skills", "claude-code");
|
|
1297
|
-
}
|
|
1298
|
-
return path9.join(__dirname, "agents", "skills", "claude-code");
|
|
1299
|
-
}
|
|
1300
804
|
|
|
1301
805
|
// src/commands/setup-mcp.ts
|
|
1302
806
|
import { Command as Command7 } from "commander";
|
|
1303
|
-
import * as
|
|
1304
|
-
import * as
|
|
807
|
+
import * as fs from "fs";
|
|
808
|
+
import * as path7 from "path";
|
|
1305
809
|
import * as os from "os";
|
|
1306
|
-
import
|
|
810
|
+
import chalk from "chalk";
|
|
1307
811
|
var HARNESS_MCP_ENTRY = {
|
|
1308
812
|
command: "harness-mcp"
|
|
1309
813
|
};
|
|
1310
814
|
function readJsonFile(filePath) {
|
|
1311
|
-
if (!
|
|
815
|
+
if (!fs.existsSync(filePath)) return null;
|
|
1312
816
|
try {
|
|
1313
|
-
return JSON.parse(
|
|
817
|
+
return JSON.parse(fs.readFileSync(filePath, "utf-8"));
|
|
1314
818
|
} catch {
|
|
1315
|
-
|
|
819
|
+
fs.copyFileSync(filePath, filePath + ".bak");
|
|
1316
820
|
return null;
|
|
1317
821
|
}
|
|
1318
822
|
}
|
|
1319
823
|
function writeJsonFile(filePath, data) {
|
|
1320
|
-
const dir =
|
|
1321
|
-
if (!
|
|
1322
|
-
|
|
824
|
+
const dir = path7.dirname(filePath);
|
|
825
|
+
if (!fs.existsSync(dir)) {
|
|
826
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
1323
827
|
}
|
|
1324
|
-
|
|
828
|
+
fs.writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n");
|
|
1325
829
|
}
|
|
1326
830
|
function configureMcpServer(configPath) {
|
|
1327
831
|
const config = readJsonFile(configPath) ?? {};
|
|
@@ -1336,7 +840,7 @@ function configureMcpServer(configPath) {
|
|
|
1336
840
|
return true;
|
|
1337
841
|
}
|
|
1338
842
|
function addGeminiTrustedFolder(cwd) {
|
|
1339
|
-
const trustedPath =
|
|
843
|
+
const trustedPath = path7.join(os.homedir(), ".gemini", "trustedFolders.json");
|
|
1340
844
|
const folders = readJsonFile(trustedPath) ?? {};
|
|
1341
845
|
if (folders[cwd] === "TRUST_FOLDER") {
|
|
1342
846
|
return false;
|
|
@@ -1350,7 +854,7 @@ function setupMcp(cwd, client) {
|
|
|
1350
854
|
const skipped = [];
|
|
1351
855
|
let trustedFolder = false;
|
|
1352
856
|
if (client === "all" || client === "claude") {
|
|
1353
|
-
const configPath =
|
|
857
|
+
const configPath = path7.join(cwd, ".mcp.json");
|
|
1354
858
|
if (configureMcpServer(configPath)) {
|
|
1355
859
|
configured.push("Claude Code");
|
|
1356
860
|
} else {
|
|
@@ -1358,7 +862,7 @@ function setupMcp(cwd, client) {
|
|
|
1358
862
|
}
|
|
1359
863
|
}
|
|
1360
864
|
if (client === "all" || client === "gemini") {
|
|
1361
|
-
const configPath =
|
|
865
|
+
const configPath = path7.join(cwd, ".gemini", "settings.json");
|
|
1362
866
|
if (configureMcpServer(configPath)) {
|
|
1363
867
|
configured.push("Gemini CLI");
|
|
1364
868
|
} else {
|
|
@@ -1379,7 +883,7 @@ function createSetupMcpCommand() {
|
|
|
1379
883
|
logger.success("MCP server configured!");
|
|
1380
884
|
console.log("");
|
|
1381
885
|
for (const name of configured) {
|
|
1382
|
-
console.log(` ${
|
|
886
|
+
console.log(` ${chalk.green("+")} ${name}`);
|
|
1383
887
|
}
|
|
1384
888
|
}
|
|
1385
889
|
if (trustedFolder) {
|
|
@@ -1390,11 +894,11 @@ function createSetupMcpCommand() {
|
|
|
1390
894
|
console.log("");
|
|
1391
895
|
logger.info("Already configured:");
|
|
1392
896
|
for (const name of skipped) {
|
|
1393
|
-
console.log(` ${
|
|
897
|
+
console.log(` ${chalk.dim("-")} ${name}`);
|
|
1394
898
|
}
|
|
1395
899
|
}
|
|
1396
900
|
console.log("");
|
|
1397
|
-
console.log(
|
|
901
|
+
console.log(chalk.bold("The harness MCP server provides:"));
|
|
1398
902
|
console.log(
|
|
1399
903
|
" - 31 tools for validation, entropy detection, skill execution, graph querying, and more"
|
|
1400
904
|
);
|
|
@@ -1402,7 +906,7 @@ function createSetupMcpCommand() {
|
|
|
1402
906
|
" - 8 resources for project context, skills, rules, learnings, state, and graph data"
|
|
1403
907
|
);
|
|
1404
908
|
console.log("");
|
|
1405
|
-
console.log(`Run ${
|
|
909
|
+
console.log(`Run ${chalk.cyan("harness skill list")} to see available skills.`);
|
|
1406
910
|
console.log("");
|
|
1407
911
|
}
|
|
1408
912
|
process.exit(ExitCode.SUCCESS);
|
|
@@ -1412,11 +916,11 @@ function createSetupMcpCommand() {
|
|
|
1412
916
|
// src/commands/init.ts
|
|
1413
917
|
async function runInit(options) {
|
|
1414
918
|
const cwd = options.cwd ?? process.cwd();
|
|
1415
|
-
const name = options.name ??
|
|
919
|
+
const name = options.name ?? path8.basename(cwd);
|
|
1416
920
|
const level = options.level ?? "basic";
|
|
1417
921
|
const force = options.force ?? false;
|
|
1418
|
-
const configPath =
|
|
1419
|
-
if (!force &&
|
|
922
|
+
const configPath = path8.join(cwd, "harness.config.json");
|
|
923
|
+
if (!force && fs2.existsSync(configPath)) {
|
|
1420
924
|
return Err(
|
|
1421
925
|
new CLIError("Project already initialized. Use --force to overwrite.", ExitCode.ERROR)
|
|
1422
926
|
);
|
|
@@ -1462,20 +966,20 @@ function createInitCommand() {
|
|
|
1462
966
|
console.log("");
|
|
1463
967
|
logger.info("Created files:");
|
|
1464
968
|
for (const file of result.value.filesCreated) {
|
|
1465
|
-
console.log(` ${
|
|
969
|
+
console.log(` ${chalk2.green("+")} ${file}`);
|
|
1466
970
|
}
|
|
1467
971
|
if (mcpResult.configured.length > 0) {
|
|
1468
972
|
console.log("");
|
|
1469
973
|
logger.info("MCP server configured for:");
|
|
1470
974
|
for (const name of mcpResult.configured) {
|
|
1471
|
-
console.log(` ${
|
|
975
|
+
console.log(` ${chalk2.green("+")} ${name}`);
|
|
1472
976
|
}
|
|
1473
977
|
}
|
|
1474
978
|
console.log("");
|
|
1475
|
-
console.log(
|
|
1476
|
-
console.log(` 1. Review ${
|
|
1477
|
-
console.log(` 2. Update ${
|
|
1478
|
-
console.log(` 3. Run ${
|
|
979
|
+
console.log(chalk2.bold("Next steps:"));
|
|
980
|
+
console.log(` 1. Review ${chalk2.cyan("harness.config.json")}`);
|
|
981
|
+
console.log(` 2. Update ${chalk2.cyan("AGENTS.md")} with your project context`);
|
|
982
|
+
console.log(` 3. Run ${chalk2.cyan("harness validate")} to check your setup`);
|
|
1479
983
|
console.log("");
|
|
1480
984
|
}
|
|
1481
985
|
process.exit(ExitCode.SUCCESS);
|
|
@@ -1485,7 +989,7 @@ function createInitCommand() {
|
|
|
1485
989
|
|
|
1486
990
|
// src/commands/cleanup.ts
|
|
1487
991
|
import { Command as Command9 } from "commander";
|
|
1488
|
-
import * as
|
|
992
|
+
import * as path9 from "path";
|
|
1489
993
|
async function runCleanup(options) {
|
|
1490
994
|
const cwd = options.cwd ?? process.cwd();
|
|
1491
995
|
const type = options.type ?? "all";
|
|
@@ -1500,11 +1004,11 @@ async function runCleanup(options) {
|
|
|
1500
1004
|
patternViolations: [],
|
|
1501
1005
|
totalIssues: 0
|
|
1502
1006
|
};
|
|
1503
|
-
const rootDir =
|
|
1504
|
-
const docsDir =
|
|
1007
|
+
const rootDir = path9.resolve(cwd, config.rootDir);
|
|
1008
|
+
const docsDir = path9.resolve(cwd, config.docsDir);
|
|
1505
1009
|
const entropyConfig = {
|
|
1506
1010
|
rootDir,
|
|
1507
|
-
entryPoints: [
|
|
1011
|
+
entryPoints: [path9.join(rootDir, "src/index.ts")],
|
|
1508
1012
|
docPaths: [docsDir],
|
|
1509
1013
|
analyze: {
|
|
1510
1014
|
drift: type === "all" || type === "drift",
|
|
@@ -1605,7 +1109,7 @@ function createCleanupCommand() {
|
|
|
1605
1109
|
|
|
1606
1110
|
// src/commands/fix-drift.ts
|
|
1607
1111
|
import { Command as Command10 } from "commander";
|
|
1608
|
-
import * as
|
|
1112
|
+
import * as path10 from "path";
|
|
1609
1113
|
async function runFixDrift(options) {
|
|
1610
1114
|
const cwd = options.cwd ?? process.cwd();
|
|
1611
1115
|
const dryRun = options.dryRun !== false;
|
|
@@ -1614,11 +1118,11 @@ async function runFixDrift(options) {
|
|
|
1614
1118
|
return Err(configResult.error);
|
|
1615
1119
|
}
|
|
1616
1120
|
const config = configResult.value;
|
|
1617
|
-
const rootDir =
|
|
1618
|
-
const docsDir =
|
|
1121
|
+
const rootDir = path10.resolve(cwd, config.rootDir);
|
|
1122
|
+
const docsDir = path10.resolve(cwd, config.docsDir);
|
|
1619
1123
|
const entropyConfig = {
|
|
1620
1124
|
rootDir,
|
|
1621
|
-
entryPoints: [
|
|
1125
|
+
entryPoints: [path10.join(rootDir, "src/index.ts")],
|
|
1622
1126
|
docPaths: [docsDir],
|
|
1623
1127
|
analyze: {
|
|
1624
1128
|
drift: true,
|
|
@@ -1769,477 +1273,41 @@ import { Command as Command13 } from "commander";
|
|
|
1769
1273
|
|
|
1770
1274
|
// src/commands/agent/run.ts
|
|
1771
1275
|
import { Command as Command11 } from "commander";
|
|
1772
|
-
import * as
|
|
1276
|
+
import * as path11 from "path";
|
|
1773
1277
|
import * as childProcess from "child_process";
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
import YAML from "yaml";
|
|
1779
|
-
|
|
1780
|
-
// src/persona/schema.ts
|
|
1781
|
-
import { z as z3 } from "zod";
|
|
1782
|
-
var TriggerContextSchema = z3.enum(["always", "on_pr", "on_commit", "on_review", "scheduled", "manual", "on_plan_approved"]).default("always");
|
|
1783
|
-
var CommandStepSchema = z3.object({
|
|
1784
|
-
command: z3.string(),
|
|
1785
|
-
when: TriggerContextSchema
|
|
1786
|
-
});
|
|
1787
|
-
var SkillStepSchema = z3.object({
|
|
1788
|
-
skill: z3.string(),
|
|
1789
|
-
when: TriggerContextSchema,
|
|
1790
|
-
output: z3.enum(["inline", "artifact", "auto"]).default("auto")
|
|
1791
|
-
});
|
|
1792
|
-
var StepSchema = z3.union([CommandStepSchema, SkillStepSchema]);
|
|
1793
|
-
var PersonaTriggerSchema = z3.discriminatedUnion("event", [
|
|
1794
|
-
z3.object({
|
|
1795
|
-
event: z3.literal("on_pr"),
|
|
1796
|
-
conditions: z3.object({
|
|
1797
|
-
paths: z3.array(z3.string()).optional(),
|
|
1798
|
-
min_files: z3.number().optional()
|
|
1799
|
-
}).optional()
|
|
1800
|
-
}),
|
|
1801
|
-
z3.object({
|
|
1802
|
-
event: z3.literal("on_commit"),
|
|
1803
|
-
conditions: z3.object({ branches: z3.array(z3.string()).optional() }).optional()
|
|
1804
|
-
}),
|
|
1805
|
-
z3.object({
|
|
1806
|
-
event: z3.literal("scheduled"),
|
|
1807
|
-
cron: z3.string()
|
|
1808
|
-
}),
|
|
1809
|
-
z3.object({
|
|
1810
|
-
event: z3.literal("manual")
|
|
1811
|
-
})
|
|
1812
|
-
]);
|
|
1813
|
-
var PersonaConfigSchema = z3.object({
|
|
1814
|
-
severity: z3.enum(["error", "warning"]).default("error"),
|
|
1815
|
-
autoFix: z3.boolean().default(false),
|
|
1816
|
-
timeout: z3.number().default(3e5)
|
|
1817
|
-
});
|
|
1818
|
-
var PersonaOutputsSchema = z3.object({
|
|
1819
|
-
"agents-md": z3.boolean().default(true),
|
|
1820
|
-
"ci-workflow": z3.boolean().default(true),
|
|
1821
|
-
"runtime-config": z3.boolean().default(true)
|
|
1822
|
-
});
|
|
1823
|
-
var PersonaSchemaV1 = z3.object({
|
|
1824
|
-
version: z3.literal(1),
|
|
1825
|
-
name: z3.string(),
|
|
1826
|
-
description: z3.string(),
|
|
1827
|
-
role: z3.string(),
|
|
1828
|
-
skills: z3.array(z3.string()),
|
|
1829
|
-
commands: z3.array(z3.string()),
|
|
1830
|
-
triggers: z3.array(PersonaTriggerSchema),
|
|
1831
|
-
config: PersonaConfigSchema.default({}),
|
|
1832
|
-
outputs: PersonaOutputsSchema.default({})
|
|
1833
|
-
});
|
|
1834
|
-
var PersonaSchemaV2 = z3.object({
|
|
1835
|
-
version: z3.literal(2),
|
|
1836
|
-
name: z3.string(),
|
|
1837
|
-
description: z3.string(),
|
|
1838
|
-
role: z3.string(),
|
|
1839
|
-
skills: z3.array(z3.string()),
|
|
1840
|
-
steps: z3.array(StepSchema),
|
|
1841
|
-
triggers: z3.array(PersonaTriggerSchema),
|
|
1842
|
-
config: PersonaConfigSchema.default({}),
|
|
1843
|
-
outputs: PersonaOutputsSchema.default({})
|
|
1844
|
-
});
|
|
1845
|
-
var PersonaSchema = z3.union([PersonaSchemaV1, PersonaSchemaV2]);
|
|
1846
|
-
|
|
1847
|
-
// src/persona/loader.ts
|
|
1848
|
-
function normalizePersona(raw) {
|
|
1849
|
-
if (raw.version === 1 && Array.isArray(raw.commands)) {
|
|
1850
|
-
const { commands, ...rest } = raw;
|
|
1851
|
-
return {
|
|
1852
|
-
...rest,
|
|
1853
|
-
steps: commands.map((cmd) => ({
|
|
1854
|
-
command: cmd,
|
|
1855
|
-
when: "always"
|
|
1856
|
-
}))
|
|
1857
|
-
};
|
|
1858
|
-
}
|
|
1859
|
-
return raw;
|
|
1860
|
-
}
|
|
1861
|
-
function loadPersona(filePath) {
|
|
1862
|
-
try {
|
|
1863
|
-
if (!fs6.existsSync(filePath)) {
|
|
1864
|
-
return Err(new Error(`Persona file not found: ${filePath}`));
|
|
1865
|
-
}
|
|
1866
|
-
const raw = fs6.readFileSync(filePath, "utf-8");
|
|
1867
|
-
const parsed = YAML.parse(raw);
|
|
1868
|
-
const result = PersonaSchema.safeParse(parsed);
|
|
1869
|
-
if (!result.success) {
|
|
1870
|
-
return Err(new Error(`Invalid persona ${filePath}: ${result.error.message}`));
|
|
1871
|
-
}
|
|
1872
|
-
return Ok(normalizePersona(result.data));
|
|
1873
|
-
} catch (error) {
|
|
1874
|
-
return Err(
|
|
1875
|
-
new Error(`Failed to load persona: ${error instanceof Error ? error.message : String(error)}`)
|
|
1876
|
-
);
|
|
1278
|
+
async function runAgentTask(task, options) {
|
|
1279
|
+
const configResult = resolveConfig(options.configPath);
|
|
1280
|
+
if (!configResult.ok) {
|
|
1281
|
+
return configResult;
|
|
1877
1282
|
}
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
const filePath = path14.join(dir, entry);
|
|
1886
|
-
const result = loadPersona(filePath);
|
|
1887
|
-
if (result.ok) {
|
|
1888
|
-
personas.push({ name: result.value.name, description: result.value.description, filePath });
|
|
1889
|
-
}
|
|
1890
|
-
}
|
|
1891
|
-
return Ok(personas);
|
|
1892
|
-
} catch (error) {
|
|
1283
|
+
const agentTypeMap = {
|
|
1284
|
+
review: "architecture-enforcer",
|
|
1285
|
+
"doc-review": "documentation-maintainer",
|
|
1286
|
+
"test-review": "test-reviewer"
|
|
1287
|
+
};
|
|
1288
|
+
const agentType = agentTypeMap[task];
|
|
1289
|
+
if (!agentType) {
|
|
1893
1290
|
return Err(
|
|
1894
|
-
new
|
|
1895
|
-
`
|
|
1291
|
+
new CLIError(
|
|
1292
|
+
`Unknown task: ${task}. Available: ${Object.keys(agentTypeMap).join(", ")}`,
|
|
1293
|
+
ExitCode.ERROR
|
|
1896
1294
|
)
|
|
1897
1295
|
);
|
|
1898
1296
|
}
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
return {
|
|
1914
|
-
trigger: "on_plan_approved",
|
|
1915
|
-
handoff: {
|
|
1916
|
-
fromSkill: handoff.fromSkill,
|
|
1917
|
-
summary: handoff.summary ?? "",
|
|
1918
|
-
pending: handoff.pending,
|
|
1919
|
-
planPath: handoff.planPath
|
|
1920
|
-
}
|
|
1921
|
-
};
|
|
1922
|
-
}
|
|
1923
|
-
return { trigger: "manual" };
|
|
1924
|
-
} catch {
|
|
1925
|
-
return { trigger: "manual" };
|
|
1926
|
-
}
|
|
1927
|
-
}
|
|
1928
|
-
|
|
1929
|
-
// src/persona/runner.ts
|
|
1930
|
-
var TIMEOUT_ERROR_MESSAGE = "__PERSONA_RUNNER_TIMEOUT__";
|
|
1931
|
-
function stepName(step) {
|
|
1932
|
-
return "command" in step ? step.command : step.skill;
|
|
1933
|
-
}
|
|
1934
|
-
function stepType(step) {
|
|
1935
|
-
return "command" in step ? "command" : "skill";
|
|
1936
|
-
}
|
|
1937
|
-
function matchesTrigger(step, trigger) {
|
|
1938
|
-
const when = step.when ?? "always";
|
|
1939
|
-
return when === "always" || when === trigger;
|
|
1940
|
-
}
|
|
1941
|
-
function skipRemaining(activeSteps, fromIndex, report) {
|
|
1942
|
-
for (let j = fromIndex; j < activeSteps.length; j++) {
|
|
1943
|
-
const remaining = activeSteps[j];
|
|
1944
|
-
report.steps.push({
|
|
1945
|
-
name: stepName(remaining),
|
|
1946
|
-
type: stepType(remaining),
|
|
1947
|
-
status: "skipped",
|
|
1948
|
-
durationMs: 0
|
|
1949
|
-
});
|
|
1950
|
-
}
|
|
1951
|
-
}
|
|
1952
|
-
async function runPersona(persona, context) {
|
|
1953
|
-
const startTime = Date.now();
|
|
1954
|
-
const timeout = persona.config.timeout;
|
|
1955
|
-
const report = {
|
|
1956
|
-
persona: persona.name.toLowerCase().replace(/\s+/g, "-"),
|
|
1957
|
-
status: "pass",
|
|
1958
|
-
steps: [],
|
|
1959
|
-
totalDurationMs: 0
|
|
1960
|
-
};
|
|
1961
|
-
let resolvedTrigger;
|
|
1962
|
-
let handoff = context.handoff;
|
|
1963
|
-
if (context.trigger === "auto") {
|
|
1964
|
-
const detection = detectTrigger(context.projectPath);
|
|
1965
|
-
resolvedTrigger = detection.trigger;
|
|
1966
|
-
handoff = detection.handoff ?? handoff;
|
|
1967
|
-
} else {
|
|
1968
|
-
resolvedTrigger = context.trigger;
|
|
1969
|
-
}
|
|
1970
|
-
const activeSteps = persona.steps.filter((s) => matchesTrigger(s, resolvedTrigger));
|
|
1971
|
-
for (let i = 0; i < activeSteps.length; i++) {
|
|
1972
|
-
const step = activeSteps[i];
|
|
1973
|
-
if (Date.now() - startTime >= timeout) {
|
|
1974
|
-
skipRemaining(activeSteps, i, report);
|
|
1975
|
-
report.status = "partial";
|
|
1976
|
-
break;
|
|
1977
|
-
}
|
|
1978
|
-
const stepStart = Date.now();
|
|
1979
|
-
const remainingTime = timeout - (Date.now() - startTime);
|
|
1980
|
-
if ("command" in step) {
|
|
1981
|
-
const result = await Promise.race([
|
|
1982
|
-
context.commandExecutor(step.command),
|
|
1983
|
-
new Promise(
|
|
1984
|
-
(resolve24) => setTimeout(
|
|
1985
|
-
() => resolve24({
|
|
1986
|
-
ok: false,
|
|
1987
|
-
error: new Error(TIMEOUT_ERROR_MESSAGE)
|
|
1988
|
-
}),
|
|
1989
|
-
remainingTime
|
|
1990
|
-
)
|
|
1991
|
-
)
|
|
1992
|
-
]);
|
|
1993
|
-
const durationMs = Date.now() - stepStart;
|
|
1994
|
-
if (result.ok) {
|
|
1995
|
-
report.steps.push({
|
|
1996
|
-
name: step.command,
|
|
1997
|
-
type: "command",
|
|
1998
|
-
status: "pass",
|
|
1999
|
-
result: result.value,
|
|
2000
|
-
durationMs
|
|
2001
|
-
});
|
|
2002
|
-
} else if (result.error.message === TIMEOUT_ERROR_MESSAGE) {
|
|
2003
|
-
report.steps.push({
|
|
2004
|
-
name: step.command,
|
|
2005
|
-
type: "command",
|
|
2006
|
-
status: "skipped",
|
|
2007
|
-
error: "timed out",
|
|
2008
|
-
durationMs
|
|
2009
|
-
});
|
|
2010
|
-
report.status = "partial";
|
|
2011
|
-
skipRemaining(activeSteps, i + 1, report);
|
|
2012
|
-
break;
|
|
2013
|
-
} else {
|
|
2014
|
-
report.steps.push({
|
|
2015
|
-
name: step.command,
|
|
2016
|
-
type: "command",
|
|
2017
|
-
status: "fail",
|
|
2018
|
-
error: result.error.message,
|
|
2019
|
-
durationMs
|
|
2020
|
-
});
|
|
2021
|
-
report.status = "fail";
|
|
2022
|
-
skipRemaining(activeSteps, i + 1, report);
|
|
2023
|
-
break;
|
|
2024
|
-
}
|
|
2025
|
-
} else {
|
|
2026
|
-
const skillContext = {
|
|
2027
|
-
trigger: resolvedTrigger,
|
|
2028
|
-
projectPath: context.projectPath,
|
|
2029
|
-
outputMode: step.output ?? "auto",
|
|
2030
|
-
...handoff ? { handoff } : {}
|
|
2031
|
-
};
|
|
2032
|
-
const SKILL_TIMEOUT_RESULT = {
|
|
2033
|
-
status: "fail",
|
|
2034
|
-
output: "timed out",
|
|
2035
|
-
durationMs: 0
|
|
2036
|
-
};
|
|
2037
|
-
const result = await Promise.race([
|
|
2038
|
-
context.skillExecutor(step.skill, skillContext),
|
|
2039
|
-
new Promise(
|
|
2040
|
-
(resolve24) => setTimeout(() => resolve24(SKILL_TIMEOUT_RESULT), remainingTime)
|
|
2041
|
-
)
|
|
2042
|
-
]);
|
|
2043
|
-
const durationMs = Date.now() - stepStart;
|
|
2044
|
-
if (result === SKILL_TIMEOUT_RESULT) {
|
|
2045
|
-
report.steps.push({
|
|
2046
|
-
name: step.skill,
|
|
2047
|
-
type: "skill",
|
|
2048
|
-
status: "skipped",
|
|
2049
|
-
error: "timed out",
|
|
2050
|
-
durationMs
|
|
2051
|
-
});
|
|
2052
|
-
report.status = "partial";
|
|
2053
|
-
skipRemaining(activeSteps, i + 1, report);
|
|
2054
|
-
break;
|
|
2055
|
-
} else if (result.status === "pass") {
|
|
2056
|
-
report.steps.push({
|
|
2057
|
-
name: step.skill,
|
|
2058
|
-
type: "skill",
|
|
2059
|
-
status: "pass",
|
|
2060
|
-
result: result.output,
|
|
2061
|
-
...result.artifactPath ? { artifactPath: result.artifactPath } : {},
|
|
2062
|
-
durationMs
|
|
2063
|
-
});
|
|
2064
|
-
} else {
|
|
2065
|
-
report.steps.push({
|
|
2066
|
-
name: step.skill,
|
|
2067
|
-
type: "skill",
|
|
2068
|
-
status: "fail",
|
|
2069
|
-
error: result.output,
|
|
2070
|
-
durationMs
|
|
2071
|
-
});
|
|
2072
|
-
report.status = "fail";
|
|
2073
|
-
skipRemaining(activeSteps, i + 1, report);
|
|
2074
|
-
break;
|
|
2075
|
-
}
|
|
2076
|
-
}
|
|
2077
|
-
}
|
|
2078
|
-
report.totalDurationMs = Date.now() - startTime;
|
|
2079
|
-
return report;
|
|
2080
|
-
}
|
|
2081
|
-
|
|
2082
|
-
// src/persona/skill-executor.ts
|
|
2083
|
-
import * as fs8 from "fs";
|
|
2084
|
-
import * as path16 from "path";
|
|
2085
|
-
import { parse as parse2 } from "yaml";
|
|
2086
|
-
function resolveOutputMode(mode, trigger) {
|
|
2087
|
-
if (mode !== "auto") return mode;
|
|
2088
|
-
return trigger === "manual" ? "inline" : "artifact";
|
|
2089
|
-
}
|
|
2090
|
-
function buildArtifactPath(projectPath, headSha) {
|
|
2091
|
-
const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
2092
|
-
const sha = headSha?.slice(0, 7) ?? "unknown";
|
|
2093
|
-
return path16.join(projectPath, ".harness", "reviews", `${date}-${sha}.md`);
|
|
2094
|
-
}
|
|
2095
|
-
function buildArtifactContent(skillName, trigger, headSha) {
|
|
2096
|
-
const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
2097
|
-
return [
|
|
2098
|
-
"---",
|
|
2099
|
-
`skill: ${skillName}`,
|
|
2100
|
-
`trigger: ${trigger}`,
|
|
2101
|
-
`sha: ${headSha?.slice(0, 7) ?? "unknown"}`,
|
|
2102
|
-
`date: ${date}`,
|
|
2103
|
-
`assessment: pending`,
|
|
2104
|
-
"---",
|
|
2105
|
-
"",
|
|
2106
|
-
`# Review by ${skillName}`,
|
|
2107
|
-
"",
|
|
2108
|
-
"## Strengths",
|
|
2109
|
-
"",
|
|
2110
|
-
"- (review pending)",
|
|
2111
|
-
"",
|
|
2112
|
-
"## Issues",
|
|
2113
|
-
"",
|
|
2114
|
-
"### Critical",
|
|
2115
|
-
"",
|
|
2116
|
-
"- None identified",
|
|
2117
|
-
"",
|
|
2118
|
-
"### Important",
|
|
2119
|
-
"",
|
|
2120
|
-
"- None identified",
|
|
2121
|
-
"",
|
|
2122
|
-
"### Suggestions",
|
|
2123
|
-
"",
|
|
2124
|
-
"- None identified",
|
|
2125
|
-
"",
|
|
2126
|
-
"## Assessment",
|
|
2127
|
-
"",
|
|
2128
|
-
"Pending \u2014 skill execution scaffolded.",
|
|
2129
|
-
"",
|
|
2130
|
-
"## Harness Checks",
|
|
2131
|
-
"",
|
|
2132
|
-
"- (run harness validate, check-deps, check-docs to populate)",
|
|
2133
|
-
""
|
|
2134
|
-
].join("\n");
|
|
2135
|
-
}
|
|
2136
|
-
async function executeSkill(skillName, context) {
|
|
2137
|
-
const startTime = Date.now();
|
|
2138
|
-
const skillsDir = resolveSkillsDir();
|
|
2139
|
-
const skillDir = path16.join(skillsDir, skillName);
|
|
2140
|
-
if (!fs8.existsSync(skillDir)) {
|
|
2141
|
-
return {
|
|
2142
|
-
status: "fail",
|
|
2143
|
-
output: `Skill not found: ${skillName}`,
|
|
2144
|
-
durationMs: Date.now() - startTime
|
|
2145
|
-
};
|
|
2146
|
-
}
|
|
2147
|
-
const yamlPath = path16.join(skillDir, "skill.yaml");
|
|
2148
|
-
if (!fs8.existsSync(yamlPath)) {
|
|
2149
|
-
return {
|
|
2150
|
-
status: "fail",
|
|
2151
|
-
output: `skill.yaml not found for ${skillName}`,
|
|
2152
|
-
durationMs: Date.now() - startTime
|
|
2153
|
-
};
|
|
2154
|
-
}
|
|
2155
|
-
const raw = fs8.readFileSync(yamlPath, "utf-8");
|
|
2156
|
-
const parsed = parse2(raw);
|
|
2157
|
-
const metadataResult = SkillMetadataSchema.safeParse(parsed);
|
|
2158
|
-
if (!metadataResult.success) {
|
|
2159
|
-
return {
|
|
2160
|
-
status: "fail",
|
|
2161
|
-
output: `Invalid skill metadata: ${metadataResult.error.message}`,
|
|
2162
|
-
durationMs: Date.now() - startTime
|
|
2163
|
-
};
|
|
2164
|
-
}
|
|
2165
|
-
const skillMdPath = path16.join(skillDir, "SKILL.md");
|
|
2166
|
-
if (!fs8.existsSync(skillMdPath)) {
|
|
2167
|
-
return {
|
|
2168
|
-
status: "fail",
|
|
2169
|
-
output: `SKILL.md not found for ${skillName}`,
|
|
2170
|
-
durationMs: Date.now() - startTime
|
|
2171
|
-
};
|
|
2172
|
-
}
|
|
2173
|
-
const skillContent = fs8.readFileSync(skillMdPath, "utf-8");
|
|
2174
|
-
const metadata = metadataResult.data;
|
|
2175
|
-
const resolvedMode = resolveOutputMode(context.outputMode, context.trigger);
|
|
2176
|
-
const output = `Skill ${metadata.name} (${metadata.type}) loaded.
|
|
2177
|
-
Cognitive mode: ${metadata.cognitive_mode ?? "default"}
|
|
2178
|
-
Content length: ${skillContent.length} chars
|
|
2179
|
-
Trigger: ${context.trigger}
|
|
2180
|
-
`;
|
|
2181
|
-
let artifactPath;
|
|
2182
|
-
if (resolvedMode === "artifact") {
|
|
2183
|
-
artifactPath = buildArtifactPath(context.projectPath, context.headSha);
|
|
2184
|
-
const artifactContent = buildArtifactContent(skillName, context.trigger, context.headSha);
|
|
2185
|
-
const dir = path16.dirname(artifactPath);
|
|
2186
|
-
fs8.mkdirSync(dir, { recursive: true });
|
|
2187
|
-
fs8.writeFileSync(artifactPath, artifactContent, "utf-8");
|
|
2188
|
-
}
|
|
2189
|
-
return {
|
|
2190
|
-
status: "pass",
|
|
2191
|
-
output,
|
|
2192
|
-
...artifactPath ? { artifactPath } : {},
|
|
2193
|
-
durationMs: Date.now() - startTime
|
|
2194
|
-
};
|
|
2195
|
-
}
|
|
2196
|
-
|
|
2197
|
-
// src/persona/constants.ts
|
|
2198
|
-
var ALLOWED_PERSONA_COMMANDS = /* @__PURE__ */ new Set([
|
|
2199
|
-
"validate",
|
|
2200
|
-
"check-deps",
|
|
2201
|
-
"check-docs",
|
|
2202
|
-
"check-perf",
|
|
2203
|
-
"check-security",
|
|
2204
|
-
"cleanup",
|
|
2205
|
-
"fix-drift",
|
|
2206
|
-
"add"
|
|
2207
|
-
]);
|
|
2208
|
-
|
|
2209
|
-
// src/commands/agent/run.ts
|
|
2210
|
-
async function runAgentTask(task, options) {
|
|
2211
|
-
const configResult = resolveConfig(options.configPath);
|
|
2212
|
-
if (!configResult.ok) {
|
|
2213
|
-
return configResult;
|
|
2214
|
-
}
|
|
2215
|
-
const agentTypeMap = {
|
|
2216
|
-
review: "architecture-enforcer",
|
|
2217
|
-
"doc-review": "documentation-maintainer",
|
|
2218
|
-
"test-review": "test-reviewer"
|
|
2219
|
-
};
|
|
2220
|
-
const agentType = agentTypeMap[task];
|
|
2221
|
-
if (!agentType) {
|
|
2222
|
-
return Err(
|
|
2223
|
-
new CLIError(
|
|
2224
|
-
`Unknown task: ${task}. Available: ${Object.keys(agentTypeMap).join(", ")}`,
|
|
2225
|
-
ExitCode.ERROR
|
|
2226
|
-
)
|
|
2227
|
-
);
|
|
2228
|
-
}
|
|
2229
|
-
const config = configResult.value;
|
|
2230
|
-
const timeout = options.timeout ?? config.agent?.timeout ?? 3e5;
|
|
2231
|
-
const reviewResult = await requestPeerReview(
|
|
2232
|
-
agentType,
|
|
2233
|
-
{
|
|
2234
|
-
files: [],
|
|
2235
|
-
diff: "",
|
|
2236
|
-
commitMessage: task,
|
|
2237
|
-
metadata: { task, timeout }
|
|
2238
|
-
},
|
|
2239
|
-
{ timeout }
|
|
2240
|
-
);
|
|
2241
|
-
if (!reviewResult.ok) {
|
|
2242
|
-
return Err(new CLIError(`Agent task failed: ${reviewResult.error.message}`, ExitCode.ERROR));
|
|
1297
|
+
const config = configResult.value;
|
|
1298
|
+
const timeout = options.timeout ?? config.agent?.timeout ?? 3e5;
|
|
1299
|
+
const reviewResult = await requestPeerReview(
|
|
1300
|
+
agentType,
|
|
1301
|
+
{
|
|
1302
|
+
files: [],
|
|
1303
|
+
diff: "",
|
|
1304
|
+
commitMessage: task,
|
|
1305
|
+
metadata: { task, timeout }
|
|
1306
|
+
},
|
|
1307
|
+
{ timeout }
|
|
1308
|
+
);
|
|
1309
|
+
if (!reviewResult.ok) {
|
|
1310
|
+
return Err(new CLIError(`Agent task failed: ${reviewResult.error.message}`, ExitCode.ERROR));
|
|
2243
1311
|
}
|
|
2244
1312
|
const review = reviewResult.value;
|
|
2245
1313
|
return Ok({
|
|
@@ -2263,7 +1331,7 @@ function createRunCommand() {
|
|
|
2263
1331
|
const globalOpts = cmd.optsWithGlobals();
|
|
2264
1332
|
if (opts.persona) {
|
|
2265
1333
|
const personasDir = resolvePersonasDir();
|
|
2266
|
-
const filePath =
|
|
1334
|
+
const filePath = path11.join(personasDir, `${opts.persona}.yaml`);
|
|
2267
1335
|
const personaResult = loadPersona(filePath);
|
|
2268
1336
|
if (!personaResult.ok) {
|
|
2269
1337
|
logger.error(personaResult.error.message);
|
|
@@ -2444,8 +1512,8 @@ function createAgentCommand() {
|
|
|
2444
1512
|
|
|
2445
1513
|
// src/commands/add.ts
|
|
2446
1514
|
import { Command as Command14 } from "commander";
|
|
2447
|
-
import * as
|
|
2448
|
-
import * as
|
|
1515
|
+
import * as fs3 from "fs";
|
|
1516
|
+
import * as path12 from "path";
|
|
2449
1517
|
var LAYER_INDEX_TEMPLATE = (name) => `// ${name} layer
|
|
2450
1518
|
// Add your ${name} exports here
|
|
2451
1519
|
|
|
@@ -2489,61 +1557,61 @@ async function runAdd(componentType, name, options) {
|
|
|
2489
1557
|
try {
|
|
2490
1558
|
switch (componentType) {
|
|
2491
1559
|
case "layer": {
|
|
2492
|
-
const layerDir =
|
|
2493
|
-
if (!
|
|
2494
|
-
|
|
1560
|
+
const layerDir = path12.join(cwd, "src", name);
|
|
1561
|
+
if (!fs3.existsSync(layerDir)) {
|
|
1562
|
+
fs3.mkdirSync(layerDir, { recursive: true });
|
|
2495
1563
|
created.push(`src/${name}/`);
|
|
2496
1564
|
}
|
|
2497
|
-
const indexPath =
|
|
2498
|
-
if (!
|
|
2499
|
-
|
|
1565
|
+
const indexPath = path12.join(layerDir, "index.ts");
|
|
1566
|
+
if (!fs3.existsSync(indexPath)) {
|
|
1567
|
+
fs3.writeFileSync(indexPath, LAYER_INDEX_TEMPLATE(name));
|
|
2500
1568
|
created.push(`src/${name}/index.ts`);
|
|
2501
1569
|
}
|
|
2502
1570
|
break;
|
|
2503
1571
|
}
|
|
2504
1572
|
case "module": {
|
|
2505
|
-
const modulePath =
|
|
2506
|
-
if (
|
|
1573
|
+
const modulePath = path12.join(cwd, "src", `${name}.ts`);
|
|
1574
|
+
if (fs3.existsSync(modulePath)) {
|
|
2507
1575
|
return Err(new CLIError(`Module ${name} already exists`, ExitCode.ERROR));
|
|
2508
1576
|
}
|
|
2509
|
-
|
|
1577
|
+
fs3.writeFileSync(modulePath, MODULE_TEMPLATE(name));
|
|
2510
1578
|
created.push(`src/${name}.ts`);
|
|
2511
1579
|
break;
|
|
2512
1580
|
}
|
|
2513
1581
|
case "doc": {
|
|
2514
|
-
const docsDir =
|
|
2515
|
-
if (!
|
|
2516
|
-
|
|
1582
|
+
const docsDir = path12.join(cwd, "docs");
|
|
1583
|
+
if (!fs3.existsSync(docsDir)) {
|
|
1584
|
+
fs3.mkdirSync(docsDir, { recursive: true });
|
|
2517
1585
|
}
|
|
2518
|
-
const docPath =
|
|
2519
|
-
if (
|
|
1586
|
+
const docPath = path12.join(docsDir, `${name}.md`);
|
|
1587
|
+
if (fs3.existsSync(docPath)) {
|
|
2520
1588
|
return Err(new CLIError(`Doc ${name} already exists`, ExitCode.ERROR));
|
|
2521
1589
|
}
|
|
2522
|
-
|
|
1590
|
+
fs3.writeFileSync(docPath, DOC_TEMPLATE(name));
|
|
2523
1591
|
created.push(`docs/${name}.md`);
|
|
2524
1592
|
break;
|
|
2525
1593
|
}
|
|
2526
1594
|
case "skill": {
|
|
2527
|
-
const { generateSkillFiles: generateSkillFiles2 } = await import("./create-skill-
|
|
1595
|
+
const { generateSkillFiles: generateSkillFiles2 } = await import("./create-skill-LUWO46WF.js");
|
|
2528
1596
|
generateSkillFiles2({
|
|
2529
1597
|
name,
|
|
2530
1598
|
description: `${name} skill`,
|
|
2531
|
-
outputDir:
|
|
1599
|
+
outputDir: path12.join(cwd, "agents", "skills", "claude-code")
|
|
2532
1600
|
});
|
|
2533
1601
|
created.push(`agents/skills/claude-code/${name}/skill.yaml`);
|
|
2534
1602
|
created.push(`agents/skills/claude-code/${name}/SKILL.md`);
|
|
2535
1603
|
break;
|
|
2536
1604
|
}
|
|
2537
1605
|
case "persona": {
|
|
2538
|
-
const personasDir =
|
|
2539
|
-
if (!
|
|
2540
|
-
|
|
1606
|
+
const personasDir = path12.join(cwd, "agents", "personas");
|
|
1607
|
+
if (!fs3.existsSync(personasDir)) {
|
|
1608
|
+
fs3.mkdirSync(personasDir, { recursive: true });
|
|
2541
1609
|
}
|
|
2542
|
-
const personaPath =
|
|
2543
|
-
if (
|
|
1610
|
+
const personaPath = path12.join(personasDir, `${name}.yaml`);
|
|
1611
|
+
if (fs3.existsSync(personaPath)) {
|
|
2544
1612
|
return Err(new CLIError(`Persona ${name} already exists`, ExitCode.ERROR));
|
|
2545
1613
|
}
|
|
2546
|
-
|
|
1614
|
+
fs3.writeFileSync(
|
|
2547
1615
|
personaPath,
|
|
2548
1616
|
`name: ${name}
|
|
2549
1617
|
description: ${name} persona
|
|
@@ -2601,346 +1669,6 @@ import { Command as Command17 } from "commander";
|
|
|
2601
1669
|
|
|
2602
1670
|
// src/commands/linter/generate.ts
|
|
2603
1671
|
import { Command as Command15 } from "commander";
|
|
2604
|
-
|
|
2605
|
-
// ../linter-gen/dist/generator/orchestrator.js
|
|
2606
|
-
import * as fs12 from "fs/promises";
|
|
2607
|
-
import * as path21 from "path";
|
|
2608
|
-
|
|
2609
|
-
// ../linter-gen/dist/parser/config-parser.js
|
|
2610
|
-
import * as fs10 from "fs/promises";
|
|
2611
|
-
import * as yaml from "yaml";
|
|
2612
|
-
|
|
2613
|
-
// ../linter-gen/dist/schema/linter-config.js
|
|
2614
|
-
import { z as z4 } from "zod";
|
|
2615
|
-
var RuleConfigSchema = z4.object({
|
|
2616
|
-
/** Rule name in kebab-case (e.g., 'no-ui-in-services') */
|
|
2617
|
-
name: z4.string().regex(/^[a-z][a-z0-9]*(-[a-z0-9]+)*$/, "Rule name must be kebab-case"),
|
|
2618
|
-
/** Rule type - determines which template to use */
|
|
2619
|
-
type: z4.string().min(1),
|
|
2620
|
-
/** ESLint severity level */
|
|
2621
|
-
severity: z4.enum(["error", "warn", "off"]).default("error"),
|
|
2622
|
-
/** Template-specific configuration */
|
|
2623
|
-
config: z4.record(z4.unknown())
|
|
2624
|
-
});
|
|
2625
|
-
var LinterConfigSchema = z4.object({
|
|
2626
|
-
/** Config version - currently only 1 is supported */
|
|
2627
|
-
version: z4.literal(1),
|
|
2628
|
-
/** Output directory for generated rules */
|
|
2629
|
-
output: z4.string().min(1),
|
|
2630
|
-
/** Optional explicit template path mappings (type → path) */
|
|
2631
|
-
templates: z4.record(z4.string()).optional(),
|
|
2632
|
-
/** Rules to generate */
|
|
2633
|
-
rules: z4.array(RuleConfigSchema).min(1, "At least one rule is required")
|
|
2634
|
-
});
|
|
2635
|
-
|
|
2636
|
-
// ../linter-gen/dist/parser/config-parser.js
|
|
2637
|
-
var ParseError = class extends Error {
|
|
2638
|
-
code;
|
|
2639
|
-
cause;
|
|
2640
|
-
constructor(message, code, cause) {
|
|
2641
|
-
super(message);
|
|
2642
|
-
this.code = code;
|
|
2643
|
-
this.cause = cause;
|
|
2644
|
-
this.name = "ParseError";
|
|
2645
|
-
}
|
|
2646
|
-
};
|
|
2647
|
-
async function parseConfig(configPath) {
|
|
2648
|
-
let content;
|
|
2649
|
-
try {
|
|
2650
|
-
content = await fs10.readFile(configPath, "utf-8");
|
|
2651
|
-
} catch (err) {
|
|
2652
|
-
if (err.code === "ENOENT") {
|
|
2653
|
-
return {
|
|
2654
|
-
success: false,
|
|
2655
|
-
error: new ParseError(`Config file not found: ${configPath}`, "FILE_NOT_FOUND", err)
|
|
2656
|
-
};
|
|
2657
|
-
}
|
|
2658
|
-
return {
|
|
2659
|
-
success: false,
|
|
2660
|
-
error: new ParseError(`Failed to read config file: ${configPath}`, "FILE_READ_ERROR", err)
|
|
2661
|
-
};
|
|
2662
|
-
}
|
|
2663
|
-
let parsed;
|
|
2664
|
-
try {
|
|
2665
|
-
parsed = yaml.parse(content);
|
|
2666
|
-
} catch (err) {
|
|
2667
|
-
return {
|
|
2668
|
-
success: false,
|
|
2669
|
-
error: new ParseError(`Invalid YAML syntax in ${configPath}: ${err.message}`, "YAML_PARSE_ERROR", err)
|
|
2670
|
-
};
|
|
2671
|
-
}
|
|
2672
|
-
const result = LinterConfigSchema.safeParse(parsed);
|
|
2673
|
-
if (!result.success) {
|
|
2674
|
-
const issues = result.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
|
|
2675
|
-
return {
|
|
2676
|
-
success: false,
|
|
2677
|
-
error: new ParseError(`Invalid config: ${issues}`, "VALIDATION_ERROR", result.error)
|
|
2678
|
-
};
|
|
2679
|
-
}
|
|
2680
|
-
return {
|
|
2681
|
-
success: true,
|
|
2682
|
-
data: result.data,
|
|
2683
|
-
configPath
|
|
2684
|
-
};
|
|
2685
|
-
}
|
|
2686
|
-
|
|
2687
|
-
// ../linter-gen/dist/engine/template-loader.js
|
|
2688
|
-
import * as fs11 from "fs/promises";
|
|
2689
|
-
import * as path19 from "path";
|
|
2690
|
-
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
2691
|
-
var __filename2 = fileURLToPath2(import.meta.url);
|
|
2692
|
-
var __dirname2 = path19.dirname(__filename2);
|
|
2693
|
-
var TemplateLoadError = class extends Error {
|
|
2694
|
-
code;
|
|
2695
|
-
cause;
|
|
2696
|
-
constructor(message, code, cause) {
|
|
2697
|
-
super(message);
|
|
2698
|
-
this.code = code;
|
|
2699
|
-
this.cause = cause;
|
|
2700
|
-
this.name = "TemplateLoadError";
|
|
2701
|
-
}
|
|
2702
|
-
};
|
|
2703
|
-
var BUILTIN_TEMPLATES = ["import-restriction", "boundary-validation", "dependency-graph"];
|
|
2704
|
-
async function fileExists(filePath) {
|
|
2705
|
-
try {
|
|
2706
|
-
await fs11.access(filePath);
|
|
2707
|
-
return true;
|
|
2708
|
-
} catch {
|
|
2709
|
-
return false;
|
|
2710
|
-
}
|
|
2711
|
-
}
|
|
2712
|
-
async function loadTemplateFile(filePath, type) {
|
|
2713
|
-
try {
|
|
2714
|
-
const content = await fs11.readFile(filePath, "utf-8");
|
|
2715
|
-
return {
|
|
2716
|
-
success: true,
|
|
2717
|
-
source: { type, path: filePath, content }
|
|
2718
|
-
};
|
|
2719
|
-
} catch (err) {
|
|
2720
|
-
return {
|
|
2721
|
-
success: false,
|
|
2722
|
-
error: new TemplateLoadError(`Failed to read template: ${filePath}`, "TEMPLATE_READ_ERROR", err)
|
|
2723
|
-
};
|
|
2724
|
-
}
|
|
2725
|
-
}
|
|
2726
|
-
async function loadTemplate(ruleType, templatesConfig, configDir) {
|
|
2727
|
-
if (templatesConfig?.[ruleType]) {
|
|
2728
|
-
const explicitPath = path19.resolve(configDir, templatesConfig[ruleType]);
|
|
2729
|
-
if (await fileExists(explicitPath)) {
|
|
2730
|
-
return loadTemplateFile(explicitPath, "explicit");
|
|
2731
|
-
}
|
|
2732
|
-
return {
|
|
2733
|
-
success: false,
|
|
2734
|
-
error: new TemplateLoadError(`Explicit template not found: ${explicitPath}`, "TEMPLATE_NOT_FOUND")
|
|
2735
|
-
};
|
|
2736
|
-
}
|
|
2737
|
-
const conventionPath = path19.join(configDir, "templates", `${ruleType}.ts.hbs`);
|
|
2738
|
-
if (await fileExists(conventionPath)) {
|
|
2739
|
-
return loadTemplateFile(conventionPath, "convention");
|
|
2740
|
-
}
|
|
2741
|
-
if (BUILTIN_TEMPLATES.includes(ruleType)) {
|
|
2742
|
-
const builtinPath = path19.join(__dirname2, "..", "templates", `${ruleType}.ts.hbs`);
|
|
2743
|
-
if (await fileExists(builtinPath)) {
|
|
2744
|
-
return loadTemplateFile(builtinPath, "builtin");
|
|
2745
|
-
}
|
|
2746
|
-
}
|
|
2747
|
-
return {
|
|
2748
|
-
success: false,
|
|
2749
|
-
error: new TemplateLoadError(`Template not found for type '${ruleType}'. Checked: explicit config, ./templates/${ruleType}.ts.hbs, built-in templates.`, "TEMPLATE_NOT_FOUND")
|
|
2750
|
-
};
|
|
2751
|
-
}
|
|
2752
|
-
|
|
2753
|
-
// ../linter-gen/dist/generator/rule-generator.js
|
|
2754
|
-
import * as path20 from "path";
|
|
2755
|
-
|
|
2756
|
-
// ../linter-gen/dist/engine/context-builder.js
|
|
2757
|
-
var GENERATOR_VERSION = "0.1.0";
|
|
2758
|
-
function toCamelCase(str) {
|
|
2759
|
-
return str.replace(/-([a-z0-9])/g, (_, char) => char.toUpperCase());
|
|
2760
|
-
}
|
|
2761
|
-
function toPascalCase(str) {
|
|
2762
|
-
const camel = toCamelCase(str);
|
|
2763
|
-
return camel.charAt(0).toUpperCase() + camel.slice(1);
|
|
2764
|
-
}
|
|
2765
|
-
function buildRuleContext(rule, configPath) {
|
|
2766
|
-
return {
|
|
2767
|
-
name: rule.name,
|
|
2768
|
-
nameCamel: toCamelCase(rule.name),
|
|
2769
|
-
namePascal: toPascalCase(rule.name),
|
|
2770
|
-
severity: rule.severity,
|
|
2771
|
-
config: rule.config,
|
|
2772
|
-
meta: {
|
|
2773
|
-
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2774
|
-
generatorVersion: GENERATOR_VERSION,
|
|
2775
|
-
configPath
|
|
2776
|
-
}
|
|
2777
|
-
};
|
|
2778
|
-
}
|
|
2779
|
-
|
|
2780
|
-
// ../linter-gen/dist/engine/template-renderer.js
|
|
2781
|
-
import Handlebars2 from "handlebars";
|
|
2782
|
-
var TemplateError = class extends Error {
|
|
2783
|
-
cause;
|
|
2784
|
-
constructor(message, cause) {
|
|
2785
|
-
super(message);
|
|
2786
|
-
this.cause = cause;
|
|
2787
|
-
this.name = "TemplateError";
|
|
2788
|
-
}
|
|
2789
|
-
};
|
|
2790
|
-
function toCamelCase2(str) {
|
|
2791
|
-
return str.replace(/-([a-z0-9])/g, (_, char) => char.toUpperCase());
|
|
2792
|
-
}
|
|
2793
|
-
function toPascalCase2(str) {
|
|
2794
|
-
const camel = toCamelCase2(str);
|
|
2795
|
-
return camel.charAt(0).toUpperCase() + camel.slice(1);
|
|
2796
|
-
}
|
|
2797
|
-
Handlebars2.registerHelper("json", (obj) => JSON.stringify(obj));
|
|
2798
|
-
Handlebars2.registerHelper("jsonPretty", (obj) => JSON.stringify(obj, null, 2));
|
|
2799
|
-
Handlebars2.registerHelper("camelCase", (str) => toCamelCase2(str));
|
|
2800
|
-
Handlebars2.registerHelper("pascalCase", (str) => toPascalCase2(str));
|
|
2801
|
-
function renderTemplate(templateSource, context) {
|
|
2802
|
-
try {
|
|
2803
|
-
const compiled = Handlebars2.compile(templateSource, { strict: true });
|
|
2804
|
-
const output = compiled(context);
|
|
2805
|
-
return { success: true, output };
|
|
2806
|
-
} catch (err) {
|
|
2807
|
-
return {
|
|
2808
|
-
success: false,
|
|
2809
|
-
error: new TemplateError(`Template rendering failed: ${err.message}`, err)
|
|
2810
|
-
};
|
|
2811
|
-
}
|
|
2812
|
-
}
|
|
2813
|
-
|
|
2814
|
-
// ../linter-gen/dist/generator/rule-generator.js
|
|
2815
|
-
function generateRule(rule, template, outputDir, configPath) {
|
|
2816
|
-
const context = buildRuleContext(rule, configPath);
|
|
2817
|
-
const renderResult = renderTemplate(template.content, context);
|
|
2818
|
-
if (!renderResult.success) {
|
|
2819
|
-
return {
|
|
2820
|
-
success: false,
|
|
2821
|
-
error: renderResult.error,
|
|
2822
|
-
ruleName: rule.name
|
|
2823
|
-
};
|
|
2824
|
-
}
|
|
2825
|
-
const outputPath = path20.join(outputDir, `${rule.name}.ts`);
|
|
2826
|
-
return {
|
|
2827
|
-
success: true,
|
|
2828
|
-
rule: {
|
|
2829
|
-
name: rule.name,
|
|
2830
|
-
outputPath,
|
|
2831
|
-
content: renderResult.output
|
|
2832
|
-
}
|
|
2833
|
-
};
|
|
2834
|
-
}
|
|
2835
|
-
|
|
2836
|
-
// ../linter-gen/dist/generator/index-generator.js
|
|
2837
|
-
function toCamelCase3(str) {
|
|
2838
|
-
return str.replace(/-([a-z0-9])/g, (_, char) => char.toUpperCase());
|
|
2839
|
-
}
|
|
2840
|
-
function generateIndex(ruleNames) {
|
|
2841
|
-
const imports = ruleNames.map((name) => {
|
|
2842
|
-
const camel = toCamelCase3(name);
|
|
2843
|
-
return `import ${camel} from './${name}';`;
|
|
2844
|
-
}).join("\n");
|
|
2845
|
-
const rulesObject = ruleNames.map((name) => {
|
|
2846
|
-
const camel = toCamelCase3(name);
|
|
2847
|
-
return ` '${name}': ${camel},`;
|
|
2848
|
-
}).join("\n");
|
|
2849
|
-
const namedExports = ruleNames.map(toCamelCase3).join(", ");
|
|
2850
|
-
return `// Generated by @harness-engineering/linter-gen
|
|
2851
|
-
// Do not edit manually - regenerate from harness-linter.yml
|
|
2852
|
-
|
|
2853
|
-
${imports}
|
|
2854
|
-
|
|
2855
|
-
export const rules = {
|
|
2856
|
-
${rulesObject}
|
|
2857
|
-
};
|
|
2858
|
-
|
|
2859
|
-
export { ${namedExports} };
|
|
2860
|
-
`;
|
|
2861
|
-
}
|
|
2862
|
-
|
|
2863
|
-
// ../linter-gen/dist/generator/orchestrator.js
|
|
2864
|
-
async function validate(options) {
|
|
2865
|
-
const parseResult = await parseConfig(options.configPath);
|
|
2866
|
-
if (!parseResult.success) {
|
|
2867
|
-
return { success: false, error: parseResult.error };
|
|
2868
|
-
}
|
|
2869
|
-
return { success: true, ruleCount: parseResult.data.rules.length };
|
|
2870
|
-
}
|
|
2871
|
-
async function generate(options) {
|
|
2872
|
-
const errors = [];
|
|
2873
|
-
const parseResult = await parseConfig(options.configPath);
|
|
2874
|
-
if (!parseResult.success) {
|
|
2875
|
-
return { success: false, errors: [{ type: "parse", error: parseResult.error }] };
|
|
2876
|
-
}
|
|
2877
|
-
const config = parseResult.data;
|
|
2878
|
-
const configDir = path21.dirname(path21.resolve(options.configPath));
|
|
2879
|
-
const outputDir = options.outputDir ? path21.resolve(options.outputDir) : path21.resolve(configDir, config.output);
|
|
2880
|
-
if (options.clean && !options.dryRun) {
|
|
2881
|
-
try {
|
|
2882
|
-
await fs12.rm(outputDir, { recursive: true, force: true });
|
|
2883
|
-
} catch {
|
|
2884
|
-
}
|
|
2885
|
-
}
|
|
2886
|
-
if (!options.dryRun) {
|
|
2887
|
-
await fs12.mkdir(outputDir, { recursive: true });
|
|
2888
|
-
}
|
|
2889
|
-
const generatedRules = [];
|
|
2890
|
-
for (const rule of config.rules) {
|
|
2891
|
-
const templateResult = await loadTemplate(rule.type, config.templates, configDir);
|
|
2892
|
-
if (!templateResult.success) {
|
|
2893
|
-
errors.push({
|
|
2894
|
-
type: "template",
|
|
2895
|
-
error: templateResult.error,
|
|
2896
|
-
ruleName: rule.name
|
|
2897
|
-
});
|
|
2898
|
-
continue;
|
|
2899
|
-
}
|
|
2900
|
-
const ruleResult = generateRule(rule, templateResult.source, outputDir, options.configPath);
|
|
2901
|
-
if (!ruleResult.success) {
|
|
2902
|
-
errors.push({
|
|
2903
|
-
type: "render",
|
|
2904
|
-
error: ruleResult.error,
|
|
2905
|
-
ruleName: ruleResult.ruleName
|
|
2906
|
-
});
|
|
2907
|
-
continue;
|
|
2908
|
-
}
|
|
2909
|
-
if (!options.dryRun) {
|
|
2910
|
-
try {
|
|
2911
|
-
await fs12.writeFile(ruleResult.rule.outputPath, ruleResult.rule.content, "utf-8");
|
|
2912
|
-
} catch (err) {
|
|
2913
|
-
errors.push({
|
|
2914
|
-
type: "write",
|
|
2915
|
-
error: err,
|
|
2916
|
-
path: ruleResult.rule.outputPath
|
|
2917
|
-
});
|
|
2918
|
-
continue;
|
|
2919
|
-
}
|
|
2920
|
-
}
|
|
2921
|
-
generatedRules.push(rule.name);
|
|
2922
|
-
}
|
|
2923
|
-
if (generatedRules.length > 0 && !options.dryRun) {
|
|
2924
|
-
const indexContent = generateIndex(generatedRules);
|
|
2925
|
-
const indexPath = path21.join(outputDir, "index.ts");
|
|
2926
|
-
try {
|
|
2927
|
-
await fs12.writeFile(indexPath, indexContent, "utf-8");
|
|
2928
|
-
} catch (err) {
|
|
2929
|
-
errors.push({ type: "write", error: err, path: indexPath });
|
|
2930
|
-
}
|
|
2931
|
-
}
|
|
2932
|
-
if (errors.length > 0) {
|
|
2933
|
-
return { success: false, errors };
|
|
2934
|
-
}
|
|
2935
|
-
return {
|
|
2936
|
-
success: true,
|
|
2937
|
-
rulesGenerated: generatedRules,
|
|
2938
|
-
outputDir,
|
|
2939
|
-
dryRun: options.dryRun ?? false
|
|
2940
|
-
};
|
|
2941
|
-
}
|
|
2942
|
-
|
|
2943
|
-
// src/commands/linter/generate.ts
|
|
2944
1672
|
function createGenerateCommand() {
|
|
2945
1673
|
return new Command15("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) => {
|
|
2946
1674
|
try {
|
|
@@ -3069,176 +1797,46 @@ function createListCommand() {
|
|
|
3069
1797
|
|
|
3070
1798
|
// src/commands/persona/generate.ts
|
|
3071
1799
|
import { Command as Command19 } from "commander";
|
|
3072
|
-
import * as
|
|
3073
|
-
import * as
|
|
3074
|
-
|
|
3075
|
-
|
|
3076
|
-
|
|
3077
|
-
|
|
3078
|
-
}
|
|
3079
|
-
|
|
3080
|
-
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
const config = {
|
|
3084
|
-
name: toKebabCase(persona.name),
|
|
3085
|
-
skills: persona.skills,
|
|
3086
|
-
steps: persona.steps,
|
|
3087
|
-
timeout: persona.config.timeout,
|
|
3088
|
-
severity: persona.config.severity
|
|
3089
|
-
};
|
|
3090
|
-
return Ok(JSON.stringify(config, null, 2));
|
|
3091
|
-
} catch (error) {
|
|
3092
|
-
return Err(
|
|
3093
|
-
new Error(
|
|
3094
|
-
`Failed to generate runtime config: ${error instanceof Error ? error.message : String(error)}`
|
|
3095
|
-
)
|
|
3096
|
-
);
|
|
3097
|
-
}
|
|
3098
|
-
}
|
|
3099
|
-
|
|
3100
|
-
// src/persona/generators/agents-md.ts
|
|
3101
|
-
function formatTrigger(trigger) {
|
|
3102
|
-
switch (trigger.event) {
|
|
3103
|
-
case "on_pr": {
|
|
3104
|
-
const paths = trigger.conditions?.paths?.join(", ") ?? "all files";
|
|
3105
|
-
return `On PR (${paths})`;
|
|
1800
|
+
import * as fs4 from "fs";
|
|
1801
|
+
import * as path13 from "path";
|
|
1802
|
+
function createGenerateCommand2() {
|
|
1803
|
+
return new Command19("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) => {
|
|
1804
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
1805
|
+
const personasDir = resolvePersonasDir();
|
|
1806
|
+
const filePath = path13.join(personasDir, `${name}.yaml`);
|
|
1807
|
+
const personaResult = loadPersona(filePath);
|
|
1808
|
+
if (!personaResult.ok) {
|
|
1809
|
+
logger.error(personaResult.error.message);
|
|
1810
|
+
process.exit(ExitCode.ERROR);
|
|
3106
1811
|
}
|
|
3107
|
-
|
|
3108
|
-
|
|
3109
|
-
|
|
3110
|
-
|
|
3111
|
-
|
|
3112
|
-
|
|
3113
|
-
|
|
3114
|
-
|
|
3115
|
-
|
|
3116
|
-
}
|
|
3117
|
-
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
const skills = persona.skills.join(", ");
|
|
3121
|
-
const commands = persona.steps.filter((s) => "command" in s).map((s) => `\`harness ${s.command}\``).join(", ");
|
|
3122
|
-
const stepSkills = persona.steps.filter((s) => "skill" in s).map((s) => `\`harness skill run ${s.skill}\``).join(", ");
|
|
3123
|
-
const allCommands = [commands, stepSkills].filter(Boolean).join(", ");
|
|
3124
|
-
const fragment = `## ${persona.name} Agent
|
|
3125
|
-
|
|
3126
|
-
**Role:** ${persona.role}
|
|
3127
|
-
|
|
3128
|
-
**Triggers:** ${triggers}
|
|
3129
|
-
|
|
3130
|
-
**Skills:** ${skills}
|
|
3131
|
-
|
|
3132
|
-
**When this agent flags an issue:** Fix violations before merging. Run ${allCommands} locally to validate.
|
|
3133
|
-
`;
|
|
3134
|
-
return Ok(fragment);
|
|
3135
|
-
} catch (error) {
|
|
3136
|
-
return Err(
|
|
3137
|
-
new Error(
|
|
3138
|
-
`Failed to generate AGENTS.md fragment: ${error instanceof Error ? error.message : String(error)}`
|
|
3139
|
-
)
|
|
3140
|
-
);
|
|
3141
|
-
}
|
|
3142
|
-
}
|
|
3143
|
-
|
|
3144
|
-
// src/persona/generators/ci-workflow.ts
|
|
3145
|
-
import YAML2 from "yaml";
|
|
3146
|
-
function buildGitHubTriggers(triggers) {
|
|
3147
|
-
const on = {};
|
|
3148
|
-
for (const trigger of triggers) {
|
|
3149
|
-
switch (trigger.event) {
|
|
3150
|
-
case "on_pr": {
|
|
3151
|
-
const prConfig = {};
|
|
3152
|
-
if (trigger.conditions?.paths) prConfig.paths = trigger.conditions.paths;
|
|
3153
|
-
on.pull_request = prConfig;
|
|
3154
|
-
break;
|
|
3155
|
-
}
|
|
3156
|
-
case "on_commit": {
|
|
3157
|
-
const pushConfig = {};
|
|
3158
|
-
if (trigger.conditions?.branches) pushConfig.branches = trigger.conditions.branches;
|
|
3159
|
-
on.push = pushConfig;
|
|
3160
|
-
break;
|
|
3161
|
-
}
|
|
3162
|
-
case "scheduled":
|
|
3163
|
-
on.schedule = [{ cron: trigger.cron }];
|
|
3164
|
-
break;
|
|
3165
|
-
}
|
|
3166
|
-
}
|
|
3167
|
-
return on;
|
|
3168
|
-
}
|
|
3169
|
-
function generateCIWorkflow(persona, platform) {
|
|
3170
|
-
try {
|
|
3171
|
-
if (platform === "gitlab") return Err(new Error("GitLab CI generation is not yet supported"));
|
|
3172
|
-
const severity = persona.config.severity;
|
|
3173
|
-
const steps = [
|
|
3174
|
-
{ uses: "actions/checkout@v4" },
|
|
3175
|
-
{ uses: "actions/setup-node@v4", with: { "node-version": "20" } },
|
|
3176
|
-
{ uses: "pnpm/action-setup@v4", with: { run_install: "frozen" } }
|
|
3177
|
-
];
|
|
3178
|
-
const commandSteps = persona.steps.filter((s) => "command" in s);
|
|
3179
|
-
for (const step of commandSteps) {
|
|
3180
|
-
const severityFlag = severity ? ` --severity ${severity}` : "";
|
|
3181
|
-
steps.push({ run: `npx harness ${step.command}${severityFlag}` });
|
|
3182
|
-
}
|
|
3183
|
-
const workflow = {
|
|
3184
|
-
name: persona.name,
|
|
3185
|
-
on: buildGitHubTriggers(persona.triggers),
|
|
3186
|
-
jobs: {
|
|
3187
|
-
enforce: {
|
|
3188
|
-
"runs-on": "ubuntu-latest",
|
|
3189
|
-
steps
|
|
3190
|
-
}
|
|
3191
|
-
}
|
|
3192
|
-
};
|
|
3193
|
-
return Ok(YAML2.stringify(workflow, { lineWidth: 0 }));
|
|
3194
|
-
} catch (error) {
|
|
3195
|
-
return Err(
|
|
3196
|
-
new Error(
|
|
3197
|
-
`Failed to generate CI workflow: ${error instanceof Error ? error.message : String(error)}`
|
|
3198
|
-
)
|
|
3199
|
-
);
|
|
3200
|
-
}
|
|
3201
|
-
}
|
|
3202
|
-
|
|
3203
|
-
// src/commands/persona/generate.ts
|
|
3204
|
-
function createGenerateCommand2() {
|
|
3205
|
-
return new Command19("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) => {
|
|
3206
|
-
const globalOpts = cmd.optsWithGlobals();
|
|
3207
|
-
const personasDir = resolvePersonasDir();
|
|
3208
|
-
const filePath = path22.join(personasDir, `${name}.yaml`);
|
|
3209
|
-
const personaResult = loadPersona(filePath);
|
|
3210
|
-
if (!personaResult.ok) {
|
|
3211
|
-
logger.error(personaResult.error.message);
|
|
3212
|
-
process.exit(ExitCode.ERROR);
|
|
3213
|
-
}
|
|
3214
|
-
const persona = personaResult.value;
|
|
3215
|
-
const outputDir = path22.resolve(opts.outputDir);
|
|
3216
|
-
const slug = toKebabCase(persona.name);
|
|
3217
|
-
const only = opts.only;
|
|
3218
|
-
const generated = [];
|
|
3219
|
-
if (!only || only === "runtime") {
|
|
3220
|
-
const result = generateRuntime(persona);
|
|
3221
|
-
if (result.ok) {
|
|
3222
|
-
const outPath = path22.join(outputDir, `${slug}.runtime.json`);
|
|
3223
|
-
fs13.mkdirSync(path22.dirname(outPath), { recursive: true });
|
|
3224
|
-
fs13.writeFileSync(outPath, result.value);
|
|
3225
|
-
generated.push(outPath);
|
|
3226
|
-
}
|
|
1812
|
+
const persona = personaResult.value;
|
|
1813
|
+
const outputDir = path13.resolve(opts.outputDir);
|
|
1814
|
+
const slug = toKebabCase(persona.name);
|
|
1815
|
+
const only = opts.only;
|
|
1816
|
+
const generated = [];
|
|
1817
|
+
if (!only || only === "runtime") {
|
|
1818
|
+
const result = generateRuntime(persona);
|
|
1819
|
+
if (result.ok) {
|
|
1820
|
+
const outPath = path13.join(outputDir, `${slug}.runtime.json`);
|
|
1821
|
+
fs4.mkdirSync(path13.dirname(outPath), { recursive: true });
|
|
1822
|
+
fs4.writeFileSync(outPath, result.value);
|
|
1823
|
+
generated.push(outPath);
|
|
1824
|
+
}
|
|
3227
1825
|
}
|
|
3228
1826
|
if (!only || only === "agents-md") {
|
|
3229
1827
|
const result = generateAgentsMd(persona);
|
|
3230
1828
|
if (result.ok) {
|
|
3231
|
-
const outPath =
|
|
3232
|
-
|
|
1829
|
+
const outPath = path13.join(outputDir, `${slug}.agents.md`);
|
|
1830
|
+
fs4.writeFileSync(outPath, result.value);
|
|
3233
1831
|
generated.push(outPath);
|
|
3234
1832
|
}
|
|
3235
1833
|
}
|
|
3236
1834
|
if (!only || only === "ci") {
|
|
3237
1835
|
const result = generateCIWorkflow(persona, "github");
|
|
3238
1836
|
if (result.ok) {
|
|
3239
|
-
const outPath =
|
|
3240
|
-
|
|
3241
|
-
|
|
1837
|
+
const outPath = path13.join(outputDir, ".github", "workflows", `${slug}.yml`);
|
|
1838
|
+
fs4.mkdirSync(path13.dirname(outPath), { recursive: true });
|
|
1839
|
+
fs4.writeFileSync(outPath, result.value);
|
|
3242
1840
|
generated.push(outPath);
|
|
3243
1841
|
}
|
|
3244
1842
|
}
|
|
@@ -3263,26 +1861,26 @@ import { Command as Command25 } from "commander";
|
|
|
3263
1861
|
|
|
3264
1862
|
// src/commands/skill/list.ts
|
|
3265
1863
|
import { Command as Command21 } from "commander";
|
|
3266
|
-
import * as
|
|
3267
|
-
import * as
|
|
3268
|
-
import { parse
|
|
1864
|
+
import * as fs5 from "fs";
|
|
1865
|
+
import * as path14 from "path";
|
|
1866
|
+
import { parse } from "yaml";
|
|
3269
1867
|
function createListCommand2() {
|
|
3270
1868
|
return new Command21("list").description("List available skills").action(async (_opts, cmd) => {
|
|
3271
1869
|
const globalOpts = cmd.optsWithGlobals();
|
|
3272
1870
|
const skillsDir = resolveSkillsDir();
|
|
3273
|
-
if (!
|
|
1871
|
+
if (!fs5.existsSync(skillsDir)) {
|
|
3274
1872
|
logger.info("No skills directory found.");
|
|
3275
1873
|
process.exit(ExitCode.SUCCESS);
|
|
3276
1874
|
return;
|
|
3277
1875
|
}
|
|
3278
|
-
const entries =
|
|
1876
|
+
const entries = fs5.readdirSync(skillsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
|
|
3279
1877
|
const skills = [];
|
|
3280
1878
|
for (const name of entries) {
|
|
3281
|
-
const yamlPath =
|
|
3282
|
-
if (!
|
|
1879
|
+
const yamlPath = path14.join(skillsDir, name, "skill.yaml");
|
|
1880
|
+
if (!fs5.existsSync(yamlPath)) continue;
|
|
3283
1881
|
try {
|
|
3284
|
-
const raw =
|
|
3285
|
-
const parsed =
|
|
1882
|
+
const raw = fs5.readFileSync(yamlPath, "utf-8");
|
|
1883
|
+
const parsed = parse(raw);
|
|
3286
1884
|
const result = SkillMetadataSchema.safeParse(parsed);
|
|
3287
1885
|
if (result.success) {
|
|
3288
1886
|
skills.push(result.data);
|
|
@@ -3312,9 +1910,9 @@ function createListCommand2() {
|
|
|
3312
1910
|
|
|
3313
1911
|
// src/commands/skill/run.ts
|
|
3314
1912
|
import { Command as Command22 } from "commander";
|
|
3315
|
-
import * as
|
|
3316
|
-
import * as
|
|
3317
|
-
import { parse as
|
|
1913
|
+
import * as fs6 from "fs";
|
|
1914
|
+
import * as path15 from "path";
|
|
1915
|
+
import { parse as parse2 } from "yaml";
|
|
3318
1916
|
|
|
3319
1917
|
// src/skill/complexity.ts
|
|
3320
1918
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
@@ -3403,18 +2001,18 @@ ${options.priorState}`);
|
|
|
3403
2001
|
function createRunCommand2() {
|
|
3404
2002
|
return new Command22("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>", "Complexity: auto, light, full", "auto").option("--phase <name>", "Start at a specific phase (for re-entry)").option("--party", "Enable multi-perspective evaluation").action(async (name, opts, _cmd) => {
|
|
3405
2003
|
const skillsDir = resolveSkillsDir();
|
|
3406
|
-
const skillDir =
|
|
3407
|
-
if (!
|
|
2004
|
+
const skillDir = path15.join(skillsDir, name);
|
|
2005
|
+
if (!fs6.existsSync(skillDir)) {
|
|
3408
2006
|
logger.error(`Skill not found: ${name}`);
|
|
3409
2007
|
process.exit(ExitCode.ERROR);
|
|
3410
2008
|
return;
|
|
3411
2009
|
}
|
|
3412
|
-
const yamlPath =
|
|
2010
|
+
const yamlPath = path15.join(skillDir, "skill.yaml");
|
|
3413
2011
|
let metadata = null;
|
|
3414
|
-
if (
|
|
2012
|
+
if (fs6.existsSync(yamlPath)) {
|
|
3415
2013
|
try {
|
|
3416
|
-
const raw =
|
|
3417
|
-
const parsed =
|
|
2014
|
+
const raw = fs6.readFileSync(yamlPath, "utf-8");
|
|
2015
|
+
const parsed = parse2(raw);
|
|
3418
2016
|
const result = SkillMetadataSchema.safeParse(parsed);
|
|
3419
2017
|
if (result.success) metadata = result.data;
|
|
3420
2018
|
} catch {
|
|
@@ -3424,17 +2022,17 @@ function createRunCommand2() {
|
|
|
3424
2022
|
if (metadata?.phases && metadata.phases.length > 0) {
|
|
3425
2023
|
const requested = opts.complexity ?? "auto";
|
|
3426
2024
|
if (requested === "auto") {
|
|
3427
|
-
const projectPath2 = opts.path ?
|
|
2025
|
+
const projectPath2 = opts.path ? path15.resolve(opts.path) : process.cwd();
|
|
3428
2026
|
complexity = detectComplexity(projectPath2);
|
|
3429
2027
|
} else {
|
|
3430
2028
|
complexity = requested;
|
|
3431
2029
|
}
|
|
3432
2030
|
}
|
|
3433
2031
|
let principles;
|
|
3434
|
-
const projectPath = opts.path ?
|
|
3435
|
-
const principlesPath =
|
|
3436
|
-
if (
|
|
3437
|
-
principles =
|
|
2032
|
+
const projectPath = opts.path ? path15.resolve(opts.path) : process.cwd();
|
|
2033
|
+
const principlesPath = path15.join(projectPath, "docs", "principles.md");
|
|
2034
|
+
if (fs6.existsSync(principlesPath)) {
|
|
2035
|
+
principles = fs6.readFileSync(principlesPath, "utf-8");
|
|
3438
2036
|
}
|
|
3439
2037
|
let priorState;
|
|
3440
2038
|
let stateWarning;
|
|
@@ -3449,16 +2047,16 @@ function createRunCommand2() {
|
|
|
3449
2047
|
}
|
|
3450
2048
|
if (metadata?.state.persistent && metadata.state.files.length > 0) {
|
|
3451
2049
|
for (const stateFilePath of metadata.state.files) {
|
|
3452
|
-
const fullPath =
|
|
3453
|
-
if (
|
|
3454
|
-
const stat =
|
|
2050
|
+
const fullPath = path15.join(projectPath, stateFilePath);
|
|
2051
|
+
if (fs6.existsSync(fullPath)) {
|
|
2052
|
+
const stat = fs6.statSync(fullPath);
|
|
3455
2053
|
if (stat.isDirectory()) {
|
|
3456
|
-
const files =
|
|
2054
|
+
const files = fs6.readdirSync(fullPath).map((f) => ({ name: f, mtime: fs6.statSync(path15.join(fullPath, f)).mtimeMs })).sort((a, b) => b.mtime - a.mtime);
|
|
3457
2055
|
if (files.length > 0) {
|
|
3458
|
-
priorState =
|
|
2056
|
+
priorState = fs6.readFileSync(path15.join(fullPath, files[0].name), "utf-8");
|
|
3459
2057
|
}
|
|
3460
2058
|
} else {
|
|
3461
|
-
priorState =
|
|
2059
|
+
priorState = fs6.readFileSync(fullPath, "utf-8");
|
|
3462
2060
|
}
|
|
3463
2061
|
break;
|
|
3464
2062
|
}
|
|
@@ -3477,17 +2075,17 @@ function createRunCommand2() {
|
|
|
3477
2075
|
...stateWarning !== void 0 && { stateWarning },
|
|
3478
2076
|
party: opts.party
|
|
3479
2077
|
});
|
|
3480
|
-
const skillMdPath =
|
|
3481
|
-
if (!
|
|
2078
|
+
const skillMdPath = path15.join(skillDir, "SKILL.md");
|
|
2079
|
+
if (!fs6.existsSync(skillMdPath)) {
|
|
3482
2080
|
logger.error(`SKILL.md not found for skill: ${name}`);
|
|
3483
2081
|
process.exit(ExitCode.ERROR);
|
|
3484
2082
|
return;
|
|
3485
2083
|
}
|
|
3486
|
-
let content =
|
|
2084
|
+
let content = fs6.readFileSync(skillMdPath, "utf-8");
|
|
3487
2085
|
if (metadata?.state.persistent && opts.path) {
|
|
3488
|
-
const stateFile =
|
|
3489
|
-
if (
|
|
3490
|
-
const stateContent =
|
|
2086
|
+
const stateFile = path15.join(projectPath, ".harness", "state.json");
|
|
2087
|
+
if (fs6.existsSync(stateFile)) {
|
|
2088
|
+
const stateContent = fs6.readFileSync(stateFile, "utf-8");
|
|
3491
2089
|
content += `
|
|
3492
2090
|
|
|
3493
2091
|
---
|
|
@@ -3505,9 +2103,9 @@ ${stateContent}
|
|
|
3505
2103
|
|
|
3506
2104
|
// src/commands/skill/validate.ts
|
|
3507
2105
|
import { Command as Command23 } from "commander";
|
|
3508
|
-
import * as
|
|
3509
|
-
import * as
|
|
3510
|
-
import { parse as
|
|
2106
|
+
import * as fs7 from "fs";
|
|
2107
|
+
import * as path16 from "path";
|
|
2108
|
+
import { parse as parse3 } from "yaml";
|
|
3511
2109
|
var REQUIRED_SECTIONS = [
|
|
3512
2110
|
"## When to Use",
|
|
3513
2111
|
"## Process",
|
|
@@ -3519,32 +2117,32 @@ function createValidateCommand3() {
|
|
|
3519
2117
|
return new Command23("validate").description("Validate all skill.yaml files and SKILL.md structure").action(async (_opts, cmd) => {
|
|
3520
2118
|
const globalOpts = cmd.optsWithGlobals();
|
|
3521
2119
|
const skillsDir = resolveSkillsDir();
|
|
3522
|
-
if (!
|
|
2120
|
+
if (!fs7.existsSync(skillsDir)) {
|
|
3523
2121
|
logger.info("No skills directory found.");
|
|
3524
2122
|
process.exit(ExitCode.SUCCESS);
|
|
3525
2123
|
return;
|
|
3526
2124
|
}
|
|
3527
|
-
const entries =
|
|
2125
|
+
const entries = fs7.readdirSync(skillsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
|
|
3528
2126
|
const errors = [];
|
|
3529
2127
|
let validated = 0;
|
|
3530
2128
|
for (const name of entries) {
|
|
3531
|
-
const skillDir =
|
|
3532
|
-
const yamlPath =
|
|
3533
|
-
const skillMdPath =
|
|
3534
|
-
if (!
|
|
2129
|
+
const skillDir = path16.join(skillsDir, name);
|
|
2130
|
+
const yamlPath = path16.join(skillDir, "skill.yaml");
|
|
2131
|
+
const skillMdPath = path16.join(skillDir, "SKILL.md");
|
|
2132
|
+
if (!fs7.existsSync(yamlPath)) {
|
|
3535
2133
|
errors.push(`${name}: missing skill.yaml`);
|
|
3536
2134
|
continue;
|
|
3537
2135
|
}
|
|
3538
2136
|
try {
|
|
3539
|
-
const raw =
|
|
3540
|
-
const parsed =
|
|
2137
|
+
const raw = fs7.readFileSync(yamlPath, "utf-8");
|
|
2138
|
+
const parsed = parse3(raw);
|
|
3541
2139
|
const result = SkillMetadataSchema.safeParse(parsed);
|
|
3542
2140
|
if (!result.success) {
|
|
3543
2141
|
errors.push(`${name}/skill.yaml: ${result.error.message}`);
|
|
3544
2142
|
continue;
|
|
3545
2143
|
}
|
|
3546
|
-
if (
|
|
3547
|
-
const mdContent =
|
|
2144
|
+
if (fs7.existsSync(skillMdPath)) {
|
|
2145
|
+
const mdContent = fs7.readFileSync(skillMdPath, "utf-8");
|
|
3548
2146
|
for (const section of REQUIRED_SECTIONS) {
|
|
3549
2147
|
if (!mdContent.includes(section)) {
|
|
3550
2148
|
errors.push(`${name}/SKILL.md: missing section "${section}"`);
|
|
@@ -3586,28 +2184,28 @@ function createValidateCommand3() {
|
|
|
3586
2184
|
|
|
3587
2185
|
// src/commands/skill/info.ts
|
|
3588
2186
|
import { Command as Command24 } from "commander";
|
|
3589
|
-
import * as
|
|
3590
|
-
import * as
|
|
3591
|
-
import { parse as
|
|
2187
|
+
import * as fs8 from "fs";
|
|
2188
|
+
import * as path17 from "path";
|
|
2189
|
+
import { parse as parse4 } from "yaml";
|
|
3592
2190
|
function createInfoCommand() {
|
|
3593
2191
|
return new Command24("info").description("Show metadata for a skill").argument("<name>", "Skill name (e.g., harness-tdd)").action(async (name, _opts, cmd) => {
|
|
3594
2192
|
const globalOpts = cmd.optsWithGlobals();
|
|
3595
2193
|
const skillsDir = resolveSkillsDir();
|
|
3596
|
-
const skillDir =
|
|
3597
|
-
if (!
|
|
2194
|
+
const skillDir = path17.join(skillsDir, name);
|
|
2195
|
+
if (!fs8.existsSync(skillDir)) {
|
|
3598
2196
|
logger.error(`Skill not found: ${name}`);
|
|
3599
2197
|
process.exit(ExitCode.ERROR);
|
|
3600
2198
|
return;
|
|
3601
2199
|
}
|
|
3602
|
-
const yamlPath =
|
|
3603
|
-
if (!
|
|
2200
|
+
const yamlPath = path17.join(skillDir, "skill.yaml");
|
|
2201
|
+
if (!fs8.existsSync(yamlPath)) {
|
|
3604
2202
|
logger.error(`skill.yaml not found for skill: ${name}`);
|
|
3605
2203
|
process.exit(ExitCode.ERROR);
|
|
3606
2204
|
return;
|
|
3607
2205
|
}
|
|
3608
2206
|
try {
|
|
3609
|
-
const raw =
|
|
3610
|
-
const parsed =
|
|
2207
|
+
const raw = fs8.readFileSync(yamlPath, "utf-8");
|
|
2208
|
+
const parsed = parse4(raw);
|
|
3611
2209
|
const result = SkillMetadataSchema.safeParse(parsed);
|
|
3612
2210
|
if (!result.success) {
|
|
3613
2211
|
logger.error(`Invalid skill.yaml: ${result.error.message}`);
|
|
@@ -3660,11 +2258,11 @@ import { Command as Command30 } from "commander";
|
|
|
3660
2258
|
|
|
3661
2259
|
// src/commands/state/show.ts
|
|
3662
2260
|
import { Command as Command26 } from "commander";
|
|
3663
|
-
import * as
|
|
2261
|
+
import * as path18 from "path";
|
|
3664
2262
|
function createShowCommand() {
|
|
3665
2263
|
return new Command26("show").description("Show current project state").option("--path <path>", "Project root path", ".").option("--stream <name>", "Target a specific stream").action(async (opts, cmd) => {
|
|
3666
2264
|
const globalOpts = cmd.optsWithGlobals();
|
|
3667
|
-
const projectPath =
|
|
2265
|
+
const projectPath = path18.resolve(opts.path);
|
|
3668
2266
|
const result = await loadState(projectPath, opts.stream);
|
|
3669
2267
|
if (!result.ok) {
|
|
3670
2268
|
logger.error(result.error.message);
|
|
@@ -3705,12 +2303,12 @@ Decisions: ${state.decisions.length}`);
|
|
|
3705
2303
|
|
|
3706
2304
|
// src/commands/state/reset.ts
|
|
3707
2305
|
import { Command as Command27 } from "commander";
|
|
3708
|
-
import * as
|
|
3709
|
-
import * as
|
|
2306
|
+
import * as fs9 from "fs";
|
|
2307
|
+
import * as path19 from "path";
|
|
3710
2308
|
import * as readline from "readline";
|
|
3711
2309
|
function createResetCommand() {
|
|
3712
2310
|
return new Command27("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, _cmd) => {
|
|
3713
|
-
const projectPath =
|
|
2311
|
+
const projectPath = path19.resolve(opts.path);
|
|
3714
2312
|
let statePath;
|
|
3715
2313
|
if (opts.stream) {
|
|
3716
2314
|
const streamResult = await resolveStreamPath(projectPath, { stream: opts.stream });
|
|
@@ -3719,708 +2317,137 @@ function createResetCommand() {
|
|
|
3719
2317
|
process.exit(ExitCode.ERROR);
|
|
3720
2318
|
return;
|
|
3721
2319
|
}
|
|
3722
|
-
statePath =
|
|
2320
|
+
statePath = path19.join(streamResult.value, "state.json");
|
|
3723
2321
|
} else {
|
|
3724
|
-
statePath =
|
|
3725
|
-
}
|
|
3726
|
-
if (!fs18.existsSync(statePath)) {
|
|
3727
|
-
logger.info("No state file found. Nothing to reset.");
|
|
3728
|
-
process.exit(ExitCode.SUCCESS);
|
|
3729
|
-
return;
|
|
3730
|
-
}
|
|
3731
|
-
if (!opts.yes) {
|
|
3732
|
-
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
3733
|
-
const answer = await new Promise((resolve24) => {
|
|
3734
|
-
rl.question("Reset project state? This cannot be undone. [y/N] ", resolve24);
|
|
3735
|
-
});
|
|
3736
|
-
rl.close();
|
|
3737
|
-
if (answer.toLowerCase() !== "y" && answer.toLowerCase() !== "yes") {
|
|
3738
|
-
logger.info("Reset cancelled.");
|
|
3739
|
-
process.exit(ExitCode.SUCCESS);
|
|
3740
|
-
return;
|
|
3741
|
-
}
|
|
3742
|
-
}
|
|
3743
|
-
try {
|
|
3744
|
-
fs18.unlinkSync(statePath);
|
|
3745
|
-
logger.success("Project state reset.");
|
|
3746
|
-
} catch (e) {
|
|
3747
|
-
logger.error(`Failed to reset state: ${e instanceof Error ? e.message : String(e)}`);
|
|
3748
|
-
process.exit(ExitCode.ERROR);
|
|
3749
|
-
return;
|
|
3750
|
-
}
|
|
3751
|
-
process.exit(ExitCode.SUCCESS);
|
|
3752
|
-
});
|
|
3753
|
-
}
|
|
3754
|
-
|
|
3755
|
-
// src/commands/state/learn.ts
|
|
3756
|
-
import { Command as Command28 } from "commander";
|
|
3757
|
-
import * as path29 from "path";
|
|
3758
|
-
function createLearnCommand() {
|
|
3759
|
-
return new Command28("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) => {
|
|
3760
|
-
const projectPath = path29.resolve(opts.path);
|
|
3761
|
-
const result = await appendLearning(projectPath, message, void 0, void 0, opts.stream);
|
|
3762
|
-
if (!result.ok) {
|
|
3763
|
-
logger.error(result.error.message);
|
|
3764
|
-
process.exit(ExitCode.ERROR);
|
|
3765
|
-
return;
|
|
3766
|
-
}
|
|
3767
|
-
logger.success(`Learning recorded.`);
|
|
3768
|
-
process.exit(ExitCode.SUCCESS);
|
|
3769
|
-
});
|
|
3770
|
-
}
|
|
3771
|
-
|
|
3772
|
-
// src/commands/state/streams.ts
|
|
3773
|
-
import { Command as Command29 } from "commander";
|
|
3774
|
-
import * as path30 from "path";
|
|
3775
|
-
function createStreamsCommand() {
|
|
3776
|
-
const command = new Command29("streams").description("Manage state streams");
|
|
3777
|
-
command.command("list").description("List all known streams").option("--path <path>", "Project root path", ".").action(async (opts, cmd) => {
|
|
3778
|
-
const globalOpts = cmd.optsWithGlobals();
|
|
3779
|
-
const projectPath = path30.resolve(opts.path);
|
|
3780
|
-
const indexResult = await loadStreamIndex(projectPath);
|
|
3781
|
-
const result = await listStreams(projectPath);
|
|
3782
|
-
if (!result.ok) {
|
|
3783
|
-
logger.error(result.error.message);
|
|
3784
|
-
process.exit(ExitCode.ERROR);
|
|
3785
|
-
return;
|
|
3786
|
-
}
|
|
3787
|
-
const active = indexResult.ok ? indexResult.value.activeStream : null;
|
|
3788
|
-
if (globalOpts.json) {
|
|
3789
|
-
logger.raw({ activeStream: active, streams: result.value });
|
|
3790
|
-
} else {
|
|
3791
|
-
if (result.value.length === 0) {
|
|
3792
|
-
console.log("No streams found.");
|
|
3793
|
-
}
|
|
3794
|
-
for (const s of result.value) {
|
|
3795
|
-
const marker = s.name === active ? " (active)" : "";
|
|
3796
|
-
const branch = s.branch ? ` [${s.branch}]` : "";
|
|
3797
|
-
console.log(` ${s.name}${marker}${branch} \u2014 last active: ${s.lastActiveAt}`);
|
|
3798
|
-
}
|
|
3799
|
-
}
|
|
3800
|
-
process.exit(ExitCode.SUCCESS);
|
|
3801
|
-
});
|
|
3802
|
-
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) => {
|
|
3803
|
-
const projectPath = path30.resolve(opts.path);
|
|
3804
|
-
const result = await createStream(projectPath, name, opts.branch);
|
|
3805
|
-
if (!result.ok) {
|
|
3806
|
-
logger.error(result.error.message);
|
|
3807
|
-
process.exit(ExitCode.ERROR);
|
|
3808
|
-
return;
|
|
3809
|
-
}
|
|
3810
|
-
logger.success(`Stream '${name}' created.`);
|
|
3811
|
-
process.exit(ExitCode.SUCCESS);
|
|
3812
|
-
});
|
|
3813
|
-
command.command("archive <name>").description("Archive a stream").option("--path <path>", "Project root path", ".").action(async (name, opts) => {
|
|
3814
|
-
const projectPath = path30.resolve(opts.path);
|
|
3815
|
-
const result = await archiveStream(projectPath, name);
|
|
3816
|
-
if (!result.ok) {
|
|
3817
|
-
logger.error(result.error.message);
|
|
3818
|
-
process.exit(ExitCode.ERROR);
|
|
3819
|
-
return;
|
|
3820
|
-
}
|
|
3821
|
-
logger.success(`Stream '${name}' archived.`);
|
|
3822
|
-
process.exit(ExitCode.SUCCESS);
|
|
3823
|
-
});
|
|
3824
|
-
command.command("activate <name>").description("Set the active stream").option("--path <path>", "Project root path", ".").action(async (name, opts) => {
|
|
3825
|
-
const projectPath = path30.resolve(opts.path);
|
|
3826
|
-
const result = await setActiveStream(projectPath, name);
|
|
3827
|
-
if (!result.ok) {
|
|
3828
|
-
logger.error(result.error.message);
|
|
3829
|
-
process.exit(ExitCode.ERROR);
|
|
3830
|
-
return;
|
|
3831
|
-
}
|
|
3832
|
-
logger.success(`Active stream set to '${name}'.`);
|
|
3833
|
-
process.exit(ExitCode.SUCCESS);
|
|
3834
|
-
});
|
|
3835
|
-
return command;
|
|
3836
|
-
}
|
|
3837
|
-
|
|
3838
|
-
// src/commands/state/index.ts
|
|
3839
|
-
function createStateCommand() {
|
|
3840
|
-
const command = new Command30("state").description("Project state management commands");
|
|
3841
|
-
command.addCommand(createShowCommand());
|
|
3842
|
-
command.addCommand(createResetCommand());
|
|
3843
|
-
command.addCommand(createLearnCommand());
|
|
3844
|
-
command.addCommand(createStreamsCommand());
|
|
3845
|
-
return command;
|
|
3846
|
-
}
|
|
3847
|
-
|
|
3848
|
-
// src/commands/check-phase-gate.ts
|
|
3849
|
-
import { Command as Command31 } from "commander";
|
|
3850
|
-
import * as path31 from "path";
|
|
3851
|
-
import * as fs19 from "fs";
|
|
3852
|
-
function resolveSpecPath(implFile, implPattern, specPattern, cwd) {
|
|
3853
|
-
const relImpl = path31.relative(cwd, implFile).replace(/\\/g, "/");
|
|
3854
|
-
const implBase = (implPattern.split("*")[0] ?? "").replace(/\/+$/, "");
|
|
3855
|
-
const afterBase = relImpl.startsWith(implBase + "/") ? relImpl.slice(implBase.length + 1) : relImpl;
|
|
3856
|
-
const segments = afterBase.split("/");
|
|
3857
|
-
const firstSegment = segments[0] ?? "";
|
|
3858
|
-
const feature = segments.length > 1 ? firstSegment : path31.basename(firstSegment, path31.extname(firstSegment));
|
|
3859
|
-
const specRelative = specPattern.replace("{feature}", feature);
|
|
3860
|
-
return path31.resolve(cwd, specRelative);
|
|
3861
|
-
}
|
|
3862
|
-
async function runCheckPhaseGate(options) {
|
|
3863
|
-
const configResult = resolveConfig(options.configPath);
|
|
3864
|
-
if (!configResult.ok) {
|
|
3865
|
-
return configResult;
|
|
3866
|
-
}
|
|
3867
|
-
const config = configResult.value;
|
|
3868
|
-
const cwd = options.cwd ?? (options.configPath ? path31.dirname(path31.resolve(options.configPath)) : process.cwd());
|
|
3869
|
-
if (!config.phaseGates?.enabled) {
|
|
3870
|
-
return Ok({
|
|
3871
|
-
pass: true,
|
|
3872
|
-
skipped: true,
|
|
3873
|
-
missingSpecs: [],
|
|
3874
|
-
checkedFiles: 0
|
|
3875
|
-
});
|
|
3876
|
-
}
|
|
3877
|
-
const phaseGates = config.phaseGates;
|
|
3878
|
-
const missingSpecs = [];
|
|
3879
|
-
let checkedFiles = 0;
|
|
3880
|
-
for (const mapping of phaseGates.mappings) {
|
|
3881
|
-
const implFiles = await findFiles(mapping.implPattern, cwd);
|
|
3882
|
-
for (const implFile of implFiles) {
|
|
3883
|
-
checkedFiles++;
|
|
3884
|
-
const expectedSpec = resolveSpecPath(implFile, mapping.implPattern, mapping.specPattern, cwd);
|
|
3885
|
-
if (!fs19.existsSync(expectedSpec)) {
|
|
3886
|
-
missingSpecs.push({
|
|
3887
|
-
implFile: path31.relative(cwd, implFile).replace(/\\/g, "/"),
|
|
3888
|
-
expectedSpec: path31.relative(cwd, expectedSpec).replace(/\\/g, "/")
|
|
3889
|
-
});
|
|
3890
|
-
}
|
|
3891
|
-
}
|
|
3892
|
-
}
|
|
3893
|
-
const pass = missingSpecs.length === 0;
|
|
3894
|
-
return Ok({
|
|
3895
|
-
pass,
|
|
3896
|
-
skipped: false,
|
|
3897
|
-
severity: phaseGates.severity,
|
|
3898
|
-
missingSpecs,
|
|
3899
|
-
checkedFiles
|
|
3900
|
-
});
|
|
3901
|
-
}
|
|
3902
|
-
function createCheckPhaseGateCommand() {
|
|
3903
|
-
const command = new Command31("check-phase-gate").description("Verify that implementation files have matching spec documents").action(async (_opts, cmd) => {
|
|
3904
|
-
const globalOpts = cmd.optsWithGlobals();
|
|
3905
|
-
const mode = globalOpts.json ? OutputMode.JSON : globalOpts.quiet ? OutputMode.QUIET : globalOpts.verbose ? OutputMode.VERBOSE : OutputMode.TEXT;
|
|
3906
|
-
const formatter = new OutputFormatter(mode);
|
|
3907
|
-
const result = await runCheckPhaseGate({
|
|
3908
|
-
configPath: globalOpts.config,
|
|
3909
|
-
json: globalOpts.json,
|
|
3910
|
-
verbose: globalOpts.verbose,
|
|
3911
|
-
quiet: globalOpts.quiet
|
|
3912
|
-
});
|
|
3913
|
-
if (!result.ok) {
|
|
3914
|
-
if (mode === OutputMode.JSON) {
|
|
3915
|
-
console.log(JSON.stringify({ error: result.error.message }));
|
|
3916
|
-
} else {
|
|
3917
|
-
logger.error(result.error.message);
|
|
3918
|
-
}
|
|
3919
|
-
process.exit(result.error.exitCode);
|
|
3920
|
-
}
|
|
3921
|
-
const value = result.value;
|
|
3922
|
-
if (value.skipped) {
|
|
3923
|
-
if (mode === OutputMode.JSON) {
|
|
3924
|
-
console.log(formatter.format(value));
|
|
3925
|
-
} else if (mode !== OutputMode.QUIET) {
|
|
3926
|
-
logger.dim("Phase gates not enabled, skipping.");
|
|
3927
|
-
}
|
|
3928
|
-
process.exit(ExitCode.SUCCESS);
|
|
3929
|
-
}
|
|
3930
|
-
const output = formatter.formatValidation({
|
|
3931
|
-
valid: value.pass,
|
|
3932
|
-
issues: value.missingSpecs.map((m) => ({
|
|
3933
|
-
file: m.implFile,
|
|
3934
|
-
message: `Missing spec: ${m.expectedSpec}`
|
|
3935
|
-
}))
|
|
3936
|
-
});
|
|
3937
|
-
if (output) {
|
|
3938
|
-
console.log(output);
|
|
3939
|
-
}
|
|
3940
|
-
const summary = formatter.formatSummary(
|
|
3941
|
-
"Phase gate check",
|
|
3942
|
-
`${value.checkedFiles} files checked, ${value.missingSpecs.length} missing specs`,
|
|
3943
|
-
value.pass
|
|
3944
|
-
);
|
|
3945
|
-
if (summary) {
|
|
3946
|
-
console.log(summary);
|
|
3947
|
-
}
|
|
3948
|
-
if (!value.pass && value.severity === "error") {
|
|
3949
|
-
process.exit(ExitCode.VALIDATION_FAILED);
|
|
3950
|
-
}
|
|
3951
|
-
process.exit(ExitCode.SUCCESS);
|
|
3952
|
-
});
|
|
3953
|
-
return command;
|
|
3954
|
-
}
|
|
3955
|
-
|
|
3956
|
-
// src/commands/generate-slash-commands.ts
|
|
3957
|
-
import { Command as Command32 } from "commander";
|
|
3958
|
-
import fs22 from "fs";
|
|
3959
|
-
import path34 from "path";
|
|
3960
|
-
import os2 from "os";
|
|
3961
|
-
import readline2 from "readline";
|
|
3962
|
-
|
|
3963
|
-
// src/slash-commands/normalize.ts
|
|
3964
|
-
import fs20 from "fs";
|
|
3965
|
-
import path32 from "path";
|
|
3966
|
-
import { parse as parse8 } from "yaml";
|
|
3967
|
-
|
|
3968
|
-
// src/slash-commands/normalize-name.ts
|
|
3969
|
-
function normalizeName(skillName) {
|
|
3970
|
-
let name = skillName;
|
|
3971
|
-
if (name.startsWith("harness-")) {
|
|
3972
|
-
name = name.slice("harness-".length);
|
|
3973
|
-
}
|
|
3974
|
-
name = name.replace(/-harness-/g, "-");
|
|
3975
|
-
if (name.endsWith("-harness")) {
|
|
3976
|
-
name = name.slice(0, -"-harness".length);
|
|
3977
|
-
}
|
|
3978
|
-
return name;
|
|
3979
|
-
}
|
|
3980
|
-
|
|
3981
|
-
// src/slash-commands/normalize.ts
|
|
3982
|
-
function normalizeSkills(skillSources, platforms) {
|
|
3983
|
-
const specs = [];
|
|
3984
|
-
const nameMap = /* @__PURE__ */ new Map();
|
|
3985
|
-
for (const { dir: skillsDir, source } of skillSources) {
|
|
3986
|
-
if (!fs20.existsSync(skillsDir)) continue;
|
|
3987
|
-
const entries = fs20.readdirSync(skillsDir, { withFileTypes: true }).filter((d) => d.isDirectory());
|
|
3988
|
-
for (const entry of entries) {
|
|
3989
|
-
const yamlPath = path32.join(skillsDir, entry.name, "skill.yaml");
|
|
3990
|
-
if (!fs20.existsSync(yamlPath)) continue;
|
|
3991
|
-
let raw;
|
|
3992
|
-
try {
|
|
3993
|
-
raw = fs20.readFileSync(yamlPath, "utf-8");
|
|
3994
|
-
} catch {
|
|
3995
|
-
continue;
|
|
3996
|
-
}
|
|
3997
|
-
const parsed = parse8(raw);
|
|
3998
|
-
const result = SkillMetadataSchema.safeParse(parsed);
|
|
3999
|
-
if (!result.success) {
|
|
4000
|
-
console.warn(`Skipping ${entry.name}: invalid skill.yaml`);
|
|
4001
|
-
continue;
|
|
4002
|
-
}
|
|
4003
|
-
const meta = result.data;
|
|
4004
|
-
const matchesPlatform = platforms.some((p) => meta.platforms.includes(p));
|
|
4005
|
-
if (!matchesPlatform) continue;
|
|
4006
|
-
const normalized = normalizeName(meta.name);
|
|
4007
|
-
const existing = nameMap.get(normalized);
|
|
4008
|
-
if (existing) {
|
|
4009
|
-
if (existing.source === source) {
|
|
4010
|
-
throw new Error(
|
|
4011
|
-
`Name collision: skills "${existing.skillName}" and "${meta.name}" both normalize to "${normalized}"`
|
|
4012
|
-
);
|
|
4013
|
-
}
|
|
4014
|
-
continue;
|
|
4015
|
-
}
|
|
4016
|
-
nameMap.set(normalized, { skillName: meta.name, source });
|
|
4017
|
-
const skillMdPath = path32.join(skillsDir, entry.name, "SKILL.md");
|
|
4018
|
-
const skillMdContent = fs20.existsSync(skillMdPath) ? fs20.readFileSync(skillMdPath, "utf-8") : "";
|
|
4019
|
-
const skillMdRelative = path32.relative(
|
|
4020
|
-
process.cwd(),
|
|
4021
|
-
path32.join(skillsDir, entry.name, "SKILL.md")
|
|
4022
|
-
);
|
|
4023
|
-
const skillYamlRelative = path32.relative(
|
|
4024
|
-
process.cwd(),
|
|
4025
|
-
path32.join(skillsDir, entry.name, "skill.yaml")
|
|
4026
|
-
);
|
|
4027
|
-
const args = (meta.cli?.args ?? []).map((a) => ({
|
|
4028
|
-
name: a.name,
|
|
4029
|
-
description: a.description ?? "",
|
|
4030
|
-
required: a.required ?? false
|
|
4031
|
-
}));
|
|
4032
|
-
const tools = [...meta.tools ?? []];
|
|
4033
|
-
if (!tools.includes("Read")) {
|
|
4034
|
-
tools.push("Read");
|
|
4035
|
-
}
|
|
4036
|
-
const contextLines = [];
|
|
4037
|
-
if (meta.cognitive_mode) {
|
|
4038
|
-
contextLines.push(`Cognitive mode: ${meta.cognitive_mode}`);
|
|
4039
|
-
}
|
|
4040
|
-
if (meta.type) {
|
|
4041
|
-
contextLines.push(`Type: ${meta.type}`);
|
|
4042
|
-
}
|
|
4043
|
-
if (meta.state?.persistent) {
|
|
4044
|
-
const files = meta.state.files?.join(", ") ?? "";
|
|
4045
|
-
contextLines.push(`State: persistent${files ? ` (files: ${files})` : ""}`);
|
|
4046
|
-
}
|
|
4047
|
-
const objectiveLines = [meta.description];
|
|
4048
|
-
if (meta.phases && meta.phases.length > 0) {
|
|
4049
|
-
objectiveLines.push("");
|
|
4050
|
-
objectiveLines.push("Phases:");
|
|
4051
|
-
for (const phase of meta.phases) {
|
|
4052
|
-
const req = phase.required !== false ? "" : " (optional)";
|
|
4053
|
-
objectiveLines.push(`- ${phase.name}: ${phase.description}${req}`);
|
|
4054
|
-
}
|
|
4055
|
-
}
|
|
4056
|
-
const executionContextLines = [];
|
|
4057
|
-
if (skillMdContent) {
|
|
4058
|
-
executionContextLines.push(`@${skillMdRelative}`);
|
|
4059
|
-
executionContextLines.push(`@${skillYamlRelative}`);
|
|
4060
|
-
}
|
|
4061
|
-
const processLines = [];
|
|
4062
|
-
if (meta.mcp?.tool) {
|
|
4063
|
-
processLines.push(
|
|
4064
|
-
`1. Try: invoke mcp__harness__${meta.mcp.tool} with skill: "${meta.name}"`
|
|
4065
|
-
);
|
|
4066
|
-
processLines.push(`2. If MCP unavailable: read SKILL.md and follow its workflow directly`);
|
|
4067
|
-
processLines.push(`3. Pass through any arguments provided by the user`);
|
|
4068
|
-
} else {
|
|
4069
|
-
processLines.push(`1. Read SKILL.md and follow its workflow directly`);
|
|
4070
|
-
processLines.push(`2. Pass through any arguments provided by the user`);
|
|
4071
|
-
}
|
|
4072
|
-
specs.push({
|
|
4073
|
-
name: normalized,
|
|
4074
|
-
namespace: "harness",
|
|
4075
|
-
fullName: `harness:${normalized}`,
|
|
4076
|
-
description: meta.description,
|
|
4077
|
-
version: meta.version,
|
|
4078
|
-
...meta.cognitive_mode ? { cognitiveMode: meta.cognitive_mode } : {},
|
|
4079
|
-
tools,
|
|
4080
|
-
args,
|
|
4081
|
-
skillYamlName: meta.name,
|
|
4082
|
-
sourceDir: entry.name,
|
|
4083
|
-
skillsBaseDir: skillsDir,
|
|
4084
|
-
source,
|
|
4085
|
-
prompt: {
|
|
4086
|
-
context: contextLines.join("\n"),
|
|
4087
|
-
objective: objectiveLines.join("\n"),
|
|
4088
|
-
executionContext: executionContextLines.join("\n"),
|
|
4089
|
-
process: processLines.join("\n")
|
|
4090
|
-
}
|
|
4091
|
-
});
|
|
4092
|
-
}
|
|
4093
|
-
}
|
|
4094
|
-
return specs;
|
|
4095
|
-
}
|
|
4096
|
-
|
|
4097
|
-
// src/slash-commands/types.ts
|
|
4098
|
-
var VALID_PLATFORMS = ["claude-code", "gemini-cli"];
|
|
4099
|
-
var GENERATED_HEADER_CLAUDE = "<!-- Generated by harness generate-slash-commands. Do not edit. -->";
|
|
4100
|
-
var GENERATED_HEADER_GEMINI = "# Generated by harness generate-slash-commands. Do not edit.";
|
|
4101
|
-
|
|
4102
|
-
// src/slash-commands/argument-hint.ts
|
|
4103
|
-
function buildArgumentHint(args) {
|
|
4104
|
-
return args.map((arg) => arg.required ? `--${arg.name} <${arg.name}>` : `[--${arg.name} <${arg.name}>]`).join(" ");
|
|
4105
|
-
}
|
|
4106
|
-
|
|
4107
|
-
// src/slash-commands/render-claude-code.ts
|
|
4108
|
-
function renderClaudeCode(spec) {
|
|
4109
|
-
const lines = ["---"];
|
|
4110
|
-
lines.push(`name: ${spec.fullName}`);
|
|
4111
|
-
lines.push(`description: ${spec.description}`);
|
|
4112
|
-
const hint = buildArgumentHint(spec.args);
|
|
4113
|
-
if (hint) {
|
|
4114
|
-
lines.push(`argument-hint: "${hint}"`);
|
|
4115
|
-
}
|
|
4116
|
-
if (spec.tools.length > 0) {
|
|
4117
|
-
lines.push("allowed-tools:");
|
|
4118
|
-
for (const tool of spec.tools) {
|
|
4119
|
-
lines.push(` - ${tool}`);
|
|
4120
|
-
}
|
|
4121
|
-
}
|
|
4122
|
-
lines.push("---");
|
|
4123
|
-
lines.push("");
|
|
4124
|
-
lines.push(GENERATED_HEADER_CLAUDE);
|
|
4125
|
-
lines.push("");
|
|
4126
|
-
lines.push("<context>");
|
|
4127
|
-
lines.push(spec.prompt.context);
|
|
4128
|
-
lines.push("</context>");
|
|
4129
|
-
lines.push("");
|
|
4130
|
-
lines.push("<objective>");
|
|
4131
|
-
lines.push(spec.prompt.objective);
|
|
4132
|
-
lines.push("</objective>");
|
|
4133
|
-
lines.push("");
|
|
4134
|
-
if (spec.prompt.executionContext) {
|
|
4135
|
-
lines.push("<execution_context>");
|
|
4136
|
-
lines.push(spec.prompt.executionContext);
|
|
4137
|
-
lines.push("</execution_context>");
|
|
4138
|
-
lines.push("");
|
|
4139
|
-
}
|
|
4140
|
-
lines.push("<process>");
|
|
4141
|
-
lines.push(spec.prompt.process);
|
|
4142
|
-
lines.push("</process>");
|
|
4143
|
-
lines.push("");
|
|
4144
|
-
return lines.join("\n");
|
|
4145
|
-
}
|
|
4146
|
-
|
|
4147
|
-
// src/slash-commands/render-gemini.ts
|
|
4148
|
-
function escapeTomlLiteral(content) {
|
|
4149
|
-
return content.replace(/'''/g, "''\\'''");
|
|
4150
|
-
}
|
|
4151
|
-
function renderGemini(spec, skillMdContent, skillYamlContent) {
|
|
4152
|
-
const lines = [GENERATED_HEADER_GEMINI];
|
|
4153
|
-
const safeDesc = spec.description.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
4154
|
-
lines.push(`description = "${safeDesc}"`);
|
|
4155
|
-
lines.push("prompt = '''");
|
|
4156
|
-
lines.push("<context>");
|
|
4157
|
-
lines.push(spec.prompt.context);
|
|
4158
|
-
lines.push("</context>");
|
|
4159
|
-
lines.push("");
|
|
4160
|
-
lines.push("<objective>");
|
|
4161
|
-
lines.push(spec.prompt.objective);
|
|
4162
|
-
lines.push("</objective>");
|
|
4163
|
-
lines.push("");
|
|
4164
|
-
if (skillMdContent || skillYamlContent) {
|
|
4165
|
-
lines.push("<execution_context>");
|
|
4166
|
-
if (skillMdContent) {
|
|
4167
|
-
const mdPath = spec.prompt.executionContext.split("\n")[0]?.replace(/^@/, "") ?? "";
|
|
4168
|
-
lines.push(`--- SKILL.md (${mdPath}) ---`);
|
|
4169
|
-
lines.push(escapeTomlLiteral(skillMdContent));
|
|
4170
|
-
lines.push("");
|
|
4171
|
-
}
|
|
4172
|
-
if (skillYamlContent) {
|
|
4173
|
-
const refs = spec.prompt.executionContext.split("\n");
|
|
4174
|
-
const yamlPath = (refs[1] ?? refs[0] ?? "").replace(/^@/, "");
|
|
4175
|
-
lines.push(`--- skill.yaml (${yamlPath}) ---`);
|
|
4176
|
-
lines.push(escapeTomlLiteral(skillYamlContent));
|
|
4177
|
-
}
|
|
4178
|
-
lines.push("</execution_context>");
|
|
4179
|
-
lines.push("");
|
|
4180
|
-
}
|
|
4181
|
-
const geminiProcess = spec.prompt.process.replace(
|
|
4182
|
-
"read SKILL.md and follow its workflow directly",
|
|
4183
|
-
"follow the SKILL.md workflow provided above directly"
|
|
4184
|
-
);
|
|
4185
|
-
lines.push("<process>");
|
|
4186
|
-
lines.push(geminiProcess);
|
|
4187
|
-
lines.push("</process>");
|
|
4188
|
-
lines.push("'''");
|
|
4189
|
-
lines.push("");
|
|
4190
|
-
return lines.join("\n");
|
|
4191
|
-
}
|
|
4192
|
-
|
|
4193
|
-
// src/slash-commands/sync.ts
|
|
4194
|
-
import fs21 from "fs";
|
|
4195
|
-
import path33 from "path";
|
|
4196
|
-
|
|
4197
|
-
// src/agent-definitions/constants.ts
|
|
4198
|
-
var GENERATED_HEADER_AGENT = "<!-- Generated by harness generate-agent-definitions. Do not edit. -->";
|
|
4199
|
-
|
|
4200
|
-
// src/slash-commands/sync.ts
|
|
4201
|
-
function computeSyncPlan(outputDir, rendered) {
|
|
4202
|
-
const added = [];
|
|
4203
|
-
const updated = [];
|
|
4204
|
-
const removed = [];
|
|
4205
|
-
const unchanged = [];
|
|
4206
|
-
for (const [filename, content] of rendered) {
|
|
4207
|
-
const filePath = path33.join(outputDir, filename);
|
|
4208
|
-
if (!fs21.existsSync(filePath)) {
|
|
4209
|
-
added.push(filename);
|
|
4210
|
-
} else {
|
|
4211
|
-
const existing = fs21.readFileSync(filePath, "utf-8");
|
|
4212
|
-
if (existing === content) {
|
|
4213
|
-
unchanged.push(filename);
|
|
4214
|
-
} else {
|
|
4215
|
-
updated.push(filename);
|
|
4216
|
-
}
|
|
4217
|
-
}
|
|
4218
|
-
}
|
|
4219
|
-
if (fs21.existsSync(outputDir)) {
|
|
4220
|
-
const existing = fs21.readdirSync(outputDir).filter((f) => {
|
|
4221
|
-
const stat = fs21.statSync(path33.join(outputDir, f));
|
|
4222
|
-
return stat.isFile();
|
|
4223
|
-
});
|
|
4224
|
-
for (const filename of existing) {
|
|
4225
|
-
if (rendered.has(filename)) continue;
|
|
4226
|
-
const content = fs21.readFileSync(path33.join(outputDir, filename), "utf-8");
|
|
4227
|
-
if (content.includes(GENERATED_HEADER_CLAUDE) || content.includes(GENERATED_HEADER_GEMINI) || content.includes(GENERATED_HEADER_AGENT)) {
|
|
4228
|
-
removed.push(filename);
|
|
4229
|
-
}
|
|
2322
|
+
statePath = path19.join(projectPath, ".harness", "state.json");
|
|
4230
2323
|
}
|
|
4231
|
-
|
|
4232
|
-
|
|
4233
|
-
|
|
4234
|
-
|
|
4235
|
-
fs21.mkdirSync(outputDir, { recursive: true });
|
|
4236
|
-
for (const filename of [...plan.added, ...plan.updated]) {
|
|
4237
|
-
const content = rendered.get(filename);
|
|
4238
|
-
if (content !== void 0) {
|
|
4239
|
-
fs21.writeFileSync(path33.join(outputDir, filename), content);
|
|
2324
|
+
if (!fs9.existsSync(statePath)) {
|
|
2325
|
+
logger.info("No state file found. Nothing to reset.");
|
|
2326
|
+
process.exit(ExitCode.SUCCESS);
|
|
2327
|
+
return;
|
|
4240
2328
|
}
|
|
4241
|
-
|
|
4242
|
-
|
|
4243
|
-
|
|
4244
|
-
|
|
4245
|
-
|
|
4246
|
-
|
|
2329
|
+
if (!opts.yes) {
|
|
2330
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
2331
|
+
const answer = await new Promise((resolve20) => {
|
|
2332
|
+
rl.question("Reset project state? This cannot be undone. [y/N] ", resolve20);
|
|
2333
|
+
});
|
|
2334
|
+
rl.close();
|
|
2335
|
+
if (answer.toLowerCase() !== "y" && answer.toLowerCase() !== "yes") {
|
|
2336
|
+
logger.info("Reset cancelled.");
|
|
2337
|
+
process.exit(ExitCode.SUCCESS);
|
|
2338
|
+
return;
|
|
4247
2339
|
}
|
|
4248
2340
|
}
|
|
4249
|
-
|
|
2341
|
+
try {
|
|
2342
|
+
fs9.unlinkSync(statePath);
|
|
2343
|
+
logger.success("Project state reset.");
|
|
2344
|
+
} catch (e) {
|
|
2345
|
+
logger.error(`Failed to reset state: ${e instanceof Error ? e.message : String(e)}`);
|
|
2346
|
+
process.exit(ExitCode.ERROR);
|
|
2347
|
+
return;
|
|
2348
|
+
}
|
|
2349
|
+
process.exit(ExitCode.SUCCESS);
|
|
2350
|
+
});
|
|
4250
2351
|
}
|
|
4251
2352
|
|
|
4252
|
-
// src/commands/
|
|
4253
|
-
|
|
4254
|
-
|
|
4255
|
-
|
|
4256
|
-
|
|
4257
|
-
|
|
4258
|
-
const
|
|
4259
|
-
|
|
4260
|
-
|
|
4261
|
-
|
|
4262
|
-
|
|
4263
|
-
|
|
4264
|
-
|
|
4265
|
-
|
|
4266
|
-
async function confirmDeletion(files) {
|
|
4267
|
-
const rl = readline2.createInterface({ input: process.stdin, output: process.stdout });
|
|
4268
|
-
return new Promise((resolve24) => {
|
|
4269
|
-
rl.question(`
|
|
4270
|
-
Remove ${files.length} orphaned command(s)? (y/N) `, (answer) => {
|
|
4271
|
-
rl.close();
|
|
4272
|
-
resolve24(answer.toLowerCase() === "y");
|
|
4273
|
-
});
|
|
2353
|
+
// src/commands/state/learn.ts
|
|
2354
|
+
import { Command as Command28 } from "commander";
|
|
2355
|
+
import * as path20 from "path";
|
|
2356
|
+
function createLearnCommand() {
|
|
2357
|
+
return new Command28("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) => {
|
|
2358
|
+
const projectPath = path20.resolve(opts.path);
|
|
2359
|
+
const result = await appendLearning(projectPath, message, void 0, void 0, opts.stream);
|
|
2360
|
+
if (!result.ok) {
|
|
2361
|
+
logger.error(result.error.message);
|
|
2362
|
+
process.exit(ExitCode.ERROR);
|
|
2363
|
+
return;
|
|
2364
|
+
}
|
|
2365
|
+
logger.success(`Learning recorded.`);
|
|
2366
|
+
process.exit(ExitCode.SUCCESS);
|
|
4274
2367
|
});
|
|
4275
2368
|
}
|
|
4276
|
-
|
|
4277
|
-
|
|
4278
|
-
|
|
4279
|
-
|
|
4280
|
-
|
|
4281
|
-
|
|
4282
|
-
|
|
4283
|
-
|
|
2369
|
+
|
|
2370
|
+
// src/commands/state/streams.ts
|
|
2371
|
+
import { Command as Command29 } from "commander";
|
|
2372
|
+
import * as path21 from "path";
|
|
2373
|
+
function createStreamsCommand() {
|
|
2374
|
+
const command = new Command29("streams").description("Manage state streams");
|
|
2375
|
+
command.command("list").description("List all known streams").option("--path <path>", "Project root path", ".").action(async (opts, cmd) => {
|
|
2376
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
2377
|
+
const projectPath = path21.resolve(opts.path);
|
|
2378
|
+
const indexResult = await loadStreamIndex(projectPath);
|
|
2379
|
+
const result = await listStreams(projectPath);
|
|
2380
|
+
if (!result.ok) {
|
|
2381
|
+
logger.error(result.error.message);
|
|
2382
|
+
process.exit(ExitCode.ERROR);
|
|
2383
|
+
return;
|
|
4284
2384
|
}
|
|
4285
|
-
|
|
4286
|
-
|
|
4287
|
-
|
|
4288
|
-
|
|
2385
|
+
const active = indexResult.ok ? indexResult.value.activeStream : null;
|
|
2386
|
+
if (globalOpts.json) {
|
|
2387
|
+
logger.raw({ activeStream: active, streams: result.value });
|
|
2388
|
+
} else {
|
|
2389
|
+
if (result.value.length === 0) {
|
|
2390
|
+
console.log("No streams found.");
|
|
4289
2391
|
}
|
|
4290
|
-
|
|
4291
|
-
|
|
4292
|
-
|
|
4293
|
-
|
|
4294
|
-
for (const platform of opts.platforms) {
|
|
4295
|
-
const outputDir = resolveOutputDir(platform, opts);
|
|
4296
|
-
const ext = fileExtension(platform);
|
|
4297
|
-
const useAbsolutePaths = opts.global;
|
|
4298
|
-
const rendered = /* @__PURE__ */ new Map();
|
|
4299
|
-
for (const spec of specs) {
|
|
4300
|
-
const filename = `${spec.name}${ext}`;
|
|
4301
|
-
if (platform === "claude-code") {
|
|
4302
|
-
const renderSpec = useAbsolutePaths ? {
|
|
4303
|
-
...spec,
|
|
4304
|
-
prompt: {
|
|
4305
|
-
...spec.prompt,
|
|
4306
|
-
executionContext: spec.prompt.executionContext.split("\n").map((line) => {
|
|
4307
|
-
if (line.startsWith("@")) {
|
|
4308
|
-
const relPath = line.slice(1);
|
|
4309
|
-
return `@${path34.resolve(relPath)}`;
|
|
4310
|
-
}
|
|
4311
|
-
return line;
|
|
4312
|
-
}).join("\n")
|
|
4313
|
-
}
|
|
4314
|
-
} : spec;
|
|
4315
|
-
rendered.set(filename, renderClaudeCode(renderSpec));
|
|
4316
|
-
} else {
|
|
4317
|
-
const mdPath = path34.join(spec.skillsBaseDir, spec.sourceDir, "SKILL.md");
|
|
4318
|
-
const yamlPath = path34.join(spec.skillsBaseDir, spec.sourceDir, "skill.yaml");
|
|
4319
|
-
const mdContent = fs22.existsSync(mdPath) ? fs22.readFileSync(mdPath, "utf-8") : "";
|
|
4320
|
-
const yamlContent = fs22.existsSync(yamlPath) ? fs22.readFileSync(yamlPath, "utf-8") : "";
|
|
4321
|
-
rendered.set(filename, renderGemini(spec, mdContent, yamlContent));
|
|
2392
|
+
for (const s of result.value) {
|
|
2393
|
+
const marker = s.name === active ? " (active)" : "";
|
|
2394
|
+
const branch = s.branch ? ` [${s.branch}]` : "";
|
|
2395
|
+
console.log(` ${s.name}${marker}${branch} \u2014 last active: ${s.lastActiveAt}`);
|
|
4322
2396
|
}
|
|
4323
2397
|
}
|
|
4324
|
-
|
|
4325
|
-
|
|
4326
|
-
|
|
4327
|
-
|
|
4328
|
-
|
|
4329
|
-
|
|
4330
|
-
|
|
4331
|
-
|
|
4332
|
-
|
|
4333
|
-
unchanged: plan.unchanged,
|
|
4334
|
-
outputDir
|
|
4335
|
-
});
|
|
4336
|
-
}
|
|
4337
|
-
return results;
|
|
4338
|
-
}
|
|
4339
|
-
async function handleOrphanDeletion(results, opts) {
|
|
4340
|
-
if (opts.dryRun) return;
|
|
4341
|
-
for (const result of results) {
|
|
4342
|
-
if (result.removed.length === 0) continue;
|
|
4343
|
-
const shouldDelete = opts.yes || await confirmDeletion(result.removed);
|
|
4344
|
-
if (shouldDelete) {
|
|
4345
|
-
for (const filename of result.removed) {
|
|
4346
|
-
const filePath = path34.join(result.outputDir, filename);
|
|
4347
|
-
if (fs22.existsSync(filePath)) {
|
|
4348
|
-
fs22.unlinkSync(filePath);
|
|
4349
|
-
}
|
|
4350
|
-
}
|
|
2398
|
+
process.exit(ExitCode.SUCCESS);
|
|
2399
|
+
});
|
|
2400
|
+
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) => {
|
|
2401
|
+
const projectPath = path21.resolve(opts.path);
|
|
2402
|
+
const result = await createStream(projectPath, name, opts.branch);
|
|
2403
|
+
if (!result.ok) {
|
|
2404
|
+
logger.error(result.error.message);
|
|
2405
|
+
process.exit(ExitCode.ERROR);
|
|
2406
|
+
return;
|
|
4351
2407
|
}
|
|
4352
|
-
|
|
4353
|
-
|
|
4354
|
-
|
|
4355
|
-
|
|
4356
|
-
|
|
4357
|
-
|
|
4358
|
-
|
|
4359
|
-
|
|
4360
|
-
|
|
4361
|
-
|
|
4362
|
-
throw new CLIError(
|
|
4363
|
-
`Invalid platform "${p}". Valid platforms: ${VALID_PLATFORMS.join(", ")}`,
|
|
4364
|
-
ExitCode.VALIDATION_FAILED
|
|
4365
|
-
);
|
|
4366
|
-
}
|
|
2408
|
+
logger.success(`Stream '${name}' created.`);
|
|
2409
|
+
process.exit(ExitCode.SUCCESS);
|
|
2410
|
+
});
|
|
2411
|
+
command.command("archive <name>").description("Archive a stream").option("--path <path>", "Project root path", ".").action(async (name, opts) => {
|
|
2412
|
+
const projectPath = path21.resolve(opts.path);
|
|
2413
|
+
const result = await archiveStream(projectPath, name);
|
|
2414
|
+
if (!result.ok) {
|
|
2415
|
+
logger.error(result.error.message);
|
|
2416
|
+
process.exit(ExitCode.ERROR);
|
|
2417
|
+
return;
|
|
4367
2418
|
}
|
|
4368
|
-
|
|
4369
|
-
|
|
4370
|
-
|
|
4371
|
-
|
|
4372
|
-
|
|
4373
|
-
|
|
4374
|
-
|
|
4375
|
-
|
|
4376
|
-
|
|
4377
|
-
|
|
4378
|
-
const results = generateSlashCommands(generateOpts);
|
|
4379
|
-
if (globalOpts.json) {
|
|
4380
|
-
console.log(JSON.stringify(results, null, 2));
|
|
4381
|
-
return;
|
|
4382
|
-
}
|
|
4383
|
-
const totalCommands = results.reduce(
|
|
4384
|
-
(sum, r) => sum + r.added.length + r.updated.length + r.unchanged.length,
|
|
4385
|
-
0
|
|
4386
|
-
);
|
|
4387
|
-
if (totalCommands === 0) {
|
|
4388
|
-
console.log(
|
|
4389
|
-
"\nNo skills found. Use --include-global to include built-in skills, or create a skill with: harness create-skill"
|
|
4390
|
-
);
|
|
4391
|
-
return;
|
|
4392
|
-
}
|
|
4393
|
-
for (const result of results) {
|
|
4394
|
-
console.log(`
|
|
4395
|
-
${result.platform} \u2192 ${result.outputDir}`);
|
|
4396
|
-
if (result.added.length > 0) {
|
|
4397
|
-
console.log(` + ${result.added.length} new: ${result.added.join(", ")}`);
|
|
4398
|
-
}
|
|
4399
|
-
if (result.updated.length > 0) {
|
|
4400
|
-
console.log(` ~ ${result.updated.length} updated: ${result.updated.join(", ")}`);
|
|
4401
|
-
}
|
|
4402
|
-
if (result.removed.length > 0) {
|
|
4403
|
-
console.log(` - ${result.removed.length} removed: ${result.removed.join(", ")}`);
|
|
4404
|
-
}
|
|
4405
|
-
if (result.unchanged.length > 0) {
|
|
4406
|
-
console.log(` = ${result.unchanged.length} unchanged`);
|
|
4407
|
-
}
|
|
4408
|
-
if (opts.dryRun) {
|
|
4409
|
-
console.log(" (dry run \u2014 no files written)");
|
|
4410
|
-
}
|
|
4411
|
-
}
|
|
4412
|
-
await handleOrphanDeletion(results, { yes: opts.yes, dryRun: opts.dryRun });
|
|
4413
|
-
} catch (error) {
|
|
4414
|
-
handleError(error);
|
|
2419
|
+
logger.success(`Stream '${name}' archived.`);
|
|
2420
|
+
process.exit(ExitCode.SUCCESS);
|
|
2421
|
+
});
|
|
2422
|
+
command.command("activate <name>").description("Set the active stream").option("--path <path>", "Project root path", ".").action(async (name, opts) => {
|
|
2423
|
+
const projectPath = path21.resolve(opts.path);
|
|
2424
|
+
const result = await setActiveStream(projectPath, name);
|
|
2425
|
+
if (!result.ok) {
|
|
2426
|
+
logger.error(result.error.message);
|
|
2427
|
+
process.exit(ExitCode.ERROR);
|
|
2428
|
+
return;
|
|
4415
2429
|
}
|
|
2430
|
+
logger.success(`Active stream set to '${name}'.`);
|
|
2431
|
+
process.exit(ExitCode.SUCCESS);
|
|
4416
2432
|
});
|
|
2433
|
+
return command;
|
|
2434
|
+
}
|
|
2435
|
+
|
|
2436
|
+
// src/commands/state/index.ts
|
|
2437
|
+
function createStateCommand() {
|
|
2438
|
+
const command = new Command30("state").description("Project state management commands");
|
|
2439
|
+
command.addCommand(createShowCommand());
|
|
2440
|
+
command.addCommand(createResetCommand());
|
|
2441
|
+
command.addCommand(createLearnCommand());
|
|
2442
|
+
command.addCommand(createStreamsCommand());
|
|
2443
|
+
return command;
|
|
4417
2444
|
}
|
|
4418
2445
|
|
|
4419
2446
|
// src/commands/ci/index.ts
|
|
4420
|
-
import { Command as
|
|
2447
|
+
import { Command as Command33 } from "commander";
|
|
4421
2448
|
|
|
4422
2449
|
// src/commands/ci/check.ts
|
|
4423
|
-
import { Command as
|
|
2450
|
+
import { Command as Command31 } from "commander";
|
|
4424
2451
|
var VALID_CHECKS = ["validate", "deps", "docs", "entropy", "phase-gate"];
|
|
4425
2452
|
async function runCICheck(options) {
|
|
4426
2453
|
const configResult = resolveConfig(options.configPath);
|
|
@@ -4451,7 +2478,7 @@ function parseFailOn(failOn) {
|
|
|
4451
2478
|
return "error";
|
|
4452
2479
|
}
|
|
4453
2480
|
function createCheckCommand() {
|
|
4454
|
-
return new
|
|
2481
|
+
return new Command31("check").description("Run all harness checks for CI (validate, deps, docs, entropy, phase-gate)").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) => {
|
|
4455
2482
|
const globalOpts = cmd.optsWithGlobals();
|
|
4456
2483
|
const mode = globalOpts.json ? OutputMode.JSON : globalOpts.quiet ? OutputMode.QUIET : globalOpts.verbose ? OutputMode.VERBOSE : OutputMode.TEXT;
|
|
4457
2484
|
const skip = parseSkip(opts.skip);
|
|
@@ -4495,9 +2522,9 @@ function createCheckCommand() {
|
|
|
4495
2522
|
}
|
|
4496
2523
|
|
|
4497
2524
|
// src/commands/ci/init.ts
|
|
4498
|
-
import { Command as
|
|
4499
|
-
import * as
|
|
4500
|
-
import * as
|
|
2525
|
+
import { Command as Command32 } from "commander";
|
|
2526
|
+
import * as fs10 from "fs";
|
|
2527
|
+
import * as path22 from "path";
|
|
4501
2528
|
var ALL_CHECKS = ["validate", "deps", "docs", "entropy", "phase-gate"];
|
|
4502
2529
|
function buildSkipFlag(checks) {
|
|
4503
2530
|
if (!checks) return "";
|
|
@@ -4589,12 +2616,12 @@ function generateCIConfig(options) {
|
|
|
4589
2616
|
});
|
|
4590
2617
|
}
|
|
4591
2618
|
function detectPlatform() {
|
|
4592
|
-
if (
|
|
4593
|
-
if (
|
|
2619
|
+
if (fs10.existsSync(".github")) return "github";
|
|
2620
|
+
if (fs10.existsSync(".gitlab-ci.yml")) return "gitlab";
|
|
4594
2621
|
return null;
|
|
4595
2622
|
}
|
|
4596
2623
|
function createInitCommand2() {
|
|
4597
|
-
return new
|
|
2624
|
+
return new Command32("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) => {
|
|
4598
2625
|
const globalOpts = cmd.optsWithGlobals();
|
|
4599
2626
|
const platform = opts.platform ?? detectPlatform() ?? "generic";
|
|
4600
2627
|
const checks = opts.checks ? opts.checks.split(",").map((s) => s.trim()) : void 0;
|
|
@@ -4606,12 +2633,12 @@ function createInitCommand2() {
|
|
|
4606
2633
|
process.exit(result.error.exitCode);
|
|
4607
2634
|
}
|
|
4608
2635
|
const { filename, content } = result.value;
|
|
4609
|
-
const targetPath =
|
|
4610
|
-
const dir =
|
|
4611
|
-
|
|
4612
|
-
|
|
2636
|
+
const targetPath = path22.resolve(filename);
|
|
2637
|
+
const dir = path22.dirname(targetPath);
|
|
2638
|
+
fs10.mkdirSync(dir, { recursive: true });
|
|
2639
|
+
fs10.writeFileSync(targetPath, content);
|
|
4613
2640
|
if (platform === "generic" && process.platform !== "win32") {
|
|
4614
|
-
|
|
2641
|
+
fs10.chmodSync(targetPath, "755");
|
|
4615
2642
|
}
|
|
4616
2643
|
if (globalOpts.json) {
|
|
4617
2644
|
console.log(JSON.stringify({ file: filename, platform }));
|
|
@@ -4624,18 +2651,18 @@ function createInitCommand2() {
|
|
|
4624
2651
|
|
|
4625
2652
|
// src/commands/ci/index.ts
|
|
4626
2653
|
function createCICommand() {
|
|
4627
|
-
const command = new
|
|
2654
|
+
const command = new Command33("ci").description("CI/CD integration commands");
|
|
4628
2655
|
command.addCommand(createCheckCommand());
|
|
4629
2656
|
command.addCommand(createInitCommand2());
|
|
4630
2657
|
return command;
|
|
4631
2658
|
}
|
|
4632
2659
|
|
|
4633
2660
|
// src/commands/update.ts
|
|
4634
|
-
import { Command as
|
|
2661
|
+
import { Command as Command34 } from "commander";
|
|
4635
2662
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
4636
2663
|
import { realpathSync } from "fs";
|
|
4637
|
-
import
|
|
4638
|
-
import
|
|
2664
|
+
import readline2 from "readline";
|
|
2665
|
+
import chalk3 from "chalk";
|
|
4639
2666
|
function detectPackageManager() {
|
|
4640
2667
|
try {
|
|
4641
2668
|
const argv1 = process.argv[1];
|
|
@@ -4687,19 +2714,19 @@ function getInstalledPackages(pm) {
|
|
|
4687
2714
|
}
|
|
4688
2715
|
}
|
|
4689
2716
|
function prompt(question) {
|
|
4690
|
-
const rl =
|
|
2717
|
+
const rl = readline2.createInterface({
|
|
4691
2718
|
input: process.stdin,
|
|
4692
2719
|
output: process.stdout
|
|
4693
2720
|
});
|
|
4694
|
-
return new Promise((
|
|
2721
|
+
return new Promise((resolve20) => {
|
|
4695
2722
|
rl.question(question, (answer) => {
|
|
4696
2723
|
rl.close();
|
|
4697
|
-
|
|
2724
|
+
resolve20(answer.trim().toLowerCase());
|
|
4698
2725
|
});
|
|
4699
2726
|
});
|
|
4700
2727
|
}
|
|
4701
2728
|
function createUpdateCommand() {
|
|
4702
|
-
return new
|
|
2729
|
+
return new Command34("update").description("Update all @harness-engineering packages to the latest version").option("--version <semver>", "Pin @harness-engineering/cli to a specific version").action(async (opts, cmd) => {
|
|
4703
2730
|
const globalOpts = cmd.optsWithGlobals();
|
|
4704
2731
|
const pm = detectPackageManager();
|
|
4705
2732
|
if (globalOpts.verbose) {
|
|
@@ -4721,8 +2748,8 @@ function createUpdateCommand() {
|
|
|
4721
2748
|
}
|
|
4722
2749
|
if (currentVersion) {
|
|
4723
2750
|
console.log("");
|
|
4724
|
-
logger.info(`Current CLI version: ${
|
|
4725
|
-
logger.info(`Latest CLI version: ${
|
|
2751
|
+
logger.info(`Current CLI version: ${chalk3.dim(`v${currentVersion}`)}`);
|
|
2752
|
+
logger.info(`Latest CLI version: ${chalk3.green(`v${latestCliVersion}`)}`);
|
|
4726
2753
|
console.log("");
|
|
4727
2754
|
}
|
|
4728
2755
|
}
|
|
@@ -4748,7 +2775,7 @@ function createUpdateCommand() {
|
|
|
4748
2775
|
} catch {
|
|
4749
2776
|
console.log("");
|
|
4750
2777
|
logger.error("Update failed. You can try manually:");
|
|
4751
|
-
console.log(` ${
|
|
2778
|
+
console.log(` ${chalk3.cyan(installCmd)}`);
|
|
4752
2779
|
process.exit(ExitCode.ERROR);
|
|
4753
2780
|
}
|
|
4754
2781
|
console.log("");
|
|
@@ -4762,277 +2789,17 @@ function createUpdateCommand() {
|
|
|
4762
2789
|
});
|
|
4763
2790
|
} catch {
|
|
4764
2791
|
logger.warn("Generation failed. Run manually:");
|
|
4765
|
-
console.log(` ${
|
|
2792
|
+
console.log(` ${chalk3.cyan(`harness generate${isGlobal ? " --global" : ""}`)}`);
|
|
4766
2793
|
}
|
|
4767
2794
|
}
|
|
4768
2795
|
process.exit(ExitCode.SUCCESS);
|
|
4769
2796
|
});
|
|
4770
2797
|
}
|
|
4771
2798
|
|
|
4772
|
-
// src/commands/generate-agent-definitions.ts
|
|
4773
|
-
import { Command as Command37 } from "commander";
|
|
4774
|
-
import * as fs24 from "fs";
|
|
4775
|
-
import * as path36 from "path";
|
|
4776
|
-
import * as os3 from "os";
|
|
4777
|
-
|
|
4778
|
-
// src/agent-definitions/generator.ts
|
|
4779
|
-
var AGENT_DESCRIPTIONS = {
|
|
4780
|
-
"code-reviewer": "Perform code review and address review findings using harness methodology. Use when reviewing code, fixing review findings, responding to review feedback, or when a code review has produced issues that need to be addressed.",
|
|
4781
|
-
"task-executor": "Execute implementation plans task-by-task with state tracking, TDD, and verification. Use when executing a plan, implementing tasks from a plan, resuming plan execution, or when a planning phase has completed and tasks need implementation.",
|
|
4782
|
-
"parallel-coordinator": "Dispatch independent tasks across isolated agents for parallel execution. Use when multiple independent tasks need to run concurrently, splitting work across agents, or coordinating parallel implementation.",
|
|
4783
|
-
"architecture-enforcer": "Validate architectural constraints and dependency rules. Use when checking layer boundaries, detecting circular dependencies, or verifying import direction compliance.",
|
|
4784
|
-
"documentation-maintainer": "Keep documentation in sync with source code. Use when detecting documentation drift, validating doc coverage, or aligning docs with code changes.",
|
|
4785
|
-
"entropy-cleaner": "Detect and fix codebase entropy including drift, dead code, and pattern violations. Use when running cleanup, detecting dead code, or fixing pattern violations.",
|
|
4786
|
-
planner: "Create detailed implementation plans from specs with task breakdown, dependency ordering, and checkpoint placement. Use when planning a phase, breaking a spec into tasks, or creating an execution plan.",
|
|
4787
|
-
verifier: "Verify implementation completeness against spec and plan at three tiers (EXISTS, SUBSTANTIVE, WIRED). Use when checking if built code matches what was planned, validating phase completion, or auditing implementation quality."
|
|
4788
|
-
};
|
|
4789
|
-
var DEFAULT_TOOLS = ["Bash", "Read", "Write", "Edit", "Glob", "Grep"];
|
|
4790
|
-
var GEMINI_TOOL_MAP = {
|
|
4791
|
-
Bash: "run_shell_command",
|
|
4792
|
-
Read: "read_file",
|
|
4793
|
-
Write: "write_file",
|
|
4794
|
-
Edit: "replace",
|
|
4795
|
-
Glob: "glob",
|
|
4796
|
-
Grep: "search_file_content"
|
|
4797
|
-
};
|
|
4798
|
-
function generateAgentDefinition(persona, skillContents) {
|
|
4799
|
-
const kebabName = toKebabCase(persona.name);
|
|
4800
|
-
const name = `harness-${kebabName}`;
|
|
4801
|
-
const description = AGENT_DESCRIPTIONS[kebabName] ?? persona.description;
|
|
4802
|
-
const methodologyParts = [];
|
|
4803
|
-
for (const skillName of persona.skills) {
|
|
4804
|
-
const content = skillContents.get(skillName);
|
|
4805
|
-
if (content) {
|
|
4806
|
-
methodologyParts.push(content);
|
|
4807
|
-
}
|
|
4808
|
-
}
|
|
4809
|
-
return {
|
|
4810
|
-
name,
|
|
4811
|
-
description,
|
|
4812
|
-
tools: [...DEFAULT_TOOLS],
|
|
4813
|
-
role: persona.role,
|
|
4814
|
-
skills: persona.skills,
|
|
4815
|
-
steps: persona.steps,
|
|
4816
|
-
methodology: methodologyParts.join("\n\n---\n\n")
|
|
4817
|
-
};
|
|
4818
|
-
}
|
|
4819
|
-
|
|
4820
|
-
// src/agent-definitions/render-claude-code.ts
|
|
4821
|
-
function formatStep(step, index) {
|
|
4822
|
-
if ("command" in step && step.command) {
|
|
4823
|
-
const cmd = step.command;
|
|
4824
|
-
const when = step.when ?? "always";
|
|
4825
|
-
return `${index + 1}. Run \`harness ${cmd}\` (${when})`;
|
|
4826
|
-
}
|
|
4827
|
-
if ("skill" in step && step.skill) {
|
|
4828
|
-
const skill = step.skill;
|
|
4829
|
-
const when = step.when ?? "always";
|
|
4830
|
-
return `${index + 1}. Execute ${skill} skill (${when})`;
|
|
4831
|
-
}
|
|
4832
|
-
return `${index + 1}. Unknown step`;
|
|
4833
|
-
}
|
|
4834
|
-
function renderClaudeCodeAgent(def) {
|
|
4835
|
-
const lines = ["---"];
|
|
4836
|
-
lines.push(`name: ${def.name}`);
|
|
4837
|
-
lines.push(`description: >`);
|
|
4838
|
-
lines.push(` ${def.description}`);
|
|
4839
|
-
lines.push(`tools: ${def.tools.join(", ")}`);
|
|
4840
|
-
lines.push("---");
|
|
4841
|
-
lines.push("");
|
|
4842
|
-
lines.push(GENERATED_HEADER_AGENT);
|
|
4843
|
-
lines.push("");
|
|
4844
|
-
lines.push("## Role");
|
|
4845
|
-
lines.push("");
|
|
4846
|
-
lines.push(def.role);
|
|
4847
|
-
lines.push("");
|
|
4848
|
-
lines.push("## Skills");
|
|
4849
|
-
lines.push("");
|
|
4850
|
-
for (const skill of def.skills) {
|
|
4851
|
-
lines.push(`- ${skill}`);
|
|
4852
|
-
}
|
|
4853
|
-
lines.push("");
|
|
4854
|
-
lines.push("## Steps");
|
|
4855
|
-
lines.push("");
|
|
4856
|
-
def.steps.forEach((step, i) => {
|
|
4857
|
-
lines.push(formatStep(step, i));
|
|
4858
|
-
});
|
|
4859
|
-
lines.push("");
|
|
4860
|
-
if (def.methodology) {
|
|
4861
|
-
lines.push("## Methodology");
|
|
4862
|
-
lines.push("");
|
|
4863
|
-
lines.push(def.methodology);
|
|
4864
|
-
lines.push("");
|
|
4865
|
-
}
|
|
4866
|
-
return lines.join("\n");
|
|
4867
|
-
}
|
|
4868
|
-
|
|
4869
|
-
// src/agent-definitions/render-gemini-cli.ts
|
|
4870
|
-
function toGeminiToolName(tool) {
|
|
4871
|
-
return GEMINI_TOOL_MAP[tool] ?? tool;
|
|
4872
|
-
}
|
|
4873
|
-
function formatStep2(step, index) {
|
|
4874
|
-
if ("command" in step && step.command) {
|
|
4875
|
-
const cmd = step.command;
|
|
4876
|
-
const when = step.when ?? "always";
|
|
4877
|
-
return `${index + 1}. Run \`harness ${cmd}\` (${when})`;
|
|
4878
|
-
}
|
|
4879
|
-
if ("skill" in step && step.skill) {
|
|
4880
|
-
const skill = step.skill;
|
|
4881
|
-
const when = step.when ?? "always";
|
|
4882
|
-
return `${index + 1}. Execute ${skill} skill (${when})`;
|
|
4883
|
-
}
|
|
4884
|
-
return `${index + 1}. Unknown step`;
|
|
4885
|
-
}
|
|
4886
|
-
function renderGeminiAgent(def) {
|
|
4887
|
-
const lines = ["---"];
|
|
4888
|
-
lines.push(`name: ${def.name}`);
|
|
4889
|
-
lines.push(`description: >`);
|
|
4890
|
-
lines.push(` ${def.description}`);
|
|
4891
|
-
if (def.tools.length > 0) {
|
|
4892
|
-
lines.push("tools:");
|
|
4893
|
-
for (const tool of def.tools) {
|
|
4894
|
-
lines.push(` - ${toGeminiToolName(tool)}`);
|
|
4895
|
-
}
|
|
4896
|
-
}
|
|
4897
|
-
lines.push("---");
|
|
4898
|
-
lines.push("");
|
|
4899
|
-
lines.push(GENERATED_HEADER_AGENT);
|
|
4900
|
-
lines.push("");
|
|
4901
|
-
lines.push("## Role");
|
|
4902
|
-
lines.push("");
|
|
4903
|
-
lines.push(def.role);
|
|
4904
|
-
lines.push("");
|
|
4905
|
-
lines.push("## Skills");
|
|
4906
|
-
lines.push("");
|
|
4907
|
-
for (const skill of def.skills) {
|
|
4908
|
-
lines.push(`- ${skill}`);
|
|
4909
|
-
}
|
|
4910
|
-
lines.push("");
|
|
4911
|
-
lines.push("## Steps");
|
|
4912
|
-
lines.push("");
|
|
4913
|
-
def.steps.forEach((step, i) => {
|
|
4914
|
-
lines.push(formatStep2(step, i));
|
|
4915
|
-
});
|
|
4916
|
-
lines.push("");
|
|
4917
|
-
if (def.methodology) {
|
|
4918
|
-
lines.push("## Methodology");
|
|
4919
|
-
lines.push("");
|
|
4920
|
-
lines.push(def.methodology);
|
|
4921
|
-
lines.push("");
|
|
4922
|
-
}
|
|
4923
|
-
return lines.join("\n");
|
|
4924
|
-
}
|
|
4925
|
-
|
|
4926
|
-
// src/commands/generate-agent-definitions.ts
|
|
4927
|
-
function resolveOutputDir2(platform, opts) {
|
|
4928
|
-
if (opts.output) {
|
|
4929
|
-
return platform === "claude-code" ? path36.join(opts.output, "claude-code") : path36.join(opts.output, "gemini-cli");
|
|
4930
|
-
}
|
|
4931
|
-
if (opts.global) {
|
|
4932
|
-
const home = os3.homedir();
|
|
4933
|
-
return platform === "claude-code" ? path36.join(home, ".claude", "agents") : path36.join(home, ".gemini", "agents");
|
|
4934
|
-
}
|
|
4935
|
-
return platform === "claude-code" ? path36.join("agents", "agents", "claude-code") : path36.join("agents", "agents", "gemini-cli");
|
|
4936
|
-
}
|
|
4937
|
-
function loadSkillContent(skillName) {
|
|
4938
|
-
const skillsDir = resolveSkillsDir();
|
|
4939
|
-
const skillMdPath = path36.join(skillsDir, skillName, "SKILL.md");
|
|
4940
|
-
if (!fs24.existsSync(skillMdPath)) return null;
|
|
4941
|
-
return fs24.readFileSync(skillMdPath, "utf-8");
|
|
4942
|
-
}
|
|
4943
|
-
function getRenderer(platform) {
|
|
4944
|
-
return platform === "claude-code" ? renderClaudeCodeAgent : renderGeminiAgent;
|
|
4945
|
-
}
|
|
4946
|
-
function generateAgentDefinitions(opts) {
|
|
4947
|
-
const personasDir = resolvePersonasDir();
|
|
4948
|
-
const personaList = listPersonas(personasDir);
|
|
4949
|
-
if (!personaList.ok) return [];
|
|
4950
|
-
const personas = personaList.value.map((meta) => loadPersona(meta.filePath)).filter((r) => r.ok).map((r) => r.value);
|
|
4951
|
-
const allSkillNames = new Set(personas.flatMap((p) => p.skills));
|
|
4952
|
-
const skillContents = /* @__PURE__ */ new Map();
|
|
4953
|
-
for (const skillName of allSkillNames) {
|
|
4954
|
-
const content = loadSkillContent(skillName);
|
|
4955
|
-
if (content) {
|
|
4956
|
-
skillContents.set(skillName, content);
|
|
4957
|
-
}
|
|
4958
|
-
}
|
|
4959
|
-
const definitions = personas.map((p) => generateAgentDefinition(p, skillContents));
|
|
4960
|
-
const results = [];
|
|
4961
|
-
for (const platform of opts.platforms) {
|
|
4962
|
-
const outputDir = resolveOutputDir2(platform, opts);
|
|
4963
|
-
const renderer = getRenderer(platform);
|
|
4964
|
-
const rendered = /* @__PURE__ */ new Map();
|
|
4965
|
-
for (const def of definitions) {
|
|
4966
|
-
const filename = `${def.name}.md`;
|
|
4967
|
-
rendered.set(filename, renderer(def));
|
|
4968
|
-
}
|
|
4969
|
-
const plan = computeSyncPlan(outputDir, rendered);
|
|
4970
|
-
if (!opts.dryRun) {
|
|
4971
|
-
applySyncPlan(outputDir, rendered, plan, false);
|
|
4972
|
-
}
|
|
4973
|
-
results.push({
|
|
4974
|
-
platform,
|
|
4975
|
-
added: plan.added,
|
|
4976
|
-
updated: plan.updated,
|
|
4977
|
-
removed: plan.removed,
|
|
4978
|
-
unchanged: plan.unchanged,
|
|
4979
|
-
outputDir
|
|
4980
|
-
});
|
|
4981
|
-
}
|
|
4982
|
-
return results;
|
|
4983
|
-
}
|
|
4984
|
-
function createGenerateAgentDefinitionsCommand() {
|
|
4985
|
-
return new Command37("generate-agent-definitions").description("Generate agent definition files from personas for Claude Code and Gemini CLI").option("--platforms <list>", "Target platforms (comma-separated)", "claude-code,gemini-cli").option("--global", "Write to global agent directories", false).option("--output <dir>", "Custom output directory").option("--dry-run", "Show what would change without writing", false).action(async (opts, cmd) => {
|
|
4986
|
-
const globalOpts = cmd.optsWithGlobals();
|
|
4987
|
-
const platforms = opts.platforms.split(",").map((p) => p.trim());
|
|
4988
|
-
for (const p of platforms) {
|
|
4989
|
-
if (!VALID_PLATFORMS.includes(p)) {
|
|
4990
|
-
throw new CLIError(
|
|
4991
|
-
`Invalid platform "${p}". Valid platforms: ${VALID_PLATFORMS.join(", ")}`,
|
|
4992
|
-
ExitCode.VALIDATION_FAILED
|
|
4993
|
-
);
|
|
4994
|
-
}
|
|
4995
|
-
}
|
|
4996
|
-
try {
|
|
4997
|
-
const results = generateAgentDefinitions({
|
|
4998
|
-
platforms,
|
|
4999
|
-
global: opts.global,
|
|
5000
|
-
output: opts.output,
|
|
5001
|
-
dryRun: opts.dryRun
|
|
5002
|
-
});
|
|
5003
|
-
if (globalOpts.json) {
|
|
5004
|
-
console.log(JSON.stringify(results, null, 2));
|
|
5005
|
-
return;
|
|
5006
|
-
}
|
|
5007
|
-
for (const result of results) {
|
|
5008
|
-
console.log(`
|
|
5009
|
-
${result.platform} \u2192 ${result.outputDir}`);
|
|
5010
|
-
if (result.added.length > 0) {
|
|
5011
|
-
console.log(` + ${result.added.length} new: ${result.added.join(", ")}`);
|
|
5012
|
-
}
|
|
5013
|
-
if (result.updated.length > 0) {
|
|
5014
|
-
console.log(` ~ ${result.updated.length} updated: ${result.updated.join(", ")}`);
|
|
5015
|
-
}
|
|
5016
|
-
if (result.removed.length > 0) {
|
|
5017
|
-
console.log(` - ${result.removed.length} removed: ${result.removed.join(", ")}`);
|
|
5018
|
-
}
|
|
5019
|
-
if (result.unchanged.length > 0) {
|
|
5020
|
-
console.log(` = ${result.unchanged.length} unchanged`);
|
|
5021
|
-
}
|
|
5022
|
-
if (opts.dryRun) {
|
|
5023
|
-
console.log(" (dry run \u2014 no files written)");
|
|
5024
|
-
}
|
|
5025
|
-
}
|
|
5026
|
-
} catch (error) {
|
|
5027
|
-
handleError(error);
|
|
5028
|
-
}
|
|
5029
|
-
});
|
|
5030
|
-
}
|
|
5031
|
-
|
|
5032
2799
|
// src/commands/generate.ts
|
|
5033
|
-
import { Command as
|
|
2800
|
+
import { Command as Command35 } from "commander";
|
|
5034
2801
|
function createGenerateCommand3() {
|
|
5035
|
-
return new
|
|
2802
|
+
return new Command35("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) => {
|
|
5036
2803
|
const globalOpts = cmd.optsWithGlobals();
|
|
5037
2804
|
const platforms = opts.platforms.split(",").map((p) => p.trim());
|
|
5038
2805
|
for (const p of platforms) {
|
|
@@ -5091,10 +2858,10 @@ function createGenerateCommand3() {
|
|
|
5091
2858
|
}
|
|
5092
2859
|
|
|
5093
2860
|
// src/commands/graph/scan.ts
|
|
5094
|
-
import { Command as
|
|
5095
|
-
import * as
|
|
2861
|
+
import { Command as Command36 } from "commander";
|
|
2862
|
+
import * as path23 from "path";
|
|
5096
2863
|
async function runScan(projectPath) {
|
|
5097
|
-
const { GraphStore, CodeIngestor, TopologicalLinker, KnowledgeIngestor, GitIngestor } = await import("./dist-
|
|
2864
|
+
const { GraphStore, CodeIngestor, TopologicalLinker, KnowledgeIngestor, GitIngestor } = await import("./dist-I7DB5VKB.js");
|
|
5098
2865
|
const store = new GraphStore();
|
|
5099
2866
|
const start = Date.now();
|
|
5100
2867
|
await new CodeIngestor(store).ingest(projectPath);
|
|
@@ -5105,13 +2872,13 @@ async function runScan(projectPath) {
|
|
|
5105
2872
|
await new GitIngestor(store).ingest(projectPath);
|
|
5106
2873
|
} catch {
|
|
5107
2874
|
}
|
|
5108
|
-
const graphDir =
|
|
2875
|
+
const graphDir = path23.join(projectPath, ".harness", "graph");
|
|
5109
2876
|
await store.save(graphDir);
|
|
5110
2877
|
return { nodeCount: store.nodeCount, edgeCount: store.edgeCount, durationMs: Date.now() - start };
|
|
5111
2878
|
}
|
|
5112
2879
|
function createScanCommand() {
|
|
5113
|
-
return new
|
|
5114
|
-
const projectPath =
|
|
2880
|
+
return new Command36("scan").description("Scan project and build knowledge graph").argument("[path]", "Project root path", ".").action(async (inputPath, _opts, cmd) => {
|
|
2881
|
+
const projectPath = path23.resolve(inputPath);
|
|
5115
2882
|
const globalOpts = cmd.optsWithGlobals();
|
|
5116
2883
|
try {
|
|
5117
2884
|
const result = await runScan(projectPath);
|
|
@@ -5130,13 +2897,13 @@ function createScanCommand() {
|
|
|
5130
2897
|
}
|
|
5131
2898
|
|
|
5132
2899
|
// src/commands/graph/ingest.ts
|
|
5133
|
-
import { Command as
|
|
5134
|
-
import * as
|
|
2900
|
+
import { Command as Command37 } from "commander";
|
|
2901
|
+
import * as path24 from "path";
|
|
5135
2902
|
async function loadConnectorConfig(projectPath, source) {
|
|
5136
2903
|
try {
|
|
5137
|
-
const
|
|
5138
|
-
const configPath =
|
|
5139
|
-
const config = JSON.parse(await
|
|
2904
|
+
const fs11 = await import("fs/promises");
|
|
2905
|
+
const configPath = path24.join(projectPath, "harness.config.json");
|
|
2906
|
+
const config = JSON.parse(await fs11.readFile(configPath, "utf-8"));
|
|
5140
2907
|
const connector = config.graph?.connectors?.find(
|
|
5141
2908
|
(c) => c.source === source
|
|
5142
2909
|
);
|
|
@@ -5175,8 +2942,8 @@ async function runIngest(projectPath, source, opts) {
|
|
|
5175
2942
|
SyncManager,
|
|
5176
2943
|
JiraConnector,
|
|
5177
2944
|
SlackConnector
|
|
5178
|
-
} = await import("./dist-
|
|
5179
|
-
const graphDir =
|
|
2945
|
+
} = await import("./dist-I7DB5VKB.js");
|
|
2946
|
+
const graphDir = path24.join(projectPath, ".harness", "graph");
|
|
5180
2947
|
const store = new GraphStore();
|
|
5181
2948
|
await store.load(graphDir);
|
|
5182
2949
|
if (opts?.all) {
|
|
@@ -5237,13 +3004,13 @@ async function runIngest(projectPath, source, opts) {
|
|
|
5237
3004
|
return result;
|
|
5238
3005
|
}
|
|
5239
3006
|
function createIngestCommand() {
|
|
5240
|
-
return new
|
|
3007
|
+
return new Command37("ingest").description("Ingest data into the knowledge graph").option("--source <name>", "Source to ingest (code, knowledge, git, jira, slack)").option("--all", "Run all sources (code, knowledge, git, and configured connectors)").option("--full", "Force full re-ingestion").action(async (opts, cmd) => {
|
|
5241
3008
|
if (!opts.source && !opts.all) {
|
|
5242
3009
|
console.error("Error: --source or --all is required");
|
|
5243
3010
|
process.exit(1);
|
|
5244
3011
|
}
|
|
5245
3012
|
const globalOpts = cmd.optsWithGlobals();
|
|
5246
|
-
const projectPath =
|
|
3013
|
+
const projectPath = path24.resolve(globalOpts.config ? path24.dirname(globalOpts.config) : ".");
|
|
5247
3014
|
try {
|
|
5248
3015
|
const result = await runIngest(projectPath, opts.source ?? "", {
|
|
5249
3016
|
full: opts.full,
|
|
@@ -5265,12 +3032,12 @@ function createIngestCommand() {
|
|
|
5265
3032
|
}
|
|
5266
3033
|
|
|
5267
3034
|
// src/commands/graph/query.ts
|
|
5268
|
-
import { Command as
|
|
5269
|
-
import * as
|
|
3035
|
+
import { Command as Command38 } from "commander";
|
|
3036
|
+
import * as path25 from "path";
|
|
5270
3037
|
async function runQuery(projectPath, rootNodeId, opts) {
|
|
5271
|
-
const { GraphStore, ContextQL } = await import("./dist-
|
|
3038
|
+
const { GraphStore, ContextQL } = await import("./dist-I7DB5VKB.js");
|
|
5272
3039
|
const store = new GraphStore();
|
|
5273
|
-
const graphDir =
|
|
3040
|
+
const graphDir = path25.join(projectPath, ".harness", "graph");
|
|
5274
3041
|
const loaded = await store.load(graphDir);
|
|
5275
3042
|
if (!loaded) throw new Error("No graph found. Run `harness scan` first.");
|
|
5276
3043
|
const params = {
|
|
@@ -5284,9 +3051,9 @@ async function runQuery(projectPath, rootNodeId, opts) {
|
|
|
5284
3051
|
return cql.execute(params);
|
|
5285
3052
|
}
|
|
5286
3053
|
function createQueryCommand() {
|
|
5287
|
-
return new
|
|
3054
|
+
return new Command38("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) => {
|
|
5288
3055
|
const globalOpts = cmd.optsWithGlobals();
|
|
5289
|
-
const projectPath =
|
|
3056
|
+
const projectPath = path25.resolve(globalOpts.config ? path25.dirname(globalOpts.config) : ".");
|
|
5290
3057
|
try {
|
|
5291
3058
|
const result = await runQuery(projectPath, rootNodeId, {
|
|
5292
3059
|
depth: parseInt(opts.depth),
|
|
@@ -5312,21 +3079,21 @@ function createQueryCommand() {
|
|
|
5312
3079
|
}
|
|
5313
3080
|
|
|
5314
3081
|
// src/commands/graph/index.ts
|
|
5315
|
-
import { Command as
|
|
3082
|
+
import { Command as Command39 } from "commander";
|
|
5316
3083
|
|
|
5317
3084
|
// src/commands/graph/status.ts
|
|
5318
|
-
import * as
|
|
3085
|
+
import * as path26 from "path";
|
|
5319
3086
|
async function runGraphStatus(projectPath) {
|
|
5320
|
-
const { GraphStore } = await import("./dist-
|
|
5321
|
-
const graphDir =
|
|
3087
|
+
const { GraphStore } = await import("./dist-I7DB5VKB.js");
|
|
3088
|
+
const graphDir = path26.join(projectPath, ".harness", "graph");
|
|
5322
3089
|
const store = new GraphStore();
|
|
5323
3090
|
const loaded = await store.load(graphDir);
|
|
5324
3091
|
if (!loaded) return { status: "no_graph", message: "No graph found. Run `harness scan` first." };
|
|
5325
|
-
const
|
|
5326
|
-
const metaPath =
|
|
3092
|
+
const fs11 = await import("fs/promises");
|
|
3093
|
+
const metaPath = path26.join(graphDir, "metadata.json");
|
|
5327
3094
|
let lastScan = "unknown";
|
|
5328
3095
|
try {
|
|
5329
|
-
const meta = JSON.parse(await
|
|
3096
|
+
const meta = JSON.parse(await fs11.readFile(metaPath, "utf-8"));
|
|
5330
3097
|
lastScan = meta.lastScanTimestamp;
|
|
5331
3098
|
} catch {
|
|
5332
3099
|
}
|
|
@@ -5337,8 +3104,8 @@ async function runGraphStatus(projectPath) {
|
|
|
5337
3104
|
}
|
|
5338
3105
|
let connectorSyncStatus = {};
|
|
5339
3106
|
try {
|
|
5340
|
-
const syncMetaPath =
|
|
5341
|
-
const syncMeta = JSON.parse(await
|
|
3107
|
+
const syncMetaPath = path26.join(graphDir, "sync-metadata.json");
|
|
3108
|
+
const syncMeta = JSON.parse(await fs11.readFile(syncMetaPath, "utf-8"));
|
|
5342
3109
|
for (const [name, data] of Object.entries(syncMeta.connectors ?? {})) {
|
|
5343
3110
|
connectorSyncStatus[name] = data.lastSyncTimestamp;
|
|
5344
3111
|
}
|
|
@@ -5355,10 +3122,10 @@ async function runGraphStatus(projectPath) {
|
|
|
5355
3122
|
}
|
|
5356
3123
|
|
|
5357
3124
|
// src/commands/graph/export.ts
|
|
5358
|
-
import * as
|
|
3125
|
+
import * as path27 from "path";
|
|
5359
3126
|
async function runGraphExport(projectPath, format) {
|
|
5360
|
-
const { GraphStore } = await import("./dist-
|
|
5361
|
-
const graphDir =
|
|
3127
|
+
const { GraphStore } = await import("./dist-I7DB5VKB.js");
|
|
3128
|
+
const graphDir = path27.join(projectPath, ".harness", "graph");
|
|
5362
3129
|
const store = new GraphStore();
|
|
5363
3130
|
const loaded = await store.load(graphDir);
|
|
5364
3131
|
if (!loaded) throw new Error("No graph found. Run `harness scan` first.");
|
|
@@ -5387,13 +3154,13 @@ async function runGraphExport(projectPath, format) {
|
|
|
5387
3154
|
}
|
|
5388
3155
|
|
|
5389
3156
|
// src/commands/graph/index.ts
|
|
5390
|
-
import * as
|
|
3157
|
+
import * as path28 from "path";
|
|
5391
3158
|
function createGraphCommand() {
|
|
5392
|
-
const graph = new
|
|
3159
|
+
const graph = new Command39("graph").description("Knowledge graph management");
|
|
5393
3160
|
graph.command("status").description("Show graph statistics").action(async (_opts, cmd) => {
|
|
5394
3161
|
try {
|
|
5395
3162
|
const globalOpts = cmd.optsWithGlobals();
|
|
5396
|
-
const projectPath =
|
|
3163
|
+
const projectPath = path28.resolve(globalOpts.config ? path28.dirname(globalOpts.config) : ".");
|
|
5397
3164
|
const result = await runGraphStatus(projectPath);
|
|
5398
3165
|
if (globalOpts.json) {
|
|
5399
3166
|
console.log(JSON.stringify(result, null, 2));
|
|
@@ -5420,7 +3187,7 @@ function createGraphCommand() {
|
|
|
5420
3187
|
});
|
|
5421
3188
|
graph.command("export").description("Export graph").requiredOption("--format <format>", "Output format (json, mermaid)").action(async (opts, cmd) => {
|
|
5422
3189
|
const globalOpts = cmd.optsWithGlobals();
|
|
5423
|
-
const projectPath =
|
|
3190
|
+
const projectPath = path28.resolve(globalOpts.config ? path28.dirname(globalOpts.config) : ".");
|
|
5424
3191
|
try {
|
|
5425
3192
|
const output = await runGraphExport(projectPath, opts.format);
|
|
5426
3193
|
console.log(output);
|
|
@@ -5432,9 +3199,18 @@ function createGraphCommand() {
|
|
|
5432
3199
|
return graph;
|
|
5433
3200
|
}
|
|
5434
3201
|
|
|
3202
|
+
// src/commands/mcp.ts
|
|
3203
|
+
import { Command as Command40 } from "commander";
|
|
3204
|
+
function createMcpCommand() {
|
|
3205
|
+
return new Command40("mcp").description("Start the MCP (Model Context Protocol) server on stdio").action(async () => {
|
|
3206
|
+
const { startServer: startServer2 } = await import("./mcp-BNLBTCXZ.js");
|
|
3207
|
+
await startServer2();
|
|
3208
|
+
});
|
|
3209
|
+
}
|
|
3210
|
+
|
|
5435
3211
|
// src/index.ts
|
|
5436
3212
|
function createProgram() {
|
|
5437
|
-
const program = new
|
|
3213
|
+
const program = new Command41();
|
|
5438
3214
|
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");
|
|
5439
3215
|
program.addCommand(createValidateCommand());
|
|
5440
3216
|
program.addCommand(createCheckDepsCommand());
|
|
@@ -5463,36 +3239,12 @@ function createProgram() {
|
|
|
5463
3239
|
program.addCommand(createIngestCommand());
|
|
5464
3240
|
program.addCommand(createQueryCommand());
|
|
5465
3241
|
program.addCommand(createGraphCommand());
|
|
3242
|
+
program.addCommand(createMcpCommand());
|
|
5466
3243
|
return program;
|
|
5467
3244
|
}
|
|
5468
3245
|
|
|
5469
3246
|
export {
|
|
5470
|
-
CLI_VERSION,
|
|
5471
|
-
findConfigFile,
|
|
5472
|
-
loadConfig,
|
|
5473
|
-
resolveConfig,
|
|
5474
|
-
OutputMode,
|
|
5475
|
-
OutputFormatter,
|
|
5476
|
-
TemplateEngine,
|
|
5477
|
-
loadPersona,
|
|
5478
|
-
listPersonas,
|
|
5479
|
-
detectTrigger,
|
|
5480
|
-
runPersona,
|
|
5481
|
-
executeSkill,
|
|
5482
|
-
ALLOWED_PERSONA_COMMANDS,
|
|
5483
|
-
generateRuntime,
|
|
5484
|
-
generateAgentsMd,
|
|
5485
|
-
generateCIWorkflow,
|
|
5486
3247
|
buildPreamble,
|
|
5487
|
-
runCheckPhaseGate,
|
|
5488
|
-
generateSlashCommands,
|
|
5489
|
-
AGENT_DESCRIPTIONS,
|
|
5490
|
-
DEFAULT_TOOLS,
|
|
5491
|
-
GEMINI_TOOL_MAP,
|
|
5492
|
-
generateAgentDefinition,
|
|
5493
|
-
renderClaudeCodeAgent,
|
|
5494
|
-
renderGeminiAgent,
|
|
5495
|
-
generateAgentDefinitions,
|
|
5496
3248
|
runScan,
|
|
5497
3249
|
runIngest,
|
|
5498
3250
|
runQuery,
|