trackfw 1.0.4 → 2.0.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/README.md CHANGED
@@ -79,12 +79,14 @@ brew install kgsaran/tap/trackfw
79
79
  go install github.com/kgsaran/trackfw/cmd/trackfw@latest
80
80
  ```
81
81
 
82
- ### npm
82
+ ### npm (pure Node.js — no binary)
83
83
 
84
84
  ```bash
85
85
  npm install -g trackfw
86
86
  ```
87
87
 
88
+ The npm package is pure Node.js — no compiled binary, no postinstall download. Works everywhere Node.js ≥ 18 is installed, including corporate Windows environments where unsigned `.exe` files are blocked by antivirus.
89
+
88
90
  ### pip
89
91
 
90
92
  ```bash
@@ -127,18 +129,25 @@ trackfw status
127
129
  | `trackfw req new "title"` | Create a REQ with guided ADR discovery |
128
130
  | `trackfw req list` | List all REQs with status |
129
131
  | `trackfw roadmap new "title"` | Create a roadmap in `backlog/` |
132
+ | `trackfw roadmap show <name>` | Print a roadmap with its current state |
130
133
  | `trackfw roadmap move <name> <state>` | Move roadmap between states |
131
134
  | `trackfw roadmap list` | List all roadmaps grouped by state |
132
135
  | `trackfw validate` | Check governance consistency (use as CI gate) |
133
136
  | `trackfw status` | Show wip, blocked, REQs waiting on ADRs |
134
- | `trackfw agents` | Install Claude Code subagents |
135
- | `trackfw gemini` | Install Gemini CLI skills and commands |
136
- | `trackfw cursor` | Install Cursor rules |
137
- | `trackfw copilot` | Install GitHub Copilot instructions |
138
- | `trackfw windsurf` | Install Windsurf rules and workflows |
139
- | `trackfw amazonq` | Install Amazon Q Developer rules |
137
+ | `trackfw log [--tail N]` | Show roadmap state transition history |
138
+ | `trackfw plugins list` | List installed plugins |
139
+ | `trackfw plugins add <user/repo>` | Install a plugin from GitHub Releases |
140
+ | `trackfw plugins remove <name>` | Remove an installed plugin |
141
+ | `trackfw agents` | Install Claude Code subagents *(Go binary only)* |
142
+ | `trackfw gemini` | Install Gemini CLI skills and commands *(Go binary only)* |
143
+ | `trackfw cursor` | Install Cursor rules *(Go binary only)* |
144
+ | `trackfw copilot` | Install GitHub Copilot instructions *(Go binary only)* |
145
+ | `trackfw windsurf` | Install Windsurf rules and workflows *(Go binary only)* |
146
+ | `trackfw amazonq` | Install Amazon Q Developer rules *(Go binary only)* |
140
147
  | `trackfw version` | Print version |
141
148
 
149
+ > **Go binary only** commands (`agents`, `gemini`, `cursor`, `copilot`, `windsurf`, `amazonq`) are available when installed via brew, `install.sh`, or `go install`. When using the npm package, AI integrations are installed through `trackfw init`.
150
+
142
151
  ---
143
152
 
144
153
  ## Governance chain
@@ -227,7 +236,7 @@ $ trackfw status
227
236
 
228
237
  ## AI assistant integration
229
238
 
230
- `trackfw init` asks which AI tools your team uses and installs native governance context for each. Commands can also be run independently.
239
+ `trackfw init` asks which AI tools your team uses and installs native governance context for each. When using the Go binary (brew, `install.sh`, `go install`), each integration can also be run as a standalone command.
231
240
 
232
241
  | Command | Installs | Format |
233
242
  |---|---|---|
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "trackfw",
3
- "version": "1.0.4",
3
+ "version": "2.0.0",
4
4
  "description": "Governed software delivery framework: ADR → REQ → ROADMAP → kanban",
