@humanbased/crosscheck 1.2.0-beta.80 → 1.2.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.
Files changed (301) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +388 -149
  3. package/README.zh.md +1 -1
  4. package/crosscheck.config.example.yml +2 -63
  5. package/dist/__tests__/codex.test.js +27 -14
  6. package/dist/__tests__/codex.test.js.map +1 -1
  7. package/dist/__tests__/comment-bodies.test.js +1 -49
  8. package/dist/__tests__/comment-bodies.test.js.map +1 -1
  9. package/dist/__tests__/conflict-resolve.test.js +1 -44
  10. package/dist/__tests__/conflict-resolve.test.js.map +1 -1
  11. package/dist/__tests__/fix.test.js +0 -33
  12. package/dist/__tests__/fix.test.js.map +1 -1
  13. package/dist/__tests__/onboard-preservation.test.js +0 -5
  14. package/dist/__tests__/onboard-preservation.test.js.map +1 -1
  15. package/dist/__tests__/optimize.test.js +0 -2
  16. package/dist/__tests__/optimize.test.js.map +1 -1
  17. package/dist/__tests__/repo-picker.test.js +1 -7
  18. package/dist/__tests__/repo-picker.test.js.map +1 -1
  19. package/dist/__tests__/review-comment-body.test.js +0 -35
  20. package/dist/__tests__/review-comment-body.test.js.map +1 -1
  21. package/dist/__tests__/review-models.test.js +3 -19
  22. package/dist/__tests__/review-models.test.js.map +1 -1
  23. package/dist/__tests__/runner.test.js +1 -29
  24. package/dist/__tests__/runner.test.js.map +1 -1
  25. package/dist/cli.js +0 -29
  26. package/dist/cli.js.map +1 -1
  27. package/dist/commands/onboard.d.ts +0 -11
  28. package/dist/commands/onboard.d.ts.map +1 -1
  29. package/dist/commands/onboard.js +2 -88
  30. package/dist/commands/onboard.js.map +1 -1
  31. package/dist/commands/review.d.ts.map +1 -1
  32. package/dist/commands/review.js +6 -65
  33. package/dist/commands/review.js.map +1 -1
  34. package/dist/commands/run.d.ts.map +1 -1
  35. package/dist/commands/run.js +3 -23
  36. package/dist/commands/run.js.map +1 -1
  37. package/dist/commands/status.d.ts.map +1 -1
  38. package/dist/commands/status.js +1 -53
  39. package/dist/commands/status.js.map +1 -1
  40. package/dist/commands/watch.d.ts.map +1 -1
  41. package/dist/commands/watch.js +6 -52
  42. package/dist/commands/watch.js.map +1 -1
  43. package/dist/config/loader.d.ts +1 -3
  44. package/dist/config/loader.d.ts.map +1 -1
  45. package/dist/config/loader.js +0 -13
  46. package/dist/config/loader.js.map +1 -1
  47. package/dist/config/review-model-tiers.json +3 -3
  48. package/dist/config/schema.d.ts +10 -254
  49. package/dist/config/schema.d.ts.map +1 -1
  50. package/dist/config/schema.js +2 -62
  51. package/dist/config/schema.js.map +1 -1
  52. package/dist/github/client.d.ts +1 -6
  53. package/dist/github/client.d.ts.map +1 -1
  54. package/dist/github/client.js +7 -15
  55. package/dist/github/client.js.map +1 -1
  56. package/dist/issues/ticket-ref.d.ts.map +1 -1
  57. package/dist/issues/ticket-ref.js +5 -6
  58. package/dist/issues/ticket-ref.js.map +1 -1
  59. package/dist/lib/clone.d.ts +0 -1
  60. package/dist/lib/clone.d.ts.map +1 -1
  61. package/dist/lib/clone.js +10 -32
  62. package/dist/lib/clone.js.map +1 -1
  63. package/dist/lib/comment-bodies.d.ts +0 -35
  64. package/dist/lib/comment-bodies.d.ts.map +1 -1
  65. package/dist/lib/comment-bodies.js +9 -43
  66. package/dist/lib/comment-bodies.js.map +1 -1
  67. package/dist/lib/repo-picker.d.ts +0 -3
  68. package/dist/lib/repo-picker.d.ts.map +1 -1
  69. package/dist/lib/repo-picker.js +9 -45
  70. package/dist/lib/repo-picker.js.map +1 -1
  71. package/dist/lib/review-models.d.ts +2 -15
  72. package/dist/lib/review-models.d.ts.map +1 -1
  73. package/dist/lib/review-models.js +6 -26
  74. package/dist/lib/review-models.js.map +1 -1
  75. package/dist/lib/runner.d.ts +0 -12
  76. package/dist/lib/runner.d.ts.map +1 -1
  77. package/dist/lib/runner.js +42 -186
  78. package/dist/lib/runner.js.map +1 -1
  79. package/dist/lib/workflow.d.ts +0 -9
  80. package/dist/lib/workflow.d.ts.map +1 -1
  81. package/dist/lib/workflow.js +0 -20
  82. package/dist/lib/workflow.js.map +1 -1
  83. package/dist/reviewers/claude.d.ts +1 -4
  84. package/dist/reviewers/claude.d.ts.map +1 -1
  85. package/dist/reviewers/claude.js +7 -39
  86. package/dist/reviewers/claude.js.map +1 -1
  87. package/dist/reviewers/codex.d.ts +1 -3
  88. package/dist/reviewers/codex.d.ts.map +1 -1
  89. package/dist/reviewers/codex.js +70 -76
  90. package/dist/reviewers/codex.js.map +1 -1
  91. package/dist/reviewers/conflict-resolve.d.ts +1 -3
  92. package/dist/reviewers/conflict-resolve.d.ts.map +1 -1
  93. package/dist/reviewers/conflict-resolve.js +6 -21
  94. package/dist/reviewers/conflict-resolve.js.map +1 -1
  95. package/dist/reviewers/fix.d.ts +2 -5
  96. package/dist/reviewers/fix.d.ts.map +1 -1
  97. package/dist/reviewers/fix.js +10 -26
  98. package/dist/reviewers/fix.js.map +1 -1
  99. package/get-started.md +4 -139
  100. package/package.json +3 -4
  101. package/assets/icon-256.png +0 -0
  102. package/assets/linear-comment.svg +0 -18
  103. package/assets/linear-onboard.svg +0 -30
  104. package/assets/linear-status.svg +0 -23
  105. package/assets/linear-test.svg +0 -34
  106. package/assets/skills/code-review/.crosscheck-skill.json +0 -9
  107. package/assets/skills/code-review/LICENSE +0 -21
  108. package/assets/skills/code-review/SKILL.md +0 -89
  109. package/assets/skills/code-review/agents/openai.yaml +0 -3
  110. package/assets/skills/code-review-skill/.crosscheck-skill.json +0 -9
  111. package/assets/skills/code-review-skill/LICENSE +0 -21
  112. package/assets/skills/code-review-skill/SKILL.md +0 -231
  113. package/assets/skills/code-review-skill/assets/pr-review-template.md +0 -137
  114. package/assets/skills/code-review-skill/assets/review-checklist.md +0 -123
  115. package/assets/skills/code-review-skill/reference/angular.md +0 -768
  116. package/assets/skills/code-review-skill/reference/architecture-review-guide.md +0 -472
  117. package/assets/skills/code-review-skill/reference/c.md +0 -890
  118. package/assets/skills/code-review-skill/reference/code-quality-universal.md +0 -488
  119. package/assets/skills/code-review-skill/reference/code-review-best-practices.md +0 -136
  120. package/assets/skills/code-review-skill/reference/common-bugs-checklist.md +0 -286
  121. package/assets/skills/code-review-skill/reference/cpp.md +0 -893
  122. package/assets/skills/code-review-skill/reference/cross-cutting/async-concurrency-patterns.md +0 -515
  123. package/assets/skills/code-review-skill/reference/cross-cutting/error-handling-principles.md +0 -492
  124. package/assets/skills/code-review-skill/reference/cross-cutting/n-plus-one-queries.md +0 -309
  125. package/assets/skills/code-review-skill/reference/cross-cutting/sql-injection-prevention.md +0 -308
  126. package/assets/skills/code-review-skill/reference/cross-cutting/xss-prevention.md +0 -264
  127. package/assets/skills/code-review-skill/reference/csharp.md +0 -525
  128. package/assets/skills/code-review-skill/reference/css-less-sass.md +0 -661
  129. package/assets/skills/code-review-skill/reference/django.md +0 -985
  130. package/assets/skills/code-review-skill/reference/fastapi.md +0 -580
  131. package/assets/skills/code-review-skill/reference/go.md +0 -993
  132. package/assets/skills/code-review-skill/reference/java.md +0 -409
  133. package/assets/skills/code-review-skill/reference/java8.md +0 -586
  134. package/assets/skills/code-review-skill/reference/kotlin.md +0 -1018
  135. package/assets/skills/code-review-skill/reference/nestjs.md +0 -593
  136. package/assets/skills/code-review-skill/reference/performance-review-guide.md +0 -816
  137. package/assets/skills/code-review-skill/reference/php.md +0 -684
  138. package/assets/skills/code-review-skill/reference/python.md +0 -1073
  139. package/assets/skills/code-review-skill/reference/qt.md +0 -757
  140. package/assets/skills/code-review-skill/reference/react.md +0 -871
  141. package/assets/skills/code-review-skill/reference/ruby.md +0 -964
  142. package/assets/skills/code-review-skill/reference/rust.md +0 -846
  143. package/assets/skills/code-review-skill/reference/security-review-guide.md +0 -494
  144. package/assets/skills/code-review-skill/reference/svelte.md +0 -1064
  145. package/assets/skills/code-review-skill/reference/swift.md +0 -936
  146. package/assets/skills/code-review-skill/reference/typescript.md +0 -1016
  147. package/assets/skills/code-review-skill/reference/vue.md +0 -924
  148. package/assets/skills/code-review-skill/reference/zig.md +0 -440
  149. package/assets/skills/code-review-skill/scripts/pr-analyzer.py +0 -435
  150. package/assets/skills/code-review-skill/scripts/test_pr_analyzer.py +0 -380
  151. package/assets/skills/codebase-design/.crosscheck-skill.json +0 -9
  152. package/assets/skills/codebase-design/DEEPENING.md +0 -37
  153. package/assets/skills/codebase-design/DESIGN-IT-TWICE.md +0 -44
  154. package/assets/skills/codebase-design/LICENSE +0 -21
  155. package/assets/skills/codebase-design/SKILL.md +0 -114
  156. package/assets/skills/codebase-design/agents/openai.yaml +0 -3
  157. package/assets/skills/diagnosing-bugs/.crosscheck-skill.json +0 -9
  158. package/assets/skills/diagnosing-bugs/LICENSE +0 -21
  159. package/assets/skills/diagnosing-bugs/SKILL.md +0 -134
  160. package/assets/skills/diagnosing-bugs/agents/openai.yaml +0 -3
  161. package/assets/skills/diagnosing-bugs/scripts/hitl-loop.template.sh +0 -41
  162. package/dist/__tests__/can-write-verdict.test.d.ts +0 -2
  163. package/dist/__tests__/can-write-verdict.test.d.ts.map +0 -1
  164. package/dist/__tests__/can-write-verdict.test.js +0 -31
  165. package/dist/__tests__/can-write-verdict.test.js.map +0 -1
  166. package/dist/__tests__/linear-branding.test.d.ts +0 -2
  167. package/dist/__tests__/linear-branding.test.d.ts.map +0 -1
  168. package/dist/__tests__/linear-branding.test.js +0 -156
  169. package/dist/__tests__/linear-branding.test.js.map +0 -1
  170. package/dist/__tests__/linear-client.test.d.ts +0 -2
  171. package/dist/__tests__/linear-client.test.d.ts.map +0 -1
  172. package/dist/__tests__/linear-client.test.js +0 -120
  173. package/dist/__tests__/linear-client.test.js.map +0 -1
  174. package/dist/__tests__/linear-comment.test.d.ts +0 -2
  175. package/dist/__tests__/linear-comment.test.d.ts.map +0 -1
  176. package/dist/__tests__/linear-comment.test.js +0 -151
  177. package/dist/__tests__/linear-comment.test.js.map +0 -1
  178. package/dist/__tests__/linear-identity.test.d.ts +0 -2
  179. package/dist/__tests__/linear-identity.test.d.ts.map +0 -1
  180. package/dist/__tests__/linear-identity.test.js +0 -253
  181. package/dist/__tests__/linear-identity.test.js.map +0 -1
  182. package/dist/__tests__/linear-notify.test.d.ts +0 -2
  183. package/dist/__tests__/linear-notify.test.d.ts.map +0 -1
  184. package/dist/__tests__/linear-notify.test.js +0 -144
  185. package/dist/__tests__/linear-notify.test.js.map +0 -1
  186. package/dist/__tests__/linear-ref.test.d.ts +0 -2
  187. package/dist/__tests__/linear-ref.test.d.ts.map +0 -1
  188. package/dist/__tests__/linear-ref.test.js +0 -261
  189. package/dist/__tests__/linear-ref.test.js.map +0 -1
  190. package/dist/__tests__/linear-test-ref.test.d.ts +0 -2
  191. package/dist/__tests__/linear-test-ref.test.d.ts.map +0 -1
  192. package/dist/__tests__/linear-test-ref.test.js +0 -81
  193. package/dist/__tests__/linear-test-ref.test.js.map +0 -1
  194. package/dist/__tests__/linear-verify.test.d.ts +0 -2
  195. package/dist/__tests__/linear-verify.test.d.ts.map +0 -1
  196. package/dist/__tests__/linear-verify.test.js +0 -132
  197. package/dist/__tests__/linear-verify.test.js.map +0 -1
  198. package/dist/__tests__/linear-worker.test.d.ts +0 -2
  199. package/dist/__tests__/linear-worker.test.d.ts.map +0 -1
  200. package/dist/__tests__/linear-worker.test.js +0 -83
  201. package/dist/__tests__/linear-worker.test.js.map +0 -1
  202. package/dist/__tests__/linear-write-possible.test.d.ts +0 -2
  203. package/dist/__tests__/linear-write-possible.test.d.ts.map +0 -1
  204. package/dist/__tests__/linear-write-possible.test.js +0 -30
  205. package/dist/__tests__/linear-write-possible.test.js.map +0 -1
  206. package/dist/__tests__/repository-guidance.test.d.ts +0 -2
  207. package/dist/__tests__/repository-guidance.test.d.ts.map +0 -1
  208. package/dist/__tests__/repository-guidance.test.js +0 -107
  209. package/dist/__tests__/repository-guidance.test.js.map +0 -1
  210. package/dist/__tests__/review-strategy.test.d.ts +0 -2
  211. package/dist/__tests__/review-strategy.test.d.ts.map +0 -1
  212. package/dist/__tests__/review-strategy.test.js +0 -195
  213. package/dist/__tests__/review-strategy.test.js.map +0 -1
  214. package/dist/__tests__/skill-attribution.test.d.ts +0 -2
  215. package/dist/__tests__/skill-attribution.test.d.ts.map +0 -1
  216. package/dist/__tests__/skill-attribution.test.js +0 -53
  217. package/dist/__tests__/skill-attribution.test.js.map +0 -1
  218. package/dist/__tests__/skill-broker.test.d.ts +0 -2
  219. package/dist/__tests__/skill-broker.test.d.ts.map +0 -1
  220. package/dist/__tests__/skill-broker.test.js +0 -144
  221. package/dist/__tests__/skill-broker.test.js.map +0 -1
  222. package/dist/__tests__/skill-catalog.test.d.ts +0 -2
  223. package/dist/__tests__/skill-catalog.test.d.ts.map +0 -1
  224. package/dist/__tests__/skill-catalog.test.js +0 -40
  225. package/dist/__tests__/skill-catalog.test.js.map +0 -1
  226. package/dist/__tests__/skill-installer.test.d.ts +0 -2
  227. package/dist/__tests__/skill-installer.test.d.ts.map +0 -1
  228. package/dist/__tests__/skill-installer.test.js +0 -96
  229. package/dist/__tests__/skill-installer.test.js.map +0 -1
  230. package/dist/__tests__/skills-config.test.d.ts +0 -2
  231. package/dist/__tests__/skills-config.test.d.ts.map +0 -1
  232. package/dist/__tests__/skills-config.test.js +0 -12
  233. package/dist/__tests__/skills-config.test.js.map +0 -1
  234. package/dist/commands/linear-test.d.ts +0 -18
  235. package/dist/commands/linear-test.d.ts.map +0 -1
  236. package/dist/commands/linear-test.js +0 -130
  237. package/dist/commands/linear-test.js.map +0 -1
  238. package/dist/commands/skill.d.ts +0 -2
  239. package/dist/commands/skill.d.ts.map +0 -1
  240. package/dist/commands/skill.js +0 -16
  241. package/dist/commands/skill.js.map +0 -1
  242. package/dist/config/review-strategy.json +0 -202
  243. package/dist/lib/repository-guidance.d.ts +0 -2
  244. package/dist/lib/repository-guidance.d.ts.map +0 -1
  245. package/dist/lib/repository-guidance.js +0 -55
  246. package/dist/lib/repository-guidance.js.map +0 -1
  247. package/dist/lib/review-strategy.d.ts +0 -62
  248. package/dist/lib/review-strategy.d.ts.map +0 -1
  249. package/dist/lib/review-strategy.js +0 -235
  250. package/dist/lib/review-strategy.js.map +0 -1
  251. package/dist/linear/client.d.ts +0 -18
  252. package/dist/linear/client.d.ts.map +0 -1
  253. package/dist/linear/client.js +0 -67
  254. package/dist/linear/client.js.map +0 -1
  255. package/dist/linear/comment.d.ts +0 -20
  256. package/dist/linear/comment.d.ts.map +0 -1
  257. package/dist/linear/comment.js +0 -57
  258. package/dist/linear/comment.js.map +0 -1
  259. package/dist/linear/identity.d.ts +0 -59
  260. package/dist/linear/identity.d.ts.map +0 -1
  261. package/dist/linear/identity.js +0 -187
  262. package/dist/linear/identity.js.map +0 -1
  263. package/dist/linear/notify.d.ts +0 -35
  264. package/dist/linear/notify.d.ts.map +0 -1
  265. package/dist/linear/notify.js +0 -76
  266. package/dist/linear/notify.js.map +0 -1
  267. package/dist/linear/ref.d.ts +0 -13
  268. package/dist/linear/ref.d.ts.map +0 -1
  269. package/dist/linear/ref.js +0 -90
  270. package/dist/linear/ref.js.map +0 -1
  271. package/dist/linear/verify.d.ts +0 -26
  272. package/dist/linear/verify.d.ts.map +0 -1
  273. package/dist/linear/verify.js +0 -67
  274. package/dist/linear/verify.js.map +0 -1
  275. package/dist/skills/attribution.d.ts +0 -4
  276. package/dist/skills/attribution.d.ts.map +0 -1
  277. package/dist/skills/attribution.js +0 -14
  278. package/dist/skills/attribution.js.map +0 -1
  279. package/dist/skills/broker-server.d.ts +0 -2
  280. package/dist/skills/broker-server.d.ts.map +0 -1
  281. package/dist/skills/broker-server.js +0 -17
  282. package/dist/skills/broker-server.js.map +0 -1
  283. package/dist/skills/broker.d.ts +0 -42
  284. package/dist/skills/broker.d.ts.map +0 -1
  285. package/dist/skills/broker.js +0 -285
  286. package/dist/skills/broker.js.map +0 -1
  287. package/dist/skills/catalog.d.ts +0 -28
  288. package/dist/skills/catalog.d.ts.map +0 -1
  289. package/dist/skills/catalog.js +0 -104
  290. package/dist/skills/catalog.js.map +0 -1
  291. package/dist/skills/installer.d.ts +0 -10
  292. package/dist/skills/installer.d.ts.map +0 -1
  293. package/dist/skills/installer.js +0 -138
  294. package/dist/skills/installer.js.map +0 -1
  295. package/dist/skills/integrity.d.ts +0 -4
  296. package/dist/skills/integrity.d.ts.map +0 -1
  297. package/dist/skills/integrity.js +0 -36
  298. package/dist/skills/integrity.js.map +0 -1
  299. package/docs/dynamic-thoroughness.md +0 -286
  300. package/docs/linear-identity-contract.md +0 -139
  301. package/docs/linear-identity.md +0 -293
