ytdb-slate 0.5.1 → 0.6.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/README.md +15 -0
- package/docs/context-budget.md +64 -40
- package/docs/model-routing.md +7 -8
- package/docs/review-rules.md +66 -13
- package/docs/writing-guidance.md +322 -0
- package/extension/index.ts +4 -1
- package/extension/mode.ts +125 -1
- package/extension/paths.ts +13 -8
- package/extension/state.ts +6 -0
- package/extension/threads.ts +1 -0
- package/extension/worker.ts +15 -2
- package/extension/writing-check.mjs +1061 -0
- package/extension/writing.ts +117 -0
- package/package.json +9 -5
package/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# ytdb-slate
|
|
2
2
|
|
|
3
|
+
[](https://github.com/JetBrains/ytdb-slate/actions/workflows/tier-1.yml)
|
|
4
|
+
|
|
3
5
|
Slate is a thread-weaving orchestration extension for the [pi coding agent](https://pi.dev).
|
|
4
6
|
|
|
5
7
|
The orchestrator (your main pi session) dispatches **bounded actions** to persistent **worker threads**. Each completed action is compressed by an LLM into an **episode** — a durable, structured record (intent, actions, findings, artifacts, open issues, handoff notes) that the orchestrator composes into further dispatches instead of re-reading raw transcripts. On top of that, Slate injects a mandatory workflow doctrine: research before design, design review, adversarial review, and track review — with optional umbrella **draft-PR publishing** for tracks. An opt-in **model-failover** map adds high availability: when a model API fails, the orchestrator, worker threads, and episode compression each retry once on a configured equal-quality alternative. A second opt-in, **action-level model routing**, gives each dispatched action a model and an effort level chosen to be up to the task and no more, so cost is bounded per action instead of per session.
|
|
@@ -57,6 +59,16 @@ Slate can pick the model and the effort level per dispatched action rather than
|
|
|
57
59
|
|
|
58
60
|
What the code enforces is deliberately narrower than what the routing data advises. The dispatch guards refuse an unlisted model, a level off the target model's ladder and a level the provider rejects outright; they warn about — and mark — an unmeasured level, refusing it only when `router.allowUnmeasuredEffort` is `false`; and the context-window guard substitutes a wider listed model instead of ever blocking an action. The obligations that come from the profile data itself — keeping review and gate actions on measured levels, and honoring a REFUSE such as `anthropic/claude-fable-5`'s for zero-data-retention work — are stated in the doctrine for the orchestrator to honor; they are **not** code-enforced guards. Full semantics: the `router.*` rows in [Configuration](#configuration) and the shipped [`docs/model-routing.md`](docs/model-routing.md) — if they disagree, that document wins.
|
|
59
61
|
|
|
62
|
+
## Optional writing guidance
|
|
63
|
+
|
|
64
|
+
Set `writing.check` to `true` to add writing guidance and human-only telemetry. In orchestrator mode, Slate adds a doctrine rule that states the convention. It also adds a status value such as `writing 1/4`. This value means that one of four measured prose turns had a fail-level finding. Each worker gets a shorter preamble reminder about sentence length, semicolons, and contractions.
|
|
65
|
+
|
|
66
|
+
The option has prompt cost. Each orchestrator system prompt contains the doctrine rule. You pay for this text on every turn. Each worker session also gets a gated preamble addition. Both additions are absent when the option is off. The worker preamble then stays byte-identical to its pre-feature form.
|
|
67
|
+
|
|
68
|
+
> **No conformance claim:** This is a dictionary-free proxy. It embeds no controlled vocabulary and claims no ASD-STE100 conformance.
|
|
69
|
+
|
|
70
|
+
The shipped CLI checks plain files, JSONL records, and unified diffs. See [`docs/writing-guidance.md`](docs/writing-guidance.md) for its rules, limits, output, and command examples.
|
|
71
|
+
|
|
60
72
|
## Install
|
|
61
73
|
|
|
62
74
|
Install into a project (pinned, project-scoped — recorded in `.pi/settings.json`):
|
|
@@ -82,6 +94,7 @@ Optional config file: `slate.json` in the project's pi config dir (`.pi/slate.js
|
|
|
82
94
|
| `orchestratorPromptDocs` | string[] | `[]` | Project markdown files (paths relative to the project root) whose **contents** are appended to the orchestrator system prompt. |
|
|
83
95
|
| `workerPromptDocs` | string[] | `[]` | Project markdown files whose **contents** are appended to every worker-thread system prompt. |
|
|
84
96
|
| `workflow.draftPRs` | boolean | `false` | Enable umbrella draft-PR publishing for tracks. |
|
|
97
|
+
| `writing.check` | boolean | `false` | Add the writing doctrine rule, per-turn status, and worker guidance. An absent option is silently off. A malformed `writing` value, or a non-boolean `check` value, warns and defaults to off. Unknown `writing` keys warn and are ignored. See [`docs/writing-guidance.md`](docs/writing-guidance.md). |
|
|
85
98
|
| `doctrineExtraPath` | string | — | Project markdown whose **content** is appended to the orchestrator doctrine (project-specific workflow additions). |
|
|
86
99
|
| `reviewPerspectivesPath` | string | — | Project review charters, each declaring its own finding-ID prefix. The doctrine references this **path**; the orchestrator reads the file alongside the shipped review rules. |
|
|
87
100
|
| `modelFailover` | object (string → string) | — (empty, failover off) | Map of `provider/id` → equal-quality alternative model; on a model API failure the affected site retries once on the mapped model (worker/orchestrator sites only after pi's own retries are exhausted; episode compression has no pi retry loop). Read once at session start — see [`docs/model-failover.md`](docs/model-failover.md). |
|
|
@@ -99,6 +112,7 @@ Example `.pi/slate.json` (the `docs/agents/...` paths are placeholders — point
|
|
|
99
112
|
"orchestratorPromptDocs": ["docs/agents/orchestrator-guidelines.md"],
|
|
100
113
|
"workerPromptDocs": ["docs/agents/thread-guidelines.md"],
|
|
101
114
|
"workflow": { "draftPRs": true },
|
|
115
|
+
"writing": { "check": true },
|
|
102
116
|
"doctrineExtraPath": "docs/agents/workflow-additions.md",
|
|
103
117
|
"reviewPerspectivesPath": "docs/agents/review-perspectives.md",
|
|
104
118
|
"modelFailover": { "anthropic/claude-sonnet-5": "openai/gpt-5.2" },
|
|
@@ -143,6 +157,7 @@ In orchestrator mode, Slate appends a short **doctrine** (a block of numbered ru
|
|
|
143
157
|
- `docs/design-principles.md` — Slate's own design rationale
|
|
144
158
|
- `docs/model-failover.md` — the opt-in `modelFailover` map (**reference documentation** — unlike the entries above it is not workflow doctrine and is not cited by the doctrine)
|
|
145
159
|
- `docs/context-budget.md` — the orchestrator `contextBudget`: defaults, per-model overrides, the window clamp, and the pricing rationale (also **reference documentation**, not cited by the doctrine)
|
|
160
|
+
- `docs/writing-guidance.md` — the optional writing convention, `writing.check` behavior, status line, and checker CLI
|
|
146
161
|
- `docs/model-routing.md` — action-level model routing: the two `router` keys, how a model becomes routable, how an omitted effort level resolves, the dispatch guards, and the warnings a first routed session emits. Unlike the two entries above it **is** cited by the doctrine, by absolute path, but only while the routing rule renders — it is the extra embedded path that appears with the rule, the fourth normally and the fifth when `workflow.draftPRs` is also on, which the size arithmetic in `docs/context-budget.md` accounts for. The routable model list itself is never read from this file: the rule renders it live from the session's own resolution, because that set depends on your registry and credentials.
|
|
147
162
|
|
|
148
163
|
Project-specific additions layer on top — they extend, not replace, the shipped doctrine — via two distinct mechanisms:
|
package/docs/context-budget.md
CHANGED
|
@@ -129,69 +129,93 @@ you pay its input tokens every turn.
|
|
|
129
129
|
|
|
130
130
|
**Its size depends on where the package is installed.** That is not
|
|
131
131
|
obvious, and it catches anyone who measures: the doctrine cites the
|
|
132
|
-
shipped docs by ABSOLUTE path, so three to
|
|
133
|
-
|
|
132
|
+
shipped docs by ABSOLUTE path, so three to six full filesystem paths
|
|
133
|
+
are embedded in the block — three at minimum
|
|
134
134
|
(`track-workflow.md`, `review-rules.md`, `design-principles.md`),
|
|
135
135
|
plus `pr-publishing.md` when `workflow.draftPRs` is on, plus
|
|
136
|
-
`model-routing.md` when the routing rule renders
|
|
137
|
-
|
|
136
|
+
`model-routing.md` when the routing rule renders, plus
|
|
137
|
+
`writing-guidance.md` when `writing.check` is on. Every additional
|
|
138
|
+
character in the installed `docs/` directory therefore costs 3–6
|
|
138
139
|
characters of doctrine.
|
|
139
140
|
|
|
140
|
-
|
|
141
|
-
Slate's own automated
|
|
142
|
-
occurrence of the installed `docs/` DIRECTORY
|
|
143
|
-
filename. That makes them install-invariant by
|
|
144
|
-
|
|
145
|
-
58-character one, which give identical portable counts — and it
|
|
146
|
-
makes the arithmetic one multiplication:
|
|
141
|
+
The figures below are **portable characters**, defined exactly as
|
|
142
|
+
Slate's own automated `doctrine-budget` check defines them: the
|
|
143
|
+
block with each occurrence of the installed `docs/` DIRECTORY
|
|
144
|
+
removed, keeping the filename. That makes them install-invariant by
|
|
145
|
+
construction and makes the arithmetic one multiplication:
|
|
147
146
|
|
|
148
147
|
> rendered characters = portable + (embedded paths × length of your
|
|
149
148
|
> installed `docs/` directory)
|
|
150
149
|
|
|
151
|
-
|
|
152
|
-
|
|
150
|
+
`doctrine-budget` in `verification/resolver-checks.mjs` is the
|
|
151
|
+
definition of record for this convention and its enforced bounds.
|
|
152
|
+
This table is a measured snapshot, not a second authority. It uses
|
|
153
|
+
the same real shipped-profile fixture as that check; the six-model
|
|
154
|
+
rows use the six configured models in this repository's documented
|
|
155
|
+
example. Every row states its full basis, because router state,
|
|
156
|
+
model count, `draftPRs`, and `writing.check` each move the number:
|
|
153
157
|
|
|
154
|
-
| router | models | `draftPRs` | paths | portable | lines |
|
|
158
|
+
| router | models | `draftPRs` | `writing.check` | paths | portable | lines |
|
|
155
159
|
| --- | --- | --- | --- | --- | --- | --- |
|
|
156
|
-
| off | — | off | 3 | 1,929 | 38 |
|
|
157
|
-
| off | — | on | 4 |
|
|
158
|
-
|
|
|
159
|
-
|
|
|
160
|
-
| on |
|
|
161
|
-
| on |
|
|
160
|
+
| off | — | off | off | 3 | 1,929 | 38 |
|
|
161
|
+
| off | — | off | on | 4 | 2,683 | 53 |
|
|
162
|
+
| off | — | on | off | 4 | 1,948 | 38 |
|
|
163
|
+
| off | — | on | on | 5 | 2,702 | 53 |
|
|
164
|
+
| on | 6 configured | off | off | 4 | 3,879 | 58 |
|
|
165
|
+
| on | 6 configured | off | on | 5 | 4,633 | 73 |
|
|
166
|
+
| on | 6 configured | on | off | 5 | 3,898 | 58 |
|
|
167
|
+
| on | 6 configured | on | on | 6 | 4,652 | 73 |
|
|
168
|
+
| on | all 9 shipped | off | off | 4 | 4,434 | 61 |
|
|
169
|
+
| on | all 9 shipped | off | on | 5 | 5,188 | 76 |
|
|
170
|
+
| on | all 9 shipped | on | off | 5 | 4,453 | 61 |
|
|
171
|
+
| on | all 9 shipped | on | on | 6 | 5,207 | 76 |
|
|
162
172
|
|
|
163
173
|
An untrusted project reads the first row whatever its `slate.json`
|
|
164
|
-
says: no project config is loaded, so
|
|
165
|
-
|
|
166
|
-
|
|
174
|
+
says: no project config is loaded, so no optional rule renders. Line
|
|
175
|
+
counts do not vary with the install path. Enabling `draftPRs` costs
|
|
176
|
+
19 portable characters plus one embedded path. Enabling
|
|
177
|
+
`writing.check` costs 754 portable characters, adds 15 lines to the
|
|
178
|
+
whole doctrine, and adds one embedded path; considered alone, the
|
|
179
|
+
writing rule is 754 portable characters / 16 lines because its
|
|
180
|
+
leading newline becomes the separator when appended.
|
|
167
181
|
|
|
168
182
|
Two parts of the block grow with configuration rather than with the
|
|
169
183
|
path:
|
|
170
184
|
|
|
171
185
|
- The routing rule is a live table with ONE ROW PER ROUTABLE MODEL,
|
|
172
186
|
so what renders is the models you CONFIGURE — not the nine Slate
|
|
173
|
-
ships profiles for.
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
first six average 171.
|
|
187
|
+
ships profiles for. In this snapshot it is 1,950 portable
|
|
188
|
+
characters and adds 20 doctrine lines for six configured models;
|
|
189
|
+
for all nine it is 2,505 characters and adds 23 lines. The six
|
|
190
|
+
model rows are 146–181 characters; all nine are 146–183. The
|
|
191
|
+
legend adds a one-off clause per marker it has to explain, so
|
|
192
|
+
growth is not only the sum of new rows.
|
|
180
193
|
- The worker-extension rule grows per whitelisted extension and per
|
|
181
194
|
tool that extension contributes, so it has no fixed size; one
|
|
182
195
|
extension contributing two tools measured 373 portable characters
|
|
183
196
|
/ 7 lines.
|
|
184
197
|
|
|
185
|
-
Against a 256,000-token budget none of this is material
|
|
186
|
-
|
|
187
|
-
table is denser than prose,
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
re-sent on every request rather than paid once. It is
|
|
192
|
-
because it is context Slate itself puts in front of the
|
|
193
|
-
every turn, and because it is the number to check before
|
|
194
|
-
the budget's headroom is all conversation.
|
|
198
|
+
Against a 256,000-token budget none of this is material. At four
|
|
199
|
+
characters per token as a rough estimate (no tokenizer was run, and
|
|
200
|
+
the routing table is denser than prose), the portable block ranges
|
|
201
|
+
from about 482 tokens at the baseline to about 1,302 with all nine
|
|
202
|
+
models, `draftPRs`, and writing enabled. That is close to half a
|
|
203
|
+
percent of the default budget at the largest measured configuration,
|
|
204
|
+
though it is re-sent on every request rather than paid once. It is
|
|
205
|
+
listed here because it is context Slate itself puts in front of the
|
|
206
|
+
model on every turn, and because it is the number to check before
|
|
207
|
+
assuming the budget's headroom is all conversation.
|
|
208
|
+
|
|
209
|
+
### Worker writing preamble
|
|
210
|
+
|
|
211
|
+
The doctrine table does not include the worker preamble. It belongs
|
|
212
|
+
to each worker session's system prompt, not to the orchestrator's
|
|
213
|
+
per-turn doctrine. The feature-off preamble is 226 bytes. With
|
|
214
|
+
`writing.check` on it is 384 bytes, a 158-byte increase (the
|
|
215
|
+
157-byte guidance plus its separating space). These are UTF-8 byte
|
|
216
|
+
counts; the current text is ASCII, so its character counts happen to
|
|
217
|
+
be identical. This separate figure states the worker-session cost
|
|
218
|
+
without presenting it as orchestrator doctrine.
|
|
195
219
|
|
|
196
220
|
## Worker threads: the routing window guard
|
|
197
221
|
|
package/docs/model-routing.md
CHANGED
|
@@ -447,14 +447,13 @@ model's ladder is unreadable.
|
|
|
447
447
|
- **In the orchestrator's own system prompt, every turn:** the
|
|
448
448
|
doctrine gains a routing rule — a table with one row per routable
|
|
449
449
|
model, plus the rules for reading it. This is the surface you do
|
|
450
|
-
not see, and it is the router's standing cost: 1,
|
|
451
|
-
20 lines for six configured models, 2,
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
characters with routing off). Those are PORTABLE characters — the
|
|
450
|
+
not see, and it is the router's standing cost: 1,950 characters /
|
|
451
|
+
20 added doctrine lines for six configured models, 2,505 / 23 for
|
|
452
|
+
all nine. In the current snapshot a model row costs 146–183
|
|
453
|
+
characters, plus a one-off legend clause for each marker it
|
|
454
|
+
introduces. Configuring the router roughly doubles Slate's
|
|
455
|
+
always-loaded block (1,929 characters with routing and writing
|
|
456
|
+
off). Those are PORTABLE characters — the
|
|
458
457
|
doctrine with each occurrence of the installed `docs/` directory
|
|
459
458
|
removed, filenames kept — because the doctrine embeds absolute doc
|
|
460
459
|
paths and its raw size therefore depends on where the package is
|
package/docs/review-rules.md
CHANGED
|
@@ -37,14 +37,45 @@ automatically).
|
|
|
37
37
|
|
|
38
38
|
## 3. Choose perspectives by what the change touches
|
|
39
39
|
|
|
40
|
-
There is no fixed roster — compose perspectives per change.
|
|
41
|
-
|
|
40
|
+
There is no fixed roster — compose perspectives per change. Every
|
|
41
|
+
perspective has this first step before its specific charter: run
|
|
42
|
+
`node /path/to/slate/extension/writing-check.mjs --diff PATH --format text`
|
|
43
|
+
over the review diff, using the package root that contains this document.
|
|
44
|
+
Report fail-level matches in changed prose governed by the reviewed
|
|
45
|
+
project's writing convention. Diff mode deliberately selects only prose
|
|
46
|
+
file types; inspect user-facing strings and comments in source files
|
|
47
|
+
directly. For this package, [writing-guidance.md](writing-guidance.md)
|
|
48
|
+
defines the convention, its scope and its exclusions.
|
|
49
|
+
|
|
50
|
+
The checker is diagnostic, not authoritative. It embeds no controlled
|
|
51
|
+
vocabulary and establishes no ASD-STE100 conformance. A clean run does
|
|
52
|
+
not prove that writing is good, and a match is not automatically a
|
|
53
|
+
defect. Apply the project's convention scope before grading output:
|
|
54
|
+
|
|
55
|
+
- `fail`: in convention-governed changed text, normally `should-fix`;
|
|
56
|
+
use `blocker` only when the underlying defect prevents safe or correct
|
|
57
|
+
delivery. In excluded text, it is advisory only.
|
|
58
|
+
- `warning`: normally at most a `suggestion`; raise it only when
|
|
59
|
+
independent evidence establishes a more consequential defect.
|
|
60
|
+
- `house-style`: at most a `suggestion` when the applicable convention
|
|
61
|
+
calls for the change; otherwise report nothing.
|
|
62
|
+
- `advisory`: never a defect by itself. File a finding only when
|
|
63
|
+
independent review establishes an underlying defect, graded on its
|
|
64
|
+
own consequences.
|
|
65
|
+
|
|
66
|
+
Reviewer judgment governs every class. The checker cannot decide
|
|
67
|
+
meaning, factual accuracy, claim truth, document structure, topic unity,
|
|
68
|
+
or instruction completeness; those usually matter more than a
|
|
69
|
+
mechanical match. Each bullet below is the remainder of that
|
|
70
|
+
perspective's charter:
|
|
42
71
|
|
|
43
72
|
- **Code baseline** (any production/test code change): correctness &
|
|
44
73
|
bugs (logic, null safety, resource leaks, lifecycle); code quality
|
|
45
74
|
(readability, duplication, error handling); test quality (do tests
|
|
46
75
|
verify behavior, cover the change, and would they fail on
|
|
47
|
-
regression?).
|
|
76
|
+
regression?). Verify documentation claims about the code under review
|
|
77
|
+
against the implementation already being read. A contradiction is a
|
|
78
|
+
code-baseline finding, not work to leave for a prose specialist.
|
|
48
79
|
- **Specialists**, added when the change touches their domain:
|
|
49
80
|
- concurrency — any defect requiring reasoning about thread
|
|
50
81
|
interleavings (locks, shared state, atomicity);
|
|
@@ -60,14 +91,26 @@ bullet below is that perspective's charter:
|
|
|
60
91
|
domain specialists). Each charter in that file MUST declare its own
|
|
61
92
|
stable finding-ID prefix (uniqueness rules in §4). Compose them per
|
|
62
93
|
change exactly like the built-in perspectives above.
|
|
63
|
-
- **Non-code
|
|
64
|
-
guidance like this file)
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
94
|
+
- **Non-code baseline** (docs, prompts, process rules, extension
|
|
95
|
+
guidance like this file): internal consistency & cross-references;
|
|
96
|
+
factual and instruction completeness (can a reader execute this
|
|
97
|
+
without guessing?); context budget (what becomes always-loaded vs
|
|
98
|
+
on-demand); and safety of any scripts/hooks/config touched. This
|
|
99
|
+
baseline applies instead of the code baseline unless a mixed diff
|
|
100
|
+
also changes code.
|
|
101
|
+
- **Text specialist**, added when a change ships user-facing or
|
|
102
|
+
licensing-adjacent prose: licensing and provenance exposure;
|
|
103
|
+
coherence with the project's writing convention; proportionality and
|
|
104
|
+
duplication; register, audience fit, structure, and accuracy as
|
|
105
|
+
prose. User documentation, command help, release notes, prompts, and
|
|
106
|
+
public-facing messages normally trigger it. Do not dispatch it solely
|
|
107
|
+
for a short internal code comment, test name, fixture string, or
|
|
108
|
+
mechanical label unless the text is licensing-adjacent or its prose
|
|
109
|
+
risk independently warrants review.
|
|
110
|
+
- **Mixed diffs**: scope each reviewer to its charter. Code reviewers own
|
|
111
|
+
claims that describe their in-scope code even when those claims live
|
|
112
|
+
in documentation. They do not take on the rest of the non-code or text
|
|
113
|
+
charter, and non-code reviewers do not take on code correctness.
|
|
71
114
|
- Selection is a judgment call, not a rigid filter — when in doubt,
|
|
72
115
|
include the perspective. Risk/complexity changes how deep iteration
|
|
73
116
|
goes (§6), never which perspectives run.
|
|
@@ -156,13 +199,19 @@ review dispatch:
|
|
|
156
199
|
library code instead of assuming its semantics.
|
|
157
200
|
- Enumerate cases exhaustively along the changed execution paths
|
|
158
201
|
(branches, error paths, boundary values); mark each case as checked
|
|
159
|
-
or explicitly out of scope.
|
|
202
|
+
or explicitly out of scope. For prose, enumerate the affected
|
|
203
|
+
audiences, reader tasks, claims, definitions, cross-references,
|
|
204
|
+
examples, exceptions, and boundary conditions instead of execution
|
|
205
|
+
paths.
|
|
160
206
|
- Back every defect claim (blocker or should-fix) with a concrete
|
|
161
207
|
counterexample: the input, state, or interleaving that triggers the
|
|
162
208
|
defect, traced through the code.
|
|
163
209
|
- Back every correctness claim ("no issue here") with a justification
|
|
164
210
|
for why no counterexample exists — not merely the absence of one
|
|
165
|
-
found.
|
|
211
|
+
found. For prose, proof of absence means a bounded, reproducible check
|
|
212
|
+
of the relevant set. Examples include checker output, targeted
|
|
213
|
+
searches, re-resolved references, and comparison with authoritative
|
|
214
|
+
sources. State explicitly what those checks cannot establish.
|
|
166
215
|
- Before finalizing, run an alternative-hypothesis check: "if the
|
|
167
216
|
opposite verdict were true, what evidence would exist?" — then look
|
|
168
217
|
for that evidence.
|
|
@@ -170,3 +219,7 @@ review dispatch:
|
|
|
170
219
|
sought → confirmed / refuted / refined) instead of wandering.
|
|
171
220
|
- Derive the final verdict explicitly from the stated definitions and
|
|
172
221
|
claims, not from overall impression.
|
|
222
|
+
- Treat a fix series as a changed artifact that needs review. Verify the
|
|
223
|
+
addressed finding, then review the fix diff and the cumulative result
|
|
224
|
+
for new paths, claims, and regressions. Clearing the original finding
|
|
225
|
+
does not clear defects introduced by its fix.
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
# Writing guidance and the prose checker
|
|
2
|
+
|
|
3
|
+
Slate ships one writing convention and one command that measures
|
|
4
|
+
prose against a mechanical proxy for it. Both are opt-in: with
|
|
5
|
+
`writing.check` absent or false nothing about them is loaded,
|
|
6
|
+
rendered or paid for.
|
|
7
|
+
|
|
8
|
+
This document is reference documentation, not workflow doctrine. It
|
|
9
|
+
is the definition of record for the rule set, the severity classes,
|
|
10
|
+
the command line and the caps. The always-loaded doctrine rule states
|
|
11
|
+
the convention in a few lines and cites this file for everything
|
|
12
|
+
else, so that a session pays for the detail only when it reads it.
|
|
13
|
+
|
|
14
|
+
## What the checker is, and what it is not
|
|
15
|
+
|
|
16
|
+
`extension/writing-check.mjs` is a dependency-free Node command. It
|
|
17
|
+
reads text, strips the parts that are not prose, splits what remains
|
|
18
|
+
into blocks and sentences, and reports surface facts about them:
|
|
19
|
+
sentence length, sentence count per paragraph, and matches for a
|
|
20
|
+
closed list of twelve patterns. It emits per-rule counts, length
|
|
21
|
+
distributions and one finding record per match, each with a source
|
|
22
|
+
offset and a framed excerpt.
|
|
23
|
+
|
|
24
|
+
**It is a proxy, and the proxy is dictionary-free.** It embeds no
|
|
25
|
+
controlled vocabulary and consults none. It therefore cannot decide
|
|
26
|
+
whether a word is approved, whether a word is used in an approved
|
|
27
|
+
sense or part of speech, or whether a technical term is legitimate.
|
|
28
|
+
It reports those limits in its own output, under `NOT CHECKED` — see
|
|
29
|
+
[What the checker declares unchecked](#what-the-checker-declares-unchecked).
|
|
30
|
+
|
|
31
|
+
**It establishes no conformance with any writing standard.** ASD-STE100
|
|
32
|
+
inspired the choice of what to measure. Slate claims no conformance
|
|
33
|
+
with it. No text, example or dictionary entry from the standard is
|
|
34
|
+
reproduced here or in the checker. TechScribe's
|
|
35
|
+
[public analysis](https://www.simplified-english.co.uk/analysis.html#write-short-sentences)
|
|
36
|
+
is the source for the 20- and 25-word figures. Boeing's
|
|
37
|
+
[public checker page](https://www.boeing.com/company/simplified-english-checker)
|
|
38
|
+
also publishes that pair and is the source for the six-sentence figure.
|
|
39
|
+
The source repository keeps the citation trail in its non-published
|
|
40
|
+
`research/writing-threshold-provenance.md`. Slate defines the severity
|
|
41
|
+
classes, patterns and suppression lists, and applies one band uniformly
|
|
42
|
+
to all prose rather than selecting a limit by text type. A clean run
|
|
43
|
+
is evidence about surface form only. It is not a certificate.
|
|
44
|
+
|
|
45
|
+
**It is not an authority over a reviewer.** A match is a place to
|
|
46
|
+
look, not a verdict. `review-rules.md` governs how a text reviewer
|
|
47
|
+
turns checker output into findings and severities, and it is the
|
|
48
|
+
document to follow when reviewing prose. The short version: a
|
|
49
|
+
`fail` class match deserves inspection, an `advisory` class match is
|
|
50
|
+
never a defect on its own, and the checker cannot see meaning,
|
|
51
|
+
accuracy, structure or completeness at all.
|
|
52
|
+
|
|
53
|
+
## Enabling it
|
|
54
|
+
|
|
55
|
+
One boolean in the project's `.pi/slate.json`:
|
|
56
|
+
|
|
57
|
+
```json
|
|
58
|
+
{ "writing": { "check": true } }
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
The default is false. An absent `writing` key is silent. A malformed
|
|
62
|
+
one warns once and leaves the feature off, as does a non-boolean
|
|
63
|
+
`check`; unknown keys under `writing` warn and are ignored. The
|
|
64
|
+
validator is `sanitizeWritingConfig` in `extension/writing.ts`.
|
|
65
|
+
|
|
66
|
+
Slate reads project config only for a TRUSTED project. Both
|
|
67
|
+
prompt-visible sites also re-check trust when they inject the doctrine
|
|
68
|
+
rule or worker preamble. The worker check is defense in depth for a
|
|
69
|
+
future direct call to `openWorkerSession`; the current config path is
|
|
70
|
+
already gated in `extension/index.ts`. An untrusted project therefore
|
|
71
|
+
behaves as if the switch were off, whatever its `slate.json` says.
|
|
72
|
+
|
|
73
|
+
The switch turns on three things and nothing else:
|
|
74
|
+
|
|
75
|
+
1. **The doctrine rule.** One numbered rule is appended to the
|
|
76
|
+
orchestrator's system prompt while orchestrator mode is on. It
|
|
77
|
+
states the convention and cites this file by absolute path.
|
|
78
|
+
2. **The worker preamble sentence.** Every worker thread dispatched
|
|
79
|
+
while the switch is on gets one extra sentence of guidance in its
|
|
80
|
+
preamble. With the switch off the preamble is byte-identical to
|
|
81
|
+
its pre-feature text.
|
|
82
|
+
3. **The turn status line.** In an interactive session, the Slate
|
|
83
|
+
status line gains a `writing <n>/<m>` counter: `m` turns of the
|
|
84
|
+
orchestrator's own prose were measured and `n` of them carried at
|
|
85
|
+
least one `fail` class match.
|
|
86
|
+
|
|
87
|
+
Running the command by hand needs no config at all. It is a plain
|
|
88
|
+
Node command and it works whether or not the switch is on.
|
|
89
|
+
|
|
90
|
+
## What it costs when it is on
|
|
91
|
+
|
|
92
|
+
The doctrine rule is part of every orchestrator system prompt, so its
|
|
93
|
+
cost is paid on every turn. The worker addition is paid once per
|
|
94
|
+
worker dispatch. `context-budget.md` defines the portable-character
|
|
95
|
+
measure, records the whole-doctrine sizes and owns the size budget.
|
|
96
|
+
|
|
97
|
+
An interactive session also runs the checker synchronously after each
|
|
98
|
+
completed assistant message. The hook refuses text over 16 KiB and
|
|
99
|
+
reports `writing skipped (message too large)`. This TUI bound is
|
|
100
|
+
separate from the command's 1 MiB input cap. The hook is human-only
|
|
101
|
+
telemetry: it does not change the model input, and a checker failure
|
|
102
|
+
reports `writing unavailable` rather than failing the turn.
|
|
103
|
+
|
|
104
|
+
## What counts as prose
|
|
105
|
+
|
|
106
|
+
Rules run over blocks, and only some blocks are prose. Before any
|
|
107
|
+
block is formed, the normalizer BLANKS the spans that are not prose,
|
|
108
|
+
keeping the source length so that every reported offset still points
|
|
109
|
+
into the original text:
|
|
110
|
+
|
|
111
|
+
- fenced code blocks, including an unterminated final fence;
|
|
112
|
+
- indented code (four or more leading spaces followed by a character
|
|
113
|
+
that is not a list marker);
|
|
114
|
+
- unified-diff lines (`diff --git`, `@@`, `---`, `+++`);
|
|
115
|
+
- timestamped log lines (an ISO-8601 timestamp followed by a level
|
|
116
|
+
such as `INFO` or `ERROR`);
|
|
117
|
+
- HTML comments;
|
|
118
|
+
- inline code spans;
|
|
119
|
+
- autolinks (`<https://…>`, `<mailto:…>`) and bare URLs.
|
|
120
|
+
|
|
121
|
+
What remains is split into lines and classified: `table-row`,
|
|
122
|
+
`heading`, `blockquote`, `list-item` or `paragraph`. Consecutive
|
|
123
|
+
paragraph lines join into one block with a single space, so a sentence
|
|
124
|
+
wrapped across source lines is one sentence. The block keeps a map
|
|
125
|
+
from its joined text back to source offsets, so findings on a
|
|
126
|
+
multi-line paragraph report the right characters.
|
|
127
|
+
|
|
128
|
+
Rules and prose statistics apply to `paragraph`, `list-item` and
|
|
129
|
+
`blockquote` blocks. Headings and table rows are parsed and counted as
|
|
130
|
+
blocks, but no rule fires in them and they contribute no words or
|
|
131
|
+
sentences to the statistics. `PARA6` is narrower still: it applies to
|
|
132
|
+
`paragraph` blocks only.
|
|
133
|
+
|
|
134
|
+
Sentences are split at `.`, `!` or `?` when followed by whitespace or
|
|
135
|
+
the end of the text. A period is treated as internal, and does not
|
|
136
|
+
split, when it is doubled, sits between two digits, ends one of a
|
|
137
|
+
small fixed abbreviation list (`e.g.`, `i.e.`, `etc.`, `vs.`, `dr.`,
|
|
138
|
+
`mr.`, `mrs.`, `ms.`, `prof.`, `sr.`, `jr.`, `no.`, `fig.`,
|
|
139
|
+
`approx.`, `dept.`, `inc.`, `ltd.`, `st.`), or closes an initialism
|
|
140
|
+
such as `U.S.`. An ellipsis is one punctuation run, not three empty
|
|
141
|
+
sentences. A candidate sentence with no letter in it is dropped.
|
|
142
|
+
|
|
143
|
+
A word is a run of letters and digits, with internal apostrophes and
|
|
144
|
+
hyphens kept, that contains at least one letter. So a hyphenated
|
|
145
|
+
compound counts as one word and a bare number counts as none.
|
|
146
|
+
|
|
147
|
+
Four of the twelve rules ignore identifier-shaped text. `PASSIVE`,
|
|
148
|
+
`INGFORM`, `NOUNCLUSTER` and `MULTICMD` run over a filtered token
|
|
149
|
+
list with path-like tokens, dotted names, `snake_case`, `camelCase`
|
|
150
|
+
and em-dash-bearing quotations removed, so a file name or a symbol
|
|
151
|
+
cannot trip them. The length, punctuation and parenthetical rules see
|
|
152
|
+
the unfiltered text.
|
|
153
|
+
|
|
154
|
+
## The rules
|
|
155
|
+
|
|
156
|
+
Twelve rules in four severity classes. The class is the checker's own
|
|
157
|
+
label, reported with every finding and in the per-rule summary.
|
|
158
|
+
|
|
159
|
+
**`fail` — a mechanical violation of the convention.**
|
|
160
|
+
|
|
161
|
+
| rule | what it flags |
|
|
162
|
+
| --- | --- |
|
|
163
|
+
| `SENT25` | a sentence of more than 25 words |
|
|
164
|
+
| `PARA6` | a paragraph block of more than 6 sentences |
|
|
165
|
+
| `SEMICOLON` | each `;` in a prose block |
|
|
166
|
+
| `CONTRACTION` | a contracted form. Two shapes match: a pronoun or one of `that`, `there`, `here`, `what`, `who`, `how`, `where`, `when`, `why`, `let` carrying `n't` or `'re`, `'ll`, `'ve`, `'d`, `'m`, `'s`; and a negated auxiliary such as `don't`, `won't` or `shan't`. Straight and curly apostrophes both match |
|
|
167
|
+
|
|
168
|
+
**`warning` — over the comfortable length, under the failing one.**
|
|
169
|
+
|
|
170
|
+
| rule | what it flags |
|
|
171
|
+
| --- | --- |
|
|
172
|
+
| `SENT20` | a sentence of more than 20 words and at most 25 |
|
|
173
|
+
|
|
174
|
+
A sentence reports one length result at most: `SENT25` when it is
|
|
175
|
+
over 25 words, `SENT20` when it is over 20, and nothing otherwise.
|
|
176
|
+
|
|
177
|
+
**`house-style` — legitimate constructions this convention avoids.**
|
|
178
|
+
|
|
179
|
+
| rule | what it flags |
|
|
180
|
+
| --- | --- |
|
|
181
|
+
| `PARENTHETICAL_PAREN` | a parenthesis pair whose content looks like a clause rather than an aside. The test is a surface one: an auxiliary or `be` form, a word ending in `ed` or `es`, or a non-initial word ending in `s` |
|
|
182
|
+
| `PARENTHETICAL_DASH` | a paired em dash, or a spaced en dash pair, enclosing text. A pair inside a quotation that itself contains an em dash is suppressed |
|
|
183
|
+
| `SLASHED` | a letters-only `word/word` construction such as `and/or`. Paths and URLs are already blanked, and a longer slash run does not match |
|
|
184
|
+
|
|
185
|
+
**`advisory` — noisy heuristics, never a defect on their own.**
|
|
186
|
+
|
|
187
|
+
| rule | what it flags |
|
|
188
|
+
| --- | --- |
|
|
189
|
+
| `PASSIVE` | a `be` form, optionally followed by an `-ly` adverb, then a likely participle: a word ending in `ed`, or one of a fixed irregular-participle list. A fixed list of state-like participles (`connected`, `installed`, `configured` and others) is suppressed, which lowers noise and can hide a real passive |
|
|
190
|
+
| `INGFORM` | a word ending in `ing` that is not preceded by a `be` form. A first token of a multi-token sentence is skipped, deliberately, because it is usually a gerund subject or a heading-like phrase |
|
|
191
|
+
| `NOUNCLUSTER` | a run of four or more words with no function word between them |
|
|
192
|
+
| `MULTICMD` | a token shape: a sentence starts with a non-function word and later has `and`, `and then` or `, then` followed by another non-function word. It approximates multiple commands but can match ordinary coordination such as “Files and folders exist.” |
|
|
193
|
+
|
|
194
|
+
Only the `fail` class moves the turn status counter. A `warning`,
|
|
195
|
+
`house-style` or `advisory` match is reported and counted, but the
|
|
196
|
+
counter treats the turn as clean.
|
|
197
|
+
|
|
198
|
+
Two properties are worth knowing before reading a report. Rule ids
|
|
199
|
+
and classes come from one closed table in the module, so a report can
|
|
200
|
+
never name a rule the table does not define. Every finding carries a
|
|
201
|
+
source offset range, a block index, a sentence index where the rule
|
|
202
|
+
is sentence-scoped, and an excerpt framed in `⟦…⟧` with the framing
|
|
203
|
+
characters removed from the content, so the frame is always the
|
|
204
|
+
checker's and never the text's.
|
|
205
|
+
|
|
206
|
+
## The command line
|
|
207
|
+
|
|
208
|
+
```
|
|
209
|
+
node extension/writing-check.mjs --input records.jsonl [--format json|text]
|
|
210
|
+
node extension/writing-check.mjs --file PATH [--file PATH ...] [--format json|text]
|
|
211
|
+
node extension/writing-check.mjs --diff changes.diff [--format json|text]
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
Exactly one input mode per run. `--format` is `json` by default and
|
|
215
|
+
`text` for a human-readable report. `--help` prints the usage.
|
|
216
|
+
`--input` and `--diff` take one path each; `--file` repeats.
|
|
217
|
+
|
|
218
|
+
Every input must be a REGULAR FILE. The command refuses a symlink, a
|
|
219
|
+
directory, a FIFO or a device. It checks the opened descriptor and
|
|
220
|
+
rejects a special file substituted between the initial check and the
|
|
221
|
+
open. It also detects growth while reading. It does not detect every
|
|
222
|
+
race or a same-size in-place modification.
|
|
223
|
+
|
|
224
|
+
### `--input`: JSONL records
|
|
225
|
+
|
|
226
|
+
One JSON object per line, each with a string `text` field. A missing
|
|
227
|
+
or non-string field is rejected. `id` names the record in the report;
|
|
228
|
+
with no `id` the record is named from its `session` and `line` fields,
|
|
229
|
+
else from its position. Blank lines are skipped. This is the mode for
|
|
230
|
+
text that is not a file on disk — an extracted message, a comment
|
|
231
|
+
body, a release note draft.
|
|
232
|
+
|
|
233
|
+
### `--file`: whole files
|
|
234
|
+
|
|
235
|
+
One record per path, named by the path. This is the mode for a
|
|
236
|
+
complete prose artifact: a document, a README, a release note.
|
|
237
|
+
|
|
238
|
+
### `--diff`: added lines of a unified diff
|
|
239
|
+
|
|
240
|
+
Reads a unified diff and checks the ADDED lines only. Consecutive
|
|
241
|
+
added lines form one record, named `path:firstAddedLine`, so a
|
|
242
|
+
sentence added across several lines is checked as one sentence. It
|
|
243
|
+
parses hunk headers strictly and fails loudly on a malformed diff
|
|
244
|
+
rather than checking a mis-attributed line.
|
|
245
|
+
|
|
246
|
+
Diff mode is for PROSE review, and its file filter is deliberately
|
|
247
|
+
narrow. It includes `.md`, `.markdown`, `.mdx`, `.txt`, `.rst`,
|
|
248
|
+
`.adoc` and `.asciidoc`, plus a small closed set of extensionless
|
|
249
|
+
conventional names (`readme`, `changelog`, `changes`, `contributing`,
|
|
250
|
+
`release_notes`).
|
|
251
|
+
|
|
252
|
+
It therefore EXCLUDES source files entirely, and that exclusion is
|
|
253
|
+
the mode's main limitation: prose that lives inside code — a
|
|
254
|
+
user-facing string, a comment, a help text, a commit message — is not
|
|
255
|
+
reviewed by `--diff`. Inspect those directly, or extract the text and
|
|
256
|
+
pass it through `--input`. Git's quoted-path encoding is decoded
|
|
257
|
+
before a path is classified or reported; a path that is not valid
|
|
258
|
+
UTF-8 is skipped and named in a `DIFF FILES SKIPPED` line rather than
|
|
259
|
+
being checked under a false name.
|
|
260
|
+
|
|
261
|
+
### Caps
|
|
262
|
+
|
|
263
|
+
Every cap is a hard bound with a reported consequence, never a silent
|
|
264
|
+
truncation.
|
|
265
|
+
|
|
266
|
+
| cap | value | applies to |
|
|
267
|
+
| --- | --- | --- |
|
|
268
|
+
| input bytes | 1,048,576 | one record, and the total across a run's records or files |
|
|
269
|
+
| records | 10,000 | a JSONL input, a `--file` list, and a diff's added-line runs |
|
|
270
|
+
| findings per record | 1,000 | one record's reported findings |
|
|
271
|
+
| findings per run | 20,000 | the whole run, shared as an EQUAL per-record allowance |
|
|
272
|
+
| excerpt characters | 2,000 | one finding's excerpt, framing characters included |
|
|
273
|
+
| reported stripped spans | 5,000 | the reported non-prose span list |
|
|
274
|
+
| reported block details | 5,000 | the reported per-block detail list |
|
|
275
|
+
|
|
276
|
+
The per-run budget is divided equally rather than first-come, so a
|
|
277
|
+
record's position never decides what it keeps, and the floor is one
|
|
278
|
+
finding per record so that a large run still shows every offending
|
|
279
|
+
record. When the run budget binds, the report states it BEFORE any
|
|
280
|
+
finding line, so a reduced report cannot read as a complete one. A
|
|
281
|
+
record that loses findings reports its own shortfall. The two
|
|
282
|
+
5,000-span caps bound the REPORT only: blanking and counting are
|
|
283
|
+
always complete.
|
|
284
|
+
|
|
285
|
+
## What the checker declares unchecked
|
|
286
|
+
|
|
287
|
+
The report ends with a `NOT CHECKED` list. It is part of the output on
|
|
288
|
+
purpose: the limits are a result, not a footnote.
|
|
289
|
+
|
|
290
|
+
| id | why not |
|
|
291
|
+
| --- | --- |
|
|
292
|
+
| `APPROVED_WORD` | approved-word membership needs an authorized controlled dictionary |
|
|
293
|
+
| `APPROVED_MEANING_POS` | approved meaning and part of speech need authorized dictionary data and contextual review |
|
|
294
|
+
| `NOUNCLUSTER_CORRECTNESS` | the token-run heuristic cannot decide whether a noun cluster is wrong or is an approved technical term |
|
|
295
|
+
| `TOPIC_UNITY` | topic unity and topic-sentence adequacy need semantic review |
|
|
296
|
+
| `WARNING_CAUTION_CONTENT` | risk level, placement, command adequacy and consequences need structured metadata and human review |
|
|
297
|
+
|
|
298
|
+
## What this feature deliberately does not do
|
|
299
|
+
|
|
300
|
+
These are settled decisions. Read them as scope, not as a backlog.
|
|
301
|
+
|
|
302
|
+
- **No controlled vocabulary, and no plan for one.** Word approval
|
|
303
|
+
needs an authorized dictionary. Slate ships none and embeds none.
|
|
304
|
+
- **No conformance claim.** ASD-STE100 inspires the choice of what to
|
|
305
|
+
measure. Slate claims no conformance. No text, example or dictionary
|
|
306
|
+
entry from the standard is reproduced here or in the code.
|
|
307
|
+
- **No vocabulary or terminology enforcement.** The convention keeps
|
|
308
|
+
exact technical terms, so the checker must not push a writer off
|
|
309
|
+
them. Nothing in it rewrites, replaces or grades a word choice.
|
|
310
|
+
- **No broad prose rewriting.** The checker reports and never edits.
|
|
311
|
+
Every fix is a human or agent decision.
|
|
312
|
+
- **No verdict.** No rule class is a defect by itself, and no clean
|
|
313
|
+
run is a pass. `review-rules.md` owns the mapping from a match to a
|
|
314
|
+
severity, and a reviewer owns the judgement.
|
|
315
|
+
- **Not a gate.** The turn hook is telemetry. It returns nothing to
|
|
316
|
+
the model, changes no prompt, and cannot fail a turn.
|
|
317
|
+
- **Not universal.** The convention covers prose written for people:
|
|
318
|
+
documents, README text, pull request and commit text, issues,
|
|
319
|
+
review comments, release notes and messages to the user. Research
|
|
320
|
+
logs, worker task text and this repository's own agent instruction
|
|
321
|
+
file are excluded, because a dense exact register serves them
|
|
322
|
+
better.
|