@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.
Files changed (69) hide show
  1. package/CHANGELOG.md +23 -23
  2. package/dist/application/task-lifecycle/advance.js +4 -254
  3. package/dist/application/task-lifecycle/gates.js +0 -50
  4. package/dist/application/task-lifecycle/observe.js +2 -11
  5. package/dist/commands/init-upgrade.js +1 -32
  6. package/dist/commands/init.js +3 -94
  7. package/dist/executors/shell-executor.js +91 -4
  8. package/dist/executors/shell-write-guard.js +8 -26
  9. package/dist/shared/operator/capabilities.js +42 -72
  10. package/dist/task/source-prepare/index.js +0 -2
  11. package/dist/task/source-prepare/parse-intent.js +10 -58
  12. package/dist/task/source-prepare/prepare.js +16 -180
  13. package/dist/task/source-prepare/reference-integrity.js +2 -18
  14. package/dist/worker/console/app-data.js +0 -2
  15. package/dist/worker/console/chat/chat-event-store.js +25 -190
  16. package/dist/worker/console/chat/instruction-skills.js +217 -0
  17. package/dist/worker/console/chat/pi-console-config.js +32 -250
  18. package/dist/worker/console/chat/pi-runtime.js +71 -625
  19. package/dist/worker/console/chat/resource-loader.js +4 -5
  20. package/dist/worker/console/chat/routes.js +146 -324
  21. package/dist/worker/console/chat/runtime-context.js +12 -48
  22. package/dist/worker/console/chat/runtime-selection.js +0 -59
  23. package/dist/worker/console/chat/shortcuts.js +0 -1
  24. package/dist/worker/console/chat/tool-adapter.js +3 -9
  25. package/dist/worker/console/chat/tools.js +1 -5
  26. package/dist/worker/console/operator-actions.js +68 -559
  27. package/dist/worker/console/server.js +15 -8
  28. package/dist/worker/console/static/assets/index-CnUXAqxG.css +1 -0
  29. package/dist/worker/console/static/assets/index-CteJFFL2.js +29 -0
  30. package/dist/worker/console/static/index.html +2 -2
  31. package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +8 -45
  32. package/dist/worker/console/static-src/operator-chat/refs.js +0 -9
  33. package/dist/worker/console/static-src/operator-chat/useChatSessions.js +0 -16
  34. package/dist/worker/console/static-src/operator-chat/useChatStream.js +184 -210
  35. package/dist/worker/console/static-src/operator-chat/useChatThread.js +5 -49
  36. package/dist/worker/console/static-src/operator-chat/useComposer.js +0 -17
  37. package/dist/worker/console/static-src/operator-chat/useRuntimeControls.js +74 -225
  38. package/dist/worker/delivery/final-verification.js +5 -13
  39. package/dist/worker/delivery/package.js +19 -31
  40. package/dist/worker/delivery/verification-bundle.js +4 -6
  41. package/dist/worker/observe/static/operator-chrome.css +2 -5
  42. package/dist/worker/observe/static/operator-chrome.js +1 -6
  43. package/dist/worker/observe/static/styles.css +9 -39
  44. package/dist/workflows/dag/backend-test-case-coverage-analysis.js +462 -33
  45. package/dist/workflows/dag/backend-test-case-manifest.js +4 -0
  46. package/dist/workflows/dag/backend-test-markdown-workflow.js +25 -1
  47. package/dist/workflows/dag/backend-test-module-stem.js +5 -0
  48. package/dist/workflows/dag/backend-test-pytest-collection.js +345 -24
  49. package/dist/workflows/dag/backend-test-scenario-param.js +269 -82
  50. package/dist/workflows/dag/backend-test-writer-completeness.js +47 -16
  51. package/dist/workflows/dag/dynamic-runtime/map.js +24 -8
  52. package/dist/workflows/dag/frontend-worktree-diff.js +27 -12
  53. package/dist/workflows/dag/init-hybrid.js +46 -34
  54. package/dist/workflows/dag/types.js +7 -0
  55. package/dist/workflows/dag/workspace-checkpoint.js +27 -8
  56. package/docs/templates/backend-test-dag.json +32 -29
  57. package/harness.json +1 -1
  58. package/package.json +1 -1
  59. package/skills/loop-agent/references/command-reference.md +1 -3
  60. package/skills/loop-agent/references/source-and-plan-practice.md +0 -13
  61. package/skills/loop-agent/references/task-workflow.md +0 -4
  62. package/dist/shared/resilient-git.js +0 -133
  63. package/dist/task/source-prepare/artifact-meta.js +0 -137
  64. package/dist/task/source-prepare/semantic-intake.js +0 -404
  65. package/dist/worker/console/dag-execution-receipt.js +0 -380
  66. package/dist/worker/console/static/assets/index-BUOLppPr.js +0 -28
  67. package/dist/worker/console/static/assets/index-C1KzazY5.css +0 -1
  68. package/dist/worker/console/static-src/operator-chat/runtime-snapshot-store.js +0 -257
  69. package/dist/worker/console/static-src/operator-chat/useRuntimeSnapshot.js +0 -196
