@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,370 @@
1
+ import {
2
+ existsSync,
3
+ mkdirSync,
4
+ readFileSync,
5
+ writeFileSync,
6
+ } from "node:fs";
7
+ import { join } from "node:path";
8
+ import {
9
+ appendIssueEvent,
10
+ getIssuePaths,
11
+ readIssueState,
12
+ } from "./state";
13
+
14
+ export interface CheckpointPayload {
15
+ status?: string;
16
+ summary: string;
17
+ decisions?: string[];
18
+ remainingWork?: string[];
19
+ notes?: string[];
20
+ filesModified?: string[];
21
+ sessionDurationSeconds?: number;
22
+ transitionReason?: string;
23
+ }
24
+
25
+ export interface ResumePacket {
26
+ schemaVersion: 1;
27
+ issueId: string;
28
+ phase: string;
29
+ title: string;
30
+ status: string;
31
+ branch: string;
32
+ writtenAt: string;
33
+ checkpointPath: string;
34
+ summary: string;
35
+ decisions: string[];
36
+ remainingWork: string[];
37
+ notes: string[];
38
+ filesModified: string[];
39
+ sessionDurationSeconds?: number;
40
+ parentCheckpointId?: string;
41
+ transitionReason?: string;
42
+ }
43
+
44
+ interface CheckpointInput {
45
+ root?: string;
46
+ issueId: string;
47
+ title?: string;
48
+ branch?: string;
49
+ now?: Date;
50
+ payload: unknown;
51
+ }
52
+
53
+ export interface IssueCheckpointRecord {
54
+ schemaVersion: 1;
55
+ issueId: string;
56
+ path: string;
57
+ resumePacketPath: string;
58
+ writtenAt: string;
59
+ }
60
+
61
+ export function writeIssueCheckpoint(input: CheckpointInput): IssueCheckpointRecord {
62
+ const root = input.root ?? process.cwd();
63
+ const paths = getIssuePaths(root, input.issueId);
64
+ const state = readIssueState({ root, issueId: input.issueId });
65
+ const payload = normalizeCheckpointPayload(input.payload);
66
+ const title = normalizeTitle(input.title ?? getPayloadTitle(input.payload) ?? "checkpoint");
67
+ const now = input.now ?? new Date();
68
+ const writtenAt = now.toISOString();
69
+ const branch = input.branch?.trim() || "unknown";
70
+
71
+ const checkpointDir = join(paths.issueDir, "memory", "checkpoints");
72
+ mkdirSync(checkpointDir, { recursive: true });
73
+ const resumePacketsDir = join(paths.issueDir, "memory", "resume-packets");
74
+ mkdirSync(resumePacketsDir, { recursive: true });
75
+
76
+ const parentCheckpointId = readPreviousResumePacketPath(paths.issueDir);
77
+
78
+ const basePacket: Omit<ResumePacket, "checkpointPath" | "parentCheckpointId"> = {
79
+ schemaVersion: 1,
80
+ issueId: input.issueId,
81
+ phase: state.currentPhase,
82
+ title,
83
+ status: payload.status ?? "in-progress",
84
+ branch,
85
+ writtenAt,
86
+ summary: payload.summary,
87
+ decisions: payload.decisions ?? [],
88
+ remainingWork: payload.remainingWork ?? [],
89
+ notes: payload.notes ?? [],
90
+ filesModified: payload.filesModified ?? [],
91
+ ...(payload.sessionDurationSeconds === undefined
92
+ ? {}
93
+ : { sessionDurationSeconds: payload.sessionDurationSeconds }),
94
+ ...(payload.transitionReason === undefined
95
+ ? {}
96
+ : { transitionReason: payload.transitionReason }),
97
+ };
98
+
99
+ const relativeCheckpointPath = writeUniqueCheckpointMarkdown({
100
+ issueDir: paths.issueDir,
101
+ timestamp: formatTimestamp(now),
102
+ titleSlug: slugTitle(title),
103
+ renderMarkdown: (checkpointPath) =>
104
+ renderCheckpointMarkdown({ ...basePacket, checkpointPath }),
105
+ });
106
+
107
+ const resumePacket: ResumePacket = {
108
+ ...basePacket,
109
+ checkpointPath: relativeCheckpointPath,
110
+ ...(parentCheckpointId ? { parentCheckpointId } : {}),
111
+ };
112
+
113
+ const relativeResumePacketPath = writeUniqueResumePacket({
114
+ issueDir: paths.issueDir,
115
+ timestamp: formatTimestamp(now),
116
+ titleSlug: slugTitle(title),
117
+ packet: resumePacket,
118
+ });
119
+
120
+ writeFileSync(
121
+ join(paths.issueDir, "memory", "latest-resume-packet.json"),
122
+ `${JSON.stringify({ schemaVersion: 1, path: relativeResumePacketPath }, null, 2)}\n`,
123
+ );
124
+
125
+ appendIssueEvent({
126
+ issueDir: paths.issueDir,
127
+ event: {
128
+ schemaVersion: 1,
129
+ type: "checkpoint.written",
130
+ issueId: input.issueId,
131
+ timestamp: writtenAt,
132
+ payload: {
133
+ checkpointPath: relativeCheckpointPath,
134
+ resumePacketPath: relativeResumePacketPath,
135
+ ...(parentCheckpointId ? { parentCheckpointId } : {}),
136
+ ...(payload.transitionReason ? { transitionReason: payload.transitionReason } : {}),
137
+ },
138
+ },
139
+ });
140
+
141
+ return {
142
+ schemaVersion: 1,
143
+ issueId: input.issueId,
144
+ path: relativeCheckpointPath,
145
+ resumePacketPath: relativeResumePacketPath,
146
+ writtenAt,
147
+ };
148
+ }
149
+
150
+ export function readResumePacket(input: { root?: string; issueId: string }): ResumePacket {
151
+ const root = input.root ?? process.cwd();
152
+ const paths = getIssuePaths(root, input.issueId);
153
+ readIssueState({ root, issueId: input.issueId });
154
+
155
+ const latestIndexPath = join(paths.issueDir, "memory", "latest-resume-packet.json");
156
+ if (existsSync(latestIndexPath)) {
157
+ const index = JSON.parse(readFileSync(latestIndexPath, "utf8")) as { path?: string };
158
+ if (index.path) {
159
+ const packetPath = join(paths.issueDir, index.path);
160
+ if (existsSync(packetPath)) {
161
+ return JSON.parse(readFileSync(packetPath, "utf8")) as ResumePacket;
162
+ }
163
+ }
164
+ }
165
+
166
+ // Backward compatibility: old single-file resume packet
167
+ const oldPath = join(paths.issueDir, "memory", "resume-packet.json");
168
+ if (existsSync(oldPath)) {
169
+ return JSON.parse(readFileSync(oldPath, "utf8")) as ResumePacket;
170
+ }
171
+
172
+ throw new Error(
173
+ `No resume packet found for ${input.issueId}; run gxpm issue checkpoint ${input.issueId} --title "handoff" --stdin`,
174
+ );
175
+ }
176
+
177
+ function readPreviousResumePacketPath(issueDir: string): string | null {
178
+ const latestIndexPath = join(issueDir, "memory", "latest-resume-packet.json");
179
+ if (existsSync(latestIndexPath)) {
180
+ const index = JSON.parse(readFileSync(latestIndexPath, "utf8")) as { path?: string };
181
+ if (index.path) {
182
+ const fullPath = join(issueDir, index.path);
183
+ if (existsSync(fullPath)) {
184
+ return index.path;
185
+ }
186
+ }
187
+ }
188
+
189
+ // Backward compatibility: old single-file resume packet
190
+ const oldPath = join(issueDir, "memory", "resume-packet.json");
191
+ if (existsSync(oldPath)) {
192
+ return "memory/resume-packet.json";
193
+ }
194
+
195
+ return null;
196
+ }
197
+
198
+ function normalizeCheckpointPayload(value: unknown): CheckpointPayload {
199
+ if (!value || typeof value !== "object") {
200
+ throw new Error("Checkpoint payload must be a JSON object");
201
+ }
202
+ const raw = value as Record<string, unknown>;
203
+ if (typeof raw.summary !== "string" || raw.summary.trim() === "") {
204
+ throw new Error("Checkpoint payload requires a non-empty string summary");
205
+ }
206
+
207
+ return {
208
+ status: typeof raw.status === "string" && raw.status.trim() ? raw.status.trim() : undefined,
209
+ summary: raw.summary.trim(),
210
+ decisions: normalizeStringArray(raw.decisions),
211
+ remainingWork: normalizeStringArray(raw.remainingWork),
212
+ notes: normalizeStringArray(raw.notes),
213
+ filesModified: normalizeStringArray(raw.filesModified),
214
+ sessionDurationSeconds:
215
+ typeof raw.sessionDurationSeconds === "number" && Number.isFinite(raw.sessionDurationSeconds)
216
+ ? raw.sessionDurationSeconds
217
+ : undefined,
218
+ transitionReason:
219
+ typeof raw.transitionReason === "string" && raw.transitionReason.trim()
220
+ ? raw.transitionReason.trim()
221
+ : undefined,
222
+ };
223
+ }
224
+
225
+ function getPayloadTitle(value: unknown) {
226
+ if (!value || typeof value !== "object") return null;
227
+ const title = (value as Record<string, unknown>).title;
228
+ return typeof title === "string" && title.trim() ? title : null;
229
+ }
230
+
231
+ function normalizeStringArray(value: unknown): string[] {
232
+ if (!Array.isArray(value)) return [];
233
+ return value.map((item) => String(item).trim()).filter(Boolean);
234
+ }
235
+
236
+ function normalizeTitle(title: string) {
237
+ return title.trim() || "checkpoint";
238
+ }
239
+
240
+ function slugTitle(title: string) {
241
+ const slug = title
242
+ .trim()
243
+ .toLowerCase()
244
+ .replace(/\s+/g, "-")
245
+ .replace(/[^a-z0-9.-]/g, "")
246
+ .slice(0, 60);
247
+ return slug || "checkpoint";
248
+ }
249
+
250
+ function formatTimestamp(date: Date) {
251
+ const yyyy = date.getUTCFullYear();
252
+ const mm = String(date.getUTCMonth() + 1).padStart(2, "0");
253
+ const dd = String(date.getUTCDate()).padStart(2, "0");
254
+ const hh = String(date.getUTCHours()).padStart(2, "0");
255
+ const mi = String(date.getUTCMinutes()).padStart(2, "0");
256
+ const ss = String(date.getUTCSeconds()).padStart(2, "0");
257
+ return `${yyyy}${mm}${dd}-${hh}${mi}${ss}`;
258
+ }
259
+
260
+ function writeUniqueCheckpointMarkdown(input: {
261
+ issueDir: string;
262
+ timestamp: string;
263
+ titleSlug: string;
264
+ renderMarkdown: (relativePath: string) => string;
265
+ }) {
266
+ const base = `memory/checkpoints/${input.timestamp}-${input.titleSlug}`;
267
+ let suffix = 1;
268
+
269
+ while (true) {
270
+ const relativePath = suffix === 1 ? `${base}.md` : `${base}-${suffix}.md`;
271
+ try {
272
+ writeFileSync(join(input.issueDir, relativePath), input.renderMarkdown(relativePath), {
273
+ flag: "wx",
274
+ });
275
+ return relativePath;
276
+ } catch (error) {
277
+ if (isFileExistsError(error)) {
278
+ suffix += 1;
279
+ continue;
280
+ }
281
+ throw error;
282
+ }
283
+ }
284
+ }
285
+
286
+ function writeUniqueResumePacket(input: {
287
+ issueDir: string;
288
+ timestamp: string;
289
+ titleSlug: string;
290
+ packet: ResumePacket;
291
+ }) {
292
+ const base = `memory/resume-packets/${input.timestamp}-${input.titleSlug}`;
293
+ let suffix = 1;
294
+
295
+ while (true) {
296
+ const relativePath = suffix === 1 ? `${base}.json` : `${base}-${suffix}.json`;
297
+ try {
298
+ writeFileSync(join(input.issueDir, relativePath), `${JSON.stringify(input.packet, null, 2)}\n`, {
299
+ flag: "wx",
300
+ });
301
+ return relativePath;
302
+ } catch (error) {
303
+ if (isFileExistsError(error)) {
304
+ suffix += 1;
305
+ continue;
306
+ }
307
+ throw error;
308
+ }
309
+ }
310
+ }
311
+
312
+ function renderCheckpointMarkdown(packet: ResumePacket) {
313
+ return `---
314
+ schemaVersion: 1
315
+ issueId: ${yamlScalar(packet.issueId)}
316
+ status: ${yamlScalar(packet.status)}
317
+ phase: ${yamlScalar(packet.phase)}
318
+ branch: ${yamlScalar(packet.branch)}
319
+ timestamp: ${yamlScalar(packet.writtenAt)}
320
+ checkpointPath: ${yamlScalar(packet.checkpointPath)}
321
+ ${renderFilesModifiedFrontmatter(packet.filesModified)}
322
+ ---
323
+
324
+ ## Working on: ${packet.title}
325
+
326
+ ### Summary
327
+
328
+ ${packet.summary}
329
+
330
+ ### Decisions Made
331
+
332
+ ${renderList(packet.decisions)}
333
+
334
+ ### Remaining Work
335
+
336
+ ${renderNumberedList(packet.remainingWork)}
337
+
338
+ ### Notes
339
+
340
+ ${renderList(packet.notes)}
341
+ `;
342
+ }
343
+
344
+ function renderList(items: string[]) {
345
+ if (items.length === 0) return "- none";
346
+ return items.map((item) => `- ${item}`).join("\n");
347
+ }
348
+
349
+ function renderNumberedList(items: string[]) {
350
+ if (items.length === 0) return "1. none";
351
+ return items.map((item, index) => `${index + 1}. ${item}`).join("\n");
352
+ }
353
+
354
+ function renderFilesModifiedFrontmatter(filesModified: string[]) {
355
+ if (filesModified.length === 0) return "files_modified: []";
356
+ return `files_modified:\n${filesModified.map((path) => ` - ${yamlScalar(path)}`).join("\n")}`;
357
+ }
358
+
359
+ function yamlScalar(value: string) {
360
+ return JSON.stringify(value);
361
+ }
362
+
363
+ function isFileExistsError(error: unknown) {
364
+ return (
365
+ typeof error === "object" &&
366
+ error !== null &&
367
+ "code" in error &&
368
+ (error as { code?: unknown }).code === "EEXIST"
369
+ );
370
+ }
@@ -0,0 +1,32 @@
1
+ import { writeArtifact } from "./artifacts";
2
+ import { readIssueState } from "./state";
3
+
4
+ interface CleanupInput {
5
+ root?: string;
6
+ issueId: string;
7
+ }
8
+
9
+ export function initializeCleanup(input: CleanupInput) {
10
+ const root = input.root ?? process.cwd();
11
+ const state = readIssueState({ root, issueId: input.issueId });
12
+ if (state.currentPhase !== "self-review") {
13
+ throw new Error(
14
+ `Cleanup can only be initialized from self-review phase: current phase is ${state.currentPhase}`,
15
+ );
16
+ }
17
+
18
+ return writeArtifact({
19
+ root,
20
+ issueId: input.issueId,
21
+ type: "cleanup-report",
22
+ payload: {
23
+ duplicatesExtracted: [],
24
+ renamesUnified: [],
25
+ interfacesAligned: [],
26
+ deadCodeRemoved: [],
27
+ testsDeduplicated: [],
28
+ notes: "",
29
+ status: "draft",
30
+ },
31
+ });
32
+ }
@@ -0,0 +1,82 @@
1
+ const SUPPORTED_PREFIXES = ["gxpm", "git", "bun", "npm", "cmux", "agent-browser"] as const;
2
+
3
+ type SupportedPrefix = (typeof SUPPORTED_PREFIXES)[number];
4
+
5
+ export interface CommandProbeFinding {
6
+ command: string;
7
+ reason: string;
8
+ }
9
+
10
+ export function probeArtifactPayloadCommands(payload: unknown): CommandProbeFinding[] {
11
+ const text = collectText(payload);
12
+ const commands = extractCandidateCommands(text);
13
+ return commands.flatMap(validateCandidateCommand);
14
+ }
15
+
16
+ function collectText(payload: unknown): string {
17
+ if (typeof payload === "string") return payload;
18
+ if (Array.isArray(payload)) return payload.map(collectText).join("\n");
19
+ if (payload && typeof payload === "object") {
20
+ return Object.values(payload as Record<string, unknown>).map(collectText).join("\n");
21
+ }
22
+ return "";
23
+ }
24
+
25
+ function extractCandidateCommands(text: string): string[] {
26
+ const commands = new Set<string>();
27
+ const fenced = [...text.matchAll(/```[\s\S]*?```/g)].map((match) =>
28
+ match[0].replace(/^```[a-zA-Z0-9_-]*\n?/, "").replace(/```$/, ""),
29
+ );
30
+ const inline = [...text.matchAll(/`([^`]+)`/g)].map((match) => match[1]);
31
+ for (const block of [...fenced, ...inline]) {
32
+ for (const line of block.split("\n")) {
33
+ const trimmed = line.trim();
34
+ if (SUPPORTED_PREFIXES.some((prefix) => trimmed === prefix || trimmed.startsWith(`${prefix} `))) {
35
+ commands.add(trimmed);
36
+ }
37
+ }
38
+ }
39
+ return [...commands];
40
+ }
41
+
42
+ function validateCandidateCommand(command: string): CommandProbeFinding[] {
43
+ const [prefix, subcommand] = command.split(/\s+/, 3);
44
+ if (!isSupportedPrefix(prefix)) {
45
+ return [];
46
+ }
47
+ if (prefix === "gxpm") {
48
+ const allowed = new Set([
49
+ "issue",
50
+ "artifact",
51
+ "triage",
52
+ "plan",
53
+ "dispatch",
54
+ "implement",
55
+ "local-verify",
56
+ "ac-check",
57
+ "self-review",
58
+ "ship",
59
+ "pr-check",
60
+ "verify",
61
+ "qa",
62
+ "land",
63
+ "doctor",
64
+ "wiki",
65
+ "gate",
66
+ "config",
67
+ "worktree",
68
+ "version",
69
+ "check",
70
+ ]);
71
+ return allowed.has(subcommand ?? "")
72
+ ? []
73
+ : [{ command, reason: `unknown gxpm subcommand: ${subcommand ?? "<missing>"}` }];
74
+ }
75
+ return subcommand
76
+ ? []
77
+ : [{ command, reason: `missing subcommand after ${prefix}` }];
78
+ }
79
+
80
+ function isSupportedPrefix(value: string): value is SupportedPrefix {
81
+ return SUPPORTED_PREFIXES.includes(value as SupportedPrefix);
82
+ }