@haystackeditor/cli 0.15.26 → 0.15.28

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 (87) hide show
  1. package/README.md +52 -23
  2. package/dist/assets/hooks/agent-context/detect.ts +7 -7
  3. package/dist/assets/hooks/agent-context/parsers/claude.ts +1 -1
  4. package/dist/assets/hooks/package-lock.json +598 -0
  5. package/dist/assets/hooks/scripts/commit-msg.sh +1 -3
  6. package/dist/assets/hooks/scripts/post-commit.sh +1 -3
  7. package/dist/assets/hooks/scripts/pre-push.sh +1 -4
  8. package/dist/assets/hooks/scripts/prepare-commit-msg.sh +1 -2
  9. package/dist/assets/skills/map-cloud-verifier-universe/SKILL.md +2051 -0
  10. package/dist/assets/skills/map-cloud-verifier-universe/agents/openai.yaml +4 -0
  11. package/dist/assets/skills/map-cloud-verifier-universe/references/output-contract.md +3411 -0
  12. package/dist/assets/telemetry/browser-runtime.js +1469 -0
  13. package/dist/assets/telemetry/runtime.cjs +1503 -0
  14. package/dist/commands/cloud-verifier-behaviors.d.ts +27 -0
  15. package/dist/commands/cloud-verifier-behaviors.js +218 -0
  16. package/dist/commands/cloud-verifier-data-store-census.d.ts +47 -0
  17. package/dist/commands/cloud-verifier-data-store-census.js +539 -0
  18. package/dist/commands/cloud-verifier-data-store-drift.d.ts +42 -0
  19. package/dist/commands/cloud-verifier-data-store-drift.js +158 -0
  20. package/dist/commands/cloud-verifier-identity-census.d.ts +88 -0
  21. package/dist/commands/cloud-verifier-identity-census.js +4057 -0
  22. package/dist/commands/cloud-verifier-materialization.d.ts +16 -0
  23. package/dist/commands/cloud-verifier-materialization.js +704 -0
  24. package/dist/commands/cloud-verifier-pascal-selector-census.d.ts +29 -0
  25. package/dist/commands/cloud-verifier-pascal-selector-census.js +1382 -0
  26. package/dist/commands/cloud-verifier-python-manifest-selector-census.d.ts +27 -0
  27. package/dist/commands/cloud-verifier-python-manifest-selector-census.js +2015 -0
  28. package/dist/commands/cloud-verifier-specialized-operational-census.d.ts +51 -0
  29. package/dist/commands/cloud-verifier-specialized-operational-census.js +11432 -0
  30. package/dist/commands/cloud-verifier-universe.d.ts +31 -0
  31. package/dist/commands/cloud-verifier-universe.js +10178 -0
  32. package/dist/commands/design-verify.d.ts +31 -0
  33. package/dist/commands/design-verify.js +285 -0
  34. package/dist/commands/hooks.d.ts +1 -5
  35. package/dist/commands/hooks.js +12 -114
  36. package/dist/commands/install-session-hooks.d.ts +2 -2
  37. package/dist/commands/install-session-hooks.js +71 -21
  38. package/dist/commands/mcp.js +1 -34
  39. package/dist/commands/pr.js +2 -7
  40. package/dist/commands/prepare-universe-review.d.ts +115 -0
  41. package/dist/commands/prepare-universe-review.js +1092 -0
  42. package/dist/commands/production-source-deny-policy.d.ts +15 -0
  43. package/dist/commands/production-source-deny-policy.js +100 -0
  44. package/dist/commands/scaffold-provisional-universe.d.ts +468 -0
  45. package/dist/commands/scaffold-provisional-universe.js +808 -0
  46. package/dist/commands/schema-cmd.js +32 -0
  47. package/dist/commands/setup.d.ts +1 -2
  48. package/dist/commands/setup.js +18 -272
  49. package/dist/commands/skills.d.ts +2 -2
  50. package/dist/commands/skills.js +415 -47
  51. package/dist/commands/submit.js +1 -2
  52. package/dist/commands/telemetry.d.ts +53 -0
  53. package/dist/commands/telemetry.js +832 -0
  54. package/dist/commands/verify-hosted.d.ts +13 -0
  55. package/dist/commands/verify-hosted.js +49 -1
  56. package/dist/commands/verify-precompute.d.ts +19 -0
  57. package/dist/commands/verify-precompute.js +328 -0
  58. package/dist/index.js +168 -47
  59. package/dist/schema.d.ts +1 -2
  60. package/dist/schema.js +1 -2
  61. package/dist/triage/prompts.d.ts +0 -7
  62. package/dist/triage/prompts.js +0 -145
  63. package/dist/triage/runner.js +10 -28
  64. package/dist/triage/types.d.ts +1 -8
  65. package/dist/types.d.ts +8 -8
  66. package/dist/types.js +1 -1
  67. package/dist/utils/design-verifier-api.d.ts +200 -0
  68. package/dist/utils/design-verifier-api.js +271 -0
  69. package/dist/utils/design-verifier-result.d.ts +57 -0
  70. package/dist/utils/design-verifier-result.js +345 -0
  71. package/dist/utils/git.d.ts +5 -1
  72. package/dist/utils/git.js +19 -9
  73. package/dist/utils/github-api.js +11 -14
  74. package/dist/utils/haystack-api.d.ts +2 -0
  75. package/dist/utils/haystack-api.js +10 -3
  76. package/dist/utils/hooks.d.ts +1 -15
  77. package/dist/utils/hooks.js +3 -128
  78. package/dist/utils/telemetry.d.ts +2 -0
  79. package/dist/utils/telemetry.js +81 -8
  80. package/package.json +8 -2
  81. package/schemas/cloud-verifier.v1.json +69 -3
  82. package/schemas/{pr.v2.json → pr.v3.json} +3 -12
  83. package/dist/commands/traces.d.ts +0 -48
  84. package/dist/commands/traces.js +0 -92
  85. package/dist/triage/traces.d.ts +0 -20
  86. package/dist/triage/traces.js +0 -311
  87. package/schemas/traces.v1.json +0 -53
