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 +2 -0
- package/.github/CODEOWNERS +1 -0
- package/.github/ISSUE_TEMPLATE/bug_report.md +24 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +17 -0
- package/.github/workflows/release.yml +76 -0
- package/.github/workflows/test.yml +55 -0
- package/.prettierignore +1 -0
- package/.prettierrc +7 -0
- package/CONTRIBUTING.md +51 -0
- package/README.md +33 -24
- package/SECURITY.md +24 -0
- package/bin/create-lore.js +107 -12
- package/eslint.config.js +31 -0
- package/package.json +19 -3
- package/test/create-lore.test.js +83 -15
package/.gitattributes
ADDED
|
@@ -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
|
package/.prettierignore
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
lore/
|
package/.prettierrc
ADDED
package/CONTRIBUTING.md
ADDED
|
@@ -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
|
-
|
|
3
|
+
Bootstrap a new [Lore](https://github.com/lorehq/lore) project — persistent memory for AI coding agents.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## The Problem
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
29
|
+
## Supported Platforms
|
|
21
30
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|
|
32
|
-
npx create-lore ./custom-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
|
-
##
|
|
36
|
-
|
|
37
|
-
```bash
|
|
38
|
-
cd lore-myproject
|
|
39
|
-
git add -A && git commit -m "Init Lore"
|
|
40
|
-
```
|
|
48
|
+
## Requirements
|
|
41
49
|
|
|
42
|
-
|
|
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 |
|
package/bin/create-lore.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
-
|
|
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
|
|
127
|
+
// -- Write .lore/config.json --
|
|
58
128
|
const projectName = isPath ? path.basename(targetDir) : name;
|
|
59
|
-
// Read version from the
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
}
|
|
66
|
-
|
|
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' });
|
package/eslint.config.js
ADDED
|
@@ -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.
|
|
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": [
|
|
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
|
}
|
package/test/create-lore.test.js
CHANGED
|
@@ -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');
|
|
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
|
|
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
|
|
42
|
-
const
|
|
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
|
-
|
|
86
|
-
|
|
87
|
-
'knowledge-tracker.js copied'
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
'.
|
|
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',
|