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,297 @@
1
+ /**
2
+ * Versioned mixed-runner CI config (`.javi-forge/ci.yaml`) — schema and loader.
3
+ *
4
+ * Hybrid repositories declare every required runner explicitly and in order.
5
+ * Single-stack repositories need no config: auto-detection stays the default.
6
+ *
7
+ * Validation is fail-closed: any schema violation produces a CIConfigError
8
+ * listing every problem, and no partially-valid config is ever returned.
9
+ * YAML parsing uses the `yaml` package, already a runtime dependency.
10
+ */
11
+ import path from "node:path";
12
+ import fs from "fs-extra";
13
+ import YAML from "yaml";
14
+ // =============================================================================
15
+ // Types
16
+ // =============================================================================
17
+ export const CI_CONFIG_VERSION = 1;
18
+ /** Default config locations, in discovery order, relative to the project root. */
19
+ export const CI_CONFIG_CANDIDATES = [
20
+ ".javi-forge/ci.yaml",
21
+ ".javi-forge/ci.yml",
22
+ ];
23
+ export class CIConfigError extends Error {
24
+ errors;
25
+ constructor(errors, source) {
26
+ const where = source ? ` in ${source}` : "";
27
+ const lines = errors.map((e) => ` - ${e.path}: ${e.message}`).join("\n");
28
+ super(`Invalid CI config${where}:\n${lines}`);
29
+ this.name = "CIConfigError";
30
+ this.errors = errors;
31
+ }
32
+ }
33
+ // =============================================================================
34
+ // Validation
35
+ // =============================================================================
36
+ const VALID_STACKS = [
37
+ "node",
38
+ "python",
39
+ "go",
40
+ "rust",
41
+ "java-gradle",
42
+ "java-maven",
43
+ "elixir",
44
+ ];
45
+ /** Stacks accepted by the CI config schema and the --stack override. */
46
+ export const CI_STACKS = VALID_STACKS;
47
+ const TOP_LEVEL_FIELDS = new Set(["version", "runners"]);
48
+ /** Runner names become docker image tags — keep them tag-safe. */
49
+ const RUNNER_NAME_RE = /^[a-zA-Z0-9_][a-zA-Z0-9._-]*$/;
50
+ /** Required tools are checked via `command -v <tool>` — no shell metachars. */
51
+ const TOOL_NAME_RE = /^[a-zA-Z0-9][a-zA-Z0-9._+~-]*$/;
52
+ const RUNNER_FIELDS = new Set([
53
+ "name",
54
+ "stack",
55
+ "directory",
56
+ "image",
57
+ "build-context",
58
+ "setup",
59
+ "lint",
60
+ "build",
61
+ "test",
62
+ "security",
63
+ "requires",
64
+ ]);
65
+ function isRecord(value) {
66
+ return typeof value === "object" && value !== null && !Array.isArray(value);
67
+ }
68
+ function normalizeCommands(value, fieldPath, errors) {
69
+ if (value === undefined)
70
+ return [];
71
+ if (typeof value === "string")
72
+ return value.trim() ? [value] : [];
73
+ if (Array.isArray(value)) {
74
+ const bad = value.some((v) => typeof v !== "string" || !v.trim());
75
+ if (!bad)
76
+ return value;
77
+ }
78
+ errors.push({
79
+ path: fieldPath,
80
+ message: "must be a non-empty string or a list of non-empty strings",
81
+ });
82
+ return [];
83
+ }
84
+ function normalizeStringList(value, fieldPath, errors) {
85
+ if (value === undefined)
86
+ return [];
87
+ if (Array.isArray(value) && value.every((v) => typeof v === "string")) {
88
+ return value;
89
+ }
90
+ errors.push({ path: fieldPath, message: "must be a list of strings" });
91
+ return [];
92
+ }
93
+ function validateRunner(raw, index, errors) {
94
+ const base = `runners[${index}]`;
95
+ if (!isRecord(raw)) {
96
+ errors.push({ path: base, message: "runner must be an object" });
97
+ return null;
98
+ }
99
+ for (const key of Object.keys(raw)) {
100
+ if (!RUNNER_FIELDS.has(key)) {
101
+ errors.push({
102
+ path: `${base}.${key}`,
103
+ message: `unknown field "${key}"`,
104
+ });
105
+ }
106
+ }
107
+ const name = raw.name;
108
+ if (typeof name !== "string" || !name.trim()) {
109
+ errors.push({
110
+ path: `${base}.name`,
111
+ message: "name is required and must be a non-empty string",
112
+ });
113
+ }
114
+ else if (!RUNNER_NAME_RE.test(name.trim())) {
115
+ errors.push({
116
+ path: `${base}.name`,
117
+ message: "name must start with a letter, digit or underscore and contain only [a-zA-Z0-9._-] (it is used as a docker image tag)",
118
+ });
119
+ }
120
+ const stack = raw.stack;
121
+ if (typeof stack !== "string" || !VALID_STACKS.includes(stack)) {
122
+ errors.push({
123
+ path: `${base}.stack`,
124
+ message: `stack is required and must be one of: ${VALID_STACKS.join(", ")}`,
125
+ });
126
+ }
127
+ let directory = ".";
128
+ if (raw.directory !== undefined) {
129
+ if (typeof raw.directory !== "string" || !raw.directory.trim()) {
130
+ errors.push({
131
+ path: `${base}.directory`,
132
+ message: "directory must be a non-empty string",
133
+ });
134
+ }
135
+ else {
136
+ const normalized = path.posix.normalize(raw.directory);
137
+ if (path.isAbsolute(raw.directory) ||
138
+ normalized === ".." ||
139
+ normalized.startsWith("../")) {
140
+ errors.push({
141
+ path: `${base}.directory`,
142
+ message: "directory must stay inside the project root",
143
+ });
144
+ }
145
+ else {
146
+ directory = normalized;
147
+ }
148
+ }
149
+ }
150
+ let image;
151
+ if (raw.image !== undefined) {
152
+ if (typeof raw.image !== "string" || !raw.image.trim()) {
153
+ errors.push({
154
+ path: `${base}.image`,
155
+ message: "image must be a non-empty string",
156
+ });
157
+ }
158
+ else {
159
+ image = raw.image;
160
+ }
161
+ }
162
+ let buildContext;
163
+ if (raw["build-context"] !== undefined) {
164
+ if (typeof raw["build-context"] !== "string" ||
165
+ !raw["build-context"].trim()) {
166
+ errors.push({
167
+ path: `${base}.build-context`,
168
+ message: "build-context must be a non-empty string (directory)",
169
+ });
170
+ }
171
+ else {
172
+ buildContext = raw["build-context"];
173
+ }
174
+ }
175
+ if (image && buildContext) {
176
+ errors.push({
177
+ path: `${base}.image`,
178
+ message: "image and build-context are mutually exclusive — set only one",
179
+ });
180
+ }
181
+ const requires = normalizeStringList(raw.requires, `${base}.requires`, errors);
182
+ for (const tool of requires) {
183
+ if (!TOOL_NAME_RE.test(tool)) {
184
+ errors.push({
185
+ path: `${base}.requires`,
186
+ message: `requires entry "${tool}" has unsafe characters — expected a plain tool name like python, ruff or node`,
187
+ });
188
+ }
189
+ }
190
+ // If anything failed, the caller discards all runners (fail closed).
191
+ return {
192
+ name: typeof name === "string" ? name.trim() : "",
193
+ stack: stack,
194
+ directory,
195
+ image,
196
+ buildContext,
197
+ setup: normalizeCommands(raw.setup, `${base}.setup`, errors),
198
+ lint: normalizeCommands(raw.lint, `${base}.lint`, errors),
199
+ build: normalizeCommands(raw.build, `${base}.build`, errors),
200
+ test: normalizeCommands(raw.test, `${base}.test`, errors),
201
+ security: normalizeCommands(raw.security, `${base}.security`, errors),
202
+ requires,
203
+ };
204
+ }
205
+ // =============================================================================
206
+ // Public API
207
+ // =============================================================================
208
+ /**
209
+ * Parse and validate CI config YAML text. Throws CIConfigError listing every
210
+ * validation problem; never returns a partially valid config.
211
+ */
212
+ export function parseCIConfig(rawYaml, source) {
213
+ let doc;
214
+ try {
215
+ doc = YAML.parse(rawYaml);
216
+ }
217
+ catch (e) {
218
+ throw new CIConfigError([
219
+ {
220
+ path: "<document>",
221
+ message: `invalid YAML: ${e instanceof Error ? e.message.split("\n")[0] : String(e)}`,
222
+ },
223
+ ], source);
224
+ }
225
+ const errors = [];
226
+ if (!isRecord(doc)) {
227
+ throw new CIConfigError([{ path: "<document>", message: "config must be a YAML object/mapping" }], source);
228
+ }
229
+ for (const key of Object.keys(doc)) {
230
+ if (!TOP_LEVEL_FIELDS.has(key)) {
231
+ errors.push({ path: key, message: `unknown field "${key}"` });
232
+ }
233
+ }
234
+ if (doc.version !== CI_CONFIG_VERSION) {
235
+ errors.push({
236
+ path: "version",
237
+ message: `version is required and must be the number ${CI_CONFIG_VERSION}`,
238
+ });
239
+ }
240
+ if (!Array.isArray(doc.runners) || doc.runners.length === 0) {
241
+ errors.push({
242
+ path: "runners",
243
+ message: "runners is required and must be a non-empty list",
244
+ });
245
+ }
246
+ const runners = [];
247
+ if (Array.isArray(doc.runners)) {
248
+ doc.runners.forEach((raw, index) => {
249
+ const runner = validateRunner(raw, index, errors);
250
+ if (runner)
251
+ runners.push(runner);
252
+ });
253
+ const seen = new Set();
254
+ for (const runner of runners) {
255
+ if (!runner.name)
256
+ continue;
257
+ if (seen.has(runner.name)) {
258
+ errors.push({
259
+ path: "runners",
260
+ message: `duplicate runner name "${runner.name}"`,
261
+ });
262
+ }
263
+ seen.add(runner.name);
264
+ }
265
+ }
266
+ if (errors.length > 0) {
267
+ throw new CIConfigError(errors, source);
268
+ }
269
+ return { version: CI_CONFIG_VERSION, runners };
270
+ }
271
+ /**
272
+ * Load and validate a CI config file. Fails closed: a missing file or any
273
+ * validation error throws — no config is ever silently ignored.
274
+ */
275
+ export async function loadCIConfig(configPath) {
276
+ if (!(await fs.pathExists(configPath))) {
277
+ throw new CIConfigError([
278
+ { path: configPath, message: "config file not found" },
279
+ ]);
280
+ }
281
+ const raw = await fs.readFile(configPath, "utf-8");
282
+ return parseCIConfig(raw, configPath);
283
+ }
284
+ /**
285
+ * Discover the default CI config for a project directory.
286
+ * Returns the config path, or null when the project has no config
287
+ * (single-stack auto-detection stays the default).
288
+ */
289
+ export async function findCIConfig(projectDir) {
290
+ for (const candidate of CI_CONFIG_CANDIDATES) {
291
+ const candidatePath = path.join(projectDir, candidate);
292
+ if (await fs.pathExists(candidatePath))
293
+ return candidatePath;
294
+ }
295
+ return null;
296
+ }
297
+ //# sourceMappingURL=ci-config.js.map
@@ -1,8 +1,20 @@
1
- import type { InitOptions, StackClaudeMdEntry } from '../types/index.js';
1
+ import type { InitOptions, StackClaudeMdEntry } from "../types/index.js";
2
+ import type { StackDetectionResult } from "./stack-detector.js";
2
3
  export declare function buildClaudeMd(projectName: string, stack: string, entry: StackClaudeMdEntry, contextDir: boolean, modules: string[]): string;
