@lazyingart/agintiflow 0.20.280 → 0.20.282

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lazyingart/agintiflow",
3
- "version": "0.20.280",
3
+ "version": "0.20.282",
4
4
  "type": "module",
5
5
  "description": "AgInTiFlow is a project-aware agent workspace for hybrid wet-dry R&D, hardware-aware intelligence, software automation, and industrial workflows.",
6
6
  "license": "Apache-2.0",
@@ -53,6 +53,7 @@ import {
53
53
  isSubstantiveTestCommand,
54
54
  mergeDurableGitEvidence,
55
55
  nextStepRuntimeConfig,
56
+ patchContextRefreshDecision,
56
57
  patchContextScopeMismatchAttemptCount,
57
58
  parseGitPorcelainStatus,
58
59
  pythonMainGuardOrderDefects,
@@ -1216,6 +1217,100 @@ try {
1216
1217
  Boolean(unrelatedInsertionBinding?.scopeIssue),
1217
1218
  "an unrelated missing declaration escaped the active failed-test symbol contract"
1218
1219
  );
1220
+ const declarationBoundarySource = [
1221
+ "def build_preview():",
1222
+ " path = 'preview.png'",
1223
+ " return path",
1224
+ "",
1225
+ "def main():",
1226
+ " return build_preview()",
1227
+ "",
1228
+ ].join("\n");
1229
+ const declarationBoundaryOffset = declarationBoundarySource.indexOf("def main():");
1230
+ const declarationBoundaryAnchor = declarationBoundarySource.slice(
1231
+ 0,
1232
+ declarationBoundaryOffset
1233
+ );
1234
+ const declarationBoundaryState = {
1235
+ meta: {
1236
+ goalContract: { revision: 1 },
1237
+ projectVerification: { mutationRevision: 1, privateMutationRevision: 0 },
1238
+ toolLoop: {
1239
+ patchContextRepair: {
1240
+ version: 1,
1241
+ path: "preview_builder.py",
1242
+ goalRevision: 1,
1243
+ mutationRevision: 1,
1244
+ privateMutationRevision: 0,
1245
+ search: declarationBoundaryAnchor,
1246
+ searchHash: crypto
1247
+ .createHash("sha256")
1248
+ .update(declarationBoundaryAnchor)
1249
+ .digest("hex"),
1250
+ sourceHash: crypto
1251
+ .createHash("sha256")
1252
+ .update(declarationBoundarySource)
1253
+ .digest("hex"),
1254
+ anchorKind: "declaration-identity",
1255
+ anchorIdentity: "build_preview",
1256
+ },
1257
+ },
1258
+ },
1259
+ };
1260
+ const declarationBoundaryBinding = bindPatchContextRepairArguments(
1261
+ declarationBoundaryState,
1262
+ {
1263
+ replace: [
1264
+ "def build_preview():",
1265
+ " path = 'preview-fixed.png'",
1266
+ " return path",
1267
+ ].join("\n"),
1268
+ }
1269
+ );
1270
+ assert(
1271
+ declarationBoundaryBinding?.boundaryWhitespacePreserved === true &&
1272
+ declarationBoundaryBinding.args.replace.endsWith("\n") &&
1273
+ /return path\n+def main\(\):/.test(
1274
+ `${declarationBoundaryBinding.args.replace}${declarationBoundarySource.slice(declarationBoundaryOffset)}`
1275
+ ),
1276
+ "revision-bound declaration replacement did not preserve its Python declaration boundary"
1277
+ );
1278
+ await fs.writeFile(
1279
+ path.join(workspace, "preview_builder.py"),
1280
+ declarationBoundarySource,
1281
+ "utf8"
1282
+ );
1283
+ assert(
1284
+ (await prospectivePythonExactPatchSyntaxBlock(
1285
+ "apply_patch",
1286
+ declarationBoundaryBinding.args,
1287
+ { commandCwd: workspace }
1288
+ )) === null,
1289
+ "the boundary-preserved declaration replacement still produced invalid Python"
1290
+ );
1291
+ const syntaxRefreshDecision = patchContextRefreshDecision(
1292
+ {
1293
+ meta: {
1294
+ goalContract: { revision: 1 },
1295
+ projectVerification: { mutationRevision: 1, privateMutationRevision: 0 },
1296
+ toolLoop: { stagnationEpoch: 0, recent: [] },
1297
+ },
1298
+ },
1299
+ {
1300
+ toolName: "apply_patch",
1301
+ ok: false,
1302
+ category: "python-syntax-regression",
1303
+ args: {
1304
+ path: "preview_builder.py",
1305
+ search: declarationBoundaryAnchor,
1306
+ },
1307
+ }
1308
+ );
1309
+ assert(
1310
+ syntaxRefreshDecision?.triggerCategory === "python-syntax-regression" &&
1311
+ syntaxRefreshDecision.path === "preview_builder.py",
1312
+ "a prospective Python syntax regression did not force a fresh exact-source read"
1313
+ );
1219
1314
  const semanticScopeMismatchState = {
1220
1315
  meta: {
1221
1316
  goalContract: { revision: 4 },
@@ -2782,6 +2877,16 @@ try {
2782
2877
  acceptanceClassification.substantiveTest === true,
2783
2878
  "a structurally named Python acceptance validator was treated as an arbitrary mutating script"
2784
2879
  );
2880
+ const externalAcceptanceValidator =
2881
+ "/opt/conda/bin/python /tmp/supervision/acceptance/webapp_booking_contract.py";
2882
+ const externalAcceptanceClassification = classifyCommand(externalAcceptanceValidator);
2883
+ assert(
2884
+ externalAcceptanceClassification.category === "general-shell" &&
2885
+ externalAcceptanceClassification.writesWorkspace === true &&
2886
+ externalAcceptanceClassification.mayMutateProject === true &&
2887
+ externalAcceptanceClassification.semanticMayMutateProject === false,
2888
+ "trusted-shell authorization for an external Python validator was conflated with project mutation"
2889
+ );
2785
2890
  assert(
2786
2891
  classifyCommand(`${acceptanceValidator} --output report.json`).writesWorkspace === true,
2787
2892
  "a Python acceptance validator with an explicit output destination was treated as read-only"
@@ -3679,6 +3784,32 @@ try {
3679
3784
  readOnlyValidatorResult.projectMutationRevision === 0,
3680
3785
  "a semantically read-only validator fabricated project mutation progress"
3681
3786
  );
3787
+ const externalReadOnlyValidatorState = { meta: { goalContract: { revision: 1 } } };
3788
+ const externalReadOnlyValidatorCommand =
3789
+ "cd . && /opt/conda/bin/python /tmp/supervision/acceptance/webapp_booking_contract.py && git status --short";
3790
+ const externalReadOnlyValidatorResult = {
3791
+ toolName: "run_command",
3792
+ ok: true,
3793
+ exitCode: 0,
3794
+ args: { command: externalReadOnlyValidatorCommand },
3795
+ stdout: "webapp booking contract: PASS\n",
3796
+ stderr: "",
3797
+ commandPolicy: classifyCommand(externalReadOnlyValidatorCommand),
3798
+ };
3799
+ recordProjectVerificationOutcome(
3800
+ externalReadOnlyValidatorState,
3801
+ externalReadOnlyValidatorResult,
3802
+ {
3803
+ commandCwd: workspace,
3804
+ taskProfile: "app",
3805
+ sandboxMode: "host",
3806
+ }
3807
+ );
3808
+ assert(
3809
+ externalReadOnlyValidatorState.meta.projectVerification?.mutationRevision === 0 &&
3810
+ externalReadOnlyValidatorResult.projectMutationRevision === 0,
3811
+ "an absolute-interpreter acceptance validator forced a post-validation commit"
3812
+ );
3682
3813
  const shellMutationState = { meta: { goalContract: { revision: 1 } } };
3683
3814
  const shellMutationResult = {
3684
3815
  toolName: "run_command",
@@ -10328,6 +10459,60 @@ try {
10328
10459
  assert(resumedBudget?.extensionsUsed === 0, "runAgent retained stale extension usage after an explicit resumed max-steps patch");
10329
10460
  assert(resumedBudget?.resetFromExplicitOverride === true, "runAgent did not record the explicit budget reset boundary");
10330
10461
 
10462
+ const educationWorkspace = path.join(tempRoot, "education-artifact-contract");
10463
+ await fs.mkdir(path.join(educationWorkspace, "workshop"), { recursive: true });
10464
+ await fs.writeFile(path.join(educationWorkspace, "workshop", "lesson-deck.md"), "# Lesson\n");
10465
+ const educationGoal =
10466
+ "Create an editable lesson deck, a separate practice sheet and answer key, printable materials, a helpful preview, and a reproducible build entrypoint. Verify and commit the work.";
10467
+ const educationState = {
10468
+ goal: educationGoal,
10469
+ commandCwd: educationWorkspace,
10470
+ plan: "Create every requested artifact, verify them, and commit the complete set.",
10471
+ messages: [
10472
+ {
10473
+ role: "tool",
10474
+ content: JSON.stringify({
10475
+ ok: true,
10476
+ toolName: "write_file",
10477
+ path: "workshop/lesson-deck.md",
10478
+ goalRevision: 1,
10479
+ projectMutationRevision: 1,
10480
+ }),
10481
+ },
10482
+ ],
10483
+ meta: {
10484
+ taskProfile: "education",
10485
+ goalContract: {
10486
+ revision: 1,
10487
+ currentRequest: educationGoal,
10488
+ taskGoal: educationGoal,
10489
+ activeGoalRevision: 1,
10490
+ lifecycle: [{ at: new Date(Date.now() - 2000).toISOString() }],
10491
+ },
10492
+ projectVerification: {
10493
+ mutationRevision: 1,
10494
+ mutationHistory: [
10495
+ {
10496
+ revision: 1,
10497
+ at: new Date().toISOString(),
10498
+ toolName: "write_file",
10499
+ paths: ["workshop/lesson-deck.md"],
10500
+ goalRevision: 1,
10501
+ },
10502
+ ],
10503
+ },
10504
+ },
10505
+ };
10506
+ const incompleteEducationRuntime = nextStepRuntimeConfig(
10507
+ { goal: educationGoal, taskProfile: "education", commandCwd: educationWorkspace },
10508
+ educationState
10509
+ );
10510
+ assert(
10511
+ incompleteEducationRuntime.requestedArtifactRequirementsPending === true &&
10512
+ incompleteEducationRuntime.taskOwnedCommitPending !== true,
10513
+ "a partial multi-artifact task entered task-owned Git completion before its deliverables existed"
10514
+ );
10515
+
10331
10516
  await fs.rm(tempRoot, { recursive: true, force: true });
10332
10517
  console.log("smoke-dynamic-step-budget ok");
10333
10518
  } catch (error) {
@@ -12,6 +12,7 @@ import {
12
12
  import {
13
13
  buildScsEvidenceLedger,
14
14
  deriveScsTaskContract,
15
+ evaluateRequestedArtifactRequirements,
15
16
  evaluateScsSemanticContract,
16
17
  extractMarkdownCommandEvidence,
17
18
  extractMarkdownPathEvidence,
@@ -19,6 +20,73 @@ import {
19
20
  finishResultClaimsIncompleteWork,
20
21
  } from "../src/scs-evidence.js";
21
22
 
23
+ const educationArtifactGoal =
24
+ "Create an editable lesson deck, a separate practice sheet and answer key, printable materials, a helpful preview, and a reproducible build entrypoint.";
25
+ const educationArtifactContract = deriveScsTaskContract({
26
+ goal: educationArtifactGoal,
27
+ taskProfile: "education",
28
+ });
29
+ assert.deepEqual(
30
+ educationArtifactContract.requiredArtifactKinds.map((item) => item.id).sort(),
31
+ [
32
+ "answer-material",
33
+ "editable-presentation",
34
+ "practice-material",
35
+ "printable-document",
36
+ "reproducible-build-entrypoint",
37
+ "visual-preview",
38
+ ],
39
+ "multi-artifact education request did not retain each semantic deliverable"
40
+ );
41
+ assert.deepEqual(
42
+ deriveScsTaskContract({
43
+ goal: "Create a canvas artifact preview for this smoke test.",
44
+ taskProfile: "auto",
45
+ }).requiredArtifactKinds,
46
+ [],
47
+ "a generic frontend canvas preview was incorrectly converted into a required image-file artifact"
48
+ );
49
+ const educationWorkspace = fs.mkdtempSync(path.join(os.tmpdir(), "aginti-education-artifacts-"));
50
+ const educationState = {
51
+ meta: {
52
+ goalContract: {
53
+ lifecycle: [{ at: new Date(Date.now() - 2000).toISOString() }],
54
+ },
55
+ projectVerification: { mutationHistory: [] },
56
+ },
57
+ };
58
+ fs.mkdirSync(path.join(educationWorkspace, "workshop"), { recursive: true });
59
+ fs.writeFileSync(path.join(educationWorkspace, "workshop", "lesson-deck.md"), "# Lesson deck\n");
60
+ const incompleteEducationArtifacts = evaluateRequestedArtifactRequirements(
61
+ educationArtifactContract,
62
+ { commandCwd: educationWorkspace, state: educationState }
63
+ );
64
+ assert.equal(incompleteEducationArtifacts.ok, false);
65
+ assert.ok(
66
+ incompleteEducationArtifacts.missing.some((item) => item.id === "editable-presentation"),
67
+ "Markdown-only deck incorrectly satisfied the editable presentation contract"
68
+ );
69
+ for (const [name, content] of [
70
+ ["lesson-deck.pptx", "pptx"],
71
+ ["practice-sheet.md", "practice"],
72
+ ["answer-key.md", "answers"],
73
+ ["practice-sheet.pdf", "pdf"],
74
+ ["lesson-preview.png", "png"],
75
+ ["build_materials.py", "print('build')\n"],
76
+ ]) {
77
+ fs.writeFileSync(path.join(educationWorkspace, "workshop", name), content);
78
+ }
79
+ const completeEducationArtifacts = evaluateRequestedArtifactRequirements(
80
+ educationArtifactContract,
81
+ { commandCwd: educationWorkspace, state: educationState }
82
+ );
83
+ assert.equal(
84
+ completeEducationArtifacts.ok,
85
+ true,
86
+ `complete semantic artifact set was rejected: ${completeEducationArtifacts.reason}`
87
+ );
88
+ fs.rmSync(educationWorkspace, { recursive: true, force: true });
89
+
22
90
  function fakeStudentClient(json) {
23
91
  return {
24
92
  chat: {
@@ -68,12 +68,23 @@ async function waitForHealth() {
68
68
  async function waitForRun(sessionId, terminalStatuses = ["finished", "failed"]) {
69
69
  const acceptedStatuses = new Set(terminalStatuses);
70
70
  const deadline = Date.now() + 20000;
71
+ let lastRun = null;
71
72
  while (Date.now() < deadline) {
72
73
  const run = await fetchJson(`/api/runs/${encodeURIComponent(sessionId)}`);
74
+ lastRun = run;
73
75
  if (acceptedStatuses.has(run.status)) return run;
74
76
  await delay(400);
75
77
  }
76
- throw new Error(`run ${sessionId} did not finish in time`);
78
+ throw new Error(
79
+ `run ${sessionId} did not finish in time; ` +
80
+ `lastRun=${JSON.stringify(lastRun ? {
81
+ status: lastRun.status,
82
+ error: lastRun.error,
83
+ endedAt: lastRun.endedAt,
84
+ logs: Array.isArray(lastRun.logs) ? lastRun.logs.slice(-12) : [],
85
+ } : null)} ` +
86
+ `stdout=${stdout.slice(-1000)} stderr=${stderr.slice(-1000)}`
87
+ );
77
88
  }
78
89
 
79
90
  try {
@@ -128,6 +128,7 @@ import {
128
128
  deterministicFinishBlocker,
129
129
  evaluateScsEvidence,
130
130
  evaluateScsSemanticContract,
131
+ evaluateRequestedArtifactRequirements,
131
132
  augmentScsTaskContractWithProjectVerification,
132
133
  agintiEvidenceScopeLine,
133
134
  filterExplicitlyExcludedOutputPaths,
@@ -1754,6 +1755,11 @@ function buildCompactedRuntimeMessages(state, config, snapshot, step, options =
1754
1755
  title: snapshot?.title || "",
1755
1756
  url: snapshot?.url || "",
1756
1757
  plan: effectivePlan,
1758
+ missingRequestedArtifactRequirements: Array.isArray(
1759
+ config.missingRequestedArtifactRequirements
1760
+ )
1761
+ ? config.missingRequestedArtifactRequirements
1762
+ : [],
1757
1763
  };
1758
1764
  const compactedContent = [
1759
1765
  options.heading || "Continue from this compacted, valid transcript.",
@@ -10110,6 +10116,12 @@ function commandCanMutateProjectContent(command = "", commandPolicy = {}) {
10110
10116
  const category = String(commandPolicy.category || "");
10111
10117
  const requiresGitMutationInspection =
10112
10118
  ["git-workflow", "git-remote"].includes(category);
10119
+ if (
10120
+ commandPolicy.semanticMayMutateProject === false &&
10121
+ !requiresGitMutationInspection
10122
+ ) {
10123
+ return false;
10124
+ }
10113
10125
  if (requiresGitMutationInspection && commandPolicy.gitOnly === true) {
10114
10126
  return inferGitActionsFromCommand(command, { requireFailurePropagation: false }).some((action) =>
10115
10127
  WORKTREE_CHANGING_GIT_ACTIONS.has(action)
@@ -11501,6 +11513,21 @@ export function nextStepRuntimeConfig(config = {}, state = {}) {
11501
11513
  ? state.meta.activeExecutionContract || {}
11502
11514
  : {};
11503
11515
  const groundingTaskContract = completionTaskContract(config, state);
11516
+ const requestedArtifactEvaluation = evaluateRequestedArtifactRequirements(
11517
+ groundingTaskContract,
11518
+ {
11519
+ commandCwd: config.commandCwd || state.commandCwd || process.cwd(),
11520
+ state,
11521
+ }
11522
+ );
11523
+ if (!requestedArtifactEvaluation.ok) {
11524
+ runtimeConfig.requestedArtifactRequirementsPending = true;
11525
+ runtimeConfig.missingRequestedArtifactRequirements =
11526
+ requestedArtifactEvaluation.missing.map((item) => ({
11527
+ id: String(item?.id || ""),
11528
+ description: String(item?.description || item?.id || ""),
11529
+ }));
11530
+ }
11504
11531
  const groundingMutationRevision = Math.max(
11505
11532
  0,
11506
11533
  Number(state.meta?.projectVerification?.mutationRevision || 0)
@@ -12257,7 +12284,12 @@ export function nextStepRuntimeConfig(config = {}, state = {}) {
12257
12284
  runtimeConfig.requiredProjectCommand = pendingRequiredProjectCommands[0];
12258
12285
  runtimeConfig.pendingRequiredProjectCommands = pendingRequiredProjectCommands;
12259
12286
  }
12260
- if (!state.meta?.artifactProgress && !retainedFailedTest && !implementationOpen) {
12287
+ if (
12288
+ !state.meta?.artifactProgress &&
12289
+ !retainedFailedTest &&
12290
+ !implementationOpen &&
12291
+ requestedArtifactEvaluation.ok
12292
+ ) {
12261
12293
  const completionContract = completionTaskContract(config, state);
12262
12294
  const completionEvaluation = evaluateScsEvidence(
12263
12295
  completionContract,
@@ -13287,6 +13319,25 @@ function comparablePatchContextText(value = "") {
13287
13319
  .trim();
13288
13320
  }
13289
13321
 
13322
+ function preservePatchContextBoundaryWhitespace(marker = {}, replacement = "") {
13323
+ const value = String(replacement || "");
13324
+ if (String(marker.anchorKind || "") === "complete-file") {
13325
+ return { replacement: value, preserved: false };
13326
+ }
13327
+ const search = String(marker.search || "");
13328
+ const trailingBoundary = search.match(/(?:(?:\r\n|\n|\r)[\t ]*)+$/)?.[0] || "";
13329
+ if (!trailingBoundary) return { replacement: value, preserved: false };
13330
+ const withoutTrailingBoundary = value.replace(
13331
+ /(?:(?:\r\n|\n|\r)[\t ]*)+$/,
13332
+ ""
13333
+ );
13334
+ const normalized = `${withoutTrailingBoundary}${trailingBoundary}`;
13335
+ return {
13336
+ replacement: normalized,
13337
+ preserved: normalized !== value,
13338
+ };
13339
+ }
13340
+
13290
13341
  function patchContextDeclarationCounts(content = "") {
13291
13342
  const counts = new Map();
13292
13343
  for (const record of sourceLineRecords(content)) {
@@ -13514,6 +13565,11 @@ export function bindPatchContextRepairArguments(state = {}, requestedArgs = {})
13514
13565
  : patchContextReplacementScopeIssue(boundMarker, replace);
13515
13566
  }
13516
13567
  }
13568
+ const boundaryWhitespace = preservePatchContextBoundaryWhitespace(
13569
+ boundMarker,
13570
+ replace
13571
+ );
13572
+ replace = boundaryWhitespace.replacement;
13517
13573
  if (boundedSubrange && state.meta?.toolLoop?.patchContextRepair) {
13518
13574
  state.meta.toolLoop.patchContextRepair.lastBoundSearchHash =
13519
13575
  boundedSubrange.searchHash;
@@ -13532,6 +13588,7 @@ export function bindPatchContextRepairArguments(state = {}, requestedArgs = {})
13532
13588
  : "",
13533
13589
  boundedRequestedSubrange: Boolean(boundedSubrange),
13534
13590
  incrementalDeclarationRecovery,
13591
+ boundaryWhitespacePreserved: boundaryWhitespace.preserved,
13535
13592
  noOp:
13536
13593
  comparablePatchContextText(replace) ===
13537
13594
  comparablePatchContextText(boundMarker.search),
@@ -13607,8 +13664,21 @@ export function patchContextRefreshDecision(state = {}, toolResult = {}) {
13607
13664
  toolResult.ok === false &&
13608
13665
  toolResult.category === "patch-context-scope-mismatch"
13609
13666
  );
13667
+ const pythonSyntaxRegression = Boolean(
13668
+ String(toolResult.toolName || "") === "apply_patch" &&
13669
+ toolResult.ok === false &&
13670
+ toolResult.category === "python-syntax-regression"
13671
+ );
13610
13672
  const missingSearch = patchSearchTextWasNotFound(toolResult);
13611
- if (!missingSearch && !idempotencyBlock && !topologyBlock && !scopeMismatch) return null;
13673
+ if (
13674
+ !missingSearch &&
13675
+ !idempotencyBlock &&
13676
+ !topologyBlock &&
13677
+ !scopeMismatch &&
13678
+ !pythonSyntaxRegression
13679
+ ) {
13680
+ return null;
13681
+ }
13612
13682
  const targetPath = toolResultWorkspacePath(toolResult);
13613
13683
  if (!targetPath) return null;
13614
13684
  const toolLoop = state.meta?.toolLoop || {};
@@ -13636,6 +13706,7 @@ export function patchContextRefreshDecision(state = {}, toolResult = {}) {
13636
13706
  !idempotencyBlock &&
13637
13707
  !topologyBlock &&
13638
13708
  !scopeMismatch &&
13709
+ !pythonSyntaxRegression &&
13639
13710
  stalePatchFailureCount < 1 &&
13640
13711
  !followedIdempotencyBlock
13641
13712
  ) {
@@ -13667,6 +13738,8 @@ export function patchContextRefreshDecision(state = {}, toolResult = {}) {
13667
13738
  ? "required-symbol-topology"
13668
13739
  : scopeMismatch
13669
13740
  ? "patch-context-scope-mismatch"
13741
+ : pythonSyntaxRegression
13742
+ ? "python-syntax-regression"
13670
13743
  : "stale-patch-search",
13671
13744
  failedSearchPreview,
13672
13745
  failedSearchHash: String(toolResult.args?.searchHash || ""),
@@ -14648,6 +14721,8 @@ async function executeTool(browserState, toolCall, snapshot, config, store, obse
14648
14721
  patchContextBinding.boundedRequestedSubrange === true,
14649
14722
  incrementalDeclarationRecovery:
14650
14723
  Boolean(patchContextBinding.incrementalDeclarationRecovery),
14724
+ boundaryWhitespacePreserved:
14725
+ patchContextBinding.boundaryWhitespacePreserved === true,
14651
14726
  };
14652
14727
  await store.appendEvent("patch_context.anchor_injected", detail);
14653
14728
  observers.event("patch_context.anchor_injected", detail);
@@ -293,8 +293,9 @@ function delegatedValidationPlanClassification(tokens = []) {
293
293
  const args = tokens.slice(1).map((token) => String(token || ""));
294
294
  const executablePath = validationExecutablePathMetadata(tokens[0]);
295
295
  const basenameCommand = [executable, ...args].join(" ");
296
+ const structuredValidation = structuredValidationCommand(basenameCommand);
296
297
  const validationLike = Boolean(
297
- structuredValidationCommand(basenameCommand) || matchAny(TEST_PATTERNS, basenameCommand)
298
+ structuredValidation || matchAny(TEST_PATTERNS, basenameCommand)
298
299
  );
299
300
  if (executablePath.outsideWorkspace && validationLike) {
300
301
  return {
@@ -302,6 +303,12 @@ function delegatedValidationPlanClassification(tokens = []) {
302
303
  needsNetwork: true,
303
304
  writesWorkspace: true,
304
305
  mayMutateProject: true,
306
+ // Requiring trusted-shell authorization for an external executable does
307
+ // not prove that its bounded validation invocation mutates the project.
308
+ // Preserve that semantic distinction for post-command evidence tracking.
309
+ semanticMayMutateProject:
310
+ structuredValidation?.mayMutateProject ??
311
+ validationCommandMayMutateProject(basenameCommand),
305
312
  substantiveTest: false,
306
313
  reason:
307
314
  "The validation executable is outside the current workspace and requires trusted shell policy.",
@@ -508,6 +508,11 @@ function formatHardContractForPrompt(contract = {}) {
508
508
  const requiredExecutableTerms = normalizeStringList(contract.requiredExecutableTerms, []);
509
509
  const forbiddenTextTerms = normalizeStringList(contract.forbiddenTextTerms, []);
510
510
  const forbiddenActions = normalizeStringList(contract.forbiddenActions, []);
511
+ const requiredArtifactKinds = (Array.isArray(contract.requiredArtifactKinds)
512
+ ? contract.requiredArtifactKinds
513
+ : [])
514
+ .map((item) => compact(item?.description || item?.id || "", 160))
515
+ .filter(Boolean);
511
516
  if (exactOutputPaths.length) lines.push(`Exact output path(s): ${exactOutputPaths.join(", ")}`);
512
517
  if (exactInputPaths.length) lines.push(`Exact input/reference path(s) to use: ${exactInputPaths.join(", ")}`);
513
518
  if (requiredTextTerms.length) lines.push(`Required text term(s) in the output: ${requiredTextTerms.join(", ")}`);
@@ -516,6 +521,9 @@ function formatHardContractForPrompt(contract = {}) {
516
521
  }
517
522
  if (forbiddenTextTerms.length) lines.push(`Forbidden text term(s) in the output: ${forbiddenTextTerms.join(", ")}`);
518
523
  if (forbiddenActions.length) lines.push(`Forbidden action(s): ${forbiddenActions.join("; ")}`);
524
+ if (requiredArtifactKinds.length) {
525
+ lines.push(`Required artifact deliverable(s): ${requiredArtifactKinds.join(", ")}`);
526
+ }
519
527
  return lines.length
520
528
  ? [
521
529
  "Inferred hard task contract. Preserve these literally; do not replace them with weaker or contradictory criteria:",
@@ -531,8 +539,19 @@ function deterministicPlanContractIssue(committee = {}, contract = {}) {
531
539
  const missingRequiredTerms = normalizeStringList(contract.requiredTextTerms, []).filter((item) => !planText.includes(item));
532
540
  const missingExecutableTerms = normalizeStringList(contract.requiredExecutableTerms, []).filter((item) => !planText.includes(item));
533
541
  const forbiddenTermsInPlan = normalizeStringList(contract.forbiddenTextTerms, []).filter((item) => planText.includes(item));
542
+ const missingArtifactKinds = (Array.isArray(contract.requiredArtifactKinds)
543
+ ? contract.requiredArtifactKinds
544
+ : []).filter((item) => {
545
+ const id = String(item?.id || "").replace(/^format:/, "");
546
+ const description = String(item?.description || "");
547
+ const terms = [id, description]
548
+ .flatMap((value) => value.toLocaleLowerCase("en-US").split(/[^a-z0-9.]+/))
549
+ .filter((value) => value.length >= 3);
550
+ const normalizedPlan = planText.toLocaleLowerCase("en-US");
551
+ return terms.length > 0 && !terms.some((term) => normalizedPlan.includes(term));
552
+ });
534
553
  const actionContradiction = deterministicPlanActionContradiction(planText, contract);
535
- if (missingPath.length || missingInputPath.length || missingRequiredTerms.length || missingExecutableTerms.length || forbiddenTermsInPlan.length || actionContradiction) {
554
+ if (missingPath.length || missingInputPath.length || missingRequiredTerms.length || missingExecutableTerms.length || forbiddenTermsInPlan.length || missingArtifactKinds.length || actionContradiction) {
536
555
  return {
537
556
  decision: "veto_plan",
538
557
  confidence: 0.94,
@@ -542,6 +561,11 @@ function deterministicPlanContractIssue(committee = {}, contract = {}) {
542
561
  missingRequiredTerms.length ? `Plan omitted required text term(s): ${missingRequiredTerms.join(", ")}` : "",
543
562
  missingExecutableTerms.length ? `Plan omitted required executable source expression(s): ${missingExecutableTerms.join(", ")}` : "",
544
563
  forbiddenTermsInPlan.length ? `Plan includes forbidden text term(s): ${forbiddenTermsInPlan.join(", ")}` : "",
564
+ missingArtifactKinds.length
565
+ ? `Plan omitted requested artifact deliverable(s): ${missingArtifactKinds
566
+ .map((item) => item.description || item.id)
567
+ .join(", ")}`
568
+ : "",
545
569
  actionContradiction || "",
546
570
  ].filter(Boolean),
547
571
  reason:
@@ -521,6 +521,120 @@ function inferExactOutputPaths(goal = "") {
521
521
  return uniqueLimited(filterShadowedBasenames(paths), 16);
522
522
  }
523
523
 
524
+ const REQUESTED_ARTIFACT_FORMATS = [
525
+ { extension: ".pptx", pattern: /\b(?:pptx|powerpoint)\b/i, description: "editable PowerPoint deck" },
526
+ { extension: ".odp", pattern: /\b(?:odp|open(?:office|document) presentation)\b/i, description: "editable ODP deck" },
527
+ { extension: ".pdf", pattern: /\bpdf\b/i, description: "PDF document" },
528
+ { extension: ".docx", pattern: /\b(?:docx|word document)\b/i, description: "editable Word document" },
529
+ { extension: ".xlsx", pattern: /\b(?:xlsx|excel workbook)\b/i, description: "editable spreadsheet" },
530
+ { extension: ".png", pattern: /\bpng\b/i, description: "PNG image" },
531
+ { extension: ".svg", pattern: /\bsvg\b/i, description: "editable SVG image" },
532
+ ];
533
+
534
+ function artifactRequestHasOutputIntent(goal = "", taskProfile = "") {
535
+ const source = stripForbiddenLanguage(String(goal || ""));
536
+ return Boolean(
537
+ goalRequestsFileMutation(source, taskProfile) ||
538
+ /\b(?:create|deliver|export|generate|leave|make|output|prepare|produce|save|write)\b/i.test(
539
+ source
540
+ ) ||
541
+ /\b(?:i|we)\s+(?:also\s+)?(?:need|want|would like)\b[^.\n;]{0,100}\b(?:answer|artifact|deck|document|handout|material|pdf|presentation|preview|report|sheet|slides?|workbook|worksheet)\b/i.test(
542
+ source
543
+ ) ||
544
+ /\b(?:give|provide|return|send)\b[^.\n;]{0,80}\b(?:answer|artifact|deck|document|handout|material|pdf|presentation|preview|report|sheet|slides?|workbook|worksheet)\b/i.test(
545
+ source
546
+ ) ||
547
+ /(?:创建|生成|输出|导出|保存|提供|需要|交付)/u.test(source)
548
+ );
549
+ }
550
+
551
+ export function inferRequestedArtifactRequirements(goal = "", taskProfile = "") {
552
+ const source = stripForbiddenLanguage(String(goal || ""));
553
+ if (!artifactRequestHasOutputIntent(source, taskProfile)) return [];
554
+
555
+ const requirements = [];
556
+ const add = (requirement) => {
557
+ if (!requirement?.id || requirements.some((item) => item.id === requirement.id)) return;
558
+ requirements.push(requirement);
559
+ };
560
+
561
+ for (const format of REQUESTED_ARTIFACT_FORMATS) {
562
+ if (!format.pattern.test(source)) continue;
563
+ add({
564
+ id: `format:${format.extension}`,
565
+ kind: "format",
566
+ extension: format.extension,
567
+ description: format.description,
568
+ });
569
+ }
570
+
571
+ const deckRequested = /\b(?:deck|presentation|slides?|slide deck|lecture deck)\b/i.test(source);
572
+ const editableRequested = /\b(?:editable|edit-friendly|native(?:ly)? editable)\b/i.test(source);
573
+ if (deckRequested && editableRequested) {
574
+ add({
575
+ id: "editable-presentation",
576
+ kind: "editable-presentation",
577
+ extensions: [".pptx", ".odp", ".key"],
578
+ description: "editable presentation deck",
579
+ });
580
+ }
581
+
582
+ if (
583
+ /\bprintable\b/i.test(source) &&
584
+ /\b(?:answer|deck|document|handout|material|practice|sheet|slides?|worksheet)\b/i.test(source)
585
+ ) {
586
+ add({
587
+ id: "printable-document",
588
+ kind: "printable-document",
589
+ extensions: [".pdf"],
590
+ description: "printable PDF material",
591
+ });
592
+ }
593
+
594
+ if (
595
+ /\b(?:helpful|high[- ]resolution|useful|visual)\s+preview\b|\b(?:image|png|rendered|screenshot)\s+preview\b|\bpreview\s+(?:image|png|render|screenshot)\b/i.test(
596
+ source
597
+ )
598
+ ) {
599
+ add({
600
+ id: "visual-preview",
601
+ kind: "visual-preview",
602
+ extensions: [".png", ".jpg", ".jpeg", ".webp"],
603
+ description: "visual preview image",
604
+ });
605
+ }
606
+
607
+ if (/\b(?:practice sheet|worksheet|exercise sheet|learner handout)\b/i.test(source)) {
608
+ add({
609
+ id: "practice-material",
610
+ kind: "practice-material",
611
+ description: "separate practice or worksheet material",
612
+ });
613
+ }
614
+
615
+ if (/\b(?:answer key|answers?|worked solutions?|solutions? sheet)\b/i.test(source)) {
616
+ add({
617
+ id: "answer-material",
618
+ kind: "answer-material",
619
+ description: "separate answer or solution material",
620
+ });
621
+ }
622
+
623
+ if (
624
+ /\b(?:reproducible|repeatable)\b[^.\n;]{0,80}\b(?:build|generation|export|render)\b|\b(?:build|generation|export|render)\s+(?:entrypoint|script|command)\b/i.test(
625
+ source
626
+ )
627
+ ) {
628
+ add({
629
+ id: "reproducible-build-entrypoint",
630
+ kind: "reproducible-build-entrypoint",
631
+ description: "reproducible build or export entrypoint",
632
+ });
633
+ }
634
+
635
+ return requirements.slice(0, 16);
636
+ }
637
+
524
638
  function normalizedContractPath(value = "") {
525
639
  return String(value || "")
526
640
  .trim()
@@ -1581,6 +1695,10 @@ export function deriveScsTaskContract({ goal = "", taskProfile = "", acceptanceC
1581
1695
  excludedOutputPaths
1582
1696
  ).filter((item) => !inferredOutputPaths.includes(item) && !exactOutputPaths.includes(item));
1583
1697
  const declaredSourceRoots = inferDeclaredSourceRoots(evidenceGoal);
1698
+ const requiredArtifactKinds = inferRequestedArtifactRequirements(
1699
+ positiveEvidenceGoal,
1700
+ taskProfile
1701
+ );
1584
1702
  return {
1585
1703
  version: 1,
1586
1704
  outcome: compact(evidenceGoal || "Complete the requested task.", 500),
@@ -1589,6 +1707,7 @@ export function deriveScsTaskContract({ goal = "", taskProfile = "", acceptanceC
1589
1707
  requiredEvidence,
1590
1708
  forbiddenActions: inferForbiddenActions(evidenceGoal),
1591
1709
  exactOutputPaths,
1710
+ requiredArtifactKinds,
1592
1711
  excludedOutputPaths,
1593
1712
  artifactRoot,
1594
1713
  exactInputPaths,
@@ -1817,6 +1936,190 @@ function resolveContractPath(commandCwd = process.cwd(), rawPath = "") {
1817
1936
  return path.resolve(commandCwd || process.cwd(), text);
1818
1937
  }
1819
1938
 
1939
+ const ARTIFACT_SCAN_IGNORED_DIRECTORIES = new Set([
1940
+ ".aginti",
1941
+ ".aginti-sessions",
1942
+ ".git",
1943
+ ".hg",
1944
+ ".svn",
1945
+ "__pycache__",
1946
+ "node_modules",
1947
+ "vendor",
1948
+ ]);
1949
+
1950
+ function taskArtifactFreshnessBoundary(state = {}) {
1951
+ const lifecycle = Array.isArray(state.meta?.goalContract?.lifecycle)
1952
+ ? state.meta.goalContract.lifecycle
1953
+ : [];
1954
+ const lifecycleTimes = lifecycle
1955
+ .map((item) => Date.parse(String(item?.at || "")))
1956
+ .filter(Number.isFinite);
1957
+ if (lifecycleTimes.length) return Math.min(...lifecycleTimes) - 2000;
1958
+ const mutationTimes = (Array.isArray(state.meta?.projectVerification?.mutationHistory)
1959
+ ? state.meta.projectVerification.mutationHistory
1960
+ : [])
1961
+ .map((item) => Date.parse(String(item?.at || "")))
1962
+ .filter(Number.isFinite);
1963
+ return mutationTimes.length ? Math.min(...mutationTimes) - 2000 : 0;
1964
+ }
1965
+
1966
+ function taskArtifactMutationPaths(state = {}) {
1967
+ const paths = new Set();
1968
+ for (const mutation of Array.isArray(state.meta?.projectVerification?.mutationHistory)
1969
+ ? state.meta.projectVerification.mutationHistory
1970
+ : []) {
1971
+ for (const candidate of Array.isArray(mutation?.paths) ? mutation.paths : []) {
1972
+ const normalized = normalizedContractPath(candidate);
1973
+ if (normalized) paths.add(normalized.toLocaleLowerCase("en-US"));
1974
+ }
1975
+ }
1976
+ return paths;
1977
+ }
1978
+
1979
+ function collectRequestedArtifactCandidates(commandCwd = process.cwd(), state = {}) {
1980
+ const root = path.resolve(commandCwd || process.cwd());
1981
+ const freshnessBoundary = taskArtifactFreshnessBoundary(state);
1982
+ const mutationPaths = taskArtifactMutationPaths(state);
1983
+ const candidates = [];
1984
+ const queue = [{ absolutePath: root, depth: 0 }];
1985
+ let visited = 0;
1986
+ while (queue.length && candidates.length < 3000 && visited < 6000) {
1987
+ const current = queue.shift();
1988
+ visited += 1;
1989
+ let entries = [];
1990
+ try {
1991
+ entries = fs.readdirSync(current.absolutePath, { withFileTypes: true });
1992
+ } catch {
1993
+ continue;
1994
+ }
1995
+ for (const entry of entries) {
1996
+ if (entry.isSymbolicLink()) continue;
1997
+ const absolutePath = path.join(current.absolutePath, entry.name);
1998
+ if (entry.isDirectory()) {
1999
+ if (
2000
+ current.depth < 7 &&
2001
+ !ARTIFACT_SCAN_IGNORED_DIRECTORIES.has(entry.name) &&
2002
+ !entry.name.startsWith(".cache")
2003
+ ) {
2004
+ queue.push({ absolutePath, depth: current.depth + 1 });
2005
+ }
2006
+ continue;
2007
+ }
2008
+ if (!entry.isFile()) continue;
2009
+ let stat;
2010
+ try {
2011
+ stat = fs.statSync(absolutePath);
2012
+ } catch {
2013
+ continue;
2014
+ }
2015
+ if (!stat.size) continue;
2016
+ const relativePath = path.relative(root, absolutePath).replace(/\\/g, "/");
2017
+ const normalized = normalizedContractPath(relativePath).toLocaleLowerCase("en-US");
2018
+ const fresh = Boolean(
2019
+ mutationPaths.has(normalized) ||
2020
+ freshnessBoundary === 0 ||
2021
+ Number(stat.mtimeMs || 0) >= freshnessBoundary
2022
+ );
2023
+ candidates.push({
2024
+ path: relativePath,
2025
+ extension: path.extname(entry.name).toLocaleLowerCase("en-US"),
2026
+ basename: entry.name.toLocaleLowerCase("en-US"),
2027
+ bytes: stat.size,
2028
+ mtimeMs: Number(stat.mtimeMs || 0),
2029
+ fresh,
2030
+ });
2031
+ }
2032
+ }
2033
+ return candidates;
2034
+ }
2035
+
2036
+ function requestedArtifactRequirementMatches(requirement = {}, candidate = {}) {
2037
+ const extension = String(candidate.extension || "").toLocaleLowerCase("en-US");
2038
+ const basename = String(candidate.basename || "").toLocaleLowerCase("en-US");
2039
+ if (requirement.kind === "format") {
2040
+ return extension === String(requirement.extension || "").toLocaleLowerCase("en-US");
2041
+ }
2042
+ if (["editable-presentation", "printable-document", "visual-preview"].includes(requirement.kind)) {
2043
+ const extensions = new Set(
2044
+ (Array.isArray(requirement.extensions) ? requirement.extensions : [])
2045
+ .map((item) => String(item || "").toLocaleLowerCase("en-US"))
2046
+ );
2047
+ if (!extensions.has(extension)) return false;
2048
+ if (requirement.kind === "visual-preview") {
2049
+ return /(?:preview|contact[-_ ]?sheet|overview|thumbnail|render)/i.test(basename);
2050
+ }
2051
+ return true;
2052
+ }
2053
+ if (requirement.kind === "practice-material") {
2054
+ return /(?:practice|worksheet|exercise|handout|activity)/i.test(basename) &&
2055
+ /\.(?:docx|html?|md|odt|pdf|tex|txt)$/i.test(extension);
2056
+ }
2057
+ if (requirement.kind === "answer-material") {
2058
+ return /(?:answer|solution|worked[-_ ]?answers?)/i.test(basename) &&
2059
+ /\.(?:docx|html?|md|odt|pdf|tex|txt)$/i.test(extension);
2060
+ }
2061
+ if (requirement.kind === "reproducible-build-entrypoint") {
2062
+ return (
2063
+ /^(?:makefile|justfile)$/i.test(basename) ||
2064
+ /(?:^|[-_])(?:build|compile|export|generate|render)(?:[-_.]|$)/i.test(basename)
2065
+ ) && /(?:^makefile$|^justfile$|\.(?:cjs|js|mjs|py|sh|ts)$)/i.test(basename);
2066
+ }
2067
+ return false;
2068
+ }
2069
+
2070
+ export function evaluateRequestedArtifactRequirements(
2071
+ contract = {},
2072
+ { commandCwd = process.cwd(), state = {} } = {}
2073
+ ) {
2074
+ const requirements = Array.isArray(contract.requiredArtifactKinds)
2075
+ ? contract.requiredArtifactKinds
2076
+ : [];
2077
+ if (!requirements.length) {
2078
+ return {
2079
+ ok: true,
2080
+ checked: false,
2081
+ requirements: [],
2082
+ satisfied: [],
2083
+ missing: [],
2084
+ candidates: [],
2085
+ reason: "No semantic artifact-set contract was inferred.",
2086
+ };
2087
+ }
2088
+ const candidates = collectRequestedArtifactCandidates(commandCwd, state);
2089
+ const requireFresh = contract.requiresFileMutation === true;
2090
+ const usableCandidates = requireFresh
2091
+ ? candidates.filter((candidate) => candidate.fresh)
2092
+ : candidates;
2093
+ const satisfied = [];
2094
+ const missing = [];
2095
+ for (const requirement of requirements) {
2096
+ const matches = usableCandidates.filter((candidate) =>
2097
+ requestedArtifactRequirementMatches(requirement, candidate)
2098
+ );
2099
+ if (matches.length) {
2100
+ satisfied.push({
2101
+ ...requirement,
2102
+ paths: matches.map((candidate) => candidate.path).slice(0, 12),
2103
+ });
2104
+ } else {
2105
+ missing.push(requirement);
2106
+ }
2107
+ }
2108
+ return {
2109
+ ok: missing.length === 0,
2110
+ checked: true,
2111
+ requirements,
2112
+ satisfied,
2113
+ missing,
2114
+ candidates: usableCandidates.slice(0, 120),
2115
+ reason: missing.length
2116
+ ? `Missing requested artifact deliverables: ${missing
2117
+ .map((item) => item.description || item.id)
2118
+ .join(", ")}.`
2119
+ : "Every requested artifact format and role has a fresh workspace deliverable.",
2120
+ };
2121
+ }
2122
+
1820
2123
  const PROJECT_SOURCE_PATH_PATTERN =
1821
2124
  /\.(?:c|cc|cpp|cxx|h|hh|hpp|cs|go|java|js|jsx|ts|tsx|mjs|cjs|kt|kts|php|py|r|rb|rs|scala|sh|sql|swift|vue|svelte|jl)$/i;
1822
2125
  const PROJECT_SOURCE_MANIFEST_PATTERN =
@@ -2993,11 +3296,16 @@ export function evaluateScsSemanticContract(
2993
3296
  const forbiddenTextTerms = Array.isArray(contract.forbiddenTextTerms) ? contract.forbiddenTextTerms : [];
2994
3297
  const projectTestVerification = evaluateProjectTestVerification(events);
2995
3298
  const executable = evaluateRequiredExecutableTerms(contract, { commandCwd, events, state });
3299
+ const requestedArtifacts = evaluateRequestedArtifactRequirements(contract, {
3300
+ commandCwd,
3301
+ state,
3302
+ });
2996
3303
  if (
2997
3304
  !exactOutputPaths.length &&
2998
3305
  !requiredTextTerms.length &&
2999
3306
  !requiredExecutableTerms.length &&
3000
- !forbiddenTextTerms.length
3307
+ !forbiddenTextTerms.length &&
3308
+ !requestedArtifacts.checked
3001
3309
  ) {
3002
3310
  return projectTestVerification.checked
3003
3311
  ? {
@@ -3009,18 +3317,23 @@ export function evaluateScsSemanticContract(
3009
3317
  : { ok: true, checked: false, projectTestVerification, reason: "No semantic file contract was inferred." };
3010
3318
  }
3011
3319
  if (!exactOutputPaths.length) {
3012
- const ok = executable.ok && projectTestVerification.ok;
3320
+ const ok = executable.ok && projectTestVerification.ok && requestedArtifacts.ok;
3013
3321
  return {
3014
3322
  ok,
3015
- checked: executable.checked || projectTestVerification.checked,
3016
- reason: !executable.ok
3323
+ checked: executable.checked || projectTestVerification.checked || requestedArtifacts.checked,
3324
+ reason: !requestedArtifacts.ok
3325
+ ? requestedArtifacts.reason
3326
+ : !executable.ok
3017
3327
  ? executable.reason
3018
3328
  : !projectTestVerification.ok
3019
3329
  ? projectTestVerification.reason
3020
- : "Semantic text terms were inferred, but no exact output path was inferred for deterministic file inspection.",
3330
+ : requestedArtifacts.checked
3331
+ ? requestedArtifacts.reason
3332
+ : "Semantic text terms were inferred, but no exact output path was inferred for deterministic file inspection.",
3021
3333
  requiredTextTerms,
3022
3334
  requiredExecutableTerms,
3023
3335
  forbiddenTextTerms,
3336
+ requestedArtifacts,
3024
3337
  missingExecutableTerms: executable.missingExecutableTerms,
3025
3338
  executableSourcePaths: executable.executableSourcePaths,
3026
3339
  executable,
@@ -3048,7 +3361,8 @@ export function evaluateScsSemanticContract(
3048
3361
  presentForbiddenText.length === 0 &&
3049
3362
  executable.ok &&
3050
3363
  sourceGrounding.ok &&
3051
- projectTestVerification.ok;
3364
+ projectTestVerification.ok &&
3365
+ requestedArtifacts.ok;
3052
3366
  return {
3053
3367
  ok,
3054
3368
  checked: true,
@@ -3056,6 +3370,7 @@ export function evaluateScsSemanticContract(
3056
3370
  requiredTextTerms,
3057
3371
  requiredExecutableTerms,
3058
3372
  forbiddenTextTerms,
3373
+ requestedArtifacts,
3059
3374
  missingFiles,
3060
3375
  missingRequiredText,
3061
3376
  missingExecutableTerms: executable.missingExecutableTerms,
@@ -3085,6 +3400,7 @@ export function evaluateScsSemanticContract(
3085
3400
  presentForbiddenText.length ? `Forbidden text terms present: ${presentForbiddenText.join(", ")}` : "",
3086
3401
  !sourceGrounding.ok ? sourceGrounding.reason : "",
3087
3402
  !projectTestVerification.ok ? projectTestVerification.reason : "",
3403
+ !requestedArtifacts.ok ? requestedArtifacts.reason : "",
3088
3404
  ]
3089
3405
  .filter(Boolean)
3090
3406
  .join("; "),
@@ -3619,6 +3935,7 @@ export function summarizeScsContractEvidence({ contract = {}, ledger = {}, evalu
3619
3935
  })),
3620
3936
  forbiddenActions: contract.forbiddenActions || [],
3621
3937
  exactOutputPaths: contract.exactOutputPaths || [],
3938
+ requiredArtifactKinds: contract.requiredArtifactKinds || [],
3622
3939
  exactInputPaths: contract.exactInputPaths || [],
3623
3940
  requiredToolCalls: contract.requiredToolCalls || [],
3624
3941
  requiredGitActions: contract.requiredGitActions || [],