@longph2102/v-flow 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +265 -0
- package/CHANGELOG.md +318 -0
- package/LICENSE +21 -0
- package/README.md +326 -0
- package/agents/ba-agent.md +437 -0
- package/agents/ba-critic-agent.md +156 -0
- package/agents/ba-to-ptyc-agent.md +112 -0
- package/agents/bugfix-analyst-agent.md +221 -0
- package/agents/constitute-agent.md +155 -0
- package/agents/help-agent.md +168 -0
- package/agents/implement-agent.md +220 -0
- package/agents/import-ba-docs-agent.md +164 -0
- package/agents/master-check-agent.md +228 -0
- package/agents/metrics-agent.md +180 -0
- package/agents/operations-agent.md +123 -0
- package/agents/plan-agent.md +218 -0
- package/agents/prototype-agent.md +191 -0
- package/agents/retrospective-agent.md +196 -0
- package/agents/review-agent.md +210 -0
- package/agents/sprint-agent.md +191 -0
- package/agents/status-agent.md +186 -0
- package/agents/sync-agent.md +201 -0
- package/agents/test-agent.md +166 -0
- package/agents/understand-agent.md +339 -0
- package/cli/commands/check.js +96 -0
- package/cli/commands/dev-quiz.js +107 -0
- package/cli/commands/doctor.js +348 -0
- package/cli/commands/feature.js +259 -0
- package/cli/commands/hooks.js +163 -0
- package/cli/commands/init.js +189 -0
- package/cli/commands/log.js +199 -0
- package/cli/commands/plugin.js +230 -0
- package/cli/commands/score-card.js +203 -0
- package/cli/commands/status.js +269 -0
- package/cli/commands/sync.js +59 -0
- package/cli/commands/upgrade.js +150 -0
- package/cli/commands/validate.js +1259 -0
- package/cli/commands/watch.js +151 -0
- package/cli/index.js +46 -0
- package/cli/lib/ac-test-gate.js +89 -0
- package/cli/lib/activity-log.js +209 -0
- package/cli/lib/cli-error.js +183 -0
- package/cli/lib/constitution-lint.js +561 -0
- package/cli/lib/dev-quiz-grade.js +127 -0
- package/cli/lib/governance.js +78 -0
- package/cli/lib/hook-targets.js +167 -0
- package/cli/lib/i18n.js +375 -0
- package/cli/lib/knowledge-oracle.js +379 -0
- package/cli/lib/logger.js +203 -0
- package/cli/lib/module-card-lint.js +304 -0
- package/cli/lib/module-card-score.js +223 -0
- package/cli/lib/plugins.js +481 -0
- package/cli/lib/scanner.js +692 -0
- package/cli/lib/sync-core.js +232 -0
- package/cli/lib/sync-targets.js +84 -0
- package/cli/lib/templates.js +268 -0
- package/cli/lib/yaml-parser.js +203 -0
- package/commands/v.ba-critic.md +101 -0
- package/commands/v.ba-to-ptyc.md +71 -0
- package/commands/v.bugfix.md +86 -0
- package/commands/v.check.md +131 -0
- package/commands/v.constitute.md +87 -0
- package/commands/v.constitution.md +84 -0
- package/commands/v.fork.md +127 -0
- package/commands/v.help.md +73 -0
- package/commands/v.hotfix.md +200 -0
- package/commands/v.implement.md +92 -0
- package/commands/v.import-ba-docs.md +222 -0
- package/commands/v.metrics.md +74 -0
- package/commands/v.operations.md +70 -0
- package/commands/v.plan.md +78 -0
- package/commands/v.prototype.md +121 -0
- package/commands/v.quickfix.md +169 -0
- package/commands/v.retrospective.md +80 -0
- package/commands/v.review.md +78 -0
- package/commands/v.rewind.md +127 -0
- package/commands/v.specify.md +118 -0
- package/commands/v.sprint.md +75 -0
- package/commands/v.status.md +62 -0
- package/commands/v.sync.md +81 -0
- package/commands/v.test.md +67 -0
- package/commands/v.understand.md +112 -0
- package/package.json +65 -0
- package/skills/_shared/constitution-reader/SKILL.md +109 -0
- package/skills/_shared/constitution-reader/config.json +52 -0
- package/skills/_shared/constitution-reader/examples/good/b1-phase-output.md +48 -0
- package/skills/_shared/constitution-reader/gotchas.md +46 -0
- package/skills/_shared/context-reader/SKILL.md +111 -0
- package/skills/_shared/context-reader/config.json +54 -0
- package/skills/_shared/context-reader/examples/good/legacy-nodejs-output.md +35 -0
- package/skills/_shared/context-reader/gotchas.md +49 -0
- package/skills/_shared/ears-notation/SKILL.md +63 -0
- package/skills/_shared/ears-notation/config.json +55 -0
- package/skills/_shared/ears-notation/examples/good/plan-test-interpretation.md +29 -0
- package/skills/_shared/ears-notation/gotchas.md +43 -0
- package/skills/check/cross-validator/SKILL.md +206 -0
- package/skills/check/cross-validator/config.json +33 -0
- package/skills/check/cross-validator/examples/good/validation-report-pass-with-concerns.md +105 -0
- package/skills/check/cross-validator/gotchas.md +43 -0
- package/skills/implement/constitution-enforcer/SKILL.md +134 -0
- package/skills/implement/constitution-enforcer/config.json +16 -0
- package/skills/implement/constitution-enforcer/examples/bad/vague-report.md +42 -0
- package/skills/implement/constitution-enforcer/examples/good/compliance-report.md +57 -0
- package/skills/implement/constitution-enforcer/gotchas.md +26 -0
- package/skills/implement/constitution-enforcer/scripts/check-constitution.sh +88 -0
- package/skills/implement/no-go-zone-guard/SKILL.md +173 -0
- package/skills/implement/no-go-zone-guard/config.json +28 -0
- package/skills/implement/no-go-zone-guard/examples/good/adapter-workaround.md +46 -0
- package/skills/implement/no-go-zone-guard/gotchas.md +27 -0
- package/skills/implement/no-go-zone-guard/scripts/check-nogo-zones.sh +148 -0
- package/skills/implement/no-go-zone-guard/scripts/nogo-precommit.sh +96 -0
- package/skills/implement/tdd-driver/SKILL.md +159 -0
- package/skills/implement/tdd-driver/config.json +33 -0
- package/skills/implement/tdd-driver/examples/good/tdd-cycle-product-repo.md +81 -0
- package/skills/implement/tdd-driver/gotchas.md +34 -0
- package/skills/metrics/metrics-collector/SKILL.md +133 -0
- package/skills/metrics/metrics-collector/config.json +16 -0
- package/skills/metrics/metrics-collector/examples/bad/incomplete-report.md +48 -0
- package/skills/metrics/metrics-collector/examples/good/full-metrics-report.md +101 -0
- package/skills/metrics/metrics-collector/gotchas.md +26 -0
- package/skills/operations/incident-runbook/SKILL.md +167 -0
- package/skills/operations/incident-runbook/config.json +21 -0
- package/skills/operations/incident-runbook/examples/bad/vague-incident-report.md +48 -0
- package/skills/operations/incident-runbook/examples/good/p1-hotfix-response.md +119 -0
- package/skills/operations/incident-runbook/gotchas.md +26 -0
- package/skills/plan/architecture-designer/SKILL.md +228 -0
- package/skills/plan/architecture-designer/config.json +32 -0
- package/skills/plan/architecture-designer/examples/bad/vague-plan.md +62 -0
- package/skills/plan/architecture-designer/examples/good/expand-contract-migration.md +56 -0
- package/skills/plan/architecture-designer/examples/good/plan-structure.md +58 -0
- package/skills/plan/architecture-designer/gotchas.md +45 -0
- package/skills/plan/task-breakdown/SKILL.md +208 -0
- package/skills/plan/task-breakdown/config.json +26 -0
- package/skills/plan/task-breakdown/examples/bad/vague-tasks.md +77 -0
- package/skills/plan/task-breakdown/examples/good/spike-clarify-tasks.md +66 -0
- package/skills/plan/task-breakdown/examples/good/tasks-login-feature.md +111 -0
- package/skills/plan/task-breakdown/gotchas.md +39 -0
- package/skills/prototype/LOGIC.md +240 -0
- package/skills/prototype/SKILL.md +185 -0
- package/skills/prototype/UI.md +407 -0
- package/skills/prototype/config.json +104 -0
- package/skills/prototype/examples/bad/prototype-notes.md +68 -0
- package/skills/prototype/examples/good/prototype-notes-ui.md +109 -0
- package/skills/prototype/examples/good/prototype-notes.md +67 -0
- package/skills/prototype/gotchas.md +128 -0
- package/skills/prototype/scripts/check-flow-state.ps1 +112 -0
- package/skills/prototype/scripts/check-flow-state.sh +104 -0
- package/skills/prototype/scripts/check-prototype-cleanup.ps1 +124 -0
- package/skills/prototype/scripts/check-prototype-cleanup.sh +109 -0
- package/skills/prototype/scripts/check-prototype-notes.ps1 +107 -0
- package/skills/prototype/scripts/check-prototype-notes.sh +102 -0
- package/skills/review/adversarial-reviewer/SKILL.md +137 -0
- package/skills/review/adversarial-reviewer/config.json +32 -0
- package/skills/review/adversarial-reviewer/examples/good/review-report-template.md +56 -0
- package/skills/review/adversarial-reviewer/gotchas.md +46 -0
- package/skills/review/adversarial-reviewer/scripts/quick-security-scan.sh +52 -0
- package/skills/specify/ba-bpmn-doc-gen/SKILL.md +108 -0
- package/skills/specify/ba-bpmn-doc-gen/reference/reference-bpmn-generation.md +528 -0
- package/skills/specify/ba-bpmn-doc-gen/reference/reference-drawio-flowchart.md +466 -0
- package/skills/specify/ba-critic/SKILL.md +172 -0
- package/skills/specify/ba-critic/config.json +32 -0
- package/skills/specify/ba-critic/examples/good/critic-report-round1.md +51 -0
- package/skills/specify/ba-critic/gotchas.md +40 -0
- package/skills/specify/ba-critic/scripts/check-spec-quality.sh +72 -0
- package/skills/specify/ba-doc-generator/SKILL.md +102 -0
- package/skills/specify/ba-doc-generator/references/template-clevel.md +84 -0
- package/skills/specify/ba-doc-generator/references/template-compliance.md +83 -0
- package/skills/specify/ba-doc-generator/references/template-dev.md +138 -0
- package/skills/specify/ba-doc-generator/references/template-partner.md +167 -0
- package/skills/specify/ba-doc-generator/references/template-pm.md +92 -0
- package/skills/specify/ba-doc-generator/references/template-review.md +114 -0
- package/skills/specify/ba-doc-generator/references/template-tester.md +108 -0
- package/skills/specify/ba-doc-generator/references/template-user.md +98 -0
- package/skills/specify/bugfix-analyst/SKILL.md +296 -0
- package/skills/specify/bugfix-analyst/config.json +41 -0
- package/skills/specify/bugfix-analyst/examples/bad/common-mistakes.md +71 -0
- package/skills/specify/bugfix-analyst/examples/good/email-validation-bugfix.md +53 -0
- package/skills/specify/bugfix-analyst/gotchas.md +51 -0
- package/skills/specify/ears-writer/SKILL.md +129 -0
- package/skills/specify/ears-writer/config.json +20 -0
- package/skills/specify/ears-writer/examples/bad/common-mistakes.md +17 -0
- package/skills/specify/ears-writer/examples/good/login-requirements.md +41 -0
- package/skills/specify/ears-writer/gotchas.md +43 -0
- package/skills/specify/ears-writer/scripts/check-ears-compliance.sh +51 -0
- package/skills/test/test-case-generator/SKILL.md +161 -0
- package/skills/test/test-case-generator/config.json +33 -0
- package/skills/test/test-case-generator/examples/good/test-cases-login.md +104 -0
- package/skills/test/test-case-generator/gotchas.md +43 -0
- package/skills/understand/ba-docs-scanner/SKILL.md +239 -0
- package/skills/understand/ba-docs-scanner/config.json +47 -0
- package/skills/understand/ba-docs-scanner/examples/good/work-order-br-extract.md +28 -0
- package/skills/understand/ba-docs-scanner/gotchas.md +44 -0
- package/skills/understand/ba-docs-scanner/merge-rules.md +47 -0
- package/skills/understand/codebase-scanner/SKILL.md +260 -0
- package/skills/understand/codebase-scanner/config.json +56 -0
- package/skills/understand/codebase-scanner/examples/good/menu-module-output.md +44 -0
- package/skills/understand/codebase-scanner/gotchas.md +42 -0
- package/skills/understand/codebase-scanner/scripts/scan-project-structure.sh +64 -0
- package/templates/DESIGN.md +456 -0
- package/templates/agent-command-template.yaml +240 -0
- package/templates/agent-config-template.md +170 -0
- package/templates/agent-definition-template.md +145 -0
- package/templates/agent-metrics-template.md +150 -0
- package/templates/api-contract-template.md +72 -0
- package/templates/bugfix-report-template.md +195 -0
- package/templates/bugfix-spec-template.md +134 -0
- package/templates/code-review-report-template.md +119 -0
- package/templates/constitution-template.md +234 -0
- package/templates/context-template.md +94 -0
- package/templates/data-model-template.md +95 -0
- package/templates/decision-log-template.md +92 -0
- package/templates/flow-state-template.yaml +208 -0
- package/templates/github/workflows/v-flow-validate.yml +30 -0
- package/templates/knowledge/adr-template.md +70 -0
- package/templates/knowledge/api-contract-template.md +140 -0
- package/templates/knowledge/domain-glossary.md +29 -0
- package/templates/knowledge/golden-tests-readme.md +115 -0
- package/templates/knowledge/lessons-learned.md +41 -0
- package/templates/knowledge/patterns.md +103 -0
- package/templates/module-card/SKILL.md +85 -0
- package/templates/module-card/api-specs.md +96 -0
- package/templates/module-card/business-quiz.md +119 -0
- package/templates/module-card/cross-service.md +125 -0
- package/templates/module-card/db.md +85 -0
- package/templates/module-card/dev-quiz.md +62 -0
- package/templates/module-card/permissions.md +83 -0
- package/templates/module-card/state-diagram.md +64 -0
- package/templates/module-card/tech-context.md +90 -0
- package/templates/module-card/ui-flows.md +91 -0
- package/templates/module-card/use-cases.md +142 -0
- package/templates/module-template.yaml +161 -0
- package/templates/operations-report-template.md +108 -0
- package/templates/plan-template.md +308 -0
- package/templates/prototype-notes-template.md +116 -0
- package/templates/ptyc/PTYC.template.docx +0 -0
- package/templates/ptyc/ptyc.meta.example.yaml +44 -0
- package/templates/retrospective-report-template.md +136 -0
- package/templates/security-review-template.md +84 -0
- package/templates/session-template.md +167 -0
- package/templates/spec-review-log-template.md +75 -0
- package/templates/spec-template.md +229 -0
- package/templates/sprint-status-template.md +101 -0
- package/templates/tasks-template.md +275 -0
- package/templates/test-cases-template.md +124 -0
- package/templates/ux-checklist-template.md +79 -0
- package/templates/validation-report-template.md +125 -0
- package/templates/vflow-config-template.yaml +22 -0
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const fs = require('fs-extra');
|
|
3
|
+
const chalk = require('chalk');
|
|
4
|
+
const { isInitialized, getVFlowDir, copyTemplate, listFeatures } = require('../lib/templates');
|
|
5
|
+
const { withErrorHandler, requireInit } = require('../lib/cli-error');
|
|
6
|
+
const { readFlowState } = require('../lib/yaml-parser');
|
|
7
|
+
|
|
8
|
+
module.exports = function (program) {
|
|
9
|
+
const feature = program
|
|
10
|
+
.command('feature')
|
|
11
|
+
.description('Manage V Flow features');
|
|
12
|
+
|
|
13
|
+
// ── v-flow feature create <name> ──────────────────────────────────────
|
|
14
|
+
feature
|
|
15
|
+
.command('create')
|
|
16
|
+
.description('Create a new feature with scaffolded artifacts')
|
|
17
|
+
.argument('<name>', 'Feature name (e.g. "user-login" or "001-user-login")')
|
|
18
|
+
.option('--type <type>', 'Project type: greenfield or legacy', 'greenfield')
|
|
19
|
+
.option('--no-session', 'Skip creating _session.md')
|
|
20
|
+
.action(withErrorHandler(async (name, options) => {
|
|
21
|
+
const projectRoot = process.cwd();
|
|
22
|
+
requireInit(projectRoot, isInitialized);
|
|
23
|
+
|
|
24
|
+
const mfDir = getVFlowDir(projectRoot);
|
|
25
|
+
const featuresDir = path.join(mfDir, 'features');
|
|
26
|
+
|
|
27
|
+
// Auto-prefix with next number if name doesn't start with digits
|
|
28
|
+
let featureDirName = name;
|
|
29
|
+
if (!/^\d{3}/.test(name)) {
|
|
30
|
+
const existing = await listFeatures(projectRoot);
|
|
31
|
+
const nextNum = existing.length > 0
|
|
32
|
+
? Math.max(...existing.map(f => parseInt(f.split('-')[0]) || 0)) + 1
|
|
33
|
+
: 1;
|
|
34
|
+
featureDirName = `${String(nextNum).padStart(3, '0')}-${name}`;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const featureDir = path.join(featuresDir, featureDirName);
|
|
38
|
+
|
|
39
|
+
// Check if feature already exists
|
|
40
|
+
if (fs.existsSync(featureDir)) {
|
|
41
|
+
console.log(chalk.yellow(`\n⚠️ Feature "${featureDirName}" already exists.`));
|
|
42
|
+
console.log(chalk.dim(` Path: ${featureDir}\n`));
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
console.log(chalk.cyan.bold(`\n🆕 Creating feature: ${featureDirName}\n`));
|
|
47
|
+
|
|
48
|
+
// Create feature directory
|
|
49
|
+
await fs.ensureDir(featureDir);
|
|
50
|
+
console.log(chalk.green(` ✅ Directory created: .v-flow/features/${featureDirName}/`));
|
|
51
|
+
|
|
52
|
+
const today = new Date().toISOString().split('T')[0];
|
|
53
|
+
const featureDisplayName = featureDirName.replace(/^\d+-/, '').replace(/-/g, ' ');
|
|
54
|
+
const featureId = featureDirName.split('-')[0];
|
|
55
|
+
|
|
56
|
+
// 1. Create spec.md from template
|
|
57
|
+
const specDest = path.join(featureDir, 'spec.md');
|
|
58
|
+
try {
|
|
59
|
+
await copyTemplate('spec-template.md', specDest, {
|
|
60
|
+
'TÊN FEATURE': featureDisplayName,
|
|
61
|
+
'YYYY-MM-DD': today,
|
|
62
|
+
});
|
|
63
|
+
console.log(chalk.green(' ✅ spec.md (specification template)'));
|
|
64
|
+
} catch {
|
|
65
|
+
// Create minimal spec if template not found
|
|
66
|
+
await fs.writeFile(specDest, `# Specification — ${featureDisplayName}\n\n> 📌 **Single Source of Truth** — Spec thắng khi có conflict với plan/tasks/tests.\n> Feature: ${featureDisplayName}\n\n## 1. Tổng quan\n\n*(BA Agent sẽ điền khi chạy /v.specify)*\n\n## 7. Status\n\n| Thông tin | Giá trị |\n|-----------|--------|\n| **Spec Version** | v1.0 |\n| **Trạng thái** | Draft |\n| **Ngày tạo** | ${today} |\n`, 'utf8');
|
|
67
|
+
console.log(chalk.green(' ✅ spec.md (minimal template)'));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// 2. Create .flow-state.yaml
|
|
71
|
+
const flowStateDest = path.join(featureDir, '.flow-state.yaml');
|
|
72
|
+
const flowStateContent = `# V Flow State — ${featureDirName}
|
|
73
|
+
# Tạo tự động bởi v-flow feature create
|
|
74
|
+
# ⚠️ KHÔNG sửa tay — dùng /v.status để xem
|
|
75
|
+
|
|
76
|
+
# ── IDENTITY ──
|
|
77
|
+
feature_id: "${featureId}"
|
|
78
|
+
feature_name: "${featureDirName}"
|
|
79
|
+
created_at: "${today}"
|
|
80
|
+
last_updated: "${today}"
|
|
81
|
+
project_type: "${options.type}"
|
|
82
|
+
schema_version: "2.0" # khớp SCHEMA_VERSION trong cli/commands/validate.js — feature mới phải theo schema hiện hành
|
|
83
|
+
pipeline_type: "full" # full | quickfix | hotfix — drive "lite" profile của validator
|
|
84
|
+
|
|
85
|
+
# ── PIPELINE STATE ──
|
|
86
|
+
current_phase: "S.1"
|
|
87
|
+
phase_status: "pending"
|
|
88
|
+
|
|
89
|
+
phase_history:
|
|
90
|
+
- phase: "S.1"
|
|
91
|
+
entered_at: "${today}"
|
|
92
|
+
exited_at: null
|
|
93
|
+
reason: "feature created via CLI"
|
|
94
|
+
|
|
95
|
+
# ── ARTIFACTS ──
|
|
96
|
+
artifacts:
|
|
97
|
+
spec:
|
|
98
|
+
exists: true
|
|
99
|
+
version: "v1.0"
|
|
100
|
+
status: "draft"
|
|
101
|
+
human_approved: false
|
|
102
|
+
plan:
|
|
103
|
+
exists: false
|
|
104
|
+
synced_to_spec: null
|
|
105
|
+
human_approved: false
|
|
106
|
+
tasks:
|
|
107
|
+
exists: false
|
|
108
|
+
synced_to_spec: null
|
|
109
|
+
total: 0
|
|
110
|
+
completed: 0
|
|
111
|
+
test_cases:
|
|
112
|
+
exists: false
|
|
113
|
+
synced_to_spec: null
|
|
114
|
+
total: 0
|
|
115
|
+
human_approved: false
|
|
116
|
+
validation_report:
|
|
117
|
+
exists: false
|
|
118
|
+
verdict: null
|
|
119
|
+
concerns_count: 0
|
|
120
|
+
code_review:
|
|
121
|
+
exists: false
|
|
122
|
+
verdict: null
|
|
123
|
+
|
|
124
|
+
# ── ROUTING ──
|
|
125
|
+
routing:
|
|
126
|
+
after_master_check:
|
|
127
|
+
last_verdict: null
|
|
128
|
+
route_decision: null
|
|
129
|
+
after_review:
|
|
130
|
+
last_verdict: null
|
|
131
|
+
route_decision: null
|
|
132
|
+
|
|
133
|
+
# ── HUMAN CHECKPOINTS ──
|
|
134
|
+
human_checkpoints:
|
|
135
|
+
spec_approved: false
|
|
136
|
+
plan_approved: false
|
|
137
|
+
master_check_approved: false
|
|
138
|
+
implementation_approved: false
|
|
139
|
+
review_approved: false
|
|
140
|
+
|
|
141
|
+
awaiting_checkpoint: "spec_approved"
|
|
142
|
+
|
|
143
|
+
# ── CONCERNS ──
|
|
144
|
+
active_concerns: []
|
|
145
|
+
|
|
146
|
+
# ── BLOCKED ──
|
|
147
|
+
blocked: false
|
|
148
|
+
blocked_reason: null
|
|
149
|
+
|
|
150
|
+
# ── METADATA ──
|
|
151
|
+
spec_version_latest: "v1.0"
|
|
152
|
+
session_file: ".v-flow/features/${featureDirName}/_session.md"
|
|
153
|
+
notes: ""
|
|
154
|
+
`;
|
|
155
|
+
await fs.writeFile(flowStateDest, flowStateContent, 'utf8');
|
|
156
|
+
console.log(chalk.green(' ✅ .flow-state.yaml (pipeline state)'));
|
|
157
|
+
|
|
158
|
+
// 3. Create _session.md
|
|
159
|
+
if (options.session !== false) {
|
|
160
|
+
const sessionDest = path.join(featureDir, '_session.md');
|
|
161
|
+
try {
|
|
162
|
+
await copyTemplate('session-template.md', sessionDest);
|
|
163
|
+
// Replace placeholders
|
|
164
|
+
let content = await fs.readFile(sessionDest, 'utf8');
|
|
165
|
+
content = content
|
|
166
|
+
.replace(/\[Feature Name\]/g, featureDisplayName)
|
|
167
|
+
.replace(/NNN-feature-name/g, featureDirName)
|
|
168
|
+
.replace(/NNN/g, featureId);
|
|
169
|
+
await fs.writeFile(sessionDest, content, 'utf8');
|
|
170
|
+
console.log(chalk.green(' ✅ _session.md (working memory)'));
|
|
171
|
+
} catch {
|
|
172
|
+
await fs.writeFile(sessionDest, `# Session Memory — ${featureDisplayName}\n\n> Working memory cho feature này.\n\n## 📋 Tóm Tắt Nhanh\n\n\`\`\`\nPhase hiện tại : S.1\nBắt đầu lúc : ${today}\nSpec version : v1.0\n\`\`\`\n\n## 🧠 Key Decisions\n\n*(Chưa có)*\n\n## ❓ Open Questions\n\n*(Chưa có)*\n\n## ⚡ Execution Trace\n\n*(Agent sẽ append khi hoàn thành mỗi phase)*\n`, 'utf8');
|
|
173
|
+
console.log(chalk.green(' ✅ _session.md (minimal template)'));
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Summary
|
|
178
|
+
console.log(chalk.cyan.bold(`\n🎉 Feature "${featureDirName}" created!\n`));
|
|
179
|
+
console.log(chalk.white('Files created:'));
|
|
180
|
+
console.log(chalk.dim(` 📂 .v-flow/features/${featureDirName}/`));
|
|
181
|
+
console.log(chalk.dim(` ├── spec.md ← Write spec here (or run /v.specify)`));
|
|
182
|
+
console.log(chalk.dim(` ├── .flow-state.yaml ← Pipeline state (auto-managed)`));
|
|
183
|
+
if (options.session !== false) {
|
|
184
|
+
console.log(chalk.dim(` └── _session.md ← Working memory`));
|
|
185
|
+
}
|
|
186
|
+
console.log('');
|
|
187
|
+
console.log(chalk.white('Next steps:'));
|
|
188
|
+
console.log(chalk.yellow(` 1. Run /v.specify to generate spec from requirements`));
|
|
189
|
+
console.log(chalk.dim(` 2. Or edit spec.md manually, then run /v.plan`));
|
|
190
|
+
console.log('');
|
|
191
|
+
}));
|
|
192
|
+
|
|
193
|
+
// ── v-flow feature list ───────────────────────────────────────────────
|
|
194
|
+
feature
|
|
195
|
+
.command('list')
|
|
196
|
+
.alias('ls')
|
|
197
|
+
.description('List all features and their current phase')
|
|
198
|
+
.action(withErrorHandler(async () => {
|
|
199
|
+
const projectRoot = process.cwd();
|
|
200
|
+
requireInit(projectRoot, isInitialized);
|
|
201
|
+
|
|
202
|
+
const features = await listFeatures(projectRoot);
|
|
203
|
+
|
|
204
|
+
if (features.length === 0) {
|
|
205
|
+
console.log(chalk.yellow('\nNo features found.'));
|
|
206
|
+
console.log(chalk.dim('Create one: v-flow feature create <name>\n'));
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
console.log(chalk.cyan.bold(`\n📋 Features (${features.length})\n`));
|
|
211
|
+
|
|
212
|
+
const mfDir = getVFlowDir(projectRoot);
|
|
213
|
+
|
|
214
|
+
for (const feature of features) {
|
|
215
|
+
const featureDir = path.join(mfDir, 'features', feature);
|
|
216
|
+
const flowStatePath = path.join(featureDir, '.flow-state.yaml');
|
|
217
|
+
|
|
218
|
+
let phase = '?';
|
|
219
|
+
let status = '';
|
|
220
|
+
|
|
221
|
+
if (fs.existsSync(flowStatePath)) {
|
|
222
|
+
const flowState = await readFlowState(featureDir);
|
|
223
|
+
if (flowState) {
|
|
224
|
+
if (flowState.currentPhase) phase = flowState.currentPhase;
|
|
225
|
+
if (flowState.phaseStatus) status = flowState.phaseStatus;
|
|
226
|
+
}
|
|
227
|
+
} else {
|
|
228
|
+
// Infer from existing files
|
|
229
|
+
const hasSpec = fs.existsSync(path.join(featureDir, 'spec.md'));
|
|
230
|
+
const hasPlan = fs.existsSync(path.join(featureDir, 'plan.md'));
|
|
231
|
+
const hasTests = fs.existsSync(path.join(featureDir, 'test-cases.md'));
|
|
232
|
+
const hasValidation = fs.existsSync(path.join(featureDir, 'validation-report.md'));
|
|
233
|
+
if (!hasSpec) phase = 'S.1';
|
|
234
|
+
else if (!hasPlan) phase = 'B.0a';
|
|
235
|
+
else if (!hasTests) phase = 'B.0b';
|
|
236
|
+
else if (!hasValidation) phase = 'R.0';
|
|
237
|
+
else phase = 'B.1';
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const statusIcon = {
|
|
241
|
+
'pending': '⏳',
|
|
242
|
+
'in_progress': '🔄',
|
|
243
|
+
'awaiting_human': '👤',
|
|
244
|
+
'done': '✅',
|
|
245
|
+
'blocked': '🚫',
|
|
246
|
+
}[status] || '📂';
|
|
247
|
+
|
|
248
|
+
const phaseColor = phase.startsWith('S') ? chalk.blue
|
|
249
|
+
: phase.startsWith('B') ? chalk.yellow
|
|
250
|
+
: phase.startsWith('R') ? chalk.magenta
|
|
251
|
+
: phase.startsWith('M') ? chalk.green
|
|
252
|
+
: chalk.cyan;
|
|
253
|
+
|
|
254
|
+
console.log(` ${statusIcon} ${chalk.white.bold(feature)} ${phaseColor(`[${phase}]`)} ${chalk.dim(status)}`);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
console.log('');
|
|
258
|
+
}));
|
|
259
|
+
};
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* v-flow hooks — install / inspect / remove activity-logging lifecycle hooks
|
|
3
|
+
* for the supported AI coding tools.
|
|
4
|
+
*
|
|
5
|
+
* Subcommands:
|
|
6
|
+
* v-flow hooks install [--target <key|all>] — write `v-flow log --hook` hooks
|
|
7
|
+
* v-flow hooks status — show which tools have it installed
|
|
8
|
+
* v-flow hooks uninstall [--target <key|all>] — remove v-flow's hooks (keep user's)
|
|
9
|
+
*
|
|
10
|
+
* Each tool's hook calls `v-flow log --hook <provider>`, which logs every agent
|
|
11
|
+
* tool-use / session boundary into .v-flow/logs/. Configs are merged, never
|
|
12
|
+
* clobbered, and the operation is idempotent.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const path = require('path');
|
|
16
|
+
const fs = require('fs-extra');
|
|
17
|
+
const chalk = require('chalk');
|
|
18
|
+
const { withErrorHandler, ValidationError, printOk, printWarn, printError, printDim } = require('../lib/cli-error');
|
|
19
|
+
const { isInitialized } = require('../lib/templates');
|
|
20
|
+
const { PROVIDERS, getProvider, mergeConfig, removeConfig, isInstalled } = require('../lib/hook-targets');
|
|
21
|
+
|
|
22
|
+
module.exports = function (program) {
|
|
23
|
+
const hooksCmd = program
|
|
24
|
+
.command('hooks')
|
|
25
|
+
.description('🪝 Activity-logging hooks for AI tools (claude, cursor, copilot, codex, antigravity)');
|
|
26
|
+
|
|
27
|
+
// ── v-flow hooks install ──
|
|
28
|
+
hooksCmd
|
|
29
|
+
.command('install')
|
|
30
|
+
.description('Install v-flow logging hooks into AI tool configs')
|
|
31
|
+
.option('--target <key>', 'Only this tool (claude|cursor|copilot|codex|antigravity); default all')
|
|
32
|
+
.option('--abs', 'Use an absolute node + CLI path instead of `v-flow` (avoids PATH issues in GUI IDEs)')
|
|
33
|
+
.action(
|
|
34
|
+
withErrorHandler(async (options) => {
|
|
35
|
+
const projectRoot = process.cwd();
|
|
36
|
+
const targets = resolveTargets(options.target);
|
|
37
|
+
const bin = options.abs
|
|
38
|
+
? `"${process.execPath}" "${path.resolve(__dirname, '..', 'index.js')}"`
|
|
39
|
+
: 'v-flow';
|
|
40
|
+
|
|
41
|
+
console.log(chalk.cyan.bold('\n🪝 Installing activity-logging hooks\n'));
|
|
42
|
+
|
|
43
|
+
let installed = 0;
|
|
44
|
+
for (const provider of targets) {
|
|
45
|
+
const filePath = path.join(projectRoot, ...provider.file);
|
|
46
|
+
const rel = path.relative(projectRoot, filePath) || filePath;
|
|
47
|
+
|
|
48
|
+
const existing = await readJsonSafe(filePath);
|
|
49
|
+
if (existing === PARSE_ERROR) {
|
|
50
|
+
printError(`${provider.label}: ${rel} is not valid JSON — skipped (won't clobber)`);
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const merged = mergeConfig(provider, existing || {}, { bin });
|
|
55
|
+
await fs.ensureDir(path.dirname(filePath));
|
|
56
|
+
await fs.writeJson(filePath, merged, { spaces: 2 });
|
|
57
|
+
printOk(`${provider.label} → ${rel}`);
|
|
58
|
+
installed++;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
console.log('');
|
|
62
|
+
printDim(`Installed for ${installed}/${targets.length} tool(s). Hooks call: ${bin} log --hook <provider>`);
|
|
63
|
+
if (options.abs) {
|
|
64
|
+
printWarn('Absolute paths are machine-specific — re-run `v-flow hooks install --abs` on each machine; do not commit these configs.');
|
|
65
|
+
} else {
|
|
66
|
+
printDim('Tip: `v-flow` must be on PATH for the hook to run. GUI IDEs may not inherit your shell PATH — use --abs if hooks do not fire.');
|
|
67
|
+
}
|
|
68
|
+
if (!isInitialized(projectRoot)) {
|
|
69
|
+
printWarn('No .v-flow/ here yet — hook entries will be logged only after `v-flow init` (or set VFLOW_LOG_DIR).');
|
|
70
|
+
}
|
|
71
|
+
console.log('');
|
|
72
|
+
})
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
// ── v-flow hooks status ──
|
|
76
|
+
hooksCmd
|
|
77
|
+
.command('status')
|
|
78
|
+
.description('Show which AI tools have v-flow logging hooks installed')
|
|
79
|
+
.action(
|
|
80
|
+
withErrorHandler(async () => {
|
|
81
|
+
const projectRoot = process.cwd();
|
|
82
|
+
console.log(chalk.cyan.bold('\n🪝 Hook status\n'));
|
|
83
|
+
|
|
84
|
+
for (const provider of PROVIDERS) {
|
|
85
|
+
const filePath = path.join(projectRoot, ...provider.file);
|
|
86
|
+
const rel = path.relative(projectRoot, filePath) || filePath;
|
|
87
|
+
const existing = await readJsonSafe(filePath);
|
|
88
|
+
|
|
89
|
+
let mark;
|
|
90
|
+
if (existing === PARSE_ERROR) {
|
|
91
|
+
mark = chalk.red('invalid JSON');
|
|
92
|
+
} else if (existing && isInstalled(provider, existing)) {
|
|
93
|
+
mark = chalk.green('✅ installed');
|
|
94
|
+
} else if (existing) {
|
|
95
|
+
mark = chalk.yellow('⬜ config exists, no v-flow hook');
|
|
96
|
+
} else {
|
|
97
|
+
mark = chalk.dim('— not configured');
|
|
98
|
+
}
|
|
99
|
+
console.log(` ${chalk.white(provider.label.padEnd(16))} ${mark} ${chalk.dim(rel)}`);
|
|
100
|
+
}
|
|
101
|
+
console.log('');
|
|
102
|
+
})
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
// ── v-flow hooks uninstall ──
|
|
106
|
+
hooksCmd
|
|
107
|
+
.command('uninstall')
|
|
108
|
+
.description("Remove v-flow's logging hooks (leaves your own hooks intact)")
|
|
109
|
+
.option('--target <key>', 'Only this tool (claude|cursor|copilot|codex|antigravity); default all')
|
|
110
|
+
.action(
|
|
111
|
+
withErrorHandler(async (options) => {
|
|
112
|
+
const projectRoot = process.cwd();
|
|
113
|
+
const targets = resolveTargets(options.target);
|
|
114
|
+
|
|
115
|
+
console.log(chalk.cyan.bold('\n🪝 Removing activity-logging hooks\n'));
|
|
116
|
+
|
|
117
|
+
for (const provider of targets) {
|
|
118
|
+
const filePath = path.join(projectRoot, ...provider.file);
|
|
119
|
+
const rel = path.relative(projectRoot, filePath) || filePath;
|
|
120
|
+
const existing = await readJsonSafe(filePath);
|
|
121
|
+
|
|
122
|
+
if (existing === PARSE_ERROR) {
|
|
123
|
+
printError(`${provider.label}: ${rel} is not valid JSON — skipped`);
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
if (!existing || !isInstalled(provider, existing)) {
|
|
127
|
+
printDim(`${provider.label}: nothing to remove`);
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
const stripped = removeConfig(provider, existing);
|
|
131
|
+
await fs.writeJson(filePath, stripped, { spaces: 2 });
|
|
132
|
+
printOk(`${provider.label} → removed from ${rel}`);
|
|
133
|
+
}
|
|
134
|
+
console.log('');
|
|
135
|
+
})
|
|
136
|
+
);
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
// ── helpers ──────────────────────────────────────────────────────────────
|
|
140
|
+
|
|
141
|
+
const PARSE_ERROR = Symbol('parse-error');
|
|
142
|
+
|
|
143
|
+
function resolveTargets(targetKey) {
|
|
144
|
+
if (!targetKey || targetKey === 'all') return PROVIDERS;
|
|
145
|
+
const provider = getProvider(targetKey);
|
|
146
|
+
if (!provider) {
|
|
147
|
+
throw new ValidationError(
|
|
148
|
+
`Unknown target: ${targetKey}`,
|
|
149
|
+
`Valid: ${PROVIDERS.map((p) => p.key).join(', ')}, all`
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
return [provider];
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/** Read JSON if present; null if missing; PARSE_ERROR sentinel if unparseable. */
|
|
156
|
+
async function readJsonSafe(filePath) {
|
|
157
|
+
if (!(await fs.pathExists(filePath))) return null;
|
|
158
|
+
try {
|
|
159
|
+
return await fs.readJson(filePath);
|
|
160
|
+
} catch (_) {
|
|
161
|
+
return PARSE_ERROR;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const chalk = require('chalk');
|
|
3
|
+
const {
|
|
4
|
+
isInitialized,
|
|
5
|
+
scaffoldDirectories,
|
|
6
|
+
copyTemplate,
|
|
7
|
+
getVFlowDir,
|
|
8
|
+
scanCodebase,
|
|
9
|
+
generateContextMd,
|
|
10
|
+
} = require('../lib/templates');
|
|
11
|
+
const { runSync, syncVFlowAssets, selectTargets } = require('../lib/sync-core');
|
|
12
|
+
const { withErrorHandler } = require('../lib/cli-error');
|
|
13
|
+
const { createT } = require('../lib/i18n');
|
|
14
|
+
|
|
15
|
+
module.exports = function (program) {
|
|
16
|
+
program
|
|
17
|
+
.command('init')
|
|
18
|
+
.description('Initialize V Flow in the current project')
|
|
19
|
+
.argument('[project-name]', 'Project name (optional)', '')
|
|
20
|
+
.option('--ai <agents>', 'AI agents to setup: antigravity, copilot, cursor, claude, all (default: all)', 'all')
|
|
21
|
+
.option('--skip-ai', 'Skip AI agent file setup')
|
|
22
|
+
.option('--force', 'Force reinitialize (overwrite existing files)')
|
|
23
|
+
.action(withErrorHandler(async (projectName, options) => {
|
|
24
|
+
const t = createT();
|
|
25
|
+
const projectRoot = process.cwd();
|
|
26
|
+
const sourceRoot = path.resolve(__dirname, '../..');
|
|
27
|
+
|
|
28
|
+
console.log(chalk.cyan.bold(`\n${t('init.title')}\n`));
|
|
29
|
+
|
|
30
|
+
// Check if already initialized
|
|
31
|
+
if (isInitialized(projectRoot) && !options.force) {
|
|
32
|
+
console.log(chalk.yellow(t('init.already_exists')));
|
|
33
|
+
console.log(chalk.dim(`${t('init.already_exists_hint') }\n`));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Step 1: Scaffold directories
|
|
37
|
+
console.log(chalk.white(t('init.creating_dirs')));
|
|
38
|
+
await scaffoldDirectories(projectRoot);
|
|
39
|
+
console.log(chalk.green(` ${t('init.dir_created')}`));
|
|
40
|
+
|
|
41
|
+
// Step 2: Copy constitution template
|
|
42
|
+
const mfDir = getVFlowDir(projectRoot);
|
|
43
|
+
const constitutionDest = path.join(mfDir, 'constitution.md');
|
|
44
|
+
const fs = require('fs-extra');
|
|
45
|
+
|
|
46
|
+
if (!fs.existsSync(constitutionDest) || options.force) {
|
|
47
|
+
const today = new Date().toISOString().split('T')[0];
|
|
48
|
+
await copyTemplate('constitution-template.md', constitutionDest, {
|
|
49
|
+
'TÊN DỰ ÁN': projectName || path.basename(projectRoot),
|
|
50
|
+
'YYYY-MM-DD': today,
|
|
51
|
+
});
|
|
52
|
+
console.log(chalk.green(` ${t('init.constitution_created')}`));
|
|
53
|
+
} else {
|
|
54
|
+
console.log(chalk.dim(` ${t('init.constitution_exists')}`));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Step 3: Copy agent-config template
|
|
58
|
+
const agentConfigDest = path.join(mfDir, 'agent-config.md');
|
|
59
|
+
if (!fs.existsSync(agentConfigDest) || options.force) {
|
|
60
|
+
await copyTemplate('agent-config-template.md', agentConfigDest);
|
|
61
|
+
console.log(chalk.green(` ${t('init.agent_config_created')}`));
|
|
62
|
+
} else {
|
|
63
|
+
console.log(chalk.dim(` ${t('init.agent_config_exists')}`));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Step 4: Scan codebase & create context.md
|
|
67
|
+
const contextDest = path.join(mfDir, 'context.md');
|
|
68
|
+
if (!fs.existsSync(contextDest) || options.force) {
|
|
69
|
+
process.stdout.write(chalk.white(`\n${t('init.scanning')}`));
|
|
70
|
+
try {
|
|
71
|
+
const scan = await scanCodebase(projectRoot);
|
|
72
|
+
const contextContent = generateContextMd(scan);
|
|
73
|
+
await fs.writeFile(contextDest, contextContent, 'utf8');
|
|
74
|
+
process.stdout.write(` ${ chalk.green('✅\n')}`);
|
|
75
|
+
console.log(chalk.green(` ${t('init.scan_complete', { count: scan.totalFiles })}`));
|
|
76
|
+
if (scan.languages.length > 0) {
|
|
77
|
+
console.log(chalk.dim(t('init.scan_languages', { languages: scan.languages.join(', ') })));
|
|
78
|
+
}
|
|
79
|
+
if (scan.frameworks.length > 0) {
|
|
80
|
+
console.log(chalk.dim(t('init.scan_frameworks', { frameworks: scan.frameworks.join(', ') })));
|
|
81
|
+
}
|
|
82
|
+
console.log(chalk.yellow(` ${t('init.scan_warning')}`));
|
|
83
|
+
} catch (err) {
|
|
84
|
+
process.stdout.write(` ${ chalk.yellow(`${t('init.scan_failed')}\n`)}`);
|
|
85
|
+
await copyTemplate('context-template.md', contextDest);
|
|
86
|
+
console.log(chalk.dim(` ${t('init.scan_template')}`));
|
|
87
|
+
}
|
|
88
|
+
} else {
|
|
89
|
+
console.log(chalk.dim(` ${t('init.context_exists')}`));
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Step 5: Seed Knowledge Base
|
|
93
|
+
const knowledgeDir = path.join(mfDir, 'knowledge');
|
|
94
|
+
const knowledgeTemplateDir = path.join(sourceRoot, 'templates', 'knowledge');
|
|
95
|
+
|
|
96
|
+
const knowledgeFiles = [
|
|
97
|
+
{ template: 'domain-glossary.md', dest: 'domain-glossary.md' },
|
|
98
|
+
{ template: 'patterns.md', dest: 'patterns/v-flow-patterns.md' },
|
|
99
|
+
{ template: 'lessons-learned.md', dest: 'lessons-learned/v-flow-lessons.md' },
|
|
100
|
+
];
|
|
101
|
+
|
|
102
|
+
let kbCreated = 0;
|
|
103
|
+
for (const kf of knowledgeFiles) {
|
|
104
|
+
const destPath = path.join(knowledgeDir, kf.dest);
|
|
105
|
+
const srcPath = path.join(knowledgeTemplateDir, kf.template);
|
|
106
|
+
if (!fs.existsSync(destPath) || options.force) {
|
|
107
|
+
await fs.ensureDir(path.dirname(destPath));
|
|
108
|
+
if (fs.existsSync(srcPath)) {
|
|
109
|
+
await fs.copy(srcPath, destPath);
|
|
110
|
+
}
|
|
111
|
+
kbCreated++;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
if (kbCreated > 0) {
|
|
115
|
+
console.log(chalk.green(` ${t('init.knowledge_seeded', { count: kbCreated })}`));
|
|
116
|
+
} else {
|
|
117
|
+
console.log(chalk.dim(` ${t('init.knowledge_exists')}`));
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Step 7: Copy DESIGN.md into .v-flow/DESIGN.md
|
|
121
|
+
const designDest = path.join(mfDir, 'DESIGN.md');
|
|
122
|
+
if (!fs.existsSync(designDest) || options.force) {
|
|
123
|
+
await copyTemplate('DESIGN.md', designDest);
|
|
124
|
+
console.log(chalk.green(` ✅ Design system: .v-flow/DESIGN.md`));
|
|
125
|
+
} else {
|
|
126
|
+
console.log(chalk.dim(` .v-flow/DESIGN.md already exists`));
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Step 8: Scaffold CI gate — runs `v-flow validate --strict` on PRs so
|
|
130
|
+
// spec/plan/tasks can't drift. Without this the validator gates nothing.
|
|
131
|
+
const ciDest = path.join(projectRoot, '.github', 'workflows', 'v-flow-validate.yml');
|
|
132
|
+
if (!fs.existsSync(ciDest) || options.force) {
|
|
133
|
+
await fs.ensureDir(path.dirname(ciDest));
|
|
134
|
+
await copyTemplate('github/workflows/v-flow-validate.yml', ciDest);
|
|
135
|
+
console.log(chalk.green(' ✅ CI gate: .github/workflows/v-flow-validate.yml'));
|
|
136
|
+
} else {
|
|
137
|
+
console.log(chalk.dim(' .github/workflows/v-flow-validate.yml already exists'));
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Step 6: Sync AI agent files using shared sync-core
|
|
141
|
+
if (!options.skipAi) {
|
|
142
|
+
console.log(chalk.white(`\n${t('init.syncing')}`));
|
|
143
|
+
|
|
144
|
+
const ai = options.ai.toLowerCase();
|
|
145
|
+
|
|
146
|
+
// Use the shared sync engine — same logic as `v-flow sync --apply`
|
|
147
|
+
const stats = await runSync({
|
|
148
|
+
projectRoot,
|
|
149
|
+
sourceRoot,
|
|
150
|
+
dryRun: false,
|
|
151
|
+
verbose: false,
|
|
152
|
+
target: ai,
|
|
153
|
+
silent: true,
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
// Per-target summary — derived from the same registry runSync walks,
|
|
157
|
+
// so a new vendor shows up here automatically (no edits needed).
|
|
158
|
+
for (const tgt of selectTargets(ai)) {
|
|
159
|
+
const cmdDir = path.join(projectRoot, ...tgt.commands.dir);
|
|
160
|
+
const ext = tgt.commands.ext || '.md';
|
|
161
|
+
const cmdCount = fs.existsSync(cmdDir)
|
|
162
|
+
? (await fs.readdir(cmdDir)).filter(f => f.endsWith(ext)).length
|
|
163
|
+
: 0;
|
|
164
|
+
const globals = (tgt.globals || []).map(g => path.join(...g.dest)).join(', ');
|
|
165
|
+
console.log(chalk.green(` ✅ ${tgt.label}: ${globals}`));
|
|
166
|
+
console.log(chalk.green(` ${path.join(...tgt.commands.dir)}/ (${cmdCount} slash commands) + agents + skills`));
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
console.log(chalk.green(' ✅ Templates: .v-flow/templates/ (all templates synced)'));
|
|
170
|
+
console.log(chalk.dim(`\n ${t('init.sync_stats', { added: stats.added, synced: stats.synced, skipped: stats.skipped })}`));
|
|
171
|
+
} else {
|
|
172
|
+
// --skip-ai bỏ qua file AI-tool, NHƯNG vẫn cài runtime assets (.v-flow/templates + skills)
|
|
173
|
+
// để agent path `.v-flow/...` resolve được (chúng là framework runtime, không phải AI files).
|
|
174
|
+
const assets = await syncVFlowAssets({ projectRoot, sourceRoot, dryRun: false });
|
|
175
|
+
console.log(chalk.green(` ✅ Runtime assets → .v-flow/ (${assets.templates} templates + ${assets.skills} skill files; AI agent files skipped)`));
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Summary
|
|
179
|
+
console.log(chalk.cyan.bold(`\n${t('init.complete')}\n`));
|
|
180
|
+
console.log(chalk.white(t('init.next_steps')));
|
|
181
|
+
console.log(chalk.yellow(` ${t('init.review_context')}`));
|
|
182
|
+
console.log(chalk.dim(` ${t('init.edit_constitution')}`));
|
|
183
|
+
console.log(chalk.dim(` ${t('init.edit_agent_config')}`));
|
|
184
|
+
console.log(chalk.dim(` ${t('init.import_ba_docs')}`));
|
|
185
|
+
console.log(chalk.dim(` ${t('init.start_feature')}`));
|
|
186
|
+
console.log(chalk.dim(` ${t('init.sync_hint')}`));
|
|
187
|
+
console.log('');
|
|
188
|
+
}));
|
|
189
|
+
};
|