tend-cli 0.6.1 → 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.
@@ -743,6 +743,10 @@ const FAILURE_CLASSES = [
743
743
  "tool-timeout",
744
744
  "rate-limit",
745
745
  "model-tool-failure",
746
+ "sandbox-setup-failed",
747
+ "patch-conflict",
748
+ "unowned-patch",
749
+ "final-integration-failed",
746
750
  "no-edit",
747
751
  "no-op",
748
752
  "regression",
@@ -809,7 +813,11 @@ const FindingSchema = z.object({
809
813
  "regression",
810
814
  "typecheck",
811
815
  "session-error",
812
- "needs-lockfile-update"
816
+ "needs-lockfile-update",
817
+ "sandbox-setup-failed",
818
+ "patch-conflict",
819
+ "unowned-patch",
820
+ "final-integration-failed"
813
821
  ]).optional(),
814
822
  revertDetail: z.string().optional(),
815
823
  finalFailureClass: z.enum(FAILURE_CLASSES).optional(),
@@ -1389,6 +1397,12 @@ var Snapshot = class Snapshot {
1389
1397
  sha: this.sha
1390
1398
  };
1391
1399
  }
1400
+ commitSha() {
1401
+ return this.sha;
1402
+ }
1403
+ repoRoot() {
1404
+ return this.root;
1405
+ }
1392
1406
  static fromJSON(data) {
1393
1407
  return new Snapshot(data.cwd, data.root, data.sha);
1394
1408
  }
