@lazyingart/agintiflow 0.20.235 → 0.20.236

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.235",
3
+ "version": "0.20.236",
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",
@@ -2,6 +2,7 @@ import assert from "node:assert/strict";
2
2
 
3
3
  import {
4
4
  evaluateCurrentStateText,
5
+ evaluateDocumentConsistency,
5
6
  evaluatePdfPageBalance,
6
7
  extractSupersededLiterals,
7
8
  } from "../src/document-artifact-quality.js";
@@ -42,6 +43,41 @@ const currentDocument = evaluateCurrentStateText({
42
43
  });
43
44
  assert.equal(currentDocument.ok, true, "authoritative current-state prose was rejected");
44
45
 
46
+ const supersedesHistory = evaluateCurrentStateText({
47
+ sourceText: source,
48
+ outputText: "This document supersedes earlier planning notes. The current owner is Mei.",
49
+ currentStateRequired: true,
50
+ });
51
+ assert.equal(supersedesHistory.ok, false, "present-tense supersedes history was accepted");
52
+ assert(
53
+ supersedesHistory.defects.some((item) => item.code === "historical-transition-prose"),
54
+ "present-tense supersedes did not produce a historical transition defect"
55
+ );
56
+
57
+ const inconsistentActions = evaluateDocumentConsistency([
58
+ "Three open actions remain.",
59
+ "Remaining Actions",
60
+ "Place filter order 28 August 2026",
61
+ "Provide wiring diagram 2 September 2026",
62
+ "Complete safety review 5 September 2026",
63
+ "Freeze checklist 10 September 2026",
64
+ "Budget",
65
+ ].join("\n"));
66
+ assert.equal(inconsistentActions.ok, false, "declared action count contradicted by the action section was accepted");
67
+ assert.equal(inconsistentActions.actionSectionItemCount, 4);
68
+ assert.equal(inconsistentActions.defects[0]?.code, "action-count-inconsistency");
69
+
70
+ const consistentActions = evaluateDocumentConsistency([
71
+ "Four open actions remain.",
72
+ "Remaining Actions",
73
+ "Place filter order 28 August 2026",
74
+ "Provide wiring diagram 2 September 2026",
75
+ "Complete safety review 5 September 2026",
76
+ "Freeze checklist 10 September 2026",
77
+ "Budget",
78
+ ].join("\n"));
79
+ assert.equal(consistentActions.ok, true, "matching declared and section action counts were rejected");
80
+
45
81
  function page(
46
82
  words,
47
83
  { height = 842, startY = 60, endY = 700, heading = "Section", wordHeight = 10 } = {}
@@ -28,8 +28,24 @@ const EXCLUDED_DIRECTORY_NAMES = new Set([
28
28
  const INTENTIONAL_SPARSE_PAGE_PATTERN =
29
29
  /^(?:appendix|approval|approvals|acknowledgements?|back cover|contact|notes|references|sign[- ]?off|signatures?)\b/i;
30
30
  const HISTORICAL_TRANSITION_PATTERN =
31
- /\b(?:formerly|no longer|previously|replac(?:ed|ing)|superseded|used to be)\b/i;
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 COUNT_WORDS = new Map([
34
+ ["zero", 0], ["one", 1], ["two", 2], ["three", 3], ["four", 4], ["five", 5],
35
+ ["six", 6], ["seven", 7], ["eight", 8], ["nine", 9], ["ten", 10],
36
+ ["eleven", 11], ["twelve", 12], ["thirteen", 13], ["fourteen", 14], ["fifteen", 15],
37
+ ["sixteen", 16], ["seventeen", 17], ["eighteen", 18], ["nineteen", 19], ["twenty", 20],
38
+ ]);
39
+ const ACTION_COUNT_PATTERN = new RegExp(
40
+ `\\b(\\d+|${[...COUNT_WORDS.keys()].join("|")})\\s+(?:open|remaining|outstanding|pending)\\s+(?:action items?|actions?|tasks?|items?)\\b`,
41
+ "gi"
42
+ );
43
+ const ACTION_SECTION_HEADING_PATTERN =
44
+ /^\s*(?:remaining|open|outstanding|pending)\s+(?:action items?|actions?|tasks?|items?|next steps)\s*$/i;
45
+ const DOCUMENT_SECTION_HEADING_PATTERN =
46
+ /^\s*(?:appendix|budget|current decisions?|executive summary|notes|references|risks?(?: and mitigations)?|summary)\s*$/i;
47
+ const HUMAN_DATE_PATTERN =
48
+ /\b(?:\d{4}-\d{2}-\d{2}|\d{1,2}\s+(?:jan(?:uary)?|feb(?:ruary)?|mar(?:ch)?|apr(?:il)?|may|jun(?:e)?|jul(?:y)?|aug(?:ust)?|sep(?:tember)?|oct(?:ober)?|nov(?:ember)?|dec(?:ember)?)(?:\s+\d{4})?|(?:jan(?:uary)?|feb(?:ruary)?|mar(?:ch)?|apr(?:il)?|may|jun(?:e)?|jul(?:y)?|aug(?:ust)?|sep(?:tember)?|oct(?:ober)?|nov(?:ember)?|dec(?:ember)?)\s+\d{1,2}(?:,\s*\d{4})?)\b/gi;
33
49
 
34
50
  function portablePath(value = "") {
35
51
  return String(value || "").replace(/\\/g, "/");
@@ -248,6 +264,47 @@ export function evaluateCurrentStateText({ sourceText = "", outputText = "", cur
248
264
  return { ok: defects.length === 0, defects, supersededLiterals, presentSupersededLiterals };
249
265
  }
250
266
 
267
+ function parsedCount(value = "") {
268
+ const normalized = String(value || "").trim().toLowerCase();
269
+ if (/^\d+$/.test(normalized)) return Number.parseInt(normalized, 10);
270
+ return COUNT_WORDS.get(normalized);
271
+ }
272
+
273
+ function actionSectionItemCount(outputText = "") {
274
+ const lines = String(outputText || "").split(/\r?\n/);
275
+ const headingIndex = lines.findIndex((line) => ACTION_SECTION_HEADING_PATTERN.test(line));
276
+ if (headingIndex < 0) return 0;
277
+ const section = [];
278
+ for (const line of lines.slice(headingIndex + 1)) {
279
+ if (DOCUMENT_SECTION_HEADING_PATTERN.test(line)) break;
280
+ section.push(line);
281
+ }
282
+ const sectionText = section.join("\n");
283
+ const dates = new Set(
284
+ [...sectionText.matchAll(HUMAN_DATE_PATTERN)].map((match) => normalizedComparableText(match[0]))
285
+ );
286
+ const bullets = section.filter((line) => /^\s*(?:[-*•]|\d+[.)])\s+\S/.test(line)).length;
287
+ return Math.max(dates.size, bullets);
288
+ }
289
+
290
+ export function evaluateDocumentConsistency(outputText = "") {
291
+ const defects = [];
292
+ const sectionCount = actionSectionItemCount(outputText);
293
+ if (sectionCount > 0) {
294
+ for (const match of String(outputText || "").matchAll(ACTION_COUNT_PATTERN)) {
295
+ const declaredCount = parsedCount(match[1]);
296
+ if (Number.isInteger(declaredCount) && declaredCount !== sectionCount) {
297
+ defects.push({
298
+ code: "action-count-inconsistency",
299
+ message: `The document declares ${declaredCount} open action${declaredCount === 1 ? "" : "s"}, but the Remaining Actions section contains ${sectionCount} dated/listed item${sectionCount === 1 ? "" : "s"}. Reconcile the summary and action table before delivery.`,
300
+ });
301
+ break;
302
+ }
303
+ }
304
+ }
305
+ return { ok: defects.length === 0, defects, actionSectionItemCount: sectionCount };
306
+ }
307
+
251
308
  async function collectSourceDocuments(commandCwd) {
252
309
  const documents = [];
253
310
  let totalBytes = 0;
@@ -419,6 +476,7 @@ export async function validateWordDocumentArtifacts({
419
476
  if (artifact.extension === ".pdf") {
420
477
  const extracted = await extractPdf(artifact);
421
478
  const semantic = evaluateCurrentStateText({ sourceText, outputText: extracted.text, currentStateRequired });
479
+ const consistency = evaluateDocumentConsistency(extracted.text);
422
480
  const pageBalance = evaluatePdfPageBalance(extracted.bbox);
423
481
  if (!String(extracted.text || "").trim()) {
424
482
  defects.push({
@@ -435,6 +493,7 @@ export async function validateWordDocumentArtifacts({
435
493
  });
436
494
  }
437
495
  defects.push(...semantic.defects.map((item) => ({ ...item, path: artifact.path })));
496
+ defects.push(...consistency.defects.map((item) => ({ ...item, path: artifact.path })));
438
497
  defects.push(...pageBalance.defects.map((item) => ({ ...item, path: artifact.path })));
439
498
  artifactReports.push({
440
499
  path: artifact.path,
@@ -442,11 +501,13 @@ export async function validateWordDocumentArtifacts({
442
501
  textChars: extracted.text.length,
443
502
  pageCount: pageBalance.pages.length,
444
503
  pages: pageBalance.pages,
504
+ actionSectionItemCount: consistency.actionSectionItemCount,
445
505
  supersededLiterals: semantic.supersededLiterals,
446
506
  });
447
507
  } else {
448
508
  const text = await extractDocxText(artifact);
449
509
  const semantic = evaluateCurrentStateText({ sourceText, outputText: text, currentStateRequired });
510
+ const consistency = evaluateDocumentConsistency(text);
450
511
  if (!String(text || "").trim()) {
451
512
  defects.push({
452
513
  code: "empty-docx-text",
@@ -455,10 +516,12 @@ export async function validateWordDocumentArtifacts({
455
516
  });
456
517
  }
457
518
  defects.push(...semantic.defects.map((item) => ({ ...item, path: artifact.path })));
519
+ defects.push(...consistency.defects.map((item) => ({ ...item, path: artifact.path })));
458
520
  artifactReports.push({
459
521
  path: artifact.path,
460
522
  extension: artifact.extension,
461
523
  textChars: text.length,
524
+ actionSectionItemCount: consistency.actionSectionItemCount,
462
525
  supersededLiterals: semantic.supersededLiterals,
463
526
  });
464
527
  }
@@ -276,7 +276,7 @@ export const TASK_PROFILES = {
276
276
  id: "word",
277
277
  label: "Word documents",
278
278
  prompt:
279
- "Bias toward Word/docx/document workflows while still using writing, conversion, LaTeX, or scripts when useful. Preserve source material byte-for-byte and synthesize a reader-facing document from the authoritative current facts instead of concatenating notes, logs, schemas, task IDs, private paths, or delivery instructions. Reconcile conflicting or superseded source facts before drafting: values labeled corrected, replaced, earlier, old, prior, cancelled, or no longer selected identify what to discard, and those discarded literals must not appear in the final document unless the user explicitly requests history or the history is necessary to explain a live decision. Keep the reconciliation history private: a current-state handoff should say who owns the work, which value is approved, and which option is selected, not narrate who was replaced or which preliminary value is no longer used. Before finishing, extract the final text and search for every superseded literal found in the sources. Prefer mature editable-document tooling already available in the workspace, such as python-docx, pandoc, or LibreOffice, over hand-written OOXML; when direct OOXML is genuinely necessary, validate its package parts and openability. Keep one maintainable source of truth and a reproducible project-local build command. Verify the DOCX is structurally editable, compile the PDF, run pdftotext or an equivalent extraction check that rejects replacement characters and unexpected control glyphs, and verify PDF text bounding boxes remain inside a readable page margin. Render every PDF page to a separate image under an ignored build/verification directory. Inspect one rendered page per read_image call, never batch pages into one vision call, and repair orphaned headings, near-empty spill pages, awkward table or paragraph breaks, overlaps, clipping, excessive whitespace, weak hierarchy, and inconsistent number formatting before finishing. Keep normal readable body type, line spacing, and margins; never shrink typography merely to force the document onto one page. When content spans pages, move or redistribute coherent sections so each page is useful instead of leaving a short spill page. Preserve an intentionally sparse appendix, approval, signature, reference, or back-matter page when it serves a real purpose. Retain ignored verification renders as evidence; optional cleanup must never block completion. Use clear descriptive filenames, exclude caches and generated debris from commits, inspect git status/diff before committing, and report success only after the editable source, reader-facing current-state content, visual layout, searchable text, and requested artifacts all pass.",
279
+ "Bias toward Word/docx/document workflows while still using writing, conversion, LaTeX, or scripts when useful. Preserve source material byte-for-byte and synthesize a reader-facing document from the authoritative current facts instead of concatenating notes, logs, schemas, task IDs, private paths, or delivery instructions. Reconcile conflicting or superseded source facts before drafting: values labeled corrected, replaced, earlier, old, prior, cancelled, or no longer selected identify what to discard, and those discarded literals must not appear in the final document unless the user explicitly requests history or the history is necessary to explain a live decision. Keep the reconciliation history private: a current-state handoff should say who owns the work, which value is approved, and which option is selected, not narrate who was replaced or which preliminary value is no longer used. Before finishing, extract the final text and search for every superseded literal found in the sources. Cross-check every quantitative statement against the detailed section it summarizes: counts of open actions, risks, decisions, line items, totals, dates, and owners must agree everywhere. Prefer mature editable-document tooling already available in the workspace, such as python-docx, pandoc, or LibreOffice, over hand-written OOXML; when direct OOXML is genuinely necessary, validate its package parts and openability. Keep one maintainable source of truth and a reproducible project-local build command. When reproducibility is requested, declare dependencies in project files and make the exact documented command work from a clean project shell; do not rely on inherited packages or claim that dependencies are already installed merely because the current agent container has them. Run that exact documented command, not only its inner generator. Verify the DOCX is structurally editable, compile the PDF, run pdftotext or an equivalent extraction check that rejects replacement characters and unexpected control glyphs, and verify PDF text bounding boxes remain inside a readable page margin. Render every PDF page to a separate image under an ignored build/verification directory. Inspect one rendered page per read_image call, never batch pages into one vision call, and repair orphaned headings, near-empty spill pages, awkward table or paragraph breaks, overlaps, clipping, excessive whitespace, weak hierarchy, and inconsistent number formatting before finishing. Keep normal readable body type, line spacing, and margins; never shrink typography merely to force the document onto one page. When content spans pages, move or redistribute coherent sections so each page is useful instead of leaving a short spill page. Preserve an intentionally sparse appendix, approval, signature, reference, or back-matter page when it serves a real purpose. Retain ignored verification renders as evidence; optional cleanup must never block completion. Use clear descriptive filenames, exclude caches and generated debris from commits, inspect git status/diff before committing, and honor any project instruction that requires an intentional clean commit even when the chat did not repeat it. Report success only after the editable source, reader-facing current-state content, internal consistency, visual layout, searchable text, exact documented build command, and requested artifacts all pass.",
280
280
  tools: ["files", "shell", "canvas", "sandbox"],
281
281
  },
282
282
  latex: {