javi-forge 1.6.0 → 1.7.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 (322) hide show
  1. package/README.md +45 -1
  2. package/ci-local/README.md +56 -11
  3. package/ci-local/ci-local.ps1 +445 -0
  4. package/ci-local/ci-local.sh +52 -9
  5. package/ci-local/docker/python.Dockerfile +7 -0
  6. package/ci-local/hooks/commit-msg +120 -44
  7. package/ci-local/hooks/commit-msg.test.sh +286 -0
  8. package/ci-local/hooks/pre-commit +9 -1
  9. package/ci-local/hooks/pre-push +13 -2
  10. package/ci-local/install.ps1 +367 -0
  11. package/ci-local/install.sh +151 -5
  12. package/ci-local/uninstall.ps1 +85 -0
  13. package/ci-local/uninstall.sh +95 -0
  14. package/dist/cli/dispatch/ci.d.ts +12 -0
  15. package/dist/cli/dispatch/ci.js +38 -0
  16. package/dist/cli/dispatch/security.d.ts +11 -0
  17. package/dist/cli/dispatch/security.js +68 -0
  18. package/dist/cli/dispatch/simple-renderers.d.ts +16 -0
  19. package/dist/cli/dispatch/simple-renderers.js +65 -0
  20. package/dist/cli/dispatch/skill-publish.d.ts +15 -0
  21. package/dist/cli/dispatch/skill-publish.js +44 -0
  22. package/dist/cli/dispatch/skills-cmd.d.ts +13 -0
  23. package/dist/cli/dispatch/skills-cmd.js +93 -0
  24. package/dist/cli/dispatch/tdd.d.ts +11 -0
  25. package/dist/cli/dispatch/tdd.js +45 -0
  26. package/dist/cli/dispatch/types.d.ts +29 -0
  27. package/dist/cli/dispatch/types.js +2 -0
  28. package/dist/cli/dispatch/workflow.d.ts +11 -0
  29. package/dist/cli/dispatch/workflow.js +67 -0
  30. package/dist/cli/help.d.ts +127 -0
  31. package/dist/cli/help.js +138 -0
  32. package/dist/cli/runtime.d.ts +16 -0
  33. package/dist/cli/runtime.js +38 -0
  34. package/dist/cli/validators.d.ts +17 -0
  35. package/dist/cli/validators.js +35 -0
  36. package/dist/commands/analyze.d.ts +1 -1
  37. package/dist/commands/analyze.js +14 -16
  38. package/dist/commands/ci.d.ts +50 -3
  39. package/dist/commands/ci.js +537 -182
  40. package/dist/commands/doctor.d.ts +2 -2
  41. package/dist/commands/doctor.js +112 -62
  42. package/dist/commands/init/report.d.ts +13 -0
  43. package/dist/commands/init/report.js +13 -0
  44. package/dist/commands/init/steps/agent-skills.d.ts +13 -0
  45. package/dist/commands/init/steps/agent-skills.js +45 -0
  46. package/dist/commands/init/steps/ai-sync.d.ts +15 -0
  47. package/dist/commands/init/steps/ai-sync.js +62 -0
  48. package/dist/commands/init/steps/ci.d.ts +24 -0
  49. package/dist/commands/init/steps/ci.js +74 -0
  50. package/dist/commands/init/steps/claude-md.d.ts +14 -0
  51. package/dist/commands/init/steps/claude-md.js +54 -0
  52. package/dist/commands/init/steps/code-graph.d.ts +16 -0
  53. package/dist/commands/init/steps/code-graph.js +68 -0
  54. package/dist/commands/init/steps/context-dir.d.ts +13 -0
  55. package/dist/commands/init/steps/context-dir.js +47 -0
  56. package/dist/commands/init/steps/docker-deploy.d.ts +16 -0
  57. package/dist/commands/init/steps/docker-deploy.js +60 -0
  58. package/dist/commands/init/steps/ghagga.d.ts +15 -0
  59. package/dist/commands/init/steps/ghagga.js +58 -0
  60. package/dist/commands/init/steps/git.d.ts +25 -0
  61. package/dist/commands/init/steps/git.js +81 -0
  62. package/dist/commands/init/steps/gitignore.d.ts +13 -0
  63. package/dist/commands/init/steps/gitignore.js +39 -0
  64. package/dist/commands/init/steps/local-ai.d.ts +14 -0
  65. package/dist/commands/init/steps/local-ai.js +53 -0
  66. package/dist/commands/init/steps/manifest.d.ts +13 -0
  67. package/dist/commands/init/steps/manifest.js +58 -0
  68. package/dist/commands/init/steps/memory.d.ts +15 -0
  69. package/dist/commands/init/steps/memory.js +60 -0
  70. package/dist/commands/init/steps/mock.d.ts +13 -0
  71. package/dist/commands/init/steps/mock.js +64 -0
  72. package/dist/commands/init/steps/sdd.d.ts +12 -0
  73. package/dist/commands/init/steps/sdd.js +40 -0
  74. package/dist/commands/init/steps/security.d.ts +28 -0
  75. package/dist/commands/init/steps/security.js +103 -0
  76. package/dist/commands/init/types.d.ts +30 -0
  77. package/dist/commands/init/types.js +2 -0
  78. package/dist/commands/init.d.ts +5 -4
  79. package/dist/commands/init.js +42 -387
  80. package/dist/commands/llmstxt.d.ts +1 -1
  81. package/dist/commands/llmstxt.js +36 -34
  82. package/dist/commands/plugin.d.ts +10 -1
  83. package/dist/commands/plugin.js +92 -47
  84. package/dist/commands/security.d.ts +11 -5
  85. package/dist/commands/security.js +236 -77
  86. package/dist/commands/skills/analysis.d.ts +19 -0
  87. package/dist/commands/skills/analysis.js +211 -0
  88. package/dist/commands/skills/benchmark.d.ts +6 -0
  89. package/dist/commands/skills/benchmark.js +82 -0
  90. package/dist/commands/skills/constants.d.ts +10 -0
  91. package/dist/commands/skills/constants.js +27 -0
  92. package/dist/commands/skills/directives.d.ts +21 -0
  93. package/dist/commands/skills/directives.js +101 -0
  94. package/dist/commands/skills/parsing.d.ts +16 -0
  95. package/dist/commands/skills/parsing.js +108 -0
  96. package/dist/commands/skills/rules.d.ts +5 -0
  97. package/dist/commands/skills/rules.js +32 -0
  98. package/dist/commands/skills/scoring.d.ts +47 -0
  99. package/dist/commands/skills/scoring.js +306 -0
  100. package/dist/commands/skills.d.ts +8 -69
  101. package/dist/commands/skills.js +9 -490
  102. package/dist/commands/tdd-pipeline.d.ts +17 -0
  103. package/dist/commands/tdd-pipeline.js +144 -0
  104. package/dist/commands/tdd.d.ts +1 -1
  105. package/dist/commands/tdd.js +21 -18
  106. package/dist/commands/workflow.d.ts +23 -0
  107. package/dist/commands/workflow.js +116 -0
  108. package/dist/constants.d.ts +15 -1
  109. package/dist/constants.js +161 -122
  110. package/dist/index.js +43 -280
  111. package/dist/lib/agent-skills.d.ts +36 -1
  112. package/dist/lib/agent-skills.js +168 -19
  113. package/dist/lib/atlassian-mcp.d.ts +42 -0
  114. package/dist/lib/atlassian-mcp.js +98 -0
  115. package/dist/lib/auto-skill-install.d.ts +37 -0
  116. package/dist/lib/auto-skill-install.js +91 -0
  117. package/dist/lib/auto-wire.d.ts +20 -0
  118. package/dist/lib/auto-wire.js +240 -0
  119. package/dist/lib/ci-config.d.ts +65 -0
  120. package/dist/lib/ci-config.js +297 -0
  121. package/dist/lib/claudemd.d.ts +13 -1
  122. package/dist/lib/claudemd.js +174 -24
  123. package/dist/lib/codex-export.d.ts +1 -1
  124. package/dist/lib/codex-export.js +26 -32
  125. package/dist/lib/common.d.ts +1 -1
  126. package/dist/lib/common.js +52 -44
  127. package/dist/lib/context.d.ts +17 -2
  128. package/dist/lib/context.js +142 -13
  129. package/dist/lib/crash-recovery.d.ts +34 -0
  130. package/dist/lib/crash-recovery.js +123 -0
  131. package/dist/lib/docker.d.ts +23 -2
  132. package/dist/lib/docker.js +168 -131
  133. package/dist/lib/exec.d.ts +10 -0
  134. package/dist/lib/exec.js +11 -0
  135. package/dist/lib/frontmatter.d.ts +1 -1
  136. package/dist/lib/frontmatter.js +29 -15
  137. package/dist/lib/harness-audit.d.ts +35 -0
  138. package/dist/lib/harness-audit.js +277 -0
  139. package/dist/lib/parallel-batch.d.ts +42 -0
  140. package/dist/lib/parallel-batch.js +88 -0
  141. package/dist/lib/plugin.d.ts +9 -3
  142. package/dist/lib/plugin.js +127 -70
  143. package/dist/lib/secret-scanner.d.ts +30 -0
  144. package/dist/lib/secret-scanner.js +274 -0
  145. package/dist/lib/security-analysis.d.ts +74 -0
  146. package/dist/lib/security-analysis.js +489 -0
  147. package/dist/lib/skill-publish.d.ts +40 -0
  148. package/dist/lib/skill-publish.js +164 -0
  149. package/dist/lib/skill-scanner.d.ts +63 -0
  150. package/dist/lib/skill-scanner.js +383 -0
  151. package/dist/lib/stack-detector.d.ts +38 -0
  152. package/dist/lib/stack-detector.js +207 -0
  153. package/dist/lib/supply-chain.d.ts +23 -0
  154. package/dist/lib/supply-chain.js +126 -0
  155. package/dist/lib/team-presets.d.ts +53 -0
  156. package/dist/lib/team-presets.js +201 -0
  157. package/dist/lib/template.d.ts +16 -1
  158. package/dist/lib/template.js +46 -17
  159. package/dist/lib/workflow/discovery.d.ts +27 -0
  160. package/dist/lib/workflow/discovery.js +85 -0
  161. package/dist/lib/workflow/index.d.ts +5 -0
  162. package/dist/lib/workflow/index.js +5 -0
  163. package/dist/lib/workflow/parser.d.ts +16 -0
  164. package/dist/lib/workflow/parser.js +198 -0
  165. package/dist/lib/workflow/renderer.d.ts +9 -0
  166. package/dist/lib/workflow/renderer.js +152 -0
  167. package/dist/lib/workflow/validator.d.ts +10 -0
  168. package/dist/lib/workflow/validator.js +189 -0
  169. package/dist/tasks/index.d.ts +4 -0
  170. package/dist/tasks/index.js +4 -0
  171. package/dist/tasks/scaffold-tasks.d.ts +3 -0
  172. package/dist/tasks/scaffold-tasks.js +14 -0
  173. package/dist/tasks/task-id.d.ts +30 -0
  174. package/dist/tasks/task-id.js +59 -0
  175. package/dist/tasks/task-tracker.d.ts +15 -0
  176. package/dist/tasks/task-tracker.js +81 -0
  177. package/dist/types/index.d.ts +134 -6
  178. package/dist/types/index.js +11 -1
  179. package/dist/ui/AnalyzeUI.d.ts +1 -1
  180. package/dist/ui/AnalyzeUI.js +38 -39
  181. package/dist/ui/App.d.ts +5 -3
  182. package/dist/ui/App.js +86 -46
  183. package/dist/ui/AutoSkills.d.ts +9 -0
  184. package/dist/ui/AutoSkills.js +124 -0
  185. package/dist/ui/CI.d.ts +2 -2
  186. package/dist/ui/CI.js +24 -26
  187. package/dist/ui/CIContext.d.ts +1 -1
  188. package/dist/ui/CIContext.js +2 -2
  189. package/dist/ui/CISelector.d.ts +2 -2
  190. package/dist/ui/CISelector.js +23 -15
  191. package/dist/ui/Doctor.d.ts +1 -1
  192. package/dist/ui/Doctor.js +35 -29
  193. package/dist/ui/Header.d.ts +1 -1
  194. package/dist/ui/Header.js +14 -14
  195. package/dist/ui/HookProfileSelector.d.ts +9 -0
  196. package/dist/ui/HookProfileSelector.js +54 -0
  197. package/dist/ui/LlmsTxt.d.ts +1 -1
  198. package/dist/ui/LlmsTxt.js +31 -22
  199. package/dist/ui/MemorySelector.d.ts +2 -2
  200. package/dist/ui/MemorySelector.js +28 -16
  201. package/dist/ui/NameInput.d.ts +1 -1
  202. package/dist/ui/NameInput.js +21 -21
  203. package/dist/ui/OptionSelector.d.ts +6 -2
  204. package/dist/ui/OptionSelector.js +83 -32
  205. package/dist/ui/Plugin.d.ts +4 -3
  206. package/dist/ui/Plugin.js +78 -35
  207. package/dist/ui/Progress.d.ts +3 -3
  208. package/dist/ui/Progress.js +23 -22
  209. package/dist/ui/Skills.d.ts +2 -2
  210. package/dist/ui/Skills.js +61 -32
  211. package/dist/ui/StackSelector.d.ts +2 -2
  212. package/dist/ui/StackSelector.js +27 -17
  213. package/dist/ui/Summary.d.ts +3 -3
  214. package/dist/ui/Summary.js +60 -50
  215. package/dist/ui/Welcome.d.ts +1 -1
  216. package/dist/ui/Welcome.js +15 -16
  217. package/dist/ui/theme.d.ts +1 -1
  218. package/dist/ui/theme.js +6 -6
  219. package/lib/common.psm1 +167 -0
  220. package/lib/common.sh +30 -0
  221. package/package.json +46 -24
  222. package/templates/common/atlassian/mcp-atlassian-snippet.json +16 -0
  223. package/templates/common/repoforge/mcp-repoforge-snippet.json +11 -0
  224. package/templates/common/repoforge/repoforge.yaml +34 -0
  225. package/templates/github/deploy-docker-zero-downtime.yml +140 -0
  226. package/templates/github/repoforge-graph.yml +45 -0
  227. package/templates/gitlab/deploy-docker-zero-downtime.yml +57 -0
  228. package/templates/local-ai/.env.example +17 -0
  229. package/templates/local-ai/docker-compose.yml +95 -0
  230. package/templates/security-hooks/claude-settings-security.json +30 -0
  231. package/templates/security-hooks/commit-msg-signing +29 -0
  232. package/templates/security-hooks/pre-commit-permissions +74 -0
  233. package/templates/security-hooks/pre-commit-secrets +74 -0
  234. package/templates/security-hooks/pre-push-branch-protection +62 -0
  235. package/templates/security-hooks/pre-push-deps +83 -0
  236. package/templates/security-hooks/pre-push-signing +67 -0
  237. package/templates/woodpecker/deploy-docker-zero-downtime.yml +50 -0
  238. package/templates/workflows/ci-pipeline.dot +15 -0
  239. package/templates/workflows/feature-flow.dot +21 -0
  240. package/templates/workflows/release.dot +16 -0
  241. package/dist/__integration__/helpers.d.ts +0 -20
  242. package/dist/__integration__/helpers.d.ts.map +0 -1
  243. package/dist/__integration__/helpers.js +0 -31
  244. package/dist/__integration__/helpers.js.map +0 -1
  245. package/dist/commands/analyze.d.ts.map +0 -1
  246. package/dist/commands/analyze.js.map +0 -1
  247. package/dist/commands/ci.d.ts.map +0 -1
  248. package/dist/commands/ci.js.map +0 -1
  249. package/dist/commands/doctor.d.ts.map +0 -1
  250. package/dist/commands/doctor.js.map +0 -1
  251. package/dist/commands/init.d.ts.map +0 -1
  252. package/dist/commands/init.js.map +0 -1
  253. package/dist/commands/llmstxt.d.ts.map +0 -1
  254. package/dist/commands/llmstxt.js.map +0 -1
  255. package/dist/commands/plugin.d.ts.map +0 -1
  256. package/dist/commands/plugin.js.map +0 -1
  257. package/dist/commands/security.d.ts.map +0 -1
  258. package/dist/commands/security.js.map +0 -1
  259. package/dist/commands/skills.d.ts.map +0 -1
  260. package/dist/commands/skills.js.map +0 -1
  261. package/dist/commands/tdd.d.ts.map +0 -1
  262. package/dist/commands/tdd.js.map +0 -1
  263. package/dist/constants.d.ts.map +0 -1
  264. package/dist/constants.js.map +0 -1
  265. package/dist/index.d.ts.map +0 -1
  266. package/dist/index.js.map +0 -1
  267. package/dist/lib/agent-skills.d.ts.map +0 -1
  268. package/dist/lib/agent-skills.js.map +0 -1
  269. package/dist/lib/claudemd.d.ts.map +0 -1
  270. package/dist/lib/claudemd.js.map +0 -1
  271. package/dist/lib/codex-export.d.ts.map +0 -1
  272. package/dist/lib/codex-export.js.map +0 -1
  273. package/dist/lib/common.d.ts.map +0 -1
  274. package/dist/lib/common.js.map +0 -1
  275. package/dist/lib/context.d.ts.map +0 -1
  276. package/dist/lib/context.js.map +0 -1
  277. package/dist/lib/docker.d.ts.map +0 -1
  278. package/dist/lib/docker.js.map +0 -1
  279. package/dist/lib/frontmatter.d.ts.map +0 -1
  280. package/dist/lib/frontmatter.js.map +0 -1
  281. package/dist/lib/plugin.d.ts.map +0 -1
  282. package/dist/lib/plugin.js.map +0 -1
  283. package/dist/lib/template.d.ts.map +0 -1
  284. package/dist/lib/template.js.map +0 -1
  285. package/dist/types/index.d.ts.map +0 -1
  286. package/dist/types/index.js.map +0 -1
  287. package/dist/ui/AnalyzeUI.d.ts.map +0 -1
  288. package/dist/ui/AnalyzeUI.js.map +0 -1
  289. package/dist/ui/App.d.ts.map +0 -1
  290. package/dist/ui/App.js.map +0 -1
  291. package/dist/ui/CI.d.ts.map +0 -1
  292. package/dist/ui/CI.js.map +0 -1
  293. package/dist/ui/CIContext.d.ts.map +0 -1
  294. package/dist/ui/CIContext.js.map +0 -1
  295. package/dist/ui/CISelector.d.ts.map +0 -1
  296. package/dist/ui/CISelector.js.map +0 -1
  297. package/dist/ui/Doctor.d.ts.map +0 -1
  298. package/dist/ui/Doctor.js.map +0 -1
  299. package/dist/ui/Header.d.ts.map +0 -1
  300. package/dist/ui/Header.js.map +0 -1
  301. package/dist/ui/LlmsTxt.d.ts.map +0 -1
  302. package/dist/ui/LlmsTxt.js.map +0 -1
  303. package/dist/ui/MemorySelector.d.ts.map +0 -1
  304. package/dist/ui/MemorySelector.js.map +0 -1
  305. package/dist/ui/NameInput.d.ts.map +0 -1
  306. package/dist/ui/NameInput.js.map +0 -1
  307. package/dist/ui/OptionSelector.d.ts.map +0 -1
  308. package/dist/ui/OptionSelector.js.map +0 -1
  309. package/dist/ui/Plugin.d.ts.map +0 -1
  310. package/dist/ui/Plugin.js.map +0 -1
  311. package/dist/ui/Progress.d.ts.map +0 -1
  312. package/dist/ui/Progress.js.map +0 -1
  313. package/dist/ui/Skills.d.ts.map +0 -1
  314. package/dist/ui/Skills.js.map +0 -1
  315. package/dist/ui/StackSelector.d.ts.map +0 -1
  316. package/dist/ui/StackSelector.js.map +0 -1
  317. package/dist/ui/Summary.d.ts.map +0 -1
  318. package/dist/ui/Summary.js.map +0 -1
  319. package/dist/ui/Welcome.d.ts.map +0 -1
  320. package/dist/ui/Welcome.js.map +0 -1
  321. package/dist/ui/theme.d.ts.map +0 -1
  322. package/dist/ui/theme.js.map +0 -1
