@hone-ai/cli 1.29.0 → 1.30.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.
Files changed (2) hide show
  1. package/lib/run-report.js +104 -2
  2. package/package.json +1 -1
package/lib/run-report.js CHANGED
@@ -60,6 +60,102 @@ function buildReport(run, extra = {}) {
60
60
  });
61
61
  }
62
62
 
63
+ // 1b. HC-GF-044: a STEP can be failed while the RUN says completed.
64
+ //
65
+ // Everything above keys on `run.status`. Nothing read `steps[].status`, so a run
66
+ // reported `completed` that contained a `failed` step produced ZERO items and the
67
+ // empty-state branch then printed the affirmative "nothing outstanding". Observed on
68
+ // run 08c27392: `Status: completed` / `Error: MAX_RETRIES_EXCEEDED: step_5 retried 2
69
+ // times` / `✗ step_5 (code-reviewer) failed`, and then `NEEDS YOU (0)`. The STEPS block
70
+ // below rendered the failure correctly, so the data was present and read — only the
71
+ // actionable layer missed it.
72
+ //
73
+ // This is the exact class HC-GF-016 exists to prevent, and the sharp part is that its
74
+ // empty-state rule was HONOURED: the section rendered and named what it checked. It
75
+ // checked the wrong field. A well-formed "nothing" is only as true as its predicate.
76
+ //
77
+ // DELIBERATELY INDEPENDENT OF HC-GF-043. That story stops the orchestrator writing
78
+ // `completed` over a failed step, which would make THIS run fire here via the branch
79
+ // above. It would not help a `completed` run whose step failed for any other reason, or
80
+ // a report built from a run recorded before that fix shipped. Both are needed; neither
81
+ // substitutes for the other.
82
+ //
83
+ // Keyed on the step's OWN state, and `rejected` counts alongside `failed`: a rejected
84
+ // gate is a human decision awaiting follow-up, not a null result.
85
+ const brokenSteps = steps.filter(s => s.status === 'failed' || s.gateResult === 'rejected');
86
+ for (const s of brokenSteps) {
87
+ // Skip the one the run-level item already names, so one problem is one entry.
88
+ if (run.status === 'failed' && s.stepKey === run.currentStep) continue;
89
+ // HC-GF-036(b): a step that RETURNED AN ADVERSE VERDICT did not break. Saying
90
+ // "code-reviewer FAILED" for a reviewer that ran correctly and said no is the same
91
+ // conflation that was inflating batch failure_rates — and it points the operator at
92
+ // the wrong thing to fix. The verdict kinds name the agent's own vocabulary.
93
+ const kind = (s.diagnostics && s.diagnostics.failure_record || {}).failure_kind;
94
+ const VERDICT = {
95
+ review_reject: 'returned a FAIL review',
96
+ security_high: 'raised a HIGH security finding',
97
+ security_medium: 'raised a MEDIUM security finding',
98
+ test_fail: 'reported failing tests',
99
+ lint_fail: 'reported lint errors',
100
+ perf_finding: 'raised a performance finding',
101
+ validator_rejected: 'produced output the format validator rejected',
102
+ };
103
+ const what = kind && VERDICT[kind]
104
+ ? `${s.stepKey} (${s.agent || 'agent'}) ${VERDICT[kind]}`
105
+ : `${s.stepKey} (${s.agent || 'agent'}) ${s.status === 'failed' ? 'FAILED' : 'was REJECTED'}`;
106
+ needsYou.push({
107
+ what: `${what} — the run is recorded ${run.status}, so nothing downstream will revisit it`,
108
+ how: `hone show step ${run.runId} ${s.stepKey}`,
109
+ howLabel: 'inspect',
110
+ });
111
+ }
112
+
113
+ // 1c. HC-GF-041: a retry that came back with LESS than the attempt it replaces.
114
+ //
115
+ // The newest attempt REPLACES the previous one for every downstream consumer, so this
116
+ // silently deletes work. Twice it was caught only because an agent refused to treat
117
+ // absence as evidence — step_5d's `UNVERIFIED-HIGH / source-unavailable`, and a
118
+ // reviewer's "I will not approve a P0 reliability primitive I have not read". Neither
119
+ // is a mechanism; both were luck. This is the mechanism.
120
+ //
121
+ // NEEDS YOU, not WORTH KNOWING: the artifact every later step reads is not the one a
122
+ // human approved, and only a human can say whether that is acceptable.
123
+ for (const s of steps) {
124
+ const reg = s.diagnostics && s.diagnostics.artifact_regression;
125
+ if (!reg) continue;
126
+ needsYou.push({
127
+ what: `${s.stepKey} attempt ${s.attempt} carries less than the attempt it replaces`
128
+ + ` — and it is the one every later step reads`,
129
+ detail: (reg.reasons || []).map(String),
130
+ how: `hone show step ${run.runId} ${s.stepKey} --attempt ${s.attempt}`,
131
+ howLabel: 'compare',
132
+ });
133
+ }
134
+
135
+ // 1d. HC-GF-041: two diagnostics the orchestrator has stamped since HC-019n-followup-41
136
+ // and -43, which NOTHING has ever read on this path — the run payload carried only
137
+ // token counts until the `diagnostics` projection was added. HC-GF-016's row specifies
138
+ // these be rendered; this is the first build where they can be.
139
+ //
140
+ // Both are written ONLY when non-empty, so an absent key means "checked, none found"
141
+ // and must not be rendered as a reassuring zero — silence here is correct, an invented
142
+ // "none" would be a claim the report cannot support for a run that never ran the check.
143
+ for (const s of steps) {
144
+ const d = s.diagnostics || {};
145
+ if (Array.isArray(d.invented_paths) && d.invented_paths.length) {
146
+ needsYou.push({
147
+ what: `${s.stepKey} named ${d.invented_paths.length} path(s) that do not exist in the repo, and was accepted anyway`,
148
+ detail: d.invented_paths.map(String),
149
+ how: `hone show step ${run.runId} ${s.stepKey}`,
150
+ howLabel: 'inspect',
151
+ });
152
+ }
153
+ if (Array.isArray(d.composed_missing) && d.composed_missing.length) {
154
+ worthKnowing.push(
155
+ `${s.stepKey} dropped composed guardrail section(s) on a retry and was accepted (budget spent): ${d.composed_missing.join(', ')}`);
156
+ }
157
+ }
158
+
63
159
  // 2. The last verification is ONE event, so it becomes ONE item with sub-lines.
