@clossys/writer 0.3.8 → 0.4.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.
Files changed (56) hide show
  1. package/README.md +103 -4
  2. package/dist/addressability.d.ts +11 -1
  3. package/dist/addressability.d.ts.map +1 -1
  4. package/dist/addressability.js +169 -3
  5. package/dist/addressability.js.map +1 -1
  6. package/dist/approved-copy-coverage-rate-cli.d.ts +10 -0
  7. package/dist/approved-copy-coverage-rate-cli.d.ts.map +1 -0
  8. package/dist/approved-copy-coverage-rate-cli.js +59 -0
  9. package/dist/approved-copy-coverage-rate-cli.js.map +1 -0
  10. package/dist/approved-copy-coverage-rate.d.ts +23 -0
  11. package/dist/approved-copy-coverage-rate.d.ts.map +1 -0
  12. package/dist/approved-copy-coverage-rate.js +153 -0
  13. package/dist/approved-copy-coverage-rate.js.map +1 -0
  14. package/dist/cli.d.ts.map +1 -1
  15. package/dist/cli.js +216 -9
  16. package/dist/cli.js.map +1 -1
  17. package/dist/fold-wallpaper-gate.d.ts +7 -0
  18. package/dist/fold-wallpaper-gate.d.ts.map +1 -0
  19. package/dist/fold-wallpaper-gate.js +29 -0
  20. package/dist/fold-wallpaper-gate.js.map +1 -0
  21. package/dist/index.d.ts +6 -0
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +3 -0
  24. package/dist/index.js.map +1 -1
  25. package/dist/live-copy-gate.d.ts +16 -0
  26. package/dist/live-copy-gate.d.ts.map +1 -0
  27. package/dist/live-copy-gate.js +135 -0
  28. package/dist/live-copy-gate.js.map +1 -0
  29. package/dist/render-registry-parity.d.ts +20 -0
  30. package/dist/render-registry-parity.d.ts.map +1 -0
  31. package/dist/render-registry-parity.js +45 -0
  32. package/dist/render-registry-parity.js.map +1 -0
  33. package/dist/schema.d.ts.map +1 -1
  34. package/dist/schema.js +22 -0
  35. package/dist/schema.js.map +1 -1
  36. package/dist/treatment-word-budget.d.ts +31 -0
  37. package/dist/treatment-word-budget.d.ts.map +1 -0
  38. package/dist/treatment-word-budget.js +80 -0
  39. package/dist/treatment-word-budget.js.map +1 -0
  40. package/dist/types.d.ts +11 -0
  41. package/dist/types.d.ts.map +1 -1
  42. package/fit-signals.json +11 -0
  43. package/package.json +202 -6
  44. package/skill/SKILL.md +49 -0
  45. package/src/addressability.ts +186 -4
  46. package/src/approved-copy-coverage-rate-cli.ts +55 -0
  47. package/src/approved-copy-coverage-rate.ts +201 -0
  48. package/src/cli.ts +228 -9
  49. package/src/fold-wallpaper-gate.ts +29 -0
  50. package/src/index.ts +16 -0
  51. package/src/live-copy-gate.ts +150 -0
  52. package/src/render-registry-parity.ts +65 -0
  53. package/src/schema.ts +24 -0
  54. package/src/treatment-word-budget.ts +100 -0
  55. package/src/types.ts +11 -0
  56. package/CHANGELOG.md +0 -307
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Treatment-level word budgets for registry entries — display headings,
3
+ * eyebrows, button labels, and other short treatments that are not body
4
+ * measure. Pure gate: takes an already-validated `CopyRegistry`, never I/O.
5
+ */
6
+ import type { CopyEntryId, CopyRegistry } from "./types.js";
7
+ /** Known slot treatments with a default maximum word count when the entry omits `maxWords`. */
8
+ export declare const DEFAULT_TREATMENT_WORD_BUDGETS: Readonly<Record<string, number>>;
9
+ export type TreatmentWordBudgetRule = "word-budget-exceeded" | "unknown-treatment";
10
+ export interface TreatmentWordBudgetFinding {
11
+ rule: TreatmentWordBudgetRule;
12
+ severity: "error";
13
+ entryId: CopyEntryId;
14
+ treatment: string;
15
+ maxWords: number;
16
+ actualWords: number;
17
+ message: string;
18
+ }
19
+ export interface TreatmentWordBudgetResult {
20
+ entriesChecked: number;
21
+ findings: TreatmentWordBudgetFinding[];
22
+ }
23
+ /** Counts words in resolved copy text (whitespace-separated tokens). */
24
+ export declare function countCopyWords(text: string): number;
25
+ /**
26
+ * Fails when an approved entry declares a `treatment` (or `maxWords`) and
27
+ * its `text` exceeds the applicable budget. Draft and retired entries are
28
+ * skipped — only copy that can resolve at render is measured.
29
+ */
30
+ export declare function checkTreatmentWordBudgets(registry: CopyRegistry): TreatmentWordBudgetResult;
31
+ //# sourceMappingURL=treatment-word-budget.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"treatment-word-budget.d.ts","sourceRoot":"","sources":["../src/treatment-word-budget.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAqB,MAAM,YAAY,CAAC;AAE/E,+FAA+F;AAC/F,eAAO,MAAM,8BAA8B,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAI3E,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG,sBAAsB,GAAG,mBAAmB,CAAC;AAEnF,MAAM,WAAW,0BAA0B;IACzC,IAAI,EAAE,uBAAuB,CAAC;IAC9B,QAAQ,EAAE,OAAO,CAAC;IAClB,OAAO,EAAE,WAAW,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,yBAAyB;IACxC,cAAc,EAAE,MAAM,CAAC;IACvB,QAAQ,EAAE,0BAA0B,EAAE,CAAC;CACxC;AAED,wEAAwE;AACxE,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAInD;AAiBD;;;;GAIG;AACH,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,YAAY,GAAG,yBAAyB,CAwC3F"}
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Treatment-level word budgets for registry entries — display headings,
3
+ * eyebrows, button labels, and other short treatments that are not body
4
+ * measure. Pure gate: takes an already-validated `CopyRegistry`, never I/O.
5
+ */
6
+ /** Known slot treatments with a default maximum word count when the entry omits `maxWords`. */
7
+ export const DEFAULT_TREATMENT_WORD_BUDGETS = {
8
+ "display-heading": 12,
9
+ eyebrow: 8,
10
+ button: 6,
11
+ };
12
+ /** Counts words in resolved copy text (whitespace-separated tokens). */
13
+ export function countCopyWords(text) {
14
+ const trimmed = text.trim();
15
+ if (trimmed.length === 0)
16
+ return 0;
17
+ return trimmed.split(/\s+/u).length;
18
+ }
19
+ function budgetForEntry(entry) {
20
+ const treatment = entry.treatment;
21
+ if (typeof treatment !== "string" || treatment.trim().length === 0)
22
+ return undefined;
23
+ const normalized = treatment.trim();
24
+ if (entry.maxWords !== undefined) {
25
+ if (typeof entry.maxWords !== "number" || !Number.isFinite(entry.maxWords) || entry.maxWords < 1) {
26
+ return undefined;
27
+ }
28
+ return { treatment: normalized, maxWords: Math.floor(entry.maxWords) };
29
+ }
30
+ const defaultMax = DEFAULT_TREATMENT_WORD_BUDGETS[normalized];
31
+ if (defaultMax === undefined)
32
+ return undefined;
33
+ return { treatment: normalized, maxWords: defaultMax };
34
+ }
35
+ /**
36
+ * Fails when an approved entry declares a `treatment` (or `maxWords`) and
37
+ * its `text` exceeds the applicable budget. Draft and retired entries are
38
+ * skipped — only copy that can resolve at render is measured.
39
+ */
40
+ export function checkTreatmentWordBudgets(registry) {
41
+ const findings = [];
42
+ let entriesChecked = 0;
43
+ for (const entry of registry.entries) {
44
+ if (entry.status !== "approved")
45
+ continue;
46
+ const budget = budgetForEntry(entry);
47
+ if (!budget) {
48
+ if (typeof entry.treatment === "string" && entry.treatment.trim().length > 0) {
49
+ const normalized = entry.treatment.trim();
50
+ if (entry.maxWords === undefined && DEFAULT_TREATMENT_WORD_BUDGETS[normalized] === undefined) {
51
+ findings.push({
52
+ rule: "unknown-treatment",
53
+ severity: "error",
54
+ entryId: entry.id,
55
+ treatment: normalized,
56
+ maxWords: 0,
57
+ actualWords: countCopyWords(entry.text),
58
+ message: `Entry "${entry.id}" declares treatment "${normalized}" with no maxWords and no built-in default — add maxWords or use a known treatment (${Object.keys(DEFAULT_TREATMENT_WORD_BUDGETS).join(", ")}).`,
59
+ });
60
+ }
61
+ }
62
+ continue;
63
+ }
64
+ entriesChecked++;
65
+ const actualWords = countCopyWords(entry.text);
66
+ if (actualWords > budget.maxWords) {
67
+ findings.push({
68
+ rule: "word-budget-exceeded",
69
+ severity: "error",
70
+ entryId: entry.id,
71
+ treatment: budget.treatment,
72
+ maxWords: budget.maxWords,
73
+ actualWords,
74
+ message: `Entry "${entry.id}" (${budget.treatment}) has ${actualWords} word(s) but the budget is ${budget.maxWords}.`,
75
+ });
76
+ }
77
+ }
78
+ return { entriesChecked, findings };
79
+ }
80
+ //# sourceMappingURL=treatment-word-budget.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"treatment-word-budget.js","sourceRoot":"","sources":["../src/treatment-word-budget.ts"],"names":[],"mappings":"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"}
package/dist/types.d.ts CHANGED
@@ -147,6 +147,17 @@ export interface CopyEntry {
147
147
  * same seam, one layer over).
