@gaberrb/polypus 0.2.2 → 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/README.md +23 -0
- package/dist/index.js +659 -95
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
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
|
|
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):",
|
|
@@ -177,9 +179,24 @@ var en = {
|
|
|
177
179
|
"swarm.mergeConflict": " conflict merging {branch}",
|
|
178
180
|
"swarm.summary": "Summary:",
|
|
179
181
|
"swarm.allMerged": "\u2713 All committed branches merged cleanly.",
|
|
182
|
+
"swarm.view.header": "Swarm \xB7 orchestrator [{lead}]",
|
|
183
|
+
"swarm.view.decomposing": "splitting the task\u2026",
|
|
184
|
+
"swarm.view.pending": "queued",
|
|
185
|
+
"swarm.view.running": "running",
|
|
186
|
+
"swarm.view.done": "done",
|
|
187
|
+
"swarm.view.stopped": "stopped",
|
|
188
|
+
"swarm.view.conflict": "conflict",
|
|
189
|
+
"swarm.view.step": "step {n}",
|
|
190
|
+
"swarm.view.steps": "{n} steps",
|
|
180
191
|
"swarm.conflictsHeader": "\u26A0 {n} branch(es) had merge conflicts (kept for inspection):",
|
|
181
192
|
"swarm.statusDone": "done",
|
|
182
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.",
|
|
183
200
|
// wizard
|
|
184
201
|
"wizard.title": " polypus setup ",
|
|
185
202
|
"wizard.intro": [
|
|
@@ -265,7 +282,8 @@ var en = {
|
|
|
265
282
|
"welcome.hints": "Type your task and press Enter \xB7 ESC cancels \xB7 /help \xB7 /exit",
|
|
266
283
|
"welcome.firstRun": "No agents configured yet \u2014 let's set you up.",
|
|
267
284
|
// agent system prompt
|
|
268
|
-
"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:"
|
|
269
287
|
};
|
|
270
288
|
var ptBR = {
|
|
271
289
|
"common.default": "padr\xE3o",
|
|
@@ -274,11 +292,13 @@ var ptBR = {
|
|
|
274
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.",
|
|
275
293
|
"cli.opt.lang": "idioma da interface: pt-BR | en",
|
|
276
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",
|
|
277
297
|
"cli.cmd.addAgent": "Cadastra um novo agente (chave de API + modelo)",
|
|
278
298
|
"cli.cmd.removeAgent": "Remove um agente configurado",
|
|
279
299
|
"cli.cmd.listAgents": "Lista os agentes configurados",
|
|
280
300
|
"cli.cmd.run": "Executa uma tarefa de c\xF3digo com um agente",
|
|
281
|
-
"cli.cmd.swarm": "Divide uma tarefa entre v\xE1rios agentes
|
|
301
|
+
"cli.cmd.swarm": "Divide uma tarefa entre v\xE1rios agentes em git worktrees paralelas (requer 3+ agentes configurados)",
|
|
282
302
|
"cli.cmd.models": "Explora os modelos do OpenRouter (pre\xE7o, contexto, suporte a tools)",
|
|
283
303
|
"cli.cmd.prd": "Gera um PRD a partir de uma issue do GitHub (usa um modelo gratuito do OpenRouter)",
|
|
284
304
|
"cli.arg.prdIssue": "n\xFAmero da issue para transformar em PRD",
|
|
@@ -360,6 +380,7 @@ var ptBR = {
|
|
|
360
380
|
"Qualquer outra coisa \xE9 enviada ao agente como tarefa."
|
|
361
381
|
].join("\n"),
|
|
362
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.",
|
|
363
384
|
"swarm.status": "swarm agentes=[{agents}] workspace={workspace}",
|
|
364
385
|
"swarm.bypassNote": "Os workers rodam em modo bypass dentro de git worktrees isoladas; os branches s\xE3o mesclados no final.",
|
|
365
386
|
"swarm.decomposed": "Dividido em {n} subtarefa(s):",
|
|
@@ -373,9 +394,24 @@ var ptBR = {
|
|
|
373
394
|
"swarm.mergeConflict": " conflito ao mesclar {branch}",
|
|
374
395
|
"swarm.summary": "Resumo:",
|
|
375
396
|
"swarm.allMerged": "\u2713 Todos os branches commitados foram mesclados sem conflito.",
|
|
397
|
+
"swarm.view.header": "Swarm \xB7 orquestrador [{lead}]",
|
|
398
|
+
"swarm.view.decomposing": "dividindo a tarefa\u2026",
|
|
399
|
+
"swarm.view.pending": "na fila",
|
|
400
|
+
"swarm.view.running": "executando",
|
|
401
|
+
"swarm.view.done": "conclu\xEDdo",
|
|
402
|
+
"swarm.view.stopped": "parado",
|
|
403
|
+
"swarm.view.conflict": "conflito",
|
|
404
|
+
"swarm.view.step": "passo {n}",
|
|
405
|
+
"swarm.view.steps": "{n} passos",
|
|
376
406
|
"swarm.conflictsHeader": "\u26A0 {n} branch(es) tiveram conflitos de merge (mantidos para inspe\xE7\xE3o):",
|
|
377
407
|
"swarm.statusDone": "ok",
|
|
378
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.",
|
|
379
415
|
"wizard.title": " configura\xE7\xE3o do polypus ",
|
|
380
416
|
"wizard.intro": [
|
|
381
417
|
"O Polypus comanda qualquer API de IA para ler e escrever c\xF3digo neste tipo de projeto.",
|
|
@@ -437,6 +473,7 @@ var ptBR = {
|
|
|
437
473
|
"wizard.envInvalid": "Use letras, d\xEDgitos e sublinhados",
|
|
438
474
|
"wizard.keyPrompt": "Chave de API (armazenada em texto puro no arquivo de config)",
|
|
439
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:",
|
|
440
477
|
"models.fetching": "Buscando modelos do OpenRouter\u2026",
|
|
441
478
|
"models.fetchError": "N\xE3o foi poss\xEDvel buscar modelos: {msg}",
|
|
442
479
|
"models.none": "Nenhum modelo corresponde aos filtros.",
|
|
@@ -1007,6 +1044,10 @@ function basePreamble(ctx) {
|
|
|
1007
1044
|
"- Do not ask for permission and do not say you cannot edit files \u2014 you can. Just emit the tool calls.",
|
|
1008
1045
|
"- Make the changes directly. When the task is fully done, call the `finish` tool with a short summary.",
|
|
1009
1046
|
t("prompt.language", { language: LOCALE_NAMES[getLocale()] }),
|
|
1047
|
+
ctx.projectInstructions ? `
|
|
1048
|
+
${t("prompt.projectInstructions")}
|
|
1049
|
+
|
|
1050
|
+
${ctx.projectInstructions}` : "",
|
|
1010
1051
|
ctx.briefing ? `
|
|
1011
1052
|
Your assigned task:
|
|
1012
1053
|
${ctx.briefing}` : ""
|
|
@@ -1650,6 +1691,23 @@ function formatSchema(spec) {
|
|
|
1650
1691
|
return lines.join("\n") || " (no parameters)";
|
|
1651
1692
|
}
|
|
1652
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
|
+
|
|
1653
1711
|
// src/core/agent/loop.ts
|
|
1654
1712
|
function looksLikeStall(text2) {
|
|
1655
1713
|
const lc = text2.toLowerCase();
|
|
@@ -1692,10 +1750,12 @@ async function runAgent(opts) {
|
|
|
1692
1750
|
const maxReprompts = opts.maxReprompts ?? 3;
|
|
1693
1751
|
const driver = makeDriver(agent.toolMode, toolSpecs());
|
|
1694
1752
|
const ctx = { workspace: opts.workspace, permissions };
|
|
1695
|
-
const
|
|
1696
|
-
|
|
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) },
|
|
1697
1757
|
{ role: "user", content: opts.task }
|
|
1698
|
-
];
|
|
1758
|
+
] : [...opts.history, { role: "user", content: opts.task }];
|
|
1699
1759
|
let consecutiveNoTool = 0;
|
|
1700
1760
|
let lastFailSig = "";
|
|
1701
1761
|
let failStreak = 0;
|
|
@@ -2164,6 +2224,23 @@ async function promptApiKey(provider) {
|
|
|
2164
2224
|
|
|
2165
2225
|
// src/ui/banner.ts
|
|
2166
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
|
|
2167
2244
|
var RESET = "\x1B[0m";
|
|
2168
2245
|
var useColor = (Boolean(process.stdout.isTTY) || Boolean(process.env.FORCE_COLOR)) && !process.env.NO_COLOR;
|
|
2169
2246
|
var animated = Boolean(process.stdout.isTTY) && !process.env.NO_COLOR && !process.env.POLYPUS_NO_ANIM;
|
|
@@ -2202,9 +2279,9 @@ var TENTACLE_FRAMES = [
|
|
|
2202
2279
|
];
|
|
2203
2280
|
var WIDTH = Math.max(...[...ART].map((l) => [...l].length));
|
|
2204
2281
|
function center(line) {
|
|
2205
|
-
const
|
|
2206
|
-
const left = Math.floor(
|
|
2207
|
-
return " ".repeat(left) + line + " ".repeat(
|
|
2282
|
+
const pad2 = WIDTH - [...line].length;
|
|
2283
|
+
const left = Math.floor(pad2 / 2);
|
|
2284
|
+
return " ".repeat(left) + line + " ".repeat(pad2 - left);
|
|
2208
2285
|
}
|
|
2209
2286
|
var GLYPHS = {
|
|
2210
2287
|
P: ["\u2588\u2588\u2588\u2588\u2588\u2588", "\u2588\u2588 \u2588\u2588", "\u2588\u2588\u2588\u2588\u2588\u2588", "\u2588\u2588 ", "\u2588\u2588 "],
|
|
@@ -2237,7 +2314,7 @@ function colorChar(rowIdx, ch) {
|
|
|
2237
2314
|
function renderArtRow(rowIdx, line) {
|
|
2238
2315
|
return [...center(line)].map((ch) => colorChar(rowIdx, ch)).join("");
|
|
2239
2316
|
}
|
|
2240
|
-
var tagline = () => c1(t("welcome.tagline")) + pc5.dim(
|
|
2317
|
+
var tagline = () => c1(t("welcome.tagline")) + pc5.dim(` v${VERSION}`);
|
|
2241
2318
|
function authorLine() {
|
|
2242
2319
|
return pc5.dim("by ") + c2(AUTHOR.name) + pc5.dim(" \xB7 ") + c1(AUTHOR.github) + pc5.dim(" \xB7 ") + c1(AUTHOR.linkedin);
|
|
2243
2320
|
}
|
|
@@ -2660,13 +2737,302 @@ async function setup() {
|
|
|
2660
2737
|
await runWizard();
|
|
2661
2738
|
}
|
|
2662
2739
|
|
|
2663
|
-
// src/cli/commands/
|
|
2740
|
+
// src/cli/commands/init.ts
|
|
2664
2741
|
import pc8 from "picocolors";
|
|
2665
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
|
+
|
|
2666
3032
|
// src/core/git/worktree.ts
|
|
2667
3033
|
import { mkdtemp } from "fs/promises";
|
|
2668
3034
|
import { tmpdir } from "os";
|
|
2669
|
-
import { join as
|
|
3035
|
+
import { join as join4 } from "path";
|
|
2670
3036
|
import { simpleGit } from "simple-git";
|
|
2671
3037
|
async function ensureRepo(workspace) {
|
|
2672
3038
|
const git = simpleGit(workspace);
|
|
@@ -2687,7 +3053,7 @@ async function identityArgs(git) {
|
|
|
2687
3053
|
}
|
|
2688
3054
|
async function createWorktree(git, label) {
|
|
2689
3055
|
const branch = `polypus/${label}-${Date.now().toString(36)}`;
|
|
2690
|
-
const path = await mkdtemp(
|
|
3056
|
+
const path = await mkdtemp(join4(tmpdir(), "polypus-wt-"));
|
|
2691
3057
|
await git.raw(["worktree", "add", "-b", branch, path, "HEAD"]);
|
|
2692
3058
|
return { path, branch };
|
|
2693
3059
|
}
|
|
@@ -2835,9 +3201,179 @@ function extractJsonArray(text2) {
|
|
|
2835
3201
|
}
|
|
2836
3202
|
}
|
|
2837
3203
|
|
|
3204
|
+
// src/ui/swarm-view.ts
|
|
3205
|
+
var RESET3 = "\x1B[0m";
|
|
3206
|
+
var FRAMES2 = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
3207
|
+
function describeToolCall(call) {
|
|
3208
|
+
const raw = call.name === "run_command" ? call.arguments.command : call.arguments.path;
|
|
3209
|
+
const arg = typeof raw === "string" ? raw : "";
|
|
3210
|
+
const short = arg.length > 40 ? arg.slice(0, 39) + "\u2026" : arg;
|
|
3211
|
+
return short ? `${call.name} ${short}` : call.name;
|
|
3212
|
+
}
|
|
3213
|
+
var SwarmView = class {
|
|
3214
|
+
constructor(leadName, opts = {}) {
|
|
3215
|
+
this.leadName = leadName;
|
|
3216
|
+
this.tty = opts.tty ?? (Boolean(process.stdout.isTTY) && !process.env.NO_COLOR);
|
|
3217
|
+
this.color = opts.color ?? this.tty;
|
|
3218
|
+
this.write = opts.sink ?? ((s) => process.stdout.write(s));
|
|
3219
|
+
}
|
|
3220
|
+
leadName;
|
|
3221
|
+
tty;
|
|
3222
|
+
color;
|
|
3223
|
+
write;
|
|
3224
|
+
workers = /* @__PURE__ */ new Map();
|
|
3225
|
+
order = [];
|
|
3226
|
+
phase = "decomposing";
|
|
3227
|
+
frame = 0;
|
|
3228
|
+
lastLines = 0;
|
|
3229
|
+
timer;
|
|
3230
|
+
start() {
|
|
3231
|
+
if (!this.tty) {
|
|
3232
|
+
this.write(`\u{1F419} ${t("swarm.view.header", { lead: this.leadName })} \u2014 ${t("swarm.view.decomposing")}
|
|
3233
|
+
`);
|
|
3234
|
+
return;
|
|
3235
|
+
}
|
|
3236
|
+
this.flush();
|
|
3237
|
+
this.timer = setInterval(() => {
|
|
3238
|
+
this.frame = (this.frame + 1) % FRAMES2.length;
|
|
3239
|
+
this.flush();
|
|
3240
|
+
}, 110);
|
|
3241
|
+
this.timer.unref?.();
|
|
3242
|
+
}
|
|
3243
|
+
setSubtasks(subtasks) {
|
|
3244
|
+
this.phase = "running";
|
|
3245
|
+
for (const s of subtasks) {
|
|
3246
|
+
this.workers.set(s.id, { id: s.id, title: s.title, agent: "", status: "pending", action: "", steps: 0 });
|
|
3247
|
+
this.order.push(s.id);
|
|
3248
|
+
}
|
|
3249
|
+
if (!this.tty) {
|
|
3250
|
+
this.write(` ${t("swarm.decomposed", { n: subtasks.length })}
|
|
3251
|
+
`);
|
|
3252
|
+
for (const s of subtasks) this.write(` ${s.id}: ${s.title}
|
|
3253
|
+
`);
|
|
3254
|
+
}
|
|
3255
|
+
this.flush();
|
|
3256
|
+
}
|
|
3257
|
+
workerStart(id, agent) {
|
|
3258
|
+
const w = this.workers.get(id);
|
|
3259
|
+
if (!w) return;
|
|
3260
|
+
w.agent = agent;
|
|
3261
|
+
w.status = "running";
|
|
3262
|
+
if (!this.tty) this.write(` \u25B6 ${id} [${agent}] ${w.title}
|
|
3263
|
+
`);
|
|
3264
|
+
this.flush();
|
|
3265
|
+
}
|
|
3266
|
+
workerAction(id, action) {
|
|
3267
|
+
const w = this.workers.get(id);
|
|
3268
|
+
if (!w) return;
|
|
3269
|
+
w.action = action;
|
|
3270
|
+
this.flush();
|
|
3271
|
+
}
|
|
3272
|
+
workerStep(id, n) {
|
|
3273
|
+
const w = this.workers.get(id);
|
|
3274
|
+
if (!w) return;
|
|
3275
|
+
w.steps = n;
|
|
3276
|
+
this.flush();
|
|
3277
|
+
}
|
|
3278
|
+
workerDone(o) {
|
|
3279
|
+
const w = this.workers.get(o.subtask.id);
|
|
3280
|
+
if (!w) return;
|
|
3281
|
+
w.status = o.finished ? "done" : "stopped";
|
|
3282
|
+
w.steps = o.steps;
|
|
3283
|
+
w.branch = o.branch;
|
|
3284
|
+
w.action = "";
|
|
3285
|
+
if (!this.tty) {
|
|
3286
|
+
const tag = o.finished ? "\u2713" : "\u25A0";
|
|
3287
|
+
const changes = o.committed ? t("swarm.changesCommitted") : t("swarm.noChanges");
|
|
3288
|
+
this.write(` ${tag} ${o.subtask.id} (${t("swarm.view.steps", { n: o.steps })}, ${changes})
|
|
3289
|
+
`);
|
|
3290
|
+
}
|
|
3291
|
+
this.flush();
|
|
3292
|
+
}
|
|
3293
|
+
merge(r) {
|
|
3294
|
+
for (const w of this.workers.values()) {
|
|
3295
|
+
if (w.branch === r.branch) w.merge = r.ok ? "ok" : "conflict";
|
|
3296
|
+
}
|
|
3297
|
+
if (!this.tty) {
|
|
3298
|
+
this.write(r.ok ? ` \u2935 ${t("swarm.merged", { branch: r.branch })}
|
|
3299
|
+
` : ` \u2717 ${t("swarm.mergeConflict", { branch: r.branch })}
|
|
3300
|
+
`);
|
|
3301
|
+
}
|
|
3302
|
+
this.flush();
|
|
3303
|
+
}
|
|
3304
|
+
stop() {
|
|
3305
|
+
this.phase = "done";
|
|
3306
|
+
if (this.timer) {
|
|
3307
|
+
clearInterval(this.timer);
|
|
3308
|
+
this.timer = void 0;
|
|
3309
|
+
}
|
|
3310
|
+
this.flush();
|
|
3311
|
+
}
|
|
3312
|
+
/** Content lines of the dashboard (no cursor control). Exposed for tests. */
|
|
3313
|
+
frameLines() {
|
|
3314
|
+
const spin = this.dim(FRAMES2[this.frame]);
|
|
3315
|
+
const lead = `\u{1F419} ${t("swarm.view.header", { lead: this.leadName })}`;
|
|
3316
|
+
const lines = [];
|
|
3317
|
+
if (this.phase === "decomposing") {
|
|
3318
|
+
lines.push(`${spin} ${lead}`);
|
|
3319
|
+
lines.push(" " + this.dim(t("swarm.view.decomposing")));
|
|
3320
|
+
return lines;
|
|
3321
|
+
}
|
|
3322
|
+
lines.push(`${this.phase === "running" ? spin : " "} ${lead}`);
|
|
3323
|
+
lines.push("");
|
|
3324
|
+
for (const id of this.order) {
|
|
3325
|
+
const w = this.workers.get(id);
|
|
3326
|
+
lines.push(this.row(w, spin));
|
|
3327
|
+
}
|
|
3328
|
+
return lines;
|
|
3329
|
+
}
|
|
3330
|
+
// -------------------------------------------------------------------------
|
|
3331
|
+
row(w, spin) {
|
|
3332
|
+
const icon = w.status === "running" ? spin : w.status === "done" ? this.c("\u2713", "32") : w.status === "stopped" ? this.c("\u25A0", "33") : this.dim("\xB7");
|
|
3333
|
+
const status = this.statusLabel(w);
|
|
3334
|
+
const meta = w.steps > 0 ? this.dim(" \xB7 " + (w.status === "running" ? t("swarm.view.step", { n: w.steps }) : t("swarm.view.steps", { n: w.steps }))) : "";
|
|
3335
|
+
const action = w.action ? w.action : this.dim("\u2014");
|
|
3336
|
+
return ` ${icon} ${pad(w.id, 4)} ${pad(status, 12)} ${pad(`[${w.agent}]`, 14)} ${action}${meta}`;
|
|
3337
|
+
}
|
|
3338
|
+
statusLabel(w) {
|
|
3339
|
+
if (w.merge === "conflict") return this.c(t("swarm.view.conflict"), "31");
|
|
3340
|
+
if (w.status === "running") return this.c(t("swarm.view.running"), "36");
|
|
3341
|
+
if (w.status === "done") return this.c(t("swarm.view.done"), "32");
|
|
3342
|
+
if (w.status === "stopped") return this.c(t("swarm.view.stopped"), "33");
|
|
3343
|
+
return this.dim(t("swarm.view.pending"));
|
|
3344
|
+
}
|
|
3345
|
+
/** Redraw the block in place (TTY) by clearing the previous frame first. */
|
|
3346
|
+
flush() {
|
|
3347
|
+
if (!this.tty) return;
|
|
3348
|
+
const lines = this.frameLines();
|
|
3349
|
+
let s = "";
|
|
3350
|
+
if (this.lastLines > 0) s += `\x1B[${this.lastLines}A`;
|
|
3351
|
+
s += "\x1B[0J";
|
|
3352
|
+
s += lines.join("\n") + "\n";
|
|
3353
|
+
this.write(s);
|
|
3354
|
+
this.lastLines = lines.length;
|
|
3355
|
+
}
|
|
3356
|
+
c(s, code) {
|
|
3357
|
+
return this.color ? `\x1B[${code}m${s}${RESET3}` : s;
|
|
3358
|
+
}
|
|
3359
|
+
dim(s) {
|
|
3360
|
+
return this.color ? `\x1B[2m${s}${RESET3}` : s;
|
|
3361
|
+
}
|
|
3362
|
+
};
|
|
3363
|
+
function pad(s, n) {
|
|
3364
|
+
return s.length >= n ? s : s + " ".repeat(n - s.length);
|
|
3365
|
+
}
|
|
3366
|
+
|
|
2838
3367
|
// src/cli/commands/swarm.ts
|
|
3368
|
+
var MIN_SWARM_AGENTS = 3;
|
|
3369
|
+
function canSwarm(agentCount) {
|
|
3370
|
+
return agentCount >= MIN_SWARM_AGENTS;
|
|
3371
|
+
}
|
|
2839
3372
|
async function swarm(task, opts) {
|
|
2840
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
|
+
}
|
|
2841
3377
|
const selected = opts.agents ? opts.agents.split(",").map((s) => s.trim()).filter(Boolean) : config.agents.map((a) => a.name);
|
|
2842
3378
|
if (selected.length === 0) {
|
|
2843
3379
|
throw new Error(t("swarm.noAgents"));
|
|
@@ -2848,57 +3384,54 @@ async function swarm(task, opts) {
|
|
|
2848
3384
|
return createProvider(a);
|
|
2849
3385
|
});
|
|
2850
3386
|
console.log(
|
|
2851
|
-
|
|
3387
|
+
pc9.dim(t("swarm.status", { agents: resolved.map((a) => a.config.name).join(", "), workspace: process.cwd() }))
|
|
2852
3388
|
);
|
|
2853
|
-
console.log(
|
|
2854
|
-
const
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
3389
|
+
console.log(pc9.yellow(t("swarm.bypassNote") + "\n"));
|
|
3390
|
+
const view = new SwarmView(resolved[0].config.name);
|
|
3391
|
+
view.start();
|
|
3392
|
+
let result;
|
|
3393
|
+
try {
|
|
3394
|
+
result = await runSwarm({
|
|
3395
|
+
task,
|
|
3396
|
+
workspace: process.cwd(),
|
|
3397
|
+
agents: resolved,
|
|
3398
|
+
allow: config.permissions.allow,
|
|
3399
|
+
deny: config.permissions.deny,
|
|
3400
|
+
maxSubtasks: opts.maxSubtasks ? Number(opts.maxSubtasks) : void 0,
|
|
3401
|
+
events: {
|
|
3402
|
+
onDecomposed: (subtasks) => view.setSubtasks(subtasks),
|
|
3403
|
+
onWorkerStart: (subtask, agentName) => view.workerStart(subtask.id, agentName),
|
|
3404
|
+
onWorkerDone: (outcome) => view.workerDone(outcome),
|
|
3405
|
+
onMerge: (merge) => view.merge(merge),
|
|
3406
|
+
workerEvents: (subtask) => ({
|
|
3407
|
+
onToolCall: (call) => view.workerAction(subtask.id, describeToolCall(call)),
|
|
3408
|
+
onStep: (step) => view.workerStep(subtask.id, step)
|
|
3409
|
+
})
|
|
3410
|
+
}
|
|
3411
|
+
});
|
|
3412
|
+
} finally {
|
|
3413
|
+
view.stop();
|
|
3414
|
+
}
|
|
3415
|
+
console.log("");
|
|
3416
|
+
console.log(pc9.bold("\n" + t("swarm.summary")));
|
|
2864
3417
|
for (const o of result.outcomes) {
|
|
2865
|
-
const status = o.finished ?
|
|
2866
|
-
const committed = o.committed ? "" :
|
|
2867
|
-
console.log(` ${
|
|
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}`);
|
|
2868
3421
|
}
|
|
2869
3422
|
const conflicts = result.merges.filter((m) => !m.ok);
|
|
2870
3423
|
if (conflicts.length > 0) {
|
|
2871
|
-
console.log(
|
|
3424
|
+
console.log(pc9.red("\n" + t("swarm.conflictsHeader", { n: conflicts.length })));
|
|
2872
3425
|
for (const m of conflicts) {
|
|
2873
|
-
console.log(
|
|
3426
|
+
console.log(pc9.red(` ${m.branch}: ${m.conflicts.join(", ")}`));
|
|
2874
3427
|
}
|
|
2875
3428
|
} else {
|
|
2876
|
-
console.log(
|
|
3429
|
+
console.log(pc9.green("\n" + t("swarm.allMerged")));
|
|
2877
3430
|
}
|
|
2878
3431
|
}
|
|
2879
|
-
function renderSwarmEvents() {
|
|
2880
|
-
return {
|
|
2881
|
-
onDecomposed(subtasks) {
|
|
2882
|
-
console.log(pc8.bold(t("swarm.decomposed", { n: subtasks.length })));
|
|
2883
|
-
for (const s of subtasks) console.log(pc8.dim(` ${s.id}: ${s.title}`));
|
|
2884
|
-
console.log("");
|
|
2885
|
-
},
|
|
2886
|
-
onWorkerStart(subtask, agentName) {
|
|
2887
|
-
console.log(pc8.cyan(t("swarm.workerStart", { id: subtask.id, agent: agentName })));
|
|
2888
|
-
},
|
|
2889
|
-
onWorkerDone(o) {
|
|
2890
|
-
const head = o.finished ? pc8.green(t("swarm.workerDone", { id: o.subtask.id })) : pc8.yellow(t("swarm.workerStopped", { id: o.subtask.id }));
|
|
2891
|
-
const changes = o.committed ? t("swarm.changesCommitted") : t("swarm.noChanges");
|
|
2892
|
-
console.log(head + pc8.dim(t("swarm.workerMeta", { steps: o.steps, changes })));
|
|
2893
|
-
},
|
|
2894
|
-
onMerge(m) {
|
|
2895
|
-
console.log(m.ok ? pc8.dim(t("swarm.merged", { branch: m.branch })) : pc8.red(t("swarm.mergeConflict", { branch: m.branch })));
|
|
2896
|
-
}
|
|
2897
|
-
};
|
|
2898
|
-
}
|
|
2899
3432
|
|
|
2900
3433
|
// src/cli/commands/models.ts
|
|
2901
|
-
import
|
|
3434
|
+
import pc10 from "picocolors";
|
|
2902
3435
|
import * as p3 from "@clack/prompts";
|
|
2903
3436
|
async function models(opts) {
|
|
2904
3437
|
const apiKey = await resolveOpenRouterKey();
|
|
@@ -2907,9 +3440,9 @@ async function models(opts) {
|
|
|
2907
3440
|
let all;
|
|
2908
3441
|
try {
|
|
2909
3442
|
all = await listOpenRouterModels(apiKey);
|
|
2910
|
-
spin.stop(
|
|
3443
|
+
spin.stop(pc10.green("\u2713 OpenRouter"));
|
|
2911
3444
|
} catch (err) {
|
|
2912
|
-
spin.stop(
|
|
3445
|
+
spin.stop(pc10.red(t("models.fetchError", { msg: err.message })), 2);
|
|
2913
3446
|
return;
|
|
2914
3447
|
}
|
|
2915
3448
|
const filtered = filterModels(all, {
|
|
@@ -2923,26 +3456,26 @@ async function models(opts) {
|
|
|
2923
3456
|
printModelsTable(filtered, limit, all.length);
|
|
2924
3457
|
}
|
|
2925
3458
|
function printModelsTable(models2, limit, total) {
|
|
2926
|
-
console.log(
|
|
3459
|
+
console.log(pc10.dim(t("models.legend")));
|
|
2927
3460
|
if (models2.length === 0) {
|
|
2928
|
-
console.log(
|
|
3461
|
+
console.log(pc10.yellow(t("models.none")));
|
|
2929
3462
|
return;
|
|
2930
3463
|
}
|
|
2931
3464
|
const rows = models2.slice(0, limit);
|
|
2932
3465
|
console.log(
|
|
2933
|
-
" " +
|
|
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"))
|
|
2934
3467
|
);
|
|
2935
3468
|
for (const m of rows) {
|
|
2936
3469
|
console.log(" " + modelRow(m));
|
|
2937
3470
|
}
|
|
2938
|
-
console.log(
|
|
3471
|
+
console.log(pc10.dim("\n" + t("models.shown", { shown: rows.length, total })));
|
|
2939
3472
|
}
|
|
2940
3473
|
function modelRow(m) {
|
|
2941
|
-
const tools = m.supportsTools ?
|
|
3474
|
+
const tools = m.supportsTools ? pc10.green("\u{1F6E0}".padEnd(5)) : pc10.dim("\u2014".padEnd(5));
|
|
2942
3475
|
const price = `${fmtPrice(m.promptPrice)}/${fmtPrice(m.completionPrice)}`;
|
|
2943
|
-
const priceColored = (m.free ?
|
|
2944
|
-
const ctx =
|
|
2945
|
-
return `${tools} ${priceColored}${ctx}${
|
|
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)}`;
|
|
2946
3479
|
}
|
|
2947
3480
|
async function resolveOpenRouterKey() {
|
|
2948
3481
|
if (process.env.OPENROUTER_API_KEY) return process.env.OPENROUTER_API_KEY;
|
|
@@ -2956,10 +3489,10 @@ async function resolveOpenRouterKey() {
|
|
|
2956
3489
|
}
|
|
2957
3490
|
|
|
2958
3491
|
// src/cli/commands/prd.ts
|
|
2959
|
-
import { writeFile as
|
|
3492
|
+
import { writeFile as writeFile5, readFile as readFile6 } from "fs/promises";
|
|
2960
3493
|
import { execFile } from "child_process";
|
|
2961
3494
|
import { promisify as promisify2 } from "util";
|
|
2962
|
-
import
|
|
3495
|
+
import pc11 from "picocolors";
|
|
2963
3496
|
|
|
2964
3497
|
// src/core/agent/prd.ts
|
|
2965
3498
|
var SYSTEM = [
|
|
@@ -2991,12 +3524,18 @@ ${comments}` : "",
|
|
|
2991
3524
|
"## Riscos e alternativas"
|
|
2992
3525
|
].join("\n");
|
|
2993
3526
|
}
|
|
2994
|
-
async function generatePrd(issue, provider) {
|
|
3527
|
+
async function generatePrd(issue, provider, projectContext) {
|
|
3528
|
+
const messages = [{ role: "system", content: SYSTEM }];
|
|
3529
|
+
if (projectContext) {
|
|
3530
|
+
messages.push({
|
|
3531
|
+
role: "system",
|
|
3532
|
+
content: `Project context (for grounding; do not restate verbatim):
|
|
3533
|
+
${projectContext}`
|
|
3534
|
+
});
|
|
3535
|
+
}
|
|
3536
|
+
messages.push({ role: "user", content: buildPrdPrompt(issue) });
|
|
2995
3537
|
const res = await provider.chat({
|
|
2996
|
-
messages
|
|
2997
|
-
{ role: "system", content: SYSTEM },
|
|
2998
|
-
{ role: "user", content: buildPrdPrompt(issue) }
|
|
2999
|
-
],
|
|
3538
|
+
messages,
|
|
3000
3539
|
params: { maxTokens: 2e3, temperature: 0.2 }
|
|
3001
3540
|
});
|
|
3002
3541
|
const text2 = res.content.trim();
|
|
@@ -3041,6 +3580,23 @@ async function withRetry(fn, opts = {}) {
|
|
|
3041
3580
|
}
|
|
3042
3581
|
|
|
3043
3582
|
// src/cli/commands/cli-io.ts
|
|
3583
|
+
import { readFileSync, existsSync as existsSync2 } from "fs";
|
|
3584
|
+
import { resolve as resolve7 } from "path";
|
|
3585
|
+
var GUIDE_MAX = 12e3;
|
|
3586
|
+
function readProjectGuide(files) {
|
|
3587
|
+
const parts = [];
|
|
3588
|
+
for (const file of files) {
|
|
3589
|
+
try {
|
|
3590
|
+
const path = resolve7(process.cwd(), file);
|
|
3591
|
+
if (existsSync2(path)) parts.push(`# ${file}
|
|
3592
|
+
${readFileSync(path, "utf8").trim()}`);
|
|
3593
|
+
} catch {
|
|
3594
|
+
}
|
|
3595
|
+
}
|
|
3596
|
+
if (parts.length === 0) return void 0;
|
|
3597
|
+
const joined = parts.join("\n\n");
|
|
3598
|
+
return joined.length > GUIDE_MAX ? joined.slice(0, GUIDE_MAX) + "\n\u2026(truncated)" : joined;
|
|
3599
|
+
}
|
|
3044
3600
|
function numericRef(ref) {
|
|
3045
3601
|
const num = ref.replace(/^#/, "");
|
|
3046
3602
|
if (!/^\d+$/.test(num)) throw new Error(t("cli.invalidRef", { ref }));
|
|
@@ -3061,17 +3617,18 @@ var exec2 = promisify2(execFile);
|
|
|
3061
3617
|
async function prd(issueRef, opts) {
|
|
3062
3618
|
const issue = await loadIssue(issueRef, opts.input);
|
|
3063
3619
|
const { provider } = resolveFreeProvider(opts.model ?? DEFAULT_PRD_MODEL);
|
|
3064
|
-
const
|
|
3620
|
+
const guide = readProjectGuide(["context.md"]);
|
|
3621
|
+
const markdown = await withRetry(() => generatePrd(issue, provider, guide));
|
|
3065
3622
|
if (opts.out) {
|
|
3066
|
-
await
|
|
3067
|
-
console.error(
|
|
3623
|
+
await writeFile5(opts.out, markdown + "\n", "utf8");
|
|
3624
|
+
console.error(pc11.green(t("prd.wrote", { path: opts.out })));
|
|
3068
3625
|
} else {
|
|
3069
3626
|
process.stdout.write(markdown + "\n");
|
|
3070
3627
|
}
|
|
3071
3628
|
}
|
|
3072
3629
|
async function loadIssue(issueRef, input) {
|
|
3073
3630
|
if (input) {
|
|
3074
|
-
const raw = input === "-" ? await readStdin() : await
|
|
3631
|
+
const raw = input === "-" ? await readStdin() : await readFile6(input, "utf8");
|
|
3075
3632
|
return normalize2(JSON.parse(stripBom(raw)));
|
|
3076
3633
|
}
|
|
3077
3634
|
const num = numericRef(issueRef);
|
|
@@ -3090,10 +3647,10 @@ function normalize2(raw) {
|
|
|
3090
3647
|
}
|
|
3091
3648
|
|
|
3092
3649
|
// src/cli/commands/review.ts
|
|
3093
|
-
import { writeFile as
|
|
3650
|
+
import { writeFile as writeFile6, readFile as readFile7 } from "fs/promises";
|
|
3094
3651
|
import { execFile as execFile2 } from "child_process";
|
|
3095
3652
|
import { promisify as promisify3 } from "util";
|
|
3096
|
-
import
|
|
3653
|
+
import pc12 from "picocolors";
|
|
3097
3654
|
|
|
3098
3655
|
// src/core/agent/review.ts
|
|
3099
3656
|
var MAX_DIFF_CHARS = Number(process.env.POLYPUS_MAX_DIFF_CHARS) || 6e4;
|
|
@@ -3129,13 +3686,19 @@ function buildReviewPrompt(diff, meta) {
|
|
|
3129
3686
|
"Liste os achados agrupados por severidade (\u{1F534} bug, \u{1F7E1} aten\xE7\xE3o, \u{1F7E2} sugest\xE3o)."
|
|
3130
3687
|
].join("\n");
|
|
3131
3688
|
}
|
|
3132
|
-
async function reviewDiff(diff, meta, provider) {
|
|
3689
|
+
async function reviewDiff(diff, meta, provider, projectGuide) {
|
|
3133
3690
|
if (!diff.trim()) return "_Sem altera\xE7\xF5es no diff para revisar._";
|
|
3691
|
+
const messages = [{ role: "system", content: SYSTEM2 }];
|
|
3692
|
+
if (projectGuide) {
|
|
3693
|
+
messages.push({
|
|
3694
|
+
role: "system",
|
|
3695
|
+
content: `Project context and conventions to review against:
|
|
3696
|
+
${projectGuide}`
|
|
3697
|
+
});
|
|
3698
|
+
}
|
|
3699
|
+
messages.push({ role: "user", content: buildReviewPrompt(diff, meta) });
|
|
3134
3700
|
const res = await provider.chat({
|
|
3135
|
-
messages
|
|
3136
|
-
{ role: "system", content: SYSTEM2 },
|
|
3137
|
-
{ role: "user", content: buildReviewPrompt(diff, meta) }
|
|
3138
|
-
],
|
|
3701
|
+
messages,
|
|
3139
3702
|
params: { maxTokens: 1500, temperature: 0.2 }
|
|
3140
3703
|
});
|
|
3141
3704
|
const text2 = res.content.trim();
|
|
@@ -3150,16 +3713,17 @@ async function review(prRef, opts) {
|
|
|
3150
3713
|
const diff = await loadDiff(num, opts.input);
|
|
3151
3714
|
const meta = await loadMeta(num, opts.input);
|
|
3152
3715
|
const { provider } = resolveFreeProvider(opts.model ?? DEFAULT_REVIEW_MODEL);
|
|
3153
|
-
const
|
|
3716
|
+
const guide = readProjectGuide(["rules.md", "context.md"]);
|
|
3717
|
+
const markdown = await withRetry(() => reviewDiff(diff, meta, provider, guide));
|
|
3154
3718
|
if (opts.out) {
|
|
3155
|
-
await
|
|
3156
|
-
console.error(
|
|
3719
|
+
await writeFile6(opts.out, markdown + "\n", "utf8");
|
|
3720
|
+
console.error(pc12.green(t("review.wrote", { path: opts.out })));
|
|
3157
3721
|
} else {
|
|
3158
3722
|
process.stdout.write(markdown + "\n");
|
|
3159
3723
|
}
|
|
3160
3724
|
}
|
|
3161
3725
|
async function loadDiff(num, input) {
|
|
3162
|
-
if (input) return input === "-" ? readStdin() :
|
|
3726
|
+
if (input) return input === "-" ? readStdin() : readFile7(input, "utf8");
|
|
3163
3727
|
const { stdout: stdout2 } = await exec3("gh", ["pr", "diff", num]);
|
|
3164
3728
|
return stdout2;
|
|
3165
3729
|
}
|
|
@@ -3171,16 +3735,16 @@ async function loadMeta(num, input) {
|
|
|
3171
3735
|
}
|
|
3172
3736
|
|
|
3173
3737
|
// src/cli/index.ts
|
|
3174
|
-
import { join as
|
|
3738
|
+
import { join as join5 } from "path";
|
|
3175
3739
|
|
|
3176
3740
|
// src/core/config/dotenv.ts
|
|
3177
|
-
import { existsSync as
|
|
3741
|
+
import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
|
|
3178
3742
|
function loadDotenv(paths) {
|
|
3179
3743
|
for (const path of paths) {
|
|
3180
|
-
if (!
|
|
3744
|
+
if (!existsSync3(path)) continue;
|
|
3181
3745
|
let text2;
|
|
3182
3746
|
try {
|
|
3183
|
-
text2 =
|
|
3747
|
+
text2 = readFileSync2(path, "utf8");
|
|
3184
3748
|
} catch {
|
|
3185
3749
|
continue;
|
|
3186
3750
|
}
|
|
@@ -3200,12 +3764,11 @@ function loadDotenv(paths) {
|
|
|
3200
3764
|
}
|
|
3201
3765
|
|
|
3202
3766
|
// src/cli/index.ts
|
|
3203
|
-
var { version: pkgVersion } = createRequire(import.meta.url)("../package.json");
|
|
3204
3767
|
async function launchInteractive() {
|
|
3205
3768
|
const config = await loadConfig();
|
|
3206
3769
|
if (config.agents.length === 0) {
|
|
3207
3770
|
console.log(banner());
|
|
3208
|
-
console.log(" " +
|
|
3771
|
+
console.log(" " + pc13.yellow(t("welcome.firstRun")) + "\n");
|
|
3209
3772
|
await setup();
|
|
3210
3773
|
}
|
|
3211
3774
|
await run(void 0, {});
|
|
@@ -3226,8 +3789,9 @@ async function resolveLocale() {
|
|
|
3226
3789
|
}
|
|
3227
3790
|
function buildProgram() {
|
|
3228
3791
|
const program = new Command();
|
|
3229
|
-
program.name("polypus").description(t("cli.description")).version(
|
|
3792
|
+
program.name("polypus").description(t("cli.description")).version(VERSION).option("--lang <locale>", t("cli.opt.lang")).action(() => launchInteractive());
|
|
3230
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));
|
|
3231
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));
|
|
3232
3796
|
program.command("remove-agent").argument("<name>", t("cli.arg.removeAgentName")).description(t("cli.cmd.removeAgent")).action((name) => removeAgent(name));
|
|
3233
3797
|
program.command("list-agents").alias("agents").description(t("cli.cmd.listAgents")).action(() => listAgents());
|
|
@@ -3240,11 +3804,11 @@ function buildProgram() {
|
|
|
3240
3804
|
}
|
|
3241
3805
|
async function main() {
|
|
3242
3806
|
try {
|
|
3243
|
-
loadDotenv([
|
|
3807
|
+
loadDotenv([join5(configDir(), ".env"), join5(process.cwd(), ".env")]);
|
|
3244
3808
|
await resolveLocale();
|
|
3245
3809
|
await buildProgram().parseAsync(process.argv);
|
|
3246
3810
|
} catch (err) {
|
|
3247
|
-
console.error(
|
|
3811
|
+
console.error(pc13.red(`\u2717 ${err.message}`));
|
|
3248
3812
|
process.exitCode = 1;
|
|
3249
3813
|
}
|
|
3250
3814
|
}
|