@@ -1652,6 +1666,8 @@ function allowsDeleteOnly(unit) {
1652
1666
  async function gateUnitChanges(unit, before, deps, opts = {}) {
1653
1667
  const usage = opts.usage ?? zeroUsage();
1654
1668
  const after = snapshotUnitNow(deps.cwd, unit.files);
1669
+ const scannerTools = [...new Set(unit.findings.map((finding) => finding.tool))];
1670
+ opts.onProgress?.("anti-suppression");
1655
1671
  const supp = antiSuppression(buildDiff(before, after), { allowDeleteOnly: allowsDeleteOnly(unit) });
1656
1672
  if (!supp.ok) return {
1657
1673
  kept: false,
@@ -1659,6 +1675,7 @@ async function gateUnitChanges(unit, before, deps, opts = {}) {
1659
1675
  detail: supp.detail,
1660
1676
  usage
1661
1677
  };
1678
+ opts.onProgress?.("typecheck");
1662
1679
  const tc = await typecheck({
1663
1680
  hasTsconfig: () => deps.typescript,
1664
1681
  runTsc: deps.runTsc
@@ -1670,6 +1687,7 @@ async function gateUnitChanges(unit, before, deps, opts = {}) {
1670
1687
  usage
1671
1688
  };
1672
1689
  if (unit.strategy === "generated-source-repair" && deps.runBuild) {
1690
+ opts.onProgress?.("build");
1673
1691
  const build = await deps.runBuild();
1674
1692
  if (build.exitCode !== 0) return {
1675
1693
  kept: false,
@@ -1680,8 +1698,14 @@ async function gateUnitChanges(unit, before, deps, opts = {}) {
1680
1698
  }
1681
1699
  const phase = await runTestPhase({
1682
1700
  baseline: deps.baseline,
1683
- runRelated: () => deps.runRelated(unit.files),
1684
- repair: opts.repair ?? (async () => {}),
1701
+ runRelated: () => {
1702
+ opts.onProgress?.("related-tests");
1703
+ return deps.runRelated(unit.files);
1704
+ },
1705
+ repair: async (attempt, regressed) => {
1706
+ opts.onProgress?.("test-repair", `${attempt}/${opts.maxRepairs ?? 0}`);
1707
+ await (opts.repair ?? (async () => {}))(attempt, regressed);
1708
+ },
1685
1709
  maxRepairs: opts.maxRepairs ?? 0,
1686
1710
  hasTestRunner: deps.hasTestRunner
1687
1711
  });
@@ -1692,7 +1716,9 @@ async function gateUnitChanges(unit, before, deps, opts = {}) {
1692
1716
  usage
1693
1717
  };
1694
1718
  const verificationTargets = unit.verificationTargets ?? unit.files;
1695
- const afterFindings = await deps.scanFindings(verificationTargets);
1719
+ opts.onProgress?.("rescan");
1720
+ const afterFindings = await deps.scanFindings(verificationTargets, scannerTools);
1721
+ opts.onProgress?.("regression-check");
1696
1722
  const regression = antiRegression(unit.findings, afterFindings, { requireResolved: opts.requireResolved || unit.strategy === "multi-file-duplicate-refactor" });
1697
1723
  if (!regression.ok) return {
1698
1724
  kept: false,
@@ -2251,7 +2277,16 @@ const FailureSummarySchema = z.object({
2251
2277
  sessionErrors: z.number().int().nonnegative(),
2252
2278
  regressions: z.number().int().nonnegative(),
2253
2279
  typecheckFailures: z.number().int().nonnegative(),
2254
- testFailures: z.number().int().nonnegative()
2280
+ testFailures: z.number().int().nonnegative(),
2281
+ sandboxSetupFailures: z.number().int().nonnegative().default(0),
2282
+ patchConflicts: z.number().int().nonnegative().default(0),
2283
+ unownedPatches: z.number().int().nonnegative().default(0),
2284
+ finalIntegrationFailures: z.number().int().nonnegative().default(0)
2285
+ });
2286
+ const FinalIntegrationSchema = z.object({
2287
+ ok: z.boolean(),
2288
+ files: z.array(z.string()).default([]),
2289
+ detail: z.string().optional()
2255
2290
  });
2256
2291
  const ZERO_AI_USAGE$1 = {
2257
2292
  estimatedCostUsd: 0,
@@ -2269,7 +2304,11 @@ const ZERO_FAILURE_SUMMARY = {
2269
2304
  sessionErrors: 0,
2270
2305
  regressions: 0,
2271
2306
  typecheckFailures: 0,
2272
- testFailures: 0
2307
+ testFailures: 0,
2308
+ sandboxSetupFailures: 0,
2309
+ patchConflicts: 0,
2310
+ unownedPatches: 0,
2311
+ finalIntegrationFailures: 0
2273
2312
  };
2274
2313
  const ReportSchema = z.object({
2275
2314
  findings: z.array(FindingSchema),
@@ -2282,6 +2321,7 @@ const ReportSchema = z.object({
2282
2321
  fixPolicy: FixPolicySchema,
2283
2322
  aiUsage: AiUsageSchema.default(ZERO_AI_USAGE$1),
2284
2323
  failureSummary: FailureSummarySchema.default(ZERO_FAILURE_SUMMARY),
2324
+ finalIntegration: FinalIntegrationSchema.optional(),
2285
2325
  unresolvedEligibleCount: z.number().int().nonnegative().default(0),
2286
2326
  loops: z.number().int().nonnegative(),
2287
2327
  durationMs: z.number().nonnegative(),
@@ -2320,7 +2360,11 @@ function deriveReportFields(findings, scannerStatuses, fixPolicy = DEFAULT_FIX_P
2320
2360
  sessionErrors: blockingUnresolved.filter((f) => f.revertReason === "session-error").length,
2321
2361
  regressions: blockingUnresolved.filter((f) => f.revertReason === "regression").length,
2322
2362
  typecheckFailures: blockingUnresolved.filter((f) => f.revertReason === "typecheck").length,
2323
- testFailures: blockingUnresolved.filter((f) => f.revertReason === "broke-test").length
2363
+ testFailures: blockingUnresolved.filter((f) => f.revertReason === "broke-test").length,
2364
+ sandboxSetupFailures: blockingUnresolved.filter((f) => f.finalFailureClass === "sandbox-setup-failed").length,
2365
+ patchConflicts: blockingUnresolved.filter((f) => f.finalFailureClass === "patch-conflict").length,
2366
+ unownedPatches: blockingUnresolved.filter((f) => f.finalFailureClass === "unowned-patch").length,
2367
+ finalIntegrationFailures: blockingUnresolved.filter((f) => f.finalFailureClass === "final-integration-failed").length
2324
2368
  };
2325
2369
  return {
2326
2370
  secrets: findings.filter((f) => f.category === "secret"),
@@ -2375,6 +2419,7 @@ var ReportBuilder = class {
2375
2419
  fixPolicy,
2376
2420
  aiUsage: meta.aiUsage ?? ZERO_AI_USAGE,
2377
2421
  failureSummary: derived.failureSummary,
2422
+ finalIntegration: meta.finalIntegration,
2378
2423
  unresolvedEligibleCount: derived.unresolvedEligibleCount,
2379
2424
  loops: meta.loops,
2380
2425
  durationMs: meta.durationMs,
@@ -2445,6 +2490,10 @@ function classFromOutcome(outcome) {
2445
2490
  case "broke-test": return "broke-test";
2446
2491
  case "suppression": return "suppression";
2447
2492
  case "needs-lockfile-update": return "needs-lockfile-update";
2493
+ case "sandbox-setup-failed": return "sandbox-setup-failed";
2494
+ case "patch-conflict": return "patch-conflict";
2495
+ case "unowned-patch": return "unowned-patch";
2496
+ case "final-integration-failed": return "final-integration-failed";
2448
2497
  case "session-error": return "model-tool-failure";
2449
2498
  default: return void 0;
2450
2499
  }
@@ -2754,6 +2803,10 @@ function reasonLabel(reason) {
2754
2803
  case "suppression": return "added a suppression";
2755
2804
  case "regression": return "introduced a new issue";
2756
2805
  case "session-error": return "the fix session failed";
2806
+ case "sandbox-setup-failed": return "sandbox setup failed";
2807
+ case "patch-conflict": return "patch conflict";
2808
+ case "unowned-patch": return "unowned patch";
2809
+ case "final-integration-failed": return "final integration failed";
2757
2810
  case "needs-lockfile-update": return "needs lockfile update";
2758
2811
  default: return "couldn't fix";
2759
2812
  }
@@ -2985,17 +3038,24 @@ function renderPlainSummary(report, b, theme, verbose) {
2985
3038
  ];
2986
3039
  const strategyCounts = repairStrategyCounts(report);
2987
3040
  if (strategyCounts.size > 0) lines$1.push(["repairStrategies", ...[...strategyCounts.entries()].map(([strategy, count]) => `${strategy}=${count}`)].join(" "));
2988
- if (report.exitStatus !== 0 || hasFailureSummary(report)) lines$1.push([
2989
- "failureSummary",
2990
- `blockingSecrets=${report.failureSummary.blockingSecrets}`,
2991
- `unresolvedEligible=${report.failureSummary.unresolvedEligible}`,
2992
- `toolFailures=${report.failureSummary.toolFailures}`,
2993
- `failedDeterministic=${report.failureSummary.failedDeterministic}`,
2994
- `sessionErrors=${report.failureSummary.sessionErrors}`,
2995
- `regressions=${report.failureSummary.regressions}`,
2996
- `typecheckFailures=${report.failureSummary.typecheckFailures}`,
2997
- `testFailures=${report.failureSummary.testFailures}`
2998
- ].join(" "));
3041
+ if (report.exitStatus !== 0 || hasFailureSummary(report)) {
3042
+ lines$1.push([
3043
+ "failureSummary",
3044
+ `blockingSecrets=${report.failureSummary.blockingSecrets}`,
3045
+ `unresolvedEligible=${report.failureSummary.unresolvedEligible}`,
3046
+ `toolFailures=${report.failureSummary.toolFailures}`,
3047
+ `failedDeterministic=${report.failureSummary.failedDeterministic}`,
3048
+ `sessionErrors=${report.failureSummary.sessionErrors}`,
3049
+ `regressions=${report.failureSummary.regressions}`,
3050
+ `typecheckFailures=${report.failureSummary.typecheckFailures}`,
3051
+ `testFailures=${report.failureSummary.testFailures}`,
3052
+ `sandboxSetupFailures=${report.failureSummary.sandboxSetupFailures}`,
3053
+ `patchConflicts=${report.failureSummary.patchConflicts}`,
3054
+ `unownedPatches=${report.failureSummary.unownedPatches}`,
3055
+ `finalIntegrationFailures=${report.failureSummary.finalIntegrationFailures}`
3056
+ ].join(" "));
3057
+ if (report.finalIntegration && !report.finalIntegration.ok) lines$1.push(`final-integration status=failed files=${report.finalIntegration.files.length} detail=${JSON.stringify(firstLine(report.finalIntegration.detail ?? ""))}`);
3058
+ }
2999
3059
  const counts = inScopeByTool(report);
3000
3060
  const statusByTool = new Map(report.scannerStatuses.map((s) => [s.tool, s]));
3001
3061
  const tools = TOOLS.filter((t) => statusByTool.has(t) || counts.has(t));
@@ -3056,6 +3116,7 @@ function renderOverallTable(report, b, theme) {
3056
3116
  const rows = [
3057
3117
  ["status", status],
3058
3118
  ["fix passes", String(report.loops)],
3119
+ ...report.finalIntegration && !report.finalIntegration.ok ? [["final integration", theme.error(firstLine(report.finalIntegration.detail ?? "failed"))]] : [],
3059
3120
  ["elapsed", formatDuration(report.durationMs)],
3060
3121
  ["fixed", `${theme.fixed(theme.glyph.fixed)} ${b.fixed.length}`],
3061
3122
  ["timed out/session error", `${theme.reverted(theme.glyph.reverted)} ${b.timedOutSessionError.length}`],
@@ -3081,7 +3142,7 @@ function renderOverallTable(report, b, theme) {
3081
3142
  }
3082
3143
  function hasFailureSummary(report) {
3083
3144
  const summary = report.failureSummary;
3084
- return summary.blockingSecrets > 0 || summary.unresolvedEligible > 0 || summary.toolFailures > 0 || summary.failedDeterministic > 0 || summary.sessionErrors > 0 || summary.regressions > 0 || summary.typecheckFailures > 0 || summary.testFailures > 0;
3145
+ return summary.blockingSecrets > 0 || summary.unresolvedEligible > 0 || summary.toolFailures > 0 || summary.failedDeterministic > 0 || summary.sessionErrors > 0 || summary.regressions > 0 || summary.typecheckFailures > 0 || summary.testFailures > 0 || summary.sandboxSetupFailures > 0 || summary.patchConflicts > 0 || summary.unownedPatches > 0 || summary.finalIntegrationFailures > 0;
3085
3146
  }
3086
3147
  /** Estimated AI cost as `$X.XX` — always two decimals, never called a "bill". */
3087
3148
  function formatCost(usd) {
@@ -3210,6 +3271,10 @@ function findingReason(f) {
3210
3271
  if (f.finalFailureClass === "tool-timeout") return "timeout/session error";
3211
3272
  if (f.finalFailureClass === "rate-limit") return "rate limited";
3212
3273
  if (f.finalFailureClass === "no-op") return "no-op";
3274
+ if (f.finalFailureClass === "sandbox-setup-failed") return "sandbox setup failed";
3275
+ if (f.finalFailureClass === "patch-conflict") return "patch conflict";
3276
+ if (f.finalFailureClass === "unowned-patch") return "unowned patch";
3277
+ if (f.finalFailureClass === "final-integration-failed") return "final integration failed";
3213
3278
  switch (f.revertReason) {
3214
3279
  case "session-error": return "timeout/session error";
3215
3280
  case "regression": return "regression introduced";
@@ -3223,6 +3288,9 @@ function retryTarget(f) {
3223
3288
  return f.retryId ?? f.id;
3224
3289
  }
3225
3290
  const COULDNT_FIX_REASON_ORDER = [
3291
+ "sandbox setup failed",
3292
+ "patch conflict",
3293
+ "unowned patch",
3226
3294
  "session error",
3227
3295
  "regression",
3228
3296
  "typecheck failed",
@@ -3232,6 +3300,9 @@ const COULDNT_FIX_REASON_ORDER = [
3232
3300
  ];
3233
3301
  function couldntFixReason(f) {
3234
3302
  if (f.track === "report-only") return "unsupported / report-only";
3303
+ if (f.finalFailureClass === "sandbox-setup-failed") return "sandbox setup failed";
3304
+ if (f.finalFailureClass === "patch-conflict") return "patch conflict";
3305
+ if (f.finalFailureClass === "unowned-patch") return "unowned patch";
3235
3306
  if (f.revertReason === "session-error" || f.finalFailureClass === "tool-timeout" || f.finalFailureClass === "rate-limit" || f.finalFailureClass === "model-tool-failure" || f.finalFailureClass === "no-edit") return "session error";
3236
3307
  if (f.revertReason === "regression" || f.finalFailureClass === "regression") return "regression";
3237
3308
  if (f.revertReason === "typecheck" || f.finalFailureClass === "typecheck") return "typecheck failed";
@@ -3565,6 +3636,7 @@ const ConfigSchema = z.object({
3565
3636
  includeTests: z.boolean().default(false),
3566
3637
  model: z.string().default("sonnet"),
3567
3638
  effort: z.enum(EFFORT_LEVELS).optional(),
3639
+ thinkingBudget: z.number().int().nonnegative().optional(),
3568
3640
  fix: FixScopeConfigSchema,
3569
3641
  tools: z.record(z.string(), ToolConfigSchema).default({})
3570
3642
  });