@hone-ai/cli 1.38.1 → 1.40.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 +218 -157
- package/lib/anthropic-response.js +58 -0
- package/lib/batch-follow.js +641 -53
- package/lib/block-grammar.js +55 -0
- package/lib/branch-protection.js +141 -25
- package/lib/bundle-paths.js +77 -13
- package/lib/call-sites.js +489 -0
- package/lib/classify-spec.js +68 -0
- package/lib/context-reserve.js +270 -0
- package/lib/detect-test-framework.js +54 -0
- package/lib/hard-pause-remedy.js +1 -1
- package/lib/judge-provider.js +20 -11
- package/lib/learnings-sync.js +2 -1
- package/lib/release-review-config.js +3 -2
- package/lib/required-checks.js +354 -0
- package/lib/reviewer-optin.js +115 -0
- package/lib/run-context.js +21 -0
- package/lib/security-scanner.js +7 -4
- package/lib/story-line.js +48 -12
- package/lib/verify-patch.js +122 -12
- package/package.json +1 -1
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
/**
|
|
3
|
+
* HC-GF-337f — read an Anthropic Messages response without losing half of it.
|
|
4
|
+
*
|
|
5
|
+
* Every CLI path used to take `data.content[0].text`. Once a model can emit a thinking block
|
|
6
|
+
* FIRST, block 0 is `{type: 'thinking'}` and that expression is `undefined`: an empty review or
|
|
7
|
+
* verdict reported as a content filter or a quota trip. The HC-GF-085 defect, on the CLI side.
|
|
8
|
+
*
|
|
9
|
+
* Returns a discriminated result; the caller owns its own exit semantics (skill-eval throws inside
|
|
10
|
+
* callLLM, release-review emits a status envelope, the judge throws so the scenario is an error and
|
|
11
|
+
* not a failed criterion). Nothing here throws, so the outcomes can be asserted in one table.
|
|
12
|
+
*
|
|
13
|
+
* { ok: true, text, stopReason }
|
|
14
|
+
* { ok: false, kind: 'refusal', category, stopReason, message } // HTTP 200, stop_reason "refusal"
|
|
15
|
+
* { ok: false, kind: 'truncated', stopReason, partialText, message } // cut off at the cap; partialText is null when there is none
|
|
16
|
+
* { ok: false, kind: 'no_text', stopReason, message } // blocks, but none with text
|
|
17
|
+
*
|
|
18
|
+
* Order is part of the contract: a refusal is decided FIRST (it can arrive with a thinking block and
|
|
19
|
+
* no text, and must never read as `no_text`). Then `max_tokens`: a response cut off at the cap is
|
|
20
|
+
* `truncated` EVEN WHEN it carries text, because a cut-off answer can be well-formed and wrong (a review
|
|
21
|
+
* cut off before its verdict). A caller whose success means approval (release-review) therefore gets
|
|
22
|
+
* the safe answer by default. A caller for which a partial answer is harmless (a judge verdict that
|
|
23
|
+
* defaults to NO, a skill-eval answer that fails its keywords) opts in with `allowPartial: true` and
|
|
24
|
+
* then gets the text back as `ok`. The first non-blank text block wins otherwise.
|
|
25
|
+
*
|
|
26
|
+
* Every non-ok message names the label, `stop_reason:` and, when the API gave one, the refusal category.
|
|
27
|
+
* Messages begin `<label> returned no text (stop_reason: ...)` when there is no text at all: callers and
|
|
28
|
+
* tests match that prefix.
|
|
29
|
+
*
|
|
30
|
+
* ADOPTER SURFACE (HC-LC-001): this file ships in the published CLI. It is a pure classifier: no
|
|
31
|
+
* I/O, no local Claude binary.
|
|
32
|
+
*
|
|
33
|
+
* @param {object} data the parsed response body
|
|
34
|
+
* @param {{label?: string, allowPartial?: boolean}} [opts] label: who is asking, so three call sites read
|
|
35
|
+
* differently in a log. allowPartial: accept text from a response cut off at max_tokens (default false).
|
|
36
|
+
*/
|
|
37
|
+
function readAnthropicText(data, { label, allowPartial = false } = {}) {
|
|
38
|
+
const who = label || 'the provider';
|
|
39
|
+
const stopReason = (data && data.stop_reason) || 'unknown';
|
|
40
|
+
const category = (data && data.stop_details && data.stop_details.category) || null;
|
|
41
|
+
const blocks = Array.isArray(data && data.content) ? data.content : [];
|
|
42
|
+
const cause = `stop_reason: ${stopReason}${category ? `, category: ${category}` : ''}`;
|
|
43
|
+
|
|
44
|
+
if (stopReason === 'refusal') {
|
|
45
|
+
return { ok: false, kind: 'refusal', category, stopReason, message: `${who} returned no text (${cause}): the model declined the request` };
|
|
46
|
+
}
|
|
47
|
+
const block = blocks.find((b) => b && b.type === 'text' && typeof b.text === 'string' && b.text.trim().length > 0);
|
|
48
|
+
if (stopReason === 'max_tokens' && !(block && allowPartial)) {
|
|
49
|
+
const message = block
|
|
50
|
+
? `${who} returned an incomplete answer (${cause}): it was cut off at max_tokens and may be missing its ending; raise max_tokens`
|
|
51
|
+
: `${who} returned no text (${cause}): the answer was cut off at max_tokens before any text, thinking included; raise max_tokens`;
|
|
52
|
+
return { ok: false, kind: 'truncated', stopReason, partialText: block ? block.text : null, message };
|
|
53
|
+
}
|
|
54
|
+
if (block) return { ok: true, text: block.text, stopReason };
|
|
55
|
+
return { ok: false, kind: 'no_text', stopReason, message: `${who} returned no text (${cause})` };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
module.exports = { readAnthropicText };
|