trackfw 2.8.0 → 2.9.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "trackfw",
3
- "version": "2.8.0",
3
+ "version": "2.9.1",
4
4
  "description": "CLI de governança para entrega de software: ADR → REQ → ROADMAP → kanban. Suporte nativo a agentes de IA (Claude Code, Gemini CLI, Cursor).",
5
5
  "keywords": [
6
6
  "cli",
@@ -388,6 +388,13 @@ cmd.action((opts) => {
388
388
  } catch (e) {
389
389
  console.log(`⚠ gates install partial: ${e.message}`);
390
390
  }
391
+ try {
392
+ const generators = require('../generators/init');
393
+ generators.injectRulesDetected(cwd);
394
+ console.log('✓ trackfw rules injected into agent config files');
395
+ } catch (e) {
396
+ console.log(`⚠ agent rules inject partial: ${e.message}`);
397
+ }
391
398
  }
392
399
  }
393
400
 
@@ -403,7 +410,30 @@ cmd.action((opts) => {
403
410
  }
404
411
  });
405
412
 
413
+ function writeCIWorkflowForce(rootDir) {
414
+ const workflowsDir = path.join(rootDir, '.github', 'workflows');
415
+ if (!isDir(workflowsDir)) fs.mkdirSync(workflowsDir, { recursive: true });
416
+ const dest = path.join(workflowsDir, 'trackfw-validate.yml');
417
+ const content = `name: trackfw validate
418
+ on: [push, pull_request]
419
+ jobs:
420
+ governance:
421
+ runs-on: ubuntu-latest
422
+ steps:
423
+ - uses: actions/checkout@v4
424
+ - uses: actions/setup-go@v5
425
+ with:
426
+ go-version: "1.22"
427
+ - run: go install github.com/kgsaran/trackfw/cmd/trackfw@latest
428
+ - run: trackfw validate
429
+ `;
430
+ fs.writeFileSync(dest, content, 'utf8');
431
+ }
432
+
406
433
  module.exports = cmd;
407
434
  module.exports.scan = scan;
408
435
  module.exports.generateYAML = generateYAML;
409
436
  module.exports.generateBootstrapLog = generateBootstrapLog;
