@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.
- package/.agents/skills/gstack/.agents/skills/gstack/SKILL.md +651 -0
- package/.agents/skills/gstack/.agents/skills/gstack-autoplan/SKILL.md +678 -0
- package/.agents/skills/gstack/.agents/skills/gstack-benchmark/SKILL.md +482 -0
- package/.agents/skills/gstack/.agents/skills/gstack-browse/SKILL.md +511 -0
- package/.agents/skills/gstack/.agents/skills/gstack-canary/SKILL.md +486 -0
- package/.agents/skills/gstack/.agents/skills/gstack-careful/SKILL.md +50 -0
- package/.agents/skills/gstack/.agents/skills/gstack-cso/SKILL.md +607 -0
- package/.agents/skills/gstack/.agents/skills/gstack-design-consultation/SKILL.md +615 -0
- package/.agents/skills/gstack/.agents/skills/gstack-design-review/SKILL.md +988 -0
- package/.agents/skills/gstack/.agents/skills/gstack-document-release/SKILL.md +604 -0
- package/.agents/skills/gstack/.agents/skills/gstack-freeze/SKILL.md +67 -0
- package/.agents/skills/gstack/.agents/skills/gstack-guard/SKILL.md +62 -0
- package/.agents/skills/gstack/.agents/skills/gstack-investigate/SKILL.md +415 -0
- package/.agents/skills/gstack/.agents/skills/gstack-land-and-deploy/SKILL.md +873 -0
- package/.agents/skills/gstack/.agents/skills/gstack-office-hours/SKILL.md +986 -0
- package/.agents/skills/gstack/.agents/skills/gstack-plan-ceo-review/SKILL.md +1268 -0
- package/.agents/skills/gstack/.agents/skills/gstack-plan-design-review/SKILL.md +668 -0
- package/.agents/skills/gstack/.agents/skills/gstack-plan-eng-review/SKILL.md +826 -0
- package/.agents/skills/gstack/.agents/skills/gstack-qa/SKILL.md +1006 -0
- package/.agents/skills/gstack/.agents/skills/gstack-qa-only/SKILL.md +626 -0
- package/.agents/skills/gstack/.agents/skills/gstack-retro/SKILL.md +1065 -0
- package/.agents/skills/gstack/.agents/skills/gstack-review/SKILL.md +704 -0
- package/.agents/skills/gstack/.agents/skills/gstack-setup-browser-cookies/SKILL.md +325 -0
- package/.agents/skills/gstack/.agents/skills/gstack-setup-deploy/SKILL.md +450 -0
- package/.agents/skills/gstack/.agents/skills/gstack-ship/SKILL.md +1312 -0
- package/.agents/skills/gstack/.agents/skills/gstack-unfreeze/SKILL.md +36 -0
- package/.agents/skills/gstack/.agents/skills/gstack-upgrade/SKILL.md +220 -0
- package/.agents/skills/gstack/.env.example +5 -0
- package/.agents/skills/gstack/.github/workflows/skill-docs.yml +17 -0
- package/.agents/skills/gstack/AGENTS.md +49 -0
- package/.agents/skills/gstack/ARCHITECTURE.md +359 -0
- package/.agents/skills/gstack/BROWSER.md +271 -0
- package/.agents/skills/gstack/CHANGELOG.md +800 -0
- package/.agents/skills/gstack/CLAUDE.md +284 -0
- package/.agents/skills/gstack/CONTRIBUTING.md +370 -0
- package/.agents/skills/gstack/ETHOS.md +129 -0
- package/.agents/skills/gstack/LICENSE +21 -0
- package/.agents/skills/gstack/README.md +228 -0
- package/.agents/skills/gstack/SKILL.md +657 -0
- package/.agents/skills/gstack/SKILL.md.tmpl +281 -0
- package/.agents/skills/gstack/TODOS.md +564 -0
- package/.agents/skills/gstack/VERSION +1 -0
- package/.agents/skills/gstack/autoplan/SKILL.md +689 -0
- package/.agents/skills/gstack/autoplan/SKILL.md.tmpl +416 -0
- package/.agents/skills/gstack/benchmark/SKILL.md +489 -0
- package/.agents/skills/gstack/benchmark/SKILL.md.tmpl +233 -0
- package/.agents/skills/gstack/bin/dev-setup +68 -0
- package/.agents/skills/gstack/bin/dev-teardown +56 -0
- package/.agents/skills/gstack/bin/gstack-analytics +191 -0
- package/.agents/skills/gstack/bin/gstack-community-dashboard +113 -0
- package/.agents/skills/gstack/bin/gstack-config +38 -0
- package/.agents/skills/gstack/bin/gstack-diff-scope +71 -0
- package/.agents/skills/gstack/bin/gstack-global-discover.ts +591 -0
- package/.agents/skills/gstack/bin/gstack-repo-mode +93 -0
- package/.agents/skills/gstack/bin/gstack-review-log +9 -0
- package/.agents/skills/gstack/bin/gstack-review-read +12 -0
- package/.agents/skills/gstack/bin/gstack-slug +15 -0
- package/.agents/skills/gstack/bin/gstack-telemetry-log +158 -0
- package/.agents/skills/gstack/bin/gstack-telemetry-sync +127 -0
- package/.agents/skills/gstack/bin/gstack-update-check +196 -0
- package/.agents/skills/gstack/browse/SKILL.md +517 -0
- package/.agents/skills/gstack/browse/SKILL.md.tmpl +141 -0
- package/.agents/skills/gstack/browse/bin/find-browse +21 -0
- package/.agents/skills/gstack/browse/bin/remote-slug +14 -0
- package/.agents/skills/gstack/browse/scripts/build-node-server.sh +48 -0
- package/.agents/skills/gstack/browse/src/browser-manager.ts +634 -0
- package/.agents/skills/gstack/browse/src/buffers.ts +137 -0
- package/.agents/skills/gstack/browse/src/bun-polyfill.cjs +109 -0
- package/.agents/skills/gstack/browse/src/cli.ts +420 -0
- package/.agents/skills/gstack/browse/src/commands.ts +111 -0
- package/.agents/skills/gstack/browse/src/config.ts +150 -0
- package/.agents/skills/gstack/browse/src/cookie-import-browser.ts +417 -0
- package/.agents/skills/gstack/browse/src/cookie-picker-routes.ts +207 -0
- package/.agents/skills/gstack/browse/src/cookie-picker-ui.ts +541 -0
- package/.agents/skills/gstack/browse/src/find-browse.ts +61 -0
- package/.agents/skills/gstack/browse/src/meta-commands.ts +269 -0
- package/.agents/skills/gstack/browse/src/platform.ts +17 -0
- package/.agents/skills/gstack/browse/src/read-commands.ts +335 -0
- package/.agents/skills/gstack/browse/src/server.ts +369 -0
- package/.agents/skills/gstack/browse/src/snapshot.ts +398 -0
- package/.agents/skills/gstack/browse/src/url-validation.ts +91 -0
- package/.agents/skills/gstack/browse/src/write-commands.ts +352 -0
- package/.agents/skills/gstack/browse/test/bun-polyfill.test.ts +72 -0
- package/.agents/skills/gstack/browse/test/commands.test.ts +1836 -0
- package/.agents/skills/gstack/browse/test/config.test.ts +250 -0
- package/.agents/skills/gstack/browse/test/cookie-import-browser.test.ts +397 -0
- package/.agents/skills/gstack/browse/test/cookie-picker-routes.test.ts +205 -0
- package/.agents/skills/gstack/browse/test/find-browse.test.ts +50 -0
- package/.agents/skills/gstack/browse/test/fixtures/basic.html +33 -0
- package/.agents/skills/gstack/browse/test/fixtures/cursor-interactive.html +22 -0
- package/.agents/skills/gstack/browse/test/fixtures/dialog.html +15 -0
- package/.agents/skills/gstack/browse/test/fixtures/empty.html +2 -0
- package/.agents/skills/gstack/browse/test/fixtures/forms.html +55 -0
- package/.agents/skills/gstack/browse/test/fixtures/qa-eval-checkout.html +108 -0
- package/.agents/skills/gstack/browse/test/fixtures/qa-eval-spa.html +98 -0
- package/.agents/skills/gstack/browse/test/fixtures/qa-eval.html +51 -0
- package/.agents/skills/gstack/browse/test/fixtures/responsive.html +49 -0
- package/.agents/skills/gstack/browse/test/fixtures/snapshot.html +55 -0
- package/.agents/skills/gstack/browse/test/fixtures/spa.html +24 -0
- package/.agents/skills/gstack/browse/test/fixtures/states.html +17 -0
- package/.agents/skills/gstack/browse/test/fixtures/upload.html +25 -0
- package/.agents/skills/gstack/browse/test/gstack-config.test.ts +125 -0
- package/.agents/skills/gstack/browse/test/gstack-update-check.test.ts +467 -0
- package/.agents/skills/gstack/browse/test/handoff.test.ts +235 -0
- package/.agents/skills/gstack/browse/test/path-validation.test.ts +63 -0
- package/.agents/skills/gstack/browse/test/platform.test.ts +37 -0
- package/.agents/skills/gstack/browse/test/snapshot.test.ts +467 -0
- package/.agents/skills/gstack/browse/test/test-server.ts +57 -0
- package/.agents/skills/gstack/browse/test/url-validation.test.ts +72 -0
- package/.agents/skills/gstack/canary/SKILL.md +493 -0
- package/.agents/skills/gstack/canary/SKILL.md.tmpl +220 -0
- package/.agents/skills/gstack/careful/SKILL.md +59 -0
- package/.agents/skills/gstack/careful/SKILL.md.tmpl +57 -0
- package/.agents/skills/gstack/careful/bin/check-careful.sh +112 -0
- package/.agents/skills/gstack/codex/SKILL.md +677 -0
- package/.agents/skills/gstack/codex/SKILL.md.tmpl +356 -0
- package/.agents/skills/gstack/conductor.json +6 -0
- package/.agents/skills/gstack/cso/SKILL.md +615 -0
- package/.agents/skills/gstack/cso/SKILL.md.tmpl +376 -0
- package/.agents/skills/gstack/design-consultation/SKILL.md +625 -0
- package/.agents/skills/gstack/design-consultation/SKILL.md.tmpl +369 -0
- package/.agents/skills/gstack/design-review/SKILL.md +998 -0
- package/.agents/skills/gstack/design-review/SKILL.md.tmpl +262 -0
- package/.agents/skills/gstack/docs/images/github-2013.png +0 -0
- package/.agents/skills/gstack/docs/images/github-2026.png +0 -0
- package/.agents/skills/gstack/docs/skills.md +877 -0
- package/.agents/skills/gstack/document-release/SKILL.md +613 -0
- package/.agents/skills/gstack/document-release/SKILL.md.tmpl +357 -0
- package/.agents/skills/gstack/freeze/SKILL.md +82 -0
- package/.agents/skills/gstack/freeze/SKILL.md.tmpl +80 -0
- package/.agents/skills/gstack/freeze/bin/check-freeze.sh +68 -0
- package/.agents/skills/gstack/gstack-upgrade/SKILL.md +226 -0
- package/.agents/skills/gstack/gstack-upgrade/SKILL.md.tmpl +224 -0
- package/.agents/skills/gstack/guard/SKILL.md +82 -0
- package/.agents/skills/gstack/guard/SKILL.md.tmpl +80 -0
- package/.agents/skills/gstack/investigate/SKILL.md +435 -0
- package/.agents/skills/gstack/investigate/SKILL.md.tmpl +196 -0
- package/.agents/skills/gstack/land-and-deploy/SKILL.md +880 -0
- package/.agents/skills/gstack/land-and-deploy/SKILL.md.tmpl +575 -0
- package/.agents/skills/gstack/office-hours/SKILL.md +996 -0
- package/.agents/skills/gstack/office-hours/SKILL.md.tmpl +624 -0
- package/.agents/skills/gstack/package.json +55 -0
- package/.agents/skills/gstack/plan-ceo-review/SKILL.md +1277 -0
- package/.agents/skills/gstack/plan-ceo-review/SKILL.md.tmpl +838 -0
- package/.agents/skills/gstack/plan-design-review/SKILL.md +676 -0
- package/.agents/skills/gstack/plan-design-review/SKILL.md.tmpl +314 -0
- package/.agents/skills/gstack/plan-eng-review/SKILL.md +836 -0
- package/.agents/skills/gstack/plan-eng-review/SKILL.md.tmpl +279 -0
- package/.agents/skills/gstack/qa/SKILL.md +1016 -0
- package/.agents/skills/gstack/qa/SKILL.md.tmpl +316 -0
- package/.agents/skills/gstack/qa/references/issue-taxonomy.md +85 -0
- package/.agents/skills/gstack/qa/templates/qa-report-template.md +126 -0
- package/.agents/skills/gstack/qa-only/SKILL.md +633 -0
- package/.agents/skills/gstack/qa-only/SKILL.md.tmpl +101 -0
- package/.agents/skills/gstack/retro/SKILL.md +1072 -0
- package/.agents/skills/gstack/retro/SKILL.md.tmpl +833 -0
- package/.agents/skills/gstack/review/SKILL.md +849 -0
- package/.agents/skills/gstack/review/SKILL.md.tmpl +259 -0
- package/.agents/skills/gstack/review/TODOS-format.md +62 -0
- package/.agents/skills/gstack/review/checklist.md +190 -0
- package/.agents/skills/gstack/review/design-checklist.md +132 -0
- package/.agents/skills/gstack/review/greptile-triage.md +220 -0
- package/.agents/skills/gstack/scripts/analytics.ts +190 -0
- package/.agents/skills/gstack/scripts/dev-skill.ts +82 -0
- package/.agents/skills/gstack/scripts/eval-compare.ts +96 -0
- package/.agents/skills/gstack/scripts/eval-list.ts +116 -0
- package/.agents/skills/gstack/scripts/eval-select.ts +86 -0
- package/.agents/skills/gstack/scripts/eval-summary.ts +187 -0
- package/.agents/skills/gstack/scripts/eval-watch.ts +172 -0
- package/.agents/skills/gstack/scripts/gen-skill-docs.ts +2414 -0
- package/.agents/skills/gstack/scripts/skill-check.ts +167 -0
- package/.agents/skills/gstack/setup +269 -0
- package/.agents/skills/gstack/setup-browser-cookies/SKILL.md +330 -0
- package/.agents/skills/gstack/setup-browser-cookies/SKILL.md.tmpl +74 -0
- package/.agents/skills/gstack/setup-deploy/SKILL.md +459 -0
- package/.agents/skills/gstack/setup-deploy/SKILL.md.tmpl +220 -0
- package/.agents/skills/gstack/ship/SKILL.md +1457 -0
- package/.agents/skills/gstack/ship/SKILL.md.tmpl +528 -0
- package/.agents/skills/gstack/supabase/config.sh +10 -0
- package/.agents/skills/gstack/supabase/functions/community-pulse/index.ts +59 -0
- package/.agents/skills/gstack/supabase/functions/telemetry-ingest/index.ts +135 -0
- package/.agents/skills/gstack/supabase/functions/update-check/index.ts +37 -0
- package/.agents/skills/gstack/supabase/migrations/001_telemetry.sql +89 -0
- package/.agents/skills/gstack/test/analytics.test.ts +277 -0
- package/.agents/skills/gstack/test/codex-e2e.test.ts +197 -0
- package/.agents/skills/gstack/test/fixtures/coverage-audit-fixture.ts +76 -0
- package/.agents/skills/gstack/test/fixtures/eval-baselines.json +7 -0
- package/.agents/skills/gstack/test/fixtures/qa-eval-checkout-ground-truth.json +43 -0
- package/.agents/skills/gstack/test/fixtures/qa-eval-ground-truth.json +43 -0
- package/.agents/skills/gstack/test/fixtures/qa-eval-spa-ground-truth.json +43 -0
- package/.agents/skills/gstack/test/fixtures/review-eval-design-slop.css +86 -0
- package/.agents/skills/gstack/test/fixtures/review-eval-design-slop.html +41 -0
- package/.agents/skills/gstack/test/fixtures/review-eval-enum-diff.rb +30 -0
- package/.agents/skills/gstack/test/fixtures/review-eval-enum.rb +27 -0
- package/.agents/skills/gstack/test/fixtures/review-eval-vuln.rb +14 -0
- package/.agents/skills/gstack/test/gemini-e2e.test.ts +173 -0
- package/.agents/skills/gstack/test/gen-skill-docs.test.ts +1049 -0
- package/.agents/skills/gstack/test/global-discover.test.ts +187 -0
- package/.agents/skills/gstack/test/helpers/codex-session-runner.ts +282 -0
- package/.agents/skills/gstack/test/helpers/e2e-helpers.ts +239 -0
- package/.agents/skills/gstack/test/helpers/eval-store.test.ts +548 -0
- package/.agents/skills/gstack/test/helpers/eval-store.ts +689 -0
- package/.agents/skills/gstack/test/helpers/gemini-session-runner.test.ts +104 -0
- package/.agents/skills/gstack/test/helpers/gemini-session-runner.ts +201 -0
- package/.agents/skills/gstack/test/helpers/llm-judge.ts +130 -0
- package/.agents/skills/gstack/test/helpers/observability.test.ts +283 -0
- package/.agents/skills/gstack/test/helpers/session-runner.test.ts +96 -0
- package/.agents/skills/gstack/test/helpers/session-runner.ts +357 -0
- package/.agents/skills/gstack/test/helpers/skill-parser.ts +206 -0
- package/.agents/skills/gstack/test/helpers/touchfiles.ts +260 -0
- package/.agents/skills/gstack/test/hook-scripts.test.ts +373 -0
- package/.agents/skills/gstack/test/skill-e2e-browse.test.ts +293 -0
- package/.agents/skills/gstack/test/skill-e2e-deploy.test.ts +279 -0
- package/.agents/skills/gstack/test/skill-e2e-design.test.ts +614 -0
- package/.agents/skills/gstack/test/skill-e2e-plan.test.ts +538 -0
- package/.agents/skills/gstack/test/skill-e2e-qa-bugs.test.ts +194 -0
- package/.agents/skills/gstack/test/skill-e2e-qa-workflow.test.ts +412 -0
- package/.agents/skills/gstack/test/skill-e2e-review.test.ts +535 -0
- package/.agents/skills/gstack/test/skill-e2e-workflow.test.ts +586 -0
- package/.agents/skills/gstack/test/skill-e2e.test.ts +3325 -0
- package/.agents/skills/gstack/test/skill-llm-eval.test.ts +787 -0
- package/.agents/skills/gstack/test/skill-parser.test.ts +179 -0
- package/.agents/skills/gstack/test/skill-routing-e2e.test.ts +605 -0
- package/.agents/skills/gstack/test/skill-validation.test.ts +1520 -0
- package/.agents/skills/gstack/test/telemetry.test.ts +278 -0
- package/.agents/skills/gstack/test/touchfiles.test.ts +262 -0
- package/.agents/skills/gstack/unfreeze/SKILL.md +40 -0
- package/.agents/skills/gstack/unfreeze/SKILL.md.tmpl +38 -0
- package/README.md +12 -7
- package/README_KO.md +12 -6
- 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
|
+
}
|