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
@@ -1,11 +1,11 @@
1
- import fs from 'fs-extra';
2
- import path from 'path';
3
- import { TEMPLATES_DIR, DEPENDABOT_FRAGMENTS_DIR, STACK_DEPENDABOT_MAP, STACK_CI_MAP, } from '../constants.js';
1
+ import path from "node:path";
2
+ import fs from "fs-extra";
3
+ import { DEPENDABOT_FRAGMENTS_DIR, DEPLOY_DESTINATION_MAP, DEPLOY_TEMPLATE_MAP, STACK_CI_MAP, STACK_DEPENDABOT_MAP, TEMPLATES_DIR, } from "../constants.js";
4
4
  /**
5
5
  * Read a template file and replace __VAR_NAME__ placeholders.
6
6
  */
7
7
  export async function renderTemplate(templatePath, vars) {
8
- let content = await fs.readFile(templatePath, 'utf-8');
8
+ let content = await fs.readFile(templatePath, "utf-8");
9
9
  for (const [key, value] of Object.entries(vars)) {
10
10
  const placeholder = `__${key}__`;
11
11
  content = content.replaceAll(placeholder, value);
@@ -17,12 +17,12 @@ export async function renderTemplate(templatePath, vars) {
17
17
  * Always includes github-actions fragment when provider is GitHub.
18
18
  */
19
19
  export async function generateDependabotYml(stacks, includeGitHubActions = true) {
20
- const headerPath = path.join(DEPENDABOT_FRAGMENTS_DIR, 'header.yml');
21
- let content = await fs.readFile(headerPath, 'utf-8');
20
+ const headerPath = path.join(DEPENDABOT_FRAGMENTS_DIR, "header.yml");
21
+ let content = await fs.readFile(headerPath, "utf-8");
22
22
  // Collect unique fragment names
23
23
  const fragments = new Set();
24
24
  if (includeGitHubActions)
25
- fragments.add('github-actions');
25
+ fragments.add("github-actions");
26
26
  for (const stack of stacks) {
27
27
  const stackFragments = STACK_DEPENDABOT_MAP[stack] ?? [];
28
28
  for (const f of stackFragments)
@@ -32,8 +32,8 @@ export async function generateDependabotYml(stacks, includeGitHubActions = true)
32
32
  for (const fragmentName of fragments) {
33
33
  const fragmentPath = path.join(DEPENDABOT_FRAGMENTS_DIR, `${fragmentName}.yml`);
34
34
  if (await fs.pathExists(fragmentPath)) {
35
- const fragment = await fs.readFile(fragmentPath, 'utf-8');
36
- content += '\n' + fragment;
35
+ const fragment = await fs.readFile(fragmentPath, "utf-8");
36
+ content += `\n${fragment}`;
37
37
  }
38
38
  }
39
39
  return content;
@@ -58,21 +58,50 @@ export async function generateCIWorkflow(stack, provider) {
58
58
  const templatePath = getCITemplatePath(stack, provider);
59
59
  if (!templatePath)
60
60
  return null;
61
- if (!await fs.pathExists(templatePath))
61
+ if (!(await fs.pathExists(templatePath)))
62
62
  return null;
63
- return fs.readFile(templatePath, 'utf-8');
63
+ return fs.readFile(templatePath, "utf-8");
64
64
  }
65
65
  /**
66
66
  * Get the destination path for a CI workflow file within a project.
67
67
  */
68
68
  export function getCIDestination(provider) {
69
69
  switch (provider) {
70
- case 'github':
71
- return '.github/workflows/ci.yml';
72
- case 'gitlab':
73
- return '.gitlab-ci.yml';
74
- case 'woodpecker':
75
- return '.woodpecker.yml';
70
+ case "github":
71
+ return ".github/workflows/ci.yml";
72
+ case "gitlab":
73
+ return ".gitlab-ci.yml";
74
+ case "woodpecker":
75
+ return ".woodpecker.yml";
76
76
  }
77
77
  }
78
+ /**
79
+ * Get the template path for the zero-downtime Docker deploy workflow.
80
+ * Returns null if no template exists for the given provider.
81
+ */
82
+ export function getDeployTemplatePath(provider) {
83
+ const filename = DEPLOY_TEMPLATE_MAP[provider];
84
+ if (!filename)
85
+ return null;
86
+ return path.join(TEMPLATES_DIR, provider, filename);
87
+ }
88
+ /**
89
+ * Get the destination path for the deploy workflow file within a project.
90
+ * Returns null if no mapping exists for the given provider.
91
+ */
92
+ export function getDeployDestination(provider) {
93
+ return DEPLOY_DESTINATION_MAP[provider] ?? null;
94
+ }
95
+ /**
96
+ * Generate the zero-downtime Docker deploy workflow content.
97
+ * Replaces __SERVICE_NAME__ with the provided service name.
98
+ */
99
+ export async function generateDeployWorkflow(provider, serviceName) {
100
+ const templatePath = getDeployTemplatePath(provider);
101
+ if (!templatePath)
102
+ return null;
103
+ if (!(await fs.pathExists(templatePath)))
104
+ return null;
105
+ return renderTemplate(templatePath, { SERVICE_NAME: serviceName });
106
+ }
78
107
  //# sourceMappingURL=template.js.map
@@ -0,0 +1,27 @@
1
+ import type { WorkflowDiscoveryEntry, WorkflowFormat } from "../../types/index.js";
2
+ /** Workflow templates directory inside javi-forge package */
3
+ export declare const WORKFLOW_TEMPLATES_DIR: string;
4
+ /**
5
+ * Discover workflow files in a project's `.javi-forge/workflows/` directory.
6
+ */
7
+ export declare function discoverWorkflows(projectDir: string): Promise<WorkflowDiscoveryEntry[]>;
8
+ /**
9
+ * List available built-in workflow templates.
10
+ */
11
+ export declare function listBuiltinTemplates(): Promise<WorkflowDiscoveryEntry[]>;
12
+ /**
13
+ * Load a built-in template by name. Returns the file content or null if not found.
14
+ *
15
+ * The name argument is restricted to a strict kebab-case alphabet — anything
16
+ * with path separators, dots, or characters outside [a-z0-9-] is rejected
17
+ * outright. Without this check, an attacker (or a user with a typo) could
18
+ * pass --template "../../../etc/passwd" and walk out of WORKFLOW_TEMPLATES_DIR.
19
+ * The file would still need to end in .dot or .mermaid, but file-existence
20
+ * probes and arbitrary DOT-parse on attacker-controlled paths is enough to
21
+ * reject up front.
22
+ */
23
+ export declare function loadBuiltinTemplate(name: string): Promise<{
24
+ content: string;
25
+ format: WorkflowFormat;
26
+ } | null>;
27
+ //# sourceMappingURL=discovery.d.ts.map
@@ -0,0 +1,85 @@
1
+ import path from "node:path";
2
+ import fs from "fs-extra";
3
+ import { TEMPLATES_DIR } from "../../constants.js";
4
+ const WORKFLOW_EXTENSIONS = {
5
+ ".dot": "dot",
6
+ ".mermaid": "mermaid",
7
+ };
8
+ /** Workflow templates directory inside javi-forge package */
9
+ export const WORKFLOW_TEMPLATES_DIR = path.join(TEMPLATES_DIR, "workflows");
10
+ /**
11
+ * Discover workflow files in a project's `.javi-forge/workflows/` directory.
12
+ */
13
+ export async function discoverWorkflows(projectDir) {
14
+ const workflowDir = path.join(projectDir, ".javi-forge", "workflows");
15
+ if (!(await fs.pathExists(workflowDir))) {
16
+ return [];
17
+ }
18
+ const entries = await fs.readdir(workflowDir);
19
+ const workflows = [];
20
+ for (const entry of entries) {
21
+ const ext = path.extname(entry);
22
+ const format = WORKFLOW_EXTENSIONS[ext];
23
+ if (format) {
24
+ workflows.push({
25
+ name: path.basename(entry, ext),
26
+ path: path.join(workflowDir, entry),
27
+ format,
28
+ });
29
+ }
30
+ }
31
+ return workflows;
32
+ }
33
+ /**
34
+ * List available built-in workflow templates.
35
+ */
36
+ export async function listBuiltinTemplates() {
37
+ if (!(await fs.pathExists(WORKFLOW_TEMPLATES_DIR))) {
38
+ return [];
39
+ }
40
+ const entries = await fs.readdir(WORKFLOW_TEMPLATES_DIR);
41
+ const templates = [];
42
+ for (const entry of entries) {
43
+ const ext = path.extname(entry);
44
+ const format = WORKFLOW_EXTENSIONS[ext];
45
+ if (format) {
46
+ templates.push({
47
+ name: path.basename(entry, ext),
48
+ path: path.join(WORKFLOW_TEMPLATES_DIR, entry),
49
+ format,
50
+ });
51
+ }
52
+ }
53
+ return templates;
54
+ }
55
+ /**
56
+ * Load a built-in template by name. Returns the file content or null if not found.
57
+ *
58
+ * The name argument is restricted to a strict kebab-case alphabet — anything
59
+ * with path separators, dots, or characters outside [a-z0-9-] is rejected
60
+ * outright. Without this check, an attacker (or a user with a typo) could
61
+ * pass --template "../../../etc/passwd" and walk out of WORKFLOW_TEMPLATES_DIR.
62
+ * The file would still need to end in .dot or .mermaid, but file-existence
63
+ * probes and arbitrary DOT-parse on attacker-controlled paths is enough to
64
+ * reject up front.
65
+ */
66
+ export async function loadBuiltinTemplate(name) {
67
+ if (!/^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/.test(name)) {
68
+ return null;
69
+ }
70
+ for (const [ext, format] of Object.entries(WORKFLOW_EXTENSIONS)) {
71
+ const templatePath = path.join(WORKFLOW_TEMPLATES_DIR, `${name}${ext}`);
72
+ // Defense in depth: even with the name regex above, ensure the
73
+ // resolved real path stays inside WORKFLOW_TEMPLATES_DIR.
74
+ const resolved = path.resolve(templatePath);
75
+ if (!resolved.startsWith(`${WORKFLOW_TEMPLATES_DIR}${path.sep}`)) {
76
+ continue;
77
+ }
78
+ if (await fs.pathExists(templatePath)) {
79
+ const content = await fs.readFile(templatePath, "utf-8");
80
+ return { content, format };
81
+ }
82
+ }
83
+ return null;
84
+ }
85
+ //# sourceMappingURL=discovery.js.map
@@ -0,0 +1,5 @@
1
+ export { discoverWorkflows, listBuiltinTemplates, loadBuiltinTemplate, WORKFLOW_TEMPLATES_DIR, } from "./discovery.js";
2
+ export { parseDot, parseMermaid, WorkflowParseError } from "./parser.js";
3
+ export { renderAscii } from "./renderer.js";
4
+ export { getAvailableChecks, validateWorkflow } from "./validator.js";
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,5 @@
1
+ export { discoverWorkflows, listBuiltinTemplates, loadBuiltinTemplate, WORKFLOW_TEMPLATES_DIR, } from "./discovery.js";
2
+ export { parseDot, parseMermaid, WorkflowParseError } from "./parser.js";
3
+ export { renderAscii } from "./renderer.js";
4
+ export { getAvailableChecks, validateWorkflow } from "./validator.js";
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,16 @@
1
+ import type { WorkflowGraph } from "../../types/index.js";
2
+ export declare class WorkflowParseError extends Error {
3
+ readonly line?: number | undefined;
4
+ constructor(message: string, line?: number | undefined);
5
+ }
6
+ /**
7
+ * Parse a DOT digraph string into a WorkflowGraph.
8
+ * Supports subset: `digraph [name] { A -> B; A [label="X" check="Y"]; }`
9
+ */
10
+ export declare function parseDot(content: string, sourceName?: string): WorkflowGraph;
11
+ /**
12
+ * Parse a Mermaid flowchart string into a WorkflowGraph.
13
+ * Supports subset: `flowchart LR/TD`, `A --> B`, `A[Label]`, `A -->|label| B`
14
+ */
15
+ export declare function parseMermaid(content: string, sourceName?: string): WorkflowGraph;
16
+ //# sourceMappingURL=parser.d.ts.map
@@ -0,0 +1,198 @@
1
+ export class WorkflowParseError extends Error {
2
+ line;
3
+ constructor(message, line) {
4
+ super(line !== undefined ? `Line ${line}: ${message}` : message);
5
+ this.line = line;
6
+ this.name = "WorkflowParseError";
7
+ }
8
+ }
9
+ // ── DOT Parser ──────────────────────────────────────────────────────────────
10
+ /**
11
+ * Parse a DOT digraph string into a WorkflowGraph.
12
+ * Supports subset: `digraph [name] { A -> B; A [label="X" check="Y"]; }`
13
+ */
14
+ export function parseDot(content, sourceName = "untitled") {
15
+ const trimmed = content.trim();
16
+ // Extract digraph header
17
+ const headerMatch = trimmed.match(/^digraph\s+(?:"([^"]+)"|(\w+))?\s*\{/i);
18
+ if (!headerMatch) {
19
+ throw new WorkflowParseError('Expected "digraph [name] {" at start of file');
20
+ }
21
+ const graphName = headerMatch[1] ?? headerMatch[2] ?? sourceName;
22
+ // Extract body between first { and last }
23
+ const openBrace = trimmed.indexOf("{");
24
+ const closeBrace = trimmed.lastIndexOf("}");
25
+ if (openBrace === -1 || closeBrace === -1 || closeBrace <= openBrace) {
26
+ throw new WorkflowParseError("Malformed digraph: missing braces");
27
+ }
28
+ const body = trimmed.slice(openBrace + 1, closeBrace);
29
+ const nodesMap = new Map();
30
+ const edges = [];
31
+ // Split by semicolons or newlines
32
+ const statements = body
33
+ .split(/[;\n]/)
34
+ .map((s) => s.trim())
35
+ .filter((s) => s.length > 0 && !s.startsWith("//") && !s.startsWith("#"));
36
+ for (const stmt of statements) {
37
+ // Skip graph-level attributes like rankdir=LR
38
+ if (/^\w+=/.test(stmt) && !stmt.includes("->"))
39
+ continue;
40
+ // Edge: A -> B [label="..."]
41
+ const edgeMatch = stmt.match(/^"?(\w[\w\s-]*?)"?\s*->\s*"?(\w[\w\s-]*?)"?\s*(?:\[([^\]]*)\])?\s*$/);
42
+ if (edgeMatch) {
43
+ const fromId = edgeMatch[1]?.trim();
44
+ const toId = edgeMatch[2]?.trim();
45
+ const attrs = edgeMatch[3] ? parseAttributes(edgeMatch[3]) : {};
46
+ ensureNode(nodesMap, fromId);
47
+ ensureNode(nodesMap, toId);
48
+ edges.push({ from: fromId, to: toId, label: attrs.label });
49
+ continue;
50
+ }
51
+ // Node: A [label="..." check="..."]
52
+ const nodeMatch = stmt.match(/^"?(\w[\w\s-]*?)"?\s*\[([^\]]+)\]\s*$/);
53
+ if (nodeMatch) {
54
+ const nodeId = nodeMatch[1]?.trim();
55
+ const attrs = parseAttributes(nodeMatch[2]);
56
+ const node = ensureNode(nodesMap, nodeId);
57
+ if (attrs.label)
58
+ node.label = attrs.label;
59
+ if (attrs.check)
60
+ node.check = attrs.check;
61
+ // Store remaining attrs as metadata
62
+ const { label: _l, check: _c, ...rest } = attrs;
63
+ if (Object.keys(rest).length > 0)
64
+ node.metadata = rest;
65
+ continue;
66
+ }
67
+ // Chained edge: A -> B -> C
68
+ const chainMatch = stmt.match(/^[\w\s"-]+(?:\s*->\s*[\w\s"-]+)+$/);
69
+ if (chainMatch) {
70
+ const parts = stmt.split("->").map((p) => p.trim().replace(/^"|"$/g, ""));
71
+ for (let i = 0; i < parts.length - 1; i++) {
72
+ const fromId = parts[i]?.trim();
73
+ const toId = parts[i + 1]?.trim();
74
+ ensureNode(nodesMap, fromId);
75
+ ensureNode(nodesMap, toId);
76
+ edges.push({ from: fromId, to: toId });
77
+ }
78
+ }
79
+ }
80
+ if (nodesMap.size === 0) {
81
+ throw new WorkflowParseError("No nodes found in digraph");
82
+ }
83
+ return {
84
+ name: graphName,
85
+ nodes: Array.from(nodesMap.values()),
86
+ edges,
87
+ format: "dot",
88
+ };
89
+ }
90
+ // ── Mermaid Parser ──────────────────────────────────────────────────────────
91
+ /**
92
+ * Parse a Mermaid flowchart string into a WorkflowGraph.
93
+ * Supports subset: `flowchart LR/TD`, `A --> B`, `A[Label]`, `A -->|label| B`
94
+ */
95
+ export function parseMermaid(content, sourceName = "untitled") {
96
+ const lines = content
97
+ .split("\n")
98
+ .map((l) => l.trim())
99
+ .filter((l) => l.length > 0);
100
+ if (lines.length === 0) {
101
+ throw new WorkflowParseError("Empty mermaid file");
102
+ }
103
+ // First line must be flowchart directive
104
+ const headerMatch = lines[0]?.match(/^(?:flowchart|graph)\s+(LR|TD|TB|RL|BT)/i);
105
+ if (!headerMatch) {
106
+ throw new WorkflowParseError('Expected "flowchart LR|TD" or "graph LR|TD" at first line', 1);
107
+ }
108
+ const nodesMap = new Map();
109
+ const edges = [];
110
+ for (let i = 1; i < lines.length; i++) {
111
+ const line = lines[i];
112
+ // Skip comments and style directives
113
+ if (line.startsWith("%%") ||
114
+ line.startsWith("style") ||
115
+ line.startsWith("classDef")) {
116
+ continue;
117
+ }
118
+ // Edge with label: A -->|label| B or A -- label --> B
119
+ const edgeLabelMatch = line.match(/^(\w[\w-]*?)(?:\[([^\]]+)\])?\s*-->?\|([^|]+)\|\s*(\w[\w-]*?)(?:\[([^\]]+)\])?\s*$/);
120
+ if (edgeLabelMatch) {
121
+ const fromId = edgeLabelMatch[1];
122
+ const fromLabel = edgeLabelMatch[2];
123
+ const edgeLabel = edgeLabelMatch[3]?.trim();
124
+ const toId = edgeLabelMatch[4];
125
+ const toLabel = edgeLabelMatch[5];
126
+ const fromNode = ensureNode(nodesMap, fromId);
127
+ if (fromLabel)
128
+ fromNode.label = fromLabel;
129
+ const toNode = ensureNode(nodesMap, toId);
130
+ if (toLabel)
131
+ toNode.label = toLabel;
132
+ edges.push({ from: fromId, to: toId, label: edgeLabel });
133
+ continue;
134
+ }
135
+ // Simple edge: A --> B (possibly chained: A --> B --> C)
136
+ // Also handles node labels: A[Label] --> B[Label]
137
+ const edgeMatch = line.match(/(\w[\w-]*?)(?:\[([^\]]+)\])?\s*-->?\s*/);
138
+ if (edgeMatch && line.includes("-->")) {
139
+ const segments = line.split(/\s*-->\s*/);
140
+ for (let s = 0; s < segments.length; s++) {
141
+ const seg = segments[s]?.trim();
142
+ const segMatch = seg.match(/^(\w[\w-]*)(?:\[([^\]]+)\])?$/);
143
+ if (segMatch) {
144
+ const nodeId = segMatch[1];
145
+ const nodeLabel = segMatch[2];
146
+ const node = ensureNode(nodesMap, nodeId);
147
+ if (nodeLabel)
148
+ node.label = nodeLabel;
149
+ }
150
+ }
151
+ // Create edges between consecutive segments
152
+ for (let s = 0; s < segments.length - 1; s++) {
153
+ const fromSeg = segments[s]?.trim().match(/^(\w[\w-]*)/);
154
+ const toSeg = segments[s + 1]?.trim().match(/^(\w[\w-]*)/);
155
+ if (fromSeg && toSeg) {
156
+ edges.push({ from: fromSeg[1], to: toSeg[1] });
157
+ }
158
+ }
159
+ continue;
160
+ }
161
+ // Standalone node with label: A[Label]
162
+ const nodeMatch = line.match(/^(\w[\w-]*)\[([^\]]+)\]\s*$/);
163
+ if (nodeMatch) {
164
+ const node = ensureNode(nodesMap, nodeMatch[1]);
165
+ node.label = nodeMatch[2];
166
+ }
167
+ }
168
+ if (nodesMap.size === 0) {
169
+ throw new WorkflowParseError("No nodes found in flowchart");
170
+ }
171
+ return {
172
+ name: sourceName,
173
+ nodes: Array.from(nodesMap.values()),
174
+ edges,
175
+ format: "mermaid",
176
+ };
177
+ }
178
+ // ── Helpers ─────────────────────────────────────────────────────────────────
179
+ function ensureNode(nodesMap, id) {
180
+ let node = nodesMap.get(id);
181
+ if (!node) {
182
+ node = { id, label: id };
183
+ nodesMap.set(id, node);
184
+ }
185
+ return node;
186
+ }
187
+ function parseAttributes(attrStr) {
188
+ const attrs = {};
189
+ // Match key="value" or key=value pairs
190
+ const regex = /(\w+)\s*=\s*(?:"([^"]*)"|(\S+))/g;
191
+ let match;
192
+ // biome-ignore lint/suspicious/noAssignInExpressions: idiomatic regex exec loop
193
+ while ((match = regex.exec(attrStr)) !== null) {
194
+ attrs[match[1]] = match[2] ?? match[3] ?? "";
195
+ }
196
+ return attrs;
197
+ }
198
+ //# sourceMappingURL=parser.js.map
@@ -0,0 +1,9 @@
1
+ import type { WorkflowGraph, WorkflowValidationResult } from "../../types/index.js";
2
+ /**
3
+ * Render a WorkflowGraph as ASCII art.
4
+ * Linear pipelines render as: [lint] -> [test] -> [build]
5
+ * Branching renders with indentation.
6
+ * Optionally overlays validation results with pass/fail icons.
7
+ */
8
+ export declare function renderAscii(graph: WorkflowGraph, validationResults?: WorkflowValidationResult[]): string;
9
+ //# sourceMappingURL=renderer.d.ts.map
@@ -0,0 +1,152 @@
1
+ /**
2
+ * Render a WorkflowGraph as ASCII art.
3
+ * Linear pipelines render as: [lint] -> [test] -> [build]
4
+ * Branching renders with indentation.
5
+ * Optionally overlays validation results with pass/fail icons.
6
+ */
7
+ export function renderAscii(graph, validationResults) {
8
+ if (graph.nodes.length === 0)
9
+ return "(empty graph)";
10
+ const resultMap = new Map();
11
+ if (validationResults) {
12
+ for (const r of validationResults) {
13
+ resultMap.set(r.node, r);
14
+ }
15
+ }
16
+ // Build adjacency and in-degree
17
+ const adjacency = new Map();
18
+ const inDegree = new Map();
19
+ for (const node of graph.nodes) {
20
+ adjacency.set(node.id, []);
21
+ inDegree.set(node.id, 0);
22
+ }
23
+ for (const edge of graph.edges) {
24
+ const children = adjacency.get(edge.from);
25
+ if (children)
26
+ children.push(edge.to);
27
+ inDegree.set(edge.to, (inDegree.get(edge.to) ?? 0) + 1);
28
+ }
29
+ // Find roots (in-degree 0)
30
+ const roots = graph.nodes
31
+ .filter((n) => (inDegree.get(n.id) ?? 0) === 0)
32
+ .map((n) => n.id);
33
+ if (roots.length === 0) {
34
+ // Cycle — just list all nodes
35
+ return graph.nodes
36
+ .map((n) => formatNode(n.id, n.label, resultMap))
37
+ .join(" -> ");
38
+ }
39
+ // Topological order using Kahn's algorithm
40
+ const queue = [...roots];
41
+ const ordered = [];
42
+ const visited = new Set();
43
+ while (queue.length > 0) {
44
+ const current = queue.shift();
45
+ if (visited.has(current))
46
+ continue;
47
+ visited.add(current);
48
+ ordered.push(current);
49
+ const children = adjacency.get(current) ?? [];
50
+ for (const child of children) {
51
+ const deg = (inDegree.get(child) ?? 1) - 1;
52
+ inDegree.set(child, deg);
53
+ if (deg <= 0 && !visited.has(child)) {
54
+ queue.push(child);
55
+ }
56
+ }
57
+ }
58
+ // Add any unvisited nodes (disconnected)
59
+ for (const node of graph.nodes) {
60
+ if (!visited.has(node.id))
61
+ ordered.push(node.id);
62
+ }
63
+ // Build label map
64
+ const labelMap = new Map();
65
+ for (const node of graph.nodes) {
66
+ labelMap.set(node.id, node.label);
67
+ }
68
+ // Build edge label map
69
+ const edgeLabelMap = new Map();
70
+ for (const edge of graph.edges) {
71
+ const key = `${edge.from}->${edge.to}`;
72
+ if (edge.label)
73
+ edgeLabelMap.set(key, edge.label);
74
+ }
75
+ // Render: try linear first, fall back to tree-like
76
+ const lines = [];
77
+ lines.push(`# ${graph.name}`);
78
+ lines.push("");
79
+ // Check if it's a simple linear chain
80
+ const isLinear = graph.nodes.every((n) => {
81
+ const children = adjacency.get(n.id) ?? [];
82
+ return children.length <= 1;
83
+ }) && roots.length === 1;
84
+ if (isLinear) {
85
+ const chain = [];
86
+ let current = roots[0];
87
+ while (current) {
88
+ chain.push(formatNode(current, labelMap.get(current) ?? current, resultMap));
89
+ const children = adjacency.get(current) ?? [];
90
+ const next = children[0];
91
+ if (next) {
92
+ const edgeKey = `${current}->${next}`;
93
+ const edgeLabel = edgeLabelMap.get(edgeKey);
94
+ if (edgeLabel) {
95
+ chain.push(`--(${edgeLabel})-->`);
96
+ }
97
+ }
98
+ current = next;
99
+ }
100
+ lines.push(chain.join(" -> ").replace(/ -> --/g, " --"));
101
+ }
102
+ else {
103
+ // Tree-like rendering
104
+ for (const nodeId of ordered) {
105
+ const children = adjacency.get(nodeId) ?? [];
106
+ const nodeStr = formatNode(nodeId, labelMap.get(nodeId) ?? nodeId, resultMap);
107
+ if (children.length === 0) {
108
+ lines.push(` ${nodeStr}`);
109
+ }
110
+ else {
111
+ const childStrs = children.map((c) => {
112
+ const edgeKey = `${nodeId}->${c}`;
113
+ const edgeLabel = edgeLabelMap.get(edgeKey);
114
+ const arrow = edgeLabel ? `--(${edgeLabel})-->` : "->";
115
+ return `${arrow} ${formatNode(c, labelMap.get(c) ?? c, resultMap)}`;
116
+ });
117
+ if (childStrs.length === 1) {
118
+ lines.push(` ${nodeStr} ${childStrs[0]}`);
119
+ }
120
+ else {
121
+ lines.push(` ${nodeStr}`);
122
+ for (const cs of childStrs) {
123
+ lines.push(` ${cs}`);
124
+ }
125
+ }
126
+ }
127
+ }
128
+ }
129
+ // Add validation legend if results are present
130
+ if (validationResults && validationResults.length > 0) {
131
+ lines.push("");
132
+ lines.push("---");
133
+ for (const r of validationResults) {
134
+ const icon = r.status === "pass" ? "\u2713" : r.status === "fail" ? "\u2717" : "-";
135
+ lines.push(` ${icon} ${r.node}${r.detail ? `: ${r.detail}` : ""}`);
136
+ }
137
+ }
138
+ return lines.join("\n");
139
+ }
140
+ function formatNode(id, label, resultMap) {
141
+ const result = resultMap.get(id);
142
+ if (result) {
143
+ const icon = result.status === "pass"
144
+ ? "\u2713"
145
+ : result.status === "fail"
146
+ ? "\u2717"
147
+ : "?";
148
+ return `[${icon} ${label}]`;
149
+ }
150
+ return `[${label}]`;
151
+ }
152
+ //# sourceMappingURL=renderer.js.map
@@ -0,0 +1,10 @@
1
+ import type { WorkflowGraph, WorkflowValidationResult } from "../../types/index.js";
2
+ /**
3
+ * Validate a project directory against a workflow graph.
4
+ * Nodes with a `check` attribute are evaluated against built-in checks.
5
+ * Nodes without a `check` attribute are skipped.
6
+ */
7
+ export declare function validateWorkflow(graph: WorkflowGraph, projectDir: string): Promise<WorkflowValidationResult[]>;
8
+ /** Expose check names for testing and documentation */
9
+ export declare function getAvailableChecks(): string[];
10
+ //# sourceMappingURL=validator.d.ts.map