@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,199 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const chalk = require('chalk');
|
|
3
|
+
const { withErrorHandler, ValidationError } = require('../lib/cli-error');
|
|
4
|
+
const activityLog = require('../lib/activity-log');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* `v-flow log` — append a structured activity entry, or view recent ones.
|
|
8
|
+
*
|
|
9
|
+
* Used by the agentic workflow (see AGENTS.md "Activity Logging") to record
|
|
10
|
+
* phase boundaries and key decisions into the same .v-flow/logs/ files the CLI
|
|
11
|
+
* writes to, giving one unified audit trail of "using v-flow".
|
|
12
|
+
*/
|
|
13
|
+
module.exports = function (program) {
|
|
14
|
+
program
|
|
15
|
+
.command('log')
|
|
16
|
+
.description('Record a structured activity entry (agents) or view recent ones (--tail)')
|
|
17
|
+
.argument('[message...]', 'Free-form log message')
|
|
18
|
+
.option('--type <type>', 'Entry type (agent, note, ...)', 'agent')
|
|
19
|
+
.option('--phase <phase>', 'V-Flow phase, e.g. S.1, B.1')
|
|
20
|
+
.option('--feature <feature>', 'Feature name')
|
|
21
|
+
.option('--actor <actor>', 'Who/what produced this, e.g. ba-agent')
|
|
22
|
+
.option('--event <event>', 'Event kind: start | end | decision | error | info')
|
|
23
|
+
.option('--level <level>', 'Severity: info | warn | error', 'info')
|
|
24
|
+
.option('--data <json>', 'Extra structured fields as a JSON object')
|
|
25
|
+
.option('--tail [n]', 'Show the last N entries instead of writing (default 20)')
|
|
26
|
+
.option(
|
|
27
|
+
'--hook <provider>',
|
|
28
|
+
'Run as a lifecycle hook (claude|cursor|copilot|codex|antigravity): read the hook payload from stdin and log it'
|
|
29
|
+
)
|
|
30
|
+
.action(
|
|
31
|
+
withErrorHandler((message, options) => {
|
|
32
|
+
// ── Hook mode ── (invoked by an AI tool's lifecycle hook; reads stdin)
|
|
33
|
+
if (options.hook) {
|
|
34
|
+
runHookMode(options.hook, options);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// ── Read mode ──
|
|
39
|
+
if (options.tail !== undefined) {
|
|
40
|
+
const n = options.tail === true ? 20 : parseInt(options.tail, 10) || 20;
|
|
41
|
+
printTail(n);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// ── Write mode ──
|
|
46
|
+
let extra = {};
|
|
47
|
+
if (options.data) {
|
|
48
|
+
try {
|
|
49
|
+
extra = JSON.parse(options.data);
|
|
50
|
+
} catch (_) {
|
|
51
|
+
throw new ValidationError('--data must be a valid JSON object', 'Example: --data \'{"req":"REQ-1"}\'');
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const msg = (message || []).join(' ').trim();
|
|
56
|
+
const entry = {
|
|
57
|
+
type: options.type,
|
|
58
|
+
level: options.level,
|
|
59
|
+
...(options.event ? { event: options.event } : {}),
|
|
60
|
+
...(options.phase ? { phase: options.phase } : {}),
|
|
61
|
+
...(options.feature ? { feature: options.feature } : {}),
|
|
62
|
+
...(options.actor ? { actor: options.actor } : {}),
|
|
63
|
+
...(msg ? { message: msg } : {}),
|
|
64
|
+
...extra,
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
if (!msg && !options.event && Object.keys(extra).length === 0) {
|
|
68
|
+
throw new ValidationError(
|
|
69
|
+
'Nothing to log',
|
|
70
|
+
'Provide a message, --event, or --data. Use --tail to view entries.'
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
activityLog.event(entry, humanSuffix(entry));
|
|
75
|
+
|
|
76
|
+
const dir = activityLog.resolveLogDir();
|
|
77
|
+
if (dir) {
|
|
78
|
+
console.log(chalk.dim(`📝 logged → ${dir}/activity.jsonl`));
|
|
79
|
+
} else {
|
|
80
|
+
console.log(chalk.yellow('⚠️ No .v-flow/ here — entry not persisted (run from a v-flow project or set VFLOW_LOG_DIR)'));
|
|
81
|
+
}
|
|
82
|
+
})
|
|
83
|
+
);
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
function humanSuffix(e) {
|
|
87
|
+
const scope = [e.phase, e.feature].filter(Boolean).join('/');
|
|
88
|
+
const actor = e.actor ? ` (${e.actor})` : '';
|
|
89
|
+
const head = `[${e.type}]${scope ? ` ${scope}` : ''}${e.event ? ` ${e.event}` : ''}${actor}`;
|
|
90
|
+
return e.message ? `${head}: ${e.message}` : head;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function printTail(n) {
|
|
94
|
+
const entries = activityLog.tail(n);
|
|
95
|
+
if (entries.length === 0) {
|
|
96
|
+
console.log(chalk.dim('No activity log entries yet.'));
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
console.log(chalk.cyan.bold(`\n📜 Last ${entries.length} activity entries\n`));
|
|
100
|
+
for (const e of entries) {
|
|
101
|
+
if (e.raw) {
|
|
102
|
+
console.log(chalk.dim(e.raw));
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
const ts = chalk.dim(e.ts || '');
|
|
106
|
+
if (e.type === 'cli') {
|
|
107
|
+
const ok = e.exitCode === 0 ? chalk.green('✓') : chalk.red(`✗ ${e.exitCode}`);
|
|
108
|
+
console.log(`${ts} ${ok} ${chalk.white(`v-flow ${(e.invocation || []).join(' ')}`)} ${chalk.dim(`(${e.durationMs}ms)`)}`);
|
|
109
|
+
} else if (e.type === 'hook') {
|
|
110
|
+
console.log(`${ts} ${chalk.magenta(`[hook:${e.provider}]`)} ${chalk.white([e.event, e.tool].filter(Boolean).join(' '))}${e.command ? chalk.dim(` — ${e.command}`) : ''}`);
|
|
111
|
+
} else {
|
|
112
|
+
console.log(`${ts} ${chalk.white(humanSuffix(e))}`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
console.log('');
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// ── Hook mode helpers ──────────────────────────────────────────────────────
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Read the hook payload from stdin, normalize it across providers, and log it.
|
|
122
|
+
* MUST be defensive: never throw, always exit 0 — a logging hook must not block
|
|
123
|
+
* or alter the host agent's behavior.
|
|
124
|
+
*/
|
|
125
|
+
function runHookMode(provider, options) {
|
|
126
|
+
let raw = '';
|
|
127
|
+
try {
|
|
128
|
+
if (!process.stdin.isTTY) {
|
|
129
|
+
raw = fs.readFileSync(0, 'utf8');
|
|
130
|
+
}
|
|
131
|
+
} catch (_) {
|
|
132
|
+
/* no stdin available */
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
let payload = null;
|
|
136
|
+
try {
|
|
137
|
+
payload = raw ? JSON.parse(raw) : null;
|
|
138
|
+
} catch (_) {
|
|
139
|
+
payload = { _unparsed: raw.slice(0, 500) };
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
try {
|
|
143
|
+
const { fields, human } = normalizeHookPayload(provider, payload, options);
|
|
144
|
+
activityLog.event({ type: 'hook', provider, level: 'info', ...fields }, human);
|
|
145
|
+
} catch (_) {
|
|
146
|
+
/* logging must never break the host agent */
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Antigravity requires a top-level JSON allow response; others accept empty
|
|
150
|
+
// stdout + exit 0 as "continue".
|
|
151
|
+
if (provider === 'antigravity') {
|
|
152
|
+
process.stdout.write(JSON.stringify({ allow_tool: true }));
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function pickField(obj, ...keys) {
|
|
157
|
+
for (const k of keys) {
|
|
158
|
+
if (obj && obj[k] !== undefined && obj[k] !== null) return obj[k];
|
|
159
|
+
}
|
|
160
|
+
return undefined;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function normalizeHookPayload(provider, payload, options) {
|
|
164
|
+
const p = payload || {};
|
|
165
|
+
const toolCallArgs = (p.toolCall && p.toolCall.args) || {};
|
|
166
|
+
|
|
167
|
+
const event = options.event || pickField(p, 'hook_event_name', 'hookEventName');
|
|
168
|
+
const tool = pickField(p, 'tool_name', 'toolName') || toolCallArgs.ToolName;
|
|
169
|
+
const sessionId = pickField(p, 'session_id', 'sessionId', 'conversation_id', 'generation_id');
|
|
170
|
+
const cwd = pickField(p, 'cwd');
|
|
171
|
+
const command = toolCallArgs.CommandLine || (p.tool_input && p.tool_input.command);
|
|
172
|
+
const status = pickField(p, 'status', 'stop_hook_active');
|
|
173
|
+
|
|
174
|
+
const fields = {
|
|
175
|
+
...(event ? { event } : {}),
|
|
176
|
+
...(tool ? { tool } : {}),
|
|
177
|
+
...(sessionId ? { sessionId } : {}),
|
|
178
|
+
...(cwd ? { cwd } : {}),
|
|
179
|
+
...(command ? { command } : {}),
|
|
180
|
+
...(status !== undefined ? { status } : {}),
|
|
181
|
+
...(options.feature ? { feature: options.feature } : {}),
|
|
182
|
+
payload: capPayload(p),
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
const scope = [event, tool].filter(Boolean).join(' ');
|
|
186
|
+
const human = `[hook:${provider}] ${scope || 'event'}${command ? ` — ${command}` : ''}`;
|
|
187
|
+
return { fields, human };
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/** Keep the log lean: drop huge tool outputs, keep a size marker instead. */
|
|
191
|
+
function capPayload(p) {
|
|
192
|
+
try {
|
|
193
|
+
const s = JSON.stringify(p);
|
|
194
|
+
if (s.length > 4000) return { _truncated: true, _bytes: s.length };
|
|
195
|
+
return p;
|
|
196
|
+
} catch (_) {
|
|
197
|
+
return undefined;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* v-flow plugin — Plugin management commands
|
|
3
|
+
*
|
|
4
|
+
* Subcommands:
|
|
5
|
+
* v-flow plugin list — List discovered plugins
|
|
6
|
+
* v-flow plugin create <name> — Scaffold a new plugin
|
|
7
|
+
* v-flow plugin install — Install all plugin assets to agent targets
|
|
8
|
+
* v-flow plugin validate — Validate plugin manifests
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const path = require('path');
|
|
12
|
+
const chalk = require('chalk');
|
|
13
|
+
const { isInitialized, getVFlowDir } = require('../lib/templates');
|
|
14
|
+
const { withErrorHandler, requireInit, printOk, printWarn, printError, printDim } = require('../lib/cli-error');
|
|
15
|
+
const { createT } = require('../lib/i18n');
|
|
16
|
+
const {
|
|
17
|
+
discoverPlugins,
|
|
18
|
+
validateManifest,
|
|
19
|
+
loadPlugin,
|
|
20
|
+
installPlugin,
|
|
21
|
+
scaffoldPlugin,
|
|
22
|
+
VALID_TYPES,
|
|
23
|
+
} = require('../lib/plugins');
|
|
24
|
+
|
|
25
|
+
module.exports = function (program) {
|
|
26
|
+
const pluginCmd = program
|
|
27
|
+
.command('plugin')
|
|
28
|
+
.description('🔌 Plugin management — create, list, install, validate');
|
|
29
|
+
|
|
30
|
+
// ════════════════════════════════════════════════════════════
|
|
31
|
+
// v-flow plugin list
|
|
32
|
+
// ════════════════════════════════════════════════════════════
|
|
33
|
+
pluginCmd
|
|
34
|
+
.command('list')
|
|
35
|
+
.description('List discovered plugins')
|
|
36
|
+
.action(withErrorHandler(async () => {
|
|
37
|
+
const t = createT();
|
|
38
|
+
const projectRoot = process.cwd();
|
|
39
|
+
requireInit(projectRoot, isInitialized);
|
|
40
|
+
|
|
41
|
+
console.log(chalk.cyan.bold(`\n${t('plugin.title')}\n`));
|
|
42
|
+
|
|
43
|
+
const plugins = await discoverPlugins(projectRoot);
|
|
44
|
+
|
|
45
|
+
if (plugins.length === 0) {
|
|
46
|
+
console.log(chalk.dim(` ${t('plugin.no_plugins')}`));
|
|
47
|
+
console.log(chalk.dim(` ${t('plugin.create_hint')}\n`));
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
console.log(chalk.white.bold(` Found ${plugins.length} plugin(s):\n`));
|
|
52
|
+
|
|
53
|
+
for (const plugin of plugins) {
|
|
54
|
+
const typeColor = {
|
|
55
|
+
agent: chalk.blue,
|
|
56
|
+
workflow: chalk.green,
|
|
57
|
+
skill: chalk.yellow,
|
|
58
|
+
composite: chalk.magenta,
|
|
59
|
+
}[plugin.type] || chalk.white;
|
|
60
|
+
|
|
61
|
+
console.log(` 📦 ${chalk.bold(plugin.name)} ${chalk.dim(`v${plugin.version}`)} ${typeColor(`[${plugin.type}]`)}`);
|
|
62
|
+
if (plugin.description) {
|
|
63
|
+
console.log(chalk.dim(` ${plugin.description}`));
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
console.log('');
|
|
67
|
+
}));
|
|
68
|
+
|
|
69
|
+
// ════════════════════════════════════════════════════════════
|
|
70
|
+
// v-flow plugin create <name>
|
|
71
|
+
// ════════════════════════════════════════════════════════════
|
|
72
|
+
pluginCmd
|
|
73
|
+
.command('create')
|
|
74
|
+
.description('Scaffold a new plugin')
|
|
75
|
+
.argument('<name>', 'Plugin name (kebab-case)')
|
|
76
|
+
.option('--type <type>', `Plugin type: ${VALID_TYPES.join(', ')}`, 'composite')
|
|
77
|
+
.action(withErrorHandler(async (name, options) => {
|
|
78
|
+
const t = createT();
|
|
79
|
+
const projectRoot = process.cwd();
|
|
80
|
+
requireInit(projectRoot, isInitialized);
|
|
81
|
+
|
|
82
|
+
if (!VALID_TYPES.includes(options.type)) {
|
|
83
|
+
throw new (require('../lib/cli-error').ValidationError)(
|
|
84
|
+
`Invalid plugin type: ${options.type}`,
|
|
85
|
+
`Valid types: ${VALID_TYPES.join(', ')}`
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
console.log(chalk.cyan.bold(`\n${t('plugin.create_title')}\n`));
|
|
90
|
+
|
|
91
|
+
const pluginDir = await scaffoldPlugin(projectRoot, name, options.type);
|
|
92
|
+
|
|
93
|
+
printOk(`Plugin created: .v-flow/plugins/${name}/`);
|
|
94
|
+
printOk(`Type: ${options.type}`);
|
|
95
|
+
console.log('');
|
|
96
|
+
console.log(chalk.white.bold(' 📁 Created files:'));
|
|
97
|
+
printDim(`plugin.json — manifest`);
|
|
98
|
+
|
|
99
|
+
if (options.type === 'agent' || options.type === 'composite') {
|
|
100
|
+
printDim(`agent.md — agent definition`);
|
|
101
|
+
}
|
|
102
|
+
if (options.type === 'workflow' || options.type === 'composite') {
|
|
103
|
+
printDim(`workflow.md — slash command`);
|
|
104
|
+
}
|
|
105
|
+
if (options.type === 'skill' || options.type === 'composite') {
|
|
106
|
+
printDim(`skill/SKILL.md — skill definition`);
|
|
107
|
+
printDim(`skill/config.json — skill config`);
|
|
108
|
+
printDim(`skill/gotchas.md — common gotchas`);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
console.log('');
|
|
112
|
+
console.log(chalk.white.bold(' 💡 Next steps:'));
|
|
113
|
+
printDim(`1. Edit files in .v-flow/plugins/${name}/`);
|
|
114
|
+
printDim(`2. Run: v-flow plugin install`);
|
|
115
|
+
console.log('');
|
|
116
|
+
}));
|
|
117
|
+
|
|
118
|
+
// ════════════════════════════════════════════════════════════
|
|
119
|
+
// v-flow plugin install
|
|
120
|
+
// ════════════════════════════════════════════════════════════
|
|
121
|
+
pluginCmd
|
|
122
|
+
.command('install')
|
|
123
|
+
.description('Install all plugin assets to agent targets')
|
|
124
|
+
.option('--dry-run', 'Preview without making changes')
|
|
125
|
+
.action(withErrorHandler(async (options) => {
|
|
126
|
+
const t = createT();
|
|
127
|
+
const projectRoot = process.cwd();
|
|
128
|
+
requireInit(projectRoot, isInitialized);
|
|
129
|
+
|
|
130
|
+
console.log(chalk.cyan.bold(`\n${t('plugin.install_title')}\n`));
|
|
131
|
+
|
|
132
|
+
const plugins = await discoverPlugins(projectRoot);
|
|
133
|
+
|
|
134
|
+
if (plugins.length === 0) {
|
|
135
|
+
console.log(chalk.dim(' No plugins to install.\n'));
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
let totalInstalled = 0;
|
|
140
|
+
let totalSkipped = 0;
|
|
141
|
+
|
|
142
|
+
for (const plugin of plugins) {
|
|
143
|
+
console.log(chalk.white.bold(` 📦 ${plugin.name} v${plugin.version}:`));
|
|
144
|
+
|
|
145
|
+
const loaded = await loadPlugin(plugin);
|
|
146
|
+
const stats = await installPlugin(projectRoot, loaded, {
|
|
147
|
+
dryRun: options.dryRun,
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
if (loaded.agents.length > 0) {
|
|
151
|
+
printOk(`${loaded.agents.length} agent(s) → .agents/agents/ & .github/agents/`);
|
|
152
|
+
}
|
|
153
|
+
if (loaded.workflows.length > 0) {
|
|
154
|
+
printOk(`${loaded.workflows.length} workflow(s) → .agents/workflows/ & .github/prompts/`);
|
|
155
|
+
}
|
|
156
|
+
if (loaded.skills.length > 0) {
|
|
157
|
+
printOk(`${loaded.skills.length} skill(s) → .agents/skills/plugins/ & .github/skills/plugins/`);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
totalInstalled += stats.installed;
|
|
161
|
+
totalSkipped += stats.skipped;
|
|
162
|
+
console.log('');
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (options.dryRun) {
|
|
166
|
+
console.log(chalk.yellow(` 🔍 Dry run — ${totalInstalled} asset(s) would be installed\n`));
|
|
167
|
+
} else {
|
|
168
|
+
console.log(chalk.green(` ✅ Installed ${totalInstalled} asset(s) from ${plugins.length} plugin(s)\n`));
|
|
169
|
+
}
|
|
170
|
+
}));
|
|
171
|
+
|
|
172
|
+
// ════════════════════════════════════════════════════════════
|
|
173
|
+
// v-flow plugin validate
|
|
174
|
+
// ════════════════════════════════════════════════════════════
|
|
175
|
+
pluginCmd
|
|
176
|
+
.command('validate')
|
|
177
|
+
.description('Validate all plugin manifests')
|
|
178
|
+
.action(withErrorHandler(async () => {
|
|
179
|
+
const t = createT();
|
|
180
|
+
const projectRoot = process.cwd();
|
|
181
|
+
requireInit(projectRoot, isInitialized);
|
|
182
|
+
|
|
183
|
+
console.log(chalk.cyan.bold(`\n${t('plugin.validate_title')}\n`));
|
|
184
|
+
|
|
185
|
+
const fs = require('fs-extra');
|
|
186
|
+
const pluginsDir = path.join(projectRoot, '.v-flow', 'plugins');
|
|
187
|
+
if (!fs.existsSync(pluginsDir)) {
|
|
188
|
+
console.log(chalk.dim(' No plugins directory found.\n'));
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const entries = await fs.readdir(pluginsDir, { withFileTypes: true });
|
|
193
|
+
let valid = 0;
|
|
194
|
+
let invalid = 0;
|
|
195
|
+
|
|
196
|
+
for (const entry of entries) {
|
|
197
|
+
if (!entry.isDirectory() || entry.name.startsWith('.')) continue;
|
|
198
|
+
|
|
199
|
+
const manifestPath = path.join(pluginsDir, entry.name, 'plugin.json');
|
|
200
|
+
if (!fs.existsSync(manifestPath)) {
|
|
201
|
+
printError(`${entry.name}: missing plugin.json`);
|
|
202
|
+
invalid++;
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
try {
|
|
207
|
+
const manifest = await fs.readJson(manifestPath);
|
|
208
|
+
const result = validateManifest(manifest);
|
|
209
|
+
|
|
210
|
+
if (result.valid) {
|
|
211
|
+
printOk(`${manifest.name} v${manifest.version} [${manifest.type}]`);
|
|
212
|
+
valid++;
|
|
213
|
+
} else {
|
|
214
|
+
printError(`${entry.name}: ${result.errors.join(', ')}`);
|
|
215
|
+
invalid++;
|
|
216
|
+
}
|
|
217
|
+
} catch (err) {
|
|
218
|
+
printError(`${entry.name}: invalid JSON — ${err.message}`);
|
|
219
|
+
invalid++;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
console.log('');
|
|
224
|
+
if (invalid === 0) {
|
|
225
|
+
console.log(chalk.green(` ✅ All ${valid} plugin(s) valid\n`));
|
|
226
|
+
} else {
|
|
227
|
+
console.log(chalk.yellow(` ${valid} valid, ${invalid} invalid\n`));
|
|
228
|
+
}
|
|
229
|
+
}));
|
|
230
|
+
};
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* v-flow score-card — Objective scoring for module cards (Decision 3).
|
|
3
|
+
*
|
|
4
|
+
* Emits a re-runnable scorecard: the objective half (6 pts) is computed from
|
|
5
|
+
* machine-checkable facts via a KnowledgeOracle, with an evidence trail. The
|
|
6
|
+
* subjective half (business/dev correctness, depth — 4 pts) needs human + AI
|
|
7
|
+
* graders and is intentionally NOT produced here.
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* v-flow score-card [module] # one card, or all if omitted
|
|
11
|
+
* v-flow score-card orders --json # machine-readable scorecard.json
|
|
12
|
+
* v-flow score-card --oracle none # declared-only mode (no verification)
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const path = require('path');
|
|
16
|
+
const fs = require('fs-extra');
|
|
17
|
+
const chalk = require('chalk');
|
|
18
|
+
const { isInitialized, getVFlowDir } = require('../lib/templates');
|
|
19
|
+
const { withErrorHandler, requireInit, printWarn } = require('../lib/cli-error');
|
|
20
|
+
const { getOracle } = require('../lib/knowledge-oracle');
|
|
21
|
+
const { scoreModuleCard } = require('../lib/module-card-score');
|
|
22
|
+
|
|
23
|
+
const DIM_LABEL = {
|
|
24
|
+
structural: 'Structural completeness',
|
|
25
|
+
grounded: 'Evidence-grounded',
|
|
26
|
+
freshness: 'Freshness / no-drift',
|
|
27
|
+
coverage: 'Coverage',
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
function evidenceLine(dim) {
|
|
31
|
+
const e = dim.evidence || {};
|
|
32
|
+
switch (dim.key) {
|
|
33
|
+
case 'structural':
|
|
34
|
+
return `${e.issues} issue · ${e.warnings} warning`;
|
|
35
|
+
case 'grounded':
|
|
36
|
+
return dim.verified
|
|
37
|
+
? `${e.refs_resolved}/${e.refs_total} ref resolve${ e.broken && e.broken.length ? ` · gãy: ${e.broken.length}` : ''}`
|
|
38
|
+
: e.reason;
|
|
39
|
+
case 'freshness':
|
|
40
|
+
return dim.verified
|
|
41
|
+
? `${e.files_checked} file kiểm · drift: ${e.drifted ? e.drifted.length : 0}${ e.last_verified ? ` (từ ${e.last_verified})` : ''}`
|
|
42
|
+
: e.reason;
|
|
43
|
+
case 'coverage':
|
|
44
|
+
return dim.verified ? `${e.symbols_mentioned}/${e.symbols_total} symbol` : e.reason;
|
|
45
|
+
default:
|
|
46
|
+
return '';
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Turn a scorecard into concrete remediation steps — the "what do I do now"
|
|
51
|
+
// the bare numbers don't answer. Only emits for dimensions below their max;
|
|
52
|
+
// each line names the dimension, its score, and the exact fix.
|
|
53
|
+
function nextSteps(sc) {
|
|
54
|
+
const steps = [];
|
|
55
|
+
for (const dim of sc.dimensions) {
|
|
56
|
+
if (dim.verified && dim.score >= dim.max) continue; // chiều này đã trọn điểm
|
|
57
|
+
const e = dim.evidence || {};
|
|
58
|
+
const at = `(${dim.score}/${dim.max})`;
|
|
59
|
+
switch (dim.key) {
|
|
60
|
+
case 'structural':
|
|
61
|
+
if (e.issues > 0 || e.warnings > 0) {
|
|
62
|
+
steps.push(`Cấu trúc ${at}: chạy \`v-flow validate --module-cards ${sc.module}\` để xem ${e.issues} issue + ${e.warnings} warning cụ thể, rồi bổ sung file/heading còn thiếu.`);
|
|
63
|
+
}
|
|
64
|
+
break;
|
|
65
|
+
case 'grounded':
|
|
66
|
+
if (e.broken && e.broken.length) {
|
|
67
|
+
steps.push(`Bằng chứng ${at}: sửa ${e.broken.length} ref code gãy — vd: ${e.broken[0]}`);
|
|
68
|
+
} else if (!dim.verified && e.refs_total === 0) {
|
|
69
|
+
steps.push(`Bằng chứng ${at}: card chưa neo về code — thêm refs dạng Source: \`đường-dẫn/file.ext:dòng\`, hàm \`tên\` vào card.`);
|
|
70
|
+
} else if (!dim.verified) {
|
|
71
|
+
steps.push(`Bằng chứng ${at}: ${e.reason || 'oracle chưa kiểm được'}.`);
|
|
72
|
+
}
|
|
73
|
+
break;
|
|
74
|
+
case 'freshness':
|
|
75
|
+
if (e.drifted && e.drifted.length) {
|
|
76
|
+
steps.push(`Độ mới ${at}: ${e.drifted.length} file đã đổi sau ${e.last_verified} — review & cập nhật card rồi bump **Last verified** (vd: ${e.drifted[0]}).`);
|
|
77
|
+
} else if (!dim.verified && /Last verified/i.test(e.reason || '')) {
|
|
78
|
+
steps.push(`Độ mới ${at}: thêm \`**Last verified**: YYYY-MM-DD\` vào SKILL.md để bật kiểm drift.`);
|
|
79
|
+
} else if (!dim.verified) {
|
|
80
|
+
steps.push(`Độ mới ${at}: ${e.reason || 'chưa kiểm được'}.`);
|
|
81
|
+
}
|
|
82
|
+
break;
|
|
83
|
+
case 'coverage':
|
|
84
|
+
if (!dim.verified) {
|
|
85
|
+
const r = e.reason || '';
|
|
86
|
+
if (/cypher error/i.test(r)) {
|
|
87
|
+
steps.push(`Coverage ${at}: "cypher error" là do GitNexus index/CLI (KHÔNG phải lỗi card). Chạy \`npx gitnexus analyze\` cho repo rồi thử lại; nếu vẫn lỗi, tạm bỏ qua chiều này.`);
|
|
88
|
+
} else if (/full-graph/i.test(r)) {
|
|
89
|
+
steps.push(`Coverage ${at}: cần full-graph — chạy lại với \`--oracle gitnexus\` sau khi \`npx gitnexus analyze\`.`);
|
|
90
|
+
} else if (/no scope|code refs/i.test(r)) {
|
|
91
|
+
steps.push(`Coverage ${at}: card chưa có ref code nên không xác định được phạm vi — thêm \`Source:\` refs trước (xem mục Bằng chứng).`);
|
|
92
|
+
} else {
|
|
93
|
+
steps.push(`Coverage ${at}: ${r || 'chưa kiểm được'}.`);
|
|
94
|
+
}
|
|
95
|
+
} else if (dim.score < dim.max) {
|
|
96
|
+
steps.push(`Coverage ${at}: card mới nhắc ${e.symbols_mentioned}/${e.symbols_total} symbol — bổ sung các symbol quan trọng còn thiếu.`);
|
|
97
|
+
}
|
|
98
|
+
break;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return steps;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function printNextSteps(sc) {
|
|
105
|
+
const steps = nextSteps(sc);
|
|
106
|
+
if (steps.length === 0) {
|
|
107
|
+
console.log(chalk.green(' 👉 Đã trọn điểm objective — chuyển sang chấm subjective (business/dev/depth) với human + AI.'));
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
console.log(chalk.white.bold(' 👉 Cần làm gì tiếp:'));
|
|
111
|
+
steps.forEach((s, i) => console.log(chalk.dim(` ${i + 1}. `) + chalk.white(s)));
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function printScorecard(sc) {
|
|
115
|
+
const stamp = sc.stamp ? chalk.yellow(sc.stamp) : '';
|
|
116
|
+
console.log(chalk.white.bold(`\n 📦 ${sc.module}`));
|
|
117
|
+
console.log(
|
|
118
|
+
chalk.cyan.bold(` Objective: ${sc.objective_total}/${sc.objective_max}${stamp}`) +
|
|
119
|
+
chalk.dim(` (oracle: ${sc.oracle}${sc.fully_verified ? '' : ' — có chiều chưa kiểm chứng'})`)
|
|
120
|
+
);
|
|
121
|
+
for (const dim of sc.dimensions) {
|
|
122
|
+
const icon = dim.verified ? '✅' : '⚠️ ';
|
|
123
|
+
const label = DIM_LABEL[dim.key] || dim.key;
|
|
124
|
+
const verifiedTag = dim.verified ? '' : chalk.yellow(' [unverified]');
|
|
125
|
+
console.log(
|
|
126
|
+
` ${icon} ${chalk.white(label.padEnd(24))} ${chalk.bold(`${dim.score}/${dim.max}`)}${verifiedTag} ${
|
|
127
|
+
chalk.dim(`— ${evidenceLine(dim)}`)}`
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
module.exports = function (program) {
|
|
133
|
+
program
|
|
134
|
+
.command('score-card [module]')
|
|
135
|
+
.description('Chấm điểm objective cho module card (.v-flow/modules/) — provable, có evidence trail')
|
|
136
|
+
.option('--json', 'Xuất scorecard dạng JSON (machine-readable)')
|
|
137
|
+
.option('--oracle <mode>', 'Oracle: lightweight | gitnexus (full-graph) | none (declared-only)', 'lightweight')
|
|
138
|
+
.option('--repo <name>', 'Tên repo GitNexus (khi --oracle gitnexus, nếu cần chỉ định)')
|
|
139
|
+
.action(
|
|
140
|
+
withErrorHandler(async (moduleName, options) => {
|
|
141
|
+
const projectRoot = process.cwd();
|
|
142
|
+
requireInit(projectRoot, isInitialized);
|
|
143
|
+
|
|
144
|
+
if (!['lightweight', 'gitnexus', 'none'].includes(options.oracle)) {
|
|
145
|
+
printWarn(`--oracle "${options.oracle}" không hợp lệ → dùng "lightweight"`);
|
|
146
|
+
options.oracle = 'lightweight';
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const modulesDir = path.join(getVFlowDir(projectRoot), 'modules');
|
|
150
|
+
const allCards = fs.existsSync(modulesDir)
|
|
151
|
+
? (await fs.readdir(modulesDir, { withFileTypes: true }))
|
|
152
|
+
.filter((d) => d.isDirectory())
|
|
153
|
+
.map((d) => d.name)
|
|
154
|
+
.sort()
|
|
155
|
+
: [];
|
|
156
|
+
|
|
157
|
+
if (allCards.length === 0) {
|
|
158
|
+
if (options.json) {
|
|
159
|
+
console.log('[]');
|
|
160
|
+
} else {
|
|
161
|
+
console.log(chalk.dim('\n Chưa có module card nào (.v-flow/modules/) — chạy /v.understand trước.\n'));
|
|
162
|
+
}
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const cards = moduleName
|
|
167
|
+
? allCards.filter((c) => c === moduleName || c === `domain-${moduleName}`)
|
|
168
|
+
: allCards;
|
|
169
|
+
|
|
170
|
+
if (cards.length === 0) {
|
|
171
|
+
printWarn(`Không tìm thấy module card "${moduleName}". Có: ${allCards.join(', ')}`);
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const oracle = getOracle(options.oracle, projectRoot, { repo: options.repo });
|
|
176
|
+
const results = cards.map((card) => {
|
|
177
|
+
const cardPath = path.join(modulesDir, card);
|
|
178
|
+
const readFile = (f) => {
|
|
179
|
+
const p = path.join(cardPath, f);
|
|
180
|
+
return fs.existsSync(p) ? fs.readFileSync(p, 'utf8') : null;
|
|
181
|
+
};
|
|
182
|
+
return scoreModuleCard(card, readFile, oracle);
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
if (options.json) {
|
|
186
|
+
console.log(JSON.stringify(results.length === 1 ? results[0] : results, null, 2));
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
console.log(chalk.cyan.bold('\n🏅 V Flow — Module Card Scorecard (objective tier)'));
|
|
191
|
+
for (const sc of results) {
|
|
192
|
+
printScorecard(sc);
|
|
193
|
+
printNextSteps(sc);
|
|
194
|
+
}
|
|
195
|
+
console.log(
|
|
196
|
+
chalk.dim(
|
|
197
|
+
'\n Chú thích: * = thiếu full-graph (coverage chưa kiểm) · † = declared-only (chưa máy nào kiểm).' +
|
|
198
|
+
'\n Objective = chứng minh được; phần subjective (business/dev/depth, 4đ) do human + AI độc lập chấm.\n'
|
|
199
|
+
)
|
|
200
|
+
);
|
|
201
|
+
})
|
|
202
|
+
);
|
|
203
|
+
};
|