@hone-ai/cli 1.22.0 → 1.24.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/hone-cli.js +114 -1
- package/lib/mcp-tools.js +127 -0
- package/lib/pipeline-config.js +62 -0
- package/package.json +1 -1
package/hone-cli.js
CHANGED
|
@@ -4486,7 +4486,7 @@ program
|
|
|
4486
4486
|
program
|
|
4487
4487
|
.command('classify-story [issueId]')
|
|
4488
4488
|
.description('Classify a GitHub issue by SDLC track. Extracts inputs from issue body/labels heuristically; reads other inputs from --input-file or interactive prompt. Outputs JSON.')
|
|
4489
|
-
.option('--input-file <path>', 'YAML file with all
|
|
4489
|
+
.option('--input-file <path>', 'YAML file with all 10 classifyStory inputs (skips GitHub fetch + interactive)')
|
|
4490
4490
|
.option('--interactive', 'prompt for the 5 judgment fields not heuristically extractable')
|
|
4491
4491
|
.option('--pretty', 'human-readable output (default: --json)')
|
|
4492
4492
|
.option('--repo-root <path>', 'repo root (default: process.cwd())')
|
|
@@ -5417,6 +5417,99 @@ program
|
|
|
5417
5417
|
});
|
|
5418
5418
|
|
|
5419
5419
|
// ── HC-041: Run Story (Orchestrator) ─────────────────────────────────────────
|
|
5420
|
+
program
|
|
5421
|
+
.command('workflows')
|
|
5422
|
+
.description("List your org's recent workflow runs (HC-E5-MCP)")
|
|
5423
|
+
.option('--status <status>', 'Filter: queued|running|paused|completed|failed|stalled|killed')
|
|
5424
|
+
.option('--limit <n>', 'Max runs to return (1-100, default 20)')
|
|
5425
|
+
.option('--format <fmt>', 'Output format: pretty | json', 'pretty')
|
|
5426
|
+
.action(async (opts) => {
|
|
5427
|
+
const config = getConfig();
|
|
5428
|
+
const client = api(config);
|
|
5429
|
+
try {
|
|
5430
|
+
const params = new URLSearchParams();
|
|
5431
|
+
if (opts.status) params.set('status', opts.status);
|
|
5432
|
+
if (opts.limit) params.set('limit', opts.limit);
|
|
5433
|
+
const qs = params.toString();
|
|
5434
|
+
const { data } = await client.get(`/orchestrate${qs ? `?${qs}` : ''}`);
|
|
5435
|
+
if (opts.format === 'json') { console.log(JSON.stringify(data, null, 2)); return; }
|
|
5436
|
+
if (!data.runs.length) { console.log('No workflow runs found.'); return; }
|
|
5437
|
+
const age = (ts) => {
|
|
5438
|
+
const m = Math.floor((Date.now() - new Date(ts).getTime()) / 60000);
|
|
5439
|
+
return m < 60 ? `${m}m` : m < 1440 ? `${Math.floor(m / 60)}h` : `${Math.floor(m / 1440)}d`;
|
|
5440
|
+
};
|
|
5441
|
+
console.log('RUN ID STORY STATUS STEP AGE');
|
|
5442
|
+
for (const r of data.runs) {
|
|
5443
|
+
console.log([
|
|
5444
|
+
String(r.id).slice(0, 8).padEnd(9),
|
|
5445
|
+
String(r.story_id || '-').slice(0, 21).padEnd(22),
|
|
5446
|
+
String(r.status).padEnd(10),
|
|
5447
|
+
String(r.current_step || '-').padEnd(10),
|
|
5448
|
+
age(r.created_at),
|
|
5449
|
+
].join(' ') + (r.error_message ? ` ⚠ ${String(r.error_message).slice(0, 40)}` : ''));
|
|
5450
|
+
}
|
|
5451
|
+
console.log(`\n${data.count} run(s). Inspect: hone show step <runId> <step> · Kill: hone run-story <runId> --kill`);
|
|
5452
|
+
} catch (e) {
|
|
5453
|
+
console.error(`Failed: ${e.response?.data?.error || e.message}`);
|
|
5454
|
+
process.exit(1);
|
|
5455
|
+
}
|
|
5456
|
+
});
|
|
5457
|
+
|
|
5458
|
+
program
|
|
5459
|
+
.command('step-instruction <runId>')
|
|
5460
|
+
.description('Fetch the editor-mode instruction for a run parked awaiting_editor (HC-COMM-010 B3)')
|
|
5461
|
+
.option('--format <fmt>', 'Output format: pretty | json', 'pretty')
|
|
5462
|
+
.action(async (runId, opts) => {
|
|
5463
|
+
const config = getConfig();
|
|
5464
|
+
const client = api(config);
|
|
5465
|
+
try {
|
|
5466
|
+
const { data } = await client.get(`/orchestrate/${encodeURIComponent(runId)}/step-instruction`);
|
|
5467
|
+
if (opts.format === 'json') { console.log(JSON.stringify(data, null, 2)); return; }
|
|
5468
|
+
console.log(`Step: ${data.stepKey} (${data.agent})`);
|
|
5469
|
+
console.log(`Contract: ${data.contractVersion}`);
|
|
5470
|
+
console.log('─'.repeat(60));
|
|
5471
|
+
console.log(data.instruction);
|
|
5472
|
+
console.log('─'.repeat(60));
|
|
5473
|
+
console.log(`Submit with: hone submit-step ${runId} ${data.stepKey} --file <output.txt>`);
|
|
5474
|
+
} catch (e) {
|
|
5475
|
+
const st = e.response?.status;
|
|
5476
|
+
if (st === 409) console.error(`No step awaiting the editor: ${e.response.data?.error || ''} (run status: ${e.response.data?.runStatus || '?'})`);
|
|
5477
|
+
else if (st === 403) console.error(`${e.response.data?.error}: ${e.response.data?.remediation || ''}`);
|
|
5478
|
+
else console.error(`Failed: ${e.response?.data?.error || e.message}`);
|
|
5479
|
+
process.exit(1);
|
|
5480
|
+
}
|
|
5481
|
+
});
|
|
5482
|
+
|
|
5483
|
+
program
|
|
5484
|
+
.command('submit-step <runId> <stepKey>')
|
|
5485
|
+
.description('Submit editor-executed output for an awaiting_editor step (HC-COMM-010 B3)')
|
|
5486
|
+
.option('--file <path>', 'Read the step output from this file')
|
|
5487
|
+
.option('--contract <version>', 'Instruction contract version (from step-instruction)', '1')
|
|
5488
|
+
.option('--format <fmt>', 'Output format: pretty | json', 'pretty')
|
|
5489
|
+
.action(async (runId, stepKey, opts) => {
|
|
5490
|
+
const config = getConfig();
|
|
5491
|
+
const client = api(config);
|
|
5492
|
+
try {
|
|
5493
|
+
if (!opts.file) { console.error('Provide the output via --file <path> (the text your editor produced).'); process.exit(1); }
|
|
5494
|
+
const fs = require('fs');
|
|
5495
|
+
const output = fs.readFileSync(opts.file, 'utf8');
|
|
5496
|
+
const { data } = await client.post(
|
|
5497
|
+
`/orchestrate/${encodeURIComponent(runId)}/step/${encodeURIComponent(stepKey)}/editor-output`,
|
|
5498
|
+
{ contractVersion: opts.contract, output }
|
|
5499
|
+
);
|
|
5500
|
+
if (opts.format === 'json') { console.log(JSON.stringify(data, null, 2)); return; }
|
|
5501
|
+
console.log(`Step ${data.stepKey} completed (executed_by: ${data.executedBy}); gate: ${data.gateResult}.`);
|
|
5502
|
+
console.log(`Approve when reviewed: hone run-story ${runId} --approve ${data.stepKey}`);
|
|
5503
|
+
console.log('Verification is unchanged — check-patch/verify-patch judge this output, not trust.');
|
|
5504
|
+
} catch (e) {
|
|
5505
|
+
const st = e.response?.status;
|
|
5506
|
+
if (st === 409) console.error(`Rejected: ${e.response.data?.error}`);
|
|
5507
|
+
else if (st === 413) console.error(`Rejected: ${e.response.data?.error}`);
|
|
5508
|
+
else console.error(`Failed: ${e.response?.data?.error || e.message}`);
|
|
5509
|
+
process.exit(1);
|
|
5510
|
+
}
|
|
5511
|
+
});
|
|
5512
|
+
|
|
5420
5513
|
program
|
|
5421
5514
|
.command('run-story <storyId>')
|
|
5422
5515
|
.description('Run the SDLC pipeline for a story via the orchestrator')
|
|
@@ -5785,6 +5878,26 @@ program
|
|
|
5785
5878
|
orchestrateConfig.ci_local_command = 'make ci';
|
|
5786
5879
|
}
|
|
5787
5880
|
|
|
5881
|
+
// HC-COMM-010 B0.5 (memo §12 A3): stamp execution.mode into the run config
|
|
5882
|
+
// at CREATION — the server reads it from run.config, making the mode
|
|
5883
|
+
// immutable per run (a mid-flight .pipeline-config.yml flip cannot change
|
|
5884
|
+
// a running run). Absent/old CLIs → the server defaults to 'server'.
|
|
5885
|
+
// Inert until B1: the server ignores the key today.
|
|
5886
|
+
try {
|
|
5887
|
+
const { readExecutionMode, EDITOR_MODE_CONSENT_NOTE } = require('./lib/pipeline-config');
|
|
5888
|
+
const executionMode = readExecutionMode(process.cwd());
|
|
5889
|
+
orchestrateConfig.execution_mode = executionMode;
|
|
5890
|
+
if (executionMode === 'editor') {
|
|
5891
|
+
// §2a: the consent note is shown at the moment editor mode is USED.
|
|
5892
|
+
console.log(` → execution mode: editor (opt-in)`);
|
|
5893
|
+
console.log(` ${EDITOR_MODE_CONSENT_NOTE}`);
|
|
5894
|
+
}
|
|
5895
|
+
} catch (e) {
|
|
5896
|
+
const msg = (e && e.message) ? e.message.split('\n')[0] : String(e);
|
|
5897
|
+
console.warn(` ⚠ execution mode read failed (non-fatal, defaulting to server): ${msg}`);
|
|
5898
|
+
orchestrateConfig.execution_mode = 'server';
|
|
5899
|
+
}
|
|
5900
|
+
|
|
5788
5901
|
// HC-019n-followup-28: resolve the E2E spec-generation mode (step_3b /
|
|
5789
5902
|
// Playwright). Precedence mirrors ci.gate: per-run flag > .pipeline-config.yml
|
|
5790
5903
|
// `e2e.spec_generation` > default `auto`. `auto` = the orchestrator runs
|
package/lib/mcp-tools.js
CHANGED
|
@@ -97,6 +97,92 @@ async function runStory({ storyId, mode = 'batch', cwd } = {}) {
|
|
|
97
97
|
};
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
+
// hone_list_workflows — list the org's recent runs (readOnly). GET /orchestrate
|
|
101
|
+
// via `hone workflows` (HC-E5-MCP). Summary rows only — the server never sends
|
|
102
|
+
// config/bundled source on the list wire (memo D1).
|
|
103
|
+
async function listWorkflows({ status, limit, cwd } = {}) {
|
|
104
|
+
const args = ['workflows', '--format', 'json'];
|
|
105
|
+
if (status) args.push('--status', String(status));
|
|
106
|
+
if (limit) args.push('--limit', String(limit));
|
|
107
|
+
const r = await runHone(args, { cwd });
|
|
108
|
+
if (r.error) return { isError: true, detail: r.error };
|
|
109
|
+
const data = parseLastJson(r.stdout);
|
|
110
|
+
return {
|
|
111
|
+
isError: !data,
|
|
112
|
+
runs: data ? data.runs : null,
|
|
113
|
+
count: data ? data.count : 0,
|
|
114
|
+
exitCode: r.exitCode,
|
|
115
|
+
detail: data ? `${data.count} run(s)` : (r.stderr || 'could not parse workflows JSON').slice(0, 500),
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// hone_kill_workflow — kill ONE run by explicit id (DESTRUCTIVE, irreversible).
|
|
120
|
+
// runId is schema-required: an agent must never guess a target for a kill —
|
|
121
|
+
// explicit id or nothing (memo D3 refutation). Server 409s non-killable states.
|
|
122
|
+
async function killWorkflow({ runId, cwd } = {}) {
|
|
123
|
+
if (!runId) return { isError: true, detail: 'runId is required' };
|
|
124
|
+
const r = await runHone(['run-story', String(runId), '--kill'], { cwd });
|
|
125
|
+
if (r.error) return { isError: true, detail: r.error };
|
|
126
|
+
return {
|
|
127
|
+
isError: !r.ok,
|
|
128
|
+
killed: r.ok,
|
|
129
|
+
exitCode: r.exitCode,
|
|
130
|
+
detail: (r.stdout || r.stderr || '').trim().slice(0, 500) || (r.ok ? 'killed' : 'kill failed'),
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// hone_step_instruction — fetch the editor-mode instruction for an
|
|
135
|
+
// awaiting_editor run (HC-COMM-010 B3). THE ephemeral delivery: the composed
|
|
136
|
+
// instruction lands in the editor's context via this tool result and is never
|
|
137
|
+
// written to disk by any Hone code.
|
|
138
|
+
async function stepInstruction({ runId, cwd } = {}) {
|
|
139
|
+
if (!runId) return { isError: true, detail: 'runId is required' };
|
|
140
|
+
const r = await runHone(['step-instruction', String(runId), '--format', 'json'], { cwd });
|
|
141
|
+
if (r.error) return { isError: true, detail: r.error };
|
|
142
|
+
const data = parseLastJson(r.stdout);
|
|
143
|
+
return {
|
|
144
|
+
isError: !data,
|
|
145
|
+
instruction: data ? data.instruction : null,
|
|
146
|
+
stepKey: data ? data.stepKey : null,
|
|
147
|
+
contractVersion: data ? data.contractVersion : null,
|
|
148
|
+
exitCode: r.exitCode,
|
|
149
|
+
detail: data
|
|
150
|
+
? `instruction for ${data.stepKey} (contract ${data.contractVersion}) — execute it and call hone_submit_step with the complete output`
|
|
151
|
+
: (r.stderr || r.stdout || 'could not fetch instruction').slice(0, 500),
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// hone_submit_step — submit editor-executed output (HC-COMM-010 B3). Output
|
|
156
|
+
// travels via a temp file (1MB-class payloads must not ride argv); the file
|
|
157
|
+
// is best-effort deleted after the shell-out.
|
|
158
|
+
async function submitStep({ runId, stepKey, output, contractVersion = '1', cwd } = {}) {
|
|
159
|
+
if (!runId) return { isError: true, detail: 'runId is required' };
|
|
160
|
+
if (!stepKey) return { isError: true, detail: 'stepKey is required' };
|
|
161
|
+
if (typeof output !== 'string' || output.trim() === '') {
|
|
162
|
+
return { isError: true, detail: 'output is required (the complete step output text)' };
|
|
163
|
+
}
|
|
164
|
+
const tmp = path.join(os.tmpdir(), `hone-editor-output-${Date.now()}-${process.pid}.txt`);
|
|
165
|
+
try {
|
|
166
|
+
fsSync.writeFileSync(tmp, output, 'utf8');
|
|
167
|
+
const r = await runHone(
|
|
168
|
+
['submit-step', String(runId), String(stepKey), '--file', tmp, '--contract', String(contractVersion), '--format', 'json'],
|
|
169
|
+
{ cwd });
|
|
170
|
+
if (r.error) return { isError: true, detail: r.error };
|
|
171
|
+
const data = parseLastJson(r.stdout);
|
|
172
|
+
return {
|
|
173
|
+
isError: !r.ok,
|
|
174
|
+
submitted: r.ok,
|
|
175
|
+
gateResult: data ? data.gateResult : null,
|
|
176
|
+
exitCode: r.exitCode,
|
|
177
|
+
detail: data
|
|
178
|
+
? `step ${data.stepKey} completed (executed_by: ${data.executedBy}); human gate is next`
|
|
179
|
+
: (r.stderr || r.stdout || 'submission failed').slice(0, 500),
|
|
180
|
+
};
|
|
181
|
+
} finally {
|
|
182
|
+
try { fsSync.unlinkSync(tmp); } catch { /* best-effort cleanup */ }
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
100
186
|
// hone_status — read run status (readOnly). GET /orchestrate/:id.
|
|
101
187
|
async function status({ runId, cwd } = {}) {
|
|
102
188
|
if (!runId) return { isError: true, detail: 'runId is required' };
|
|
@@ -295,6 +381,43 @@ const TOOLS = [
|
|
|
295
381
|
inputSchema: { type: 'object', properties: { skillsOnly: { type: 'boolean', default: false }, agentsOnly: { type: 'boolean', default: false } } },
|
|
296
382
|
handler: sync,
|
|
297
383
|
},
|
|
384
|
+
{
|
|
385
|
+
name: 'hone_list_workflows',
|
|
386
|
+
description: "List your org's recent Hone workflow runs (id, story, status, step, age). Read-only; optional status filter and limit (1-100).",
|
|
387
|
+
readOnly: true,
|
|
388
|
+
inputSchema: { type: 'object', properties: { status: { type: 'string', enum: ['queued', 'running', 'paused', 'completed', 'failed', 'stalled', 'killed'] }, limit: { type: 'number' } } },
|
|
389
|
+
handler: listWorkflows,
|
|
390
|
+
},
|
|
391
|
+
{
|
|
392
|
+
name: 'hone_kill_workflow',
|
|
393
|
+
description: 'KILL a Hone workflow run by explicit run id. DESTRUCTIVE and irreversible: the run stops and cannot be resumed. Requires the exact runId — list first if unsure.',
|
|
394
|
+
readOnly: false,
|
|
395
|
+
inputSchema: { type: 'object', properties: { runId: { type: 'string' } }, required: ['runId'] },
|
|
396
|
+
handler: killWorkflow,
|
|
397
|
+
},
|
|
398
|
+
{
|
|
399
|
+
name: 'hone_step_instruction',
|
|
400
|
+
description: 'Fetch the editor-mode instruction for a Hone run that is awaiting the editor (execution.mode: editor). Read-only. Execute the returned instruction in THIS session, then call hone_submit_step with the complete output.',
|
|
401
|
+
readOnly: true,
|
|
402
|
+
inputSchema: { type: 'object', properties: { runId: { type: 'string' } }, required: ['runId'] },
|
|
403
|
+
handler: stepInstruction,
|
|
404
|
+
},
|
|
405
|
+
{
|
|
406
|
+
name: 'hone_submit_step',
|
|
407
|
+
description: "Submit the editor-executed output for a Hone step that was fetched via hone_step_instruction. The output enters Hone's NORMAL gate + verification flow (judged by real test execution, not trust).",
|
|
408
|
+
readOnly: false,
|
|
409
|
+
inputSchema: {
|
|
410
|
+
type: 'object',
|
|
411
|
+
properties: {
|
|
412
|
+
runId: { type: 'string' },
|
|
413
|
+
stepKey: { type: 'string' },
|
|
414
|
+
output: { type: 'string' },
|
|
415
|
+
contractVersion: { type: 'string', default: '1' },
|
|
416
|
+
},
|
|
417
|
+
required: ['runId', 'stepKey', 'output'],
|
|
418
|
+
},
|
|
419
|
+
handler: submitStep,
|
|
420
|
+
},
|
|
298
421
|
{
|
|
299
422
|
name: 'hone_doctor',
|
|
300
423
|
description: 'Self-check the Hone setup (read-only): is a token configured, and is the `hone` CLI installed + reachable? Run this first if other tools fail.',
|
|
@@ -305,6 +428,10 @@ const TOOLS = [
|
|
|
305
428
|
];
|
|
306
429
|
|
|
307
430
|
module.exports = {
|
|
431
|
+
stepInstruction,
|
|
432
|
+
submitStep,
|
|
433
|
+
listWorkflows,
|
|
434
|
+
killWorkflow,
|
|
308
435
|
runStory, status, verifyPatch, checkPatch, showStep, approve, emitPr, verifyPr, agentEval, derive, sync, doctor,
|
|
309
436
|
runHone, parseRunStarted, parseLastJson, honeBin, readHoneConfig, TOOLS,
|
|
310
437
|
};
|
package/lib/pipeline-config.js
CHANGED
|
@@ -205,6 +205,63 @@ function readE2eSpecMode(repoRoot) {
|
|
|
205
205
|
return DEFAULT_E2E_SPEC_MODE;
|
|
206
206
|
}
|
|
207
207
|
|
|
208
|
+
// ── HC-COMM-010 (Track B / B0): execution mode ────────────────────────────────
|
|
209
|
+
// Which side EXECUTES the generation steps:
|
|
210
|
+
// server (default) → the Hone server runs every agent (BYO key / metered);
|
|
211
|
+
// editor → (feature ships with B1-B4) generation steps run in the
|
|
212
|
+
// adopter's editor AI via ephemeral server-composed
|
|
213
|
+
// instructions. The knob is reserved NOW (memo §2a) so
|
|
214
|
+
// adopters never migrate config to get editor mode later.
|
|
215
|
+
// Default-safe: missing/malformed → 'server'.
|
|
216
|
+
const EXECUTION_MODES = ['server', 'editor'];
|
|
217
|
+
const DEFAULT_EXECUTION_MODE = 'server';
|
|
218
|
+
|
|
219
|
+
// Memo §2a consent note — shown verbatim whenever `editor` is selected.
|
|
220
|
+
// Neutral own-use consent; supports but does not replace the ToS position.
|
|
221
|
+
const EDITOR_MODE_CONSENT_NOTE =
|
|
222
|
+
'Editor mode runs pipeline steps in your own editor AI on whatever plan ' +
|
|
223
|
+
"you've authorized there. By enabling it you confirm you are using your own " +
|
|
224
|
+
'authorized plan for your own development work, and that you are responsible ' +
|
|
225
|
+
'for ensuring your plan and usage comply with your agreement with your AI ' +
|
|
226
|
+
'provider. Hone does not select or provision your plan.';
|
|
227
|
+
|
|
228
|
+
/** Normalize an execution.mode value, defaulting to server. */
|
|
229
|
+
function normalizeExecutionMode(v) {
|
|
230
|
+
const s = String(v == null ? '' : v).toLowerCase().trim();
|
|
231
|
+
return EXECUTION_MODES.includes(s) ? s : DEFAULT_EXECUTION_MODE;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Read execution.mode from .pipeline-config.yml's `execution:` block.
|
|
236
|
+
* Same candidate order + graceful degradation as readE2eSpecMode.
|
|
237
|
+
*
|
|
238
|
+
* @param {string} repoRoot
|
|
239
|
+
* @returns {'server'|'editor'} default 'server' when config is missing/malformed.
|
|
240
|
+
*/
|
|
241
|
+
function readExecutionMode(repoRoot) {
|
|
242
|
+
if (!repoRoot || typeof repoRoot !== 'string') return DEFAULT_EXECUTION_MODE;
|
|
243
|
+
const candidates = [
|
|
244
|
+
path.join(repoRoot, '.pipeline-config.yml'),
|
|
245
|
+
path.join(repoRoot, '.github/.pipeline-config.yml'),
|
|
246
|
+
];
|
|
247
|
+
for (const p of candidates) {
|
|
248
|
+
if (!fs.existsSync(p)) continue;
|
|
249
|
+
let raw;
|
|
250
|
+
try { raw = fs.readFileSync(p, 'utf8'); } catch { continue; }
|
|
251
|
+
let parsed;
|
|
252
|
+
try {
|
|
253
|
+
const yaml = require('js-yaml');
|
|
254
|
+
parsed = yaml.load(raw);
|
|
255
|
+
} catch { continue; }
|
|
256
|
+
if (!parsed || typeof parsed !== 'object') continue;
|
|
257
|
+
const block = parsed.execution;
|
|
258
|
+
if (!block || typeof block !== 'object') continue;
|
|
259
|
+
if (block.mode === undefined) continue;
|
|
260
|
+
return normalizeExecutionMode(block.mode);
|
|
261
|
+
}
|
|
262
|
+
return DEFAULT_EXECUTION_MODE;
|
|
263
|
+
}
|
|
264
|
+
|
|
208
265
|
// ── HC-019n-followup-34: closed-loop verification gate ────────────────────────
|
|
209
266
|
// The CLI verbs (verify-patch/verify-pr) EXECUTE and report a verdict to the
|
|
210
267
|
// server. `verification.gate` controls what the server does with it:
|
|
@@ -360,6 +417,11 @@ function readAgentEvalConfig(repoRoot) {
|
|
|
360
417
|
}
|
|
361
418
|
|
|
362
419
|
module.exports = {
|
|
420
|
+
EXECUTION_MODES,
|
|
421
|
+
DEFAULT_EXECUTION_MODE,
|
|
422
|
+
EDITOR_MODE_CONSENT_NOTE,
|
|
423
|
+
normalizeExecutionMode,
|
|
424
|
+
readExecutionMode,
|
|
363
425
|
readStoryClassifierConfig,
|
|
364
426
|
KNOWN_THRESHOLD_KEYS,
|
|
365
427
|
readCIGateConfig,
|