@@ -9,22 +9,15 @@ import { runFixStep, runCodexFixStep } from '../reviewers/fix.js';
9
9
  import { runConflictResolveStep, findConflictedFiles } from '../reviewers/conflict-resolve.js';
10
10
  import { parseVerdict, prependVerdictToComment, NULL_VERDICT_WARNING, applySeverityGate, SEVERITY_GATE_NOTE } from '../lib/verdict.js';
11
11
  import { createGithubClient, postReviewComment, getLastCrossCheckCommentId, getLastCrossCheckReviewComment } from '../github/client.js';
12
- import { resolveLinearAuth, withWorker } from '../linear/identity.js';
13
- import { notifyLinear } from '../linear/notify.js';
14
- import { shouldPostToLinear } from '../linear/comment.js';
15
- import { getLinearCredentials } from '../config/loader.js';
16
12
  import { acquireRemoteLock, releaseRemoteLock } from '../github/review-status.js';
17
13
  import { log as fileLog, logError, classifyError } from '../lib/logger.js';
18
14
  import { buildCommitTrailers } from '../lib/annotation.js';
19
15
  import { resolveClaudeModel, resolveCodexModel } from '../lib/review-models.js';
20
16
  import { buildStepIdentityFields } from '../lib/event-fields.js';
21
- import { buildAttributionFooter, buildFixAppliedCommentBody, buildFixFailedCommentBody, buildConflictResolvedCommentBody, buildRetriedReviewBanner } from '../lib/comment-bodies.js';
22
- import { linearWritePossible, loadWorkflow, loadHarnessSection, evaluateWhen } from '../lib/workflow.js';
17
+ import { buildFixAppliedCommentBody, buildConflictResolvedCommentBody, buildRetriedReviewBanner } from '../lib/comment-bodies.js';
18
+ import { loadWorkflow, loadHarnessSection, evaluateWhen } from '../lib/workflow.js';
23
19
  import { isSubscriptionLimitError, isVendorUnavailableError } from '../lib/smart-switch.js';
