inclusion-md 0.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.
@@ -0,0 +1,95 @@
1
+ #!/usr/bin/env node
2
+ /* eslint-disable no-console */
3
+
4
+ const { init } = require("../lib/init");
5
+
6
+ const HELP = `inclusion-md - scaffold an INCLUSION.md for your repository.
7
+
8
+ Usage:
9
+ npx inclusion-md init [options]
10
+ npx inclusion-md --help
11
+
12
+ Commands:
13
+ init Interactively generate an INCLUSION.md.
14
+
15
+ Options:
16
+ -o, --out <path> Output path (default: ./INCLUSION.md).
17
+ --variant <name> Start from a variant template:
18
+ generic (default) | frontend-app | design-system | backend-api
19
+ --force Overwrite an existing INCLUSION.md without prompting.
20
+ --yes Accept defaults for any unanswered prompts.
21
+ --no-color Disable ANSI color output.
22
+ -h, --help Show this help.
23
+ -v, --version Show CLI version.
24
+
25
+ Examples:
26
+ npx inclusion-md init
27
+ npx inclusion-md init --variant design-system
28
+ npx inclusion-md init --out docs/INCLUSION.md --force
29
+
30
+ Read more: https://github.com/BranonConor/inclusion.md
31
+ `;
32
+
33
+ function parseArgs(argv) {
34
+ const args = {
35
+ command: null,
36
+ out: null,
37
+ variant: null,
38
+ force: false,
39
+ yes: false,
40
+ color: true,
41
+ help: false,
42
+ version: false,
43
+ };
44
+ const rest = argv.slice(2);
45
+ for (let i = 0; i < rest.length; i++) {
46
+ const a = rest[i];
47
+ if (a === "-h" || a === "--help") args.help = true;
48
+ else if (a === "-v" || a === "--version") args.version = true;
49
+ else if (a === "--force") args.force = true;
50
+ else if (a === "--yes" || a === "-y") args.yes = true;
51
+ else if (a === "--no-color") args.color = false;
52
+ else if (a === "-o" || a === "--out") args.out = rest[++i];
53
+ else if (a === "--variant") args.variant = rest[++i];
54
+ else if (!a.startsWith("-") && !args.command) args.command = a;
55
+ else {
56
+ console.error(`Unknown argument: ${a}`);
57
+ process.exit(2);
58
+ }
59
+ }
60
+ return args;
61
+ }
62
+
63
+ async function main() {
64
+ const args = parseArgs(process.argv);
65
+
66
+ if (args.help) {
67
+ process.stdout.write(HELP);
68
+ return;
69
+ }
70
+ if (args.version) {
71
+ const pkg = require("../package.json");
72
+ console.log(pkg.version);
73
+ return;
74
+ }
75
+ if (!args.command) args.command = "init";
76
+
77
+ if (args.command !== "init") {
78
+ console.error(`Unknown command: ${args.command}`);
79
+ process.stdout.write("\n" + HELP);
80
+ process.exit(2);
81
+ }
82
+
83
+ try {
84
+ await init(args);
85
+ } catch (err) {
86
+ if (err && err.code === "USER_CANCELLED") {
87
+ console.error("\nCancelled. No file was written.");
88
+ process.exit(130);
89
+ }
90
+ console.error("\nError:", err && err.message ? err.message : err);
91
+ process.exit(1);
92
+ }
93
+ }
94
+
95
+ main();
@@ -0,0 +1,118 @@
1
+ # INCLUSION.md - Backend API / SDK (example)
2
+
3
+ > Example adaptation for a backend API or SDK.
4
+
5
+ Backend systems are not exempt from inclusion considerations. Schema choices,
6
+ error messages, rate limits, language defaults, and data models ripple
7
+ outward into every UI and integration that consumes them.
8
+
9
+ This file extends the base [`INCLUSION.md`](../../INCLUSION.md) template.
10
+
11
+ ---
12
+
13
+ ## 1. Project Context
14
+
15
+ - **Product:** [API or SDK description.]
16
+ - **Primary consumers:** Internal teams and/or external developers.
17
+ - **End-user impact:** This API mediates [user data, authentication,
18
+ payments, messaging, etc.] for downstream products. Exclusionary defaults
19
+ here become exclusionary defaults everywhere.
20
+ - **Distribution context:** Used in [N] downstream products across [regions
21
+ / locales / device classes].
22
+ - **Stakes:** Schema and validation choices made here are extremely difficult
23
+ to roll back once published.
24
+
25
+ ---
26
+
27
+ ## 2. Schema & Data Model Guidance
28
+
29
+ Generated and hand-authored schemas in this repo should:
30
+
31
+ - **Names:** A single `full_name` string field (Unicode, up to 255
32
+ characters, no validation against "first/last" splits). Optionally a
33
+ separate `display_name` or `preferred_name`. If legal name is required
34
+ separately for compliance, name the field `legal_name` and document why.
35
+ See Patrick McKenzie, _Falsehoods Programmers Believe About Names_:
36
+ <https://www.kalzumeus.com/2010/06/17/falsehoods-programmers-believe-about-names/>.
37
+ - **Email:** Validate per RFC 5321 + 5322. Do not enforce
38
+ ASCII-only or single-`@` heuristics.
39
+ - **Phone:** Store E.164. Accept any digits and the international format on
40
+ input.
41
+ - **Addresses:** Support multi-line, international addresses. Do not require
42
+ US states or 5-digit ZIPs.
43
+ - **Gender / pronouns:** Optional, free-text, plus an enumerated suggestion
44
+ list. Never required. Never used for access control.
45
+ - **Date of birth:** Stored as ISO 8601 date, no assumed minimum or maximum
46
+ year. Do not collect unless legally required.
47
+ - **Time:** Always store UTC. Always expose timezone separately. Never
48
+ assume the consumer's locale.
49
+ - **Language:** Every endpoint accepts `Accept-Language`. Every response
50
+ includes a `Content-Language`. Default to the user's stated preference,
51
+ not the server's locale.
52
+ - **Currency:** Store amounts as integer minor units (cents) + ISO 4217
53
+ currency code. Never assume USD.
54
+ - **Units:** Expose units explicitly. Never assume imperial vs. metric.
55
+
56
+ ---
57
+
58
+ ## 3. Error Messages & API Surface
59
+
60
+ API error messages are user-facing more often than designers admit.
61
+
62
+ - Error messages must be plain language and translatable. Provide a stable
63
+ `error_code` separate from the human-readable `message`.
64
+ - Never leak PII, secrets, or stack traces in error responses.
65
+ - Provide a `next_steps` or `documentation_url` field where helpful.
66
+ - Do not blame the caller for ambiguous input. "We couldn't find that
67
+ account" is more usable than "INVALID_INPUT."
68
+ - Validation errors return per-field detail so downstream UIs can render
69
+ accessible inline form errors.
70
+
71
+ ---
72
+
73
+ ## 4. Rate Limits, Quotas, and Accessibility
74
+
75
+ - Rate limits should not penalize assistive-technology-driven traffic
76
+ patterns. Screen reader users often re-request pages, refocus, and
77
+ re-trigger flows.
78
+ - Provide a `Retry-After` header and a documented exponential backoff
79
+ strategy. Do not silently drop requests.
80
+ - For paginated endpoints, expose total counts and cursors. Infinite scroll
81
+ without an end is hostile to screen reader and keyboard users.
82
+ - Do not use CAPTCHA as a primary anti-abuse strategy. Where CAPTCHA is
83
+ unavoidable, support accessible alternatives.
84
+
85
+ ---
86
+
87
+ ## 5. Authentication & Identity
88
+
89
+ - Support more than one factor of authentication, and more than one type of
90
+ second factor (TOTP, hardware key, SMS as last resort, email).
91
+ - Do not require SMS as the only second factor. SMS excludes users without
92
+ reliable cell service, users abroad, and users with shared phones.
93
+ - Support account recovery flows that do not depend on a single device or
94
+ carrier.
95
+ - Never lock users out of their own data because they have changed name,
96
+ gender marker, address, or contact info.
97
+
98
+ ---
99
+
100
+ ## 6. Logging, Analytics, and Privacy
101
+
102
+ - Log levels: never log PII at INFO or above. Audit before adding fields.
103
+ - Do not log message bodies, names, addresses, or identifiers in plaintext
104
+ to shared aggregators.
105
+ - Telemetry: opt-in for end-user-identifying telemetry. Opt-out is not
106
+ consent.
107
+ - Anonymization is hard. Aggregate where possible. Hash with care.
108
+
109
+ ---
110
+
111
+ ## 7. SDK Documentation
112
+
113
+ - Code examples in SDK docs use realistic, internationally varied sample
114
+ data - not "John Doe, 123 Main St, USA."
115
+ - Document defaults explicitly so consumers can override them per locale.
116
+ - Document accessibility implications of API choices (e.g. "this endpoint
117
+ returns infinite-scroll cursors; consumers must provide an end-of-results
118
+ cue for assistive tech").
@@ -0,0 +1,92 @@
1
+ # INCLUSION.md - Design System (example)
2
+
3
+ > Example adaptation for a component library or design system.
4
+
5
+ This file extends the base [`INCLUSION.md`](../../INCLUSION.md) template with
6
+ guidance specific to a design system whose components are reused across many
7
+ products.
8
+
9
+ Design systems sit upstream of every product surface they touch. Their
10
+ inclusion defaults become every downstream team's path of least resistance.
11
+ That makes their `INCLUSION.md` unusually high-leverage.
12
+
13
+ ---
14
+
15
+ ## 1. Project Context
16
+
17
+ - **Product:** [Design system name.] Provides foundational components,
18
+ tokens, and patterns to [N] downstream product teams.
19
+ - **Primary users:** Product engineers and designers consuming components.
20
+ - **End users:** Every user of every product that consumes this system. We
21
+ do not get to assume who they are.
22
+ - **Known underserved users:** [Document known gaps. Examples: "Right-to-left
23
+ layouts are partial. Switch-control support is unverified. Component-level
24
+ text scaling above 200% breaks several layouts."]
25
+ - **Distribution context:** Used in [N] products spanning [web / iOS /
26
+ Android / desktop]. Localized to [N] languages including RTL scripts.
27
+ - **Stakes:** A regression here is a regression in every downstream product.
28
+
29
+ ---
30
+
31
+ ## 2. Component Authoring Requirements
32
+
33
+ Generated or hand-authored components in this repo must:
34
+
35
+ - Have a documented accessible name, role, and state model.
36
+ - Be keyboard operable per the ARIA Authoring Practices Guide pattern for
37
+ that component type. Cite the pattern in the component's README.
38
+ - Manage focus correctly: focus trap in modals, focus restoration on close,
39
+ visible focus indicator on every interactive element meeting WCAG 2.4.11.
40
+ - Pass automated checks (axe, Pa11y, or equivalent) at the component level.
41
+ - Have at least one screen-reader test plan documented in the component's
42
+ README.
43
+ - Support `prefers-reduced-motion` for any animation.
44
+ - Support `forced-colors: active` and high-contrast modes.
45
+ - Use design tokens for color, spacing, type, and motion - never hardcoded
46
+ values - so themes, contrast modes, and density preferences can apply.
47
+ - Support RTL layouts. Use logical CSS properties (`margin-inline-start`,
48
+ `padding-block-end`, etc.) instead of physical ones (`margin-left`).
49
+ - Support variable text length: components must not break when localized
50
+ strings are 30-40% longer than English defaults.
51
+ - Not assume a single language direction, locale, or numeric system.
52
+
53
+ ---
54
+
55
+ ## 3. Token Design
56
+
57
+ - Color tokens are paired with intent (text, surface, border, focus, danger,
58
+ success) and contrast role - not raw hex values exposed downstream.
59
+ - Every text-on-surface token pair has documented contrast ratios meeting
60
+ WCAG 2.2 AA at minimum.
61
+ - Provide a high-contrast token theme.
62
+ - Provide a token theme for `prefers-reduced-transparency`.
63
+ - Motion tokens have a `none` variant that resolves when
64
+ `prefers-reduced-motion: reduce` is set.
65
+ - Density tokens (compact, default, spacious) preserve a 24x24 minimum touch
66
+ target even at the most compact setting.
67
+
68
+ ---
69
+
70
+ ## 4. Documentation & Examples
71
+
72
+ - Every component README includes: accessible name model, keyboard
73
+ interactions, screen-reader behavior, RTL behavior, localization notes,
74
+ known limitations.
75
+ - Examples in documentation use realistic, diverse user names, addresses,
76
+ and content. Avoid placeholder names like "John Doe" or "Jane Smith" by
77
+ default. Use a curated set of realistic, internationally varied placeholder
78
+ data.
79
+ - Example imagery includes a representative range of skin tones, body types,
80
+ ages, abilities, and contexts. No tokenized "diversity stock photo" set.
81
+ - Do not document accessibility features as "bonus." They are baseline.
82
+
83
+ ---
84
+
85
+ ## 5. Deprecation & Migration
86
+
87
+ - When deprecating a component or token, provide an automated migration path
88
+ (codemod) where feasible, and a documented manual path otherwise.
89
+ - Never silently change accessibility behavior. Breaking changes to focus,
90
+ ARIA, or keyboard interaction must be flagged in release notes.
91
+ - Provide at least one major version of overlap before removing a deprecated
92
+ inclusive feature.
@@ -0,0 +1,100 @@
1
+ # INCLUSION.md - Frontend Web App (example)
2
+
3
+ > Example adaptation for a consumer-facing frontend web application.
4
+ > Replace bracketed placeholders with values for your project.
5
+
6
+ This file extends the base [`INCLUSION.md`](../../INCLUSION.md) template with
7
+ guidance specific to a public, consumer-facing web app.
8
+
9
+ ---
10
+
11
+ ## 1. Project Context
12
+
13
+ - **Product:** [Short description of the web app and what users come here to do.]
14
+ - **Primary users:** [e.g. "US-based adults managing personal finances."]
15
+ - **Known underserved users:** [e.g. "Screen reader users on the dashboard;
16
+ Spanish-language users in onboarding; users on Android Go devices."]
17
+ - **Distribution context:** Web. Modern evergreen browsers + last 2 versions
18
+ of Safari/Firefox. Mobile web is [N]% of traffic. Top non-English language
19
+ in analytics is [language]. Assistive tech in our analytics includes
20
+ [VoiceOver, NVDA, JAWS, TalkBack, Dragon, Switch Control].
21
+ - **Stakes:** [e.g. "Users who cannot complete onboarding lose access to
22
+ [service]. This is not a low-stakes flow."]
23
+
24
+ ---
25
+
26
+ ## 2. Frontend-Specific Engineering Guidance
27
+
28
+ In addition to the base `INCLUSION.md` Section 8, generated frontend code in
29
+ this repo should:
30
+
31
+ - Use semantic HTML elements (`<button>`, `<a>`, `<nav>`, `<main>`,
32
+ `<section>`, `<label>`, `<input>`, `<dialog>` where supported). Never
33
+ `div` + `onClick` for interactive controls.
34
+ - Manage focus on route changes, modal open/close, and async state
35
+ transitions.
36
+ - Pair every icon-only button with an `aria-label` or visually hidden text.
37
+ - Use `aria-live` regions for important async updates (form errors, toast
38
+ notifications, search results) and verify them with a screen reader before
39
+ merging.
40
+ - Never rely on hover, color, or position alone to convey state.
41
+ - Support `prefers-reduced-motion` for all transitions and animations.
42
+ - Default to system color scheme. Provide a manual override.
43
+ - Lazy-load images and below-the-fold media. Always provide meaningful `alt`
44
+ text. Decorative imagery uses `alt=""`.
45
+ - Render text content server-side wherever possible to support assistive tech
46
+ and slow networks.
47
+ - Test interactive components with keyboard alone before merging.
48
+
49
+ ---
50
+
51
+ ## 3. Copy & Microcopy
52
+
53
+ - Default reading level: US grade 7. Consent, onboarding, and error states:
54
+ grade 6.
55
+ - Error messages should: (1) name the problem in plain language, (2) tell the
56
+ user what to do next, (3) preserve their input. Never blame the user.
57
+ - Empty states should explain what the user can do, not just that there is
58
+ nothing here.
59
+ - Avoid jokes, idioms, and US-cultural references in core flows. They do not
60
+ localize.
61
+
62
+ ---
63
+
64
+ ## 4. Forms
65
+
66
+ Forms are where most exclusion happens.
67
+
68
+ - Every input has a visible, persistent `<label>`. Placeholder text is not a
69
+ label.
70
+ - Required fields are explicitly marked. Optional fields are explicitly
71
+ marked.
72
+ - Error messages are associated with their inputs via `aria-describedby`.
73
+ - Names: do not require a "first name" + "last name" split. Use a single
74
+ full-name field. Allow Unicode, spaces, hyphens, apostrophes, and length up
75
+ to at least 70 characters.
76
+ - Addresses: support international formats. Do not assume US ZIP, US states,
77
+ or a single-line city/state/zip.
78
+ - Phone numbers: store E.164. Support country codes.
79
+ - Gender: if you must ask, provide a free-text option and "prefer not to
80
+ say." Do not require it for transactional flows.
81
+ - Date of birth: do not require it unless legally necessary. Support
82
+ full-range, not just current decade.
83
+ - Never auto-advance focus between inputs unless the user is filling a
84
+ segmented code field.
85
+
86
+ ---
87
+
88
+ ## 5. Personalization & AI Features
89
+
90
+ If this app uses AI features (recommendations, summarization, generative
91
+ content, chat):
92
+
93
+ - Inform users when content is AI-generated.
94
+ - Provide a clear opt-out for AI personalization.
95
+ - Do not use AI features to gate access to core functionality.
96
+ - Surface confidence and uncertainty. Do not present probabilistic output as
97
+ fact.
98
+ - Do not collect biometric, voice, or facial data without explicit,
99
+ separately-toggled consent.
100
+ - Preserve user agency: users can edit, reject, or override any AI output.
package/lib/colors.js ADDED
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+
3
+ const supportsColor =
4
+ process.stdout.isTTY &&
5
+ process.env.TERM !== "dumb" &&
6
+ !("NO_COLOR" in process.env);
7
+
8
+ let enabled = supportsColor;
9
+
10
+ function set(on) {
11
+ enabled = on && supportsColor;
12
+ }
13
+
14
+ function wrap(open, close) {
15
+ return (s) => (enabled ? `\x1b[${open}m${s}\x1b[${close}m` : String(s));
16
+ }
17
+
18
+ module.exports = {
19
+ set,
20
+ bold: wrap(1, 22),
21
+ dim: wrap(2, 22),
22
+ italic: wrap(3, 23),
23
+ cyan: wrap(36, 39),
24
+ magenta: wrap(35, 39),
25
+ green: wrap(32, 39),
26
+ yellow: wrap(33, 39),
27
+ red: wrap(31, 39),
28
+ gray: wrap(90, 39),
29
+ };
package/lib/init.js ADDED
@@ -0,0 +1,210 @@
1
+ "use strict";
2
+
3
+ const fs = require("fs");
4
+ const path = require("path");
5
+
6
+ const c = require("./colors");
7
+ const { createPrompter } = require("./prompt");
8
+ const { loadTemplate, renderGeneric, VARIANTS } = require("./template");
9
+
10
+ const BANNER = `
11
+ ${c.magenta(c.bold("INCLUSION.md"))} ${c.dim("scaffolder")}
12
+ ${c.dim("An LLM/agent context convention for model biases - https://github.com/BranonConor/inclusion.md")}
13
+ `;
14
+
15
+ async function init(args) {
16
+ c.set(args.color);
17
+
18
+ process.stdout.write(BANNER + "\n");
19
+ process.stdout.write(
20
+ c.dim(
21
+ "I'll ask a few questions about your project, then write a customized\n" +
22
+ "INCLUSION.md to disk. Press Ctrl+C any time to bail.\n"
23
+ ) + "\n"
24
+ );
25
+
26
+ const p = createPrompter({ acceptDefaults: !!args.yes });
27
+
28
+ try {
29
+ // --- variant ----------------------------------------------------------
30
+ let variant = args.variant;
31
+ if (!variant) {
32
+ variant = await p.choice(
33
+ "Which template do you want to start from?",
34
+ [
35
+ { value: "generic", label: "Generic", hint: "Default. Good for most repos." },
36
+ {
37
+ value: "frontend-app",
38
+ label: "Frontend web app",
39
+ hint: "Forms, microcopy, AI features.",
40
+ },
41
+ {
42
+ value: "design-system",
43
+ label: "Design system / component library",
44
+ hint: "Tokens, components, RTL, density.",
45
+ },
46
+ {
47
+ value: "backend-api",
48
+ label: "Backend API / SDK",
49
+ hint: "Schema, errors, auth, telemetry.",
50
+ },
51
+ ],
52
+ { default: 0 }
53
+ );
54
+ }
55
+ if (!VARIANTS[variant]) {
56
+ throw new Error(
57
+ `Unknown variant "${variant}". Choose one of: ${Object.keys(VARIANTS).join(", ")}.`
58
+ );
59
+ }
60
+
61
+ process.stdout.write("\n" + c.bold("Project context") + "\n");
62
+ process.stdout.write(
63
+ c.dim(
64
+ "These answers fill in Section 1. Be specific - generic answers make for generic guidance.\n"
65
+ ) + "\n"
66
+ );
67
+
68
+ const product = await p.text("What does this project do?", {
69
+ default: "A short description of what this software does.",
70
+ });
71
+ const primaryUsers = await p.text("Who have you intentionally designed for?", {
72
+ default: "Describe your primary users.",
73
+ });
74
+ const underservedUsers = await p.text(
75
+ "Who do you know you haven't designed for well yet?",
76
+ {
77
+ default:
78
+ "Document a real gap here (e.g. screen reader users on the dashboard; Spanish-language onboarding).",
79
+ }
80
+ );
81
+ const distribution = await p.text(
82
+ "Where is this software used? (geography, devices, network, assistive tech)",
83
+ {
84
+ default:
85
+ "Web/iOS/Android, primary regions, languages, common assistive technologies.",
86
+ }
87
+ );
88
+ const stakes = await p.text(
89
+ "What happens when this software excludes someone?",
90
+ {
91
+ default:
92
+ "Describe the real-world cost of exclusion (loss of access to healthcare, employment, civic services, etc.).",
93
+ }
94
+ );
95
+
96
+ process.stdout.write("\n" + c.bold("Maintenance") + "\n\n");
97
+
98
+ const owner = await p.text("Who owns this file? (person or team)", {
99
+ default: "An accountable person or team",
100
+ });
101
+ const cadence = await p.choice(
102
+ "How often will this file be reviewed?",
103
+ [
104
+ { value: "Monthly.", label: "Monthly" },
105
+ { value: "Quarterly.", label: "Quarterly", hint: "Recommended." },
106
+ { value: "Twice a year.", label: "Twice a year" },
107
+ { value: "Annually (minimum).", label: "Annually (minimum)" },
108
+ ],
109
+ { default: 1 }
110
+ );
111
+ const feedback = await p.text(
112
+ "How can users and contributors report exclusionary patterns?",
113
+ {
114
+ default:
115
+ "Open an issue on this repository, or contact the owner directly.",
116
+ }
117
+ );
118
+
119
+ // --- output path ------------------------------------------------------
120
+ const outPath = path.resolve(
121
+ process.cwd(),
122
+ args.out || "INCLUSION.md"
123
+ );
124
+
125
+ if (fs.existsSync(outPath) && !args.force) {
126
+ const overwrite = await p.confirm(
127
+ `${path.relative(process.cwd(), outPath) || outPath} already exists. Overwrite?`,
128
+ { default: false }
129
+ );
130
+ if (!overwrite) {
131
+ process.stdout.write(
132
+ c.yellow("\nAborted. ") + "Existing file left untouched.\n"
133
+ );
134
+ p.close();
135
+ return;
136
+ }
137
+ }
138
+
139
+ // --- render -----------------------------------------------------------
140
+ const template = loadTemplate(variant);
141
+ const answers = {
142
+ product,
143
+ primaryUsers,
144
+ underservedUsers,
145
+ distribution,
146
+ stakes,
147
+ owner,
148
+ cadence,
149
+ feedback,
150
+ };
151
+
152
+ let rendered;
153
+ if (variant === "generic") {
154
+ rendered = renderGeneric(template, answers);
155
+ } else {
156
+ rendered = prependPersonalizedContext(template, answers);
157
+ }
158
+
159
+ fs.mkdirSync(path.dirname(outPath), { recursive: true });
160
+ fs.writeFileSync(outPath, rendered, "utf8");
161
+
162
+ p.close();
163
+
164
+ const rel = path.relative(process.cwd(), outPath) || outPath;
165
+ process.stdout.write(
166
+ "\n" +
167
+ c.green("✓ ") +
168
+ c.bold(`Wrote ${rel}`) +
169
+ c.dim(` (${formatBytes(Buffer.byteLength(rendered))})`) +
170
+ "\n\n"
171
+ );
172
+
173
+ printNextSteps(rel);
174
+ } finally {
175
+ p.close();
176
+ }
177
+ }
178
+
179
+ function prependPersonalizedContext(template, answers) {
180
+ // For non-generic variants, the canonical template already has its own
181
+ // Section 1. We replace it with the user's answers.
182
+ return renderGeneric(template, answers);
183
+ }
184
+
185
+ function formatBytes(n) {
186
+ if (n < 1024) return `${n} B`;
187
+ if (n < 1024 * 1024) return `${(n / 1024).toFixed(1)} KB`;
188
+ return `${(n / 1024 / 1024).toFixed(2)} MB`;
189
+ }
190
+
191
+ function printNextSteps(file) {
192
+ process.stdout.write(
193
+ c.bold("Next steps") +
194
+ "\n\n" +
195
+ ` 1. Open ${c.cyan(file)} and read through it once.\n` +
196
+ ` 2. Point your AI assistant at it:\n\n` +
197
+ ` ${c.dim("# GitHub Copilot - .github/copilot-instructions.md")}\n` +
198
+ ` Follow the inclusion guidance in /INCLUSION.md.\n\n` +
199
+ ` ${c.dim("# Cursor - .cursor/rules/inclusion.md")}\n` +
200
+ ` Always read and follow /INCLUSION.md.\n\n` +
201
+ ` ${c.dim("# Claude Code - CLAUDE.md")}\n` +
202
+ ` Read /INCLUSION.md and apply its review prompts.\n\n` +
203
+ ` 3. Commit it. Treat it like the rest of your engineering docs.\n\n` +
204
+ c.dim(
205
+ "Companion essay: https://branon.dev/blog/posts/the-need-for-inclusion-md\n"
206
+ )
207
+ );
208
+ }
209
+
210
+ module.exports = { init };