@geminix/gxpm 0.1.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 (299) hide show
  1. package/AGENTS.md +148 -0
  2. package/CANON.md +53 -0
  3. package/CLAUDE.md +60 -0
  4. package/CONTEXT.md +49 -0
  5. package/DEBUG.md +59 -0
  6. package/ISSUE_CONTEXT.md +25 -0
  7. package/README.md +143 -0
  8. package/VERSION +1 -0
  9. package/agents/cleanup-auditor/cleanup-auditor.md +56 -0
  10. package/agents/grill-master.md +26 -0
  11. package/agents/implementer.md +32 -0
  12. package/agents/review-army/accessibility-reviewer.md +54 -0
  13. package/agents/review-army/code-quality-reviewer.md +54 -0
  14. package/agents/review-army/security-reviewer.md +56 -0
  15. package/agents/review-army/spec-compliance-reviewer.md +51 -0
  16. package/agents/review-army/test-reviewer.md +55 -0
  17. package/agents/reviewer.md +59 -0
  18. package/agents/ship-audit-army/docs-auditor.md +53 -0
  19. package/agents/ship-audit-army/performance-auditor.md +52 -0
  20. package/agents/ship-audit-army/security-auditor.md +52 -0
  21. package/agents/specifier.md +55 -0
  22. package/agents/triage-officer.md +27 -0
  23. package/bin/gxpm +17 -0
  24. package/bin/gxpm-browser +17 -0
  25. package/bin/gxpm-config +15 -0
  26. package/bin/gxpm-eval +13 -0
  27. package/bin/gxpm-global-discover +15 -0
  28. package/bin/gxpm-init +38 -0
  29. package/bin/gxpm-investigate +194 -0
  30. package/bin/gxpm-uninstall +15 -0
  31. package/bin/gxpm-update-check +165 -0
  32. package/commands/build.md +40 -0
  33. package/commands/help.md +53 -0
  34. package/commands/plan.md +34 -0
  35. package/commands/refine.md +46 -0
  36. package/commands/review.md +34 -0
  37. package/commands/ship.md +37 -0
  38. package/core/ac-check.ts +20 -0
  39. package/core/agent-runtime.ts +363 -0
  40. package/core/artifact-validator.ts +151 -0
  41. package/core/artifacts.ts +313 -0
  42. package/core/autopilot.ts +250 -0
  43. package/core/capabilities.ts +779 -0
  44. package/core/checkpoint.ts +370 -0
  45. package/core/cleanup.ts +32 -0
  46. package/core/command-probe.ts +82 -0
  47. package/core/config.ts +533 -0
  48. package/core/contracts/behavior-spec.schema.ts +38 -0
  49. package/core/contracts/converter.ts +61 -0
  50. package/core/contracts/host.ts +43 -0
  51. package/core/converters/converter.ts +93 -0
  52. package/core/converters/index.ts +8 -0
  53. package/core/converters/managed-artifact.ts +119 -0
  54. package/core/converters/parser.ts +159 -0
  55. package/core/converters/template-renderer.ts +35 -0
  56. package/core/converters/writer.ts +61 -0
  57. package/core/dag-executor.ts +426 -0
  58. package/core/dag-loader.ts +292 -0
  59. package/core/dag-schemas.ts +150 -0
  60. package/core/dispatch.ts +125 -0
  61. package/core/evidence.ts +148 -0
  62. package/core/gate.ts +269 -0
  63. package/core/hook-engine.ts +566 -0
  64. package/core/host-probe.ts +64 -0
  65. package/core/implement.ts +16 -0
  66. package/core/isolation-errors.ts +174 -0
  67. package/core/isolation-resolver.ts +921 -0
  68. package/core/issue-context.ts +381 -0
  69. package/core/issue-readiness.ts +457 -0
  70. package/core/issue-sync.ts +427 -0
  71. package/core/issues.ts +132 -0
  72. package/core/land.ts +108 -0
  73. package/core/orchestrator.ts +54 -0
  74. package/core/phase-artifact.ts +32 -0
  75. package/core/phase-gates.ts +130 -0
  76. package/core/phase-rewind.ts +94 -0
  77. package/core/plan-lint.ts +61 -0
  78. package/core/plan.ts +77 -0
  79. package/core/port-allocation.ts +50 -0
  80. package/core/pr-check.ts +15 -0
  81. package/core/preset-system/preset-resolver.ts +221 -0
  82. package/core/project-init-status.ts +127 -0
  83. package/core/qa.ts +15 -0
  84. package/core/resilience.ts +165 -0
  85. package/core/runs.ts +288 -0
  86. package/core/safe-path.test.ts +80 -0
  87. package/core/safe-path.ts +60 -0
  88. package/core/sdd-gate.test.ts +98 -0
  89. package/core/sdd-gate.ts +134 -0
  90. package/core/self-review.ts +62 -0
  91. package/core/session.ts +70 -0
  92. package/core/ship.ts +86 -0
  93. package/core/specify.ts +173 -0
  94. package/core/state.ts +1002 -0
  95. package/core/template-engine.ts +152 -0
  96. package/core/template-resolver.test.ts +70 -0
  97. package/core/template-resolver.ts +156 -0
  98. package/core/triage.ts +26 -0
  99. package/core/verify.ts +15 -0
  100. package/core/wiki-native.ts +2423 -0
  101. package/core/wiki.ts +27 -0
  102. package/core/workflow-event-emitter.ts +163 -0
  103. package/core/workflows/engine.ts +273 -0
  104. package/core/workflows/expressions.ts +76 -0
  105. package/core/workflows/index.ts +38 -0
  106. package/core/workflows/steps/command.ts +43 -0
  107. package/core/workflows/steps/gate.ts +47 -0
  108. package/core/workflows/steps/gxpm.ts +44 -0
  109. package/core/workflows/steps/linear.ts +31 -0
  110. package/core/workflows/steps/shell.ts +65 -0
  111. package/core/workflows/types.ts +62 -0
  112. package/core/workspace-runtime.ts +227 -0
  113. package/core/worktree-init-steps.ts +647 -0
  114. package/core/worktree-init.ts +330 -0
  115. package/core/worktree-owner.ts +143 -0
  116. package/docs/GXPM_VERIFY.md +98 -0
  117. package/docs/INSTALL_FOR_AGENTS.md +113 -0
  118. package/docs/README.md +57 -0
  119. package/docs/adr/adr-005-multi-platform-skill-converter.md +72 -0
  120. package/docs/agents/domain.md +30 -0
  121. package/docs/agents/issue-tracker.md +30 -0
  122. package/docs/agents/triage-labels.md +32 -0
  123. package/docs/architecture/gxpm-architecture-diagram.md +265 -0
  124. package/docs/architecture/gxpm-current-architecture.md +175 -0
  125. package/docs/architecture/gxpm-current-flow.md +278 -0
  126. package/docs/architecture/gxpm-replacement-architecture.md +211 -0
  127. package/docs/architecture/gxpm-target-architecture.md +449 -0
  128. package/docs/architecture/gxpm-v0-contract.md +311 -0
  129. package/docs/architecture/layered-workflow-boundaries.md +193 -0
  130. package/docs/architecture/preset-system.md +126 -0
  131. package/docs/architecture/scaffold-northstar.md +23 -0
  132. package/docs/brainstorms/2026-05-14-bdd-then-tdd-design.md +320 -0
  133. package/docs/brainstorms/README.md +22 -0
  134. package/docs/brainstorms/docs-knowledge-system-requirements.md +29 -0
  135. package/docs/governance/beta-skill-promotion.md +39 -0
  136. package/docs/governance/development-contract.md +144 -0
  137. package/docs/governance/gherkin-style.md +90 -0
  138. package/docs/governance/host-adapter.md +56 -0
  139. package/docs/governance/skill-authoring.md +87 -0
  140. package/docs/governance/skill-testing.md +356 -0
  141. package/docs/governance/template-authoring.md +53 -0
  142. package/docs/migrations/v0.2.md +51 -0
  143. package/docs/plans/README.md +23 -0
  144. package/docs/plans/bdd-then-tdd-plan.md +1767 -0
  145. package/docs/plans/docs-knowledge-system-plan.md +31 -0
  146. package/docs/plans/spec-kit-sdd-adoption-plan.md +305 -0
  147. package/docs/research/agents-md-best-practices.md +207 -0
  148. package/docs/research/archon-study.md +351 -0
  149. package/docs/research/claude-hooks-study.md +440 -0
  150. package/docs/research/codex-hooks-study.md +624 -0
  151. package/docs/research/everything-claude-code-study.md +252 -0
  152. package/docs/research/from-skills-to-layered-workflow.md +322 -0
  153. package/docs/research/gsd-study.md +69 -0
  154. package/docs/research/kimi-hooks-study.md +274 -0
  155. package/docs/research/mattpocock-skills-comparison.md +429 -0
  156. package/docs/research/mattpocock-skills-study.md +275 -0
  157. package/docs/research/oh-my-codex-study.md +279 -0
  158. package/docs/research/perplexity-agent-skills-design.md +168 -0
  159. package/docs/research/pmc-gstack-skill-study.md +122 -0
  160. package/docs/research/spec-kit-study.md +224 -0
  161. package/docs/research/superpowers-study.md +209 -0
  162. package/docs/roadmap/initial-roadmap.md +53 -0
  163. package/docs/solutions/README.md +45 -0
  164. package/docs/solutions/artifact-nesting-recovery.md +58 -0
  165. package/docs/solutions/session-context-restore-practice.md +67 -0
  166. package/docs/solutions/workflow/version-drift-recovery.md +49 -0
  167. package/docs/solutions/worktree-gate-recovery.md +62 -0
  168. package/docs/specs/README.md +28 -0
  169. package/docs/specs/claude.md +45 -0
  170. package/docs/specs/codex.md +44 -0
  171. package/docs/specs/cursor.md +44 -0
  172. package/hosts/adapters/claude.ts +29 -0
  173. package/hosts/adapters/codex.ts +27 -0
  174. package/hosts/adapters/cursor.ts +27 -0
  175. package/hosts/adapters/kimi.ts +27 -0
  176. package/hosts/claude.ts +23 -0
  177. package/hosts/codex.ts +26 -0
  178. package/hosts/cursor.ts +19 -0
  179. package/hosts/index.ts +33 -0
  180. package/hosts/registry.test.ts +52 -0
  181. package/hosts/registry.ts +57 -0
  182. package/hosts/schema.ts +58 -0
  183. package/package.json +52 -0
  184. package/scripts/browser.ts +185 -0
  185. package/scripts/cleanup.ts +142 -0
  186. package/scripts/commands/artifact.ts +115 -0
  187. package/scripts/commands/autopilot.ts +143 -0
  188. package/scripts/commands/capability.ts +57 -0
  189. package/scripts/commands/config.ts +69 -0
  190. package/scripts/commands/dag.ts +126 -0
  191. package/scripts/commands/feedback.ts +123 -0
  192. package/scripts/commands/gate.ts +291 -0
  193. package/scripts/commands/helpers.ts +126 -0
  194. package/scripts/commands/hook.ts +66 -0
  195. package/scripts/commands/init.ts +515 -0
  196. package/scripts/commands/issue.ts +825 -0
  197. package/scripts/commands/phase.ts +61 -0
  198. package/scripts/commands/preset.ts +159 -0
  199. package/scripts/commands/runtime.ts +199 -0
  200. package/scripts/commands/specify.ts +71 -0
  201. package/scripts/commands/upgrade.ts +243 -0
  202. package/scripts/commands/verify.ts +183 -0
  203. package/scripts/commands/wiki.ts +242 -0
  204. package/scripts/commands/workflow.ts +131 -0
  205. package/scripts/dev-skill.ts +55 -0
  206. package/scripts/discover-skills.ts +116 -0
  207. package/scripts/doctor.ts +410 -0
  208. package/scripts/dogfood-check.ts +125 -0
  209. package/scripts/eval-functional.ts +218 -0
  210. package/scripts/eval.ts +246 -0
  211. package/scripts/gen-skill-docs.ts +201 -0
  212. package/scripts/global-discover.ts +217 -0
  213. package/scripts/governance-check.ts +75 -0
  214. package/scripts/gxpm-check.ts +12 -0
  215. package/scripts/gxpm.ts +216 -0
  216. package/scripts/host-config.ts +62 -0
  217. package/scripts/install-claude-hooks.ts +138 -0
  218. package/scripts/install-codex-hooks.ts +271 -0
  219. package/scripts/install-hooks.ts +128 -0
  220. package/scripts/install-kimi-hooks.ts +92 -0
  221. package/scripts/install-skill.ts +184 -0
  222. package/scripts/phase-artifact-commands.ts +100 -0
  223. package/scripts/post-land-sync.ts +46 -0
  224. package/scripts/scaffold-check.ts +85 -0
  225. package/scripts/skill-naming-check.ts +78 -0
  226. package/scripts/skill-structure-check.ts +157 -0
  227. package/scripts/skills-lock-check.ts +60 -0
  228. package/scripts/sync-markdown-artifacts.ts +172 -0
  229. package/scripts/uninstall.ts +162 -0
  230. package/scripts/version.ts +47 -0
  231. package/scripts/wait-pr-ready.ts +407 -0
  232. package/skills/gxpm/SKILL.md +485 -0
  233. package/skills/gxpm/SKILL.md.tmpl +422 -0
  234. package/skills/gxpm/references/CANON.md +53 -0
  235. package/skills/gxpm/references/key-rules.md +130 -0
  236. package/skills/gxpm-architecture/SKILL.md +106 -0
  237. package/skills/gxpm-architecture/references/DEEPENING.md +37 -0
  238. package/skills/gxpm-architecture/references/INTERFACE-DESIGN.md +44 -0
  239. package/skills/gxpm-autopilot/SKILL.md +116 -0
  240. package/skills/gxpm-autopilot/SKILL.md.tmpl +107 -0
  241. package/skills/gxpm-browser/SKILL.md +105 -0
  242. package/skills/gxpm-browser/SKILL.md.tmpl +41 -0
  243. package/skills/gxpm-browser/references/commands.md +43 -0
  244. package/skills/gxpm-browser/references/evidence-path.md +20 -0
  245. package/skills/gxpm-build/SKILL.md +78 -0
  246. package/skills/gxpm-cleanup/SKILL.md +76 -0
  247. package/skills/gxpm-debug-issue/SKILL.md +39 -0
  248. package/skills/gxpm-diagnose/SKILL.md +220 -0
  249. package/skills/gxpm-diagnose/SKILL.md.tmpl +31 -0
  250. package/skills/gxpm-diagnose/references/feedback-loop.md +34 -0
  251. package/skills/gxpm-diagnose/references/feedback-loops.md +43 -0
  252. package/skills/gxpm-diagnose/references/phases.md +60 -0
  253. package/skills/gxpm-eval/SKILL.md +78 -0
  254. package/skills/gxpm-explore-codebase/SKILL.md +36 -0
  255. package/skills/gxpm-explore-codebase/scripts/summarize-communities.ts +51 -0
  256. package/skills/gxpm-feedback/SKILL.md +122 -0
  257. package/skills/gxpm-grill/SKILL.md +159 -0
  258. package/skills/gxpm-grill/SKILL.md.tmpl +77 -0
  259. package/skills/gxpm-grill/references/documentation-templates.md +56 -0
  260. package/skills/gxpm-grill/references/process.md +25 -0
  261. package/skills/gxpm-handoff/SKILL.md +112 -0
  262. package/skills/gxpm-hygiene/SKILL.md +69 -0
  263. package/skills/gxpm-implementer/SKILL.md +142 -0
  264. package/skills/gxpm-implementer/SKILL.md.tmpl +141 -0
  265. package/skills/gxpm-linear/SKILL.md +282 -0
  266. package/skills/gxpm-linear/SKILL.md.tmpl +86 -0
  267. package/skills/gxpm-linear/references/commands.md +75 -0
  268. package/skills/gxpm-linear/references/workflows.md +120 -0
  269. package/skills/gxpm-planning/SKILL.md +134 -0
  270. package/skills/gxpm-prototype/SKILL.md +64 -0
  271. package/skills/gxpm-refactor-safely/SKILL.md +62 -0
  272. package/skills/gxpm-review-army/SKILL.md +117 -0
  273. package/skills/gxpm-review-changes/SKILL.md +36 -0
  274. package/skills/gxpm-setup/SKILL.md +101 -0
  275. package/skills/gxpm-specifier/SKILL.md +135 -0
  276. package/skills/gxpm-tdd/SKILL.md +187 -0
  277. package/skills/gxpm-tdd/references/interface-design.md +23 -0
  278. package/skills/gxpm-tdd/references/mocking.md +27 -0
  279. package/skills/gxpm-tdd/references/red-green-refactor.md +61 -0
  280. package/skills/gxpm-tdd/references/troubleshooting.md +28 -0
  281. package/skills/gxpm-tdd/references/workflow.md +50 -0
  282. package/skills/gxpm-tdd/testing-anti-patterns.tmpl +304 -0
  283. package/skills/gxpm-triage/SKILL.md +160 -0
  284. package/skills/gxpm-verify/SKILL.md +107 -0
  285. package/skills/gxpm-write-skill/SKILL.md +131 -0
  286. package/skills/gxpm-zoom-out/SKILL.md +69 -0
  287. package/skills/maintain-hygiene-skills-lock/SKILL.md +54 -0
  288. package/skills/maintain-hygiene-skills-lock/SKILL.md.tmpl +53 -0
  289. package/templates/constitution-template.md +63 -0
  290. package/templates/hooks/gxpm-commit-msg +16 -0
  291. package/templates/hooks/gxpm-post-checkout +19 -0
  292. package/templates/hooks/gxpm-post-commit +7 -0
  293. package/templates/hooks/gxpm-post-merge +29 -0
  294. package/templates/hooks/gxpm-pre-commit +39 -0
  295. package/templates/hooks/gxpm-pre-push +33 -0
  296. package/templates/plan-template.md.tmpl +46 -0
  297. package/templates/spec-template.md.tmpl +63 -0
  298. package/templates/specify-stub.tmpl +22 -0
  299. package/templates/tasks-template.md.tmpl +32 -0
