@erclx/aitk 0.23.0 → 0.25.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-roadmap/references/roadmap.md +12 -2
- package/claude/skills/create-standard/references/snippets.md +9 -0
- package/claude/skills/git-branch/references/branch.md +11 -0
- package/claude/skills/git-commit/references/commit.md +11 -1
- package/claude/skills/git-issue/references/issue.md +10 -0
- package/claude/skills/git-pr/references/branch.md +11 -0
- package/claude/skills/git-pr/references/pr.md +11 -0
- package/claude/skills/git-split/references/branch.md +11 -0
- package/claude/skills/git-split/references/pr.md +11 -0
- package/claude/skills/git-stage/references/commit.md +11 -1
- package/claude/skills/setup-init/SKILL.md +1 -1
- package/docs/agents.md +11 -3
- package/docs/target-projects.md +4 -2
- package/package.json +1 -1
- package/src/commands/sync.ts +5 -1
- package/src/sync/check.ts +6 -0
- package/src/sync/engine.ts +73 -3
- package/src/sync/history.ts +123 -0
- package/standards/architecture.md +10 -2
- package/standards/bundled/branch.md +11 -0
- package/standards/bundled/commit.md +11 -1
- package/standards/bundled/issue.md +10 -0
- package/standards/bundled/pr.md +11 -0
- package/standards/bundled/roadmap.md +12 -2
- package/standards/bundled/snippets.md +9 -0
- package/standards/context.md +11 -0
- package/standards/design.md +9 -1
- package/standards/diagrams.md +13 -2
- package/standards/prose.md +11 -0
- package/standards/readme.md +9 -0
- package/standards/requirements.md +10 -1
- package/standards/rule.md +10 -0
- package/standards/skill.md +11 -0
- package/standards/standard.md +24 -4
- package/standards/tasks.md +11 -0
- package/standards/versioning.md +11 -0
- package/standards/wireframes.md +11 -1
|
@@ -10,6 +10,17 @@ Applies to `.claude/ROADMAP.md`. Sequences the scope from `.claude/REQUIREMENTS.
|
|
|
10
10
|
|
|
11
11
|
The roadmap is one scannable table. That is what keeps it distinct from `.claude/tasks/`: the roadmap is an overview read at a glance, while tasks are worked one file at a time.
|
|
12
12
|
|
|
13
|
+
## Scope
|
|
14
|
+
|
|
15
|
+
Governs the sequencing document at `.claude/ROADMAP.md`: the version table, its columns, and its lifecycle.
|
|
16
|
+
|
|
17
|
+
Does not govern:
|
|
18
|
+
|
|
19
|
+
- What the scope is, which the roadmap sequences rather than defines: `requirements.md`
|
|
20
|
+
- Task files, outcomes, and board state: `tasks.md`
|
|
21
|
+
- Phase-label format and semver discipline: `versioning.md`
|
|
22
|
+
- Rationale for a technical choice: `architecture.md`
|
|
23
|
+
|
|
13
24
|
## What goes in
|
|
14
25
|
|
|
15
26
|
- One row per version, ordered top to bottom by sequence
|
|
@@ -20,9 +31,8 @@ The roadmap is one scannable table. That is what keeps it distinct from `.claude
|
|
|
20
31
|
|
|
21
32
|
## What does not go in
|
|
22
33
|
|
|
23
|
-
- Task breakdown, checkboxes, or per-feature file lists
|
|
34
|
+
- Task breakdown, checkboxes, or per-feature file lists
|
|
24
35
|
- Implementation detail, API names, or component references
|
|
25
|
-
- Rationale for tech choices. That belongs in `.claude/ARCHITECTURE.md`.
|
|
26
36
|
- Sentence-long cells. Keep each cell terse so the table stays scannable.
|
|
27
37
|
|
|
28
38
|
## Format
|
|
@@ -6,6 +6,15 @@ consumers: create-standard
|
|
|
6
6
|
|
|
7
7
|
# Snippet reference
|
|
8
8
|
|
|
9
|
+
## Scope
|
|
10
|
+
|
|
11
|
+
Governs a snippet file: what one is for, how it is invoked, and the structure of its body.
|
|
12
|
+
|
|
13
|
+
Does not govern:
|
|
14
|
+
|
|
15
|
+
- Skill folders, which carry frontmatter, references, and scripts a snippet has none of: `skill.md`
|
|
16
|
+
- Voice, punctuation, and formatting in snippet prose: `prose.md`
|
|
17
|
+
|
|
9
18
|
## What a snippet is
|
|
10
19
|
|
|
11
20
|
A snippet is a short, focused prompt stored as a plain markdown file. Invoke one to insert a prepared instruction into any AI chat without retyping it. Each snippet covers one purpose. If a prompt needs headers or multiple goals, use a system prompt instead.
|
|
@@ -6,6 +6,17 @@ consumers: git-branch, git-split, git-pr
|
|
|
6
6
|
|
|
7
7
|
# Branch reference
|
|
8
8
|
|
|
9
|
+
## Scope
|
|
10
|
+
|
|
11
|
+
Governs a git branch name: its structure, its length, and the type vocabulary it draws from.
|
|
12
|
+
|
|
13
|
+
Does not govern:
|
|
14
|
+
|
|
15
|
+
- Commit subject format, which shares the type vocabulary: `commit.md`
|
|
16
|
+
- Pull request title and body: `pr.md`
|
|
17
|
+
- Whether a phase label may appear in a branch name: `versioning.md`
|
|
18
|
+
- Deriving an output filename from a branch name, which is a transform the skill running it owns
|
|
19
|
+
|
|
9
20
|
## Format
|
|
10
21
|
|
|
11
22
|
- Structure: `<type>/<description>` or `<type>/<ticket>-<description>`
|
|
@@ -6,6 +6,16 @@ consumers: git-commit, git-stage
|
|
|
6
6
|
|
|
7
7
|
# Commit message reference
|
|
8
8
|
|
|
9
|
+
## Scope
|
|
10
|
+
|
|
11
|
+
Governs a git commit message: subject structure, the type and scope vocabulary, and the body.
|
|
12
|
+
|
|
13
|
+
Does not govern:
|
|
14
|
+
|
|
15
|
+
- Branch naming, which shares the type vocabulary: `branch.md`
|
|
16
|
+
- Pull request title and body, which share the subject form: `pr.md`
|
|
17
|
+
- Whether a phase label or a semver tag may appear in a subject: `versioning.md`
|
|
18
|
+
|
|
9
19
|
## Format
|
|
10
20
|
|
|
11
21
|
- Structure: `<type>(<scope>): <subject>`
|
|
@@ -26,7 +36,7 @@ consumers: git-commit, git-stage
|
|
|
26
36
|
- `ci`: CI/CD pipeline changes (GitHub Actions)
|
|
27
37
|
- `revert`: revert a previous commit
|
|
28
38
|
|
|
29
|
-
## Scope
|
|
39
|
+
## Scope vocabulary
|
|
30
40
|
|
|
31
41
|
- Single lowercase word representing a system component
|
|
32
42
|
- Prefer single word
|
|
@@ -6,6 +6,16 @@ consumers: git-issue
|
|
|
6
6
|
|
|
7
7
|
# Issue reference
|
|
8
8
|
|
|
9
|
+
## Scope
|
|
10
|
+
|
|
11
|
+
Governs a tracker issue: its title, its labels, and the sections its body carries.
|
|
12
|
+
|
|
13
|
+
Does not govern:
|
|
14
|
+
|
|
15
|
+
- Pull request title and body: `pr.md`
|
|
16
|
+
- Whether a phase label may appear in issue text: `versioning.md`
|
|
17
|
+
- Voice, punctuation, and banned words in issue prose: `prose.md`
|
|
18
|
+
|
|
9
19
|
## Title
|
|
10
20
|
|
|
11
21
|
- Format: `<type>: <subject>`
|
|
@@ -6,6 +6,17 @@ consumers: git-branch, git-split, git-pr
|
|
|
6
6
|
|
|
7
7
|
# Branch reference
|
|
8
8
|
|
|
9
|
+
## Scope
|
|
10
|
+
|
|
11
|
+
Governs a git branch name: its structure, its length, and the type vocabulary it draws from.
|
|
12
|
+
|
|
13
|
+
Does not govern:
|
|
14
|
+
|
|
15
|
+
- Commit subject format, which shares the type vocabulary: `commit.md`
|
|
16
|
+
- Pull request title and body: `pr.md`
|
|
17
|
+
- Whether a phase label may appear in a branch name: `versioning.md`
|
|
18
|
+
- Deriving an output filename from a branch name, which is a transform the skill running it owns
|
|
19
|
+
|
|
9
20
|
## Format
|
|
10
21
|
|
|
11
22
|
- Structure: `<type>/<description>` or `<type>/<ticket>-<description>`
|
|
@@ -6,6 +6,17 @@ consumers: git-split, git-pr
|
|
|
6
6
|
|
|
7
7
|
# Pull request reference
|
|
8
8
|
|
|
9
|
+
## Scope
|
|
10
|
+
|
|
11
|
+
Governs a pull request title and body: their format and the sections the body carries.
|
|
12
|
+
|
|
13
|
+
Does not govern:
|
|
14
|
+
|
|
15
|
+
- Commit subject format, which shares the title form: `commit.md`
|
|
16
|
+
- Branch naming: `branch.md`
|
|
17
|
+
- Whether a phase label or a semver tag may appear in a title or body: `versioning.md`
|
|
18
|
+
- Voice, punctuation, and banned words in pull request prose: `prose.md`
|
|
19
|
+
|
|
9
20
|
## Title
|
|
10
21
|
|
|
11
22
|
- Format: `<type>(<scope>): <subject>`
|
|
@@ -6,6 +6,17 @@ consumers: git-branch, git-split, git-pr
|
|
|
6
6
|
|
|
7
7
|
# Branch reference
|
|
8
8
|
|
|
9
|
+
## Scope
|
|
10
|
+
|
|
11
|
+
Governs a git branch name: its structure, its length, and the type vocabulary it draws from.
|
|
12
|
+
|
|
13
|
+
Does not govern:
|
|
14
|
+
|
|
15
|
+
- Commit subject format, which shares the type vocabulary: `commit.md`
|
|
16
|
+
- Pull request title and body: `pr.md`
|
|
17
|
+
- Whether a phase label may appear in a branch name: `versioning.md`
|
|
18
|
+
- Deriving an output filename from a branch name, which is a transform the skill running it owns
|
|
19
|
+
|
|
9
20
|
## Format
|
|
10
21
|
|
|
11
22
|
- Structure: `<type>/<description>` or `<type>/<ticket>-<description>`
|
|
@@ -6,6 +6,17 @@ consumers: git-split, git-pr
|
|
|
6
6
|
|
|
7
7
|
# Pull request reference
|
|
8
8
|
|
|
9
|
+
## Scope
|
|
10
|
+
|
|
11
|
+
Governs a pull request title and body: their format and the sections the body carries.
|
|
12
|
+
|
|
13
|
+
Does not govern:
|
|
14
|
+
|
|
15
|
+
- Commit subject format, which shares the title form: `commit.md`
|
|
16
|
+
- Branch naming: `branch.md`
|
|
17
|
+
- Whether a phase label or a semver tag may appear in a title or body: `versioning.md`
|
|
18
|
+
- Voice, punctuation, and banned words in pull request prose: `prose.md`
|
|
19
|
+
|
|
9
20
|
## Title
|
|
10
21
|
|
|
11
22
|
- Format: `<type>(<scope>): <subject>`
|
|
@@ -6,6 +6,16 @@ consumers: git-commit, git-stage
|
|
|
6
6
|
|
|
7
7
|
# Commit message reference
|
|
8
8
|
|
|
9
|
+
## Scope
|
|
10
|
+
|
|
11
|
+
Governs a git commit message: subject structure, the type and scope vocabulary, and the body.
|
|
12
|
+
|
|
13
|
+
Does not govern:
|
|
14
|
+
|
|
15
|
+
- Branch naming, which shares the type vocabulary: `branch.md`
|
|
16
|
+
- Pull request title and body, which share the subject form: `pr.md`
|
|
17
|
+
- Whether a phase label or a semver tag may appear in a subject: `versioning.md`
|
|
18
|
+
|
|
9
19
|
## Format
|
|
10
20
|
|
|
11
21
|
- Structure: `<type>(<scope>): <subject>`
|
|
@@ -26,7 +36,7 @@ consumers: git-commit, git-stage
|
|
|
26
36
|
- `ci`: CI/CD pipeline changes (GitHub Actions)
|
|
27
37
|
- `revert`: revert a previous commit
|
|
28
38
|
|
|
29
|
-
## Scope
|
|
39
|
+
## Scope vocabulary
|
|
30
40
|
|
|
31
41
|
- Single lowercase word representing a system component
|
|
32
42
|
- Prefer single word
|
|
@@ -63,7 +63,7 @@ Before executing, output:
|
|
|
63
63
|
|
|
64
64
|
## Execute
|
|
65
65
|
|
|
66
|
-
Run the chain in order. Each step's permission dialog is the confirmation gate. Run from the target project's current directory.
|
|
66
|
+
Run the chain in order, starting immediately after the preview. Each step's permission dialog is the confirmation gate. Do not pause for additional confirmation. Run from the target project's current directory.
|
|
67
67
|
|
|
68
68
|
Step 1: `aitk init` installs base tooling, claude seeds, governance rules, standards, snippets, and wiki.
|
|
69
69
|
|
package/docs/agents.md
CHANGED
|
@@ -163,8 +163,16 @@ matches what the toolkit installed, `customized` when the project edited it,
|
|
|
163
163
|
when the project authored it, or `drifted` when no stamp covers it. Use `--json`
|
|
164
164
|
for the machine-readable report and `--exit-code` to fail a CI job. Orphaned
|
|
165
165
|
files are excluded from that exit code, since a project-authored rule never
|
|
166
|
-
converges. Attribution
|
|
167
|
-
writes.
|
|
166
|
+
converges. Attribution reads `.claude/aitk.json`, which every install and sync
|
|
167
|
+
writes.
|
|
168
|
+
|
|
169
|
+
A target installed before stamping shipped has no such file, and the report
|
|
170
|
+
falls back to the toolkit's own git history. Installed content matching any
|
|
171
|
+
version that history ever published proves the file is untouched, so it reports
|
|
172
|
+
`stale` naming the commit it came from, and content matching no published
|
|
173
|
+
version stays `drifted`. A toolkit reached outside a git clone, which is what a
|
|
174
|
+
registry install is, cannot run that fallback and reports
|
|
175
|
+
`historyUnavailable` alongside the unattributed files.
|
|
168
176
|
|
|
169
177
|
Each domain carries its own toolkit anchor in that file, so syncing one domain
|
|
170
178
|
never advances the revision another measures from, and each reports the upstream
|
|
@@ -238,7 +246,7 @@ The record carries every scenario with the arms that declare, plus `totalScenari
|
|
|
238
246
|
|
|
239
247
|
`--skills` answers what the scenario count cannot, which is whether anything can fail a given skill. It adds `skills`, `totalSkills`, `asserted`, `shouldBeAsserted`, `exempt`, `staleExemptions`, and `supersededExemptions` to the record, and keeps the scenario view rather than replacing it. The two denominators disagree on purpose: an armed scenario under `infra/` or `tooling/` exercises a CLI domain and pairs with no skill at all.
|
|
240
248
|
|
|
241
|
-
A skill pairs to a scenario by filename, `<category>-<command>` first and bare `<command>` second, so `claude/setup-init.sh` reaches the `setup-init` skill. `should-be-asserted` is the default and is the
|
|
249
|
+
A skill pairs to a scenario by filename, `<category>-<command>` first and bare `<command>` second, so `claude/setup-init.sh` reaches the `setup-init` skill. `should-be-asserted` is the default rather than a queue to drain, and which of those skills earns an arm is a project decision the census does not make. `exempt` means no arm should be written and holds only with a reason, declared in `scripts/sandbox/exempt.toml` and limited to a harness limit the checker cannot reach past or a skill that writes no artifact. An armed arm outranks an exemption. An exemption naming no shipped skill, or naming one an arm now asserts, exits 1 without `--strict`. Each armed arm reports as `<category>:<command>/<arm>`, so two same-named arms under different scenarios stay distinct.
|
|
242
250
|
|
|
243
251
|
`scripts/sandbox/run.sh` calls this after a headless run and merges the verdict into the envelope it prints. It also writes that merged record to `.claude/.tmp/sandbox-runs/<target>-<arm>-<timestamp>.json` with a `writes` array appended, and logs the path on stderr. Both fields are what a later re-score needs, since `--envelope` and `--writes` read files the run deletes on exit. Stdout carries the same bytes it did before the record existed.
|
|
244
252
|
|
package/docs/target-projects.md
CHANGED
|
@@ -100,7 +100,9 @@ When the toolkit updates, target projects pull changes per domain. There is one
|
|
|
100
100
|
|
|
101
101
|
`aitk sync --check <path>` reports what has drifted without writing anything. It splits each difference by cause, which is the question that decides what to do next. A `stale` file still matches what the toolkit installed, so the update is mechanical. A `customized` file carries local edits, so taking the upstream version is a decision and `aitk:claude-seed-sync` is the tool for it. A `stranded` file sits where an older toolkit installed it and the toolkit has since moved, which is what `aitk:migration-standards` handles.
|
|
102
102
|
|
|
103
|
-
That attribution comes from `.claude/aitk.json`, a stamp every install and sync writes recording a hash per installed file. Each domain holds its own toolkit commit, so syncing governance today does not move the revision standards measures against, and each domain reports the upstream commits touching its own source path.
|
|
103
|
+
That attribution comes from `.claude/aitk.json`, a stamp every install and sync writes recording a hash per installed file. Each domain holds its own toolkit commit, so syncing governance today does not move the revision standards measures against, and each domain reports the upstream commits touching its own source path. Running any sync stamps that domain, and the report names the ones still unstamped.
|
|
104
|
+
|
|
105
|
+
A project that has never synced under a toolkit new enough to write a stamp falls back to the toolkit's own git history. Installed content matching any version that history published proves the file untouched, so it reports `stale` naming the commit it came from, and content matching no published version stays `drifted`. That fallback needs the toolkit as a git checkout. Installed from the registry it ships source without history, and the report says attribution was unavailable rather than reading every file as a local edit.
|
|
104
106
|
|
|
105
107
|
Add `--json` for the machine-readable report, and `--exit-code` to fail a CI job when a target falls behind. Files the project authored itself never count toward that exit code.
|
|
106
108
|
|
|
@@ -110,7 +112,7 @@ Tooling is not covered by the stamp. Reconcile those configs with `aitk tooling
|
|
|
110
112
|
|
|
111
113
|
`aitk sync <path>` runs every installed domain's sync in sequence. Safe to run on a cadence. It never touches user-owned seed files. Governance rules in `.claude/rules/`, tooling configs, and reference docs refresh in place. Stale `.claude/GOV.md` from earlier installs is removed.
|
|
112
114
|
|
|
113
|
-
Standards are the exception inside that run, and the stamp narrows it. A standard the project customized is reported and left alone rather than overwritten. A standard still matching what was installed carries no local edits to lose, so a headless run updates it.
|
|
115
|
+
Standards are the exception inside that run, and the stamp narrows it. A standard the project customized is reported and left alone rather than overwritten. A standard still matching what was installed carries no local edits to lose, so a headless run updates it. An unstamped project reaches the same split through the history fallback, so a headless run updates every standard it can prove untouched and refuses while any file resists attribution. To take the upstream version of a customized file, run `aitk standards sync <path>` interactively, or use `aitk:claude-seed-sync` below to merge section by section.
|
|
114
116
|
|
|
115
117
|
### Targeted
|
|
116
118
|
|
package/package.json
CHANGED
package/src/commands/sync.ts
CHANGED
|
@@ -99,7 +99,11 @@ function renderCheck(report: CheckReport): void {
|
|
|
99
99
|
logStep(domain.domain)
|
|
100
100
|
|
|
101
101
|
if (domain.commit === undefined) {
|
|
102
|
-
logWarn(
|
|
102
|
+
logWarn(
|
|
103
|
+
domain.historyUnavailable
|
|
104
|
+
? 'Not stamped, and this toolkit has no git history. Drift below is reported unattributed.'
|
|
105
|
+
: 'Not stamped. Drift below is attributed from toolkit history.',
|
|
106
|
+
)
|
|
103
107
|
} else {
|
|
104
108
|
logInfo(`Synced from ${domain.commit} on ${domain.syncedAt}`)
|
|
105
109
|
}
|
package/src/sync/check.ts
CHANGED
|
@@ -54,6 +54,11 @@ export interface DomainReport {
|
|
|
54
54
|
readonly counts: StateCounts
|
|
55
55
|
readonly entries: readonly ScanEntry[]
|
|
56
56
|
readonly upstream: readonly UpstreamCommit[]
|
|
57
|
+
/**
|
|
58
|
+
* Separates a toolkit that could not attribute from one that attributed and
|
|
59
|
+
* found a local edit. Only the first is a capability the install lacks.
|
|
60
|
+
*/
|
|
61
|
+
readonly historyUnavailable: boolean
|
|
57
62
|
}
|
|
58
63
|
|
|
59
64
|
export interface UpstreamCommit {
|
|
@@ -145,6 +150,7 @@ async function buildDomainReport(
|
|
|
145
150
|
syncedAt: record?.syncedAt,
|
|
146
151
|
counts: countStates(plan.entries),
|
|
147
152
|
entries: plan.entries,
|
|
153
|
+
historyUnavailable: plan.historyUnavailable,
|
|
148
154
|
upstream:
|
|
149
155
|
since === undefined
|
|
150
156
|
? []
|
package/src/sync/engine.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { existsSync, readFileSync } from 'node:fs'
|
|
|
2
2
|
import { rm } from 'node:fs/promises'
|
|
3
3
|
import { isAbsolute, relative, resolve } from 'node:path'
|
|
4
4
|
import { copyPreservingMode } from '@/copy'
|
|
5
|
+
import { findInstalledOrigin, readHistoryIndex } from '@/sync/history'
|
|
5
6
|
import {
|
|
6
7
|
type DomainHashes,
|
|
7
8
|
hashFile,
|
|
@@ -74,12 +75,16 @@ export type EntryState =
|
|
|
74
75
|
export interface ScanEntry {
|
|
75
76
|
readonly state: EntryState
|
|
76
77
|
readonly rel: string
|
|
78
|
+
/** Toolkit revision this file's content came from, when history proved it. */
|
|
79
|
+
readonly since?: string
|
|
77
80
|
}
|
|
78
81
|
|
|
79
82
|
export interface SyncPlan {
|
|
80
83
|
readonly entries: readonly ScanEntry[]
|
|
81
84
|
readonly retired: readonly RetiredSurface[]
|
|
82
85
|
readonly changes: readonly SyncChange[]
|
|
86
|
+
/** Set when a file needed history to attribute it and this toolkit has none. */
|
|
87
|
+
readonly historyUnavailable: boolean
|
|
83
88
|
}
|
|
84
89
|
|
|
85
90
|
/**
|
|
@@ -156,6 +161,7 @@ export function listInstalled(root: string, target: string): InstalledFile[] {
|
|
|
156
161
|
export function planSync(adapter: SyncAdapter, target: string): SyncPlan {
|
|
157
162
|
const entries: ScanEntry[] = []
|
|
158
163
|
const changes: SyncChange[] = []
|
|
164
|
+
const unattributed: UnattributedFile[] = []
|
|
159
165
|
|
|
160
166
|
const hashes = stampedHashes(readStamp(target), adapter.stamp?.domain)
|
|
161
167
|
const walked = new Set<string>()
|
|
@@ -176,7 +182,12 @@ export function planSync(adapter: SyncAdapter, target: string): SyncPlan {
|
|
|
176
182
|
continue
|
|
177
183
|
}
|
|
178
184
|
|
|
179
|
-
|
|
185
|
+
const state = attribute(hashes, file)
|
|
186
|
+
if (state === 'drifted') {
|
|
187
|
+
unattributed.push({ index: entries.length, source, path: file.path })
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
entries.push({ state, rel: file.rel })
|
|
180
191
|
changes.push({
|
|
181
192
|
kind: 'copy',
|
|
182
193
|
source,
|
|
@@ -185,6 +196,8 @@ export function planSync(adapter: SyncAdapter, target: string): SyncPlan {
|
|
|
185
196
|
})
|
|
186
197
|
}
|
|
187
198
|
|
|
199
|
+
const historyUnavailable = recoverAttribution(adapter, entries, unattributed)
|
|
200
|
+
|
|
188
201
|
entries.push(...strandedByRelocation(target, hashes, walked))
|
|
189
202
|
|
|
190
203
|
const retired = adapter.collectRetired?.(target) ?? []
|
|
@@ -192,7 +205,53 @@ export function planSync(adapter: SyncAdapter, target: string): SyncPlan {
|
|
|
192
205
|
changes.push({ kind: 'delete', dest: surface.path, rel: surface.rel })
|
|
193
206
|
}
|
|
194
207
|
|
|
195
|
-
return { entries, retired, changes }
|
|
208
|
+
return { entries, retired, changes, historyUnavailable }
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
interface UnattributedFile {
|
|
212
|
+
readonly index: number
|
|
213
|
+
readonly source: string
|
|
214
|
+
readonly path: string
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Second pass over the files the stamp could not attribute, which is every file
|
|
219
|
+
* in a target installed before stamping shipped. Matching the installed content
|
|
220
|
+
* against the toolkit's own history recovers the fact a stamp would have held,
|
|
221
|
+
* and a file matching no published version stays unattributed.
|
|
222
|
+
*
|
|
223
|
+
* Runs as one git call for the whole domain rather than one per file, and only
|
|
224
|
+
* when the first pass left something to attribute. Reports whether history was
|
|
225
|
+
* readable at all, so a registry install can say why it fell short instead of
|
|
226
|
+
* reporting every file as a local edit.
|
|
227
|
+
*/
|
|
228
|
+
function recoverAttribution(
|
|
229
|
+
adapter: SyncAdapter,
|
|
230
|
+
entries: ScanEntry[],
|
|
231
|
+
unattributed: readonly UnattributedFile[],
|
|
232
|
+
): boolean {
|
|
233
|
+
const toolkitRoot = adapter.stamp?.toolkitRoot
|
|
234
|
+
if (toolkitRoot === undefined || unattributed.length === 0) return false
|
|
235
|
+
|
|
236
|
+
const index = readHistoryIndex(
|
|
237
|
+
toolkitRoot,
|
|
238
|
+
unattributed.map((file) => relative(toolkitRoot, file.source)),
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
if (index === undefined) return true
|
|
242
|
+
|
|
243
|
+
for (const file of unattributed) {
|
|
244
|
+
const since = findInstalledOrigin(
|
|
245
|
+
index,
|
|
246
|
+
relative(toolkitRoot, file.source),
|
|
247
|
+
file.path,
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
if (since === undefined) continue
|
|
251
|
+
entries[file.index] = { ...entries[file.index], state: 'stale', since }
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return false
|
|
196
255
|
}
|
|
197
256
|
|
|
198
257
|
export async function applyChanges(
|
|
@@ -314,7 +373,12 @@ function report(adapter: SyncAdapter, plan: SyncPlan): void {
|
|
|
314
373
|
for (const entry of plan.entries) {
|
|
315
374
|
if (entry.state === 'matching') logInfo(entry.rel)
|
|
316
375
|
else if (entry.state === 'drifted') logWarn(entry.rel)
|
|
317
|
-
else if (entry.state === 'stale')
|
|
376
|
+
else if (entry.state === 'stale')
|
|
377
|
+
logWarn(
|
|
378
|
+
entry.since === undefined
|
|
379
|
+
? `${entry.rel} (toolkit updated)`
|
|
380
|
+
: `${entry.rel} (toolkit updated since ${entry.since.slice(0, 7)})`,
|
|
381
|
+
)
|
|
318
382
|
else if (entry.state === 'customized')
|
|
319
383
|
logWarn(`${entry.rel} (locally customized)`)
|
|
320
384
|
else if (entry.state === 'stranded')
|
|
@@ -325,6 +389,12 @@ function report(adapter: SyncAdapter, plan: SyncPlan): void {
|
|
|
325
389
|
for (const surface of plan.retired) {
|
|
326
390
|
logWarn(surface.notice)
|
|
327
391
|
}
|
|
392
|
+
|
|
393
|
+
if (plan.historyUnavailable) {
|
|
394
|
+
logWarn(
|
|
395
|
+
'Attribution unavailable: this toolkit has no git history to match against.',
|
|
396
|
+
)
|
|
397
|
+
}
|
|
328
398
|
}
|
|
329
399
|
|
|
330
400
|
function sameContent(left: string, right: string): boolean {
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto'
|
|
2
|
+
import { readFileSync } from 'node:fs'
|
|
3
|
+
import { sep } from 'node:path'
|
|
4
|
+
import { execaSync } from 'execa'
|
|
5
|
+
import { gitEnv } from '@/git-env'
|
|
6
|
+
|
|
7
|
+
/** Blob SHA to the newest commit whose post-image for that path held it. */
|
|
8
|
+
export type PathHistory = ReadonlyMap<string, string>
|
|
9
|
+
|
|
10
|
+
/** Source path relative to the toolkit root, to that path's known blobs. */
|
|
11
|
+
export type HistoryIndex = ReadonlyMap<string, PathHistory>
|
|
12
|
+
|
|
13
|
+
const EMPTY_BLOB = '0000000000000000000000000000000000000000'
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Git's own object name for a file's contents: sha1 over `blob <len>\0` and the
|
|
17
|
+
* bytes. Computed here rather than shelled out to `git hash-object`, which would
|
|
18
|
+
* cost a process per file inside the attribution pass.
|
|
19
|
+
*
|
|
20
|
+
* A repository normalizing line endings on checkout stores a blob the working
|
|
21
|
+
* tree never holds, so this returns a name that matches no historical version.
|
|
22
|
+
* The caller treats an unmatched file as unattributed, which is the safe label.
|
|
23
|
+
*/
|
|
24
|
+
export function gitBlobHash(content: Buffer): string {
|
|
25
|
+
const header = Buffer.from(`blob ${content.length}\0`, 'utf8')
|
|
26
|
+
return createHash('sha1').update(header).update(content).digest('hex')
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Reads `--raw` log output into blob-to-commit maps, one per path.
|
|
31
|
+
*
|
|
32
|
+
* Reverse-chronological input means the first commit naming a blob is the
|
|
33
|
+
* newest that produced it, so an existing key is never overwritten. Content
|
|
34
|
+
* reverted and restored therefore reports the recent commit rather than the
|
|
35
|
+
* original, which is the one an operator can act on.
|
|
36
|
+
*/
|
|
37
|
+
export function parseRawLog(output: string): HistoryIndex {
|
|
38
|
+
const index = new Map<string, Map<string, string>>()
|
|
39
|
+
let commit = ''
|
|
40
|
+
|
|
41
|
+
for (const line of output.split('\n')) {
|
|
42
|
+
if (line === '') continue
|
|
43
|
+
|
|
44
|
+
if (!line.startsWith(':')) {
|
|
45
|
+
commit = line.trim()
|
|
46
|
+
continue
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const [meta, path] = line.split('\t')
|
|
50
|
+
if (path === undefined || commit === '') continue
|
|
51
|
+
|
|
52
|
+
const blob = meta.trim().split(/\s+/)[3]
|
|
53
|
+
if (blob === undefined || blob === EMPTY_BLOB) continue
|
|
54
|
+
|
|
55
|
+
const blobs = index.get(path) ?? new Map<string, string>()
|
|
56
|
+
if (!blobs.has(blob)) blobs.set(blob, commit)
|
|
57
|
+
index.set(path, blobs)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return index
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Every historical version of the given toolkit-relative paths, or `undefined`
|
|
65
|
+
* when this toolkit has no history to read. A package installed from the
|
|
66
|
+
* registry ships source without `.git`, so absence is an ordinary state rather
|
|
67
|
+
* than a failure, and the caller degrades to the unattributed path.
|
|
68
|
+
*
|
|
69
|
+
* Renames are disabled so a raw line always carries exactly one path. Detection
|
|
70
|
+
* would emit a two-path form that the parser would read as an unknown blob.
|
|
71
|
+
*/
|
|
72
|
+
export function readHistoryIndex(
|
|
73
|
+
toolkitRoot: string,
|
|
74
|
+
paths: readonly string[],
|
|
75
|
+
): HistoryIndex | undefined {
|
|
76
|
+
if (paths.length === 0) return new Map()
|
|
77
|
+
|
|
78
|
+
const result = execaSync(
|
|
79
|
+
'git',
|
|
80
|
+
[
|
|
81
|
+
'-C',
|
|
82
|
+
toolkitRoot,
|
|
83
|
+
'log',
|
|
84
|
+
'--all',
|
|
85
|
+
'--raw',
|
|
86
|
+
'--no-renames',
|
|
87
|
+
'--no-abbrev',
|
|
88
|
+
'--relative',
|
|
89
|
+
'--format=%H',
|
|
90
|
+
'--',
|
|
91
|
+
...paths.map(toRepoPath),
|
|
92
|
+
],
|
|
93
|
+
{ reject: false, env: gitEnv(), extendEnv: false },
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
return result.exitCode === 0 ? parseRawLog(result.stdout) : undefined
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Git prints and accepts posix separators on every platform, so a Windows
|
|
101
|
+
* caller's `relative()` output has to be converted before it can match a path
|
|
102
|
+
* the log emitted.
|
|
103
|
+
*/
|
|
104
|
+
function toRepoPath(path: string): string {
|
|
105
|
+
return path.split(sep).join('/')
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* The commit whose version of `sourceRel` matches what sits at `installedPath`.
|
|
110
|
+
* A match proves the file is untouched since it was installed, so the toolkit is
|
|
111
|
+
* what moved. No match means the content matches nothing this toolkit ever
|
|
112
|
+
* published, which is a local edit and stays unattributed.
|
|
113
|
+
*/
|
|
114
|
+
export function findInstalledOrigin(
|
|
115
|
+
index: HistoryIndex,
|
|
116
|
+
sourceRel: string,
|
|
117
|
+
installedPath: string,
|
|
118
|
+
): string | undefined {
|
|
119
|
+
const blobs = index.get(toRepoPath(sourceRel))
|
|
120
|
+
if (blobs === undefined) return undefined
|
|
121
|
+
|
|
122
|
+
return blobs.get(gitBlobHash(readFileSync(installedPath)))
|
|
123
|
+
}
|
|
@@ -7,6 +7,16 @@ description: Shape and content rules for .claude/ARCHITECTURE.md
|
|
|
7
7
|
|
|
8
8
|
Applies to `.claude/ARCHITECTURE.md`. Describes the system shape and the decisions behind it, not a tutorial, setup guide, or implementation walkthrough. Pair it with `CLAUDE.md`: principles live there, patterns and decisions live here. Update when a decision is made or a risk is resolved.
|
|
9
9
|
|
|
10
|
+
## Scope
|
|
11
|
+
|
|
12
|
+
Governs the system-shape document at `.claude/ARCHITECTURE.md`: the overview, the decision entries, and the open risks.
|
|
13
|
+
|
|
14
|
+
Does not govern:
|
|
15
|
+
|
|
16
|
+
- Per-domain structure and narrative: `context.md`
|
|
17
|
+
- Setup commands and install instructions: `readme.md`
|
|
18
|
+
- Product scope, goals, and non-goals: `requirements.md`
|
|
19
|
+
|
|
10
20
|
## What goes in
|
|
11
21
|
|
|
12
22
|
- A high-level overview of how the system is structured and why
|
|
@@ -15,8 +25,6 @@ Applies to `.claude/ARCHITECTURE.md`. Describes the system shape and the decisio
|
|
|
15
25
|
|
|
16
26
|
## What does not go in
|
|
17
27
|
|
|
18
|
-
- Per-domain structure and narrative. That belongs in `.claude/context/<domain>.md`, one file per domain.
|
|
19
|
-
- Setup commands and install instructions. Those live in the README.
|
|
20
28
|
- How individual functions work line by line. The code carries its own behavior.
|
|
21
29
|
- Full type definitions. They live in code. Reference the shape conceptually if needed.
|
|
22
30
|
|
|
@@ -6,6 +6,17 @@ consumers: git-branch, git-split, git-pr
|
|
|
6
6
|
|
|
7
7
|
# Branch reference
|
|
8
8
|
|
|
9
|
+
## Scope
|
|
10
|
+
|
|
11
|
+
Governs a git branch name: its structure, its length, and the type vocabulary it draws from.
|
|
12
|
+
|
|
13
|
+
Does not govern:
|
|
14
|
+
|
|
15
|
+
- Commit subject format, which shares the type vocabulary: `commit.md`
|
|
16
|
+
- Pull request title and body: `pr.md`
|
|
17
|
+
- Whether a phase label may appear in a branch name: `versioning.md`
|
|
18
|
+
- Deriving an output filename from a branch name, which is a transform the skill running it owns
|
|
19
|
+
|
|
9
20
|
## Format
|
|
10
21
|
|
|
11
22
|
- Structure: `<type>/<description>` or `<type>/<ticket>-<description>`
|
|
@@ -6,6 +6,16 @@ consumers: git-commit, git-stage
|
|
|
6
6
|
|
|
7
7
|
# Commit message reference
|
|
8
8
|
|
|
9
|
+
## Scope
|
|
10
|
+
|
|
11
|
+
Governs a git commit message: subject structure, the type and scope vocabulary, and the body.
|
|
12
|
+
|
|
13
|
+
Does not govern:
|
|
14
|
+
|
|
15
|
+
- Branch naming, which shares the type vocabulary: `branch.md`
|
|
16
|
+
- Pull request title and body, which share the subject form: `pr.md`
|
|
17
|
+
- Whether a phase label or a semver tag may appear in a subject: `versioning.md`
|
|
18
|
+
|
|
9
19
|
## Format
|
|
10
20
|
|
|
11
21
|
- Structure: `<type>(<scope>): <subject>`
|
|
@@ -26,7 +36,7 @@ consumers: git-commit, git-stage
|
|
|
26
36
|
- `ci`: CI/CD pipeline changes (GitHub Actions)
|
|
27
37
|
- `revert`: revert a previous commit
|
|
28
38
|
|
|
29
|
-
## Scope
|
|
39
|
+
## Scope vocabulary
|
|
30
40
|
|
|
31
41
|
- Single lowercase word representing a system component
|
|
32
42
|
- Prefer single word
|
|
@@ -6,6 +6,16 @@ consumers: git-issue
|
|
|
6
6
|
|
|
7
7
|
# Issue reference
|
|
8
8
|
|
|
9
|
+
## Scope
|
|
10
|
+
|
|
11
|
+
Governs a tracker issue: its title, its labels, and the sections its body carries.
|
|
12
|
+
|
|
13
|
+
Does not govern:
|
|
14
|
+
|
|
15
|
+
- Pull request title and body: `pr.md`
|
|
16
|
+
- Whether a phase label may appear in issue text: `versioning.md`
|
|
17
|
+
- Voice, punctuation, and banned words in issue prose: `prose.md`
|
|
18
|
+
|
|
9
19
|
## Title
|
|
10
20
|
|
|
11
21
|
- Format: `<type>: <subject>`
|
package/standards/bundled/pr.md
CHANGED
|
@@ -6,6 +6,17 @@ consumers: git-split, git-pr
|
|
|
6
6
|
|
|
7
7
|
# Pull request reference
|
|
8
8
|
|
|
9
|
+
## Scope
|
|
10
|
+
|
|
11
|
+
Governs a pull request title and body: their format and the sections the body carries.
|
|
12
|
+
|
|
13
|
+
Does not govern:
|
|
14
|
+
|
|
15
|
+
- Commit subject format, which shares the title form: `commit.md`
|
|
16
|
+
- Branch naming: `branch.md`
|
|
17
|
+
- Whether a phase label or a semver tag may appear in a title or body: `versioning.md`
|
|
18
|
+
- Voice, punctuation, and banned words in pull request prose: `prose.md`
|
|
19
|
+
|
|
9
20
|
## Title
|
|
10
21
|
|
|
11
22
|
- Format: `<type>(<scope>): <subject>`
|
|
@@ -10,6 +10,17 @@ Applies to `.claude/ROADMAP.md`. Sequences the scope from `.claude/REQUIREMENTS.
|
|
|
10
10
|
|
|
11
11
|
The roadmap is one scannable table. That is what keeps it distinct from `.claude/tasks/`: the roadmap is an overview read at a glance, while tasks are worked one file at a time.
|
|
12
12
|
|
|
13
|
+
## Scope
|
|
14
|
+
|
|
15
|
+
Governs the sequencing document at `.claude/ROADMAP.md`: the version table, its columns, and its lifecycle.
|
|
16
|
+
|
|
17
|
+
Does not govern:
|
|
18
|
+
|
|
19
|
+
- What the scope is, which the roadmap sequences rather than defines: `requirements.md`
|
|
20
|
+
- Task files, outcomes, and board state: `tasks.md`
|
|
21
|
+
- Phase-label format and semver discipline: `versioning.md`
|
|
22
|
+
- Rationale for a technical choice: `architecture.md`
|
|
23
|
+
|
|
13
24
|
## What goes in
|
|
14
25
|
|
|
15
26
|
- One row per version, ordered top to bottom by sequence
|
|
@@ -20,9 +31,8 @@ The roadmap is one scannable table. That is what keeps it distinct from `.claude
|
|
|
20
31
|
|
|
21
32
|
## What does not go in
|
|
22
33
|
|
|
23
|
-
- Task breakdown, checkboxes, or per-feature file lists
|
|
34
|
+
- Task breakdown, checkboxes, or per-feature file lists
|
|
24
35
|
- Implementation detail, API names, or component references
|
|
25
|
-
- Rationale for tech choices. That belongs in `.claude/ARCHITECTURE.md`.
|
|
26
36
|
- Sentence-long cells. Keep each cell terse so the table stays scannable.
|
|
27
37
|
|
|
28
38
|
## Format
|
|
@@ -6,6 +6,15 @@ consumers: create-standard
|
|
|
6
6
|
|
|
7
7
|
# Snippet reference
|
|
8
8
|
|
|
9
|
+
## Scope
|
|
10
|
+
|
|
11
|
+
Governs a snippet file: what one is for, how it is invoked, and the structure of its body.
|
|
12
|
+
|
|
13
|
+
Does not govern:
|
|
14
|
+
|
|
15
|
+
- Skill folders, which carry frontmatter, references, and scripts a snippet has none of: `skill.md`
|
|
16
|
+
- Voice, punctuation, and formatting in snippet prose: `prose.md`
|
|
17
|
+
|
|
9
18
|
## What a snippet is
|
|
10
19
|
|
|
11
20
|
A snippet is a short, focused prompt stored as a plain markdown file. Invoke one to insert a prepared instruction into any AI chat without retyping it. Each snippet covers one purpose. If a prompt needs headers or multiple goals, use a system prompt instead.
|
package/standards/context.md
CHANGED
|
@@ -7,6 +7,17 @@ description: Shape and content rules for .claude/context/<domain>.md entries
|
|
|
7
7
|
|
|
8
8
|
Applies to per-domain narrative entries under `.claude/context/`. Skip for `index.md`, which is regenerated by `aitk indexes regen`.
|
|
9
9
|
|
|
10
|
+
## Scope
|
|
11
|
+
|
|
12
|
+
Governs per-domain narrative entries under `.claude/context/`: their structure, the decisions and gotchas they carry, and what they leave to the code.
|
|
13
|
+
|
|
14
|
+
Does not govern:
|
|
15
|
+
|
|
16
|
+
- Cross-domain decisions and system-wide risks: `architecture.md`
|
|
17
|
+
- Product scope, goals, and non-goals: `requirements.md`
|
|
18
|
+
- Path-scoped coding rules: `rule.md`
|
|
19
|
+
- Diagrams and wireframes, which answer structure and layout questions an entry hands off: `diagrams.md` and `wireframes.md`
|
|
20
|
+
|
|
10
21
|
## Organizing principle
|
|
11
22
|
|
|
12
23
|
Weight the entry toward what cannot be re-derived from the repo. That single rule sorts every section below.
|
package/standards/design.md
CHANGED
|
@@ -7,6 +7,15 @@ description: Shape and content rules for .claude/DESIGN.md
|
|
|
7
7
|
|
|
8
8
|
Applies to `.claude/DESIGN.md`. Captures visual intent and the decisions behind how things look, not a style guide, component spec, or framework reference. Update when a visual decision is made or a rule changes.
|
|
9
9
|
|
|
10
|
+
## Scope
|
|
11
|
+
|
|
12
|
+
Governs the visual-intent document at `.claude/DESIGN.md`: tokens described as intent, layout constraints, and the omissions that keep visual scope closed.
|
|
13
|
+
|
|
14
|
+
Does not govern:
|
|
15
|
+
|
|
16
|
+
- Screen layout, on-screen copy, and interaction intent: `wireframes.md`
|
|
17
|
+
- Per-domain implementation narrative: `context.md`
|
|
18
|
+
|
|
10
19
|
## What goes in
|
|
11
20
|
|
|
12
21
|
- Tokens described as intent ("mid gray, muted text"), not computed values. Exact values live in code.
|
|
@@ -17,7 +26,6 @@ Applies to `.claude/DESIGN.md`. Captures visual intent and the decisions behind
|
|
|
17
26
|
## What does not go in
|
|
18
27
|
|
|
19
28
|
- CSS classes, computed values, component filenames, and prop names. Those live in code.
|
|
20
|
-
- UX copy and interaction flows. Those live in the wireframes.
|
|
21
29
|
- Anything that needs updating every time the code is refactored
|
|
22
30
|
|
|
23
31
|
## Format
|
package/standards/diagrams.md
CHANGED
|
@@ -9,6 +9,17 @@ Applies to per-kind entries under `.claude/diagrams/`. Skip for `index.md`, whic
|
|
|
9
9
|
|
|
10
10
|
A diagram entry answers one question about the system with one or more Mermaid diagrams and the prose that makes them readable. It is not a rendering of the file tree. The check for any single line: does it tell a reader something the code layout would not have told them? If not, it belongs in `.claude/context/`.
|
|
11
11
|
|
|
12
|
+
## Scope
|
|
13
|
+
|
|
14
|
+
Governs per-kind diagram entries under `.claude/diagrams/`: which question each answers, the Mermaid source, the accessibility fields, and the explanation prose beneath. It states the voice for that prose, which is the yield `prose.md` grants a surface whose own standard sets one.
|
|
15
|
+
|
|
16
|
+
Does not govern:
|
|
17
|
+
|
|
18
|
+
- Punctuation, formatting, and language in explanation prose and node labels: `prose.md`, whose bans the yield does not lift
|
|
19
|
+
- The mechanism behind any component a diagram draws: `context.md`
|
|
20
|
+
- UI layout, on-screen copy, and interaction intent: `wireframes.md`
|
|
21
|
+
- The decision record a components diagram is drawn from: `architecture.md`
|
|
22
|
+
|
|
12
23
|
## What a working entry looks like
|
|
13
24
|
|
|
14
25
|
An entry works when a reader who has not opened the repository can answer its question:
|
|
@@ -82,7 +93,7 @@ A second entry for one kind takes a suffixed name (`request-flow-admin.md`) and
|
|
|
82
93
|
- Do not duplicate prose across entries. An entry that restates its neighbor has taken the neighbor's job.
|
|
83
94
|
- 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.
|
|
84
95
|
|
|
85
|
-
This section states the voice for the surface, which is what claims the yield `
|
|
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. Punctuation, formatting, and language bans stay in force.
|
|
86
97
|
|
|
87
98
|
## Verification
|
|
88
99
|
|
|
@@ -109,5 +120,5 @@ Reference the context entry by path when a reader needs the mechanism. The diagr
|
|
|
109
120
|
- `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.
|
|
110
121
|
- 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.
|
|
111
122
|
- 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.
|
|
112
|
-
- Mermaid
|
|
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.
|
|
113
124
|
- 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.
|
package/standards/prose.md
CHANGED
|
@@ -7,6 +7,17 @@ description: Voice, structure, formatting, and language rules for reference mark
|
|
|
7
7
|
|
|
8
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. Punctuation, formatting, and language rules below stay in force on every surface, including the surfaces no automated check reaches, which is what the scan below is for.
|
|
9
9
|
|
|
10
|
+
## Scope
|
|
11
|
+
|
|
12
|
+
Governs voice, punctuation, formatting, and word choice 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, and yields on voice alone where that standard states one.
|
|
13
|
+
|
|
14
|
+
Does not govern:
|
|
15
|
+
|
|
16
|
+
- What sections a document has, or what belongs in each: the standard for that document type
|
|
17
|
+
- 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
|
+
- Phase-label and semver discipline: `versioning.md`
|
|
19
|
+
- Code style and language conventions, which are governance rules rather than a standard
|
|
20
|
+
|
|
10
21
|
## Voice
|
|
11
22
|
|
|
12
23
|
- Write for a developer who is scanning, not studying. Every sentence should be understandable on first read.
|
package/standards/readme.md
CHANGED
|
@@ -9,6 +9,15 @@ Applies to every `README.md`. The `## Voice` section states the voice for a repo
|
|
|
9
9
|
|
|
10
10
|
The reader is what changes. Reference prose serves someone who already committed to the project and is scanning for a fact. A root README meets someone deciding whether to commit at all, and it is often the only file they read.
|
|
11
11
|
|
|
12
|
+
## Scope
|
|
13
|
+
|
|
14
|
+
Governs every `README.md`: voice, heading structure, required and optional sections, badge selection, and what the page links out to instead of carrying.
|
|
15
|
+
|
|
16
|
+
Does not govern:
|
|
17
|
+
|
|
18
|
+
- Punctuation, formatting, spelling, and banned words in README prose: `prose.md`, which yields the voice and keeps the rest
|
|
19
|
+
- Product scope and goals: `requirements.md`
|
|
20
|
+
|
|
12
21
|
## Voice
|
|
13
22
|
|
|
14
23
|
Scoped to the README at a repository root. A nested README documenting a folder, a harness, or an internal tool keeps the reference voice in `prose.md`, since its reader has already committed and arrived looking for a fact.
|
|
@@ -7,6 +7,16 @@ description: Shape and content rules for .claude/REQUIREMENTS.md
|
|
|
7
7
|
|
|
8
8
|
Applies to `.claude/REQUIREMENTS.md`. Describes what the product does and why, not how it works. Update when scope changes, goals shift, or a non-goal is promoted to a feature.
|
|
9
9
|
|
|
10
|
+
## Scope
|
|
11
|
+
|
|
12
|
+
Governs the product-scope document at `.claude/REQUIREMENTS.md`: problem, goals, non-goals, MVP features, distribution, stack, and constraints.
|
|
13
|
+
|
|
14
|
+
Does not govern:
|
|
15
|
+
|
|
16
|
+
- Rationale for a technical choice: `architecture.md`
|
|
17
|
+
- Sequencing the scope into ordered versions: `roadmap.md`
|
|
18
|
+
- Per-domain structure and narrative: `context.md`
|
|
19
|
+
|
|
10
20
|
## What goes in
|
|
11
21
|
|
|
12
22
|
- The problem being solved and for whom
|
|
@@ -19,7 +29,6 @@ Applies to `.claude/REQUIREMENTS.md`. Describes what the product does and why, n
|
|
|
19
29
|
## What does not go in
|
|
20
30
|
|
|
21
31
|
- Implementation details, API names, or internal component references
|
|
22
|
-
- Rationale for tech choices. That belongs in `.claude/ARCHITECTURE.md`.
|
|
23
32
|
- Anything that describes how a feature is built rather than what it does
|
|
24
33
|
|
|
25
34
|
## Sections
|
package/standards/rule.md
CHANGED
|
@@ -9,6 +9,16 @@ description: Rule frontmatter, body shape, and voice for .claude/rules files
|
|
|
9
9
|
|
|
10
10
|
Rules give Claude Code coding constraints scoped to file paths. Claude Code discovers `.claude/rules/**/*.md` at session start. A rule with no `paths:` field always applies, at the same priority as `CLAUDE.md`. A rule with `paths:` applies when Claude reads a file matching the glob. Author one rule per topic so the scope stays precise.
|
|
11
11
|
|
|
12
|
+
## Scope
|
|
13
|
+
|
|
14
|
+
Governs governance rules under `.claude/rules/`: their location, numbering, frontmatter, and body shape.
|
|
15
|
+
|
|
16
|
+
Does not govern:
|
|
17
|
+
|
|
18
|
+
- Authoring conventions for a document type, which are standards rather than rules. A rule points at the standard that owns one and never restates it.
|
|
19
|
+
- Skill folders and skill frontmatter: `skill.md`
|
|
20
|
+
- Cross-domain behavior rules, which live in `CLAUDE.md` at the project root
|
|
21
|
+
|
|
12
22
|
## Location
|
|
13
23
|
|
|
14
24
|
- Rules live at `.claude/rules/<subdirectory>/<n>-<slug>.md`
|
package/standards/skill.md
CHANGED
|
@@ -9,6 +9,17 @@ description: Claude skill structure and authoring rules
|
|
|
9
9
|
|
|
10
10
|
Skills give Claude Code domain-specific constraints and rules inline, so it can act immediately without reading all docs. Each skill body contains actionable rules for its domain. Full reference docs are the fallback for edge cases and deeper context. Skills use progressive disclosure: Claude reads only frontmatter at session start (~100 tokens each), matches a query against descriptions, then loads the full skill body.
|
|
11
11
|
|
|
12
|
+
## Scope
|
|
13
|
+
|
|
14
|
+
Governs a skill folder as one artifact: `SKILL.md`, its optional sibling `REQUIREMENT.md`, and the bundled `references/`, `scripts/`, and `assets/` beside them.
|
|
15
|
+
|
|
16
|
+
Does not govern:
|
|
17
|
+
|
|
18
|
+
- Path-scoped coding rules, which load on a file match rather than on a request match: `rule.md`
|
|
19
|
+
- Single-purpose chat prompts carrying no frontmatter, references, or scripts: `snippets.md`
|
|
20
|
+
- Voice, punctuation, and formatting in a skill body: `prose.md`
|
|
21
|
+
- The domain conventions a skill cites, each of which belongs to the standard that owns it
|
|
22
|
+
|
|
12
23
|
## Skill types
|
|
13
24
|
|
|
14
25
|
Pick the type before writing. It decides the body shape.
|
package/standards/standard.md
CHANGED
|
@@ -9,22 +9,42 @@ Applies to each authored standard in the folder. Skip for `index.md`, which is g
|
|
|
9
9
|
|
|
10
10
|
## Overview
|
|
11
11
|
|
|
12
|
-
A standard is a target-facing authoring convention for one document type. It installs into a project under `.claude/standards/` and is consumed by skills and developers alike. This file governs itself, so every rule below applies to it.
|
|
12
|
+
A standard is a target-facing authoring convention for one document type, or for one attribute carried across every document. It installs into a project under `.claude/standards/` and is consumed by skills and developers alike. This file governs itself, so every rule below applies to it.
|
|
13
|
+
|
|
14
|
+
## Scope
|
|
15
|
+
|
|
16
|
+
Governs what an authored standard contains: its stated jurisdiction, success criterion, frontmatter, structure, and rule phrasing.
|
|
17
|
+
|
|
18
|
+
Does not govern:
|
|
19
|
+
|
|
20
|
+
- The voice, punctuation, and formatting a standard is written in: `prose.md`
|
|
21
|
+
- The shape of any artifact a standard governs, which is that standard's own subject
|
|
13
22
|
|
|
14
23
|
## What a working standard looks like
|
|
15
24
|
|
|
16
25
|
A standard answers these questions. Each can be answered wrong, which is what makes them a test rather than a preamble.
|
|
17
26
|
|
|
18
|
-
- Which single document type does this govern, and where does
|
|
27
|
+
- Which single document type or attribute does this govern, and where does it apply?
|
|
19
28
|
- Can an author who has seen no example produce a conforming document from this file alone?
|
|
20
29
|
- Does every rule state a shape the document must have, rather than a fact about the repository that happens to store it?
|
|
21
30
|
- What does a conforming document achieve, stated so a reviewer can call one non-conforming without appealing to taste?
|
|
22
31
|
|
|
23
32
|
A standard failing these questions is non-conforming even when it satisfies every shape rule below.
|
|
24
33
|
|
|
25
|
-
##
|
|
34
|
+
## Scoping rules
|
|
35
|
+
|
|
36
|
+
### Declaring scope
|
|
37
|
+
|
|
38
|
+
- Govern one document type per standard, or one attribute across every document. Split unrelated conventions into separate files.
|
|
39
|
+
- Open with a `## Scope` section stating what the standard governs and what it does not, placed above the shape rules. A standard that specifies shape exhaustively and jurisdiction nowhere cannot refuse a rule, so the rule with no obvious owner lands in whichever standard sits nearest.
|
|
40
|
+
- Write it as one line naming the artifact or attribute and where it applies, then a `Does not govern:` list. Give each entry the excluded concern and the owner it goes to. Name a sibling standard by bare filename, since standards install as siblings, and name the surface instead where the owner is one, such as a coding rule, a project policy, or the code.
|
|
41
|
+
- Cut an entry that names no owner at all. It is either excluding something nothing was going to claim, or it is a content exclusion, which the rule below sends to the shape rules instead.
|
|
42
|
+
- Declare a boundary from both sides. A yield, an exemption, or a handoff stated in one standard alone is never checked against the standard on the other side of it, which is how two files come to claim the same rule or neither does.
|
|
43
|
+
- Separate a jurisdiction exclusion from a content exclusion. The first names a concern another standard owns and belongs in `## Scope`. The second names what does not belong inside the document and stays with the shape rules. Merging them puts a boundary claim where no sibling will read it.
|
|
44
|
+
- Stay silent on a section the standard holds today but should not own. Claiming it makes the scope statement false the moment it moves, and the mismatch is the evidence that moves it.
|
|
45
|
+
|
|
46
|
+
### Staying inside it
|
|
26
47
|
|
|
27
|
-
- Govern one document type per standard. Split unrelated conventions into separate files.
|
|
28
48
|
- Name no path, filename, or folder outside the document type the standard governs. A standard reaches projects whose layout is their own, so a path borrowed from the authoring repository is wrong in a target and nothing reports it.
|
|
29
49
|
- State the rule, never the mechanism enforcing it. Hooks, scripts, checks, and skill catalogs are facts about one repository. Name the condition the document must meet and let the enforcing surface name its own case.
|
|
30
50
|
- Invent inline examples rather than citing a real file elsewhere in the project. A cited file moves or is deleted and the standard goes stale in silence.
|
package/standards/tasks.md
CHANGED
|
@@ -9,6 +9,17 @@ Applies to `.claude/tasks/`. Tracks what is being built and why, at the level of
|
|
|
9
9
|
|
|
10
10
|
The folder is gitignored. Board state changes when work ships rather than when a branch is written, so committing it would put a claim about the future into the diff of an unrelated pull request. The git log records what shipped.
|
|
11
11
|
|
|
12
|
+
## Scope
|
|
13
|
+
|
|
14
|
+
Governs the task board under `.claude/tasks/`: folder layout, filenames, frontmatter, file format, origin lines, and archiving.
|
|
15
|
+
|
|
16
|
+
Does not govern:
|
|
17
|
+
|
|
18
|
+
- Phase-label format and which surfaces a label may appear on: `versioning.md`
|
|
19
|
+
- Sequencing across versions and why the order is what it is: `roadmap.md`
|
|
20
|
+
- Architectural reasoning that outlives a task: `architecture.md`
|
|
21
|
+
- When a project opens a task at all, which is project policy rather than a shape rule
|
|
22
|
+
|
|
12
23
|
## Layout
|
|
13
24
|
|
|
14
25
|
```plaintext
|
package/standards/versioning.md
CHANGED
|
@@ -7,6 +7,17 @@ description: Phase label vs semver discipline across tasks, PRs, reviews, issues
|
|
|
7
7
|
|
|
8
8
|
Two namespaces, kept separate.
|
|
9
9
|
|
|
10
|
+
## Scope
|
|
11
|
+
|
|
12
|
+
Governs the two version namespaces, phase labels and semver tags, and which surfaces each may appear on. It is an attribute standard rather than a document-type one, so it applies wherever either namespace is written.
|
|
13
|
+
|
|
14
|
+
Does not govern:
|
|
15
|
+
|
|
16
|
+
- The format of a phase label, which is project-specific by the rule below
|
|
17
|
+
- Task filenames and board layout: `tasks.md`
|
|
18
|
+
- Commit subject, branch name, and pull request title format: `commit.md`, `branch.md`, and `pr.md`
|
|
19
|
+
- Voice, punctuation, and formatting in any text carrying a label: `prose.md`
|
|
20
|
+
|
|
10
21
|
## Phase labels
|
|
11
22
|
|
|
12
23
|
Internal coordination vocabulary used in the task board and chat.
|
package/standards/wireframes.md
CHANGED
|
@@ -9,6 +9,16 @@ Applies to per-surface entries under `.claude/wireframes/`. Skip for `index.md`,
|
|
|
9
9
|
|
|
10
10
|
A wireframe file captures the spatial layout and interaction intent of one UI surface. It is not an implementation log. The check for any single line: would it still be true if the surface were rebuilt in a different framework? If not, it belongs in `.claude/context/`.
|
|
11
11
|
|
|
12
|
+
## Scope
|
|
13
|
+
|
|
14
|
+
Governs per-surface wireframe entries under `.claude/wireframes/`: layout, variants, on-screen copy, and interaction intent.
|
|
15
|
+
|
|
16
|
+
Does not govern:
|
|
17
|
+
|
|
18
|
+
- Tokens, typography, spacing, and the rest of the visual system: `design.md`
|
|
19
|
+
- The mechanism behind a surface: `context.md`
|
|
20
|
+
- Voice, punctuation, and formatting in wireframe prose: `prose.md`
|
|
21
|
+
|
|
12
22
|
## What a working wireframe looks like
|
|
13
23
|
|
|
14
24
|
A wireframe works when someone can rebuild the surface from it without opening the components:
|
|
@@ -64,4 +74,4 @@ Reference the context entry from the wireframe by path when a reader needs the m
|
|
|
64
74
|
## Maintenance
|
|
65
75
|
|
|
66
76
|
- When a surface's layout or interaction changes, update its wireframe file in the same PR. A wireframe showing a defunct layout is worse than none.
|
|
67
|
-
-
|
|
77
|
+
- The Behavior and Copy prose around an ASCII block is prose and follows `prose.md`. The fenced block itself is not, so a check scoped to prose is the wrong thing to rely on for what sits inside it.
|