trackfw 1.0.3 → 1.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/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/bin/trackfw ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+ 'use strict'
3
+
4
+ require('../src/commands/index').createProgram().parseAsync(process.argv)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "trackfw",
3
- "version": "1.0.3",
3
+ "version": "1.1.0",
4
4
  "description": "Governed software delivery framework: ADR → REQ → ROADMAP → kanban",
5
5
  "keywords": [
6
6
  "cli",
@@ -18,19 +18,16 @@
18
18
  "bin": {
19
19
  "trackfw": "./bin/trackfw"
20
20
  },
21
+ "main": "./src/commands/index.js",
21
22
  "files": [
22
- "bin/"
23
+ "bin/",
24
+ "src/"
23
25
  ],
24
- "engines": {
25
- "node": ">=14"
26
+ "dependencies": {
27
+ "commander": "^12.0.0",
28
+ "@inquirer/prompts": "^5.0.0"
26
29
  },
27
- "os": [
28
- "linux",
29
- "darwin",
30
- "win32"
31
- ],
32
- "cpu": [
33
- "x64",
34
- "arm64"
35
- ]
30
+ "engines": {
31
+ "node": ">=18"
32
+ }
36
33
  }
@@ -0,0 +1,31 @@
1
+ 'use strict'
2
+
3
+ const { Command } = require('commander')
4
+ const { input } = require('@inquirer/prompts')
5
+ const generators = require('../generators/adr')
6
+ const { t } = require('../i18n')
7
+
8
+ const cmd = new Command('adr')
9
+ cmd.description(t('adr.description'))
10
+
11
+ cmd.command('new <title>')
12
+ .description(t('adr.new.description'))
13
+ .action(async (title) => {
14
+ const content = { title }
15
+ // wizard interativo se TTY
16
+ if (process.stdin.isTTY) {
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: '' })
21
+ }
22
+ await generators.newADR(content)
23
+ })
24
+
25
+ cmd.command('list')
26
+ .description(t('adr.list.description'))
27
+ .action(async () => {
28
+ await generators.listADRs('docs/adr')
29
+ })
30
+
31
+ module.exports = cmd
@@ -0,0 +1,28 @@
1
+ 'use strict'
2
+
3
+ const { Command } = require('commander')
4
+ const { version } = require('../../package.json')
5
+
6
+ function createProgram() {
7
+ const program = new Command()
8
+ program
9
+ .name('trackfw')
10
+ .description('trackfw — governed software delivery framework\nADR → REQ → ROADMAP → kanban')
11
+ .version(version)
12
+
13
+ program.addCommand(require('./init'))
14
+ program.addCommand(require('./adr'))
15
+ program.addCommand(require('./req'))
16
+ program.addCommand(require('./roadmap'))
17
+ program.addCommand(require('./validate'))
18
+ program.addCommand(require('./status'))
19
+ program.addCommand(require('./log'))
20
+ program.addCommand(require('./plugins'))
21
+
22
+ // plugin dispatch — comandos desconhecidos tentam executar plugin
23
+ program.hook('preSubcommand', () => {})
24
+
25
+ return program
26
+ }
27
+
28
+ module.exports = { createProgram }
@@ -0,0 +1,174 @@
1
+ 'use strict'
2
+ const { Command } = require('commander')
3
+ const { t } = require('../i18n')
4
+
5
+ const cmd = new Command('init')
6
+ cmd.description(t('init.description'))
7
+ cmd.action(async () => {
8
+ const path = require('path')
9
+ const generators = require('../generators/init')
10
+
11
+ // Modo não-TTY: usar defaults e chamar scaffold diretamente
12
+ if (!process.stdin.isTTY) {
13
+ const cfg = {
14
+ projectName: path.basename(process.cwd()),
15
+ projectType: 'governance',
16
+ frontend: '',
17
+ backend: '',
18
+ pkgManager: 'npm',
19
+ hooks: 'none',
20
+ ci: 'none',
21
+ }
22
+ await generators.scaffold(cfg)
23
+ console.log(`\n${t('init.success')}`)
24
+ return
25
+ }
26
+
27
+ const { input, select, checkbox } = require('@inquirer/prompts')
28
+
29
+ let projectName, projectType, frontend, pkgManager, backend, backendFramework, hooks, ci, aiTools
30
+
31
+ try {
32
+ projectName = await input({
33
+ message: t('init.prompt.projectName'),
34
+ default: path.basename(process.cwd()),
35
+ })
36
+
37
+ projectType = await select({
38
+ message: t('init.prompt.projectType'),
39
+ choices: [
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' },
44
+ ],
45
+ })
46
+
47
+ frontend = ''
48
+ pkgManager = ''
49
+ if (projectType === 'fullstack' || projectType === 'frontend') {
50
+ frontend = await select({
51
+ message: t('init.prompt.frontendStack'),
52
+ choices: [
53
+ { name: 'React / Next.js', value: 'react' },
54
+ { name: 'Vue', value: 'vue' },
55
+ { name: 'Angular', value: 'angular' },
56
+ ],
57
+ })
58
+ pkgManager = await select({
59
+ message: t('init.prompt.pkgManager'),
60
+ choices: [
61
+ { name: 'npm', value: 'npm' },
62
+ { name: 'pnpm', value: 'pnpm' },
63
+ { name: 'yarn', value: 'yarn' },
64
+ { name: 'bun', value: 'bun' },
65
+ ],
66
+ })
67
+ }
68
+
69
+ backend = ''
70
+ let backendFramework = ''
71
+ if (projectType === 'fullstack' || projectType === 'backend') {
72
+ backend = await select({
73
+ message: t('init.prompt.backendLang'),
74
+ choices: [
75
+ { name: 'Go', value: 'go' },
76
+ { name: 'Java', value: 'java' },
77
+ { name: 'Node.js', value: 'node' },
78
+ { name: 'Python', value: 'python' },
79
+ ],
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
+ })
110
+ }
111
+
112
+ hooks = await select({
113
+ message: t('init.prompt.gitHooks'),
114
+ choices: [
115
+ { name: 'husky', value: 'husky' },
116
+ { name: 'lefthook', value: 'lefthook' },
117
+ { name: 'None', value: 'none' },
118
+ ],
119
+ })
120
+
121
+ ci = await select({
122
+ message: t('init.prompt.ci'),
123
+ choices: [
124
+ { name: 'GitHub Actions', value: 'github-actions' },
125
+ { name: 'GitLab CI', value: 'gitlab-ci' },
126
+ { name: 'None', value: 'none' },
127
+ ],
128
+ })
129
+
130
+ aiTools = await checkbox({
131
+ message: t('init.prompt.aiTools'),
132
+ choices: [
133
+ { name: 'Claude Code', value: 'claude' },
134
+ { name: 'Gemini CLI', value: 'gemini' },
135
+ { name: 'Cursor', value: 'cursor' },
136
+ { name: 'GitHub Copilot', value: 'copilot' },
137
+ { name: 'Windsurf', value: 'windsurf' },
138
+ { name: 'Amazon Q Developer', value: 'amazonq' },
139
+ ],
140
+ })
141
+ } catch (err) {
142
+ // Fallback quando stdin fecha inesperadamente (ex: pipe em TTY simulado)
143
+ const cfg = {
144
+ projectName: path.basename(process.cwd()),
145
+ projectType: 'governance',
146
+ frontend: '',
147
+ backend: '',
148
+ pkgManager: 'npm',
149
+ hooks: 'none',
150
+ ci: 'none',
151
+ }
152
+ await generators.scaffold(cfg)
153
+ console.log(`\n${t('init.success')}`)
154
+ return
155
+ }
156
+
157
+ const cfg = { projectName, projectType, frontend, backend, backendFramework, pkgManager, hooks, ci }
158
+ await generators.scaffold(cfg)
159
+
160
+ for (const tool of (aiTools || [])) {
161
+ switch (tool) {
162
+ case 'claude': await generators.installAgents(); break
163
+ case 'gemini': await generators.installGemini(); break
164
+ case 'cursor': await generators.installCursor(); break
165
+ case 'copilot': await generators.installCopilot(); break
166
+ case 'windsurf': await generators.installWindsurf(); break
167
+ case 'amazonq': await generators.installAmazonQ(); break
168
+ }
169
+ }
170
+
171
+ console.log(`\n${t('init.success')}`)
172
+ })
173
+
174
+ module.exports = cmd
@@ -0,0 +1,30 @@
1
+ 'use strict'
2
+ const { Command } = require('commander')
3
+ const fs = require('fs')
4
+ const path = require('path')
5
+ const { t } = require('../i18n')
6
+
7
+ const cmd = new Command('log')
8
+ cmd.description(t('log.description'))
9
+ cmd.option('--tail <n>', t('log.tail'), '20')
10
+ cmd.action(async (opts) => {
11
+ const tail = parseInt(opts.tail, 10)
12
+ const logPath = path.join('docs', 'roadmaps', '.trackfw-log')
13
+
14
+ if (!fs.existsSync(logPath)) {
15
+ console.log(t('log.empty'))
16
+ return
17
+ }
18
+
19
+ const lines = fs.readFileSync(logPath, 'utf8')
20
+ .split('\n')
21
+ .filter(l => l.trim() !== '')
22
+
23
+ const start = Math.max(0, lines.length - tail)
24
+ const visible = lines.slice(start)
25
+
26
+ console.log(t('log.header'))
27
+ visible.forEach(l => console.log(l))
28
+ })
29
+
30
+ module.exports = cmd
@@ -0,0 +1,97 @@
1
+ 'use strict'
2
+ const { Command } = require('commander')
3
+ const os = require('os')
4
+ const path = require('path')
5
+ const fs = require('fs')
6
+ const { t } = require('../i18n')
7
+
8
+ function pluginsDir() {
9
+ return path.join(os.homedir(), '.trackfw', 'plugins')
10
+ }
11
+
12
+ function platformOS() {
13
+ if (process.platform === 'win32') return 'windows'
14
+ if (process.platform === 'darwin') return 'darwin'
15
+ return 'linux'
16
+ }
17
+
18
+ function platformArch() {
19
+ if (process.arch === 'x64') return 'amd64'
20
+ return process.arch
21
+ }
22
+
23
+ function listPlugins() {
24
+ const dir = pluginsDir()
25
+ fs.mkdirSync(dir, { recursive: true })
26
+ return fs.readdirSync(dir).filter(f => fs.statSync(path.join(dir, f)).isFile())
27
+ }
28
+
29
+ async function installPlugin(repo) {
30
+ let base = repo
31
+ let tag = 'latest'
32
+ const atIdx = repo.indexOf('@')
33
+ if (atIdx !== -1) {
34
+ base = repo.slice(0, atIdx)
35
+ tag = repo.slice(atIdx + 1)
36
+ }
37
+ const pluginName = path.basename(base)
38
+ const assetName = `trackfw-plugin-${pluginName}-${platformOS()}-${platformArch()}`
39
+ const url = tag === 'latest'
40
+ ? `https://github.com/${base}/releases/latest/download/${assetName}`
41
+ : `https://github.com/${base}/releases/download/${tag}/${assetName}`
42
+
43
+ const res = await fetch(url)
44
+ if (!res.ok) throw new Error(t('errors.downloadFailed', { status: res.status, url }))
45
+
46
+ const dir = pluginsDir()
47
+ fs.mkdirSync(dir, { recursive: true })
48
+ fs.writeFileSync(path.join(dir, pluginName), Buffer.from(await res.arrayBuffer()), { mode: 0o755 })
49
+ }
50
+
51
+ function removePlugin(name) {
52
+ const filePath = path.join(pluginsDir(), name)
53
+ if (!fs.existsSync(filePath)) throw new Error(t('errors.pluginNotFound', { name }))
54
+ fs.unlinkSync(filePath)
55
+ }
56
+
57
+ const cmd = new Command('plugins')
58
+ cmd.description(t('plugins.description'))
59
+
60
+ cmd.command('list')
61
+ .description(t('plugins.list.description'))
62
+ .action(() => {
63
+ const plugins = listPlugins()
64
+ if (plugins.length === 0) {
65
+ console.log(t('plugins.list.empty'))
66
+ return
67
+ }
68
+ plugins.forEach(p => console.log(p))
69
+ })
70
+
71
+ cmd.command('add <repo>')
72
+ .description(t('plugins.add.description'))
73
+ .action(async (repo) => {
74
+ try {
75
+ console.log(t('plugins.add.installing', { repo }))
76
+ await installPlugin(repo)
77
+ const name = repo.split('@')[0].split('/').pop()
78
+ console.log(t('plugins.add.success', { name }))
79
+ } catch (err) {
80
+ console.error(`Error: ${err.message}`)
81
+ process.exit(1)
82
+ }
83
+ })
84
+
85
+ cmd.command('remove <name>')
86
+ .description(t('plugins.remove.description'))
87
+ .action((name) => {
88
+ try {
89
+ removePlugin(name)
90
+ console.log(t('plugins.remove.success', { name }))
91
+ } catch (err) {
92
+ console.error(`Error: ${err.message}`)
93
+ process.exit(1)
94
+ }
95
+ })
96
+
97
+ module.exports = cmd
@@ -0,0 +1,70 @@
1
+ 'use strict'
2
+ const { Command } = require('commander')
3
+ const { listREQs } = require('../generators/req')
4
+ const { t } = require('../i18n')
5
+
6
+ const cmd = new Command('req')
7
+ cmd.description(t('req.description'))
8
+
9
+ cmd.command('new <title>')
10
+ .description(t('req.new.description'))
11
+ .action(async (title) => {
12
+ const { input, select } = require('@inquirer/prompts')
13
+ const generators = require('../generators/req')
14
+ const adrGenerators = require('../generators/adr')
15
+
16
+ const content = { title, motivation: '', criteria: '', dependsOnADRs: [] }
17
+
18
+ if (process.stdin.isTTY) {
19
+ // Form 1 — título + motivação
20
+ content.title = await input({ message: t('req.new.prompt.title'), default: title })
21
+ content.motivation = await input({ message: t('req.new.prompt.motivation'), default: '' })
22
+
23
+ // Detectar domínios com base em título + motivação
24
+ const probes = generators.detectDomains(content.title + ' ' + content.motivation)
25
+
26
+ // Form 2 — critérios de aceite
27
+ content.criteria = await input({ message: t('req.new.prompt.criteria'), default: '- [ ]\n- [ ]' })
28
+
29
+ // Perguntas dinâmicas por probe
30
+ const generatedADRs = []
31
+ for (const probe of probes) {
32
+ for (const question of probe.questions) {
33
+ const choices = question.options.map(opt => ({
34
+ name: opt.label,
35
+ value: opt.adrSlug || '',
36
+ }))
37
+ const answer = await select({
38
+ message: question.text,
39
+ choices,
40
+ })
41
+ if (answer) {
42
+ try {
43
+ const basename = await adrGenerators.newADRDraft(answer)
44
+ if (basename) generatedADRs.push(basename)
45
+ } catch (e) {
46
+ console.warn(t('req.new.adrWarning', { slug: answer, message: e.message }))
47
+ }
48
+ }
49
+ }
50
+ }
51
+
52
+ content.dependsOnADRs = [...new Set(generatedADRs)]
53
+ }
54
+
55
+ await generators.newREQ(content)
56
+
57
+ if (content.dependsOnADRs.length > 0) {
58
+ console.log(`\n${t('req.new.adrDraftsCreated')}`)
59
+ content.dependsOnADRs.forEach(adr => console.log(` -> ${adr}`))
60
+ console.log(`\n${t('req.new.resolveADRs')}`)
61
+ }
62
+ })
63
+
64
+ cmd.command('list')
65
+ .description(t('req.list.description'))
66
+ .action(async () => {
67
+ listREQs('docs/req')
68
+ })
69
+
70
+ module.exports = cmd
@@ -0,0 +1,37 @@
1
+ 'use strict'
2
+ const { Command } = require('commander')
3
+ const { listRoadmaps, showRoadmap, moveRoadmap, newRoadmap } = require('../generators/roadmap')
4
+ const { t } = require('../i18n')
5
+
6
+ const cmd = new Command('roadmap')
7
+ cmd.description(t('roadmap.description'))
8
+
9
+ cmd.command('new')
10
+ .description(t('roadmap.new.description'))
11
+ .option('-t, --title <title>', 'Roadmap title')
12
+ .option('-r, --req <path>', 'Path to the linked REQ')
13
+ .action(async (opts) => {
14
+ const title = opts.title || 'New Roadmap'
15
+ const reqPath = opts.req || ''
16
+ newRoadmap(title, reqPath)
17
+ })
18
+
19
+ cmd.command('list')
20
+ .description(t('roadmap.list.description'))
21
+ .action(async () => {
22
+ listRoadmaps()
23
+ })
24
+
25
+ cmd.command('show <name>')
26
+ .description(t('roadmap.show.description'))
27
+ .action(async (name) => {
28
+ showRoadmap(name)
29
+ })
30
+
31
+ cmd.command('move <name> <state>')
32
+ .description(t('roadmap.move.description'))
33
+ .action(async (name, state) => {
34
+ moveRoadmap(name, state)
35
+ })
36
+
37
+ module.exports = cmd
@@ -0,0 +1,12 @@
1
+ 'use strict'
2
+ const { Command } = require('commander')
3
+ const { getStatus } = require('../validator')
4
+ const { t } = require('../i18n')
5
+
6
+ const cmd = new Command('status')
7
+ cmd.description(t('status.description'))
8
+ cmd.action(async () => {
9
+ console.log(await getStatus())
10
+ })
11
+
12
+ module.exports = cmd
@@ -0,0 +1,29 @@
1
+ 'use strict'
2
+ const { Command } = require('commander')
3
+ const { validate } = require('../validator')
4
+ const { t } = require('../i18n')
5
+
6
+ const cmd = new Command('validate')
7
+ cmd.description(t('validate.description'))
8
+ cmd.action(async () => {
9
+ const { violations, warnings } = await validate()
10
+
11
+ if (violations.length === 0 && warnings.length === 0) {
12
+ console.log(t('validate.ok'))
13
+ return
14
+ }
15
+
16
+ if (violations.length > 0) {
17
+ console.log(`\n${t('validate.violations', { count: violations.length })}`)
18
+ violations.forEach(v => console.log(` • ${v}`))
19
+ }
20
+
21
+ if (warnings.length > 0) {
22
+ console.log(`\n${t('validate.warnings', { count: warnings.length })}`)
23
+ warnings.forEach(w => console.log(` • ${w}`))
24
+ }
25
+
26
+ if (violations.length > 0) process.exit(1)
27
+ })
28
+
29
+ module.exports = cmd