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,4 +1,4 @@
1
- import type { PluginValidationResult, InstalledPlugin, PluginRegistryEntry, PluginSyncResult } from '../types/index.js';
1
+ import type { InstalledPlugin, PluginRegistryEntry, PluginSyncResult, PluginValidationResult } from "../types/index.js";
2
2
  /**
3
3
  * Validate a plugin directory structure and manifest.
4
4
  */
@@ -37,8 +37,14 @@ export declare function searchRegistry(query?: string): Promise<PluginRegistryEn
37
37
  */
38
38
  export declare function detectProjectPlugins(projectDir: string): Promise<string[]>;
39
39
  /**
40
- * Sync detected plugins into the project manifest.
41
- * Returns a report of added, removed, and unchanged plugins.
40
+ * Detect installed plugins with full metadata (including manifest).
41
+ * Used by auto-wiring to read plugin capabilities.
42
+ */
43
+ export declare function detectProjectPluginsFull(projectDir: string): Promise<InstalledPlugin[]>;
44
+ /**
45
+ * Sync detected plugins into the project manifest and auto-wire
46
+ * their capabilities into CLAUDE.md and .claude/settings.json.
47
+ * Returns a report of added, removed, unchanged, wired, and unwired plugins.
42
48
  */
43
49
  export declare function syncPlugins(projectDir: string, options?: {
44
50
  dryRun?: boolean;
@@ -1,9 +1,10 @@
1
- import fs from 'fs-extra';
2
- import path from 'path';
3
- import { execFile } from 'child_process';
4
- import { promisify } from 'util';
5
- import { PLUGINS_DIR, PLUGIN_MANIFEST_FILE, PLUGIN_ASSET_DIRS, PLUGIN_REGISTRY_URL } from '../constants.js';
6
- import { generateAgentSkillsManifest } from './agent-skills.js';
1
+ import { execFile } from "child_process";
2
+ import fs from "fs-extra";
3
+ import path from "path";
4
+ import { promisify } from "util";
5
+ import { PLUGIN_ASSET_DIRS, PLUGIN_MANIFEST_FILE, PLUGIN_REGISTRY_URL, PLUGINS_DIR, } from "../constants.js";
6
+ import { generateAgentSkillsManifest } from "./agent-skills.js";
7
+ import { autoWirePlugins } from "./auto-wire.js";
7
8
  const execFileAsync = promisify(execFile);
8
9
  const KEBAB_RE = /^[a-z0-9]+(-[a-z0-9]+)*$/;
9
10
  const SEMVER_RE = /^\d+\.\d+\.\d+$/;
@@ -15,49 +16,60 @@ export async function validatePlugin(pluginDir) {
15
16
  const errors = [];
16
17
  // Check plugin.json exists
17
18
  const manifestPath = path.join(pluginDir, PLUGIN_MANIFEST_FILE);
18
- if (!await fs.pathExists(manifestPath)) {
19
+ if (!(await fs.pathExists(manifestPath))) {
19
20
  return {
20
21
  valid: false,
21
- errors: [{ path: PLUGIN_MANIFEST_FILE, message: 'plugin.json not found' }],
22
+ errors: [
23
+ { path: PLUGIN_MANIFEST_FILE, message: "plugin.json not found" },
24
+ ],
22
25
  manifest: null,
23
26
  };
24
27
  }
25
28
  // Parse manifest
26
29
  let manifest;
27
30
  try {
28
- manifest = await fs.readJson(manifestPath);
31
+ manifest = (await fs.readJson(manifestPath));
29
32
  }
30
33
  catch {
31
34
  return {
32
35
  valid: false,
33
- errors: [{ path: PLUGIN_MANIFEST_FILE, message: 'invalid JSON' }],
36
+ errors: [{ path: PLUGIN_MANIFEST_FILE, message: "invalid JSON" }],
34
37
  manifest: null,
35
38
  };
36
39
  }
37
40
  // Validate required fields
38
- if (typeof manifest.name !== 'string' || !manifest.name) {
39
- errors.push({ path: 'name', message: 'name is required' });
41
+ if (typeof manifest.name !== "string" || !manifest.name) {
42
+ errors.push({ path: "name", message: "name is required" });
40
43
  }
41
44
  else if (!KEBAB_RE.test(manifest.name)) {
42
- errors.push({ path: 'name', message: 'name must be kebab-case' });
45
+ errors.push({ path: "name", message: "name must be kebab-case" });
43
46
  }
44
47
  else if (manifest.name.length < 2 || manifest.name.length > 60) {
45
- errors.push({ path: 'name', message: 'name must be 2-60 characters' });
48
+ errors.push({ path: "name", message: "name must be 2-60 characters" });
46
49
  }
47
- if (typeof manifest.version !== 'string' || !manifest.version) {
48
- errors.push({ path: 'version', message: 'version is required' });
50
+ if (typeof manifest.version !== "string" || !manifest.version) {
51
+ errors.push({ path: "version", message: "version is required" });
49
52
  }
50
53
  else if (!SEMVER_RE.test(manifest.version)) {
51
- errors.push({ path: 'version', message: 'version must be semver (e.g. 1.0.0)' });
54
+ errors.push({
55
+ path: "version",
56
+ message: "version must be semver (e.g. 1.0.0)",
57
+ });
52
58
  }
53
- if (typeof manifest.description !== 'string' || !manifest.description) {
54
- errors.push({ path: 'description', message: 'description is required' });
59
+ if (typeof manifest.description !== "string" || !manifest.description) {
60
+ errors.push({ path: "description", message: "description is required" });
55
61
  }
56
62
  else if (manifest.description.length < 10) {
57
- errors.push({ path: 'description', message: 'description must be at least 10 characters' });
63
+ errors.push({
64
+ path: "description",
65
+ message: "description must be at least 10 characters",
66
+ });
58
67
  }
59
68
  else if (manifest.description.length > 200) {
60
- errors.push({ path: 'description', message: 'description must be at most 200 characters' });
69
+ errors.push({
70
+ path: "description",
71
+ message: "description must be at most 200 characters",
72
+ });
61
73
  }
62
74
  // Validate asset directories actually exist when declared
63
75
  for (const assetType of PLUGIN_ASSET_DIRS) {
@@ -65,26 +77,36 @@ export async function validatePlugin(pluginDir) {
65
77
  if (!Array.isArray(declared) || declared.length === 0)
66
78
  continue;
67
79
  const assetDir = path.join(pluginDir, assetType);
68
- if (!await fs.pathExists(assetDir)) {
69
- errors.push({ path: assetType, message: `declared ${assetType}/ directory not found` });
80
+ if (!(await fs.pathExists(assetDir))) {
81
+ errors.push({
82
+ path: assetType,
83
+ message: `declared ${assetType}/ directory not found`,
84
+ });
70
85
  continue;
71
86
  }
72
87
  // Check each declared asset exists
73
88
  for (const entry of declared) {
74
89
  const entryPath = path.join(assetDir, entry);
75
- if (!await fs.pathExists(entryPath)) {
76
- errors.push({ path: `${assetType}/${entry}`, message: `declared entry not found` });
90
+ if (!(await fs.pathExists(entryPath))) {
91
+ errors.push({
92
+ path: `${assetType}/${entry}`,
93
+ message: `declared entry not found`,
94
+ });
77
95
  }
78
96
  }
79
97
  }
80
98
  // Validate tags
81
99
  if (manifest.tags && !Array.isArray(manifest.tags)) {
82
- errors.push({ path: 'tags', message: 'tags must be an array' });
100
+ errors.push({ path: "tags", message: "tags must be an array" });
83
101
  }
84
102
  else if (manifest.tags && manifest.tags.length > 10) {
85
- errors.push({ path: 'tags', message: 'max 10 tags allowed' });
103
+ errors.push({ path: "tags", message: "max 10 tags allowed" });
86
104
  }
87
- return { valid: errors.length === 0, errors, manifest: errors.length === 0 ? manifest : manifest };
105
+ return {
106
+ valid: errors.length === 0,
107
+ errors,
108
+ manifest: errors.length === 0 ? manifest : manifest,
109
+ };
88
110
  }
89
111
  // ── Installation ────────────────────────────────────────────────────────────
90
112
  /**
@@ -96,23 +118,36 @@ export async function installPlugin(source, options = {}) {
96
118
  // Normalize source to a git URL
97
119
  const gitUrl = normalizeGitUrl(source);
98
120
  if (!gitUrl) {
99
- return { success: false, error: `invalid source: ${source}. Use org/repo or a GitHub URL` };
121
+ return {
122
+ success: false,
123
+ error: `invalid source: ${source}. Use org/repo or a GitHub URL`,
124
+ };
100
125
  }
101
126
  // Clone to temp
102
- const tmpDir = path.join(PLUGINS_DIR, '.tmp', `install-${Date.now()}`);
127
+ const tmpDir = path.join(PLUGINS_DIR, ".tmp", `install-${Date.now()}`);
103
128
  try {
104
129
  if (!dryRun) {
105
130
  await fs.ensureDir(tmpDir);
106
- await execFileAsync('git', ['clone', '--depth', '1', gitUrl, tmpDir], {
131
+ await execFileAsync("git", ["clone", "--depth", "1", gitUrl, tmpDir], {
107
132
  timeout: 60_000,
108
133
  });
109
134
  }
110
135
  // Validate
111
136
  const validation = dryRun
112
- ? { valid: true, errors: [], manifest: { name: source.split('/').pop() ?? 'unknown', version: '0.0.0', description: 'dry-run placeholder' } }
137
+ ? {
138
+ valid: true,
139
+ errors: [],
140
+ manifest: {
141
+ name: source.split("/").pop() ?? "unknown",
142
+ version: "0.0.0",
143
+ description: "dry-run placeholder",
144
+ },
145
+ }
113
146
  : await validatePlugin(tmpDir);
114
147
  if (!validation.valid || !validation.manifest) {
115
- const msgs = validation.errors.map(e => ` ${e.path}: ${e.message}`).join('\n');
148
+ const msgs = validation.errors
149
+ .map((e) => ` ${e.path}: ${e.message}`)
150
+ .join("\n");
116
151
  return { success: false, error: `validation failed:\n${msgs}` };
117
152
  }
118
153
  const pluginName = validation.manifest.name;
@@ -131,7 +166,7 @@ export async function installPlugin(source, options = {}) {
131
166
  source,
132
167
  manifest: validation.manifest,
133
168
  };
134
- await fs.writeJson(path.join(destDir, '.installed.json'), installedPlugin, { spaces: 2 });
169
+ await fs.writeJson(path.join(destDir, ".installed.json"), installedPlugin, { spaces: 2 });
135
170
  // Generate Agent Skills spec manifest for cross-agent compatibility
136
171
  await generateAgentSkillsManifest(destDir, source).catch(() => { });
137
172
  }
@@ -143,7 +178,7 @@ export async function installPlugin(source, options = {}) {
143
178
  }
144
179
  finally {
145
180
  // Clean up tmp if it still exists (error path)
146
- if (!dryRun && await fs.pathExists(tmpDir)) {
181
+ if (!dryRun && (await fs.pathExists(tmpDir))) {
147
182
  await fs.remove(tmpDir).catch(() => { });
148
183
  }
149
184
  }
@@ -153,7 +188,7 @@ export async function installPlugin(source, options = {}) {
153
188
  */
154
189
  export async function removePlugin(name, options = {}) {
155
190
  const pluginDir = path.join(PLUGINS_DIR, name);
156
- if (!await fs.pathExists(pluginDir)) {
191
+ if (!(await fs.pathExists(pluginDir))) {
157
192
  return { success: false, error: `plugin "${name}" is not installed` };
158
193
  }
159
194
  if (!options.dryRun) {
@@ -166,20 +201,22 @@ export async function removePlugin(name, options = {}) {
166
201
  * List all installed plugins.
167
202
  */
168
203
  export async function listInstalledPlugins() {
169
- if (!await fs.pathExists(PLUGINS_DIR))
204
+ if (!(await fs.pathExists(PLUGINS_DIR)))
170
205
  return [];
171
206
  const entries = await fs.readdir(PLUGINS_DIR);
172
207
  const plugins = [];
173
208
  for (const entry of entries) {
174
- if (entry.startsWith('.'))
209
+ if (entry.startsWith("."))
175
210
  continue;
176
- const metaPath = path.join(PLUGINS_DIR, entry, '.installed.json');
211
+ const metaPath = path.join(PLUGINS_DIR, entry, ".installed.json");
177
212
  if (await fs.pathExists(metaPath)) {
178
213
  try {
179
- const meta = await fs.readJson(metaPath);
214
+ const meta = (await fs.readJson(metaPath));
180
215
  plugins.push(meta);
181
216
  }
182
- catch { /* skip corrupt entries */ }
217
+ catch {
218
+ /* skip corrupt entries */
219
+ }
183
220
  }
184
221
  }
185
222
  return plugins;
@@ -193,13 +230,13 @@ export async function searchRegistry(query) {
193
230
  if (!response.ok) {
194
231
  return [];
195
232
  }
196
- const registry = await response.json();
233
+ const registry = (await response.json());
197
234
  let plugins = registry.plugins ?? [];
198
235
  if (query) {
199
236
  const q = query.toLowerCase();
200
- plugins = plugins.filter(p => p.id.toLowerCase().includes(q) ||
237
+ plugins = plugins.filter((p) => p.id.toLowerCase().includes(q) ||
201
238
  p.description.toLowerCase().includes(q) ||
202
- p.tags.some(t => t.toLowerCase().includes(q)));
239
+ p.tags.some((t) => t.toLowerCase().includes(q)));
203
240
  }
204
241
  return plugins;
205
242
  }
@@ -213,47 +250,59 @@ export async function searchRegistry(query) {
213
250
  * Returns an array of plugin names (sorted alphabetically).
214
251
  */
215
252
  export async function detectProjectPlugins(projectDir) {
216
- const pluginsDir = path.join(projectDir, '.javi-forge', 'plugins');
217
- if (!await fs.pathExists(pluginsDir))
253
+ const full = await detectProjectPluginsFull(projectDir);
254
+ return full.map((p) => p.name).sort();
255
+ }
256
+ /**
257
+ * Detect installed plugins with full metadata (including manifest).
258
+ * Used by auto-wiring to read plugin capabilities.
259
+ */
260
+ export async function detectProjectPluginsFull(projectDir) {
261
+ const pluginsDir = path.join(projectDir, ".javi-forge", "plugins");
262
+ if (!(await fs.pathExists(pluginsDir)))
218
263
  return [];
219
264
  const entries = await fs.readdir(pluginsDir);
220
265
  const plugins = [];
221
266
  for (const entry of entries) {
222
- if (entry.startsWith('.'))
267
+ if (entry.startsWith("."))
223
268
  continue;
224
- const metaPath = path.join(pluginsDir, entry, '.installed.json');
269
+ const metaPath = path.join(pluginsDir, entry, ".installed.json");
225
270
  if (await fs.pathExists(metaPath)) {
226
271
  try {
227
- const meta = await fs.readJson(metaPath);
272
+ const meta = (await fs.readJson(metaPath));
228
273
  if (meta.name) {
229
- plugins.push(meta.name);
274
+ plugins.push(meta);
230
275
  }
231
276
  }
232
- catch { /* skip corrupt entries */ }
277
+ catch {
278
+ /* skip corrupt entries */
279
+ }
233
280
  }
234
281
  }
235
- return plugins.sort();
282
+ return plugins.sort((a, b) => a.name.localeCompare(b.name));
236
283
  }
237
284
  /**
238
- * Sync detected plugins into the project manifest.
239
- * Returns a report of added, removed, and unchanged plugins.
285
+ * Sync detected plugins into the project manifest and auto-wire
286
+ * their capabilities into CLAUDE.md and .claude/settings.json.
287
+ * Returns a report of added, removed, unchanged, wired, and unwired plugins.
240
288
  */
241
289
  export async function syncPlugins(projectDir, options = {}) {
242
290
  const { dryRun = false } = options;
243
- const detected = await detectProjectPlugins(projectDir);
244
- const manifestPath = path.join(projectDir, '.javi-forge', 'manifest.json');
291
+ const detectedFull = await detectProjectPluginsFull(projectDir);
292
+ const detected = detectedFull.map((p) => p.name).sort();
293
+ const manifestPath = path.join(projectDir, ".javi-forge", "manifest.json");
245
294
  let manifest;
246
295
  if (await fs.pathExists(manifestPath)) {
247
- manifest = await fs.readJson(manifestPath);
296
+ manifest = (await fs.readJson(manifestPath));
248
297
  }
249
298
  else {
250
299
  // No manifest yet — treat current plugins as empty
251
300
  manifest = {
252
- version: '0.1.0',
301
+ version: "0.1.0",
253
302
  projectName: path.basename(projectDir),
254
- stack: 'node',
255
- ciProvider: 'github',
256
- memory: 'none',
303
+ stack: "node",
304
+ ciProvider: "github",
305
+ memory: "none",
257
306
  createdAt: new Date().toISOString(),
258
307
  updatedAt: new Date().toISOString(),
259
308
  modules: [],
@@ -261,16 +310,26 @@ export async function syncPlugins(projectDir, options = {}) {
261
310
  }
262
311
  const previous = new Set(manifest.plugins ?? []);
263
312
  const current = new Set(detected);
264
- const added = detected.filter(p => !previous.has(p));
265
- const removed = [...previous].filter(p => !current.has(p));
266
- const unchanged = detected.filter(p => previous.has(p));
313
+ const added = detected.filter((p) => !previous.has(p));
314
+ const removed = [...previous].filter((p) => !current.has(p));
315
+ const unchanged = detected.filter((p) => previous.has(p));
267
316
  if (!dryRun && (added.length > 0 || removed.length > 0)) {
268
317
  manifest.plugins = detected;
269
318
  manifest.updatedAt = new Date().toISOString();
270
319
  await fs.ensureDir(path.dirname(manifestPath));
271
320
  await fs.writeJson(manifestPath, manifest, { spaces: 2 });
272
321
  }
273
- return { added, removed, unchanged };
322
+ // ── Auto-wire plugin capabilities ──────────────────────────────────
323
+ const wireResult = await autoWirePlugins(projectDir, detectedFull, {
324
+ dryRun,
325
+ });
326
+ return {
327
+ added,
328
+ removed,
329
+ unchanged,
330
+ wired: wireResult.wired,
331
+ unwired: wireResult.unwired,
332
+ };
274
333
  }
275
334
  // ── Helpers ─────────────────────────────────────────────────────────────────
276
335
  /**
@@ -279,15 +338,15 @@ export async function syncPlugins(projectDir, options = {}) {
279
338
  */
280
339
  export function normalizeGitUrl(source) {
281
340
  // Already a full URL
282
- if (source.startsWith('https://github.com/')) {
283
- return source.endsWith('.git') ? source : `${source}.git`;
341
+ if (source.startsWith("https://github.com/")) {
342
+ return source.endsWith(".git") ? source : `${source}.git`;
284
343
  }
285
344
  // github.com/org/repo
286
- if (source.startsWith('github.com/')) {
345
+ if (source.startsWith("github.com/")) {
287
346
  return `https://${source}.git`;
288
347
  }
289
348
  // org/repo shorthand
290
- const parts = source.split('/');
349
+ const parts = source.split("/");
291
350
  if (parts.length === 2 && parts[0] && parts[1]) {
292
351
  return `https://github.com/${parts[0]}/${parts[1]}.git`;
293
352
  }
@@ -0,0 +1,40 @@
1
+ import type { PluginManifest } from "../types/index.js";
2
+ export interface SkillPublishResult {
3
+ success: boolean;
4
+ /** Path to the generated plugin.json */
5
+ pluginJsonPath?: string;
6
+ /** The generated manifest */
7
+ manifest?: PluginManifest;
8
+ error?: string;
9
+ }
10
+ export interface SkillPublishOptions {
11
+ /** Path to the skill directory (contains SKILL.md) */
12
+ skillDir: string;
13
+ /** Author name (optional, falls back to frontmatter or git user) */
14
+ author?: string;
15
+ /** Repository URL (optional) */
16
+ repository?: string;
17
+ /** Tags for marketplace discovery */
18
+ tags?: string[];
19
+ /** If true, skip writing files */
20
+ dryRun?: boolean;
21
+ }
22
+ /**
23
+ * Package a skill directory for marketplace distribution.
24
+ *
25
+ * Reads the SKILL.md, extracts metadata from frontmatter, and generates
26
+ * a plugin.json compatible with both `javi-forge plugin install` and
27
+ * `claude plugin install` (Anthropic's plugin format).
28
+ *
29
+ * Expected input structure:
30
+ * skill-name/
31
+ * SKILL.md
32
+ * (optional other files)
33
+ *
34
+ * Output:
35
+ * skill-name/
36
+ * SKILL.md
37
+ * plugin.json ← generated
38
+ */
39
+ export declare function publishSkill(options: SkillPublishOptions): Promise<SkillPublishResult>;
40
+ //# sourceMappingURL=skill-publish.d.ts.map
@@ -0,0 +1,146 @@
1
+ import fs from "fs-extra";
2
+ import path from "path";
3
+ import { PLUGIN_MANIFEST_FILE } from "../constants.js";
4
+ import { parseFrontmatter } from "./frontmatter.js";
5
+ // ── Core ───────────────────────────────────────────────────────────────────
6
+ /**
7
+ * Package a skill directory for marketplace distribution.
8
+ *
9
+ * Reads the SKILL.md, extracts metadata from frontmatter, and generates
10
+ * a plugin.json compatible with both `javi-forge plugin install` and
11
+ * `claude plugin install` (Anthropic's plugin format).
12
+ *
13
+ * Expected input structure:
14
+ * skill-name/
15
+ * SKILL.md
16
+ * (optional other files)
17
+ *
18
+ * Output:
19
+ * skill-name/
20
+ * SKILL.md
21
+ * plugin.json ← generated
22
+ */
23
+ export async function publishSkill(options) {
24
+ const { skillDir, author, repository, tags = [], dryRun = false } = options;
25
+ // Validate skill directory
26
+ const skillMdPath = path.join(skillDir, "SKILL.md");
27
+ if (!(await fs.pathExists(skillMdPath))) {
28
+ return { success: false, error: `SKILL.md not found in ${skillDir}` };
29
+ }
30
+ // Read and parse SKILL.md
31
+ let raw;
32
+ try {
33
+ raw = await fs.readFile(skillMdPath, "utf-8");
34
+ }
35
+ catch {
36
+ return { success: false, error: "Failed to read SKILL.md" };
37
+ }
38
+ const parsed = parseFrontmatter(raw);
39
+ const frontmatter = parsed?.data ?? {};
40
+ // Extract metadata
41
+ const name = frontmatter["name"] ?? path.basename(skillDir);
42
+ const version = frontmatter["version"] ?? "1.0.0";
43
+ const description = frontmatter["description"] ?? `${name} AI skill`;
44
+ // Validate name format (kebab-case)
45
+ if (!/^[a-z0-9]+(-[a-z0-9]+)*$/.test(name)) {
46
+ return {
47
+ success: false,
48
+ error: `Skill name "${name}" must be kebab-case (e.g., "react-19", "tailwind-4")`,
49
+ };
50
+ }
51
+ // Validate description length
52
+ if (description.length < 10) {
53
+ return {
54
+ success: false,
55
+ error: "Description must be at least 10 characters",
56
+ };
57
+ }
58
+ // Build plugin.json manifest
59
+ const manifest = {
60
+ name,
61
+ version,
62
+ description: description.length > 200
63
+ ? description.slice(0, 197) + "..."
64
+ : description,
65
+ skills: [name],
66
+ tags: tags.length > 0 ? tags : extractTagsFromDescription(description),
67
+ ...(author ? { author } : {}),
68
+ ...(repository ? { repository } : {}),
69
+ };
70
+ if (!dryRun) {
71
+ const pluginJsonPath = path.join(skillDir, PLUGIN_MANIFEST_FILE);
72
+ await fs.writeJson(pluginJsonPath, manifest, { spaces: 2 });
73
+ // Also create a skills/ subdirectory structure for plugin compatibility
74
+ const skillsSubdir = path.join(skillDir, "skills", name);
75
+ if (!(await fs.pathExists(skillsSubdir))) {
76
+ await fs.ensureDir(skillsSubdir);
77
+ // Symlink or copy SKILL.md into skills/name/
78
+ const targetSkillMd = path.join(skillsSubdir, "SKILL.md");
79
+ if (!(await fs.pathExists(targetSkillMd))) {
80
+ await fs.copy(skillMdPath, targetSkillMd);
81
+ }
82
+ }
83
+ return { success: true, pluginJsonPath, manifest };
84
+ }
85
+ return {
86
+ success: true,
87
+ pluginJsonPath: path.join(skillDir, PLUGIN_MANIFEST_FILE),
88
+ manifest,
89
+ };
90
+ }
91
+ // ── Helpers ────────────────────────────────────────────────────────────────
92
+ /**
93
+ * Extract reasonable tags from a skill description.
94
+ * Looks for common tech keywords.
95
+ */
96
+ function extractTagsFromDescription(description) {
97
+ const keywords = [
98
+ "react",
99
+ "next",
100
+ "nextjs",
101
+ "angular",
102
+ "vue",
103
+ "svelte",
104
+ "typescript",
105
+ "javascript",
106
+ "python",
107
+ "go",
108
+ "rust",
109
+ "java",
110
+ "elixir",
111
+ "tailwind",
112
+ "css",
113
+ "styling",
114
+ "testing",
115
+ "test",
116
+ "e2e",
117
+ "unit",
118
+ "api",
119
+ "rest",
120
+ "graphql",
121
+ "ai",
122
+ "llm",
123
+ "ml",
124
+ "agent",
125
+ "security",
126
+ "auth",
127
+ "database",
128
+ "sql",
129
+ "nosql",
130
+ "docker",
131
+ "kubernetes",
132
+ "devops",
133
+ "ci",
134
+ "cd",
135
+ "state",
136
+ "management",
137
+ "store",
138
+ "validation",
139
+ "schema",
140
+ "zod",
141
+ ];
142
+ const lower = description.toLowerCase();
143
+ const found = keywords.filter((kw) => lower.includes(kw));
144
+ return found.slice(0, 10); // Max 10 tags per plugin spec
145
+ }
146
+ //# sourceMappingURL=skill-publish.js.map
@@ -0,0 +1,38 @@
1
+ import type { Stack } from "../types/index.js";
2
+ /**
3
+ * Maps detected project signals to recommended AI skills.
4
+ * Each key is a detection signal (file/dependency/pattern),
5
+ * and the value is the list of skills it implies.
6
+ */
7
+ export declare const SIGNAL_SKILL_MAP: Record<string, string[]>;
8
+ /**
9
+ * Docker-related files that indicate a containerized project.
10
+ * Used by the init command to suggest Docker zero-downtime deploy scaffolding.
11
+ */
12
+ export declare const DOCKER_FILES: string[];
13
+ /**
14
+ * Detect whether the project uses Docker (Dockerfile or compose file present).
15
+ */
16
+ export declare function detectDockerPresence(projectDir: string): Promise<boolean>;
17
+ export interface StackDetectionResult {
18
+ /** Primary stack type (node, python, etc.) */
19
+ stack: Stack | null;
20
+ /** All detected signals with their source */
21
+ signals: DetectedSignal[];
22
+ /** De-duplicated list of recommended skill names */
23
+ recommendedSkills: string[];
24
+ }
25
+ export interface DetectedSignal {
26
+ /** What was detected (e.g., "react", "tailwindcss", "tsconfig.json") */
27
+ signal: string;
28
+ /** Where it was found (e.g., "package.json dependencies", "file exists") */
29
+ source: string;
30
+ /** Skills this signal maps to */
31
+ skills: string[];
32
+ }
33
+ /**
34
+ * Scan a project directory and detect its tech stack + recommended skills.
35
+ * Reads package.json deps, pyproject.toml, and well-known config files.
36
+ */
37
+ export declare function detectProjectStack(projectDir: string): Promise<StackDetectionResult>;
38
+ //# sourceMappingURL=stack-detector.d.ts.map