javi-forge 1.6.0 → 1.6.1

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 (231) hide show
  1. package/dist/commands/analyze.d.ts +1 -1
  2. package/dist/commands/analyze.js +15 -15
  3. package/dist/commands/atlassian-mcp.d.ts +42 -0
  4. package/dist/commands/atlassian-mcp.js +98 -0
  5. package/dist/commands/ci.d.ts +3 -3
  6. package/dist/commands/ci.js +185 -147
  7. package/dist/commands/crash-recovery.d.ts +34 -0
  8. package/dist/commands/crash-recovery.js +123 -0
  9. package/dist/commands/doctor.d.ts +2 -2
  10. package/dist/commands/doctor.js +113 -61
  11. package/dist/commands/harness-audit.d.ts +35 -0
  12. package/dist/commands/harness-audit.js +277 -0
  13. package/dist/commands/init.d.ts +1 -1
  14. package/dist/commands/init.js +384 -141
  15. package/dist/commands/llmstxt.d.ts +1 -1
  16. package/dist/commands/llmstxt.js +36 -34
  17. package/dist/commands/parallel-batch.d.ts +42 -0
  18. package/dist/commands/parallel-batch.js +90 -0
  19. package/dist/commands/plugin.d.ts +10 -1
  20. package/dist/commands/plugin.js +92 -47
  21. package/dist/commands/secret-scanner.d.ts +30 -0
  22. package/dist/commands/secret-scanner.js +272 -0
  23. package/dist/commands/security-analysis.d.ts +74 -0
  24. package/dist/commands/security-analysis.js +487 -0
  25. package/dist/commands/security.d.ts +11 -5
  26. package/dist/commands/security.js +216 -76
  27. package/dist/commands/skill-scanner.d.ts +63 -0
  28. package/dist/commands/skill-scanner.js +383 -0
  29. package/dist/commands/skills.d.ts +62 -5
  30. package/dist/commands/skills.js +439 -54
  31. package/dist/commands/supply-chain.d.ts +23 -0
  32. package/dist/commands/supply-chain.js +126 -0
  33. package/dist/commands/tdd-pipeline.d.ts +17 -0
  34. package/dist/commands/tdd-pipeline.js +144 -0
  35. package/dist/commands/tdd.d.ts +1 -1
  36. package/dist/commands/tdd.js +21 -18
  37. package/dist/commands/team-presets.d.ts +53 -0
  38. package/dist/commands/team-presets.js +201 -0
  39. package/dist/commands/workflow.d.ts +23 -0
  40. package/dist/commands/workflow.js +114 -0
  41. package/dist/constants.d.ts +15 -1
  42. package/dist/constants.js +161 -122
  43. package/dist/index.js +308 -98
  44. package/dist/lib/agent-skills.d.ts +36 -1
  45. package/dist/lib/agent-skills.js +168 -19
  46. package/dist/lib/auto-skill-install.d.ts +37 -0
  47. package/dist/lib/auto-skill-install.js +92 -0
  48. package/dist/lib/auto-wire.d.ts +20 -0
  49. package/dist/lib/auto-wire.js +240 -0
  50. package/dist/lib/claudemd.d.ts +13 -1
  51. package/dist/lib/claudemd.js +174 -24
  52. package/dist/lib/codex-export.d.ts +1 -1
  53. package/dist/lib/codex-export.js +29 -31
  54. package/dist/lib/common.d.ts +1 -1
  55. package/dist/lib/common.js +52 -44
  56. package/dist/lib/context.d.ts +17 -2
  57. package/dist/lib/context.js +142 -13
  58. package/dist/lib/docker.d.ts +1 -1
  59. package/dist/lib/docker.js +141 -112
  60. package/dist/lib/frontmatter.d.ts +1 -1
  61. package/dist/lib/frontmatter.js +29 -15
  62. package/dist/lib/plugin.d.ts +9 -3
  63. package/dist/lib/plugin.js +128 -69
  64. package/dist/lib/skill-publish.d.ts +40 -0
  65. package/dist/lib/skill-publish.js +146 -0
  66. package/dist/lib/stack-detector.d.ts +38 -0
  67. package/dist/lib/stack-detector.js +207 -0
  68. package/dist/lib/template.d.ts +16 -1
  69. package/dist/lib/template.js +46 -17
  70. package/dist/lib/workflow/discovery.d.ts +19 -0
  71. package/dist/lib/workflow/discovery.js +68 -0
  72. package/dist/lib/workflow/index.d.ts +5 -0
  73. package/dist/lib/workflow/index.js +5 -0
  74. package/dist/lib/workflow/parser.d.ts +16 -0
  75. package/dist/lib/workflow/parser.js +198 -0
  76. package/dist/lib/workflow/renderer.d.ts +9 -0
  77. package/dist/lib/workflow/renderer.js +152 -0
  78. package/dist/lib/workflow/validator.d.ts +10 -0
  79. package/dist/lib/workflow/validator.js +189 -0
  80. package/dist/tasks/index.d.ts +4 -0
  81. package/dist/tasks/index.js +4 -0
  82. package/dist/tasks/scaffold-tasks.d.ts +3 -0
  83. package/dist/tasks/scaffold-tasks.js +14 -0
  84. package/dist/tasks/task-id.d.ts +30 -0
  85. package/dist/tasks/task-id.js +55 -0
  86. package/dist/tasks/task-tracker.d.ts +15 -0
  87. package/dist/tasks/task-tracker.js +81 -0
  88. package/dist/types/index.d.ts +134 -6
  89. package/dist/types/index.js +11 -1
  90. package/dist/ui/AnalyzeUI.d.ts +1 -1
  91. package/dist/ui/AnalyzeUI.js +38 -39
  92. package/dist/ui/App.d.ts +5 -3
  93. package/dist/ui/App.js +86 -46
  94. package/dist/ui/AutoSkills.d.ts +9 -0
  95. package/dist/ui/AutoSkills.js +124 -0
  96. package/dist/ui/CI.d.ts +2 -2
  97. package/dist/ui/CI.js +24 -26
  98. package/dist/ui/CIContext.d.ts +1 -1
  99. package/dist/ui/CIContext.js +3 -2
  100. package/dist/ui/CISelector.d.ts +2 -2
  101. package/dist/ui/CISelector.js +23 -15
  102. package/dist/ui/Doctor.d.ts +1 -1
  103. package/dist/ui/Doctor.js +35 -29
  104. package/dist/ui/Header.d.ts +1 -1
  105. package/dist/ui/Header.js +14 -14
  106. package/dist/ui/HookProfileSelector.d.ts +9 -0
  107. package/dist/ui/HookProfileSelector.js +54 -0
  108. package/dist/ui/LlmsTxt.d.ts +1 -1
  109. package/dist/ui/LlmsTxt.js +31 -22
  110. package/dist/ui/MemorySelector.d.ts +2 -2
  111. package/dist/ui/MemorySelector.js +28 -16
  112. package/dist/ui/NameInput.d.ts +1 -1
  113. package/dist/ui/NameInput.js +21 -21
  114. package/dist/ui/OptionSelector.d.ts +6 -2
  115. package/dist/ui/OptionSelector.js +83 -32
  116. package/dist/ui/Plugin.d.ts +4 -3
  117. package/dist/ui/Plugin.js +78 -35
  118. package/dist/ui/Progress.d.ts +3 -3
  119. package/dist/ui/Progress.js +23 -22
  120. package/dist/ui/Skills.d.ts +2 -2
  121. package/dist/ui/Skills.js +61 -32
  122. package/dist/ui/StackSelector.d.ts +2 -2
  123. package/dist/ui/StackSelector.js +26 -16
  124. package/dist/ui/Summary.d.ts +3 -3
  125. package/dist/ui/Summary.js +60 -50
  126. package/dist/ui/Welcome.d.ts +1 -1
  127. package/dist/ui/Welcome.js +15 -16
  128. package/dist/ui/theme.d.ts +1 -1
  129. package/dist/ui/theme.js +6 -6
  130. package/package.json +9 -6
  131. package/templates/common/atlassian/mcp-atlassian-snippet.json +16 -0
  132. package/templates/common/repoforge/mcp-repoforge-snippet.json +11 -0
  133. package/templates/common/repoforge/repoforge.yaml +34 -0
  134. package/templates/github/deploy-docker-zero-downtime.yml +140 -0
  135. package/templates/github/repoforge-graph.yml +45 -0
  136. package/templates/gitlab/deploy-docker-zero-downtime.yml +57 -0
  137. package/templates/local-ai/.env.example +17 -0
  138. package/templates/local-ai/docker-compose.yml +95 -0
  139. package/templates/security-hooks/claude-settings-security.json +30 -0
  140. package/templates/security-hooks/commit-msg-signing +29 -0
  141. package/templates/security-hooks/pre-commit-permissions +74 -0
  142. package/templates/security-hooks/pre-commit-secrets +74 -0
  143. package/templates/security-hooks/pre-push-branch-protection +62 -0
  144. package/templates/security-hooks/pre-push-deps +83 -0
  145. package/templates/security-hooks/pre-push-signing +67 -0
  146. package/templates/woodpecker/deploy-docker-zero-downtime.yml +50 -0
  147. package/templates/workflows/ci-pipeline.dot +15 -0
  148. package/templates/workflows/feature-flow.dot +21 -0
  149. package/templates/workflows/release.dot +16 -0
  150. package/dist/__integration__/helpers.d.ts +0 -20
  151. package/dist/__integration__/helpers.d.ts.map +0 -1
  152. package/dist/__integration__/helpers.js +0 -31
  153. package/dist/__integration__/helpers.js.map +0 -1
  154. package/dist/commands/analyze.d.ts.map +0 -1
  155. package/dist/commands/analyze.js.map +0 -1
  156. package/dist/commands/ci.d.ts.map +0 -1
  157. package/dist/commands/ci.js.map +0 -1
  158. package/dist/commands/doctor.d.ts.map +0 -1
  159. package/dist/commands/doctor.js.map +0 -1
  160. package/dist/commands/init.d.ts.map +0 -1
  161. package/dist/commands/init.js.map +0 -1
  162. package/dist/commands/llmstxt.d.ts.map +0 -1
  163. package/dist/commands/llmstxt.js.map +0 -1
  164. package/dist/commands/plugin.d.ts.map +0 -1
  165. package/dist/commands/plugin.js.map +0 -1
  166. package/dist/commands/security.d.ts.map +0 -1
  167. package/dist/commands/security.js.map +0 -1
  168. package/dist/commands/skills.d.ts.map +0 -1
  169. package/dist/commands/skills.js.map +0 -1
  170. package/dist/commands/tdd.d.ts.map +0 -1
  171. package/dist/commands/tdd.js.map +0 -1
  172. package/dist/constants.d.ts.map +0 -1
  173. package/dist/constants.js.map +0 -1
  174. package/dist/index.d.ts.map +0 -1
  175. package/dist/index.js.map +0 -1
  176. package/dist/lib/agent-skills.d.ts.map +0 -1
  177. package/dist/lib/agent-skills.js.map +0 -1
  178. package/dist/lib/claudemd.d.ts.map +0 -1
  179. package/dist/lib/claudemd.js.map +0 -1
  180. package/dist/lib/codex-export.d.ts.map +0 -1
  181. package/dist/lib/codex-export.js.map +0 -1
  182. package/dist/lib/common.d.ts.map +0 -1
  183. package/dist/lib/common.js.map +0 -1
  184. package/dist/lib/context.d.ts.map +0 -1
  185. package/dist/lib/context.js.map +0 -1
  186. package/dist/lib/docker.d.ts.map +0 -1
  187. package/dist/lib/docker.js.map +0 -1
  188. package/dist/lib/frontmatter.d.ts.map +0 -1
  189. package/dist/lib/frontmatter.js.map +0 -1
  190. package/dist/lib/plugin.d.ts.map +0 -1
  191. package/dist/lib/plugin.js.map +0 -1
  192. package/dist/lib/template.d.ts.map +0 -1
  193. package/dist/lib/template.js.map +0 -1
  194. package/dist/types/index.d.ts.map +0 -1
  195. package/dist/types/index.js.map +0 -1
  196. package/dist/ui/AnalyzeUI.d.ts.map +0 -1
  197. package/dist/ui/AnalyzeUI.js.map +0 -1
  198. package/dist/ui/App.d.ts.map +0 -1
  199. package/dist/ui/App.js.map +0 -1
  200. package/dist/ui/CI.d.ts.map +0 -1
  201. package/dist/ui/CI.js.map +0 -1
  202. package/dist/ui/CIContext.d.ts.map +0 -1
  203. package/dist/ui/CIContext.js.map +0 -1
  204. package/dist/ui/CISelector.d.ts.map +0 -1
  205. package/dist/ui/CISelector.js.map +0 -1
  206. package/dist/ui/Doctor.d.ts.map +0 -1
  207. package/dist/ui/Doctor.js.map +0 -1
  208. package/dist/ui/Header.d.ts.map +0 -1
  209. package/dist/ui/Header.js.map +0 -1
  210. package/dist/ui/LlmsTxt.d.ts.map +0 -1
  211. package/dist/ui/LlmsTxt.js.map +0 -1
  212. package/dist/ui/MemorySelector.d.ts.map +0 -1
  213. package/dist/ui/MemorySelector.js.map +0 -1
  214. package/dist/ui/NameInput.d.ts.map +0 -1
  215. package/dist/ui/NameInput.js.map +0 -1
  216. package/dist/ui/OptionSelector.d.ts.map +0 -1
  217. package/dist/ui/OptionSelector.js.map +0 -1
  218. package/dist/ui/Plugin.d.ts.map +0 -1
  219. package/dist/ui/Plugin.js.map +0 -1
  220. package/dist/ui/Progress.d.ts.map +0 -1
  221. package/dist/ui/Progress.js.map +0 -1
  222. package/dist/ui/Skills.d.ts.map +0 -1
  223. package/dist/ui/Skills.js.map +0 -1
  224. package/dist/ui/StackSelector.d.ts.map +0 -1
  225. package/dist/ui/StackSelector.js.map +0 -1
  226. package/dist/ui/Summary.d.ts.map +0 -1
  227. package/dist/ui/Summary.js.map +0 -1
  228. package/dist/ui/Welcome.d.ts.map +0 -1
  229. package/dist/ui/Welcome.js.map +0 -1
  230. package/dist/ui/theme.d.ts.map +0 -1
  231. package/dist/ui/theme.js.map +0 -1
