raptor-aios 0.11.0 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +11 -0
- package/README.md +11 -3
- package/dist/_core/package.json +1 -1
- package/dist/commands/init.js +29 -240
- package/dist/commands/setup.js +149 -0
- package/dist/shared/init-core.js +260 -0
- package/dist/shared/setup/agent-resolver.js +54 -0
- package/dist/shared/setup/banner.js +19 -0
- package/dist/shared/setup/detect-stack.js +31 -0
- package/dist/shared/setup/onboard.js +18 -0
- package/dist/shared/setup/plan.js +93 -0
- package/dist/shared/setup/wizard.js +164 -0
- package/package.json +2 -1
- package/scripts/prepare-npm.mjs +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,17 @@ Format: [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
|
5
5
|
|
|
6
6
|
## [Unreleased]
|
|
7
7
|
|
|
8
|
+
## [0.12.0] - 2026-06-13
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **`raptor setup` — menu de instalação interativo + onboarding (`packages/cli/src/commands/setup.ts`).** Uma porta de entrada guiada por setas (via `@clack/prompts`) que conduz, fase a fase: ❶ **onboarding** (a essência do Raptor, pulável com `--skip-intro`); ❷ **ponto de partida** (projeto do zero vs. existente, com detecção de stack do `package.json`); ❸ **ambiente em duas perguntas** — **IDE** (VS Code, Cursor, Antigravity, JetBrains, terminal) → **IA** (Claude Code, Copilot, Gemini, Codex), filtradas em cascata e mapeadas para a *agent key* correta; ❹ **preset** (com a sugestão da stack destacada); ❺ **Jira/Figma "configurar depois"** (grava o bloco pronto no `raptor.yml`, sem disparar OAuth no fluxo); ❻ **revisão e aplicação**.
|
|
13
|
+
- **Coleta separada da execução.** O wizard apenas monta um `SetupPlan`; `applySetupPlan` o executa reusando a lógica de `init` — extraída intacta para `performInit` (`shared/init-core.ts`) —, então o `.raptor/` gerado é byte-equivalente ao de `raptor init`.
|
|
14
|
+
- **Caminho não-interativo** (`--non-interactive --ide --ai --preset …`, ou simplesmente a ausência de TTY) para CI/scripts, compartilhando o MESMO plano do wizard — o que mantém a experiência interativa testável sem dirigir um terminal.
|
|
15
|
+
- **Reconfigure não-destrutivo:** `raptor setup --force` agora preserva os blocos do usuário no `raptor.yml` (`git` com `commit_trailer`/`kind_prefixes`, `gates`, conexões `jira`/`design` — inclusive desconectadas-mas-configuradas, que retêm `cloud_id`/`custom_fields`/token — e o nome do projeto), em vez de revertê-los aos defaults do template.
|
|
16
|
+
- Banner ASCII responsivo (degrada em terminais estreitos, respeita `NO_COLOR`/não-TTY) e exit codes corretos (cancelar ou falhar → não-zero; recusar a aplicação → zero).
|
|
17
|
+
- Endurecido por revisão adversarial multi-dimensão (correção, fidelidade de API `@clack`, paridade da extração de `init`, contrato de config, reuso), com etapa cética por achado e o ciclo repetido até convergir.
|
|
18
|
+
|
|
8
19
|
## [0.11.0] - 2026-06-12
|
|
9
20
|
|
|
10
21
|
### Added
|
package/README.md
CHANGED
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
```bash
|
|
23
23
|
npm install -g raptor-aios
|
|
24
24
|
cd meu-app-react-native
|
|
25
|
-
raptor
|
|
25
|
+
raptor setup # 🧭 assistente guiado: IA/IDE, preset, Jira/Figma (ou 'raptor init' direto)
|
|
26
26
|
raptor new login-biometrico -d "Permitir login com Face ID / digital"
|
|
27
27
|
# → cria a feature E já troca para a branch feat/001-login-biometrico
|
|
28
28
|
```
|
|
@@ -95,11 +95,19 @@ raptor doctor # 🩺 checagem de saúde do ambiente e do projeto
|
|
|
95
95
|
|
|
96
96
|
### 1️⃣ Inicialize o Raptor
|
|
97
97
|
|
|
98
|
+
A forma **guiada** — um assistente interativo (navegação por setas) que apresenta o Raptor e coleta IA/IDE, preset e integrações Jira/Figma:
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
raptor setup
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Ou, **direto e não-interativo** (ideal para CI/scripts):
|
|
105
|
+
|
|
98
106
|
```bash
|
|
99
107
|
raptor init --ai=claude-code --preset=mobile-opinionated
|
|
100
108
|
```
|
|
101
109
|
|
|
102
|
-
|
|
110
|
+
Qualquer um cria a pasta `.raptor/`, a **constituição** do projeto, e materializa os **slash commands** em `.claude/commands/` (ou `.cursor/`, etc., conforme o agente).
|
|
103
111
|
|
|
104
112
|
| Flag | Para que serve |
|
|
105
113
|
| ----------------------------- | ------------------------------------------------------------------------------------------------------------ |
|
|
@@ -481,7 +489,7 @@ raptor new login --jira APP-1234 # semeia a spec a partir da issue
|
|
|
481
489
|
## 📖 Referência de comandos CLI
|
|
482
490
|
|
|
483
491
|
```text
|
|
484
|
-
🔄 Ciclo init · new · clarify · plan · tasks · analyze · checklist · implement · approve · taskstoissues
|
|
492
|
+
🔄 Ciclo setup · init · new · clarify · plan · tasks · analyze · checklist · implement · approve · taskstoissues
|
|
485
493
|
🌿 Branch/commit commit · scan
|
|
486
494
|
🔬 Verificação verify · verify a11y · verify perf · verify stores · verify os-matrix
|
|
487
495
|
verify architecture · verify audit · verify constitution
|
package/dist/_core/package.json
CHANGED
package/dist/commands/init.js
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
import { Flags } from "@oclif/core";
|
|
2
2
|
import { BaseCommand } from "../base-command.js";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import { currentActor } from "../shared/project.js";
|
|
7
|
-
import { collectKnowledgeRefs, installBundledExtensions, installShellScripts, installTemplates, } from "../shared/scaffold.js";
|
|
3
|
+
import { basename } from "node:path";
|
|
4
|
+
import { CORE_VERSION, SELECTABLE_AGENT_KEYS } from "../_core/dist/index.js";
|
|
5
|
+
import { describeCi, describeHooks, InitExistsError, performInit, } from "../shared/init-core.js";
|
|
8
6
|
export default class Init extends BaseCommand {
|
|
9
7
|
static description = "Initialize a Raptor project in the current directory";
|
|
10
8
|
static examples = [
|
|
@@ -56,184 +54,31 @@ export default class Init extends BaseCommand {
|
|
|
56
54
|
};
|
|
57
55
|
async run() {
|
|
58
56
|
const { flags } = await this.parse(Init);
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
57
|
+
let result;
|
|
58
|
+
try {
|
|
59
|
+
result = performInit({
|
|
60
|
+
cwd: process.cwd(),
|
|
61
|
+
projectName: flags["project-name"],
|
|
62
|
+
preset: flags.preset,
|
|
63
|
+
ai: flags.ai,
|
|
64
|
+
script: flags.script,
|
|
65
|
+
branchNumbering: flags["branch-numbering"],
|
|
66
|
+
noGit: flags["no-git"],
|
|
67
|
+
withCi: flags["with-ci"],
|
|
68
|
+
withHooks: flags["with-hooks"],
|
|
69
|
+
force: flags.force,
|
|
70
|
+
}, this);
|
|
63
71
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
"specs",
|
|
68
|
-
"memory",
|
|
69
|
-
"templates",
|
|
70
|
-
"templates/overrides",
|
|
71
|
-
"extensions",
|
|
72
|
-
"workflows",
|
|
73
|
-
"presets",
|
|
74
|
-
"agents",
|
|
75
|
-
"skills",
|
|
76
|
-
"scripts",
|
|
77
|
-
"scripts/bash",
|
|
78
|
-
"scripts/powershell",
|
|
79
|
-
"cache",
|
|
80
|
-
];
|
|
81
|
-
mkdirSync(raptorDir, { recursive: true });
|
|
82
|
-
for (const sub of subdirs)
|
|
83
|
-
mkdirSync(join(raptorDir, sub), { recursive: true });
|
|
84
|
-
const rawPresetIds = flags.preset
|
|
85
|
-
.split(",")
|
|
86
|
-
.map((s) => s.trim())
|
|
87
|
-
.filter((s) => s.length > 0);
|
|
88
|
-
const resolved = rawPresetIds.map((raw) => ({
|
|
89
|
-
raw,
|
|
90
|
-
id: resolvePresetId(raw) ?? raw,
|
|
91
|
-
preset: getPreset(raw),
|
|
92
|
-
}));
|
|
93
|
-
for (const r of resolved) {
|
|
94
|
-
if (!r.preset) {
|
|
95
|
-
this.warn(`unknown preset "${r.raw}" — known presets: ${knownPresetIds().join(", ")}. It will have no constitution articles.`);
|
|
72
|
+
catch (err) {
|
|
73
|
+
if (err instanceof InitExistsError) {
|
|
74
|
+
this.error(err.message);
|
|
96
75
|
}
|
|
76
|
+
throw err;
|
|
97
77
|
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
const
|
|
102
|
-
const presetArticles = presets.length > 1
|
|
103
|
-
? renderPresetsArticles(presets)
|
|
104
|
-
: preset
|
|
105
|
-
? renderPresetArticles(preset)
|
|
106
|
-
: "";
|
|
107
|
-
const presetLabel = presetIds.join(", ");
|
|
108
|
-
const allArticleIds = presets.length
|
|
109
|
-
? presets.flatMap((p) => p.articles.map((a) => a.id))
|
|
110
|
-
: undefined;
|
|
111
|
-
const presetsBlock = presetIds.length > 1
|
|
112
|
-
? `presets:\n${presetIds.map((id) => ` - ${id}`).join("\n")}`
|
|
113
|
-
: `preset: ${presetId}`;
|
|
114
|
-
const raptorYml = renderBundled("raptorYml", {
|
|
115
|
-
raptorVersion: VERSION,
|
|
116
|
-
projectName,
|
|
117
|
-
createdAt,
|
|
118
|
-
presetsBlock,
|
|
119
|
-
});
|
|
120
|
-
writeFileSync(join(raptorDir, "raptor.yml"), raptorYml);
|
|
121
|
-
const manifest = createManifest({
|
|
122
|
-
raptor_version: VERSION,
|
|
123
|
-
project_name: projectName,
|
|
124
|
-
integration: flags.ai,
|
|
125
|
-
script_type: flags.script,
|
|
126
|
-
branch_numbering: flags["branch-numbering"],
|
|
127
|
-
preset: presetId,
|
|
128
|
-
scripts_installed: [],
|
|
129
|
-
templates_installed: [],
|
|
130
|
-
extensions_registered: [],
|
|
131
|
-
});
|
|
132
|
-
writeFileSync(join(raptorDir, "raptor.manifest.json"), JSON.stringify(manifest, null, 2) + "\n");
|
|
133
|
-
const initOptions = {
|
|
134
|
-
ai: flags.ai,
|
|
135
|
-
script_type: flags.script,
|
|
136
|
-
branch_numbering: flags["branch-numbering"],
|
|
137
|
-
preset: presetId,
|
|
138
|
-
no_git: flags["no-git"],
|
|
139
|
-
created_at: createdAt,
|
|
140
|
-
};
|
|
141
|
-
writeFileSync(join(raptorDir, "init-options.json"), JSON.stringify(initOptions, null, 2) + "\n");
|
|
142
|
-
const constitution = renderBundled("constitution", {
|
|
143
|
-
projectName,
|
|
144
|
-
preset: presetLabel,
|
|
145
|
-
coreBlock: coreBlock(),
|
|
146
|
-
presetArticles,
|
|
147
|
-
});
|
|
148
|
-
const constitutionPath = join(raptorDir, "constitution.md");
|
|
149
|
-
writeFileSync(constitutionPath, constitution);
|
|
150
|
-
writeFileSync(join(raptorDir, "memory", "constitution.md"), constitution);
|
|
151
|
-
writeFileSync(join(raptorDir, "memory", "amendments.log"), "");
|
|
152
|
-
writeFileSync(join(raptorDir, "memory", "decisions.md"), "# Architecture Decision Records\n\n");
|
|
153
|
-
writeFileSync(join(raptorDir, "memory", "glossary.md"), "# Project Glossary\n\n");
|
|
154
|
-
writeFileSync(join(raptorDir, ".gitignore"), "cache/\n*.log\n");
|
|
155
|
-
installShellScripts(this, raptorDir, manifest);
|
|
156
|
-
installTemplates(raptorDir);
|
|
157
|
-
installBundledExtensions(this, raptorDir, manifest, presetIds);
|
|
158
|
-
const knowledgeRefs = collectKnowledgeRefs(raptorDir);
|
|
159
|
-
const installedAgents = detectInstalledAgents();
|
|
160
|
-
const primaryAgent = getAgentByKind(flags.ai);
|
|
161
|
-
if (primaryAgent && !installedAgents.some((a) => a.id === primaryAgent.id)) {
|
|
162
|
-
this.warn(`Primary agent "${flags.ai}" CLI was not detected on PATH — materializing its commands anyway.`);
|
|
163
|
-
}
|
|
164
|
-
const targetAgents = primaryAgent
|
|
165
|
-
? [
|
|
166
|
-
primaryAgent,
|
|
167
|
-
...installedAgents.filter((a) => a.id !== primaryAgent.id),
|
|
168
|
-
]
|
|
169
|
-
: installedAgents;
|
|
170
|
-
const agentsYamlContent = generateAgentsYaml(targetAgents.map((a) => ({
|
|
171
|
-
id: a.id,
|
|
172
|
-
kind: a.id,
|
|
173
|
-
capabilities: [...a.capabilities],
|
|
174
|
-
})));
|
|
175
|
-
const agentsYamlPath = join(raptorDir, "agents.yml");
|
|
176
|
-
writeFileSync(agentsYamlPath, agentsYamlContent);
|
|
177
|
-
const contextBlock = buildContextBlock({
|
|
178
|
-
projectName,
|
|
179
|
-
preset: presetLabel,
|
|
180
|
-
constitutionArticles: allArticleIds,
|
|
181
|
-
knowledge: knowledgeRefs,
|
|
182
|
-
});
|
|
183
|
-
for (const agent of targetAgents) {
|
|
184
|
-
if (agent.contextFile) {
|
|
185
|
-
const filePath = join(cwd, agent.contextFile);
|
|
186
|
-
upsertContextFile(filePath, contextBlock);
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
const allMaterialized = [];
|
|
190
|
-
for (const agent of targetAgents) {
|
|
191
|
-
if (agent.id === "human")
|
|
192
|
-
continue;
|
|
193
|
-
try {
|
|
194
|
-
const result = materializeSlashCommands({
|
|
195
|
-
projectRoot: cwd,
|
|
196
|
-
projectName,
|
|
197
|
-
adapter: agent,
|
|
198
|
-
constitutionArticles: allArticleIds,
|
|
199
|
-
presetId: presetId,
|
|
200
|
-
});
|
|
201
|
-
allMaterialized.push({
|
|
202
|
-
agentId: agent.id,
|
|
203
|
-
count: result.commands.length,
|
|
204
|
-
paths: result.commands.map((c) => c.path),
|
|
205
|
-
});
|
|
206
|
-
}
|
|
207
|
-
catch (err) {
|
|
208
|
-
this.warn(`Failed to materialize slash commands for ${agent.displayName}: ${err instanceof Error ? err.message : String(err)}`);
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
const amendmentsLog = join(raptorDir, "memory", "amendments.log");
|
|
212
|
-
const actor = currentActor("human");
|
|
213
|
-
appendAuditEvent(amendmentsLog, {
|
|
214
|
-
event: "constitution.initialized",
|
|
215
|
-
command: "raptor init",
|
|
216
|
-
actor: { user: actor.user, via: actor.via },
|
|
217
|
-
outputs: [
|
|
218
|
-
{ path: ".raptor/constitution.md", hash: hashString(constitution) },
|
|
219
|
-
{ path: ".raptor/agents.yml", hash: hashString(agentsYamlContent) },
|
|
220
|
-
],
|
|
221
|
-
meta: {
|
|
222
|
-
core_version: CORE_VERSION,
|
|
223
|
-
preset: presetLabel,
|
|
224
|
-
preset_version: preset?.version,
|
|
225
|
-
preset_articles: allArticleIds,
|
|
226
|
-
project: projectName,
|
|
227
|
-
detected_agents: installedAgents.map((a) => a.id),
|
|
228
|
-
configured_agents: targetAgents.map((a) => a.id),
|
|
229
|
-
},
|
|
230
|
-
});
|
|
231
|
-
const ciResult = flags["with-ci"]
|
|
232
|
-
? this.installCiWorkflow(cwd)
|
|
233
|
-
: { status: "disabled" };
|
|
234
|
-
const hooksResult = flags["with-hooks"]
|
|
235
|
-
? this.installGitHooks(cwd)
|
|
236
|
-
: { status: "disabled" };
|
|
78
|
+
this.renderSummary(result, flags);
|
|
79
|
+
}
|
|
80
|
+
renderSummary(result, flags) {
|
|
81
|
+
const { raptorDir, projectName, presets, presetLabel, allArticleIds, targetAgents, materialized, primaryAgentId, agentsYamlPath, manifestPath, ciResult, hooksResult, } = result;
|
|
237
82
|
this.log(`✓ Initialized Raptor project in ${raptorDir}`);
|
|
238
83
|
this.log(` Project: ${projectName}`);
|
|
239
84
|
if (presets.length > 1) {
|
|
@@ -242,6 +87,7 @@ export default class Init extends BaseCommand {
|
|
|
242
87
|
this.log(` Presets: ${presetLabel} (${presets.length} stacked, ${totalArticles} articles, ${totalGates} gates)`);
|
|
243
88
|
}
|
|
244
89
|
else {
|
|
90
|
+
const preset = presets[0];
|
|
245
91
|
this.log(` Preset: ${flags.preset}${preset ? ` (v${preset.version}, ${preset.articles.length} articles, ${preset.gates.length} gates)` : " (unknown)"}`);
|
|
246
92
|
}
|
|
247
93
|
this.log(` Core: v${CORE_VERSION}`);
|
|
@@ -249,7 +95,7 @@ export default class Init extends BaseCommand {
|
|
|
249
95
|
this.log(` Articles: ${allArticleIds.join(", ")}`);
|
|
250
96
|
}
|
|
251
97
|
this.log(` Agents: ${targetAgents.map((a) => `${a.displayName} (${a.id})`).join(", ")}`);
|
|
252
|
-
for (const m of
|
|
98
|
+
for (const m of materialized) {
|
|
253
99
|
this.log(` Slash: ${m.count} commands materialized for ${m.agentId}`);
|
|
254
100
|
}
|
|
255
101
|
this.log(` Config: ${agentsYamlPath}`);
|
|
@@ -257,11 +103,11 @@ export default class Init extends BaseCommand {
|
|
|
257
103
|
this.log(` Hooks: ${describeHooks(hooksResult)}`);
|
|
258
104
|
this.log("");
|
|
259
105
|
this.log(` AI: ${flags.ai} (script: ${flags.script}, branching: ${flags["branch-numbering"]})`);
|
|
260
|
-
this.log(` Manifest: ${
|
|
106
|
+
this.log(` Manifest: ${manifestPath}`);
|
|
261
107
|
this.log("Next steps:");
|
|
262
108
|
this.log(` 1. Review ${raptorDir}/constitution.md (RAPTOR-CORE block is immutable)`);
|
|
263
109
|
this.log(` 2. Review ${raptorDir}/agents.yml (agent policy + fallback chain)`);
|
|
264
|
-
const primaryMat =
|
|
110
|
+
const primaryMat = materialized.find((m) => primaryAgentId && m.agentId === primaryAgentId) ?? materialized[0];
|
|
265
111
|
const slashList = primaryMat
|
|
266
112
|
? primaryMat.paths
|
|
267
113
|
.map((p) => `/${basename(p).replace(/\.[^.]+$/, "")}`)
|
|
@@ -270,61 +116,4 @@ export default class Init extends BaseCommand {
|
|
|
270
116
|
this.log(` 3. Use slash commands: ${slashList}`);
|
|
271
117
|
this.log(` 4. Or run 'raptor new <slug>' to create your first feature directly`);
|
|
272
118
|
}
|
|
273
|
-
installCiWorkflow(cwd) {
|
|
274
|
-
const dir = join(cwd, ".github", "workflows");
|
|
275
|
-
const path = join(dir, "raptor-verify.yml");
|
|
276
|
-
if (existsSync(path)) {
|
|
277
|
-
return { status: "skipped-existing", path };
|
|
278
|
-
}
|
|
279
|
-
mkdirSync(dir, { recursive: true });
|
|
280
|
-
const content = renderBundled("ghaWorkflow", { raptorVersion: VERSION });
|
|
281
|
-
writeFileSync(path, content);
|
|
282
|
-
return { status: "installed", path };
|
|
283
|
-
}
|
|
284
|
-
installGitHooks(cwd) {
|
|
285
|
-
const gitDir = join(cwd, ".git");
|
|
286
|
-
if (!existsSync(gitDir)) {
|
|
287
|
-
return { status: "skipped-no-git" };
|
|
288
|
-
}
|
|
289
|
-
const hooksDir = join(gitDir, "hooks");
|
|
290
|
-
mkdirSync(hooksDir, { recursive: true });
|
|
291
|
-
const hooks = [
|
|
292
|
-
{ name: "pre-commit", key: "preCommitHook" },
|
|
293
|
-
{ name: "pre-push", key: "prePushHook" },
|
|
294
|
-
{ name: "commit-msg", key: "commitMsgHook" },
|
|
295
|
-
];
|
|
296
|
-
const installed = [];
|
|
297
|
-
const skipped = [];
|
|
298
|
-
for (const h of hooks) {
|
|
299
|
-
const path = join(hooksDir, h.name);
|
|
300
|
-
if (existsSync(path)) {
|
|
301
|
-
skipped.push(h.name);
|
|
302
|
-
continue;
|
|
303
|
-
}
|
|
304
|
-
const content = renderBundled(h.key, {});
|
|
305
|
-
writeFileSync(path, content);
|
|
306
|
-
chmodSync(path, 0o755);
|
|
307
|
-
installed.push(h.name);
|
|
308
|
-
}
|
|
309
|
-
return { status: "ok", installed, skipped };
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
function describeCi(r) {
|
|
313
|
-
if (r.status === "disabled")
|
|
314
|
-
return "disabled (--no-with-ci)";
|
|
315
|
-
if (r.status === "skipped-existing")
|
|
316
|
-
return `skipped (${r.path} already exists)`;
|
|
317
|
-
return `installed at ${r.path}`;
|
|
318
|
-
}
|
|
319
|
-
function describeHooks(r) {
|
|
320
|
-
if (r.status === "disabled")
|
|
321
|
-
return "disabled (--no-with-hooks)";
|
|
322
|
-
if (r.status === "skipped-no-git")
|
|
323
|
-
return "skipped (.git/ not found — run 'git init' first)";
|
|
324
|
-
const parts = [];
|
|
325
|
-
if (r.installed.length > 0)
|
|
326
|
-
parts.push(`installed: ${r.installed.join(", ")}`);
|
|
327
|
-
if (r.skipped.length > 0)
|
|
328
|
-
parts.push(`skipped (already exist): ${r.skipped.join(", ")}`);
|
|
329
|
-
return parts.length > 0 ? parts.join("; ") : "nothing to do";
|
|
330
119
|
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { Flags } from "@oclif/core";
|
|
2
|
+
import { BaseCommand } from "../base-command.js";
|
|
3
|
+
import { applySetupPlan, buildSetupPlan, } from "../shared/setup/plan.js";
|
|
4
|
+
import { describeCi, describeHooks, InitExistsError, } from "../shared/init-core.js";
|
|
5
|
+
import { aisForIde, IDES, resolveAgentKey, } from "../shared/setup/agent-resolver.js";
|
|
6
|
+
import { detectProjectStack } from "../shared/setup/detect-stack.js";
|
|
7
|
+
import { resolveInteractive } from "../shared/git-prompt.js";
|
|
8
|
+
import { runSetupWizard } from "../shared/setup/wizard.js";
|
|
9
|
+
const IDE_IDS = IDES.map((i) => i.id);
|
|
10
|
+
export default class Setup extends BaseCommand {
|
|
11
|
+
static description = "Guided interactive setup — onboarding + project initialization (arrow-key wizard)";
|
|
12
|
+
static examples = [
|
|
13
|
+
"<%= config.bin %> setup",
|
|
14
|
+
"<%= config.bin %> setup --ide=vscode --ai=claude-code",
|
|
15
|
+
"<%= config.bin %> setup --non-interactive --ide=terminal --ai=claude-code --preset=mobile-opinionated",
|
|
16
|
+
];
|
|
17
|
+
static flags = {
|
|
18
|
+
ide: Flags.string({
|
|
19
|
+
description: `Target IDE/environment (${IDE_IDS.join(", ")})`,
|
|
20
|
+
options: IDE_IDS,
|
|
21
|
+
}),
|
|
22
|
+
ai: Flags.string({
|
|
23
|
+
description: "AI assistant id within the chosen IDE (valid values depend on --ide)",
|
|
24
|
+
}),
|
|
25
|
+
preset: Flags.string({
|
|
26
|
+
description: "Constitution preset, or comma-separated stack",
|
|
27
|
+
}),
|
|
28
|
+
"project-name": Flags.string({
|
|
29
|
+
description: "Project display name (default: current directory name)",
|
|
30
|
+
}),
|
|
31
|
+
"jira-later": Flags.boolean({
|
|
32
|
+
description: "Write a disabled jira: block so 'raptor jira connect' is one step away",
|
|
33
|
+
default: false,
|
|
34
|
+
}),
|
|
35
|
+
"figma-later": Flags.boolean({
|
|
36
|
+
description: "Write a disabled design: block so 'raptor design connect' is one step away",
|
|
37
|
+
default: false,
|
|
38
|
+
}),
|
|
39
|
+
"skip-intro": Flags.boolean({
|
|
40
|
+
description: "Skip the onboarding intro in the wizard",
|
|
41
|
+
default: false,
|
|
42
|
+
}),
|
|
43
|
+
"non-interactive": Flags.boolean({
|
|
44
|
+
description: "Run without prompts (requires --ide and --ai)",
|
|
45
|
+
default: false,
|
|
46
|
+
}),
|
|
47
|
+
force: Flags.boolean({
|
|
48
|
+
description: "Overwrite an existing .raptor/ directory",
|
|
49
|
+
default: false,
|
|
50
|
+
}),
|
|
51
|
+
};
|
|
52
|
+
async run() {
|
|
53
|
+
const { flags } = await this.parse(Setup);
|
|
54
|
+
const cwd = process.cwd();
|
|
55
|
+
if (resolveInteractive(flags["non-interactive"])) {
|
|
56
|
+
const outcome = await runSetupWizard({
|
|
57
|
+
cwd,
|
|
58
|
+
force: flags.force,
|
|
59
|
+
skipIntro: flags["skip-intro"],
|
|
60
|
+
defaults: {
|
|
61
|
+
ide: flags.ide,
|
|
62
|
+
ai: flags.ai,
|
|
63
|
+
preset: flags.preset,
|
|
64
|
+
projectName: flags["project-name"],
|
|
65
|
+
jiraLater: flags["jira-later"],
|
|
66
|
+
figmaLater: flags["figma-later"],
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
if (outcome === "cancelled" || outcome === "failed")
|
|
70
|
+
this.exit(1);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
const answers = this.answersFromFlags(flags, cwd);
|
|
74
|
+
const plan = this.buildPlanOrFail(answers, cwd, flags.force);
|
|
75
|
+
const result = this.applyOrFail(plan);
|
|
76
|
+
this.renderSummary(result, plan);
|
|
77
|
+
}
|
|
78
|
+
answersFromFlags(flags, cwd) {
|
|
79
|
+
const ide = flags.ide;
|
|
80
|
+
const ai = flags.ai;
|
|
81
|
+
if (!ide || !ai) {
|
|
82
|
+
this.error("Non-interactive setup requires --ide and --ai.\n" +
|
|
83
|
+
"Example: raptor setup --non-interactive --ide=terminal --ai=claude-code");
|
|
84
|
+
}
|
|
85
|
+
if (!resolveAgentKey(ide, ai)) {
|
|
86
|
+
const valid = aisForIde(ide).map((a) => a.id).join(", ") || "(none)";
|
|
87
|
+
this.error(`AI "${ai}" is not valid for IDE "${ide}". Valid: ${valid}`);
|
|
88
|
+
}
|
|
89
|
+
const stack = detectProjectStack(cwd);
|
|
90
|
+
const preset = flags.preset ?? stack.suggestedPreset ?? "mobile-opinionated";
|
|
91
|
+
return {
|
|
92
|
+
startingPoint: stack.isBrownfield ? "existing" : "scratch",
|
|
93
|
+
projectName: flags["project-name"],
|
|
94
|
+
ide,
|
|
95
|
+
ai,
|
|
96
|
+
preset,
|
|
97
|
+
jiraLater: flags["jira-later"],
|
|
98
|
+
figmaLater: flags["figma-later"],
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
buildPlanOrFail(answers, cwd, force) {
|
|
102
|
+
try {
|
|
103
|
+
return buildSetupPlan(answers, { cwd, force });
|
|
104
|
+
}
|
|
105
|
+
catch (err) {
|
|
106
|
+
this.error(err instanceof Error ? err.message : String(err));
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
applyOrFail(plan) {
|
|
110
|
+
try {
|
|
111
|
+
return applySetupPlan(plan, this);
|
|
112
|
+
}
|
|
113
|
+
catch (err) {
|
|
114
|
+
if (err instanceof InitExistsError) {
|
|
115
|
+
this.error(`${err.message} Use 'raptor setup --force' to reconfigure.`);
|
|
116
|
+
}
|
|
117
|
+
throw err;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
renderSummary(result, plan) {
|
|
121
|
+
const { init } = result;
|
|
122
|
+
this.log(`✓ Raptor setup complete in ${init.raptorDir}`);
|
|
123
|
+
this.log(` Project: ${init.projectName}`);
|
|
124
|
+
this.log(` Preset: ${init.presetLabel}`);
|
|
125
|
+
this.log(` Agent: ${plan.primaryAgentKey}`);
|
|
126
|
+
for (const m of init.materialized) {
|
|
127
|
+
this.log(` Slash: ${m.count} commands materialized for ${m.agentId}`);
|
|
128
|
+
}
|
|
129
|
+
this.log(` CI: ${describeCi(init.ciResult)}`);
|
|
130
|
+
this.log(` Hooks: ${describeHooks(init.hooksResult)}`);
|
|
131
|
+
if (result.jiraPreserved) {
|
|
132
|
+
this.log(" Jira: existing config preserved");
|
|
133
|
+
}
|
|
134
|
+
else if (result.jiraConfigured) {
|
|
135
|
+
this.log(" Jira: configured later (run 'raptor jira connect')");
|
|
136
|
+
}
|
|
137
|
+
if (result.figmaPreserved) {
|
|
138
|
+
this.log(" Figma: existing config preserved");
|
|
139
|
+
}
|
|
140
|
+
else if (result.figmaConfigured) {
|
|
141
|
+
this.log(" Figma: configured later (run 'raptor design connect')");
|
|
142
|
+
}
|
|
143
|
+
this.log("Next steps:");
|
|
144
|
+
this.log(plan.startingPoint === "existing"
|
|
145
|
+
? " 1. raptor new <slug> — specify a feature over your existing code"
|
|
146
|
+
: " 1. raptor new <slug> — create your first feature");
|
|
147
|
+
this.log(" 2. raptor status — see the project at a glance");
|
|
148
|
+
}
|
|
149
|
+
}
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
import { chmodSync, existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { basename, join } from "node:path";
|
|
3
|
+
import { appendAuditEvent, buildContextBlock, coreBlock, CORE_VERSION, createManifest, detectInstalledAgents, generateAgentsYaml, getAgentByKind, getPreset, hashString, knownPresetIds, resolvePresetId, materializeSlashCommands, renderBundled, renderPresetArticles, renderPresetsArticles, upsertContextFile, VERSION, } from "../_core/dist/index.js";
|
|
4
|
+
import { currentActor } from "./project.js";
|
|
5
|
+
import { collectKnowledgeRefs, installBundledExtensions, installShellScripts, installTemplates, } from "./scaffold.js";
|
|
6
|
+
export class InitExistsError extends Error {
|
|
7
|
+
constructor(message) {
|
|
8
|
+
super(message);
|
|
9
|
+
this.name = "InitExistsError";
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
export function performInit(opts, logger) {
|
|
13
|
+
const cwd = opts.cwd;
|
|
14
|
+
const raptorDir = join(cwd, ".raptor");
|
|
15
|
+
if (existsSync(raptorDir) && !opts.force) {
|
|
16
|
+
throw new InitExistsError("A .raptor/ directory already exists here. Use --force to overwrite.");
|
|
17
|
+
}
|
|
18
|
+
const projectName = opts.projectName ?? basename(cwd);
|
|
19
|
+
const createdAt = new Date().toISOString();
|
|
20
|
+
const subdirs = [
|
|
21
|
+
"specs",
|
|
22
|
+
"memory",
|
|
23
|
+
"templates",
|
|
24
|
+
"templates/overrides",
|
|
25
|
+
"extensions",
|
|
26
|
+
"workflows",
|
|
27
|
+
"presets",
|
|
28
|
+
"agents",
|
|
29
|
+
"skills",
|
|
30
|
+
"scripts",
|
|
31
|
+
"scripts/bash",
|
|
32
|
+
"scripts/powershell",
|
|
33
|
+
"cache",
|
|
34
|
+
];
|
|
35
|
+
mkdirSync(raptorDir, { recursive: true });
|
|
36
|
+
for (const sub of subdirs)
|
|
37
|
+
mkdirSync(join(raptorDir, sub), { recursive: true });
|
|
38
|
+
const rawPresetIds = opts.preset
|
|
39
|
+
.split(",")
|
|
40
|
+
.map((s) => s.trim())
|
|
41
|
+
.filter((s) => s.length > 0);
|
|
42
|
+
const resolved = rawPresetIds.map((raw) => ({
|
|
43
|
+
raw,
|
|
44
|
+
id: resolvePresetId(raw) ?? raw,
|
|
45
|
+
preset: getPreset(raw),
|
|
46
|
+
}));
|
|
47
|
+
for (const r of resolved) {
|
|
48
|
+
if (!r.preset) {
|
|
49
|
+
logger.warn(`unknown preset "${r.raw}" — known presets: ${knownPresetIds().join(", ")}. It will have no constitution articles.`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
const presets = resolved
|
|
53
|
+
.map((r) => r.preset)
|
|
54
|
+
.filter((p) => Boolean(p));
|
|
55
|
+
const presetIds = resolved.map((r) => r.id);
|
|
56
|
+
const preset = presets[0];
|
|
57
|
+
const presetId = presetIds[0];
|
|
58
|
+
const presetArticles = presets.length > 1
|
|
59
|
+
? renderPresetsArticles(presets)
|
|
60
|
+
: preset
|
|
61
|
+
? renderPresetArticles(preset)
|
|
62
|
+
: "";
|
|
63
|
+
const presetLabel = presetIds.join(", ");
|
|
64
|
+
const allArticleIds = presets.length
|
|
65
|
+
? presets.flatMap((p) => p.articles.map((a) => a.id))
|
|
66
|
+
: undefined;
|
|
67
|
+
const presetsBlock = presetIds.length > 1
|
|
68
|
+
? `presets:\n${presetIds.map((id) => ` - ${id}`).join("\n")}`
|
|
69
|
+
: `preset: ${presetId}`;
|
|
70
|
+
const raptorYml = renderBundled("raptorYml", {
|
|
71
|
+
raptorVersion: VERSION,
|
|
72
|
+
projectName,
|
|
73
|
+
createdAt,
|
|
74
|
+
presetsBlock,
|
|
75
|
+
});
|
|
76
|
+
writeFileSync(join(raptorDir, "raptor.yml"), raptorYml);
|
|
77
|
+
const manifest = createManifest({
|
|
78
|
+
raptor_version: VERSION,
|
|
79
|
+
project_name: projectName,
|
|
80
|
+
integration: opts.ai,
|
|
81
|
+
script_type: opts.script,
|
|
82
|
+
branch_numbering: opts.branchNumbering,
|
|
83
|
+
preset: presetId,
|
|
84
|
+
scripts_installed: [],
|
|
85
|
+
templates_installed: [],
|
|
86
|
+
extensions_registered: [],
|
|
87
|
+
});
|
|
88
|
+
const manifestPath = join(raptorDir, "raptor.manifest.json");
|
|
89
|
+
writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + "\n");
|
|
90
|
+
const initOptions = {
|
|
91
|
+
ai: opts.ai,
|
|
92
|
+
script_type: opts.script,
|
|
93
|
+
branch_numbering: opts.branchNumbering,
|
|
94
|
+
preset: presetId,
|
|
95
|
+
no_git: opts.noGit,
|
|
96
|
+
created_at: createdAt,
|
|
97
|
+
};
|
|
98
|
+
writeFileSync(join(raptorDir, "init-options.json"), JSON.stringify(initOptions, null, 2) + "\n");
|
|
99
|
+
const constitution = renderBundled("constitution", {
|
|
100
|
+
projectName,
|
|
101
|
+
preset: presetLabel,
|
|
102
|
+
coreBlock: coreBlock(),
|
|
103
|
+
presetArticles,
|
|
104
|
+
});
|
|
105
|
+
writeFileSync(join(raptorDir, "constitution.md"), constitution);
|
|
106
|
+
writeFileSync(join(raptorDir, "memory", "constitution.md"), constitution);
|
|
107
|
+
writeFileSync(join(raptorDir, "memory", "amendments.log"), "");
|
|
108
|
+
writeFileSync(join(raptorDir, "memory", "decisions.md"), "# Architecture Decision Records\n\n");
|
|
109
|
+
writeFileSync(join(raptorDir, "memory", "glossary.md"), "# Project Glossary\n\n");
|
|
110
|
+
writeFileSync(join(raptorDir, ".gitignore"), "cache/\n*.log\n");
|
|
111
|
+
installShellScripts(logger, raptorDir, manifest);
|
|
112
|
+
installTemplates(raptorDir);
|
|
113
|
+
installBundledExtensions(logger, raptorDir, manifest, presetIds);
|
|
114
|
+
const knowledgeRefs = collectKnowledgeRefs(raptorDir);
|
|
115
|
+
const installedAgents = detectInstalledAgents();
|
|
116
|
+
const primaryAgent = getAgentByKind(opts.ai);
|
|
117
|
+
if (primaryAgent && !installedAgents.some((a) => a.id === primaryAgent.id)) {
|
|
118
|
+
logger.warn(`Primary agent "${opts.ai}" CLI was not detected on PATH — materializing its commands anyway.`);
|
|
119
|
+
}
|
|
120
|
+
const targetAgents = primaryAgent
|
|
121
|
+
? [primaryAgent, ...installedAgents.filter((a) => a.id !== primaryAgent.id)]
|
|
122
|
+
: installedAgents;
|
|
123
|
+
const agentsYamlContent = generateAgentsYaml(targetAgents.map((a) => ({
|
|
124
|
+
id: a.id,
|
|
125
|
+
kind: a.id,
|
|
126
|
+
capabilities: [...a.capabilities],
|
|
127
|
+
})));
|
|
128
|
+
const agentsYamlPath = join(raptorDir, "agents.yml");
|
|
129
|
+
writeFileSync(agentsYamlPath, agentsYamlContent);
|
|
130
|
+
const contextBlock = buildContextBlock({
|
|
131
|
+
projectName,
|
|
132
|
+
preset: presetLabel,
|
|
133
|
+
constitutionArticles: allArticleIds,
|
|
134
|
+
knowledge: knowledgeRefs,
|
|
135
|
+
});
|
|
136
|
+
for (const agent of targetAgents) {
|
|
137
|
+
if (agent.contextFile) {
|
|
138
|
+
upsertContextFile(join(cwd, agent.contextFile), contextBlock);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
const materialized = [];
|
|
142
|
+
for (const agent of targetAgents) {
|
|
143
|
+
if (agent.id === "human")
|
|
144
|
+
continue;
|
|
145
|
+
try {
|
|
146
|
+
const result = materializeSlashCommands({
|
|
147
|
+
projectRoot: cwd,
|
|
148
|
+
projectName,
|
|
149
|
+
adapter: agent,
|
|
150
|
+
constitutionArticles: allArticleIds,
|
|
151
|
+
presetId: presetId,
|
|
152
|
+
});
|
|
153
|
+
materialized.push({
|
|
154
|
+
agentId: agent.id,
|
|
155
|
+
count: result.commands.length,
|
|
156
|
+
paths: result.commands.map((c) => c.path),
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
catch (err) {
|
|
160
|
+
logger.warn(`Failed to materialize slash commands for ${agent.displayName}: ${err instanceof Error ? err.message : String(err)}`);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
const amendmentsLog = join(raptorDir, "memory", "amendments.log");
|
|
164
|
+
const actor = currentActor("human");
|
|
165
|
+
appendAuditEvent(amendmentsLog, {
|
|
166
|
+
event: "constitution.initialized",
|
|
167
|
+
command: opts.auditCommand ?? "raptor init",
|
|
168
|
+
actor: { user: actor.user, via: actor.via },
|
|
169
|
+
outputs: [
|
|
170
|
+
{ path: ".raptor/constitution.md", hash: hashString(constitution) },
|
|
171
|
+
{ path: ".raptor/agents.yml", hash: hashString(agentsYamlContent) },
|
|
172
|
+
],
|
|
173
|
+
meta: {
|
|
174
|
+
core_version: CORE_VERSION,
|
|
175
|
+
preset: presetLabel,
|
|
176
|
+
preset_version: preset?.version,
|
|
177
|
+
preset_articles: allArticleIds,
|
|
178
|
+
project: projectName,
|
|
179
|
+
detected_agents: installedAgents.map((a) => a.id),
|
|
180
|
+
configured_agents: targetAgents.map((a) => a.id),
|
|
181
|
+
},
|
|
182
|
+
});
|
|
183
|
+
const ciResult = opts.withCi
|
|
184
|
+
? installCiWorkflow(cwd)
|
|
185
|
+
: { status: "disabled" };
|
|
186
|
+
const hooksResult = opts.withHooks
|
|
187
|
+
? installGitHooks(cwd)
|
|
188
|
+
: { status: "disabled" };
|
|
189
|
+
return {
|
|
190
|
+
raptorDir,
|
|
191
|
+
projectName,
|
|
192
|
+
presets,
|
|
193
|
+
presetLabel,
|
|
194
|
+
allArticleIds,
|
|
195
|
+
primaryAgentId: primaryAgent?.id,
|
|
196
|
+
targetAgents,
|
|
197
|
+
materialized,
|
|
198
|
+
agentsYamlPath,
|
|
199
|
+
manifestPath,
|
|
200
|
+
ciResult,
|
|
201
|
+
hooksResult,
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
function installCiWorkflow(cwd) {
|
|
205
|
+
const dir = join(cwd, ".github", "workflows");
|
|
206
|
+
const path = join(dir, "raptor-verify.yml");
|
|
207
|
+
if (existsSync(path)) {
|
|
208
|
+
return { status: "skipped-existing", path };
|
|
209
|
+
}
|
|
210
|
+
mkdirSync(dir, { recursive: true });
|
|
211
|
+
const content = renderBundled("ghaWorkflow", { raptorVersion: VERSION });
|
|
212
|
+
writeFileSync(path, content);
|
|
213
|
+
return { status: "installed", path };
|
|
214
|
+
}
|
|
215
|
+
function installGitHooks(cwd) {
|
|
216
|
+
const gitDir = join(cwd, ".git");
|
|
217
|
+
if (!existsSync(gitDir)) {
|
|
218
|
+
return { status: "skipped-no-git" };
|
|
219
|
+
}
|
|
220
|
+
const hooksDir = join(gitDir, "hooks");
|
|
221
|
+
mkdirSync(hooksDir, { recursive: true });
|
|
222
|
+
const hooks = [
|
|
223
|
+
{ name: "pre-commit", key: "preCommitHook" },
|
|
224
|
+
{ name: "pre-push", key: "prePushHook" },
|
|
225
|
+
{ name: "commit-msg", key: "commitMsgHook" },
|
|
226
|
+
];
|
|
227
|
+
const installed = [];
|
|
228
|
+
const skipped = [];
|
|
229
|
+
for (const h of hooks) {
|
|
230
|
+
const path = join(hooksDir, h.name);
|
|
231
|
+
if (existsSync(path)) {
|
|
232
|
+
skipped.push(h.name);
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
235
|
+
const content = renderBundled(h.key, {});
|
|
236
|
+
writeFileSync(path, content);
|
|
237
|
+
chmodSync(path, 0o755);
|
|
238
|
+
installed.push(h.name);
|
|
239
|
+
}
|
|
240
|
+
return { status: "ok", installed, skipped };
|
|
241
|
+
}
|
|
242
|
+
export function describeCi(r) {
|
|
243
|
+
if (r.status === "disabled")
|
|
244
|
+
return "disabled (--no-with-ci)";
|
|
245
|
+
if (r.status === "skipped-existing")
|
|
246
|
+
return `skipped (${r.path} already exists)`;
|
|
247
|
+
return `installed at ${r.path}`;
|
|
248
|
+
}
|
|
249
|
+
export function describeHooks(r) {
|
|
250
|
+
if (r.status === "disabled")
|
|
251
|
+
return "disabled (--no-with-hooks)";
|
|
252
|
+
if (r.status === "skipped-no-git")
|
|
253
|
+
return "skipped (.git/ not found — run 'git init' first)";
|
|
254
|
+
const parts = [];
|
|
255
|
+
if (r.installed.length > 0)
|
|
256
|
+
parts.push(`installed: ${r.installed.join(", ")}`);
|
|
257
|
+
if (r.skipped.length > 0)
|
|
258
|
+
parts.push(`skipped (already exist): ${r.skipped.join(", ")}`);
|
|
259
|
+
return parts.length > 0 ? parts.join("; ") : "nothing to do";
|
|
260
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
export const IDES = [
|
|
2
|
+
{ id: "vscode", label: "VS Code" },
|
|
3
|
+
{ id: "cursor", label: "Cursor", hint: "IDE multi-modelo" },
|
|
4
|
+
{ id: "antigravity", label: "Antigravity", hint: "IDE do Google · Gemini" },
|
|
5
|
+
{ id: "jetbrains", label: "JetBrains" },
|
|
6
|
+
{ id: "terminal", label: "Terminal / CLI" },
|
|
7
|
+
{ id: "other", label: "Outro / manual" },
|
|
8
|
+
];
|
|
9
|
+
const AIS_BY_IDE = {
|
|
10
|
+
vscode: [
|
|
11
|
+
{ id: "claude-code", label: "Claude Code", agentKey: "claude-code", hint: "→ CLAUDE.md" },
|
|
12
|
+
{ id: "copilot", label: "GitHub Copilot", agentKey: "copilot", hint: "→ .github/copilot-instructions.md" },
|
|
13
|
+
{ id: "gemini", label: "Gemini", agentKey: "gemini", hint: "→ GEMINI.md" },
|
|
14
|
+
{ id: "codex", label: "OpenAI Codex", agentKey: "codex", hint: "→ AGENTS.md" },
|
|
15
|
+
],
|
|
16
|
+
cursor: [
|
|
17
|
+
{ id: "cursor", label: "Cursor", agentKey: "cursor", hint: "modelo gerenciado pelo Cursor → .cursor/rules/" },
|
|
18
|
+
],
|
|
19
|
+
antigravity: [
|
|
20
|
+
{ id: "gemini", label: "Gemini", agentKey: "antigravity", hint: "→ AGENTS.md" },
|
|
21
|
+
],
|
|
22
|
+
jetbrains: [
|
|
23
|
+
{ id: "claude-code", label: "Claude Code", agentKey: "claude-code", hint: "→ CLAUDE.md" },
|
|
24
|
+
{ id: "copilot", label: "GitHub Copilot", agentKey: "copilot", hint: "→ .github/copilot-instructions.md" },
|
|
25
|
+
],
|
|
26
|
+
terminal: [
|
|
27
|
+
{ id: "claude-code", label: "Claude Code", agentKey: "claude-code", hint: "→ CLAUDE.md" },
|
|
28
|
+
{ id: "gemini", label: "Gemini CLI", agentKey: "gemini", hint: "→ GEMINI.md" },
|
|
29
|
+
{ id: "codex", label: "OpenAI Codex CLI", agentKey: "codex", hint: "→ AGENTS.md" },
|
|
30
|
+
],
|
|
31
|
+
other: [
|
|
32
|
+
{ id: "claude-code", label: "Claude Code", agentKey: "claude-code", hint: "→ CLAUDE.md" },
|
|
33
|
+
{ id: "cursor", label: "Cursor", agentKey: "cursor", hint: "→ .cursor/rules/" },
|
|
34
|
+
{ id: "copilot", label: "GitHub Copilot", agentKey: "copilot", hint: "→ .github/copilot-instructions.md" },
|
|
35
|
+
{ id: "gemini", label: "Gemini", agentKey: "gemini", hint: "→ GEMINI.md" },
|
|
36
|
+
{ id: "codex", label: "OpenAI Codex", agentKey: "codex", hint: "→ AGENTS.md" },
|
|
37
|
+
],
|
|
38
|
+
};
|
|
39
|
+
export function isKnownIde(ide) {
|
|
40
|
+
return IDES.some((i) => i.id === ide);
|
|
41
|
+
}
|
|
42
|
+
export function aisForIde(ide) {
|
|
43
|
+
return AIS_BY_IDE[ide] ?? [];
|
|
44
|
+
}
|
|
45
|
+
export function resolveAgentKey(ide, ai) {
|
|
46
|
+
return aisForIde(ide).find((a) => a.id === ai)?.agentKey;
|
|
47
|
+
}
|
|
48
|
+
export function referencedAgentKeys() {
|
|
49
|
+
const keys = new Set();
|
|
50
|
+
for (const list of Object.values(AIS_BY_IDE))
|
|
51
|
+
for (const ai of list)
|
|
52
|
+
keys.add(ai.agentKey);
|
|
53
|
+
return [...keys];
|
|
54
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { bold, cyan, dim } from "../ui.js";
|
|
2
|
+
const ASCII = [
|
|
3
|
+
"██████╗ █████╗ ██████╗ ████████╗ ██████╗ ██████╗",
|
|
4
|
+
"██╔══██╗██╔══██╗██╔══██╗╚══██╔══╝██╔═══██╗██╔══██╗",
|
|
5
|
+
"██████╔╝███████║██████╔╝ ██║ ██║ ██║██████╔╝",
|
|
6
|
+
"██╔══██╗██╔══██║██╔═══╝ ██║ ██║ ██║██╔══██╗",
|
|
7
|
+
"██║ ██║██║ ██║██║ ██║ ╚██████╔╝██║ ██║",
|
|
8
|
+
"╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝",
|
|
9
|
+
];
|
|
10
|
+
const ASCII_WIDTH = Math.max(...ASCII.map((l) => l.length));
|
|
11
|
+
export function renderBanner(version) {
|
|
12
|
+
const cols = process.stdout.columns ?? 80;
|
|
13
|
+
if (cols < ASCII_WIDTH + 2) {
|
|
14
|
+
return `${cyan(bold("▲ RAPTOR"))} ${dim(`· AIOS · SDD · v${version}`)}`;
|
|
15
|
+
}
|
|
16
|
+
const art = ASCII.map((line) => cyan(line)).join("\n");
|
|
17
|
+
const tag = `${bold("AIOS")} ${dim(`· Spec-Driven Development · v${version}`)}`;
|
|
18
|
+
return `${art}\n${tag}`;
|
|
19
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
export function detectProjectStack(root) {
|
|
4
|
+
const pkgPath = join(root, "package.json");
|
|
5
|
+
const hasPackageJson = existsSync(pkgPath);
|
|
6
|
+
const hasGit = existsSync(join(root, ".git"));
|
|
7
|
+
let deps = {};
|
|
8
|
+
if (hasPackageJson) {
|
|
9
|
+
try {
|
|
10
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
|
|
11
|
+
deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
const isReactNative = Boolean(deps["react-native"]);
|
|
17
|
+
const isWeb = Boolean(deps["react"]) && !isReactNative;
|
|
18
|
+
const language = existsSync(join(root, "tsconfig.json")) || Boolean(deps["typescript"])
|
|
19
|
+
? "typescript"
|
|
20
|
+
: "javascript";
|
|
21
|
+
const suggestedPreset = isReactNative ? "mobile-opinionated" : undefined;
|
|
22
|
+
return {
|
|
23
|
+
hasPackageJson,
|
|
24
|
+
hasGit,
|
|
25
|
+
isReactNative,
|
|
26
|
+
isWeb,
|
|
27
|
+
language,
|
|
28
|
+
suggestedPreset,
|
|
29
|
+
isBrownfield: hasPackageJson,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export const ONBOARD = [
|
|
2
|
+
"O RAPTOR transforma uma ideia em linguagem natural numa especificação",
|
|
3
|
+
"rica e sem pontas soltas — e conduz, fase a fase, até o código",
|
|
4
|
+
"verificado, sempre com um humano no comando das aprovações.",
|
|
5
|
+
"",
|
|
6
|
+
"Ele não escreve a spec nem o código sozinho: orquestra o ciclo, aplica",
|
|
7
|
+
"gates que bloqueiam transições incompletas, mantém uma trilha de",
|
|
8
|
+
"auditoria e materializa slash commands para o seu agente de IA.",
|
|
9
|
+
"",
|
|
10
|
+
"Diferencial — a ponta de verificação: o RAPTOR mede a realidade do app",
|
|
11
|
+
"(acessibilidade, performance, permissões de loja, versões de SO) e",
|
|
12
|
+
"compara com o que a spec prometeu.",
|
|
13
|
+
"",
|
|
14
|
+
"Ciclo: new → specify → clarify → plan → tasks → implement → verify",
|
|
15
|
+
].join("\n");
|
|
16
|
+
export function renderOnboard() {
|
|
17
|
+
return ONBOARD;
|
|
18
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { performInit, } from "../init-core.js";
|
|
2
|
+
import { readConfig, writeConfig } from "../project.js";
|
|
3
|
+
import { resolveAgentKey } from "./agent-resolver.js";
|
|
4
|
+
export function buildSetupPlan(answers, ctx) {
|
|
5
|
+
const primaryAgentKey = resolveAgentKey(answers.ide, answers.ai);
|
|
6
|
+
if (!primaryAgentKey) {
|
|
7
|
+
throw new Error(`Nenhuma integração de agente mapeia IDE "${answers.ide}" + IA "${answers.ai}".`);
|
|
8
|
+
}
|
|
9
|
+
return {
|
|
10
|
+
cwd: ctx.cwd,
|
|
11
|
+
projectName: answers.projectName,
|
|
12
|
+
preset: answers.preset,
|
|
13
|
+
primaryAgentKey,
|
|
14
|
+
startingPoint: answers.startingPoint,
|
|
15
|
+
noGit: false,
|
|
16
|
+
jiraLater: answers.jiraLater,
|
|
17
|
+
figmaLater: answers.figmaLater,
|
|
18
|
+
force: ctx.force,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
function worthPreserving(block) {
|
|
22
|
+
return (!!block &&
|
|
23
|
+
typeof block === "object" &&
|
|
24
|
+
!Array.isArray(block) &&
|
|
25
|
+
(block.enabled === true || Object.keys(block).some((k) => k !== "enabled")));
|
|
26
|
+
}
|
|
27
|
+
export function applySetupPlan(plan, logger) {
|
|
28
|
+
const prior = readConfig(plan.cwd);
|
|
29
|
+
const priorJira = worthPreserving(prior.jira) ? prior.jira : undefined;
|
|
30
|
+
const priorDesign = worthPreserving(prior.design) ? prior.design : undefined;
|
|
31
|
+
const priorGit = prior.git;
|
|
32
|
+
const priorGates = prior.gates;
|
|
33
|
+
const init = performInit({
|
|
34
|
+
cwd: plan.cwd,
|
|
35
|
+
projectName: plan.projectName ?? prior.project?.name,
|
|
36
|
+
preset: plan.preset,
|
|
37
|
+
ai: plan.primaryAgentKey,
|
|
38
|
+
script: "sh",
|
|
39
|
+
branchNumbering: "sequential",
|
|
40
|
+
noGit: plan.noGit,
|
|
41
|
+
withCi: true,
|
|
42
|
+
withHooks: true,
|
|
43
|
+
force: plan.force,
|
|
44
|
+
auditCommand: "raptor setup",
|
|
45
|
+
}, logger);
|
|
46
|
+
const cfg = readConfig(plan.cwd);
|
|
47
|
+
let changed = false;
|
|
48
|
+
if (priorGit) {
|
|
49
|
+
cfg.git = { ...cfg.git, ...priorGit };
|
|
50
|
+
changed = true;
|
|
51
|
+
}
|
|
52
|
+
if (priorGates) {
|
|
53
|
+
cfg.gates = { ...cfg.gates, ...priorGates };
|
|
54
|
+
changed = true;
|
|
55
|
+
}
|
|
56
|
+
if (priorJira) {
|
|
57
|
+
cfg.jira = priorJira;
|
|
58
|
+
changed = true;
|
|
59
|
+
}
|
|
60
|
+
if (priorDesign) {
|
|
61
|
+
cfg.design = priorDesign;
|
|
62
|
+
changed = true;
|
|
63
|
+
}
|
|
64
|
+
let jiraConfigured = false;
|
|
65
|
+
let figmaConfigured = false;
|
|
66
|
+
if (plan.jiraLater && !cfg.jira?.enabled) {
|
|
67
|
+
cfg.jira = {
|
|
68
|
+
...cfg.jira,
|
|
69
|
+
enabled: false,
|
|
70
|
+
provider: cfg.jira?.provider ?? "atlassian-oauth",
|
|
71
|
+
};
|
|
72
|
+
jiraConfigured = true;
|
|
73
|
+
changed = true;
|
|
74
|
+
}
|
|
75
|
+
if (plan.figmaLater && !cfg.design?.enabled) {
|
|
76
|
+
cfg.design = {
|
|
77
|
+
...cfg.design,
|
|
78
|
+
enabled: false,
|
|
79
|
+
provider: cfg.design?.provider ?? "figma-rest",
|
|
80
|
+
};
|
|
81
|
+
figmaConfigured = true;
|
|
82
|
+
changed = true;
|
|
83
|
+
}
|
|
84
|
+
if (changed)
|
|
85
|
+
writeConfig(plan.cwd, cfg);
|
|
86
|
+
return {
|
|
87
|
+
init,
|
|
88
|
+
jiraConfigured,
|
|
89
|
+
figmaConfigured,
|
|
90
|
+
jiraPreserved: Boolean(priorJira),
|
|
91
|
+
figmaPreserved: Boolean(priorDesign),
|
|
92
|
+
};
|
|
93
|
+
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import * as p from "@clack/prompts";
|
|
2
|
+
import { basename } from "node:path";
|
|
3
|
+
import { knownPresetIds, VERSION } from "../../_core/dist/index.js";
|
|
4
|
+
import { applySetupPlan, buildSetupPlan, } from "./plan.js";
|
|
5
|
+
import { aisForIde, IDES } from "./agent-resolver.js";
|
|
6
|
+
import { detectProjectStack } from "./detect-stack.js";
|
|
7
|
+
import { renderBanner } from "./banner.js";
|
|
8
|
+
import { renderOnboard } from "./onboard.js";
|
|
9
|
+
import { describeCi, describeHooks, } from "../init-core.js";
|
|
10
|
+
export async function runSetupWizard(ctx) {
|
|
11
|
+
console.log(`\n${renderBanner(VERSION)}\n`);
|
|
12
|
+
p.intro("Configuração guiada do projeto");
|
|
13
|
+
if (!ctx.skipIntro) {
|
|
14
|
+
const see = await p.confirm({
|
|
15
|
+
message: "Ver uma introdução rápida (30s) ao RAPTOR?",
|
|
16
|
+
initialValue: true,
|
|
17
|
+
});
|
|
18
|
+
if (p.isCancel(see))
|
|
19
|
+
return cancelled();
|
|
20
|
+
if (see)
|
|
21
|
+
p.note(renderOnboard(), "O que é o RAPTOR AIOS");
|
|
22
|
+
}
|
|
23
|
+
const stack = detectProjectStack(ctx.cwd);
|
|
24
|
+
const startingPoint = await p.select({
|
|
25
|
+
message: "Este projeto é...",
|
|
26
|
+
options: [
|
|
27
|
+
{
|
|
28
|
+
value: "existing",
|
|
29
|
+
label: "Já existente",
|
|
30
|
+
hint: stack.isBrownfield ? "código detectado aqui" : undefined,
|
|
31
|
+
},
|
|
32
|
+
{ value: "scratch", label: "Do zero" },
|
|
33
|
+
],
|
|
34
|
+
initialValue: stack.isBrownfield ? "existing" : "scratch",
|
|
35
|
+
});
|
|
36
|
+
if (p.isCancel(startingPoint))
|
|
37
|
+
return cancelled();
|
|
38
|
+
const ide = await p.select({
|
|
39
|
+
message: "Qual seu ambiente?",
|
|
40
|
+
options: IDES.map((i) => ({ value: i.id, label: i.label, hint: i.hint })),
|
|
41
|
+
initialValue: ctx.defaults?.ide,
|
|
42
|
+
});
|
|
43
|
+
if (p.isCancel(ide))
|
|
44
|
+
return cancelled();
|
|
45
|
+
const ais = aisForIde(ide);
|
|
46
|
+
let ai;
|
|
47
|
+
if (ais.length === 1) {
|
|
48
|
+
ai = ais[0].id;
|
|
49
|
+
p.log.info(`Assistente: ${ais[0].label}`);
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
const picked = await p.select({
|
|
53
|
+
message: "Qual assistente de IA?",
|
|
54
|
+
options: ais.map((a) => ({ value: a.id, label: a.label, hint: a.hint })),
|
|
55
|
+
initialValue: ctx.defaults?.ai,
|
|
56
|
+
});
|
|
57
|
+
if (p.isCancel(picked))
|
|
58
|
+
return cancelled();
|
|
59
|
+
ai = picked;
|
|
60
|
+
}
|
|
61
|
+
const presetIds = knownPresetIds();
|
|
62
|
+
const suggested = stack.suggestedPreset ?? "mobile-opinionated";
|
|
63
|
+
const preset = await p.select({
|
|
64
|
+
message: "Preset da constituição",
|
|
65
|
+
options: presetIds.map((id) => ({
|
|
66
|
+
value: id,
|
|
67
|
+
label: id,
|
|
68
|
+
hint: id === suggested ? "sugerido para sua stack" : undefined,
|
|
69
|
+
})),
|
|
70
|
+
initialValue: ctx.defaults?.preset ?? suggested,
|
|
71
|
+
});
|
|
72
|
+
if (p.isCancel(preset))
|
|
73
|
+
return cancelled();
|
|
74
|
+
const jiraLater = await p.confirm({
|
|
75
|
+
message: "Deixar o Jira pronto para conectar depois?",
|
|
76
|
+
initialValue: ctx.defaults?.jiraLater ?? false,
|
|
77
|
+
});
|
|
78
|
+
if (p.isCancel(jiraLater))
|
|
79
|
+
return cancelled();
|
|
80
|
+
const figmaLater = await p.confirm({
|
|
81
|
+
message: "Deixar o Figma pronto para conectar depois?",
|
|
82
|
+
initialValue: ctx.defaults?.figmaLater ?? false,
|
|
83
|
+
});
|
|
84
|
+
if (p.isCancel(figmaLater))
|
|
85
|
+
return cancelled();
|
|
86
|
+
const answers = {
|
|
87
|
+
startingPoint,
|
|
88
|
+
projectName: ctx.defaults?.projectName,
|
|
89
|
+
ide,
|
|
90
|
+
ai,
|
|
91
|
+
preset,
|
|
92
|
+
jiraLater,
|
|
93
|
+
figmaLater,
|
|
94
|
+
};
|
|
95
|
+
let plan;
|
|
96
|
+
try {
|
|
97
|
+
plan = buildSetupPlan(answers, { cwd: ctx.cwd, force: ctx.force });
|
|
98
|
+
}
|
|
99
|
+
catch (err) {
|
|
100
|
+
p.cancel(err instanceof Error ? err.message : String(err));
|
|
101
|
+
return "failed";
|
|
102
|
+
}
|
|
103
|
+
p.note([
|
|
104
|
+
`Projeto: ${plan.projectName ?? basename(ctx.cwd)}`,
|
|
105
|
+
`Preset: ${plan.preset}`,
|
|
106
|
+
`Agente: ${plan.primaryAgentKey}`,
|
|
107
|
+
`Jira: ${plan.jiraLater ? "configurar depois" : "pular"}`,
|
|
108
|
+
`Figma: ${plan.figmaLater ? "configurar depois" : "pular"}`,
|
|
109
|
+
].join("\n"), "Revisão");
|
|
110
|
+
const go = await p.confirm({ message: "Aplicar agora?" });
|
|
111
|
+
if (p.isCancel(go))
|
|
112
|
+
return cancelled();
|
|
113
|
+
if (!go) {
|
|
114
|
+
p.outro("Nada foi alterado.");
|
|
115
|
+
return "declined";
|
|
116
|
+
}
|
|
117
|
+
const s = p.spinner();
|
|
118
|
+
s.start("Inicializando o projeto Raptor…");
|
|
119
|
+
const warnings = [];
|
|
120
|
+
const logger = {
|
|
121
|
+
log: () => { },
|
|
122
|
+
warn: (m) => warnings.push(typeof m === "string" ? m : String(m)),
|
|
123
|
+
};
|
|
124
|
+
let result;
|
|
125
|
+
try {
|
|
126
|
+
result = applySetupPlan(plan, logger);
|
|
127
|
+
}
|
|
128
|
+
catch (err) {
|
|
129
|
+
s.stop("Falhou.");
|
|
130
|
+
p.cancel(err instanceof Error ? err.message : String(err));
|
|
131
|
+
return "failed";
|
|
132
|
+
}
|
|
133
|
+
s.stop("Projeto inicializado.");
|
|
134
|
+
for (const w of warnings)
|
|
135
|
+
p.log.warn(w);
|
|
136
|
+
const matLine = result.init.materialized
|
|
137
|
+
.map((m) => `${m.agentId} (${m.count})`)
|
|
138
|
+
.join(", ") || "(nenhum)";
|
|
139
|
+
p.note([
|
|
140
|
+
`Comandos: ${matLine}`,
|
|
141
|
+
`CI: ${describeCi(result.init.ciResult)}`,
|
|
142
|
+
`Hooks: ${describeHooks(result.init.hooksResult)}`,
|
|
143
|
+
result.jiraPreserved
|
|
144
|
+
? "Jira: config existente preservada"
|
|
145
|
+
: result.jiraConfigured
|
|
146
|
+
? "Jira: pronto (raptor jira connect)"
|
|
147
|
+
: "",
|
|
148
|
+
result.figmaPreserved
|
|
149
|
+
? "Figma: config existente preservada"
|
|
150
|
+
: result.figmaConfigured
|
|
151
|
+
? "Figma: pronto (raptor design connect)"
|
|
152
|
+
: "",
|
|
153
|
+
]
|
|
154
|
+
.filter(Boolean)
|
|
155
|
+
.join("\n"), "Pronto");
|
|
156
|
+
p.outro(plan.startingPoint === "existing"
|
|
157
|
+
? "Próximo: 'raptor new <slug>' para especificar uma feature do código existente."
|
|
158
|
+
: "Próximo: 'raptor new <slug>' para criar sua primeira feature.");
|
|
159
|
+
return "completed";
|
|
160
|
+
}
|
|
161
|
+
function cancelled() {
|
|
162
|
+
p.cancel("Setup cancelado.");
|
|
163
|
+
return "cancelled";
|
|
164
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "raptor-aios",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
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": {
|
|
@@ -53,6 +53,7 @@
|
|
|
53
53
|
"access": "public"
|
|
54
54
|
},
|
|
55
55
|
"dependencies": {
|
|
56
|
+
"@clack/prompts": "^1.5.1",
|
|
56
57
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
57
58
|
"@oclif/core": "^4",
|
|
58
59
|
"ajv": "^8.18.0",
|
package/scripts/prepare-npm.mjs
CHANGED
|
@@ -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.
|
|
32
|
+
const VERSION = "0.12.0";
|
|
33
33
|
|
|
34
34
|
function log(msg) {
|
|
35
35
|
process.stdout.write(` ${msg}\n`);
|
|
@@ -177,6 +177,7 @@ const pkg = {
|
|
|
177
177
|
bugs: { url: "https://github.com/lucaspedronet/raptor-aios/issues" },
|
|
178
178
|
publishConfig: { access: "public" },
|
|
179
179
|
dependencies: {
|
|
180
|
+
"@clack/prompts": "^1.5.1",
|
|
180
181
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
181
182
|
"@oclif/core": "^4",
|
|
182
183
|
ajv: "^8.18.0",
|