@runchr/gstack-antigravity 0.1.0 → 0.1.2

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.

Potentially problematic release.


This version of @runchr/gstack-antigravity might be problematic. Click here for more details.

Files changed (231) hide show
  1. package/.agents/skills/gstack/.agents/skills/gstack/SKILL.md +651 -0
  2. package/.agents/skills/gstack/.agents/skills/gstack-autoplan/SKILL.md +678 -0
  3. package/.agents/skills/gstack/.agents/skills/gstack-benchmark/SKILL.md +482 -0
  4. package/.agents/skills/gstack/.agents/skills/gstack-browse/SKILL.md +511 -0
  5. package/.agents/skills/gstack/.agents/skills/gstack-canary/SKILL.md +486 -0
  6. package/.agents/skills/gstack/.agents/skills/gstack-careful/SKILL.md +50 -0
  7. package/.agents/skills/gstack/.agents/skills/gstack-cso/SKILL.md +607 -0
  8. package/.agents/skills/gstack/.agents/skills/gstack-design-consultation/SKILL.md +615 -0
  9. package/.agents/skills/gstack/.agents/skills/gstack-design-review/SKILL.md +988 -0
  10. package/.agents/skills/gstack/.agents/skills/gstack-document-release/SKILL.md +604 -0
  11. package/.agents/skills/gstack/.agents/skills/gstack-freeze/SKILL.md +67 -0
  12. package/.agents/skills/gstack/.agents/skills/gstack-guard/SKILL.md +62 -0
  13. package/.agents/skills/gstack/.agents/skills/gstack-investigate/SKILL.md +415 -0
  14. package/.agents/skills/gstack/.agents/skills/gstack-land-and-deploy/SKILL.md +873 -0
  15. package/.agents/skills/gstack/.agents/skills/gstack-office-hours/SKILL.md +986 -0
  16. package/.agents/skills/gstack/.agents/skills/gstack-plan-ceo-review/SKILL.md +1268 -0
  17. package/.agents/skills/gstack/.agents/skills/gstack-plan-design-review/SKILL.md +668 -0
  18. package/.agents/skills/gstack/.agents/skills/gstack-plan-eng-review/SKILL.md +826 -0
  19. package/.agents/skills/gstack/.agents/skills/gstack-qa/SKILL.md +1006 -0
  20. package/.agents/skills/gstack/.agents/skills/gstack-qa-only/SKILL.md +626 -0
  21. package/.agents/skills/gstack/.agents/skills/gstack-retro/SKILL.md +1065 -0
  22. package/.agents/skills/gstack/.agents/skills/gstack-review/SKILL.md +704 -0
  23. package/.agents/skills/gstack/.agents/skills/gstack-setup-browser-cookies/SKILL.md +325 -0
  24. package/.agents/skills/gstack/.agents/skills/gstack-setup-deploy/SKILL.md +450 -0
  25. package/.agents/skills/gstack/.agents/skills/gstack-ship/SKILL.md +1312 -0
  26. package/.agents/skills/gstack/.agents/skills/gstack-unfreeze/SKILL.md +36 -0
  27. package/.agents/skills/gstack/.agents/skills/gstack-upgrade/SKILL.md +220 -0
  28. package/.agents/skills/gstack/.env.example +5 -0
  29. package/.agents/skills/gstack/.github/workflows/skill-docs.yml +17 -0
  30. package/.agents/skills/gstack/AGENTS.md +49 -0
  31. package/.agents/skills/gstack/ARCHITECTURE.md +359 -0
  32. package/.agents/skills/gstack/BROWSER.md +271 -0
  33. package/.agents/skills/gstack/CHANGELOG.md +800 -0
  34. package/.agents/skills/gstack/CLAUDE.md +284 -0
  35. package/.agents/skills/gstack/CONTRIBUTING.md +370 -0
  36. package/.agents/skills/gstack/ETHOS.md +129 -0
  37. package/.agents/skills/gstack/LICENSE +21 -0
  38. package/.agents/skills/gstack/README.md +228 -0
  39. package/.agents/skills/gstack/SKILL.md +657 -0
  40. package/.agents/skills/gstack/SKILL.md.tmpl +281 -0
  41. package/.agents/skills/gstack/TODOS.md +564 -0
  42. package/.agents/skills/gstack/VERSION +1 -0
  43. package/.agents/skills/gstack/autoplan/SKILL.md +689 -0
  44. package/.agents/skills/gstack/autoplan/SKILL.md.tmpl +416 -0
  45. package/.agents/skills/gstack/benchmark/SKILL.md +489 -0
  46. package/.agents/skills/gstack/benchmark/SKILL.md.tmpl +233 -0
  47. package/.agents/skills/gstack/bin/dev-setup +68 -0
  48. package/.agents/skills/gstack/bin/dev-teardown +56 -0
  49. package/.agents/skills/gstack/bin/gstack-analytics +191 -0
  50. package/.agents/skills/gstack/bin/gstack-community-dashboard +113 -0
  51. package/.agents/skills/gstack/bin/gstack-config +38 -0
  52. package/.agents/skills/gstack/bin/gstack-diff-scope +71 -0
  53. package/.agents/skills/gstack/bin/gstack-global-discover.ts +591 -0
  54. package/.agents/skills/gstack/bin/gstack-repo-mode +93 -0
  55. package/.agents/skills/gstack/bin/gstack-review-log +9 -0
  56. package/.agents/skills/gstack/bin/gstack-review-read +12 -0
  57. package/.agents/skills/gstack/bin/gstack-slug +15 -0
  58. package/.agents/skills/gstack/bin/gstack-telemetry-log +158 -0
  59. package/.agents/skills/gstack/bin/gstack-telemetry-sync +127 -0
  60. package/.agents/skills/gstack/bin/gstack-update-check +196 -0
  61. package/.agents/skills/gstack/browse/SKILL.md +517 -0
  62. package/.agents/skills/gstack/browse/SKILL.md.tmpl +141 -0
  63. package/.agents/skills/gstack/browse/bin/find-browse +21 -0
  64. package/.agents/skills/gstack/browse/bin/remote-slug +14 -0
  65. package/.agents/skills/gstack/browse/scripts/build-node-server.sh +48 -0
  66. package/.agents/skills/gstack/browse/src/browser-manager.ts +634 -0
  67. package/.agents/skills/gstack/browse/src/buffers.ts +137 -0
  68. package/.agents/skills/gstack/browse/src/bun-polyfill.cjs +109 -0
  69. package/.agents/skills/gstack/browse/src/cli.ts +420 -0
  70. package/.agents/skills/gstack/browse/src/commands.ts +111 -0
  71. package/.agents/skills/gstack/browse/src/config.ts +150 -0
  72. package/.agents/skills/gstack/browse/src/cookie-import-browser.ts +417 -0
  73. package/.agents/skills/gstack/browse/src/cookie-picker-routes.ts +207 -0
  74. package/.agents/skills/gstack/browse/src/cookie-picker-ui.ts +541 -0
  75. package/.agents/skills/gstack/browse/src/find-browse.ts +61 -0
  76. package/.agents/skills/gstack/browse/src/meta-commands.ts +269 -0
  77. package/.agents/skills/gstack/browse/src/platform.ts +17 -0
  78. package/.agents/skills/gstack/browse/src/read-commands.ts +335 -0
  79. package/.agents/skills/gstack/browse/src/server.ts +369 -0
  80. package/.agents/skills/gstack/browse/src/snapshot.ts +398 -0
  81. package/.agents/skills/gstack/browse/src/url-validation.ts +91 -0
  82. package/.agents/skills/gstack/browse/src/write-commands.ts +352 -0
  83. package/.agents/skills/gstack/browse/test/bun-polyfill.test.ts +72 -0
  84. package/.agents/skills/gstack/browse/test/commands.test.ts +1836 -0
  85. package/.agents/skills/gstack/browse/test/config.test.ts +250 -0
  86. package/.agents/skills/gstack/browse/test/cookie-import-browser.test.ts +397 -0
  87. package/.agents/skills/gstack/browse/test/cookie-picker-routes.test.ts +205 -0
  88. package/.agents/skills/gstack/browse/test/find-browse.test.ts +50 -0
  89. package/.agents/skills/gstack/browse/test/fixtures/basic.html +33 -0
  90. package/.agents/skills/gstack/browse/test/fixtures/cursor-interactive.html +22 -0
  91. package/.agents/skills/gstack/browse/test/fixtures/dialog.html +15 -0
  92. package/.agents/skills/gstack/browse/test/fixtures/empty.html +2 -0
  93. package/.agents/skills/gstack/browse/test/fixtures/forms.html +55 -0
  94. package/.agents/skills/gstack/browse/test/fixtures/qa-eval-checkout.html +108 -0
  95. package/.agents/skills/gstack/browse/test/fixtures/qa-eval-spa.html +98 -0
  96. package/.agents/skills/gstack/browse/test/fixtures/qa-eval.html +51 -0
  97. package/.agents/skills/gstack/browse/test/fixtures/responsive.html +49 -0
  98. package/.agents/skills/gstack/browse/test/fixtures/snapshot.html +55 -0
  99. package/.agents/skills/gstack/browse/test/fixtures/spa.html +24 -0
  100. package/.agents/skills/gstack/browse/test/fixtures/states.html +17 -0
  101. package/.agents/skills/gstack/browse/test/fixtures/upload.html +25 -0
  102. package/.agents/skills/gstack/browse/test/gstack-config.test.ts +125 -0
  103. package/.agents/skills/gstack/browse/test/gstack-update-check.test.ts +467 -0
  104. package/.agents/skills/gstack/browse/test/handoff.test.ts +235 -0
  105. package/.agents/skills/gstack/browse/test/path-validation.test.ts +63 -0
  106. package/.agents/skills/gstack/browse/test/platform.test.ts +37 -0
  107. package/.agents/skills/gstack/browse/test/snapshot.test.ts +467 -0
  108. package/.agents/skills/gstack/browse/test/test-server.ts +57 -0
  109. package/.agents/skills/gstack/browse/test/url-validation.test.ts +72 -0
  110. package/.agents/skills/gstack/canary/SKILL.md +493 -0
  111. package/.agents/skills/gstack/canary/SKILL.md.tmpl +220 -0
  112. package/.agents/skills/gstack/careful/SKILL.md +59 -0
  113. package/.agents/skills/gstack/careful/SKILL.md.tmpl +57 -0
  114. package/.agents/skills/gstack/careful/bin/check-careful.sh +112 -0
  115. package/.agents/skills/gstack/codex/SKILL.md +677 -0
  116. package/.agents/skills/gstack/codex/SKILL.md.tmpl +356 -0
  117. package/.agents/skills/gstack/conductor.json +6 -0
  118. package/.agents/skills/gstack/cso/SKILL.md +615 -0
  119. package/.agents/skills/gstack/cso/SKILL.md.tmpl +376 -0
  120. package/.agents/skills/gstack/design-consultation/SKILL.md +625 -0
  121. package/.agents/skills/gstack/design-consultation/SKILL.md.tmpl +369 -0
  122. package/.agents/skills/gstack/design-review/SKILL.md +998 -0
  123. package/.agents/skills/gstack/design-review/SKILL.md.tmpl +262 -0
  124. package/.agents/skills/gstack/docs/images/github-2013.png +0 -0
  125. package/.agents/skills/gstack/docs/images/github-2026.png +0 -0
  126. package/.agents/skills/gstack/docs/skills.md +877 -0
  127. package/.agents/skills/gstack/document-release/SKILL.md +613 -0
  128. package/.agents/skills/gstack/document-release/SKILL.md.tmpl +357 -0
  129. package/.agents/skills/gstack/freeze/SKILL.md +82 -0
  130. package/.agents/skills/gstack/freeze/SKILL.md.tmpl +80 -0
  131. package/.agents/skills/gstack/freeze/bin/check-freeze.sh +68 -0
  132. package/.agents/skills/gstack/gstack-upgrade/SKILL.md +226 -0
  133. package/.agents/skills/gstack/gstack-upgrade/SKILL.md.tmpl +224 -0
  134. package/.agents/skills/gstack/guard/SKILL.md +82 -0
  135. package/.agents/skills/gstack/guard/SKILL.md.tmpl +80 -0
  136. package/.agents/skills/gstack/investigate/SKILL.md +435 -0
  137. package/.agents/skills/gstack/investigate/SKILL.md.tmpl +196 -0
  138. package/.agents/skills/gstack/land-and-deploy/SKILL.md +880 -0
  139. package/.agents/skills/gstack/land-and-deploy/SKILL.md.tmpl +575 -0
  140. package/.agents/skills/gstack/office-hours/SKILL.md +996 -0
  141. package/.agents/skills/gstack/office-hours/SKILL.md.tmpl +624 -0
  142. package/.agents/skills/gstack/package.json +55 -0
  143. package/.agents/skills/gstack/plan-ceo-review/SKILL.md +1277 -0
  144. package/.agents/skills/gstack/plan-ceo-review/SKILL.md.tmpl +838 -0
  145. package/.agents/skills/gstack/plan-design-review/SKILL.md +676 -0
  146. package/.agents/skills/gstack/plan-design-review/SKILL.md.tmpl +314 -0
  147. package/.agents/skills/gstack/plan-eng-review/SKILL.md +836 -0
  148. package/.agents/skills/gstack/plan-eng-review/SKILL.md.tmpl +279 -0
  149. package/.agents/skills/gstack/qa/SKILL.md +1016 -0
  150. package/.agents/skills/gstack/qa/SKILL.md.tmpl +316 -0
  151. package/.agents/skills/gstack/qa/references/issue-taxonomy.md +85 -0
  152. package/.agents/skills/gstack/qa/templates/qa-report-template.md +126 -0
  153. package/.agents/skills/gstack/qa-only/SKILL.md +633 -0
  154. package/.agents/skills/gstack/qa-only/SKILL.md.tmpl +101 -0
  155. package/.agents/skills/gstack/retro/SKILL.md +1072 -0
  156. package/.agents/skills/gstack/retro/SKILL.md.tmpl +833 -0
  157. package/.agents/skills/gstack/review/SKILL.md +849 -0
  158. package/.agents/skills/gstack/review/SKILL.md.tmpl +259 -0
  159. package/.agents/skills/gstack/review/TODOS-format.md +62 -0
  160. package/.agents/skills/gstack/review/checklist.md +190 -0
  161. package/.agents/skills/gstack/review/design-checklist.md +132 -0
  162. package/.agents/skills/gstack/review/greptile-triage.md +220 -0
  163. package/.agents/skills/gstack/scripts/analytics.ts +190 -0
  164. package/.agents/skills/gstack/scripts/dev-skill.ts +82 -0
  165. package/.agents/skills/gstack/scripts/eval-compare.ts +96 -0
  166. package/.agents/skills/gstack/scripts/eval-list.ts +116 -0
  167. package/.agents/skills/gstack/scripts/eval-select.ts +86 -0
  168. package/.agents/skills/gstack/scripts/eval-summary.ts +187 -0
  169. package/.agents/skills/gstack/scripts/eval-watch.ts +172 -0
  170. package/.agents/skills/gstack/scripts/gen-skill-docs.ts +2414 -0
  171. package/.agents/skills/gstack/scripts/skill-check.ts +167 -0
  172. package/.agents/skills/gstack/setup +269 -0
  173. package/.agents/skills/gstack/setup-browser-cookies/SKILL.md +330 -0
  174. package/.agents/skills/gstack/setup-browser-cookies/SKILL.md.tmpl +74 -0
  175. package/.agents/skills/gstack/setup-deploy/SKILL.md +459 -0
  176. package/.agents/skills/gstack/setup-deploy/SKILL.md.tmpl +220 -0
  177. package/.agents/skills/gstack/ship/SKILL.md +1457 -0
  178. package/.agents/skills/gstack/ship/SKILL.md.tmpl +528 -0
  179. package/.agents/skills/gstack/supabase/config.sh +10 -0
  180. package/.agents/skills/gstack/supabase/functions/community-pulse/index.ts +59 -0
  181. package/.agents/skills/gstack/supabase/functions/telemetry-ingest/index.ts +135 -0
  182. package/.agents/skills/gstack/supabase/functions/update-check/index.ts +37 -0
  183. package/.agents/skills/gstack/supabase/migrations/001_telemetry.sql +89 -0
  184. package/.agents/skills/gstack/test/analytics.test.ts +277 -0
  185. package/.agents/skills/gstack/test/codex-e2e.test.ts +197 -0
  186. package/.agents/skills/gstack/test/fixtures/coverage-audit-fixture.ts +76 -0
  187. package/.agents/skills/gstack/test/fixtures/eval-baselines.json +7 -0
  188. package/.agents/skills/gstack/test/fixtures/qa-eval-checkout-ground-truth.json +43 -0
  189. package/.agents/skills/gstack/test/fixtures/qa-eval-ground-truth.json +43 -0
  190. package/.agents/skills/gstack/test/fixtures/qa-eval-spa-ground-truth.json +43 -0
  191. package/.agents/skills/gstack/test/fixtures/review-eval-design-slop.css +86 -0
  192. package/.agents/skills/gstack/test/fixtures/review-eval-design-slop.html +41 -0
  193. package/.agents/skills/gstack/test/fixtures/review-eval-enum-diff.rb +30 -0
  194. package/.agents/skills/gstack/test/fixtures/review-eval-enum.rb +27 -0
  195. package/.agents/skills/gstack/test/fixtures/review-eval-vuln.rb +14 -0
  196. package/.agents/skills/gstack/test/gemini-e2e.test.ts +173 -0
  197. package/.agents/skills/gstack/test/gen-skill-docs.test.ts +1049 -0
  198. package/.agents/skills/gstack/test/global-discover.test.ts +187 -0
  199. package/.agents/skills/gstack/test/helpers/codex-session-runner.ts +282 -0
  200. package/.agents/skills/gstack/test/helpers/e2e-helpers.ts +239 -0
  201. package/.agents/skills/gstack/test/helpers/eval-store.test.ts +548 -0
  202. package/.agents/skills/gstack/test/helpers/eval-store.ts +689 -0
  203. package/.agents/skills/gstack/test/helpers/gemini-session-runner.test.ts +104 -0
  204. package/.agents/skills/gstack/test/helpers/gemini-session-runner.ts +201 -0
  205. package/.agents/skills/gstack/test/helpers/llm-judge.ts +130 -0
  206. package/.agents/skills/gstack/test/helpers/observability.test.ts +283 -0
  207. package/.agents/skills/gstack/test/helpers/session-runner.test.ts +96 -0
  208. package/.agents/skills/gstack/test/helpers/session-runner.ts +357 -0
  209. package/.agents/skills/gstack/test/helpers/skill-parser.ts +206 -0
  210. package/.agents/skills/gstack/test/helpers/touchfiles.ts +260 -0
  211. package/.agents/skills/gstack/test/hook-scripts.test.ts +373 -0
  212. package/.agents/skills/gstack/test/skill-e2e-browse.test.ts +293 -0
  213. package/.agents/skills/gstack/test/skill-e2e-deploy.test.ts +279 -0
  214. package/.agents/skills/gstack/test/skill-e2e-design.test.ts +614 -0
  215. package/.agents/skills/gstack/test/skill-e2e-plan.test.ts +538 -0
  216. package/.agents/skills/gstack/test/skill-e2e-qa-bugs.test.ts +194 -0
  217. package/.agents/skills/gstack/test/skill-e2e-qa-workflow.test.ts +412 -0
  218. package/.agents/skills/gstack/test/skill-e2e-review.test.ts +535 -0
  219. package/.agents/skills/gstack/test/skill-e2e-workflow.test.ts +586 -0
  220. package/.agents/skills/gstack/test/skill-e2e.test.ts +3325 -0
  221. package/.agents/skills/gstack/test/skill-llm-eval.test.ts +787 -0
  222. package/.agents/skills/gstack/test/skill-parser.test.ts +179 -0
  223. package/.agents/skills/gstack/test/skill-routing-e2e.test.ts +605 -0
  224. package/.agents/skills/gstack/test/skill-validation.test.ts +1520 -0
  225. package/.agents/skills/gstack/test/telemetry.test.ts +278 -0
  226. package/.agents/skills/gstack/test/touchfiles.test.ts +262 -0
  227. package/.agents/skills/gstack/unfreeze/SKILL.md +40 -0
  228. package/.agents/skills/gstack/unfreeze/SKILL.md.tmpl +38 -0
  229. package/README.md +12 -7
  230. package/README_KO.md +12 -6
  231. package/package.json +3 -2