@@ -1,14 +1,16 @@
1
- import { STACK_CONTEXT_MAP } from '../constants.js';
1
+ import fs from "fs-extra";
2
+ import path from "path";
3
+ import { STACK_CONTEXT_MAP } from "../constants.js";
2
4
  // =============================================================================
3
5
  // Internal helpers
4
6
  // =============================================================================
5
7
  function getStackContext(stack) {
6
- return STACK_CONTEXT_MAP[stack] ?? STACK_CONTEXT_MAP['default'];
8
+ return STACK_CONTEXT_MAP[stack] ?? STACK_CONTEXT_MAP["default"];
7
9
  }
8
10
  export function buildIndexMd(projectName, stackCtx, ciProvider, memory) {
9
- return `# ${projectName} — File Index
11
+ return `# ${projectName} — Project Index
10
12
 
11
- ## Directory Structure
13
+ ## Structure
12
14
 
13
15
  \`\`\`
14
16
  ${stackCtx.tree}
@@ -20,13 +22,14 @@ ${stackCtx.tree}
20
22
 
21
23
  ## Conventions
22
24
 
23
- - **Stack conventions**: ${stackCtx.conventions}
25
+ - **Stack**: ${stackCtx.conventions}
24
26
  - **CI**: ${ciProvider}
25
27
  - **Memory**: ${memory}
26
28
  `;
27
29
  }
