@lazyingart/agintiflow 0.20.306 → 0.20.308

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.306",
3
+ "version": "0.20.308",
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",
@@ -7471,6 +7471,212 @@ try {
7471
7471
  precommitDocumentQualityState.meta.completionEvidenceRepair === undefined,
7472
7472
  "a passing pre-commit document check did not clear its bounded repair marker"
7473
7473
  );
7474
+ const currentGoalGeneratedDocumentState = {
7475
+ goal:
7476
+ "Read TASK.md, produce a verified PDF memo for the current task, and commit only the intentional work.",
7477
+ messages: [],
7478
+ meta: {
7479
+ taskProfile: "writing",
7480
+ goalContract: {
7481
+ revision: 5,
7482
+ activeGoalRevision: 5,
7483
+ activeGoal:
7484
+ "Read TASK.md, produce a verified PDF memo for the current task, and commit only the intentional work.",
7485
+ },
7486
+ projectVerification: {
7487
+ mutationRevision: 12,
7488
+ privateMutationRevision: 0,
7489
+ mutationHistory: [
7490
+ {
7491
+ revision: 8,
7492
+ at: "2026-09-01T09:00:00.000Z",
7493
+ toolName: "run_command",
7494
+ paths: ["stale-root.pdf"],
7495
+ goalRevision: 4,
7496
+ },
7497
+ {
7498
+ revision: 11,
7499
+ at: "2026-09-01T09:05:00.000Z",
7500
+ toolName: "write_file",
7501
+ paths: ["daily_memo.tex"],
7502
+ goalRevision: 5,
7503
+ },
7504
+ {
7505
+ revision: 12,
7506
+ at: "2026-09-01T09:06:00.000Z",
7507
+ toolName: "run_command",
7508
+ paths: [
7509
+ "daily_memo.pdf",
7510
+ "daily_memo.aux",
7511
+ "/tmp/escape.pdf",
7512
+ ".env.pdf",
7513
+ "secrets/hidden.pdf",
7514
+ ],
7515
+ goalRevision: 5,
7516
+ },
7517
+ ],
7518
+ },
7519
+ },
7520
+ };
7521
+ let currentGoalDocumentValidatorInput = null;
7522
+ const currentGoalDocumentAssessment = await documentQualityCommitAssessment(
7523
+ currentGoalGeneratedDocumentState,
7524
+ "run_command",
7525
+ { command: "git add -- daily_memo.tex daily_memo.pdf && git commit -m memo" },
7526
+ {
7527
+ commandCwd: missingArtifactCommitWorkspace,
7528
+ taskProfile: "writing",
7529
+ documentArtifactValidator: async (input) => {
7530
+ currentGoalDocumentValidatorInput = input;
7531
+ return {
7532
+ ok: true,
7533
+ checked: true,
7534
+ artifacts: [{ path: "daily_memo.pdf" }],
7535
+ defects: [],
7536
+ reason: "Independent document checks passed for daily_memo.pdf.",
7537
+ };
7538
+ },
7539
+ }
7540
+ );
7541
+ assert(
7542
+ currentGoalDocumentAssessment?.ok === true &&
7543
+ currentGoalDocumentValidatorInput?.exactOutputPaths?.includes(
7544
+ "daily_memo.pdf"
7545
+ ) &&
7546
+ !currentGoalDocumentValidatorInput.exactOutputPaths.includes(
7547
+ "stale-root.pdf"
7548
+ ) &&
7549
+ !currentGoalDocumentValidatorInput.exactOutputPaths.includes(
7550
+ "daily_memo.aux"
7551
+ ) &&
7552
+ !currentGoalDocumentValidatorInput.exactOutputPaths.includes(
7553
+ "secrets/hidden.pdf"
7554
+ ),
7555
+ `a current-goal generated root PDF was not discovered safely for pre-commit validation: ${JSON.stringify(
7556
+ currentGoalDocumentValidatorInput
7557
+ )}`
7558
+ );
7559
+ const unrelatedRootWorkspace = path.join(
7560
+ tempRoot,
7561
+ "unrelated-root-document"
7562
+ );
7563
+ await fs.mkdir(unrelatedRootWorkspace, { recursive: true });
7564
+ await fs.writeFile(
7565
+ path.join(unrelatedRootWorkspace, "unrelated.pdf"),
7566
+ "pre-existing root PDF that the current goal did not create",
7567
+ "utf8"
7568
+ );
7569
+ const unrelatedRootDocumentState = {
7570
+ goal:
7571
+ "Produce a verified PDF artifact for this task and commit the intentional work.",
7572
+ messages: [],
7573
+ meta: {
7574
+ taskProfile: "writing",
7575
+ goalContract: {
7576
+ revision: 6,
7577
+ activeGoalRevision: 6,
7578
+ activeGoal:
7579
+ "Produce a verified PDF artifact for this task and commit the intentional work.",
7580
+ },
7581
+ projectVerification: {
7582
+ mutationRevision: 4,
7583
+ privateMutationRevision: 0,
7584
+ mutationHistory: [
7585
+ {
7586
+ revision: 3,
7587
+ at: "2026-09-01T09:10:00.000Z",
7588
+ toolName: "run_command",
7589
+ paths: ["unrelated.pdf"],
7590
+ goalRevision: 5,
7591
+ },
7592
+ {
7593
+ revision: 4,
7594
+ at: "2026-09-01T09:12:00.000Z",
7595
+ toolName: "write_file",
7596
+ paths: ["notes.tex"],
7597
+ goalRevision: 6,
7598
+ },
7599
+ ],
7600
+ },
7601
+ },
7602
+ };
7603
+ let unrelatedRootValidatorInput = null;
7604
+ const unrelatedRootAssessment = await documentQualityCommitAssessment(
7605
+ unrelatedRootDocumentState,
7606
+ "run_command",
7607
+ { command: "git add -- notes.tex && git commit -m notes" },
7608
+ {
7609
+ commandCwd: unrelatedRootWorkspace,
7610
+ taskProfile: "writing",
7611
+ documentArtifactValidator: async (input) => {
7612
+ unrelatedRootValidatorInput = input;
7613
+ return {
7614
+ ok: false,
7615
+ checked: true,
7616
+ artifacts: [],
7617
+ defects: [{ code: "missing-document-artifact" }],
7618
+ reason: "No readable DOCX or PDF artifact was found for the completed document task.",
7619
+ };
7620
+ },
7621
+ }
7622
+ );
7623
+ assert(
7624
+ unrelatedRootAssessment?.ok === false &&
7625
+ unrelatedRootAssessment.category === "document-quality-incomplete" &&
7626
+ !unrelatedRootValidatorInput?.exactOutputPaths?.includes(
7627
+ "unrelated.pdf"
7628
+ ),
7629
+ `an unrelated pre-existing root PDF was accepted as current-goal evidence: ${JSON.stringify(
7630
+ unrelatedRootValidatorInput
7631
+ )}`
7632
+ );
7633
+ const semanticGateDocumentState = structuredClone(
7634
+ currentGoalGeneratedDocumentState
7635
+ );
7636
+ semanticGateDocumentState.meta.scs = {
7637
+ taskContract: { exactInputPaths: ["TASK.md"] },
7638
+ };
7639
+ let semanticGateValidatorInput = null;
7640
+ const semanticGateAssessment = await documentQualityCommitAssessment(
7641
+ semanticGateDocumentState,
7642
+ "run_command",
7643
+ { command: "git add -- daily_memo.tex daily_memo.pdf && git commit -m memo" },
7644
+ {
7645
+ commandCwd: missingArtifactCommitWorkspace,
7646
+ taskProfile: "writing",
7647
+ documentArtifactValidator: async (input) => {
7648
+ semanticGateValidatorInput = input;
7649
+ return {
7650
+ ok: false,
7651
+ checked: true,
7652
+ artifacts: [{ path: "daily_memo.pdf" }],
7653
+ defects: [{
7654
+ code: "source-topic-coverage-incomplete",
7655
+ path: "daily_memo.pdf",
7656
+ message: "The memo omitted current source-status facts.",
7657
+ }],
7658
+ reason: "daily_memo.pdf: The memo omitted current source-status facts.",
7659
+ };
7660
+ },
7661
+ }
7662
+ );
7663
+ assert(
7664
+ semanticGateAssessment?.ok === false &&
7665
+ semanticGateAssessment.category === "document-quality-incomplete" &&
7666
+ semanticGateDocumentState.meta.completionEvidenceRepair
7667
+ ?.artifactQualityRepairRequired === true &&
7668
+ semanticGateDocumentState.meta.completionEvidenceRepair
7669
+ ?.documentArtifactGenerationRequired !== true &&
7670
+ semanticGateValidatorInput?.exactOutputPaths?.includes(
7671
+ "daily_memo.pdf"
7672
+ ) &&
7673
+ semanticGateValidatorInput?.exactInputPaths?.includes("TASK.md"),
7674
+ `current-goal artifact discovery bypassed source/status quality gates or reopened the missing-artifact loop: ${JSON.stringify({
7675
+ assessment: semanticGateAssessment,
7676
+ input: semanticGateValidatorInput,
7677
+ repair: semanticGateDocumentState.meta.completionEvidenceRepair,
7678
+ })}`
7679
+ );
7474
7680
  await fs.writeFile(
7475
7681
  path.join(missingArtifactCommitWorkspace, "TASK.md"),
7476
7682
  [
@@ -11,7 +11,7 @@ import {
11
11
  } from "../src/agent-runner.js";
12
12
  import { resolveRuntimeConfig } from "../src/config.js";
13
13
  import { SessionStore } from "../src/session-store.js";
14
- import { finishResultClaimsIncompleteWork } from "../src/scs-evidence.js";
14
+ import { deriveScsTaskContract, finishResultClaimsIncompleteWork } from "../src/scs-evidence.js";
15
15
 
16
16
  const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
17
17
  const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "agintiflow-truthful-completion-"));
