@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,78 @@
1
+ /**
2
+ * Governance — risk-tier gate + approval metadata (lightweight schema check).
3
+ *
4
+ * Declared in a feature's .flow-state.yaml:
5
+ * risk_tier: low | medium | high | critical
6
+ * approvals:
7
+ * - role: spec # which gate this approval covers
8
+ * actor: "Nguyen Van A"
9
+ * decision: approved
10
+ * timestamp: "2026-06-06T10:00:00Z"
11
+ *
12
+ * GATE_MATRIX maps each tier → the approval roles it requires. checkGovernance
13
+ * returns warnings (surfaced by `v-flow validate`; `--strict` blocks CI). This is
14
+ * intentionally lightweight — it checks the PRESENCE + structural validity of
15
+ * approvals (actor/timestamp), NOT whether an approval was semantically warranted.
16
+ */
17
+
18
+ const TIERS = ['low', 'medium', 'high', 'critical'];
19
+
20
+ // Required approval roles per risk tier (escalating). Tune per organisation.
21
+ const GATE_MATRIX = {
22
+ low: [],
23
+ medium: ['spec'],
24
+ high: ['spec', 'security'],
25
+ critical: ['spec', 'security', 'tech_lead'],
26
+ };
27
+
28
+ /**
29
+ * @param {object} state - normalized flow state ({ riskTier, approvals })
30
+ * @returns {{ tier: string|null, issues: string[], warnings: string[], ok: string[] }}
31
+ */
32
+ function checkGovernance(state) {
33
+ const issues = [];
34
+ const warnings = [];
35
+ const ok = [];
36
+
37
+ const tier = state && state.riskTier ? String(state.riskTier).toLowerCase() : null;
38
+
39
+ if (!tier) {
40
+ warnings.push('Chưa khai bÃĄo risk_tier trong .flow-state.yaml (low|medium|high|critical) — không xÃĄc đáģ‹nh đưáģŖc gate báē¯t buáģ™c');
41
+ return { tier: null, issues, warnings, ok };
42
+ }
43
+ if (!TIERS.includes(tier)) {
44
+ warnings.push(`risk_tier không háģŖp láģ‡: "${tier}" — dÚng low|medium|high|critical`);
45
+ return { tier, issues, warnings, ok };
46
+ }
47
+
48
+ const required = GATE_MATRIX[tier];
49
+ const approvals = Array.isArray(state.approvals) ? state.approvals : [];
50
+
51
+ // Structural validity — an approval without actor + timestamp is not auditable.
52
+ for (const a of approvals) {
53
+ const role = a.role || a.gate || '(không rÃĩ role)';
54
+ if (!a.actor || !a.timestamp) {
55
+ warnings.push(`Approval "${role}" thiáēŋu actor/timestamp — không audit đưáģŖc`);
56
+ }
57
+ }
58
+
59
+ // Each required gate must have an "approved" approval entry.
60
+ const approvedRoles = new Set(
61
+ approvals
62
+ .filter((a) => !a.decision || String(a.decision).toLowerCase() === 'approved')
63
+ .map((a) => a.role || a.gate)
64
+ );
65
+ for (const role of required) {
66
+ if (!approvedRoles.has(role)) {
67
+ warnings.push(`Tier "${tier}" yÃĒu cáē§u approval "${role}" — chưa cÃŗ (cáē§n actor + timestamp)`);
68
+ }
69
+ }
70
+
71
+ if (warnings.length === 0) {
72
+ ok.push(`Governance: tier "${tier}" đáģ§ approval báē¯t buáģ™c (${required.join(', ') || 'không yÃĒu cáē§u'})`);
73
+ }
74
+
75
+ return { tier, issues, warnings, ok };
76
+ }
77
+
78
+ module.exports = { TIERS, GATE_MATRIX, checkGovernance };
@@ -0,0 +1,167 @@
1
+ /**
2
+ * Hook Targets — per-provider lifecycle-hook config descriptors.
3
+ *
4
+ * Each AI coding tool now ships a hooks system, but the config shapes differ
5
+ * (event-name casing, `command` vs `bash`, nested-with-matcher vs flat, a named
6
+ * group for Antigravity). This module declares those shapes once so the
7
+ * `v-flow hooks` command can install / remove / inspect a single logging hook
8
+ * (`v-flow log --hook <provider>`) for every tool, merging into existing config
9
+ * without clobbering the user's own hooks.
10
+ *
11
+ * Verified against vendor docs (mid-2026):
12
+ * Claude Code .claude/settings.json → hooks.{PostToolUse,Stop,SessionStart} (nested, matcher)
13
+ * OpenAI Codex .codex/hooks.json → hooks.{PostToolUse,Stop,SessionStart} (nested)
14
+ * Antigravity .agents/hooks.json → vflow-log.{PostToolUse,Stop} (named group, nested, matcher)
15
+ * Cursor .cursor/hooks.json → hooks.{postToolUse,stop,sessionStart} (flat, `command`, version 1)
16
+ * Copilot .github/hooks/vflow-log.json → hooks.{postToolUse,agentStop,sessionStart} (flat, `bash`, version 1)
17
+ */
18
+
19
+ const PROVIDERS = [
20
+ {
21
+ key: 'claude',
22
+ label: 'Claude Code',
23
+ file: ['.claude', 'settings.json'],
24
+ root: 'hooks',
25
+ form: 'nested',
26
+ version: null,
27
+ events: ['PostToolUse', 'Stop', 'SessionStart'],
28
+ matcherEvents: ['PostToolUse'],
29
+ timeout: null,
30
+ },
31
+ {
32
+ key: 'codex',
33
+ label: 'OpenAI Codex',
34
+ file: ['.codex', 'hooks.json'],
35
+ root: 'hooks',
36
+ form: 'nested',
37
+ version: null,
38
+ events: ['PostToolUse', 'Stop', 'SessionStart'],
39
+ matcherEvents: [],
40
+ timeout: null,
41
+ },
42
+ {
43
+ key: 'antigravity',
44
+ label: 'Antigravity',
45
+ file: ['.agents', 'hooks.json'],
46
+ root: 'vflow-log',
47
+ form: 'nested',
48
+ version: null,
49
+ events: ['PostToolUse', 'Stop'],
50
+ matcherEvents: ['PostToolUse', 'Stop'],
51
+ timeout: 15,
52
+ },
53
+ {
54
+ key: 'cursor',
55
+ label: 'Cursor',
56
+ file: ['.cursor', 'hooks.json'],
57
+ root: 'hooks',
58
+ form: 'flat-command',
59
+ version: 1,
60
+ events: ['postToolUse', 'stop', 'sessionStart'],
61
+ matcherEvents: [],
62
+ timeout: null,
63
+ },
64
+ {
65
+ key: 'copilot',
66
+ label: 'GitHub Copilot',
67
+ file: ['.github', 'hooks', 'vflow-log.json'],
68
+ root: 'hooks',
69
+ form: 'flat-bash',
70
+ version: 1,
71
+ events: ['postToolUse', 'agentStop', 'sessionStart'],
72
+ matcherEvents: [],
73
+ timeout: null,
74
+ },
75
+ ];
76
+
77
+ function getProvider(key) {
78
+ return PROVIDERS.find((p) => p.key === key) || null;
79
+ }
80
+
81
+ /** The single logging command each hook invokes. `bin` defaults to PATH lookup. */
82
+ function hookCommand(provider, event, bin = 'v-flow') {
83
+ return `${bin} log --hook ${provider.key} --event ${event}`;
84
+ }
85
+
86
+ /** Build one event entry in the provider's native shape. */
87
+ function buildEntry(provider, event, bin) {
88
+ const command = hookCommand(provider, event, bin);
89
+ if (provider.form === 'flat-command') {
90
+ return { command };
91
+ }
92
+ if (provider.form === 'flat-bash') {
93
+ return { type: 'command', bash: command };
94
+ }
95
+ // nested (claude / codex / antigravity)
96
+ const inner = { type: 'command', command };
97
+ if (provider.timeout) inner.timeout = provider.timeout;
98
+ const group = { hooks: [inner] };
99
+ if (provider.matcherEvents.includes(event)) group.matcher = '*';
100
+ return group;
101
+ }
102
+
103
+ /** Does an existing entry belong to v-flow (so we can replace/remove it)? */
104
+ function entryIsOurs(provider, entry) {
105
+ if (!entry || typeof entry !== 'object') return false;
106
+ const needle = `--hook ${provider.key}`;
107
+ const hasNeedle = (s) => typeof s === 'string' && s.includes(needle);
108
+ if (hasNeedle(entry.command) || hasNeedle(entry.bash)) return true;
109
+ if (Array.isArray(entry.hooks)) {
110
+ return entry.hooks.some((h) => h && (hasNeedle(h.command) || hasNeedle(h.bash)));
111
+ }
112
+ return false;
113
+ }
114
+
115
+ /**
116
+ * Merge our hooks into an existing parsed config (or {}). Idempotent: replaces
117
+ * any prior v-flow entries, preserves everything else.
118
+ */
119
+ function mergeConfig(provider, existing, opts = {}) {
120
+ const cfg = existing && typeof existing === 'object' ? existing : {};
121
+ if (provider.version !== null && cfg.version === undefined) {
122
+ cfg.version = provider.version;
123
+ }
124
+ cfg[provider.root] = cfg[provider.root] || {};
125
+ const container = cfg[provider.root];
126
+ for (const event of provider.events) {
127
+ const arr = Array.isArray(container[event]) ? container[event] : [];
128
+ const cleaned = arr.filter((e) => !entryIsOurs(provider, e));
129
+ cleaned.push(buildEntry(provider, event, opts.bin));
130
+ container[event] = cleaned;
131
+ }
132
+ return cfg;
133
+ }
134
+
135
+ /** Strip our hooks from an existing parsed config; preserve user hooks. */
136
+ function removeConfig(provider, existing) {
137
+ if (!existing || typeof existing !== 'object') return existing;
138
+ const container = existing[provider.root];
139
+ if (!container || typeof container !== 'object') return existing;
140
+ for (const event of Object.keys(container)) {
141
+ if (Array.isArray(container[event])) {
142
+ container[event] = container[event].filter((e) => !entryIsOurs(provider, e));
143
+ if (container[event].length === 0) delete container[event];
144
+ }
145
+ }
146
+ if (Object.keys(container).length === 0) delete existing[provider.root];
147
+ return existing;
148
+ }
149
+
150
+ /** Is our hook currently present in an existing parsed config? */
151
+ function isInstalled(provider, existing) {
152
+ const container = existing && existing[provider.root];
153
+ if (!container || typeof container !== 'object') return false;
154
+ return provider.events.some(
155
+ (ev) => Array.isArray(container[ev]) && container[ev].some((e) => entryIsOurs(provider, e))
156
+ );
157
+ }
158
+
159
+ module.exports = {
160
+ PROVIDERS,
161
+ getProvider,
162
+ hookCommand,
163
+ buildEntry,
164
+ mergeConfig,
165
+ removeConfig,
166
+ isInstalled,
167
+ };
@@ -0,0 +1,375 @@
1
+ /**
2
+ * i18n — Internationalization for V-Flow CLI
3
+ *
4
+ * Supports: vi (default), en
5
+ * Language selection: --lang flag, VFLOW_LANG env var, or system locale
6
+ *
7
+ * STATUS: Active — All 9 commands use i18n for user-facing strings.
8
+ * Integration: Global --lang flag sets process.env.VFLOW_LANG via cli/index.js preAction hook.
9
+ *
10
+ * Usage in commands:
11
+ * const { createT } = require('../lib/i18n');
12
+ * const t = createT();
13
+ * console.log(t('init.created', { dir: '.v-flow' }));
14
+ */
15
+
16
+ const vi = {
17
+ // Common
18
+ 'common.success': '✅ Thành công',
19
+ 'common.error': '❌ Láģ—i',
20
+ 'common.warning': 'âš ī¸ CáēŖnh bÃĄo',
21
+ 'common.done': 'Hoàn táēĨt',
22
+ 'common.skipped': 'ÄÃŖ báģ qua',
23
+ 'common.not_found': 'Không tÃŦm tháēĨy',
24
+ 'common.created': 'ÄÃŖ táēĄo',
25
+ 'common.updated': 'ÄÃŖ cáē­p nháē­t',
26
+ 'common.unchanged': 'Không thay đáģ•i',
27
+ 'common.dry_run': '(cháēŋ đáģ™ xem trưáģ›c — không thay đáģ•i gÃŦ)',
28
+ 'common.version': 'PhiÃĒn báēŖn',
29
+ 'common.summary': '── Táģ•ng káēŋt ──',
30
+ 'common.added': 'Máģ›i',
31
+ 'common.errors': 'Láģ—i',
32
+
33
+ // Init
34
+ 'init.title': '🚀 V Flow — Đang kháģŸi táēĄo...',
35
+ 'init.already_exists': 'âš ī¸ .v-flow/ Ä‘ÃŖ táģ“n táēĄi. DÚng --force đáģƒ kháģŸi táēĄo láēĄi.',
36
+ 'init.already_exists_hint': ' Dáģ¯ liáģ‡u hiáģ‡n táēĄi sáēŊ không báģ‹ ghi đè.',
37
+ 'init.creating_dirs': '📁 Đang táēĄo cáēĨu trÃēc thư máģĨc...',
38
+ 'init.dir_created': '✅ .v-flow/ Ä‘ÃŖ táēĄo',
39
+ 'init.constitution_created': '✅ constitution.md Ä‘ÃŖ táēĄo (táģĢ template)',
40
+ 'init.constitution_exists': 'â­ī¸ constitution.md Ä‘ÃŖ táģ“n táēĄi',
41
+ 'init.agent_config_created': '✅ agent-config.md Ä‘ÃŖ táēĄo (táģĢ template)',
42
+ 'init.agent_config_exists': 'â­ī¸ agent-config.md Ä‘ÃŖ táģ“n táēĄi',
43
+ 'init.scanning': '🔍 Đang quÊt codebase cho context.md...',
44
+ 'init.scan_complete': '✅ context.md Ä‘ÃŖ táēĄo (U.0 auto-scan: {count} files)',
45
+ 'init.scan_languages': ' Ngôn ngáģ¯: {languages}',
46
+ 'init.scan_frameworks': ' Frameworks: {frameworks}',
47
+ 'init.scan_warning': 'âš ī¸ Tech Lead cáē§n báģ• sung: No-Go Zones, Technical Debt, Architecture',
48
+ 'init.scan_failed': 'âš ī¸ (quÊt tháēĨt báēĄi, dÚng template)',
49
+ 'init.scan_template': '📝 context.md Ä‘ÃŖ táēĄo táģĢ template (điáģn tháģ§ công)',
50
+ 'init.context_exists': 'â­ī¸ context.md Ä‘ÃŖ táģ“n táēĄi',
51
+ 'init.knowledge_seeded': '✅ knowledge/ Ä‘ÃŖ táēĄo ({count} files: glossary, patterns, lessons-learned)',
52
+ 'init.knowledge_exists': 'â­ī¸ knowledge/ Ä‘ÃŖ táģ“n táēĄi',
53
+ 'init.syncing': '🤖 Đang đáģ“ng báģ™ AI agent files...',
54
+ 'init.sync_stats': '📊 Đáģ“ng báģ™: {added} máģ›i, {synced} cáē­p nháē­t, {skipped} không đáģ•i',
55
+ 'init.complete': '🎉 V Flow Ä‘ÃŖ kháģŸi táēĄo thành công!',
56
+ 'init.next_steps': 'Bưáģ›c tiáēŋp theo:',
57
+ 'init.review_context': '1. âš ī¸ Review .v-flow/context.md → điáģn No-Go Zones & Technical Debt',
58
+ 'init.edit_constitution': '2. Sáģ­a .v-flow/constitution.md váģ›i quy táē¯c dáģą ÃĄn',
59
+ 'init.edit_agent_config': '3. Sáģ­a .v-flow/agent-config.md váģ›i model preferences',
60
+ 'init.import_ba_docs': '4. (TÚy cháģn) CÃŗ tài liáģ‡u BA trÃĒn NotebookLM? /v.import-ba-docs',
61
+ 'init.start_feature': '5. Báē¯t đáē§u feature: /v.specify',
62
+ 'init.sync_hint': '6. Náēŋu spec thay đáģ•i: v-flow sync --apply đáģƒ giáģ¯ artifacts đáģ“ng báģ™',
63
+
64
+ // Check
65
+ 'check.title': '🔍 V Flow — Kiáģƒm tra sáģŠc kháģe',
66
+ 'check.vflow_found': '✅ .v-flow/ directory found',
67
+ 'check.core_files': '📋 Core Files:',
68
+ 'check.knowledge': '📚 Knowledge Base:',
69
+ 'check.features': 'đŸˇī¸ Features: {count} found',
70
+ 'check.no_features': 'Chưa cÃŗ feature. TáēĄo: .v-flow/features/001-feature-name/',
71
+ 'check.ai_agents': '🤖 AI Agent Integration:',
72
+
73
+ // Status
74
+ 'status.title': '📊 V Flow — TráēĄng thÃĄi Pipeline',
75
+ 'status.no_features': 'Chưa cÃŗ feature.',
76
+ 'status.no_features_hint': 'TáēĄo feature: v-flow feature create <tÃĒn>',
77
+ 'status.feature_not_found': 'Feature "{name}" không tÃŦm tháēĨy.',
78
+ 'status.available_features': 'Features hiáģ‡n cÃŗ: {features}',
79
+ 'status.blocked': 'đŸšĢ BLOCKED: {reason}',
80
+ 'status.awaiting': '👤 Đang cháģ: {checkpoint}',
81
+ 'status.concerns': '⚡ Concerns: {count} đang máģŸ',
82
+ 'status.next': 'â–ļ Tiáēŋp theo:',
83
+ 'status.all_complete': '🎉 TáēĨt cáēŖ phases hoàn thành!',
84
+
85
+ // Sync
86
+ 'sync.title': '🔄 V Flow — Đáģ“ng báģ™',
87
+ 'sync.dry_run_note': ' â„šī¸ Cháēŋ đáģ™ xem trưáģ›c: không thay đáģ•i file nào.',
88
+ 'sync.dry_run_hint': ' ThÃĒm --apply đáģƒ tháģąc thi.',
89
+ 'sync.commands': '── Commands ──',
90
+ 'sync.agents': '── Agents ──',
91
+ 'sync.skills': '── Skills ──',
92
+ 'sync.global_files': '── Global Files ──',
93
+ 'sync.module_templates': '── Module Card Templates ──',
94
+ 'sync.skills_synced': '✅ Skills directory synced',
95
+ 'sync.mc_synced': '✅ Module card templates synced',
96
+ 'sync.mc_skipped': 'â­ī¸ Báģ qua — đang cháēĄy táģĢ source root (templates/ Ä‘ÃŖ cÃŗ sáēĩn)',
97
+ 'sync.summary': '── Táģ•ng káēŋt{mode} ──',
98
+ 'sync.added': ' ✨ Máģ›i: {count}',
99
+ 'sync.updated': ' 🔄 Cáē­p nháē­t: {count}',
100
+ 'sync.unchanged': ' â­ī¸ Không đáģ•i: {count}',
101
+ 'sync.errors': ' ❌ Láģ—i: {count}',
102
+ 'sync.apply_hint': ' â„šī¸ CháēĄy {cmd} đáģƒ tháģąc thi nháģ¯ng thay đáģ•i này.',
103
+ 'sync.all_synced': ' ✅ TáēĨt cáēŖ files Ä‘ÃŖ đáģ“ng báģ™.',
104
+ 'sync.all_uptodate': ' ✅ TáēĨt cáēŖ files Ä‘ÃŖ cáē­p nháē­t.',
105
+
106
+ // Doctor
107
+ 'doctor.title': 'đŸŠē V Flow — Doctor',
108
+ 'doctor.core_structure': '📁 Core Structure',
109
+ 'doctor.core_files': '📄 Core Files',
110
+ 'doctor.ai_sync': '🤖 AI Agent Sync Status',
111
+ 'doctor.feature_health': 'đŸˇī¸ Feature Health ({count} features)',
112
+ 'doctor.templates': '📝 Templates',
113
+ 'doctor.diagnosis': '── CháēŠn đoÃĄn ──',
114
+ 'doctor.health': 'SáģŠc kháģe',
115
+ 'doctor.errors': '❌ {count} láģ—i — cáē§n sáģ­a:',
116
+ 'doctor.warnings': 'âš ī¸ {count} cáēŖnh bÃĄo — nÃĒn sáģ­a:',
117
+ 'doctor.passed': '✅ {count} kiáģƒm tra Ä‘ÃŖ qua',
118
+ 'doctor.recommendations': '💡 Khuyáēŋn ngháģ‹:',
119
+ 'doctor.run_init': 'â€ĸ CháēĄy: v-flow init',
120
+ 'doctor.run_sync': 'â€ĸ CháēĄy: v-flow sync --apply',
121
+ 'doctor.review_context': 'â€ĸ Review: .v-flow/context.md — điáģn cÃĄc máģĨc Tech Lead',
122
+ 'doctor.run_feature': 'â€ĸ CháēĄy: v-flow feature create <tÃĒn> (đáģƒ scaffolding đÃēng)',
123
+
124
+ // Validate
125
+ 'validate.title': '🔍 V Flow — Kiáģƒm tra cháēĨt lưáģŖng',
126
+ 'validate.no_features': 'Chưa cÃŗ feature. TáēĄo báēąng: v-flow feature create <tÃĒn>',
127
+ 'validate.summary': '── Táģ•ng káēŋt ──',
128
+ 'validate.quality_score': '📊 Điáģƒm cháēĨt lưáģŖng: {score}%',
129
+
130
+ // Plugin
131
+ 'plugin.title': '🔌 V Flow — Plugins',
132
+ 'plugin.no_plugins': 'Chưa cÃŗ plugin.',
133
+ 'plugin.create_hint': 'TáēĄo plugin: v-flow plugin create <tÃĒn>',
134
+ 'plugin.created': 'Plugin Ä‘ÃŖ táēĄo: .v-flow/plugins/{name}/',
135
+ 'plugin.installed': '✅ ÄÃŖ cài {count} asset(s) táģĢ {plugins} plugin(s)',
136
+ 'plugin.create_title': '🔌 V Flow — TáēĄo Plugin',
137
+ 'plugin.install_title': '🔌 V Flow — Cài Plugin',
138
+ 'plugin.validate_title': '🔌 V Flow — Validate Plugins',
139
+ 'plugin.all_valid': '✅ TáēĨt cáēŖ {count} plugin(s) háģŖp láģ‡',
140
+ 'plugin.dry_run': '🔍 Dry run — {count} asset(s) sáēŊ đưáģŖc cài',
141
+
142
+ // Feature
143
+ 'feature.title': 'đŸˇī¸ Feature',
144
+ 'feature.created': 'Feature Ä‘ÃŖ táēĄo: {name}',
145
+ 'feature.no_features': 'Chưa cÃŗ feature.',
146
+
147
+ // Upgrade
148
+ 'upgrade.title': 'âŦ†ī¸ V Flow — NÃĸng cáēĨp{mode}',
149
+ 'upgrade.source_version': ' PhiÃĒn báēŖn nguáģ“n: {version}',
150
+ 'upgrade.installed_version': ' PhiÃĒn báēŖn Ä‘ÃŖ cài: {version}',
151
+ 'upgrade.up_to_date': '✅ ÄÃŖ cáē­p nháē­t!',
152
+ 'upgrade.templates': '📝 Templates',
153
+ 'upgrade.agents': '🤖 Agent Definitions & Commands',
154
+ 'upgrade.agents_hint': ' (Đáģ“ng báģ™ táģ›i .agents/, .github/, .cursor/)',
155
+ 'upgrade.synced': '✅ ÄÃŖ đáģ“ng báģ™: {added} máģ›i, {synced} cáē­p nháē­t, {skipped} không đáģ•i',
156
+ 'upgrade.sync_errors': 'âš ī¸ {count} láģ—i đáģ“ng báģ™',
157
+ 'upgrade.upgraded': '✅ ÄÃŖ nÃĸng cáēĨp lÃĒn v{version}',
158
+ 'upgrade.verify_hint': '→ CháēĄy: v-flow doctor đáģƒ kiáģƒm tra',
159
+ 'upgrade.apply_hint': 'â„šī¸ CháēĄy láēĄi không cÃŗ --dry-run đáģƒ ÃĄp dáģĨng.',
160
+ 'upgrade.templates_uptodate': '✅ TáēĨt cáēŖ templates Ä‘ÃŖ cáē­p nháē­t.',
161
+ };
162
+
163
+ const en = {
164
+ // Common
165
+ 'common.success': '✅ Success',
166
+ 'common.error': '❌ Error',
167
+ 'common.warning': 'âš ī¸ Warning',
168
+ 'common.done': 'Done',
169
+ 'common.skipped': 'Skipped',
170
+ 'common.not_found': 'Not found',
171
+ 'common.created': 'Created',
172
+ 'common.updated': 'Updated',
173
+ 'common.unchanged': 'Unchanged',
174
+ 'common.dry_run': '(preview mode — no changes made)',
175
+ 'common.version': 'Version',
176
+ 'common.summary': '── Summary ──',
177
+ 'common.added': 'Added',
178
+ 'common.errors': 'Errors',
179
+
180
+ // Init
181
+ 'init.title': '🚀 V Flow — Initializing...',
182
+ 'init.already_exists': 'âš ī¸ .v-flow/ already exists. Use --force to reinitialize.',
183
+ 'init.already_exists_hint': ' Existing data will not be overwritten.',
184
+ 'init.creating_dirs': '📁 Creating directory structure...',
185
+ 'init.dir_created': '✅ .v-flow/ directory created',
186
+ 'init.constitution_created': '✅ constitution.md created (from template)',
187
+ 'init.constitution_exists': 'â­ī¸ constitution.md already exists',
188
+ 'init.agent_config_created': '✅ agent-config.md created (from template)',
189
+ 'init.agent_config_exists': 'â­ī¸ agent-config.md already exists',
190
+ 'init.scanning': '🔍 Scanning codebase for context.md...',
191
+ 'init.scan_complete': '✅ context.md created (U.0 auto-scan: {count} files)',
192
+ 'init.scan_languages': ' Languages: {languages}',
193
+ 'init.scan_frameworks': ' Frameworks: {frameworks}',
194
+ 'init.scan_warning': 'âš ī¸ Tech Lead should fill in: No-Go Zones, Technical Debt, Architecture',
195
+ 'init.scan_failed': 'âš ī¸ (scan failed, using template)',
196
+ 'init.scan_template': '📝 context.md created from template (fill in manually)',
197
+ 'init.context_exists': 'â­ī¸ context.md already exists',
198
+ 'init.knowledge_seeded': '✅ knowledge/ seeded ({count} files: glossary, patterns, lessons-learned)',
199
+ 'init.knowledge_exists': 'â­ī¸ knowledge/ already exists',
200
+ 'init.syncing': '🤖 Syncing AI agent integration files...',
201
+ 'init.sync_stats': '📊 Sync stats: {added} new, {synced} updated, {skipped} unchanged',
202
+ 'init.complete': '🎉 V Flow initialized successfully!',
203
+ 'init.next_steps': 'Next steps:',
204
+ 'init.review_context': '1. âš ī¸ Review .v-flow/context.md → fill in No-Go Zones & Technical Debt',
205
+ 'init.edit_constitution': '2. Edit .v-flow/constitution.md with your project rules',
206
+ 'init.edit_agent_config': '3. Edit .v-flow/agent-config.md with model preferences',
207
+ 'init.import_ba_docs': '4. (Optional) BA docs on NotebookLM? /v.import-ba-docs',
208
+ 'init.start_feature': '5. Start a feature: /v.specify',
209
+ 'init.sync_hint': '6. If spec changes later: v-flow sync --apply to keep artifacts in sync',
210
+
211
+ // Check
212
+ 'check.title': '🔍 V Flow — Health Check',
213
+ 'check.vflow_found': '✅ .v-flow/ directory found',
214
+ 'check.core_files': '📋 Core Files:',
215
+ 'check.knowledge': '📚 Knowledge Base:',
216
+ 'check.features': 'đŸˇī¸ Features: {count} found',
217
+ 'check.no_features': 'No features yet. Create: .v-flow/features/001-feature-name/',
218
+ 'check.ai_agents': '🤖 AI Agent Integration:',
219
+
220
+ // Status
221
+ 'status.title': '📊 V Flow — Pipeline Status',
222
+ 'status.no_features': 'No features found.',
223
+ 'status.no_features_hint': 'Create a feature: v-flow feature create <name>',
224
+ 'status.feature_not_found': 'Feature "{name}" not found.',
225
+ 'status.available_features': 'Available features: {features}',
226
+ 'status.blocked': 'đŸšĢ BLOCKED: {reason}',
227
+ 'status.awaiting': '👤 Awaiting: {checkpoint}',
228
+ 'status.concerns': '⚡ Concerns: {count} active',
229
+ 'status.next': 'â–ļ Next:',
230
+ 'status.all_complete': '🎉 All phases complete!',
231
+
232
+ // Sync
233
+ 'sync.title': '🔄 V Flow — Sync',
234
+ 'sync.dry_run_note': ' â„šī¸ Dry-run mode: no files will be changed.',
235
+ 'sync.dry_run_hint': ' Add --apply to execute changes.',
236
+ 'sync.commands': '── Commands ──',
237
+ 'sync.agents': '── Agents ──',
238
+ 'sync.skills': '── Skills ──',
239
+ 'sync.global_files': '── Global Files ──',
240
+ 'sync.module_templates': '── Module Card Templates ──',
241
+ 'sync.skills_synced': '✅ Skills directory synced',
242
+ 'sync.mc_synced': '✅ Module card templates synced',
243
+ 'sync.mc_skipped': 'â­ī¸ Skipped — running from source root (templates/ already exists)',
244
+ 'sync.summary': '── Summary{mode} ──',
245
+ 'sync.added': ' ✨ Added: {count}',
246
+ 'sync.updated': ' 🔄 Updated: {count}',
247
+ 'sync.unchanged': ' â­ī¸ Unchanged: {count}',
248
+ 'sync.errors': ' ❌ Errors: {count}',
249
+ 'sync.apply_hint': ' â„šī¸ Run {cmd} to execute these changes.',
250
+ 'sync.all_synced': ' ✅ All files synced successfully.',
251
+ 'sync.all_uptodate': ' ✅ All files are up-to-date.',
252
+
253
+ // Doctor
254
+ 'doctor.title': 'đŸŠē V Flow — Doctor',
255
+ 'doctor.core_structure': '📁 Core Structure',
256
+ 'doctor.core_files': '📄 Core Files',
257
+ 'doctor.ai_sync': '🤖 AI Agent Sync Status',
258
+ 'doctor.feature_health': 'đŸˇī¸ Feature Health ({count} features)',
259
+ 'doctor.templates': '📝 Templates',
260
+ 'doctor.diagnosis': '── Diagnosis ──',
261
+ 'doctor.health': 'Health',
262
+ 'doctor.errors': '❌ {count} error(s) — must fix:',
263
+ 'doctor.warnings': 'âš ī¸ {count} warning(s) — should fix:',
264
+ 'doctor.passed': '✅ {count} check(s) passed',
265
+ 'doctor.recommendations': '💡 Recommendations:',
266
+ 'doctor.run_init': 'â€ĸ Run: v-flow init',
267
+ 'doctor.run_sync': 'â€ĸ Run: v-flow sync --apply',
268
+ 'doctor.review_context': 'â€ĸ Review: .v-flow/context.md — fill in Tech Lead sections',
269
+ 'doctor.run_feature': 'â€ĸ Run: v-flow feature create <name> (for proper scaffolding)',
270
+
271
+ // Validate
272
+ 'validate.title': '🔍 V Flow — Quality Validation',
273
+ 'validate.no_features': 'No features found. Create one with: v-flow feature create <name>',
274
+ 'validate.summary': '── Summary ──',
275
+ 'validate.quality_score': '📊 Quality score: {score}%',
276
+
277
+ // Plugin
278
+ 'plugin.title': '🔌 V Flow — Plugins',
279
+ 'plugin.no_plugins': 'No plugins found.',
280
+ 'plugin.create_hint': 'Create one with: v-flow plugin create <name>',
281
+ 'plugin.created': 'Plugin created: .v-flow/plugins/{name}/',
282
+ 'plugin.installed': '✅ Installed {count} asset(s) from {plugins} plugin(s)',
283
+ 'plugin.create_title': '🔌 V Flow — Create Plugin',
284
+ 'plugin.install_title': '🔌 V Flow — Install Plugins',
285
+ 'plugin.validate_title': '🔌 V Flow — Validate Plugins',
286
+ 'plugin.all_valid': '✅ All {count} plugin(s) valid',
287
+ 'plugin.dry_run': '🔍 Dry run — {count} asset(s) would be installed',
288
+
289
+ // Feature
290
+ 'feature.title': 'đŸˇī¸ Feature',
291
+ 'feature.created': 'Feature created: {name}',
292
+ 'feature.no_features': 'No features yet.',
293
+
294
+ // Upgrade
295
+ 'upgrade.title': 'âŦ†ī¸ V Flow — Upgrade{mode}',
296
+ 'upgrade.source_version': ' Source version: {version}',
297
+ 'upgrade.installed_version': ' Installed version: {version}',
298
+ 'upgrade.up_to_date': '✅ Already up-to-date!',
299
+ 'upgrade.templates': '📝 Templates',
300
+ 'upgrade.agents': '🤖 Agent Definitions & Commands',
301
+ 'upgrade.agents_hint': ' (Syncing to .agents/, .github/, .cursor/)',
302
+ 'upgrade.synced': '✅ Synced: {added} new, {synced} updated, {skipped} unchanged',
303
+ 'upgrade.sync_errors': 'âš ī¸ {count} sync error(s)',
304
+ 'upgrade.upgraded': '✅ Upgraded to v{version}',
305
+ 'upgrade.verify_hint': '→ Run: v-flow doctor to verify integrity',
306
+ 'upgrade.apply_hint': 'â„šī¸ Run without --dry-run to apply changes.',
307
+ 'upgrade.templates_uptodate': '✅ All templates are up-to-date.',
308
+ };
309
+
310
+ const locales = { vi, en };
311
+
312
+ /**
313
+ * Detect language from environment
314
+ * Priority: --lang flag > VFLOW_LANG env > system locale > default (vi)
315
+ */
316
+ function detectLanguage() {
317
+ // Check process args for --lang
318
+ const langArgIndex = process.argv.indexOf('--lang');
319
+ if (langArgIndex !== -1 && process.argv[langArgIndex + 1]) {
320
+ const lang = process.argv[langArgIndex + 1].toLowerCase();
321
+ if (locales[lang]) return lang;
322
+ }
323
+
324
+ // Check environment variable
325
+ if (process.env.VFLOW_LANG) {
326
+ const lang = process.env.VFLOW_LANG.toLowerCase();
327
+ if (locales[lang]) return lang;
328
+ }
329
+
330
+ // Check system locale
331
+ const sysLang = (process.env.LANG || process.env.LC_ALL || '').toLowerCase();
332
+ if (sysLang.startsWith('en')) return 'en';
333
+
334
+ return 'vi'; // Default
335
+ }
336
+
337
+ /**
338
+ * Get translation function for current language
339
+ *
340
+ * @param {string} [lang] - Language override
341
+ * @returns {function(key: string, vars?: object): string}
342
+ */
343
+ function createT(lang) {
344
+ const currentLang = lang || detectLanguage();
345
+ const messages = locales[currentLang] || locales.vi;
346
+ const fallback = locales.vi;
347
+
348
+ return function t(key, vars = {}) {
349
+ let msg = messages[key] || fallback[key] || key;
350
+ for (const [k, v] of Object.entries(vars)) {
351
+ msg = msg.replace(new RegExp(`\\{${k}\\}`, 'g'), String(v));
352
+ }
353
+ return msg;
354
+ };
355
+ }
356
+
357
+ /**
358
+ * Get number of translation keys available
359
+ * @returns {{ vi: number, en: number }}
360
+ */
361
+ function getKeyCount() {
362
+ return {
363
+ vi: Object.keys(vi).length,
364
+ en: Object.keys(en).length,
365
+ };
366
+ }
367
+
368
+ module.exports = {
369
+ locales,
370
+ detectLanguage,
371
+ createT,
372
+ getKeyCount,
373
+ vi,
374
+ en,
375
+ };