@harness-engineering/cli 8.0.0 → 9.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/harness.js +1 -1
- package/dist/{chunk-MSJJU6KH.js → chunk-SHOTI4TA.js} +96 -13
- package/dist/index.d.ts +8 -0
- package/dist/index.js +1 -1
- package/dist/templates/orchestrator/harness.orchestrator.local.md +195 -0
- package/dist/templates/orchestrator/harness.orchestrator.md +5 -0
- package/package.json +8 -8
package/dist/bin/harness.js
CHANGED
|
@@ -3338,7 +3338,7 @@ import { spawn } from "child_process";
|
|
|
3338
3338
|
import { existsSync as existsSync6 } from "fs";
|
|
3339
3339
|
import { createRequire } from "module";
|
|
3340
3340
|
import { dirname as dirname4, join as join10, resolve as resolve15 } from "path";
|
|
3341
|
-
import { setTimeout } from "timers";
|
|
3341
|
+
import { setTimeout as setTimeout2 } from "timers";
|
|
3342
3342
|
import { fileURLToPath } from "url";
|
|
3343
3343
|
var __filename = fileURLToPath(import.meta.url);
|
|
3344
3344
|
var __dirname = dirname4(__filename);
|
|
@@ -3413,7 +3413,7 @@ function runDashboard(opts) {
|
|
|
3413
3413
|
console.log(`Dashboard API starting on http://localhost:${apiPort}`);
|
|
3414
3414
|
console.log(`Open ${url} (pass --no-open to suppress)`);
|
|
3415
3415
|
if (opts.noOpen !== true) {
|
|
3416
|
-
|
|
3416
|
+
setTimeout2(() => openBrowser(url), 1500);
|
|
3417
3417
|
}
|
|
3418
3418
|
}
|
|
3419
3419
|
function createDashboardCommand() {
|
|
@@ -9518,9 +9518,12 @@ function createOrchestratorCommand() {
|
|
|
9518
9518
|
logger.error(`Failed to load workflow: ${result.error.message}`);
|
|
9519
9519
|
process.exit(ExitCode.ERROR);
|
|
9520
9520
|
}
|
|
9521
|
-
const { config, promptTemplate, warnings } = result.value;
|
|
9521
|
+
const { config, promptTemplate, localPromptTemplate, warnings } = result.value;
|
|
9522
9522
|
for (const w of warnings) logger.warn(w);
|
|
9523
|
-
const daemon = new Orchestrator(config, promptTemplate, {
|
|
9523
|
+
const daemon = new Orchestrator(config, promptTemplate, {
|
|
9524
|
+
discoverCandidates,
|
|
9525
|
+
...localPromptTemplate !== void 0 ? { localPromptTemplate } : {}
|
|
9526
|
+
});
|
|
9524
9527
|
const shutdown = () => {
|
|
9525
9528
|
daemon.stop();
|
|
9526
9529
|
process.exit(ExitCode.SUCCESS);
|
|
@@ -11792,7 +11795,7 @@ function firstModel(model) {
|
|
|
11792
11795
|
if (Array.isArray(model)) return model[0];
|
|
11793
11796
|
return model;
|
|
11794
11797
|
}
|
|
11795
|
-
function resolveTriageProvider(config, modelOverride) {
|
|
11798
|
+
function resolveTriageProvider(config, modelOverride, localModelPreference) {
|
|
11796
11799
|
const explicit = config?.intelligence?.provider;
|
|
11797
11800
|
if (explicit) {
|
|
11798
11801
|
if (explicit.kind === "anthropic") {
|
|
@@ -11815,7 +11818,7 @@ function resolveTriageProvider(config, modelOverride) {
|
|
|
11815
11818
|
if (!backends) return null;
|
|
11816
11819
|
const local = pickBackend(backends, ["local", "pi"]);
|
|
11817
11820
|
if (local && local.endpoint) {
|
|
11818
|
-
const model = modelOverride ?? firstModel(local.model);
|
|
11821
|
+
const model = modelOverride ?? localModelPreference ?? firstModel(local.model);
|
|
11819
11822
|
return new OpenAICompatibleAnalysisProvider2({
|
|
11820
11823
|
apiKey: local.apiKey ?? "ollama",
|
|
11821
11824
|
baseUrl: local.endpoint,
|
|
@@ -11841,6 +11844,48 @@ function pickBackend(backends, types) {
|
|
|
11841
11844
|
return null;
|
|
11842
11845
|
}
|
|
11843
11846
|
|
|
11847
|
+
// src/commands/roadmap/triage-pool.ts
|
|
11848
|
+
import {
|
|
11849
|
+
PoolStateStore,
|
|
11850
|
+
poolStateToCandidates
|
|
11851
|
+
} from "@harness-engineering/orchestrator";
|
|
11852
|
+
var PROBE_TIMEOUT_MS = 4e3;
|
|
11853
|
+
async function probeServedModels(endpoint, apiKey, fetchImpl) {
|
|
11854
|
+
const url = endpoint.replace(/\/$/, "") + "/models";
|
|
11855
|
+
const controller = new AbortController();
|
|
11856
|
+
const timer = setTimeout(() => controller.abort(), PROBE_TIMEOUT_MS);
|
|
11857
|
+
try {
|
|
11858
|
+
const res = await fetchImpl(url, {
|
|
11859
|
+
signal: controller.signal,
|
|
11860
|
+
...apiKey !== void 0 ? { headers: { Authorization: `Bearer ${apiKey}` } } : {}
|
|
11861
|
+
});
|
|
11862
|
+
if (!res.ok) return void 0;
|
|
11863
|
+
const body = await res.json();
|
|
11864
|
+
const ids = (body.data ?? []).map((m) => m.id).filter((id) => typeof id === "string" && id.length > 0);
|
|
11865
|
+
return new Set(ids);
|
|
11866
|
+
} catch {
|
|
11867
|
+
return void 0;
|
|
11868
|
+
} finally {
|
|
11869
|
+
clearTimeout(timer);
|
|
11870
|
+
}
|
|
11871
|
+
}
|
|
11872
|
+
async function resolvePreferredLocalModel(profile, deps = {}) {
|
|
11873
|
+
try {
|
|
11874
|
+
const store = deps.store ?? new PoolStateStore();
|
|
11875
|
+
await store.load();
|
|
11876
|
+
const candidates = poolStateToCandidates(store.snapshot(), profile).filter(
|
|
11877
|
+
(name) => typeof name === "string" && name.length > 0
|
|
11878
|
+
);
|
|
11879
|
+
if (candidates.length === 0) return void 0;
|
|
11880
|
+
if (deps.endpoint === void 0) return void 0;
|
|
11881
|
+
const served = await probeServedModels(deps.endpoint, deps.apiKey, deps.fetchImpl ?? fetch);
|
|
11882
|
+
if (served === void 0) return void 0;
|
|
11883
|
+
return candidates.find((name) => served.has(name));
|
|
11884
|
+
} catch {
|
|
11885
|
+
return void 0;
|
|
11886
|
+
}
|
|
11887
|
+
}
|
|
11888
|
+
|
|
11844
11889
|
// src/commands/roadmap/triage-approve.ts
|
|
11845
11890
|
import {
|
|
11846
11891
|
detectScopeTier,
|
|
@@ -12010,12 +12055,13 @@ async function runTriageReport(roadmap, deps = {}) {
|
|
|
12010
12055
|
...deps.only !== void 0 ? { only: deps.only } : {},
|
|
12011
12056
|
...deps.limit !== void 0 ? { limit: deps.limit } : {}
|
|
12012
12057
|
});
|
|
12058
|
+
const useModel = deps.offline !== true && deps.provider != null;
|
|
12013
12059
|
const cheapDeps = {
|
|
12014
12060
|
...deps.graphStore ? { graphStore: deps.graphStore } : {},
|
|
12015
12061
|
...deps.precedent ? { precedent: deps.precedent } : {},
|
|
12016
|
-
...deps.config ? { config: deps.config } : {}
|
|
12062
|
+
...deps.config ? { config: deps.config } : {},
|
|
12063
|
+
...useModel ? { modelDeferred: true } : {}
|
|
12017
12064
|
};
|
|
12018
|
-
const useModel = deps.offline !== true && deps.provider != null;
|
|
12019
12065
|
const rows = [];
|
|
12020
12066
|
for (const feature of features) {
|
|
12021
12067
|
let verdict = await triageIssue(featureToIssue(feature), cheapDeps);
|
|
@@ -12214,10 +12260,29 @@ function renderBrainstormJson(rows) {
|
|
|
12214
12260
|
2
|
|
12215
12261
|
);
|
|
12216
12262
|
}
|
|
12217
|
-
function resolveBrainstormProvider(configPath, model) {
|
|
12263
|
+
async function resolveBrainstormProvider(configPath, model) {
|
|
12218
12264
|
const configResult = resolveConfig(configPath);
|
|
12219
12265
|
if (!configResult.ok) return null;
|
|
12220
|
-
|
|
12266
|
+
const config = configResult.value;
|
|
12267
|
+
const localBackend = pickLocalBackendConn(config);
|
|
12268
|
+
const preferredLocalModel = await resolvePreferredLocalModel("reasoning", {
|
|
12269
|
+
...localBackend?.endpoint !== void 0 ? { endpoint: localBackend.endpoint } : {},
|
|
12270
|
+
...localBackend?.apiKey !== void 0 ? { apiKey: localBackend.apiKey } : {}
|
|
12271
|
+
});
|
|
12272
|
+
return resolveTriageProvider(config, model, preferredLocalModel);
|
|
12273
|
+
}
|
|
12274
|
+
function pickLocalBackendConn(config) {
|
|
12275
|
+
const backends = config.agent?.backends;
|
|
12276
|
+
if (!backends) return void 0;
|
|
12277
|
+
for (const def of Object.values(backends)) {
|
|
12278
|
+
if (def.type === "local" || def.type === "pi") {
|
|
12279
|
+
return {
|
|
12280
|
+
...def.endpoint !== void 0 ? { endpoint: def.endpoint } : {},
|
|
12281
|
+
...def.apiKey !== void 0 ? { apiKey: def.apiKey } : {}
|
|
12282
|
+
};
|
|
12283
|
+
}
|
|
12284
|
+
}
|
|
12285
|
+
return void 0;
|
|
12221
12286
|
}
|
|
12222
12287
|
async function runApproveCommand(cwd, opts) {
|
|
12223
12288
|
const configResult = resolveConfig(opts.config);
|
|
@@ -12234,7 +12299,7 @@ async function runApproveCommand(cwd, opts) {
|
|
|
12234
12299
|
const parsed = parseRoadmap(fs36.readFileSync(roadmapPath, "utf-8"));
|
|
12235
12300
|
if (!parsed.ok) return { marked: [], held: [], note: "roadmap-parse-error" };
|
|
12236
12301
|
const graphStore = await loadGraphStore(cwd);
|
|
12237
|
-
const provider = resolveBrainstormProvider(opts.config);
|
|
12302
|
+
const provider = await resolveBrainstormProvider(opts.config);
|
|
12238
12303
|
const precedent = await buildPrecedentLookup(cwd);
|
|
12239
12304
|
const rows = await runBrainstormReport(parsed.value, {
|
|
12240
12305
|
...graphStore ? { graphStore } : {},
|
|
@@ -12339,7 +12404,7 @@ async function runTriageCommandAction(opts, cmd) {
|
|
|
12339
12404
|
if (!parsed) return;
|
|
12340
12405
|
const graphStore = await loadGraphStore(cwd);
|
|
12341
12406
|
const precedent = await buildPrecedentLookup(cwd);
|
|
12342
|
-
const provider = opts.offline === true ? null : resolveBrainstormProvider(globalOpts.config);
|
|
12407
|
+
const provider = opts.offline === true ? null : await resolveBrainstormProvider(globalOpts.config);
|
|
12343
12408
|
const onProgress = globalOpts.json === true ? void 0 : (message) => logger.info(message);
|
|
12344
12409
|
const shared = {
|
|
12345
12410
|
...graphStore ? { graphStore } : {},
|
|
@@ -14114,6 +14179,17 @@ function detectComplexity(projectPath) {
|
|
|
14114
14179
|
}
|
|
14115
14180
|
|
|
14116
14181
|
// src/commands/skill/preamble.ts
|
|
14182
|
+
var AUTONOMOUS_SECTION = `## Autonomous Decision-Making: Active
|
|
14183
|
+
|
|
14184
|
+
You are running this workflow **headless \u2014 there is no human in this session to answer questions.**
|
|
14185
|
+
|
|
14186
|
+
Wherever this skill instructs you to present options and a recommendation and then STOP and wait for the human (design forks, \`emit_interaction\`/\`AskUserQuestion\` prompts, spec sign-off, the next-skill handoff), do NOT wait. Instead:
|
|
14187
|
+
|
|
14188
|
+
1. Do the full analysis exactly as written \u2014 honor every gate (at least two approaches with tradeoffs, YAGNI, the persona council, soundness review). Autonomy narrows the decider, never the rigor.
|
|
14189
|
+
2. Commit to the option **you** recommend, and record the question, the chosen answer, and the rationale in the spec's **"Decisions made"** section.
|
|
14190
|
+
3. Continue to the next step. Self-approve the Phase-4 sign-off once the soundness review converges; proceed through the handoff to the next skill.
|
|
14191
|
+
|
|
14192
|
+
**Safety valve \u2014 surface, do not bury.** For any decision made at **low confidence**, and for any **strategy contradiction** the skill says to "never auto-resolve," still decide and proceed, but additionally record it under an **"## Autonomous decisions requiring review"** heading in the spec (question, your call, confidence, why) so the human's PR review sees exactly what you decided and where you were unsure. You are the decider, not a shortcut \u2014 the human's checkpoint is the PR, not a mid-run pause.`;
|
|
14117
14193
|
function buildActivePhasesSection(complexity, phases) {
|
|
14118
14194
|
const lines = [`## Active Phases (complexity: ${complexity})`];
|
|
14119
14195
|
for (const phase of phases) {
|
|
@@ -14138,6 +14214,9 @@ ${priorState}`);
|
|
|
14138
14214
|
}
|
|
14139
14215
|
function buildPreamble(options) {
|
|
14140
14216
|
const sections = [];
|
|
14217
|
+
if (options.autonomous) {
|
|
14218
|
+
sections.push(AUTONOMOUS_SECTION);
|
|
14219
|
+
}
|
|
14141
14220
|
if (options.complexity && options.phases && options.phases.length > 0) {
|
|
14142
14221
|
sections.push(buildActivePhasesSection(options.complexity, options.phases));
|
|
14143
14222
|
}
|
|
@@ -14256,7 +14335,8 @@ async function runSkill(name, opts) {
|
|
|
14256
14335
|
...opts.phase !== void 0 && { phase: opts.phase },
|
|
14257
14336
|
...priorState !== void 0 && { priorState },
|
|
14258
14337
|
...stateWarning !== void 0 && { stateWarning },
|
|
14259
|
-
...opts.party !== void 0 && { party: opts.party }
|
|
14338
|
+
...opts.party !== void 0 && { party: opts.party },
|
|
14339
|
+
...opts.autonomous !== void 0 && { autonomous: opts.autonomous }
|
|
14260
14340
|
});
|
|
14261
14341
|
const skillMdPath = path66.join(skillDir, "SKILL.md");
|
|
14262
14342
|
if (!fs42.existsSync(skillMdPath)) {
|
|
@@ -14277,6 +14357,9 @@ async function runSkill(name, opts) {
|
|
|
14277
14357
|
}
|
|
14278
14358
|
function createRunCommand3() {
|
|
14279
14359
|
return new Command104("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>", "Rigor level: fast, standard, thorough", "standard").option("--phase <name>", "Start at a specific phase (for re-entry)").option("--party", "Enable multi-perspective evaluation").option(
|
|
14360
|
+
"--autonomous",
|
|
14361
|
+
"Headless mode: inject the autonomous-decider preamble so the agent decides every fork at full rigor (recording each decision in the spec) instead of pausing for a human"
|
|
14362
|
+
).option(
|
|
14280
14363
|
"--backend <name>",
|
|
14281
14364
|
"Spec B: one-shot routing override forwarded to the orchestrator as HARNESS_BACKEND_OVERRIDE"
|
|
14282
14365
|
).action(async (name, opts) => runSkill(name, opts));
|
package/dist/index.d.ts
CHANGED
|
@@ -18,6 +18,7 @@ interface PreambleOptions {
|
|
|
18
18
|
priorState?: string;
|
|
19
19
|
stateWarning?: string;
|
|
20
20
|
party?: boolean;
|
|
21
|
+
autonomous?: boolean;
|
|
21
22
|
}
|
|
22
23
|
declare function buildPreamble(options: PreambleOptions): string;
|
|
23
24
|
|
|
@@ -864,6 +865,7 @@ declare const HarnessConfigSchema: z.ZodObject<{
|
|
|
864
865
|
model?: string | undefined;
|
|
865
866
|
} | undefined;
|
|
866
867
|
}>>;
|
|
868
|
+
workflowGates: z.ZodOptional<z.ZodEnum<["local", "primary"]>>;
|
|
867
869
|
}, "strict", z.ZodTypeAny, {
|
|
868
870
|
default: string | readonly [string, ...string[]];
|
|
869
871
|
'quick-fix'?: string | readonly [string, ...string[]] | undefined;
|
|
@@ -898,6 +900,7 @@ declare const HarnessConfigSchema: z.ZodObject<{
|
|
|
898
900
|
model?: string | undefined;
|
|
899
901
|
} | undefined;
|
|
900
902
|
} | undefined;
|
|
903
|
+
workflowGates?: "local" | "primary" | undefined;
|
|
901
904
|
}, {
|
|
902
905
|
default: string | readonly [string, ...string[]];
|
|
903
906
|
'quick-fix'?: string | readonly [string, ...string[]] | undefined;
|
|
@@ -932,6 +935,7 @@ declare const HarnessConfigSchema: z.ZodObject<{
|
|
|
932
935
|
model?: string | undefined;
|
|
933
936
|
} | undefined;
|
|
934
937
|
} | undefined;
|
|
938
|
+
workflowGates?: "local" | "primary" | undefined;
|
|
935
939
|
}>>;
|
|
936
940
|
}, "strip", z.ZodTypeAny, {
|
|
937
941
|
executor: "subprocess" | "cloud" | "noop";
|
|
@@ -1059,6 +1063,7 @@ declare const HarnessConfigSchema: z.ZodObject<{
|
|
|
1059
1063
|
model?: string | undefined;
|
|
1060
1064
|
} | undefined;
|
|
1061
1065
|
} | undefined;
|
|
1066
|
+
workflowGates?: "local" | "primary" | undefined;
|
|
1062
1067
|
} | undefined;
|
|
1063
1068
|
}, {
|
|
1064
1069
|
executor?: "subprocess" | "cloud" | "noop" | undefined;
|
|
@@ -1186,6 +1191,7 @@ declare const HarnessConfigSchema: z.ZodObject<{
|
|
|
1186
1191
|
model?: string | undefined;
|
|
1187
1192
|
} | undefined;
|
|
1188
1193
|
} | undefined;
|
|
1194
|
+
workflowGates?: "local" | "primary" | undefined;
|
|
1189
1195
|
} | undefined;
|
|
1190
1196
|
}>>;
|
|
1191
1197
|
/** Source-file ingestion controls (skip-dirs, exclude patterns, gitignore handling) */
|
|
@@ -2825,6 +2831,7 @@ declare const HarnessConfigSchema: z.ZodObject<{
|
|
|
2825
2831
|
model?: string | undefined;
|
|
2826
2832
|
} | undefined;
|
|
2827
2833
|
} | undefined;
|
|
2834
|
+
workflowGates?: "local" | "primary" | undefined;
|
|
2828
2835
|
} | undefined;
|
|
2829
2836
|
} | undefined;
|
|
2830
2837
|
ingest?: {
|
|
@@ -3247,6 +3254,7 @@ declare const HarnessConfigSchema: z.ZodObject<{
|
|
|
3247
3254
|
model?: string | undefined;
|
|
3248
3255
|
} | undefined;
|
|
3249
3256
|
} | undefined;
|
|
3257
|
+
workflowGates?: "local" | "primary" | undefined;
|
|
3250
3258
|
} | undefined;
|
|
3251
3259
|
} | undefined;
|
|
3252
3260
|
ingest?: {
|
package/dist/index.js
CHANGED
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
---
|
|
2
|
+
tracker:
|
|
3
|
+
kind: roadmap
|
|
4
|
+
filePath: docs/roadmap.md
|
|
5
|
+
activeStates: [planned, in-progress]
|
|
6
|
+
terminalStates: [done]
|
|
7
|
+
polling:
|
|
8
|
+
intervalMs: 30000
|
|
9
|
+
workspace:
|
|
10
|
+
root: .harness/workspaces
|
|
11
|
+
hooks:
|
|
12
|
+
afterCreate: null
|
|
13
|
+
beforeRun: null
|
|
14
|
+
afterRun: null
|
|
15
|
+
beforeRemove: null
|
|
16
|
+
timeoutMs: 60000
|
|
17
|
+
agent:
|
|
18
|
+
# Named backend definitions (Spec 2). See docs/guides/multi-backend-routing.md.
|
|
19
|
+
# `capabilities` (tier/cost/privacy/context) let AMR compare backends and select a
|
|
20
|
+
# capability tier per dispatch — a backend without one is invisible to tier selection.
|
|
21
|
+
backends:
|
|
22
|
+
primary:
|
|
23
|
+
type: claude
|
|
24
|
+
command: claude
|
|
25
|
+
capabilities:
|
|
26
|
+
{ tier: strong, costPer1kTokens: 15, privacyClass: shared-cloud, contextWindow: 200000 }
|
|
27
|
+
# Local backend for autonomous execution of simple tasks.
|
|
28
|
+
# `model` accepts a string OR a prefer-and-fallback array — first
|
|
29
|
+
# match wins after a `/v1/models` probe.
|
|
30
|
+
local:
|
|
31
|
+
type: pi
|
|
32
|
+
# Ollama's OpenAI-compatible API lives under /v1 (the resolver probes
|
|
33
|
+
# `${endpoint}/models`). Names must match `ollama list` exactly.
|
|
34
|
+
endpoint: http://127.0.0.1:11434/v1
|
|
35
|
+
# Prefer-and-fallback: first name present in /v1/models wins. Coder model
|
|
36
|
+
# first (local routes are quick-fix/diagnostic), general model as fallback.
|
|
37
|
+
# Quote the names — the ':tag' colon otherwise breaks YAML flow parsing.
|
|
38
|
+
model: ['qwen2.5-coder:7b', 'gemma3n:e4b']
|
|
39
|
+
capabilities:
|
|
40
|
+
{ tier: fast, costPer1kTokens: 0, privacyClass: on-device, contextWindow: 32768 }
|
|
41
|
+
# Routing — controls WHICH backend handles each use case.
|
|
42
|
+
routing:
|
|
43
|
+
default: primary
|
|
44
|
+
quick-fix: local
|
|
45
|
+
diagnostic: local
|
|
46
|
+
# Route the intelligence pipeline (sel/pesl) to the local backend.
|
|
47
|
+
intelligence:
|
|
48
|
+
sel: local
|
|
49
|
+
pesl: local
|
|
50
|
+
# AMR (Adaptive Model Routing) — opt-in; its PRESENCE flips dispatch from the
|
|
51
|
+
# identity/default chain to complexity-aware tier selection. trivial/simple →
|
|
52
|
+
# local (fast, free); moderate/complex → claude (no `standard` backend, so a
|
|
53
|
+
# `standard` requirement resolves to the cheapest backend at-or-above it =
|
|
54
|
+
# primary). The always-on baseline-relative security-defect feeder climbs a
|
|
55
|
+
# unit's tier after `escalationThreshold` consecutive quality failures
|
|
56
|
+
# (strong-capped, then hard-fails to a human). Budget cap + LLM acceptance-eval
|
|
57
|
+
# are available opt-ins (docs/guides/adaptive-model-routing.md) — left off here.
|
|
58
|
+
policy:
|
|
59
|
+
complexityTierMatrix: { trivial: fast, simple: fast, moderate: standard, complex: strong }
|
|
60
|
+
escalationThreshold: 2
|
|
61
|
+
# Escalation — controls WHETHER a tier dispatches at all (orthogonal to routing).
|
|
62
|
+
escalation:
|
|
63
|
+
alwaysHuman: [full-exploration]
|
|
64
|
+
autoExecute: [quick-fix, diagnostic]
|
|
65
|
+
primaryExecute: [guided-change]
|
|
66
|
+
signalGated: []
|
|
67
|
+
diagnosticRetryBudget: 1
|
|
68
|
+
maxConcurrentAgents: 1
|
|
69
|
+
maxTurns: 10
|
|
70
|
+
maxRetryBackoffMs: 5000
|
|
71
|
+
maxConcurrentAgentsByState: {}
|
|
72
|
+
globalCooldownMs: 60000
|
|
73
|
+
maxRequestsPerMinute: 50
|
|
74
|
+
maxRequestsPerSecond: 1
|
|
75
|
+
# Default limits based on Anthropic Tier 3. Adjust according to your account tier.
|
|
76
|
+
# Tier 1: 40k ITPM / 10k OTPM
|
|
77
|
+
# Tier 2: 200k ITPM / 40k OTPM
|
|
78
|
+
# Tier 3: 400k ITPM / 80k OTPM
|
|
79
|
+
# Tier 4: 1m ITPM / 200k OTPM
|
|
80
|
+
maxInputTokensPerMinute: 400000
|
|
81
|
+
maxOutputTokensPerMinute: 80000
|
|
82
|
+
turnTimeoutMs: 300000
|
|
83
|
+
readTimeoutMs: 30000
|
|
84
|
+
stallTimeoutMs: 60000
|
|
85
|
+
intelligence:
|
|
86
|
+
enabled: true
|
|
87
|
+
requestTimeoutMs: 180000
|
|
88
|
+
server:
|
|
89
|
+
port: 8080
|
|
90
|
+
localModels:
|
|
91
|
+
enabled: true
|
|
92
|
+
pool:
|
|
93
|
+
diskBudgetGb: 100
|
|
94
|
+
allowedOrgs: [Qwen, deepseek-ai, meta-llama, google]
|
|
95
|
+
allowedFamilies: []
|
|
96
|
+
refresh:
|
|
97
|
+
intervalMs: 86400000
|
|
98
|
+
proposalThreshold: 5
|
|
99
|
+
jitterMs: 600000
|
|
100
|
+
installer:
|
|
101
|
+
backend: ollama
|
|
102
|
+
ollamaEndpoint: http://127.0.0.1:11434
|
|
103
|
+
# Built-in maintenance tasks run on cron when `maintenance.enabled: true`.
|
|
104
|
+
# Notable housekeeping tasks: `main-sync` (every 15 min) fast-forwards the
|
|
105
|
+
# orchestrator's local default branch from origin so files read from `cwd`
|
|
106
|
+
# (e.g., docs/roadmap.md, harness.orchestrator.md) stay current. Sync is
|
|
107
|
+
# fast-forward-only — never destructive — and skips with a structured
|
|
108
|
+
# warning event if the working tree is dirty, the branch is wrong, or the
|
|
109
|
+
# local default has diverged. Disable all maintenance via `maintenance.enabled: false`.
|
|
110
|
+
#
|
|
111
|
+
# Per-task Run Now (since 2026-05-09): the dashboard Maintenance page renders
|
|
112
|
+
# a Run Now button on every row of the schedule table. The previous single-
|
|
113
|
+
# button affordance (which always triggered `project-health`) has been
|
|
114
|
+
# removed. Each button is disabled while a `maintenance:started` event is in
|
|
115
|
+
# flight for that task ID and re-enables on the matching `maintenance:completed`
|
|
116
|
+
# or `maintenance:error` event.
|
|
117
|
+
maintenance:
|
|
118
|
+
enabled: true
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
# Prompt Template (Local Backend — bootstrap shim)
|
|
122
|
+
|
|
123
|
+
You are a local agent with `bash`, `read`, `write`, `grep`, and `find`. You do
|
|
124
|
+
NOT have `/harness:*` slash commands or harness MCP tools. You run the REAL
|
|
125
|
+
harness workflow skills by reading them over bash — this template carries no
|
|
126
|
+
methodology of its own.
|
|
127
|
+
|
|
128
|
+
## Issue: {{ issue.title }}
|
|
129
|
+
|
|
130
|
+
**Identifier:** {{ issue.identifier }}
|
|
131
|
+
**Description:** {{ issue.description }}
|
|
132
|
+
|
|
133
|
+
## How to run a harness skill (the indirection rule)
|
|
134
|
+
|
|
135
|
+
To run any harness workflow skill, execute it over bash and follow its output
|
|
136
|
+
**verbatim**:
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
harness skill run <skill-name> --autonomous --path .
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
`harness skill run` prints the skill's full instructions (the same content the
|
|
143
|
+
primary backend gets from a `/harness:*` slash command) to stdout as a plain CLI
|
|
144
|
+
read. `--autonomous` prepends the headless-decider preamble: you do the full
|
|
145
|
+
analysis at full rigor but YOU decide every fork and record it in the spec — you
|
|
146
|
+
never pause for a human.
|
|
147
|
+
|
|
148
|
+
**Redirect rule.** Whenever a skill's output instructs you to run `/harness:X`,
|
|
149
|
+
instead run `harness skill run harness-X --autonomous`. Slash commands are
|
|
150
|
+
unavailable on this backend; the skill-run indirection is their equivalent.
|
|
151
|
+
|
|
152
|
+
## Full-workflow entry sequence
|
|
153
|
+
|
|
154
|
+
Run these in order, each via `harness skill run <name> --autonomous --path .`,
|
|
155
|
+
following each skill's output before moving on:
|
|
156
|
+
|
|
157
|
+
1. `harness-brainstorming` — runs at full rigor (≥2 approaches, YAGNI, persona
|
|
158
|
+
council, soundness), but YOU decide the forks (autonomous) and record each
|
|
159
|
+
decision in the spec.
|
|
160
|
+
2. `harness-planning`
|
|
161
|
+
3. `harness-execution` (or `harness-tdd` for test-driven work)
|
|
162
|
+
4. `harness-verification`
|
|
163
|
+
5. `harness-code-review`
|
|
164
|
+
|
|
165
|
+
Run `harness skill list` to see the full roster of available skills.
|
|
166
|
+
|
|
167
|
+
## Gates (enforced by the harness, not just by you)
|
|
168
|
+
|
|
169
|
+
The orchestrator INDEPENDENTLY enforces `harness validate` plus the
|
|
170
|
+
verify/outcome-eval gates against your branch after you exit — you cannot ship
|
|
171
|
+
past a red gate. Reach a green state: run `harness validate` and the project's
|
|
172
|
+
own typecheck/lint/test yourself and fix every failure before shipping. A run
|
|
173
|
+
that cannot reach green is halted and re-dispatched rather than shipped, and
|
|
174
|
+
escalated to a human if the retry budget is exhausted.
|
|
175
|
+
|
|
176
|
+
## Ship (only after the gates are green)
|
|
177
|
+
|
|
178
|
+
- Create a topic branch if you are still on `main`/`master`
|
|
179
|
+
(e.g. `feat/{{ issue.identifier }}`).
|
|
180
|
+
- Stage your changes and create a descriptive commit (Conventional Commits style).
|
|
181
|
+
- Push the branch with `git push -u origin HEAD`.
|
|
182
|
+
- Open a pull request with `gh pr create`. Use a HEREDOC for the body:
|
|
183
|
+
|
|
184
|
+
```bash
|
|
185
|
+
gh pr create --title "<title>" --body "$(cat <<'EOF'
|
|
186
|
+
## Summary
|
|
187
|
+
|
|
188
|
+
<body content with real newlines>
|
|
189
|
+
EOF
|
|
190
|
+
)"
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
- Report the PR URL as your final output, then stop.
|
|
194
|
+
|
|
195
|
+
Attempt Number: {{ attempt }}
|
|
@@ -82,6 +82,11 @@ Your goal is to implement the following issue using the standard Harness Enginee
|
|
|
82
82
|
Follow these steps exactly, using the corresponding slash commands to ensure
|
|
83
83
|
high-quality, architecturally sound delivery:
|
|
84
84
|
|
|
85
|
+
<!-- This slash-command workflow targets Claude-shaped backends. A backend-specific
|
|
86
|
+
variant, `harness.orchestrator.local.md`, is auto-selected for `local`/`pi`
|
|
87
|
+
backends (which lack `/harness:*` slash commands) and expresses the same
|
|
88
|
+
workflow via bash `harness <gate>` CLI calls. See ADR 0071. -->
|
|
89
|
+
|
|
85
90
|
1. **Brainstorming:** Use `/harness:brainstorming` to explore the problem space
|
|
86
91
|
and draft a technical proposal in `docs/changes/`. The spec MUST include an
|
|
87
92
|
Integration Points section defining how the feature connects to the system.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@harness-engineering/cli",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "9.0.0",
|
|
4
4
|
"description": "CLI for Harness Engineering toolkit",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -39,16 +39,16 @@
|
|
|
39
39
|
"web-tree-sitter": "^0.24.7",
|
|
40
40
|
"yaml": "^2.8.3",
|
|
41
41
|
"zod": "^3.25.76",
|
|
42
|
-
"@harness-engineering/core": "0.37.
|
|
43
|
-
"@harness-engineering/dashboard": "0.14.
|
|
44
|
-
"@harness-engineering/graph": "0.11.
|
|
42
|
+
"@harness-engineering/core": "0.37.1",
|
|
43
|
+
"@harness-engineering/dashboard": "0.14.5",
|
|
44
|
+
"@harness-engineering/graph": "0.11.9",
|
|
45
45
|
"@harness-engineering/linter-gen": "0.1.7",
|
|
46
|
-
"@harness-engineering/orchestrator": "0.
|
|
47
|
-
"@harness-engineering/signals": "0.2.
|
|
48
|
-
"@harness-engineering/types": "0.
|
|
46
|
+
"@harness-engineering/orchestrator": "0.16.0",
|
|
47
|
+
"@harness-engineering/signals": "0.2.7",
|
|
48
|
+
"@harness-engineering/types": "0.23.0"
|
|
49
49
|
},
|
|
50
50
|
"peerDependencies": {
|
|
51
|
-
"@harness-engineering/intelligence": "0.
|
|
51
|
+
"@harness-engineering/intelligence": "0.9.0"
|
|
52
52
|
},
|
|
53
53
|
"peerDependenciesMeta": {
|
|
54
54
|
"@harness-engineering/intelligence": {
|