@erclx/canon 5.35.0 → 5.36.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 +11 -11
- package/claude/.claude-plugin/plugin.json +1 -1
- package/claude/skills/canon-cli/REQUIREMENT.md +2 -2
- package/claude/skills/canon-cli/SKILL.md +2 -1
- package/claude/skills/canvas-board/REQUIREMENT.md +1 -1
- package/claude/skills/canvas-board/SKILL.md +2 -2
- package/claude/skills/canvas-board/references/design.md +53 -36
- package/claude/skills/codebase-layout/REQUIREMENT.md +1 -1
- package/claude/skills/codebase-layout/SKILL.md +2 -2
- package/claude/skills/create-rule/REQUIREMENT.md +6 -8
- package/claude/skills/create-rule/SKILL.md +8 -8
- package/claude/skills/create-rule/references/rule.md +11 -23
- package/claude/skills/create-skill/references/skill.md +1 -1
- package/claude/skills/design-taste/REQUIREMENT.md +6 -6
- package/claude/skills/design-taste/SKILL.md +2 -2
- package/claude/skills/design-taste/references/craft.md +1 -1
- package/claude/skills/design-taste/references/preflight.md +2 -2
- package/claude/skills/design-taste/references/tells.md +1 -1
- package/claude/skills/draft-identity/SKILL.md +3 -2
- package/claude/skills/markdown-craft/references/markdown.md +3 -2
- package/claude/skills/plan-feature/SKILL.md +1 -1
- package/claude/skills/review-craft/references/ui.md +1 -1
- package/claude/skills/test-craft/SKILL.md +2 -2
- package/claude/skills/ux-audit/REQUIREMENT.md +1 -1
- package/claude/skills/ux-audit/SKILL.md +2 -2
- package/claude/skills/ux-measure/REQUIREMENT.md +1 -1
- package/claude/skills/write-human/references/machine-tells.md +12 -0
- package/docs/agents/commands.md +12 -9
- package/docs/agents/comments.md +1 -1
- package/docs/agents/gov-detect.md +1 -1
- package/docs/agents/index.md +1 -1
- package/docs/agents/install-and-sync.md +4 -4
- package/docs/agents/markdown-audit.md +12 -0
- package/docs/agents/output-shape.md +1 -1
- package/docs/agents/overview.md +2 -2
- package/docs/agents/rule-citations.md +5 -10
- package/docs/agents/scripting.md +1 -1
- package/docs/target/projects.md +2 -2
- package/docs/target/scenarios.md +5 -5
- package/docs/workflow/ai-workflow.md +1 -1
- package/docs/workflow/visual-design-workflow.md +3 -3
- package/governance/detect.toml +32 -32
- package/governance/renames.toml +44 -0
- package/governance/rules/lib/{305-e2e-reliability.md → e2e-reliability.md} +1 -1
- package/governance/rules/lib/{306-test-scope.md → test-scope.md} +2 -2
- package/governance/rules/ui/{410-a11y.md → a11y.md} +1 -1
- package/governance/rules/ui/{440-surface-capture.md → surface-capture.md} +3 -3
- package/governance/stacks/base.toml +6 -6
- package/governance/stacks/go.toml +1 -1
- package/governance/stacks/node.toml +1 -1
- package/governance/stacks/php.toml +1 -1
- package/governance/stacks/python.toml +1 -1
- package/package.json +1 -1
- package/scripts/core/install-check.sh +2 -7
- package/src/capture/frame-values.ts +3 -6
- package/src/cli/help-all.ts +96 -0
- package/src/cli/help.ts +27 -26
- package/src/cli.ts +8 -1
- package/src/commands/claude.ts +29 -0
- package/src/commands/design.ts +22 -1
- package/src/commands/gov.ts +25 -3
- package/src/commands/init.ts +10 -1
- package/src/commands/markdown.ts +96 -3
- package/src/commands/records.ts +1 -1
- package/src/commands/sync.ts +11 -0
- package/src/commands/tooling.ts +8 -0
- package/src/design/budget.ts +14 -10
- package/src/design/document.ts +5 -5
- package/src/design/parse.ts +21 -4
- package/src/design/render.ts +3 -3
- package/src/gate/measures.ts +1 -1
- package/src/gov/citations.ts +10 -33
- package/src/gov/stacks.ts +3 -3
- package/src/inventory/subjects.ts +1 -1
- package/src/markdown/ceiling.ts +6 -1
- package/src/markdown/colons.ts +86 -0
- package/src/markdown/gate.ts +15 -0
- package/src/markdown/heading-case.ts +74 -0
- package/src/shipped/references.ts +14 -14
- package/src/sync/copy.ts +4 -5
- package/tooling/astro/manifest.toml +0 -1
- package/tooling/astro/reference.md +0 -8
- package/tooling/base/configs/scripts/verify.json +1 -1
- package/tooling/base/configs/scripts/verify.ts +22 -6
- package/tooling/base/manifest.toml +6 -5
- package/tooling/base/reference.md +15 -22
- package/tooling/base/seeds/.github/workflows/verify.yml +0 -6
- package/tooling/base/seeds/.lintstagedrc +1 -2
- package/tooling/base/seeds/canon/context/ci.md +4 -5
- package/tooling/base/seeds/canon/context/development.md +2 -2
- package/tooling/claude/seeds/.claude/hooks/scratch-guard.sh +1 -1
- package/tooling/claude/seeds/.claude/hooks/standards-audit.sh +27 -2
- package/tooling/claude/seeds/CLAUDE.md +1 -1
- package/tooling/claude/seeds/canon/DESIGN.md +24 -37
- package/tooling/shell/configs/.github/workflows/shell.yml +30 -0
- package/tooling/shell/manifest.toml +12 -0
- package/tooling/shell/reference.md +46 -0
- package/tooling/vite-react/manifest.toml +0 -1
- package/tooling/vite-react/reference.md +0 -8
- package/tooling/web/configs/.github/workflows/readme-screenshot.yml +3 -5
- package/tooling/web/configs/.github/workflows/verify.yml +0 -6
- package/tooling/web/manifest.toml +1 -1
- package/tooling/web/reference.md +3 -3
- package/tooling/base/configs/scripts/verify.sh +0 -2
- /package/governance/rules/canon/{606-git.md → git.md} +0 -0
- /package/governance/rules/canon/{611-indexes.md → indexes.md} +0 -0
- /package/governance/rules/canon/{604-scratch.md → scratch.md} +0 -0
- /package/governance/rules/canon/{602-tasks.md → task-scope.md} +0 -0
- /package/governance/rules/canon/{605-worktrees.md → worktrees.md} +0 -0
- /package/governance/rules/claude/{566-output.md → output.md} +0 -0
- /package/governance/rules/code/{000-code.md → routing.md} +0 -0
- /package/governance/rules/code/{010-testing.md → testing.md} +0 -0
- /package/governance/rules/framework/{210-astro.md → astro.md} +0 -0
- /package/governance/rules/framework/{220-fastapi.md → fastapi.md} +0 -0
- /package/governance/rules/framework/{230-nextjs.md → nextjs.md} +0 -0
- /package/governance/rules/framework/{200-react.md → react.md} +0 -0
- /package/governance/rules/framework/{260-shadcn.md → shadcn.md} +0 -0
- /package/governance/rules/framework/{250-tailwind.md → tailwind.md} +0 -0
- /package/governance/rules/lang/{130-go.md → go.md} +0 -0
- /package/governance/rules/lang/{140-php.md → php.md} +0 -0
- /package/governance/rules/lang/{110-python.md → python.md} +0 -0
- /package/governance/rules/lang/{100-typescript.md → typescript.md} +0 -0
- /package/governance/rules/lib/{370-database.md → database.md} +0 -0
- /package/governance/rules/lib/{340-pydantic.md → pydantic.md} +0 -0
- /package/governance/rules/lib/{360-security-server.md → security-server.md} +0 -0
- /package/governance/rules/lib/{350-security-web.md → security-web.md} +0 -0
- /package/governance/rules/lib/{320-tanstack-query.md → tanstack-query.md} +0 -0
- /package/governance/rules/lib/{335-testing-go.md → testing-go.md} +0 -0
- /package/governance/rules/lib/{336-testing-php.md → testing-php.md} +0 -0
- /package/governance/rules/lib/{330-testing-py.md → testing-py.md} +0 -0
- /package/governance/rules/lib/{300-testing-ts.md → testing-ts.md} +0 -0
- /package/governance/rules/lib/{310-zod.md → zod.md} +0 -0
- /package/governance/rules/tooling/{700-ci-workflow.md → ci-workflow.md} +0 -0
- /package/governance/rules/tooling/{790-code-comments.md → code-comments.md} +0 -0
- /package/governance/rules/tooling/{780-config-comments.md → config-comments.md} +0 -0
- /package/governance/rules/tooling/{775-dependencies.md → dependencies.md} +0 -0
- /package/governance/rules/ui/{480-canvas.md → canvas.md} +0 -0
- /package/governance/rules/ui/{400-ui.md → copy.md} +0 -0
- /package/governance/rules/ui/{460-design-taste.md → design-taste.md} +0 -0
- /package/governance/rules/ui/{420-forms.md → forms.md} +0 -0
- /package/governance/rules/ui/{465-interface-casing.md → interface-casing.md} +0 -0
- /package/governance/rules/ui/{450-link-behavior.md → link-behavior.md} +0 -0
- /package/governance/rules/ui/{470-motion.md → motion.md} +0 -0
- /package/governance/rules/ui/{430-ux-completeness.md → ux-completeness.md} +0 -0
- /package/tooling/{base → shell}/configs/.editorconfig +0 -0
- /package/tooling/{base → shell}/configs/.shellcheckrc +0 -0
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { bodyLines, maskDisplayed } from '@/markdown/scan'
|
|
2
|
+
|
|
3
|
+
export interface HeadingCaseFinding {
|
|
4
|
+
readonly line: number
|
|
5
|
+
/** The heading or title as written, markers removed. */
|
|
6
|
+
readonly text: string
|
|
7
|
+
/** Every word after the first that opens on a capital. */
|
|
8
|
+
readonly words: readonly string[]
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const HEADING = /^#{1,6}\s+(.*?)(?:\s+#+)?\s*$/
|
|
12
|
+
const FRONTMATTER = /^---\n([\s\S]*?)\n---\n?/
|
|
13
|
+
const TITLE = /^title:\s*(["']?)(.*?)\1\s*$/
|
|
14
|
+
const CODE_SPAN = /(`+)(?:(?!\1).)*\1/g
|
|
15
|
+
const CAPITALIZED = /^[A-Z][a-z]/
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* The words of `text` that break sentence case.
|
|
19
|
+
*
|
|
20
|
+
* A code span stands for one lowercase word, so a heading opening on one
|
|
21
|
+
* counts that span as its first word. The first word with a letter in it is
|
|
22
|
+
* exempt, which keeps a leading numeral from promoting the next word.
|
|
23
|
+
*/
|
|
24
|
+
function capitalizedAfterFirst(text: string): string[] {
|
|
25
|
+
const masked = maskDisplayed(text.replace(CODE_SPAN, 'code'))
|
|
26
|
+
.replace(/\s+/g, ' ')
|
|
27
|
+
.trim()
|
|
28
|
+
const words = masked === '' ? [] : masked.split(' ')
|
|
29
|
+
const flagged: string[] = []
|
|
30
|
+
let seenFirst = false
|
|
31
|
+
|
|
32
|
+
for (const raw of words) {
|
|
33
|
+
const word = raw.replace(/^[^\p{L}]+/u, '')
|
|
34
|
+
if (!/\p{L}/u.test(raw)) continue
|
|
35
|
+
if (!seenFirst) {
|
|
36
|
+
seenFirst = true
|
|
37
|
+
continue
|
|
38
|
+
}
|
|
39
|
+
if (CAPITALIZED.test(word)) flagged.push(word)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return flagged
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Finds a heading or a frontmatter `title` carrying a capitalized word after
|
|
47
|
+
* its first, outside code spans and all-caps tokens.
|
|
48
|
+
*
|
|
49
|
+
* A proper noun is the one case it cannot tell from title case, so the finding
|
|
50
|
+
* is a warning and no names list backs it.
|
|
51
|
+
*/
|
|
52
|
+
export function findHeadingCase(source: string): HeadingCaseFinding[] {
|
|
53
|
+
const found: HeadingCaseFinding[] = []
|
|
54
|
+
const check = (line: number, text: string): void => {
|
|
55
|
+
const words = capitalizedAfterFirst(text)
|
|
56
|
+
if (words.length > 0) found.push({ line, text, words })
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const frontmatter = FRONTMATTER.exec(source)
|
|
60
|
+
if (frontmatter) {
|
|
61
|
+
for (const [index, text] of frontmatter[1].split('\n').entries()) {
|
|
62
|
+
const title = TITLE.exec(text)
|
|
63
|
+
if (title) check(index + 2, title[2])
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
for (const line of bodyLines(source)) {
|
|
68
|
+
if (line.fenced) continue
|
|
69
|
+
const heading = HEADING.exec(line.text)
|
|
70
|
+
if (heading) check(line.number, heading[1])
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return found
|
|
74
|
+
}
|
|
@@ -54,7 +54,7 @@ export const REFERENCE_MARKER = 'canon-allow-reference'
|
|
|
54
54
|
* already ships `anthropics/claude-code#58345` in exactly that form.
|
|
55
55
|
*
|
|
56
56
|
* The trailing boundary is what the first shape of this pattern lacked, and it
|
|
57
|
-
* costs a live finding to omit. `governance/rules/framework/
|
|
57
|
+
* costs a live finding to omit. `governance/rules/framework/tailwind.md`
|
|
58
58
|
* writes `bg-[#316ff6]`, a Tailwind arbitrary hex color, which an unbounded
|
|
59
59
|
* `#[0-9]+` reads as pull request 316. Requiring a non-word character after the
|
|
60
60
|
* digits rejects it and rejects nothing real, since a citation is always
|
|
@@ -171,20 +171,20 @@ const STANDARDS_CALL = /\bcanon standards(?![\w-])/g
|
|
|
171
171
|
const PHASE_LABEL = /\bv\d+\.\d+(?!\.\d)\b/g
|
|
172
172
|
|
|
173
173
|
/**
|
|
174
|
-
* A `.claude/rules/<segments>/<
|
|
175
|
-
* `claude/skills/`, the installed-path spelling `
|
|
176
|
-
*
|
|
174
|
+
* A `.claude/rules/<segments>/<slug>.md` citation from a body under
|
|
175
|
+
* `claude/skills/`, the installed-path spelling `authoring-layout.md` bans a
|
|
176
|
+
* shipped skill body from citing as authority for its own behavior.
|
|
177
177
|
*
|
|
178
|
-
* Anchored on
|
|
179
|
-
*
|
|
180
|
-
*
|
|
181
|
-
*
|
|
182
|
-
*
|
|
183
|
-
* (`
|
|
184
|
-
*
|
|
185
|
-
*
|
|
178
|
+
* Anchored on a trailing `.md` file rather than on the bare `.claude/rules/`
|
|
179
|
+
* prefix, which is what keeps a folder mention, such as `create-rule`,
|
|
180
|
+
* `target-check`, and `target-setup` write, from matching: it ends in `/` or
|
|
181
|
+
* names no `.md`. The segments between `rules/` and the file admit both a
|
|
182
|
+
* governance-namespace path (`canon/canon/scratch.md`) and a
|
|
183
|
+
* project-namespace one (`project/<subdir>/<slug>.md`) without distinguishing
|
|
184
|
+
* them, since either shape is the same broken citation, and the placeholder
|
|
185
|
+
* check spares the bracketed illustration.
|
|
186
186
|
*/
|
|
187
|
-
const RULE_PATH = /(?<![\w./-])\.claude\/rules\/[^\s`)\]]
|
|
187
|
+
const RULE_PATH = /(?<![\w./-])\.claude\/rules\/[^\s`)\]]*?[\w-]+\.md\b/g
|
|
188
188
|
|
|
189
189
|
export interface ShippedReference {
|
|
190
190
|
readonly file: string
|
|
@@ -246,7 +246,7 @@ function isDocsPathReportable(
|
|
|
246
246
|
* Whether `file` sits in the one corpus `RULE_PATH` gates: a shipped skill
|
|
247
247
|
* body, minus its own `REQUIREMENT.md`.
|
|
248
248
|
*
|
|
249
|
-
* The ban lives in `
|
|
249
|
+
* The ban lives in `authoring-layout.md`, stated for one reader: a
|
|
250
250
|
* session loading the `SKILL.md` body a target actually receives.
|
|
251
251
|
* `REQUIREMENT.md` is excluded for the reason that file states there, since a
|
|
252
252
|
* maintainer or an audit command reads it rather than a session loading it,
|
package/src/sync/copy.ts
CHANGED
|
@@ -4,11 +4,10 @@ import { dirname } from 'node:path'
|
|
|
4
4
|
/**
|
|
5
5
|
* Copies with `cp` semantics. Two behaviors matter and neither is the default
|
|
6
6
|
* in the obvious alternatives. `Bun.write` drops the executable bit, which
|
|
7
|
-
* would land husky hooks
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* alone.
|
|
7
|
+
* would land husky hooks non-executable. Plain `copyFile` imposes the source
|
|
8
|
+
* mode on an existing destination, so a file the target deliberately
|
|
9
|
+
* restricted to 600, or an earlier stack set executable, comes back at the
|
|
10
|
+
* source's mode. `cp` leaves an existing destination's mode alone.
|
|
12
11
|
*
|
|
13
12
|
* Every domain that writes into a target routes through here. The tooling
|
|
14
13
|
* inject path found the trap first, and the sync engine inherits it for every
|
|
@@ -20,7 +20,6 @@ packages = [
|
|
|
20
20
|
"preview" = "WORKTREE_PORT_OFFSET=$(bash scripts/worktree-port.sh) && export WORKTREE_PORT_OFFSET && astro preview"
|
|
21
21
|
"astro" = "astro"
|
|
22
22
|
"typecheck" = "astro check"
|
|
23
|
-
"setup" = "./scripts/setup.sh"
|
|
24
23
|
"card:dev" = "WORKTREE_PORT_OFFSET=$(bash scripts/worktree-port.sh) && export WORKTREE_PORT_OFFSET && astro dev --config card.config.mjs"
|
|
25
24
|
"card:check" = "bash scripts/check-card-exclusion.sh"
|
|
26
25
|
|
|
@@ -34,14 +34,6 @@ Unit-test React island components (`.tsx`) only. Do not test `.astro` files. Pag
|
|
|
34
34
|
|
|
35
35
|
Vitest is pinned to `^3` in the web manifest. Vitest `^4` bundles rolldown-vite (vite@8), which ignores the esbuild JSX config in `@vitejs/plugin-react` and breaks `.tsx` tests, plus emits deprecation warnings. Unpin once `@astrojs/react` ships a rolldown-aware plugin (or astro's bundled vite reaches 8).
|
|
36
36
|
|
|
37
|
-
## Setup script
|
|
38
|
-
|
|
39
|
-
- File: `scripts/setup.sh`. Destructive: deletes `.git` and self-removes after running. Run once immediately after scaffolding.
|
|
40
|
-
- Prompt for project name, normalize to kebab-case, derive title-cased display name.
|
|
41
|
-
- Update `package.json` name and version, inject verify/clean/update scripts, remove setup.
|
|
42
|
-
- Update `astro.config.mjs`: set `site` if provided.
|
|
43
|
-
- Update `<title>` and `<meta name="description">` in the default layout.
|
|
44
|
-
|
|
45
37
|
## Prettier (extend)
|
|
46
38
|
|
|
47
39
|
Add `prettier-plugin-astro` first in plugins, then `prettier-plugin-tailwindcss` last (per its docs). Add a parser override for `.astro` files.
|
|
@@ -27,8 +27,12 @@ interface Phase {
|
|
|
27
27
|
readonly title: string
|
|
28
28
|
readonly failed: string
|
|
29
29
|
readonly passed: string
|
|
30
|
+
readonly tools?: readonly Tool[]
|
|
30
31
|
}
|
|
31
32
|
|
|
33
|
+
const SHELL_TOOLS_HINT =
|
|
34
|
+
"Install it with 'sudo apt-get install shfmt shellcheck' or 'brew install shfmt shellcheck'"
|
|
35
|
+
|
|
32
36
|
const PHASES: readonly Phase[] = [
|
|
33
37
|
{
|
|
34
38
|
script: 'typecheck',
|
|
@@ -59,6 +63,10 @@ const PHASES: readonly Phase[] = [
|
|
|
59
63
|
title: 'Shell',
|
|
60
64
|
failed: 'Shell check failed',
|
|
61
65
|
passed: 'Shell check passed',
|
|
66
|
+
tools: [
|
|
67
|
+
{ name: 'shfmt', hint: SHELL_TOOLS_HINT },
|
|
68
|
+
{ name: 'shellcheck', hint: SHELL_TOOLS_HINT },
|
|
69
|
+
],
|
|
62
70
|
},
|
|
63
71
|
{
|
|
64
72
|
script: 'test:run',
|
|
@@ -77,7 +85,11 @@ const PHASES: readonly Phase[] = [
|
|
|
77
85
|
// A subfolder synced with --skip base declares none of these, since the
|
|
78
86
|
// repository root owns them, so a list that does not require one runs it only
|
|
79
87
|
// where the folder declares it.
|
|
80
|
-
const BASE_PHASES = new Set(['format', 'check:format'
|
|
88
|
+
const BASE_PHASES = new Set(['format', 'check:format'])
|
|
89
|
+
|
|
90
|
+
// An add-on stack declares these, keyed to the stack that ships each, so a
|
|
91
|
+
// folder that never synced the add-on skips the phase rather than failing it.
|
|
92
|
+
const ADD_ON_PHASES = new Map([['check:shell', 'shell']])
|
|
81
93
|
|
|
82
94
|
const PHASE_LIST = 'scripts/verify.json'
|
|
83
95
|
const CARD_EXCLUSION = 'scripts/check-card-exclusion.sh'
|
|
@@ -226,8 +238,8 @@ function readDeclaredScripts(): Set<string> {
|
|
|
226
238
|
)
|
|
227
239
|
}
|
|
228
240
|
|
|
229
|
-
function checkTools(
|
|
230
|
-
for (const tool of
|
|
241
|
+
function checkTools(tools: readonly Tool[]): void {
|
|
242
|
+
for (const tool of tools) {
|
|
231
243
|
if (onPath(tool.name)) continue
|
|
232
244
|
fail(
|
|
233
245
|
tool.hint === undefined
|
|
@@ -250,7 +262,8 @@ function runPhase(
|
|
|
250
262
|
declared: Set<string>,
|
|
251
263
|
): void {
|
|
252
264
|
const isBase = BASE_PHASES.has(phase.script)
|
|
253
|
-
|
|
265
|
+
const addOn = ADD_ON_PHASES.get(phase.script)
|
|
266
|
+
if (!required && !isBase && addOn === undefined) return
|
|
254
267
|
|
|
255
268
|
logStep(phase.title)
|
|
256
269
|
if (!declared.has(phase.script)) {
|
|
@@ -260,10 +273,13 @@ function runPhase(
|
|
|
260
273
|
)
|
|
261
274
|
}
|
|
262
275
|
logSkip(
|
|
263
|
-
|
|
276
|
+
addOn === undefined
|
|
277
|
+
? `Skipped: ${phase.script} is not declared here, so the repository root runs it`
|
|
278
|
+
: `Skipped: ${phase.script} is not declared here. Run 'canon tooling sync ${addOn} . --write' to add it`,
|
|
264
279
|
)
|
|
265
280
|
return
|
|
266
281
|
}
|
|
282
|
+
checkTools(phase.tools ?? [])
|
|
267
283
|
runCheck(`bun run ${phase.script}`, phase.failed)
|
|
268
284
|
logInfo(phase.passed)
|
|
269
285
|
}
|
|
@@ -332,7 +348,7 @@ function checkMarkdownBans(): void {
|
|
|
332
348
|
function main(): void {
|
|
333
349
|
const declared = readDeclaredScripts()
|
|
334
350
|
const list = readPhaseList()
|
|
335
|
-
checkTools(list)
|
|
351
|
+
checkTools(list.tools)
|
|
336
352
|
checkPreconditions(list)
|
|
337
353
|
|
|
338
354
|
if (!isNested) print(`${GREY}┌${NC}`)
|
|
@@ -6,7 +6,7 @@ scaffold = ""
|
|
|
6
6
|
|
|
7
7
|
[sync]
|
|
8
8
|
source = "configs"
|
|
9
|
-
per_root = ["scripts/verify.
|
|
9
|
+
per_root = ["scripts/verify.ts", "scripts/verify.json"]
|
|
10
10
|
|
|
11
11
|
[dependencies.dev]
|
|
12
12
|
packages = [
|
|
@@ -17,11 +17,12 @@ packages = [
|
|
|
17
17
|
]
|
|
18
18
|
|
|
19
19
|
[scripts]
|
|
20
|
-
"check:format" = "prettier --check --log-level warn --ignore-path .gitignore --ignore-path .prettierignore .
|
|
21
|
-
"
|
|
22
|
-
"format" = "prettier --write --log-level warn --ignore-path .gitignore --ignore-path .prettierignore . && find scripts -name '*.sh' -exec shfmt --write --indent 2 {} +"
|
|
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 ."
|
|
23
22
|
"prepare" = "husky"
|
|
24
|
-
|
|
23
|
+
|
|
24
|
+
[scripts.override]
|
|
25
|
+
"check" = "bun scripts/verify.ts"
|
|
25
26
|
|
|
26
27
|
[gitignore]
|
|
27
28
|
"# System" = [".DS_Store"]
|
|
@@ -2,7 +2,7 @@
|
|
|
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,
|
|
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, which a project syncs once it writes shell.
|
|
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
|
|
|
@@ -11,13 +11,10 @@ A repository with several language roots layers this stack once at the root and
|
|
|
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
|
- `.prettierrc`: `semi: false`, `singleQuote: true`, plus a parser override per non-standard extension (`.mdx` to `markdown`).
|
|
14
|
-
- `.shellcheckrc`: `external-sources=true`. Required for shellcheck to follow `source` directives.
|
|
15
|
-
- `.editorconfig`: `root = true`, with an `[*.sh]` block setting `indent_style = space` and `indent_size = 2`.
|
|
16
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.
|
|
17
15
|
- `.husky/`: `pre-commit`, `commit-msg`, `pre-push`, `post-merge`, `post-rewrite`.
|
|
18
|
-
- `scripts/verify.
|
|
19
|
-
- `scripts/verify.
|
|
20
|
-
- `scripts/verify.json`: the phases a stack requires, plus any tools and preconditions it probes first. The nearest stack's copy wins. 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. Base requires its three phases, so a base-only target still fails on a missing one. These three files are base's `[sync] per_root` set, which a `--skip base` subfolder still receives.
|
|
16
|
+
- `scripts/verify.ts`: the one verify runner every stack shares, and the command behind `check`. It runs typecheck, lint, format, 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.
|
|
17
|
+
- `scripts/verify.json`: the phases a stack requires, plus any tools and preconditions it probes first. The nearest stack's copy wins. 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. Base requires its two phases, so a base-only target still fails on a missing one. The shell phase runs only where `package.json` declares `check:shell`, which the `shell` stack adds, and probes for `shfmt` and `shellcheck` first. These two files are base's `[sync] per_root` set, which a `--skip base` subfolder still receives.
|
|
21
18
|
|
|
22
19
|
## What ships as user-owned seeds
|
|
23
20
|
|
|
@@ -32,14 +29,12 @@ Seeds live in `tooling/base/seeds/`. Sync drops each once on first install and n
|
|
|
32
29
|
## Tool pairing
|
|
33
30
|
|
|
34
31
|
- Runtime: `bun` as package manager and script runner, `bunx` over `npx` for a one-off executable.
|
|
35
|
-
- Formatting: Prettier for what it parses
|
|
36
|
-
- Shell: shfmt formats and shellcheck lints at warning severity. Both take their files from `find scripts -name '*.sh'`, since `scripts/` can mix shell with TypeScript and shellcheck has no directory mode. A shell script with a shebang and no `.sh` extension is covered by neither, so give it the extension.
|
|
32
|
+
- Formatting: Prettier for what it parses. Prettier has no shell parser, so shell formatting is the `shell` stack's.
|
|
37
33
|
- Commits: commitlint against conventional commits, wired through the husky `commit-msg` hook. Format is `<type>(<scope>): <subject>` in imperative mood with no trailing period.
|
|
38
34
|
- Dev dependencies: `prettier`, `husky`, `@commitlint/cli`, `@commitlint/config-conventional`. Install via `bun add -D`.
|
|
39
35
|
|
|
40
36
|
## File layout
|
|
41
37
|
|
|
42
|
-
- All shell scripts live in `scripts/`. Do not place a `.sh` file outside it.
|
|
43
38
|
- Hooks live in `.husky/`, seeded context docs in `canon/context/`.
|
|
44
39
|
- The `canon/context/` location matches the three-tier context model: project-wide invariants in `CLAUDE.md` and `canon/REQUIREMENTS.md`, path-scoped rules in `.claude/rules/`, and on-demand domain narrative in `canon/context/`. Indexes stay opt-in.
|
|
45
40
|
|
|
@@ -57,11 +52,10 @@ Seeded baseline globs:
|
|
|
57
52
|
|
|
58
53
|
- `**/*.{json,md,mdc}` runs `prettier --write --ignore-path .gitignore --ignore-path .prettierignore`
|
|
59
54
|
- `**/*.md` runs `canon indexes regen`
|
|
60
|
-
- `**/*.sh` runs `shfmt --write --indent 2` then `shellcheck --severity=warning`
|
|
61
55
|
|
|
62
56
|
## CI
|
|
63
57
|
|
|
64
|
-
- Steps: checkout, setup Bun at latest, `bun install --frozen-lockfile`,
|
|
58
|
+
- Steps: checkout, setup Bun at latest, `bun install --frozen-lockfile`, then `check:format`.
|
|
65
59
|
- CI asserts and never writes. Format must be clean before push.
|
|
66
60
|
|
|
67
61
|
## Gitignore
|
|
@@ -78,19 +72,18 @@ Sticky negative knowledge. Do not relearn.
|
|
|
78
72
|
- Do NOT invoke prettier without both `--ignore-path .gitignore --ignore-path .prettierignore`. Passing one drops the other, since the flag replaces the default rather than adding to it.
|
|
79
73
|
- Do NOT omit `--log-level warn` from a prettier invocation. The default prints a line per unchanged file.
|
|
80
74
|
- 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.
|
|
81
|
-
- Do NOT drop the `[*.sh]` block from `.editorconfig` because shfmt already sets indentation. The editor and shfmt then disagree and produce spurious git diffs.
|
|
82
|
-
- Do NOT copy lint-staged's `**/*.sh` glob into a package.json script. lint-staged expands its own globs and passes matched files as arguments, which a bare shell script does not.
|
|
83
75
|
|
|
84
76
|
## CLI
|
|
85
77
|
|
|
86
|
-
| Script | What it does
|
|
87
|
-
| ---------------------- |
|
|
88
|
-
| `bun run check` | Repairs a checkout
|
|
89
|
-
| `bun run check:format` | Asserts prettier
|
|
90
|
-
| `bun run
|
|
91
|
-
| `bun run
|
|
92
|
-
| `bun run prepare` | Initializes husky hooks, run automatically on `bun install` |
|
|
78
|
+
| Script | What it does |
|
|
79
|
+
| ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
|
|
80
|
+
| `bun run check` | Repairs a checkout through `scripts/verify.ts`. Honors `VERIFY_NESTED=true` to suppress timeline boundaries when another script calls it. |
|
|
81
|
+
| `bun run check:format` | Asserts prettier formatting without writing |
|
|
82
|
+
| `bun run format` | Writes prettier formatting in place |
|
|
83
|
+
| `bun run prepare` | Initializes husky hooks, run automatically on `bun install` |
|
|
93
84
|
|
|
94
|
-
|
|
85
|
+
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.
|
|
95
86
|
|
|
96
|
-
`
|
|
87
|
+
`bun run check` repairs a checkout rather than gating one. It runs `format` first to auto-fix drifted code, then asserts only what the formatter could not fix. The pull request workflow calls `check:format` directly, and that is the gate.
|
|
88
|
+
|
|
89
|
+
`scripts/verify.ts`'s `Markdown bans` stage is the exception: it gates rather than repairs. It 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, and 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. A refusal to measure exits `1` and is logged as a skip rather than a failure, as does a corpus with no file left to check once `CHANGELOG.md` is set aside. The stage needs `canon` on PATH. `canon docs projects` names the install command. Without it, the stage is skipped and logged, and coverage falls back to the format check above.
|
|
@@ -22,11 +22,5 @@ jobs:
|
|
|
22
22
|
- name: Install Dependencies
|
|
23
23
|
run: bun install --frozen-lockfile
|
|
24
24
|
|
|
25
|
-
- name: Install Shell Tools
|
|
26
|
-
run: sudo apt-get install -y shfmt shellcheck
|
|
27
|
-
|
|
28
25
|
- name: Check Formatting
|
|
29
26
|
run: bun run check:format
|
|
30
|
-
|
|
31
|
-
- name: Check Shell
|
|
32
|
-
run: bun run check:shell
|
|
@@ -22,11 +22,10 @@ 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` | prettier
|
|
28
|
-
| Shell | `bun run check:shell` | shellcheck passes at warning level |
|
|
25
|
+
| Check | Command | What it asserts |
|
|
26
|
+
| ------ | ---------------------- | ----------------- |
|
|
27
|
+
| Format | `bun run check:format` | prettier is clean |
|
|
29
28
|
|
|
30
29
|
## Running CI locally
|
|
31
30
|
|
|
32
|
-
`bun run check` runs the same
|
|
31
|
+
`bun run check` runs the same assert plus auto-formats first. If CI fails on format, run `bun run check` locally and commit the diff.
|
|
@@ -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` | Repairs locally. Auto-formats, then asserts what the formatters could not fix. |
|
|
27
|
-
| `bun run format` | Auto-fix prettier
|
|
27
|
+
| `bun run format` | Auto-fix prettier 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` (prettier
|
|
35
|
+
- `pre-commit` runs `lint-staged` (prettier 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`. After pushing, run `git status`. If files changed, commit the diff as `style(<scope>):` and push again.
|
|
@@ -57,5 +57,5 @@ marker="$marker_dir/$key"
|
|
|
57
57
|
mkdir -p "$marker_dir"
|
|
58
58
|
: >"$marker"
|
|
59
59
|
|
|
60
|
-
msg='Temporary file write outside the project scratch folder. Write temp files to .canon/tmp/<slug>/ in the project root, not system temp. See the Scratch rule (
|
|
60
|
+
msg='Temporary file write outside the project scratch folder. Write temp files to .canon/tmp/<slug>/ in the project root, not system temp. See the Scratch rule (scratch.md, under canon/ in your installed governance rules).'
|
|
61
61
|
jq -nc --arg msg "$msg" '{hookSpecificOutput:{hookEventName:"PreToolUse",additionalContext:$msg}}'
|
|
@@ -62,13 +62,28 @@ fi
|
|
|
62
62
|
hits=$(printf '%s' "$record" |
|
|
63
63
|
jq -r '.entries[]?.bans[]? | ":\(.line):\(.column + 1) \(.kind) \(.term)"' 2>/dev/null)
|
|
64
64
|
|
|
65
|
+
# Two advisory measures ride in the same record and never gate. A heading's
|
|
66
|
+
# case is read for the whole file, since it is cheap to fix where it stands. A
|
|
67
|
+
# colon is kept only where the word before it and the colon appear in the text
|
|
68
|
+
# this write carried, since an edit often rewrites part of a line and a
|
|
69
|
+
# whole-line match would drop exactly that case. The verb names that word as
|
|
70
|
+
# `lead`, since slicing `text` at `column` miscounts after an emoji, and a
|
|
71
|
+
# binary predating the field falls back to the slice. A file holds colons nobody is
|
|
72
|
+
# asking this edit to answer for.
|
|
73
|
+
new=$(printf '%s' "$input" |
|
|
74
|
+
jq -r '[.tool_input.new_string?, .tool_input.content?, (.tool_input.edits[]?.new_string?)] | map(select(. != null)) | join("\n")' 2>/dev/null)
|
|
75
|
+
headings=$(printf '%s' "$record" |
|
|
76
|
+
jq -r '.entries[]?.headingCase[]? | ":\(.line) \(.text)"' 2>/dev/null)
|
|
77
|
+
colons=$(printf '%s' "$record" |
|
|
78
|
+
jq -r --arg new "$new" '.entries[]?.colons[]? | (.lead // (.text[:.column] | sub("^.*\\s"; ""))) as $lead | select($new | contains($lead + ":")) | ":\(.line):\(.column + 1) \(.text)"' 2>/dev/null)
|
|
79
|
+
|
|
65
80
|
# A set the verb shipped empty measures nothing and would report a clean file.
|
|
66
81
|
# Reading the findings alone turns that narrowed check into a pass, so the field
|
|
67
82
|
# is read beside them.
|
|
68
83
|
empty=$(printf '%s' "$record" |
|
|
69
84
|
jq -r '[.bans.emptySets[]?] | join(", ")' 2>/dev/null)
|
|
70
85
|
|
|
71
|
-
[ -z "$hits" ] && [ -z "$unread" ] && [ -z "$empty" ] && exit 0
|
|
86
|
+
[ -z "$hits" ] && [ -z "$headings" ] && [ -z "$colons" ] && [ -z "$unread" ] && [ -z "$empty" ] && exit 0
|
|
72
87
|
|
|
73
88
|
nl=$'\n'
|
|
74
89
|
msg=""
|
|
@@ -82,7 +97,17 @@ elif [ -n "$empty" ]; then
|
|
|
82
97
|
fi
|
|
83
98
|
|
|
84
99
|
if [ -n "$hits" ]; then
|
|
85
|
-
found=$(printf 'Standards-audit: markdown.md violations in %s.
|
|
100
|
+
found=$(printf 'Standards-audit: markdown.md violations in %s. Split the sentence or rewrite it. A colon or a comma standing where the em dash was is the same sentence. A code span is the answer only where the token is genuinely an identifier under discussion.\n%s' "$file" "$hits")
|
|
101
|
+
msg="${msg:+$msg$nl}$found"
|
|
102
|
+
fi
|
|
103
|
+
|
|
104
|
+
if [ -n "$headings" ]; then
|
|
105
|
+
found=$(printf 'Standards-audit: headings in %s are not sentence case. Lowercase every word after the first unless it is a proper noun, and leave a proper noun as it is.\n%s' "$file" "$headings")
|
|
106
|
+
msg="${msg:+$msg$nl}$found"
|
|
107
|
+
fi
|
|
108
|
+
|
|
109
|
+
if [ -n "$colons" ]; then
|
|
110
|
+
found=$(printf 'Standards-audit: this write added a colon in the middle of a sentence in %s. A colon belongs after a label or at the end of a line introducing a list. Split the sentence or rewrite it.\n%s' "$file" "$colons")
|
|
86
111
|
msg="${msg:+$msg$nl}$found"
|
|
87
112
|
fi
|
|
88
113
|
|
|
@@ -1,57 +1,44 @@
|
|
|
1
1
|
# Design
|
|
2
2
|
|
|
3
|
-
Authoring guidance: the design standard.
|
|
3
|
+
Authoring guidance: the design standard. Values live in `<your stylesheet>`, such as `src/styles/tokens.css` or a Tailwind `@theme` block. A project with none writes them under `.claude/design/project/`, beside the installed base stylesheet.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Overview
|
|
6
6
|
|
|
7
7
|
One paragraph. Voice, tone, and the feeling a user should have. The same content a design intake form asks for.
|
|
8
8
|
|
|
9
|
-
##
|
|
9
|
+
## Colors
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
Up to three rules a developer could get wrong.
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
| ---------- | ----------------------------- | ----- |
|
|
15
|
-
| background | page canvas | |
|
|
16
|
-
| surface | cards, panels, raised blocks | |
|
|
17
|
-
| text | primary body text | |
|
|
18
|
-
| muted | secondary text, captions | |
|
|
19
|
-
| accent | links, primary action | |
|
|
20
|
-
| success | confirmations, positive state | |
|
|
21
|
-
| warning | cautions, pending state | |
|
|
22
|
-
| error | failures, destructive action | |
|
|
13
|
+
One row per role, naming its custom property. Intent is a short phrase a human can picture.
|
|
23
14
|
|
|
24
|
-
|
|
15
|
+
| Role | Property | Intent |
|
|
16
|
+
| ---------- | -------------------- | ----------------------------- |
|
|
17
|
+
| background | `--color-background` | page canvas |
|
|
18
|
+
| surface | `--color-surface` | cards, panels, raised blocks |
|
|
19
|
+
| text | `--color-text` | primary body text |
|
|
20
|
+
| muted | `--color-muted` | secondary text, captions |
|
|
21
|
+
| accent | `--color-accent` | links, primary action |
|
|
22
|
+
| success | `--color-success` | confirmations, positive state |
|
|
23
|
+
| warning | `--color-warning` | cautions, pending state |
|
|
24
|
+
| error | `--color-error` | failures, destructive action |
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
## Typography
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
| ------- | ------ | ------ | ---- | ----------- |
|
|
30
|
-
| display | | | | |
|
|
31
|
-
| heading | | | | |
|
|
32
|
-
| body | | | | |
|
|
33
|
-
| label | | | | |
|
|
34
|
-
| code | | | | |
|
|
28
|
+
Up to three rules. How the roles divide between families, and any role that breaks the pattern.
|
|
35
29
|
|
|
36
30
|
## Spacing
|
|
37
31
|
|
|
38
|
-
|
|
32
|
+
One line. The base unit and how the scale steps.
|
|
39
33
|
|
|
40
|
-
|
|
41
|
-
| ---- | ---------- | ----- |
|
|
42
|
-
| xs | 0.5 | |
|
|
43
|
-
| sm | 1 | |
|
|
44
|
-
| md | 2 | |
|
|
45
|
-
| lg | 3 | |
|
|
46
|
-
| xl | 5 | |
|
|
34
|
+
## Shapes
|
|
47
35
|
|
|
48
|
-
|
|
36
|
+
One row per role, naming its radius property.
|
|
49
37
|
|
|
50
|
-
| Role |
|
|
51
|
-
| ------- |
|
|
52
|
-
| default |
|
|
53
|
-
| pill |
|
|
54
|
-
| none | 0 | 0 | edge-to-edge surfaces |
|
|
38
|
+
| Role | Property | When used |
|
|
39
|
+
| ------- | ------------------ | ------------------ |
|
|
40
|
+
| default | `--radius-default` | cards, inputs |
|
|
41
|
+
| pill | `--radius-pill` | tags, status chips |
|
|
55
42
|
|
|
56
43
|
## Motion
|
|
57
44
|
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
name: Shell
|
|
2
|
+
# canon-no-seed: an add-on stack's workflow with no root counterpart by design, since the toolkit checks its own shell inside verify.yml.
|
|
3
|
+
|
|
4
|
+
on:
|
|
5
|
+
pull_request:
|
|
6
|
+
branches:
|
|
7
|
+
- main
|
|
8
|
+
workflow_dispatch:
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
shell-checks:
|
|
12
|
+
name: 🐚 Shell Checks
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
steps:
|
|
15
|
+
- name: Checkout
|
|
16
|
+
uses: actions/checkout@v4
|
|
17
|
+
|
|
18
|
+
- name: Setup Bun
|
|
19
|
+
uses: oven-sh/setup-bun@v2
|
|
20
|
+
with:
|
|
21
|
+
bun-version: latest
|
|
22
|
+
|
|
23
|
+
- name: Install Dependencies
|
|
24
|
+
run: bun install --frozen-lockfile
|
|
25
|
+
|
|
26
|
+
- name: Install Shell Tools
|
|
27
|
+
run: sudo apt-get install -y shfmt shellcheck
|
|
28
|
+
|
|
29
|
+
- name: Check Shell
|
|
30
|
+
run: bun run check:shell
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
[stack]
|
|
2
|
+
name = "shell"
|
|
3
|
+
extends = ""
|
|
4
|
+
runtime = "bun"
|
|
5
|
+
scaffold = ""
|
|
6
|
+
|
|
7
|
+
[sync]
|
|
8
|
+
source = "configs"
|
|
9
|
+
|
|
10
|
+
[scripts]
|
|
11
|
+
"check:shell" = "find scripts -name '*.sh' -exec shfmt --diff --indent 2 {} + && find scripts -name '*.sh' -exec shellcheck --severity=warning {} +"
|
|
12
|
+
"format:shell" = "find scripts -name '*.sh' -exec shfmt --write --indent 2 {} +"
|