trackfw 7.6.0 → 8.0.0-rc2
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 +28 -11
- package/bin/trackfw.js +81 -0
- package/package.json +14 -8
- package/bin/trackfw +0 -41
- package/src/branch/prune.js +0 -450
- package/src/branch/runner.js +0 -153
- package/src/changelog/index.js +0 -114
- package/src/commands/adr.js +0 -56
- package/src/commands/agents.js +0 -3
- package/src/commands/audit-surface.js +0 -520
- package/src/commands/barrier.js +0 -822
- package/src/commands/baseline.js +0 -13
- package/src/commands/branch.js +0 -98
- package/src/commands/changelog.js +0 -29
- package/src/commands/commit.js +0 -66
- package/src/commands/configure.js +0 -106
- package/src/commands/context.js +0 -215
- package/src/commands/discover.js +0 -628
- package/src/commands/doctor.js +0 -66
- package/src/commands/help.js +0 -358
- package/src/commands/identity-wizard.js +0 -258
- package/src/commands/index.js +0 -68
- package/src/commands/init.js +0 -296
- package/src/commands/integrations.js +0 -305
- package/src/commands/log.js +0 -31
- package/src/commands/metrics.js +0 -242
- package/src/commands/note.js +0 -24
- package/src/commands/push.js +0 -51
- package/src/commands/release.js +0 -47
- package/src/commands/req.js +0 -98
- package/src/commands/roadmap.js +0 -44
- package/src/commands/serve.js +0 -345
- package/src/commands/ship.js +0 -58
- package/src/commands/skills.js +0 -3
- package/src/commands/status.js +0 -12
- package/src/commands/sync.js +0 -348
- package/src/commands/thirdparty.js +0 -390
- package/src/commands/update-harness.js +0 -1002
- package/src/commands/update.js +0 -500
- package/src/commands/validate.js +0 -57
- package/src/commands/version.js +0 -10
- package/src/commit/runner.js +0 -352
- package/src/config/index.js +0 -457
- package/src/forge/adapter.js +0 -189
- package/src/forge/resolve.js +0 -191
- package/src/generators/adr.js +0 -198
- package/src/generators/codex.js +0 -79
- package/src/generators/date.js +0 -23
- package/src/generators/hooks.js +0 -2414
- package/src/generators/init.js +0 -1563
- package/src/generators/note.js +0 -154
- package/src/generators/req.js +0 -436
- package/src/generators/roadmap.js +0 -841
- package/src/homedir.js +0 -30
- package/src/i18n/index.js +0 -53
- package/src/i18n/locales/en-US.json +0 -124
- package/src/i18n/locales/es-ES.json +0 -124
- package/src/i18n/locales/pt-BR.json +0 -124
- package/src/identity/config.js +0 -134
- package/src/identity/index.js +0 -19
- package/src/identity/preset.js +0 -219
- package/src/identity/slug.js +0 -62
- package/src/integrations/assets/agents/architect.md +0 -61
- package/src/integrations/assets/agents/backend.md +0 -41
- package/src/integrations/assets/agents/code-quality.md +0 -48
- package/src/integrations/assets/agents/data.md +0 -41
- package/src/integrations/assets/agents/dba.md +0 -41
- package/src/integrations/assets/agents/frontend.md +0 -41
- package/src/integrations/assets/agents/iac.md +0 -46
- package/src/integrations/assets/agents/infra.md +0 -43
- package/src/integrations/assets/agents/qa.md +0 -41
- package/src/integrations/assets/agents/security.md +0 -51
- package/src/integrations/assets/agents/tooling.md +0 -47
- package/src/integrations/assets/agents/ux.md +0 -48
- package/src/integrations/assets/catalog.json +0 -48
- package/src/integrations/assets/skills/architecture.md +0 -66
- package/src/integrations/assets/skills/backend.md +0 -67
- package/src/integrations/assets/skills/code-quality.md +0 -78
- package/src/integrations/assets/skills/data.md +0 -75
- package/src/integrations/assets/skills/dba.md +0 -77
- package/src/integrations/assets/skills/frontend.md +0 -70
- package/src/integrations/assets/skills/governance.md +0 -6
- package/src/integrations/assets/skills/iac.md +0 -90
- package/src/integrations/assets/skills/implement.md +0 -6
- package/src/integrations/assets/skills/infra.md +0 -62
- package/src/integrations/assets/skills/plan.md +0 -6
- package/src/integrations/assets/skills/qa.md +0 -63
- package/src/integrations/assets/skills/release.md +0 -6
- package/src/integrations/assets/skills/review.md +0 -6
- package/src/integrations/assets/skills/security.md +0 -74
- package/src/integrations/assets/skills/tooling.md +0 -80
- package/src/integrations/assets/skills/ux.md +0 -70
- package/src/integrations/catalog.js +0 -58
- package/src/integrations/doctor.js +0 -0
- package/src/integrations/doctor_remote.js +0 -224
- package/src/integrations/index.js +0 -171
- package/src/integrations/legacy.js +0 -35
- package/src/integrations/manager.js +0 -438
- package/src/integrations/register-agent.js +0 -106
- package/src/integrations/render.js +0 -563
- package/src/integrations/scaffold_doctor.js +0 -395
- package/src/lib/fatal-error.js +0 -64
- package/src/lib/pathfmt.js +0 -43
- package/src/lib/unknown-command.js +0 -82
- package/src/lib/update-engine.js +0 -242
- package/src/push/runner.js +0 -226
- package/src/release/runner.js +0 -474
- package/src/serve/api_attention.js +0 -24
- package/src/serve/api_board.js +0 -105
- package/src/serve/api_chain.js +0 -246
- package/src/serve/api_file.js +0 -117
- package/src/serve/api_metrics.js +0 -139
- package/src/serve/static/app.js +0 -1151
- package/src/serve/static/index.html +0 -338
- package/src/serve/static/style.css +0 -297
- package/src/ship/runner.js +0 -775
- package/src/thirdparty/fetch.js +0 -140
- package/src/thirdparty/markers.js +0 -207
- package/src/thirdparty/provenance.js +0 -114
- package/src/thirdparty/quarantine.js +0 -113
- package/src/thirdparty/references.js +0 -194
- package/src/validator/git-exec.js +0 -72
- package/src/validator/index.js +0 -4086
- package/src/validator/traceid.js +0 -155
package/src/commands/baseline.js
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
'use strict'
|
|
2
|
-
const { Command } = require('commander')
|
|
3
|
-
const { validateUnfiltered, saveBaseline } = require('../validator')
|
|
4
|
-
|
|
5
|
-
const cmd = new Command('baseline')
|
|
6
|
-
cmd.description('Grava snapshot das violations atuais em .trackfw-baseline.json')
|
|
7
|
-
cmd.action(async () => {
|
|
8
|
-
const { violations, warnings } = await validateUnfiltered()
|
|
9
|
-
saveBaseline(violations, warnings)
|
|
10
|
-
console.log(`Baseline gravado: ${violations.length} violations, ${warnings.length} warnings`)
|
|
11
|
-
})
|
|
12
|
-
|
|
13
|
-
module.exports = cmd
|
package/src/commands/branch.js
DELETED
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
'use strict'
|
|
2
|
-
|
|
3
|
-
// trackfw branch new <type>/<slug>
|
|
4
|
-
//
|
|
5
|
-
// CLI wiring for the `trackfw branch` command group. The testable logic lives in
|
|
6
|
-
// ../branch/runner.js (mirrors the ship.js / ship/runner.js split already used in this CLI).
|
|
7
|
-
|
|
8
|
-
const { Command } = require('commander')
|
|
9
|
-
const { runBranchNew } = require('../branch/runner')
|
|
10
|
-
const { runBranchPrune } = require('../branch/prune')
|
|
11
|
-
|
|
12
|
-
function createBranchCommand() {
|
|
13
|
-
const cmd = new Command('branch')
|
|
14
|
-
cmd.description('Manage governed feature branches')
|
|
15
|
-
|
|
16
|
-
const newCmd = new Command('new')
|
|
17
|
-
newCmd
|
|
18
|
-
.description(
|
|
19
|
-
'Create a feat/fix/refactor/chore/docs branch; feat/fix/refactor gated on a matching REQ + roadmap already in wip/ or done/\n\n' +
|
|
20
|
-
'trackfw branch new moves the branch_has_wip_roadmap governance gate (already enforced\n' +
|
|
21
|
-
"by 'trackfw validate' and 'trackfw ship') to before branch creation, instead of after:\n\n" +
|
|
22
|
-
' 1. Validates <type> is one of feat, fix, refactor, chore, docs and <slug> is non-empty.\n' +
|
|
23
|
-
' 2. For feat, fix, refactor: checks whether a roadmap in wip/ or done/ matches the given\n' +
|
|
24
|
-
" slug — the exact matching logic 'trackfw validate' already uses (normalized slug,\n" +
|
|
25
|
-
" filename contains match). Without a match: blocks — 'git checkout -b' is never\n" +
|
|
26
|
-
" executed — and prints the same governance orientation message 'trackfw validate'\n" +
|
|
27
|
-
" already prints for this rule.\n" +
|
|
28
|
-
" 3. For chore, docs: housekeeping types already treated as roadmap-exempt by 'trackfw ship'\n" +
|
|
29
|
-
" and 'trackfw commit' — the branch is created without the roadmap gate.\n" +
|
|
30
|
-
" 4. With a match (or for chore/docs): runs 'git checkout -b <type>/<slug>', propagating\n" +
|
|
31
|
-
" Git's own output and exit status literally.\n\n" +
|
|
32
|
-
'Create the governance artifacts first if this blocks you:\n' +
|
|
33
|
-
' trackfw req new "title" # flat project\n' +
|
|
34
|
-
' trackfw req new --agent <agent> "title" # by_agent project with 2+ agents\n' +
|
|
35
|
-
' trackfw roadmap new "title" # flat project\n' +
|
|
36
|
-
' trackfw roadmap new --agent <agent> "title" # by_agent project with 2+ agents\n' +
|
|
37
|
-
' trackfw roadmap move <name> wip'
|
|
38
|
-
)
|
|
39
|
-
.argument('<spec>', '<type>/<slug>, type in feat, fix, refactor, chore, docs')
|
|
40
|
-
.option('--dry-run', 'Report whether the branch would be created or blocked, without executing git', false)
|
|
41
|
-
.action((spec, options) => {
|
|
42
|
-
const exitCode = runBranchNew(spec, !!options.dryRun, {})
|
|
43
|
-
if (exitCode !== 0) {
|
|
44
|
-
process.exit(exitCode)
|
|
45
|
-
}
|
|
46
|
-
})
|
|
47
|
-
|
|
48
|
-
cmd.addCommand(newCmd)
|
|
49
|
-
|
|
50
|
-
const pruneCmd = new Command('prune')
|
|
51
|
-
pruneCmd
|
|
52
|
-
.description('Report (and, with --apply, delete) local branches already integrated into origin/main')
|
|
53
|
-
.addHelpText(
|
|
54
|
-
'after',
|
|
55
|
-
'\n' +
|
|
56
|
-
'trackfw branch prune automates the "one active branch at a time" check documented in\n' +
|
|
57
|
-
'CLAUDE.md §1 — it does not remove human judgment from every case: a branch whose only\n' +
|
|
58
|
-
'remaining divergence is doc/config files is flagged for manual review, never deleted\n' +
|
|
59
|
-
'automatically.\n\n' +
|
|
60
|
-
"A best-effort 'git fetch origin --prune' runs first. Failure (offline, no remote) is\n" +
|
|
61
|
-
'non-blocking: a warning is printed and evaluation proceeds against the local origin/main\n' +
|
|
62
|
-
'ref, whatever its state. A stale origin/main only ever makes the result MORE conservative —\n' +
|
|
63
|
-
'it can miss a branch that was in fact integrated since the last fetch (reporting it kept\n' +
|
|
64
|
-
'when a fresh fetch would show it deletable), but it never reports one as deletable that a\n' +
|
|
65
|
-
'fresh fetch would show as pending.\n\n' +
|
|
66
|
-
"Decides integration with the touched-files heuristic, NOT git's own ancestry check (which\n" +
|
|
67
|
-
'always refuses squash-merged branches) and NOT a naive bidirectional diff against origin/main\n' +
|
|
68
|
-
'(which false-positives on a branch that is merged but stale, once main has advanced further):\n\n' +
|
|
69
|
-
' mb = git merge-base origin/main <branch>\n' +
|
|
70
|
-
' touched = git diff --name-only mb <branch> (what the branch touched)\n' +
|
|
71
|
-
' diverg = git diff --name-only origin/main <branch> -- touched (what still differs there)\n\n' +
|
|
72
|
-
'touched empty -> integrated (safe to delete)\n' +
|
|
73
|
-
'diverg empty -> integrated (safe to delete) -- squash-merged, stale, main advanced since\n' +
|
|
74
|
-
'diverg doc/config only -> flagged for review (kept; probable housekeeping, confirm and delete manually)\n' +
|
|
75
|
-
'otherwise -> kept, with the diverging files named\n\n' +
|
|
76
|
-
'Every local branch is reported, always, with its decision and reason. The current branch, any\n' +
|
|
77
|
-
'branch checked out in another worktree, and the default branch (main) are always kept and\n' +
|
|
78
|
-
'never evaluated for deletion. Without origin/main resolvable at all (offline with no prior\n' +
|
|
79
|
-
'fetch ever having run, or no remote configured), the whole command refuses and deletes\n' +
|
|
80
|
-
'nothing.\n\n' +
|
|
81
|
-
'--dry-run is the default: without --apply, nothing is ever deleted, even the clearly\n' +
|
|
82
|
-
"integrated. Deletion tries 'git branch -d' first — confirming the integration via git's own\n" +
|
|
83
|
-
"ancestry check too, when possible — and falls back to 'git branch -D' only when -d refuses,\n" +
|
|
84
|
-
'the expected case for squash-merged branches, which never have fast-forward ancestry with main.'
|
|
85
|
-
)
|
|
86
|
-
.option('--apply', 'Actually delete branches decided as integrated (default: report only, delete nothing)', false)
|
|
87
|
-
.action((options) => {
|
|
88
|
-
const exitCode = runBranchPrune(!!options.apply, {})
|
|
89
|
-
if (exitCode !== 0) {
|
|
90
|
-
process.exit(exitCode)
|
|
91
|
-
}
|
|
92
|
-
})
|
|
93
|
-
|
|
94
|
-
cmd.addCommand(pruneCmd)
|
|
95
|
-
return cmd
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
module.exports = createBranchCommand()
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
'use strict'
|
|
2
|
-
const { Command } = require('commander')
|
|
3
|
-
const { read, parseSections, firstSection, findVersion, formatSection } = require('../changelog')
|
|
4
|
-
|
|
5
|
-
const cmd = new Command('changelog')
|
|
6
|
-
cmd.description('Show entries from CHANGELOG.md')
|
|
7
|
-
// enablePositionalOptions escopa a flag local "--version" a este subcomando,
|
|
8
|
-
// evitando que o root a intercepte como a flag global "trackfw --version".
|
|
9
|
-
cmd.enablePositionalOptions()
|
|
10
|
-
cmd.option('--version <x.y.z>', 'Show a specific version section')
|
|
11
|
-
cmd.option('--all', 'Show the entire CHANGELOG.md')
|
|
12
|
-
cmd.action(async (opts) => {
|
|
13
|
-
try {
|
|
14
|
-
const root = process.cwd()
|
|
15
|
-
const content = read(root)
|
|
16
|
-
if (opts.all) {
|
|
17
|
-
process.stdout.write(content)
|
|
18
|
-
return
|
|
19
|
-
}
|
|
20
|
-
const sections = parseSections(content)
|
|
21
|
-
const section = opts.version ? findVersion(sections, opts.version) : firstSection(sections)
|
|
22
|
-
process.stdout.write(formatSection(section))
|
|
23
|
-
} catch (err) {
|
|
24
|
-
console.error(`Error: ${err.message}`)
|
|
25
|
-
process.exitCode = 1
|
|
26
|
-
}
|
|
27
|
-
})
|
|
28
|
-
|
|
29
|
-
module.exports = cmd
|
package/src/commands/commit.js
DELETED
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
'use strict'
|
|
2
|
-
|
|
3
|
-
// trackfw commit -m "<message>"
|
|
4
|
-
//
|
|
5
|
-
// CLI wiring for the `trackfw commit` command. The testable logic lives in
|
|
6
|
-
// ../commit/runner.js (mirrors the branch.js / branch/runner.js split already used in this CLI).
|
|
7
|
-
|
|
8
|
-
const { Command } = require('commander')
|
|
9
|
-
const { runCommit, buildSuggestedMessage } = require('../commit/runner')
|
|
10
|
-
|
|
11
|
-
function createCommitCommand() {
|
|
12
|
-
const cmd = new Command('commit')
|
|
13
|
-
cmd
|
|
14
|
-
.description(
|
|
15
|
-
"trackfw commit commits staged changes directly, but blocks the commit before it\n" +
|
|
16
|
-
'happens when governance is missing, instead of letting it land and only catching it later.\n\n' +
|
|
17
|
-
'Compositional vocabulary:\n' +
|
|
18
|
-
' trackfw commit -m "..." commits\n' +
|
|
19
|
-
' trackfw push pushes\n' +
|
|
20
|
-
' trackfw ship -m "..." commit + push + PR (composition)\n\n' +
|
|
21
|
-
'Behavioral steps:\n\n' +
|
|
22
|
-
" 1. On 'main'/'master': always blocked — commit directly on the default branch is never\n" +
|
|
23
|
-
' permitted.\n' +
|
|
24
|
-
' 2. On a feat/fix/refactor branch: requires a roadmap matching the branch slug already in\n' +
|
|
25
|
-
" wip/ or done/ — the exact matching logic 'trackfw branch new' and 'trackfw validate'\n" +
|
|
26
|
-
' already use. Without a match, blocks with the same governance orientation message.\n' +
|
|
27
|
-
' 3. On any other branch (e.g. doc/housekeeping branches): allowed without requiring a\n' +
|
|
28
|
-
' roadmap — a warning is logged, but the commit proceeds.\n' +
|
|
29
|
-
" 4. When allowed: runs 'git commit -m <message>', propagating Git's own output and exit\n" +
|
|
30
|
-
' status literally.\n\n' +
|
|
31
|
-
"'--suggest' takes a completely separate path: it prints a heuristic Conventional Commits\n" +
|
|
32
|
-
"skeleton built from 'git diff --cached --name-status' (type + staged file list) and exits\n" +
|
|
33
|
-
'without ever committing — no LLM call, just a structural heuristic. It is not a\n' +
|
|
34
|
-
'ready-to-use message; review and edit before using it with -m. When \'--suggest\' is set,\n' +
|
|
35
|
-
"'-m' (if also passed) is ignored and no commit ever happens.\n\n" +
|
|
36
|
-
'Create the governance artifacts first if this blocks you:\n' +
|
|
37
|
-
' trackfw req new "title" # flat project\n' +
|
|
38
|
-
' trackfw req new --agent <agent> "title" # by_agent project with 2+ agents\n' +
|
|
39
|
-
' trackfw roadmap new "title" # flat project\n' +
|
|
40
|
-
' trackfw roadmap new --agent <agent> "title" # by_agent project with 2+ agents\n' +
|
|
41
|
-
' trackfw roadmap move <name> wip'
|
|
42
|
-
)
|
|
43
|
-
.option('-m, --message <msg>', 'Commit message (required)')
|
|
44
|
-
.option('--suggest', 'Print a heuristic Conventional Commits message skeleton from staged files and exit without committing (ignores -m)')
|
|
45
|
-
.action((options) => {
|
|
46
|
-
if (options.suggest) {
|
|
47
|
-
try {
|
|
48
|
-
const suggestion = buildSuggestedMessage({})
|
|
49
|
-
process.stdout.write(suggestion + '\n')
|
|
50
|
-
return
|
|
51
|
-
} catch (err) {
|
|
52
|
-
process.stderr.write(err.message + '\n')
|
|
53
|
-
process.exit(1)
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
const exitCode = runCommit(options.message || '', {})
|
|
58
|
-
if (exitCode !== 0) {
|
|
59
|
-
process.exit(exitCode)
|
|
60
|
-
}
|
|
61
|
-
})
|
|
62
|
-
|
|
63
|
-
return cmd
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
module.exports = createCommitCommand()
|
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
'use strict'
|
|
2
|
-
const { Command } = require('commander')
|
|
3
|
-
const readline = require('readline')
|
|
4
|
-
const fs = require('fs')
|
|
5
|
-
const path = require('path')
|
|
6
|
-
|
|
7
|
-
const DEFAULTS = {
|
|
8
|
-
adr_dirs: 'docs/adr',
|
|
9
|
-
req_dir: 'docs/req',
|
|
10
|
-
roadmap_dir: 'docs/roadmaps',
|
|
11
|
-
wip_limit: '1',
|
|
12
|
-
link_req: 'REQ:'
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Pergunta ao usuário via readline e retorna a resposta como Promise<string>.
|
|
17
|
-
* Se o usuário pressionar Enter sem digitar nada, retorna o valor padrão.
|
|
18
|
-
* @param {readline.Interface} rl
|
|
19
|
-
* @param {string} question
|
|
20
|
-
* @param {string} defaultValue
|
|
21
|
-
* @returns {Promise<string>}
|
|
22
|
-
*/
|
|
23
|
-
function ask(rl, question, defaultValue) {
|
|
24
|
-
return new Promise((resolve) => {
|
|
25
|
-
rl.question(question, (answer) => {
|
|
26
|
-
resolve(answer.trim() || defaultValue)
|
|
27
|
-
})
|
|
28
|
-
})
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Executa o wizard interativo e grava trackfw.yaml.
|
|
33
|
-
* Exportada separadamente para facilitar testes.
|
|
34
|
-
* @param {readline.Interface} [rl] — opcional; se não fornecido, cria um novo
|
|
35
|
-
* @param {string} [cwd] — diretório de trabalho (padrão: process.cwd())
|
|
36
|
-
* @returns {Promise<{ fieldsWritten: number, filePath: string }>}
|
|
37
|
-
*/
|
|
38
|
-
async function runConfigure(rl, cwd) {
|
|
39
|
-
const dir = cwd || process.cwd()
|
|
40
|
-
const yamlPath = path.join(dir, 'trackfw.yaml')
|
|
41
|
-
let ownRl = false
|
|
42
|
-
|
|
43
|
-
if (!rl) {
|
|
44
|
-
rl = readline.createInterface({ input: process.stdin, output: process.stdout })
|
|
45
|
-
ownRl = true
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
try {
|
|
49
|
-
// Verificar se já existe e perguntar se recria
|
|
50
|
-
if (fs.existsSync(yamlPath)) {
|
|
51
|
-
const answer = await ask(rl, 'trackfw.yaml já existe. Recriar do zero? (s/N) ', 'N')
|
|
52
|
-
if (answer.toLowerCase() !== 's') {
|
|
53
|
-
console.log('Operação cancelada.')
|
|
54
|
-
return { fieldsWritten: 0, filePath: yamlPath }
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// Coletar campos via prompts
|
|
59
|
-
const adrDirs = await ask(rl, `ADR dirs [${DEFAULTS.adr_dirs}]: `, DEFAULTS.adr_dirs)
|
|
60
|
-
const reqDir = await ask(rl, `REQ dir [${DEFAULTS.req_dir}]: `, DEFAULTS.req_dir)
|
|
61
|
-
const roadmapDir = await ask(rl, `Roadmap dir [${DEFAULTS.roadmap_dir}]: `, DEFAULTS.roadmap_dir)
|
|
62
|
-
const wipLimit = await ask(rl, `WIP limit [${DEFAULTS.wip_limit}]: `, DEFAULTS.wip_limit)
|
|
63
|
-
const linkReq = await ask(rl, `Marcador de REQ [${DEFAULTS.link_req}]: `, DEFAULTS.link_req)
|
|
64
|
-
|
|
65
|
-
// Montar campos customizados (somente diferenças dos defaults)
|
|
66
|
-
const custom = {}
|
|
67
|
-
if (adrDirs !== DEFAULTS.adr_dirs) custom.adr_dirs = adrDirs
|
|
68
|
-
if (reqDir !== DEFAULTS.req_dir) custom.req_dir = reqDir
|
|
69
|
-
if (roadmapDir !== DEFAULTS.roadmap_dir) custom.roadmap_dir = roadmapDir
|
|
70
|
-
if (wipLimit !== DEFAULTS.wip_limit) custom.wip_limit = wipLimit
|
|
71
|
-
if (linkReq !== DEFAULTS.link_req) custom['link_fields.req'] = linkReq
|
|
72
|
-
|
|
73
|
-
// Gerar conteúdo do YAML
|
|
74
|
-
let content = '# trackfw.yaml — gerado por trackfw configure\n'
|
|
75
|
-
const fieldsWritten = Object.keys(custom).length
|
|
76
|
-
|
|
77
|
-
if (fieldsWritten > 0) {
|
|
78
|
-
for (const [key, value] of Object.entries(custom)) {
|
|
79
|
-
if (key === 'adr_dirs') {
|
|
80
|
-
content += `adr_dirs:\n - ${value}\n`
|
|
81
|
-
} else if (key === 'link_fields.req') {
|
|
82
|
-
content += `link_fields:\n req:\n - "${value}"\n`
|
|
83
|
-
} else {
|
|
84
|
-
content += `${key}: ${value}\n`
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
fs.writeFileSync(yamlPath, content, 'utf8')
|
|
90
|
-
console.log(`trackfw.yaml gravado com ${fieldsWritten} campos customizados`)
|
|
91
|
-
|
|
92
|
-
return { fieldsWritten, filePath: yamlPath }
|
|
93
|
-
} finally {
|
|
94
|
-
if (ownRl) rl.close()
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
const cmd = new Command('configure')
|
|
99
|
-
cmd.description('Wizard interativo para criar/atualizar trackfw.yaml')
|
|
100
|
-
cmd.action(async () => {
|
|
101
|
-
await runConfigure()
|
|
102
|
-
})
|
|
103
|
-
|
|
104
|
-
module.exports = cmd
|
|
105
|
-
module.exports.runConfigure = runConfigure
|
|
106
|
-
module.exports.DEFAULTS = DEFAULTS
|
package/src/commands/context.js
DELETED
|
@@ -1,215 +0,0 @@
|
|
|
1
|
-
'use strict'
|
|
2
|
-
|
|
3
|
-
const { Command } = require('commander')
|
|
4
|
-
const fs = require('fs')
|
|
5
|
-
const path = require('path')
|
|
6
|
-
const config = require('../config')
|
|
7
|
-
const { validate, resolveAgentNamespaces, resolveReqFiles } = require('../validator')
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* extractFrontmatterField — extrai valor de campo YAML dentro de bloco --- ... ---.
|
|
11
|
-
* Retorna string vazia se não encontrado ou valor '""'.
|
|
12
|
-
* @param {string} content
|
|
13
|
-
* @param {string} field
|
|
14
|
-
* @returns {string}
|
|
15
|
-
*/
|
|
16
|
-
function extractFrontmatterField(content, field) {
|
|
17
|
-
const lines = content.split('\n')
|
|
18
|
-
let started = false
|
|
19
|
-
let inFrontmatter = false
|
|
20
|
-
for (const line of lines) {
|
|
21
|
-
const trimmed = line.trim()
|
|
22
|
-
if (trimmed === '---') {
|
|
23
|
-
if (!started) {
|
|
24
|
-
started = true
|
|
25
|
-
inFrontmatter = true
|
|
26
|
-
continue
|
|
27
|
-
}
|
|
28
|
-
break // segundo --- fecha o bloco
|
|
29
|
-
}
|
|
30
|
-
if (!inFrontmatter) break
|
|
31
|
-
const key = field + ':'
|
|
32
|
-
if (trimmed.startsWith(key)) {
|
|
33
|
-
let val = trimmed.slice(key.length).trim()
|
|
34
|
-
val = val.replace(/^["']|["']$/g, '') // remover aspas
|
|
35
|
-
return val
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
return ''
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* extractInlineStatus — extrai status da linha "| Status: ..." do markdown.
|
|
43
|
-
* @param {string} content
|
|
44
|
-
* @returns {string}
|
|
45
|
-
*/
|
|
46
|
-
function extractInlineStatus(content) {
|
|
47
|
-
for (const line of content.split('\n')) {
|
|
48
|
-
const idx = line.indexOf('| Status: ')
|
|
49
|
-
if (idx >= 0) {
|
|
50
|
-
let rest = line.slice(idx + '| Status: '.length)
|
|
51
|
-
const pipeIdx = rest.indexOf(' |')
|
|
52
|
-
if (pipeIdx >= 0) rest = rest.slice(0, pipeIdx)
|
|
53
|
-
rest = rest.replace(/[\s>|]+$/, '').trim()
|
|
54
|
-
return rest || 'unknown'
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
return 'unknown'
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* collectEntries — lê diretório e retorna lista de entradas com type, file, status, state.
|
|
62
|
-
* @param {string} dir
|
|
63
|
-
* @param {string} type - 'ADR' | 'REQ' | 'ROADMAP'
|
|
64
|
-
* @param {string} [state] - estado kanban (somente ROADMAP)
|
|
65
|
-
* @returns {Array<{type: string, file: string, status: string, state?: string}>}
|
|
66
|
-
*/
|
|
67
|
-
function collectEntries(dir, type, state) {
|
|
68
|
-
const entries = []
|
|
69
|
-
let files = []
|
|
70
|
-
try {
|
|
71
|
-
files = fs.readdirSync(dir).filter(f => f.endsWith('.md') && !fs.statSync(path.join(dir, f)).isDirectory())
|
|
72
|
-
} catch (_) {
|
|
73
|
-
return entries
|
|
74
|
-
}
|
|
75
|
-
for (const file of files) {
|
|
76
|
-
let content = ''
|
|
77
|
-
try { content = fs.readFileSync(path.join(dir, file), 'utf8') } catch (_) {}
|
|
78
|
-
let status = extractFrontmatterField(content, 'status')
|
|
79
|
-
if (!status) status = extractInlineStatus(content)
|
|
80
|
-
if (!status) status = state || 'unknown'
|
|
81
|
-
const entry = { type, file, status }
|
|
82
|
-
if (state) entry.state = state
|
|
83
|
-
entries.push(entry)
|
|
84
|
-
}
|
|
85
|
-
return entries
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* collectReqEntries — entradas de REQ para o `context`, derivadas do ponto único de leitura
|
|
90
|
-
* (resolveReqFiles). O `state` sai do nome da pasta-pai quando ela é um estado legado; em layout
|
|
91
|
-
* flat ou no canônico req_dir/<agente>/*.md não há estado (invariante D1: REQ não tem estado).
|
|
92
|
-
* @param {object} cfg
|
|
93
|
-
* @returns {Array<{type:string,file:string,status:string,state?:string}>}
|
|
94
|
-
*/
|
|
95
|
-
function collectReqEntries(cfg) {
|
|
96
|
-
const STATES = ['backlog', 'analyzing', 'wip', 'blocked', 'done', 'abandoned']
|
|
97
|
-
const entries = []
|
|
98
|
-
for (const full of resolveReqFiles(cfg)) {
|
|
99
|
-
let content = ''
|
|
100
|
-
try { content = fs.readFileSync(full, 'utf8') } catch (_) {}
|
|
101
|
-
const parent = path.basename(path.dirname(full))
|
|
102
|
-
const state = STATES.includes(parent) ? parent : ''
|
|
103
|
-
let status = extractFrontmatterField(content, 'status')
|
|
104
|
-
if (!status) status = extractInlineStatus(content)
|
|
105
|
-
if (!status) status = state || 'unknown'
|
|
106
|
-
const entry = { type: 'REQ', file: path.basename(full), status }
|
|
107
|
-
if (state) entry.state = state
|
|
108
|
-
entries.push(entry)
|
|
109
|
-
}
|
|
110
|
-
return entries
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
/**
|
|
114
|
-
* getContext — coleta governança e imprime em md ou json.
|
|
115
|
-
* @param {string} format - 'md' | 'json'
|
|
116
|
-
* @returns {Promise<void>}
|
|
117
|
-
*/
|
|
118
|
-
async function getContext(format) {
|
|
119
|
-
const cfg = config.load()
|
|
120
|
-
|
|
121
|
-
// ADRs
|
|
122
|
-
const adrs = []
|
|
123
|
-
for (const adrDir of (cfg.adrDirs || ['docs/adr'])) {
|
|
124
|
-
adrs.push(...collectEntries(adrDir, 'ADR'))
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
// REQs — pelo PONTO ÚNICO de leitura (ADR-2026-09-03, D3/D4). Antes, `context` montava a própria
|
|
128
|
-
// árvore (flat, ou <agente>/<estado>/ em by_agent) e não enxergava o layout canônico <agente>/*.md.
|
|
129
|
-
const reqs = collectReqEntries(cfg)
|
|
130
|
-
|
|
131
|
-
// Roadmaps
|
|
132
|
-
const roadmaps = []
|
|
133
|
-
const states = ['wip', 'backlog', 'blocked', 'done', 'abandoned']
|
|
134
|
-
if (cfg.roadmapNamespacing === 'by_agent') {
|
|
135
|
-
const agents = resolveAgentNamespaces(cfg, cfg.roadmapDir)
|
|
136
|
-
for (const agent of agents) {
|
|
137
|
-
for (const state of states) {
|
|
138
|
-
const dir = path.join(cfg.roadmapDir, agent, state)
|
|
139
|
-
roadmaps.push(...collectEntries(dir, 'ROADMAP', state))
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
} else {
|
|
143
|
-
for (const state of states) {
|
|
144
|
-
const dir = path.join(cfg.roadmapDir, state)
|
|
145
|
-
roadmaps.push(...collectEntries(dir, 'ROADMAP', state))
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
// Validate
|
|
150
|
-
const { violations, warnings } = await validate()
|
|
151
|
-
|
|
152
|
-
// Score
|
|
153
|
-
let score = 0
|
|
154
|
-
if (adrs.length > 0) score += 20
|
|
155
|
-
if (reqs.length > 0) score += 20
|
|
156
|
-
if (roadmaps.length > 0) score += 20
|
|
157
|
-
if (violations.length === 0) score += 40
|
|
158
|
-
|
|
159
|
-
if (format === 'json') {
|
|
160
|
-
console.log(JSON.stringify({ score, violations, warnings, adrs, reqs, roadmaps }, null, 2))
|
|
161
|
-
return
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
// Markdown
|
|
165
|
-
console.log('# trackfw governance context\n')
|
|
166
|
-
console.log(`**Governance score:** ${score}/100\n`)
|
|
167
|
-
|
|
168
|
-
console.log(`## ADRs (${adrs.length})`)
|
|
169
|
-
if (adrs.length === 0) {
|
|
170
|
-
console.log('- (none)')
|
|
171
|
-
} else {
|
|
172
|
-
for (const a of adrs) console.log(`- ${a.file} [${a.status}]`)
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
console.log(`\n## REQs (${reqs.length})`)
|
|
176
|
-
if (reqs.length === 0) {
|
|
177
|
-
console.log('- (none)')
|
|
178
|
-
} else {
|
|
179
|
-
for (const r of reqs) console.log(`- ${r.file} [${r.status}]`)
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
console.log(`\n## Roadmaps (${roadmaps.length})`)
|
|
183
|
-
if (roadmaps.length === 0) {
|
|
184
|
-
console.log('- (none)')
|
|
185
|
-
} else {
|
|
186
|
-
for (const r of roadmaps) console.log(`- ${r.file} [${r.state}]`)
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
if (violations.length > 0) {
|
|
190
|
-
console.log(`\n## Violations (${violations.length})`)
|
|
191
|
-
for (const v of violations) console.log(`- ${v}`)
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
if (warnings.length > 0) {
|
|
195
|
-
console.log(`\n## Warnings (${warnings.length})`)
|
|
196
|
-
for (const w of warnings) console.log(`- ${w}`)
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
module.exports = (function () {
|
|
201
|
-
const cmd = new Command('context')
|
|
202
|
-
cmd
|
|
203
|
-
.description('Print governance context for LLM consumption')
|
|
204
|
-
.option('--format <fmt>', 'Output format: md or json', 'md')
|
|
205
|
-
// Retorna a Promise: o bin roda parseAsync() e só assim uma rejeição chega
|
|
206
|
-
// ao .catch(reportFatalError) com exit code 1 em vez de virar unhandled
|
|
207
|
-
// rejection (ML-1A, ROADMAP-2026-09-02-context-do-cli-node-aguarda-validate).
|
|
208
|
-
.action((opts) => getContext(opts.format))
|
|
209
|
-
return cmd
|
|
210
|
-
})()
|
|
211
|
-
|
|
212
|
-
// Exposto para teste: o teste de context em by_agent duplicava a lógica de coleta e, por isso,
|
|
213
|
-
// continuava verde mesmo quando a produção mudava (asserção sobre uma CÓPIA). Anexar a função ao
|
|
214
|
-
// Command exportado é o menor caminho para o teste exercitar o código de produção de fato.
|
|
215
|
-
module.exports.collectReqEntries = collectReqEntries
|