@@ -0,0 +1,123 @@
1
+ /**
2
+ * Crash recovery — reconstruct pipeline state from git commit history
3
+ * when checkpoint files are corrupted or lost. Git is the single
4
+ * source of truth for what was actually done.
5
+ */
6
+ import { execFileAsync } from "./exec.js";
7
+ // ── Git operations ──
8
+ export async function getGitLog(projectDir, options = {}) {
9
+ const args = [
10
+ "log",
11
+ `--max-count=${options.maxCommits ?? 50}`,
12
+ "--pretty=format:%H|%s|%aI",
13
+ "--name-only",
14
+ ];
15
+ if (options.since)
16
+ args.push(`--since=${options.since}`);
17
+ try {
18
+ const { stdout } = await execFileAsync("git", args, {
19
+ cwd: projectDir,
20
+ timeout: 10_000,
21
+ });
22
+ return stdout;
23
+ }
24
+ catch {
25
+ return "";
26
+ }
27
+ }
28
+ export async function getCurrentBranch(projectDir) {
29
+ try {
30
+ const { stdout } = await execFileAsync("git", ["branch", "--show-current"], { cwd: projectDir, timeout: 5_000 });
31
+ return stdout.trim();
32
+ }
33
+ catch {
34
+ return "unknown";
35
+ }
36
+ }
37
+ // ── Parsing ──
38
+ const CONVENTIONAL_RE = /^(feat|fix|test|refactor|docs|chore|ci|perf|style|build)(?:\(([^)]*)\))?:\s*(.+)/;
39
+ const TASK_ID_RE = /#(\d+)|task[- ](\d+[.\d]*)/i;
40
+ export function parseCommitPhase(message) {
41
+ const match = CONVENTIONAL_RE.exec(message);
42
+ return match ? (match[1] ?? null) : null;
43
+ }
44
+ export function extractTaskId(message) {
45
+ const match = TASK_ID_RE.exec(message);
46
+ if (match)
47
+ return match[1] ?? match[2] ?? null;
48
+ return null;
49
+ }
50
+ export function parseGitLog(raw) {
51
+ if (!raw.trim())
52
+ return [];
53
+ const tasks = [];
54
+ const blocks = raw.split("\n\n").filter((b) => b.trim());
55
+ for (const block of blocks) {
56
+ const lines = block.split("\n").filter((l) => l.trim());
57
+ if (lines.length === 0)
58
+ continue;
59
+ const headerLine = lines[0];
60
+ if (!headerLine)
61
+ continue;
62
+ const parts = headerLine.split("|");
63
+ if (parts.length < 3)
64
+ continue;
65
+ const [hash, message, timestamp] = parts;
66
+ const filesChanged = lines.slice(1).filter((l) => !l.includes("|"));
67
+ tasks.push({
68
+ commitHash: hash.slice(0, 7),
69
+ message,
70
+ timestamp,
71
+ filesChanged,
72
+ taskId: extractTaskId(message),
73
+ phase: parseCommitPhase(message),
74
+ });
75
+ }
76
+ return tasks;
77
+ }
78
+ // ── Recovery ──
79
+ export async function recoverFromGit(projectDir, options = {}) {
80
+ const branch = await getCurrentBranch(projectDir);
81
+ const raw = await getGitLog(projectDir, options);
82
+ const tasks = parseGitLog(raw);
83
+ const phases = {};
84
+ for (const task of tasks) {
85
+ if (task.phase) {
86
+ phases[task.phase] = (phases[task.phase] ?? 0) + 1;
87
+ }
88
+ }
89
+ return {
90
+ branch,
91
+ totalCommits: tasks.length,
92
+ tasks,
93
+ phases,
94
+ lastActivity: tasks[0]?.timestamp ?? null,
95
+ };
96
+ }
97
+ // ── Formatting ──
98
+ export function formatRecovery(report) {
99
+ const lines = [];
100
+ lines.push(`## Crash Recovery Report`);
101
+ lines.push(`**Branch**: ${report.branch} | **Commits**: ${report.totalCommits}`);
102
+ if (report.lastActivity) {
103
+ lines.push(`**Last activity**: ${report.lastActivity}`);
104
+ }
105
+ lines.push("");
106
+ if (Object.keys(report.phases).length > 0) {
107
+ const phaseStr = Object.entries(report.phases)
108
+ .map(([p, c]) => `${p}: ${c}`)
109
+ .join(", ");
110
+ lines.push(`**Phases**: ${phaseStr}`);
111
+ lines.push("");
112
+ }
113
+ for (const task of report.tasks.slice(0, 10)) {
114
+ const tag = task.phase ? `[${task.phase}]` : "";
115
+ const id = task.taskId ? ` #${task.taskId}` : "";
116
+ lines.push(`- \`${task.commitHash}\` ${tag}${id} ${task.message} (${task.filesChanged.length} files)`);
117
+ }
118
+ if (report.totalCommits > 10) {
119
+ lines.push(`\n... and ${report.totalCommits - 10} more commits`);
120
+ }
121
+ return lines.join("\n");
122
+ }
123
+ //# sourceMappingURL=crash-recovery.js.map
@@ -1,7 +1,13 @@
1
- import type { Stack } from '../types/index.js';
1
+ import type { Stack } from "../types/index.js";
2
2
  export interface DockerRunOptions {
3
3
  /** Absolute path to mount as /home/runner/work */
4
4
  projectDir: string;
5
+ /**
6
+ * Pre-resolved image from the CI runner (required). Passed through
7
+ * verbatim, including digest pins (`name@sha256:...`). runInContainer
8
+ * never performs marker detection — resolution happens once, upstream.
9
+ */
10
+ image: string;
5
11
  /** Command to run inside the container */
6
12
  command: string;
7
13
  /** Timeout in seconds (default: 600) */
@@ -22,6 +28,14 @@ export interface DockerImageOptions {
22
28
  javaVersion?: string;
23
29
  /** Directory where Dockerfiles are stored (defaults to package-bundled dir) */
24
30
  dockerfilesDir?: string;
31
+ /**
32
+ * Custom build context directory (must contain a Dockerfile, which is
33
+ * the source of truth and is never overwritten). Mutually exclusive
34
+ * with dockerfilesDir.
35
+ */
36
+ buildContext?: string;
37
+ /** Image tag for build-context builds (default: derived from the stack) */
38
+ imageTag?: string;
25
39
  }
26
40
  export declare function getImageName(stack: Stack): string;
27
41
  export declare function getDockerfileContent(stack: Stack): string;
@@ -30,6 +44,12 @@ export declare function isDockerAvailable(): Promise<boolean>;
30
44
  * Ensure a CI Docker image exists and is up-to-date.
31
45
  * Rebuilds only if the Dockerfile content has changed (hash-based staleness check).
32
46
  * Returns the image name.
47
+ *
48
+ * Two modes:
49
+ * - per-stack (default): the Dockerfile is generated/managed by javi-forge
50
+ * - build-context: the configured context directory provides its own
51
+ * Dockerfile (source of truth, never overwritten); fails closed if
52
+ * the Dockerfile is missing.
33
53
  */
34
54
  export declare function ensureImage(options: DockerImageOptions): Promise<string>;
35
55
  /**
@@ -40,6 +60,7 @@ export declare function ensureImage(options: DockerImageOptions): Promise<string
40
60
  export declare function runInContainer(options: DockerRunOptions): Promise<DockerRunResult>;
41
61
  /**
42
62
  * Open an interactive shell inside the CI container.
63
+ * The image must be pre-resolved by the caller (no marker detection here).
43
64
  */
44
- export declare function openShell(projectDir: string): Promise<void>;
65
+ export declare function openShell(projectDir: string, image: string): Promise<void>;
45
66
  //# sourceMappingURL=docker.d.ts.map
@@ -1,9 +1,8 @@
1
- import { execFile, spawn } from 'child_process';
2
- import { promisify } from 'util';
3
- import crypto from 'crypto';
4
- import fs from 'fs-extra';
5
- import path from 'path';
6
- const execFileAsync = promisify(execFile);
1
+ import { spawn } from "node:child_process";
2
+ import crypto from "node:crypto";
3
+ import path from "node:path";
4
+ import fs from "fs-extra";
5
+ import { execFileAsync } from "./exec.js";
7
6
  // =============================================================================
8
7
  // Image name
9
8
  // =============================================================================
@@ -15,66 +14,66 @@ export function getImageName(stack) {
15
14
  // =============================================================================
16
15
  export function getDockerfileContent(stack) {
17
16
  switch (stack) {
18
- case 'java-gradle':
19
- case 'java-maven':
17
+ case "java-gradle":
18
+ case "java-maven":
20
19
  return [
21
- 'ARG JAVA_VERSION=21',
22
- 'FROM eclipse-temurin:${JAVA_VERSION}-jdk-noble',
23
- 'RUN apt-get update && apt-get install -y git curl unzip && rm -rf /var/lib/apt/lists/*',
24
- 'RUN useradd -m -s /bin/bash runner',
25
- 'USER runner',
26
- 'WORKDIR /home/runner/work',
20
+ "ARG JAVA_VERSION=21",
21
+ "FROM eclipse-temurin:${JAVA_VERSION}-jdk-noble",
22
+ "RUN apt-get update && apt-get install -y git curl unzip && rm -rf /var/lib/apt/lists/*",
23
+ "RUN useradd -m -s /bin/bash runner",
24
+ "USER runner",
25
+ "WORKDIR /home/runner/work",
27
26
  'ENTRYPOINT ["/bin/bash", "-c"]',
28
- ].join('\n');
29
- case 'node':
27
+ ].join("\n");
28
+ case "node":
30
29
  return [
31
- 'FROM node:22-slim',
32
- 'RUN apt-get update && apt-get install -y git && rm -rf /var/lib/apt/lists/*',
33
- 'RUN npm install -g pnpm',
34
- 'RUN useradd -m -s /bin/bash runner',
35
- 'USER runner',
36
- 'WORKDIR /home/runner/work',
30
+ "FROM node:22-slim",
31
+ "RUN apt-get update && apt-get install -y git && rm -rf /var/lib/apt/lists/*",
32
+ "RUN npm install -g pnpm",
33
+ "RUN useradd -m -s /bin/bash runner",
34
+ "USER runner",
35
+ "WORKDIR /home/runner/work",
37
36
  'ENTRYPOINT ["/bin/bash", "-c"]',
38
- ].join('\n');
39
- case 'python':
37
+ ].join("\n");
38
+ case "python":
40
39
  return [
41
- 'FROM python:3.12-slim',
42
- 'RUN apt-get update && apt-get install -y git && rm -rf /var/lib/apt/lists/*',
43
- 'RUN pip install --no-cache-dir pytest ruff pylint poetry',
44
- 'RUN useradd -m -s /bin/bash runner',
45
- 'USER runner',
46
- 'WORKDIR /home/runner/work',
40
+ "FROM python:3.12-slim",
41
+ "RUN apt-get update && apt-get install -y git && rm -rf /var/lib/apt/lists/*",
42
+ "RUN pip install --no-cache-dir pytest ruff pylint poetry",
43
+ "RUN useradd -m -s /bin/bash runner",
44
+ "USER runner",
45
+ "WORKDIR /home/runner/work",
47
46
  'ENTRYPOINT ["/bin/bash", "-c"]',
48
- ].join('\n');
49
- case 'go':
47
+ ].join("\n");
48
+ case "go":
50
49
  return [
51
- 'FROM golang:1.23-bookworm',
52
- 'RUN apt-get update && apt-get install -y git && rm -rf /var/lib/apt/lists/*',
53
- 'RUN go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.62.0 && mv /root/go/bin/golangci-lint /usr/local/bin/',
54
- 'RUN useradd -m -s /bin/bash runner',
55
- 'USER runner',
56
- 'WORKDIR /home/runner/work',
50
+ "FROM golang:1.23-bookworm",
51
+ "RUN apt-get update && apt-get install -y git && rm -rf /var/lib/apt/lists/*",
52
+ "RUN go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.62.0 && mv /root/go/bin/golangci-lint /usr/local/bin/",
53
+ "RUN useradd -m -s /bin/bash runner",
54
+ "USER runner",
55
+ "WORKDIR /home/runner/work",
57
56
  'ENTRYPOINT ["/bin/bash", "-c"]',
58
- ].join('\n');
59
- case 'rust':
57
+ ].join("\n");
58
+ case "rust":
60
59
  return [
61
- 'FROM rust:1.83-slim',
62
- 'RUN apt-get update && apt-get install -y git pkg-config libssl-dev && rm -rf /var/lib/apt/lists/*',
63
- 'RUN rustup component add clippy rustfmt',
64
- 'RUN useradd -m -s /bin/bash runner',
65
- 'USER runner',
66
- 'WORKDIR /home/runner/work',
60
+ "FROM rust:1.83-slim",
61
+ "RUN apt-get update && apt-get install -y git pkg-config libssl-dev && rm -rf /var/lib/apt/lists/*",
62
+ "RUN rustup component add clippy rustfmt",
63
+ "RUN useradd -m -s /bin/bash runner",
64
+ "USER runner",
65
+ "WORKDIR /home/runner/work",
67
66
  'ENTRYPOINT ["/bin/bash", "-c"]',
68
- ].join('\n');
67
+ ].join("\n");
69
68
  default:
