trackfw 2.12.4 → 2.14.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/README.md CHANGED
@@ -10,7 +10,7 @@
10
10
 
11
11
  **trackfw** is an open-source governance CLI for AI-native software delivery. It enforces a traceable chain from architectural decision to shipped code — without SaaS, accounts, or databases. Markdown files are state.
12
12
 
13
- It is designed for teams looking for an ADR / REQ / ROADMAP governance framework with native support for AI coding assistants such as Codex, Claude Code, Gemini CLI, Cursor, GitHub Copilot, Windsurf, and Amazon Q.
13
+ It is designed for teams looking for an ADR / REQ / ROADMAP governance framework with native support for AI coding assistants such as Codex, Claude Code, Gemini CLI, Antigravity, Cursor, GitHub Copilot, Windsurf, Amazon Q, and Kiro.
14
14
 
15
15
  ```
16
16
  ADR → REQ → ROADMAP → backlog / wip / blocked / done / abandoned
@@ -93,8 +93,8 @@ npm install -g trackfw
93
93
  ```
94
94
 
95
95
  The npm package is pure Node.js — no compiled binary or postinstall download.
96
- It works wherever Node.js ≥ 18 is installed. Shared behavior and intentional
97
- Go-only integration installers follow the [CLI parity contract](docs/cli-parity.md).
96
+ It works wherever Node.js ≥ 18 is installed. Shared behavior, including the AI
97
+ integration lifecycle, follows the [CLI parity contract](docs/cli-parity.md).
98
98
 
99
99
  ### pip
100
100
 
@@ -153,15 +153,17 @@ trackfw status
153
153
  | `trackfw plugins list` | List installed plugins |
154
154
  | `trackfw plugins add <user/repo>` | Install a plugin from GitHub Releases |
155
155
  | `trackfw plugins remove <name>` | Remove an installed plugin |
156
- | `trackfw agents` | Install Claude Code subagents *(Go binary only)* |
157
- | `trackfw gemini` | Install Gemini CLI skills and commands *(Go binary only)* |
158
- | `trackfw cursor` | Install Cursor rules *(Go binary only)* |
159
- | `trackfw copilot` | Install GitHub Copilot instructions *(Go binary only)* |
160
- | `trackfw windsurf` | Install Windsurf rules and workflows *(Go binary only)* |
161
- | `trackfw amazonq` | Install Amazon Q Developer rules *(Go binary only)* |
156
+ | `trackfw agents list` | List available agents and deployment state across AI CLIs |
157
+ | `trackfw agents install` | Install selected specialist agents |
158
+ | `trackfw agents update` | Safely update managed agents |
159
+ | `trackfw agents uninstall` | Remove selected owned agent deployments |
160
+ | `trackfw skills list` | List available governance skills and deployment state |
161
+ | `trackfw skills install/update/uninstall` | Manage selected governance skills |
162
+ | `trackfw gemini`, `cursor`, `copilot`, `windsurf`, `amazonq` | Historical aliases in the Go distribution only; use `agents`/`skills` |
162
163
  | `trackfw version` | Print version |
163
164
 
164
- > **Go binary only** commands (`agents`, `gemini`, `cursor`, `copilot`, `windsurf`, `amazonq`) are available when installed via brew, `install.sh`, or `go install`. When using the npm package, AI integrations are installed through `trackfw init`.
165
+ The same lifecycle contract is available from the Go/Homebrew, npm, and PyPI
166
+ distributions.
165
167
 
166
168
  ---
167
169
 
@@ -325,19 +327,41 @@ $ trackfw status
325
327
 
326
328
  ## AI assistant integration
327
329
 
328
- `trackfw init` asks which AI tools your team uses and installs native governance context for each. When using the Go binary (brew, `install.sh`, `go install`), each integration can also be run as a standalone command.
330
+ `trackfw init` can install initial AI integrations. The `agents` and `skills`
331
+ command families provide the complete lifecycle in every distribution.
329
332
 
330
- | Command | Installs | Format |
331
- |---|---|---|
332
- | `trackfw init --ai-tools codex` | `AGENTS.md`, 5 repository skills, 6 custom agents, Codex config and hooks | `.agents/skills/` + `.codex/` |
333
- | `trackfw agents` | 10 subagents in `~/.claude/agents/` | Claude Code `.md` with frontmatter |
334
- | `trackfw gemini` | GEMINI.md + 10 skills + 3 commands | `~/.gemini/` + project root |
335
- | `trackfw cursor` | 10 rules in `.cursor/rules/` | `.mdc` with YAML frontmatter |
336
- | `trackfw copilot` | `copilot-instructions.md` + 10 instructions + 10 prompts | `.github/` |
337
- | `trackfw windsurf` | 10 rules + workflows in `.windsurf/` + global rules | Appends to `~/.codeium/windsurf/memories/` |
338
- | `trackfw amazonq` | 10 rules in `.amazonq/rules/` | Plain Markdown |
333
+ | Target | Native/fallback representation |
334
+ |---|---|
335
+ | Claude Code | Subagent Markdown and Agent Skills |
336
+ | Codex | Custom-agent TOML and Agent Skills |
337
+ | Gemini CLI | Agent Markdown and skills |
338
+ | Antigravity | Agent/skill directories; explicit `legacy-cli` surface available |
339
+ | Cursor | Agent Markdown and skills |
340
+ | GitHub Copilot | Custom agents and Agent Skills |
341
+ | Windsurf | Specialist-skill fallback for agents and native skills |
342
+ | Amazon Q | CLI agent JSON and workflow-rule fallback |
343
+ | Kiro | Native IDE/CLI agents and Agent Skills |
344
+
345
+ ```bash
346
+ # Inspect every deployment, including legacy surfaces
347
+ trackfw agents list --json
348
+
349
+ # Install selected items in the repository
350
+ trackfw agents install --targets codex,claude --items architect,backend --scope project
351
+ trackfw skills install --targets codex,antigravity --items governance,implement --scope project
352
+
353
+ # Select an alternate surface explicitly
354
+ trackfw agents install --targets kiro --surface kiro=cli
355
+ trackfw agents list --targets antigravity --surface antigravity=legacy-cli
356
+ ```
339
357
 
340
- Each installer is idempotent — running it twice never overwrites your customizations.
358
+ Without `--targets`, mutations open a numbered/checkbox selector in a TTY and
359
+ fail with an actionable error in CI. The lifecycle reports `not-installed`,
360
+ `current`, `outdated`, or `modified`. A manifest under `.trackfw/` records
361
+ scope-specific ownership, version, SHA-256, and shared claims. Modified files
362
+ are never replaced or removed unless `--force` is explicit, and unmanaged files
363
+ are never removed. Known historical templates are adopted without overwriting;
364
+ unknown unmanaged content cannot be adopted by `update`, even with `--force`.
341
365
 
342
366
  The 10 roles installed for each tool: **architect · backend · frontend · qa · infra · security · code-quality · dba · ux · data**
343
367
 
@@ -352,7 +376,7 @@ The 10 roles installed for each tool: **architect · backend · frontend · qa
352
376
  ? Package manager? npm / pnpm / yarn / bun
353
377
  ? Git hooks? husky / lefthook / none
354
378
  ? CI system? GitHub Actions / GitLab CI / none