437
+ module.exports.writeValidateScript = writeValidateScript;
438
+ module.exports.writeCIWorkflow = writeCIWorkflow;
439
+ module.exports.writeCIWorkflowForce = writeCIWorkflowForce;
@@ -19,6 +19,7 @@ function createProgram() {
19
19
  program.addCommand(require('./log'))
20
20
  program.addCommand(require('./plugins'))
21
21
  program.addCommand(require('./discover'))
22
+ program.addCommand(require('./update'))
22
23
  program.addCommand(require('./metrics'))
23
24
  program.addCommand(require('./sync'))
24
25
  program.addCommand(require('./context'))
@@ -0,0 +1,115 @@
1
+ 'use strict';
2
+
3
+ const { Command } = require('commander');
4
+ const fs = require('fs');
5
+ const path = require('path');
6
+
7
+ function readUpdateConfig(rootDir) {
8
+ const yaml = path.join(rootDir, 'trackfw.yaml');
9
+ if (!fs.existsSync(yaml)) return {};
10
+ const lines = fs.readFileSync(yaml, 'utf8').split('\n');
11
+ const cfg = {};
12
+ for (const line of lines) {
13
+ const trimmed = line.trim();
14
+ if (trimmed.startsWith('#')) continue;
15
+ const idx = trimmed.indexOf(':');
16
+ if (idx < 0) continue;
17
+ const key = trimmed.slice(0, idx).trim();
18
+ let val = trimmed.slice(idx + 1).trim();
19
+ const ci = val.indexOf(' #');
20
+ if (ci >= 0) val = val.slice(0, ci).trim();
21
+ cfg[key] = val;
22
+ }
23
+ return cfg;
24
+ }
25
+
26
+ function updateHooksSurgical(cfg, rootDir) {
27
+ const hooks = cfg.hooks || '';
28
+ if (hooks === 'husky') {
29
+ const hookPath = path.join(rootDir, '.husky', 'pre-commit');
30
+ const content = fs.existsSync(hookPath) ? fs.readFileSync(hookPath, 'utf8') : '';
31
+ if (content.includes('trackfw validate')) {
32
+ console.log(' ✓ .husky/pre-commit — trackfw validate já presente');
33
+ } else {
34
+ fs.mkdirSync(path.join(rootDir, '.husky'), { recursive: true });
35
+ fs.appendFileSync(hookPath, '\ntrackfw validate\n', 'utf8');
36
+ try { fs.chmodSync(hookPath, 0o755); } catch (_) {}
37
+ console.log(' ✓ .husky/pre-commit — trackfw validate injetado');
38
+ }
39
+ } else if (hooks === 'lefthook') {
40
+ const lefthookPath = path.join(rootDir, 'lefthook.yml');
41
+ const content = fs.existsSync(lefthookPath) ? fs.readFileSync(lefthookPath, 'utf8') : '';
42
+ if (content.includes('trackfw-validate:') || content.includes('trackfw validate')) {
43
+ console.log(' ✓ lefthook.yml — trackfw já presente');
44
+ } else {
45
+ fs.appendFileSync(lefthookPath, '\npre-commit:\n commands:\n trackfw-validate:\n run: trackfw validate\n', 'utf8');
46
+ console.log(' ✓ lefthook.yml — trackfw-validate injetado');
47
+ }
48
+ }
49
+ }
50
+
51
+ const cmd = new Command('update');
52
+ cmd.description('Update trackfw-managed artifacts to the current version');
53
+ cmd.action(() => {
54
+ const cwd = process.cwd();
55
+ const yaml = path.join(cwd, 'trackfw.yaml');
56
+ if (!fs.existsSync(yaml)) {
57
+ console.error('✗ trackfw.yaml não encontrado — execute trackfw init primeiro');
58
+ process.exit(1);
59
+ }
60
+
61
+ const cfg = readUpdateConfig(cwd);
62
+ const generators = require('../generators/init');
63
+ const discover = require('./discover');
64
+
65
+ console.log('trackfw update — re-aplicando templates atuais...\n');
66
+
67
+ // 1. Agent rules (marker-delimited, idempotent)
68
+ try {
69
+ generators.injectRulesDetected(cwd);
70
+ console.log(' ✓ agent rules atualizadas');
71
+ } catch (e) {
72
+ console.log(` ⚠ agent rules: ${e.message}`);
73
+ }
74
+
75
+ // 2. Validate script (trackfw-owned, overwrite)
76
+ try {
77
+ discover.writeValidateScript(cwd);
78
+ console.log(' ✓ scripts/trackfw-validate.sh atualizado');
79
+ } catch (e) {
80
+ console.log(` ⚠ validate script: ${e.message}`);
81
+ }
82
+
83
+ // 3. CI workflow (trackfw-owned, overwrite)
84
+ if (cfg.ci === 'github-actions' || cfg.ci === 'github_actions') {
85
+ try {
86
+ discover.writeCIWorkflowForce(cwd);
87
+ console.log(' ✓ .github/workflows/trackfw-validate.yml atualizado');
88
+ } catch (e) {
89
+ console.log(` ⚠ CI workflow: ${e.message}`);
90
+ }
91
+ }
92
+
93
+ // 4. Git hooks (surgical)
94
+ updateHooksSurgical(cfg, cwd);
95
+
96
+ // 5. Claude commands (force overwrite)
97
+ try {
98
+ generators.generateClaudeCommandsForce(cwd);
99
+ console.log(' ✓ .claude/commands/trackfw/ atualizado');
100
+ } catch (e) {
101
+ console.log(` ⚠ Claude commands: ${e.message}`);
102
+ }
103
+
104
+ // 6. Global skill (force overwrite)
105
+ try {
106
+ generators.installSkillsForce(cwd);
107
+ console.log(' ✓ skill global atualizada');
108
+ } catch (e) {
109
+ console.log(` ⚠ skills: ${e.message}`);
110
+ }
111
+
112
+ console.log('\n✓ trackfw update concluído');
113
+ });
114
+
115
+ module.exports = cmd;
@@ -301,6 +301,136 @@ function generatePomXml(cfg) {
301
301
  console.log(' ✓ pom.xml')
302
302
  }
303
303
 
304
+ // ---------------------------------------------------------------------------
305
+ // trackfw rules inject-or-update (idempotente via marcadores HTML)
306
+ // ---------------------------------------------------------------------------
307
+
308
+ const RULES_START = '<!-- trackfw:rules:start -->'
309
+ const RULES_END = '<!-- trackfw:rules:end -->'
310
+
311
+ const AGENT_FILES = {
312
+ claude: 'CLAUDE.md',
313
+ codex: 'AGENTS.md',
314
+ gemini: 'GEMINI.md',
315
+ copilot: '.github/copilot-instructions.md',
316
+ windsurf: '.windsurfrules',
317
+ amazonq: '.amazonq/developer/guidelines.md',
318
+ cursor: '.cursor/rules/trackfw.mdc',
319
+ }
320
+
321
+ const AGENT_HEADERS = {
322
+ claude: '# Project Instructions\n',
323
+ codex: '# Project Instructions\n',
324
+ gemini: '# Project Instructions\n',
325
+ copilot: '# GitHub Copilot Instructions\n',
326
+ windsurf: '# Windsurf Rules\n',
327
+ amazonq: '# Amazon Q Developer Guidelines\n',
328
+ cursor: '---\ndescription: trackfw governance rules\nglob: "**/*"\nalwaysApply: true\n---\n',
329
+ }
330
+
331
+ function trackfwRulesBlock() {
332
+ return RULES_START + `
333
+ ## trackfw — Governance Rules
334
+
335
+ This project uses **trackfw** for AI-native delivery governance.
336
+ Chain: \`ADR → REQ → ROADMAP\` · States: \`backlog / wip / blocked / done / abandoned\`
337
+
338
+ ### Agent Protocol
339
+ 1. **Before starting:** run \`trackfw context\` · read \`docs/agents-working-context.md\`
340
+ 2. **After finishing:** update \`docs/agents-working-context.md\` with what changed
341
+ 3. **Before PR:** \`trackfw validate\` must pass
342
+
343
+ ### Key Commands
344
+ - \`trackfw context\` — current governance state (always run first)
345
+ - \`trackfw status\` — all artifacts and states
346
+ - \`trackfw validate\` — governance consistency check
347
+ - \`trackfw roadmap move <name> <state>\` — transition roadmap state
348
+ - \`trackfw serve\` — live Kanban board at http://localhost:4080
349
+
350
+ ### Attention Signal (when you need user input during a task)
351
+ Write \`docs/roadmaps/.trackfw-attention.json\`:
352
+ \`\`\`json
353
+ {"roadmap":"file.md","ml":"ML-1A","message":"what you need","level":"action_required","timestamp":"ISO8601Z"}
354
+ \`\`\`
355
+ Delete the file when resolved. Visible as a live banner in \`trackfw serve\`.
356
+ ` + RULES_END
357
+ }
358
+
359
+ /**
360
+ * injectOrUpdateRules — injeta ou atualiza o bloco de regras trackfw no arquivo.
361
+ * - Arquivo não existe: cria com headerIfNew + bloco de regras
362
+ * - Arquivo existe, sem marcador: appenda o bloco no final
363
+ * - Arquivo existe, com marcador: substitui conteúdo entre marcadores
364
+ */
365
+ function injectOrUpdateRules(filePath, headerIfNew) {
366
+ fs.mkdirSync(path.dirname(filePath), { recursive: true })
367
+
368
+ const block = trackfwRulesBlock()
369
+
370
+ if (!fs.existsSync(filePath)) {
371
+ let content = headerIfNew || ''
372
+ if (content && !content.endsWith('\n')) content += '\n'
373
+ content += '\n' + block + '\n'
374
+ fs.writeFileSync(filePath, content, 'utf8')
375
+ return
376
+ }
377
+
378
+ let content = fs.readFileSync(filePath, 'utf8')
379
+
380
+ const start = content.indexOf(RULES_START)
381
+ if (start === -1) {
382
+ // No marker: append
383
+ if (!content.endsWith('\n')) content += '\n'
384
+ content += '\n' + block + '\n'
385
+ fs.writeFileSync(filePath, content, 'utf8')
386
+ return
387
+ }
388
+
389
+ // Has marker: replace between start and end
390
+ const end = content.indexOf(RULES_END, start)
391
+ if (end === -1) {
392
+ // Malformed: append new block
393
+ content += '\n' + block + '\n'
394
+ fs.writeFileSync(filePath, content, 'utf8')
395
+ return
396
+ }
397
+
398
+ const newContent = content.slice(0, start) + block + content.slice(end + RULES_END.length)
399
+ fs.writeFileSync(filePath, newContent, 'utf8')
400
+ }
401
+
402
+ /**
403
+ * injectRulesForTool — injeta regras trackfw no arquivo de config do tool dado.
404
+ * tool: 'claude' | 'codex' | 'gemini' | 'copilot' | 'windsurf' | 'amazonq' | 'cursor'
405
+ * cwd: diretório raiz do projeto (default: process.cwd())
406
+ */
407
+ function injectRulesForTool(tool, cwd) {
408
+ const relPath = AGENT_FILES[tool]
409
+ if (!relPath) return
410
+ const header = AGENT_HEADERS[tool] || ''
411
+ injectOrUpdateRules(path.join(cwd || process.cwd(), relPath), header)
412
+ }
413
+
414
+ /**
415
+ * injectRulesDetected — varre cwd por arquivos de config de agentes existentes
416
+ * e injeta as regras trackfw em cada um encontrado.
417
+ * Cursor: injeta também se .cursor/ existir, mesmo que trackfw.mdc não exista ainda.
418
+ */
419
+ function injectRulesDetected(cwd) {
420
+ const root = cwd || process.cwd()
421
+ for (const [tool, relPath] of Object.entries(AGENT_FILES)) {
422
+ if (tool === 'cursor') {
423
+ if (fs.existsSync(path.join(root, '.cursor'))) {
424
+ try { injectRulesForTool('cursor', root) } catch (_) {}
425
+ }
426
+ continue
427
+ }
428
+ if (fs.existsSync(path.join(root, relPath))) {
429
+ try { injectRulesForTool(tool, root) } catch (_) {}
430
+ }
431
+ }
432
+ }
433
+
304
434
  // ---------------------------------------------------------------------------
305
435
  // CLAUDE.md
306
436
  // ---------------------------------------------------------------------------
@@ -417,7 +547,7 @@ function generateClaudeMD(cfg) {
417
547
  content += 'No CI gate configured.\n'
418
548
  }
419
549
 
420
- fs.writeFileSync('CLAUDE.md', content, 'utf8')
550
+ injectOrUpdateRules('CLAUDE.md', content)
421
551
  console.log(' ✓ CLAUDE.md')
422
552
  }
