diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..a0d9c74 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,8 @@ +github: + - jb-thery + +# Optional funding platforms can be added later. +# open_collective: +# - mimir +# custom: +# - https://polar.sh/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 2639a2d..fa9aeb1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## 0.2.1 - 2026-06-28 + +- Add GitHub Sponsors funding metadata and document suggested sponsor tiers. +- Add maintainer positioning for Jean-Baptiste Thery and JCode Labs in the README. +- Make `kb init` and `kb install-skill` automatically keep `.kb/` and `.mimir/` + ignored by Git. + ## 0.2.0 - 2026-06-28 - Rename public product branding to Mimir while keeping the JCode Labs npm scope. diff --git a/README.md b/README.md index a6b8833..fdf1b04 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,8 @@ and uses Ollama for local embeddings and answers. Created by Jean-Baptiste Thery and published under the JCode Labs npm scope. +Built by Jean-Baptiste Thery, freelance full-stack/AI tooling engineer at JCode Labs. + ## Open Source Mimir is a public open-source project under the MIT License. It is designed to be @@ -21,6 +23,20 @@ inspectable, forkable, and usable without a JCode Labs account. Contributions are welcome through pull requests. Start with [`CONTRIBUTING.md`](./CONTRIBUTING.md). Security reports should stay private and follow the policy in [`SECURITY.md`](./SECURITY.md). +## Sponsors + +Mimir stays MIT open source. Sponsorship helps fund maintenance, issue triage, +documentation, and practical agent-workflow improvements. + +Sponsor the project through [GitHub Sponsors](https://github.com/sponsors/jb-thery). + +Suggested GitHub Sponsors tiers: + +- EUR 5/month: support the project. +- EUR 15/month: active sponsor. +- EUR 49/month: priority on issues and questions. +- EUR 199/month: company sponsor and light advisory support. + ## Status Early public package. APIs may evolve before `1.0.0`. @@ -161,8 +177,9 @@ your-project/ .kb/storage/ # generated LanceDB index ``` -The package never ships project documents. `kb init` adds gitignore entries for `.kb/storage/` -and `private/**`. +The package never ships project documents. `kb init` adds gitignore entries for `.kb/` +and `private/**`, and `kb install-skill` keeps `.mimir/` ignored as generated local agent +state. ## Supported Files diff --git a/dist/gitignore.d.ts b/dist/gitignore.d.ts new file mode 100644 index 0000000..9871cae --- /dev/null +++ b/dist/gitignore.d.ts @@ -0,0 +1,3 @@ +export declare const MIMIR_GITIGNORE_ENTRIES: readonly [".kb/", ".mimir/", "private/**", "!private/", "!private/README.md", "!private/**/", "!private/**/.gitkeep"]; +export declare function ensureMimirGitignore(cwd?: string): Promise; +//# sourceMappingURL=gitignore.d.ts.map \ No newline at end of file diff --git a/dist/gitignore.d.ts.map b/dist/gitignore.d.ts.map new file mode 100644 index 0000000..d3a258b --- /dev/null +++ b/dist/gitignore.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"gitignore.d.ts","sourceRoot":"","sources":["../src/gitignore.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,uBAAuB,uHAQ1B,CAAA;AAEV,wBAAsB,oBAAoB,CAAC,GAAG,SAAgB,GAAG,OAAO,CAAC,OAAO,CAAC,CAyBhF"} \ No newline at end of file diff --git a/dist/gitignore.js b/dist/gitignore.js new file mode 100644 index 0000000..7cd4746 --- /dev/null +++ b/dist/gitignore.js @@ -0,0 +1,34 @@ +import { existsSync } from "node:fs"; +import { readFile, writeFile } from "node:fs/promises"; +import path from "node:path"; +export const MIMIR_GITIGNORE_ENTRIES = [ + ".kb/", + ".mimir/", + "private/**", + "!private/", + "!private/README.md", + "!private/**/", + "!private/**/.gitkeep", +]; +export async function ensureMimirGitignore(cwd = process.cwd()) { + const root = path.resolve(cwd); + const gitignorePath = path.join(root, ".gitignore"); + const current = existsSync(gitignorePath) ? await readFile(gitignorePath, "utf8") : ""; + const currentLines = new Set(current + .split(/\r?\n/) + .map((line) => line.trim()) + .filter(Boolean)); + const missingEntries = MIMIR_GITIGNORE_ENTRIES.filter((entry) => !currentLines.has(entry)); + if (missingEntries.length === 0) { + return false; + } + const hasMimirHeader = currentLines.has("# Mimir") || currentLines.has("# JCode Mimir"); + const block = [hasMimirHeader ? undefined : "# Mimir", ...missingEntries] + .filter((line) => line !== undefined) + .join("\n"); + const prefix = current.trimEnd(); + const next = `${prefix ? `${prefix}\n\n` : ""}${block}\n`; + await writeFile(gitignorePath, next, "utf8"); + return true; +} +//# sourceMappingURL=gitignore.js.map \ No newline at end of file diff --git a/dist/gitignore.js.map b/dist/gitignore.js.map new file mode 100644 index 0000000..0460f9c --- /dev/null +++ b/dist/gitignore.js.map @@ -0,0 +1 @@ +{"version":3,"file":"gitignore.js","sourceRoot":"","sources":["../src/gitignore.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAA;AACpC,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AACtD,OAAO,IAAI,MAAM,WAAW,CAAA;AAE5B,MAAM,CAAC,MAAM,uBAAuB,GAAG;IACrC,MAAM;IACN,SAAS;IACT,YAAY;IACZ,WAAW;IACX,oBAAoB;IACpB,cAAc;IACd,sBAAsB;CACd,CAAA;AAEV,MAAM,CAAC,KAAK,UAAU,oBAAoB,CAAC,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE;IAC5D,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;IAC9B,MAAM,aAAa,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,YAAY,CAAC,CAAA;IACnD,MAAM,OAAO,GAAG,UAAU,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,MAAM,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,CAAA;IACtF,MAAM,YAAY,GAAG,IAAI,GAAG,CAC1B,OAAO;SACJ,KAAK,CAAC,OAAO,CAAC;SACd,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;SAC1B,MAAM,CAAC,OAAO,CAAC,CACnB,CAAA;IACD,MAAM,cAAc,GAAG,uBAAuB,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,YAAY,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAA;IAE1F,IAAI,cAAc,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAChC,OAAO,KAAK,CAAA;IACd,CAAC;IAED,MAAM,cAAc,GAAG,YAAY,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,YAAY,CAAC,GAAG,CAAC,eAAe,CAAC,CAAA;IACvF,MAAM,KAAK,GAAG,CAAC,cAAc,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,EAAE,GAAG,cAAc,CAAC;SACtE,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,KAAK,SAAS,CAAC;SACpC,IAAI,CAAC,IAAI,CAAC,CAAA;IACb,MAAM,MAAM,GAAG,OAAO,CAAC,OAAO,EAAE,CAAA;IAChC,MAAM,IAAI,GAAG,GAAG,MAAM,CAAC,CAAC,CAAC,GAAG,MAAM,MAAM,CAAC,CAAC,CAAC,EAAE,GAAG,KAAK,IAAI,CAAA;IAEzD,MAAM,SAAS,CAAC,aAAa,EAAE,IAAI,EAAE,MAAM,CAAC,CAAA;IAC5C,OAAO,IAAI,CAAA;AACb,CAAC"} \ No newline at end of file diff --git a/dist/init.d.ts.map b/dist/init.d.ts.map index cfc5f34..f2b4adc 100644 --- a/dist/init.d.ts.map +++ b/dist/init.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../src/init.ts"],"names":[],"mappings":"AAmBA,wBAAsB,WAAW,CAAC,GAAG,SAAgB,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CA2CxE"} \ No newline at end of file +{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../src/init.ts"],"names":[],"mappings":"AAkBA,wBAAsB,WAAW,CAAC,GAAG,SAAgB,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAwCxE"} \ No newline at end of file diff --git a/dist/init.js b/dist/init.js index 01481ca..4f59a38 100644 --- a/dist/init.js +++ b/dist/init.js @@ -1,6 +1,7 @@ import { existsSync } from "node:fs"; -import { mkdir, readFile, writeFile } from "node:fs/promises"; +import { mkdir, writeFile } from "node:fs/promises"; import path from "node:path"; +import { ensureMimirGitignore } from "./gitignore.js"; const DEFAULT_CONFIG = { rawDir: "private", storageDir: ".kb/storage", @@ -13,7 +14,6 @@ const DEFAULT_CONFIG = { chunkSize: 1200, chunkOverlap: 150, }; -const GITIGNORE_BLOCK = `\n# Mimir\n.kb/storage/\n.kb/cache/\n.kb/*.local.json\nprivate/**\n!private/\n!private/README.md\n!private/**/\n!private/**/.gitkeep\n`; export async function initProject(cwd = process.cwd()) { const root = path.resolve(cwd); const kbDir = path.join(root, ".kb"); @@ -36,11 +36,8 @@ export async function initProject(cwd = process.cwd()) { await writeFile(readmePath, "# Private documents\n\nPut raw documents to ingest here. Keep this folder ignored by Git.\n", "utf8"); created.push(path.relative(root, readmePath)); } - const gitignorePath = path.join(root, ".gitignore"); - const currentGitignore = existsSync(gitignorePath) ? await readFile(gitignorePath, "utf8") : ""; - if (!currentGitignore.includes("# Mimir") && !currentGitignore.includes("# JCode Mimir")) { - await writeFile(gitignorePath, `${currentGitignore.trimEnd()}${GITIGNORE_BLOCK}`, "utf8"); - created.push(path.relative(root, gitignorePath)); + if (await ensureMimirGitignore(root)) { + created.push(".gitignore"); } return created; } diff --git a/dist/init.js.map b/dist/init.js.map index c8e4e3f..aa1d15d 100644 --- a/dist/init.js.map +++ b/dist/init.js.map @@ -1 +1 @@ -{"version":3,"file":"init.js","sourceRoot":"","sources":["../src/init.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"init.js","sourceRoot":"","sources":["../src/init.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAA;AACpC,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AACnD,OAAO,IAAI,MAAM,WAAW,CAAA;AAC5B,OAAO,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAA;AAErD,MAAM,cAAc,GAAG;IACrB,MAAM,EAAE,SAAS;IACjB,UAAU,EAAE,aAAa;IACzB,WAAW,EAAE,iBAAiB;IAC9B,SAAS,EAAE,QAAQ;IACnB,UAAU,EAAE,wBAAwB;IACpC,UAAU,EAAE,kBAAkB;IAC9B,QAAQ,EAAE,eAAe;IACzB,IAAI,EAAE,CAAC;IACP,SAAS,EAAE,IAAI;IACf,YAAY,EAAE,GAAG;CAClB,CAAA;AAED,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE;IACnD,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;IAC9B,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC,CAAA;IACpC,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,SAAS,CAAC,CAAA;IAC7C,MAAM,OAAO,GAAa,EAAE,CAAA;IAE5B,MAAM,KAAK,CAAC,KAAK,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;IACvC,MAAM,KAAK,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;IAE5C,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,aAAa,CAAC,CAAA;IAClD,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QAC5B,MAAM,SAAS,CAAC,UAAU,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,cAAc,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,MAAM,CAAC,CAAA;QACnF,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC,CAAA;IAC/C,CAAC;IAED,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,aAAa,CAAC,CAAA;IACnD,IAAI,CAAC,UAAU,CAAC,WAAW,CAAC,EAAE,CAAC;QAC7B,MAAM,SAAS,CACb,WAAW,EACX,8FAA8F,EAC9F,MAAM,CACP,CAAA;QACD,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC,CAAA;IAChD,CAAC;IAED,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,WAAW,CAAC,CAAA;IACrD,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QAC5B,MAAM,SAAS,CACb,UAAU,EACV,6FAA6F,EAC7F,MAAM,CACP,CAAA;QACD,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC,CAAA;IAC/C,CAAC;IAED,IAAI,MAAM,oBAAoB,CAAC,IAAI,CAAC,EAAE,CAAC;QACrC,OAAO,CAAC,IAAI,CAAC,YAAY,CAAC,CAAA;IAC5B,CAAC;IAED,OAAO,OAAO,CAAA;AAChB,CAAC"} \ No newline at end of file diff --git a/dist/skill.d.ts.map b/dist/skill.d.ts.map index 98efc10..c44432e 100644 --- a/dist/skill.d.ts.map +++ b/dist/skill.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"skill.d.ts","sourceRoot":"","sources":["../src/skill.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,mBAAmB;IAClC,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,MAAM,WAAW,kBAAkB;IACjC,SAAS,EAAE,MAAM,CAAA;IACjB,aAAa,EAAE,MAAM,CAAA;IACrB,UAAU,EAAE,MAAM,CAAA;IAClB,OAAO,EAAE,MAAM,EAAE,CAAA;CAClB;AAKD,wBAAgB,gBAAgB,IAAI,MAAM,CAEzC;AAED,wBAAsB,YAAY,CAAC,OAAO,GAAE,mBAAwB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAyBjG"} \ No newline at end of file +{"version":3,"file":"skill.d.ts","sourceRoot":"","sources":["../src/skill.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,mBAAmB;IAClC,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,MAAM,WAAW,kBAAkB;IACjC,SAAS,EAAE,MAAM,CAAA;IACjB,aAAa,EAAE,MAAM,CAAA;IACrB,UAAU,EAAE,MAAM,CAAA;IAClB,OAAO,EAAE,MAAM,EAAE,CAAA;CAClB;AAKD,wBAAgB,gBAAgB,IAAI,MAAM,CAEzC;AAED,wBAAsB,YAAY,CAAC,OAAO,GAAE,mBAAwB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAgCjG"} \ No newline at end of file diff --git a/dist/skill.js b/dist/skill.js index d9a2ba0..6d19270 100644 --- a/dist/skill.js +++ b/dist/skill.js @@ -1,6 +1,7 @@ import { cp, mkdir, writeFile } from "node:fs/promises"; import path from "node:path"; import { fileURLToPath } from "node:url"; +import { ensureMimirGitignore } from "./gitignore.js"; const PACKAGE_ROOT = path.dirname(path.dirname(fileURLToPath(import.meta.url))); const SKILL_NAME = "mimir"; export function bundledSkillPath() { @@ -18,15 +19,20 @@ export async function installSkill(options = {}) { await cp(bundledSkillPath(), skillPath, { recursive: true, force: true }); await writeFile(mcpConfigPath, `${JSON.stringify(mcpConfig(cwd), null, 2)}\n`, "utf8"); await writeFile(readmePath, agentKitReadme(skillPath, mcpConfigPath), "utf8"); + const wroteGitignore = await ensureMimirGitignore(cwd); + const written = [ + path.relative(cwd, skillPath), + path.relative(cwd, mcpConfigPath), + path.relative(cwd, readmePath), + ]; + if (wroteGitignore) { + written.push(".gitignore"); + } return { skillPath, mcpConfigPath, readmePath, - written: [ - path.relative(cwd, skillPath), - path.relative(cwd, mcpConfigPath), - path.relative(cwd, readmePath), - ], + written, }; } function mcpConfig(cwd) { diff --git a/dist/skill.js.map b/dist/skill.js.map index 7037cb8..bf6541d 100644 --- a/dist/skill.js.map +++ b/dist/skill.js.map @@ -1 +1 @@ -{"version":3,"file":"skill.js","sourceRoot":"","sources":["../src/skill.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AACvD,OAAO,IAAI,MAAM,WAAW,CAAA;AAC5B,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAcxC,MAAM,YAAY,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAA;AAC/E,MAAM,UAAU,GAAG,OAAO,CAAA;AAE1B,MAAM,UAAU,gBAAgB;IAC9B,OAAO,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,QAAQ,EAAE,UAAU,CAAC,CAAA;AACtD,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,UAA+B,EAAE;IAClE,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC,CAAA;IACtD,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,OAAO,CAAC,SAAS,IAAI,eAAe,CAAC,CAAA;IACzE,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,UAAU,CAAC,CAAA;IAClD,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAA;IAC5C,MAAM,aAAa,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAA;IACrD,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAA;IAEnD,MAAM,KAAK,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;IAC3C,MAAM,KAAK,CAAC,QAAQ,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;IAC1C,MAAM,EAAE,CAAC,gBAAgB,EAAE,EAAE,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAA;IAEzE,MAAM,SAAS,CAAC,aAAa,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,MAAM,CAAC,CAAA;IACtF,MAAM,SAAS,CAAC,UAAU,EAAE,cAAc,CAAC,SAAS,EAAE,aAAa,CAAC,EAAE,MAAM,CAAC,CAAA;IAE7E,OAAO;QACL,SAAS;QACT,aAAa;QACb,UAAU;QACV,OAAO,EAAE;YACP,IAAI,CAAC,QAAQ,CAAC,GAAG,EAAE,SAAS,CAAC;YAC7B,IAAI,CAAC,QAAQ,CAAC,GAAG,EAAE,aAAa,CAAC;YACjC,IAAI,CAAC,QAAQ,CAAC,GAAG,EAAE,UAAU,CAAC;SAC/B;KACF,CAAA;AACH,CAAC;AAED,SAAS,SAAS,CAAC,GAAW;IAC5B,OAAO;QACL,UAAU,EAAE;YACV,KAAK,EAAE;gBACL,OAAO,EAAE,MAAM;gBACf,IAAI,EAAE,CAAC,MAAM,EAAE,IAAI,EAAE,WAAW,CAAC;gBACjC,GAAG;aACJ;SACF;KACF,CAAA;AACH,CAAC;AAED,SAAS,cAAc,CAAC,SAAiB,EAAE,aAAqB;IAC9D,OAAO;;;;;;;;;EASP,SAAS;;;;;;;;;;EAUT,aAAa;;;;;;;;;CASd,CAAA;AACD,CAAC"} \ No newline at end of file +{"version":3,"file":"skill.js","sourceRoot":"","sources":["../src/skill.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/version.d.ts b/dist/version.d.ts index 58b2108..d6d58cb 100644 --- a/dist/version.d.ts +++ b/dist/version.d.ts @@ -1,2 +1,2 @@ -export declare const VERSION = "0.2.0"; +export declare const VERSION = "0.2.1"; //# sourceMappingURL=version.d.ts.map \ No newline at end of file diff --git a/dist/version.js b/dist/version.js index 7ac8e24..da208e6 100644 --- a/dist/version.js +++ b/dist/version.js @@ -1,2 +1,2 @@ -export const VERSION = "0.2.0"; +export const VERSION = "0.2.1"; //# sourceMappingURL=version.js.map \ No newline at end of file diff --git a/package.json b/package.json index 2b36157..7d602c8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@jcode.labs/mimir", - "version": "0.2.0", + "version": "0.2.1", "description": "Mimir: open-source local-first memory and retrieval for private project knowledge.", "type": "module", "license": "MIT", diff --git a/scripts/smoke.mjs b/scripts/smoke.mjs index f154bec..be64fc1 100644 --- a/scripts/smoke.mjs +++ b/scripts/smoke.mjs @@ -39,6 +39,9 @@ try { await runKb(["install-skill"], tempRoot) const skill = await readFile(path.join(tempRoot, ".mimir", "skills", "mimir", "SKILL.md"), "utf8") assertIncludes(skill, "name: mimir", "install-skill should copy the bundled skill") + const gitignore = await readFile(path.join(tempRoot, ".gitignore"), "utf8") + assertIncludes(gitignore, ".kb/", "init should ignore the Mimir config and index directory") + assertIncludes(gitignore, ".mimir/", "install-skill should ignore generated agent kit files") await smokeMcp(tempRoot) console.log("Smoke test passed.") diff --git a/src/gitignore.ts b/src/gitignore.ts new file mode 100644 index 0000000..46c8ab5 --- /dev/null +++ b/src/gitignore.ts @@ -0,0 +1,40 @@ +import { existsSync } from "node:fs" +import { readFile, writeFile } from "node:fs/promises" +import path from "node:path" + +export const MIMIR_GITIGNORE_ENTRIES = [ + ".kb/", + ".mimir/", + "private/**", + "!private/", + "!private/README.md", + "!private/**/", + "!private/**/.gitkeep", +] as const + +export async function ensureMimirGitignore(cwd = process.cwd()): Promise { + const root = path.resolve(cwd) + const gitignorePath = path.join(root, ".gitignore") + const current = existsSync(gitignorePath) ? await readFile(gitignorePath, "utf8") : "" + const currentLines = new Set( + current + .split(/\r?\n/) + .map((line) => line.trim()) + .filter(Boolean), + ) + const missingEntries = MIMIR_GITIGNORE_ENTRIES.filter((entry) => !currentLines.has(entry)) + + if (missingEntries.length === 0) { + return false + } + + const hasMimirHeader = currentLines.has("# Mimir") || currentLines.has("# JCode Mimir") + const block = [hasMimirHeader ? undefined : "# Mimir", ...missingEntries] + .filter((line) => line !== undefined) + .join("\n") + const prefix = current.trimEnd() + const next = `${prefix ? `${prefix}\n\n` : ""}${block}\n` + + await writeFile(gitignorePath, next, "utf8") + return true +} diff --git a/src/init.ts b/src/init.ts index d3f5d41..159e5da 100644 --- a/src/init.ts +++ b/src/init.ts @@ -1,6 +1,7 @@ import { existsSync } from "node:fs" -import { mkdir, readFile, writeFile } from "node:fs/promises" +import { mkdir, writeFile } from "node:fs/promises" import path from "node:path" +import { ensureMimirGitignore } from "./gitignore.js" const DEFAULT_CONFIG = { rawDir: "private", @@ -15,8 +16,6 @@ const DEFAULT_CONFIG = { chunkOverlap: 150, } -const GITIGNORE_BLOCK = `\n# Mimir\n.kb/storage/\n.kb/cache/\n.kb/*.local.json\nprivate/**\n!private/\n!private/README.md\n!private/**/\n!private/**/.gitkeep\n` - export async function initProject(cwd = process.cwd()): Promise { const root = path.resolve(cwd) const kbDir = path.join(root, ".kb") @@ -52,11 +51,8 @@ export async function initProject(cwd = process.cwd()): Promise { created.push(path.relative(root, readmePath)) } - const gitignorePath = path.join(root, ".gitignore") - const currentGitignore = existsSync(gitignorePath) ? await readFile(gitignorePath, "utf8") : "" - if (!currentGitignore.includes("# Mimir") && !currentGitignore.includes("# JCode Mimir")) { - await writeFile(gitignorePath, `${currentGitignore.trimEnd()}${GITIGNORE_BLOCK}`, "utf8") - created.push(path.relative(root, gitignorePath)) + if (await ensureMimirGitignore(root)) { + created.push(".gitignore") } return created diff --git a/src/skill.test.ts b/src/skill.test.ts index 02dc0b2..e24ad09 100644 --- a/src/skill.test.ts +++ b/src/skill.test.ts @@ -28,4 +28,18 @@ describe("installSkill", () => { expect(mcpConfig.mcpServers.mimir.args).toEqual(["exec", "kb", "serve-mcp"]) expect(mcpConfig.mcpServers.mimir.cwd).toBe(root) }) + + it("adds Mimir runtime folders to gitignore without duplicating entries", async () => { + const root = await mkdtemp(path.join(os.tmpdir(), "mimir-skill-")) + tempDirs.push(root) + + const first = await installSkill({ cwd: root }) + const second = await installSkill({ cwd: root }) + const gitignore = await readFile(path.join(root, ".gitignore"), "utf8") + + expect(first.written).toContain(".gitignore") + expect(second.written).not.toContain(".gitignore") + expect(gitignore.match(/^\.kb\/$/gm)).toHaveLength(1) + expect(gitignore.match(/^\.mimir\/$/gm)).toHaveLength(1) + }) }) diff --git a/src/skill.ts b/src/skill.ts index b4a8e13..c88c45a 100644 --- a/src/skill.ts +++ b/src/skill.ts @@ -1,6 +1,7 @@ import { cp, mkdir, writeFile } from "node:fs/promises" import path from "node:path" import { fileURLToPath } from "node:url" +import { ensureMimirGitignore } from "./gitignore.js" export interface InstallSkillOptions { cwd?: string @@ -35,16 +36,23 @@ export async function installSkill(options: InstallSkillOptions = {}): Promise