@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.
- package/README.md +52 -23
- package/dist/assets/hooks/agent-context/detect.ts +7 -7
- package/dist/assets/hooks/agent-context/parsers/claude.ts +1 -1
- package/dist/assets/hooks/package-lock.json +598 -0
- package/dist/assets/hooks/scripts/commit-msg.sh +1 -3
- package/dist/assets/hooks/scripts/post-commit.sh +1 -3
- package/dist/assets/hooks/scripts/pre-push.sh +1 -4
- package/dist/assets/hooks/scripts/prepare-commit-msg.sh +1 -2
- package/dist/assets/skills/map-cloud-verifier-universe/SKILL.md +2051 -0
- package/dist/assets/skills/map-cloud-verifier-universe/agents/openai.yaml +4 -0
- package/dist/assets/skills/map-cloud-verifier-universe/references/output-contract.md +3411 -0
- package/dist/assets/telemetry/browser-runtime.js +1469 -0
- package/dist/assets/telemetry/runtime.cjs +1503 -0
- package/dist/commands/cloud-verifier-behaviors.d.ts +27 -0
- package/dist/commands/cloud-verifier-behaviors.js +218 -0
- package/dist/commands/cloud-verifier-data-store-census.d.ts +47 -0
- package/dist/commands/cloud-verifier-data-store-census.js +539 -0
- package/dist/commands/cloud-verifier-data-store-drift.d.ts +42 -0
- package/dist/commands/cloud-verifier-data-store-drift.js +158 -0
- package/dist/commands/cloud-verifier-identity-census.d.ts +88 -0
- package/dist/commands/cloud-verifier-identity-census.js +4057 -0
- package/dist/commands/cloud-verifier-materialization.d.ts +16 -0
- package/dist/commands/cloud-verifier-materialization.js +704 -0
- package/dist/commands/cloud-verifier-pascal-selector-census.d.ts +29 -0
- package/dist/commands/cloud-verifier-pascal-selector-census.js +1382 -0
- package/dist/commands/cloud-verifier-python-manifest-selector-census.d.ts +27 -0
- package/dist/commands/cloud-verifier-python-manifest-selector-census.js +2015 -0
- package/dist/commands/cloud-verifier-specialized-operational-census.d.ts +51 -0
- package/dist/commands/cloud-verifier-specialized-operational-census.js +11432 -0
- package/dist/commands/cloud-verifier-universe.d.ts +31 -0
- package/dist/commands/cloud-verifier-universe.js +10178 -0
- package/dist/commands/design-verify.d.ts +31 -0
- package/dist/commands/design-verify.js +285 -0
- package/dist/commands/hooks.d.ts +1 -5
- package/dist/commands/hooks.js +12 -114
- package/dist/commands/install-session-hooks.d.ts +2 -2
- package/dist/commands/install-session-hooks.js +71 -21
- package/dist/commands/mcp.js +1 -34
- package/dist/commands/pr.js +2 -7
- package/dist/commands/prepare-universe-review.d.ts +115 -0
- package/dist/commands/prepare-universe-review.js +1092 -0
- package/dist/commands/production-source-deny-policy.d.ts +15 -0
- package/dist/commands/production-source-deny-policy.js +100 -0
- package/dist/commands/scaffold-provisional-universe.d.ts +468 -0
- package/dist/commands/scaffold-provisional-universe.js +808 -0
- package/dist/commands/schema-cmd.js +32 -0
- package/dist/commands/setup.d.ts +1 -2
- package/dist/commands/setup.js +18 -272
- package/dist/commands/skills.d.ts +2 -2
- package/dist/commands/skills.js +415 -47
- package/dist/commands/submit.js +1 -2
- package/dist/commands/telemetry.d.ts +53 -0
- package/dist/commands/telemetry.js +832 -0
- package/dist/commands/verify-hosted.d.ts +13 -0
- package/dist/commands/verify-hosted.js +49 -1
- package/dist/commands/verify-precompute.d.ts +19 -0
- package/dist/commands/verify-precompute.js +328 -0
- package/dist/index.js +168 -47
- package/dist/schema.d.ts +1 -2
- package/dist/schema.js +1 -2
- package/dist/triage/prompts.d.ts +0 -7
- package/dist/triage/prompts.js +0 -145
- package/dist/triage/runner.js +10 -28
- package/dist/triage/types.d.ts +1 -8
- package/dist/types.d.ts +8 -8
- package/dist/types.js +1 -1
- package/dist/utils/design-verifier-api.d.ts +200 -0
- package/dist/utils/design-verifier-api.js +271 -0
- package/dist/utils/design-verifier-result.d.ts +57 -0
- package/dist/utils/design-verifier-result.js +345 -0
- package/dist/utils/git.d.ts +5 -1
- package/dist/utils/git.js +19 -9
- package/dist/utils/github-api.js +11 -14
- package/dist/utils/haystack-api.d.ts +2 -0
- package/dist/utils/haystack-api.js +10 -3
- package/dist/utils/hooks.d.ts +1 -15
- package/dist/utils/hooks.js +3 -128
- package/dist/utils/telemetry.d.ts +2 -0
- package/dist/utils/telemetry.js +81 -8
- package/package.json +8 -2
- package/schemas/cloud-verifier.v1.json +69 -3
- package/schemas/{pr.v2.json → pr.v3.json} +3 -12
- package/dist/commands/traces.d.ts +0 -48
- package/dist/commands/traces.js +0 -92
- package/dist/triage/traces.d.ts +0 -20
- package/dist/triage/traces.js +0 -311
- 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
|
+
}
|
package/dist/commands/hooks.d.ts
CHANGED
|
@@ -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
|
|
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 {};
|
package/dist/commands/hooks.js
CHANGED
|
@@ -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
|
|
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,
|
|
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:
|
|
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
|
|
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
|
|
47
|
+
console.log(chalk.yellow('⚠ Failed to install hook dependencies. Run `npm ci` in hooks/ manually.'));
|
|
72
48
|
}
|
|
73
|
-
// Step
|
|
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
|
|
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
|
|
119
|
-
console.log(chalk.dim(' post-commit
|
|
120
|
-
console.log(chalk.dim(' pre-push
|
|
121
|
-
console.log(chalk.dim(' prepare-commit-msg
|
|
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
|
|
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
|
|
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
|
*/
|