@humanbased/crosscheck 0.14.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/AGENT.md +207 -0
- package/ISSUE.md +234 -0
- package/LICENSE +21 -0
- package/README.md +234 -0
- package/README.zh.md +169 -0
- package/assets/logo.png +0 -0
- package/assets/screenshot-watch-timing.png +0 -0
- package/assets/screenshot-watch-timing.svg +1 -0
- package/assets/screenshot-watch.png +0 -0
- package/crosscheck.config.example.yml +214 -0
- package/dist/__tests__/annotation.test.d.ts +2 -0
- package/dist/__tests__/annotation.test.d.ts.map +1 -0
- package/dist/__tests__/annotation.test.js +134 -0
- package/dist/__tests__/annotation.test.js.map +1 -0
- package/dist/__tests__/backtrace.test.d.ts +2 -0
- package/dist/__tests__/backtrace.test.d.ts.map +1 -0
- package/dist/__tests__/backtrace.test.js +280 -0
- package/dist/__tests__/backtrace.test.js.map +1 -0
- package/dist/__tests__/board.test.d.ts +2 -0
- package/dist/__tests__/board.test.d.ts.map +1 -0
- package/dist/__tests__/board.test.js +149 -0
- package/dist/__tests__/board.test.js.map +1 -0
- package/dist/__tests__/codex.test.d.ts +2 -0
- package/dist/__tests__/codex.test.d.ts.map +1 -0
- package/dist/__tests__/codex.test.js +92 -0
- package/dist/__tests__/codex.test.js.map +1 -0
- package/dist/__tests__/comment-bodies.test.d.ts +2 -0
- package/dist/__tests__/comment-bodies.test.d.ts.map +1 -0
- package/dist/__tests__/comment-bodies.test.js +75 -0
- package/dist/__tests__/comment-bodies.test.js.map +1 -0
- package/dist/__tests__/conflict-resolve.test.d.ts +2 -0
- package/dist/__tests__/conflict-resolve.test.d.ts.map +1 -0
- package/dist/__tests__/conflict-resolve.test.js +123 -0
- package/dist/__tests__/conflict-resolve.test.js.map +1 -0
- package/dist/__tests__/crosscheck-commit.test.d.ts +2 -0
- package/dist/__tests__/crosscheck-commit.test.d.ts.map +1 -0
- package/dist/__tests__/crosscheck-commit.test.js +13 -0
- package/dist/__tests__/crosscheck-commit.test.js.map +1 -0
- package/dist/__tests__/detector.test.d.ts +2 -0
- package/dist/__tests__/detector.test.d.ts.map +1 -0
- package/dist/__tests__/detector.test.js +112 -0
- package/dist/__tests__/detector.test.js.map +1 -0
- package/dist/__tests__/diagnose.test.d.ts +2 -0
- package/dist/__tests__/diagnose.test.d.ts.map +1 -0
- package/dist/__tests__/diagnose.test.js +164 -0
- package/dist/__tests__/diagnose.test.js.map +1 -0
- package/dist/__tests__/diff-hash.test.d.ts +2 -0
- package/dist/__tests__/diff-hash.test.d.ts.map +1 -0
- package/dist/__tests__/diff-hash.test.js +126 -0
- package/dist/__tests__/diff-hash.test.js.map +1 -0
- package/dist/__tests__/durations.test.d.ts +2 -0
- package/dist/__tests__/durations.test.d.ts.map +1 -0
- package/dist/__tests__/durations.test.js +26 -0
- package/dist/__tests__/durations.test.js.map +1 -0
- package/dist/__tests__/event-fields.test.d.ts +2 -0
- package/dist/__tests__/event-fields.test.d.ts.map +1 -0
- package/dist/__tests__/event-fields.test.js +50 -0
- package/dist/__tests__/event-fields.test.js.map +1 -0
- package/dist/__tests__/filter.test.d.ts +2 -0
- package/dist/__tests__/filter.test.d.ts.map +1 -0
- package/dist/__tests__/filter.test.js +21 -0
- package/dist/__tests__/filter.test.js.map +1 -0
- package/dist/__tests__/fix.test.d.ts +2 -0
- package/dist/__tests__/fix.test.d.ts.map +1 -0
- package/dist/__tests__/fix.test.js +124 -0
- package/dist/__tests__/fix.test.js.map +1 -0
- package/dist/__tests__/github-client.test.d.ts +2 -0
- package/dist/__tests__/github-client.test.d.ts.map +1 -0
- package/dist/__tests__/github-client.test.js +22 -0
- package/dist/__tests__/github-client.test.js.map +1 -0
- package/dist/__tests__/github-scan-client.test.d.ts +2 -0
- package/dist/__tests__/github-scan-client.test.d.ts.map +1 -0
- package/dist/__tests__/github-scan-client.test.js +100 -0
- package/dist/__tests__/github-scan-client.test.js.map +1 -0
- package/dist/__tests__/is-fresh-review-comment.test.d.ts +2 -0
- package/dist/__tests__/is-fresh-review-comment.test.d.ts.map +1 -0
- package/dist/__tests__/is-fresh-review-comment.test.js +86 -0
- package/dist/__tests__/is-fresh-review-comment.test.js.map +1 -0
- package/dist/__tests__/issue.test.d.ts +2 -0
- package/dist/__tests__/issue.test.d.ts.map +1 -0
- package/dist/__tests__/issue.test.js +259 -0
- package/dist/__tests__/issue.test.js.map +1 -0
- package/dist/__tests__/kickass.test.d.ts +2 -0
- package/dist/__tests__/kickass.test.d.ts.map +1 -0
- package/dist/__tests__/kickass.test.js +268 -0
- package/dist/__tests__/kickass.test.js.map +1 -0
- package/dist/__tests__/loader.test.d.ts +2 -0
- package/dist/__tests__/loader.test.d.ts.map +1 -0
- package/dist/__tests__/loader.test.js +180 -0
- package/dist/__tests__/loader.test.js.map +1 -0
- package/dist/__tests__/onboard-preservation.test.d.ts +2 -0
- package/dist/__tests__/onboard-preservation.test.d.ts.map +1 -0
- package/dist/__tests__/onboard-preservation.test.js +506 -0
- package/dist/__tests__/onboard-preservation.test.js.map +1 -0
- package/dist/__tests__/optimize.test.d.ts +2 -0
- package/dist/__tests__/optimize.test.d.ts.map +1 -0
- package/dist/__tests__/optimize.test.js +101 -0
- package/dist/__tests__/optimize.test.js.map +1 -0
- package/dist/__tests__/post-review-comment.test.d.ts +2 -0
- package/dist/__tests__/post-review-comment.test.d.ts.map +1 -0
- package/dist/__tests__/post-review-comment.test.js +44 -0
- package/dist/__tests__/post-review-comment.test.js.map +1 -0
- package/dist/__tests__/pr-lock.test.d.ts +2 -0
- package/dist/__tests__/pr-lock.test.d.ts.map +1 -0
- package/dist/__tests__/pr-lock.test.js +115 -0
- package/dist/__tests__/pr-lock.test.js.map +1 -0
- package/dist/__tests__/pr-picker.test.d.ts +2 -0
- package/dist/__tests__/pr-picker.test.d.ts.map +1 -0
- package/dist/__tests__/pr-picker.test.js +57 -0
- package/dist/__tests__/pr-picker.test.js.map +1 -0
- package/dist/__tests__/pr-status-scan.test.d.ts +2 -0
- package/dist/__tests__/pr-status-scan.test.d.ts.map +1 -0
- package/dist/__tests__/pr-status-scan.test.js +92 -0
- package/dist/__tests__/pr-status-scan.test.js.map +1 -0
- package/dist/__tests__/pr-status.test.d.ts +2 -0
- package/dist/__tests__/pr-status.test.d.ts.map +1 -0
- package/dist/__tests__/pr-status.test.js +346 -0
- package/dist/__tests__/pr-status.test.js.map +1 -0
- package/dist/__tests__/repo-picker.test.d.ts +2 -0
- package/dist/__tests__/repo-picker.test.d.ts.map +1 -0
- package/dist/__tests__/repo-picker.test.js +115 -0
- package/dist/__tests__/repo-picker.test.js.map +1 -0
- package/dist/__tests__/review-comment-body.test.d.ts +2 -0
- package/dist/__tests__/review-comment-body.test.d.ts.map +1 -0
- package/dist/__tests__/review-comment-body.test.js +54 -0
- package/dist/__tests__/review-comment-body.test.js.map +1 -0
- package/dist/__tests__/review-models.test.d.ts +2 -0
- package/dist/__tests__/review-models.test.d.ts.map +1 -0
- package/dist/__tests__/review-models.test.js +39 -0
- package/dist/__tests__/review-models.test.js.map +1 -0
- package/dist/__tests__/review-status.test.d.ts +2 -0
- package/dist/__tests__/review-status.test.d.ts.map +1 -0
- package/dist/__tests__/review-status.test.js +95 -0
- package/dist/__tests__/review-status.test.js.map +1 -0
- package/dist/__tests__/runner.test.d.ts +2 -0
- package/dist/__tests__/runner.test.d.ts.map +1 -0
- package/dist/__tests__/runner.test.js +204 -0
- package/dist/__tests__/runner.test.js.map +1 -0
- package/dist/__tests__/scan-cache.test.d.ts +2 -0
- package/dist/__tests__/scan-cache.test.d.ts.map +1 -0
- package/dist/__tests__/scan-cache.test.js +59 -0
- package/dist/__tests__/scan-cache.test.js.map +1 -0
- package/dist/__tests__/scan-client.test.d.ts +2 -0
- package/dist/__tests__/scan-client.test.d.ts.map +1 -0
- package/dist/__tests__/scan-client.test.js +30 -0
- package/dist/__tests__/scan-client.test.js.map +1 -0
- package/dist/__tests__/scan.test.d.ts +2 -0
- package/dist/__tests__/scan.test.d.ts.map +1 -0
- package/dist/__tests__/scan.test.js +115 -0
- package/dist/__tests__/scan.test.js.map +1 -0
- package/dist/__tests__/scopes.test.d.ts +2 -0
- package/dist/__tests__/scopes.test.d.ts.map +1 -0
- package/dist/__tests__/scopes.test.js +101 -0
- package/dist/__tests__/scopes.test.js.map +1 -0
- package/dist/__tests__/sha-cache.test.d.ts +2 -0
- package/dist/__tests__/sha-cache.test.d.ts.map +1 -0
- package/dist/__tests__/sha-cache.test.js +40 -0
- package/dist/__tests__/sha-cache.test.js.map +1 -0
- package/dist/__tests__/smart-switch.test.d.ts +2 -0
- package/dist/__tests__/smart-switch.test.d.ts.map +1 -0
- package/dist/__tests__/smart-switch.test.js +145 -0
- package/dist/__tests__/smart-switch.test.js.map +1 -0
- package/dist/ck.d.ts +3 -0
- package/dist/ck.d.ts.map +1 -0
- package/dist/ck.js +8 -0
- package/dist/ck.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +132 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/diagnose.d.ts +54 -0
- package/dist/commands/diagnose.d.ts.map +1 -0
- package/dist/commands/diagnose.js +294 -0
- package/dist/commands/diagnose.js.map +1 -0
- package/dist/commands/impact.d.ts +38 -0
- package/dist/commands/impact.d.ts.map +1 -0
- package/dist/commands/impact.js +210 -0
- package/dist/commands/impact.js.map +1 -0
- package/dist/commands/init.d.ts +12 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +183 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/issue.d.ts +25 -0
- package/dist/commands/issue.d.ts.map +1 -0
- package/dist/commands/issue.js +445 -0
- package/dist/commands/issue.js.map +1 -0
- package/dist/commands/kickass.d.ts +59 -0
- package/dist/commands/kickass.d.ts.map +1 -0
- package/dist/commands/kickass.js +288 -0
- package/dist/commands/kickass.js.map +1 -0
- package/dist/commands/onboard.d.ts +70 -0
- package/dist/commands/onboard.d.ts.map +1 -0
- package/dist/commands/onboard.js +883 -0
- package/dist/commands/onboard.js.map +1 -0
- package/dist/commands/optimize.d.ts +16 -0
- package/dist/commands/optimize.d.ts.map +1 -0
- package/dist/commands/optimize.js +244 -0
- package/dist/commands/optimize.js.map +1 -0
- package/dist/commands/review.d.ts +2 -0
- package/dist/commands/review.d.ts.map +1 -0
- package/dist/commands/review.js +118 -0
- package/dist/commands/review.js.map +1 -0
- package/dist/commands/run.d.ts +13 -0
- package/dist/commands/run.d.ts.map +1 -0
- package/dist/commands/run.js +243 -0
- package/dist/commands/run.js.map +1 -0
- package/dist/commands/scan.d.ts +94 -0
- package/dist/commands/scan.d.ts.map +1 -0
- package/dist/commands/scan.js +276 -0
- package/dist/commands/scan.js.map +1 -0
- package/dist/commands/serve.d.ts +9 -0
- package/dist/commands/serve.d.ts.map +1 -0
- package/dist/commands/serve.js +402 -0
- package/dist/commands/serve.js.map +1 -0
- package/dist/commands/status.d.ts +2 -0
- package/dist/commands/status.d.ts.map +1 -0
- package/dist/commands/status.js +89 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/commands/watch.d.ts +9 -0
- package/dist/commands/watch.d.ts.map +1 -0
- package/dist/commands/watch.js +902 -0
- package/dist/commands/watch.js.map +1 -0
- package/dist/config/loader.d.ts +47 -0
- package/dist/config/loader.d.ts.map +1 -0
- package/dist/config/loader.js +334 -0
- package/dist/config/loader.js.map +1 -0
- package/dist/config/schema.d.ts +814 -0
- package/dist/config/schema.d.ts.map +1 -0
- package/dist/config/schema.js +152 -0
- package/dist/config/schema.js.map +1 -0
- package/dist/github/client.d.ts +139 -0
- package/dist/github/client.d.ts.map +1 -0
- package/dist/github/client.js +711 -0
- package/dist/github/client.js.map +1 -0
- package/dist/github/detector.d.ts +12 -0
- package/dist/github/detector.d.ts.map +1 -0
- package/dist/github/detector.js +120 -0
- package/dist/github/detector.js.map +1 -0
- package/dist/github/merge.d.ts +9 -0
- package/dist/github/merge.d.ts.map +1 -0
- package/dist/github/merge.js +33 -0
- package/dist/github/merge.js.map +1 -0
- package/dist/github/review-status.d.ts +6 -0
- package/dist/github/review-status.d.ts.map +1 -0
- package/dist/github/review-status.js +51 -0
- package/dist/github/review-status.js.map +1 -0
- package/dist/github/webhook.d.ts +41 -0
- package/dist/github/webhook.d.ts.map +1 -0
- package/dist/github/webhook.js +50 -0
- package/dist/github/webhook.js.map +1 -0
- package/dist/lib/annotation.d.ts +23 -0
- package/dist/lib/annotation.d.ts.map +1 -0
- package/dist/lib/annotation.js +103 -0
- package/dist/lib/annotation.js.map +1 -0
- package/dist/lib/backtrace.d.ts +40 -0
- package/dist/lib/backtrace.d.ts.map +1 -0
- package/dist/lib/backtrace.js +169 -0
- package/dist/lib/backtrace.js.map +1 -0
- package/dist/lib/board.d.ts +74 -0
- package/dist/lib/board.d.ts.map +1 -0
- package/dist/lib/board.js +640 -0
- package/dist/lib/board.js.map +1 -0
- package/dist/lib/clone.d.ts +12 -0
- package/dist/lib/clone.d.ts.map +1 -0
- package/dist/lib/clone.js +30 -0
- package/dist/lib/clone.js.map +1 -0
- package/dist/lib/comment-bodies.d.ts +17 -0
- package/dist/lib/comment-bodies.d.ts.map +1 -0
- package/dist/lib/comment-bodies.js +51 -0
- package/dist/lib/comment-bodies.js.map +1 -0
- package/dist/lib/crosscheck-commit.d.ts +2 -0
- package/dist/lib/crosscheck-commit.d.ts.map +1 -0
- package/dist/lib/crosscheck-commit.js +4 -0
- package/dist/lib/crosscheck-commit.js.map +1 -0
- package/dist/lib/diff-hash.d.ts +16 -0
- package/dist/lib/diff-hash.d.ts.map +1 -0
- package/dist/lib/diff-hash.js +71 -0
- package/dist/lib/diff-hash.js.map +1 -0
- package/dist/lib/durations.d.ts +5 -0
- package/dist/lib/durations.d.ts.map +1 -0
- package/dist/lib/durations.js +39 -0
- package/dist/lib/durations.js.map +1 -0
- package/dist/lib/event-fields.d.ts +6 -0
- package/dist/lib/event-fields.d.ts.map +1 -0
- package/dist/lib/event-fields.js +20 -0
- package/dist/lib/event-fields.js.map +1 -0
- package/dist/lib/filter.d.ts +2 -0
- package/dist/lib/filter.d.ts.map +1 -0
- package/dist/lib/filter.js +4 -0
- package/dist/lib/filter.js.map +1 -0
- package/dist/lib/fortune.d.ts +2 -0
- package/dist/lib/fortune.d.ts.map +1 -0
- package/dist/lib/fortune.js +26 -0
- package/dist/lib/fortune.js.map +1 -0
- package/dist/lib/languages.d.ts +3 -0
- package/dist/lib/languages.d.ts.map +1 -0
- package/dist/lib/languages.js +26 -0
- package/dist/lib/languages.js.map +1 -0
- package/dist/lib/log-analysis.d.ts +17 -0
- package/dist/lib/log-analysis.d.ts.map +1 -0
- package/dist/lib/log-analysis.js +72 -0
- package/dist/lib/log-analysis.js.map +1 -0
- package/dist/lib/logger.d.ts +14 -0
- package/dist/lib/logger.d.ts.map +1 -0
- package/dist/lib/logger.js +84 -0
- package/dist/lib/logger.js.map +1 -0
- package/dist/lib/port.d.ts +2 -0
- package/dist/lib/port.d.ts.map +1 -0
- package/dist/lib/port.js +21 -0
- package/dist/lib/port.js.map +1 -0
- package/dist/lib/pr-lock.d.ts +4 -0
- package/dist/lib/pr-lock.d.ts.map +1 -0
- package/dist/lib/pr-lock.js +91 -0
- package/dist/lib/pr-lock.js.map +1 -0
- package/dist/lib/pr-picker.d.ts +10 -0
- package/dist/lib/pr-picker.d.ts.map +1 -0
- package/dist/lib/pr-picker.js +80 -0
- package/dist/lib/pr-picker.js.map +1 -0
- package/dist/lib/pr-status.d.ts +206 -0
- package/dist/lib/pr-status.d.ts.map +1 -0
- package/dist/lib/pr-status.js +613 -0
- package/dist/lib/pr-status.js.map +1 -0
- package/dist/lib/repo-picker.d.ts +23 -0
- package/dist/lib/repo-picker.d.ts.map +1 -0
- package/dist/lib/repo-picker.js +411 -0
- package/dist/lib/repo-picker.js.map +1 -0
- package/dist/lib/review-models.d.ts +7 -0
- package/dist/lib/review-models.d.ts.map +1 -0
- package/dist/lib/review-models.js +32 -0
- package/dist/lib/review-models.js.map +1 -0
- package/dist/lib/runner.d.ts +65 -0
- package/dist/lib/runner.d.ts.map +1 -0
- package/dist/lib/runner.js +710 -0
- package/dist/lib/runner.js.map +1 -0
- package/dist/lib/scan-cache.d.ts +31 -0
- package/dist/lib/scan-cache.d.ts.map +1 -0
- package/dist/lib/scan-cache.js +112 -0
- package/dist/lib/scan-cache.js.map +1 -0
- package/dist/lib/scopes.d.ts +16 -0
- package/dist/lib/scopes.d.ts.map +1 -0
- package/dist/lib/scopes.js +37 -0
- package/dist/lib/scopes.js.map +1 -0
- package/dist/lib/sha-cache.d.ts +7 -0
- package/dist/lib/sha-cache.d.ts.map +1 -0
- package/dist/lib/sha-cache.js +44 -0
- package/dist/lib/sha-cache.js.map +1 -0
- package/dist/lib/smart-switch.d.ts +44 -0
- package/dist/lib/smart-switch.d.ts.map +1 -0
- package/dist/lib/smart-switch.js +145 -0
- package/dist/lib/smart-switch.js.map +1 -0
- package/dist/lib/verdict.d.ts +9 -0
- package/dist/lib/verdict.d.ts.map +1 -0
- package/dist/lib/verdict.js +52 -0
- package/dist/lib/verdict.js.map +1 -0
- package/dist/lib/workflow.d.ts +85 -0
- package/dist/lib/workflow.d.ts.map +1 -0
- package/dist/lib/workflow.js +116 -0
- package/dist/lib/workflow.js.map +1 -0
- package/dist/reviewers/address.d.ts +5 -0
- package/dist/reviewers/address.d.ts.map +1 -0
- package/dist/reviewers/address.js +87 -0
- package/dist/reviewers/address.js.map +1 -0
- package/dist/reviewers/claude.d.ts +12 -0
- package/dist/reviewers/claude.d.ts.map +1 -0
- package/dist/reviewers/claude.js +78 -0
- package/dist/reviewers/claude.js.map +1 -0
- package/dist/reviewers/codex.d.ts +9 -0
- package/dist/reviewers/codex.d.ts.map +1 -0
- package/dist/reviewers/codex.js +121 -0
- package/dist/reviewers/codex.js.map +1 -0
- package/dist/reviewers/conflict-resolve.d.ts +15 -0
- package/dist/reviewers/conflict-resolve.d.ts.map +1 -0
- package/dist/reviewers/conflict-resolve.js +219 -0
- package/dist/reviewers/conflict-resolve.js.map +1 -0
- package/dist/reviewers/fix.d.ts +7 -0
- package/dist/reviewers/fix.d.ts.map +1 -0
- package/dist/reviewers/fix.js +197 -0
- package/dist/reviewers/fix.js.map +1 -0
- package/get-started.md +1271 -0
- package/get-started.zh.md +1208 -0
- package/package.json +75 -0
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { execSync } from 'child_process';
|
|
2
|
+
import { existsSync, writeFileSync, readFileSync, mkdirSync } from 'fs';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
import { homedir } from 'os';
|
|
5
|
+
import chalk from 'chalk';
|
|
6
|
+
import { checkCodexAuth } from '../reviewers/codex.js';
|
|
7
|
+
import { checkClaudeAuth } from '../reviewers/claude.js';
|
|
8
|
+
import { getWebhookSecret, getWebhookSecretPath, detectGitHubLogin, patchAllowedAuthors, loadConfig, resolveConfigPath } from '../config/loader.js';
|
|
9
|
+
export async function runChecks() {
|
|
10
|
+
const results = [];
|
|
11
|
+
let aiCliCount = 0;
|
|
12
|
+
// Check codex CLI
|
|
13
|
+
try {
|
|
14
|
+
const version = execSync('codex --version 2>&1', { encoding: 'utf8' }).trim();
|
|
15
|
+
const auth = await checkCodexAuth();
|
|
16
|
+
if (auth.ok)
|
|
17
|
+
aiCliCount++;
|
|
18
|
+
results.push({ label: 'codex CLI', ok: auth.ok, detail: `${version} — ${auth.detail}`, fix: auth.ok ? undefined : 'Run: codex login --device-auth' });
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
results.push({ label: 'codex CLI', ok: false, detail: 'not found', fix: 'Install: npm install -g @openai/codex' });
|
|
22
|
+
}
|
|
23
|
+
// Check claude CLI
|
|
24
|
+
try {
|
|
25
|
+
const auth = await checkClaudeAuth();
|
|
26
|
+
if (auth.ok)
|
|
27
|
+
aiCliCount++;
|
|
28
|
+
results.push({ label: 'claude CLI', ok: auth.ok, detail: auth.detail, fix: auth.ok ? undefined : 'Run: claude auth login' });
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
results.push({ label: 'claude CLI', ok: false, detail: 'not found', fix: 'Install: npm install -g @anthropic-ai/claude-code' });
|
|
32
|
+
}
|
|
33
|
+
// Check gh CLI — authenticated if stored credentials OR GITHUB_TOKEN env var is set
|
|
34
|
+
const envToken = process.env.GITHUB_TOKEN ?? process.env.GH_TOKEN;
|
|
35
|
+
let ghAuthed = false;
|
|
36
|
+
try {
|
|
37
|
+
const version = execSync('gh --version 2>&1', { encoding: 'utf8' }).split('\n')[0];
|
|
38
|
+
// gh auth status exits non-zero when GITHUB_TOKEN env var is set — handle separately
|
|
39
|
+
let authOutput = '';
|
|
40
|
+
try {
|
|
41
|
+
authOutput = execSync('gh auth status 2>&1', { encoding: 'utf8' });
|
|
42
|
+
}
|
|
43
|
+
catch { /* GITHUB_TOKEN in use */ }
|
|
44
|
+
const keyringAuthed = authOutput.includes('Logged in');
|
|
45
|
+
ghAuthed = keyringAuthed || !!envToken;
|
|
46
|
+
const detail = !!envToken && !keyringAuthed
|
|
47
|
+
? `${version} — authenticated via GITHUB_TOKEN`
|
|
48
|
+
: version;
|
|
49
|
+
results.push({ label: 'gh CLI', ok: ghAuthed, detail, fix: ghAuthed ? undefined : 'Run: gh auth login' });
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
results.push({ label: 'gh CLI', ok: false, detail: 'not found', fix: 'Install: brew install gh && gh auth login' });
|
|
53
|
+
}
|
|
54
|
+
// Token is resolvable if env var is set OR if gh keyring has a token (getGithubToken() falls back to `gh auth token`)
|
|
55
|
+
const tokenResolvable = !!envToken || ghAuthed;
|
|
56
|
+
const tokenDetail = envToken ? 'set via env' : ghAuthed ? 'via gh auth login' : 'missing';
|
|
57
|
+
results.push({ label: 'GITHUB_TOKEN', ok: tokenResolvable, detail: tokenDetail, fix: tokenResolvable ? undefined : 'Set GITHUB_TOKEN or run: gh auth login' });
|
|
58
|
+
// Check admin:org_hook scope — needed for org-level webhook registration in watch/serve
|
|
59
|
+
if (ghAuthed) {
|
|
60
|
+
try {
|
|
61
|
+
const statusOutput = execSync('gh auth status 2>&1', {
|
|
62
|
+
encoding: 'utf8',
|
|
63
|
+
env: { ...process.env, GITHUB_TOKEN: undefined, GH_TOKEN: undefined },
|
|
64
|
+
});
|
|
65
|
+
const scopeMatch = statusOutput.match(/Token scopes:\s*(.+)/);
|
|
66
|
+
if (scopeMatch) {
|
|
67
|
+
const scopes = scopeMatch[1];
|
|
68
|
+
const hasOrgHook = /admin:org_hook|'admin:org'|"admin:org"/.test(scopes);
|
|
69
|
+
results.push({
|
|
70
|
+
label: 'org webhook scope',
|
|
71
|
+
ok: hasOrgHook,
|
|
72
|
+
detail: hasOrgHook ? 'admin:org_hook present' : `not granted (scopes: ${scopes.trim()})`,
|
|
73
|
+
fix: hasOrgHook ? undefined : 'gh auth refresh -s admin:org_hook (required for org-level webhooks)',
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
catch { /* gh not available or scope line absent — skip silently */ }
|
|
78
|
+
}
|
|
79
|
+
// Check WEBHOOK_SECRET — auto-generated if missing, so always ok
|
|
80
|
+
const fromEnv = process.env.CROSSCHECK_WEBHOOK_SECRET ?? process.env.GITHUB_WEBHOOK_SECRET;
|
|
81
|
+
const secretDetail = fromEnv
|
|
82
|
+
? 'set via env'
|
|
83
|
+
: `auto-managed at ${getWebhookSecretPath()}`;
|
|
84
|
+
getWebhookSecret(); // ensure it's generated/persisted
|
|
85
|
+
results.push({ label: 'WEBHOOK_SECRET', ok: true, detail: secretDetail });
|
|
86
|
+
return { results, aiCliCount };
|
|
87
|
+
}
|
|
88
|
+
function printCheck({ label, ok, detail, fix }) {
|
|
89
|
+
const icon = ok ? chalk.green('✓') : chalk.red('✗');
|
|
90
|
+
const labelStr = chalk.bold(label.padEnd(20));
|
|
91
|
+
console.log(` ${icon} ${labelStr} ${detail}`);
|
|
92
|
+
if (!ok && fix)
|
|
93
|
+
console.log(` ${chalk.dim('→')} ${chalk.yellow(fix)}`);
|
|
94
|
+
}
|
|
95
|
+
export async function runInit(configPath) {
|
|
96
|
+
console.log(chalk.bold('\ncrosscheck — environment check\n'));
|
|
97
|
+
const { results: checks, aiCliCount } = await runChecks();
|
|
98
|
+
for (const check of checks)
|
|
99
|
+
printCheck(check);
|
|
100
|
+
// AI CLI checks: only BOTH missing is a hard blocker. One CLI = single-vendor mode (still usable).
|
|
101
|
+
const aiChecks = checks.filter(c => c.label === 'codex CLI' || c.label === 'claude CLI');
|
|
102
|
+
const nonAiFailures = checks.filter(c => !aiChecks.includes(c) && !c.ok && c.fix);
|
|
103
|
+
if (aiCliCount === 0) {
|
|
104
|
+
// Neither AI CLI is authenticated — hard failure
|
|
105
|
+
const total = nonAiFailures.length + 1; // +1 for "no AI CLI" pseudo-failure
|
|
106
|
+
console.log(chalk.red(`\nAt least one AI CLI (codex or claude) must be authenticated before crosscheck can run.\n`));
|
|
107
|
+
if (nonAiFailures.length > 0) {
|
|
108
|
+
console.log(chalk.yellow(`${nonAiFailures.length} other issue(s) also need attention.\n`));
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
else if (nonAiFailures.length > 0) {
|
|
112
|
+
console.log(chalk.yellow(`\n${nonAiFailures.length} issue(s) need attention before crosscheck can run fully.\n`));
|
|
113
|
+
if (aiCliCount === 1) {
|
|
114
|
+
const missing = aiChecks.find(c => !c.ok)?.label ?? 'one AI CLI';
|
|
115
|
+
console.log(chalk.dim(`Note: ${missing} is not available — running in single-vendor mode (cross-vendor review disabled).\n`));
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
if (aiCliCount === 1) {
|
|
120
|
+
const missing = aiChecks.find(c => !c.ok)?.label ?? 'one AI CLI';
|
|
121
|
+
console.log(chalk.yellow(`\nNote: ${missing} is not available — running in single-vendor mode (cross-vendor review disabled).\n`));
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
console.log(chalk.green('\nAll checks passed.\n'));
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
// Write config if none exists, pre-filling allowed_authors with the detected GitHub login.
|
|
128
|
+
// Prefer the active config (local project file or explicit path) before falling back to
|
|
129
|
+
// the global default, so `crosscheck init` patches the same file that `watch` reads.
|
|
130
|
+
const dest = configPath ?? resolveConfigPath() ?? join(homedir(), '.crosscheck', 'config.yml');
|
|
131
|
+
mkdirSync(join(homedir(), '.crosscheck'), { recursive: true });
|
|
132
|
+
if (!existsSync(dest)) {
|
|
133
|
+
const examplePath = new URL('../../crosscheck.config.example.yml', import.meta.url).pathname;
|
|
134
|
+
if (existsSync(examplePath)) {
|
|
135
|
+
let content = readFileSync(examplePath, 'utf8');
|
|
136
|
+
const login = detectGitHubLogin();
|
|
137
|
+
if (login) {
|
|
138
|
+
// Uncomment allowed_authors placeholder
|
|
139
|
+
content = content.replace(/ # allowed_authors:\n( #[^\n]*\n)+/, ` allowed_authors:\n - ${login} # auto-detected from gh auth\n`);
|
|
140
|
+
// author_routes is intentionally left commented out. In cross-vendor mode with
|
|
141
|
+
// both vendors enabled (the default), routes for an author who uses both
|
|
142
|
+
// agents are structurally wrong — attribution detection + fallback_reviewer
|
|
143
|
+
// are the correct path. `crosscheck onboard` writes the route only when the
|
|
144
|
+
// user picks a single authorVendor.
|
|
145
|
+
}
|
|
146
|
+
writeFileSync(dest, content);
|
|
147
|
+
const hint = login ? `allowed_authors set to ${chalk.cyan(login)}` : 'edit to customize';
|
|
148
|
+
console.log(chalk.dim(`Config written to ${dest} — ${hint}.\n`));
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
// Config exists — patch allowed_authors if still empty.
|
|
153
|
+
// author_routes is intentionally not auto-patched: `crosscheck onboard` writes it
|
|
154
|
+
// only when the user picks a single authorVendor, since cross-vendor + both
|
|
155
|
+
// enabled bypasses the route (see detector.ts).
|
|
156
|
+
const existing = loadConfig(resolveConfigPath(configPath) ?? dest);
|
|
157
|
+
const login = detectGitHubLogin();
|
|
158
|
+
let patched = false;
|
|
159
|
+
if (login) {
|
|
160
|
+
if (existing.routing.allowed_authors.length === 0) {
|
|
161
|
+
patched = patchAllowedAuthors(dest, login) || patched;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
if (patched) {
|
|
165
|
+
console.log(chalk.green(` ✓ routing updated for ${chalk.cyan(login ?? 'unknown')} in ${dest}\n`));
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
console.log(chalk.dim(`Config already exists at ${dest}\n`));
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
// Smee-client is optional but improves watch mode reliability.
|
|
172
|
+
// Check if it's installed and show a one-line tip if not.
|
|
173
|
+
try {
|
|
174
|
+
execSync('smee --version', { stdio: 'ignore' });
|
|
175
|
+
}
|
|
176
|
+
catch {
|
|
177
|
+
console.log(chalk.dim('Tip: install smee-client for reliable webhook delivery (events queued while offline):'));
|
|
178
|
+
console.log(chalk.dim(' npm install -g smee-client'));
|
|
179
|
+
console.log(chalk.dim(' Then set tunnel: backend: smee in your config.\n'));
|
|
180
|
+
}
|
|
181
|
+
console.log(chalk.dim(' Run crosscheck onboard to configure scope and persona.\n'));
|
|
182
|
+
}
|
|
183
|
+
//# sourceMappingURL=init.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"init.js","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export interface QuestionAnswers {
|
|
2
|
+
reproducibility: string;
|
|
3
|
+
trigger: string;
|
|
4
|
+
impact: string;
|
|
5
|
+
}
|
|
6
|
+
export declare function parseDraft(output: string): {
|
|
7
|
+
title: string;
|
|
8
|
+
body: string;
|
|
9
|
+
labels?: string[];
|
|
10
|
+
} | null;
|
|
11
|
+
export declare function buildIssueContent(draft: {
|
|
12
|
+
title: string;
|
|
13
|
+
body: string;
|
|
14
|
+
}, answers: QuestionAnswers): {
|
|
15
|
+
title: string;
|
|
16
|
+
body: string;
|
|
17
|
+
};
|
|
18
|
+
export declare function runIssue(opts: {
|
|
19
|
+
since?: string;
|
|
20
|
+
dryRun?: boolean;
|
|
21
|
+
yes?: boolean;
|
|
22
|
+
config?: string;
|
|
23
|
+
opportunities?: boolean;
|
|
24
|
+
}): Promise<void>;
|
|
25
|
+
//# sourceMappingURL=issue.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"issue.d.ts","sourceRoot":"","sources":["../../src/commands/issue.ts"],"names":[],"mappings":"AAwDA,MAAM,WAAW,eAAe;IAC9B,eAAe,EAAE,MAAM,CAAA;IACvB,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,EAAE,MAAM,CAAA;CACf;AAED,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,EAAE,CAAA;CAAE,GAAG,IAAI,CAuBpG;AAED,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,EACtC,OAAO,EAAE,eAAe,GACvB;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAUjC;AAgKD,wBAAsB,QAAQ,CAAC,IAAI,EAAE;IACnC,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,GAAG,CAAC,EAAE,OAAO,CAAA;IACb,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,aAAa,CAAC,EAAE,OAAO,CAAA;CACxB,GAAG,OAAO,CAAC,IAAI,CAAC,CA2OhB"}
|
|
@@ -0,0 +1,445 @@
|
|
|
1
|
+
import { existsSync, mkdtempSync, writeFileSync, rmSync, readFileSync, readdirSync } from 'fs';
|
|
2
|
+
import { fileURLToPath } from 'url';
|
|
3
|
+
import { dirname, join, resolve } from 'path';
|
|
4
|
+
import { homedir, platform, tmpdir } from 'os';
|
|
5
|
+
import { createInterface } from 'readline';
|
|
6
|
+
import chalk from 'chalk';
|
|
7
|
+
import { execa } from 'execa';
|
|
8
|
+
import { loadConfig } from '../config/loader.js';
|
|
9
|
+
import { buildDiagnoseReport } from './diagnose.js';
|
|
10
|
+
import { selectOptimizeAgent } from './optimize.js';
|
|
11
|
+
import { sanitizeEntry, loadErrorEntriesForPattern, sanitizeDraftContent } from '../lib/log-analysis.js';
|
|
12
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
13
|
+
const __dirname = dirname(__filename);
|
|
14
|
+
const PACKAGE_ROOT = resolve(__dirname, '..', '..');
|
|
15
|
+
const LOG_DIR = join(homedir(), '.crosscheck', 'logs');
|
|
16
|
+
const ISSUE_REPO = 'humanbased-ai/crosscheck';
|
|
17
|
+
function loadIssueHarness(cwd) {
|
|
18
|
+
const candidates = [
|
|
19
|
+
join(cwd, 'ISSUE.md'),
|
|
20
|
+
join(cwd, '.crosscheck', 'ISSUE.md'),
|
|
21
|
+
join(PACKAGE_ROOT, 'ISSUE.md'),
|
|
22
|
+
];
|
|
23
|
+
for (const p of candidates) {
|
|
24
|
+
if (existsSync(p))
|
|
25
|
+
return readFileSync(p, 'utf8');
|
|
26
|
+
}
|
|
27
|
+
return '';
|
|
28
|
+
}
|
|
29
|
+
function loadRawLogEntries(since) {
|
|
30
|
+
if (!existsSync(LOG_DIR))
|
|
31
|
+
return [];
|
|
32
|
+
const sinceMs = new Date(since).getTime();
|
|
33
|
+
const entries = [];
|
|
34
|
+
for (const f of readdirSync(LOG_DIR).sort()) {
|
|
35
|
+
if (!f.endsWith('.ndjson'))
|
|
36
|
+
continue;
|
|
37
|
+
const fileDate = f.replace('.ndjson', '');
|
|
38
|
+
if (fileDate < since.slice(0, 10))
|
|
39
|
+
continue;
|
|
40
|
+
const lines = readFileSync(join(LOG_DIR, f), 'utf8').split('\n');
|
|
41
|
+
for (const line of lines) {
|
|
42
|
+
const trimmed = line.trim();
|
|
43
|
+
if (!trimmed)
|
|
44
|
+
continue;
|
|
45
|
+
try {
|
|
46
|
+
const entry = JSON.parse(trimmed);
|
|
47
|
+
if (new Date(entry.ts).getTime() >= sinceMs)
|
|
48
|
+
entries.push(entry);
|
|
49
|
+
}
|
|
50
|
+
catch { /* skip malformed lines */ }
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return entries;
|
|
54
|
+
}
|
|
55
|
+
const { version: PKG_VERSION } = JSON.parse(readFileSync(join(PACKAGE_ROOT, 'package.json'), 'utf8'));
|
|
56
|
+
export function parseDraft(output) {
|
|
57
|
+
const lines = output.split('\n');
|
|
58
|
+
const titleIdx = lines.findIndex(l => l.startsWith('TITLE:'));
|
|
59
|
+
if (titleIdx === -1)
|
|
60
|
+
return null;
|
|
61
|
+
const title = lines[titleIdx].replace(/^TITLE:\s*/, '').trim();
|
|
62
|
+
if (!title)
|
|
63
|
+
return null;
|
|
64
|
+
let sepIdx = -1;
|
|
65
|
+
for (let i = titleIdx + 1; i < lines.length; i++) {
|
|
66
|
+
if (lines[i].trim() === '---') {
|
|
67
|
+
sepIdx = i;
|
|
68
|
+
break;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
if (sepIdx === -1)
|
|
72
|
+
return null;
|
|
73
|
+
const body = lines.slice(sepIdx + 1).join('\n').trim();
|
|
74
|
+
if (!body)
|
|
75
|
+
return null;
|
|
76
|
+
const labelsLine = lines.slice(titleIdx + 1, sepIdx).find(l => l.startsWith('LABELS:'));
|
|
77
|
+
const labels = labelsLine
|
|
78
|
+
? labelsLine.replace(/^LABELS:\s*/, '').split(',').map(l => l.trim()).filter(Boolean)
|
|
79
|
+
: undefined;
|
|
80
|
+
return { title, body, labels };
|
|
81
|
+
}
|
|
82
|
+
export function buildIssueContent(draft, answers) {
|
|
83
|
+
const context = [
|
|
84
|
+
'',
|
|
85
|
+
'## User Context',
|
|
86
|
+
'',
|
|
87
|
+
`- **Reproducibility:** ${answers.reproducibility}`,
|
|
88
|
+
`- **Trigger command:** ${answers.trigger}`,
|
|
89
|
+
`- **Impact:** ${answers.impact}`,
|
|
90
|
+
].join('\n');
|
|
91
|
+
return { title: draft.title, body: draft.body + context };
|
|
92
|
+
}
|
|
93
|
+
function defaultSince() {
|
|
94
|
+
const d = new Date();
|
|
95
|
+
d.setDate(d.getDate() - 3);
|
|
96
|
+
return d.toISOString().split('T')[0];
|
|
97
|
+
}
|
|
98
|
+
function daysBetween(since) {
|
|
99
|
+
return Math.max(1, Math.ceil((Date.now() - new Date(since).getTime()) / 86_400_000));
|
|
100
|
+
}
|
|
101
|
+
async function ask(question) {
|
|
102
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
103
|
+
return new Promise(resolve => {
|
|
104
|
+
rl.question(question, answer => { rl.close(); resolve(answer.trim()); });
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
async function pickOne(label, choices) {
|
|
108
|
+
console.log(`\n ${label}`);
|
|
109
|
+
choices.forEach((c, i) => console.log(` [${i + 1}] ${c}`));
|
|
110
|
+
while (true) {
|
|
111
|
+
const raw = await ask(' > ');
|
|
112
|
+
const n = parseInt(raw, 10);
|
|
113
|
+
if (Number.isInteger(n) && n >= 1 && n <= choices.length)
|
|
114
|
+
return n - 1;
|
|
115
|
+
console.log(chalk.dim(` Enter a number from 1 to ${choices.length}`));
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
function buildAgentPrompt(errorPattern, errorCount, daysSince, reviewer, entries, mode) {
|
|
119
|
+
const entriesFormatted = entries.map(e => JSON.stringify(sanitizeEntry(e))).join('\n');
|
|
120
|
+
return [
|
|
121
|
+
'You are drafting a GitHub issue for the crosscheck project (a cross-vendor AI code review CLI tool).',
|
|
122
|
+
'',
|
|
123
|
+
`Error pattern: ${errorPattern}`,
|
|
124
|
+
`Frequency: ${errorCount} occurrence${errorCount !== 1 ? 's' : ''} in the last ${daysSince} day${daysSince !== 1 ? 's' : ''}`,
|
|
125
|
+
reviewer ? `Reviewer at time of failure: ${reviewer}` : '',
|
|
126
|
+
'',
|
|
127
|
+
'Sanitized log entries (up to 5 most recent):',
|
|
128
|
+
entriesFormatted || '(none available)',
|
|
129
|
+
'',
|
|
130
|
+
`Environment: crosscheck ${PKG_VERSION} · ${platform()} · mode: ${mode}`,
|
|
131
|
+
'',
|
|
132
|
+
'Write a GitHub issue for this failure. Output exactly:',
|
|
133
|
+
'TITLE: <concise title under 80 characters>',
|
|
134
|
+
'---',
|
|
135
|
+
'<issue body in GitHub-flavored markdown>',
|
|
136
|
+
'',
|
|
137
|
+
'The body must contain: ## Description, ## Steps to Reproduce, ## Log Excerpt, ## Environment',
|
|
138
|
+
'In Log Excerpt, show the sanitized log entries as a json code block.',
|
|
139
|
+
'Do not invent details not present in the provided context.',
|
|
140
|
+
].filter(Boolean).join('\n');
|
|
141
|
+
}
|
|
142
|
+
function buildOpportunityPrompt(harness, entries, daysSince, mode) {
|
|
143
|
+
const sample = entries
|
|
144
|
+
.filter(e => ['session_start', 'session_end', 'tunnel_opened', 'tunnel_closed',
|
|
145
|
+
'tunnel_error', 'webhook_registered', 'webhook_error', 'pr_received',
|
|
146
|
+
'review_complete', 'verdict_parse_failed', 'error'].includes(e.event))
|
|
147
|
+
.map(e => JSON.stringify(sanitizeEntry(e)))
|
|
148
|
+
.join('\n');
|
|
149
|
+
return [
|
|
150
|
+
harness,
|
|
151
|
+
'',
|
|
152
|
+
'---',
|
|
153
|
+
'',
|
|
154
|
+
`## Log data to analyze (last ${daysSince} day${daysSince !== 1 ? 's' : ''})`,
|
|
155
|
+
'',
|
|
156
|
+
`Environment: crosscheck ${PKG_VERSION} · ${platform()} · mode: ${mode}`,
|
|
157
|
+
`Total events loaded: ${entries.length}`,
|
|
158
|
+
'',
|
|
159
|
+
'Filtered events (session lifecycle, tunnel, webhook, review):',
|
|
160
|
+
'```',
|
|
161
|
+
sample || '(no matching events in range)',
|
|
162
|
+
'```',
|
|
163
|
+
'',
|
|
164
|
+
'Apply the analysis framework above. Identify the highest-priority issue or',
|
|
165
|
+
'improvement opportunity present in this data. Output exactly:',
|
|
166
|
+
'TITLE: <concise title under 80 characters>',
|
|
167
|
+
'LABELS: <comma-separated labels>',
|
|
168
|
+
'---',
|
|
169
|
+
'<issue body per the Output format section above>',
|
|
170
|
+
'',
|
|
171
|
+
'Report one issue only — the highest priority finding.',
|
|
172
|
+
].join('\n');
|
|
173
|
+
}
|
|
174
|
+
// eslint-disable-next-line no-control-regex
|
|
175
|
+
const ANSI_RE = /[\x1B\x9B][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><~]/g;
|
|
176
|
+
async function runWithClaude(prompt) {
|
|
177
|
+
let result;
|
|
178
|
+
try {
|
|
179
|
+
result = await execa('claude', ['--print', prompt], {
|
|
180
|
+
timeout: 120_000,
|
|
181
|
+
stdin: 'ignore',
|
|
182
|
+
env: { ...process.env },
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
catch (err) {
|
|
186
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
187
|
+
if (/not logged in|please run \/login|run \/login|403|request not allowed|failed to authenticate/i.test(msg)) {
|
|
188
|
+
throw new Error('claude is not authenticated — run: claude /login');
|
|
189
|
+
}
|
|
190
|
+
throw err;
|
|
191
|
+
}
|
|
192
|
+
return (result.stdout ?? result.stderr ?? '').replace(ANSI_RE, '').trim();
|
|
193
|
+
}
|
|
194
|
+
async function runWithCodex(prompt) {
|
|
195
|
+
const tmpDir = mkdtempSync(join(tmpdir(), 'crosscheck-issue-'));
|
|
196
|
+
try {
|
|
197
|
+
writeFileSync(join(tmpDir, 'ISSUE_PROMPT.md'), prompt);
|
|
198
|
+
const result = await execa('codex', [
|
|
199
|
+
'exec',
|
|
200
|
+
'--skip-git-repo-check',
|
|
201
|
+
'Read ISSUE_PROMPT.md and produce a GitHub issue draft. ' +
|
|
202
|
+
'Output exactly: TITLE: line, then ---, then the markdown body.',
|
|
203
|
+
], {
|
|
204
|
+
cwd: tmpDir,
|
|
205
|
+
timeout: 180_000,
|
|
206
|
+
env: { ...process.env },
|
|
207
|
+
});
|
|
208
|
+
return (result.stdout ?? '').trim();
|
|
209
|
+
}
|
|
210
|
+
finally {
|
|
211
|
+
rmSync(tmpDir, { recursive: true, force: true });
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
function printDraft(title, body) {
|
|
215
|
+
const width = Math.min(process.stdout.columns ?? 80, 80);
|
|
216
|
+
const bar = '─'.repeat(width);
|
|
217
|
+
console.log('\n' + chalk.dim(bar));
|
|
218
|
+
console.log(chalk.bold(` TITLE: ${title}`));
|
|
219
|
+
console.log(chalk.dim(bar));
|
|
220
|
+
for (const line of body.split('\n')) {
|
|
221
|
+
console.log(` ${line}`);
|
|
222
|
+
}
|
|
223
|
+
console.log(chalk.dim(bar) + '\n');
|
|
224
|
+
}
|
|
225
|
+
function errorLabel(e) {
|
|
226
|
+
if (e.pattern === 'command_not_found')
|
|
227
|
+
return `command not found: ${e.command}`;
|
|
228
|
+
if (e.pattern === 'base_branch_missing')
|
|
229
|
+
return `base branch missing: ${e.branch}`;
|
|
230
|
+
return e.pattern;
|
|
231
|
+
}
|
|
232
|
+
export async function runIssue(opts) {
|
|
233
|
+
const since = opts.since ?? defaultSince();
|
|
234
|
+
if (!existsSync(LOG_DIR)) {
|
|
235
|
+
console.error(chalk.yellow('No logs found. Run `crosscheck watch` or `crosscheck serve` first.'));
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
const config = loadConfig(opts.config);
|
|
239
|
+
const days = daysBetween(since);
|
|
240
|
+
// --- Opportunity analysis mode (--opportunities) ---
|
|
241
|
+
if (opts.opportunities) {
|
|
242
|
+
const harness = loadIssueHarness(process.cwd());
|
|
243
|
+
if (!harness) {
|
|
244
|
+
console.error(chalk.red('✗ ISSUE.md harness not found — expected at project root or .crosscheck/ISSUE.md'));
|
|
245
|
+
process.exit(1);
|
|
246
|
+
}
|
|
247
|
+
console.log(chalk.dim(' loading logs for opportunity analysis...'));
|
|
248
|
+
const allEntries = loadRawLogEntries(since);
|
|
249
|
+
if (allEntries.length === 0) {
|
|
250
|
+
console.log(' No log entries found in range — nothing to analyze');
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
console.log(chalk.dim(` ${allEntries.length} events across ${days} day${days !== 1 ? 's' : ''}`));
|
|
254
|
+
const prompt = buildOpportunityPrompt(harness, allEntries, days, config.mode);
|
|
255
|
+
const candidates = [];
|
|
256
|
+
try {
|
|
257
|
+
const sel = selectOptimizeAgent(config, buildDiagnoseReport(since, LOG_DIR));
|
|
258
|
+
candidates.push(sel.agent);
|
|
259
|
+
}
|
|
260
|
+
catch {
|
|
261
|
+
candidates.push('claude');
|
|
262
|
+
}
|
|
263
|
+
const fallback = ['claude', 'codex'].find(v => !candidates.includes(v) && config.vendors[v].enabled);
|
|
264
|
+
if (fallback)
|
|
265
|
+
candidates.push(fallback);
|
|
266
|
+
let agentOutput;
|
|
267
|
+
for (let i = 0; i < candidates.length; i++) {
|
|
268
|
+
const agent = candidates[i];
|
|
269
|
+
const reason = i === 0 ? 'selected by optimize logic' : `fallback — ${candidates[i - 1]} failed`;
|
|
270
|
+
console.log(chalk.dim(` analyzing with ${agent} (${reason})...`));
|
|
271
|
+
try {
|
|
272
|
+
agentOutput = agent === 'claude'
|
|
273
|
+
? await runWithClaude(prompt)
|
|
274
|
+
: await runWithCodex(prompt);
|
|
275
|
+
break;
|
|
276
|
+
}
|
|
277
|
+
catch (err) {
|
|
278
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
279
|
+
const hasNext = i < candidates.length - 1;
|
|
280
|
+
console.error(chalk.yellow(` ✗ ${agent} failed: ${msg}${hasNext ? ' — trying next agent...' : ''}`));
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
if (agentOutput === undefined) {
|
|
284
|
+
console.error(chalk.red('✗ all configured agents failed'));
|
|
285
|
+
process.exit(1);
|
|
286
|
+
}
|
|
287
|
+
const draftParsed = parseDraft(agentOutput);
|
|
288
|
+
if (!draftParsed) {
|
|
289
|
+
console.error(chalk.red('✗ Agent returned unexpected output format'));
|
|
290
|
+
console.error(chalk.dim(' Expected: TITLE: line, then ---, then body'));
|
|
291
|
+
process.exit(1);
|
|
292
|
+
}
|
|
293
|
+
const cleanDraft = sanitizeDraftContent(draftParsed.title, draftParsed.body);
|
|
294
|
+
printDraft(cleanDraft.title, cleanDraft.body);
|
|
295
|
+
if (opts.dryRun) {
|
|
296
|
+
console.log(chalk.dim(' (dry run — not submitting)'));
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
if (!opts.yes) {
|
|
300
|
+
const confirmed = await ask(` Submit to ${ISSUE_REPO}? [y/N]: `);
|
|
301
|
+
if (!/^y(es)?$/i.test(confirmed)) {
|
|
302
|
+
console.log(' Cancelled.');
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
await submitIssue(cleanDraft.title, cleanDraft.body, draftParsed.labels ?? ['improvement']);
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
// --- Default mode: error-pattern bug report ---
|
|
310
|
+
// 1. Scan logs for error patterns
|
|
311
|
+
console.log(chalk.dim(' scanning logs...'));
|
|
312
|
+
const report = buildDiagnoseReport(since, LOG_DIR);
|
|
313
|
+
if (report.errors.length === 0) {
|
|
314
|
+
console.log(' No errors found in recent logs — nothing to report');
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
// 2. Select which error to report
|
|
318
|
+
let errorIdx = 0;
|
|
319
|
+
if (report.errors.length > 1 && !opts.yes) {
|
|
320
|
+
console.log(chalk.bold(`\n Found ${report.errors.length} error patterns in the last ${days} day${days !== 1 ? 's' : ''}:\n`));
|
|
321
|
+
report.errors.forEach((e, i) => {
|
|
322
|
+
const label = errorLabel(e).padEnd(44);
|
|
323
|
+
const rev = e.reviewer ? chalk.dim(` ${e.reviewer}`) : '';
|
|
324
|
+
console.log(` [${i + 1}] ${label} ×${e.count}${rev}`);
|
|
325
|
+
});
|
|
326
|
+
errorIdx = await pickOne('Which issue do you want to report?', report.errors.map((_, i) => String(i + 1)));
|
|
327
|
+
}
|
|
328
|
+
const selected = report.errors[errorIdx];
|
|
329
|
+
if (!selected) {
|
|
330
|
+
console.error(chalk.red('✗ Invalid selection'));
|
|
331
|
+
process.exit(1);
|
|
332
|
+
}
|
|
333
|
+
// 3. Load representative log entries for this pattern
|
|
334
|
+
const rawEntries = loadErrorEntriesForPattern(selected.pattern, selected.command, since);
|
|
335
|
+
// 4. Select agents — primary first, then other enabled vendor as fallback
|
|
336
|
+
const candidates = [];
|
|
337
|
+
let primaryReason = 'default';
|
|
338
|
+
try {
|
|
339
|
+
const sel = selectOptimizeAgent(config, report);
|
|
340
|
+
candidates.push(sel.agent);
|
|
341
|
+
primaryReason = sel.reason;
|
|
342
|
+
}
|
|
343
|
+
catch {
|
|
344
|
+
candidates.push('claude');
|
|
345
|
+
}
|
|
346
|
+
const fallbackVendor = ['claude', 'codex'].find(v => !candidates.includes(v) && config.vendors[v].enabled);
|
|
347
|
+
if (fallbackVendor)
|
|
348
|
+
candidates.push(fallbackVendor);
|
|
349
|
+
// 5. Draft issue via AI agent — try each candidate in order
|
|
350
|
+
const prompt = buildAgentPrompt(errorLabel(selected), selected.count, days, selected.reviewer, rawEntries, config.mode);
|
|
351
|
+
let agentOutput;
|
|
352
|
+
for (let i = 0; i < candidates.length; i++) {
|
|
353
|
+
const agent = candidates[i];
|
|
354
|
+
const reason = i === 0 ? primaryReason : `fallback — ${candidates[i - 1]} failed`;
|
|
355
|
+
console.log(chalk.dim(` drafting issue with ${agent} (${reason})...`));
|
|
356
|
+
try {
|
|
357
|
+
agentOutput = agent === 'claude'
|
|
358
|
+
? await runWithClaude(prompt)
|
|
359
|
+
: await runWithCodex(prompt);
|
|
360
|
+
break;
|
|
361
|
+
}
|
|
362
|
+
catch (err) {
|
|
363
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
364
|
+
const hasNext = i < candidates.length - 1;
|
|
365
|
+
console.error(chalk.yellow(` ✗ ${agent} failed: ${msg}${hasNext ? ' — trying next agent...' : ''}`));
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
if (agentOutput === undefined) {
|
|
369
|
+
console.error(chalk.red('✗ all configured agents failed to draft the issue'));
|
|
370
|
+
process.exit(1);
|
|
371
|
+
}
|
|
372
|
+
const draftParsed = parseDraft(agentOutput);
|
|
373
|
+
if (!draftParsed) {
|
|
374
|
+
console.error(chalk.red('✗ Agent returned unexpected output format'));
|
|
375
|
+
console.error(chalk.dim(' Expected: TITLE: <title>\\n---\\n<body>'));
|
|
376
|
+
process.exit(1);
|
|
377
|
+
}
|
|
378
|
+
// 6. Ask follow-up questions (skipped when --yes)
|
|
379
|
+
let answers = {
|
|
380
|
+
reproducibility: 'Unknown',
|
|
381
|
+
trigger: 'Unknown',
|
|
382
|
+
impact: 'Degraded',
|
|
383
|
+
};
|
|
384
|
+
if (!opts.yes) {
|
|
385
|
+
const repIdx = await pickOne('Can you reproduce this consistently?', ['Every time', 'Sometimes', 'Happened once']);
|
|
386
|
+
answers.reproducibility = ['Every time', 'Sometimes', 'Happened once'][repIdx];
|
|
387
|
+
const trigIdx = await pickOne('Which command triggered this?', ['watch', 'serve', 'review', 'Unknown']);
|
|
388
|
+
answers.trigger = ['watch', 'serve', 'review', 'Unknown'][trigIdx];
|
|
389
|
+
const impactIdx = await pickOne('Is this blocking you from using crosscheck?', ['Blocked', 'Degraded', 'Cosmetic']);
|
|
390
|
+
answers.impact = ['Blocked', 'Degraded', 'Cosmetic'][impactIdx];
|
|
391
|
+
}
|
|
392
|
+
// 7. Build final content and show draft
|
|
393
|
+
// Sanitize AI output before use — the agent may echo back content it received,
|
|
394
|
+
// even if inputs were sanitized; this is the last gate before posting.
|
|
395
|
+
const cleanDraft = sanitizeDraftContent(draftParsed.title, draftParsed.body);
|
|
396
|
+
const { title, body } = buildIssueContent(cleanDraft, answers);
|
|
397
|
+
printDraft(title, body);
|
|
398
|
+
// 8. Submit
|
|
399
|
+
if (opts.dryRun) {
|
|
400
|
+
console.log(chalk.dim(' (dry run — not submitting)'));
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
if (!opts.yes) {
|
|
404
|
+
const confirmed = (await ask(` Submit to ${ISSUE_REPO}? [y/N]: `));
|
|
405
|
+
if (!/^y(es)?$/i.test(confirmed)) {
|
|
406
|
+
console.log(' Cancelled.');
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
const labels = ['bug'];
|
|
411
|
+
if (answers.impact === 'Blocked')
|
|
412
|
+
labels.push('priority:high');
|
|
413
|
+
await submitIssue(title, body, labels);
|
|
414
|
+
}
|
|
415
|
+
async function submitIssue(title, body, labels) {
|
|
416
|
+
const ghArgs = [
|
|
417
|
+
'issue', 'create',
|
|
418
|
+
'--repo', ISSUE_REPO,
|
|
419
|
+
'--title', title,
|
|
420
|
+
'--body', body,
|
|
421
|
+
...labels.flatMap(l => ['--label', l]),
|
|
422
|
+
];
|
|
423
|
+
let issueUrl;
|
|
424
|
+
try {
|
|
425
|
+
const result = await execa('gh', ghArgs, { timeout: 30_000 });
|
|
426
|
+
issueUrl = (result.stdout ?? '').trim();
|
|
427
|
+
}
|
|
428
|
+
catch (err) {
|
|
429
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
430
|
+
if (/not logged in|not authenticated/i.test(msg)) {
|
|
431
|
+
console.error(chalk.yellow(' gh is not authenticated — run: gh auth login'));
|
|
432
|
+
}
|
|
433
|
+
else {
|
|
434
|
+
console.error(chalk.yellow(` gh issue create failed: ${msg}`));
|
|
435
|
+
}
|
|
436
|
+
const escapedTitle = title.replace(/'/g, "'\\''");
|
|
437
|
+
const escapedBody = body.replace(/'/g, "'\\''");
|
|
438
|
+
const labelsStr = labels.map(l => `--label '${l}'`).join(' ');
|
|
439
|
+
console.log('\n Run this manually:');
|
|
440
|
+
console.log(chalk.cyan(` gh issue create --repo ${ISSUE_REPO} --title '${escapedTitle}' --body '${escapedBody}' ${labelsStr}`));
|
|
441
|
+
process.exit(2);
|
|
442
|
+
}
|
|
443
|
+
console.log(chalk.green(`\n ✓ issue created → ${issueUrl}`));
|
|
444
|
+
}
|
|
445
|
+
//# sourceMappingURL=issue.js.map
|