423
553
 
@@ -710,27 +840,112 @@ Próximo passo: abrir PR com gh pr create
710
840
  * ao usuário que esta operação requer o binário Go instalado.
711
841
  */
712
842
  async function installAgents() {
843
+ // CLAUDE.md já tratado por generateClaudeMD via injectOrUpdateRules
713
844
  console.log(' ✓ claude (agentes — execute `trackfw install agents` com o binário Go para instalar os templates)')
714
845
  }
715
846
 
716
847
  async function installGemini() {
717
- console.log(' ✓ gemini (sem templates disponíveis no pacote npm — instale o binário Go para acesso completo)')
848
+ try {
849
+ injectRulesForTool('gemini', process.cwd())
850
+ console.log(' ✓ trackfw rules → GEMINI.md')
851
+ } catch (e) {
852
+ console.log(` ⚠ gemini rules inject: ${e.message}`)
853
+ }
718
854
  }
719
855
 
720
856
  async function installCursor() {
721
- console.log(' ✓ cursor (sem templates disponíveis no pacote npm — instale o binário Go para acesso completo)')
857
+ try {
858
+ injectRulesForTool('cursor', process.cwd())
859
+ console.log(' ✓ trackfw rules → .cursor/rules/trackfw.mdc')
860
+ } catch (e) {
861
+ console.log(` ⚠ cursor rules inject: ${e.message}`)
862
+ }
722
863
  }
