@mestreyoda/fabrica 0.2.36 → 0.2.38

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 +168 -9
  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.36") {
113909
- return "0.2.36";
113908
+ if ("0.2.38") {
113909
+ return "0.2.38";
113910
113910
  }
113911
113911
  try {
113912
113912
  const pkgPath = path5.join(THIS_DIR, "..", "..", "package.json");
@@ -130477,6 +130477,135 @@ function createProjectStatusTool(ctx) {
130477
130477
  });
130478
130478
  }
130479
130479
 
130480
+ // lib/setup/doctor-run.ts
130481
+ async function runIssueDoctor(opts) {
130482
+ const data = await readProjects(opts.workspacePath);
130483
+ const project = data.projects[opts.projectSlug];
130484
+ if (!project) throw new Error(`Project not found: ${opts.projectSlug}`);
130485
+ const issueRuntime = getIssueRuntime(project, opts.issueId) ?? null;
130486
+ const hasArtifact = Boolean(
130487
+ issueRuntime?.currentPrUrl || issueRuntime?.currentPrNumber || issueRuntime?.artifactOfRecord
130488
+ );
130489
+ const { provider } = await createProvider({
130490
+ repo: project.repo,
130491
+ provider: project.provider,
130492
+ providerProfile: project.providerProfile,
130493
+ runCommand: opts.runCommand,
130494
+ pluginConfig: opts.pluginConfig
130495
+ });
130496
+ let prStatus = null;
130497
+ try {
130498
+ const pr = await provider.getPrStatus(opts.issueId);
130499
+ if (pr.url || pr.number) prStatus = pr;
130500
+ } catch {
130501
+ prStatus = null;
130502
+ }
130503
+ let issue2 = null;
130504
+ try {
130505
+ issue2 = await provider.getIssue(opts.issueId);
130506
+ } catch {
130507
+ issue2 = null;
130508
+ }
130509
+ const convergenceCause = issueRuntime?.lastConvergenceCause ?? null;
130510
+ const convergenceAction = issueRuntime?.lastConvergenceAction ?? null;
130511
+ const retryCount = issueRuntime?.lastConvergenceRetryCount ?? 0;
130512
+ const convergenceReason = issueRuntime?.lastConvergenceReason ?? issueRuntime?.inconclusiveCompletionReason ?? null;
130513
+ const summaryParts = [
130514
+ hasArtifact ? "artifact_present" : "artifact_missing",
130515
+ convergenceCause ? `cause=${convergenceCause}` : "cause=none",
130516
+ convergenceAction ? `action=${convergenceAction}` : "action=none",
130517
+ retryCount ? `retries=${retryCount}` : "retries=0",
130518
+ prStatus?.state ? `pr=${prStatus.state}` : "pr=unknown"
130519
+ ];
130520
+ const likelyNextAction = (() => {
130521
+ if (convergenceAction === "escalate_human") return "human_intervention";
130522
+ if (convergenceCause === "invalid_qa_evidence") return "repair_qa_evidence";
130523
+ if (convergenceCause === "merge_conflict") return "repair_merge_conflict";
130524
+ if (convergenceCause === "stalled_with_artifact") return "force_convergence_review";
130525
+ if (hasArtifact) return "post_pr_convergence";
130526
+ return "redispatch_or_investigate";
130527
+ })();
130528
+ return {
130529
+ projectSlug: project.slug,
130530
+ projectName: project.name,
130531
+ issueId: opts.issueId,
130532
+ issueRuntime,
130533
+ hasArtifact,
130534
+ convergence: {
130535
+ cause: convergenceCause,
130536
+ action: convergenceAction,
130537
+ retryCount,
130538
+ reason: convergenceReason,
130539
+ at: issueRuntime?.lastConvergenceAt ?? null
130540
+ },
130541
+ pr: prStatus ? {
130542
+ url: prStatus.url ?? null,
130543
+ state: prStatus.state ?? null,
130544
+ number: prStatus.number ?? null,
130545
+ mergeable: prStatus.mergeable ?? null,
130546
+ currentIssueMatch: prStatus.currentIssueMatch ?? null,
130547
+ sourceBranch: prStatus.sourceBranch ?? null
130548
+ } : null,
130549
+ issue: issue2 ? {
130550
+ url: issue2.web_url ?? null,
130551
+ state: issue2.state ?? null,
130552
+ labels: issue2.labels ?? [],
130553
+ title: issue2.title ?? null
130554
+ } : null,
130555
+ recommendation: {
130556
+ summary: summaryParts.join(" | "),
130557
+ likelyNextAction
130558
+ }
130559
+ };
130560
+ }
130561
+ function formatIssueDoctor(result) {
130562
+ const lines = [
130563
+ `Issue run doctor \u2014 ${result.projectSlug}#${result.issueId}`,
130564
+ ` Artifact: ${result.hasArtifact ? "yes" : "no"}`,
130565
+ ` PR: ${result.pr?.url ?? "n/a"} (${result.pr?.state ?? "unknown"})`,
130566
+ ` Issue: ${result.issue?.url ?? "n/a"} (${result.issue?.state ?? "unknown"})`,
130567
+ ` Labels: ${result.issue?.labels?.join(", ") ?? "n/a"}`,
130568
+ ` Convergence cause: ${result.convergence.cause ?? "none"}`,
130569
+ ` Convergence action: ${result.convergence.action ?? "none"}`,
130570
+ ` Retry count: ${result.convergence.retryCount}`,
130571
+ ` Last reason: ${result.convergence.reason ?? "n/a"}`,
130572
+ ` Suggested next action: ${result.recommendation.likelyNextAction}`
130573
+ ];
130574
+ return lines.join("\n");
130575
+ }
130576
+
130577
+ // lib/tools/admin/doctor-issue.ts
130578
+ function createDoctorIssueTool(ctx) {
130579
+ return (toolCtx) => ({
130580
+ name: "doctor_issue",
130581
+ label: "Doctor Issue",
130582
+ description: "Inspect one Fabrica issue/run with convergence metadata, PR context, issue labels, and a recommended next action.",
130583
+ parameters: {
130584
+ type: "object",
130585
+ required: ["projectSlug", "issueId"],
130586
+ properties: {
130587
+ projectSlug: { type: "string", description: "Project slug (for example: fabrica or my-project)." },
130588
+ issueId: { type: "number", description: "Issue number to inspect." }
130589
+ }
130590
+ },
130591
+ async execute(_id, params) {
130592
+ const workspaceDir = requireWorkspaceDir(toolCtx);
130593
+ const projectSlug = String(params.projectSlug ?? "").trim();
130594
+ const issueId = Number(params.issueId);
130595
+ if (!projectSlug) throw new Error("projectSlug is required");
130596
+ if (!Number.isFinite(issueId)) throw new Error("issueId must be a number");
130597
+ const result = await runIssueDoctor({
130598
+ workspacePath: workspaceDir,
130599
+ projectSlug,
130600
+ issueId,
130601
+ runCommand: ctx.runCommand,
130602
+ pluginConfig: ctx.pluginConfig
130603
+ });
130604
+ return jsonResult(result);
130605
+ }
130606
+ });
130607
+ }
130608
+
130480
130609
  // lib/tools/admin/project-register.ts
