@erclx/canon 5.39.0 → 5.41.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 +39 -22
- package/claude/.claude-plugin/plugin.json +1 -1
- package/claude/skills/auto-ship/REQUIREMENT.md +1 -0
- package/claude/skills/auto-ship/SKILL.md +6 -0
- package/claude/skills/auto-ship/references/fan-in.md +59 -0
- package/claude/skills/canvas-board/SKILL.md +1 -1
- package/claude/skills/create-skill/references/skill.md +1 -1
- package/claude/skills/decision-escalate/REQUIREMENT.md +1 -1
- package/claude/skills/decision-escalate/SKILL.md +1 -2
- package/claude/skills/draft-slides/SKILL.md +2 -3
- package/claude/skills/draft-slides/references/deck.md +183 -0
- package/claude/skills/git-branch/REQUIREMENT.md +17 -6
- package/claude/skills/git-branch/SKILL.md +63 -7
- package/claude/skills/git-pr/references/evidence.md +2 -2
- package/claude/skills/git-pr/references/final-command.md +1 -1
- package/claude/skills/git-pr/references/labels.md +1 -1
- package/claude/skills/git-pr/references/publish.md +1 -1
- package/claude/skills/plan-feature/references/batches.md +48 -1
- package/claude/skills/plan-feature/references/plan.md +2 -0
- package/claude/skills/plan-intake-answer/REQUIREMENT.md +1 -1
- package/claude/skills/plan-intake-answer/SKILL.md +1 -2
- package/claude/skills/review-address/references/rebase-conflicts.md +7 -0
- package/claude/skills/review-ui/SKILL.md +3 -0
- package/claude/skills/review-ui/references/close-out.md +47 -0
- package/claude/skills/review-ui/references/post.md +3 -1
- package/claude/skills/role-orchestrator/references/orchestrator-dispatch.md +1 -0
- package/claude/skills/role-orchestrator/references/orchestrator-handback.md +1 -1
- package/claude/skills/role-worker/REQUIREMENT.md +1 -0
- package/claude/skills/role-worker/SKILL.md +9 -0
- package/claude/skills/task-board/references/board.md +1 -1
- package/claude/skills/task-board/references/tasks.md +1 -1
- package/claude/skills/teach-workspace/REQUIREMENT.md +1 -1
- package/claude/skills/teach-workspace/SKILL.md +1 -2
- package/claude/skills/ux-measure/SKILL.md +1 -1
- package/docs/agents/audits.md +61 -60
- package/docs/agents/context-audit.md +40 -58
- package/docs/agents/index.md +5 -51
- package/docs/agents/install-and-sync.md +1 -0
- package/docs/agents/markdown-audit.md +53 -99
- package/docs/agents/records.md +37 -79
- package/docs/agents/sessions-transfer.md +26 -70
- package/docs/target/index.md +0 -1
- package/docs/target/migrations.md +57 -66
- package/docs/target/projects.md +36 -78
- package/docs/target/scenarios.md +9 -19
- package/docs/target/sync.md +27 -68
- package/docs/workflow/ai-workflow.md +22 -68
- package/docs/workflow/operating-model.md +32 -186
- package/docs/workflow/parallel-features.md +30 -66
- package/docs/workflow/skill-map-on-request.md +22 -24
- package/docs/workflow/skill-map.md +72 -76
- package/docs/workflow/visual-design-workflow.md +25 -118
- package/docs/workflow/zshrc-aliases.md +18 -37
- package/package.json +1 -1
- package/src/canvas/client/inspector/fields.css +4 -0
- package/src/canvas/index.html +6 -0
- package/src/claude/cases/git.ts +4 -0
- package/src/claude/skills-reach.ts +2 -2
- package/src/cli/ui.ts +3 -1
- package/src/commands/canvas.ts +23 -0
- package/src/commands/capture.ts +14 -0
- package/src/commands/claude.ts +13 -0
- package/src/commands/demo.ts +22 -0
- package/src/commands/deps.ts +6 -0
- package/src/commands/driver.ts +28 -2
- package/src/commands/gov.ts +52 -18
- package/src/commands/intake.ts +17 -0
- package/src/commands/labels.ts +38 -5
- package/src/commands/pr.ts +192 -52
- package/src/commands/records.ts +19 -3
- package/src/commands/secrets.ts +6 -0
- package/src/commands/sessions.ts +8 -0
- package/src/commands/tasks.ts +52 -0
- package/src/commands/teach.ts +13 -0
- package/src/commands/upstream.ts +25 -1
- package/src/commands/worktrees.ts +5 -0
- package/src/context/audit.ts +22 -4
- package/src/context/folders.ts +1 -1
- package/src/context/shape-gated.ts +5 -2
- package/src/counts/scan.ts +1 -1
- package/src/design/components.ts +1 -1
- package/src/design/document.ts +1 -1
- package/src/design/tokens.ts +1 -1
- package/src/gate/boundaries.ts +4 -1
- package/src/gate/measures.ts +1 -1
- package/src/gate/stages.ts +7 -7
- package/src/git/env.ts +6 -0
- package/src/markdown/scope.ts +11 -19
- package/src/pr/frames.ts +53 -9
- package/src/pr/lift.ts +153 -19
- package/src/pr/painted.ts +87 -0
- package/src/records/validate.ts +1 -0
- package/src/roots/record.ts +18 -1
- package/src/sandbox/census.ts +1 -1
- package/src/sandbox/headless/run.ts +3 -2
- package/src/sandbox/headless/watch.ts +3 -4
- package/src/slides/convert/master.ts +3 -0
- package/src/sync/format.ts +46 -0
- package/src/sync/stamp.ts +6 -0
- package/src/tasks/answers.ts +79 -2
- package/src/tasks/fan-in.ts +176 -0
- package/src/tasks/stack.ts +1 -1
- package/tooling/astro/reference.md +80 -39
- package/tooling/base/reference.md +63 -32
- package/tooling/base/seeds/canon/context/ci.md +7 -15
- package/tooling/base/seeds/canon/context/development.md +14 -14
- package/tooling/claude/reference.md +40 -22
- package/tooling/cloudflare/reference.md +31 -12
- package/tooling/go/reference.md +47 -23
- package/tooling/nextjs/reference.md +52 -19
- package/tooling/php/reference.md +35 -16
- package/tooling/python/reference.md +47 -30
- package/tooling/shell/reference.md +6 -5
- package/tooling/vite-react/reference.md +49 -21
- package/tooling/web/reference.md +131 -71
- package/docs/agents/canvas.md +0 -86
- package/docs/agents/capture.md +0 -65
- package/docs/agents/census.md +0 -23
- package/docs/agents/comments.md +0 -34
- package/docs/agents/context-audit-checks.md +0 -108
- package/docs/agents/context-classify.md +0 -101
- package/docs/agents/counts.md +0 -74
- package/docs/agents/demo.md +0 -140
- package/docs/agents/driver.md +0 -103
- package/docs/agents/gate.md +0 -90
- package/docs/agents/hooks.md +0 -78
- package/docs/agents/intake.md +0 -81
- package/docs/agents/key-changes.md +0 -116
- package/docs/agents/label-coverage.md +0 -86
- package/docs/agents/markdown-audit-density.md +0 -39
- package/docs/agents/markdown-audit-weight.md +0 -83
- package/docs/agents/pr-evidence-layout.md +0 -70
- package/docs/agents/pr-evidence.md +0 -273
- package/docs/agents/pr-frames.md +0 -131
- package/docs/agents/pr-lift.md +0 -132
- package/docs/agents/pr-local.md +0 -111
- package/docs/agents/pr-preview.md +0 -155
- package/docs/agents/pr-reads.md +0 -111
- package/docs/agents/pr-tick.md +0 -72
- package/docs/agents/records-push.md +0 -61
- package/docs/agents/records-stale.md +0 -40
- package/docs/agents/records-validate.md +0 -57
- package/docs/agents/restated.md +0 -83
- package/docs/agents/routing.md +0 -61
- package/docs/agents/rule-citations.md +0 -90
- package/docs/agents/sandbox.md +0 -126
- package/docs/agents/sessions.md +0 -128
- package/docs/agents/skills-audit.md +0 -120
- package/docs/agents/skills-reach.md +0 -64
- package/docs/agents/slides.md +0 -176
- package/docs/agents/state-scoped-risk.md +0 -105
- package/docs/agents/superseded.md +0 -85
- package/docs/agents/tasks-archive.md +0 -83
- package/docs/agents/tasks-plans.md +0 -152
- package/docs/agents/tasks-validate.md +0 -133
- package/docs/agents/tasks.md +0 -123
- package/docs/agents/teach-authoring.md +0 -118
- package/docs/agents/teach.md +0 -157
- package/docs/agents/test-order.md +0 -58
- package/docs/agents/upstream.md +0 -94
- package/docs/agents/worktrees.md +0 -118
- package/docs/target/standards-moved.md +0 -34
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import type { Finding } from '@/records/validate'
|
|
2
|
+
import { PLAN_BRANCH_TYPE } from '@/tasks/branch'
|
|
3
|
+
import { constraintBullets, type StackPlan } from '@/tasks/stack'
|
|
4
|
+
|
|
5
|
+
const PLAN_PREFIX = 'feature-'
|
|
6
|
+
|
|
7
|
+
const MEMBER_OF = /\bfan[- ]in member of\s+/gi
|
|
8
|
+
|
|
9
|
+
const FANS_IN = /\bfans in\s+/gi
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* A backticked slug may drop the `feature-` prefix, and a bare one must carry
|
|
13
|
+
* it. Without the prefix a bare word is indistinguishable from the prose that
|
|
14
|
+
* follows the list, so `Fans in feature-a and the rest` would read `the` as a
|
|
15
|
+
* member.
|
|
16
|
+
*/
|
|
17
|
+
const TICKED_SLUG = /^`((?:feature-)?[a-z0-9][a-z0-9-]*)`/i
|
|
18
|
+
const BARE_SLUG = /^(feature-[a-z0-9][a-z0-9-]*)/i
|
|
19
|
+
|
|
20
|
+
const LIST_SEPARATOR = /^\s*(?:,\s*(?:and\s+)?|and\s+)/i
|
|
21
|
+
|
|
22
|
+
function stemOf(slug: string): string {
|
|
23
|
+
const lower = slug.toLowerCase()
|
|
24
|
+
|
|
25
|
+
return lower.startsWith(PLAN_PREFIX) ? lower : `${PLAN_PREFIX}${lower}`
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function leadingSlug(text: string): { stem: string; rest: string } | null {
|
|
29
|
+
const match = TICKED_SLUG.exec(text) ?? BARE_SLUG.exec(text)
|
|
30
|
+
const slug = match?.[1]
|
|
31
|
+
if (match === null || slug === undefined) return null
|
|
32
|
+
|
|
33
|
+
return { stem: stemOf(slug), rest: text.slice(match[0].length) }
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Every slug in the list standing after each match of `phrase`, in order. */
|
|
37
|
+
function readList(text: string, phrase: RegExp, many: boolean): string[] {
|
|
38
|
+
const stems: string[] = []
|
|
39
|
+
|
|
40
|
+
for (const bullet of constraintBullets(text)) {
|
|
41
|
+
for (const match of bullet.matchAll(phrase)) {
|
|
42
|
+
let rest = bullet.slice((match.index ?? 0) + match[0].length)
|
|
43
|
+
|
|
44
|
+
for (;;) {
|
|
45
|
+
const slug = leadingSlug(rest)
|
|
46
|
+
if (slug === null) break
|
|
47
|
+
stems.push(slug.stem)
|
|
48
|
+
if (!many) break
|
|
49
|
+
|
|
50
|
+
const separator = LIST_SEPARATOR.exec(slug.rest)
|
|
51
|
+
if (separator === null) break
|
|
52
|
+
rest = slug.rest.slice(separator[0].length)
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return [...new Set(stems)]
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** The integrators a member plan names, which a conforming member holds one of. */
|
|
61
|
+
export function readFanInIntegrators(text: string): string[] {
|
|
62
|
+
return readList(text, MEMBER_OF, false)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** The members an integrator plan fans in, in the order its merges run. */
|
|
66
|
+
export function readFanInMembers(text: string): string[] {
|
|
67
|
+
return readList(text, FANS_IN, true)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* The branch a member builds on, derived the way `planBranch` derives it, since
|
|
72
|
+
* the integrator merges and the dispatch gate reads the branch the worker took.
|
|
73
|
+
*/
|
|
74
|
+
export function fanInBranch(stem: string): string {
|
|
75
|
+
const slug = stem.startsWith(PLAN_PREFIX)
|
|
76
|
+
? stem.slice(PLAN_PREFIX.length)
|
|
77
|
+
: stem
|
|
78
|
+
|
|
79
|
+
return `${PLAN_BRANCH_TYPE}/${slug}`
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function unmatched(stem: string, subject: string, message: string): Finding {
|
|
83
|
+
return { kind: 'fan-in-unmatched', record: `${stem}.md`, subject, message }
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Every place a fan-in set's two sides disagree across the live plans.
|
|
88
|
+
*
|
|
89
|
+
* An absent plan is reported rather than skipped, unlike `stackFindings`. The
|
|
90
|
+
* caller hands over the live folder only, so a member archived after it merged
|
|
91
|
+
* reads as absent, and an integrator still listing it would otherwise take its
|
|
92
|
+
* leftover branch on origin as a member that finished.
|
|
93
|
+
*
|
|
94
|
+
* A member two integrators list is reported on each of them rather than paired
|
|
95
|
+
* with whichever sorts first, since either merge would carry it.
|
|
96
|
+
*/
|
|
97
|
+
export function fanInFindings(plans: readonly StackPlan[]): Finding[] {
|
|
98
|
+
const integratorsOf = new Map(
|
|
99
|
+
plans.map((plan) => [plan.stem, readFanInIntegrators(plan.text)]),
|
|
100
|
+
)
|
|
101
|
+
const membersOf = new Map(
|
|
102
|
+
plans.map((plan) => [plan.stem, readFanInMembers(plan.text)]),
|
|
103
|
+
)
|
|
104
|
+
const listers = new Map<string, string[]>()
|
|
105
|
+
for (const [integrator, members] of membersOf) {
|
|
106
|
+
for (const member of members) {
|
|
107
|
+
listers.set(member, [...(listers.get(member) ?? []), integrator])
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const findings: Finding[] = []
|
|
112
|
+
|
|
113
|
+
for (const { stem } of plans) {
|
|
114
|
+
const integrators = integratorsOf.get(stem) ?? []
|
|
115
|
+
|
|
116
|
+
for (const integrator of integrators) {
|
|
117
|
+
if (integrators.length > 1) {
|
|
118
|
+
findings.push(
|
|
119
|
+
unmatched(
|
|
120
|
+
stem,
|
|
121
|
+
integrator,
|
|
122
|
+
`is a fan-in member of ${integrators.map((name) => `\`${name}\``).join(' and ')}. A member fans in to one integrator.`,
|
|
123
|
+
),
|
|
124
|
+
)
|
|
125
|
+
} else if (!membersOf.has(integrator)) {
|
|
126
|
+
findings.push(
|
|
127
|
+
unmatched(
|
|
128
|
+
stem,
|
|
129
|
+
integrator,
|
|
130
|
+
`is a fan-in member of \`${integrator}\`, which is not a live plan. Write the integrator's plan, or drop the member bullet.`,
|
|
131
|
+
),
|
|
132
|
+
)
|
|
133
|
+
} else if (!(membersOf.get(integrator) ?? []).includes(stem)) {
|
|
134
|
+
findings.push(
|
|
135
|
+
unmatched(
|
|
136
|
+
stem,
|
|
137
|
+
integrator,
|
|
138
|
+
`is a fan-in member of \`${integrator}\`, whose \`Fans in\` bullet does not list it.`,
|
|
139
|
+
),
|
|
140
|
+
)
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
for (const member of membersOf.get(stem) ?? []) {
|
|
145
|
+
const others = (listers.get(member) ?? []).filter((name) => name !== stem)
|
|
146
|
+
|
|
147
|
+
if (others.length > 0) {
|
|
148
|
+
findings.push(
|
|
149
|
+
unmatched(
|
|
150
|
+
stem,
|
|
151
|
+
member,
|
|
152
|
+
`fans in \`${member}\`, which ${others.map((name) => `\`${name}\``).join(' and ')} also fans in. A member fans in to one integrator.`,
|
|
153
|
+
),
|
|
154
|
+
)
|
|
155
|
+
} else if (!integratorsOf.has(member)) {
|
|
156
|
+
findings.push(
|
|
157
|
+
unmatched(
|
|
158
|
+
stem,
|
|
159
|
+
member,
|
|
160
|
+
`fans in \`${member}\`, which is not a live plan. An archived member is not a finished one, so drop it from the list or restore its plan.`,
|
|
161
|
+
),
|
|
162
|
+
)
|
|
163
|
+
} else if (!(integratorsOf.get(member) ?? []).includes(stem)) {
|
|
164
|
+
findings.push(
|
|
165
|
+
unmatched(
|
|
166
|
+
stem,
|
|
167
|
+
member,
|
|
168
|
+
`fans in \`${member}\`, which does not name it as its integrator.`,
|
|
169
|
+
),
|
|
170
|
+
)
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return findings
|
|
176
|
+
}
|
package/src/tasks/stack.ts
CHANGED
|
@@ -41,7 +41,7 @@ export interface StackPair {
|
|
|
41
41
|
* One string per `**Constraints:**` bullet, with a wrapped continuation line
|
|
42
42
|
* joined to the bullet it belongs to.
|
|
43
43
|
*/
|
|
44
|
-
function constraintBullets(text: string): string[] {
|
|
44
|
+
export function constraintBullets(text: string): string[] {
|
|
45
45
|
const bullets: string[] = []
|
|
46
46
|
|
|
47
47
|
for (const line of splitPlanSections(text).get('Constraints') ?? []) {
|
|
@@ -4,25 +4,49 @@
|
|
|
4
4
|
|
|
5
5
|
## Overview
|
|
6
6
|
|
|
7
|
-
The astro stack covers Astro + TypeScript projects
|
|
7
|
+
The astro stack covers Astro + TypeScript projects, being content sites, marketing sites, blogs, and docs. Interactive islands use React only and output is static.
|
|
8
|
+
|
|
9
|
+
It ships golden configs for `astro.config.mjs`, `vitest.config.ts`, `playwright.config.ts` (preview-server backed), and a `tsconfig.json` extending `astro/tsconfigs/strict`. An `eslint.config.js` keeps ESLint for this stack alone, and a `.prettierrc` formats `.astro` files alone.
|
|
8
10
|
|
|
9
11
|
## Scaffold checklist
|
|
10
12
|
|
|
11
13
|
1. Scaffold with `bunx create-astro@latest`. Choose `TypeScript: Strict`. Skip git init and install.
|
|
12
|
-
2. Add React
|
|
13
|
-
3. Install web tooling
|
|
14
|
-
4. Install astro adapter
|
|
14
|
+
2. Add React with `bunx astro add react`. Never use `astro add tailwind`, which installs the v3 integration. Tailwind v4 arrives via the web manifest.
|
|
15
|
+
3. Install web tooling with `canon tooling sync web . --write`.
|
|
16
|
+
4. Install the astro adapter with `canon tooling sync astro . --write`.
|
|
15
17
|
5. Extend the `ci` and `development` context entries under `canon/context/` per the web reference's extend sections plus the astro rows below.
|
|
16
18
|
6. Run `bun run lint:fix`, then `bun run format`, then `bun run check`.
|
|
17
19
|
|
|
18
20
|
## What ships as golden configs
|
|
19
21
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
- `
|
|
25
|
-
-
|
|
22
|
+
### Astro and test configs
|
|
23
|
+
|
|
24
|
+
The configs below wire Astro into the web layer's test setup.
|
|
25
|
+
|
|
26
|
+
- `astro.config.mjs` adds `@astrojs/react`, `@tailwindcss/vite` in `vite.plugins`, the `@/` alias via `vite.resolve.alias`, and `ASTRO_SITE` for the `site` field.
|
|
27
|
+
- It serves port `4321` plus `WORKTREE_PORT_OFFSET` at `server.port`, with `strictPort` under `vite.server` and `vite.preview`.
|
|
28
|
+
- Astro feeds `server.port` through as the preview port but merges the `vite` block into the config backing both servers, so the port sits at the top level and the bind guarantee under `vite`.
|
|
29
|
+
- `vitest.config.ts` uses `getViteConfig` from `astro/config`, not `mergeConfig`, with jsdom, globals, the setup file, `passWithNoTests: true`, v8 coverage, and `**/*.astro` in coverage excludes.
|
|
30
|
+
- `tsconfig.json` adds `skipLibCheck`, `vitest/globals` and `@testing-library/jest-dom/vitest` in types, and `@/` paths.
|
|
31
|
+
|
|
32
|
+
### Playwright config
|
|
33
|
+
|
|
34
|
+
`playwright.config.ts` runs all browsers, with `webServer` running `bun run build && bun run preview` on port `4321` plus `WORKTREE_PORT_OFFSET` and `reuseExistingServer: false`.
|
|
35
|
+
|
|
36
|
+
- Astro's dev/prod gap is wide (MDX, island hydration, asset optimization), so E2E always tests the built `dist/`.
|
|
37
|
+
- `DIST_PREBUILT` in the environment drops the `build` half, running `bun run preview` alone against a `dist/` a prior CI job produced.
|
|
38
|
+
|
|
39
|
+
### ESLint config
|
|
40
|
+
|
|
41
|
+
`eslint.config.js` is this stack's own flat config, since the web layer lints with oxlint, which parses no `.astro` file. It adds `eslint-plugin-astro` with `astro-eslint-parser`.
|
|
42
|
+
|
|
43
|
+
- React hooks apply to `.jsx` and `.tsx` only, since `.astro` is not React.
|
|
44
|
+
- `check-file/filename-naming-convention` reaches `.ts`, `.tsx`, and `.astro` under `KEBAB_CASE`, overriding Astro's PascalCase on purpose, since markup names a component by its import binding.
|
|
45
|
+
- `.js` and `.jsx` stay out of that rule, matching `web` and `nextjs`.
|
|
46
|
+
- `check-file/folder-naming-convention` reaches every `src/**` folder except `__tests__` and the `pages/` subtree, which has its own off-block for bracket-named dynamic routes and slug folders.
|
|
47
|
+
- `canon/context/surfaces/tooling.md` carries the measurement behind both naming rules.
|
|
48
|
+
|
|
49
|
+
The social card files also ship as golden configs, being `card.config.mjs`, `card-src/pages/og-card.astro`, and `scripts/check-card-exclusion.sh`. See `## Social card route` below.
|
|
26
50
|
|
|
27
51
|
## Typecheck
|
|
28
52
|
|
|
@@ -30,73 +54,90 @@ The astro stack covers Astro + TypeScript projects: content sites, marketing sit
|
|
|
30
54
|
|
|
31
55
|
## Vitest scope
|
|
32
56
|
|
|
33
|
-
Unit-test React island components (`.tsx`) only. Do not test `.astro` files
|
|
57
|
+
Unit-test React island components (`.tsx`) only. Do not test `.astro` files, since Playwright verifies page-level behavior against rendered output.
|
|
34
58
|
|
|
35
|
-
Vitest is pinned to `^5` in the web manifest, which supports the Vite 8 that Astro 7 bundles. The earlier `^3` pin
|
|
59
|
+
Vitest is pinned to `^5` in the web manifest, which supports the Vite 8 that Astro 7 bundles. The earlier `^3` pin dodged `^4` bundling rolldown-vite, which ignored the esbuild JSX config in `@vitejs/plugin-react`. Astro bundling Vite 8 itself ended that conflict.
|
|
36
60
|
|
|
37
61
|
## Formatting (extend)
|
|
38
62
|
|
|
39
|
-
- Format `.astro` files with Prettier and every other file with oxfmt
|
|
40
|
-
- Load `prettier-plugin-astro` first in `.prettierrc`'s plugins
|
|
63
|
+
- Format `.astro` files with Prettier and every other file with oxfmt, which loads no Prettier plugin and cannot parse `.astro`.
|
|
64
|
+
- Load `prettier-plugin-astro` first in `.prettierrc`'s plugins and `prettier-plugin-tailwindcss` last, with a parser override for `.astro` files.
|
|
41
65
|
- Ignore `**/*.astro` in this stack's `.oxfmtrc.json`, so the two formatters never touch one file.
|
|
42
|
-
- Copy every other option from base's `.oxfmtrc.json`
|
|
66
|
+
- Copy every other option from base's `.oxfmtrc.json` by hand. Scan takes the child config over the parent's whole file, so a change to base's options is lost here until copied.
|
|
43
67
|
- Override `check:format` and `format` to run oxfmt, then Prettier over `**/*.astro`.
|
|
44
68
|
|
|
45
69
|
## ESLint
|
|
46
70
|
|
|
47
71
|
- Override `lint` and `lint:fix` back to `eslint . --max-warnings 0`, since the web layer sets them to oxlint.
|
|
48
|
-
- Install
|
|
72
|
+
- Install `eslint@^9`, `@eslint/js`, `typescript-eslint`, `eslint-config-prettier`, `eslint-plugin-react-hooks`, `eslint-plugin-vitest`, and `globals`, since the web layer no longer ships them.
|
|
73
|
+
- Pin `eslint@^9`, because ESLint 10 dropped the `LegacyESLint` shim `typescript-eslint@8` still imports.
|
|
49
74
|
- Expect the web layer's `.oxlintrc.json` in the target. Nothing runs it here.
|
|
50
75
|
|
|
51
76
|
## Development docs (extend)
|
|
52
77
|
|
|
53
|
-
Append to the `## Scripts` table
|
|
78
|
+
Append these rows to the `## Scripts` table.
|
|
54
79
|
|
|
55
|
-
|
|
|
56
|
-
|
|
|
57
|
-
| `bun run
|
|
58
|
-
| `bun run astro`
|
|
59
|
-
| `bun run
|
|
80
|
+
| Command | Purpose |
|
|
81
|
+
| ------------------- | --------------------------------------------------------------- |
|
|
82
|
+
| `bun run dev` | Start the Astro dev server on 4321 plus this worktree's offset. |
|
|
83
|
+
| `bun run build` | Run `astro check` then build the static output. |
|
|
84
|
+
| `bun run preview` | Serve the built site locally. |
|
|
85
|
+
| `bun run astro` | Expose the Astro CLI. |
|
|
86
|
+
| `bun run typecheck` | Run `astro check`. |
|
|
60
87
|
|
|
61
88
|
## CI docs (extend)
|
|
62
89
|
|
|
63
|
-
In `canon/context/ci.md`, the Typecheck row
|
|
90
|
+
In `canon/context/development/ci.md`, the Typecheck row asserts `` `astro check` passes `` and the Build row asserts `` `astro build` succeeds ``. <!-- audit-ignore-citations: canon/context/development/ci.md -->
|
|
64
91
|
|
|
65
92
|
## Social card route
|
|
66
93
|
|
|
67
|
-
The social card is a route the project keeps rather than a page a render writes and deletes
|
|
94
|
+
The social card is a route the project keeps rather than a page a render writes and deletes. Re-rendering it after a rename is an edit to one string and a re-capture, not another pass through the `draft-identity` pick loop.
|
|
95
|
+
|
|
96
|
+
`canon:draft-identity` writes the picked composition into the route and captures it through the server below.
|
|
68
97
|
|
|
69
98
|
### What ships
|
|
70
99
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
- `
|
|
74
|
-
- `
|
|
100
|
+
The route is a second config, an exclusion check, and two scripts. The page itself is covered below.
|
|
101
|
+
|
|
102
|
+
- `card.config.mjs` is a second Astro config whose `srcDir` points at `./card-src`, which the main config's page router never reads. That excludes the card from the published build structurally.
|
|
103
|
+
- `card.config.mjs` uses base port `4421` plus `WORKTREE_PORT_OFFSET`, outside the band `astro.config.mjs` serves from, so a worktree's card and dev servers never contend.
|
|
104
|
+
- `scripts/check-card-exclusion.sh` fails when the route's `<meta name="og-card-marker">` tag reaches `dist/`, and when the route dropped the tag, since the leak test reads it.
|
|
105
|
+
- `bun run card:dev` serves the route and `bun run card:check` runs the exclusion check, which base's `scripts/verify.ts` also runs after its build stage when the script is installed.
|
|
75
106
|
|
|
76
|
-
###
|
|
107
|
+
### Stylesheet and tokens
|
|
77
108
|
|
|
78
|
-
|
|
109
|
+
`card-src/pages/og-card.astro` ships as a working placeholder, so it serves before any pick. It imports the project's global stylesheet to read real tokens and fonts, uses `--color-background` and `--color-text` as `canon design css` emits them, and sets no `font-family`, so the card inherits the body font.
|
|
79
110
|
|
|
80
|
-
|
|
111
|
+
- The import points at `@/styles/global.css`. A project keeping its stylesheet elsewhere retargets that one line.
|
|
112
|
+
- An unresolved path refuses the card server by name. That is the loud direction, replacing a card captured against no stylesheet that renders unstyled and reports success.
|
|
81
113
|
|
|
82
|
-
|
|
114
|
+
- A custom property the stylesheet does not define fails the same silent way. It takes its fallback, captures cleanly, and ships typed values.
|
|
115
|
+
- So the route names only properties `canon design css` emits, and a project retargeting the import checks the names against its own stylesheet.
|
|
83
116
|
|
|
84
|
-
|
|
117
|
+
### Exclusion and card size
|
|
118
|
+
|
|
119
|
+
The exclusion is a check rather than a convention because the route has three consumers and only one can be enforced. The card server reads it, the capture reads it, and the published build must not carry it.
|
|
120
|
+
|
|
121
|
+
`.card` is declared at 600x315, half of the 1200x630 target. `canon capture` opens every page at a fixed 2x device scale factor with no flag to change it, so a half-size element lands the PNG on the literal size. Declaring 1200x630 captures at 2400x1260.
|
|
85
122
|
|
|
86
123
|
### Why v1 is Astro-only
|
|
87
124
|
|
|
88
|
-
Astro is the one web stack whose config gives a route a structural build exclusion.
|
|
125
|
+
Astro is the one web stack whose config gives a route a structural build exclusion.
|
|
126
|
+
|
|
127
|
+
- Next's App Router has no build-time route exclusion, and needs none on its path, since `ImageResponse` is that framework's production mechanism and a card route there is meant to be served.
|
|
128
|
+
- `vite-react` builds a single-page app with no page router, so a card is a second Rollup entry rather than a route.
|
|
89
129
|
|
|
90
130
|
## Gitignore (extend)
|
|
91
131
|
|
|
92
|
-
`[gitignore]` groups
|
|
132
|
+
These `[gitignore]` groups mirror the manifest.
|
|
93
133
|
|
|
94
134
|
- `"# Astro" = [".astro/"]`
|
|
95
135
|
- `"# Social card" = ["card-dist/"]`
|
|
96
136
|
|
|
97
137
|
## Scenario switcher
|
|
98
138
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
-
|
|
102
|
-
-
|
|
139
|
+
`src/components/dev/scenarios.astro` ships as a golden config, always overwritten on sync, since it is toolkit-authored infrastructure rather than a file a project hand-edits.
|
|
140
|
+
|
|
141
|
+
- Import it into a page under test to drive candidate treatments of one decision by hand, selected by a query parameter, with a switcher between them.
|
|
142
|
+
- It is guarded by `import.meta.env.DEV`, so it renders nothing and ships nothing in a production build.
|
|
143
|
+
- It is Astro-only, using `is:inline`, `define:vars`, and `set:html`, which only the `.astro` format parses. `vite-react` gets no equivalent until a real decision drives one.
|
|
@@ -2,65 +2,86 @@
|
|
|
2
2
|
|
|
3
3
|
## Overview
|
|
4
4
|
|
|
5
|
-
The base layer covers every project the toolkit scaffolds, whatever language sits on top. It ships formatting, conventional commits, git hooks, CI, and the verify runner. Every other stack extends it, so a decision made here is one every stack inherits. Shell formatting and linting live in the `shell` add-on stack
|
|
5
|
+
The base layer covers every project the toolkit scaffolds, whatever language sits on top. It ships formatting, conventional commits, git hooks, CI, and the verify runner. Every other stack extends it, so a decision made here is one every stack inherits. Shell formatting and linting live in the `shell` add-on stack.
|
|
6
6
|
|
|
7
7
|
A repository with several language roots layers this stack once at the root and skips it per subtree, since git honors only one `core.hooksPath` and a re-dropped husky silently breaks. `canon docs scenarios` carries the recipe.
|
|
8
8
|
|
|
9
9
|
## What ships as golden configs
|
|
10
10
|
|
|
11
|
-
Golden
|
|
11
|
+
Golden configs live in `tooling/base/configs/`. `canon tooling sync base . --write` copies them into the target and replaces whatever sits at those paths. They are the source of truth, so this reference covers rationale and the configs show the setup.
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
-
|
|
13
|
+
### Formatting and commit configs
|
|
14
|
+
|
|
15
|
+
Two configs fix how output looks and how commits read.
|
|
16
|
+
|
|
17
|
+
- `.oxfmtrc.json` sets `semi: false`, `singleQuote: true`, `printWidth: 80`, and `sortPackageJson: false`, keeping the output Prettier wrote.
|
|
18
|
+
- The `astro` stack repeats those options in its own copy, so copy a change here into it.
|
|
19
|
+
- `commitlint.config.js` is an ESM default export extending `@commitlint/config-conventional`. It sets `header-max-length: 72`, `scope-case: lower-case`, and `subject-full-stop: never`.
|
|
20
|
+
- Its `subject-case` rule is off, and a local `no-claude-co-author` rule refuses a `Co-authored-by` trailer that names Claude or Anthropic.
|
|
21
|
+
- `.husky/` holds `pre-commit`, `commit-msg`, `pre-push`, `post-merge`, and `post-rewrite`.
|
|
22
|
+
|
|
23
|
+
### Verify runner
|
|
24
|
+
|
|
25
|
+
`scripts/verify.ts` is the one verify runner every stack shares, and the command behind `check`. It uses Node modules only, since a target's own lint and typecheck read it.
|
|
26
|
+
|
|
27
|
+
- It runs typecheck, lint, format check, shell, unit tests, build, markdown bans, then card exclusion, always in that order, and skips any phase the folder's `scripts/verify.json` leaves out.
|
|
28
|
+
- `scripts/verify.json` lists the phases a stack requires, plus any tools and preconditions it probes first. The nearest stack's copy wins.
|
|
29
|
+
- A required phase whose script `package.json` lacks fails the run. A base phase the list leaves out runs only where the folder declares its script.
|
|
30
|
+
- Base requires `check:format`, so a base-only target still fails on a missing one.
|
|
31
|
+
- The shell phase runs only where `package.json` declares `check:shell`, which the `shell` stack adds, and probes for `shfmt` and `shellcheck` first.
|
|
32
|
+
|
|
33
|
+
These two files are base's `[sync] per_root` set, which a `--skip base` subfolder still receives.
|
|
18
34
|
|
|
19
35
|
## What ships as user-owned seeds
|
|
20
36
|
|
|
21
37
|
Seeds live in `tooling/base/seeds/`. Sync drops each once on first install and never overwrites it, so a project extends them freely.
|
|
22
38
|
|
|
23
|
-
- `.lintstagedrc
|
|
24
|
-
- `.github/workflows/verify.yml
|
|
25
|
-
- `.github/pull_request_template.md
|
|
26
|
-
- `canon/context
|
|
39
|
+
- `.lintstagedrc` holds the glob map below.
|
|
40
|
+
- `.github/workflows/verify.yml` runs on pull requests targeting `main` and on `workflow_dispatch`. A multi-root project adds its own jobs here.
|
|
41
|
+
- `.github/pull_request_template.md` has `## Summary`, `## Key changes`, `## Technical context`, and `## Testing`. A project adds its own checklist lines.
|
|
42
|
+
- `canon/context/` holds the `ci` and `development` entries. Extend them with project-specific commands, workflows, or deploy steps, and keep canonical rationale in this reference.
|
|
27
43
|
|
|
28
44
|
## Tool pairing
|
|
29
45
|
|
|
30
|
-
- Runtime
|
|
31
|
-
- Formatting
|
|
32
|
-
- Commits
|
|
33
|
-
- Dev dependencies
|
|
46
|
+
- Runtime is `bun` as package manager and script runner, with `bunx` over `npx` for a one-off executable.
|
|
47
|
+
- Formatting is oxfmt for what it parses, pinned to an exact version so an upgrade arrives as a reviewed diff. Shell formatting is the `shell` stack's, since oxfmt has no shell parser.
|
|
48
|
+
- Commits go through commitlint against conventional commits, wired through the husky `commit-msg` hook. The format is `<type>(<scope>): <subject>` in imperative mood with no trailing period.
|
|
49
|
+
- Dev dependencies are `oxfmt`, `husky`, `@commitlint/cli`, and `@commitlint/config-conventional`, installed via `bun add -D`.
|
|
34
50
|
|
|
35
51
|
## oxfmt
|
|
36
52
|
|
|
37
53
|
- Add a project's own ignores to `.prettierignore`, which oxfmt reads beside `.gitignore` by default. Sync overwrites `.oxfmtrc.json`, so an entry in its `ignorePatterns` is lost on the next sync.
|
|
38
|
-
-
|
|
54
|
+
- After the first sync onto oxfmt, delete a leftover `.prettierrc` and run `bun remove prettier`. Sync deletes nothing a stack stopped shipping.
|
|
39
55
|
|
|
40
56
|
## File layout
|
|
41
57
|
|
|
42
|
-
|
|
43
|
-
|
|
58
|
+
Hooks live in `.husky/` and seeded context docs in `canon/context/`. That location matches the three-tier context model.
|
|
59
|
+
|
|
60
|
+
- Project-wide invariants sit in `CLAUDE.md` and `canon/REQUIREMENTS.md`.
|
|
61
|
+
- Path-scoped rules sit in `.claude/rules/`.
|
|
62
|
+
- On-demand domain narrative sits in `canon/context/`, with indexes opt-in.
|
|
44
63
|
|
|
45
64
|
## Hooks
|
|
46
65
|
|
|
47
66
|
- `pre-commit` runs `bunx lint-staged`.
|
|
48
67
|
- `commit-msg` runs `bunx commitlint --edit "$1"`.
|
|
49
68
|
- `pre-push` runs `bun run check`.
|
|
50
|
-
- `post-merge` runs `canon hooks post-merge`: archive the task a merged pull request closed, push the records, reclaim merged worktrees, reinstall the CLI, and update the plugin. It does nothing when the board is absent. `CANON_SKIP_RECLAIM`, `CANON_SKIP_UPGRADE`, and `CANON_SKIP_PLUGIN_UPDATE` turn off the three steps that act on the machine. Read `canon docs hooks` for the contract.
|
|
51
69
|
- `post-rewrite` delegates to `post-merge` on `rebase`, so a `pull.rebase=true` machine still gets the check.
|
|
70
|
+
- `post-merge` runs `canon hooks post-merge`, which archives the task a merged pull request closed, pushes the records, reclaims merged worktrees, reinstalls the CLI, and updates the plugin.
|
|
71
|
+
|
|
72
|
+
The hook does nothing when the board is absent. `CANON_SKIP_RECLAIM`, `CANON_SKIP_UPGRADE`, and `CANON_SKIP_PLUGIN_UPDATE` turn off the three steps that act on the machine, and `canon hooks post-merge --help` states the contract.
|
|
52
73
|
|
|
53
74
|
## lint-staged
|
|
54
75
|
|
|
55
|
-
|
|
76
|
+
The seeded baseline globs.
|
|
56
77
|
|
|
57
78
|
- `**/*.{json,md}` runs `oxfmt --no-error-on-unmatched-pattern`. Without the flag, a staged file every pattern ignores, such as `CHANGELOG.md`, exits 2 and fails the commit.
|
|
58
|
-
- `**/*.md` runs `canon indexes regen
|
|
79
|
+
- `**/*.md` runs `canon indexes regen`.
|
|
59
80
|
|
|
60
81
|
## CI
|
|
61
82
|
|
|
62
|
-
- Steps
|
|
63
|
-
- CI asserts and never writes
|
|
83
|
+
- Steps are checkout, setup Bun at latest, `bun install --frozen-lockfile`, then `check:format`.
|
|
84
|
+
- CI asserts and never writes, so format must be clean before push.
|
|
64
85
|
|
|
65
86
|
## Gitignore
|
|
66
87
|
|
|
@@ -71,22 +92,32 @@ Seeded baseline globs:
|
|
|
71
92
|
|
|
72
93
|
## Anti-patterns
|
|
73
94
|
|
|
74
|
-
|
|
95
|
+
Negative knowledge that costs a failed run to relearn.
|
|
75
96
|
|
|
76
97
|
- Do NOT pass `--ignore-path` to oxfmt. It replaces the default `.gitignore` and `.prettierignore` pair rather than adding to it.
|
|
77
98
|
- Do NOT put logic in a husky hook and expect its shebang to hold. Husky runs hooks as `sh -e`, so a hook carrying logic is POSIX sh under errexit whatever the first line says.
|
|
78
99
|
|
|
79
100
|
## CLI
|
|
80
101
|
|
|
81
|
-
| Script | What it does
|
|
82
|
-
| ---------------------- |
|
|
83
|
-
| `bun run check` | Gates a checkout through `scripts/verify.ts
|
|
84
|
-
| `bun run check:format` | Asserts oxfmt formatting without writing
|
|
85
|
-
| `bun run format` | Writes oxfmt formatting in place
|
|
86
|
-
| `bun run prepare` | Initializes husky hooks, run automatically on `bun install`
|
|
102
|
+
| Script | What it does |
|
|
103
|
+
| ---------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
|
|
104
|
+
| `bun run check` | Gates a checkout through `scripts/verify.ts`, writing nothing. `VERIFY_NESTED=true` suppresses timeline boundaries in a call. |
|
|
105
|
+
| `bun run check:format` | Asserts oxfmt formatting without writing |
|
|
106
|
+
| `bun run format` | Writes oxfmt formatting in place |
|
|
107
|
+
| `bun run prepare` | Initializes husky hooks, run automatically on `bun install` |
|
|
108
|
+
|
|
109
|
+
### The check script
|
|
87
110
|
|
|
88
111
|
Base writes `check` as a `[scripts.override]`, so a sync replaces whatever `check` a target declared, including one that chained further checks after the runner. Re-add a chained check as its own script after a sync.
|
|
89
112
|
|
|
90
|
-
`bun run check`
|
|
113
|
+
- `bun run check` writes nothing. Formatting drift fails it at `check:format`, and the failure line names `bun run format`, the one command that writes.
|
|
114
|
+
- Auto-fixing drift inside `check` rewrote files in a shared checkout on every push and left a gate that could not fail, so the writing pass stays in `bun run format`.
|
|
115
|
+
- The pull request workflow calls `check:format` directly as well.
|
|
116
|
+
|
|
117
|
+
### The markdown bans stage
|
|
118
|
+
|
|
119
|
+
The `Markdown bans` stage of `scripts/verify.ts` runs `canon markdown audit` over every tracked markdown file except `CHANGELOG.md`, since a generated changelog carries commit subjects nobody wrote against the ban set.
|
|
91
120
|
|
|
92
|
-
|
|
121
|
+
- It exits `2` on a finding or `3` on a shipped-empty ban set, failing `bun run check` and the `pre-push` hook that calls it.
|
|
122
|
+
- A refusal to measure exits `1` and is logged as a skip, as does a corpus with no file left once `CHANGELOG.md` is set aside.
|
|
123
|
+
- The stage needs `canon` on PATH, and `canon docs projects` names the install command. Without it the stage is skipped and logged.
|
|
@@ -7,25 +7,17 @@ description: GitHub Actions workflow triggers and checks
|
|
|
7
7
|
|
|
8
8
|
## Overview
|
|
9
9
|
|
|
10
|
-
Owns the GitHub Actions workflow that gates a merge
|
|
10
|
+
Owns the GitHub Actions workflow that gates a merge, being which events start a run and which checks have to pass before the branch can land. The checks call package scripts rather than defining commands of their own, so what each one runs is the development entry's subject.
|
|
11
11
|
|
|
12
12
|
## Layout
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
### `.github/workflows/`
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
Holds `verify.yml`, the workflow that gates a merge. A pull request targeting `main` starts it, and so does a manual `workflow_dispatch` run from the Actions tab.
|
|
17
17
|
|
|
18
|
-
-
|
|
19
|
-
- `
|
|
18
|
+
- Every job must pass before merge
|
|
19
|
+
- The Format check runs `bun run check:format` and asserts oxfmt is clean
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
## Gotchas
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
| Check | Command | What it asserts |
|
|
26
|
-
| ------ | ---------------------- | --------------- |
|
|
27
|
-
| Format | `bun run check:format` | oxfmt is clean |
|
|
28
|
-
|
|
29
|
-
### Running CI locally
|
|
30
|
-
|
|
31
|
-
`bun run check` runs the same assert and writes nothing. If CI fails on format, run `bun run format` locally and commit the diff.
|
|
23
|
+
- `bun run check` runs the same assert as CI and writes nothing, so a CI format failure is fixed by running `bun run format` locally and committing the diff
|
|
@@ -7,31 +7,31 @@ description: Local dev workflow, scripts, and husky hooks
|
|
|
7
7
|
|
|
8
8
|
## Overview
|
|
9
9
|
|
|
10
|
-
Owns how the project runs on a developer machine
|
|
10
|
+
Owns how the project runs on a developer machine, being the toolchain install, the scripts that verify a change, and the git hooks that run them before a commit or a push leaves. CI calls the same scripts from a workflow, which is the CI entry's subject.
|
|
11
11
|
|
|
12
12
|
## Layout
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
- `.husky/` owns the git hooks
|
|
14
|
+
### `scripts/`
|
|
16
15
|
|
|
17
|
-
|
|
16
|
+
Holds every shell script, since none belongs outside this folder. The package scripts below call them.
|
|
18
17
|
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
### `.husky/`
|
|
19
|
+
|
|
20
|
+
Holds the git hooks that run the checks before a commit or a push leaves.
|
|
21
|
+
|
|
22
|
+
- `pre-commit` runs `lint-staged` (oxfmt on staged files)
|
|
23
|
+
- `commit-msg` runs `commitlint` against the conventional commit format
|
|
24
|
+
- `pre-push` runs `bun run check`, and also gates on banned characters across every tracked markdown file except `CHANGELOG.md` when a markdown-bans audit tool is on PATH
|
|
21
25
|
|
|
22
26
|
### Scripts
|
|
23
27
|
|
|
28
|
+
Install [Bun](https://bun.sh) with `curl -fsSL https://bun.sh/install | bash` and run `bun install`, then use these commands.
|
|
29
|
+
|
|
24
30
|
| Command | Purpose |
|
|
25
31
|
| ---------------- | ---------------------------------------------------------------------------------------- |
|
|
26
32
|
| `bun run check` | Gates locally and writes nothing. Fails on formatting drift until `bun run format` runs. |
|
|
27
33
|
| `bun run format` | Auto-fix oxfmt formatting. |
|
|
28
34
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
All `.sh` files live under `scripts/`. Do not place shell scripts outside `scripts/`.
|
|
32
|
-
|
|
33
|
-
### Husky hooks
|
|
35
|
+
## Gotchas
|
|
34
36
|
|
|
35
|
-
- `
|
|
36
|
-
- `commit-msg` runs `commitlint` against the conventional commit format.
|
|
37
|
-
- `pre-push` runs `bun run check`. When a markdown-bans audit tool is on PATH, it also gates on banned characters across every tracked markdown file except `CHANGELOG.md`. A push fails on formatting drift until `bun run format` runs, since the hook writes nothing.
|
|
37
|
+
- A push fails on formatting drift until `bun run format` runs, since the `pre-push` hook writes nothing
|