liteagents 2.4.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 (215) hide show
  1. package/CHANGELOG.md +441 -0
  2. package/LICENSE +21 -0
  3. package/README.md +179 -0
  4. package/cli.js +230 -0
  5. package/docs/.gitkeep +1 -0
  6. package/docs/CONTRIBUTING.md +739 -0
  7. package/docs/DUAL_PUBLISH_SUMMARY.md +177 -0
  8. package/docs/ERROR_HANDLING_IMPLEMENTATION.md +327 -0
  9. package/docs/GITHUB_PACKAGES.md +181 -0
  10. package/docs/GITHUB_SETUP.md +158 -0
  11. package/docs/INSTALLATION_DEMO.md +691 -0
  12. package/docs/INSTALLATION_LOCATIONS.md +299 -0
  13. package/docs/INSTALLER_GUIDE.md +1586 -0
  14. package/docs/INTEGRATION_ISSUES_9.1.md +341 -0
  15. package/docs/KNOWLEDGE_BASE.md +727 -0
  16. package/docs/MIGRATION.md +384 -0
  17. package/docs/PACKAGE_BASELINE.md +557 -0
  18. package/docs/PACKAGE_VALIDATION_REPORT.md +427 -0
  19. package/docs/PASS_INTEGRATION.md +307 -0
  20. package/docs/PASS_QUICK_START.md +150 -0
  21. package/docs/PRIVACY.md +203 -0
  22. package/docs/PUBLISHING.md +494 -0
  23. package/docs/QUICK-START.md +318 -0
  24. package/docs/RELEASE_NOTES_1.2.0.md +323 -0
  25. package/docs/SECURITY.md +317 -0
  26. package/docs/SILENT_MODE_GUIDE.md +526 -0
  27. package/docs/SKILLS_CONVERSION.md +154 -0
  28. package/docs/TESTING.md +582 -0
  29. package/docs/TEST_COVERAGE.md +347 -0
  30. package/docs/TROUBLESHOOTING.md +788 -0
  31. package/docs/UPDATED_VARIANT_CONFIGURATION.md +274 -0
  32. package/docs/VARIANT_CONFIGURATION.md +440 -0
  33. package/installer/cli.js +761 -0
  34. package/installer/installation-engine.js +1536 -0
  35. package/installer/package-manager.js +640 -0
  36. package/installer/path-manager.js +427 -0
  37. package/installer/report-template.js +298 -0
  38. package/installer/verification-system.js +274 -0
  39. package/package.json +83 -0
  40. package/packages/ampcode/AGENT.md +58 -0
  41. package/packages/ampcode/README.md +17 -0
  42. package/packages/ampcode/agents/1-create-prd.md +175 -0
  43. package/packages/ampcode/agents/2-generate-tasks.md +190 -0
  44. package/packages/ampcode/agents/3-process-task-list.md +225 -0
  45. package/packages/ampcode/agents/code-developer.md +198 -0
  46. package/packages/ampcode/agents/context-builder.md +142 -0
  47. package/packages/ampcode/agents/feature-planner.md +199 -0
  48. package/packages/ampcode/agents/market-researcher.md +89 -0
  49. package/packages/ampcode/agents/orchestrator.md +116 -0
  50. package/packages/ampcode/agents/quality-assurance.md +115 -0
  51. package/packages/ampcode/agents/system-architect.md +135 -0
  52. package/packages/ampcode/agents/ui-designer.md +184 -0
  53. package/packages/ampcode/commands/brainstorming.md +56 -0
  54. package/packages/ampcode/commands/code-review.md +107 -0
  55. package/packages/ampcode/commands/condition-based-waiting/example.ts +158 -0
  56. package/packages/ampcode/commands/condition-based-waiting.md +122 -0
  57. package/packages/ampcode/commands/debug.md +20 -0
  58. package/packages/ampcode/commands/docs-builder/templates.md +572 -0
  59. package/packages/ampcode/commands/docs-builder.md +106 -0
  60. package/packages/ampcode/commands/explain.md +18 -0
  61. package/packages/ampcode/commands/git-commit.md +14 -0
  62. package/packages/ampcode/commands/optimize.md +20 -0
  63. package/packages/ampcode/commands/refactor.md +21 -0
  64. package/packages/ampcode/commands/review.md +18 -0
  65. package/packages/ampcode/commands/root-cause-tracing/find-polluter.sh +63 -0
  66. package/packages/ampcode/commands/root-cause-tracing.md +176 -0
  67. package/packages/ampcode/commands/security.md +21 -0
  68. package/packages/ampcode/commands/ship.md +18 -0
  69. package/packages/ampcode/commands/skill-creator/scripts/init_skill.py +303 -0
  70. package/packages/ampcode/commands/skill-creator/scripts/package_skill.py +110 -0
  71. package/packages/ampcode/commands/skill-creator/scripts/quick_validate.py +65 -0
  72. package/packages/ampcode/commands/skill-creator.md +211 -0
  73. package/packages/ampcode/commands/stash.md +45 -0
  74. package/packages/ampcode/commands/systematic-debugging.md +297 -0
  75. package/packages/ampcode/commands/test-driven-development.md +390 -0
  76. package/packages/ampcode/commands/test-generate.md +18 -0
  77. package/packages/ampcode/commands/testing-anti-patterns.md +304 -0
  78. package/packages/ampcode/commands/verification-before-completion.md +152 -0
  79. package/packages/ampcode/settings.json +13 -0
  80. package/packages/ampcode/variants.json +8 -0
  81. package/packages/claude/CLAUDE.md +58 -0
  82. package/packages/claude/README.md +23 -0
  83. package/packages/claude/agents/1-create-prd.md +175 -0
  84. package/packages/claude/agents/2-generate-tasks.md +190 -0
  85. package/packages/claude/agents/3-process-task-list.md +225 -0
  86. package/packages/claude/agents/code-developer.md +198 -0
  87. package/packages/claude/agents/context-builder.md +142 -0
  88. package/packages/claude/agents/feature-planner.md +199 -0
  89. package/packages/claude/agents/market-researcher.md +89 -0
  90. package/packages/claude/agents/orchestrator.md +117 -0
  91. package/packages/claude/agents/quality-assurance.md +115 -0
  92. package/packages/claude/agents/system-architect.md +135 -0
  93. package/packages/claude/agents/ui-designer.md +184 -0
  94. package/packages/claude/commands/debug.md +20 -0
  95. package/packages/claude/commands/explain.md +18 -0
  96. package/packages/claude/commands/git-commit.md +14 -0
  97. package/packages/claude/commands/optimize.md +20 -0
  98. package/packages/claude/commands/refactor.md +21 -0
  99. package/packages/claude/commands/review.md +18 -0
  100. package/packages/claude/commands/security.md +21 -0
  101. package/packages/claude/commands/ship.md +18 -0
  102. package/packages/claude/commands/stash.md +45 -0
  103. package/packages/claude/commands/test-generate.md +18 -0
  104. package/packages/claude/skills/brainstorming/SKILL.md +56 -0
  105. package/packages/claude/skills/code-review/SKILL.md +107 -0
  106. package/packages/claude/skills/code-review/code-reviewer.md +146 -0
  107. package/packages/claude/skills/condition-based-waiting/SKILL.md +122 -0
  108. package/packages/claude/skills/condition-based-waiting/example.ts +158 -0
  109. package/packages/claude/skills/docs-builder/SKILL.md +106 -0
  110. package/packages/claude/skills/docs-builder/references/templates.md +572 -0
  111. package/packages/claude/skills/root-cause-tracing/SKILL.md +176 -0
  112. package/packages/claude/skills/root-cause-tracing/find-polluter.sh +63 -0
  113. package/packages/claude/skills/skill-creator/LICENSE.txt +202 -0
  114. package/packages/claude/skills/skill-creator/SKILL.md +211 -0
  115. package/packages/claude/skills/skill-creator/scripts/init_skill.py +303 -0
  116. package/packages/claude/skills/skill-creator/scripts/package_skill.py +110 -0
  117. package/packages/claude/skills/skill-creator/scripts/quick_validate.py +65 -0
  118. package/packages/claude/skills/systematic-debugging/CREATION-LOG.md +119 -0
  119. package/packages/claude/skills/systematic-debugging/SKILL.md +296 -0
  120. package/packages/claude/skills/systematic-debugging/test-academic.md +14 -0
  121. package/packages/claude/skills/systematic-debugging/test-pressure-1.md +58 -0
  122. package/packages/claude/skills/systematic-debugging/test-pressure-2.md +68 -0
  123. package/packages/claude/skills/systematic-debugging/test-pressure-3.md +69 -0
  124. package/packages/claude/skills/test-driven-development/SKILL.md +392 -0
  125. package/packages/claude/skills/testing-anti-patterns/SKILL.md +304 -0
  126. package/packages/claude/skills/verification-before-completion/SKILL.md +152 -0
  127. package/packages/claude/variants.json +9 -0
  128. package/packages/droid/AGENTS.md +52 -0
  129. package/packages/droid/README.md +17 -0
  130. package/packages/droid/change_settings.json +61 -0
  131. package/packages/droid/commands/brainstorming.md +56 -0
  132. package/packages/droid/commands/code-review.md +107 -0
  133. package/packages/droid/commands/condition-based-waiting/example.ts +158 -0
  134. package/packages/droid/commands/condition-based-waiting.md +122 -0
  135. package/packages/droid/commands/debug.md +20 -0
  136. package/packages/droid/commands/docs-builder/templates.md +572 -0
  137. package/packages/droid/commands/docs-builder.md +106 -0
  138. package/packages/droid/commands/explain.md +18 -0
  139. package/packages/droid/commands/git-commit.md +14 -0
  140. package/packages/droid/commands/optimize.md +20 -0
  141. package/packages/droid/commands/refactor.md +21 -0
  142. package/packages/droid/commands/review.md +18 -0
  143. package/packages/droid/commands/root-cause-tracing/find-polluter.sh +63 -0
  144. package/packages/droid/commands/root-cause-tracing.md +176 -0
  145. package/packages/droid/commands/security.md +21 -0
  146. package/packages/droid/commands/ship.md +18 -0
  147. package/packages/droid/commands/skill-creator/scripts/init_skill.py +303 -0
  148. package/packages/droid/commands/skill-creator/scripts/package_skill.py +110 -0
  149. package/packages/droid/commands/skill-creator/scripts/quick_validate.py +65 -0
  150. package/packages/droid/commands/skill-creator.md +211 -0
  151. package/packages/droid/commands/stash.md +45 -0
  152. package/packages/droid/commands/systematic-debugging.md +297 -0
  153. package/packages/droid/commands/test-driven-development.md +390 -0
  154. package/packages/droid/commands/test-generate.md +18 -0
  155. package/packages/droid/commands/testing-anti-patterns.md +304 -0
  156. package/packages/droid/commands/verification-before-completion.md +152 -0
  157. package/packages/droid/droids/1-create-prd.md +170 -0
  158. package/packages/droid/droids/2-generate-tasks.md +190 -0
  159. package/packages/droid/droids/3-process-task-list.md +225 -0
  160. package/packages/droid/droids/code-developer.md +198 -0
  161. package/packages/droid/droids/context-builder.md +142 -0
  162. package/packages/droid/droids/feature-planner.md +199 -0
  163. package/packages/droid/droids/market-researcher.md +89 -0
  164. package/packages/droid/droids/orchestrator.md +116 -0
  165. package/packages/droid/droids/quality-assurance.md +115 -0
  166. package/packages/droid/droids/system-architect.md +135 -0
  167. package/packages/droid/droids/ui-designer.md +184 -0
  168. package/packages/droid/variants.json +8 -0
  169. package/packages/opencode/AGENTS.md +52 -0
  170. package/packages/opencode/README.md +17 -0
  171. package/packages/opencode/agent/1-create-prd.md +179 -0
  172. package/packages/opencode/agent/2-generate-tasks.md +194 -0
  173. package/packages/opencode/agent/3-process-task-list.md +229 -0
  174. package/packages/opencode/agent/code-developer.md +202 -0
  175. package/packages/opencode/agent/context-builder.md +146 -0
  176. package/packages/opencode/agent/feature-planner.md +203 -0
  177. package/packages/opencode/agent/market-researcher.md +93 -0
  178. package/packages/opencode/agent/orchestrator.md +120 -0
  179. package/packages/opencode/agent/quality-assurance.md +119 -0
  180. package/packages/opencode/agent/system-architect.md +139 -0
  181. package/packages/opencode/agent/ui-designer.md +188 -0
  182. package/packages/opencode/command/brainstorming.md +56 -0
  183. package/packages/opencode/command/code-review.md +107 -0
  184. package/packages/opencode/command/condition-based-waiting/example.ts +158 -0
  185. package/packages/opencode/command/condition-based-waiting.md +122 -0
  186. package/packages/opencode/command/debug.md +20 -0
  187. package/packages/opencode/command/docs-builder/templates.md +572 -0
  188. package/packages/opencode/command/docs-builder.md +106 -0
  189. package/packages/opencode/command/explain.md +18 -0
  190. package/packages/opencode/command/git-commit.md +14 -0
  191. package/packages/opencode/command/optimize.md +20 -0
  192. package/packages/opencode/command/refactor.md +21 -0
  193. package/packages/opencode/command/review.md +18 -0
  194. package/packages/opencode/command/root-cause-tracing/find-polluter.sh +63 -0
  195. package/packages/opencode/command/root-cause-tracing.md +176 -0
  196. package/packages/opencode/command/security.md +21 -0
  197. package/packages/opencode/command/ship.md +18 -0
  198. package/packages/opencode/command/skill-creator/scripts/init_skill.py +303 -0
  199. package/packages/opencode/command/skill-creator/scripts/package_skill.py +110 -0
  200. package/packages/opencode/command/skill-creator/scripts/quick_validate.py +65 -0
  201. package/packages/opencode/command/skill-creator.md +211 -0
  202. package/packages/opencode/command/stash.md +45 -0
  203. package/packages/opencode/command/systematic-debugging.md +297 -0
  204. package/packages/opencode/command/test-driven-development.md +390 -0
  205. package/packages/opencode/command/test-generate.md +18 -0
  206. package/packages/opencode/command/testing-anti-patterns.md +304 -0
  207. package/packages/opencode/command/verification-before-completion.md +152 -0
  208. package/packages/opencode/opencode.jsonc +201 -0
  209. package/packages/opencode/variants.json +8 -0
  210. package/packages/subagentic-manual.md +349 -0
  211. package/postinstall.js +21 -0
  212. package/tools/ampcode/manifest-template.json +14 -0
  213. package/tools/claude/manifest-template.json +14 -0
  214. package/tools/droid/manifest-template.json +14 -0
  215. package/tools/opencode/manifest-template.json +14 -0