130481
130610
  import fs24 from "node:fs/promises";
130482
130611
  import path24 from "node:path";
@@ -131291,9 +131420,12 @@ function decidePostPrConvergence(params) {
131291
131420
  const { workflow, issueRuntime, reason, feedbackQueueLabel } = params;
131292
131421
  const cause = classifyConvergenceCause(reason);
131293
131422
  const hasArtifact = hasReviewableArtifact(issueRuntime);
131423
+ const progressHeadSha = issueRuntime?.currentPrHeadSha ?? issueRuntime?.lastHeadSha ?? issueRuntime?.artifactOfRecord?.headSha ?? null;
131294
131424
  const previousCause = issueRuntime?.lastConvergenceCause ?? null;
131295
131425
  const previousCount = issueRuntime?.lastConvergenceRetryCount ?? 0;
131296
- const retryCount = previousCause === cause ? previousCount + 1 : 1;
131426
+ const previousHeadSha = issueRuntime?.lastConvergenceHeadSha ?? null;
131427
+ const sameHeadSha = !progressHeadSha || !previousHeadSha ? true : progressHeadSha === previousHeadSha;
131428
+ const retryCount = previousCause === cause && sameHeadSha ? previousCount + 1 : 1;
131297
131429
  const maxRetries = getConvergenceRetryBudget(cause);
131298
131430
  const holdLabel = getPreferredHoldLabel(workflow);
131299
131431
  const shouldEscalate = hasArtifact && retryCount > maxRetries && Boolean(holdLabel);
@@ -131303,7 +131435,8 @@ function decidePostPrConvergence(params) {
131303
131435
  targetLabel: shouldEscalate ? holdLabel ?? feedbackQueueLabel : feedbackQueueLabel,
131304
131436
  retryCount,
131305
131437
  maxRetries,
131306
- hasArtifact
131438
+ hasArtifact,
131439
+ progressHeadSha
131307
131440
  };
131308
131441
  }
131309
131442
 
@@ -131895,7 +132028,8 @@ ${validationReason}`;
131895
132028
  lastConvergenceAction: convergence.action,
131896
132029
  lastConvergenceRetryCount: convergence.retryCount,
131897
132030
  lastConvergenceReason: validationReason,
131898
- lastConvergenceAt: (/* @__PURE__ */ new Date()).toISOString()
132031
+ lastConvergenceAt: (/* @__PURE__ */ new Date()).toISOString(),
132032
+ lastConvergenceHeadSha: convergence.progressHeadSha
131899
132033
  }).catch(() => {
131900
132034
  });
131901
132035
  await executeCompletion({
@@ -132021,7 +132155,8 @@ ${validationReason}`;
132021
132155
  lastConvergenceAction: null,
