@haystackeditor/cli 0.15.7 → 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
 
@@ -1124,23 +1124,51 @@ async function runSetupFlow(options) {
1124
1124
  else {
1125
1125
  await stepInstallEntire(selectedRepos, token, bootstrapPRs);
1126
1126
  }
1127
- // Close the loop: point the user to their Haystack feed (the inbox/card stack
1128
- // where their PRs surface). Interactive users get offered a browser open; an
1129
- // agent gets the URL in the `result` event to surface/open as it sees fit.
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.
1130
1133
  const feedUrl = `${HAYSTACK_API}/inbox`;
1131
- console.log(chalk.bold('\n Your Haystack feed:'));
1132
- console.log(` ${chalk.cyan(feedUrl)}\n`);
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`);
1133
1159
  if (ui.interactive) {
1134
1160
  const openFeed = await ui.confirm({ id: 'open_feed', message: 'Open your Haystack feed now?', default: true });
1135
1161
  if (openFeed)
1136
1162
  tryOpenBrowser(feedUrl);
1137
1163
  }
1138
- // Final structured outcome for non-interactive/agent callers.
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.
1139
1166
  ui.result({
1140
1167
  status: 'complete',
1141
1168
  repos: selectedRepos,
1142
- pullRequests: [...bootstrapPRs.entries()].map(([repo, pr]) => ({ repo, url: pr.prUrl })),
1169
+ pullRequests: prs,
1143
1170
  feedUrl,
1171
+ summary,
1144
1172
  });
1145
1173
  }
1146
1174
  // =============================================================================
@@ -69,38 +69,16 @@ function parsePRIdentifier(identifier) {
69
69
  // Output formatting
70
70
  // ============================================================================
71
71
  /**
72
- * Decide whether a finding is one the auto-fixer is already handling (and thus
73
- * shouldn't be re-fixed). The fixer re-derives often paraphrasesits manifest
74
- * summaries rather than copying them verbatim, so exact string matching misses
75
- * them. This mirrors the normalized + fuzzy matching the backend uses in
76
- * agent/cloudflare/src/auto-fix-post-synthesis.ts (normalizeText / categoriesCompatible).
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 idthe same stable id
74
+ * analysis assigns and the fixer echoes back in its manifest. We never compare
75
+ * summary/category text.
77
76
  */
78
- function normalizeText(value) {
79
- return value.toLowerCase().replace(/\s+/g, ' ').trim();
80
- }
81
- /** Categories match if equal, or if one's tokens are a subset of the other's. */
82
- function categoriesCompatible(left, right) {
83
- const a = normalizeText(left);
84
- const b = normalizeText(right);
85
- if (a === b)
86
- return true;
87
- const aTokens = a.split(/[^a-z0-9]+/).filter(Boolean);
88
- const bTokens = b.split(/[^a-z0-9]+/).filter(Boolean);
89
- const aSet = new Set(aTokens);
90
- const bSet = new Set(bTokens);
91
- return ((bTokens.length > 0 && bTokens.every((t) => aSet.has(t))) ||
92
- (aTokens.length > 0 && aTokens.every((t) => bSet.has(t))));
93
- }
94
- /** True if the finding matches any item the auto-fixer is handling. */
95
77
  function isAutoFixing(finding, manifest) {
96
- const items = manifest?.fixedItems ?? [];
97
- if (items.length === 0)
78
+ if (!finding.id)
98
79
  return false;
99
- const summaryKey = normalizeText(finding.summary);
100
- if (summaryKey.length === 0)
101
- return false;
102
- return items.some((item) => normalizeText(item.summary) === summaryKey &&
103
- categoriesCompatible(finding.category, item.category));
80
+ const items = manifest?.fixedItems ?? [];
81
+ return items.some((item) => item.id === finding.id);
104
82
  }
105
83
  function printRichOutput(pr, synthesis, manifest) {
106
84
  const reviewUrl = `https://haystackeditor.com/review/${pr.owner}/${pr.repo}/${pr.prNumber}`;
@@ -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.7",
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": {