@lazyingart/agintiflow 0.20.237 → 0.20.238

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.237",
3
+ "version": "0.20.238",
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",
@@ -3,7 +3,9 @@ import assert from "node:assert/strict";
3
3
  import {
4
4
  evaluateCurrentStateText,
5
5
  evaluateDocumentConsistency,
6
+ evaluateExtractedDocumentText,
6
7
  evaluatePdfPageBalance,
8
+ evaluatePdfTextBounds,
7
9
  extractSupersededLiterals,
8
10
  } from "../src/document-artifact-quality.js";
9
11
 
@@ -122,4 +124,21 @@ const readableOnePage = evaluatePdfPageBalance(
122
124
  );
123
125
  assert.equal(readableOnePage.ok, true, "readable one-page typography was rejected");
124
126
 
127
+ const cleanExtractedText = evaluateExtractedDocumentText("Page one\n\fPage two\tvalue\r\n");
128
+ assert.equal(cleanExtractedText.ok, true, "normal whitespace and PDF form feeds were rejected");
129
+
130
+ const corruptExtractedText = evaluateExtractedDocumentText("Reference detector \u0016 complete \ufffd");
131
+ assert.equal(corruptExtractedText.ok, false, "control and replacement glyphs were accepted");
132
+ assert.deepEqual(corruptExtractedText.codePoints, [0x16, 0xfffd]);
133
+ assert.equal(corruptExtractedText.defects[0]?.code, "corrupt-extracted-text");
134
+
135
+ const boundedText = evaluatePdfTextBounds(`<doc>${page(20)}</doc>`);
136
+ assert.equal(boundedText.ok, true, "text inside readable margins was rejected");
137
+
138
+ const clippedText = evaluatePdfTextBounds(
139
+ '<doc><page width="595" height="842"><word xMin="8" yMin="60" xMax="90" yMax="70">clipped</word></page></doc>'
140
+ );
141
+ assert.equal(clippedText.ok, false, "text outside readable margins was accepted");
142
+ assert.equal(clippedText.defects[0]?.code, "pdf-text-outside-readable-margin");
143
+
125
144
  console.log("document artifact quality smoke test passed");
@@ -29,7 +29,13 @@ import {
29
29
  shouldActivateScs,
30
30
  shouldRequestScsReplan,
31
31
  } from "../src/scs-controller.js";
32
- import { buildScsEvidenceLedger, deriveScsTaskContract, evaluateScsEvidence } from "../src/scs-evidence.js";
32
+ import {
33
+ buildScsEvidenceLedger,
34
+ deriveScsTaskContract,
35
+ evaluateScsEvidence,
36
+ gitActionsSatisfyContract,
37
+ inferSuccessfulGitActionsFromCommandResult,
38
+ } from "../src/scs-evidence.js";
33
39
  import { resolveRuntimeConfig } from "../src/config.js";
34
40
  import { classifyGoalIntent, isDirectAnswerIntent } from "../src/goal-intent.js";
35
41
  import { languageWriterDefaults } from "../src/writing-specialist.js";
@@ -922,6 +928,26 @@ assert(
922
928
  !committedEvaluation.missing.some((item) => item.category === "git"),
923
929
  "a successful git commit did not satisfy the explicit git requirement"
924
930
  );
