paperlint 13.0.2 → 13.1.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/eslint.config.mjs +1 -1
- package/hooks/hooks.harness.ts +51 -13
- package/hooks/paper-skills-nudge.hook.d.mts +22 -0
- package/hooks/paper-skills-nudge.hook.mjs +85 -23
- package/hooks/paper-skills-nudge.test.ts +82 -4
- package/package.json +1 -1
- package/plugin/hooks/hooks.json +7 -2
- package/presets/aidc.jsonc +2 -2
- package/presets/msr.jsonc +11 -4
- package/skills/argument-arc/SKILL.md.spec.ts +1 -1
- package/skills/find-venue/SKILL.md.spec.ts +1 -1
- package/skills/paper-status/SKILL.md.spec.ts +1 -1
- package/skills/render-paper/SKILL.md.spec.ts +1 -1
package/eslint.config.mjs
CHANGED
|
@@ -687,7 +687,7 @@ export default [
|
|
|
687
687
|
* not itself.
|
|
688
688
|
*
|
|
689
689
|
* Measurement on the first run: 15 files with dead imports (`resolve`, `pathToFileURL`) and
|
|
690
|
-
* orphaned constants — remnants of a move from
|
|
690
|
+
* orphaned constants — remnants of a move from the downstream repository this package was extracted from, where those names were needed. All
|
|
691
691
|
* cleaned to zero in the same commit, so the rule opens as a GATE on a clean corpus,
|
|
692
692
|
* not as debt to be silenced.
|
|
693
693
|
*
|
package/hooks/hooks.harness.ts
CHANGED
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
import { runHook, type HookInput, type HookRunResult } from "vigiles";
|
|
26
26
|
import { checkHookImports } from "vigiles/hook";
|
|
27
27
|
import {
|
|
28
|
+
existsSync,
|
|
28
29
|
mkdtempSync,
|
|
29
30
|
mkdirSync,
|
|
30
31
|
readdirSync,
|
|
@@ -105,6 +106,12 @@ const onEdit = (file_path: string): HookInput => ({
|
|
|
105
106
|
tool_input: { file_path },
|
|
106
107
|
tool_response: {},
|
|
107
108
|
});
|
|
109
|
+
const onRead = (file_path: string): HookInput => ({
|
|
110
|
+
hook_event_name: "PostToolUse",
|
|
111
|
+
tool_name: "Read",
|
|
112
|
+
tool_input: { file_path },
|
|
113
|
+
tool_response: {},
|
|
114
|
+
});
|
|
108
115
|
const STOP: HookInput = {
|
|
109
116
|
hook_event_name: "Stop",
|
|
110
117
|
tool_name: "",
|
|
@@ -459,33 +466,64 @@ try {
|
|
|
459
466
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
460
467
|
{
|
|
461
468
|
const dir = fixture({ [PAPERS_DIR_FIELD]: "docs/papers" });
|
|
462
|
-
const
|
|
463
|
-
|
|
469
|
+
const shipped = readdirSync(join(ROOT, "skills")).filter((d) =>
|
|
470
|
+
existsSync(join(ROOT, "skills", d, "SKILL.md")),
|
|
471
|
+
);
|
|
472
|
+
const lands = (label: string, input: HookInput) => {
|
|
473
|
+
const r = at(dir, "paper-skills-nudge", input);
|
|
464
474
|
const ctx = injected(r);
|
|
465
475
|
check(
|
|
466
476
|
`nudge LANDS on ${label} (${String(r.stdout.length)}b stdout)`,
|
|
467
477
|
ctx.length > 0,
|
|
468
478
|
);
|
|
469
|
-
check(
|
|
470
|
-
`nudge's text on ${label} is the checklist`,
|
|
471
|
-
/Pre-submit checklist/.test(ctx),
|
|
472
|
-
);
|
|
473
479
|
check(`nudge on ${label} exits 0`, r.exitCode === 0);
|
|
480
|
+
return ctx;
|
|
474
481
|
};
|
|
475
|
-
const silent = (label: string,
|
|
476
|
-
const r = at(dir, "paper-skills-nudge",
|
|
482
|
+
const silent = (label: string, input: HookInput) => {
|
|
483
|
+
const r = at(dir, "paper-skills-nudge", input);
|
|
477
484
|
check(
|
|
478
485
|
`nudge SILENT on ${label} (${String(r.stdout.length)}b stdout, ${String(r.stderr.length)}b stderr)`,
|
|
479
486
|
r.stdout.length === 0 && r.stderr.length === 0 && r.exitCode === 0,
|
|
480
487
|
);
|
|
481
488
|
};
|
|
482
|
-
|
|
483
|
-
|
|
489
|
+
const first = lands(
|
|
490
|
+
"a relative paper.tex",
|
|
491
|
+
onEdit("docs/papers/alpha/paper.tex"),
|
|
492
|
+
);
|
|
493
|
+
// The list is read from the package's skills folder, not typed into the hook: every shipped
|
|
494
|
+
// skill appears, one bullet each, so a new skill reaches the nudge without an edit here.
|
|
495
|
+
check(
|
|
496
|
+
`nudge lists every shipped skill (${String(shipped.length)} on disk)`,
|
|
497
|
+
shipped.every((name) => first.includes(` • ${name} — `)) &&
|
|
498
|
+
(first.match(/^ • /gm) ?? []).length >= shipped.length,
|
|
499
|
+
);
|
|
500
|
+
check("nudge names the paper it fired for", first.includes('"alpha"'));
|
|
501
|
+
// Once per paper: the same paper again, in its ABSOLUTE spelling, stays quiet.
|
|
502
|
+
silent(
|
|
503
|
+
"the same paper again (absolute path)",
|
|
504
|
+
onEdit(join(dir, "docs/papers/alpha/notes.md")),
|
|
505
|
+
);
|
|
484
506
|
// 🔴 THE ABSOLUTE SPELLING. The live harness sends absolute paths; a helper that only builds
|
|
485
507
|
// relative ones let an anchored predecessor pass every test while being dead in production.
|
|
486
|
-
lands(
|
|
487
|
-
|
|
488
|
-
|
|
508
|
+
lands(
|
|
509
|
+
"an ABSOLUTE path in another paper",
|
|
510
|
+
onEdit(join(dir, "docs/papers/beta/paper.tex")),
|
|
511
|
+
);
|
|
512
|
+
// Any file of a paper, not only its source: siblings, claims, data are where decisions start.
|
|
513
|
+
lands(
|
|
514
|
+
"a non-.tex file of a paper",
|
|
515
|
+
onEdit("docs/papers/gamma/siblings/novelty.json"),
|
|
516
|
+
);
|
|
517
|
+
// A Read is enough: the skills are worth knowing when the paper is opened, before anything is decided.
|
|
518
|
+
lands(
|
|
519
|
+
"a Read of a paper file",
|
|
520
|
+
onRead(join(dir, "docs/papers/delta/CLAIMS.md")),
|
|
521
|
+
);
|
|
522
|
+
silent(
|
|
523
|
+
"a README directly under the papers root",
|
|
524
|
+
onEdit("docs/papers/README.md"),
|
|
525
|
+
);
|
|
526
|
+
silent("a file outside the papers root", onEdit("README.md"));
|
|
489
527
|
// The advisory's own asymmetry: it goes quiet where the gate refuses.
|
|
490
528
|
{
|
|
491
529
|
const broken = fixture(
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// Types for the exports `hooks/paper-skills-nudge.test.ts` imports from the hook. The hook ships as
|
|
2
|
+
// plain .mjs (a compiled hook may import nothing but `vigiles/hook`), so without this file TypeScript
|
|
3
|
+
// sees `any` and `tsc -p tsconfig.test.json` fails with TS7016 — the same reason
|
|
4
|
+
// `paper-edit-guard.hook.d.mts` exists.
|
|
5
|
+
|
|
6
|
+
/** One line per skill from the provider's tab-separated `name<TAB>description` output. */
|
|
7
|
+
export declare const skillLines: (raw: string | undefined) => readonly string[];
|
|
8
|
+
|
|
9
|
+
/** The paper a path belongs to, from every spelling of the path the event carries; null when none. */
|
|
10
|
+
export declare const paperOf: (
|
|
11
|
+
paths: readonly (string | undefined)[],
|
|
12
|
+
root: string,
|
|
13
|
+
) => string | null;
|
|
14
|
+
|
|
15
|
+
/** The notice text for one paper and its skill lines. */
|
|
16
|
+
export declare const message: (
|
|
17
|
+
paper: string,
|
|
18
|
+
lines: readonly string[],
|
|
19
|
+
) => string;
|
|
20
|
+
|
|
21
|
+
declare const hook: unknown;
|
|
22
|
+
export default hook;
|
|
@@ -36,6 +36,8 @@ import {
|
|
|
36
36
|
provide,
|
|
37
37
|
notice,
|
|
38
38
|
nothing,
|
|
39
|
+
record,
|
|
40
|
+
state,
|
|
39
41
|
} from "vigiles/hook";
|
|
40
42
|
|
|
41
43
|
/** The tool's name — the package this hook resolves its scripts through. */
|
|
@@ -96,28 +98,78 @@ const papersRoot = (rawConfig) => {
|
|
|
96
98
|
return root;
|
|
97
99
|
};
|
|
98
100
|
|
|
99
|
-
/**
|
|
100
|
-
|
|
101
|
+
/**
|
|
102
|
+
* The shipped skills, one per line as name, a tab, and the description, read from the package's own
|
|
103
|
+
* skills folder (the consumer's node_modules copy; in this repository, its own folder). The list used
|
|
104
|
+
* to be typed into this file and drifted: a skill added to the package never reached the nudge.
|
|
105
|
+
* An empty result (no folder, no skills) leaves the list out and keeps the rest of the message.
|
|
106
|
+
*/
|
|
107
|
+
const READ_SKILLS =
|
|
108
|
+
`d="\${CLAUDE_PROJECT_DIR:-.}/node_modules/paperlint/skills"; [ -d "$d" ] || d="\${CLAUDE_PROJECT_DIR:-.}/skills"; ` +
|
|
109
|
+
`for f in "$d"/*/SKILL.md; do [ -f "$f" ] || continue; ` +
|
|
110
|
+
`awk 'NR==1 && $0!="---" {exit} NR>1 && $0=="---" {exit} ` +
|
|
111
|
+
`/^name: / {n=substr($0, 7)} /^description: / {d=substr($0, 14)} END {if (n!="") print n "\t" d}' "$f"; done`;
|
|
112
|
+
|
|
113
|
+
/** One line per skill: its name and the description's first clause, at most 110 characters. */
|
|
114
|
+
export const skillLines = (raw) =>
|
|
115
|
+
String(raw ?? "")
|
|
116
|
+
.split("\n")
|
|
117
|
+
.map((l) => l.split("\t"))
|
|
118
|
+
.filter(([name]) => /^[a-z0-9-]+$/.test(name))
|
|
119
|
+
.map(([name, desc = ""]) => {
|
|
120
|
+
const clause = desc
|
|
121
|
+
.replace(/^["']|["']$/g, "")
|
|
122
|
+
.replace(/\\"/g, '"')
|
|
123
|
+
.split(/ — |\. /)[0];
|
|
124
|
+
return ` • ${name} — ${clause.length > 110 ? `${clause.slice(0, 107)}...` : clause}`;
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
/** A path or root in one spelling: no leading "./", no doubled or trailing separators. */
|
|
128
|
+
const tidy = (p) =>
|
|
129
|
+
p
|
|
130
|
+
.replace(/^(\.\/+)+/, "")
|
|
131
|
+
.replace(/\/{2,}/g, "/")
|
|
132
|
+
.replace(/\/+$/, "");
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* The paper a path belongs to: the first folder under the papers root, when the file sits inside one.
|
|
136
|
+
* A file directly in the root (its README) belongs to no paper and stays silent. The path comes in
|
|
137
|
+
* every spelling the event has (repo-relative and as sent), and the root in any spelling the config
|
|
138
|
+
* allows (with a leading dot-slash, doubled slashes, absolute); the first spelling under the root wins.
|
|
139
|
+
*/
|
|
140
|
+
export const paperOf = (paths, root) => {
|
|
141
|
+
const prefix = `${tidy(root)}/`;
|
|
142
|
+
const names = paths
|
|
143
|
+
.filter((p) => typeof p === "string")
|
|
144
|
+
.map(tidy)
|
|
145
|
+
.filter((p) => p.startsWith(prefix))
|
|
146
|
+
.map((p) => p.slice(prefix.length).split("/"))
|
|
147
|
+
.map((parts) => (parts.length >= 2 ? parts[0] : null));
|
|
148
|
+
return names.length ? names[0] : null;
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
/** How long one paper stays nudged. Touching a different paper speaks again at once. */
|
|
152
|
+
const QUIET_FOR = "3h";
|
|
101
153
|
|
|
102
|
-
const
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
•
|
|
112
|
-
|
|
113
|
-
•
|
|
114
|
-
•
|
|
115
|
-
• bib inside \\begin{filecontents*}{refs.bib}? edit it THERE — refs.bib is overwritten on build
|
|
116
|
-
• Done + claim-clean + page-legal ships. Further churn breaks about as much as it fixes.`;
|
|
154
|
+
export const message = (paper, lines) =>
|
|
155
|
+
`📄 Working on paper "${paper}". Before deciding a step yourself, check whether a skill owns it ` +
|
|
156
|
+
"(Skill tool) — a hand-written brief or a winged review is how pipeline stages get skipped.\n" +
|
|
157
|
+
" → Update <paper-dir>/PIPELINE-STATUS.md: mark the row for any stage you (re)ran, and read its\n" +
|
|
158
|
+
" verdict line to see what's still ☐/◐/⚠ before submit.\n" +
|
|
159
|
+
(lines.length
|
|
160
|
+
? `The paper skills shipped with this package:\n${lines.join("\n")}\n`
|
|
161
|
+
: "") +
|
|
162
|
+
"RULES earned the hard way:\n" +
|
|
163
|
+
" • After every aggressive rewrite/thinning pass → claim-preservation DIFF vs the pre-pass baseline\n" +
|
|
164
|
+
" • Numbers overwhelming the reader? secondary numbers live in the artifact, not the paper\n" +
|
|
165
|
+
" • bib inside \\begin{filecontents*}{refs.bib}? edit it THERE — refs.bib is overwritten on build\n" +
|
|
166
|
+
" • Done + claim-clean + page-legal ships. Further churn breaks about as much as it fixes.";
|
|
117
167
|
|
|
118
168
|
export default experimental_defineReact({
|
|
119
169
|
on: "PostToolUse",
|
|
120
|
-
|
|
170
|
+
// Read too: the moment a paper is opened is when its skills are worth knowing, before anything is
|
|
171
|
+
// decided — an edit-only trigger fires after the decision.
|
|
172
|
+
match: tools("Read", "Edit", "Write", "MultiEdit"),
|
|
121
173
|
// The provider is anchored to the project root (`READ_CONFIG`, above).
|
|
122
174
|
//
|
|
123
175
|
// ⚠️ THE FAILURE HERE IS SILENT, which makes it more dangerous than its neighbor's.
|
|
@@ -126,12 +178,22 @@ export default experimental_defineReact({
|
|
|
126
178
|
// the `cat` fails → empty string → `JSON.parse("")` throws → `papersRoot` returns null
|
|
127
179
|
// → silence. And silence is exactly what a nudge's success state looks like, so a dead hook
|
|
128
180
|
// is indistinguishable from a working one.
|
|
129
|
-
needs: [
|
|
181
|
+
needs: [
|
|
182
|
+
provide("config", READ_CONFIG),
|
|
183
|
+
provide("skills", READ_SKILLS),
|
|
184
|
+
state("paper-skills.shown"),
|
|
185
|
+
],
|
|
130
186
|
react: (e) => {
|
|
131
187
|
const root = papersRoot(e.ctx.config);
|
|
132
|
-
if (root === null) return nothing();
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
188
|
+
if (root === null || !e.path.under([tidy(root)])) return nothing();
|
|
189
|
+
const paper = paperOf([e.path.rel, e.path.raw], root);
|
|
190
|
+
if (paper === null) return nothing();
|
|
191
|
+
const shown = e.ctx["paper-skills.shown"];
|
|
192
|
+
// Once per paper: the same paper again within QUIET_FOR stays silent; another paper speaks.
|
|
193
|
+
if (shown.value === paper && shown.fresherThan(QUIET_FOR)) return nothing();
|
|
194
|
+
return notice(
|
|
195
|
+
message(paper, skillLines(e.ctx.skills)),
|
|
196
|
+
record("paper-skills.shown", paper),
|
|
197
|
+
);
|
|
136
198
|
},
|
|
137
199
|
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* paper-skills-nudge on the
|
|
2
|
+
* paper-skills-nudge on the declarations the hooks harness does not carry: none at all (the
|
|
3
3
|
* default `papers/` root applies) and one that does not parse (silence — a nudge never blocks).
|
|
4
4
|
*/
|
|
5
5
|
import assert from "node:assert/strict";
|
|
@@ -7,6 +7,7 @@ import { join } from "node:path";
|
|
|
7
7
|
import { test } from "vitest";
|
|
8
8
|
import { hookConsumer, onEdit, runShippedHook } from "../test/hook-consumer.ts";
|
|
9
9
|
import { useTempDir } from "../test/support.ts";
|
|
10
|
+
import { message, paperOf, skillLines } from "./paper-skills-nudge.hook.mjs";
|
|
10
11
|
|
|
11
12
|
const root = useTempDir("paper-skills-nudge-");
|
|
12
13
|
|
|
@@ -33,12 +34,89 @@ test("a paperlint.json that does not parse: silent, exit 0", () => {
|
|
|
33
34
|
assert.deepEqual([r.exitCode, r.stdout, r.stderr], [0, "", ""]);
|
|
34
35
|
});
|
|
35
36
|
|
|
36
|
-
test("
|
|
37
|
-
const dir = hookConsumer(join(root, "
|
|
37
|
+
test("a file directly under the papers root belongs to no paper: silent", () => {
|
|
38
|
+
const dir = hookConsumer(join(root, "root-file"));
|
|
38
39
|
const r = runShippedHook(
|
|
39
40
|
"paper-skills-nudge",
|
|
40
|
-
onEdit("papers/
|
|
41
|
+
onEdit("papers/README.md"),
|
|
41
42
|
dir,
|
|
42
43
|
);
|
|
43
44
|
assert.deepEqual([r.exitCode, r.stdout, r.stderr], [0, "", ""]);
|
|
44
45
|
});
|
|
46
|
+
|
|
47
|
+
test("any file of a paper, not only its source: the checklist", () => {
|
|
48
|
+
const dir = hookConsumer(join(root, "any-file"));
|
|
49
|
+
const r = runShippedHook(
|
|
50
|
+
"paper-skills-nudge",
|
|
51
|
+
onEdit("papers/alpha/notes.txt"),
|
|
52
|
+
dir,
|
|
53
|
+
);
|
|
54
|
+
assert.equal(r.exitCode, 0);
|
|
55
|
+
assert.match(r.stdout, /Working on paper \\"alpha\\"/);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// ── the pure helpers, on the inputs the shipped run never sends ─────────────────────────────
|
|
59
|
+
|
|
60
|
+
test("skillLines: a missing provider result is no skills, not a crash", () => {
|
|
61
|
+
assert.deepEqual(skillLines(undefined), []);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test("skillLines: a line that is not a skill name is dropped; a name without a description stays", () => {
|
|
65
|
+
assert.deepEqual(skillLines("Not A Name\tx\nbare-name"), [
|
|
66
|
+
" • bare-name — ",
|
|
67
|
+
]);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test("skillLines: a quoted description is unquoted and cut at its first clause, at most 110 characters", () => {
|
|
71
|
+
const long = "a".repeat(200);
|
|
72
|
+
assert.deepEqual(
|
|
73
|
+
skillLines(`quoted\t"Say \\"hi\\" — then more"\nlong\t${long}`),
|
|
74
|
+
[' • quoted — Say "hi"', ` • long — ${"a".repeat(107)}...`],
|
|
75
|
+
);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test("paperOf: only a file inside a paper folder belongs to a paper", () => {
|
|
79
|
+
assert.deepEqual(
|
|
80
|
+
[
|
|
81
|
+
paperOf([undefined], "papers"),
|
|
82
|
+
paperOf(["elsewhere/alpha/x.tex"], "papers"),
|
|
83
|
+
paperOf(["papers/README.md"], "papers"),
|
|
84
|
+
paperOf(["papers/alpha/x.tex"], "papers/"),
|
|
85
|
+
],
|
|
86
|
+
[null, null, null, "alpha"],
|
|
87
|
+
);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test("message: with no skills found, the list is left out and the rest stays", () => {
|
|
91
|
+
const m = message("alpha", []);
|
|
92
|
+
assert.equal(m.includes("shipped with this package"), false);
|
|
93
|
+
assert.match(m, /RULES earned the hard way/);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test("paperOf: the root in any spelling the path check accepts — ./, doubled slashes, absolute", () => {
|
|
97
|
+
assert.deepEqual(
|
|
98
|
+
[
|
|
99
|
+
paperOf(["papers/alpha/x.tex"], "./papers"),
|
|
100
|
+
paperOf(["papers/alpha/x.tex"], ".//papers//"),
|
|
101
|
+
paperOf(
|
|
102
|
+
["papers/alpha/x.tex", "/repo/papers/alpha/x.tex"],
|
|
103
|
+
"/repo/papers",
|
|
104
|
+
),
|
|
105
|
+
paperOf(["papers/README.md", "/repo/papers/README.md"], "/repo/papers"),
|
|
106
|
+
],
|
|
107
|
+
["alpha", "alpha", "alpha", null],
|
|
108
|
+
);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test("a ./-spelled papersDir: the checklist still lands", () => {
|
|
112
|
+
const dir = hookConsumer(join(root, "dot-slash"), {
|
|
113
|
+
"paperlint.json": JSON.stringify({ papersDir: "./papers" }),
|
|
114
|
+
});
|
|
115
|
+
const r = runShippedHook(
|
|
116
|
+
"paper-skills-nudge",
|
|
117
|
+
onEdit("papers/alpha/paper.tex"),
|
|
118
|
+
dir,
|
|
119
|
+
);
|
|
120
|
+
assert.equal(r.exitCode, 0);
|
|
121
|
+
assert.match(r.stdout, /Working on paper/);
|
|
122
|
+
});
|
package/package.json
CHANGED
package/plugin/hooks/hooks.json
CHANGED
|
@@ -26,12 +26,17 @@
|
|
|
26
26
|
],
|
|
27
27
|
"PostToolUse": [
|
|
28
28
|
{
|
|
29
|
-
"matcher": "Edit|Write|MultiEdit",
|
|
29
|
+
"matcher": "Read|Edit|Write|MultiEdit",
|
|
30
30
|
"hooks": [
|
|
31
31
|
{
|
|
32
32
|
"type": "command",
|
|
33
33
|
"command": "node \"${CLAUDE_PROJECT_DIR}/node_modules/paperlint/bin/paperlint.mjs\" hook paper-skills-nudge"
|
|
34
|
-
}
|
|
34
|
+
}
|
|
35
|
+
]
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"matcher": "Edit|Write|MultiEdit",
|
|
39
|
+
"hooks": [
|
|
35
40
|
{
|
|
36
41
|
"type": "command",
|
|
37
42
|
"command": "node \"${CLAUDE_PROJECT_DIR}/node_modules/paperlint/bin/paperlint.mjs\" hook paper-status-gates"
|
package/presets/aidc.jsonc
CHANGED
package/presets/msr.jsonc
CHANGED
|
@@ -45,18 +45,25 @@
|
|
|
45
45
|
// on the abstract and paper days themselves; the portal moved both later on or before 2026-10-19.
|
|
46
46
|
"deadlines": [
|
|
47
47
|
{
|
|
48
|
-
"what": "
|
|
48
|
+
"what": "registration",
|
|
49
49
|
"at": "2026-10-21T11:59:59Z",
|
|
50
50
|
"source": "portal",
|
|
51
51
|
"url": "https://msr2027.hotcrp.com/deadlines",
|
|
52
|
-
"read": "2026-10-
|
|
52
|
+
"read": "2026-10-29",
|
|
53
53
|
},
|
|
54
54
|
{
|
|
55
|
-
"what": "
|
|
55
|
+
"what": "submission",
|
|
56
56
|
"at": "2026-10-24T11:59:59Z",
|
|
57
57
|
"source": "portal",
|
|
58
58
|
"url": "https://msr2027.hotcrp.com/deadlines",
|
|
59
|
-
"read": "2026-10-
|
|
59
|
+
"read": "2026-10-29",
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
"what": "resubmission",
|
|
63
|
+
"at": "2026-10-26T11:59:59Z",
|
|
64
|
+
"source": "portal",
|
|
65
|
+
"url": "https://msr2027.hotcrp.com/deadlines",
|
|
66
|
+
"read": "2026-10-29",
|
|
60
67
|
},
|
|
61
68
|
],
|
|
62
69
|
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// Adopted 2026-08-17 as part of the second batch. The body is the previous SKILL.md
|
|
4
4
|
// VERBATIM, so the compiled diff shows only what the compiler adds. No `disallowedTools`
|
|
5
5
|
// fence yet: the field exists on `SkillSpec` as of the branch `claude/skill-disallowed-tools`
|
|
6
|
-
// but is not in a release
|
|
6
|
+
// but is not in a release the consumer installs, so adding it here would not compile.
|
|
7
7
|
import { experimental_skill } from "vigiles/spec";
|
|
8
8
|
|
|
9
9
|
export default experimental_skill({
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// Adopted 2026-08-17 as part of the second batch. The body is the previous SKILL.md
|
|
4
4
|
// VERBATIM, so the compiled diff shows only what the compiler adds. No `disallowedTools`
|
|
5
5
|
// fence yet: the field exists on `SkillSpec` as of the branch `claude/skill-disallowed-tools`
|
|
6
|
-
// but is not in a release
|
|
6
|
+
// but is not in a release the consumer installs, so adding it here would not compile.
|
|
7
7
|
import { experimental_skill } from "vigiles/spec";
|
|
8
8
|
|
|
9
9
|
export default experimental_skill({
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// Adopted 2026-08-17 as part of the second batch. The body is the previous SKILL.md
|
|
4
4
|
// VERBATIM, so the compiled diff shows only what the compiler adds. No `disallowedTools`
|
|
5
5
|
// fence yet: the field exists on `SkillSpec` as of the branch `claude/skill-disallowed-tools`
|
|
6
|
-
// but is not in a release
|
|
6
|
+
// but is not in a release the consumer installs, so adding it here would not compile.
|
|
7
7
|
import { experimental_skill } from "vigiles/spec";
|
|
8
8
|
|
|
9
9
|
export default experimental_skill({
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// Adopted 2026-08-17 as part of the second batch. The body is the previous SKILL.md
|
|
4
4
|
// VERBATIM, so the compiled diff shows only what the compiler adds. No `disallowedTools`
|
|
5
5
|
// fence yet: the field exists on `SkillSpec` as of the branch `claude/skill-disallowed-tools`
|
|
6
|
-
// but is not in a release
|
|
6
|
+
// but is not in a release the consumer installs, so adding it here would not compile.
|
|
7
7
|
import { experimental_skill } from "vigiles/spec";
|
|
8
8
|
|
|
9
9
|
export default experimental_skill({
|