trackfw 1.0.2 → 1.0.4

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/bin/trackfw CHANGED
@@ -1,22 +1,4 @@
1
1
  #!/usr/bin/env node
2
-
3
2
  'use strict'
4
3
 
5
- const { spawnSync } = require('child_process')
6
- const path = require('path')
7
- const fs = require('fs')
8
-
9
- const isWindows = process.platform === 'win32'
10
- const binaryName = isWindows ? 'trackfw-bin.exe' : 'trackfw-bin'
11
- const binaryPath = path.join(__dirname, binaryName)
12
-
13
- if (!fs.existsSync(binaryPath)) {
14
- console.error(
15
- 'trackfw: binário não encontrado em ' + binaryPath + '\n' +
16
- 'Tente reinstalar: npm install -g trackfw'
17
- )
18
- process.exit(1)
19
- }
20
-
21
- const result = spawnSync(binaryPath, process.argv.slice(2), { stdio: 'inherit' })
22
- process.exit(result.status ?? 1)
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.2",
3
+ "version": "1.0.4",
4
4
  "description": "Governed software delivery framework: ADR → REQ → ROADMAP → kanban",
5
5
  "keywords": [
6
6
  "cli",
@@ -18,23 +18,16 @@
18
18
  "bin": {
19
19
  "trackfw": "./bin/trackfw"
20
20
  },
21
- "scripts": {
22
- "postinstall": "node scripts/postinstall.js"
23
- },
21
+ "main": "./src/commands/index.js",
24
22
  "files": [
25
23
  "bin/",
26
- "scripts/"
24
+ "src/"
27
25
  ],
28
- "engines": {
29
- "node": ">=14"
26
+ "dependencies": {
27
+ "commander": "^12.0.0",
28
+ "@inquirer/prompts": "^5.0.0"
30
29
  },
31
- "os": [
32
- "linux",
33
- "darwin",
34
- "win32"
35
- ],
36
- "cpu": [
37
- "x64",
38
- "arm64"
39
- ]
30
+ "engines": {
31
+ "node": ">=18"
32
+ }
40
33
  }
@@ -0,0 +1,30 @@
1
+ 'use strict'
2
+
3
+ const { Command } = require('commander')
4
+ const { input } = require('@inquirer/prompts')
5
+ const generators = require('../generators/adr')
6
+
7
+ const cmd = new Command('adr')
8
+ cmd.description('Manage Architecture Decision Records')
9
+
10
+ cmd.command('new <title>')
11
+ .description('Create a new ADR')
12
+ .action(async (title) => {
13
+ const content = { title }
14
+ // wizard interativo se TTY
15
+ 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: '' })
20
+ }
21
+ await generators.newADR(content)
22
+ })
23
+
24
+ cmd.command('list')
25
+ .description('List all ADRs in docs/adr/')
26
+ .action(async () => {
27
+ await generators.listADRs('docs/adr')
28
+ })
29
+
30
+ 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,143 @@
1
+ 'use strict'
2
+ const { Command } = require('commander')
3
+
4
+ const cmd = new Command('init')
5
+ cmd.description('Initialize trackfw governance in the current project')
6
+ cmd.action(async () => {
7
+ const path = require('path')
8
+ const generators = require('../generators/init')
9
+
10
+ // Modo não-TTY: usar defaults e chamar scaffold diretamente
11
+ if (!process.stdin.isTTY) {
12
+ const cfg = {
13
+ projectName: path.basename(process.cwd()),
14
+ projectType: 'governance',
15
+ frontend: '',
16
+ backend: '',
17
+ pkgManager: 'npm',
18
+ hooks: 'none',
19
+ ci: 'none',
20
+ }
21
+ await generators.scaffold(cfg)
22
+ console.log("\n✓ trackfw initialized — run 'trackfw status' to see your governance state.")
23
+ return
24
+ }
25
+
26
+ const { input, select, checkbox } = require('@inquirer/prompts')
27
+
28
+ let projectName, projectType, frontend, pkgManager, backend, hooks, ci, aiTools
29
+
30
+ try {
31
+ projectName = await input({
32
+ message: 'Project name?',
33
+ default: path.basename(process.cwd()),
34
+ })
35
+
36
+ projectType = await select({
37
+ message: 'Project type?',
38
+ 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' },
43
+ ],
44
+ })
45
+
46
+ frontend = ''
47
+ pkgManager = ''
48
+ if (projectType === 'fullstack' || projectType === 'frontend') {
49
+ frontend = await select({
50
+ message: 'Frontend stack?',
51
+ choices: [
52
+ { name: 'React / Next.js', value: 'react' },
53
+ { name: 'Vue', value: 'vue' },
54
+ { name: 'Angular', value: 'angular' },
55
+ ],
56
+ })
57
+ pkgManager = await select({
58
+ message: 'Package manager?',
59
+ choices: [
60
+ { name: 'npm', value: 'npm' },
61
+ { name: 'pnpm', value: 'pnpm' },
62
+ { name: 'yarn', value: 'yarn' },
63
+ { name: 'bun', value: 'bun' },
64
+ ],
65
+ })
66
+ }
67
+
68
+ backend = ''
69
+ if (projectType === 'fullstack' || projectType === 'backend') {
70
+ backend = await select({
71
+ message: 'Backend stack?',
72
+ choices: [
73
+ { name: 'Go', value: 'go' },
74
+ { name: 'Java / Spring Boot', value: 'java' },
75
+ { name: 'Node.js', value: 'node' },
76
+ { name: 'Python', value: 'python' },
77
+ ],
78
+ })
79
+ }
80
+
81
+ hooks = await select({
82
+ message: 'Git hooks?',
83
+ choices: [
84
+ { name: 'husky', value: 'husky' },
85
+ { name: 'lefthook', value: 'lefthook' },
86
+ { name: 'None', value: 'none' },
87
+ ],
88
+ })
89
+
90
+ ci = await select({
91
+ message: 'CI system?',
92
+ choices: [
93
+ { name: 'GitHub Actions', value: 'github-actions' },
94
+ { name: 'GitLab CI', value: 'gitlab-ci' },
95
+ { name: 'None', value: 'none' },
96
+ ],
97
+ })
98
+
99
+ aiTools = await checkbox({
100
+ message: 'Which AI assistants do you use?',
101
+ choices: [
102
+ { name: 'Claude Code', value: 'claude' },
103
+ { name: 'Gemini CLI', value: 'gemini' },
104
+ { name: 'Cursor', value: 'cursor' },
105
+ { name: 'GitHub Copilot', value: 'copilot' },
106
+ { name: 'Windsurf', value: 'windsurf' },
107
+ { name: 'Amazon Q Developer', value: 'amazonq' },
108
+ ],
109
+ })
110
+ } catch (err) {
111
+ // Fallback quando stdin fecha inesperadamente (ex: pipe em TTY simulado)
112
+ const cfg = {
113
+ projectName: path.basename(process.cwd()),
114
+ projectType: 'governance',
115
+ frontend: '',
116
+ backend: '',
117
+ pkgManager: 'npm',
118
+ hooks: 'none',
119
+ ci: 'none',
120
+ }
121
+ await generators.scaffold(cfg)
122
+ console.log("\n✓ trackfw initialized — run 'trackfw status' to see your governance state.")
123
+ return
124
+ }
125
+
126
+ const cfg = { projectName, projectType, frontend, backend, pkgManager, hooks, ci }
127
+ await generators.scaffold(cfg)
128
+
129
+ for (const tool of (aiTools || [])) {
130
+ switch (tool) {
131
+ case 'claude': await generators.installAgents(); break
132
+ case 'gemini': await generators.installGemini(); break
133
+ case 'cursor': await generators.installCursor(); break
134
+ case 'copilot': await generators.installCopilot(); break
135
+ case 'windsurf': await generators.installWindsurf(); break
136
+ case 'amazonq': await generators.installAmazonQ(); break
137
+ }
138
+ }
139
+
140
+ console.log("\n✓ trackfw initialized — run 'trackfw status' to see your governance state.")
141
+ })
142
+
143
+ module.exports = cmd
@@ -0,0 +1,29 @@
1
+ 'use strict'
2
+ const { Command } = require('commander')
3
+ const fs = require('fs')
4
+ const path = require('path')
5
+
6
+ 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')
9
+ cmd.action(async (opts) => {
10
+ const tail = parseInt(opts.tail, 10)
11
+ const logPath = path.join('docs', 'roadmaps', '.trackfw-log')
12
+
13
+ if (!fs.existsSync(logPath)) {
14
+ console.log('No transitions recorded yet.')
15
+ return
16
+ }
17
+
18
+ const lines = fs.readFileSync(logPath, 'utf8')
19
+ .split('\n')
20
+ .filter(l => l.trim() !== '')
21
+
22
+ const start = Math.max(0, lines.length - tail)
23
+ const visible = lines.slice(start)
24
+
25
+ console.log('── trackfw log ─────────────────────────')
26
+ visible.forEach(l => console.log(l))
27
+ })
28
+
29
+ module.exports = cmd
@@ -0,0 +1,96 @@
1
+ 'use strict'
2
+ const { Command } = require('commander')
3
+ const os = require('os')
4
+ const path = require('path')
5
+ const fs = require('fs')
6
+
7
+ function pluginsDir() {
8
+ return path.join(os.homedir(), '.trackfw', 'plugins')
9
+ }
10
+
11
+ function platformOS() {
12
+ if (process.platform === 'win32') return 'windows'
13
+ if (process.platform === 'darwin') return 'darwin'
14
+ return 'linux'
15
+ }
16
+
17
+ function platformArch() {
18
+ if (process.arch === 'x64') return 'amd64'
19
+ return process.arch
20
+ }
21
+
22
+ function listPlugins() {
23
+ const dir = pluginsDir()
24
+ fs.mkdirSync(dir, { recursive: true })
25
+ return fs.readdirSync(dir).filter(f => fs.statSync(path.join(dir, f)).isFile())
26
+ }
27
+
28
+ async function installPlugin(repo) {
29
+ let base = repo
30
+ let tag = 'latest'
31
+ const atIdx = repo.indexOf('@')
32
+ if (atIdx !== -1) {
33
+ base = repo.slice(0, atIdx)
34
+ tag = repo.slice(atIdx + 1)
35
+ }
36
+ const pluginName = path.basename(base)
37
+ const assetName = `trackfw-plugin-${pluginName}-${platformOS()}-${platformArch()}`
38
+ const url = tag === 'latest'
39
+ ? `https://github.com/${base}/releases/latest/download/${assetName}`
40
+ : `https://github.com/${base}/releases/download/${tag}/${assetName}`
41
+
42
+ const res = await fetch(url)
43
+ if (!res.ok) throw new Error(`download failed: HTTP ${res.status} for ${url}`)
44
+
45
+ const dir = pluginsDir()
46
+ fs.mkdirSync(dir, { recursive: true })
47
+ fs.writeFileSync(path.join(dir, pluginName), Buffer.from(await res.arrayBuffer()), { mode: 0o755 })
48
+ }
49
+
50
+ function removePlugin(name) {
51
+ const filePath = path.join(pluginsDir(), name)
52
+ if (!fs.existsSync(filePath)) throw new Error(`plugin "${name}" not found`)
53
+ fs.unlinkSync(filePath)
54
+ }
55
+
56
+ const cmd = new Command('plugins')
57
+ cmd.description('Manage trackfw plugins')
58
+
59
+ cmd.command('list')
60
+ .description('List installed plugins')
61
+ .action(() => {
62
+ const plugins = listPlugins()
63
+ if (plugins.length === 0) {
64
+ console.log('No plugins installed. Use `trackfw plugins add <user/repo>` to install one.')
65
+ return
66
+ }
67
+ plugins.forEach(p => console.log(p))
68
+ })
69
+
70
+ cmd.command('add <repo>')
71
+ .description('Install a plugin from GitHub Releases (user/repo or user/repo@tag)')
72
+ .action(async (repo) => {
73
+ try {
74
+ console.log(`Installing plugin from ${repo}...`)
75
+ await installPlugin(repo)
76
+ const name = repo.split('@')[0].split('/').pop()
77
+ console.log(`Plugin "${name}" installed successfully.`)
78
+ } catch (err) {
79
+ console.error(`Error: ${err.message}`)
80
+ process.exit(1)
81
+ }
82
+ })
83
+
84
+ cmd.command('remove <name>')
85
+ .description('Remove an installed plugin')
86
+ .action((name) => {
87
+ try {
88
+ removePlugin(name)
89
+ console.log(`Plugin "${name}" removed.`)
90
+ } catch (err) {
91
+ console.error(`Error: ${err.message}`)
92
+ process.exit(1)
93
+ }
94
+ })
95
+
96
+ module.exports = cmd
@@ -0,0 +1,69 @@
1
+ 'use strict'
2
+ const { Command } = require('commander')
3
+ const { listREQs } = require('../generators/req')
4
+
5
+ const cmd = new Command('req')
6
+ cmd.description('Manage Requirements')
7
+
8
+ cmd.command('new <title>')
9
+ .description('Create a new REQ')
10
+ .action(async (title) => {
11
+ const { input, select } = require('@inquirer/prompts')
12
+ const generators = require('../generators/req')
13
+ const adrGenerators = require('../generators/adr')
14
+
15
+ const content = { title, motivation: '', criteria: '', dependsOnADRs: [] }
16
+
17
+ if (process.stdin.isTTY) {
18
+ // Form 1 — título + motivação
19
+ content.title = await input({ message: 'Project requirement', default: title })
20
+ content.motivation = await input({ message: 'Motivation (why is this needed?)', default: '' })
21
+
22
+ // Detectar domínios com base em título + motivação
23
+ const probes = generators.detectDomains(content.title + ' ' + content.motivation)
24
+
25
+ // Form 2 — critérios de aceite
26
+ content.criteria = await input({ message: 'Acceptance Criteria (one per line)', default: '- [ ]\n- [ ]' })
27
+
28
+ // Perguntas dinâmicas por probe
29
+ const generatedADRs = []
30
+ for (const probe of probes) {
31
+ for (const question of probe.questions) {
32
+ const choices = question.options.map(opt => ({
33
+ name: opt.label,
34
+ value: opt.adrSlug || '',
35
+ }))
36
+ const answer = await select({
37
+ message: question.text,
38
+ choices,
39
+ })
40
+ if (answer) {
41
+ try {
42
+ const basename = await adrGenerators.newADRDraft(answer)
43
+ if (basename) generatedADRs.push(basename)
44
+ } catch (e) {
45
+ console.warn(`warning: could not create ADR draft for ${answer}: ${e.message}`)
46
+ }
47
+ }
48
+ }
49
+ }
50
+
51
+ content.dependsOnADRs = [...new Set(generatedADRs)]
52
+ }
53
+
54
+ await generators.newREQ(content)
55
+
56
+ if (content.dependsOnADRs.length > 0) {
57
+ console.log('\nADR drafts created:')
58
+ content.dependsOnADRs.forEach(adr => console.log(` -> ${adr}`))
59
+ console.log('\nResolve these ADRs (set Status: Accepted) before creating a roadmap.')
60
+ }
61
+ })
62
+
63
+ cmd.command('list')
64
+ .description('List all REQs in docs/req/')
65
+ .action(async () => {
66
+ listREQs('docs/req')
67
+ })
68
+
69
+ module.exports = cmd
@@ -0,0 +1,36 @@
1
+ 'use strict'
2
+ const { Command } = require('commander')
3
+ const { listRoadmaps, showRoadmap, moveRoadmap, newRoadmap } = require('../generators/roadmap')
4
+
5
+ const cmd = new Command('roadmap')
6
+ cmd.description('Manage Roadmaps')
7
+
8
+ cmd.command('new')
9
+ .description('Create a new roadmap from a REQ')
10
+ .option('-t, --title <title>', 'Roadmap title')
11
+ .option('-r, --req <path>', 'Path to the linked REQ')
12
+ .action(async (opts) => {
13
+ const title = opts.title || 'New Roadmap'
14
+ const reqPath = opts.req || ''
15
+ newRoadmap(title, reqPath)
16
+ })
17
+
18
+ cmd.command('list')
19
+ .description('List all roadmaps grouped by state')
20
+ .action(async () => {
21
+ listRoadmaps()
22
+ })
23
+
24
+ cmd.command('show <name>')
25
+ .description('Show a roadmap by name (partial match)')
26
+ .action(async (name) => {
27
+ showRoadmap(name)
28
+ })
29
+
30
+ cmd.command('move <name> <state>')
31
+ .description('Move a roadmap between states (backlog|wip|blocked|done|abandoned)')
32
+ .action(async (name, state) => {
33
+ moveRoadmap(name, state)
34
+ })
35
+
36
+ module.exports = cmd
@@ -0,0 +1,11 @@
1
+ 'use strict'
2
+ const { Command } = require('commander')
3
+ const { getStatus } = require('../validator')
4
+
5
+ const cmd = new Command('status')
6
+ cmd.description('Show project governance status')
7
+ cmd.action(async () => {
8
+ console.log(await getStatus())
9
+ })
10
+
11
+ module.exports = cmd
@@ -0,0 +1,28 @@
1
+ 'use strict'
2
+ const { Command } = require('commander')
3
+ const { validate } = require('../validator')
4
+
5
+ const cmd = new Command('validate')
6
+ cmd.description('Validate governance rules')
7
+ cmd.action(async () => {
8
+ const { violations, warnings } = await validate()
9
+
10
+ if (violations.length === 0 && warnings.length === 0) {
11
+ console.log('✓ No violations found.')
12
+ return
13
+ }
14
+
15
+ if (violations.length > 0) {
16
+ console.log(`\n✗ Violations (${violations.length}):`)
17
+ violations.forEach(v => console.log(` • ${v}`))
18
+ }
19
+
20
+ if (warnings.length > 0) {
21
+ console.log(`\n⚠ Warnings (${warnings.length}):`)
22
+ warnings.forEach(w => console.log(` • ${w}`))
23
+ }
24
+
25
+ if (violations.length > 0) process.exit(1)
26
+ })
27
+
28
+ module.exports = cmd