@repomem/memory 0.0.0-stage → 0.1.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.
Files changed (43) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +204 -2
  3. package/assets/kiro/hooks/repomem-validate.sh +10 -0
  4. package/assets/kiro/skills/repomem-distill/SKILL.md +56 -0
  5. package/assets/kiro/skills/repomem-memory/SKILL.md +41 -0
  6. package/dist/cli.d.ts +11 -0
  7. package/dist/cli.d.ts.map +1 -0
  8. package/dist/cli.js +183 -0
  9. package/dist/cli.js.map +1 -0
  10. package/dist/index.d.ts +7 -0
  11. package/dist/index.d.ts.map +1 -0
  12. package/dist/index.js +35 -0
  13. package/dist/index.js.map +1 -0
  14. package/dist/schema.d.ts +97 -0
  15. package/dist/schema.d.ts.map +1 -0
  16. package/dist/schema.js +86 -0
  17. package/dist/schema.js.map +1 -0
  18. package/dist/search.d.ts +55 -0
  19. package/dist/search.d.ts.map +1 -0
  20. package/dist/search.js +160 -0
  21. package/dist/search.js.map +1 -0
  22. package/dist/server.d.ts +26 -0
  23. package/dist/server.d.ts.map +1 -0
  24. package/dist/server.js +176 -0
  25. package/dist/server.js.map +1 -0
  26. package/dist/setup/spec.d.ts +11 -0
  27. package/dist/setup/spec.d.ts.map +1 -0
  28. package/dist/setup/spec.js +44 -0
  29. package/dist/setup/spec.js.map +1 -0
  30. package/dist/store.d.ts +119 -0
  31. package/dist/store.d.ts.map +1 -0
  32. package/dist/store.js +350 -0
  33. package/dist/store.js.map +1 -0
  34. package/dist/validate.d.ts +42 -0
  35. package/dist/validate.d.ts.map +1 -0
  36. package/dist/validate.js +164 -0
  37. package/dist/validate.js.map +1 -0
  38. package/package.json +66 -4
  39. package/templates/context.md +25 -0
  40. package/templates/convention.md +16 -0
  41. package/templates/decision.md +20 -0
  42. package/templates/learning.md +16 -0
  43. package/templates/limitation.md +16 -0