148
148
  */
149
149
  factRef?: string;
150
+ /**
151
+ * Named slot treatment for word-budget governance — e.g. `display-heading`,
152
+ * `eyebrow`, `button`. When set on an approved registry entry, `writer-check`
153
+ * applies `maxWords` or the built-in default for that treatment.
154
+ */
155
+ treatment?: string;
156
+ /**
157
+ * Maximum word count for this entry's `text` when `treatment` is set.
158
+ * Overrides the built-in default table when present.
159
+ */
160
+ maxWords?: number;
150
161
  }
151
162
  /**
152
163
  * Content-derived provenance for one target-locale translation. Present only
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "role": "@clossys/writer",
4
+ "signals": [
5
+ {
6
+ "id": "ships-audience-facing-copy",
7
+ "prompt": "The repository ships text a person outside the team reads: interface strings, page copy, or other audience-facing prose in its source.",
8
+ "evidenceKind": "repository-structure"
9
+ }
10
+ ]
11
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clossys/writer",
3
- "version": "0.3.8",
3
+ "version": "0.4.0",
4
4
  "private": false,
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -19,19 +19,215 @@
19
19
  "./voice-record.template.jsonc": "./templates/voice-record.template.jsonc"
20
20
  },
21
21
  "bin": {
22
- "writer-check": "dist/cli.js"
22
+ "writer-check": "dist/cli.js",
23
+ "writer-rate-check": "dist/approved-copy-coverage-rate-cli.js"
23
24
  },
24
25
  "files": [
25
26
  "dist",
26
27
  "src",
27
28
  "!src/**/*.test.ts",
