raptor-aios 0.12.2 → 0.13.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,62 @@ Format: [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
5
5
 
6
6
  ## [Unreleased]
7
7
 
8
+ ## [0.13.1] - 2026-06-19
9
+
10
+ ### Fixed
11
+
12
+ - **`raptor new` agora empilha as specs de verdade — auto-commit da spec na sua
13
+ branch.** O empilhamento de branches (v0.13.0) dependia de a spec anterior estar
14
+ **commitada** antes da próxima `raptor new`, mas havia um impasse: o `new` deixava
15
+ a spec *untracked* e o pre-commit hook **bloqueava** commitá-la manualmente (a spec
16
+ recém-criada falha nos gates M1–M5, que valem para uma feature já preenchida). Sem
17
+ commit, a árvore ficava suja, o guard pulava a criação da próxima branch e o
18
+ empilhamento nunca acontecia — a nova spec não herdava as anteriores. Agora o `new`
19
+ **commita automaticamente** a spec scaffolded na sua branch de feature (escopo
20
+ restrito a `.raptor/specs/<NNN-slug>`, sem varrer outras mudanças; `--no-verify`
21
+ porque os gates não se aplicam a um scaffold). A árvore fica limpa, a próxima
22
+ `raptor new` forka de uma base commitada e **cada branch carrega todas as specs
23
+ anteriores** — preservando dependências entre specs. Use `--no-commit` para
24
+ desativar. Novo helper `commitPaths` em `@raptor/core`.
25
+
26
+ ### Fixed
27
+
28
+ - **`raptor new` parou de "sobrescrever" specs anteriores — resolução de feature por slug
29
+ exato.** `featureDir()`/`auditableDir()` casavam o slug informado por **sufixo**
30
+ (`d.endsWith('-'+slug)`) e devolviam o primeiro match, então um slug curto resolvia
31
+ silenciosamente para um diretório irmão mais longo (`home-screen` →
32
+ `001-dashboard-home-screen`). Os ~22 comandos a jusante (`plan`, `tasks`, `implement`,
33
+ `clarify`, `verify`, `status`, `audit`, …) passavam a operar sobre a feature **errada**,
34
+ parecendo sobrescrever os artefatos da anterior. Agora o match é **exato** (nome completo
35
+ `NNN-slug` ou slug após o número); múltiplos diretórios casando viram **erro de
36
+ ambiguidade** em vez de adivinhação.
37
+ - **`raptor new` não quebra mais quando `.raptor/specs` não existe.** Git não versiona
38
+ diretórios vazios, então um clone novo (ou após `git clean`) podia não ter a pasta; o
39
+ `readdir` agora tolera a ausência e trata como "sem features".
40
+
41
+ ### Changed
42
+
43
+ - **Branches de feature passam a empilhar (forkam do HEAD atual, não mais de `main`).** Com
44
+ `git.branch_per_feature`, cada feature nascia de uma `main` sem as specs das irmãs
45
+ (commitadas só nas branches delas); ao trocar de branch o Git esvaziava a pasta da spec
46
+ anterior (deixando só a casca `contracts/`), dando a impressão de perda de artefatos. Agora
47
+ a branch nova forka da **branch ativa**, então as specs anteriores são herdadas no working
48
+ tree e o histórico empilha. A primeira feature ainda nasce de `main` naturalmente (é onde
49
+ você começa); HEAD destacado cai no fallback da branch-base.
50
+ - **Numeração `NNN` à prova de colisão entre branches.** O próximo número agora vem da
51
+ **união** das pastas em `.raptor/specs/` **e** dos números embutidos nos nomes de **todas
52
+ as branches** (locais e remotas, via novo `listBranches()` do core), eliminando o
53
+ `feat/002-*` duplicado que surgia quando o contador só via a árvore da branch atual. O
54
+ reuso-por-slug também consulta as branches, preservando a idempotência de `raptor new`
55
+ (re-rodar um slug que vive só em uma branch irmã volta para o número dela).
56
+
57
+ ### Docs
58
+
59
+ - **Novo:** [`docs/feature-branching-and-numbering.md`](docs/feature-branching-and-numbering.md)
60
+ documenta o diagnóstico completo do falso "o `new` sobrescreve a spec anterior" (resolução
61
+ por sufixo + branch forkando da `main` + casca vazia ao trocar de branch) e o modelo de
62
+ branches empilhadas / numeração por união a partir desta release.
63
+
8
64
  ## [0.12.2] - 2026-06-16
9
65
 
10
66
  ### Changed
package/README.md CHANGED
@@ -7,8 +7,8 @@
7
7
  [![npm](https://img.shields.io/npm/v/raptor-aios?color=ef4444&label=raptor-aios&logo=npm)](https://www.npmjs.com/package/raptor-aios)
8
8
  [![node](https://img.shields.io/badge/node-%E2%89%A5%2020-3c873a?logo=node.js&logoColor=white)](https://nodejs.org)
9
9
  [![platform](https://img.shields.io/badge/target-React%20Native-61dafb?logo=react&logoColor=black)](https://reactnative.dev)
10
- [![tests](https://img.shields.io/badge/tests-1356%20passing-22c55e)](#️-desenvolvimento-local)
11
- [![version](https://img.shields.io/badge/version-0.12.2-ef4444)](CHANGELOG.md)
10
+ [![tests](https://img.shields.io/badge/tests-1374%20passing-22c55e)](#️-desenvolvimento-local)
11
+ [![version](https://img.shields.io/badge/version-0.13.0-ef4444)](CHANGELOG.md)
12
12
  [![license](https://img.shields.io/badge/license-MIT-blue)](LICENSE)
13
13
 
14
14
  </div>
@@ -621,7 +621,7 @@ Monorepo pnpm (`packages/core` = `@raptor/core`, `packages/cli` = binário `rapt
621
621
  ```bash
622
622
  pnpm install
623
623
  pnpm build
624
- pnpm -r test && pnpm test:e2e # ~1356 testes (1071 core + ~251 cli + 34 e2e)
624
+ pnpm -r test && pnpm test:e2e # ~1374 testes (1071 core + 258 cli + 45 e2e)
625
625
 
626
626
  # usar a CLI local em outro projeto:
627
627
  cd packages/cli && npm link # ou: pnpm link --global
@@ -14,6 +14,15 @@ export function branchExists(exec, cwd, name) {
14
14
  });
15
15
  return r.status === 0;
16
16
  }
17
+ export function listBranches(exec, cwd) {
18
+ const r = exec(["for-each-ref", "--format=%(refname:short)", "refs/heads", "refs/remotes"], { cwd });
19
+ if (r.status !== 0 || !r.stdout)
20
+ return [];
21
+ return r.stdout
22
+ .split("\n")
23
+ .map((s) => s.trim())
24
+ .filter(Boolean);
25
+ }
17
26
  export function isWorkingTreeClean(exec, cwd) {
18
27
  const r = exec(["status", "--porcelain"], { cwd });
19
28
  return r.status === 0 && r.stdout === "";
@@ -47,3 +56,17 @@ export function stagedFiles(exec, cwd) {
47
56
  .map((s) => s.trim())
48
57
  .filter(Boolean);
49
58
  }
59
+ export function commitPaths(exec, cwd, paths, message) {
60
+ if (paths.length === 0)
61
+ return { status: 0, committed: false };
62
+ const status = exec(["status", "--porcelain", "--", ...paths], { cwd });
63
+ if (status.status !== 0)
64
+ return { status: status.status, committed: false };
65
+ if (status.stdout.trim() === "")
66
+ return { status: 0, committed: false };
67
+ const add = exec(["add", "--", ...paths], { cwd });
68
+ if (add.status !== 0)
69
+ return { status: add.status, committed: false };
70
+ const commit = exec(["commit", "--no-verify", "-m", message, "--", ...paths], { cwd });
71
+ return { status: commit.status, committed: commit.status === 0 };
72
+ }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@raptor/core",
3
- "version": "0.12.2",
3
+ "version": "0.13.1",
4
4
  "type": "module",
5
5
  "main": "dist/index.js"
6
6
  }
@@ -3,7 +3,7 @@ import { BaseCommand } from "../base-command.js";
3
3
  import { step, heading } from "../shared/ui.js";
4
4
  import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync, } from "node:fs";
5
5
  import { join } from "node:path";
6
- import { appendAuditEvent, buildCanonicalPrompt, deriveBranchName, executeWorkflow, extractFigmaRefs, hashString, inferKindFromJiraIssueType, loadAgentsConfig, mapDesignToSpecContext, mapIssueToSpecContext, parseFrontmatter, renderBundled, selectAgent, writeDesignScaffold, } from "../_core/dist/index.js";
6
+ import { appendAuditEvent, buildCanonicalPrompt, commitPaths, deriveBranchName, execSyncGitExecutor, executeWorkflow, isGitRepo, listBranches, extractFigmaRefs, hashString, inferKindFromJiraIssueType, loadAgentsConfig, mapDesignToSpecContext, mapIssueToSpecContext, parseFrontmatter, renderBundled, selectAgent, writeDesignScaffold, } from "../_core/dist/index.js";
7
7
  import { currentActor, readConfig, readInitOptions, requireProjectRoot, } from "../shared/project.js";
8
8
  import { ensureWorkItemBranch, resolveInteractive, } from "../shared/git-prompt.js";
9
9
  import { jiraConn, jiraReadyNonInteractive, openJiraClient, } from "../shared/jira.js";
@@ -14,6 +14,12 @@ import { waitForPhaseArtifact, } from "../shared/artifact-io.js";
14
14
  import { resolveWorkflow } from "../shared/workflow-resolver.js";
15
15
  import { writeFeaturePrompt } from "../shared/feature-prompt.js";
16
16
  import { cliCommandExecutor } from "../workflow/executor.js";
17
+ function parseFeatureBranch(name) {
18
+ const m = /(?:^|\/)(\d{3})-(.+)$/.exec(name);
19
+ if (!m)
20
+ return null;
21
+ return { nnn: m[1], slug: (m[2] ?? "").split(".")[0] };
22
+ }
17
23
  export default class New extends BaseCommand {
18
24
  static description = "Create a new feature spec in the current Raptor project";
19
25
  static examples = [
@@ -61,6 +67,11 @@ export default class New extends BaseCommand {
61
67
  description: "Skip automatic branch creation even when git.branch_per_feature is enabled",
62
68
  default: false,
63
69
  }),
70
+ commit: Flags.boolean({
71
+ description: "Auto-commit the scaffolded spec on its feature branch (bypasses the pre-commit hook so the tree stays clean and the next `raptor new` stacks the next spec on top). Use --no-commit to leave the spec uncommitted.",
72
+ allowNo: true,
73
+ default: true,
74
+ }),
64
75
  "non-interactive": Flags.boolean({
65
76
  description: "Never prompt; on a dirty work tree, skip branch creation instead of asking",
66
77
  default: false,
@@ -88,17 +99,25 @@ export default class New extends BaseCommand {
88
99
  if (!/^[a-z][a-z0-9-]*$/.test(args.slug)) {
89
100
  this.error("Slug must be kebab-case (lowercase letters, digits, hyphens; starts with a letter).");
90
101
  }
91
- const existing = readdirSync(specsDir).filter((d) => /^\d{3}-/.test(d));
102
+ const existing = existsSync(specsDir)
103
+ ? readdirSync(specsDir).filter((d) => /^\d{3}-/.test(d))
104
+ : [];
105
+ const branchFeatures = listBranches(execSyncGitExecutor, root)
106
+ .map(parseFeatureBranch)
107
+ .filter((b) => b !== null);
92
108
  const reusedDir = existing.find((d) => d.replace(/^\d{3}-/, "") === args.slug);
93
- const reusing = reusedDir !== undefined;
94
- const numbers = existing
109
+ const reusedBranch = branchFeatures.find((b) => b.slug === args.slug);
110
+ const reusedNnn = reusedDir
111
+ ? reusedDir.slice(0, 3)
112
+ : reusedBranch?.nnn;
113
+ const reusing = reusedNnn !== undefined;
114
+ const dirNumbers = existing
95
115
  .map((d) => parseInt(d.slice(0, 3), 10))
96
116
  .filter((n) => !Number.isNaN(n));
97
- const nextN = (numbers.length ? Math.max(...numbers) : 0) + 1;
98
- const nnn = reusing
99
- ? reusedDir.slice(0, 3)
100
- : String(nextN).padStart(3, "0");
101
- const featureName = reusing ? reusedDir : `${nnn}-${args.slug}`;
117
+ const branchNumbers = branchFeatures.map((b) => parseInt(b.nnn, 10));
118
+ const maxN = Math.max(0, ...dirNumbers, ...branchNumbers);
119
+ const nnn = reusing ? reusedNnn : String(maxN + 1).padStart(3, "0");
120
+ const featureName = reusing && reusedDir ? reusedDir : `${nnn}-${args.slug}`;
102
121
  const featureDir = join(specsDir, featureName);
103
122
  const actor0 = currentActor("human");
104
123
  runBeforeHook(this, {
@@ -365,6 +384,12 @@ export default class New extends BaseCommand {
365
384
  else if (branchResult?.outcome === "already") {
366
385
  this.log(` Branch: ${branchResult.branch} (already checked out)`);
367
386
  }
387
+ this.maybeAutoCommitSpec({
388
+ root,
389
+ featureName,
390
+ branchResult,
391
+ doCommit: flags.commit,
392
+ });
368
393
  this.log("");
369
394
  if (flags["wait-artifact"] && agentInfo.commandFile) {
370
395
  await waitForPhaseArtifact({
@@ -467,6 +492,22 @@ export default class New extends BaseCommand {
467
492
  interactive: resolveInteractive(ctx.nonInteractive),
468
493
  });
469
494
  }
495
+ maybeAutoCommitSpec(ctx) {
496
+ if (!ctx.doCommit)
497
+ return;
498
+ if (!ctx.branchResult || ctx.branchResult.outcome === "skipped")
499
+ return;
500
+ if (!isGitRepo(execSyncGitExecutor, ctx.root))
501
+ return;
502
+ const rel = join(".raptor", "specs", ctx.featureName);
503
+ const res = commitPaths(execSyncGitExecutor, ctx.root, [rel], `chore: scaffold spec ${ctx.featureName}`);
504
+ if (res.committed) {
505
+ this.log(` Committed: ${rel} (spec sealed on ${ctx.branchResult.branch} — next \`raptor new\` will stack on top)`);
506
+ }
507
+ else if (res.status !== 0) {
508
+ this.warn(`Auto-commit of the spec failed (git exit ${res.status}). Spec is saved; commit it manually so the next \`raptor new\` stacks.`);
509
+ }
510
+ }
470
511
  async fetchJiraContext(root, key) {
471
512
  const conn = jiraConn(readConfig(root));
472
513
  if (!conn) {
@@ -21,7 +21,7 @@ export async function confirmPrompt(question) {
21
21
  }
22
22
  async function promptDirtyTree(cmd, branchName) {
23
23
  cmd.log("");
24
- cmd.warn(`Your working tree has uncommitted changes, and the new branch "${branchName}" forks from the base branch.`);
24
+ cmd.warn(`Your working tree has uncommitted changes, and the new branch "${branchName}" will fork from the current branch.`);
25
25
  cmd.log(" How do you want to proceed?");
26
26
  cmd.log(" 1) stash — set the changes aside, branch from a clean base");
27
27
  cmd.log(" 2) carry — take the uncommitted changes onto the new branch");
@@ -86,7 +86,7 @@ export async function ensureWorkItemBranch(input) {
86
86
  }
87
87
  return { outcome: "switched", branch: branchName, base: null };
88
88
  }
89
- const base = defaultBaseBranch(exec, root);
89
+ const base = currentBranch(exec, root) ?? defaultBaseBranch(exec, root);
90
90
  const st = createBranch(exec, root, branchName, base);
91
91
  if (st !== 0) {
92
92
  const hint = carry
@@ -59,17 +59,30 @@ export function activePresetIds(cfg) {
59
59
  ordered.push(singular);
60
60
  return ordered;
61
61
  }
62
- export function featureDir(root, slug) {
63
- const specs = join(root, '.raptor', 'specs');
64
- const dirs = existsSync(specs) ? readdirSync(specs) : [];
65
- const match = dirs.find((d) => d === slug || d.endsWith(`-${slug}`));
66
- if (match)
67
- return join(specs, match);
62
+ function resolveFeatureDir(base, slug) {
63
+ if (!existsSync(base))
64
+ return null;
65
+ const dirs = readdirSync(base);
66
+ const matches = dirs.filter((d) => d === slug || d.replace(/^\d{3}-/, '') === slug);
67
+ if (matches.length === 1)
68
+ return join(base, matches[0]);
69
+ if (matches.length > 1) {
70
+ throw new Error(`Ambiguous feature '${slug}' — matches ${matches
71
+ .sort()
72
+ .join(', ')}. Pass the full directory name (e.g. ${matches.sort()[0]}).`);
73
+ }
68
74
  if (/^\d{3}-/.test(slug)) {
69
- const direct = join(specs, slug);
75
+ const direct = join(base, slug);
70
76
  if (existsSync(direct))
71
77
  return direct;
72
78
  }
79
+ return null;
80
+ }
81
+ export function featureDir(root, slug) {
82
+ const specs = join(root, '.raptor', 'specs');
83
+ const resolved = resolveFeatureDir(specs, slug);
84
+ if (resolved)
85
+ return resolved;
73
86
  throw new Error(`feature not found: ${slug} (under ${specs})`);
74
87
  }
75
88
  export function auditableDir(root, slug) {
@@ -78,17 +91,9 @@ export function auditableDir(root, slug) {
78
91
  join(root, '.raptor', 'hotfixes'),
79
92
  ];
80
93
  for (const base of candidates) {
81
- if (!existsSync(base))
82
- continue;
83
- const dirs = readdirSync(base);
84
- const match = dirs.find((d) => d === slug || d.endsWith(`-${slug}`));
85
- if (match)
86
- return join(base, match);
87
- if (/^\d{3}-/.test(slug)) {
88
- const direct = join(base, slug);
89
- if (existsSync(direct))
90
- return direct;
91
- }
94
+ const resolved = resolveFeatureDir(base, slug);
95
+ if (resolved)
96
+ return resolved;
92
97
  }
93
98
  throw new Error(`slug not found: ${slug} (checked .raptor/specs and .raptor/hotfixes)`);
94
99
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "raptor-aios",
3
- "version": "0.12.2",
3
+ "version": "0.13.1",
4
4
  "description": "Raptor — Spec-Driven Development (SDD) CLI for modern mobile apps. Constitutional gates, audit trail, real verification (a11y/perf/stores/OS matrix), and AI-agent slash commands.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -29,7 +29,7 @@ const CLI = join(ROOT, "packages", "cli");
29
29
  const CORE = join(ROOT, "packages", "core");
30
30
  const OUT = join(ROOT, "build", "npm");
31
31
 
32
- const VERSION = "0.12.2";
32
+ const VERSION = "0.13.1";
33
33
 
34
34
  function log(msg) {
35
35
  process.stdout.write(` ${msg}\n`);
@@ -1,156 +0,0 @@
1
- /**
2
- * Cross-Agent Materialization Pipeline — Materializes slash commands
3
- * for all registered agents.
4
- *
5
- * Given a canonical prompt (agent-neutral), this pipeline:
6
- * 1. Resolves placeholders (renderer)
7
- * 2. Packs the prompt for each agent (adapter.packPrompt)
8
- * 3. Writes command files to the agent's expected location
9
- *
10
- * Spec Kit ref: E11 — "Pipeline de materialização cross-agent"
11
- */
12
- import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
13
- import { join, basename } from "node:path";
14
- import { listAgents, getAgent } from "../agents/registry.js";
15
- import { renderPrompt, autoResolveContext } from "../prompts/renderer.js";
16
- // ---------------------------------------------------------------------------
17
- // Pipeline
18
- // ---------------------------------------------------------------------------
19
- /**
20
- * Materialize all canonical prompts for the specified agents.
21
- *
22
- * Reads templates from `templates/commands/` and extension `commands/`,
23
- * renders them, packs them for each agent, and writes output files.
24
- */
25
- export function materializeAllCommands(options) {
26
- const results = [];
27
- const ctx = autoResolveContext(options.projectRoot, options.context);
28
- // Determine target agents
29
- const registeredAgents = listAgents();
30
- const targetIds = options.agentIds ?? registeredAgents.map((a) => a.id);
31
- // Collect prompt templates
32
- const templates = collectPromptTemplates(options.projectRoot);
33
- for (const agentId of targetIds) {
34
- const adapter = getAgent(agentId);
35
- if (!adapter) {
36
- results.push({
37
- agentId,
38
- commandName: "*",
39
- outputPath: "",
40
- written: false,
41
- error: `Agent '${agentId}' not registered`,
42
- });
43
- continue;
44
- }
45
- for (const { name, content } of templates) {
46
- const result = materializeSingleCommand(options.projectRoot, adapter, name, content, ctx, options.dryRun);
47
- results.push(result);
48
- }
49
- }
50
- return results;
51
- }
52
- /**
53
- * Materialize a single command for a specific agent.
54
- */
55
- function materializeSingleCommand(projectRoot, adapter, commandName, templateContent, ctx, dryRun) {
56
- const agentId = adapter.id;
57
- try {
58
- // 1. Render placeholders
59
- const rendered = renderPrompt(templateContent, ctx);
60
- // 2. Pack for agent (converts to agent-specific format)
61
- // We construct a mock CanonicalPrompt because static slash commands
62
- // don't have dynamic feature context yet (handled at runtime).
63
- const canonical = {
64
- command: commandName,
65
- feature: adapter.argumentSyntax, // e.g. "$ARGUMENTS"
66
- promptHash: "static-template",
67
- generatedAt: new Date().toISOString(),
68
- body: rendered,
69
- scope: { read: [], write: [], forbidden: [] },
70
- invariants: [],
71
- inputs: [],
72
- expectedOutput: "",
73
- outputSchema: "",
74
- articles: [],
75
- };
76
- const packed = adapter.packPrompt(canonical);
77
- // 3. Determine output path
78
- const outputDir = resolveOutputDir(projectRoot, adapter);
79
- const fileName = adapter.commandFileName(`rpt.${commandName}`);
80
- const outputPath = join(outputDir, fileName);
81
- // 4. Write
82
- if (!dryRun) {
83
- if (!existsSync(outputDir)) {
84
- mkdirSync(outputDir, { recursive: true });
85
- }
86
- writeFileSync(outputPath, packed);
87
- }
88
- return {
89
- agentId,
90
- commandName,
91
- outputPath,
92
- written: !dryRun,
93
- };
94
- }
95
- catch (err) {
96
- return {
97
- agentId,
98
- commandName,
99
- outputPath: "",
100
- written: false,
101
- error: err.message,
102
- };
103
- }
104
- }
105
- // ---------------------------------------------------------------------------
106
- // Helpers
107
- // ---------------------------------------------------------------------------
108
- /**
109
- * Collect all prompt templates from the project.
110
- */
111
- function collectPromptTemplates(projectRoot) {
112
- const templates = [];
113
- // Core prompts
114
- const coreDir = join(projectRoot, "templates", "commands");
115
- if (existsSync(coreDir)) {
116
- for (const file of readdirSync(coreDir)) {
117
- if (!file.endsWith(".md"))
118
- continue;
119
- const name = basename(file, ".md");
120
- const content = readFileSync(join(coreDir, file), "utf-8");
121
- templates.push({ name, content });
122
- }
123
- }
124
- // Extension prompts
125
- const extDir = join(projectRoot, "extensions");
126
- if (existsSync(extDir)) {
127
- for (const ext of readdirSync(extDir)) {
128
- const cmdDir = join(extDir, ext, "commands");
129
- if (!existsSync(cmdDir))
130
- continue;
131
- for (const file of readdirSync(cmdDir)) {
132
- if (!file.endsWith(".md"))
133
- continue;
134
- const name = basename(file, ".md");
135
- const content = readFileSync(join(cmdDir, file), "utf-8");
136
- templates.push({ name, content });
137
- }
138
- }
139
- }
140
- return templates;
141
- }
142
- /**
143
- * Resolve the output directory for an agent's command files.
144
- */
145
- function resolveOutputDir(projectRoot, adapter) {
146
- // Agent-specific directories
147
- const agentDirMap = {
148
- "claude-code": ".claude/commands",
149
- "cursor": ".cursor/commands",
150
- "antigravity": ".gemini/commands",
151
- "gemini": ".gemini/commands",
152
- "human": ".raptor/commands",
153
- };
154
- const dir = agentDirMap[adapter.id] ?? `.raptor/agents/${adapter.id}/commands`;
155
- return join(projectRoot, dir);
156
- }