@mcp-graph-workflow/agent-graph-flow 0.7.0 → 0.8.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.
Files changed (2) hide show
  1. package/dist/cli/index.js +176 -8
  2. package/package.json +1 -1
package/dist/cli/index.js CHANGED
@@ -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
  ];
@@ -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, { repoMap: options.repoMap, flowContext: options.flowContext }) : buildRetryPrompt(options.node, lastResult, maxFeedbackChars);
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 { success: true, attempts: attempt, lastResult, appliedEdits };
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
  });
@@ -9856,15 +10024,15 @@ function meanPoolAndNormalize(data, validTokens, dim) {
9856
10024
  embedding[dVar] += data[tVar * dim + dVar];
9857
10025
  }
9858
10026
  }
9859
- let norm2 = 0;
10027
+ let norm3 = 0;
9860
10028
  for (let dVar = 0; dVar < dim; dVar++) {
9861
10029
  embedding[dVar] /= validTokens;
9862
- norm2 += embedding[dVar] * embedding[dVar];
10030
+ norm3 += embedding[dVar] * embedding[dVar];
9863
10031
  }
9864
- norm2 = Math.sqrt(norm2);
9865
- if (norm2 > 0) {
10032
+ norm3 = Math.sqrt(norm3);
10033
+ if (norm3 > 0) {
9866
10034
  for (let dVar = 0; dVar < dim; dVar++) {
9867
- embedding[dVar] /= norm2;
10035
+ embedding[dVar] /= norm3;
9868
10036
  }
9869
10037
  }
9870
10038
  return embedding;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mcp-graph-workflow/agent-graph-flow",
3
- "version": "0.7.0",
3
+ "version": "0.8.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",