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 +17 -8
- package/package.json +1 -1
- package/src/commands/adr.js +9 -8
- package/src/commands/discover.js +359 -0
- package/src/commands/index.js +1 -0
- package/src/commands/init.js +50 -19
- package/src/commands/log.js +5 -4
- package/src/commands/plugins.js +11 -10
- package/src/commands/req.js +11 -10
- package/src/commands/roadmap.js +6 -5
- package/src/commands/status.js +2 -1
- package/src/commands/validate.js +5 -4
- package/src/config/index.js +92 -0
- package/src/generators/adr.js +6 -5
- package/src/generators/init.js +66 -0
- package/src/generators/req.js +3 -2
- package/src/generators/roadmap.js +150 -57
- 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 +196 -97
package/src/commands/plugins.js
CHANGED
|
@@ -3,6 +3,7 @@ const { Command } = require('commander')
|
|
|
3
3
|
const os = require('os')
|
|
4
4
|
const path = require('path')
|
|
5
5
|
const fs = require('fs')
|
|
6
|
+
const { t } = require('../i18n')
|
|
6
7
|
|
|
7
8
|
function pluginsDir() {
|
|
8
9
|
return path.join(os.homedir(), '.trackfw', 'plugins')
|
|
@@ -40,7 +41,7 @@ async function installPlugin(repo) {
|
|
|
40
41
|
: `https://github.com/${base}/releases/download/${tag}/${assetName}`
|
|
41
42
|
|
|
42
43
|
const res = await fetch(url)
|
|
43
|
-
if (!res.ok) throw new Error(
|
|
44
|
+
if (!res.ok) throw new Error(t('errors.downloadFailed', { status: res.status, url }))
|
|
44
45
|
|
|
45
46
|
const dir = pluginsDir()
|
|
46
47
|
fs.mkdirSync(dir, { recursive: true })
|
|
@@ -49,32 +50,32 @@ async function installPlugin(repo) {
|
|
|
49
50
|
|
|
50
51
|
function removePlugin(name) {
|
|
51
52
|
const filePath = path.join(pluginsDir(), name)
|
|
52
|
-
if (!fs.existsSync(filePath)) throw new Error(
|
|
53
|
+
if (!fs.existsSync(filePath)) throw new Error(t('errors.pluginNotFound', { name }))
|
|
53
54
|
fs.unlinkSync(filePath)
|
|
54
55
|
}
|
|
55
56
|
|
|
56
57
|
const cmd = new Command('plugins')
|
|
57
|
-
cmd.description('
|
|
58
|
+
cmd.description(t('plugins.description'))
|
|
58
59
|
|
|
59
60
|
cmd.command('list')
|
|
60
|
-
.description('
|
|
61
|
+
.description(t('plugins.list.description'))
|
|
61
62
|
.action(() => {
|
|
62
63
|
const plugins = listPlugins()
|
|
63
64
|
if (plugins.length === 0) {
|
|
64
|
-
console.log('
|
|
65
|
+
console.log(t('plugins.list.empty'))
|
|
65
66
|
return
|
|
66
67
|
}
|
|
67
68
|
plugins.forEach(p => console.log(p))
|
|
68
69
|
})
|
|
69
70
|
|
|
70
71
|
cmd.command('add <repo>')
|
|
71
|
-
.description(
|
|
72
|
+
.description(t('plugins.add.description'))
|
|
72
73
|
.action(async (repo) => {
|
|
73
74
|
try {
|
|
74
|
-
console.log(
|
|
75
|
+
console.log(t('plugins.add.installing', { repo }))
|
|
75
76
|
await installPlugin(repo)
|
|
76
77
|
const name = repo.split('@')[0].split('/').pop()
|
|
77
|
-
console.log(
|
|
78
|
+
console.log(t('plugins.add.success', { name }))
|
|
78
79
|
} catch (err) {
|
|
79
80
|
console.error(`Error: ${err.message}`)
|
|
80
81
|
process.exit(1)
|
|
@@ -82,11 +83,11 @@ cmd.command('add <repo>')
|
|
|
82
83
|
})
|
|
83
84
|
|
|
84
85
|
cmd.command('remove <name>')
|
|
85
|
-
.description('
|
|
86
|
+
.description(t('plugins.remove.description'))
|
|
86
87
|
.action((name) => {
|
|
87
88
|
try {
|
|
88
89
|
removePlugin(name)
|
|
89
|
-
console.log(
|
|
90
|
+
console.log(t('plugins.remove.success', { name }))
|
|
90
91
|
} catch (err) {
|
|
91
92
|
console.error(`Error: ${err.message}`)
|
|
92
93
|
process.exit(1)
|
package/src/commands/req.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
const { Command } = require('commander')
|
|
3
3
|
const { listREQs } = require('../generators/req')
|
|
4
|
+
const { t } = require('../i18n')
|
|
4
5
|
|
|
5
6
|
const cmd = new Command('req')
|
|
6
|
-
cmd.description('
|
|
7
|
+
cmd.description(t('req.description'))
|
|
7
8
|
|
|
8
9
|
cmd.command('new <title>')
|
|
9
|
-
.description('
|
|
10
|
+
.description(t('req.new.description'))
|
|
10
11
|
.action(async (title) => {
|
|
11
12
|
const { input, select } = require('@inquirer/prompts')
|
|
12
13
|
const generators = require('../generators/req')
|
|
@@ -16,14 +17,14 @@ cmd.command('new <title>')
|
|
|
16
17
|
|
|
17
18
|
if (process.stdin.isTTY) {
|
|
18
19
|
// Form 1 — título + motivação
|
|
19
|
-
content.title = await input({ message: '
|
|
20
|
-
content.motivation = await input({ message: '
|
|
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: '' })
|
|
21
22
|
|
|
22
23
|
// Detectar domínios com base em título + motivação
|
|
23
24
|
const probes = generators.detectDomains(content.title + ' ' + content.motivation)
|
|
24
25
|
|
|
25
26
|
// Form 2 — critérios de aceite
|
|
26
|
-
content.criteria = await input({ message: '
|
|
27
|
+
content.criteria = await input({ message: t('req.new.prompt.criteria'), default: '- [ ]\n- [ ]' })
|
|
27
28
|
|
|
28
29
|
// Perguntas dinâmicas por probe
|
|
29
30
|
const generatedADRs = []
|
|
@@ -42,7 +43,7 @@ cmd.command('new <title>')
|
|
|
42
43
|
const basename = await adrGenerators.newADRDraft(answer)
|
|
43
44
|
if (basename) generatedADRs.push(basename)
|
|
44
45
|
} catch (e) {
|
|
45
|
-
console.warn(
|
|
46
|
+
console.warn(t('req.new.adrWarning', { slug: answer, message: e.message }))
|
|
46
47
|
}
|
|
47
48
|
}
|
|
48
49
|
}
|
|
@@ -54,16 +55,16 @@ cmd.command('new <title>')
|
|
|
54
55
|
await generators.newREQ(content)
|
|
55
56
|
|
|
56
57
|
if (content.dependsOnADRs.length > 0) {
|
|
57
|
-
console.log('
|
|
58
|
+
console.log(`\n${t('req.new.adrDraftsCreated')}`)
|
|
58
59
|
content.dependsOnADRs.forEach(adr => console.log(` -> ${adr}`))
|
|
59
|
-
console.log('
|
|
60
|
+
console.log(`\n${t('req.new.resolveADRs')}`)
|
|
60
61
|
}
|
|
61
62
|
})
|
|
62
63
|
|
|
63
64
|
cmd.command('list')
|
|
64
|
-
.description('
|
|
65
|
+
.description(t('req.list.description'))
|
|
65
66
|
.action(async () => {
|
|
66
|
-
listREQs('
|
|
67
|
+
listREQs(require('../config').load().reqDir)
|
|
67
68
|
})
|
|
68
69
|
|
|
69
70
|
module.exports = cmd
|
package/src/commands/roadmap.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
const { Command } = require('commander')
|
|
3
3
|
const { listRoadmaps, showRoadmap, moveRoadmap, newRoadmap } = require('../generators/roadmap')
|
|
4
|
+
const { t } = require('../i18n')
|
|
4
5
|
|
|
5
6
|
const cmd = new Command('roadmap')
|
|
6
|
-
cmd.description('
|
|
7
|
+
cmd.description(t('roadmap.description'))
|
|
7
8
|
|
|
8
9
|
cmd.command('new')
|
|
9
|
-
.description('
|
|
10
|
+
.description(t('roadmap.new.description'))
|
|
10
11
|
.option('-t, --title <title>', 'Roadmap title')
|
|
11
12
|
.option('-r, --req <path>', 'Path to the linked REQ')
|
|
12
13
|
.action(async (opts) => {
|
|
@@ -16,19 +17,19 @@ cmd.command('new')
|
|
|
16
17
|
})
|
|
17
18
|
|
|
18
19
|
cmd.command('list')
|
|
19
|
-
.description('
|
|
20
|
+
.description(t('roadmap.list.description'))
|
|
20
21
|
.action(async () => {
|
|
21
22
|
listRoadmaps()
|
|
22
23
|
})
|
|
23
24
|
|
|
24
25
|
cmd.command('show <name>')
|
|
25
|
-
.description('
|
|
26
|
+
.description(t('roadmap.show.description'))
|
|
26
27
|
.action(async (name) => {
|
|
27
28
|
showRoadmap(name)
|
|
28
29
|
})
|
|
29
30
|
|
|
30
31
|
cmd.command('move <name> <state>')
|
|
31
|
-
.description('
|
|
32
|
+
.description(t('roadmap.move.description'))
|
|
32
33
|
.action(async (name, state) => {
|
|
33
34
|
moveRoadmap(name, state)
|
|
34
35
|
})
|
package/src/commands/status.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
const { Command } = require('commander')
|
|
3
3
|
const { getStatus } = require('../validator')
|
|
4
|
+
const { t } = require('../i18n')
|
|
4
5
|
|
|
5
6
|
const cmd = new Command('status')
|
|
6
|
-
cmd.description('
|
|
7
|
+
cmd.description(t('status.description'))
|
|
7
8
|
cmd.action(async () => {
|
|
8
9
|
console.log(await getStatus())
|
|
9
10
|
})
|
package/src/commands/validate.js
CHANGED
|
@@ -1,24 +1,25 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
const { Command } = require('commander')
|
|
3
3
|
const { validate } = require('../validator')
|
|
4
|
+
const { t } = require('../i18n')
|
|
4
5
|
|
|
5
6
|
const cmd = new Command('validate')
|
|
6
|
-
cmd.description('
|
|
7
|
+
cmd.description(t('validate.description'))
|
|
7
8
|
cmd.action(async () => {
|
|
8
9
|
const { violations, warnings } = await validate()
|
|
9
10
|
|
|
10
11
|
if (violations.length === 0 && warnings.length === 0) {
|
|
11
|
-
console.log('
|
|
12
|
+
console.log(t('validate.ok'))
|
|
12
13
|
return
|
|
13
14
|
}
|
|
14
15
|
|
|
15
16
|
if (violations.length > 0) {
|
|
16
|
-
console.log(`\n
|
|
17
|
+
console.log(`\n${t('validate.violations', { count: violations.length })}`)
|
|
17
18
|
violations.forEach(v => console.log(` • ${v}`))
|
|
18
19
|
}
|
|
19
20
|
|
|
20
21
|
if (warnings.length > 0) {
|
|
21
|
-
console.log(`\n
|
|
22
|
+
console.log(`\n${t('validate.warnings', { count: warnings.length })}`)
|
|
22
23
|
warnings.forEach(w => console.log(` • ${w}`))
|
|
23
24
|
}
|
|
24
25
|
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
function defaults() {
|
|
7
|
+
return {
|
|
8
|
+
adrDirs: ['docs/adr'],
|
|
9
|
+
reqDir: 'docs/req',
|
|
10
|
+
roadmapDir: 'docs/roadmaps',
|
|
11
|
+
roadmapNamespacing: 'flat',
|
|
12
|
+
agents: [],
|
|
13
|
+
governanceMode: '',
|
|
14
|
+
lenientUntil: '',
|
|
15
|
+
wipLimit: 1,
|
|
16
|
+
wipBySquad: false,
|
|
17
|
+
requireReqInCommit: false,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
let _instance = null;
|
|
22
|
+
|
|
23
|
+
function load(cwd) {
|
|
24
|
+
if (_instance) return _instance;
|
|
25
|
+
_instance = defaults();
|
|
26
|
+
const yamlPath = path.join(cwd || process.cwd(), 'trackfw.yaml');
|
|
27
|
+
if (!fs.existsSync(yamlPath)) return _instance;
|
|
28
|
+
const content = fs.readFileSync(yamlPath, 'utf8');
|
|
29
|
+
parse(content, _instance);
|
|
30
|
+
return _instance;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function reset() {
|
|
34
|
+
_instance = null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function parse(content, cfg) {
|
|
38
|
+
const lines = content.split('\n');
|
|
39
|
+
let inAdrDirs = false;
|
|
40
|
+
let inAgents = false;
|
|
41
|
+
let adrDirs = [];
|
|
42
|
+
let agents = [];
|
|
43
|
+
|
|
44
|
+
for (const rawLine of lines) {
|
|
45
|
+
const line = rawLine.trim();
|
|
46
|
+
|
|
47
|
+
if (inAdrDirs) {
|
|
48
|
+
if (line.startsWith('- ')) {
|
|
49
|
+
adrDirs.push(line.slice(2).trim());
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
inAdrDirs = false;
|
|
53
|
+
if (adrDirs.length) cfg.adrDirs = adrDirs;
|
|
54
|
+
}
|
|
55
|
+
if (inAgents) {
|
|
56
|
+
if (line.startsWith('- ')) {
|
|
57
|
+
agents.push(line.slice(2).trim());
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
inAgents = false;
|
|
61
|
+
if (agents.length) cfg.agents = agents;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const colonIdx = line.indexOf(':');
|
|
65
|
+
if (colonIdx < 0) continue;
|
|
66
|
+
const key = line.slice(0, colonIdx).trim();
|
|
67
|
+
const val = line.slice(colonIdx + 1).trim();
|
|
68
|
+
if (!key) continue;
|
|
69
|
+
|
|
70
|
+
switch (key) {
|
|
71
|
+
case 'adr_dirs': inAdrDirs = true; adrDirs = []; break;
|
|
72
|
+
case 'req_dir': cfg.reqDir = val; break;
|
|
73
|
+
case 'roadmap_dir': cfg.roadmapDir = val; break;
|
|
74
|
+
case 'roadmap_namespacing': cfg.roadmapNamespacing = val; break;
|
|
75
|
+
case 'agents': inAgents = true; agents = []; break;
|
|
76
|
+
case 'governance_mode': cfg.governanceMode = val; break;
|
|
77
|
+
case 'lenient_until': cfg.lenientUntil = val; break;
|
|
78
|
+
case 'wip_limit': { const n = parseInt(val, 10); if (n > 0) cfg.wipLimit = n; break; }
|
|
79
|
+
case 'wip_by_squad': cfg.wipBySquad = val === 'true'; break;
|
|
80
|
+
case 'require_req_in_commit': cfg.requireReqInCommit = val === 'true'; break;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// flush pending lists at EOF
|
|
85
|
+
if (inAdrDirs && adrDirs.length) cfg.adrDirs = adrDirs;
|
|
86
|
+
if (inAgents && agents.length) cfg.agents = agents;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const NAMESPACING_FLAT = 'flat'
|
|
90
|
+
const NAMESPACING_BY_AGENT = 'by_agent'
|
|
91
|
+
|
|
92
|
+
module.exports = { load, reset, defaults, NAMESPACING_FLAT, NAMESPACING_BY_AGENT };
|
package/src/generators/adr.js
CHANGED
|
@@ -40,11 +40,12 @@ function today() {
|
|
|
40
40
|
* @returns {Promise<void>}
|
|
41
41
|
*/
|
|
42
42
|
async function newADR(content) {
|
|
43
|
-
|
|
43
|
+
const adrDir = require('../config').load().adrDirs[0]
|
|
44
|
+
fs.mkdirSync(adrDir, { recursive: true })
|
|
44
45
|
|
|
45
46
|
const slug = toSlug(content.title)
|
|
46
47
|
const date = today()
|
|
47
|
-
const filename =
|
|
48
|
+
const filename = `${adrDir}/ADR-${date}-${slug}.md`
|
|
48
49
|
|
|
49
50
|
const contextSection = content.context || '<!-- What is the situation that motivates this decision? -->'
|
|
50
51
|
const decisionSection = content.decision || '<!-- What was decided? -->'
|
|
@@ -129,10 +130,10 @@ function parseADRStatus(filepath) {
|
|
|
129
130
|
* @returns {Promise<string>} basename do arquivo criado
|
|
130
131
|
*/
|
|
131
132
|
async function newADRDraft(slug) {
|
|
132
|
-
|
|
133
|
+
const adrDir = require('../config').load().adrDirs[0]
|
|
134
|
+
fs.mkdirSync(adrDir, { recursive: true })
|
|
133
135
|
|
|
134
136
|
// Verificar idempotência: buscar arquivo existente com o mesmo slug
|
|
135
|
-
const adrDir = 'docs/adr'
|
|
136
137
|
const existing = fs.existsSync(adrDir)
|
|
137
138
|
? fs.readdirSync(adrDir).find((f) => f.match(new RegExp(`^ADR-.*-${slug}\\.md$`)))
|
|
138
139
|
: null
|
|
@@ -144,7 +145,7 @@ async function newADRDraft(slug) {
|
|
|
144
145
|
|
|
145
146
|
const date = today()
|
|
146
147
|
const filename = `ADR-${date}-${slug}.md`
|
|
147
|
-
const filepath = path.join(
|
|
148
|
+
const filepath = path.join(adrDir, filename)
|
|
148
149
|
const title = slugToTitle(slug)
|
|
149
150
|
|
|
150
151
|
const body = `# ADR: ${title}
|
package/src/generators/init.js
CHANGED
|
@@ -29,6 +29,7 @@ async function scaffold(cfg) {
|
|
|
29
29
|
generateCIWorkflow(cfg)
|
|
30
30
|
generateGitHooks(cfg)
|
|
31
31
|
generateClaudeMD(cfg)
|
|
32
|
+
if (cfg.backend === 'java') generatePomXml(cfg)
|
|
32
33
|
generateClaudeCommands()
|
|
33
34
|
}
|
|
34
35
|
|
|
@@ -43,9 +44,17 @@ function writeTrackfwConfig(cfg) {
|
|
|
43
44
|
|
|
44
45
|
frontend: ${cfg.frontend || ''}
|
|
45
46
|
backend: ${cfg.backend || ''}
|
|
47
|
+
backend_framework: ${cfg.backendFramework || ''}
|
|
46
48
|
pkg_manager: ${cfg.pkgManager || ''}
|
|
47
49
|
hooks: ${cfg.hooks || ''}
|
|
48
50
|
ci: ${cfg.ci || ''}
|
|
51
|
+
|
|
52
|
+
# governance paths (edit to match your project structure)
|
|
53
|
+
adr_dirs:
|
|
54
|
+
- docs/adr
|
|
55
|
+
req_dir: docs/req
|
|
56
|
+
roadmap_dir: docs/roadmaps
|
|
57
|
+
roadmap_namespacing: flat
|
|
49
58
|
`
|
|
50
59
|
fs.writeFileSync('trackfw.yaml', content, 'utf8')
|
|
51
60
|
console.log(' ✓ trackfw.yaml')
|
|
@@ -193,6 +202,63 @@ function generateLefthookHook() {
|
|
|
193
202
|
console.log(' ✓ lefthook.yml')
|
|
194
203
|
}
|
|
195
204
|
|
|
205
|
+
// ---------------------------------------------------------------------------
|
|
206
|
+
// pom.xml (Java / Spring Boot)
|
|
207
|
+
// ---------------------------------------------------------------------------
|
|
208
|
+
|
|
209
|
+
function generatePomXml(cfg) {
|
|
210
|
+
const slug = cfg.projectName
|
|
211
|
+
? cfg.projectName.toLowerCase().replace(/[^a-z0-9-]/g, '-').replace(/-+/g, '-').replace(/^-|-$/g, '')
|
|
212
|
+
: 'my-app'
|
|
213
|
+
const name = cfg.projectName || 'My App'
|
|
214
|
+
const content = `<?xml version="1.0" encoding="UTF-8"?>
|
|
215
|
+
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
|
216
|
+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
217
|
+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
|
218
|
+
<modelVersion>4.0.0</modelVersion>
|
|
219
|
+
<parent>
|
|
220
|
+
<groupId>org.springframework.boot</groupId>
|
|
221
|
+
<artifactId>spring-boot-starter-parent</artifactId>
|
|
222
|
+
<version>3.3.0</version>
|
|
223
|
+
<relativePath/>
|
|
224
|
+
</parent>
|
|
225
|
+
<groupId>com.example</groupId>
|
|
226
|
+
<artifactId>${slug}</artifactId>
|
|
227
|
+
<version>0.0.1-SNAPSHOT</version>
|
|
228
|
+
<name>${name}</name>
|
|
229
|
+
<description>${name} — generated by trackfw</description>
|
|
230
|
+
<properties>
|
|
231
|
+
<java.version>21</java.version>
|
|
232
|
+
</properties>
|
|
233
|
+
<dependencies>
|
|
234
|
+
<dependency>
|
|
235
|
+
<groupId>org.springframework.boot</groupId>
|
|
236
|
+
<artifactId>spring-boot-starter-web</artifactId>
|
|
237
|
+
</dependency>
|
|
238
|
+
<dependency>
|
|
239
|
+
<groupId>org.springframework.boot</groupId>
|
|
240
|
+
<artifactId>spring-boot-starter-actuator</artifactId>
|
|
241
|
+
</dependency>
|
|
242
|
+
<dependency>
|
|
243
|
+
<groupId>org.springframework.boot</groupId>
|
|
244
|
+
<artifactId>spring-boot-starter-test</artifactId>
|
|
245
|
+
<scope>test</scope>
|
|
246
|
+
</dependency>
|
|
247
|
+
</dependencies>
|
|
248
|
+
<build>
|
|
249
|
+
<plugins>
|
|
250
|
+
<plugin>
|
|
251
|
+
<groupId>org.springframework.boot</groupId>
|
|
252
|
+
<artifactId>spring-boot-maven-plugin</artifactId>
|
|
253
|
+
</plugin>
|
|
254
|
+
</plugins>
|
|
255
|
+
</build>
|
|
256
|
+
</project>
|
|
257
|
+
`
|
|
258
|
+
fs.writeFileSync('pom.xml', content, 'utf8')
|
|
259
|
+
console.log(' ✓ pom.xml')
|
|
260
|
+
}
|
|
261
|
+
|
|
196
262
|
// ---------------------------------------------------------------------------
|
|
197
263
|
// CLAUDE.md
|
|
198
264
|
// ---------------------------------------------------------------------------
|
package/src/generators/req.js
CHANGED
|
@@ -69,11 +69,12 @@ function toSlug(s) {
|
|
|
69
69
|
* @returns {Promise<void>}
|
|
70
70
|
*/
|
|
71
71
|
async function newREQ(content) {
|
|
72
|
-
|
|
72
|
+
const reqDir = require('../config').load().reqDir
|
|
73
|
+
fs.mkdirSync(reqDir, { recursive: true })
|
|
73
74
|
|
|
74
75
|
const slug = toSlug(content.title)
|
|
75
76
|
const date = new Date().toISOString().slice(0, 10)
|
|
76
|
-
const filename =
|
|
77
|
+
const filename = `${reqDir}/REQ-${date}-${slug}.md`
|
|
77
78
|
|
|
78
79
|
const motivationSection = content.motivation || '<!-- Why is this requirement needed? What problem does it solve? -->'
|
|
79
80
|
const criteriaSection = content.criteria || '- [ ]\n- [ ]'
|