@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,152 @@
1
+ /**
2
+ * Template Engine — Variable substitution and inter-node data flow for
3
+ * capability prompts and skill templates.
4
+ *
5
+ * Inspired by Archon dag-executor.ts and executor-shared.ts, but stripped
6
+ * of provider/platform dependencies to remain a pure string-replacement
7
+ * utility that gxpm capabilities can call.
8
+ */
9
+
10
+ export interface NodeOutput {
11
+ state: string;
12
+ output: string;
13
+ error?: string;
14
+ }
15
+
16
+ export interface SubstitutionContext {
17
+ workflowId?: string;
18
+ userMessage?: string;
19
+ artifactsDir?: string;
20
+ baseBranch?: string;
21
+ docsDir?: string;
22
+ loopUserInput?: string;
23
+ rejectionReason?: string;
24
+ loopPrevOutput?: string;
25
+ issueContext?: string;
26
+ }
27
+
28
+ const CONTEXT_VAR_PATTERN = /\$CONTEXT|\$EXTERNAL_CONTEXT|\$ISSUE_CONTEXT/g;
29
+
30
+ /**
31
+ * Substitute workflow-level variables in a template.
32
+ *
33
+ * Supported variables:
34
+ * $WORKFLOW_ID, $USER_MESSAGE, $ARGUMENTS, $ARTIFACTS_DIR,
35
+ * $BASE_BRANCH, $DOCS_DIR, $LOOP_USER_INPUT, $REJECTION_REASON,
36
+ * $LOOP_PREV_OUTPUT, $CONTEXT, $EXTERNAL_CONTEXT, $ISSUE_CONTEXT
37
+ *
38
+ * Returns the substituted prompt and a flag indicating whether a $CONTEXT
39
+ * variable was present (used by buildPromptWithContext to avoid duplication).
40
+ */
41
+ export function substituteWorkflowVariables(
42
+ template: string,
43
+ context: SubstitutionContext = {}
44
+ ): { prompt: string; contextSubstituted: boolean } {
45
+ if (!context.baseBranch && template.includes("$BASE_BRANCH")) {
46
+ throw new Error(
47
+ "No base branch could be resolved. " +
48
+ "Set worktree.baseBranch in .gxpm/config.json or use --from flag."
49
+ );
50
+ }
51
+
52
+ const resolvedDocsDir = context.docsDir || "docs/";
53
+
54
+ let result = template
55
+ .replace(/\$WORKFLOW_ID/g, context.workflowId ?? "")
56
+ .replace(/\$USER_MESSAGE/g, context.userMessage ?? "")
57
+ .replace(/\$ARGUMENTS/g, context.userMessage ?? "")
58
+ .replace(/\$ARTIFACTS_DIR/g, context.artifactsDir ?? "")
59
+ .replace(/\$BASE_BRANCH/g, context.baseBranch ?? "")
60
+ .replace(/\$DOCS_DIR/g, resolvedDocsDir)
61
+ .replace(/\$LOOP_USER_INPUT/g, context.loopUserInput ?? "")
62
+ .replace(/\$REJECTION_REASON/g, context.rejectionReason ?? "")
63
+ .replace(/\$LOOP_PREV_OUTPUT/g, context.loopPrevOutput ?? "");
64
+
65
+ const hasContextVariables = CONTEXT_VAR_PATTERN.test(result);
66
+
67
+ if (!context.issueContext && hasContextVariables) {
68
+ // Clear context variables when no context is provided
69
+ }
70
+
71
+ result = result.replace(CONTEXT_VAR_PATTERN, context.issueContext ?? "");
72
+
73
+ return {
74
+ prompt: result,
75
+ contextSubstituted: hasContextVariables && !!context.issueContext,
76
+ };
77
+ }
78
+
79
+ /**
80
+ * Build a final prompt by substituting workflow variables and optionally
81
+ * appending issue context when it was not already substituted inline.
82
+ */
83
+ export function buildPromptWithContext(
84
+ template: string,
85
+ context: SubstitutionContext = {}
86
+ ): string {
87
+ const { prompt, contextSubstituted } = substituteWorkflowVariables(
88
+ template,
89
+ context
90
+ );
91
+
92
+ if (context.issueContext && !contextSubstituted) {
93
+ return prompt + "\n\n---\n\n" + context.issueContext;
94
+ }
95
+
96
+ return prompt;
97
+ }
98
+
99
+ /**
100
+ * Single-quote a string for safe inline shell use.
101
+ */
102
+ function shellQuote(value: string): string {
103
+ return `'${value.replaceAll("'", "'\\''")}'`;
104
+ }
105
+
106
+ /**
107
+ * Substitute $nodeId.output and $nodeId.output.field references in a prompt.
108
+ *
109
+ * Called *after* the standard substituteWorkflowVariables pass.
110
+ *
111
+ * @param prompt - The prompt template with variable placeholders
112
+ * @param nodeOutputs - Map of node IDs to their outputs
113
+ * @param escapedForBash - When true, wraps substituted values in single quotes
114
+ * so they are safe to embed in bash scripts. Set true only for bash node
115
+ * script substitution; AI/command prompt substitution should use false.
116
+ */
117
+ export function substituteNodeOutputRefs(
118
+ prompt: string,
119
+ nodeOutputs: Map<string, NodeOutput>,
120
+ escapedForBash = false
121
+ ): string {
122
+ return prompt.replace(
123
+ /\$([a-zA-Z_][a-zA-Z0-9_-]*)\.output(?:\.([a-zA-Z_][a-zA-Z0-9_]*))?/g,
124
+ (match, nodeId: string, field: string | undefined) => {
125
+ const nodeOutput = nodeOutputs.get(nodeId);
126
+ if (!nodeOutput) {
127
+ return escapedForBash ? "''" : "";
128
+ }
129
+ if (!field) {
130
+ return escapedForBash
131
+ ? shellQuote(nodeOutput.output)
132
+ : nodeOutput.output;
133
+ }
134
+ try {
135
+ const parsed = JSON.parse(nodeOutput.output) as Record<
136
+ string,
137
+ unknown
138
+ >;
139
+ const value = parsed[field];
140
+ if (typeof value === "string") {
141
+ return escapedForBash ? shellQuote(value) : value;
142
+ }
143
+ if (typeof value === "number" || typeof value === "boolean") {
144
+ return String(value);
145
+ }
146
+ return escapedForBash ? "''" : "";
147
+ } catch {
148
+ return escapedForBash ? "''" : "";
149
+ }
150
+ }
151
+ );
152
+ }
@@ -0,0 +1,70 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from "bun:test";
2
+ import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import { tmpdir } from "node:os";
5
+ import { TemplateResolver } from "./template-resolver";
6
+
7
+ describe("TemplateResolver", () => {
8
+ let root: string;
9
+ let resolver: TemplateResolver;
10
+
11
+ beforeEach(() => {
12
+ root = mkdtempSync(join(tmpdir(), "gxpm-template-test-"));
13
+ mkdirSync(join(root, ".gxpm", "overrides"), { recursive: true });
14
+ mkdirSync(join(root, ".gxpm", "presets"), { recursive: true });
15
+ mkdirSync(join(root, ".gxpm", "extensions"), { recursive: true });
16
+ mkdirSync(join(root, "skills", "gxpm"), { recursive: true });
17
+ resolver = new TemplateResolver(root);
18
+ });
19
+
20
+ afterEach(() => {
21
+ rmSync(root, { recursive: true, force: true });
22
+ });
23
+
24
+ it("resolves core layer when nothing else exists", () => {
25
+ resolver.load();
26
+ const result = resolver.resolve("skills/gxpm/SKILL.md", "core-content");
27
+ expect(result).not.toBeNull();
28
+ expect(result!.content).toBe("core-content");
29
+ expect(result!.source).toBe("core");
30
+ });
31
+
32
+ it("override layer takes highest priority", () => {
33
+ const overrideFile = join(root, ".gxpm", "overrides", "skills", "gxpm", "SKILL.md");
34
+ mkdirSync(join(root, ".gxpm", "overrides", "skills", "gxpm"), { recursive: true });
35
+ writeFileSync(overrideFile, "override-content");
36
+ resolver.load();
37
+ const result = resolver.resolve("skills/gxpm/SKILL.md", "core-content");
38
+ expect(result).not.toBeNull();
39
+ expect(result!.content).toBe("override-content");
40
+ expect(result!.source).toBe("override");
41
+ });
42
+
43
+ it("extension layer is applied above core", () => {
44
+ const extDir = join(root, ".gxpm", "extensions", "my-ext");
45
+ mkdirSync(extDir, { recursive: true });
46
+ writeFileSync(join(extDir, "manifest.json"), JSON.stringify({ id: "my-ext", name: "My Ext", version: "1.0.0" }));
47
+ mkdirSync(join(extDir, "skills", "gxpm"), { recursive: true });
48
+ writeFileSync(join(extDir, "skills", "gxpm", "SKILL.md"), "ext-content");
49
+ resolver.load();
50
+ const result = resolver.resolve("skills/gxpm/SKILL.md", "core-content");
51
+ expect(result).not.toBeNull();
52
+ expect(result!.content).toBe("ext-content");
53
+ expect(result!.source).toBe("extension");
54
+ expect(result!.appliedExtensions).toContain("my-ext");
55
+ });
56
+
57
+ it("returns null when no content available", () => {
58
+ resolver.load();
59
+ const result = resolver.resolve("nonexistent/file.txt");
60
+ expect(result).toBeNull();
61
+ });
62
+
63
+ it("lists loaded extensions", () => {
64
+ const extDir = join(root, ".gxpm", "extensions", "ext-a");
65
+ mkdirSync(extDir, { recursive: true });
66
+ writeFileSync(join(extDir, "manifest.json"), JSON.stringify({ id: "ext-a", name: "Ext A", version: "1.0.0" }));
67
+ resolver.load();
68
+ expect(resolver.listExtensions()).toContain("ext-a");
69
+ });
70
+ });
@@ -0,0 +1,156 @@
1
+ /**
2
+ * Template Resolver — 4-layer priority stack.
3
+ *
4
+ * Priority: Override > Preset > Extension > Core
5
+ *
6
+ * Built on top of PresetResolver (which already handles Override > Preset > Core).
7
+ * This module adds the Extension layer and provides a unified resolve() API.
8
+ */
9
+
10
+ import { existsSync, readFileSync, readdirSync } from "node:fs";
11
+ import { join } from "node:path";
12
+ import { PresetResolver, type ResolveResult as PresetResolveResult } from "./preset-system/preset-resolver";
13
+
14
+ export type ResolutionLayer = "override" | "preset" | "extension" | "core";
15
+
16
+ export interface TemplateResolveResult {
17
+ content: string;
18
+ /** Which layer provided the final content. */
19
+ source: ResolutionLayer;
20
+ /** Preset IDs that contributed (empty for override/extension/core). */
21
+ appliedPresets: string[];
22
+ /** Extension IDs that contributed (empty for override/preset/core). */
23
+ appliedExtensions: string[];
24
+ }
25
+
26
+ export interface ExtensionManifest {
27
+ id: string;
28
+ name: string;
29
+ version: string;
30
+ description?: string;
31
+ }
32
+
33
+ const EXTENSION_MANIFEST_FILE = "manifest.json";
34
+
35
+ export class TemplateResolver {
36
+ private presetResolver: PresetResolver;
37
+ private extensions = new Map<string, ExtensionManifest>();
38
+ private extensionDir: string;
39
+
40
+ constructor(private projectRoot: string) {
41
+ this.presetResolver = new PresetResolver(projectRoot);
42
+ this.extensionDir = join(projectRoot, ".gxpm", "extensions");
43
+ }
44
+
45
+ /** Load presets and extensions. */
46
+ load(): void {
47
+ this.presetResolver.load();
48
+ this.loadExtensions();
49
+ }
50
+
51
+ /**
52
+ * Resolve template content for a target path.
53
+ *
54
+ * Priority stack:
55
+ * 1. Override (`.gxpm/overrides/`) — highest
56
+ * 2. Preset (`.gxpm/presets/<id>/`)
57
+ * 3. Extension (`.gxpm/extensions/<id>/`)
58
+ * 4. Core (`skills/`, `templates/`) — lowest
59
+ */
60
+ resolve(targetPath: string, baseContent?: string): TemplateResolveResult | null {
61
+ const normalized = targetPath.replace(/^\//, "");
62
+
63
+ // 1. Override layer
64
+ const overridePath = join(this.projectRoot, ".gxpm", "overrides", normalized);
65
+ if (existsSync(overridePath)) {
66
+ return {
67
+ content: readFileSync(overridePath, "utf8"),
68
+ source: "override",
69
+ appliedPresets: [],
70
+ appliedExtensions: [],
71
+ };
72
+ }
73
+
74
+ // 2. Preset layer (reuse PresetResolver for Override > Preset > Core logic,
75
+ // but we only want Preset + Core here; we handle Override above and
76
+ // Extension below manually.)
77
+ const presetResult = this.presetResolver.resolve(normalized, baseContent);
78
+
79
+ // 3. Extension layer — applied AFTER preset but BEFORE core.
80
+ // If preset already resolved, extension can further compose on top.
81
+ let content = presetResult?.content ?? baseContent ?? "";
82
+ let source: ResolutionLayer = presetResult?.source ?? "core";
83
+ const appliedExtensions: string[] = [];
84
+
85
+ for (const [extId, manifest] of this.extensions) {
86
+ const extFilePath = join(this.extensionDir, extId, normalized);
87
+ if (!existsSync(extFilePath)) continue;
88
+ const extContent = readFileSync(extFilePath, "utf8");
89
+ // Extension defaults to "replace" behavior for MVP.
90
+ // Future: support composition strategies via manifest.
91
+ content = extContent;
92
+ source = "extension";
93
+ appliedExtensions.push(manifest.id);
94
+ }
95
+
96
+ if (appliedExtensions.length > 0) {
97
+ return {
98
+ content,
99
+ source,
100
+ appliedPresets: presetResult?.appliedPresets ?? [],
101
+ appliedExtensions,
102
+ };
103
+ }
104
+
105
+ // If no extension applied, return whatever preset/core gave us.
106
+ if (presetResult) {
107
+ return {
108
+ content: presetResult.content,
109
+ source: presetResult.source as ResolutionLayer,
110
+ appliedPresets: presetResult.appliedPresets,
111
+ appliedExtensions: [],
112
+ };
113
+ }
114
+
115
+ // 4. Core layer — if baseContent was provided but no higher layer matched
116
+ if (baseContent !== undefined) {
117
+ return {
118
+ content: baseContent,
119
+ source: "core",
120
+ appliedPresets: [],
121
+ appliedExtensions: [],
122
+ };
123
+ }
124
+
125
+ return null;
126
+ }
127
+
128
+ /** List loaded extension IDs. */
129
+ listExtensions(): string[] {
130
+ return Array.from(this.extensions.keys());
131
+ }
132
+
133
+ /** Get a loaded extension manifest by ID. */
134
+ getExtension(id: string): ExtensionManifest | undefined {
135
+ return this.extensions.get(id);
136
+ }
137
+
138
+ private loadExtensions(): void {
139
+ this.extensions.clear();
140
+ if (!existsSync(this.extensionDir)) return;
141
+
142
+ const entries = readdirSync(this.extensionDir, { withFileTypes: true });
143
+ for (const entry of entries) {
144
+ if (!entry.isDirectory()) continue;
145
+ const manifestPath = join(this.extensionDir, entry.name, EXTENSION_MANIFEST_FILE);
146
+ if (!existsSync(manifestPath)) continue;
147
+ try {
148
+ const raw = readFileSync(manifestPath, "utf8");
149
+ const manifest = JSON.parse(raw) as ExtensionManifest;
150
+ this.extensions.set(manifest.id, manifest);
151
+ } catch {
152
+ // Silently skip malformed extensions
153
+ }
154
+ }
155
+ }
156
+ }
package/core/triage.ts ADDED
@@ -0,0 +1,26 @@
1
+ import { writeArtifact } from "./artifacts";
2
+
3
+ interface TriageInput {
4
+ root?: string;
5
+ issueId: string;
6
+ }
7
+
8
+ export function initializeTriage(input: TriageInput) {
9
+ return writeArtifact({
10
+ root: input.root,
11
+ issueId: input.issueId,
12
+ type: "acceptance-contract",
13
+ payload: {
14
+ criteria: [],
15
+ notes: "Fill criteria during triage before planning work.",
16
+ status: "draft",
17
+ capabilities: {
18
+ new: [],
19
+ modified: [],
20
+ removed: [],
21
+ },
22
+ affectedCapabilities: [],
23
+ breaking: false,
24
+ },
25
+ });
26
+ }
package/core/verify.ts ADDED
@@ -0,0 +1,15 @@
1
+ import { createPhaseArtifactInitializer } from "./phase-artifact";
2
+
3
+ export const initializeVerifyFindings = createPhaseArtifactInitializer({
4
+ artifactType: "verify-findings",
5
+ label: "Verify findings",
6
+ payload: {
7
+ acceptanceContractArtifact: "acceptance-contract",
8
+ findings: [],
9
+ prCheckArtifact: "pr-check",
10
+ risks: [],
11
+ status: "draft",
12
+ summary: "",
13
+ },
14
+ requiredPhase: "pr-check",
15
+ });