@erclx/canon 5.37.0 → 5.38.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/claude/.claude-plugin/plugin.json +1 -1
- package/claude/skills/auto-ship/SKILL.md +1 -1
- package/claude/skills/backlog-triage/SKILL.md +1 -1
- package/claude/skills/build-in-slices/SKILL.md +1 -1
- package/claude/skills/git-issue/references/issue.md +11 -10
- package/claude/skills/markdown-craft/references/markdown.md +3 -2
- package/claude/skills/plan-groundwork/references/groundwork.md +53 -24
- package/claude/skills/review-pr/references/post.md +1 -1
- package/claude/skills/role-orchestrator/SKILL.md +1 -1
- package/claude/skills/role-orchestrator/references/orchestrator-parked.md +1 -1
- package/claude/skills/role-planner/SKILL.md +1 -1
- package/claude/skills/role-worker/SKILL.md +1 -1
- package/claude/skills/target-setup/SKILL.md +3 -4
- package/claude/skills/target-setup/references/verify.md +6 -5
- package/claude/skills/task-board/references/tasks.md +3 -1
- package/docs/target/projects.md +1 -1
- package/docs/target/scenarios.md +1 -1
- package/governance/detect.toml +6 -1
- package/package.json +6 -3
- package/scripts/core/install-check.sh +1 -1
- package/src/commands/claude.ts +0 -1
- package/src/commands/pr.ts +1 -1
- package/src/commands/records.ts +0 -1
- package/src/docs/read.ts +15 -21
- package/src/gate/stages.ts +14 -0
- package/src/markdown/structure.ts +1 -1
- package/src/sandbox/equivalence.ts +7 -9
- package/src/teach/browser/head.ts +2 -2
- package/src/teach/browser/sidebar.ts +3 -3
- package/src/teach/browser/theme.ts +2 -2
- package/src/tooling/inject.ts +2 -2
- package/src/tooling/package.ts +15 -18
- package/src/tooling/verify-stack.ts +6 -3
- package/tooling/astro/configs/.oxfmtrc.json +8 -0
- package/tooling/astro/configs/.prettierrc +13 -0
- package/tooling/astro/manifest.toml +13 -0
- package/tooling/astro/reference.md +15 -5
- package/tooling/base/configs/.oxfmtrc.json +7 -0
- package/tooling/base/configs/scripts/verify.ts +1 -1
- package/tooling/base/manifest.toml +6 -6
- package/tooling/base/reference.md +12 -9
- package/tooling/base/seeds/.lintstagedrc +1 -3
- package/tooling/base/seeds/canon/context/ci.md +3 -3
- package/tooling/base/seeds/canon/context/development.md +2 -2
- package/tooling/claude/manifest.toml +5 -1
- package/tooling/go/reference.md +2 -2
- package/tooling/nextjs/configs/.oxlintrc.json +223 -0
- package/tooling/nextjs/manifest.toml +2 -5
- package/tooling/nextjs/reference.md +4 -4
- package/tooling/php/manifest.toml +6 -1
- package/tooling/php/reference.md +2 -2
- package/tooling/python/manifest.toml +6 -1
- package/tooling/python/reference.md +4 -4
- package/tooling/vite-react/manifest.toml +1 -4
- package/tooling/vite-react/reference.md +3 -3
- package/tooling/web/configs/.oxlintrc.json +213 -0
- package/tooling/web/manifest.toml +9 -13
- package/tooling/web/reference.md +14 -14
- package/tooling/base/configs/.prettierrc +0 -12
- package/tooling/base/seeds/.prettierignore +0 -0
- package/tooling/nextjs/configs/eslint.config.js +0 -91
- package/tooling/web/configs/eslint.config.js +0 -82
|
@@ -19,12 +19,12 @@ export function head(page: 'index' | 'lesson', breakpoint: number): void {
|
|
|
19
19
|
}
|
|
20
20
|
try {
|
|
21
21
|
s = localStorage.getItem('teach-sb')
|
|
22
|
-
} catch
|
|
22
|
+
} catch {}
|
|
23
23
|
var idx = r.dataset.page === 'index'
|
|
24
24
|
if (s === 'shut' || (s === null && idx)) r.classList.add('sb-shut')
|
|
25
25
|
var w: string | null = null
|
|
26
26
|
try {
|
|
27
27
|
w = localStorage.getItem('teach-sb-w')
|
|
28
|
-
} catch
|
|
28
|
+
} catch {}
|
|
29
29
|
if (w) r.style.setProperty('--sb-w', w + 'px')
|
|
30
30
|
}
|
|
@@ -34,7 +34,7 @@ export function sidebar(breakpoint: number): void {
|
|
|
34
34
|
root.classList.add('sb-shut')
|
|
35
35
|
try {
|
|
36
36
|
localStorage.setItem('teach-sb', 'shut')
|
|
37
|
-
} catch
|
|
37
|
+
} catch {}
|
|
38
38
|
if (inside && fold) fold.focus()
|
|
39
39
|
}
|
|
40
40
|
|
|
@@ -46,7 +46,7 @@ export function sidebar(breakpoint: number): void {
|
|
|
46
46
|
'teach-sb',
|
|
47
47
|
root.classList.contains('sb-shut') ? 'shut' : 'open',
|
|
48
48
|
)
|
|
49
|
-
} catch
|
|
49
|
+
} catch {}
|
|
50
50
|
/* An overlay takes focus with it. Without this a keyboard reader opens the
|
|
51
51
|
panel and goes on tabbing through the lesson behind the scrim. */
|
|
52
52
|
if (!narrow.matches || root.classList.contains('sb-shut')) return
|
|
@@ -112,7 +112,7 @@ export function sidebar(breakpoint: number): void {
|
|
|
112
112
|
if (save) {
|
|
113
113
|
try {
|
|
114
114
|
localStorage.setItem('teach-sb-w', String(w))
|
|
115
|
-
} catch
|
|
115
|
+
} catch {}
|
|
116
116
|
}
|
|
117
117
|
}
|
|
118
118
|
|
|
@@ -3,7 +3,7 @@ export function theme(): void {
|
|
|
3
3
|
try {
|
|
4
4
|
var s = localStorage.getItem('course-theme')
|
|
5
5
|
if (s) r.dataset.theme = s
|
|
6
|
-
} catch
|
|
6
|
+
} catch {}
|
|
7
7
|
document.addEventListener('click', function (e) {
|
|
8
8
|
var b = (e.target as Element).closest('.theme')
|
|
9
9
|
if (!b) return
|
|
@@ -13,6 +13,6 @@ export function theme(): void {
|
|
|
13
13
|
r.dataset.theme = d ? 'light' : 'dark'
|
|
14
14
|
try {
|
|
15
15
|
localStorage.setItem('course-theme', r.dataset.theme)
|
|
16
|
-
} catch
|
|
16
|
+
} catch {}
|
|
17
17
|
})
|
|
18
18
|
}
|
package/src/tooling/inject.ts
CHANGED
|
@@ -16,7 +16,6 @@ import {
|
|
|
16
16
|
import {
|
|
17
17
|
applyScripts,
|
|
18
18
|
collectDeps,
|
|
19
|
-
collectScripts,
|
|
20
19
|
readPackage,
|
|
21
20
|
serializePackage,
|
|
22
21
|
} from '@/tooling/package'
|
|
@@ -198,7 +197,8 @@ export async function injectManifest(
|
|
|
198
197
|
|
|
199
198
|
function warnSkippedInstalls(chain: readonly Manifest[], target: string): void {
|
|
200
199
|
const deps = collectDeps(chain, {}).map((dep) => dep.spec)
|
|
201
|
-
const
|
|
200
|
+
const skipped = applyScripts({}, chain)
|
|
201
|
+
const scripts = [...new Set([...skipped.added, ...skipped.overridden])]
|
|
202
202
|
if (deps.length === 0 && scripts.length === 0) return
|
|
203
203
|
|
|
204
204
|
logStep('Skipped installs')
|
package/src/tooling/package.ts
CHANGED
|
@@ -101,34 +101,31 @@ export function collectDeps(
|
|
|
101
101
|
}
|
|
102
102
|
|
|
103
103
|
/**
|
|
104
|
-
* Compares manifest scripts
|
|
105
|
-
*
|
|
104
|
+
* Compares manifest scripts and `[scripts.override]` keys against
|
|
105
|
+
* package.json. The expected value of each key comes from `applyScripts`, so
|
|
106
|
+
* the scan and the write path agree on which stack wins. Plain keys come
|
|
107
|
+
* first, nearest stack first, then override-only keys.
|
|
106
108
|
*/
|
|
107
109
|
export function collectScripts(
|
|
108
110
|
chain: readonly Manifest[],
|
|
109
111
|
pkg: PackageJson,
|
|
110
112
|
): ScriptState[] {
|
|
111
113
|
const scripts = pkg.scripts ?? {}
|
|
112
|
-
const
|
|
113
|
-
const
|
|
114
|
+
const expected = applyScripts({}, chain).pkg.scripts ?? {}
|
|
115
|
+
const keys = new Set<string>()
|
|
114
116
|
|
|
115
117
|
for (const manifest of chain) {
|
|
116
|
-
for (const
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
const current = scripts[key]
|
|
121
|
-
if (current === undefined) {
|
|
122
|
-
states.push({ key, state: 'missing' })
|
|
123
|
-
} else if (current !== value) {
|
|
124
|
-
states.push({ key, state: 'drifted' })
|
|
125
|
-
} else {
|
|
126
|
-
states.push({ key, state: 'matching' })
|
|
127
|
-
}
|
|
128
|
-
}
|
|
118
|
+
for (const key of Object.keys(manifest.scripts)) keys.add(key)
|
|
119
|
+
}
|
|
120
|
+
for (const manifest of chain) {
|
|
121
|
+
for (const key of Object.keys(manifest.scriptOverrides)) keys.add(key)
|
|
129
122
|
}
|
|
130
123
|
|
|
131
|
-
return
|
|
124
|
+
return [...keys].map((key): ScriptState => {
|
|
125
|
+
const current = scripts[key]
|
|
126
|
+
if (current === undefined) return { key, state: 'missing' }
|
|
127
|
+
return { key, state: current === expected[key] ? 'matching' : 'drifted' }
|
|
128
|
+
})
|
|
132
129
|
}
|
|
133
130
|
|
|
134
131
|
/**
|
|
@@ -32,7 +32,8 @@ interface Run {
|
|
|
32
32
|
/**
|
|
33
33
|
* Scaffolds a stack into `.canon/tmp/runs/verify-<stack>/`, syncs its chain
|
|
34
34
|
* from the checkout at `root`, and runs the scaffold's own `lint:fix`,
|
|
35
|
-
* `check`, `test:e2e`, and `screenshot` scripts. Every line goes
|
|
35
|
+
* `format`, `check`, `test:e2e`, and `screenshot` scripts. Every line goes
|
|
36
|
+
* to stderr.
|
|
36
37
|
*
|
|
37
38
|
* A failed phase is recorded and the run carries on, so the Results block
|
|
38
39
|
* reports every phase rather than the first failure. The tmp dir is removed
|
|
@@ -153,11 +154,13 @@ async function run({
|
|
|
153
154
|
}
|
|
154
155
|
|
|
155
156
|
/**
|
|
156
|
-
* `lint:fix`
|
|
157
|
-
*
|
|
157
|
+
* `lint:fix` and `format` run first and rewrite the scaffold, so `check`
|
|
158
|
+
* reads the tree a project would commit. `check` itself writes nothing, and a
|
|
159
|
+
* fresh scaffold ships files its own generator formatted another way.
|
|
158
160
|
*/
|
|
159
161
|
async function runScripts(state: Run, scripts: Set<string>): Promise<void> {
|
|
160
162
|
await phase(state, 'lint:fix', () => bunRun('lint:fix', state.dir))
|
|
163
|
+
await phase(state, 'format', () => bunRun('format', state.dir))
|
|
161
164
|
await phase(state, 'check', () => bunRun('check', state.dir))
|
|
162
165
|
|
|
163
166
|
if (scripts.has('test:e2e')) {
|
|
@@ -9,9 +9,18 @@ packages = [
|
|
|
9
9
|
"astro",
|
|
10
10
|
"@astrojs/react",
|
|
11
11
|
"@astrojs/check",
|
|
12
|
+
"eslint@^9",
|
|
13
|
+
"@eslint/js",
|
|
14
|
+
"typescript-eslint",
|
|
15
|
+
"eslint-config-prettier",
|
|
16
|
+
"eslint-plugin-react-hooks",
|
|
17
|
+
"eslint-plugin-vitest",
|
|
18
|
+
"globals",
|
|
12
19
|
"eslint-plugin-astro",
|
|
13
20
|
"astro-eslint-parser",
|
|
21
|
+
"prettier",
|
|
14
22
|
"prettier-plugin-astro",
|
|
23
|
+
"prettier-plugin-tailwindcss",
|
|
15
24
|
]
|
|
16
25
|
|
|
17
26
|
[scripts]
|
|
@@ -24,6 +33,10 @@ packages = [
|
|
|
24
33
|
"card:check" = "bash scripts/check-card-exclusion.sh"
|
|
25
34
|
|
|
26
35
|
[scripts.override]
|
|
36
|
+
"lint" = "eslint . --max-warnings 0"
|
|
37
|
+
"lint:fix" = "eslint . --fix --max-warnings 0"
|
|
38
|
+
"check:format" = "oxfmt --check . && prettier --check --log-level warn '**/*.astro'"
|
|
39
|
+
"format" = "oxfmt . && prettier --write --log-level warn '**/*.astro'"
|
|
27
40
|
"screenshot" = "PREVIEW_PORT=$(bash scripts/worktree-port.sh 4321) && export PREVIEW_PORT && bash scripts/screenshot.sh"
|
|
28
41
|
"readme-screenshot" = "PREVIEW_PORT=$(bash scripts/worktree-port.sh 4321) && export PREVIEW_PORT && bash scripts/readme-screenshot.sh"
|
|
29
42
|
"dev" = "WORKTREE_PORT_OFFSET=$(bash scripts/worktree-port.sh) && export WORKTREE_PORT_OFFSET && astro dev"
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
## Overview
|
|
6
6
|
|
|
7
|
-
The astro stack covers Astro + TypeScript projects: content sites, marketing sites, blogs, and docs. Interactive islands use React only. Output is static. It ships golden configs for `astro.config.mjs`, `vitest.config.ts` (using `getViteConfig` from `astro/config`), `playwright.config.ts` (preview-server backed), a `tsconfig.json` that extends `astro/tsconfigs/strict`,
|
|
7
|
+
The astro stack covers Astro + TypeScript projects: content sites, marketing sites, blogs, and docs. Interactive islands use React only. Output is static. It ships golden configs for `astro.config.mjs`, `vitest.config.ts` (using `getViteConfig` from `astro/config`), `playwright.config.ts` (preview-server backed), a `tsconfig.json` that extends `astro/tsconfigs/strict`, an `eslint.config.js` that keeps ESLint for this stack alone, and a `.prettierrc` that formats `.astro` files alone.
|
|
8
8
|
|
|
9
9
|
## Scaffold checklist
|
|
10
10
|
|
|
@@ -13,7 +13,7 @@ The astro stack covers Astro + TypeScript projects: content sites, marketing sit
|
|
|
13
13
|
3. Install web tooling: `canon tooling sync web . --write`
|
|
14
14
|
4. Install astro adapter: `canon tooling sync astro . --write`
|
|
15
15
|
5. Extend the `ci` and `development` context entries under `canon/context/` per the web reference's extend sections plus the astro rows below.
|
|
16
|
-
6. Run `bun run lint:fix` then `bun run check`.
|
|
16
|
+
6. Run `bun run lint:fix`, then `bun run format`, then `bun run check`.
|
|
17
17
|
|
|
18
18
|
## What ships as golden configs
|
|
19
19
|
|
|
@@ -22,7 +22,7 @@ The astro stack covers Astro + TypeScript projects: content sites, marketing sit
|
|
|
22
22
|
- `playwright.config.ts`: all browsers, `webServer` runs `bun run build && bun run preview` on port `4321` plus `WORKTREE_PORT_OFFSET`, `reuseExistingServer: false`. Astro's dev/prod gap is wide (MDX, island hydration, asset optimization), so E2E always tests the built `dist/`. `DIST_PREBUILT` set in the environment drops the `build` half, running `bun run preview` alone against a `dist/` a prior CI job already produced.
|
|
23
23
|
- `tsconfig.json`: extends `astro/tsconfigs/strict`, adds `skipLibCheck`, `vitest/globals` and `@testing-library/jest-dom/vitest` in types, `@/` paths.
|
|
24
24
|
- `card.config.mjs`, `card-src/pages/og-card.astro`, and `scripts/check-card-exclusion.sh`: the social card route, its structural build exclusion, and the check enforcing it. See `## Social card route` below.
|
|
25
|
-
- `eslint.config.js`:
|
|
25
|
+
- `eslint.config.js`: this stack's own flat config, since the web layer lints with oxlint, which parses no `.astro` file. Adds `eslint-plugin-astro` (`.astro` parser via `astro-eslint-parser`). React-hooks scoped to `.jsx`/`.tsx` only (`.astro` is not React). The shared block's `files` selector includes `.astro`, so `check-file/filename-naming-convention` reaches `.ts`, `.tsx`, and `.astro` under `KEBAB_CASE`, overriding Astro's own PascalCase component convention deliberately, on the ground that a component's name in markup comes from the import binding rather than the filename. `.js` and `.jsx` stay out of the rule's own pattern, matching `web` and `nextjs`. `check-file/folder-naming-convention` reaches every `src/**` folder except `__tests__` and the whole `pages/` subtree, which carries its own off-block for the bracket-named dynamic routes and nested slug folders Astro's file-based routing produces. See `canon/context/tooling/stacks.md` for the measurement.
|
|
26
26
|
|
|
27
27
|
## Typecheck
|
|
28
28
|
|
|
@@ -34,9 +34,19 @@ Unit-test React island components (`.tsx`) only. Do not test `.astro` files. Pag
|
|
|
34
34
|
|
|
35
35
|
Vitest is pinned to `^5` in the web manifest, which supports the Vite 8 that Astro 7 bundles. The earlier `^3` pin avoided `^4` bundling rolldown-vite, which ignored the esbuild JSX config in `@vitejs/plugin-react`. Astro now bundles Vite 8 itself, so that conflict no longer applies.
|
|
36
36
|
|
|
37
|
-
##
|
|
37
|
+
## Formatting (extend)
|
|
38
38
|
|
|
39
|
-
|
|
39
|
+
- Format `.astro` files with Prettier and every other file with oxfmt. oxfmt loads no Prettier plugin, so it cannot parse `.astro`.
|
|
40
|
+
- Load `prettier-plugin-astro` first in `.prettierrc`'s plugins, then `prettier-plugin-tailwindcss` last (per its docs), with a parser override for `.astro` files.
|
|
41
|
+
- 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` into this one by hand. Scan takes the child config over the parent's whole file, so a change to base's options is lost here until it is copied.
|
|
43
|
+
- Override `check:format` and `format` to run oxfmt, then Prettier over `**/*.astro`.
|
|
44
|
+
|
|
45
|
+
## ESLint
|
|
46
|
+
|
|
47
|
+
- Override `lint` and `lint:fix` back to `eslint . --max-warnings 0`, since the web layer sets them to oxlint.
|
|
48
|
+
- Install the ESLint packages here (`eslint@^9`, `@eslint/js`, `typescript-eslint`, `eslint-config-prettier`, `eslint-plugin-react-hooks`, `eslint-plugin-vitest`, `globals`), since the web layer no longer ships them. Pin `eslint@^9`: ESLint 10 dropped the `LegacyESLint` shim `typescript-eslint@8` still imports.
|
|
49
|
+
- Expect the web layer's `.oxlintrc.json` in the target. Nothing runs it here.
|
|
40
50
|
|
|
41
51
|
## Development docs (extend)
|
|
42
52
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Runs this folder's verify phases in a fixed order. The phases a stack
|
|
2
2
|
// requires come from scripts/verify.json, which each stack ships and the
|
|
3
|
-
// nearest one wins. Only node: modules, since a target's own
|
|
3
|
+
// nearest one wins. Only node: modules, since a target's own linter and
|
|
4
4
|
// tsc --noEmit read this file with no Bun types.
|
|
5
5
|
import { spawnSync } from 'node:child_process'
|
|
6
6
|
import { accessSync, constants, existsSync, readFileSync } from 'node:fs'
|
|
@@ -10,19 +10,19 @@ per_root = ["scripts/verify.ts", "scripts/verify.json"]
|
|
|
10
10
|
|
|
11
11
|
[dependencies.dev]
|
|
12
12
|
packages = [
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
"oxfmt@0.72.0",
|
|
14
|
+
"husky",
|
|
15
|
+
"@commitlint/cli",
|
|
16
|
+
"@commitlint/config-conventional",
|
|
17
17
|
]
|
|
18
18
|
|
|
19
19
|
[scripts]
|
|
20
|
-
"check:format" = "prettier --check --log-level warn --ignore-path .gitignore --ignore-path .prettierignore ."
|
|
21
|
-
"format" = "prettier --write --log-level warn --ignore-path .gitignore --ignore-path .prettierignore ."
|
|
22
20
|
"prepare" = "husky"
|
|
23
21
|
|
|
24
22
|
[scripts.override]
|
|
25
23
|
"check" = "bun scripts/verify.ts"
|
|
24
|
+
"check:format" = "oxfmt --check ."
|
|
25
|
+
"format" = "oxfmt ."
|
|
26
26
|
|
|
27
27
|
[gitignore]
|
|
28
28
|
"# System" = [".DS_Store"]
|
|
@@ -10,7 +10,7 @@ A repository with several language roots layers this stack once at the root and
|
|
|
10
10
|
|
|
11
11
|
Golden config files live in `tooling/base/configs/` and are copied into the target on `canon tooling sync base . --write`, replacing whatever sits at those paths. They are the source of truth. The reference covers rationale and tradeoffs. Configs show the concrete setup.
|
|
12
12
|
|
|
13
|
-
- `.
|
|
13
|
+
- `.oxfmtrc.json`: `semi: false`, `singleQuote: true`, `printWidth: 80`, and `sortPackageJson: false`, which keep the output Prettier wrote. The `astro` stack repeats these options in its own copy, so copy a change here into it.
|
|
14
14
|
- `commitlint.config.js`: ESM default export extending `@commitlint/config-conventional`. Rules are `header-max-length: 72`, `scope-case: lower-case`, `subject-full-stop: never`, `subject-case` disabled, and a local `no-claude-co-author` rule refusing a `Co-authored-by` trailer that names Claude or Anthropic.
|
|
15
15
|
- `.husky/`: `pre-commit`, `commit-msg`, `pre-push`, `post-merge`, `post-rewrite`.
|
|
16
16
|
- `scripts/verify.ts`: the one verify runner every stack shares, and the command behind `check`. 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. Node modules only, since a target's own lint and typecheck read it.
|
|
@@ -21,7 +21,6 @@ Golden config files live in `tooling/base/configs/` and are copied into the targ
|
|
|
21
21
|
Seeds live in `tooling/base/seeds/`. Sync drops each once on first install and never overwrites it, so a project extends them freely.
|
|
22
22
|
|
|
23
23
|
- `.lintstagedrc`: the glob map below.
|
|
24
|
-
- `.prettierignore`: created empty. Projects add their own entries.
|
|
25
24
|
- `.github/workflows/verify.yml`: runs on pull requests targeting `main` and on `workflow_dispatch`. A project with more than one language root adds its own jobs here.
|
|
26
25
|
- `.github/pull_request_template.md`: `## Summary`, `## Key changes`, `## Technical context`, `## Testing`. A project adds the checklist lines its own review needs.
|
|
27
26
|
- `canon/context/`: extend the `ci` and `development` entries with project-specific commands, workflows, or deploy steps. Canonical rationale stays in this reference.
|
|
@@ -29,9 +28,14 @@ Seeds live in `tooling/base/seeds/`. Sync drops each once on first install and n
|
|
|
29
28
|
## Tool pairing
|
|
30
29
|
|
|
31
30
|
- Runtime: `bun` as package manager and script runner, `bunx` over `npx` for a one-off executable.
|
|
32
|
-
- Formatting:
|
|
31
|
+
- Formatting: oxfmt for what it parses, pinned to an exact version so an upgrade arrives as a reviewed diff. oxfmt has no shell parser, so shell formatting is the `shell` stack's.
|
|
33
32
|
- Commits: commitlint against conventional commits, wired through the husky `commit-msg` hook. Format is `<type>(<scope>): <subject>` in imperative mood with no trailing period.
|
|
34
|
-
- Dev dependencies: `
|
|
33
|
+
- Dev dependencies: `oxfmt`, `husky`, `@commitlint/cli`, `@commitlint/config-conventional`. Install via `bun add -D`.
|
|
34
|
+
|
|
35
|
+
## oxfmt
|
|
36
|
+
|
|
37
|
+
- 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
|
+
- Delete a leftover `.prettierrc` and run `bun remove prettier` by hand after the first sync onto oxfmt. Sync deletes nothing a stack stopped shipping, and the script overrides already stop Prettier from running.
|
|
35
39
|
|
|
36
40
|
## File layout
|
|
37
41
|
|
|
@@ -50,7 +54,7 @@ Seeds live in `tooling/base/seeds/`. Sync drops each once on first install and n
|
|
|
50
54
|
|
|
51
55
|
Seeded baseline globs:
|
|
52
56
|
|
|
53
|
-
- `**/*.{json,md
|
|
57
|
+
- `**/*.{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.
|
|
54
58
|
- `**/*.md` runs `canon indexes regen`
|
|
55
59
|
|
|
56
60
|
## CI
|
|
@@ -69,8 +73,7 @@ Seeded baseline globs:
|
|
|
69
73
|
|
|
70
74
|
Sticky negative knowledge. Do not relearn.
|
|
71
75
|
|
|
72
|
-
- Do NOT
|
|
73
|
-
- Do NOT omit `--log-level warn` from a prettier invocation. The default prints a line per unchanged file.
|
|
76
|
+
- Do NOT pass `--ignore-path` to oxfmt. It replaces the default `.gitignore` and `.prettierignore` pair rather than adding to it.
|
|
74
77
|
- 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.
|
|
75
78
|
|
|
76
79
|
## CLI
|
|
@@ -78,8 +81,8 @@ Sticky negative knowledge. Do not relearn.
|
|
|
78
81
|
| Script | What it does |
|
|
79
82
|
| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
80
83
|
| `bun run check` | Gates a checkout through `scripts/verify.ts` and writes nothing. Honors `VERIFY_NESTED=true` to suppress timeline boundaries when another script calls it. |
|
|
81
|
-
| `bun run check:format` | Asserts
|
|
82
|
-
| `bun run format` | Writes
|
|
84
|
+
| `bun run check:format` | Asserts oxfmt formatting without writing |
|
|
85
|
+
| `bun run format` | Writes oxfmt formatting in place |
|
|
83
86
|
| `bun run prepare` | Initializes husky hooks, run automatically on `bun install` |
|
|
84
87
|
|
|
85
88
|
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.
|
|
@@ -22,9 +22,9 @@ Owns the GitHub Actions workflow that gates a merge: which events start a run, a
|
|
|
22
22
|
|
|
23
23
|
Defined in `.github/workflows/verify.yml`. All jobs must pass before merge.
|
|
24
24
|
|
|
25
|
-
| Check | Command | What it asserts
|
|
26
|
-
| ------ | ---------------------- |
|
|
27
|
-
| Format | `bun run check:format` |
|
|
25
|
+
| Check | Command | What it asserts |
|
|
26
|
+
| ------ | ---------------------- | --------------- |
|
|
27
|
+
| Format | `bun run check:format` | oxfmt is clean |
|
|
28
28
|
|
|
29
29
|
### Running CI locally
|
|
30
30
|
|
|
@@ -24,7 +24,7 @@ Owns how the project runs on a developer machine: installing the toolchain, the
|
|
|
24
24
|
| Command | Purpose |
|
|
25
25
|
| ---------------- | ---------------------------------------------------------------------------------------- |
|
|
26
26
|
| `bun run check` | Gates locally and writes nothing. Fails on formatting drift until `bun run format` runs. |
|
|
27
|
-
| `bun run format` | Auto-fix
|
|
27
|
+
| `bun run format` | Auto-fix oxfmt formatting. |
|
|
28
28
|
|
|
29
29
|
### Shell scripts
|
|
30
30
|
|
|
@@ -32,6 +32,6 @@ All `.sh` files live under `scripts/`. Do not place shell scripts outside `scrip
|
|
|
32
32
|
|
|
33
33
|
### Husky hooks
|
|
34
34
|
|
|
35
|
-
- `pre-commit` runs `lint-staged` (
|
|
35
|
+
- `pre-commit` runs `lint-staged` (oxfmt on staged files).
|
|
36
36
|
- `commit-msg` runs `commitlint` against the conventional commit format.
|
|
37
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.
|
|
@@ -21,4 +21,8 @@ scaffold = ""
|
|
|
21
21
|
# array no longer tells it to ignore them, so the first sign is a memory file or
|
|
22
22
|
# a task board appearing in someone's commit.
|
|
23
23
|
[gitignore]
|
|
24
|
-
"# Claude" = [
|
|
24
|
+
"# Claude" = [
|
|
25
|
+
".canon/",
|
|
26
|
+
".claude/worktrees/",
|
|
27
|
+
".claude/.tmp/", # canon-keep-record-root
|
|
28
|
+
]
|
package/tooling/go/reference.md
CHANGED
|
@@ -10,11 +10,11 @@ The go stack covers Go modules linted and formatted by `golangci-lint` v2, type-
|
|
|
10
10
|
|
|
11
11
|
1. Scaffold with `go mod init <module-path>`. The module path is the project's own identity, so this stack never ships a `go.mod`.
|
|
12
12
|
2. Keep `./...` out of `node_modules`: `go mod edit -ignore=./node_modules`. At a root sync the base layer installs `node_modules/` inside the module, and a package there can ship a `.go` file that `go vet`, `go test`, and `golangci-lint` would otherwise walk. The directive needs Go 1.25 or later.
|
|
13
|
-
3. Seed `package.json` so the base layer's bun-side tools (husky,
|
|
13
|
+
3. Seed `package.json` so the base layer's bun-side tools (husky, oxfmt, commitlint) have a target to install into: `bun init -y`.
|
|
14
14
|
4. Install base tooling: `canon tooling sync base . --write`
|
|
15
15
|
5. Install go tooling: `canon tooling sync go . --write`
|
|
16
16
|
6. Install the linter: `go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest`. The manifest declares no `[dependencies.dev]`, because manifest injection runs `bun add -D`, which cannot install a Go tool. This step stays manual.
|
|
17
|
-
7. Run `bun run lint:fix` then `bun run check`.
|
|
17
|
+
7. Run `bun run lint:fix`, then `bun run format`, then `bun run check`.
|
|
18
18
|
|
|
19
19
|
The go stack also ships `smoke_test.go` at the module root as a copy-once seed. A module with no `.go` file fails `golangci-lint run`, `go vet`, and `go test` on an empty package list, so the seed is what lets `bun run check` pass on a fresh scaffold. It declares `package main`, so delete or rename it once the root package takes another name, or the build reports two packages in one folder.
|
|
20
20
|
|