@mcp-graph-workflow/agent-graph-flow 0.7.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 +651 -17
- 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';
|
|
@@ -2890,6 +2890,28 @@ var init_migrations = __esm({
|
|
|
2890
2890
|
ON flow_metrics(project_id, created_at DESC);
|
|
2891
2891
|
CREATE INDEX IF NOT EXISTS idx_flow_metrics_mode
|
|
2892
2892
|
ON flow_metrics(mode, created_at DESC);
|
|
2893
|
+
`
|
|
2894
|
+
},
|
|
2895
|
+
{
|
|
2896
|
+
version: 97,
|
|
2897
|
+
// Reuso determinístico (Épico R): cache de artefatos (edits gerados e verdes)
|
|
2898
|
+
// por assinatura de task. Quando a mesma assinatura reaparece, o loop reusa os
|
|
2899
|
+
// edits sem chamar o modelo (~0 tokens). Pairs com src/core/reuse/*.
|
|
2900
|
+
description: "artifact_cache \u2014 reuso determin\xEDstico de edits por task-signature",
|
|
2901
|
+
sql: `
|
|
2902
|
+
CREATE TABLE IF NOT EXISTS artifact_cache (
|
|
2903
|
+
id TEXT PRIMARY KEY,
|
|
2904
|
+
signature TEXT NOT NULL,
|
|
2905
|
+
node_id TEXT,
|
|
2906
|
+
applied_edits TEXT NOT NULL,
|
|
2907
|
+
approach_summary TEXT,
|
|
2908
|
+
model TEXT,
|
|
2909
|
+
outcome TEXT NOT NULL,
|
|
2910
|
+
created_at INTEGER NOT NULL,
|
|
2911
|
+
UNIQUE(signature, outcome)
|
|
2912
|
+
);
|
|
2913
|
+
CREATE INDEX IF NOT EXISTS idx_artifact_cache_signature
|
|
2914
|
+
ON artifact_cache(signature, created_at DESC);
|
|
2893
2915
|
`
|
|
2894
2916
|
}
|
|
2895
2917
|
];
|
|
@@ -7094,8 +7116,8 @@ var init_implementation_executor = __esm({
|
|
|
7094
7116
|
};
|
|
7095
7117
|
defaultRunner = (command, cwd) => {
|
|
7096
7118
|
try {
|
|
7097
|
-
const
|
|
7098
|
-
return { exitCode: 0, output:
|
|
7119
|
+
const output20 = execSync(command, { cwd, encoding: "utf8", stdio: "pipe" });
|
|
7120
|
+
return { exitCode: 0, output: output20 };
|
|
7099
7121
|
} catch (err) {
|
|
7100
7122
|
const e = err;
|
|
7101
7123
|
return {
|
|
@@ -7255,9 +7277,11 @@ function defaultSleep(ms) {
|
|
|
7255
7277
|
function buildInitialPrompt(node, opts = {}) {
|
|
7256
7278
|
const repoMapBlock = opts.repoMap && opts.repoMap.length > 0 ? [`Contexto do reposit\xF3rio (refer\xEAncia, n\xE3o reescreva o que j\xE1 existe):`, opts.repoMap, ""] : [];
|
|
7257
7279
|
const flowBlock = opts.flowContext && opts.flowContext.length > 0 ? [opts.flowContext, ""] : [];
|
|
7280
|
+
const scaffoldBlock = opts.scaffoldHint && opts.scaffoldHint.length > 0 ? [`Scaffold de refer\xEAncia (task semelhante j\xE1 resolvida \u2014 reaproveite o que servir):`, opts.scaffoldHint, ""] : [];
|
|
7258
7281
|
return [
|
|
7259
7282
|
...repoMapBlock,
|
|
7260
7283
|
...flowBlock,
|
|
7284
|
+
...scaffoldBlock,
|
|
7261
7285
|
`Implemente a task "${node.title}" (id: ${node.id}) seguindo TDD.`,
|
|
7262
7286
|
"Responda APENAS com um bloco ```json. H\xE1 DOIS mecanismos:",
|
|
7263
7287
|
'- "edits" (PREFIRA \u2014 economia de token): [{ "path", "oldString", "newString", "replaceAll"? }].',
|
|
@@ -7286,8 +7310,25 @@ async function attemptImplementation(deps, options) {
|
|
|
7286
7310
|
const sleep2 = deps.sleep ?? defaultSleep;
|
|
7287
7311
|
let lastResult;
|
|
7288
7312
|
let lastError;
|
|
7313
|
+
const reuse = options.reuse;
|
|
7314
|
+
if (reuse?.kind === "exact") {
|
|
7315
|
+
const result = await deps.execute({ edits: reuse.edits });
|
|
7316
|
+
if (result.testPassed === true) {
|
|
7317
|
+
log25.info("Reuso exato verde \u2014 0 tokens de modelo", { node: options.node.id, sourceId: reuse.sourceId });
|
|
7318
|
+
return { success: true, attempts: 1, lastResult: result, appliedEdits: reuse.edits, reused: "exact" };
|
|
7319
|
+
}
|
|
7320
|
+
lastResult = result;
|
|
7321
|
+
lastError = result.testOutput;
|
|
7322
|
+
log25.warn("Reuso exato vermelho \u2014 caindo para gera\xE7\xE3o", { node: options.node.id });
|
|
7323
|
+
}
|
|
7324
|
+
const scaffoldHint = reuse?.kind === "scaffold" ? reuse.edits.map((e) => `\u2500\u2500 ${e.path} \u2500\u2500
|
|
7325
|
+
+ ${e.newString}`).join("\n") : void 0;
|
|
7289
7326
|
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
7290
|
-
const prompt = attempt === 1 || !lastResult ? buildInitialPrompt(options.node, {
|
|
7327
|
+
const prompt = attempt === 1 || !lastResult ? buildInitialPrompt(options.node, {
|
|
7328
|
+
repoMap: options.repoMap,
|
|
7329
|
+
flowContext: options.flowContext,
|
|
7330
|
+
scaffoldHint
|
|
7331
|
+
}) : buildRetryPrompt(options.node, lastResult, maxFeedbackChars);
|
|
7291
7332
|
let text;
|
|
7292
7333
|
try {
|
|
7293
7334
|
text = await deps.generate(prompt);
|
|
@@ -7328,7 +7369,13 @@ async function attemptImplementation(deps, options) {
|
|
|
7328
7369
|
oldString: e.oldString,
|
|
7329
7370
|
newString: e.newString
|
|
7330
7371
|
}));
|
|
7331
|
-
return {
|
|
7372
|
+
return {
|
|
7373
|
+
success: true,
|
|
7374
|
+
attempts: attempt,
|
|
7375
|
+
lastResult,
|
|
7376
|
+
appliedEdits,
|
|
7377
|
+
...reuse?.kind === "scaffold" ? { reused: "scaffold" } : {}
|
|
7378
|
+
};
|
|
7332
7379
|
}
|
|
7333
7380
|
lastError = lastResult.testOutput;
|
|
7334
7381
|
log25.warn("Testes vermelhos", { attempt, node: options.node.id });
|
|
@@ -8653,6 +8700,97 @@ var init_flow_compact = __esm({
|
|
|
8653
8700
|
log28 = createLogger({ layer: "core", source: "flow-compact.ts" });
|
|
8654
8701
|
}
|
|
8655
8702
|
});
|
|
8703
|
+
function norm2(text) {
|
|
8704
|
+
return text.trim().toLowerCase().replace(/\s+/g, " ");
|
|
8705
|
+
}
|
|
8706
|
+
function normSorted(items) {
|
|
8707
|
+
return (items ?? []).map(norm2).filter((s) => s.length > 0).sort();
|
|
8708
|
+
}
|
|
8709
|
+
function computeTaskSignature(input) {
|
|
8710
|
+
const canonical = {
|
|
8711
|
+
title: norm2(input.title),
|
|
8712
|
+
type: norm2(input.type ?? "task"),
|
|
8713
|
+
ac: normSorted(input.acceptanceCriteria),
|
|
8714
|
+
tags: normSorted(input.tags)
|
|
8715
|
+
};
|
|
8716
|
+
return createHash("sha256").update(JSON.stringify(canonical)).digest("hex");
|
|
8717
|
+
}
|
|
8718
|
+
var init_task_signature = __esm({
|
|
8719
|
+
"src/core/reuse/task-signature.ts"() {
|
|
8720
|
+
init_esm_shims();
|
|
8721
|
+
}
|
|
8722
|
+
});
|
|
8723
|
+
|
|
8724
|
+
// src/core/reuse/artifact-cache.ts
|
|
8725
|
+
function recordArtifact(db, row) {
|
|
8726
|
+
db.prepare(
|
|
8727
|
+
`INSERT OR IGNORE INTO artifact_cache
|
|
8728
|
+
(id, signature, node_id, applied_edits, approach_summary, model, outcome, created_at)
|
|
8729
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`
|
|
8730
|
+
).run(
|
|
8731
|
+
row.id,
|
|
8732
|
+
row.signature,
|
|
8733
|
+
row.nodeId ?? null,
|
|
8734
|
+
JSON.stringify(row.appliedEdits),
|
|
8735
|
+
row.approachSummary ?? null,
|
|
8736
|
+
row.model ?? null,
|
|
8737
|
+
row.outcome,
|
|
8738
|
+
row.createdAt
|
|
8739
|
+
);
|
|
8740
|
+
}
|
|
8741
|
+
function parseEdits(json) {
|
|
8742
|
+
try {
|
|
8743
|
+
const parsed = JSON.parse(json);
|
|
8744
|
+
return Array.isArray(parsed) ? parsed : [];
|
|
8745
|
+
} catch {
|
|
8746
|
+
return [];
|
|
8747
|
+
}
|
|
8748
|
+
}
|
|
8749
|
+
function queryBySignature(db, signature) {
|
|
8750
|
+
const rows = db.prepare(
|
|
8751
|
+
`SELECT id, signature, node_id, applied_edits, approach_summary, model, outcome, created_at
|
|
8752
|
+
FROM artifact_cache
|
|
8753
|
+
WHERE signature = ?
|
|
8754
|
+
ORDER BY created_at DESC`
|
|
8755
|
+
).all(signature);
|
|
8756
|
+
return rows.map((r) => ({
|
|
8757
|
+
id: r.id,
|
|
8758
|
+
signature: r.signature,
|
|
8759
|
+
nodeId: r.node_id ?? void 0,
|
|
8760
|
+
appliedEdits: parseEdits(r.applied_edits),
|
|
8761
|
+
approachSummary: r.approach_summary ?? void 0,
|
|
8762
|
+
model: r.model ?? void 0,
|
|
8763
|
+
outcome: r.outcome,
|
|
8764
|
+
createdAt: r.created_at
|
|
8765
|
+
}));
|
|
8766
|
+
}
|
|
8767
|
+
var init_artifact_cache = __esm({
|
|
8768
|
+
"src/core/reuse/artifact-cache.ts"() {
|
|
8769
|
+
init_esm_shims();
|
|
8770
|
+
}
|
|
8771
|
+
});
|
|
8772
|
+
|
|
8773
|
+
// src/core/reuse/resolve-reuse.ts
|
|
8774
|
+
function resolveReuse(db, signature, deps = {}, options = {}) {
|
|
8775
|
+
const exact = queryBySignature(db, signature).find((r) => r.outcome === "success");
|
|
8776
|
+
if (exact && exact.appliedEdits.length > 0) {
|
|
8777
|
+
return { kind: "exact", edits: exact.appliedEdits, sourceId: exact.id };
|
|
8778
|
+
}
|
|
8779
|
+
const threshold = options.scaffoldThreshold ?? DEFAULT_SCAFFOLD_THRESHOLD;
|
|
8780
|
+
const neighbor = deps.findNeighbor?.(signature);
|
|
8781
|
+
if (neighbor && neighbor.similarity >= threshold && neighbor.edits.length > 0) {
|
|
8782
|
+
return { kind: "scaffold", edits: neighbor.edits, sourceId: neighbor.sourceId, similarity: neighbor.similarity };
|
|
8783
|
+
}
|
|
8784
|
+
return { kind: "none" };
|
|
8785
|
+
}
|
|
8786
|
+
var DEFAULT_SCAFFOLD_THRESHOLD;
|
|
8787
|
+
var init_resolve_reuse = __esm({
|
|
8788
|
+
"src/core/reuse/resolve-reuse.ts"() {
|
|
8789
|
+
init_esm_shims();
|
|
8790
|
+
init_artifact_cache();
|
|
8791
|
+
DEFAULT_SCAFFOLD_THRESHOLD = 0.85;
|
|
8792
|
+
}
|
|
8793
|
+
});
|
|
8656
8794
|
|
|
8657
8795
|
// src/cli/shared/live-implement.ts
|
|
8658
8796
|
function buildLiveImplement(options) {
|
|
@@ -8689,6 +8827,17 @@ function buildLiveImplement(options) {
|
|
|
8689
8827
|
` [flow] \u03A6=${flow.flow.phi.toFixed(2)} \u03BB=${flow.flow.lambda.toFixed(2)} podados=${flow.flow.prunedCount} pinados=${flow.flow.pinnedCount} \u2192 ${flow.flow.tokensSaved} tok economizados`
|
|
8690
8828
|
);
|
|
8691
8829
|
}
|
|
8830
|
+
const fullNode = store2.getNodeById?.(node.id);
|
|
8831
|
+
const signature = computeTaskSignature({
|
|
8832
|
+
title: node.title,
|
|
8833
|
+
acceptanceCriteria: fullNode?.acceptanceCriteria,
|
|
8834
|
+
type: fullNode?.type,
|
|
8835
|
+
tags: fullNode?.tags
|
|
8836
|
+
});
|
|
8837
|
+
const reuse = resolveReuse(store2.getDb(), signature);
|
|
8838
|
+
if (reuse.kind !== "none") {
|
|
8839
|
+
onLog?.(` [reuse] ${reuse.kind} (sig ${signature.slice(0, 8)}\u2026) \u2192 ${reuse.kind === "exact" ? "0 tok se verde" : "scaffold no prompt"}`);
|
|
8840
|
+
}
|
|
8692
8841
|
const outcome = await attemptImplementation(
|
|
8693
8842
|
{
|
|
8694
8843
|
generate: async (prompt) => {
|
|
@@ -8704,8 +8853,9 @@ function buildLiveImplement(options) {
|
|
|
8704
8853
|
},
|
|
8705
8854
|
execute: (plan) => executePlan(plan, { workspaceDir: dir, defaultTestCommand: testCmd, runCommand: guardedRunner })
|
|
8706
8855
|
},
|
|
8707
|
-
{ node, maxAttempts, repoMap, flowContext }
|
|
8856
|
+
{ node, maxAttempts, repoMap, flowContext, reuse }
|
|
8708
8857
|
);
|
|
8858
|
+
if (outcome.reused === "exact") onLog?.(` [reuse] exact verde \u2014 0 tokens de modelo`);
|
|
8709
8859
|
const files = outcome.lastResult?.applied.length ?? 0;
|
|
8710
8860
|
const task = ledger.byTask(node.id);
|
|
8711
8861
|
onLog?.(
|
|
@@ -8729,6 +8879,21 @@ function buildLiveImplement(options) {
|
|
|
8729
8879
|
});
|
|
8730
8880
|
} catch {
|
|
8731
8881
|
}
|
|
8882
|
+
if (outcome.success && outcome.appliedEdits && outcome.appliedEdits.length > 0) {
|
|
8883
|
+
try {
|
|
8884
|
+
recordArtifact(store2.getDb(), {
|
|
8885
|
+
id: generateId("art"),
|
|
8886
|
+
signature,
|
|
8887
|
+
nodeId: node.id,
|
|
8888
|
+
appliedEdits: outcome.appliedEdits,
|
|
8889
|
+
approachSummary: buildApproachSummary(outcome.lastResult?.applied ?? [], []),
|
|
8890
|
+
model: client.modelFor("implement"),
|
|
8891
|
+
outcome: "success",
|
|
8892
|
+
createdAt: Date.now()
|
|
8893
|
+
});
|
|
8894
|
+
} catch {
|
|
8895
|
+
}
|
|
8896
|
+
}
|
|
8732
8897
|
return outcome.success;
|
|
8733
8898
|
};
|
|
8734
8899
|
return { implement, repoSymbolCount: repoSymbols.length };
|
|
@@ -8748,6 +8913,9 @@ var init_live_implement = __esm({
|
|
|
8748
8913
|
init_flow_compact();
|
|
8749
8914
|
init_episodic_outcomes_store();
|
|
8750
8915
|
init_id();
|
|
8916
|
+
init_task_signature();
|
|
8917
|
+
init_resolve_reuse();
|
|
8918
|
+
init_artifact_cache();
|
|
8751
8919
|
REPO_MAP_TOKEN_BUDGET = 1e3;
|
|
8752
8920
|
}
|
|
8753
8921
|
});
|
|
@@ -9093,7 +9261,7 @@ function InteractiveApp({ dashboard, port, asyncPort, liveRunner, skillCommands
|
|
|
9093
9261
|
process.stdout.isTTY ? "banner" : "dashboard"
|
|
9094
9262
|
);
|
|
9095
9263
|
const [input, setInput] = useState("");
|
|
9096
|
-
const [
|
|
9264
|
+
const [log50, setLog] = useState([]);
|
|
9097
9265
|
const [running, setRunning] = useState(false);
|
|
9098
9266
|
const [elapsedMs, setElapsedMs] = useState(0);
|
|
9099
9267
|
const [showHelp, setShowHelp] = useState(false);
|
|
@@ -9178,7 +9346,7 @@ ${skill.body}` : `Skill n\xE3o encontrada: ${parsed.cmd}`);
|
|
|
9178
9346
|
}
|
|
9179
9347
|
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
|
|
9180
9348
|
/* @__PURE__ */ jsx(App, { model: dashboard }),
|
|
9181
|
-
|
|
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)) }),
|
|
9182
9350
|
showHelp && /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginTop: 1, borderStyle: "round", paddingX: 1, children: [
|
|
9183
9351
|
/* @__PURE__ */ jsx(Text, { bold: true, children: "Comandos:" }),
|
|
9184
9352
|
COMMANDS.map((c) => /* @__PURE__ */ jsxs(Text, { children: [
|
|
@@ -9856,15 +10024,15 @@ function meanPoolAndNormalize(data, validTokens, dim) {
|
|
|
9856
10024
|
embedding[dVar] += data[tVar * dim + dVar];
|
|
9857
10025
|
}
|
|
9858
10026
|
}
|
|
9859
|
-
let
|
|
10027
|
+
let norm3 = 0;
|
|
9860
10028
|
for (let dVar = 0; dVar < dim; dVar++) {
|
|
9861
10029
|
embedding[dVar] /= validTokens;
|
|
9862
|
-
|
|
10030
|
+
norm3 += embedding[dVar] * embedding[dVar];
|
|
9863
10031
|
}
|
|
9864
|
-
|
|
9865
|
-
if (
|
|
10032
|
+
norm3 = Math.sqrt(norm3);
|
|
10033
|
+
if (norm3 > 0) {
|
|
9866
10034
|
for (let dVar = 0; dVar < dim; dVar++) {
|
|
9867
|
-
embedding[dVar] /=
|
|
10035
|
+
embedding[dVar] /= norm3;
|
|
9868
10036
|
}
|
|
9869
10037
|
}
|
|
9870
10038
|
return embedding;
|
|
@@ -14578,13 +14746,13 @@ function pruneOrphanWorktrees(options) {
|
|
|
14578
14746
|
}
|
|
14579
14747
|
}
|
|
14580
14748
|
try {
|
|
14581
|
-
const
|
|
14749
|
+
const output20 = execSync("git worktree prune --verbose", execOpts).toString();
|
|
14582
14750
|
if (reapedBranches > 0 || reapedWorktrees > 0) {
|
|
14583
14751
|
log46.info("shadow-branch:prune-ok", { reapedBranches, reapedWorktrees, ttlMs });
|
|
14584
14752
|
} else {
|
|
14585
|
-
log46.debug("shadow-branch:prune-ok", { reapedBranches, reapedWorktrees, output:
|
|
14753
|
+
log46.debug("shadow-branch:prune-ok", { reapedBranches, reapedWorktrees, output: output20 });
|
|
14586
14754
|
}
|
|
14587
|
-
return { pruned: true, reapedBranches, reapedWorktrees, output:
|
|
14755
|
+
return { pruned: true, reapedBranches, reapedWorktrees, output: output20 };
|
|
14588
14756
|
} catch (err) {
|
|
14589
14757
|
const error = String(err);
|
|
14590
14758
|
log46.debug("shadow-branch:prune-failed", { error });
|
|
@@ -14830,6 +14998,470 @@ ${listPrinciples().length} princ\xEDpios \xB7 use 'principles show <id>' para de
|
|
|
14830
14998
|
return cmd;
|
|
14831
14999
|
}
|
|
14832
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
|
+
|
|
14833
15465
|
// src/cli/index.ts
|
|
14834
15466
|
var program = new Command();
|
|
14835
15467
|
program.name("agent-graph-flow").description(PROMISE).version(VERSION, "-v, --version");
|
|
@@ -14853,6 +15485,8 @@ program.addCommand(gcCommand());
|
|
|
14853
15485
|
program.addCommand(skillCommand());
|
|
14854
15486
|
program.addCommand(profileCommand());
|
|
14855
15487
|
program.addCommand(principlesCommand());
|
|
15488
|
+
program.addCommand(generatePrdCommand());
|
|
15489
|
+
program.addCommand(buildCommand());
|
|
14856
15490
|
function shouldLaunchTui() {
|
|
14857
15491
|
const noArgs = process.argv.length <= 2;
|
|
14858
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
|
+
```
|