@gaberrb/polypus 0.3.0 → 0.4.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/dist/index.js CHANGED
@@ -2,8 +2,7 @@
2
2
 
3
3
  // src/cli/index.ts
4
4
  import { Command } from "commander";
5
- import { createRequire } from "module";
6
- import pc12 from "picocolors";
5
+ import pc13 from "picocolors";
7
6
 
8
7
  // src/cli/commands/add-agent.ts
9
8
  import pc from "picocolors";
@@ -74,11 +73,13 @@ var en = {
74
73
  "cli.description": "Agentic coding harness that makes any AI API generate and apply code \u2014 OpenRouter, Ollama, and any OpenAI-compatible endpoint.",
75
74
  "cli.opt.lang": "interface language: pt-BR | en",
76
75
  "cli.cmd.setup": "Interactive setup wizard (configure agents, keys, permissions)",
76
+ "cli.cmd.init": "Scaffold a .poly/ workspace (agents.md, skills, SDD spec template, README)",
77
+ "cli.opt.force": "overwrite files that already exist",
77
78
  "cli.cmd.addAgent": "Register a new agent (API key + model)",
78
79
  "cli.cmd.removeAgent": "Remove a configured agent",
79
80
  "cli.cmd.listAgents": "List configured agents",
80
81
  "cli.cmd.run": "Run a coding task with an agent",
81
- "cli.cmd.swarm": "Split a task across multiple agents working in parallel git worktrees",
82
+ "cli.cmd.swarm": "Split a task across multiple agents working in parallel git worktrees (requires 3+ configured agents)",
82
83
  "cli.cmd.models": "Browse OpenRouter models (price, context, tool support)",
83
84
  "cli.cmd.prd": "Generate a PRD from a GitHub issue (uses a free OpenRouter model)",
84
85
  "cli.arg.prdIssue": "issue number to turn into a PRD",
@@ -164,6 +165,7 @@ var en = {
164
165
  ].join("\n"),
165
166
  // swarm
166
167
  "swarm.noAgents": "No agents configured. Run `polypus setup` or `polypus add-agent` first.",
168
+ "swarm.needsAgents": "Swarm mode needs at least {min} configured agents (you have {have}). Add more with `polypus add-agent`, or use `polypus run` for a single agent.",
167
169
  "swarm.status": "swarm agents=[{agents}] workspace={workspace}",
168
170
  "swarm.bypassNote": "Workers run in bypass mode inside isolated git worktrees; branches are merged at the end.",
169
171
  "swarm.decomposed": "Decomposed into {n} subtask(s):",
@@ -189,6 +191,12 @@ var en = {
189
191
  "swarm.conflictsHeader": "\u26A0 {n} branch(es) had merge conflicts (kept for inspection):",
190
192
  "swarm.statusDone": "done",
191
193
  "swarm.statusIncomplete": "incomplete",
194
+ // init
195
+ "init.created": "\u2713 .poly scaffolded:",
196
+ "init.skipped": "Kept (already existed):",
197
+ "init.allExist": "Nothing to do \u2014 .poly already has these files:",
198
+ "init.forceHint": "Run `polypus init --force` to overwrite them.",
199
+ "init.tip": "Tip: edit .poly/agents.md \u2014 Polypus loads it into the agent's context automatically.",
192
200
  // wizard
193
201
  "wizard.title": " polypus setup ",
194
202
  "wizard.intro": [
@@ -274,7 +282,8 @@ var en = {
274
282
  "welcome.hints": "Type your task and press Enter \xB7 ESC cancels \xB7 /help \xB7 /exit",
275
283
  "welcome.firstRun": "No agents configured yet \u2014 let's set you up.",
276
284
  // agent system prompt
277
- "prompt.language": "Communicate with the user in {language}."
285
+ "prompt.language": "Communicate with the user in {language}.",
286
+ "prompt.projectInstructions": "Project-specific operating instructions follow, loaded from `.poly/agents.md`. Treat them as authoritative for how to work in THIS repo. Paths they reference (e.g. skills/*.md, ../context.md, ../rules.md) are relative to the `.poly/` directory \u2014 read those files when relevant before acting:"
278
287
  };
279
288
  var ptBR = {
280
289
  "common.default": "padr\xE3o",
@@ -283,11 +292,13 @@ var ptBR = {
283
292
  "cli.description": "Harness ag\xEAntico que faz qualquer API de IA gerar e aplicar c\xF3digo \u2014 OpenRouter, Ollama e qualquer endpoint compat\xEDvel com OpenAI.",
284
293
  "cli.opt.lang": "idioma da interface: pt-BR | en",
285
294
  "cli.cmd.setup": "Assistente de configura\xE7\xE3o interativo (agentes, chaves, permiss\xF5es)",
295
+ "cli.cmd.init": "Cria um workspace .poly/ (agents.md, skills, template de spec SDD, README)",
296
+ "cli.opt.force": "sobrescreve arquivos que j\xE1 existem",
286
297
  "cli.cmd.addAgent": "Cadastra um novo agente (chave de API + modelo)",
287
298
  "cli.cmd.removeAgent": "Remove um agente configurado",
288
299
  "cli.cmd.listAgents": "Lista os agentes configurados",
289
300
  "cli.cmd.run": "Executa uma tarefa de c\xF3digo com um agente",
290
- "cli.cmd.swarm": "Divide uma tarefa entre v\xE1rios agentes trabalhando em paralelo em git worktrees",
301
+ "cli.cmd.swarm": "Divide uma tarefa entre v\xE1rios agentes em git worktrees paralelas (requer 3+ agentes configurados)",
291
302
  "cli.cmd.models": "Explora os modelos do OpenRouter (pre\xE7o, contexto, suporte a tools)",
292
303
  "cli.cmd.prd": "Gera um PRD a partir de uma issue do GitHub (usa um modelo gratuito do OpenRouter)",
293
304
  "cli.arg.prdIssue": "n\xFAmero da issue para transformar em PRD",
@@ -369,6 +380,7 @@ var ptBR = {
369
380
  "Qualquer outra coisa \xE9 enviada ao agente como tarefa."
370
381
  ].join("\n"),
371
382
  "swarm.noAgents": "Nenhum agente configurado. Rode `polypus setup` ou `polypus add-agent` primeiro.",
383
+ "swarm.needsAgents": "O modo swarm precisa de pelo menos {min} agentes configurados (voc\xEA tem {have}). Adicione mais com `polypus add-agent`, ou use `polypus run` para um agente s\xF3.",
372
384
  "swarm.status": "swarm agentes=[{agents}] workspace={workspace}",
373
385
  "swarm.bypassNote": "Os workers rodam em modo bypass dentro de git worktrees isoladas; os branches s\xE3o mesclados no final.",
374
386
  "swarm.decomposed": "Dividido em {n} subtarefa(s):",
@@ -394,6 +406,12 @@ var ptBR = {
394
406
  "swarm.conflictsHeader": "\u26A0 {n} branch(es) tiveram conflitos de merge (mantidos para inspe\xE7\xE3o):",
395
407
  "swarm.statusDone": "ok",
396
408
  "swarm.statusIncomplete": "incompleta",
409
+ // init
410
+ "init.created": "\u2713 .poly criado:",
411
+ "init.skipped": "Mantidos (j\xE1 existiam):",
412
+ "init.allExist": "Nada a fazer \u2014 o .poly j\xE1 tem estes arquivos:",
413
+ "init.forceHint": "Rode `polypus init --force` para sobrescrev\xEA-los.",
414
+ "init.tip": "Dica: edite o .poly/agents.md \u2014 o Polypus carrega ele no contexto do agente automaticamente.",
397
415
  "wizard.title": " configura\xE7\xE3o do polypus ",
398
416
  "wizard.intro": [
399
417
  "O Polypus comanda qualquer API de IA para ler e escrever c\xF3digo neste tipo de projeto.",
@@ -455,6 +473,7 @@ var ptBR = {
455
473
  "wizard.envInvalid": "Use letras, d\xEDgitos e sublinhados",
456
474
  "wizard.keyPrompt": "Chave de API (armazenada em texto puro no arquivo de config)",
457
475
  "prompt.language": "Comunique-se com o usu\xE1rio em {language}.",
476
+ "prompt.projectInstructions": "Seguem instru\xE7\xF5es operacionais espec\xEDficas do projeto, carregadas de `.poly/agents.md`. Trate-as como autoritativas para trabalhar NESTE reposit\xF3rio. Os caminhos que elas citam (ex.: skills/*.md, ../context.md, ../rules.md) s\xE3o relativos \xE0 pasta `.poly/` \u2014 leia esses arquivos quando relevante antes de agir:",
458
477
  "models.fetching": "Buscando modelos do OpenRouter\u2026",
459
478
  "models.fetchError": "N\xE3o foi poss\xEDvel buscar modelos: {msg}",
460
479
  "models.none": "Nenhum modelo corresponde aos filtros.",
@@ -1025,6 +1044,10 @@ function basePreamble(ctx) {
1025
1044
  "- Do not ask for permission and do not say you cannot edit files \u2014 you can. Just emit the tool calls.",
1026
1045
  "- Make the changes directly. When the task is fully done, call the `finish` tool with a short summary.",
1027
1046
  t("prompt.language", { language: LOCALE_NAMES[getLocale()] }),
1047
+ ctx.projectInstructions ? `
1048
+ ${t("prompt.projectInstructions")}
1049
+
1050
+ ${ctx.projectInstructions}` : "",
1028
1051
  ctx.briefing ? `
1029
1052
  Your assigned task:
1030
1053
  ${ctx.briefing}` : ""
@@ -1668,6 +1691,23 @@ function formatSchema(spec) {
1668
1691
  return lines.join("\n") || " (no parameters)";
1669
1692
  }
1670
1693
 
1694
+ // src/core/agent/project-context.ts
1695
+ import { readFile as readFile5 } from "fs/promises";
1696
+ import { join as join2 } from "path";
1697
+ var INSTRUCTION_FILES = [join2(".poly", "agents.md"), "AGENTS.md"];
1698
+ var MAX_CHARS2 = 8e3;
1699
+ async function loadProjectInstructions(workspace) {
1700
+ for (const rel of INSTRUCTION_FILES) {
1701
+ try {
1702
+ const raw = (await readFile5(join2(workspace, rel), "utf8")).trim();
1703
+ if (!raw) continue;
1704
+ return raw.length > MAX_CHARS2 ? raw.slice(0, MAX_CHARS2) + "\n\u2026(truncated)" : raw;
1705
+ } catch {
1706
+ }
1707
+ }
1708
+ return void 0;
1709
+ }
1710
+
1671
1711
  // src/core/agent/loop.ts
1672
1712
  function looksLikeStall(text2) {
1673
1713
  const lc = text2.toLowerCase();
@@ -1710,10 +1750,12 @@ async function runAgent(opts) {
1710
1750
  const maxReprompts = opts.maxReprompts ?? 3;
1711
1751
  const driver = makeDriver(agent.toolMode, toolSpecs());
1712
1752
  const ctx = { workspace: opts.workspace, permissions };
1713
- const messages = opts.history && opts.history.length > 0 ? [...opts.history, { role: "user", content: opts.task }] : [
1714
- { role: "system", content: driver.systemPrompt(opts.promptContext) },
1753
+ const seeding = !(opts.history && opts.history.length > 0);
1754
+ const promptContext = seeding && opts.promptContext.projectInstructions === void 0 ? { ...opts.promptContext, projectInstructions: await loadProjectInstructions(opts.workspace) } : opts.promptContext;
1755
+ const messages = seeding ? [
1756
+ { role: "system", content: driver.systemPrompt(promptContext) },
1715
1757
  { role: "user", content: opts.task }
1716
- ];
1758
+ ] : [...opts.history, { role: "user", content: opts.task }];
1717
1759
  let consecutiveNoTool = 0;
1718
1760
  let lastFailSig = "";
1719
1761
  let failStreak = 0;
@@ -2182,6 +2224,23 @@ async function promptApiKey(provider) {
2182
2224
 
2183
2225
  // src/ui/banner.ts
2184
2226
  import pc5 from "picocolors";
2227
+
2228
+ // src/core/version.ts
2229
+ import { createRequire } from "module";
2230
+ function resolveVersion() {
2231
+ const require2 = createRequire(import.meta.url);
2232
+ for (const rel of ["../package.json", "../../package.json"]) {
2233
+ try {
2234
+ const version = require2(rel).version;
2235
+ if (typeof version === "string" && version.length > 0) return version;
2236
+ } catch {
2237
+ }
2238
+ }
2239
+ return "0.0.0";
2240
+ }
2241
+ var VERSION = resolveVersion();
2242
+
2243
+ // src/ui/banner.ts
2185
2244
  var RESET = "\x1B[0m";
2186
2245
  var useColor = (Boolean(process.stdout.isTTY) || Boolean(process.env.FORCE_COLOR)) && !process.env.NO_COLOR;
2187
2246
  var animated = Boolean(process.stdout.isTTY) && !process.env.NO_COLOR && !process.env.POLYPUS_NO_ANIM;
@@ -2255,7 +2314,7 @@ function colorChar(rowIdx, ch) {
2255
2314
  function renderArtRow(rowIdx, line) {
2256
2315
  return [...center(line)].map((ch) => colorChar(rowIdx, ch)).join("");
2257
2316
  }
2258
- var tagline = () => c1(t("welcome.tagline")) + pc5.dim(" v0.1.0");
2317
+ var tagline = () => c1(t("welcome.tagline")) + pc5.dim(` v${VERSION}`);
2259
2318
  function authorLine() {
2260
2319
  return pc5.dim("by ") + c2(AUTHOR.name) + pc5.dim(" \xB7 ") + c1(AUTHOR.github) + pc5.dim(" \xB7 ") + c1(AUTHOR.linkedin);
2261
2320
  }
@@ -2678,13 +2737,302 @@ async function setup() {
2678
2737
  await runWizard();
2679
2738
  }
2680
2739
 
2681
- // src/cli/commands/swarm.ts
2740
+ // src/cli/commands/init.ts
2682
2741
  import pc8 from "picocolors";
2683
2742
 
2743
+ // src/core/scaffold/init.ts
2744
+ import { mkdir as mkdir3, writeFile as writeFile4, access } from "fs/promises";
2745
+ import { dirname as dirname3, join as join3 } from "path";
2746
+
2747
+ // src/core/scaffold/templates.ts
2748
+ function polyTemplates(locale) {
2749
+ return locale === "pt-BR" ? PT : EN;
2750
+ }
2751
+ var EN = {
2752
+ "agents.md": `# agents.md \u2014 how an AI agent operates this repo
2753
+
2754
+ > Local workspace under \`.poly/\`. Conditions any AI agent (Polypus, Claude, \u2026)
2755
+ > to work the way this project expects. **Polypus loads this file automatically**
2756
+ > into the agent's system prompt on every run, so keep it accurate and lean.
2757
+
2758
+ ## Role
2759
+
2760
+ You implement changes end to end \u2014 from understanding the task to a reviewable
2761
+ result \u2014 respecting the rules below.
2762
+
2763
+ ## Golden rules
2764
+
2765
+ 1. Green before a PR: the project builds, type-checks and tests pass.
2766
+ 2. Keep docs/changelog in sync with behavior changes.
2767
+ 3. Confirm before irreversible actions (publishing, deleting, force-pushing).
2768
+ 4. Small, targeted changes over broad rewrites.
2769
+
2770
+ ## Skills index
2771
+
2772
+ | Skill | When to use |
2773
+ |-------|-------------|
2774
+ | [skills/coding.md](skills/coding.md) | Technical standards for any code change |
2775
+ | [skills/spec-driven.md](skills/spec-driven.md) | Write a spec before non-trivial work |
2776
+
2777
+ ## Environment
2778
+
2779
+ - Describe the OS, shell, package manager and any tooling the agent needs.
2780
+ - Note where credentials/CLIs live and how commands are run.
2781
+ `,
2782
+ "README.md": `# .poly \u2014 your project's AI operating manual
2783
+
2784
+ \`.poly/\` is a small, local workspace that teaches AI agents how to work in THIS
2785
+ repository. Gitignore it to keep it personal, or commit it to standardize the
2786
+ workflow across your team.
2787
+
2788
+ ## What's inside
2789
+
2790
+ - **\`agents.md\`** \u2014 the entry point: role, golden rules and an index of skills.
2791
+ Polypus loads it automatically into the agent's system prompt on every run.
2792
+ - **\`skills/\`** \u2014 focused how-to guides the agent reads when relevant.
2793
+ - **\`templates/spec.md\`** \u2014 a lean Spec-Driven Development (SDD) template.
2794
+
2795
+ ## How it works
2796
+
2797
+ 1. You describe a task to the agent.
2798
+ 2. The agent reads \`agents.md\`, follows the golden rules and opens the skills it needs.
2799
+ 3. For non-trivial work, it writes a spec first from \`templates/spec.md\`.
2800
+
2801
+ ## Extend it
2802
+
2803
+ - Edit \`agents.md\` to encode your conventions.
2804
+ - Add one skill file per recurring workflow (releases, reviews, migrations\u2026).
2805
+ - Reference new skills from \`agents.md\` so the agent can discover them.
2806
+
2807
+ Regenerate any missing files with \`polypus init\` (existing files are preserved;
2808
+ use \`--force\` to overwrite).
2809
+ `,
2810
+ "skills/coding.md": `# skill: coding
2811
+
2812
+ Technical standards for changes in this repo.
2813
+
2814
+ ## Principles
2815
+
2816
+ - Match the style, naming and structure of the surrounding code.
2817
+ - Prefer small, targeted edits over broad rewrites.
2818
+ - Add or update tests with every behavior change.
2819
+
2820
+ ## Checklist before opening a PR
2821
+
2822
+ - [ ] Builds and type-checks
2823
+ - [ ] Tests pass
2824
+ - [ ] Docs / changelog updated when behavior changed
2825
+ `,
2826
+ "skills/spec-driven.md": `# skill: spec-driven development
2827
+
2828
+ For anything non-trivial, write a short spec BEFORE coding.
2829
+
2830
+ ## Flow
2831
+
2832
+ 1. Copy \`templates/spec.md\` into your issue (or \`specs/<slug>.md\`).
2833
+ 2. Fill **Why / What / Acceptance criteria / Out of scope**.
2834
+ 3. Get a thumbs-up, then implement to the acceptance criteria.
2835
+ 4. Keep the spec updated if scope changes.
2836
+
2837
+ Lean by design: if a section is empty, delete it.
2838
+ `,
2839
+ "templates/spec.md": `# Spec: <title>
2840
+
2841
+ > Status: draft \xB7 Owner: <name> \xB7 Updated: <yyyy-mm-dd>
2842
+
2843
+ ## Why
2844
+
2845
+ What problem are we solving, and for whom? Why now?
2846
+
2847
+ ## What
2848
+
2849
+ The change in plain terms \u2014 the behavior a user will actually see.
2850
+
2851
+ ## Acceptance criteria
2852
+
2853
+ - [ ] Observable outcome 1
2854
+ - [ ] Observable outcome 2
2855
+
2856
+ ## Out of scope
2857
+
2858
+ - Things we are explicitly NOT doing here.
2859
+
2860
+ ## Notes / open questions
2861
+
2862
+ - \u2026
2863
+ `
2864
+ };
2865
+ var PT = {
2866
+ "agents.md": `# agents.md \u2014 como um agente de IA opera este reposit\xF3rio
2867
+
2868
+ > Workspace local em \`.poly/\`. Condiciona qualquer agente de IA (Polypus, Claude\u2026)
2869
+ > a trabalhar do jeito que este projeto espera. **O Polypus carrega este arquivo
2870
+ > automaticamente** no system prompt do agente a cada execu\xE7\xE3o \u2014 mantenha-o
2871
+ > preciso e enxuto.
2872
+
2873
+ ## Papel
2874
+
2875
+ Voc\xEA implementa mudan\xE7as de ponta a ponta \u2014 do entendimento da tarefa a um
2876
+ resultado revis\xE1vel \u2014 respeitando as regras abaixo.
2877
+
2878
+ ## Regras de ouro
2879
+
2880
+ 1. Verde antes do PR: o projeto builda, passa no type-check e nos testes.
2881
+ 2. Mantenha docs/changelog em sincronia com mudan\xE7as de comportamento.
2882
+ 3. Confirme antes de a\xE7\xF5es irrevers\xEDveis (publicar, deletar, force-push).
2883
+ 4. Mudan\xE7as pequenas e focadas em vez de reescritas amplas.
2884
+
2885
+ ## \xCDndice de skills
2886
+
2887
+ | Skill | Quando usar |
2888
+ |-------|-------------|
2889
+ | [skills/coding.md](skills/coding.md) | Padr\xF5es t\xE9cnicos para qualquer mudan\xE7a de c\xF3digo |
2890
+ | [skills/spec-driven.md](skills/spec-driven.md) | Escrever um spec antes de trabalho n\xE3o-trivial |
2891
+
2892
+ ## Ambiente
2893
+
2894
+ - Descreva SO, shell, gerenciador de pacotes e ferramentas que o agente precisa.
2895
+ - Anote onde ficam credenciais/CLIs e como os comandos s\xE3o executados.
2896
+ `,
2897
+ "README.md": `# .poly \u2014 o manual de opera\xE7\xE3o de IA do seu projeto
2898
+
2899
+ O \`.poly/\` \xE9 um workspace local e pequeno que ensina agentes de IA a trabalhar
2900
+ NESTE reposit\xF3rio. Coloque no .gitignore para mant\xEA-lo pessoal, ou commite para
2901
+ padronizar o fluxo entre o time.
2902
+
2903
+ ## O que tem dentro
2904
+
2905
+ - **\`agents.md\`** \u2014 o ponto de entrada: papel, regras de ouro e um \xEDndice de skills.
2906
+ O Polypus carrega ele automaticamente no system prompt do agente a cada execu\xE7\xE3o.
2907
+ - **\`skills/\`** \u2014 guias pr\xE1ticos e focados que o agente l\xEA quando relevante.
2908
+ - **\`templates/spec.md\`** \u2014 um template enxuto de Spec-Driven Development (SDD).
2909
+
2910
+ ## Como funciona
2911
+
2912
+ 1. Voc\xEA descreve uma tarefa ao agente.
2913
+ 2. O agente l\xEA o \`agents.md\`, segue as regras de ouro e abre as skills necess\xE1rias.
2914
+ 3. Para trabalho n\xE3o-trivial, escreve um spec primeiro a partir de \`templates/spec.md\`.
2915
+
2916
+ ## Como estender
2917
+
2918
+ - Edite o \`agents.md\` para codificar suas conven\xE7\xF5es.
2919
+ - Adicione um arquivo de skill por fluxo recorrente (releases, reviews, migra\xE7\xF5es\u2026).
2920
+ - Referencie as novas skills no \`agents.md\` para o agente descobri-las.
2921
+
2922
+ Regenere arquivos que faltarem com \`polypus init\` (os existentes s\xE3o preservados;
2923
+ use \`--force\` para sobrescrever).
2924
+ `,
2925
+ "skills/coding.md": `# skill: coding
2926
+
2927
+ Padr\xF5es t\xE9cnicos para mudan\xE7as neste reposit\xF3rio.
2928
+
2929
+ ## Princ\xEDpios
2930
+
2931
+ - Siga o estilo, a nomenclatura e a estrutura do c\xF3digo ao redor.
2932
+ - Prefira edi\xE7\xF5es pequenas e focadas a reescritas amplas.
2933
+ - Adicione ou atualize testes a cada mudan\xE7a de comportamento.
2934
+
2935
+ ## Checklist antes de abrir um PR
2936
+
2937
+ - [ ] Builda e passa no type-check
2938
+ - [ ] Testes passam
2939
+ - [ ] Docs / changelog atualizados quando o comportamento mudou
2940
+ `,
2941
+ "skills/spec-driven.md": `# skill: spec-driven development
2942
+
2943
+ Para qualquer coisa n\xE3o-trivial, escreva um spec curto ANTES de codar.
2944
+
2945
+ ## Fluxo
2946
+
2947
+ 1. Copie \`templates/spec.md\` para a issue (ou \`specs/<slug>.md\`).
2948
+ 2. Preencha **Por qu\xEA / O qu\xEA / Crit\xE9rios de aceite / Fora de escopo**.
2949
+ 3. Valide com um "ok", ent\xE3o implemente at\xE9 os crit\xE9rios de aceite.
2950
+ 4. Mantenha o spec atualizado se o escopo mudar.
2951
+
2952
+ Enxuto por design: se uma se\xE7\xE3o ficar vazia, apague-a.
2953
+ `,
2954
+ "templates/spec.md": `# Spec: <t\xEDtulo>
2955
+
2956
+ > Status: rascunho \xB7 Dono: <nome> \xB7 Atualizado: <aaaa-mm-dd>
2957
+
2958
+ ## Por qu\xEA
2959
+
2960
+ Que problema estamos resolvendo, e para quem? Por que agora?
2961
+
2962
+ ## O qu\xEA
2963
+
2964
+ A mudan\xE7a em termos simples \u2014 o comportamento que o usu\xE1rio vai realmente ver.
2965
+
2966
+ ## Crit\xE9rios de aceite
2967
+
2968
+ - [ ] Resultado observ\xE1vel 1
2969
+ - [ ] Resultado observ\xE1vel 2
2970
+
2971
+ ## Fora de escopo
2972
+
2973
+ - Coisas que explicitamente N\xC3O faremos aqui.
2974
+
2975
+ ## Notas / d\xFAvidas em aberto
2976
+
2977
+ - \u2026
2978
+ `
2979
+ };
2980
+
2981
+ // src/core/scaffold/init.ts
2982
+ async function scaffoldPoly(workspace, opts) {
2983
+ const templates = polyTemplates(opts.locale);
2984
+ const created = [];
2985
+ const skipped = [];
2986
+ for (const [rel, content] of Object.entries(templates)) {
2987
+ const display = `.poly/${rel}`;
2988
+ const abs = join3(workspace, ".poly", ...rel.split("/"));
2989
+ if (!opts.force && await exists(abs)) {
2990
+ skipped.push(display);
2991
+ continue;
2992
+ }
2993
+ await mkdir3(dirname3(abs), { recursive: true });
2994
+ await writeFile4(abs, content, "utf8");
2995
+ created.push(display);
2996
+ }
2997
+ return { created, skipped };
2998
+ }
2999
+ async function exists(path) {
3000
+ try {
3001
+ await access(path);
3002
+ return true;
3003
+ } catch {
3004
+ return false;
3005
+ }
3006
+ }
3007
+
3008
+ // src/cli/commands/init.ts
3009
+ async function init(opts) {
3010
+ const { created, skipped } = await scaffoldPoly(process.cwd(), {
3011
+ force: Boolean(opts.force),
3012
+ locale: getLocale()
3013
+ });
3014
+ if (created.length === 0) {
3015
+ console.log(pc8.yellow(t("init.allExist")));
3016
+ for (const f of skipped) console.log(pc8.dim(` ${f}`));
3017
+ console.log(pc8.dim(t("init.forceHint")));
3018
+ return;
3019
+ }
3020
+ console.log(pc8.green(t("init.created")));
3021
+ for (const f of created) console.log(pc8.dim(` ${f}`));
3022
+ if (skipped.length > 0) {
3023
+ console.log(pc8.dim(t("init.skipped")));
3024
+ for (const f of skipped) console.log(pc8.dim(` ${f}`));
3025
+ }
3026
+ console.log("\n" + t("init.tip"));
3027
+ }
3028
+
3029
+ // src/cli/commands/swarm.ts
3030
+ import pc9 from "picocolors";
3031
+
2684
3032
  // src/core/git/worktree.ts
2685
3033
  import { mkdtemp } from "fs/promises";
2686
3034
  import { tmpdir } from "os";
2687
- import { join as join2 } from "path";
3035
+ import { join as join4 } from "path";
2688
3036
  import { simpleGit } from "simple-git";
2689
3037
  async function ensureRepo(workspace) {
2690
3038
  const git = simpleGit(workspace);
@@ -2705,7 +3053,7 @@ async function identityArgs(git) {
2705
3053
  }
2706
3054
  async function createWorktree(git, label) {
2707
3055
  const branch = `polypus/${label}-${Date.now().toString(36)}`;
2708
- const path = await mkdtemp(join2(tmpdir(), "polypus-wt-"));
3056
+ const path = await mkdtemp(join4(tmpdir(), "polypus-wt-"));
2709
3057
  await git.raw(["worktree", "add", "-b", branch, path, "HEAD"]);
2710
3058
  return { path, branch };
2711
3059
  }
@@ -3017,8 +3365,15 @@ function pad(s, n) {
3017
3365
  }
3018
3366
 
3019
3367
  // src/cli/commands/swarm.ts
3368
+ var MIN_SWARM_AGENTS = 3;
3369
+ function canSwarm(agentCount) {
3370
+ return agentCount >= MIN_SWARM_AGENTS;
3371
+ }
3020
3372
  async function swarm(task, opts) {
3021
3373
  const config = await loadConfig();
3374
+ if (!canSwarm(config.agents.length)) {
3375
+ throw new Error(t("swarm.needsAgents", { min: MIN_SWARM_AGENTS, have: config.agents.length }));
3376
+ }
3022
3377
  const selected = opts.agents ? opts.agents.split(",").map((s) => s.trim()).filter(Boolean) : config.agents.map((a) => a.name);
3023
3378
  if (selected.length === 0) {
3024
3379
  throw new Error(t("swarm.noAgents"));
@@ -3029,9 +3384,9 @@ async function swarm(task, opts) {
3029
3384
  return createProvider(a);
3030
3385
  });
3031
3386
  console.log(
3032
- pc8.dim(t("swarm.status", { agents: resolved.map((a) => a.config.name).join(", "), workspace: process.cwd() }))
3387
+ pc9.dim(t("swarm.status", { agents: resolved.map((a) => a.config.name).join(", "), workspace: process.cwd() }))
3033
3388
  );
3034
- console.log(pc8.yellow(t("swarm.bypassNote") + "\n"));
3389
+ console.log(pc9.yellow(t("swarm.bypassNote") + "\n"));
3035
3390
  const view = new SwarmView(resolved[0].config.name);
3036
3391
  view.start();
3037
3392
  let result;
@@ -3058,25 +3413,25 @@ async function swarm(task, opts) {
3058
3413
  view.stop();
3059
3414
  }
3060
3415
  console.log("");
3061
- console.log(pc8.bold("\n" + t("swarm.summary")));
3416
+ console.log(pc9.bold("\n" + t("swarm.summary")));
3062
3417
  for (const o of result.outcomes) {
3063
- const status = o.finished ? pc8.green(t("swarm.statusDone")) : pc8.yellow(t("swarm.statusIncomplete"));
3064
- const committed = o.committed ? "" : pc8.dim(` (${t("swarm.noChanges")})`);
3065
- console.log(` ${pc8.bold(o.subtask.id)} [${o.agentName}] ${status}${committed} \u2014 ${o.subtask.title}`);
3418
+ const status = o.finished ? pc9.green(t("swarm.statusDone")) : pc9.yellow(t("swarm.statusIncomplete"));
3419
+ const committed = o.committed ? "" : pc9.dim(` (${t("swarm.noChanges")})`);
3420
+ console.log(` ${pc9.bold(o.subtask.id)} [${o.agentName}] ${status}${committed} \u2014 ${o.subtask.title}`);
3066
3421
  }
3067
3422
  const conflicts = result.merges.filter((m) => !m.ok);
3068
3423
  if (conflicts.length > 0) {
3069
- console.log(pc8.red("\n" + t("swarm.conflictsHeader", { n: conflicts.length })));
3424
+ console.log(pc9.red("\n" + t("swarm.conflictsHeader", { n: conflicts.length })));
3070
3425
  for (const m of conflicts) {
3071
- console.log(pc8.red(` ${m.branch}: ${m.conflicts.join(", ")}`));
3426
+ console.log(pc9.red(` ${m.branch}: ${m.conflicts.join(", ")}`));
3072
3427
  }
3073
3428
  } else {
3074
- console.log(pc8.green("\n" + t("swarm.allMerged")));
3429
+ console.log(pc9.green("\n" + t("swarm.allMerged")));
3075
3430
  }
3076
3431
  }
3077
3432
 
3078
3433
  // src/cli/commands/models.ts
3079
- import pc9 from "picocolors";
3434
+ import pc10 from "picocolors";
3080
3435
  import * as p3 from "@clack/prompts";
3081
3436
  async function models(opts) {
3082
3437
  const apiKey = await resolveOpenRouterKey();
@@ -3085,9 +3440,9 @@ async function models(opts) {
3085
3440
  let all;
3086
3441
  try {
3087
3442
  all = await listOpenRouterModels(apiKey);
3088
- spin.stop(pc9.green("\u2713 OpenRouter"));
3443
+ spin.stop(pc10.green("\u2713 OpenRouter"));
3089
3444
  } catch (err) {
3090
- spin.stop(pc9.red(t("models.fetchError", { msg: err.message })), 2);
3445
+ spin.stop(pc10.red(t("models.fetchError", { msg: err.message })), 2);
3091
3446
  return;
3092
3447
  }
3093
3448
  const filtered = filterModels(all, {
@@ -3101,26 +3456,26 @@ async function models(opts) {
3101
3456
  printModelsTable(filtered, limit, all.length);
3102
3457
  }
3103
3458
  function printModelsTable(models2, limit, total) {
3104
- console.log(pc9.dim(t("models.legend")));
3459
+ console.log(pc10.dim(t("models.legend")));
3105
3460
  if (models2.length === 0) {
3106
- console.log(pc9.yellow(t("models.none")));
3461
+ console.log(pc10.yellow(t("models.none")));
3107
3462
  return;
3108
3463
  }
3109
3464
  const rows = models2.slice(0, limit);
3110
3465
  console.log(
3111
- " " + pc9.dim(t("models.colTools").padEnd(6)) + pc9.dim(t("models.colPrice").padEnd(16)) + pc9.dim(t("models.colCtx").padEnd(9)) + pc9.dim(t("models.colModel"))
3466
+ " " + pc10.dim(t("models.colTools").padEnd(6)) + pc10.dim(t("models.colPrice").padEnd(16)) + pc10.dim(t("models.colCtx").padEnd(9)) + pc10.dim(t("models.colModel"))
3112
3467
  );
3113
3468
  for (const m of rows) {
3114
3469
  console.log(" " + modelRow(m));
3115
3470
  }
3116
- console.log(pc9.dim("\n" + t("models.shown", { shown: rows.length, total })));
3471
+ console.log(pc10.dim("\n" + t("models.shown", { shown: rows.length, total })));
3117
3472
  }
3118
3473
  function modelRow(m) {
3119
- const tools = m.supportsTools ? pc9.green("\u{1F6E0}".padEnd(5)) : pc9.dim("\u2014".padEnd(5));
3474
+ const tools = m.supportsTools ? pc10.green("\u{1F6E0}".padEnd(5)) : pc10.dim("\u2014".padEnd(5));
3120
3475
  const price = `${fmtPrice(m.promptPrice)}/${fmtPrice(m.completionPrice)}`;
3121
- const priceColored = (m.free ? pc9.green : pc9.yellow)(price.padEnd(16));
3122
- const ctx = pc9.cyan(fmtContext(m.contextLength).padEnd(9));
3123
- return `${tools} ${priceColored}${ctx}${pc9.bold(m.id)}`;
3476
+ const priceColored = (m.free ? pc10.green : pc10.yellow)(price.padEnd(16));
3477
+ const ctx = pc10.cyan(fmtContext(m.contextLength).padEnd(9));
3478
+ return `${tools} ${priceColored}${ctx}${pc10.bold(m.id)}`;
3124
3479
  }
3125
3480
  async function resolveOpenRouterKey() {
3126
3481
  if (process.env.OPENROUTER_API_KEY) return process.env.OPENROUTER_API_KEY;
@@ -3134,10 +3489,10 @@ async function resolveOpenRouterKey() {
3134
3489
  }
3135
3490
 
3136
3491
  // src/cli/commands/prd.ts
3137
- import { writeFile as writeFile4, readFile as readFile5 } from "fs/promises";
3492
+ import { writeFile as writeFile5, readFile as readFile6 } from "fs/promises";
3138
3493
  import { execFile } from "child_process";
3139
3494
  import { promisify as promisify2 } from "util";
3140
- import pc10 from "picocolors";
3495
+ import pc11 from "picocolors";
3141
3496
 
3142
3497
  // src/core/agent/prd.ts
3143
3498
  var SYSTEM = [
@@ -3265,15 +3620,15 @@ async function prd(issueRef, opts) {
3265
3620
  const guide = readProjectGuide(["context.md"]);
3266
3621
  const markdown = await withRetry(() => generatePrd(issue, provider, guide));
3267
3622
  if (opts.out) {
3268
- await writeFile4(opts.out, markdown + "\n", "utf8");
3269
- console.error(pc10.green(t("prd.wrote", { path: opts.out })));
3623
+ await writeFile5(opts.out, markdown + "\n", "utf8");
3624
+ console.error(pc11.green(t("prd.wrote", { path: opts.out })));
3270
3625
  } else {
3271
3626
  process.stdout.write(markdown + "\n");
3272
3627
  }
3273
3628
  }
3274
3629
  async function loadIssue(issueRef, input) {
3275
3630
  if (input) {
3276
- const raw = input === "-" ? await readStdin() : await readFile5(input, "utf8");
3631
+ const raw = input === "-" ? await readStdin() : await readFile6(input, "utf8");
3277
3632
  return normalize2(JSON.parse(stripBom(raw)));
3278
3633
  }
3279
3634
  const num = numericRef(issueRef);
@@ -3292,10 +3647,10 @@ function normalize2(raw) {
3292
3647
  }
3293
3648
 
3294
3649
  // src/cli/commands/review.ts
3295
- import { writeFile as writeFile5, readFile as readFile6 } from "fs/promises";
3650
+ import { writeFile as writeFile6, readFile as readFile7 } from "fs/promises";
3296
3651
  import { execFile as execFile2 } from "child_process";
3297
3652
  import { promisify as promisify3 } from "util";
3298
- import pc11 from "picocolors";
3653
+ import pc12 from "picocolors";
3299
3654
 
3300
3655
  // src/core/agent/review.ts
3301
3656
  var MAX_DIFF_CHARS = Number(process.env.POLYPUS_MAX_DIFF_CHARS) || 6e4;
@@ -3361,14 +3716,14 @@ async function review(prRef, opts) {
3361
3716
  const guide = readProjectGuide(["rules.md", "context.md"]);
3362
3717
  const markdown = await withRetry(() => reviewDiff(diff, meta, provider, guide));
3363
3718
  if (opts.out) {
3364
- await writeFile5(opts.out, markdown + "\n", "utf8");
3365
- console.error(pc11.green(t("review.wrote", { path: opts.out })));
3719
+ await writeFile6(opts.out, markdown + "\n", "utf8");
3720
+ console.error(pc12.green(t("review.wrote", { path: opts.out })));
3366
3721
  } else {
3367
3722
  process.stdout.write(markdown + "\n");
3368
3723
  }
3369
3724
  }
3370
3725
  async function loadDiff(num, input) {
3371
- if (input) return input === "-" ? readStdin() : readFile6(input, "utf8");
3726
+ if (input) return input === "-" ? readStdin() : readFile7(input, "utf8");
3372
3727
  const { stdout: stdout2 } = await exec3("gh", ["pr", "diff", num]);
3373
3728
  return stdout2;
3374
3729
  }
@@ -3380,7 +3735,7 @@ async function loadMeta(num, input) {
3380
3735
  }
3381
3736
 
3382
3737
  // src/cli/index.ts
3383
- import { join as join3 } from "path";
3738
+ import { join as join5 } from "path";
3384
3739
 
3385
3740
  // src/core/config/dotenv.ts
3386
3741
  import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
@@ -3409,12 +3764,11 @@ function loadDotenv(paths) {
3409
3764
  }
3410
3765
 
3411
3766
  // src/cli/index.ts
3412
- var { version: pkgVersion } = createRequire(import.meta.url)("../package.json");
3413
3767
  async function launchInteractive() {
3414
3768
  const config = await loadConfig();
3415
3769
  if (config.agents.length === 0) {
3416
3770
  console.log(banner());
3417
- console.log(" " + pc12.yellow(t("welcome.firstRun")) + "\n");
3771
+ console.log(" " + pc13.yellow(t("welcome.firstRun")) + "\n");
3418
3772
  await setup();
3419
3773
  }
3420
3774
  await run(void 0, {});
@@ -3435,8 +3789,9 @@ async function resolveLocale() {
3435
3789
  }
3436
3790
  function buildProgram() {
3437
3791
  const program = new Command();
3438
- program.name("polypus").description(t("cli.description")).version(pkgVersion).option("--lang <locale>", t("cli.opt.lang")).action(() => launchInteractive());
3792
+ program.name("polypus").description(t("cli.description")).version(VERSION).option("--lang <locale>", t("cli.opt.lang")).action(() => launchInteractive());
3439
3793
  program.command("setup").description(t("cli.cmd.setup")).action(() => setup());
3794
+ program.command("init").option("--force", t("cli.opt.force")).description(t("cli.cmd.init")).action((opts) => init(opts));
3440
3795
  program.command("add-agent").argument("<name>", t("cli.arg.addAgentName")).requiredOption("--provider <provider>", t("cli.opt.provider")).requiredOption("--model <model>", t("cli.opt.model")).option("--api-key <key>", t("cli.opt.apiKey")).option("--base-url <url>", t("cli.opt.baseUrl")).option("--tool-mode <mode>", t("cli.opt.toolMode"), "auto").option("--set-default", t("cli.opt.setDefault")).description(t("cli.cmd.addAgent")).action((name, opts) => addAgent(name, opts));
3441
3796
  program.command("remove-agent").argument("<name>", t("cli.arg.removeAgentName")).description(t("cli.cmd.removeAgent")).action((name) => removeAgent(name));
3442
3797
  program.command("list-agents").alias("agents").description(t("cli.cmd.listAgents")).action(() => listAgents());
@@ -3449,11 +3804,11 @@ function buildProgram() {
3449
3804
  }
3450
3805
  async function main() {
3451
3806
  try {
3452
- loadDotenv([join3(configDir(), ".env"), join3(process.cwd(), ".env")]);
3807
+ loadDotenv([join5(configDir(), ".env"), join5(process.cwd(), ".env")]);
3453
3808
  await resolveLocale();
3454
3809
  await buildProgram().parseAsync(process.argv);
3455
3810
  } catch (err) {
3456
- console.error(pc12.red(`\u2717 ${err.message}`));
3811
+ console.error(pc13.red(`\u2717 ${err.message}`));
3457
3812
  process.exitCode = 1;
3458
3813
  }
3459
3814
  }