@@ -0,0 +1,187 @@
1
+ import { describe, test, expect, beforeEach, afterEach } from "bun:test";
2
+ import { mkdtempSync, mkdirSync, writeFileSync, rmSync, existsSync } from "fs";
3
+ import { join } from "path";
4
+ import { tmpdir } from "os";
5
+ import { spawnSync } from "child_process";
6
+
7
+ // Import normalizeRemoteUrl for unit testing
8
+ // We test the script end-to-end via CLI and normalizeRemoteUrl via import
9
+ const scriptPath = join(import.meta.dir, "..", "bin", "gstack-global-discover.ts");
10
+
11
+ describe("gstack-global-discover", () => {
12
+ describe("normalizeRemoteUrl", () => {
13
+ // Dynamically import to test the exported function
14
+ let normalizeRemoteUrl: (url: string) => string;
15
+
16
+ beforeEach(async () => {
17
+ const mod = await import("../bin/gstack-global-discover.ts");
18
+ normalizeRemoteUrl = mod.normalizeRemoteUrl;
19
+ });
20
+
21
+ test("strips .git suffix", () => {
22
+ expect(normalizeRemoteUrl("https://github.com/user/repo.git")).toBe(
23
+ "https://github.com/user/repo"
24
+ );
25
+ });
26
+
27
+ test("converts SSH to HTTPS", () => {
28
+ expect(normalizeRemoteUrl("git@github.com:user/repo.git")).toBe(
29
+ "https://github.com/user/repo"
30
+ );
31
+ });
32
+
33
+ test("converts SSH without .git to HTTPS", () => {
34
+ expect(normalizeRemoteUrl("git@github.com:user/repo")).toBe(
35
+ "https://github.com/user/repo"
36
+ );
37
+ });
38
+
39
+ test("lowercases host", () => {
40
+ expect(normalizeRemoteUrl("https://GitHub.COM/user/repo")).toBe(
41
+ "https://github.com/user/repo"
42
+ );
43
+ });
44
+
45
+ test("SSH and HTTPS for same repo normalize to same URL", () => {
46
+ const ssh = normalizeRemoteUrl("git@github.com:garrytan/gstack.git");
47
+ const https = normalizeRemoteUrl("https://github.com/garrytan/gstack.git");
48
+ const httpsNoDotGit = normalizeRemoteUrl("https://github.com/garrytan/gstack");
49
+ expect(ssh).toBe(https);
50
+ expect(https).toBe(httpsNoDotGit);
51
+ });
52
+
53
+ test("handles local: URLs consistently", () => {
54
+ const result = normalizeRemoteUrl("local:/tmp/my-repo");
55
+ // local: gets parsed as a URL scheme — the important thing is consistency
56
+ expect(result).toContain("/tmp/my-repo");
57
+ });
58
+
59
+ test("handles GitLab SSH URLs", () => {
60
+ expect(normalizeRemoteUrl("git@gitlab.com:org/project.git")).toBe(
61
+ "https://gitlab.com/org/project"
62
+ );
63
+ });
64
+ });
65
+
66
+ describe("CLI", () => {
67
+ test("--help exits 0 and prints usage", () => {
68
+ const result = spawnSync("bun", ["run", scriptPath, "--help"], {
69
+ encoding: "utf-8",
70
+ timeout: 10000,
71
+ });
72
+ expect(result.status).toBe(0);
73
+ expect(result.stderr).toContain("--since");
74
+ });
75
+
76
+ test("no args exits 1 with error", () => {
77
+ const result = spawnSync("bun", ["run", scriptPath], {
78
+ encoding: "utf-8",
79
+ timeout: 10000,
80
+ });
81
+ expect(result.status).toBe(1);
82
+ expect(result.stderr).toContain("--since is required");
83
+ });
84
+
85
+ test("invalid window format exits 1", () => {
86
+ const result = spawnSync("bun", ["run", scriptPath, "--since", "abc"], {
87
+ encoding: "utf-8",
88
+ timeout: 10000,
89
+ });
90
+ expect(result.status).toBe(1);
91
+ expect(result.stderr).toContain("Invalid window format");
92
+ });
93
+
94
+ test("--since 7d produces valid JSON", () => {
95
+ const result = spawnSync(
96
+ "bun",
97
+ ["run", scriptPath, "--since", "7d", "--format", "json"],
98
+ { encoding: "utf-8", timeout: 30000 }
99
+ );
100
+ expect(result.status).toBe(0);
101
+ const json = JSON.parse(result.stdout);
102
+ expect(json).toHaveProperty("window", "7d");
103
+ expect(json).toHaveProperty("repos");
104
+ expect(json).toHaveProperty("total_sessions");
105
+ expect(json).toHaveProperty("total_repos");
106
+ expect(json).toHaveProperty("tools");
107
+ expect(Array.isArray(json.repos)).toBe(true);
108
+ });
109
+
110
+ test("--since 7d --format summary produces readable output", () => {
111
+ const result = spawnSync(
112
+ "bun",
113
+ ["run", scriptPath, "--since", "7d", "--format", "summary"],
114
+ { encoding: "utf-8", timeout: 30000 }
115
+ );
116
+ expect(result.status).toBe(0);
117
+ expect(result.stdout).toContain("Window: 7d");
118
+ expect(result.stdout).toContain("Sessions:");
119
+ expect(result.stdout).toContain("Repos:");
120
+ });
121
+
122
+ test("--since 1h returns results (may be empty)", () => {
123
+ const result = spawnSync(
124
+ "bun",
125
+ ["run", scriptPath, "--since", "1h", "--format", "json"],
126
+ { encoding: "utf-8", timeout: 30000 }
127
+ );
128
+ expect(result.status).toBe(0);
129
+ const json = JSON.parse(result.stdout);
130
+ expect(json.total_sessions).toBeGreaterThanOrEqual(0);
131
+ });
132
+ });
133
+
134
+ describe("discovery output structure", () => {
135
+ test("repos have required fields", () => {
136
+ const result = spawnSync(
137
+ "bun",
138
+ ["run", scriptPath, "--since", "30d", "--format", "json"],
139
+ { encoding: "utf-8", timeout: 30000 }
140
+ );
141
+ expect(result.status).toBe(0);
142
+ const json = JSON.parse(result.stdout);
143
+
144
+ for (const repo of json.repos) {
145
+ expect(repo).toHaveProperty("name");
146
+ expect(repo).toHaveProperty("remote");
147
+ expect(repo).toHaveProperty("paths");
148
+ expect(repo).toHaveProperty("sessions");
149
+ expect(Array.isArray(repo.paths)).toBe(true);
150
+ expect(repo.paths.length).toBeGreaterThan(0);
151
+ expect(repo.sessions).toHaveProperty("claude_code");
152
+ expect(repo.sessions).toHaveProperty("codex");
153
+ expect(repo.sessions).toHaveProperty("gemini");
154
+ }
155
+ });
156
+
157
+ test("tools summary matches repo data", () => {
158
+ const result = spawnSync(
159
+ "bun",
160
+ ["run", scriptPath, "--since", "30d", "--format", "json"],
161
+ { encoding: "utf-8", timeout: 30000 }
162
+ );
163
+ const json = JSON.parse(result.stdout);
164
+
165
+ // Total sessions should equal sum across tools
166
+ const toolTotal =
167
+ json.tools.claude_code.total_sessions +
168
+ json.tools.codex.total_sessions +
169
+ json.tools.gemini.total_sessions;
170
+ expect(json.total_sessions).toBe(toolTotal);
171
+ });
172
+
173
+ test("deduplicates Conductor workspaces by remote", () => {
174
+ const result = spawnSync(
175
+ "bun",
176
+ ["run", scriptPath, "--since", "30d", "--format", "json"],
177
+ { encoding: "utf-8", timeout: 30000 }
178
+ );
179
+ const json = JSON.parse(result.stdout);
180
+
181
+ // Check that no two repos share the same normalized remote
182
+ const remotes = json.repos.map((r: any) => r.remote);
183
+ const uniqueRemotes = new Set(remotes);
184
+ expect(remotes.length).toBe(uniqueRemotes.size);
185
+ });
186
+ });
187
+ });
@@ -0,0 +1,282 @@
1
+ /**
2
+ * Codex CLI subprocess runner for skill E2E testing.
3
+ *
4
+ * Spawns `codex exec` as a completely independent process, parses its JSONL
5
+ * output, and returns structured results. Follows the same pattern as
6
+ * session-runner.ts but adapted for the Codex CLI.
7
+ *
8
+ * Key differences from Claude session-runner:
9
+ * - Uses `codex exec` instead of `claude -p`
10
+ * - Output is JSONL with different event types (item.completed, turn.completed, thread.started)
11
+ * - Uses `--json` flag instead of `--output-format stream-json`
12
+ * - Needs temp HOME with skill installed at ~/.codex/skills/{skillName}/SKILL.md
13
+ */
14
+
15
+ import * as fs from 'fs';
16
+ import * as path from 'path';
17
+ import * as os from 'os';
18
+
19
+ // --- Interfaces ---
20
+
21
+ export interface CodexResult {
22
+ output: string; // Full agent message text
23
+ reasoning: string[]; // [codex thinking] blocks
24
+ toolCalls: string[]; // [codex ran] commands
25
+ tokens: number; // Total tokens used
26
+ exitCode: number; // Process exit code
27
+ durationMs: number; // Wall clock time
28
+ sessionId: string | null; // Thread ID for session continuity
29
+ rawLines: string[]; // Raw JSONL lines for debugging
30
+ }
31
+
32
+ // --- JSONL parser (ported from Python in codex/SKILL.md.tmpl) ---
33
+
34
+ export interface ParsedCodexJSONL {
35
+ output: string;
36
+ reasoning: string[];
37
+ toolCalls: string[];
38
+ tokens: number;
39
+ sessionId: string | null;
40
+ }
41
+
42
+ /**
43
+ * Parse an array of JSONL lines from `codex exec --json` into structured data.
44
+ * Pure function — no I/O, no side effects.
45
+ *
46
+ * Handles these Codex event types:
47
+ * - thread.started → extract thread_id (session ID)
48
+ * - item.completed → extract reasoning, agent_message, command_execution
49
+ * - turn.completed → extract token usage
50
+ */
51
+ export function parseCodexJSONL(lines: string[]): ParsedCodexJSONL {
52
+ const outputParts: string[] = [];
53
+ const reasoning: string[] = [];
54
+ const toolCalls: string[] = [];
55
+ let tokens = 0;
56
+ let sessionId: string | null = null;
57
+
58
+ for (const line of lines) {
59
+ if (!line.trim()) continue;
60
+ try {
61
+ const obj = JSON.parse(line);
62
+ const t = obj.type || '';
63
+
64
+ if (t === 'thread.started') {
65
+ const tid = obj.thread_id || '';
66
+ if (tid) sessionId = tid;
67
+ } else if (t === 'item.completed' && obj.item) {
68
+ const item = obj.item;
69
+ const itype = item.type || '';
70
+ const text = item.text || '';
71
+
72
+ if (itype === 'reasoning' && text) {
73
+ reasoning.push(text);
74
+ } else if (itype === 'agent_message' && text) {
75
+ outputParts.push(text);
76
+ } else if (itype === 'command_execution') {
77
+ const cmd = item.command || '';
78
+ if (cmd) toolCalls.push(cmd);
79
+ }
80
+ } else if (t === 'turn.completed') {
81
+ const usage = obj.usage || {};
82
+ const turnTokens = (usage.input_tokens || 0) + (usage.output_tokens || 0);
83
+ tokens += turnTokens;
84
+ }
85
+ } catch { /* skip malformed lines */ }
86
+ }
87
+
88
+ return {
89
+ output: outputParts.join('\n'),
90
+ reasoning,
91
+ toolCalls,
92
+ tokens,
93
+ sessionId,
94
+ };
95
+ }
96
+
97
+ // --- Skill installation helper ---
98
+
99
+ /**
100
+ * Install a SKILL.md into a temp HOME directory for Codex to discover.
101
+ * Creates ~/.codex/skills/{skillName}/SKILL.md in the temp HOME.
102
+ *
103
+ * Returns the temp HOME path. Caller is responsible for cleanup.
104
+ */
105
+ export function installSkillToTempHome(
106
+ skillDir: string,
107
+ skillName: string,
108
+ tempHome?: string,
109
+ ): string {
110
+ const home = tempHome || fs.mkdtempSync(path.join(os.tmpdir(), 'codex-e2e-'));
111
+ const destDir = path.join(home, '.codex', 'skills', skillName);
112
+ fs.mkdirSync(destDir, { recursive: true });
113
+
114
+ const srcSkill = path.join(skillDir, 'SKILL.md');
115
+ if (fs.existsSync(srcSkill)) {
116
+ fs.copyFileSync(srcSkill, path.join(destDir, 'SKILL.md'));
117
+ }
118
+
119
+ return home;
120
+ }
121
+
122
+ // --- Main runner ---
123
+
124
+ /**
125
+ * Run a Codex skill via `codex exec` and return structured results.
126
+ *
127
+ * Spawns codex in a temp HOME with the skill installed, parses JSONL output,
128
+ * and returns a CodexResult. Skips gracefully if codex binary is not found.
129
+ */
130
+ export async function runCodexSkill(opts: {
131
+ skillDir: string; // Path to skill directory containing SKILL.md
132
+ prompt: string; // What to ask Codex to do with the skill
133
+ timeoutMs?: number; // Default 300000 (5 min)
134
+ cwd?: string; // Working directory
135
+ skillName?: string; // Skill name for installation (default: dirname)
136
+ sandbox?: string; // Sandbox mode (default: 'read-only')
137
+ }): Promise<CodexResult> {
138
+ const {
139
+ skillDir,
140
+ prompt,
141
+ timeoutMs = 300_000,
142
+ cwd,
143
+ skillName,
144
+ sandbox = 'read-only',
145
+ } = opts;
146
+
147
+ const startTime = Date.now();
148
+ const name = skillName || path.basename(skillDir) || 'gstack';
149
+
150
+ // Check if codex binary exists
151
+ const whichResult = Bun.spawnSync(['which', 'codex']);
152
+ if (whichResult.exitCode !== 0) {
153
+ return {
154
+ output: 'SKIP: codex binary not found',
155
+ reasoning: [],
156
+ toolCalls: [],
157
+ tokens: 0,
158
+ exitCode: -1,
159
+ durationMs: Date.now() - startTime,
160
+ sessionId: null,
161
+ rawLines: [],
162
+ };
163
+ }
164
+
165
+ // Set up temp HOME with skill installed
166
+ const tempHome = fs.mkdtempSync(path.join(os.tmpdir(), 'codex-e2e-'));
167
+ const realHome = os.homedir();
168
+
169
+ try {
170
+ installSkillToTempHome(skillDir, name, tempHome);
171
+
172
+ // Symlink real Codex auth config so codex can authenticate from temp HOME.
173
+ // Codex stores auth in ~/.codex/ — we need the config but not the skills
174
+ // (we install our own test skills above).
175
+ const realCodexConfig = path.join(realHome, '.codex');
176
+ const tempCodexDir = path.join(tempHome, '.codex');
177
+ if (fs.existsSync(realCodexConfig)) {
178
+ // Copy auth-related files from real ~/.codex/ into temp ~/.codex/
179
+ // (skills/ is already set up by installSkillToTempHome)
180
+ const entries = fs.readdirSync(realCodexConfig);
181
+ for (const entry of entries) {
182
+ if (entry === 'skills') continue; // don't clobber our test skills
183
+ const src = path.join(realCodexConfig, entry);
184
+ const dst = path.join(tempCodexDir, entry);
185
+ if (!fs.existsSync(dst)) {
186
+ fs.cpSync(src, dst, { recursive: true });
187
+ }
188
+ }
189
+ }
190
+
191
+ // Build codex exec command
192
+ const args = ['exec', prompt, '--json', '-s', sandbox];
193
+
194
+ // Spawn codex with temp HOME so it discovers our installed skill
195
+ const proc = Bun.spawn(['codex', ...args], {
196
+ cwd: cwd || skillDir,
197
+ stdout: 'pipe',
198
+ stderr: 'pipe',
199
+ env: {
200
+ ...process.env,
201
+ HOME: tempHome,
202
+ },
203
+ });
204
+
205
+ // Race against timeout
206
+ let timedOut = false;
207
+ const timeoutId = setTimeout(() => {
208
+ timedOut = true;
209
+ proc.kill();
210
+ }, timeoutMs);
211
+
212
+ // Stream and collect JSONL from stdout
213
+ const collectedLines: string[] = [];
214
+ const stderrPromise = new Response(proc.stderr).text();
215
+
216
+ const reader = proc.stdout.getReader();
217
+ const decoder = new TextDecoder();
218
+ let buf = '';
219
+
220
+ try {
221
+ while (true) {
222
+ const { done, value } = await reader.read();
223
+ if (done) break;
224
+ buf += decoder.decode(value, { stream: true });
225
+ const lines = buf.split('\n');
226
+ buf = lines.pop() || '';
227
+ for (const line of lines) {
228
+ if (!line.trim()) continue;
229
+ collectedLines.push(line);
230
+
231
+ // Real-time progress to stderr
232
+ try {
233
+ const event = JSON.parse(line);
234
+ if (event.type === 'item.completed' && event.item) {
235
+ const item = event.item;
236
+ if (item.type === 'command_execution' && item.command) {
237
+ const elapsed = Math.round((Date.now() - startTime) / 1000);
238
+ process.stderr.write(` [codex ${elapsed}s] ran: ${item.command.slice(0, 100)}\n`);
239
+ } else if (item.type === 'agent_message' && item.text) {
240
+ const elapsed = Math.round((Date.now() - startTime) / 1000);
241
+ process.stderr.write(` [codex ${elapsed}s] message: ${item.text.slice(0, 100)}\n`);
242
+ }
243
+ }
244
+ } catch { /* skip — parseCodexJSONL will handle it later */ }
245
+ }
246
+ }
247
+ } catch { /* stream read error — fall through to exit code handling */ }
248
+
249
+ // Flush remaining buffer
250
+ if (buf.trim()) {
251
+ collectedLines.push(buf);
252
+ }
253
+
254
+ const stderr = await stderrPromise;
255
+ const exitCode = await proc.exited;
256
+ clearTimeout(timeoutId);
257
+
258
+ const durationMs = Date.now() - startTime;
259
+
260
+ // Parse all collected JSONL lines
261
+ const parsed = parseCodexJSONL(collectedLines);
262
+
263
+ // Log stderr if non-empty (may contain auth errors, etc.)
264
+ if (stderr.trim()) {
265
+ process.stderr.write(` [codex stderr] ${stderr.trim().slice(0, 200)}\n`);
266
+ }
267
+
268
+ return {
269
+ output: parsed.output,
270
+ reasoning: parsed.reasoning,
271
+ toolCalls: parsed.toolCalls,
272
+ tokens: parsed.tokens,
273
+ exitCode: timedOut ? 124 : exitCode,
274
+ durationMs,
275
+ sessionId: parsed.sessionId,
276
+ rawLines: collectedLines,
277
+ };
278
+ } finally {
279
+ // Clean up temp HOME
280
+ try { fs.rmSync(tempHome, { recursive: true, force: true }); } catch { /* non-fatal */ }
281
+ }
282
+ }