scoops 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/LICENSE +21 -0
- package/README.md +148 -0
- package/bin/arivu.js +2 -0
- package/bin/ascr.js +2 -0
- package/bin/scoops.js +2 -0
- package/bin/zute.js +2 -0
- package/package.json +31 -0
- package/src/cli.js +70 -0
- package/src/commands/export.js +115 -0
- package/src/commands/init.js +110 -0
- package/src/commands/list.js +81 -0
- package/src/commands/prune.js +107 -0
- package/src/commands/status.js +62 -0
- package/src/hooks/prompt-retriever.js +147 -0
- package/src/hooks/session-start.js +82 -0
- package/src/hooks/stop-gate.js +124 -0
- package/src/lib/index.js +65 -0
- package/src/lib/memory.js +49 -0
- package/src/lib/merge-settings.js +59 -0
- package/src/lib/paths.js +38 -0
- package/src/lib/scoring.js +60 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 alphaq
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# arivu
|
|
2
|
+
|
|
3
|
+
> *arivu* (அறிவு) — Tamil for **knowledge, wisdom**
|
|
4
|
+
|
|
5
|
+
Persistent knowledge base for Claude Code. Gives your AI agent memory that survives across sessions.
|
|
6
|
+
|
|
7
|
+
## The Problem
|
|
8
|
+
|
|
9
|
+
Every new Claude Code session starts with zero context. The agent re-explores your codebase, rediscovers the same dead ends, and forgets every architectural decision you made yesterday.
|
|
10
|
+
|
|
11
|
+
**arivu fixes this.** It maintains a living knowledge base about your project — and injects only what's relevant, based on what you're actually working on.
|
|
12
|
+
|
|
13
|
+
## How It Works
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
You type: "add rate limiting to the auth routes"
|
|
17
|
+
↓
|
|
18
|
+
arivu reads your prompt
|
|
19
|
+
matches keywords against .arivu/index.json
|
|
20
|
+
pulls the 3 most relevant knowledge entries
|
|
21
|
+
↓
|
|
22
|
+
[Architecture] Auth middleware (src/middleware/auth.ts)
|
|
23
|
+
Verifies JWT from httpOnly cookie
|
|
24
|
+
Gotcha: jose v5 — use SignJWT class, not jwt.sign()
|
|
25
|
+
|
|
26
|
+
[Thread] Auth system — IN PROGRESS
|
|
27
|
+
Remaining: token refresh flow, rate limiting on auth routes
|
|
28
|
+
|
|
29
|
+
[Decision] Use jose for JWT, not jsonwebtoken
|
|
30
|
+
Why: jsonwebtoken has known CVEs
|
|
31
|
+
↓
|
|
32
|
+
Injected into Claude's context automatically
|
|
33
|
+
Agent knows exactly where to start — no re-exploration
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Three Claude Code hooks do the work:
|
|
37
|
+
- **SessionStart** — announces what's in memory (~30 tokens)
|
|
38
|
+
- **UserPromptSubmit** — matches your prompt to relevant knowledge (~200 tokens, surgically targeted)
|
|
39
|
+
- **Stop** — enforces a knowledge update if you made code changes
|
|
40
|
+
|
|
41
|
+
## Install
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
npx arivu init
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
That's it. Run it in any project.
|
|
48
|
+
|
|
49
|
+
## What Gets Created
|
|
50
|
+
|
|
51
|
+
```
|
|
52
|
+
.arivu/
|
|
53
|
+
decisions.json — why things are the way they are
|
|
54
|
+
architecture.json — how components connect and work
|
|
55
|
+
threads.json — what's in progress, what's next
|
|
56
|
+
warnings.json — dead ends and traps to avoid
|
|
57
|
+
index.json — lightweight tag index for fast retrieval
|
|
58
|
+
hooks/
|
|
59
|
+
session-start.js
|
|
60
|
+
prompt-retriever.js
|
|
61
|
+
stop-gate.js
|
|
62
|
+
|
|
63
|
+
.claude/
|
|
64
|
+
settings.json — hook configuration (merged, not overwritten)
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
All files are committed to git. Teammates get the knowledge base automatically when they clone.
|
|
68
|
+
|
|
69
|
+
## Knowledge Schema
|
|
70
|
+
|
|
71
|
+
### decisions.json
|
|
72
|
+
```json
|
|
73
|
+
{
|
|
74
|
+
"id": "d_a1b2c3d4",
|
|
75
|
+
"decision": "Use jose for JWT, not jsonwebtoken",
|
|
76
|
+
"why": "jsonwebtoken has known CVEs, jose supports ESM",
|
|
77
|
+
"alternatives_rejected": ["jsonwebtoken"],
|
|
78
|
+
"tags": ["auth", "jwt", "security"],
|
|
79
|
+
"affects": ["src/auth/*"],
|
|
80
|
+
"date": "2026-03-26"
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### architecture.json
|
|
85
|
+
```json
|
|
86
|
+
{
|
|
87
|
+
"id": "a_e5f6g7h8",
|
|
88
|
+
"component": "Auth middleware",
|
|
89
|
+
"location": "src/middleware/auth.ts",
|
|
90
|
+
"does": "Verifies JWT from httpOnly cookie, attaches user to req",
|
|
91
|
+
"depends_on": ["jose"],
|
|
92
|
+
"depended_by": ["all /api/admin/* routes"],
|
|
93
|
+
"tags": ["auth", "middleware", "jwt"],
|
|
94
|
+
"gotchas": ["jose v5: use SignJWT class, not jwt.sign()"]
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### threads.json
|
|
99
|
+
```json
|
|
100
|
+
{
|
|
101
|
+
"id": "t_i9j0k1l2",
|
|
102
|
+
"thread": "Auth system",
|
|
103
|
+
"status": "in_progress",
|
|
104
|
+
"done": ["JWT generation", "login route"],
|
|
105
|
+
"remaining": ["token refresh", "rate limiting"],
|
|
106
|
+
"tags": ["auth", "jwt"],
|
|
107
|
+
"blocked_by": null,
|
|
108
|
+
"updated": "2026-03-26"
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### warnings.json
|
|
113
|
+
```json
|
|
114
|
+
{
|
|
115
|
+
"id": "w_m2n3o4p5",
|
|
116
|
+
"warning": "bcrypt segfaults on M1 Macs in this Node version",
|
|
117
|
+
"context": "Spent 45min debugging — switched to argon2",
|
|
118
|
+
"avoid": "Do not add bcrypt as a dependency",
|
|
119
|
+
"tags": ["auth", "hashing"],
|
|
120
|
+
"date": "2026-03-25"
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## CLI Commands
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
arivu init # Set up in current project
|
|
128
|
+
arivu status # Check everything is wired up
|
|
129
|
+
arivu list # View all knowledge entries
|
|
130
|
+
arivu list --type decisions # Filter by type
|
|
131
|
+
arivu list --tag auth # Filter by tag
|
|
132
|
+
arivu prune --keep 50 # Keep only last 50 entries
|
|
133
|
+
arivu prune --before 2026-01-01 # Remove entries older than date
|
|
134
|
+
arivu export --format md # Export as markdown
|
|
135
|
+
arivu export --format json # Export as JSON
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## Design Principles
|
|
139
|
+
|
|
140
|
+
- **Zero dependencies** — Node.js built-ins only
|
|
141
|
+
- **Standalone hooks** — work even if you uninstall arivu
|
|
142
|
+
- **Smart retrieval** — keyword scoring, not "dump everything"
|
|
143
|
+
- **Git-tracked** — knowledge is version-controlled and team-shared
|
|
144
|
+
- **Never crashes** — all hooks exit 0 on any error
|
|
145
|
+
|
|
146
|
+
## License
|
|
147
|
+
|
|
148
|
+
MIT
|
package/bin/arivu.js
ADDED
package/bin/ascr.js
ADDED
package/bin/scoops.js
ADDED
package/bin/zute.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "scoops",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Persistent knowledge base for Claude Code — context that survives across sessions",
|
|
5
|
+
"bin": {
|
|
6
|
+
"scoops": "bin/scoops.js"
|
|
7
|
+
},
|
|
8
|
+
"files": [
|
|
9
|
+
"bin/",
|
|
10
|
+
"src/",
|
|
11
|
+
"README.md",
|
|
12
|
+
"LICENSE"
|
|
13
|
+
],
|
|
14
|
+
"engines": {
|
|
15
|
+
"node": ">=18.0.0"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"claude",
|
|
19
|
+
"claude-code",
|
|
20
|
+
"memory",
|
|
21
|
+
"context",
|
|
22
|
+
"ai",
|
|
23
|
+
"cli",
|
|
24
|
+
"knowledge",
|
|
25
|
+
"ascr"
|
|
26
|
+
],
|
|
27
|
+
"license": "MIT",
|
|
28
|
+
"scripts": {
|
|
29
|
+
"test": "node --test src/**/*.test.js"
|
|
30
|
+
}
|
|
31
|
+
}
|
package/src/cli.js
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { parseArgs } = require('node:util');
|
|
4
|
+
const { readFileSync } = require('node:fs');
|
|
5
|
+
const { join } = require('node:path');
|
|
6
|
+
|
|
7
|
+
const pkg = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf8'));
|
|
8
|
+
|
|
9
|
+
const HELP = `
|
|
10
|
+
ascr — Automated Semantic Context Registry
|
|
11
|
+
|
|
12
|
+
USAGE
|
|
13
|
+
ascr <command> [options]
|
|
14
|
+
|
|
15
|
+
COMMANDS
|
|
16
|
+
init Set up ASCR in the current project
|
|
17
|
+
status Check if ASCR is correctly configured
|
|
18
|
+
list View knowledge base entries
|
|
19
|
+
prune Remove old or irrelevant entries
|
|
20
|
+
export Export knowledge base as markdown or JSON
|
|
21
|
+
|
|
22
|
+
OPTIONS
|
|
23
|
+
--help, -h Show this help message
|
|
24
|
+
--version, -v Show version
|
|
25
|
+
|
|
26
|
+
EXAMPLES
|
|
27
|
+
ascr init
|
|
28
|
+
ascr list --tag auth
|
|
29
|
+
ascr list --type decisions
|
|
30
|
+
ascr prune --keep 50
|
|
31
|
+
ascr export --format md
|
|
32
|
+
`.trim();
|
|
33
|
+
|
|
34
|
+
const { values, positionals } = parseArgs({
|
|
35
|
+
allowPositionals: true,
|
|
36
|
+
options: {
|
|
37
|
+
help: { type: 'boolean', short: 'h', default: false },
|
|
38
|
+
version: { type: 'boolean', short: 'v', default: false },
|
|
39
|
+
},
|
|
40
|
+
strict: false,
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
if (values.version) {
|
|
44
|
+
console.log(pkg.version);
|
|
45
|
+
process.exit(0);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const command = positionals[0];
|
|
49
|
+
|
|
50
|
+
if (!command || values.help) {
|
|
51
|
+
console.log(HELP);
|
|
52
|
+
process.exit(0);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const commands = {
|
|
56
|
+
init: () => require('./commands/init'),
|
|
57
|
+
status: () => require('./commands/status'),
|
|
58
|
+
list: () => require('./commands/list'),
|
|
59
|
+
prune: () => require('./commands/prune'),
|
|
60
|
+
export: () => require('./commands/export'),
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
if (!commands[command]) {
|
|
64
|
+
console.error(`ascr: unknown command '${command}'\nRun 'ascr --help' for usage.`);
|
|
65
|
+
process.exit(1);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Pass remaining args (skip the command itself)
|
|
69
|
+
process.argv = [process.argv[0], process.argv[1], ...process.argv.slice(3)];
|
|
70
|
+
commands[command]();
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { parseArgs } = require('node:util');
|
|
4
|
+
const fs = require('node:fs');
|
|
5
|
+
const { readAllKnowledge } = require('../lib/memory');
|
|
6
|
+
|
|
7
|
+
const { values } = parseArgs({
|
|
8
|
+
options: {
|
|
9
|
+
format: { type: 'string', short: 'f', default: 'md' },
|
|
10
|
+
output: { type: 'string', short: 'o' },
|
|
11
|
+
},
|
|
12
|
+
strict: false,
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
const cwd = process.cwd();
|
|
16
|
+
const format = values.format;
|
|
17
|
+
|
|
18
|
+
if (!['md', 'json'].includes(format)) {
|
|
19
|
+
console.error(`Unknown format '${format}'. Use --format md or --format json`);
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const knowledge = readAllKnowledge(cwd);
|
|
24
|
+
const totalEntries = Object.values(knowledge).reduce((n, arr) => n + arr.length, 0);
|
|
25
|
+
const now = new Date().toISOString();
|
|
26
|
+
|
|
27
|
+
let output = '';
|
|
28
|
+
|
|
29
|
+
if (format === 'json') {
|
|
30
|
+
output = JSON.stringify(knowledge, null, 2) + '\n';
|
|
31
|
+
} else {
|
|
32
|
+
const lines = [];
|
|
33
|
+
lines.push('# ASCR Knowledge Base Export');
|
|
34
|
+
lines.push(`Generated: ${now}`);
|
|
35
|
+
lines.push(`Total entries: ${totalEntries}`);
|
|
36
|
+
lines.push('');
|
|
37
|
+
|
|
38
|
+
// Decisions
|
|
39
|
+
if (knowledge.decisions.length > 0) {
|
|
40
|
+
lines.push('## Decisions');
|
|
41
|
+
lines.push('*Why things are the way they are.*');
|
|
42
|
+
lines.push('');
|
|
43
|
+
for (const e of knowledge.decisions) {
|
|
44
|
+
lines.push(`### ${e.decision || e.id}`);
|
|
45
|
+
if (e.why) lines.push(`**Why:** ${e.why}`);
|
|
46
|
+
if (e.alternatives_rejected?.length) lines.push(`**Alternatives rejected:** ${e.alternatives_rejected.join(', ')}`);
|
|
47
|
+
if (e.affects?.length) lines.push(`**Affects:** ${e.affects.join(', ')}`);
|
|
48
|
+
if (e.tags?.length) lines.push(`**Tags:** ${e.tags.join(', ')}`);
|
|
49
|
+
if (e.date) lines.push(`**Date:** ${e.date}`);
|
|
50
|
+
lines.push('');
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Architecture
|
|
55
|
+
if (knowledge.architecture.length > 0) {
|
|
56
|
+
lines.push('## Architecture');
|
|
57
|
+
lines.push('*How components connect and work.*');
|
|
58
|
+
lines.push('');
|
|
59
|
+
for (const e of knowledge.architecture) {
|
|
60
|
+
lines.push(`### ${e.component || e.id}`);
|
|
61
|
+
if (e.location) lines.push(`**Location:** \`${e.location}\``);
|
|
62
|
+
if (e.does) lines.push(`**Does:** ${e.does}`);
|
|
63
|
+
if (e.depends_on?.length) lines.push(`**Depends on:** ${e.depends_on.join(', ')}`);
|
|
64
|
+
if (e.depended_by?.length) lines.push(`**Used by:** ${e.depended_by.join(', ')}`);
|
|
65
|
+
if (e.gotchas?.length) { lines.push('**Gotchas:**'); e.gotchas.forEach(g => lines.push(`- ${g}`)); }
|
|
66
|
+
if (e.tags?.length) lines.push(`**Tags:** ${e.tags.join(', ')}`);
|
|
67
|
+
lines.push('');
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Threads
|
|
72
|
+
if (knowledge.threads.length > 0) {
|
|
73
|
+
lines.push('## Threads');
|
|
74
|
+
lines.push('*What\'s in progress and what\'s next.*');
|
|
75
|
+
lines.push('');
|
|
76
|
+
for (const e of knowledge.threads) {
|
|
77
|
+
lines.push(`### ${e.thread || e.id} — ${(e.status || '?').toUpperCase()}`);
|
|
78
|
+
if (e.done?.length) { lines.push('**Done:**'); e.done.forEach(d => lines.push(`- ${d}`)); }
|
|
79
|
+
if (e.remaining?.length) { lines.push('**Remaining:**'); e.remaining.forEach(r => lines.push(`- ${r}`)); }
|
|
80
|
+
if (e.blocked_by) lines.push(`**Blocked by:** ${e.blocked_by}`);
|
|
81
|
+
if (e.tags?.length) lines.push(`**Tags:** ${e.tags.join(', ')}`);
|
|
82
|
+
if (e.updated) lines.push(`**Updated:** ${e.updated}`);
|
|
83
|
+
lines.push('');
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Warnings
|
|
88
|
+
if (knowledge.warnings.length > 0) {
|
|
89
|
+
lines.push('## Warnings');
|
|
90
|
+
lines.push('*Dead ends, traps, and things to avoid.*');
|
|
91
|
+
lines.push('');
|
|
92
|
+
for (const e of knowledge.warnings) {
|
|
93
|
+
lines.push(`### ${e.warning || e.id}`);
|
|
94
|
+
if (e.context) lines.push(`**Context:** ${e.context}`);
|
|
95
|
+
if (e.avoid) lines.push(`**Avoid:** ${e.avoid}`);
|
|
96
|
+
if (e.tags?.length) lines.push(`**Tags:** ${e.tags.join(', ')}`);
|
|
97
|
+
if (e.date) lines.push(`**Date:** ${e.date}`);
|
|
98
|
+
lines.push('');
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (totalEntries === 0) {
|
|
103
|
+
lines.push('*No entries yet. Start a Claude Code session and build something!*');
|
|
104
|
+
lines.push('');
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
output = lines.join('\n');
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (values.output) {
|
|
111
|
+
fs.writeFileSync(values.output, output, 'utf8');
|
|
112
|
+
console.log(`Exported ${totalEntries} entries to ${values.output}`);
|
|
113
|
+
} else {
|
|
114
|
+
process.stdout.write(output);
|
|
115
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('node:fs');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
const { parseArgs } = require('node:util');
|
|
6
|
+
const paths = require('../lib/paths');
|
|
7
|
+
const { mergeHooksIntoSettings } = require('../lib/merge-settings');
|
|
8
|
+
|
|
9
|
+
const { values } = parseArgs({
|
|
10
|
+
options: { force: { type: 'boolean', short: 'f', default: false } },
|
|
11
|
+
strict: false,
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
const cwd = process.cwd();
|
|
15
|
+
const ascrDir = paths.ascrDir(cwd);
|
|
16
|
+
const alreadyExists = fs.existsSync(ascrDir) && fs.existsSync(paths.decisionsFile(cwd));
|
|
17
|
+
|
|
18
|
+
if (alreadyExists && !values.force) {
|
|
19
|
+
console.log('ASCR is already initialized in this project.');
|
|
20
|
+
console.log('Run `ascr init --force` to re-initialize and update hooks.');
|
|
21
|
+
process.exit(0);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const created = [];
|
|
25
|
+
const updated = [];
|
|
26
|
+
|
|
27
|
+
// ── 1. Create .ascr/ directories ──────────────────────────────────────────────
|
|
28
|
+
fs.mkdirSync(paths.hooksDir(cwd), { recursive: true });
|
|
29
|
+
|
|
30
|
+
// ── 2. Create empty knowledge files (only if not present) ────────────────────
|
|
31
|
+
const knowledgeFiles = {
|
|
32
|
+
'decisions.json': paths.decisionsFile(cwd),
|
|
33
|
+
'architecture.json': paths.architectureFile(cwd),
|
|
34
|
+
'threads.json': paths.threadsFile(cwd),
|
|
35
|
+
'warnings.json': paths.warningsFile(cwd),
|
|
36
|
+
'index.json': paths.indexFile(cwd),
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const emptyIndex = JSON.stringify({ decisions: [], architecture: [], threads: [], warnings: [] }, null, 2) + '\n';
|
|
40
|
+
|
|
41
|
+
for (const [name, file] of Object.entries(knowledgeFiles)) {
|
|
42
|
+
if (!fs.existsSync(file) || values.force) {
|
|
43
|
+
const content = name === 'index.json' ? emptyIndex : '[]\n';
|
|
44
|
+
fs.writeFileSync(file, content, 'utf8');
|
|
45
|
+
created.push(`.ascr/${name}`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// ── 3. Create .ascr/.gitignore ─────────────────────────────────────────────────
|
|
50
|
+
const gitignorePath = paths.gitignoreFile(cwd);
|
|
51
|
+
const gitignoreContent = '# ASCR session state (ephemeral — do not commit)\n.session-hash-*\n';
|
|
52
|
+
if (!fs.existsSync(gitignorePath) || values.force) {
|
|
53
|
+
fs.writeFileSync(gitignorePath, gitignoreContent, 'utf8');
|
|
54
|
+
created.push('.ascr/.gitignore');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// ── 4. Copy hook scripts from package source ──────────────────────────────────
|
|
58
|
+
const hookSources = {
|
|
59
|
+
'session-start.js': path.join(__dirname, '..', 'hooks', 'session-start.js'),
|
|
60
|
+
'prompt-retriever.js':path.join(__dirname, '..', 'hooks', 'prompt-retriever.js'),
|
|
61
|
+
'stop-gate.js': path.join(__dirname, '..', 'hooks', 'stop-gate.js'),
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
for (const [name, src] of Object.entries(hookSources)) {
|
|
65
|
+
const dest = path.join(paths.hooksDir(cwd), name);
|
|
66
|
+
const content = fs.readFileSync(src, 'utf8');
|
|
67
|
+
const exists = fs.existsSync(dest);
|
|
68
|
+
fs.writeFileSync(dest, content, 'utf8');
|
|
69
|
+
if (exists) updated.push(`.ascr/hooks/${name}`);
|
|
70
|
+
else created.push(`.ascr/hooks/${name}`);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// ── 5. Create or merge .claude/settings.json ─────────────────────────────────
|
|
74
|
+
fs.mkdirSync(paths.claudeDir(cwd), { recursive: true });
|
|
75
|
+
const settingsPath = paths.settingsFile(cwd);
|
|
76
|
+
let existingSettings = {};
|
|
77
|
+
|
|
78
|
+
if (fs.existsSync(settingsPath)) {
|
|
79
|
+
try {
|
|
80
|
+
existingSettings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
|
|
81
|
+
} catch {
|
|
82
|
+
console.error(`\nError: .claude/settings.json is malformed JSON. Please fix it before running ascr init.`);
|
|
83
|
+
process.exit(1);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const mergedSettings = mergeHooksIntoSettings(existingSettings);
|
|
88
|
+
fs.writeFileSync(settingsPath, JSON.stringify(mergedSettings, null, 2) + '\n', 'utf8');
|
|
89
|
+
if (fs.existsSync(settingsPath) && JSON.stringify(existingSettings) !== '{}') {
|
|
90
|
+
updated.push('.claude/settings.json');
|
|
91
|
+
} else {
|
|
92
|
+
created.push('.claude/settings.json');
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// ── 6. Print summary ──────────────────────────────────────────────────────────
|
|
96
|
+
console.log('');
|
|
97
|
+
if (created.length > 0) {
|
|
98
|
+
for (const f of created) console.log(` Created: ${f}`);
|
|
99
|
+
}
|
|
100
|
+
if (updated.length > 0) {
|
|
101
|
+
for (const f of updated) console.log(` Updated: ${f}`);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
console.log('');
|
|
105
|
+
console.log('ASCR initialized! Start a new Claude Code session to activate.');
|
|
106
|
+
console.log('');
|
|
107
|
+
console.log('How it works:');
|
|
108
|
+
console.log(' • Each prompt → relevant knowledge injected automatically');
|
|
109
|
+
console.log(' • After making code changes → you must update .ascr/ before ending');
|
|
110
|
+
console.log(' • Run `ascr status` to verify setup');
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { parseArgs } = require('node:util');
|
|
4
|
+
const paths = require('../lib/paths');
|
|
5
|
+
const { readKnowledge, TYPES, filterEntries } = require('../lib/memory');
|
|
6
|
+
|
|
7
|
+
const { values } = parseArgs({
|
|
8
|
+
options: {
|
|
9
|
+
type: { type: 'string', short: 't' },
|
|
10
|
+
tag: { type: 'string' },
|
|
11
|
+
status: { type: 'string' },
|
|
12
|
+
json: { type: 'boolean', default: false },
|
|
13
|
+
last: { type: 'string' },
|
|
14
|
+
},
|
|
15
|
+
strict: false,
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
const cwd = process.cwd();
|
|
19
|
+
const typeFilter = values.type;
|
|
20
|
+
const typesToShow = typeFilter ? [typeFilter] : TYPES;
|
|
21
|
+
const last = values.last ? parseInt(values.last, 10) : null;
|
|
22
|
+
|
|
23
|
+
if (typeFilter && !TYPES.includes(typeFilter)) {
|
|
24
|
+
console.error(`Unknown type '${typeFilter}'. Valid: ${TYPES.join(', ')}`);
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
let hasAny = false;
|
|
29
|
+
|
|
30
|
+
for (const type of typesToShow) {
|
|
31
|
+
let entries = readKnowledge(cwd, type);
|
|
32
|
+
entries = filterEntries(entries, { tag: values.tag, status: values.status });
|
|
33
|
+
if (last) entries = entries.slice(-last);
|
|
34
|
+
if (entries.length === 0) continue;
|
|
35
|
+
hasAny = true;
|
|
36
|
+
|
|
37
|
+
if (values.json) {
|
|
38
|
+
console.log(JSON.stringify({ [type]: entries }, null, 2));
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
console.log(`\n── ${type.toUpperCase()} (${entries.length}) ─────────────────────────────────`);
|
|
43
|
+
for (const e of entries) {
|
|
44
|
+
switch (type) {
|
|
45
|
+
case 'decisions':
|
|
46
|
+
console.log(` [${e.id || '?'}] ${e.decision}`);
|
|
47
|
+
if (e.why) console.log(` Why: ${e.why}`);
|
|
48
|
+
if (e.tags?.length) console.log(` Tags: ${e.tags.join(', ')}`);
|
|
49
|
+
if (e.date) console.log(` Date: ${e.date}`);
|
|
50
|
+
break;
|
|
51
|
+
case 'architecture':
|
|
52
|
+
console.log(` [${e.id || '?'}] ${e.component} — ${e.location || 'no location'}`);
|
|
53
|
+
if (e.does) console.log(` ${e.does}`);
|
|
54
|
+
if (e.tags?.length) console.log(` Tags: ${e.tags.join(', ')}`);
|
|
55
|
+
if (e.gotchas?.length) console.log(` Gotchas: ${e.gotchas.join(' | ')}`);
|
|
56
|
+
break;
|
|
57
|
+
case 'threads':
|
|
58
|
+
console.log(` [${e.id || '?'}] ${e.thread} — ${(e.status || '?').toUpperCase()}`);
|
|
59
|
+
if (e.remaining?.length) console.log(` Remaining: ${e.remaining.join(', ')}`);
|
|
60
|
+
if (e.blocked_by) console.log(` Blocked by: ${e.blocked_by}`);
|
|
61
|
+
if (e.tags?.length) console.log(` Tags: ${e.tags.join(', ')}`);
|
|
62
|
+
break;
|
|
63
|
+
case 'warnings':
|
|
64
|
+
console.log(` [${e.id || '?'}] ${e.warning}`);
|
|
65
|
+
if (e.avoid) console.log(` Avoid: ${e.avoid}`);
|
|
66
|
+
if (e.tags?.length) console.log(` Tags: ${e.tags.join(', ')}`);
|
|
67
|
+
if (e.date) console.log(` Date: ${e.date}`);
|
|
68
|
+
break;
|
|
69
|
+
}
|
|
70
|
+
console.log('');
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (!hasAny) {
|
|
75
|
+
const ctx = typeFilter ? `in '${typeFilter}'` : 'in any knowledge file';
|
|
76
|
+
console.log(`\nNo entries found ${ctx}.`);
|
|
77
|
+
if (!paths.decisionsFile(cwd).includes('.ascr')) {
|
|
78
|
+
console.log('Run `ascr init` to set up ASCR first.');
|
|
79
|
+
}
|
|
80
|
+
console.log('');
|
|
81
|
+
}
|