723
864
 
724
865
  async function installCopilot() {
725
- console.log(' ✓ copilot (sem templates disponíveis no pacote npm — instale o binário Go para acesso completo)')
866
+ try {
867
+ injectRulesForTool('copilot', process.cwd())
868
+ console.log(' ✓ trackfw rules → .github/copilot-instructions.md')
869
+ } catch (e) {
870
+ console.log(` ⚠ copilot rules inject: ${e.message}`)
871
+ }
726
872
  }
727
873
 
728
874
  async function installWindsurf() {
729
- console.log(' ✓ windsurf (sem templates disponíveis no pacote npm — instale o binário Go para acesso completo)')
875
+ try {
876
+ injectRulesForTool('windsurf', process.cwd())
877
+ console.log(' ✓ trackfw rules → .windsurfrules')
878
+ } catch (e) {
879
+ console.log(` ⚠ windsurf rules inject: ${e.message}`)
880
+ }
730
881
  }
731
882
 
732
883
  async function installAmazonQ() {
733
- console.log(' ✓ amazonq (sem templates disponíveis no pacote npm — instale o binário Go para acesso completo)')
884
+ try {
885
+ injectRulesForTool('amazonq', process.cwd())
886
+ console.log(' ✓ trackfw rules → .amazonq/developer/guidelines.md')
887
+ } catch (e) {
888
+ console.log(` ⚠ amazonq rules inject: ${e.message}`)
889
+ }
890
+ }
891
+
892
+ /**
893
+ * generateClaudeCommandsForce — re-gera todos os slash commands, sobrescrevendo arquivos existentes.
894
+ */
895
+ function generateClaudeCommandsForce(rootDir) {
896
+ const dir = rootDir ? path.join(rootDir, '.claude', 'commands', 'trackfw') : '.claude/commands/trackfw'
897
+ fs.mkdirSync(dir, { recursive: true })
898
+
899
+ const commands = {
900
+ 'adr.md': `Execute o seguinte comando bash: \`trackfw adr new "$ARGUMENTS"\`\n\nSe o comando falhar com \`trackfw: command not found\` ou similar, informe ao usuário:\n\n\`\`\`\ntrackfw não está instalado. Instale com uma das opções:\n\n curl -sSfL https://github.com/kgsaran/trackfw/releases/latest/download/install.sh | sh\n npm install -g trackfw\n pip install trackfw\n\`\`\``,
901
+ 'req.md': `Execute o seguinte comando bash: \`trackfw req new "$ARGUMENTS"\`\n\nSe o comando falhar com \`trackfw: command not found\` ou similar, informe ao usuário:\n\n\`\`\`\ntrackfw não está instalado. Instale com uma das opções:\n\n curl -sSfL https://github.com/kgsaran/trackfw/releases/latest/download/install.sh | sh\n npm install -g trackfw\n pip install trackfw\n\`\`\``,
902
+ 'validate.md': `Execute o seguinte comando bash: \`trackfw validate\`\n\nSe o comando falhar com \`trackfw: command not found\` ou similar, informe ao usuário:\n\n\`\`\`\ntrackfw não está instalado. Instale com uma das opções:\n\n curl -sSfL https://github.com/kgsaran/trackfw/releases/latest/download/install.sh | sh\n npm install -g trackfw\n pip install trackfw\n\`\`\``,
903
+ 'status.md': `Execute o seguinte comando bash: \`trackfw status\`\n\nSe o comando falhar com \`trackfw: command not found\` ou similar, informe ao usuário:\n\n\`\`\`\ntrackfw não está instalado. Instale com uma das opções:\n\n curl -sSfL https://github.com/kgsaran/trackfw/releases/latest/download/install.sh | sh\n npm install -g trackfw\n pip install trackfw\n\`\`\``,
904
+ 'move.md': `Execute o seguinte comando bash: \`trackfw roadmap move $ARGUMENTS\`\n\nO formato esperado é: \`<nome-do-roadmap> <estado>\`\nEstados válidos: \`backlog\`, \`wip\`, \`blocked\`, \`done\`, \`abandoned\`\n\nSe o comando falhar, instale trackfw com:\n curl -sSfL https://github.com/kgsaran/trackfw/releases/latest/download/install.sh | sh`,
905
+ }
906
+
907
+ for (const [filename, content] of Object.entries(commands)) {
908
+ fs.writeFileSync(path.join(dir, filename), content, 'utf8')
909
+ }
910
+ console.log(` ✓ .claude/commands/trackfw/ (${Object.keys(commands).length} slash commands sobrescritos)`)
911
+ }
912
+
913
+ /**
914
+ * installSkillsForce — sobrescreve ~/.claude/skills/trackfw/SKILL.md sempre.
915
+ */
916
+ function installSkillsForce() {
917
+ const skillDir = path.join(os.homedir(), '.claude', 'skills', 'trackfw')
918
+ fs.mkdirSync(skillDir, { recursive: true })
919
+
920
+ const content = `---
921
+ name: trackfw
922
+ description: "trackfw — Governed Software Delivery: ADR → REQ → ROADMAP → kanban"
923
+ signature: "📦 trackfw - Governed Delivery"
924
+ ---
925
+
926
+ # trackfw — Modo de Operação
927
+
928
+ Este projeto usa **trackfw** para governança de entrega de software.
929
+ Cadeia: **ADR → REQ → ROADMAP** · Estados: \`backlog / wip / blocked / done / abandoned\`
930
+
931
+ ## Comandos principais
932
+
933
+ - \`trackfw context\` — contexto de trabalho atual (sempre execute primeiro)
934
+ - \`trackfw status\` — todos os artefatos e estados
935
+ - \`trackfw validate\` — valida consistência de governança
936
+ - \`trackfw roadmap move <nome> <estado>\` — transição de estado
937
+ - \`trackfw serve\` — board Kanban em http://localhost:4080
938
+
939
+ ## Protocolo de agente
940
+
941
+ 1. Antes de iniciar: \`trackfw context\` + ler \`docs/agents-working-context.md\`
942
+ 2. Após concluir: atualizar \`docs/agents-working-context.md\`
943
+ 3. Antes de PR: \`trackfw validate\` deve passar com zero violations
944
+ `
945
+
946
+ const dest = path.join(skillDir, 'SKILL.md')
947
+ fs.writeFileSync(dest, content, 'utf8')
948
+ console.log(` ✓ skill global atualizada em ${dest}`)
734
949
  }
735
950
 
736
951
  module.exports = {
@@ -743,10 +958,15 @@ module.exports = {
743
958
  generateCommitMsgHook,
744
959
  generateClaudeMD,
745
960
  generateClaudeCommands,
961
+ generateClaudeCommandsForce,
962
+ installSkillsForce,
746
963
  installAgents,
747
964
  installGemini,
748
965
  installCursor,
749
966
  installCopilot,
750
967
  installWindsurf,
751
968
  installAmazonQ,
969
+ injectRulesForTool,
970
+ injectRulesDetected,
971
+ trackfwRulesBlock,
752
972
  }