@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.
- package/AGENTS.md +265 -0
- package/CHANGELOG.md +318 -0
- package/LICENSE +21 -0
- package/README.md +326 -0
- package/agents/ba-agent.md +437 -0
- package/agents/ba-critic-agent.md +156 -0
- package/agents/ba-to-ptyc-agent.md +112 -0
- package/agents/bugfix-analyst-agent.md +221 -0
- package/agents/constitute-agent.md +155 -0
- package/agents/help-agent.md +168 -0
- package/agents/implement-agent.md +220 -0
- package/agents/import-ba-docs-agent.md +164 -0
- package/agents/master-check-agent.md +228 -0
- package/agents/metrics-agent.md +180 -0
- package/agents/operations-agent.md +123 -0
- package/agents/plan-agent.md +218 -0
- package/agents/prototype-agent.md +191 -0
- package/agents/retrospective-agent.md +196 -0
- package/agents/review-agent.md +210 -0
- package/agents/sprint-agent.md +191 -0
- package/agents/status-agent.md +186 -0
- package/agents/sync-agent.md +201 -0
- package/agents/test-agent.md +166 -0
- package/agents/understand-agent.md +339 -0
- package/cli/commands/check.js +96 -0
- package/cli/commands/dev-quiz.js +107 -0
- package/cli/commands/doctor.js +348 -0
- package/cli/commands/feature.js +259 -0
- package/cli/commands/hooks.js +163 -0
- package/cli/commands/init.js +189 -0
- package/cli/commands/log.js +199 -0
- package/cli/commands/plugin.js +230 -0
- package/cli/commands/score-card.js +203 -0
- package/cli/commands/status.js +269 -0
- package/cli/commands/sync.js +59 -0
- package/cli/commands/upgrade.js +150 -0
- package/cli/commands/validate.js +1259 -0
- package/cli/commands/watch.js +151 -0
- package/cli/index.js +46 -0
- package/cli/lib/ac-test-gate.js +89 -0
- package/cli/lib/activity-log.js +209 -0
- package/cli/lib/cli-error.js +183 -0
- package/cli/lib/constitution-lint.js +561 -0
- package/cli/lib/dev-quiz-grade.js +127 -0
- package/cli/lib/governance.js +78 -0
- package/cli/lib/hook-targets.js +167 -0
- package/cli/lib/i18n.js +375 -0
- package/cli/lib/knowledge-oracle.js +379 -0
- package/cli/lib/logger.js +203 -0
- package/cli/lib/module-card-lint.js +304 -0
- package/cli/lib/module-card-score.js +223 -0
- package/cli/lib/plugins.js +481 -0
- package/cli/lib/scanner.js +692 -0
- package/cli/lib/sync-core.js +232 -0
- package/cli/lib/sync-targets.js +84 -0
- package/cli/lib/templates.js +268 -0
- package/cli/lib/yaml-parser.js +203 -0
- package/commands/v.ba-critic.md +101 -0
- package/commands/v.ba-to-ptyc.md +71 -0
- package/commands/v.bugfix.md +86 -0
- package/commands/v.check.md +131 -0
- package/commands/v.constitute.md +87 -0
- package/commands/v.constitution.md +84 -0
- package/commands/v.fork.md +127 -0
- package/commands/v.help.md +73 -0
- package/commands/v.hotfix.md +200 -0
- package/commands/v.implement.md +92 -0
- package/commands/v.import-ba-docs.md +222 -0
- package/commands/v.metrics.md +74 -0
- package/commands/v.operations.md +70 -0
- package/commands/v.plan.md +78 -0
- package/commands/v.prototype.md +121 -0
- package/commands/v.quickfix.md +169 -0
- package/commands/v.retrospective.md +80 -0
- package/commands/v.review.md +78 -0
- package/commands/v.rewind.md +127 -0
- package/commands/v.specify.md +118 -0
- package/commands/v.sprint.md +75 -0
- package/commands/v.status.md +62 -0
- package/commands/v.sync.md +81 -0
- package/commands/v.test.md +67 -0
- package/commands/v.understand.md +112 -0
- package/package.json +65 -0
- package/skills/_shared/constitution-reader/SKILL.md +109 -0
- package/skills/_shared/constitution-reader/config.json +52 -0
- package/skills/_shared/constitution-reader/examples/good/b1-phase-output.md +48 -0
- package/skills/_shared/constitution-reader/gotchas.md +46 -0
- package/skills/_shared/context-reader/SKILL.md +111 -0
- package/skills/_shared/context-reader/config.json +54 -0
- package/skills/_shared/context-reader/examples/good/legacy-nodejs-output.md +35 -0
- package/skills/_shared/context-reader/gotchas.md +49 -0
- package/skills/_shared/ears-notation/SKILL.md +63 -0
- package/skills/_shared/ears-notation/config.json +55 -0
- package/skills/_shared/ears-notation/examples/good/plan-test-interpretation.md +29 -0
- package/skills/_shared/ears-notation/gotchas.md +43 -0
- package/skills/check/cross-validator/SKILL.md +206 -0
- package/skills/check/cross-validator/config.json +33 -0
- package/skills/check/cross-validator/examples/good/validation-report-pass-with-concerns.md +105 -0
- package/skills/check/cross-validator/gotchas.md +43 -0
- package/skills/implement/constitution-enforcer/SKILL.md +134 -0
- package/skills/implement/constitution-enforcer/config.json +16 -0
- package/skills/implement/constitution-enforcer/examples/bad/vague-report.md +42 -0
- package/skills/implement/constitution-enforcer/examples/good/compliance-report.md +57 -0
- package/skills/implement/constitution-enforcer/gotchas.md +26 -0
- package/skills/implement/constitution-enforcer/scripts/check-constitution.sh +88 -0
- package/skills/implement/no-go-zone-guard/SKILL.md +173 -0
- package/skills/implement/no-go-zone-guard/config.json +28 -0
- package/skills/implement/no-go-zone-guard/examples/good/adapter-workaround.md +46 -0
- package/skills/implement/no-go-zone-guard/gotchas.md +27 -0
- package/skills/implement/no-go-zone-guard/scripts/check-nogo-zones.sh +148 -0
- package/skills/implement/no-go-zone-guard/scripts/nogo-precommit.sh +96 -0
- package/skills/implement/tdd-driver/SKILL.md +159 -0
- package/skills/implement/tdd-driver/config.json +33 -0
- package/skills/implement/tdd-driver/examples/good/tdd-cycle-product-repo.md +81 -0
- package/skills/implement/tdd-driver/gotchas.md +34 -0
- package/skills/metrics/metrics-collector/SKILL.md +133 -0
- package/skills/metrics/metrics-collector/config.json +16 -0
- package/skills/metrics/metrics-collector/examples/bad/incomplete-report.md +48 -0
- package/skills/metrics/metrics-collector/examples/good/full-metrics-report.md +101 -0
- package/skills/metrics/metrics-collector/gotchas.md +26 -0
- package/skills/operations/incident-runbook/SKILL.md +167 -0
- package/skills/operations/incident-runbook/config.json +21 -0
- package/skills/operations/incident-runbook/examples/bad/vague-incident-report.md +48 -0
- package/skills/operations/incident-runbook/examples/good/p1-hotfix-response.md +119 -0
- package/skills/operations/incident-runbook/gotchas.md +26 -0
- package/skills/plan/architecture-designer/SKILL.md +228 -0
- package/skills/plan/architecture-designer/config.json +32 -0
- package/skills/plan/architecture-designer/examples/bad/vague-plan.md +62 -0
- package/skills/plan/architecture-designer/examples/good/expand-contract-migration.md +56 -0
- package/skills/plan/architecture-designer/examples/good/plan-structure.md +58 -0
- package/skills/plan/architecture-designer/gotchas.md +45 -0
- package/skills/plan/task-breakdown/SKILL.md +208 -0
- package/skills/plan/task-breakdown/config.json +26 -0
- package/skills/plan/task-breakdown/examples/bad/vague-tasks.md +77 -0
- package/skills/plan/task-breakdown/examples/good/spike-clarify-tasks.md +66 -0
- package/skills/plan/task-breakdown/examples/good/tasks-login-feature.md +111 -0
- package/skills/plan/task-breakdown/gotchas.md +39 -0
- package/skills/prototype/LOGIC.md +240 -0
- package/skills/prototype/SKILL.md +185 -0
- package/skills/prototype/UI.md +407 -0
- package/skills/prototype/config.json +104 -0
- package/skills/prototype/examples/bad/prototype-notes.md +68 -0
- package/skills/prototype/examples/good/prototype-notes-ui.md +109 -0
- package/skills/prototype/examples/good/prototype-notes.md +67 -0
- package/skills/prototype/gotchas.md +128 -0
- package/skills/prototype/scripts/check-flow-state.ps1 +112 -0
- package/skills/prototype/scripts/check-flow-state.sh +104 -0
- package/skills/prototype/scripts/check-prototype-cleanup.ps1 +124 -0
- package/skills/prototype/scripts/check-prototype-cleanup.sh +109 -0
- package/skills/prototype/scripts/check-prototype-notes.ps1 +107 -0
- package/skills/prototype/scripts/check-prototype-notes.sh +102 -0
- package/skills/review/adversarial-reviewer/SKILL.md +137 -0
- package/skills/review/adversarial-reviewer/config.json +32 -0
- package/skills/review/adversarial-reviewer/examples/good/review-report-template.md +56 -0
- package/skills/review/adversarial-reviewer/gotchas.md +46 -0
- package/skills/review/adversarial-reviewer/scripts/quick-security-scan.sh +52 -0
- package/skills/specify/ba-bpmn-doc-gen/SKILL.md +108 -0
- package/skills/specify/ba-bpmn-doc-gen/reference/reference-bpmn-generation.md +528 -0
- package/skills/specify/ba-bpmn-doc-gen/reference/reference-drawio-flowchart.md +466 -0
- package/skills/specify/ba-critic/SKILL.md +172 -0
- package/skills/specify/ba-critic/config.json +32 -0
- package/skills/specify/ba-critic/examples/good/critic-report-round1.md +51 -0
- package/skills/specify/ba-critic/gotchas.md +40 -0
- package/skills/specify/ba-critic/scripts/check-spec-quality.sh +72 -0
- package/skills/specify/ba-doc-generator/SKILL.md +102 -0
- package/skills/specify/ba-doc-generator/references/template-clevel.md +84 -0
- package/skills/specify/ba-doc-generator/references/template-compliance.md +83 -0
- package/skills/specify/ba-doc-generator/references/template-dev.md +138 -0
- package/skills/specify/ba-doc-generator/references/template-partner.md +167 -0
- package/skills/specify/ba-doc-generator/references/template-pm.md +92 -0
- package/skills/specify/ba-doc-generator/references/template-review.md +114 -0
- package/skills/specify/ba-doc-generator/references/template-tester.md +108 -0
- package/skills/specify/ba-doc-generator/references/template-user.md +98 -0
- package/skills/specify/bugfix-analyst/SKILL.md +296 -0
- package/skills/specify/bugfix-analyst/config.json +41 -0
- package/skills/specify/bugfix-analyst/examples/bad/common-mistakes.md +71 -0
- package/skills/specify/bugfix-analyst/examples/good/email-validation-bugfix.md +53 -0
- package/skills/specify/bugfix-analyst/gotchas.md +51 -0
- package/skills/specify/ears-writer/SKILL.md +129 -0
- package/skills/specify/ears-writer/config.json +20 -0
- package/skills/specify/ears-writer/examples/bad/common-mistakes.md +17 -0
- package/skills/specify/ears-writer/examples/good/login-requirements.md +41 -0
- package/skills/specify/ears-writer/gotchas.md +43 -0
- package/skills/specify/ears-writer/scripts/check-ears-compliance.sh +51 -0
- package/skills/test/test-case-generator/SKILL.md +161 -0
- package/skills/test/test-case-generator/config.json +33 -0
- package/skills/test/test-case-generator/examples/good/test-cases-login.md +104 -0
- package/skills/test/test-case-generator/gotchas.md +43 -0
- package/skills/understand/ba-docs-scanner/SKILL.md +239 -0
- package/skills/understand/ba-docs-scanner/config.json +47 -0
- package/skills/understand/ba-docs-scanner/examples/good/work-order-br-extract.md +28 -0
- package/skills/understand/ba-docs-scanner/gotchas.md +44 -0
- package/skills/understand/ba-docs-scanner/merge-rules.md +47 -0
- package/skills/understand/codebase-scanner/SKILL.md +260 -0
- package/skills/understand/codebase-scanner/config.json +56 -0
- package/skills/understand/codebase-scanner/examples/good/menu-module-output.md +44 -0
- package/skills/understand/codebase-scanner/gotchas.md +42 -0
- package/skills/understand/codebase-scanner/scripts/scan-project-structure.sh +64 -0
- package/templates/DESIGN.md +456 -0
- package/templates/agent-command-template.yaml +240 -0
- package/templates/agent-config-template.md +170 -0
- package/templates/agent-definition-template.md +145 -0
- package/templates/agent-metrics-template.md +150 -0
- package/templates/api-contract-template.md +72 -0
- package/templates/bugfix-report-template.md +195 -0
- package/templates/bugfix-spec-template.md +134 -0
- package/templates/code-review-report-template.md +119 -0
- package/templates/constitution-template.md +234 -0
- package/templates/context-template.md +94 -0
- package/templates/data-model-template.md +95 -0
- package/templates/decision-log-template.md +92 -0
- package/templates/flow-state-template.yaml +208 -0
- package/templates/github/workflows/v-flow-validate.yml +30 -0
- package/templates/knowledge/adr-template.md +70 -0
- package/templates/knowledge/api-contract-template.md +140 -0
- package/templates/knowledge/domain-glossary.md +29 -0
- package/templates/knowledge/golden-tests-readme.md +115 -0
- package/templates/knowledge/lessons-learned.md +41 -0
- package/templates/knowledge/patterns.md +103 -0
- package/templates/module-card/SKILL.md +85 -0
- package/templates/module-card/api-specs.md +96 -0
- package/templates/module-card/business-quiz.md +119 -0
- package/templates/module-card/cross-service.md +125 -0
- package/templates/module-card/db.md +85 -0
- package/templates/module-card/dev-quiz.md +62 -0
- package/templates/module-card/permissions.md +83 -0
- package/templates/module-card/state-diagram.md +64 -0
- package/templates/module-card/tech-context.md +90 -0
- package/templates/module-card/ui-flows.md +91 -0
- package/templates/module-card/use-cases.md +142 -0
- package/templates/module-template.yaml +161 -0
- package/templates/operations-report-template.md +108 -0
- package/templates/plan-template.md +308 -0
- package/templates/prototype-notes-template.md +116 -0
- package/templates/ptyc/PTYC.template.docx +0 -0
- package/templates/ptyc/ptyc.meta.example.yaml +44 -0
- package/templates/retrospective-report-template.md +136 -0
- package/templates/security-review-template.md +84 -0
- package/templates/session-template.md +167 -0
- package/templates/spec-review-log-template.md +75 -0
- package/templates/spec-template.md +229 -0
- package/templates/sprint-status-template.md +101 -0
- package/templates/tasks-template.md +275 -0
- package/templates/test-cases-template.md +124 -0
- package/templates/ux-checklist-template.md +79 -0
- package/templates/validation-report-template.md +125 -0
- 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
|
+
|