claudenv 1.2.5 → 1.3.1
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/README.md +93 -3
- package/bin/cli.js +294 -0
- package/package.json +1 -1
- package/scaffold/.claude/memories/README.md +44 -0
- package/scaffold/global/.claude/commands/add-source.md +31 -0
- package/scaffold/global/.claude/commands/canon.md +33 -0
- package/scaffold/global/.claude/commands/decisions.md +29 -0
- package/scaffold/global/.claude/commands/deeper.md +55 -0
- package/scaffold/global/.claude/commands/just-code.md +26 -0
- package/scaffold/global/.claude/commands/why.md +33 -0
- package/scaffold/global/.claude/skills/source-connector/SKILL.md +128 -0
- package/scaffold/global/.claude/skills/vibe-decisions/SKILL.md +127 -0
- package/scaffold/global/.claude/skills/vibe-decisions/deep-dive-template.md +41 -0
- package/scaffold/global-claudenv/config.yaml +16 -0
- package/scaffold/global-claudenv/memories/INDEX.md +25 -0
- package/scaffold/global-claudenv/memories/canon/index.yaml +21 -0
- package/scaffold/global-claudenv/memories/user/preferences.md.example +16 -0
- package/src/autonomy.js +6 -1
- package/src/canon.js +214 -0
- package/src/decisions.js +161 -0
- package/src/doctor.js +201 -0
- package/src/hooks/decisions-logger.js +183 -0
- package/src/hooks/dispatcher.js +91 -0
- package/src/hooks/regen-index.js +198 -0
- package/src/hooks-gen.js +43 -1
- package/src/installer.js +57 -14
- package/src/loop.js +26 -1
- package/src/memory-context.js +63 -0
- package/src/memory-paths.js +128 -0
- package/src/memory.js +111 -0
- package/src/sources.js +78 -0
- package/src/workspaces.js +129 -0
- package/templates/connector-mssql.ejs +62 -0
- package/templates/connector-rest.ejs +59 -0
package/README.md
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
# claudenv
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/claudenv)
|
|
4
|
+
|
|
5
|
+
Set up [Claude Code](https://docs.anthropic.com/en/docs/claude-code) in any project with one command. claudenv analyzes your codebase and generates everything Claude needs to work effectively — documentation, rules, hooks, MCP servers, slash commands, **plus cross-session memory and vibe-decisions logging (1.3.0).**
|
|
6
|
+
|
|
7
|
+
> **New in 1.3.0** — split memory layout (`~/.claudenv/memories/` global + `.claude/memories/` project), `vibe-decisions` skill (auto-log in loop, pause-and-ask interactive), new CLI: `memory`, `decisions`, `canon`, `doctor`. Companion Python package `claudenv-memory` on PyPI (alpha). See [CHANGELOG.md](./CHANGELOG.md).
|
|
4
8
|
|
|
5
9
|
## Quick Start
|
|
6
10
|
|
|
@@ -17,8 +21,8 @@ Claude reads your code, asks a few questions, and generates:
|
|
|
17
21
|
- **CLAUDE.md** — project overview, architecture, key commands
|
|
18
22
|
- **Rules** — coding style, testing patterns, workflow guidelines (`.claude/rules/`)
|
|
19
23
|
- **MCP servers** — auto-detected from your stack, configured in `.mcp.json`
|
|
20
|
-
- **Slash commands** — `/init-docs`, `/update-docs`, `/validate-docs`, `/setup-mcp`, `/improve
|
|
21
|
-
- **Hooks** — validation on tool use, audit logging (`.claude/settings.json`
|
|
24
|
+
- **Slash commands** — `/init-docs`, `/update-docs`, `/validate-docs`, `/setup-mcp`, `/improve`, **`/deeper`**, **`/why`**, **`/decisions`**, **`/canon`**, **`/just-code`** (1.3.0)
|
|
25
|
+
- **Hooks** — validation on tool use, audit logging, decisions-logger (PostToolUse Write), regen-index (SessionEnd) — `.claude/settings.json`
|
|
22
26
|
|
|
23
27
|
Everything is committed to your repo. Team members get the same Claude experience.
|
|
24
28
|
|
|
@@ -151,6 +155,87 @@ claudenv loop --profile moderate --goal "add types" # sonnet, deny-list guard
|
|
|
151
155
|
claudenv loop --profile ci --model sonnet # ci profile but with sonnet
|
|
152
156
|
```
|
|
153
157
|
|
|
158
|
+
## Memory & vibe-decisions (1.3.0)
|
|
159
|
+
|
|
160
|
+
Persistent memory across sessions + brief overview before non-trivial technical choices. Auto-log inside `claudenv loop` (no pauses — `autonomy=law`), pause-and-ask in interactive Claude Code chats.
|
|
161
|
+
|
|
162
|
+
### Layout (split)
|
|
163
|
+
|
|
164
|
+
```
|
|
165
|
+
~/.claudenv/memories/ # global, cross-project
|
|
166
|
+
├── INDEX.md # briefing — auto-regenerated
|
|
167
|
+
├── decisions/ # universal tech decisions
|
|
168
|
+
├── canon/index.yaml # personal canon of references
|
|
169
|
+
└── user/preferences.md # cross-project preferences
|
|
170
|
+
|
|
171
|
+
<project>/.claude/memories/ # project-scoped, committed
|
|
172
|
+
├── project.md # stack, conventions, internal urls
|
|
173
|
+
└── decisions/ # project-only decisions
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### Commands
|
|
177
|
+
|
|
178
|
+
```bash
|
|
179
|
+
# Memory
|
|
180
|
+
claudenv memory init # initialise ~/.claudenv/memories/
|
|
181
|
+
claudenv memory index # regenerate INDEX.md
|
|
182
|
+
claudenv memory show <path> # print a memory file
|
|
183
|
+
claudenv memory edit <path> # open in $EDITOR
|
|
184
|
+
|
|
185
|
+
# Decisions
|
|
186
|
+
claudenv decisions list [--scope all|global|project] [--limit N]
|
|
187
|
+
claudenv decisions show <id-or-slug>
|
|
188
|
+
claudenv decisions search <query>
|
|
189
|
+
claudenv decisions archive <id>
|
|
190
|
+
|
|
191
|
+
# Canon
|
|
192
|
+
claudenv canon add <topic> <url> --why "<reason>" [--title <t>] [--author <a>]
|
|
193
|
+
claudenv canon list [<topic>]
|
|
194
|
+
claudenv canon search <query>
|
|
195
|
+
claudenv canon prune --months 6 # find stale entries
|
|
196
|
+
|
|
197
|
+
# Health
|
|
198
|
+
claudenv doctor # OK/WARN/FAIL check
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### Slash-commands (in Claude Code session)
|
|
202
|
+
|
|
203
|
+
| Command | Purpose |
|
|
204
|
+
|---|---|
|
|
205
|
+
| `/deeper` | Develop the most recent decision into a full deep dive (concept → variants → trade-offs → canon) |
|
|
206
|
+
| `/why <X>` | Explain technology X without logging a decision |
|
|
207
|
+
| `/decisions` | List/search logged decisions via the CLI |
|
|
208
|
+
| `/canon` | Browse the personal canon |
|
|
209
|
+
| `/just-code` | Suppress vibe-decisions overview for the next response only |
|
|
210
|
+
|
|
211
|
+
### How auto-log works in `claudenv loop`
|
|
212
|
+
|
|
213
|
+
`claudenv loop` appends a system-prompt fragment that switches the `vibe-decisions` skill into **auto-log mode** — it picks the approach, writes the decision file (`/memories/decisions/<date>-<slug>.md`), and continues coding without pausing. Outside loop (interactive Claude Code) the skill defaults to **pause-and-ask** mode.
|
|
214
|
+
|
|
215
|
+
After each iteration, `claudenv loop` regenerates `INDEX.md` so the next iteration's briefing reflects what was just decided.
|
|
216
|
+
|
|
217
|
+
### Python companion: `claudenv-memory`
|
|
218
|
+
|
|
219
|
+
Building your own agent on Claude Agent SDK and want the same memory layout?
|
|
220
|
+
|
|
221
|
+
```bash
|
|
222
|
+
pip install claudenv-memory
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
```python
|
|
226
|
+
from claudenv_memory import LocalFileMemoryBackend
|
|
227
|
+
from claude_agent_sdk import ClaudeAgentOptions
|
|
228
|
+
|
|
229
|
+
backend = LocalFileMemoryBackend() # ~/.claudenv/memories + ./.claude/memories
|
|
230
|
+
options = ClaudeAgentOptions(
|
|
231
|
+
model="claude-opus-4-7",
|
|
232
|
+
extra_headers={"anthropic-beta": "context-management-2025-06-27"},
|
|
233
|
+
tools=[backend.as_tool()],
|
|
234
|
+
)
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
Status: 0.1.x alpha — first production consumer is claudenv 2.0. See `python/README.md`.
|
|
238
|
+
|
|
154
239
|
## MCP Server Setup
|
|
155
240
|
|
|
156
241
|
`/claudenv` auto-detects your tech stack and recommends MCP servers from the [official registry](https://registry.modelcontextprotocol.io). You can also run `/setup-mcp` independently.
|
|
@@ -221,6 +306,11 @@ claudenv autonomy [-p <profile>] Configure autonomy profiles
|
|
|
221
306
|
claudenv init [dir] [-y] Legacy: static analysis (no AI)
|
|
222
307
|
claudenv generate [-d <dir>] Templates only, no scaffold
|
|
223
308
|
claudenv validate [-d <dir>] Check documentation completeness
|
|
309
|
+
|
|
310
|
+
claudenv memory init|index|show|edit Manage ~/.claudenv/memories/ (1.3.0+)
|
|
311
|
+
claudenv decisions list|show|search Logged vibe-decisions (1.3.0+)
|
|
312
|
+
claudenv canon add|list|search|prune Personal canon (1.3.0+)
|
|
313
|
+
claudenv doctor Health check (1.3.0+)
|
|
224
314
|
```
|
|
225
315
|
|
|
226
316
|
## Run Without Installing
|
package/bin/cli.js
CHANGED
|
@@ -288,6 +288,300 @@ program
|
|
|
288
288
|
.option('--dry-run', 'Preview without writing')
|
|
289
289
|
.action(runAutonomy);
|
|
290
290
|
|
|
291
|
+
// --- hook (internal entry point for Claude Code hooks) ---
|
|
292
|
+
program
|
|
293
|
+
.command('hook')
|
|
294
|
+
.description('Internal: dispatch a Claude Code hook by name (decisions-logger, regen-index)')
|
|
295
|
+
.argument('<name>', 'Hook name')
|
|
296
|
+
.action(async (name) => {
|
|
297
|
+
const { dispatch } = await import('../src/hooks/dispatcher.js');
|
|
298
|
+
await dispatch(name);
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
// =============================================
|
|
302
|
+
// 1.3.0: memory / decisions / canon / doctor
|
|
303
|
+
// =============================================
|
|
304
|
+
|
|
305
|
+
// --- memory ---
|
|
306
|
+
const memoryCmd = program.command('memory').description('Manage the global ~/.claudenv/memories/ layout');
|
|
307
|
+
|
|
308
|
+
memoryCmd
|
|
309
|
+
.command('init')
|
|
310
|
+
.description('Create the ~/.claudenv/memories/ structure (idempotent)')
|
|
311
|
+
.action(async () => {
|
|
312
|
+
const { memoryInit } = await import('../src/memory.js');
|
|
313
|
+
const { created, skipped } = await memoryInit();
|
|
314
|
+
for (const p of created) console.log(` + ${p}`);
|
|
315
|
+
for (const p of skipped) console.log(` ~ ${p}`);
|
|
316
|
+
console.log(`\n ${created.length} created, ${skipped.length} already present.`);
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
memoryCmd
|
|
320
|
+
.command('index')
|
|
321
|
+
.description('Regenerate ~/.claudenv/memories/INDEX.md from current decisions and prefs')
|
|
322
|
+
.action(async () => {
|
|
323
|
+
const { memoryIndex } = await import('../src/memory.js');
|
|
324
|
+
const result = await memoryIndex();
|
|
325
|
+
console.log(` INDEX.md regenerated → ${result.indexPath}`);
|
|
326
|
+
console.log(` ${result.recentCount} recent of ${result.decisionCount} total decisions`);
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
memoryCmd
|
|
330
|
+
.command('show')
|
|
331
|
+
.argument('<path>', 'Path relative to ~/.claudenv/memories/')
|
|
332
|
+
.description('Print a memory file to stdout')
|
|
333
|
+
.action(async (path) => {
|
|
334
|
+
const { memoryShow } = await import('../src/memory.js');
|
|
335
|
+
try {
|
|
336
|
+
process.stdout.write(await memoryShow(path));
|
|
337
|
+
} catch (err) {
|
|
338
|
+
console.error(err.message);
|
|
339
|
+
process.exit(2);
|
|
340
|
+
}
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
memoryCmd
|
|
344
|
+
.command('edit')
|
|
345
|
+
.argument('<path>', 'Path relative to ~/.claudenv/memories/')
|
|
346
|
+
.description('Open a memory file in $EDITOR (vi by default)')
|
|
347
|
+
.action(async (path) => {
|
|
348
|
+
const { memoryEdit } = await import('../src/memory.js');
|
|
349
|
+
const result = await memoryEdit(path);
|
|
350
|
+
process.exit(result.exitCode);
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
// --- decisions ---
|
|
354
|
+
const decisionsCmd = program.command('decisions').description('List, show, or search logged vibe-decisions');
|
|
355
|
+
|
|
356
|
+
decisionsCmd
|
|
357
|
+
.command('list')
|
|
358
|
+
.description('List recent decisions (newest first)')
|
|
359
|
+
.option('--scope <s>', 'global | project | all', 'all')
|
|
360
|
+
.option('--limit <n>', 'Max entries', '10')
|
|
361
|
+
.action(async (opts) => {
|
|
362
|
+
const { listDecisions, formatDecisionList } = await import('../src/decisions.js');
|
|
363
|
+
const limit = parseInt(opts.limit, 10) || 10;
|
|
364
|
+
const all = await listDecisions({ scope: opts.scope });
|
|
365
|
+
console.log(formatDecisionList(all.slice(0, limit)));
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
decisionsCmd
|
|
369
|
+
.command('show')
|
|
370
|
+
.argument('<id>', 'Slug or substring of the decision')
|
|
371
|
+
.description('Show full details of one decision')
|
|
372
|
+
.action(async (id) => {
|
|
373
|
+
const { showDecision, formatDecisionDetail } = await import('../src/decisions.js');
|
|
374
|
+
try {
|
|
375
|
+
const d = await showDecision(id);
|
|
376
|
+
console.log(formatDecisionDetail(d));
|
|
377
|
+
console.log('\n---\n');
|
|
378
|
+
console.log(d.text);
|
|
379
|
+
} catch (err) {
|
|
380
|
+
console.error(err.message);
|
|
381
|
+
process.exit(err.notFound ? 1 : 2);
|
|
382
|
+
}
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
decisionsCmd
|
|
386
|
+
.command('search')
|
|
387
|
+
.argument('<query>', 'Substring to search topic/reason/chose')
|
|
388
|
+
.description('Search decisions')
|
|
389
|
+
.action(async (query) => {
|
|
390
|
+
const { searchDecisions, formatDecisionList } = await import('../src/decisions.js');
|
|
391
|
+
const hits = await searchDecisions(query);
|
|
392
|
+
console.log(formatDecisionList(hits));
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
decisionsCmd
|
|
396
|
+
.command('archive')
|
|
397
|
+
.argument('<id>', 'Slug to archive')
|
|
398
|
+
.description('Move a decision into <scope-dir>/archive/')
|
|
399
|
+
.action(async (id) => {
|
|
400
|
+
const { archiveDecision } = await import('../src/decisions.js');
|
|
401
|
+
try {
|
|
402
|
+
const { from, to } = await archiveDecision(id);
|
|
403
|
+
console.log(` archived: ${from} → ${to}`);
|
|
404
|
+
} catch (err) {
|
|
405
|
+
console.error(err.message);
|
|
406
|
+
process.exit(2);
|
|
407
|
+
}
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
// --- canon ---
|
|
411
|
+
const canonCmd = program.command('canon').description('Personal canon of references (~/.claudenv/memories/canon/index.yaml)');
|
|
412
|
+
|
|
413
|
+
canonCmd
|
|
414
|
+
.command('add')
|
|
415
|
+
.argument('<topic>', 'Topic slug')
|
|
416
|
+
.argument('<url>', 'URL to add')
|
|
417
|
+
.option('--why <reason>', 'Why this reference is in the canon')
|
|
418
|
+
.option('--title <title>', 'Title')
|
|
419
|
+
.option('--author <author>', 'Author or venue')
|
|
420
|
+
.action(async (topic, url, opts) => {
|
|
421
|
+
const { canonAdd } = await import('../src/canon.js');
|
|
422
|
+
try {
|
|
423
|
+
const res = await canonAdd({
|
|
424
|
+
topic,
|
|
425
|
+
url,
|
|
426
|
+
why: opts.why,
|
|
427
|
+
title: opts.title,
|
|
428
|
+
author: opts.author,
|
|
429
|
+
});
|
|
430
|
+
if (res.added) console.log(` + ${topic}: ${res.entry.title || res.entry.url}`);
|
|
431
|
+
else console.log(` ~ duplicate url in ${topic} — skipped`);
|
|
432
|
+
} catch (err) {
|
|
433
|
+
console.error(err.message);
|
|
434
|
+
process.exit(2);
|
|
435
|
+
}
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
canonCmd
|
|
439
|
+
.command('list')
|
|
440
|
+
.argument('[topic]', 'Optional topic filter')
|
|
441
|
+
.action(async (topic) => {
|
|
442
|
+
const { canonList, formatCanon } = await import('../src/canon.js');
|
|
443
|
+
const data = await canonList(topic);
|
|
444
|
+
console.log(formatCanon(data));
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
canonCmd
|
|
448
|
+
.command('search')
|
|
449
|
+
.argument('<query>', 'Substring search')
|
|
450
|
+
.action(async (query) => {
|
|
451
|
+
const { canonSearch, formatCanon } = await import('../src/canon.js');
|
|
452
|
+
const data = await canonSearch(query);
|
|
453
|
+
console.log(formatCanon(data));
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
canonCmd
|
|
457
|
+
.command('prune')
|
|
458
|
+
.option('--months <n>', 'Age threshold in months', '6')
|
|
459
|
+
.action(async (opts) => {
|
|
460
|
+
const { canonPrune } = await import('../src/canon.js');
|
|
461
|
+
const stale = await canonPrune(parseInt(opts.months, 10) || 6);
|
|
462
|
+
if (stale.length === 0) {
|
|
463
|
+
console.log(' No stale entries.');
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
466
|
+
for (const { topic, entry, reason } of stale) {
|
|
467
|
+
const why = reason || `added ${entry.added}`;
|
|
468
|
+
console.log(` ${topic}: ${entry.url} (${why})`);
|
|
469
|
+
}
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
// =============================================
|
|
473
|
+
// Workspaces (isolated per-company/context memory)
|
|
474
|
+
// =============================================
|
|
475
|
+
const wsCmd = program.command('workspace').description('Isolated memory spaces (~/.claudenv/workspaces/)');
|
|
476
|
+
|
|
477
|
+
wsCmd
|
|
478
|
+
.command('add')
|
|
479
|
+
.argument('<id>', 'Workspace id (slug: a-z0-9-_)')
|
|
480
|
+
.option('--name <name>', 'Human-readable name')
|
|
481
|
+
.option('--desc <description>', 'Description')
|
|
482
|
+
.option('--path <path...>', 'Project path(s) bound to this workspace')
|
|
483
|
+
.action(async (id, opts) => {
|
|
484
|
+
const { createWorkspace } = await import('../src/workspaces.js');
|
|
485
|
+
try {
|
|
486
|
+
await createWorkspace(id, { name: opts.name, description: opts.desc, paths: opts.path });
|
|
487
|
+
console.log(` + workspace "${id}" created`);
|
|
488
|
+
} catch (err) {
|
|
489
|
+
console.error(err.message);
|
|
490
|
+
process.exit(2);
|
|
491
|
+
}
|
|
492
|
+
});
|
|
493
|
+
|
|
494
|
+
wsCmd
|
|
495
|
+
.command('list')
|
|
496
|
+
.action(async () => {
|
|
497
|
+
const { listWorkspaces } = await import('../src/workspaces.js');
|
|
498
|
+
const list = await listWorkspaces();
|
|
499
|
+
if (list.length === 0) {
|
|
500
|
+
console.log(' No workspaces yet — create one with `claudenv workspace add <id>`');
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
503
|
+
for (const w of list) {
|
|
504
|
+
console.log(` ${w.active ? '*' : ' '} ${w.id}${w.name && w.name !== w.id ? ` — ${w.name}` : ''}`);
|
|
505
|
+
}
|
|
506
|
+
});
|
|
507
|
+
|
|
508
|
+
wsCmd
|
|
509
|
+
.command('use')
|
|
510
|
+
.argument('<id>', 'Workspace id to activate')
|
|
511
|
+
.action(async (id) => {
|
|
512
|
+
const { useWorkspace } = await import('../src/workspaces.js');
|
|
513
|
+
try {
|
|
514
|
+
await useWorkspace(id);
|
|
515
|
+
console.log(` active workspace → ${id}`);
|
|
516
|
+
} catch (err) {
|
|
517
|
+
console.error(err.message);
|
|
518
|
+
process.exit(2);
|
|
519
|
+
}
|
|
520
|
+
});
|
|
521
|
+
|
|
522
|
+
wsCmd
|
|
523
|
+
.command('show')
|
|
524
|
+
.action(async () => {
|
|
525
|
+
const { showActive } = await import('../src/workspaces.js');
|
|
526
|
+
const w = await showActive();
|
|
527
|
+
if (!w) {
|
|
528
|
+
console.log(' No active workspace (set CLAUDENV_WORKSPACE or run `claudenv workspace use <id>`)');
|
|
529
|
+
return;
|
|
530
|
+
}
|
|
531
|
+
console.log(` active: ${w.id} (${w.source})`);
|
|
532
|
+
if (w.name && w.name !== w.id) console.log(` name: ${w.name}`);
|
|
533
|
+
if (w.description) console.log(` desc: ${w.description}`);
|
|
534
|
+
if (w.paths && w.paths.length) console.log(` paths: ${w.paths.join(', ')}`);
|
|
535
|
+
});
|
|
536
|
+
|
|
537
|
+
// =============================================
|
|
538
|
+
// Sources (connectors in the active workspace)
|
|
539
|
+
// =============================================
|
|
540
|
+
const srcCmd = program.command('source').description('Data-source connectors in the active workspace');
|
|
541
|
+
|
|
542
|
+
srcCmd
|
|
543
|
+
.command('list')
|
|
544
|
+
.action(async () => {
|
|
545
|
+
const { listConnectors } = await import('../src/sources.js');
|
|
546
|
+
const { workspace, connectors } = await listConnectors();
|
|
547
|
+
if (!workspace) {
|
|
548
|
+
console.log(' No active workspace — set one first (`claudenv workspace use <id>`)');
|
|
549
|
+
return;
|
|
550
|
+
}
|
|
551
|
+
if (connectors.length === 0) {
|
|
552
|
+
console.log(` [${workspace}] no connectors yet — add one via /add-source in Claude Code`);
|
|
553
|
+
return;
|
|
554
|
+
}
|
|
555
|
+
console.log(` [${workspace}]`);
|
|
556
|
+
for (const c of connectors) {
|
|
557
|
+
console.log(` ${c.name} (${c.type}, ${c.status})${c.host ? ` ${c.host}` : ''}`);
|
|
558
|
+
}
|
|
559
|
+
});
|
|
560
|
+
|
|
561
|
+
srcCmd
|
|
562
|
+
.command('show')
|
|
563
|
+
.argument('<name>', 'Connector name')
|
|
564
|
+
.action(async (name) => {
|
|
565
|
+
const { showConnector } = await import('../src/sources.js');
|
|
566
|
+
const text = await showConnector(name);
|
|
567
|
+
if (!text) {
|
|
568
|
+
console.error(` Connector "${name}" not found in active workspace`);
|
|
569
|
+
process.exit(2);
|
|
570
|
+
}
|
|
571
|
+
console.log(text);
|
|
572
|
+
});
|
|
573
|
+
|
|
574
|
+
// --- doctor ---
|
|
575
|
+
program
|
|
576
|
+
.command('doctor')
|
|
577
|
+
.description('Health-check the claudenv setup')
|
|
578
|
+
.action(async () => {
|
|
579
|
+
const { runDoctor } = await import('../src/doctor.js');
|
|
580
|
+
const { lines, hasFail } = await runDoctor();
|
|
581
|
+
for (const l of lines) console.log(l);
|
|
582
|
+
process.exit(hasFail ? 1 : 0);
|
|
583
|
+
});
|
|
584
|
+
|
|
291
585
|
// =============================================
|
|
292
586
|
// Install / Uninstall
|
|
293
587
|
// =============================================
|
package/package.json
CHANGED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Project memories
|
|
2
|
+
|
|
3
|
+
This directory holds **project-scoped** memory for `vibe-decisions` and related skills. Cross-project memory lives in `~/.claudenv/memories/`.
|
|
4
|
+
|
|
5
|
+
## Layout
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
.claude/memories/
|
|
9
|
+
├── project.md # стек, конвенции, важные urls — заполняешь вручную
|
|
10
|
+
├── decisions/ # project-specific tech decisions (committed to repo)
|
|
11
|
+
│ └── 2026-05-27-auth-jwt.md
|
|
12
|
+
└── README.md # this file
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## When goes here vs `~/.claudenv/memories/`
|
|
16
|
+
|
|
17
|
+
- **Здесь (project)**: выбор специфичен этому проекту — внутренний сервис, формат API, deployment target, project-only convention
|
|
18
|
+
- **В `~/.claudenv/memories/decisions/` (global)**: универсальный tech-выбор — какая БД, какой algo, который применим в любом проекте
|
|
19
|
+
|
|
20
|
+
Skill `vibe-decisions` решает scope автоматически по полю `scope: global|project` в frontmatter. По умолчанию `global` если непонятно.
|
|
21
|
+
|
|
22
|
+
## project.md шаблон
|
|
23
|
+
|
|
24
|
+
Создай этот файл вручную (claudenv не генерит автоматически):
|
|
25
|
+
|
|
26
|
+
```markdown
|
|
27
|
+
# <project-name>
|
|
28
|
+
|
|
29
|
+
- **Stack:** Python 3.12, FastAPI, Postgres, Redis
|
|
30
|
+
- **Tests:** `pytest -m "not slow"` локально; CI запускает всё
|
|
31
|
+
- **Package manager:** uv (NOT pip, NOT poetry)
|
|
32
|
+
- **Линтеры:** ruff + mypy strict
|
|
33
|
+
- **Deploy:** GitHub Actions → ArgoCD → k8s
|
|
34
|
+
- **Внутренние ADR:** `docs/adr/`
|
|
35
|
+
- **Слаг для decisions/:** `<project-slug>`
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
`vibe-decisions` читает project.md перед каждым нетривиальным выбором.
|
|
39
|
+
|
|
40
|
+
## Commit policy
|
|
41
|
+
|
|
42
|
+
- `project.md` — committed
|
|
43
|
+
- `decisions/*.md` — committed (это shared с командой)
|
|
44
|
+
- Никаких secrets (production endpoints, API keys, internal hostnames) — для них используй `~/.claudenv/memories/` с глобальным scope (выйдет за пределы repo)
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Добавить коннектор к источнику данных (SQL/REST/вики/Redash) с изоляцией по workspace
|
|
3
|
+
allowed-tools: Bash, Read, Write, Edit, Skill
|
|
4
|
+
argument-hint: [<описание источника>]
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# /add-source - подключить источник данных
|
|
8
|
+
|
|
9
|
+
Запускает skill `source-connector`: опрашивает тебя, генерирует рабочий коннектор,
|
|
10
|
+
кладёт секреты в `.env.local`, кэширует знание о коннекторе (параметры + provenance)
|
|
11
|
+
в память активного workspace.
|
|
12
|
+
|
|
13
|
+
## Routing
|
|
14
|
+
|
|
15
|
+
1. Активируй skill `source-connector`.
|
|
16
|
+
2. Если в `$ARGUMENTS` есть описание источника (тип/хост/система) - передай его как
|
|
17
|
+
стартовый контекст, остальное доспроси.
|
|
18
|
+
3. Если аргументов нет - начни с вопроса, к какому источнику подключаемся.
|
|
19
|
+
|
|
20
|
+
## Перед стартом
|
|
21
|
+
|
|
22
|
+
- Определи активный workspace (env `CLAUDENV_WORKSPACE` -> файл
|
|
23
|
+
`~/.claudenv/active-workspace`). Нет активного - предложи выбрать/создать,
|
|
24
|
+
не пиши в глобальную память.
|
|
25
|
+
- Напомни инвариант: секреты идут только в `.env.local` (gitignored), в память -
|
|
26
|
+
только имена переменных.
|
|
27
|
+
|
|
28
|
+
## Когда НЕ создавать коннектор
|
|
29
|
+
|
|
30
|
+
Если для источника есть готовый MCP-сервер - предложи путь через `/setup-mcp`
|
|
31
|
+
(`.mcp.json`), коннектор нужен только для источников без MCP.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Показать или добавить ссылки в личный канон (~/.claudenv/memories/canon/index.yaml)
|
|
3
|
+
allowed-tools: Bash(claudenv:*), Read
|
|
4
|
+
argument-hint: [list [<topic>]|search <query>|add <topic> <url> --why "<reason>"]
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# /canon — Личный канон ссылок
|
|
8
|
+
|
|
9
|
+
Wrapper над `claudenv canon` CLI.
|
|
10
|
+
|
|
11
|
+
## Routing
|
|
12
|
+
|
|
13
|
+
Разбери `$ARGUMENTS`:
|
|
14
|
+
|
|
15
|
+
- пусто или `list` → `claudenv canon list`
|
|
16
|
+
- `list <topic>` → `claudenv canon list <topic>`
|
|
17
|
+
- `search <query>` → `claudenv canon search "<query>"`
|
|
18
|
+
- `add <topic> <url> --why "<reason>"` → `claudenv canon add <topic> <url> --why "<reason>"`
|
|
19
|
+
- иначе если выглядит как topic slug → `claudenv canon list <slug>`
|
|
20
|
+
- иначе → `claudenv canon search "$ARGUMENTS"`
|
|
21
|
+
|
|
22
|
+
## Output
|
|
23
|
+
|
|
24
|
+
Печатай stdout как есть. На non-zero exit code покажи stderr и подскажи usage.
|
|
25
|
+
|
|
26
|
+
## Когда канон пуст
|
|
27
|
+
|
|
28
|
+
Если `claudenv canon list` ничего не вернул:
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
Канон пока пуст. Добавь первые записи когда будешь делать /deeper —
|
|
32
|
+
после deep dive предложу `claudenv canon add <topic> <url> --why "..."`.
|
|
33
|
+
```
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Показать или найти принятые vibe-decisions (последние N, по slug, или по поисковому запросу)
|
|
3
|
+
allowed-tools: Bash(claudenv:*), Read
|
|
4
|
+
argument-hint: [list|show <id>|search <query>] [--limit N] [--scope global|project|all]
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# /decisions — Список и поиск принятых решений
|
|
8
|
+
|
|
9
|
+
Wrapper над `claudenv decisions` CLI. Запускается из Bash, результат показывается пользователю как есть.
|
|
10
|
+
|
|
11
|
+
## Routing
|
|
12
|
+
|
|
13
|
+
Разбери `$ARGUMENTS`:
|
|
14
|
+
|
|
15
|
+
- пусто или `list` → `claudenv decisions list --limit 10`
|
|
16
|
+
- `list --limit N` → `claudenv decisions list --limit N`
|
|
17
|
+
- `list --scope <s>` → `claudenv decisions list --scope <s>`
|
|
18
|
+
- `show <id>` → `claudenv decisions show <id>`
|
|
19
|
+
- `search <query>` → `claudenv decisions search "<query>"`
|
|
20
|
+
- иначе если выглядит как id или slug → `claudenv decisions show "$ARGUMENTS"`
|
|
21
|
+
- иначе → `claudenv decisions search "$ARGUMENTS"`
|
|
22
|
+
|
|
23
|
+
## Output
|
|
24
|
+
|
|
25
|
+
Печатай stdout команды без редактирования. Если команда вернула non-zero exit code — покажи stderr пользователю и предложи `claudenv decisions list` для проверки доступных id.
|
|
26
|
+
|
|
27
|
+
## Если `claudenv` не на PATH
|
|
28
|
+
|
|
29
|
+
Подскажи: `npm i -g claudenv` или `npx claudenv decisions list`.
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Развернуть последнее принятое решение в подробное объяснение (deep dive — концепт, варианты, канон)
|
|
3
|
+
allowed-tools: Read, Glob, Grep, WebSearch, WebFetch, Bash(claudenv:*), Bash(ls:*), Write, Edit
|
|
4
|
+
argument-hint: [topic-or-decision-id]
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# /deeper — Подробное объяснение последнего решения
|
|
8
|
+
|
|
9
|
+
Развернуть последнее (или указанное в `$ARGUMENTS`) принятое vibe-decision в полный формат.
|
|
10
|
+
|
|
11
|
+
## Step 1 — Find the target decision
|
|
12
|
+
|
|
13
|
+
If `$ARGUMENTS` is empty:
|
|
14
|
+
|
|
15
|
+
1. Run `claudenv decisions list --limit 1` (Bash) — get the most recent decision id
|
|
16
|
+
2. Run `claudenv decisions show <id>` to load its frontmatter
|
|
17
|
+
|
|
18
|
+
If `$ARGUMENTS` is a topic slug or id:
|
|
19
|
+
|
|
20
|
+
1. Run `claudenv decisions show $ARGUMENTS` to load it
|
|
21
|
+
2. If not found — fall back to `claudenv decisions search "$ARGUMENTS"` and pick the top match
|
|
22
|
+
|
|
23
|
+
If nothing matches:
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
Не нашёл недавнего решения. Сначала сделай нетривиальный выбор — vibe-decisions залогирует его. Потом вызови /deeper.
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Stop here.
|
|
30
|
+
|
|
31
|
+
## Step 2 — Expand into the deep dive format
|
|
32
|
+
|
|
33
|
+
Follow `~/.claude/skills/vibe-decisions/deep-dive-template.md` exactly:
|
|
34
|
+
|
|
35
|
+
1. **Концептуально** (3-5 lines) — what it is, what problem it solves, name 1-2 alternatives
|
|
36
|
+
2. **Как работает** (5-10 lines, optional code sketch) — simplified model + key invariants
|
|
37
|
+
3. **2-3 варианта реализации** — for each: code sketch + when-suits / when-doesn't
|
|
38
|
+
4. **Канон** — 2-4 references, **first** from `~/.claudenv/memories/canon/index.yaml` by topic; if no match, WebSearch and propose `claudenv canon add` afterward
|
|
39
|
+
|
|
40
|
+
## Step 3 — Update the decision file
|
|
41
|
+
|
|
42
|
+
Edit the original decision file to set:
|
|
43
|
+
|
|
44
|
+
```yaml
|
|
45
|
+
deep_dive_done: yes
|
|
46
|
+
sources_consulted: [<urls and canon ids you cited>]
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Preserve the `__VIBE_DECISION__` marker on the last line.
|
|
50
|
+
|
|
51
|
+
## Style
|
|
52
|
+
|
|
53
|
+
- User's language
|
|
54
|
+
- Concrete to the actual task, not generic textbook
|
|
55
|
+
- If a sketch is shorter than prose — use the sketch
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Подавить vibe-decisions overview на следующий ответ — просто пиши код без brief overview
|
|
3
|
+
allowed-tools:
|
|
4
|
+
argument-hint:
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# /just-code — Подавить vibe overview одноразово
|
|
8
|
+
|
|
9
|
+
Эффект на ОДИН следующий ответ:
|
|
10
|
+
|
|
11
|
+
- vibe-decisions skill НЕ выдаёт brief overview / auto-log block
|
|
12
|
+
- Просто пишет код для текущей задачи
|
|
13
|
+
- Decision файл НЕ создаётся для этого ответа
|
|
14
|
+
- На ответ после следующего поведение возвращается к дефолту
|
|
15
|
+
|
|
16
|
+
## В AUTO-LOG mode (loop)
|
|
17
|
+
|
|
18
|
+
В loop эта команда фактически no-op для большинства случаев — auto-log не паузит, а просто логирует. `/just-code` пропускает логирование на один шаг. Это полезно когда выбор тривиален но vibe-decisions всё равно зажёгся.
|
|
19
|
+
|
|
20
|
+
## В INTERACTIVE mode
|
|
21
|
+
|
|
22
|
+
Пропускает паузу на следующем шаге. Используй когда явно знаешь что хочешь, без нужды в overview.
|
|
23
|
+
|
|
24
|
+
## После использования
|
|
25
|
+
|
|
26
|
+
Не нужно ничего откатывать — действие истекает само через один turn. Если хочешь надолго отключить vibe-decisions — отредактируй его SKILL.md trigger criteria.
|