@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,216 @@
1
+ import { runCleanupLandCommand } from "./cleanup";
2
+ import { formatDoctorReport, runDoctor } from "./doctor";
3
+ import { runGlobalDiscover } from "./global-discover";
4
+ import { findPhaseArtifactCommand } from "./phase-artifact-commands";
5
+ import { runScaffoldCheck } from "./scaffold-check";
6
+ import { readGxpmVersion } from "./version";
7
+ import { resolveSessionId } from "../core/session";
8
+ import { getWorkflowEventEmitter } from "../core/workflow-event-emitter";
9
+ import { runArtifactCommand } from "./commands/artifact";
10
+ import { runCapabilityCommand } from "./commands/capability";
11
+ import { runConfigCommand, runWorktreePolicyCommand } from "./commands/config";
12
+ import { runGateCommand } from "./commands/gate";
13
+ import { runIssueCommand } from "./commands/issue";
14
+ import { runOrchestratorCommand, runRunCommand, runWorkspaceCommand } from "./commands/runtime";
15
+ import { runWikiCommand } from "./commands/wiki";
16
+ import { runInitCommand } from "./commands/init";
17
+ import { runPostUpgradeCommand, runUpgradeCommand } from "./commands/upgrade";
18
+ import { runVerifyCommand } from "./commands/verify";
19
+ import { runDagCommand } from "./commands/dag";
20
+ import { runHookCommand } from "./commands/hook";
21
+ import { runWorkflowCommand } from "./commands/workflow";
22
+ import { runPresetCommand } from "./commands/preset";
23
+ import { runPhaseCommand } from "./commands/phase";
24
+ import { runSpecifyCommand } from "./commands/specify";
25
+ import { runFeedbackCommand } from "./commands/feedback";
26
+
27
+ async function main(argv: string[]) {
28
+ if (argv.includes("--verbose-events")) {
29
+ argv = argv.filter((arg) => arg !== "--verbose-events");
30
+ getWorkflowEventEmitter().subscribe((event) => {
31
+ console.error(`[event] ${JSON.stringify(event)}`);
32
+ });
33
+ }
34
+ // Filter out flags like --army before positional parsing
35
+ const positional = argv.filter((arg) => !arg.startsWith("--"));
36
+ const [command, subcommand, issueId, value] = positional;
37
+
38
+ if (!command || command === "check") {
39
+ console.log(runScaffoldCheck());
40
+ return;
41
+ }
42
+
43
+ if (command === "version" || command === "--version" || command === "-v") {
44
+ console.log(readGxpmVersion());
45
+ return;
46
+ }
47
+
48
+ if (command === "session-id") {
49
+ console.log(resolveSessionId());
50
+ return;
51
+ }
52
+
53
+ if (command === "config") {
54
+ runConfigCommand(argv, subcommand, issueId, value);
55
+ return;
56
+ }
57
+
58
+ if (command === "worktree") {
59
+ runWorktreePolicyCommand(argv, subcommand);
60
+ return;
61
+ }
62
+
63
+ if (command === "capability") {
64
+ runCapabilityCommand(argv, subcommand, issueId);
65
+ return;
66
+ }
67
+
68
+ if (command === "wiki") {
69
+ runWikiCommand(argv, subcommand);
70
+ return;
71
+ }
72
+
73
+ if (command === "init") {
74
+ runInitCommand(argv.slice(1));
75
+ return;
76
+ }
77
+
78
+ if (command === "doctor") {
79
+ const json = argv.includes("--json");
80
+ const fix = argv.includes("--fix");
81
+ const report = runDoctor({ fix });
82
+ if (json) {
83
+ console.log(JSON.stringify(report, null, 2));
84
+ } else {
85
+ console.log(formatDoctorReport(report));
86
+ }
87
+ return;
88
+ }
89
+
90
+ if (command === "upgrade") {
91
+ runUpgradeCommand(argv.slice(1));
92
+ return;
93
+ }
94
+
95
+ if (command === "post-upgrade") {
96
+ runPostUpgradeCommand(argv.slice(1));
97
+ return;
98
+ }
99
+
100
+ if (command === "verify") {
101
+ // gxpm verify qa <id> is a phase artifact command; do not shadow it.
102
+ const phaseArtifactCommand = findPhaseArtifactCommand(command, subcommand);
103
+ if (!phaseArtifactCommand) {
104
+ runVerifyCommand(argv.slice(1));
105
+ return;
106
+ }
107
+ }
108
+
109
+ if (command === "run") {
110
+ runRunCommand(argv, subcommand, issueId, value);
111
+ return;
112
+ }
113
+
114
+ if (command === "workspace") {
115
+ await runWorkspaceCommand(argv, subcommand, issueId);
116
+ return;
117
+ }
118
+
119
+ if (command === "orchestrator") {
120
+ runOrchestratorCommand(argv, subcommand);
121
+ return;
122
+ }
123
+
124
+ if (command === "global-discover") {
125
+ const entries = runGlobalDiscover();
126
+ if (argv.includes("--json")) {
127
+ console.log(JSON.stringify(entries, null, 2));
128
+ } else {
129
+ for (const entry of entries) {
130
+ console.log(`${entry.key}\t${entry.repos.join(",")}`);
131
+ }
132
+ }
133
+ return;
134
+ }
135
+
136
+ if (command === "issue") {
137
+ await runIssueCommand(argv, subcommand, issueId, value);
138
+ return;
139
+ }
140
+
141
+ if (command === "artifact") {
142
+ runArtifactCommand(argv, subcommand, issueId, value);
143
+ return;
144
+ }
145
+
146
+ if (command === "autopilot") {
147
+ const { runAutopilotCommand } = await import("./commands/autopilot");
148
+ runAutopilotCommand(argv, subcommand, issueId);
149
+ return;
150
+ }
151
+
152
+ if (command === "gate") {
153
+ runGateCommand(argv, subcommand, issueId);
154
+ return;
155
+ }
156
+
157
+ if (command === "dag") {
158
+ runDagCommand(argv);
159
+ return;
160
+ }
161
+
162
+ if (command === "workflow") {
163
+ runWorkflowCommand(argv);
164
+ return;
165
+ }
166
+
167
+ if (command === "hook") {
168
+ await runHookCommand(argv);
169
+ return;
170
+ }
171
+
172
+ if (command === "preset") {
173
+ runPresetCommand(argv, subcommand, issueId);
174
+ return;
175
+ }
176
+
177
+ if (command === "specify" && subcommand !== "init") {
178
+ runSpecifyCommand(argv, subcommand, issueId);
179
+ return;
180
+ }
181
+
182
+ if (command === "feedback") {
183
+ await runFeedbackCommand(argv, subcommand);
184
+ return;
185
+ }
186
+
187
+ if (command === "phase") {
188
+ runPhaseCommand(argv, subcommand);
189
+ return;
190
+ }
191
+
192
+ if (command === "cleanup" && subcommand === "land") {
193
+ if (!issueId) {
194
+ throw new Error("Usage: gxpm cleanup land <issue-id> [--execute] [--force]");
195
+ }
196
+ runCleanupLandCommand(argv, issueId);
197
+ return;
198
+ }
199
+
200
+ const phaseArtifactCommand = findPhaseArtifactCommand(command, subcommand);
201
+ if (phaseArtifactCommand) {
202
+ if (!issueId) {
203
+ throw new Error(`Usage: ${phaseArtifactCommand.command}`);
204
+ }
205
+ phaseArtifactCommand.initialize({ issueId });
206
+ console.log(phaseArtifactCommand.successMessage(issueId));
207
+ return;
208
+ }
209
+
210
+ throw new Error(`Unknown command: ${[command, subcommand].filter(Boolean).join(" ")}`);
211
+ }
212
+
213
+ main(Bun.argv.slice(2)).catch((error) => {
214
+ console.error(error instanceof Error ? error.message : String(error));
215
+ process.exit(1);
216
+ });
@@ -0,0 +1,62 @@
1
+ import type { HostConfig } from "../core/contracts/host";
2
+
3
+ export type { FrontmatterConfig, HostConfig } from "../core/contracts/host";
4
+
5
+ const HOST_NAME_PATTERN = /^[a-z][a-z0-9-]*$/;
6
+ const CLI_PATTERN = /^[a-z][a-z0-9-]*$/;
7
+ const SAFE_RELATIVE_PATH = /^(?!\/)(?!.*(?:^|\/)\.\.(?:\/|$))(?!.*\/\/).+$/;
8
+
9
+ export function validateHostConfig(config: HostConfig): string[] {
10
+ const errors: string[] = [];
11
+
12
+ if (!HOST_NAME_PATTERN.test(config.name)) {
13
+ errors.push(`${config.name || "<missing>"}: host name must be lowercase kebab-case`);
14
+ }
15
+
16
+ if (!config.displayName.trim()) {
17
+ errors.push(`${config.name}: displayName is required`);
18
+ }
19
+
20
+ if (!CLI_PATTERN.test(config.cliCommand)) {
21
+ errors.push(`${config.name}: cliCommand must be a simple executable name`);
22
+ }
23
+
24
+ for (const [field, value] of [
25
+ ["hostSubdir", config.hostSubdir],
26
+ ["globalRoot", config.globalRoot],
27
+ ["localSkillRoot", config.localSkillRoot],
28
+ ] as const) {
29
+ if (!SAFE_RELATIVE_PATH.test(value)) {
30
+ errors.push(`${config.name}: ${field} must be a safe relative path`);
31
+ }
32
+ }
33
+
34
+ if (config.frontmatter.mode === "allowlist" && config.frontmatter.keys.length === 0) {
35
+ errors.push(`${config.name}: frontmatter allowlist needs at least one key`);
36
+ }
37
+
38
+ if (!["copy", "symlink"].includes(config.install.strategy)) {
39
+ errors.push(`${config.name}: unsupported install strategy ${config.install.strategy}`);
40
+ }
41
+
42
+ return errors;
43
+ }
44
+
45
+ export function validateAllConfigs(configs: readonly HostConfig[]): string[] {
46
+ const errors = configs.flatMap(validateHostConfig);
47
+
48
+ for (const field of ["name", "hostSubdir", "globalRoot"] as const) {
49
+ const seen = new Map<string, string>();
50
+ for (const config of configs) {
51
+ const value = config[field];
52
+ const owner = seen.get(value);
53
+ if (owner) {
54
+ errors.push(`${config.name}: ${field} duplicates ${owner} (${value})`);
55
+ } else {
56
+ seen.set(value, config.name);
57
+ }
58
+ }
59
+ }
60
+
61
+ return errors;
62
+ }
@@ -0,0 +1,138 @@
1
+ /**
2
+ * Install Claude Code hooks that call the unified `gxpm hook` entry point.
3
+ *
4
+ * Writes/merges a `hooks` block into `.claude/settings.json` (repo scope)
5
+ * or `~/.claude/settings.json` (user scope).
6
+ */
7
+
8
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
9
+ import { homedir } from "node:os";
10
+ import { join, resolve } from "node:path";
11
+
12
+ interface InstallClaudeHooksOptions {
13
+ /** "repo" → <target>/.claude/ (default), "user" → ~/.claude/ */
14
+ scope?: "user" | "repo";
15
+ target?: string;
16
+ home?: string;
17
+ }
18
+
19
+ interface InstallResult {
20
+ settingsJsonPath: string;
21
+ rootDir: string;
22
+ }
23
+
24
+ export function installClaudeHooks(options: InstallClaudeHooksOptions = {}): InstallResult {
25
+ const scope = options.scope ?? "repo";
26
+ const home = options.home ?? homedir();
27
+
28
+ const rootDir =
29
+ scope === "user" ? join(home, ".claude") : join(resolve(options.target ?? process.cwd()), ".claude");
30
+ mkdirSync(rootDir, { recursive: true });
31
+
32
+ const settingsJsonPath = join(rootDir, "settings.json");
33
+
34
+ const newConfig = {
35
+ hooks: {
36
+ SessionStart: [
37
+ {
38
+ matcher: "startup|clear|compact",
39
+ hooks: [
40
+ {
41
+ type: "command",
42
+ command: "gxpm hook SessionStart --host claude",
43
+ async: false,
44
+ statusMessage: "gxpm: loading capability hint",
45
+ },
46
+ ],
47
+ },
48
+ ],
49
+ UserPromptSubmit: [
50
+ {
51
+ hooks: [
52
+ {
53
+ type: "command",
54
+ command: "gxpm hook UserPromptSubmit --host claude",
55
+ async: false,
56
+ statusMessage: "gxpm: resolving referenced issue",
57
+ },
58
+ ],
59
+ },
60
+ ],
61
+ PreToolUse: [
62
+ {
63
+ matcher: "ExitPlanMode",
64
+ hooks: [
65
+ {
66
+ type: "command",
67
+ command: "gxpm hook PreToolUse --host claude",
68
+ async: false,
69
+ statusMessage: "gxpm: recording plan payload",
70
+ },
71
+ ],
72
+ },
73
+ ],
74
+ },
75
+ };
76
+
77
+ const merged = mergeWithExisting(settingsJsonPath, newConfig);
78
+ writeFileSync(settingsJsonPath, JSON.stringify(merged, null, 2) + "\n");
79
+
80
+ return { settingsJsonPath, rootDir };
81
+ }
82
+
83
+ function mergeWithExisting(path: string, fresh: Record<string, any>) {
84
+ if (!existsSync(path)) return fresh;
85
+ let existing: Record<string, any> = {};
86
+ try {
87
+ existing = JSON.parse(readFileSync(path, "utf8"));
88
+ } catch {
89
+ return fresh;
90
+ }
91
+
92
+ const eventNames = Object.keys(fresh.hooks ?? {});
93
+ const existingHooks = existing.hooks ?? {};
94
+
95
+ for (const event of eventNames) {
96
+ const ourEntries = (fresh.hooks as Record<string, any[]>)[event] ?? [];
97
+ const ourCommands = new Set(
98
+ ourEntries.flatMap((e: any) => (e.hooks ?? []).map((h: any) => h.command)),
99
+ );
100
+ const otherEntries: any[] = [];
101
+ for (const entry of existingHooks[event] ?? []) {
102
+ const filteredHooks = (entry.hooks ?? []).filter((h: any) => !ourCommands.has(h.command));
103
+ if (filteredHooks.length > 0) {
104
+ otherEntries.push({ ...entry, hooks: filteredHooks });
105
+ }
106
+ }
107
+ existingHooks[event] = [...otherEntries, ...ourEntries];
108
+ }
109
+
110
+ return { ...existing, hooks: existingHooks };
111
+ }
112
+
113
+ function parseArgs(argv: string[]): InstallClaudeHooksOptions {
114
+ const opts: InstallClaudeHooksOptions = {};
115
+ for (let i = 0; i < argv.length; i += 1) {
116
+ const a = argv[i];
117
+ if (a === "--scope") opts.scope = argv[++i] as "user" | "repo";
118
+ else if (a === "--target") opts.target = argv[++i];
119
+ else if (a === "--home") opts.home = argv[++i];
120
+ else throw new Error(`Unknown argument: ${a}`);
121
+ }
122
+ return opts;
123
+ }
124
+
125
+ if (import.meta.main) {
126
+ try {
127
+ const result = installClaudeHooks(parseArgs(Bun.argv.slice(2)));
128
+ console.log(`wrote: ${result.settingsJsonPath}`);
129
+ console.log(
130
+ "scope:",
131
+ result.rootDir.includes(homedir() + "/.claude") ? "user (~/.claude/)" : "repo (<repo>/.claude/)",
132
+ );
133
+ console.log("Restart Claude Code to activate hooks.");
134
+ } catch (error) {
135
+ console.error(error instanceof Error ? error.message : String(error));
136
+ process.exit(1);
137
+ }
138
+ }
@@ -0,0 +1,271 @@
1
+ /**
2
+ * Install Codex CLI hooks that call the unified `gxpm hook` entry point.
3
+ *
4
+ * Instead of installing per-event bash scripts, we write a single hooks.json
5
+ * that delegates to `gxpm hook <event> --host codex`. All business logic lives
6
+ * in core/hook-engine.ts and is shared across Claude, Codex, Kimi, and Cursor.
7
+ */
8
+
9
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
10
+ import { homedir } from "node:os";
11
+ import { join, resolve } from "node:path";
12
+
13
+ interface InstallCodexHooksOptions {
14
+ /** "repo" → <target>/.codex/ (default), "user" → ~/.codex/ */
15
+ scope?: "user" | "repo";
16
+ target?: string;
17
+ home?: string;
18
+ enableFeatureFlag?: boolean;
19
+ }
20
+
21
+ interface InstallResult {
22
+ hooksJsonPath: string;
23
+ rootDir: string;
24
+ featureFlagEnabled: "already-set" | "enabled-now" | "config-missing" | "skipped";
25
+ }
26
+
27
+ const DEFAULT_GXPM_ROOT = resolve(import.meta.dir, "..");
28
+ const GXPM_CODEX_HOOK_CONFIG = {
29
+ hooks: {
30
+ SessionStart: [
31
+ {
32
+ matcher: "startup|clear|compact",
33
+ hooks: [
34
+ {
35
+ type: "command",
36
+ command: "gxpm hook SessionStart --host codex",
37
+ statusMessage: "gxpm: loading capability hint",
38
+ },
39
+ ],
40
+ },
41
+ ],
42
+ UserPromptSubmit: [
43
+ {
44
+ hooks: [
45
+ {
46
+ type: "command",
47
+ command: "gxpm hook UserPromptSubmit --host codex",
48
+ statusMessage: "gxpm: resolving referenced issue",
49
+ },
50
+ ],
51
+ },
52
+ ],
53
+ PreToolUse: [
54
+ {
55
+ hooks: [
56
+ {
57
+ type: "command",
58
+ command: "gxpm hook PreToolUse --host codex",
59
+ statusMessage: "gxpm: recording update_plan payload",
60
+ },
61
+ ],
62
+ },
63
+ ],
64
+ Stop: [
65
+ {
66
+ hooks: [
67
+ {
68
+ type: "command",
69
+ command: "gxpm hook Stop --host codex",
70
+ statusMessage: "gxpm: checking autopilot grant",
71
+ },
72
+ ],
73
+ },
74
+ ],
75
+ },
76
+ };
77
+ const LEGACY_GXPM_CODEX_HOOK_MARKERS: Record<string, string[]> = {
78
+ SessionStart: [
79
+ ".codex/hooks/gxpm-session-start.sh",
80
+ "gxpm-session-start.sh",
81
+ ],
82
+ UserPromptSubmit: [
83
+ ".codex/hooks/gxpm-user-prompt-submit.sh",
84
+ "gxpm-user-prompt-submit.sh",
85
+ ],
86
+ PreToolUse: [
87
+ ".codex/hooks/gxpm-pre-tool-use.sh",
88
+ "gxpm-pre-tool-use.sh",
89
+ ],
90
+ Stop: [
91
+ ".codex/hooks/gxpm-stop.sh",
92
+ "gxpm-stop.sh",
93
+ ],
94
+ };
95
+
96
+ export function installCodexHooks(options: InstallCodexHooksOptions = {}): InstallResult {
97
+ const scope = options.scope ?? "repo";
98
+ const home = options.home ?? homedir();
99
+
100
+ const rootDir =
101
+ scope === "user" ? join(home, ".codex") : join(resolve(options.target ?? process.cwd()), ".codex");
102
+ mkdirSync(rootDir, { recursive: true });
103
+
104
+ const hooksJsonPath = join(rootDir, "hooks.json");
105
+
106
+ const merged = mergeWithExisting(hooksJsonPath, GXPM_CODEX_HOOK_CONFIG);
107
+ writeFileSync(hooksJsonPath, JSON.stringify(merged, null, 2) + "\n");
108
+ const userHooksJsonPath = join(home, ".codex", "hooks.json");
109
+ if (scope === "repo" && resolve(hooksJsonPath) !== resolve(userHooksJsonPath)) {
110
+ removeGxpmOwnedHooksFrom(userHooksJsonPath, GXPM_CODEX_HOOK_CONFIG);
111
+ }
112
+
113
+ const featureFlagEnabled = (options.enableFeatureFlag ?? true)
114
+ ? ensureCodexHooksFeatureFlag(home)
115
+ : "skipped";
116
+
117
+ return { hooksJsonPath, rootDir, featureFlagEnabled };
118
+ }
119
+
120
+ function ensureCodexHooksFeatureFlag(home: string): "already-set" | "enabled-now" | "config-missing" {
121
+ const configPath = join(home, ".codex", "config.toml");
122
+ if (!existsSync(configPath)) return "config-missing";
123
+
124
+ const content = readFileSync(configPath, "utf8");
125
+ if (/^codex_hooks\s*=\s*true\s*$/m.test(content)) return "already-set";
126
+
127
+ const ts = new Date().toISOString().replace(/[-:.TZ]/g, "").slice(0, 14);
128
+ const backup = `${configPath}.bak-codex-hooks-${ts}`;
129
+ writeFileSync(backup, content);
130
+
131
+ const featuresMatch = content.match(/^\[features\]\s*$/m);
132
+ let updated: string;
133
+ if (featuresMatch) {
134
+ const lines = content.split("\n");
135
+ const idx = lines.findIndex((line) => /^\[features\]\s*$/.test(line));
136
+ let endIdx = lines.length;
137
+ for (let i = idx + 1; i < lines.length; i += 1) {
138
+ if (/^\[/.test(lines[i])) { endIdx = i; break; }
139
+ }
140
+ let insertAt = endIdx;
141
+ while (insertAt > idx + 1 && lines[insertAt - 1].trim() === "") insertAt -= 1;
142
+ lines.splice(insertAt, 0, "codex_hooks = true");
143
+ updated = lines.join("\n");
144
+ } else {
145
+ updated = content.replace(/\n*$/, "\n\n[features]\ncodex_hooks = true\n");
146
+ }
147
+ writeFileSync(configPath, updated);
148
+ return "enabled-now";
149
+ }
150
+
151
+ function mergeWithExisting(path: string, fresh: Record<string, any>) {
152
+ if (!existsSync(path)) return fresh;
153
+ let existing: Record<string, any> = {};
154
+ try {
155
+ existing = JSON.parse(readFileSync(path, "utf8"));
156
+ } catch {
157
+ return fresh;
158
+ }
159
+
160
+ const eventNames = Object.keys(fresh.hooks ?? {});
161
+ const existingHooks = existing.hooks ?? {};
162
+
163
+ for (const event of eventNames) {
164
+ const ourEntries = (fresh.hooks as Record<string, any[]>)[event] ?? [];
165
+ const ourCommands = new Set(
166
+ ourEntries.flatMap((e: any) => (e.hooks ?? []).map((h: any) => h.command)),
167
+ );
168
+ const otherEntries: any[] = [];
169
+ for (const entry of existingHooks[event] ?? []) {
170
+ const filteredHooks = (entry.hooks ?? []).filter(
171
+ (h: any) => !isGxpmOwnedCodexHook(event, h.command, ourCommands),
172
+ );
173
+ if (filteredHooks.length > 0) {
174
+ otherEntries.push({ ...entry, hooks: filteredHooks });
175
+ }
176
+ }
177
+ existingHooks[event] = [...otherEntries, ...ourEntries];
178
+ }
179
+
180
+ return { ...existing, hooks: existingHooks };
181
+ }
182
+
183
+ function removeGxpmOwnedHooksFrom(path: string, fresh: Record<string, any>) {
184
+ if (!existsSync(path)) return;
185
+ let existing: Record<string, any> = {};
186
+ try {
187
+ existing = JSON.parse(readFileSync(path, "utf8"));
188
+ } catch {
189
+ return;
190
+ }
191
+ if (!existing.hooks || typeof existing.hooks !== "object") return;
192
+
193
+ const eventNames = Object.keys(fresh.hooks ?? {});
194
+ for (const event of eventNames) {
195
+ const entries = Array.isArray(existing.hooks[event]) ? existing.hooks[event] : [];
196
+ const freshEntries = (fresh.hooks as Record<string, any[]>)[event] ?? [];
197
+ const freshCommands = new Set(
198
+ freshEntries.flatMap((e: any) => (e.hooks ?? []).map((h: any) => h.command)),
199
+ );
200
+ const nextEntries = entries
201
+ .map((entry: any) => {
202
+ const hooks = Array.isArray(entry?.hooks) ? entry.hooks : [];
203
+ const keptHooks = hooks.filter(
204
+ (hook: any) => !isGxpmOwnedCodexHook(event, hook.command, freshCommands),
205
+ );
206
+ return { ...entry, hooks: keptHooks };
207
+ })
208
+ .filter((entry: any) => Array.isArray(entry.hooks) && entry.hooks.length > 0);
209
+
210
+ if (nextEntries.length > 0) {
211
+ existing.hooks[event] = nextEntries;
212
+ } else {
213
+ delete existing.hooks[event];
214
+ }
215
+ }
216
+
217
+ writeFileSync(path, JSON.stringify(existing, null, 2) + "\n");
218
+ }
219
+
220
+ function isGxpmOwnedCodexHook(event: string, command: unknown, currentCommands: Set<unknown>) {
221
+ if (currentCommands.has(command)) return true;
222
+ if (typeof command !== "string") return false;
223
+ return (LEGACY_GXPM_CODEX_HOOK_MARKERS[event] ?? []).some((marker) =>
224
+ command.includes(marker),
225
+ );
226
+ }
227
+
228
+ function parseArgs(argv: string[]): InstallCodexHooksOptions {
229
+ const opts: InstallCodexHooksOptions = {};
230
+ for (let i = 0; i < argv.length; i += 1) {
231
+ const a = argv[i];
232
+ if (a === "--scope") opts.scope = argv[++i] as "user" | "repo";
233
+ else if (a === "--target") opts.target = argv[++i];
234
+ else if (a === "--home") opts.home = argv[++i];
235
+ else if (a === "--no-feature-flag") opts.enableFeatureFlag = false;
236
+ else throw new Error(`Unknown argument: ${a}`);
237
+ }
238
+ return opts;
239
+ }
240
+
241
+ if (import.meta.main) {
242
+ try {
243
+ const result = installCodexHooks(parseArgs(Bun.argv.slice(2)));
244
+ console.log(`wrote: ${result.hooksJsonPath}`);
245
+ console.log(
246
+ "scope:",
247
+ result.rootDir.includes(homedir() + "/.codex") ? "user (~/.codex/)" : "repo (<repo>/.codex/)",
248
+ );
249
+
250
+ switch (result.featureFlagEnabled) {
251
+ case "already-set":
252
+ console.log("feature flag: codex_hooks = true (already enabled)");
253
+ break;
254
+ case "enabled-now":
255
+ console.log("feature flag: codex_hooks = true (enabled now; backup written)");
256
+ break;
257
+ case "config-missing":
258
+ console.log("feature flag: ⚠️ ~/.codex/config.toml not found; create it with [features]\ncodex_hooks = true");
259
+ break;
260
+ case "skipped":
261
+ console.log("feature flag: skipped per --no-feature-flag");
262
+ break;
263
+ }
264
+ console.log("");
265
+ console.log("For repo-scope hooks: trust the .codex/ layer when Codex prompts.");
266
+ console.log("Restart Codex to activate hooks.");
267
+ } catch (error) {
268
+ console.error(error instanceof Error ? error.message : String(error));
269
+ process.exit(1);
270
+ }
271
+ }