@haystackeditor/cli 0.15.7 → 0.15.9

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
 
@@ -1032,9 +1032,10 @@ async function stepConfirm(byRepo, token) {
1032
1032
  // waits for the user. Step 7 ensures .entire/settings.json rode along in
1033
1033
  // the same commit.
1034
1034
  bootstrapPRs.set(repoFullName, writeResult.bootstrapPR);
1035
- const note = autoMerge ? ' (opened a PR)' : ' (opened a PR for you to review and merge)';
1036
- console.log(chalk.green(` ✓ ${repoFullName}`) + chalk.dim(note));
1037
- console.log(chalk.dim(` ${writeResult.bootstrapPR.prUrl}`));
1035
+ // Don't print the GitHub PR URL: the user reviews and merges from their
1036
+ // Haystack feed, not the GitHub PR page. The feed link is the single CTA
1037
+ // in the Next steps block below.
1038
+ console.log(chalk.green(` ✓ ${repoFullName}`) + chalk.dim(' (configured)'));
1038
1039
  }
1039
1040
  catch (err) {
1040
1041
  ui.clearProgress();
@@ -1124,23 +1125,37 @@ async function runSetupFlow(options) {
1124
1125
  else {
1125
1126
  await stepInstallEntire(selectedRepos, token, bootstrapPRs);
1126
1127
  }
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.
1128
+ // Close the loop with ONE prominent CTA: the Haystack feed. The Configure
1129
+ // Haystack PR (and every PR after it) is reviewed and merged from the feed,
1130
+ // NOT the GitHub PR page, so the feed is the only link we surface. In JSON
1131
+ // mode these console writes go to stderr, so a driving agent instead gets the
1132
+ // same content as `summary` in the `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
+ // Plain-text summary an agent can print as-is (no ANSI codes).
1136
+ const summary = [
1137
+ 'Open your Haystack feed to review and merge your Configure Haystack PR:',
1138
+ ` ${feedUrl}`,
1139
+ ].join('\n');
1140
+ // Styled version for the human at a TTY.
1141
+ console.log(chalk.green.bold('\n Next steps\n'));
1142
+ console.log(' Open your Haystack feed to review and merge your Configure Haystack PR:');
1143
+ console.log(` ${chalk.cyan(feedUrl)}\n`);
1133
1144
  if (ui.interactive) {
1134
1145
  const openFeed = await ui.confirm({ id: 'open_feed', message: 'Open your Haystack feed now?', default: true });
1135
1146
  if (openFeed)
1136
1147
  tryOpenBrowser(feedUrl);
1137
1148
  }
1138
- // Final structured outcome for non-interactive/agent callers.
1149
+ // Final structured outcome for non-interactive/agent callers. `summary` is the
1150
+ // ready-to-print Next steps text so a driving agent can surface it verbatim.
1151
+ // `pullRequests` stays as structured metadata (programmatic use), but the
1152
+ // human-facing copy points only to the feed.
1139
1153
  ui.result({
1140
1154
  status: 'complete',
1141
1155
  repos: selectedRepos,
1142
- pullRequests: [...bootstrapPRs.entries()].map(([repo, pr]) => ({ repo, url: pr.prUrl })),
1156
+ pullRequests: prs,
1143
1157
  feedUrl,
1158
+ summary,
1144
1159
  });
1145
1160
  }
1146
1161
  // =============================================================================
@@ -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.9",
4
4
  "description": "Set up Haystack for your project — automated PR review, triage, and merge queue",
5
5
  "type": "module",
6
6
  "bin": {