@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
@@ -0,0 +1,31 @@
1
+ /**
2
+ * design-verify command — Kick off a blind design-by-execution verification
3
+ * run for a PR.
4
+ *
5
+ * haystack design-verify <pr>
6
+ *
7
+ * Unlike `haystack review` (finding-oriented analysis of the diff), design
8
+ * verification executes the change: it infers intent from the diff alone,
9
+ * designs pre-registered behavioral test cases, runs control-before, before,
10
+ * and after in isolated sandboxes, and reports evidence-backed anomaly
11
+ * candidates outside the registered intent.
12
+ *
13
+ * The command waits by default and returns an agent-facing behavioral result.
14
+ * --no-wait preserves the asynchronous trigger-only path.
15
+ */
16
+ import { type DesignVerifierUniverseRole } from '../utils/design-verifier-api.js';
17
+ export interface DesignVerifyOptions {
18
+ json?: boolean;
19
+ wait?: boolean;
20
+ run?: string;
21
+ pollInterval?: string;
22
+ timeout?: string;
23
+ case?: string;
24
+ universe?: DesignVerifierUniverseRole;
25
+ replay?: boolean;
26
+ execJson?: string;
27
+ cwd?: string;
28
+ commandTimeout?: string;
29
+ action?: string;
30
+ }
31
+ export declare function designVerifyCommand(identifier: string, options: DesignVerifyOptions): Promise<void>;
@@ -0,0 +1,285 @@
1
+ /**
2
+ * design-verify command — Kick off a blind design-by-execution verification
3
+ * run for a PR.
4
+ *
5
+ * haystack design-verify <pr>
6
+ *
7
+ * Unlike `haystack review` (finding-oriented analysis of the diff), design
8
+ * verification executes the change: it infers intent from the diff alone,
9
+ * designs pre-registered behavioral test cases, runs control-before, before,
10
+ * and after in isolated sandboxes, and reports evidence-backed anomaly
11
+ * candidates outside the registered intent.
12
+ *
13
+ * The command waits by default and returns an agent-facing behavioral result.
14
+ * --no-wait preserves the asynchronous trigger-only path.
15
+ */
16
+ import chalk from 'chalk';
17
+ import { loadToken } from '../utils/auth.js';
18
+ import { parsePrRef } from '../utils/pr-ref.js';
19
+ import { getDesignVerifierUniverseAction, startDesignVerifierUniverseAction, triggerDesignVerifier, waitForDesignVerifier, waitForDesignVerifierUniverseAction, } from '../utils/design-verifier-api.js';
20
+ import { buildDesignVerifierAgentReport, designVerifierStageLabel, formatDesignVerifierAgentReport, } from '../utils/design-verifier-result.js';
21
+ function emit(json, payload, human) {
22
+ if (json) {
23
+ console.log(JSON.stringify(payload));
24
+ }
25
+ else {
26
+ human();
27
+ }
28
+ }
29
+ function parseExecArgv(source) {
30
+ let value;
31
+ try {
32
+ value = JSON.parse(source);
33
+ }
34
+ catch (error) {
35
+ throw new Error(`--exec-json is not valid JSON: ${error.message}`);
36
+ }
37
+ if (!Array.isArray(value) || value.length === 0 ||
38
+ value.some(argument => typeof argument !== 'string')) {
39
+ throw new Error('--exec-json must be a non-empty JSON array of string arguments');
40
+ }
41
+ return value;
42
+ }
43
+ function terminalSafe(value) {
44
+ // Preserve ordinary whitespace while removing C0/C1 controls, including
45
+ // ESC/OSC introducers. JSON output retains the server-returned strings.
46
+ let safe = '';
47
+ for (const character of value) {
48
+ const codePoint = character.codePointAt(0) ?? 0;
49
+ const ordinaryWhitespace = codePoint === 9 || codePoint === 10 || codePoint === 13;
50
+ if ((codePoint < 32 && !ordinaryWhitespace) ||
51
+ (codePoint >= 127 && codePoint <= 159))
52
+ continue;
53
+ safe += character;
54
+ }
55
+ return safe;
56
+ }
57
+ function printUniverseAction(action) {
58
+ const result = action.result;
59
+ if (action.status === 'queued' || action.status === 'running') {
60
+ console.log(chalk.dim(`${action.caseId}/${action.universe} ${action.action} is ${action.status}; action ${action.actionId}`));
61
+ return;
62
+ }
63
+ if (action.status === 'failed') {
64
+ console.error(chalk.red(`Universe ${action.action} failed: ${action.error || 'unknown error'}`));
65
+ return;
66
+ }
67
+ if (!result) {
68
+ console.error(chalk.red(`Universe ${action.action} completed without a result.`));
69
+ return;
70
+ }
71
+ console.log(chalk.dim(`${action.caseId}/${action.universe} ${action.action} (${result.durationMs}ms, exit ${result.exitCode ?? 'unknown'})`));
72
+ if (result.stdout) {
73
+ const stdout = terminalSafe(result.stdout);
74
+ process.stdout.write(stdout.endsWith('\n') ? stdout : `${stdout}\n`);
75
+ }
76
+ if (result.stderr) {
77
+ const stderr = terminalSafe(result.stderr);
78
+ process.stderr.write(stderr.endsWith('\n') ? stderr : `${stderr}\n`);
79
+ }
80
+ if (result.stdoutOmittedBytes || result.stderrOmittedBytes) {
81
+ console.error(chalk.dim(`Output truncated: ${result.stdoutOmittedBytes} stdout bytes, ${result.stderrOmittedBytes} stderr bytes omitted.`));
82
+ }
83
+ }
84
+ async function runUniverseOperation(pr, prLabel, options, token, pollIntervalSeconds, timeoutSeconds) {
85
+ const requested = Boolean(options.action || options.case || options.universe ||
86
+ options.replay || options.execJson || options.cwd || options.commandTimeout);
87
+ if (!requested)
88
+ return false;
89
+ if (!options.run)
90
+ throw new Error('universe exploration requires --run <run-id>');
91
+ let actionId = options.action;
92
+ if (actionId) {
93
+ if (options.case || options.universe || options.replay || options.execJson ||
94
+ options.cwd || options.commandTimeout) {
95
+ throw new Error('--action cannot be combined with a new universe command');
96
+ }
97
+ if (options.wait === false) {
98
+ const loaded = await getDesignVerifierUniverseAction(pr.owner, pr.repo, pr.prNumber, options.run, actionId, token);
99
+ if (loaded.status !== 'ok')
100
+ throw new Error(loaded.message);
101
+ emit(options.json, { ok: true, pr: prLabel, ...loaded.action }, () => printUniverseAction(loaded.action));
102
+ return true;
103
+ }
104
+ }
105
+ else {
106
+ if (!options.case || !/^case-\d{5}$/.test(options.case)) {
107
+ throw new Error('a canonical --case case-00000 is required');
108
+ }
109
+ if (!options.universe || !['control-before', 'before', 'after'].includes(options.universe)) {
110
+ throw new Error('--universe must be control-before, before, or after');
111
+ }
112
+ if (Boolean(options.replay) === Boolean(options.execJson)) {
113
+ throw new Error('choose exactly one of --replay or --exec-json');
114
+ }
115
+ if (options.replay && (options.cwd || options.commandTimeout)) {
116
+ throw new Error('--cwd and --command-timeout apply only to --exec-json');
117
+ }
118
+ const commandTimeoutMs = options.commandTimeout === undefined
119
+ ? undefined
120
+ : Number(options.commandTimeout) * 1000;
121
+ if (commandTimeoutMs !== undefined &&
122
+ !(Number.isInteger(commandTimeoutMs) && commandTimeoutMs >= 1_000 && commandTimeoutMs <= 300_000)) {
123
+ throw new Error('--command-timeout must be between 1 and 300 seconds');
124
+ }
125
+ const started = await startDesignVerifierUniverseAction(pr.owner, pr.repo, pr.prNumber, options.run, token, {
126
+ caseId: options.case,
127
+ universe: options.universe,
128
+ action: options.replay ? 'replay' : 'exec',
129
+ ...(options.execJson ? { argv: parseExecArgv(options.execJson) } : {}),
130
+ ...(options.cwd ? { cwd: options.cwd } : {}),
131
+ ...(commandTimeoutMs === undefined ? {} : { timeoutMs: commandTimeoutMs }),
132
+ });
133
+ if (started.status !== 'ok')
134
+ throw new Error(started.message);
135
+ actionId = started.action.actionId;
136
+ if (options.wait === false) {
137
+ emit(options.json, { ok: true, pr: prLabel, ...started.action }, () => {
138
+ console.log(chalk.green('✓ Universe action queued'));
139
+ console.log(chalk.dim(` Action: ${actionId}`));
140
+ console.log(chalk.dim(` Resume: haystack design-verify ${prLabel} --run ${options.run} --action ${actionId}`));
141
+ });
142
+ return true;
143
+ }
144
+ }
145
+ const waited = await waitForDesignVerifierUniverseAction(pr.owner, pr.repo, pr.prNumber, options.run, actionId, token, { intervalMs: pollIntervalSeconds * 1000, timeoutMs: timeoutSeconds * 1000 });
146
+ if (waited.status === 'timeout') {
147
+ throw new Error(`Timed out waiting; resume with: haystack design-verify ${prLabel} --run ${options.run} --action ${actionId}`);
148
+ }
149
+ if (waited.status === 'error')
150
+ throw new Error(waited.message);
151
+ emit(options.json, {
152
+ ok: waited.action.status === 'completed' && waited.action.result?.exitCode === 0,
153
+ pr: prLabel,
154
+ ...waited.action,
155
+ }, () => printUniverseAction(waited.action));
156
+ process.exitCode = waited.action.status === 'failed'
157
+ ? 2
158
+ : waited.action.result?.exitCode === 0 ? 0 : 1;
159
+ return true;
160
+ }
161
+ export async function designVerifyCommand(identifier, options) {
162
+ let pr;
163
+ try {
164
+ pr = parsePrRef(identifier, 'design-verify');
165
+ }
166
+ catch (err) {
167
+ emit(options.json, { ok: false, error: err.message }, () => {
168
+ console.error(chalk.red(err.message));
169
+ });
170
+ process.exitCode = 1;
171
+ return;
172
+ }
173
+ const prLabel = `${pr.owner}/${pr.repo}#${pr.prNumber}`;
174
+ let token = null;
175
+ try {
176
+ token = await loadToken({ owner: pr.owner, repo: pr.repo });
177
+ }
178
+ catch (err) {
179
+ emit(options.json, { ok: false, pr: prLabel, error: `Could not load Haystack credentials: ${err.message}` }, () => {
180
+ console.error(chalk.red(`Could not load Haystack credentials: ${err.message}`));
181
+ });
182
+ process.exitCode = 1;
183
+ return;
184
+ }
185
+ if (!token) {
186
+ emit(options.json, { ok: false, pr: prLabel, error: 'Authentication required. Run `haystack login` first.' }, () => {
187
+ console.error(chalk.red('Design verification requires authentication. Run `haystack login` first.'));
188
+ });
189
+ process.exitCode = 1;
190
+ return;
191
+ }
192
+ const pollIntervalSeconds = Number(options.pollInterval || '3');
193
+ const timeoutSeconds = Number(options.timeout || '7200');
194
+ if (!(Number.isFinite(pollIntervalSeconds) && pollIntervalSeconds >= 1 && pollIntervalSeconds <= 60)) {
195
+ emit(options.json, { ok: false, error: '--poll-interval must be between 1 and 60 seconds' }, () => {
196
+ console.error(chalk.red('--poll-interval must be between 1 and 60 seconds'));
197
+ });
198
+ process.exitCode = 2;
199
+ return;
200
+ }
201
+ if (!(Number.isFinite(timeoutSeconds) && timeoutSeconds >= 1 && timeoutSeconds <= 7200)) {
202
+ emit(options.json, { ok: false, error: '--timeout must be between 1 and 7200 seconds' }, () => {
203
+ console.error(chalk.red('--timeout must be between 1 and 7200 seconds'));
204
+ });
205
+ process.exitCode = 2;
206
+ return;
207
+ }
208
+ try {
209
+ if (await runUniverseOperation(pr, prLabel, options, token, pollIntervalSeconds, timeoutSeconds))
210
+ return;
211
+ }
212
+ catch (error) {
213
+ emit(options.json, { ok: false, pr: prLabel, error: error.message }, () => {
214
+ console.error(chalk.red(error.message));
215
+ });
216
+ process.exitCode = 2;
217
+ return;
218
+ }
219
+ if (!options.json) {
220
+ console.log(chalk.dim(options.run
221
+ ? `\nResuming design verification ${options.run}...`
222
+ : `\nStarting design verification for ${prLabel}...`));
223
+ }
224
+ const result = options.run
225
+ ? { status: 'duplicate', runId: options.run }
226
+ : await triggerDesignVerifier(pr.owner, pr.repo, pr.prNumber, token);
227
+ switch (result.status) {
228
+ case 'started':
229
+ case 'queued':
230
+ case 'duplicate':
231
+ if (options.wait === false) {
232
+ emit(options.json, { ok: true, pr: prLabel, runId: result.runId, outcome: result.status }, () => {
233
+ const suffix = result.status === 'queued'
234
+ ? ' (at capacity — queued; it will start as a slot frees up)'
235
+ : '';
236
+ console.log(chalk.green(`✓ Design verification ${result.status}${suffix}`));
237
+ console.log(chalk.dim(` Run: ${result.runId}`));
238
+ console.log(chalk.dim(` Resume: haystack design-verify ${prLabel} --run ${result.runId}`));
239
+ });
240
+ return;
241
+ }
242
+ break;
243
+ default:
244
+ emit(options.json, { ok: false, pr: prLabel, error: result.message, kind: result.status }, () => {
245
+ console.error(chalk.red(`✗ ${result.message}`));
246
+ });
247
+ process.exitCode = 1;
248
+ return;
249
+ }
250
+ let lastProgress = '';
251
+ const waited = await waitForDesignVerifier(pr.owner, pr.repo, pr.prNumber, result.runId, token, {
252
+ intervalMs: pollIntervalSeconds * 1000,
253
+ timeoutMs: timeoutSeconds * 1000,
254
+ onProgress: (run) => {
255
+ if (options.json)
256
+ return;
257
+ const label = designVerifierStageLabel(run.stage);
258
+ if (label === lastProgress)
259
+ return;
260
+ lastProgress = label;
261
+ console.error(chalk.dim(` ${label}...`));
262
+ },
263
+ });
264
+ if (waited.status === 'timeout') {
265
+ const message = `Timed out waiting; the run continues. Resume with: haystack design-verify ${prLabel} --run ${result.runId}`;
266
+ emit(options.json, { ok: false, status: 'pending', runId: result.runId, error: message }, () => {
267
+ console.error(chalk.yellow(message));
268
+ });
269
+ process.exitCode = 2;
270
+ return;
271
+ }
272
+ if (waited.status === 'error') {
273
+ emit(options.json, { ok: false, status: 'failed', runId: result.runId, error: waited.message }, () => {
274
+ console.error(chalk.red(`Verification status failed: ${waited.message}`));
275
+ });
276
+ process.exitCode = 2;
277
+ return;
278
+ }
279
+ const report = buildDesignVerifierAgentReport(waited.run);
280
+ if (options.json)
281
+ console.log(JSON.stringify(report));
282
+ else
283
+ console.log(`\n${formatDesignVerifierAgentReport(report)}`);
284
+ process.exitCode = report.exitCode;
285
+ }
@@ -2,16 +2,12 @@
2
2
  * haystack hooks - Install and manage git hooks for AI agent quality checks
