@geminix/gxpm 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (299) hide show
  1. package/AGENTS.md +148 -0
  2. package/CANON.md +53 -0
  3. package/CLAUDE.md +60 -0
  4. package/CONTEXT.md +49 -0
  5. package/DEBUG.md +59 -0
  6. package/ISSUE_CONTEXT.md +25 -0
  7. package/README.md +143 -0
  8. package/VERSION +1 -0
  9. package/agents/cleanup-auditor/cleanup-auditor.md +56 -0
  10. package/agents/grill-master.md +26 -0
  11. package/agents/implementer.md +32 -0
  12. package/agents/review-army/accessibility-reviewer.md +54 -0
  13. package/agents/review-army/code-quality-reviewer.md +54 -0
  14. package/agents/review-army/security-reviewer.md +56 -0
  15. package/agents/review-army/spec-compliance-reviewer.md +51 -0
  16. package/agents/review-army/test-reviewer.md +55 -0
  17. package/agents/reviewer.md +59 -0
  18. package/agents/ship-audit-army/docs-auditor.md +53 -0
  19. package/agents/ship-audit-army/performance-auditor.md +52 -0
  20. package/agents/ship-audit-army/security-auditor.md +52 -0
  21. package/agents/specifier.md +55 -0
  22. package/agents/triage-officer.md +27 -0
  23. package/bin/gxpm +17 -0
  24. package/bin/gxpm-browser +17 -0
  25. package/bin/gxpm-config +15 -0
  26. package/bin/gxpm-eval +13 -0
  27. package/bin/gxpm-global-discover +15 -0
  28. package/bin/gxpm-init +38 -0
  29. package/bin/gxpm-investigate +194 -0
  30. package/bin/gxpm-uninstall +15 -0
  31. package/bin/gxpm-update-check +165 -0
  32. package/commands/build.md +40 -0
  33. package/commands/help.md +53 -0
  34. package/commands/plan.md +34 -0
  35. package/commands/refine.md +46 -0
  36. package/commands/review.md +34 -0
  37. package/commands/ship.md +37 -0
  38. package/core/ac-check.ts +20 -0
  39. package/core/agent-runtime.ts +363 -0
  40. package/core/artifact-validator.ts +151 -0
  41. package/core/artifacts.ts +313 -0
  42. package/core/autopilot.ts +250 -0
  43. package/core/capabilities.ts +779 -0
  44. package/core/checkpoint.ts +370 -0
  45. package/core/cleanup.ts +32 -0
  46. package/core/command-probe.ts +82 -0
  47. package/core/config.ts +533 -0
  48. package/core/contracts/behavior-spec.schema.ts +38 -0
  49. package/core/contracts/converter.ts +61 -0
  50. package/core/contracts/host.ts +43 -0
  51. package/core/converters/converter.ts +93 -0
  52. package/core/converters/index.ts +8 -0
  53. package/core/converters/managed-artifact.ts +119 -0
  54. package/core/converters/parser.ts +159 -0
  55. package/core/converters/template-renderer.ts +35 -0
  56. package/core/converters/writer.ts +61 -0
  57. package/core/dag-executor.ts +426 -0
  58. package/core/dag-loader.ts +292 -0
  59. package/core/dag-schemas.ts +150 -0
  60. package/core/dispatch.ts +125 -0
  61. package/core/evidence.ts +148 -0
  62. package/core/gate.ts +269 -0
  63. package/core/hook-engine.ts +566 -0
  64. package/core/host-probe.ts +64 -0
  65. package/core/implement.ts +16 -0
  66. package/core/isolation-errors.ts +174 -0
  67. package/core/isolation-resolver.ts +921 -0
  68. package/core/issue-context.ts +381 -0
  69. package/core/issue-readiness.ts +457 -0
  70. package/core/issue-sync.ts +427 -0
  71. package/core/issues.ts +132 -0
  72. package/core/land.ts +108 -0
  73. package/core/orchestrator.ts +54 -0
  74. package/core/phase-artifact.ts +32 -0
  75. package/core/phase-gates.ts +130 -0
  76. package/core/phase-rewind.ts +94 -0
  77. package/core/plan-lint.ts +61 -0
  78. package/core/plan.ts +77 -0
  79. package/core/port-allocation.ts +50 -0
  80. package/core/pr-check.ts +15 -0
  81. package/core/preset-system/preset-resolver.ts +221 -0
  82. package/core/project-init-status.ts +127 -0
  83. package/core/qa.ts +15 -0
  84. package/core/resilience.ts +165 -0
  85. package/core/runs.ts +288 -0
  86. package/core/safe-path.test.ts +80 -0
  87. package/core/safe-path.ts +60 -0
  88. package/core/sdd-gate.test.ts +98 -0
  89. package/core/sdd-gate.ts +134 -0
  90. package/core/self-review.ts +62 -0
  91. package/core/session.ts +70 -0
  92. package/core/ship.ts +86 -0
  93. package/core/specify.ts +173 -0
  94. package/core/state.ts +1002 -0
  95. package/core/template-engine.ts +152 -0
  96. package/core/template-resolver.test.ts +70 -0
  97. package/core/template-resolver.ts +156 -0
  98. package/core/triage.ts +26 -0
  99. package/core/verify.ts +15 -0
  100. package/core/wiki-native.ts +2423 -0
  101. package/core/wiki.ts +27 -0
  102. package/core/workflow-event-emitter.ts +163 -0
  103. package/core/workflows/engine.ts +273 -0
  104. package/core/workflows/expressions.ts +76 -0
  105. package/core/workflows/index.ts +38 -0
  106. package/core/workflows/steps/command.ts +43 -0
  107. package/core/workflows/steps/gate.ts +47 -0
  108. package/core/workflows/steps/gxpm.ts +44 -0
  109. package/core/workflows/steps/linear.ts +31 -0
  110. package/core/workflows/steps/shell.ts +65 -0
  111. package/core/workflows/types.ts +62 -0
  112. package/core/workspace-runtime.ts +227 -0
  113. package/core/worktree-init-steps.ts +647 -0
  114. package/core/worktree-init.ts +330 -0
  115. package/core/worktree-owner.ts +143 -0
  116. package/docs/GXPM_VERIFY.md +98 -0
  117. package/docs/INSTALL_FOR_AGENTS.md +113 -0
  118. package/docs/README.md +57 -0
  119. package/docs/adr/adr-005-multi-platform-skill-converter.md +72 -0
  120. package/docs/agents/domain.md +30 -0
  121. package/docs/agents/issue-tracker.md +30 -0
  122. package/docs/agents/triage-labels.md +32 -0
  123. package/docs/architecture/gxpm-architecture-diagram.md +265 -0
  124. package/docs/architecture/gxpm-current-architecture.md +175 -0
  125. package/docs/architecture/gxpm-current-flow.md +278 -0
  126. package/docs/architecture/gxpm-replacement-architecture.md +211 -0
  127. package/docs/architecture/gxpm-target-architecture.md +449 -0
  128. package/docs/architecture/gxpm-v0-contract.md +311 -0
  129. package/docs/architecture/layered-workflow-boundaries.md +193 -0
  130. package/docs/architecture/preset-system.md +126 -0
  131. package/docs/architecture/scaffold-northstar.md +23 -0
  132. package/docs/brainstorms/2026-05-14-bdd-then-tdd-design.md +320 -0
  133. package/docs/brainstorms/README.md +22 -0
  134. package/docs/brainstorms/docs-knowledge-system-requirements.md +29 -0
  135. package/docs/governance/beta-skill-promotion.md +39 -0
  136. package/docs/governance/development-contract.md +144 -0
  137. package/docs/governance/gherkin-style.md +90 -0
  138. package/docs/governance/host-adapter.md +56 -0
  139. package/docs/governance/skill-authoring.md +87 -0
  140. package/docs/governance/skill-testing.md +356 -0
  141. package/docs/governance/template-authoring.md +53 -0
  142. package/docs/migrations/v0.2.md +51 -0
  143. package/docs/plans/README.md +23 -0
  144. package/docs/plans/bdd-then-tdd-plan.md +1767 -0
  145. package/docs/plans/docs-knowledge-system-plan.md +31 -0
  146. package/docs/plans/spec-kit-sdd-adoption-plan.md +305 -0
  147. package/docs/research/agents-md-best-practices.md +207 -0
  148. package/docs/research/archon-study.md +351 -0
  149. package/docs/research/claude-hooks-study.md +440 -0
  150. package/docs/research/codex-hooks-study.md +624 -0
  151. package/docs/research/everything-claude-code-study.md +252 -0
  152. package/docs/research/from-skills-to-layered-workflow.md +322 -0
  153. package/docs/research/gsd-study.md +69 -0
  154. package/docs/research/kimi-hooks-study.md +274 -0
  155. package/docs/research/mattpocock-skills-comparison.md +429 -0
  156. package/docs/research/mattpocock-skills-study.md +275 -0
  157. package/docs/research/oh-my-codex-study.md +279 -0
  158. package/docs/research/perplexity-agent-skills-design.md +168 -0
  159. package/docs/research/pmc-gstack-skill-study.md +122 -0
  160. package/docs/research/spec-kit-study.md +224 -0
  161. package/docs/research/superpowers-study.md +209 -0
  162. package/docs/roadmap/initial-roadmap.md +53 -0
  163. package/docs/solutions/README.md +45 -0
  164. package/docs/solutions/artifact-nesting-recovery.md +58 -0
  165. package/docs/solutions/session-context-restore-practice.md +67 -0
  166. package/docs/solutions/workflow/version-drift-recovery.md +49 -0
  167. package/docs/solutions/worktree-gate-recovery.md +62 -0
  168. package/docs/specs/README.md +28 -0
  169. package/docs/specs/claude.md +45 -0
  170. package/docs/specs/codex.md +44 -0
  171. package/docs/specs/cursor.md +44 -0
  172. package/hosts/adapters/claude.ts +29 -0
  173. package/hosts/adapters/codex.ts +27 -0
  174. package/hosts/adapters/cursor.ts +27 -0
  175. package/hosts/adapters/kimi.ts +27 -0
  176. package/hosts/claude.ts +23 -0
  177. package/hosts/codex.ts +26 -0
  178. package/hosts/cursor.ts +19 -0
  179. package/hosts/index.ts +33 -0
  180. package/hosts/registry.test.ts +52 -0
  181. package/hosts/registry.ts +57 -0
  182. package/hosts/schema.ts +58 -0
  183. package/package.json +52 -0
  184. package/scripts/browser.ts +185 -0
  185. package/scripts/cleanup.ts +142 -0
  186. package/scripts/commands/artifact.ts +115 -0
  187. package/scripts/commands/autopilot.ts +143 -0
  188. package/scripts/commands/capability.ts +57 -0
  189. package/scripts/commands/config.ts +69 -0
  190. package/scripts/commands/dag.ts +126 -0
  191. package/scripts/commands/feedback.ts +123 -0
  192. package/scripts/commands/gate.ts +291 -0
  193. package/scripts/commands/helpers.ts +126 -0
  194. package/scripts/commands/hook.ts +66 -0
  195. package/scripts/commands/init.ts +515 -0
  196. package/scripts/commands/issue.ts +825 -0
  197. package/scripts/commands/phase.ts +61 -0
  198. package/scripts/commands/preset.ts +159 -0
  199. package/scripts/commands/runtime.ts +199 -0
  200. package/scripts/commands/specify.ts +71 -0
  201. package/scripts/commands/upgrade.ts +243 -0
  202. package/scripts/commands/verify.ts +183 -0
  203. package/scripts/commands/wiki.ts +242 -0
  204. package/scripts/commands/workflow.ts +131 -0
  205. package/scripts/dev-skill.ts +55 -0
  206. package/scripts/discover-skills.ts +116 -0
  207. package/scripts/doctor.ts +410 -0
  208. package/scripts/dogfood-check.ts +125 -0
  209. package/scripts/eval-functional.ts +218 -0
  210. package/scripts/eval.ts +246 -0
  211. package/scripts/gen-skill-docs.ts +201 -0
  212. package/scripts/global-discover.ts +217 -0
  213. package/scripts/governance-check.ts +75 -0
  214. package/scripts/gxpm-check.ts +12 -0
  215. package/scripts/gxpm.ts +216 -0
  216. package/scripts/host-config.ts +62 -0
  217. package/scripts/install-claude-hooks.ts +138 -0
  218. package/scripts/install-codex-hooks.ts +271 -0
  219. package/scripts/install-hooks.ts +128 -0
  220. package/scripts/install-kimi-hooks.ts +92 -0
  221. package/scripts/install-skill.ts +184 -0
  222. package/scripts/phase-artifact-commands.ts +100 -0
  223. package/scripts/post-land-sync.ts +46 -0
  224. package/scripts/scaffold-check.ts +85 -0
  225. package/scripts/skill-naming-check.ts +78 -0
  226. package/scripts/skill-structure-check.ts +157 -0
  227. package/scripts/skills-lock-check.ts +60 -0
  228. package/scripts/sync-markdown-artifacts.ts +172 -0
  229. package/scripts/uninstall.ts +162 -0
  230. package/scripts/version.ts +47 -0
  231. package/scripts/wait-pr-ready.ts +407 -0
  232. package/skills/gxpm/SKILL.md +485 -0
  233. package/skills/gxpm/SKILL.md.tmpl +422 -0
  234. package/skills/gxpm/references/CANON.md +53 -0
  235. package/skills/gxpm/references/key-rules.md +130 -0
  236. package/skills/gxpm-architecture/SKILL.md +106 -0
  237. package/skills/gxpm-architecture/references/DEEPENING.md +37 -0
  238. package/skills/gxpm-architecture/references/INTERFACE-DESIGN.md +44 -0
  239. package/skills/gxpm-autopilot/SKILL.md +116 -0
  240. package/skills/gxpm-autopilot/SKILL.md.tmpl +107 -0
  241. package/skills/gxpm-browser/SKILL.md +105 -0
  242. package/skills/gxpm-browser/SKILL.md.tmpl +41 -0
  243. package/skills/gxpm-browser/references/commands.md +43 -0
  244. package/skills/gxpm-browser/references/evidence-path.md +20 -0
  245. package/skills/gxpm-build/SKILL.md +78 -0
  246. package/skills/gxpm-cleanup/SKILL.md +76 -0
  247. package/skills/gxpm-debug-issue/SKILL.md +39 -0
  248. package/skills/gxpm-diagnose/SKILL.md +220 -0
  249. package/skills/gxpm-diagnose/SKILL.md.tmpl +31 -0
  250. package/skills/gxpm-diagnose/references/feedback-loop.md +34 -0
  251. package/skills/gxpm-diagnose/references/feedback-loops.md +43 -0
  252. package/skills/gxpm-diagnose/references/phases.md +60 -0
  253. package/skills/gxpm-eval/SKILL.md +78 -0
  254. package/skills/gxpm-explore-codebase/SKILL.md +36 -0
  255. package/skills/gxpm-explore-codebase/scripts/summarize-communities.ts +51 -0
  256. package/skills/gxpm-feedback/SKILL.md +122 -0
  257. package/skills/gxpm-grill/SKILL.md +159 -0
  258. package/skills/gxpm-grill/SKILL.md.tmpl +77 -0
  259. package/skills/gxpm-grill/references/documentation-templates.md +56 -0
  260. package/skills/gxpm-grill/references/process.md +25 -0
  261. package/skills/gxpm-handoff/SKILL.md +112 -0
  262. package/skills/gxpm-hygiene/SKILL.md +69 -0
  263. package/skills/gxpm-implementer/SKILL.md +142 -0
  264. package/skills/gxpm-implementer/SKILL.md.tmpl +141 -0
  265. package/skills/gxpm-linear/SKILL.md +282 -0
  266. package/skills/gxpm-linear/SKILL.md.tmpl +86 -0
  267. package/skills/gxpm-linear/references/commands.md +75 -0
  268. package/skills/gxpm-linear/references/workflows.md +120 -0
  269. package/skills/gxpm-planning/SKILL.md +134 -0
  270. package/skills/gxpm-prototype/SKILL.md +64 -0
  271. package/skills/gxpm-refactor-safely/SKILL.md +62 -0
  272. package/skills/gxpm-review-army/SKILL.md +117 -0
  273. package/skills/gxpm-review-changes/SKILL.md +36 -0
  274. package/skills/gxpm-setup/SKILL.md +101 -0
  275. package/skills/gxpm-specifier/SKILL.md +135 -0
  276. package/skills/gxpm-tdd/SKILL.md +187 -0
  277. package/skills/gxpm-tdd/references/interface-design.md +23 -0
  278. package/skills/gxpm-tdd/references/mocking.md +27 -0
  279. package/skills/gxpm-tdd/references/red-green-refactor.md +61 -0
  280. package/skills/gxpm-tdd/references/troubleshooting.md +28 -0
  281. package/skills/gxpm-tdd/references/workflow.md +50 -0
  282. package/skills/gxpm-tdd/testing-anti-patterns.tmpl +304 -0
  283. package/skills/gxpm-triage/SKILL.md +160 -0
  284. package/skills/gxpm-verify/SKILL.md +107 -0
  285. package/skills/gxpm-write-skill/SKILL.md +131 -0
  286. package/skills/gxpm-zoom-out/SKILL.md +69 -0
  287. package/skills/maintain-hygiene-skills-lock/SKILL.md +54 -0
  288. package/skills/maintain-hygiene-skills-lock/SKILL.md.tmpl +53 -0
  289. package/templates/constitution-template.md +63 -0
  290. package/templates/hooks/gxpm-commit-msg +16 -0
  291. package/templates/hooks/gxpm-post-checkout +19 -0
  292. package/templates/hooks/gxpm-post-commit +7 -0
  293. package/templates/hooks/gxpm-post-merge +29 -0
  294. package/templates/hooks/gxpm-pre-commit +39 -0
  295. package/templates/hooks/gxpm-pre-push +33 -0
  296. package/templates/plan-template.md.tmpl +46 -0
  297. package/templates/spec-template.md.tmpl +63 -0
  298. package/templates/specify-stub.tmpl +22 -0
  299. package/templates/tasks-template.md.tmpl +32 -0
