@haystackeditor/cli 0.15.6 → 0.15.8

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.
@@ -29,6 +29,8 @@ After submitting, use `haystack triage <pr-number>` to check analysis results (r
29
29
  - Do not add silent fallbacks.
30
30
  - Do not add backwards compatibility, unless you confirmed with the user first that it's desired.
31
31
  - Do not add TODOs and incomplete code unless they have been explicitly flagged to the user.
32
+ - When the user asks for a general or systematic fix, do not satisfy it with a check that only catches the known example. Implement the requested class of protection, or explicitly say what remains unfinished.
33
+ - Finish both sides of cross-boundary implementations. When adding a new keyed capability, mode, event, route, config value, feature flag, enum variant, or serialized field, verify and update every required consumer, producer, registry, allow-list, schema, persistence path, and delivery surface. If the existing system requires "also register it in X", do that wiring or explicitly report that it is intentionally out of scope.
32
34
 
33
35
  ## No Truncation Without Permission
34
36
 
@@ -56,6 +56,10 @@ function buildAnswerMap(options) {
56
56
  answers.upgrade_entire = true;
57
57
  if (!('install_hooks' in answers))
58
58
  answers.install_hooks = true;
59
+ // Don't auto-launch a browser in an unattended --yes run; the feed URL is
60
+ // still printed and returned in the result for the caller to open.
61
+ if (!('open_feed' in answers))
62
+ answers.open_feed = false;
59
63
  }
60
64
  return answers;
61
65
  }
@@ -1120,11 +1124,51 @@ async function runSetupFlow(options) {
1120
1124
  else {
1121
1125
  await stepInstallEntire(selectedRepos, token, bootstrapPRs);
1122
1126
  }
1123
- // Final structured outcome for non-interactive/agent callers.
1127
+ // Close the loop with ONE prominent "Next steps" block. The two things that
1128
+ // actually matter next (merge the config PR, then open the feed) land here
1129
+ // together, not dimmed and not scattered mid-run, so a human scanning the
1130
+ // terminal can't miss them. In JSON mode these console writes go to stderr,
1131
+ // so a driving agent instead gets the same content as `summary` in the
1132
+ // `result` event to relay verbatim.
1133
+ const feedUrl = `${HAYSTACK_API}/inbox`;
1134
+ const prs = [...bootstrapPRs.entries()].map(([repo, pr]) => ({ repo, url: pr.prUrl }));
1135
+ const prLabel = prs.length === 1
1136
+ ? 'Review and merge your Configure Haystack PR:'
1137
+ : 'Review and merge your Configure Haystack PRs:';
1138
+ // Plain-text summary an agent can print as-is (no ANSI codes).
1139
+ const summaryLines = [];
1140
+ if (prs.length > 0) {
1141
+ summaryLines.push(prLabel);
1142
+ for (const { repo, url } of prs)
1143
+ summaryLines.push(` ${repo}: ${url}`);
1144
+ summaryLines.push('');
1145
+ }
1146
+ summaryLines.push('Then open your Haystack feed (where your PRs surface for review):');
1147
+ summaryLines.push(` ${feedUrl}`);
1148
+ const summary = summaryLines.join('\n');
1149
+ // Styled version for the human at a TTY.
1150
+ console.log(chalk.green.bold('\n Next steps\n'));
1151
+ if (prs.length > 0) {
1152
+ console.log(` ${prLabel}`);
1153
+ for (const { repo, url } of prs)
1154
+ console.log(` ${chalk.bold(repo)} ${chalk.cyan(url)}`);
1155
+ console.log('');
1156
+ }
1157
+ console.log(' Then open your Haystack feed (where your PRs surface for review):');
1158
+ console.log(` ${chalk.cyan(feedUrl)}\n`);
1159
+ if (ui.interactive) {
1160
+ const openFeed = await ui.confirm({ id: 'open_feed', message: 'Open your Haystack feed now?', default: true });
1161
+ if (openFeed)
1162
+ tryOpenBrowser(feedUrl);
1163
+ }
1164
+ // Final structured outcome for non-interactive/agent callers. `summary` is the
1165
+ // ready-to-print Next steps text so a driving agent can surface it verbatim.
1124
1166
  ui.result({
1125
1167
  status: 'complete',
1126
1168
  repos: selectedRepos,
1127
- pullRequests: [...bootstrapPRs.entries()].map(([repo, pr]) => ({ repo, url: pr.prUrl })),
1169
+ pullRequests: prs,
1170
+ feedUrl,
1171
+ summary,
1128
1172
  });
1129
1173
  }
