trackfw 1.0.3 → 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 +4 -0
- package/package.json +10 -13
- 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/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
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const fs = require('fs')
|
|
4
|
+
const path = require('path')
|
|
5
|
+
|
|
6
|
+
const STALE_WIP_DAYS = 7
|
|
7
|
+
|
|
8
|
+
// listDir retorna array de nomes de arquivo (não-diretórios) em dir.
|
|
9
|
+
// Retorna [] se o diretório não existir.
|
|
10
|
+
function listDir(dir) {
|
|
11
|
+
try {
|
|
12
|
+
return fs.readdirSync(dir).filter(name => {
|
|
13
|
+
try {
|
|
14
|
+
return !fs.statSync(path.join(dir, name)).isDirectory()
|
|
15
|
+
} catch (_) {
|
|
16
|
+
return false
|
|
17
|
+
}
|
|
18
|
+
})
|
|
19
|
+
} catch (_) {
|
|
20
|
+
return []
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// parseBlockedADRs extrai basenames de ADRs da seção "## Blocked by ADRs" de um arquivo REQ.
|
|
25
|
+
function parseBlockedADRs(filePath) {
|
|
26
|
+
let content
|
|
27
|
+
try {
|
|
28
|
+
content = fs.readFileSync(filePath, 'utf8')
|
|
29
|
+
} catch (_) {
|
|
30
|
+
return []
|
|
31
|
+
}
|
|
32
|
+
const lines = content.split('\n')
|
|
33
|
+
const adrs = []
|
|
34
|
+
let inSection = false
|
|
35
|
+
for (const line of lines) {
|
|
36
|
+
if (line === '## Blocked by ADRs') {
|
|
37
|
+
inSection = true
|
|
38
|
+
continue
|
|
39
|
+
}
|
|
40
|
+
if (inSection) {
|
|
41
|
+
if (line.startsWith('## ')) break
|
|
42
|
+
if (line.startsWith('- ')) {
|
|
43
|
+
const item = line.slice(2).trim()
|
|
44
|
+
const parts = item.split(/\s+/)
|
|
45
|
+
if (parts.length > 0 && parts[0].endsWith('.md')) {
|
|
46
|
+
adrs.push(parts[0])
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return adrs
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// adrIsDraft verifica se docs/adr/<basename> contém "Status: Draft".
|
|
55
|
+
function adrIsDraft(basename) {
|
|
56
|
+
try {
|
|
57
|
+
const content = fs.readFileSync(path.join('docs', 'adr', basename), 'utf8')
|
|
58
|
+
return content.includes('Status: Draft')
|
|
59
|
+
} catch (_) {
|
|
60
|
+
return false
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// validateWIPHasREQ — roadmaps em docs/roadmaps/wip/ sem "REQ:" no conteúdo → violation
|
|
65
|
+
function validateWIPHasREQ() {
|
|
66
|
+
const entries = listDir('docs/roadmaps/wip')
|
|
67
|
+
const violations = []
|
|
68
|
+
for (const name of entries) {
|
|
69
|
+
try {
|
|
70
|
+
const content = fs.readFileSync(path.join('docs/roadmaps/wip', name), 'utf8')
|
|
71
|
+
if (!content.includes('REQ:') || content.includes('REQ: \n')) {
|
|
72
|
+
violations.push(`roadmap "${name}" is in wip but has no linked REQ`)
|
|
73
|
+
}
|
|
74
|
+
} catch (_) {
|
|
75
|
+
// ignorar erro de leitura
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return violations
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// validateREQsHaveADR — REQs em docs/req/ sem "ADR:" no conteúdo → violation
|
|
82
|
+
function validateREQsHaveADR() {
|
|
83
|
+
const entries = listDir('docs/req')
|
|
84
|
+
const violations = []
|
|
85
|
+
for (const name of entries) {
|
|
86
|
+
try {
|
|
87
|
+
const content = fs.readFileSync(path.join('docs/req', name), 'utf8')
|
|
88
|
+
if (!content.includes('ADR:') || content.includes('ADR: \n')) {
|
|
89
|
+
violations.push(`req "${name}" has no linked ADR`)
|
|
90
|
+
}
|
|
91
|
+
} catch (_) {
|
|
92
|
+
// ignorar
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return violations
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// validateBlockedHasREQ — roadmaps em docs/roadmaps/blocked/ sem "REQ:" → violation
|
|
99
|
+
function validateBlockedHasREQ() {
|
|
100
|
+
const entries = listDir('docs/roadmaps/blocked')
|
|
101
|
+
const violations = []
|
|
102
|
+
for (const name of entries) {
|
|
103
|
+
try {
|
|
104
|
+
const content = fs.readFileSync(path.join('docs/roadmaps/blocked', name), 'utf8')
|
|
105
|
+
if (!content.includes('REQ:') || content.includes('REQ: \n')) {
|
|
106
|
+
violations.push(`roadmap "${name}" is in blocked but has no linked REQ`)
|
|
107
|
+
}
|
|
108
|
+
} catch (_) {
|
|
109
|
+
// ignorar
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return violations
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// validateREQsHaveRoadmap — REQs sem "Roadmap:" → violation
|
|
116
|
+
function validateREQsHaveRoadmap() {
|
|
117
|
+
const entries = listDir('docs/req')
|
|
118
|
+
const violations = []
|
|
119
|
+
for (const name of entries) {
|
|
120
|
+
try {
|
|
121
|
+
const content = fs.readFileSync(path.join('docs/req', name), 'utf8')
|
|
122
|
+
if (!content.includes('Roadmap:') || content.includes('Roadmap: \n')) {
|
|
123
|
+
violations.push(`req "${name}" has no linked Roadmap`)
|
|
124
|
+
}
|
|
125
|
+
} catch (_) {
|
|
126
|
+
// ignorar
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return violations
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// validateADRsAreReferenced — ADRs em docs/adr/ não referenciados em nenhuma REQ → violation
|
|
133
|
+
function validateADRsAreReferenced() {
|
|
134
|
+
const adrs = listDir('docs/adr')
|
|
135
|
+
const reqEntries = listDir('docs/req')
|
|
136
|
+
|
|
137
|
+
let combined = ''
|
|
138
|
+
for (const name of reqEntries) {
|
|
139
|
+
try {
|
|
140
|
+
combined += fs.readFileSync(path.join('docs/req', name), 'utf8')
|
|
141
|
+
} catch (_) {
|
|
142
|
+
// ignorar
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const violations = []
|
|
147
|
+
for (const adr of adrs) {
|
|
148
|
+
if (!combined.includes(adr)) {
|
|
149
|
+
violations.push(`adr "${adr}" is not referenced by any REQ`)
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return violations
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// validateWIPHasAcceptanceCriteria — roadmaps wip sem bloco de critérios de aceite → violation
|
|
156
|
+
function validateWIPHasAcceptanceCriteria() {
|
|
157
|
+
const entries = listDir('docs/roadmaps/wip')
|
|
158
|
+
const violations = []
|
|
159
|
+
for (const name of entries) {
|
|
160
|
+
try {
|
|
161
|
+
const content = fs.readFileSync(path.join('docs/roadmaps/wip', name), 'utf8')
|
|
162
|
+
const hasBlock =
|
|
163
|
+
content.includes('## Acceptance Criteria') ||
|
|
164
|
+
content.includes('## Critérios de Aceite') ||
|
|
165
|
+
content.includes('acceptance criteria') ||
|
|
166
|
+
content.includes('Acceptance Criteria:')
|
|
167
|
+
if (!hasBlock) {
|
|
168
|
+
violations.push(`roadmap "${name}" is in wip but has no acceptance criteria block`)
|
|
169
|
+
}
|
|
170
|
+
} catch (_) {
|
|
171
|
+
// ignorar
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return violations
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// validateSingleWIP — mais de 1 roadmap em wip → warning
|
|
178
|
+
function validateSingleWIP() {
|
|
179
|
+
const entries = listDir('docs/roadmaps/wip')
|
|
180
|
+
if (entries.length > 1) {
|
|
181
|
+
return [`${entries.length} roadmaps in wip/ (recommended: keep only 1 active at a time)`]
|
|
182
|
+
}
|
|
183
|
+
return []
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// validateStaleWIP — roadmaps wip com mtime >= 7 dias → warning
|
|
187
|
+
function validateStaleWIP() {
|
|
188
|
+
let files = []
|
|
189
|
+
try {
|
|
190
|
+
files = fs.readdirSync('docs/roadmaps/wip')
|
|
191
|
+
.filter(f => f.endsWith('.md'))
|
|
192
|
+
.map(f => path.join('docs/roadmaps/wip', f))
|
|
193
|
+
} catch (_) {
|
|
194
|
+
return []
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const warnings = []
|
|
198
|
+
const now = Date.now()
|
|
199
|
+
for (const filePath of files) {
|
|
200
|
+
try {
|
|
201
|
+
const stat = fs.statSync(filePath)
|
|
202
|
+
const ageMs = now - stat.mtimeMs
|
|
203
|
+
const days = Math.floor(ageMs / (1000 * 60 * 60 * 24))
|
|
204
|
+
if (days >= STALE_WIP_DAYS) {
|
|
205
|
+
const lastModified = stat.mtime.toISOString().slice(0, 10)
|
|
206
|
+
const basename = path.basename(filePath)
|
|
207
|
+
warnings.push(
|
|
208
|
+
`roadmap/wip/${basename} has been in WIP for ${days} days (last modified ${lastModified})`
|
|
209
|
+
)
|
|
210
|
+
}
|
|
211
|
+
} catch (_) {
|
|
212
|
+
// ignorar
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return warnings
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// validateREQsNotBlockedByDraftADRs — REQs Open com ADRs Draft na seção "## Blocked by ADRs" → violation
|
|
219
|
+
function validateREQsNotBlockedByDraftADRs() {
|
|
220
|
+
const entries = listDir('docs/req')
|
|
221
|
+
const violations = []
|
|
222
|
+
for (const name of entries) {
|
|
223
|
+
const filePath = path.join('docs/req', name)
|
|
224
|
+
let content
|
|
225
|
+
try {
|
|
226
|
+
content = fs.readFileSync(filePath, 'utf8')
|
|
227
|
+
} catch (_) {
|
|
228
|
+
continue
|
|
229
|
+
}
|
|
230
|
+
if (!content.includes('Status: Open')) continue
|
|
231
|
+
|
|
232
|
+
const blockedADRs = parseBlockedADRs(filePath)
|
|
233
|
+
for (const adrBasename of blockedADRs) {
|
|
234
|
+
if (adrIsDraft(adrBasename)) {
|
|
235
|
+
violations.push(`REQ ${name} is blocked by Draft ADR: ${adrBasename}`)
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
return violations
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// blockedREQs retorna mapa de reqBasename → [adrBasenames Draft] para uso em getStatus()
|
|
243
|
+
function blockedREQs() {
|
|
244
|
+
const entries = listDir('docs/req')
|
|
245
|
+
const result = {}
|
|
246
|
+
for (const name of entries) {
|
|
247
|
+
const filePath = path.join('docs/req', name)
|
|
248
|
+
let content
|
|
249
|
+
try {
|
|
250
|
+
content = fs.readFileSync(filePath, 'utf8')
|
|
251
|
+
} catch (_) {
|
|
252
|
+
continue
|
|
253
|
+
}
|
|
254
|
+
if (!content.includes('Status: Open')) continue
|
|
255
|
+
|
|
256
|
+
const adrNames = parseBlockedADRs(filePath)
|
|
257
|
+
const draftADRs = adrNames.filter(a => adrIsDraft(a))
|
|
258
|
+
if (draftADRs.length > 0) {
|
|
259
|
+
result[name] = draftADRs
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
return result
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// validate executa todas as validações e retorna { violations, warnings }
|
|
266
|
+
async function validate() {
|
|
267
|
+
const violations = [
|
|
268
|
+
...validateWIPHasREQ(),
|
|
269
|
+
...validateREQsHaveADR(),
|
|
270
|
+
...validateBlockedHasREQ(),
|
|
271
|
+
...validateREQsHaveRoadmap(),
|
|
272
|
+
...validateADRsAreReferenced(),
|
|
273
|
+
...validateWIPHasAcceptanceCriteria(),
|
|
274
|
+
...validateREQsNotBlockedByDraftADRs(),
|
|
275
|
+
]
|
|
276
|
+
const warnings = [
|
|
277
|
+
...validateSingleWIP(),
|
|
278
|
+
...validateStaleWIP(),
|
|
279
|
+
]
|
|
280
|
+
return { violations, warnings }
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// getStatus retorna string formatada com o status de governança do projeto
|
|
284
|
+
async function getStatus() {
|
|
285
|
+
const wip = listDir('docs/roadmaps/wip')
|
|
286
|
+
const blocked = listDir('docs/roadmaps/blocked')
|
|
287
|
+
const done = listDir('docs/roadmaps/done')
|
|
288
|
+
|
|
289
|
+
let out = ''
|
|
290
|
+
out += '── trackfw status ──────────────────────\n'
|
|
291
|
+
|
|
292
|
+
out += `\n🔄 WIP (${wip.length})\n`
|
|
293
|
+
for (const f of wip) out += ` ${f}\n`
|
|
294
|
+
|
|
295
|
+
out += `\n❌ Blocked (${blocked.length})\n`
|
|
296
|
+
for (const f of blocked) out += ` ${f}\n`
|
|
297
|
+
|
|
298
|
+
const staleWIPs = validateStaleWIP()
|
|
299
|
+
if (staleWIPs.length > 0) {
|
|
300
|
+
out += `\n⚠ Stale WIP (${staleWIPs.length})\n`
|
|
301
|
+
for (const w of staleWIPs) out += ` ${w}\n`
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
const blockedByDraft = blockedREQs()
|
|
305
|
+
const blockedKeys = Object.keys(blockedByDraft)
|
|
306
|
+
if (blockedKeys.length > 0) {
|
|
307
|
+
out += `\n⏳ REQs blocked by Draft ADRs (${blockedKeys.length})\n`
|
|
308
|
+
for (const reqFile of blockedKeys) {
|
|
309
|
+
out += ` ${reqFile}\n`
|
|
310
|
+
for (const adr of blockedByDraft[reqFile]) {
|
|
311
|
+
out += ` → ${adr} (Draft)\n`
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
out += `\n✅ Done (last 5)\n`
|
|
317
|
+
const last5 = done.length > 5 ? done.slice(done.length - 5) : done
|
|
318
|
+
for (const f of last5) out += ` ${f}\n`
|
|
319
|
+
|
|
320
|
+
out += '\n────────────────────────────────────────\n'
|
|
321
|
+
return out
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
module.exports = {
|
|
325
|
+
validate,
|
|
326
|
+
getStatus,
|
|
327
|
+
// exportadas para testes unitários
|
|
328
|
+
validateWIPHasREQ,
|
|
329
|
+
validateREQsHaveADR,
|
|
330
|
+
validateBlockedHasREQ,
|
|
331
|
+
validateREQsHaveRoadmap,
|
|
332
|
+
validateADRsAreReferenced,
|
|
333
|
+
validateWIPHasAcceptanceCriteria,
|
|
334
|
+
validateSingleWIP,
|
|
335
|
+
validateStaleWIP,
|
|
336
|
+
validateREQsNotBlockedByDraftADRs,
|
|
337
|
+
parseBlockedADRs,
|
|
338
|
+
adrIsDraft,
|
|
339
|
+
listDir,
|
|
340
|
+
}
|
package/bin/.gitkeep
DELETED
|
File without changes
|
package/bin/README.md
DELETED
|
@@ -1,301 +0,0 @@
|
|
|
1
|
-
# trackfw
|
|
2
|
-
|
|
3
|
-
> Governed software delivery — ADR → REQ → ROADMAP → backlog / wip / done
|
|
4
|
-
|
|
5
|
-
[](https://github.com/kgsaran/trackfw/releases/latest)
|
|
6
|
-
[](go.mod)
|
|
7
|
-
[](LICENSE)
|
|
8
|
-
|
|
9
|
-
**trackfw** is an open-source CLI that enforces a traceable chain from architectural decision to shipped code — without SaaS, accounts, or databases. Markdown files are state.
|
|
10
|
-
|
|
11
|
-
```
|
|
12
|
-
ADR → REQ → ROADMAP → backlog / wip / blocked / done / abandoned
|
|
13
|
-
```
|
|
14
|
-
|
|
15
|
-
Every piece of work traces back to a decision. Every decision links to a requirement. Every requirement lands in a roadmap. No orphan work, no undocumented choices.
|
|
16
|
-
|
|
17
|
-
---
|
|
18
|
-
|
|
19
|
-
## The problem
|
|
20
|
-
|
|
21
|
-
Most teams accumulate technical debt not because they lack tools, but because they lack **governance traceability**. Decisions are made in Slack. Requirements live in someone's head. Roadmaps drift from what was actually shipped.
|
|
22
|
-
|
|
23
|
-
- **ADR tools** manage decision records, but don't connect them to delivery.
|
|
24
|
-
- **Kanban tools** track tasks, but don't enforce that tasks are backed by a decision.
|
|
25
|
-
- **CI tools** validate code, but don't validate governance.
|
|
26
|
-
|
|
27
|
-
trackfw closes the loop — connective tissue between *why*, *what*, and *when*.
|
|
28
|
-
|
|
29
|
-
---
|
|
30
|
-
|
|
31
|
-
## Demo
|
|
32
|
-
|
|
33
|
-

|
|
34
|
-
|
|
35
|
-
```bash
|
|
36
|
-
$ trackfw req new "Login screen"
|
|
37
|
-
|
|
38
|
-
? Describe what you want to build Login screen for the application
|
|
39
|
-
? Motivation Users need to authenticate to access the system
|
|
40
|
-
|
|
41
|
-
Detected domains: authentication, ui
|
|
42
|
-
|
|
43
|
-
? How will users authenticate?
|
|
44
|
-
> Local login (email + password)
|
|
45
|
-
SSO (Google, Azure AD, Okta...)
|
|
46
|
-
Not decided yet ← generates ADR draft
|
|
47
|
-
|
|
48
|
-
? Is there an existing UI framework or design system?
|
|
49
|
-
Yes, already chosen
|
|
50
|
-
> No, need to choose a UI framework ← generates ADR draft
|
|
51
|
-
|
|
52
|
-
ADR drafts created:
|
|
53
|
-
→ ADR-2026-06-12-authentication-strategy.md
|
|
54
|
-
→ ADR-2026-06-12-ui-framework.md
|
|
55
|
-
|
|
56
|
-
Resolve these ADRs (set Status: Accepted) before creating a roadmap.
|
|
57
|
-
created docs/req/REQ-2026-06-12-login-screen.md
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
---
|
|
61
|
-
|
|
62
|
-
## Installation
|
|
63
|
-
|
|
64
|
-
### macOS / Linux — curl
|
|
65
|
-
|
|
66
|
-
```bash
|
|
67
|
-
curl -sSfL https://github.com/kgsaran/trackfw/releases/latest/download/install.sh | sh
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
### Homebrew
|
|
71
|
-
|
|
72
|
-
```bash
|
|
73
|
-
brew install kgsaran/tap/trackfw
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
### Go
|
|
77
|
-
|
|
78
|
-
```bash
|
|
79
|
-
go install github.com/kgsaran/trackfw/cmd/trackfw@latest
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
### npm
|
|
83
|
-
|
|
84
|
-
```bash
|
|
85
|
-
npm install -g trackfw
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
### pip
|
|
89
|
-
|
|
90
|
-
```bash
|
|
91
|
-
pip install trackfw
|
|
92
|
-
```
|
|
93
|
-
|
|
94
|
-
---
|
|
95
|
-
|
|
96
|
-
## Quick start
|
|
97
|
-
|
|
98
|
-
```bash
|
|
99
|
-
# 1. Set up governance in your project (interactive wizard)
|
|
100
|
-
trackfw init
|
|
101
|
-
|
|
102
|
-
# 2. Document an architectural decision
|
|
103
|
-
trackfw adr new "Use PostgreSQL as primary database"
|
|
104
|
-
|
|
105
|
-
# 3. Create a requirement — wizard detects domains and proposes ADR drafts
|
|
106
|
-
trackfw req new "User authentication"
|
|
107
|
-
|
|
108
|
-
# 4. Once ADRs are accepted, plan the work
|
|
109
|
-
trackfw roadmap new "Auth service"
|
|
110
|
-
|
|
111
|
-
# 5. Check governance health
|
|
112
|
-
trackfw validate
|
|
113
|
-
|
|
114
|
-
# 6. See what is in flight
|
|
115
|
-
trackfw status
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
---
|
|
119
|
-
|
|
120
|
-
## Commands
|
|
121
|
-
|
|
122
|
-
| Command | Description |
|
|
123
|
-
|---|---|
|
|
124
|
-
| `trackfw init` | Interactive wizard — scaffolds governance + AI integrations |
|
|
125
|
-
| `trackfw adr new "title"` | Create a new Architecture Decision Record |
|
|
126
|
-
| `trackfw adr list` | List all ADRs with status |
|
|
127
|
-
| `trackfw req new "title"` | Create a REQ with guided ADR discovery |
|
|
128
|
-
| `trackfw req list` | List all REQs with status |
|
|
129
|
-
| `trackfw roadmap new "title"` | Create a roadmap in `backlog/` |
|
|
130
|
-
| `trackfw roadmap move <name> <state>` | Move roadmap between states |
|
|
131
|
-
| `trackfw roadmap list` | List all roadmaps grouped by state |
|
|
132
|
-
| `trackfw validate` | Check governance consistency (use as CI gate) |
|
|
133
|
-
| `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 |
|
|
140
|
-
| `trackfw version` | Print version |
|
|
141
|
-
|
|
142
|
-
---
|
|
143
|
-
|
|
144
|
-
## Governance chain
|
|
145
|
-
|
|
146
|
-
| Layer | Artifact | Purpose |
|
|
147
|
-
|---|---|---|
|
|
148
|
-
| Decide | `ADR` | Document the *why* behind a technical decision |
|
|
149
|
-
| Specify | `REQ` | Define *what* needs to be delivered, linked to an ADR |
|
|
150
|
-
| Plan | `ROADMAP` | Break the requirement into microbatches with acceptance criteria |
|
|
151
|
-
| Execute | `backlog → wip → done` | Folder position is the source of truth |
|
|
152
|
-
|
|
153
|
-
### Roadmap states
|
|
154
|
-
|
|
155
|
-
```
|
|
156
|
-
docs/roadmaps/
|
|
157
|
-
├── backlog/ queued, not started
|
|
158
|
-
├── wip/ actively being worked on (one at a time)
|
|
159
|
-
├── blocked/ waiting on a dependency or decision
|
|
160
|
-
├── done/ completed and validated
|
|
161
|
-
└── abandoned/ discontinued — reason required in file
|
|
162
|
-
```
|
|
163
|
-
|
|
164
|
-
Moving a file between folders **is** the state transition. No database, no API.
|
|
165
|
-
|
|
166
|
-
---
|
|
167
|
-
|
|
168
|
-
## REQ-driven ADR discovery
|
|
169
|
-
|
|
170
|
-
When you run `trackfw req new`, the wizard analyzes your intent and asks targeted questions for each detected domain — authentication, UI, persistence, API, deploy, events. Unanswered architectural decisions become ADR drafts automatically.
|
|
171
|
-
|
|
172
|
-
```
|
|
173
|
-
trackfw req new "checkout flow with payment integration"
|
|
174
|
-
```
|
|
175
|
-
|
|
176
|
-
Detected domains: **persistence**, **api**, **events**
|
|
177
|
-
|
|
178
|
-
Questions asked:
|
|
179
|
-
- Which database engine will be used? → *Not decided yet* → `ADR: database-engine (Draft)`
|
|
180
|
-
- Which API protocol will be used? → *REST (already decided)* → no ADR
|
|
181
|
-
- Which event broker will be used? → *Not decided yet* → `ADR: event-broker (Draft)`
|
|
182
|
-
|
|
183
|
-
The REQ is linked to its blocking ADRs. `trackfw validate` enforces that no roadmap is created until every linked ADR reaches `Accepted` status.
|
|
184
|
-
|
|
185
|
-
This is the difference between experienced architects (who know which decisions to make) and everyone else — trackfw brings the architectural checklist to the requirement.
|
|
186
|
-
|
|
187
|
-
---
|
|
188
|
-
|
|
189
|
-
## `trackfw validate` — governance gate
|
|
190
|
-
|
|
191
|
-
```bash
|
|
192
|
-
$ trackfw validate
|
|
193
|
-
|
|
194
|
-
✗ REQ-2026-06-12-login-screen.md is blocked by Draft ADR: ADR-authentication-strategy.md
|
|
195
|
-
✗ roadmap/wip/auth-service.md has no linked REQ
|
|
196
|
-
⚠ 2 roadmaps in wip/ (recommended: 1)
|
|
197
|
-
|
|
198
|
-
2 violation(s) found
|
|
199
|
-
```
|
|
200
|
-
|
|
201
|
-
Designed to run as a **pre-commit hook** and a **CI quality gate**. `trackfw init` wires both automatically for your stack.
|
|
202
|
-
|
|
203
|
-
---
|
|
204
|
-
|
|
205
|
-
## `trackfw status` — current state at a glance
|
|
206
|
-
|
|
207
|
-
```bash
|
|
208
|
-
$ trackfw status
|
|
209
|
-
|
|
210
|
-
── trackfw status ──────────────────────
|
|
211
|
-
|
|
212
|
-
🔄 WIP (1)
|
|
213
|
-
roadmap-auth-service.md
|
|
214
|
-
|
|
215
|
-
❌ Blocked (0)
|
|
216
|
-
|
|
217
|
-
⏳ REQs blocked by Draft ADRs (1)
|
|
218
|
-
REQ-2026-06-12-login-screen.md
|
|
219
|
-
→ ADR-2026-06-12-authentication-strategy.md (Draft)
|
|
220
|
-
|
|
221
|
-
✅ Done (last 5)
|
|
222
|
-
roadmap-user-profile.md
|
|
223
|
-
roadmap-db-setup.md
|
|
224
|
-
```
|
|
225
|
-
|
|
226
|
-
---
|
|
227
|
-
|
|
228
|
-
## AI assistant integration
|
|
229
|
-
|
|
230
|
-
`trackfw init` asks which AI tools your team uses and installs native governance context for each. Commands can also be run independently.
|
|
231
|
-
|
|
232
|
-
| Command | Installs | Format |
|
|
233
|
-
|---|---|---|
|
|
234
|
-
| `trackfw agents` | 10 subagents in `~/.claude/agents/` | Claude Code `.md` with frontmatter |
|
|
235
|
-
| `trackfw gemini` | GEMINI.md + 10 skills + 3 commands | `~/.gemini/` + project root |
|
|
236
|
-
| `trackfw cursor` | 10 rules in `.cursor/rules/` | `.mdc` with YAML frontmatter |
|
|
237
|
-
| `trackfw copilot` | `copilot-instructions.md` + 10 instructions + 10 prompts | `.github/` |
|
|
238
|
-
| `trackfw windsurf` | 10 rules + workflows in `.windsurf/` + global rules | Appends to `~/.codeium/windsurf/memories/` |
|
|
239
|
-
| `trackfw amazonq` | 10 rules in `.amazonq/rules/` | Plain Markdown |
|
|
240
|
-
|
|
241
|
-
Each installer is idempotent — running it twice never overwrites your customizations.
|
|
242
|
-
|
|
243
|
-
The 10 roles installed for each tool: **architect · backend · frontend · qa · infra · security · code-quality · dba · ux · data**
|
|
244
|
-
|
|
245
|
-
---
|
|
246
|
-
|
|
247
|
-
## `trackfw init` — stack-aware scaffolding
|
|
248
|
-
|
|
249
|
-
```
|
|
250
|
-
? Project type? Full-stack / Frontend / Backend / Governance only
|
|
251
|
-
? Frontend stack? React / Vue / Angular
|
|
252
|
-
? Backend stack? Go / Java / Node / Python
|
|
253
|
-
? Package manager? npm / pnpm / yarn / bun
|
|
254
|
-
? Git hooks? husky / lefthook / none
|
|
255
|
-
? CI system? GitHub Actions / GitLab CI / none
|
|
256
|
-
? Which AI assistants? Claude Code / Gemini CLI / Cursor / Copilot / Windsurf / Amazon Q
|
|
257
|
-
```
|
|
258
|
-
|
|
259
|
-
The governance structure (`docs/adr/`, `docs/req/`, `docs/roadmaps/`) is always identical — stack-agnostic. The generated hooks, workflows, and AI integrations adapt to your answers.
|
|
260
|
-
|
|
261
|
-
---
|
|
262
|
-
|
|
263
|
-
## Design principles
|
|
264
|
-
|
|
265
|
-
1. **Files are state** — folder position is the source of truth. No database, no lock-in.
|
|
266
|
-
2. **Traceability is mandatory** — `validate` is a gate, not a suggestion.
|
|
267
|
-
3. **Framework-agnostic, integration-aware** — governance never changes; generated artifacts adapt to your stack.
|
|
268
|
-
4. **One active roadmap at a time** — parallel work without traceability is the root of most delivery chaos.
|
|
269
|
-
5. **Human-readable, machine-parseable** — every artifact is a Markdown file with a predictable structure.
|
|
270
|
-
6. **Guided, not prescriptive** — the wizard surfaces decisions you might not know to ask; it never blocks work unnecessarily.
|
|
271
|
-
|
|
272
|
-
---
|
|
273
|
-
|
|
274
|
-
## What trackfw is not
|
|
275
|
-
|
|
276
|
-
- Not a project management SaaS — no UI, no accounts, no cloud sync
|
|
277
|
-
- Not a replacement for Git history — it complements, not duplicates
|
|
278
|
-
- Not a task tracker — use GitHub Issues, Linear, or Jira for tasks; trackfw governs the *why*
|
|
279
|
-
- Not opinionated about how you write code — only about how you document decisions
|
|
280
|
-
|
|
281
|
-
---
|
|
282
|
-
|
|
283
|
-
## Contributing
|
|
284
|
-
|
|
285
|
-
```bash
|
|
286
|
-
git clone https://github.com/kgsaran/trackfw
|
|
287
|
-
cd trackfw
|
|
288
|
-
make build # compiles to bin/trackfw
|
|
289
|
-
make test # go test ./...
|
|
290
|
-
make lint # go vet ./...
|
|
291
|
-
```
|
|
292
|
-
|
|
293
|
-
Generators are the stack-specific components — you can add support for a new stack without touching core logic. See `internal/generators/` for examples.
|
|
294
|
-
|
|
295
|
-
Issues and pull requests welcome at [github.com/kgsaran/trackfw](https://github.com/kgsaran/trackfw).
|
|
296
|
-
|
|
297
|
-
---
|
|
298
|
-
|
|
299
|
-
## License
|
|
300
|
-
|
|
301
|
-
MIT — see [LICENSE](LICENSE)
|
package/bin/trackfw-darwin-amd64
DELETED
|
Binary file
|
package/bin/trackfw-darwin-arm64
DELETED
|
Binary file
|
package/bin/trackfw-linux-amd64
DELETED
|
Binary file
|
package/bin/trackfw-linux-arm64
DELETED
|
Binary file
|
|
Binary file
|