@runchr/gstack-antigravity 0.1.1 → 0.1.3

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 (229) 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/package.json +2 -1
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env bash
2
+ # gstack-config — read/write ~/.gstack/config.yaml
3
+ #
4
+ # Usage:
5
+ # gstack-config get <key> — read a config value
6
+ # gstack-config set <key> <value> — write a config value
7
+ # gstack-config list — show all config
8
+ #
9
+ # Env overrides (for testing):
10
+ # GSTACK_STATE_DIR — override ~/.gstack state directory
11
+ set -euo pipefail
12
+
13
+ STATE_DIR="${GSTACK_STATE_DIR:-$HOME/.gstack}"
14
+ CONFIG_FILE="$STATE_DIR/config.yaml"
15
+
16
+ case "${1:-}" in
17
+ get)
18
+ KEY="${2:?Usage: gstack-config get <key>}"
19
+ grep -E "^${KEY}:" "$CONFIG_FILE" 2>/dev/null | tail -1 | awk '{print $2}' | tr -d '[:space:]' || true
20
+ ;;
21
+ set)
22
+ KEY="${2:?Usage: gstack-config set <key> <value>}"
23
+ VALUE="${3:?Usage: gstack-config set <key> <value>}"
24
+ mkdir -p "$STATE_DIR"
25
+ if grep -qE "^${KEY}:" "$CONFIG_FILE" 2>/dev/null; then
26
+ sed -i '' "s/^${KEY}:.*/${KEY}: ${VALUE}/" "$CONFIG_FILE"
27
+ else
28
+ echo "${KEY}: ${VALUE}" >> "$CONFIG_FILE"
29
+ fi
30
+ ;;
31
+ list)
32
+ cat "$CONFIG_FILE" 2>/dev/null || true
33
+ ;;
34
+ *)
35
+ echo "Usage: gstack-config {get|set|list} [key] [value]"
36
+ exit 1
37
+ ;;
38
+ esac
@@ -0,0 +1,71 @@
1
+ #!/usr/bin/env bash
2
+ # gstack-diff-scope — categorize what changed in the diff against a base branch
3
+ # Usage: source <(gstack-diff-scope main) → sets SCOPE_FRONTEND=true SCOPE_BACKEND=false ...
4
+ # Or: gstack-diff-scope main → prints SCOPE_*=... lines
5
+ set -euo pipefail
6
+
7
+ BASE="${1:-main}"
8
+
9
+ # Get changed file list
10
+ FILES=$(git diff "${BASE}...HEAD" --name-only 2>/dev/null || git diff "${BASE}" --name-only 2>/dev/null || echo "")
11
+
12
+ if [ -z "$FILES" ]; then
13
+ echo "SCOPE_FRONTEND=false"
14
+ echo "SCOPE_BACKEND=false"
15
+ echo "SCOPE_PROMPTS=false"
16
+ echo "SCOPE_TESTS=false"
17
+ echo "SCOPE_DOCS=false"
18
+ echo "SCOPE_CONFIG=false"
19
+ exit 0
20
+ fi
21
+
22
+ FRONTEND=false
23
+ BACKEND=false
24
+ PROMPTS=false
25
+ TESTS=false
26
+ DOCS=false
27
+ CONFIG=false
28
+
29
+ while IFS= read -r f; do
30
+ case "$f" in
31
+ # Frontend: CSS, views, components, templates
32
+ *.css|*.scss|*.less|*.sass|*.pcss|*.module.css|*.module.scss) FRONTEND=true ;;
33
+ *.tsx|*.jsx|*.vue|*.svelte|*.astro) FRONTEND=true ;;
34
+ *.erb|*.haml|*.slim|*.hbs|*.ejs) FRONTEND=true ;;
35
+ *.html) FRONTEND=true ;;
36
+ tailwind.config.*|postcss.config.*) FRONTEND=true ;;
37
+ app/views/*|*/components/*|styles/*|css/*|app/assets/stylesheets/*) FRONTEND=true ;;
38
+
39
+ # Prompts: prompt builders, system prompts, generation services
40
+ *prompt_builder*|*generation_service*|*writer_service*|*designer_service*) PROMPTS=true ;;
41
+ *evaluator*|*scorer*|*classifier_service*|*analyzer*) PROMPTS=true ;;
42
+ *voice*.rb|*writing*.rb|*prompt*.rb|*token*.rb) PROMPTS=true ;;
43
+ app/services/chat_tools/*|app/services/x_thread_tools/*) PROMPTS=true ;;
44
+ config/system_prompts/*) PROMPTS=true ;;
45
+
46
+ # Tests
47
+ *.test.*|*.spec.*|*_test.*|*_spec.*) TESTS=true ;;
48
+ test/*|tests/*|spec/*|__tests__/*|cypress/*|e2e/*) TESTS=true ;;
49
+
50
+ # Docs
51
+ *.md) DOCS=true ;;
52
+
53
+ # Config
54
+ package.json|package-lock.json|yarn.lock|bun.lockb) CONFIG=true ;;
55
+ Gemfile|Gemfile.lock) CONFIG=true ;;
56
+ *.yml|*.yaml) CONFIG=true ;;
57
+ .github/*) CONFIG=true ;;
58
+ requirements.txt|pyproject.toml|go.mod|Cargo.toml|composer.json) CONFIG=true ;;
59
+
60
+ # Backend: everything else that's code (excluding views/components already matched)
61
+ *.rb|*.py|*.go|*.rs|*.java|*.php|*.ex|*.exs) BACKEND=true ;;
62
+ *.ts|*.js) BACKEND=true ;; # Non-component TS/JS is backend
63
+ esac
64
+ done <<< "$FILES"
65
+
66
+ echo "SCOPE_FRONTEND=$FRONTEND"
67
+ echo "SCOPE_BACKEND=$BACKEND"
68
+ echo "SCOPE_PROMPTS=$PROMPTS"
69
+ echo "SCOPE_TESTS=$TESTS"
70
+ echo "SCOPE_DOCS=$DOCS"
71
+ echo "SCOPE_CONFIG=$CONFIG"
@@ -0,0 +1,591 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * gstack-global-discover — Discover AI coding sessions across Claude Code, Codex CLI, and Gemini CLI.
4
+ * Resolves each session's working directory to a git repo, deduplicates by normalized remote URL,
5
+ * and outputs structured JSON to stdout.
6
+ *
7
+ * Usage:
8
+ * gstack-global-discover --since 7d [--format json|summary]
9
+ * gstack-global-discover --help
10
+ */
11
+
12
+ import { existsSync, readdirSync, statSync, readFileSync, openSync, readSync, closeSync } from "fs";
13
+ import { join, basename } from "path";
14
+ import { execSync } from "child_process";
15
+ import { homedir } from "os";
16
+
17
+ // ── Types ──────────────────────────────────────────────────────────────────
18
+
19
+ interface Session {
20
+ tool: "claude_code" | "codex" | "gemini";
21
+ cwd: string;
22
+ }
23
+
24
+ interface Repo {
25
+ name: string;
26
+ remote: string;
27
+ paths: string[];
28
+ sessions: { claude_code: number; codex: number; gemini: number };
29
+ }
30
+
31
+ interface DiscoveryResult {
32
+ window: string;
33
+ start_date: string;
34
+ repos: Repo[];
35
+ tools: {
36
+ claude_code: { total_sessions: number; repos: number };
37
+ codex: { total_sessions: number; repos: number };
38
+ gemini: { total_sessions: number; repos: number };
39
+ };
40
+ total_sessions: number;
41
+ total_repos: number;
42
+ }
43
+
44
+ // ── CLI parsing ────────────────────────────────────────────────────────────
45
+
46
+ function printUsage(): void {
47
+ console.error(`Usage: gstack-global-discover --since <window> [--format json|summary]
48
+
49
+ --since <window> Time window: e.g. 7d, 14d, 30d, 24h
50
+ --format <fmt> Output format: json (default) or summary
51
+ --help Show this help
52
+
53
+ Examples:
54
+ gstack-global-discover --since 7d
55
+ gstack-global-discover --since 14d --format summary`);
56
+ }
57
+
58
+ function parseArgs(): { since: string; format: "json" | "summary" } {
59
+ const args = process.argv.slice(2);
60
+ let since = "";
61
+ let format: "json" | "summary" = "json";
62
+
63
+ for (let i = 0; i < args.length; i++) {
64
+ if (args[i] === "--help" || args[i] === "-h") {
65
+ printUsage();
66
+ process.exit(0);
67
+ } else if (args[i] === "--since" && args[i + 1]) {
68
+ since = args[++i];
69
+ } else if (args[i] === "--format" && args[i + 1]) {
70
+ const f = args[++i];
71
+ if (f !== "json" && f !== "summary") {
72
+ console.error(`Invalid format: ${f}. Use 'json' or 'summary'.`);
73
+ printUsage();
74
+ process.exit(1);
75
+ }
76
+ format = f;
77
+ } else {
78
+ console.error(`Unknown argument: ${args[i]}`);
79
+ printUsage();
80
+ process.exit(1);
81
+ }
82
+ }
83
+
84
+ if (!since) {
85
+ console.error("Error: --since is required.");
86
+ printUsage();
87
+ process.exit(1);
88
+ }
89
+
90
+ if (!/^\d+(d|h|w)$/.test(since)) {
91
+ console.error(`Invalid window format: ${since}. Use e.g. 7d, 24h, 2w.`);
92
+ process.exit(1);
93
+ }
94
+
95
+ return { since, format };
96
+ }
97
+
98
+ function windowToDate(window: string): Date {
99
+ const match = window.match(/^(\d+)(d|h|w)$/);
100
+ if (!match) throw new Error(`Invalid window: ${window}`);
101
+ const [, numStr, unit] = match;
102
+ const num = parseInt(numStr, 10);
103
+ const now = new Date();
104
+
105
+ if (unit === "h") {
106
+ return new Date(now.getTime() - num * 60 * 60 * 1000);
107
+ } else if (unit === "w") {
108
+ // weeks — midnight-aligned like days
109
+ const d = new Date(now);
110
+ d.setDate(d.getDate() - num * 7);
111
+ d.setHours(0, 0, 0, 0);
112
+ return d;
113
+ } else {
114
+ // days — midnight-aligned
115
+ const d = new Date(now);
116
+ d.setDate(d.getDate() - num);
117
+ d.setHours(0, 0, 0, 0);
118
+ return d;
119
+ }
120
+ }
121
+
122
+ // ── URL normalization ──────────────────────────────────────────────────────
123
+
124
+ export function normalizeRemoteUrl(url: string): string {
125
+ let normalized = url.trim();
126
+
127
+ // SSH → HTTPS: git@github.com:user/repo → https://github.com/user/repo
128
+ const sshMatch = normalized.match(/^(?:ssh:\/\/)?git@([^:]+):(.+)$/);
129
+ if (sshMatch) {
130
+ normalized = `https://${sshMatch[1]}/${sshMatch[2]}`;
131
+ }
132
+
133
+ // Strip .git suffix
134
+ if (normalized.endsWith(".git")) {
135
+ normalized = normalized.slice(0, -4);
136
+ }
137
+
138
+ // Lowercase the host portion
139
+ try {
140
+ const parsed = new URL(normalized);
141
+ parsed.hostname = parsed.hostname.toLowerCase();
142
+ normalized = parsed.toString();
143
+ // Remove trailing slash
144
+ if (normalized.endsWith("/")) {
145
+ normalized = normalized.slice(0, -1);
146
+ }
147
+ } catch {
148
+ // Not a valid URL (e.g., local:<path>), return as-is
149
+ }
150
+
151
+ return normalized;
152
+ }
153
+
154
+ // ── Git helpers ────────────────────────────────────────────────────────────
155
+
156
+ function isGitRepo(dir: string): boolean {
157
+ return existsSync(join(dir, ".git"));
158
+ }
159
+
160
+ function getGitRemote(cwd: string): string | null {
161
+ if (!existsSync(cwd) || !isGitRepo(cwd)) return null;
162
+ try {
163
+ const remote = execSync("git remote get-url origin", {
164
+ cwd,
165
+ encoding: "utf-8",
166
+ timeout: 5000,
167
+ stdio: ["pipe", "pipe", "pipe"],
168
+ }).trim();
169
+ return remote || null;
170
+ } catch {
171
+ return null;
172
+ }
173
+ }
174
+
175
+ // ── Scanners ───────────────────────────────────────────────────────────────
176
+
177
+ function scanClaudeCode(since: Date): Session[] {
178
+ const projectsDir = join(homedir(), ".claude", "projects");
179
+ if (!existsSync(projectsDir)) return [];
180
+
181
+ const sessions: Session[] = [];
182
+
183
+ let dirs: string[];
184
+ try {
185
+ dirs = readdirSync(projectsDir);
186
+ } catch {
187
+ return [];
188
+ }
189
+
190
+ for (const dirName of dirs) {
191
+ const dirPath = join(projectsDir, dirName);
192
+ try {
193
+ const stat = statSync(dirPath);
194
+ if (!stat.isDirectory()) continue;
195
+ } catch {
196
+ continue;
197
+ }
198
+
199
+ // Find JSONL files
200
+ let jsonlFiles: string[];
201
+ try {
202
+ jsonlFiles = readdirSync(dirPath).filter((f) => f.endsWith(".jsonl"));
203
+ } catch {
204
+ continue;
205
+ }
206
+ if (jsonlFiles.length === 0) continue;
207
+
208
+ // Coarse mtime pre-filter: check if any JSONL file is recent
209
+ const hasRecentFile = jsonlFiles.some((f) => {
210
+ try {
211
+ return statSync(join(dirPath, f)).mtime >= since;
212
+ } catch {
213
+ return false;
214
+ }
215
+ });
216
+ if (!hasRecentFile) continue;
217
+
218
+ // Resolve cwd
219
+ let cwd = resolveClaudeCodeCwd(dirPath, dirName, jsonlFiles);
220
+ if (!cwd) continue;
221
+
222
+ // Count only JSONL files modified within the window as sessions
223
+ const recentFiles = jsonlFiles.filter((f) => {
224
+ try {
225
+ return statSync(join(dirPath, f)).mtime >= since;
226
+ } catch {
227
+ return false;
228
+ }
229
+ });
230
+ for (let i = 0; i < recentFiles.length; i++) {
231
+ sessions.push({ tool: "claude_code", cwd });
232
+ }
233
+ }
234
+
235
+ return sessions;
236
+ }
237
+
238
+ function resolveClaudeCodeCwd(
239
+ dirPath: string,
240
+ dirName: string,
241
+ jsonlFiles: string[]
242
+ ): string | null {
243
+ // Fast-path: decode directory name
244
+ // e.g., -Users-garrytan-git-repo → /Users/garrytan/git/repo
245
+ const decoded = dirName.replace(/^-/, "/").replace(/-/g, "/");
246
+ if (existsSync(decoded)) return decoded;
247
+
248
+ // Fallback: read cwd from first JSONL file
249
+ // Sort by mtime descending, pick most recent
250
+ const sorted = jsonlFiles
251
+ .map((f) => {
252
+ try {
253
+ return { name: f, mtime: statSync(join(dirPath, f)).mtime.getTime() };
254
+ } catch {
255
+ return null;
256
+ }
257
+ })
258
+ .filter(Boolean)
259
+ .sort((a, b) => b!.mtime - a!.mtime) as { name: string; mtime: number }[];
260
+
261
+ for (const file of sorted.slice(0, 3)) {
262
+ const cwd = extractCwdFromJsonl(join(dirPath, file.name));
263
+ if (cwd && existsSync(cwd)) return cwd;
264
+ }
265
+
266
+ return null;
267
+ }
268
+
269
+ function extractCwdFromJsonl(filePath: string): string | null {
270
+ try {
271
+ // Read only the first 8KB to avoid loading huge JSONL files into memory
272
+ const fd = openSync(filePath, "r");
273
+ const buf = Buffer.alloc(8192);
274
+ const bytesRead = readSync(fd, buf, 0, 8192, 0);
275
+ closeSync(fd);
276
+ const text = buf.toString("utf-8", 0, bytesRead);
277
+ const lines = text.split("\n").slice(0, 15);
278
+ for (const line of lines) {
279
+ if (!line.trim()) continue;
280
+ try {
281
+ const obj = JSON.parse(line);
282
+ if (obj.cwd) return obj.cwd;
283
+ } catch {
284
+ continue;
285
+ }
286
+ }
287
+ } catch {
288
+ // File read error
289
+ }
290
+ return null;
291
+ }
292
+
293
+ function scanCodex(since: Date): Session[] {
294
+ const sessionsDir = join(homedir(), ".codex", "sessions");
295
+ if (!existsSync(sessionsDir)) return [];
296
+
297
+ const sessions: Session[] = [];
298
+
299
+ // Walk YYYY/MM/DD directory structure
300
+ try {
301
+ const years = readdirSync(sessionsDir);
302
+ for (const year of years) {
303
+ const yearPath = join(sessionsDir, year);
304
+ if (!statSync(yearPath).isDirectory()) continue;
305
+
306
+ const months = readdirSync(yearPath);
307
+ for (const month of months) {
308
+ const monthPath = join(yearPath, month);
309
+ if (!statSync(monthPath).isDirectory()) continue;
310
+
311
+ const days = readdirSync(monthPath);
312
+ for (const day of days) {
313
+ const dayPath = join(monthPath, day);
314
+ if (!statSync(dayPath).isDirectory()) continue;
315
+
316
+ const files = readdirSync(dayPath).filter((f) =>
317
+ f.startsWith("rollout-") && f.endsWith(".jsonl")
318
+ );
319
+
320
+ for (const file of files) {
321
+ const filePath = join(dayPath, file);
322
+ try {
323
+ const stat = statSync(filePath);
324
+ if (stat.mtime < since) continue;
325
+ } catch {
326
+ continue;
327
+ }
328
+
329
+ // Read first line for session_meta (only first 4KB)
330
+ try {
331
+ const fd = openSync(filePath, "r");
332
+ const buf = Buffer.alloc(4096);
333
+ const bytesRead = readSync(fd, buf, 0, 4096, 0);
334
+ closeSync(fd);
335
+ const firstLine = buf.toString("utf-8", 0, bytesRead).split("\n")[0];
336
+ if (!firstLine) continue;
337
+ const meta = JSON.parse(firstLine);
338
+ if (meta.type === "session_meta" && meta.payload?.cwd) {
339
+ sessions.push({ tool: "codex", cwd: meta.payload.cwd });
340
+ }
341
+ } catch {
342
+ console.error(`Warning: could not parse Codex session ${filePath}`);
343
+ }
344
+ }
345
+ }
346
+ }
347
+ }
348
+ } catch {
349
+ // Directory read error
350
+ }
351
+
352
+ return sessions;
353
+ }
354
+
355
+ function scanGemini(since: Date): Session[] {
356
+ const tmpDir = join(homedir(), ".gemini", "tmp");
357
+ if (!existsSync(tmpDir)) return [];
358
+
359
+ // Load projects.json for path mapping
360
+ const projectsPath = join(homedir(), ".gemini", "projects.json");
361
+ let projectsMap: Record<string, string> = {}; // name → path
362
+ if (existsSync(projectsPath)) {
363
+ try {
364
+ const data = JSON.parse(readFileSync(projectsPath, { encoding: "utf-8" }));
365
+ // Format: { projects: { "/path": "name" } } — we want name → path
366
+ const projects = data.projects || {};
367
+ for (const [path, name] of Object.entries(projects)) {
368
+ projectsMap[name as string] = path;
369
+ }
370
+ } catch {
371
+ console.error("Warning: could not parse ~/.gemini/projects.json");
372
+ }
373
+ }
374
+
375
+ const sessions: Session[] = [];
376
+ const seenTimestamps = new Map<string, Set<string>>(); // projectName → Set<startTime>
377
+
378
+ let projectDirs: string[];
379
+ try {
380
+ projectDirs = readdirSync(tmpDir);
381
+ } catch {
382
+ return [];
383
+ }
384
+
385
+ for (const projectName of projectDirs) {
386
+ const chatsDir = join(tmpDir, projectName, "chats");
387
+ if (!existsSync(chatsDir)) continue;
388
+
389
+ // Resolve cwd from projects.json
390
+ let cwd = projectsMap[projectName] || null;
391
+
392
+ // Fallback: check .project_root
393
+ if (!cwd) {
394
+ const projectRootFile = join(tmpDir, projectName, ".project_root");
395
+ if (existsSync(projectRootFile)) {
396
+ try {
397
+ cwd = readFileSync(projectRootFile, { encoding: "utf-8" }).trim();
398
+ } catch {}
399
+ }
400
+ }
401
+
402
+ if (!cwd || !existsSync(cwd)) continue;
403
+
404
+ const seen = seenTimestamps.get(projectName) || new Set<string>();
405
+ seenTimestamps.set(projectName, seen);
406
+
407
+ let files: string[];
408
+ try {
409
+ files = readdirSync(chatsDir).filter((f) =>
410
+ f.startsWith("session-") && f.endsWith(".json")
411
+ );
412
+ } catch {
413
+ continue;
414
+ }
415
+
416
+ for (const file of files) {
417
+ const filePath = join(chatsDir, file);
418
+ try {
419
+ const stat = statSync(filePath);
420
+ if (stat.mtime < since) continue;
421
+ } catch {
422
+ continue;
423
+ }
424
+
425
+ try {
426
+ const data = JSON.parse(readFileSync(filePath, { encoding: "utf-8" }));
427
+ const startTime = data.startTime || "";
428
+
429
+ // Deduplicate by startTime within project
430
+ if (startTime && seen.has(startTime)) continue;
431
+ if (startTime) seen.add(startTime);
432
+
433
+ sessions.push({ tool: "gemini", cwd });
434
+ } catch {
435
+ console.error(`Warning: could not parse Gemini session ${filePath}`);
436
+ }
437
+ }
438
+ }
439
+
440
+ return sessions;
441
+ }
442
+
443
+ // ── Deduplication ──────────────────────────────────────────────────────────
444
+
445
+ async function resolveAndDeduplicate(sessions: Session[]): Promise<Repo[]> {
446
+ // Group sessions by cwd
447
+ const byCwd = new Map<string, Session[]>();
448
+ for (const s of sessions) {
449
+ const existing = byCwd.get(s.cwd) || [];
450
+ existing.push(s);
451
+ byCwd.set(s.cwd, existing);
452
+ }
453
+
454
+ // Resolve git remotes for each cwd
455
+ const cwds = Array.from(byCwd.keys());
456
+ const remoteMap = new Map<string, string>(); // cwd → normalized remote
457
+
458
+ for (const cwd of cwds) {
459
+ const raw = getGitRemote(cwd);
460
+ if (raw) {
461
+ remoteMap.set(cwd, normalizeRemoteUrl(raw));
462
+ } else if (existsSync(cwd) && isGitRepo(cwd)) {
463
+ remoteMap.set(cwd, `local:${cwd}`);
464
+ }
465
+ }
466
+
467
+ // Group by normalized remote
468
+ const byRemote = new Map<string, { paths: string[]; sessions: Session[] }>();
469
+ for (const [cwd, cwdSessions] of byCwd) {
470
+ const remote = remoteMap.get(cwd);
471
+ if (!remote) continue;
472
+
473
+ const existing = byRemote.get(remote) || { paths: [], sessions: [] };
474
+ if (!existing.paths.includes(cwd)) existing.paths.push(cwd);
475
+ existing.sessions.push(...cwdSessions);
476
+ byRemote.set(remote, existing);
477
+ }
478
+
479
+ // Build Repo objects
480
+ const repos: Repo[] = [];
481
+ for (const [remote, data] of byRemote) {
482
+ // Find first valid path
483
+ const validPath = data.paths.find((p) => existsSync(p) && isGitRepo(p));
484
+ if (!validPath) continue;
485
+
486
+ // Derive name from remote URL
487
+ let name: string;
488
+ if (remote.startsWith("local:")) {
489
+ name = basename(remote.replace("local:", ""));
490
+ } else {
491
+ try {
492
+ const url = new URL(remote);
493
+ name = basename(url.pathname);
494
+ } catch {
495
+ name = basename(remote);
496
+ }
497
+ }
498
+
499
+ const sessionCounts = { claude_code: 0, codex: 0, gemini: 0 };
500
+ for (const s of data.sessions) {
501
+ sessionCounts[s.tool]++;
502
+ }
503
+
504
+ repos.push({
505
+ name,
506
+ remote,
507
+ paths: data.paths,
508
+ sessions: sessionCounts,
509
+ });
510
+ }
511
+
512
+ // Sort by total sessions descending
513
+ repos.sort(
514
+ (a, b) =>
515
+ b.sessions.claude_code + b.sessions.codex + b.sessions.gemini -
516
+ (a.sessions.claude_code + a.sessions.codex + a.sessions.gemini)
517
+ );
518
+
519
+ return repos;
520
+ }
521
+
522
+ // ── Main ───────────────────────────────────────────────────────────────────
523
+
524
+ async function main() {
525
+ const { since, format } = parseArgs();
526
+ const sinceDate = windowToDate(since);
527
+ const startDate = sinceDate.toISOString().split("T")[0];
528
+
529
+ // Run all scanners
530
+ const ccSessions = scanClaudeCode(sinceDate);
531
+ const codexSessions = scanCodex(sinceDate);
532
+ const geminiSessions = scanGemini(sinceDate);
533
+
534
+ const allSessions = [...ccSessions, ...codexSessions, ...geminiSessions];
535
+
536
+ // Summary to stderr
537
+ console.error(
538
+ `Discovered: ${ccSessions.length} CC sessions, ${codexSessions.length} Codex sessions, ${geminiSessions.length} Gemini sessions`
539
+ );
540
+
541
+ // Deduplicate
542
+ const repos = await resolveAndDeduplicate(allSessions);
543
+
544
+ console.error(`→ ${repos.length} unique repos`);
545
+
546
+ // Count per-tool repo counts
547
+ const ccRepos = new Set(repos.filter((r) => r.sessions.claude_code > 0).map((r) => r.remote)).size;
548
+ const codexRepos = new Set(repos.filter((r) => r.sessions.codex > 0).map((r) => r.remote)).size;
549
+ const geminiRepos = new Set(repos.filter((r) => r.sessions.gemini > 0).map((r) => r.remote)).size;
550
+
551
+ const result: DiscoveryResult = {
552
+ window: since,
553
+ start_date: startDate,
554
+ repos,
555
+ tools: {
556
+ claude_code: { total_sessions: ccSessions.length, repos: ccRepos },
557
+ codex: { total_sessions: codexSessions.length, repos: codexRepos },
558
+ gemini: { total_sessions: geminiSessions.length, repos: geminiRepos },
559
+ },
560
+ total_sessions: allSessions.length,
561
+ total_repos: repos.length,
562
+ };
563
+
564
+ if (format === "json") {
565
+ console.log(JSON.stringify(result, null, 2));
566
+ } else {
567
+ // Summary format
568
+ console.log(`Window: ${since} (since ${startDate})`);
569
+ console.log(`Sessions: ${allSessions.length} total (CC: ${ccSessions.length}, Codex: ${codexSessions.length}, Gemini: ${geminiSessions.length})`);
570
+ console.log(`Repos: ${repos.length} unique`);
571
+ console.log("");
572
+ for (const repo of repos) {
573
+ const total = repo.sessions.claude_code + repo.sessions.codex + repo.sessions.gemini;
574
+ const tools = [];
575
+ if (repo.sessions.claude_code > 0) tools.push(`CC:${repo.sessions.claude_code}`);
576
+ if (repo.sessions.codex > 0) tools.push(`Codex:${repo.sessions.codex}`);
577
+ if (repo.sessions.gemini > 0) tools.push(`Gemini:${repo.sessions.gemini}`);
578
+ console.log(` ${repo.name} (${total} sessions) — ${tools.join(", ")}`);
579
+ console.log(` Remote: ${repo.remote}`);
580
+ console.log(` Paths: ${repo.paths.join(", ")}`);
581
+ }
582
+ }
583
+ }
584
+
585
+ // Only run main when executed directly (not when imported for testing)
586
+ if (import.meta.main) {
587
+ main().catch((err) => {
588
+ console.error(`Fatal error: ${err.message}`);
589
+ process.exit(1);
590
+ });
591
+ }