@lazyingart/agintiflow 0.20.274 → 0.20.276

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lazyingart/agintiflow",
3
- "version": "0.20.274",
3
+ "version": "0.20.276",
4
4
  "type": "module",
5
5
  "description": "AgInTiFlow is a project-aware agent workspace for hybrid wet-dry R&D, hardware-aware intelligence, software automation, and industrial workflows.",
6
6
  "license": "Apache-2.0",
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import crypto from "node:crypto";
3
+ import assertStrict from "node:assert/strict";
3
4
  import { spawnSync } from "node:child_process";
4
5
  import fs from "node:fs/promises";
5
6
  import net from "node:net";
@@ -30,6 +31,7 @@ import {
30
31
  buildFailedTestRecoveryPacket,
31
32
  buildKnownConstrainedPhasePlan,
32
33
  buildTaskOwnedCommitCommand,
34
+ changedGitStatusPaths,
33
35
  bindPatchContextRepairArguments,
34
36
  canonicalizeVerifiedArtifactCompletion,
35
37
  completedDeepResearchReuse,
@@ -52,6 +54,7 @@ import {
52
54
  mergeDurableGitEvidence,
53
55
  nextStepRuntimeConfig,
54
56
  patchContextScopeMismatchAttemptCount,
57
+ parseGitPorcelainStatus,
55
58
  pythonMainGuardOrderDefects,
56
59
  projectAcceptanceFromMarkdown,
57
60
  projectTestVerificationFinishBlock,
@@ -62,6 +65,7 @@ import {
62
65
  recordCanonicalGeneratedOutputProgress,
63
66
  recordAlreadyCommittedRepositoryRepair,
64
67
  recordProjectVerificationOutcome,
68
+ repositoryStateInspectionCommand,
65
69
  recordExactOutputProgress,
66
70
  recordStaticDiscoveryProgress,
67
71
  resetGoalScopedRuntimeState,
@@ -6483,12 +6487,113 @@ try {
6483
6487
  }),
6484
6488
  "a direct clean-worktree assertion was not classified when the traceback omitted the Git helper call"
6485
6489
  );
6490
+ assert(
6491
+ failedTestRequiresCleanRepositoryState({
6492
+ failureSummary: "acceptance failed: repository is not clean",
6493
+ }),
6494
+ "a concise authoritative acceptance failure did not activate repository-state repair"
6495
+ );
6486
6496
  assert(
6487
6497
  !failedTestRequiresCleanRepositoryState({
6488
6498
  failureSummary: "The generated prose says the repository worktree is not clean.",
6489
6499
  }),
6490
6500
  "plain task prose was mistaken for a repository-state gate without Git-status evidence"
6491
6501
  );
6502
+ assertStrict.deepEqual(
6503
+ parseGitPorcelainStatus(
6504
+ " M build_deck.py\n D dist/deck.pdf\n?? output/deck.pptx\n?? output/slide 1.png\n"
6505
+ ),
6506
+ [
6507
+ { status: " M", path: "build_deck.py" },
6508
+ { status: " D", path: "dist/deck.pdf" },
6509
+ { status: "??", path: "output/deck.pptx" },
6510
+ { status: "??", path: "output/slide 1.png" },
6511
+ ],
6512
+ "bounded porcelain parsing lost a deletion, untracked output, or path containing spaces"
6513
+ );
6514
+ assertStrict.deepEqual(
6515
+ changedGitStatusPaths(
6516
+ {
6517
+ entries: [
6518
+ { status: " M", path: "unrelated.md", fingerprint: "same" },
6519
+ { status: " M", path: "build_deck.py", fingerprint: "before" },
6520
+ ],
6521
+ },
6522
+ {
6523
+ entries: [
6524
+ { status: " M", path: "unrelated.md", fingerprint: "same" },
6525
+ { status: " M", path: "build_deck.py", fingerprint: "after" },
6526
+ { status: " D", path: "dist/deck.pdf", fingerprint: "missing" },
6527
+ { status: "??", path: "output/deck.pptx", fingerprint: "file:1" },
6528
+ ],
6529
+ }
6530
+ ),
6531
+ ["build_deck.py", "dist/deck.pdf", "output/deck.pptx"],
6532
+ "command-bound Git capture included untouched pre-existing dirt or lost generated relocation paths"
6533
+ );
6534
+ const producerMutationState = {
6535
+ goal: "Rebuild the generated presentation.",
6536
+ meta: {
6537
+ goalContract: { revision: 3, taskGoal: "Rebuild the generated presentation." },
6538
+ activeExecutionContract: { revision: 3 },
6539
+ },
6540
+ };
6541
+ recordProjectVerificationOutcome(
6542
+ producerMutationState,
6543
+ {
6544
+ toolName: "run_command",
6545
+ ok: true,
6546
+ exitCode: 0,
6547
+ args: { command: "python3 build_deck.py" },
6548
+ commandPolicy: {
6549
+ category: "toolchain",
6550
+ writesWorkspace: true,
6551
+ mayMutateProject: false,
6552
+ },
6553
+ projectMutationPaths: [
6554
+ "build_deck.py",
6555
+ "dist/deck.pdf",
6556
+ "output/deck.pptx",
6557
+ ],
6558
+ stdout: "wrote output/deck.pptx",
6559
+ stderr: "",
6560
+ },
6561
+ { commandCwd: workspace, taskProfile: "slides" }
6562
+ );
6563
+ assertStrict.deepEqual(
6564
+ producerMutationState.meta.projectVerification.mutationHistory.at(-1).paths,
6565
+ ["build_deck.py", "dist/deck.pdf", "output/deck.pptx"],
6566
+ "a producer command discarded the exact Git paths captured across its execution boundary"
6567
+ );
6568
+ const inspectedRepositoryState = structuredClone(repositoryCleanGateFailure);
6569
+ inspectedRepositoryState.meta.goalContract = { revision: 4 };
6570
+ const repositoryInspectionResult = {
6571
+ toolName: "run_command",
6572
+ ok: true,
6573
+ exitCode: 0,
6574
+ args: { command: repositoryStateInspectionCommand() },
6575
+ stdout: " D dist/deck.pdf\n?? output/deck.pptx\n",
6576
+ stderr: "",
6577
+ };
6578
+ recordProjectVerificationOutcome(
6579
+ inspectedRepositoryState,
6580
+ repositoryInspectionResult,
6581
+ {
6582
+ commandCwd: workspace,
6583
+ taskProfile: "slides",
6584
+ testFailureRepositoryStateRepair: true,
6585
+ testFailureSignature: "repository-state-gate",
6586
+ }
6587
+ );
6588
+ const inspectedRepositoryRuntime = nextStepRuntimeConfig(
6589
+ { provider: "deepseek", taskProfile: "slides", commandCwd: workspace },
6590
+ inspectedRepositoryState
6591
+ );
6592
+ assertStrict.deepEqual(
6593
+ inspectedRepositoryRuntime.repositoryStateRepairObservedPaths,
6594
+ ["dist/deck.pdf", "output/deck.pptx"],
6595
+ "a bounded dirty-worktree inspection did not persist exact path candidates for the next turn"
6596
+ );
6492
6597
  assert(
6493
6598
  unchangedFailedTestRerunBlock(
6494
6599
  repositoryCleanGateFailure,
@@ -2003,10 +2003,120 @@ sameNames(
2003
2003
  "repository-state repair exposed document mutation tools"
2004
2004
  );
2005
2005
  assert(
2006
- /stage and commit only those changes/i.test(
2006
+ /exact bounded Git status inspection once/i.test(
2007
2007
  repositoryStateRepairTools.find((item) => item.function.name === "run_command")?.function.description || ""
2008
2008
  ),
2009
- "repository-state repair did not explain the bounded Git workflow"
2009
+ "repository-state repair did not constrain the first recovery turn to one status inspection"
2010
+ );
2011
+ assertStrict.deepEqual(
2012
+ repositoryStateRepairTools[0].function.parameters.properties.command.enum,
2013
+ ["git status --porcelain=v1 --untracked-files=all"],
2014
+ "repository-state repair exposed an open-ended shell instead of the exact status command"
2015
+ );
2016
+ const conciseRepositoryStateRepairRuntime = nextStepRuntimeConfig(
2017
+ { provider: "deepseek", taskProfile: "slides" },
2018
+ {
2019
+ meta: {
2020
+ projectVerification: {
2021
+ mutationRevision: 18,
2022
+ testRuns: [{
2023
+ command: "python acceptance.py --root .",
2024
+ mutationRevision: 18,
2025
+ passed: false,
2026
+ failureEvidenceVersion: 2,
2027
+ failureSignature: "concise-repository-state-gate",
2028
+ failureSummary: "acceptance failed: repository is not clean",
2029
+ }],
2030
+ },
2031
+ failedTestRecoveryPacket: {
2032
+ packetVersion: 15,
2033
+ mutationRevision: 18,
2034
+ failureSignature: "concise-repository-state-gate",
2035
+ content: "Retained canonical source evidence from the prior content failure.",
2036
+ },
2037
+ toolLoop: {
2038
+ stagnationEpoch: 4,
2039
+ recent: [],
2040
+ },
2041
+ },
2042
+ messages: [],
2043
+ }
2044
+ );
2045
+ assertStrict.equal(
2046
+ conciseRepositoryStateRepairRuntime.testFailureRepositoryStateRepair,
2047
+ true,
2048
+ "a concise acceptance failure remained in canonical-source repair mode"
2049
+ );
2050
+ assertStrict.equal(
2051
+ conciseRepositoryStateRepairRuntime.testFailureRepairMutationRequired,
2052
+ false,
2053
+ "stale retained source evidence overrode a clean-repository repair gate"
2054
+ );
2055
+ sameNames(
2056
+ selectProgressiveTools(allTools, {
2057
+ config: conciseRepositoryStateRepairRuntime,
2058
+ goal: "Commit the accepted presentation and leave the repository clean.",
2059
+ profile: "slides",
2060
+ }),
2061
+ ["run_command", "finish"],
2062
+ "a concise clean-repository acceptance failure exposed content mutation tools"
2063
+ );
2064
+ const observedRepositoryState = {
2065
+ meta: {
2066
+ goalContract: { revision: 7 },
2067
+ projectVerification: {
2068
+ mutationRevision: 18,
2069
+ repositoryStateInspection: {
2070
+ version: 1,
2071
+ mutationRevision: 18,
2072
+ failureSignature: "observed-repository-state-gate",
2073
+ command: "git status --porcelain=v1 --untracked-files=all",
2074
+ entries: [
2075
+ { status: " D", path: "dist/deck.pdf" },
2076
+ { status: "??", path: "output/deck.pptx" },
2077
+ { status: " M", path: "unrelated-notes.md" },
2078
+ ],
2079
+ },
2080
+ testRuns: [{
2081
+ command: "python acceptance.py --phase final",
2082
+ mutationRevision: 18,
2083
+ passed: false,
2084
+ failureEvidenceVersion: 2,
2085
+ failureSignature: "observed-repository-state-gate",
2086
+ failureSummary: "acceptance failed: repository is not clean",
2087
+ }],
2088
+ },
2089
+ },
2090
+ messages: [],
2091
+ };
2092
+ const observedRepositoryRuntime = nextStepRuntimeConfig(
2093
+ { provider: "deepseek", taskProfile: "slides" },
2094
+ observedRepositoryState
2095
+ );
2096
+ assertStrict.deepEqual(
2097
+ observedRepositoryRuntime.repositoryStateRepairObservedPaths,
2098
+ ["dist/deck.pdf", "output/deck.pptx", "unrelated-notes.md"],
2099
+ "the clean-state recovery phase lost exact paths from its bounded status observation"
2100
+ );
2101
+ const observedRepositoryTools = selectProgressiveTools(allTools, {
2102
+ config: observedRepositoryRuntime,
2103
+ goal: "Commit only the accepted presentation relocation.",
2104
+ profile: "slides",
2105
+ });
2106
+ sameNames(
2107
+ observedRepositoryTools,
2108
+ ["commit_project_changes", "finish"],
2109
+ "an observed clean-state recovery did not replace the shell with path-enumerated commit selection"
2110
+ );
2111
+ assertStrict.deepEqual(
2112
+ observedRepositoryTools[0].function.parameters.required,
2113
+ ["paths", "message"],
2114
+ "the observed-path commit did not require explicit task-owned path selection"
2115
+ );
2116
+ assertStrict.deepEqual(
2117
+ observedRepositoryTools[0].function.parameters.properties.paths.items.enum,
2118
+ ["dist/deck.pdf", "output/deck.pptx", "unrelated-notes.md"],
2119
+ "the observed-path commit broadened its candidate scope beyond the exact status result"
2010
2120
  );
2011
2121
  const taskOwnedRepositoryState = {
2012
2122
  meta: {
@@ -2237,6 +2237,11 @@ export function buildKnownConstrainedPhasePlan(
2237
2237
  Array.isArray(runtimeConfig.repositoryStateRepairCommitPaths) &&
2238
2238
  runtimeConfig.repositoryStateRepairCommitPaths.length > 0
2239
2239
  );
2240
+ const repositoryObservedCommitReady = Boolean(
2241
+ repositoryStateRepair &&
2242
+ Array.isArray(runtimeConfig.repositoryStateRepairObservedPaths) &&
2243
+ runtimeConfig.repositoryStateRepairObservedPaths.length > 0
2244
+ );
2240
2245
  const plan = repositoryStateRepair
2241
2246
  ? repositoryCommitReady
2242
2247
  ? [
@@ -2244,9 +2249,16 @@ export function buildKnownConstrainedPhasePlan(
2244
2249
  `2. Run the exact retained verification command after that commit: ${command}`,
2245
2250
  "3. Finish only after that exact command passes at the current mutation revision.",
2246
2251
  ].join("\n")
2252
+ : repositoryObservedCommitReady
2253
+ ? [
2254
+ "1. Use the offered commit_project_changes tool once and select the complete task-owned subset from the exact observed dirty paths.",
2255
+ "2. Include intended generated-output relocations and deletions, but exclude unrelated pre-existing work.",
2256
+ `3. Run the exact retained verification command after that commit: ${command}`,
2257
+ "4. Finish only after that exact command passes at the current mutation revision.",
2258
+ ].join("\n")
2247
2259
  : [
2248
- "1. Inspect the current repository status and diff using the smallest read-only Git command needed.",
2249
- "2. Preserve task-owned work, stage and commit only those changes, and do not alter source content merely to make the worktree clean.",
2260
+ "1. Run the one exact bounded Git status inspection offered by the runtime.",
2261
+ "2. On the next turn, select and commit only task-owned paths from the runtime-enumerated candidates.",
2250
2262
  `3. Run the exact retained verification command after the repository state is clean: ${command}`,
2251
2263
  "4. Finish only after that exact command passes at the current mutation revision.",
2252
2264
  ].join("\n")
@@ -2294,7 +2306,9 @@ export function buildKnownConstrainedPhasePlan(
2294
2306
  const recoveryInstruction = repositoryStateRepair
2295
2307
  ? repositoryCommitReady
2296
2308
  ? "The current failure is only a repository-state acceptance gate. The runtime already knows the task-owned changed paths, so call commit_project_changes with a concise factual subject. Do not inspect again or invent another source edit."
2297
- : "The current failure is a repository-state acceptance gate, not a content defect. Use only the offered Git-capable command tool to inspect and cleanly commit task-owned changes, then run the exact retained verifier. Do not invent another source edit."
2309
+ : repositoryObservedCommitReady
2310
+ ? "The bounded status inspection is complete. Select the complete task-owned subset from the exact offered paths and call commit_project_changes once. Do not include unrelated work, inspect again, or invent another source edit."
2311
+ : "The current failure is a repository-state acceptance gate, not a content defect. Run only the exact offered status inspection once. The runtime will convert its result into a bounded path-selection commit phase; do not start a status/diff loop or invent another source edit."
2298
2312
  : freshSourceMutation
2299
2313
  ? runtimeConfig.completionFreshMutationNeedsSourceRead === true
2300
2314
  ? "Completion was rejected because a fresh canonical source correction is still missing. Read one exact offered project file now; command, test, Git, and finish actions remain intentionally unavailable until the source is grounded and materially patched."
@@ -6055,6 +6069,15 @@ export function recordProjectVerificationOutcome(state = {}, toolResult = {}, co
6055
6069
  generatedArtifactProducer
6056
6070
  )
6057
6071
  );
6072
+ const commandMutationPaths = Array.isArray(toolResult.projectMutationPaths)
6073
+ ? [
6074
+ ...new Set(
6075
+ toolResult.projectMutationPaths
6076
+ .map(safeTaskOwnedCommitPath)
6077
+ .filter(Boolean)
6078
+ ),
6079
+ ].slice(0, 64)
6080
+ : [];
6058
6081
  let requiredBatch = currentRequiredCommandBatch(verification, requiredCommands);
6059
6082
  const activeExecutionContract = state.meta?.activeExecutionContract;
6060
6083
  const activeTurnCommands =
@@ -6074,6 +6097,7 @@ export function recordProjectVerificationOutcome(state = {}, toolResult = {}, co
6074
6097
  let batchRequiredCommands = requiredBatch?.requiredCommands || requiredCommands;
6075
6098
  if (projectContentMutation) {
6076
6099
  delete state.meta.verifiedCompletionCandidate;
6100
+ delete verification.repositoryStateInspection;
6077
6101
  if (
6078
6102
  requiredCommand &&
6079
6103
  batchRequiredCommands.includes(requiredCommand) &&
@@ -6108,7 +6132,7 @@ export function recordProjectVerificationOutcome(state = {}, toolResult = {}, co
6108
6132
  revision: verification.mutationRevision,
6109
6133
  at: now,
6110
6134
  toolName,
6111
- paths: [],
6135
+ paths: commandMutationPaths,
6112
6136
  commandCategory: String(commandPolicy.category || "general-shell"),
6113
6137
  }, config);
6114
6138
  if (
@@ -6125,8 +6149,61 @@ export function recordProjectVerificationOutcome(state = {}, toolResult = {}, co
6125
6149
  generatedArtifactProducer,
6126
6150
  ]),
6127
6151
  ].slice(0, 12);
6152
+ if (commandMutationPaths.length) {
6153
+ activeExecutionContract.materialMutationPaths = [
6154
+ ...new Set([
6155
+ ...(Array.isArray(activeExecutionContract.materialMutationPaths)
6156
+ ? activeExecutionContract.materialMutationPaths
6157
+ : []),
6158
+ ...commandMutationPaths,
6159
+ ]),
6160
+ ].slice(0, 24);
6161
+ }
6128
6162
  }
6129
6163
  }
6164
+ if (commandIncludesGitCommit(command)) {
6165
+ delete verification.repositoryStateInspection;
6166
+ }
6167
+ if (
6168
+ commandSucceeded &&
6169
+ config.testFailureRepositoryStateRepair === true &&
6170
+ projectCommandsEquivalent(
6171
+ command,
6172
+ REPOSITORY_STATE_INSPECTION_COMMAND,
6173
+ config
6174
+ )
6175
+ ) {
6176
+ const currentFailure = [...verification.testRuns]
6177
+ .reverse()
6178
+ .find(
6179
+ (candidate) =>
6180
+ candidate?.passed !== true &&
6181
+ Number(candidate?.mutationRevision || 0) ===
6182
+ verification.mutationRevision
6183
+ );
6184
+ const entries = (
6185
+ Array.isArray(toolResult.repositoryStateStatusEntries)
6186
+ ? toolResult.repositoryStateStatusEntries
6187
+ .map((entry) => ({
6188
+ status: String(entry?.status || ""),
6189
+ path: safeTaskOwnedCommitPath(entry?.path),
6190
+ }))
6191
+ .filter((entry) => entry.path)
6192
+ : parseGitPorcelainStatus(toolResult.stdout)
6193
+ ).slice(0, 64);
6194
+ verification.repositoryStateInspection = {
6195
+ version: REPOSITORY_STATE_INSPECTION_VERSION,
6196
+ mutationRevision: verification.mutationRevision,
6197
+ failureSignature: String(
6198
+ config.testFailureSignature || currentFailure?.failureSignature || ""
6199
+ ),
6200
+ command: REPOSITORY_STATE_INSPECTION_COMMAND,
6201
+ entries,
6202
+ at: now,
6203
+ };
6204
+ toolResult.repositoryStateInspection = verification.repositoryStateInspection;
6205
+ toolResult.repositoryStateObservedPaths = entries.map((entry) => entry.path);
6206
+ }
6130
6207
  const run = {
6131
6208
  command,
6132
6209
  at: now,
@@ -6306,6 +6383,9 @@ export function failedTestRequiresCleanRepositoryState(testRun = {}) {
6306
6383
  const directCleanStateAssertion =
6307
6384
  /(?:assertionerror|assert(?:ion)?(?:\s+failed)?)[^.!;]{0,200}(?:(?:project|repository)\s+)?(?:work\s*tree|worktree|repository)[^.!;]{0,80}(?:is\s+not\s+clean|must\s+be\s+clean|expected\s+(?:to\s+be\s+)?clean)\b/i.test(summary);
6308
6385
  if (directCleanStateAssertion) return true;
6386
+ const authoritativeCleanStateFailure =
6387
+ /(?:^|\b)(?:acceptance|validation|verification|contract|test|check)\s+failed\s*:[^.!;]{0,200}(?:(?:project|repository)\s+)?(?:work\s*tree|worktree|repository)[^.!;]{0,80}(?:is\s+not\s+clean|must\s+be\s+clean|expected\s+(?:to\s+be\s+)?clean)\b/i.test(summary);
6388
+ if (authoritativeCleanStateFailure) return true;
6309
6389
  const invokesShortStatus =
6310
6390
  /git[a-z_]*\s*\([^)]*["']status["'][^)]*(?:["']--short["']|["']--porcelain(?:=[^"']+)?["'])[^)]*\)/i.test(summary) ||
6311
6391
  /\bgit\s+status\s+(?:--short|--porcelain(?:=\S+)?)\b/i.test(summary);
@@ -6397,6 +6477,158 @@ function safeTaskOwnedCommitPath(value = "") {
6397
6477
  return candidate;
6398
6478
  }
6399
6479
 
6480
+ const REPOSITORY_STATE_INSPECTION_VERSION = 1;
6481
+ const REPOSITORY_STATE_INSPECTION_COMMAND =
6482
+ "git status --porcelain=v1 --untracked-files=all";
6483
+
6484
+ export function repositoryStateInspectionCommand() {
6485
+ return REPOSITORY_STATE_INSPECTION_COMMAND;
6486
+ }
6487
+
6488
+ function decodeGitPorcelainPath(value = "") {
6489
+ const raw = String(value || "").trimEnd();
6490
+ if (!raw.startsWith('"')) return raw;
6491
+ // Git's quoted-path grammar overlaps JSON for ordinary escaped paths. Octal
6492
+ // escapes are deliberately rejected instead of guessing a path that may be
6493
+ // staged later.
6494
+ try {
6495
+ return JSON.parse(raw);
6496
+ } catch {
6497
+ return "";
6498
+ }
6499
+ }
6500
+
6501
+ export function parseGitPorcelainStatus(output = "") {
6502
+ const entries = [];
6503
+ const seen = new Set();
6504
+ for (const line of String(output || "").replace(/\r/g, "").split("\n")) {
6505
+ if (line.length < 4 || line[2] !== " ") continue;
6506
+ const status = line.slice(0, 2);
6507
+ if (!/^[ MADRCUT?!]{2}$/u.test(status)) continue;
6508
+ const candidate = safeTaskOwnedCommitPath(
6509
+ decodeGitPorcelainPath(line.slice(3))
6510
+ );
6511
+ if (!candidate || seen.has(candidate)) continue;
6512
+ seen.add(candidate);
6513
+ entries.push({ status, path: candidate });
6514
+ if (entries.length >= 256) break;
6515
+ }
6516
+ return entries;
6517
+ }
6518
+
6519
+ async function gitStatusPathFingerprint(commandCwd = "", relativePath = "") {
6520
+ const root = path.resolve(commandCwd || process.cwd());
6521
+ const candidate = safeTaskOwnedCommitPath(relativePath);
6522
+ if (!candidate) return "";
6523
+ const absolutePath = path.resolve(root, candidate);
6524
+ const relative = path.relative(root, absolutePath);
6525
+ if (!relative || relative.startsWith("..") || path.isAbsolute(relative)) return "";
6526
+ try {
6527
+ const stat = await fs.lstat(absolutePath);
6528
+ return [
6529
+ stat.isFile() ? "file" : stat.isDirectory() ? "directory" : stat.isSymbolicLink() ? "symlink" : "other",
6530
+ Number(stat.mode || 0),
6531
+ Number(stat.size || 0),
6532
+ Number(stat.mtimeMs || 0),
6533
+ Number(stat.ctimeMs || 0),
6534
+ Number(stat.ino || 0),
6535
+ ].join(":");
6536
+ } catch (error) {
6537
+ return error?.code === "ENOENT" ? "missing" : "unavailable";
6538
+ }
6539
+ }
6540
+
6541
+ async function captureGitWorktreeSnapshot(config = {}) {
6542
+ const commandCwd = path.resolve(config.commandCwd || process.cwd());
6543
+ const stdout = await new Promise((resolve) => {
6544
+ let text = "";
6545
+ let settled = false;
6546
+ let timer;
6547
+ const finish = (value = null) => {
6548
+ if (settled) return;
6549
+ settled = true;
6550
+ if (timer) clearTimeout(timer);
6551
+ resolve(value);
6552
+ };
6553
+ const child = spawn(
6554
+ "git",
6555
+ ["status", "--porcelain=v1", "--untracked-files=all"],
6556
+ {
6557
+ cwd: commandCwd,
6558
+ stdio: ["ignore", "pipe", "ignore"],
6559
+ windowsHide: true,
6560
+ }
6561
+ );
6562
+ timer = setTimeout(() => {
6563
+ child.kill("SIGKILL");
6564
+ finish(null);
6565
+ }, 5000);
6566
+ child.stdout?.on("data", (chunk) => {
6567
+ text += String(chunk || "");
6568
+ if (text.length > 1024 * 1024) {
6569
+ child.kill("SIGKILL");
6570
+ finish(null);
6571
+ }
6572
+ });
6573
+ child.on("error", () => finish(null));
6574
+ child.on("close", (code) => finish(code === 0 ? text : null));
6575
+ });
6576
+ if (typeof stdout !== "string") return null;
6577
+ const parsed = parseGitPorcelainStatus(stdout);
6578
+ if (parsed.length >= 256) return null;
6579
+ const entries = await Promise.all(
6580
+ parsed.map(async (entry) => ({
6581
+ ...entry,
6582
+ fingerprint: await gitStatusPathFingerprint(commandCwd, entry.path),
6583
+ }))
6584
+ );
6585
+ return { version: 1, entries };
6586
+ }
6587
+
6588
+ export function changedGitStatusPaths(before = null, after = null) {
6589
+ if (!before || !after || !Array.isArray(before.entries) || !Array.isArray(after.entries)) {
6590
+ return [];
6591
+ }
6592
+ const prior = new Map(
6593
+ before.entries
6594
+ .filter((entry) => safeTaskOwnedCommitPath(entry?.path))
6595
+ .map((entry) => [String(entry.path), entry])
6596
+ );
6597
+ return [
6598
+ ...new Set(
6599
+ after.entries
6600
+ .filter((entry) => {
6601
+ const candidate = safeTaskOwnedCommitPath(entry?.path);
6602
+ if (!candidate) return false;
6603
+ const previous = prior.get(candidate);
6604
+ return Boolean(
6605
+ !previous ||
6606
+ String(previous.status || "") !== String(entry.status || "") ||
6607
+ String(previous.fingerprint || "") !== String(entry.fingerprint || "")
6608
+ );
6609
+ })
6610
+ .map((entry) => safeTaskOwnedCommitPath(entry.path))
6611
+ .filter(Boolean)
6612
+ ),
6613
+ ].slice(0, 64);
6614
+ }
6615
+
6616
+ function currentRepositoryStateInspection(verification = {}, testRun = {}) {
6617
+ const inspection = verification.repositoryStateInspection;
6618
+ if (
6619
+ !inspection ||
6620
+ Number(inspection.version || 0) !== REPOSITORY_STATE_INSPECTION_VERSION ||
6621
+ Number(inspection.mutationRevision || 0) !==
6622
+ Math.max(0, Number(verification.mutationRevision || 0)) ||
6623
+ String(inspection.failureSignature || "") !==
6624
+ String(testRun?.failureSignature || "") ||
6625
+ !Array.isArray(inspection.entries)
6626
+ ) {
6627
+ return null;
6628
+ }
6629
+ return inspection;
6630
+ }
6631
+
6400
6632
  function taskOwnedMutationPathsSinceLatestCommit(verification = {}) {
6401
6633
  const latestCommittedRevision = [...(verification.commandRuns || [])]
6402
6634
  .reverse()
@@ -7929,6 +8161,8 @@ function runCommandResultHasDurableProgress(toolResult = {}) {
7929
8161
  return Boolean(
7930
8162
  policyAllowsMutation ||
7931
8163
  policy.substantiveTest === true ||
8164
+ (Array.isArray(toolResult.projectMutationPaths) &&
8165
+ toolResult.projectMutationPaths.length > 0) ||
7932
8166
  (Array.isArray(toolResult.verifiedGeneratedOutputPaths) &&
7933
8167
  toolResult.verifiedGeneratedOutputPaths.length > 0)
7934
8168
  );
@@ -11941,6 +12175,29 @@ export function nextStepRuntimeConfig(config = {}, state = {}) {
11941
12175
  runtimeConfig.testFailureRepairPatchTargets = [];
11942
12176
  runtimeConfig.repositoryStateRepairCommitPaths =
11943
12177
  taskOwnedMutationPathsSinceLatestCommit(verification);
12178
+ const repositoryInspection = currentRepositoryStateInspection(
12179
+ verification,
12180
+ retainedFailedTest
12181
+ );
12182
+ const observedEntries = runtimeConfig.repositoryStateRepairCommitPaths.length
12183
+ ? []
12184
+ : (repositoryInspection?.entries || [])
12185
+ .map((entry) => ({
12186
+ status: String(entry?.status || ""),
12187
+ path: safeTaskOwnedCommitPath(entry?.path),
12188
+ }))
12189
+ .filter((entry) => entry.path)
12190
+ .slice(0, 64);
12191
+ runtimeConfig.repositoryStateRepairObservedEntries = observedEntries;
12192
+ runtimeConfig.repositoryStateRepairObservedPaths = observedEntries.map(
12193
+ (entry) => entry.path
12194
+ );
12195
+ runtimeConfig.repositoryStateRepairInspectionCommand =
12196
+ REPOSITORY_STATE_INSPECTION_COMMAND;
12197
+ runtimeConfig.repositoryStateRepairInspectionRequired = Boolean(
12198
+ runtimeConfig.repositoryStateRepairCommitPaths.length === 0 &&
12199
+ !repositoryInspection
12200
+ );
11944
12201
  } else if (retainedRepositoryStateRepair) {
11945
12202
  delete runtimeConfig.completionFreshMutationRequired;
11946
12203
  delete runtimeConfig.completionFreshMutationRevision;
@@ -14255,20 +14512,45 @@ async function executeTool(browserState, toolCall, snapshot, config, store, obse
14255
14512
  ? config.taskOwnedCommitPaths
14256
14513
  : []),
14257
14514
  ].map(safeTaskOwnedCommitPath).filter(Boolean);
14515
+ const observedRepositoryCommitPaths = Array.isArray(
14516
+ config.repositoryStateRepairObservedPaths
14517
+ )
14518
+ ? config.repositoryStateRepairObservedPaths
14519
+ .map(safeTaskOwnedCommitPath)
14520
+ .filter(Boolean)
14521
+ : [];
14522
+ const requestedObservedCommitPaths = Array.isArray(args.paths)
14523
+ ? [...new Set(args.paths.map(safeTaskOwnedCommitPath).filter(Boolean))]
14524
+ : [];
14525
+ const observedCommitPathSet = new Set(observedRepositoryCommitPaths);
14526
+ const selectedRepositoryCommitPaths = repositoryCommitPaths.length
14527
+ ? repositoryCommitPaths
14528
+ : requestedObservedCommitPaths.length > 0 &&
14529
+ requestedObservedCommitPaths.length === args.paths?.length &&
14530
+ requestedObservedCommitPaths.every((candidate) =>
14531
+ observedCommitPathSet.has(candidate)
14532
+ )
14533
+ ? requestedObservedCommitPaths
14534
+ : [];
14258
14535
  if (
14259
14536
  requestedToolName === "commit_project_changes" &&
14260
- repositoryCommitPaths.length > 0 &&
14537
+ selectedRepositoryCommitPaths.length > 0 &&
14261
14538
  typeof args.message === "string" &&
14262
14539
  args.message.trim()
14263
14540
  ) {
14264
- const commitCommand = buildTaskOwnedCommitCommand(repositoryCommitPaths, args.message);
14541
+ const commitCommand = buildTaskOwnedCommitCommand(
14542
+ selectedRepositoryCommitPaths,
14543
+ args.message
14544
+ );
14265
14545
  if (commitCommand) {
14266
14546
  args = { command: commitCommand };
14267
14547
  toolName = "run_command";
14268
14548
  autoCorrection = {
14269
14549
  requestedToolName,
14270
14550
  toolName,
14271
- reason: "evidence-derived-task-owned-commit",
14551
+ reason: repositoryCommitPaths.length
14552
+ ? "evidence-derived-task-owned-commit"
14553
+ : "observed-task-owned-commit-selection",
14272
14554
  };
14273
14555
  }
14274
14556
  }
@@ -15054,6 +15336,20 @@ async function executeTool(browserState, toolCall, snapshot, config, store, obse
15054
15336
  const policy = evaluateCommandPolicy(rawCommand, config);
15055
15337
  const observedCommand = normalizeProjectCommand(rawCommand);
15056
15338
  const observedInnerCommand = explicitExitProbeStatus(observedCommand).command;
15339
+ const repositoryStateInspectionRequested = projectCommandsEquivalent(
15340
+ observedCommand,
15341
+ REPOSITORY_STATE_INSPECTION_COMMAND,
15342
+ config
15343
+ );
15344
+ const generatedArtifactProducer = safeProjectProducerSegment(
15345
+ observedCommand,
15346
+ config
15347
+ );
15348
+ const captureMutationScope = Boolean(
15349
+ policy.writesWorkspace ||
15350
+ policy.mayMutateProject === true ||
15351
+ generatedArtifactProducer
15352
+ );
15057
15353
  const requiredCommands = effectiveRequiredProjectCommands(
15058
15354
  state,
15059
15355
  state.meta?.projectVerification || {},
@@ -15065,10 +15361,24 @@ async function executeTool(browserState, toolCall, snapshot, config, store, obse
15065
15361
  const exactOutputSnapshotsBefore = isRequiredCommand && policy.writesWorkspace
15066
15362
  ? await captureExactOutputSnapshots(state, config)
15067
15363
  : [];
15364
+ const gitWorktreeBefore = captureMutationScope
15365
+ ? await captureGitWorktreeSnapshot(config)
15366
+ : null;
15068
15367
  if (config.useDockerSandbox) {
15069
15368
  await ensureDockerSandboxReady(config, observers);
15070
15369
  }
15071
15370
  const commandResult = await runShellCommand(rawCommand, config, policy);
15371
+ const gitWorktreeAfter = captureMutationScope
15372
+ ? await captureGitWorktreeSnapshot(config)
15373
+ : null;
15374
+ const repositoryStateInspectionSnapshot =
15375
+ repositoryStateInspectionRequested && commandResult.ok !== false
15376
+ ? await captureGitWorktreeSnapshot(config)
15377
+ : null;
15378
+ const projectMutationPaths = changedGitStatusPaths(
15379
+ gitWorktreeBefore,
15380
+ gitWorktreeAfter
15381
+ );
15072
15382
  const generatedOutputPaths = commandResult.ok !== false
15073
15383
  ? await verifiedGeneratedOutputPaths(state, exactOutputSnapshotsBefore, config)
15074
15384
  : [];
@@ -15103,6 +15413,20 @@ async function executeTool(browserState, toolCall, snapshot, config, store, obse
15103
15413
  ...(generatedOutputPaths.length
15104
15414
  ? { verifiedGeneratedOutputPaths: generatedOutputPaths }
15105
15415
  : {}),
15416
+ ...(projectMutationPaths.length
15417
+ ? { projectMutationPaths }
15418
+ : {}),
15419
+ ...(repositoryStateInspectionSnapshot
15420
+ ? {
15421
+ repositoryStateStatusEntries:
15422
+ repositoryStateInspectionSnapshot.entries.map(
15423
+ ({ status, path: statusPath }) => ({
15424
+ status,
15425
+ path: statusPath,
15426
+ })
15427
+ ),
15428
+ }
15429
+ : {}),
15106
15430
  ...commandResult,
15107
15431
  ...(permissionAdvice ? { permissionAdvice } : {}),
15108
15432
  };
@@ -1646,6 +1646,51 @@ function constrainRepositoryStateCommit(tool, paths = []) {
1646
1646
  };
1647
1647
  }
1648
1648
 
1649
+ function constrainObservedRepositoryStateCommit(tool, paths = []) {
1650
+ if (!tool || !Array.isArray(paths) || paths.length === 0) return null;
1651
+ const candidates = [...new Set(paths.map((item) => String(item || "").trim()).filter(Boolean))]
1652
+ .slice(0, 64);
1653
+ if (!candidates.length) return null;
1654
+ return {
1655
+ ...tool,
1656
+ function: {
1657
+ ...tool.function,
1658
+ name: "commit_project_changes",
1659
+ description: [
1660
+ "Commit only the task-owned subset of the exact currently dirty paths discovered by the bounded repository inspection.",
1661
+ "Select every intended task source, generated output, relocation deletion, or manifest involved in this task, and exclude unrelated pre-existing work.",
1662
+ "The runtime rejects paths outside this exact observation and stages only the selected paths before creating one local commit.",
1663
+ `Observed candidates: ${candidates.join(", ")}.`,
1664
+ ].join(" "),
1665
+ parameters: {
1666
+ type: "object",
1667
+ properties: {
1668
+ paths: {
1669
+ type: "array",
1670
+ minItems: 1,
1671
+ maxItems: candidates.length,
1672
+ uniqueItems: true,
1673
+ items: {
1674
+ type: "string",
1675
+ enum: candidates,
1676
+ },
1677
+ description: "The complete task-owned subset of the exact observed dirty paths.",
1678
+ },
1679
+ message: {
1680
+ type: "string",
1681
+ minLength: 3,
1682
+ maxLength: 120,
1683
+ pattern: "^[^\\r\\n\\u0000]+$",
1684
+ description: "A concise factual Git commit subject for the selected task-owned changes.",
1685
+ },
1686
+ },
1687
+ required: ["paths", "message"],
1688
+ additionalProperties: false,
1689
+ },
1690
+ },
1691
+ };
1692
+ }
1693
+
1649
1694
  function roundRobinToolNames(groups) {
1650
1695
  const names = [];
1651
1696
  const maxLength = Math.max(0, ...groups.map((group) => group.length));
@@ -2000,18 +2045,18 @@ export function selectProgressiveTools(
2000
2045
  if (commitProjectChanges) {
2001
2046
  return [commitProjectChanges, finish].filter(Boolean);
2002
2047
  }
2003
- const repositoryStateTool = runCommand
2004
- ? {
2005
- ...runCommand,
2006
- function: {
2007
- ...runCommand.function,
2008
- description: [
2009
- "Resolve the retained repository-state verification gate without changing task content merely to alter Git status.",
2010
- "Inspect status and diff, preserve intended task-owned changes, run appropriate checks, stage and commit only those changes, then rerun the exact retained verification command.",
2011
- ].join(" "),
2012
- },
2013
- }
2014
- : null;
2048
+ const observedCommit = constrainObservedRepositoryStateCommit(
2049
+ runCommand,
2050
+ config.repositoryStateRepairObservedPaths
2051
+ );
2052
+ if (observedCommit) {
2053
+ return [observedCommit, finish].filter(Boolean);
2054
+ }
2055
+ const repositoryStateTool = constrainRunCommand(
2056
+ runCommand,
2057
+ config.repositoryStateRepairInspectionCommand,
2058
+ "Run this exact bounded Git status inspection once. The runtime records the current dirty paths and will offer a path-enumerated commit tool on the next turn; do not substitute a broader status/diff loop or alter project content."
2059
+ );
2015
2060
  return [repositoryStateTool, finish].filter(Boolean);
2016
2061
  }
2017
2062
  const constrainedRepairPatch = annotateRequiredSymbolRepair(