@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 +475 -9
- package/package.json +1 -1
- package/src/skills/any/orchestrate-delivery.md +53 -0
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,
|
|
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
|
|
7120
|
-
return { exitCode: 0, output:
|
|
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 [
|
|
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
|
-
|
|
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
|
|
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:
|
|
14753
|
+
log46.debug("shadow-branch:prune-ok", { reapedBranches, reapedWorktrees, output: output20 });
|
|
14754
14754
|
}
|
|
14755
|
-
return { pruned: true, reapedBranches, reapedWorktrees, output:
|
|
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.
|
|
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
|
+
```
|