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.
- package/INCLUSION.md +421 -0
- package/LICENSE +21 -0
- package/README.md +228 -0
- package/bin/inclusion-md.js +95 -0
- package/examples/backend-api/INCLUSION.md +118 -0
- package/examples/design-system/INCLUSION.md +92 -0
- package/examples/frontend-app/INCLUSION.md +100 -0
- package/lib/colors.js +29 -0
- package/lib/init.js +210 -0
- package/lib/prompt.js +111 -0
- package/lib/template.js +70 -0
- package/package.json +46 -0
|
@@ -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 };
|