@erclx/aitk 0.51.0 → 0.53.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/claude-design-extract/SKILL.md +2 -1
- package/claude/skills/claude-docs/SKILL.md +1 -1
- package/claude/skills/claude-memory-capture/SKILL.md +2 -1
- package/claude/skills/claude-standards-audit/SKILL.md +5 -3
- package/claude/skills/create-skill/SKILL.md +2 -1
- package/claude/skills/create-snippet/SKILL.md +3 -2
- package/claude/skills/create-snippet/references/snippets.md +2 -1
- package/claude/skills/create-standard/SKILL.md +2 -1
- package/claude/skills/docs-sync/SKILL.md +2 -1
- package/claude/skills/git-issue/SKILL.md +2 -1
- package/claude/skills/git-issue/references/issue.md +2 -1
- package/claude/skills/git-pr/SKILL.md +2 -1
- package/claude/skills/git-pr/references/pr.md +2 -1
- package/claude/skills/git-split/references/pr.md +2 -1
- package/claude/skills/git-stage/SKILL.md +2 -1
- package/docs/agents/context-audit-checks.md +3 -1
- package/docs/agents/context-audit.md +35 -2
- package/docs/agents/index.md +1 -1
- package/docs/agents/install-and-sync.md +6 -0
- package/docs/ai-workflow.md +1 -1
- package/docs/operating-model.md +4 -4
- package/docs/visual-design-workflow.md +16 -16
- package/docs/zshrc-aliases.md +1 -1
- package/governance/rules/claude/500-prose.md +4 -3
- package/governance/rules/claude/501-markdown.md +13 -0
- package/governance/stacks/base.toml +1 -1
- package/package.json +1 -1
- package/scripts/core/install-check.sh +1 -1
- package/scripts/core/verify.sh +83 -0
- package/src/claude/seeds.ts +17 -3
- package/src/commands/context.ts +34 -7
- package/src/comments/vocabulary.ts +1 -1
- package/src/context/audit.ts +14 -0
- package/src/context/gate.ts +43 -0
- package/src/seed-marker.ts +74 -0
- package/src/sync/seeds-report.ts +16 -1
- package/src/tooling/inject.ts +17 -1
- package/standards/bundled/issue.md +2 -1
- package/standards/bundled/pr.md +2 -1
- package/standards/bundled/snippets.md +2 -1
- package/standards/diagrams.md +4 -3
- package/standards/index.md +2 -1
- package/standards/markdown.md +72 -0
- package/standards/prose.md +6 -57
- package/standards/publish.md +3 -2
- package/standards/readme.md +3 -2
- package/standards/skill.md +2 -1
- package/standards/standard.md +2 -1
- package/standards/versioning.md +2 -1
- package/standards/wireframes.md +3 -2
- package/tooling/claude/seeds/.claude/hooks/standards-audit.sh +5 -1
package/src/claude/seeds.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs'
|
|
2
|
-
import { chmod } from 'node:fs/promises'
|
|
3
|
-
import { join } from 'node:path'
|
|
2
|
+
import { chmod, mkdir, readFile, writeFile } from 'node:fs/promises'
|
|
3
|
+
import { dirname, join } from 'node:path'
|
|
4
4
|
import { copyPreservingMode } from '@/copy'
|
|
5
|
+
import { rewritesOnInstall, stripSeedMarker } from '@/seed-marker'
|
|
5
6
|
|
|
6
7
|
const SEEDS_DIR = join('tooling', 'claude', 'seeds')
|
|
7
8
|
const CLAUDE_DIR = '.claude'
|
|
@@ -122,12 +123,25 @@ export function countByScope(seeds: readonly Seed[]): SeedCounts {
|
|
|
122
123
|
/**
|
|
123
124
|
* Copies each pending seed. Hooks get the executable bit the way `chmod +x`
|
|
124
125
|
* granted it, added on top of whatever mode the destination already carried.
|
|
126
|
+
*
|
|
127
|
+
* A markdown seed is rewritten rather than copied, so the stub marker the seed
|
|
128
|
+
* gate reads does not reach the target. Every other seed copies byte for byte,
|
|
129
|
+
* which is what keeps the hook scripts and `settings.json` untouched.
|
|
125
130
|
*/
|
|
126
131
|
export async function applySeeds(seeds: readonly Seed[]): Promise<string[]> {
|
|
127
132
|
const applied: string[] = []
|
|
128
133
|
|
|
129
134
|
for (const seed of seeds) {
|
|
130
|
-
|
|
135
|
+
if (rewritesOnInstall(seed.src)) {
|
|
136
|
+
await mkdir(dirname(seed.dest), { recursive: true })
|
|
137
|
+
await writeFile(
|
|
138
|
+
seed.dest,
|
|
139
|
+
stripSeedMarker(await readFile(seed.src, 'utf8')),
|
|
140
|
+
)
|
|
141
|
+
} else {
|
|
142
|
+
await copyPreservingMode(seed.src, seed.dest)
|
|
143
|
+
}
|
|
144
|
+
|
|
131
145
|
if (seed.executable) await chmod(seed.dest, 0o755)
|
|
132
146
|
applied.push(seed.applyLabel)
|
|
133
147
|
}
|
package/src/commands/context.ts
CHANGED
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
presentNames,
|
|
22
22
|
resolveFolders,
|
|
23
23
|
} from '@/context/folders'
|
|
24
|
+
import { isGating } from '@/context/gate'
|
|
24
25
|
import { auditIndexes, type FolderDrift } from '@/context/index-drift'
|
|
25
26
|
import {
|
|
26
27
|
frameError,
|
|
@@ -34,8 +35,8 @@ import {
|
|
|
34
35
|
plural,
|
|
35
36
|
} from '@/ui'
|
|
36
37
|
|
|
37
|
-
/** Returned when
|
|
38
|
-
const
|
|
38
|
+
/** Returned when a gating finding is present. */
|
|
39
|
+
const EXIT_GATE = 2
|
|
39
40
|
|
|
40
41
|
/** A name of dots alone is `.` or `..`, both of which escape the audit root. */
|
|
41
42
|
const FOLDER_NAME = /^(?!\.+$)[A-Za-z0-9._-]+$/
|
|
@@ -44,6 +45,7 @@ interface AuditCommandOptions {
|
|
|
44
45
|
readonly json?: boolean
|
|
45
46
|
readonly folder?: string
|
|
46
47
|
readonly citationsOnly?: boolean
|
|
48
|
+
readonly gate?: boolean
|
|
47
49
|
}
|
|
48
50
|
|
|
49
51
|
export function register(program: Command): void {
|
|
@@ -65,17 +67,23 @@ export function register(program: Command): void {
|
|
|
65
67
|
'Comma-separated folder names, resolved under .claude/ then the project root',
|
|
66
68
|
)
|
|
67
69
|
.option('--citations-only', 'Run the gating citation check alone')
|
|
70
|
+
.option(
|
|
71
|
+
'--gate',
|
|
72
|
+
'Also fail on a missing required section or index drift, the findings that are facts',
|
|
73
|
+
)
|
|
68
74
|
.addHelpText(
|
|
69
75
|
'after',
|
|
70
76
|
[
|
|
71
77
|
'',
|
|
72
78
|
'Exit codes:',
|
|
73
|
-
' 0 the audit completed with
|
|
79
|
+
' 0 the audit completed with no gating finding',
|
|
74
80
|
' 1 refused, with the reason on stderr',
|
|
75
|
-
' 2 a
|
|
81
|
+
' 2 a gating finding is present',
|
|
76
82
|
'',
|
|
77
|
-
'
|
|
78
|
-
'
|
|
83
|
+
'An unresolved citation always gates. --gate widens the gate to the',
|
|
84
|
+
'other two findings that are facts rather than judgments: a missing',
|
|
85
|
+
'required section and index drift. Length, depth, bullet, table, and',
|
|
86
|
+
'provenance findings are thresholds and stay advisory under both.',
|
|
79
87
|
'',
|
|
80
88
|
'Examples:',
|
|
81
89
|
' aitk context audit',
|
|
@@ -83,6 +91,7 @@ export function register(program: Command): void {
|
|
|
83
91
|
' aitk context audit --citations-only',
|
|
84
92
|
' aitk context audit --folder context,diagrams',
|
|
85
93
|
' aitk context audit --folder docs',
|
|
94
|
+
' aitk context audit tooling/base/seeds --gate',
|
|
86
95
|
'',
|
|
87
96
|
].join('\n'),
|
|
88
97
|
)
|
|
@@ -118,6 +127,17 @@ async function runAudit(
|
|
|
118
127
|
const root = resolve(path ?? process.cwd())
|
|
119
128
|
const names = parseFolders(opts.folder)
|
|
120
129
|
const gateOnly = opts.citationsOnly ?? false
|
|
130
|
+
const widened = opts.gate ?? false
|
|
131
|
+
|
|
132
|
+
// `--citations-only` runs the citation check alone, so the two findings
|
|
133
|
+
// `--gate` adds are never measured. Honouring both would exit 0 on a seed
|
|
134
|
+
// short a required section, which is the pass a gate exists to prevent.
|
|
135
|
+
if (gateOnly && widened) {
|
|
136
|
+
return refuse(
|
|
137
|
+
'--citations-only runs the citation check alone, so --gate would widen the gate to findings the run never measures. Pass one.',
|
|
138
|
+
gateOnly,
|
|
139
|
+
)
|
|
140
|
+
}
|
|
121
141
|
|
|
122
142
|
if (typeof names === 'string') return refuse(names, gateOnly)
|
|
123
143
|
|
|
@@ -213,7 +233,14 @@ async function runAudit(
|
|
|
213
233
|
)
|
|
214
234
|
}
|
|
215
235
|
|
|
216
|
-
|
|
236
|
+
const gating = isGating({
|
|
237
|
+
unresolvedCitations: citations.unresolved.length,
|
|
238
|
+
sections,
|
|
239
|
+
drift,
|
|
240
|
+
widened,
|
|
241
|
+
})
|
|
242
|
+
|
|
243
|
+
return gating ? EXIT_GATE : 0
|
|
217
244
|
}
|
|
218
245
|
|
|
219
246
|
function refuse(message: string, gateOnly: boolean): number {
|
|
@@ -57,7 +57,7 @@ export function parseVocabulary(markdown: string): string[] | undefined {
|
|
|
57
57
|
*
|
|
58
58
|
* Reading the list out of the rule rather than hardcoding it is what keeps one
|
|
59
59
|
* definition when the rule installs into a target, the same way
|
|
60
|
-
* `.claude/hooks/standards-audit.sh` reads its bans out of `prose.md`.
|
|
60
|
+
* `.claude/hooks/standards-audit.sh` reads its word bans out of `prose.md`.
|
|
61
61
|
*/
|
|
62
62
|
export async function loadVocabulary(root: string): Promise<Vocabulary> {
|
|
63
63
|
for (const ruleRoot of RULE_ROOTS) {
|
package/src/context/audit.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { readFile } from 'node:fs/promises'
|
|
2
2
|
import { relative } from 'node:path'
|
|
3
3
|
import type { AuditedFolder } from '@/context/folders'
|
|
4
|
+
import { isStubSeed } from '@/seed-marker'
|
|
4
5
|
|
|
5
6
|
/** Checkpoints quoted from `standards/context.md`. Neither is a cap. */
|
|
6
7
|
export const LENGTH_CHECKPOINT = 150
|
|
@@ -166,6 +167,12 @@ export interface EntryReport {
|
|
|
166
167
|
* is `missingSections`, since one entry answers for its siblings.
|
|
167
168
|
*/
|
|
168
169
|
readonly sections: readonly string[]
|
|
170
|
+
/**
|
|
171
|
+
* Whether the file declares itself a skeleton, which excludes it from the
|
|
172
|
+
* section check alone. Every other measure still reads it, since a stub is
|
|
173
|
+
* exempt from owing sections rather than from being well formed.
|
|
174
|
+
*/
|
|
175
|
+
readonly stub: boolean
|
|
169
176
|
}
|
|
170
177
|
|
|
171
178
|
export interface SectionFinding {
|
|
@@ -544,6 +551,7 @@ export function measureEntry(
|
|
|
544
551
|
provenance: governsContent ? provenance(lines) : [],
|
|
545
552
|
heavyBullets: governsContent ? heavyBullets(lines) : [],
|
|
546
553
|
sections: governsContent ? declaredSections(lines) : [],
|
|
554
|
+
stub: isStubSeed(source),
|
|
547
555
|
}
|
|
548
556
|
}
|
|
549
557
|
|
|
@@ -615,9 +623,15 @@ export function missingSections(
|
|
|
615
623
|
for (const folder of folders) {
|
|
616
624
|
if (!governsContent(folder) || folder.entries.length === 0) continue
|
|
617
625
|
|
|
626
|
+
// A stub owes no sections, so it is dropped before either branch rather
|
|
627
|
+
// than inside them. Leaving one in the split-folder aggregate would let a
|
|
628
|
+
// skeleton answer for the siblings that do owe the sections.
|
|
618
629
|
const reports = folder.entries
|
|
619
630
|
.map((path) => byRel.get(relative(root, path)))
|
|
620
631
|
.filter((entry) => entry !== undefined)
|
|
632
|
+
.filter((entry) => !entry.stub)
|
|
633
|
+
|
|
634
|
+
if (reports.length === 0) continue
|
|
621
635
|
|
|
622
636
|
if (folder.nested) {
|
|
623
637
|
const missing = shortOf(reports.flatMap((entry) => entry.sections))
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { SectionFinding } from '@/context/audit'
|
|
2
|
+
import type { FolderDrift } from '@/context/index-drift'
|
|
3
|
+
|
|
4
|
+
export interface GateInput {
|
|
5
|
+
/** Cited paths that resolved to nothing, which gate under either mode. */
|
|
6
|
+
readonly unresolvedCitations: number
|
|
7
|
+
readonly sections: readonly SectionFinding[]
|
|
8
|
+
readonly drift: readonly FolderDrift[]
|
|
9
|
+
/**
|
|
10
|
+
* Whether the caller asked for the widened gate. False leaves a missing
|
|
11
|
+
* section and a drifted index advisory, which is what the project-root stage
|
|
12
|
+
* runs so a judgment threshold never fails a push.
|
|
13
|
+
*/
|
|
14
|
+
readonly widened: boolean
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Whether any folder disagrees with its own index. */
|
|
18
|
+
export function hasDrift(drift: readonly FolderDrift[]): boolean {
|
|
19
|
+
return drift.some(
|
|
20
|
+
(folder) => folder.unlisted.length > 0 || folder.missing.length > 0,
|
|
21
|
+
)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Whether the audit found something that should fail the caller.
|
|
26
|
+
*
|
|
27
|
+
* An unresolved citation is a broken pointer and gates unconditionally. The two
|
|
28
|
+
* findings `--gate` adds are the ones answerable from the file itself: a
|
|
29
|
+
* required section it does not declare, and an index disagreeing with its
|
|
30
|
+
* folder. Length, depth, bullet, table, and provenance findings are thresholds
|
|
31
|
+
* a reader weighs, so they stay out under both modes.
|
|
32
|
+
*/
|
|
33
|
+
export function isGating({
|
|
34
|
+
unresolvedCitations,
|
|
35
|
+
sections,
|
|
36
|
+
drift,
|
|
37
|
+
widened,
|
|
38
|
+
}: GateInput): boolean {
|
|
39
|
+
if (unresolvedCitations > 0) return true
|
|
40
|
+
if (!widened) return false
|
|
41
|
+
|
|
42
|
+
return sections.length > 0 || hasDrift(drift)
|
|
43
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The frontmatter field exempting a seed from the section check the seed gate
|
|
3
|
+
* runs.
|
|
4
|
+
*
|
|
5
|
+
* The check has a false-positive class its own comment records: a standard may
|
|
6
|
+
* sanction omitting a section, and no measure separates that from a file that
|
|
7
|
+
* forgot it. Reporting is the right response in a live project, where the
|
|
8
|
+
* finding is advisory. The seed gate promotes the same finding to a failing
|
|
9
|
+
* exit code, so the seed tree needs a way to say the omission is deliberate.
|
|
10
|
+
*/
|
|
11
|
+
export const SEED_STUB_FIELD = 'stub'
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Whether an install rewrites this seed rather than copying it byte for byte.
|
|
15
|
+
*
|
|
16
|
+
* This answers what the caller branches on and not whether the file holds a
|
|
17
|
+
* marker, which is `isStubSeed` reading content. Only markdown carries
|
|
18
|
+
* frontmatter, so only markdown can hold one, and the extension decides the
|
|
19
|
+
* copy path without opening the file. Every install path and the drift report
|
|
20
|
+
* ask this one question, or the report compares a marked source against a
|
|
21
|
+
* stripped target and reads a seed nobody touched as drifted.
|
|
22
|
+
*/
|
|
23
|
+
export function rewritesOnInstall(src: string): boolean {
|
|
24
|
+
return src.endsWith('.md')
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const FRONTMATTER = /^---\n([\s\S]*?)\n---(\n|$)/
|
|
28
|
+
const STUB_LINE = new RegExp(`^${SEED_STUB_FIELD}:[ \\t]*true[ \\t]*$`, 'm')
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Whether the source declares itself a skeleton for the target to fill.
|
|
32
|
+
*
|
|
33
|
+
* Only `true` counts. A field set to anything else reads as a seed that meant
|
|
34
|
+
* to turn the exemption off, and treating an unparsed value as exempt would
|
|
35
|
+
* make a typo silence the gate.
|
|
36
|
+
*/
|
|
37
|
+
export function isStubSeed(source: string): boolean {
|
|
38
|
+
const block = source.match(FRONTMATTER)
|
|
39
|
+
if (!block) return false
|
|
40
|
+
|
|
41
|
+
return STUB_LINE.test(block[1])
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Removes the marker so it reaches no target.
|
|
46
|
+
*
|
|
47
|
+
* The field is toolkit bookkeeping about the seed tree, and a project that
|
|
48
|
+
* received it would carry a field its own tooling never reads. A file whose
|
|
49
|
+
* frontmatter holds nothing else loses the block entirely rather than keeping
|
|
50
|
+
* an empty one.
|
|
51
|
+
*/
|
|
52
|
+
export function stripSeedMarker(source: string): string {
|
|
53
|
+
const block = source.match(FRONTMATTER)
|
|
54
|
+
if (!block) return source
|
|
55
|
+
|
|
56
|
+
const kept = block[1]
|
|
57
|
+
.split('\n')
|
|
58
|
+
.filter((line) => !STUB_LINE.test(line))
|
|
59
|
+
.join('\n')
|
|
60
|
+
|
|
61
|
+
// A function replacement, because a string one reads `$1` and `$&` in the
|
|
62
|
+
// kept fields as references to this match. A `description` naming a dollar
|
|
63
|
+
// amount would otherwise substitute the whole frontmatter into itself and
|
|
64
|
+
// carry the marker along with it.
|
|
65
|
+
if (kept.trim() !== '') {
|
|
66
|
+
const replacement = `---\n${kept}\n---${block[2] ?? ''}`
|
|
67
|
+
return source.replace(FRONTMATTER, () => replacement)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Dropping the block takes the blank line that separated it from the body
|
|
71
|
+
// with it. Leaving that behind opens the installed file on whitespace, which
|
|
72
|
+
// is a diff every target would carry against its own formatter.
|
|
73
|
+
return source.slice(block[0].length).replace(/^\n+/, '')
|
|
74
|
+
}
|
package/src/sync/seeds-report.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { readFileSync } from 'node:fs'
|
|
2
2
|
import { relative } from 'node:path'
|
|
3
3
|
import { planSeeds, type Seed } from '@/claude/seeds'
|
|
4
|
+
import { rewritesOnInstall, stripSeedMarker } from '@/seed-marker'
|
|
4
5
|
import { findInstalledOrigin, readHistoryIndex } from '@/sync/history'
|
|
5
6
|
|
|
6
7
|
/**
|
|
@@ -106,6 +107,20 @@ function attribute(
|
|
|
106
107
|
return false
|
|
107
108
|
}
|
|
108
109
|
|
|
110
|
+
/**
|
|
111
|
+
* Compares the target against what an install would write, not against the seed
|
|
112
|
+
* source. The two differ for a markdown seed carrying the stub marker, which the
|
|
113
|
+
* install strips, so comparing sources would report a file the target never
|
|
114
|
+
* touched as drifted for as long as the marker stays set.
|
|
115
|
+
*/
|
|
109
116
|
function sameContent(source: string, dest: string): boolean {
|
|
110
|
-
|
|
117
|
+
const installed = readFileSync(dest)
|
|
118
|
+
|
|
119
|
+
if (!rewritesOnInstall(source)) {
|
|
120
|
+
return readFileSync(source).equals(installed)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return (
|
|
124
|
+
stripSeedMarker(readFileSync(source, 'utf8')) === installed.toString('utf8')
|
|
125
|
+
)
|
|
111
126
|
}
|
package/src/tooling/inject.ts
CHANGED
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
import { dirname, join } from 'node:path'
|
|
11
11
|
import { $ } from 'bun'
|
|
12
12
|
import { copyPreservingMode } from '@/copy'
|
|
13
|
+
import { rewritesOnInstall, stripSeedMarker } from '@/seed-marker'
|
|
13
14
|
import { mergeSections, pruneSections } from '@/tooling/gitignore'
|
|
14
15
|
import { ancestorsFirst, listFiles, type Manifest } from '@/tooling/manifest'
|
|
15
16
|
import {
|
|
@@ -44,6 +45,21 @@ export async function injectConfigs(
|
|
|
44
45
|
return applied
|
|
45
46
|
}
|
|
46
47
|
|
|
48
|
+
/**
|
|
49
|
+
* Writes a seed to a target that does not have it, dropping the stub marker on
|
|
50
|
+
* the way. The marker is toolkit bookkeeping read by the seed gate, so a target
|
|
51
|
+
* receiving it would carry a field its own tooling never reads. Only markdown
|
|
52
|
+
* carries frontmatter, and every other seed copies byte for byte.
|
|
53
|
+
*/
|
|
54
|
+
async function writeSeed(src: string, dest: string): Promise<void> {
|
|
55
|
+
if (!rewritesOnInstall(src)) {
|
|
56
|
+
await copyFile(src, dest)
|
|
57
|
+
return
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
await writeFile(dest, stripSeedMarker(await readFile(src, 'utf8')))
|
|
61
|
+
}
|
|
62
|
+
|
|
47
63
|
/**
|
|
48
64
|
* Copies a seed when the target lacks it. When the target already has one and
|
|
49
65
|
* the seed is a `.txt` word list, missing lines are appended and the file is
|
|
@@ -53,7 +69,7 @@ async function mergeSeedFile(src: string, dest: string): Promise<void> {
|
|
|
53
69
|
await mkdir(dirname(dest), { recursive: true })
|
|
54
70
|
|
|
55
71
|
if (!existsSync(dest)) {
|
|
56
|
-
await
|
|
72
|
+
await writeSeed(src, dest)
|
|
57
73
|
return
|
|
58
74
|
}
|
|
59
75
|
|
|
@@ -14,7 +14,8 @@ Does not govern:
|
|
|
14
14
|
|
|
15
15
|
- Pull request title and body: `pr.md`
|
|
16
16
|
- Whether a phase label may appear in issue text: `versioning.md`
|
|
17
|
-
- Voice
|
|
17
|
+
- Voice and banned words in issue prose: `prose.md`
|
|
18
|
+
- Punctuation and formatting in issue prose: `markdown.md`
|
|
18
19
|
|
|
19
20
|
## Title
|
|
20
21
|
|
package/standards/bundled/pr.md
CHANGED
|
@@ -15,7 +15,8 @@ Does not govern:
|
|
|
15
15
|
- Commit subject format, which shares the title form: `commit.md`
|
|
16
16
|
- Branch naming: `branch.md`
|
|
17
17
|
- Whether a phase label or a semver tag may appear in a title or body: `versioning.md`
|
|
18
|
-
- Voice
|
|
18
|
+
- Voice and banned words in pull request prose: `prose.md`
|
|
19
|
+
- Punctuation and formatting in pull request prose: `markdown.md`
|
|
19
20
|
|
|
20
21
|
## Title
|
|
21
22
|
|
|
@@ -13,7 +13,8 @@ Governs a snippet file: what one is for, whether a prompt qualifies as one, how
|
|
|
13
13
|
Does not govern:
|
|
14
14
|
|
|
15
15
|
- Skill folders, which carry frontmatter, references, and scripts a snippet has none of: `skill.md`
|
|
16
|
-
- Voice
|
|
16
|
+
- Voice and word choice in snippet prose: `prose.md`
|
|
17
|
+
- Punctuation and formatting in snippet prose: `markdown.md`
|
|
17
18
|
|
|
18
19
|
## What a snippet is
|
|
19
20
|
|
package/standards/diagrams.md
CHANGED
|
@@ -15,7 +15,8 @@ Governs per-kind diagram entries under `.claude/diagrams/`: which question each
|
|
|
15
15
|
|
|
16
16
|
Does not govern:
|
|
17
17
|
|
|
18
|
-
-
|
|
18
|
+
- Language and word choice in explanation prose and node labels: `prose.md`, whose bans the yield does not lift
|
|
19
|
+
- Punctuation and formatting in explanation prose: `markdown.md`, which the yield does not reach
|
|
19
20
|
- The mechanism behind any component a diagram draws: `context.md`
|
|
20
21
|
- UI layout, on-screen copy, and interaction intent: `wireframes.md`
|
|
21
22
|
- The decision record a components diagram is drawn from: `architecture.md`
|
|
@@ -93,7 +94,7 @@ A second entry for one kind takes a suffixed name (`request-flow-admin.md`) and
|
|
|
93
94
|
- Do not duplicate prose across entries. An entry that restates its neighbor has taken the neighbor's job.
|
|
94
95
|
- The audience is mixed, so vocabulary runs as a gradient across the set. `System context` assumes no knowledge of the repository. `Deployment` may assume the reader has read the others.
|
|
95
96
|
|
|
96
|
-
This section states the voice for the surface, which is what claims the yield `prose.md` grants to a surface whose own standard sets it. Explanation prose is pedagogical here and the default developer-facing voice does not apply. The yield covers voice alone.
|
|
97
|
+
This section states the voice for the surface, which is what claims the yield `prose.md` grants to a surface whose own standard sets it. Explanation prose is pedagogical here and the default developer-facing voice does not apply. The yield covers voice alone. The language bans in `prose.md` stay in force, as do the punctuation and formatting rules in `markdown.md`, which grants no yield at all.
|
|
97
98
|
|
|
98
99
|
## Verification
|
|
99
100
|
|
|
@@ -120,7 +121,7 @@ Reference the context entry by path when a reader needs the mechanism. The diagr
|
|
|
120
121
|
- `System context` has no named source signal beyond `.claude/REQUIREMENTS.md`, so nothing tells a session it went stale. Re-read it when the boundary or the set of external dependencies moves.
|
|
121
122
|
- The `claude-docs` sweep watches two things and writes frontmatter only. It appends `stale` when a path an entry cites leaves the tree, and it stubs a kind when a diff adds the source signal that kind is drawn from. Diagram bodies and explanation paragraphs are off limits to it, because a change that removes a module does not carry the new correct shape of the picture.
|
|
122
123
|
- That watch samples thinly. It sees the one or two paths an entry happened to cite and nothing else, so a change elsewhere leaves the entry looking current. `verified` is what covers the gap, and an entry whose date sits far behind the branch is due a read whether or not anything flagged it.
|
|
123
|
-
- The explanation paragraphs around a Mermaid block are prose and follow `prose.md`. The fenced block itself is not, which is why a check scoped to prose is the wrong thing to rely on for what sits inside it.
|
|
124
|
+
- The explanation paragraphs around a Mermaid block are prose and follow `prose.md` and `markdown.md`. The fenced block itself is not, which is why a check scoped to prose is the wrong thing to rely on for what sits inside it.
|
|
124
125
|
- The punctuation bans still apply to node and subgraph labels, and nothing checks them there. An em dash in a label passes every gate the repository has, so read the labels before shipping the entry.
|
|
125
126
|
|
|
126
127
|
## Template
|
package/standards/index.md
CHANGED
|
@@ -11,7 +11,8 @@ Reference docs for consistent authoring across the toolkit and target projects.
|
|
|
11
11
|
- [Context entry reference](context.md): Shape and content rules for .claude/context/<domain>.md entries
|
|
12
12
|
- [Design reference](design.md): Shape and content rules for .claude/DESIGN.md
|
|
13
13
|
- [Diagram reference](diagrams.md): Shape and content rules for .claude/diagrams/<kind>.md files
|
|
14
|
-
- [
|
|
14
|
+
- [Markdown reference](markdown.md): Headings, paragraph and list structure, code spans, punctuation, emphasis, and file references
|
|
15
|
+
- [Prose reference](prose.md): Voice, language, and frontmatter wording for reference markdown
|
|
15
16
|
- [Publish reference](publish.md): Scan run against finished text leaving through a channel no automated check covers
|
|
16
17
|
- [Readme reference](readme.md): Readme voice, structure, and content conventions
|
|
17
18
|
- [Requirements reference](requirements.md): Shape and content rules for .claude/REQUIREMENTS.md
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Markdown reference
|
|
3
|
+
description: Headings, paragraph and list structure, code spans, punctuation, emphasis, and file references
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Markdown reference
|
|
7
|
+
|
|
8
|
+
Applies to markdown reference docs, READMEs, and inline documentation in repos. These are mechanics rather than voice, so no surface yields them. A surface stating its own voice claims that yield from `prose.md` and formats by this file regardless.
|
|
9
|
+
|
|
10
|
+
## Scope
|
|
11
|
+
|
|
12
|
+
Governs the markdown mechanics of every markdown file: headings, paragraph and list structure, code spans and fences, punctuation, emphasis, and file references. It is an attribute standard rather than a document-type one, so it applies over documents whose shape another standard sets, and it carries no template because mechanics are written across every document and have none of their own to shape.
|
|
13
|
+
|
|
14
|
+
Does not govern:
|
|
15
|
+
|
|
16
|
+
- Voice, word choice, and the wording of a `title` or `description`: `prose.md`
|
|
17
|
+
- What sections a document has, or what belongs in each: the standard for that document type
|
|
18
|
+
- The text inside a fenced block, which follows the conventions of its own language rather than these
|
|
19
|
+
- The scan that applies the punctuation bans to finished text on its way out: `publish.md`
|
|
20
|
+
|
|
21
|
+
## Headings
|
|
22
|
+
|
|
23
|
+
- H1 for document title, H2 for main sections, H3 for subsections
|
|
24
|
+
- Use sentence case for all headings (H1, H2, H3)
|
|
25
|
+
- Proper nouns and product names retain their casing in headings
|
|
26
|
+
|
|
27
|
+
## Paragraphs and lists
|
|
28
|
+
|
|
29
|
+
- Use prose by default. Reserve bullets for discrete, unrelated items.
|
|
30
|
+
- Keep paragraphs to four sentences or fewer. Split longer blocks at the next logical boundary.
|
|
31
|
+
- Keep bullets tight. If a bullet needs more than a couple of sentences, it belongs in prose.
|
|
32
|
+
- Use dashes (`-`) not asterisks (`*`) for bulleted lists
|
|
33
|
+
- Do not end single-sentence or fragment bullets with a period. Use periods when a bullet has two or more sentences.
|
|
34
|
+
- For key path lists, use colon format: `- \`src/\`: description`. Never use an em dash.
|
|
35
|
+
- Do not introduce a list with a "Here are the X:" or "The following X:" lead-in
|
|
36
|
+
|
|
37
|
+
## Code and identifiers
|
|
38
|
+
|
|
39
|
+
- Wrap commands, API names, file paths, and code identifiers in backticks
|
|
40
|
+
- Use a language identifier on all fenced code blocks (`markdown`, `typescript`, `plaintext`). Never use a bare ` ``` `
|
|
41
|
+
- In ASCII tree diagrams, use `←` for inline annotations. Never use `#`.
|
|
42
|
+
|
|
43
|
+
## Punctuation
|
|
44
|
+
|
|
45
|
+
- Do not use em dashes (`—`) or semicolons (`;`). Rewrite or restructure the sentence to avoid them.
|
|
46
|
+
- Do not use parenthetical asides in prose (`the config (which is optional) controls...`). Split into its own sentence or drop it. Parentheses in rule definitions for grouping examples are fine.
|
|
47
|
+
|
|
48
|
+
The closed-set word bans sit in `prose.md` under `## Language` rather than here, because a banned word is a word-choice rule and these are character rules. A surface applying both reads both files.
|
|
49
|
+
|
|
50
|
+
## Emphasis and dividers
|
|
51
|
+
|
|
52
|
+
- Do not over-format with excessive bold, italic, or header usage
|
|
53
|
+
- Do not use horizontal rules or dividers (`---`) in body content. The `---` delimiters of a YAML frontmatter block at the top of the file are allowed.
|
|
54
|
+
|
|
55
|
+
## Links and file references
|
|
56
|
+
|
|
57
|
+
- Use descriptive anchor text for links. Avoid `click here` or `read more`.
|
|
58
|
+
- Wrap file references in backticks by default. Use a labeled markdown link (`[label](path)`) only on rendered-for-human surfaces (`README.md`, `docs/`) and in an index file, whose rows exist to be followed. Never repeat the path verbatim as the label.
|
|
59
|
+
|
|
60
|
+
## Examples
|
|
61
|
+
|
|
62
|
+
Each pair shows a banned pattern and its fix.
|
|
63
|
+
|
|
64
|
+
```markdown
|
|
65
|
+
Bad: See [.claude/context/retrieval.md](.claude/context/retrieval.md) for the retrieval flow.
|
|
66
|
+
Good: See `.claude/context/retrieval.md` for the retrieval flow.
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
```markdown
|
|
70
|
+
Bad: Read [docs/development.md](docs/development.md) before contributing.
|
|
71
|
+
Good: Read the [development guide](docs/development.md) before contributing.
|
|
72
|
+
```
|
package/standards/prose.md
CHANGED
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: Prose reference
|
|
3
|
-
description: Voice,
|
|
3
|
+
description: Voice, language, and frontmatter wording for reference markdown
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Prose reference
|
|
7
7
|
|
|
8
|
-
Applies to markdown reference docs, READMEs, and inline documentation in repos. It is the default voice for `.md` files and yields to any surface with its own voice, such as blogs, emails, changelogs, or commit messages. It also yields wherever another standard states the voice for the surface it governs, which is how a surface claims the exemption without this file having to name it. The yield covers voice alone.
|
|
8
|
+
Applies to markdown reference docs, READMEs, and inline documentation in repos. It is the default voice for `.md` files and yields to any surface with its own voice, such as blogs, emails, changelogs, or commit messages. It also yields wherever another standard states the voice for the surface it governs, which is how a surface claims the exemption without this file having to name it. The yield covers voice alone. The language rules below stay in force on every surface, including the surfaces no automated check reaches, as do the mechanics in `markdown.md`.
|
|
9
9
|
|
|
10
10
|
## Scope
|
|
11
11
|
|
|
12
|
-
Governs voice,
|
|
12
|
+
Governs voice, word choice, and frontmatter wording wherever prose is written. It is an attribute standard rather than a document-type one, so it applies over documents whose shape another standard sets, yields on voice alone where that standard states one, and carries no template because voice is written across every document and has none of its own to shape.
|
|
13
13
|
|
|
14
14
|
Does not govern:
|
|
15
15
|
|
|
16
|
+
- Headings, list and paragraph structure, code spans, punctuation, emphasis, and file references: `markdown.md`
|
|
16
17
|
- What sections a document has, or what belongs in each: the standard for that document type
|
|
17
18
|
- Which frontmatter fields a document carries, which is that standard's own subject. This file governs the wording of a `title` and a `description` and nothing else about them.
|
|
18
19
|
- Phase-label and semver discipline: `versioning.md`
|
|
@@ -28,52 +29,8 @@ Does not govern:
|
|
|
28
29
|
- Use substantive connectives where flow matters, but never add words solely for rhythm. Terse reference prose needs no padding.
|
|
29
30
|
- Be direct on established facts. Hedge on genuinely uncertain claims.
|
|
30
31
|
- Assume developer-level technical knowledge. Skip hand-holding explanations.
|
|
31
|
-
- Keep paragraphs to four sentences or fewer. Split longer blocks at the next logical boundary.
|
|
32
|
-
|
|
33
|
-
## Structure
|
|
34
|
-
|
|
35
|
-
### Headings
|
|
36
|
-
|
|
37
|
-
- H1 for document title, H2 for main sections, H3 for subsections
|
|
38
|
-
- Use sentence case for all headings (H1, H2, H3)
|
|
39
|
-
- Proper nouns and product names retain their casing in headings
|
|
40
|
-
|
|
41
|
-
### Paragraphs and lists
|
|
42
|
-
|
|
43
32
|
- Front-load key information in each paragraph. Keep paragraphs concise and scannable.
|
|
44
33
|
- Every sentence must provide new information. Cut redundant context.
|
|
45
|
-
- Use prose by default. Reserve bullets for discrete, unrelated items.
|
|
46
|
-
- Keep bullets tight. If a bullet needs more than a couple of sentences, it belongs in prose.
|
|
47
|
-
|
|
48
|
-
## Formatting
|
|
49
|
-
|
|
50
|
-
### Lists
|
|
51
|
-
|
|
52
|
-
- Use dashes (`-`) not asterisks (`*`) for bulleted lists
|
|
53
|
-
- Do not end single-sentence or fragment bullets with a period. Use periods when a bullet has two or more sentences.
|
|
54
|
-
- For key path lists, use colon format: `- \`src/\`: description`. Never use an em dash.
|
|
55
|
-
- Do not introduce a list with a "Here are the X:" or "The following X:" lead-in
|
|
56
|
-
|
|
57
|
-
### Code and identifiers
|
|
58
|
-
|
|
59
|
-
- Wrap commands, API names, file paths, and code identifiers in backticks
|
|
60
|
-
- Use a language identifier on all fenced code blocks (`markdown`, `typescript`, `plaintext`). Never use a bare ` ``` `
|
|
61
|
-
- In ASCII tree diagrams, use `←` for inline annotations. Never use `#`.
|
|
62
|
-
|
|
63
|
-
### Punctuation
|
|
64
|
-
|
|
65
|
-
- Do not use em dashes (`—`) or semicolons (`;`). Rewrite or restructure the sentence to avoid them.
|
|
66
|
-
- Do not use parenthetical asides in prose (`the config (which is optional) controls...`). Split into its own sentence or drop it. Parentheses in rule definitions for grouping examples are fine.
|
|
67
|
-
|
|
68
|
-
### Emphasis and dividers
|
|
69
|
-
|
|
70
|
-
- Do not over-format with excessive bold, italic, or header usage
|
|
71
|
-
- Do not use horizontal rules or dividers (`---`) in body content. The `---` delimiters of a YAML frontmatter block at the top of the file are allowed.
|
|
72
|
-
|
|
73
|
-
### Links and file references
|
|
74
|
-
|
|
75
|
-
- Use descriptive anchor text for links. Avoid `click here` or `read more`.
|
|
76
|
-
- Wrap file references in backticks by default. Use a labeled markdown link (`[label](path)`) only on rendered-for-human surfaces (`README.md`, `docs/`) for cross-folder navigation. Never repeat the path verbatim as the label.
|
|
77
34
|
|
|
78
35
|
## Language
|
|
79
36
|
|
|
@@ -86,6 +43,8 @@ Does not govern:
|
|
|
86
43
|
- Do not address the reader as a participant (`Let's`, `Here's`, `Here are`). State the content directly.
|
|
87
44
|
- Commit to a position. Do not hedge in clusters (`It might be worth considering`) or use false balance (`While X is true, Y is also important`). Recommend, or state the tradeoff.
|
|
88
45
|
|
|
46
|
+
The character bans sit in `markdown.md` under `## Punctuation` rather than here, because an em dash and a semicolon are typography and these are word choice. A surface applying both reads both files.
|
|
47
|
+
|
|
89
48
|
## Frontmatter descriptions
|
|
90
49
|
|
|
91
50
|
When frontmatter carries a short `title` or `description` used for catalog display:
|
|
@@ -122,13 +81,3 @@ Good: Use the `retry` option for failed webhooks. Set `maxRetries` to 3.
|
|
|
122
81
|
Bad: It might be worth considering whether to enable caching.
|
|
123
82
|
Good: Enable caching for read-heavy endpoints. Skip it for writes.
|
|
124
83
|
```
|
|
125
|
-
|
|
126
|
-
```markdown
|
|
127
|
-
Bad: See [.claude/context/retrieval.md](.claude/context/retrieval.md) for the retrieval flow.
|
|
128
|
-
Good: See `.claude/context/retrieval.md` for the retrieval flow.
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
```markdown
|
|
132
|
-
Bad: Read [docs/development.md](docs/development.md) before contributing.
|
|
133
|
-
Good: Read the [development guide](docs/development.md) before contributing.
|
|
134
|
-
```
|
package/standards/publish.md
CHANGED
|
@@ -11,7 +11,8 @@ Governs the scan an author runs against finished text on its way out, and the re
|
|
|
11
11
|
|
|
12
12
|
Does not govern:
|
|
13
13
|
|
|
14
|
-
- Which characters are banned, and the
|
|
14
|
+
- Which characters are banned, and the formatting the text carries: `markdown.md`
|
|
15
|
+
- The voice and word choice the text is written in: `prose.md`
|
|
15
16
|
- The phase-label rule and the table of surfaces each namespace may appear on: `versioning.md`
|
|
16
17
|
- Which gap a given surface has, and what it publishes through, which that surface names for itself
|
|
17
18
|
|
|
@@ -23,7 +24,7 @@ Run the scan as an explicit step against the finished text. Having read the unde
|
|
|
23
24
|
|
|
24
25
|
## Banned characters
|
|
25
26
|
|
|
26
|
-
`
|
|
27
|
+
`markdown.md` holds the character bans and `prose.md` holds the banned words. Read both at scan time rather than working them from memory, then scan the drafted text and rewrite each occurrence.
|
|
27
28
|
|
|
28
29
|
Restructure the sentence rather than substituting the character. A semicolon swapped for a period leaves both clauses in the order the semicolon chose, which is the shape the ban exists to remove.
|
|
29
30
|
|