@lazyingart/agintiflow 0.20.281 → 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.281",
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 },
@@ -10364,6 +10459,60 @@ try {
10364
10459
  assert(resumedBudget?.extensionsUsed === 0, "runAgent retained stale extension usage after an explicit resumed max-steps patch");
10365
10460
  assert(resumedBudget?.resetFromExplicitOverride === true, "runAgent did not record the explicit budget reset boundary");
10366
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
+
10367
10516
  await fs.rm(tempRoot, { recursive: true, force: true });
10368
10517
  console.log("smoke-dynamic-step-budget ok");
10369
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.",
@@ -11507,6 +11513,21 @@ export function nextStepRuntimeConfig(config = {}, state = {}) {
11507
11513
  ? state.meta.activeExecutionContract || {}
11508
11514
  : {};
11509
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
+ }
11510
11531
  const groundingMutationRevision = Math.max(
11511
11532
  0,
11512
11533
  Number(state.meta?.projectVerification?.mutationRevision || 0)
@@ -12263,7 +12284,12 @@ export function nextStepRuntimeConfig(config = {}, state = {}) {
12263
12284
  runtimeConfig.requiredProjectCommand = pendingRequiredProjectCommands[0];
12264
12285
  runtimeConfig.pendingRequiredProjectCommands = pendingRequiredProjectCommands;
12265
12286
  }
12266
- if (!state.meta?.artifactProgress && !retainedFailedTest && !implementationOpen) {
12287
+ if (
12288
+ !state.meta?.artifactProgress &&
12289
+ !retainedFailedTest &&
12290
+ !implementationOpen &&
12291
+ requestedArtifactEvaluation.ok
12292
+ ) {
12267
12293
  const completionContract = completionTaskContract(config, state);
12268
12294
  const completionEvaluation = evaluateScsEvidence(
12269
12295
  completionContract,
@@ -13293,6 +13319,25 @@ function comparablePatchContextText(value = "") {
13293
13319
  .trim();
13294
13320
  }
13295
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
+
13296
13341
  function patchContextDeclarationCounts(content = "") {
13297
13342
  const counts = new Map();
13298
13343
  for (const record of sourceLineRecords(content)) {
@@ -13520,6 +13565,11 @@ export function bindPatchContextRepairArguments(state = {}, requestedArgs = {})
13520
13565
  : patchContextReplacementScopeIssue(boundMarker, replace);
13521
13566
  }
13522
13567
  }
13568
+ const boundaryWhitespace = preservePatchContextBoundaryWhitespace(
13569
+ boundMarker,
13570
+ replace
13571
+ );
13572
+ replace = boundaryWhitespace.replacement;
13523
13573
  if (boundedSubrange && state.meta?.toolLoop?.patchContextRepair) {
13524
13574
  state.meta.toolLoop.patchContextRepair.lastBoundSearchHash =
13525
13575
  boundedSubrange.searchHash;
@@ -13538,6 +13588,7 @@ export function bindPatchContextRepairArguments(state = {}, requestedArgs = {})
13538
13588
  : "",
13539
13589
  boundedRequestedSubrange: Boolean(boundedSubrange),
13540
13590
  incrementalDeclarationRecovery,
13591
+ boundaryWhitespacePreserved: boundaryWhitespace.preserved,
13541
13592
  noOp:
13542
13593
  comparablePatchContextText(replace) ===
13543
13594
  comparablePatchContextText(boundMarker.search),
@@ -13613,8 +13664,21 @@ export function patchContextRefreshDecision(state = {}, toolResult = {}) {
13613
13664
  toolResult.ok === false &&
13614
13665
  toolResult.category === "patch-context-scope-mismatch"
13615
13666
  );
13667
+ const pythonSyntaxRegression = Boolean(
13668
+ String(toolResult.toolName || "") === "apply_patch" &&
13669
+ toolResult.ok === false &&
13670
+ toolResult.category === "python-syntax-regression"
13671
+ );
13616
13672
  const missingSearch = patchSearchTextWasNotFound(toolResult);
13617
- if (!missingSearch && !idempotencyBlock && !topologyBlock && !scopeMismatch) return null;
13673
+ if (
13674
+ !missingSearch &&
13675
+ !idempotencyBlock &&
13676
+ !topologyBlock &&
13677
+ !scopeMismatch &&
13678
+ !pythonSyntaxRegression
13679
+ ) {
13680
+ return null;
13681
+ }
13618
13682
  const targetPath = toolResultWorkspacePath(toolResult);
13619
13683
  if (!targetPath) return null;
13620
13684
  const toolLoop = state.meta?.toolLoop || {};
@@ -13642,6 +13706,7 @@ export function patchContextRefreshDecision(state = {}, toolResult = {}) {
13642
13706
  !idempotencyBlock &&
13643
13707
  !topologyBlock &&
13644
13708
  !scopeMismatch &&
13709
+ !pythonSyntaxRegression &&
13645
13710
  stalePatchFailureCount < 1 &&
13646
13711
  !followedIdempotencyBlock
13647
13712
  ) {
@@ -13673,6 +13738,8 @@ export function patchContextRefreshDecision(state = {}, toolResult = {}) {
13673
13738
  ? "required-symbol-topology"
13674
13739
  : scopeMismatch
13675
13740
  ? "patch-context-scope-mismatch"
13741
+ : pythonSyntaxRegression
13742
+ ? "python-syntax-regression"
13676
13743
  : "stale-patch-search",
13677
13744
  failedSearchPreview,
13678
13745
  failedSearchHash: String(toolResult.args?.searchHash || ""),
@@ -14654,6 +14721,8 @@ async function executeTool(browserState, toolCall, snapshot, config, store, obse
14654
14721
  patchContextBinding.boundedRequestedSubrange === true,
14655
14722
  incrementalDeclarationRecovery:
14656
14723
  Boolean(patchContextBinding.incrementalDeclarationRecovery),
14724
+ boundaryWhitespacePreserved:
14725
+ patchContextBinding.boundaryWhitespacePreserved === true,
14657
14726
  };
14658
14727
  await store.appendEvent("patch_context.anchor_injected", detail);
14659
14728
  observers.event("patch_context.anchor_injected", detail);
@@ -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 || [],