@@ -0,0 +1,44 @@
1
+ /**
2
+ * spec.ts — repomem's own {@link AgentSetupSpec}.
3
+ *
4
+ * Resolves asset paths relative to the compiled file location, mirroring the
5
+ * `TEMPLATES_DIR` pattern in cli.ts so it works both from `dist/` in a
6
+ * published package and during local development.
7
+ */
8
+ import path from "node:path";
9
+ import { fileURLToPath } from "node:url";
10
+ const here = path.dirname(fileURLToPath(import.meta.url));
11
+ // dist/setup/spec.js -> ../../assets (package root holds `assets/`)
12
+ const ASSETS_DIR = path.resolve(here, "..", "..", "assets");
13
+ const KIRO_ASSETS = path.join(ASSETS_DIR, "kiro");
14
+ /** The spec repomem contributes to a Kiro setup. */
15
+ export const repomemSpec = {
16
+ serverName: "repomem",
17
+ mcpServer: {
18
+ command: "npx",
19
+ args: ["-y", "repomem"],
20
+ env: {},
21
+ disabled: false,
22
+ autoApprove: ["search_memory", "get_memory", "memory_for_path"],
23
+ },
24
+ skills: [
25
+ {
26
+ id: "repomem-memory",
27
+ srcDir: path.join(KIRO_ASSETS, "skills", "repomem-memory"),
28
+ },
29
+ {
30
+ id: "repomem-distill",
31
+ srcDir: path.join(KIRO_ASSETS, "skills", "repomem-distill"),
32
+ },
33
+ ],
34
+ hookScripts: [
35
+ {
36
+ name: "repomem-validate.sh",
37
+ srcPath: path.join(KIRO_ASSETS, "hooks", "repomem-validate.sh"),
38
+ },
39
+ ],
40
+ agentHooks: {
41
+ agentSpawn: [{ command: ".kiro/hooks/repomem-validate.sh" }],
42
+ },
43
+ };
44
+ //# sourceMappingURL=spec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spec.js","sourceRoot":"","sources":["../../src/setup/spec.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAGzC,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAC1D,qEAAqE;AACrE,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC;AAC5D,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;AAElD,oDAAoD;AACpD,MAAM,CAAC,MAAM,WAAW,GAAmB;IACzC,UAAU,EAAE,SAAS;IACrB,SAAS,EAAE;QACT,OAAO,EAAE,KAAK;QACd,IAAI,EAAE,CAAC,IAAI,EAAE,SAAS,CAAC;QACvB,GAAG,EAAE,EAAE;QACP,QAAQ,EAAE,KAAK;QACf,WAAW,EAAE,CAAC,eAAe,EAAE,YAAY,EAAE,iBAAiB,CAAC;KAChE;IACD,MAAM,EAAE;QACN;YACE,EAAE,EAAE,gBAAgB;YACpB,MAAM,EAAE,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,QAAQ,EAAE,gBAAgB,CAAC;SAC3D;QACD;YACE,EAAE,EAAE,iBAAiB;YACrB,MAAM,EAAE,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,QAAQ,EAAE,iBAAiB,CAAC;SAC5D;KACF;IACD,WAAW,EAAE;QACX;YACE,IAAI,EAAE,qBAAqB;YAC3B,OAAO,EAAE,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,OAAO,EAAE,qBAAqB,CAAC;SAChE;KACF;IACD,UAAU,EAAE;QACV,UAAU,EAAE,CAAC,EAAE,OAAO,EAAE,iCAAiC,EAAE,CAAC;KAC7D;CACF,CAAC"}
@@ -0,0 +1,119 @@
1
+ /**
2
+ * store.ts — read and write memory entries on disk.
3
+ *
4
+ * Responsibilities:
5
+ * - Resolve the memory root: `--root` flag > `MEMORY_ROOT` env > <git root>/.repomem/memory.
6
+ * - Load entries from `memory/<type>s/<slug>.md`, skipping (not crashing on)
7
+ * malformed files and reporting them to stderr.
8
+ * - Generate ASCII slugs from titles and write entries without ever
9
+ * overwriting an existing file.
10
+ * - Propose new draft entries (reject duplicates), deprecate existing ones
11
+ * in place, and find entries whose related_paths match a given file path.
12
+ *
13
+ * Never writes to stdout — diagnostics go to stderr only, to keep the MCP
14
+ * stdio protocol clean.
15
+ */
16
+ import { type EntryType, type Frontmatter, type FrontmatterInput, type MemoryEntry } from "./schema.js";
17
+ /** Today's date in UTC as `YYYY-MM-DD`. */
18
+ export declare function todayIso(): string;
19
+ export interface ResolveRootOptions {
20
+ /** Explicit `--root` value, if provided on the CLI. */
21
+ rootFlag?: string;
22
+ /** Directory to start git-root discovery from. Defaults to process.cwd(). */
23
+ cwd?: string;
24
+ /** Environment map (injectable for tests). Defaults to process.env. */
25
+ env?: NodeJS.ProcessEnv;
26
+ }
27
+ /**
28
+ * Walk upward from `start` looking for a `.git` entry (dir or file).
29
+ * Returns the directory containing it, or undefined if none is found.
30
+ */
31
+ export declare function findGitRoot(start: string): string | undefined;
32
+ /**
33
+ * Resolve the memory root using the precedence:
34
+ * 1. `--root` flag
35
+ * 2. `MEMORY_ROOT` env var
36
+ * 3. `<git root>/.repomem/memory`
37
+ *
38
+ * Throws if none can be resolved (no flag, no env, no git root found).
39
+ */
40
+ export declare function resolveMemoryRoot(options?: ResolveRootOptions): string;
41
+ /**
42
+ * Convert a title into an ASCII, URL-safe slug.
43
+ * Strips diacritics (á -> a), lowercases, replaces non-alphanumerics with
44
+ * single hyphens, trims hyphens, and caps length.
45
+ */
46
+ export declare function slugify(title: string): string;
47
+ /**
48
+ * Build a slug that does not collide with `existing`. Appends `-2`, `-3`, …
49
+ * until unique. Returns the input slug when there is no collision.
50
+ */
51
+ export declare function uniqueSlug(slug: string, existing: ReadonlySet<string>): string;
52
+ /**
53
+ * Parse a single entry file. Returns the entry, or undefined if the file is
54
+ * malformed (invalid frontmatter, wrong type for its folder). Malformed files
55
+ * are reported to stderr and skipped rather than throwing.
56
+ */
57
+ export declare function readEntryFile(file: string, expectedType?: EntryType): Promise<MemoryEntry | undefined>;
58
+ /**
59
+ * Load all valid entries under the memory root. Missing type folders are
60
+ * treated as empty. Malformed entries are skipped (reported to stderr).
61
+ */
62
+ export declare function loadEntries(root: string): Promise<MemoryEntry[]>;
63
+ /** Find a single entry by id across all type folders, or undefined. */
64
+ export declare function findEntryById(root: string, id: string): Promise<MemoryEntry | undefined>;
65
+ /** Collect the set of existing slugs for a given type (for collision checks). */
66
+ export declare function existingSlugs(root: string, type: EntryType): Promise<Set<string>>;
67
+ /**
68
+ * Serialize frontmatter + body back into a markdown file string.
69
+ * Keys are written in a stable, human-friendly order; optional keys are only
70
+ * emitted when present.
71
+ */
72
+ export declare function serializeEntry(frontmatter: Frontmatter, body: string): string;
73
+ export interface WriteResult {
74
+ id: string;
75
+ path: string;
76
+ }
77
+ /**
78
+ * Write a new entry to disk. Validates frontmatter, resolves a unique slug
79
+ * from the title, creates the type folder if needed, and NEVER overwrites an
80
+ * existing file. Returns the id and path written.
81
+ *
82
+ * @throws if frontmatter is invalid or the resolved file already exists.
83
+ */
84
+ export declare function writeEntry(root: string, input: FrontmatterInput, body: string): Promise<WriteResult>;
85
+ export interface ProposeInput {
86
+ type: EntryType;
87
+ title: string;
88
+ body: string;
89
+ tags?: string[];
90
+ related_paths?: string[];
91
+ supersedes?: string;
92
+ author?: string;
93
+ }
94
+ /**
95
+ * Propose a new entry. ALWAYS writes `status: draft`. The date defaults to
96
+ * today (UTC). Rejects a title whose slug already exists within the same type
97
+ * (a likely duplicate the human should resolve) rather than auto-suffixing.
98
+ *
99
+ * @throws if the derived slug collides with an existing entry of that type.
100
+ */
101
+ export declare function proposeEntry(root: string, input: ProposeInput): Promise<WriteResult>;
102
+ /**
103
+ * Mark an existing entry as deprecated, recording the reason (and optionally
104
+ * the id that supersedes it) in the frontmatter. Rewrites the file in place,
105
+ * preserving the body and other fields.
106
+ *
107
+ * @throws if the entry does not exist.
108
+ */
109
+ export declare function deprecateEntry(root: string, id: string, reason: string, supersededBy?: string): Promise<WriteResult>;
110
+ /**
111
+ * Return entries whose `related_paths` globs match the given file path.
112
+ * Deprecated entries are excluded. Results are ordered accepted-before-draft,
113
+ * then by date descending.
114
+ *
115
+ * The input path is normalized to posix separators so globs behave the same
116
+ * across platforms.
117
+ */
118
+ export declare function entriesForPath(root: string, filePath: string): Promise<MemoryEntry[]>;
119
+ //# sourceMappingURL=store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAOH,OAAO,EAIL,KAAK,SAAS,EACd,KAAK,WAAW,EAChB,KAAK,gBAAgB,EACrB,KAAK,WAAW,EACjB,MAAM,aAAa,CAAC;AAOrB,2CAA2C;AAC3C,wBAAgB,QAAQ,IAAI,MAAM,CAEjC;AAMD,MAAM,WAAW,kBAAkB;IACjC,uDAAuD;IACvD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,6EAA6E;IAC7E,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,uEAAuE;IACvE,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;CACzB;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAQ7D;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,GAAE,kBAAuB,GAAG,MAAM,CAc1E;AAQD;;;;GAIG;AACH,wBAAgB,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAS7C;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,WAAW,CAAC,MAAM,CAAC,GAAG,MAAM,CAK9E;AAeD;;;;GAIG;AACH,wBAAsB,aAAa,CACjC,IAAI,EAAE,MAAM,EACZ,YAAY,CAAC,EAAE,SAAS,GACvB,OAAO,CAAC,WAAW,GAAG,SAAS,CAAC,CAyClC;AAED;;;GAGG;AACH,wBAAsB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC,CAoBtE;AAED,uEAAuE;AACvE,wBAAsB,aAAa,CACjC,IAAI,EAAE,MAAM,EACZ,EAAE,EAAE,MAAM,GACT,OAAO,CAAC,WAAW,GAAG,SAAS,CAAC,CAQlC;AAED,iFAAiF;AACjF,wBAAsB,aAAa,CACjC,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,SAAS,GACd,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAUtB;AAMD;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,WAAW,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAuB7E;AAMD,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;;;;GAMG;AACH,wBAAsB,UAAU,CAC9B,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,gBAAgB,EACvB,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,WAAW,CAAC,CAmBtB;AAMD,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,SAAS,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;GAMG;AACH,wBAAsB,YAAY,CAChC,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,YAAY,GAClB,OAAO,CAAC,WAAW,CAAC,CAsCtB;AAMD;;;;;;GAMG;AACH,wBAAsB,cAAc,CAClC,IAAI,EAAE,MAAM,EACZ,EAAE,EAAE,MAAM,EACV,MAAM,EAAE,MAAM,EACd,YAAY,CAAC,EAAE,MAAM,GACpB,OAAO,CAAC,WAAW,CAAC,CAmBtB;AAYD;;;;;;;GAOG;AACH,wBAAsB,cAAc,CAClC,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,WAAW,EAAE,CAAC,CAoBxB"}
package/dist/store.js ADDED
@@ -0,0 +1,350 @@
1
+ /**
2
+ * store.ts — read and write memory entries on disk.
3
+ *
4
+ * Responsibilities:
5
+ * - Resolve the memory root: `--root` flag > `MEMORY_ROOT` env > <git root>/.repomem/memory.
6
+ * - Load entries from `memory/<type>s/<slug>.md`, skipping (not crashing on)
7
+ * malformed files and reporting them to stderr.
8
+ * - Generate ASCII slugs from titles and write entries without ever
9
+ * overwriting an existing file.
10
+ * - Propose new draft entries (reject duplicates), deprecate existing ones
11
+ * in place, and find entries whose related_paths match a given file path.
12
+ *
13
+ * Never writes to stdout — diagnostics go to stderr only, to keep the MCP
14
+ * stdio protocol clean.
15
+ */
16
+ import { promises as fs } from "node:fs";
17
+ import { existsSync } from "node:fs";
18
+ import path from "node:path";
19
+ import matter from "gray-matter";
20
+ import picomatch from "picomatch";
21
+ import { frontmatterSchema, TYPE_DIRS, ENTRY_TYPES, } from "./schema.js";
22
+ /** Log a diagnostic to stderr. Never use stdout (reserved for MCP). */
23
+ function warn(message) {
24
+ process.stderr.write(`[repomem] ${message}\n`);
25
+ }
26
+ /** Today's date in UTC as `YYYY-MM-DD`. */
27
+ export function todayIso() {
28
+ return new Date().toISOString().slice(0, 10);
29
+ }
30
+ /**
31
+ * Walk upward from `start` looking for a `.git` entry (dir or file).
32
+ * Returns the directory containing it, or undefined if none is found.
33
+ */
34
+ export function findGitRoot(start) {
35
+ let dir = path.resolve(start);
36
+ for (;;) {
37
+ if (existsSync(path.join(dir, ".git")))
38
+ return dir;
39
+ const parent = path.dirname(dir);
40
+ if (parent === dir)
41
+ return undefined;
42
+ dir = parent;
43
+ }
44
+ }
45
+ /**
46
+ * Resolve the memory root using the precedence:
47
+ * 1. `--root` flag
48
+ * 2. `MEMORY_ROOT` env var
49
+ * 3. `<git root>/.repomem/memory`
50
+ *
51
+ * Throws if none can be resolved (no flag, no env, no git root found).
52
+ */
53
+ export function resolveMemoryRoot(options = {}) {
54
+ const { rootFlag, cwd = process.cwd(), env = process.env } = options;
55
+ if (rootFlag && rootFlag.trim() !== "")
56
+ return path.resolve(rootFlag);
57
+ const envRoot = env.MEMORY_ROOT;
58
+ if (envRoot && envRoot.trim() !== "")
59
+ return path.resolve(envRoot);
60
+ const gitRoot = findGitRoot(cwd);
61
+ if (gitRoot)
62
+ return path.join(gitRoot, ".repomem", "memory");
63
+ throw new Error("Could not resolve a memory root: pass --root, set MEMORY_ROOT, or run inside a git repository.");
64
+ }
65
+ // ---------------------------------------------------------------------------
66
+ // Slug generation (option A: ASCII, diacritics stripped)
67
+ // ---------------------------------------------------------------------------
68
+ const SLUG_MAX_LENGTH = 80;
69
+ /**
70
+ * Convert a title into an ASCII, URL-safe slug.
71
+ * Strips diacritics (á -> a), lowercases, replaces non-alphanumerics with
72
+ * single hyphens, trims hyphens, and caps length.
73
+ */
74
+ export function slugify(title) {
75
+ return title
76
+ .normalize("NFKD")
77
+ .replace(/[\u0300-\u036f]/g, "")
78
+ .toLowerCase()
79
+ .replace(/[^a-z0-9]+/g, "-")
80
+ .replace(/^-+|-+$/g, "")
81
+ .slice(0, SLUG_MAX_LENGTH)
82
+ .replace(/-+$/g, "");
83
+ }
84
+ /**
85
+ * Build a slug that does not collide with `existing`. Appends `-2`, `-3`, …
86
+ * until unique. Returns the input slug when there is no collision.
87
+ */
88
+ export function uniqueSlug(slug, existing) {
89
+ if (!existing.has(slug))
90
+ return slug;
91
+ let n = 2;
92
+ while (existing.has(`${slug}-${n}`))
93
+ n++;
94
+ return `${slug}-${n}`;
95
+ }
96
+ // ---------------------------------------------------------------------------
97
+ // Reading
98
+ // ---------------------------------------------------------------------------
99
+ function typeDir(root, type) {
100
+ return path.join(root, TYPE_DIRS[type]);
101
+ }
102
+ /** The slug (id) is the file name without its `.md` extension. */
103
+ function slugFromFile(file) {
104
+ return path.basename(file, ".md");
105
+ }
106
+ /**
107
+ * Parse a single entry file. Returns the entry, or undefined if the file is
108
+ * malformed (invalid frontmatter, wrong type for its folder). Malformed files
109
+ * are reported to stderr and skipped rather than throwing.
110
+ */
111
+ export async function readEntryFile(file, expectedType) {
112
+ let raw;
113
+ try {
114
+ raw = await fs.readFile(file, "utf8");
115
+ }
116
+ catch (err) {
117
+ warn(`could not read ${file}: ${err.message}`);
118
+ return undefined;
119
+ }
120
+ let parsed;
121
+ try {
122
+ parsed = matter(raw);
123
+ }
124
+ catch (err) {
125
+ warn(`invalid frontmatter in ${file}: ${err.message}`);
126
+ return undefined;
127
+ }
128
+ const result = frontmatterSchema.safeParse(parsed.data);
129
+ if (!result.success) {
130
+ const issues = result.error.issues
131
+ .map((i) => `${i.path.join(".") || "(root)"}: ${i.message}`)
132
+ .join("; ");
133
+ warn(`skipping ${file}: ${issues}`);
134
+ return undefined;
135
+ }
136
+ const frontmatter = result.data;
137
+ if (expectedType && frontmatter.type !== expectedType) {
138
+ warn(`skipping ${file}: declared type "${frontmatter.type}" does not match folder "${TYPE_DIRS[expectedType]}"`);
139
+ return undefined;
140
+ }
141
+ return {
142
+ id: slugFromFile(file),
143
+ frontmatter,
144
+ body: parsed.content.trim(),
145
+ path: file,
146
+ };
147
+ }
148
+ /**
149
+ * Load all valid entries under the memory root. Missing type folders are
150
+ * treated as empty. Malformed entries are skipped (reported to stderr).
151
+ */
152
+ export async function loadEntries(root) {
153
+ const entries = [];
154
+ for (const type of ENTRY_TYPES) {
155
+ const dir = typeDir(root, type);
156
+ let files;
157
+ try {
158
+ files = await fs.readdir(dir);
159
+ }
160
+ catch {
161
+ continue;
162
+ }
163
+ for (const name of files) {
164
+ if (!name.endsWith(".md"))
165
+ continue;
166
+ const entry = await readEntryFile(path.join(dir, name), type);
167
+ if (entry)
168
+ entries.push(entry);
169
+ }
170
+ }
171
+ return entries;
172
+ }
173
+ /** Find a single entry by id across all type folders, or undefined. */
174
+ export async function findEntryById(root, id) {
175
+ for (const type of ENTRY_TYPES) {
176
+ const file = path.join(typeDir(root, type), `${id}.md`);
177
+ if (existsSync(file)) {
178
+ return readEntryFile(file, type);
179
+ }
180
+ }
181
+ return undefined;
182
+ }
183
+ /** Collect the set of existing slugs for a given type (for collision checks). */
184
+ export async function existingSlugs(root, type) {
185
+ const dir = typeDir(root, type);
186
+ try {
187
+ const files = await fs.readdir(dir);
188
+ return new Set(files.filter((f) => f.endsWith(".md")).map((f) => slugFromFile(f)));
189
+ }
190
+ catch {
191
+ return new Set();
192
+ }
193
+ }
194
+ // ---------------------------------------------------------------------------
195
+ // Serialization
196
+ // ---------------------------------------------------------------------------
197
+ /**
198
+ * Serialize frontmatter + body back into a markdown file string.
199
+ * Keys are written in a stable, human-friendly order; optional keys are only
200
+ * emitted when present.
201
+ */
202
+ export function serializeEntry(frontmatter, body) {
203
+ const data = {
204
+ type: frontmatter.type,
205
+ title: frontmatter.title,
206
+ status: frontmatter.status,
207
+ date: frontmatter.date,
208
+ scope: frontmatter.scope,
209
+ };
210
+ if (frontmatter.author !== undefined)
211
+ data.author = frontmatter.author;
212
+ data.tags = frontmatter.tags;
213
+ data.related_paths = frontmatter.related_paths;
214
+ if (frontmatter.supersedes !== undefined)
215
+ data.supersedes = frontmatter.supersedes;
216
+ if (frontmatter.last_verified !== undefined) {
217
+ data.last_verified = frontmatter.last_verified;
218
+ }
219
+ if (frontmatter.deprecated_reason !== undefined) {
220
+ data.deprecated_reason = frontmatter.deprecated_reason;
221
+ }
222
+ if (frontmatter.superseded_by !== undefined) {
223
+ data.superseded_by = frontmatter.superseded_by;
224
+ }
225
+ return matter.stringify(body.trim() + "\n", data);
226
+ }
227
+ /**
228
+ * Write a new entry to disk. Validates frontmatter, resolves a unique slug
229
+ * from the title, creates the type folder if needed, and NEVER overwrites an
230
+ * existing file. Returns the id and path written.
231
+ *
232
+ * @throws if frontmatter is invalid or the resolved file already exists.
233
+ */
234
+ export async function writeEntry(root, input, body) {
235
+ const frontmatter = frontmatterSchema.parse(input);
236
+ const dir = typeDir(root, frontmatter.type);
237
+ await fs.mkdir(dir, { recursive: true });
238
+ const taken = await existingSlugs(root, frontmatter.type);
239
+ const slug = uniqueSlug(slugify(frontmatter.title), taken);
240
+ const file = path.join(dir, `${slug}.md`);
241
+ if (existsSync(file)) {
242
+ throw new Error(`refusing to overwrite existing entry: ${file}`);
243
+ }
244
+ await fs.writeFile(file, serializeEntry(frontmatter, body), {
245
+ encoding: "utf8",
246
+ flag: "wx",
247
+ });
248
+ return { id: slug, path: file };
249
+ }
250
+ /**
251
+ * Propose a new entry. ALWAYS writes `status: draft`. The date defaults to
252
+ * today (UTC). Rejects a title whose slug already exists within the same type
253
+ * (a likely duplicate the human should resolve) rather than auto-suffixing.
254
+ *
255
+ * @throws if the derived slug collides with an existing entry of that type.
256
+ */
257
+ export async function proposeEntry(root, input) {
258
+ const slug = slugify(input.title);
259
+ if (slug === "") {
260
+ throw new Error("title does not produce a usable slug");
261
+ }
262
+ const taken = await existingSlugs(root, input.type);
263
+ if (taken.has(slug)) {
264
+ throw new Error(`a ${input.type} entry with id "${slug}" already exists; refusing to create a duplicate`);
265
+ }
266
+ const frontmatter = frontmatterSchema.parse({
267
+ type: input.type,
268
+ title: input.title,
269
+ status: "draft", // forced, never taken from caller
270
+ date: todayIso(),
271
+ author: input.author,
272
+ tags: input.tags ?? [],
273
+ related_paths: input.related_paths ?? [],
274
+ supersedes: input.supersedes,
275
+ });
276
+ const dir = typeDir(root, input.type);
277
+ await fs.mkdir(dir, { recursive: true });
278
+ const file = path.join(dir, `${slug}.md`);
279
+ if (existsSync(file)) {
280
+ throw new Error(`refusing to overwrite existing entry: ${file}`);
281
+ }
282
+ await fs.writeFile(file, serializeEntry(frontmatter, input.body), {
283
+ encoding: "utf8",
284
+ flag: "wx",
285
+ });
286
+ return { id: slug, path: file };
287
+ }
288
+ // ---------------------------------------------------------------------------
289
+ // Deprecate (rewrite an existing entry in place)
290
+ // ---------------------------------------------------------------------------
291
+ /**
292
+ * Mark an existing entry as deprecated, recording the reason (and optionally
293
+ * the id that supersedes it) in the frontmatter. Rewrites the file in place,
294
+ * preserving the body and other fields.
295
+ *
296
+ * @throws if the entry does not exist.
297
+ */
298
+ export async function deprecateEntry(root, id, reason, supersededBy) {
299
+ if (reason.trim() === "") {
300
+ throw new Error("a deprecation reason is required");
301
+ }
302
+ const entry = await findEntryById(root, id);
303
+ if (!entry) {
304
+ throw new Error(`no memory entry found with id "${id}"`);
305
+ }
306
+ const updated = frontmatterSchema.parse({
307
+ ...entry.frontmatter,
308
+ status: "deprecated",
309
+ deprecated_reason: reason,
310
+ superseded_by: supersededBy,
311
+ });
312
+ await fs.writeFile(entry.path, serializeEntry(updated, entry.body), "utf8");
313
+ return { id: entry.id, path: entry.path };
314
+ }
315
+ // ---------------------------------------------------------------------------
316
+ // memory_for_path (match related_paths globs against a file path)
317
+ // ---------------------------------------------------------------------------
318
+ const STATUS_PRIORITY = {
319
+ accepted: 0,
320
+ draft: 1,
321
+ deprecated: 2,
322
+ };
323
+ /**
324
+ * Return entries whose `related_paths` globs match the given file path.
325
+ * Deprecated entries are excluded. Results are ordered accepted-before-draft,
326
+ * then by date descending.
327
+ *
328
+ * The input path is normalized to posix separators so globs behave the same
329
+ * across platforms.
330
+ */
331
+ export async function entriesForPath(root, filePath) {
332
+ const normalized = filePath.split(path.sep).join("/").replace(/^\.?\/+/, "");
333
+ const entries = await loadEntries(root);
334
+ const matched = entries.filter((e) => {
335
+ if (e.frontmatter.status === "deprecated")
336
+ return false;
337
+ if (e.frontmatter.related_paths.length === 0)
338
+ return false;
339
+ return e.frontmatter.related_paths.some((glob) => picomatch.isMatch(normalized, glob, { dot: true }));
340
+ });
341
+ matched.sort((a, b) => {
342
+ const pa = STATUS_PRIORITY[a.frontmatter.status] ?? 99;
343
+ const pb = STATUS_PRIORITY[b.frontmatter.status] ?? 99;
344
+ if (pa !== pb)
345
+ return pa - pb;
346
+ return b.frontmatter.date.localeCompare(a.frontmatter.date);
347
+ });
348
+ return matched;
349
+ }
350
+ //# sourceMappingURL=store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.js","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,42 @@
1
+ /**
2
+ * validate.ts — pure validation logic for `repomem validate`.
3
+ *
4
+ * Checks performed:
5
+ * 1. Every `.md` entry under the memory root has valid frontmatter.
6
+ * 2. Every `supersedes` reference points at an existing entry id.
7
+ * 3. No obvious secrets (API keys, tokens, private keys) appear in any entry.
8
+ *
9
+ * This module is I/O-light and returns a structured report; the CLI decides
10
+ * the process exit code. Nothing here writes to stdout.
11
+ */
12
+ export type Severity = "error" | "warning";
13
+ export interface Finding {
14
+ severity: Severity;
15
+ file: string;
16
+ line?: number;
17
+ rule: string;
18
+ message: string;
19
+ }
20
+ export interface ValidateReport {
21
+ findings: Finding[];
22
+ checked: number;
23
+ ok: boolean;
24
+ }
25
+ /**
26
+ * High-confidence secret patterns. Tuned to minimize false positives; this is
27
+ * a safety net, not a replacement for a dedicated secret scanner.
28
+ */
29
+ export declare const SECRET_PATTERNS: Array<{
30
+ rule: string;
31
+ re: RegExp;
32
+ }>;
33
+ /** Scan a block of text for secrets, returning one finding per match line. */
34
+ export declare function scanSecrets(text: string, file: string): Finding[];
35
+ /**
36
+ * Validate every entry under `root`. Returns a structured report. `ok` is true
37
+ * only when there are no error-severity findings.
38
+ */
39
+ export declare function validateRoot(root: string): Promise<ValidateReport>;
40
+ /** Format a report as human-readable lines (for stderr/stdout in the CLI). */
41
+ export declare function formatReport(report: ValidateReport, root: string): string;
42
+ //# sourceMappingURL=validate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../src/validate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAYH,MAAM,MAAM,QAAQ,GAAG,OAAO,GAAG,SAAS,CAAC;AAE3C,MAAM,WAAW,OAAO;IACtB,QAAQ,EAAE,QAAQ,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,EAAE,EAAE,OAAO,CAAC;CACb;AAED;;;GAGG;AACH,eAAO,MAAM,eAAe,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,CAW/D,CAAC;AAEF,8EAA8E;AAC9E,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,EAAE,CAiBjE;AA4BD;;;GAGG;AACH,wBAAsB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,CA+ExE;AAED,8EAA8E;AAC9E,wBAAgB,YAAY,CAAC,MAAM,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAczE"}