@mcp-graph-workflow/agent-graph-flow 0.8.0 → 0.9.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/cli/index.js CHANGED
@@ -1,10 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
  import * as path17 from 'path';
3
- import path17__default, { join, isAbsolute, dirname, resolve, relative } from 'path';
3
+ import path17__default, { join, basename, isAbsolute, dirname, resolve, relative } from 'path';
4
4
  import { fileURLToPath } from 'url';
5
5
  import { z } from 'zod/v4';
6
6
  import * as fs from 'fs';
7
- import fs__default, { existsSync, rmSync, readFileSync, mkdirSync, writeFileSync, statSync, lstatSync, readdirSync, unlinkSync, copyFileSync } from 'fs';
7
+ import fs__default, { existsSync, rmSync, writeFileSync, readFileSync, mkdirSync, statSync, lstatSync, readdirSync, unlinkSync, copyFileSync } from 'fs';
8
8
  import { AsyncLocalStorage } from 'async_hooks';
9
9
  import Database2 from 'better-sqlite3';
10
10
  import { randomUUID, randomBytes, createHash } from 'crypto';
@@ -7116,8 +7116,8 @@ var init_implementation_executor = __esm({
7116
7116
  };
7117
7117
  defaultRunner = (command, cwd) => {
7118
7118
  try {
7119
- const output18 = execSync(command, { cwd, encoding: "utf8", stdio: "pipe" });
7120
- return { exitCode: 0, output: output18 };
7119
+ const output20 = execSync(command, { cwd, encoding: "utf8", stdio: "pipe" });
7120
+ return { exitCode: 0, output: output20 };
7121
7121
  } catch (err) {
7122
7122
  const e = err;
7123
7123
  return {
@@ -9261,7 +9261,7 @@ function InteractiveApp({ dashboard, port, asyncPort, liveRunner, skillCommands
9261
9261
  process.stdout.isTTY ? "banner" : "dashboard"
9262
9262
  );
9263
9263
  const [input, setInput] = useState("");
9264
- const [log48, setLog] = useState([]);
9264
+ const [log50, setLog] = useState([]);
9265
9265
  const [running, setRunning] = useState(false);
9266
9266
  const [elapsedMs, setElapsedMs] = useState(0);
9267
9267
  const [showHelp, setShowHelp] = useState(false);
@@ -9346,7 +9346,7 @@ ${skill.body}` : `Skill n\xE3o encontrada: ${parsed.cmd}`);
9346
9346
  }
9347
9347
  return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
9348
9348
  /* @__PURE__ */ jsx(App, { model: dashboard }),
9349
- log48.length > 0 && /* @__PURE__ */ jsx(Box, { flexDirection: "column", marginTop: 1, borderStyle: "single", paddingX: 1, children: log48.map((line, i) => /* @__PURE__ */ jsx(Text, { children: line }, i)) }),
9349
+ log50.length > 0 && /* @__PURE__ */ jsx(Box, { flexDirection: "column", marginTop: 1, borderStyle: "single", paddingX: 1, children: log50.map((line, i) => /* @__PURE__ */ jsx(Text, { children: line }, i)) }),
9350
9350
  showHelp && /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginTop: 1, borderStyle: "round", paddingX: 1, children: [
9351
9351
  /* @__PURE__ */ jsx(Text, { bold: true, children: "Comandos:" }),
9352
9352
  COMMANDS.map((c) => /* @__PURE__ */ jsxs(Text, { children: [
@@ -14746,13 +14746,13 @@ function pruneOrphanWorktrees(options) {
14746
14746
  }
14747
14747
  }
14748
14748
  try {
14749
- const output18 = execSync("git worktree prune --verbose", execOpts).toString();
14749
+ const output20 = execSync("git worktree prune --verbose", execOpts).toString();
14750
14750
  if (reapedBranches > 0 || reapedWorktrees > 0) {
14751
14751
  log46.info("shadow-branch:prune-ok", { reapedBranches, reapedWorktrees, ttlMs });
14752
14752
  } else {
14753
- log46.debug("shadow-branch:prune-ok", { reapedBranches, reapedWorktrees, output: output18 });
14753
+ log46.debug("shadow-branch:prune-ok", { reapedBranches, reapedWorktrees, output: output20 });
14754
14754
  }
14755
- return { pruned: true, reapedBranches, reapedWorktrees, output: output18 };
14755
+ return { pruned: true, reapedBranches, reapedWorktrees, output: output20 };
14756
14756
  } catch (err) {
14757
14757
  const error = String(err);
14758
14758
  log46.debug("shadow-branch:prune-failed", { error });
@@ -14998,6 +14998,470 @@ ${listPrinciples().length} princ\xEDpios \xB7 use 'principles show <id>' para de
14998
14998
  return cmd;
14999
14999
  }
15000
15000
 
15001
+ // src/cli/commands/generate-prd-cmd.ts
15002
+ init_esm_shims();
15003
+ init_resolve_adapter();
15004
+ init_model_client();
15005
+
15006
+ // src/core/prd/generate-prd.ts
15007
+ init_esm_shims();
15008
+ init_errors();
15009
+ function buildPrdPrompt(description) {
15010
+ return [
15011
+ `Voc\xEA \xE9 um Product Manager s\xEAnior. Escreva um PRD em Markdown para o produto descrito abaixo,`,
15012
+ `pronto para ser importado num grafo de execu\xE7\xE3o (epics \u2192 tasks \u2192 crit\xE9rios de aceita\xE7\xE3o).`,
15013
+ "",
15014
+ `Descri\xE7\xE3o do produto:`,
15015
+ description.trim(),
15016
+ "",
15017
+ `Estruture com EXATAMENTE estas se\xE7\xF5es (Markdown, headings ##):`,
15018
+ `## Objetivo`,
15019
+ `## Usu\xE1rios`,
15020
+ `## Requisitos (lista de epics; cada um com um id curto)`,
15021
+ `## Crit\xE9rios de Aceita\xE7\xE3o (Given/When/Then por requisito \u2014 test\xE1veis)`,
15022
+ `## Constraints (t\xE9cnicas e de neg\xF3cio)`,
15023
+ `## Riscos`,
15024
+ "",
15025
+ `Seja espec\xEDfico e conciso. Cada AC deve ser verific\xE1vel por um teste automatizado.`
15026
+ ].join("\n");
15027
+ }
15028
+ async function generatePrd(description, deps) {
15029
+ if (description.trim().length === 0) {
15030
+ throw new ValidationError("Descri\xE7\xE3o vazia \u2014 forne\xE7a o que deve ser constru\xEDdo para gerar o PRD.", []);
15031
+ }
15032
+ return deps.generate(buildPrdPrompt(description));
15033
+ }
15034
+
15035
+ // src/cli/commands/generate-prd-cmd.ts
15036
+ init_extract();
15037
+ init_prd_to_graph();
15038
+ function output18(msg) {
15039
+ process.stdout.write(msg + "\n");
15040
+ }
15041
+ function generatePrdCommand() {
15042
+ return new Command("generate-prd").description("Gera um PRD.md a partir de uma descri\xE7\xE3o (via modelo); --import j\xE1 vira grafo").argument("<descricao>", "O que deve ser constru\xEDdo").option("-d, --dir <dir>", "Diret\xF3rio do projeto", process.cwd()).option("-o, --out <file>", "Arquivo de sa\xEDda do PRD", "PRD.md").option("--import", "Importa o PRD gerado para o grafo de execu\xE7\xE3o", false).option("--model <id>", "Modelo fixo; 'auto' usa o tier-router", "auto").action(async (descricao, opts) => {
15043
+ const config = opts.model === "auto" ? { mode: "auto" } : { mode: "pinned", modelId: opts.model };
15044
+ const resolved = resolveModelAdapter();
15045
+ const client = new TieredModelClient(resolved.adapter, config);
15046
+ output18(`[generate-prd] ${client.modelFor("plan")} via ${resolved.kind === "api" ? "API HTTP" : "CLI"} \u2192 gerando PRD\u2026`);
15047
+ const md = await generatePrd(descricao, {
15048
+ generate: async (prompt) => (await client.run("plan", prompt)).text
15049
+ });
15050
+ const outPath = join(opts.dir, opts.out);
15051
+ writeFileSync(outPath, md, "utf8");
15052
+ output18(`\u2713 PRD gravado em ${outPath} (${md.length} chars)`);
15053
+ if (opts.import) {
15054
+ const store2 = openStoreOrFail(opts.dir);
15055
+ try {
15056
+ if (!store2.getProject()) store2.initProject(basename(opts.dir));
15057
+ const entities = extractEntities(md);
15058
+ const graph = convertToGraph(entities, outPath);
15059
+ store2.bulkInsert(graph.nodes, graph.edges);
15060
+ store2.recordImport?.(outPath, graph.nodes.length, graph.edges.length);
15061
+ output18(`\u2713 Importado: ${graph.nodes.length} n\xF3s, ${graph.edges.length} arestas`);
15062
+ } finally {
15063
+ store2.close();
15064
+ }
15065
+ } else {
15066
+ output18(`Dica: rode 'import-prd ${opts.out}' ou repita com --import.`);
15067
+ }
15068
+ });
15069
+ }
15070
+
15071
+ // src/cli/commands/build-cmd.ts
15072
+ init_esm_shims();
15073
+
15074
+ // src/core/orchestrator/run-delivery.ts
15075
+ init_esm_shims();
15076
+
15077
+ // src/core/orchestrator/orchestrator.ts
15078
+ init_esm_shims();
15079
+ function nextDeliveryAction(state) {
15080
+ if (state.totalNodes === 0 || !state.hasRequirements) {
15081
+ return { action: "import_prd", reason: "Grafo sem PRD \u2014 importe requisitos primeiro." };
15082
+ }
15083
+ if (state.oversizedCount > 0) {
15084
+ return {
15085
+ action: "decompose",
15086
+ reason: `${state.oversizedCount} epic(s)/task(s) L/XL sem subtasks \u2014 decompor antes de implementar.`
15087
+ };
15088
+ }
15089
+ if (state.readyTasks > 0 || state.inProgress > 0) {
15090
+ return {
15091
+ action: "implement",
15092
+ reason: `${state.readyTasks} pronta(s), ${state.inProgress} em andamento \u2014 implementar (TDD).`
15093
+ };
15094
+ }
15095
+ if (state.doneRatio >= 1) {
15096
+ return { action: "done", reason: "Todas as tasks conclu\xEDdas \u2014 entrega completa." };
15097
+ }
15098
+ if (state.allBlocked) {
15099
+ return { action: "escalate", reason: "Todas as tasks restantes est\xE3o bloqueadas \u2014 interven\xE7\xE3o humana." };
15100
+ }
15101
+ return { action: "escalate", reason: "Sem tasks acion\xE1veis \u2014 escalando para o humano." };
15102
+ }
15103
+
15104
+ // src/core/orchestrator/run-delivery.ts
15105
+ async function runDelivery(getState, handlers, options) {
15106
+ const actions = [];
15107
+ for (let step = 0; step < options.maxSteps; step++) {
15108
+ if (options.signal?.aborted === true) {
15109
+ return { steps: step, stopped: "aborted", actions };
15110
+ }
15111
+ const decision = nextDeliveryAction(getState());
15112
+ options.onStep?.(decision);
15113
+ if (decision.action === "done") {
15114
+ return { steps: step, stopped: "done", actions };
15115
+ }
15116
+ if (decision.action === "escalate") {
15117
+ return { steps: step, stopped: "escalation", actions };
15118
+ }
15119
+ actions.push(decision.action);
15120
+ if (decision.action === "import_prd") await handlers.importPrd();
15121
+ else if (decision.action === "decompose") await handlers.decompose();
15122
+ else await handlers.implement();
15123
+ }
15124
+ return { steps: options.maxSteps, stopped: "budget", actions };
15125
+ }
15126
+
15127
+ // src/core/orchestrator/delivery-state.ts
15128
+ init_esm_shims();
15129
+ init_next_task();
15130
+ init_decompose();
15131
+ var TASK_TYPES = /* @__PURE__ */ new Set(["task", "subtask"]);
15132
+ var REQUIREMENT_TYPES = /* @__PURE__ */ new Set(["epic", "requirement"]);
15133
+ var EMPTY_STATE = {
15134
+ totalNodes: 0,
15135
+ hasRequirements: false,
15136
+ oversizedCount: 0,
15137
+ readyTasks: 0,
15138
+ inProgress: 0,
15139
+ allBlocked: false,
15140
+ doneRatio: 0
15141
+ };
15142
+ function deriveDeliveryState(store2) {
15143
+ if (!store2.getProject()) return { ...EMPTY_STATE };
15144
+ const stats = store2.getStats();
15145
+ const doc = store2.toGraphDocument();
15146
+ const hasRequirements = doc.nodes.some((n) => REQUIREMENT_TYPES.has(n.type));
15147
+ const oversizedCount = detectLargeTasks(doc).length;
15148
+ const tasks = doc.nodes.filter((n) => TASK_TYPES.has(n.type));
15149
+ const done = tasks.filter((t) => t.status === "done").length;
15150
+ const inProgress = tasks.filter((t) => t.status === "in_progress").length;
15151
+ const doneRatio = tasks.length > 0 ? done / tasks.length : 0;
15152
+ const next = findNextTask(doc);
15153
+ const actionable = next !== null && !("warning" in next);
15154
+ const allBlocked = next !== null && "warning" in next && next.warning === "all_tasks_blocked";
15155
+ return {
15156
+ totalNodes: stats.totalNodes,
15157
+ hasRequirements,
15158
+ oversizedCount,
15159
+ readyTasks: actionable ? 1 : 0,
15160
+ inProgress,
15161
+ allBlocked,
15162
+ doneRatio
15163
+ };
15164
+ }
15165
+
15166
+ // src/cli/commands/build-cmd.ts
15167
+ init_extract();
15168
+ init_prd_to_graph();
15169
+
15170
+ // src/core/planner/auto-decompose.ts
15171
+ init_esm_shims();
15172
+ init_xp_sizing();
15173
+ init_id();
15174
+
15175
+ // src/core/planner/smart-decompose.ts
15176
+ init_esm_shims();
15177
+ init_id();
15178
+ init_logger();
15179
+
15180
+ // src/core/planner/invest-validator.ts
15181
+ init_esm_shims();
15182
+
15183
+ // src/core/planner/smart-decompose.ts
15184
+ var log48 = createLogger({ layer: "core", source: "smart-decompose.ts" });
15185
+ var INTEGRATION_KEYWORDS2 = [
15186
+ "api",
15187
+ "endpoint",
15188
+ "database",
15189
+ "db",
15190
+ "persiste",
15191
+ "persists",
15192
+ "saves",
15193
+ "sync",
15194
+ "indexa",
15195
+ "indexes",
15196
+ "fetch",
15197
+ "request",
15198
+ "response",
15199
+ "query",
15200
+ "http",
15201
+ "rest",
15202
+ "graphql",
15203
+ "grpc",
15204
+ "webhook"
15205
+ ];
15206
+ var E2E_KEYWORDS2 = [
15207
+ "page",
15208
+ "navega",
15209
+ "navigates",
15210
+ "click",
15211
+ "clicks",
15212
+ "form",
15213
+ "browser",
15214
+ "redirect",
15215
+ "ui",
15216
+ "dashboard",
15217
+ "tab",
15218
+ "button",
15219
+ "modal",
15220
+ "toast",
15221
+ "screen",
15222
+ "display",
15223
+ "render",
15224
+ "shows",
15225
+ "visible"
15226
+ ];
15227
+ function inferTestType2(acText) {
15228
+ const lower = acText.toLowerCase();
15229
+ if (E2E_KEYWORDS2.some((kw) => lower.includes(kw))) return "e2e";
15230
+ if (INTEGRATION_KEYWORDS2.some((kw) => lower.includes(kw))) return "integration";
15231
+ return "unit";
15232
+ }
15233
+ function acToTitle(ac, index) {
15234
+ const truncated = ac.length > 60 ? ac.slice(0, 60).replace(/\s\S*$/, "...") : ac;
15235
+ return `Subtask ${index + 1}: ${truncated}`;
15236
+ }
15237
+ function estimateFromTestType(testType) {
15238
+ switch (testType) {
15239
+ case "unit":
15240
+ return 30;
15241
+ case "integration":
15242
+ return 60;
15243
+ case "e2e":
15244
+ return 90;
15245
+ }
15246
+ }
15247
+ function smartDecompose(store2, nodeId) {
15248
+ const node = store2.getNodeById(nodeId);
15249
+ if (!node) {
15250
+ log48.warn("smart-decompose:node_not_found", { nodeId });
15251
+ return null;
15252
+ }
15253
+ const doc = store2.toGraphDocument();
15254
+ const acChildNodes = doc.nodes.filter(
15255
+ (n) => n.type === "acceptance_criteria" && n.parentId === nodeId
15256
+ );
15257
+ const acTexts = [
15258
+ ...node.acceptanceCriteria ?? [],
15259
+ ...acChildNodes.map((n) => n.title)
15260
+ ];
15261
+ if (acTexts.length === 0) {
15262
+ log48.info("smart-decompose:no_ac", { nodeId });
15263
+ return null;
15264
+ }
15265
+ const subtaskIds = [];
15266
+ const subtasks = acTexts.map((ac, i) => {
15267
+ subtaskIds.push(generateId("sub"));
15268
+ const testType = inferTestType2(ac);
15269
+ return {
15270
+ title: acToTitle(ac, i),
15271
+ type: "subtask",
15272
+ acceptanceCriteria: [ac],
15273
+ estimateMinutes: estimateFromTestType(testType),
15274
+ suggestedTestType: testType
15275
+ };
15276
+ });
15277
+ const edges = [];
15278
+ for (let i = 1; i < subtaskIds.length; i++) {
15279
+ edges.push({
15280
+ from: subtaskIds[i],
15281
+ to: subtaskIds[i - 1],
15282
+ relation: "depends_on"
15283
+ });
15284
+ }
15285
+ log48.info("smart-decompose:ok", {
15286
+ nodeId,
15287
+ subtasks: subtasks.length,
15288
+ edges: edges.length,
15289
+ testTypes: subtasks.map((s) => s.suggestedTestType)
15290
+ });
15291
+ return {
15292
+ parentId: nodeId,
15293
+ subtasks,
15294
+ edges,
15295
+ rationale: `Decomposed "${node.title}" into ${subtasks.length} subtasks (1 per AC). Test types: ${subtasks.map((s) => s.suggestedTestType).join(", ")}.`
15296
+ };
15297
+ }
15298
+
15299
+ // src/core/planner/auto-decompose.ts
15300
+ init_logger();
15301
+ init_time();
15302
+ var log49 = createLogger({ layer: "core", source: "auto-decompose.ts" });
15303
+ function persistDecomposition(store2, result) {
15304
+ const createdNodeIds = [];
15305
+ const provisionalToReal = /* @__PURE__ */ new Map();
15306
+ const plannerIds = Array.from(
15307
+ new Set(result.edges.flatMap((e) => [e.from, e.to]))
15308
+ );
15309
+ result.subtasks.forEach((sub, index) => {
15310
+ const realId = generateId("sub");
15311
+ createdNodeIds.push(realId);
15312
+ if (plannerIds[index]) provisionalToReal.set(plannerIds[index], realId);
15313
+ const timestamp = now();
15314
+ const node = {
15315
+ id: realId,
15316
+ type: "subtask",
15317
+ title: sub.title,
15318
+ status: "backlog",
15319
+ priority: 3,
15320
+ parentId: result.parentId,
15321
+ acceptanceCriteria: sub.acceptanceCriteria,
15322
+ estimateMinutes: sub.estimateMinutes,
15323
+ createdAt: timestamp,
15324
+ updatedAt: timestamp
15325
+ };
15326
+ store2.insertNode(node);
15327
+ });
15328
+ let createdEdgeCount = 0;
15329
+ for (const edge of result.edges) {
15330
+ const from = provisionalToReal.get(edge.from);
15331
+ const to = provisionalToReal.get(edge.to);
15332
+ if (!from || !to) continue;
15333
+ const realEdge = {
15334
+ id: generateId("edge"),
15335
+ from,
15336
+ to,
15337
+ relationType: "depends_on",
15338
+ createdAt: now()
15339
+ };
15340
+ try {
15341
+ store2.insertEdge(realEdge);
15342
+ createdEdgeCount++;
15343
+ } catch (err) {
15344
+ log49.warn("auto-decompose:edge_insert_failed", {
15345
+ from,
15346
+ to,
15347
+ error: err instanceof Error ? err.message : String(err)
15348
+ });
15349
+ }
15350
+ }
15351
+ log49.info("auto-decompose:persisted", {
15352
+ parentId: result.parentId,
15353
+ subtasks: createdNodeIds.length,
15354
+ edges: createdEdgeCount
15355
+ });
15356
+ return { createdNodeIds, createdEdgeCount };
15357
+ }
15358
+ var LARGE_XP_THRESHOLD3 = 4;
15359
+ function autoDecomposeLarge(store2, options = {}) {
15360
+ const maxSubtasks = options.maxSubtasks ?? 8;
15361
+ const minAcs = options.minAcs ?? 2;
15362
+ const doc = store2.toGraphDocument();
15363
+ const decomposed = [];
15364
+ const skipped = [];
15365
+ for (const node of doc.nodes) {
15366
+ if (node.type !== "task") continue;
15367
+ const ord = XP_SIZE_ORDER[node.xpSize ?? ""] ?? 0;
15368
+ if (ord < LARGE_XP_THRESHOLD3) {
15369
+ continue;
15370
+ }
15371
+ const hasChildren = doc.nodes.some((n) => n.parentId === node.id);
15372
+ if (hasChildren) {
15373
+ skipped.push({ parentId: node.id, reason: "has_children" });
15374
+ continue;
15375
+ }
15376
+ const acChildren = doc.nodes.filter(
15377
+ (n) => n.type === "acceptance_criteria" && n.parentId === node.id
15378
+ );
15379
+ const acCount = (node.acceptanceCriteria?.length ?? 0) + acChildren.length;
15380
+ if (acCount < minAcs) {
15381
+ skipped.push({ parentId: node.id, reason: "insufficient_acs" });
15382
+ continue;
15383
+ }
15384
+ if (acCount > maxSubtasks) {
15385
+ skipped.push({ parentId: node.id, reason: "too_many_acs" });
15386
+ continue;
15387
+ }
15388
+ const resultValue = smartDecompose(store2, node.id);
15389
+ if (!resultValue) {
15390
+ skipped.push({ parentId: node.id, reason: "decompose_failed" });
15391
+ continue;
15392
+ }
15393
+ const persisted = persistDecomposition(store2, resultValue);
15394
+ decomposed.push({ parentId: node.id, subtaskIds: persisted.createdNodeIds });
15395
+ }
15396
+ log49.info("auto-decompose:scan_complete", {
15397
+ decomposed: decomposed.length,
15398
+ skipped: skipped.length
15399
+ });
15400
+ return { decomposed, skipped };
15401
+ }
15402
+
15403
+ // src/cli/commands/build-cmd.ts
15404
+ init_autopilot_loop();
15405
+ init_store_port();
15406
+ init_live_implement();
15407
+ init_token_ledger();
15408
+ function output19(msg) {
15409
+ process.stdout.write(msg + "\n");
15410
+ }
15411
+ function importPrdFile(store2, dir, prdPath) {
15412
+ const path22 = prdPath ?? join(dir, "PRD.md");
15413
+ if (!existsSync(path22)) {
15414
+ throw new Error(`PRD n\xE3o encontrado em ${path22}. Rode 'generate-prd "<descri\xE7\xE3o>"' ou passe --prd <arquivo>.`);
15415
+ }
15416
+ if (!store2.getProject()) store2.initProject(basename(dir));
15417
+ const entities = extractEntities(readFileSync(path22, "utf8"));
15418
+ const graph = convertToGraph(entities, path22);
15419
+ store2.bulkInsert(graph.nodes, graph.edges);
15420
+ output19(` \u2713 importado: ${graph.nodes.length} n\xF3s, ${graph.edges.length} arestas`);
15421
+ }
15422
+ function buildCommand() {
15423
+ return new Command("build").description("Orquestra a entrega: PRD \u2192 grafo \u2192 decomp\xF5e \u2192 autopilot \u2192 entrega (m\xE1quina de estados)").option("-d, --dir <dir>", "Diret\xF3rio do projeto", process.cwd()).option("--prd <file>", "Caminho do PRD a importar (default: PRD.md)").option("--max <n>", "Teto de passos do orquestrador (cost-runaway)", "20").option("--live", "Implementa com o modelo real (autopilot --live)", false).option("--test-cmd <cmd>", "Comando de teste no --live", "npm test").action(async (opts) => {
15424
+ const store2 = openStoreOrFail(opts.dir);
15425
+ const ledger = new TokenLedger();
15426
+ try {
15427
+ const maxSteps = Math.max(1, parseInt(opts.max, 10) || 20);
15428
+ const live = opts.live ? buildLiveImplement({ store: store2, dir: opts.dir, testCmd: opts.testCmd, retries: 2, ledger, onLog: output19 }) : null;
15429
+ const handlers = {
15430
+ importPrd: async () => importPrdFile(store2, opts.dir, opts.prd),
15431
+ decompose: async () => {
15432
+ const report2 = autoDecomposeLarge(store2);
15433
+ output19(` \u2713 decompostas ${report2.decomposed.length} epic(s) em subtasks`);
15434
+ },
15435
+ implement: async () => {
15436
+ const port = makeStorePort(store2);
15437
+ const implement = live ? live.implement : void 0;
15438
+ const result = await runAutopilot(port, {
15439
+ maxIterations: 1,
15440
+ implement,
15441
+ onStep: (s) => output19(` ${s.action === "done" ? "\u2713" : s.action === "escalated" ? "\u26A0" : "\u2192"} ${s.title} [${s.action}] ${s.detail}`)
15442
+ });
15443
+ if (result.stopped === "escalation") throw new Error("autopilot escalou \u2014 interven\xE7\xE3o necess\xE1ria");
15444
+ }
15445
+ };
15446
+ output19(`[build] orquestrando (max ${maxSteps} passos${opts.live ? ", --live" : ""})\u2026
15447
+ `);
15448
+ const report = await runDelivery(() => deriveDeliveryState(store2), handlers, {
15449
+ maxSteps,
15450
+ onStep: (s) => output19(`\xBB ${s.action}: ${s.reason}`)
15451
+ });
15452
+ const t = ledger.totals();
15453
+ output19(`
15454
+ Resumo: ${report.steps} passo(s), parou em '${report.stopped}'. Tokens: ${t.total}.`);
15455
+ if (report.stopped === "escalation") process.exitCode = 1;
15456
+ } catch (err) {
15457
+ output19(`erro: ${err instanceof Error ? err.message : String(err)}`);
15458
+ process.exitCode = 1;
15459
+ } finally {
15460
+ store2.close();
15461
+ }
15462
+ });
15463
+ }
15464
+
15001
15465
  // src/cli/index.ts
15002
15466
  var program = new Command();
15003
15467
  program.name("agent-graph-flow").description(PROMISE).version(VERSION, "-v, --version");
@@ -15021,6 +15485,8 @@ program.addCommand(gcCommand());
15021
15485
  program.addCommand(skillCommand());
15022
15486
  program.addCommand(profileCommand());
15023
15487
  program.addCommand(principlesCommand());
15488
+ program.addCommand(generatePrdCommand());
15489
+ program.addCommand(buildCommand());
15024
15490
  function shouldLaunchTui() {
15025
15491
  const noArgs = process.argv.length <= 2;
15026
15492
  const isTty = Boolean(process.stdin.isTTY) && Boolean(process.stdout.isTTY);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mcp-graph-workflow/agent-graph-flow",
3
- "version": "0.8.0",
3
+ "version": "0.9.0",
4
4
  "description": "Agente SWE autônomo, local-first e token-frugal: PRD → grafo de execução persistente, TDD obrigatório, custo de token brutalmente baixo. AGPL v3.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -0,0 +1,53 @@
1
+ ---
2
+ name: orchestrate-delivery
3
+ description: Skill mestre — orquestra a entrega end-to-end (dir vazio → PRD → grafo → autopilot → entrega) com economia brutal de token
4
+ category: any
5
+ phases: [ANALYZE, PLAN, IMPLEMENT, VALIDATE]
6
+ ---
7
+
8
+ # orchestrate-delivery
9
+
10
+ A skill mestre que conduz um produto do zero à entrega, **orquestrando as demais
11
+ skills e comandos** por uma máquina de estados determinística (`nextDeliveryAction`)
12
+ — sem gastar tokens decidindo o que fazer.
13
+
14
+ ## Quando usar
15
+
16
+ - Diretório vazio: "construa um kanban do zero".
17
+ - Quando você quer **um comando** que vá de PRD a software testado.
18
+
19
+ ## Fluxo (determinístico)
20
+
21
+ ```
22
+ estado do grafo → nextDeliveryAction →
23
+ vazio/sem PRD → import_prd (generate-prd → import-prd)
24
+ epics L/XL → decompose (decompose / auto-subtasks)
25
+ tasks prontas → implement (autopilot --live, TDD)
26
+ todas done → done (entrega completa)
27
+ todas bloqueadas → escalate (intervenção humana)
28
+ ```
29
+
30
+ O comando `build` executa este loop (`runDelivery`) com teto de passos
31
+ (cost-runaway) e cancelamento (Esc). `generate-prd "<descrição>"` cria o PRD.
32
+
33
+ ## Economia de token (peça central)
34
+
35
+ - **λ_flow** (`λ_flow = λ_base + α·Φ(t)`) dilui o contexto do grafo por
36
+ esquecimento determinístico.
37
+ - **Reuso determinístico**: tasks já resolvidas reaplicam edits cacheados por
38
+ assinatura — **0 token de modelo** (não re-raciocina).
39
+ - **repo-map ranqueado** (~1k tok) + **feedback compacto** no retry.
40
+ - **exec-policy** barra comandos perigosos; **interrupt** (Esc) corta turnos.
41
+
42
+ ## Disciplina (inegociável)
43
+
44
+ TDD Red→Green→Refactor · WIP=1 · pull-não-push · DoD antes de `done` ·
45
+ decomposição atômica (≤2h) · anti-one-shot · code-detachment. Veja `principles`.
46
+
47
+ ## Comandos
48
+
49
+ ```bash
50
+ mcp-graph-agent generate-prd "um kanban com colunas e cards" --import
51
+ mcp-graph-agent build --live --max 20 # orquestra até entregar
52
+ mcp-graph-agent principles # o credo de engenharia
53
+ ```