@lazyingart/agintiflow 0.20.301 → 0.20.302
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/README.md +1 -0
- package/docs/supervision-campaign-ledger.md +62 -0
- package/package.json +1 -1
- package/scripts/smoke-coding-tools.js +37 -0
- package/scripts/smoke-document-artifact-quality.js +293 -0
- package/scripts/smoke-dynamic-step-budget.js +1218 -0
- package/scripts/smoke-progressive-tool-selection.js +262 -0
- package/scripts/smoke-truthful-completion.js +22 -0
- package/scripts/smoke-writing-specialist-routing.js +92 -0
- package/src/agent-runner.js +1817 -61
- package/src/command-policy.js +4 -3
- package/src/document-artifact-quality.js +570 -10
- package/src/model-client.js +1 -1
- package/src/progressive-tool-selection.js +115 -16
- package/src/scs-evidence.js +5 -0
- package/src/step-budget-controller.js +38 -1
- package/src/task-profiles.js +1 -1
package/README.md
CHANGED
|
@@ -39,6 +39,7 @@ The two screenshots below match the current website hero: terminal-first launch
|
|
|
39
39
|
| Website | [https://flow.lazying.art](https://flow.lazying.art) |
|
|
40
40
|
| GitHub | [https://github.com/lazyingart/AgInTiFlow](https://github.com/lazyingart/AgInTiFlow) |
|
|
41
41
|
| npm | [https://www.npmjs.com/package/@lazyingart/agintiflow](https://www.npmjs.com/package/@lazyingart/agintiflow) |
|
|
42
|
+
| npm package chart | [https://npm.chart.dev/@lazyingart/agintiflow](https://npm.chart.dev/@lazyingart/agintiflow) |
|
|
42
43
|
| AAPS npm | [https://www.npmjs.com/package/@lazyingart/aaps](https://www.npmjs.com/package/@lazyingart/aaps) |
|
|
43
44
|
| Product positioning | [references/agintiflow-product-positioning.md](references/agintiflow-product-positioning.md) |
|
|
44
45
|
| Full archived README reference | [references/notes/readme-full-reference-2026-05-05.md](references/notes/readme-full-reference-2026-05-05.md) |
|
|
@@ -588,3 +588,65 @@ fallback derives a concise public answer from the successful bounded command
|
|
|
588
588
|
output. JSON status snapshots summarize visible delivered/retrying schedule
|
|
589
589
|
states, ingress reachability, queue counts, and top-level health while keeping
|
|
590
590
|
the original SCS checks for genuinely unfinished agent work.
|
|
591
|
+
|
|
592
|
+
### Retained tmux Git completion evidence
|
|
593
|
+
|
|
594
|
+
`labcanvas-mix2s-recovery-review-061` exposed a restart boundary in supervised
|
|
595
|
+
project work. The retained AgInTi session had sent a marked commit command to a
|
|
596
|
+
project-owned tmux pane, captured the canonical successful commit output, and
|
|
597
|
+
left the target worktree clean at `90d61bb`. SCS still rejected completion
|
|
598
|
+
after resume because the durable event ledger did not reconstruct that tmux
|
|
599
|
+
sequence as a consequential Git action.
|
|
600
|
+
|
|
601
|
+
AgInTiFlow now binds an explicit uppercase start marker and requested Git
|
|
602
|
+
action to the exact tmux target, accepts a commit only when the same pane later
|
|
603
|
+
contains canonical commit output plus a zero exit marker, and preserves the
|
|
604
|
+
tail of truncated captures. Restart reconciliation replays the ordered
|
|
605
|
+
`tool.started`, successful `tmux_send_keys`, and successful same-pane capture
|
|
606
|
+
events to recover goal- and mutation-revision-scoped evidence. Failed sends,
|
|
607
|
+
wrong panes, stale markers, and nonzero exits remain rejected.
|
|
608
|
+
|
|
609
|
+
The real retained session then finished without repeating the commit or any
|
|
610
|
+
external side effect. The complete npm suite, focused evidence regressions, and
|
|
611
|
+
registry installation passed. The fix is released as AgInTiFlow `0.20.301`.
|
|
612
|
+
|
|
613
|
+
### Context-complete document repair and semantic validation
|
|
614
|
+
|
|
615
|
+
Memo retests `082` through `084` exercised a retained LocalLLM document task
|
|
616
|
+
against a realistic mixed-language chat history. The task required a compiled
|
|
617
|
+
PDF, faithful current-state reconciliation, and a committed artifact. The
|
|
618
|
+
campaign exposed three independent AgInTiFlow runtime gaps rather than a
|
|
619
|
+
LabCanvas-specific routing problem.
|
|
620
|
+
|
|
621
|
+
In `082`, the TeX compiler repeatedly identified one exact line containing an
|
|
622
|
+
unescaped underscore. After several valid repairs, the provider ignored the
|
|
623
|
+
remaining narrow patch schema and attempted to replace the whole document.
|
|
624
|
+
AgInTiFlow now derives the mechanically safe replacement from the compiler
|
|
625
|
+
diagnostic and current source, constrains the trusted patch contract to that
|
|
626
|
+
replacement, and can recover an invalid broad model patch into the exact
|
|
627
|
+
evidence-derived edit. The recovery is limited to one active
|
|
628
|
+
revision-bound producer diagnostic and cannot generalize into an arbitrary
|
|
629
|
+
rewrite.
|
|
630
|
+
|
|
631
|
+
In `083`, the provider repaired a quality defect by deleting the remainder of
|
|
632
|
+
the TeX document. Patch anchoring now recognizes LaTeX section boundaries and
|
|
633
|
+
binds a defect to its containing section. A replacement is rejected when it
|
|
634
|
+
changes the net balance of revision-bound LaTeX environments, preventing a
|
|
635
|
+
local list repair from dropping later sections or environment terminators.
|
|
636
|
+
|
|
637
|
+
In `084`, the provider produced and committed a syntactically valid PDF that
|
|
638
|
+
described the document-generation process but omitted the substantive source
|
|
639
|
+
topics. Document validation now extracts bounded salient topic anchors from a
|
|
640
|
+
complete-context source and requires representative coverage when the request
|
|
641
|
+
explicitly asks for complete, reconciled, or context-complete output. It also
|
|
642
|
+
preserves unverified source status, rejects contradictory completion claims,
|
|
643
|
+
validates LaTeX source structure, and keeps decimal measurements such as
|
|
644
|
+
`0.25 mm` intact during status parsing.
|
|
645
|
+
|
|
646
|
+
The release also preserves requested artifact and project contracts across
|
|
647
|
+
compaction, replays failed requested PDF producers without repeating successful
|
|
648
|
+
side effects, retains exact compiler lines through bounded retries, rejects
|
|
649
|
+
append-only repairs when replacement is required, and caps repair schemas so a
|
|
650
|
+
small local model receives only the actionable tool surface. Focused coding,
|
|
651
|
+
truthful-completion, writing-routing, progressive-tool, document-quality, and
|
|
652
|
+
dynamic-budget suites pass. These fixes are prepared for AgInTiFlow `0.20.302`.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lazyingart/agintiflow",
|
|
3
|
-
"version": "0.20.
|
|
3
|
+
"version": "0.20.302",
|
|
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",
|
|
@@ -7,6 +7,7 @@ import { fileURLToPath } from "node:url";
|
|
|
7
7
|
import {
|
|
8
8
|
buildModelTimeoutRetryMessages,
|
|
9
9
|
genericArtifactFilenameBlock,
|
|
10
|
+
goalClearlyAllowsOverwrite,
|
|
10
11
|
modelTimeoutExhaustionRoute,
|
|
11
12
|
modelTimeoutRetryRoute,
|
|
12
13
|
normalizeNoMatchQueryResult,
|
|
@@ -917,6 +918,14 @@ try {
|
|
|
917
918
|
exactPdfHeaderAuditPolicy.writesWorkspace === false,
|
|
918
919
|
"a bounded workspace-local PDF header audit incorrectly required destructive host permission"
|
|
919
920
|
);
|
|
921
|
+
const pdfinfoPolicy = evaluateCommandPolicy("pdfinfo report.pdf", hostWorkspacePolicy);
|
|
922
|
+
assert(
|
|
923
|
+
pdfinfoPolicy.allowed &&
|
|
924
|
+
pdfinfoPolicy.category === "read-only" &&
|
|
925
|
+
pdfinfoPolicy.needsNetwork === false &&
|
|
926
|
+
pdfinfoPolicy.writesWorkspace === false,
|
|
927
|
+
"plain workspace-local pdfinfo inspection incorrectly required host permission"
|
|
928
|
+
);
|
|
920
929
|
for (const unsafeXxdCommand of [
|
|
921
930
|
"xxd -r - output.bin",
|
|
922
931
|
"xxd report.pdf output.hex",
|
|
@@ -1181,12 +1190,40 @@ try {
|
|
|
1181
1190
|
);
|
|
1182
1191
|
assert(pdflatexCompilePolicy.allowed, "workspace-local pdflatex compile should be allowed without package installs");
|
|
1183
1192
|
assert(pdflatexCompilePolicy.category === "toolchain", "workspace-local pdflatex compile should be classified as toolchain");
|
|
1193
|
+
for (const engine of ["xelatex", "lualatex"]) {
|
|
1194
|
+
const unicodeLatexCompilePolicy = evaluateCommandPolicy(
|
|
1195
|
+
`${engine} -interaction=nonstopmode -halt-on-error main.tex`,
|
|
1196
|
+
hostWorkspacePolicy
|
|
1197
|
+
);
|
|
1198
|
+
assert(unicodeLatexCompilePolicy.allowed, `workspace-local ${engine} compile should be allowed without destructive host access`);
|
|
1199
|
+
assert(unicodeLatexCompilePolicy.category === "toolchain", `workspace-local ${engine} compile should be classified as toolchain`);
|
|
1200
|
+
}
|
|
1184
1201
|
const latexmkCompilePolicy = evaluateCommandPolicy(
|
|
1185
1202
|
'cd profile-latex-20260506 && latexmk -pdf main.tex 2>&1; echo "LATEXMK_EXIT:$?"',
|
|
1186
1203
|
dockerWorkspaceNoInstallsPolicy
|
|
1187
1204
|
);
|
|
1188
1205
|
assert(latexmkCompilePolicy.allowed, "workspace-local latexmk compile should be allowed without package installs");
|
|
1189
1206
|
assert(latexmkCompilePolicy.category === "toolchain", "workspace-local latexmk compile should be classified as toolchain");
|
|
1207
|
+
const latexmkSynctexCompilePolicy = evaluateCommandPolicy(
|
|
1208
|
+
"latexmk -synctex=1 -interaction=nonstopmode main.tex",
|
|
1209
|
+
dockerWorkspaceNoInstallsPolicy
|
|
1210
|
+
);
|
|
1211
|
+
assert(
|
|
1212
|
+
latexmkSynctexCompilePolicy.allowed,
|
|
1213
|
+
"bounded workspace-local latexmk synctex compile should be allowed"
|
|
1214
|
+
);
|
|
1215
|
+
assert(
|
|
1216
|
+
latexmkSynctexCompilePolicy.category === "toolchain",
|
|
1217
|
+
"bounded workspace-local latexmk synctex compile should be classified as toolchain"
|
|
1218
|
+
);
|
|
1219
|
+
assert(
|
|
1220
|
+
goalClearlyAllowsOverwrite("Remove the unrelated article and produce the requested memo."),
|
|
1221
|
+
"explicit removal of a wrong artifact did not authorize its coherent replacement"
|
|
1222
|
+
);
|
|
1223
|
+
assert(
|
|
1224
|
+
goalClearlyAllowsOverwrite("删除错误文件后生成正确报告。"),
|
|
1225
|
+
"explicit Chinese deletion request did not authorize coherent replacement"
|
|
1226
|
+
);
|
|
1190
1227
|
const pythonUnittestPolicy = evaluateCommandPolicy(
|
|
1191
1228
|
"python3 -m unittest test_data_helper.py 2>&1",
|
|
1192
1229
|
dockerWorkspaceNoInstallsPolicy
|
|
@@ -1,11 +1,20 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
2
5
|
|
|
3
6
|
import {
|
|
7
|
+
collectDocumentSourceDocuments,
|
|
8
|
+
currentStateRequested,
|
|
9
|
+
documentArtifactVersioningDefect,
|
|
4
10
|
evaluateCurrentStateText,
|
|
5
11
|
evaluateDocumentConsistency,
|
|
6
12
|
evaluateExtractedDocumentText,
|
|
13
|
+
evaluateLatexSourceStructure,
|
|
7
14
|
evaluatePdfPageBalance,
|
|
8
15
|
evaluatePdfTextBounds,
|
|
16
|
+
evaluateSourceTopicCoverage,
|
|
17
|
+
evaluateUnverifiedSourceCompletionClaims,
|
|
9
18
|
extractSupersededLiterals,
|
|
10
19
|
} from "../src/document-artifact-quality.js";
|
|
11
20
|
import { evaluateSpreadsheetStructure } from "../src/spreadsheet-artifact-quality.js";
|
|
@@ -82,6 +91,121 @@ const currentDocument = evaluateCurrentStateText({
|
|
|
82
91
|
});
|
|
83
92
|
assert.equal(currentDocument.ok, true, "authoritative current-state prose was rejected");
|
|
84
93
|
|
|
94
|
+
const multilingualUnverifiedSource = [
|
|
95
|
+
"09:40 Lachlan: 我把一本显微成像的 PDF 发到群里了。请保存到 Downloads/Books,并同步 Nutstore 私人备份。",
|
|
96
|
+
"17:40 Lachlan: 今日真正完成:JLC 已付款;adapter 已验证;书籍归档请求已收到。书是否真的同步成功需要查证后才能写完成。",
|
|
97
|
+
].join("\n");
|
|
98
|
+
const unsupportedBookCompletion = evaluateUnverifiedSourceCompletionClaims({
|
|
99
|
+
sourceText: multilingualUnverifiedSource,
|
|
100
|
+
outputText: "Completed Today\nBook on microscopy archived to Downloads/Books and synced to Nutstore.",
|
|
101
|
+
});
|
|
102
|
+
assert.equal(unsupportedBookCompletion.ok, false, "an unverified cross-language backup was reported as completed");
|
|
103
|
+
assert(
|
|
104
|
+
unsupportedBookCompletion.defects.some((item) => item.code === "source-unverified-completion-claim"),
|
|
105
|
+
"an unsupported completion claim did not produce a precise source-status defect"
|
|
106
|
+
);
|
|
107
|
+
const preservedBookUncertainty = evaluateUnverifiedSourceCompletionClaims({
|
|
108
|
+
sourceText: multilingualUnverifiedSource,
|
|
109
|
+
outputText: "Pending\nVerify whether the microscopy book reached Nutstore; the archive request was received only.",
|
|
110
|
+
});
|
|
111
|
+
assert.equal(preservedBookUncertainty.ok, true, "a correctly preserved unverified source state was rejected");
|
|
112
|
+
|
|
113
|
+
const contextCompleteChatSource = [
|
|
114
|
+
"08:07 Lachlan: Instagram retry must reuse the existing LazyEdit output.",
|
|
115
|
+
"09:02 Sunny: Dataset publication permission remains unconfirmed.",
|
|
116
|
+
"09:40 Lachlan: Archive the microscopy PDF and verify the Nutstore backup.",
|
|
117
|
+
"10:15 Lachlan: Record the 5V 400mA PCB idea only; do not manufacture it.",
|
|
118
|
+
"14:04 Lachlan: The M10-to-M6 adapter is assembled and verified.",
|
|
119
|
+
"17:40 Lachlan: JLC is paid, but the book backup is still unverified.",
|
|
120
|
+
].join("\n");
|
|
121
|
+
const genericProcessOnlyReport = evaluateSourceTopicCoverage({
|
|
122
|
+
goal: "Read the complete chat history and produce a context-complete report.",
|
|
123
|
+
sourceText: contextCompleteChatSource,
|
|
124
|
+
outputText:
|
|
125
|
+
"This report read the source and created a structured LaTeX document. The PDF is ready for verification.",
|
|
126
|
+
});
|
|
127
|
+
assert.equal(
|
|
128
|
+
genericProcessOnlyReport.ok,
|
|
129
|
+
false,
|
|
130
|
+
"a process-only report passed despite omitting every salient source topic"
|
|
131
|
+
);
|
|
132
|
+
assert(
|
|
133
|
+
genericProcessOnlyReport.defects.some(
|
|
134
|
+
(item) => item.code === "source-topic-coverage-incomplete"
|
|
135
|
+
),
|
|
136
|
+
"missing source topic coverage did not produce a precise semantic defect"
|
|
137
|
+
);
|
|
138
|
+
const contextGroundedReport = evaluateSourceTopicCoverage({
|
|
139
|
+
goal: "Read the complete chat history and produce a context-complete report.",
|
|
140
|
+
sourceText: contextCompleteChatSource,
|
|
141
|
+
outputText: [
|
|
142
|
+
"Retry Instagram from the existing LazyEdit result only.",
|
|
143
|
+
"Wait for Sunny before dataset publication and verify the Nutstore book backup.",
|
|
144
|
+
"JLC is already paid and the M10-to-M6 adapter is verified.",
|
|
145
|
+
"Keep the 5V 400mA PCB item as a note; do not manufacture it.",
|
|
146
|
+
].join(" "),
|
|
147
|
+
});
|
|
148
|
+
assert.equal(
|
|
149
|
+
contextGroundedReport.ok,
|
|
150
|
+
true,
|
|
151
|
+
`a report grounded in the source topics was rejected: ${JSON.stringify(contextGroundedReport)}`
|
|
152
|
+
);
|
|
153
|
+
const contradictoryBookCompletion = evaluateUnverifiedSourceCompletionClaims({
|
|
154
|
+
sourceText: multilingualUnverifiedSource,
|
|
155
|
+
outputText: "Completed Today\nMicroscopy PDF archived to Downloads/Books and synced to Nutstore (verification pending).",
|
|
156
|
+
});
|
|
157
|
+
assert.equal(
|
|
158
|
+
contradictoryBookCompletion.ok,
|
|
159
|
+
false,
|
|
160
|
+
"a positive completion claim escaped validation merely by appending a pending-verification qualifier"
|
|
161
|
+
);
|
|
162
|
+
const unrelatedVerifiedCompletion = evaluateUnverifiedSourceCompletionClaims({
|
|
163
|
+
sourceText: multilingualUnverifiedSource,
|
|
164
|
+
outputText: "Completed Today\nJLC order paid and adapter verified.",
|
|
165
|
+
});
|
|
166
|
+
assert.equal(unrelatedVerifiedCompletion.ok, true, "a separately verified completion was tied to an unrelated source uncertainty");
|
|
167
|
+
const repeatedSpeakerDoesNotJoinStatuses = evaluateUnverifiedSourceCompletionClaims({
|
|
168
|
+
sourceText: [
|
|
169
|
+
"09:06 Lachlan: 在 Sunny 明确确认许可前不要上传数据。",
|
|
170
|
+
"10:43 Lachlan: 已支付的 JLC 订单不需要再操作。",
|
|
171
|
+
].join("\n"),
|
|
172
|
+
outputText: "Completed Today\nJLC order paid and confirmed.",
|
|
173
|
+
});
|
|
174
|
+
assert.equal(
|
|
175
|
+
repeatedSpeakerDoesNotJoinStatuses.ok,
|
|
176
|
+
true,
|
|
177
|
+
"transport sender labels connected an unresolved item to an unrelated verified completion"
|
|
178
|
+
);
|
|
179
|
+
const transitiveChatTermsDoNotJoinStatuses = evaluateUnverifiedSourceCompletionClaims({
|
|
180
|
+
sourceText: [
|
|
181
|
+
"09:06 Lachlan: 在 Sunny 明确确认许可前不要上传数据。",
|
|
182
|
+
"10:43 Lachlan: 已支付的 JLC 订单不需要再操作。",
|
|
183
|
+
"14:35 Lachlan: 每日 memo 要整理完成事项、等待事项和明确边界。",
|
|
184
|
+
"16:28 Lachlan: 数据上传任务状态是等待 Sunny,明天中午检查。",
|
|
185
|
+
].join("\n"),
|
|
186
|
+
outputText: "Daily Memo\nCompleted Today\nJLC order paid and confirmed.",
|
|
187
|
+
});
|
|
188
|
+
assert.equal(
|
|
189
|
+
transitiveChatTermsDoNotJoinStatuses.ok,
|
|
190
|
+
true,
|
|
191
|
+
"transitive chat vocabulary connected an unresolved dataset to an unrelated JLC completion"
|
|
192
|
+
);
|
|
193
|
+
const adjacentPronounCompletion = evaluateUnverifiedSourceCompletionClaims({
|
|
194
|
+
sourceText: [
|
|
195
|
+
"13:12 System: Instagram automation reports login required; no upload started.",
|
|
196
|
+
"13:18 Lachlan: 这是阻塞,不是失败。等登录恢复后只重试 Instagram。",
|
|
197
|
+
].join("\n"),
|
|
198
|
+
outputText: [
|
|
199
|
+
"Instagram upload is blocked by login; retry only after login is restored.",
|
|
200
|
+
"Do not re-publish the bird video on Instagram; it has already been posted.",
|
|
201
|
+
].join("\n"),
|
|
202
|
+
});
|
|
203
|
+
assert.equal(
|
|
204
|
+
adjacentPronounCompletion.ok,
|
|
205
|
+
false,
|
|
206
|
+
"an adjacent pronoun completion claim contradicted a source-verified login blocker"
|
|
207
|
+
);
|
|
208
|
+
|
|
85
209
|
const supersedesHistory = evaluateCurrentStateText({
|
|
86
210
|
sourceText: source,
|
|
87
211
|
outputText: "This document supersedes earlier planning notes. The current owner is Mei.",
|
|
@@ -117,6 +241,164 @@ const consistentActions = evaluateDocumentConsistency([
|
|
|
117
241
|
].join("\n"));
|
|
118
242
|
assert.equal(consistentActions.ok, true, "matching declared and section action counts were rejected");
|
|
119
243
|
|
|
244
|
+
const duplicatedProse = evaluateDocumentConsistency(
|
|
245
|
+
"Ideas: the concept of the concept of an instrument as agent."
|
|
246
|
+
);
|
|
247
|
+
assert.equal(duplicatedProse.ok, false, "an adjacent duplicated prose fragment was accepted");
|
|
248
|
+
assert(
|
|
249
|
+
duplicatedProse.defects.some((item) => item.code === "duplicated-prose-fragment"),
|
|
250
|
+
"an adjacent duplicated prose fragment did not produce its quality defect"
|
|
251
|
+
);
|
|
252
|
+
const duplicatedWrappedBullet = evaluateDocumentConsistency([
|
|
253
|
+
"Future Ideas",
|
|
254
|
+
"\u0088 Develop a conceptual article titled Turning Instruments into Collaborators, using case studies",
|
|
255
|
+
"from closed-loop microscopy, active spectroscopy, and autonomous experimentation.",
|
|
256
|
+
"\u0088 Explore another bounded idea for the roadmap and preserve it for later review.",
|
|
257
|
+
"\u0088 Develop a conceptual article titled Turning Instruments into Collaborators, using case studies",
|
|
258
|
+
"from closed-loop microscopy, active spectroscopy, and autonomous experimentation.",
|
|
259
|
+
].join("\n"));
|
|
260
|
+
assert.equal(
|
|
261
|
+
duplicatedWrappedBullet.ok,
|
|
262
|
+
false,
|
|
263
|
+
"a repeated non-adjacent wrapped PDF bullet was accepted"
|
|
264
|
+
);
|
|
265
|
+
assert(
|
|
266
|
+
duplicatedWrappedBullet.defects.some(
|
|
267
|
+
(item) => item.code === "duplicated-prose-fragment"
|
|
268
|
+
),
|
|
269
|
+
"a repeated wrapped PDF bullet did not produce its quality defect"
|
|
270
|
+
);
|
|
271
|
+
|
|
272
|
+
const unverifiedCompletedSection = evaluateDocumentConsistency([
|
|
273
|
+
"Completed Tasks",
|
|
274
|
+
"\u0088 A microscopy book was received and archived to the designated",
|
|
275
|
+
"local and cloud storage locations; verification is pending.",
|
|
276
|
+
"Pending Actions",
|
|
277
|
+
"\u0088 Verify the private backup evidence.",
|
|
278
|
+
].join("\n"));
|
|
279
|
+
assert.equal(
|
|
280
|
+
unverifiedCompletedSection.ok,
|
|
281
|
+
false,
|
|
282
|
+
"wrapped unverified work inside a Completed section was accepted"
|
|
283
|
+
);
|
|
284
|
+
assert(
|
|
285
|
+
unverifiedCompletedSection.defects.some(
|
|
286
|
+
(item) => item.code === "completed-section-contains-unverified-work"
|
|
287
|
+
),
|
|
288
|
+
"a wrapped completed/pending contradiction did not produce its section defect"
|
|
289
|
+
);
|
|
290
|
+
|
|
291
|
+
const contradictoryStatus = evaluateDocumentConsistency([
|
|
292
|
+
"Completed: JLC paid; book archive synchronized.",
|
|
293
|
+
"Pending: book archive evidence remains unverified.",
|
|
294
|
+
].join("\n"));
|
|
295
|
+
assert.equal(contradictoryStatus.ok, false, "completed and unverified status for one subject was accepted");
|
|
296
|
+
assert(
|
|
297
|
+
contradictoryStatus.defects.some((item) => item.code === "completed-unverified-status-conflict"),
|
|
298
|
+
"a completed/unverified status conflict did not produce its quality defect"
|
|
299
|
+
);
|
|
300
|
+
|
|
301
|
+
const separateStatusBullets = evaluateDocumentConsistency([
|
|
302
|
+
"Executive Summary",
|
|
303
|
+
"This report outlines the current work without assigning status.",
|
|
304
|
+
"- Completed: authentication tests passed.",
|
|
305
|
+
"- Pending: deployment verification is waiting.",
|
|
306
|
+
].join("\n"));
|
|
307
|
+
assert.equal(
|
|
308
|
+
separateStatusBullets.defects.some((item) => item.code === "completed-unverified-status-conflict"),
|
|
309
|
+
false,
|
|
310
|
+
"the status parser treated report preamble text as part of a wrapped status bullet"
|
|
311
|
+
);
|
|
312
|
+
|
|
313
|
+
const malformedLatexSource = evaluateLatexSourceStructure([
|
|
314
|
+
"\\documentclass{article}",
|
|
315
|
+
"\\begin{document}",
|
|
316
|
+
"Current report.",
|
|
317
|
+
"\\end{document}",
|
|
318
|
+
"Duplicated report suffix.",
|
|
319
|
+
"\\end{document}",
|
|
320
|
+
].join("\n"));
|
|
321
|
+
assert.equal(malformedLatexSource.ok, false, "duplicated LaTeX document suffix was accepted");
|
|
322
|
+
assert(
|
|
323
|
+
malformedLatexSource.defects.some((item) => item.code === "latex-content-after-document-end") &&
|
|
324
|
+
malformedLatexSource.defects.some((item) => item.code === "latex-duplicate-document-end"),
|
|
325
|
+
"malformed LaTeX source did not report both trailing content and duplicate document termination"
|
|
326
|
+
);
|
|
327
|
+
const validLatexSource = evaluateLatexSourceStructure([
|
|
328
|
+
"\\documentclass{article}",
|
|
329
|
+
"\\begin{document}",
|
|
330
|
+
"Current report with an escaped \\% sign.",
|
|
331
|
+
"\\end{document}",
|
|
332
|
+
"% \\end{document} in a comment is harmless.",
|
|
333
|
+
].join("\n"));
|
|
334
|
+
assert.equal(validLatexSource.ok, true, "valid LaTeX source with trailing comments was rejected");
|
|
335
|
+
const duplicateLatexDate = evaluateLatexSourceStructure([
|
|
336
|
+
"\\documentclass{article}",
|
|
337
|
+
"\\date{2026-08-23}",
|
|
338
|
+
"% \\date{ignored comment}",
|
|
339
|
+
"\\date{2026-09-01}",
|
|
340
|
+
"\\begin{document}",
|
|
341
|
+
"Current report.",
|
|
342
|
+
"\\end{document}",
|
|
343
|
+
].join("\n"));
|
|
344
|
+
assert.equal(duplicateLatexDate.ok, false, "duplicate active LaTeX date declarations were accepted");
|
|
345
|
+
assert(
|
|
346
|
+
duplicateLatexDate.defects.some(
|
|
347
|
+
(item) => item.code === "latex-duplicate-singleton-command" && item.command === "date"
|
|
348
|
+
),
|
|
349
|
+
"duplicate LaTeX date declarations did not produce a precise source defect"
|
|
350
|
+
);
|
|
351
|
+
|
|
352
|
+
assert(
|
|
353
|
+
currentStateRequested(
|
|
354
|
+
"Read the complete chat history and reconcile corrections into the current memo.",
|
|
355
|
+
"更正:巴黎视频今天不要发布,上面这句取消。"
|
|
356
|
+
),
|
|
357
|
+
"multilingual current-state correction language did not activate reconciliation checks"
|
|
358
|
+
);
|
|
359
|
+
|
|
360
|
+
const exactSourceRoot = await fs.mkdtemp(path.join(os.tmpdir(), "aginti-document-source-"));
|
|
361
|
+
try {
|
|
362
|
+
await fs.writeFile(
|
|
363
|
+
path.join(exactSourceRoot, "chat_history.md"),
|
|
364
|
+
"Later correction: keep the publication blocked until login is restored.\n",
|
|
365
|
+
"utf8"
|
|
366
|
+
);
|
|
367
|
+
const exactSources = await collectDocumentSourceDocuments(
|
|
368
|
+
exactSourceRoot,
|
|
369
|
+
["chat_history.md"]
|
|
370
|
+
);
|
|
371
|
+
assert.deepEqual(
|
|
372
|
+
exactSources.map((item) => item.path),
|
|
373
|
+
["chat_history.md"],
|
|
374
|
+
"an exact root-level source path was omitted from document validation"
|
|
375
|
+
);
|
|
376
|
+
} finally {
|
|
377
|
+
await fs.rm(exactSourceRoot, { recursive: true, force: true });
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
const versioningRoot = await fs.mkdtemp(path.join(os.tmpdir(), "aginti-document-versioning-"));
|
|
381
|
+
try {
|
|
382
|
+
const { execFile } = await import("node:child_process");
|
|
383
|
+
const { promisify } = await import("node:util");
|
|
384
|
+
const run = promisify(execFile);
|
|
385
|
+
await run("git", ["init", "-q"], { cwd: versioningRoot });
|
|
386
|
+
await fs.writeFile(path.join(versioningRoot, "report.pdf"), "fixture", "utf8");
|
|
387
|
+
assert.equal(
|
|
388
|
+
(await documentArtifactVersioningDefect(versioningRoot, "report.pdf"))?.code,
|
|
389
|
+
"document-artifact-not-versioned",
|
|
390
|
+
"an untracked final document artifact passed commit-aware validation"
|
|
391
|
+
);
|
|
392
|
+
await run("git", ["add", "--", "report.pdf"], { cwd: versioningRoot });
|
|
393
|
+
assert.equal(
|
|
394
|
+
await documentArtifactVersioningDefect(versioningRoot, "report.pdf"),
|
|
395
|
+
null,
|
|
396
|
+
"a staged document artifact was reported as unversioned"
|
|
397
|
+
);
|
|
398
|
+
} finally {
|
|
399
|
+
await fs.rm(versioningRoot, { recursive: true, force: true });
|
|
400
|
+
}
|
|
401
|
+
|
|
120
402
|
function page(
|
|
121
403
|
words,
|
|
122
404
|
{ height = 842, startY = 60, endY = 700, heading = "Section", wordHeight = 10 } = {}
|
|
@@ -169,6 +451,17 @@ assert.equal(corruptExtractedText.ok, false, "control and replacement glyphs wer
|
|
|
169
451
|
assert.deepEqual(corruptExtractedText.codePoints, [0x16, 0xfffd]);
|
|
170
452
|
assert.equal(corruptExtractedText.defects[0]?.code, "corrupt-extracted-text");
|
|
171
453
|
|
|
454
|
+
const latexBulletExtraction = evaluateExtractedDocumentText(
|
|
455
|
+
"Completed tasks\n\u0088 First item\n\u0088 Second item\n"
|
|
456
|
+
);
|
|
457
|
+
assert.equal(
|
|
458
|
+
latexBulletExtraction.ok,
|
|
459
|
+
true,
|
|
460
|
+
"the bounded pdflatex list-marker extraction glyph was treated as document corruption"
|
|
461
|
+
);
|
|
462
|
+
const embeddedControl = evaluateExtractedDocumentText("broken\u0088inside");
|
|
463
|
+
assert.equal(embeddedControl.ok, false, "an embedded C1 control byte was accepted as a list marker");
|
|
464
|
+
|
|
172
465
|
const boundedText = evaluatePdfTextBounds(`<doc>${page(20)}</doc>`);
|
|
173
466
|
assert.equal(boundedText.ok, true, "text inside readable margins was rejected");
|
|
174
467
|
|