@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.
- package/LICENSE +21 -0
- package/README.md +204 -2
- package/assets/kiro/hooks/repomem-validate.sh +10 -0
- package/assets/kiro/skills/repomem-distill/SKILL.md +56 -0
- package/assets/kiro/skills/repomem-memory/SKILL.md +41 -0
- package/dist/cli.d.ts +11 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +183 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +35 -0
- package/dist/index.js.map +1 -0
- package/dist/schema.d.ts +97 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +86 -0
- package/dist/schema.js.map +1 -0
- package/dist/search.d.ts +55 -0
- package/dist/search.d.ts.map +1 -0
- package/dist/search.js +160 -0
- package/dist/search.js.map +1 -0
- package/dist/server.d.ts +26 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +176 -0
- package/dist/server.js.map +1 -0
- package/dist/setup/spec.d.ts +11 -0
- package/dist/setup/spec.d.ts.map +1 -0
- package/dist/setup/spec.js +44 -0
- package/dist/setup/spec.js.map +1 -0
- package/dist/store.d.ts +119 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/store.js +350 -0
- package/dist/store.js.map +1 -0
- package/dist/validate.d.ts +42 -0
- package/dist/validate.d.ts.map +1 -0
- package/dist/validate.js +164 -0
- package/dist/validate.js.map +1 -0
- package/package.json +66 -4
- package/templates/context.md +25 -0
- package/templates/convention.md +16 -0
- package/templates/decision.md +20 -0
- package/templates/learning.md +16 -0
- 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"}
|
package/dist/store.d.ts
ADDED
|
@@ -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":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,QAAQ,IAAI,EAAE,EAAE,MAAM,SAAS,CAAC;AACzC,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,MAAM,MAAM,aAAa,CAAC;AACjC,OAAO,SAAS,MAAM,WAAW,CAAC;AAClC,OAAO,EACL,iBAAiB,EACjB,SAAS,EACT,WAAW,GAKZ,MAAM,aAAa,CAAC;AAErB,uEAAuE;AACvE,SAAS,IAAI,CAAC,OAAe;IAC3B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,aAAa,OAAO,IAAI,CAAC,CAAC;AACjD,CAAC;AAED,2CAA2C;AAC3C,MAAM,UAAU,QAAQ;IACtB,OAAO,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;AAC/C,CAAC;AAeD;;;GAGG;AACH,MAAM,UAAU,WAAW,CAAC,KAAa;IACvC,IAAI,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IAC9B,SAAS,CAAC;QACR,IAAI,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;YAAE,OAAO,GAAG,CAAC;QACnD,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QACjC,IAAI,MAAM,KAAK,GAAG;YAAE,OAAO,SAAS,CAAC;QACrC,GAAG,GAAG,MAAM,CAAC;IACf,CAAC;AACH,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,iBAAiB,CAAC,UAA8B,EAAE;IAChE,MAAM,EAAE,QAAQ,EAAE,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE,EAAE,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE,GAAG,OAAO,CAAC;IAErE,IAAI,QAAQ,IAAI,QAAQ,CAAC,IAAI,EAAE,KAAK,EAAE;QAAE,OAAO,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IAEtE,MAAM,OAAO,GAAG,GAAG,CAAC,WAAW,CAAC;IAChC,IAAI,OAAO,IAAI,OAAO,CAAC,IAAI,EAAE,KAAK,EAAE;QAAE,OAAO,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IAEnE,MAAM,OAAO,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC;IACjC,IAAI,OAAO;QAAE,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,UAAU,EAAE,QAAQ,CAAC,CAAC;IAE7D,MAAM,IAAI,KAAK,CACb,gGAAgG,CACjG,CAAC;AACJ,CAAC;AAED,8EAA8E;AAC9E,yDAAyD;AACzD,8EAA8E;AAE9E,MAAM,eAAe,GAAG,EAAE,CAAC;AAE3B;;;;GAIG;AACH,MAAM,UAAU,OAAO,CAAC,KAAa;IACnC,OAAO,KAAK;SACT,SAAS,CAAC,MAAM,CAAC;SACjB,OAAO,CAAC,kBAAkB,EAAE,EAAE,CAAC;SAC/B,WAAW,EAAE;SACb,OAAO,CAAC,aAAa,EAAE,GAAG,CAAC;SAC3B,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC;SACvB,KAAK,CAAC,CAAC,EAAE,eAAe,CAAC;SACzB,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;AACzB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,UAAU,CAAC,IAAY,EAAE,QAA6B;IACpE,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IACrC,IAAI,CAAC,GAAG,CAAC,CAAC;IACV,OAAO,QAAQ,CAAC,GAAG,CAAC,GAAG,IAAI,IAAI,CAAC,EAAE,CAAC;QAAE,CAAC,EAAE,CAAC;IACzC,OAAO,GAAG,IAAI,IAAI,CAAC,EAAE,CAAC;AACxB,CAAC;AAED,8EAA8E;AAC9E,UAAU;AACV,8EAA8E;AAE9E,SAAS,OAAO,CAAC,IAAY,EAAE,IAAe;IAC5C,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC;AAC1C,CAAC;AAED,kEAAkE;AAClE,SAAS,YAAY,CAAC,IAAY;IAChC,OAAO,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;AACpC,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,IAAY,EACZ,YAAwB;IAExB,IAAI,GAAW,CAAC;IAChB,IAAI,CAAC;QACH,GAAG,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IACxC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,CAAC,kBAAkB,IAAI,KAAM,GAAa,CAAC,OAAO,EAAE,CAAC,CAAC;QAC1D,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,IAAI,MAAqC,CAAC;IAC1C,IAAI,CAAC;QACH,MAAM,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;IACvB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,CAAC,0BAA0B,IAAI,KAAM,GAAa,CAAC,OAAO,EAAE,CAAC,CAAC;QAClE,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,MAAM,MAAM,GAAG,iBAAiB,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IACxD,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QACpB,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,MAAM;aAC/B,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,QAAQ,KAAK,CAAC,CAAC,OAAO,EAAE,CAAC;aAC3D,IAAI,CAAC,IAAI,CAAC,CAAC;QACd,IAAI,CAAC,YAAY,IAAI,KAAK,MAAM,EAAE,CAAC,CAAC;QACpC,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,MAAM,WAAW,GAAG,MAAM,CAAC,IAAI,CAAC;IAEhC,IAAI,YAAY,IAAI,WAAW,CAAC,IAAI,KAAK,YAAY,EAAE,CAAC;QACtD,IAAI,CACF,YAAY,IAAI,oBAAoB,WAAW,CAAC,IAAI,4BAA4B,SAAS,CAAC,YAAY,CAAC,GAAG,CAC3G,CAAC;QACF,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,OAAO;QACL,EAAE,EAAE,YAAY,CAAC,IAAI,CAAC;QACtB,WAAW;QACX,IAAI,EAAE,MAAM,CAAC,OAAO,CAAC,IAAI,EAAE;QAC3B,IAAI,EAAE,IAAI;KACX,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,IAAY;IAC5C,MAAM,OAAO,GAAkB,EAAE,CAAC;IAElC,KAAK,MAAM,IAAI,IAAI,WAAW,EAAE,CAAC;QAC/B,MAAM,GAAG,GAAG,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;QAChC,IAAI,KAAe,CAAC;QACpB,IAAI,CAAC;YACH,KAAK,GAAG,MAAM,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QAChC,CAAC;QAAC,MAAM,CAAC;YACP,SAAS;QACX,CAAC;QAED,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC;gBAAE,SAAS;YACpC,MAAM,KAAK,GAAG,MAAM,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,EAAE,IAAI,CAAC,CAAC;YAC9D,IAAI,KAAK;gBAAE,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACjC,CAAC;IACH,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,uEAAuE;AACvE,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,IAAY,EACZ,EAAU;IAEV,KAAK,MAAM,IAAI,IAAI,WAAW,EAAE,CAAC;QAC/B,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE,GAAG,EAAE,KAAK,CAAC,CAAC;QACxD,IAAI,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;YACrB,OAAO,aAAa,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;QACnC,CAAC;IACH,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,iFAAiF;AACjF,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,IAAY,EACZ,IAAe;IAEf,MAAM,GAAG,GAAG,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IAChC,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,MAAM,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QACpC,OAAO,IAAI,GAAG,CACZ,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CACnE,CAAC;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,GAAG,EAAE,CAAC;IACnB,CAAC;AACH,CAAC;AAED,8EAA8E;AAC9E,gBAAgB;AAChB,8EAA8E;AAE9E;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAAC,WAAwB,EAAE,IAAY;IACnE,MAAM,IAAI,GAA4B;QACpC,IAAI,EAAE,WAAW,CAAC,IAAI;QACtB,KAAK,EAAE,WAAW,CAAC,KAAK;QACxB,MAAM,EAAE,WAAW,CAAC,MAAM;QAC1B,IAAI,EAAE,WAAW,CAAC,IAAI;QACtB,KAAK,EAAE,WAAW,CAAC,KAAK;KACzB,CAAC;IACF,IAAI,WAAW,CAAC,MAAM,KAAK,SAAS;QAAE,IAAI,CAAC,MAAM,GAAG,WAAW,CAAC,MAAM,CAAC;IACvE,IAAI,CAAC,IAAI,GAAG,WAAW,CAAC,IAAI,CAAC;IAC7B,IAAI,CAAC,aAAa,GAAG,WAAW,CAAC,aAAa,CAAC;IAC/C,IAAI,WAAW,CAAC,UAAU,KAAK,SAAS;QAAE,IAAI,CAAC,UAAU,GAAG,WAAW,CAAC,UAAU,CAAC;IACnF,IAAI,WAAW,CAAC,aAAa,KAAK,SAAS,EAAE,CAAC;QAC5C,IAAI,CAAC,aAAa,GAAG,WAAW,CAAC,aAAa,CAAC;IACjD,CAAC;IACD,IAAI,WAAW,CAAC,iBAAiB,KAAK,SAAS,EAAE,CAAC;QAChD,IAAI,CAAC,iBAAiB,GAAG,WAAW,CAAC,iBAAiB,CAAC;IACzD,CAAC;IACD,IAAI,WAAW,CAAC,aAAa,KAAK,SAAS,EAAE,CAAC;QAC5C,IAAI,CAAC,aAAa,GAAG,WAAW,CAAC,aAAa,CAAC;IACjD,CAAC;IAED,OAAO,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,IAAI,EAAE,IAAI,CAAC,CAAC;AACpD,CAAC;AAWD;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAC9B,IAAY,EACZ,KAAuB,EACvB,IAAY;IAEZ,MAAM,WAAW,GAAG,iBAAiB,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IACnD,MAAM,GAAG,GAAG,OAAO,CAAC,IAAI,EAAE,WAAW,CAAC,IAAI,CAAC,CAAC;IAC5C,MAAM,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAEzC,MAAM,KAAK,GAAG,MAAM,aAAa,CAAC,IAAI,EAAE,WAAW,CAAC,IAAI,CAAC,CAAC;IAC1D,MAAM,IAAI,GAAG,UAAU,CAAC,OAAO,CAAC,WAAW,CAAC,KAAK,CAAC,EAAE,KAAK,CAAC,CAAC;IAC3D,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,KAAK,CAAC,CAAC;IAE1C,IAAI,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;QACrB,MAAM,IAAI,KAAK,CAAC,yCAAyC,IAAI,EAAE,CAAC,CAAC;IACnE,CAAC;IAED,MAAM,EAAE,CAAC,SAAS,CAAC,IAAI,EAAE,cAAc,CAAC,WAAW,EAAE,IAAI,CAAC,EAAE;QAC1D,QAAQ,EAAE,MAAM;QAChB,IAAI,EAAE,IAAI;KACX,CAAC,CAAC;IAEH,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;AAClC,CAAC;AAgBD;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,IAAY,EACZ,KAAmB;IAEnB,MAAM,IAAI,GAAG,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IAClC,IAAI,IAAI,KAAK,EAAE,EAAE,CAAC;QAChB,MAAM,IAAI,KAAK,CAAC,sCAAsC,CAAC,CAAC;IAC1D,CAAC;IAED,MAAM,KAAK,GAAG,MAAM,aAAa,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;IACpD,IAAI,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;QACpB,MAAM,IAAI,KAAK,CACb,KAAK,KAAK,CAAC,IAAI,mBAAmB,IAAI,kDAAkD,CACzF,CAAC;IACJ,CAAC;IAED,MAAM,WAAW,GAAG,iBAAiB,CAAC,KAAK,CAAC;QAC1C,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,KAAK,EAAE,KAAK,CAAC,KAAK;QAClB,MAAM,EAAE,OAAO,EAAE,kCAAkC;QACnD,IAAI,EAAE,QAAQ,EAAE;QAChB,MAAM,EAAE,KAAK,CAAC,MAAM;QACpB,IAAI,EAAE,KAAK,CAAC,IAAI,IAAI,EAAE;QACtB,aAAa,EAAE,KAAK,CAAC,aAAa,IAAI,EAAE;QACxC,UAAU,EAAE,KAAK,CAAC,UAAU;KAC7B,CAAC,CAAC;IAEH,MAAM,GAAG,GAAG,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;IACtC,MAAM,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACzC,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,KAAK,CAAC,CAAC;IAE1C,IAAI,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;QACrB,MAAM,IAAI,KAAK,CAAC,yCAAyC,IAAI,EAAE,CAAC,CAAC;IACnE,CAAC;IAED,MAAM,EAAE,CAAC,SAAS,CAAC,IAAI,EAAE,cAAc,CAAC,WAAW,EAAE,KAAK,CAAC,IAAI,CAAC,EAAE;QAChE,QAAQ,EAAE,MAAM;QAChB,IAAI,EAAE,IAAI;KACX,CAAC,CAAC;IAEH,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;AAClC,CAAC;AAED,8EAA8E;AAC9E,iDAAiD;AACjD,8EAA8E;AAE9E;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,IAAY,EACZ,EAAU,EACV,MAAc,EACd,YAAqB;IAErB,IAAI,MAAM,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;QACzB,MAAM,IAAI,KAAK,CAAC,kCAAkC,CAAC,CAAC;IACtD,CAAC;IAED,MAAM,KAAK,GAAG,MAAM,aAAa,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;IAC5C,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,IAAI,KAAK,CAAC,kCAAkC,EAAE,GAAG,CAAC,CAAC;IAC3D,CAAC;IAED,MAAM,OAAO,GAAG,iBAAiB,CAAC,KAAK,CAAC;QACtC,GAAG,KAAK,CAAC,WAAW;QACpB,MAAM,EAAE,YAAY;QACpB,iBAAiB,EAAE,MAAM;QACzB,aAAa,EAAE,YAAY;KAC5B,CAAC,CAAC;IAEH,MAAM,EAAE,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,EAAE,cAAc,CAAC,OAAO,EAAE,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,CAAC;IAC5E,OAAO,EAAE,EAAE,EAAE,KAAK,CAAC,EAAE,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC;AAC5C,CAAC;AAED,8EAA8E;AAC9E,kEAAkE;AAClE,8EAA8E;AAE9E,MAAM,eAAe,GAA2B;IAC9C,QAAQ,EAAE,CAAC;IACX,KAAK,EAAE,CAAC;IACR,UAAU,EAAE,CAAC;CACd,CAAC;AAEF;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,IAAY,EACZ,QAAgB;IAEhB,MAAM,UAAU,GAAG,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC;IAC7E,MAAM,OAAO,GAAG,MAAM,WAAW,CAAC,IAAI,CAAC,CAAC;IAExC,MAAM,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE;QACnC,IAAI,CAAC,CAAC,WAAW,CAAC,MAAM,KAAK,YAAY;YAAE,OAAO,KAAK,CAAC;QACxD,IAAI,CAAC,CAAC,WAAW,CAAC,aAAa,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,KAAK,CAAC;QAC3D,OAAO,CAAC,CAAC,WAAW,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAC/C,SAAS,CAAC,OAAO,CAAC,UAAU,EAAE,IAAI,EAAE,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC,CACnD,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;QACpB,MAAM,EAAE,GAAG,eAAe,CAAC,CAAC,CAAC,WAAW,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;QACvD,MAAM,EAAE,GAAG,eAAe,CAAC,CAAC,CAAC,WAAW,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;QACvD,IAAI,EAAE,KAAK,EAAE;YAAE,OAAO,EAAE,GAAG,EAAE,CAAC;QAC9B,OAAO,CAAC,CAAC,WAAW,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;IAC9D,CAAC,CAAC,CAAC;IAEH,OAAO,OAAO,CAAC;AACjB,CAAC"}
|
|
@@ -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"}
|