4
+ /**
5
+ * Build an enriched CLAUDE.md using detected skills, architecture patterns,
6
+ * and plugin instructions. Pure function — no I/O.
7
+ */
8
+ export declare function buildSmartClaudeMd(projectName: string, stack: string, entry: StackClaudeMdEntry, detectedSkills: string[], contextDir: boolean, modules: string[]): string;
3
9
  /**
4
10
  * Generate CLAUDE.md content from InitOptions metadata.
5
11
  * Pure function — does NOT perform filesystem I/O.
6
12
  */
7
13
  export declare function generateClaudeMd(options: InitOptions): string;
14
+ /**
15
+ * Generate a project-aware CLAUDE.md using stack detection results.
16
+ * Falls back to static generation if no detection result provided.
17
+ * Pure function — does NOT perform filesystem I/O.
18
+ */
19
+ export declare function generateSmartClaudeMd(options: InitOptions, detection: StackDetectionResult | null): string;
8
20
  //# sourceMappingURL=claudemd.d.ts.map
@@ -1,47 +1,170 @@
1
- import { STACK_CLAUDEMD_MAP } from '../constants.js';
1
+ import { STACK_CLAUDEMD_MAP } from "../constants.js";
2
+ // =============================================================================
3
+ // Architecture pattern mapping (signal → patterns)
4
+ // =============================================================================
5
+ const ARCHITECTURE_PATTERNS = {
6
+ "react-19": [
7
+ "Container-Presentational pattern",
8
+ "Atomic Design for component hierarchy",
9
+ ],
10
+ "nextjs-15": [
11
+ "App Router file-based routing",
12
+ "Server Components by default, client opt-in",
13
+ ],
14
+ "django-drf": ["ViewSets + Serializers", "URL namespace per app"],
15
+ "zustand-5": [
16
+ "Slice pattern for store modules",
17
+ "Selectors for derived state",
18
+ ],
19
+ typescript: [
20
+ "Strict mode enabled",
21
+ "Prefer interfaces for public APIs, types for unions",
22
+ ],
23
+ "tailwind-4": ["Utility-first CSS", "Use `cn()` for conditional classes"],
24
+ "zod-4": [
25
+ "Schema-first validation",
26
+ "Infer types from schemas with `z.infer`",
27
+ ],
28
+ playwright: [
29
+ "Page Object Model for E2E tests",
30
+ "Locator-based selectors over CSS",
31
+ ],
32
+ pytest: ["Fixtures for setup/teardown", "Parametrize for data-driven tests"],
33
+ "ai-sdk-5": [
34
+ "Streaming responses by default",
35
+ "Tool calling with structured schemas",
36
+ ],
37
+ };
38
+ // =============================================================================
39
+ // Plugin instruction mapping (detected skill → plugin hints)
40
+ // =============================================================================
41
+ const PLUGIN_INSTRUCTIONS = {
42
+ "react-19": "Check `~/.claude/plugins/` for merge-checks and mermaid diagram support",
43
+ "nextjs-15": "Check `~/.claude/plugins/` for merge-checks and mermaid diagram support",
44
+ typescript: "Run `javi-forge skills doctor` to validate skill compatibility",
45
+ playwright: "Use `javi-forge tdd` for test-driven development workflow",
46
+ };
2
47
  // =============================================================================