355
- ? Which AI assistants? Claude Code / OpenAI Codex / Gemini CLI / Cursor / Copilot / Windsurf / Amazon Q
379
+ ? Which AI assistants? Claude / Codex / Gemini / Antigravity / Cursor / Copilot / Windsurf / Amazon Q / Kiro
356
380
  ```
357
381
 
358
382
  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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "trackfw",
3
- "version": "2.12.4",
3
+ "version": "2.14.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, and Amazon Q.",
5
5
  "keywords": [
6
6
  "cli",
@@ -0,0 +1,3 @@
1
+ 'use strict'
2
+
3
+ module.exports = require('./integrations').createLifecycleCommand('agents')
@@ -27,6 +27,8 @@ function createProgram() {
27
27
  program.addCommand(require('./help'))
28
28
  program.addCommand(require('./configure'))
29
29
  program.addCommand(require('./version'))
30
+ program.addCommand(require('./agents'))
31
+ program.addCommand(require('./skills'))
30
32
 
31
33
  const { createServeCommand } = require('./serve')
32
34
  program.addCommand(createServeCommand())
@@ -4,7 +4,7 @@ const { t } = require('../i18n')
4
4
 
5
5
  const cmd = new Command('init')
6
6
  cmd.description(t('init.description'))
7
- cmd.option('--ai-tools <tools>', 'Comma-separated AI tools to configure (codex,claude,gemini,cursor,copilot,windsurf,amazonq)', '')
7
+ cmd.option('--ai-tools <tools>', 'Comma-separated AI tools to configure (claude,codex,gemini,antigravity,cursor,copilot,windsurf,amazonq,kiro)', '')
8
8
  cmd.action(async (options) => {
9
9
  const path = require('path')
10
10
  const generators = require('../generators/init')
@@ -22,17 +22,10 @@ cmd.action(async (options) => {
22
22
  }
23
23
  await generators.scaffold(cfg)
24
24
  const aiTools = String(options.aiTools || '').split(',').map(tool => tool.trim()).filter(Boolean)
25
+ const supported = new Set(['claude', 'codex', 'gemini', 'antigravity', 'cursor', 'copilot', 'windsurf', 'amazonq', 'kiro'])
25
26
  for (const tool of aiTools) {
26
- switch (tool) {
27
- case 'codex': require('../generators/codex').installCodex(process.cwd()); break
28
- case 'claude': await generators.installAgents(); break
29
- case 'gemini': await generators.installGemini(); break
30
- case 'cursor': await generators.installCursor(); break
31
- case 'copilot': await generators.installCopilot(); break
32
- case 'windsurf': await generators.installWindsurf(); break
33
- case 'amazonq': await generators.installAmazonQ(); break
34
- default: throw new Error(`Unsupported AI tool: ${tool}`)
35
- }
27
+ if (!supported.has(tool)) throw new Error(`Unsupported AI tool: ${tool}`)
28
+ await generators.installIntegrationTarget(tool, process.cwd())
36
29
  }
37
30
  console.log(`\n${t('init.success')}`)
38
31
  require('../generators/init').printArchitectNextSteps(process.cwd())
@@ -157,10 +150,12 @@ cmd.action(async (options) => {
157
150
  { name: 'Claude Code', value: 'claude' },
158
151
  { name: 'OpenAI Codex', value: 'codex' },
159
152
  { name: 'Gemini CLI', value: 'gemini' },
153
+ { name: 'Google Antigravity', value: 'antigravity' },
160
154
  { name: 'Cursor', value: 'cursor' },
161
155
  { name: 'GitHub Copilot', value: 'copilot' },
162
156
  { name: 'Windsurf', value: 'windsurf' },
163
157
  { name: 'Amazon Q Developer', value: 'amazonq' },
158
+ { name: 'Kiro', value: 'kiro' },
164
159
  ],
165
160
  })
166
161
  } catch (err) {
@@ -183,17 +178,7 @@ cmd.action(async (options) => {
183
178
  const cfg = { projectName, projectType, frontend, backend, backendFramework, pkgManager, hooks, ci, requireReqInCommit }
184
179
  await generators.scaffold(cfg)
185
180
 
186
- for (const tool of (aiTools || [])) {
187
- switch (tool) {
188
- case 'codex': require('../generators/codex').installCodex(process.cwd()); break
189
- case 'claude': await generators.installAgents(); break
190
- case 'gemini': await generators.installGemini(); break
191
- case 'cursor': await generators.installCursor(); break
192
- case 'copilot': await generators.installCopilot(); break
193
- case 'windsurf': await generators.installWindsurf(); break
194
- case 'amazonq': await generators.installAmazonQ(); break
195
- }
196
- }
181
+ for (const tool of (aiTools || [])) await generators.installIntegrationTarget(tool, process.cwd())
197
182
 
198
183
  console.log(`\n${t('init.success')}`)
199
184
  require('../generators/init').printArchitectNextSteps(process.cwd())
@@ -0,0 +1,67 @@
1
+ 'use strict'
2
+
3
+ const { Command } = require('commander')
4
+ const { catalog, execute, parseSurfaces } = require('../integrations')
5
+
6
+ const csv = value => String(value).split(',').map(entry => entry.trim()).filter(Boolean)
7
+ const collect = (value, previous) => previous.concat(value)
8
+
9
+ function human(result) {
10
+ const lines = [`Available ${result.kind} (catalog ${result.catalog_version}):`]
11
+ for (const item of result.items) lines.push(` ${item.id.padEnd(14)} ${item.name} — ${item.description}`)
12
+ lines.push('', 'Deployments:')
13
+ for (const deployment of result.deployments) {
14
+ const managed = deployment.managed ? 'managed' : 'unmanaged'
15
+ lines.push(` ${deployment.target.padEnd(12)} ${deployment.surface.padEnd(12)} ${deployment.item.padEnd(14)} ${deployment.state.padEnd(13)} ${deployment.destination} (${managed})`)
16
+ }
17
+ return lines.join('\n')
18
+ }
19
+
20
+ async function promptSelection(kind, options, prompts = require('@inquirer/prompts')) {
21
+ const { checkbox } = prompts
22
+ options.targets = await checkbox({ message: 'Target CLIs', choices: catalog.targets.map(target => ({ name: target.name, value: target.id })), required: true })
23
+ options.items = await checkbox({ message: `${kind} to manage`, choices: catalog[kind].map(item => ({ name: item.name, value: item.id })), required: true })
24
+ }
25
+
26
+ async function promptAmbiguousSurfaces(kind, options, prompts = require('@inquirer/prompts')) {
27
+ const { select } = prompts
28
+ const selected = parseSurfaces(options.surfaces)
29
+ for (const targetID of options.targets || []) {
30
+ if (selected[targetID]) continue
31
+ const target = catalog.targets.find(entry => entry.id === targetID)
32
+ const eligible = target.surfaces.filter(surface => !['legacy', 'unsupported'].includes(surface.capabilities[kind].support_level))
33
+ if (eligible.length <= 1) continue
34
+ const surface = await select({ message: `Surface for ${target.name}`, choices: eligible.map(entry => ({ name: entry.name, value: entry.id })) })
35
+ options.surfaces.push(`${targetID}=${surface}`)
36
+ }
37
+ }
38
+
39
+ function createLifecycleCommand(kind) {
40
+ const root = new Command(kind).description(`Manage trackfw ${kind}`)
41
+ for (const operation of ['list', 'install', 'uninstall', 'update']) {
42
+ const command = new Command(operation)
43
+ .option('--targets <targets>', 'Comma-separated target CLIs', csv)
44
+ .option('--items <items>', `Comma-separated ${kind} IDs`, csv)
45
+ .option('--scope <scope>', 'Installation scope: project or global', 'project')
46
+ .option('--surface <target=surface>', 'Surface selection (repeatable)', collect, [])
47
+ .option('--json', 'Print deterministic JSON')
48
+ .option('--force', 'Replace or remove modified artifacts')
49
+ command.action(async options => {
50
+ options.surfaces = options.surface || []
51
+ if (options.scope !== 'project' && options.scope !== 'global') throw new Error(`Unsupported scope: ${options.scope}`)
52
+ const mutation = operation !== 'list'
53
+ if (mutation && (!options.targets || !options.targets.length)) {
54
+ if (!process.stdin.isTTY) throw new Error(`${operation} requires --targets in non-interactive mode`)
55
+ await promptSelection(kind, options)
56
+ }
57
+ if (mutation && process.stdin.isTTY) await promptAmbiguousSurfaces(kind, options)
58
+ options.allSurfaces = operation === 'list'
59
+ const output = execute(kind, operation, options)
60
+ console.log(options.json ? JSON.stringify(output) : human(output))
61
+ })
62
+ root.addCommand(command)
63
+ }
64
+ return root
65
+ }
66
+
67
+ module.exports = { createLifecycleCommand, csv, human, promptSelection, promptAmbiguousSurfaces }
@@ -0,0 +1,3 @@
1
+ 'use strict'
2
+
3
+ module.exports = require('./integrations').createLifecycleCommand('skills')
@@ -73,7 +73,15 @@ cmd.action(() => {
73
73
  }
74
74
  if (fs.existsSync(path.join(cwd, 'AGENTS.md')) || fs.existsSync(path.join(cwd, '.codex'))) {
75
75
  try {
76
- require('../generators/codex').installCodex(cwd);
76
+ const { buildPlans, IntegrationManager } = require('../integrations');
77
+ const roots = { projectRoot: cwd };
78
+ const manager = new IntegrationManager(roots);
79
+ for (const kind of ['agents', 'skills']) {
80
+ const plans = buildPlans(kind, { targets: ['codex'], scope: 'project' });
81
+ const statuses = manager.inspect(plans);
82
+ const existing = plans.filter((_, index) => statuses[index].state !== 'not-installed');
83
+ manager.update(existing);
84
+ }
77
85
  } catch (e) {
78
86
  console.warn(` ⚠ Codex integration: ${e.message}`);
79
87
  }
@@ -81,13 +81,11 @@ Review the branch as an owner. Return findings first, ordered by severity, with
81
81
  `,
82
82
  }
83
83
 
84
- function writeManaged(filePath, content) {
85
- fs.mkdirSync(path.dirname(filePath), { recursive: true })
86
- fs.writeFileSync(filePath, content.trim() + '\n', 'utf8')
87
- }
88
-
89
84
  function installCodex(cwd) {
90
85
  const root = cwd || process.cwd()
86
+ const { execute } = require('../integrations')
87
+ execute('agents', 'install', { targets: ['codex'], scope: 'project' }, { projectRoot: root })
88
+ execute('skills', 'install', { targets: ['codex'], scope: 'project' }, { projectRoot: root })
91
89
  injectRulesForTool('codex', root)
92
90
 
93
91
  const configPath = path.join(root, '.codex', 'config.toml')
@@ -98,14 +96,8 @@ function installCodex(cwd) {
98
96
  fs.writeFileSync(configPath, config, 'utf8')
99
97
  }
100
98
 
101
- for (const [name, content] of Object.entries(skills)) {
102
- writeManaged(path.join(root, '.agents', 'skills', name, 'SKILL.md'), content)
103
- }
104
- for (const [name, content] of Object.entries(agents)) {
105
- writeManaged(path.join(root, '.codex', 'agents', name), content)
106
- }
107
99
  injectCodexHooks(root)
108
100
  console.log(' ✓ Codex: AGENTS.md, skills, custom agents and hooks')
109
101
  }