@@ -1,12 +1,12 @@
1
1
  /**
2
- * install-session-hooks — Wire up session-start hooks for coding CLIs.
2
+ * install-session-hooks — Wire up session-start and Stop hooks for coding CLIs.
3
3
  *
4
4
  * When a CLI session starts, `haystack triage --hook` runs automatically
5
5
  * and shows the user whether their last submitted PR is "Good to merge" or
6
6
  * "Needs input", with the Haystack rating and auto-fixer status.
7
7
  *
8
8
  * Supported CLIs:
9
- * - Claude Code: Native SessionStart hook in .claude/settings.json
9
+ * - Claude Code: Native SessionStart and Stop hooks in .claude/settings.json
10
10
  * - Codex CLI: AGENTS.md instructions (no native hook support)
11
11
  * - Gemini CLI: GEMINI.md instructions (no native hook support)
12
12
  */
@@ -43,6 +43,7 @@ function detectInstalledCLIs() {
43
43
  // Claude Code: Native SessionStart hook
44
44
  // ============================================================================
45
45
  const HAYSTACK_HOOK_COMMAND = 'haystack triage --hook';
46
+ const HAYSTACK_PRECOMPUTE_HOOK_COMMAND = 'haystack verify precompute --hook';
46
47
  function installClaudeHook(gitRoot) {
47
48
  const settingsDir = join(gitRoot, '.claude');
48
49
  const settingsPath = join(settingsDir, 'settings.json');
@@ -65,6 +66,8 @@ function installClaudeHook(gitRoot) {
65
66
  // Check if haystack hook already installed (current or legacy)
66
67
  const isHaystackHook = (h) => h.command?.includes('haystack triage') || h.command?.includes('haystack check-pending');
67
68
  const existingIndex = sessionStart.findIndex(entry => entry.hooks?.some(h => isHaystackHook(h)));
69
+ let changed = false;
70
+ let migrated = false;
68
71
  if (existingIndex !== -1) {
69
72
  const entry = sessionStart[existingIndex];
70
73
  const isLegacy = entry.hooks?.some(h => h.command?.includes('haystack check-pending'));
@@ -73,27 +76,51 @@ function installClaudeHook(gitRoot) {
73
76
  for (const h of entry.hooks || []) {
74
77
  if (h.command?.includes('haystack check-pending')) {
75
78
  h.command = HAYSTACK_HOOK_COMMAND;
79
+ changed = true;
80
+ migrated = true;
76
81
  }
77
82
  }
78
- writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
79
- console.log(chalk.green(' Claude Code hook migrated from check-pending to triage.'));
80
83
  }
81
- else {
82
- console.log(chalk.dim(' Claude Code hook already installed.'));
84
+ }
85
+ else {
86
+ // Add the session-start hook (fresh install).
87
+ sessionStart.push({
88
+ matcher: '',
89
+ hooks: [
90
+ {
91
+ type: 'command',
92
+ command: HAYSTACK_HOOK_COMMAND,
93
+ },
94
+ ],
95
+ });
96
+ changed = true;
97
+ }
98
+ // Stop precompute is independent of SessionStart. Preserve every unrelated
99
+ // entry and add the exact command only when no matching command exists.
100
+ const stop = (hooks.Stop || []);
101
+ hooks.Stop = stop;
102
+ const hasPrecomputeHook = stop.some(entry => entry.hooks?.some(h => h.command?.includes(HAYSTACK_PRECOMPUTE_HOOK_COMMAND)));
103
+ if (!hasPrecomputeHook) {
104
+ stop.push({
105
+ matcher: '',
106
+ hooks: [
107
+ {
108
+ type: 'command',
109
+ command: HAYSTACK_PRECOMPUTE_HOOK_COMMAND,
110
+ },
111
+ ],
112
+ });
113
+ changed = true;
114
+ }
115
+ if (changed) {
116
+ writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
117
+ if (migrated) {
118
+ console.log(chalk.green(' Claude Code hook migrated from check-pending to triage.'));
83
119
  }
84
- return true;
85
120
  }
86
- // Add the hook (fresh install)
87
- sessionStart.push({
88
- matcher: '',
89
- hooks: [
90
- {
91
- type: 'command',
92
- command: HAYSTACK_HOOK_COMMAND,
93
- },
94
- ],
95
- });
96
- writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
121
+ else {
122
+ console.log(chalk.dim(' Claude Code session and Stop hooks already installed.'));
123
+ }
97
124
  return true;
98
125
  }
99
126
  catch (err) {
@@ -101,6 +128,19 @@ function installClaudeHook(gitRoot) {
101
128
  return false;
102
129
  }
103
130
  }
131
+ function checkClaudePrecomputeHook(gitRoot) {
132
+ const settingsPath = join(gitRoot, '.claude', 'settings.json');
133
+ if (!existsSync(settingsPath))
134
+ return false;
135
+ try {
136
+ const settings = JSON.parse(readFileSync(settingsPath, 'utf-8'));
137
+ const stop = settings?.hooks?.Stop || [];
138
+ return stop.some((entry) => entry.hooks?.some(h => h.command?.includes(HAYSTACK_PRECOMPUTE_HOOK_COMMAND)));
139
+ }
140
+ catch {
141
+ return false;
142
+ }
143
+ }
104
144
  function checkClaudeHook(gitRoot) {
105
145
  const settingsPath = join(gitRoot, '.claude', 'settings.json');
106
146
  if (!existsSync(settingsPath))
@@ -220,6 +260,7 @@ const CLI_HOOKS = {
220
260
  hasNativeHook: true,
221
261
  install: installClaudeHook,
222
262
  check: checkClaudeHook,
263
+ checkStop: checkClaudePrecomputeHook,
223
264
  },
224
265
  codex: {
225
266
  name: 'codex',
@@ -286,6 +327,9 @@ export async function installSessionHooks(options) {
286
327
  }
287
328
  }
288
329
  console.log(chalk.green('\n✓ Session hooks installed!\n'));
330
+ if (targets.includes('claude')) {
331
+ console.log(chalk.dim('Claude Code will also precompute verification inputs when a session stops.'));
332
+ }
289
333
  console.log(chalk.dim('When you start a new session, you\'ll see:'));
290
334
  console.log(chalk.green(' [Haystack] ✓ PR #42 "Fix auth": Good to merge'));
291
335
  console.log(chalk.yellow(' [Haystack] ⚠ PR #37 "Refactor API": Needs your input (2 bugs)'));
@@ -306,11 +350,17 @@ export async function sessionHooksStatus() {
306
350
  const cliIcon = isInstalled ? chalk.green('✓') : chalk.dim('-');
307
351
  const hookIcon = hookConfigured ? chalk.green('✓') : chalk.red('✗');
308
352
  console.log(` ${cliIcon} ${config.displayName} ${isInstalled ? '(installed)' : '(not found)'}`);
309
- if (isInstalled) {
310
- console.log(` ${hookIcon} Session hook ${hookConfigured ? 'configured' : 'not configured'}`);
353
+ console.log(` ${hookIcon} Session hook ${hookConfigured ? 'configured' : 'not configured'}`);
354
+ if (config.checkStop) {
355
+ const stopConfigured = config.checkStop(gitRoot);
356
+ const stopIcon = stopConfigured ? chalk.green('✓') : chalk.red('✗');
357
+ console.log(` ${stopIcon} Stop precompute hook ${stopConfigured ? 'configured' : 'not configured'}`);
311
358
  }
312
359
  }
313
- const anyMissing = installed.some(cli => !CLI_HOOKS[cli].check(gitRoot));
360
+ const anyMissing = installed.some(cli => {
361
+ const config = CLI_HOOKS[cli];
362
+ return !config.check(gitRoot) || (config.checkStop ? !config.checkStop(gitRoot) : false);
363
+ });
314
364
  if (anyMissing) {
315
365
  console.log(chalk.dim('\nRun: haystack hooks install-session'));
316
366
  }
@@ -9,7 +9,6 @@ import { fetchPrStatePayload } from './pr.js';
9
9
  import { fetchPrStatusPayload } from './pr-status.js';
10
10
  import { fetchTriagePayload, fetchTriageHeadSha } from './triage.js';
11
11
  import { fetchAskPayload } from './ask.js';
12
- import { fetchTracesGetPayload, fetchTracesListPayload } from './traces.js';
13
12
  import { performOverride, performUndismiss } from './dismiss.js';
14
13
  import { performRequestReview } from './request-review.js';
15
14
  import { loadToken } from '../utils/auth.js';
@@ -29,7 +28,7 @@ const TOOLS = [
29
28
  },
30
29
  {
31
30
  name: 'pr_get',
32
- description: 'Get useful state for one PR: triage, minimal merge blockers, trace availability, and conditional fixer outcome.',
31
+ description: 'Get useful state for one PR: triage, minimal merge blockers, and conditional fixer outcome.',
33
32
  inputSchema: { type: 'object', properties: { ref: refInput }, required: ['ref'] },
34
33
  },
35
34
  {
@@ -50,25 +49,6 @@ const TOOLS = [
50
49
  required: ['ref', 'question'],
51
50
  },
52
51
  },
53
- {
54
- name: 'traces_list',
55
- description: 'List retained customer-owned Entire checkpoints for one PR.',
56
- inputSchema: { type: 'object', properties: { ref: refInput }, required: ['ref'] },
57
- },
58
- {
59
- name: 'traces_get',
60
- description: 'Get a page of retained customer-owned transcript chunks for one checkpoint.',
61
- inputSchema: {
62
- type: 'object',
63
- properties: {
64
- ref: refInput,
65
- checkpoint_id: { type: 'string' },
66
- cursor: { type: 'string' },
67
- limit: { type: 'integer', minimum: 1, maximum: 100 },
68
- },
69
- required: ['ref', 'checkpoint_id'],
70
- },
71
- },
72
52
  {
73
53
  name: 'pr_status',
74
54
  description: 'Get which Haystack feed bucket a PR is in (analyzing, good_to_merge, issues, needs_assignment, ...) and why.',
@@ -153,19 +133,6 @@ export async function runMcpServer() {
153
133
  session: typeof input.session_id === 'string' ? input.session_id : undefined,
154
134
  }));
155
135
  }
156
- if (name === 'traces_list') {
157
- if (typeof input.ref !== 'string')
158
- return fail('ref required');
159
- return ok(await fetchTracesListPayload(input.ref));
160
- }
161
- if (name === 'traces_get') {
162
- if (typeof input.ref !== 'string' || typeof input.checkpoint_id !== 'string')
163
- return fail('ref and checkpoint_id required');
164
- return ok(await fetchTracesGetPayload(input.ref, input.checkpoint_id, {
165
- cursor: typeof input.cursor === 'string' ? input.cursor : undefined,
166
- limit: typeof input.limit === 'number' ? String(input.limit) : undefined,
167
- }));
168
- }
169
136
  if (name === 'pr_status') {
170
137
  if (typeof input.ref !== 'string')
171
138
  return fail('ref required');
@@ -1,7 +1,7 @@
1
1
  import chalk from 'chalk';
2
2
  import { loadToken } from '../utils/auth.js';
3
3
  import { fetchAutoFixManifest, fetchRatingSynthesis } from '../utils/analysis-api.js';
4
- import { fetchAnalysisArtifact, fetchGitHubJson, haystackJson, HaystackApiError } from '../utils/haystack-api.js';
4
+ import { fetchGitHubJson, haystackJson, HaystackApiError } from '../utils/haystack-api.js';
5
5
  import { parsePrRef } from '../utils/pr-ref.js';
6
6
  import { toPublicVerdict } from '../states.js';
7
7
  import { withSchema } from '../schema.js';
@@ -59,10 +59,9 @@ export async function fetchPrStatePayload(ref) {
59
59
  if (!token)
60
60
  throw new Error('Not authenticated. Run `haystack login` first.');
61
61
  const meta = await fetchGitHubJson(`/repos/${pr.owner}/${pr.repo}/pulls/${pr.prNumber}`, token);
62
- const [synthesis, manifest, traces, merge] = await Promise.all([
62
+ const [synthesis, manifest, merge] = await Promise.all([
63
63
  fetchRatingSynthesis(pr.owner, pr.repo, pr.prNumber, token),
64
64
  fetchAutoFixManifest(pr.owner, pr.repo, pr.prNumber, token),
65
- fetchAnalysisArtifact(pr.owner, pr.repo, pr.prNumber, 'entire_traces.json', token),
66
65
  fetchMergePublicState(meta, pr.prNumber, pr.owner, pr.repo, token),
67
66
  ]);
68
67
  const payload = {
@@ -77,10 +76,6 @@ export async function fetchPrStatePayload(ref) {
77
76
  }
78
77
  : { status: 'pending', verdict: null, findings_count: 0 },
79
78
  merge,
80
- traces: {
81
- available: traces?.hasTraces === true,
82
- count: traces?.traces?.length ?? 0,
83
- },
84
79
  };
85
80
  if (manifest) {
86
81
  const fixed = manifest.fixedItems.length;
@@ -0,0 +1,115 @@
1
+ export interface ReviewExclusion {
2
+ path: string;
3
+ rule_id: string;
4
+ }
5
+ export interface ReviewSymlinkIssue {
6
+ path: string;
7
+ target: string;
8
+ reason: 'absolute-target' | 'target-escapes-repository' | 'target-is-excluded' | 'target-is-not-retained-file' | 'target-cycle' | 'ambiguous-target' | 'unreadable-link';
9
+ }
10
+ export interface InlineTestCandidate {
11
+ path: string;
12
+ }
13
+ export interface SnapshotLimitBlocker {
14
+ kind: 'entry-count' | 'total-output-bytes';
15
+ path: string;
16
+ limit: number;
17
+ attempted: number;
18
+ reason: string;
19
+ }
20
+ export interface PrepareUniverseReviewResult {
21
+ status: 'ready-for-confinement' | 'blocked';
22
+ path: string;
23
+ process_confinement_required: true;
24
+ counts: {
25
+ tracked: number;
26
+ retained: number;
27
+ copied: number;
28
+ excluded: number;
29
+ submodules_omitted: number;
30
+ approved_inactive_submodules_omitted: number;
31
+ symlinks_materialized: number;
32
+ symlink_issues: number;
33
+ symlinks_omitted_target_not_retained: number;
34
+ omitted_unscannable: number;
35
+ missing_or_changed_type: number;
36
+ inline_test_candidates: number;
37
+ inline_scan_skipped: number;
38
+ binary_inline_scan_skipped: number;
39
+ oversized_text_inline_scan_blockers: number;
40
+ snapshot_limit_blockers: number;
41
+ copied_bytes: number;
42
+ };
43
+ exclusions: ReviewExclusion[];
44
+ submodules_omitted: string[];
45
+ approved_inactive_submodule_omissions: {
46
+ rule: {
47
+ id: string;
48
+ description: string;
49
+ };
50
+ paths: string[];
51
+ };
52
+ rules: Array<{
53
+ id: string;
54
+ description: string;
55
+ }>;
56
+ activated_conditional_rule_ids: string[];
57
+ inline_marker_rules: Array<{
58
+ id: string;
59
+ description: string;
60
+ }>;
61
+ symlink_issues: ReviewSymlinkIssue[];
62
+ omitted_symlinks: ReviewSymlinkIssue[];
63
+ omitted_unscannable: {
64
+ path: string;
65
+ reason: string;
66
+ }[];
67
+ missing_or_changed_type: Array<{
68
+ path: string;
69
+ reason: string;
70
+ }>;
71
+ inline_test_candidates: InlineTestCandidate[];
72
+ inline_scan_skipped: Array<{
73
+ path: string;
74
+ reason: string;
75
+ }>;
76
+ snapshot_limit_blockers: SnapshotLimitBlocker[];
77
+ receipt: {
78
+ detail: 'compact' | 'full';
79
+ sample_limit: number;
80
+ complete_blocking_details: true;
81
+ collections: {
82
+ exclusions: ReceiptCollectionSummary;
83
+ approved_inactive_submodule_omissions: ReceiptCollectionSummary;
84
+ binary_inline_scan_skips: ReceiptCollectionSummary;
85
+ };
86
+ };
87
+ manifest: {
88
+ path: string;
89
+ sha256: string;
90
+ entries: number;
91
+ };
92
+ }
93
+ export interface PrepareUniverseReviewOptions {
94
+ excludeInactiveSubmodules?: string[];
95
+ /** Omit files whose test content cannot be ruled out instead of blocking. */
96
+ omitUnscannableFiles?: boolean;
97
+ fullReceipt?: boolean;
98
+ limits?: {
99
+ maxSnapshotEntries?: number;
100
+ maxSnapshotOutputBytes?: number;
101
+ };
102
+ }
103
+ interface ReceiptCollectionSummary {
104
+ total: number;
105
+ returned: number;
106
+ truncated: number;
107
+ }
108
+ export declare function prepareUniverseReview(root: string, options?: PrepareUniverseReviewOptions): PrepareUniverseReviewResult;
109
+ export declare function prepareUniverseReviewCommand(options: {
110
+ json?: boolean;
111
+ excludeInactiveSubmodule?: string[];
112
+ omitUnscannableFiles?: boolean;
113
+ fullReceipt?: boolean;
114
+ }): Promise<void>;
115
+ export {};