@mcp-graph-workflow/agent-graph-flow 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/cli/index.js +280 -92
  2. package/package.json +1 -1
package/dist/cli/index.js CHANGED
@@ -13,7 +13,7 @@ import { stat, readFile, access, constants } from 'fs/promises';
13
13
  import { execFile, execSync, spawn } from 'child_process';
14
14
  import { render, useApp, useInput, Box, Text } from 'ink';
15
15
  import { jsx, jsxs } from 'react/jsx-runtime';
16
- import { createElement, useState, useCallback, useEffect } from 'react';
16
+ import { createElement, useState, useEffect, useCallback } from 'react';
17
17
  import TextInput from 'ink-text-input';
18
18
  import Spinner from 'ink-spinner';
19
19
  import { Command } from 'commander';
@@ -7091,8 +7091,8 @@ var init_implementation_executor = __esm({
7091
7091
  };
7092
7092
  defaultRunner = (command, cwd) => {
7093
7093
  try {
7094
- const output16 = execSync(command, { cwd, encoding: "utf8", stdio: "pipe" });
7095
- return { exitCode: 0, output: output16 };
7094
+ const output17 = execSync(command, { cwd, encoding: "utf8", stdio: "pipe" });
7095
+ return { exitCode: 0, output: output17 };
7096
7096
  } catch (err) {
7097
7097
  const e = err;
7098
7098
  return {
@@ -7320,7 +7320,12 @@ async function attemptImplementation(deps, options) {
7320
7320
  lastResult = await deps.execute(plan);
7321
7321
  if (lastResult.testPassed === true) {
7322
7322
  log25.info("Implementa\xE7\xE3o verde", { attempt, node: options.node.id });
7323
- return { success: true, attempts: attempt, lastResult };
7323
+ const appliedEdits = (plan.edits ?? []).map((e) => ({
7324
+ path: e.path,
7325
+ oldString: e.oldString,
7326
+ newString: e.newString
7327
+ }));
7328
+ return { success: true, attempts: attempt, lastResult, appliedEdits };
7324
7329
  }
7325
7330
  lastError = lastResult.testOutput;
7326
7331
  log25.warn("Testes vermelhos", { attempt, node: options.node.id });
@@ -7341,6 +7346,79 @@ var init_implement_attempt = __esm({
7341
7346
  }
7342
7347
  });
7343
7348
 
7349
+ // src/core/autonomy/exec-policy.ts
7350
+ function norm(cmd) {
7351
+ return cmd.trim().replace(/\s+/g, " ");
7352
+ }
7353
+ function evaluateExecPolicy(command, rules, defaultEffect = "ask") {
7354
+ const cmd = norm(command);
7355
+ let best;
7356
+ for (const rule of rules) {
7357
+ const m = norm(rule.match);
7358
+ if (cmd === m || cmd.startsWith(m + " ") || cmd.startsWith(m)) {
7359
+ if (!best || m.length > norm(best.match).length) best = rule;
7360
+ }
7361
+ }
7362
+ if (best) return { effect: best.effect, matchedRule: best };
7363
+ const low = cmd.toLowerCase();
7364
+ for (const danger of DEFAULT_DENY) {
7365
+ if (low.includes(danger)) return { effect: "deny", builtin: true };
7366
+ }
7367
+ return { effect: defaultEffect };
7368
+ }
7369
+ function guardExecRunner(base, opts = {}) {
7370
+ const rules = opts.rules ?? [];
7371
+ const defaultEffect = opts.defaultEffect ?? "ask";
7372
+ return (command, cwd) => {
7373
+ const decision = evaluateExecPolicy(command, rules, defaultEffect);
7374
+ const allowed = decision.effect === "allow" || decision.effect === "ask" && opts.cache?.isApproved(command) === true;
7375
+ if (allowed) return base(command, cwd);
7376
+ const reason = decision.effect === "deny" ? decision.builtin ? "deny (built-in perigoso)" : "deny (regra)" : "ask (n\xE3o aprovado nesta sess\xE3o)";
7377
+ return { exitCode: 126, output: `[exec-policy] comando bloqueado \u2014 ${reason}: ${command}` };
7378
+ };
7379
+ }
7380
+ var DEFAULT_DENY;
7381
+ var init_exec_policy = __esm({
7382
+ "src/core/autonomy/exec-policy.ts"() {
7383
+ init_esm_shims();
7384
+ DEFAULT_DENY = [
7385
+ "rm -rf",
7386
+ "sudo ",
7387
+ "git push --force",
7388
+ "git push -f",
7389
+ "chmod -R 777",
7390
+ "chmod 777",
7391
+ "dd if=",
7392
+ ":(){",
7393
+ "mkfs",
7394
+ "| sh",
7395
+ "|sh",
7396
+ "| bash",
7397
+ "> /dev/sd"
7398
+ ];
7399
+ }
7400
+ });
7401
+
7402
+ // src/tui/diff-render.ts
7403
+ function renderEditDiff(edit) {
7404
+ const lines = [`\u2500\u2500 ${edit.path} \u2500\u2500`];
7405
+ if (edit.oldString.length > 0) {
7406
+ for (const l of edit.oldString.split("\n")) lines.push(`- ${l}`);
7407
+ }
7408
+ if (edit.newString.length > 0) {
7409
+ for (const l of edit.newString.split("\n")) lines.push(`+ ${l}`);
7410
+ }
7411
+ return lines;
7412
+ }
7413
+ function renderPlanDiff(edits) {
7414
+ return edits.flatMap(renderEditDiff);
7415
+ }
7416
+ var init_diff_render = __esm({
7417
+ "src/tui/diff-render.ts"() {
7418
+ init_esm_shims();
7419
+ }
7420
+ });
7421
+
7344
7422
  // src/core/code/code-store.ts
7345
7423
  function rowToSymbol(row) {
7346
7424
  return {
@@ -8582,6 +8660,18 @@ function buildLiveImplement(options) {
8582
8660
  onLog?.(`[live] provider: ${resolved.kind === "api" ? "Copilot API (HTTP, logado)" : "Copilot CLI"}`);
8583
8661
  const client = new TieredModelClient(resolved.adapter, config);
8584
8662
  const maxAttempts = Math.max(1, retries);
8663
+ let execRules = [];
8664
+ let execDefault = "allow";
8665
+ const rawPolicy = store2.getProjectSetting("exec_policy");
8666
+ if (rawPolicy) {
8667
+ try {
8668
+ const parsed = JSON.parse(rawPolicy);
8669
+ if (parsed.default) execDefault = parsed.default;
8670
+ if (Array.isArray(parsed.rules)) execRules = parsed.rules;
8671
+ } catch {
8672
+ }
8673
+ }
8674
+ const guardedRunner = guardExecRunner(defaultRunner, { rules: execRules, defaultEffect: execDefault });
8585
8675
  const codeStore = new CodeStore(store2.getDb());
8586
8676
  const projectId = store2.getProject()?.id;
8587
8677
  const repoSymbols = projectId ? codeStore.getAllSymbols(projectId) : [];
@@ -8609,7 +8699,7 @@ function buildLiveImplement(options) {
8609
8699
  });
8610
8700
  return res.text;
8611
8701
  },
8612
- execute: (plan) => executePlan(plan, { workspaceDir: dir, defaultTestCommand: testCmd })
8702
+ execute: (plan) => executePlan(plan, { workspaceDir: dir, defaultTestCommand: testCmd, runCommand: guardedRunner })
8613
8703
  },
8614
8704
  { node, maxAttempts, repoMap, flowContext }
8615
8705
  );
@@ -8618,6 +8708,9 @@ function buildLiveImplement(options) {
8618
8708
  onLog?.(
8619
8709
  ` [live] ${client.modelFor("implement")}: ${outcome.attempts} tentativa(s), ${files} arquivo(s), ${task.total} tok \u2192 ${outcome.success ? "verde" : "escala"}`
8620
8710
  );
8711
+ if (outcome.success && outcome.appliedEdits && outcome.appliedEdits.length > 0) {
8712
+ for (const line of renderPlanDiff(outcome.appliedEdits)) onLog?.(line);
8713
+ }
8621
8714
  try {
8622
8715
  const applied = outcome.lastResult?.applied ?? [];
8623
8716
  insertEpisodicOutcome(store2.getDb(), {
@@ -8645,6 +8738,8 @@ var init_live_implement = __esm({
8645
8738
  init_resolve_adapter();
8646
8739
  init_implementation_executor();
8647
8740
  init_implement_attempt();
8741
+ init_exec_policy();
8742
+ init_diff_render();
8648
8743
  init_code_store();
8649
8744
  init_repo_map();
8650
8745
  init_flow_compact();
@@ -8842,6 +8937,22 @@ var init_history = __esm({
8842
8937
  }
8843
8938
  });
8844
8939
 
8940
+ // src/tui/elapsed.ts
8941
+ function formatElapsed(ms) {
8942
+ const totalSec = Math.max(0, Math.floor(ms / 1e3));
8943
+ const h = Math.floor(totalSec / 3600);
8944
+ const m = Math.floor(totalSec % 3600 / 60);
8945
+ const s = totalSec % 60;
8946
+ if (h > 0) return `${h}h ${String(m).padStart(2, "0")}m`;
8947
+ if (m > 0) return `${m}m ${String(s).padStart(2, "0")}s`;
8948
+ return `${s}s`;
8949
+ }
8950
+ var init_elapsed = __esm({
8951
+ "src/tui/elapsed.ts"() {
8952
+ init_esm_shims();
8953
+ }
8954
+ });
8955
+
8845
8956
  // src/tui/dispatch.ts
8846
8957
  function parseCommand(input) {
8847
8958
  const trimmed = input.trim();
@@ -8981,7 +9092,15 @@ function InteractiveApp({ dashboard, port, asyncPort, liveRunner, skillCommands
8981
9092
  const [input, setInput] = useState("");
8982
9093
  const [log48, setLog] = useState([]);
8983
9094
  const [running, setRunning] = useState(false);
9095
+ const [elapsedMs, setElapsedMs] = useState(0);
8984
9096
  const [showHelp, setShowHelp] = useState(false);
9097
+ useEffect(() => {
9098
+ if (!running) return;
9099
+ setElapsedMs(0);
9100
+ const startedAt = Date.now();
9101
+ const t = setInterval(() => setElapsedMs(Date.now() - startedAt), 1e3);
9102
+ return () => clearInterval(t);
9103
+ }, [running]);
8985
9104
  const [history, setHistory] = useState([]);
8986
9105
  const [histCursor, setHistCursor] = useState(-1);
8987
9106
  const [draft, setDraft] = useState("");
@@ -9054,7 +9173,10 @@ ${skill.body}` : `Skill n\xE3o encontrada: ${parsed.cmd}`);
9054
9173
  ] }),
9055
9174
  running && /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsxs(Text, { color: "yellow", children: [
9056
9175
  /* @__PURE__ */ jsx(Spinner, { type: "dots" }),
9057
- " executando\u2026"
9176
+ " executando\u2026 ",
9177
+ formatElapsed(elapsedMs),
9178
+ " \xB7 ",
9179
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Esc para interromper" })
9058
9180
  ] }) }),
9059
9181
  /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(CommandBar, { value: input, onChange: setInput, onSubmit: submit, suggestions: filterCommands(input, skillCommands) }) })
9060
9182
  ] });
@@ -9067,6 +9189,7 @@ var init_interactive_app = __esm({
9067
9189
  init_banner_screen();
9068
9190
  init_command_bar();
9069
9191
  init_history();
9192
+ init_elapsed();
9070
9193
  init_dispatch();
9071
9194
  MAX_LOG_LINES = 12;
9072
9195
  }
@@ -9717,15 +9840,15 @@ function meanPoolAndNormalize(data, validTokens, dim) {
9717
9840
  embedding[dVar] += data[tVar * dim + dVar];
9718
9841
  }
9719
9842
  }
9720
- let norm = 0;
9843
+ let norm2 = 0;
9721
9844
  for (let dVar = 0; dVar < dim; dVar++) {
9722
9845
  embedding[dVar] /= validTokens;
9723
- norm += embedding[dVar] * embedding[dVar];
9846
+ norm2 += embedding[dVar] * embedding[dVar];
9724
9847
  }
9725
- norm = Math.sqrt(norm);
9726
- if (norm > 0) {
9848
+ norm2 = Math.sqrt(norm2);
9849
+ if (norm2 > 0) {
9727
9850
  for (let dVar = 0; dVar < dim; dVar++) {
9728
- embedding[dVar] /= norm;
9851
+ embedding[dVar] /= norm2;
9729
9852
  }
9730
9853
  }
9731
9854
  return embedding;
@@ -11217,7 +11340,7 @@ init_store_port();
11217
11340
  // src/cli/shared/enable-flow.ts
11218
11341
  init_esm_shims();
11219
11342
  init_flow_config();
11220
- function enableFlowConfig(store2) {
11343
+ function setFlowEnabled(store2, enabled) {
11221
11344
  const raw = store2.getProjectSetting(FLOW_CONFIG_SETTING_KEY);
11222
11345
  let current = {};
11223
11346
  if (raw) {
@@ -11227,24 +11350,88 @@ function enableFlowConfig(store2) {
11227
11350
  current = {};
11228
11351
  }
11229
11352
  }
11230
- store2.setProjectSetting(FLOW_CONFIG_SETTING_KEY, JSON.stringify({ ...current, enabled: true }));
11353
+ store2.setProjectSetting(FLOW_CONFIG_SETTING_KEY, JSON.stringify({ ...current, enabled }));
11354
+ }
11355
+ function enableFlowConfig(store2) {
11356
+ setFlowEnabled(store2, true);
11231
11357
  }
11358
+
11359
+ // src/cli/commands/profile-cmd.ts
11360
+ init_esm_shims();
11361
+
11362
+ // src/core/config/profiles.ts
11363
+ init_esm_shims();
11364
+ var BUILT_IN_PROFILES = {
11365
+ fast: { modelTier: "cheap", flow: false, retries: 1 },
11366
+ build: { modelTier: "build", flow: true, retries: 2 },
11367
+ frontier: { modelTier: "frontier", flow: true, retries: 3 }
11368
+ };
11369
+ function resolveProfile(name) {
11370
+ return BUILT_IN_PROFILES[name];
11371
+ }
11372
+ function listProfiles() {
11373
+ return Object.keys(BUILT_IN_PROFILES);
11374
+ }
11375
+
11376
+ // src/cli/commands/profile-cmd.ts
11377
+ init_tier_router();
11232
11378
  function output7(msg) {
11233
11379
  process.stdout.write(msg + "\n");
11234
11380
  }
11381
+ function applyProfile(store2, name) {
11382
+ const profile = resolveProfile(name);
11383
+ if (!profile) return void 0;
11384
+ store2.setProjectSetting("model", resolveTierModel(profile.modelTier));
11385
+ setFlowEnabled(store2, profile.flow);
11386
+ return profile;
11387
+ }
11388
+ function profileCommand() {
11389
+ const cmd = new Command("profile").description("Bundles de trabalho: tier de modelo + flow + retries");
11390
+ cmd.command("list").description("Lista os profiles dispon\xEDveis").action(() => {
11391
+ for (const name of listProfiles()) {
11392
+ const p = BUILT_IN_PROFILES[name];
11393
+ output7(`${name.padEnd(10)} tier=${p.modelTier} flow=${p.flow} retries=${p.retries}`);
11394
+ }
11395
+ });
11396
+ cmd.command("show <nome>").description("Detalha um profile").action((nome) => {
11397
+ const p = resolveProfile(nome);
11398
+ if (!p) {
11399
+ output7(`Profile desconhecido: ${nome}. Tente 'profile list'.`);
11400
+ process.exitCode = 1;
11401
+ return;
11402
+ }
11403
+ output7(`${nome}: tier=${p.modelTier} \xB7 flow=${p.flow} \xB7 retries=${p.retries} \xB7 modelo=${resolveTierModel(p.modelTier)}`);
11404
+ });
11405
+ return cmd;
11406
+ }
11407
+ function output8(msg) {
11408
+ process.stdout.write(msg + "\n");
11409
+ }
11235
11410
  function autopilotCommand() {
11236
- return new Command("autopilot").description("Loop aut\xF4nomo com guardrails: next \u2192 in_progress \u2192 DoD \u2192 done|escalate (WIP=1)").option("-d, --dir <dir>", "Diret\xF3rio do projeto", process.cwd()).option("-m, --max <n>", "Budget: m\xE1ximo de tasks por sess\xE3o (cost-runaway guard)", "5").option("--simulate", "Simula impl bem-sucedida (deixa o DoD real decidir) \u2014 n\xE3o escreve c\xF3digo", false).option("--live", "Invoca o modelo real via SDK do Copilot: gera plano \u2192 aplica \u2192 roda testes \u2192 done|escala", false).option("--test-cmd <cmd>", "Comando de teste rodado no modo --live quando o plano n\xE3o traz um", "npm test").option("--retries <n>", "Tentativas por task no --live (retry com feedback compacto do teste)", "2").option("--flow", "Ativa a dilui\xE7\xE3o de contexto por \u03BB_flow (hipofrontalidade) no --live", false).action(
11411
+ return new Command("autopilot").description("Loop aut\xF4nomo com guardrails: next \u2192 in_progress \u2192 DoD \u2192 done|escalate (WIP=1)").option("-d, --dir <dir>", "Diret\xF3rio do projeto", process.cwd()).option("-m, --max <n>", "Budget: m\xE1ximo de tasks por sess\xE3o (cost-runaway guard)", "5").option("--simulate", "Simula impl bem-sucedida (deixa o DoD real decidir) \u2014 n\xE3o escreve c\xF3digo", false).option("--live", "Invoca o modelo real via SDK do Copilot: gera plano \u2192 aplica \u2192 roda testes \u2192 done|escala", false).option("--test-cmd <cmd>", "Comando de teste rodado no modo --live quando o plano n\xE3o traz um", "npm test").option("--retries <n>", "Tentativas por task no --live (retry com feedback compacto do teste)", "2").option("--flow", "Ativa a dilui\xE7\xE3o de contexto por \u03BB_flow (hipofrontalidade) no --live", false).option("--profile <nome>", "Aplica um bundle de trabalho (fast|build|frontier): tier+flow+retries").action(
11237
11412
  async (opts) => {
11238
11413
  const store2 = openStoreOrFail(opts.dir, { requireExisting: true });
11239
11414
  try {
11240
11415
  const maxIterations = Math.max(1, parseInt(opts.max, 10) || 5);
11241
11416
  const port = makeStorePort(store2);
11417
+ let retriesOverride;
11418
+ if (opts.profile) {
11419
+ const applied = applyProfile(store2, opts.profile);
11420
+ if (!applied) {
11421
+ output8(`Profile desconhecido: ${opts.profile}. Tente 'profile list'.`);
11422
+ store2.close();
11423
+ return;
11424
+ }
11425
+ retriesOverride = applied.retries;
11426
+ output8(`[PROFILE] ${opts.profile}: tier=${applied.modelTier} flow=${applied.flow} retries=${applied.retries}
11427
+ `);
11428
+ }
11242
11429
  if (opts.flow) {
11243
11430
  enableFlowConfig(store2);
11244
- output7("[FLOW] \u03BB_flow ativo: contexto do grafo dilu\xEDdo por \u03A6(t) (esquecimento din\xE2mico).\n");
11431
+ output8("[FLOW] \u03BB_flow ativo: contexto do grafo dilu\xEDdo por \u03A6(t) (esquecimento din\xE2mico).\n");
11245
11432
  }
11246
- if (opts.simulate) output7("[SIMULA\xC7\xC3O] impl tratada como verde \u2014 DoD real decide prontid\xE3o.\n");
11247
- if (opts.live) output7("[LIVE] modelo via SDK do Copilot: gera plano \u2192 aplica no workspace \u2192 roda testes.\n");
11433
+ if (opts.simulate) output8("[SIMULA\xC7\xC3O] impl tratada como verde \u2014 DoD real decide prontid\xE3o.\n");
11434
+ if (opts.live) output8("[LIVE] modelo via SDK do Copilot: gera plano \u2192 aplica no workspace \u2192 roda testes.\n");
11248
11435
  let implement;
11249
11436
  let ledger;
11250
11437
  if (opts.live) {
@@ -11253,11 +11440,11 @@ function autopilotCommand() {
11253
11440
  store: store2,
11254
11441
  dir: opts.dir,
11255
11442
  testCmd: opts.testCmd,
11256
- retries: parseInt(opts.retries, 10) || 2,
11443
+ retries: retriesOverride ?? (parseInt(opts.retries, 10) || 2),
11257
11444
  ledger,
11258
- onLog: output7
11445
+ onLog: output8
11259
11446
  });
11260
- if (live.repoSymbolCount > 0) output7(`[LIVE] repo-map: ${live.repoSymbolCount} s\xEDmbolo(s) indexado(s).`);
11447
+ if (live.repoSymbolCount > 0) output8(`[LIVE] repo-map: ${live.repoSymbolCount} s\xEDmbolo(s) indexado(s).`);
11261
11448
  implement = live.implement;
11262
11449
  } else if (opts.simulate) {
11263
11450
  implement = () => true;
@@ -11267,28 +11454,28 @@ function autopilotCommand() {
11267
11454
  const result = await runAutopilot(port, { maxIterations, implement });
11268
11455
  for (const s of result.steps) {
11269
11456
  const icon = s.action === "done" ? "\u2713" : s.action === "escalated" ? "\u26A0" : "\u2192";
11270
- output7(`${icon} ${s.nodeId} ${s.title} [${s.action}] ${s.detail}`);
11457
+ output8(`${icon} ${s.nodeId} ${s.title} [${s.action}] ${s.detail}`);
11271
11458
  }
11272
- output7(`
11459
+ output8(`
11273
11460
  Resumo: ${result.completed} conclu\xEDda(s), ${result.escalated} escalada(s). Parou: ${result.stopped}`);
11274
11461
  if (ledger) {
11275
11462
  const totals = ledger.totals();
11276
- output7(`
11463
+ output8(`
11277
11464
  Tokens (sess\xE3o): ${totals.total} (in ${totals.tokensIn} / out ${totals.tokensOut}) em ${totals.calls} chamada(s)`);
11278
11465
  for (const t of ledger.tasks()) {
11279
- output7(` ${t.nodeId}: ${t.total} tok (in ${t.tokensIn} / out ${t.tokensOut}, ${t.calls} chamada(s))`);
11466
+ output8(` ${t.nodeId}: ${t.total} tok (in ${t.tokensIn} / out ${t.tokensOut}, ${t.calls} chamada(s))`);
11280
11467
  }
11281
11468
  if (result.completed > 0) {
11282
- output7(` m\xE9dia/task conclu\xEDda: ${Math.round(totals.total / result.completed)} tok`);
11469
+ output8(` m\xE9dia/task conclu\xEDda: ${Math.round(totals.total / result.completed)} tok`);
11283
11470
  }
11284
11471
  if (totals.calls > 0) {
11285
11472
  const sessionId = `autopilot_${randomUUID().replace(/-/g, "").slice(0, 12)}`;
11286
11473
  const rows = persistLedger(store2.getDb(), ledger, { sessionId, provider: "copilot" });
11287
- output7(` ${rows} chamada(s) persistida(s) (session ${sessionId})`);
11474
+ output8(` ${rows} chamada(s) persistida(s) (session ${sessionId})`);
11288
11475
  }
11289
11476
  }
11290
11477
  if (!opts.simulate && !opts.live && result.stopped === "escalation") {
11291
- output7("\nDica: --simulate exercita o loop + gate DoD; --live invoca o modelo real via SDK do Copilot.");
11478
+ output8("\nDica: --simulate exercita o loop + gate DoD; --live invoca o modelo real via SDK do Copilot.");
11292
11479
  }
11293
11480
  } finally {
11294
11481
  store2.close();
@@ -11301,7 +11488,7 @@ Tokens (sess\xE3o): ${totals.total} (in ${totals.tokensIn} / out ${totals.tokens
11301
11488
  init_esm_shims();
11302
11489
  init_tier_router();
11303
11490
  var SETTING_KEY = "model";
11304
- function output8(msg) {
11491
+ function output9(msg) {
11305
11492
  process.stdout.write(msg + "\n");
11306
11493
  }
11307
11494
  function readConfig(dir) {
@@ -11318,29 +11505,29 @@ function modelCommand() {
11318
11505
  "Seleciona/inspeciona o modelo do tier-router (pool do Copilot CLI; 'auto' roteia por tarefa)"
11319
11506
  );
11320
11507
  cmd.command("list").description("Lista o pool agrupado por tier").action(() => {
11321
- output8("auto \u2014 roteia por tarefa (cheap classifica \xB7 build implementa \xB7 frontier planeja)\n");
11508
+ output9("auto \u2014 roteia por tarefa (cheap classifica \xB7 build implementa \xB7 frontier planeja)\n");
11322
11509
  for (const tier of MODEL_TIERS) {
11323
- output8(`[${tier}]`);
11510
+ output9(`[${tier}]`);
11324
11511
  for (const m of modelsForTier(tier)) {
11325
11512
  const mark = m.id === DEFAULT_MODEL ? " (default)" : "";
11326
- output8(` ${m.id} \u2014 ${m.label}${mark}`);
11513
+ output9(` ${m.id} \u2014 ${m.label}${mark}`);
11327
11514
  }
11328
11515
  }
11329
11516
  });
11330
11517
  cmd.command("current").description("Mostra o modelo/modo selecionado").option("-d, --dir <dir>", "Diret\xF3rio do projeto", process.cwd()).action((opts) => {
11331
11518
  const config = readConfig(opts.dir);
11332
- output8(config.mode === "auto" ? "auto (roteamento por tarefa)" : `pinned: ${config.modelId}`);
11519
+ output9(config.mode === "auto" ? "auto (roteamento por tarefa)" : `pinned: ${config.modelId}`);
11333
11520
  });
11334
11521
  cmd.command("set").description("Fixa um modelo (id do pool) ou 'auto' para roteamento por tarefa").argument("<idOrAuto>", "ID do modelo ou 'auto'").option("-d, --dir <dir>", "Diret\xF3rio do projeto", process.cwd()).action((idOrAuto, opts) => {
11335
11522
  const value = idOrAuto.trim();
11336
11523
  if (value !== "auto" && !isKnownModel(value)) {
11337
- output8(`Modelo desconhecido: "${value}". Rode 'model list' para ver o pool.`);
11524
+ output9(`Modelo desconhecido: "${value}". Rode 'model list' para ver o pool.`);
11338
11525
  process.exit(1);
11339
11526
  }
11340
11527
  const store2 = openStoreOrFail(opts.dir, { requireExisting: true });
11341
11528
  try {
11342
11529
  store2.setProjectSetting(SETTING_KEY, value);
11343
- output8(value === "auto" ? "Modo: auto (roteamento por tarefa)." : `Modelo fixado: ${value}.`);
11530
+ output9(value === "auto" ? "Modo: auto (roteamento por tarefa)." : `Modelo fixado: ${value}.`);
11344
11531
  } finally {
11345
11532
  store2.close();
11346
11533
  }
@@ -11348,11 +11535,11 @@ function modelCommand() {
11348
11535
  cmd.command("route").description("Mostra qual modelo o router escolhe para um tipo de tarefa").argument("<kind>", `Tipo: ${TaskKindSchema.options.join("|")}`).option("-d, --dir <dir>", "Diret\xF3rio do projeto", process.cwd()).action((kind, opts) => {
11349
11536
  const parsed = TaskKindSchema.safeParse(kind);
11350
11537
  if (!parsed.success) {
11351
- output8(`Tipo inv\xE1lido: "${kind}". Esperado: ${TaskKindSchema.options.join(", ")}.`);
11538
+ output9(`Tipo inv\xE1lido: "${kind}". Esperado: ${TaskKindSchema.options.join(", ")}.`);
11352
11539
  process.exit(1);
11353
11540
  }
11354
11541
  const config = readConfig(opts.dir);
11355
- output8(`${kind} \u2192 ${routeModel(config, parsed.data)}`);
11542
+ output9(`${kind} \u2192 ${routeModel(config, parsed.data)}`);
11356
11543
  });
11357
11544
  return cmd;
11358
11545
  }
@@ -11360,7 +11547,7 @@ function modelCommand() {
11360
11547
  // src/cli/commands/metrics-cmd.ts
11361
11548
  init_esm_shims();
11362
11549
  init_llm_call_ledger();
11363
- function output9(msg) {
11550
+ function output10(msg) {
11364
11551
  process.stdout.write(msg + "\n");
11365
11552
  }
11366
11553
  function metricsCommand() {
@@ -11369,30 +11556,30 @@ function metricsCommand() {
11369
11556
  try {
11370
11557
  const summary = summarizeLedger(store2.getDb(), { sessionId: opts.session });
11371
11558
  if (summary.totals.calls === 0) {
11372
- output9("Sem chamadas de modelo registradas. Rode `autopilot --live` para gerar m\xE9tricas.");
11559
+ output10("Sem chamadas de modelo registradas. Rode `autopilot --live` para gerar m\xE9tricas.");
11373
11560
  return;
11374
11561
  }
11375
11562
  const usd = (n) => `$${n.toFixed(4)}`;
11376
11563
  const { totals } = summary;
11377
11564
  const taskCount = summary.byTask.length;
11378
11565
  const avgCost = taskCount > 0 ? totals.costUsd / taskCount : 0;
11379
- output9(`Tokens totais: ${totals.total} (in ${totals.tokensIn} / out ${totals.tokensOut}) em ${totals.calls} chamada(s)`);
11380
- output9(`Custo total: \u2248 ${usd(totals.costUsd)}`);
11381
- output9(`M\xE9dia por task: ${summary.avgTokensPerTask} tok \u2248 ${usd(avgCost)} | ${taskCount} task(s), ${summary.bySession.length} sess\xE3o(\xF5es)`);
11566
+ output10(`Tokens totais: ${totals.total} (in ${totals.tokensIn} / out ${totals.tokensOut}) em ${totals.calls} chamada(s)`);
11567
+ output10(`Custo total: \u2248 ${usd(totals.costUsd)}`);
11568
+ output10(`M\xE9dia por task: ${summary.avgTokensPerTask} tok \u2248 ${usd(avgCost)} | ${taskCount} task(s), ${summary.bySession.length} sess\xE3o(\xF5es)`);
11382
11569
  const top = Math.max(1, parseInt(opts.top, 10) || 10);
11383
- output9(`
11570
+ output10(`
11384
11571
  Tokens por task (top ${top}):`);
11385
11572
  for (const t of summary.byTask.slice(0, top)) {
11386
- output9(` ${t.nodeId}: ${t.total} tok \u2248 ${usd(t.costUsd)} (in ${t.tokensIn} / out ${t.tokensOut}, ${t.calls} chamada(s))`);
11573
+ output10(` ${t.nodeId}: ${t.total} tok \u2248 ${usd(t.costUsd)} (in ${t.tokensIn} / out ${t.tokensOut}, ${t.calls} chamada(s))`);
11387
11574
  }
11388
11575
  if (!opts.session) {
11389
- output9("\nTokens por sess\xE3o:");
11576
+ output10("\nTokens por sess\xE3o:");
11390
11577
  for (const s of summary.bySession) {
11391
- output9(` ${s.sessionId}: ${s.total} tok \u2248 ${usd(s.costUsd)} (${s.calls} chamada(s))`);
11578
+ output10(` ${s.sessionId}: ${s.total} tok \u2248 ${usd(s.costUsd)} (${s.calls} chamada(s))`);
11392
11579
  }
11393
11580
  }
11394
11581
  if (totals.costUsd === 0) {
11395
- output9("\nNota: custo $0 \u2014 modelos usados n\xE3o t\xEAm pre\xE7o cadastrado (ver cost-tracker MODEL_PRICING).");
11582
+ output10("\nNota: custo $0 \u2014 modelos usados n\xE3o t\xEAm pre\xE7o cadastrado (ver cost-tracker MODEL_PRICING).");
11396
11583
  }
11397
11584
  } finally {
11398
11585
  store2.close();
@@ -11407,7 +11594,7 @@ init_resolve_adapter();
11407
11594
  init_implementation_executor();
11408
11595
  init_implement_attempt();
11409
11596
  init_token_ledger();
11410
- function output10(msg) {
11597
+ function output11(msg) {
11411
11598
  process.stdout.write(msg + "\n");
11412
11599
  }
11413
11600
  function runCommand() {
@@ -11418,7 +11605,7 @@ function runCommand() {
11418
11605
  const maxAttempts = Math.max(1, parseInt(opts.retries, 10) || 2);
11419
11606
  const ledger = new TokenLedger();
11420
11607
  const node = { id: `run_${randomUUID().replace(/-/g, "").slice(0, 8)}`, title: prompt };
11421
- output10(`[run] ${client.modelFor("implement")} via ${resolved.kind === "api" ? "API HTTP" : "CLI"} \u2192 "${prompt}"`);
11608
+ output11(`[run] ${client.modelFor("implement")} via ${resolved.kind === "api" ? "API HTTP" : "CLI"} \u2192 "${prompt}"`);
11422
11609
  const outcome = await attemptImplementation(
11423
11610
  {
11424
11611
  generate: async (p) => {
@@ -11438,7 +11625,7 @@ function runCommand() {
11438
11625
  );
11439
11626
  const files = outcome.lastResult?.applied.length ?? 0;
11440
11627
  const totals = ledger.totals();
11441
- output10(
11628
+ output11(
11442
11629
  `${outcome.success ? "\u2713" : "\u26A0"} ${outcome.attempts} tentativa(s), ${files} arquivo(s), ${totals.total} tok (in ${totals.tokensIn} / out ${totals.tokensOut}) \u2192 ${outcome.success ? "verde" : "falhou (testes n\xE3o passaram)"}`
11443
11630
  );
11444
11631
  if (!outcome.success) process.exitCode = 1;
@@ -11462,7 +11649,7 @@ function tuiCommand() {
11462
11649
  // src/cli/commands/login-cmd.ts
11463
11650
  init_esm_shims();
11464
11651
  init_copilot_auth();
11465
- function output11(msg) {
11652
+ function output12(msg) {
11466
11653
  process.stdout.write(msg + "\n");
11467
11654
  }
11468
11655
  function loginCommand() {
@@ -11470,15 +11657,15 @@ function loginCommand() {
11470
11657
  const path22 = defaultAuthPath();
11471
11658
  if (opts.token) {
11472
11659
  saveAuth(path22, { githubToken: opts.token });
11473
- output11(`\u2713 Token salvo em ${path22}. Provider HTTP do Copilot habilitado.`);
11660
+ output12(`\u2713 Token salvo em ${path22}. Provider HTTP do Copilot habilitado.`);
11474
11661
  return;
11475
11662
  }
11476
11663
  const device = await requestDeviceCode(globalThis.fetch);
11477
- output11("\nPara autenticar, abra:");
11478
- output11(` ${device.verificationUri}`);
11479
- output11(`e informe o c\xF3digo: \x1B[1m${device.userCode}\x1B[0m
11664
+ output12("\nPara autenticar, abra:");
11665
+ output12(` ${device.verificationUri}`);
11666
+ output12(`e informe o c\xF3digo: \x1B[1m${device.userCode}\x1B[0m
11480
11667
  `);
11481
- output11("Aguardando autoriza\xE7\xE3o\u2026 (Ctrl+C para cancelar)");
11668
+ output12("Aguardando autoriza\xE7\xE3o\u2026 (Ctrl+C para cancelar)");
11482
11669
  const deadline = Date.now() + device.expiresIn * 1e3;
11483
11670
  let interval = Math.max(1, device.interval);
11484
11671
  while (Date.now() < deadline) {
@@ -11486,13 +11673,13 @@ function loginCommand() {
11486
11673
  const r = await pollForAccessToken(globalThis.fetch, device.deviceCode);
11487
11674
  if ("accessToken" in r) {
11488
11675
  saveAuth(path22, { githubToken: r.accessToken });
11489
- output11(`
11676
+ output12(`
11490
11677
  \u2713 Autenticado. Token salvo em ${path22}. Provider HTTP do Copilot habilitado.`);
11491
11678
  return;
11492
11679
  }
11493
11680
  if ("slowDown" in r) interval += 5;
11494
11681
  }
11495
- output11("\n\u26A0 Tempo esgotado sem autoriza\xE7\xE3o. Rode `agf login` de novo.");
11682
+ output12("\n\u26A0 Tempo esgotado sem autoriza\xE7\xE3o. Rode `agf login` de novo.");
11496
11683
  process.exitCode = 1;
11497
11684
  });
11498
11685
  }
@@ -11501,9 +11688,9 @@ function logoutCommand() {
11501
11688
  const path22 = defaultAuthPath();
11502
11689
  if (existsSync(path22) && loadAuth(path22)) {
11503
11690
  rmSync(path22, { force: true });
11504
- output11(`\u2713 Logout \u2014 ${path22} removido.`);
11691
+ output12(`\u2713 Logout \u2014 ${path22} removido.`);
11505
11692
  } else {
11506
- output11("Nenhum login salvo.");
11693
+ output12("Nenhum login salvo.");
11507
11694
  }
11508
11695
  });
11509
11696
  }
@@ -14164,7 +14351,7 @@ function reapDaemons(options = {}) {
14164
14351
  // src/cli/commands/daemon-cmd.ts
14165
14352
  init_logger();
14166
14353
  var log44 = createLogger({ layer: "cli", source: "daemon.ts" });
14167
- function output12(msg) {
14354
+ function output13(msg) {
14168
14355
  process.stdout.write(msg + "\n");
14169
14356
  }
14170
14357
  function daemonCommand() {
@@ -14175,23 +14362,23 @@ function daemonCommand() {
14175
14362
  const prefix = opts.dryRun ? "[dry-run] " : "";
14176
14363
  for (const a of report.actions) {
14177
14364
  if (a.outcome === "kept") continue;
14178
- output12(`${prefix}${a.outcome}: ${a.stateDir}${a.pid ? ` (pid=${a.pid})` : ""} \u2014 ${a.reason}`);
14365
+ output13(`${prefix}${a.outcome}: ${a.stateDir}${a.pid ? ` (pid=${a.pid})` : ""} \u2014 ${a.reason}`);
14179
14366
  }
14180
- output12(
14367
+ output13(
14181
14368
  `${prefix}daemon prune: scanned ${report.scanned}, killed ${report.killed}, removed ${report.removed}, kept ${report.kept}`
14182
14369
  );
14183
14370
  });
14184
14371
  cmd.command("list").description("List daemon state directories and their status (read-only)").action(() => {
14185
14372
  const report = reapDaemons({ dryRun: true });
14186
14373
  if (report.scanned === 0) {
14187
- output12("daemon list: no daemon state directories found");
14374
+ output13("daemon list: no daemon state directories found");
14188
14375
  return;
14189
14376
  }
14190
14377
  for (const a of report.actions) {
14191
14378
  const would = a.outcome === "kept" ? "alive" : `stale \u2192 would ${a.outcome}`;
14192
- output12(`${would}: ${a.stateDir}${a.pid ? ` (pid=${a.pid})` : ""} \u2014 ${a.reason}`);
14379
+ output13(`${would}: ${a.stateDir}${a.pid ? ` (pid=${a.pid})` : ""} \u2014 ${a.reason}`);
14193
14380
  }
14194
- output12(`daemon list: ${report.scanned} state dir(s), ${report.kept} alive`);
14381
+ output13(`daemon list: ${report.scanned} state dir(s), ${report.kept} alive`);
14195
14382
  });
14196
14383
  return cmd;
14197
14384
  }
@@ -14243,7 +14430,7 @@ function formatProviderReport(report) {
14243
14430
  init_errors();
14244
14431
  init_logger();
14245
14432
  var log45 = createLogger({ layer: "cli", source: "doctor.ts" });
14246
- function output13(msg) {
14433
+ function output14(msg) {
14247
14434
  process.stdout.write(msg + "\n");
14248
14435
  }
14249
14436
  var LEVEL_ICON2 = {
@@ -14266,31 +14453,31 @@ function doctorCommand() {
14266
14453
  if (opts.providers) {
14267
14454
  const providerReport = checkProviders();
14268
14455
  if (opts.json) {
14269
- output13(JSON.stringify(providerReport, null, 2));
14456
+ output14(JSON.stringify(providerReport, null, 2));
14270
14457
  } else {
14271
- output13("mcp-graph doctor \u2014 LLM providers\n");
14458
+ output14("mcp-graph doctor \u2014 LLM providers\n");
14272
14459
  for (const line of formatProviderReport(providerReport)) {
14273
- output13(line);
14460
+ output14(line);
14274
14461
  }
14275
14462
  }
14276
14463
  return;
14277
14464
  }
14278
14465
  const report = await runDoctor(opts.dir);
14279
14466
  if (opts.json) {
14280
- output13(JSON.stringify(report, null, 2));
14467
+ output14(JSON.stringify(report, null, 2));
14281
14468
  } else {
14282
- output13("mcp-graph doctor\n");
14469
+ output14("mcp-graph doctor\n");
14283
14470
  for (const check of report.checks) {
14284
- output13(formatCheck(check));
14471
+ output14(formatCheck(check));
14285
14472
  }
14286
- output13("");
14287
- output13(
14473
+ output14("");
14474
+ output14(
14288
14475
  `Summary: ${report.summary.ok} ok, ${report.summary.warning} warnings, ${report.summary.error} errors`
14289
14476
  );
14290
14477
  if (report.passed) {
14291
- output13("\nAll critical checks passed.");
14478
+ output14("\nAll critical checks passed.");
14292
14479
  } else {
14293
- output13("\nSome critical checks failed. Fix errors above.");
14480
+ output14("\nSome critical checks failed. Fix errors above.");
14294
14481
  }
14295
14482
  }
14296
14483
  if (!report.passed) {
@@ -14374,13 +14561,13 @@ function pruneOrphanWorktrees(options) {
14374
14561
  }
14375
14562
  }
14376
14563
  try {
14377
- const output16 = execSync("git worktree prune --verbose", execOpts).toString();
14564
+ const output17 = execSync("git worktree prune --verbose", execOpts).toString();
14378
14565
  if (reapedBranches > 0 || reapedWorktrees > 0) {
14379
14566
  log46.info("shadow-branch:prune-ok", { reapedBranches, reapedWorktrees, ttlMs });
14380
14567
  } else {
14381
- log46.debug("shadow-branch:prune-ok", { reapedBranches, reapedWorktrees, output: output16 });
14568
+ log46.debug("shadow-branch:prune-ok", { reapedBranches, reapedWorktrees, output: output17 });
14382
14569
  }
14383
- return { pruned: true, reapedBranches, reapedWorktrees, output: output16 };
14570
+ return { pruned: true, reapedBranches, reapedWorktrees, output: output17 };
14384
14571
  } catch (err) {
14385
14572
  const error = String(err);
14386
14573
  log46.debug("shadow-branch:prune-failed", { error });
@@ -14391,7 +14578,7 @@ function pruneOrphanWorktrees(options) {
14391
14578
  // src/cli/commands/gc-cmd.ts
14392
14579
  init_logger();
14393
14580
  var log47 = createLogger({ layer: "cli", source: "gc.ts" });
14394
- function output14(msg) {
14581
+ function output15(msg) {
14395
14582
  process.stdout.write(msg + "\n");
14396
14583
  }
14397
14584
  function gcCommand() {
@@ -14401,9 +14588,9 @@ function gcCommand() {
14401
14588
  log47.info("cli:gc:start", { dir: opts.dir, ttlMs });
14402
14589
  const result = pruneOrphanWorktrees({ cwd: opts.dir, ttlMs });
14403
14590
  if (result.pruned) {
14404
- output14(`gc: reaped ${result.reapedBranches} branches, ${result.reapedWorktrees} worktrees`);
14591
+ output15(`gc: reaped ${result.reapedBranches} branches, ${result.reapedWorktrees} worktrees`);
14405
14592
  } else {
14406
- output14(`gc: failed \u2014 ${result.error ?? "unknown error"}`);
14593
+ output15(`gc: failed \u2014 ${result.error ?? "unknown error"}`);
14407
14594
  process.exit(1);
14408
14595
  }
14409
14596
  });
@@ -14412,7 +14599,7 @@ function gcCommand() {
14412
14599
  // src/cli/commands/skill-cmd.ts
14413
14600
  init_esm_shims();
14414
14601
  init_skill_registry();
14415
- function output15(msg) {
14602
+ function output16(msg) {
14416
14603
  process.stdout.write(msg + "\n");
14417
14604
  }
14418
14605
  function skillCommand() {
@@ -14426,26 +14613,26 @@ function skillCommand() {
14426
14613
  if (seen.has(s.name)) continue;
14427
14614
  seen.add(s.name);
14428
14615
  count += 1;
14429
- output15(`${s.name.padEnd(28)} [${s.category}] ${s.description}`);
14616
+ output16(`${s.name.padEnd(28)} [${s.category}] ${s.description}`);
14430
14617
  }
14431
14618
  }
14432
- if (count === 0) output15("Nenhuma skill encontrada.");
14433
- else output15(`
14619
+ if (count === 0) output16("Nenhuma skill encontrada.");
14620
+ else output16(`
14434
14621
  ${count} skill(s).`);
14435
14622
  });
14436
14623
  cmd.command("show <nome>").description("Imprime as instru\xE7\xF5es completas de uma skill").option("-d, --dir <dir>", "Raiz do projeto", process.cwd()).action((nome, opts) => {
14437
14624
  for (const root of defaultSkillRoots(opts.dir)) {
14438
14625
  const found = invokeSkill(root, nome);
14439
14626
  if (found) {
14440
- output15(`=== ${found.name} ===`);
14441
- output15(`[${found.category}] ${found.description}`);
14442
- if (found.phases.length > 0) output15(`fases: ${found.phases.join(", ")}`);
14443
- output15("");
14444
- output15(found.body);
14627
+ output16(`=== ${found.name} ===`);
14628
+ output16(`[${found.category}] ${found.description}`);
14629
+ if (found.phases.length > 0) output16(`fases: ${found.phases.join(", ")}`);
14630
+ output16("");
14631
+ output16(found.body);
14445
14632
  return;
14446
14633
  }
14447
14634
  }
14448
- output15(`Skill n\xE3o encontrada: ${nome}. Tente 'skill list'.`);
14635
+ output16(`Skill n\xE3o encontrada: ${nome}. Tente 'skill list'.`);
14449
14636
  process.exitCode = 1;
14450
14637
  });
14451
14638
  return cmd;
@@ -14472,6 +14659,7 @@ program.addCommand(daemonCommand());
14472
14659
  program.addCommand(doctorCommand());
14473
14660
  program.addCommand(gcCommand());
14474
14661
  program.addCommand(skillCommand());
14662
+ program.addCommand(profileCommand());
14475
14663
  function shouldLaunchTui() {
14476
14664
  const noArgs = process.argv.length <= 2;
14477
14665
  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.0",
3
+ "version": "0.4.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",