create-lore 0.8.1 → 0.10.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/.gitattributes ADDED
@@ -0,0 +1,2 @@
1
+ # Force LF line endings everywhere — prevents CRLF issues on Windows.
2
+ * text=auto eol=lf
@@ -0,0 +1 @@
1
+ * @drewswiredin
@@ -0,0 +1,24 @@
1
+ ---
2
+ name: Bug report
3
+ about: Something isn't working as expected
4
+ labels: bug
5
+ ---
6
+
7
+ **What happened?**
8
+
9
+ A clear description of the bug.
10
+
11
+ **Steps to reproduce**
12
+
13
+ 1. ...
14
+ 2. ...
15
+
16
+ **Expected behavior**
17
+
18
+ What should have happened instead.
19
+
20
+ **Environment**
21
+
22
+ - OS:
23
+ - Node version:
24
+ - create-lore version:
@@ -0,0 +1,17 @@
1
+ ---
2
+ name: Feature request
3
+ about: Suggest an improvement or new capability
4
+ labels: enhancement
5
+ ---
6
+
7
+ **What problem does this solve?**
8
+
9
+ Describe the use case or pain point.
10
+
11
+ **Proposed solution**
12
+
13
+ How you'd like it to work.
14
+
15
+ **Alternatives considered**
16
+
17
+ Any workarounds or other approaches you've tried.
@@ -0,0 +1,76 @@
1
+ name: Release
2
+
3
+ on:
4
+ push:
5
+ tags: ['v*']
6
+
7
+ permissions:
8
+ contents: write
9
+ id-token: write
10
+
11
+ jobs:
12
+ test:
13
+ runs-on: ${{ matrix.os }}
14
+ strategy:
15
+ fail-fast: false
16
+ matrix:
17
+ os: [ubuntu-latest, macos-latest, windows-latest]
18
+ node-version: [18, 20]
19
+ steps:
20
+ - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 # checkout create-lore (this repo)
21
+ - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 # checkout lore template at matching tag
22
+ with:
23
+ repository: lorehq/lore
24
+ ref: ${{ github.ref_name }}
25
+ path: lore
26
+ - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
27
+ with:
28
+ node-version: ${{ matrix.node-version }}
29
+ - run: npm test
30
+ env:
31
+ LORE_TEMPLATE: ${{ github.workspace }}/lore
32
+
33
+ verify-tag:
34
+ runs-on: ubuntu-latest
35
+ steps:
36
+ - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
37
+ - name: Verify tag matches package.json version
38
+ run: |
39
+ tag="${GITHUB_REF#refs/tags/v}"
40
+ pkg="$(node -p "require('./package.json').version")"
41
+ if [[ "$tag" != "$pkg" ]]; then
42
+ echo "::error::Tag v$tag does not match package.json version $pkg"
43
+ exit 1
44
+ fi
45
+ echo "Tag v$tag matches package.json"
46
+ - name: Verify matching lore tag exists
47
+ run: |
48
+ tag="${GITHUB_REF#refs/tags/}"
49
+ if ! git ls-remote --tags https://github.com/lorehq/lore.git "refs/tags/$tag" | grep -q "$tag"; then
50
+ echo "::error::lorehq/lore does not have tag $tag — tag lore first"
51
+ exit 1
52
+ fi
53
+ echo "lorehq/lore has matching tag $tag"
54
+
55
+ publish:
56
+ needs: [test, verify-tag]
57
+ runs-on: ubuntu-latest
58
+ steps:
59
+ - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
60
+ - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
61
+ with:
62
+ node-version: 20
63
+ registry-url: https://registry.npmjs.org
64
+ - name: Publish to npm
65
+ run: npm publish --provenance
66
+ env:
67
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
68
+ - name: Create GitHub Release
69
+ env:
70
+ GH_TOKEN: ${{ github.token }}
71
+ run: |
72
+ tag="${GITHUB_REF#refs/tags/}"
73
+ gh release delete "$tag" --yes 2>/dev/null || true
74
+ gh release create "$tag" \
75
+ --title "$tag" \
76
+ --generate-notes
@@ -0,0 +1,55 @@
1
+ name: Test
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ jobs:
10
+ test:
11
+ runs-on: ${{ matrix.os }}
12
+ strategy:
13
+ fail-fast: false
14
+ matrix:
15
+ os: [ubuntu-latest, macos-latest, windows-latest]
16
+ node-version: [18, 20]
17
+ steps:
18
+ - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 # checkout create-lore (this repo)
19
+ - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 # checkout lore template (used as LORE_TEMPLATE)
20
+ with:
21
+ repository: lorehq/lore
22
+ path: lore
23
+ - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
24
+ with:
25
+ node-version: ${{ matrix.node-version }}
26
+ - run: npm ci
27
+ - name: Lint & format
28
+ run: npx eslint . && npx prettier --check .
29
+ - run: npm test
30
+ env:
31
+ LORE_TEMPLATE: ${{ github.workspace }}/lore
32
+
33
+ e2e:
34
+ runs-on: ${{ matrix.os }}
35
+ strategy:
36
+ fail-fast: false
37
+ matrix:
38
+ os: [ubuntu-latest, macos-latest, windows-latest]
39
+ steps:
40
+ - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
41
+ - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
42
+ with:
43
+ repository: lorehq/lore
44
+ path: lore
45
+ - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
46
+ with:
47
+ node-version: 20
48
+ - name: Scaffold a project and validate it
49
+ shell: bash
50
+ env:
51
+ LORE_TEMPLATE: ${{ github.workspace }}/lore
52
+ run: |
53
+ node bin/create-lore.js test-project
54
+ cd test-project
55
+ bash scripts/validate-consistency.sh
@@ -0,0 +1 @@
1
+ lore/
package/.prettierrc ADDED
@@ -0,0 +1,7 @@
1
+ {
2
+ "singleQuote": true,
3
+ "semi": true,
4
+ "trailingComma": "all",
5
+ "printWidth": 120,
6
+ "tabWidth": 2
7
+ }
@@ -0,0 +1,51 @@
1
+ # Contributing to create-lore
2
+
3
+ Thanks for your interest in contributing. `create-lore` is the CLI scaffolder for [Lore](https://github.com/lorehq/lore).
4
+
5
+ ## Dev Setup
6
+
7
+ ```bash
8
+ git clone https://github.com/lorehq/create-lore.git
9
+ cd create-lore
10
+ ```
11
+
12
+ Requires **Node.js 18+**. Zero runtime dependencies.
13
+
14
+ ## Running Tests
15
+
16
+ ```bash
17
+ npm test
18
+ ```
19
+
20
+ Tests use `LORE_TEMPLATE` env var to point at a local lore repo instead of cloning from GitHub:
21
+
22
+ ```bash
23
+ LORE_TEMPLATE=../lore npm test
24
+ ```
25
+
26
+ ## Pull Requests
27
+
28
+ 1. Fork the repo and create a branch from `main`
29
+ 2. Make your changes
30
+ 3. Run `npm test`
31
+ 4. Open a pull request
32
+
33
+ ## What We're Looking For
34
+
35
+ - Bug fixes with clear reproduction steps
36
+ - Better error messaging for common failure modes
37
+ - Test coverage improvements
38
+
39
+ ## Guidelines
40
+
41
+ - Keep changes focused — one concern per PR
42
+ - Match existing code style
43
+ - For framework changes (hooks, skills, lib), contribute to [lorehq/lore](https://github.com/lorehq/lore) instead
44
+
45
+ ## Reporting Issues
46
+
47
+ Use [GitHub Issues](../../issues). For security vulnerabilities, see [SECURITY.md](SECURITY.md).
48
+
49
+ ## License
50
+
51
+ By contributing, you agree that your contributions will be licensed under the Apache-2.0 license.
package/README.md CHANGED
@@ -1,45 +1,54 @@
1
1
  # create-lore
2
2
 
3
- Create a new [Lore](https://github.com/lorehq/lore) knowledge-persistent AI coding agent repo.
3
+ Bootstrap a new [Lore](https://github.com/lorehq/lore) project — persistent memory for AI coding agents.
4
4
 
5
- ## Usage
5
+ ## The Problem
6
6
 
7
- ```bash
8
- npx create-lore myproject
9
- ```
7
+ AI coding agents (Claude Code, Cursor, OpenCode) forget everything between sessions. Every session you re-explain project structure, re-discover API quirks, and repeat lessons learned yesterday. Lore fixes that.
8
+
9
+ ## What Lore Does
10
10
 
11
- This creates `lore-myproject/` with the full Lore framework hooks, skills, scripts, and operating instructions that teach your coding agent to learn and remember across sessions.
11
+ Lore wraps your coding agent in a git-versioned knowledge base. Hooks fire automatically to reinforce knowledge capture as you work. Gotchas become skills, and every future session starts with what previous sessions learned. Complex work delegates to focused workers loaded with curated skills.
12
12
 
13
- ## What you get
13
+ - **Skills** API quirks, auth gotchas, encoding tricks. Captured once, loaded forever.
14
+ - **Knowledge docs** — Environment details, runbooks, architecture decisions. Accumulated across sessions.
15
+ - **Work tracking** — Roadmaps, plans, and brainstorms that persist and appear in every session banner.
16
+ - **Hooks** — Session init, capture reminders, memory protection. All automatic.
17
+ - **Docs UI & Semantic Search** — Run `/lore-docker` to start a local Docker sidecar. Gives agents semantic search over the full knowledge base and opens a live MkDocs site for browsing it visually. Falls back to Grep/Glob without Docker.
18
+
19
+ ## Quick Start
20
+
21
+ ```bash
22
+ npx create-lore my-project
23
+ cd my-project
24
+ git add -A && git commit -m "Init Lore"
25
+ ```
14
26
 
15
- - **Instructions** Operating instructions loaded automatically by each platform
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
27
+ Then open the project in your agent. Hooks fire automatically.
19
28
 
20
- ## Supported platforms
29
+ ## Supported Platforms
21
30
 
22
- - **Claude Code** — `hooks/` + `CLAUDE.md`
23
- - **Cursor** `.cursor/hooks/` + `.cursorrules`
24
- - **OpenCode** `.opencode/plugins/` + `opencode.json`
31
+ | Platform | Integration |
32
+ | ----------- | --------------------------------------------------------------------- |
33
+ | Claude Code | `.lore/hooks/` + `CLAUDE.md` |
34
+ | Cursor | `.lore/hooks/` + `.cursor/hooks/` + `.cursor/mcp/` + `.cursor/rules/` |
35
+ | OpenCode | `.opencode/plugins/` + `opencode.json` |
25
36
 
26
37
  All platforms share the same knowledge base. No configuration needed.
27
38
 
28
39
  ## Options
29
40
 
30
41
  ```bash
31
- npx create-lore myproject # creates ./lore-myproject/
32
- npx create-lore ./custom-path # creates at specific path
42
+ npx create-lore my-project # creates ./my-project/
43
+ npx create-lore ./custom-path # creates at specific path
44
+ npx create-lore --help # show usage
45
+ npx create-lore --version # show version
33
46
  ```
34
47
 
35
- ## After setup
36
-
37
- ```bash
38
- cd lore-myproject
39
- git add -A && git commit -m "Init Lore"
40
- ```
48
+ ## Requirements
41
49
 
42
- Then open the project in your agent. Hooks fire automatically and the self-learning loop begins.
50
+ - Node.js 18+
51
+ - git
43
52
 
44
53
  ## Docs
45
54
 
package/SECURITY.md ADDED
@@ -0,0 +1,24 @@
1
+ # Security Policy
2
+
3
+ ## Reporting a Vulnerability
4
+
5
+ If you discover a security vulnerability, please report it responsibly:
6
+
7
+ 1. **Do not** open a public GitHub issue
8
+ 2. Email the maintainers or use [GitHub's private vulnerability reporting](../../security/advisories/new)
9
+ 3. Include steps to reproduce and potential impact
10
+
11
+ We will acknowledge receipt within 48 hours and provide a timeline for a fix.
12
+
13
+ ## Scope
14
+
15
+ `create-lore` is a CLI scaffolder that clones a template repo and initializes a new project. Security concerns are primarily:
16
+
17
+ - Shell command execution during project scaffolding (`git clone`, `git init`)
18
+ - Template integrity (clones from a pinned version tag on GitHub)
19
+
20
+ ## Supported Versions
21
+
22
+ | Version | Supported |
23
+ | ------- | --------- |
24
+ | 0.10.x | Yes |
@@ -7,7 +7,7 @@
7
7
  // How it works:
8
8
  // 1. Clones the Lore template from GitHub (or copies from LORE_TEMPLATE env var)
9
9
  // 2. Strips the template's .git history
10
- // 3. Writes a .lore-config with the project name and creation date
10
+ // 3. Writes .lore/config.json with the project name and creation date
11
11
  // 4. Runs git init for a clean start
12
12
  //
13
13
  // The LORE_TEMPLATE env var is used by tests to point at a local template
@@ -18,9 +18,25 @@ const fs = require('fs');
18
18
  const path = require('path');
19
19
 
20
20
  const REPO_URL = 'https://github.com/lorehq/lore.git';
21
+ const pkg = require('../package.json');
21
22
 
22
23
  // -- Parse arguments --
23
- const name = process.argv[2];
24
+ const arg = process.argv[2];
25
+ if (arg === '--help' || arg === '-h') {
26
+ console.log(`create-lore v${pkg.version}\n`);
27
+ console.log('Usage: create-lore <name|path>\n');
28
+ console.log('Bootstrap a new Lore knowledge-persistent agent repo.\n');
29
+ console.log('Examples:');
30
+ console.log(' npx create-lore myproject # creates ./myproject/');
31
+ console.log(' npx create-lore ./custom-path # creates at specific path');
32
+ process.exit(0);
33
+ }
34
+ if (arg === '--version' || arg === '-v') {
35
+ console.log(pkg.version);
36
+ process.exit(0);
37
+ }
38
+
39
+ const name = arg;
24
40
  if (!name) {
25
41
  console.error('Usage: create-lore <name>');
26
42
  process.exit(1);
@@ -31,6 +47,25 @@ if (!name) {
31
47
  const isPath = name.includes('/') || name.includes(path.sep);
32
48
  const targetDir = path.resolve(isPath ? name : `./${name}`);
33
49
 
50
+ // Validate every segment of the resolved path's tail (the parts the user controls).
51
+ // For simple names, validate the name directly. For paths, validate the basename.
52
+ // This prevents shell-hostile characters like ; | & $ ` from sneaking through.
53
+ const segmentPattern = /^[a-zA-Z0-9._-]+$/;
54
+ const finalName = path.basename(targetDir);
55
+ if (!segmentPattern.test(finalName)) {
56
+ console.error(`Error: Invalid project name '${finalName}'`);
57
+ console.error('Names may contain letters, numbers, dots, hyphens, and underscores.');
58
+ process.exit(1);
59
+ }
60
+
61
+ // Guard against path traversal — resolved target must be under cwd
62
+ const cwd = process.cwd();
63
+ if (!targetDir.startsWith(cwd + path.sep) && targetDir !== cwd) {
64
+ console.error(`Error: Target directory '${targetDir}' is outside the current working directory.`);
65
+ console.error('Use a relative name or path within the current directory.');
66
+ process.exit(1);
67
+ }
68
+
34
69
  if (fs.existsSync(targetDir)) {
35
70
  console.error(`Error: ${targetDir} already exists`);
36
71
  process.exit(1);
@@ -45,25 +80,85 @@ try {
45
80
  if (templateDir) {
46
81
  fs.cpSync(templateDir, tmpDir, { recursive: true });
47
82
  } else {
48
- execSync(`git clone --depth 1 ${REPO_URL} "${tmpDir}"`, { stdio: 'pipe' });
83
+ try {
84
+ execSync(`git clone --depth 1 --branch v${pkg.version} ${REPO_URL} "${tmpDir}"`, { stdio: 'pipe' });
85
+ } catch (err) {
86
+ const stderr = err.stderr ? err.stderr.toString() : '';
87
+ if (stderr.includes('not found') || stderr.includes('not a valid')) {
88
+ console.error(`Error: Version tag v${pkg.version} not found in ${REPO_URL}`);
89
+ console.error('This usually means the release tag is missing. Try:');
90
+ console.error(' npx create-lore@latest ' + name);
91
+ } else if (stderr.includes('Could not resolve host') || stderr.includes('unable to access')) {
92
+ console.error('Error: Cannot reach github.com');
93
+ console.error('Check your internet connection, DNS, and firewall/proxy settings.');
94
+ } else {
95
+ console.error('Error: Failed to clone template from GitHub');
96
+ console.error(stderr.trim() || err.message);
97
+ }
98
+ process.exit(1);
99
+ }
49
100
  }
50
101
  fs.rmSync(path.join(tmpDir, '.git'), { recursive: true, force: true });
102
+
103
+ // Only keep files needed in instances — remove everything else
104
+ const keep = new Set([
105
+ '.lore',
106
+ '.claude',
107
+ '.cursor',
108
+ '.opencode',
109
+ 'docs',
110
+ 'CLAUDE.md',
111
+ 'opencode.json',
112
+ '.gitattributes',
113
+ '.gitignore',
114
+ ]);
115
+ for (const entry of fs.readdirSync(tmpDir)) {
116
+ if (!keep.has(entry)) {
117
+ fs.rmSync(path.join(tmpDir, entry), { recursive: true, force: true });
118
+ }
119
+ }
120
+
51
121
  fs.cpSync(tmpDir, targetDir, { recursive: true });
52
122
  } finally {
53
123
  // Always clean up the temp dir
54
124
  if (fs.existsSync(tmpDir)) fs.rmSync(tmpDir, { recursive: true });
55
125
  }
56
126
 
57
- // -- Write .lore-config --
127
+ // -- Write .lore/config.json --
58
128
  const projectName = isPath ? path.basename(targetDir) : name;
59
- // Read version from the template's .lore-config so instances track their source version
60
- const templateConfig = JSON.parse(fs.readFileSync(path.join(targetDir, '.lore-config'), 'utf8'));
61
- const config = {
62
- name: projectName,
63
- version: templateConfig.version || '0.0.0',
64
- created: new Date().toISOString().split('T')[0],
65
- };
66
- fs.writeFileSync(path.join(targetDir, '.lore-config'), JSON.stringify(config, null, 2) + '\n');
129
+ // Read version from the copied config.json, then rewrite from template
130
+ let templateConfig;
131
+ try {
132
+ templateConfig = JSON.parse(fs.readFileSync(path.join(targetDir, '.lore', 'config.json'), 'utf8'));
133
+ } catch {
134
+ templateConfig = {};
135
+ }
136
+ const templateVersion = templateConfig.version || '0.0.0';
137
+ const createdDate = new Date().toISOString().split('T')[0];
138
+ const configTemplate = fs.readFileSync(path.join(targetDir, '.lore', 'templates', 'config.json'), 'utf8');
139
+ const configContent = configTemplate
140
+ .replace('{{name}}', projectName)
141
+ .replace('{{version}}', templateVersion)
142
+ .replace('{{created}}', createdDate);
143
+ fs.writeFileSync(path.join(targetDir, '.lore', 'config.json'), configContent);
144
+
145
+ // -- Create gitignored files from templates --
146
+ // Templates are already in targetDir (copied from the template clone via .lore/).
147
+ // These are normally recreated by ensureStickyFiles() each session, but the
148
+ // installer should produce a complete instance from the start.
149
+ const tplDir = path.join(targetDir, '.lore', 'templates');
150
+ const localDir = path.join(targetDir, 'docs', 'knowledge', 'local');
151
+ fs.mkdirSync(localDir, { recursive: true });
152
+
153
+ fs.writeFileSync(path.join(localDir, 'index.md'), fs.readFileSync(path.join(tplDir, 'local-index.md'), 'utf8'));
154
+ fs.writeFileSync(
155
+ path.join(localDir, 'operator-profile.md'),
156
+ fs.readFileSync(path.join(tplDir, 'operator-profile.md'), 'utf8'),
157
+ );
158
+ fs.writeFileSync(
159
+ path.join(targetDir, '.lore', 'memory.local.md'),
160
+ fs.readFileSync(path.join(tplDir, 'memory-local.md'), 'utf8'),
161
+ );
67
162
 
68
163
  // -- Initialize git --
69
164
  execSync('git init -b main', { cwd: targetDir, stdio: 'pipe' });
@@ -0,0 +1,31 @@
1
+ const prettier = require('eslint-config-prettier');
2
+
3
+ module.exports = [
4
+ { ignores: ['lore/'] },
5
+ {
6
+ files: ['**/*.js'],
7
+ languageOptions: {
8
+ ecmaVersion: 2022,
9
+ sourceType: 'commonjs',
10
+ globals: {
11
+ console: 'readonly',
12
+ process: 'readonly',
13
+ require: 'readonly',
14
+ module: 'readonly',
15
+ __dirname: 'readonly',
16
+ __filename: 'readonly',
17
+ exports: 'readonly',
18
+ Buffer: 'readonly',
19
+ },
20
+ },
21
+ rules: {
22
+ 'no-unused-vars': ['warn', { argsIgnorePattern: '^_' }],
23
+ 'no-undef': 'error',
24
+ 'no-constant-condition': 'warn',
25
+ eqeqeq: ['error', 'always'],
26
+ 'no-var': 'error',
27
+ 'prefer-const': 'warn',
28
+ },
29
+ },
30
+ prettier,
31
+ ];
package/package.json CHANGED
@@ -1,16 +1,32 @@
1
1
  {
2
2
  "name": "create-lore",
3
- "version": "0.8.1",
3
+ "version": "0.10.0",
4
4
  "description": "Create a new Lore knowledge-persistent agent repo",
5
5
  "bin": {
6
6
  "create-lore": "bin/create-lore.js"
7
7
  },
8
8
  "scripts": {
9
- "test": "node --test test/create-lore.test.js"
9
+ "test": "node --test test/create-lore.test.js",
10
+ "lint": "eslint .",
11
+ "format:check": "prettier --check ."
10
12
  },
11
- "keywords": ["lore", "agent", "ai", "knowledge"],
13
+ "keywords": [
14
+ "lore",
15
+ "agent",
16
+ "ai",
17
+ "knowledge"
18
+ ],
12
19
  "license": "Apache-2.0",
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "https://github.com/lorehq/create-lore"
23
+ },
13
24
  "engines": {
14
25
  "node": ">=18"
26
+ },
27
+ "devDependencies": {
28
+ "eslint": "^9.0.0",
29
+ "eslint-config-prettier": "^10.1.8",
30
+ "prettier": "^3.8.1"
15
31
  }
16
32
  }
@@ -4,17 +4,26 @@
4
4
 
5
5
  const { describe, it, before, after } = require('node:test');
6
6
  const assert = require('node:assert/strict');
7
- const { execSync } = require('child_process');
7
+ const { execSync, execFileSync } = require('child_process');
8
8
  const fs = require('fs');
9
9
  const path = require('path');
10
10
 
11
11
  const BIN = path.resolve(__dirname, '../bin/create-lore.js');
12
- const TEMPLATE = path.resolve(__dirname, '../../lore'); // Sibling lore repo
12
+ const TEMPLATE = process.env.LORE_TEMPLATE || path.resolve(__dirname, '../../lore');
13
13
  const OUTPUT = path.resolve(__dirname, '../test-output');
14
14
 
15
15
  // Run the installer with LORE_TEMPLATE pointing at the local template
16
16
  function run(args = '') {
17
- return execSync(`node ${BIN} ${args}`, {
17
+ return execFileSync('node', [BIN, ...args.split(' ').filter(Boolean)], {
18
+ env: { ...process.env, LORE_TEMPLATE: TEMPLATE },
19
+ stdio: 'pipe',
20
+ encoding: 'utf8',
21
+ });
22
+ }
23
+
24
+ // Run with exact argv (no shell interpretation) — for testing shell metacharacters
25
+ function runExact(name) {
26
+ return execFileSync('node', [BIN, name], {
18
27
  env: { ...process.env, LORE_TEMPLATE: TEMPLATE },
19
28
  stdio: 'pipe',
20
29
  encoding: 'utf8',
@@ -29,6 +38,18 @@ describe('create-lore', () => {
29
38
  before(cleanup);
30
39
  after(cleanup);
31
40
 
41
+ it('--help shows usage text', () => {
42
+ const output = run('--help');
43
+ assert.ok(output.includes('Usage:'), 'shows usage');
44
+ assert.ok(output.includes('create-lore'), 'mentions create-lore');
45
+ });
46
+
47
+ it('--version outputs package.json version', () => {
48
+ const output = run('--version');
49
+ const pkg = require('../package.json');
50
+ assert.equal(output.trim(), pkg.version);
51
+ });
52
+
32
53
  it('exits with error when no name given', () => {
33
54
  assert.throws(() => run(''), { status: 1 });
34
55
  });
@@ -38,8 +59,14 @@ describe('create-lore', () => {
38
59
 
39
60
  assert.ok(fs.existsSync(OUTPUT), 'output directory exists');
40
61
 
41
- // .lore-config has required fields
42
- const config = JSON.parse(fs.readFileSync(path.join(OUTPUT, '.lore-config'), 'utf8'));
62
+ // .lore/config.json has required fields (JSONC — strip comments before parsing)
63
+ const raw = fs.readFileSync(path.join(OUTPUT, '.lore', 'config.json'), 'utf8');
64
+ const config = JSON.parse(
65
+ raw
66
+ .replace(/^\s*\/\/.*$/gm, '')
67
+ .replace(/\/\*[\s\S]*?\*\//g, '')
68
+ .replace(/,(\s*[}\]])/g, '$1'),
69
+ );
43
70
  assert.ok(config.name, 'name present');
44
71
  assert.ok(config.created, 'created date present');
45
72
 
@@ -49,12 +76,51 @@ describe('create-lore', () => {
49
76
  assert.ok(entries.includes('HEAD'), '.git looks like a fresh init');
50
77
  });
51
78
 
79
+ it('strips dev-only files from scaffolded instance', () => {
80
+ cleanup();
81
+ run(OUTPUT);
82
+
83
+ const devOnly = [
84
+ 'test',
85
+ '.github',
86
+ 'node_modules',
87
+ 'site',
88
+ 'docs/assets',
89
+ 'docs/javascripts',
90
+ 'docs/stylesheets',
91
+ 'CODE_OF_CONDUCT.md',
92
+ 'CONTRIBUTING.md',
93
+ 'SECURITY.md',
94
+ 'LICENSE',
95
+ 'README.md',
96
+ '.prettierrc',
97
+ '.prettierignore',
98
+ 'eslint.config.js',
99
+ 'package-lock.json',
100
+ ];
101
+ for (const name of devOnly) {
102
+ assert.ok(!fs.existsSync(path.join(OUTPUT, name)), `${name} should be stripped`);
103
+ }
104
+ cleanup();
105
+ });
106
+
52
107
  it('fails if target directory already exists', () => {
53
108
  fs.mkdirSync(OUTPUT, { recursive: true });
54
109
  assert.throws(() => run(OUTPUT), /already exists/);
55
110
  fs.rmSync(OUTPUT, { recursive: true });
56
111
  });
57
112
 
113
+ it('rejects names with shell metacharacters', () => {
114
+ assert.throws(() => runExact('foo;echo pwned'), /Invalid project name/);
115
+ assert.throws(() => runExact('foo$(cmd)'), /Invalid project name/);
116
+ assert.throws(() => runExact('foo|bar'), /Invalid project name/);
117
+ });
118
+
119
+ it('rejects path arguments with shell metacharacters in basename', () => {
120
+ assert.throws(() => runExact('./foo;rm'), /Invalid project name/);
121
+ assert.throws(() => runExact('/tmp/bad$(cmd)'), /Invalid project name/);
122
+ });
123
+
58
124
  // -- Template content tests --
59
125
  // These skip gracefully if the template hasn't reached that phase yet.
60
126
 
@@ -81,14 +147,16 @@ describe('create-lore', () => {
81
147
  if (!fs.existsSync(path.join(TEMPLATE, '.opencode/plugins'))) return;
82
148
 
83
149
  run(OUTPUT);
84
- assert.ok(fs.existsSync(path.join(OUTPUT, '.opencode', 'plugins', 'session-init.js')),
85
- 'session-init.js copied');
86
- assert.ok(fs.existsSync(path.join(OUTPUT, '.opencode', 'plugins', 'knowledge-tracker.js')),
87
- 'knowledge-tracker.js copied');
88
- assert.ok(fs.existsSync(path.join(OUTPUT, '.opencode', 'plugins', 'protect-memory.js')),
89
- 'protect-memory.js copied');
90
- assert.ok(fs.existsSync(path.join(OUTPUT, '.opencode', 'package.json')),
91
- '.opencode/package.json copied');
150
+ assert.ok(fs.existsSync(path.join(OUTPUT, '.opencode', 'plugins', 'session-init.js')), 'session-init.js copied');
151
+ assert.ok(
152
+ fs.existsSync(path.join(OUTPUT, '.opencode', 'plugins', 'knowledge-tracker.js')),
153
+ 'knowledge-tracker.js copied',
154
+ );
155
+ assert.ok(
156
+ fs.existsSync(path.join(OUTPUT, '.opencode', 'plugins', 'protect-memory.js')),
157
+ 'protect-memory.js copied',
158
+ );
159
+ assert.ok(fs.existsSync(path.join(OUTPUT, '.opencode', 'package.json')), '.opencode/package.json copied');
92
160
  cleanup();
93
161
  });
94
162
 
@@ -103,10 +171,10 @@ describe('create-lore', () => {
103
171
  });
104
172
 
105
173
  it('passes validate-consistency.sh when template provides it', () => {
106
- if (!fs.existsSync(path.join(TEMPLATE, 'scripts/validate-consistency.sh'))) return;
174
+ if (!fs.existsSync(path.join(TEMPLATE, '.lore/scripts/validate-consistency.sh'))) return;
107
175
 
108
176
  run(OUTPUT);
109
- const result = execSync('bash scripts/validate-consistency.sh', {
177
+ const result = execSync('bash .lore/scripts/validate-consistency.sh', {
110
178
  cwd: OUTPUT,
111
179
  encoding: 'utf8',
112
180
  stdio: 'pipe',