claude-priors 0.1.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/bin/cli.js +227 -0
- package/bootstrap/CLAUDE.md.snippet +8 -0
- package/package.json +34 -0
- package/skill/SKILL.md +121 -0
package/bin/cli.js
ADDED
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
const PRIORS_MARKER = '## Priors';
|
|
7
|
+
const VERSION = '0.1.0';
|
|
8
|
+
|
|
9
|
+
// Resolve paths to bundled assets (works both locally and via npx)
|
|
10
|
+
const PKG_ROOT = path.resolve(__dirname, '..');
|
|
11
|
+
const SKILL_SRC = path.resolve(PKG_ROOT, '..', 'skill', 'SKILL.md');
|
|
12
|
+
const BOOTSTRAP_SRC = path.resolve(PKG_ROOT, '..', 'bootstrap', 'CLAUDE.md.snippet');
|
|
13
|
+
|
|
14
|
+
// Fallback: when installed as npm package, skill/ and bootstrap/ are siblings to bin/
|
|
15
|
+
const SKILL_SRC_ALT = path.resolve(PKG_ROOT, 'skill', 'SKILL.md');
|
|
16
|
+
const BOOTSTRAP_SRC_ALT = path.resolve(PKG_ROOT, 'bootstrap', 'CLAUDE.md.snippet');
|
|
17
|
+
|
|
18
|
+
function resolveAsset(primary, fallback) {
|
|
19
|
+
if (fs.existsSync(primary)) return primary;
|
|
20
|
+
if (fs.existsSync(fallback)) return fallback;
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function readAsset(primary, fallback) {
|
|
25
|
+
const resolved = resolveAsset(primary, fallback);
|
|
26
|
+
if (!resolved) {
|
|
27
|
+
console.error(`Error: Could not find asset. Looked in:\n ${primary}\n ${fallback}`);
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}
|
|
30
|
+
return fs.readFileSync(resolved, 'utf8');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function printUsage() {
|
|
34
|
+
console.log(`
|
|
35
|
+
claude-priors v${VERSION} — Persistent agent memory for AI coding assistants
|
|
36
|
+
|
|
37
|
+
Usage:
|
|
38
|
+
claude-priors init [options] Bootstrap priors in current directory
|
|
39
|
+
claude-priors status Show priors stats
|
|
40
|
+
claude-priors help Show this help
|
|
41
|
+
|
|
42
|
+
Options:
|
|
43
|
+
--shared Git-track priors (default: git-ignored)
|
|
44
|
+
--global Install skill to ~/.claude/skills/ instead of repo
|
|
45
|
+
`);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function init(args) {
|
|
49
|
+
const cwd = process.cwd();
|
|
50
|
+
const shared = args.includes('--shared');
|
|
51
|
+
const global = args.includes('--global');
|
|
52
|
+
|
|
53
|
+
console.log(`\nclaude-priors v${VERSION}`);
|
|
54
|
+
console.log(`Initializing in: ${cwd}\n`);
|
|
55
|
+
|
|
56
|
+
// 1. Create .claude/priors/
|
|
57
|
+
const priorsDir = path.join(cwd, '.claude', 'priors');
|
|
58
|
+
if (!fs.existsSync(priorsDir)) {
|
|
59
|
+
fs.mkdirSync(priorsDir, { recursive: true });
|
|
60
|
+
fs.writeFileSync(path.join(priorsDir, '.gitkeep'), '');
|
|
61
|
+
console.log(' Created .claude/priors/');
|
|
62
|
+
} else {
|
|
63
|
+
console.log(' .claude/priors/ already exists');
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// 2. Copy SKILL.md
|
|
67
|
+
const skillContent = readAsset(SKILL_SRC, SKILL_SRC_ALT);
|
|
68
|
+
let skillDir;
|
|
69
|
+
if (global) {
|
|
70
|
+
skillDir = path.join(
|
|
71
|
+
process.env.HOME || process.env.USERPROFILE,
|
|
72
|
+
'.claude', 'skills', 'knowledge-extraction'
|
|
73
|
+
);
|
|
74
|
+
} else {
|
|
75
|
+
skillDir = path.join(cwd, '.claude', 'skills', 'knowledge-extraction');
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (!fs.existsSync(skillDir)) {
|
|
79
|
+
fs.mkdirSync(skillDir, { recursive: true });
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const skillDest = path.join(skillDir, 'SKILL.md');
|
|
83
|
+
if (!fs.existsSync(skillDest)) {
|
|
84
|
+
fs.writeFileSync(skillDest, skillContent);
|
|
85
|
+
console.log(` Created ${global ? '~/.claude/skills' : '.claude/skills'}/knowledge-extraction/SKILL.md`);
|
|
86
|
+
} else {
|
|
87
|
+
// Check version
|
|
88
|
+
const existing = fs.readFileSync(skillDest, 'utf8');
|
|
89
|
+
if (existing === skillContent) {
|
|
90
|
+
console.log(` SKILL.md already up to date`);
|
|
91
|
+
} else {
|
|
92
|
+
fs.writeFileSync(skillDest, skillContent);
|
|
93
|
+
console.log(` Updated SKILL.md to v${VERSION}`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// 3. Append bootstrap to CLAUDE.md (or AGENTS.md)
|
|
98
|
+
const bootstrapContent = readAsset(BOOTSTRAP_SRC, BOOTSTRAP_SRC_ALT);
|
|
99
|
+
|
|
100
|
+
// Detect which instruction file exists
|
|
101
|
+
let instructionFile = null;
|
|
102
|
+
const candidates = ['CLAUDE.md', 'AGENTS.md'];
|
|
103
|
+
for (const candidate of candidates) {
|
|
104
|
+
if (fs.existsSync(path.join(cwd, candidate))) {
|
|
105
|
+
instructionFile = candidate;
|
|
106
|
+
break;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (!instructionFile) {
|
|
111
|
+
instructionFile = 'CLAUDE.md';
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const instructionPath = path.join(cwd, instructionFile);
|
|
115
|
+
|
|
116
|
+
if (fs.existsSync(instructionPath)) {
|
|
117
|
+
const existing = fs.readFileSync(instructionPath, 'utf8');
|
|
118
|
+
if (existing.includes(PRIORS_MARKER)) {
|
|
119
|
+
console.log(` ${instructionFile} already has priors bootstrap`);
|
|
120
|
+
} else {
|
|
121
|
+
fs.writeFileSync(instructionPath, existing.trimEnd() + '\n\n' + bootstrapContent.trim() + '\n');
|
|
122
|
+
console.log(` Appended priors bootstrap to ${instructionFile}`);
|
|
123
|
+
}
|
|
124
|
+
} else {
|
|
125
|
+
fs.writeFileSync(instructionPath, bootstrapContent.trim() + '\n');
|
|
126
|
+
console.log(` Created ${instructionFile} with priors bootstrap`);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// 4. Handle .gitignore
|
|
130
|
+
const gitignorePath = path.join(cwd, '.gitignore');
|
|
131
|
+
const priorsGlob = '.claude/priors/';
|
|
132
|
+
|
|
133
|
+
if (!shared) {
|
|
134
|
+
if (fs.existsSync(gitignorePath)) {
|
|
135
|
+
const gitignore = fs.readFileSync(gitignorePath, 'utf8');
|
|
136
|
+
if (!gitignore.includes(priorsGlob)) {
|
|
137
|
+
fs.writeFileSync(gitignorePath, gitignore.trimEnd() + '\n\n# Agent priors (local knowledge)\n' + priorsGlob + '\n');
|
|
138
|
+
console.log(' Added .claude/priors/ to .gitignore');
|
|
139
|
+
} else {
|
|
140
|
+
console.log(' .gitignore already excludes priors');
|
|
141
|
+
}
|
|
142
|
+
} else {
|
|
143
|
+
fs.writeFileSync(gitignorePath, '# Agent priors (local knowledge)\n' + priorsGlob + '\n');
|
|
144
|
+
console.log(' Created .gitignore with priors exclusion');
|
|
145
|
+
}
|
|
146
|
+
} else {
|
|
147
|
+
console.log(' --shared: priors will be git-tracked');
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
console.log('\nDone! Your agent will now learn from every conversation.\n');
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function status() {
|
|
154
|
+
const cwd = process.cwd();
|
|
155
|
+
const priorsDir = path.join(cwd, '.claude', 'priors');
|
|
156
|
+
|
|
157
|
+
if (!fs.existsSync(priorsDir)) {
|
|
158
|
+
console.log('No priors directory found. Run `claude-priors init` first.');
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const files = fs.readdirSync(priorsDir).filter(f => f.endsWith('.md'));
|
|
163
|
+
|
|
164
|
+
if (files.length === 0) {
|
|
165
|
+
console.log('Priors directory exists but is empty. The agent will start writing priors as it learns.\n');
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
console.log(`\nPriors: ${priorsDir}\n`);
|
|
170
|
+
|
|
171
|
+
let totalEntries = 0;
|
|
172
|
+
|
|
173
|
+
for (const file of files) {
|
|
174
|
+
const content = fs.readFileSync(path.join(priorsDir, file), 'utf8');
|
|
175
|
+
|
|
176
|
+
// Parse frontmatter
|
|
177
|
+
const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
178
|
+
let topic = file.replace('.md', '');
|
|
179
|
+
let summary = '';
|
|
180
|
+
let entries = 0;
|
|
181
|
+
let lastUpdated = '';
|
|
182
|
+
|
|
183
|
+
if (fmMatch) {
|
|
184
|
+
const fm = fmMatch[1];
|
|
185
|
+
const topicMatch = fm.match(/topic:\s*(.+)/);
|
|
186
|
+
const summaryMatch = fm.match(/summary:\s*(.+)/);
|
|
187
|
+
const entriesMatch = fm.match(/entries:\s*(\d+)/);
|
|
188
|
+
const dateMatch = fm.match(/last_updated:\s*(.+)/);
|
|
189
|
+
|
|
190
|
+
if (topicMatch) topic = topicMatch[1].trim();
|
|
191
|
+
if (summaryMatch) summary = summaryMatch[1].trim();
|
|
192
|
+
if (entriesMatch) entries = parseInt(entriesMatch[1]);
|
|
193
|
+
if (dateMatch) lastUpdated = dateMatch[1].trim();
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
totalEntries += entries;
|
|
197
|
+
console.log(` ${file}`);
|
|
198
|
+
console.log(` Topic: ${topic} | Entries: ${entries} | Updated: ${lastUpdated || 'unknown'}`);
|
|
199
|
+
if (summary) console.log(` ${summary}`);
|
|
200
|
+
console.log();
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
console.log(`Total: ${files.length} files, ${totalEntries} entries\n`);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// --- Main ---
|
|
207
|
+
const args = process.argv.slice(2);
|
|
208
|
+
const command = args[0];
|
|
209
|
+
|
|
210
|
+
switch (command) {
|
|
211
|
+
case 'init':
|
|
212
|
+
init(args.slice(1));
|
|
213
|
+
break;
|
|
214
|
+
case 'status':
|
|
215
|
+
status();
|
|
216
|
+
break;
|
|
217
|
+
case 'help':
|
|
218
|
+
case '--help':
|
|
219
|
+
case '-h':
|
|
220
|
+
case undefined:
|
|
221
|
+
printUsage();
|
|
222
|
+
break;
|
|
223
|
+
default:
|
|
224
|
+
console.error(`Unknown command: ${command}`);
|
|
225
|
+
printUsage();
|
|
226
|
+
process.exit(1);
|
|
227
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
|
|
2
|
+
## Priors
|
|
3
|
+
|
|
4
|
+
This project uses agent priors (`.claude/priors/`) — persistent knowledge the agent accumulates across sessions.
|
|
5
|
+
|
|
6
|
+
**Start of every task:** List `.claude/priors/` and read each file's YAML frontmatter (`topic`, `summary`). Load full content only for files relevant to the current task.
|
|
7
|
+
|
|
8
|
+
**End of every conversation** where you were corrected, discovered something unexpected, or the user provided context you couldn't derive from code: load the `knowledge-extraction` skill from `.claude/skills/` and extract priors.
|
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "claude-priors",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Persistent agent memory for AI coding assistants. One command to enable your agent to learn from every conversation.",
|
|
5
|
+
"bin": {
|
|
6
|
+
"claude-priors": "./bin/cli.js"
|
|
7
|
+
},
|
|
8
|
+
"keywords": [
|
|
9
|
+
"claude",
|
|
10
|
+
"ai",
|
|
11
|
+
"agent",
|
|
12
|
+
"memory",
|
|
13
|
+
"priors",
|
|
14
|
+
"coding-assistant",
|
|
15
|
+
"opencode",
|
|
16
|
+
"cursor",
|
|
17
|
+
"mcp"
|
|
18
|
+
],
|
|
19
|
+
"author": "Jiahao Li",
|
|
20
|
+
"license": "MIT",
|
|
21
|
+
"homepage": "https://github.com/jiahaoli/claude-priors#readme",
|
|
22
|
+
"repository": {
|
|
23
|
+
"type": "git",
|
|
24
|
+
"url": "git+https://github.com/jiahaoli/claude-priors.git"
|
|
25
|
+
},
|
|
26
|
+
"bugs": {
|
|
27
|
+
"url": "https://github.com/jiahaoli/claude-priors/issues"
|
|
28
|
+
},
|
|
29
|
+
"files": [
|
|
30
|
+
"bin/",
|
|
31
|
+
"skill/",
|
|
32
|
+
"bootstrap/"
|
|
33
|
+
]
|
|
34
|
+
}
|
package/skill/SKILL.md
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: knowledge-extraction
|
|
3
|
+
description: Detailed instructions for the agent priors system — how to extract, format, store, consume, and manage priors files in .claude/priors/. Load this skill when you need to write new priors, reorganize existing priors, or understand the priors file format. The always-on bootstrap lives in CLAUDE.md; this skill provides the full reference.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Knowledge Extraction — Priors System Reference
|
|
7
|
+
|
|
8
|
+
This skill is the detailed reference for the priors system. The always-on bootstrap in `CLAUDE.md` tells the agent to scan and use priors every session. This skill is loaded on demand when the agent needs to **write, format, or manage** priors.
|
|
9
|
+
|
|
10
|
+
## Skills vs Priors
|
|
11
|
+
|
|
12
|
+
- **Skills** (`.claude/skills/`) — Explicit, user-provided and user-maintained. **Never write agent-learned knowledge into these files.**
|
|
13
|
+
- **Priors** (`.claude/priors/`) — Implicit, agent-learned and agent-maintained. The agent's working memory persisted to disk.
|
|
14
|
+
|
|
15
|
+
## Agent Autonomy Over Priors
|
|
16
|
+
|
|
17
|
+
Priors are fully agent-managed. The agent has **unconditional permission** to create, edit, rewrite, merge, split, delete, and reorganize priors files and directory structure. No user confirmation needed. Optimize purely for agent utility: fast scanning, low token cost, high signal density.
|
|
18
|
+
|
|
19
|
+
## When to Extract
|
|
20
|
+
|
|
21
|
+
After completing a task, extract if the conversation contained:
|
|
22
|
+
|
|
23
|
+
1. **User correction** — User pointed out something wrong
|
|
24
|
+
2. **Unexpected discovery** — Something behaved differently than assumed
|
|
25
|
+
3. **Domain context the user provided** — System behavior, business logic, conventions not in code
|
|
26
|
+
4. **Multi-step debugging** — Root cause of a back-and-forth diagnosis
|
|
27
|
+
5. **Workaround or gotcha** — Non-obvious solution or edge case
|
|
28
|
+
6. **User-volunteered context** — User proactively provided info (strong non-derivable signal)
|
|
29
|
+
|
|
30
|
+
Do NOT extract when:
|
|
31
|
+
- Task completed smoothly on first attempt
|
|
32
|
+
- Knowledge already exists in skills or priors
|
|
33
|
+
- Insight is too one-time-specific to reuse
|
|
34
|
+
|
|
35
|
+
## Derivable vs Non-Derivable Context
|
|
36
|
+
|
|
37
|
+
- **Derivable** — Discoverable from codebase (schemas, file structure). Don't persist unless expensive to re-derive.
|
|
38
|
+
- **Non-derivable** — Exists only in user's head or external systems (timezone configs, business events, product behavior). Always persist.
|
|
39
|
+
|
|
40
|
+
When a user volunteers information unprompted, it's almost certainly non-derivable — they're telling you because they know you can't find it. Always capture it.
|
|
41
|
+
|
|
42
|
+
## Priors File Format
|
|
43
|
+
|
|
44
|
+
### Frontmatter (required)
|
|
45
|
+
|
|
46
|
+
Every priors file has YAML frontmatter for progressive disclosure — the agent scans frontmatter to decide relevance before loading the body.
|
|
47
|
+
|
|
48
|
+
```yaml
|
|
49
|
+
---
|
|
50
|
+
topic: <short topic name>
|
|
51
|
+
summary: <one-line description — used for relevance scanning without loading body>
|
|
52
|
+
entries: <count>
|
|
53
|
+
last_updated: <YYYY-MM-DD>
|
|
54
|
+
---
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Body
|
|
58
|
+
|
|
59
|
+
```markdown
|
|
60
|
+
# <Topic>
|
|
61
|
+
|
|
62
|
+
## <Section>
|
|
63
|
+
|
|
64
|
+
### <Fact title>
|
|
65
|
+
<!-- Learned: YYYY-MM-DD -->
|
|
66
|
+
<1-3 sentences: fact, consequence, correct approach. Max 5 lines.>
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Entry rules
|
|
70
|
+
|
|
71
|
+
- State the **fact**, not the discovery story
|
|
72
|
+
- Include **consequence** of getting it wrong
|
|
73
|
+
- Include the **correct approach**
|
|
74
|
+
- Max 5 lines per entry
|
|
75
|
+
- Code snippets only when they save more words than they cost
|
|
76
|
+
- Newest entries first within each section
|
|
77
|
+
|
|
78
|
+
## Where to Store
|
|
79
|
+
|
|
80
|
+
All priors: `<repo>/.claude/priors/`. Organize by **topic domain**:
|
|
81
|
+
|
|
82
|
+
```
|
|
83
|
+
.claude/priors/
|
|
84
|
+
├── data-analysis.md
|
|
85
|
+
├── schema.md
|
|
86
|
+
├── deployment.md
|
|
87
|
+
└── product.md
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Decision tree:
|
|
91
|
+
1. `.claude/priors/` doesn't exist? Create it.
|
|
92
|
+
2. Relevant file exists? Append. If not, create with frontmatter.
|
|
93
|
+
3. Relevant `##` section exists? Append under it. If not, create one.
|
|
94
|
+
4. Update frontmatter (`entries`, `last_updated`) after every edit.
|
|
95
|
+
|
|
96
|
+
## Execution (Writing Priors)
|
|
97
|
+
|
|
98
|
+
1. Identify extractable lessons from conversation
|
|
99
|
+
2. Draft compact entries
|
|
100
|
+
3. Read target priors file (or create with frontmatter)
|
|
101
|
+
4. Append under appropriate section
|
|
102
|
+
5. Update frontmatter
|
|
103
|
+
6. Briefly confirm to user what was captured
|
|
104
|
+
|
|
105
|
+
Do this automatically at conversation end without being asked.
|
|
106
|
+
|
|
107
|
+
## Execution (Reading Priors)
|
|
108
|
+
|
|
109
|
+
1. List `.claude/priors/`
|
|
110
|
+
2. Read frontmatter only (first lines up to closing `---`)
|
|
111
|
+
3. Based on `topic` and `summary`, decide relevance to current task
|
|
112
|
+
4. Load full body only for relevant files
|
|
113
|
+
|
|
114
|
+
## Maintenance
|
|
115
|
+
|
|
116
|
+
Proactively maintain quality:
|
|
117
|
+
- **Merge** overlapping files
|
|
118
|
+
- **Split** files exceeding ~50 entries
|
|
119
|
+
- **Rewrite** verbose or unclear entries
|
|
120
|
+
- **Delete** outdated entries or entries now covered by explicit skills
|
|
121
|
+
- **Update frontmatter** after every change
|