@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,151 @@
1
+ /**
2
+ * v-flow watch — tool-agnostic activity safety net.
3
+ *
4
+ * Watches `.v-flow/**` and logs every file create/modify/delete to
5
+ * `.v-flow/logs/`. Because it observes the real artifacts agents produce, it
6
+ * captures activity from ANY AI tool (even ones without hooks) and from manual
7
+ * edits — a deterministic fallback complementing per-tool hooks.
8
+ *
9
+ * Portable recursive watch: watches each directory individually (and adds
10
+ * watchers for new ones) instead of relying on fs.watch({recursive:true}),
11
+ * which is unavailable on Linux before Node 20.
12
+ */
13
+
14
+ const path = require('path');
15
+ const fs = require('fs');
16
+ const chalk = require('chalk');
17
+ const { withErrorHandler, requireInit } = require('../lib/cli-error');
18
+ const { isInitialized, getVFlowDir } = require('../lib/templates');
19
+ const activityLog = require('../lib/activity-log');
20
+
21
+ // First path segment under .v-flow/ to never watch. `logs` is mandatory (we
22
+ // write there — watching it would loop). The others are gitignored runtime
23
+ // copies that would only add noise.
24
+ const EXCLUDE_TOP = new Set(['logs', 'templates', 'skills']);
25
+ const IGNORE_BASENAMES = new Set(['.DS_Store']);
26
+ const DEBOUNCE_MS = 200;
27
+
28
+ module.exports = function (program) {
29
+ program
30
+ .command('watch')
31
+ .description('Watch .v-flow/ and log every file change (tool-agnostic safety net)')
32
+ .option('--timeout <seconds>', 'Stop automatically after N seconds (0 = run until Ctrl-C)', '0')
33
+ .action(
34
+ withErrorHandler((options) => {
35
+ const projectRoot = process.cwd();
36
+ requireInit(projectRoot, isInitialized);
37
+ const vflowDir = getVFlowDir(projectRoot);
38
+
39
+ const watchers = [];
40
+ const known = new Set(); // paths seen → distinguish created vs modified
41
+ const timers = new Map(); // debounce per path
42
+ let stopped = false;
43
+
44
+ const relTop = (full) => path.relative(vflowDir, full).split(path.sep)[0];
45
+ const isExcluded = (full) => EXCLUDE_TOP.has(relTop(full));
46
+
47
+ function watchDir(dir) {
48
+ let watcher;
49
+ try {
50
+ watcher = fs.watch(dir, (eventType, filename) => {
51
+ if (!filename) return;
52
+ const full = path.join(dir, filename);
53
+ if (isExcluded(full) || IGNORE_BASENAMES.has(path.basename(full))) return;
54
+ schedule(full);
55
+ });
56
+ } catch (_) {
57
+ return; // dir vanished before we could watch it
58
+ }
59
+ watcher.on('error', () => {});
60
+ watchers.push(watcher);
61
+ }
62
+
63
+ // emit(full) is called for each newly-discovered path so a directory
64
+ // created together with its files in one tick still logs those files.
65
+ function addTree(dir, emit) {
66
+ watchDir(dir);
67
+ let entries = [];
68
+ try {
69
+ entries = fs.readdirSync(dir, { withFileTypes: true });
70
+ } catch (_) {
71
+ return;
72
+ }
73
+ for (const ent of entries) {
74
+ const full = path.join(dir, ent.name);
75
+ if (isExcluded(full) || IGNORE_BASENAMES.has(ent.name)) continue;
76
+ const isNew = !known.has(full);
77
+ known.add(full);
78
+ if (isNew && emit) emit(full);
79
+ if (ent.isDirectory()) addTree(full, emit);
80
+ }
81
+ }
82
+
83
+ function schedule(full) {
84
+ if (timers.has(full)) clearTimeout(timers.get(full));
85
+ timers.set(
86
+ full,
87
+ setTimeout(() => {
88
+ timers.delete(full);
89
+ handleChange(full);
90
+ }, DEBOUNCE_MS)
91
+ );
92
+ }
93
+
94
+ function logFs(full, action) {
95
+ const rel = path.relative(vflowDir, full);
96
+ activityLog.event({ type: 'fs', action, path: rel }, `[fs] ${action} ${rel}`);
97
+ const colors = { created: chalk.green, modified: chalk.yellow, deleted: chalk.red };
98
+ const color = colors[action] || chalk.white;
99
+ console.log(`${chalk.dim(new Date().toISOString())} ${color(action.padEnd(8))} ${rel}`);
100
+ }
101
+
102
+ function handleChange(full) {
103
+ const exists = fs.existsSync(full);
104
+ if (exists && !known.has(full)) {
105
+ known.add(full);
106
+ logFs(full, 'created');
107
+ try {
108
+ if (fs.statSync(full).isDirectory()) addTree(full, (child) => logFs(child, 'created'));
109
+ } catch (_) {
110
+ /* ignore */
111
+ }
112
+ } else if (exists) {
113
+ logFs(full, 'modified');
114
+ } else if (known.has(full)) {
115
+ known.delete(full);
116
+ logFs(full, 'deleted');
117
+ }
118
+ // else: spurious event for an unknown, non-existent path — ignore
119
+ }
120
+
121
+ function stop(reason) {
122
+ if (stopped) return;
123
+ stopped = true;
124
+ for (const t of timers.values()) clearTimeout(t);
125
+ for (const w of watchers) {
126
+ try {
127
+ w.close();
128
+ } catch (_) {
129
+ /* ignore */
130
+ }
131
+ }
132
+ activityLog.event({ type: 'watch', event: 'stop', reason }, `[watch] stop (${reason})`);
133
+ console.log(chalk.dim(`\n🛑 watch stopped (${reason})`));
134
+ process.exit(0);
135
+ }
136
+
137
+ addTree(vflowDir);
138
+ activityLog.event({ type: 'watch', event: 'start' }, '[watch] start');
139
+ console.log(chalk.cyan.bold(`\n👀 Watching ${path.relative(projectRoot, vflowDir) || '.v-flow'}/ — logging changes to .v-flow/logs/`));
140
+ console.log(chalk.dim(` (excluding: ${[...EXCLUDE_TOP].join(', ')}) — Ctrl-C to stop\n`));
141
+
142
+ process.on('SIGINT', () => stop('SIGINT'));
143
+ process.on('SIGTERM', () => stop('SIGTERM'));
144
+
145
+ const timeout = parseInt(options.timeout, 10) || 0;
146
+ if (timeout > 0) {
147
+ setTimeout(() => stop(`timeout ${timeout}s`), timeout * 1000);
148
+ }
149
+ })
150
+ );
151
+ };
package/cli/index.js ADDED
@@ -0,0 +1,46 @@
1
+ #!/usr/bin/env node
2
+
3
+ const { program } = require('commander');
4
+ const pkg = require('../package.json');
5
+ const activityLog = require('./lib/activity-log');
6
+
7
+ // Capture all CLI output + persist a per-command record to .v-flow/logs/.
8
+ activityLog.install();
9
+
10
+ program
11
+ .name('v-flow')
12
+ .description('🚀 V Flow CLI — Agentic AI Software Development Process')
13
+ .version(pkg.version)
14
+ // ── Global Options ──
15
+ .option('--verbose', 'Show detailed output')
16
+ .option('--lang <language>', 'Set language (vi, en)', process.env.VFLOW_LANG || 'vi');
17
+
18
+ // Make global options accessible to all commands via environment
19
+ program.hook('preAction', (thisCommand, actionCommand) => {
20
+ const opts = program.opts();
21
+ if (opts.verbose) {
22
+ process.env.VFLOW_VERBOSE = '1';
23
+ }
24
+ if (opts.lang) {
25
+ process.env.VFLOW_LANG = opts.lang;
26
+ }
27
+ activityLog.setCommand(actionCommand.name(), process.argv.slice(2));
28
+ });
29
+
30
+ // Register commands
31
+ require('./commands/init')(program);
32
+ require('./commands/check')(program);
33
+ require('./commands/status')(program);
34
+ require('./commands/feature')(program);
35
+ require('./commands/sync')(program);
36
+ require('./commands/doctor')(program);
37
+ require('./commands/upgrade')(program);
38
+ require('./commands/validate')(program);
39
+ require('./commands/score-card')(program);
40
+ require('./commands/dev-quiz')(program);
41
+ require('./commands/plugin')(program);
42
+ require('./commands/log')(program);
43
+ require('./commands/hooks')(program);
44
+ require('./commands/watch')(program);
45
+
46
+ program.parse();
@@ -0,0 +1,89 @@
1
+ /**
2
+ * AC-PASS Gate — verify every spec Acceptance Criterion (AC-NN) is satisfied by
3
+ * a PASSING automated test, by reading a JUnit XML test report.
4
+ *
5
+ * This is the "living-spec" leg of validation: existing checks confirm each AC
6
+ * is *referenced* by a task/test-case on paper; this one reaches into the real
7
+ * test runner and fails when an AC has no PASSING test behind it.
8
+ *
9
+ * Convention: a testcase "covers" AC-NN when the AC id token appears in its
10
+ * `name`/`classname` (e.g. `it('AC-03: rejects expired token', ...)`), reusing
11
+ * the same AC-NN ids V Flow already threads through spec → task → test-case.
12
+ *
13
+ * JUnit XML is the universal interchange format — Jest (jest-junit), Vitest,
14
+ * pytest, go-junit-report, Maven Surefire, etc. all emit it.
15
+ */
16
+
17
+ /**
18
+ * Parse JUnit XML into a flat list of testcases.
19
+ * A case counts as failed if it contains a <failure>/<error> child, skipped if
20
+ * it contains <skipped>. Anything else is a pass.
21
+ *
22
+ * @param {string} xml
23
+ * @returns {Array<{name,classname,label,passed,failed,skipped}>}
24
+ */
25
+ function parseJUnitResults(xml) {
26
+ const cases = [];
27
+ if (!xml || typeof xml !== 'string') return cases;
28
+
29
+ // Match <testcase .../> (self-closing) or <testcase ...>...</testcase>.
30
+ const reCase = /<testcase\b([^>]*?)(?:\/>|>([\s\S]*?)<\/testcase>)/g;
31
+ let m;
32
+ while ((m = reCase.exec(xml)) !== null) {
33
+ const attrs = m[1] || '';
34
+ const inner = m[2] || '';
35
+ const name = (attrs.match(/\bname\s*=\s*"([^"]*)"/) || [])[1] || '';
36
+ const classname = (attrs.match(/\bclassname\s*=\s*"([^"]*)"/) || [])[1] || '';
37
+ const failed = /<(?:failure|error)\b/.test(inner);
38
+ const skipped = /<skipped\b/.test(inner);
39
+ cases.push({
40
+ name,
41
+ classname,
42
+ label: `${classname} ${name}`.trim(),
43
+ passed: !failed && !skipped,
44
+ failed,
45
+ skipped,
46
+ });
47
+ }
48
+ return cases;
49
+ }
50
+
51
+ /**
52
+ * Extract unique AC-NN ids from arbitrary text.
53
+ * @param {string} text
54
+ * @returns {string[]}
55
+ */
56
+ function extractAcIds(text) {
57
+ return [...new Set((text || '').match(/\bAC-\d+\b/g) || [])];
58
+ }
59
+
60
+ /**
61
+ * Classify each spec AC against parsed test cases.
62
+ *
63
+ * @param {string[]} specAcs - AC ids declared in the spec (e.g. ['AC-01','AC-02'])
64
+ * @param {Array} cases - output of parseJUnitResults
65
+ * @returns {{ total:number, passed:string[], failing:string[], missing:string[] }}
66
+ * passed = AC referenced by ≥1 PASSING test
67
+ * failing = AC referenced only by failing/skipped tests (no passing one)
68
+ * missing = AC referenced by no test at all
69
+ */
70
+ function checkAcTestPass(specAcs, cases) {
71
+ const acs = [...new Set(specAcs || [])];
72
+ const list = cases || [];
73
+ const passed = [];
74
+ const failing = [];
75
+ const missing = [];
76
+
77
+ for (const ac of acs) {
78
+ // Word-boundary match so AC-1 does not match AC-12.
79
+ const re = new RegExp(`\\b${ac}\\b`);
80
+ const refs = list.filter((c) => re.test(c.label));
81
+ if (refs.length === 0) missing.push(ac);
82
+ else if (refs.some((c) => c.passed)) passed.push(ac);
83
+ else failing.push(ac);
84
+ }
85
+
86
+ return { total: acs.length, passed, failing, missing };
87
+ }
88
+
89
+ module.exports = { parseJUnitResults, extractAcIds, checkAcTestPass };
@@ -0,0 +1,209 @@
1
+ /**
2
+ * Activity Log — Persistent, file-based audit of V-Flow usage.
3
+ *
4
+ * Records two surfaces into <projectRoot>/.v-flow/logs/:
5
+ * • CLI runs — every `v-flow <cmd>` invocation (args, exit code, duration,
6
+ * full console transcript). Captured automatically from
7
+ * cli/index.js via install().
8
+ * • Agent steps — structured events appended by the `v-flow log` command,
9
+ * which the agentic workflow (AGENTS.md) calls at phase
10
+ * boundaries.
11
+ *
12
+ * Output files:
13
+ * activity.jsonl — one JSON object per line (machine-readable / grep / jq).
14
+ * activity.log — human-readable mirror.
15
+ *
16
+ * Design notes:
17
+ * • Best-effort — every failure is swallowed; logging must NEVER break the CLI.
18
+ * • Synchronous appends — survive process.exit()/crashes without an async flush.
19
+ * • Disabled via VFLOW_NO_LOG=1. Custom path via VFLOW_LOG_DIR / VFLOW_LOG_FILE.
20
+ * • Never auto-creates .v-flow/ (that would corrupt isInitialized()); only logs
21
+ * into the project when .v-flow/ already exists (or an explicit override set).
22
+ */
23
+
24
+ const fs = require('fs');
25
+ const path = require('path');
26
+ const util = require('util');
27
+
28
+ // eslint-disable-next-line no-control-regex
29
+ const ANSI = /\x1b\[[0-9;]*m/g;
30
+ const stripAnsi = (s) => String(s).replace(ANSI, '');
31
+
32
+ let _installed = false;
33
+ let _finalized = false;
34
+ const _disabled = process.env.VFLOW_NO_LOG === '1';
35
+ let _startTime = null;
36
+ let _command = null; // { name, invocation }
37
+ const _transcript = []; // buffered console output lines
38
+
39
+ // Commands that write their own log entries — skip the auto CLI command record
40
+ // (avoids double-logging and, for `watch`, a huge buffered transcript).
41
+ const SELF_LOGGING = new Set(['log', 'watch']);
42
+
43
+ function nowIso() {
44
+ return new Date().toISOString();
45
+ }
46
+
47
+ function safeVersion() {
48
+ try {
49
+ return require('../../package.json').version;
50
+ } catch (_) {
51
+ return null;
52
+ }
53
+ }
54
+
55
+ /**
56
+ * Resolve the log directory.
57
+ * Priority: VFLOW_LOG_DIR > dir(VFLOW_LOG_FILE) > <cwd>/.v-flow/logs (only if
58
+ * .v-flow already exists). Returns null when logging should be skipped.
59
+ */
60
+ function resolveLogDir() {
61
+ if (process.env.VFLOW_LOG_DIR) return process.env.VFLOW_LOG_DIR;
62
+ if (process.env.VFLOW_LOG_FILE) return path.dirname(process.env.VFLOW_LOG_FILE);
63
+ const vflow = path.join(process.cwd(), '.v-flow');
64
+ if (fs.existsSync(vflow)) return path.join(vflow, 'logs');
65
+ return null;
66
+ }
67
+
68
+ function jsonlPath(dir) {
69
+ if (process.env.VFLOW_LOG_FILE) return process.env.VFLOW_LOG_FILE;
70
+ return path.join(dir, 'activity.jsonl');
71
+ }
72
+
73
+ function logPath(dir) {
74
+ if (process.env.VFLOW_LOG_FILE) {
75
+ return `${process.env.VFLOW_LOG_FILE.replace(/\.jsonl?$/i, '') }.log`;
76
+ }
77
+ return path.join(dir, 'activity.log');
78
+ }
79
+
80
+ /**
81
+ * Append one structured entry to activity.jsonl, plus an optional human-readable
82
+ * line to activity.log. Safe to call anytime — no-op if disabled or no dir.
83
+ * `ts` is added automatically.
84
+ *
85
+ * @param {object} entry Fields to record (without ts).
86
+ * @param {string} [humanLine] Suffix for the .log mirror (ts is prepended).
87
+ */
88
+ function event(entry, humanLine) {
89
+ if (_disabled) return;
90
+ try {
91
+ const dir = resolveLogDir();
92
+ if (!dir) return;
93
+ fs.mkdirSync(dir, { recursive: true });
94
+ const record = { ts: nowIso(), ...entry };
95
+ fs.appendFileSync(jsonlPath(dir), `${JSON.stringify(record)}\n`);
96
+ if (humanLine) {
97
+ fs.appendFileSync(logPath(dir), `${record.ts} ${humanLine}\n`);
98
+ }
99
+ } catch (_) {
100
+ /* logging must never break the CLI */
101
+ }
102
+ }
103
+
104
+ /**
105
+ * Install console capture + the exit hook that persists the CLI command record.
106
+ * Call once from cli/index.js. No-op if disabled.
107
+ */
108
+ function install() {
109
+ if (_installed || _disabled) return;
110
+ _installed = true;
111
+ _startTime = Date.now();
112
+
113
+ const orig = {
114
+ log: console.log.bind(console),
115
+ info: console.info.bind(console),
116
+ warn: console.warn.bind(console),
117
+ error: console.error.bind(console),
118
+ debug: console.debug.bind(console),
119
+ };
120
+ const tee = (fn) => (...args) => {
121
+ try {
122
+ _transcript.push(stripAnsi(util.format(...args)));
123
+ } catch (_) {
124
+ /* ignore formatting errors */
125
+ }
126
+ fn(...args);
127
+ };
128
+ console.log = tee(orig.log);
129
+ console.info = tee(orig.info);
130
+ console.warn = tee(orig.warn);
131
+ console.error = tee(orig.error);
132
+ console.debug = tee(orig.debug);
133
+
134
+ process.on('exit', (code) => finalize(code));
135
+ }
136
+
137
+ /**
138
+ * Record which command is running. Called from index.js preAction hook.
139
+ */
140
+ function setCommand(name, invocation) {
141
+ _command = { name, invocation: invocation || [] };
142
+ }
143
+
144
+ /**
145
+ * Persist the CLI command record (jsonl + transcript). Idempotent; runs on exit.
146
+ */
147
+ function finalize(exitCode) {
148
+ if (_finalized || _disabled) return;
149
+ _finalized = true;
150
+ // Only persist when a real subcommand ran. Skip help/version/no-op and any
151
+ // self-logging command (avoids double/circular logging).
152
+ if (!_command || SELF_LOGGING.has(_command.name)) return;
153
+ try {
154
+ const dir = resolveLogDir();
155
+ if (!dir) return;
156
+ fs.mkdirSync(dir, { recursive: true });
157
+ const durationMs = _startTime ? Date.now() - _startTime : null;
158
+ const code = typeof exitCode === 'number' ? exitCode : process.exitCode || 0;
159
+ const record = {
160
+ ts: nowIso(),
161
+ type: 'cli',
162
+ cmd: _command.name,
163
+ invocation: _command.invocation,
164
+ exitCode: code,
165
+ durationMs,
166
+ cwd: process.cwd(),
167
+ version: safeVersion(),
168
+ };
169
+ fs.appendFileSync(jsonlPath(dir), `${JSON.stringify(record)}\n`);
170
+ const label = `v-flow ${record.invocation.join(' ')}`.trim();
171
+ const header = `\n── ${record.ts} ${label} (exit ${code}, ${durationMs}ms) ──\n`;
172
+ const body = _transcript.length ? `${_transcript.join('\n')}\n` : '';
173
+ fs.appendFileSync(logPath(dir), `${header}${body}`);
174
+ } catch (_) {
175
+ /* never break the CLI */
176
+ }
177
+ }
178
+
179
+ /**
180
+ * Read the last N entries from activity.jsonl (best-effort). Used by `v-flow log --tail`.
181
+ * @returns {object[]} parsed records (oldest → newest)
182
+ */
183
+ function tail(n = 20) {
184
+ try {
185
+ const dir = resolveLogDir();
186
+ if (!dir) return [];
187
+ const file = jsonlPath(dir);
188
+ if (!fs.existsSync(file)) return [];
189
+ const lines = fs.readFileSync(file, 'utf8').split('\n').filter(Boolean);
190
+ return lines.slice(-n).map((line) => {
191
+ try {
192
+ return JSON.parse(line);
193
+ } catch (_) {
194
+ return { raw: line };
195
+ }
196
+ });
197
+ } catch (_) {
198
+ return [];
199
+ }
200
+ }
201
+
202
+ module.exports = {
203
+ install,
204
+ setCommand,
205
+ finalize,
206
+ event,
207
+ tail,
208
+ resolveLogDir,
209
+ };
@@ -0,0 +1,183 @@
1
+ /**
2
+ * CLI Error Utilities — Unified error handling for V-Flow CLI
3
+ *
4
+ * Provides consistent error formatting, exit codes, and error types
5
+ * across all CLI commands.
6
+ */
7
+
8
+ const chalk = require('chalk');
9
+
10
+ // ============================================================
11
+ // ERROR TYPES
12
+ // ============================================================
13
+
14
+ /**
15
+ * Base CLI error with exit code and user-friendly message
16
+ */
17
+ class CliError extends Error {
18
+ constructor(message, { exitCode = 1, hint = null, cause = null } = {}) {
19
+ super(message);
20
+ this.name = 'CliError';
21
+ this.exitCode = exitCode;
22
+ this.hint = hint;
23
+ if (cause) this.cause = cause;
24
+ }
25
+ }
26
+
27
+ /**
28
+ * Project not initialized error
29
+ */
30
+ class NotInitializedError extends CliError {
31
+ constructor() {
32
+ super('V Flow is not initialized (.v-flow/ not found)', {
33
+ exitCode: 1,
34
+ hint: 'Run: v-flow init',
35
+ });
36
+ this.name = 'NotInitializedError';
37
+ }
38
+ }
39
+
40
+ /**
41
+ * Feature not found error
42
+ */
43
+ class FeatureNotFoundError extends CliError {
44
+ constructor(featureName) {
45
+ super(`Feature not found: ${featureName}`, {
46
+ exitCode: 1,
47
+ hint: 'Run: v-flow feature list to see available features',
48
+ });
49
+ this.name = 'FeatureNotFoundError';
50
+ }
51
+ }
52
+
53
+ /**
54
+ * Validation error (bad input, missing argument, etc.)
55
+ */
56
+ class ValidationError extends CliError {
57
+ constructor(message, hint = null) {
58
+ super(message, { exitCode: 1, hint });
59
+ this.name = 'ValidationError';
60
+ }
61
+ }
62
+
63
+ // ============================================================
64
+ // ERROR HANDLER
65
+ // ============================================================
66
+
67
+ /**
68
+ * Wrap a command action with consistent error handling.
69
+ *
70
+ * Usage:
71
+ * program.command('foo').action(withErrorHandler(async (options) => { ... }));
72
+ *
73
+ * @param {Function} actionFn - async function(options) to wrap
74
+ * @returns {Function} wrapped function
75
+ */
76
+ function withErrorHandler(actionFn) {
77
+ return async (...args) => {
78
+ try {
79
+ await actionFn(...args);
80
+ } catch (err) {
81
+ if (err instanceof CliError) {
82
+ console.error(chalk.red(`\n❌ ${err.message}`));
83
+ if (err.hint) {
84
+ console.error(chalk.dim(` → ${err.hint}`));
85
+ }
86
+ console.error('');
87
+ process.exit(err.exitCode);
88
+ } else {
89
+ // Unexpected error — show stack in verbose mode
90
+ console.error(chalk.red(`\n❌ Unexpected error: ${err.message}`));
91
+ if (process.env.VFLOW_DEBUG || process.argv.includes('--debug')) {
92
+ console.error(chalk.dim(err.stack));
93
+ } else {
94
+ console.error(chalk.dim(' → Re-run with VFLOW_DEBUG=1 for full stack trace'));
95
+ }
96
+ console.error('');
97
+ process.exit(1);
98
+ }
99
+ }
100
+ };
101
+ }
102
+
103
+ // ============================================================
104
+ // GUARD UTILITIES
105
+ // ============================================================
106
+
107
+ /**
108
+ * Assert project is initialized, throw NotInitializedError if not.
109
+ * @param {string} projectRoot
110
+ * @param {Function} isInitialized - from templates.js
111
+ */
112
+ function requireInit(projectRoot, isInitialized) {
113
+ if (!isInitialized(projectRoot)) {
114
+ throw new NotInitializedError();
115
+ }
116
+ }
117
+
118
+ // ============================================================
119
+ // FORMATTING UTILITIES
120
+ // ============================================================
121
+
122
+ /**
123
+ * Format bytes to human-readable string
124
+ */
125
+ function formatBytes(bytes) {
126
+ if (bytes < 1024) return `${bytes}B`;
127
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}KB`;
128
+ return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
129
+ }
130
+
131
+ /**
132
+ * Print a section header
133
+ */
134
+ function printHeader(title) {
135
+ console.log(chalk.white.bold(`\n${title}`));
136
+ }
137
+
138
+ /**
139
+ * Print a success line
140
+ */
141
+ function printOk(message) {
142
+ console.log(chalk.green(` ✅ ${message}`));
143
+ }
144
+
145
+ /**
146
+ * Print a warning line
147
+ */
148
+ function printWarn(message) {
149
+ console.log(chalk.yellow(` ⚠️ ${message}`));
150
+ }
151
+
152
+ /**
153
+ * Print an error line
154
+ */
155
+ function printError(message) {
156
+ console.log(chalk.red(` ❌ ${message}`));
157
+ }
158
+
159
+ /**
160
+ * Print a dimmed info line
161
+ */
162
+ function printDim(message) {
163
+ console.log(chalk.dim(` ${message}`));
164
+ }
165
+
166
+ module.exports = {
167
+ // Error classes
168
+ CliError,
169
+ NotInitializedError,
170
+ FeatureNotFoundError,
171
+ ValidationError,
172
+ // Handler
173
+ withErrorHandler,
174
+ // Guards
175
+ requireInit,
176
+ // Formatting
177
+ formatBytes,
178
+ printHeader,
179
+ printOk,
180
+ printWarn,
181
+ printError,
182
+ printDim,
183
+ };