3
3
  *
4
4
  * Installs:
5
- * - Entire CLI binary (session tracking, powered by https://entire.dev)
6
5
  * - Pre-commit hook (agent detection, truncation checking, LLM rules review)
7
- * - Git hooks for session logging (commit-msg, post-commit, pre-push, prepare-commit-msg)
6
+ * - Git hooks for Haystack quality checks and workflow guidance
8
7
  */
9
8
  interface HooksInstallOptions {
10
- version?: string;
11
9
  force?: boolean;
12
- skipEntire?: boolean;
13
10
  }
14
11
  export declare function hooksInstall(options: HooksInstallOptions): Promise<void>;
15
12
  export declare function hooksStatus(): Promise<void>;
16
- export declare function hooksUpdate(): Promise<void>;
17
13
  export {};
@@ -2,15 +2,14 @@
2
2
  * haystack hooks - Install and manage git hooks for AI agent quality checks
3
3
  *
4
4
  * Installs:
5
- * - Entire CLI binary (session tracking, powered by https://entire.dev)
6
5
  * - Pre-commit hook (agent detection, truncation checking, LLM rules review)
7
- * - Git hooks for session logging (commit-msg, post-commit, pre-push, prepare-commit-msg)
6
+ * - Git hooks for Haystack quality checks and workflow guidance
8
7
  */
9
8
  import chalk from 'chalk';
10
9
  import { existsSync, accessSync, constants } from 'fs';
11
10
  import * as path from 'path';
12
11
  import { execSync } from 'child_process';
13
- import { findGitRoot, ENTIRE_DEFAULT_VERSION, ENTIRE_BIN_PATH, getInstalledEntireVersion, getLatestEntireVersion, downloadEntireBinary, copyHookFiles, installHookDeps, copyLlmRulesTemplate, createEntireConfig, updateGitignore, } from '../utils/hooks.js';
12
+ import { findGitRoot, copyHookFiles, installHookDeps, copyLlmRulesTemplate, } from '../utils/hooks.js';
14
13
  export async function hooksInstall(options) {
15
14
  console.log(chalk.cyan('\nHaystack Hooks Setup\n'));
16
15
  // Step 1: Validate git repo
@@ -28,30 +27,7 @@ export async function hooksInstall(options) {
28
27
  if (existsSync(hooksDir) && options.force) {
29
28
  console.log(chalk.yellow('Overwriting existing hooks...'));
30
29
  }
31
- // Step 3: Download Entire binary
32
- if (!options.skipEntire) {
33
- const version = options.version || ENTIRE_DEFAULT_VERSION;
34
- const installedVersion = await getInstalledEntireVersion();
35
- if (installedVersion === version) {
36
- console.log(chalk.green(`✓ Entire CLI v${version} already installed`));
37
- }
38
- else {
39
- console.log(chalk.dim(`Downloading Entire CLI v${version}...`));
40
- try {
41
- await downloadEntireBinary(version);
42
- console.log(chalk.green(`✓ Entire CLI v${version} installed to ~/.haystack/bin/entire`));
43
- }
44
- catch (err) {
45
- const message = err instanceof Error ? err.message : String(err);
46
- console.error(chalk.red(`Failed to download Entire CLI: ${message}\n`));
47
- process.exit(1);
48
- }
49
- }
50
- }
51
- else {
52
- console.log(chalk.dim('Skipping Entire CLI download (--skip-entire)'));
53
- }
54
- // Step 4: Copy hook files
30
+ // Step 3: Copy hook files
55
31
  try {
56
32
  await copyHookFiles(hooksDir);
57
33
  console.log(chalk.green('✓ Hook scripts installed to hooks/'));
@@ -61,16 +37,16 @@ export async function hooksInstall(options) {
61
37
  console.error(chalk.red(`Failed to copy hook files: ${message}\n`));
62
38
  process.exit(1);
63
39
  }
64
- // Step 5: Install hook dependencies
40
+ // Step 4: Install hook dependencies
65
41
  console.log(chalk.dim('Installing hook dependencies (tree-sitter)...'));
66
42
  try {
67
43
  await installHookDeps(hooksDir);
68
44
  console.log(chalk.green('✓ Hook dependencies installed'));
69
45
  }
70
46
  catch {
71
- console.log(chalk.yellow('⚠ Failed to install hook dependencies. Run `npm install` in hooks/ manually.'));
47
+ console.log(chalk.yellow('⚠ Failed to install hook dependencies. Run `npm ci` in hooks/ manually.'));
72
48
  }
73
- // Step 6: Configure git hooks path
49
+ // Step 5: Configure git hooks path
74
50
  try {
75
51
  execSync('git config core.hooksPath hooks', { cwd: gitRoot, stdio: 'pipe' });
76
52
  console.log(chalk.green('✓ Git configured to use hooks/ directory'));
@@ -80,15 +56,7 @@ export async function hooksInstall(options) {
80
56
  console.error(chalk.red(`Failed to set git hooks path: ${message}\n`));
81
57
  process.exit(1);
82
58
  }
83
- // Step 7: Create .entire/ configuration
84
- try {
85
- await createEntireConfig(gitRoot);
86
- console.log(chalk.green('✓ Created .entire/settings.json'));
87
- }
88
- catch {
89
- console.log(chalk.yellow('⚠ Failed to create .entire/ configuration'));
90
- }
91
- // Step 8: Create LLM_RULES.md if needed
59
+ // Step 6: Create LLM_RULES.md if needed
92
60
  try {
93
61
  const created = await copyLlmRulesTemplate(gitRoot);
94
62
  if (created) {
@@ -101,29 +69,17 @@ export async function hooksInstall(options) {
101
69
  catch {
102
70
  console.log(chalk.yellow('⚠ Failed to create LLM_RULES.md'));
103
71
  }
104
- // Step 9: Update .gitignore
105
- try {
106
- const updated = await updateGitignore(gitRoot);
107
- if (updated) {
108
- console.log(chalk.green('✓ Updated .gitignore'));
109
- }
110
- }
111
- catch {
112
- console.log(chalk.yellow('⚠ Failed to update .gitignore'));
113
- }
114
72
  // Summary
115
73
  console.log(chalk.green('\n✓ Haystack hooks installed!\n'));
116
74
  console.log(chalk.dim('Hooks installed:'));
117
75
  console.log(chalk.dim(' pre-commit Agent detection, truncation check, LLM rules review'));
118
- console.log(chalk.dim(' commit-msg Session tracking (powered by Entire)'));
119
- console.log(chalk.dim(' post-commit Session condensing (powered by Entire)'));
120
- console.log(chalk.dim(' pre-push Session log push (powered by Entire)'));
121
- console.log(chalk.dim(' prepare-commit-msg Commit preparation (powered by Entire)'));
76
+ console.log(chalk.dim(' commit-msg Haystack commit checks'));
77
+ console.log(chalk.dim(' post-commit Haystack post-commit checks'));
78
+ console.log(chalk.dim(' pre-push Haystack workflow guidance'));
79
+ console.log(chalk.dim(' prepare-commit-msg Haystack commit preparation'));
122
80
  console.log('');
123
81
  console.log(chalk.dim('Files to commit:'));
124
82
  console.log(chalk.dim(' hooks/ Hook scripts and TypeScript modules'));
125
- console.log(chalk.dim(' .entire/settings.json'));
126
- console.log(chalk.dim(' .entire/.gitignore'));
127
83
  console.log(chalk.dim(' LLM_RULES.md Customize this for your project'));
128
84
  console.log('');
129
85
  }
@@ -181,7 +137,7 @@ export async function hooksStatus() {
181
137
  const nodeModules = path.join(hooksDir, 'node_modules');
182
138
  console.log(existsSync(nodeModules)
183
139
  ? chalk.green('✓ Hook dependencies installed')
184
- : chalk.yellow('⚠ Hook dependencies not installed (run npm install in hooks/)'));
140
+ : chalk.yellow('⚠ Hook dependencies not installed (run npm ci in hooks/)'));
185
141
  // Check git core.hooksPath
186
142
  try {
187
143
  const hooksPath = execSync('git config core.hooksPath', {
@@ -199,25 +155,6 @@ export async function hooksStatus() {
199
155
  catch {
200
156
  console.log(chalk.red('✗ Git core.hooksPath not set'));
201
157
  }
202
- // Check Entire binary
203
- const installedVersion = await getInstalledEntireVersion();
204
- if (installedVersion) {
205
- console.log(chalk.green(`✓ Entire CLI v${installedVersion}`));
206
- }
207
- else if (existsSync(ENTIRE_BIN_PATH)) {
208
- console.log(chalk.yellow('⚠ Entire CLI binary exists but failed to run'));
209
- }
210
- else {
211
- console.log(chalk.yellow('⚠ Entire CLI not installed (~/.haystack/bin/entire)'));
212
- }
213
- // Check .entire/settings.json
214
- const settingsPath = path.join(gitRoot, '.entire', 'settings.json');
215
- if (existsSync(settingsPath)) {
216
- console.log(chalk.green('✓ .entire/settings.json'));
217
- }
218
- else {
219
- console.log(chalk.yellow('⚠ .entire/settings.json not found'));
220
- }
221
158
  // Check LLM_RULES.md
222
159
  const rulesPath = path.join(gitRoot, 'LLM_RULES.md');
223
160
  if (existsSync(rulesPath)) {
@@ -228,42 +165,3 @@ export async function hooksStatus() {
228
165
  }
229
166
  console.log('');
230
167
  }
231
- // ============================================================================
232
- // haystack hooks update
233
- // ============================================================================
234
- export async function hooksUpdate() {
235
- console.log(chalk.cyan('\nUpdating Entire CLI...\n'));
236
- // Check current version
237
- const currentVersion = await getInstalledEntireVersion();
238
- if (currentVersion) {
239
- console.log(chalk.dim(`Current version: v${currentVersion}`));
240
- }
241
- else {
242
- console.log(chalk.dim('Entire CLI not currently installed.'));
243
- }
244
- // Fetch latest version
245
- let latestVersion;
246
- try {
247
- latestVersion = await getLatestEntireVersion();
248
- }
249
- catch (err) {
250
- const message = err instanceof Error ? err.message : String(err);
251
- console.error(chalk.red(`Failed to check for updates: ${message}\n`));
252
- process.exit(1);
253
- }
254
- console.log(chalk.dim(`Latest version: v${latestVersion}`));
255
- if (currentVersion === latestVersion) {
256
- console.log(chalk.green('\n✓ Already up to date.\n'));
257
- return;
258
- }
259
- // Download and install
260
- try {
261
- await downloadEntireBinary(latestVersion);
262
- console.log(chalk.green(`\n✓ Updated Entire CLI to v${latestVersion}\n`));
263
- }
264
- catch (err) {
265
- const message = err instanceof Error ? err.message : String(err);
266
- console.error(chalk.red(`Failed to update: ${message}\n`));
267
- process.exit(1);
268
- }
269
- }
@@ -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
  */