28
- export function buildSummaryMd(projectName, stack, ciProvider, memory, modules) {
29
- const modulesList = modules.length > 0 ? modules.join(', ') : 'none';
30
+ export function buildSummaryMd(projectName, stack, ciProvider, memory, modules, dependencies = []) {
31
+ const modulesList = modules.length > 0 ? modules.join(", ") : "none";
32
+ const depsList = dependencies.length > 0 ? dependencies.join(", ") : "none detected";
30
33
  return `# ${projectName}
31
34
 
32
35
  ## Overview
@@ -35,10 +38,11 @@ ${stack}-based project scaffolded with javi-forge.
35
38
 
36
39
  ## Stack
37
40
 
38
- - **Language/Runtime**: ${stack}
41
+ - **Runtime**: ${stack}
39
42
  - **CI**: ${ciProvider}
40
43
  - **Memory**: ${memory}
41
44
  - **Modules**: ${modulesList}
45
+ - **Dependencies**: ${depsList}
42
46
 
43
47
  ## Key Decisions
44
48
 
@@ -47,6 +51,99 @@ ${stack}-based project scaffolded with javi-forge.
47
51
  `;
48
52
  }
49
53
  // =============================================================================
54
+ // Dependency detection
55
+ // =============================================================================
56
+ /**
57
+ * Detect top-level dependencies from project manifest files.
58
+ * Returns up to 10 dependency names (key deps only, not devDeps).
59
+ */
60
+ export async function detectDependencies(projectDir, stack) {
61
+ const MAX_DEPS = 10;
62
+ try {
63
+ switch (stack) {
64
+ case "node": {
65
+ const pkgPath = path.join(projectDir, "package.json");
66
+ if (!(await fs.pathExists(pkgPath)))
67
+ return [];
68
+ const pkg = await fs.readJson(pkgPath).catch(() => ({}));
69
+ const deps = Object.keys(pkg.dependencies ?? {});
70
+ return deps.slice(0, MAX_DEPS);
71
+ }
72
+ case "python": {
73
+ const pyprojectPath = path.join(projectDir, "pyproject.toml");
74
+ if (await fs.pathExists(pyprojectPath)) {
75
+ const content = await fs.readFile(pyprojectPath, "utf-8");
76
+ const match = content.match(/dependencies\s*=\s*\[([\s\S]*?)\]/);
77
+ if (match?.[1]) {
78
+ const deps = match[1]
79
+ .split("\n")
80
+ .map((l) => l.replace(/[",]/g, "").trim())
81
+ .filter((l) => l.length > 0 && !l.startsWith("#"))
82
+ .map((l) => l.split(/[>=<~!]/)[0].trim())
83
+ .filter(Boolean);
84
+ return deps.slice(0, MAX_DEPS);
85
+ }
86
+ }
87
+ const reqPath = path.join(projectDir, "requirements.txt");
88
+ if (await fs.pathExists(reqPath)) {
89
+ const content = await fs.readFile(reqPath, "utf-8");
90
+ const deps = content
91
+ .split("\n")
92
+ .map((l) => l.trim())
93
+ .filter((l) => l.length > 0 && !l.startsWith("#") && !l.startsWith("-"))
94
+ .map((l) => l.split(/[>=<~!]/)[0].trim())
95
+ .filter(Boolean);
96
+ return deps.slice(0, MAX_DEPS);
97
+ }
98
+ return [];
99
+ }
100
+ case "go": {
101
+ const goModPath = path.join(projectDir, "go.mod");
102
+ if (!(await fs.pathExists(goModPath)))
103
+ return [];
104
+ const content = await fs.readFile(goModPath, "utf-8");
105
+ const requireBlock = content.match(/require\s*\(([\s\S]*?)\)/);
106
+ if (requireBlock?.[1]) {
107
+ const deps = requireBlock[1]
108
+ .split("\n")
109
+ .map((l) => l.trim())
110
+ .filter((l) => l.length > 0 && !l.startsWith("//"))
111
+ .map((l) => {
112
+ const parts = l.split(/\s+/);
113
+ const mod = parts[0] ?? "";
114
+ return mod.split("/").pop() ?? mod;
115
+ })
116
+ .filter(Boolean);
117
+ return deps.slice(0, MAX_DEPS);
118
+ }
119
+ return [];
120
+ }
121
+ case "rust": {
122
+ const cargoPath = path.join(projectDir, "Cargo.toml");
123
+ if (!(await fs.pathExists(cargoPath)))
124
+ return [];
125
+ const content = await fs.readFile(cargoPath, "utf-8");
126
+ const depsSection = content.match(/\[dependencies\]([\s\S]*?)(?=\n\[|$)/);
127
+ if (depsSection?.[1]) {
128
+ const deps = depsSection[1]
129
+ .split("\n")
130
+ .map((l) => l.trim())
131
+ .filter((l) => l.length > 0 && !l.startsWith("#"))
132
+ .map((l) => l.split(/\s*=/)[0].trim())
133
+ .filter(Boolean);
134
+ return deps.slice(0, MAX_DEPS);
135
+ }
136
+ return [];
137
+ }
138
+ default:
139
+ return [];
140
+ }
141
+ }
142
+ catch {
143
+ return [];
144
+ }
145
+ }
146
+ // =============================================================================
50
147
  // Public API
51
148
  // =============================================================================
52
149
  /**
@@ -59,17 +156,49 @@ export async function generateContextDir(options) {
59
156
  // Collect enabled modules for summary
60
157
  const modules = [];
61
158
  if (options.aiSync)
62
- modules.push('ai-sync');
159
+ modules.push("ai-sync");
63
160
  if (options.sdd)
64
- modules.push('sdd');
161
+ modules.push("sdd");
65
162
  if (options.ghagga)
66
- modules.push('ghagga');
163
+ modules.push("ghagga");
67
164
  if (options.mock)
68
- modules.push('mock');
165
+ modules.push("mock");
69
166
  if (options.contextDir)
70
- modules.push('context');
167
+ modules.push("context");
71
168
  const index = buildIndexMd(projectName, stackCtx, ciProvider, memory);
72
169
  const summary = buildSummaryMd(projectName, stack, ciProvider, memory, modules);
73
170
  return { index, summary };
74
171
  }
172
+ /**
173
+ * Refresh .context/ directory from the forge manifest and live project state.
174
+ * Reads manifest, detects current dependencies, regenerates INDEX.md + summary.md.
175
+ * Returns null if the project is not forge-managed or has no .context/ dir.
176
+ */
177
+ export async function refreshContextDir(projectDir) {
178
+ const manifestPath = path.join(projectDir, ".javi-forge", "manifest.json");
179
+ const contextDirPath = path.join(projectDir, ".context");
180
+ // Only refresh if forge-managed and .context/ exists
181
+ if (!(await fs.pathExists(manifestPath)) ||
182
+ !(await fs.pathExists(contextDirPath))) {
183
+ return null;
184
+ }
185
+ let manifest;
186
+ try {
187
+ manifest = (await fs.readJson(manifestPath));
188
+ }
189
+ catch {
190
+ return null;
191
+ }
192
+ const stackCtx = getStackContext(manifest.stack);
193
+ const dependencies = await detectDependencies(projectDir, manifest.stack);
194
+ const index = buildIndexMd(manifest.projectName, stackCtx, manifest.ciProvider, manifest.memory);
195
+ const summary = buildSummaryMd(manifest.projectName, manifest.stack, manifest.ciProvider, manifest.memory, manifest.modules, dependencies);
196
+ // Write updated files
197
+ await fs.writeFile(path.join(contextDirPath, "INDEX.md"), index, "utf-8");
198
+ await fs.writeFile(path.join(contextDirPath, "summary.md"), summary, "utf-8");
199
+ // Update manifest timestamp
200
+ manifest.updatedAt = new Date().toISOString();
201
+ await fs.writeJson(manifestPath, manifest, { spaces: 2 });
202
+ return { index, summary, updated: true };
203
+ }
75
204
  //# sourceMappingURL=context.js.map
@@ -1,4 +1,4 @@
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;
@@ -1,8 +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';
1
+ import { execFile, spawn } from "child_process";
2
+ import crypto from "crypto";
3
+ import fs from "fs-extra";
4
+ import path from "path";
5
+ import { promisify } from "util";
6
6
  const execFileAsync = promisify(execFile);
7
7
  // =============================================================================
8
8
  // Image name
@@ -15,66 +15,66 @@ export function getImageName(stack) {
15
15
  // =============================================================================
16
16
  export function getDockerfileContent(stack) {
17
17
  switch (stack) {
18
- case 'java-gradle':
19
- case 'java-maven':
18
+ case "java-gradle":
19
+ case "java-maven":
20
20
  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',
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",
27
27
  'ENTRYPOINT ["/bin/bash", "-c"]',
28
- ].join('\n');
29
- case 'node':
28
+ ].join("\n");
29
+ case "node":
30
30
  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',
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",
37
37
  'ENTRYPOINT ["/bin/bash", "-c"]',
38
- ].join('\n');
39
- case 'python':
38
+ ].join("\n");
39
+ case "python":
40
40
  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',
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",
47
47
  'ENTRYPOINT ["/bin/bash", "-c"]',
48
- ].join('\n');
49
- case 'go':
48
+ ].join("\n");
49
+ case "go":
50
50
  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',
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",
57
57
  'ENTRYPOINT ["/bin/bash", "-c"]',
58
- ].join('\n');
59
- case 'rust':
58
+ ].join("\n");
59
+ case "rust":
60
60
  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',
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",
67
67
  'ENTRYPOINT ["/bin/bash", "-c"]',