28
29
  "templates",
29
- "CHANGELOG.md",
30
+ "fit-signals.json",
30
31
  "README.md",
31
- "LICENSE"
32
+ "LICENSE",
33
+ "skill"
32
34
  ],
33
35
  "sideEffects": false,
34
- "description": "The writer role: is it well said? A consumer-owned language system: voice rules, glossary and claims validation, addressable copy records, source traceability, locale coverage, and passage-layer composition — five gates reachable from one bin. Ships machinery and templates, never a consumer's actual copy or voice.",
36
+ "foundry": {
37
+ "assessment": {
38
+ "bin": "writer-rate-check",
39
+ "invocation": "single-json-input"
40
+ },
41
+ "capabilities": [
42
+ {
43
+ "id": "voice-record",
44
+ "subQuestion": "Is the voice record itself complete — every field bound, every rule derived from a real brand attribute?",
45
+ "worldClass": "The voice record is fully bound (no unedited template placeholder) and every rule traces to a brand-derivation attribute — checked as a record, before any copy is ever measured against it.",
46
+ "inputs": [
47
+ {
48
+ "producerRole": "@clossys/strategist",
49
+ "artifact": "brand-derivation"
50
+ }
51
+ ],
52
+ "outputs": [
53
+ "clossys/writer/voice.json"
54
+ ],
55
+ "proofCase": "voice-derivation-violated",
56
+ "maturity": "partial",
57
+ "v0": true,
58
+ "businessLifecycleStage": "define"
59
+ },
60
+ {
61
+ "id": "copy-registry",
62
+ "subQuestion": "Is every registry entry itself schema-valid, unique, and free of unfilled placeholders?",
63
+ "worldClass": "Every registry entry is schema-valid, has a unique id, and every declared placeholder is present in its own text — registry validity only; whether a component actually USES the registry by id is addressability's own question.",
64
+ "inputs": [],
65
+ "outputs": [
66
+ "clossys/writer/copy-registry.json"
67
+ ],
68
+ "proofCase": "registry-placeholder-missing",
69
+ "maturity": "built",
70
+ "v0": true,
71
+ "businessLifecycleStage": "build"
72
+ },
73
+ {
74
+ "id": "copy-coverage",
75
+ "subQuestion": "Does every user-facing string in code trace to a registered copy entry, by literal match or citation?",
76
+ "worldClass": "Every component string either matches a registered CopyRecord entry's text/alias or carries a copy:<id> citation -- the \"approved copy coverage rate\" metric this role owns (docs/contracts/role-loop-archetypes.json), and what writer-check's default command (copy-clean) actually proves. Distinct from `copy-registry` (is the registry entry itself valid) and from `claims-to-copy-traceability` (does a claim-SHAPED sentence cite an approved Strategist claim).",
77
+ "inputs": [
78
+ {
79
+ "producerRole": "@clossys/writer",
80
+ "artifact": "copy-registry"
81
+ }
82
+ ],
83
+ "outputs": [
84
+ "clossys/writer/.generated/copy-coverage-report.json"
85
+ ],
86
+ "proofCase": "copy-clean",
87
+ "maturity": "built",
88
+ "v0": true,
89
+ "businessLifecycleStage": "build"
90
+ },
91
+ {
92
+ "id": "claims-to-copy-traceability",
93
+ "subQuestion": "Does every claim-shaped or numeric sentence in shipped copy trace to an approved registry claim, by citation?",
94
+ "worldClass": "A currency amount, percentage, multiplier, or superlative sentence in live copy either matches a registered claim's text or carries a claim:<id> citation to one -- checked on the COPY side. Whether Strategist's own facts/markers gate should stop scanning shipped surfaces at all (leaving this the one place that check happens) is a decision pending #1271, not yet a settled boundary -- today `strategist-check` still scans authored prose too (its own facts-clean fixture is an announcement.md).",
95
+ "inputs": [
96
+ {
97
+ "producerRole": "@clossys/strategist",
98
+ "artifact": "claims"
99
+ }
100
+ ],
101
+ "outputs": [
102
+ "clossys/writer/.generated/claim-citations-report.json"
103
+ ],
104
+ "proofCase": "live-magnitude-unmarked",
105
+ "maturity": "partial",
106
+ "v0": true,
107
+ "businessLifecycleStage": "build"
108
+ },
109
+ {
110
+ "id": "live-copy-conformance",
111
+ "subQuestion": "Does live, shipped copy still match its registry source and stay within its word-budget and fold rules?",
112
+ "worldClass": "Shipped copy never drifts silently from its registry source, and the above-the-fold word budget is enforced, not eyeballed.",
113
+ "inputs": [],
114
+ "outputs": [
115
+ "clossys/writer/.generated/live-copy-report.json"
116
+ ],
117
+ "proofCase": "live-fold-wallpaper",
118
+ "maturity": "partial",
119
+ "v0": false,
120
+ "businessLifecycleStage": "operate"
121
+ },
122
+ {
123
+ "id": "addressability",
124
+ "subQuestion": "Is every piece of user-facing prose resolved from the registry by id, never typed directly into a component?",
125
+ "worldClass": "A rename is always cheap: renaming a piece of copy means editing one registry entry, never hunting every component that typed the sentence — #1208's site template reads copy by id on this guarantee.",
126
+ "inputs": [],
127
+ "outputs": [
128
+ "clossys/writer/.generated/addressability-report.json"
129
+ ],
130
+ "proofCase": "addressability-violated",
131
+ "maturity": "built",
132
+ "v0": true,
133
+ "businessLifecycleStage": "build"
134
+ },
135
+ {
136
+ "id": "locale-coverage",
137
+ "subQuestion": "Does the whole set of a project's locales actually cover the same ground, current and correctly interpolating?",
138
+ "worldClass": "Every target locale covers what the source locale covers, with no orphaned or stale translation — governance over WHICH locales are covered, not runtime translation itself.",
139
+ "inputs": [],
140
+ "outputs": [
141
+ "clossys/writer/.generated/locale-coverage-report.json"
142
+ ],
143
+ "proofCase": "locale-coverage-missing",
144
+ "maturity": "built",
145
+ "v0": false,
146
+ "businessLifecycleStage": "operate"
147
+ },
148
+ {
149
+ "id": "voice-conformance",
150
+ "subQuestion": "Is shipped copy actually well said — in voice, in tense, in person, every claim supported?",
151
+ "worldClass": "Every mechanically detectable voice violation (forbidden term, wrong tense or person, an unsupported claim marked as requiring support) is caught against the bound voice record — the answer to \"is it well said\", not merely \"does it trace\".",
152
+ "inputs": [
153
+ {
154
+ "producerRole": "@clossys/writer",
155
+ "artifact": "voice-record"
156
+ }
157
+ ],
158
+ "outputs": [
159
+ "clossys/writer/.generated/voice-conformance-report.json"
160
+ ],
161
+ "proofCase": "live-claim-unsupported",
162
+ "maturity": "partial",
163
+ "v0": false,
164
+ "businessLifecycleStage": "operate"
165
+ },
166
+ {
167
+ "id": "messaging-kit",
168
+ "subQuestion": "Does the product have one governed tagline, pitch-length set, boilerplate, and FAQ that everything else derives from?",
169
+ "worldClass": "One governed messaging kit (tagline, short/medium/long pitch, short/medium/long boilerplate, FAQ) exists and every surface's short-form copy derives from it, per #1204's Identity layer, rather than being improvised per surface.",
170
+ "inputs": [
171
+ {
172
+ "producerRole": "@clossys/strategist",
173
+ "artifact": "positioning"
174
+ }
175
+ ],
176
+ "outputs": [
177
+ "clossys/writer/messaging-kit.json"
178
+ ],
179
+ "proofCase": null,
180
+ "maturity": "planned",
181
+ "v0": true,
182
+ "businessLifecycleStage": "launch"
183
+ },
184
+ {
185
+ "id": "privacy-and-terms-placeholders",
186
+ "subQuestion": "Do the placeholder privacy notice and terms say, in the brand's voice, only what the supplied data-holding facts support, and say plainly that they are not legal advice?",
187
+ "worldClass": "Placeholder privacy-notice and terms text is drafted in the brand's voice from the facts Keeper supplies (what data is held, why, and for how long), never from assumption, and each page is labelled clearly as not legal advice pending review by counsel (#1213). Legal work beyond placeholders is out of scope for this role.",
188
+ "inputs": [],
189
+ "outputs": [
190
+ "clossys/writer/policy-placeholders.json"
191
+ ],
192
+ "proofCase": null,
193
+ "maturity": "planned",
194
+ "v0": true,
195
+ "businessLifecycleStage": "launch"
196
+ }
197
+ ],
198
+ "outputs": [
199
+ "clossys/writer/copy-registry.json",
200
+ "clossys/writer/voice.json"
201
+ ],
202
+ "feeds": [
203
+ {
204
+ "artifact": "copy-registry",
205
+ "path": "clossys/writer/copy-registry.json"
206
+ }
207
+ ],
208
+ "needs": [
209
+ {
210
+ "producerRole": "@clossys/strategist",
211
+ "artifact": "brand-derivation"
212
+ },
213
+ {
214
+ "producerRole": "@clossys/strategist",
215
+ "artifact": "claims"
216
+ }
217
+ ],
218
+ "solves": [
219
+ {
220
+ "problem": "writer-unapproved-copy",
221
+ "statement": "We ship words nobody signed off on.",
222
+ "metric": "approved copy coverage rate",
223
+ "proofCase": "copy-violated",
224
+ "evidence": "designed",
225
+ "capability": "copy-coverage"
226
+ }
227
+ ],
228
+ "fit": "fit-signals.json"
229
+ },
230
+ "description": "The writer role: is it well said? A consumer-owned language system: voice rules, glossary and claims validation, addressable copy records, source traceability, locale coverage, and passage-layer composition \u2014 five gates reachable from one bin. Ships machinery and templates, never a consumer's actual copy or voice.",
35
231
  "keywords": [
36
232
  "writer",
37
233
  "copy",
@@ -63,7 +259,7 @@
63
259
  "devDependencies": {
64
260
  "@types/node": "^26.5.1",
65
261
  "typescript": "~6.0.0",
66
- "vitest": "^5.0.0"
262
+ "vitest": "^5.0.1"
67
263
  },
68
264
  "publishConfig": {
69
265
  "registry": "https://registry.npmjs.org",
package/skill/SKILL.md ADDED
@@ -0,0 +1,49 @@
1
+ ---
2
+ name: clossys-writer
3
+ description: Copy registry, voice conformance, and approved-language coverage. Invoke with @clossys-writer when shipped copy must trace to approved records.
4
+ disable-model-invocation: true
5
+ ---
6
+ # clossys-writer
7
+
8
+ You are Writer. Your job is to keep audience-facing language approved, traceable, and well said.
9
+
10
+ You maintain approved copy records for a named page, voice conformance, and language traceability. With Designer you supply the copy ids and block kinds the in-tree page document cites by reference, and iterate until a local render of that document is the page; Publisher authors and owns the surface document itself (#1205). You do not invent strategy facts, treat yourself as outline-only for Publisher to finish, or publish surfaces.
11
+
12
+
13
+ ## Foundry voices
14
+
15
+ The same team is in every inventoried repo. Name another `@clossys-<package>` to talk to them. A missing mention is a bug, not a compatibility signal. Hiring and fit always go through `@clossys-advisor`.
16
+
17
+ ## Operating wave
18
+
19
+ 1. **Strategist first** — direction and brand facts, across every inventoried product repo that needs it, until the record is current enough to cite.
20
+ 2. **Designer and Writer together** — tokens→atoms→blocks in parallel with copy structure for pre-auth pages. Do not start if Strategist still has no citable direction.
21
+ 3. **Customer inhabit** — independent `@clossys-customer` session speaks first person as the named Audience, fresh look, not a checklist. That person can also be asked for lived feedback on any topic, comparison from their consideration set, what it would take to start or to refer, whether it is worth what it costs them, and what would make them leave. This role does not inhabit the user.
22
+ 4. **Publisher last** — seal approved surfaces (OG/meta consistency and release proof) only after a keep. Start in each repo when that repo's pages exist; do not wait for every sibling.
23
+
24
+ An engine gap or a missing check is a Foundry issue about the package that owns it. Never dump a consumer's strategy. Never name a consumer.
25
+
26
+ ## Handoff citations
27
+
28
+ Cite the strategist handoff: an audience id, approved strategist claim ids (`claim:<id>` in prose), applicable constraint ids (`constraint:<id>`), and the current direction id. Do not edit `clossys/strategist/`. Voice-glossary claims in this package are not strategist claims.
29
+
30
+ ## Pre-auth page
31
+
32
+ Done is exceptional (5) as defined in PRE-AUTH-QUALITY (the brief that ships with `@clossys/designer`). `designer-hero-css-check`, `designer-fold-check`, and `writer-check --live` prove 3 only — never call 3 done or world class. After `designer-fold-check` is green, a bounded taste pass uses desktop and narrow screenshots in a separate session that is not this doer walk; at most 3 inhabit rounds or 45 minutes wall clock, whichever first — see PRE-AUTH-QUALITY (the brief that ships with `@clossys/designer`). This walk does not self-certify exceptional keep. A 5 keep is a synthetic user in that separate session, first person as the named Strategist Audience, not a copy score and not a checklist. This role does not author keep-review evidence and does not inhabit the persona. Name `MarketingView`, `SectionedView`, or a registered web template before filling bands; do not author a page shape the shipped views cannot hold.
33
+
34
+ ## When this package is installed
35
+
36
+ If `node_modules/@clossys/writer` is present (or this package's bins are on PATH), use the exact pin in the tree. Read `package.json` `bin` for the real command names.
37
+ - Assessment CLI: `writer-rate-check`
38
+ - Additional gate CLI: `writer-check`
39
+
40
+ Summarize gate results in human language; keep machine kinds for tooling, not as the default reply.
41
+
42
+ ## When this package is not installed
43
+
44
+ You are here as a person in this repo the same way you are in every other inventoried repo.
45
+
46
+ - Intro and quick questions are always in scope.
47
+ - If this package's engine is not pinned in *this* tree, do not act and do not run a binary. Ask `@clossys-advisor` whether to hire you **in this repository**.
48
+ - Never say "I don't exist here," "open the hub to find me," or "this skill is missing from this folder."
49
+ - Never imply they should npm-install the whole catalogue.