@@ -0,0 +1,640 @@
1
+ /**
2
+ * Package Manager for Agentic Kit Installer
3
+ *
4
+ * Manages tool-specific variant packages and content selection
5
+ */
6
+
7
+ const fs = require('fs');
8
+ const path = require('path');
9
+
10
+ class PackageManager {
11
+ constructor() {
12
+ this.packagesDir = path.join(__dirname, '..', 'packages');
13
+ this.variants = ['pro'];
14
+ this.tools = ['claude', 'opencode', 'ampcode', 'droid'];
15
+ this.variantConfigCache = {}; // Cache for loaded variant configurations
16
+ }
17
+
18
+ /**
19
+ * Load and parse variants.json for a specific tool
20
+ * @param {string} toolId - Tool identifier (claude, opencode, ampcode, droid)
21
+ * @returns {Promise<Object>} Parsed variant configuration
22
+ * @throws {Error} If file not found, invalid JSON, or missing required variants
23
+ */
24
+ async loadVariantConfig(toolId) {
25
+ // Return cached configuration if available
26
+ if (this.variantConfigCache[toolId]) {
27
+ return this.variantConfigCache[toolId];
28
+ }
29
+
30
+ const variantsFilePath = path.join(this.packagesDir, toolId, 'variants.json');
31
+
32
+ // Check if variants.json exists
33
+ if (!fs.existsSync(variantsFilePath)) {
34
+ throw new Error(`Variants file not found for tool: ${toolId}`);
35
+ }
36
+
37
+ // Read and parse JSON with security checks
38
+ let config;
39
+ try {
40
+ // Check file size before reading (max 1MB to prevent DoS)
41
+ const stats = await fs.promises.stat(variantsFilePath);
42
+ const maxSize = 1024 * 1024; // 1MB
43
+ if (stats.size > maxSize) {
44
+ throw new Error(`Variants file for tool ${toolId} is too large (${stats.size} bytes, max ${maxSize} bytes)`);
45
+ }
46
+
47
+ const fileContent = await fs.promises.readFile(variantsFilePath, 'utf8');
48
+
49
+ // Check for null bytes (security risk)
50
+ if (fileContent.includes('\0')) {
51
+ throw new Error(`Variants file for tool ${toolId} contains null bytes (security risk)`);
52
+ }
53
+
54
+ config = JSON.parse(fileContent);
55
+
56
+ // Validate config is an object (not array or primitive)
57
+ if (typeof config !== 'object' || config === null || Array.isArray(config)) {
58
+ throw new Error(`Variants file for tool ${toolId} must contain a JSON object`);
59
+ }
60
+ } catch (error) {
61
+ if (error instanceof SyntaxError) {
62
+ throw new Error(`Invalid JSON in variants.json for tool ${toolId}: ${error.message}`);
63
+ }
64
+ throw error;
65
+ }
66
+
67
+ // Validate required variants exist
68
+ const requiredVariants = ['pro'];
69
+ for (const variant of requiredVariants) {
70
+ if (!config[variant]) {
71
+ throw new Error(`Required variant '${variant}' not found in variants.json for tool ${toolId}`);
72
+ }
73
+ }
74
+
75
+ // Validate each variant has required fields
76
+ // Note: 'skills', 'commands', 'resources', 'hooks' are optional
77
+ for (const variant of requiredVariants) {
78
+ const variantConfig = config[variant];
79
+ const requiredFields = ['name', 'description', 'agents'];
80
+
81
+ for (const field of requiredFields) {
82
+ if (variantConfig[field] === undefined) {
83
+ throw new Error(`Required field '${field}' missing in '${variant}' variant for tool ${toolId}`);
84
+ }
85
+ }
86
+ }
87
+
88
+ // Cache the configuration
89
+ this.variantConfigCache[toolId] = config;
90
+
91
+ return config;
92
+ }
93
+
94
+ /**
95
+ * Get metadata for a specific variant
96
+ * @param {string} toolId - Tool identifier
97
+ * @param {string} variant - Variant name (lite, standard, pro)
98
+ * @returns {Promise<Object>} Variant metadata (name, description, useCase, targetUsers)
99
+ * @throws {Error} If variant not found
100
+ */
101
+ async getVariantMetadata(toolId, variant) {
102
+ const config = await this.loadVariantConfig(toolId);
103
+
104
+ if (!config[variant]) {
105
+ throw new Error(`Variant '${variant}' not found for tool ${toolId}`);
106
+ }
107
+
108
+ const variantConfig = config[variant];
109
+
110
+ return {
111
+ name: variantConfig.name,
112
+ description: variantConfig.description,
113
+ useCase: variantConfig.useCase,
114
+ targetUsers: variantConfig.targetUsers
115
+ };
116
+ }
117
+
118
+ /**
119
+ * Select content based on variant configuration
120
+ * @param {string} toolId - Tool identifier (claude, opencode, ampcode, droid)
121
+ * @param {string} variant - Variant name (lite, standard, pro)
122
+ * @param {Object} availableContent - Object with arrays of available agents, skills, resources, hooks
123
+ * @returns {Promise<Object>} Object with filtered arrays of selected content
124
+ * @throws {Error} If variant not found or specified items don't exist
125
+ */
126
+ async selectVariantContent(toolId, variant, availableContent) {
127
+ // Load variant configuration
128
+ const config = await this.loadVariantConfig(toolId);
129
+
130
+ if (!config[variant]) {
131
+ throw new Error(`Variant '${variant}' not found for tool ${toolId}`);
132
+ }
133
+
134
+ const variantConfig = config[variant];
135
+ const selected = {
136
+ agents: [],
137
+ skills: [],
138
+ commands: [],
139
+ resources: [],
140
+ hooks: []
141
+ };
142
+
143
+ // Helper function to process each content category
144
+ const selectItems = (category, variantSelection, availableItems, skipMissing = false) => {
145
+ // Handle wildcard: "*" expands to all available items
146
+ if (variantSelection === '*') {
147
+ return [...availableItems];
148
+ }
149
+
150
+ // Handle array selection (specific items or empty array)
151
+ if (Array.isArray(variantSelection)) {
152
+ // Empty array [] means no items selected
153
+ if (variantSelection.length === 0) {
154
+ return [];
155
+ }
156
+
157
+ // Specific selection: validate all items exist (unless skipMissing)
158
+ const selectedItems = [];
159
+ for (const item of variantSelection) {
160
+ if (!availableItems.includes(item)) {
161
+ if (skipMissing) {
162
+ // Skip missing items silently (e.g., removed commands like subagent-spawning)
163
+ continue;
164
+ }
165
+ throw new Error(
166
+ `Item '${item}' specified in ${variant} variant ${category} not found in available content. ` +
167
+ `Available ${category}: ${availableItems.join(', ')}`
168
+ );
169
+ }
170
+ selectedItems.push(item);
171
+ }
172
+ return selectedItems;
173
+ }
174
+
175
+ // If neither wildcard nor array, treat as empty selection
176
+ return [];
177
+ };
178
+
179
+ // Process each content category
180
+ selected.agents = selectItems('agents', variantConfig.agents, availableContent.agents || []);
181
+ selected.skills = selectItems('skills', variantConfig.skills, availableContent.skills || []);
182
+ selected.commands = selectItems('commands', variantConfig.commands, availableContent.commands || [], true);
183
+ selected.resources = selectItems('resources', variantConfig.resources, availableContent.resources || []);
184
+ selected.hooks = selectItems('hooks', variantConfig.hooks, availableContent.hooks || []);
185
+
186
+ return selected;
187
+ }
188
+
189
+ /**
190
+ * Get available variants for a tool
191
+ */
192
+ getAvailableVariants(toolId) {
193
+ const toolDir = path.join(this.packagesDir, toolId);
194
+
195
+ if (!fs.existsSync(toolDir)) {
196
+ return [];
197
+ }
198
+
199
+ return this.variants.filter(variant => {
200
+ const variantDir = path.join(toolDir, variant);
201
+ return fs.existsSync(variantDir);
202
+ });
203
+ }
204
+
205
+ /**
206
+ * Get package contents for a specific tool and variant
207
+ * Uses variant filtering to return only selected content based on variants.json
208
+ */
209
+ async getPackageContents(toolId, variant) {
210
+ // Use base package directory (not variant-specific)
211
+ const packageDir = path.join(this.packagesDir, toolId);
212
+
213
+ if (!fs.existsSync(packageDir)) {
214
+ throw new Error(`Package not found: ${toolId}`);
215
+ }
216
+
217
+ // Get all available content from the package (includes dynamic directory names)
218
+ const availableContent = await this.getAvailableContent(packageDir);
219
+
220
+ // Use selectVariantContent to filter based on variant configuration
221
+ const selectedContent = await this.selectVariantContent(toolId, variant, availableContent);
222
+
223
+ // Get file paths for selected content
224
+ const contents = {
225
+ agents: [],
226
+ skills: [],
227
+ commands: [],
228
+ resources: [],
229
+ hooks: [],
230
+ totalFiles: 0,
231
+ totalSize: 0
232
+ };
233
+
234
+ // Build file paths for selected agents (use dynamic directory name)
235
+ const agentsDirName = availableContent.agentsDir || 'agents';
236
+ const agentsDir = path.join(packageDir, agentsDirName);
237
+ if (fs.existsSync(agentsDir)) {
238
+ for (const agent of selectedContent.agents) {
239
+ const agentPath = path.join(agentsDir, `${agent}.md`);
240
+ if (fs.existsSync(agentPath)) {
241
+ contents.agents.push(agentPath);
242
+ }
243
+ }
244
+ }
245
+
246
+ // Build directory paths for selected skills (skills are directories)
247
+ const skillsDir = path.join(packageDir, 'skills');
248
+ if (fs.existsSync(skillsDir)) {
249
+ for (const skill of selectedContent.skills) {
250
+ const skillPath = path.join(skillsDir, skill);
251
+ if (fs.existsSync(skillPath)) {
252
+ // Store the skill directory path (not individual files within it)
253
+ // The installation engine will handle copying the entire directory
254
+ contents.skills.push(skillPath);
255
+ }
256
+ }
257
+ }
258
+
259
+ // Build file paths for selected commands (use dynamic directory name)
260
+ const commandsDirName = availableContent.commandsDir || 'commands';
261
+ const commandsDir = path.join(packageDir, commandsDirName);
262
+ if (fs.existsSync(commandsDir) && selectedContent.commands) {
263
+ for (const command of selectedContent.commands) {
264
+ const commandPath = path.join(commandsDir, `${command}.md`);
265
+ if (fs.existsSync(commandPath)) {
266
+ contents.commands.push(commandPath);
267
+ }
268
+ // Also check for command subdirectories (like docs-builder/templates.md)
269
+ const commandSubDir = path.join(commandsDir, command);
270
+ if (fs.existsSync(commandSubDir) && fs.statSync(commandSubDir).isDirectory()) {
271
+ contents.commands.push(commandSubDir);
272
+ }
273
+ }
274
+ }
275
+
276
+ // Build file paths for selected resources
277
+ const resourcesDir = path.join(packageDir, 'resources');
278
+ if (fs.existsSync(resourcesDir)) {
279
+ for (const resource of selectedContent.resources) {
280
+ const resourcePath = path.join(resourcesDir, resource);
281
+ if (fs.existsSync(resourcePath)) {
282
+ contents.resources.push(resourcePath);
283
+ }
284
+ }
285
+ }
286
+
287
+ // Build file paths for selected hooks
288
+ const hooksDir = path.join(packageDir, 'hooks');
289
+ if (fs.existsSync(hooksDir)) {
290
+ for (const hook of selectedContent.hooks) {
291
+ const hookPath = path.join(hooksDir, hook);
292
+ if (fs.existsSync(hookPath)) {
293
+ contents.hooks.push(hookPath);
294
+ }
295
+ }
296
+ }
297
+
298
+ // Calculate total files
299
+ contents.totalFiles = contents.agents.length +
300
+ contents.skills.length +
301
+ contents.commands.length +
302
+ contents.resources.length +
303
+ contents.hooks.length;
304
+
305
+ return contents;
306
+ }
307
+
308
+ /**
309
+ * Get all available content from a package directory
310
+ * Helper method for getPackageContents
311
+ */
312
+ async getAvailableContent(packageDir) {
313
+ const getItemsInDir = async (dir, isAgentDir = false) => {
314
+ if (!fs.existsSync(dir)) return { items: [], dirName: null };
315
+ const items = await fs.promises.readdir(dir);
316
+ const result = [];
317
+
318
+ for (const item of items) {
319
+ const itemPath = path.join(dir, item);
320
+ const stat = await fs.promises.stat(itemPath);
321
+
322
+ if (stat.isFile()) {
323
+ // For agents, strip the .md extension
324
+ if (isAgentDir) {
325
+ result.push(item.replace('.md', ''));
326
+ } else if (dir.includes('resources') || dir.includes('hooks')) {
327
+ // For resources and hooks, keep the full filename
328
+ result.push(item);
329
+ }
330
+ // For skills directory, ignore files (only directories are skills)
331
+ } else if (stat.isDirectory()) {
332
+ // For skills (which are directories), include the directory name
333
+ result.push(item);
334
+ }
335
+ }
336
+
337
+ return { items: result, dirName: path.basename(dir) };
338
+ };
339
+
340
+ // Check for both plural and singular directory names
341
+ const agentsDir = fs.existsSync(path.join(packageDir, 'agents')) ? 'agents' :
342
+ fs.existsSync(path.join(packageDir, 'agent')) ? 'agent' :
343
+ fs.existsSync(path.join(packageDir, 'droids')) ? 'droids' : 'agents';
344
+ const commandsDir = fs.existsSync(path.join(packageDir, 'commands')) ? 'commands' :
345
+ fs.existsSync(path.join(packageDir, 'command')) ? 'command' : 'commands';
346
+
347
+ const agentsResult = await getItemsInDir(path.join(packageDir, agentsDir), true);
348
+ const skillsResult = await getItemsInDir(path.join(packageDir, 'skills'));
349
+ const commandsResult = await getItemsInDir(path.join(packageDir, commandsDir), true);
350
+ const resourcesResult = await getItemsInDir(path.join(packageDir, 'resources'));
351
+ const hooksResult = await getItemsInDir(path.join(packageDir, 'hooks'));
352
+
353
+ return {
354
+ agents: agentsResult.items,
355
+ agentsDir: agentsDir,
356
+ skills: skillsResult.items,
357
+ commands: commandsResult.items,
358
+ commandsDir: commandsDir,
359
+ resources: resourcesResult.items,
360
+ hooks: hooksResult.items
361
+ };
362
+ }
363
+
364
+ /**
365
+ * Count files in a directory recursively
366
+ */
367
+ async countFiles(dir) {
368
+ const files = [];
369
+
370
+ async function traverse(currentDir) {
371
+ const items = await fs.promises.readdir(currentDir);
372
+
373
+ for (const item of items) {
374
+ const itemPath = path.join(currentDir, item);
375
+ const stat = await fs.promises.stat(itemPath);
376
+
377
+ if (stat.isDirectory()) {
378
+ await traverse(itemPath);
379
+ } else {
380
+ files.push(itemPath);
381
+ }
382
+ }
383
+ }
384
+
385
+ await traverse(dir);
386
+ return files;
387
+ }
388
+
389
+ /**
390
+ * Get package size information based on variant-filtered content
391
+ * @param {string} toolId - Tool identifier (claude, opencode, ampcode, droid)
392
+ * @param {string} variant - Variant name (lite, standard, pro)
393
+ * @returns {Promise<Object>} Object with size (bytes) and formattedSize
394
+ */
395
+ async getPackageSize(toolId, variant) {
396
+ // Get variant-filtered package contents
397
+ const contents = await this.getPackageContents(toolId, variant);
398
+
399
+ let totalSize = 0;
400
+
401
+ // Helper function to calculate size of a file or directory recursively
402
+ const calculatePathSize = async (itemPath) => {
403
+ try {
404
+ const stat = await fs.promises.stat(itemPath);
405
+
406
+ if (stat.isFile()) {
407
+ return stat.size;
408
+ } else if (stat.isDirectory()) {
409
+ // Recursively calculate directory size
410
+ let dirSize = 0;
411
+ const items = await fs.promises.readdir(itemPath);
412
+
413
+ for (const item of items) {
414
+ const subPath = path.join(itemPath, item);
415
+ dirSize += await calculatePathSize(subPath);
416
+ }
417
+
418
+ return dirSize;
419
+ }
420
+ } catch (error) {
421
+ // Skip files that don't exist or can't be accessed
422
+ return 0;
423
+ }
424
+
425
+ return 0;
426
+ };
427
+
428
+ // Calculate size for all selected agents (files)
429
+ for (const agentPath of contents.agents) {
430
+ totalSize += await calculatePathSize(agentPath);
431
+ }
432
+
433
+ // Calculate size for all selected skills (directories)
434
+ for (const skillPath of contents.skills) {
435
+ totalSize += await calculatePathSize(skillPath);
436
+ }
437
+
438
+ // Calculate size for all selected resources (files)
439
+ for (const resourcePath of contents.resources) {
440
+ totalSize += await calculatePathSize(resourcePath);
441
+ }
442
+
443
+ // Calculate size for all selected hooks (files)
444
+ for (const hookPath of contents.hooks) {
445
+ totalSize += await calculatePathSize(hookPath);
446
+ }
447
+
448
+ return {
449
+ size: totalSize,
450
+ formattedSize: this.formatBytes(totalSize)
451
+ };
452
+ }
453
+
454
+ /**
455
+ * Format bytes to human readable format
456
+ */
457
+ formatBytes(bytes) {
458
+ if (bytes === 0) return '0 Bytes';
459
+
460
+ const k = 1024;
461
+ const sizes = ['Bytes', 'KB', 'MB', 'GB'];
462
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
463
+
464
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
465
+ }
466
+
467
+ /**
468
+ * Validate package integrity with variant support
469
+ * Checks variants.json exists and is valid, and validates that variant-selected content exists
470
+ * @param {string} toolId - Tool identifier (claude, opencode, ampcode, droid)
471
+ * @param {string} variant - Variant name (lite, standard, pro)
472
+ * @returns {Promise<Object>} Validation result with detailed information
473
+ */
474
+ async validatePackage(toolId, variant) {
475
+ const packageDir = path.join(this.packagesDir, toolId);
476
+ const issues = [];
477
+ let checkedFiles = 0;
478
+ let missingFiles = 0;
479
+
480
+ // Check 1: Package directory exists
481
+ if (!fs.existsSync(packageDir)) {
482
+ return {
483
+ valid: false,
484
+ error: 'Package directory not found',
485
+ issues: [`Package directory not found: ${packageDir}`],
486
+ checkedFiles: 0,
487
+ missingFiles: 0
488
+ };
489
+ }
490
+
491
+ // Check 2: variants.json exists
492
+ const variantsFilePath = path.join(packageDir, 'variants.json');
493
+ if (!fs.existsSync(variantsFilePath)) {
494
+ return {
495
+ valid: false,
496
+ error: 'variants.json file not found',
497
+ issues: [`variants.json file not found in package: ${toolId}`],
498
+ checkedFiles: 0,
499
+ missingFiles: 0
500
+ };
501
+ }
502
+
503
+ // Check 3: variants.json is valid JSON
504
+ let config;
505
+ try {
506
+ config = await this.loadVariantConfig(toolId);
507
+ } catch (error) {
508
+ return {
509
+ valid: false,
510
+ error: error.message,
511
+ issues: [`Invalid variants.json: ${error.message}`],
512
+ checkedFiles: 0,
513
+ missingFiles: 0
514
+ };
515
+ }
516
+
517
+ // Check 4: Required variant is present (pro)
518
+ const requiredVariants = ['pro'];
519
+ for (const reqVariant of requiredVariants) {
520
+ if (!config[reqVariant]) {
521
+ return {
522
+ valid: false,
523
+ error: `Required variant '${reqVariant}' not found`,
524
+ issues: [`Required variant '${reqVariant}' not found in variants.json`],
525
+ checkedFiles: 0,
526
+ missingFiles: 0
527
+ };
528
+ }
529
+ }
530
+
531
+ // Check 5: Each variant has required fields
532
+ // Note: 'skills', 'commands', 'resources', 'hooks' are optional
533
+ const requiredFields = ['name', 'description', 'agents'];
534
+ for (const reqVariant of requiredVariants) {
535
+ const variantConfig = config[reqVariant];
536
+ for (const field of requiredFields) {
537
+ if (variantConfig[field] === undefined) {
538
+ return {
539
+ valid: false,
540
+ error: `Required field '${field}' missing in '${reqVariant}' variant`,
541
+ issues: [`Required field '${field}' missing in '${reqVariant}' variant`],
542
+ checkedFiles: 0,
543
+ missingFiles: 0
544
+ };
545
+ }
546
+ }
547
+ }
548
+
549
+ // Check 6: Validate that variant-selected content actually exists
550
+ try {
551
+ // Get available content
552
+ const availableContent = await this.getAvailableContent(packageDir);
553
+
554
+ // Get selected content for the specified variant
555
+ const selectedContent = await this.selectVariantContent(toolId, variant, availableContent);
556
+
557
+ // Helper function to check if file/directory exists
558
+ const checkContentExists = async (category, items, dirName, addExtension = false) => {
559
+ const categoryDir = path.join(packageDir, dirName);
560
+
561
+ // If directory doesn't exist but items are expected, that's an issue
562
+ if (items.length > 0 && !fs.existsSync(categoryDir)) {
563
+ issues.push(`Directory '${dirName}' not found but ${category} are selected in ${variant} variant`);
564
+ missingFiles += items.length;
565
+ checkedFiles += items.length;
566
+ return;
567
+ }
568
+
569
+ // Check each item
570
+ for (const item of items) {
571
+ checkedFiles++;
572
+ let itemPath;
573
+
574
+ if (addExtension) {
575
+ // For agents, add .md extension
576
+ itemPath = path.join(categoryDir, `${item}.md`);
577
+ } else if (dirName === 'skills') {
578
+ // For skills, check directory exists
579
+ itemPath = path.join(categoryDir, item);
580
+ } else {
581
+ // For resources and hooks, use filename as-is
582
+ itemPath = path.join(categoryDir, item);
583
+ }
584
+
585
+ if (!fs.existsSync(itemPath)) {
586
+ const displayPath = addExtension ? `${item}.md` : item;
587
+ issues.push(`${category.slice(0, -1)} '${displayPath}' not found in ${dirName}/ (required by ${variant} variant)`);
588
+ missingFiles++;
589
+ }
590
+ }
591
+ };
592
+
593
+ // Validate agents (use dynamic directory name)
594
+ const agentsDirName = availableContent.agentsDir || 'agents';
595
+ await checkContentExists('agents', selectedContent.agents, agentsDirName, true);
596
+
597
+ // Validate skills (directories)
598
+ await checkContentExists('skills', selectedContent.skills || [], 'skills', false);
599
+
600
+ // Validate commands (use dynamic directory name)
601
+ const commandsDirName = availableContent.commandsDir || 'commands';
602
+ await checkContentExists('commands', selectedContent.commands || [], commandsDirName, true);
603
+
604
+ } catch (error) {
605
+ return {
606
+ valid: false,
607
+ error: error.message,
608
+ issues: [`Content validation failed: ${error.message}`],
609
+ checkedFiles,
610
+ missingFiles
611
+ };
612
+ }
613
+
614
+ // Final result
615
+ const valid = issues.length === 0;
616
+
617
+ return {
618
+ valid,
619
+ issues,
620
+ checkedFiles,
621
+ missingFiles,
622
+ ...(valid ? {} : { error: `Package validation failed: ${issues.length} issue(s) found` })
623
+ };
624
+ }
625
+
626
+ /**
627
+ * Get tool-specific manifest template
628
+ */
629
+ getManifestTemplate(toolId) {
630
+ const templatePath = path.join(__dirname, '..', 'tools', toolId, 'manifest-template.json');
631
+
632
+ if (!fs.existsSync(templatePath)) {
633
+ throw new Error(`Manifest template not found for tool: ${toolId}`);
634
+ }
635
+
636
+ return JSON.parse(fs.readFileSync(templatePath, 'utf8'));
637
+ }
638
+ }
639
+
640
+ module.exports = PackageManager;