@tea-agent/loop-agent 0.33.6 → 0.33.7-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +23 -23
- package/dist/application/task-lifecycle/advance.js +4 -254
- package/dist/application/task-lifecycle/gates.js +0 -50
- package/dist/application/task-lifecycle/observe.js +2 -11
- package/dist/commands/init-upgrade.js +1 -32
- package/dist/commands/init.js +3 -94
- package/dist/executors/shell-executor.js +91 -4
- package/dist/executors/shell-write-guard.js +8 -26
- package/dist/shared/operator/capabilities.js +42 -72
- package/dist/task/source-prepare/index.js +0 -2
- package/dist/task/source-prepare/parse-intent.js +10 -58
- package/dist/task/source-prepare/prepare.js +16 -180
- package/dist/task/source-prepare/reference-integrity.js +2 -18
- package/dist/worker/console/app-data.js +0 -2
- package/dist/worker/console/chat/chat-event-store.js +25 -190
- package/dist/worker/console/chat/instruction-skills.js +217 -0
- package/dist/worker/console/chat/pi-console-config.js +32 -250
- package/dist/worker/console/chat/pi-runtime.js +71 -625
- package/dist/worker/console/chat/resource-loader.js +4 -5
- package/dist/worker/console/chat/routes.js +146 -324
- package/dist/worker/console/chat/runtime-context.js +12 -48
- package/dist/worker/console/chat/runtime-selection.js +0 -59
- package/dist/worker/console/chat/shortcuts.js +0 -1
- package/dist/worker/console/chat/tool-adapter.js +3 -9
- package/dist/worker/console/chat/tools.js +1 -5
- package/dist/worker/console/operator-actions.js +68 -559
- package/dist/worker/console/server.js +15 -8
- package/dist/worker/console/static/assets/index-CnUXAqxG.css +1 -0
- package/dist/worker/console/static/assets/index-CteJFFL2.js +29 -0
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +8 -45
- package/dist/worker/console/static-src/operator-chat/refs.js +0 -9
- package/dist/worker/console/static-src/operator-chat/useChatSessions.js +0 -16
- package/dist/worker/console/static-src/operator-chat/useChatStream.js +184 -210
- package/dist/worker/console/static-src/operator-chat/useChatThread.js +5 -49
- package/dist/worker/console/static-src/operator-chat/useComposer.js +0 -17
- package/dist/worker/console/static-src/operator-chat/useRuntimeControls.js +74 -225
- package/dist/worker/delivery/final-verification.js +5 -13
- package/dist/worker/delivery/package.js +19 -31
- package/dist/worker/delivery/verification-bundle.js +4 -6
- package/dist/worker/observe/static/operator-chrome.css +2 -5
- package/dist/worker/observe/static/operator-chrome.js +1 -6
- package/dist/worker/observe/static/styles.css +9 -39
- package/dist/workflows/dag/backend-test-case-coverage-analysis.js +462 -33
- package/dist/workflows/dag/backend-test-case-manifest.js +4 -0
- package/dist/workflows/dag/backend-test-markdown-workflow.js +25 -1
- package/dist/workflows/dag/backend-test-module-stem.js +5 -0
- package/dist/workflows/dag/backend-test-pytest-collection.js +345 -24
- package/dist/workflows/dag/backend-test-scenario-param.js +269 -82
- package/dist/workflows/dag/backend-test-writer-completeness.js +47 -16
- package/dist/workflows/dag/dynamic-runtime/map.js +24 -8
- package/dist/workflows/dag/frontend-worktree-diff.js +27 -12
- package/dist/workflows/dag/init-hybrid.js +46 -34
- package/dist/workflows/dag/types.js +7 -0
- package/dist/workflows/dag/workspace-checkpoint.js +27 -8
- package/docs/templates/backend-test-dag.json +32 -29
- package/harness.json +1 -1
- package/package.json +1 -1
- package/skills/loop-agent/references/command-reference.md +1 -3
- package/skills/loop-agent/references/source-and-plan-practice.md +0 -13
- package/skills/loop-agent/references/task-workflow.md +0 -4
- package/dist/shared/resilient-git.js +0 -133
- package/dist/task/source-prepare/artifact-meta.js +0 -137
- package/dist/task/source-prepare/semantic-intake.js +0 -404
- package/dist/worker/console/dag-execution-receipt.js +0 -380
- package/dist/worker/console/static/assets/index-BUOLppPr.js +0 -28
- package/dist/worker/console/static/assets/index-C1KzazY5.css +0 -1
- package/dist/worker/console/static-src/operator-chat/runtime-snapshot-store.js +0 -257
- package/dist/worker/console/static-src/operator-chat/useRuntimeSnapshot.js +0 -196
package/dist/commands/init.js
CHANGED
|
@@ -1414,70 +1414,6 @@ 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
|
-
}
|
|
1481
1417
|
/**
|
|
1482
1418
|
* Lightweight read-only preflight for the post-upgrade init surface notifier.
|
|
1483
1419
|
*
|
|
@@ -1522,20 +1458,10 @@ async function buildCurrentSurfaceState(input) {
|
|
|
1522
1458
|
entry: manifestEntry,
|
|
1523
1459
|
});
|
|
1524
1460
|
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
|
-
});
|
|
1534
1461
|
const base = {
|
|
1535
1462
|
status: targetStat ? "present" : "missing",
|
|
1536
1463
|
relationship: "missing-from-target",
|
|
1537
1464
|
sourceSha256,
|
|
1538
|
-
sourceAnchorSha256,
|
|
1539
1465
|
sourcePath: desired.sourcePath
|
|
1540
1466
|
? repoRelative(assetRoot, desired.sourcePath)
|
|
1541
1467
|
: undefined,
|
|
@@ -1584,7 +1510,7 @@ async function buildCurrentSurfaceState(input) {
|
|
|
1584
1510
|
if (targetRelativePath === "harness.json" &&
|
|
1585
1511
|
desired.content !== undefined) {
|
|
1586
1512
|
try {
|
|
1587
|
-
semanticallyMatchesGeneratedJson =
|
|
1513
|
+
semanticallyMatchesGeneratedJson = isDeepStrictEqual(JSON.parse(current.toString("utf-8")), JSON.parse(desired.content));
|
|
1588
1514
|
}
|
|
1589
1515
|
catch {
|
|
1590
1516
|
// Invalid JSON remains local-existing-unknown for model merge / doctor.
|
|
@@ -1637,9 +1563,6 @@ async function writeInitSurfaceState(input) {
|
|
|
1637
1563
|
harness.acceptedMerge = {
|
|
1638
1564
|
currentSha256: harness.currentSha256,
|
|
1639
1565
|
desiredSha256: harness.sourceSha256,
|
|
1640
|
-
...(harness.sourceAnchorSha256 !== undefined
|
|
1641
|
-
? { sourceAnchorSha256: harness.sourceAnchorSha256 }
|
|
1642
|
-
: {}),
|
|
1643
1566
|
};
|
|
1644
1567
|
}
|
|
1645
1568
|
}
|
|
@@ -2806,7 +2729,6 @@ function modelMergeTaskFor(pathName, state, allPaths, recordedState) {
|
|
|
2806
2729
|
baseSha256: recordedState?.files[pathName]?.currentSha256,
|
|
2807
2730
|
currentSha256: state.currentSha256,
|
|
2808
2731
|
desiredSha256: state.sourceSha256,
|
|
2809
|
-
sourceAnchorSha256: state.sourceAnchorSha256,
|
|
2810
2732
|
},
|
|
2811
2733
|
};
|
|
2812
2734
|
}
|
|
@@ -2925,16 +2847,8 @@ export async function checkInitUpdate(input) {
|
|
|
2925
2847
|
if (state.relationship === "local-existing-unknown") {
|
|
2926
2848
|
const acceptedMerge = recordedState?.files[pathName]?.acceptedMerge;
|
|
2927
2849
|
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;
|
|
2935
2850
|
if (acceptedMerge.currentSha256 === state.currentSha256 &&
|
|
2936
|
-
|
|
2937
|
-
acceptedMerge.desiredSha256 === state.sourceSha256)) {
|
|
2851
|
+
acceptedMerge.desiredSha256 === state.sourceSha256) {
|
|
2938
2852
|
continue;
|
|
2939
2853
|
}
|
|
2940
2854
|
// A previously accepted semantic merge is user-preserving ownership.
|
|
@@ -2992,12 +2906,7 @@ export async function checkInitUpdate(input) {
|
|
|
2992
2906
|
reason: `replace the legacy default governanceRoot docs with ${governanceRoot}`,
|
|
2993
2907
|
});
|
|
2994
2908
|
}
|
|
2995
|
-
|
|
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") {
|
|
2909
|
+
if (isRecord(harness)) {
|
|
3001
2910
|
const modelMigration = assessHarnessModelMigration(harness);
|
|
3002
2911
|
if (modelMigration.kind === "safe") {
|
|
3003
2912
|
deterministicActions.push({
|
|
@@ -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, materializeBackendTestExecutionReadiness, materializeEffectiveBackendPytestCollection, readBackendPytestCollectionFacts, readBackendTestExecutionReadiness, writeBackendPytestCollectionArtifacts, } from "../workflows/dag/backend-test-pytest-collection.js";
|
|
34
|
+
import { assessBackendPytestCollection, assessMissingBackendPytestScripts, assessPriorityOnlyBackendPytestModules, assertBackendTestExecutionReadinessFresh, buildBackendPytestAssetInventory, buildBackendTestItemEligibility, 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,6 +678,42 @@ 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
|
+
}
|
|
681
717
|
const artifacts = await writeBackendPytestCollectionArtifacts({
|
|
682
718
|
runDir: meta.runDir,
|
|
683
719
|
stem: "initial",
|
|
@@ -776,12 +812,56 @@ async function executeBackendTestPipeline(input, meta) {
|
|
|
776
812
|
facts: effective,
|
|
777
813
|
});
|
|
778
814
|
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}`);
|
|
779
857
|
const readiness = await materializeBackendTestExecutionReadiness({
|
|
780
858
|
runDir: meta.runDir,
|
|
781
859
|
workspaceRoot: input.cwd,
|
|
782
860
|
effective,
|
|
783
861
|
scenarioParamStatus: scenarioFacts.overallStatus,
|
|
784
862
|
scenarioParamRepairAttempt: scenarioFacts.repairAttempt,
|
|
863
|
+
eligibility,
|
|
864
|
+
eligibilityInputHashes,
|
|
785
865
|
});
|
|
786
866
|
return {
|
|
787
867
|
ok: true,
|
|
@@ -792,6 +872,8 @@ async function executeBackendTestPipeline(input, meta) {
|
|
|
792
872
|
collectedItemCount: effective.collectedItemCount,
|
|
793
873
|
fixtureResolutionStatus: effective.fixtureResolutionStatus,
|
|
794
874
|
executionReadinessStatus: readiness.status,
|
|
875
|
+
eligibleItemCount: readiness.eligibleItemIds.length,
|
|
876
|
+
excludedItemCount: readiness.excludedItems.length,
|
|
795
877
|
factsPath: "contracts/backend-test-pytest-collection-effective.json",
|
|
796
878
|
readinessPath: "contracts/backend-test-execution-readiness.json",
|
|
797
879
|
reportPath: artifacts.reportPath,
|
|
@@ -890,7 +972,8 @@ async function executeBackendTestPipeline(input, meta) {
|
|
|
890
972
|
await assertBackendTestExecutionReadinessFresh(input.cwd, executionReadiness);
|
|
891
973
|
const mappedScripts = await collectBackendTestMappedPytestScripts(input.cwd);
|
|
892
974
|
const shellQuote = (value) => `'${value.replaceAll("'", `'"'"'`)}'`;
|
|
893
|
-
const
|
|
975
|
+
const eligibleItemIds = executionReadiness.eligibleItemIds;
|
|
976
|
+
const pytestTargets = eligibleItemIds.map(shellQuote).join(" ");
|
|
894
977
|
// Split into short bash -c commands (aligned with markdown-environment).
|
|
895
978
|
// A single ultra-long compound command has been observed on Windows to
|
|
896
979
|
// exit 0xC0000142 (STATUS_DLL_INIT_FAILED) in ~40ms with empty stdout/
|
|
@@ -967,7 +1050,9 @@ async function executeBackendTestPipeline(input, meta) {
|
|
|
967
1050
|
'PYTHON_BIN="$(command -v python || command -v python3 || true)"',
|
|
968
1051
|
'if [ -z "${PYTHON_BIN}" ]; then echo "python/python3 is required for backend-test execution" | tee -a "${DIAG_FILE}" >&2; exit 127; fi',
|
|
969
1052
|
'echo "STEP=pytest-run"',
|
|
970
|
-
`echo "- pytestCommand: python -m pytest
|
|
1053
|
+
`echo "- pytestCommand: python -m pytest <${eligibleItemIds.length} eligibility-authorized-items> -v -p no:cacheprovider --html=reports/backend-test.html --self-contained-html" >> "\${DIAG_FILE}"`,
|
|
1054
|
+
`echo "- eligibleItemCount: ${eligibleItemIds.length}" >> "\${DIAG_FILE}"`,
|
|
1055
|
+
`echo "- excludedItemCount: ${executionReadiness.excludedItems.length}" >> "\${DIAG_FILE}"`,
|
|
971
1056
|
`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`,
|
|
972
1057
|
"STATUS=$?",
|
|
973
1058
|
'printf "%s" "${STATUS}" > "${HARNESS_DAG_RUN_DIR}/reports/backend-test-pytest-exit.txt"',
|
|
@@ -1057,7 +1142,9 @@ async function executeBackendTestPipeline(input, meta) {
|
|
|
1057
1142
|
const caseValidationSummary = await readRequiredRunReport(reportsDir, "backend-md-case-validation.md");
|
|
1058
1143
|
const caseCoverageSummary = await readAdvisoryRunReport(reportsDir, "backend-test-case-coverage-analysis.md", "Backend Test Case Coverage Analysis");
|
|
1059
1144
|
const traceabilitySummary = await readRequiredRunReport(reportsDir, "backend-test-traceability.md");
|
|
1060
|
-
const
|
|
1145
|
+
const correspondenceSummaryBase = await readAdvisoryRunReport(reportsDir, "backend-test-markdown-pytest-correspondence.md", "Backend Test Markdown → pytest Correspondence");
|
|
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`;
|
|
1061
1148
|
const htmlContent = renderBackendTestHtml({
|
|
1062
1149
|
title: meta.spec.title,
|
|
1063
1150
|
parsed,
|
|
@@ -4,7 +4,6 @@ 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";
|
|
8
7
|
async function sha256File(filePath) {
|
|
9
8
|
const hash = createHash("sha256");
|
|
10
9
|
for await (const chunk of createReadStream(filePath)) {
|
|
@@ -402,31 +401,14 @@ async function resolveGitExecutableCandidates(platform, env) {
|
|
|
402
401
|
return ["git"];
|
|
403
402
|
}
|
|
404
403
|
export async function readGitStatusPorcelain(cwd, options = {}) {
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
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
|
-
}
|
|
404
|
+
return readGitStatusPorcelainWithDependencies(cwd, options, {
|
|
405
|
+
platform: process.platform,
|
|
406
|
+
resolveExecutableCandidates: () => resolveGitExecutableCandidates(process.platform, process.env),
|
|
407
|
+
runAttempt: readGitStatusPorcelainOnce,
|
|
408
|
+
sleep: async (delayMs) => new Promise((resolve) => setTimeout(resolve, delayMs)),
|
|
409
|
+
now: Date.now,
|
|
410
|
+
env: process.env,
|
|
411
|
+
});
|
|
430
412
|
}
|
|
431
413
|
export async function readGitStatusPorcelainWithDependencies(cwd, options, dependencies) {
|
|
432
414
|
const candidates = await dependencies.resolveExecutableCandidates();
|
|
@@ -699,36 +699,6 @@ export function buildOperatorCapabilitiesDocument() {
|
|
|
699
699
|
modelCallable: "always",
|
|
700
700
|
humanConfirmation: "none",
|
|
701
701
|
},
|
|
702
|
-
{
|
|
703
|
-
action: "prepareDagExecution",
|
|
704
|
-
cli: "console aggregate execution receipt (server-side G2 assessment)",
|
|
705
|
-
kind: "read",
|
|
706
|
-
inputSchemaVersion: 1,
|
|
707
|
-
resultSchemaVersion: 1,
|
|
708
|
-
envelopeSchemaVersion: 1,
|
|
709
|
-
requiredErrorCodes: [
|
|
710
|
-
"INVALID_INPUT",
|
|
711
|
-
"BINDING_DRIFT",
|
|
712
|
-
"HUMAN_CONFIRMATION_REQUIRED",
|
|
713
|
-
],
|
|
714
|
-
description: "Issue a session-bound single-use execution receipt after server-side G2 assessment (bounded writeSet ⊆ allowedPaths, no forbidden overlap, no broad/destructive risk, structured verification). The model never passes a raw DAG path.",
|
|
715
|
-
inputParams: [
|
|
716
|
-
{
|
|
717
|
-
name: "taskId",
|
|
718
|
-
type: "string",
|
|
719
|
-
required: true,
|
|
720
|
-
description: "task id",
|
|
721
|
-
},
|
|
722
|
-
{
|
|
723
|
-
name: "profile",
|
|
724
|
-
type: "string",
|
|
725
|
-
required: false,
|
|
726
|
-
description: "DAG profile (defaults to auto)",
|
|
727
|
-
},
|
|
728
|
-
],
|
|
729
|
-
modelCallable: "always",
|
|
730
|
-
humanConfirmation: "none",
|
|
731
|
-
},
|
|
732
702
|
{
|
|
733
703
|
action: "confirmDagConfirmation",
|
|
734
704
|
cli: "console aggregate confirmation human challenge (server-side)",
|
|
@@ -931,7 +901,7 @@ export function buildOperatorCapabilitiesDocument() {
|
|
|
931
901
|
},
|
|
932
902
|
{
|
|
933
903
|
action: "runDag",
|
|
934
|
-
cli: "loop-agent task advance <taskId> --approve-gate <id:digest> --json | dag execute --dag <
|
|
904
|
+
cli: "loop-agent task advance <taskId> --approve-gate <id:digest> --json | dag execute --dag <path>",
|
|
935
905
|
kind: "long-running",
|
|
936
906
|
inputSchemaVersion: 1,
|
|
937
907
|
resultSchemaVersion: 1,
|
|
@@ -941,20 +911,19 @@ export function buildOperatorCapabilitiesDocument() {
|
|
|
941
911
|
"CONTROLLER_MISMATCH",
|
|
942
912
|
"INVALID_INPUT",
|
|
943
913
|
],
|
|
944
|
-
description: "
|
|
914
|
+
description: "Execute reviewed DAG after human confirmation (prefer task advance --approve-gate when gate token present).",
|
|
945
915
|
inputParams: [
|
|
946
916
|
{
|
|
947
|
-
name: "
|
|
917
|
+
name: "confirmationId",
|
|
948
918
|
type: "string",
|
|
949
919
|
required: true,
|
|
950
|
-
description: "
|
|
920
|
+
description: "confirmation id (from prepareDagConfirmation) — raw dag path is never accepted from the model",
|
|
951
921
|
},
|
|
952
922
|
],
|
|
953
|
-
//
|
|
954
|
-
//
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
humanConfirmation: "none",
|
|
923
|
+
// prepare-only: the model can invoke runDag but it MUST carry a
|
|
924
|
+
// confirmationId that was consumed by a human-origin confirm (M0-B).
|
|
925
|
+
modelCallable: "prepare-only",
|
|
926
|
+
humanConfirmation: "required",
|
|
958
927
|
},
|
|
959
928
|
{
|
|
960
929
|
action: "dagRerunPlan",
|
|
@@ -994,7 +963,7 @@ export function buildOperatorCapabilitiesDocument() {
|
|
|
994
963
|
"BINDING_DRIFT",
|
|
995
964
|
"INVALID_INPUT",
|
|
996
965
|
],
|
|
997
|
-
description: "Execute R1 continuation from effective node. Requires planHash from a fresh dagRerunPlan (eligible=true). Prefer this over new task / standaloneTaskRerun for provider flake and safe downstream failures.
|
|
966
|
+
description: "Execute R1 continuation from effective node. Requires planHash from a fresh dagRerunPlan (eligible=true). Prefer this over new task / standaloneTaskRerun for provider flake and safe downstream failures. Human Gate required.",
|
|
998
967
|
inputParams: [
|
|
999
968
|
{
|
|
1000
969
|
name: "runId",
|
|
@@ -1021,11 +990,8 @@ export function buildOperatorCapabilitiesDocument() {
|
|
|
1021
990
|
description: "why continue from this node (e.g. provider flake; resume from review-pi)",
|
|
1022
991
|
},
|
|
1023
992
|
],
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
// binding/fingerprint and request idempotency.
|
|
1027
|
-
modelCallable: "always",
|
|
1028
|
-
humanConfirmation: "none",
|
|
993
|
+
modelCallable: "prepare-only",
|
|
994
|
+
humanConfirmation: "required",
|
|
1029
995
|
},
|
|
1030
996
|
{
|
|
1031
997
|
action: "prepareMutationGate",
|
|
@@ -1035,13 +1001,13 @@ export function buildOperatorCapabilitiesDocument() {
|
|
|
1035
1001
|
resultSchemaVersion: 1,
|
|
1036
1002
|
envelopeSchemaVersion: 1,
|
|
1037
1003
|
requiredErrorCodes: ["INVALID_INPUT", "NOT_FOUND"],
|
|
1038
|
-
description: "Prepare a one-shot Human Gate receipt for Night Scheduler mutations. Model may prepare; only the browser mutation gate may consume.",
|
|
1004
|
+
description: "Prepare a one-shot Human Gate receipt for standaloneTaskRerun / workerTaskRetry / Night Scheduler mutations. Model may prepare; only the browser mutation gate may consume.",
|
|
1039
1005
|
inputParams: [
|
|
1040
1006
|
{
|
|
1041
1007
|
name: "action",
|
|
1042
1008
|
type: "string",
|
|
1043
1009
|
required: true,
|
|
1044
|
-
description: "target action: workerAdmissionPrepare | workerSchedulerAdd | workerSchedulerCancel | workerSchedulerHarvest | workerSchedulerDiscard",
|
|
1010
|
+
description: "target action: standaloneTaskRerun | workerTaskRetry | workerAdmissionPrepare | workerSchedulerAdd | workerSchedulerCancel | workerSchedulerHarvest | workerSchedulerDiscard",
|
|
1045
1011
|
},
|
|
1046
1012
|
{
|
|
1047
1013
|
name: "actionParams",
|
|
@@ -1293,7 +1259,7 @@ export function buildOperatorCapabilitiesDocument() {
|
|
|
1293
1259
|
"INVALID_INPUT",
|
|
1294
1260
|
"HUMAN_CONFIRMATION_REQUIRED",
|
|
1295
1261
|
],
|
|
1296
|
-
description: "Full standalone task regenerate → validate → execute with parent lineage.
|
|
1262
|
+
description: "Full standalone task regenerate → validate → execute with parent lineage.",
|
|
1297
1263
|
inputParams: [
|
|
1298
1264
|
{
|
|
1299
1265
|
name: "runId",
|
|
@@ -1308,22 +1274,20 @@ export function buildOperatorCapabilitiesDocument() {
|
|
|
1308
1274
|
description: "rerun reason",
|
|
1309
1275
|
},
|
|
1310
1276
|
{
|
|
1311
|
-
name: "
|
|
1277
|
+
name: "confirmationId",
|
|
1312
1278
|
type: "string",
|
|
1313
|
-
required:
|
|
1314
|
-
description: "
|
|
1279
|
+
required: true,
|
|
1280
|
+
description: "mutation-gate receipt id from prepareMutationGate",
|
|
1315
1281
|
},
|
|
1316
1282
|
{
|
|
1317
|
-
name: "
|
|
1318
|
-
type: "
|
|
1319
|
-
required:
|
|
1320
|
-
description: "
|
|
1283
|
+
name: "humanGateToken",
|
|
1284
|
+
type: "object",
|
|
1285
|
+
required: true,
|
|
1286
|
+
description: "server-signed human-gate token from prepareMutationGate",
|
|
1321
1287
|
},
|
|
1322
1288
|
],
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
modelCallable: "always",
|
|
1326
|
-
humanConfirmation: "none",
|
|
1289
|
+
modelCallable: "prepare-only",
|
|
1290
|
+
humanConfirmation: "required",
|
|
1327
1291
|
},
|
|
1328
1292
|
{
|
|
1329
1293
|
action: "workerTaskRetry",
|
|
@@ -1337,7 +1301,7 @@ export function buildOperatorCapabilitiesDocument() {
|
|
|
1337
1301
|
"INVALID_INPUT",
|
|
1338
1302
|
"HUMAN_CONFIRMATION_REQUIRED",
|
|
1339
1303
|
],
|
|
1340
|
-
description: "Requeue failed Task Pool task (Failed → Ready) via in-package pool store.
|
|
1304
|
+
description: "Requeue failed Task Pool task (Failed → Ready) via in-package pool store.",
|
|
1341
1305
|
inputParams: [
|
|
1342
1306
|
{
|
|
1343
1307
|
name: "taskId",
|
|
@@ -1357,11 +1321,21 @@ export function buildOperatorCapabilitiesDocument() {
|
|
|
1357
1321
|
required: true,
|
|
1358
1322
|
description: "retry reason",
|
|
1359
1323
|
},
|
|
1324
|
+
{
|
|
1325
|
+
name: "confirmationId",
|
|
1326
|
+
type: "string",
|
|
1327
|
+
required: true,
|
|
1328
|
+
description: "mutation-gate receipt id from prepareMutationGate",
|
|
1329
|
+
},
|
|
1330
|
+
{
|
|
1331
|
+
name: "humanGateToken",
|
|
1332
|
+
type: "object",
|
|
1333
|
+
required: true,
|
|
1334
|
+
description: "server-signed human-gate token from prepareMutationGate",
|
|
1335
|
+
},
|
|
1360
1336
|
],
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
modelCallable: "always",
|
|
1364
|
-
humanConfirmation: "none",
|
|
1337
|
+
modelCallable: "prepare-only",
|
|
1338
|
+
humanConfirmation: "required",
|
|
1365
1339
|
},
|
|
1366
1340
|
...OFFICIAL_ACTIONS,
|
|
1367
1341
|
];
|
|
@@ -1437,10 +1411,9 @@ export const OPERATOR_COMMAND_COVERAGE = Object.freeze([
|
|
|
1437
1411
|
},
|
|
1438
1412
|
{
|
|
1439
1413
|
command: "loop-agent dag execute",
|
|
1440
|
-
coverage: "
|
|
1414
|
+
coverage: "human-gated-required",
|
|
1441
1415
|
action: "runDag",
|
|
1442
1416
|
source: "loop-agent",
|
|
1443
|
-
note: "runDag consumes a server-issued single-use execution receipt (prepareDagExecution); browser Human Gate no longer required for bounded DAGs (2026-08-11).",
|
|
1444
1417
|
},
|
|
1445
1418
|
{
|
|
1446
1419
|
command: "loop-agent cursor-prompt",
|
|
@@ -1856,17 +1829,15 @@ export const OPERATOR_COMMAND_COVERAGE = Object.freeze([
|
|
|
1856
1829
|
},
|
|
1857
1830
|
{
|
|
1858
1831
|
command: "loop-agent dag rerun",
|
|
1859
|
-
coverage: "
|
|
1832
|
+
coverage: "human-gated-required",
|
|
1860
1833
|
action: "dagRerun",
|
|
1861
1834
|
source: "loop-agent",
|
|
1862
|
-
note: "fresh eligible dagRerunPlan (planHash) is model-callable; the CLI still enforces plan/binding/fingerprint freshness (2026-08-11).",
|
|
1863
1835
|
},
|
|
1864
1836
|
{
|
|
1865
1837
|
command: "loop-agent dag rerun-task",
|
|
1866
|
-
coverage: "
|
|
1838
|
+
coverage: "human-gated-required",
|
|
1867
1839
|
action: "standaloneTaskRerun",
|
|
1868
1840
|
source: "loop-agent",
|
|
1869
|
-
note: "server-side read-only run-facts check (ineligible plan / explicit rerun-task recommendation) replaces the browser Human Gate (2026-08-11).",
|
|
1870
1841
|
},
|
|
1871
1842
|
{
|
|
1872
1843
|
command: "loop-agent dag reconcile-tasks",
|
|
@@ -2098,10 +2069,9 @@ export const OPERATOR_COMMAND_COVERAGE = Object.freeze([
|
|
|
2098
2069
|
},
|
|
2099
2070
|
{
|
|
2100
2071
|
command: "agent-worker task retry",
|
|
2101
|
-
coverage: "
|
|
2072
|
+
coverage: "human-gated-required",
|
|
2102
2073
|
action: "workerTaskRetry",
|
|
2103
2074
|
source: "agent-worker",
|
|
2104
|
-
note: "server-side read-only pool doctor facts check (Failed state) replaces the browser Human Gate (2026-08-11).",
|
|
2105
2075
|
},
|
|
2106
2076
|
{
|
|
2107
2077
|
command: "agent-worker task draft-followup",
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
export * from "./types.js";
|
|
2
2
|
export * from "./path-policy.js";
|
|
3
3
|
export * from "./parse-intent.js";
|
|
4
|
-
export * from "./artifact-meta.js";
|
|
5
|
-
export * from "./semantic-intake.js";
|
|
6
4
|
export * from "./reference-integrity.js";
|
|
7
5
|
export * from "./completeness.js";
|
|
8
6
|
export * from "./build-draft.js";
|