@@ -0,0 +1,647 @@
1
+ /**
2
+ * Built-in WorktreeInitSteps for gxpm.
3
+ *
4
+ * Steps are registered by name and consumed by WorktreeInitPipeline.
5
+ * Each step is self-contained and reads configuration from gxpm config.
6
+ */
7
+
8
+ import { existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, readlinkSync, realpathSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
9
+ import { dirname, join, relative, resolve, basename } from "node:path";
10
+ import { getResolvedConfigValue } from "./config";
11
+ import { writeWorktreeOwnerMarker, writeIssueContextMd } from "./worktree-owner";
12
+ import {
13
+ type WorktreeInitStep,
14
+ type WorktreeInitContext,
15
+ type WorktreeInitStepResult,
16
+ type PortAllocation,
17
+ registerBuiltinStep,
18
+ ensureSymlink,
19
+ setEnvVar,
20
+ readEnvFile,
21
+ safeWorktreeSlug,
22
+ hashSlot,
23
+ checkPortAvailable,
24
+ appendGitExcludeEntry,
25
+ } from "./worktree-init";
26
+
27
+ // ---------------------------------------------------------------------------
28
+ // Helpers
29
+ // ---------------------------------------------------------------------------
30
+
31
+ function resolveConfigStringArray(root: string, key: string): string[] {
32
+ const cfg = getResolvedConfigValue({ root, key });
33
+ if (Array.isArray(cfg.value)) return cfg.value as string[];
34
+ return [];
35
+ }
36
+
37
+ function resolveConfigString(root: string, key: string, fallback: string): string {
38
+ const cfg = getResolvedConfigValue({ root, key });
39
+ return typeof cfg.value === "string" ? cfg.value : fallback;
40
+ }
41
+
42
+ function resolveConfigNumber(root: string, key: string, fallback: number): number {
43
+ const cfg = getResolvedConfigValue({ root, key });
44
+ return typeof cfg.value === "number" ? cfg.value : fallback;
45
+ }
46
+
47
+ function warnIfNotOk(result: { ok: boolean; warning?: string }, warnings: string[]): void {
48
+ if (!result.ok && result.warning) warnings.push(result.warning);
49
+ }
50
+
51
+ function findNodeModulesDirs(basePath: string, depth = 0): string[] {
52
+ const results: string[] = [];
53
+ if (depth > 10) return results;
54
+ let entries;
55
+ try {
56
+ entries = readdirSync(basePath, { withFileTypes: true });
57
+ } catch {
58
+ return results;
59
+ }
60
+ for (const entry of entries) {
61
+ if (!entry.isDirectory()) continue;
62
+ const fullPath = join(basePath, entry.name);
63
+ if (entry.name === "node_modules") {
64
+ results.push(fullPath);
65
+ continue;
66
+ }
67
+ results.push(...findNodeModulesDirs(fullPath, depth + 1));
68
+ }
69
+ return results;
70
+ }
71
+
72
+ // ---------------------------------------------------------------------------
73
+ // Step: symlink-node-modules
74
+ // ---------------------------------------------------------------------------
75
+
76
+ registerBuiltinStep("symlink-node-modules", () => ({
77
+ name: "symlink-node-modules",
78
+ run(ctx): WorktreeInitStepResult {
79
+ const mode = resolveConfigString(ctx.canonicalRepoPath, "worktree.nodeModulesMode", "symlink");
80
+ const warnings: string[] = [];
81
+ const canonicalNodeModules = findNodeModulesDirs(ctx.canonicalRepoPath);
82
+
83
+ for (const canonicalNm of canonicalNodeModules) {
84
+ const rel = relative(ctx.canonicalRepoPath, canonicalNm);
85
+ const worktreeNm = resolve(ctx.worktreePath, rel);
86
+
87
+ // Skip if this is the root node_modules and mode is overlay
88
+ if (rel === "node_modules" && mode === "overlay") {
89
+ setupRootNodeModulesOverlay(ctx.canonicalRepoPath, ctx.worktreePath, warnings);
90
+ continue;
91
+ }
92
+
93
+ let current;
94
+ try {
95
+ current = lstatSync(worktreeNm);
96
+ } catch {
97
+ current = undefined;
98
+ }
99
+
100
+ if (current) {
101
+ if (current.isSymbolicLink()) {
102
+ let pointsToCanonical = false;
103
+ try {
104
+ const existingTarget = resolve(ctx.worktreePath, readlinkSync(worktreeNm));
105
+ pointsToCanonical = realpathSync(existingTarget) === realpathSync(canonicalNm);
106
+ } catch {
107
+ pointsToCanonical = false;
108
+ }
109
+ if (!pointsToCanonical) {
110
+ try {
111
+ rmSync(worktreeNm, { force: true });
112
+ mkdirSync(dirname(worktreeNm), { recursive: true });
113
+ symlinkSync(canonicalNm, worktreeNm, "dir");
114
+ warnings.push(`Worktree ${rel} symlink pointed elsewhere; rewrote to canonical.`);
115
+ } catch (error) {
116
+ const message = error instanceof Error ? error.message : String(error);
117
+ warnings.push(`Worktree ${rel} symlink rewrite failed: ${message}`);
118
+ }
119
+ }
120
+ continue;
121
+ }
122
+ if (current.isDirectory()) {
123
+ warnings.push(
124
+ `Worktree ${rel} exists as a real directory; leaving unchanged to avoid data loss.`,
125
+ );
126
+ continue;
127
+ }
128
+ }
129
+
130
+ try {
131
+ mkdirSync(dirname(worktreeNm), { recursive: true });
132
+ symlinkSync(canonicalNm, worktreeNm, "dir");
133
+ } catch (error) {
134
+ const message = error instanceof Error ? error.message : String(error);
135
+ warnings.push(`Failed to create ${rel} symlink: ${message}`);
136
+ }
137
+ }
138
+
139
+ return { ok: true, warnings };
140
+ },
141
+ }));
142
+
143
+ function setupRootNodeModulesOverlay(main: string, wt: string, warnings: string[]): void {
144
+ const mainNm = join(main, "node_modules");
145
+ const wtNm = join(wt, "node_modules");
146
+
147
+ if (!existsSync(mainNm)) return;
148
+
149
+ if (existsSync(wtNm)) {
150
+ if (lstatSync(wtNm).isSymbolicLink()) {
151
+ rmSync(wtNm, { force: true });
152
+ } else if (lstatSync(wtNm).isDirectory()) {
153
+ warnings.push("node_modules overlay conflict: real directory exists; skipping overlay");
154
+ return;
155
+ }
156
+ }
157
+
158
+ mkdirSync(join(wtNm, "@gxg"), { recursive: true });
159
+
160
+ // Symlink .bin
161
+ const binResult = ensureSymlink(join(mainNm, ".bin"), join(wtNm, ".bin"));
162
+ if (!binResult.ok) warnings.push(binResult.warning ?? ".bin symlink failed");
163
+
164
+ // Discover workspace local packages and symlink @gxg/*
165
+ const workspaceTargets: string[] = [];
166
+ if (existsSync(join(wt, "server"))) workspaceTargets.push(join(wt, "server"));
167
+ if (existsSync(join(wt, "apps", "web"))) workspaceTargets.push(join(wt, "apps", "web"));
168
+ if (existsSync(join(wt, "packages"))) {
169
+ for (const pkgDir of readdirSync(join(wt, "packages"))) {
170
+ const p = join(wt, "packages", pkgDir);
171
+ if (existsSync(p) && lstatSync(p).isDirectory()) workspaceTargets.push(p);
172
+ }
173
+ }
174
+
175
+ for (const target of workspaceTargets) {
176
+ const pkgJson = join(target, "package.json");
177
+ if (!existsSync(pkgJson)) continue;
178
+ try {
179
+ const pkg = JSON.parse(readFileSync(pkgJson, "utf8")) as { name?: string };
180
+ if (typeof pkg.name === "string" && pkg.name.startsWith("@gxg/")) {
181
+ const shortName = pkg.name.slice(5);
182
+ const dst = join(wtNm, "@gxg", shortName);
183
+ const r = ensureSymlink(target, dst);
184
+ if (!r.ok) warnings.push(r.warning ?? `${pkg.name} symlink failed`);
185
+ }
186
+ } catch {
187
+ // ignore parse errors
188
+ }
189
+ }
190
+ }
191
+
192
+ // ---------------------------------------------------------------------------
193
+ // Step: symlink-env
194
+ // ---------------------------------------------------------------------------
195
+
196
+ registerBuiltinStep("symlink-env", () => ({
197
+ name: "symlink-env",
198
+ run(ctx): WorktreeInitStepResult {
199
+ const warnings: string[] = [];
200
+ const envFiles = resolveConfigStringArray(ctx.canonicalRepoPath, "worktree.envFiles");
201
+
202
+ for (const subpath of envFiles) {
203
+ const src = join(ctx.canonicalRepoPath, subpath);
204
+ const dst = join(ctx.worktreePath, subpath);
205
+ if (!existsSync(src)) continue;
206
+ const r = ensureSymlink(src, dst);
207
+ warnIfNotOk(r, warnings);
208
+ }
209
+
210
+ return { ok: true, warnings };
211
+ },
212
+ }));
213
+
214
+ // ---------------------------------------------------------------------------
215
+ // Step: symlink-shared-dirs
216
+ // ---------------------------------------------------------------------------
217
+
218
+ registerBuiltinStep("symlink-shared-dirs", () => ({
219
+ name: "symlink-shared-dirs",
220
+ run(ctx): WorktreeInitStepResult {
221
+ const warnings: string[] = [];
222
+ const dirs = resolveConfigStringArray(ctx.canonicalRepoPath, "worktree.sharedDirs");
223
+
224
+ for (const subpath of dirs) {
225
+ const src = join(ctx.canonicalRepoPath, subpath);
226
+ const dst = join(ctx.worktreePath, subpath);
227
+ if (!existsSync(src)) continue;
228
+ const r = ensureSymlink(src, dst);
229
+ warnIfNotOk(r, warnings);
230
+ }
231
+
232
+ return { ok: true, warnings };
233
+ },
234
+ }));
235
+
236
+ // ---------------------------------------------------------------------------
237
+ // Step: port-allocation
238
+ // ---------------------------------------------------------------------------
239
+
240
+ registerBuiltinStep("port-allocation", () => ({
241
+ name: "port-allocation",
242
+ run(ctx): WorktreeInitStepResult {
243
+ const root = ctx.canonicalRepoPath;
244
+ const maxSlot = resolveConfigNumber(root, "worktree.portSlots", 10);
245
+ const baseWeb = resolveConfigNumber(root, "worktree.portBaseWeb", 5173);
246
+ const baseServer = resolveConfigNumber(root, "worktree.portBaseServer", 3000);
247
+ const baseStudio = resolveConfigNumber(root, "worktree.portBaseStudio", 4111);
248
+
249
+ const wtName = basename(ctx.worktreePath);
250
+ const initialSlot = hashSlot(wtName, maxSlot);
251
+
252
+ let selectedSlot = 0;
253
+ const ownershipErrors: string[] = [];
254
+
255
+ for (let attempt = 0; attempt < maxSlot; attempt++) {
256
+ const candidate = ((initialSlot + attempt - 1) % maxSlot) + 1;
257
+ const offset = candidate * 10;
258
+ const webPort = baseWeb + offset;
259
+ const serverPort = baseServer + offset;
260
+ const studioPort = baseStudio + offset;
261
+
262
+ const webOk = checkPortAvailable(webPort, ctx.worktreePath);
263
+ const serverOk = checkPortAvailable(serverPort, ctx.worktreePath);
264
+ const studioOk = checkPortAvailable(studioPort, ctx.worktreePath);
265
+
266
+ if (webOk && serverOk && studioOk) {
267
+ selectedSlot = candidate;
268
+ break;
269
+ }
270
+ if (!webOk) ownershipErrors.push(`slot ${candidate}: web port ${webPort} occupied`);
271
+ if (!serverOk) ownershipErrors.push(`slot ${candidate}: server port ${serverPort} occupied`);
272
+ if (!studioOk) ownershipErrors.push(`slot ${candidate}: studio port ${studioPort} occupied`);
273
+ }
274
+
275
+ if (selectedSlot === 0) {
276
+ return {
277
+ ok: false,
278
+ error: `No available worktree port slot (checked ${maxSlot}). ${ownershipErrors.join("; ")}`,
279
+ };
280
+ }
281
+
282
+ const offset = selectedSlot * 10;
283
+ const wtPathHash = createHash("sha256").update(ctx.worktreePath).digest("hex").slice(0, 8);
284
+ const queuePrefix = `gxpm_wt_${safeWorktreeSlug(wtName)}_${wtPathHash}_${selectedSlot}`;
285
+
286
+ ctx.ports = {
287
+ slot: selectedSlot,
288
+ webPort: baseWeb + offset,
289
+ serverPort: baseServer + offset,
290
+ studioPort: baseStudio + offset,
291
+ queuePrefix,
292
+ };
293
+
294
+ const warnings = selectedSlot !== initialSlot
295
+ ? [`Slot ${initialSlot} occupied, fell back to slot ${selectedSlot}`]
296
+ : undefined;
297
+
298
+ return { ok: true, warnings };
299
+ },
300
+ }));
301
+
302
+ // ---------------------------------------------------------------------------
303
+ // Step: generate-env-local
304
+ // ---------------------------------------------------------------------------
305
+
306
+ registerBuiltinStep("generate-env-local", () => ({
307
+ name: "generate-env-local",
308
+ run(ctx): WorktreeInitStepResult {
309
+ if (!ctx.ports) {
310
+ return { ok: false, error: "port-allocation step must run before generate-env-local" };
311
+ }
312
+
313
+ const root = ctx.canonicalRepoPath;
314
+ const envLocalFiles = resolveConfigStringArray(root, "worktree.envLocalFiles");
315
+ const warnings: string[] = [];
316
+
317
+ // Inherit main repo .env.local values first
318
+ for (const subpath of envLocalFiles) {
319
+ const mainLocal = join(root, subpath);
320
+ const wtLocal = join(ctx.worktreePath, subpath);
321
+ if (existsSync(mainLocal)) {
322
+ const mainVars = readEnvFile(mainLocal);
323
+ for (const [k, v] of Object.entries(mainVars)) {
324
+ setEnvVar(wtLocal, k, v);
325
+ }
326
+ }
327
+ }
328
+
329
+ const { webPort, serverPort, studioPort, queuePrefix } = ctx.ports;
330
+
331
+ // Web .env.local
332
+ const webEnvLocal = join(ctx.worktreePath, "apps", "web", ".env.local");
333
+ setEnvVar(webEnvLocal, "VITE_DEV_PORT", String(webPort));
334
+ setEnvVar(webEnvLocal, "VITE_API_BASE_URL", `http://127.0.0.1:${serverPort}`);
335
+
336
+ // Server .env.local
337
+ const serverEnvLocal = join(ctx.worktreePath, "server", ".env.local");
338
+ setEnvVar(serverEnvLocal, "PORT", String(serverPort));
339
+ setEnvVar(serverEnvLocal, "HOST", "0.0.0.0");
340
+ setEnvVar(serverEnvLocal, "STUDIO_PORT", String(studioPort));
341
+ setEnvVar(serverEnvLocal, "BULLMQ_PREFIX", queuePrefix);
342
+ setEnvVar(serverEnvLocal, "DEV_CORS_EXTRA_ORIGINS", `http://localhost:${webPort},http://127.0.0.1:${webPort}`);
343
+
344
+ // Git/worktree identity
345
+ const branchResult = Bun.spawnSync({
346
+ cmd: ["git", "rev-parse", "--abbrev-ref", "HEAD"],
347
+ cwd: ctx.worktreePath,
348
+ stdout: "pipe",
349
+ stderr: "pipe",
350
+ });
351
+ const branch = branchResult.exitCode === 0 ? branchResult.stdout.toString().trim() : "unknown";
352
+
353
+ const shaResult = Bun.spawnSync({
354
+ cmd: ["git", "rev-parse", "--short", "HEAD"],
355
+ cwd: ctx.worktreePath,
356
+ stdout: "pipe",
357
+ stderr: "pipe",
358
+ });
359
+ const sha = shaResult.exitCode === 0 ? shaResult.stdout.toString().trim() : "unknown";
360
+
361
+ setEnvVar(serverEnvLocal, "GIT_BRANCH", branch);
362
+ setEnvVar(serverEnvLocal, "GIT_SHA", sha);
363
+ setEnvVar(serverEnvLocal, "WORKTREE_NAME", basename(ctx.worktreePath));
364
+
365
+ return { ok: true, warnings };
366
+ },
367
+ }));
368
+
369
+ // ---------------------------------------------------------------------------
370
+ // Step: generate-warp-md
371
+ // ---------------------------------------------------------------------------
372
+
373
+ registerBuiltinStep("generate-warp-md", () => ({
374
+ name: "generate-warp-md",
375
+ run(ctx): WorktreeInitStepResult {
376
+ if (!ctx.ports) {
377
+ return { ok: false, error: "port-allocation step must run before generate-warp-md" };
378
+ }
379
+
380
+ const root = ctx.canonicalRepoPath;
381
+ const warpPath = resolveConfigString(root, "worktree.warpMdPath", "warp.md");
382
+ const dst = join(ctx.worktreePath, warpPath);
383
+
384
+ const branchResult = Bun.spawnSync({
385
+ cmd: ["git", "branch", "--show-current"],
386
+ cwd: ctx.worktreePath,
387
+ stdout: "pipe",
388
+ stderr: "pipe",
389
+ });
390
+ const branch = branchResult.exitCode === 0 ? branchResult.stdout.toString().trim() : "unknown";
391
+
392
+ const { slot, webPort, serverPort, studioPort, queuePrefix } = ctx.ports;
393
+
394
+ const content = `# Worktree Port Configuration
395
+
396
+ This worktree (Slot ${slot}) uses isolated ports and queue prefix, separated from the main repo and other worktrees for parallel debugging.
397
+
398
+ ## Port Allocation
399
+
400
+ | Service | Port | URL |
401
+ |---------|------|-----|
402
+ | Web (Vite) | ${webPort} | http://127.0.0.1:${webPort} |
403
+ | API Server | ${serverPort} | http://127.0.0.1:${serverPort} |
404
+ | Mastra Studio | ${studioPort} | http://127.0.0.1:${studioPort} (debug only, requires explicit authorization) |
405
+ | BullMQ Prefix | ${queuePrefix} | Redis queue isolation |
406
+
407
+ ## Branch Baseline Traceability
408
+
409
+ | Field | Value |
410
+ |-------|-------|
411
+ | Current branch | ${branch} |
412
+ | Baseline ref | ${ctx.baseBranch ?? "unknown"} |
413
+ | Baseline sha | ${ctx.baseSha ?? "unknown"} |
414
+
415
+ ## Agent Rules
416
+
417
+ - This worktree uses the ports above. **Do not** use main repo defaults (${resolveConfigNumber(root, "worktree.portBaseWeb", 5173)} / ${resolveConfigNumber(root, "worktree.portBaseServer", 3000)} / ${resolveConfigNumber(root, "worktree.portBaseStudio", 4111)}).
418
+ - Ports and queue isolation are injected via \`apps/web/.env.local\` and \`server/.env.local\`.
419
+ - Start services with \`npm run dev\` — **do not** append \`--port\`.
420
+ - Do not override PORT, VITE_DEV_PORT, STUDIO_PORT, or BULLMQ_PREFIX in .env.local.
421
+ - Report URLs using the ports in this table, not defaults.
422
+ - Do **not** start Mastra Studio without explicit user authorization.
423
+ `;
424
+
425
+ writeFileSync(dst, content);
426
+ return { ok: true };
427
+ },
428
+ }));
429
+
430
+ // ---------------------------------------------------------------------------
431
+ // Step: generate-launch-json
432
+ // ---------------------------------------------------------------------------
433
+
434
+ registerBuiltinStep("generate-launch-json", () => ({
435
+ name: "generate-launch-json",
436
+ run(ctx): WorktreeInitStepResult {
437
+ if (!ctx.ports) {
438
+ return { ok: false, error: "port-allocation step must run before generate-launch-json" };
439
+ }
440
+
441
+ const root = ctx.canonicalRepoPath;
442
+ const launchPath = resolveConfigString(root, "worktree.launchJsonPath", ".claude/launch.json");
443
+ const dst = join(ctx.worktreePath, launchPath);
444
+
445
+ const { webPort, serverPort } = ctx.ports;
446
+
447
+ const config = {
448
+ version: "0.0.2",
449
+ configurations: [
450
+ {
451
+ name: "dev",
452
+ runtimeExecutable: "npm",
453
+ runtimeArgs: ["run", "dev"],
454
+ port: webPort,
455
+ autoPort: false,
456
+ env: {
457
+ PORT: String(serverPort),
458
+ },
459
+ },
460
+ ],
461
+ };
462
+
463
+ mkdirSync(dirname(dst), { recursive: true });
464
+ writeFileSync(dst, JSON.stringify(config, null, 2) + "\n");
465
+
466
+ // Exclude from git to avoid dirty state
467
+ appendGitExcludeEntry(ctx.worktreePath, launchPath);
468
+
469
+ return { ok: true };
470
+ },
471
+ }));
472
+
473
+ // ---------------------------------------------------------------------------
474
+ // Step: git-hooks
475
+ // ---------------------------------------------------------------------------
476
+
477
+ registerBuiltinStep("git-hooks", () => ({
478
+ name: "git-hooks",
479
+ run(ctx): WorktreeInitStepResult {
480
+ const root = ctx.canonicalRepoPath;
481
+ const hooksScript = resolveConfigString(root, "worktree.hooksScript", "");
482
+ if (!hooksScript) return { ok: true }; // nothing to do
483
+
484
+ const scriptPath = join(ctx.worktreePath, hooksScript);
485
+ if (!existsSync(scriptPath)) {
486
+ return { ok: false, error: `hooks script not found: ${scriptPath}` };
487
+ }
488
+
489
+ const result = Bun.spawnSync({
490
+ cmd: ["bash", scriptPath],
491
+ cwd: ctx.worktreePath,
492
+ stdout: "pipe",
493
+ stderr: "pipe",
494
+ });
495
+
496
+ if (result.exitCode !== 0) {
497
+ return { ok: false, error: result.stderr.toString().trim() || "git-hooks script failed" };
498
+ }
499
+
500
+ return { ok: true };
501
+ },
502
+ }));
503
+
504
+ // ---------------------------------------------------------------------------
505
+ // Step: baseline-freshness
506
+ // ---------------------------------------------------------------------------
507
+
508
+ registerBuiltinStep("baseline-freshness", () => ({
509
+ name: "baseline-freshness",
510
+ run(ctx): WorktreeInitStepResult {
511
+ const root = ctx.canonicalRepoPath;
512
+ const baseBranch = ctx.baseBranch ?? resolveConfigString(root, "worktree.baseBranch", "main");
513
+ const script = resolveConfigString(root, "worktree.baselineFreshnessScript", "");
514
+
515
+ if (script) {
516
+ const scriptPath = join(ctx.worktreePath, script);
517
+ if (!existsSync(scriptPath)) {
518
+ return { ok: false, error: `baseline freshness script not found: ${scriptPath}` };
519
+ }
520
+ const result = Bun.spawnSync({
521
+ cmd: ["node", scriptPath, "--no-fetch"],
522
+ cwd: ctx.worktreePath,
523
+ stdout: "pipe",
524
+ stderr: "pipe",
525
+ });
526
+ if (result.exitCode !== 0) {
527
+ return {
528
+ ok: true,
529
+ warnings: [
530
+ `Worktree baseline may be behind origin/${baseBranch}. Consider rebase or merge to reduce conflict cost.`,
531
+ ],
532
+ };
533
+ }
534
+ const out = result.stdout.toString();
535
+ if (out.includes("Baseline drift detected")) {
536
+ return {
537
+ ok: true,
538
+ warnings: [
539
+ `Worktree baseline is behind origin/${baseBranch}. Early sync recommended to avoid rising conflict cost.`,
540
+ ],
541
+ };
542
+ }
543
+ return { ok: true };
544
+ }
545
+
546
+ // Built-in lightweight check
547
+ try {
548
+ const result = Bun.spawnSync({
549
+ cmd: ["git", "merge-base", "--is-ancestor", `origin/${baseBranch}`, ctx.branchName],
550
+ cwd: ctx.worktreePath,
551
+ stdout: "pipe",
552
+ stderr: "pipe",
553
+ });
554
+ if (result.exitCode !== 0) {
555
+ return {
556
+ ok: true,
557
+ warnings: [
558
+ `Worktree branch '${ctx.branchName}' is not based on '${baseBranch}'. Recreate if needed.`,
559
+ ],
560
+ };
561
+ }
562
+ } catch {
563
+ // non-blocking
564
+ }
565
+
566
+ return { ok: true };
567
+ },
568
+ }));
569
+
570
+ // ---------------------------------------------------------------------------
571
+ // Step: owner-marker
572
+ // ---------------------------------------------------------------------------
573
+
574
+ registerBuiltinStep("owner-marker", {
575
+ name: "owner-marker",
576
+ run(ctx): WorktreeInitStepResult {
577
+ try {
578
+ writeWorktreeOwnerMarker(ctx.worktreePath, {
579
+ ownerIssueId: ctx.issueId,
580
+ linkedIssues: [],
581
+ branchName: ctx.branchName,
582
+ workspacePath: ctx.worktreePath,
583
+ });
584
+ return { ok: true };
585
+ } catch (err) {
586
+ const message = err instanceof Error ? err.message : String(err);
587
+ return { ok: false, error: message };
588
+ }
589
+ },
590
+ });
591
+
592
+ // ---------------------------------------------------------------------------
593
+ // Step: issue-context
594
+ // ---------------------------------------------------------------------------
595
+
596
+ registerBuiltinStep("issue-context", {
597
+ name: "issue-context",
598
+ run(ctx): WorktreeInitStepResult {
599
+ try {
600
+ writeIssueContextMd(ctx.worktreePath, {
601
+ issueId: ctx.issueId,
602
+ currentPhase: "dispatch",
603
+ branchName: ctx.branchName,
604
+ workspacePath: ctx.worktreePath,
605
+ updatedAt: new Date().toISOString(),
606
+ });
607
+ return { ok: true };
608
+ } catch (err) {
609
+ const message = err instanceof Error ? err.message : String(err);
610
+ return { ok: false, error: message };
611
+ }
612
+ },
613
+ });
614
+
615
+ // ---------------------------------------------------------------------------
616
+ // Step: dep-check (fail-fast dependency verification)
617
+ // ---------------------------------------------------------------------------
618
+
619
+ registerBuiltinStep("dep-check", () => ({
620
+ name: "dep-check",
621
+ run(ctx): WorktreeInitStepResult {
622
+ const root = ctx.canonicalRepoPath;
623
+ const warnings: string[] = [];
624
+
625
+ // Check main repo node_modules exists
626
+ const mainNm = join(root, "node_modules");
627
+ if (!existsSync(mainNm)) {
628
+ return {
629
+ ok: false,
630
+ error: `Main repo node_modules missing. Run 'npm install' in ${root} first.`,
631
+ };
632
+ }
633
+
634
+ // Check workspace sub-dirs
635
+ const subDirs = ["server/node_modules", "apps/web/node_modules"];
636
+ for (const sub of subDirs) {
637
+ const src = join(root, sub);
638
+ const dst = join(ctx.worktreePath, sub);
639
+ if (existsSync(src) && !existsSync(dst)) {
640
+ const r = ensureSymlink(src, dst);
641
+ warnIfNotOk(r, warnings);
642
+ }
643
+ }
644
+
645
+ return { ok: true, warnings };
646
+ },
647
+ }));