1130
1174
  // =============================================================================
@@ -68,20 +68,32 @@ function parsePRIdentifier(identifier) {
68
68
  // ============================================================================
69
69
  // Output formatting
70
70
  // ============================================================================
71
+ /**
72
+ * True if the finding is one the auto-fixer is already handling (and thus
73
+ * shouldn't be re-fixed). Correlation is by exact finding id — the same stable id
74
+ * analysis assigns and the fixer echoes back in its manifest. We never compare
75
+ * summary/category text.
76
+ */
77
+ function isAutoFixing(finding, manifest) {
78
+ if (!finding.id)
79
+ return false;
80
+ const items = manifest?.fixedItems ?? [];
81
+ return items.some((item) => item.id === finding.id);
82
+ }
71
83
  function printRichOutput(pr, synthesis, manifest) {
72
84
  const reviewUrl = `https://haystackeditor.com/review/${pr.owner}/${pr.repo}/${pr.prNumber}`;
73
85
  console.log(`\n ${chalk.bold(`Haystack Triage: ${pr.owner}/${pr.repo}#${pr.prNumber}`)}\n`);
74
86
  // Auto-fixer status — what's being fixed vs what it skipped
75
87
  if (manifest && (manifest.fixedItems.length > 0 || manifest.leftForReview.length > 0)) {
76
88
  if (manifest.fixedItems.length > 0) {
77
- console.log(chalk.bold(' Auto-fixer is handling:\n'));
89
+ console.log(chalk.bold(' Auto-fixer is handling these (leave them alone):\n'));
78
90
  for (const item of manifest.fixedItems) {
79
91
  console.log(` - [${item.category}] ${item.summary}`);
80
92
  }
81
- console.log('');
93
+ console.log(` ${chalk.dim("Don't fix these. The auto-fixer is already on them and will push its own commit.")}\n`);
82
94
  }
83
95
  if (manifest.leftForReview.length > 0) {
84
- console.log(chalk.bold(' Auto-fixer skipped:\n'));
96
+ console.log(chalk.bold(' Auto-fixer skipped these (they need you):\n'));
85
97
  for (const item of manifest.leftForReview) {
86
98
  console.log(` - [${item.category}] ${item.summary}`);
87
99
  console.log(` ${chalk.dim(item.reason)}`);
@@ -89,10 +101,13 @@ function printRichOutput(pr, synthesis, manifest) {
89
101
  console.log('');
90
102
  }
91
103
  }
92
- // Findings — the actionable output for agents
93
- const findings = synthesis.synthesisDisplay || [];
104
+ // Findings — the actionable output for agents. Exclude anything the auto-fixer
105
+ // is already handling so the agent doesn't duplicate (and fight) its work.
106
+ const allFindings = synthesis.synthesisDisplay || [];
107
+ const findings = allFindings.filter((f) => !isAutoFixing(f, manifest));
108
+ const fixingCount = manifest?.fixedItems?.length ?? 0;
94
109
  if (findings.length > 0) {
95
- console.log(chalk.bold(' Findings:\n'));
110
+ console.log(chalk.bold(' Findings to address:\n'));
96
111
  for (let i = 0; i < findings.length; i++) {
97
112
  const finding = findings[i];
98
113
  const sourceTag = finding.source ? ` (${finding.source})` : '';
@@ -112,7 +127,11 @@ function printRichOutput(pr, synthesis, manifest) {
112
127
  console.log('');
113
128
  }
114
129
  }
115
- else if (!manifest || manifest.fixedItems.length === 0) {
130
+ else if (fixingCount > 0 && allFindings.length > 0) {
131
+ // Every finding is being auto-fixed — nothing left for the agent.
132
+ console.log(chalk.dim(' Nothing to address. Every finding is being auto-fixed (see above).\n'));
133
+ }
134
+ else {
116
135
  console.log(chalk.dim(' No findings.\n'));
117
136
  }
118
137
  console.log(` ${chalk.dim(reviewUrl)}\n`);
@@ -123,6 +142,7 @@ function printJsonOutput(pr, synthesis, manifest) {
123
142
  repo: pr.repo,
124
143
  prNumber: pr.prNumber,
125
144
  rating: synthesis.haystackRating,
145
+ // Issues the auto-fixer is already on — leave these alone, do not re-fix.
126
146
  autoFixerHandling: manifest?.fixedItems ?? [],
127
147
  autoFixerSkipped: manifest?.leftForReview ?? [],
128
148
  findings: (synthesis.synthesisDisplay || []).map((f) => ({
@@ -131,6 +151,8 @@ function printJsonOutput(pr, synthesis, manifest) {
131
151
  detail: f.detail,
132
152
  agentFixPrompt: f.agentFixPrompt || null,
133
153
  source: f.source || null,
154
+ // True when the auto-fixer is handling this finding — don't fix it yourself.
155
+ autoFixing: isAutoFixing(f, manifest),
134
156
  })),
135
157
  };
136
158
  console.log(JSON.stringify(output, null, 2));
@@ -159,16 +181,19 @@ function printHookOutput(pr, synthesis, manifest, status) {
159
181
  console.log(`[Haystack] ${label}: Analysis in progress...`);
160
182
  return;
161
183
  }
162
- const findings = synthesis.synthesisDisplay || [];
163
184
  const stars = ratingStars(synthesis.haystackRating);
164
185
  const fixingCount = manifest?.fixedItems?.length ?? 0;
165
- if (findings.length === 0 && fixingCount === 0) {
186
+ // Only count findings the auto-fixer is NOT handling \u2014 those are what need the user.
187
+ const actionable = (synthesis.synthesisDisplay || []).filter((f) => !isAutoFixing(f, manifest));
188
+ if (actionable.length === 0 && fixingCount === 0) {
166
189
  console.log(`[Haystack] \u2705 ${label} ${stars}: Good to merge`);
167
190
  }
191
+ else if (actionable.length === 0) {
192
+ // Everything is being auto-fixed \u2014 nothing for the user to do.
193
+ console.log(`[Haystack] \u2705 ${label} ${stars}: ${fixingCount} being auto-fixed, nothing for you to do`);
194
+ }
168
195
  else {
169
- const parts = [];
170
- if (findings.length > 0)
171
- parts.push(`${findings.length} finding(s)`);
196
+ const parts = [`${actionable.length} finding(s)`];
172
197
  if (fixingCount > 0)
173
198
  parts.push(`${fixingCount} being auto-fixed`);
174
199
  console.log(`[Haystack] \u26A0\uFE0F ${label} ${stars}: Needs your input (${parts.join(', ')})`);
@@ -259,6 +259,7 @@ ${traceFileList}
259
259
  2. From each file, identify:
260
260
  - The user's original instruction/prompt (the first message)
261
261
  - Any follow-up instructions or corrections from the user
262
+ - The final effective scope after later corrections or reframes
262
263
 
263
264
  3. ${precomputedDiff ? 'Review the diff below' : `Run \`git diff ${baseBranch}...HEAD\``} to see what was actually implemented.
264
265
 
@@ -274,7 +275,11 @@ The agent implemented something DIFFERENT from what was asked:
274
275
  ### Incomplete Fulfillment
275
276
  The agent didn't finish everything that was asked:
276
277
  - User requested 3 things, agent only did 2
278
+ - User requested a general/systematic guardrail or end-to-end behavior, but the diff only handles one known instance, one special case, or one side of the required wiring
279
+ - User initially mentioned a known example, then clarified "not the specific case" / "the general class"; the diff still implements only the known example or category-specific check
277
280
  - Interface fields declared but never populated
281
+ - A new keyed capability, event, route, config value, enum variant, or serialized field is referenced on one side of a boundary but the required registry, producer, consumer, schema, handler, persistence path, or delivery surface is missing
282
+ - A change appears to work through local/dev/test defaults, mocks, or overrides, but the production wiring path needed to deliver the requested behavior was not updated
278
283
  - Functions stubbed with TODO/placeholder comments
279
284
  - Agent said "I'll skip X for now" for something the user explicitly requested
280
285
  - Tests not written when user asked for tests
@@ -297,6 +302,7 @@ Do NOT flag internal implementation choices with no observable effect (naming, f
297
302
  ### Ignored Correction
298
303
  The user gave an explicit correction or redirection and the final code does NOT honor it:
299
304
  - User said "don't use a global / use X instead / that's racy, do Y" and the agent shipped the thing it was told not to
305
+ - User said the target is the general class rather than the specific instance, but the agent still shipped only the instance-specific implementation
300
306
  - Agent applied the correction, then quietly reverted it in a later step
301
307
  - A general later "looks good" does NOT cancel a specific earlier correction
302
308
  This is high severity by default: the user actively steered and was overridden.
@@ -322,6 +328,10 @@ Compare each concrete claim the agent made to the user against what the diff act
322
328
  - TODO, FIXME, placeholder, stub comments in new code
323
329
  - Empty function bodies or early returns
324
330
  - Interface fields that are declared but never assigned anywhere
331
+ - Feature-specific or instance-specific checks added after the user asked for a general guardrail against a broader failure mode
332
+ - Earlier narrow examples treated as the whole task even though later user messages broadened or generalized the requested scope
333
+ - New string-keyed names, enum variants, serialized fields, action types, routes, events, or config keys that have no matching entry in the surrounding registry, allow-list, schema, handler, producer, consumer, or template path
334
+ - Comments or neighboring code saying "must also register/list/wire this" where the diff updated only the reference side
325
335
  - New mechanisms (cooldowns, retries, caches, rate limits) not requested by the user
326
336
  - Decisions that drop, limit, or reshape data flowing to the output, or pick a default that changes what end-users see, with no instruction specifying it
327
337
 
@@ -26,6 +26,9 @@ export interface AnalysisIssue {
26
26
  source: 'bug-detection' | 'rule-violation';
27
27
  }
28
28
  export interface SynthesisDisplayIssue {
29
+ /** Stable finding id (assigned at synthesis). Used to correlate with the
30
+ * auto-fix manifest by exact match instead of comparing text. */
31
+ id?: string;
29
32
  category: string;
30
33
  summary: string;
31
34
  detail: string;
@@ -110,10 +113,12 @@ export interface AutoFixManifest {
110
113
  repo: string;
111
114
  prNumber: number;
112
115
  fixedItems: Array<{
116
+ id?: string;
113
117
  category: string;
114
118
  summary: string;
115
119
  }>;
116
120
  leftForReview: Array<{
121
+ id?: string;
117
122
  category: string;
118
123
  summary: string;
119
124
  reason: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@haystackeditor/cli",
3
- "version": "0.15.6",
3
+ "version": "0.15.8",
4
4
  "description": "Set up Haystack for your project — automated PR review, triage, and merge queue",
5
5
  "type": "module",
6
6
  "bin": {