24
20
  import { tierTimeoutMs } from '../reviewers/tier-timeouts.js';
25
- import { loadSkillCatalog } from '../skills/catalog.js';
26
- import { createSkillActivationSession } from '../skills/broker.js';
27
- import { formatSkillAttribution } from '../skills/attribution.js';
28
21
  const MAX_CROSSCHECK_COMMITS = 5;
29
22
  const FIX_RETRY_DELAY_MS = 2 * 60 * 1000;
30
23
  const REVIEW_RETRY_DELAY_MS = 2 * 60 * 1000;
@@ -56,27 +49,33 @@ function isTransientApiError(err) {
56
49
  export function getEffectiveStepType(stepType, isRecheckRun) {
57
50
  return stepType === 'review' && isRecheckRun ? 'recheck' : stepType;
58
51
  }
59
- export function countCrosscheckCommitsForPRDetailed(tmpDir, baseRef) {
52
+ // Counts crosscheck-authored commits unique to this PR (ahead of base) rather
53
+ // than the branch's full history. Long-lived integration branches like
54
+ // `staging` accumulate [crosscheck] commits from many merged PRs — counting
55
+ // those would trip the per-PR fix-loop guard immediately and skip fix/recheck.
56
+ //
57
+ // Fails closed: when `origin/<base>` isn't available (e.g. clone fetched the
58
+ // base ref with `base_branch_fetch_skipped`), fall back to the full-history
59
+ // count rather than returning 0. Over-counting can stop fix early; returning 0
60
+ // would silently disable the cap and let runaway fix loops keep pushing.
61
+ export function countCrosscheckCommitsForPR(tmpDir, baseRef) {
60
62
  const runLog = (args) => execFileSync('git', ['log', '--oneline', ...args], { cwd: tmpDir, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] });
61
63
  const count = (out) => out.split('\n').filter(l => l.includes('[crosscheck]')).length;
62
64
  try {
63
- return { count: count(runLog([`origin/${baseRef}..HEAD`])), scoped: true };
65
+ return count(runLog([`origin/${baseRef}..HEAD`]));
64
66
  }
65
67
  catch {
66
68
  // Scoped range unavailable — fall back to full history so the cap still
67
- // applies. Over-counts when the branch has prior merged crosscheck commits,
68
- // but that's preferable to bypassing the safety guard.
69
+ // applies. May over-count when the branch has prior merged crosscheck
70
+ // commits, but that's preferable to bypassing the safety guard.
69
71
  try {
70
- return { count: count(runLog([])), scoped: false };
72
+ return count(runLog([]));
71
73
  }
72
74
  catch {
73
- return { count: 0, scoped: false };
75
+ return 0;
74
76
  }
75
77
  }
76
78
  }
77
- export function countCrosscheckCommitsForPR(tmpDir, baseRef) {
78
- return countCrosscheckCommitsForPRDetailed(tmpDir, baseRef).count;
79
- }
80
79
  export function buildWorkflowCompleteEvent(inputs) {
81
80
  const stepValues = Object.values(inputs.results);
82
81
  const lastVerdict = stepValues.reverse().find(r => r.verdict !== undefined)?.verdict ?? null;
@@ -357,47 +356,6 @@ export async function runWorkflow(ctx) {
357
356
  // can iterate the same list and release these shas if SIGINT/SIGTERM fires
358
357
  // mid-workflow (process.exit there bypasses our finally below).
359
358
  const pushedShasNeedingRelease = ctx.pushedShas ?? [];
360
- const skillCatalog = config.skills.enabled.length > 0 ? loadSkillCatalog() : [];
361
- const skillSessions = new Map();
362
- const skillSessionFor = (stepName, stepType) => {
363
- if (skillCatalog.length === 0)
364
- return undefined;
365
- const existing = skillSessions.get(stepName);
366
- if (existing)
367
- return existing;
368
- const session = createSkillActivationSession(stepType, config.skills.enabled, skillCatalog);
369
- skillSessions.set(stepName, session);
370
- return session;
371
- };
372
- // Skills were offered and the agent took none. Silent before, which is how a
373
- // prompt that never triggered activation ran unnoticed for 336 steps.
374
- // `skills_activated: []` on the *_complete events says something similar but
375
- // quietly; what is new here is `enabled` (what was actually on offer) and the
376
- // warn level, which makes a step that activates nothing greppable on its own.
377
- const logSkillsNoneActivated = (session, identity) => {
378
- // skillSessionFor hands back a session whenever the catalog is non-empty,
379
- // but createSkillActivationSession filters that catalog down to
380
- // config.skills.enabled — so a configured name that never resolved (typo,
381
- // skill not installed) leaves enabledSkills empty, and then
382
- // renderSkillBrokerInstructions renders nothing at all. Nothing was offered,
383
- // so nothing was refused: that is broken config, not agent non-compliance,
384
- // and logging it here would conflate the two causes the event exists to
385
- // tell apart.
386
- if (session.enabledSkills.length === 0)
387
- return;
388
- fileLog({ level: 'warn', event: 'skills_none_activated', repo: `${owner}/${repoName}`, pr: prNumber, ...identity, enabled: session.enabledSkills.map(skill => skill.name) });
389
- };
390
- // Linear write-back identity. Resolved up front — before any expensive step —
391
- // and allowed to throw. The contract is that a configured-but-failing
392
- // client_credentials mint ABORTS rather than degrading, because silently
393
- // continuing would either drop the write or re-attribute it to a human. This
394
- // matches commands/review.ts; the two paths must not disagree.
395
- // The contract is one token per command run. runWorkflow is re-entered for every
396
- // fix/recheck round under --crazy and max_rounds, so minting here would mint per
397
- // round and let a late transient failure abort work already done. The caller
398
- // resolves once and passes it in; resolving here is the single-round fallback.
399
- // A dry run posts nothing, so it never mints.
400
- let linearAuth = ctx.linearAuth ?? null;
401
359
  let workflowFailed = false;
402
360
  let workflowError = undefined;
403
361
  let failedStep = undefined;
@@ -428,16 +386,6 @@ export async function runWorkflow(ctx) {
428
386
  let currentStepName;
429
387
  emitPRComplexity(ctx, triggerField);
430
388
  try {
431
- // Inside the try so a preflight failure still reaches the completion handler in
432
- // the finally — resolving above it meant a failed mint skipped workflow_complete
433
- // entirely and left no record of the run.
434
- // canWriteVerdict here too: the caller passes null deliberately when the selected
435
- // steps cannot write a verdict, and this fallback previously read that as
436
- // "unresolved" and resolved anyway — defeating the gate one line upstream.
437
- if (!ctx.dryRun && !linearAuth && linearWritePossible(config.linear, steps)) {
438
- linearAuth = await resolveLinearAuth(config.linear, getLinearCredentials(config.linear.auth));
439
- fileLog({ level: 'info', event: 'linear_auth_resolved', repo: `${owner}/${repoName}`, pr: prNumber, mode: linearAuth.mode, actor: linearAuth.actor });
440
- }
441
389
  for (const step of steps) {
442
390
  currentStepName = step.name;
443
391
  stepsRun.push(step.name);
@@ -500,19 +448,17 @@ export async function runWorkflow(ctx) {
500
448
  let inputTokens;
501
449
  let outputTokens;
502
450
  let model = 'default';
503
- let effort;
504
451
  let retried;
505
- const skillSession = skillSessionFor(step.name, effectiveType);
506
452
  const runReviewWithVendor = async (candidate) => {
507
453
  if (candidate === 'codex') {
508
454
  ;
509
- ({ review: rawReview, tokensUsed, model, effort, retried } = await runCodexReview(tmpDir, pr.base.ref, pr.title, config.quality, config.vendors.codex, step.instructions, undefined, ctx.overrideTimeoutMs ?? vendorTimeoutMs(config.vendors.codex.timeout_sec), log, ctx.issueContext, skillSession));
455
+ ({ review: rawReview, tokensUsed, model, retried } = await runCodexReview(tmpDir, pr.base.ref, pr.title, config.quality, config.vendors.codex, step.instructions, undefined, ctx.overrideTimeoutMs ?? vendorTimeoutMs(config.vendors.codex.timeout_sec), log, ctx.issueContext));
510
456
  inputTokens = undefined;
511
457
  outputTokens = undefined;
512
458
  }
513
459
  else {
514
460
  ;
515
- ({ review: rawReview, tokensUsed, inputTokens, outputTokens, model, effort, retried } = await runClaudeReview(tmpDir, pr.base.ref, pr.title, config.quality, config.vendors.claude, config.budget.per_review_usd, step.instructions, undefined, ctx.overrideTimeoutMs ?? vendorTimeoutMs(config.vendors.claude.timeout_sec), !!ctx.roundMode, log, ctx.issueContext, skillSession));
461
+ ({ review: rawReview, tokensUsed, inputTokens, outputTokens, model, retried } = await runClaudeReview(tmpDir, pr.base.ref, pr.title, config.quality, config.vendors.claude, config.budget.per_review_usd, step.instructions, undefined, ctx.overrideTimeoutMs ?? vendorTimeoutMs(config.vendors.claude.timeout_sec), !!ctx.roundMode, log, ctx.issueContext));
516
462
  }
517
463
  };
518
464
  try {
@@ -569,11 +515,6 @@ export async function runWorkflow(ctx) {
569
515
  }
570
516
  }
571
517
  }
572
- const activatedSkills = skillSession?.activations() ?? [];
573
- if (activatedSkills.length > 0)
574
- log(chalk.dim(` skills: ${formatSkillAttribution(activatedSkills)}`));
575
- else if (skillSession)
576
- logSkillsNoneActivated(skillSession, stepIdentity);
577
518
  // First attempt timed out but the delayed retry succeeded — surface a
578
519
  // soft notice on the review comment so the author knows it was a transient blip.
579
520
  if (retried) {
@@ -595,13 +536,11 @@ export async function runWorkflow(ctx) {
595
536
  const baseBody = verdict === null
596
537
  ? `${NULL_VERDICT_WARNING}\n\n${clean}`
597
538
  : prependVerdictToComment(gate.downgraded ? `${SEVERITY_GATE_NOTE}\n\n${clean}` : clean, verdict);
598
- // Skills are not folded into the body — postReviewComment renders the
599
- // receipt beneath the attribution footer.
600
539
  const commentBody = retried
601
540
  ? `${buildRetriedReviewBanner(retried.timeoutMs, retried.delayMs)}\n\n${baseBody}`
602
541
  : baseBody;
603
542
  const commentCount = countComments(rawReview);
604
- fileLog({ level: 'info', event: 'review_complete', repo: `${owner}/${repoName}`, pr: prNumber, reviewer, model, ...stepIdentity, verdict, duration_ms: Date.now() - stepStart, tokens_used: tokensUsed, skills_activated: activatedSkills.map(skill => skill.name), ...(inputTokens !== undefined && { input_tokens: inputTokens }), ...(outputTokens !== undefined && { output_tokens: outputTokens }), ...(ctx.round !== undefined && { round: ctx.round }), ...(ctx.roundMode && { mode: ctx.roundMode }), ...triggerField });
543
+ fileLog({ level: 'info', event: 'review_complete', repo: `${owner}/${repoName}`, pr: prNumber, reviewer, model, ...stepIdentity, verdict, duration_ms: Date.now() - stepStart, tokens_used: tokensUsed, ...(inputTokens !== undefined && { input_tokens: inputTokens }), ...(outputTokens !== undefined && { output_tokens: outputTokens }), ...(ctx.round !== undefined && { round: ctx.round }), ...(ctx.roundMode && { mode: ctx.roundMode }), ...triggerField });
605
544
  // Recheck verdict is stored separately to preserve the original review's commentCount on the board
606
545
  const phaseUpdate = isRecheck
607
546
  ? { recheckVerdict: verdict, phase: donePhase, recheckTokens: tokensUsed, recheckReviewer: reviewer, qualityTier: config.quality.tier }
@@ -640,41 +579,9 @@ export async function runWorkflow(ctx) {
640
579
  annotationSha = execSync('git rev-parse HEAD', { cwd: tmpDir, encoding: 'utf8' }).trim();
641
580
  }
642
581
  catch { /* fall back to pr.head.sha if git is unavailable */ }
643
- const commentId = await postReviewComment(octokit, owner, repoName, prNumber, commentBody, reviewer, config.brand, origin, verdict ?? undefined, priorReviewId, isRecheck, model, effectiveType, ctx.round ?? 1, annotationSha, nextStepAnnotation, ctx.trigger === 'kickass' ? 'kickass' : undefined, activatedSkills, effort);
582
+ const commentId = await postReviewComment(octokit, owner, repoName, prNumber, commentBody, reviewer, config.brand, origin, verdict ?? undefined, priorReviewId, isRecheck, model, effectiveType, ctx.round ?? 1, annotationSha, nextStepAnnotation, ctx.trigger === 'kickass' ? 'kickass' : undefined);
644
583
  const commentUrl = `github.com/${owner}/${repoName}/pull/${prNumber}`;
645
584
  fileLog({ level: 'info', event: 'comment_posted', repo: `${owner}/${repoName}`, pr: prNumber, url: `https://${commentUrl}` });
646
- // Mirror the verdict onto the PR's Linear issue. `run` and `watch` both
647
- // land here, so this is the path that matters — reviews posted from
648
- // commands/review.ts are the exception, not the rule.
649
- if (linearAuth && shouldPostToLinear(verdict ?? null, config.linear.comment_on)) {
650
- {
651
- // Attribute to crosscheck/review, /fix, /recheck rather than a flat actor.
652
- const stepAuth = config.linear.identity.per_step_actor ? withWorker(linearAuth, effectiveType) : linearAuth;
653
- const linearResult = await notifyLinear({
654
- auth: stepAuth,
655
- config: config.linear,
656
- pr: { branch: pr.head.ref, title: pr.title, body: pr.body ?? '', url: `https://${commentUrl}`, sha: annotationSha },
657
- verdict: verdict ?? null,
658
- reviewer,
659
- origin,
660
- model,
661
- stepType: effectiveType,
662
- round: ctx.round ?? 1,
663
- service: config.brand.service_name,
664
- });
665
- fileLog({
666
- level: linearResult.status === 'failed' ? 'warn' : 'info',
667
- event: 'linear_comment', repo: `${owner}/${repoName}`, pr: prNumber,
668
- status: linearResult.status, reason: linearResult.reason, issue: linearResult.identifier,
669
- });
670
- if (linearResult.status === 'posted') {
671
- log(chalk.dim(` linear: commented on ${linearResult.identifier}`));
672
- }
673
- else if (linearResult.status === 'failed') {
674
- log(chalk.yellow(` linear: write failed — ${linearResult.reason}`));
675
- }
676
- }
677
- }
678
585
  results[step.name] = { verdict, commentBody, commentUrl, commentId, tokens_used: tokensUsed, input_tokens: inputTokens, output_tokens: outputTokens, vendor: reviewer, model };
679
586
  }
680
587
  }
@@ -740,16 +647,11 @@ export async function runWorkflow(ctx) {
740
647
  // Scope to commits ahead of base so long-lived branches (e.g. staging)
741
648
  // don't count [crosscheck] commits from previously merged PRs.
742
649
  // Crazy/halfcrazy mode doubles the cap since it deliberately loops.
743
- const commitCount = countCrosscheckCommitsForPRDetailed(tmpDir, pr.base.ref);
650
+ const existingCount = countCrosscheckCommitsForPR(tmpDir, pr.base.ref);
744
651
  const effectiveCommitLimit = ctx.roundMode ? MAX_CROSSCHECK_COMMITS * 2 : MAX_CROSSCHECK_COMMITS;
745
- if (commitCount.count >= effectiveCommitLimit) {
746
- // Report the count, not the limit. An unscoped count means origin/<base>
747
- // was missing, so this is an over-count from whole-repo history rather
748
- // than a real cap hit — say so, or the next person debugs the wrong thing.
749
- log(commitCount.scoped
750
- ? chalk.yellow(`⚠ PR #${prNumber}: ${commitCount.count}/${effectiveCommitLimit} [crosscheck] commits already — stopping auto-fix`)
751
- : chalk.yellow(`⚠ PR #${prNumber}: cannot scope [crosscheck] commit count (origin/${pr.base.ref} missing; ${commitCount.count} across all history) — stopping auto-fix`));
752
- skipFix(commitCount.scoped ? 'commit_limit_reached' : 'commit_count_unscoped');
652
+ if (existingCount >= effectiveCommitLimit) {
653
+ log(chalk.yellow(`⚠ PR #${prNumber}: ${effectiveCommitLimit} [crosscheck] commits already stopping auto-fix`));
654
+ skipFix('commit_limit_reached');
753
655
  continue;
754
656
  }
755
657
  onPhaseChange(`${vendor} fixing...`, { phase: 'fixing' });
@@ -757,20 +659,18 @@ export async function runWorkflow(ctx) {
757
659
  let appliedCount = 0;
758
660
  let fixChangedFiles = [];
759
661
  let fixTokensUsed;
760
- let fixEffort;
761
662
  let fixErr = undefined;
762
663
  let activeVendor = vendor;
763
664
  const tierMs = tierTimeoutMs(config.quality.tier);
764
- const skillSession = skillSessionFor(step.name, effectiveType);
765
665
  const runFix = async (v) => {
766
666
  if (v === 'codex') {
767
- return runCodexFixStep(tmpDir, pr.base.ref, pr.title, reviewCommentBody, step.instructions ?? '', codexFixModel, ctx.overrideTimeoutMs ?? vendorTimeoutMs(config.vendors.codex.timeout_sec) ?? tierMs, skillSession, config.vendors.codex.effort);
667
+ return runCodexFixStep(tmpDir, pr.base.ref, pr.title, reviewCommentBody, step.instructions ?? '', codexFixModel, ctx.overrideTimeoutMs ?? vendorTimeoutMs(config.vendors.codex.timeout_sec) ?? tierMs);
768
668
  }
769
- return runFixStep(tmpDir, pr.base.ref, pr.title, reviewCommentBody, step.instructions ?? '', config, claudeFixModel, ctx.overrideTimeoutMs ?? vendorTimeoutMs(config.vendors.claude.timeout_sec) ?? tierMs, skillSession);
669
+ return runFixStep(tmpDir, pr.base.ref, pr.title, reviewCommentBody, step.instructions ?? '', config, claudeFixModel, ctx.overrideTimeoutMs ?? vendorTimeoutMs(config.vendors.claude.timeout_sec) ?? tierMs);
770
670
  };
771
671
  try {
772
672
  ;
773
- ({ appliedCount, changedFiles: fixChangedFiles, tokensUsed: fixTokensUsed, effort: fixEffort } = await runFix(vendor));
673
+ ({ appliedCount, changedFiles: fixChangedFiles, tokensUsed: fixTokensUsed } = await runFix(vendor));
774
674
  }
775
675
  catch (err) {
776
676
  logError({ repo: `${owner}/${repoName}`, pr: prNumber, phase: 'fix', attempt: 1, vendor }, err);
@@ -784,7 +684,7 @@ export async function runWorkflow(ctx) {
784
684
  fileLog({ level: 'warn', event: 'fix_vendor_fallback', repo: `${owner}/${repoName}`, pr: prNumber, from: vendor, to: fallbackVendor, ...(isSubscriptionLimitError(err) && { reason: 'vendor_limit' }) });
785
685
  try {
786
686
  ;
787
- ({ appliedCount, changedFiles: fixChangedFiles, tokensUsed: fixTokensUsed, effort: fixEffort } = await runFix(fallbackVendor));
687
+ ({ appliedCount, changedFiles: fixChangedFiles, tokensUsed: fixTokensUsed } = await runFix(fallbackVendor));
788
688
  activeVendor = fallbackVendor;
789
689
  }
790
690
  catch (fallbackErr) {
@@ -805,7 +705,7 @@ export async function runWorkflow(ctx) {
805
705
  onPhaseChange(`${activeVendor} fixing (retry)...`, { phase: 'fixing' });
806
706
  try {
807
707
  ;
808
- ({ appliedCount, changedFiles: fixChangedFiles, tokensUsed: fixTokensUsed, effort: fixEffort } = await runFix(activeVendor));
708
+ ({ appliedCount, changedFiles: fixChangedFiles, tokensUsed: fixTokensUsed } = await runFix(activeVendor));
809
709
  fileLog({ level: 'info', event: 'fix_retry_succeeded', repo: `${owner}/${repoName}`, pr: prNumber });
810
710
  fixErr = undefined;
811
711
  }
@@ -814,20 +714,6 @@ export async function runWorkflow(ctx) {
814
714
  fixErr = retryErr;
815
715
  }
816
716
  }
817
- const activatedSkills = skillSession?.activations() ?? [];
818
- if (activatedSkills.length > 0)
819
- log(chalk.dim(` skills: ${formatSkillAttribution(activatedSkills)}`));
820
- else if (skillSession)
821
- logSkillsNoneActivated(skillSession, { step_type: 'fix', step_name: step.name });
822
- // Every delivery mode (commit card, fix PR, suggested-diff comment) closes
823
- // with the same footer as a review comment.
824
- const fixAttributionFooter = () => buildAttributionFooter({
825
- action: 'Fixed',
826
- vendor: activeVendor,
827
- model: activeVendor === 'codex' ? codexFixModel : claudeFixModel,
828
- effort: fixEffort,
829
- skills: activatedSkills,
830
- });
831
717
  if (fixErr !== undefined) {
832
718
  skipFix(isSubscriptionLimitError(fixErr) ? 'vendor_limit' : 'fix_error');
833
719
  // Only notify for transient failures — auth errors are operator issues, not PR author issues
@@ -836,13 +722,7 @@ export async function runWorkflow(ctx) {
836
722
  const octokit = createGithubClient(token);
837
723
  await octokit.rest.issues.createComment({
838
724
  owner, repo: repoName, issue_number: prNumber,
839
- body: buildFixFailedCommentBody({
840
- prUrl: pr.html_url,
841
- vendor: activeVendor,
842
- model: activeVendor === 'codex' ? codexFixModel : claudeFixModel,
843
- effort: fixEffort,
844
- skills: activatedSkills,
845
- }),
725
+ body: `⚠️ **Auto-fix failed**\n\nThe fix step timed out after retrying. Push a new commit or run \`crosscheck run ${pr.html_url}\` to retry manually.\n\n<!-- crosscheck: fix_failed -->`,
846
726
  });
847
727
  fileLog({ level: 'info', event: 'fix_failed_comment_posted', repo: `${owner}/${repoName}`, pr: prNumber });
848
728
  }
@@ -861,7 +741,7 @@ export async function runWorkflow(ctx) {
861
741
  // event — NOT `fix_complete` — so status consumers (WORKFLOW_ACTIVITY_EVENTS,
862
742
  // the legacy status fold) don't misread a no-op as real workflow progress and
863
743
  // mark the PR NEEDS_RECHECK or hide it from `scan --tidy` with no fix applied.
864
- fileLog({ level: 'info', event: 'fix_noop', repo: `${owner}/${repoName}`, pr: prNumber, vendor: activeVendor, applied_count: 0, no_changes: true, tokens_used: fixTokensUsed, skills_activated: activatedSkills.map(skill => skill.name), duration_ms: Date.now() - fixStepStart, ...triggerField });
744
+ fileLog({ level: 'info', event: 'fix_noop', repo: `${owner}/${repoName}`, pr: prNumber, vendor: activeVendor, applied_count: 0, no_changes: true, tokens_used: fixTokensUsed, duration_ms: Date.now() - fixStepStart, ...triggerField });
865
745
  results[step.name] = { applied_count: 0, ...(fixTokensUsed !== undefined && { tokens_used: fixTokensUsed }), vendor };
866
746
  continue;
867
747
  }
@@ -913,7 +793,7 @@ export async function runWorkflow(ctx) {
913
793
  }
914
794
  }
915
795
  onPhaseChange('fixed ✓', { fixCount: appliedCount, phase: 'fixed', fixTokens: fixTokensUsed });
916
- fileLog({ level: 'info', event: 'fix_complete', repo: `${owner}/${repoName}`, pr: prNumber, vendor: activeVendor, applied_count: appliedCount, sha: newSha, delivery: 'commit', tokens_used: fixTokensUsed, skills_activated: activatedSkills.map(skill => skill.name), duration_ms: Date.now() - fixStepStart, ...triggerField });
796
+ fileLog({ level: 'info', event: 'fix_complete', repo: `${owner}/${repoName}`, pr: prNumber, vendor: activeVendor, applied_count: appliedCount, sha: newSha, delivery: 'commit', tokens_used: fixTokensUsed, duration_ms: Date.now() - fixStepStart, ...triggerField });
917
797
  // Post a summary comment so the silent commit push is visible on the timeline
918
798
  // as a comment card. Best-effort — a failure here must not fail the run.
919
799
  try {
@@ -924,9 +804,6 @@ export async function runWorkflow(ctx) {
924
804
  changedFiles: fixChangedFiles,
925
805
  vendor: activeVendor,
926
806
  reviewCommentBody,
927
- model: activeVendor === 'codex' ? codexFixModel : claudeFixModel,
928
- effort: fixEffort,
929
- skills: activatedSkills,
930
807
  });
931
808
  await octokit.rest.issues.createComment({ owner, repo: repoName, issue_number: prNumber, body });
932
809
  fileLog({ level: 'info', event: 'fix_applied_comment_posted', repo: `${owner}/${repoName}`, pr: prNumber, sha: newSha });
@@ -963,13 +840,7 @@ export async function runWorkflow(ctx) {
963
840
  head: fixBranch,
964
841
  base: pr.head.ref,
965
842
  title: fixPrTitle,
966
- body: [
967
- `Auto-fix by crosscheck for CR issues found in #${prNumber}.`,
968
- '',
969
- `Review: https://github.com/${owner}/${repoName}/pull/${prNumber}`,
970
- '',
971
- fixAttributionFooter(),
972
- ].join('\n'),
843
+ body: `Auto-fix by crosscheck for CR issues found in #${prNumber}.\n\nReview: https://github.com/${owner}/${repoName}/pull/${prNumber}`,
973
844
  });
974
845
  if (config.post_review.auto_fix.delivery.label) {
975
846
  try {
@@ -980,7 +851,7 @@ export async function runWorkflow(ctx) {
980
851
  catch { /* label may not exist in this repo — skip */ }
981
852
  }
982
853
  onPhaseChange('fixed ✓', { fixCount: appliedCount, phase: 'fixed', fixTokens: fixTokensUsed });
983
- fileLog({ level: 'info', event: 'fix_complete', repo: `${owner}/${repoName}`, pr: prNumber, vendor: activeVendor, applied_count: appliedCount, sha: newSha, delivery: 'pull_request', fix_pr: fixPr.number, tokens_used: fixTokensUsed, skills_activated: activatedSkills.map(skill => skill.name), duration_ms: Date.now() - fixStepStart, ...triggerField });
854
+ fileLog({ level: 'info', event: 'fix_complete', repo: `${owner}/${repoName}`, pr: prNumber, vendor: activeVendor, applied_count: appliedCount, sha: newSha, delivery: 'pull_request', fix_pr: fixPr.number, tokens_used: fixTokensUsed, duration_ms: Date.now() - fixStepStart, ...triggerField });
984
855
  results[step.name] = { applied_count: appliedCount, tokens_used: fixTokensUsed, vendor: activeVendor };
985
856
  }
986
857
  else {
@@ -992,11 +863,11 @@ export async function runWorkflow(ctx) {
992
863
  catch { /* ignore */ }
993
864
  if (patch) {
994
865
  const octokit = createGithubClient(token);
995
- const body = `### Suggested fixes (crosscheck auto-fix)\n\n\`\`\`diff\n${patch.slice(0, 16000)}\n\`\`\`\n\n${fixAttributionFooter()}`;
866
+ const body = `### Suggested fixes (crosscheck auto-fix)\n\n\`\`\`diff\n${patch.slice(0, 16000)}\n\`\`\``;
996
867
  await octokit.rest.issues.createComment({ owner, repo: repoName, issue_number: prNumber, body });
997
868
  }
998
869
  onPhaseChange('fixed ✓', { fixCount: appliedCount, phase: 'fixed', fixTokens: fixTokensUsed });
999
- fileLog({ level: 'info', event: 'fix_complete', repo: `${owner}/${repoName}`, pr: prNumber, vendor: activeVendor, applied_count: appliedCount, delivery: 'comment', tokens_used: fixTokensUsed, skills_activated: activatedSkills.map(skill => skill.name), duration_ms: Date.now() - fixStepStart, ...triggerField });
870
+ fileLog({ level: 'info', event: 'fix_complete', repo: `${owner}/${repoName}`, pr: prNumber, vendor: activeVendor, applied_count: appliedCount, delivery: 'comment', tokens_used: fixTokensUsed, duration_ms: Date.now() - fixStepStart, ...triggerField });
1000
871
  results[step.name] = { applied_count: appliedCount, tokens_used: fixTokensUsed, vendor: activeVendor };
1001
872
  }
1002
873
  }
@@ -1080,15 +951,13 @@ export async function runWorkflow(ctx) {
1080
951
  skipConflictResolve('fork_pr');
1081
952
  continue;
1082
953
  }
1083
- const crCommitCount = countCrosscheckCommitsForPRDetailed(tmpDir, pr.base.ref);
1084
- if (crCommitCount.count >= MAX_CROSSCHECK_COMMITS) {
954
+ const existingCount = countCrosscheckCommitsForPR(tmpDir, pr.base.ref);
955
+ if (existingCount >= MAX_CROSSCHECK_COMMITS) {
1085
956
  try {
1086
957
  execSync('git merge --abort', { cwd: tmpDir });
1087
958
  }
1088
959
  catch { /* ignore */ }
1089
- log(crCommitCount.scoped
1090
- ? chalk.yellow(`⚠ PR #${prNumber}: ${crCommitCount.count}/${MAX_CROSSCHECK_COMMITS} [crosscheck] commits already — stopping conflict-resolve`)
1091
- : chalk.yellow(`⚠ PR #${prNumber}: cannot scope [crosscheck] commit count (origin/${pr.base.ref} missing) — stopping conflict-resolve`));
960
+ log(chalk.yellow(`⚠ PR #${prNumber}: ${MAX_CROSSCHECK_COMMITS} [crosscheck] commits already — stopping conflict-resolve`));
1092
961
  skipConflictResolve('commit_limit_reached');
1093
962
  continue;
1094
963
  }
@@ -1098,11 +967,9 @@ export async function runWorkflow(ctx) {
1098
967
  let appliedCount = 0;
1099
968
  let resolvedPaths = [];
1100
969
  let resolveTokensUsed;
1101
- let resolveEffort;
1102
- const skillSession = skillSessionFor(step.name, effectiveType);
1103
970
  try {
1104
971
  ;
1105
- ({ appliedCount, resolvedPaths, tokensUsed: resolveTokensUsed, effort: resolveEffort } = await runConflictResolveStep(tmpDir, pr.title, step.instructions ?? '', conflictResolveModel, ctx.overrideTimeoutMs ?? vendorTimeoutMs(config.vendors.claude.timeout_sec), skillSession, config.vendors.claude.effort));
972
+ ({ appliedCount, resolvedPaths, tokensUsed: resolveTokensUsed } = await runConflictResolveStep(tmpDir, pr.title, step.instructions ?? '', conflictResolveModel, ctx.overrideTimeoutMs ?? vendorTimeoutMs(config.vendors.claude.timeout_sec)));
1106
973
  }
1107
974
  catch (err) {
1108
975
  logError({ repo: `${owner}/${repoName}`, pr: prNumber, phase: 'conflict-resolve', attempt: 1 }, err);
@@ -1113,11 +980,6 @@ export async function runWorkflow(ctx) {
1113
980
  skipConflictResolve(isSubscriptionLimitError(err) ? 'vendor_limit' : 'resolve_error');
1114
981
  continue;
1115
982
  }
1116
- const activatedSkills = skillSession?.activations() ?? [];
1117
- if (activatedSkills.length > 0)
1118
- log(chalk.dim(` skills: ${formatSkillAttribution(activatedSkills)}`));
1119
- else if (skillSession)
1120
- logSkillsNoneActivated(skillSession, { step_type: 'conflict-resolve', step_name: step.name });
1121
983
  if (appliedCount === 0) {
1122
984
  try {
1123
985
  execSync('git merge --abort', { cwd: tmpDir });
@@ -1220,7 +1082,7 @@ export async function runWorkflow(ctx) {
1220
1082
  fileLog({ level: 'warn', event: 'remote_lock_refresh_failed', repo: `${owner}/${repoName}`, pr: prNumber, sha: newSha, error: err instanceof Error ? err.message : String(err) });
1221
1083
  }
1222
1084
  onPhaseChange('conflicts resolved ✓', { fixCount: appliedCount, phase: 'fixed', fixTokens: resolveTokensUsed });
1223
- fileLog({ level: 'info', event: 'conflict_resolve_complete', repo: `${owner}/${repoName}`, pr: prNumber, vendor, conflicts_resolved: conflictedFiles.length, sha: newSha, tokens_used: resolveTokensUsed, skills_activated: activatedSkills.map(skill => skill.name), duration_ms: Date.now() - conflictResolveStepStart, ...triggerField });
1085
+ fileLog({ level: 'info', event: 'conflict_resolve_complete', repo: `${owner}/${repoName}`, pr: prNumber, vendor, conflicts_resolved: conflictedFiles.length, sha: newSha, tokens_used: resolveTokensUsed, duration_ms: Date.now() - conflictResolveStepStart, ...triggerField });
1224
1086
  // Post a summary comment so the silent merge-commit push is visible on the
1225
1087
  // timeline as a comment card. Best-effort — a failure here must not fail the run.
1226
1088
  // Prefer the resolver's actual rewrite set; fall back to the originally-conflicted
@@ -1231,9 +1093,6 @@ export async function runWorkflow(ctx) {
1231
1093
  owner, repo: repoName, sha: newSha,
1232
1094
  conflictCount: conflictedFiles.length,
1233
1095
  files: resolvedPaths.length > 0 ? resolvedPaths : conflictedFiles,
1234
- model: conflictResolveModel,
1235
- effort: resolveEffort,
1236
- skills: activatedSkills,
1237
1096
  });
1238
1097
  await octokit.rest.issues.createComment({ owner, repo: repoName, issue_number: prNumber, body });
1239
1098
  fileLog({ level: 'info', event: 'conflict_resolved_comment_posted', repo: `${owner}/${repoName}`, pr: prNumber, sha: newSha });
@@ -1270,9 +1129,6 @@ export async function runWorkflow(ctx) {
1270
1129
  throw err;
1271
1130
  }
1272
1131
  finally {
1273
- for (const session of skillSessions.values())
1274
- session.close();
1275
- skillSessions.clear();
1276
1132
  if (pushedShasNeedingRelease.length > 0 || fixPushedShaRequiresRecheck !== null) {
1277
1133
  const lockOctokit = createGithubClient(token);
1278
1134
  const outcome = workflowFailed ? 'failure' : 'success';