931
+ const recoveredCommitActions = inferSuccessfulGitActionsFromCommandResult({
932
+ ok: true,
933
+ exitCode: 0,
934
+ args: {
935
+ command:
936
+ 'echo "seed author: $(git log -1 --format=\'%an <%ae>\')"; git config user.name "$(git log -1 --format=\'%an\')"; git commit -m "finish handoff" && git log -1 --oneline',
937
+ },
938
+ stdout: "[main abc1234] finish handoff\n 8 files changed, 499 insertions(+)",
939
+ });
940
+ assert(
941
+ recoveredCommitActions.length === 1 && recoveredCommitActions[0] === "commit",
942
+ "a canonical successful commit was lost because its command had a setup prefix or shell expansion"
943
+ );
944
+ assert(
945
+ gitActionsSatisfyContract(
946
+ { requiredGitActions: ["add", "commit"] },
947
+ recoveredCommitActions
948
+ ),
949
+ "a successful commit did not satisfy the implied staging step after a partially successful add/commit chain"
950
+ );
925
951
  const explainCodeContract = deriveScsTaskContract({
926
952
  goal: "Explain JavaScript closures at a high level.",
927
953
  taskProfile: "code",
@@ -20,3 +20,16 @@ tools:
20
20
  Prefer safe conversions through available tools such as `pandoc`, `libreoffice`, or Python libraries when installed. Preserve originals; write converted or edited outputs to a new file unless overwrite is explicit.
21
21
 
22
22
  If binary `.docx` content cannot be inspected directly, explain the needed converter and create a project-local script or setup note.
23
+
24
+ For a repository handoff or generated document project:
25
+
26
+ - synthesize the authoritative current state instead of narrating superseded values unless history was requested;
27
+ - keep editable source plus the requested reader format, normally DOCX and a phone-readable PDF;
28
+ - provide and document a conventional project-local `build.sh` entry point, even when it delegates to a Python or TeX implementation;
29
+ - make a clean-checkout build provision or clearly verify its dependencies rather than relying silently on the current shell;
30
+ - preserve source inputs byte-for-byte and keep session data, visual-inspection renders, LaTeX intermediates, caches, and other transient evidence ignored; prefer ignore rules over deleting evidence, and never couple cleanup commands to the build or validation command;
31
+ - validate extracted DOCX/PDF text for current facts, stale or private values, encoding damage, and missing action/evidence content;
32
+ - render every PDF page and inspect it for clipping, overlap, orphaned headings, sparse spill pages, readable margins, and phone-scale readability;
33
+ - when the task is in a git repository and requests a finished handoff, commit only intentional project files and finish with a clean worktree unless the user says not to commit.
34
+
35
+ Visual polish does not establish content quality. Cross-check names, dates, totals, counts, statuses, decisions, actions, risks, evidence, and limitations against the source material before completion.
@@ -30,6 +30,7 @@ const INTENTIONAL_SPARSE_PAGE_PATTERN =
30
30
  const HISTORICAL_TRANSITION_PATTERN =
31
31
  /\b(?:formerly|no longer|previously|replac(?:ed|ing)|supersed(?:e|ed|es|ing)|used to be)\b/i;
32
32
  const MIN_READABLE_MEDIAN_WORD_HEIGHT_PT = 8.8;
33
+ const MIN_READABLE_HORIZONTAL_MARGIN_PT = 18;
33
34
  const COUNT_WORDS = new Map([
34
35
  ["zero", 0], ["one", 1], ["two", 2], ["three", 3], ["four", 4], ["five", 5],
35
36
  ["six", 6], ["seven", 7], ["eight", 8], ["nine", 9], ["ten", 10],
@@ -239,6 +240,49 @@ export function evaluatePdfPageBalance(bboxXml = "") {
239
240
  };
240
241
  }
241
242
 
243
+ export function evaluateExtractedDocumentText(text = "") {
244
+ const unexpected = [...String(text || "").matchAll(/[\u0000-\u0008\u000b\u000e-\u001f\u007f-\u009f\ufffd]/gu)]
245
+ .map((match) => match[0].codePointAt(0))
246
+ .filter(Number.isInteger);
247
+ const codePoints = [...new Set(unexpected)].sort((a, b) => a - b);
248
+ const defects = [];
249
+ if (codePoints.length) {
250
+ defects.push({
251
+ code: "corrupt-extracted-text",
252
+ message: `The independently extracted reader text contains unexpected control or replacement glyphs: ${codePoints
253
+ .map((value) => `U+${value.toString(16).toUpperCase().padStart(4, "0")}`)
254
+ .join(", ")}. Repair the document encoding or generator instead of stripping these bytes only during validation.`,
255
+ });
256
+ }
257
+ return { ok: defects.length === 0, defects, codePoints };
258
+ }
259
+
260
+ export function evaluatePdfTextBounds(bboxXml = "", minimumMargin = MIN_READABLE_HORIZONTAL_MARGIN_PT) {
261
+ const pages = parsePdfBboxPages(bboxXml);
262
+ const defects = [];
263
+ for (const [index, page] of pages.entries()) {
264
+ if (!Number.isFinite(page.width) || page.width <= 0) continue;
265
+ const outside = page.words.filter((word) =>
266
+ word.text && (
267
+ !Number.isFinite(word.xMin) ||
268
+ !Number.isFinite(word.xMax) ||
269
+ word.xMin < minimumMargin ||
270
+ word.xMax > page.width - minimumMargin
271
+ )
272
+ );
273
+ if (!outside.length) continue;
274
+ const sample = outside
275
+ .slice(0, 4)
276
+ .map((word) => `${JSON.stringify(word.text)} at ${Number(word.xMin).toFixed(1)}..${Number(word.xMax).toFixed(1)} pt`)
277
+ .join("; ");
278
+ defects.push({
279
+ code: "pdf-text-outside-readable-margin",
280
+ message: `PDF page ${index + 1} places ${outside.length} text item${outside.length === 1 ? "" : "s"} outside the ${minimumMargin} pt horizontal readability margin (${sample}). Reflow the text or table instead of accepting clipping.`,
281
+ });
282
+ }
283
+ return { ok: pages.length > 0 && defects.length === 0, checked: pages.length > 0, defects };
284
+ }
285
+
242
286
  export function evaluateCurrentStateText({ sourceText = "", outputText = "", currentStateRequired = false } = {}) {
243
287
  const supersededLiterals = extractSupersededLiterals(sourceText);
244
288
  const presentSupersededLiterals = supersededLiterals.filter((literal) => containsLiteral(outputText, literal));
@@ -475,9 +519,11 @@ export async function validateWordDocumentArtifacts({
475
519
  try {
476
520
  if (artifact.extension === ".pdf") {
477
521
  const extracted = await extractPdf(artifact);
522
+ const textQuality = evaluateExtractedDocumentText(extracted.text);
478
523
  const semantic = evaluateCurrentStateText({ sourceText, outputText: extracted.text, currentStateRequired });
479
524
  const consistency = evaluateDocumentConsistency(extracted.text);
480
525
  const pageBalance = evaluatePdfPageBalance(extracted.bbox);
526
+ const textBounds = evaluatePdfTextBounds(extracted.bbox);
481
527
  if (!String(extracted.text || "").trim()) {
482
528
  defects.push({
483
529
  code: "empty-pdf-text",
@@ -493,8 +539,10 @@ export async function validateWordDocumentArtifacts({
493
539
  });
494
540
  }
495
541
  defects.push(...semantic.defects.map((item) => ({ ...item, path: artifact.path })));
542
+ defects.push(...textQuality.defects.map((item) => ({ ...item, path: artifact.path })));
496
543
  defects.push(...consistency.defects.map((item) => ({ ...item, path: artifact.path })));
497
544
  defects.push(...pageBalance.defects.map((item) => ({ ...item, path: artifact.path })));
545
+ defects.push(...textBounds.defects.map((item) => ({ ...item, path: artifact.path })));
498
546
  artifactReports.push({
499
547
  path: artifact.path,
500
548
  extension: artifact.extension,
@@ -506,6 +554,7 @@ export async function validateWordDocumentArtifacts({
506
554
  });
507
555
  } else {
508
556
  const text = await extractDocxText(artifact);
557
+ const textQuality = evaluateExtractedDocumentText(text);
509
558
  const semantic = evaluateCurrentStateText({ sourceText, outputText: text, currentStateRequired });
510
559
  const consistency = evaluateDocumentConsistency(text);
511
560
  if (!String(text || "").trim()) {
@@ -516,6 +565,7 @@ export async function validateWordDocumentArtifacts({
516
565
  });
517
566
  }
518
567
  defects.push(...semantic.defects.map((item) => ({ ...item, path: artifact.path })));
568
+ defects.push(...textQuality.defects.map((item) => ({ ...item, path: artifact.path })));
519
569
  defects.push(...consistency.defects.map((item) => ({ ...item, path: artifact.path })));
520
570
  artifactReports.push({
521
571
  path: artifact.path,
@@ -204,7 +204,20 @@ function missingRequiredGitActionSequence(required = [], observed = []) {
204
204
  let cursor = 0;
205
205
  for (let index = 0; index < expected.length; index += 1) {
206
206
  const observedIndex = actual.indexOf(expected[index], cursor);
207
- if (observedIndex < 0) return expected.slice(index);
207
+ if (observedIndex < 0) {
208
+ // A successful commit proves that an index was staged, even when the
209
+ // preceding `git add` happened in an earlier partially successful shell
210
+ // chain or the commit used `-a`. Preserve the requested add -> commit
211
+ // order without forcing the model to repeat a completed commit.
212
+ if (expected[index] === "add" && expected[index + 1] === "commit") {
213
+ const commitIndex = actual.indexOf("commit", cursor);
214
+ if (commitIndex >= 0) {
215
+ cursor = commitIndex;
216
+ continue;
217
+ }
218
+ }
219
+ return expected.slice(index);
220
+ }
208
221
  cursor = observedIndex + 1;
209
222
  }
210
223
  if (expected.at(-1) === "push") {
@@ -1070,7 +1083,20 @@ export function inferSuccessfulGitActionsFromCommandResult(payload = {}) {
1070
1083
  return inferGitActionsFromCommand(exitWrapper.command);
1071
1084
  }
1072
1085
  if (payload.ok === false || Number(payload.exitCode ?? 0) !== 0) return [];
1073
- return inferGitActionsFromCommand(command);
1086
+ const inferred = inferGitActionsFromCommand(command);
1087
+ if (inferred.length) return inferred;
1088
+
1089
+ // Shell expansion or a setup prefix can make the full command ambiguous to
1090
+ // the static parser. A canonical successful commit line plus an explicit
1091
+ // top-level `git commit` still provides concrete commit evidence.
1092
+ const output = String(payload.stdout || payload.result || "");
1093
+ if (
1094
+ /(?:^|[;&|]\s*)git\s+commit\b/i.test(command) &&
1095
+ /^\[[^\]\n]+\s+[0-9a-f]{7,}\]\s+\S+/mi.test(output)
1096
+ ) {
1097
+ return ["commit"];
1098
+ }
1099
+ return [];
1074
1100
  }
1075
1101
 
1076
1102
  function observedProjectCommandSatisfies(requiredCommand = "", item = {}) {