64
160
  // Listing "verification failed" and "lint failed" separately gave two entries
65
161
  // for one problem resolved by one fix — and NEEDS YOU is a list of DISTINCT
@@ -92,10 +188,16 @@ function buildReport(run, extra = {}) {
92
188
  }
93
189
 
94
190
  // ── WORTH KNOWING ──────────────────────────────────────────────────────────
95
- // Retries: the step produced something the pipeline rejected at least once.
191
+ // Retries: the step ran more than once. HC-GF-041(c): this used to append "(its
192
+ // first output was rejected)", which the report never checked and which is often
193
+ // FALSE — on run 08c27392 step_4's attempt 1 was `completed | approved` and it was
194
+ // attempt 2 that got rejected; a route-back retries a step whose first output was
195
+ // fine. Narrating an unchecked cause inside the tool built to be derived-never-
196
+ // narrated is the defect HC-GF-016 exists to prevent. State the count, which is
197
+ // measured, and let the regression check below say when something is wrong.
96
198
  for (const s of steps) {
97
199
  if (Number(s.attempts) > 1) {
98
- worthKnowing.push(`${s.stepKey} retried — ${s.attempts} attempts (its first output was rejected)`);
200
+ worthKnowing.push(`${s.stepKey} ran ${s.attempts} times`);
99
201
  }
100
202
  }
101
203
  // The two "blind run" tells, both from HC-GF-006's preflight reasoning.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hone-ai/cli",
3
- "version": "1.29.0",
3
+ "version": "1.30.0",
4
4
  "description": "Hone AI — Enterprise SDLC Pipeline CLI",
5
5
  "main": "hone-cli.js",
6
6
  "bin": {