trackfw 6.9.1 → 6.10.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/integrations/render.js +102 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "trackfw",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.10.0",
|
|
4
4
|
"description": "Governance CLI for AI-native software delivery and AI coding agents. Enforces traceability: ADR → REQ → ROADMAP → kanban. Native support for Codex, Claude Code, Gemini CLI, Cursor, Copilot, Windsurf, Amazon Q, OpenCode, and Kiro.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cli",
|
|
@@ -67,6 +67,92 @@ function resolveModel(model) {
|
|
|
67
67
|
return MODEL_MAP[model] || ''
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
+
// Mapa de model: nomes canônicos do catálogo → ID de modelo aceito pelo Codex
|
|
71
|
+
// CLI. Fonte: documentação Codex CLI pesquisada em 2026-08-14 (ver ADR
|
|
72
|
+
// ADR-2026-08-14-roteamento-de-model-tier-por-alvo-no-render-de-agentes-para-
|
|
73
|
+
// codex-e-cursor). IDs de modelo Codex são versionados e mudam com o ciclo de
|
|
74
|
+
// release da OpenAI.
|
|
75
|
+
const CODEX_MODEL_MAP = { opus: 'gpt-5.4', sonnet: 'gpt-5.4-mini' }
|
|
76
|
+
|
|
77
|
+
// resolveModelCodex converte o modelo canônico para o ID de modelo aceito
|
|
78
|
+
// pelo Codex CLI. Retorna o valor mapeado, ou string vazia se a linha model
|
|
79
|
+
// deve ser omitida (valor desconhecido ou ausente). Espelha
|
|
80
|
+
// internal/integrations/render.go:mapModelCodex.
|
|
81
|
+
function resolveModelCodex(model) {
|
|
82
|
+
if (!model) return ''
|
|
83
|
+
return CODEX_MODEL_MAP[model] || ''
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Mapa de model: nomes canônicos do catálogo → valor aceito pela Cursor
|
|
87
|
+
// (fonte: cursor.com/docs/subagents, ver ADR ADR-2026-08-14-roteamento-de-
|
|
88
|
+
// model-tier-por-alvo-no-render-de-agentes-para-codex-e-cursor).
|
|
89
|
+
const CURSOR_MODEL_MAP = { opus: 'claude-opus-5[effort=high]', sonnet: 'composer-2.5[fast=true]' }
|
|
90
|
+
|
|
91
|
+
// mapModelCursor converte o modelo canônico para o valor aceito pela Cursor.
|
|
92
|
+
// Retorna o valor mapeado, ou string vazia se a linha "model:" deve ser
|
|
93
|
+
// removida do frontmatter (Cursor cai no default "inherit"/Auto). Espelha
|
|
94
|
+
// internal/integrations/render.go:mapModelCursor.
|
|
95
|
+
function mapModelCursor(model) {
|
|
96
|
+
if (!model) return ''
|
|
97
|
+
return CURSOR_MODEL_MAP[model] || ''
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// rewriteFrontmatterModelLine substitui a linha "model:" do frontmatter de um
|
|
101
|
+
// markdown cru por value, preservando toda outra linha do frontmatter e o
|
|
102
|
+
// corpo byte a byte. Se o frontmatter não tiver linha "model:", uma é
|
|
103
|
+
// anexada como última linha do bloco de frontmatter. Se source não tiver
|
|
104
|
+
// frontmatter reconhecível, source é retornado sem alteração (trimado) —
|
|
105
|
+
// espelha a detecção de fronteira usada por rewriteFrontmatterFields,
|
|
106
|
+
// escopada à chave única "model". Espelha
|
|
107
|
+
// internal/integrations/render.go:rewriteFrontmatterModelLine.
|
|
108
|
+
function rewriteFrontmatterModelLine(source, value) {
|
|
109
|
+
const trimmed = String(source).trim()
|
|
110
|
+
if (!trimmed.startsWith('---\n')) return trimmed
|
|
111
|
+
const end = trimmed.indexOf('\n---', 4)
|
|
112
|
+
if (end < 0) return trimmed
|
|
113
|
+
const frontmatter = trimmed.slice(4, end)
|
|
114
|
+
const rest = trimmed.slice(end) // começa com "\n---", seguido do corpo
|
|
115
|
+
|
|
116
|
+
const lines = frontmatter.split('\n')
|
|
117
|
+
let found = false
|
|
118
|
+
for (let i = 0; i < lines.length; i++) {
|
|
119
|
+
const line = lines[i]
|
|
120
|
+
const separator = line.indexOf(':')
|
|
121
|
+
if (separator < 0 || line.slice(0, separator).trim() !== 'model') continue
|
|
122
|
+
const rawValue = line.slice(separator + 1).trim()
|
|
123
|
+
const quoted = rawValue.length >= 2 && rawValue.startsWith('"') && rawValue.endsWith('"')
|
|
124
|
+
lines[i] = quoted ? `model: "${value}"` : `model: ${value}`
|
|
125
|
+
found = true
|
|
126
|
+
break
|
|
127
|
+
}
|
|
128
|
+
if (!found) lines.push(`model: ${value}`)
|
|
129
|
+
|
|
130
|
+
return `---\n${lines.join('\n')}${rest}`
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// removeFrontmatterModelLine remove a linha "model:" do frontmatter de um
|
|
134
|
+
// markdown cru, se presente, preservando toda outra linha do frontmatter e o
|
|
135
|
+
// corpo byte a byte. Se source não tiver linha "model:" ou frontmatter
|
|
136
|
+
// reconhecível, source é retornado sem alteração (trimado). Espelha
|
|
137
|
+
// internal/integrations/render.go:removeFrontmatterModelLine.
|
|
138
|
+
function removeFrontmatterModelLine(source) {
|
|
139
|
+
const trimmed = String(source).trim()
|
|
140
|
+
if (!trimmed.startsWith('---\n')) return trimmed
|
|
141
|
+
const end = trimmed.indexOf('\n---', 4)
|
|
142
|
+
if (end < 0) return trimmed
|
|
143
|
+
const frontmatter = trimmed.slice(4, end)
|
|
144
|
+
const rest = trimmed.slice(end) // começa com "\n---", seguido do corpo
|
|
145
|
+
|
|
146
|
+
const lines = frontmatter.split('\n')
|
|
147
|
+
const kept = lines.filter(line => {
|
|
148
|
+
const separator = line.indexOf(':')
|
|
149
|
+
return !(separator >= 0 && line.slice(0, separator).trim() === 'model')
|
|
150
|
+
})
|
|
151
|
+
if (kept.length === lines.length) return trimmed
|
|
152
|
+
|
|
153
|
+
return `---\n${kept.join('\n')}${rest}`
|
|
154
|
+
}
|
|
155
|
+
|
|
70
156
|
// toolsFor retorna SET_ARCH para o agente canônico "architect" (item.id do
|
|
71
157
|
// catálogo, não o nome renderizado — que pode ser customizado pela
|
|
72
158
|
// identidade), SET_IMPL para os demais. IDs proibidos (edit_file, read_file,
|
|
@@ -235,12 +321,13 @@ function frontmatterName(content) {
|
|
|
235
321
|
// normalize(content) — a mesma expressão usada antes de existir suporte a
|
|
236
322
|
// identidade — então a saída sem identidade é garantida byte a byte
|
|
237
323
|
// inalterada por construção, não por coincidência.
|
|
238
|
-
function render({ kind, content, capability, item, identity: cfg }) {
|
|
324
|
+
function render({ kind, content, capability, item, identity: cfg, target }) {
|
|
239
325
|
if (kind === 'skills') return normalize(content)
|
|
240
326
|
|
|
241
327
|
const id = item && cfg ? lookupIdentity(cfg, item.id) : undefined
|
|
242
328
|
const parts = markdownParts(content)
|
|
243
329
|
let { name, description, body } = parts
|
|
330
|
+
let source = content
|
|
244
331
|
let greeting = ''
|
|
245
332
|
if (id) {
|
|
246
333
|
greeting = greetingLine(id.display_name, (cfg && cfg.user_nickname) || '')
|
|
@@ -250,7 +337,14 @@ function render({ kind, content, capability, item, identity: cfg }) {
|
|
|
250
337
|
}
|
|
251
338
|
|
|
252
339
|
if (capability.representation === 'custom-agent-toml') {
|
|
253
|
-
|
|
340
|
+
const lines = [
|
|
341
|
+
`name = ${JSON.stringify(name.replaceAll('-', '_'))}`,
|
|
342
|
+
`description = ${JSON.stringify(description)}`,
|
|
343
|
+
]
|
|
344
|
+
const mappedModel = resolveModelCodex(parts.model)
|
|
345
|
+
if (mappedModel) lines.push(`model = ${JSON.stringify(mappedModel)}`)
|
|
346
|
+
lines.push(`developer_instructions = ${JSON.stringify(body)}`)
|
|
347
|
+
return `${lines.join('\n')}\n`
|
|
254
348
|
}
|
|
255
349
|
if (capability.representation === 'cli-agent-json' || capability.representation === 'agent-json') {
|
|
256
350
|
// Ordem alfabética das chaves (description, name, prompt) é obrigatória:
|
|
@@ -310,8 +404,12 @@ function render({ kind, content, capability, item, identity: cfg }) {
|
|
|
310
404
|
return out
|
|
311
405
|
}
|
|
312
406
|
|
|
313
|
-
if (
|
|
314
|
-
|
|
407
|
+
if (target === 'cursor') {
|
|
408
|
+
const mappedModel = mapModelCursor(parts.model)
|
|
409
|
+
source = mappedModel ? rewriteFrontmatterModelLine(source, mappedModel) : removeFrontmatterModelLine(source)
|
|
410
|
+
}
|
|
411
|
+
if (!id) return normalize(source)
|
|
412
|
+
const withBody = insertBodyPrefix(source, greeting)
|
|
315
413
|
const withFrontmatter = rewriteFrontmatterFields(withBody, name, description)
|
|
316
414
|
const withSignature = rewriteSignatureLine(withFrontmatter, id.display_name)
|
|
317
415
|
return normalize(withSignature)
|