@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,232 @@
1
+ /**
2
+ * Sync Core — Shared sync logic for CLI
3
+ *
4
+ * Used by both `v-flow sync` and `v-flow init` commands.
5
+ * Single source of truth for file synchronization.
6
+ */
7
+
8
+ const path = require('path');
9
+ const fs = require('fs-extra');
10
+ const crypto = require('crypto');
11
+ const { TARGETS, convertToMdc, convertAgentToMdc } = require('./sync-targets');
12
+
13
+ /**
14
+ * Compute SHA256 hash of content (first 12 chars)
15
+ */
16
+ function hashContent(content) {
17
+ return crypto.createHash('sha256').update(content).digest('hex').slice(0, 12);
18
+ }
19
+
20
+ /**
21
+ * Sync a single file from source to destination
22
+ *
23
+ * @param {string} srcPath - Source file path
24
+ * @param {string} destPath - Destination file path
25
+ * @param {object} options - { dryRun, verbose, transform, stats }
26
+ * @returns {'added'|'updated'|'unchanged'}
27
+ */
28
+ async function syncFile(srcPath, destPath, options = {}) {
29
+ const { dryRun = false, verbose = false, transform = null, stats = null } = options;
30
+
31
+ const srcContent = await fs.readFile(srcPath, 'utf8');
32
+ const destContent = transform ? transform(srcContent, path.basename(srcPath)) : srcContent;
33
+
34
+ if (fs.existsSync(destPath)) {
35
+ const existingContent = await fs.readFile(destPath, 'utf8');
36
+ const srcHash = hashContent(destContent);
37
+ const destHash = hashContent(existingContent);
38
+
39
+ if (srcHash === destHash) {
40
+ if (stats) stats.skipped++;
41
+ return 'unchanged';
42
+ }
43
+
44
+ if (stats) stats.synced++;
45
+ if (!dryRun) {
46
+ await fs.writeFile(destPath, destContent, 'utf8');
47
+ }
48
+ return 'updated';
49
+ }
50
+ if (stats) stats.added++;
51
+ if (!dryRun) {
52
+ await fs.ensureDir(path.dirname(destPath));
53
+ await fs.writeFile(destPath, destContent, 'utf8');
54
+ }
55
+ return 'added';
56
+
57
+ }
58
+
59
+ /**
60
+ * Sync all .md files from a source directory to a destination directory
61
+ *
62
+ * @param {string} srcDir - Source directory
63
+ * @param {string} destDir - Destination directory
64
+ * @param {object} options - { dryRun, verbose, transform, ext, stats }
65
+ * @returns {{ added: number, updated: number, unchanged: number }}
66
+ */
67
+ async function syncDir(srcDir, destDir, options = {}) {
68
+ const { dryRun = false, verbose = false, transform = null, ext = '.md', stats = null } = options;
69
+
70
+ if (!fs.existsSync(srcDir)) return { added: 0, updated: 0, unchanged: 0 };
71
+
72
+ if (!dryRun) await fs.ensureDir(destDir);
73
+
74
+ const files = await fs.readdir(srcDir);
75
+ const targetFiles = files.filter(f => f.endsWith('.md'));
76
+
77
+ const result = { added: 0, updated: 0, unchanged: 0 };
78
+
79
+ for (const file of targetFiles) {
80
+ const srcPath = path.join(srcDir, file);
81
+ const destFileName = ext === '.mdc' ? file.replace(/\.md$/, '.mdc') : file;
82
+ const destPath = path.join(destDir, destFileName);
83
+
84
+ // Explicit transform wins; else `.mdc` implies the Cursor command transform.
85
+ const fileTransform = transform || (ext === '.mdc' ? convertToMdc : null);
86
+
87
+ try {
88
+ const status = await syncFile(srcPath, destPath, { dryRun, verbose, transform: fileTransform, stats });
89
+ result[status]++;
90
+ } catch (err) {
91
+ if (stats) stats.errors = (stats.errors || 0) + 1;
92
+ }
93
+ }
94
+
95
+ return result;
96
+ }
97
+
98
+ /**
99
+ * Copy framework RUNTIME ASSETS (templates + skills) into .v-flow/.
100
+ * Canonical locations agents reference: `.v-flow/templates/X` and
101
+ * `.v-flow/skills/.../scripts/*.sh`. Populated in EVERY project — INCLUDING the framework
102
+ * repo itself — so those paths resolve everywhere. Independent of AI-tool file sync, so it
103
+ * runs even with `init --skip-ai` (these are framework runtime, not AI integration files).
104
+ *
105
+ * @returns {{ templates: number, skills: number }} source-side file counts
106
+ */
107
+ async function syncVFlowAssets({ projectRoot, sourceRoot, dryRun = false }) {
108
+ const vFlowDir = path.join(projectRoot, '.v-flow');
109
+ const out = { templates: 0, skills: 0 };
110
+
111
+ const countFiles = async (dir) => {
112
+ if (!fs.existsSync(dir)) return 0;
113
+ const entries = await fs.readdir(dir, { withFileTypes: true });
114
+ let n = 0;
115
+ for (const e of entries) {
116
+ if (e.isFile()) n++;
117
+ else if (e.isDirectory()) n += await countFiles(path.join(dir, e.name));
118
+ }
119
+ return n;
120
+ };
121
+
122
+ for (const name of ['templates', 'skills']) {
123
+ const src = path.join(sourceRoot, name);
124
+ if (!fs.existsSync(src)) continue;
125
+ out[name] = await countFiles(src);
126
+ if (!dryRun) {
127
+ const dest = path.join(vFlowDir, name);
128
+ await fs.ensureDir(dest);
129
+ await fs.copy(src, dest, { overwrite: true });
130
+ }
131
+ }
132
+ return out;
133
+ }
134
+
135
+ /**
136
+ * Select target descriptors from the registry for a given selector.
137
+ *
138
+ * @param {string} target - 'all' (or falsy) → every target; else match by key
139
+ * @returns {Array<object>} matching entries from TARGETS
140
+ */
141
+ function selectTargets(target) {
142
+ return TARGETS.filter((t) => !target || target === 'all' || t.key === target);
143
+ }
144
+
145
+ /**
146
+ * Run full sync operation — sync commands, agents, skills, and global files
147
+ * for every selected target. Target specifics live in the config-driven
148
+ * registry (`sync-targets.js`); this function only walks it.
149
+ *
150
+ * @param {object} config
151
+ * @param {string} config.projectRoot - Target project root
152
+ * @param {string} config.sourceRoot - ADLC source root
153
+ * @param {boolean} [config.dryRun=false] - Dry run mode
154
+ * @param {boolean} [config.verbose=false] - Verbose output
155
+ * @param {string} [config.target='all'] - 'all' or a target key (antigravity, copilot, cursor, claude)
156
+ * @returns {{ added: number, synced: number, skipped: number, errors: number }}
157
+ */
158
+ async function runSync(config) {
159
+ const {
160
+ projectRoot,
161
+ sourceRoot,
162
+ dryRun = false,
163
+ verbose = false,
164
+ target = 'all',
165
+ } = config;
166
+
167
+ const stats = { synced: 0, skipped: 0, added: 0, errors: 0 };
168
+ const syncOpts = { dryRun, verbose, stats };
169
+
170
+ // Source directories — vendor-neutral single source of truth.
171
+ const COMMANDS_DIR = path.join(sourceRoot, 'commands');
172
+ const AGENTS_DIR = path.join(sourceRoot, 'agents');
173
+ const SKILLS_DIR = path.join(sourceRoot, 'skills');
174
+
175
+ for (const t of selectTargets(target)) {
176
+ const dest = (segs) => path.join(projectRoot, ...segs);
177
+
178
+ // ── Commands ──
179
+ await syncDir(COMMANDS_DIR, dest(t.commands.dir), {
180
+ ...syncOpts,
181
+ ext: t.commands.ext || '.md',
182
+ transform: t.commands.transform || null,
183
+ });
184
+
185
+ // ── Agents ──
186
+ await syncDir(AGENTS_DIR, dest(t.agents.dir), {
187
+ ...syncOpts,
188
+ ext: t.agents.ext || '.md',
189
+ transform: t.agents.transform || null,
190
+ });
191
+
192
+ // ── Skills (bulk copy — preserves nested SKILL.md structure) ──
193
+ if (fs.existsSync(SKILLS_DIR) && !dryRun) {
194
+ await fs.copy(SKILLS_DIR, dest(t.skills.dir), { overwrite: true });
195
+ }
196
+
197
+ // ── Global instruction file(s) ──
198
+ for (const g of t.globals || []) {
199
+ const srcPath = path.join(sourceRoot, ...g.src);
200
+ if (!fs.existsSync(srcPath)) continue;
201
+ const destPath = dest(g.dest);
202
+ // noClobber: never overwrite a hand-authored file (e.g. CLAUDE.md).
203
+ if (g.noClobber && fs.existsSync(destPath)) {
204
+ stats.skipped++;
205
+ continue;
206
+ }
207
+ try {
208
+ await syncFile(srcPath, destPath, syncOpts);
209
+ } catch (err) {
210
+ stats.errors++;
211
+ }
212
+ }
213
+ }
214
+
215
+ // ── SYNC RUNTIME ASSETS (templates + skills) → .v-flow/ ──
216
+ // Extracted to syncVFlowAssets so `init --skip-ai` and `v-flow sync` install them too.
217
+ await syncVFlowAssets({ projectRoot, sourceRoot, dryRun });
218
+
219
+ return stats;
220
+ }
221
+
222
+ module.exports = {
223
+ hashContent,
224
+ convertToMdc,
225
+ convertAgentToMdc,
226
+ syncFile,
227
+ syncDir,
228
+ runSync,
229
+ syncVFlowAssets,
230
+ selectTargets,
231
+ TARGETS,
232
+ };
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Sync Targets — Config-driven registry of supported AI tools.
3
+ *
4
+ * V Flow's single source of truth is vendor-neutral markdown (`commands/`,
5
+ * `agents/`, `skills/`, `AGENTS.md`). Each entry below declares HOW that
6
+ * canonical form maps into one AI tool's conventional folders.
7
+ *
8
+ * ➕ Adding a new vendor = add ONE entry here. No changes to sync-core.js.
9
+ *
10
+ * Target shape:
11
+ * {
12
+ * key: string // CLI selector (`--target <key>`)
13
+ * label: string // human-readable name for output
14
+ * commands: { dir: string[], ext?, transform? } // slash commands
15
+ * agents: { dir: string[], ext?, transform? } // agent personas
16
+ * skills: { dir: string[] } // bulk-copied
17
+ * globals: [{ src: string[], dest: string[], noClobber? }] // instruction file(s)
18
+ * }
19
+ *
20
+ * `dir`/`src`/`dest` are path segments joined under projectRoot (or sourceRoot
21
+ * for `src`). `ext: '.mdc'` renames `.md` → `.mdc` on write. `transform` is a
22
+ * (content, fileName) => string applied per file. `noClobber: true` means
23
+ * never overwrite an existing destination (for hand-authored files).
24
+ */
25
+
26
+ /**
27
+ * Convert .md content to .mdc format for Cursor (inject `globs: *`).
28
+ */
29
+ function convertToMdc(content, fileName) {
30
+ if (content.startsWith('---')) {
31
+ return content.replace(/^---\r?\n/, '---\nglobs: *\n');
32
+ }
33
+ const cmdName = fileName.replace(/\.md$/, '');
34
+ return `---\ndescription: Execute /${cmdName}\nglobs: *\n---\n${content}`;
35
+ }
36
+
37
+ /**
38
+ * Convert agent .md to .mdc for Cursor with persona frontmatter.
39
+ */
40
+ function convertAgentToMdc(content, fileName) {
41
+ const agentName = fileName.replace(/\.md$/, '');
42
+ return `---\ndescription: Agent persona - ${agentName}\nglobs: *\nalwaysApply: false\n---\n${content}`;
43
+ }
44
+
45
+ const TARGETS = [
46
+ {
47
+ key: 'antigravity',
48
+ label: 'Antigravity',
49
+ commands: { dir: ['.agents', 'workflows'] },
50
+ agents: { dir: ['.agents', 'agents'] },
51
+ skills: { dir: ['.agents', 'skills'] },
52
+ globals: [{ src: ['AGENTS.md'], dest: ['AGENTS.md'] }],
53
+ },
54
+ {
55
+ key: 'copilot',
56
+ label: 'GitHub Copilot',
57
+ commands: { dir: ['.github', 'prompts'] },
58
+ agents: { dir: ['.github', 'agents'] },
59
+ skills: { dir: ['.github', 'skills'] },
60
+ globals: [
61
+ { src: ['.github', 'copilot-instructions.md'], dest: ['.github', 'copilot-instructions.md'] },
62
+ ],
63
+ },
64
+ {
65
+ key: 'cursor',
66
+ label: 'Cursor',
67
+ commands: { dir: ['.cursor', 'rules'], ext: '.mdc', transform: convertToMdc },
68
+ agents: { dir: ['.cursor', 'agents'], ext: '.mdc', transform: convertAgentToMdc },
69
+ skills: { dir: ['.cursor', 'skills'] },
70
+ globals: [{ src: ['AGENTS.md'], dest: ['.cursorrules'] }],
71
+ },
72
+ {
73
+ key: 'claude',
74
+ label: 'Claude Code',
75
+ commands: { dir: ['.claude', 'commands'] },
76
+ agents: { dir: ['.claude', 'agents'] },
77
+ skills: { dir: ['.claude', 'skills'] },
78
+ // CLAUDE.md is Claude Code's primary memory file and is very often
79
+ // hand-authored — so never clobber it. Created only when absent.
80
+ globals: [{ src: ['AGENTS.md'], dest: ['CLAUDE.md'], noClobber: true }],
81
+ },
82
+ ];
83
+
84
+ module.exports = { TARGETS, convertToMdc, convertAgentToMdc };
@@ -0,0 +1,268 @@
1
+ const path = require('path');
2
+ const fs = require('fs-extra');
3
+
4
+ const MASTER_FLOW_DIR = '.v-flow';
5
+
6
+ // ============================================================
7
+ // CODEBASE SCANNER — Delegated to scanner.js (Phase 3 refactor)
8
+ // ============================================================
9
+ const { scanCodebase, generateContextMd } = require('./scanner');
10
+
11
+
12
+ const COMMANDS_SOURCE = path.resolve(__dirname, '../../commands');
13
+ const TEMPLATES_SOURCE = path.resolve(__dirname, '../../templates');
14
+
15
+ /**
16
+ * Get the .v-flow directory path for a given project root
17
+ */
18
+ function getVFlowDir(projectRoot) {
19
+ return path.join(projectRoot, MASTER_FLOW_DIR);
20
+ }
21
+
22
+ /**
23
+ * Check if v-flow is initialized in the given directory
24
+ */
25
+ function isInitialized(projectRoot) {
26
+ return fs.existsSync(getVFlowDir(projectRoot));
27
+ }
28
+
29
+ /**
30
+ * Create the .v-flow directory structure
31
+ */
32
+ async function scaffoldDirectories(projectRoot) {
33
+ const mfDir = getVFlowDir(projectRoot);
34
+ const dirs = [
35
+ mfDir,
36
+ path.join(mfDir, 'features'),
37
+ path.join(mfDir, 'modules'),
38
+ path.join(mfDir, 'knowledge'),
39
+ path.join(mfDir, 'knowledge', 'architecture-decisions'),
40
+ path.join(mfDir, 'knowledge', 'patterns'),
41
+ path.join(mfDir, 'knowledge', 'lessons-learned'),
42
+ path.join(mfDir, 'knowledge', 'golden-tests'),
43
+ path.join(mfDir, 'incidents'),
44
+ ];
45
+
46
+ for (const dir of dirs) {
47
+ await fs.ensureDir(dir);
48
+ }
49
+ }
50
+
51
+ /**
52
+ * Copy a template file to destination, replacing placeholders
53
+ */
54
+ async function copyTemplate(templateName, destPath, replacements = {}) {
55
+ const srcPath = path.join(TEMPLATES_SOURCE, templateName);
56
+ if (!fs.existsSync(srcPath)) {
57
+ throw new Error(`Template not found: ${templateName}`);
58
+ }
59
+
60
+ let content = await fs.readFile(srcPath, 'utf8');
61
+
62
+ for (const [key, value] of Object.entries(replacements)) {
63
+ content = content.replace(new RegExp(`\\[${key}\\]`, 'g'), value);
64
+ }
65
+
66
+ await fs.writeFile(destPath, content, 'utf8');
67
+ }
68
+
69
+ const AGENTS_SOURCE = path.resolve(__dirname, '../../agents');
70
+ const SKILLS_SOURCE = path.resolve(__dirname, '../../skills');
71
+ const MODULE_CARD_TEMPLATES_SOURCE = path.resolve(__dirname, '../../templates/module-card');
72
+ const PTYC_SCRIPTS_SOURCE = path.resolve(__dirname, '../../scripts/ptyc');
73
+ const PTYC_TEMPLATES_SOURCE = path.resolve(__dirname, '../../templates/ptyc');
74
+
75
+ /**
76
+ * Setup AI agent integration files (workflows, skills, agents)
77
+ */
78
+ async function setupAgentFiles(projectRoot) {
79
+ // ==============================
80
+ // Antigravity: .agents/
81
+ // ==============================
82
+
83
+ // 1. Workflows (slash commands)
84
+ const agentsWorkflowDir = path.join(projectRoot, '.agents', 'workflows');
85
+ await fs.ensureDir(agentsWorkflowDir);
86
+
87
+ // 2. Skills (agent definitions as skills with SKILL.md)
88
+ const agentsSkillsDir = path.join(projectRoot, '.agents', 'skills');
89
+ await fs.ensureDir(agentsSkillsDir);
90
+
91
+ // ==============================
92
+ // GitHub Copilot: .github/
93
+ // ==============================
94
+
95
+ // 1. Prompts (slash commands)
96
+ const githubPromptsDir = path.join(projectRoot, '.github', 'prompts');
97
+ await fs.ensureDir(githubPromptsDir);
98
+
99
+ // 2. Agents (custom agent definitions)
100
+ const githubAgentsDir = path.join(projectRoot, '.github', 'agents');
101
+ await fs.ensureDir(githubAgentsDir);
102
+
103
+ // ==============================
104
+ // Cursor: .cursor/
105
+ // ==============================
106
+
107
+ // 1. Rules (slash commands)
108
+ const cursorRulesDir = path.join(projectRoot, '.cursor', 'rules');
109
+ await fs.ensureDir(cursorRulesDir);
110
+
111
+ // --- Copy command files to workflows & prompts ---
112
+ if (fs.existsSync(COMMANDS_SOURCE)) {
113
+ const commandFiles = await fs.readdir(COMMANDS_SOURCE);
114
+ for (const file of commandFiles) {
115
+ if (file.endsWith('.md')) {
116
+ const srcPath = path.join(COMMANDS_SOURCE, file);
117
+ await fs.copy(srcPath, path.join(agentsWorkflowDir, file));
118
+ await fs.copy(srcPath, path.join(githubPromptsDir, file));
119
+
120
+ // Cursor expects .mdc extension for rules
121
+ const mdcName = file.replace(/\.md$/, '.mdc');
122
+ let content = await fs.readFile(srcPath, 'utf8');
123
+
124
+ // If it already has YAML frontmatter, inject globs: *
125
+ if (content.startsWith('---')) {
126
+ content = content.replace(/^---\r?\n/, '---\nglobs: *\n');
127
+ } else {
128
+ // Otherwise prepend a basic frontmatter
129
+ const cmdName = file.replace(/\.md$/, '');
130
+ content = `---
131
+ description: Execute /${cmdName}
132
+ globs: *
133
+ ---
134
+ ${content}`;
135
+ }
136
+ await fs.writeFile(path.join(cursorRulesDir, mdcName), content, 'utf8');
137
+ }
138
+ }
139
+ }
140
+
141
+ // --- Copy skills to .agents/skills/ ---
142
+ if (fs.existsSync(SKILLS_SOURCE)) {
143
+ await fs.copy(SKILLS_SOURCE, agentsSkillsDir);
144
+ }
145
+
146
+ // --- Copy module-card templates to .v-flow/templates/module-card/ ---
147
+ // These templates are used by understand-agent to generate module cards
148
+ // with the correct structure (headings, tables, mermaid diagrams)
149
+ const moduleCardTemplatesDir = path.join(getVFlowDir(projectRoot), 'templates', 'module-card');
150
+ if (fs.existsSync(MODULE_CARD_TEMPLATES_SOURCE)) {
151
+ await fs.ensureDir(moduleCardTemplatesDir);
152
+ await fs.copy(MODULE_CARD_TEMPLATES_SOURCE, moduleCardTemplatesDir);
153
+ }
154
+
155
+ // --- Copy PTYC doc-export tooling INTO .v-flow/ (scripts + template) ---
156
+ // Used by /v.ba-to-ptyc (ba-to-ptyc-agent). Paths MUST live under .v-flow so they
157
+ // resolve in the target project — not the framework repo. render_ptyc.py finds its
158
+ // template via ../../templates/ptyc, which holds inside .v-flow too. Skips sample/ fixture.
159
+ const vflowRoot = getVFlowDir(projectRoot);
160
+ if (fs.existsSync(PTYC_SCRIPTS_SOURCE)) {
161
+ await fs.copy(PTYC_SCRIPTS_SOURCE, path.join(vflowRoot, 'scripts', 'ptyc'), {
162
+ filter: (src) => path.basename(src) !== 'sample' && !src.includes('__pycache__'),
163
+ });
164
+ }
165
+ if (fs.existsSync(PTYC_TEMPLATES_SOURCE)) {
166
+ await fs.copy(PTYC_TEMPLATES_SOURCE, path.join(vflowRoot, 'templates', 'ptyc'));
167
+ }
168
+
169
+ // --- Copy agent definitions to .agents/agents/ & .github/agents/ & .cursor/agents/ ---
170
+ if (fs.existsSync(AGENTS_SOURCE)) {
171
+ // Antigravity: .agents/agents/ (flat .md files — personas)
172
+ const antigravityAgentsDir = path.join(projectRoot, '.agents', 'agents');
173
+ await fs.ensureDir(antigravityAgentsDir);
174
+
175
+ // Cursor: .cursor/agents/ (.mdc files & agent personas)
176
+ const cursorAgentsDir = path.join(projectRoot, '.cursor', 'agents');
177
+ await fs.ensureDir(cursorAgentsDir);
178
+
179
+ const agentFiles = await fs.readdir(AGENTS_SOURCE);
180
+ for (const file of agentFiles) {
181
+ if (file.endsWith('.md')) {
182
+ const srcPath = path.join(AGENTS_SOURCE, file);
183
+
184
+ // Antigravity: flat .md file in .agents/agents/
185
+ await fs.copy(srcPath, path.join(antigravityAgentsDir, file));
186
+
187
+ // Copilot: flat .md file in .github/agents/
188
+ await fs.copy(srcPath, path.join(githubAgentsDir, file));
189
+
190
+ // Cursor: .mdc file with frontmatter in .cursor/agents/
191
+ const mdcName = file.replace(/\.md$/, '.mdc');
192
+ let content = await fs.readFile(srcPath, 'utf8');
193
+ const agentName = file.replace(/\.md$/, '');
194
+ content = `---\ndescription: Agent persona - ${agentName}\nglobs: *\nalwaysApply: false\n---\n${content}`;
195
+ await fs.writeFile(path.join(cursorAgentsDir, mdcName), content, 'utf8');
196
+ }
197
+ }
198
+ }
199
+ }
200
+
201
+ /**
202
+ * Copy AGENTS.md and copilot-instructions.md to project root
203
+ */
204
+ async function setupAgentInstructionFiles(projectRoot, sourceRoot) {
205
+ const agentsFile = path.join(sourceRoot, 'AGENTS.md');
206
+ const copilotFile = path.join(sourceRoot, '.github', 'copilot-instructions.md');
207
+
208
+ if (fs.existsSync(agentsFile)) {
209
+ await fs.copy(agentsFile, path.join(projectRoot, 'AGENTS.md'));
210
+ // For Cursor, we use AGENTS.md as the global .cursorrules
211
+ await fs.copy(agentsFile, path.join(projectRoot, '.cursorrules'));
212
+ }
213
+
214
+ if (fs.existsSync(copilotFile)) {
215
+ const destDir = path.join(projectRoot, '.github');
216
+ await fs.ensureDir(destDir);
217
+ await fs.copy(copilotFile, path.join(destDir, 'copilot-instructions.md'));
218
+ }
219
+ }
220
+
221
+ /**
222
+ * List all features in .v-flow/features/
223
+ */
224
+ async function listFeatures(projectRoot) {
225
+ const featuresDir = path.join(getVFlowDir(projectRoot), 'features');
226
+ if (!fs.existsSync(featuresDir)) return [];
227
+
228
+ const entries = await fs.readdir(featuresDir, { withFileTypes: true });
229
+ return entries.filter(e => e.isDirectory()).map(e => e.name);
230
+ }
231
+
232
+ /**
233
+ * Check which artifacts exist for a feature
234
+ */
235
+ async function checkFeatureArtifacts(projectRoot, featureName) {
236
+ const featureDir = path.join(getVFlowDir(projectRoot), 'features', featureName);
237
+ const artifacts = [
238
+ { name: 'spec.md', phase: '[S.1] Specification' },
239
+ { name: '.flow-state.yaml', phase: '[S.1] Pipeline State' },
240
+ { name: 'plan.md', phase: '[B.0a] Planning' },
241
+ { name: 'tasks.md', phase: '[B.0a] Task Breakdown' },
242
+ { name: 'test-cases.md', phase: '[B.0b] Test Cases' },
243
+ { name: 'validation-report.md', phase: '[R.0] Master Check' },
244
+ { name: 'metrics-report.md', phase: '[M.0] Metrics' },
245
+ { name: 'operations-report.md', phase: '[O.0] Operations' },
246
+ ];
247
+
248
+ return artifacts.map(a => ({
249
+ ...a,
250
+ exists: fs.existsSync(path.join(featureDir, a.name)),
251
+ }));
252
+ }
253
+
254
+ module.exports = {
255
+ MASTER_FLOW_DIR,
256
+ getVFlowDir,
257
+ isInitialized,
258
+ scaffoldDirectories,
259
+ copyTemplate,
260
+ setupAgentFiles,
261
+ setupAgentInstructionFiles,
262
+ listFeatures,
263
+ checkFeatureArtifacts,
264
+ scanCodebase,
265
+ generateContextMd,
266
+ };
267
+
268
+