@harness-engineering/cli 1.25.1 → 1.25.3
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/bin/harness-mcp.js +1 -1
- package/dist/bin/harness.js +93 -2
- package/dist/{chunk-CJXVNDZZ.js → chunk-6WFSSYZG.js} +25 -8
- package/dist/{chunk-RX7TUMBR.js → chunk-IAMXXLUT.js} +198 -198
- package/dist/index.js +2 -2
- package/dist/{mcp-2553PNUC.js → mcp-PMMA53ZX.js} +1 -1
- package/dist/templates/orchestrator/template.json +1 -1
- package/package.json +4 -4
- /package/dist/templates/orchestrator/{WORKFLOW.md → harness.orchestrator.md} +0 -0
package/dist/bin/harness-mcp.js
CHANGED
package/dist/bin/harness.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import {
|
|
3
3
|
createProgram,
|
|
4
4
|
printFirstRunWelcome
|
|
5
|
-
} from "../chunk-
|
|
5
|
+
} from "../chunk-6WFSSYZG.js";
|
|
6
6
|
import "../chunk-5BQ5BOJL.js";
|
|
7
7
|
import "../chunk-EHRZMOQ2.js";
|
|
8
8
|
import "../chunk-XTITAVUR.js";
|
|
@@ -19,7 +19,7 @@ import "../chunk-P7PANON5.js";
|
|
|
19
19
|
import "../chunk-4NK7Z3BP.js";
|
|
20
20
|
import {
|
|
21
21
|
dispatchSkillsFromGit
|
|
22
|
-
} from "../chunk-
|
|
22
|
+
} from "../chunk-IAMXXLUT.js";
|
|
23
23
|
import "../chunk-FES2YEQU.js";
|
|
24
24
|
import "../chunk-UV3BZMGT.js";
|
|
25
25
|
import "../chunk-F23H3U5U.js";
|
|
@@ -113,6 +113,96 @@ ${message}
|
|
|
113
113
|
}
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
+
// src/bin/command-telemetry.ts
|
|
117
|
+
import { existsSync, readFileSync, mkdirSync, appendFileSync, writeFileSync } from "fs";
|
|
118
|
+
import { join, parse as parsePath, resolve } from "path";
|
|
119
|
+
import { spawn } from "child_process";
|
|
120
|
+
var EXCLUDED_COMMANDS = /* @__PURE__ */ new Set(["help", "completion"]);
|
|
121
|
+
function findProjectRoot(cwd) {
|
|
122
|
+
let dir = resolve(cwd);
|
|
123
|
+
const { root } = parsePath(dir);
|
|
124
|
+
while (dir !== root) {
|
|
125
|
+
if (existsSync(join(dir, "harness.config.json"))) return dir;
|
|
126
|
+
dir = resolve(dir, "..");
|
|
127
|
+
}
|
|
128
|
+
return cwd;
|
|
129
|
+
}
|
|
130
|
+
var commandName = "";
|
|
131
|
+
var startTime = 0;
|
|
132
|
+
var recorded = false;
|
|
133
|
+
function installCommandTelemetry(program, cwd) {
|
|
134
|
+
if (typeof program.hook !== "function") return;
|
|
135
|
+
const projectRoot = findProjectRoot(cwd);
|
|
136
|
+
flushTelemetryBackground(projectRoot);
|
|
137
|
+
program.hook("preAction", (thisCommand) => {
|
|
138
|
+
commandName = resolveCommandName(thisCommand);
|
|
139
|
+
startTime = Date.now();
|
|
140
|
+
});
|
|
141
|
+
process.on("exit", (code) => {
|
|
142
|
+
if (recorded || !commandName || EXCLUDED_COMMANDS.has(commandName)) return;
|
|
143
|
+
recorded = true;
|
|
144
|
+
const duration = Date.now() - startTime;
|
|
145
|
+
const outcome = code === 0 ? "completed" : "failed";
|
|
146
|
+
writeCommandRecordSync(projectRoot, commandName, duration, outcome);
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
function resolveCommandName(cmd) {
|
|
150
|
+
const parts = [];
|
|
151
|
+
let current = cmd;
|
|
152
|
+
while (current) {
|
|
153
|
+
const name = current.name();
|
|
154
|
+
if (name && name !== "harness") {
|
|
155
|
+
parts.unshift(name);
|
|
156
|
+
}
|
|
157
|
+
current = current.parent;
|
|
158
|
+
}
|
|
159
|
+
return parts.length > 0 ? `cli/${parts.join(".")}` : "";
|
|
160
|
+
}
|
|
161
|
+
function writeCommandRecordSync(cwd, command, duration, outcome) {
|
|
162
|
+
try {
|
|
163
|
+
const metricsDir = join(cwd, ".harness", "metrics");
|
|
164
|
+
mkdirSync(metricsDir, { recursive: true });
|
|
165
|
+
const record = {
|
|
166
|
+
skill: command,
|
|
167
|
+
session: `cli-${Date.now()}`,
|
|
168
|
+
startedAt: new Date(Date.now() - duration).toISOString(),
|
|
169
|
+
duration,
|
|
170
|
+
outcome,
|
|
171
|
+
phasesReached: []
|
|
172
|
+
};
|
|
173
|
+
const adoptionFile = join(metricsDir, "adoption.jsonl");
|
|
174
|
+
appendFileSync(adoptionFile, JSON.stringify(record) + "\n");
|
|
175
|
+
} catch {
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
function flushTelemetryBackground(cwd) {
|
|
179
|
+
try {
|
|
180
|
+
const adoptionFile = join(cwd, ".harness", "metrics", "adoption.jsonl");
|
|
181
|
+
if (!existsSync(adoptionFile)) return;
|
|
182
|
+
const configPath = join(cwd, "harness.config.json");
|
|
183
|
+
if (existsSync(configPath)) {
|
|
184
|
+
try {
|
|
185
|
+
const config = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
186
|
+
if (config?.telemetry?.enabled === false) return;
|
|
187
|
+
} catch {
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
if (process.env.DO_NOT_TRACK === "1") return;
|
|
191
|
+
if (process.env.HARNESS_TELEMETRY_OPTOUT === "1") return;
|
|
192
|
+
const reporterPath = join(cwd, ".harness", "hooks", "telemetry-reporter.js");
|
|
193
|
+
if (!existsSync(reporterPath)) return;
|
|
194
|
+
const child = spawn(process.execPath, [reporterPath], {
|
|
195
|
+
cwd,
|
|
196
|
+
stdio: ["pipe", "ignore", "ignore"],
|
|
197
|
+
detached: true
|
|
198
|
+
});
|
|
199
|
+
child.stdin?.write("{}");
|
|
200
|
+
child.stdin?.end();
|
|
201
|
+
child.unref();
|
|
202
|
+
} catch {
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
116
206
|
// src/skill/dispatch-session.ts
|
|
117
207
|
import { execSync } from "child_process";
|
|
118
208
|
import fs from "fs";
|
|
@@ -211,6 +301,7 @@ async function main() {
|
|
|
211
301
|
() => ({ dispatched: false })
|
|
212
302
|
);
|
|
213
303
|
const program = createProgram();
|
|
304
|
+
installCommandTelemetry(program, process.cwd());
|
|
214
305
|
try {
|
|
215
306
|
await program.parseAsync(process.argv);
|
|
216
307
|
} catch (error) {
|
|
@@ -56,7 +56,7 @@ import {
|
|
|
56
56
|
loadOrRebuildIndex,
|
|
57
57
|
persistToolingConfig,
|
|
58
58
|
recommend
|
|
59
|
-
} from "./chunk-
|
|
59
|
+
} from "./chunk-IAMXXLUT.js";
|
|
60
60
|
import {
|
|
61
61
|
findConfigFile,
|
|
62
62
|
resolveConfig
|
|
@@ -3137,7 +3137,12 @@ function buildSettingsHooks(profile) {
|
|
|
3137
3137
|
}
|
|
3138
3138
|
hooks[script.event].push({
|
|
3139
3139
|
matcher: script.matcher,
|
|
3140
|
-
hooks: [
|
|
3140
|
+
hooks: [
|
|
3141
|
+
{
|
|
3142
|
+
type: "command",
|
|
3143
|
+
command: `node "$(git rev-parse --show-toplevel)/.harness/hooks/${script.name}.js"`
|
|
3144
|
+
}
|
|
3145
|
+
]
|
|
3141
3146
|
});
|
|
3142
3147
|
}
|
|
3143
3148
|
return hooks;
|
|
@@ -3355,7 +3360,7 @@ function readJson(p) {
|
|
|
3355
3360
|
}
|
|
3356
3361
|
function registerHook(s, ev, matcher, name) {
|
|
3357
3362
|
if (!s.hooks[ev]) s.hooks[ev] = [];
|
|
3358
|
-
const cmd = `node
|
|
3363
|
+
const cmd = `node "$(git rev-parse --show-toplevel)/.harness/hooks/${name}.js"`;
|
|
3359
3364
|
if (!s.hooks[ev].some((e) => e.hooks?.some((h) => h.command === cmd))) {
|
|
3360
3365
|
s.hooks[ev].push({ matcher, hooks: [{ type: "command", command: cmd }] });
|
|
3361
3366
|
}
|
|
@@ -5418,7 +5423,7 @@ function createMcpCommand() {
|
|
|
5418
5423
|
parseBudget
|
|
5419
5424
|
).action(async (opts) => {
|
|
5420
5425
|
const [{ startServer: startServer2, getToolDefinitions: getToolDefinitions2 }, { selectTier }] = await Promise.all([
|
|
5421
|
-
import("./mcp-
|
|
5426
|
+
import("./mcp-PMMA53ZX.js"),
|
|
5422
5427
|
import("./tool-tiers-7QGZ3FKY.js")
|
|
5423
5428
|
]);
|
|
5424
5429
|
if (opts.tools && opts.tools.length > 0) {
|
|
@@ -5444,7 +5449,7 @@ import * as path39 from "path";
|
|
|
5444
5449
|
import { Orchestrator, WorkflowLoader, launchTUI } from "@harness-engineering/orchestrator";
|
|
5445
5450
|
function createOrchestratorCommand() {
|
|
5446
5451
|
const orchestrator = new Command47("orchestrator");
|
|
5447
|
-
orchestrator.command("run").description("Run the orchestrator daemon").option("-w, --workflow <path>", "Path to
|
|
5452
|
+
orchestrator.command("run").description("Run the orchestrator daemon").option("-w, --workflow <path>", "Path to harness.orchestrator.md", "harness.orchestrator.md").option("--headless", "Run without TUI (server-only mode for use with web dashboard)").action(async (opts) => {
|
|
5448
5453
|
const workflowPath = path39.resolve(process.cwd(), opts.workflow);
|
|
5449
5454
|
const loader = new WorkflowLoader();
|
|
5450
5455
|
const result = await loader.loadWorkflow(workflowPath);
|
|
@@ -6272,6 +6277,18 @@ function prompt(question) {
|
|
|
6272
6277
|
});
|
|
6273
6278
|
});
|
|
6274
6279
|
}
|
|
6280
|
+
function promptRaw(question) {
|
|
6281
|
+
const rl = readline.createInterface({
|
|
6282
|
+
input: process.stdin,
|
|
6283
|
+
output: process.stdout
|
|
6284
|
+
});
|
|
6285
|
+
return new Promise((resolve35) => {
|
|
6286
|
+
rl.question(question, (answer) => {
|
|
6287
|
+
rl.close();
|
|
6288
|
+
resolve35(answer.trim());
|
|
6289
|
+
});
|
|
6290
|
+
});
|
|
6291
|
+
}
|
|
6275
6292
|
function isNo(answer) {
|
|
6276
6293
|
return answer === "n" || answer === "no";
|
|
6277
6294
|
}
|
|
@@ -6312,11 +6329,11 @@ async function promptIdentity() {
|
|
|
6312
6329
|
const answer = await prompt(" Set identity fields? (y/N) ");
|
|
6313
6330
|
const identity = {};
|
|
6314
6331
|
if (answer === "y" || answer === "yes") {
|
|
6315
|
-
const projectName = await
|
|
6332
|
+
const projectName = await promptRaw(" Project name: ");
|
|
6316
6333
|
if (projectName) identity.project = projectName;
|
|
6317
|
-
const teamName = await
|
|
6334
|
+
const teamName = await promptRaw(" Team name: ");
|
|
6318
6335
|
if (teamName) identity.team = teamName;
|
|
6319
|
-
const alias = await
|
|
6336
|
+
const alias = await promptRaw(" Alias: ");
|
|
6320
6337
|
if (alias) identity.alias = alias;
|
|
6321
6338
|
}
|
|
6322
6339
|
return identity;
|
|
@@ -122,6 +122,203 @@ import {
|
|
|
122
122
|
ReadResourceRequestSchema
|
|
123
123
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
124
124
|
|
|
125
|
+
// src/templates/post-write.ts
|
|
126
|
+
import * as fs from "fs";
|
|
127
|
+
import * as path from "path";
|
|
128
|
+
|
|
129
|
+
// src/templates/agents-append.ts
|
|
130
|
+
var FRAMEWORK_SECTIONS = {
|
|
131
|
+
nextjs: {
|
|
132
|
+
title: "Next.js Conventions",
|
|
133
|
+
content: [
|
|
134
|
+
"- Use the App Router (`src/app/`) for all routes",
|
|
135
|
+
'- Server Components by default; add `"use client"` only when needed',
|
|
136
|
+
"- Use `next/image` for images and `next/link` for navigation",
|
|
137
|
+
"- API routes go in `src/app/api/`",
|
|
138
|
+
"- Run `next dev` for development, `next build` for production"
|
|
139
|
+
].join("\n")
|
|
140
|
+
},
|
|
141
|
+
"react-vite": {
|
|
142
|
+
title: "React + Vite Conventions",
|
|
143
|
+
content: [
|
|
144
|
+
"- Component files use `.tsx` extension in `src/`",
|
|
145
|
+
"- Use Vite for dev server and bundling (`npm run dev`)",
|
|
146
|
+
"- Prefer function components with hooks",
|
|
147
|
+
"- CSS modules or styled-components for styling",
|
|
148
|
+
"- Tests use Vitest (`npm test`)"
|
|
149
|
+
].join("\n")
|
|
150
|
+
},
|
|
151
|
+
vue: {
|
|
152
|
+
title: "Vue Conventions",
|
|
153
|
+
content: [
|
|
154
|
+
"- Single File Components (`.vue`) in `src/`",
|
|
155
|
+
"- Use `<script setup>` with Composition API",
|
|
156
|
+
"- Vite for dev server and bundling (`npm run dev`)",
|
|
157
|
+
"- Vue Router for routing, Pinia for state management",
|
|
158
|
+
"- Tests use Vitest (`npm test`)"
|
|
159
|
+
].join("\n")
|
|
160
|
+
},
|
|
161
|
+
express: {
|
|
162
|
+
title: "Express Conventions",
|
|
163
|
+
content: [
|
|
164
|
+
"- Entry point at `src/app.ts`",
|
|
165
|
+
"- Routes in `src/routes/`, middleware in `src/middleware/`",
|
|
166
|
+
"- Use `express.json()` for body parsing",
|
|
167
|
+
"- Error handling via centralized error middleware",
|
|
168
|
+
"- Tests use Vitest with supertest (`npm test`)"
|
|
169
|
+
].join("\n")
|
|
170
|
+
},
|
|
171
|
+
nestjs: {
|
|
172
|
+
title: "NestJS Conventions",
|
|
173
|
+
content: [
|
|
174
|
+
"- Module-based architecture: each feature in its own module",
|
|
175
|
+
"- Use decorators (`@Controller`, `@Injectable`, `@Module`)",
|
|
176
|
+
"- Entry point at `src/main.ts`, root module at `src/app.module.ts`",
|
|
177
|
+
"- Use Nest CLI for generating components (`nest g`)",
|
|
178
|
+
"- Tests use Vitest (`npm test`)"
|
|
179
|
+
].join("\n")
|
|
180
|
+
},
|
|
181
|
+
fastapi: {
|
|
182
|
+
title: "FastAPI Conventions",
|
|
183
|
+
content: [
|
|
184
|
+
"- Entry point at `src/main.py` with FastAPI app instance",
|
|
185
|
+
"- Use Pydantic models for request/response validation",
|
|
186
|
+
"- Async endpoints preferred; sync is acceptable for CPU-bound work",
|
|
187
|
+
"- Run with `uvicorn src.main:app --reload` for development",
|
|
188
|
+
"- Tests use pytest (`pytest`)"
|
|
189
|
+
].join("\n")
|
|
190
|
+
},
|
|
191
|
+
django: {
|
|
192
|
+
title: "Django Conventions",
|
|
193
|
+
content: [
|
|
194
|
+
"- Settings at `src/settings.py`, URLs at `src/urls.py`",
|
|
195
|
+
"- Use `manage.py` for management commands",
|
|
196
|
+
"- Apps in `src/` directory; each app has models, views, urls",
|
|
197
|
+
"- Run with `python manage.py runserver` for development",
|
|
198
|
+
"- Tests use pytest with pytest-django (`pytest`)"
|
|
199
|
+
].join("\n")
|
|
200
|
+
},
|
|
201
|
+
gin: {
|
|
202
|
+
title: "Gin Conventions",
|
|
203
|
+
content: [
|
|
204
|
+
"- Entry point at `main.go` with Gin router setup",
|
|
205
|
+
"- Group routes by feature using `router.Group()`",
|
|
206
|
+
"- Use middleware for logging, auth, error recovery",
|
|
207
|
+
"- Run with `go run main.go` for development",
|
|
208
|
+
"- Tests use `go test ./...`"
|
|
209
|
+
].join("\n")
|
|
210
|
+
},
|
|
211
|
+
axum: {
|
|
212
|
+
title: "Axum Conventions",
|
|
213
|
+
content: [
|
|
214
|
+
"- Entry point at `src/main.rs` with Axum router",
|
|
215
|
+
"- Use extractors for request parsing (`Path`, `Query`, `Json`)",
|
|
216
|
+
"- Shared state via `Extension` or `State`",
|
|
217
|
+
"- Run with `cargo run` for development",
|
|
218
|
+
"- Tests use `cargo test`"
|
|
219
|
+
].join("\n")
|
|
220
|
+
},
|
|
221
|
+
"spring-boot": {
|
|
222
|
+
title: "Spring Boot Conventions",
|
|
223
|
+
content: [
|
|
224
|
+
"- Entry point annotated with `@SpringBootApplication`",
|
|
225
|
+
"- Controllers in `controller/` package, services in `service/`",
|
|
226
|
+
"- Use constructor injection for dependencies",
|
|
227
|
+
"- Run with `mvn spring-boot:run` for development",
|
|
228
|
+
"- Tests use JUnit 5 with Spring Boot Test (`mvn test`)"
|
|
229
|
+
].join("\n")
|
|
230
|
+
}
|
|
231
|
+
};
|
|
232
|
+
function buildFrameworkSection(framework) {
|
|
233
|
+
const entry = FRAMEWORK_SECTIONS[framework];
|
|
234
|
+
if (!entry) return "";
|
|
235
|
+
return `## ${entry.title}
|
|
236
|
+
|
|
237
|
+
<!-- framework: ${framework} -->
|
|
238
|
+
${entry.content}
|
|
239
|
+
`;
|
|
240
|
+
}
|
|
241
|
+
function appendFrameworkSection(existingContent, framework, _language) {
|
|
242
|
+
if (!framework) return existingContent;
|
|
243
|
+
const startMarker = `<!-- harness:framework-conventions:${framework} -->`;
|
|
244
|
+
const endMarker = `<!-- /harness:framework-conventions:${framework} -->`;
|
|
245
|
+
if (existingContent.includes(startMarker)) return existingContent;
|
|
246
|
+
const section = buildFrameworkSection(framework);
|
|
247
|
+
if (!section) return existingContent;
|
|
248
|
+
const block = `
|
|
249
|
+
${startMarker}
|
|
250
|
+
${section}${endMarker}
|
|
251
|
+
`;
|
|
252
|
+
return existingContent.trimEnd() + "\n" + block;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// src/templates/post-write.ts
|
|
256
|
+
function persistToolingConfig(targetDir, resolveResult, framework) {
|
|
257
|
+
const configPath = path.join(targetDir, "harness.config.json");
|
|
258
|
+
if (!fs.existsSync(configPath)) return;
|
|
259
|
+
try {
|
|
260
|
+
const config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
|
|
261
|
+
const overlayMeta = resolveResult.overlayMetadata;
|
|
262
|
+
if (framework) {
|
|
263
|
+
config.template = config.template || {};
|
|
264
|
+
config.template.framework = framework;
|
|
265
|
+
}
|
|
266
|
+
if (overlayMeta?.tooling) {
|
|
267
|
+
config.tooling = { ...config.tooling, ...overlayMeta.tooling };
|
|
268
|
+
delete config.tooling.lockFile;
|
|
269
|
+
} else if (resolveResult.metadata.tooling && !config.tooling) {
|
|
270
|
+
config.tooling = { ...resolveResult.metadata.tooling };
|
|
271
|
+
delete config.tooling.lockFile;
|
|
272
|
+
}
|
|
273
|
+
if (config.template?.level === null || config.template?.level === void 0) {
|
|
274
|
+
delete config.template.level;
|
|
275
|
+
}
|
|
276
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
277
|
+
} catch {
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
function ensureHarnessGitignore(targetDir) {
|
|
281
|
+
const gitignorePath = path.join(targetDir, ".harness", ".gitignore");
|
|
282
|
+
const content = `# Runtime artifacts (generated, ephemeral, session-scoped)
|
|
283
|
+
graph/
|
|
284
|
+
debug/
|
|
285
|
+
hooks/
|
|
286
|
+
security/
|
|
287
|
+
sessions/
|
|
288
|
+
state.json
|
|
289
|
+
state/
|
|
290
|
+
handoff.json
|
|
291
|
+
handoff-*.json
|
|
292
|
+
autopilot-state.json
|
|
293
|
+
session-taint-*.json
|
|
294
|
+
dispatch-last-head.txt
|
|
295
|
+
health-snapshot.json
|
|
296
|
+
release-readiness.json
|
|
297
|
+
skills-index.json
|
|
298
|
+
stack-profile.json
|
|
299
|
+
metrics/
|
|
300
|
+
events.jsonl
|
|
301
|
+
.install-id
|
|
302
|
+
telemetry.json
|
|
303
|
+
.telemetry-notice-shown
|
|
304
|
+
`;
|
|
305
|
+
fs.mkdirSync(path.dirname(gitignorePath), { recursive: true });
|
|
306
|
+
fs.writeFileSync(gitignorePath, content);
|
|
307
|
+
}
|
|
308
|
+
function appendFrameworkAgents(targetDir, framework, language) {
|
|
309
|
+
if (!framework) return;
|
|
310
|
+
const agentsPath = path.join(targetDir, "AGENTS.md");
|
|
311
|
+
if (!fs.existsSync(agentsPath)) return;
|
|
312
|
+
try {
|
|
313
|
+
const existing = fs.readFileSync(agentsPath, "utf-8");
|
|
314
|
+
const updated = appendFrameworkSection(existing, framework, language);
|
|
315
|
+
if (updated !== existing) {
|
|
316
|
+
fs.writeFileSync(agentsPath, updated);
|
|
317
|
+
}
|
|
318
|
+
} catch {
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
125
322
|
// src/mcp/middleware/injection-guard.ts
|
|
126
323
|
init_dist();
|
|
127
324
|
import { realpathSync } from "fs";
|
|
@@ -402,204 +599,6 @@ async function handleValidateLinterConfig(input) {
|
|
|
402
599
|
// src/mcp/tools/init.ts
|
|
403
600
|
import * as path2 from "path";
|
|
404
601
|
import * as fs2 from "fs";
|
|
405
|
-
|
|
406
|
-
// src/templates/post-write.ts
|
|
407
|
-
import * as fs from "fs";
|
|
408
|
-
import * as path from "path";
|
|
409
|
-
|
|
410
|
-
// src/templates/agents-append.ts
|
|
411
|
-
var FRAMEWORK_SECTIONS = {
|
|
412
|
-
nextjs: {
|
|
413
|
-
title: "Next.js Conventions",
|
|
414
|
-
content: [
|
|
415
|
-
"- Use the App Router (`src/app/`) for all routes",
|
|
416
|
-
'- Server Components by default; add `"use client"` only when needed',
|
|
417
|
-
"- Use `next/image` for images and `next/link` for navigation",
|
|
418
|
-
"- API routes go in `src/app/api/`",
|
|
419
|
-
"- Run `next dev` for development, `next build` for production"
|
|
420
|
-
].join("\n")
|
|
421
|
-
},
|
|
422
|
-
"react-vite": {
|
|
423
|
-
title: "React + Vite Conventions",
|
|
424
|
-
content: [
|
|
425
|
-
"- Component files use `.tsx` extension in `src/`",
|
|
426
|
-
"- Use Vite for dev server and bundling (`npm run dev`)",
|
|
427
|
-
"- Prefer function components with hooks",
|
|
428
|
-
"- CSS modules or styled-components for styling",
|
|
429
|
-
"- Tests use Vitest (`npm test`)"
|
|
430
|
-
].join("\n")
|
|
431
|
-
},
|
|
432
|
-
vue: {
|
|
433
|
-
title: "Vue Conventions",
|
|
434
|
-
content: [
|
|
435
|
-
"- Single File Components (`.vue`) in `src/`",
|
|
436
|
-
"- Use `<script setup>` with Composition API",
|
|
437
|
-
"- Vite for dev server and bundling (`npm run dev`)",
|
|
438
|
-
"- Vue Router for routing, Pinia for state management",
|
|
439
|
-
"- Tests use Vitest (`npm test`)"
|
|
440
|
-
].join("\n")
|
|
441
|
-
},
|
|
442
|
-
express: {
|
|
443
|
-
title: "Express Conventions",
|
|
444
|
-
content: [
|
|
445
|
-
"- Entry point at `src/app.ts`",
|
|
446
|
-
"- Routes in `src/routes/`, middleware in `src/middleware/`",
|
|
447
|
-
"- Use `express.json()` for body parsing",
|
|
448
|
-
"- Error handling via centralized error middleware",
|
|
449
|
-
"- Tests use Vitest with supertest (`npm test`)"
|
|
450
|
-
].join("\n")
|
|
451
|
-
},
|
|
452
|
-
nestjs: {
|
|
453
|
-
title: "NestJS Conventions",
|
|
454
|
-
content: [
|
|
455
|
-
"- Module-based architecture: each feature in its own module",
|
|
456
|
-
"- Use decorators (`@Controller`, `@Injectable`, `@Module`)",
|
|
457
|
-
"- Entry point at `src/main.ts`, root module at `src/app.module.ts`",
|
|
458
|
-
"- Use Nest CLI for generating components (`nest g`)",
|
|
459
|
-
"- Tests use Vitest (`npm test`)"
|
|
460
|
-
].join("\n")
|
|
461
|
-
},
|
|
462
|
-
fastapi: {
|
|
463
|
-
title: "FastAPI Conventions",
|
|
464
|
-
content: [
|
|
465
|
-
"- Entry point at `src/main.py` with FastAPI app instance",
|
|
466
|
-
"- Use Pydantic models for request/response validation",
|
|
467
|
-
"- Async endpoints preferred; sync is acceptable for CPU-bound work",
|
|
468
|
-
"- Run with `uvicorn src.main:app --reload` for development",
|
|
469
|
-
"- Tests use pytest (`pytest`)"
|
|
470
|
-
].join("\n")
|
|
471
|
-
},
|
|
472
|
-
django: {
|
|
473
|
-
title: "Django Conventions",
|
|
474
|
-
content: [
|
|
475
|
-
"- Settings at `src/settings.py`, URLs at `src/urls.py`",
|
|
476
|
-
"- Use `manage.py` for management commands",
|
|
477
|
-
"- Apps in `src/` directory; each app has models, views, urls",
|
|
478
|
-
"- Run with `python manage.py runserver` for development",
|
|
479
|
-
"- Tests use pytest with pytest-django (`pytest`)"
|
|
480
|
-
].join("\n")
|
|
481
|
-
},
|
|
482
|
-
gin: {
|
|
483
|
-
title: "Gin Conventions",
|
|
484
|
-
content: [
|
|
485
|
-
"- Entry point at `main.go` with Gin router setup",
|
|
486
|
-
"- Group routes by feature using `router.Group()`",
|
|
487
|
-
"- Use middleware for logging, auth, error recovery",
|
|
488
|
-
"- Run with `go run main.go` for development",
|
|
489
|
-
"- Tests use `go test ./...`"
|
|
490
|
-
].join("\n")
|
|
491
|
-
},
|
|
492
|
-
axum: {
|
|
493
|
-
title: "Axum Conventions",
|
|
494
|
-
content: [
|
|
495
|
-
"- Entry point at `src/main.rs` with Axum router",
|
|
496
|
-
"- Use extractors for request parsing (`Path`, `Query`, `Json`)",
|
|
497
|
-
"- Shared state via `Extension` or `State`",
|
|
498
|
-
"- Run with `cargo run` for development",
|
|
499
|
-
"- Tests use `cargo test`"
|
|
500
|
-
].join("\n")
|
|
501
|
-
},
|
|
502
|
-
"spring-boot": {
|
|
503
|
-
title: "Spring Boot Conventions",
|
|
504
|
-
content: [
|
|
505
|
-
"- Entry point annotated with `@SpringBootApplication`",
|
|
506
|
-
"- Controllers in `controller/` package, services in `service/`",
|
|
507
|
-
"- Use constructor injection for dependencies",
|
|
508
|
-
"- Run with `mvn spring-boot:run` for development",
|
|
509
|
-
"- Tests use JUnit 5 with Spring Boot Test (`mvn test`)"
|
|
510
|
-
].join("\n")
|
|
511
|
-
}
|
|
512
|
-
};
|
|
513
|
-
function buildFrameworkSection(framework) {
|
|
514
|
-
const entry = FRAMEWORK_SECTIONS[framework];
|
|
515
|
-
if (!entry) return "";
|
|
516
|
-
return `## ${entry.title}
|
|
517
|
-
|
|
518
|
-
<!-- framework: ${framework} -->
|
|
519
|
-
${entry.content}
|
|
520
|
-
`;
|
|
521
|
-
}
|
|
522
|
-
function appendFrameworkSection(existingContent, framework, _language) {
|
|
523
|
-
if (!framework) return existingContent;
|
|
524
|
-
const startMarker = `<!-- harness:framework-conventions:${framework} -->`;
|
|
525
|
-
const endMarker = `<!-- /harness:framework-conventions:${framework} -->`;
|
|
526
|
-
if (existingContent.includes(startMarker)) return existingContent;
|
|
527
|
-
const section = buildFrameworkSection(framework);
|
|
528
|
-
if (!section) return existingContent;
|
|
529
|
-
const block = `
|
|
530
|
-
${startMarker}
|
|
531
|
-
${section}${endMarker}
|
|
532
|
-
`;
|
|
533
|
-
return existingContent.trimEnd() + "\n" + block;
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
// src/templates/post-write.ts
|
|
537
|
-
function persistToolingConfig(targetDir, resolveResult, framework) {
|
|
538
|
-
const configPath = path.join(targetDir, "harness.config.json");
|
|
539
|
-
if (!fs.existsSync(configPath)) return;
|
|
540
|
-
try {
|
|
541
|
-
const config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
|
|
542
|
-
const overlayMeta = resolveResult.overlayMetadata;
|
|
543
|
-
if (framework) {
|
|
544
|
-
config.template = config.template || {};
|
|
545
|
-
config.template.framework = framework;
|
|
546
|
-
}
|
|
547
|
-
if (overlayMeta?.tooling) {
|
|
548
|
-
config.tooling = { ...config.tooling, ...overlayMeta.tooling };
|
|
549
|
-
delete config.tooling.lockFile;
|
|
550
|
-
} else if (resolveResult.metadata.tooling && !config.tooling) {
|
|
551
|
-
config.tooling = { ...resolveResult.metadata.tooling };
|
|
552
|
-
delete config.tooling.lockFile;
|
|
553
|
-
}
|
|
554
|
-
if (config.template?.level === null || config.template?.level === void 0) {
|
|
555
|
-
delete config.template.level;
|
|
556
|
-
}
|
|
557
|
-
fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
558
|
-
} catch {
|
|
559
|
-
}
|
|
560
|
-
}
|
|
561
|
-
function ensureHarnessGitignore(targetDir) {
|
|
562
|
-
const gitignorePath = path.join(targetDir, ".harness", ".gitignore");
|
|
563
|
-
if (fs.existsSync(gitignorePath)) return;
|
|
564
|
-
const content = `# Runtime artifacts (generated, ephemeral, session-scoped)
|
|
565
|
-
graph/
|
|
566
|
-
debug/
|
|
567
|
-
sessions/
|
|
568
|
-
state.json
|
|
569
|
-
state/
|
|
570
|
-
handoff.json
|
|
571
|
-
handoff-*.json
|
|
572
|
-
autopilot-state.json
|
|
573
|
-
session-taint-*.json
|
|
574
|
-
dispatch-last-head.txt
|
|
575
|
-
health-snapshot.json
|
|
576
|
-
release-readiness.json
|
|
577
|
-
skills-index.json
|
|
578
|
-
stack-profile.json
|
|
579
|
-
metrics/
|
|
580
|
-
events.jsonl
|
|
581
|
-
.install-id
|
|
582
|
-
telemetry.json
|
|
583
|
-
.telemetry-notice-shown
|
|
584
|
-
`;
|
|
585
|
-
fs.mkdirSync(path.dirname(gitignorePath), { recursive: true });
|
|
586
|
-
fs.writeFileSync(gitignorePath, content);
|
|
587
|
-
}
|
|
588
|
-
function appendFrameworkAgents(targetDir, framework, language) {
|
|
589
|
-
if (!framework) return;
|
|
590
|
-
const agentsPath = path.join(targetDir, "AGENTS.md");
|
|
591
|
-
if (!fs.existsSync(agentsPath)) return;
|
|
592
|
-
try {
|
|
593
|
-
const existing = fs.readFileSync(agentsPath, "utf-8");
|
|
594
|
-
const updated = appendFrameworkSection(existing, framework, language);
|
|
595
|
-
if (updated !== existing) {
|
|
596
|
-
fs.writeFileSync(agentsPath, updated);
|
|
597
|
-
}
|
|
598
|
-
} catch {
|
|
599
|
-
}
|
|
600
|
-
}
|
|
601
|
-
|
|
602
|
-
// src/mcp/tools/init.ts
|
|
603
602
|
var initProjectDefinition = {
|
|
604
603
|
name: "init_project",
|
|
605
604
|
description: "Scaffold a new harness engineering project from a template",
|
|
@@ -7512,6 +7511,7 @@ async function handleReadResource(uri, resolvedRoot) {
|
|
|
7512
7511
|
}
|
|
7513
7512
|
function createHarnessServer(projectRoot, toolFilter) {
|
|
7514
7513
|
const resolvedRoot = projectRoot ?? process.cwd();
|
|
7514
|
+
ensureHarnessGitignore(resolvedRoot);
|
|
7515
7515
|
const { definitions, handlers } = buildFilteredTools(toolFilter);
|
|
7516
7516
|
const trustedOutputTools = new Set(
|
|
7517
7517
|
definitions.filter((t) => t.trustedOutput === true).map((t) => t.name)
|
package/dist/index.js
CHANGED
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
runSnapshotCapture,
|
|
13
13
|
runUninstall,
|
|
14
14
|
runUninstallConstraints
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-6WFSSYZG.js";
|
|
16
16
|
import {
|
|
17
17
|
generateAgentsMd
|
|
18
18
|
} from "./chunk-5BQ5BOJL.js";
|
|
@@ -66,7 +66,7 @@ import {
|
|
|
66
66
|
generateSlashCommands,
|
|
67
67
|
getToolDefinitions,
|
|
68
68
|
startServer
|
|
69
|
-
} from "./chunk-
|
|
69
|
+
} from "./chunk-IAMXXLUT.js";
|
|
70
70
|
import "./chunk-FES2YEQU.js";
|
|
71
71
|
import "./chunk-UV3BZMGT.js";
|
|
72
72
|
import "./chunk-F23H3U5U.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@harness-engineering/cli",
|
|
3
|
-
"version": "1.25.
|
|
3
|
+
"version": "1.25.3",
|
|
4
4
|
"description": "CLI for Harness Engineering toolkit",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -38,11 +38,11 @@
|
|
|
38
38
|
"yaml": "^2.8.3",
|
|
39
39
|
"zod": "^3.25.76",
|
|
40
40
|
"@harness-engineering/core": "0.22.0",
|
|
41
|
-
"@harness-engineering/dashboard": "0.1.5",
|
|
42
41
|
"@harness-engineering/graph": "0.4.3",
|
|
43
|
-
"@harness-engineering/orchestrator": "0.2.8",
|
|
44
42
|
"@harness-engineering/linter-gen": "0.1.6",
|
|
45
|
-
"@harness-engineering/
|
|
43
|
+
"@harness-engineering/orchestrator": "0.2.9",
|
|
44
|
+
"@harness-engineering/types": "0.9.2",
|
|
45
|
+
"@harness-engineering/dashboard": "0.1.5"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
48
|
"@types/node": "^22.19.15",
|
|
File without changes
|