@erclx/aitk 0.59.0 → 0.60.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-memory-capture/SKILL.md +10 -24
- package/claude/skills/claude-memory-review/SKILL.md +3 -1
- package/docs/agents/commands.md +1 -1
- package/docs/agents/records.md +7 -3
- package/docs/ai-workflow.md +1 -1
- package/governance/rules/claude/559-memory.md +20 -0
- package/package.json +1 -1
- package/src/commands/records.ts +2 -0
- package/src/records/validate.ts +177 -9
- package/standards/index.md +1 -0
- package/standards/memory.md +131 -0
- package/tooling/claude/seeds/CLAUDE.md +2 -5
|
@@ -9,6 +9,8 @@ Scan the current session for patterns worth persisting, send each to the surface
|
|
|
9
9
|
|
|
10
10
|
A fact about a domain belongs in that domain's context entry, which the three-tier model already loads on demand. Writing it to memory instead puts it in a folder nothing opens. Routing is therefore the point of this skill and the memory file is the fallback.
|
|
11
11
|
|
|
12
|
+
The filename and its type prefix, the frontmatter, the body shape each type carries, and the lifecycle are fixed by `.claude/standards/memory.md`, or `${CLAUDE_SKILL_DIR}/../../standards/memory.md` when the project does not have it. Read it before writing an entry and follow it rather than working the shape from memory.
|
|
13
|
+
|
|
12
14
|
## Guards
|
|
13
15
|
|
|
14
16
|
- All `.claude/memory/` reads and writes resolve at the main worktree root, not the current worktree. See Worktrees in `CLAUDE.md`.
|
|
@@ -20,7 +22,8 @@ A fact about a domain belongs in that domain's context entry, which the three-ti
|
|
|
20
22
|
|
|
21
23
|
Read in parallel from the project root, skipping any that do not exist:
|
|
22
24
|
|
|
23
|
-
-
|
|
25
|
+
- `.claude/standards/memory.md`: the filename, frontmatter, body shape, and lifecycle every entry follows
|
|
26
|
+
- `CLAUDE.md`: the project's write location and any rule it states over the folder
|
|
24
27
|
- `.claude/memory/index.md`: existing index, to avoid duplicates
|
|
25
28
|
- `.claude/context/index.md`: the domain catalog Step 3 routes against
|
|
26
29
|
- `.claude/standards/prose.md`: voice and banned words applied to memory file bodies
|
|
@@ -30,14 +33,9 @@ Read a standard from `${CLAUDE_SKILL_DIR}/../../standards/` instead when the pro
|
|
|
30
33
|
|
|
31
34
|
## Step 2: classify candidates
|
|
32
35
|
|
|
33
|
-
Scan the session and group candidate patterns
|
|
34
|
-
|
|
35
|
-
- **feedback**: explicit user corrections, stated preferences, or non-obvious confirmations
|
|
36
|
-
- **project**: decisions, initiatives, deadlines, or motivations not derivable from git or code
|
|
37
|
-
- **user**: role, expertise, responsibilities, or working preferences
|
|
38
|
-
- **reference**: pointers to external systems (dashboards, trackers, channels)
|
|
36
|
+
Scan the session and group candidate patterns as `feedback`, `project`, `user`, or `reference`. What each type holds and what makes one fire are the Types table in `.claude/standards/memory.md`. Read the table and classify against it rather than against a recollection of the four names.
|
|
39
37
|
|
|
40
|
-
|
|
38
|
+
Scan the whole session rather than its last exchange. A rule the user stated early and you followed since reads as settled and is exactly the one no file records.
|
|
41
39
|
|
|
42
40
|
## Step 3: route what a context entry owns
|
|
43
41
|
|
|
@@ -65,26 +63,14 @@ For each remaining candidate, grep `.claude/memory/` for an existing file on the
|
|
|
65
63
|
|
|
66
64
|
## Step 5: write the residue
|
|
67
65
|
|
|
68
|
-
|
|
66
|
+
Write each remaining candidate to `.claude/memory/<type>-<slug>.md`, following the template and the shape rules in `.claude/standards/memory.md`. Copy the shape from there rather than from this body, so one edit to the standard moves every entry.
|
|
69
67
|
|
|
70
|
-
|
|
71
|
-
---
|
|
72
|
-
title: <one-line human title, as it should read in the index>
|
|
73
|
-
description: <one-line description per .claude/standards/prose.md § Frontmatter descriptions>
|
|
74
|
-
category: <Feedback|Project|User|Reference>
|
|
75
|
-
---
|
|
76
|
-
|
|
77
|
-
<memory body>
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
`category` is the type in sentence case, which is what the index renderer groups on, while the filename keeps the lowercase `<type>-` prefix. A description opening with a backtick or a colon needs single quotes, or the frontmatter fails to parse and the index goes stale.
|
|
81
|
-
|
|
82
|
-
Feedback and project bodies must be three lines: the rule or fact on one line, a `**Why:**` line naming the session signal, and a `**How to apply:**` line for when the rule fires next. Keep each line tight. No narrative.
|
|
83
|
-
|
|
84
|
-
User and reference bodies are a single sentence each.
|
|
68
|
+
Two of its rules are the ones a capture pass gets wrong under time pressure. State the rule rather than the incident that produced it, since the session ending is the only reader who has the narrative. Write the `title` as the rule itself, never as the filename stem.
|
|
85
69
|
|
|
86
70
|
Do not edit the index. `.claude/memory/index.md` is generated from sibling frontmatter by a `PostToolUse` hook, the same way the task board's index is, so a hand-appended row is drift the next regeneration discards.
|
|
87
71
|
|
|
72
|
+
Run `aitk records validate memory` when the writes are done and fix what it names. It reads the whole pen rather than this session's writes, so treat a finding on a carried entry as one to fix in place rather than as a reason to stop.
|
|
73
|
+
|
|
88
74
|
## Output
|
|
89
75
|
|
|
90
76
|
Respond with one line per fact routed, written, or updated:
|
|
@@ -7,6 +7,8 @@ description: Reviews `.claude/memory/` and proposes per-entry actions (promote t
|
|
|
7
7
|
|
|
8
8
|
This skill drives the full memory review lifecycle in five phases. Pick the phase from what the user said and whether a review receipt already exists at `<main-root>/.claude/review/memory-review-*.md`.
|
|
9
9
|
|
|
10
|
+
What an entry looks like and why a retired one is moved rather than deleted are fixed by `.claude/standards/memory.md`, or `${CLAUDE_SKILL_DIR}/../../standards/memory.md` when the project does not have it. Read it before rewriting an entry, since a promotion rewrites the rule and a rewrite has to leave the entry conforming.
|
|
11
|
+
|
|
10
12
|
| User intent | Phase | Mutates |
|
|
11
13
|
| ---------------------------------------------------------------------- | --------- | ---------------------------- |
|
|
12
14
|
| "review memory", "promote memory", "sweep stale memories" (no receipt) | Propose | review file only |
|
|
@@ -68,7 +70,7 @@ For each in-scope entry (see Scope), pick one action:
|
|
|
68
70
|
- **Hand off to governance**: the rule is coding-standards class (typescript, testing, naming, error-handling, performance, logging, concurrency, planning). Do not author the rule file inline. In the toolkit repo, point the user at `aitk-governance` and `.claude/standards/rule.md`, which own the source-of-truth rules under `governance/rules/`. In a target project, point the user at the `create-rule` skill, which scaffolds a project-local rule under `.claude/rules/`. Never edit the synced `.claude/rules/` copies of toolkit rules, because `aitk gov sync` overwrites them. Stop at handoff.
|
|
69
71
|
- **Retire**: the rule is stale, already absorbed into a durable surface, too vague to phrase as a rule, or a one-time incident narrative. Apply moves the file to `.claude/.tmp/memory-archive/` rather than deleting it.
|
|
70
72
|
|
|
71
|
-
Retire is an archive, not a deletion
|
|
73
|
+
Retire is an archive, not a deletion, which `.claude/standards/memory.md` states as the rule and this skill executes. The archive is worth less than a plan's, since a promoted entry survives in its destination and a stale one is discarded on purpose, which is why the move is cheap rather than free.
|
|
72
74
|
|
|
73
75
|
When two or more memories collapse into one rule on the same target, propose them as a single merged edit under the matching promote category. The consolidate case is a variant of promote, not a separate action.
|
|
74
76
|
|
package/docs/agents/commands.md
CHANGED
|
@@ -28,7 +28,7 @@ Full help: `aitk <command> --help`. Behavior notes for the install and sync verb
|
|
|
28
28
|
| `aitk transcripts <url>` | Fetch a YouTube transcript with metadata frontmatter (needs `yt-dlp`) |
|
|
29
29
|
| `aitk tasks archive` | Move a shipped task off the board, clear its ordering row, and regenerate the index |
|
|
30
30
|
| `aitk tasks validate` | Report board rows whose plan, task file, group, or file set does not hold (`--json`) |
|
|
31
|
-
| `aitk records validate` | Report a
|
|
31
|
+
| `aitk records validate` | Report a session record against the standard governing it, per kind (`--json`) |
|
|
32
32
|
| `aitk comments scan` | Measure comment density by language and comment kind, with a trend recomputed from git |
|
|
33
33
|
| `aitk context audit` | Report required sections, length, depth, bullet weight, cited paths, provenance, and drift |
|
|
34
34
|
| `aitk claude skills audit` | Report both skill corpora against the mechanical rules in `standards/skill.md` |
|
package/docs/agents/records.md
CHANGED
|
@@ -7,10 +7,11 @@ description: Validating the gitignored session records under .claude/, the per-k
|
|
|
7
7
|
|
|
8
8
|
## Validate
|
|
9
9
|
|
|
10
|
-
`aitk records validate <kind>` reports where a session record and the standard governing it disagree. The
|
|
10
|
+
`aitk records validate <kind>` reports where a session record and the standard governing it disagree. The four kinds are `plans`, `groundwork`, `intake`, and `memory`, each a gitignored folder under `.claude/`.
|
|
11
11
|
|
|
12
12
|
```bash
|
|
13
13
|
aitk records validate plans
|
|
14
|
+
aitk records validate memory
|
|
14
15
|
aitk records validate intake --json
|
|
15
16
|
```
|
|
16
17
|
|
|
@@ -30,10 +31,13 @@ Nothing fires it automatically. The folders are gitignored, so the standards-aud
|
|
|
30
31
|
| `plans` | A filename that is not `feature-<slug>.md`, a missing `# Feature:` heading, a missing required section, a files-to-touch entry naming no file or saying nothing about one, and a question carrying no suggestion or no answer slot |
|
|
31
32
|
| `groundwork` | A track with no `README.md` or no `01-current-state.md`, a file missing `title` or `description`, a `README.md` with no `date` as `YYYY-MM-DD`, an unnumbered file, and a track holding a decision without its handoff or the reverse |
|
|
32
33
|
| `intake` | A dump with no `00-overview.md`, the same frontmatter and numbering checks, an item missing any of `Problem`, `Fix`, `Worth it`, or `You`, and an item carrying `Open` with no `Suggested` |
|
|
34
|
+
| `memory` | A filename whose prefix names none of the four types, an entry missing `title`, `description`, or `category`, a `category` disagreeing with that prefix, a title repeating the filename, and a rule-bearing body missing a part |
|
|
33
35
|
|
|
34
36
|
The half-closed track is the groundwork check a reader cannot run by eye. A folder holding `06` without `07` reads as closed to anyone scanning filenames while the file a returning session actually opens is absent.
|
|
35
37
|
|
|
36
|
-
The item check skips `00-overview.md` and `99-next-session.md`, since neither holds items and running it over the handoff would report every heading it carries.
|
|
38
|
+
The item check skips `00-overview.md` and `99-next-session.md`, since neither holds items and running it over the handoff would report every heading it carries. The memory walk skips `index.md` for the same reason, since the catalog is generated from its siblings rather than authored as an entry.
|
|
39
|
+
|
|
40
|
+
A memory `category` is compared against the sentence-case form of the filename prefix rather than checked field by field, so one finding covers a prefix outside the four types, a field disagreeing with the prefix, and a casing drift that would open a second group in the catalog. The body check runs on `feedback` and `project` entries alone, because a `user` or `reference` entry is a single sentence by design and has no rule to apply.
|
|
37
41
|
|
|
38
42
|
A plan section opens as a bold label or as an H2 and the check counts both, naming the standard's spelling when it reports one missing. The corpus splits roughly four to one between the two forms, so failing the variant would report nearly every plan on a rule that costs a reader nothing.
|
|
39
43
|
|
|
@@ -51,4 +55,4 @@ Skills branch on the findings rather than on the exit code:
|
|
|
51
55
|
aitk records validate plans --json | jq -r '.findings[] | "\(.kind): \(.subject)"'
|
|
52
56
|
```
|
|
53
57
|
|
|
54
|
-
For the shapes each check enforces, see `.claude/standards/plan.md`, `.claude/standards/groundwork.md`, and `.claude/standards/
|
|
58
|
+
For the shapes each check enforces, see `.claude/standards/plan.md`, `.claude/standards/groundwork.md`, `.claude/standards/intake.md`, and `.claude/standards/memory.md`.
|
package/docs/ai-workflow.md
CHANGED
|
@@ -81,7 +81,7 @@ Before a handoff, the orchestrator checks the plan against the tree rather than
|
|
|
81
81
|
|
|
82
82
|
The plan's shape is fixed by `.claude/standards/plan.md`: the section list, the filename, the lifecycle, and the contract its questions keep. Every question carries a `- Suggested:` line and an empty `- Answer:` slot, and a blank answer accepts the suggestion at execution time. That default is what makes a plan decision-ready in one pass, and it is the opposite of the contract an intake folder keeps, where an empty slot means nobody reached the item.
|
|
83
83
|
|
|
84
|
-
`aitk records validate plans` reports where a plan and that standard disagree: a filename that is not `feature-<slug>.md`, a missing required section, a files-to-touch entry naming no file, and a question carrying a suggestion with no answer slot. The same verb takes `groundwork` and `
|
|
84
|
+
`aitk records validate plans` reports where a plan and that standard disagree: a filename that is not `feature-<slug>.md`, a missing required section, a files-to-touch entry naming no file, and a question carrying a suggestion with no answer slot. The same verb takes `groundwork`, `intake`, and `memory`, which are governed the same way and were unreachable for the same reason. Nothing fires it automatically, because all four folders are gitignored and every check the repository runs reads changed files from git. It reports and never writes, since the folders are per-machine scratch with no history to recover a wrong repair from.
|
|
85
85
|
|
|
86
86
|
A plan that ships is archived, never deleted. `aitk:claude-docs` moves it to `.claude/plans-archive/` and retargets the task file's `Plan:` line at the new location, so a completed task still leads to the reasoning behind it. Both folders are gitignored, which is why a deleted plan had no recovery path. A plan cited by more than one task stays put until the last of them closes, since moving it early would strand every other pointer.
|
|
87
87
|
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Route .claude/memory/ edits to the memory standard for the filename, frontmatter, body shape, and lifecycle
|
|
3
|
+
paths:
|
|
4
|
+
- '.claude/memory/**'
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Memory standards
|
|
8
|
+
|
|
9
|
+
## Routing
|
|
10
|
+
|
|
11
|
+
- Write no memory entry for a fact a per-domain context entry already owns. Memory keeps the residue, which in practice is feedback about how to work.
|
|
12
|
+
|
|
13
|
+
## The pen
|
|
14
|
+
|
|
15
|
+
- Never delete a memory entry. Retire one by moving it to an archive under its own name, since the folder is gitignored and a wrong call has no undo.
|
|
16
|
+
- Never hand-edit `.claude/memory/index.md`. A hook regenerates it from sibling frontmatter.
|
|
17
|
+
|
|
18
|
+
## Authority
|
|
19
|
+
|
|
20
|
+
- Follow `.claude/standards/memory.md` for the filename and type prefix, the frontmatter, the body shape per type, links between entries, and the lifecycle. It is the single source.
|
package/package.json
CHANGED
package/src/commands/records.ts
CHANGED
|
@@ -55,6 +55,7 @@ export function register(program: Command): void {
|
|
|
55
55
|
' plans filename, required sections, and the suggested-and-answer contract',
|
|
56
56
|
' groundwork README and current-state files, numbering, dating, and a half-closed track',
|
|
57
57
|
' intake overview file, numbering, dating, and the four bullets every item carries',
|
|
58
|
+
' memory filename and type prefix, frontmatter, and the body shape each type carries',
|
|
58
59
|
'',
|
|
59
60
|
'Exit codes:',
|
|
60
61
|
' 0 every check passed',
|
|
@@ -66,6 +67,7 @@ export function register(program: Command): void {
|
|
|
66
67
|
'',
|
|
67
68
|
'Examples:',
|
|
68
69
|
' aitk records validate plans',
|
|
70
|
+
' aitk records validate memory',
|
|
69
71
|
' aitk records validate intake --json',
|
|
70
72
|
'',
|
|
71
73
|
].join('\n'),
|
package/src/records/validate.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { readdir, readFile } from 'node:fs/promises'
|
|
|
3
3
|
import { join } from 'node:path'
|
|
4
4
|
import { parseFrontmatter, readField } from '@/indexes/frontmatter'
|
|
5
5
|
|
|
6
|
-
export const RECORD_KINDS = ['plans', 'groundwork', 'intake'] as const
|
|
6
|
+
export const RECORD_KINDS = ['plans', 'groundwork', 'intake', 'memory'] as const
|
|
7
7
|
|
|
8
8
|
export type RecordKind = (typeof RECORD_KINDS)[number]
|
|
9
9
|
|
|
@@ -11,6 +11,7 @@ const FOLDER_BY_KIND: Readonly<Record<RecordKind, string>> = {
|
|
|
11
11
|
plans: join('.claude', 'plans'),
|
|
12
12
|
groundwork: join('.claude', 'groundwork'),
|
|
13
13
|
intake: join('.claude', 'intake'),
|
|
14
|
+
memory: join('.claude', 'memory'),
|
|
14
15
|
}
|
|
15
16
|
|
|
16
17
|
/**
|
|
@@ -26,6 +27,7 @@ export type ValidateRefusal = (typeof VALIDATE_REFUSALS)[number]
|
|
|
26
27
|
export const FINDING_KINDS = [
|
|
27
28
|
'name-malformed',
|
|
28
29
|
'title-missing',
|
|
30
|
+
'title-is-slug',
|
|
29
31
|
'section-missing',
|
|
30
32
|
'entry-unreasoned',
|
|
31
33
|
'suggestion-missing',
|
|
@@ -36,6 +38,7 @@ export const FINDING_KINDS = [
|
|
|
36
38
|
'state-missing',
|
|
37
39
|
'closing-partial',
|
|
38
40
|
'item-incomplete',
|
|
41
|
+
'category-mismatch',
|
|
39
42
|
] as const
|
|
40
43
|
|
|
41
44
|
export type FindingKind = (typeof FINDING_KINDS)[number]
|
|
@@ -559,10 +562,175 @@ async function checkDump(dir: string, slug: string): Promise<Finding[]> {
|
|
|
559
562
|
return [...findings, ...perCluster.flat()]
|
|
560
563
|
}
|
|
561
564
|
|
|
565
|
+
const MEMORY_INDEX = 'index.md'
|
|
566
|
+
const MEMORY_FIELDS = ['title', 'description', 'category'] as const
|
|
567
|
+
|
|
568
|
+
/**
|
|
569
|
+
* The filename prefix and the `category` field are one fact in two spellings,
|
|
570
|
+
* so the map is the whole type list and the comparison against it is what
|
|
571
|
+
* catches a prefix outside the set, a field disagreeing with the prefix, and a
|
|
572
|
+
* casing drift that would open a second group in the catalog.
|
|
573
|
+
*/
|
|
574
|
+
const CATEGORY_BY_TYPE = {
|
|
575
|
+
feedback: 'Feedback',
|
|
576
|
+
project: 'Project',
|
|
577
|
+
user: 'User',
|
|
578
|
+
reference: 'Reference',
|
|
579
|
+
} as const
|
|
580
|
+
|
|
581
|
+
type MemoryType = keyof typeof CATEGORY_BY_TYPE
|
|
582
|
+
|
|
583
|
+
const MEMORY_TYPES = Object.keys(CATEGORY_BY_TYPE) as readonly MemoryType[]
|
|
584
|
+
|
|
585
|
+
const MEMORY_NAME = /^([a-z]+)-[a-z0-9]+(?:-[a-z0-9]+)*\.md$/
|
|
586
|
+
|
|
587
|
+
/** The two markers a rule-bearing body carries, on top of the rule line itself. */
|
|
588
|
+
const MEMORY_MARKERS = ['**Why:**', '**How to apply:**'] as const
|
|
589
|
+
|
|
590
|
+
function memoryType(value: string): MemoryType | undefined {
|
|
591
|
+
return MEMORY_TYPES.find((type) => type === value)
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
export function checkMemory(name: string, text: string): Finding[] {
|
|
595
|
+
const findings: Finding[] = []
|
|
596
|
+
const match = MEMORY_NAME.exec(name)
|
|
597
|
+
const named = match ? memoryType(match[1]) : undefined
|
|
598
|
+
|
|
599
|
+
if (!named) {
|
|
600
|
+
findings.push(
|
|
601
|
+
finding(
|
|
602
|
+
'name-malformed',
|
|
603
|
+
name,
|
|
604
|
+
name,
|
|
605
|
+
`is not named <type>-<slug>.md with a type of ${MEMORY_TYPES.join(', ')}.`,
|
|
606
|
+
),
|
|
607
|
+
)
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
const frontmatter = parseFrontmatter(text)
|
|
611
|
+
const missing = MEMORY_FIELDS.filter(
|
|
612
|
+
(field) => !readField(frontmatter, field),
|
|
613
|
+
)
|
|
614
|
+
|
|
615
|
+
if (missing.length > 0) {
|
|
616
|
+
findings.push(
|
|
617
|
+
finding(
|
|
618
|
+
'frontmatter-incomplete',
|
|
619
|
+
name,
|
|
620
|
+
name,
|
|
621
|
+
`carries no ${missing.join(' and no ')}.`,
|
|
622
|
+
),
|
|
623
|
+
)
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
// Its own kind rather than `title-missing`, which means an absent heading on a
|
|
627
|
+
// plan. One kind covering both leaves a caller filtering the JSON unable to
|
|
628
|
+
// tell a record with no title from one whose title is its own slug.
|
|
629
|
+
if (readField(frontmatter, 'title') === name.replace(/\.md$/, '')) {
|
|
630
|
+
findings.push(
|
|
631
|
+
finding(
|
|
632
|
+
'title-is-slug',
|
|
633
|
+
name,
|
|
634
|
+
name,
|
|
635
|
+
'is titled with its own filename, so the catalog renders a slug where the rule belongs.',
|
|
636
|
+
),
|
|
637
|
+
)
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
const category = readField(frontmatter, 'category')
|
|
641
|
+
|
|
642
|
+
// Reported against the prefix alone. A name the prefix rule already failed
|
|
643
|
+
// has no type to compare against, and reporting it twice names one defect as
|
|
644
|
+
// two.
|
|
645
|
+
if (named && category && category !== CATEGORY_BY_TYPE[named]) {
|
|
646
|
+
findings.push(
|
|
647
|
+
finding(
|
|
648
|
+
'category-mismatch',
|
|
649
|
+
name,
|
|
650
|
+
category,
|
|
651
|
+
`is not ${CATEGORY_BY_TYPE[named]}, which the filename prefix declares.`,
|
|
652
|
+
),
|
|
653
|
+
)
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
return [
|
|
657
|
+
...findings,
|
|
658
|
+
...checkMemoryBody(
|
|
659
|
+
name,
|
|
660
|
+
text.slice(frontmatter?.raw.length ?? 0),
|
|
661
|
+
named ?? category,
|
|
662
|
+
),
|
|
663
|
+
]
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
/**
|
|
667
|
+
* A `user` or `reference` entry is a single sentence by design, so the markers
|
|
668
|
+
* are checked only where a rule is being stated. The type is read off the
|
|
669
|
+
* prefix, falling back to the category so a misnamed file is still checked
|
|
670
|
+
* against the shape it claims.
|
|
671
|
+
*/
|
|
672
|
+
function checkMemoryBody(
|
|
673
|
+
name: string,
|
|
674
|
+
text: string,
|
|
675
|
+
claimed: string | undefined,
|
|
676
|
+
): Finding[] {
|
|
677
|
+
const type = claimed && memoryType(claimed.toLowerCase())
|
|
678
|
+
if (type !== 'feedback' && type !== 'project') return []
|
|
679
|
+
|
|
680
|
+
const body = linesOutsideFences(text).filter((line) => line.trim().length > 0)
|
|
681
|
+
|
|
682
|
+
const findings: Finding[] = []
|
|
683
|
+
const opening = body[0]
|
|
684
|
+
|
|
685
|
+
if (!opening || MEMORY_MARKERS.some((marker) => opening.startsWith(marker))) {
|
|
686
|
+
findings.push(
|
|
687
|
+
finding(
|
|
688
|
+
'section-missing',
|
|
689
|
+
name,
|
|
690
|
+
'the rule line',
|
|
691
|
+
'is absent, so the entry carries a rationale with no rule to apply.',
|
|
692
|
+
),
|
|
693
|
+
)
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
for (const marker of MEMORY_MARKERS) {
|
|
697
|
+
if (!body.some((line) => line.startsWith(marker))) {
|
|
698
|
+
findings.push(
|
|
699
|
+
finding(
|
|
700
|
+
'section-missing',
|
|
701
|
+
name,
|
|
702
|
+
marker,
|
|
703
|
+
`is required on a ${type} entry and the body carries no such line.`,
|
|
704
|
+
),
|
|
705
|
+
)
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
return findings
|
|
710
|
+
}
|
|
711
|
+
|
|
562
712
|
function refuse(reason: ValidateRefusal, message: string): ValidateRefused {
|
|
563
713
|
return { ok: false, reason, message }
|
|
564
714
|
}
|
|
565
715
|
|
|
716
|
+
/** The walk for a kind whose records are files in one flat folder. */
|
|
717
|
+
async function validateFiles(
|
|
718
|
+
dir: string,
|
|
719
|
+
kind: RecordKind,
|
|
720
|
+
check: (name: string, text: string) => Finding[],
|
|
721
|
+
skip: (file: string) => boolean = () => false,
|
|
722
|
+
): Promise<ValidateReport> {
|
|
723
|
+
const files = (await listMarkdown(dir)).filter((file) => !skip(file))
|
|
724
|
+
|
|
725
|
+
const perFile = await Promise.all(
|
|
726
|
+
files.map(async (file) =>
|
|
727
|
+
check(file, await readFile(join(dir, file), 'utf8')),
|
|
728
|
+
),
|
|
729
|
+
)
|
|
730
|
+
|
|
731
|
+
return { ok: true, kind, records: files.length, findings: perFile.flat() }
|
|
732
|
+
}
|
|
733
|
+
|
|
566
734
|
/**
|
|
567
735
|
* Reports what every record in one gitignored folder claims against the shape
|
|
568
736
|
* its standard fixes. It writes nothing: the folder is per-machine scratch with
|
|
@@ -578,15 +746,15 @@ export async function validateRecords(
|
|
|
578
746
|
return refuse('no-folder', `No ${kind} folder at ${dir}.`)
|
|
579
747
|
}
|
|
580
748
|
|
|
581
|
-
if (kind === 'plans')
|
|
582
|
-
const files = await listMarkdown(dir)
|
|
583
|
-
const perFile = await Promise.all(
|
|
584
|
-
files.map(async (file) =>
|
|
585
|
-
checkPlan(file, await readFile(join(dir, file), 'utf8')),
|
|
586
|
-
),
|
|
587
|
-
)
|
|
749
|
+
if (kind === 'plans') return validateFiles(dir, kind, checkPlan)
|
|
588
750
|
|
|
589
|
-
|
|
751
|
+
if (kind === 'memory') {
|
|
752
|
+
return validateFiles(
|
|
753
|
+
dir,
|
|
754
|
+
kind,
|
|
755
|
+
checkMemory,
|
|
756
|
+
(file) => file === MEMORY_INDEX,
|
|
757
|
+
)
|
|
590
758
|
}
|
|
591
759
|
|
|
592
760
|
const folders = await listFolders(dir)
|
package/standards/index.md
CHANGED
|
@@ -14,6 +14,7 @@ Reference docs for consistent authoring across the toolkit and target projects.
|
|
|
14
14
|
- [Groundwork reference](groundwork.md): Folder layout, reserved numbering, frontmatter and dating, required file contents, and conventions for a measurement track
|
|
15
15
|
- [Intake reference](intake.md): Folder layout, reserved index number, frontmatter and dating, the item template, the answer contract, and retrieval
|
|
16
16
|
- [Markdown reference](markdown.md): Headings, paragraph and list structure, code spans, punctuation, emphasis, and file references
|
|
17
|
+
- [Memory reference](memory.md): Filename and type prefix, frontmatter, the body shape per type, links between entries, and the lifecycle from write to retire
|
|
17
18
|
- [Plan reference](plan.md): Filename and slug, required sections, the suggested-and-answer contract, and the lifecycle from the live folder to the archive
|
|
18
19
|
- [Prose reference](prose.md): Voice, language, and frontmatter wording for reference markdown
|
|
19
20
|
- [Publish reference](publish.md): Scan run against finished text leaving through a channel no automated check covers
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Memory reference
|
|
3
|
+
description: Filename and type prefix, frontmatter, the body shape per type, links between entries, and the lifecycle from write to retire
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Memory reference
|
|
7
|
+
|
|
8
|
+
Applies to a memory entry at `.claude/memory/<type>-<slug>.md`. One file holds one rule or one fact, written at the end of the session that produced it and read by a session that holds none of it. Which surface owns a given fact is settled before an entry is written at all, and that routing is project policy rather than a shape rule.
|
|
9
|
+
|
|
10
|
+
The folder is gitignored and unbacked. Nothing recovers a deleted entry, which is why the retire step below is a move rather than a cleanup.
|
|
11
|
+
|
|
12
|
+
## Scope
|
|
13
|
+
|
|
14
|
+
Governs a memory entry under `.claude/memory/<type>-<slug>.md`: the filename and its type prefix, the frontmatter, the body shape each type carries, links between entries, and the lifecycle from the first write to retirement.
|
|
15
|
+
|
|
16
|
+
Does not govern:
|
|
17
|
+
|
|
18
|
+
- The per-domain entry a domain fact is routed to instead of memory: `context.md`
|
|
19
|
+
- The feature plan a session executes, and its answer contract: `plan.md`
|
|
20
|
+
- The task file tracking what is being built: `tasks.md`
|
|
21
|
+
- Voice and word choice: `prose.md`
|
|
22
|
+
- Headings, punctuation, and file references: `markdown.md`
|
|
23
|
+
- Which facts a project captures at all, and where the folder sits, which are project policy
|
|
24
|
+
|
|
25
|
+
## What a working entry looks like
|
|
26
|
+
|
|
27
|
+
An entry works when a session holding none of the conversation that produced it can act on the rule from the file alone:
|
|
28
|
+
|
|
29
|
+
- What is the rule or the fact, stated in one line?
|
|
30
|
+
- What happened in a session to earn it, so a later reader can tell whether it still holds?
|
|
31
|
+
- When does it fire next, and what does the reader do at that moment?
|
|
32
|
+
- Does another surface already own this, which would make the entry a duplicate of something sessions trust more?
|
|
33
|
+
|
|
34
|
+
An entry failing these is non-conforming even when it satisfies every shape rule below.
|
|
35
|
+
|
|
36
|
+
## Types
|
|
37
|
+
|
|
38
|
+
Four types, and the type decides both the filename prefix and the body shape.
|
|
39
|
+
|
|
40
|
+
| Type | Holds | Fires on |
|
|
41
|
+
| ----------- | -------------------------------------------------------------------- | ------------------------------------------- |
|
|
42
|
+
| `feedback` | a correction or a confirmed approach governing how the agent works | explicit correction, or the same slip twice |
|
|
43
|
+
| `project` | a decision, constraint, or measured fact not derivable from the code | first disclosure |
|
|
44
|
+
| `user` | role, expertise, responsibilities, or working preferences | first disclosure |
|
|
45
|
+
| `reference` | a pointer to an external system, repository, or channel | first disclosure |
|
|
46
|
+
|
|
47
|
+
Hold a feedback entry to the higher bar. A first-occurrence slip is noise, and a folder that records every one of them buries the rules that were paid for.
|
|
48
|
+
|
|
49
|
+
Write no entry for a fact another surface already owns. A fact about a domain belongs in that domain's entry, where sessions working the domain already read it, and the same fact in memory sits in a folder nothing opens. Memory keeps the residue, which in practice is feedback about how to work.
|
|
50
|
+
|
|
51
|
+
## Filename
|
|
52
|
+
|
|
53
|
+
- Name the file `<type>-<slug>.md`, with `<type>` one of the four above and `<slug>` kebab-case naming the rule rather than the incident.
|
|
54
|
+
- Write one rule per file. Two rules under one slug cannot be retired or promoted separately, and one of them always outlives the other.
|
|
55
|
+
- Keep the prefix and the `category` field the same fact in two spellings. A prefix outside the four types reads as a fifth type to anything grouping the folder, and it silently belongs to none.
|
|
56
|
+
|
|
57
|
+
## Frontmatter
|
|
58
|
+
|
|
59
|
+
Every entry carries all three fields.
|
|
60
|
+
|
|
61
|
+
```yaml
|
|
62
|
+
---
|
|
63
|
+
title: A scope glob wide enough for a correct run can be wide enough for every wrong one
|
|
64
|
+
description: Omit a declaration key whose only passing value admits the whole tree
|
|
65
|
+
category: Project
|
|
66
|
+
---
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
- `title` (required): the rule stated as a line a reader can act on, in sentence case.
|
|
70
|
+
- `description` (required): one line naming what the entry settles, so a reader scanning the catalog decides whether to open it.
|
|
71
|
+
- `category` (required): the type in sentence case, which is what groups the catalog.
|
|
72
|
+
|
|
73
|
+
Never write the filename stem as the `title`. The stem is a slug, and an entry titled with it reads as an unwritten file in every catalog that renders the field.
|
|
74
|
+
|
|
75
|
+
Quote a `description` opening with a backtick or a colon. An unquoted one fails to parse and takes the whole folder's catalog with it.
|
|
76
|
+
|
|
77
|
+
## Body
|
|
78
|
+
|
|
79
|
+
A `feedback` or `project` body carries three parts in this order. Blank lines between them are optional and the three parts are not.
|
|
80
|
+
|
|
81
|
+
- The rule or the fact, stated in one or two sentences as something to do rather than something that happened.
|
|
82
|
+
- A `**Why:**` line naming the session signal that earned it. This is what a later reader tests the rule against when the tree has moved.
|
|
83
|
+
- A `**How to apply:**` line naming the next moment the rule fires and what to do then.
|
|
84
|
+
|
|
85
|
+
A `user` or `reference` body is a single sentence and carries neither marker. There is no rule to apply and no signal to date, so the two lines would be filler.
|
|
86
|
+
|
|
87
|
+
Capture the pattern rather than the recovery. What was tried, what failed, and who noticed belong to the session that is ending, and a body carrying them is a story where the next reader needs an instruction.
|
|
88
|
+
|
|
89
|
+
## Links
|
|
90
|
+
|
|
91
|
+
Link a related entry as `[[name]]`, where `name` is the target's filename stem without the extension. Link freely: the folder is flat and the links are the only structure it has.
|
|
92
|
+
|
|
93
|
+
- Place links inside the body part they support, not in a list of their own at the end.
|
|
94
|
+
- A link naming an entry nobody has written yet is legal, and it marks a rule worth writing rather than a defect.
|
|
95
|
+
- A bracketed token inside a code span is not a link. Backticked syntax from another language routinely reads as one.
|
|
96
|
+
|
|
97
|
+
## Lifecycle
|
|
98
|
+
|
|
99
|
+
- Check the folder for an entry on the same topic before writing a new one, and update that entry in place when one exists. Two entries on one rule disagree the moment either is edited.
|
|
100
|
+
- Rewrite an entry the tree has moved under rather than appending a second passage narrating the change. A reader cannot tell which of two claims is current.
|
|
101
|
+
- Never delete an entry. Retire one by moving it to an archive under its own name, because the folder is unbacked and a bulk judgment has no undo behind it.
|
|
102
|
+
- Treat the folder as a holding pen rather than a destination. An entry whose rule belongs on a durable surface is promoted there and retired here, and the rest is what the pen is for.
|
|
103
|
+
|
|
104
|
+
The catalog is generated from sibling frontmatter rather than authored. Never hand-edit it, since the next regeneration discards whatever was added by hand.
|
|
105
|
+
|
|
106
|
+
## Anti-patterns
|
|
107
|
+
|
|
108
|
+
- **The domain fact filed as memory.** It reads as a capture and lands in the one folder no session opens while working that domain.
|
|
109
|
+
- **The incident narrative.** A body recounting what went wrong states no rule, so the next reader has to infer one and infers a different one.
|
|
110
|
+
- **The entry titled with its own slug.** Every catalog rendering the field shows a filename where the rule should be.
|
|
111
|
+
- **The duplicate written beside the original.** Two entries on one topic drift, and nothing says which is current.
|
|
112
|
+
- **The first-occurrence capture.** A folder recording every slip buries the rules that repeated.
|
|
113
|
+
- **The entry deleted on retire.** The folder has no history, so the judgment that discarded it cannot be reviewed or reversed.
|
|
114
|
+
|
|
115
|
+
## Template
|
|
116
|
+
|
|
117
|
+
```markdown
|
|
118
|
+
---
|
|
119
|
+
title: <the rule, stated as a line a reader can act on>
|
|
120
|
+
description: <one line naming what the entry settles>
|
|
121
|
+
category: <Feedback|Project|User|Reference>
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
<the rule or fact, in one or two sentences, as something to do>
|
|
125
|
+
|
|
126
|
+
**Why:** <the session signal that earned it, with the measurement where one exists>
|
|
127
|
+
|
|
128
|
+
**How to apply:** <the next moment it fires, and what to do then> See [[related-entry]].
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
A `user` or `reference` entry carries the same frontmatter and a single sentence in place of the three parts.
|
|
@@ -73,11 +73,8 @@
|
|
|
73
73
|
|
|
74
74
|
- Write all memory files to `.claude/memory/`, not `~/.claude/projects/`
|
|
75
75
|
- A fact about a domain goes to that domain's `.claude/context/` entry, not to memory. `claude-memory-capture` routes it there and `claude-docs` folds it in. Memory keeps only what no context entry owns.
|
|
76
|
-
-
|
|
77
|
-
-
|
|
78
|
-
- Before creating a new memory file, check for an existing one on the same topic. Update rather than duplicate.
|
|
79
|
-
- Give every entry `title`, `description`, and a sentence-case `category`. Never hand-edit `.claude/memory/index.md`. A hook regenerates it from sibling frontmatter.
|
|
80
|
-
- Never delete a memory entry. `claude-memory-review` moves a retired one to `.claude/.tmp/memory-archive/`.
|
|
76
|
+
- Never delete a memory entry. Retire one by moving it to `.claude/.tmp/memory-archive/`. A bulk retire runs through the shell, where no file edit fires a path-scoped rule, and the folder is gitignored with nothing to recover from.
|
|
77
|
+
- Follow `.claude/standards/memory.md` for the filename and type prefix, the frontmatter, the body shape each type carries, and the lifecycle. Run `aitk records validate memory` to check the pen against it.
|
|
81
78
|
|
|
82
79
|
## Scratch
|
|
83
80
|
|