create-lore 0.1.1 → 0.1.2
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 +38 -0
- package/bin/create-lore.js +53 -82
- package/package.json +1 -1
- package/test/create-lore.test.js +24 -24
package/README.md
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# create-lore
|
|
2
|
+
|
|
3
|
+
Create a new [Lore](https://github.com/lorehq/lore) knowledge-persistent AI coding framework repo.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npx create-lore myproject
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
This creates `lore-myproject/` with the full Lore framework — hooks, skills, scripts, and operating instructions that teach Claude Code to learn and remember across sessions.
|
|
12
|
+
|
|
13
|
+
## What you get
|
|
14
|
+
|
|
15
|
+
- **AGENTS.md / CLAUDE.md** — Operating instructions Claude Code reads automatically
|
|
16
|
+
- **Hooks** — Session init, memory guard, post-action capture reminders
|
|
17
|
+
- **Skills** — `create-skill` and `create-agent` for building your knowledge base
|
|
18
|
+
- **Scripts** — Registry generation, agent generation, consistency validation
|
|
19
|
+
|
|
20
|
+
## Options
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npx create-lore myproject # creates ./lore-myproject/
|
|
24
|
+
npx create-lore ./custom-path # creates at specific path
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## After setup
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
cd lore-myproject
|
|
31
|
+
git add -A && git commit -m "Init Lore"
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Then open Claude Code in the project. The hooks will fire automatically and the self-learning loop begins.
|
|
35
|
+
|
|
36
|
+
## License
|
|
37
|
+
|
|
38
|
+
Apache-2.0
|
package/bin/create-lore.js
CHANGED
|
@@ -1,98 +1,69 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
+
// create-lore: Bootstrap a new Lore knowledge-persistent agent repo.
|
|
4
|
+
//
|
|
5
|
+
// Usage: npx create-lore <name|path>
|
|
6
|
+
//
|
|
7
|
+
// How it works:
|
|
8
|
+
// 1. Clones the Lore template from GitHub (or copies from LORE_TEMPLATE env var)
|
|
9
|
+
// 2. Strips the template's .git history
|
|
10
|
+
// 3. Writes a .lore-config with the project name and creation date
|
|
11
|
+
// 4. Runs git init for a clean start
|
|
12
|
+
//
|
|
13
|
+
// The LORE_TEMPLATE env var is used by tests to point at a local template
|
|
14
|
+
// directory instead of cloning from GitHub.
|
|
15
|
+
|
|
3
16
|
const { execSync } = require('child_process');
|
|
4
17
|
const fs = require('fs');
|
|
5
18
|
const path = require('path');
|
|
6
|
-
const readline = require('readline');
|
|
7
19
|
|
|
8
|
-
const TOOLS = ['Claude Code', 'Cursor', 'OpenCode'];
|
|
9
20
|
const REPO_URL = 'https://github.com/lorehq/lore.git';
|
|
10
21
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
async function selectTools() {
|
|
17
|
-
console.log('\nWhich AI coding tools will you use?\n');
|
|
18
|
-
TOOLS.forEach((t, i) => console.log(` ${i + 1}. ${t}`));
|
|
19
|
-
console.log();
|
|
20
|
-
const answer = await prompt('Select tools (comma-separated numbers, e.g. 1,3): ');
|
|
21
|
-
const indices = answer.split(',').map(s => parseInt(s.trim(), 10) - 1);
|
|
22
|
-
const selected = indices.filter(i => i >= 0 && i < TOOLS.length).map(i => TOOLS[i]);
|
|
23
|
-
return selected.length > 0 ? selected : ['Claude Code'];
|
|
22
|
+
// -- Parse arguments --
|
|
23
|
+
const name = process.argv[2];
|
|
24
|
+
if (!name) {
|
|
25
|
+
console.error('Usage: create-lore <name>');
|
|
26
|
+
process.exit(1);
|
|
24
27
|
}
|
|
25
28
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
if (!name) {
|
|
32
|
-
console.error('Usage: create-lore <name> [--template <path>]');
|
|
33
|
-
process.exit(1);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
const isPath = name.includes('/') || name.includes(path.sep);
|
|
37
|
-
const targetDir = path.resolve(isPath ? name : `./lore-${name}`);
|
|
38
|
-
|
|
39
|
-
if (fs.existsSync(targetDir)) {
|
|
40
|
-
console.error(`Error: ${targetDir} already exists`);
|
|
41
|
-
process.exit(1);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// Clone or copy template
|
|
45
|
-
const tmpDir = path.join(require('os').tmpdir(), `create-lore-${Date.now()}`);
|
|
46
|
-
try {
|
|
47
|
-
if (templateDir) {
|
|
48
|
-
console.log(`Copying template from ${templateDir}...`);
|
|
49
|
-
copyDir(templateDir, tmpDir);
|
|
50
|
-
} else {
|
|
51
|
-
console.log(`Cloning ${REPO_URL}...`);
|
|
52
|
-
execSync(`git clone --depth 1 ${REPO_URL} "${tmpDir}"`, { stdio: 'pipe' });
|
|
53
|
-
}
|
|
29
|
+
// If the name contains a path separator, treat it as a custom path.
|
|
30
|
+
// Otherwise, create a {name}/ directory in the current folder.
|
|
31
|
+
const isPath = name.includes('/') || name.includes(path.sep);
|
|
32
|
+
const targetDir = path.resolve(isPath ? name : `./${name}`);
|
|
54
33
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
// Copy to target
|
|
60
|
-
copyDir(tmpDir, targetDir);
|
|
61
|
-
} finally {
|
|
62
|
-
if (fs.existsSync(tmpDir)) fs.rmSync(tmpDir, { recursive: true });
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
// Select tools (skip in non-interactive mode)
|
|
66
|
-
let tools = ['Claude Code'];
|
|
67
|
-
if (process.stdin.isTTY) {
|
|
68
|
-
tools = await selectTools();
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// Write .lore-config
|
|
72
|
-
const config = { name, tools, created: new Date().toISOString().split('T')[0] };
|
|
73
|
-
fs.writeFileSync(path.join(targetDir, '.lore-config'), JSON.stringify(config, null, 2) + '\n');
|
|
74
|
-
|
|
75
|
-
// Init git repo
|
|
76
|
-
execSync('git init', { cwd: targetDir, stdio: 'pipe' });
|
|
77
|
-
|
|
78
|
-
console.log(`\nCreated lore-${name} at ${targetDir}`);
|
|
79
|
-
console.log(`Tools: ${tools.join(', ')}`);
|
|
80
|
-
console.log(`\nNext steps:`);
|
|
81
|
-
console.log(` cd ${targetDir}`);
|
|
82
|
-
console.log(` git add -A && git commit -m "Init Lore"`);
|
|
34
|
+
if (fs.existsSync(targetDir)) {
|
|
35
|
+
console.error(`Error: ${targetDir} already exists`);
|
|
36
|
+
process.exit(1);
|
|
83
37
|
}
|
|
84
38
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
}
|
|
39
|
+
// -- Copy template to target --
|
|
40
|
+
// Clone from GitHub or copy from a local template directory.
|
|
41
|
+
// Uses a temp dir so we can strip .git before copying to the final location.
|
|
42
|
+
const tmpDir = path.join(require('os').tmpdir(), `create-lore-${Date.now()}`);
|
|
43
|
+
try {
|
|
44
|
+
const templateDir = process.env.LORE_TEMPLATE;
|
|
45
|
+
if (templateDir) {
|
|
46
|
+
fs.cpSync(templateDir, tmpDir, { recursive: true });
|
|
47
|
+
} else {
|
|
48
|
+
execSync(`git clone --depth 1 ${REPO_URL} "${tmpDir}"`, { stdio: 'pipe' });
|
|
95
49
|
}
|
|
50
|
+
fs.rmSync(path.join(tmpDir, '.git'), { recursive: true, force: true });
|
|
51
|
+
fs.cpSync(tmpDir, targetDir, { recursive: true });
|
|
52
|
+
} finally {
|
|
53
|
+
// Always clean up the temp dir
|
|
54
|
+
if (fs.existsSync(tmpDir)) fs.rmSync(tmpDir, { recursive: true });
|
|
96
55
|
}
|
|
97
56
|
|
|
98
|
-
|
|
57
|
+
// -- Write .lore-config --
|
|
58
|
+
const projectName = isPath ? path.basename(targetDir) : name;
|
|
59
|
+
const config = { name: projectName, created: new Date().toISOString().split('T')[0] };
|
|
60
|
+
fs.writeFileSync(path.join(targetDir, '.lore-config'), JSON.stringify(config, null, 2) + '\n');
|
|
61
|
+
|
|
62
|
+
// -- Initialize git --
|
|
63
|
+
execSync('git init', { cwd: targetDir, stdio: 'pipe' });
|
|
64
|
+
|
|
65
|
+
// -- Done --
|
|
66
|
+
console.log(`\nCreated ${targetDir}`);
|
|
67
|
+
console.log(`\nNext steps:`);
|
|
68
|
+
console.log(` cd ${targetDir}`);
|
|
69
|
+
console.log(` git add -A && git commit -m "Init Lore"`);
|
package/package.json
CHANGED
package/test/create-lore.test.js
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
// Tests for create-lore installer.
|
|
2
|
+
// Uses the local lore repo (sibling directory) as the template via LORE_TEMPLATE env var.
|
|
3
|
+
// Runs the installer in a subprocess and validates the output directory structure.
|
|
4
|
+
|
|
1
5
|
const { describe, it, before, after } = require('node:test');
|
|
2
6
|
const assert = require('node:assert/strict');
|
|
3
7
|
const { execSync } = require('child_process');
|
|
@@ -5,9 +9,10 @@ const fs = require('fs');
|
|
|
5
9
|
const path = require('path');
|
|
6
10
|
|
|
7
11
|
const BIN = path.resolve(__dirname, '../bin/create-lore.js');
|
|
8
|
-
const TEMPLATE = path.resolve(__dirname, '../../lore');
|
|
12
|
+
const TEMPLATE = path.resolve(__dirname, '../../lore'); // Sibling lore repo
|
|
9
13
|
const OUTPUT = path.resolve(__dirname, '../test-output');
|
|
10
14
|
|
|
15
|
+
// Run the installer with LORE_TEMPLATE pointing at the local template
|
|
11
16
|
function run(args = '') {
|
|
12
17
|
return execSync(`node ${BIN} ${args}`, {
|
|
13
18
|
env: { ...process.env, LORE_TEMPLATE: TEMPLATE },
|
|
@@ -29,59 +34,54 @@ describe('create-lore', () => {
|
|
|
29
34
|
});
|
|
30
35
|
|
|
31
36
|
it('creates project directory with expected structure', () => {
|
|
32
|
-
|
|
37
|
+
run(OUTPUT);
|
|
33
38
|
|
|
34
39
|
assert.ok(fs.existsSync(OUTPUT), 'output directory exists');
|
|
35
40
|
|
|
36
|
-
// .lore-config
|
|
41
|
+
// .lore-config has required fields
|
|
37
42
|
const config = JSON.parse(fs.readFileSync(path.join(OUTPUT, '.lore-config'), 'utf8'));
|
|
38
|
-
assert.
|
|
39
|
-
assert.ok(Array.isArray(config.tools), 'tools is an array');
|
|
43
|
+
assert.ok(config.name, 'name present');
|
|
40
44
|
assert.ok(config.created, 'created date present');
|
|
41
45
|
|
|
42
|
-
// git repo initialized
|
|
46
|
+
// Fresh git repo initialized (not cloned template history)
|
|
43
47
|
assert.ok(fs.existsSync(path.join(OUTPUT, '.git')), 'git initialized');
|
|
44
|
-
|
|
45
|
-
// no template .git leaked
|
|
46
48
|
const entries = fs.readdirSync(path.join(OUTPUT, '.git'));
|
|
47
49
|
assert.ok(entries.includes('HEAD'), '.git looks like a fresh init');
|
|
48
50
|
});
|
|
49
51
|
|
|
50
52
|
it('fails if target directory already exists', () => {
|
|
51
53
|
fs.mkdirSync(OUTPUT, { recursive: true });
|
|
52
|
-
assert.throws(() => run(
|
|
54
|
+
assert.throws(() => run(OUTPUT), /already exists/);
|
|
53
55
|
fs.rmSync(OUTPUT, { recursive: true });
|
|
54
56
|
});
|
|
55
57
|
|
|
56
|
-
//
|
|
58
|
+
// -- Template content tests --
|
|
59
|
+
// These skip gracefully if the template hasn't reached that phase yet.
|
|
57
60
|
|
|
58
|
-
it('has
|
|
59
|
-
|
|
60
|
-
if (!fs.existsSync(agentsMd)) return; // skip until Phase 2
|
|
61
|
+
it('has CLAUDE.md when template provides it', () => {
|
|
62
|
+
if (!fs.existsSync(path.join(TEMPLATE, 'CLAUDE.md'))) return;
|
|
61
63
|
|
|
62
|
-
run(
|
|
63
|
-
assert.ok(fs.existsSync(path.join(OUTPUT, '
|
|
64
|
-
const content = fs.readFileSync(path.join(OUTPUT, '
|
|
65
|
-
assert.ok(content.length > 0, '
|
|
64
|
+
run(OUTPUT);
|
|
65
|
+
assert.ok(fs.existsSync(path.join(OUTPUT, 'CLAUDE.md')), 'CLAUDE.md copied');
|
|
66
|
+
const content = fs.readFileSync(path.join(OUTPUT, 'CLAUDE.md'), 'utf8');
|
|
67
|
+
assert.ok(content.length > 0, 'CLAUDE.md is non-empty');
|
|
66
68
|
cleanup();
|
|
67
69
|
});
|
|
68
70
|
|
|
69
71
|
it('has hooks wired in .claude/settings.json when template provides it', () => {
|
|
70
|
-
|
|
71
|
-
if (!fs.existsSync(settings)) return; // skip until Phase 3
|
|
72
|
+
if (!fs.existsSync(path.join(TEMPLATE, '.claude/settings.json'))) return;
|
|
72
73
|
|
|
73
|
-
run(
|
|
74
|
+
run(OUTPUT);
|
|
74
75
|
const parsed = JSON.parse(fs.readFileSync(path.join(OUTPUT, '.claude/settings.json'), 'utf8'));
|
|
75
76
|
assert.ok(parsed.hooks, 'hooks key exists in settings');
|
|
76
77
|
cleanup();
|
|
77
78
|
});
|
|
78
79
|
|
|
79
80
|
it('passes validate-consistency.sh when template provides it', () => {
|
|
80
|
-
|
|
81
|
-
if (!fs.existsSync(script)) return; // skip until Phase 5
|
|
81
|
+
if (!fs.existsSync(path.join(TEMPLATE, 'scripts/validate-consistency.sh'))) return;
|
|
82
82
|
|
|
83
|
-
run(
|
|
84
|
-
const result = execSync(
|
|
83
|
+
run(OUTPUT);
|
|
84
|
+
const result = execSync('bash scripts/validate-consistency.sh', {
|
|
85
85
|
cwd: OUTPUT,
|
|
86
86
|
encoding: 'utf8',
|
|
87
87
|
stdio: 'pipe',
|