3
48
  // Internal helpers
4
49
  // =============================================================================
5
50
  function getStackClaudeMd(stack) {
6
- return STACK_CLAUDEMD_MAP[stack] ?? STACK_CLAUDEMD_MAP['default'];
51
+ return STACK_CLAUDEMD_MAP[stack] ?? STACK_CLAUDEMD_MAP.default;
7
52
  }
8
53
  export function buildClaudeMd(projectName, stack, entry, contextDir, modules) {
9
54
  const lines = [];
10
55
  lines.push(`# ${projectName}`);
11
- lines.push('');
56
+ lines.push("");
12
57
  // Stack section
13
- lines.push('## Stack');
58
+ lines.push("## Stack");
14
59
  lines.push(`- **Language/Runtime**: ${stack}`);
15
60
  lines.push(`- **Conventions**: ${entry.conventions}`);
16
61
  lines.push(`- **Testing**: ${entry.testFramework}`);
17
- lines.push('');
62
+ lines.push("");
18
63
  // Skills section
19
64
  if (entry.skills.length > 0) {
20
- lines.push('## Recommended Skills');
65
+ lines.push("## Recommended Skills");
21
66
  for (const skill of entry.skills) {
22
67
  lines.push(`- Load \`~/.claude/skills/${skill}/SKILL.md\` before writing code`);
23
68
  }
24
- lines.push('');
69
+ lines.push("");
25
70
  }
26
71
  // Project Context section (conditional on contextDir)
27
72
  if (contextDir) {
28
- lines.push('## Project Context');
29
- lines.push('- See `.context/INDEX.md` for file structure and entry points');
30
- lines.push('- See `.context/summary.md` for project overview');
31
- lines.push('');
73
+ lines.push("## Project Context");
74
+ lines.push("- See `.context/INDEX.md` for file structure and entry points");
75
+ lines.push("- See `.context/summary.md` for project overview");
76
+ lines.push("");
32
77
  }
33
78
  // Conventions section
34
- lines.push('## Conventions');
79
+ lines.push("## Conventions");
35
80
  lines.push(`- ${entry.conventions}`);
36
- lines.push('- Scaffolded with javi-forge — see `.javi-forge/manifest.json` for config');
37
- lines.push('');
81
+ lines.push("- Scaffolded with javi-forge — see `.javi-forge/manifest.json` for config");
82
+ lines.push("");
38
83
  // Modules section
39
84
  if (modules.length > 0) {
40
- lines.push('## Modules');
41
- lines.push(`- ${modules.join(', ')}`);
42
- lines.push('');
85
+ lines.push("## Modules");
86
+ lines.push(`- ${modules.join(", ")}`);
87
+ lines.push("");
43
88
  }
44
- return lines.join('\n');
89
+ return lines.join("\n");
90
+ }
91
+ /**
92
+ * Build an enriched CLAUDE.md using detected skills, architecture patterns,
93
+ * and plugin instructions. Pure function — no I/O.
94
+ */
95
+ export function buildSmartClaudeMd(projectName, stack, entry, detectedSkills, contextDir, modules) {
96
+ const lines = [];
97
+ // Merge: detected skills + static entry skills (deduplicated)
98
+ const allSkills = [...new Set([...detectedSkills, ...entry.skills])].sort();
99
+ lines.push(`# ${projectName}`);
100
+ lines.push("");
101
+ // Stack section
102
+ lines.push("## Stack");
103
+ lines.push(`- **Language/Runtime**: ${stack}`);
104
+ lines.push(`- **Conventions**: ${entry.conventions}`);
105
+ lines.push(`- **Testing**: ${entry.testFramework}`);
106
+ lines.push("");
107
+ // Skills section — auto-detected + static
108
+ if (allSkills.length > 0) {
109
+ lines.push("## Skills (auto-detected)");
110
+ lines.push("");
111
+ lines.push("Load these skill files BEFORE writing code in this project:");
112
+ lines.push("");
113
+ for (const skill of allSkills) {
114
+ lines.push(`- \`~/.claude/skills/${skill}/SKILL.md\``);
115
+ }
116
+ lines.push("");
117
+ }
118
+ // Architecture patterns section — derived from detected skills
119
+ const patterns = [];
120
+ for (const skill of allSkills) {
121
+ const skillPatterns = ARCHITECTURE_PATTERNS[skill];
122
+ if (skillPatterns) {
123
+ patterns.push(...skillPatterns);
124
+ }
125
+ }
126
+ if (patterns.length > 0) {
127
+ lines.push("## Architecture Patterns");
128
+ lines.push("");
129
+ for (const pattern of patterns) {
130
+ lines.push(`- ${pattern}`);
131
+ }
132
+ lines.push("");
133
+ }
134
+ // Project Context section
135
+ if (contextDir) {
136
+ lines.push("## Project Context");
137
+ lines.push("- See `.context/INDEX.md` for file structure and entry points");
138
+ lines.push("- See `.context/summary.md` for project overview");
139
+ lines.push("");
140
+ }
141
+ // Conventions section
142
+ lines.push("## Conventions");
143
+ lines.push(`- ${entry.conventions}`);
144
+ lines.push("- Scaffolded with javi-forge — see `.javi-forge/manifest.json` for config");
145
+ lines.push("");
146
+ // Plugin instructions — derived from detected skills
147
+ const pluginHints = new Set();
148
+ for (const skill of allSkills) {
149
+ const hint = PLUGIN_INSTRUCTIONS[skill];
150
+ if (hint)
151
+ pluginHints.add(hint);
152
+ }
153
+ if (pluginHints.size > 0) {
154
+ lines.push("## Plugins");
155
+ lines.push("");
156
+ for (const hint of pluginHints) {
157
+ lines.push(`- ${hint}`);
158
+ }
159
+ lines.push("");
160
+ }
161
+ // Modules section
162
+ if (modules.length > 0) {
163
+ lines.push("## Modules");
164
+ lines.push(`- ${modules.join(", ")}`);
165
+ lines.push("");
166
+ }
167
+ return lines.join("\n");
45
168
  }