@@ -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 = harnessJsonShapeMatches(JSON.parse(current.toString("utf-8")), JSON.parse(desired.content));
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
- (anchorAccepted ||
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
- // 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") {
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 pytestTargets = mappedScripts.map(shellQuote).join(" ");
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 ${mappedScripts.join(" ")} -v -p no:cacheprovider --html=reports/backend-test.html --self-contained-html" >> "\${DIAG_FILE}"`,
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 correspondenceSummary = await readAdvisoryRunReport(reportsDir, "backend-test-markdown-pytest-correspondence.md", "Backend Test Markdown → pytest Correspondence");
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
- 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
- }
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 <staged-path>",
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: "Consume a single-use execution receipt and execute the reviewed DAG (prefer task advance --approve-gate when gate token present). accepted/queued/running/operationId are NOT completion — supervise via operationGet/status/dagReport/dagDoctor.",
914
+ description: "Execute reviewed DAG after human confirmation (prefer task advance --approve-gate when gate token present).",
945
915
  inputParams: [
946
916
  {
947
- name: "executionId",
917
+ name: "confirmationId",
948
918
  type: "string",
949
919
  required: true,
950
- description: "execution id (from prepareDagExecution) — raw dag path is never accepted from the model",
920
+ description: "confirmation id (from prepareDagConfirmation) — raw dag path is never accepted from the model",
951
921
  },
952
922
  ],
953
- // always: the model can invoke runDag with a server-issued executionId.
954
- // The server re-validates staged bytes/hash + bindings at consume time
955
- // (2026-08-11 autonomous DAG supervision; receipt replaces browser gate).
956
- modelCallable: "always",
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. Server enforces planHash/binding/fingerprint freshness.",
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
- // always: fresh eligible dagRerun plans are model-callable (2026-08-11).
1025
- // The CLI keeps enforcing parent lifecycle, fromNode, planHash,
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. Only when node rerun plan is ineligible or primaryRecovery recommends rerun-task; the server checks read-only run facts before accepting.",
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: "profile",
1277
+ name: "confirmationId",
1312
1278
  type: "string",
1313
- required: false,
1314
- description: "DAG profile (defaults to auto)",
1279
+ required: true,
1280
+ description: "mutation-gate receipt id from prepareMutationGate",
1315
1281
  },
1316
1282
  {
1317
- name: "taskId",
1318
- type: "string",
1319
- required: false,
1320
- description: "original task id (must match the run's task binding; never invent a new task-id)",
1283
+ name: "humanGateToken",
1284
+ type: "object",
1285
+ required: true,
1286
+ description: "server-signed human-gate token from prepareMutationGate",
1321
1287
  },
1322
1288
  ],
1323
- // always: the server gate is the read-only run-facts check (2026-08-11);
1324
- // no browser Human Gate token is required for safe same-task rerun.
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. Only when read-only validation confirms the task exists in Failed state; the server checks this before accepting.",
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
- // always: the server gate is the read-only worker task facts check
1362
- // (2026-08-11); no browser Human Gate token is required.
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: "model-callable",
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: "model-callable",
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: "model-callable",
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: "model-callable",
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";