5
5
  "keywords": [
6
6
  "cli",
@@ -3,28 +3,29 @@
3
3
  const { Command } = require('commander')
4
4
  const { input } = require('@inquirer/prompts')
5
5
  const generators = require('../generators/adr')
6
+ const { t } = require('../i18n')
6
7
 
7
8
  const cmd = new Command('adr')
8
- cmd.description('Manage Architecture Decision Records')
9
+ cmd.description(t('adr.description'))
9
10
 
10
11
  cmd.command('new <title>')
11
- .description('Create a new ADR')
12
+ .description(t('adr.new.description'))
12
13
  .action(async (title) => {
13
14
  const content = { title }
14
15
  // wizard interativo se TTY
15
16
  if (process.stdin.isTTY) {
16
- content.context = await input({ message: 'Context (what motivates this decision)?', default: '' })
17
- content.decision = await input({ message: 'Decision (what was decided)?', default: '' })
18
- content.consequences = await input({ message: 'Consequences (positive and negative)?', default: '' })
19
- content.alternatives = await input({ message: 'Alternatives considered?', default: '' })
17
+ content.context = await input({ message: t('adr.new.prompt.context'), default: '' })
18
+ content.decision = await input({ message: t('adr.new.prompt.decision'), default: '' })
19
+ content.consequences = await input({ message: t('adr.new.prompt.consequences'), default: '' })
20
+ content.alternatives = await input({ message: t('adr.new.prompt.alternatives'), default: '' })
20
21
  }
21
22
  await generators.newADR(content)
22
23
  })
23
24
 
24
25
  cmd.command('list')
25
- .description('List all ADRs in docs/adr/')
26
+ .description(t('adr.list.description'))
26
27
  .action(async () => {
27
- await generators.listADRs('docs/adr')
28
+ await generators.listADRs(require('../config').load().adrDirs[0])
28
29
  })
29
30
 
30
31
  module.exports = cmd
@@ -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,7 @@ 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'))
21
22
 
22
23
  // plugin dispatch — comandos desconhecidos tentam executar plugin
23
24
  program.hook('preSubcommand', () => {})
@@ -1,8 +1,9 @@
1
1
  'use strict'
2
2
  const { Command } = require('commander')
3
+ const { t } = require('../i18n')
3
4
 
4
5
  const cmd = new Command('init')
5
- cmd.description('Initialize trackfw governance in the current project')
6
+ cmd.description(t('init.description'))
6
7
  cmd.action(async () => {
7
8
  const path = require('path')
8
9
  const generators = require('../generators/init')
@@ -19,27 +20,27 @@ cmd.action(async () => {
19
20
  ci: 'none',
20
21
  }
21
22
  await generators.scaffold(cfg)
22
- console.log("\n✓ trackfw initialized — run 'trackfw status' to see your governance state.")
23
+ console.log(`\n${t('init.success')}`)
23
24
  return
24
25
  }
25
26
 
26
27
  const { input, select, checkbox } = require('@inquirer/prompts')
27
28
 
28
- let projectName, projectType, frontend, pkgManager, backend, hooks, ci, aiTools
29
+ let projectName, projectType, frontend, pkgManager, backend, backendFramework, hooks, ci, aiTools
29
30
 
30
31
  try {
31
32
  projectName = await input({
32
- message: 'Project name?',
33
+ message: t('init.prompt.projectName'),
33
34
  default: path.basename(process.cwd()),
34
35
  })
35
36
 
36
37
  projectType = await select({
37
- message: 'Project type?',
38
+ message: t('init.prompt.projectType'),
38
39
  choices: [
39
- { name: 'Full-stack (frontend + backend)', value: 'fullstack' },
40
- { name: 'Frontend only', value: 'frontend' },
41
- { name: 'Backend only', value: 'backend' },
42
- { name: 'Governance only (no build stack)', value: 'governance' },
40
+ { name: t('init.prompt.projectType_fullstack'), value: 'fullstack' },
41
+ { name: t('init.prompt.projectType_frontend'), value: 'frontend' },
42
+ { name: t('init.prompt.projectType_backend'), value: 'backend' },
43
+ { name: t('init.prompt.projectType_governance'), value: 'governance' },
43
44
  ],
44
45
  })
45
46
 
@@ -47,7 +48,7 @@ cmd.action(async () => {
47
48
  pkgManager = ''
48
49
  if (projectType === 'fullstack' || projectType === 'frontend') {
49
50
  frontend = await select({
50
- message: 'Frontend stack?',
51
+ message: t('init.prompt.frontendStack'),
51
52
  choices: [
52
53
  { name: 'React / Next.js', value: 'react' },
53
54
  { name: 'Vue', value: 'vue' },
@@ -55,7 +56,7 @@ cmd.action(async () => {
55
56
  ],
56
57
  })
57
58
  pkgManager = await select({
58
- message: 'Package manager?',
59
+ message: t('init.prompt.pkgManager'),
59
60
  choices: [
60
61
  { name: 'npm', value: 'npm' },
61
62
  { name: 'pnpm', value: 'pnpm' },
@@ -66,20 +67,50 @@ cmd.action(async () => {
66
67
  }
67
68
 
68
69
  backend = ''
70
+ let backendFramework = ''
69
71
  if (projectType === 'fullstack' || projectType === 'backend') {
70
72
  backend = await select({
71
- message: 'Backend stack?',
73
+ message: t('init.prompt.backendLang'),
72
74
  choices: [
73
75
  { name: 'Go', value: 'go' },
74
- { name: 'Java / Spring Boot', value: 'java' },
76
+ { name: 'Java', value: 'java' },
75
77
  { name: 'Node.js', value: 'node' },
76
78
  { name: 'Python', value: 'python' },
77
79
  ],
78
80
  })
81
+
82
+ const frameworkChoices = {
83
+ go: [
84
+ { name: 'Gin', value: 'gin' },
85
+ { name: 'Echo', value: 'echo' },
86
+ { name: 'Fiber', value: 'fiber' },
87
+ { name: 'Standard library (net/http)', value: 'stdlib' },
88
+ ],
89
+ java: [
90
+ { name: 'Spring Boot', value: 'spring-boot' },
91
+ { name: 'Quarkus', value: 'quarkus' },
92
+ { name: 'Micronaut', value: 'micronaut' },
93
+ ],
94
+ node: [
95
+ { name: 'Express', value: 'express' },
96
+ { name: 'Fastify', value: 'fastify' },
97
+ { name: 'NestJS', value: 'nestjs' },
98
+ { name: 'Koa', value: 'koa' },
99
+ ],
100
+ python: [
101
+ { name: 'FastAPI', value: 'fastapi' },
102
+ { name: 'Django', value: 'django' },
103
+ { name: 'Flask', value: 'flask' },
104
+ ],
105
+ }
106
+ backendFramework = await select({
107
+ message: t('init.prompt.backendFramework'),
108
+ choices: frameworkChoices[backend] || [],
109
+ })
79
110
  }
80
111
 
81
112
  hooks = await select({
82
- message: 'Git hooks?',
113
+ message: t('init.prompt.gitHooks'),
83
114
  choices: [
84
115
  { name: 'husky', value: 'husky' },
85
116
  { name: 'lefthook', value: 'lefthook' },
@@ -88,7 +119,7 @@ cmd.action(async () => {
88
119
  })
89
120
 
90
121
  ci = await select({
91
- message: 'CI system?',
122
+ message: t('init.prompt.ci'),
92
123
  choices: [
93
124
  { name: 'GitHub Actions', value: 'github-actions' },
94
125
  { name: 'GitLab CI', value: 'gitlab-ci' },
@@ -97,7 +128,7 @@ cmd.action(async () => {
97
128
  })
98
129
 
99
130
  aiTools = await checkbox({
100
- message: 'Which AI assistants do you use?',
131
+ message: t('init.prompt.aiTools'),
101
132
  choices: [
102
133
  { name: 'Claude Code', value: 'claude' },
103
134
  { name: 'Gemini CLI', value: 'gemini' },
@@ -119,11 +150,11 @@ cmd.action(async () => {
119
150
  ci: 'none',
120
151
  }
121
152
  await generators.scaffold(cfg)
122
- console.log("\n✓ trackfw initialized — run 'trackfw status' to see your governance state.")
153
+ console.log(`\n${t('init.success')}`)
123
154
  return
124
155
  }
125
156
 
126
- const cfg = { projectName, projectType, frontend, backend, pkgManager, hooks, ci }
157
+ const cfg = { projectName, projectType, frontend, backend, backendFramework, pkgManager, hooks, ci }
127
158
  await generators.scaffold(cfg)
128
159
 
129
160
  for (const tool of (aiTools || [])) {
@@ -137,7 +168,7 @@ cmd.action(async () => {
137
168
  }
138
169
  }
139
170
 
140
- console.log("\n✓ trackfw initialized — run 'trackfw status' to see your governance state.")
171
+ console.log(`\n${t('init.success')}`)
141
172
  })
142
173
 
143
174
  module.exports = cmd
@@ -2,16 +2,17 @@
2
2
  const { Command } = require('commander')
3
3
  const fs = require('fs')
4
4
  const path = require('path')
5
+ const { t } = require('../i18n')
5
6
 
6
7
  const cmd = new Command('log')
7
- cmd.description('Show roadmap state transition history')
8
- cmd.option('--tail <n>', 'Number of recent transitions to show', '20')
8
+ cmd.description(t('log.description'))
9
+ cmd.option('--tail <n>', t('log.tail'), '20')
9
10
  cmd.action(async (opts) => {
10
11
  const tail = parseInt(opts.tail, 10)
11
12
  const logPath = path.join('docs', 'roadmaps', '.trackfw-log')
12
13
 
13
14
  if (!fs.existsSync(logPath)) {
14
- console.log('No transitions recorded yet.')
15
+ console.log(t('log.empty'))
15
16
  return
16
17
  }
17
18
 
@@ -22,7 +23,7 @@ cmd.action(async (opts) => {
22
23
  const start = Math.max(0, lines.length - tail)
23
24
  const visible = lines.slice(start)
24
25
 
25
- console.log('── trackfw log ─────────────────────────')
26
+ console.log(t('log.header'))
26
27
  visible.forEach(l => console.log(l))
27
28
  })
28
29