@lazyingart/agintiflow 0.20.210 → 0.20.212

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.
@@ -75,3 +75,24 @@ The incident established two general contracts:
75
75
  canvas/download filename derived from the task title and source purpose.
76
76
  Internal collision identifiers are short suffixes, never the leading or only
77
77
  visible filename information.
78
+
79
+ ### QA repair continuity and evidence intent
80
+
81
+ `qa-incident-metrics-001` passed after two reusable runtime fixes. A normal,
82
+ underspecified QA prompt led the DeepSeek-backed agent to reproduce and diagnose
83
+ compound-duration parsing, percentile interpolation/mutation, and deterministic
84
+ summary-order defects. The first partial patch advanced the mutation revision,
85
+ but the runtime then forgot the retained failing test and prematurely reduced
86
+ the tool surface to test-only mode. Source-next restored the failed-test repair
87
+ state until a fresh current-revision test passed, allowing the same durable
88
+ session to finish the coherent patch, add regressions, run 15 tests at 100%
89
+ statement coverage, clean debris, and commit `667891f`.
90
+
91
+ Independent `pytest` and the hidden `qa_incident_metrics_contract.py` checker
92
+ both passed. The run also exposed an evidence-intent false positive: `figure
93
+ out` and `clean up generated test debris` were interpreted as a request for a
94
+ canvas artifact. Evidence inference now excludes those non-production phrases
95
+ while retaining the artifact gate for real generated figures. Exact session
96
+ evidence remains in
97
+ `~/.agintiflow/sessions/aginti-qa-incident-metrics-001/events.jsonl` and the
98
+ machine ledger records the run as `passed_after_fix`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lazyingart/agintiflow",
3
- "version": "0.20.210",
3
+ "version": "0.20.212",
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",
@@ -74,6 +74,12 @@ function cellWidth(value = "") {
74
74
  return [...String(value || "").replace(/\x1b\[[0-9;?]*[ -/]*[@-~]/g, "")].reduce((sum, char) => sum + charCellWidth(char), 0);
75
75
  }
76
76
 
77
+ function stripTerminalControls(value = "") {
78
+ return String(value || "")
79
+ .replace(/\x1b\][\s\S]*?(?:\x07|\x1b\\)/g, "")
80
+ .replace(/\x1b\[[0-9;?]*[ -/]*[@-~]/g, "");
81
+ }
82
+
77
83
  function runChat(inputText) {
78
84
  return runCli(
79
85
  ["chat", "--provider", "mock", "--routing", "manual", "--profile", "code", "--headless", "--sandbox-mode", "host"],
@@ -596,7 +602,7 @@ try {
596
602
  animated: false,
597
603
  webAppUrl: "http://127.0.0.1:3210",
598
604
  }).join("\n");
599
- if (!launchHeaderWithWeb.includes("webapp: http://127.0.0.1:3210")) {
605
+ if (!stripTerminalControls(launchHeaderWithWeb).includes("webapp: http://127.0.0.1:3210")) {
600
606
  throw new Error("launch header did not render the active webapp URL in the tagline row");
601
607
  }
602
608
  const launchHeaderWithWebError = buildLaunchHeaderLines({
@@ -749,17 +755,21 @@ try {
749
755
  throw new Error("terminal prompt layout did not render live input queue and cwd footer");
750
756
  }
751
757
  const hintPromptLayout = buildPromptLayout("/mo", 3, 90, 24, { suggestions: ["/models", "/model"], suggestionIndex: 1 });
752
- const hintText = hintPromptLayout.renderedRows
753
- .map((line) => line.replace(/\x1b\[[0-9;?]*[ -/]*[@-~]/g, ""))
754
- .join("\n");
755
- if (!hintText.includes(" user> /mo") || !hintText.includes("hint /models >/model")) {
756
- throw new Error("terminal prompt layout did not align user and hint text columns");
758
+ const hintRaw = hintPromptLayout.renderedRows.join("\n");
759
+ const hintText = stripTerminalControls(hintRaw);
760
+ const selectedHintRendered =
761
+ hintText.includes("hint /models >/model") ||
762
+ (hintText.includes("hint /models /model") && hintRaw.includes("\x1b[1m/model\x1b[0m"));
763
+ if (!hintText.includes(" user> /mo") || !selectedHintRendered) {
764
+ throw new Error(`terminal prompt layout did not align user and hint text columns: ${JSON.stringify(hintText)}`);
757
765
  }
758
766
  const exactHintLayout = buildPromptLayout("/model", 6, 90, 24);
759
- const exactHintText = exactHintLayout.renderedRows
760
- .map((line) => line.replace(/\x1b\[[0-9;?]*[ -/]*[@-~]/g, ""))
761
- .join("\n");
762
- if (!exactHintText.includes("hint >/model") || exactHintText.includes("/models")) {
767
+ const exactHintRaw = exactHintLayout.renderedRows.join("\n");
768
+ const exactHintText = stripTerminalControls(exactHintRaw);
769
+ const exactHintSelected =
770
+ exactHintText.includes("hint >/model") ||
771
+ (exactHintText.includes("hint /model") && exactHintRaw.includes("\x1b[1m/model\x1b[0m"));
772
+ if (!exactHintSelected || exactHintText.includes("/models")) {
763
773
  throw new Error("exact slash commands should not show broader prefix matches");
764
774
  }
765
775
  if (classifyEscapeAction({ active: false }) !== "noop") {
@@ -52,6 +52,17 @@ const artifactContract = deriveScsTaskContract({
52
52
  });
53
53
  assert.equal(artifactContract.requiresExternalEvidence, true, "real chat artifact work lost its evidence gate");
54
54
  assert.ok(artifactContract.requiredEvidence.some((item) => item.category === "artifact"));
55
+ const scopedArtifactRootContract = deriveScsTaskContract({
56
+ goal:
57
+ 'AGINTI_EVIDENCE_SCOPE_JSON: {"mode":"task","request":"Create result.txt with the exact requested content.","artifact_root":"/tmp/labcanvas-task-artifacts"}',
58
+ taskProfile: "chatops",
59
+ });
60
+ assert.equal(scopedArtifactRootContract.artifactRoot, "/tmp/labcanvas-task-artifacts");
61
+ assert.deepEqual(
62
+ scopedArtifactRootContract.exactOutputPaths,
63
+ ["/tmp/labcanvas-task-artifacts/result.txt"],
64
+ "a bare task artifact filename was not resolved against the host-declared artifact root"
65
+ );
55
66
 
56
67
  let capturedPayload = null;
57
68
  const client = {
@@ -986,6 +986,14 @@ const generatedReviewContract = deriveScsTaskContract({
986
986
  ].join("\n"),
987
987
  taskProfile: "review",
988
988
  });
989
+ const qaCleanupContract = deriveScsTaskContract({
990
+ goal: "Inspect the project, figure out what is actually wrong, fix the failing tests, clean up generated test debris, and commit only the intentional fix.",
991
+ taskProfile: "qa",
992
+ });
993
+ const generatedFigureContract = deriveScsTaskContract({
994
+ goal: "Generate a publication-ready figure that compares the two repair strategies.",
995
+ taskProfile: "design",
996
+ });
989
997
  assert(
990
998
  !explainCodeContract.requiresExternalEvidence,
991
999
  "code profile alone should not force external evidence for a pure explanation"
@@ -1037,6 +1045,16 @@ assert(
1037
1045
  !generatedReviewContract.requiredEvidence.some((item) => item.category === "artifact"),
1038
1046
  "review profile should not turn review-format boilerplate into file/artifact production requirements"
1039
1047
  );
1048
+ assert(
1049
+ qaCleanupContract.requiredEvidence.some((item) => item.category === "command") &&
1050
+ qaCleanupContract.requiredEvidence.some((item) => item.category === "git") &&
1051
+ !qaCleanupContract.requiredEvidence.some((item) => item.category === "artifact"),
1052
+ "cleaning generated test debris should not invent an unrelated artifact-delivery requirement"
1053
+ );
1054
+ assert(
1055
+ generatedFigureContract.requiredEvidence.some((item) => item.category === "artifact"),
1056
+ "tightening QA intent phrases removed the real generated-figure artifact gate"
1057
+ );
1040
1058
  const fileOnlyLedger = buildScsEvidenceLedger({
1041
1059
  context: { events: [{ type: "file.changed", data: { path: "src/app.js" } }] },
1042
1060
  });
@@ -725,6 +725,47 @@ const constrainedInstructionWrite = failedTestRepairWithRequiredInstruction.find
725
725
  assertStrict.deepEqual(constrainedInstructionWrite.function.parameters.properties.path.enum, ["AGINTI.md"]);
726
726
  assertStrict.deepEqual(constrainedInstructionWrite.function.parameters.properties.mode.enum, ["create"]);
727
727
 
728
+ const continuedFailedTestRepairRuntime = nextStepRuntimeConfig(
729
+ { provider: "localllm", taskProfile: "qa" },
730
+ {
731
+ meta: {
732
+ projectVerification: {
733
+ mutationRevision: 1,
734
+ discoveredTests: ["python -m pytest -q"],
735
+ requiredOutputs: [],
736
+ testRuns: [
737
+ {
738
+ command: "python -m pytest -q",
739
+ mutationRevision: 0,
740
+ passed: false,
741
+ failureSignature: "baseline-failure",
742
+ },
743
+ ],
744
+ },
745
+ },
746
+ messages: [],
747
+ }
748
+ );
749
+ assertStrict.equal(
750
+ continuedFailedTestRepairRuntime.testFailureRepairActive,
751
+ true,
752
+ "a partial repair forgot the retained failed test after the mutation revision advanced"
753
+ );
754
+ assertStrict.equal(
755
+ continuedFailedTestRepairRuntime.testFailureCommand,
756
+ "python -m pytest -q",
757
+ "a partial repair lost the retained failing test command"
758
+ );
759
+ sameNames(
760
+ selectProgressiveTools(allTools, {
761
+ config: continuedFailedTestRepairRuntime,
762
+ goal: "Continue the coherent repair, then retest.",
763
+ profile: "qa",
764
+ }),
765
+ ["read_file", "search_files", "apply_patch", "run_command", "finish"],
766
+ "a partial repair was forced into test-only mode before the coherent patch was complete"
767
+ );
768
+
728
769
  const pendingTestTools = selectProgressiveTools(allTools, {
729
770
  config: {
730
771
  provider: "localllm",
@@ -4239,13 +4239,23 @@ export function nextStepRuntimeConfig(config = {}, state = {}) {
4239
4239
  };
4240
4240
  const verification = state.meta?.projectVerification || {};
4241
4241
  const mutationRevision = Number(verification.mutationRevision || 0);
4242
- const latestCurrentTest = [...(verification.testRuns || [])]
4242
+ const testRuns = Array.isArray(verification.testRuns) ? verification.testRuns : [];
4243
+ const latestCurrentTest = [...testRuns]
4243
4244
  .reverse()
4244
4245
  .find((run) => Number(run.mutationRevision || 0) === mutationRevision);
4245
- if (latestCurrentTest && latestCurrentTest.passed !== true) {
4246
+ const latestRecordedTest = [...testRuns]
4247
+ .reverse()
4248
+ .find((run) => String(run?.command || "").trim());
4249
+ const retainedFailedTest =
4250
+ latestCurrentTest && latestCurrentTest.passed !== true
4251
+ ? latestCurrentTest
4252
+ : !latestCurrentTest && latestRecordedTest?.passed === false
4253
+ ? latestRecordedTest
4254
+ : null;
4255
+ if (retainedFailedTest) {
4246
4256
  runtimeConfig.testFailureRepairActive = true;
4247
- runtimeConfig.testFailureCommand = String(latestCurrentTest.command || "");
4248
- runtimeConfig.testFailureSignature = String(latestCurrentTest.failureSignature || "");
4257
+ runtimeConfig.testFailureCommand = String(retainedFailedTest.command || "");
4258
+ runtimeConfig.testFailureSignature = String(retainedFailedTest.failureSignature || "");
4249
4259
  const completedOutputs = new Set(
4250
4260
  (state.meta?.artifactProgress?.completed || [])
4251
4261
  .map((item) => String(item || "").replace(/\\/g, "/").replace(/^\.\//, ""))
@@ -4256,10 +4266,7 @@ export function nextStepRuntimeConfig(config = {}, state = {}) {
4256
4266
  .filter((item) => /(?:^|\/)(?:AGINTI|AGENTS)\.md$/i.test(item))
4257
4267
  .slice(0, 8);
4258
4268
  } else if (!latestCurrentTest && mutationRevision > 0 && (verification.discoveredTests || []).length) {
4259
- const retainedTestCommand = [...(verification.testRuns || [])]
4260
- .reverse()
4261
- .map((run) => String(run.command || ""))
4262
- .find(Boolean);
4269
+ const retainedTestCommand = String(latestRecordedTest?.command || "");
4263
4270
  if (retainedTestCommand) {
4264
4271
  runtimeConfig.testVerificationPending = true;
4265
4272
  runtimeConfig.testVerificationCommand = retainedTestCommand;
@@ -544,6 +544,12 @@ function requiresSourceGrounding(goal = "") {
544
544
  function inferRequirementCategories(goal = "", taskProfile = "", acceptanceCriteria = []) {
545
545
  const positiveGoal = stripForbiddenLanguage(goal);
546
546
  const text = normalizedText(positiveGoal);
547
+ const artifactSignalText = text
548
+ .replace(
549
+ /\b(?:clean(?:\s+up)?|remove|delete|clear|purge)\b[^.\n;]{0,120}\b(?:generated|temporary|stale|test)?\s*(?:test\s+)?(?:debris|caches?|byproducts?)\b/gi,
550
+ ""
551
+ )
552
+ .replace(/\bfigure\s+out\b/gi, "");
547
553
  const profile = String(taskProfile || "").toLowerCase();
548
554
  const categories = new Set(
549
555
  goalRequiresEvidence(positiveGoal, "") ? profileRequirementsForGoal(taskProfile, positiveGoal) : []
@@ -571,7 +577,7 @@ function inferRequirementCategories(goal = "", taskProfile = "", acceptanceCrite
571
577
  if (directCommandSignal || (validationSignal && codeProfileRequiresCommand(positiveGoal))) {
572
578
  categories.add("command");
573
579
  }
574
- if (textHas(text, /\b(artifact|canvas|pdf|image|video|screenshot|cover|plot|chart|figure|docx|archive|copy to|export|generated|generate|draft)\b/) || /输出|产物|图片|视频|截图|封面|生成/.test(text)) {
580
+ if (textHas(artifactSignalText, /\b(artifact|canvas|pdf|image|video|screenshot|cover|plot|chart|figure|docx|archive|copy to|export|generated|generate|draft)\b/) || /输出|产物|图片|视频|截图|封面|生成/.test(artifactSignalText)) {
575
581
  categories.add("artifact");
576
582
  }
577
583
  if (textHas(text, /\b(browser|chrome|chromium|cdp|devtools|playwright|selenium|web[- ]?ui|website|page|tab|composer|click|type|upload|attach|submit|form)\b/) || /浏览器|网页|页面|上传|提交|附件|资产库/.test(text)) {
@@ -670,13 +676,27 @@ function stripForbiddenLanguage(goal = "") {
670
676
  .replace(/禁止([^。\n;]+)/g, "");
671
677
  }
672
678
 
679
+ function parseAgintiEvidenceScope(goal = "") {
680
+ const matches = [
681
+ ...String(goal || "").matchAll(/^AGINTI_EVIDENCE_SCOPE_JSON:\s*(\{[^\n]+\})\s*$/gm),
682
+ ];
683
+ const match = matches.at(-1);
684
+ if (!match) return null;
685
+ try {
686
+ const payload = JSON.parse(match[1]);
687
+ return payload && typeof payload === "object" ? payload : null;
688
+ } catch {
689
+ return null;
690
+ }
691
+ }
692
+
673
693
  export function hasAgintiEvidenceScope(goal = "") {
674
- return /^AGINTI_EVIDENCE_SCOPE_JSON:\s*\{[^\n]+\}\s*$/m.test(String(goal || ""));
694
+ return Boolean(parseAgintiEvidenceScope(goal));
675
695
  }
676
696
 
677
697
  export function scopedChatopsEvidenceGoal(goal = "", taskProfile = "") {
678
- const match = String(goal || "").match(/^AGINTI_EVIDENCE_SCOPE_JSON:\s*(\{[^\n]+\})\s*$/m);
679
- if (!match) {
698
+ const payload = parseAgintiEvidenceScope(goal);
699
+ if (!payload) {
680
700
  const text = String(goal || "");
681
701
  const lines = text
682
702
  .split(/\r?\n/)
@@ -694,22 +714,32 @@ export function scopedChatopsEvidenceGoal(goal = "", taskProfile = "") {
694
714
  }
695
715
  return text;
696
716
  }
697
- try {
698
- const payload = JSON.parse(match[1]);
699
- if (!payload || typeof payload !== "object") return String(goal || "");
700
- const mode = String(payload.mode || "").trim().toLowerCase();
701
- if (["chat-response", "host-managed-response", "plan-response", "read-only-answer"].includes(mode)) {
702
- return "Answer the current chat turn directly without external execution.";
703
- }
704
- const request = String(payload.request || "").trim();
705
- return request || String(goal || "");
706
- } catch {
707
- return String(goal || "");
717
+ const mode = String(payload.mode || "").trim().toLowerCase();
718
+ if (["chat-response", "host-managed-response", "plan-response", "read-only-answer"].includes(mode)) {
719
+ return "Answer the current chat turn directly without external execution.";
708
720
  }
721
+ const request = String(payload.request || "").trim();
722
+ return request || String(goal || "");
723
+ }
724
+
725
+ function scopedArtifactRoot(goal = "") {
726
+ const payload = parseAgintiEvidenceScope(goal);
727
+ if (!payload || String(payload.mode || "").trim().toLowerCase() !== "task") return "";
728
+ return String(payload.artifact_root || "").trim();
729
+ }
730
+
731
+ function applyScopedArtifactRoot(items = [], artifactRoot = "") {
732
+ if (!artifactRoot) return items;
733
+ return items.map((item) => {
734
+ const value = String(item || "").trim();
735
+ if (!value || path.isAbsolute(value) || value.startsWith("~/") || /[\\/]/.test(value)) return value;
736
+ return path.join(artifactRoot, value);
737
+ });
709
738
  }
710
739
 
711
740
  export function deriveScsTaskContract({ goal = "", taskProfile = "", acceptanceCriteria = [] } = {}) {
712
741
  const evidenceGoal = scopedChatopsEvidenceGoal(goal, taskProfile);
742
+ const artifactRoot = scopedArtifactRoot(goal);
713
743
  const requirementCategories = inferRequirementCategories(evidenceGoal, taskProfile, acceptanceCriteria);
714
744
  const requiredToolCalls = inferRequiredToolCalls(evidenceGoal);
715
745
  const requiresExternalEvidence = requirementCategories.length > 0 || requiredToolCalls.length > 0 || goalRequiresEvidence(evidenceGoal, taskProfile);
@@ -718,8 +748,11 @@ export function deriveScsTaskContract({ goal = "", taskProfile = "", acceptanceC
718
748
  category,
719
749
  description: CATEGORY_LABELS[category] || category,
720
750
  }));
721
- const exactOutputPaths = inferExactOutputPaths(evidenceGoal);
722
- const exactInputPaths = inferExactInputPaths(evidenceGoal).filter((item) => !exactOutputPaths.includes(item));
751
+ const inferredOutputPaths = inferExactOutputPaths(evidenceGoal);
752
+ const exactOutputPaths = applyScopedArtifactRoot(inferredOutputPaths, artifactRoot);
753
+ const exactInputPaths = inferExactInputPaths(evidenceGoal).filter(
754
+ (item) => !inferredOutputPaths.includes(item) && !exactOutputPaths.includes(item)
755
+ );
723
756
  const declaredSourceRoots = inferDeclaredSourceRoots(evidenceGoal);
724
757
  return {
725
758
  version: 1,
@@ -729,6 +762,7 @@ export function deriveScsTaskContract({ goal = "", taskProfile = "", acceptanceC
729
762
  requiredEvidence,
730
763
  forbiddenActions: inferForbiddenActions(evidenceGoal),
731
764
  exactOutputPaths,
765
+ artifactRoot,
732
766
  exactInputPaths,
733
767
  declaredSourceRoots,
734
768
  readOnlyReadiness: isReadOnlyReadinessTask(evidenceGoal),
@@ -786,11 +820,12 @@ export function augmentScsTaskContractWithProjectVerification(contract = {}, sta
786
820
  .map((item) => String(item?.path || item || "").trim())
787
821
  .filter(Boolean)
788
822
  ).slice(0, 80);
789
- const requiredOutputs = unique(
823
+ const requiredOutputs = unique(applyScopedArtifactRoot(
790
824
  (Array.isArray(verification.requiredOutputs) ? verification.requiredOutputs : [])
791
825
  .map((item) => String(item || "").trim())
792
- .filter(Boolean)
793
- ).slice(0, 64);
826
+ .filter(Boolean),
827
+ String(contract.artifactRoot || "")
828
+ )).slice(0, 64);
794
829
  const requiredProjectCommands = unique(
795
830
  (Array.isArray(verification.requiredCommands) ? verification.requiredCommands : [])
796
831
  .map(normalizeProjectCommand)