@@ -0,0 +1,407 @@
1
+ #!/usr/bin/env bun
2
+
3
+ type GateState = "ready" | "pending" | "blocked";
4
+
5
+ export interface WaitPrReadyOptions {
6
+ pr: string;
7
+ repo?: string;
8
+ intervalSec: number;
9
+ timeoutSec: number;
10
+ allowReviewRequired: boolean;
11
+ once: boolean;
12
+ json: boolean;
13
+ }
14
+
15
+ export interface NormalizedCheck {
16
+ name: string;
17
+ status: string;
18
+ conclusion: string;
19
+ }
20
+
21
+ export interface GateClassification {
22
+ state: GateState;
23
+ reason: string;
24
+ headRefOid?: string;
25
+ mergeStateStatus?: string;
26
+ mergeable?: string;
27
+ reviewDecision?: string;
28
+ pendingChecks: string[];
29
+ failedChecks: string[];
30
+ }
31
+
32
+ const SUCCESS_STATES = new Set(["SUCCESS", "SKIPPED", "NEUTRAL"]);
33
+ const COMPLETED_STATES = new Set(["COMPLETED", "SUCCESS", "SKIPPED", "NEUTRAL"]);
34
+ const FAILURE_STATES = new Set([
35
+ "ACTION_REQUIRED",
36
+ "CANCELLED",
37
+ "ERROR",
38
+ "FAILURE",
39
+ "FAILED",
40
+ "STARTUP_FAILURE",
41
+ "STALE",
42
+ "TIMED_OUT",
43
+ ]);
44
+
45
+ export function parseWaitPrReadyArgs(argv: string[]): WaitPrReadyOptions {
46
+ const options: WaitPrReadyOptions = {
47
+ pr: "",
48
+ intervalSec: 60,
49
+ timeoutSec: 900,
50
+ allowReviewRequired: false,
51
+ once: false,
52
+ json: false,
53
+ };
54
+
55
+ const positional: string[] = [];
56
+ for (let i = 0; i < argv.length; i++) {
57
+ const arg = argv[i];
58
+ if (arg === "--help" || arg === "-h") {
59
+ throw new UsageError(usage(), 0);
60
+ }
61
+ if (arg === "--json") {
62
+ options.json = true;
63
+ continue;
64
+ }
65
+ if (arg === "--once") {
66
+ options.once = true;
67
+ continue;
68
+ }
69
+ if (arg === "--allow-review-required") {
70
+ options.allowReviewRequired = true;
71
+ continue;
72
+ }
73
+ if (arg === "--repo") {
74
+ options.repo = requiredValue(argv, ++i, arg);
75
+ continue;
76
+ }
77
+ if (arg === "--interval-sec") {
78
+ options.intervalSec = positiveInteger(requiredValue(argv, ++i, arg), arg);
79
+ continue;
80
+ }
81
+ if (arg === "--timeout-sec") {
82
+ options.timeoutSec = positiveInteger(requiredValue(argv, ++i, arg), arg);
83
+ continue;
84
+ }
85
+ if (arg.startsWith("--")) {
86
+ throw new UsageError(`Unknown option: ${arg}\n\n${usage()}`, 2);
87
+ }
88
+ positional.push(arg);
89
+ }
90
+
91
+ if (positional.length !== 1) {
92
+ throw new UsageError(`Expected exactly one PR number, URL, or branch.\n\n${usage()}`, 2);
93
+ }
94
+ options.pr = positional[0];
95
+ return options;
96
+ }
97
+
98
+ export function classifyPrGate(
99
+ prView: unknown,
100
+ options: Pick<WaitPrReadyOptions, "allowReviewRequired"> = { allowReviewRequired: false },
101
+ ): GateClassification {
102
+ const record = asRecord(prView);
103
+ const mergeStateStatus = upperString(record.mergeStateStatus);
104
+ const mergeable = upperString(record.mergeable);
105
+ const reviewDecision = upperString(record.reviewDecision);
106
+ const headRefOid = stringValue(record.headRefOid);
107
+ const checks = normalizeStatusCheckRollup(record.statusCheckRollup);
108
+
109
+ const failedChecks = checks.filter(isFailedCheck).map((check) => check.name);
110
+ if (failedChecks.length > 0) {
111
+ return classification("blocked", "one or more status checks failed", {
112
+ headRefOid,
113
+ mergeStateStatus,
114
+ mergeable,
115
+ reviewDecision,
116
+ checks,
117
+ failedChecks,
118
+ });
119
+ }
120
+
121
+ if (mergeable === "CONFLICTING" || mergeStateStatus === "DIRTY") {
122
+ return classification("blocked", "PR has merge conflicts or dirty merge state", {
123
+ headRefOid,
124
+ mergeStateStatus,
125
+ mergeable,
126
+ reviewDecision,
127
+ checks,
128
+ });
129
+ }
130
+
131
+ if (reviewDecision === "CHANGES_REQUESTED") {
132
+ return classification("blocked", "review changes were requested", {
133
+ headRefOid,
134
+ mergeStateStatus,
135
+ mergeable,
136
+ reviewDecision,
137
+ checks,
138
+ });
139
+ }
140
+
141
+ const pendingChecks = checks.filter((check) => !isPassedCheck(check)).map((check) => check.name);
142
+ if (pendingChecks.length > 0) {
143
+ return classification("pending", "waiting for status checks", {
144
+ headRefOid,
145
+ mergeStateStatus,
146
+ mergeable,
147
+ reviewDecision,
148
+ checks,
149
+ pendingChecks,
150
+ });
151
+ }
152
+
153
+ if (!options.allowReviewRequired && reviewDecision !== "APPROVED") {
154
+ return classification("pending", "waiting for approving review decision", {
155
+ headRefOid,
156
+ mergeStateStatus,
157
+ mergeable,
158
+ reviewDecision,
159
+ checks,
160
+ });
161
+ }
162
+
163
+ const mergeStateReady = mergeStateStatus === "CLEAN" || (!mergeStateStatus && mergeable === "MERGEABLE");
164
+ if (!mergeStateReady) {
165
+ return classification("pending", "waiting for clean merge state", {
166
+ headRefOid,
167
+ mergeStateStatus,
168
+ mergeable,
169
+ reviewDecision,
170
+ checks,
171
+ });
172
+ }
173
+
174
+ return classification("ready", "review, checks, and merge state are ready", {
175
+ headRefOid,
176
+ mergeStateStatus,
177
+ mergeable,
178
+ reviewDecision,
179
+ checks,
180
+ });
181
+ }
182
+
183
+ export function normalizeStatusCheckRollup(value: unknown): NormalizedCheck[] {
184
+ if (!Array.isArray(value)) return [];
185
+ return value.map((item, index) => {
186
+ const record = asRecord(item);
187
+ const name =
188
+ stringValue(record.name) ||
189
+ stringValue(record.workflowName) ||
190
+ stringValue(record.context) ||
191
+ `check-${index + 1}`;
192
+ const status = upperString(record.status) || upperString(record.state);
193
+ const conclusion = upperString(record.conclusion);
194
+ return { name, status, conclusion };
195
+ });
196
+ }
197
+
198
+ function classification(
199
+ state: GateState,
200
+ reason: string,
201
+ input: {
202
+ headRefOid?: string;
203
+ mergeStateStatus?: string;
204
+ mergeable?: string;
205
+ reviewDecision?: string;
206
+ checks: NormalizedCheck[];
207
+ pendingChecks?: string[];
208
+ failedChecks?: string[];
209
+ },
210
+ ): GateClassification {
211
+ return {
212
+ state,
213
+ reason,
214
+ headRefOid: input.headRefOid,
215
+ mergeStateStatus: input.mergeStateStatus,
216
+ mergeable: input.mergeable,
217
+ reviewDecision: input.reviewDecision,
218
+ pendingChecks: input.pendingChecks ?? [],
219
+ failedChecks: input.failedChecks ?? [],
220
+ };
221
+ }
222
+
223
+ function isPassedCheck(check: NormalizedCheck): boolean {
224
+ if (FAILURE_STATES.has(check.conclusion) || FAILURE_STATES.has(check.status)) return false;
225
+ if (SUCCESS_STATES.has(check.conclusion) || SUCCESS_STATES.has(check.status)) return true;
226
+ if (COMPLETED_STATES.has(check.status)) return SUCCESS_STATES.has(check.conclusion);
227
+ return false;
228
+ }
229
+
230
+ function isFailedCheck(check: NormalizedCheck): boolean {
231
+ return FAILURE_STATES.has(check.conclusion) || FAILURE_STATES.has(check.status);
232
+ }
233
+
234
+ async function main() {
235
+ let options: WaitPrReadyOptions;
236
+ try {
237
+ options = parseWaitPrReadyArgs(process.argv.slice(2));
238
+ } catch (error) {
239
+ if (error instanceof UsageError) {
240
+ console.log(error.message);
241
+ process.exit(error.exitCode);
242
+ }
243
+ throw error;
244
+ }
245
+
246
+ const startedAt = Date.now();
247
+ const deadline = startedAt + options.timeoutSec * 1000;
248
+ let lastError = "";
249
+
250
+ while (true) {
251
+ const result = fetchPrView(options);
252
+ if (!result.ok) {
253
+ lastError = result.error;
254
+ emit(options, {
255
+ state: "pending",
256
+ reason: "gh pr view failed; retrying until timeout",
257
+ error: result.error,
258
+ elapsedSec: elapsedSec(startedAt),
259
+ });
260
+ if (options.once) process.exit(2);
261
+ } else {
262
+ const gate = classifyPrGate(result.prView, options);
263
+ emit(options, { ...gate, elapsedSec: elapsedSec(startedAt) });
264
+ if (gate.state === "ready") process.exit(0);
265
+ if (gate.state === "blocked") process.exit(1);
266
+ lastError = "";
267
+ if (options.once) process.exit(124);
268
+ }
269
+
270
+ if (Date.now() >= deadline) {
271
+ emit(options, {
272
+ state: "pending",
273
+ reason: "timeout waiting for PR gate readiness",
274
+ error: lastError || undefined,
275
+ elapsedSec: elapsedSec(startedAt),
276
+ });
277
+ process.exit(124);
278
+ }
279
+
280
+ await sleep(Math.min(options.intervalSec * 1000, Math.max(0, deadline - Date.now())));
281
+ }
282
+ }
283
+
284
+ function fetchPrView(options: WaitPrReadyOptions): { ok: true; prView: unknown } | { ok: false; error: string } {
285
+ const cmd = [
286
+ "gh",
287
+ "pr",
288
+ "view",
289
+ options.pr,
290
+ "--json",
291
+ "headRefOid,mergeStateStatus,mergeable,reviewDecision,statusCheckRollup",
292
+ ];
293
+ if (options.repo) {
294
+ cmd.push("--repo", options.repo);
295
+ }
296
+
297
+ const result = Bun.spawnSync({
298
+ cmd,
299
+ stdout: "pipe",
300
+ stderr: "pipe",
301
+ });
302
+
303
+ if (result.exitCode !== 0) {
304
+ return { ok: false, error: result.stderr.toString().trim() || `gh exited with ${result.exitCode}` };
305
+ }
306
+
307
+ try {
308
+ return { ok: true, prView: JSON.parse(result.stdout.toString()) };
309
+ } catch (error) {
310
+ return {
311
+ ok: false,
312
+ error: `failed to parse gh output: ${error instanceof Error ? error.message : String(error)}`,
313
+ };
314
+ }
315
+ }
316
+
317
+ function emit(options: WaitPrReadyOptions, payload: Record<string, unknown>) {
318
+ if (options.json) {
319
+ console.log(JSON.stringify(payload));
320
+ return;
321
+ }
322
+ const details = [
323
+ `state=${payload.state}`,
324
+ `reason=${payload.reason}`,
325
+ payload.headRefOid ? `head=${String(payload.headRefOid).slice(0, 12)}` : "",
326
+ payload.mergeStateStatus ? `mergeState=${payload.mergeStateStatus}` : "",
327
+ payload.reviewDecision ? `review=${payload.reviewDecision}` : "",
328
+ payload.elapsedSec !== undefined ? `elapsed=${payload.elapsedSec}s` : "",
329
+ ].filter(Boolean);
330
+ console.log(details.join(" "));
331
+ if (Array.isArray(payload.pendingChecks) && payload.pendingChecks.length > 0) {
332
+ console.log(`pendingChecks=${payload.pendingChecks.join(",")}`);
333
+ }
334
+ if (Array.isArray(payload.failedChecks) && payload.failedChecks.length > 0) {
335
+ console.log(`failedChecks=${payload.failedChecks.join(",")}`);
336
+ }
337
+ if (payload.error) {
338
+ console.log(`error=${payload.error}`);
339
+ }
340
+ }
341
+
342
+ function usage() {
343
+ return `Usage: bun run scripts/wait-pr-ready.ts <pr-number-or-url> [options]
344
+
345
+ Poll GitHub PR readiness with a finite timeout. Intended for in-turn gxpm
346
+ ship/pr-check waits such as CodeRabbit and GitHub checks.
347
+
348
+ Options:
349
+ --repo <owner/repo> Repository for gh pr view
350
+ --interval-sec <seconds> Poll interval (default: 60)
351
+ --timeout-sec <seconds> Maximum wait (default: 900)
352
+ --allow-review-required Do not require reviewDecision=APPROVED
353
+ --once Check once and exit
354
+ --json Emit JSON lines
355
+
356
+ Exit codes:
357
+ 0 ready
358
+ 1 blocked by failure, conflict, or requested changes
359
+ 2 usage or command error in --once mode
360
+ 124 timeout or still pending in --once mode`;
361
+ }
362
+
363
+ function requiredValue(argv: string[], index: number, option: string): string {
364
+ const value = argv[index];
365
+ if (!value || value.startsWith("--")) {
366
+ throw new UsageError(`${option} requires a value\n\n${usage()}`, 2);
367
+ }
368
+ return value;
369
+ }
370
+
371
+ function positiveInteger(raw: string, option: string): number {
372
+ const value = Number.parseInt(raw, 10);
373
+ if (!Number.isInteger(value) || value < 1 || String(value) !== raw) {
374
+ throw new UsageError(`${option} requires a positive integer`, 2);
375
+ }
376
+ return value;
377
+ }
378
+
379
+ function asRecord(value: unknown): Record<string, unknown> {
380
+ return value && typeof value === "object" && !Array.isArray(value) ? (value as Record<string, unknown>) : {};
381
+ }
382
+
383
+ function stringValue(value: unknown): string | undefined {
384
+ return typeof value === "string" && value.trim() ? value.trim() : undefined;
385
+ }
386
+
387
+ function upperString(value: unknown): string {
388
+ return stringValue(value)?.toUpperCase() ?? "";
389
+ }
390
+
391
+ function elapsedSec(startedAt: number): number {
392
+ return Math.floor((Date.now() - startedAt) / 1000);
393
+ }
394
+
395
+ function sleep(ms: number): Promise<void> {
396
+ return new Promise((resolve) => setTimeout(resolve, ms));
397
+ }
398
+
399
+ class UsageError extends Error {
400
+ constructor(message: string, readonly exitCode: number) {
401
+ super(message);
402
+ }
403
+ }
404
+
405
+ if (import.meta.main) {
406
+ await main();
407
+ }