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 +17 -8
- package/bin/trackfw +4 -0
- package/package.json +10 -13
- package/src/commands/adr.js +31 -0
- package/src/commands/index.js +28 -0
- package/src/commands/init.js +174 -0
- package/src/commands/log.js +30 -0
- package/src/commands/plugins.js +97 -0
- package/src/commands/req.js +70 -0
- package/src/commands/roadmap.js +37 -0
- package/src/commands/status.js +12 -0
- package/src/commands/validate.js +29 -0
- package/src/generators/adr.js +172 -0
- package/src/generators/init.js +702 -0
- package/src/generators/req.js +239 -0
- package/src/generators/roadmap.js +224 -0
- package/src/i18n/index.js +53 -0
- package/src/i18n/locales/en-US.json +122 -0
- package/src/i18n/locales/es-ES.json +122 -0
- package/src/i18n/locales/pt-BR.json +122 -0
- package/src/validator/index.js +340 -0
- package/bin/.gitkeep +0 -0
- package/bin/README.md +0 -301
- package/bin/trackfw-darwin-amd64 +0 -0
- package/bin/trackfw-darwin-arm64 +0 -0
- package/bin/trackfw-linux-amd64 +0 -0
- package/bin/trackfw-linux-arm64 +0 -0
- package/bin/trackfw-windows-amd64.exe +0 -0
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
|
|
135
|
-
| `trackfw
|
|
136
|
-
| `trackfw
|
|
137
|
-
| `trackfw
|
|
138
|
-
| `trackfw
|
|
139
|
-
| `trackfw
|
|
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.
|
|
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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "trackfw",
|
|
3
|
-
"version": "1.0
|
|
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
|
-
"
|
|
25
|
-
"
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"commander": "^12.0.0",
|
|
28
|
+
"@inquirer/prompts": "^5.0.0"
|
|
26
29
|
},
|
|
27
|
-
"
|
|
28
|
-
"
|
|
29
|
-
|
|
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
|