paperlint 10.3.0 → 10.4.1
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 +15 -4
- package/README.md +1 -0
- package/dist/CLAUDE.md.spec.d.ts.map +1 -1
- package/dist/CLAUDE.md.spec.js +6 -3
- package/dist/CLAUDE.md.spec.js.map +1 -1
- package/dist/adapters/git/index.d.ts +10 -0
- package/dist/adapters/git/index.d.ts.map +1 -0
- package/dist/adapters/git/index.js +151 -0
- package/dist/adapters/git/index.js.map +1 -0
- package/dist/adapters/latex/captions.d.ts +9 -0
- package/dist/adapters/latex/captions.d.ts.map +1 -0
- package/dist/adapters/latex/captions.js +40 -0
- package/dist/adapters/latex/captions.js.map +1 -0
- package/dist/adapters/latex/index.d.ts +1 -0
- package/dist/adapters/latex/index.d.ts.map +1 -1
- package/dist/adapters/latex/index.js +1 -0
- package/dist/adapters/latex/index.js.map +1 -1
- package/dist/adapters/latex/parse.d.ts.map +1 -1
- package/dist/adapters/latex/parse.js +6 -0
- package/dist/adapters/latex/parse.js.map +1 -1
- package/dist/adapters/memory/index.d.ts +6 -0
- package/dist/adapters/memory/index.d.ts.map +1 -1
- package/dist/adapters/memory/index.js +8 -0
- package/dist/adapters/memory/index.js.map +1 -1
- package/dist/adapters/node/index.d.ts +1 -0
- package/dist/adapters/node/index.d.ts.map +1 -1
- package/dist/adapters/node/index.js +1 -0
- package/dist/adapters/node/index.js.map +1 -1
- package/dist/adapters/node/list-entries.io.d.ts +3 -0
- package/dist/adapters/node/list-entries.io.d.ts.map +1 -0
- package/dist/adapters/node/list-entries.io.js +26 -0
- package/dist/adapters/node/list-entries.io.js.map +1 -0
- package/dist/adapters/study-registry/index.d.ts +70 -10
- package/dist/adapters/study-registry/index.d.ts.map +1 -1
- package/dist/adapters/study-registry/index.js +84 -10
- package/dist/adapters/study-registry/index.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +33 -3
- package/dist/cli.js.map +1 -1
- package/dist/domain/decide-first.d.ts +110 -0
- package/dist/domain/decide-first.d.ts.map +1 -0
- package/dist/domain/decide-first.js +108 -0
- package/dist/domain/decide-first.js.map +1 -0
- package/dist/domain/index-columns.d.ts +45 -0
- package/dist/domain/index-columns.d.ts.map +1 -0
- package/dist/domain/index-columns.js +64 -0
- package/dist/domain/index-columns.js.map +1 -0
- package/dist/domain/study-mode.d.ts +37 -0
- package/dist/domain/study-mode.d.ts.map +1 -0
- package/dist/domain/study-mode.js +45 -0
- package/dist/domain/study-mode.js.map +1 -0
- package/dist/domain/study-registry.d.ts +116 -12
- package/dist/domain/study-registry.d.ts.map +1 -1
- package/dist/domain/study-registry.js +164 -44
- package/dist/domain/study-registry.js.map +1 -1
- package/dist/outputs-before-decision-rule.d.ts +17 -0
- package/dist/outputs-before-decision-rule.d.ts.map +1 -0
- package/dist/outputs-before-decision-rule.js +94 -0
- package/dist/outputs-before-decision-rule.js.map +1 -0
- package/dist/ports/git-history.d.ts +44 -0
- package/dist/ports/git-history.d.ts.map +1 -0
- package/dist/ports/git-history.js +2 -0
- package/dist/ports/git-history.js.map +1 -0
- package/dist/ports/list-entries.d.ts +20 -0
- package/dist/ports/list-entries.d.ts.map +1 -0
- package/dist/ports/list-entries.js +2 -0
- package/dist/ports/list-entries.js.map +1 -0
- package/dist/ports/study-deps.d.ts +14 -0
- package/dist/ports/study-deps.d.ts.map +1 -0
- package/dist/ports/study-deps.js +2 -0
- package/dist/ports/study-deps.js.map +1 -0
- package/dist/ports/study-folder.d.ts +43 -0
- package/dist/ports/study-folder.d.ts.map +1 -0
- package/dist/ports/study-folder.js +2 -0
- package/dist/ports/study-folder.js.map +1 -0
- package/dist/ports/study-registry-codec.d.ts +6 -3
- package/dist/ports/study-registry-codec.d.ts.map +1 -1
- package/dist/presets.d.ts +1 -2
- package/dist/presets.d.ts.map +1 -1
- package/dist/presets.js +1 -2
- package/dist/presets.js.map +1 -1
- package/dist/studies.d.ts +4 -0
- package/dist/studies.d.ts.map +1 -1
- package/dist/studies.js +52 -21
- package/dist/studies.js.map +1 -1
- package/dist/study-folder.d.ts +14 -0
- package/dist/study-folder.d.ts.map +1 -0
- package/dist/study-folder.js +123 -0
- package/dist/study-folder.js.map +1 -0
- package/dist/study-gate.d.ts +14 -0
- package/dist/study-gate.d.ts.map +1 -0
- package/dist/study-gate.js +132 -0
- package/dist/study-gate.js.map +1 -0
- package/dist/study-index-input.d.ts +5 -0
- package/dist/study-index-input.d.ts.map +1 -0
- package/dist/study-index-input.js +18 -0
- package/dist/study-index-input.js.map +1 -0
- package/dist/study-modes.d.ts +56 -0
- package/dist/study-modes.d.ts.map +1 -0
- package/dist/study-modes.js +59 -0
- package/dist/study-modes.js.map +1 -0
- package/dist/study-registry-rule.d.ts +13 -1
- package/dist/study-registry-rule.d.ts.map +1 -1
- package/dist/study-registry-rule.js +33 -21
- package/dist/study-registry-rule.js.map +1 -1
- package/dist/study-run.d.ts +19 -0
- package/dist/study-run.d.ts.map +1 -0
- package/dist/study-run.js +9 -0
- package/dist/study-run.js.map +1 -0
- package/docs/design/study-modes.md +265 -0
- package/docs/design/study-registry.md +6 -0
- package/docs/e2e.md +8 -4
- package/docs/layout.md +22 -19
- package/docs/rules/repro/outputs-before-decision.md +122 -0
- package/docs/rules/repro/study-registered.md +49 -15
- package/docs/rules/tex/heading-case.md +1 -1
- package/docs/rules.md +54 -53
- package/docs/testing.md +1 -0
- package/eslint.config.mjs +36 -24
- package/package.json +2 -1
- package/scripts/check.ts +6 -0
- package/skills/camera-ready/SKILL.md +6 -1
- package/skills/camera-ready/SKILL.md.spec.ts +5 -0
- package/skills/draft-paper/SKILL.md +11 -3
- package/skills/draft-paper/SKILL.md.spec.ts +10 -2
- package/skills/grade-paper-writing/SKILL.md +4 -2
- package/skills/grade-paper-writing/SKILL.md.spec.ts +3 -1
- package/skills/grade-paper-writing/__snapshots__/prose-lint.test.ts.snap +16 -3
- package/skills/grade-paper-writing/prose-lint.mjs +127 -95
- package/skills/grade-paper-writing/prose-lint.test.ts +119 -10
- package/skills/harden-paper/SKILL.md +12 -2
- package/skills/harden-paper/SKILL.md.spec.ts +11 -1
- package/skills/osf-artifact-upload/SKILL.md +7 -1
- package/skills/osf-artifact-upload/SKILL.md.spec.ts +6 -0
- package/skills/pc-panel-review/SKILL.md +10 -3
- package/skills/pc-panel-review/SKILL.md.spec.ts +9 -2
- package/skills/tighten-paper/SKILL.md +5 -1
- package/skills/tighten-paper/SKILL.md.spec.ts +4 -0
- package/src/CLAUDE.md +7 -4
- package/src/CLAUDE.md.spec.ts +6 -3
- package/src/adapters/git/history.test.ts +121 -0
- package/src/adapters/git/index.test.ts +268 -0
- package/src/adapters/git/index.ts +227 -0
- package/src/adapters/latex/captions.test.ts +57 -0
- package/src/adapters/latex/captions.ts +56 -0
- package/src/adapters/latex/index.ts +1 -0
- package/src/adapters/latex/parse.ts +7 -0
- package/src/adapters/memory/index.test.ts +15 -1
- package/src/adapters/memory/index.ts +13 -0
- package/src/adapters/node/index.ts +1 -0
- package/src/adapters/node/list-entries.io.ts +27 -0
- package/src/adapters/node/list-entries.test.ts +61 -0
- package/src/adapters/study-registry/index.test.ts +12 -9
- package/src/adapters/study-registry/index.ts +130 -24
- package/src/adapters/study-registry/mode.test.ts +162 -0
- package/src/cli.ts +47 -3
- package/src/domain/decide-first.test.ts +291 -0
- package/src/domain/decide-first.ts +258 -0
- package/src/domain/index-columns.test.ts +261 -0
- package/src/domain/index-columns.ts +107 -0
- package/src/domain/study-measurements.test.ts +16 -8
- package/src/domain/study-mode.test.ts +55 -0
- package/src/domain/study-mode.ts +78 -0
- package/src/domain/study-registry.test.ts +72 -37
- package/src/domain/study-registry.ts +281 -50
- package/src/new-paper.test.ts +5 -1
- package/src/outputs-before-decision-rule.test.ts +251 -0
- package/src/outputs-before-decision-rule.ts +133 -0
- package/src/ports/git-history.ts +44 -0
- package/src/ports/list-entries.ts +21 -0
- package/src/ports/study-deps.ts +14 -0
- package/src/ports/study-folder.ts +38 -0
- package/src/ports/study-registry-codec.ts +10 -3
- package/src/presets.ts +1 -2
- package/src/rule-cases.test.ts +78 -3
- package/src/studies-index-modes.test.ts +129 -0
- package/src/studies.test.ts +35 -11
- package/src/studies.ts +96 -26
- package/src/study-folder.test.ts +237 -0
- package/src/study-folder.ts +176 -0
- package/src/study-gate.test.ts +467 -0
- package/src/study-gate.ts +234 -0
- package/src/study-index-input.ts +31 -0
- package/src/study-modes.ts +107 -0
- package/src/study-registry-rule.test.ts +285 -11
- package/src/study-registry-rule.ts +64 -35
- package/src/study-run.ts +21 -0
|
@@ -8,7 +8,7 @@ import { join } from "node:path";
|
|
|
8
8
|
import { dirname } from "node:path";
|
|
9
9
|
import { fileURLToPath } from "node:url";
|
|
10
10
|
import { expect, test } from "vitest";
|
|
11
|
-
import { cpSync } from "node:fs";
|
|
11
|
+
import { cpSync, readFileSync } from "node:fs";
|
|
12
12
|
import { layRecord, recordedTree } from "../../test/recorded-fixture.ts";
|
|
13
13
|
import { runNode, useTempDir, writeTree } from "../../test/support.ts";
|
|
14
14
|
|
|
@@ -62,6 +62,16 @@ const TREE = {
|
|
|
62
62
|
"commented/paper.tex": tex(
|
|
63
63
|
`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}`,
|
|
64
64
|
),
|
|
65
|
+
// The caption command with an optional argument, and with a space before its brace.
|
|
66
|
+
"optarg/paper.tex": tex(
|
|
67
|
+
`Prose here. \\begin{figure}\\caption[Short]{${LONG_CAPTION_SENTENCE}.}\\end{figure}`,
|
|
68
|
+
),
|
|
69
|
+
"spaced/paper.tex": tex(
|
|
70
|
+
`Prose here. \\begin{figure}\\caption {${LONG_CAPTION_SENTENCE}.}\\end{figure}`,
|
|
71
|
+
),
|
|
72
|
+
"negated/paper.tex": tex(
|
|
73
|
+
"It is not fast. It is not small. It is not red. Nothing here is plain.",
|
|
74
|
+
),
|
|
65
75
|
"ends-on-marks/paper.tex": tex(
|
|
66
76
|
"The method holds on every input~\\cite{knuth}. The proof is given in full (Section~\\ref{proof}). The result is new.",
|
|
67
77
|
),
|
|
@@ -83,6 +93,9 @@ const READ: Readonly<Record<string, readonly string[]>> = {
|
|
|
83
93
|
inline: ["paper.tex", "sec/results.tex"],
|
|
84
94
|
commented: ["paper.tex"],
|
|
85
95
|
"ends-on-marks": ["paper.tex"],
|
|
96
|
+
negated: ["paper.tex"],
|
|
97
|
+
optarg: ["paper.tex"],
|
|
98
|
+
spaced: ["paper.tex"],
|
|
86
99
|
empty: ["paper.tex"],
|
|
87
100
|
};
|
|
88
101
|
writeTree(
|
|
@@ -182,6 +195,23 @@ test("--flags-only: an over-long caption flags and exits 1", () => {
|
|
|
182
195
|
expect(r.stderr).toMatchSnapshot();
|
|
183
196
|
});
|
|
184
197
|
|
|
198
|
+
test("--flags-only: a metric over its threshold (not a caption) is a finding too, and the report flags the same line", () => {
|
|
199
|
+
const file = join(root, "negated", "paper.tex");
|
|
200
|
+
const only = runNode(SCRIPT, ["--flags-only", file]);
|
|
201
|
+
const report = runNode(SCRIPT, [file]);
|
|
202
|
+
// Guards: the mode promised "print nothing on a clean paper, any output means a finding" and
|
|
203
|
+
// looked only at captions; the metrics the report FLAGs are findings of the same kind.
|
|
204
|
+
assert.deepEqual(
|
|
205
|
+
{ status: only.status, stdout: only.stdout },
|
|
206
|
+
{ status: 1, stdout: "" },
|
|
207
|
+
);
|
|
208
|
+
expect(only.stderr).toMatchSnapshot();
|
|
209
|
+
assert.match(
|
|
210
|
+
report.stdout,
|
|
211
|
+
/^FLAG sentences whose main claim is a negation: 75%$/m,
|
|
212
|
+
);
|
|
213
|
+
});
|
|
214
|
+
|
|
185
215
|
test("--flags-only: a paper with no figures directory is clean, exit 0 and silent", () => {
|
|
186
216
|
assert.deepEqual(
|
|
187
217
|
runNode(SCRIPT, ["--flags-only", join(root, "clean", "paper.tex")]),
|
|
@@ -248,17 +278,13 @@ test("🔴 --flags-only reads the captions of the paper.tex and of the files it
|
|
|
248
278
|
stdout: r.stdout,
|
|
249
279
|
flagged: r.stderr
|
|
250
280
|
.split("\n")
|
|
251
|
-
.filter((l) => l.startsWith("
|
|
281
|
+
.filter((l) => l.startsWith(" > "))
|
|
252
282
|
.map((l) => l.split(":")[0]),
|
|
253
283
|
},
|
|
254
284
|
{
|
|
255
285
|
status: 1,
|
|
256
286
|
stdout: "",
|
|
257
|
-
flagged: [
|
|
258
|
-
" paper.tex",
|
|
259
|
-
" sec/results.tex",
|
|
260
|
-
" run prose-lint.mjs on the file without --flags-only for the sentences",
|
|
261
|
-
],
|
|
287
|
+
flagged: [" > paper.tex", " > sec/results.tex"],
|
|
262
288
|
},
|
|
263
289
|
);
|
|
264
290
|
});
|
|
@@ -274,8 +300,14 @@ test("--flags-only on a rendered page reads the captions in figures/*.tex beside
|
|
|
274
300
|
join(root, "rendered", "page.txt"),
|
|
275
301
|
]);
|
|
276
302
|
assert.deepEqual(
|
|
277
|
-
{
|
|
278
|
-
|
|
303
|
+
{
|
|
304
|
+
status: r.status,
|
|
305
|
+
first: r.stderr
|
|
306
|
+
.split("\n")
|
|
307
|
+
.find((l) => l.startsWith(" > "))
|
|
308
|
+
?.split(":")[0],
|
|
309
|
+
},
|
|
310
|
+
{ status: 1, first: " > figures/f.tex" },
|
|
279
311
|
);
|
|
280
312
|
// …and a page with no figures/ beside it has no caption to flag.
|
|
281
313
|
assert.deepEqual(runNode(SCRIPT, ["--flags-only", join(root, "page.txt")]), {
|
|
@@ -296,9 +328,86 @@ test("🔴 a caption commented out or quoted in verbatim is not measured; a real
|
|
|
296
328
|
status: r.status,
|
|
297
329
|
findings: r.stderr
|
|
298
330
|
.split("\n")
|
|
299
|
-
.filter((l) => l.startsWith("
|
|
331
|
+
.filter((l) => l.startsWith(" > paper.tex:"))
|
|
300
332
|
.map((l) => /— "(\w+)/.exec(l)?.[1]),
|
|
301
333
|
},
|
|
302
334
|
{ status: 1, findings: ["Real"] },
|
|
303
335
|
);
|
|
304
336
|
});
|
|
337
|
+
|
|
338
|
+
/** The `prose-lint.mjs` invocations SKILL.md documents (Step 6), their arguments with `<paper-dir>` filled in. */
|
|
339
|
+
const documentedInvocations = (paperDir: string): readonly string[][] =>
|
|
340
|
+
readFileSync(join(dirname(SCRIPT), "SKILL.md"), "utf8")
|
|
341
|
+
.split("\n")
|
|
342
|
+
.flatMap((line) => {
|
|
343
|
+
const m =
|
|
344
|
+
/node \$\{CLAUDE_SKILL_DIR\}\/prose-lint\.mjs ([^#]+?)\s*(#|$)/.exec(
|
|
345
|
+
line,
|
|
346
|
+
);
|
|
347
|
+
return m?.[1] === undefined
|
|
348
|
+
? []
|
|
349
|
+
: [m[1].replaceAll("<paper-dir>", paperDir).split(/\s+/)];
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
test("🔴 the invocation SKILL.md documents reports an over-long caption, with no flag the skill does not name", () => {
|
|
353
|
+
const documented = documentedInvocations(join(root, "p"));
|
|
354
|
+
// Guards: the report is the documented way to run the checks (Step 6), and captions were
|
|
355
|
+
// measured only under an undocumented flag — an over-long caption produced no finding.
|
|
356
|
+
const report = documented.find((a) => !a.includes("--headings"));
|
|
357
|
+
assert.ok(report !== undefined, "SKILL.md documents a report invocation");
|
|
358
|
+
const r = runNode(SCRIPT, report);
|
|
359
|
+
assert.deepEqual(
|
|
360
|
+
{
|
|
361
|
+
status: r.status,
|
|
362
|
+
flagged: r.stdout
|
|
363
|
+
.split("\n")
|
|
364
|
+
.filter(
|
|
365
|
+
(l) => l.startsWith("FLAG figure captions") || l.includes("caption"),
|
|
366
|
+
),
|
|
367
|
+
},
|
|
368
|
+
{
|
|
369
|
+
status: 0,
|
|
370
|
+
flagged: [
|
|
371
|
+
"FLAG figure captions over the limit: 2",
|
|
372
|
+
' > figures/fig1.tex: caption sentence of 46 words (warn >40) — "Big. word0 word1 word2 word3 word4 word5 word6 word7 word8 word9 word10 word11 word12 word13 word14 word15 wor…"',
|
|
373
|
+
" > figures/fig1.tex: caption is 106 words (warn >100) — a caption this long is a section under a picture",
|
|
374
|
+
],
|
|
375
|
+
},
|
|
376
|
+
);
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
test("the report says a paper whose captions are within the limits is clean on that line", () => {
|
|
380
|
+
const r = runNode(SCRIPT, [join(root, "clean", "paper.tex")]);
|
|
381
|
+
// Guards: the line is printed on a clean paper too, so its absence is never read as a pass.
|
|
382
|
+
assert.match(r.stdout, /^ok {3}figure captions over the limit: 0$/m);
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
test("🔴 a caption with an optional argument, or a space before its brace, is measured", () => {
|
|
386
|
+
const found = (dir: string) => {
|
|
387
|
+
const r = runNode(SCRIPT, [join(root, dir, "paper.tex")]);
|
|
388
|
+
return [
|
|
389
|
+
r.status,
|
|
390
|
+
/^FLAG figure captions over the limit: (\d+)$/m.exec(r.stdout)?.[1],
|
|
391
|
+
];
|
|
392
|
+
};
|
|
393
|
+
// Guards: the scan matched `\caption{` only, so `\caption[Short]{…}` and `\caption {…}` were never
|
|
394
|
+
// read and the report printed "ok ... 0" for a long caption.
|
|
395
|
+
expect([found("optarg"), found("spaced")]).toEqual([
|
|
396
|
+
[0, "1"],
|
|
397
|
+
[0, "1"],
|
|
398
|
+
]);
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
test("🔴 the caption of the package's own p1 fixture, which has an optional argument, is reported", () => {
|
|
402
|
+
const p1 = join(
|
|
403
|
+
dirname(fileURLToPath(import.meta.url)),
|
|
404
|
+
"..",
|
|
405
|
+
"..",
|
|
406
|
+
"fixtures",
|
|
407
|
+
"paper-sources",
|
|
408
|
+
"p1",
|
|
409
|
+
"paper.tex",
|
|
410
|
+
);
|
|
411
|
+
const r = runNode(SCRIPT, ["--flags-only", p1]);
|
|
412
|
+
assert.match(r.stderr, /— "Optional-arg caption this sentence/);
|
|
413
|
+
});
|
|
@@ -4,7 +4,7 @@ description: Use when asking "is this paper actually ready to submit?" / "what's
|
|
|
4
4
|
allowed-tools: [Read, Write, Edit, Grep, Glob, Bash, WebSearch, WebFetch, Agent, Skill]
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
<!-- vigiles:sha256:
|
|
7
|
+
<!-- vigiles:sha256:85001dd768358bb5 compiled from skills/harden-paper/SKILL.md.spec.ts -->
|
|
8
8
|
|
|
9
9
|
# harden-paper — the multi-axis pre-submit gate
|
|
10
10
|
|
|
@@ -61,8 +61,18 @@ middle sags" — wasting the polish on possibly-cut sections.
|
|
|
61
61
|
there with no pointer reaching it, the body is overclaiming and "it's in the appendix" is not
|
|
62
62
|
a defence — reviewers are not required to read appendices, and ARR says so in as many words.
|
|
63
63
|
Rule and the sorting test: `paper-pipeline/references/body-vs-appendix.md`.
|
|
64
|
+
- 🔴 **A confirmatory claim on a study that was not confirmatory.** Every claim the paper presents
|
|
65
|
+
as tested — a prediction, a hypothesis confirmed, a headline effect — must rest on a study whose
|
|
66
|
+
`mode` is `decide_before_run` and whose `decision` commit precedes its `first output`, read
|
|
67
|
+
from the index in `repro/README.md` after `npx paperlint studies index <paper-dir>` (the plans and git, not
|
|
68
|
+
the prose). A confirmatory claim on an exploratory study, on one whose decision reads `not
|
|
69
|
+
committed`, or on one whose first output is not after its decision, is a **soundness** finding,
|
|
70
|
+
not a wording one: re-label the result exploratory, or rest the claim on a study decided first.
|
|
71
|
+
Flag every "pre-registered", "preregistered", "registered in advance" or "decided before" the
|
|
72
|
+
table does not support, and check the Methods sentence names each study's mode as the table does.
|
|
64
73
|
Mechanical leg: `tex/claim-provenance` (axis 7) reports every sentence that states a number and
|
|
65
|
-
does not say whose it is
|
|
74
|
+
does not say whose it is; `repro/outputs-before-decision` every output that entered history
|
|
75
|
+
before its decision.
|
|
66
76
|
|
|
67
77
|
3. **Novelty vs the nearest neighbor** — the scoop check. Search for the paper that *already did your
|
|
68
78
|
thing* (`verify-citations` covers metadata; here, actively hunt the closest empirical/method twin).
|
|
@@ -78,8 +78,18 @@ middle sags" — wasting the polish on possibly-cut sections.
|
|
|
78
78
|
there with no pointer reaching it, the body is overclaiming and "it's in the appendix" is not
|
|
79
79
|
a defence — reviewers are not required to read appendices, and ARR says so in as many words.
|
|
80
80
|
Rule and the sorting test: \`paper-pipeline/references/body-vs-appendix.md\`.
|
|
81
|
+
- 🔴 **A confirmatory claim on a study that was not confirmatory.** Every claim the paper presents
|
|
82
|
+
as tested — a prediction, a hypothesis confirmed, a headline effect — must rest on a study whose
|
|
83
|
+
\`mode\` is \`decide_before_run\` and whose \`decision\` commit precedes its \`first output\`, read
|
|
84
|
+
from the index in \`repro/README.md\` after \`npx paperlint studies index <paper-dir>\` (the plans and git, not
|
|
85
|
+
the prose). A confirmatory claim on an exploratory study, on one whose decision reads \`not
|
|
86
|
+
committed\`, or on one whose first output is not after its decision, is a **soundness** finding,
|
|
87
|
+
not a wording one: re-label the result exploratory, or rest the claim on a study decided first.
|
|
88
|
+
Flag every "pre-registered", "preregistered", "registered in advance" or "decided before" the
|
|
89
|
+
table does not support, and check the Methods sentence names each study's mode as the table does.
|
|
81
90
|
Mechanical leg: \`tex/claim-provenance\` (axis 7) reports every sentence that states a number and
|
|
82
|
-
does not say whose it is
|
|
91
|
+
does not say whose it is; \`repro/outputs-before-decision\` every output that entered history
|
|
92
|
+
before its decision.
|
|
83
93
|
|
|
84
94
|
3. **Novelty vs the nearest neighbor** — the scoop check. Search for the paper that *already did your
|
|
85
95
|
thing* (\`verify-citations\` covers metadata; here, actively hunt the closest empirical/method twin).
|
|
@@ -4,7 +4,7 @@ description: Upload or replace a paper's reproduction artifact file on an OSF pr
|
|
|
4
4
|
allowed-tools: [Read, Write, Grep, Glob, Bash]
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
<!-- vigiles:sha256:
|
|
7
|
+
<!-- vigiles:sha256:953fdd419dbc789c compiled from skills/osf-artifact-upload/SKILL.md.spec.ts -->
|
|
8
8
|
|
|
9
9
|
# osf-artifact-upload — push/replace a file on an OSF project via API
|
|
10
10
|
|
|
@@ -45,6 +45,12 @@ curl -g -s --retry 3 --cacert $CA -H "$AUTH" -X PUT --data-binary @"$ZIP" "${UP}
|
|
|
45
45
|
Verification that actually holds: **OSF-stored md5 == `md5sum $ZIP`**. The upload response also returns the
|
|
46
46
|
md5; matching it to the local file is proof even when the download-back call flakes.
|
|
47
47
|
|
|
48
|
+
## What the artifact ships about each study
|
|
49
|
+
Before zipping, the artifact carries, for every study the paper reports: its `plan.json` (its mode)
|
|
50
|
+
and its decision file; and `repro/README.md` with the index regenerated (`npx paperlint studies index <paper-dir>`)
|
|
51
|
+
— each study's mode, decision commit and first output — with the command that re-checks the order
|
|
52
|
+
(`npx paperlint lint` on a full clone). A zip without them asks a reviewer to take the modes on trust.
|
|
53
|
+
|
|
48
54
|
## After
|
|
49
55
|
- Confirm the paper's Availability `\url{}` still points at the same node (it does — same guid).
|
|
50
56
|
- Tell the user to **revoke the token** (osf.io/settings/tokens → Delete).
|
|
@@ -51,6 +51,12 @@ curl -g -s --retry 3 --cacert $CA -H "$AUTH" -X PUT --data-binary @"$ZIP" "\${UP
|
|
|
51
51
|
Verification that actually holds: **OSF-stored md5 == \`md5sum $ZIP\`**. The upload response also returns the
|
|
52
52
|
md5; matching it to the local file is proof even when the download-back call flakes.
|
|
53
53
|
|
|
54
|
+
## What the artifact ships about each study
|
|
55
|
+
Before zipping, the artifact carries, for every study the paper reports: its \`plan.json\` (its mode)
|
|
56
|
+
and its decision file; and \`repro/README.md\` with the index regenerated (\`npx paperlint studies index <paper-dir>\`)
|
|
57
|
+
— each study's mode, decision commit and first output — with the command that re-checks the order
|
|
58
|
+
(\`npx paperlint lint\` on a full clone). A zip without them asks a reviewer to take the modes on trust.
|
|
59
|
+
|
|
54
60
|
## After
|
|
55
61
|
- Confirm the paper's Availability \`\\url{}\` still points at the same node (it does — same guid).
|
|
56
62
|
- Tell the user to **revoke the token** (osf.io/settings/tokens → Delete).
|
|
@@ -4,7 +4,7 @@ description: Use when asking "what would the program committee decide?" / "simul
|
|
|
4
4
|
allowed-tools: [Read, Write, Grep, Glob, Bash, WebSearch, WebFetch, Agent, Skill]
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
<!-- vigiles:sha256:
|
|
7
|
+
<!-- vigiles:sha256:d92a744ec1cc1b05 compiled from skills/pc-panel-review/SKILL.md.spec.ts -->
|
|
8
8
|
|
|
9
9
|
# pc-panel-review — model the whole PC, not one reviewer
|
|
10
10
|
|
|
@@ -61,11 +61,18 @@ prose-only read can.
|
|
|
61
61
|
and calls the paper a blog post — the one reviewer a panel of careful full readers never
|
|
62
62
|
predicts. Give it the three sections as text and the venue's name, nothing else.
|
|
63
63
|
- **Methods/stats skeptic** — sample size, construct validity, multiple comparisons, overclaims,
|
|
64
|
-
whether the CIs support the claims
|
|
64
|
+
whether the CIs support the claims, and **whether every confirmatory claim rests on a study
|
|
65
|
+
decided before its run**. Run `npx paperlint studies index <paper-dir>` and give this reviewer the index in
|
|
66
|
+
`repro/README.md` (each study's `mode`, `decision` and `first output`) beside the paper: a
|
|
67
|
+
claim of a tested prediction on an exploratory study, on one whose decision reads `not
|
|
68
|
+
committed`, or on one whose first output is not after its decision is a soundness finding, and
|
|
69
|
+
so is a "pre-registered" or "decided before" the table does not support; the reviewer names it.
|
|
65
70
|
- **Domain practitioner** — relevance to the CFP, novelty vs the nearest prior work, does the
|
|
66
71
|
finding change behavior, generalizability, fairness/tone toward anyone named.
|
|
67
72
|
- **Artifact / reproducibility reviewer (MANDATORY if an artifact is attached)** — *actually
|
|
68
|
-
`cd` into the artifact and run it*; verify every headline number reproduces;
|
|
73
|
+
`cd` into the artifact and run it*; verify every headline number reproduces; re-run the order
|
|
74
|
+
check the paper's decided-before claims rest on (`npx paperlint lint` on a full clone); read the
|
|
75
|
+
code for
|
|
69
76
|
circularity (echoing stored fields vs recomputing); grep all files (incl. data + compiled
|
|
70
77
|
caches) for anonymization leaks; scope the badge (Available / Functional / Reproduced).
|
|
71
78
|
- **Security/ethics reviewer** (security venues) — threat model soundness, responsible disclosure,
|
|
@@ -77,11 +77,18 @@ prose-only read can.
|
|
|
77
77
|
and calls the paper a blog post — the one reviewer a panel of careful full readers never
|
|
78
78
|
predicts. Give it the three sections as text and the venue's name, nothing else.
|
|
79
79
|
- **Methods/stats skeptic** — sample size, construct validity, multiple comparisons, overclaims,
|
|
80
|
-
whether the CIs support the claims
|
|
80
|
+
whether the CIs support the claims, and **whether every confirmatory claim rests on a study
|
|
81
|
+
decided before its run**. Run \`npx paperlint studies index <paper-dir>\` and give this reviewer the index in
|
|
82
|
+
\`repro/README.md\` (each study's \`mode\`, \`decision\` and \`first output\`) beside the paper: a
|
|
83
|
+
claim of a tested prediction on an exploratory study, on one whose decision reads \`not
|
|
84
|
+
committed\`, or on one whose first output is not after its decision is a soundness finding, and
|
|
85
|
+
so is a "pre-registered" or "decided before" the table does not support; the reviewer names it.
|
|
81
86
|
- **Domain practitioner** — relevance to the CFP, novelty vs the nearest prior work, does the
|
|
82
87
|
finding change behavior, generalizability, fairness/tone toward anyone named.
|
|
83
88
|
- **Artifact / reproducibility reviewer (MANDATORY if an artifact is attached)** — *actually
|
|
84
|
-
\`cd\` into the artifact and run it*; verify every headline number reproduces;
|
|
89
|
+
\`cd\` into the artifact and run it*; verify every headline number reproduces; re-run the order
|
|
90
|
+
check the paper's decided-before claims rest on (\`npx paperlint lint\` on a full clone); read the
|
|
91
|
+
code for
|
|
85
92
|
circularity (echoing stored fields vs recomputing); grep all files (incl. data + compiled
|
|
86
93
|
caches) for anonymization leaks; scope the badge (Available / Functional / Reproduced).
|
|
87
94
|
- **Security/ethics reviewer** (security venues) — threat model soundness, responsible disclosure,
|
|
@@ -4,7 +4,7 @@ description: "Use when a paper \"feels long / bloated / overly complex / hard to
|
|
|
4
4
|
allowed-tools: [Read, Write, Edit, Grep, Glob, Agent]
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
<!-- vigiles:sha256:
|
|
7
|
+
<!-- vigiles:sha256:ccf39ff26a2146e9 compiled from skills/tighten-paper/SKILL.md.spec.ts -->
|
|
8
8
|
|
|
9
9
|
# tighten-paper — the developmental edit
|
|
10
10
|
|
|
@@ -149,6 +149,10 @@ called it "fat for no reason" after a pass that cut almost nothing and left it 1
|
|
|
149
149
|
measurement paper's fat HIDES as explanation of real numbers, which is exactly why "it's all numbers" lets it
|
|
150
150
|
survive a lazy pass (GateBench 2026-07-25: a pass cut only 5% hiding behind this; the author correctly pushed
|
|
151
151
|
back that whole sections were over-explained, not just number-dense).
|
|
152
|
+
- **The study-mode sentence is never cut for length.** The Methods sentence that says a study was
|
|
153
|
+
decided before its run (with its decision's commit and date, from the index in `repro/README.md`)
|
|
154
|
+
or is exploratory — and the word "exploratory" beside an exploratory number — is the claim's
|
|
155
|
+
warrant, not its explanation: cut around it, never it.
|
|
152
156
|
- **"Fold to the artifact" is the PRIMARY length lever for a measurement/benchmark paper — not a last resort.**
|
|
153
157
|
FIRST verify the artifact exists and recomputes the numbers (it usually does for these papers). Then per-case
|
|
154
158
|
breakdowns, secondary analyses, exhaustive robustness sub-results, ablation walkthroughs, and every "we also
|
|
@@ -155,6 +155,10 @@ called it "fat for no reason" after a pass that cut almost nothing and left it 1
|
|
|
155
155
|
measurement paper's fat HIDES as explanation of real numbers, which is exactly why "it's all numbers" lets it
|
|
156
156
|
survive a lazy pass (GateBench 2026-07-25: a pass cut only 5% hiding behind this; the author correctly pushed
|
|
157
157
|
back that whole sections were over-explained, not just number-dense).
|
|
158
|
+
- **The study-mode sentence is never cut for length.** The Methods sentence that says a study was
|
|
159
|
+
decided before its run (with its decision's commit and date, from the index in \`repro/README.md\`)
|
|
160
|
+
or is exploratory — and the word "exploratory" beside an exploratory number — is the claim's
|
|
161
|
+
warrant, not its explanation: cut around it, never it.
|
|
158
162
|
- **"Fold to the artifact" is the PRIMARY length lever for a measurement/benchmark paper — not a last resort.**
|
|
159
163
|
FIRST verify the artifact exists and recomputes the numbers (it usually does for these papers). Then per-case
|
|
160
164
|
breakdowns, secondary analyses, exhaustive robustness sub-results, ablation walkthroughs, and every "we also
|
package/src/CLAUDE.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<!-- vigiles:sha256:
|
|
1
|
+
<!-- vigiles:sha256:599c83e36edde981 compiled from src/CLAUDE.md.spec.ts -->
|
|
2
2
|
|
|
3
3
|
# CLAUDE.md
|
|
4
4
|
|
|
@@ -21,12 +21,15 @@ test — a parser for banal's JSON is pure and is still banal's.
|
|
|
21
21
|
|
|
22
22
|
| element | where | holds | may import |
|
|
23
23
|
| --------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------- |
|
|
24
|
-
| `domain` | `src/domain/` | values and decisions in the paper's vocabulary | `domain`;
|
|
25
|
-
| `port` | `src/ports/` | interfaces **shaped by what the inside needs**, never by what a tool does (Graça: "created to fit the Application Core needs and not simply mimic the tools APIs") | `domain`, `port`; the same
|
|
26
|
-
| `app` | `src/*.ts` | use cases over ports; **no I/O defaults** — a use case without its deps does not compile | `domain`, `port`, `app`, the package's `.mjs`;
|
|
24
|
+
| `domain` | `src/domain/` | values and decisions in the paper's vocabulary | `domain`; any package or Node built-in that is no tool package (`TOOL_PACKAGES`) and no effect |
|
|
25
|
+
| `port` | `src/ports/` | interfaces **shaped by what the inside needs**, never by what a tool does (Graça: "created to fit the Application Core needs and not simply mimic the tools APIs") | `domain`, `port`; the same as the domain |
|
|
26
|
+
| `app` | `src/*.ts` | use cases over ports; **no I/O defaults** — a use case without its deps does not compile | `domain`, `port`, `app`, the package's `.mjs`; the same externals as the domain |
|
|
27
27
|
| `adapter` | `src/adapters/<tool>/` | everything that knows `<tool>` exists, pure parsers included; used through its `index.ts` | `domain`, `port`, its own folder, its tool's packages, the package's `.mjs`. **Never** `app`, **never** another adapter |
|
|
28
28
|
| `root` | `src/cli.ts` | reads `process.*`, builds adapters, wires ports | anything |
|
|
29
29
|
|
|
30
|
+
A package that exists because of one external tool goes in `TOOL_PACKAGES` (`eslint.config.mjs`), in the same PR that
|
|
31
|
+
adds its adapter; a pure utility library (`remeda`, `zod`, `ts-essentials`) needs nothing, and effects are axis B's.
|
|
32
|
+
|
|
30
33
|
Folders are ELEMENTS of `eslint-plugin-boundaries`, single files are CATEGORIES (`root`, `app`,
|
|
31
34
|
`test`, `io`): v7 matches element patterns as folders. Everyone — tests included — reaches an
|
|
32
35
|
adapter through its `index.ts`; the files inside one adapter import each other freely.
|
package/src/CLAUDE.md.spec.ts
CHANGED
|
@@ -18,12 +18,15 @@ test — a parser for banal's JSON is pure and is still banal's.
|
|
|
18
18
|
|
|
19
19
|
| element | where | holds | may import |
|
|
20
20
|
| --------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------- |
|
|
21
|
-
| \`domain\` | \`src/domain/\` | values and decisions in the paper's vocabulary | \`domain\`;
|
|
22
|
-
| \`port\` | \`src/ports/\` | interfaces **shaped by what the inside needs**, never by what a tool does (Graça: "created to fit the Application Core needs and not simply mimic the tools APIs") | \`domain\`, \`port\`; the same
|
|
23
|
-
| \`app\` | \`src/*.ts\` | use cases over ports; **no I/O defaults** — a use case without its deps does not compile | \`domain\`, \`port\`, \`app\`, the package's \`.mjs\`;
|
|
21
|
+
| \`domain\` | \`src/domain/\` | values and decisions in the paper's vocabulary | \`domain\`; any package or Node built-in that is no tool package (\`TOOL_PACKAGES\`) and no effect |
|
|
22
|
+
| \`port\` | \`src/ports/\` | interfaces **shaped by what the inside needs**, never by what a tool does (Graça: "created to fit the Application Core needs and not simply mimic the tools APIs") | \`domain\`, \`port\`; the same as the domain |
|
|
23
|
+
| \`app\` | \`src/*.ts\` | use cases over ports; **no I/O defaults** — a use case without its deps does not compile | \`domain\`, \`port\`, \`app\`, the package's \`.mjs\`; the same externals as the domain |
|
|
24
24
|
| \`adapter\` | \`src/adapters/<tool>/\` | everything that knows \`<tool>\` exists, pure parsers included; used through its \`index.ts\` | \`domain\`, \`port\`, its own folder, its tool's packages, the package's \`.mjs\`. **Never** \`app\`, **never** another adapter |
|
|
25
25
|
| \`root\` | \`src/cli.ts\` | reads \`process.*\`, builds adapters, wires ports | anything |
|
|
26
26
|
|
|
27
|
+
A package that exists because of one external tool goes in \`TOOL_PACKAGES\` (\`eslint.config.mjs\`), in the same PR that
|
|
28
|
+
adds its adapter; a pure utility library (\`remeda\`, \`zod\`, \`ts-essentials\`) needs nothing, and effects are axis B's.
|
|
29
|
+
|
|
27
30
|
Folders are ELEMENTS of \`eslint-plugin-boundaries\`, single files are CATEGORIES (\`root\`, \`app\`,
|
|
28
31
|
\`test\`, \`io\`): v7 matches element patterns as folders. Everyone — tests included — reaches an
|
|
29
32
|
adapter through its \`index.ts\`; the files inside one adapter import each other freely.
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The git ports over a REAL repository in a scratch directory: the committed state of a decision,
|
|
3
|
+
* and the order of every output's first commit against the decision's — after, the same commit, or
|
|
4
|
+
* not after — a deleted output included, and a shallow clone named as such.
|
|
5
|
+
*/
|
|
6
|
+
import { writeFileSync } from "node:fs";
|
|
7
|
+
import { join } from "node:path";
|
|
8
|
+
import { expect, it } from "vitest";
|
|
9
|
+
import { absolutePath } from "../../domain/paths.ts";
|
|
10
|
+
import { spawnProcess } from "../node/index.ts";
|
|
11
|
+
import { commit, git, initRepo } from "../../../test/git-repo.ts";
|
|
12
|
+
import { useTempDir } from "../../../test/support.ts";
|
|
13
|
+
import { gitHistory } from "./index.ts";
|
|
14
|
+
|
|
15
|
+
const root = useTempDir("git-history-");
|
|
16
|
+
const ports = gitHistory(spawnProcess(), { PATH: String(process.env.PATH) });
|
|
17
|
+
const T1 = "2026-10-16T10:00:00+05:00";
|
|
18
|
+
const T2 = "2026-10-16T11:00:00+05:00";
|
|
19
|
+
const T3 = "2026-10-16T12:00:00+05:00";
|
|
20
|
+
|
|
21
|
+
it("a decision committed, then changed, then never committed: three states", () => {
|
|
22
|
+
const repo = join(root, "committed");
|
|
23
|
+
initRepo(repo);
|
|
24
|
+
const sha = commit(repo, { "p/decision.md": "exclusions: none\n" }, T1);
|
|
25
|
+
const paper = absolutePath(join(repo, "p"));
|
|
26
|
+
const file = absolutePath(join(repo, "p/decision.md"));
|
|
27
|
+
// Guards: the commit and its date as git records them.
|
|
28
|
+
expect(ports.committed(paper, file)).toEqual({
|
|
29
|
+
kind: "committed",
|
|
30
|
+
commit: sha,
|
|
31
|
+
date: T1,
|
|
32
|
+
});
|
|
33
|
+
writeFileSync(file, "exclusions: run 7\n");
|
|
34
|
+
// Guards: an edit after the commit is not the recorded decision.
|
|
35
|
+
expect(ports.committed(paper, file)).toEqual({
|
|
36
|
+
kind: "changed",
|
|
37
|
+
commit: sha,
|
|
38
|
+
});
|
|
39
|
+
writeFileSync(join(repo, "p/new.md"), "x\n");
|
|
40
|
+
expect(ports.committed(paper, absolutePath(join(repo, "p/new.md")))).toEqual({
|
|
41
|
+
kind: "not-committed",
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("outputs after the decision, in its commit, before it, and deleted since: each with its order", () => {
|
|
46
|
+
const repo = join(root, "order");
|
|
47
|
+
initRepo(repo);
|
|
48
|
+
const early = commit(repo, { "p/repro/S/early.csv": "1\n" }, T1);
|
|
49
|
+
const decided = commit(
|
|
50
|
+
repo,
|
|
51
|
+
{ "p/repro/S/decision.md": "d\n", "p/repro/S/same.csv": "2\n" },
|
|
52
|
+
T2,
|
|
53
|
+
);
|
|
54
|
+
const later = commit(
|
|
55
|
+
repo,
|
|
56
|
+
{ "p/repro/S/later.csv": "3\n", "p/repro/S/early.csv": null },
|
|
57
|
+
T3,
|
|
58
|
+
);
|
|
59
|
+
// Guards: the order is read from ancestry; a deleted early output is still evidence.
|
|
60
|
+
expect(
|
|
61
|
+
ports.order(
|
|
62
|
+
absolutePath(join(repo, "p/repro/S")),
|
|
63
|
+
absolutePath(join(repo, "p/repro/S/decision.md")),
|
|
64
|
+
),
|
|
65
|
+
).toEqual({
|
|
66
|
+
kind: "read",
|
|
67
|
+
decision: { commit: decided, date: T2 },
|
|
68
|
+
decisionDeleted: false,
|
|
69
|
+
files: [
|
|
70
|
+
{ path: "early.csv", commit: early, date: T1, order: "not-after" },
|
|
71
|
+
{ path: "decision.md", commit: decided, date: T2, order: "same" },
|
|
72
|
+
{ path: "same.csv", commit: decided, date: T2, order: "same" },
|
|
73
|
+
{ path: "later.csv", commit: later, date: T3, order: "after" },
|
|
74
|
+
],
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it("a shallow clone is named, not judged", () => {
|
|
79
|
+
const repo = join(root, "deep");
|
|
80
|
+
initRepo(repo);
|
|
81
|
+
commit(repo, { "p/repro/S/decision.md": "d\n" }, T1);
|
|
82
|
+
commit(repo, { "p/repro/S/out.csv": "1\n" }, T2);
|
|
83
|
+
const shallow = join(root, "shallow");
|
|
84
|
+
git(root, ["clone", "-q", "--depth", "1", `file://${repo}`, shallow]);
|
|
85
|
+
// Guards: a CI checkout of depth 1 says so instead of reading every file as born in one commit.
|
|
86
|
+
expect(
|
|
87
|
+
ports.order(
|
|
88
|
+
absolutePath(join(shallow, "p/repro/S")),
|
|
89
|
+
absolutePath(join(shallow, "p/repro/S/decision.md")),
|
|
90
|
+
),
|
|
91
|
+
).toEqual({ kind: "shallow" });
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("outside any repository, git's own refusal is the reason", () => {
|
|
95
|
+
const r = ports.order(absolutePath("/"), absolutePath("/decision.md"));
|
|
96
|
+
// Guards: no repository is "unknown", never an empty history.
|
|
97
|
+
expect(r.kind).toBe("unknown");
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it("a decision committed and deleted in a later commit is not in HEAD: never `committed`", () => {
|
|
101
|
+
const repo = join(root, "deleted");
|
|
102
|
+
initRepo(repo);
|
|
103
|
+
const sha = commit(repo, { "p/repro/S/decision.md": "d\n" }, T1);
|
|
104
|
+
const gone = commit(repo, { "p/repro/S/decision.md": null }, T2);
|
|
105
|
+
const paper = absolutePath(join(repo, "p"));
|
|
106
|
+
const file = absolutePath(join(repo, "p/repro/S/decision.md"));
|
|
107
|
+
// Guards: `git log -1` names the deletion and the tree is clean, yet there is no decision to gate on;
|
|
108
|
+
// the order still reads the decision's first commit, and says it is gone.
|
|
109
|
+
expect([
|
|
110
|
+
ports.committed(paper, file),
|
|
111
|
+
ports.order(absolutePath(join(repo, "p/repro/S")), file),
|
|
112
|
+
]).toEqual([
|
|
113
|
+
{ kind: "deleted", commit: gone },
|
|
114
|
+
{
|
|
115
|
+
kind: "read",
|
|
116
|
+
decision: { commit: sha, date: T1 },
|
|
117
|
+
decisionDeleted: true,
|
|
118
|
+
files: [{ path: "decision.md", commit: sha, date: T1, order: "same" }],
|
|
119
|
+
},
|
|
120
|
+
]);
|
|
121
|
+
});
|