70
69
  return [
71
- 'FROM ubuntu:24.04',
72
- 'RUN apt-get update && apt-get install -y git curl && rm -rf /var/lib/apt/lists/*',
73
- 'RUN useradd -m -s /bin/bash runner',
74
- 'USER runner',
75
- 'WORKDIR /home/runner/work',
70
+ "FROM ubuntu:24.04",
71
+ "RUN apt-get update && apt-get install -y git curl && rm -rf /var/lib/apt/lists/*",
72
+ "RUN useradd -m -s /bin/bash runner",
73
+ "USER runner",
74
+ "WORKDIR /home/runner/work",
76
75
  'ENTRYPOINT ["/bin/bash", "-c"]',
77
- ].join('\n');
76
+ ].join("\n");
78
77
  }
79
78
  }
80
79
  // =============================================================================
@@ -82,7 +81,7 @@ export function getDockerfileContent(stack) {
82
81
  // =============================================================================
83
82
  export async function isDockerAvailable() {
84
83
  try {
85
- await execFileAsync('docker', ['info'], { timeout: 5000 });
84
+ await execFileAsync("docker", ["info"], { timeout: 5000 });
86
85
  return true;
87
86
  }
88
87
  catch {
@@ -96,25 +95,51 @@ export async function isDockerAvailable() {
96
95
  * Ensure a CI Docker image exists and is up-to-date.
97
96
  * Rebuilds only if the Dockerfile content has changed (hash-based staleness check).
98
97
  * Returns the image name.
98
+ *
99
+ * Two modes:
100
+ * - per-stack (default): the Dockerfile is generated/managed by javi-forge
101
+ * - build-context: the configured context directory provides its own
102
+ * Dockerfile (source of truth, never overwritten); fails closed if
103
+ * the Dockerfile is missing.
99
104
  */
100
105
  export async function ensureImage(options) {
101
- const { stack, javaVersion, dockerfilesDir } = options;
102
- const imageName = getImageName(stack);
103
- // Resolve Dockerfile path
104
- const dockerDir = dockerfilesDir ?? path.join(path.dirname(new URL(import.meta.url).pathname), '../../ci-local/docker');
105
- const dockerfilePath = path.join(dockerDir, `${stack}.Dockerfile`);
106
- // Write Dockerfile if it doesn't exist yet (first run)
107
- if (!await fs.pathExists(dockerfilePath)) {
108
- await fs.ensureDir(dockerDir);
109
- await fs.writeFile(dockerfilePath, getDockerfileContent(stack), 'utf-8');
106
+ const { stack, javaVersion, dockerfilesDir, buildContext, imageTag } = options;
107
+ let imageName;
108
+ let dockerfilePath;
109
+ let contextDir;
110
+ if (buildContext) {
111
+ imageName = imageTag ?? getImageName(stack);
112
+ dockerfilePath = path.join(buildContext, "Dockerfile");
113
+ contextDir = buildContext;
114
+ // Fail closed: a build context without a Dockerfile is a config error,
115
+ // never something to silently work around.
116
+ if (!(await fs.pathExists(dockerfilePath))) {
117
+ throw new Error(`build-context "${buildContext}" has no Dockerfile — ` +
118
+ "add one or use image/stack defaults instead");
119
+ }
120
+ }
121
+ else {
122
+ imageName = getImageName(stack);
123
+ const dockerDir = dockerfilesDir ??
124
+ path.join(path.dirname(new URL(import.meta.url).pathname), "../../ci-local/docker");
125
+ dockerfilePath = path.join(dockerDir, `${stack}.Dockerfile`);
126
+ contextDir = dockerDir;
127
+ // Write Dockerfile if it doesn't exist yet (first run)
128
+ if (!(await fs.pathExists(dockerfilePath))) {
129
+ await fs.ensureDir(dockerDir);
130
+ await fs.writeFile(dockerfilePath, getDockerfileContent(stack), "utf-8");
131
+ }
110
132
  }
111
133
  // Staleness check: compare Dockerfile hash with the one embedded in the image label
112
- const content = await fs.readFile(dockerfilePath, 'utf-8');
113
- const currentHash = crypto.createHash('sha256').update(content).digest('hex');
114
- let imageHash = '';
134
+ const content = await fs.readFile(dockerfilePath, "utf-8");
135
+ const currentHash = crypto.createHash("sha256").update(content).digest("hex");
136
+ let imageHash = "";
115
137
  try {
116
- const { stdout } = await execFileAsync('docker', [
117
- 'inspect', '--format', '{{index .Config.Labels "dockerfile-hash"}}', imageName,
138
+ const { stdout } = await execFileAsync("docker", [
139
+ "inspect",
140
+ "--format",
141
+ '{{index .Config.Labels "dockerfile-hash"}}',
142
+ imageName,
118
143
  ]);
119
144
  imageHash = stdout.trim();
120
145
  }
@@ -126,19 +151,26 @@ export async function ensureImage(options) {
126
151
  }
127
152
  // Build image
128
153
  const buildArgs = [
129
- 'build',
130
- '--label', `dockerfile-hash=${currentHash}`,
131
- '-f', dockerfilePath,
132
- '-t', imageName,
154
+ "build",
155
+ "--label",
156
+ `dockerfile-hash=${currentHash}`,
157
+ "-f",
158
+ dockerfilePath,
159
+ "-t",
160
+ imageName,
133
161
  ];
134
- if (javaVersion && (stack === 'java-gradle' || stack === 'java-maven')) {
135
- buildArgs.push('--build-arg', `JAVA_VERSION=${javaVersion}`);
162
+ if (!buildContext &&
163
+ javaVersion &&
164
+ (stack === "java-gradle" || stack === "java-maven")) {
165
+ buildArgs.push("--build-arg", `JAVA_VERSION=${javaVersion}`);
136
166
  }
137
- buildArgs.push(dockerDir);
167
+ buildArgs.push(contextDir);
138
168
  await new Promise((resolve, reject) => {
139
- const proc = spawn('docker', buildArgs, { stdio: 'inherit' });
140
- proc.on('close', code => code === 0 ? resolve() : reject(new Error(`docker build exited with code ${code}`)));
141
- proc.on('error', reject);
169
+ const proc = spawn("docker", buildArgs, { stdio: "inherit" });
170
+ proc.on("close", (code) => code === 0
171
+ ? resolve()
172
+ : reject(new Error(`docker build exited with code ${code}`)));
173
+ proc.on("error", reject);
142
174
  });
143
175
  return imageName;
144
176
  }
@@ -151,74 +183,79 @@ export async function ensureImage(options) {
151
183
  * Streams output to process.stdout/stderr by default.
152
184
  */
153
185
  export async function runInContainer(options) {
154
- const { projectDir, command, timeout = 600, stream = true, user } = options;
155
- const stack = await detectStackFromDir(projectDir);
156
- const imageName = getImageName(stack);
186
+ const { projectDir, image, command, timeout = 600, stream = true, user, } = options;
187
+ // The image is always pre-resolved by the caller (resolveCIRunners →
188
+ // ensureImage or an explicit/digest-pinned config image). No marker
189
+ // detection happens on this path — ever.
190
+ const imageName = image;
157
191
  const isInteractive = process.stdin.isTTY && stream;
192
+ // Use --mount instead of -v: the -v form parses the value as a single
193
+ // "src:dst[:opt]" colon-separated string, which breaks (and could be
194
+ // hijacked) when projectDir itself contains a colon. --mount takes
195
+ // comma-separated key=value pairs and is colon-safe.
158
196
  const dockerArgs = [
159
- 'run', '--rm',
160
- ...(isInteractive ? ['-it'] : []),
161
- '--stop-timeout', '30',
162
- '--entrypoint', '',
163
- ...(user ? ['--user', user] : []),
164
- '-v', `${projectDir}:/home/runner/work`,
165
- '-e', 'CI=true',
197
+ "run",
198
+ "--rm",
199
+ ...(isInteractive ? ["-it"] : []),
200
+ "--stop-timeout",
201
+ "30",
202
+ "--entrypoint",
203
+ "",
204
+ ...(user ? ["--user", user] : []),
205
+ "--mount",
206
+ `type=bind,source=${projectDir},target=/home/runner/work`,
207
+ "-e",
208
+ "CI=true",
166
209
  imageName,
167
- 'timeout', String(timeout), 'bash', '-c', command,
210
+ "timeout",
211
+ String(timeout),
212
+ "bash",
213
+ "-c",
214
+ command,
168
215
  ];
169
216
  return new Promise((resolve, reject) => {
170
- const proc = spawn('docker', dockerArgs, {
171
- stdio: stream ? 'inherit' : 'pipe',
217
+ const proc = spawn("docker", dockerArgs, {
218
+ stdio: stream ? "inherit" : "pipe",
172
219
  });
173
- let stdout = '';
174
- let stderr = '';
220
+ let stdout = "";
221
+ let stderr = "";
175
222
  if (!stream) {
176
- proc.stdout?.on('data', (d) => { stdout += d.toString(); });
177
- proc.stderr?.on('data', (d) => { stderr += d.toString(); });
223
+ proc.stdout?.on("data", (d) => {
224
+ stdout += d.toString();
225
+ });
226
+ proc.stderr?.on("data", (d) => {
227
+ stderr += d.toString();
228
+ });
178
229
  }
179
- proc.on('close', code => resolve({ exitCode: code ?? 1, stdout, stderr }));
180
- proc.on('error', reject);
230
+ proc.on("close", (code) => resolve({ exitCode: code ?? 1, stdout, stderr }));
231
+ proc.on("error", reject);
181
232
  });
182
233
  }
183
234
  /**
184
235
  * Open an interactive shell inside the CI container.
236
+ * The image must be pre-resolved by the caller (no marker detection here).
185
237
  */
186
- export async function openShell(projectDir) {
187
- const stack = await detectStackFromDir(projectDir);
188
- const imageName = getImageName(stack);
238
+ export async function openShell(projectDir, image) {
239
+ const imageName = image;
189
240
  await new Promise((resolve, reject) => {
190
- const proc = spawn('docker', [
191
- 'run', '--rm', '-it',
192
- '--entrypoint', '',
193
- '-v', `${projectDir}:/home/runner/work`,
194
- '-e', 'CI=true',
241
+ const proc = spawn("docker", [
242
+ "run",
243
+ "--rm",
244
+ "-it",
245
+ "--entrypoint",
246
+ "",
247
+ // --mount is colon-safe; see runInContainer for the rationale.
248
+ "--mount",
249
+ `type=bind,source=${projectDir},target=/home/runner/work`,
250
+ "-e",
251
+ "CI=true",
195
252
  imageName,
196
- 'bash', '-c', 'cd /home/runner/work && exec bash',
197
- ], { stdio: 'inherit' });
198
- proc.on('close', () => resolve());
199
- proc.on('error', reject);
253
+ "bash",
254
+ "-c",
255
+ "cd /home/runner/work && exec bash",
256
+ ], { stdio: "inherit" });
257
+ proc.on("close", () => resolve());
258
+ proc.on("error", reject);
200
259
  });
201
260
  }
202
- // =============================================================================
203
- // Internal helpers
204
- // =============================================================================
205
- async function detectStackFromDir(projectDir) {
206
- if (await fs.pathExists(path.join(projectDir, 'build.gradle.kts')))
207
- return 'java-gradle';
208
- if (await fs.pathExists(path.join(projectDir, 'build.gradle')))
209
- return 'java-gradle';
210
- if (await fs.pathExists(path.join(projectDir, 'pom.xml')))
211
- return 'java-maven';
212
- if (await fs.pathExists(path.join(projectDir, 'package.json')))
213
- return 'node';
214
- if (await fs.pathExists(path.join(projectDir, 'go.mod')))
215
- return 'go';
216
- if (await fs.pathExists(path.join(projectDir, 'Cargo.toml')))
217
- return 'rust';
218
- if (await fs.pathExists(path.join(projectDir, 'pyproject.toml')) ||
219
- await fs.pathExists(path.join(projectDir, 'requirements.txt')) ||
220
- await fs.pathExists(path.join(projectDir, 'setup.py')))
221
- return 'python';
222
- return 'node'; // fallback
223
- }
224
261
  //# sourceMappingURL=docker.js.map
@@ -0,0 +1,10 @@
1
+ import { execFile } from "node:child_process";
2
+ /**
3
+ * Promisified `execFile` for shelling out to subprocesses with arguments
4
+ * (safer than `exec` because arguments are NOT shell-interpreted).
5
+ *
6
+ * Centralized helper used across the codebase by command modules and
7
+ * init steps that invoke external CLIs (git, javi-ai, etc.).
8
+ */
9
+ export declare const execFileAsync: typeof execFile.__promisify__;
10
+ //# sourceMappingURL=exec.d.ts.map
@@ -0,0 +1,11 @@
1
+ import { execFile } from "node:child_process";
2
+ import { promisify } from "node:util";
3
+ /**
4
+ * Promisified `execFile` for shelling out to subprocesses with arguments
5
+ * (safer than `exec` because arguments are NOT shell-interpreted).
6
+ *
7
+ * Centralized helper used across the codebase by command modules and
8
+ * init steps that invoke external CLIs (git, javi-ai, etc.).
9
+ */
10
+ export const execFileAsync = promisify(execFile);
11
+ //# sourceMappingURL=exec.js.map
@@ -14,5 +14,5 @@ export interface ValidationError {
14
14
  /**
15
15
  * Validate frontmatter against schema rules for agent or skill definitions.
16
16
  */
17
- export declare function validateFrontmatter(frontmatter: Record<string, unknown>, type: 'agent' | 'skill'): ValidationError[];
17
+ export declare function validateFrontmatter(frontmatter: Record<string, unknown>, type: "agent" | "skill"): ValidationError[];
18
18
  //# sourceMappingURL=frontmatter.d.ts.map