@lazyingart/agintiflow 0.20.275 → 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.275",
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,
@@ -6495,6 +6499,101 @@ try {
6495
6499
  }),
6496
6500
  "plain task prose was mistaken for a repository-state gate without Git-status evidence"
6497
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
+ );
6498
6597
  assert(
6499
6598
  unchangedFailedTestRerunBlock(
6500
6599
  repositoryCleanGateFailure,
@@ -2003,10 +2003,15 @@ 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"
2010
2015
  );
2011
2016
  const conciseRepositoryStateRepairRuntime = nextStepRuntimeConfig(
2012
2017
  { provider: "deepseek", taskProfile: "slides" },
@@ -2056,6 +2061,63 @@ sameNames(
2056
2061
  ["run_command", "finish"],
2057
2062
  "a concise clean-repository acceptance failure exposed content mutation tools"
2058
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"
2120
+ );
2059
2121
  const taskOwnedRepositoryState = {
2060
2122
  meta: {
2061
2123
  goalContract: { revision: 6 },
@@ -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,
@@ -6400,6 +6477,158 @@ function safeTaskOwnedCommitPath(value = "") {
6400
6477
  return candidate;
6401
6478
  }
6402
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
+
6403
6632
  function taskOwnedMutationPathsSinceLatestCommit(verification = {}) {
6404
6633
  const latestCommittedRevision = [...(verification.commandRuns || [])]
6405
6634
  .reverse()
@@ -7932,6 +8161,8 @@ function runCommandResultHasDurableProgress(toolResult = {}) {
7932
8161
  return Boolean(
7933
8162
  policyAllowsMutation ||
7934
8163
  policy.substantiveTest === true ||
8164
+ (Array.isArray(toolResult.projectMutationPaths) &&
8165
+ toolResult.projectMutationPaths.length > 0) ||
7935
8166
  (Array.isArray(toolResult.verifiedGeneratedOutputPaths) &&
7936
8167
  toolResult.verifiedGeneratedOutputPaths.length > 0)
7937
8168
  );
@@ -11944,6 +12175,29 @@ export function nextStepRuntimeConfig(config = {}, state = {}) {
11944
12175
  runtimeConfig.testFailureRepairPatchTargets = [];
11945
12176
  runtimeConfig.repositoryStateRepairCommitPaths =
11946
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
+ );
11947
12201
  } else if (retainedRepositoryStateRepair) {
11948
12202
  delete runtimeConfig.completionFreshMutationRequired;
11949
12203
  delete runtimeConfig.completionFreshMutationRevision;
@@ -14258,20 +14512,45 @@ async function executeTool(browserState, toolCall, snapshot, config, store, obse
14258
14512
  ? config.taskOwnedCommitPaths
14259
14513
  : []),
14260
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
+ : [];
14261
14535
  if (
14262
14536
  requestedToolName === "commit_project_changes" &&
14263
- repositoryCommitPaths.length > 0 &&
14537
+ selectedRepositoryCommitPaths.length > 0 &&
14264
14538
  typeof args.message === "string" &&
14265
14539
  args.message.trim()
14266
14540
  ) {
14267
- const commitCommand = buildTaskOwnedCommitCommand(repositoryCommitPaths, args.message);
14541
+ const commitCommand = buildTaskOwnedCommitCommand(
14542
+ selectedRepositoryCommitPaths,
14543
+ args.message
14544
+ );
14268
14545
  if (commitCommand) {
14269
14546
  args = { command: commitCommand };
14270
14547
  toolName = "run_command";
14271
14548
  autoCorrection = {
14272
14549
  requestedToolName,
14273
14550
  toolName,
14274
- reason: "evidence-derived-task-owned-commit",
14551
+ reason: repositoryCommitPaths.length
14552
+ ? "evidence-derived-task-owned-commit"
14553
+ : "observed-task-owned-commit-selection",
14275
14554
  };
14276
14555
  }
14277
14556
  }
@@ -15057,6 +15336,20 @@ async function executeTool(browserState, toolCall, snapshot, config, store, obse
15057
15336
  const policy = evaluateCommandPolicy(rawCommand, config);
15058
15337
  const observedCommand = normalizeProjectCommand(rawCommand);
15059
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
+ );
15060
15353
  const requiredCommands = effectiveRequiredProjectCommands(
15061
15354
  state,
15062
15355
  state.meta?.projectVerification || {},
@@ -15068,10 +15361,24 @@ async function executeTool(browserState, toolCall, snapshot, config, store, obse
15068
15361
  const exactOutputSnapshotsBefore = isRequiredCommand && policy.writesWorkspace
15069
15362
  ? await captureExactOutputSnapshots(state, config)
15070
15363
  : [];
15364
+ const gitWorktreeBefore = captureMutationScope
15365
+ ? await captureGitWorktreeSnapshot(config)
15366
+ : null;
15071
15367
  if (config.useDockerSandbox) {
15072
15368
  await ensureDockerSandboxReady(config, observers);
15073
15369
  }
15074
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
+ );
15075
15382
  const generatedOutputPaths = commandResult.ok !== false
15076
15383
  ? await verifiedGeneratedOutputPaths(state, exactOutputSnapshotsBefore, config)
15077
15384
  : [];
@@ -15106,6 +15413,20 @@ async function executeTool(browserState, toolCall, snapshot, config, store, obse
15106
15413
  ...(generatedOutputPaths.length
15107
15414
  ? { verifiedGeneratedOutputPaths: generatedOutputPaths }
15108
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
+ : {}),
15109
15430
  ...commandResult,
15110
15431
  ...(permissionAdvice ? { permissionAdvice } : {}),
15111
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(