110
102
 
111
- module.exports = { installCodex }
103
+ module.exports = { installCodex, legacyCodexFixtures: { skills, agents } }
@@ -931,17 +931,13 @@ Oriente o usuário:
931
931
  // AI tool installers
932
932
  // ---------------------------------------------------------------------------
933
933
 
934
- /**
935
- * installAgents — instala agentes Claude Code em ~/.claude/agents/
936
- * O pacote npm não embute os templates binários do Go, por isso informa
937
- * ao usuário que esta operação requer o binário Go instalado.
938
- */
939
- async function installAgents() {
940
- // CLAUDE.md já tratado por generateClaudeMD via injectOrUpdateRules
941
- console.log(' ✓ claude (agentes — execute `trackfw install agents` com o binário Go para instalar os templates)')
934
+ /** Compatibility entrypoint backed by the canonical integration engine. */
935
+ async function installAgents(cwd = process.cwd()) {
936
+ await installIntegrationTarget('claude', cwd)
942
937
  }
943
938
 
944
939
  async function installGemini(cwd = process.cwd()) {
940
+ await installIntegrationTarget('gemini', cwd)
945
941
  try {
946
942
  injectRulesForTool('gemini', cwd)
947
943
  console.log(' ✓ trackfw rules → GEMINI.md')
@@ -951,6 +947,7 @@ async function installGemini(cwd = process.cwd()) {
951
947
  }
952
948
 
953
949
  async function installCursor(cwd = process.cwd()) {
950
+ await installIntegrationTarget('cursor', cwd)
954
951
  try {
955
952
  injectRulesForTool('cursor', cwd)
956
953
  console.log(' ✓ trackfw rules → .cursor/rules/trackfw.mdc')
@@ -960,6 +957,7 @@ async function installCursor(cwd = process.cwd()) {
960
957
  }
961
958
 
962
959
  async function installCopilot(cwd = process.cwd()) {
960
+ await installIntegrationTarget('copilot', cwd)
963
961
  try {
964
962
  injectRulesForTool('copilot', cwd)
965
963
  console.log(' ✓ trackfw rules → .github/copilot-instructions.md')
@@ -969,6 +967,7 @@ async function installCopilot(cwd = process.cwd()) {
969
967
  }
970
968
 
971
969
  async function installWindsurf(cwd = process.cwd()) {
970
+ await installIntegrationTarget('windsurf', cwd)
972
971
  try {
973
972
  injectRulesForTool('windsurf', cwd)
974
973
  console.log(' ✓ trackfw rules → .windsurfrules')
@@ -978,6 +977,7 @@ async function installWindsurf(cwd = process.cwd()) {
978
977
  }
979
978
 
980
979
  async function installAmazonQ(cwd = process.cwd()) {
980
+ await installIntegrationTarget('amazonq', cwd)
981
981
  try {
982
982
  injectRulesForTool('amazonq', cwd)
983
983
  console.log(' ✓ trackfw rules → .amazonq/developer/guidelines.md')
@@ -986,6 +986,15 @@ async function installAmazonQ(cwd = process.cwd()) {
986
986
  }
987
987
  }
988
988
 
989
+ /** Install the canonical agents and skills for one AI target. */
990
+ async function installIntegrationTarget(target, cwd = process.cwd()) {
991
+ const { execute } = require('../integrations')
992
+ const roots = { projectRoot: cwd }
993
+ execute('agents', 'install', { targets: [target], scope: 'project' }, roots)
994
+ execute('skills', 'install', { targets: [target], scope: 'project' }, roots)
995
+ console.log(` ✓ ${target} agents and skills`)
996
+ }
997
+
989
998
  /**
990
999
  * generateClaudeCommandsForce — re-gera todos os slash commands, sobrescrevendo arquivos existentes.
991
1000
  */
@@ -1093,6 +1102,7 @@ module.exports = {
1093
1102
  installCopilot,
1094
1103
  installWindsurf,
1095
1104
  installAmazonQ,
1105
+ installIntegrationTarget,
1096
1106
  injectRulesForTool,
1097
1107
  injectRulesDetected,
1098
1108
  trackfwRulesBlock,
@@ -0,0 +1,9 @@
1
+ ---
2
+ name: trackfw-architect
3
+ description: Principal software architect for system design, ADRs and governed multi-agent coordination.
4
+ model: opus
5
+ ---
6
+
7
+ # Architect
8
+
9
+ Map the existing architecture and traceability chain before proposing changes. Record material decisions as ADRs, produce decision-complete plans, and delegate implementation to the appropriate specialist. Do not implement product code.
@@ -0,0 +1,9 @@
1
+ ---
2
+ name: trackfw-backend
3
+ description: Senior backend specialist for APIs, domain logic, integrations and data access.
4
+ model: sonnet
5
+ ---
6
+
7
+ # Backend
8
+
9
+ Implement only the assigned backend scope. Preserve public contracts, Clean Architecture boundaries, observability and trackfw traceability. Run focused build and tests and report evidence and remaining risks.
@@ -0,0 +1,9 @@
1
+ ---
2
+ name: trackfw-code-quality
3
+ description: Code quality specialist for maintainability, static analysis, refactoring and quality gates.
4
+ model: sonnet
5
+ ---
6
+
7
+ # Code Quality
8
+
9
+ Assess duplication, complexity, architecture fitness, coverage and code smells. Run the repository's linters and quality gates, then report actionable findings by impact without expanding the assigned scope.
@@ -0,0 +1,9 @@
1
+ ---
2
+ name: trackfw-data
3
+ description: Data engineering and data science specialist for pipelines, quality, analytics and MLOps.
4
+ model: sonnet
5
+ ---
6
+
7
+ # Data
8
+
9
+ Implement reliable, observable and idempotent data workflows. Validate schemas, lineage, quality expectations and cost before changes, and never substitute invented production data for real contracts.
@@ -0,0 +1,9 @@
1
+ ---
2
+ name: trackfw-dba
3
+ description: Database specialist for modeling, queries, indexes, migrations, backup and recovery.
4
+ model: sonnet
5
+ ---
6
+
7
+ # DBA
8
+
9
+ Inspect schemas and execution plans before proposing changes. Favor safe, reversible migrations, validate backup and recovery constraints, and preserve data integrity and least-privilege access.
@@ -0,0 +1,9 @@
1
+ ---
2
+ name: trackfw-frontend
3
+ description: Senior frontend specialist for accessible, localized and performant user interfaces.
4
+ model: sonnet
5
+ ---
6
+
7
+ # Frontend
8
+
9
+ Implement only the assigned frontend scope. Preserve the design system, WCAG accessibility, localization and browser behavior. Validate the affected build and UI tests before completion.
@@ -0,0 +1,9 @@
1
+ ---
2
+ name: trackfw-infra
3
+ description: Infrastructure specialist for cloud, Kubernetes, GitOps, CI/CD, reliability and FinOps.
4
+ model: sonnet
5
+ ---
6
+
7
+ # Infrastructure
8
+
9
+ Implement only the assigned infrastructure scope. Prefer declarative, least-privilege and reversible changes. Validate plans and manifests without applying to live environments unless explicitly authorized.
@@ -0,0 +1,9 @@
1
+ ---
2
+ name: trackfw-qa
3
+ description: Quality assurance specialist for unit, integration, contract and end-to-end testing.
4
+ model: sonnet
5
+ ---
6
+
7
+ # QA
8
+
9
+ Trace critical flows against roadmap acceptance criteria. Create reproducible tests, investigate regressions and flaky behavior, and report concrete contract gaps with validation evidence.
@@ -0,0 +1,9 @@
1
+ ---
2
+ name: trackfw-security
3
+ description: Security reviewer for trust boundaries, secrets, injection, permissions and supply-chain risk.
4
+ model: sonnet
5
+ ---
6
+
7
+ # Security
8
+
9
+ Perform evidence-backed threat analysis. Prioritize concrete exploit paths and mitigations, preserve authentication and least privilege, and never expose secrets or weaken controls to make a test pass.
@@ -0,0 +1,9 @@
1
+ ---
2
+ name: trackfw-ux
3
+ description: UX and UI specialist for research, interaction design, design systems and accessibility.
4
+ model: sonnet
5
+ ---
6
+
7
+ # UX
8
+
9
+ Evaluate user journeys, interaction consistency and WCAG 2.2 AA accessibility. Ground recommendations in evidence and validate complex or uncertain flows visually before production implementation.
@@ -0,0 +1,33 @@
1
+ {
2
+ "version": "1.1.0",
3
+ "agents": [
4
+ {"id":"architect","name":"Architect","description":"Architecture, ADRs and governed coordination","asset":"assets/agents/architect.md"},
5
+ {"id":"backend","name":"Backend","description":"Backend APIs, domain logic and integrations","asset":"assets/agents/backend.md"},
6
+ {"id":"frontend","name":"Frontend","description":"Accessible and localized user interfaces","asset":"assets/agents/frontend.md"},
7
+ {"id":"qa","name":"QA","description":"Automated testing and acceptance coverage","asset":"assets/agents/qa.md"},
8
+ {"id":"infra","name":"Infrastructure","description":"Cloud, Kubernetes, delivery and reliability","asset":"assets/agents/infra.md"},
9
+ {"id":"security","name":"Security","description":"Threat analysis and DevSecOps controls","asset":"assets/agents/security.md"},
10
+ {"id":"code-quality","name":"Code Quality","description":"Maintainability and quality gates","asset":"assets/agents/code-quality.md"},
11
+ {"id":"dba","name":"DBA","description":"Database modeling, performance and recovery","asset":"assets/agents/dba.md"},
12
+ {"id":"ux","name":"UX","description":"User experience, design systems and accessibility","asset":"assets/agents/ux.md"},
13
+ {"id":"data","name":"Data","description":"Data pipelines, quality, analytics and MLOps","asset":"assets/agents/data.md"}
14
+ ],
15
+ "skills": [
16
+ {"id":"governance","name":"Governance","description":"Inspect and maintain the governance chain","asset":"assets/skills/governance.md"},
17
+ {"id":"plan","name":"Plan","description":"Plan a governed implementation","asset":"assets/skills/plan.md"},
18
+ {"id":"implement","name":"Implement","description":"Execute an approved roadmap","asset":"assets/skills/implement.md"},
19
+ {"id":"review","name":"Review","description":"Review correctness and traceability","asset":"assets/skills/review.md"},
20
+ {"id":"release","name":"Release","description":"Prepare and verify a release","asset":"assets/skills/release.md"}
21
+ ],
22
+ "targets": [
23
+ {"id":"claude","name":"Claude Code","surfaces":[{"id":"cli","name":"Claude Code CLI","scopes":["global","project"],"capabilities":{"agents":{"support_level":"native","representation":"subagent"},"skills":{"support_level":"native","representation":"skill"}},"paths":{"agents":[{"scope":"global","path":"~/.claude/agents/trackfw-{{id}}.md","extension":".md"},{"scope":"project","path":".claude/agents/trackfw-{{id}}.md","extension":".md"}],"skills":[{"scope":"global","path":"~/.claude/skills/trackfw-{{id}}/SKILL.md","extension":".md"},{"scope":"project","path":".claude/skills/trackfw-{{id}}/SKILL.md","extension":".md"}]}}],"auxiliary_artifacts":[{"id":"settings","surface":"cli","scope":"project","path":".claude/settings.json","purpose":"attention hooks","required":false}]},
24
+ {"id":"codex","name":"Codex CLI","surfaces":[{"id":"cli","name":"Codex CLI","scopes":["global","project"],"capabilities":{"agents":{"support_level":"native","representation":"custom-agent-toml"},"skills":{"support_level":"native","representation":"skill"}},"paths":{"agents":[{"scope":"global","path":"~/.codex/agents/trackfw-{{id}}.toml","extension":".toml"},{"scope":"project","path":".codex/agents/trackfw-{{id}}.toml","extension":".toml"}],"skills":[{"scope":"global","path":"~/.agents/skills/trackfw-{{id}}/SKILL.md","extension":".md"},{"scope":"project","path":".agents/skills/trackfw-{{id}}/SKILL.md","extension":".md"}]}}],"auxiliary_artifacts":[{"id":"config","surface":"cli","scope":"project","path":".codex/config.toml","purpose":"custom agent configuration","required":false},{"id":"hooks","surface":"cli","scope":"project","path":".codex/hooks.json","purpose":"attention hooks","required":false}]},
25
+ {"id":"gemini","name":"Gemini CLI","surfaces":[{"id":"cli","name":"Gemini CLI","scopes":["global","project"],"capabilities":{"agents":{"support_level":"native","representation":"agent-markdown"},"skills":{"support_level":"native","representation":"skill"}},"paths":{"agents":[{"scope":"global","path":"~/.gemini/agents/trackfw-{{id}}.md","extension":".md"},{"scope":"project","path":".gemini/agents/trackfw-{{id}}.md","extension":".md"}],"skills":[{"scope":"global","path":"~/.gemini/skills/trackfw-{{id}}/SKILL.md","extension":".md"},{"scope":"project","path":".gemini/skills/trackfw-{{id}}/SKILL.md","extension":".md"}]}}],"auxiliary_artifacts":[{"id":"context","surface":"cli","scope":"global","path":"~/.gemini/GEMINI.md","purpose":"global governance context","required":false},{"id":"settings","surface":"cli","scope":"project","path":".gemini/settings.json","purpose":"attention hooks","required":false}]},
26
+ {"id":"antigravity","name":"Google Antigravity","surfaces":[{"id":"current","name":"Antigravity IDE and CLI","scopes":["global","project"],"capabilities":{"agents":{"support_level":"native","representation":"agent-directory"},"skills":{"support_level":"native","representation":"agent-skill"}},"paths":{"agents":[{"scope":"global","path":"~/.gemini/config/agents/trackfw-{{id}}/agent.md","extension":".md"},{"scope":"project","path":".agents/agents/trackfw-{{id}}/agent.md","extension":".md"}],"skills":[{"scope":"global","path":"~/.gemini/config/skills/trackfw-{{id}}/SKILL.md","extension":".md"},{"scope":"project","path":".agents/skills/trackfw-{{id}}/SKILL.md","extension":".md"}]}},{"id":"legacy-cli","name":"Antigravity legacy CLI","scopes":["global","project"],"capabilities":{"agents":{"support_level":"legacy","representation":"agent-json"},"skills":{"support_level":"legacy","representation":"agent-skill"}},"paths":{"agents":[{"scope":"global","path":"~/.gemini/antigravity-cli/agents/trackfw-{{id}}/agent.json","extension":".json"},{"scope":"project","path":".agents/agents/trackfw-{{id}}/agent.json","extension":".json"}],"skills":[{"scope":"global","path":"~/.gemini/antigravity-cli/skills/trackfw-{{id}}/SKILL.md","extension":".md"},{"scope":"project","path":".agents/skills/trackfw-{{id}}/SKILL.md","extension":".md"}]}}],"auxiliary_artifacts":[{"id":"rules","surface":"current","scope":"project","path":".agents/rules/trackfw.md","purpose":"workspace governance rules","required":false},{"id":"workflows","surface":"current","scope":"project","path":".agents/workflows/trackfw.md","purpose":"workflow discovery","required":false}]},
27
+ {"id":"cursor","name":"Cursor","surfaces":[{"id":"ide","name":"Cursor IDE","scopes":["global","project"],"capabilities":{"agents":{"support_level":"native","representation":"agent-markdown"},"skills":{"support_level":"native","representation":"skill"}},"paths":{"agents":[{"scope":"global","path":"~/.cursor/agents/trackfw-{{id}}.md","extension":".md"},{"scope":"project","path":".cursor/agents/trackfw-{{id}}.md","extension":".md"}],"skills":[{"scope":"global","path":"~/.cursor/skills/trackfw-{{id}}/SKILL.md","extension":".md"},{"scope":"project","path":".cursor/skills/trackfw-{{id}}/SKILL.md","extension":".md"}]}}],"auxiliary_artifacts":[{"id":"hooks","surface":"ide","scope":"project","path":".cursor/hooks.json","purpose":"attention hooks","required":false}]},
28
+ {"id":"copilot","name":"GitHub Copilot","surfaces":[{"id":"ide","name":"GitHub Copilot","scopes":["global","project"],"capabilities":{"agents":{"support_level":"native","representation":"custom-agent"},"skills":{"support_level":"native","representation":"agent-skill"}},"paths":{"agents":[{"scope":"global","path":"~/.copilot/agents/trackfw-{{id}}.agent.md","extension":".agent.md"},{"scope":"project","path":".github/agents/trackfw-{{id}}.agent.md","extension":".agent.md"}],"skills":[{"scope":"global","path":"~/.copilot/skills/trackfw-{{id}}/SKILL.md","extension":".md"},{"scope":"project","path":".github/skills/trackfw-{{id}}/SKILL.md","extension":".md"}]}}],"auxiliary_artifacts":[{"id":"instructions","surface":"ide","scope":"project","path":".github/copilot-instructions.md","purpose":"repository instructions","required":false},{"id":"hooks","surface":"ide","scope":"project","path":".github/hooks/trackfw-attention.json","purpose":"attention hooks","required":false}]},
29
+ {"id":"windsurf","name":"Windsurf","surfaces":[{"id":"ide","name":"Windsurf IDE","scopes":["global","project"],"capabilities":{"agents":{"support_level":"fallback","representation":"skill","fallback_representation":"specialist skill"},"skills":{"support_level":"native","representation":"skill"}},"paths":{"agents":[{"scope":"global","path":"~/.codeium/windsurf/skills/trackfw-agent-{{id}}/SKILL.md","extension":".md"},{"scope":"project","path":".windsurf/skills/trackfw-agent-{{id}}/SKILL.md","extension":".md"}],"skills":[{"scope":"global","path":"~/.codeium/windsurf/skills/trackfw-{{id}}/SKILL.md","extension":".md"},{"scope":"project","path":".windsurf/skills/trackfw-{{id}}/SKILL.md","extension":".md"}]}}],"auxiliary_artifacts":[{"id":"global-rules","surface":"ide","scope":"project","path":".windsurfrules","purpose":"governance rule discovery","required":false}]},
30
+ {"id":"amazonq","name":"Amazon Q Developer","surfaces":[{"id":"cli","name":"Amazon Q Developer CLI","scopes":["global","project"],"capabilities":{"agents":{"support_level":"native","representation":"cli-agent-json"},"skills":{"support_level":"fallback","representation":"rule","fallback_representation":"workflow rule"}},"paths":{"agents":[{"scope":"global","path":"~/.aws/amazonq/cli-agents/trackfw-{{id}}.json","extension":".json"},{"scope":"project","path":".amazonq/cli-agents/trackfw-{{id}}.json","extension":".json"}],"skills":[{"scope":"global","path":"~/.aws/amazonq/rules/trackfw-{{id}}.md","extension":".md"},{"scope":"project","path":".amazonq/rules/trackfw-{{id}}.md","extension":".md"}]}}],"auxiliary_artifacts":[{"id":"guidelines","surface":"cli","scope":"project","path":".amazonq/developer/guidelines.md","purpose":"project governance guidelines","required":false}]},
31
+ {"id":"kiro","name":"Kiro","surfaces":[{"id":"ide","name":"Kiro IDE","scopes":["global","project"],"capabilities":{"agents":{"support_level":"native","representation":"agent-markdown"},"skills":{"support_level":"native","representation":"agent-skill"}},"paths":{"agents":[{"scope":"global","path":"~/.kiro/agents/trackfw-{{id}}.md","extension":".md"},{"scope":"project","path":".kiro/agents/trackfw-{{id}}.md","extension":".md"}],"skills":[{"scope":"global","path":"~/.kiro/skills/trackfw-{{id}}/SKILL.md","extension":".md"},{"scope":"project","path":".kiro/skills/trackfw-{{id}}/SKILL.md","extension":".md"}]}},{"id":"cli","name":"Kiro CLI","scopes":["global","project"],"capabilities":{"agents":{"support_level":"native","representation":"agent-json"},"skills":{"support_level":"native","representation":"agent-skill"}},"paths":{"agents":[{"scope":"global","path":"~/.kiro/agents/trackfw-{{id}}.json","extension":".json"},{"scope":"project","path":".kiro/agents/trackfw-{{id}}.json","extension":".json"}],"skills":[{"scope":"global","path":"~/.kiro/skills/trackfw-{{id}}/SKILL.md","extension":".md"},{"scope":"project","path":".kiro/skills/trackfw-{{id}}/SKILL.md","extension":".md"}]}}],"auxiliary_artifacts":[{"id":"hooks","surface":"ide","scope":"project","path":".kiro/hooks/trackfw-attention.json","purpose":"attention hooks","required":false}]}
32
+ ]
33
+ }
@@ -0,0 +1,6 @@
1
+ ---
2
+ name: trackfw-governance
3
+ description: Inspect and maintain ADR, REQ, ROADMAP, traceability, lifecycle state and validation.
4
+ ---
5
+
6
+ Run `trackfw context --format=json`, read the working context, preserve trace IDs and status-folder coherence, and run `trackfw validate --json` before reporting completion.
@@ -0,0 +1,6 @@
1
+ ---
2
+ name: trackfw-implement
3
+ description: Implement work governed by an existing trackfw requirement and roadmap.
4
+ ---
5
+
6
+ Identify the matching REQ and WIP roadmap before editing. Work one microbatch at a time, run focused project gates plus `trackfw validate --json`, and do not mark completion before acceptance criteria pass.
@@ -0,0 +1,6 @@
1
+ ---
2
+ name: trackfw-plan
3
+ description: Plan governed changes with REQs, ADRs, roadmaps, microbatches and acceptance criteria.
4
+ ---
5
+
6
+ Inspect existing governance artifacts, code and tests. Resolve material decisions in ADRs, create a linked REQ, and produce decision-complete parallelizable microbatches with exact validation commands.
@@ -0,0 +1,6 @@
1
+ ---
2
+ name: trackfw-release
3
+ description: Prepare and verify versioning, changelog, packaging, parity and publication gates.
4
+ ---
5
+
6
+ Confirm traceability and completion, execute quality and package gates, derive SemVer from commits since the latest tag, and stop before publishing unless explicitly authorized.
@@ -0,0 +1,6 @@
1
+ ---
2
+ name: trackfw-review
3
+ description: Review changes for correctness, security, regression risk, tests and governance traceability.
4
+ ---
5
+
6
+ Compare the branch with its base, verify the linked REQ and roadmap, and report evidence-backed findings by severity. Remain read-only unless fixes are explicitly requested.
@@ -0,0 +1,34 @@
1
+ 'use strict'
2
+
3
+ const fs = require('node:fs')
4
+ const path = require('node:path')
5
+
6
+ const ASSET_ROOT = path.join(__dirname, 'assets')
7
+ const catalog = Object.freeze(JSON.parse(fs.readFileSync(path.join(ASSET_ROOT, 'catalog.json'), 'utf8')))
8
+
9
+ function items(kind) {
10
+ if (kind !== 'agents' && kind !== 'skills') throw new Error(`Unsupported integration kind: ${kind}`)
11
+ return catalog[kind]
12
+ }
13
+
14
+ function target(id) {
15
+ const found = catalog.targets.find(entry => entry.id === id)
16
+ if (!found) throw new Error(`Unsupported target: ${id}`)
17
+ return found
18
+ }
19
+
20
+ function surfaceFor(targetEntry, requested, kind = 'agents') {
21
+ const surfaces = targetEntry.surfaces || []
22
+ const found = requested
23
+ ? surfaces.find(entry => entry.id === requested)
24
+ : surfaces.find(entry => !['legacy', 'unsupported'].includes(entry.capabilities[kind].support_level)) || surfaces[0]
25
+ if (!found) throw new Error(`Unsupported surface ${requested} for target ${targetEntry.id}`)
26
+ return found
27
+ }
28
+
29
+ function readAsset(item) {
30
+ const relative = item.asset.replace(/^assets\//, '')
31
+ return fs.readFileSync(path.join(ASSET_ROOT, relative), 'utf8')
32
+ }
33
+
34
+ module.exports = { catalog, items, target, surfaceFor, readAsset }
@@ -0,0 +1,103 @@
1
+ 'use strict'
2
+
3
+ const { catalog, items, target, surfaceFor, readAsset } = require('./catalog')
4
+ const { render } = require('./render')
5
+ const { IntegrationManager } = require('./manager')
6
+ const { legacyHashes } = require('./legacy')
7
+
8
+ function parseSurfaces(values = []) {
9
+ const result = {}
10
+ for (const value of values) {
11
+ const [targetID, surfaceID, extra] = String(value).split('=')
12
+ if (!targetID || !surfaceID || extra !== undefined) throw new Error(`Invalid --surface ${value}: expected target=surface`)
13
+ if (result[targetID]) throw new Error(`Duplicate --surface for target ${targetID}`)
14
+ result[targetID] = surfaceID
15
+ }
16
+ return result
17
+ }
18
+
19
+ function selections(kind, options = {}) {
20
+ const selectedItems = options.items && options.items.length ? options.items : items(kind).map(item => item.id)
21
+ const itemEntries = selectedItems.map(id => {
22
+ const found = items(kind).find(item => item.id === id)
23
+ if (!found) throw new Error(`Unsupported ${kind} item: ${id}`)
24
+ return found
25
+ })
26
+ const targetValues = options.targets && options.targets.length ? options.targets : catalog.targets.map(entry => entry.id)
27
+ const surfaceSelections = parseSurfaces(options.surfaces)
28
+ const scopes = options.scope ? [options.scope] : ['project']
29
+ const targets = []
30
+ for (const targetID of targetValues) {
31
+ const targetEntry = target(targetID)
32
+ const selected = surfaceSelections[targetID]
33
+ const surfaces = options.allSurfaces && !selected
34
+ ? targetEntry.surfaces.filter(entry => entry.capabilities[kind].support_level !== 'unsupported')
35
+ : [surfaceFor(targetEntry, selected, kind)]
36
+ for (const surface of surfaces) targets.push({ target: targetEntry, surface })
37
+ }
38
+ return { itemEntries, targets, scopes }
39
+ }
40
+
41
+ function buildPlans(kind, options = {}) {
42
+ const selected = selections(kind, options)
43
+ const plans = []
44
+ for (const { target: targetEntry, surface } of selected.targets) {
45
+ const capability = surface.capabilities[kind]
46
+ if (capability.support_level === 'unsupported') continue
47
+ for (const scope of selected.scopes) {
48
+ if (!surface.scopes.includes(scope)) continue
49
+ const paths = surface.paths[kind].filter(entry => entry.scope === scope)
50
+ for (const item of selected.itemEntries) {
51
+ for (const installPath of paths) {
52
+ const destination = installPath.path.replace('{{id}}', item.id)
53
+ const content = render({ target: targetEntry.id, kind, item, content: readAsset(item), capability, destination })
54
+ const claim = { target: targetEntry.id, surface: surface.id, scope, kind, item: item.id }
55
+ plans.push({
56
+ claim,
57
+ destination,
58
+ content,
59
+ catalogVersion: catalog.version,
60
+ supportLevel: capability.support_level,
61
+ representation: capability.representation,
62
+ legacyHashes: legacyHashes(claim),
63
+ item,
64
+ })
65
+ }
66
+ }
67
+ }
68
+ }
69
+ return plans.sort((a, b) => [a.claim.target, a.claim.surface, a.claim.scope, a.claim.item, a.destination].join('\0').localeCompare([b.claim.target, b.claim.surface, b.claim.scope, b.claim.item, b.destination].join('\0')))
70
+ }
71
+
72
+ function result(kind, plans, statuses) {
73
+ return {
74
+ kind,
75
+ catalog_version: catalog.version,
76
+ items: items(kind).map(({ id, name, description }) => ({ id, name, description })),
77
+ deployments: statuses.map((status, index) => ({
78
+ target: status.claim.target,
79
+ surface: status.claim.surface,
80
+ scope: status.claim.scope,
81
+ item: status.claim.item,
82
+ support_level: status.supportLevel,
83
+ representation: status.representation,
84
+ destination: plans[index].destination,
85
+ state: status.state,
86
+ managed: status.managed,
87
+ })),
88
+ }
89
+ }
90
+
91
+ function execute(kind, operation, options = {}, roots = {}) {
92
+ const plans = buildPlans(kind, options)
93
+ const manager = new IntegrationManager(roots)
94
+ let statuses
95
+ if (operation === 'list') statuses = manager.inspect(plans)
96
+ else if (operation === 'install') statuses = manager.install(plans, { force: options.force })
97
+ else if (operation === 'update') statuses = manager.update(plans, { force: options.force })
98
+ else if (operation === 'uninstall') statuses = manager.uninstall(plans, { force: options.force })
99
+ else throw new Error(`Unsupported integration operation: ${operation}`)
100
+ return result(kind, plans, statuses)
101
+ }
102
+
103
+ module.exports = { catalog, buildPlans, execute, IntegrationManager, parseSurfaces }
@@ -0,0 +1,35 @@
1
+ 'use strict'
2
+
3
+ // SHA-256 values emitted by the pre-lifecycle installers. They are deliberately
4
+ // static: only byte-identical historical templates may be adopted.
5
+ const LEGACY_HASHES = Object.freeze({
6
+ // Go/Homebrew `trackfw agents` Claude templates.
7
+ 'claude:cli:global:agents:architect': ['d28ae507d2ce9fd3fcd7cb1a0c1ffaaebc994dc9c45b219e5361b909dc6132ba'],
8
+ 'claude:cli:global:agents:backend': ['587bf790907bc7451976c026b9c7dc5419541a5fdfb064586744198dcf8c0439'],
9
+ 'claude:cli:global:agents:code-quality': ['8917909b55ed54cfa8bb66015a301428f4d924bb3658a9c4c5d443e14e60d399'],
10
+ 'claude:cli:global:agents:data': ['3a402a85610cf3d463200d0f81fda61b5c6c02a0198d302a5d6da49e8e5ff688'],
11
+ 'claude:cli:global:agents:dba': ['07aa92c09833a7c72c3d9b3e39ec1272a91e435deda2da5ddef321abed3702fa'],
12
+ 'claude:cli:global:agents:frontend': ['7ae3940a0827b0047363cd984be5e96b66902098c5ea669d0975a234027ca39c'],
13
+ 'claude:cli:global:agents:infra': ['9f5534c83c3d3b0a9e8c550bd7a3cbb861eb7c4d7671fe8783cd68ff5d5c1605'],
14
+ 'claude:cli:global:agents:qa': ['384283eb46d5e3c5ee978fc87e1b5cd44009e3c62d57c8dcd5cf92189b79e291'],
15
+ 'claude:cli:global:agents:security': ['10e02aa03fe502174174e376d9ec845c549f828af885059214af23854efb4c3f'],
16
+ 'claude:cli:global:agents:ux': ['49b5bbcb8063075bda0b254d1040ae7d2b8d8c315f538cf05e39774ab8a2907b'],
17
+
18
+ // npm `installCodex`: five catalog agents and five workflow skills.
19
+ 'codex:cli:project:agents:architect': ['c7e4b34984a8ba54753ecb9c0b2a1ad10b0d2083a15e03bcb19016012efa39e1', '38199d6edfc0fd7c5d6663996541a25a17203d87713709d0a0daad2ecc5d6be6', '94512bd3db605d5170841018adc2246ce314a57b3be07938fc61d0c41ef6126c'],
20
+ 'codex:cli:project:agents:backend': ['0e2327ace2c719fcb0abe2f17dfdbad4a0d987824f888e6802a9f4a132163f64', '348c5ab0a57597f332c2a5b293f045a0d3980875a9c9083a06cad15f909e23a7', '43759e9a912bb236a997d58ad184dfcda2b45f6e00ed5a5edad93d83dd9c906d'],
21
+ 'codex:cli:project:agents:frontend': ['1a377e6c9a51f1df549e475558897dac428f94ae50e6955a1ca99cb0b04b0648', '608c10cc0f910725ad813736a2548e1c66479abce28d32d4b8969707be1054f1', '5ebf3a404d300e17338538eeb8f50d63bda8fde5f5f0bf0b73767434233ae9c4'],
22
+ 'codex:cli:project:agents:qa': ['aad7e86d59e511e6b1519e2e26bb4de376eb124725a44d82f704edf396989ac0', '3e8b3a5d04aa44c62ce6af801be9d907aa6458931c19676ca999776ac961254d', '33663bb0ee4bee4cd971a691283649b308ad093a01000762a70ec79481d10e52'],
23
+ 'codex:cli:project:agents:security': ['b7743d40163066794941f37fab259f965bb78c54c9a8fe3cc7a0146d2552f016', 'cdd197001ebabf8cf63b8c1562d0eb89b226df8a2e5dcb1ce84e9a1ea6de782d', '1e3987835233bbdb0751e98a50ec7512dfc7855e293aa40d1ad77c80dde1945f'],
24
+ 'codex:cli:project:skills:governance': ['54edf417d53e2a91f52651de5a31a9c1b80be8d0f7f1252c5b9f3b55630a9b96', 'c30786495105715ae707d374b414d21344b20a0cd354713f8173c5109d690fd9', 'be044d1a1f2af4835d476a3f4798b35e2067eeebdff8f30872353eee6b212736'],
25
+ 'codex:cli:project:skills:implement': ['bb9bbe197244cd4537610579b324757e8c7f97755a78fdb01daab4638485bfc2', '898682d21e6ba938a2ef2fe4908f4047358e3f27ca291068a5585a47aca1426e', 'c5a53c5e9444810d3beeebe563798de792615c7703a0d99e954d578d068af4d4'],
26
+ 'codex:cli:project:skills:plan': ['dc038a9f18bd0fee7e03d8c7816f058e9c95a4e729dddfb086d642a61bc676ba', '2ef36f3ac8c77eb46d13ba4cbe420e1c872f71edace9b7851d63cb50bc5f3708', 'b2cd10338db33369e78656a1e3877427a454f9eab6fa1ec948fd17730c66b46a'],
27
+ 'codex:cli:project:skills:release': ['2db9c460046da9cb918acb7f716727a07fdb5b361f2a81a67c35b8fe09135fcd', '2450fd34b81a14d28c794abc0e9ac0213ba929a950d15b3226443c92419d42c9'],
28
+ 'codex:cli:project:skills:review': ['bea0debcb3b8f5014a7c7ae0e93b9b8c4b318e14c8fc8ff6510594410a123783', 'b17fbc6d59bdfad64d2c1993cc588c0e6e9ae8e4b4a16669abf254d41cc40330', '8b3119641a0d06b99b451cf6437c15a2f7f58069fb0ebf855ab757f66b919b53'],
29
+ })
30
+
31
+ function legacyHashes(claim) {
32
+ return [...(LEGACY_HASHES[`${claim.target}:${claim.surface}:${claim.scope}:${claim.kind}:${claim.item}`] || [])]
33
+ }
34
+
35
+ module.exports = { LEGACY_HASHES, legacyHashes }
@@ -0,0 +1,237 @@
1
+ 'use strict'
2
+
3
+ const crypto = require('node:crypto')
4
+ const fs = require('node:fs')
5
+ const os = require('node:os')
6
+ const path = require('node:path')
7
+
8
+ const SCHEMA_VERSION = 1
9
+ const sha256 = content => crypto.createHash('sha256').update(content).digest('hex')
10
+ const claimKey = claim => [claim.target, claim.surface, claim.scope, claim.kind, claim.item].join('\u0000')
11
+ const cleanClaim = claim => ({ target: claim.target, surface: claim.surface, scope: claim.scope, kind: claim.kind, item: claim.item })
12
+
13
+ class IntegrationManager {
14
+ constructor({ projectRoot = process.cwd(), homeRoot = os.homedir() } = {}) {
15
+ this.roots = { project: path.resolve(projectRoot), global: path.resolve(homeRoot) }
16
+ }
17
+
18
+ manifestPath(scope) { return path.join(this.roots[scope], '.trackfw', 'integrations-manifest.json') }
19
+
20
+ resolve(scope, destination) {
21
+ const root = this.roots[scope]
22
+ if (!root) throw new Error(`Unsupported scope: ${scope}`)
23
+ if (typeof destination !== 'string' || destination.includes('\u0000') || destination.includes('\\')) throw new Error(`Unsafe destination: ${destination}`)
24
+ let resolved
25
+ if (destination.startsWith('~/')) {
26
+ if (scope !== 'global') throw new Error('Home destination requires global scope')
27
+ resolved = path.resolve(root, destination.slice(2))
28
+ } else if (path.isAbsolute(destination)) {
29
+ resolved = path.normalize(destination)
30
+ } else {
31
+ if (!destination || path.normalize(destination) !== destination || destination === '.' || destination.startsWith(`..${path.sep}`)) throw new Error(`Unsafe destination: ${destination}`)
32
+ resolved = path.resolve(root, destination)
33
+ }
34
+ const rel = path.relative(root, resolved)
35
+ if (!rel || rel === '..' || rel.startsWith(`..${path.sep}`) || path.isAbsolute(rel)) throw new Error(`Destination is outside ${scope} root: ${destination}`)
36
+ this.assertNoSymlinks(root, resolved)
37
+ this.assertNoSymlinks(root, this.manifestPath(scope))
38
+ return resolved
39
+ }
40
+
41
+ assertNoSymlinks(root, destination) {
42
+ let current = destination
43
+ while (true) {
44
+ if (fs.existsSync(current) && fs.lstatSync(current).isSymbolicLink()) throw new Error(`Symlink path is not allowed: ${current}`)
45
+ if (current === root) return
46
+ const parent = path.dirname(current)
47
+ const rel = path.relative(root, current)
48
+ if (parent === current || rel === '..' || rel.startsWith(`..${path.sep}`)) throw new Error(`Path escapes root: ${destination}`)
49
+ current = parent
50
+ }
51
+ }
52
+
53
+ loadManifest(scope) {
54
+ const file = this.manifestPath(scope)
55
+ this.assertNoSymlinks(this.roots[scope], file)
56
+ if (!fs.existsSync(file)) return { schema_version: SCHEMA_VERSION, artifacts: {} }
57
+ const parsed = JSON.parse(fs.readFileSync(file, 'utf8'))
58
+ if (parsed.schema_version !== SCHEMA_VERSION || !parsed.artifacts || Array.isArray(parsed.artifacts)) throw new Error(`Unsupported integration manifest: ${file}`)
59
+ return parsed
60
+ }
61
+
62
+ atomicWrite(file, content, mode) {
63
+ const root = this.rootFor(file)
64
+ this.assertNoSymlinks(root, file)
65
+ fs.mkdirSync(path.dirname(file), { recursive: true })
66
+ const tmp = path.join(path.dirname(file), `.${path.basename(file)}.${process.pid}.${crypto.randomBytes(6).toString('hex')}.tmp`)
67
+ try {
68
+ fs.writeFileSync(tmp, content, { mode })
69
+ fs.chmodSync(tmp, mode)
70
+ fs.renameSync(tmp, file)
71
+ fs.chmodSync(file, mode)
72
+ } finally {
73
+ if (fs.existsSync(tmp)) fs.unlinkSync(tmp)
74
+ }
75
+ }
76
+
77
+ rootFor(file) {
78
+ const found = Object.values(this.roots).find(root => {
79
+ const rel = path.relative(root, file)
80
+ return rel && rel !== '..' && !rel.startsWith(`..${path.sep}`) && !path.isAbsolute(rel)
81
+ })
82
+ if (!found) throw new Error(`Path is outside integration roots: ${file}`)
83
+ return found
84
+ }
85
+
86
+ saveManifest(scope, manifest) {
87
+ const artifacts = {}
88
+ for (const destination of Object.keys(manifest.artifacts).sort()) {
89
+ const artifact = manifest.artifacts[destination]
90
+ artifact.claims = artifact.claims.map(cleanClaim).sort((a, b) => claimKey(a).localeCompare(claimKey(b)))
91
+ artifacts[destination] = artifact
92
+ }
93
+ this.atomicWrite(this.manifestPath(scope), `${JSON.stringify({ schema_version: SCHEMA_VERSION, artifacts }, null, 2)}\n`, 0o600)
94
+ }
95
+
96
+ inspect(plans) {
97
+ const manifests = new Map()
98
+ return plans.map(plan => {
99
+ const scope = plan.claim.scope
100
+ if (!manifests.has(scope)) manifests.set(scope, this.loadManifest(scope))
101
+ const file = this.resolve(scope, plan.destination)
102
+ const record = manifests.get(scope).artifacts[file]
103
+ const managed = Boolean(record && record.claims.some(claim => claimKey(claim) === claimKey(plan.claim)))
104
+ if (!fs.existsSync(file)) return { ...plan, destination: file, state: 'not-installed', managed }
105
+ const actual = sha256(fs.readFileSync(file))
106
+ const desired = sha256(plan.content)
107
+ let state
108
+ if (record) {
109
+ if (actual !== record.sha256) state = 'modified'
110
+ else if (actual !== desired || record.catalog_version !== plan.catalogVersion) state = 'outdated'
111
+ else state = 'current'
112
+ } else if (actual === desired) state = 'current'
113
+ else if ((plan.legacyHashes || []).includes(actual)) state = 'outdated'
114
+ else state = 'modified'
115
+ return { ...plan, destination: file, state, managed }
116
+ })
117
+ }
118
+
119
+ install(plans, { force = false } = {}) { return this.mutate('install', plans, force) }
120
+ update(plans, { force = false } = {}) { return this.mutate('update', plans, force) }
121
+ uninstall(plans, { force = false } = {}) { return this.mutate('uninstall', plans, force) }
122
+
123
+ mutate(operation, plans, force) {
124
+ const resolved = plans.map(plan => ({ plan, file: this.resolve(plan.claim.scope, plan.destination) }))
125
+ const manifests = new Map()
126
+ for (const { plan } of resolved) if (!manifests.has(plan.claim.scope)) manifests.set(plan.claim.scope, this.loadManifest(plan.claim.scope))
127
+ const desiredByFile = new Map()
128
+ for (const item of resolved) {
129
+ const desired = sha256(item.plan.content)
130
+ if (operation !== 'uninstall' && desiredByFile.has(item.file) && desiredByFile.get(item.file) !== desired) throw new Error(`Conflicting content planned for: ${item.file}`)
131
+ desiredByFile.set(item.file, desired)
132
+ this.preflight(operation, item, manifests.get(item.plan.claim.scope), force)
133
+ }
134
+ const snapshots = new Map()
135
+ for (const item of resolved) this.snapshot(snapshots, item.file)
136
+ for (const scope of manifests.keys()) this.snapshot(snapshots, this.manifestPath(scope))
137
+ try {
138
+ for (const item of resolved) this.apply(operation, item, manifests.get(item.plan.claim.scope), force)
139
+ for (const [scope, manifest] of [...manifests].sort(([a], [b]) => a.localeCompare(b))) this.saveManifest(scope, manifest)
140
+ } catch (error) {
141
+ this.rollback(snapshots)
142
+ throw error
143
+ }
144
+ return this.inspect(plans)
145
+ }
146
+
147
+ preflight(operation, { plan, file }, manifest, force) {
148
+ const status = this.inspectResolved(plan, file, manifest)
149
+ const record = manifest.artifacts[file]
150
+ const owned = Boolean(record && record.claims.some(claim => claimKey(claim) === claimKey(plan.claim)))
151
+ if (operation === 'install') {
152
+ if (status.state === 'modified' && !force) throw new Error(`Artifact is modified; use --force: ${file}`)
153
+ if (status.state === 'outdated' && owned && !force) throw new Error(`Artifact is outdated; use update: ${file}`)
154
+ } else if (operation === 'update') {
155
+ if (!owned && status.state === 'modified') throw new Error(`Unmanaged artifact does not match a trackfw template: ${file}`)
156
+ if (status.state === 'modified' && !force) throw new Error(`Artifact is modified; use --force: ${file}`)
157
+ } else if (operation === 'uninstall' && owned && status.state === 'modified' && !force) {
158
+ throw new Error(`Artifact is modified; use --force: ${file}`)
159
+ }
160
+ }
161
+
162
+ inspectResolved(plan, file, manifest) {
163
+ const record = manifest.artifacts[file]
164
+ const managed = Boolean(record && record.claims.some(claim => claimKey(claim) === claimKey(plan.claim)))
165
+ if (!fs.existsSync(file)) return { state: 'not-installed', managed }
166
+ const actual = sha256(fs.readFileSync(file))
167
+ const desired = sha256(plan.content)
168
+ if (record) {
169
+ if (actual !== record.sha256) return { state: 'modified', managed }
170
+ return { state: actual === desired && record.catalog_version === plan.catalogVersion ? 'current' : 'outdated', managed }
171
+ }
172
+ if (actual === desired) return { state: 'current', managed: false }
173
+ if ((plan.legacyHashes || []).includes(actual)) return { state: 'outdated', managed: false }
174
+ return { state: 'modified', managed: false }
175
+ }
176
+
177
+ apply(operation, { plan, file }, manifest, force) {
178
+ let record = manifest.artifacts[file]
179
+ const key = claimKey(plan.claim)
180
+ const owned = Boolean(record && record.claims.some(claim => claimKey(claim) === key))
181
+ if (operation === 'uninstall') {
182
+ if (!owned) return
183
+ record.claims = record.claims.filter(claim => claimKey(claim) !== key)
184
+ if (record.claims.length) return
185
+ if (fs.existsSync(file)) fs.unlinkSync(file)
186
+ delete manifest.artifacts[file]
187
+ this.cleanEmpty(path.dirname(file), this.roots[plan.claim.scope])
188
+ return
189
+ }
190
+
191
+ const exists = fs.existsSync(file)
192
+ let actual = exists ? sha256(fs.readFileSync(file)) : ''
193
+ const desired = sha256(plan.content)
194
+ const knownLegacy = (plan.legacyHashes || []).includes(actual)
195
+ let writeDesired = !exists
196
+ if (exists && !owned) writeDesired = (operation === 'update' && actual !== desired) || (force && actual !== desired)
197
+ else if (exists && owned) writeDesired = actual !== desired
198
+ if (!record) record = { destination: file, sha256: '', catalog_version: '', claims: [] }
199
+ if (writeDesired) {
200
+ this.atomicWrite(file, plan.content, 0o644)
201
+ actual = desired
202
+ } else if (exists && !owned && actual !== desired && !knownLegacy && !force) {
203
+ throw new Error(`Unmanaged artifact does not match a trackfw template: ${file}`)
204
+ }
205
+ if (!record.claims.some(claim => claimKey(claim) === key)) record.claims.push(cleanClaim(plan.claim))
206
+ record.sha256 = actual
207
+ record.catalog_version = actual === desired ? plan.catalogVersion : 'legacy'
208
+ manifest.artifacts[file] = record
209
+ }
210
+
211
+ snapshot(snapshots, file) {
212
+ if (snapshots.has(file)) return
213
+ if (!fs.existsSync(file)) snapshots.set(file, null)
214
+ else snapshots.set(file, { content: fs.readFileSync(file), mode: fs.statSync(file).mode & 0o777 })
215
+ }
216
+
217
+ rollback(snapshots) {
218
+ for (const [file, snapshot] of [...snapshots].reverse()) {
219
+ try {
220
+ if (!snapshot) { if (fs.existsSync(file) && !fs.lstatSync(file).isDirectory()) fs.unlinkSync(file) }
221
+ else this.atomicWrite(file, snapshot.content, snapshot.mode)
222
+ } catch { /* preserve original error */ }
223
+ }
224
+ }
225
+
226
+ cleanEmpty(directory, root) {
227
+ while (directory !== root) {
228
+ const rel = path.relative(root, directory)
229
+ if (!rel || rel === '..' || rel.startsWith(`..${path.sep}`) || path.isAbsolute(rel)) return
230
+ if (!fs.existsSync(directory) || fs.lstatSync(directory).isSymbolicLink() || fs.readdirSync(directory).length) return
231
+ fs.rmdirSync(directory)
232
+ directory = path.dirname(directory)
233
+ }
234
+ }
235
+ }
236
+
237
+ module.exports = { IntegrationManager, sha256, claimKey, SCHEMA_VERSION }
@@ -0,0 +1,102 @@
1
+ 'use strict'
2
+
3
+ // Mapa de model: nomes canônicos do catálogo → tier aceito pelo Antigravity CLI (agy)
4
+ const MODEL_MAP = { opus: 'pro', sonnet: 'flash' }
5
+ // Valores que já são tiers válidos do agy e devem passar sem transformação
6
+ const MODEL_PASSTHROUGH = new Set(['flash_lite', 'flash', 'pro'])
7
+
8
+ // SET_IMPL — conjunto base de 10 ferramentas para agentes de implementação
9
+ const SET_IMPL = [
10
+ 'view_file',
11
+ 'list_dir',
12
+ 'grep_search',
13
+ 'search_web',
14
+ 'read_url_content',
15
+ 'write_to_file',
16
+ 'replace_file_content',
17
+ 'run_command',
18
+ 'command_status',
19
+ 'generate_image',
20
+ ]
21
+
22
+ // SET_ARCH — SET_IMPL + 4 ferramentas de orquestração (total 14) para agentes arquitetos
23
+ const SET_ARCH = [
24
+ ...SET_IMPL,
25
+ 'send_message',
26
+ 'define_subagent',
27
+ 'invoke_subagent',
28
+ 'schedule',
29
+ ]
30
+
31
+ function normalize(content) {
32
+ return `${content.trim()}\n`
33
+ }
34
+
35
+ function markdownParts(content) {
36
+ const text = content.trim()
37
+ let name = 'trackfw-agent'
38
+ let description = 'trackfw specialist'
39
+ let model = ''
40
+ let body = text
41
+ if (text.startsWith('---\n')) {
42
+ const end = text.indexOf('\n---', 4)
43
+ if (end >= 0) {
44
+ const frontmatter = text.slice(4, end)
45
+ body = text.slice(end + 4).trim()
46
+ for (const line of frontmatter.split('\n')) {
47
+ const separator = line.indexOf(':')
48
+ if (separator < 0) continue
49
+ const key = line.slice(0, separator).trim()
50
+ const value = line.slice(separator + 1).trim().replace(/^['"]|['"]$/g, '')
51
+ if (key === 'name') name = value
52
+ if (key === 'description') description = value
53
+ if (key === 'model') model = value
54
+ }
55
+ }
56
+ }
57
+ return { name, description, model, body }
58
+ }
59
+
60
+ // resolveModel converte o modelo canônico para o tier aceito pelo agy.
61
+ // Retorna o valor mapeado, ou string vazia se a linha model deve ser omitida.
62
+ function resolveModel(model) {
63
+ if (!model) return ''
64
+ if (MODEL_PASSTHROUGH.has(model)) return model
65
+ return MODEL_MAP[model] || ''
66
+ }
67
+
68
+ // toolsFor retorna SET_ARCH para agentes cujo nome termina em "architect", SET_IMPL para os demais.
69
+ // IDs proibidos (edit_file, read_file, find, view_code_item, view_file_outline, call_mcp_tool)
70
+ // nunca fazem parte de nenhum dos conjuntos.
71
+ function toolsFor(name) {
72
+ return name.endsWith('architect') ? SET_ARCH : SET_IMPL
73
+ }
74
+
75
+ function render({ kind, content, capability }) {
76
+ if (kind === 'skills') return normalize(content)
77
+ const parts = markdownParts(content)
78
+ if (capability.representation === 'custom-agent-toml') {
79
+ return `name = ${JSON.stringify(parts.name.replaceAll('-', '_'))}\ndescription = ${JSON.stringify(parts.description)}\ndeveloper_instructions = ${JSON.stringify(parts.body)}\n`
80
+ }
81
+ if (capability.representation === 'cli-agent-json' || capability.representation === 'agent-json') {
82
+ return `${JSON.stringify({ name: parts.name, description: parts.description, prompt: parts.body }, null, 2)}\n`
83
+ }
84
+ if (capability.representation === 'agent-directory') {
85
+ // Reconstrói o frontmatter para o Antigravity CLI (agy):
86
+ // - mapeia model canônico para o tier aceito (opus→pro, sonnet→flash)
87
+ // - injeta tools: SET_IMPL ou SET_ARCH dependendo do nome do agente
88
+ // - omite campos não suportados pelo agy
89
+ const mappedModel = resolveModel(parts.model)
90
+ const tools = toolsFor(parts.name)
91
+ let out = `---\nname: ${parts.name}\ndescription: ${parts.description}\n`
92
+ if (mappedModel) out += `model: ${mappedModel}\n`
93
+ out += 'tools:\n'
94
+ for (const tool of tools) out += ` - ${tool}\n`
95
+ out += '---\n'
96
+ if (parts.body) out += `${parts.body}\n`
97
+ return out
98
+ }
99
+ return normalize(content)
100
+ }
101
+
102
+ module.exports = { render, markdownParts }