132022
132156
  lastConvergenceRetryCount: 0,
132023
132157
  lastConvergenceReason: null,
132024
- lastConvergenceAt: null
132158
+ lastConvergenceAt: null,
132159
+ lastConvergenceHeadSha: null
132025
132160
  }).catch(() => {
132026
132161
  });
132027
132162
  } else {
@@ -132032,7 +132167,8 @@ ${validationReason}`;
132032
132167
  lastConvergenceAction: null,
132033
132168
  lastConvergenceRetryCount: 0,
132034
132169
  lastConvergenceReason: null,
132035
- lastConvergenceAt: null
132170
+ lastConvergenceAt: null,
132171
+ lastConvergenceHeadSha: null
132036
132172
  }).catch(() => {
132037
132173
  });
132038
132174
  }
@@ -132555,7 +132691,8 @@ async function checkWorkerHealth(opts) {
132555
132691
  lastConvergenceAction: convergence.action,
132556
132692
  lastConvergenceRetryCount: convergence.retryCount,
132557
132693
  lastConvergenceReason: inconclusiveReason,
132558
- lastConvergenceAt: (/* @__PURE__ */ new Date()).toISOString()
132694
+ lastConvergenceAt: (/* @__PURE__ */ new Date()).toISOString(),
132695
+ lastConvergenceHeadSha: convergence.progressHeadSha
132559
132696
  }).catch(() => {
132560
132697
  });
132561
132698
  fix.fixed = true;
@@ -132938,7 +133075,8 @@ async function checkWorkerHealth(opts) {
132938
133075
  lastConvergenceAction: convergence.action,
132939
133076
  lastConvergenceRetryCount: convergence.retryCount,
132940
133077
  lastConvergenceReason: "stalled_with_artifact",
132941
- lastConvergenceAt: (/* @__PURE__ */ new Date()).toISOString()
133078
+ lastConvergenceAt: (/* @__PURE__ */ new Date()).toISOString(),
133079
+ lastConvergenceHeadSha: convergence.progressHeadSha
132942
133080
  }).catch(() => {
132943
133081
  });
132944
133082
  fix.fixed = true;
@@ -149533,6 +149671,26 @@ function registerCli(program, ctx) {
149533
149671
  ${result.checks.length} checks: ${result.errors} errors, ${result.warnings} warnings`);
149534
149672
  process.exit(result.errors > 0 ? 1 : 0);
149535
149673
  });
149674
+ doctor.command("issue").description("Inspect one Fabrica issue/run with convergence and PR context").requiredOption("-p, --project <slug>", "Project slug").requiredOption("-i, --issue <id>", "Issue number").option("-w, --workspace <path>", "Workspace directory").option("--json", "Emit machine-readable JSON").action(async (opts) => {
149675
+ const workspaceDir = requireWorkspaceDir2(ctx.runtime, opts.workspace);
149676
+ const issueId = Number.parseInt(String(opts.issue), 10);
149677
+ if (!Number.isFinite(issueId)) {
149678
+ console.error(`Invalid issue id: ${opts.issue}`);
149679
+ process.exit(1);
149680
+ }
149681
+ const result = await runIssueDoctor({
149682
+ workspacePath: workspaceDir,
149683
+ projectSlug: String(opts.project),
149684
+ issueId,
149685
+ runCommand: ctx.runCommand,
149686
+ pluginConfig: ctx.pluginConfig
149687
+ });
149688
+ if (opts.json) {
149689
+ console.log(JSON.stringify(result, null, 2));
149690
+ return;
149691
+ }
149692
+ console.log(formatIssueDoctor(result));
149693
+ });
149536
149694
  doctor.option("-w, --workspace <path>", "Workspace directory").option("--fix", "Apply fixes for detected issues").action(async (opts) => {
149537
149695
  const workspaceDir = requireWorkspaceDir2(ctx.runtime, opts.workspace);
149538
149696
  const result = await runDoctor({ workspacePath: workspaceDir, fix: opts.fix ?? false, pluginConfig: ctx.pluginConfig });
@@ -150903,6 +151061,7 @@ var plugin = {
150903
151061
  api.registerTool(createTaskListTool(ctx), { names: ["task_list"] });
150904
151062
  api.registerTool(createTasksStatusTool(ctx), { names: ["tasks_status"] });
150905
151063
  api.registerTool(createProjectStatusTool(ctx), { names: ["project_status"] });
151064
+ api.registerTool(createDoctorIssueTool(ctx), { names: ["doctor_issue"] });
150906
151065
  api.registerTool(createProjectRegisterTool(ctx), { names: ["project_register"] });
150907
151066
  api.registerTool(createHealthTool(ctx), { names: ["health"] });
150908
151067
  api.registerTool(createSyncLabelsTool(ctx), { names: ["sync_labels"] });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mestreyoda/fabrica",
3
- "version": "0.2.36",
3
+ "version": "0.2.38",
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",