@haystackeditor/cli 0.10.3 → 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 +15 -16
- 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 +94 -69
- package/dist/commands/dismiss.js +2 -1
- 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/request-review.d.ts +15 -0
- package/dist/commands/request-review.js +95 -0
- package/dist/commands/setup.d.ts +1 -0
- package/dist/commands/setup.js +285 -2
- package/dist/commands/submit.d.ts +1 -0
- package/dist/commands/submit.js +12 -19
- package/dist/commands/triage.d.ts +16 -4
- package/dist/commands/triage.js +278 -179
- package/dist/index.d.ts +2 -1
- package/dist/index.js +73 -50
- package/dist/triage/prompts.js +13 -5
- package/dist/triage/runner.js +1 -1
- package/dist/triage/traces.js +9 -3
- package/dist/triage/types.d.ts +1 -1
- package/dist/types.d.ts +146 -26
- package/dist/types.js +44 -5
- package/dist/utils/analysis-api.d.ts +16 -0
- package/dist/utils/analysis-api.js +23 -5
- package/dist/utils/telemetry.d.ts +17 -0
- package/dist/utils/telemetry.js +109 -0
- package/package.json +1 -1
- package/dist/commands/check-pending.d.ts +0 -19
- package/dist/commands/check-pending.js +0 -217
package/dist/commands/setup.js
CHANGED
|
@@ -9,10 +9,17 @@
|
|
|
9
9
|
* 5. Scan for review policies
|
|
10
10
|
* 6. Review discovered items (toggle on/off)
|
|
11
11
|
* 7. Confirm & write .haystack.json to selected repos
|
|
12
|
+
* 8. Install session tracking (Entire CLI + .entire/settings.json + local hooks)
|
|
12
13
|
*/
|
|
13
14
|
import chalk from 'chalk';
|
|
14
15
|
import inquirer from 'inquirer';
|
|
16
|
+
import { execFileSync, execSync } from 'child_process';
|
|
17
|
+
import { mkdirSync } from 'fs';
|
|
18
|
+
import { basename, join } from 'path';
|
|
15
19
|
import { loadToken } from './login.js';
|
|
20
|
+
import { hooksInstall } from './hooks.js';
|
|
21
|
+
import { findGitRoot } from '../utils/hooks.js';
|
|
22
|
+
import { trackError, trackSetupEvent } from '../utils/telemetry.js';
|
|
16
23
|
// =============================================================================
|
|
17
24
|
// Constants
|
|
18
25
|
// =============================================================================
|
|
@@ -433,7 +440,7 @@ async function stepConfirm(selectedRepos, rules, signals, policies, token) {
|
|
|
433
440
|
]);
|
|
434
441
|
if (!confirmed) {
|
|
435
442
|
console.log(chalk.yellow('\n Setup cancelled.\n'));
|
|
436
|
-
return;
|
|
443
|
+
return false;
|
|
437
444
|
}
|
|
438
445
|
// Write to each repo
|
|
439
446
|
const failedRepos = [];
|
|
@@ -458,6 +465,7 @@ async function stepConfirm(selectedRepos, rules, signals, policies, token) {
|
|
|
458
465
|
process.exit(1);
|
|
459
466
|
}
|
|
460
467
|
console.log(chalk.green('\n Setup complete!\n'));
|
|
468
|
+
return true;
|
|
461
469
|
}
|
|
462
470
|
// =============================================================================
|
|
463
471
|
// Main command
|
|
@@ -492,5 +500,280 @@ export async function setupCommand() {
|
|
|
492
500
|
// Step 5: Review
|
|
493
501
|
const reviewed = await stepReview(rules, signals, policies);
|
|
494
502
|
// Step 6: Confirm & write
|
|
495
|
-
await stepConfirm(selectedRepos, reviewed.rules, reviewed.signals, reviewed.policies, token);
|
|
503
|
+
const confirmed = await stepConfirm(selectedRepos, reviewed.rules, reviewed.signals, reviewed.policies, token);
|
|
504
|
+
if (!confirmed)
|
|
505
|
+
return;
|
|
506
|
+
// Step 7: Install Entire CLI (session tracking)
|
|
507
|
+
await stepInstallEntire(selectedRepos, token);
|
|
508
|
+
}
|
|
509
|
+
// =============================================================================
|
|
510
|
+
// Step 7: Install Entire CLI for session tracking
|
|
511
|
+
// =============================================================================
|
|
512
|
+
const ENTIRE_VERSION = 'v0.5.3-haystack.2';
|
|
513
|
+
const ENTIRE_RELEASE_BASE = `https://github.com/haystackeditor/cli/releases/download/${ENTIRE_VERSION}`;
|
|
514
|
+
function getEntireBinaryName() {
|
|
515
|
+
const platform = process.platform === 'darwin' ? 'darwin'
|
|
516
|
+
: process.platform === 'linux' ? 'linux'
|
|
517
|
+
: null;
|
|
518
|
+
if (!platform)
|
|
519
|
+
return null; // unsupported (Windows, FreeBSD, etc.)
|
|
520
|
+
const arch = process.arch === 'arm64' ? 'arm64'
|
|
521
|
+
: process.arch === 'x64' ? 'amd64'
|
|
522
|
+
: null;
|
|
523
|
+
if (!arch)
|
|
524
|
+
return null; // unsupported (s390x, ppc64, etc.)
|
|
525
|
+
return `entire_${platform}_${arch}`;
|
|
526
|
+
}
|
|
527
|
+
function isEntireInstalled() {
|
|
528
|
+
// Check the canonical install location first, then fall back to PATH
|
|
529
|
+
const haystackBin = process.env.HOME
|
|
530
|
+
? join(process.env.HOME, '.haystack', 'bin', 'entire')
|
|
531
|
+
: null;
|
|
532
|
+
const candidates = haystackBin ? [haystackBin, 'entire'] : ['entire'];
|
|
533
|
+
for (const bin of candidates) {
|
|
534
|
+
try {
|
|
535
|
+
const version = execSync(`"${bin}" --version 2>&1`, { encoding: 'utf-8' }).trim();
|
|
536
|
+
return {
|
|
537
|
+
installed: true,
|
|
538
|
+
isHaystackFork: version.includes('haystack'),
|
|
539
|
+
version,
|
|
540
|
+
};
|
|
541
|
+
}
|
|
542
|
+
catch {
|
|
543
|
+
// Try next candidate
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
return { installed: false, isHaystackFork: false, version: '' };
|
|
547
|
+
}
|
|
548
|
+
async function installEntireBinary() {
|
|
549
|
+
const binaryName = getEntireBinaryName();
|
|
550
|
+
if (!binaryName) {
|
|
551
|
+
console.log(chalk.yellow(` Entire CLI is not available for ${process.platform}/${process.arch}.`));
|
|
552
|
+
console.log(chalk.dim(' Supported: macOS (Intel/Apple Silicon), Linux (x86_64/ARM64)'));
|
|
553
|
+
return false;
|
|
554
|
+
}
|
|
555
|
+
const url = `${ENTIRE_RELEASE_BASE}/${binaryName}`;
|
|
556
|
+
const installDir = process.env.HOME
|
|
557
|
+
? join(process.env.HOME, '.haystack', 'bin')
|
|
558
|
+
: '/usr/local/bin';
|
|
559
|
+
try {
|
|
560
|
+
mkdirSync(installDir, { recursive: true });
|
|
561
|
+
const targetPath = join(installDir, 'entire');
|
|
562
|
+
console.log(chalk.dim(` Downloading ${ENTIRE_VERSION}...`));
|
|
563
|
+
execFileSync('curl', ['-fsSL', url, '-o', targetPath], { stdio: 'pipe' });
|
|
564
|
+
execFileSync('chmod', ['+x', targetPath], { stdio: 'pipe' });
|
|
565
|
+
// Verify using absolute path (don't rely on PATH)
|
|
566
|
+
const version = execFileSync(targetPath, ['--version'], { encoding: 'utf-8' }).trim();
|
|
567
|
+
if (!version.includes('haystack')) {
|
|
568
|
+
console.log(chalk.red(` Unexpected version: ${version}`));
|
|
569
|
+
return false;
|
|
570
|
+
}
|
|
571
|
+
console.log(chalk.green(` ✓ Installed: ${version.split('\n')[0]}`));
|
|
572
|
+
// Check if install dir is on PATH
|
|
573
|
+
const pathDirs = (process.env.PATH || '').split(':');
|
|
574
|
+
if (!pathDirs.includes(installDir)) {
|
|
575
|
+
console.log(chalk.yellow(`\n Note: ${installDir} is not on your PATH.`));
|
|
576
|
+
console.log(chalk.dim(` Add to your shell profile: export PATH="${installDir}:$PATH"\n`));
|
|
577
|
+
}
|
|
578
|
+
return true;
|
|
579
|
+
}
|
|
580
|
+
catch (err) {
|
|
581
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
582
|
+
console.log(chalk.red(` Failed to install Entire CLI: ${errorMsg}`));
|
|
583
|
+
console.log(chalk.dim(` Manual install: curl -fsSL ${url} -o ~/.haystack/bin/entire && chmod +x ~/.haystack/bin/entire`));
|
|
584
|
+
trackError('entire_install_failed', {
|
|
585
|
+
error_message: errorMsg,
|
|
586
|
+
error_stack: err instanceof Error ? err.stack : undefined,
|
|
587
|
+
entire_version: ENTIRE_VERSION,
|
|
588
|
+
install_dir: installDir,
|
|
589
|
+
});
|
|
590
|
+
return false;
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
/**
|
|
594
|
+
* Write .entire/settings.json via the GitHub API.
|
|
595
|
+
* selectedRepos are GitHub full names (owner/repo), not local paths.
|
|
596
|
+
*/
|
|
597
|
+
async function configureEntireSettingsViaAPI(repoFullName, token) {
|
|
598
|
+
const [owner, repo] = repoFullName.split('/');
|
|
599
|
+
const filePath = '.entire/settings.json';
|
|
600
|
+
const headers = {
|
|
601
|
+
Authorization: `Bearer ${token}`,
|
|
602
|
+
Accept: 'application/vnd.github.v3+json',
|
|
603
|
+
'User-Agent': 'Haystack-CLI',
|
|
604
|
+
'Content-Type': 'application/json',
|
|
605
|
+
};
|
|
606
|
+
// Fetch existing .entire/settings.json (if any)
|
|
607
|
+
let existingSha = null;
|
|
608
|
+
let settings = { enabled: true };
|
|
609
|
+
let originalContent = '';
|
|
610
|
+
const getResp = await fetch(`${GITHUB_API}/repos/${owner}/${repo}/contents/${filePath}`, { headers });
|
|
611
|
+
if (getResp.ok) {
|
|
612
|
+
const data = (await getResp.json());
|
|
613
|
+
existingSha = data.sha;
|
|
614
|
+
originalContent = Buffer.from(data.content, 'base64').toString('utf-8');
|
|
615
|
+
try {
|
|
616
|
+
settings = JSON.parse(originalContent);
|
|
617
|
+
}
|
|
618
|
+
catch {
|
|
619
|
+
// Malformed JSON — keep sha for update but use fresh defaults
|
|
620
|
+
console.log(chalk.dim(` Existing .entire/settings.json in ${repoFullName} is malformed — will repair`));
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
else if (getResp.status !== 404) {
|
|
624
|
+
// Non-404 failure (auth, network, rate limit) — don't guess, surface it
|
|
625
|
+
const errText = await getResp.text().catch(() => '');
|
|
626
|
+
const error = new Error(`Failed to read .entire/settings.json: ${getResp.status} ${errText}`);
|
|
627
|
+
trackError('entire_settings_read_failed', {
|
|
628
|
+
error_message: error.message,
|
|
629
|
+
repo: repoFullName,
|
|
630
|
+
status: getResp.status,
|
|
631
|
+
});
|
|
632
|
+
throw error;
|
|
633
|
+
}
|
|
634
|
+
// 404 → file doesn't exist, existingSha stays null → PUT will create
|
|
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');
|
|
649
|
+
const body = {
|
|
650
|
+
message: 'chore: configure Entire CLI settings',
|
|
651
|
+
content,
|
|
652
|
+
};
|
|
653
|
+
if (existingSha)
|
|
654
|
+
body.sha = existingSha;
|
|
655
|
+
const putResp = await fetch(`${GITHUB_API}/repos/${owner}/${repo}/contents/${filePath}`, {
|
|
656
|
+
method: 'PUT',
|
|
657
|
+
headers,
|
|
658
|
+
body: JSON.stringify(body),
|
|
659
|
+
});
|
|
660
|
+
if (!putResp.ok) {
|
|
661
|
+
const errText = await putResp.text().catch(() => '');
|
|
662
|
+
const error = new Error(`GitHub API ${putResp.status}: ${errText}`);
|
|
663
|
+
trackError('entire_settings_write_failed', {
|
|
664
|
+
error_message: error.message,
|
|
665
|
+
repo: repoFullName,
|
|
666
|
+
status: putResp.status,
|
|
667
|
+
had_existing: !!existingSha,
|
|
668
|
+
});
|
|
669
|
+
throw error;
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
async function stepInstallEntire(selectedRepos, token) {
|
|
673
|
+
console.log(chalk.bold('\n Step 7: Session tracking (Entire CLI)\n'));
|
|
674
|
+
const status = isEntireInstalled();
|
|
675
|
+
let binaryReady = status.installed && status.isHaystackFork;
|
|
676
|
+
let userConsented = true; // true if user said yes or was already installed
|
|
677
|
+
if (binaryReady) {
|
|
678
|
+
console.log(chalk.dim(` Entire CLI already installed (${status.version.split('\n')[0]})`));
|
|
679
|
+
}
|
|
680
|
+
else if (status.installed) {
|
|
681
|
+
console.log(chalk.dim(` Stock Entire CLI detected: ${status.version.split('\n')[0]}`));
|
|
682
|
+
const { upgrade } = await inquirer.prompt([
|
|
683
|
+
{
|
|
684
|
+
type: 'confirm',
|
|
685
|
+
name: 'upgrade',
|
|
686
|
+
message: 'Upgrade to Haystack fork? (adds transcript optimization — saves GB of git storage)',
|
|
687
|
+
default: true,
|
|
688
|
+
},
|
|
689
|
+
]);
|
|
690
|
+
if (upgrade) {
|
|
691
|
+
binaryReady = await installEntireBinary();
|
|
692
|
+
}
|
|
693
|
+
else {
|
|
694
|
+
userConsented = false;
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
else {
|
|
698
|
+
const { install } = await inquirer.prompt([
|
|
699
|
+
{
|
|
700
|
+
type: 'confirm',
|
|
701
|
+
name: 'install',
|
|
702
|
+
message: 'Install Entire CLI? (tracks AI coding sessions for analysis)',
|
|
703
|
+
default: true,
|
|
704
|
+
},
|
|
705
|
+
]);
|
|
706
|
+
if (install) {
|
|
707
|
+
binaryReady = await installEntireBinary();
|
|
708
|
+
}
|
|
709
|
+
else {
|
|
710
|
+
userConsented = false;
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
if (!userConsented) {
|
|
714
|
+
console.log(chalk.dim(' Skipping session tracking.\n'));
|
|
715
|
+
}
|
|
716
|
+
// Configure .entire/settings.json on each repo via GitHub API
|
|
717
|
+
// (works even if binary install failed — settings are remote; skip if user declined)
|
|
718
|
+
if (userConsented) {
|
|
719
|
+
let configured = 0;
|
|
720
|
+
for (const repoFullName of selectedRepos) {
|
|
721
|
+
try {
|
|
722
|
+
process.stdout.write(chalk.dim(` Configuring ${repoFullName}...`));
|
|
723
|
+
await configureEntireSettingsViaAPI(repoFullName, token);
|
|
724
|
+
process.stdout.write('\r\x1b[K');
|
|
725
|
+
console.log(chalk.green(` ✓ ${repoFullName}`));
|
|
726
|
+
configured++;
|
|
727
|
+
}
|
|
728
|
+
catch (err) {
|
|
729
|
+
process.stdout.write('\r\x1b[K');
|
|
730
|
+
console.log(chalk.yellow(` ⚠ ${repoFullName}: ${err instanceof Error ? err.message : err}`));
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
if (configured > 0) {
|
|
734
|
+
console.log(chalk.green(`\n ✓ Session tracking configured on ${configured} repo(s) (transcripts auto-compacted)\n`));
|
|
735
|
+
trackSetupEvent('entire_configured', { configured, total: selectedRepos.length });
|
|
736
|
+
}
|
|
737
|
+
else {
|
|
738
|
+
console.log(chalk.yellow('\n ⚠ Could not configure session tracking on any repos.\n'));
|
|
739
|
+
trackError('entire_config_all_failed', { total: selectedRepos.length });
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
// Offer to install local git hooks if cwd is inside one of the selected repos
|
|
743
|
+
const gitRoot = findGitRoot();
|
|
744
|
+
if (gitRoot) {
|
|
745
|
+
const gitDirName = basename(gitRoot).toLowerCase();
|
|
746
|
+
const repoName = selectedRepos.find((r) => {
|
|
747
|
+
const name = r.split('/')[1]?.replace(/\.git$/, '');
|
|
748
|
+
return name && gitDirName === name.toLowerCase();
|
|
749
|
+
});
|
|
750
|
+
if (repoName) {
|
|
751
|
+
try {
|
|
752
|
+
const { installHooks } = await inquirer.prompt([
|
|
753
|
+
{
|
|
754
|
+
type: 'confirm',
|
|
755
|
+
name: 'installHooks',
|
|
756
|
+
message: `Install git hooks locally for ${repoName}? (agent detection, truncation checking, LLM rules)`,
|
|
757
|
+
default: true,
|
|
758
|
+
},
|
|
759
|
+
]);
|
|
760
|
+
if (installHooks) {
|
|
761
|
+
console.log('');
|
|
762
|
+
await hooksInstall({ skipEntire: true, force: true });
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
catch (err) {
|
|
766
|
+
console.log(chalk.yellow(` ⚠ Hook installation failed: ${err instanceof Error ? err.message : err}`));
|
|
767
|
+
console.log(chalk.dim(' You can install hooks later with: haystack hooks install'));
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
else {
|
|
771
|
+
console.log(chalk.dim(' To install git hooks in a repo, cd into it and run:'));
|
|
772
|
+
console.log(chalk.dim(' haystack hooks install\n'));
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
else {
|
|
776
|
+
console.log(chalk.dim(' To install git hooks in a repo, cd into it and run:'));
|
|
777
|
+
console.log(chalk.dim(' haystack hooks install\n'));
|
|
778
|
+
}
|
|
496
779
|
}
|
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
|
|
@@ -51,7 +50,7 @@ function checkerDisplayName(checker) {
|
|
|
51
50
|
switch (checker) {
|
|
52
51
|
case 'code-review': return 'Code Review';
|
|
53
52
|
case 'rules-validator': return 'Rules Validator';
|
|
54
|
-
case 'intent-drift': return '
|
|
53
|
+
case 'intent-drift': return 'Instruction Drift';
|
|
55
54
|
default: return checker;
|
|
56
55
|
}
|
|
57
56
|
}
|
|
@@ -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>;
|