trackfw 2.10.0 → 2.12.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/package.json +1 -1
- package/src/commands/discover.js +28 -0
- package/src/commands/update.js +9 -0
- package/src/generators/hooks.js +302 -0
- package/src/generators/init.js +21 -37
- package/src/validator/index.js +29 -0
package/package.json
CHANGED
package/src/commands/discover.js
CHANGED
|
@@ -211,11 +211,32 @@ function installHook(framework, rootDir) {
|
|
|
211
211
|
if (fs.existsSync(pkgJson)) {
|
|
212
212
|
installHusky(rootDir);
|
|
213
213
|
} else {
|
|
214
|
+
// Node.js disponível mas sem package.json → husky via npx
|
|
215
|
+
try {
|
|
216
|
+
execSync('node --version', { stdio: 'pipe' });
|
|
217
|
+
installHuskyNPX(rootDir);
|
|
218
|
+
return;
|
|
219
|
+
} catch (_) {}
|
|
220
|
+
// fallback: lefthook
|
|
214
221
|
installLefthook(rootDir);
|
|
215
222
|
}
|
|
216
223
|
}
|
|
217
224
|
}
|
|
218
225
|
|
|
226
|
+
function installHuskyNPX(rootDir) {
|
|
227
|
+
console.log('ℹ node detected — using husky via npx (no package.json required)');
|
|
228
|
+
try {
|
|
229
|
+
execSync('npx husky init', { cwd: rootDir, stdio: 'inherit' });
|
|
230
|
+
} catch (_) {
|
|
231
|
+
console.log('⚠ npx husky init failed — install manually: npx husky init');
|
|
232
|
+
}
|
|
233
|
+
const huskyDir = path.join(rootDir, '.husky');
|
|
234
|
+
fs.mkdirSync(huskyDir, { recursive: true });
|
|
235
|
+
const hookPath = path.join(huskyDir, 'pre-commit');
|
|
236
|
+
fs.appendFileSync(hookPath, '\nscripts/trackfw-validate.sh\n', { mode: 0o755 });
|
|
237
|
+
console.log('✓ trackfw entry added to .husky/pre-commit');
|
|
238
|
+
}
|
|
239
|
+
|
|
219
240
|
function installHusky(rootDir) {
|
|
220
241
|
try {
|
|
221
242
|
execSync('npm install --save-dev husky', { cwd: rootDir, stdio: 'inherit' });
|
|
@@ -396,6 +417,13 @@ cmd.action((opts) => {
|
|
|
396
417
|
} catch (e) {
|
|
397
418
|
console.log(`⚠ agent rules inject partial: ${e.message}`);
|
|
398
419
|
}
|
|
420
|
+
try {
|
|
421
|
+
const { injectHooksDetected } = require('../generators/hooks');
|
|
422
|
+
injectHooksDetected(cwd);
|
|
423
|
+
console.log('✓ agent hooks injected');
|
|
424
|
+
} catch (e) {
|
|
425
|
+
console.warn(`⚠ agent hooks inject partial: ${e.message}`);
|
|
426
|
+
}
|
|
399
427
|
try {
|
|
400
428
|
const generators = require('../generators/init')
|
|
401
429
|
generators.printArchitectNextSteps(cwd)
|
package/src/commands/update.js
CHANGED
|
@@ -72,6 +72,15 @@ cmd.action(() => {
|
|
|
72
72
|
console.log(` ⚠ agent rules: ${e.message}`);
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
+
// 1b. Agent hooks (attention signal/cleanup)
|
|
76
|
+
try {
|
|
77
|
+
const { injectHooksDetected } = require('../generators/hooks');
|
|
78
|
+
injectHooksDetected(cwd);
|
|
79
|
+
console.log(' ✓ agent hooks atualizados');
|
|
80
|
+
} catch (e) {
|
|
81
|
+
console.warn(` ⚠ agent hooks: ${e.message}`);
|
|
82
|
+
}
|
|
83
|
+
|
|
75
84
|
// 2. Validate script (trackfw-owned, overwrite)
|
|
76
85
|
try {
|
|
77
86
|
discover.writeValidateScript(cwd);
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const fs = require('fs')
|
|
4
|
+
const path = require('path')
|
|
5
|
+
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
// Helpers
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
|
|
10
|
+
/** Lê JSON de arquivo (retorna {} se não existir ou inválido) */
|
|
11
|
+
function readJSON(filePath) {
|
|
12
|
+
try {
|
|
13
|
+
const raw = fs.readFileSync(filePath, 'utf8')
|
|
14
|
+
return JSON.parse(raw)
|
|
15
|
+
} catch (_) {
|
|
16
|
+
return {}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Escreve JSON com indent 2 */
|
|
21
|
+
function writeJSON(filePath, data) {
|
|
22
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true })
|
|
23
|
+
fs.writeFileSync(filePath, JSON.stringify(data, null, 2) + '\n', 'utf8')
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Verifica se array já tem entry com determinado campo=valor */
|
|
27
|
+
function hasEntry(arr, field, value) {
|
|
28
|
+
return Array.isArray(arr) && arr.some(e => e && e[field] === value)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// ---------------------------------------------------------------------------
|
|
32
|
+
// Scripts content
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
|
|
35
|
+
const SIGNAL_SCRIPT = `#!/usr/bin/env bash
|
|
36
|
+
# trackfw attention signal — PreToolUse/BeforeTool hook
|
|
37
|
+
# Writes .trackfw-attention.json so trackfw serve board shows a banner.
|
|
38
|
+
set -euo pipefail
|
|
39
|
+
|
|
40
|
+
INPUT=$(cat)
|
|
41
|
+
[ -f "trackfw.yaml" ] || exit 0
|
|
42
|
+
|
|
43
|
+
if command -v jq &>/dev/null; then
|
|
44
|
+
TOOL=$(echo "$INPUT" | jq -r '.tool_name // ""')
|
|
45
|
+
MSG=$(echo "$INPUT" | jq -r '(.tool_input.question // .tool_input.command // ("Agent executing: " + (.tool_name // "unknown"))) | .[0:300]')
|
|
46
|
+
else
|
|
47
|
+
TOOL=$(echo "$INPUT" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('tool_name',''))" 2>/dev/null || echo "")
|
|
48
|
+
MSG=$(echo "$INPUT" | python3 -c "import sys,json; d=json.load(sys.stdin); ti=d.get('tool_input',{}); print((ti.get('question') or ti.get('command') or 'Agent executing: '+d.get('tool_name','unknown'))[:300])" 2>/dev/null || echo "Agent needs attention")
|
|
49
|
+
fi
|
|
50
|
+
|
|
51
|
+
ROADMAP_DIR=$(grep '^roadmap_dir:' trackfw.yaml 2>/dev/null | awk '{print $2}' | tr -d "\"'" | head -1)
|
|
52
|
+
ROADMAP_DIR=\${ROADMAP_DIR:-docs/roadmaps}
|
|
53
|
+
|
|
54
|
+
TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
55
|
+
|
|
56
|
+
mkdir -p "$ROADMAP_DIR"
|
|
57
|
+
printf '{"tool":"%s","message":"%s","level":"action_required","timestamp":"%s"}\\n' \\
|
|
58
|
+
"$(echo "$TOOL" | sed 's/"/\\\\"/g')" \\
|
|
59
|
+
"$(echo "$MSG" | sed 's/"/\\\\"/g; s/$//' | tr -d '\\n')" \\
|
|
60
|
+
"$TIMESTAMP" > "$ROADMAP_DIR/.trackfw-attention.json"
|
|
61
|
+
|
|
62
|
+
exit 0
|
|
63
|
+
`
|
|
64
|
+
|
|
65
|
+
const CLEANUP_SCRIPT = `#!/usr/bin/env bash
|
|
66
|
+
# trackfw attention cleanup — PostToolUse/AfterTool hook
|
|
67
|
+
set -euo pipefail
|
|
68
|
+
|
|
69
|
+
[ -f "trackfw.yaml" ] || exit 0
|
|
70
|
+
|
|
71
|
+
ROADMAP_DIR=$(grep '^roadmap_dir:' trackfw.yaml 2>/dev/null | awk '{print $2}' | tr -d "\"'" | head -1)
|
|
72
|
+
ROADMAP_DIR=\${ROADMAP_DIR:-docs/roadmaps}
|
|
73
|
+
|
|
74
|
+
rm -f "$ROADMAP_DIR/.trackfw-attention.json"
|
|
75
|
+
exit 0
|
|
76
|
+
`
|
|
77
|
+
|
|
78
|
+
const SIGNAL_CMD = 'scripts/trackfw-attention-signal.sh'
|
|
79
|
+
const CLEANUP_CMD = 'scripts/trackfw-attention-cleanup.sh'
|
|
80
|
+
|
|
81
|
+
// ---------------------------------------------------------------------------
|
|
82
|
+
// generateAttentionScripts — writes the two shell scripts to scripts/
|
|
83
|
+
// ---------------------------------------------------------------------------
|
|
84
|
+
|
|
85
|
+
function generateAttentionScripts(cfg, cwd) {
|
|
86
|
+
const root = cwd || process.cwd()
|
|
87
|
+
const scriptsDir = path.join(root, 'scripts')
|
|
88
|
+
fs.mkdirSync(scriptsDir, { recursive: true })
|
|
89
|
+
|
|
90
|
+
const signalPath = path.join(scriptsDir, 'trackfw-attention-signal.sh')
|
|
91
|
+
fs.writeFileSync(signalPath, SIGNAL_SCRIPT, { encoding: 'utf8', mode: 0o755 })
|
|
92
|
+
|
|
93
|
+
const cleanupPath = path.join(scriptsDir, 'trackfw-attention-cleanup.sh')
|
|
94
|
+
fs.writeFileSync(cleanupPath, CLEANUP_SCRIPT, { encoding: 'utf8', mode: 0o755 })
|
|
95
|
+
|
|
96
|
+
console.log(' ✓ scripts/trackfw-attention-signal.sh')
|
|
97
|
+
console.log(' ✓ scripts/trackfw-attention-cleanup.sh')
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// ---------------------------------------------------------------------------
|
|
101
|
+
// Claude Code — .claude/settings.json
|
|
102
|
+
// ---------------------------------------------------------------------------
|
|
103
|
+
|
|
104
|
+
function injectClaudeHooks(cwd) {
|
|
105
|
+
const filePath = path.join(cwd, '.claude', 'settings.json')
|
|
106
|
+
const data = readJSON(filePath)
|
|
107
|
+
|
|
108
|
+
if (!data.hooks) data.hooks = {}
|
|
109
|
+
|
|
110
|
+
// PreToolUse — AskUserQuestion
|
|
111
|
+
if (!data.hooks.PreToolUse) data.hooks.PreToolUse = []
|
|
112
|
+
let preEntry = data.hooks.PreToolUse.find(e => e && e.matcher === 'AskUserQuestion')
|
|
113
|
+
if (!preEntry) {
|
|
114
|
+
preEntry = { matcher: 'AskUserQuestion', hooks: [] }
|
|
115
|
+
data.hooks.PreToolUse.push(preEntry)
|
|
116
|
+
}
|
|
117
|
+
if (!Array.isArray(preEntry.hooks)) preEntry.hooks = []
|
|
118
|
+
if (!hasEntry(preEntry.hooks, 'command', SIGNAL_CMD)) {
|
|
119
|
+
preEntry.hooks.push({ type: 'command', command: SIGNAL_CMD })
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// PostToolUse — cleanup
|
|
123
|
+
if (!data.hooks.PostToolUse) data.hooks.PostToolUse = []
|
|
124
|
+
let postEntry = data.hooks.PostToolUse.find(e => e && e.matcher === 'AskUserQuestion')
|
|
125
|
+
if (!postEntry) {
|
|
126
|
+
postEntry = { matcher: 'AskUserQuestion', hooks: [] }
|
|
127
|
+
data.hooks.PostToolUse.push(postEntry)
|
|
128
|
+
}
|
|
129
|
+
if (!Array.isArray(postEntry.hooks)) postEntry.hooks = []
|
|
130
|
+
if (!hasEntry(postEntry.hooks, 'command', CLEANUP_CMD)) {
|
|
131
|
+
postEntry.hooks.push({ type: 'command', command: CLEANUP_CMD })
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
writeJSON(filePath, data)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// ---------------------------------------------------------------------------
|
|
138
|
+
// Codex — .codex/hooks.json
|
|
139
|
+
// ---------------------------------------------------------------------------
|
|
140
|
+
|
|
141
|
+
function injectCodexHooks(cwd) {
|
|
142
|
+
const filePath = path.join(cwd, '.codex', 'hooks.json')
|
|
143
|
+
const data = readJSON(filePath)
|
|
144
|
+
|
|
145
|
+
if (!data.PreToolUse) data.PreToolUse = []
|
|
146
|
+
if (!data.PostToolUse) data.PostToolUse = []
|
|
147
|
+
|
|
148
|
+
if (!hasEntry(data.PreToolUse, 'command', SIGNAL_CMD)) {
|
|
149
|
+
data.PreToolUse.push({ matcher: '.*', command: SIGNAL_CMD })
|
|
150
|
+
}
|
|
151
|
+
if (!hasEntry(data.PostToolUse, 'command', CLEANUP_CMD)) {
|
|
152
|
+
data.PostToolUse.push({ matcher: '.*', command: CLEANUP_CMD })
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
writeJSON(filePath, data)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// ---------------------------------------------------------------------------
|
|
159
|
+
// Gemini — .gemini/settings.json
|
|
160
|
+
// ---------------------------------------------------------------------------
|
|
161
|
+
|
|
162
|
+
function injectGeminiHooks(cwd) {
|
|
163
|
+
const filePath = path.join(cwd, '.gemini', 'settings.json')
|
|
164
|
+
const data = readJSON(filePath)
|
|
165
|
+
|
|
166
|
+
if (!data.hooks) data.hooks = {}
|
|
167
|
+
|
|
168
|
+
if (!Array.isArray(data.hooks.BeforeTool)) data.hooks.BeforeTool = []
|
|
169
|
+
if (!hasEntry(data.hooks.BeforeTool, 'command', SIGNAL_CMD)) {
|
|
170
|
+
data.hooks.BeforeTool.push({ command: SIGNAL_CMD })
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (!Array.isArray(data.hooks.AfterTool)) data.hooks.AfterTool = []
|
|
174
|
+
if (!hasEntry(data.hooks.AfterTool, 'command', CLEANUP_CMD)) {
|
|
175
|
+
data.hooks.AfterTool.push({ command: CLEANUP_CMD })
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
writeJSON(filePath, data)
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// ---------------------------------------------------------------------------
|
|
182
|
+
// Kiro — .kiro/hooks/trackfw-attention.json (dedicated file, safe overwrite)
|
|
183
|
+
// ---------------------------------------------------------------------------
|
|
184
|
+
|
|
185
|
+
function injectKiroHooks(cwd) {
|
|
186
|
+
const filePath = path.join(cwd, '.kiro', 'hooks', 'trackfw-attention.json')
|
|
187
|
+
const data = {
|
|
188
|
+
hooks: [
|
|
189
|
+
{
|
|
190
|
+
name: 'trackfw-attention-signal',
|
|
191
|
+
event: 'PreToolUse',
|
|
192
|
+
matcher: { tool_name: '.*' },
|
|
193
|
+
action: { type: 'command', command: SIGNAL_CMD },
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
name: 'trackfw-attention-cleanup',
|
|
197
|
+
event: 'PostToolUse',
|
|
198
|
+
matcher: { tool_name: '.*' },
|
|
199
|
+
action: { type: 'command', command: CLEANUP_CMD },
|
|
200
|
+
},
|
|
201
|
+
],
|
|
202
|
+
}
|
|
203
|
+
writeJSON(filePath, data)
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// ---------------------------------------------------------------------------
|
|
207
|
+
// Copilot — .github/hooks/trackfw-attention.json (dedicated file, safe overwrite)
|
|
208
|
+
// ---------------------------------------------------------------------------
|
|
209
|
+
|
|
210
|
+
function injectCopilotHooks(cwd) {
|
|
211
|
+
const filePath = path.join(cwd, '.github', 'hooks', 'trackfw-attention.json')
|
|
212
|
+
const data = {
|
|
213
|
+
hooks: [
|
|
214
|
+
{ event: 'preToolUse', run: SIGNAL_CMD },
|
|
215
|
+
{ event: 'postToolUse', run: CLEANUP_CMD },
|
|
216
|
+
],
|
|
217
|
+
}
|
|
218
|
+
writeJSON(filePath, data)
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// ---------------------------------------------------------------------------
|
|
222
|
+
// Cursor — .cursor/hooks.json
|
|
223
|
+
// ---------------------------------------------------------------------------
|
|
224
|
+
|
|
225
|
+
function injectCursorHooks(cwd) {
|
|
226
|
+
const filePath = path.join(cwd, '.cursor', 'hooks.json')
|
|
227
|
+
const data = readJSON(filePath)
|
|
228
|
+
|
|
229
|
+
if (!Array.isArray(data.preToolUse)) data.preToolUse = []
|
|
230
|
+
if (!hasEntry(data.preToolUse, 'command', SIGNAL_CMD)) {
|
|
231
|
+
data.preToolUse.push({ command: SIGNAL_CMD })
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (!Array.isArray(data.postToolUse)) data.postToolUse = []
|
|
235
|
+
if (!hasEntry(data.postToolUse, 'command', CLEANUP_CMD)) {
|
|
236
|
+
data.postToolUse.push({ command: CLEANUP_CMD })
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
writeJSON(filePath, data)
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// ---------------------------------------------------------------------------
|
|
243
|
+
// injectHooksDetected — public entry point
|
|
244
|
+
// ---------------------------------------------------------------------------
|
|
245
|
+
|
|
246
|
+
function injectHooksDetected(cwd) {
|
|
247
|
+
const root = cwd || process.cwd()
|
|
248
|
+
|
|
249
|
+
const detections = {
|
|
250
|
+
claude: {
|
|
251
|
+
check: () =>
|
|
252
|
+
fs.existsSync(path.join(root, '.claude')) ||
|
|
253
|
+
fs.existsSync(path.join(root, 'CLAUDE.md')),
|
|
254
|
+
fn: injectClaudeHooks,
|
|
255
|
+
},
|
|
256
|
+
codex: {
|
|
257
|
+
check: () =>
|
|
258
|
+
fs.existsSync(path.join(root, 'AGENTS.md')) ||
|
|
259
|
+
fs.existsSync(path.join(root, '.codex')),
|
|
260
|
+
fn: injectCodexHooks,
|
|
261
|
+
},
|
|
262
|
+
gemini: {
|
|
263
|
+
check: () =>
|
|
264
|
+
fs.existsSync(path.join(root, 'GEMINI.md')) ||
|
|
265
|
+
fs.existsSync(path.join(root, '.gemini')),
|
|
266
|
+
fn: injectGeminiHooks,
|
|
267
|
+
},
|
|
268
|
+
kiro: {
|
|
269
|
+
check: () => fs.existsSync(path.join(root, '.kiro')),
|
|
270
|
+
fn: injectKiroHooks,
|
|
271
|
+
},
|
|
272
|
+
copilot: {
|
|
273
|
+
check: () =>
|
|
274
|
+
fs.existsSync(path.join(root, '.github', 'copilot-instructions.md')),
|
|
275
|
+
fn: injectCopilotHooks,
|
|
276
|
+
},
|
|
277
|
+
cursor: {
|
|
278
|
+
check: () => fs.existsSync(path.join(root, '.cursor')),
|
|
279
|
+
fn: injectCursorHooks,
|
|
280
|
+
},
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
for (const [name, { check, fn }] of Object.entries(detections)) {
|
|
284
|
+
if (!check()) continue
|
|
285
|
+
try {
|
|
286
|
+
fn(root)
|
|
287
|
+
} catch (e) {
|
|
288
|
+
console.warn(` ⚠ hooks (${name}): ${e.message}`)
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
module.exports = {
|
|
294
|
+
generateAttentionScripts,
|
|
295
|
+
injectClaudeHooks,
|
|
296
|
+
injectCodexHooks,
|
|
297
|
+
injectGeminiHooks,
|
|
298
|
+
injectKiroHooks,
|
|
299
|
+
injectCopilotHooks,
|
|
300
|
+
injectCursorHooks,
|
|
301
|
+
injectHooksDetected,
|
|
302
|
+
}
|
package/src/generators/init.js
CHANGED
|
@@ -27,6 +27,7 @@ async function scaffold(cfg) {
|
|
|
27
27
|
|
|
28
28
|
writeTrackfwConfig(cfg)
|
|
29
29
|
generateValidateScript(cfg)
|
|
30
|
+
generateAttentionScripts(cfg)
|
|
30
31
|
generateCIWorkflow(cfg)
|
|
31
32
|
generateGitHooks(cfg)
|
|
32
33
|
generateCommitMsgHook(cfg)
|
|
@@ -116,6 +117,15 @@ trackfw validate
|
|
|
116
117
|
return base
|
|
117
118
|
}
|
|
118
119
|
|
|
120
|
+
// ---------------------------------------------------------------------------
|
|
121
|
+
// scripts/trackfw-attention-signal.sh + trackfw-attention-cleanup.sh
|
|
122
|
+
// ---------------------------------------------------------------------------
|
|
123
|
+
|
|
124
|
+
function generateAttentionScripts(cfg, cwd) {
|
|
125
|
+
const { generateAttentionScripts: gen } = require('./hooks')
|
|
126
|
+
gen(cfg, cwd || process.cwd())
|
|
127
|
+
}
|
|
128
|
+
|
|
119
129
|
// ---------------------------------------------------------------------------
|
|
120
130
|
// CI workflows
|
|
121
131
|
// ---------------------------------------------------------------------------
|
|
@@ -336,30 +346,14 @@ function trackfwRulesBlock() {
|
|
|
336
346
|
This project uses **trackfw** for AI-native delivery governance.
|
|
337
347
|
Chain: \`ADR → REQ → ROADMAP\` · States: \`backlog / analyzing / wip / blocked / done / abandoned\`
|
|
338
348
|
|
|
339
|
-
\`\`\`
|
|
340
|
-
backlog → roadmaps aguardando execução
|
|
341
|
-
analyzing → roadmap em análise/validação pré-wip
|
|
342
|
-
wip → roadmap em execução ativa (máximo 1)
|
|
343
|
-
blocked → impedido por dependência ou decisão
|
|
344
|
-
done → concluído e validado
|
|
345
|
-
abandoned → descontinuado ou superado
|
|
346
|
-
\`\`\`
|
|
347
|
-
|
|
348
349
|
### Agent Protocol
|
|
349
350
|
1. **Before starting:** run \`trackfw context\` · read \`docs/agents-working-context.md\`
|
|
350
351
|
2. **After finishing:** update \`docs/agents-working-context.md\` with what changed
|
|
351
352
|
3. **Before PR:** \`trackfw validate\` must pass
|
|
352
|
-
4. **
|
|
353
|
-
-
|
|
354
|
-
-
|
|
355
|
-
-
|
|
356
|
-
|
|
357
|
-
### Key Commands
|
|
358
|
-
- \`trackfw context\` — current governance state (always run first)
|
|
359
|
-
- \`trackfw status\` — all artifacts and states
|
|
360
|
-
- \`trackfw validate\` — governance consistency check
|
|
361
|
-
- \`trackfw roadmap move <name> <state>\` — transition roadmap state (\`backlog\`, \`analyzing\`, \`wip\`, \`blocked\`, \`done\`, \`abandoned\`)
|
|
362
|
-
- \`trackfw serve\` — live Kanban board at http://localhost:4080
|
|
353
|
+
4. **ML lifecycle — mandatory:**
|
|
354
|
+
- Starting a ML: edit roadmap \`**Status:** ⬜ Pendente\` → \`**Status:** 🔄 Em andamento\` + commit.
|
|
355
|
+
- Completing a ML: edit roadmap → \`**Status:** ✅ Concluído\` + include in ML commit.
|
|
356
|
+
- Analyzing a roadmap: move from \`backlog/\` to \`analyzing/\`; to \`wip/\` only when coding starts.
|
|
363
357
|
|
|
364
358
|
### Attention Signal (when you need user input during a task)
|
|
365
359
|
Write \`docs/roadmaps/.trackfw-attention.json\`:
|
|
@@ -369,14 +363,10 @@ Write \`docs/roadmaps/.trackfw-attention.json\`:
|
|
|
369
363
|
Delete the file when resolved. Visible as a live banner in \`trackfw serve\`.
|
|
370
364
|
` +
|
|
371
365
|
'\n### Architecture Directives (mandatory)\n' +
|
|
372
|
-
'- **3-layer
|
|
373
|
-
'- **
|
|
374
|
-
'- **
|
|
375
|
-
'- **
|
|
376
|
-
'- **2-layer validation:** frontend (UX) + backend (security) — never only one\n' +
|
|
377
|
-
'- **API-first:** define OpenAPI contract before coding frontend/backend integration\n' +
|
|
378
|
-
'- **Security wave:** include a red-team review wave in every feature roadmap\n' +
|
|
379
|
-
'- **Test coverage:** TDD for critical logic; min 60% (prototype) / 80% (production)\n' +
|
|
366
|
+
'- **3-layer arch + no in-memory data:** frontend / backend / database; always DB + ORM — never arrays/globals\n' +
|
|
367
|
+
'- **Auth + Docker + .env from day 1:** never defer auth; containerize early; all config via env vars\n' +
|
|
368
|
+
'- **2-layer validation + API-first:** frontend (UX) + backend (security); define OpenAPI contract first\n' +
|
|
369
|
+
'- **Security wave + test coverage:** red-team review in every roadmap; TDD for critical; min 60%/80%\n' +
|
|
380
370
|
'- Use `/trackfw:architect` to define stack before the first REQ\n' +
|
|
381
371
|
RULES_END
|
|
382
372
|
}
|
|
@@ -893,14 +883,7 @@ Explique a arquitetura com uma metáfora de negócio:
|
|
|
893
883
|
- **Backend** = a cozinha: onde as regras de negócio acontecem, nunca exposta diretamente
|
|
894
884
|
- **Banco de dados** = a despensa: onde os dados ficam guardados, acessada só pela cozinha"
|
|
895
885
|
|
|
896
|
-
Reforce as
|
|
897
|
-
- **Nunca dados em memória** (arrays, variáveis globais): sempre banco + ORM
|
|
898
|
-
- **Docker + .env desde o dia 1**: facilita deploys e evita problemas de ambiente
|
|
899
|
-
- **Auth desde o início**: nunca deixe para depois — refatorar auth é muito custoso
|
|
900
|
-
- **Validação em 2 camadas**: frontend (UX) + backend (segurança)
|
|
901
|
-
- **API-first**: defina o contrato OpenAPI antes de codar frontend e backend juntos
|
|
902
|
-
- **Red team wave**: reserve 1 wave de segurança no roadmap para revisar vulnerabilidades
|
|
903
|
-
- **Cobertura mínima de testes**: 60% (protótipo) / 80% (produção)
|
|
886
|
+
Reforce as **Architecture Directives** já injetadas no CLAUDE.md deste projeto: separação em 3 camadas sem dados em memória (sempre DB + ORM), auth + Docker + .env desde o dia 1, validação em 2 camadas, contrato OpenAPI antes de codar, wave de segurança em todo roadmap e cobertura mínima de testes (60% protótipo / 80% produção).
|
|
904
887
|
|
|
905
888
|
---
|
|
906
889
|
|
|
@@ -1016,7 +999,7 @@ function generateClaudeCommandsForce(rootDir) {
|
|
|
1016
999
|
'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\`\`\``,
|
|
1017
1000
|
'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\`\`\``,
|
|
1018
1001
|
'move.md': `Execute o seguinte comando bash: \`trackfw roadmap move $ARGUMENTS\`\n\nO formato esperado é: \`<nome-do-roadmap> <estado>\`\nEstados válidos: \`backlog\`, \`analyzing\`, \`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`,
|
|
1019
|
-
'architect.md': `Você é o guia de arquitetura do trackfw. Ajude o usuário a escolher a stack correta e arquitetar a aplicação em linguagem simples, acessível para times não técnicos.\n\n## Passo 1 — Descoberta de Negócio\n\nFaça ao usuário as seguintes perguntas em linguagem simples, uma por vez:\n\n1. "O que sua aplicação vai fazer? Descreva em 2-3 frases como se fosse explicar para alguém de fora da TI."\n2. "Quantas pessoas vão usar esse sistema simultaneamente? (< 10 pessoas / 10-100 pessoas / > 100 pessoas)"\n3. "Esse sistema vai para produção de verdade ou é um protótipo para validar uma ideia?"\n4. "Você precisa de login/autenticação de usuários? (Sim / Não / Não sei)"\n5. "Tem alguma restrição de tecnologia ou preferência da empresa? (ex: só Java, só Microsoft, etc.)"\n\n---\n\n## Passo 2 — Recomendação de Stack\n\nCom base nas respostas, escolha **UM** dos combos pré-validados:\n\n### Combo A — Protótipo Rápido\n**Quando usar:** prototipagem, validação de ideia, até ~10 usuários, sem pressão de produção.\n- **Frontend:** React + Vite\n- **Backend:** FastAPI (Python) ou Express (Node.js)\n- **Banco:** SQLite + SQLAlchemy / Prisma\n- **Auth:** JWT simples quando necessário\n- **Docker:** Dockerfile básico para o backend\n\n### Combo B — Sistema Pequeno/Médio em Produção\n**Quando usar:** sistema real, 10-100 usuários, robustez e manutenibilidade.\n- **Frontend:** Next.js (SSR + rotas prontas)\n- **Backend:** FastAPI (Python) ou NestJS (Node.js)\n- **Banco:** PostgreSQL + ORM (SQLAlchemy / Prisma / TypeORM)\n- **Auth:** OAuth2 com JWT (Supabase Auth ou Auth0)\n- **Docker:** docker-compose com frontend + backend + banco\n\n### Combo C — Enterprise / Java\n**Quando usar:** integração com sistemas corporativos, > 100 usuários, exigência de Java.\n- **Frontend:** Angular\n- **Backend:** Spring Boot\n- **Banco:** PostgreSQL + Hibernate\n- **Auth:** Spring Security + OAuth2 (Keycloak ou Azure AD)\n- **Docker:** docker-compose com todos os serviços\n\nApresente o combo recomendado com explicação simples do motivo.\n\n---\n\n## Passo 3 — Arquitetura em Camadas (explicação simples)\n\nExplique a arquitetura com uma metáfora de negócio:\n\n"Pense na aplicação como um restaurante:\n- **Frontend** = o salão: o que o cliente vê e interage\n- **Backend** = a cozinha: onde as regras de negócio acontecem, nunca exposta diretamente\n- **Banco de dados** = a despensa: onde os dados ficam guardados, acessada só pela cozinha"\n\nReforce as
|
|
1002
|
+
'architect.md': `Você é o guia de arquitetura do trackfw. Ajude o usuário a escolher a stack correta e arquitetar a aplicação em linguagem simples, acessível para times não técnicos.\n\n## Passo 1 — Descoberta de Negócio\n\nFaça ao usuário as seguintes perguntas em linguagem simples, uma por vez:\n\n1. "O que sua aplicação vai fazer? Descreva em 2-3 frases como se fosse explicar para alguém de fora da TI."\n2. "Quantas pessoas vão usar esse sistema simultaneamente? (< 10 pessoas / 10-100 pessoas / > 100 pessoas)"\n3. "Esse sistema vai para produção de verdade ou é um protótipo para validar uma ideia?"\n4. "Você precisa de login/autenticação de usuários? (Sim / Não / Não sei)"\n5. "Tem alguma restrição de tecnologia ou preferência da empresa? (ex: só Java, só Microsoft, etc.)"\n\n---\n\n## Passo 2 — Recomendação de Stack\n\nCom base nas respostas, escolha **UM** dos combos pré-validados:\n\n### Combo A — Protótipo Rápido\n**Quando usar:** prototipagem, validação de ideia, até ~10 usuários, sem pressão de produção.\n- **Frontend:** React + Vite\n- **Backend:** FastAPI (Python) ou Express (Node.js)\n- **Banco:** SQLite + SQLAlchemy / Prisma\n- **Auth:** JWT simples quando necessário\n- **Docker:** Dockerfile básico para o backend\n\n### Combo B — Sistema Pequeno/Médio em Produção\n**Quando usar:** sistema real, 10-100 usuários, robustez e manutenibilidade.\n- **Frontend:** Next.js (SSR + rotas prontas)\n- **Backend:** FastAPI (Python) ou NestJS (Node.js)\n- **Banco:** PostgreSQL + ORM (SQLAlchemy / Prisma / TypeORM)\n- **Auth:** OAuth2 com JWT (Supabase Auth ou Auth0)\n- **Docker:** docker-compose com frontend + backend + banco\n\n### Combo C — Enterprise / Java\n**Quando usar:** integração com sistemas corporativos, > 100 usuários, exigência de Java.\n- **Frontend:** Angular\n- **Backend:** Spring Boot\n- **Banco:** PostgreSQL + Hibernate\n- **Auth:** Spring Security + OAuth2 (Keycloak ou Azure AD)\n- **Docker:** docker-compose com todos os serviços\n\nApresente o combo recomendado com explicação simples do motivo.\n\n---\n\n## Passo 3 — Arquitetura em Camadas (explicação simples)\n\nExplique a arquitetura com uma metáfora de negócio:\n\n"Pense na aplicação como um restaurante:\n- **Frontend** = o salão: o que o cliente vê e interage\n- **Backend** = a cozinha: onde as regras de negócio acontecem, nunca exposta diretamente\n- **Banco de dados** = a despensa: onde os dados ficam guardados, acessada só pela cozinha"\n\nReforce as **Architecture Directives** já injetadas no CLAUDE.md deste projeto: separação em 3 camadas sem dados em memória (sempre DB + ORM), auth + Docker + .env desde o dia 1, validação em 2 camadas, contrato OpenAPI antes de codar, wave de segurança em todo roadmap e cobertura mínima de testes (60% protótipo / 80% produção).\n\n---\n\n## Passo 4 — Gerar o ADR de Stack\n\nExecute \`/trackfw:adr\` com o título: \`"Stack e arquitetura em camadas — [nome do projeto]"\`\n\nO ADR deve registrar a stack escolhida (combo e componentes), motivação baseada nas respostas, alternativas descartadas e princípios de arquitetura adotados.\n\n---\n\n## Passo 5 — Próximos Passos\n\nOriente o usuário:\n\n\`\`\`\n✅ Stack definida. Próximos passos:\n\n1. Crie a REQ da primeira feature com /trackfw:req\n2. Gere o roadmap em microlotes com /trackfw:roadmap\n3. Inicie a implementação com /trackfw:implement\n\`\`\``,
|
|
1020
1003
|
}
|
|
1021
1004
|
|
|
1022
1005
|
for (const [filename, content] of Object.entries(commands)) {
|
|
@@ -1096,6 +1079,7 @@ module.exports = {
|
|
|
1096
1079
|
scaffold,
|
|
1097
1080
|
writeTrackfwConfig,
|
|
1098
1081
|
generateValidateScript,
|
|
1082
|
+
generateAttentionScripts,
|
|
1099
1083
|
generateCIWorkflow,
|
|
1100
1084
|
generateGitHooks,
|
|
1101
1085
|
generateCommitMsgHook,
|
package/src/validator/index.js
CHANGED
|
@@ -735,6 +735,33 @@ function validateFilenameUniqueness() {
|
|
|
735
735
|
return violations
|
|
736
736
|
}
|
|
737
737
|
|
|
738
|
+
// validateBranchHasWIPRoadmap — verifica que branch feat/fix/refactor tem ao menos um roadmap em wip/
|
|
739
|
+
function validateBranchHasWIPRoadmap() {
|
|
740
|
+
const { execSync } = require('child_process')
|
|
741
|
+
let branch
|
|
742
|
+
try {
|
|
743
|
+
branch = execSync('git symbolic-ref --short HEAD', { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] }).trim()
|
|
744
|
+
} catch {
|
|
745
|
+
return [] // não é git repo ou HEAD detached
|
|
746
|
+
}
|
|
747
|
+
if (!branch.startsWith('feat/') && !branch.startsWith('fix/') && !branch.startsWith('refactor/')) {
|
|
748
|
+
return []
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
const cfg = config.load()
|
|
752
|
+
const wipDirs = resolveWIPDirs(cfg)
|
|
753
|
+
let total = 0
|
|
754
|
+
for (const wipDir of wipDirs) {
|
|
755
|
+
const files = listDir(wipDir).filter(f => f.endsWith('.md'))
|
|
756
|
+
total += files.length
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
if (total === 0) {
|
|
760
|
+
return [`branch "${branch}" is a feat/fix/refactor branch but no roadmap is in wip/ — create governance artifacts first:\n trackfw req new "title"\n trackfw roadmap new "title"\n trackfw roadmap move <name> wip`]
|
|
761
|
+
}
|
|
762
|
+
return []
|
|
763
|
+
}
|
|
764
|
+
|
|
738
765
|
// _itemMeta: mapa de message → { rule, file } para enriquecer saída JSON.
|
|
739
766
|
// Populado em applyRule e nos pushs diretos do validateUnfiltered.
|
|
740
767
|
// Permanece em memória apenas durante a execução de uma chamada validate*.
|
|
@@ -816,6 +843,7 @@ async function validateUnfiltered() {
|
|
|
816
843
|
applyRule('ref_targets_exist', validateRefTargetsExist(), violations, warnings)
|
|
817
844
|
applyRule('folder_status', validateFolderStatusCoherence(), violations, warnings)
|
|
818
845
|
applyRule('filename_uniqueness', validateFilenameUniqueness(), violations, warnings)
|
|
846
|
+
applyRule('branch_has_wip_roadmap', validateBranchHasWIPRoadmap(), violations, warnings)
|
|
819
847
|
applyRule('blocked_by_draft_adr', validateREQsNotBlockedByDraftADRs(), violations, warnings)
|
|
820
848
|
|
|
821
849
|
// Regras configuráveis via applyRule (popula _itemMeta automaticamente)
|
|
@@ -979,6 +1007,7 @@ module.exports = {
|
|
|
979
1007
|
validateRefTargetsExist,
|
|
980
1008
|
validateFolderStatusCoherence,
|
|
981
1009
|
validateFilenameUniqueness,
|
|
1010
|
+
validateBranchHasWIPRoadmap,
|
|
982
1011
|
// novas funções ML-2B
|
|
983
1012
|
contentHasMarker,
|
|
984
1013
|
ruleSeverity,
|