@@ -198,6 +198,9 @@ async function runCase({
198
198
  id,
199
199
  goal,
200
200
  taskProfile = "auto",
201
+ provider = "openai",
202
+ model = "scripted-model",
203
+ routingMode = "manual",
201
204
  responses,
202
205
  allowShellTool = false,
203
206
  allowFileTools = false,
@@ -205,6 +208,9 @@ async function runCase({
205
208
  executionTier = "",
206
209
  maxOutputTokens = undefined,
207
210
  resume = false,
211
+ runtimePatch = undefined,
212
+ expectedRuntimeRevision = undefined,
213
+ providerReadinessMode = undefined,
208
214
  setup = null,
209
215
  scsActive = false,
210
216
  }) {
@@ -215,11 +221,20 @@ async function runCase({
215
221
  if (typeof setup === "function") await setup(workspace);
216
222
  const calls = [];
217
223
  const client = scriptedClient([...responses], calls);
224
+ const factoryConfigs = [];
225
+ const clientFactory = async (runtimeConfig = {}) => {
226
+ factoryConfigs.push({
227
+ provider: runtimeConfig.provider,
228
+ model: runtimeConfig.model,
229
+ });
230
+ return client;
231
+ };
232
+ clientFactory.agintiDeterministicTest = true;
218
233
  const config = resolveRuntimeConfig(
219
234
  {
220
- provider: "openai",
221
- routingMode: "manual",
222
- model: "scripted-model",
235
+ provider,
236
+ routingMode,
237
+ model,
223
238
  goal,
224
239
  taskProfile,
225
240
  executionTier,
@@ -236,9 +251,9 @@ async function runCase({
236
251
  {
237
252
  baseDir: workspace,
238
253
  packageDir: repoRoot,
239
- provider: "openai",
240
- routingMode: "manual",
241
- model: "scripted-model",
254
+ provider,
255
+ routingMode,
256
+ model,
242
257
  executionTier,
243
258
  sessionId: id,
244
259
  resume: resume ? id : "",
@@ -255,13 +270,13 @@ async function runCase({
255
270
  allowMcpTools: false,
256
271
  allowParallelScouts: false,
257
272
  enableScs: scsActive ? "auto" : "off",
258
- clientFactory: async () => client,
273
+ clientFactory,
259
274
  }
260
275
  );
261
276
  Object.assign(config, {
262
277
  apiKey: "scripted-test-only",
263
278
  resume: resume ? id : "",
264
- clientFactory: async () => client,
279
+ clientFactory,
265
280
  sessionsDir,
266
281
  projectSessionsDir,
267
282
  useDockerSandbox: false,
@@ -283,17 +298,51 @@ async function runCase({
283
298
  : undefined,
284
299
  modelTimeoutMs: 1_000,
285
300
  ...(executionTier ? { executionTier, executionPolicy: { tier: executionTier, requiresPlan: false, reason: "Scripted completion smoke." } } : {}),
301
+ ...(providerReadinessMode ? { providerReadinessMode } : {}),
302
+ ...(runtimePatch ? { runtimePatch } : {}),
303
+ ...(expectedRuntimeRevision !== undefined ? { expectedRuntimeRevision } : {}),
286
304
  });
287
305
  const result = await runAgent(config);
288
306
  const store = new SessionStore(sessionsDir, id, { projectRoot: workspace, commandCwd: workspace, projectSessionsDir });
289
307
  return {
290
308
  result,
291
309
  calls,
310
+ factoryConfigs,
292
311
  events: await store.loadEvents(),
293
312
  state: await store.loadState(),
294
313
  };
295
314
  }
296
315
 
316
+ function providerRuntimePatch(provider, model) {
317
+ return {
318
+ provider,
319
+ model,
320
+ routingMode: "manual",
321
+ routeProvider: provider,
322
+ routeModel: model,
323
+ mainProvider: provider,
324
+ mainModel: model,
325
+ spareProvider: provider,
326
+ spareModel: model,
327
+ };
328
+ }
329
+
330
+ function scopedTaskGoal(request, artifactRoot) {
331
+ return [
332
+ "User request:",
333
+ request,
334
+ "",
335
+ `AGINTI_EVIDENCE_SCOPE_JSON: ${JSON.stringify({
336
+ mode: "task",
337
+ request,
338
+ artifact_root: artifactRoot,
339
+ })}`,
340
+ "",
341
+ "Artifact contract:",
342
+ "- If no file is produced, use an empty artifacts list.",
343
+ ].join("\n");
344
+ }
345
+
297
346
  try {
298
347
  const explanation = await runCase({
299
348
  id: "ordinary-explanation",
@@ -410,6 +459,125 @@ try {
410
459
  assert.equal(quotedChatClassification.result.result, '{"intent":"generation_only","publish":false}');
411
460
  assert(!quotedChatClassification.events.some((event) => event.type === "completion.evidence_rejected"));
412
461
 
462
+ const providerSwitchSessionId = "provider-switch-resume-contract";
463
+ const providerSwitchFirstTurn = await runCase({
464
+ id: providerSwitchSessionId,
465
+ goal: "Create provider-switch-proof.md with the exact text provider switch proof.",
466
+ taskProfile: "auto",
467
+ provider: "localllm",
468
+ model: "localllm-fast",
469
+ providerReadinessMode: "deterministic-test",
470
+ allowFileTools: true,
471
+ responses: [
472
+ assistant("", [
473
+ toolCall("write-provider-switch-proof", "write_file", {
474
+ path: "provider-switch-proof.md",
475
+ mode: "create",
476
+ content: "provider switch proof\n",
477
+ }),
478
+ ]),
479
+ assistant("", [
480
+ toolCall("finish-provider-switch-proof", "finish", {
481
+ result: "Created provider-switch-proof.md.",
482
+ }),
483
+ ]),
484
+ ],
485
+ });
486
+ assert.equal(providerSwitchFirstTurn.result.stopped, undefined);
487
+ assert.equal(providerSwitchFirstTurn.state.meta?.runtimeConfig?.provider, "localllm");
488
+ assert.equal(providerSwitchFirstTurn.state.meta?.runtimeConfig?.model, "localllm-fast");
489
+
490
+ const providerSwitchSecondTurn = await runCase({
491
+ id: providerSwitchSessionId,
492
+ goal: "Resume this exact session with the default provider and return exactly: DEEPSEEK_RESUME_OK",
493
+ taskProfile: "auto",
494
+ provider: "deepseek",
495
+ model: "deepseek-v4-flash",
496
+ resume: true,
497
+ runtimePatch: providerRuntimePatch("deepseek", "deepseek-v4-flash"),
498
+ expectedRuntimeRevision: providerSwitchFirstTurn.state.meta.runtimeConfig.revision,
499
+ responses: [
500
+ assistant("", [
501
+ toolCall("finish-provider-switch-deepseek", "finish", {
502
+ result: "DEEPSEEK_RESUME_OK",
503
+ }),
504
+ ]),
505
+ ],
506
+ });
507
+ assert.equal(providerSwitchSecondTurn.result.stopped, undefined);
508
+ assert.equal(providerSwitchSecondTurn.result.result, "DEEPSEEK_RESUME_OK");
509
+ assert.equal(providerSwitchSecondTurn.state.meta?.runtimeConfig?.provider, "deepseek");
510
+ assert.equal(providerSwitchSecondTurn.state.meta?.runtimeConfig?.model, "deepseek-v4-flash");
511
+ assert.equal(
512
+ providerSwitchSecondTurn.events.filter(
513
+ (event) => event.type === "session.runtime_resolved" && event.data?.provider === "deepseek"
514
+ ).length,
515
+ 1,
516
+ "explicit DeepSeek resume did not persist a provider switch"
517
+ );
518
+
519
+ const forcedLocalRequest =
520
+ "Resume this exact session and return exactly: LOCALLLM_FORCED_RESUME_OK Do not create or modify any file.";
521
+ const forcedLocalGoal = scopedTaskGoal(
522
+ forcedLocalRequest,
523
+ path.join(tempRoot, "artifacts", "provider-switch-resume-contract")
524
+ );
525
+ const forcedLocalContract = deriveScsTaskContract({ goal: forcedLocalGoal, taskProfile: "auto" });
526
+ assert.equal(
527
+ forcedLocalContract.requiresExternalEvidence,
528
+ false,
529
+ "a forbidden file-mutation clause made a pure response-only resume require external evidence"
530
+ );
531
+ assert.deepEqual(forcedLocalContract.requiredEvidence, []);
532
+ assert.deepEqual(forcedLocalContract.exactOutputPaths, []);
533
+ assert(
534
+ forcedLocalContract.forbiddenActions.some((item) => /create or modify any file/i.test(item)),
535
+ "forbidden file mutation was not retained as a guardrail"
536
+ );
537
+
538
+ const forcedLocalResume = await runCase({
539
+ id: providerSwitchSessionId,
540
+ goal: forcedLocalGoal,
541
+ taskProfile: "auto",
542
+ provider: "localllm",
543
+ model: "localllm-fast",
544
+ providerReadinessMode: "deterministic-test",
545
+ resume: true,
546
+ runtimePatch: providerRuntimePatch("localllm", "localllm-fast"),
547
+ expectedRuntimeRevision: providerSwitchSecondTurn.state.meta.runtimeConfig.revision,
548
+ responses: [
549
+ assistant("", [
550
+ toolCall("finish-provider-switch-localllm", "finish", {
551
+ result: "LOCALLLM_FORCED_RESUME_OK",
552
+ }),
553
+ ]),
554
+ ],
555
+ });
556
+ assert.equal(forcedLocalResume.result.stopped, undefined);
557
+ assert.equal(forcedLocalResume.result.result, "LOCALLLM_FORCED_RESUME_OK");
558
+ assert.equal(forcedLocalResume.calls.length, 1);
559
+ assert.deepEqual(forcedLocalResume.factoryConfigs, [{ provider: "localllm", model: "localllm-fast" }]);
560
+ assert.equal(forcedLocalResume.state.meta?.runtimeConfig?.provider, "localllm");
561
+ assert.equal(forcedLocalResume.state.meta?.runtimeConfig?.model, "localllm-fast");
562
+ assert.equal(
563
+ forcedLocalResume.events.filter(
564
+ (event) =>
565
+ event.type === "session.runtime_resolved" &&
566
+ event.data?.provider === "localllm" &&
567
+ event.data?.model === "localllm-fast"
568
+ ).length,
569
+ 1,
570
+ "explicit LocalLLM resume did not persist the forced provider/model switch"
571
+ );
572
+ assert(
573
+ !forcedLocalResume.events.some(
574
+ (event) =>
575
+ event.type === "completion.evidence_rejected" &&
576
+ /ledger is empty/i.test(String(event.data?.reason || ""))
577
+ ),
578
+ "pure LocalLLM resume was rejected for missing external evidence"
579
+ );
580
+
413
581
  const proseOnlyAction = await runCase({
414
582
  id: "prose-only-action",
415
583
  goal: "Run pwd and report the output.",
@@ -10257,6 +10257,7 @@ export async function documentQualityCommitAssessment(
10257
10257
  ? contract.exactOutputPaths
10258
10258
  : []),
10259
10259
  ...exactOutputPathsForState(state),
10260
+ ...currentGoalDocumentArtifactPathsForState(state),
10260
10261
  ...(Array.isArray(config.taskOwnedCommitPaths)
10261
10262
  ? config.taskOwnedCommitPaths
10262
10263
  : []),
@@ -14185,6 +14186,46 @@ function exactOutputPathsForState(state = {}) {
14185
14186
  ], exclusions).slice(0, 32);
14186
14187
  }
14187
14188
 
14189
+ function currentGoalDocumentArtifactPathsForState(state = {}) {
14190
+ const verification = state.meta?.projectVerification || {};
14191
+ const history = Array.isArray(verification.mutationHistory)
14192
+ ? verification.mutationHistory
14193
+ : [];
14194
+ const currentGoalRevision = Math.max(
14195
+ 0,
14196
+ Number(state.meta?.goalContract?.revision || 0)
14197
+ );
14198
+ if (currentGoalRevision <= 0) return [];
14199
+ const candidates = [];
14200
+ const seen = new Set();
14201
+ for (const mutation of history) {
14202
+ if (Number(mutation?.goalRevision || 0) !== currentGoalRevision) continue;
14203
+ const paths = [
14204
+ ...(Array.isArray(mutation?.paths) ? mutation.paths : []),
14205
+ ...(Array.isArray(mutation?.projectMutationPaths)
14206
+ ? mutation.projectMutationPaths
14207
+ : []),
14208
+ ...(Array.isArray(mutation?.verifiedGeneratedOutputPaths)
14209
+ ? mutation.verifiedGeneratedOutputPaths
14210
+ : []),
14211
+ ];
14212
+ for (const value of paths) {
14213
+ const candidate = safeTaskOwnedCommitPath(value);
14214
+ if (
14215
+ !candidate ||
14216
+ !/\.(?:docx|pdf)$/iu.test(candidate) ||
14217
+ seen.has(candidate)
14218
+ ) {
14219
+ continue;
14220
+ }
14221
+ seen.add(candidate);
14222
+ candidates.push(candidate);
14223
+ if (candidates.length >= 16) return candidates;
14224
+ }
14225
+ }
14226
+ return candidates;
14227
+ }
14228
+
14188
14229
  function exactInputPathsForState(state = {}) {
14189
14230
  const scsInputPaths = Array.isArray(state.meta?.scs?.taskContract?.exactInputPaths)
14190
14231
  ? state.meta.scs.taskContract.exactInputPaths.filter(Boolean)
@@ -21389,6 +21430,7 @@ async function completionEvidenceDecision({ config, state, store, observers, ste
21389
21430
  const documentExactOutputPaths = [
21390
21431
  ...(assessment.contract?.exactOutputPaths || []),
21391
21432
  ...exactOutputPathsForState(state),
21433
+ ...currentGoalDocumentArtifactPathsForState(state),
21392
21434
  ];
21393
21435
  const documentContractText = `${completionContractGoal(config, state)}\n${candidateResult}`;
21394
21436
  const documentDeliverableRequested =
@@ -2059,7 +2059,8 @@ export function deriveScsTaskContract({ goal = "", taskProfile = "", acceptanceC
2059
2059
  if (requiredGitActions.length && !requirementCategories.includes("git")) {
2060
2060
  requirementCategories.push("git");
2061
2061
  }
2062
- const requiresExternalEvidence = requirementCategories.length > 0 || requiredToolCalls.length > 0 || goalRequiresEvidence(evidenceGoal, taskProfile);
2062
+ const requiresExternalEvidence =
2063
+ requirementCategories.length > 0 || requiredToolCalls.length > 0 || goalRequiresEvidence(positiveEvidenceGoal, taskProfile);
2063
2064
  const requiredEvidence = requirementCategories.map((category) => ({
2064
2065
  id: category,
2065
2066
  category,