46
169
  // =============================================================================
47
170
  // Public API
@@ -56,17 +179,44 @@ export function generateClaudeMd(options) {
56
179
  // Collect enabled modules for listing
57
180
  const modules = [];
58
181
  if (options.aiSync)
59
- modules.push('ai-sync');
182
+ modules.push("ai-sync");
60
183
  if (options.sdd)
61
- modules.push('sdd');
184
+ modules.push("sdd");
62
185
  if (options.ghagga)
63
- modules.push('ghagga');
186
+ modules.push("ghagga");
64
187
  if (options.mock)
65
- modules.push('mock');
188
+ modules.push("mock");
66
189
  if (options.contextDir)
67
- modules.push('context');
190
+ modules.push("context");
68
191
  if (options.claudeMd)
69
- modules.push('claude-md');
192
+ modules.push("claude-md");
70
193
  return buildClaudeMd(projectName, stack, entry, contextDir, modules);
71
194
  }
195
+ /**
196
+ * Generate a project-aware CLAUDE.md using stack detection results.
197
+ * Falls back to static generation if no detection result provided.
198
+ * Pure function — does NOT perform filesystem I/O.
199
+ */
200
+ export function generateSmartClaudeMd(options, detection) {
201
+ // Fallback: no detection result, use static generation
202
+ if (!detection || detection.recommendedSkills.length === 0) {
203
+ return generateClaudeMd(options);
204
+ }
205
+ const { projectName, stack, contextDir } = options;
206
+ const entry = getStackClaudeMd(stack);
207
+ const modules = [];
208
+ if (options.aiSync)
209
+ modules.push("ai-sync");
210
+ if (options.sdd)
211
+ modules.push("sdd");
212
+ if (options.ghagga)
213
+ modules.push("ghagga");
214
+ if (options.mock)
215
+ modules.push("mock");
216
+ if (options.contextDir)
217
+ modules.push("context");
218
+ if (options.claudeMd)
219
+ modules.push("claude-md");
220
+ return buildSmartClaudeMd(projectName, stack, entry, detection.recommendedSkills, contextDir, modules);
221
+ }
72
222
  //# sourceMappingURL=claudemd.js.map
@@ -1,4 +1,4 @@
1
- import type { CodexTomlEntry, CodexExportResult } from '../types/index.js';
1
+ import type { CodexExportResult, CodexTomlEntry } from "../types/index.js";
2
2
  /**
3
3
  * Convert a SKILL.md raw content string into a CodexTomlEntry.
4
4
  * Returns null if the file has no valid frontmatter or missing name.