@longph2102/v-flow 1.5.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 (247) hide show
  1. package/AGENTS.md +265 -0
  2. package/CHANGELOG.md +318 -0
  3. package/LICENSE +21 -0
  4. package/README.md +326 -0
  5. package/agents/ba-agent.md +437 -0
  6. package/agents/ba-critic-agent.md +156 -0
  7. package/agents/ba-to-ptyc-agent.md +112 -0
  8. package/agents/bugfix-analyst-agent.md +221 -0
  9. package/agents/constitute-agent.md +155 -0
  10. package/agents/help-agent.md +168 -0
  11. package/agents/implement-agent.md +220 -0
  12. package/agents/import-ba-docs-agent.md +164 -0
  13. package/agents/master-check-agent.md +228 -0
  14. package/agents/metrics-agent.md +180 -0
  15. package/agents/operations-agent.md +123 -0
  16. package/agents/plan-agent.md +218 -0
  17. package/agents/prototype-agent.md +191 -0
  18. package/agents/retrospective-agent.md +196 -0
  19. package/agents/review-agent.md +210 -0
  20. package/agents/sprint-agent.md +191 -0
  21. package/agents/status-agent.md +186 -0
  22. package/agents/sync-agent.md +201 -0
  23. package/agents/test-agent.md +166 -0
  24. package/agents/understand-agent.md +339 -0
  25. package/cli/commands/check.js +96 -0
  26. package/cli/commands/dev-quiz.js +107 -0
  27. package/cli/commands/doctor.js +348 -0
  28. package/cli/commands/feature.js +259 -0
  29. package/cli/commands/hooks.js +163 -0
  30. package/cli/commands/init.js +189 -0
  31. package/cli/commands/log.js +199 -0
  32. package/cli/commands/plugin.js +230 -0
  33. package/cli/commands/score-card.js +203 -0
  34. package/cli/commands/status.js +269 -0
  35. package/cli/commands/sync.js +59 -0
  36. package/cli/commands/upgrade.js +150 -0
  37. package/cli/commands/validate.js +1259 -0
  38. package/cli/commands/watch.js +151 -0
  39. package/cli/index.js +46 -0
  40. package/cli/lib/ac-test-gate.js +89 -0
  41. package/cli/lib/activity-log.js +209 -0
  42. package/cli/lib/cli-error.js +183 -0
  43. package/cli/lib/constitution-lint.js +561 -0
  44. package/cli/lib/dev-quiz-grade.js +127 -0
  45. package/cli/lib/governance.js +78 -0
  46. package/cli/lib/hook-targets.js +167 -0
  47. package/cli/lib/i18n.js +375 -0
  48. package/cli/lib/knowledge-oracle.js +379 -0
  49. package/cli/lib/logger.js +203 -0
  50. package/cli/lib/module-card-lint.js +304 -0
  51. package/cli/lib/module-card-score.js +223 -0
  52. package/cli/lib/plugins.js +481 -0
  53. package/cli/lib/scanner.js +692 -0
  54. package/cli/lib/sync-core.js +232 -0
  55. package/cli/lib/sync-targets.js +84 -0
  56. package/cli/lib/templates.js +268 -0
  57. package/cli/lib/yaml-parser.js +203 -0
  58. package/commands/v.ba-critic.md +101 -0
  59. package/commands/v.ba-to-ptyc.md +71 -0
  60. package/commands/v.bugfix.md +86 -0
  61. package/commands/v.check.md +131 -0
  62. package/commands/v.constitute.md +87 -0
  63. package/commands/v.constitution.md +84 -0
  64. package/commands/v.fork.md +127 -0
  65. package/commands/v.help.md +73 -0
  66. package/commands/v.hotfix.md +200 -0
  67. package/commands/v.implement.md +92 -0
  68. package/commands/v.import-ba-docs.md +222 -0
  69. package/commands/v.metrics.md +74 -0
  70. package/commands/v.operations.md +70 -0
  71. package/commands/v.plan.md +78 -0
  72. package/commands/v.prototype.md +121 -0
  73. package/commands/v.quickfix.md +169 -0
  74. package/commands/v.retrospective.md +80 -0
  75. package/commands/v.review.md +78 -0
  76. package/commands/v.rewind.md +127 -0
  77. package/commands/v.specify.md +118 -0
  78. package/commands/v.sprint.md +75 -0
  79. package/commands/v.status.md +62 -0
  80. package/commands/v.sync.md +81 -0
  81. package/commands/v.test.md +67 -0
  82. package/commands/v.understand.md +112 -0
  83. package/package.json +65 -0
  84. package/skills/_shared/constitution-reader/SKILL.md +109 -0
  85. package/skills/_shared/constitution-reader/config.json +52 -0
  86. package/skills/_shared/constitution-reader/examples/good/b1-phase-output.md +48 -0
  87. package/skills/_shared/constitution-reader/gotchas.md +46 -0
  88. package/skills/_shared/context-reader/SKILL.md +111 -0
  89. package/skills/_shared/context-reader/config.json +54 -0
  90. package/skills/_shared/context-reader/examples/good/legacy-nodejs-output.md +35 -0
  91. package/skills/_shared/context-reader/gotchas.md +49 -0
  92. package/skills/_shared/ears-notation/SKILL.md +63 -0
  93. package/skills/_shared/ears-notation/config.json +55 -0
  94. package/skills/_shared/ears-notation/examples/good/plan-test-interpretation.md +29 -0
  95. package/skills/_shared/ears-notation/gotchas.md +43 -0
  96. package/skills/check/cross-validator/SKILL.md +206 -0
  97. package/skills/check/cross-validator/config.json +33 -0
  98. package/skills/check/cross-validator/examples/good/validation-report-pass-with-concerns.md +105 -0
  99. package/skills/check/cross-validator/gotchas.md +43 -0
  100. package/skills/implement/constitution-enforcer/SKILL.md +134 -0
  101. package/skills/implement/constitution-enforcer/config.json +16 -0
  102. package/skills/implement/constitution-enforcer/examples/bad/vague-report.md +42 -0
  103. package/skills/implement/constitution-enforcer/examples/good/compliance-report.md +57 -0
  104. package/skills/implement/constitution-enforcer/gotchas.md +26 -0
  105. package/skills/implement/constitution-enforcer/scripts/check-constitution.sh +88 -0
  106. package/skills/implement/no-go-zone-guard/SKILL.md +173 -0
  107. package/skills/implement/no-go-zone-guard/config.json +28 -0
  108. package/skills/implement/no-go-zone-guard/examples/good/adapter-workaround.md +46 -0
  109. package/skills/implement/no-go-zone-guard/gotchas.md +27 -0
  110. package/skills/implement/no-go-zone-guard/scripts/check-nogo-zones.sh +148 -0
  111. package/skills/implement/no-go-zone-guard/scripts/nogo-precommit.sh +96 -0
  112. package/skills/implement/tdd-driver/SKILL.md +159 -0
  113. package/skills/implement/tdd-driver/config.json +33 -0
  114. package/skills/implement/tdd-driver/examples/good/tdd-cycle-product-repo.md +81 -0
  115. package/skills/implement/tdd-driver/gotchas.md +34 -0
  116. package/skills/metrics/metrics-collector/SKILL.md +133 -0
  117. package/skills/metrics/metrics-collector/config.json +16 -0
  118. package/skills/metrics/metrics-collector/examples/bad/incomplete-report.md +48 -0
  119. package/skills/metrics/metrics-collector/examples/good/full-metrics-report.md +101 -0
  120. package/skills/metrics/metrics-collector/gotchas.md +26 -0
  121. package/skills/operations/incident-runbook/SKILL.md +167 -0
  122. package/skills/operations/incident-runbook/config.json +21 -0
  123. package/skills/operations/incident-runbook/examples/bad/vague-incident-report.md +48 -0
  124. package/skills/operations/incident-runbook/examples/good/p1-hotfix-response.md +119 -0
  125. package/skills/operations/incident-runbook/gotchas.md +26 -0
  126. package/skills/plan/architecture-designer/SKILL.md +228 -0
  127. package/skills/plan/architecture-designer/config.json +32 -0
  128. package/skills/plan/architecture-designer/examples/bad/vague-plan.md +62 -0
  129. package/skills/plan/architecture-designer/examples/good/expand-contract-migration.md +56 -0
  130. package/skills/plan/architecture-designer/examples/good/plan-structure.md +58 -0
  131. package/skills/plan/architecture-designer/gotchas.md +45 -0
  132. package/skills/plan/task-breakdown/SKILL.md +208 -0
  133. package/skills/plan/task-breakdown/config.json +26 -0
  134. package/skills/plan/task-breakdown/examples/bad/vague-tasks.md +77 -0
  135. package/skills/plan/task-breakdown/examples/good/spike-clarify-tasks.md +66 -0
  136. package/skills/plan/task-breakdown/examples/good/tasks-login-feature.md +111 -0
  137. package/skills/plan/task-breakdown/gotchas.md +39 -0
  138. package/skills/prototype/LOGIC.md +240 -0
  139. package/skills/prototype/SKILL.md +185 -0
  140. package/skills/prototype/UI.md +407 -0
  141. package/skills/prototype/config.json +104 -0
  142. package/skills/prototype/examples/bad/prototype-notes.md +68 -0
  143. package/skills/prototype/examples/good/prototype-notes-ui.md +109 -0
  144. package/skills/prototype/examples/good/prototype-notes.md +67 -0
  145. package/skills/prototype/gotchas.md +128 -0
  146. package/skills/prototype/scripts/check-flow-state.ps1 +112 -0
  147. package/skills/prototype/scripts/check-flow-state.sh +104 -0
  148. package/skills/prototype/scripts/check-prototype-cleanup.ps1 +124 -0
  149. package/skills/prototype/scripts/check-prototype-cleanup.sh +109 -0
  150. package/skills/prototype/scripts/check-prototype-notes.ps1 +107 -0
  151. package/skills/prototype/scripts/check-prototype-notes.sh +102 -0
  152. package/skills/review/adversarial-reviewer/SKILL.md +137 -0
  153. package/skills/review/adversarial-reviewer/config.json +32 -0
  154. package/skills/review/adversarial-reviewer/examples/good/review-report-template.md +56 -0
  155. package/skills/review/adversarial-reviewer/gotchas.md +46 -0
  156. package/skills/review/adversarial-reviewer/scripts/quick-security-scan.sh +52 -0
  157. package/skills/specify/ba-bpmn-doc-gen/SKILL.md +108 -0
  158. package/skills/specify/ba-bpmn-doc-gen/reference/reference-bpmn-generation.md +528 -0
  159. package/skills/specify/ba-bpmn-doc-gen/reference/reference-drawio-flowchart.md +466 -0
  160. package/skills/specify/ba-critic/SKILL.md +172 -0
  161. package/skills/specify/ba-critic/config.json +32 -0
  162. package/skills/specify/ba-critic/examples/good/critic-report-round1.md +51 -0
  163. package/skills/specify/ba-critic/gotchas.md +40 -0
  164. package/skills/specify/ba-critic/scripts/check-spec-quality.sh +72 -0
  165. package/skills/specify/ba-doc-generator/SKILL.md +102 -0
  166. package/skills/specify/ba-doc-generator/references/template-clevel.md +84 -0
  167. package/skills/specify/ba-doc-generator/references/template-compliance.md +83 -0
  168. package/skills/specify/ba-doc-generator/references/template-dev.md +138 -0
  169. package/skills/specify/ba-doc-generator/references/template-partner.md +167 -0
  170. package/skills/specify/ba-doc-generator/references/template-pm.md +92 -0
  171. package/skills/specify/ba-doc-generator/references/template-review.md +114 -0
  172. package/skills/specify/ba-doc-generator/references/template-tester.md +108 -0
  173. package/skills/specify/ba-doc-generator/references/template-user.md +98 -0
  174. package/skills/specify/bugfix-analyst/SKILL.md +296 -0
  175. package/skills/specify/bugfix-analyst/config.json +41 -0
  176. package/skills/specify/bugfix-analyst/examples/bad/common-mistakes.md +71 -0
  177. package/skills/specify/bugfix-analyst/examples/good/email-validation-bugfix.md +53 -0
  178. package/skills/specify/bugfix-analyst/gotchas.md +51 -0
  179. package/skills/specify/ears-writer/SKILL.md +129 -0
  180. package/skills/specify/ears-writer/config.json +20 -0
  181. package/skills/specify/ears-writer/examples/bad/common-mistakes.md +17 -0
  182. package/skills/specify/ears-writer/examples/good/login-requirements.md +41 -0
  183. package/skills/specify/ears-writer/gotchas.md +43 -0
  184. package/skills/specify/ears-writer/scripts/check-ears-compliance.sh +51 -0
  185. package/skills/test/test-case-generator/SKILL.md +161 -0
  186. package/skills/test/test-case-generator/config.json +33 -0
  187. package/skills/test/test-case-generator/examples/good/test-cases-login.md +104 -0
  188. package/skills/test/test-case-generator/gotchas.md +43 -0
  189. package/skills/understand/ba-docs-scanner/SKILL.md +239 -0
  190. package/skills/understand/ba-docs-scanner/config.json +47 -0
  191. package/skills/understand/ba-docs-scanner/examples/good/work-order-br-extract.md +28 -0
  192. package/skills/understand/ba-docs-scanner/gotchas.md +44 -0
  193. package/skills/understand/ba-docs-scanner/merge-rules.md +47 -0
  194. package/skills/understand/codebase-scanner/SKILL.md +260 -0
  195. package/skills/understand/codebase-scanner/config.json +56 -0
  196. package/skills/understand/codebase-scanner/examples/good/menu-module-output.md +44 -0
  197. package/skills/understand/codebase-scanner/gotchas.md +42 -0
  198. package/skills/understand/codebase-scanner/scripts/scan-project-structure.sh +64 -0
  199. package/templates/DESIGN.md +456 -0
  200. package/templates/agent-command-template.yaml +240 -0
  201. package/templates/agent-config-template.md +170 -0
  202. package/templates/agent-definition-template.md +145 -0
  203. package/templates/agent-metrics-template.md +150 -0
  204. package/templates/api-contract-template.md +72 -0
  205. package/templates/bugfix-report-template.md +195 -0
  206. package/templates/bugfix-spec-template.md +134 -0
  207. package/templates/code-review-report-template.md +119 -0
  208. package/templates/constitution-template.md +234 -0
  209. package/templates/context-template.md +94 -0
  210. package/templates/data-model-template.md +95 -0
  211. package/templates/decision-log-template.md +92 -0
  212. package/templates/flow-state-template.yaml +208 -0
  213. package/templates/github/workflows/v-flow-validate.yml +30 -0
  214. package/templates/knowledge/adr-template.md +70 -0
  215. package/templates/knowledge/api-contract-template.md +140 -0
  216. package/templates/knowledge/domain-glossary.md +29 -0
  217. package/templates/knowledge/golden-tests-readme.md +115 -0
  218. package/templates/knowledge/lessons-learned.md +41 -0
  219. package/templates/knowledge/patterns.md +103 -0
  220. package/templates/module-card/SKILL.md +85 -0
  221. package/templates/module-card/api-specs.md +96 -0
  222. package/templates/module-card/business-quiz.md +119 -0
  223. package/templates/module-card/cross-service.md +125 -0
  224. package/templates/module-card/db.md +85 -0
  225. package/templates/module-card/dev-quiz.md +62 -0
  226. package/templates/module-card/permissions.md +83 -0
  227. package/templates/module-card/state-diagram.md +64 -0
  228. package/templates/module-card/tech-context.md +90 -0
  229. package/templates/module-card/ui-flows.md +91 -0
  230. package/templates/module-card/use-cases.md +142 -0
  231. package/templates/module-template.yaml +161 -0
  232. package/templates/operations-report-template.md +108 -0
  233. package/templates/plan-template.md +308 -0
  234. package/templates/prototype-notes-template.md +116 -0
  235. package/templates/ptyc/PTYC.template.docx +0 -0
  236. package/templates/ptyc/ptyc.meta.example.yaml +44 -0
  237. package/templates/retrospective-report-template.md +136 -0
  238. package/templates/security-review-template.md +84 -0
  239. package/templates/session-template.md +167 -0
  240. package/templates/spec-review-log-template.md +75 -0
  241. package/templates/spec-template.md +229 -0
  242. package/templates/sprint-status-template.md +101 -0
  243. package/templates/tasks-template.md +275 -0
  244. package/templates/test-cases-template.md +124 -0
  245. package/templates/ux-checklist-template.md +79 -0
  246. package/templates/validation-report-template.md +125 -0
  247. package/templates/vflow-config-template.yaml +22 -0
