@haystackeditor/cli 0.11.0 → 0.12.0
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 +9 -12
- package/dist/assets/hooks/agent-context/detect.ts +180 -0
- package/dist/assets/hooks/agent-context/format.ts +1 -0
- package/dist/assets/hooks/agent-context/index.ts +2 -0
- package/dist/assets/hooks/agent-context/parsers/claude.ts +14 -5
- package/dist/assets/hooks/agent-context/parsers/codex.ts +416 -0
- package/dist/assets/hooks/agent-context/tsconfig.json +1 -0
- package/dist/assets/hooks/agent-context/types.ts +1 -1
- package/dist/assets/hooks/package.json +2 -1
- package/dist/assets/hooks/scripts/pre-commit.sh +80 -2
- package/dist/assets/hooks/scripts/pre-push.sh +1 -1
- package/dist/assets/skills/submit.md +20 -0
- package/dist/commands/config.d.ts +4 -0
- package/dist/commands/config.js +15 -5
- package/dist/commands/install-session-hooks.d.ts +3 -2
- package/dist/commands/install-session-hooks.js +27 -11
- package/dist/commands/pr-status.d.ts +5 -0
- package/dist/commands/pr-status.js +2 -2
- package/dist/commands/setup.js +21 -10
- package/dist/commands/submit.d.ts +1 -0
- package/dist/commands/submit.js +11 -18
- package/dist/commands/triage.d.ts +16 -4
- package/dist/commands/triage.js +210 -203
- package/dist/index.d.ts +1 -1
- package/dist/index.js +30 -32
- package/dist/triage/traces.js +9 -3
- package/dist/types.d.ts +14 -0
- package/dist/types.js +2 -0
- package/package.json +1 -1
- package/dist/commands/check-pending.d.ts +0 -19
- package/dist/commands/check-pending.js +0 -217
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* install-session-hooks — Wire up session-start hooks for coding CLIs.
|
|
3
3
|
*
|
|
4
|
-
* When a CLI session starts, `haystack
|
|
5
|
-
* and shows the user whether their
|
|
4
|
+
* When a CLI session starts, `haystack triage --hook` runs automatically
|
|
5
|
+
* and shows the user whether their last submitted PR is "Good to merge" or
|
|
6
|
+
* "Needs input", with the Haystack rating and auto-fixer status.
|
|
6
7
|
*
|
|
7
8
|
* Supported CLIs:
|
|
8
9
|
* - Claude Code: Native SessionStart hook in .claude/settings.json
|
|
@@ -41,7 +42,7 @@ function detectInstalledCLIs() {
|
|
|
41
42
|
// ============================================================================
|
|
42
43
|
// Claude Code: Native SessionStart hook
|
|
43
44
|
// ============================================================================
|
|
44
|
-
const HAYSTACK_HOOK_COMMAND = 'haystack
|
|
45
|
+
const HAYSTACK_HOOK_COMMAND = 'haystack triage --hook';
|
|
45
46
|
function installClaudeHook(gitRoot) {
|
|
46
47
|
const settingsDir = join(gitRoot, '.claude');
|
|
47
48
|
const settingsPath = join(settingsDir, 'settings.json');
|
|
@@ -61,13 +62,28 @@ function installClaudeHook(gitRoot) {
|
|
|
61
62
|
// Ensure SessionStart array exists
|
|
62
63
|
const sessionStart = (hooks.SessionStart || []);
|
|
63
64
|
hooks.SessionStart = sessionStart;
|
|
64
|
-
// Check if haystack hook already installed
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
65
|
+
// Check if haystack hook already installed (current or legacy)
|
|
66
|
+
const isHaystackHook = (h) => h.command?.includes('haystack triage') || h.command?.includes('haystack check-pending');
|
|
67
|
+
const existingIndex = sessionStart.findIndex(entry => entry.hooks?.some(h => isHaystackHook(h)));
|
|
68
|
+
if (existingIndex !== -1) {
|
|
69
|
+
const entry = sessionStart[existingIndex];
|
|
70
|
+
const isLegacy = entry.hooks?.some(h => h.command?.includes('haystack check-pending'));
|
|
71
|
+
if (isLegacy) {
|
|
72
|
+
// Migrate legacy hook in-place
|
|
73
|
+
for (const h of entry.hooks || []) {
|
|
74
|
+
if (h.command?.includes('haystack check-pending')) {
|
|
75
|
+
h.command = HAYSTACK_HOOK_COMMAND;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
|
|
79
|
+
console.log(chalk.green(' Claude Code hook migrated from check-pending to triage.'));
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
console.log(chalk.dim(' Claude Code hook already installed.'));
|
|
83
|
+
}
|
|
68
84
|
return true;
|
|
69
85
|
}
|
|
70
|
-
// Add the hook
|
|
86
|
+
// Add the hook (fresh install)
|
|
71
87
|
sessionStart.push({
|
|
72
88
|
matcher: '',
|
|
73
89
|
hooks: [
|
|
@@ -92,7 +108,7 @@ function checkClaudeHook(gitRoot) {
|
|
|
92
108
|
try {
|
|
93
109
|
const settings = JSON.parse(readFileSync(settingsPath, 'utf-8'));
|
|
94
110
|
const sessionStart = settings?.hooks?.SessionStart || [];
|
|
95
|
-
return sessionStart.some((entry) => entry.hooks?.some((h) => h.command?.includes('haystack check-pending')));
|
|
111
|
+
return sessionStart.some((entry) => entry.hooks?.some((h) => h.command?.includes('haystack triage') || h.command?.includes('haystack check-pending')));
|
|
96
112
|
}
|
|
97
113
|
catch {
|
|
98
114
|
return false;
|
|
@@ -109,7 +125,7 @@ ${CODEX_HOOK_MARKER}
|
|
|
109
125
|
When starting a new session, run this command first to check for pending PR analysis results:
|
|
110
126
|
|
|
111
127
|
\`\`\`bash
|
|
112
|
-
haystack
|
|
128
|
+
haystack triage --hook
|
|
113
129
|
\`\`\`
|
|
114
130
|
|
|
115
131
|
This shows whether your submitted PRs are "Good to merge" or "Need your input".
|
|
@@ -157,7 +173,7 @@ ${GEMINI_HOOK_MARKER}
|
|
|
157
173
|
When starting a new session, run this command first to check for pending PR analysis results:
|
|
158
174
|
|
|
159
175
|
\`\`\`bash
|
|
160
|
-
haystack
|
|
176
|
+
haystack triage --hook
|
|
161
177
|
\`\`\`
|
|
162
178
|
|
|
163
179
|
This shows whether your submitted PRs are "Good to merge" or "Need your input".
|
|
@@ -13,4 +13,9 @@
|
|
|
13
13
|
export interface PRStatusOptions {
|
|
14
14
|
json?: boolean;
|
|
15
15
|
}
|
|
16
|
+
export declare const BUCKET_DISPLAY: Record<string, {
|
|
17
|
+
label: string;
|
|
18
|
+
color: (s: string) => string;
|
|
19
|
+
icon: string;
|
|
20
|
+
}>;
|
|
16
21
|
export declare function prStatusCommand(identifier: string, options: PRStatusOptions): Promise<void>;
|
|
@@ -46,9 +46,9 @@ function parsePRIdentifier(identifier) {
|
|
|
46
46
|
// ============================================================================
|
|
47
47
|
// Display helpers
|
|
48
48
|
// ============================================================================
|
|
49
|
-
const BUCKET_DISPLAY = {
|
|
49
|
+
export const BUCKET_DISPLAY = {
|
|
50
50
|
'analyzing': { label: 'Analyzing', color: chalk.blue, icon: '...' },
|
|
51
|
-
'auto-fixing': { label: 'Auto-fixing', color: chalk.blue, icon: '>>>' },
|
|
51
|
+
'auto-fixing': { label: 'Auto-fixing (alpha)', color: chalk.blue, icon: '>>>' },
|
|
52
52
|
'good-to-merge': { label: 'Good to Merge', color: chalk.green, icon: '[+]' },
|
|
53
53
|
'failed-when-merging': { label: 'Failed When Merging', color: chalk.red, icon: '[!]' },
|
|
54
54
|
'issues': { label: 'Issues Found', color: chalk.yellow, icon: '[?]' },
|
package/dist/commands/setup.js
CHANGED
|
@@ -509,7 +509,7 @@ export async function setupCommand() {
|
|
|
509
509
|
// =============================================================================
|
|
510
510
|
// Step 7: Install Entire CLI for session tracking
|
|
511
511
|
// =============================================================================
|
|
512
|
-
const ENTIRE_VERSION = 'v0.5.3-haystack.
|
|
512
|
+
const ENTIRE_VERSION = 'v0.5.3-haystack.2';
|
|
513
513
|
const ENTIRE_RELEASE_BASE = `https://github.com/haystackeditor/cli/releases/download/${ENTIRE_VERSION}`;
|
|
514
514
|
function getEntireBinaryName() {
|
|
515
515
|
const platform = process.platform === 'darwin' ? 'darwin'
|
|
@@ -591,7 +591,7 @@ async function installEntireBinary() {
|
|
|
591
591
|
}
|
|
592
592
|
}
|
|
593
593
|
/**
|
|
594
|
-
* Write .entire/settings.json
|
|
594
|
+
* Write .entire/settings.json via the GitHub API.
|
|
595
595
|
* selectedRepos are GitHub full names (owner/repo), not local paths.
|
|
596
596
|
*/
|
|
597
597
|
async function configureEntireSettingsViaAPI(repoFullName, token) {
|
|
@@ -606,12 +606,14 @@ async function configureEntireSettingsViaAPI(repoFullName, token) {
|
|
|
606
606
|
// Fetch existing .entire/settings.json (if any)
|
|
607
607
|
let existingSha = null;
|
|
608
608
|
let settings = { enabled: true };
|
|
609
|
+
let originalContent = '';
|
|
609
610
|
const getResp = await fetch(`${GITHUB_API}/repos/${owner}/${repo}/contents/${filePath}`, { headers });
|
|
610
611
|
if (getResp.ok) {
|
|
611
612
|
const data = (await getResp.json());
|
|
612
613
|
existingSha = data.sha;
|
|
614
|
+
originalContent = Buffer.from(data.content, 'base64').toString('utf-8');
|
|
613
615
|
try {
|
|
614
|
-
settings = JSON.parse(
|
|
616
|
+
settings = JSON.parse(originalContent);
|
|
615
617
|
}
|
|
616
618
|
catch {
|
|
617
619
|
// Malformed JSON — keep sha for update but use fresh defaults
|
|
@@ -630,13 +632,22 @@ async function configureEntireSettingsViaAPI(repoFullName, token) {
|
|
|
630
632
|
throw error;
|
|
631
633
|
}
|
|
632
634
|
// 404 → file doesn't exist, existingSha stays null → PUT will create
|
|
633
|
-
//
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
635
|
+
// Remove legacy skip_raw_transcript if present (v0.5.3-haystack.2+ compacts automatically)
|
|
636
|
+
if (settings.strategy_options && typeof settings.strategy_options === 'object') {
|
|
637
|
+
const strategyOptions = settings.strategy_options;
|
|
638
|
+
delete strategyOptions.skip_raw_transcript;
|
|
639
|
+
if (Object.keys(strategyOptions).length === 0) {
|
|
640
|
+
delete settings.strategy_options;
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
const updatedContent = JSON.stringify(settings, null, 2) + '\n';
|
|
644
|
+
// Skip write if nothing changed (avoids no-op commits)
|
|
645
|
+
if (existingSha && updatedContent === originalContent) {
|
|
646
|
+
return;
|
|
647
|
+
}
|
|
648
|
+
const content = Buffer.from(updatedContent).toString('base64');
|
|
638
649
|
const body = {
|
|
639
|
-
message: 'chore:
|
|
650
|
+
message: 'chore: configure Entire CLI settings',
|
|
640
651
|
content,
|
|
641
652
|
};
|
|
642
653
|
if (existingSha)
|
|
@@ -720,7 +731,7 @@ async function stepInstallEntire(selectedRepos, token) {
|
|
|
720
731
|
}
|
|
721
732
|
}
|
|
722
733
|
if (configured > 0) {
|
|
723
|
-
console.log(chalk.green(`\n ✓ Session tracking configured on ${configured} repo(s) (
|
|
734
|
+
console.log(chalk.green(`\n ✓ Session tracking configured on ${configured} repo(s) (transcripts auto-compacted)\n`));
|
|
724
735
|
trackSetupEvent('entire_configured', { configured, total: selectedRepos.length });
|
|
725
736
|
}
|
|
726
737
|
else {
|
package/dist/commands/submit.js
CHANGED
|
@@ -16,7 +16,6 @@ import { createPullRequest, findExistingPR, requireGitHubAuth, requestReviewers,
|
|
|
16
16
|
import { runTriage } from '../triage/runner.js';
|
|
17
17
|
import { checkAnalysisReady, fetchAnalysisResults } from '../utils/analysis-api.js';
|
|
18
18
|
import { savePendingSubmit, updatePendingSubmit } from '../utils/pending-state.js';
|
|
19
|
-
import { isAutoMergeEnabled } from './config.js';
|
|
20
19
|
import { ensureHaystackLabels, addLabelsToIssue, } from '../utils/github-api.js';
|
|
21
20
|
// ============================================================================
|
|
22
21
|
// Helpers
|
|
@@ -269,14 +268,7 @@ export async function submitCommand(options) {
|
|
|
269
268
|
// -------------------------------------------------------------------------
|
|
270
269
|
// Step 6: Apply auto-merge label if enabled
|
|
271
270
|
// -------------------------------------------------------------------------
|
|
272
|
-
|
|
273
|
-
try {
|
|
274
|
-
autoMergeEnabled = await isAutoMergeEnabled();
|
|
275
|
-
}
|
|
276
|
-
catch {
|
|
277
|
-
// Preference check failure should not block submission
|
|
278
|
-
}
|
|
279
|
-
if (autoMergeEnabled) {
|
|
271
|
+
if (options.autoMerge) {
|
|
280
272
|
try {
|
|
281
273
|
await ensureHaystackLabels(remoteInfo.owner, remoteInfo.repo, ['haystack:auto-merge']);
|
|
282
274
|
await addLabelsToIssue(remoteInfo.owner, remoteInfo.repo, prNumber, ['haystack:auto-merge']);
|
|
@@ -293,7 +285,7 @@ export async function submitCommand(options) {
|
|
|
293
285
|
try {
|
|
294
286
|
await ensureHaystackLabels(remoteInfo.owner, remoteInfo.repo, ['haystack:auto-fix']);
|
|
295
287
|
await addLabelsToIssue(remoteInfo.owner, remoteInfo.repo, prNumber, ['haystack:auto-fix']);
|
|
296
|
-
console.log(chalk.green('✓ Auto-fix enabled for this PR'));
|
|
288
|
+
console.log(chalk.green('✓ Auto-fix (alpha) enabled for this PR'));
|
|
297
289
|
}
|
|
298
290
|
catch (err) {
|
|
299
291
|
console.log(chalk.yellow(`⚠ Could not set auto-fix label: ${err.message}`));
|
|
@@ -328,11 +320,11 @@ export async function submitCommand(options) {
|
|
|
328
320
|
console.log(` ${chalk.dim('URL:')} ${chalk.cyan(prUrl)}`);
|
|
329
321
|
console.log(` ${chalk.dim('Title:')} ${options.title || getLastCommitMessage()}`);
|
|
330
322
|
console.log(` ${chalk.dim('Branch:')} ${currentBranch} → ${baseBranch}`);
|
|
331
|
-
if (
|
|
323
|
+
if (options.autoMerge) {
|
|
332
324
|
console.log(` ${chalk.dim('Merge:')} ${chalk.green('Auto-merge if safe')}`);
|
|
333
325
|
}
|
|
334
326
|
if (options.autoFix) {
|
|
335
|
-
console.log(` ${chalk.dim('Fix:')} ${chalk.cyan('Auto-fix
|
|
327
|
+
console.log(` ${chalk.dim('Fix:')} ${chalk.cyan('Auto-fix alpha enabled')}`);
|
|
336
328
|
}
|
|
337
329
|
if (options.review) {
|
|
338
330
|
const reviewer = typeof options.review === 'string' ? options.review : 'needs assignment';
|
|
@@ -343,7 +335,7 @@ export async function submitCommand(options) {
|
|
|
343
335
|
// Step 9: Wait for Haystack analysis (triggered via GitHub App webhook)
|
|
344
336
|
// -------------------------------------------------------------------------
|
|
345
337
|
console.log(chalk.dim('Haystack analysis triggered via GitHub App webhook.'));
|
|
346
|
-
// Save pending state (so
|
|
338
|
+
// Save pending state (so `haystack triage` works even if we disconnect)
|
|
347
339
|
const pendingState = {
|
|
348
340
|
version: 1,
|
|
349
341
|
prNumber,
|
|
@@ -359,7 +351,7 @@ export async function submitCommand(options) {
|
|
|
359
351
|
// If --no-wait or non-interactive, exit here
|
|
360
352
|
if (options.wait === false || !process.stdin.isTTY) {
|
|
361
353
|
console.log(chalk.dim('\nAnalysis running in background.'));
|
|
362
|
-
console.log(chalk.dim('Check results later with: ') + chalk.cyan('haystack
|
|
354
|
+
console.log(chalk.dim('Check results later with: ') + chalk.cyan('haystack triage\n'));
|
|
363
355
|
return;
|
|
364
356
|
}
|
|
365
357
|
// Wait for analysis inline by polling the result endpoint
|
|
@@ -369,7 +361,7 @@ export async function submitCommand(options) {
|
|
|
369
361
|
const sigintHandler = () => {
|
|
370
362
|
interrupted = true;
|
|
371
363
|
console.log(chalk.dim('\n\nAnalysis still running. Check later with:'));
|
|
372
|
-
console.log(chalk.cyan(' haystack
|
|
364
|
+
console.log(chalk.cyan(' haystack triage\n'));
|
|
373
365
|
process.exit(0);
|
|
374
366
|
};
|
|
375
367
|
process.on('SIGINT', sigintHandler);
|
|
@@ -407,9 +399,10 @@ export async function submitCommand(options) {
|
|
|
407
399
|
else if (options.autoFix) {
|
|
408
400
|
// Auto-fix flow: agent fixes straightforward issues in the background.
|
|
409
401
|
// Issues appear in Feed immediately — user can review while agent works.
|
|
410
|
-
console.log(chalk.cyan.bold(' ⚙ Auto-fix triggered\n'));
|
|
402
|
+
console.log(chalk.cyan.bold(' ⚙ Auto-fix triggered (alpha)\n'));
|
|
411
403
|
console.log(chalk.dim(` ${verdict.summary}\n`));
|
|
412
|
-
console.log(chalk.dim(' Coding agent is fixing straightforward issues in the background.'));
|
|
404
|
+
console.log(chalk.dim(' Coding agent is fixing straightforward mechanical issues in the background.'));
|
|
405
|
+
console.log(chalk.dim(' Auto-fix is currently alpha.'));
|
|
413
406
|
console.log(chalk.dim(' Issues that need your judgment are in the Feed now.\n'));
|
|
414
407
|
const reviewUrl = `https://haystackeditor.com/review/${remoteInfo.owner}/${remoteInfo.repo}/${prNumber}`;
|
|
415
408
|
console.log(` ${chalk.dim('Feed:')} ${chalk.cyan(reviewUrl)}\n`);
|
|
@@ -451,6 +444,6 @@ export async function submitCommand(options) {
|
|
|
451
444
|
process.removeListener('SIGINT', sigintHandler);
|
|
452
445
|
if (Date.now() >= deadline && !interrupted) {
|
|
453
446
|
console.log(chalk.yellow('\n\n Timed out waiting for analysis.'));
|
|
454
|
-
console.log(chalk.dim(' Check results later with: ') + chalk.cyan('haystack
|
|
447
|
+
console.log(chalk.dim(' Check results later with: ') + chalk.cyan('haystack triage\n'));
|
|
455
448
|
}
|
|
456
449
|
}
|
|
@@ -1,16 +1,28 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* triage command —
|
|
2
|
+
* triage command — Show Haystack analysis results for a PR.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Designed for coding agents to consume. Shows two things:
|
|
5
|
+
* 1. What the auto-fixer is already handling (so the agent doesn't duplicate work)
|
|
6
|
+
* 2. Findings with attribution and fix prompts (so the agent knows what to fix)
|
|
7
|
+
*
|
|
8
|
+
* When called without a PR identifier, reads the last submitted PR from
|
|
9
|
+
* `.haystack/pending-submit.json` (written by `haystack submit`).
|
|
6
10
|
*
|
|
7
11
|
* Accepts a PR identifier in several formats:
|
|
12
|
+
* haystack triage # Last submitted PR
|
|
8
13
|
* haystack triage 123 # Uses current repo's owner/repo
|
|
9
14
|
* haystack triage owner/repo#123 # Fully qualified
|
|
10
15
|
* haystack triage https://github.com/owner/repo/pull/123
|
|
16
|
+
*
|
|
17
|
+
* Output modes:
|
|
18
|
+
* --hook Minimal single-line for session-start hooks
|
|
19
|
+
* --json Machine-readable JSON
|
|
20
|
+
* (default) Rich terminal output
|
|
11
21
|
*/
|
|
12
22
|
export interface TriageOptions {
|
|
13
23
|
json?: boolean;
|
|
14
24
|
wait?: boolean;
|
|
25
|
+
hook?: boolean;
|
|
26
|
+
clear?: boolean;
|
|
15
27
|
}
|
|
16
|
-
export declare function triageCommand(identifier: string, options: TriageOptions): Promise<void>;
|
|
28
|
+
export declare function triageCommand(identifier: string | undefined, options: TriageOptions): Promise<void>;
|