@erclx/canon 5.49.0 → 5.50.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/canvas-board/REQUIREMENT.md +4 -3
- package/claude/skills/canvas-board/SKILL.md +8 -9
- package/claude/skills/context-fold/references/context.md +4 -3
- package/claude/skills/design-record/REQUIREMENT.md +33 -0
- package/claude/skills/design-record/SKILL.md +84 -0
- package/claude/skills/design-record/references/folder.md +69 -0
- package/claude/skills/design-taste/SKILL.md +1 -1
- package/claude/skills/markdown-craft/REQUIREMENT.md +1 -0
- package/claude/skills/markdown-craft/SKILL.md +6 -1
- package/claude/skills/markdown-craft/references/markdown.md +3 -3
- package/claude/skills/markdown-craft/references/tables.md +30 -0
- package/claude/skills/pick-by-eye/SKILL.md +10 -8
- package/claude/skills/plan-feature/SKILL.md +13 -1
- package/claude/skills/session-worktree/SKILL.md +1 -1
- package/docs/agents/index.md +1 -1
- package/docs/agents/markdown-audit.md +5 -2
- package/docs/target/migrations.md +7 -0
- package/docs/target/projects.md +1 -1
- package/docs/workflow/skill-map.md +2 -1
- package/docs/workflow/visual-design-workflow.md +2 -1
- package/governance/ledger/ui/design-reference.md +1 -0
- package/governance/rules/ui/design-reference.md +1 -0
- package/package.json +1 -1
- package/src/claude/cases/misc.ts +5 -0
- package/src/commands/claude.ts +26 -1
- package/src/commands/gate.ts +57 -16
- package/src/commands/markdown.ts +98 -19
- package/src/context/audit.ts +6 -5
- package/src/design/budget.ts +1 -1
- package/src/design/components.ts +1 -1
- package/src/design/document.ts +1 -1
- package/src/design/regen.ts +1 -1
- package/src/design/render.ts +1 -1
- package/src/gate/hand-suite.ts +52 -0
- package/src/gate/measures.ts +9 -2
- package/src/gate/sequencer.ts +17 -8
- package/src/markdown/gated.ts +5 -7
- package/src/markdown/scope.ts +60 -10
- package/src/markdown/structure.ts +1 -1
- package/src/markdown/tables.ts +150 -0
- package/src/tasks/validate.ts +15 -12
- package/tooling/claude/reference.md +1 -0
- package/tooling/claude/seeds/.claude/hooks/standards-audit.sh +11 -1
- package/tooling/claude/seeds/CLAUDE.md +9 -0
- /package/claude/skills/{canvas-board → design-record}/references/design.md +0 -0
package/src/markdown/scope.ts
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import
|
|
1
|
+
import {
|
|
2
|
+
type DensityFinding,
|
|
3
|
+
type DensityKind,
|
|
4
|
+
densityFindings,
|
|
5
|
+
} from '@/markdown/density'
|
|
6
|
+
import type { BodyLine } from '@/markdown/scan'
|
|
2
7
|
|
|
3
8
|
/** The surfaces the retired bullet-stacks stage held, per `ALWAYS_GATED`. */
|
|
4
9
|
const BULLET_RUN_SCOPE = [
|
|
@@ -21,11 +26,12 @@ const REQUIREMENT_SCOPE =
|
|
|
21
26
|
* pages, the skill bodies, references, and requirements of both skill trees,
|
|
22
27
|
* the tooling seeds and stack references, the wiki, and the two root guides.
|
|
23
28
|
*
|
|
24
|
-
* Rules stay out, since a rule is one imperative per bullet
|
|
25
|
-
* `CLAUDE.md
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
+
* Rules stay out, since a rule is one imperative per bullet, and so does every
|
|
30
|
+
* `CLAUDE.md` section but Key paths, which `KEY_PATHS_SCOPE` holds. So do the
|
|
31
|
+
* project requirement and design records, a seed carrying one of those three
|
|
32
|
+
* names, the changelog, the examples, and every `.canon/` record, each with a
|
|
33
|
+
* shape and cap of its own. Named here and nowhere else, so the audit, the gate
|
|
34
|
+
* stage reading its exit, and their tests read one scope.
|
|
29
35
|
*/
|
|
30
36
|
const DENSITY_SCOPE = [
|
|
31
37
|
...BULLET_RUN_SCOPE,
|
|
@@ -36,10 +42,49 @@ const DENSITY_SCOPE = [
|
|
|
36
42
|
/^(?:README|CONTRIBUTING)\.md$/,
|
|
37
43
|
]
|
|
38
44
|
|
|
45
|
+
/**
|
|
46
|
+
* The root `CLAUDE.md` and its seed, measured inside `## Key paths` alone. That
|
|
47
|
+
* section is a catalog rather than rules, so it takes the run limit and the H3
|
|
48
|
+
* opening a grouped catalog needs, and nothing else a rule would breach.
|
|
49
|
+
*/
|
|
50
|
+
const KEY_PATHS_SCOPE = /^(?:tooling\/claude\/seeds\/)?CLAUDE\.md$/
|
|
51
|
+
const KEY_PATHS_HEADING = /^## Key paths\s*$/
|
|
52
|
+
const KEY_PATHS_KINDS: readonly DensityKind[] = ['bullet-run', 'h3-opening']
|
|
53
|
+
|
|
54
|
+
/** The lines under `## Key paths` up to the next H1 or H2, keeping their numbers. */
|
|
55
|
+
function keyPathsSection(lines: readonly BodyLine[]): readonly BodyLine[] {
|
|
56
|
+
const start = lines.findIndex(
|
|
57
|
+
(line) => !line.fenced && KEY_PATHS_HEADING.test(line.text),
|
|
58
|
+
)
|
|
59
|
+
if (start === -1) return []
|
|
60
|
+
const rest = lines.slice(start + 1)
|
|
61
|
+
const end = rest.findIndex(
|
|
62
|
+
(line) => !line.fenced && /^#{1,2}\s/.test(line.text),
|
|
63
|
+
)
|
|
64
|
+
return end === -1 ? rest : rest.slice(0, end)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* The density findings a path takes, being every finding on a density-scope
|
|
69
|
+
* file and the Key paths findings on a `CLAUDE.md`. The audit, the init probe,
|
|
70
|
+
* and `canon claude sync` all read this, so the three agree on what counts.
|
|
71
|
+
*/
|
|
72
|
+
export function scopedDensityFindings(
|
|
73
|
+
rel: string,
|
|
74
|
+
lines: readonly BodyLine[],
|
|
75
|
+
): DensityFinding[] {
|
|
76
|
+
if (inDensityScope(rel)) return densityFindings(lines)
|
|
77
|
+
if (!KEY_PATHS_SCOPE.test(rel)) return []
|
|
78
|
+
return densityFindings(keyPathsSection(lines)).filter((finding) =>
|
|
79
|
+
KEY_PATHS_KINDS.includes(finding.kind),
|
|
80
|
+
)
|
|
81
|
+
}
|
|
82
|
+
|
|
39
83
|
/**
|
|
40
84
|
* The roots `canon init` gates in a new project when each passes at init time:
|
|
41
|
-
* every density surface a target authors itself
|
|
42
|
-
* `claude/skills/` and `tooling/`
|
|
85
|
+
* every density surface a target authors itself and the Key paths of its
|
|
86
|
+
* `CLAUDE.md`, leaving out the toolkit's own `claude/skills/` and `tooling/`
|
|
87
|
+
* trees.
|
|
43
88
|
*/
|
|
44
89
|
export const GATED_SCOPE_ROOTS: readonly string[] = [
|
|
45
90
|
'canon/context/',
|
|
@@ -48,6 +93,7 @@ export const GATED_SCOPE_ROOTS: readonly string[] = [
|
|
|
48
93
|
'wiki/',
|
|
49
94
|
'README.md',
|
|
50
95
|
'CONTRIBUTING.md',
|
|
96
|
+
'CLAUDE.md',
|
|
51
97
|
]
|
|
52
98
|
|
|
53
99
|
/**
|
|
@@ -93,7 +139,6 @@ export const GATED_PATHS: readonly string[] = [
|
|
|
93
139
|
'claude/skills/canvas-board/REQUIREMENT.md',
|
|
94
140
|
'claude/skills/canvas-board/SKILL.md',
|
|
95
141
|
'claude/skills/canvas-board/references/canvas.md',
|
|
96
|
-
'claude/skills/canvas-board/references/design.md',
|
|
97
142
|
'claude/skills/ci-workflow/REQUIREMENT.md',
|
|
98
143
|
'claude/skills/ci-workflow/SKILL.md',
|
|
99
144
|
'claude/skills/ci-workflow/references/workflows.md',
|
|
@@ -133,6 +178,10 @@ export const GATED_PATHS: readonly string[] = [
|
|
|
133
178
|
'claude/skills/deprecation-migration/REQUIREMENT.md',
|
|
134
179
|
'claude/skills/deprecation-migration/SKILL.md',
|
|
135
180
|
'claude/skills/deprecation-migration/references/adopted.md',
|
|
181
|
+
'claude/skills/design-record/REQUIREMENT.md',
|
|
182
|
+
'claude/skills/design-record/SKILL.md',
|
|
183
|
+
'claude/skills/design-record/references/design.md',
|
|
184
|
+
'claude/skills/design-record/references/folder.md',
|
|
136
185
|
'claude/skills/design-taste/REQUIREMENT.md',
|
|
137
186
|
'claude/skills/design-taste/SKILL.md',
|
|
138
187
|
'claude/skills/design-taste/references/adopted.md',
|
|
@@ -394,6 +443,7 @@ export const GATED_PATHS: readonly string[] = [
|
|
|
394
443
|
'wiki/',
|
|
395
444
|
'README.md',
|
|
396
445
|
'CONTRIBUTING.md',
|
|
446
|
+
'CLAUDE.md',
|
|
397
447
|
]
|
|
398
448
|
|
|
399
449
|
/**
|
|
@@ -425,7 +475,7 @@ export function densityGates(
|
|
|
425
475
|
gated: readonly string[] = GATED_PATHS,
|
|
426
476
|
alwaysGated = true,
|
|
427
477
|
): boolean {
|
|
428
|
-
if (!inDensityScope(rel)) return false
|
|
478
|
+
if (!inDensityScope(rel) && !KEY_PATHS_SCOPE.test(rel)) return false
|
|
429
479
|
if (REQUIREMENT_SCOPE.test(rel)) return gated.includes(rel)
|
|
430
480
|
if (
|
|
431
481
|
alwaysGated &&
|
|
@@ -67,7 +67,7 @@ const MARKER_WIDTH = 20
|
|
|
67
67
|
|
|
68
68
|
export const LIST_ITEM = /^(\s*)([-*+]|\d+\.)\s+/
|
|
69
69
|
export const TABLE_ROW = /^\s*\|/
|
|
70
|
-
const TABLE_SEPARATOR = /^\s*\|[\s:|-]+\|\s*$/
|
|
70
|
+
export const TABLE_SEPARATOR = /^\s*\|[\s:|-]+\|\s*$/
|
|
71
71
|
export const BLOCKQUOTE = /^\s*>/
|
|
72
72
|
|
|
73
73
|
/**
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { type BodyLine, visibleText } from '@/markdown/scan'
|
|
2
|
+
import { LIST_ITEM, TABLE_ROW, TABLE_SEPARATOR } from '@/markdown/structure'
|
|
3
|
+
|
|
4
|
+
/** The most visible characters one table cell may hold. */
|
|
5
|
+
export const TABLE_CELL = 120
|
|
6
|
+
|
|
7
|
+
/** The most body rows a table may carry before it splits into groups. */
|
|
8
|
+
export const TABLE_ROWS = 8
|
|
9
|
+
|
|
10
|
+
/** The longest unbroken run a code span in a table cell may carry. */
|
|
11
|
+
export const TABLE_TOKEN = 30
|
|
12
|
+
|
|
13
|
+
export type TableKind =
|
|
14
|
+
| 'table-cell'
|
|
15
|
+
| 'table-rows'
|
|
16
|
+
| 'table-then-list'
|
|
17
|
+
| 'table-token'
|
|
18
|
+
|
|
19
|
+
export interface TableFinding {
|
|
20
|
+
readonly kind: TableKind
|
|
21
|
+
/** The row holding the cell or span, the header for a row count, or the list's first item. */
|
|
22
|
+
readonly line: number
|
|
23
|
+
/** What was measured, or null for a trailing list, which is a shape rather than a count. */
|
|
24
|
+
readonly value: number | null
|
|
25
|
+
readonly limit: number | null
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* The body of every code span in a cell, read after link destinations are
|
|
30
|
+
* dropped, since only a link's label renders.
|
|
31
|
+
*/
|
|
32
|
+
export function codeSpans(cell: string): string[] {
|
|
33
|
+
return (cell.replace(/\]\([^)]*\)/g, ']').match(/`[^`]+`/g) ?? []).map(
|
|
34
|
+
(span) => span.slice(1, -1),
|
|
35
|
+
)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* The longest whitespace-free run in a code span. A preview also breaks a span
|
|
40
|
+
* after a hyphen or a slash, but a run this long still splits mid-token or
|
|
41
|
+
* widens its column, so whitespace is the break this counts.
|
|
42
|
+
*/
|
|
43
|
+
export function unbrokenRun(span: string): number {
|
|
44
|
+
return Math.max(...span.split(/\s+/).map((part) => part.length))
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Splits a row on its unescaped pipes, dropping the outer two. */
|
|
48
|
+
function cellsOf(row: string): string[] {
|
|
49
|
+
return row
|
|
50
|
+
.trim()
|
|
51
|
+
.replace(/^\|/, '')
|
|
52
|
+
.replace(/(?<!\\)\|$/, '')
|
|
53
|
+
.split(/(?<!\\)\|/)
|
|
54
|
+
.map((cell) => cell.trim().replaceAll('\\|', '|'))
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Every breach of the four table limits, in line order.
|
|
59
|
+
*
|
|
60
|
+
* A table is a run of pipe rows whose second row is a delimiter, the shape a
|
|
61
|
+
* renderer draws. Pipe lines with no delimiter render as paragraph text, and a
|
|
62
|
+
* fenced line is an example, so neither is measured. The limits are a
|
|
63
|
+
* 120-character cell, 8 body rows, a list as the next block, and a code span
|
|
64
|
+
* holding a run past 30 characters. Every finding reports and none gates.
|
|
65
|
+
*/
|
|
66
|
+
export function tableFindings(lines: readonly BodyLine[]): TableFinding[] {
|
|
67
|
+
const found: TableFinding[] = []
|
|
68
|
+
let index = 0
|
|
69
|
+
|
|
70
|
+
while (index < lines.length) {
|
|
71
|
+
const header = lines[index]
|
|
72
|
+
const delimiter = lines[index + 1]
|
|
73
|
+
if (
|
|
74
|
+
header.fenced ||
|
|
75
|
+
!TABLE_ROW.test(header.text) ||
|
|
76
|
+
delimiter === undefined ||
|
|
77
|
+
delimiter.fenced ||
|
|
78
|
+
!TABLE_SEPARATOR.test(delimiter.text)
|
|
79
|
+
) {
|
|
80
|
+
index++
|
|
81
|
+
continue
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const tableRows = [header]
|
|
85
|
+
let end = index + 2
|
|
86
|
+
while (
|
|
87
|
+
end < lines.length &&
|
|
88
|
+
!lines[end].fenced &&
|
|
89
|
+
TABLE_ROW.test(lines[end].text)
|
|
90
|
+
) {
|
|
91
|
+
tableRows.push(lines[end])
|
|
92
|
+
end++
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
for (const row of tableRows) {
|
|
96
|
+
for (const cell of cellsOf(row.text)) {
|
|
97
|
+
const length = visibleText(cell).length
|
|
98
|
+
if (length > TABLE_CELL) {
|
|
99
|
+
found.push({
|
|
100
|
+
kind: 'table-cell',
|
|
101
|
+
line: row.number,
|
|
102
|
+
value: length,
|
|
103
|
+
limit: TABLE_CELL,
|
|
104
|
+
})
|
|
105
|
+
}
|
|
106
|
+
for (const span of codeSpans(cell)) {
|
|
107
|
+
const run = unbrokenRun(span)
|
|
108
|
+
if (run > TABLE_TOKEN) {
|
|
109
|
+
found.push({
|
|
110
|
+
kind: 'table-token',
|
|
111
|
+
line: row.number,
|
|
112
|
+
value: run,
|
|
113
|
+
limit: TABLE_TOKEN,
|
|
114
|
+
})
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const bodyRows = tableRows.length - 1
|
|
121
|
+
if (bodyRows > TABLE_ROWS) {
|
|
122
|
+
found.push({
|
|
123
|
+
kind: 'table-rows',
|
|
124
|
+
line: header.number,
|
|
125
|
+
value: bodyRows,
|
|
126
|
+
limit: TABLE_ROWS,
|
|
127
|
+
})
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
let next = end
|
|
131
|
+
while (next < lines.length && lines[next].text.trim() === '') next++
|
|
132
|
+
const following = lines[next]
|
|
133
|
+
if (
|
|
134
|
+
following !== undefined &&
|
|
135
|
+
!following.fenced &&
|
|
136
|
+
LIST_ITEM.test(following.text)
|
|
137
|
+
) {
|
|
138
|
+
found.push({
|
|
139
|
+
kind: 'table-then-list',
|
|
140
|
+
line: following.number,
|
|
141
|
+
value: null,
|
|
142
|
+
limit: null,
|
|
143
|
+
})
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
index = end
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return found.sort((a, b) => a.line - b.line)
|
|
150
|
+
}
|
package/src/tasks/validate.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { existsSync, statSync } from 'node:fs'
|
|
2
2
|
import { readdir, readFile } from 'node:fs/promises'
|
|
3
3
|
import { join, resolve } from 'node:path'
|
|
4
|
+
import { codeSpans, unbrokenRun } from '@/markdown/tables'
|
|
4
5
|
import {
|
|
5
6
|
archiveDir,
|
|
6
7
|
declinedDir,
|
|
@@ -1084,28 +1085,30 @@ function checkFolderClaims(
|
|
|
1084
1085
|
/**
|
|
1085
1086
|
* The widest the longest unbroken run in a code span may be before a table
|
|
1086
1087
|
* scrolls in the preview pane. A 57-character span fit and a 78-character one
|
|
1087
|
-
* did not, so the limit sits at the nearest round number above the first.
|
|
1088
|
+
* did not, so the limit sits at the nearest round number above the first. It
|
|
1089
|
+
* is still too high: a 36-character path with no hyphen widened a board column
|
|
1090
|
+
* until the next one ran off the pane, which is why a table cell reads the same
|
|
1091
|
+
* measure at 30. The board keeps 60 until its own report is revisited.
|
|
1088
1092
|
*/
|
|
1089
1093
|
const WIDE_LIMIT = 60
|
|
1090
1094
|
|
|
1091
1095
|
/**
|
|
1092
|
-
* Reports a code span a table cell cannot wrap
|
|
1093
|
-
*
|
|
1094
|
-
*
|
|
1095
|
-
*
|
|
1096
|
-
*
|
|
1097
|
-
* and bare words at hyphens,
|
|
1096
|
+
* Reports a code span a table cell cannot wrap, measured by `unbrokenRun`,
|
|
1097
|
+
* which `canon markdown audit` reads for a table too. A rendered preview breaks
|
|
1098
|
+
* a span at whitespace and after a hyphen or a slash, so a long run either
|
|
1099
|
+
* widens its column or splits mid-token. Every group is scanned, since the
|
|
1100
|
+
* overflow this exists for happened under `## Up next`, where the folder-claim
|
|
1101
|
+
* scan would not have looked. Prose breaks at spaces and bare words at hyphens,
|
|
1102
|
+
* so only a span is read.
|
|
1098
1103
|
*/
|
|
1099
1104
|
function checkWideTokens(rows: readonly BoardRow[]): WideToken[] {
|
|
1100
1105
|
const wide: WideToken[] = []
|
|
1101
1106
|
|
|
1102
1107
|
for (const row of rows) {
|
|
1103
1108
|
for (const cell of row.cells) {
|
|
1104
|
-
const
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
const text = span.slice(1, -1)
|
|
1108
|
-
const run = Math.max(...text.split(/\s+/).map((part) => part.length))
|
|
1109
|
+
for (const text of codeSpans(cell)) {
|
|
1110
|
+
const span = `\`${text}\``
|
|
1111
|
+
const run = unbrokenRun(text)
|
|
1109
1112
|
|
|
1110
1113
|
if (/\{[^}]*,[^}]*\}/.test(text)) {
|
|
1111
1114
|
wide.push({
|
|
@@ -57,6 +57,7 @@ A project installed before the board became a folder still holds `.claude/TASKS.
|
|
|
57
57
|
|
|
58
58
|
- Fill in the seed's placeholder project name, description, and key paths after init
|
|
59
59
|
- Keep its sections `Context`, `Commands`, and `Key paths`, since skills detect, extend, or skip by heading
|
|
60
|
+
- Group `Key paths` under H3s, each opening on a lead sentence, before it runs past 5 bullets. `canon markdown audit` and `canon claude sync` both report a longer run.
|
|
60
61
|
- Keep the `Context` section to one line pointing at the state docs, since `plan-feature` does the parallel read when invoked
|
|
61
62
|
|
|
62
63
|
## Seed docs
|
|
@@ -81,6 +81,11 @@ colons=$(printf '%s' "$record" |
|
|
|
81
81
|
density=$(printf '%s' "$record" |
|
|
82
82
|
jq -r '.entries[]?.density[]? | ":\(.line) \(.kind) " + (if .limit == null then "opens with neither a paragraph nor a table" else "\(.value) past \(.limit)" end) + (if .gating then " gating" else "" end)' 2>/dev/null)
|
|
83
83
|
|
|
84
|
+
# Table findings report and never gate, so each kind is handed back with the
|
|
85
|
+
# fix its shape takes rather than the seam a density finding splits at.
|
|
86
|
+
tables=$(printf '%s' "$record" |
|
|
87
|
+
jq -r '.entries[]?.tables[]? | ":\(.line) \(.kind) " + (if .limit == null then "a list follows the table with no lead sentence" else "\(.value) past \(.limit)" end)' 2>/dev/null)
|
|
88
|
+
|
|
84
89
|
# A page marked `kind: reference` carries reference shape findings, handed back
|
|
85
90
|
# whole the same way, since a gated one fails the push like a density finding.
|
|
86
91
|
shape=$(printf '%s' "$record" |
|
|
@@ -92,7 +97,7 @@ shape=$(printf '%s' "$record" |
|
|
|
92
97
|
empty=$(printf '%s' "$record" |
|
|
93
98
|
jq -r '[.bans.emptySets[]?] | join(", ")' 2>/dev/null)
|
|
94
99
|
|
|
95
|
-
[ -z "$hits" ] && [ -z "$headings" ] && [ -z "$colons" ] && [ -z "$density" ] && [ -z "$shape" ] && [ -z "$unread" ] && [ -z "$empty" ] && exit 0
|
|
100
|
+
[ -z "$hits" ] && [ -z "$headings" ] && [ -z "$colons" ] && [ -z "$density" ] && [ -z "$tables" ] && [ -z "$shape" ] && [ -z "$unread" ] && [ -z "$empty" ] && exit 0
|
|
96
101
|
|
|
97
102
|
nl=$'\n'
|
|
98
103
|
msg=""
|
|
@@ -125,6 +130,11 @@ if [ -n "$density" ]; then
|
|
|
125
130
|
msg="${msg:+$msg$nl}$found"
|
|
126
131
|
fi
|
|
127
132
|
|
|
133
|
+
if [ -n "$tables" ]; then
|
|
134
|
+
found=$(printf 'Standards-audit: %s breaks the markdown table limits, being a 120-character cell, 8 body rows, a list directly after a table, and a code span holding a run past 30 characters. These report and never fail a push. Move a long cell into a bullet under the table'"'"'s lead, split a long table into groups each under a lead sentence, give a trailing list its own lead sentence, and move a wide token out of the table.\n%s' "$file" "$tables")
|
|
135
|
+
msg="${msg:+$msg$nl}$found"
|
|
136
|
+
fi
|
|
137
|
+
|
|
128
138
|
if [ -n "$shape" ]; then
|
|
129
139
|
found=$(printf 'Standards-audit: %s is marked kind: reference and breaks its shape, being an opening, then Output, Refusals, and See also, each at most once and in that order, no H3, and no flag table. Point at the command'"'"'s own --help for flags. A finding marked gating fails the push.\n%s' "$file" "$shape")
|
|
130
140
|
msg="${msg:+$msg$nl}$found"
|
|
@@ -15,5 +15,14 @@
|
|
|
15
15
|
|
|
16
16
|
## Key paths
|
|
17
17
|
|
|
18
|
+
### Code
|
|
19
|
+
|
|
20
|
+
[One sentence on what the code paths below hold]
|
|
21
|
+
|
|
18
22
|
- `src/`: [description]
|
|
23
|
+
|
|
24
|
+
### Content
|
|
25
|
+
|
|
26
|
+
[One sentence on what the content paths below hold]
|
|
27
|
+
|
|
19
28
|
- `canon/DESIGN.md`: visual intent, pointing at the stylesheet that holds the values
|
|
File without changes
|