@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,44 @@
1
+ // GXPM STEP — invoke gxpm CLI commands within a workflow.
2
+
3
+ import { execSync } from "node:child_process";
4
+ import type { StepBase, StepResult, StepContext } from "../types";
5
+
6
+ export const GxpmStep: StepBase = {
7
+ typeKey: "gxpm",
8
+
9
+ validate(config) {
10
+ const errors: string[] = [];
11
+ if (!config.command || typeof config.command !== "string") {
12
+ errors.push("'command' is required and must be a string");
13
+ }
14
+ return errors;
15
+ },
16
+
17
+ execute(config, context) {
18
+ const command = config.command as string;
19
+ const fullCommand = `bun run bin/gxpm ${command}`;
20
+ const cwd = context.projectRoot;
21
+ const capture = config.captureOutput !== false;
22
+
23
+ try {
24
+ const output = execSync(fullCommand, {
25
+ cwd,
26
+ encoding: "utf8",
27
+ stdio: capture ? ["pipe", "pipe", "pipe"] : "inherit",
28
+ timeout: (config.timeout as number) || 120000,
29
+ });
30
+
31
+ return {
32
+ status: "completed",
33
+ output: capture ? { stdout: output, command: fullCommand } : { command: fullCommand },
34
+ };
35
+ } catch (err) {
36
+ const errorMessage = err instanceof Error ? err.message : String(err);
37
+ return {
38
+ status: config.ignoreFailure ? "completed" : "failed",
39
+ output: { command: fullCommand },
40
+ error: errorMessage,
41
+ };
42
+ }
43
+ },
44
+ };
@@ -0,0 +1,31 @@
1
+ // LINEAR STEP — interact with Linear issues.
2
+ // Thin wrapper; actual Linear integration delegates to existing core/linear module.
3
+
4
+ import type { StepBase, StepResult, StepContext } from "../types";
5
+
6
+ export const LinearStep: StepBase = {
7
+ typeKey: "linear",
8
+
9
+ validate(config) {
10
+ const errors: string[] = [];
11
+ if (!config.action || typeof config.action !== "string") {
12
+ errors.push("'action' is required");
13
+ }
14
+ const valid = ["create", "update", "list", "transition"];
15
+ if (!valid.includes(config.action as string)) {
16
+ errors.push(`'action' must be one of: ${valid.join(", ")}`);
17
+ }
18
+ return errors;
19
+ },
20
+
21
+ async execute(config, _context) {
22
+ const action = config.action as string;
23
+
24
+ // Placeholder: will delegate to core/linear.ts when available
25
+ // For now, return a mock result to keep workflow engine testable
26
+ return {
27
+ status: "completed",
28
+ output: { action, mock: true, id: config.id as string },
29
+ };
30
+ },
31
+ };
@@ -0,0 +1,65 @@
1
+ // SHELL STEP — execute an arbitrary shell command within a workflow.
2
+ // Captures stdout, stderr, and exitCode for downstream steps.
3
+
4
+ import { execSync } from "node:child_process";
5
+ import type { StepBase, StepResult, StepContext } from "../types";
6
+
7
+ export const ShellStep: StepBase = {
8
+ typeKey: "shell",
9
+
10
+ validate(config) {
11
+ const errors: string[] = [];
12
+ if (!config.command || typeof config.command !== "string") {
13
+ errors.push("'command' is required and must be a string");
14
+ }
15
+ return errors;
16
+ },
17
+
18
+ execute(config, context) {
19
+ const command = config.command as string;
20
+ const cwd = config.cwd
21
+ ? String(config.cwd).replace("${projectRoot}", context.projectRoot)
22
+ : context.projectRoot;
23
+ const capture = config.captureOutput !== false;
24
+ const timeout = (config.timeout as number) || 60000;
25
+
26
+ try {
27
+ const output = execSync(command, {
28
+ cwd,
29
+ encoding: "utf8",
30
+ stdio: capture ? ["pipe", "pipe", "pipe"] : "inherit",
31
+ timeout,
32
+ });
33
+
34
+ return {
35
+ status: "completed",
36
+ output: capture
37
+ ? { stdout: output, exitCode: 0, command }
38
+ : { exitCode: 0, command },
39
+ };
40
+ } catch (err) {
41
+ const errorMessage = err instanceof Error ? err.message : String(err);
42
+ // execSync throws on non-zero exit; try to capture stdout/stderr from the error object
43
+ const stdout =
44
+ err && typeof err === "object" && "stdout" in err
45
+ ? String((err as { stdout?: unknown }).stdout)
46
+ : "";
47
+ const stderr =
48
+ err && typeof err === "object" && "stderr" in err
49
+ ? String((err as { stderr?: unknown }).stderr)
50
+ : "";
51
+ const exitCode =
52
+ err && typeof err === "object" && "status" in err
53
+ ? Number((err as { status?: unknown }).status)
54
+ : 1;
55
+
56
+ return {
57
+ status: config.ignoreFailure ? "completed" : "failed",
58
+ output: capture
59
+ ? { stdout, stderr, exitCode, command }
60
+ : { exitCode, command },
61
+ error: errorMessage,
62
+ };
63
+ }
64
+ },
65
+ };
@@ -0,0 +1,62 @@
1
+ // WORKFLOW ENGINE — core types and interfaces.
2
+ // Zero external dependencies beyond Node.js builtins.
3
+
4
+ export type RunStatus = "created" | "running" | "paused" | "completed" | "failed" | "aborted";
5
+ export type StepStatus = "pending" | "running" | "completed" | "failed" | "skipped" | "paused";
6
+
7
+ export interface WorkflowDefinition {
8
+ schemaVersion: string;
9
+ id: string;
10
+ name: string;
11
+ version: string;
12
+ description?: string;
13
+ inputs: Record<string, WorkflowInputSchema>;
14
+ steps: StepConfig[];
15
+ }
16
+
17
+ export interface WorkflowInputSchema {
18
+ type: "string" | "boolean" | "number" | "array";
19
+ required?: boolean;
20
+ default?: unknown;
21
+ }
22
+
23
+ export interface StepConfig {
24
+ id: string;
25
+ type: string;
26
+ config: Record<string, unknown>;
27
+ }
28
+
29
+ export interface StepContext {
30
+ inputs: Record<string, unknown>;
31
+ steps: Record<string, StepResult>;
32
+ runId: string;
33
+ projectRoot: string;
34
+ item?: unknown;
35
+ fanIn?: unknown[];
36
+ }
37
+
38
+ export interface StepResult {
39
+ status: StepStatus;
40
+ output: Record<string, unknown>;
41
+ nextSteps?: StepConfig[];
42
+ error?: string;
43
+ }
44
+
45
+ export interface RunState {
46
+ runId: string;
47
+ workflowId: string;
48
+ status: RunStatus;
49
+ currentStepIndex: number;
50
+ stepIndexStack: number[];
51
+ stepResults: Record<string, StepResult>;
52
+ inputs: Record<string, unknown>;
53
+ createdAt: string;
54
+ updatedAt: string;
55
+ }
56
+
57
+ export interface StepBase {
58
+ readonly typeKey: string;
59
+ execute(config: Record<string, unknown>, context: StepContext): StepResult | Promise<StepResult>;
60
+ validate?(config: Record<string, unknown>): string[];
61
+ canResume?(state: StepResult): boolean;
62
+ }
@@ -0,0 +1,227 @@
1
+ import {
2
+ existsSync,
3
+ lstatSync,
4
+ mkdirSync,
5
+ rmSync,
6
+ } from "node:fs";
7
+ import { homedir } from "node:os";
8
+ import { isAbsolute, join, relative, resolve } from "node:path";
9
+ import { getResolvedConfigValue } from "./config";
10
+ import { resolveDevPort } from "./port-allocation";
11
+ import { readIssueState } from "./state";
12
+ import {
13
+ IsolationResolver,
14
+ createFileSystemStore,
15
+ createGitProvider,
16
+ type IsolationHints,
17
+ type IsolationMethod,
18
+ type IsolationResolution,
19
+ } from "./isolation-resolver";
20
+ import { runWorktreeInit, type WorktreeInitContext } from "./worktree-init";
21
+ import "./worktree-init-steps"; // side-effect: registers built-in init steps
22
+
23
+ export interface WorkspacePlanInput {
24
+ root?: string;
25
+ issueId: string;
26
+ workspaceRoot?: string;
27
+ }
28
+
29
+ export interface WorkspacePlan {
30
+ issueId: string;
31
+ workspaceKey: string;
32
+ workspaceRoot: string;
33
+ workspacePath: string;
34
+ exists: boolean;
35
+ devPort: number;
36
+ }
37
+
38
+ export interface WorkspaceEnsureResult extends WorkspacePlan {
39
+ created: boolean;
40
+ }
41
+
42
+ export interface WorkspaceIsolationResult extends WorkspaceEnsureResult {
43
+ method?: IsolationMethod;
44
+ warnings?: string[];
45
+ userMessage?: string;
46
+ resolution?: IsolationResolution;
47
+ }
48
+
49
+ export interface WorkspaceCleanupResult extends WorkspacePlan {
50
+ removed: boolean;
51
+ }
52
+
53
+ export function planIssueWorkspace(input: WorkspacePlanInput): WorkspacePlan {
54
+ const root = input.root ?? process.cwd();
55
+ const state = readIssueState({ root, issueId: input.issueId });
56
+ const workspaceRoot = resolveWorkspaceRoot({ root, workspaceRoot: input.workspaceRoot });
57
+ const workspaceKey = sanitizeWorkspaceKey(state.issueId);
58
+ const workspacePath = join(workspaceRoot, workspaceKey);
59
+ assertPathInsideRoot(workspaceRoot, workspacePath);
60
+
61
+ const plan = {
62
+ issueId: state.issueId,
63
+ workspaceKey,
64
+ workspaceRoot,
65
+ workspacePath,
66
+ exists: existsSync(workspacePath),
67
+ };
68
+
69
+ return {
70
+ ...plan,
71
+ devPort: resolveDevPort({ workspacePath, root }),
72
+ };
73
+ }
74
+
75
+ export function ensureIssueWorkspace(input: WorkspacePlanInput): WorkspaceEnsureResult {
76
+ const plan = planIssueWorkspace(input);
77
+ mkdirSync(plan.workspaceRoot, { recursive: true });
78
+ assertPathInsideRoot(plan.workspaceRoot, plan.workspacePath);
79
+
80
+ const existed = existsSync(plan.workspacePath);
81
+ if (existed && !lstatSync(plan.workspacePath).isDirectory()) {
82
+ throw new Error(`Workspace path exists but is not a directory: ${plan.workspacePath}`);
83
+ }
84
+ if (!existed) {
85
+ mkdirSync(plan.workspacePath, { recursive: true });
86
+ }
87
+
88
+ return { ...plan, exists: true, created: !existed };
89
+ }
90
+
91
+ export async function ensureIssueWorkspaceWithResolver(
92
+ input: WorkspacePlanInput & { hints?: IsolationHints },
93
+ ): Promise<WorkspaceIsolationResult> {
94
+ const root = input.root ?? process.cwd();
95
+ const issueId = input.issueId;
96
+ const configuredBaseBranch = String(getResolvedConfigValue({ root, key: "worktree.baseBranch" }).value);
97
+ const hints: IsolationHints = {
98
+ ...input.hints,
99
+ baseBranch: input.hints?.baseBranch ?? configuredBaseBranch,
100
+ };
101
+
102
+ // Run resolver first
103
+ const store = createFileSystemStore(root);
104
+ const provider = createGitProvider();
105
+ const resolver = new IsolationResolver({ store, provider });
106
+ const resolution = await resolver.resolve({ issueId, root, hints });
107
+
108
+ let plan: WorkspacePlan;
109
+ let created = false;
110
+
111
+ let initWarnings: string[] = [];
112
+
113
+ if (resolution.status === "resolved" && resolution.env) {
114
+ // Use the resolved workspace path
115
+ const workspacePath = resolution.env.workspacePath;
116
+ const workspaceRoot = resolveWorkspaceRoot({ root, workspaceRoot: input.workspaceRoot });
117
+ plan = {
118
+ issueId,
119
+ workspaceKey: sanitizeWorkspaceKey(issueId),
120
+ workspaceRoot,
121
+ workspacePath,
122
+ exists: existsSync(workspacePath),
123
+ devPort: resolveDevPort({ workspacePath, root }),
124
+ };
125
+ if (!plan.exists) {
126
+ mkdirSync(workspacePath, { recursive: true });
127
+ created = true;
128
+ }
129
+
130
+ // Run worktree init pipeline for reused worktrees (new worktrees are
131
+ // already initialized inside the provider). Idempotent — safe to re-run.
132
+ const repoResult = Bun.spawnSync({
133
+ cmd: ["git", "rev-parse", "--show-toplevel"],
134
+ cwd: root,
135
+ stdout: "pipe",
136
+ stderr: "pipe",
137
+ });
138
+ if (repoResult.exitCode === 0) {
139
+ const canonicalRepoPath = repoResult.stdout.toString().trim();
140
+ const initCtx: WorktreeInitContext = {
141
+ canonicalRepoPath,
142
+ worktreePath: workspacePath,
143
+ branchName: resolution.env.branchName ?? `gxpm-${issueId}`,
144
+ issueId,
145
+ };
146
+ const initResult = await runWorktreeInit(initCtx);
147
+ initWarnings = initResult.warnings;
148
+ if (!initResult.ok) {
149
+ initWarnings.push(...initResult.errors);
150
+ }
151
+ }
152
+ } else if (resolution.status === "none") {
153
+ // No git repo: fall back to plain directory workspace
154
+ plan = planIssueWorkspace(input);
155
+ mkdirSync(plan.workspaceRoot, { recursive: true });
156
+ assertPathInsideRoot(plan.workspaceRoot, plan.workspacePath);
157
+ const existed = existsSync(plan.workspacePath);
158
+ if (!existed) {
159
+ mkdirSync(plan.workspacePath, { recursive: true });
160
+ created = true;
161
+ }
162
+ } else {
163
+ // blocked or stale_cleaned — return as-is with minimal plan
164
+ plan = planIssueWorkspace(input);
165
+ }
166
+
167
+ return {
168
+ ...plan,
169
+ exists: true,
170
+ created,
171
+ method: resolution.method,
172
+ warnings: [...(resolution.warnings ?? []), ...initWarnings],
173
+ userMessage: resolution.userMessage,
174
+ resolution,
175
+ };
176
+ }
177
+
178
+ export function cleanupIssueWorkspace(input: WorkspacePlanInput): WorkspaceCleanupResult {
179
+ const plan = planIssueWorkspace(input);
180
+ if (!plan.exists) {
181
+ return { ...plan, removed: false };
182
+ }
183
+ if (!lstatSync(plan.workspacePath).isDirectory()) {
184
+ throw new Error(`Workspace path exists but is not a directory: ${plan.workspacePath}`);
185
+ }
186
+ assertPathInsideRoot(plan.workspaceRoot, plan.workspacePath);
187
+ rmSync(plan.workspacePath, { recursive: true, force: true });
188
+ return { ...plan, exists: false, removed: true };
189
+ }
190
+
191
+ export function sanitizeWorkspaceKey(identifier: string) {
192
+ const safe = identifier.replace(/[^A-Za-z0-9._-]/g, "_");
193
+ return safe || "issue";
194
+ }
195
+
196
+ export function isPathInsideRoot(
197
+ root: string,
198
+ candidate: string,
199
+ pathApi = { isAbsolute, relative, resolve },
200
+ ) {
201
+ const relativePath = pathApi.relative(pathApi.resolve(root), pathApi.resolve(candidate));
202
+ return relativePath === "" || (!relativePath.startsWith("..") && !pathApi.isAbsolute(relativePath));
203
+ }
204
+
205
+ function resolveWorkspaceRoot(input: { root: string; workspaceRoot?: string }) {
206
+ const configured =
207
+ input.workspaceRoot ??
208
+ String(getResolvedConfigValue({ root: input.root, key: "workspace.root" }).value);
209
+ const expanded = expandHome(configured);
210
+ return isAbsolute(expanded) ? resolve(expanded) : resolve(input.root, expanded);
211
+ }
212
+
213
+ function expandHome(path: string) {
214
+ if (path === "~") {
215
+ return homedir();
216
+ }
217
+ if (path.startsWith("~/")) {
218
+ return join(homedir(), path.slice(2));
219
+ }
220
+ return path;
221
+ }
222
+
223
+ function assertPathInsideRoot(root: string, candidate: string) {
224
+ if (!isPathInsideRoot(root, candidate)) {
225
+ throw new Error(`Workspace path escapes workspace root: ${resolve(candidate)}`);
226
+ }
227
+ }