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 +1 -19
- package/package.json +9 -16
- package/src/commands/adr.js +30 -0
- package/src/commands/index.js +28 -0
- package/src/commands/init.js +143 -0
- package/src/commands/log.js +29 -0
- package/src/commands/plugins.js +96 -0
- package/src/commands/req.js +69 -0
- package/src/commands/roadmap.js +36 -0
- package/src/commands/status.js +11 -0
- package/src/commands/validate.js +28 -0
- package/src/generators/adr.js +172 -0
- package/src/generators/init.js +643 -0
- package/src/generators/req.js +239 -0
- package/src/generators/roadmap.js +224 -0
- package/src/validator/index.js +340 -0
- package/bin/.gitkeep +0 -0
- package/scripts/.gitkeep +0 -0
- package/scripts/postinstall.js +0 -221
package/bin/trackfw
CHANGED
|
@@ -1,22 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
2
|
'use strict'
|
|
4
3
|
|
|
5
|
-
|
|
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.
|
|
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
|
-
"
|
|
22
|
-
"postinstall": "node scripts/postinstall.js"
|
|
23
|
-
},
|
|
21
|
+
"main": "./src/commands/index.js",
|
|
24
22
|
"files": [
|
|
25
23
|
"bin/",
|
|
26
|
-
"
|
|
24
|
+
"src/"
|
|
27
25
|
],
|
|
28
|
-
"
|
|
29
|
-
"
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"commander": "^12.0.0",
|
|
28
|
+
"@inquirer/prompts": "^5.0.0"
|
|
30
29
|
},
|
|
31
|
-
"
|
|
32
|
-
"
|
|
33
|
-
|
|
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
|