paperlint 7.1.0 → 8.0.0
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/.github/workflows/ci.yml +3 -0
- package/.github/workflows/deadlines.yml +100 -0
- package/README.md +6 -0
- package/dist/adapters/hotcrp/deadlines.d.ts +8 -0
- package/dist/adapters/hotcrp/deadlines.d.ts.map +1 -0
- package/dist/adapters/hotcrp/deadlines.io.d.ts +6 -0
- package/dist/adapters/hotcrp/deadlines.io.d.ts.map +1 -0
- package/dist/adapters/hotcrp/deadlines.io.js +26 -0
- package/dist/adapters/hotcrp/deadlines.io.js.map +1 -0
- package/dist/adapters/hotcrp/deadlines.js +134 -0
- package/dist/adapters/hotcrp/deadlines.js.map +1 -0
- package/dist/adapters/hotcrp/index.d.ts +2 -0
- package/dist/adapters/hotcrp/index.d.ts.map +1 -1
- package/dist/adapters/hotcrp/index.js +2 -0
- package/dist/adapters/hotcrp/index.js.map +1 -1
- package/dist/build.d.ts.map +1 -1
- package/dist/build.js +1 -6
- package/dist/build.js.map +1 -1
- package/dist/cli.d.ts +3 -4
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +53 -47
- package/dist/cli.js.map +1 -1
- package/dist/cycle-rules.d.ts +24 -0
- package/dist/cycle-rules.d.ts.map +1 -0
- package/dist/cycle-rules.js +212 -0
- package/dist/cycle-rules.js.map +1 -0
- package/dist/domain/cycle.d.ts +148 -0
- package/dist/domain/cycle.d.ts.map +1 -0
- package/dist/domain/cycle.js +281 -0
- package/dist/domain/cycle.js.map +1 -0
- package/dist/domain/deadline.d.ts +128 -0
- package/dist/domain/deadline.d.ts.map +1 -0
- package/dist/domain/deadline.js +281 -0
- package/dist/domain/deadline.js.map +1 -0
- package/dist/eslint-rules/cycle-stage.d.ts +39 -0
- package/dist/eslint-rules/cycle-stage.d.ts.map +1 -0
- package/dist/eslint-rules/cycle-stage.js +119 -0
- package/dist/eslint-rules/cycle-stage.js.map +1 -0
- package/dist/eslint-rules/latex-language.d.ts.map +1 -1
- package/dist/eslint-rules/latex-language.js +10 -13
- package/dist/eslint-rules/latex-language.js.map +1 -1
- package/dist/eslint-rules/paper-context.js +2 -2
- package/dist/eslint-rules/paper-typography.d.ts.map +1 -1
- package/dist/eslint-rules/paper-typography.js +22 -91
- package/dist/eslint-rules/paper-typography.js.map +1 -1
- package/dist/eslint-rules/papers.d.ts +1 -3
- package/dist/eslint-rules/papers.d.ts.map +1 -1
- package/dist/eslint-rules/papers.js +2 -4
- package/dist/eslint-rules/papers.js.map +1 -1
- package/dist/eslint-rules/tex-build.d.ts.map +1 -1
- package/dist/eslint-rules/tex-build.js +8 -25
- package/dist/eslint-rules/tex-build.js.map +1 -1
- package/dist/init.d.ts +1 -4
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +3 -9
- package/dist/init.js.map +1 -1
- package/dist/lib/markdown.d.ts +5 -81
- package/dist/lib/markdown.d.ts.map +1 -1
- package/dist/lib/markdown.js +5 -116
- package/dist/lib/markdown.js.map +1 -1
- package/dist/lib/paper-config.d.ts.map +1 -1
- package/dist/lib/paper-config.js +2 -2
- package/dist/lib/paper-config.js.map +1 -1
- package/dist/lib/skill-eval-fixture.d.ts +6 -4
- package/dist/lib/skill-eval-fixture.d.ts.map +1 -1
- package/dist/lib/skill-eval-fixture.js +25 -18
- package/dist/lib/skill-eval-fixture.js.map +1 -1
- package/dist/new-paper.d.ts +5 -11
- package/dist/new-paper.d.ts.map +1 -1
- package/dist/new-paper.js +12 -23
- package/dist/new-paper.js.map +1 -1
- package/dist/paper-files.d.ts +4 -4
- package/dist/paper-prose.d.ts +17 -0
- package/dist/paper-prose.d.ts.map +1 -0
- package/dist/paper-prose.js +46 -0
- package/dist/paper-prose.js.map +1 -0
- package/dist/paper-settings.d.ts +7 -0
- package/dist/paper-settings.d.ts.map +1 -1
- package/dist/paper-settings.js +61 -8
- package/dist/paper-settings.js.map +1 -1
- package/dist/ports/venue-deadlines.d.ts +14 -0
- package/dist/ports/venue-deadlines.d.ts.map +1 -0
- package/dist/ports/venue-deadlines.js +2 -0
- package/dist/ports/venue-deadlines.js.map +1 -0
- package/dist/presets.d.ts +12 -0
- package/dist/presets.d.ts.map +1 -1
- package/dist/presets.js +19 -11
- package/dist/presets.js.map +1 -1
- package/dist/structure.d.ts.map +1 -1
- package/dist/structure.js +4 -3
- package/dist/structure.js.map +1 -1
- package/dist/submission.d.ts +8 -0
- package/dist/submission.d.ts.map +1 -1
- package/dist/submission.js +41 -10
- package/dist/submission.js.map +1 -1
- package/dist/tex-requirements.d.ts +3 -0
- package/dist/tex-requirements.d.ts.map +1 -1
- package/dist/tex-requirements.js +19 -0
- package/dist/tex-requirements.js.map +1 -1
- package/dist/tex-venue-rules.d.ts +19 -0
- package/dist/tex-venue-rules.d.ts.map +1 -1
- package/dist/tex-venue-rules.js +69 -0
- package/dist/tex-venue-rules.js.map +1 -1
- package/dist/types.d.ts +0 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/venue-rules.d.ts +10 -0
- package/dist/venue-rules.d.ts.map +1 -1
- package/dist/venue-rules.js +26 -10
- package/dist/venue-rules.js.map +1 -1
- package/docs/configuration.md +146 -20
- package/docs/e2e.md +4 -6
- package/docs/prior-art/paper-folder-scaffolding.md +24 -29
- package/docs/rules/cycle/evidence.md +87 -0
- package/docs/rules/cycle/port-done.md +75 -0
- package/docs/rules/cycle/record.md +152 -0
- package/docs/rules/cycle/stage.md +85 -0
- package/docs/rules/tex/claim-provenance.md +0 -1
- package/docs/rules.md +48 -47
- package/docs/submission.md +5 -0
- package/eslint-rules/cycle-stage.test.mjs +131 -0
- package/eslint-rules/cycle-stage.ts +141 -0
- package/eslint-rules/latex-language.harness.mjs +3 -3
- package/eslint-rules/latex-language.ts +10 -13
- package/eslint-rules/paper-context.ts +2 -2
- package/eslint-rules/paper-typography.test.mjs +17 -85
- package/eslint-rules/paper-typography.ts +22 -98
- package/eslint-rules/papers.harness.mjs +1 -1
- package/eslint-rules/papers.ts +2 -4
- package/eslint-rules/tex-build.harness.mjs +2 -2
- package/eslint-rules/tex-build.ts +8 -25
- package/eslint.config.mjs +41 -2
- package/hooks/hooks.harness.ts +15 -15
- package/hooks/paper-edit-guard.hook.mjs +5 -8
- package/hooks/paper-edit-guard.test.ts +12 -3
- package/hooks/paper-skills-nudge.hook.mjs +2 -3
- package/hooks/paper-skills-nudge.test.ts +13 -3
- package/hooks/paper-status-gates.hook.mjs +2 -2
- package/hooks/paper-status-gates.test.ts +14 -2
- package/lib/markdown.test.mjs +5 -60
- package/lib/markdown.ts +9 -136
- package/lib/paper-config.ts +3 -2
- package/lib/skill-checks.test.mjs +4 -1
- package/lib/skill-eval-fixture.ts +25 -18
- package/package.json +1 -3
- package/presets/aidc.jsonc +24 -3
- package/presets/aidc.md +6 -3
- package/presets/msr.jsonc +25 -1
- package/presets/msr.md +5 -1
- package/presets/realm.jsonc +1 -1
- package/presets/venue-profile.schema.json +39 -0
- package/scripts/check.ts +4 -0
- package/scripts/harness-api.frozen.json +0 -5
- package/scripts/refresh-deadlines.test.ts +431 -0
- package/scripts/refresh-deadlines.ts +387 -0
- package/skills/cold-read-diff/SKILL.md +3 -3
- package/skills/cold-read-diff/SKILL.md.spec.ts +2 -2
- package/skills/grade-paper-writing/SKILL.md +14 -22
- package/skills/grade-paper-writing/SKILL.md.spec.ts +13 -21
- package/skills/grade-paper-writing/__snapshots__/prose-lint.test.ts.snap +40 -26
- package/skills/grade-paper-writing/prose-lint.mjs +108 -278
- package/skills/grade-paper-writing/prose-lint.test.ts +184 -81
- package/skills/paper-adversarial-review/SKILL.md +2 -2
- package/skills/paper-adversarial-review/SKILL.md.spec.ts +1 -1
- package/skills/paper-pipeline/SKILL.md +9 -12
- package/skills/paper-pipeline/SKILL.md.spec.ts +8 -11
- package/skills/paper-pipeline/description-language.eval.mjs +25 -15
- package/skills/paper-pipeline/framing-vs-vocabulary.eval.mjs +25 -15
- package/skills/paper-pipeline/grade-paper-writing-ablation.eval.mjs +25 -15
- package/skills/paper-pipeline/pipeline-firing.eval.mjs +27 -16
- package/skills/paper-pipeline/pipeline-language.eval.mjs +25 -15
- package/skills/paper-pipeline/references/acceptance-gate.md +16 -20
- package/skills/paper-pipeline/references/artifact-checklist.md +4 -5
- package/skills/paper-pipeline/references/pipeline-status-template.md +1 -1
- package/skills/paper-pipeline/scripts/README.md +0 -4
- package/skills/paper-pipeline/scripts/extract-ref-facts.harness.mjs +8 -72
- package/skills/paper-pipeline/scripts/extract-ref-facts.mjs +48 -156
- package/skills/paper-pipeline/scripts/extract-ref-facts.test.mjs +81 -75
- package/skills/paper-pipeline/scripts/generated-code.harness.mjs +3 -3
- package/skills/paper-status/SKILL.md +2 -2
- package/skills/paper-status/SKILL.md.spec.ts +1 -1
- package/skills/render-paper/SKILL.md +2 -2
- package/skills/render-paper/SKILL.md.spec.ts +1 -1
- package/skills/render-paper/extract-pdf-facts.mjs +2 -2
- package/skills/tighten-paper/SKILL.md +29 -42
- package/skills/tighten-paper/SKILL.md.spec.ts +28 -41
- package/skills/verify-citations/scripts/bib-authors.check.test.mjs +0 -13
- package/skills/verify-citations/scripts/bib-authors.cli.test.ts +23 -11
- package/skills/verify-citations/scripts/bib-authors.mjs +8 -93
- package/skills/verify-citations/scripts/bib-authors.test.mjs +1 -44
- package/src/adapters/hotcrp/cassette/README.md +22 -0
- package/src/adapters/hotcrp/cassette/deadlines-aidc26.html +17 -0
- package/src/adapters/hotcrp/cassette/deadlines-fse2027.html +24 -0
- package/src/adapters/hotcrp/cassette/deadlines-icse2027.html +25 -0
- package/src/adapters/hotcrp/cassette/deadlines-msr2027.html +25 -0
- package/src/adapters/hotcrp/cassette/deadlines-nosuch.html +18 -0
- package/src/adapters/hotcrp/cassette/deadlines-sosp26.html +26 -0
- package/src/adapters/hotcrp/deadlines.io.test.ts +86 -0
- package/src/adapters/hotcrp/deadlines.io.ts +27 -0
- package/src/adapters/hotcrp/deadlines.test.ts +170 -0
- package/src/adapters/hotcrp/deadlines.ts +161 -0
- package/src/adapters/hotcrp/index.ts +2 -0
- package/src/build.harness.mjs +2 -2
- package/src/build.test.ts +1 -1
- package/src/build.ts +1 -6
- package/src/cli.harness.mjs +26 -46
- package/src/cli.test.ts +41 -40
- package/src/cli.ts +57 -58
- package/src/cycle-rules.test.ts +278 -0
- package/src/cycle-rules.ts +273 -0
- package/src/domain/cycle.test.ts +456 -0
- package/src/domain/cycle.ts +432 -0
- package/src/domain/deadline.test.ts +386 -0
- package/src/domain/deadline.ts +472 -0
- package/src/folder-venue-rule.test.ts +1 -1
- package/src/init.test.ts +6 -7
- package/src/init.ts +3 -22
- package/src/lint-fix.test.ts +10 -4
- package/src/new-paper.harness.mjs +9 -28
- package/src/new-paper.test.ts +7 -6
- package/src/new-paper.ts +11 -30
- package/src/paper-files.test.ts +5 -4
- package/src/paper-files.ts +4 -4
- package/src/paper-prose.test.ts +49 -0
- package/src/paper-prose.ts +67 -0
- package/src/paper-settings.test.ts +167 -0
- package/src/paper-settings.ts +85 -11
- package/src/parse-args.test.ts +0 -1
- package/src/ports/venue-deadlines.ts +14 -0
- package/src/presets.test.ts +105 -0
- package/src/presets.ts +43 -10
- package/src/rule-cases.test.ts +144 -0
- package/src/structure.harness.ts +5 -11
- package/src/structure.ts +4 -3
- package/src/submission.test.ts +105 -1
- package/src/submission.ts +73 -12
- package/src/tex-requirements.ts +31 -0
- package/src/tex-venue-rules.test.ts +76 -0
- package/src/tex-venue-rules.ts +85 -0
- package/src/types.ts +0 -2
- package/src/venue-rules.test.ts +82 -0
- package/src/venue-rules.ts +44 -12
- package/fixtures/real-markdown-paper/baseline.json +0 -22
- package/fixtures/real-markdown-paper/baseline.ts +0 -70
- package/skills/paper-pipeline/PIPELINE-MAP.md +0 -381
- package/skills/paper-pipeline/pipeline-firing.baseline.json +0 -270
- package/skills/paper-pipeline/references/sweep-2026-08-09-ideal-pipeline.md +0 -585
- package/skills/paper-pipeline/scripts/artifact-coverage.harness.mjs +0 -492
- package/skills/paper-pipeline/scripts/artifact-coverage.mjs +0 -397
- package/skills/paper-pipeline/scripts/artifact-coverage.test.ts +0 -86
- package/skills/paper-pipeline/scripts/check-provenance.mjs +0 -184
- package/skills/paper-pipeline/scripts/population-map.harness.mjs +0 -135
- package/skills/paper-pipeline/scripts/population-map.mjs +0 -188
- package/skills/paper-pipeline/scripts/population-map.test.mjs +0 -154
- package/skills/paper-pipeline/scripts/provenance.harness.mjs +0 -266
- package/skills/paper-pipeline/scripts/round-diff.harness.ts +0 -907
- package/skills/paper-pipeline/scripts/round-diff.mjs +0 -583
- package/skills/paper-pipeline/scripts/round-diff.test.mjs +0 -159
- package/skills/tighten-paper/__snapshots__/structure.test.ts.snap +0 -133
- package/skills/tighten-paper/structure.mjs +0 -364
- package/skills/tighten-paper/structure.test.ts +0 -117
- package/templates/paper/paper.md +0 -4
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* prose-lint.mjs as a process — the way the skill runs it. Every mode, on
|
|
2
|
+
* prose-lint.mjs as a process — the way the skill runs it. Every mode, on an accepted paper and on
|
|
3
3
|
* fixtures written here, with the WHOLE output compared: the report is the product, so a changed
|
|
4
4
|
* number or a dropped line is a changed result.
|
|
5
5
|
*/
|
|
@@ -11,84 +11,111 @@ import { expect, test } from "vitest";
|
|
|
11
11
|
import { runNode, useTempDir, writeTree } from "../../test/support.ts";
|
|
12
12
|
|
|
13
13
|
const SCRIPT = join(dirname(fileURLToPath(import.meta.url)), "prose-lint.mjs");
|
|
14
|
+
/**
|
|
15
|
+
* One process that loads the LaTeX parser and reads a full accepted paper: about 1 s alone, 5.6 s
|
|
16
|
+
* measured under coverage with the suite running in parallel — past vitest's 5 s default.
|
|
17
|
+
*/
|
|
18
|
+
const SPAWN_LATEX_MS = 60_000;
|
|
19
|
+
/** An accepted paper whose bibliography sits inline in `filecontents`, beside a long preamble. */
|
|
20
|
+
const ACCEPTED = join(
|
|
21
|
+
dirname(fileURLToPath(import.meta.url)),
|
|
22
|
+
"..",
|
|
23
|
+
"..",
|
|
24
|
+
"fixtures",
|
|
25
|
+
"accepted-papers",
|
|
26
|
+
"agenticdev-acm26",
|
|
27
|
+
"paper.tex",
|
|
28
|
+
);
|
|
14
29
|
const root = useTempDir("prose-lint-");
|
|
15
30
|
|
|
16
31
|
const LONG_CAPTION_SENTENCE = Array.from(
|
|
17
32
|
{ length: 45 },
|
|
18
33
|
(_, i) => `word${String(i)}`,
|
|
19
34
|
).join(" ");
|
|
20
|
-
const PAPER = `---
|
|
21
|
-
title: A title the linter must not read as a sentence
|
|
22
|
-
---
|
|
23
|
-
# The paper
|
|
24
|
-
|
|
25
|
-
<!-- TIGHTEN: a working note, not prose -->
|
|
26
|
-
|
|
27
|
-
## Introduction
|
|
28
|
-
|
|
29
|
-
In this paper we present a linter. It might possibly help, but this is not a proof. Clearly the
|
|
30
|
-
first result matters (Section 3). Prior work agrees [1, 2]. The speedup was 40% on average across the
|
|
31
|
-
full suite of papers, which suggests the method might hold generally.
|
|
32
|
-
|
|
33
|
-
- The first item is short.
|
|
34
|
-
- The second item is not long either.
|
|
35
|
-
|
|
36
|
-
\`\`\`
|
|
37
|
-
# a heading inside a code block is code
|
|
38
|
-
\`\`\`
|
|
39
|
-
|
|
40
|
-
| a | table |
|
|
41
|
-
|---|---|
|
|
42
|
-
| is | data |
|
|
43
|
-
|
|
44
|
-
### Method
|
|
45
35
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
[1] A reference that is not prose.
|
|
51
|
-
`;
|
|
36
|
+
/** A paper.tex whose body is `body`. */
|
|
37
|
+
const tex = (body: string): string =>
|
|
38
|
+
`\\documentclass{article}\n\\begin{document}\n${body}\n\\end{document}\n`;
|
|
52
39
|
|
|
53
40
|
writeTree(root, {
|
|
54
|
-
"p/paper.
|
|
41
|
+
"p/paper.tex": tex(
|
|
42
|
+
"\\section{Introduction}\nIn this paper we present a linter. It might possibly help.\n\\input{figures/fig1}\n\\input{figures/fig2}\n\\input{figures/fig4}\n\\input{figures/fig5}",
|
|
43
|
+
),
|
|
55
44
|
"p/figures/fig1.tex": `\\begin{figure}\\caption{\\textbf{Big.} ${LONG_CAPTION_SENTENCE}. ${"More words here. ".repeat(20)}}\\end{figure}`,
|
|
56
45
|
// Empty once LaTeX commands and braces are stripped: skipped, not counted as a zero-word caption.
|
|
57
46
|
"p/figures/fig2.tex": "\\caption{\\centering}",
|
|
58
|
-
// A lone "#": no word at all
|
|
47
|
+
// A lone "#": no word at all — measured as zero words, never flagged.
|
|
59
48
|
"p/figures/fig4.tex": "\\caption{{#}}",
|
|
49
|
+
// Rules only: seen by the reader, but no word once the dashes are stripped — skipped.
|
|
50
|
+
"p/figures/fig5.tex": "\\caption{---}",
|
|
60
51
|
"p/figures/notes.txt": "not a figure",
|
|
61
|
-
|
|
62
|
-
// the
|
|
63
|
-
"
|
|
64
|
-
|
|
52
|
+
"clean/paper.tex": tex("Short and plain. Nothing else."),
|
|
53
|
+
// Captions in the paper itself and in a section it includes — not under figures/.
|
|
54
|
+
"inline/paper.tex": tex(
|
|
55
|
+
`Prose here. \\begin{figure}\\caption{${LONG_CAPTION_SENTENCE}.}\\end{figure}\n\\input{sec/results}`,
|
|
56
|
+
),
|
|
57
|
+
"inline/sec/results.tex": `\\begin{figure}\\caption{${"Short words here. ".repeat(40)}}\\end{figure}`,
|
|
58
|
+
// One real over-long caption, and the same caption commented out and inside verbatim.
|
|
59
|
+
"commented/paper.tex": tex(
|
|
60
|
+
`Prose here.\n% \\caption{Commented ${LONG_CAPTION_SENTENCE}.}\n\\begin{verbatim}\n\\caption{Quoted ${LONG_CAPTION_SENTENCE}.}\n\\end{verbatim}\n\\begin{figure}\\caption{Real ${LONG_CAPTION_SENTENCE}.}\\end{figure}`,
|
|
61
|
+
),
|
|
62
|
+
"ends-on-marks/paper.tex": tex(
|
|
63
|
+
"The method holds on every input~\\cite{knuth}. The proof is given in full (Section~\\ref{proof}). The result is new.",
|
|
64
|
+
),
|
|
65
|
+
"empty/paper.tex": "\\documentclass{article}",
|
|
65
66
|
"page.txt":
|
|
66
67
|
"A rendered page. It was hyphen-\nated across a line.\n\n12\n\fNext page text here.\nReferences\n[1] cut here.\n",
|
|
67
|
-
"
|
|
68
|
+
"notes.rtf": "not a paper",
|
|
68
69
|
});
|
|
69
70
|
|
|
70
71
|
test("no arguments: the usage line, exit 0", () => {
|
|
71
72
|
assert.deepEqual(runNode(SCRIPT), {
|
|
72
73
|
status: 0,
|
|
73
74
|
stdout: "",
|
|
74
|
-
stderr:
|
|
75
|
+
stderr:
|
|
76
|
+
"usage: node prose-lint.mjs <paper.tex|page.txt> [more files] [--flags-only|--headings]\n",
|
|
75
77
|
});
|
|
76
78
|
});
|
|
77
79
|
|
|
78
|
-
test(
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
80
|
+
test(
|
|
81
|
+
"🔴 a paper.tex is measured on the prose a reader sees — not its preamble, comments or inline .bib",
|
|
82
|
+
{ timeout: SPAWN_LATEX_MS },
|
|
83
|
+
() => {
|
|
84
|
+
const r = runNode(SCRIPT, [ACCEPTED]);
|
|
85
|
+
// Guards: a .tex read as raw text counted the preamble, the comments and the `filecontents`
|
|
86
|
+
// bibliography — 8127 "words" on this paper's submitted version against about 4636 real ones.
|
|
87
|
+
const words = Number(
|
|
88
|
+
/^=== paper\.tex — (\d+) words, \d+ sentences ===$/m.exec(r.stdout)?.[1],
|
|
89
|
+
);
|
|
90
|
+
assert.deepEqual(
|
|
91
|
+
{
|
|
92
|
+
status: r.status,
|
|
93
|
+
stderr: r.stderr,
|
|
94
|
+
inBand: words > 4000 && words < 5500,
|
|
95
|
+
},
|
|
96
|
+
{ status: 0, stderr: "", inBand: true },
|
|
97
|
+
);
|
|
98
|
+
// No BibTeX field and no preamble command reaches the measured text.
|
|
99
|
+
assert.doesNotMatch(r.stdout, /author\s*=|\\usepackage|\\documentclass/);
|
|
100
|
+
expect(r.stdout).toMatchSnapshot();
|
|
101
|
+
},
|
|
102
|
+
);
|
|
86
103
|
|
|
87
|
-
test(
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
104
|
+
test(
|
|
105
|
+
"--headings on a paper.tex lists its section titles, in order",
|
|
106
|
+
{ timeout: SPAWN_LATEX_MS },
|
|
107
|
+
() => {
|
|
108
|
+
const r = runNode(SCRIPT, ["--headings", ACCEPTED]);
|
|
109
|
+
assert.deepEqual(
|
|
110
|
+
{ status: r.status, stderr: r.stderr },
|
|
111
|
+
{ status: 0, stderr: "" },
|
|
112
|
+
);
|
|
113
|
+
expect(r.stdout).toMatchSnapshot();
|
|
114
|
+
},
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
test("the report over a rendered page: page numbers, hyphenation and the references are dropped", () => {
|
|
118
|
+
const r = runNode(SCRIPT, [join(root, "page.txt")]);
|
|
92
119
|
assert.deepEqual(
|
|
93
120
|
{ status: r.status, stderr: r.stderr },
|
|
94
121
|
{ status: 0, stderr: "" },
|
|
@@ -96,17 +123,26 @@ test("the full report over markdown and rendered text", () => {
|
|
|
96
123
|
expect(r.stdout).toMatchSnapshot();
|
|
97
124
|
});
|
|
98
125
|
|
|
99
|
-
test("--headings
|
|
100
|
-
const
|
|
101
|
-
assert.deepEqual(
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
126
|
+
test("--headings on a rendered page is refused: it marks no heading", () => {
|
|
127
|
+
const file = join(root, "page.txt");
|
|
128
|
+
assert.deepEqual(runNode(SCRIPT, ["--headings", file]), {
|
|
129
|
+
status: 2,
|
|
130
|
+
stdout: "",
|
|
131
|
+
stderr: `prose-lint: --headings reads a paper.tex — a rendered page (${file}) marks no heading\n`,
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test("a file that is neither a paper.tex nor a rendered page is refused with exit 2, and nothing is measured", () => {
|
|
136
|
+
const file = join(root, "notes.rtf");
|
|
137
|
+
assert.deepEqual(runNode(SCRIPT, [file]), {
|
|
138
|
+
status: 2,
|
|
139
|
+
stdout: "",
|
|
140
|
+
stderr: `prose-lint reads a paper.tex or the text of its rendered PDF (.txt), not: ${file}\n`,
|
|
141
|
+
});
|
|
106
142
|
});
|
|
107
143
|
|
|
108
144
|
test("--flags-only: an over-long caption flags and exits 1", () => {
|
|
109
|
-
const r = runNode(SCRIPT, ["--flags-only", join(root, "p", "paper.
|
|
145
|
+
const r = runNode(SCRIPT, ["--flags-only", join(root, "p", "paper.tex")]);
|
|
110
146
|
assert.deepEqual(
|
|
111
147
|
{ status: r.status, stdout: r.stdout },
|
|
112
148
|
{ status: 1, stdout: "" },
|
|
@@ -116,38 +152,105 @@ test("--flags-only: an over-long caption flags and exits 1", () => {
|
|
|
116
152
|
|
|
117
153
|
test("--flags-only: a paper with no figures directory is clean, exit 0 and silent", () => {
|
|
118
154
|
assert.deepEqual(
|
|
119
|
-
runNode(SCRIPT, ["--flags-only", join(root, "clean", "paper.
|
|
120
|
-
{
|
|
121
|
-
status: 0,
|
|
122
|
-
stdout: "",
|
|
123
|
-
stderr: "",
|
|
124
|
-
},
|
|
155
|
+
runNode(SCRIPT, ["--flags-only", join(root, "clean", "paper.tex")]),
|
|
156
|
+
{ status: 0, stdout: "", stderr: "" },
|
|
125
157
|
);
|
|
126
158
|
});
|
|
127
159
|
|
|
128
|
-
test("a
|
|
129
|
-
|
|
130
|
-
"empty/paper.md":
|
|
131
|
-
"---\ntitle: only frontmatter\n---\n<!-- and a note -->\n",
|
|
132
|
-
});
|
|
133
|
-
const file = join(root, "empty", "paper.md");
|
|
160
|
+
test("a .tex with no prose in its body is refused with exit 2, not reported as NaN", () => {
|
|
161
|
+
const file = join(root, "empty", "paper.tex");
|
|
134
162
|
assert.deepEqual(runNode(SCRIPT, [file]), {
|
|
135
163
|
status: 2,
|
|
136
164
|
stdout: "",
|
|
137
165
|
stderr:
|
|
138
|
-
`prose-lint: ${file} has no prose to measure — after the
|
|
139
|
-
"
|
|
166
|
+
`prose-lint: ${file} has no prose to measure — after the preamble, comments, floats and ` +
|
|
167
|
+
"the back matter are left out, no word is left.\n",
|
|
140
168
|
});
|
|
141
169
|
});
|
|
142
170
|
|
|
143
|
-
test("a
|
|
144
|
-
writeTree(root, { "
|
|
145
|
-
const file = join(root, "
|
|
171
|
+
test("a rendered page with no sentence before its references is refused the same way", () => {
|
|
172
|
+
writeTree(root, { "blank.txt": "12\nReferences\n[1] only this.\n" });
|
|
173
|
+
const file = join(root, "blank.txt");
|
|
146
174
|
assert.deepEqual(runNode(SCRIPT, [file]), {
|
|
147
175
|
status: 2,
|
|
148
176
|
stdout: "",
|
|
149
|
-
stderr:
|
|
150
|
-
|
|
151
|
-
|
|
177
|
+
stderr: `prose-lint: ${file} has no prose to measure — no sentence is left before the references.\n`,
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
test("🔴 in a paper.tex, a sentence ending on a citation or a cross-reference wastes its stress position", () => {
|
|
182
|
+
const r = runNode(SCRIPT, [join(root, "ends-on-marks", "paper.tex")]);
|
|
183
|
+
// Guards: the marks used to be dropped from the prose, so neither ending could be seen.
|
|
184
|
+
assert.match(
|
|
185
|
+
r.stdout,
|
|
186
|
+
/^FLAG sentences ending on a cross-ref\/citation\/hedge \(wasted stress position\): 2$/m,
|
|
187
|
+
);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
test("🔴 --flags-only reads the captions of the paper.tex and of the files it includes", () => {
|
|
191
|
+
const r = runNode(SCRIPT, [
|
|
192
|
+
"--flags-only",
|
|
193
|
+
join(root, "inline", "paper.tex"),
|
|
194
|
+
]);
|
|
195
|
+
// Guards: captions were read only from figures/*.tex beside the paper, so a caption set inline,
|
|
196
|
+
// or in an included section, was never checked and the run exited clean.
|
|
197
|
+
assert.deepEqual(
|
|
198
|
+
{
|
|
199
|
+
status: r.status,
|
|
200
|
+
stdout: r.stdout,
|
|
201
|
+
flagged: r.stderr
|
|
202
|
+
.split("\n")
|
|
203
|
+
.filter((l) => l.startsWith(" "))
|
|
204
|
+
.map((l) => l.split(":")[0]),
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
status: 1,
|
|
208
|
+
stdout: "",
|
|
209
|
+
flagged: [
|
|
210
|
+
" paper.tex",
|
|
211
|
+
" sec/results.tex",
|
|
212
|
+
" run prose-lint.mjs on the file without --flags-only for the sentences",
|
|
213
|
+
],
|
|
214
|
+
},
|
|
215
|
+
);
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
test("--flags-only on a rendered page reads the captions in figures/*.tex beside it", () => {
|
|
219
|
+
writeTree(root, {
|
|
220
|
+
"rendered/page.txt": "A rendered page. Its prose is fine.\n",
|
|
221
|
+
"rendered/figures/f.tex": `\\caption{${LONG_CAPTION_SENTENCE}.}`,
|
|
222
|
+
"rendered/figures/notes.txt": "not a figure",
|
|
223
|
+
});
|
|
224
|
+
const r = runNode(SCRIPT, [
|
|
225
|
+
"--flags-only",
|
|
226
|
+
join(root, "rendered", "page.txt"),
|
|
227
|
+
]);
|
|
228
|
+
assert.deepEqual(
|
|
229
|
+
{ status: r.status, first: r.stderr.split("\n")[1]?.split(":")[0] },
|
|
230
|
+
{ status: 1, first: " figures/f.tex" },
|
|
231
|
+
);
|
|
232
|
+
// …and a page with no figures/ beside it has no caption to flag.
|
|
233
|
+
assert.deepEqual(runNode(SCRIPT, ["--flags-only", join(root, "page.txt")]), {
|
|
234
|
+
status: 0,
|
|
235
|
+
stdout: "",
|
|
236
|
+
stderr: "",
|
|
152
237
|
});
|
|
153
238
|
});
|
|
239
|
+
|
|
240
|
+
test("🔴 a caption commented out or quoted in verbatim is not measured; a real one still is", () => {
|
|
241
|
+
const r = runNode(SCRIPT, [
|
|
242
|
+
"--flags-only",
|
|
243
|
+
join(root, "commented", "paper.tex"),
|
|
244
|
+
]);
|
|
245
|
+
// Guards: the scan matched every `\caption{` in the raw text, comments and verbatim included.
|
|
246
|
+
assert.deepEqual(
|
|
247
|
+
{
|
|
248
|
+
status: r.status,
|
|
249
|
+
findings: r.stderr
|
|
250
|
+
.split("\n")
|
|
251
|
+
.filter((l) => l.startsWith(" paper.tex:"))
|
|
252
|
+
.map((l) => /— "(\w+)/.exec(l)?.[1]),
|
|
253
|
+
},
|
|
254
|
+
{ status: 1, findings: ["Real"] },
|
|
255
|
+
);
|
|
256
|
+
});
|
|
@@ -5,7 +5,7 @@ context: fork
|
|
|
5
5
|
allowed-tools: [Read, Write, Grep, Glob, Agent, Skill]
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
-
<!-- vigiles:sha256:
|
|
8
|
+
<!-- vigiles:sha256:cb852f6fb5fb9988 compiled from skills/paper-adversarial-review/SKILL.md.spec.ts -->
|
|
9
9
|
|
|
10
10
|
# paper-adversarial-review — be the reviewer who wants to reject it
|
|
11
11
|
|
|
@@ -22,7 +22,7 @@ reviewer and returns an actionable critique.
|
|
|
22
22
|
|
|
23
23
|
## How to run it
|
|
24
24
|
Prefer a **separate model as the reviewer** (e.g. Fable via a subagent) so it's not the author
|
|
25
|
-
grading itself. Give the reviewer the full paper text (Read the `.tex
|
|
25
|
+
grading itself. Give the reviewer the full paper text (Read the `.tex`/PDF) and the target
|
|
26
26
|
venue + paper type (short/full/position/benchmark). Ask for the structured output below.
|
|
27
27
|
|
|
28
28
|
## The review must cover (ranked by how often it kills a paper)
|
|
@@ -28,7 +28,7 @@ reviewer and returns an actionable critique.
|
|
|
28
28
|
|
|
29
29
|
## How to run it
|
|
30
30
|
Prefer a **separate model as the reviewer** (e.g. Fable via a subagent) so it's not the author
|
|
31
|
-
grading itself. Give the reviewer the full paper text (Read the \`.tex
|
|
31
|
+
grading itself. Give the reviewer the full paper text (Read the \`.tex\`/PDF) and the target
|
|
32
32
|
venue + paper type (short/full/position/benchmark). Ask for the structured output below.
|
|
33
33
|
|
|
34
34
|
## The review must cover (ranked by how often it kills a paper)
|
|
@@ -4,7 +4,7 @@ description: The orchestrator for writing a research paper end-to-end, from idea
|
|
|
4
4
|
allowed-tools: [Read, Write, Edit, Grep, Glob, Bash, WebSearch, WebFetch, Agent, Skill]
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
<!-- vigiles:sha256:
|
|
7
|
+
<!-- vigiles:sha256:67a3989629952dc1 compiled from skills/paper-pipeline/SKILL.md.spec.ts -->
|
|
8
8
|
|
|
9
9
|
# paper-pipeline — the conductor for the whole organism
|
|
10
10
|
|
|
@@ -191,7 +191,7 @@ These have no position, because a position is what made them look finished. Each
|
|
|
191
191
|
| any `\cite` added or moved | **`verify-citations`** | An edit on the last day adds a citation the last run never saw. "We checked last cycle" does not count — the project rule, and it was numbered as a one-shot anyway. |
|
|
192
192
|
| a genuine sibling surfaces | **`analyze-sibling-paper`** | Deep-read, scoop/delta, saved in `<paper-dir>/siblings/`. |
|
|
193
193
|
| the contribution's framing moves | **`map-prior-work`** (delta mode) | A cheap re-sweep. ⚠️ If a rival first surfaces during citation checking, the SETUP sweep was skipped and you are reshaping the contribution under deadline. |
|
|
194
|
-
| any `.tex`
|
|
194
|
+
| any `.tex` edit | **`render-paper`** + page count | **Not a stage — a command.** Render, then eyeball pages 1–2 before believing anything about the paper. 🔴 **Rendering is continuous; ACTING on the page count is not** — see the rule below. |
|
|
195
195
|
| the deadline approaches | the `access` row | Re-check that the profile is still ACTIVE and the portal still opens. External state rots without telling you. |
|
|
196
196
|
|
|
197
197
|
## GATES — late, expensive, each blocked on a named input
|
|
@@ -310,25 +310,22 @@ binds every row from now on, and the existing rows are *enumerated rather than e
|
|
|
310
310
|
says out loud how many figures still have no machine-checked path to data. On the paper that has it:
|
|
311
311
|
7 guarded, 248 grandfathered. That ratio is a finding, not a score.
|
|
312
312
|
|
|
313
|
-
**Adopting it in another paper:** copy `repro/paper_numbers.py`, `repro/paper_numbers.selftest.py`
|
|
314
|
-
the
|
|
315
|
-
current draft; register the numbers a reviewer will actually check first.
|
|
316
|
-
reports `no-numbers-gate` for any paper that has not.
|
|
313
|
+
**Adopting it in another paper:** copy `repro/paper_numbers.py`, `repro/paper_numbers.selftest.py`
|
|
314
|
+
and the build step that calls `expand()` on `paper.tex` before `pdflatex`; generate the
|
|
315
|
+
grandfather list from the current draft; register the numbers a reviewer will actually check first.
|
|
317
316
|
|
|
318
317
|
🔴 **Do not write the substitution half — it is `jinja2`, and `{{name}}` is already its syntax.**
|
|
319
|
-
Eleven candidates were installed and run on a real
|
|
320
|
-
|
|
318
|
+
Eleven candidates were installed and run on a real ACL paper on 2026-08-05. The result is not a
|
|
319
|
+
matter of taste:
|
|
321
320
|
|
|
322
321
|
| candidate | verdict, from an actual run |
|
|
323
322
|
|---|---|
|
|
324
323
|
| **jinja2** | **adopt.** Our `{{annotated.failRate}}` was already valid Jinja2; dotted names are attribute access. Zero characters changed in the paper, byte-identical `.tex`. This is Manubot's mechanism, taken without Manubot. Use `StrictUndefined`, and move the block/comment delimiters off `{%`/`{#` if the paper might ever quote a config file |
|
|
325
324
|
| **knitr** | equally proven, also byte-identical. Rejected only for putting an R runtime into an artifact that otherwise needs `python3` + `pdflatex` |
|
|
326
|
-
| `\newcommand` + `numbers.tex` | works
|
|
327
|
-
| **Quarto**, **pandoc lua filters** | ❌ **destroy the paper.** Markdown→markdown is a pandoc AST round-trip: HTML comments become raw blocks and `[58]` becomes `\[58\]`, so every citation renders as literal `\textbackslash{}[58]`. 3,120 diff lines on the real source. No ACL format exists for Quarto either |
|
|
325
|
+
| `\newcommand` + `numbers.tex` | works; the folk convention, ~12 GitHub hits, all hand-rolled. No package exists |
|
|
328
326
|
| **cog.py** | ❌ line-oriented; cannot substitute mid-sentence |
|
|
329
327
|
| **pythontex** | ❌ not in Ubuntu TeX Live at any level, not on PyPI; CTAN zip + `.ins` build |
|
|
330
|
-
| **showyourwork** | ❌
|
|
331
|
-
| **Pweave** | ❌ dead — the one candidate with a markdown native format, broken against modern IPython |
|
|
328
|
+
| **showyourwork** | ❌ it manages *figures*, not in-text numbers |
|
|
332
329
|
| DVC · datalad · papermill | orthogonal and **worth adding**: they pin the data file to the pipeline that made it, a layer below anything here |
|
|
333
330
|
|
|
334
331
|
**🔴 Computing the value DISSOLVES the freshness problem — do not build a checker for it.** With no
|
|
@@ -208,7 +208,7 @@ These have no position, because a position is what made them look finished. Each
|
|
|
208
208
|
| any \`\\cite\` added or moved | **\`verify-citations\`** | An edit on the last day adds a citation the last run never saw. "We checked last cycle" does not count — the project rule, and it was numbered as a one-shot anyway. |
|
|
209
209
|
| a genuine sibling surfaces | **\`analyze-sibling-paper\`** | Deep-read, scoop/delta, saved in \`<paper-dir>/siblings/\`. |
|
|
210
210
|
| the contribution's framing moves | **\`map-prior-work\`** (delta mode) | A cheap re-sweep. ⚠️ If a rival first surfaces during citation checking, the SETUP sweep was skipped and you are reshaping the contribution under deadline. |
|
|
211
|
-
| any \`.tex\`
|
|
211
|
+
| any \`.tex\` edit | **\`render-paper\`** + page count | **Not a stage — a command.** Render, then eyeball pages 1–2 before believing anything about the paper. 🔴 **Rendering is continuous; ACTING on the page count is not** — see the rule below. |
|
|
212
212
|
| the deadline approaches | the \`access\` row | Re-check that the profile is still ACTIVE and the portal still opens. External state rots without telling you. |
|
|
213
213
|
|
|
214
214
|
## GATES — late, expensive, each blocked on a named input
|
|
@@ -327,25 +327,22 @@ binds every row from now on, and the existing rows are *enumerated rather than e
|
|
|
327
327
|
says out loud how many figures still have no machine-checked path to data. On the paper that has it:
|
|
328
328
|
7 guarded, 248 grandfathered. That ratio is a finding, not a score.
|
|
329
329
|
|
|
330
|
-
**Adopting it in another paper:** copy \`repro/paper_numbers.py\`, \`repro/paper_numbers.selftest.py\`
|
|
331
|
-
the
|
|
332
|
-
current draft; register the numbers a reviewer will actually check first.
|
|
333
|
-
reports \`no-numbers-gate\` for any paper that has not.
|
|
330
|
+
**Adopting it in another paper:** copy \`repro/paper_numbers.py\`, \`repro/paper_numbers.selftest.py\`
|
|
331
|
+
and the build step that calls \`expand()\` on \`paper.tex\` before \`pdflatex\`; generate the
|
|
332
|
+
grandfather list from the current draft; register the numbers a reviewer will actually check first.
|
|
334
333
|
|
|
335
334
|
🔴 **Do not write the substitution half — it is \`jinja2\`, and \`{{name}}\` is already its syntax.**
|
|
336
|
-
Eleven candidates were installed and run on a real
|
|
337
|
-
|
|
335
|
+
Eleven candidates were installed and run on a real ACL paper on 2026-08-05. The result is not a
|
|
336
|
+
matter of taste:
|
|
338
337
|
|
|
339
338
|
| candidate | verdict, from an actual run |
|
|
340
339
|
|---|---|
|
|
341
340
|
| **jinja2** | **adopt.** Our \`{{annotated.failRate}}\` was already valid Jinja2; dotted names are attribute access. Zero characters changed in the paper, byte-identical \`.tex\`. This is Manubot's mechanism, taken without Manubot. Use \`StrictUndefined\`, and move the block/comment delimiters off \`{%\`/\`{#\` if the paper might ever quote a config file |
|
|
342
341
|
| **knitr** | equally proven, also byte-identical. Rejected only for putting an R runtime into an artifact that otherwise needs \`python3\` + \`pdflatex\` |
|
|
343
|
-
| \`\\newcommand\` + \`numbers.tex\` | works
|
|
344
|
-
| **Quarto**, **pandoc lua filters** | ❌ **destroy the paper.** Markdown→markdown is a pandoc AST round-trip: HTML comments become raw blocks and \`[58]\` becomes \`\\[58\\]\`, so every citation renders as literal \`\\textbackslash{}[58]\`. 3,120 diff lines on the real source. No ACL format exists for Quarto either |
|
|
342
|
+
| \`\\newcommand\` + \`numbers.tex\` | works; the folk convention, ~12 GitHub hits, all hand-rolled. No package exists |
|
|
345
343
|
| **cog.py** | ❌ line-oriented; cannot substitute mid-sentence |
|
|
346
344
|
| **pythontex** | ❌ not in Ubuntu TeX Live at any level, not on PyPI; CTAN zip + \`.ins\` build |
|
|
347
|
-
| **showyourwork** | ❌
|
|
348
|
-
| **Pweave** | ❌ dead — the one candidate with a markdown native format, broken against modern IPython |
|
|
345
|
+
| **showyourwork** | ❌ it manages *figures*, not in-text numbers |
|
|
349
346
|
| DVC · datalad · papermill | orthogonal and **worth adding**: they pin the data file to the pipeline that made it, a layer below anything here |
|
|
350
347
|
|
|
351
348
|
**🔴 Computing the value DISSOLVES the freshness problem — do not build a checker for it.** With no
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* description-language.eval.mjs — does adding Russian to a skill's DESCRIPTION close the language gap?
|
|
3
3
|
*
|
|
4
|
+
* ⚠️ THE FIXTURE CHANGED AFTER THE RECORDED RUNS. The paper the model is shown is now `paper.tex`;
|
|
5
|
+
* every result recorded for this eval (here and in `repro/`) was measured on the previous fixture,
|
|
6
|
+
* the same paper in a source form paperlint no longer reads. The next run is a fresh, deliberate
|
|
7
|
+
* measurement — record it, do not compare it against those numbers.
|
|
8
|
+
*
|
|
4
9
|
* Run: node .claude/skills/paper-pipeline/description-language.eval.mjs --arm before|after [--trials N]
|
|
5
10
|
*
|
|
6
11
|
* --arm before measure with the descriptions AS THEY ARE (English only) <- run this FIRST
|
|
@@ -119,42 +124,47 @@ if (ARM !== "before" && ARM !== "after")
|
|
|
119
124
|
// Byte-identical to the other two evals. If these drift, a difference between runs stops being
|
|
120
125
|
// attributable to the descriptions.
|
|
121
126
|
const FIXTURE = {
|
|
122
|
-
"paper/paper.
|
|
123
|
-
"
|
|
127
|
+
"paper/paper.tex": [
|
|
128
|
+
"\\documentclass{article}",
|
|
129
|
+
"\\title{Prose Isn't Policy: Measuring Whether Agent-Config Rules Are Enforceable}",
|
|
130
|
+
"\\begin{document}",
|
|
131
|
+
"\\maketitle",
|
|
124
132
|
"",
|
|
125
|
-
"
|
|
133
|
+
"\\begin{abstract}",
|
|
126
134
|
"Agent configuration files state rules in prose and assume the model obeys them. We compile a",
|
|
127
|
-
"corpus of real rules and measure what fraction can be mechanically enforced. We find that 84
|
|
135
|
+
"corpus of real rules and measure what fraction can be mechanically enforced. We find that 84\\%",
|
|
128
136
|
"of rules in our corpus are enforceable, and that LLM-authored checkers for the remainder leak",
|
|
129
|
-
"silently in 84
|
|
137
|
+
"silently in 84--96\\% of adversarial cases~\\cite{greshake2023}.",
|
|
138
|
+
"\\end{abstract}",
|
|
130
139
|
"",
|
|
131
|
-
"
|
|
140
|
+
"\\section{Introduction}",
|
|
132
141
|
"Every agent harness ships a natural-language rulebook. Nothing checks it. This is the same",
|
|
133
|
-
"mistake as a code comment that claims an invariant no test enforces
|
|
142
|
+
"mistake as a code comment that claims an invariant no test enforces~\\cite{thompson1984}.",
|
|
134
143
|
"",
|
|
135
|
-
"
|
|
144
|
+
"\\section{Method}",
|
|
136
145
|
"We gather rules from public repositories, classify each by enforceability, and build a",
|
|
137
146
|
"two-stage adversarial gate that validates a synthesized rule against a blind gold set.",
|
|
138
147
|
"",
|
|
139
|
-
"
|
|
140
|
-
"See Table
|
|
148
|
+
"\\section{Results}",
|
|
149
|
+
"See Table~1. The headline number is 84\\%.",
|
|
141
150
|
"",
|
|
142
|
-
"
|
|
151
|
+
"\\section{Discussion}",
|
|
143
152
|
"The result generalizes beyond our corpus in the sense that the mechanism is not corpus-specific,",
|
|
144
153
|
"though of course the specific percentages are, and it is important to note in this context that",
|
|
145
154
|
"the framing itself may be what carries, rather than the measurement.",
|
|
146
155
|
"",
|
|
147
|
-
"
|
|
156
|
+
"\\section{Threats to Validity}",
|
|
148
157
|
"Our corpus is drawn from public repositories and may not represent private configurations.",
|
|
149
158
|
"",
|
|
150
|
-
"
|
|
159
|
+
"\\section{Related Work}",
|
|
151
160
|
"TODO",
|
|
152
161
|
"",
|
|
153
|
-
"
|
|
162
|
+
"\\section{Conclusion}",
|
|
154
163
|
"Prose is not policy.",
|
|
164
|
+
"\\end{document}",
|
|
155
165
|
].join("\n"),
|
|
156
166
|
"paper/repro/README.md":
|
|
157
|
-
"# Reproduction artifact\n\n`python3 paper_numbers.py` recomputes every bolded figure in paper.
|
|
167
|
+
"# Reproduction artifact\n\n`python3 paper_numbers.py` recomputes every bolded figure in paper.tex.\n",
|
|
158
168
|
"paper/PIPELINE-STATUS.md":
|
|
159
169
|
"# Pipeline status\n\n| gate | state |\n|---|---|\n| numbers | pass |\n| structure | not run |\n| citations | not run |\n",
|
|
160
170
|
};
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* framing-vs-vocabulary.eval.mjs — WHY does a skill fire only when the user names its verb?
|
|
3
3
|
*
|
|
4
|
+
* ⚠️ THE FIXTURE CHANGED AFTER THE RECORDED RUNS. The paper the model is shown is now `paper.tex`;
|
|
5
|
+
* every result recorded for this eval (here and in `repro/`) was measured on the previous fixture,
|
|
6
|
+
* the same paper in a source form paperlint no longer reads. The next run is a fresh, deliberate
|
|
7
|
+
* measurement — record it, do not compare it against those numbers.
|
|
8
|
+
*
|
|
4
9
|
* Run: node .claude/skills/paper-pipeline/framing-vs-vocabulary.eval.mjs [--trials N] [--mode main|oracle|preflight]
|
|
5
10
|
*
|
|
6
11
|
* --mode preflight run every guard and print the labelling matrix; spends NO tokens <- run this FIRST
|
|
@@ -287,42 +292,47 @@ if (!["main", "oracle", "preflight"].includes(MODE))
|
|
|
287
292
|
// Byte-identical to BOTH siblings. If it drifts, a difference between this run and theirs stops
|
|
288
293
|
// being attributable to the prompts.
|
|
289
294
|
const FIXTURE = {
|
|
290
|
-
"paper/paper.
|
|
291
|
-
"
|
|
295
|
+
"paper/paper.tex": [
|
|
296
|
+
"\\documentclass{article}",
|
|
297
|
+
"\\title{Prose Isn't Policy: Measuring Whether Agent-Config Rules Are Enforceable}",
|
|
298
|
+
"\\begin{document}",
|
|
299
|
+
"\\maketitle",
|
|
292
300
|
"",
|
|
293
|
-
"
|
|
301
|
+
"\\begin{abstract}",
|
|
294
302
|
"Agent configuration files state rules in prose and assume the model obeys them. We compile a",
|
|
295
|
-
"corpus of real rules and measure what fraction can be mechanically enforced. We find that 84
|
|
303
|
+
"corpus of real rules and measure what fraction can be mechanically enforced. We find that 84\\%",
|
|
296
304
|
"of rules in our corpus are enforceable, and that LLM-authored checkers for the remainder leak",
|
|
297
|
-
"silently in 84
|
|
305
|
+
"silently in 84--96\\% of adversarial cases~\\cite{greshake2023}.",
|
|
306
|
+
"\\end{abstract}",
|
|
298
307
|
"",
|
|
299
|
-
"
|
|
308
|
+
"\\section{Introduction}",
|
|
300
309
|
"Every agent harness ships a natural-language rulebook. Nothing checks it. This is the same",
|
|
301
|
-
"mistake as a code comment that claims an invariant no test enforces
|
|
310
|
+
"mistake as a code comment that claims an invariant no test enforces~\\cite{thompson1984}.",
|
|
302
311
|
"",
|
|
303
|
-
"
|
|
312
|
+
"\\section{Method}",
|
|
304
313
|
"We gather rules from public repositories, classify each by enforceability, and build a",
|
|
305
314
|
"two-stage adversarial gate that validates a synthesized rule against a blind gold set.",
|
|
306
315
|
"",
|
|
307
|
-
"
|
|
308
|
-
"See Table
|
|
316
|
+
"\\section{Results}",
|
|
317
|
+
"See Table~1. The headline number is 84\\%.",
|
|
309
318
|
"",
|
|
310
|
-
"
|
|
319
|
+
"\\section{Discussion}",
|
|
311
320
|
"The result generalizes beyond our corpus in the sense that the mechanism is not corpus-specific,",
|
|
312
321
|
"though of course the specific percentages are, and it is important to note in this context that",
|
|
313
322
|
"the framing itself may be what carries, rather than the measurement.",
|
|
314
323
|
"",
|
|
315
|
-
"
|
|
324
|
+
"\\section{Threats to Validity}",
|
|
316
325
|
"Our corpus is drawn from public repositories and may not represent private configurations.",
|
|
317
326
|
"",
|
|
318
|
-
"
|
|
327
|
+
"\\section{Related Work}",
|
|
319
328
|
"TODO",
|
|
320
329
|
"",
|
|
321
|
-
"
|
|
330
|
+
"\\section{Conclusion}",
|
|
322
331
|
"Prose is not policy.",
|
|
332
|
+
"\\end{document}",
|
|
323
333
|
].join("\n"),
|
|
324
334
|
"paper/repro/README.md":
|
|
325
|
-
"# Reproduction artifact\n\n`python3 paper_numbers.py` recomputes every bolded figure in paper.
|
|
335
|
+
"# Reproduction artifact\n\n`python3 paper_numbers.py` recomputes every bolded figure in paper.tex.\n",
|
|
326
336
|
"paper/PIPELINE-STATUS.md":
|
|
327
337
|
"# Pipeline status\n\n| gate | state |\n|---|---|\n| numbers | pass |\n| structure | not run |\n| citations | not run |\n",
|
|
328
338
|
};
|