@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,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* v-flow dev-quiz — Machine-graded technical verification (Decision 2: Dev = graph key).
|
|
3
|
+
*
|
|
4
|
+
* The dev counterpart of business-quiz: instead of a human answer key, the GRAPH
|
|
5
|
+
* is the key. Each question declares a `target` symbol; ground truth = its real
|
|
6
|
+
* blast radius from GitNexus `impact`.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* v-flow dev-quiz <module> # print graph-derived answer key (tech-lead briefing)
|
|
10
|
+
* v-flow dev-quiz <module> --answers a.md # grade an answers file (F1 vs graph)
|
|
11
|
+
* v-flow dev-quiz <module> --json [--repo R]
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const path = require('path');
|
|
15
|
+
const fs = require('fs-extra');
|
|
16
|
+
const chalk = require('chalk');
|
|
17
|
+
const { isInitialized, getVFlowDir } = require('../lib/templates');
|
|
18
|
+
const { withErrorHandler, requireInit, printWarn, printError } = require('../lib/cli-error');
|
|
19
|
+
const { getOracle } = require('../lib/knowledge-oracle');
|
|
20
|
+
const { generateGroundTruth, gradeDevQuiz } = require('../lib/dev-quiz-grade');
|
|
21
|
+
|
|
22
|
+
function resolveCard(modulesDir, moduleName) {
|
|
23
|
+
for (const cand of [moduleName, `domain-${moduleName}`]) {
|
|
24
|
+
if (fs.existsSync(path.join(modulesDir, cand, 'dev-quiz.md'))) return cand;
|
|
25
|
+
}
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
module.exports = function (program) {
|
|
30
|
+
program
|
|
31
|
+
.command('dev-quiz <module>')
|
|
32
|
+
.description('Kiểm chứng kỹ thuật module card — chấm máy theo GitNexus impact (graph = answer key)')
|
|
33
|
+
.option('--answers <file>', 'File trả lời để chấm (F1 vs ground truth); bỏ trống = in answer key từ graph')
|
|
34
|
+
.option('--oracle <mode>', 'Oracle: gitnexus (mặc định, cần full-graph) | none', 'gitnexus')
|
|
35
|
+
.option('--repo <name>', 'Tên repo GitNexus (nếu cần chỉ định)')
|
|
36
|
+
.option('--json', 'Xuất JSON')
|
|
37
|
+
.action(
|
|
38
|
+
withErrorHandler(async (moduleName, options) => {
|
|
39
|
+
const projectRoot = process.cwd();
|
|
40
|
+
requireInit(projectRoot, isInitialized);
|
|
41
|
+
|
|
42
|
+
const modulesDir = path.join(getVFlowDir(projectRoot), 'modules');
|
|
43
|
+
const card = resolveCard(modulesDir, moduleName);
|
|
44
|
+
if (!card) {
|
|
45
|
+
printError(`Không tìm thấy dev-quiz.md cho module "${moduleName}" trong .v-flow/modules/`);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
const quizContent = fs.readFileSync(path.join(modulesDir, card, 'dev-quiz.md'), 'utf8');
|
|
49
|
+
const oracle = getOracle(options.oracle, projectRoot, { repo: options.repo });
|
|
50
|
+
|
|
51
|
+
if (oracle.name !== 'full-graph') {
|
|
52
|
+
printWarn(`Oracle "${oracle.name}" không có impact() → dev-quiz cần --oracle gitnexus để chấm.`);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// ── Grade mode ──────────────────────────────────────
|
|
56
|
+
if (options.answers) {
|
|
57
|
+
const ansPath = path.resolve(projectRoot, options.answers);
|
|
58
|
+
if (!fs.existsSync(ansPath)) {
|
|
59
|
+
printError(`--answers: file không tồn tại: ${options.answers}`);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
const result = gradeDevQuiz(quizContent, fs.readFileSync(ansPath, 'utf8'), oracle);
|
|
63
|
+
if (options.json) {
|
|
64
|
+
console.log(JSON.stringify({ module: card, ...result }, null, 2));
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
console.log(chalk.cyan.bold(`\n🧠 Dev-Quiz — ${card} (chấm máy theo graph)\n`));
|
|
68
|
+
for (const q of result.questions) {
|
|
69
|
+
if (!q.verified) {
|
|
70
|
+
console.log(` ⚠️ ${q.id} ${chalk.dim(`[${q.target}]`)} — ${chalk.yellow('unverified')}: ${q.reason}`);
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
const icon = q.f1 >= 0.8 ? '✅' : q.f1 >= 0.5 ? '🟡' : '❌';
|
|
74
|
+
console.log(
|
|
75
|
+
` ${icon} ${q.id} ${chalk.dim(`[${q.target}]`)} F1=${chalk.bold(q.f1)} ${
|
|
76
|
+
chalk.dim(`(P=${q.precision} R=${q.recall} · risk=${q.risk} · truth=${q.truth.length})`)}`
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
const tag = result.verified ? chalk.bold(`${result.score}/1`) : chalk.yellow('unverified');
|
|
80
|
+
console.log(chalk.cyan.bold(`\n Dev-correctness: ${tag}\n`));
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// ── Generate mode (graph-derived answer key) ────────
|
|
85
|
+
const gt = generateGroundTruth(quizContent, oracle);
|
|
86
|
+
if (options.json) {
|
|
87
|
+
console.log(JSON.stringify({ module: card, ...gt }, null, 2));
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
console.log(chalk.cyan.bold(`\n🧠 Dev-Quiz Answer Key — ${card} (sinh từ GitNexus)\n`));
|
|
91
|
+
if (gt.questions.length === 0) {
|
|
92
|
+
console.log(chalk.dim(' dev-quiz.md chưa khai báo câu hỏi nào ([target: <symbol> | direction: …]).\n'));
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
for (const q of gt.questions) {
|
|
96
|
+
if (!q.found) {
|
|
97
|
+
console.log(` ⚠️ ${q.id} ${chalk.dim(`[${q.target}]`)} — ${chalk.yellow(q.reason)}`);
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
console.log(` ▸ ${chalk.white.bold(q.id)} ${chalk.dim(`[${q.target} · ${q.direction} · risk=${q.risk}]`)}`);
|
|
101
|
+
console.log(chalk.dim(` ${q.name}`));
|
|
102
|
+
console.log(` ${chalk.green('Đáp án (graph):')} ${q.truth.length ? q.truth.join(', ') : chalk.dim('(không có dependent)')}`);
|
|
103
|
+
}
|
|
104
|
+
console.log(chalk.dim('\n Đây là answer key máy sinh — agent/dev trả lời độc lập rồi chấm bằng: dev-quiz --answers <file>.\n'));
|
|
105
|
+
})
|
|
106
|
+
);
|
|
107
|
+
};
|
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const fs = require('fs-extra');
|
|
3
|
+
const chalk = require('chalk');
|
|
4
|
+
const crypto = require('crypto');
|
|
5
|
+
const { isInitialized, getVFlowDir, listFeatures } = require('../lib/templates');
|
|
6
|
+
const { withErrorHandler, requireInit, formatBytes } = require('../lib/cli-error');
|
|
7
|
+
const { readFlowState } = require('../lib/yaml-parser');
|
|
8
|
+
const { createT } = require('../lib/i18n');
|
|
9
|
+
|
|
10
|
+
module.exports = function (program) {
|
|
11
|
+
program
|
|
12
|
+
.command('doctor')
|
|
13
|
+
.description('Deep health check — verify V Flow installation integrity')
|
|
14
|
+
.option('--fix', 'Attempt to fix issues automatically')
|
|
15
|
+
.action(withErrorHandler(async (options) => {
|
|
16
|
+
const t = createT();
|
|
17
|
+
const projectRoot = process.cwd();
|
|
18
|
+
const sourceRoot = path.resolve(__dirname, '../..');
|
|
19
|
+
|
|
20
|
+
console.log(chalk.cyan.bold(`\n${t('doctor.title')}\n`));
|
|
21
|
+
|
|
22
|
+
const issues = [];
|
|
23
|
+
const warnings = [];
|
|
24
|
+
const ok = [];
|
|
25
|
+
|
|
26
|
+
// ════════════════════════════════════════════════════════════════════
|
|
27
|
+
// 1. Core Structure
|
|
28
|
+
// ════════════════════════════════════════════════════════════════════
|
|
29
|
+
console.log(chalk.white.bold(t('doctor.core_structure')));
|
|
30
|
+
|
|
31
|
+
const mfDir = getVFlowDir(projectRoot);
|
|
32
|
+
if (!isInitialized(projectRoot)) {
|
|
33
|
+
issues.push('V Flow is not initialized (.v-flow/ not found)');
|
|
34
|
+
console.log(chalk.red(' ❌ .v-flow/ not found — run: v-flow init'));
|
|
35
|
+
printSummary(issues, warnings, ok);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
ok.push('.v-flow/ directory exists');
|
|
39
|
+
console.log(chalk.green(' ✅ .v-flow/ directory exists'));
|
|
40
|
+
|
|
41
|
+
const requiredDirs = [
|
|
42
|
+
'features',
|
|
43
|
+
'knowledge',
|
|
44
|
+
'knowledge/architecture-decisions',
|
|
45
|
+
'knowledge/patterns',
|
|
46
|
+
'knowledge/golden-tests',
|
|
47
|
+
'knowledge/lessons-learned',
|
|
48
|
+
'incidents',
|
|
49
|
+
];
|
|
50
|
+
|
|
51
|
+
for (const dir of requiredDirs) {
|
|
52
|
+
const dirPath = path.join(mfDir, dir);
|
|
53
|
+
if (fs.existsSync(dirPath)) {
|
|
54
|
+
ok.push(`${dir}/ exists`);
|
|
55
|
+
} else {
|
|
56
|
+
warnings.push(`Missing directory: .v-flow/${dir}/`);
|
|
57
|
+
console.log(chalk.yellow(` ⚠️ Missing: .v-flow/${dir}/`));
|
|
58
|
+
if (options.fix) {
|
|
59
|
+
await fs.ensureDir(dirPath);
|
|
60
|
+
console.log(chalk.green(` → Fixed: created ${dir}/`));
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// ════════════════════════════════════════════════════════════════════
|
|
66
|
+
// 2. Core Files
|
|
67
|
+
// ════════════════════════════════════════════════════════════════════
|
|
68
|
+
console.log(chalk.white.bold(`\n${t('doctor.core_files')}`));
|
|
69
|
+
|
|
70
|
+
const coreFiles = [
|
|
71
|
+
{ file: 'constitution.md', label: 'Constitution', severity: 'error' },
|
|
72
|
+
{ file: 'agent-config.md', label: 'Agent Config', severity: 'error' },
|
|
73
|
+
{ file: 'context.md', label: 'Context (U.0)', severity: 'warning' },
|
|
74
|
+
{ file: 'knowledge/domain-glossary.md', label: 'Domain Glossary', severity: 'warning' },
|
|
75
|
+
];
|
|
76
|
+
|
|
77
|
+
for (const { file, label, severity } of coreFiles) {
|
|
78
|
+
const filePath = path.join(mfDir, file);
|
|
79
|
+
if (fs.existsSync(filePath)) {
|
|
80
|
+
const stats = fs.statSync(filePath);
|
|
81
|
+
const content = await fs.readFile(filePath, 'utf8');
|
|
82
|
+
const lines = content.split('\n').length;
|
|
83
|
+
|
|
84
|
+
// Check if file is still template (mostly placeholders)
|
|
85
|
+
const placeholderCount = (content.match(/\*(tự điền|chưa phát hiện|tự xác định|xyz)\*/g) || []).length;
|
|
86
|
+
if (placeholderCount > 3) {
|
|
87
|
+
warnings.push(`${label}: still has ${placeholderCount} unfilled placeholders`);
|
|
88
|
+
console.log(chalk.yellow(` ⚠️ ${label}: ${placeholderCount} placeholders still unfilled — needs Tech Lead review`));
|
|
89
|
+
} else {
|
|
90
|
+
ok.push(`${label} exists (${lines} lines)`);
|
|
91
|
+
console.log(chalk.green(` ✅ ${label} ${chalk.dim(`(${lines} lines, ${formatBytes(stats.size)})`)}`));
|
|
92
|
+
}
|
|
93
|
+
} else if (severity === 'error') {
|
|
94
|
+
issues.push(`Missing required file: ${file}`);
|
|
95
|
+
console.log(chalk.red(` ❌ ${label}: MISSING`));
|
|
96
|
+
} else {
|
|
97
|
+
warnings.push(`Missing optional file: ${file}`);
|
|
98
|
+
console.log(chalk.yellow(` ⚠️ ${label}: not found`));
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// ════════════════════════════════════════════════════════════════════
|
|
103
|
+
// 3. AI Agent Integration — Sync Check
|
|
104
|
+
// ════════════════════════════════════════════════════════════════════
|
|
105
|
+
console.log(chalk.white.bold(`\n${t('doctor.ai_sync')}`));
|
|
106
|
+
|
|
107
|
+
const syncChecks = [
|
|
108
|
+
{
|
|
109
|
+
label: 'Antigravity',
|
|
110
|
+
checks: [
|
|
111
|
+
{ src: null, dest: 'AGENTS.md', root: true },
|
|
112
|
+
{ src: 'commands', dest: '.agents/workflows', root: true, isDir: true },
|
|
113
|
+
{ src: 'agents', dest: '.agents/agents', root: true, isDir: true },
|
|
114
|
+
],
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
label: 'Copilot',
|
|
118
|
+
checks: [
|
|
119
|
+
{ src: null, dest: '.github/copilot-instructions.md', root: true },
|
|
120
|
+
{ src: 'commands', dest: '.github/prompts', root: true, isDir: true },
|
|
121
|
+
],
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
label: 'Cursor',
|
|
125
|
+
checks: [
|
|
126
|
+
{ src: null, dest: '.cursorrules', root: true },
|
|
127
|
+
{ src: 'commands', dest: '.cursor/rules', root: true, isDir: true },
|
|
128
|
+
],
|
|
129
|
+
},
|
|
130
|
+
];
|
|
131
|
+
|
|
132
|
+
for (const { label, checks } of syncChecks) {
|
|
133
|
+
let allGood = true;
|
|
134
|
+
for (const check of checks) {
|
|
135
|
+
const destPath = check.root
|
|
136
|
+
? path.join(projectRoot, check.dest)
|
|
137
|
+
: path.join(mfDir, check.dest);
|
|
138
|
+
|
|
139
|
+
if (!fs.existsSync(destPath)) {
|
|
140
|
+
allGood = false;
|
|
141
|
+
warnings.push(`${label}: missing ${check.dest}`);
|
|
142
|
+
} else if (check.isDir && check.src) {
|
|
143
|
+
// Count files and compare with source
|
|
144
|
+
const srcDir = path.join(sourceRoot, check.src);
|
|
145
|
+
if (fs.existsSync(srcDir)) {
|
|
146
|
+
const srcFiles = (await fs.readdir(srcDir)).filter(f => f.endsWith('.md'));
|
|
147
|
+
const destFiles = await fs.readdir(destPath);
|
|
148
|
+
const destMdFiles = destFiles.filter(f => f.endsWith('.md') || f.endsWith('.mdc'));
|
|
149
|
+
|
|
150
|
+
if (destMdFiles.length < srcFiles.length) {
|
|
151
|
+
allGood = false;
|
|
152
|
+
warnings.push(`${label} ${check.dest}: ${destMdFiles.length}/${srcFiles.length} files — needs sync`);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Check for stale files (hash mismatch)
|
|
156
|
+
let staleCount = 0;
|
|
157
|
+
for (const srcFile of srcFiles) {
|
|
158
|
+
const srcContent = await fs.readFile(path.join(srcDir, srcFile), 'utf8');
|
|
159
|
+
const destFile = check.dest.includes('.cursor')
|
|
160
|
+
? srcFile.replace(/\.md$/, '.mdc')
|
|
161
|
+
: srcFile;
|
|
162
|
+
const destFilePath = path.join(destPath, destFile);
|
|
163
|
+
|
|
164
|
+
if (fs.existsSync(destFilePath)) {
|
|
165
|
+
// Simple length check (full hash would be expensive)
|
|
166
|
+
const destContent = await fs.readFile(destFilePath, 'utf8');
|
|
167
|
+
// For cursor files, skip the added frontmatter
|
|
168
|
+
const cleanDest = destContent.replace(/^---[\s\S]*?---\n/, '');
|
|
169
|
+
const cleanSrc = srcContent.replace(/^---[\s\S]*?---\n/, '');
|
|
170
|
+
if (cleanDest.trim() !== cleanSrc.trim()) {
|
|
171
|
+
staleCount++;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (staleCount > 0) {
|
|
177
|
+
allGood = false;
|
|
178
|
+
warnings.push(`${label} ${check.dest}: ${staleCount} file(s) out of sync`);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (allGood) {
|
|
185
|
+
ok.push(`${label}: all files synced`);
|
|
186
|
+
console.log(chalk.green(` ✅ ${label}: up-to-date`));
|
|
187
|
+
} else {
|
|
188
|
+
console.log(chalk.yellow(` ⚠️ ${label}: needs sync — run: v-flow sync --apply`));
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// ════════════════════════════════════════════════════════════════════
|
|
193
|
+
// 4. Feature Health
|
|
194
|
+
// ════════════════════════════════════════════════════════════════════
|
|
195
|
+
const features = await listFeatures(projectRoot);
|
|
196
|
+
console.log(chalk.white.bold(`\n${t('doctor.feature_health', { count: features.length })}`));
|
|
197
|
+
|
|
198
|
+
if (features.length === 0) {
|
|
199
|
+
console.log(chalk.dim(' No features yet'));
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
for (const feature of features) {
|
|
203
|
+
const featureDir = path.join(mfDir, 'features', feature);
|
|
204
|
+
const featureIssues = [];
|
|
205
|
+
|
|
206
|
+
// Check .flow-state.yaml
|
|
207
|
+
const flowStatePath = path.join(featureDir, '.flow-state.yaml');
|
|
208
|
+
if (!fs.existsSync(flowStatePath)) {
|
|
209
|
+
featureIssues.push('missing .flow-state.yaml');
|
|
210
|
+
if (options.fix) {
|
|
211
|
+
// Can't auto-fix this meaningfully
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Check spec.md
|
|
216
|
+
const specPath = path.join(featureDir, 'spec.md');
|
|
217
|
+
if (!fs.existsSync(specPath)) {
|
|
218
|
+
featureIssues.push('missing spec.md');
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Check _session.md
|
|
222
|
+
const sessionPath = path.join(featureDir, '_session.md');
|
|
223
|
+
if (!fs.existsSync(sessionPath)) {
|
|
224
|
+
featureIssues.push('missing _session.md');
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Check for artifact consistency
|
|
228
|
+
// Use shared YAML parser for robust .flow-state.yaml reading
|
|
229
|
+
const flowState = await readFlowState(featureDir);
|
|
230
|
+
if (flowState) {
|
|
231
|
+
const phase = flowState.currentPhase || '';
|
|
232
|
+
|
|
233
|
+
// If phase is past B.0a, plan should exist
|
|
234
|
+
const postPlanPhases = ['B.0b', 'R.0', 'B.1', 'R.1', 'M.0', 'O.0'];
|
|
235
|
+
if (postPlanPhases.includes(phase) && !fs.existsSync(path.join(featureDir, 'plan.md'))) {
|
|
236
|
+
featureIssues.push(`phase is ${phase} but plan.md is missing`);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// If phase is past R.0, validation should exist
|
|
240
|
+
const postCheckPhases = ['B.1', 'R.1', 'M.0', 'O.0'];
|
|
241
|
+
if (postCheckPhases.includes(phase) && !fs.existsSync(path.join(featureDir, 'validation-report.md'))) {
|
|
242
|
+
featureIssues.push(`phase is ${phase} but validation-report.md is missing`);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
if (featureIssues.length === 0) {
|
|
247
|
+
ok.push(`Feature ${feature}: healthy`);
|
|
248
|
+
console.log(chalk.green(` ✅ ${feature}`));
|
|
249
|
+
} else {
|
|
250
|
+
for (const issue of featureIssues) {
|
|
251
|
+
warnings.push(`Feature ${feature}: ${issue}`);
|
|
252
|
+
}
|
|
253
|
+
console.log(chalk.yellow(` ⚠️ ${feature}: ${featureIssues.join(', ')}`));
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// ════════════════════════════════════════════════════════════════════
|
|
258
|
+
// 5. Templates Check
|
|
259
|
+
// ════════════════════════════════════════════════════════════════════
|
|
260
|
+
console.log(chalk.white.bold(`\n${t('doctor.templates')}`));
|
|
261
|
+
|
|
262
|
+
const templatesDir = path.join(mfDir, 'templates');
|
|
263
|
+
if (fs.existsSync(templatesDir)) {
|
|
264
|
+
// Count all files recursively
|
|
265
|
+
const countAllFiles = async (dir) => {
|
|
266
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
267
|
+
let count = 0;
|
|
268
|
+
for (const e of entries) {
|
|
269
|
+
if (e.isFile()) count++;
|
|
270
|
+
else if (e.isDirectory()) count += await countAllFiles(path.join(dir, e.name));
|
|
271
|
+
}
|
|
272
|
+
return count;
|
|
273
|
+
};
|
|
274
|
+
const totalTemplates = await countAllFiles(templatesDir);
|
|
275
|
+
const hasModuleCard = fs.existsSync(path.join(templatesDir, 'module-card'));
|
|
276
|
+
const hasKnowledge = fs.existsSync(path.join(templatesDir, 'knowledge'));
|
|
277
|
+
ok.push(`Templates: ${totalTemplates} files`);
|
|
278
|
+
console.log(chalk.green(` ✅ .v-flow/templates/ — ${totalTemplates} files`));
|
|
279
|
+
console.log(chalk.green(` ✅ module-card/ ${hasModuleCard ? 'present' : chalk.red('MISSING')}`));
|
|
280
|
+
console.log(chalk.green(` ✅ knowledge/ ${hasKnowledge ? 'present' : chalk.red('MISSING')}`));
|
|
281
|
+
} else {
|
|
282
|
+
warnings.push('Templates directory missing');
|
|
283
|
+
console.log(chalk.yellow(' ⚠️ .v-flow/templates/ missing — run: v-flow sync --apply'));
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// ════════════════════════════════════════════════════════════════════
|
|
287
|
+
// SUMMARY
|
|
288
|
+
// ════════════════════════════════════════════════════════════════════
|
|
289
|
+
printSummary(issues, warnings, ok, t);
|
|
290
|
+
}));
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
// formatBytes is now imported from cli-error.js
|
|
294
|
+
|
|
295
|
+
function printSummary(issues, warnings, ok, t) {
|
|
296
|
+
console.log(chalk.cyan.bold(`\n${t('doctor.diagnosis')}\n`));
|
|
297
|
+
|
|
298
|
+
const total = issues.length + warnings.length + ok.length;
|
|
299
|
+
const healthScore = total > 0 ? Math.round((ok.length / total) * 100) : 0;
|
|
300
|
+
|
|
301
|
+
// Health bar
|
|
302
|
+
const barLength = 30;
|
|
303
|
+
const filledLength = Math.round((healthScore / 100) * barLength);
|
|
304
|
+
const bar = '█'.repeat(filledLength) + '░'.repeat(barLength - filledLength);
|
|
305
|
+
const barColor = healthScore >= 80 ? chalk.green
|
|
306
|
+
: healthScore >= 50 ? chalk.yellow
|
|
307
|
+
: chalk.red;
|
|
308
|
+
|
|
309
|
+
console.log(` ${t('doctor.health')}: ${barColor(bar)} ${healthScore}%\n`);
|
|
310
|
+
|
|
311
|
+
if (issues.length > 0) {
|
|
312
|
+
console.log(chalk.red(` ${t('doctor.errors', { count: issues.length })}`));
|
|
313
|
+
for (const issue of issues) {
|
|
314
|
+
console.log(chalk.red(` • ${issue}`));
|
|
315
|
+
}
|
|
316
|
+
console.log('');
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
if (warnings.length > 0) {
|
|
320
|
+
console.log(chalk.yellow(` ${t('doctor.warnings', { count: warnings.length })}`));
|
|
321
|
+
for (const warning of warnings) {
|
|
322
|
+
console.log(chalk.yellow(` • ${warning}`));
|
|
323
|
+
}
|
|
324
|
+
console.log('');
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
console.log(chalk.green(` ${t('doctor.passed', { count: ok.length })}`));
|
|
328
|
+
|
|
329
|
+
// Recommendations
|
|
330
|
+
if (issues.length > 0 || warnings.length > 0) {
|
|
331
|
+
console.log(chalk.white.bold(`\n ${t('doctor.recommendations')}`));
|
|
332
|
+
if (issues.some(i => i.includes('not initialized'))) {
|
|
333
|
+
console.log(chalk.dim(` ${t('doctor.run_init')}`));
|
|
334
|
+
}
|
|
335
|
+
if (warnings.some(w => w.includes('needs sync') || w.includes('out of sync'))) {
|
|
336
|
+
console.log(chalk.dim(` ${t('doctor.run_sync')}`));
|
|
337
|
+
}
|
|
338
|
+
if (warnings.some(w => w.includes('placeholders'))) {
|
|
339
|
+
console.log(chalk.dim(` ${t('doctor.review_context')}`));
|
|
340
|
+
}
|
|
341
|
+
if (warnings.some(w => w.includes('.flow-state.yaml'))) {
|
|
342
|
+
console.log(chalk.dim(` ${t('doctor.run_feature')}`));
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
console.log('');
|
|
347
|
+
}
|
|
348
|
+
|