@humanbased/crosscheck 1.2.0 → 1.3.0-beta.82
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/LICENSE +1 -1
- package/README.md +182 -375
- package/README.zh.md +1 -1
- package/assets/icon-256.png +0 -0
- package/assets/linear-comment.svg +18 -0
- package/assets/linear-onboard.svg +30 -0
- package/assets/linear-status.svg +23 -0
- package/assets/linear-test.svg +34 -0
- package/assets/skills/code-review/.crosscheck-skill.json +9 -0
- package/assets/skills/code-review/LICENSE +21 -0
- package/assets/skills/code-review/SKILL.md +89 -0
- package/assets/skills/code-review/agents/openai.yaml +3 -0
- package/assets/skills/code-review-skill/.crosscheck-skill.json +9 -0
- package/assets/skills/code-review-skill/LICENSE +21 -0
- package/assets/skills/code-review-skill/SKILL.md +231 -0
- package/assets/skills/code-review-skill/assets/pr-review-template.md +137 -0
- package/assets/skills/code-review-skill/assets/review-checklist.md +123 -0
- package/assets/skills/code-review-skill/reference/angular.md +768 -0
- package/assets/skills/code-review-skill/reference/architecture-review-guide.md +472 -0
- package/assets/skills/code-review-skill/reference/c.md +890 -0
- package/assets/skills/code-review-skill/reference/code-quality-universal.md +488 -0
- package/assets/skills/code-review-skill/reference/code-review-best-practices.md +136 -0
- package/assets/skills/code-review-skill/reference/common-bugs-checklist.md +286 -0
- package/assets/skills/code-review-skill/reference/cpp.md +893 -0
- package/assets/skills/code-review-skill/reference/cross-cutting/async-concurrency-patterns.md +515 -0
- package/assets/skills/code-review-skill/reference/cross-cutting/error-handling-principles.md +492 -0
- package/assets/skills/code-review-skill/reference/cross-cutting/n-plus-one-queries.md +309 -0
- package/assets/skills/code-review-skill/reference/cross-cutting/sql-injection-prevention.md +308 -0
- package/assets/skills/code-review-skill/reference/cross-cutting/xss-prevention.md +264 -0
- package/assets/skills/code-review-skill/reference/csharp.md +525 -0
- package/assets/skills/code-review-skill/reference/css-less-sass.md +661 -0
- package/assets/skills/code-review-skill/reference/django.md +985 -0
- package/assets/skills/code-review-skill/reference/fastapi.md +580 -0
- package/assets/skills/code-review-skill/reference/go.md +993 -0
- package/assets/skills/code-review-skill/reference/java.md +409 -0
- package/assets/skills/code-review-skill/reference/java8.md +586 -0
- package/assets/skills/code-review-skill/reference/kotlin.md +1018 -0
- package/assets/skills/code-review-skill/reference/nestjs.md +593 -0
- package/assets/skills/code-review-skill/reference/performance-review-guide.md +816 -0
- package/assets/skills/code-review-skill/reference/php.md +684 -0
- package/assets/skills/code-review-skill/reference/python.md +1073 -0
- package/assets/skills/code-review-skill/reference/qt.md +757 -0
- package/assets/skills/code-review-skill/reference/react.md +871 -0
- package/assets/skills/code-review-skill/reference/ruby.md +964 -0
- package/assets/skills/code-review-skill/reference/rust.md +846 -0
- package/assets/skills/code-review-skill/reference/security-review-guide.md +494 -0
- package/assets/skills/code-review-skill/reference/svelte.md +1064 -0
- package/assets/skills/code-review-skill/reference/swift.md +936 -0
- package/assets/skills/code-review-skill/reference/typescript.md +1016 -0
- package/assets/skills/code-review-skill/reference/vue.md +924 -0
- package/assets/skills/code-review-skill/reference/zig.md +440 -0
- package/assets/skills/code-review-skill/scripts/pr-analyzer.py +435 -0
- package/assets/skills/code-review-skill/scripts/test_pr_analyzer.py +380 -0
- package/assets/skills/codebase-design/.crosscheck-skill.json +9 -0
- package/assets/skills/codebase-design/DEEPENING.md +37 -0
- package/assets/skills/codebase-design/DESIGN-IT-TWICE.md +44 -0
- package/assets/skills/codebase-design/LICENSE +21 -0
- package/assets/skills/codebase-design/SKILL.md +114 -0
- package/assets/skills/codebase-design/agents/openai.yaml +3 -0
- package/assets/skills/diagnosing-bugs/.crosscheck-skill.json +9 -0
- package/assets/skills/diagnosing-bugs/LICENSE +21 -0
- package/assets/skills/diagnosing-bugs/SKILL.md +134 -0
- package/assets/skills/diagnosing-bugs/agents/openai.yaml +3 -0
- package/assets/skills/diagnosing-bugs/scripts/hitl-loop.template.sh +41 -0
- package/crosscheck.config.example.yml +101 -9
- package/dist/__tests__/board.test.js +11 -0
- package/dist/__tests__/board.test.js.map +1 -1
- package/dist/__tests__/can-write-verdict.test.d.ts +2 -0
- package/dist/__tests__/can-write-verdict.test.d.ts.map +1 -0
- package/dist/__tests__/can-write-verdict.test.js +31 -0
- package/dist/__tests__/can-write-verdict.test.js.map +1 -0
- package/dist/__tests__/codex.test.js +14 -27
- package/dist/__tests__/codex.test.js.map +1 -1
- package/dist/__tests__/comment-bodies.test.js +49 -1
- package/dist/__tests__/comment-bodies.test.js.map +1 -1
- package/dist/__tests__/conflict-resolve.test.js +44 -1
- package/dist/__tests__/conflict-resolve.test.js.map +1 -1
- package/dist/__tests__/fix.test.js +33 -0
- package/dist/__tests__/fix.test.js.map +1 -1
- package/dist/__tests__/linear-branding.test.d.ts +2 -0
- package/dist/__tests__/linear-branding.test.d.ts.map +1 -0
- package/dist/__tests__/linear-branding.test.js +156 -0
- package/dist/__tests__/linear-branding.test.js.map +1 -0
- package/dist/__tests__/linear-client.test.d.ts +2 -0
- package/dist/__tests__/linear-client.test.d.ts.map +1 -0
- package/dist/__tests__/linear-client.test.js +120 -0
- package/dist/__tests__/linear-client.test.js.map +1 -0
- package/dist/__tests__/linear-comment.test.d.ts +2 -0
- package/dist/__tests__/linear-comment.test.d.ts.map +1 -0
- package/dist/__tests__/linear-comment.test.js +151 -0
- package/dist/__tests__/linear-comment.test.js.map +1 -0
- package/dist/__tests__/linear-identity.test.d.ts +2 -0
- package/dist/__tests__/linear-identity.test.d.ts.map +1 -0
- package/dist/__tests__/linear-identity.test.js +253 -0
- package/dist/__tests__/linear-identity.test.js.map +1 -0
- package/dist/__tests__/linear-notify.test.d.ts +2 -0
- package/dist/__tests__/linear-notify.test.d.ts.map +1 -0
- package/dist/__tests__/linear-notify.test.js +144 -0
- package/dist/__tests__/linear-notify.test.js.map +1 -0
- package/dist/__tests__/linear-ref.test.d.ts +2 -0
- package/dist/__tests__/linear-ref.test.d.ts.map +1 -0
- package/dist/__tests__/linear-ref.test.js +261 -0
- package/dist/__tests__/linear-ref.test.js.map +1 -0
- package/dist/__tests__/linear-test-ref.test.d.ts +2 -0
- package/dist/__tests__/linear-test-ref.test.d.ts.map +1 -0
- package/dist/__tests__/linear-test-ref.test.js +81 -0
- package/dist/__tests__/linear-test-ref.test.js.map +1 -0
- package/dist/__tests__/linear-verify.test.d.ts +2 -0
- package/dist/__tests__/linear-verify.test.d.ts.map +1 -0
- package/dist/__tests__/linear-verify.test.js +132 -0
- package/dist/__tests__/linear-verify.test.js.map +1 -0
- package/dist/__tests__/linear-worker.test.d.ts +2 -0
- package/dist/__tests__/linear-worker.test.d.ts.map +1 -0
- package/dist/__tests__/linear-worker.test.js +83 -0
- package/dist/__tests__/linear-worker.test.js.map +1 -0
- package/dist/__tests__/linear-write-possible.test.d.ts +2 -0
- package/dist/__tests__/linear-write-possible.test.d.ts.map +1 -0
- package/dist/__tests__/linear-write-possible.test.js +30 -0
- package/dist/__tests__/linear-write-possible.test.js.map +1 -0
- package/dist/__tests__/onboard-preservation.test.js +59 -3
- package/dist/__tests__/onboard-preservation.test.js.map +1 -1
- package/dist/__tests__/optimize.test.js +2 -0
- package/dist/__tests__/optimize.test.js.map +1 -1
- package/dist/__tests__/pr-status.test.js +163 -2
- package/dist/__tests__/pr-status.test.js.map +1 -1
- package/dist/__tests__/pr-workflow-state.test.js +102 -1
- package/dist/__tests__/pr-workflow-state.test.js.map +1 -1
- package/dist/__tests__/repo-picker.test.js +7 -1
- package/dist/__tests__/repo-picker.test.js.map +1 -1
- package/dist/__tests__/repository-guidance.test.d.ts +2 -0
- package/dist/__tests__/repository-guidance.test.d.ts.map +1 -0
- package/dist/__tests__/repository-guidance.test.js +107 -0
- package/dist/__tests__/repository-guidance.test.js.map +1 -0
- package/dist/__tests__/review-comment-body.test.js +35 -0
- package/dist/__tests__/review-comment-body.test.js.map +1 -1
- package/dist/__tests__/review-models.test.js +19 -3
- package/dist/__tests__/review-models.test.js.map +1 -1
- package/dist/__tests__/review-strategy.test.d.ts +2 -0
- package/dist/__tests__/review-strategy.test.d.ts.map +1 -0
- package/dist/__tests__/review-strategy.test.js +397 -0
- package/dist/__tests__/review-strategy.test.js.map +1 -0
- package/dist/__tests__/runner.test.js +29 -1
- package/dist/__tests__/runner.test.js.map +1 -1
- package/dist/__tests__/skill-attribution.test.d.ts +2 -0
- package/dist/__tests__/skill-attribution.test.d.ts.map +1 -0
- package/dist/__tests__/skill-attribution.test.js +53 -0
- package/dist/__tests__/skill-attribution.test.js.map +1 -0
- package/dist/__tests__/skill-broker.test.d.ts +2 -0
- package/dist/__tests__/skill-broker.test.d.ts.map +1 -0
- package/dist/__tests__/skill-broker.test.js +144 -0
- package/dist/__tests__/skill-broker.test.js.map +1 -0
- package/dist/__tests__/skill-catalog.test.d.ts +2 -0
- package/dist/__tests__/skill-catalog.test.d.ts.map +1 -0
- package/dist/__tests__/skill-catalog.test.js +40 -0
- package/dist/__tests__/skill-catalog.test.js.map +1 -0
- package/dist/__tests__/skill-installer.test.d.ts +2 -0
- package/dist/__tests__/skill-installer.test.d.ts.map +1 -0
- package/dist/__tests__/skill-installer.test.js +96 -0
- package/dist/__tests__/skill-installer.test.js.map +1 -0
- package/dist/__tests__/skills-config.test.d.ts +2 -0
- package/dist/__tests__/skills-config.test.d.ts.map +1 -0
- package/dist/__tests__/skills-config.test.js +12 -0
- package/dist/__tests__/skills-config.test.js.map +1 -0
- package/dist/cli.js +29 -0
- package/dist/cli.js.map +1 -1
- package/dist/commands/detect-step.d.ts.map +1 -1
- package/dist/commands/detect-step.js +4 -0
- package/dist/commands/detect-step.js.map +1 -1
- package/dist/commands/kickass.d.ts.map +1 -1
- package/dist/commands/kickass.js +3 -2
- package/dist/commands/kickass.js.map +1 -1
- package/dist/commands/linear-test.d.ts +18 -0
- package/dist/commands/linear-test.d.ts.map +1 -0
- package/dist/commands/linear-test.js +130 -0
- package/dist/commands/linear-test.js.map +1 -0
- package/dist/commands/onboard.d.ts +36 -3
- package/dist/commands/onboard.d.ts.map +1 -1
- package/dist/commands/onboard.js +233 -42
- package/dist/commands/onboard.js.map +1 -1
- package/dist/commands/review.d.ts.map +1 -1
- package/dist/commands/review.js +65 -6
- package/dist/commands/review.js.map +1 -1
- package/dist/commands/run.d.ts.map +1 -1
- package/dist/commands/run.js +51 -7
- package/dist/commands/run.js.map +1 -1
- package/dist/commands/skill.d.ts +2 -0
- package/dist/commands/skill.d.ts.map +1 -0
- package/dist/commands/skill.js +16 -0
- package/dist/commands/skill.js.map +1 -0
- package/dist/commands/status.d.ts.map +1 -1
- package/dist/commands/status.js +53 -1
- package/dist/commands/status.js.map +1 -1
- package/dist/commands/watch.d.ts.map +1 -1
- package/dist/commands/watch.js +169 -64
- package/dist/commands/watch.js.map +1 -1
- package/dist/config/loader.d.ts +3 -1
- package/dist/config/loader.d.ts.map +1 -1
- package/dist/config/loader.js +13 -0
- package/dist/config/loader.js.map +1 -1
- package/dist/config/review-model-tiers.json +3 -3
- package/dist/config/review-strategy.json +204 -0
- package/dist/config/schema.d.ts +261 -15
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/config/schema.js +90 -8
- package/dist/config/schema.js.map +1 -1
- package/dist/github/client.d.ts +21 -1
- package/dist/github/client.d.ts.map +1 -1
- package/dist/github/client.js +46 -7
- package/dist/github/client.js.map +1 -1
- package/dist/github/webhook.d.ts +4 -0
- package/dist/github/webhook.d.ts.map +1 -1
- package/dist/github/webhook.js.map +1 -1
- package/dist/issues/ticket-ref.d.ts.map +1 -1
- package/dist/issues/ticket-ref.js +6 -5
- package/dist/issues/ticket-ref.js.map +1 -1
- package/dist/lib/annotation.d.ts +7 -0
- package/dist/lib/annotation.d.ts.map +1 -1
- package/dist/lib/annotation.js +11 -1
- package/dist/lib/annotation.js.map +1 -1
- package/dist/lib/board.d.ts +3 -0
- package/dist/lib/board.d.ts.map +1 -1
- package/dist/lib/board.js +4 -2
- package/dist/lib/board.js.map +1 -1
- package/dist/lib/clone.d.ts +1 -0
- package/dist/lib/clone.d.ts.map +1 -1
- package/dist/lib/clone.js +32 -10
- package/dist/lib/clone.js.map +1 -1
- package/dist/lib/comment-bodies.d.ts +37 -0
- package/dist/lib/comment-bodies.d.ts.map +1 -1
- package/dist/lib/comment-bodies.js +47 -9
- package/dist/lib/comment-bodies.js.map +1 -1
- package/dist/lib/pr-status.d.ts.map +1 -1
- package/dist/lib/pr-status.js +36 -2
- package/dist/lib/pr-status.js.map +1 -1
- package/dist/lib/pr-workflow-state.d.ts +5 -0
- package/dist/lib/pr-workflow-state.d.ts.map +1 -1
- package/dist/lib/pr-workflow-state.js +36 -1
- package/dist/lib/pr-workflow-state.js.map +1 -1
- package/dist/lib/repo-picker.d.ts +3 -0
- package/dist/lib/repo-picker.d.ts.map +1 -1
- package/dist/lib/repo-picker.js +45 -9
- package/dist/lib/repo-picker.js.map +1 -1
- package/dist/lib/repository-guidance.d.ts +2 -0
- package/dist/lib/repository-guidance.d.ts.map +1 -0
- package/dist/lib/repository-guidance.js +55 -0
- package/dist/lib/repository-guidance.js.map +1 -0
- package/dist/lib/review-models.d.ts +15 -2
- package/dist/lib/review-models.d.ts.map +1 -1
- package/dist/lib/review-models.js +26 -6
- package/dist/lib/review-models.js.map +1 -1
- package/dist/lib/review-strategy.d.ts +92 -0
- package/dist/lib/review-strategy.d.ts.map +1 -0
- package/dist/lib/review-strategy.js +282 -0
- package/dist/lib/review-strategy.js.map +1 -0
- package/dist/lib/runner.d.ts +92 -0
- package/dist/lib/runner.d.ts.map +1 -1
- package/dist/lib/runner.js +470 -54
- package/dist/lib/runner.js.map +1 -1
- package/dist/lib/workflow.d.ts +9 -0
- package/dist/lib/workflow.d.ts.map +1 -1
- package/dist/lib/workflow.js +20 -0
- package/dist/lib/workflow.js.map +1 -1
- package/dist/linear/client.d.ts +18 -0
- package/dist/linear/client.d.ts.map +1 -0
- package/dist/linear/client.js +67 -0
- package/dist/linear/client.js.map +1 -0
- package/dist/linear/comment.d.ts +20 -0
- package/dist/linear/comment.d.ts.map +1 -0
- package/dist/linear/comment.js +57 -0
- package/dist/linear/comment.js.map +1 -0
- package/dist/linear/identity.d.ts +59 -0
- package/dist/linear/identity.d.ts.map +1 -0
- package/dist/linear/identity.js +187 -0
- package/dist/linear/identity.js.map +1 -0
- package/dist/linear/notify.d.ts +35 -0
- package/dist/linear/notify.d.ts.map +1 -0
- package/dist/linear/notify.js +76 -0
- package/dist/linear/notify.js.map +1 -0
- package/dist/linear/ref.d.ts +13 -0
- package/dist/linear/ref.d.ts.map +1 -0
- package/dist/linear/ref.js +90 -0
- package/dist/linear/ref.js.map +1 -0
- package/dist/linear/verify.d.ts +26 -0
- package/dist/linear/verify.d.ts.map +1 -0
- package/dist/linear/verify.js +67 -0
- package/dist/linear/verify.js.map +1 -0
- package/dist/reviewers/claude.d.ts +4 -1
- package/dist/reviewers/claude.d.ts.map +1 -1
- package/dist/reviewers/claude.js +39 -7
- package/dist/reviewers/claude.js.map +1 -1
- package/dist/reviewers/codex.d.ts +3 -1
- package/dist/reviewers/codex.d.ts.map +1 -1
- package/dist/reviewers/codex.js +76 -70
- package/dist/reviewers/codex.js.map +1 -1
- package/dist/reviewers/conflict-resolve.d.ts +3 -1
- package/dist/reviewers/conflict-resolve.d.ts.map +1 -1
- package/dist/reviewers/conflict-resolve.js +21 -6
- package/dist/reviewers/conflict-resolve.js.map +1 -1
- package/dist/reviewers/fix.d.ts +5 -2
- package/dist/reviewers/fix.d.ts.map +1 -1
- package/dist/reviewers/fix.js +26 -10
- package/dist/reviewers/fix.js.map +1 -1
- package/dist/skills/attribution.d.ts +4 -0
- package/dist/skills/attribution.d.ts.map +1 -0
- package/dist/skills/attribution.js +14 -0
- package/dist/skills/attribution.js.map +1 -0
- package/dist/skills/broker-server.d.ts +2 -0
- package/dist/skills/broker-server.d.ts.map +1 -0
- package/dist/skills/broker-server.js +17 -0
- package/dist/skills/broker-server.js.map +1 -0
- package/dist/skills/broker.d.ts +42 -0
- package/dist/skills/broker.d.ts.map +1 -0
- package/dist/skills/broker.js +285 -0
- package/dist/skills/broker.js.map +1 -0
- package/dist/skills/catalog.d.ts +28 -0
- package/dist/skills/catalog.d.ts.map +1 -0
- package/dist/skills/catalog.js +104 -0
- package/dist/skills/catalog.js.map +1 -0
- package/dist/skills/installer.d.ts +10 -0
- package/dist/skills/installer.d.ts.map +1 -0
- package/dist/skills/installer.js +138 -0
- package/dist/skills/installer.js.map +1 -0
- package/dist/skills/integrity.d.ts +4 -0
- package/dist/skills/integrity.d.ts.map +1 -0
- package/dist/skills/integrity.js +36 -0
- package/dist/skills/integrity.js.map +1 -0
- package/docs/dynamic-thoroughness.md +738 -0
- package/docs/linear-identity-contract.md +139 -0
- package/docs/linear-identity.md +293 -0
- package/get-started.md +223 -11
- package/package.json +4 -3
package/dist/commands/status.js
CHANGED
|
@@ -1,18 +1,37 @@
|
|
|
1
1
|
import { execSync } from 'child_process';
|
|
2
2
|
import { existsSync, statSync } from 'fs';
|
|
3
3
|
import chalk from 'chalk';
|
|
4
|
-
import { loadConfig, getGithubTokenSource, getWebhookSecretPath, resolveConfigPath } from '../config/loader.js';
|
|
4
|
+
import { loadConfig, getGithubTokenSource, getWebhookSecretPath, resolveConfigPath, getLinearCredentials } from '../config/loader.js';
|
|
5
|
+
import { verifyLinearIdentity } from '../linear/verify.js';
|
|
5
6
|
import { checkCodexAuth } from '../reviewers/codex.js';
|
|
6
7
|
import { checkClaudeAuth } from '../reviewers/claude.js';
|
|
7
8
|
import { getLogDir, getTodayLogPath } from '../lib/logger.js';
|
|
8
9
|
import { buildImpactReport } from './impact.js';
|
|
10
|
+
import { formatSkillIdentity, loadSkillCatalog } from '../skills/catalog.js';
|
|
9
11
|
function row(label, value, ok) {
|
|
10
12
|
const indicator = ok === undefined ? ' ' : ok ? chalk.green('✓') : chalk.red('✗');
|
|
11
13
|
console.log(` ${indicator} ${chalk.bold(label.padEnd(22))} ${value}`);
|
|
12
14
|
}
|
|
15
|
+
const LINEAR_PROBE_TIMEOUT_MS = 10_000;
|
|
13
16
|
export async function runStatus(configPath) {
|
|
14
17
|
const config = loadConfig(configPath);
|
|
15
18
|
const activeConfigPath = resolveConfigPath(configPath);
|
|
19
|
+
// Start the Linear probe before the CLI auth checks so the two network round
|
|
20
|
+
// trips overlap, and bound it — status must stay responsive even if Linear is
|
|
21
|
+
// slow or unreachable. verifyLinearIdentity never throws; the timeout is the
|
|
22
|
+
// only way this can reject, so it resolves to a report either way.
|
|
23
|
+
const linearProbe = config.linear.enabled
|
|
24
|
+
? Promise.race([
|
|
25
|
+
verifyLinearIdentity(config.linear, getLinearCredentials(config.linear.auth)),
|
|
26
|
+
new Promise(resolve => setTimeout(() => resolve({
|
|
27
|
+
ok: false,
|
|
28
|
+
mode: config.linear.auth.mode,
|
|
29
|
+
actor: config.linear.identity.actor,
|
|
30
|
+
attribution: config.linear.auth.mode === 'client_credentials' ? 'app' : 'user',
|
|
31
|
+
error: `timed out after ${LINEAR_PROBE_TIMEOUT_MS / 1000}s`,
|
|
32
|
+
}), LINEAR_PROBE_TIMEOUT_MS).unref?.()),
|
|
33
|
+
])
|
|
34
|
+
: null;
|
|
16
35
|
console.log(chalk.bold('\ncrosscheck status\n'));
|
|
17
36
|
// Auth
|
|
18
37
|
console.log(chalk.dim(' Auth'));
|
|
@@ -38,6 +57,15 @@ export async function runStatus(configPath) {
|
|
|
38
57
|
row('config path', activeConfigPath ?? 'defaults only');
|
|
39
58
|
row('mode', config.mode);
|
|
40
59
|
row('quality tier', config.quality.tier);
|
|
60
|
+
const skillCatalog = loadSkillCatalog();
|
|
61
|
+
const bundledSkills = new Map(skillCatalog.map(skill => [skill.name, skill]));
|
|
62
|
+
row('installed skills', skillCatalog.map(formatSkillIdentity).join(', '));
|
|
63
|
+
row('enabled skills', config.skills.enabled.length > 0
|
|
64
|
+
? config.skills.enabled.map(name => {
|
|
65
|
+
const skill = bundledSkills.get(name);
|
|
66
|
+
return skill ? formatSkillIdentity(skill) : `${name} (not installed or failed integrity check)`;
|
|
67
|
+
}).join(', ')
|
|
68
|
+
: 'none');
|
|
41
69
|
row('codex auth', config.vendors.codex.auth);
|
|
42
70
|
row('codex model', config.vendors.codex.model ?? 'auto (by tier)');
|
|
43
71
|
row('claude model', config.vendors.claude.model ?? 'default');
|
|
@@ -50,6 +78,30 @@ export async function runStatus(configPath) {
|
|
|
50
78
|
if (config.quality.focus.length > 0) {
|
|
51
79
|
row('focus', config.quality.focus.join(', '));
|
|
52
80
|
}
|
|
81
|
+
// Linear identity — only when the operator has opted in.
|
|
82
|
+
if (linearProbe) {
|
|
83
|
+
console.log();
|
|
84
|
+
console.log(chalk.dim(' Linear'));
|
|
85
|
+
const report = await linearProbe;
|
|
86
|
+
row('auth mode', report.mode, report.ok);
|
|
87
|
+
if (!report.ok) {
|
|
88
|
+
row('identity', report.error ?? 'verification failed', false);
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
row('organization', report.organization ?? 'unknown');
|
|
92
|
+
if (report.attribution === 'app') {
|
|
93
|
+
const actor = config.linear.identity.per_step_actor ? `${report.actor}/<step>` : report.actor;
|
|
94
|
+
row('writes as', `${actor} ${chalk.dim('(crosscheck itself)')}`, true);
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
// Deliberately not a failed check. Attributing to a person is the wrong
|
|
98
|
+
// state for a shared workspace, but it is a perfectly reasonable choice for
|
|
99
|
+
// a solo user with no attribution problem — so this informs, it does not nag.
|
|
100
|
+
row('writes as', `${report.attributesTo ?? 'your Linear account'} ${chalk.dim('(api key)')}`);
|
|
101
|
+
console.log(chalk.dim(' to post as crosscheck itself, see docs/linear-identity.md'));
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
53
105
|
// Logs
|
|
54
106
|
console.log();
|
|
55
107
|
console.log(chalk.dim(' Logs'));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"status.js","sourceRoot":"","sources":["../../src/commands/status.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AACxC,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,IAAI,CAAA;AACzC,OAAO,KAAK,MAAM,OAAO,CAAA;AACzB,OAAO,EAAE,UAAU,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAA;
|
|
1
|
+
{"version":3,"file":"status.js","sourceRoot":"","sources":["../../src/commands/status.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"watch.d.ts","sourceRoot":"","sources":["../../src/commands/watch.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"watch.d.ts","sourceRoot":"","sources":["../../src/commands/watch.ts"],"names":[],"mappings":"AA2KA,MAAM,WAAW,SAAS;IACxB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,SAAS,CAAC,EAAE,OAAO,CAAA;CACpB;AAuBD,wBAAsB,QAAQ,CAAC,IAAI,GAAE,SAAc,iBAo1ClD"}
|
package/dist/commands/watch.js
CHANGED
|
@@ -3,18 +3,19 @@ import chalk from 'chalk';
|
|
|
3
3
|
import { createWebhookServer } from '../github/webhook.js';
|
|
4
4
|
import { createGithubClient, getCommitMessage, registerOrgWebhook, deleteOrgWebhook, registerRepoWebhook, deleteRepoWebhook, patchOrgWebhookEvents, patchRepoWebhookEvents, findOrgWebhook, findRepoWebhook, listUserRepos, checkRepoAccessible, } from '../github/client.js';
|
|
5
5
|
import { detectOriginFull, assignReviewer } from '../github/detector.js';
|
|
6
|
-
import { loadConfig, getGithubToken, getWebhookSecret, resolveConfigPath, promptDeploymentMode, detectScopesForDeployment, patchDeploymentConfig, detectGitHubLogin, } from '../config/loader.js';
|
|
6
|
+
import { loadConfig, getGithubToken, getWebhookSecret, resolveConfigPath, promptDeploymentMode, detectScopesForDeployment, patchDeploymentConfig, detectGitHubLogin, getLinearCredentials, } from '../config/loader.js';
|
|
7
7
|
import { randomFortune } from '../lib/fortune.js';
|
|
8
8
|
import { scanUnreviewedPRs } from '../lib/backtrace.js';
|
|
9
9
|
import { initLogger, log as fileLog, logError, logUncaught } from '../lib/logger.js';
|
|
10
10
|
import { isAuthorAllowed } from '../lib/filter.js';
|
|
11
11
|
import { runWorkflow } from '../lib/runner.js';
|
|
12
|
-
import { loadWorkflow, DEFAULT_RECHECK_INSTRUCTIONS } from '../lib/workflow.js';
|
|
13
|
-
import { filterStepsByTypes, formatRepoWorkflowSteps,
|
|
12
|
+
import { loadWorkflow, linearWritePossible, DEFAULT_RECHECK_INSTRUCTIONS } from '../lib/workflow.js';
|
|
13
|
+
import { filterStepsByTypes, formatRepoWorkflowSteps, isReviewOnlyWorkflow, readRepoWorkflowStepTypes, resolveRepoWorkflowSteps, workflowHasStep } from '../lib/repo-workflow.js';
|
|
14
14
|
import { fetchStepHistory, identifyNextWorkflowStep, decideReviewOnly } from '../lib/pr-workflow-state.js';
|
|
15
15
|
import { parseAnnotation } from '../lib/annotation.js';
|
|
16
16
|
import { PRBoard, fmtTime, FMT_TIME_WIDTH } from '../lib/board.js';
|
|
17
17
|
import { clonePRForReview } from '../lib/clone.js';
|
|
18
|
+
import { resolveLinearAuth, isLinearConfigError } from '../linear/identity.js';
|
|
18
19
|
import { getSmartSwitch, isSubscriptionLimitError, detectFailedVendor, triggerSwitch, notifyReviewSuccess, stopSmartSwitch, } from '../lib/smart-switch.js';
|
|
19
20
|
import { buildReviewFailedCommentBody } from '../lib/comment-bodies.js';
|
|
20
21
|
import { classifyReviewerError } from '../lib/reviewer-error.js';
|
|
@@ -128,6 +129,21 @@ function openTunnel(localPort) {
|
|
|
128
129
|
});
|
|
129
130
|
});
|
|
130
131
|
}
|
|
132
|
+
// PR history is what decides whether a step runs at all — the approval stop, the
|
|
133
|
+
// per-SHA dedup, the resume point. Acting without it means posting a duplicate review
|
|
134
|
+
// or modifying an already-approved commit, so callers fail closed rather than assume an
|
|
135
|
+
// empty history. One short retry first: the usual cause is a transient GitHub blip or a
|
|
136
|
+
// rate-limit tick, and deferring the event strands the PR until something else happens.
|
|
137
|
+
const HISTORY_RETRY_DELAY_MS = 3_000;
|
|
138
|
+
async function fetchStepHistoryWithRetry(owner, repo, prNumber, token) {
|
|
139
|
+
try {
|
|
140
|
+
return await fetchStepHistory(owner, repo, prNumber, token);
|
|
141
|
+
}
|
|
142
|
+
catch {
|
|
143
|
+
await new Promise(resolve => setTimeout(resolve, HISTORY_RETRY_DELAY_MS));
|
|
144
|
+
return await fetchStepHistory(owner, repo, prNumber, token);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
131
147
|
export async function runWatch(opts = {}) {
|
|
132
148
|
const configPath = opts.config;
|
|
133
149
|
let config = loadConfig(configPath);
|
|
@@ -156,6 +172,25 @@ export async function runWatch(opts = {}) {
|
|
|
156
172
|
console.error(chalk.red(`✗ ${err instanceof Error ? err.message : String(err)}`));
|
|
157
173
|
process.exit(1);
|
|
158
174
|
}
|
|
175
|
+
// Preflight Linear identity at startup. watch is a long-running daemon: a bad
|
|
176
|
+
// credential discovered per-event would either drop events silently or spam the
|
|
177
|
+
// board. Fail loudly at boot instead, the way the run path fails before its
|
|
178
|
+
// clone. The token is discarded — per-event resolution mints a fresh one, since
|
|
179
|
+
// app tokens outlive neither a long daemon run nor a rotation.
|
|
180
|
+
// Same gate as the run and per-event paths: a daemon whose configured workflow
|
|
181
|
+
// is fix/conflict-resolve only can never call notifyLinear, so a missing
|
|
182
|
+
// credential must not stop it booting.
|
|
183
|
+
if (linearWritePossible(config.linear, loadWorkflow(process.cwd()))) {
|
|
184
|
+
try {
|
|
185
|
+
const preflight = await resolveLinearAuth(config.linear, getLinearCredentials(config.linear.auth));
|
|
186
|
+
console.log(chalk.dim(` linear: ${preflight.mode} — writes as ${preflight.actor}`));
|
|
187
|
+
}
|
|
188
|
+
catch (err) {
|
|
189
|
+
logError({ command: 'watch', phase: 'linear-auth' }, err);
|
|
190
|
+
console.error(chalk.red(`✗ ${err instanceof Error ? err.message : String(err)}`));
|
|
191
|
+
process.exit(isLinearConfigError(err) ? 1 : 2);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
159
194
|
fileLog({ level: 'info', event: 'session_start', command: 'watch' });
|
|
160
195
|
// Fetch the authenticated user so the onComment handler can reject annotation
|
|
161
196
|
// injection from non-token accounts. If this fails, the handler is disabled
|
|
@@ -191,12 +226,16 @@ export async function runWatch(opts = {}) {
|
|
|
191
226
|
// Last-reviewed diff hash per PR — skip reviews when a new SHA has identical diff vs base
|
|
192
227
|
// (force-push, amend, no-op rebase). Persisted so restarts don't re-review unchanged content.
|
|
193
228
|
const diffHashes = new PersistentDiffHashMap();
|
|
194
|
-
//
|
|
195
|
-
|
|
229
|
+
// Completed round count per PR — the auto-loop's round counter, which history cannot
|
|
230
|
+
// supply (it returns the last recheck's round unchanged across iterations).
|
|
196
231
|
const prRoundCounts = new Map();
|
|
197
|
-
//
|
|
198
|
-
//
|
|
199
|
-
|
|
232
|
+
// Comment IDs the issue_comment bridge has already handled, so a webhook redelivery
|
|
233
|
+
// of the same comment does not dispatch twice. There is no need to guess at which
|
|
234
|
+
// comments are "ours": webhook.ts forwards only `trigger=kickass` annotations, and
|
|
235
|
+
// this watcher's own reviews carry trigger=watch/comment/backtrace, so they never
|
|
236
|
+
// reach the bridge. Every comment that does reach it is a kickass dispatch whose
|
|
237
|
+
// verdict this watcher is meant to act on — history detection decides how.
|
|
238
|
+
const handledCommentIds = new Set();
|
|
200
239
|
// Idle tracking — reset on any PR activity; used by the idle-issue timer.
|
|
201
240
|
let lastActivityAt = Date.now();
|
|
202
241
|
let idleAnalysisRunning = false;
|
|
@@ -215,6 +254,7 @@ export async function runWatch(opts = {}) {
|
|
|
215
254
|
fileLog({ level: 'info', event: 'pr_skipped', repo: `${owner}/${repoName}`, pr: prNumber, reason: 'author_not_allowed', author: params.author });
|
|
216
255
|
return;
|
|
217
256
|
}
|
|
257
|
+
const prKey = `${owner}/${repoName}#${prNumber}`;
|
|
218
258
|
const { origin, method: originMethod } = await detectOriginFull(params.body ?? '', params.headRef, owner, repoName, prNumber, config, token, params.author);
|
|
219
259
|
// Smart-switch: when cross-vendor is degraded, override to single-vendor with the healthy vendor
|
|
220
260
|
const ss = getSmartSwitch();
|
|
@@ -235,9 +275,16 @@ export async function runWatch(opts = {}) {
|
|
|
235
275
|
title: params.title,
|
|
236
276
|
body: params.body ?? '',
|
|
237
277
|
head: { ref: params.headRef, sha: params.headSha, repo: params.headRepo ? { full_name: params.headRepo } : null },
|
|
238
|
-
base: {
|
|
278
|
+
base: {
|
|
279
|
+
ref: params.baseRef,
|
|
280
|
+
repo: {
|
|
281
|
+
full_name: `${owner}/${repoName}`,
|
|
282
|
+
...(params.defaultBranch !== undefined && { default_branch: params.defaultBranch }),
|
|
283
|
+
},
|
|
284
|
+
},
|
|
239
285
|
html_url: `https://github.com/${owner}/${repoName}/pull/${prNumber}`,
|
|
240
286
|
user: { login: params.author },
|
|
287
|
+
...(params.labels !== undefined && { labels: params.labels.map(name => ({ name })) }),
|
|
241
288
|
};
|
|
242
289
|
if (!acquirePRLock(owner, repoName, prNumber, params.headSha)) {
|
|
243
290
|
fileLog({ level: 'info', event: 'pr_skipped', repo: `${owner}/${repoName}`, pr: prNumber, reason: 'in_progress_local' });
|
|
@@ -257,21 +304,26 @@ export async function runWatch(opts = {}) {
|
|
|
257
304
|
logError({ repo: `${owner}/${repoName}`, pr: prNumber, phase: 'lock' }, err);
|
|
258
305
|
return;
|
|
259
306
|
}
|
|
260
|
-
|
|
261
|
-
//
|
|
262
|
-
//
|
|
263
|
-
//
|
|
264
|
-
//
|
|
265
|
-
//
|
|
266
|
-
//
|
|
267
|
-
//
|
|
268
|
-
//
|
|
269
|
-
//
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
307
|
+
// Determine the correct starting step from PR comment history. Webhook, backtrace,
|
|
308
|
+
// and auto_loop events always ask — there is no PR-level session fast-path.
|
|
309
|
+
//
|
|
310
|
+
// There used to be one (`reviewedPRKeys`): reviewed-in-this-session meant "run the
|
|
311
|
+
// next event as a recheck". It was wrong by construction on two axes. It is
|
|
312
|
+
// SHA-agnostic, so a push it had never seen inherited the previous SHA's routing;
|
|
313
|
+
// and it only knows what THIS process did, so an approval, a forced re-review, or a
|
|
314
|
+
// fix pushed by `kickass`, `crosscheck run`, or a second watcher was invisible to
|
|
315
|
+
// it. Each case where it guessed wrong grew its own guard — recheck-no-fix depth,
|
|
316
|
+
// repo overrides, approvals — and every one of those guards did the same thing:
|
|
317
|
+
// undo the guess and ask history. So ask history. It costs one call per event, and
|
|
318
|
+
// it is the only thing that sees the PR as it actually is.
|
|
319
|
+
//
|
|
320
|
+
// Comment triggers ask too. The SHA-specific fast path they used to take ran an
|
|
321
|
+
// externally-posted NEEDS_WORK as a recheck instead of routing to fix, because it
|
|
322
|
+
// skipped detection whenever this session had already touched the SHA.
|
|
323
|
+
let isRecheckRun = false;
|
|
324
|
+
// auto_loop's round is pre-computed from the completed round count rather than
|
|
325
|
+
// from history, which returns the last recheck's round unchanged across iterations
|
|
326
|
+
// and would defeat the max_rounds cap.
|
|
275
327
|
let round = params.action === 'auto_loop'
|
|
276
328
|
? (prRoundCounts.get(prKey) ?? 1) + 1
|
|
277
329
|
: isRecheckRun ? (prRoundCounts.get(prKey) ?? 1) + 1 : 1;
|
|
@@ -285,17 +337,6 @@ export async function runWatch(opts = {}) {
|
|
|
285
337
|
isRecheckRun = false;
|
|
286
338
|
round = 1;
|
|
287
339
|
}
|
|
288
|
-
// A recheck-no-fix depth (`review,recheck`) decides review-vs-recheck per SHA from
|
|
289
|
-
// the last verdict: an unresolved review gets a recheck, an APPROVE gets a fresh
|
|
290
|
-
// review (identifyNextWorkflowStep). The session fast-path above is PR-level and
|
|
291
|
-
// SHA-agnostic, so it would coerce every later push in this process into a recheck
|
|
292
|
-
// and never consult that decision. Force history detection instead. Comment-
|
|
293
|
-
// triggered runs keep their SHA-specific fast path — a kickass re-review targets a
|
|
294
|
-
// SHA history detection would report as already complete.
|
|
295
|
-
if (isRecheckWithoutFix(repoStepOverride) && isRecheckRun && params.action !== 'comment') {
|
|
296
|
-
isRecheckRun = false;
|
|
297
|
-
round = 1;
|
|
298
|
-
}
|
|
299
340
|
if (reviewOnlyForRepo) {
|
|
300
341
|
// Review-only comes from a per-repo override (crosscheck alter --review-only).
|
|
301
342
|
// Restrict the workflow to review step(s) and dedup on the exact head sha so
|
|
@@ -312,7 +353,7 @@ export async function runWatch(opts = {}) {
|
|
|
312
353
|
round = 1;
|
|
313
354
|
resolvedSteps = reviewSteps;
|
|
314
355
|
try {
|
|
315
|
-
const history = await
|
|
356
|
+
const history = await fetchStepHistoryWithRetry(owner, repoName, prNumber, token);
|
|
316
357
|
const decision = decideReviewOnly(history, params.headSha);
|
|
317
358
|
if (decision.alreadyReviewed) {
|
|
318
359
|
await releaseRemoteLock(lockOctokit, owner, repoName, params.headSha, 'success');
|
|
@@ -322,7 +363,18 @@ export async function runWatch(opts = {}) {
|
|
|
322
363
|
}
|
|
323
364
|
round = decision.round;
|
|
324
365
|
}
|
|
325
|
-
catch {
|
|
366
|
+
catch (err) {
|
|
367
|
+
// The dedup this mode relies on lives in that history. Reviewing without it
|
|
368
|
+
// would post a second review on a SHA already covered.
|
|
369
|
+
// Released as `failure`, not `success`: this SHA was not reviewed, and
|
|
370
|
+
// `crosscheck/review` is a commit status a repo can require for merge — a
|
|
371
|
+
// green one here would let an unreviewed HEAD satisfy branch protection
|
|
372
|
+
// during a GitHub outage.
|
|
373
|
+
await releaseRemoteLock(lockOctokit, owner, repoName, params.headSha, 'failure');
|
|
374
|
+
releasePRLock(owner, repoName, prNumber, params.headSha);
|
|
375
|
+
fileLog({ level: 'warn', event: 'pr_skipped', repo: `${owner}/${repoName}`, pr: prNumber, reason: 'history_unavailable', sha: params.headSha, error: err instanceof Error ? err.message : String(err) });
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
326
378
|
}
|
|
327
379
|
else if (!isRecheckRun) {
|
|
328
380
|
try {
|
|
@@ -331,14 +383,14 @@ export async function runWatch(opts = {}) {
|
|
|
331
383
|
// fresh so live edits to workflow.yml apply across events.
|
|
332
384
|
const globalWorkflow = loadWorkflow(process.cwd());
|
|
333
385
|
const allSteps = repoStepOverride ? filterStepsByTypes(globalWorkflow, repoStepOverride) : globalWorkflow;
|
|
334
|
-
const history = await
|
|
386
|
+
const history = await fetchStepHistoryWithRetry(owner, repoName, prNumber, token);
|
|
335
387
|
const nextResult = identifyNextWorkflowStep(history, allSteps, params.headSha);
|
|
336
388
|
if (nextResult.step === null) {
|
|
337
389
|
// Workflow already complete for this HEAD sha — release lock and skip.
|
|
338
390
|
// Happens when a synchronize event fires after all steps are done.
|
|
339
391
|
await releaseRemoteLock(lockOctokit, owner, repoName, params.headSha, 'success');
|
|
340
392
|
releasePRLock(owner, repoName, prNumber, params.headSha);
|
|
341
|
-
fileLog({ level: 'info', event: 'pr_skipped', repo: `${owner}/${repoName}`, pr: prNumber, reason: 'workflow_complete', sha: params.headSha });
|
|
393
|
+
fileLog({ level: 'info', event: 'pr_skipped', repo: `${owner}/${repoName}`, pr: prNumber, reason: nextResult.stopReason ?? 'workflow_complete', sha: params.headSha });
|
|
342
394
|
return;
|
|
343
395
|
}
|
|
344
396
|
if (nextResult.hasExistingReview) {
|
|
@@ -391,15 +443,26 @@ export async function runWatch(opts = {}) {
|
|
|
391
443
|
}
|
|
392
444
|
}
|
|
393
445
|
}
|
|
394
|
-
catch {
|
|
395
|
-
//
|
|
396
|
-
//
|
|
397
|
-
//
|
|
398
|
-
//
|
|
399
|
-
//
|
|
400
|
-
//
|
|
401
|
-
|
|
402
|
-
|
|
446
|
+
catch (err) {
|
|
447
|
+
// History still unreadable after the retry. Everything this function decides —
|
|
448
|
+
// whether HEAD is already approved, already reviewed, or mid-loop — lives in
|
|
449
|
+
// that history, so running anyway risks a second review on an approved commit
|
|
450
|
+
// or a fix re-applied to code that already has one. Defer instead: release the
|
|
451
|
+
// locks and let the next event, `kickass`, or `crosscheck run` pick the PR up.
|
|
452
|
+
//
|
|
453
|
+
// auto_loop is the exception. It is a continuation of a run this process just
|
|
454
|
+
// completed, so the state is known from the session rather than from history,
|
|
455
|
+
// and deferring would strand the loop — nothing else re-triggers it.
|
|
456
|
+
if (params.action !== 'auto_loop') {
|
|
457
|
+
// `failure`, not `success` — see the review-only branch above: a green
|
|
458
|
+
// `crosscheck/review` on a HEAD nobody reviewed can satisfy branch
|
|
459
|
+
// protection.
|
|
460
|
+
await releaseRemoteLock(lockOctokit, owner, repoName, params.headSha, 'failure');
|
|
461
|
+
releasePRLock(owner, repoName, prNumber, params.headSha);
|
|
462
|
+
fileLog({ level: 'warn', event: 'pr_skipped', repo: `${owner}/${repoName}`, pr: prNumber, reason: 'history_unavailable', sha: params.headSha, error: err instanceof Error ? err.message : String(err) });
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
isRecheckRun = true;
|
|
403
466
|
}
|
|
404
467
|
}
|
|
405
468
|
const reviewStart = Date.now();
|
|
@@ -407,6 +470,32 @@ export async function runWatch(opts = {}) {
|
|
|
407
470
|
let stopHeartbeat = () => { };
|
|
408
471
|
let boardAdded = false;
|
|
409
472
|
try {
|
|
473
|
+
// Board first, so any failure below is visible rather than swallowed by the
|
|
474
|
+
// enclosing catch (which only calls logError — a no-op when file logging is
|
|
475
|
+
// off). Then auth, then the clone: during an outage a long-running watcher
|
|
476
|
+
// would otherwise pay for a full clone on every event before rejecting it.
|
|
477
|
+
// Computed here rather than at the runWorkflow call so the auth gate below
|
|
478
|
+
// sees the workflow that will actually run. resolvedSteps alone is
|
|
479
|
+
// undefined for a plain event, and canWriteVerdict fails open on undefined
|
|
480
|
+
// — which let a fix-only daemon resolve credentials on every event.
|
|
481
|
+
const workflowStepsForRun = resolvedSteps
|
|
482
|
+
?? (repoStepOverride ? filterStepsByTypes(loadWorkflow(process.cwd()), repoStepOverride) : loadWorkflow(process.cwd()));
|
|
483
|
+
board.addPR(key, prNumber, `${owner}/${repoName}`, params.headRef, round);
|
|
484
|
+
boardAdded = true;
|
|
485
|
+
// Only workflows that can write a verdict need an identity — the fix and
|
|
486
|
+
// resolve paths never call notifyLinear.
|
|
487
|
+
let linearAuth = null;
|
|
488
|
+
if (linearWritePossible(effectiveConfig.linear, workflowStepsForRun)) {
|
|
489
|
+
try {
|
|
490
|
+
linearAuth = await resolveLinearAuth(effectiveConfig.linear, getLinearCredentials(effectiveConfig.linear.auth));
|
|
491
|
+
}
|
|
492
|
+
catch (err) {
|
|
493
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
494
|
+
board.failPR(key, `linear identity unavailable: ${message}`);
|
|
495
|
+
fileLog({ level: 'error', event: 'linear_auth_failed', repo: `${owner}/${repoName}`, pr: prNumber, reason: message });
|
|
496
|
+
throw err;
|
|
497
|
+
}
|
|
498
|
+
}
|
|
410
499
|
await clonePRForReview({
|
|
411
500
|
owner, repo: repoName, prNumber, baseRef: params.baseRef,
|
|
412
501
|
tmpDir, token, protocol: config.clone_protocol,
|
|
@@ -442,18 +531,18 @@ export async function runWatch(opts = {}) {
|
|
|
442
531
|
logError({ repo: `${owner}/${repoName}`, pr: prNumber, phase: 'no_diff_comment' }, err);
|
|
443
532
|
}
|
|
444
533
|
await releaseRemoteLock(lockOctokit, owner, repoName, params.headSha, 'success');
|
|
534
|
+
// The PR is on the board from before the clone, so close its slot rather
|
|
535
|
+
// than leaving a row that never resolves.
|
|
536
|
+
board.completePR(key, { elapsedMs: Date.now() - reviewStart, url: `https://github.com/${owner}/${repoName}/pull/${prNumber}` });
|
|
445
537
|
return;
|
|
446
538
|
}
|
|
447
|
-
board.addPR(key, prNumber, `${owner}/${repoName}`, params.headRef, round);
|
|
448
|
-
boardAdded = true;
|
|
449
539
|
const prLoc = computePRLoc(tmpDir, params.baseRef);
|
|
450
540
|
board.updatePR(key, { prLoc });
|
|
451
541
|
stopHeartbeat = startRemoteLockHeartbeat(lockOctokit, owner, repoName, params.headSha);
|
|
452
|
-
const
|
|
453
|
-
?? (repoStepOverride ? filterStepsByTypes(loadWorkflow(process.cwd()), repoStepOverride) : undefined);
|
|
454
|
-
const { verdict, fixAppliedCount } = await runWorkflow({
|
|
542
|
+
const { verdict, fixAppliedCount, strategySkipped } = await runWorkflow({
|
|
455
543
|
owner, repoName, prNumber, pr,
|
|
456
544
|
tmpDir, token, config: effectiveConfig, origin,
|
|
545
|
+
linearAuth,
|
|
457
546
|
reviewStart,
|
|
458
547
|
log: (msg) => bLog(`${chalk.dim(fmtTime())} ${msg}`),
|
|
459
548
|
onPhaseChange: (label, data) => board.updatePR(key, { label, ...data }),
|
|
@@ -470,10 +559,11 @@ export async function runWatch(opts = {}) {
|
|
|
470
559
|
round,
|
|
471
560
|
trigger: params.action === 'backtrace' ? 'backtrace' : params.action === 'comment' ? 'comment' : 'watch',
|
|
472
561
|
});
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
562
|
+
// A strategy-skipped PR never ran a review, so it must not advance the
|
|
563
|
+
// auto-loop's round counter — that run posted nothing to count.
|
|
564
|
+
if (strategySkipped === undefined) {
|
|
565
|
+
prRoundCounts.set(prKey, round);
|
|
566
|
+
}
|
|
477
567
|
// Recompute the diff hash AFTER runWorkflow — workflow steps such as
|
|
478
568
|
// `conflict-resolve` or `fix` followed by `recheck` can mutate the checkout,
|
|
479
569
|
// so the pre-workflow hash may not represent the content that was actually
|
|
@@ -493,7 +583,10 @@ export async function runWatch(opts = {}) {
|
|
|
493
583
|
}
|
|
494
584
|
catch { /* fall back — skip auto-loop this cycle */ }
|
|
495
585
|
}
|
|
496
|
-
|
|
586
|
+
// Same gate as the session caches above: caching the diff of a PR that was
|
|
587
|
+
// never reviewed would make the first event that *should* review it get
|
|
588
|
+
// skipped as `no_diff_change`.
|
|
589
|
+
if (newDiffHash && strategySkipped === undefined) {
|
|
497
590
|
// For non-commit delivery with an applied fix the checkout may be on a fix
|
|
498
591
|
// branch; computeDiffHash would return the post-fix diff, not the original PR
|
|
499
592
|
// diff. Caching that under prKey corrupts future no_diff_change checks for the
|
|
@@ -514,9 +607,15 @@ export async function runWatch(opts = {}) {
|
|
|
514
607
|
}
|
|
515
608
|
}
|
|
516
609
|
}
|
|
610
|
+
// A class-skipped PR (e.g. lockfile-only) never ran a review, so say so
|
|
611
|
+
// rather than reporting it as a completed one.
|
|
612
|
+
if (strategySkipped) {
|
|
613
|
+
bLog(`${chalk.dim(fmtTime())} ${chalk.dim(`PR #${prNumber} skipped — ${strategySkipped} class, nothing to review`)}`);
|
|
614
|
+
}
|
|
517
615
|
board.completePR(key, {
|
|
518
616
|
elapsedMs: Date.now() - reviewStart,
|
|
519
617
|
url: `github.com/${owner}/${repoName}/pull/${prNumber}`,
|
|
618
|
+
...(strategySkipped !== undefined && { label: `skipped · ${strategySkipped}` }),
|
|
520
619
|
});
|
|
521
620
|
// Smart-switch recovery confirmation: if a restore attempt is pending and
|
|
522
621
|
// this reviewer matches the previously-degraded vendor, announce full restoration.
|
|
@@ -649,6 +748,8 @@ export async function runWatch(opts = {}) {
|
|
|
649
748
|
title: pr.title, body: pr.body, author: pr.user.login,
|
|
650
749
|
headSha: pr.head.sha, headRef: pr.head.ref, headRepo: pr.head.repo?.full_name ?? null,
|
|
651
750
|
baseRef: pr.base.ref, action: event.action,
|
|
751
|
+
...(pr.labels !== undefined && { labels: pr.labels.map(l => l.name) }),
|
|
752
|
+
...(pr.base.repo.default_branch !== undefined && { defaultBranch: pr.base.repo.default_branch }),
|
|
652
753
|
});
|
|
653
754
|
}, (msg) => bLog(chalk.dim(fmtTime()) + ' ' + msg), fileLog, async (event) => {
|
|
654
755
|
const owner = event.repository.owner.login;
|
|
@@ -698,10 +799,11 @@ export async function runWatch(opts = {}) {
|
|
|
698
799
|
fileLog({ level: 'info', event: 'pr_skipped', repo: `${owner}/${repoName}`, pr: prNumber, reason: 'comment_stale_sha', sha: prData.head.sha, annotation_sha: annotationSha });
|
|
699
800
|
return;
|
|
700
801
|
}
|
|
701
|
-
if (
|
|
702
|
-
fileLog({ level: 'info', event: 'pr_skipped', repo: `${owner}/${repoName}`, pr: prNumber, reason: '
|
|
802
|
+
if (handledCommentIds.has(event.comment.id)) {
|
|
803
|
+
fileLog({ level: 'info', event: 'pr_skipped', repo: `${owner}/${repoName}`, pr: prNumber, reason: 'comment_already_handled', comment_id: event.comment.id });
|
|
703
804
|
return;
|
|
704
805
|
}
|
|
806
|
+
handledCommentIds.add(event.comment.id);
|
|
705
807
|
// GitHub can deliver issue_comment before ck run's finally block releases
|
|
706
808
|
// the remote lock. Retry with backoff; re-fetch the PR head on each attempt
|
|
707
809
|
// so we don't dispatch against a sha that advanced while we were waiting.
|
|
@@ -725,10 +827,6 @@ export async function runWatch(opts = {}) {
|
|
|
725
827
|
break;
|
|
726
828
|
}
|
|
727
829
|
}
|
|
728
|
-
if (reviewedPRShaKeys.has(`${owner}/${repoName}#${prNumber}@${prData.head.sha}`)) {
|
|
729
|
-
fileLog({ level: 'info', event: 'pr_skipped', repo: `${owner}/${repoName}`, pr: prNumber, reason: 'comment_self_trigger', sha: prData.head.sha });
|
|
730
|
-
break;
|
|
731
|
-
}
|
|
732
830
|
if (await checkRemoteLock(octokit, owner, repoName, prData.head.sha).catch(() => false)) {
|
|
733
831
|
fileLog({ level: 'info', event: 'comment_trigger_deferred', repo: `${owner}/${repoName}`, pr: prNumber, reason: 'in_progress_remote', attempt });
|
|
734
832
|
continue;
|
|
@@ -743,6 +841,11 @@ export async function runWatch(opts = {}) {
|
|
|
743
841
|
headRepo: prData.head.repo?.full_name ?? null,
|
|
744
842
|
baseRef: prData.base.ref,
|
|
745
843
|
action: 'comment',
|
|
844
|
+
// Same fields the webhook path forwards: without them the risky
|
|
845
|
+
// class's label and hotfix rules classify this PR differently
|
|
846
|
+
// depending on which trigger ran it.
|
|
847
|
+
...(prData.labels !== undefined && { labels: prData.labels.map((l) => l.name) }),
|
|
848
|
+
...(prData.base.repo?.default_branch !== undefined && { defaultBranch: prData.base.repo.default_branch }),
|
|
746
849
|
});
|
|
747
850
|
break;
|
|
748
851
|
}
|
|
@@ -1029,6 +1132,8 @@ export async function runWatch(opts = {}) {
|
|
|
1029
1132
|
title: pr.title, body: pr.body, author: pr.author,
|
|
1030
1133
|
headSha: pr.headSha, headRef: pr.headRef, headRepo: pr.headRepo,
|
|
1031
1134
|
baseRef: pr.baseRef, action: 'backtrace',
|
|
1135
|
+
...(pr.labels !== undefined && { labels: pr.labels }),
|
|
1136
|
+
...(pr.defaultBranch !== undefined && { defaultBranch: pr.defaultBranch }),
|
|
1032
1137
|
})));
|
|
1033
1138
|
}
|
|
1034
1139
|
catch (err) {
|