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 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
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ require('../src/cli.js');
package/bin/ascr.js ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ require('../src/cli.js');
package/bin/scoops.js ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ require('../src/cli.js');
package/bin/zute.js ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ require('../src/cli.js');
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
+ }