@hone-ai/cli 1.34.0 → 1.35.1
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/bin/hone-mcp.js +1 -0
- package/hone-cli.js +1341 -795
- package/lib/agent-eval-pacing.js +102 -0
- package/lib/agent-eval-probes-boundary.js +0 -0
- package/lib/agent-eval-probes-stateful.js +72 -0
- package/lib/agent-eval-score.js +70 -0
- package/lib/agent-executor.js +26 -4
- package/lib/asset-fetch-list.js +72 -0
- package/lib/auto-verify.js +67 -3
- package/lib/batch-follow.js +333 -0
- package/lib/block-grammar.js +126 -0
- package/lib/bundle-context.js +43 -4
- package/lib/bundle-paths.js +698 -1
- package/lib/bundle-source-dirs.js +79 -0
- package/lib/doctor-bind-default.js +192 -18
- package/lib/doctor-local-run.js +139 -1
- package/lib/emit-pr.js +39 -6
- package/lib/git-helpers.js +19 -8
- package/lib/hard-pause-remedy.js +101 -0
- package/lib/include-paths.js +155 -0
- package/lib/learnings-parse.js +97 -7
- package/lib/materialize-diff.js +131 -73
- package/lib/mcp-tools.js +20 -14
- package/lib/pipeline-config.js +182 -0
- package/lib/private-temp.js +52 -0
- package/lib/promote-payload.js +77 -0
- package/lib/refresh-knowledge.js +15 -6
- package/lib/repo-tree.js +22 -3
- package/lib/run-context.js +698 -0
- package/lib/run-target.js +66 -1
- package/lib/sandbox-lifecycle.js +357 -0
- package/lib/schedule-cron.js +1 -1
- package/lib/skill-assertions.js +61 -7
- package/lib/skill-audit-render.js +1 -1
- package/lib/story-classifier-extract.js +16 -8
- package/lib/story-classifier.js +64 -0
- package/lib/story-compose.js +69 -0
- package/lib/story-line.js +40 -0
- package/lib/story-provenance.js +267 -0
- package/lib/timeout-option.js +42 -0
- package/lib/untrusted-code-env.js +51 -0
- package/lib/verdict-env.js +31 -0
- package/lib/verify-patch.js +74 -4
- package/lib/verify-pr.js +2 -1
- package/lib/worktree-provision.js +62 -2
- package/package.json +1 -1
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
/**
|
|
3
|
+
* promote-payload.js — what `hone promote-learnings` sends to `POST /learnings/promote`.
|
|
4
|
+
*
|
|
5
|
+
* Extracted by HC-GF-143's batch because HC-GF-136's gate found all seven fields computed inline
|
|
6
|
+
* and asserted by nothing: deleting any of them broke no test.
|
|
7
|
+
*
|
|
8
|
+
* This mapping is an IP BOUNDARY, which is why it is the one to extract first. A learning carries
|
|
9
|
+
* BOTH `local_summary` (this repo's specifics — paths, story ids, internal detail) and
|
|
10
|
+
* `enterprise_summary` (the generalised rule). Only the second may leave the machine. A rename, a
|
|
11
|
+
* copy-paste, or a spread of the whole object would ship the first, and nothing would have noticed.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/** Fields that may cross to the server. Anything not named here does not leave. */
|
|
15
|
+
const PROMOTED_FIELDS = Object.freeze([
|
|
16
|
+
'id', 'repo_name', 'type', 'agent', 'target_skill', 'target_kind', 'enterprise_summary',
|
|
17
|
+
'proposed_fix',
|
|
18
|
+
]);
|
|
19
|
+
|
|
20
|
+
/** A path that looks like a pipeline script, named in prose. */
|
|
21
|
+
// HC-GF-161: `tests` added. A learning whose fix is "extend this regression test" names a real,
|
|
22
|
+
// editable target in exactly the same way a script does, and 3 learnings were unroutable only
|
|
23
|
+
// because this list omitted it. The backticks and the extension list are NOT relaxed: the
|
|
24
|
+
// backticks are what stop this matching prose that merely mentions a file, and the extensions
|
|
25
|
+
// are what stop it claiming a doc, for which there is no kind.
|
|
26
|
+
const SCRIPT_PATH = /`((?:scripts|cli|server|tests|\.github)\/[\w./-]+\.(?:js|mjs|cjs|sh))`/;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* HC-GF-159a: WHAT a learning would change, and what it is called.
|
|
30
|
+
*
|
|
31
|
+
* A learning is not always about a skill. It can change a skill, an agent's PROMPT, or a pipeline
|
|
32
|
+
* SCRIPT — and sending only a field named `target_skill` is why all 418 promoted learnings routed
|
|
33
|
+
* to the server's `|| 'general'` fallback. The kind travels with the name so the far side can tell
|
|
34
|
+
* "change pipeline-integrity" from "change the code-reviewer prompt" from "change mutate-check.js".
|
|
35
|
+
*
|
|
36
|
+
* Order matters: a learning that names a skill targets the skill even when an agent captured it —
|
|
37
|
+
* `agent:` records WHO noticed, which is not the same as what should change.
|
|
38
|
+
*
|
|
39
|
+
* An unroutable learning says `unrouted` rather than borrowing a default. 'general' is a claim that
|
|
40
|
+
* the lesson belongs everywhere, which is how 418 of them became indistinguishable.
|
|
41
|
+
*/
|
|
42
|
+
function targetOf(l) {
|
|
43
|
+
// An explicitly-set `target_skill` is the most specific statement of intent there is — a caller
|
|
44
|
+
// that already decided. Deriving over the top of it silently overrode the decision, which the
|
|
45
|
+
// HC-GF-143b suite caught when its fixture's `target_skill` was replaced by the agent name.
|
|
46
|
+
if (l.target_skill) return { kind: 'skill', name: l.target_skill };
|
|
47
|
+
if (l.referenced_skill) return { kind: 'skill', name: l.referenced_skill };
|
|
48
|
+
if (l.agent) return { kind: 'prompt', name: l.agent };
|
|
49
|
+
const m = SCRIPT_PATH.exec(String(l.proposed_fix || ''));
|
|
50
|
+
if (m) return { kind: 'script', name: m[1] };
|
|
51
|
+
return { kind: 'unrouted', name: null };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* @param {Array<object>} eligible learnings selected for promotion
|
|
56
|
+
* @returns {Array<object>} one payload row per learning
|
|
57
|
+
*/
|
|
58
|
+
function promotePayload(eligible) {
|
|
59
|
+
return (eligible || []).map((l) => {
|
|
60
|
+
const target = targetOf(l);
|
|
61
|
+
return {
|
|
62
|
+
id: l.id || l._file,
|
|
63
|
+
repo_name: l.repo_name,
|
|
64
|
+
// HC-GF-159a: `type` and `agent` come from the parser now. They were mapped here from names
|
|
65
|
+
// the parser never emitted, so JSON.stringify dropped them and the server saw `unknown`.
|
|
66
|
+
type: l.type || null,
|
|
67
|
+
agent: l.agent || null,
|
|
68
|
+
// The NAME the far side routes on, whatever kind of thing it is.
|
|
69
|
+
target_skill: target.name,
|
|
70
|
+
target_kind: target.kind,
|
|
71
|
+
enterprise_summary: l.enterprise_summary,
|
|
72
|
+
proposed_fix: l.proposed_fix || l.absorbed_into || null,
|
|
73
|
+
};
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
module.exports = { promotePayload, PROMOTED_FIELDS, targetOf };
|
package/lib/refresh-knowledge.js
CHANGED
|
@@ -24,9 +24,12 @@
|
|
|
24
24
|
const fs = require('node:fs');
|
|
25
25
|
const path = require('node:path');
|
|
26
26
|
const crypto = require('node:crypto');
|
|
27
|
-
const {
|
|
27
|
+
const { execFileSync } = require('node:child_process');
|
|
28
28
|
const { gitEnv } = require('./git-env');
|
|
29
29
|
|
|
30
|
+
// A backup of the knowledge dirs is seconds of work; this only stops a stuck tar holding the run.
|
|
31
|
+
const TAR_TIMEOUT_MS = 2 * 60 * 1000;
|
|
32
|
+
|
|
30
33
|
// HC-019y (2026-05-29): removed `.github/agents` from refresh paths.
|
|
31
34
|
// Agents live exclusively at `.claude/agents/` (Claude Code's read path)
|
|
32
35
|
// after the install-time mirror was eliminated. `.github/agents/` is now
|
|
@@ -42,7 +45,7 @@ const ENTERPRISE_MANAGED_END_RE = /<!--\s*END ENTERPRISE-MANAGED\s*-->/;
|
|
|
42
45
|
const FRONTMATTER_MANAGED_RE = /^---[\s\S]*?managed_by:\s*\S+[\s\S]*?---/m;
|
|
43
46
|
|
|
44
47
|
/**
|
|
45
|
-
* Default channel runner — invokes `hone <name>` via
|
|
48
|
+
* Default channel runner — invokes `hone <name>` via execFileSync (argv, no shell).
|
|
46
49
|
* Tests inject a fake runner; real CLI uses this default.
|
|
47
50
|
*/
|
|
48
51
|
function defaultRunChannel(name, repoRoot) {
|
|
@@ -53,7 +56,9 @@ function defaultRunChannel(name, repoRoot) {
|
|
|
53
56
|
// down an ambient GIT_DIR — under a hook, `rebase -x`, or
|
|
54
57
|
// `submodule foreach` — the child would resolve those against the OUTER
|
|
55
58
|
// repo while we intended repoRoot. Sanitize at the boundary instead.
|
|
56
|
-
|
|
59
|
+
// HC-GF-097-followup-2: argv, not a shell string — double quotes around a path do not
|
|
60
|
+
// stop `$(...)` or backticks, and a channel name is one argument, not a command line.
|
|
61
|
+
const out = execFileSync('node', [cliPath, name], {
|
|
57
62
|
cwd: repoRoot,
|
|
58
63
|
env: gitEnv(),
|
|
59
64
|
encoding: 'utf8',
|
|
@@ -319,11 +324,14 @@ function createBackup({ repoRoot, backupDir, knowledgeFiles, now }) {
|
|
|
319
324
|
|
|
320
325
|
if (relPaths.length === 0) return null;
|
|
321
326
|
|
|
322
|
-
// Use system tar for portability
|
|
327
|
+
// Use system tar for portability. HC-GF-097-followup-2: argv, not a shell string. These are
|
|
328
|
+
// the adopter repo's own file names, and a double-quoted `$(...)` in one runs as a command.
|
|
329
|
+
// Every relPath starts with a KNOWLEDGE_DIRS entry, so none can be read as a tar option.
|
|
323
330
|
try {
|
|
324
|
-
|
|
331
|
+
execFileSync('tar', ['-czf', backupPath, ...relPaths], {
|
|
325
332
|
cwd: repoRoot,
|
|
326
333
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
334
|
+
timeout: TAR_TIMEOUT_MS,
|
|
327
335
|
});
|
|
328
336
|
} catch (e) {
|
|
329
337
|
throw new Error(`tar failed: ${e.message}`);
|
|
@@ -333,9 +341,10 @@ function createBackup({ repoRoot, backupDir, knowledgeFiles, now }) {
|
|
|
333
341
|
}
|
|
334
342
|
|
|
335
343
|
function restoreFromBackup({ repoRoot, backupPath }) {
|
|
336
|
-
|
|
344
|
+
execFileSync('tar', ['-xzf', backupPath], {
|
|
337
345
|
cwd: repoRoot,
|
|
338
346
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
347
|
+
timeout: TAR_TIMEOUT_MS,
|
|
339
348
|
});
|
|
340
349
|
}
|
|
341
350
|
|
package/lib/repo-tree.js
CHANGED
|
@@ -88,6 +88,16 @@ function renderTree(paths, { maxEntries = DEFAULT_MAX_ENTRIES, maxBytes = DEFAUL
|
|
|
88
88
|
|
|
89
89
|
const MAX_TRANSPORT_PATHS = 5000;
|
|
90
90
|
const MAX_TRANSPORT_DIRS = 3000;
|
|
91
|
+
// HC-GF-097: a BYTE bound on the two lists, because the count caps alone bound nothing a request
|
|
92
|
+
// body limit can be derived from — 5000 paths can be any length. Sized so the count caps are what
|
|
93
|
+
// normally decides: 8000 entries at 64 bytes each. This repo transports 2,452 paths + 227 dirs in
|
|
94
|
+
// 116,634 bytes (~44 bytes an entry, measured 2026-09-24), well inside it; a 64 KB cap would have
|
|
95
|
+
// dropped both lists here and switched off the server's `## Files` guard on every run. Over the
|
|
96
|
+
// cap the lists are omitted exactly as over the count caps: the server reads v1 and skips the guard.
|
|
97
|
+
const MAX_TRANSPORT_BYTES = (MAX_TRANSPORT_PATHS + MAX_TRANSPORT_DIRS) * 64;
|
|
98
|
+
// The whole transported tree: both lists, the rendered `text`, and the scalar and detected-dir
|
|
99
|
+
// fields (a few hundred bytes on a real repo; 16 KB leaves room for a pathological layout).
|
|
100
|
+
const MAX_TREE_TRANSPORT_BYTES = MAX_TRANSPORT_BYTES + DEFAULT_MAX_BYTES + 16 * 1024;
|
|
91
101
|
|
|
92
102
|
/**
|
|
93
103
|
* HC-019n-followup-43: every tracked directory repo-wide (minus generated/dependency dirs),
|
|
@@ -111,7 +121,7 @@ function allDirs(allPaths) {
|
|
|
111
121
|
* from v1 (prompt bytes stable). Both lists are capped; over the cap they are omitted and
|
|
112
122
|
* the server treats the tree as v1 (guard skipped, never a false rejection).
|
|
113
123
|
*/
|
|
114
|
-
function buildRepoTree(allPaths, { sourceDirs = [], testDirs = [], maxEntries, maxBytes } = {}) {
|
|
124
|
+
function buildRepoTree(allPaths, { sourceDirs = [], testDirs = [], maxEntries, maxBytes, maxTransportBytes = Infinity } = {}) {
|
|
115
125
|
const selected = selectPaths(allPaths, { sourceDirs, testDirs });
|
|
116
126
|
const r = renderTree(selected, { maxEntries, maxBytes });
|
|
117
127
|
const dirs = allDirs(allPaths);
|
|
@@ -130,7 +140,13 @@ function buildRepoTree(allPaths, { sourceDirs = [], testDirs = [], maxEntries, m
|
|
|
130
140
|
shown: r.shown,
|
|
131
141
|
text: r.text,
|
|
132
142
|
};
|
|
133
|
-
|
|
143
|
+
// The BYTE cap applies only when the caller passes one: `hone queue-stories`, whose request
|
|
144
|
+
// limit is derived from it. `hone run-story` sends one story and keeps the count caps alone, as
|
|
145
|
+
// before HC-GF-097 — review of #1100 found a 4,900-path repository (under both count caps) that
|
|
146
|
+
// the byte cap would have stripped of its lists, switching off the `## Files` guard on single runs.
|
|
147
|
+
if (selected.length <= MAX_TRANSPORT_PATHS && dirs.length <= MAX_TRANSPORT_DIRS
|
|
148
|
+
&& (maxTransportBytes === Infinity
|
|
149
|
+
|| Buffer.byteLength(JSON.stringify({ paths: selected, dirs }), 'utf8') <= maxTransportBytes)) {
|
|
134
150
|
tree.paths = selected; tree.dirs = dirs;
|
|
135
151
|
}
|
|
136
152
|
try {
|
|
@@ -145,4 +161,7 @@ function buildRepoTree(allPaths, { sourceDirs = [], testDirs = [], maxEntries, m
|
|
|
145
161
|
return tree;
|
|
146
162
|
}
|
|
147
163
|
|
|
148
|
-
module.exports = {
|
|
164
|
+
module.exports = {
|
|
165
|
+
selectPaths, renderTree, buildRepoTree, allDirs, DEFAULT_MAX_ENTRIES, DEFAULT_MAX_BYTES,
|
|
166
|
+
MAX_TRANSPORT_PATHS, MAX_TRANSPORT_DIRS, MAX_TRANSPORT_BYTES, MAX_TREE_TRANSPORT_BYTES,
|
|
167
|
+
};
|