trackfw 1.1.0 → 2.1.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "trackfw",
3
- "version": "1.1.0",
3
+ "version": "2.1.0",
4
4
  "description": "Governed software delivery framework: ADR → REQ → ROADMAP → kanban",
5
5
  "keywords": [
6
6
  "cli",
@@ -25,7 +25,7 @@ cmd.command('new <title>')
25
25
  cmd.command('list')
26
26
  .description(t('adr.list.description'))
27
27
  .action(async () => {
28
- await generators.listADRs('docs/adr')
28
+ await generators.listADRs(require('../config').load().adrDirs[0])
29
29
  })
30
30
 
31
31
  module.exports = cmd
@@ -0,0 +1,189 @@
1
+ 'use strict'
2
+
3
+ const { Command } = require('commander')
4
+ const fs = require('fs')
5
+ const path = require('path')
6
+ const config = require('../config')
7
+ const { validate } = require('../validator')
8
+
9
+ /**
10
+ * extractFrontmatterField — extrai valor de campo YAML dentro de bloco --- ... ---.
11
+ * Retorna string vazia se não encontrado ou valor '""'.
12
+ * @param {string} content
13
+ * @param {string} field
14
+ * @returns {string}
15
+ */
16
+ function extractFrontmatterField(content, field) {
17
+ const lines = content.split('\n')
18
+ let started = false
19
+ let inFrontmatter = false
20
+ for (const line of lines) {
21
+ const trimmed = line.trim()
22
+ if (trimmed === '---') {
23
+ if (!started) {
24
+ started = true
25
+ inFrontmatter = true
26
+ continue
27
+ }
28
+ break // segundo --- fecha o bloco
29
+ }
30
+ if (!inFrontmatter) break
31
+ const key = field + ':'
32
+ if (trimmed.startsWith(key)) {
33
+ let val = trimmed.slice(key.length).trim()
34
+ val = val.replace(/^["']|["']$/g, '') // remover aspas
35
+ return val
36
+ }
37
+ }
38
+ return ''
39
+ }
40
+
41
+ /**
42
+ * extractInlineStatus — extrai status da linha "| Status: ..." do markdown.
43
+ * @param {string} content
44
+ * @returns {string}
45
+ */
46
+ function extractInlineStatus(content) {
47
+ for (const line of content.split('\n')) {
48
+ const idx = line.indexOf('| Status: ')
49
+ if (idx >= 0) {
50
+ let rest = line.slice(idx + '| Status: '.length)
51
+ const pipeIdx = rest.indexOf(' |')
52
+ if (pipeIdx >= 0) rest = rest.slice(0, pipeIdx)
53
+ rest = rest.replace(/[\s>|]+$/, '').trim()
54
+ return rest || 'unknown'
55
+ }
56
+ }
57
+ return 'unknown'
58
+ }
59
+
60
+ /**
61
+ * collectEntries — lê diretório e retorna lista de entradas com type, file, status, state.
62
+ * @param {string} dir
63
+ * @param {string} type - 'ADR' | 'REQ' | 'ROADMAP'
64
+ * @param {string} [state] - estado kanban (somente ROADMAP)
65
+ * @returns {Array<{type: string, file: string, status: string, state?: string}>}
66
+ */
67
+ function collectEntries(dir, type, state) {
68
+ const entries = []
69
+ let files = []
70
+ try {
71
+ files = fs.readdirSync(dir).filter(f => f.endsWith('.md') && !fs.statSync(path.join(dir, f)).isDirectory())
72
+ } catch (_) {
73
+ return entries
74
+ }
75
+ for (const file of files) {
76
+ let content = ''
77
+ try { content = fs.readFileSync(path.join(dir, file), 'utf8') } catch (_) {}
78
+ let status = extractFrontmatterField(content, 'status')
79
+ if (!status) status = extractInlineStatus(content)
80
+ if (!status) status = state || 'unknown'
81
+ const entry = { type, file, status }
82
+ if (state) entry.state = state
83
+ entries.push(entry)
84
+ }
85
+ return entries
86
+ }
87
+
88
+ /**
89
+ * getContext — coleta governança e imprime em md ou json.
90
+ * @param {string} format - 'md' | 'json'
91
+ */
92
+ function getContext(format) {
93
+ const cfg = config.load()
94
+
95
+ // ADRs
96
+ const adrs = []
97
+ for (const adrDir of (cfg.adrDirs || ['docs/adr'])) {
98
+ adrs.push(...collectEntries(adrDir, 'ADR'))
99
+ }
100
+
101
+ // REQs
102
+ const reqs = collectEntries(cfg.reqDir || 'docs/req', 'REQ')
103
+
104
+ // Roadmaps
105
+ const roadmaps = []
106
+ const states = ['wip', 'backlog', 'blocked', 'done', 'abandoned']
107
+ if (cfg.roadmapNamespacing === 'by_agent') {
108
+ let agents = cfg.agents || []
109
+ if (agents.length === 0) {
110
+ try {
111
+ agents = fs.readdirSync(cfg.roadmapDir).filter(f => {
112
+ try { return fs.statSync(path.join(cfg.roadmapDir, f)).isDirectory() } catch (_) { return false }
113
+ })
114
+ } catch (_) { agents = [] }
115
+ }
116
+ for (const agent of agents) {
117
+ for (const state of states) {
118
+ const dir = path.join(cfg.roadmapDir, agent, state)
119
+ roadmaps.push(...collectEntries(dir, 'ROADMAP', state))
120
+ }
121
+ }
122
+ } else {
123
+ for (const state of states) {
124
+ const dir = path.join(cfg.roadmapDir, state)
125
+ roadmaps.push(...collectEntries(dir, 'ROADMAP', state))
126
+ }
127
+ }
128
+
129
+ // Validate
130
+ const { violations, warnings } = validate()
131
+
132
+ // Score
133
+ let score = 0
134
+ if (adrs.length > 0) score += 20
135
+ if (reqs.length > 0) score += 20
136
+ if (roadmaps.length > 0) score += 20
137
+ if (violations.length === 0) score += 40
138
+
139
+ if (format === 'json') {
140
+ console.log(JSON.stringify({ score, violations, warnings, adrs, reqs, roadmaps }, null, 2))
141
+ return
142
+ }
143
+
144
+ // Markdown
145
+ console.log('# trackfw governance context\n')
146
+ console.log(`**Governance score:** ${score}/100\n`)
147
+
148
+ console.log(`## ADRs (${adrs.length})`)
149
+ if (adrs.length === 0) {
150
+ console.log('- (none)')
151
+ } else {
152
+ for (const a of adrs) console.log(`- ${a.file} [${a.status}]`)
153
+ }
154
+
155
+ console.log(`\n## REQs (${reqs.length})`)
156
+ if (reqs.length === 0) {
157
+ console.log('- (none)')
158
+ } else {
159
+ for (const r of reqs) console.log(`- ${r.file} [${r.status}]`)
160
+ }
161
+
162
+ console.log(`\n## Roadmaps (${roadmaps.length})`)
163
+ if (roadmaps.length === 0) {
164
+ console.log('- (none)')
165
+ } else {
166
+ for (const r of roadmaps) console.log(`- ${r.file} [${r.state}]`)
167
+ }
168
+
169
+ if (violations.length > 0) {
170
+ console.log(`\n## Violations (${violations.length})`)
171
+ for (const v of violations) console.log(`- ${v}`)
172
+ }
173
+
174
+ if (warnings.length > 0) {
175
+ console.log(`\n## Warnings (${warnings.length})`)
176
+ for (const w of warnings) console.log(`- ${w}`)
177
+ }
178
+ }
179
+
180
+ module.exports = (function () {
181
+ const cmd = new Command('context')
182
+ cmd
183
+ .description('Print governance context for LLM consumption')
184
+ .option('--format <fmt>', 'Output format: md or json', 'md')
185
+ .action((opts) => {
186
+ getContext(opts.format)
187
+ })
188
+ return cmd
189
+ })()
@@ -0,0 +1,359 @@
1
+ 'use strict';
2
+
3
+ const { Command } = require('commander');
4
+ const fs = require('fs');
5
+ const path = require('path');
6
+
7
+ function scan(rootDir) {
8
+ const r = {
9
+ adrDirs: [],
10
+ reqDir: '',
11
+ roadmapDir: '',
12
+ roadmapNamespacing: 'flat',
13
+ agents: [],
14
+ adrCount: 0,
15
+ reqCount: 0,
16
+ roadmapCount: 0,
17
+ hasTrackfwYAML: false,
18
+ hasTrackfwLog: false,
19
+ governanceScore: 0,
20
+ hookFramework: 'none',
21
+ ciSystem: 'none',
22
+ };
23
+
24
+ // trackfw.yaml
25
+ r.hasTrackfwYAML = fs.existsSync(path.join(rootDir, 'trackfw.yaml'));
26
+
27
+ // REQ dir
28
+ for (const candidate of ['docs/req', 'docs/requisições', 'docs/requirements', 'docs/reqs']) {
29
+ const full = path.join(rootDir, candidate);
30
+ if (isDir(full)) {
31
+ r.reqDir = candidate;
32
+ r.reqCount = countMD(full);
33
+ break;
34
+ }
35
+ }
36
+
37
+ // ADR dirs
38
+ const adrRoot = path.join(rootDir, 'docs', 'adr');
39
+ if (isDir(adrRoot)) {
40
+ const subDirs = listSubDirs(adrRoot);
41
+ if (subDirs.length > 0) {
42
+ for (const sub of subDirs) {
43
+ const rel = 'docs/adr/' + sub;
44
+ r.adrDirs.push(rel);
45
+ r.adrCount += countMD(path.join(rootDir, rel));
46
+ }
47
+ } else {
48
+ r.adrDirs = ['docs/adr'];
49
+ r.adrCount = countMD(adrRoot);
50
+ }
51
+ }
52
+
53
+ // Roadmap dir e namespacing
54
+ const roadmapRoot = path.join(rootDir, 'docs', 'roadmaps');
55
+ if (isDir(roadmapRoot)) {
56
+ r.roadmapDir = 'docs/roadmaps';
57
+ const agentDirs = listSubDirs(roadmapRoot);
58
+ let byAgent = false;
59
+ const agents = [];
60
+ for (const sub of agentDirs) {
61
+ const wipDir = path.join(roadmapRoot, sub, 'wip');
62
+ const backlogDir = path.join(roadmapRoot, sub, 'backlog');
63
+ const doneDir = path.join(roadmapRoot, sub, 'done');
64
+ const abandonedDir = path.join(roadmapRoot, sub, 'abandoned');
65
+ const blockedDir = path.join(roadmapRoot, sub, 'blocked');
66
+ if (isDir(wipDir) || isDir(backlogDir) || isDir(doneDir) || isDir(abandonedDir) || isDir(blockedDir)) {
67
+ byAgent = true;
68
+ agents.push(sub);
69
+ }
70
+ }
71
+ if (byAgent) {
72
+ r.roadmapNamespacing = 'by_agent';
73
+ r.agents = agents;
74
+ for (const agent of agents) {
75
+ for (const state of ['backlog', 'wip', 'blocked', 'done', 'abandoned']) {
76
+ r.roadmapCount += countMD(path.join(roadmapRoot, agent, state));
77
+ }
78
+ }
79
+ } else {
80
+ r.roadmapNamespacing = 'flat';
81
+ for (const state of ['backlog', 'wip', 'blocked', 'done', 'abandoned']) {
82
+ r.roadmapCount += countMD(path.join(roadmapRoot, state));
83
+ }
84
+ }
85
+
86
+ r.hasTrackfwLog = fs.existsSync(path.join(roadmapRoot, '.trackfw-log'));
87
+ }
88
+
89
+ // Hook framework
90
+ if (isFile(path.join(rootDir, 'lefthook.yml')) || isFile(path.join(rootDir, '.lefthook.yml'))) {
91
+ r.hookFramework = 'lefthook';
92
+ } else if (isDir(path.join(rootDir, '.husky'))) {
93
+ r.hookFramework = 'husky';
94
+ } else if (isFile(path.join(rootDir, '.pre-commit-config.yaml'))) {
95
+ r.hookFramework = 'pre-commit';
96
+ } else {
97
+ r.hookFramework = 'none';
98
+ }
99
+
100
+ // CI
101
+ if (isDir(path.join(rootDir, '.github', 'workflows'))) {
102
+ r.ciSystem = 'github-actions';
103
+ } else if (isFile(path.join(rootDir, '.gitlab-ci.yml'))) {
104
+ r.ciSystem = 'gitlab';
105
+ } else {
106
+ r.ciSystem = 'none';
107
+ }
108
+
109
+ r.governanceScore = calcScore(r);
110
+ return r;
111
+ }
112
+
113
+ function calcScore(r) {
114
+ let score = 0;
115
+ if (r.adrCount > 0) score += 20;
116
+ if (r.reqCount > 0) score += 20;
117
+ if (r.roadmapCount > 0) score += 20;
118
+ if (r.hasTrackfwYAML) score += 20;
119
+ if (r.hasTrackfwLog) score += 20;
120
+ return score;
121
+ }
122
+
123
+ function generateYAML(r) {
124
+ let out = '# trackfw configuration — gerado por trackfw discover\n';
125
+ out += '# governance_mode: lenient permite validação não-bloqueante durante onboarding\n\n';
126
+ out += 'governance_mode: lenient\n\n';
127
+
128
+ if (r.adrDirs.length > 0) {
129
+ out += 'adr_dirs:\n';
130
+ r.adrDirs.forEach(d => { out += ` - ${d}\n`; });
131
+ } else {
132
+ out += 'adr_dirs:\n - docs/adr\n';
133
+ }
134
+
135
+ out += `req_dir: ${r.reqDir || 'docs/req'}\n`;
136
+ out += `roadmap_dir: ${r.roadmapDir || 'docs/roadmaps'}\n`;
137
+ out += `roadmap_namespacing: ${r.roadmapNamespacing}\n`;
138
+
139
+ if (r.agents.length > 0) {
140
+ out += 'agents:\n';
141
+ r.agents.forEach(a => { out += ` - ${a}\n`; });
142
+ }
143
+
144
+ out += `hooks: ${r.hookFramework}\n`;
145
+ out += `ci: ${r.ciSystem}\n`;
146
+
147
+ return out;
148
+ }
149
+
150
+ function generateBootstrapLog(r, rootDir) {
151
+ let out = '';
152
+ const roadmapRoot = path.join(rootDir, r.roadmapDir);
153
+
154
+ const appendEntries = (dir, agent) => {
155
+ if (!isDir(dir)) return;
156
+ for (const entry of fs.readdirSync(dir)) {
157
+ if (!entry.endsWith('.md')) continue;
158
+ const filePath = path.join(dir, entry);
159
+ const stat = fs.statSync(filePath);
160
+ const ts = stat.mtime.toISOString().slice(0, 16).replace('T', ' ');
161
+ const basename = agent ? agent + '/' + entry : entry;
162
+ out += `${ts} ${basename.padEnd(50)} backlog → done\n`;
163
+ }
164
+ };
165
+
166
+ if (r.roadmapNamespacing === 'by_agent') {
167
+ for (const agent of r.agents) {
168
+ appendEntries(path.join(roadmapRoot, agent, 'done'), agent);
169
+ }
170
+ } else {
171
+ appendEntries(path.join(roadmapRoot, 'done'), '');
172
+ }
173
+
174
+ return out;
175
+ }
176
+
177
+ // installGates instala artefatos de governança: validate script, hook entry, CI workflow.
178
+ function installGates(r, rootDir) {
179
+ writeValidateScript(rootDir);
180
+ installHook(r.hookFramework, rootDir);
181
+ if (r.ciSystem === 'github-actions') {
182
+ writeCIWorkflow(rootDir);
183
+ }
184
+ }
185
+
186
+ function writeValidateScript(rootDir) {
187
+ const scriptsDir = path.join(rootDir, 'scripts');
188
+ if (!isDir(scriptsDir)) fs.mkdirSync(scriptsDir, { recursive: true });
189
+ const content = '#!/usr/bin/env bash\nset -euo pipefail\ntrackfw validate\n';
190
+ const dest = path.join(scriptsDir, 'trackfw-validate.sh');
191
+ fs.writeFileSync(dest, content, { mode: 0o755 });
192
+ }
193
+
194
+ function installHook(framework, rootDir) {
195
+ const hookEntry = '\npre-commit:\n commands:\n trackfw-validate:\n run: scripts/trackfw-validate.sh\n';
196
+ const huskyEntry = '\nscripts/trackfw-validate.sh\n';
197
+
198
+ if (framework === 'lefthook') {
199
+ let cfgPath = path.join(rootDir, 'lefthook.yml');
200
+ if (!isFile(cfgPath)) cfgPath = path.join(rootDir, '.lefthook.yml');
201
+ const content = fs.readFileSync(cfgPath, 'utf8');
202
+ if (content.includes('trackfw')) return; // idempotente
203
+ fs.appendFileSync(cfgPath, hookEntry, 'utf8');
204
+ } else if (framework === 'husky') {
205
+ const huskyHook = path.join(rootDir, '.husky', 'pre-commit');
206
+ fs.appendFileSync(huskyHook, huskyEntry, 'utf8');
207
+ } else {
208
+ console.log('⚠ No hook framework detected — skipping hook installation');
209
+ }
210
+ }
211
+
212
+ function writeCIWorkflow(rootDir) {
213
+ const workflowsDir = path.join(rootDir, '.github', 'workflows');
214
+ if (!isDir(workflowsDir)) fs.mkdirSync(workflowsDir, { recursive: true });
215
+ const dest = path.join(workflowsDir, 'trackfw-validate.yml');
216
+ if (isFile(dest)) return; // idempotente
217
+ const content = `name: trackfw validate
218
+ on: [push, pull_request]
219
+ jobs:
220
+ governance:
221
+ runs-on: ubuntu-latest
222
+ steps:
223
+ - uses: actions/checkout@v4
224
+ - uses: actions/setup-go@v5
225
+ with:
226
+ go-version: "1.22"
227
+ - run: go install github.com/kgsaran/trackfw/cmd/trackfw@latest
228
+ - run: trackfw validate
229
+ `;
230
+ fs.writeFileSync(dest, content, 'utf8');
231
+ }
232
+
233
+ // helpers
234
+ function isDir(p) {
235
+ try { return fs.statSync(p).isDirectory(); } catch { return false; }
236
+ }
237
+
238
+ function isFile(p) {
239
+ try { return fs.statSync(p).isFile(); } catch { return false; }
240
+ }
241
+
242
+ function countMD(dir) {
243
+ let n = 0;
244
+ function walk(d) {
245
+ let entries;
246
+ try { entries = fs.readdirSync(d, { withFileTypes: true }); } catch { return; }
247
+ for (const e of entries) {
248
+ if (e.isDirectory()) walk(path.join(d, e.name));
249
+ else if (e.name.endsWith('.md')) n++;
250
+ }
251
+ }
252
+ walk(dir);
253
+ return n;
254
+ }
255
+
256
+ function listSubDirs(dir) {
257
+ try {
258
+ return fs.readdirSync(dir).filter(f => {
259
+ try { return fs.statSync(path.join(dir, f)).isDirectory(); } catch { return false; }
260
+ });
261
+ } catch { return []; }
262
+ }
263
+
264
+ const cmd = new Command('discover');
265
+ cmd.description('Scan the repository and auto-detect the governance structure');
266
+ cmd.option('--init', 'generate trackfw.yaml calibrated for this project');
267
+ cmd.option('--bootstrap-log', 'create retroactive .trackfw-log from done/ files');
268
+ cmd.action((opts) => {
269
+ const cwd = process.cwd();
270
+ console.log(`trackfw discover — scanning ${cwd}\n`);
271
+
272
+ const r = scan(cwd);
273
+
274
+ // ADR dirs
275
+ if (r.adrCount > 0) {
276
+ const dirs = r.adrDirs.join(', ');
277
+ console.log(`✓ ADRs found: ${String(r.adrCount).padEnd(4)} (${dirs})`);
278
+ } else {
279
+ console.log('⚠ No ADRs found');
280
+ }
281
+
282
+ // REQ dir
283
+ if (r.reqCount > 0) {
284
+ console.log(`✓ REQs found: ${String(r.reqCount).padEnd(4)} (${r.reqDir})`);
285
+ } else {
286
+ console.log('⚠ No REQs found');
287
+ }
288
+
289
+ // Roadmaps
290
+ if (r.roadmapCount > 0) {
291
+ const mode = r.roadmapNamespacing === 'by_agent' ? 'by_agent mode' : r.roadmapNamespacing;
292
+ console.log(`✓ Roadmaps found: ${String(r.roadmapCount).padEnd(4)} (${r.roadmapDir} — ${mode})`);
293
+ } else {
294
+ console.log('⚠ No roadmaps found');
295
+ }
296
+
297
+ // Agents
298
+ if (r.agents.length > 0) {
299
+ console.log(`✓ Agents detected: ${r.agents.join(', ')}`);
300
+ }
301
+
302
+ // trackfw.yaml
303
+ if (!r.hasTrackfwYAML) {
304
+ console.log('⚠ No trackfw.yaml — run with --init to generate one');
305
+ } else {
306
+ console.log('✓ trackfw.yaml found');
307
+ }
308
+
309
+ // .trackfw-log
310
+ if (!r.hasTrackfwLog) {
311
+ console.log('⚠ No .trackfw-log — run with --bootstrap-log to create retroactive history');
312
+ } else {
313
+ console.log('✓ .trackfw-log found');
314
+ }
315
+
316
+ // hooks
317
+ if (r.hookFramework !== 'none') {
318
+ console.log(`✓ Hooks: ${r.hookFramework}`);
319
+ } else {
320
+ console.log('⚠ No hook framework detected');
321
+ }
322
+
323
+ // CI
324
+ if (r.ciSystem !== 'none') {
325
+ console.log(`✓ CI: ${r.ciSystem}`);
326
+ } else {
327
+ console.log('⚠ No CI system detected');
328
+ }
329
+
330
+ console.log(`\nGovernance Score: ${r.governanceScore}/100`);
331
+
332
+ if (opts.init) {
333
+ const yaml = generateYAML(r);
334
+ fs.writeFileSync('trackfw.yaml', yaml, 'utf8');
335
+ console.log('\n✓ trackfw.yaml generated');
336
+ try {
337
+ installGates(r, cwd);
338
+ console.log('✓ governance gates installed');
339
+ } catch (e) {
340
+ console.log(`⚠ gates install partial: ${e.message}`);
341
+ }
342
+ }
343
+
344
+ if (opts.bootstrapLog) {
345
+ if (!r.roadmapDir) {
346
+ console.error('⚠ No roadmap dir detected — cannot bootstrap log');
347
+ return;
348
+ }
349
+ const logContent = generateBootstrapLog(r, cwd);
350
+ const logPath = r.roadmapDir + '/.trackfw-log';
351
+ fs.appendFileSync(logPath, logContent, 'utf8');
352
+ console.log(`✓ bootstrap log written to ${logPath}`);
353
+ }
354
+ });
355
+
356
+ module.exports = cmd;
357
+ module.exports.scan = scan;
358
+ module.exports.generateYAML = generateYAML;
359
+ module.exports.generateBootstrapLog = generateBootstrapLog;
@@ -18,6 +18,10 @@ function createProgram() {
18
18
  program.addCommand(require('./status'))
19
19
  program.addCommand(require('./log'))
20
20
  program.addCommand(require('./plugins'))
21
+ program.addCommand(require('./discover'))
22
+ program.addCommand(require('./metrics'))
23
+ program.addCommand(require('./sync'))
24
+ program.addCommand(require('./context'))
21
25
 
22
26
  // plugin dispatch — comandos desconhecidos tentam executar plugin
23
27
  program.hook('preSubcommand', () => {})
@@ -26,7 +26,7 @@ cmd.action(async () => {
26
26
 
27
27
  const { input, select, checkbox } = require('@inquirer/prompts')
28
28
 
29
- let projectName, projectType, frontend, pkgManager, backend, backendFramework, hooks, ci, aiTools
29
+ let projectName, projectType, frontend, pkgManager, backend, backendFramework, hooks, ci, aiTools, requireReqInCommit
30
30
 
31
31
  try {
32
32
  projectName = await input({
@@ -127,6 +127,15 @@ cmd.action(async () => {
127
127
  ],
128
128
  })
129
129
 
130
+ requireReqInCommit = false
131
+ if (hooks !== 'none') {
132
+ const { confirm: confirmPrompt } = require('@inquirer/prompts')
133
+ requireReqInCommit = await confirmPrompt({
134
+ message: t('init.prompt.require_req_in_commit'),
135
+ default: false,
136
+ })
137
+ }
138
+
130
139
  aiTools = await checkbox({
131
140
  message: t('init.prompt.aiTools'),
132
141
  choices: [
@@ -154,7 +163,7 @@ cmd.action(async () => {
154
163
  return
155
164
  }
156
165
 
157
- const cfg = { projectName, projectType, frontend, backend, backendFramework, pkgManager, hooks, ci }
166
+ const cfg = { projectName, projectType, frontend, backend, backendFramework, pkgManager, hooks, ci, requireReqInCommit }
158
167
  await generators.scaffold(cfg)
159
168
 
160
169
  for (const tool of (aiTools || [])) {