@mestreyoda/fabrica 0.2.38 → 0.2.40

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/index.js +234 -27
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -113905,8 +113905,8 @@ import fsSync from "node:fs";
113905
113905
  import path5 from "node:path";
113906
113906
  import { fileURLToPath as fileURLToPath3 } from "node:url";
113907
113907
  function getCurrentVersion() {
113908
- if ("0.2.38") {
113909
- return "0.2.38";
113908
+ if ("0.2.40") {
113909
+ return "0.2.40";
113910
113910
  }
113911
113911
  try {
113912
113912
  const pkgPath = path5.join(THIS_DIR, "..", "..", "package.json");
@@ -130510,6 +130510,10 @@ async function runIssueDoctor(opts) {
130510
130510
  const convergenceAction = issueRuntime?.lastConvergenceAction ?? null;
130511
130511
  const retryCount = issueRuntime?.lastConvergenceRetryCount ?? 0;
130512
130512
  const convergenceReason = issueRuntime?.lastConvergenceReason ?? issueRuntime?.inconclusiveCompletionReason ?? null;
130513
+ const convergenceHeadSha = issueRuntime?.lastConvergenceHeadSha ?? null;
130514
+ const currentHeadSha = issueRuntime?.currentPrHeadSha ?? issueRuntime?.lastHeadSha ?? prStatus?.sourceBranch ?? null;
130515
+ const headShaChangedSinceLastConvergence = convergenceHeadSha && currentHeadSha ? convergenceHeadSha !== currentHeadSha : null;
130516
+ const progressState = issueRuntime?.sessionCompletedAt ? "completed" : issueRuntime?.firstWorkerActivityAt ? "active" : issueRuntime?.agentAcceptedAt ? "accepted_idle" : "no_dispatch";
130513
130517
  const summaryParts = [
130514
130518
  hasArtifact ? "artifact_present" : "artifact_missing",
130515
130519
  convergenceCause ? `cause=${convergenceCause}` : "cause=none",
@@ -130531,12 +130535,22 @@ async function runIssueDoctor(opts) {
130531
130535
  issueId: opts.issueId,
130532
130536
  issueRuntime,
130533
130537
  hasArtifact,
130538
+ lifecycle: {
130539
+ dispatchCycleId: issueRuntime?.lastDispatchCycleId ?? null,
130540
+ dispatchRunId: issueRuntime?.dispatchRunId ?? null,
130541
+ agentAcceptedAt: issueRuntime?.agentAcceptedAt ?? null,
130542
+ firstWorkerActivityAt: issueRuntime?.firstWorkerActivityAt ?? null,
130543
+ sessionCompletedAt: issueRuntime?.sessionCompletedAt ?? null,
130544
+ progressState
130545
+ },
130534
130546
  convergence: {
130535
130547
  cause: convergenceCause,
130536
130548
  action: convergenceAction,
130537
130549
  retryCount,
130538
130550
  reason: convergenceReason,
130539
- at: issueRuntime?.lastConvergenceAt ?? null
130551
+ at: issueRuntime?.lastConvergenceAt ?? null,
130552
+ headSha: convergenceHeadSha,
130553
+ headShaChangedSinceLastConvergence
130540
130554
  },
130541
130555
  pr: prStatus ? {
130542
130556
  url: prStatus.url ?? null,
@@ -130565,9 +130579,17 @@ function formatIssueDoctor(result) {
130565
130579
  ` PR: ${result.pr?.url ?? "n/a"} (${result.pr?.state ?? "unknown"})`,
130566
130580
  ` Issue: ${result.issue?.url ?? "n/a"} (${result.issue?.state ?? "unknown"})`,
130567
130581
  ` Labels: ${result.issue?.labels?.join(", ") ?? "n/a"}`,
130582
+ ` Dispatch cycle: ${result.lifecycle.dispatchCycleId ?? "n/a"}`,
130583
+ ` Dispatch run: ${result.lifecycle.dispatchRunId ?? "n/a"}`,
130584
+ ` Progress state: ${result.lifecycle.progressState}`,
130585
+ ` Agent accepted: ${result.lifecycle.agentAcceptedAt ?? "n/a"}`,
130586
+ ` First worker activity: ${result.lifecycle.firstWorkerActivityAt ?? "n/a"}`,
130587
+ ` Session completed: ${result.lifecycle.sessionCompletedAt ?? "n/a"}`,
130568
130588
  ` Convergence cause: ${result.convergence.cause ?? "none"}`,
130569
130589
  ` Convergence action: ${result.convergence.action ?? "none"}`,
130570
130590
  ` Retry count: ${result.convergence.retryCount}`,
130591
+ ` Convergence head SHA: ${result.convergence.headSha ?? "n/a"}`,
130592
+ ` Head SHA changed since last convergence: ${result.convergence.headShaChangedSinceLastConvergence == null ? "unknown" : result.convergence.headShaChangedSinceLastConvergence ? "yes" : "no"}`,
130571
130593
  ` Last reason: ${result.convergence.reason ?? "n/a"}`,
130572
130594
  ` Suggested next action: ${result.recommendation.likelyNextAction}`
130573
130595
  ];
@@ -131368,6 +131390,51 @@ init_workflow();
131368
131390
  init_context3();
131369
131391
  init_labels();
131370
131392
 
131393
+ // lib/services/doctor-snapshot.ts
131394
+ init_audit();
131395
+ async function captureIssueDoctorSnapshot(opts) {
131396
+ try {
131397
+ const result = await runIssueDoctor({
131398
+ workspacePath: opts.workspaceDir,
131399
+ projectSlug: opts.projectSlug,
131400
+ issueId: opts.issueId,
131401
+ runCommand: opts.runCommand,
131402
+ pluginConfig: opts.pluginConfig
131403
+ });
131404
+ await log(opts.workspaceDir, opts.event, {
131405
+ projectSlug: opts.projectSlug,
131406
+ issueId: opts.issueId,
131407
+ trigger: opts.trigger,
131408
+ summary: result.recommendation.summary,
131409
+ likelyNextAction: result.recommendation.likelyNextAction,
131410
+ doctor: {
131411
+ artifact: result.hasArtifact,
131412
+ progressState: result.lifecycle.progressState,
131413
+ dispatchCycleId: result.lifecycle.dispatchCycleId,
131414
+ dispatchRunId: result.lifecycle.dispatchRunId,
131415
+ prUrl: result.pr?.url ?? null,
131416
+ prState: result.pr?.state ?? null,
131417
+ labels: result.issue?.labels ?? [],
131418
+ convergenceCause: result.convergence.cause,
131419
+ convergenceAction: result.convergence.action,
131420
+ convergenceRetryCount: result.convergence.retryCount,
131421
+ convergenceHeadSha: result.convergence.headSha,
131422
+ headShaChangedSinceLastConvergence: result.convergence.headShaChangedSinceLastConvergence
131423
+ },
131424
+ ...opts.extra ?? {}
131425
+ });
131426
+ } catch (error48) {
131427
+ await log(opts.workspaceDir, `${opts.event}_failed`, {
131428
+ projectSlug: opts.projectSlug,
131429
+ issueId: opts.issueId,
131430
+ trigger: opts.trigger,
131431
+ error: error48 instanceof Error ? error48.message : String(error48),
131432
+ ...opts.extra ?? {}
131433
+ }).catch(() => {
131434
+ });
131435
+ }
131436
+ }
131437
+
131371
131438
  // lib/services/post-pr-convergence.ts
131372
131439
  init_types3();
131373
131440
  function classifyConvergenceCause(reason) {
@@ -132032,6 +132099,23 @@ ${validationReason}`;
132032
132099
  lastConvergenceHeadSha: convergence.progressHeadSha
132033
132100
  }).catch(() => {
132034
132101
  });
132102
+ if (convergenceIssueRuntime?.currentPrUrl || convergence.action === "escalate_human") {
132103
+ await captureIssueDoctorSnapshot({
132104
+ workspaceDir: opts.workspaceDir,
132105
+ projectSlug: context2.projectSlug,
132106
+ issueId: context2.issueId,
132107
+ runCommand: opts.runCommand,
132108
+ pluginConfig: opts.pluginConfig,
132109
+ event: "doctor_snapshot",
132110
+ trigger: convergence.action === "escalate_human" ? "worker_completion_escalated" : "worker_completion_blocked_with_artifact",
132111
+ extra: {
132112
+ convergenceCause: convergence.cause,
132113
+ convergenceAction: convergence.action,
132114
+ convergenceRetryCount: convergence.retryCount
132115
+ }
132116
+ }).catch(() => {
132117
+ });
132118
+ }
132035
132119
  await executeCompletion({
132036
132120
  workspaceDir: opts.workspaceDir,
132037
132121
  projectSlug: context2.projectSlug,
@@ -132719,6 +132803,22 @@ async function checkWorkerHealth(opts) {
132719
132803
  toLabel: convergence.targetLabel,
132720
132804
  deliveryState
132721
132805
  });
132806
+ if (runCommand) {
132807
+ await captureIssueDoctorSnapshot({
132808
+ workspaceDir,
132809
+ projectSlug,
132810
+ issueId: issueIdNum,
132811
+ runCommand,
132812
+ event: "doctor_snapshot",
132813
+ trigger: convergence.action === "escalate_human" ? "completion_recovery_escalated" : "completion_recovery_requeued",
132814
+ extra: {
132815
+ convergenceCause: convergence.cause,
132816
+ convergenceAction: convergence.action,
132817
+ convergenceRetryCount: convergence.retryCount
132818
+ }
132819
+ }).catch(() => {
132820
+ });
132821
+ }
132722
132822
  }
132723
132823
  }
132724
132824
  fixes.push(fix);
@@ -133087,6 +133187,23 @@ async function checkWorkerHealth(opts) {
133087
133187
  idleMinutes: Math.round(quietMinutes),
133088
133188
  deliveryState
133089
133189
  });
133190
+ if (runCommand) {
133191
+ await captureIssueDoctorSnapshot({
133192
+ workspaceDir,
133193
+ projectSlug,
133194
+ issueId: issueIdNum,
133195
+ runCommand,
133196
+ event: "doctor_snapshot",
133197
+ trigger: convergence.action === "escalate_human" ? "stalled_with_artifact_escalated" : "stalled_with_artifact_requeued",
133198
+ extra: {
133199
+ convergenceCause: convergence.cause,
133200
+ convergenceAction: convergence.action,
133201
+ convergenceRetryCount: convergence.retryCount,
133202
+ idleMinutes: Math.round(quietMinutes)
133203
+ }
133204
+ }).catch(() => {
133205
+ });
133206
+ }
133090
133207
  }
133091
133208
  }
133092
133209
  fixes.push(fix);
@@ -149107,12 +149224,29 @@ async function readAuditLines(filePath) {
149107
149224
  }
149108
149225
  return entries;
149109
149226
  }
149227
+ function keyFor(entry) {
149228
+ const projectSlug = entry.projectSlug ?? entry.project ?? null;
149229
+ const issueId = entry.issueId ?? entry.issue ?? null;
149230
+ if (!projectSlug || issueId == null) return null;
149231
+ return `${projectSlug}:${issueId}`;
149232
+ }
149233
+ function normalizeCause(entry) {
149234
+ const cause = entry.convergenceCause ?? entry.reason ?? null;
149235
+ return cause ? String(cause) : null;
149236
+ }
149110
149237
  async function computeMetrics(workspaceDir) {
149111
149238
  const auditLogPath = join4(workspaceDir, DATA_DIR, "log", "audit.log");
149239
+ const bakEntries3 = await readAuditLines(`${auditLogPath}.3.bak`);
149112
149240
  const bakEntries2 = await readAuditLines(`${auditLogPath}.2.bak`);
149113
149241
  const bakEntries = await readAuditLines(`${auditLogPath}.bak`);
149114
149242
  const currentEntries = await readAuditLines(auditLogPath);
149115
- let entries = [...bakEntries2, ...bakEntries, ...currentEntries];
149243
+ const entries = [...bakEntries3, ...bakEntries2, ...bakEntries, ...currentEntries];
149244
+ const projectsData = await readProjects(workspaceDir).catch(() => ({ projects: {} }));
149245
+ const stackByProject = /* @__PURE__ */ new Map();
149246
+ for (const [slug, project] of Object.entries(projectsData.projects ?? {})) {
149247
+ const stack = project.stack ?? project.environment?.stack ?? null;
149248
+ if (stack) stackByProject.set(slug, String(stack));
149249
+ }
149116
149250
  const entriesScanned = entries.length;
149117
149251
  let dispatches = 0;
149118
149252
  let completionsTotal = 0;
@@ -149122,16 +149256,40 @@ async function computeMetrics(workspaceDir) {
149122
149256
  let completionsOther = 0;
149123
149257
  let conflictsDetected = 0;
149124
149258
  let sessionBudgetResets = 0;
149259
+ let humanEscalations = 0;
149260
+ const causeCounts = {};
149125
149261
  const dispatchTimes = /* @__PURE__ */ new Map();
149262
+ const firstPrTimes = /* @__PURE__ */ new Map();
149126
149263
  const completionDeltas = [];
149264
+ const firstPrDeltas = [];
149265
+ const stackMetrics = /* @__PURE__ */ new Map();
149266
+ function stackBucket(entry) {
149267
+ const slug = entry.projectSlug ?? entry.project ?? null;
149268
+ const stack = entry.stack ?? (slug ? stackByProject.get(String(slug)) : null) ?? "unknown";
149269
+ if (!stackMetrics.has(String(stack))) {
149270
+ stackMetrics.set(String(stack), {
149271
+ issues: /* @__PURE__ */ new Set(),
149272
+ dispatches: 0,
149273
+ escalations: 0,
149274
+ causeCounts: {},
149275
+ completionDeltas: [],
149276
+ firstPrDeltas: []
149277
+ });
149278
+ }
149279
+ return String(stack);
149280
+ }
149127
149281
  for (const entry of entries) {
149282
+ const issueKey = keyFor(entry);
149283
+ const stack = stackBucket(entry);
149284
+ const stackBucketState = stackMetrics.get(stack);
149285
+ if (issueKey) stackBucketState.issues.add(issueKey);
149128
149286
  switch (entry.event) {
149129
- case "dispatch":
149287
+ case "dispatch": {
149130
149288
  dispatches++;
149131
- if (entry.issue != null && entry.project) {
149132
- dispatchTimes.set(`${entry.project}:${entry.issue}`, Date.parse(entry.ts));
149133
- }
149289
+ stackBucketState.dispatches++;
149290
+ if (issueKey) dispatchTimes.set(issueKey, Date.parse(entry.ts));
149134
149291
  break;
149292
+ }
149135
149293
  case "work_finish": {
149136
149294
  completionsTotal++;
149137
149295
  const result = String(entry.result ?? "");
@@ -149139,14 +149297,13 @@ async function computeMetrics(workspaceDir) {
149139
149297
  else if (result === "pass") completionsPass++;
149140
149298
  else if (result === "fail") completionsFail++;
149141
149299
  else completionsOther++;
149142
- if (entry.issue != null && entry.project) {
149143
- const key = `${entry.project}:${entry.issue}`;
149144
- const dispatchTime = dispatchTimes.get(key);
149145
- if (dispatchTime !== void 0) {
149146
- const completionTime = Date.parse(entry.ts);
149147
- if (!isNaN(completionTime) && completionTime > dispatchTime) {
149148
- completionDeltas.push((completionTime - dispatchTime) / 6e4);
149149
- }
149300
+ if (issueKey) {
149301
+ const dispatchTime = dispatchTimes.get(issueKey);
149302
+ const completionTime = Date.parse(entry.ts);
149303
+ if (dispatchTime !== void 0 && !Number.isNaN(completionTime) && completionTime > dispatchTime) {
149304
+ const delta = (completionTime - dispatchTime) / 6e4;
149305
+ completionDeltas.push(delta);
149306
+ stackBucketState.completionDeltas.push(delta);
149150
149307
  }
149151
149308
  }
149152
149309
  break;
@@ -149160,9 +149317,47 @@ async function computeMetrics(workspaceDir) {
149160
149317
  case "session_budget_reset":
149161
149318
  sessionBudgetResets++;
149162
149319
  break;
149320
+ case "pr_discovered_via_polling":
149321
+ case "pr_updated_via_polling": {
149322
+ if (issueKey && !firstPrTimes.has(issueKey)) {
149323
+ const prTime = Date.parse(entry.ts);
149324
+ const dispatchTime = dispatchTimes.get(issueKey);
149325
+ firstPrTimes.set(issueKey, prTime);
149326
+ if (dispatchTime !== void 0 && !Number.isNaN(prTime) && prTime > dispatchTime) {
149327
+ const delta = (prTime - dispatchTime) / 6e4;
149328
+ firstPrDeltas.push(delta);
149329
+ stackBucketState.firstPrDeltas.push(delta);
149330
+ }
149331
+ }
149332
+ break;
149333
+ }
149334
+ case "worker_completion_skipped":
149335
+ case "doctor_snapshot":
149336
+ case "health_fix_applied": {
149337
+ const cause = normalizeCause(entry);
149338
+ if (cause) {
149339
+ causeCounts[cause] = (causeCounts[cause] ?? 0) + 1;
149340
+ stackBucketState.causeCounts[cause] = (stackBucketState.causeCounts[cause] ?? 0) + 1;
149341
+ }
149342
+ if (entry.convergenceAction === "escalate_human" || entry.action === "escalate_human") {
149343
+ humanEscalations++;
149344
+ stackBucketState.escalations++;
149345
+ }
149346
+ break;
149347
+ }
149163
149348
  }
149164
149349
  }
149165
- const avgDispatchToCompletionMinutes = completionDeltas.length > 0 ? completionDeltas.reduce((a, b) => a + b, 0) / completionDeltas.length : null;
149350
+ const avg = (values) => values.length ? values.reduce((a, b) => a + b, 0) / values.length : null;
149351
+ const stackMetricsObject = Object.fromEntries(
149352
+ [...stackMetrics.entries()].map(([stack, data]) => [stack, {
149353
+ issues: data.issues.size,
149354
+ dispatches: data.dispatches,
149355
+ escalations: data.escalations,
149356
+ causeCounts: data.causeCounts,
149357
+ avgDispatchToCompletionMinutes: avg(data.completionDeltas),
149358
+ avgDispatchToFirstPrMinutes: avg(data.firstPrDeltas)
149359
+ }])
149360
+ );
149166
149361
  return {
149167
149362
  entriesScanned,
149168
149363
  dispatches,
@@ -149173,28 +149368,40 @@ async function computeMetrics(workspaceDir) {
149173
149368
  fail: completionsFail,
149174
149369
  other: completionsOther
149175
149370
  },
149176
- avgDispatchToCompletionMinutes,
149371
+ avgDispatchToCompletionMinutes: avg(completionDeltas),
149372
+ avgDispatchToFirstPrMinutes: avg(firstPrDeltas),
149177
149373
  conflictsDetected,
149178
149374
  sessionBudgetResets,
149375
+ humanEscalations,
149376
+ causeCounts,
149377
+ stackMetrics: stackMetricsObject,
149179
149378
  auditLogPath
149180
149379
  };
149181
149380
  }
149182
149381
  function formatMetrics(metrics2) {
149183
149382
  const lines = [
149184
- `Fabrica \u2014 Metricas (${metrics2.entriesScanned} entradas do audit.log)`,
149383
+ `Fabrica \u2014 M\xE9tricas (${metrics2.entriesScanned} entradas do audit.log)`,
149185
149384
  ` Dispatches: ${metrics2.dispatches}`
149186
149385
  ];
149187
149386
  const c = metrics2.completions;
149188
- lines.push(
149189
- ` Conclusoes: ${c.total} (done: ${c.done}, pass: ${c.pass}, fail: ${c.fail}${c.other > 0 ? `, other: ${c.other}` : ""})`
149190
- );
149191
- if (metrics2.avgDispatchToCompletionMinutes !== null) {
149192
- lines.push(` Tempo medio dispatch \u2192 completion: ${metrics2.avgDispatchToCompletionMinutes.toFixed(1)} min`);
149193
- } else {
149194
- lines.push(` Tempo medio dispatch \u2192 completion: n/a`);
149195
- }
149387
+ lines.push(` Conclus\xF5es: ${c.total} (done: ${c.done}, pass: ${c.pass}, fail: ${c.fail}${c.other > 0 ? `, other: ${c.other}` : ""})`);
149388
+ lines.push(` Tempo m\xE9dio dispatch \u2192 completion: ${metrics2.avgDispatchToCompletionMinutes?.toFixed(1) ?? "n/a"} min`);
149389
+ lines.push(` Tempo m\xE9dio dispatch \u2192 primeira PR: ${metrics2.avgDispatchToFirstPrMinutes?.toFixed(1) ?? "n/a"} min`);
149196
149390
  lines.push(` Conflitos detectados: ${metrics2.conflictsDetected}`);
149197
149391
  lines.push(` Session budget resets: ${metrics2.sessionBudgetResets}`);
149392
+ lines.push(` Escalonamentos humanos: ${metrics2.humanEscalations}`);
149393
+ if (Object.keys(metrics2.causeCounts).length > 0) {
149394
+ lines.push(" Causas tipadas:");
149395
+ for (const [cause, count] of Object.entries(metrics2.causeCounts).sort((a, b) => b[1] - a[1])) {
149396
+ lines.push(` - ${cause}: ${count}`);
149397
+ }
149398
+ }
149399
+ if (Object.keys(metrics2.stackMetrics).length > 0) {
149400
+ lines.push(" Por stack:");
149401
+ for (const [stack, data] of Object.entries(metrics2.stackMetrics)) {
149402
+ lines.push(` - ${stack}: issues=${data.issues}, dispatches=${data.dispatches}, escalations=${data.escalations}, avgPR=${data.avgDispatchToFirstPrMinutes?.toFixed(1) ?? "n/a"}m, avgDone=${data.avgDispatchToCompletionMinutes?.toFixed(1) ?? "n/a"}m`);
149403
+ }
149404
+ }
149198
149405
  return lines.join("\n");
149199
149406
  }
149200
149407
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mestreyoda/fabrica",
3
- "version": "0.2.38",
3
+ "version": "0.2.40",
4
4
  "description": "Autonomous software engineering pipeline for OpenClaw. Turns ideas into deployed code via intake, dispatch, review, test, and merge.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",