@tea-agent/loop-agent 0.33.7-beta.0 → 0.34.1
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/CHANGELOG.md +67 -16
- package/dist/application/task-lifecycle/advance.js +309 -9
- package/dist/application/task-lifecycle/gates.js +50 -0
- package/dist/application/task-lifecycle/observe.js +11 -2
- package/dist/application/task-lifecycle/plan-transitions.js +13 -21
- package/dist/commands/init-upgrade.js +32 -1
- package/dist/commands/init.js +94 -3
- package/dist/executors/dag-pi-executor.js +18 -3
- package/dist/executors/shell-executor.js +4 -91
- package/dist/executors/shell-write-guard.js +26 -8
- package/dist/shared/operator/capabilities.js +98 -44
- package/dist/shared/resilient-git.js +133 -0
- package/dist/task/source-prepare/artifact-meta.js +137 -0
- package/dist/task/source-prepare/index.js +2 -0
- package/dist/task/source-prepare/parse-intent.js +58 -10
- package/dist/task/source-prepare/prepare.js +229 -18
- package/dist/task/source-prepare/reference-integrity.js +18 -2
- package/dist/task/source-prepare/semantic-intake.js +404 -0
- package/dist/worker/console/app-data.js +2 -0
- package/dist/worker/console/chat/chat-event-store.js +190 -25
- package/dist/worker/console/chat/model-resolver.js +17 -0
- package/dist/worker/console/chat/pi-console-config.js +250 -32
- package/dist/worker/console/chat/pi-runtime.js +1007 -188
- package/dist/worker/console/chat/resource-loader.js +5 -4
- package/dist/worker/console/chat/routes.js +495 -157
- package/dist/worker/console/chat/runtime-context.js +48 -12
- package/dist/worker/console/chat/runtime-selection.js +59 -0
- package/dist/worker/console/chat/session-store.js +39 -0
- package/dist/worker/console/chat/shortcuts.js +1 -0
- package/dist/worker/console/chat/tool-adapter.js +9 -3
- package/dist/worker/console/chat/tools.js +5 -1
- package/dist/worker/console/dag-execution-receipt.js +380 -0
- package/dist/worker/console/interview/grill-me.js +7 -6
- package/dist/worker/console/operator-actions.js +785 -85
- package/dist/worker/console/prd-intake-bridge.js +393 -0
- package/dist/worker/console/recovery-cta.js +35 -6
- package/dist/worker/console/server.js +8 -15
- package/dist/worker/console/static/assets/index-BQkhJpV8.css +1 -0
- package/dist/worker/console/static/assets/index-BpuHmlSP.js +29 -0
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static-src/app/console-types.js +1 -0
- package/dist/worker/console/static-src/app/usePrdImport.js +2 -1
- package/dist/worker/console/static-src/app/useRecoveryActions.js +24 -1
- package/dist/worker/console/static-src/app/useRecoveryConsole.js +19 -1
- package/dist/worker/console/static-src/app/useTaskWizard.js +57 -2
- package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +45 -8
- package/dist/worker/console/static-src/operator-chat/landing-density.js +23 -0
- package/dist/worker/console/static-src/operator-chat/refs.js +9 -0
- package/dist/worker/console/static-src/operator-chat/runtime-snapshot-store.js +257 -0
- package/dist/worker/console/static-src/operator-chat/session-title-watcher.js +128 -0
- package/dist/worker/console/static-src/operator-chat/sidebar-split.js +90 -0
- package/dist/worker/console/static-src/operator-chat/spatial-overlay.js +37 -0
- package/dist/worker/console/static-src/operator-chat/useChatSessions.js +125 -22
- package/dist/worker/console/static-src/operator-chat/useChatStream.js +215 -184
- package/dist/worker/console/static-src/operator-chat/useChatThread.js +49 -5
- package/dist/worker/console/static-src/operator-chat/useComposer.js +17 -0
- package/dist/worker/console/static-src/operator-chat/useOverlayFocus.js +84 -0
- package/dist/worker/console/static-src/operator-chat/useRuntimeControls.js +225 -74
- package/dist/worker/console/static-src/operator-chat/useRuntimeSnapshot.js +196 -0
- package/dist/worker/console/static-src/operator-chat/useWorkspaceLayout.js +58 -0
- package/dist/worker/console/static-src/operator-chat/workspace-layout-mode.js +31 -0
- package/dist/worker/delivery/final-verification.js +13 -5
- package/dist/worker/delivery/package.js +31 -19
- package/dist/worker/delivery/verification-bundle.js +6 -4
- package/dist/worker/observability/read-model.js +3 -0
- package/dist/worker/observe/static/operator-chrome.css +5 -2
- package/dist/worker/observe/static/operator-chrome.js +6 -1
- package/dist/worker/observe/static/styles.css +39 -9
- package/dist/workflows/dag/backend-test-case-coverage-analysis.js +33 -462
- package/dist/workflows/dag/backend-test-case-manifest.js +0 -4
- package/dist/workflows/dag/backend-test-markdown-workflow.js +1 -25
- package/dist/workflows/dag/backend-test-module-stem.js +0 -5
- package/dist/workflows/dag/backend-test-pytest-collection.js +24 -345
- package/dist/workflows/dag/backend-test-scenario-param.js +82 -269
- package/dist/workflows/dag/backend-test-writer-completeness.js +16 -47
- package/dist/workflows/dag/dynamic-runtime/map.js +8 -24
- package/dist/workflows/dag/failure-category.js +3 -0
- package/dist/workflows/dag/frontend-worktree-diff.js +12 -27
- package/dist/workflows/dag/init-hybrid.js +49 -55
- package/dist/workflows/dag/node-execution.js +3 -2
- package/dist/workflows/dag/retry-policy.js +44 -11
- package/dist/workflows/dag/runner.js +6 -0
- package/dist/workflows/dag/scheduler.js +49 -1
- package/dist/workflows/dag/types.js +0 -7
- package/dist/workflows/dag/validate.js +16 -2
- package/dist/workflows/dag/workspace-checkpoint.js +8 -27
- package/docs/templates/agent-dag.schema.json +4 -4
- package/docs/templates/backend-test-dag.json +29 -32
- package/harness.json +1 -1
- package/package.json +1 -1
- package/skills/local-jacoco-coverage/SKILL.md +281 -0
- package/skills/local-jacoco-coverage/references/requirement-to-source-mapping.md +85 -0
- package/skills/local-jacoco-coverage/references/runtime-alignment.md +106 -0
- package/skills/local-jacoco-coverage/scripts/run-coverage-analysis.sh +148 -0
- package/skills/local-jacoco-coverage/scripts/start-jacoco-agent.sh +110 -0
- package/skills/loop-agent/references/command-reference.md +3 -1
- package/skills/loop-agent/references/source-and-plan-practice.md +13 -0
- package/skills/loop-agent/references/task-workflow.md +4 -0
- package/dist/worker/console/chat/instruction-skills.js +0 -217
- package/dist/worker/console/static/assets/index-CnUXAqxG.css +0 -1
- package/dist/worker/console/static/assets/index-CteJFFL2.js +0 -29
|
@@ -5,7 +5,7 @@ import { logicalRevisionForState, observeTaskContractState, } from "../../task/c
|
|
|
5
5
|
import { assessDagRunLiveness, listAllDagRunEntries, readDagRunSpec, readDagRunState, } from "../../workflows/dag/lifecycle.js";
|
|
6
6
|
import { resolveRunningControllerIdentity } from "../../shared/package-metadata.js";
|
|
7
7
|
import { loadLifecycleRecord } from "./record.js";
|
|
8
|
-
import { buildGateBinding, buildWriteSetGate, controllerIdentityAnchor, hashCanonicalPayload, normalizeWriteSet, sha256Hex, } from "./gates.js";
|
|
8
|
+
import { buildGateBinding, buildWriteSetGate, buildWriteSetGateDelta, controllerIdentityAnchor, hashCanonicalPayload, normalizeWriteSet, sha256Hex, } from "./gates.js";
|
|
9
9
|
async function exists(filePath) {
|
|
10
10
|
try {
|
|
11
11
|
await access(filePath);
|
|
@@ -263,6 +263,15 @@ export async function observeTaskLifecycle(repoRoot, taskId) {
|
|
|
263
263
|
: null),
|
|
264
264
|
})
|
|
265
265
|
: null;
|
|
266
|
+
const gateDelta = gateBound != null
|
|
267
|
+
? buildWriteSetGateDelta({
|
|
268
|
+
writeSet: gateBound.writeSet,
|
|
269
|
+
contractAllowedPaths: taskConfigAllowed,
|
|
270
|
+
writers: (parsedSpec?.tasks ?? [])
|
|
271
|
+
.filter((task) => task.writePolicy === "exclusive")
|
|
272
|
+
.map((task) => ({ id: task.id, writeSet: task.writeSet ?? [] })),
|
|
273
|
+
})
|
|
274
|
+
: null;
|
|
266
275
|
const lastGate = record?.lastGate;
|
|
267
276
|
const currentDigest = gateBound != null
|
|
268
277
|
? buildWriteSetGate({ taskId, bound: gateBound }).digest
|
|
@@ -277,7 +286,7 @@ export async function observeTaskLifecycle(repoRoot, taskId) {
|
|
|
277
286
|
// not already approved/rejected. Rejected same-digest gates stay closed
|
|
278
287
|
// until contract/source/DAG/writeSet/controller binding changes.
|
|
279
288
|
const gate = gateBound && strictValidated && !approvedCurrent && !rejectedCurrent
|
|
280
|
-
? buildWriteSetGate({ taskId, bound: gateBound })
|
|
289
|
+
? buildWriteSetGate({ taskId, bound: gateBound, delta: gateDelta })
|
|
281
290
|
: null;
|
|
282
291
|
const gateOpen = Boolean(gate);
|
|
283
292
|
let legacyState;
|
|
@@ -62,17 +62,21 @@ export function planTransitions(input) {
|
|
|
62
62
|
expectedTransitions.push("contract-managed");
|
|
63
63
|
}
|
|
64
64
|
else if (!sourceReady || !contractManaged) {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
65
|
+
// Console PRD Intake Bridge (2026-08-11), two-phase:
|
|
66
|
+
// 1) hasPrdInput without engineering boundary → import only (archive
|
|
67
|
+
// references). Do NOT prepare yet so multi-file bootstrap can finish
|
|
68
|
+
// all imports before the one-shot Semantic Intake burns.
|
|
69
|
+
// 2) alreadyImportedPrd without new --prd (and/or with boundary) →
|
|
70
|
+
// prepare-contract so deterministic parse + Semantic Intake run.
|
|
71
|
+
// prepare soft-stops on EMPTY_ALLOWED_PATHS etc. and never opens the
|
|
72
|
+
// writeSet gate without a complete managed contract.
|
|
73
|
+
if (shouldImportPrd && !input.hasEngineeringBoundary) {
|
|
74
|
+
actions.push({ type: "import-prd" });
|
|
75
|
+
expectedTransitions.push("prd-imported");
|
|
72
76
|
actions.push({ type: "stop", reason: "blocker" });
|
|
73
77
|
blockers.push({
|
|
74
78
|
code: "BOUNDARY_REQUIRED",
|
|
75
|
-
message: "PRD imported;
|
|
79
|
+
message: "PRD imported; re-run task advance (optionally with --allowed-path / --verify) to prepare contract / semantic intake",
|
|
76
80
|
});
|
|
77
81
|
return {
|
|
78
82
|
actions,
|
|
@@ -80,7 +84,7 @@ export function planTransitions(input) {
|
|
|
80
84
|
blockers,
|
|
81
85
|
next: {
|
|
82
86
|
kind: "advance",
|
|
83
|
-
command: `loop-agent task advance ${snapshot.taskId} --
|
|
87
|
+
command: `loop-agent task advance ${snapshot.taskId} --json`,
|
|
84
88
|
},
|
|
85
89
|
expectedTransitions,
|
|
86
90
|
};
|
|
@@ -218,18 +222,6 @@ export function planTransitions(input) {
|
|
|
218
222
|
expectedTransitions,
|
|
219
223
|
};
|
|
220
224
|
}
|
|
221
|
-
function stopWith(actions, expectedTransitions, blockers, snapshot, reason) {
|
|
222
|
-
return {
|
|
223
|
-
actions: [...actions, { type: "stop", reason }],
|
|
224
|
-
gate: snapshot.gate ?? null,
|
|
225
|
-
blockers,
|
|
226
|
-
next: {
|
|
227
|
-
kind: "advance",
|
|
228
|
-
command: `loop-agent task advance ${snapshot.taskId} --json`,
|
|
229
|
-
},
|
|
230
|
-
expectedTransitions,
|
|
231
|
-
};
|
|
232
|
-
}
|
|
233
225
|
function deriveNext(snapshot, gate, approveGate) {
|
|
234
226
|
if (gate && !approveGate) {
|
|
235
227
|
return { kind: "approve-gate", command: gate.approveCommand };
|
|
@@ -1023,6 +1023,7 @@ function assertPendingAcceptance(value, state, task) {
|
|
|
1023
1023
|
}
|
|
1024
1024
|
const pending = value;
|
|
1025
1025
|
const desiredSha256 = task.evidence.desiredSha256;
|
|
1026
|
+
const sourceAnchorSha256 = task.evidence.sourceAnchorSha256;
|
|
1026
1027
|
const guard = pending.guard;
|
|
1027
1028
|
const receipt = pending.receipt;
|
|
1028
1029
|
const transition = pending.transition;
|
|
@@ -1039,6 +1040,9 @@ function assertPendingAcceptance(value, state, task) {
|
|
|
1039
1040
|
!isSha256(guard.desiredSha256) ||
|
|
1040
1041
|
!isSha256(desiredSha256) ||
|
|
1041
1042
|
guard.desiredSha256 !== desiredSha256 ||
|
|
1043
|
+
(sourceAnchorSha256 === undefined
|
|
1044
|
+
? guard.sourceAnchorSha256 !== undefined
|
|
1045
|
+
: guard.sourceAnchorSha256 !== sourceAnchorSha256) ||
|
|
1042
1046
|
!isMergeReceipt(receipt) ||
|
|
1043
1047
|
receipt.taskId !== guard.taskId ||
|
|
1044
1048
|
receipt.path !== guard.path ||
|
|
@@ -1076,6 +1080,9 @@ function acceptedSurface(input) {
|
|
|
1076
1080
|
acceptedEntry.acceptedMerge = {
|
|
1077
1081
|
currentSha256: input.currentSha256,
|
|
1078
1082
|
desiredSha256: input.desiredSha256,
|
|
1083
|
+
...(typeof input.task.evidence.sourceAnchorSha256 === "string"
|
|
1084
|
+
? { sourceAnchorSha256: input.task.evidence.sourceAnchorSha256 }
|
|
1085
|
+
: {}),
|
|
1079
1086
|
};
|
|
1080
1087
|
return { beforeEntry: cloneJson(entry), accepted };
|
|
1081
1088
|
}
|
|
@@ -1210,6 +1217,7 @@ async function recordAcceptedMerge(input) {
|
|
|
1210
1217
|
if (!desiredSha256) {
|
|
1211
1218
|
throw new Error(`init upgrade cannot accept ${input.task.path}: desired hash evidence is missing`);
|
|
1212
1219
|
}
|
|
1220
|
+
const sourceAnchorSha256 = input.task.evidence.sourceAnchorSha256;
|
|
1213
1221
|
const canonicalPath = normalizeAllowedPath(input.task.path);
|
|
1214
1222
|
const surfacePath = path.join(input.state.repoRoot, INIT_SURFACE_STATE_PATH);
|
|
1215
1223
|
let pending = input.pending;
|
|
@@ -1263,6 +1271,9 @@ async function recordAcceptedMerge(input) {
|
|
|
1263
1271
|
allowedPaths: [canonicalPath],
|
|
1264
1272
|
currentSha256: input.currentSha256,
|
|
1265
1273
|
desiredSha256,
|
|
1274
|
+
...(sourceAnchorSha256 !== undefined
|
|
1275
|
+
? { sourceAnchorSha256 }
|
|
1276
|
+
: {}),
|
|
1266
1277
|
},
|
|
1267
1278
|
receipt,
|
|
1268
1279
|
transition: {
|
|
@@ -2412,7 +2423,27 @@ async function executeUpgrade(directory, state, input) {
|
|
|
2412
2423
|
});
|
|
2413
2424
|
state.actionReceipts.push(...safe.applied);
|
|
2414
2425
|
await writeJsonAtomic(path.join(directory, "change-manifest.json"), state.actionReceipts);
|
|
2415
|
-
|
|
2426
|
+
let post = await checkInitUpdate({ repoRoot: state.repoRoot });
|
|
2427
|
+
// Converge remaining safe deterministic init actions before the final check.
|
|
2428
|
+
// Project/governance-derived drift (e.g. a personalized project rename after
|
|
2429
|
+
// an accepted merge) settles here instead of failing finalCheck. Model merge
|
|
2430
|
+
// tasks and human decisions are never auto-applied.
|
|
2431
|
+
while (post.deterministicActions.length > 0) {
|
|
2432
|
+
const converged = await applyInitUpdate({
|
|
2433
|
+
repoRoot: state.repoRoot,
|
|
2434
|
+
applySafe: true,
|
|
2435
|
+
clientRecovery: "project",
|
|
2436
|
+
});
|
|
2437
|
+
state.actionReceipts.push(...converged.applied);
|
|
2438
|
+
await writeJsonAtomic(path.join(directory, "change-manifest.json"), state.actionReceipts);
|
|
2439
|
+
if (converged.applied.length === 0)
|
|
2440
|
+
break;
|
|
2441
|
+
post = await checkInitUpdate({ repoRoot: state.repoRoot });
|
|
2442
|
+
if (post.humanDecisions.length > 0 ||
|
|
2443
|
+
post.modelMergeTasks.length > 0) {
|
|
2444
|
+
break;
|
|
2445
|
+
}
|
|
2446
|
+
}
|
|
2416
2447
|
state.humanDecisions = post.humanDecisions;
|
|
2417
2448
|
if (post.humanDecisions.length > 0) {
|
|
2418
2449
|
state.mergeTasks = [];
|
package/dist/commands/init.js
CHANGED
|
@@ -1414,6 +1414,70 @@ async function readExistingSurfaceState(repoRoot) {
|
|
|
1414
1414
|
return undefined;
|
|
1415
1415
|
return parsed;
|
|
1416
1416
|
}
|
|
1417
|
+
/** The IDE schema reference remains a structural update signal. */
|
|
1418
|
+
const HARNESS_STRUCTURAL_VALUE_KEYS = new Set(["$schema"]);
|
|
1419
|
+
const PI_MODEL_ROUTING_KEYS = new Set(["LOW", "MED", "HIGH", "defaultModel"]);
|
|
1420
|
+
function isPiModelRoutingPath(path) {
|
|
1421
|
+
return path.length === 2 && path[0] === "executors" && path[1] === "pi";
|
|
1422
|
+
}
|
|
1423
|
+
/**
|
|
1424
|
+
* Attribute-set iteration comparison for harness.json: object key shapes must
|
|
1425
|
+
* agree at every level, except that the Pi model-routing fields are opaque
|
|
1426
|
+
* project-owned configuration. Scalars and array contents may otherwise differ
|
|
1427
|
+
* freely unless the key is structurally sensitive.
|
|
1428
|
+
*/
|
|
1429
|
+
function harnessJsonShapeMatches(current, desired, path = []) {
|
|
1430
|
+
if (isRecord(current) || isRecord(desired)) {
|
|
1431
|
+
if (!isRecord(current) || !isRecord(desired))
|
|
1432
|
+
return false;
|
|
1433
|
+
const filterKeys = (value) => Object.keys(value)
|
|
1434
|
+
.filter((key) => !isPiModelRoutingPath(path) || !PI_MODEL_ROUTING_KEYS.has(key))
|
|
1435
|
+
.sort();
|
|
1436
|
+
const currentKeys = filterKeys(current);
|
|
1437
|
+
const desiredKeys = filterKeys(desired);
|
|
1438
|
+
if (currentKeys.length !== desiredKeys.length)
|
|
1439
|
+
return false;
|
|
1440
|
+
for (let index = 0; index < currentKeys.length; index += 1) {
|
|
1441
|
+
const key = currentKeys[index];
|
|
1442
|
+
if (key !== desiredKeys[index])
|
|
1443
|
+
return false;
|
|
1444
|
+
if (HARNESS_STRUCTURAL_VALUE_KEYS.has(key)) {
|
|
1445
|
+
if (!isDeepStrictEqual(current[key], desired[key]))
|
|
1446
|
+
return false;
|
|
1447
|
+
continue;
|
|
1448
|
+
}
|
|
1449
|
+
if (!harnessJsonShapeMatches(current[key], desired[key], [...path, key])) {
|
|
1450
|
+
return false;
|
|
1451
|
+
}
|
|
1452
|
+
}
|
|
1453
|
+
return true;
|
|
1454
|
+
}
|
|
1455
|
+
return true;
|
|
1456
|
+
}
|
|
1457
|
+
/**
|
|
1458
|
+
* Stable package/source anchor for generated desired content: sha256 of the
|
|
1459
|
+
* desired content re-rendered through buildDesiredSurfaceContent with fixed
|
|
1460
|
+
* sentinel identity values (PROJECT_NAME_TOKEN / GOVERNANCE_ROOT_TOKEN). The
|
|
1461
|
+
* anchor is therefore deterministic and invariant to the real project name and
|
|
1462
|
+
* governance root: a project rename or governance-root change never invalidates
|
|
1463
|
+
* a previously accepted semantic merge, while package/template content changes
|
|
1464
|
+
* still do. Rendering with sentinels (instead of reverse split/join replacement
|
|
1465
|
+
* on already-rendered bytes) avoids false drift when a real project name is a
|
|
1466
|
+
* common substring such as `init` or `docs`.
|
|
1467
|
+
*/
|
|
1468
|
+
async function stableDesiredSourceAnchor(input) {
|
|
1469
|
+
const manifestPath = targetPathToManifestPath(input.entry.path, input.governanceRoot);
|
|
1470
|
+
const sentinel = await buildDesiredSurfaceContent({
|
|
1471
|
+
assetRoot: input.assetRoot,
|
|
1472
|
+
repoRoot: input.repoRoot,
|
|
1473
|
+
projectName: PROJECT_NAME_TOKEN,
|
|
1474
|
+
governanceRoot: GOVERNANCE_ROOT_TOKEN,
|
|
1475
|
+
entry: { ...input.entry, path: manifestPath },
|
|
1476
|
+
});
|
|
1477
|
+
return sentinel.content === undefined
|
|
1478
|
+
? undefined
|
|
1479
|
+
: sha256Text(sentinel.content);
|
|
1480
|
+
}
|
|
1417
1481
|
/**
|
|
1418
1482
|
* Lightweight read-only preflight for the post-upgrade init surface notifier.
|
|
1419
1483
|
*
|
|
@@ -1458,10 +1522,20 @@ async function buildCurrentSurfaceState(input) {
|
|
|
1458
1522
|
entry: manifestEntry,
|
|
1459
1523
|
});
|
|
1460
1524
|
const sourceSha256 = desired.content === undefined ? undefined : sha256Text(desired.content);
|
|
1525
|
+
const sourceAnchorSha256 = entry.mode !== "generated" || desired.content === undefined
|
|
1526
|
+
? undefined
|
|
1527
|
+
: await stableDesiredSourceAnchor({
|
|
1528
|
+
assetRoot,
|
|
1529
|
+
repoRoot: input.repoRoot,
|
|
1530
|
+
projectName: input.projectName,
|
|
1531
|
+
governanceRoot: input.governanceRoot,
|
|
1532
|
+
entry,
|
|
1533
|
+
});
|
|
1461
1534
|
const base = {
|
|
1462
1535
|
status: targetStat ? "present" : "missing",
|
|
1463
1536
|
relationship: "missing-from-target",
|
|
1464
1537
|
sourceSha256,
|
|
1538
|
+
sourceAnchorSha256,
|
|
1465
1539
|
sourcePath: desired.sourcePath
|
|
1466
1540
|
? repoRelative(assetRoot, desired.sourcePath)
|
|
1467
1541
|
: undefined,
|
|
@@ -1510,7 +1584,7 @@ async function buildCurrentSurfaceState(input) {
|
|
|
1510
1584
|
if (targetRelativePath === "harness.json" &&
|
|
1511
1585
|
desired.content !== undefined) {
|
|
1512
1586
|
try {
|
|
1513
|
-
semanticallyMatchesGeneratedJson =
|
|
1587
|
+
semanticallyMatchesGeneratedJson = harnessJsonShapeMatches(JSON.parse(current.toString("utf-8")), JSON.parse(desired.content));
|
|
1514
1588
|
}
|
|
1515
1589
|
catch {
|
|
1516
1590
|
// Invalid JSON remains local-existing-unknown for model merge / doctor.
|
|
@@ -1563,6 +1637,9 @@ async function writeInitSurfaceState(input) {
|
|
|
1563
1637
|
harness.acceptedMerge = {
|
|
1564
1638
|
currentSha256: harness.currentSha256,
|
|
1565
1639
|
desiredSha256: harness.sourceSha256,
|
|
1640
|
+
...(harness.sourceAnchorSha256 !== undefined
|
|
1641
|
+
? { sourceAnchorSha256: harness.sourceAnchorSha256 }
|
|
1642
|
+
: {}),
|
|
1566
1643
|
};
|
|
1567
1644
|
}
|
|
1568
1645
|
}
|
|
@@ -2729,6 +2806,7 @@ function modelMergeTaskFor(pathName, state, allPaths, recordedState) {
|
|
|
2729
2806
|
baseSha256: recordedState?.files[pathName]?.currentSha256,
|
|
2730
2807
|
currentSha256: state.currentSha256,
|
|
2731
2808
|
desiredSha256: state.sourceSha256,
|
|
2809
|
+
sourceAnchorSha256: state.sourceAnchorSha256,
|
|
2732
2810
|
},
|
|
2733
2811
|
};
|
|
2734
2812
|
}
|
|
@@ -2847,8 +2925,16 @@ export async function checkInitUpdate(input) {
|
|
|
2847
2925
|
if (state.relationship === "local-existing-unknown") {
|
|
2848
2926
|
const acceptedMerge = recordedState?.files[pathName]?.acceptedMerge;
|
|
2849
2927
|
if (acceptedMerge) {
|
|
2928
|
+
// Generated acceptance is bound to the stable source anchor so that
|
|
2929
|
+
// project-derived desired drift (projectName/governanceRoot changes)
|
|
2930
|
+
// cannot invalidate an already accepted merge. Copied files remain
|
|
2931
|
+
// byte-strict; desiredSha256 stays the audit receipt in both cases.
|
|
2932
|
+
const anchorAccepted = state.mode === "generated" &&
|
|
2933
|
+
state.sourceAnchorSha256 !== undefined &&
|
|
2934
|
+
acceptedMerge.sourceAnchorSha256 === state.sourceAnchorSha256;
|
|
2850
2935
|
if (acceptedMerge.currentSha256 === state.currentSha256 &&
|
|
2851
|
-
|
|
2936
|
+
(anchorAccepted ||
|
|
2937
|
+
acceptedMerge.desiredSha256 === state.sourceSha256)) {
|
|
2852
2938
|
continue;
|
|
2853
2939
|
}
|
|
2854
2940
|
// A previously accepted semantic merge is user-preserving ownership.
|
|
@@ -2906,7 +2992,12 @@ export async function checkInitUpdate(input) {
|
|
|
2906
2992
|
reason: `replace the legacy default governanceRoot docs with ${governanceRoot}`,
|
|
2907
2993
|
});
|
|
2908
2994
|
}
|
|
2909
|
-
|
|
2995
|
+
// A harness that already matches the current generated surface except for
|
|
2996
|
+
// opaque Pi routing fields is modern, not a legacy-routing candidate.
|
|
2997
|
+
// Keep the explicit migration assessment for every other harness shape.
|
|
2998
|
+
if (isRecord(harness) &&
|
|
2999
|
+
currentState.files["harness.json"]?.relationship !==
|
|
3000
|
+
"matches-current-generated") {
|
|
2910
3001
|
const modelMigration = assessHarnessModelMigration(harness);
|
|
2911
3002
|
if (modelMigration.kind === "safe") {
|
|
2912
3003
|
deterministicActions.push({
|
|
@@ -8,7 +8,7 @@ import { cleanupPlaywrightCliDefaultSession, createPlaywrightCliTool, PI_COMMAND
|
|
|
8
8
|
import { dagCommandPolicyAllows, resolveDagCommandPolicy, } from "../workflows/dag/types.js";
|
|
9
9
|
import { redactPromptForLog, truncateOutput, } from "../shared/output-truncation.js";
|
|
10
10
|
import { GitStatusUnavailableError, pathsChangedDuringRun, readGitStatusPorcelain, recoverRootNulArtifact, snapshotGitStatusPathFingerprints, snapshotGitStatusPorcelain, validateShellWriteGuard, } from "./shell-write-guard.js";
|
|
11
|
-
import { isWriterEmptyDiffRetryCandidate, INCOMPLETE_WRITE_SET_RETRY_CATEGORY, WRITER_EMPTY_DIFF_RETRY_CATEGORY, } from "../workflows/dag/retry-policy.js";
|
|
11
|
+
import { isWriterEmptyDiffRetryCandidate, isWriterTransportRetryCandidate, INCOMPLETE_WRITE_SET_RETRY_CATEGORY, WRITER_CLEAN_TIMEOUT_RETRY_CATEGORY, WRITER_EMPTY_DIFF_RETRY_CATEGORY, } from "../workflows/dag/retry-policy.js";
|
|
12
12
|
import { assessBackendTestMdPlanCompleteness, assessBackendTestMdWriterCompleteness, assessBackendTestPytestPlanCompleteness, assessBackendTestPytestWriterCompleteness, assessBackendTestShardChildCompleteness, backendTestWriterProgressRoleForTask, classifyBackendTestWriterCompletenessFailure, isBackendTestCompletenessRetryCandidate, isBackendTestMdPlanTask, isBackendTestPytestPlanTask, isBackendTestShardChildTask, writeBackendTestWriterProgressArtifacts, } from "../workflows/dag/backend-test-writer-completeness.js";
|
|
13
13
|
import { redactSecrets, truncateUtf8Preview } from "../shared/preview.js";
|
|
14
14
|
/**
|
|
@@ -831,6 +831,18 @@ export async function executeDagPiNode(input, meta, piStepFn = executePiStep, wr
|
|
|
831
831
|
if (meta.writeGuardAttribution === "best-effort") {
|
|
832
832
|
stderrParts.push("write guard note: concurrent rank writers use best-effort per-node attribution; keep same-rank writeSet entries disjoint");
|
|
833
833
|
}
|
|
834
|
+
// Pure provider stall/timeout with zero workspace side effects is eligible
|
|
835
|
+
// for a single writer transport retry (implement-pi flake resilience).
|
|
836
|
+
// Partial writes, tool calls, or write-guard failures keep raw `timeout`.
|
|
837
|
+
const writerCleanTimeout = !mapped.ok &&
|
|
838
|
+
mapped.failureCategory === "timeout" &&
|
|
839
|
+
writeGuardOk &&
|
|
840
|
+
!completenessFailure &&
|
|
841
|
+
!writerThinkingExhausted &&
|
|
842
|
+
isWriterTransportRetryCandidate(input.task) &&
|
|
843
|
+
changeManifestChangedFiles !== undefined &&
|
|
844
|
+
changeManifestChangedFiles.length === 0 &&
|
|
845
|
+
readWriterThinkingExhaustionEvidence(result).writeToolCallCount === 0;
|
|
834
846
|
return {
|
|
835
847
|
...mapped,
|
|
836
848
|
ok: false,
|
|
@@ -851,7 +863,8 @@ export async function executeDagPiNode(input, meta, piStepFn = executePiStep, wr
|
|
|
851
863
|
// the continuation is retryable and carries the embedded target paths.
|
|
852
864
|
// Provider/transport failures (quota, auth, network, timeout,
|
|
853
865
|
// rate-limit, unavailable) keep their original category so a provider
|
|
854
|
-
// outage is never masked as a recoverable local write problem
|
|
866
|
+
// outage is never masked as a recoverable local write problem — except
|
|
867
|
+
// clean timeouts with zero attributed writes, remapped below.
|
|
855
868
|
writeGuardOk &&
|
|
856
869
|
completenessFailure?.failureCategory ===
|
|
857
870
|
INCOMPLETE_WRITE_SET_RETRY_CATEGORY &&
|
|
@@ -867,7 +880,9 @@ export async function executeDagPiNode(input, meta, piStepFn = executePiStep, wr
|
|
|
867
880
|
// upgrade to incomplete-write-set above.
|
|
868
881
|
writerThinkingExhausted
|
|
869
882
|
? WRITER_THINKING_EXHAUSTED_CATEGORY
|
|
870
|
-
:
|
|
883
|
+
: writerCleanTimeout
|
|
884
|
+
? WRITER_CLEAN_TIMEOUT_RETRY_CATEGORY
|
|
885
|
+
: mapped.failureCategory,
|
|
871
886
|
durationMs: mapped.durationMs || Date.now() - started,
|
|
872
887
|
};
|
|
873
888
|
}
|
|
@@ -31,7 +31,7 @@ import { analyzeBackendTestCaseCoverage, analyzeBackendTestMarkdownPytestCorresp
|
|
|
31
31
|
import { materializeBackendTestResultFromPytestHtml, materializeBackendTestResultFromRunDir, parsePytestHtmlReport, } from "../workflows/dag/backend-test-result-contract.js";
|
|
32
32
|
import { collectBackendTestHumanCaseCatalog, collectBackendTestMappedPytestScripts, resolveBackendTestMappedPytestScripts, collectJacocoCoverage, hasBlockingBackendMarkdownSafetyFindings, hasBlockingBackendMarkdownModuleStemFindings, inspectBackendTestEnvironment, requiredBackendMarkdownCaseAcIds, renderBackendTestFacts, renderBackendTestHtml, renderBackendTestL5Dashboard, redactBackendTestOutput, validateBackendMarkdownCases, validateBackendMarkdownTraceability, writeRunReport, } from "../workflows/dag/backend-test-markdown-workflow.js";
|
|
33
33
|
import { applyDeterministicScenarioParamRepairs, assessBackendScenarioParamConsistency, classifyBackendTestFailureWithScenarioParam, readBackendScenarioParamFacts, renderBackendTestFailureAnalysis, writeBackendScenarioParamArtifacts, writeScenarioParamRepairAudit, } from "../workflows/dag/backend-test-scenario-param.js";
|
|
34
|
-
import { assessBackendPytestCollection, assessMissingBackendPytestScripts, assessPriorityOnlyBackendPytestModules, assertBackendTestExecutionReadinessFresh, buildBackendPytestAssetInventory,
|
|
34
|
+
import { assessBackendPytestCollection, assessMissingBackendPytestScripts, assessPriorityOnlyBackendPytestModules, assertBackendTestExecutionReadinessFresh, buildBackendPytestAssetInventory, materializeBackendTestExecutionReadiness, materializeEffectiveBackendPytestCollection, readBackendPytestCollectionFacts, readBackendTestExecutionReadiness, writeBackendPytestCollectionArtifacts, } from "../workflows/dag/backend-test-pytest-collection.js";
|
|
35
35
|
import { computeL5ReportMetrics } from "../workflows/dag/l5-report-metrics.js";
|
|
36
36
|
import { buildBackendTestCanonicalResultFromInitialShellSnippet, materializeBackendTestClassification, } from "../workflows/dag/backend-test-classification-contract.js";
|
|
37
37
|
import { backendTestSemanticReviewSchema, materializeBackendTestSemanticReview, } from "../workflows/dag/backend-test-semantic-review-contract.js";
|
|
@@ -678,42 +678,6 @@ async function executeBackendTestPipeline(input, meta) {
|
|
|
678
678
|
fixtureResolution,
|
|
679
679
|
});
|
|
680
680
|
}
|
|
681
|
-
try {
|
|
682
|
-
const correspondence = await analyzeBackendTestMarkdownPytestCorrespondence({
|
|
683
|
-
workspaceRoot: input.cwd,
|
|
684
|
-
taskId: meta.spec.sourceBinding?.taskId ?? "unbound",
|
|
685
|
-
});
|
|
686
|
-
const correspondenceReportPath = await writeRunReport(meta.runDir, "backend-test-markdown-pytest-correspondence-initial.md", correspondence.markdown);
|
|
687
|
-
const contractsDir = path.join(meta.runDir, "contracts");
|
|
688
|
-
await mkdir(contractsDir, { recursive: true });
|
|
689
|
-
const correspondenceFactsPath = path.join(contractsDir, "backend-test-markdown-pytest-correspondence-initial.json");
|
|
690
|
-
await writeFile(correspondenceFactsPath, JSON.stringify(correspondence.facts, null, 2), "utf8");
|
|
691
|
-
outputs.push(`initialCorrespondence=${correspondenceReportPath}`, `initialCorrespondenceFacts=${correspondenceFactsPath}`);
|
|
692
|
-
if (correspondence.facts.status === "FAIL" && facts.status === "PASS") {
|
|
693
|
-
const correspondenceRepairPaths = Array.from(new Set(correspondence.facts.entries
|
|
694
|
-
.filter((entry) => entry.status !== "EXACT_1_TO_1")
|
|
695
|
-
.flatMap((entry) => [entry.expectedScript, ...entry.actualScripts])
|
|
696
|
-
.filter((candidate) => candidate.startsWith("testcase/") && candidate.endsWith(".py")))).sort();
|
|
697
|
-
if (correspondenceRepairPaths.length > 0) {
|
|
698
|
-
facts.status = "REPAIRABLE";
|
|
699
|
-
facts.repairEligible = true;
|
|
700
|
-
facts.repairPaths = Array.from(new Set([...facts.repairPaths, ...correspondenceRepairPaths])).sort();
|
|
701
|
-
facts.findings.push({
|
|
702
|
-
kind: "markdown-pytest-correspondence",
|
|
703
|
-
classification: "test-asset-defect",
|
|
704
|
-
repairability: "repairable",
|
|
705
|
-
detail: correspondence.facts.entries
|
|
706
|
-
.filter((entry) => entry.status !== "EXACT_1_TO_1")
|
|
707
|
-
.flatMap((entry) => entry.findings)
|
|
708
|
-
.join("; ")
|
|
709
|
-
.slice(0, 12_000) || "Markdown-to-pytest correspondence is incomplete",
|
|
710
|
-
});
|
|
711
|
-
}
|
|
712
|
-
}
|
|
713
|
-
}
|
|
714
|
-
catch (error) {
|
|
715
|
-
outputs.push(`initialCorrespondence=UNAVAILABLE: ${error instanceof Error ? error.message : String(error)}`);
|
|
716
|
-
}
|
|
717
681
|
const artifacts = await writeBackendPytestCollectionArtifacts({
|
|
718
682
|
runDir: meta.runDir,
|
|
719
683
|
stem: "initial",
|
|
@@ -812,56 +776,12 @@ async function executeBackendTestPipeline(input, meta) {
|
|
|
812
776
|
facts: effective,
|
|
813
777
|
});
|
|
814
778
|
const scenarioFacts = await readBackendScenarioParamFacts(path.join(meta.runDir, "contracts", "backend-test-scenario-param-consistency-facts.json"));
|
|
815
|
-
const correspondence = meta.spec.sourceBinding
|
|
816
|
-
? await analyzeBackendTestMarkdownPytestCorrespondence({
|
|
817
|
-
workspaceRoot: input.cwd,
|
|
818
|
-
taskId: meta.spec.sourceBinding.taskId,
|
|
819
|
-
})
|
|
820
|
-
: undefined;
|
|
821
|
-
const eligibility = correspondence
|
|
822
|
-
? buildBackendTestItemEligibility(effective.collectedItemIds, {
|
|
823
|
-
correspondenceEntries: correspondence.facts.entries,
|
|
824
|
-
scenarioEntries: scenarioFacts.entries,
|
|
825
|
-
})
|
|
826
|
-
: { eligibleItemIds: effective.collectedItemIds, excludedItems: [] };
|
|
827
|
-
const eligibilityInputHashes = Object.fromEntries((correspondence?.facts.inputFiles ?? []).map((item) => [item.path, item.sha256]));
|
|
828
|
-
const eligibilityFactsPath = path.join(meta.runDir, "contracts", "backend-test-execution-eligibility.json");
|
|
829
|
-
await mkdir(path.dirname(eligibilityFactsPath), { recursive: true });
|
|
830
|
-
await writeFile(eligibilityFactsPath, `${JSON.stringify({
|
|
831
|
-
schemaId: "backend-test-execution-eligibility-v1",
|
|
832
|
-
status: eligibility.eligibleItemIds.length === 0 ? "BLOCKED" : eligibility.excludedItems.length > 0 ? "PARTIAL" : "PASS",
|
|
833
|
-
collectedItemCount: effective.collectedItemIds.length,
|
|
834
|
-
eligibleItemCount: eligibility.eligibleItemIds.length,
|
|
835
|
-
excludedItemCount: eligibility.excludedItems.length,
|
|
836
|
-
eligibleItemIds: eligibility.eligibleItemIds,
|
|
837
|
-
excludedItems: eligibility.excludedItems,
|
|
838
|
-
inputHashes: eligibilityInputHashes,
|
|
839
|
-
}, null, 2)}\n`, "utf8");
|
|
840
|
-
const eligibilityReportPath = await writeRunReport(meta.runDir, "backend-test-execution-eligibility.md", [
|
|
841
|
-
"# Backend Test Execution Eligibility",
|
|
842
|
-
"",
|
|
843
|
-
"## Status",
|
|
844
|
-
"",
|
|
845
|
-
eligibility.eligibleItemIds.length === 0 ? "BLOCKED" : eligibility.excludedItems.length > 0 ? "PARTIAL" : "PASS",
|
|
846
|
-
"",
|
|
847
|
-
`- Collected items: ${effective.collectedItemIds.length}`,
|
|
848
|
-
`- Eligible items: ${eligibility.eligibleItemIds.length}`,
|
|
849
|
-
`- Excluded items: ${eligibility.excludedItems.length}`,
|
|
850
|
-
"",
|
|
851
|
-
"## Excluded Items",
|
|
852
|
-
"",
|
|
853
|
-
...(eligibility.excludedItems.length > 0 ? eligibility.excludedItems.map((item) => `- \`${item.itemId}\` (${item.caseId ?? "unknown Case"}): ${item.reasons.join("; ")}`) : ["- None"]),
|
|
854
|
-
"",
|
|
855
|
-
].join("\n"));
|
|
856
|
-
outputs.push(`executionEligibility=${eligibilityReportPath}`, `executionEligibilityFacts=${eligibilityFactsPath}`);
|
|
857
779
|
const readiness = await materializeBackendTestExecutionReadiness({
|
|
858
780
|
runDir: meta.runDir,
|
|
859
781
|
workspaceRoot: input.cwd,
|
|
860
782
|
effective,
|
|
861
783
|
scenarioParamStatus: scenarioFacts.overallStatus,
|
|
862
784
|
scenarioParamRepairAttempt: scenarioFacts.repairAttempt,
|
|
863
|
-
eligibility,
|
|
864
|
-
eligibilityInputHashes,
|
|
865
785
|
});
|
|
866
786
|
return {
|
|
867
787
|
ok: true,
|
|
@@ -872,8 +792,6 @@ async function executeBackendTestPipeline(input, meta) {
|
|
|
872
792
|
collectedItemCount: effective.collectedItemCount,
|
|
873
793
|
fixtureResolutionStatus: effective.fixtureResolutionStatus,
|
|
874
794
|
executionReadinessStatus: readiness.status,
|
|
875
|
-
eligibleItemCount: readiness.eligibleItemIds.length,
|
|
876
|
-
excludedItemCount: readiness.excludedItems.length,
|
|
877
795
|
factsPath: "contracts/backend-test-pytest-collection-effective.json",
|
|
878
796
|
readinessPath: "contracts/backend-test-execution-readiness.json",
|
|
879
797
|
reportPath: artifacts.reportPath,
|
|
@@ -972,8 +890,7 @@ async function executeBackendTestPipeline(input, meta) {
|
|
|
972
890
|
await assertBackendTestExecutionReadinessFresh(input.cwd, executionReadiness);
|
|
973
891
|
const mappedScripts = await collectBackendTestMappedPytestScripts(input.cwd);
|
|
974
892
|
const shellQuote = (value) => `'${value.replaceAll("'", `'"'"'`)}'`;
|
|
975
|
-
const
|
|
976
|
-
const pytestTargets = eligibleItemIds.map(shellQuote).join(" ");
|
|
893
|
+
const pytestTargets = mappedScripts.map(shellQuote).join(" ");
|
|
977
894
|
// Split into short bash -c commands (aligned with markdown-environment).
|
|
978
895
|
// A single ultra-long compound command has been observed on Windows to
|
|
979
896
|
// exit 0xC0000142 (STATUS_DLL_INIT_FAILED) in ~40ms with empty stdout/
|
|
@@ -1050,9 +967,7 @@ async function executeBackendTestPipeline(input, meta) {
|
|
|
1050
967
|
'PYTHON_BIN="$(command -v python || command -v python3 || true)"',
|
|
1051
968
|
'if [ -z "${PYTHON_BIN}" ]; then echo "python/python3 is required for backend-test execution" | tee -a "${DIAG_FILE}" >&2; exit 127; fi',
|
|
1052
969
|
'echo "STEP=pytest-run"',
|
|
1053
|
-
`echo "- pytestCommand: python -m pytest
|
|
1054
|
-
`echo "- eligibleItemCount: ${eligibleItemIds.length}" >> "\${DIAG_FILE}"`,
|
|
1055
|
-
`echo "- excludedItemCount: ${executionReadiness.excludedItems.length}" >> "\${DIAG_FILE}"`,
|
|
970
|
+
`echo "- pytestCommand: python -m pytest ${mappedScripts.join(" ")} -v -p no:cacheprovider --html=reports/backend-test.html --self-contained-html" >> "\${DIAG_FILE}"`,
|
|
1056
971
|
`PYTHONUTF8=1 PYTHONIOENCODING=utf-8 PYTHONDONTWRITEBYTECODE=1 PYTHONUNBUFFERED=1 "\${PYTHON_BIN}" -m pytest ${pytestTargets} -v -p no:cacheprovider --html="\${HARNESS_DAG_RUN_DIR}/reports/backend-test.html" --self-contained-html`,
|
|
1057
972
|
"STATUS=$?",
|
|
1058
973
|
'printf "%s" "${STATUS}" > "${HARNESS_DAG_RUN_DIR}/reports/backend-test-pytest-exit.txt"',
|
|
@@ -1142,9 +1057,7 @@ async function executeBackendTestPipeline(input, meta) {
|
|
|
1142
1057
|
const caseValidationSummary = await readRequiredRunReport(reportsDir, "backend-md-case-validation.md");
|
|
1143
1058
|
const caseCoverageSummary = await readAdvisoryRunReport(reportsDir, "backend-test-case-coverage-analysis.md", "Backend Test Case Coverage Analysis");
|
|
1144
1059
|
const traceabilitySummary = await readRequiredRunReport(reportsDir, "backend-test-traceability.md");
|
|
1145
|
-
const
|
|
1146
|
-
const eligibilitySummary = await readAdvisoryRunReport(reportsDir, "backend-test-execution-eligibility.md", "Backend Test Execution Eligibility");
|
|
1147
|
-
const correspondenceSummary = `${correspondenceSummaryBase.trim()}\n\n${eligibilitySummary.trim()}\n`;
|
|
1060
|
+
const correspondenceSummary = await readAdvisoryRunReport(reportsDir, "backend-test-markdown-pytest-correspondence.md", "Backend Test Markdown → pytest Correspondence");
|
|
1148
1061
|
const htmlContent = renderBackendTestHtml({
|
|
1149
1062
|
title: meta.spec.title,
|
|
1150
1063
|
parsed,
|
|
@@ -4,6 +4,7 @@ import { constants, createReadStream } from "node:fs";
|
|
|
4
4
|
import { access, lstat, readlink, unlink } from "node:fs/promises";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import { pathMatchesPattern } from "../shared/git-progress.js";
|
|
7
|
+
import { ResilientGitCommandError, runResilientGitCommand, } from "../shared/resilient-git.js";
|
|
7
8
|
async function sha256File(filePath) {
|
|
8
9
|
const hash = createHash("sha256");
|
|
9
10
|
for await (const chunk of createReadStream(filePath)) {
|
|
@@ -401,14 +402,31 @@ async function resolveGitExecutableCandidates(platform, env) {
|
|
|
401
402
|
return ["git"];
|
|
402
403
|
}
|
|
403
404
|
export async function readGitStatusPorcelain(cwd, options = {}) {
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
405
|
+
try {
|
|
406
|
+
const result = await runResilientGitCommand({
|
|
407
|
+
cwd,
|
|
408
|
+
args: ["status", "--porcelain=v1", "--untracked-files=all"],
|
|
409
|
+
readOnly: true,
|
|
410
|
+
attempts: options.attempts,
|
|
411
|
+
retryDelayMs: options.retryDelayMs,
|
|
412
|
+
});
|
|
413
|
+
if (result.code === 0)
|
|
414
|
+
return result.stdout;
|
|
415
|
+
throw new Error(result.stderr.trim() || result.stdout.trim() || `exit ${result.code}`);
|
|
416
|
+
}
|
|
417
|
+
catch (error) {
|
|
418
|
+
if (error instanceof ResilientGitCommandError) {
|
|
419
|
+
throw new GitStatusUnavailableError({
|
|
420
|
+
cwd,
|
|
421
|
+
phase: options.phase,
|
|
422
|
+
platform: error.diagnostics.platform,
|
|
423
|
+
executableCandidates: error.diagnostics.executableCandidates,
|
|
424
|
+
attempts: error.diagnostics.attempts,
|
|
425
|
+
requiredWindowsEnvironment: requiredWindowsEnvironment(process.env),
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
throw error;
|
|
429
|
+
}
|
|
412
430
|
}
|
|
413
431
|
export async function readGitStatusPorcelainWithDependencies(cwd, options, dependencies) {
|
|
414
432
|
const candidates = await dependencies.resolveExecutableCandidates();
|