@@ -0,0 +1,481 @@
1
+ /**
2
+ * Plugin System — V-Flow Extension Architecture
3
+ *
4
+ * Discovers, loads, and registers custom plugins from:
5
+ * 1. .v-flow/plugins/ (project-level)
6
+ * 2. ~/.v-flow/plugins/ (user-level, future)
7
+ *
8
+ * Plugin Structure:
9
+ * my-plugin/
10
+ * ├── plugin.json ← Manifest (name, version, type, entry)
11
+ * ├── agent.md ← Custom agent definition (optional)
12
+ * ├── workflow.md ← Custom slash command (optional)
13
+ * └── skill/ ← Custom skill directory (optional)
14
+ * ├── SKILL.md
15
+ * ├── config.json
16
+ * └── gotchas.md
17
+ */
18
+
19
+ const path = require('path');
20
+ const fs = require('fs-extra');
21
+
22
+ /**
23
+ * Plugin manifest schema (plugin.json)
24
+ */
25
+ const REQUIRED_MANIFEST_FIELDS = ['name', 'version', 'type'];
26
+ const VALID_TYPES = ['agent', 'workflow', 'skill', 'composite'];
27
+
28
+ /**
29
+ * Discover plugins from .v-flow/plugins/ directory
30
+ *
31
+ * @param {string} projectRoot - Project root directory
32
+ * @returns {Array<{ name, version, type, dir, manifest }>} Discovered plugins
33
+ */
34
+ async function discoverPlugins(projectRoot) {
35
+ const pluginsDir = path.join(projectRoot, '.v-flow', 'plugins');
36
+ if (!fs.existsSync(pluginsDir)) return [];
37
+
38
+ const entries = await fs.readdir(pluginsDir, { withFileTypes: true });
39
+ const plugins = [];
40
+
41
+ for (const entry of entries) {
42
+ if (!entry.isDirectory()) continue;
43
+ if (entry.name.startsWith('.') || entry.name.startsWith('_')) continue;
44
+
45
+ const pluginDir = path.join(pluginsDir, entry.name);
46
+ const manifestPath = path.join(pluginDir, 'plugin.json');
47
+
48
+ if (!fs.existsSync(manifestPath)) continue;
49
+
50
+ try {
51
+ const manifest = await fs.readJson(manifestPath);
52
+ const validation = validateManifest(manifest);
53
+
54
+ if (validation.valid) {
55
+ plugins.push({
56
+ name: manifest.name,
57
+ version: manifest.version,
58
+ type: manifest.type,
59
+ description: manifest.description || '',
60
+ dir: pluginDir,
61
+ manifest,
62
+ });
63
+ }
64
+ } catch {
65
+ // Skip invalid plugins silently during discovery
66
+ }
67
+ }
68
+
69
+ return plugins;
70
+ }
71
+
72
+ /**
73
+ * Validate plugin manifest
74
+ *
75
+ * @param {object} manifest - Parsed plugin.json
76
+ * @returns {{ valid: boolean, errors: string[] }}
77
+ */
78
+ function validateManifest(manifest) {
79
+ const errors = [];
80
+
81
+ for (const field of REQUIRED_MANIFEST_FIELDS) {
82
+ if (!manifest[field]) {
83
+ errors.push(`Missing required field: ${field}`);
84
+ }
85
+ }
86
+
87
+ if (manifest.type && !VALID_TYPES.includes(manifest.type)) {
88
+ errors.push(`Invalid type: ${manifest.type}. Must be one of: ${VALID_TYPES.join(', ')}`);
89
+ }
90
+
91
+ if (manifest.version && !/^\d+\.\d+\.\d+/.test(manifest.version)) {
92
+ errors.push(`Invalid version format: ${manifest.version}. Use semver (e.g., 1.0.0)`);
93
+ }
94
+
95
+ return { valid: errors.length === 0, errors };
96
+ }
97
+
98
+ /**
99
+ * Load a single plugin — resolves its assets
100
+ *
101
+ * @param {{ name, dir, manifest }} plugin - Discovered plugin info
102
+ * @returns {{ name, type, agents, workflows, skills }}
103
+ */
104
+ async function loadPlugin(plugin) {
105
+ const result = {
106
+ name: plugin.name,
107
+ type: plugin.manifest.type,
108
+ agents: [],
109
+ workflows: [],
110
+ skills: [],
111
+ };
112
+
113
+ // Discover agent definitions (.md files in plugin root or agents/ subdir)
114
+ const agentFiles = await findMdFiles(plugin.dir, 'agent');
115
+ result.agents = agentFiles;
116
+
117
+ // Discover workflows (.md files)
118
+ const workflowFiles = await findMdFiles(plugin.dir, 'workflow');
119
+ result.workflows = workflowFiles;
120
+
121
+ // Discover skills (directories with SKILL.md)
122
+ const skillDir = path.join(plugin.dir, 'skill');
123
+ if (fs.existsSync(skillDir) && fs.existsSync(path.join(skillDir, 'SKILL.md'))) {
124
+ result.skills.push({
125
+ name: plugin.name,
126
+ dir: skillDir,
127
+ hasConfig: fs.existsSync(path.join(skillDir, 'config.json')),
128
+ hasGotchas: fs.existsSync(path.join(skillDir, 'gotchas.md')),
129
+ });
130
+ }
131
+
132
+ // Also check skills/ subdirectory for multiple skills
133
+ const skillsDir = path.join(plugin.dir, 'skills');
134
+ if (fs.existsSync(skillsDir)) {
135
+ const skillEntries = await fs.readdir(skillsDir, { withFileTypes: true });
136
+ for (const entry of skillEntries) {
137
+ if (entry.isDirectory()) {
138
+ const subSkillDir = path.join(skillsDir, entry.name);
139
+ if (fs.existsSync(path.join(subSkillDir, 'SKILL.md'))) {
140
+ result.skills.push({
141
+ name: entry.name,
142
+ dir: subSkillDir,
143
+ hasConfig: fs.existsSync(path.join(subSkillDir, 'config.json')),
144
+ hasGotchas: fs.existsSync(path.join(subSkillDir, 'gotchas.md')),
145
+ });
146
+ }
147
+ }
148
+ }
149
+ }
150
+
151
+ return result;
152
+ }
153
+
154
+ /**
155
+ * Install plugin assets to agent targets
156
+ *
157
+ * @param {string} projectRoot
158
+ * @param {object} loadedPlugin - Result from loadPlugin()
159
+ * @param {object} options - { dryRun, verbose }
160
+ * @returns {{ installed: number, skipped: number }}
161
+ */
162
+ async function installPlugin(projectRoot, loadedPlugin, options = {}) {
163
+ const { dryRun = false } = options;
164
+ const stats = { installed: 0, skipped: 0 };
165
+
166
+ // Install agents
167
+ for (const agentFile of loadedPlugin.agents) {
168
+ const destAntigravity = path.join(projectRoot, '.agents', 'agents', path.basename(agentFile));
169
+ const destCopilot = path.join(projectRoot, '.github', 'agents', path.basename(agentFile));
170
+
171
+ if (!dryRun) {
172
+ await fs.ensureDir(path.dirname(destAntigravity));
173
+ await fs.ensureDir(path.dirname(destCopilot));
174
+ await fs.copy(agentFile, destAntigravity);
175
+ await fs.copy(agentFile, destCopilot);
176
+ }
177
+ stats.installed++;
178
+ }
179
+
180
+ // Install workflows
181
+ for (const workflowFile of loadedPlugin.workflows) {
182
+ const destAntigravity = path.join(projectRoot, '.agents', 'workflows', path.basename(workflowFile));
183
+ const destCopilot = path.join(projectRoot, '.github', 'prompts', path.basename(workflowFile));
184
+
185
+ if (!dryRun) {
186
+ await fs.ensureDir(path.dirname(destAntigravity));
187
+ await fs.ensureDir(path.dirname(destCopilot));
188
+ await fs.copy(workflowFile, destAntigravity);
189
+ await fs.copy(workflowFile, destCopilot);
190
+ }
191
+ stats.installed++;
192
+ }
193
+
194
+ // Install skills
195
+ for (const skill of loadedPlugin.skills) {
196
+ const destAntigravity = path.join(projectRoot, '.agents', 'skills', 'plugins', skill.name);
197
+ const destCopilot = path.join(projectRoot, '.github', 'skills', 'plugins', skill.name);
198
+
199
+ if (!dryRun) {
200
+ await fs.ensureDir(destAntigravity);
201
+ await fs.ensureDir(destCopilot);
202
+ await fs.copy(skill.dir, destAntigravity, { overwrite: true });
203
+ await fs.copy(skill.dir, destCopilot, { overwrite: true });
204
+ }
205
+ stats.installed++;
206
+ }
207
+
208
+ return stats;
209
+ }
210
+
211
+ /**
212
+ * Scaffold a new plugin directory
213
+ *
214
+ * @param {string} projectRoot
215
+ * @param {string} pluginName
216
+ * @param {string} pluginType - 'agent' | 'workflow' | 'skill' | 'composite'
217
+ */
218
+ async function scaffoldPlugin(projectRoot, pluginName, pluginType = 'composite') {
219
+ const pluginDir = path.join(projectRoot, '.v-flow', 'plugins', pluginName);
220
+ if (fs.existsSync(pluginDir)) {
221
+ throw new Error(`Plugin already exists: ${pluginName}`);
222
+ }
223
+
224
+ await fs.ensureDir(pluginDir);
225
+
226
+ // Create manifest
227
+ const manifest = {
228
+ name: pluginName,
229
+ version: '1.0.0',
230
+ type: pluginType,
231
+ description: `Custom ${pluginType} plugin for V-Flow`,
232
+ author: '',
233
+ v_flow_version: '>=1.0.0',
234
+ };
235
+ await fs.writeJson(path.join(pluginDir, 'plugin.json'), manifest, { spaces: 2 });
236
+
237
+ // Create type-specific files
238
+ if (pluginType === 'agent' || pluginType === 'composite') {
239
+ await fs.writeFile(path.join(pluginDir, 'agent.md'), `# ${pluginName} Agent
240
+
241
+ ## Persona
242
+ You are the ${pluginName} Agent — *(describe role)*.
243
+
244
+ ## Responsibilities
245
+ - *(list responsibilities)*
246
+
247
+ ## Input
248
+ - *(what this agent reads)*
249
+
250
+ ## Output
251
+ - *(what this agent produces)*
252
+
253
+ ## Routing
254
+ - **On success**: → *(next phase)*
255
+ - **On failure**: → *(fallback)*
256
+ `, 'utf8');
257
+ }
258
+
259
+ if (pluginType === 'workflow' || pluginType === 'composite') {
260
+ await fs.writeFile(path.join(pluginDir, 'workflow.md'), `---
261
+ description: ${pluginName} workflow
262
+ ---
263
+
264
+ # /${pluginName}
265
+
266
+ ## Mục đích
267
+ *(Describe what this workflow does)*
268
+
269
+ ## Workflow Steps
270
+
271
+ ### Step 1: *(Step name)*
272
+ *(Instructions)*
273
+
274
+ ### Step 2: *(Step name)*
275
+ *(Instructions)*
276
+
277
+ ## Output
278
+ *(What this workflow produces)*
279
+ `, 'utf8');
280
+ }
281
+
282
+ if (pluginType === 'skill' || pluginType === 'composite') {
283
+ const skillDir = path.join(pluginDir, 'skill');
284
+ await fs.ensureDir(skillDir);
285
+
286
+ await fs.writeFile(path.join(skillDir, 'SKILL.md'), `---
287
+ name: ${pluginName}
288
+ version: 1.0.0
289
+ category: custom
290
+ agent: *(which agent uses this skill)*
291
+ trigger: manual
292
+ ---
293
+
294
+ # ${pluginName}
295
+
296
+ ## Purpose
297
+ *(Describe what this skill teaches the agent)*
298
+
299
+ ## When to Use
300
+ - *(Trigger conditions)*
301
+
302
+ ## Procedure
303
+ 1. *(Step 1)*
304
+ 2. *(Step 2)*
305
+ 3. *(Step 3)*
306
+
307
+ ## Quality Checklist
308
+ - [ ] *(Check 1)*
309
+ - [ ] *(Check 2)*
310
+ `, 'utf8');
311
+
312
+ await fs.writeJson(path.join(skillDir, 'config.json'), {
313
+ name: pluginName,
314
+ version: '1.0.0',
315
+ category: 'custom',
316
+ triggers: ['manual'],
317
+ input_artifacts: [],
318
+ output_artifacts: [],
319
+ }, { spaces: 2 });
320
+
321
+ await fs.writeFile(path.join(skillDir, 'gotchas.md'), `# Gotchas — ${pluginName}
322
+
323
+ ## Common Mistakes
324
+ 1. *(Describe common mistake)*
325
+
326
+ ## Edge Cases
327
+ 1. *(Describe edge case)*
328
+ `, 'utf8');
329
+ }
330
+
331
+ return pluginDir;
332
+ }
333
+
334
+ // ============================================================
335
+ // Internal Helpers
336
+ // ============================================================
337
+
338
+ /**
339
+ * Find .md files matching a pattern name in a plugin directory
340
+ */
341
+ async function findMdFiles(dir, pattern) {
342
+ const found = [];
343
+ const entries = await fs.readdir(dir);
344
+
345
+ for (const file of entries) {
346
+ if (file.endsWith('.md') && file.toLowerCase().includes(pattern)) {
347
+ found.push(path.join(dir, file));
348
+ }
349
+ }
350
+
351
+ return found;
352
+ }
353
+
354
+ // ============================================================
355
+ // Lifecycle Hooks
356
+ // ============================================================
357
+
358
+ /**
359
+ * Execute plugin lifecycle hook (onInstall / onUninstall).
360
+ *
361
+ * Hooks are defined in plugin.json:
362
+ * "hooks": {
363
+ * "onInstall": "echo 'Plugin installed'",
364
+ * "onUninstall": "echo 'Plugin uninstalled'"
365
+ * }
366
+ *
367
+ * @param {object} plugin - Plugin info with manifest
368
+ * @param {'onInstall'|'onUninstall'} hookName
369
+ * @param {object} context - { projectRoot, dryRun }
370
+ * @returns {{ executed: boolean, message: string }}
371
+ */
372
+ async function executeLifecycleHook(plugin, hookName, context = {}) {
373
+ const hooks = plugin.manifest.hooks || {};
374
+ const hookValue = hooks[hookName];
375
+
376
+ if (!hookValue) {
377
+ return { executed: false, message: `No ${hookName} hook defined` };
378
+ }
379
+
380
+ if (context.dryRun) {
381
+ return { executed: false, message: `[dry-run] Would execute ${hookName}: ${hookValue}` };
382
+ }
383
+
384
+ // Hook can be a plain message string (logged only) or a script path
385
+ if (typeof hookValue === 'string') {
386
+ // Check if it's a file path (starts with ./ or has extension)
387
+ const isScript = hookValue.startsWith('./') || hookValue.endsWith('.js');
388
+ if (isScript) {
389
+ const scriptPath = path.resolve(plugin.dir, hookValue);
390
+ if (fs.existsSync(scriptPath)) {
391
+ try {
392
+ const hookModule = require(scriptPath);
393
+ if (typeof hookModule === 'function') {
394
+ await hookModule({ projectRoot: context.projectRoot, plugin });
395
+ }
396
+ return { executed: true, message: `Executed hook script: ${hookValue}` };
397
+ } catch (err) {
398
+ return { executed: false, message: `Hook error: ${err.message}` };
399
+ }
400
+ }
401
+ return { executed: false, message: `Hook script not found: ${hookValue}` };
402
+ }
403
+ // Plain message — just log it
404
+ return { executed: true, message: hookValue };
405
+ }
406
+
407
+ return { executed: false, message: `Invalid hook format for ${hookName}` };
408
+ }
409
+
410
+ /**
411
+ * Uninstall a plugin — remove installed assets from agent targets
412
+ *
413
+ * @param {string} projectRoot
414
+ * @param {object} loadedPlugin - Result from loadPlugin()
415
+ * @param {object} options - { dryRun }
416
+ * @returns {{ removed: number }}
417
+ */
418
+ async function uninstallPlugin(projectRoot, loadedPlugin, options = {}) {
419
+ const { dryRun = false } = options;
420
+ const stats = { removed: 0 };
421
+
422
+ // Remove agents
423
+ for (const agentFile of loadedPlugin.agents) {
424
+ const basename = path.basename(agentFile);
425
+ const targets = [
426
+ path.join(projectRoot, '.agents', 'agents', basename),
427
+ path.join(projectRoot, '.github', 'agents', basename),
428
+ ];
429
+ for (const target of targets) {
430
+ if (fs.existsSync(target)) {
431
+ if (!dryRun) await fs.remove(target);
432
+ stats.removed++;
433
+ }
434
+ }
435
+ }
436
+
437
+ // Remove workflows
438
+ for (const workflowFile of loadedPlugin.workflows) {
439
+ const basename = path.basename(workflowFile);
440
+ const targets = [
441
+ path.join(projectRoot, '.agents', 'workflows', basename),
442
+ path.join(projectRoot, '.github', 'prompts', basename),
443
+ ];
444
+ for (const target of targets) {
445
+ if (fs.existsSync(target)) {
446
+ if (!dryRun) await fs.remove(target);
447
+ stats.removed++;
448
+ }
449
+ }
450
+ }
451
+
452
+ // Remove skills
453
+ for (const skill of loadedPlugin.skills) {
454
+ const targets = [
455
+ path.join(projectRoot, '.agents', 'skills', 'plugins', skill.name),
456
+ path.join(projectRoot, '.github', 'skills', 'plugins', skill.name),
457
+ ];
458
+ for (const target of targets) {
459
+ if (fs.existsSync(target)) {
460
+ if (!dryRun) await fs.remove(target);
461
+ stats.removed++;
462
+ }
463
+ }
464
+ }
465
+
466
+ return stats;
467
+ }
468
+
469
+ module.exports = {
470
+ VALID_TYPES,
471
+ REQUIRED_MANIFEST_FIELDS,
472
+ discoverPlugins,
473
+ validateManifest,
474
+ loadPlugin,
475
+ installPlugin,
476
+ uninstallPlugin,
477
+ scaffoldPlugin,
478
+ findMdFiles,
479
+ executeLifecycleHook,
480
+ };
481
+