68
- ].join('\n');
68
+ ].join("\n");
69
69
  default:
70
70
  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',
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",
76
76
  'ENTRYPOINT ["/bin/bash", "-c"]',
77
- ].join('\n');
77
+ ].join("\n");
78
78
  }
79
79
  }
80
80
  // =============================================================================
@@ -82,7 +82,7 @@ export function getDockerfileContent(stack) {
82
82
  // =============================================================================
83
83
  export async function isDockerAvailable() {
84
84
  try {
85
- await execFileAsync('docker', ['info'], { timeout: 5000 });
85
+ await execFileAsync("docker", ["info"], { timeout: 5000 });
86
86
  return true;
87
87
  }
88
88
  catch {
@@ -101,20 +101,24 @@ export async function ensureImage(options) {
101
101
  const { stack, javaVersion, dockerfilesDir } = options;
102
102
  const imageName = getImageName(stack);
103
103
  // Resolve Dockerfile path
104
- const dockerDir = dockerfilesDir ?? path.join(path.dirname(new URL(import.meta.url).pathname), '../../ci-local/docker');
104
+ const dockerDir = dockerfilesDir ??
105
+ path.join(path.dirname(new URL(import.meta.url).pathname), "../../ci-local/docker");
105
106
  const dockerfilePath = path.join(dockerDir, `${stack}.Dockerfile`);
106
107
  // Write Dockerfile if it doesn't exist yet (first run)
107
- if (!await fs.pathExists(dockerfilePath)) {
108
+ if (!(await fs.pathExists(dockerfilePath))) {
108
109
  await fs.ensureDir(dockerDir);
109
- await fs.writeFile(dockerfilePath, getDockerfileContent(stack), 'utf-8');
110
+ await fs.writeFile(dockerfilePath, getDockerfileContent(stack), "utf-8");
110
111
  }
111
112
  // 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 = '';
113
+ const content = await fs.readFile(dockerfilePath, "utf-8");
114
+ const currentHash = crypto.createHash("sha256").update(content).digest("hex");
115
+ let imageHash = "";
115
116
  try {
116
- const { stdout } = await execFileAsync('docker', [
117
- 'inspect', '--format', '{{index .Config.Labels "dockerfile-hash"}}', imageName,
117
+ const { stdout } = await execFileAsync("docker", [
118
+ "inspect",
119
+ "--format",
120
+ '{{index .Config.Labels "dockerfile-hash"}}',
121
+ imageName,
118
122
  ]);
119
123
  imageHash = stdout.trim();
120
124
  }
@@ -126,19 +130,24 @@ export async function ensureImage(options) {
126
130
  }
127
131
  // Build image
128
132
  const buildArgs = [
129
- 'build',
130
- '--label', `dockerfile-hash=${currentHash}`,
131
- '-f', dockerfilePath,
132
- '-t', imageName,
133
+ "build",
134
+ "--label",
135
+ `dockerfile-hash=${currentHash}`,
136
+ "-f",
137
+ dockerfilePath,
138
+ "-t",
139
+ imageName,
133
140
  ];
134
- if (javaVersion && (stack === 'java-gradle' || stack === 'java-maven')) {
135
- buildArgs.push('--build-arg', `JAVA_VERSION=${javaVersion}`);
141
+ if (javaVersion && (stack === "java-gradle" || stack === "java-maven")) {
142
+ buildArgs.push("--build-arg", `JAVA_VERSION=${javaVersion}`);
136
143
  }
137
144
  buildArgs.push(dockerDir);
138
145
  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);
146
+ const proc = spawn("docker", buildArgs, { stdio: "inherit" });
147
+ proc.on("close", (code) => code === 0
148
+ ? resolve()
149
+ : reject(new Error(`docker build exited with code ${code}`)));
150
+ proc.on("error", reject);
142
151
  });
143
152
  return imageName;
144
153
  }
@@ -156,28 +165,41 @@ export async function runInContainer(options) {
156
165
  const imageName = getImageName(stack);
157
166
  const isInteractive = process.stdin.isTTY && stream;
158
167
  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',
168
+ "run",
169
+ "--rm",
170
+ ...(isInteractive ? ["-it"] : []),
171
+ "--stop-timeout",
172
+ "30",
173
+ "--entrypoint",
174
+ "",
175
+ ...(user ? ["--user", user] : []),
176
+ "-v",
177
+ `${projectDir}:/home/runner/work`,
178
+ "-e",
179
+ "CI=true",
166
180
  imageName,
167
- 'timeout', String(timeout), 'bash', '-c', command,
181
+ "timeout",
182
+ String(timeout),
183
+ "bash",
184
+ "-c",
185
+ command,
168
186
  ];
169
187
  return new Promise((resolve, reject) => {
170
- const proc = spawn('docker', dockerArgs, {
171
- stdio: stream ? 'inherit' : 'pipe',
188
+ const proc = spawn("docker", dockerArgs, {
189
+ stdio: stream ? "inherit" : "pipe",
172
190
  });
173
- let stdout = '';
174
- let stderr = '';
191
+ let stdout = "";
192
+ let stderr = "";
175
193
  if (!stream) {
176
- proc.stdout?.on('data', (d) => { stdout += d.toString(); });
177
- proc.stderr?.on('data', (d) => { stderr += d.toString(); });
194
+ proc.stdout?.on("data", (d) => {
195
+ stdout += d.toString();
196
+ });
197
+ proc.stderr?.on("data", (d) => {
198
+ stderr += d.toString();
199
+ });
178
200
  }
179
- proc.on('close', code => resolve({ exitCode: code ?? 1, stdout, stderr }));
180
- proc.on('error', reject);
201
+ proc.on("close", (code) => resolve({ exitCode: code ?? 1, stdout, stderr }));
202
+ proc.on("error", reject);
181
203
  });
182
204
  }
183
205
  /**
@@ -187,38 +209,45 @@ export async function openShell(projectDir) {
187
209
  const stack = await detectStackFromDir(projectDir);
188
210
  const imageName = getImageName(stack);
189
211
  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',
212
+ const proc = spawn("docker", [
213
+ "run",
214
+ "--rm",
215
+ "-it",
216
+ "--entrypoint",
217
+ "",
218
+ "-v",
219
+ `${projectDir}:/home/runner/work`,
220
+ "-e",
221
+ "CI=true",
195
222
  imageName,
196
- 'bash', '-c', 'cd /home/runner/work && exec bash',
197
- ], { stdio: 'inherit' });
198
- proc.on('close', () => resolve());
199
- proc.on('error', reject);
223
+ "bash",
224
+ "-c",
225
+ "cd /home/runner/work && exec bash",
226
+ ], { stdio: "inherit" });
227
+ proc.on("close", () => resolve());
228
+ proc.on("error", reject);
200
229
  });
201
230
  }
202
231
  // =============================================================================
203
232
  // Internal helpers
204
233
  // =============================================================================
205
234
  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
235
+ if (await fs.pathExists(path.join(projectDir, "build.gradle.kts")))
236
+ return "java-gradle";
237
+ if (await fs.pathExists(path.join(projectDir, "build.gradle")))
238
+ return "java-gradle";
239
+ if (await fs.pathExists(path.join(projectDir, "pom.xml")))
240
+ return "java-maven";
241
+ if (await fs.pathExists(path.join(projectDir, "package.json")))
242
+ return "node";
243
+ if (await fs.pathExists(path.join(projectDir, "go.mod")))
244
+ return "go";
245
+ if (await fs.pathExists(path.join(projectDir, "Cargo.toml")))
246
+ return "rust";
247
+ if ((await fs.pathExists(path.join(projectDir, "pyproject.toml"))) ||
248
+ (await fs.pathExists(path.join(projectDir, "requirements.txt"))) ||
249
+ (await fs.pathExists(path.join(projectDir, "setup.py"))))
250
+ return "python";
251
+ return "node"; // fallback
223
252
  }
224
253
  //# sourceMappingURL=docker.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
@@ -1,20 +1,20 @@
1
- import YAML from 'yaml';
1
+ import YAML from "yaml";
2
2
  /**
3
3
  * Extract YAML frontmatter from a markdown string.
4
4
  * Frontmatter is delimited by --- at the start of the file.
5
5
  */
6
6
  export function parseFrontmatter(raw) {
7
7
  const trimmed = raw.trimStart();
8
- if (!trimmed.startsWith('---'))
8
+ if (!trimmed.startsWith("---"))
9
9
  return null;
10
- const endIdx = trimmed.indexOf('---', 3);
10
+ const endIdx = trimmed.indexOf("---", 3);
11
11
  if (endIdx === -1)
12
12
  return null;
13
13
  const yamlBlock = trimmed.slice(3, endIdx).trim();
14
14
  const content = trimmed.slice(endIdx + 3).trim();
15
15
  try {
16
16
  const data = YAML.parse(yamlBlock);
17
- if (typeof data !== 'object' || data === null)
17
+ if (typeof data !== "object" || data === null)
18
18
  return null;
19
19
  return { data, content };
20
20
  }
@@ -29,30 +29,44 @@ const KEBAB_CASE_RE = /^[a-z0-9]+(-[a-z0-9]+)*$/;
29
29
  export function validateFrontmatter(frontmatter, type) {
30
30
  const errors = [];
31
31
  // name: required, kebab-case, 2-60 chars
32
- const name = frontmatter['name'];
33
- if (typeof name !== 'string' || !name) {
34
- errors.push({ field: 'name', message: 'name is required and must be a string' });
32
+ const name = frontmatter["name"];
33
+ if (typeof name !== "string" || !name) {
34
+ errors.push({
35
+ field: "name",
36
+ message: "name is required and must be a string",
37
+ });
35
38
  }
36
39
  else {
37
40
  if (name.length < 2 || name.length > 60) {
38
- errors.push({ field: 'name', message: 'name must be 2-60 characters' });
41
+ errors.push({ field: "name", message: "name must be 2-60 characters" });
39
42
  }
40
43
  if (!KEBAB_CASE_RE.test(name)) {
41
- errors.push({ field: 'name', message: 'name must be kebab-case (e.g. my-skill-name)' });
44
+ errors.push({
45
+ field: "name",
46
+ message: "name must be kebab-case (e.g. my-skill-name)",
47
+ });
42
48
  }
43
49
  }
44
50
  // description: required, non-empty, min 10 chars
45
- const desc = frontmatter['description'];
46
- if (typeof desc !== 'string' || !desc) {
47
- errors.push({ field: 'description', message: 'description is required and must be a string' });
51
+ const desc = frontmatter["description"];
52
+ if (typeof desc !== "string" || !desc) {
53
+ errors.push({
54
+ field: "description",
55
+ message: "description is required and must be a string",
56
+ });
48
57
  }
49
58
  else if (desc.length < 10) {
50
- errors.push({ field: 'description', message: 'description must be at least 10 characters' });
59
+ errors.push({
60
+ field: "description",
61
+ message: "description must be at least 10 characters",
62
+ });
51
63
  }
52
64
  // skill-specific: description should include "Trigger:" hint
53
- if (type === 'skill' && typeof desc === 'string' && !desc.includes('Trigger:')) {
65
+ if (type === "skill" &&
66
+ typeof desc === "string" &&
67
+ !desc.includes("Trigger:")) {
54
68
  errors.push({
55
- field: 'description',
69
+ field: "description",
56
70
  message: 'skill description should include a "Trigger:" hint for auto-invoke',
57
71
  });
58
72
  }