@ebay-dt/bonfire-context 0.1.0-alpha.4

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 (67) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +17 -0
  3. package/dist/discover.d.ts +9 -0
  4. package/dist/discover.d.ts.map +1 -0
  5. package/dist/discover.js +103 -0
  6. package/dist/discover.js.map +1 -0
  7. package/dist/errors.d.ts +5 -0
  8. package/dist/errors.d.ts.map +1 -0
  9. package/dist/errors.js +9 -0
  10. package/dist/errors.js.map +1 -0
  11. package/dist/index.d.ts +10 -0
  12. package/dist/index.d.ts.map +1 -0
  13. package/dist/index.js +9 -0
  14. package/dist/index.js.map +1 -0
  15. package/dist/install.d.ts +33 -0
  16. package/dist/install.d.ts.map +1 -0
  17. package/dist/install.js +488 -0
  18. package/dist/install.js.map +1 -0
  19. package/dist/internal/filesystem.d.ts +5 -0
  20. package/dist/internal/filesystem.d.ts.map +1 -0
  21. package/dist/internal/filesystem.js +82 -0
  22. package/dist/internal/filesystem.js.map +1 -0
  23. package/dist/mcp.d.ts +26 -0
  24. package/dist/mcp.d.ts.map +1 -0
  25. package/dist/mcp.js +178 -0
  26. package/dist/mcp.js.map +1 -0
  27. package/dist/migration.d.ts +23 -0
  28. package/dist/migration.d.ts.map +1 -0
  29. package/dist/migration.js +206 -0
  30. package/dist/migration.js.map +1 -0
  31. package/dist/paths.d.ts +58 -0
  32. package/dist/paths.d.ts.map +1 -0
  33. package/dist/paths.js +191 -0
  34. package/dist/paths.js.map +1 -0
  35. package/dist/prototype-project.d.ts +7 -0
  36. package/dist/prototype-project.d.ts.map +1 -0
  37. package/dist/prototype-project.js +38 -0
  38. package/dist/prototype-project.js.map +1 -0
  39. package/dist/registry.d.ts +36 -0
  40. package/dist/registry.d.ts.map +1 -0
  41. package/dist/registry.js +189 -0
  42. package/dist/registry.js.map +1 -0
  43. package/dist/render.d.ts +15 -0
  44. package/dist/render.d.ts.map +1 -0
  45. package/dist/render.js +64 -0
  46. package/dist/render.js.map +1 -0
  47. package/dist/root-project.d.ts +11 -0
  48. package/dist/root-project.d.ts.map +1 -0
  49. package/dist/root-project.js +152 -0
  50. package/dist/root-project.js.map +1 -0
  51. package/dist/skills.d.ts +13 -0
  52. package/dist/skills.d.ts.map +1 -0
  53. package/dist/skills.js +170 -0
  54. package/dist/skills.js.map +1 -0
  55. package/dist/sync.d.ts +23 -0
  56. package/dist/sync.d.ts.map +1 -0
  57. package/dist/sync.js +232 -0
  58. package/dist/sync.js.map +1 -0
  59. package/dist/template.d.ts +18 -0
  60. package/dist/template.d.ts.map +1 -0
  61. package/dist/template.js +114 -0
  62. package/dist/template.js.map +1 -0
  63. package/dist/types.d.ts +5 -0
  64. package/dist/types.d.ts.map +1 -0
  65. package/dist/types.js +2 -0
  66. package/dist/types.js.map +1 -0
  67. package/package.json +44 -0
package/dist/paths.js ADDED
@@ -0,0 +1,191 @@
1
+ import { lstatSync } from "node:fs";
2
+ import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
3
+ export const DEFAULT_PROTOTYPE_ROOT = join("src", "app", "(prototypes)");
4
+ export const PROTOTYPE_METADATA_FILE = "metadata.json";
5
+ const CONTEXT_DIRECTORY = "context";
6
+ const ROOT_INSTRUCTIONS_FILE = "ROOT.md";
7
+ const PROTOTYPE_INSTRUCTIONS_FILE = "PROTOTYPE.md";
8
+ const MCP_FILE = "mcp.json";
9
+ const SETTINGS_FILE = "settings.json";
10
+ const SKILLS_DIRECTORY = "skills";
11
+ export class ContextPathError extends Error {
12
+ path;
13
+ constructor(path, message) {
14
+ super(`Invalid context path at ${path}: ${message}`);
15
+ this.name = "ContextPathError";
16
+ this.path = path;
17
+ }
18
+ }
19
+ function requirePath(value, label) {
20
+ if (typeof value !== "string" || value.trim().length === 0) {
21
+ throw new ContextPathError(label, "must be a nonempty path");
22
+ }
23
+ return value;
24
+ }
25
+ function resolveRepositoryRoot(value) {
26
+ return resolve(requirePath(value, "repositoryRoot"));
27
+ }
28
+ function resolveChildPath(root, value, label, defaultPath) {
29
+ const path = value === undefined ? defaultPath : requirePath(value, label);
30
+ return isAbsolute(path) ? resolve(path) : resolve(root, path);
31
+ }
32
+ function isContained(root, candidate, allowRoot = false) {
33
+ const pathFromRoot = relative(root, candidate);
34
+ return ((allowRoot && pathFromRoot.length === 0) ||
35
+ (pathFromRoot.length > 0 &&
36
+ pathFromRoot !== ".." &&
37
+ !pathFromRoot.startsWith(`..${sep}`) &&
38
+ !isAbsolute(pathFromRoot)));
39
+ }
40
+ function assertContained(root, candidate, label, allowRoot = false) {
41
+ if (!isContained(root, candidate, allowRoot)) {
42
+ throw new ContextPathError(label, `must be contained within ${root}`);
43
+ }
44
+ }
45
+ function assertDirectory(path, label, allowMissing) {
46
+ let stats;
47
+ try {
48
+ stats = lstatSync(path);
49
+ }
50
+ catch (error) {
51
+ if (allowMissing &&
52
+ error !== null &&
53
+ typeof error === "object" &&
54
+ "code" in error &&
55
+ error.code === "ENOENT") {
56
+ return;
57
+ }
58
+ throw new ContextPathError(label, "could not be inspected");
59
+ }
60
+ if (stats.isSymbolicLink()) {
61
+ throw new ContextPathError(label, "must not be a symbolic link");
62
+ }
63
+ if (!stats.isDirectory()) {
64
+ throw new ContextPathError(label, "must be a directory");
65
+ }
66
+ }
67
+ function assertNoSymlinkSegments(path, label) {
68
+ let current = path;
69
+ while (true) {
70
+ let stats;
71
+ try {
72
+ stats = lstatSync(current);
73
+ }
74
+ catch (error) {
75
+ if (error !== null &&
76
+ typeof error === "object" &&
77
+ "code" in error &&
78
+ error.code === "ENOENT") {
79
+ const parent = dirname(current);
80
+ if (parent === current)
81
+ return;
82
+ current = parent;
83
+ continue;
84
+ }
85
+ throw new ContextPathError(label, "could not be inspected");
86
+ }
87
+ if (stats.isSymbolicLink()) {
88
+ throw new ContextPathError(label, "must not pass through a symbolic link");
89
+ }
90
+ return;
91
+ }
92
+ }
93
+ function assertSafeFilePath(path, label) {
94
+ assertNoSymlinkSegments(path, label);
95
+ }
96
+ export function assertSafeGeneratedPath(path, label) {
97
+ assertSafeFilePath(path, label);
98
+ }
99
+ function assertSafeDirectoryPath(path, label) {
100
+ assertNoSymlinkSegments(path, label);
101
+ assertDirectory(path, label, true);
102
+ }
103
+ function buildSources(contextRoot) {
104
+ return {
105
+ rootInstructions: join(contextRoot, ROOT_INSTRUCTIONS_FILE),
106
+ prototypeInstructions: join(contextRoot, PROTOTYPE_INSTRUCTIONS_FILE),
107
+ mcp: join(contextRoot, MCP_FILE),
108
+ settings: join(contextRoot, SETTINGS_FILE),
109
+ skills: join(contextRoot, SKILLS_DIRECTORY),
110
+ };
111
+ }
112
+ function buildRootOutputs(repositoryRoot) {
113
+ return {
114
+ agents: join(repositoryRoot, "AGENTS.md"),
115
+ claude: join(repositoryRoot, "CLAUDE.md"),
116
+ mcp: join(repositoryRoot, ".mcp.json"),
117
+ settings: join(repositoryRoot, ".claude", "settings.json"),
118
+ skills: join(repositoryRoot, ".claude", "skills"),
119
+ };
120
+ }
121
+ function buildPrototypeOutputs(prototypePath) {
122
+ return {
123
+ agents: join(prototypePath, "AGENTS.md"),
124
+ claude: join(prototypePath, "CLAUDE.md"),
125
+ settings: join(prototypePath, ".claude", "settings.json"),
126
+ };
127
+ }
128
+ export function resolveContextPaths(options) {
129
+ const repositoryRoot = resolveRepositoryRoot(options?.repositoryRoot);
130
+ const prototypeRoot = resolveChildPath(repositoryRoot, options?.prototypeRoot, "prototypeRoot", DEFAULT_PROTOTYPE_ROOT);
131
+ assertContained(repositoryRoot, prototypeRoot, "prototypeRoot");
132
+ const contextRoot = join(repositoryRoot, CONTEXT_DIRECTORY);
133
+ const sources = buildSources(contextRoot);
134
+ const rootOutputs = buildRootOutputs(repositoryRoot);
135
+ for (const [label, path] of Object.entries(sources)) {
136
+ assertContained(contextRoot, path, `sources.${label}`, true);
137
+ }
138
+ for (const [label, path] of Object.entries(rootOutputs)) {
139
+ assertContained(repositoryRoot, path, `rootOutputs.${label}`, true);
140
+ }
141
+ return {
142
+ repositoryRoot,
143
+ prototypeRoot,
144
+ contextRoot,
145
+ sources,
146
+ rootOutputs,
147
+ };
148
+ }
149
+ export function resolvePrototypeContextPaths(options) {
150
+ const paths = resolveContextPaths(options);
151
+ const prototypePath = resolveChildPath(paths.prototypeRoot, options?.prototypePath, "prototypePath", paths.prototypeRoot);
152
+ assertContained(paths.prototypeRoot, prototypePath, "prototypePath");
153
+ const prototypeMetadata = join(prototypePath, PROTOTYPE_METADATA_FILE);
154
+ return {
155
+ ...paths,
156
+ prototypePath,
157
+ prototypeMetadata,
158
+ prototypeOutputs: buildPrototypeOutputs(prototypePath),
159
+ };
160
+ }
161
+ /**
162
+ * Validate the filesystem boundaries before reading or writing context.
163
+ * Missing roots are allowed so callers can produce a useful source/filesystem
164
+ * error later, but existing roots, parents, and managed targets may not be
165
+ * symbolic links.
166
+ */
167
+ export function assertSafeContextPaths(paths) {
168
+ assertSafeDirectoryPath(paths.repositoryRoot, "repositoryRoot");
169
+ assertSafeDirectoryPath(paths.prototypeRoot, "prototypeRoot");
170
+ assertSafeDirectoryPath(paths.contextRoot, "contextRoot");
171
+ for (const [label, path] of Object.entries(paths.sources)) {
172
+ assertSafeFilePath(path, `sources.${label}`);
173
+ }
174
+ for (const [label, path] of Object.entries(paths.rootOutputs)) {
175
+ if (label === "skills") {
176
+ assertSafeDirectoryPath(path, `rootOutputs.${label}`);
177
+ }
178
+ else {
179
+ assertSafeFilePath(path, `rootOutputs.${label}`);
180
+ }
181
+ }
182
+ }
183
+ export function assertSafePrototypeContextPaths(paths) {
184
+ assertSafeContextPaths(paths);
185
+ assertSafeDirectoryPath(paths.prototypePath, "prototypePath");
186
+ assertSafeFilePath(paths.prototypeMetadata, "prototypeMetadata");
187
+ for (const [label, path] of Object.entries(paths.prototypeOutputs)) {
188
+ assertSafeFilePath(path, `prototypeOutputs.${label}`);
189
+ }
190
+ }
191
+ //# sourceMappingURL=paths.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"paths.js","sourceRoot":"","sources":["../src/paths.ts"],"names":[],"mappings":"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","sourcesContent":["import { lstatSync } from \"node:fs\";\nimport { dirname, isAbsolute, join, relative, resolve, sep } from \"node:path\";\n\nexport const DEFAULT_PROTOTYPE_ROOT = join(\"src\", \"app\", \"(prototypes)\");\nexport const PROTOTYPE_METADATA_FILE = \"metadata.json\";\n\nconst CONTEXT_DIRECTORY = \"context\";\nconst ROOT_INSTRUCTIONS_FILE = \"ROOT.md\";\nconst PROTOTYPE_INSTRUCTIONS_FILE = \"PROTOTYPE.md\";\nconst MCP_FILE = \"mcp.json\";\nconst SETTINGS_FILE = \"settings.json\";\nconst SKILLS_DIRECTORY = \"skills\";\n\nexport type ContextPathOptions = Readonly<{\n repositoryRoot: string;\n prototypeRoot?: string;\n}>;\n\nexport type PrototypeContextPathOptions = Readonly<{\n repositoryRoot: string;\n prototypeRoot?: string;\n prototypePath: string;\n}>;\n\nexport type ContextSources = Readonly<{\n rootInstructions: string;\n prototypeInstructions: string;\n mcp: string;\n settings: string;\n skills: string;\n}>;\n\nexport type RootContextOutputs = Readonly<{\n agents: string;\n claude: string;\n mcp: string;\n settings: string;\n skills: string;\n}>;\n\nexport type PrototypeContextOutputs = Readonly<{\n agents: string;\n claude: string;\n settings: string;\n}>;\n\nexport type ResolvedContextPaths = Readonly<{\n repositoryRoot: string;\n prototypeRoot: string;\n contextRoot: string;\n sources: ContextSources;\n rootOutputs: RootContextOutputs;\n}>;\n\nexport type ResolvedPrototypeContextPaths = Readonly<\n ResolvedContextPaths & {\n prototypePath: string;\n prototypeMetadata: string;\n prototypeOutputs: PrototypeContextOutputs;\n }\n>;\n\nexport class ContextPathError extends Error {\n readonly path: string;\n\n constructor(path: string, message: string) {\n super(`Invalid context path at ${path}: ${message}`);\n this.name = \"ContextPathError\";\n this.path = path;\n }\n}\n\nfunction requirePath(value: unknown, label: string) {\n if (typeof value !== \"string\" || value.trim().length === 0) {\n throw new ContextPathError(label, \"must be a nonempty path\");\n }\n return value;\n}\n\nfunction resolveRepositoryRoot(value: unknown) {\n return resolve(requirePath(value, \"repositoryRoot\"));\n}\n\nfunction resolveChildPath(\n root: string,\n value: string | undefined,\n label: string,\n defaultPath: string,\n) {\n const path = value === undefined ? defaultPath : requirePath(value, label);\n return isAbsolute(path) ? resolve(path) : resolve(root, path);\n}\n\nfunction isContained(root: string, candidate: string, allowRoot = false) {\n const pathFromRoot = relative(root, candidate);\n return (\n (allowRoot && pathFromRoot.length === 0) ||\n (pathFromRoot.length > 0 &&\n pathFromRoot !== \"..\" &&\n !pathFromRoot.startsWith(`..${sep}`) &&\n !isAbsolute(pathFromRoot))\n );\n}\n\nfunction assertContained(\n root: string,\n candidate: string,\n label: string,\n allowRoot = false,\n) {\n if (!isContained(root, candidate, allowRoot)) {\n throw new ContextPathError(label, `must be contained within ${root}`);\n }\n}\n\nfunction assertDirectory(path: string, label: string, allowMissing: boolean) {\n let stats;\n try {\n stats = lstatSync(path);\n } catch (error) {\n if (\n allowMissing &&\n error !== null &&\n typeof error === \"object\" &&\n \"code\" in error &&\n error.code === \"ENOENT\"\n ) {\n return;\n }\n throw new ContextPathError(label, \"could not be inspected\");\n }\n\n if (stats.isSymbolicLink()) {\n throw new ContextPathError(label, \"must not be a symbolic link\");\n }\n if (!stats.isDirectory()) {\n throw new ContextPathError(label, \"must be a directory\");\n }\n}\n\nfunction assertNoSymlinkSegments(path: string, label: string) {\n let current = path;\n while (true) {\n let stats;\n try {\n stats = lstatSync(current);\n } catch (error) {\n if (\n error !== null &&\n typeof error === \"object\" &&\n \"code\" in error &&\n error.code === \"ENOENT\"\n ) {\n const parent = dirname(current);\n if (parent === current) return;\n current = parent;\n continue;\n }\n throw new ContextPathError(label, \"could not be inspected\");\n }\n\n if (stats.isSymbolicLink()) {\n throw new ContextPathError(\n label,\n \"must not pass through a symbolic link\",\n );\n }\n return;\n }\n}\n\nfunction assertSafeFilePath(path: string, label: string) {\n assertNoSymlinkSegments(path, label);\n}\n\nexport function assertSafeGeneratedPath(path: string, label: string) {\n assertSafeFilePath(path, label);\n}\n\nfunction assertSafeDirectoryPath(path: string, label: string) {\n assertNoSymlinkSegments(path, label);\n assertDirectory(path, label, true);\n}\n\nfunction buildSources(contextRoot: string): ContextSources {\n return {\n rootInstructions: join(contextRoot, ROOT_INSTRUCTIONS_FILE),\n prototypeInstructions: join(contextRoot, PROTOTYPE_INSTRUCTIONS_FILE),\n mcp: join(contextRoot, MCP_FILE),\n settings: join(contextRoot, SETTINGS_FILE),\n skills: join(contextRoot, SKILLS_DIRECTORY),\n };\n}\n\nfunction buildRootOutputs(repositoryRoot: string): RootContextOutputs {\n return {\n agents: join(repositoryRoot, \"AGENTS.md\"),\n claude: join(repositoryRoot, \"CLAUDE.md\"),\n mcp: join(repositoryRoot, \".mcp.json\"),\n settings: join(repositoryRoot, \".claude\", \"settings.json\"),\n skills: join(repositoryRoot, \".claude\", \"skills\"),\n };\n}\n\nfunction buildPrototypeOutputs(prototypePath: string): PrototypeContextOutputs {\n return {\n agents: join(prototypePath, \"AGENTS.md\"),\n claude: join(prototypePath, \"CLAUDE.md\"),\n settings: join(prototypePath, \".claude\", \"settings.json\"),\n };\n}\n\nexport function resolveContextPaths(\n options: ContextPathOptions,\n): ResolvedContextPaths {\n const repositoryRoot = resolveRepositoryRoot(options?.repositoryRoot);\n const prototypeRoot = resolveChildPath(\n repositoryRoot,\n options?.prototypeRoot,\n \"prototypeRoot\",\n DEFAULT_PROTOTYPE_ROOT,\n );\n assertContained(repositoryRoot, prototypeRoot, \"prototypeRoot\");\n\n const contextRoot = join(repositoryRoot, CONTEXT_DIRECTORY);\n const sources = buildSources(contextRoot);\n const rootOutputs = buildRootOutputs(repositoryRoot);\n\n for (const [label, path] of Object.entries(sources)) {\n assertContained(contextRoot, path, `sources.${label}`, true);\n }\n for (const [label, path] of Object.entries(rootOutputs)) {\n assertContained(repositoryRoot, path, `rootOutputs.${label}`, true);\n }\n\n return {\n repositoryRoot,\n prototypeRoot,\n contextRoot,\n sources,\n rootOutputs,\n };\n}\n\nexport function resolvePrototypeContextPaths(\n options: PrototypeContextPathOptions,\n): ResolvedPrototypeContextPaths {\n const paths = resolveContextPaths(options);\n const prototypePath = resolveChildPath(\n paths.prototypeRoot,\n options?.prototypePath,\n \"prototypePath\",\n paths.prototypeRoot,\n );\n assertContained(paths.prototypeRoot, prototypePath, \"prototypePath\");\n const prototypeMetadata = join(prototypePath, PROTOTYPE_METADATA_FILE);\n\n return {\n ...paths,\n prototypePath,\n prototypeMetadata,\n prototypeOutputs: buildPrototypeOutputs(prototypePath),\n };\n}\n\n/**\n * Validate the filesystem boundaries before reading or writing context.\n * Missing roots are allowed so callers can produce a useful source/filesystem\n * error later, but existing roots, parents, and managed targets may not be\n * symbolic links.\n */\nexport function assertSafeContextPaths(paths: ResolvedContextPaths) {\n assertSafeDirectoryPath(paths.repositoryRoot, \"repositoryRoot\");\n assertSafeDirectoryPath(paths.prototypeRoot, \"prototypeRoot\");\n assertSafeDirectoryPath(paths.contextRoot, \"contextRoot\");\n\n for (const [label, path] of Object.entries(paths.sources)) {\n assertSafeFilePath(path, `sources.${label}`);\n }\n for (const [label, path] of Object.entries(paths.rootOutputs)) {\n if (label === \"skills\") {\n assertSafeDirectoryPath(path, `rootOutputs.${label}`);\n } else {\n assertSafeFilePath(path, `rootOutputs.${label}`);\n }\n }\n}\n\nexport function assertSafePrototypeContextPaths(\n paths: ResolvedPrototypeContextPaths,\n) {\n assertSafeContextPaths(paths);\n assertSafeDirectoryPath(paths.prototypePath, \"prototypePath\");\n assertSafeFilePath(paths.prototypeMetadata, \"prototypeMetadata\");\n for (const [label, path] of Object.entries(paths.prototypeOutputs)) {\n assertSafeFilePath(path, `prototypeOutputs.${label}`);\n }\n}\n"]}
@@ -0,0 +1,7 @@
1
+ import type { ContextProjectionResult } from "./types.js";
2
+ export type ProjectPrototypeContextOptions = Readonly<{
3
+ repositoryRoot: string;
4
+ prototypePath: string;
5
+ }>;
6
+ export declare function projectPrototypeContext(options: ProjectPrototypeContextOptions): Promise<ContextProjectionResult>;
7
+ //# sourceMappingURL=prototype-project.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prototype-project.d.ts","sourceRoot":"","sources":["../src/prototype-project.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAC;AAI1D,MAAM,MAAM,8BAA8B,GAAG,QAAQ,CAAC;IACpD,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;CACvB,CAAC,CAAC;AAaH,wBAAsB,uBAAuB,CAC3C,OAAO,EAAE,8BAA8B,GACtC,OAAO,CAAC,uBAAuB,CAAC,CAgDlC","sourcesContent":["import { relative } from \"node:path\";\nimport {\n assertSafePrototypeContextPaths,\n resolvePrototypeContextPaths,\n type ResolvedPrototypeContextPaths,\n} from \"./paths.js\";\nimport {\n atomicWrite,\n assertWritableTarget,\n removeManagedFile,\n} from \"./internal/filesystem.js\";\nimport type { ContextProjectionResult } from \"./types.js\";\nimport { renderPrototypeMarkdown } from \"./render.js\";\nimport { renderOptionalSettings } from \"./template.js\";\n\nexport type ProjectPrototypeContextOptions = Readonly<{\n repositoryRoot: string;\n prototypePath: string;\n}>;\n\nasync function preflightTargets(paths: ResolvedPrototypeContextPaths) {\n await Promise.all([\n assertWritableTarget(paths.prototypeOutputs.agents, \"AGENTS.md\"),\n assertWritableTarget(paths.prototypeOutputs.claude, \"CLAUDE.md\"),\n assertWritableTarget(\n paths.prototypeOutputs.settings,\n \".claude/settings.json\",\n ),\n ]);\n}\n\nexport async function projectPrototypeContext(\n options: ProjectPrototypeContextOptions,\n): Promise<ContextProjectionResult> {\n const paths = resolvePrototypeContextPaths(options);\n assertSafePrototypeContextPaths(paths);\n await preflightTargets(paths);\n\n const [markdown, settings] = await Promise.all([\n renderPrototypeMarkdown(paths),\n renderOptionalSettings(paths),\n ]);\n\n const written: string[] = [];\n const removed: string[] = [];\n await atomicWrite(\n paths.prototypeOutputs.agents,\n markdown.agents,\n \"AGENTS.md\",\n );\n written.push(relative(paths.repositoryRoot, paths.prototypeOutputs.agents));\n await atomicWrite(\n paths.prototypeOutputs.claude,\n markdown.claude,\n \"CLAUDE.md\",\n );\n written.push(relative(paths.repositoryRoot, paths.prototypeOutputs.claude));\n\n if (settings === undefined) {\n if (\n await removeManagedFile(\n paths.prototypeOutputs.settings,\n \".claude/settings.json\",\n )\n ) {\n removed.push(\n relative(paths.repositoryRoot, paths.prototypeOutputs.settings),\n );\n }\n } else {\n await atomicWrite(\n paths.prototypeOutputs.settings,\n settings,\n \".claude/settings.json\",\n );\n written.push(\n relative(paths.repositoryRoot, paths.prototypeOutputs.settings),\n );\n }\n\n return { written, removed };\n}\n"]}
@@ -0,0 +1,38 @@
1
+ import { relative } from "node:path";
2
+ import { assertSafePrototypeContextPaths, resolvePrototypeContextPaths, } from "./paths.js";
3
+ import { atomicWrite, assertWritableTarget, removeManagedFile, } from "./internal/filesystem.js";
4
+ import { renderPrototypeMarkdown } from "./render.js";
5
+ import { renderOptionalSettings } from "./template.js";
6
+ async function preflightTargets(paths) {
7
+ await Promise.all([
8
+ assertWritableTarget(paths.prototypeOutputs.agents, "AGENTS.md"),
9
+ assertWritableTarget(paths.prototypeOutputs.claude, "CLAUDE.md"),
10
+ assertWritableTarget(paths.prototypeOutputs.settings, ".claude/settings.json"),
11
+ ]);
12
+ }
13
+ export async function projectPrototypeContext(options) {
14
+ const paths = resolvePrototypeContextPaths(options);
15
+ assertSafePrototypeContextPaths(paths);
16
+ await preflightTargets(paths);
17
+ const [markdown, settings] = await Promise.all([
18
+ renderPrototypeMarkdown(paths),
19
+ renderOptionalSettings(paths),
20
+ ]);
21
+ const written = [];
22
+ const removed = [];
23
+ await atomicWrite(paths.prototypeOutputs.agents, markdown.agents, "AGENTS.md");
24
+ written.push(relative(paths.repositoryRoot, paths.prototypeOutputs.agents));
25
+ await atomicWrite(paths.prototypeOutputs.claude, markdown.claude, "CLAUDE.md");
26
+ written.push(relative(paths.repositoryRoot, paths.prototypeOutputs.claude));
27
+ if (settings === undefined) {
28
+ if (await removeManagedFile(paths.prototypeOutputs.settings, ".claude/settings.json")) {
29
+ removed.push(relative(paths.repositoryRoot, paths.prototypeOutputs.settings));
30
+ }
31
+ }
32
+ else {
33
+ await atomicWrite(paths.prototypeOutputs.settings, settings, ".claude/settings.json");
34
+ written.push(relative(paths.repositoryRoot, paths.prototypeOutputs.settings));
35
+ }
36
+ return { written, removed };
37
+ }
38
+ //# sourceMappingURL=prototype-project.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prototype-project.js","sourceRoot":"","sources":["../src/prototype-project.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AACrC,OAAO,EACL,+BAA+B,EAC/B,4BAA4B,GAE7B,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,WAAW,EACX,oBAAoB,EACpB,iBAAiB,GAClB,MAAM,0BAA0B,CAAC;AAElC,OAAO,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC;AACtD,OAAO,EAAE,sBAAsB,EAAE,MAAM,eAAe,CAAC;AAOvD,KAAK,UAAU,gBAAgB,CAAC,KAAoC;IAClE,MAAM,OAAO,CAAC,GAAG,CAAC;QAChB,oBAAoB,CAAC,KAAK,CAAC,gBAAgB,CAAC,MAAM,EAAE,WAAW,CAAC;QAChE,oBAAoB,CAAC,KAAK,CAAC,gBAAgB,CAAC,MAAM,EAAE,WAAW,CAAC;QAChE,oBAAoB,CAClB,KAAK,CAAC,gBAAgB,CAAC,QAAQ,EAC/B,uBAAuB,CACxB;KACF,CAAC,CAAC;AACL,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,uBAAuB,CAC3C,OAAuC;IAEvC,MAAM,KAAK,GAAG,4BAA4B,CAAC,OAAO,CAAC,CAAC;IACpD,+BAA+B,CAAC,KAAK,CAAC,CAAC;IACvC,MAAM,gBAAgB,CAAC,KAAK,CAAC,CAAC;IAE9B,MAAM,CAAC,QAAQ,EAAE,QAAQ,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;QAC7C,uBAAuB,CAAC,KAAK,CAAC;QAC9B,sBAAsB,CAAC,KAAK,CAAC;KAC9B,CAAC,CAAC;IAEH,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,MAAM,WAAW,CACf,KAAK,CAAC,gBAAgB,CAAC,MAAM,EAC7B,QAAQ,CAAC,MAAM,EACf,WAAW,CACZ,CAAC;IACF,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,cAAc,EAAE,KAAK,CAAC,gBAAgB,CAAC,MAAM,CAAC,CAAC,CAAC;IAC5E,MAAM,WAAW,CACf,KAAK,CAAC,gBAAgB,CAAC,MAAM,EAC7B,QAAQ,CAAC,MAAM,EACf,WAAW,CACZ,CAAC;IACF,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,cAAc,EAAE,KAAK,CAAC,gBAAgB,CAAC,MAAM,CAAC,CAAC,CAAC;IAE5E,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;QAC3B,IACE,MAAM,iBAAiB,CACrB,KAAK,CAAC,gBAAgB,CAAC,QAAQ,EAC/B,uBAAuB,CACxB,EACD,CAAC;YACD,OAAO,CAAC,IAAI,CACV,QAAQ,CAAC,KAAK,CAAC,cAAc,EAAE,KAAK,CAAC,gBAAgB,CAAC,QAAQ,CAAC,CAChE,CAAC;QACJ,CAAC;IACH,CAAC;SAAM,CAAC;QACN,MAAM,WAAW,CACf,KAAK,CAAC,gBAAgB,CAAC,QAAQ,EAC/B,QAAQ,EACR,uBAAuB,CACxB,CAAC;QACF,OAAO,CAAC,IAAI,CACV,QAAQ,CAAC,KAAK,CAAC,cAAc,EAAE,KAAK,CAAC,gBAAgB,CAAC,QAAQ,CAAC,CAChE,CAAC;IACJ,CAAC;IAED,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC;AAC9B,CAAC","sourcesContent":["import { relative } from \"node:path\";\nimport {\n assertSafePrototypeContextPaths,\n resolvePrototypeContextPaths,\n type ResolvedPrototypeContextPaths,\n} from \"./paths.js\";\nimport {\n atomicWrite,\n assertWritableTarget,\n removeManagedFile,\n} from \"./internal/filesystem.js\";\nimport type { ContextProjectionResult } from \"./types.js\";\nimport { renderPrototypeMarkdown } from \"./render.js\";\nimport { renderOptionalSettings } from \"./template.js\";\n\nexport type ProjectPrototypeContextOptions = Readonly<{\n repositoryRoot: string;\n prototypePath: string;\n}>;\n\nasync function preflightTargets(paths: ResolvedPrototypeContextPaths) {\n await Promise.all([\n assertWritableTarget(paths.prototypeOutputs.agents, \"AGENTS.md\"),\n assertWritableTarget(paths.prototypeOutputs.claude, \"CLAUDE.md\"),\n assertWritableTarget(\n paths.prototypeOutputs.settings,\n \".claude/settings.json\",\n ),\n ]);\n}\n\nexport async function projectPrototypeContext(\n options: ProjectPrototypeContextOptions,\n): Promise<ContextProjectionResult> {\n const paths = resolvePrototypeContextPaths(options);\n assertSafePrototypeContextPaths(paths);\n await preflightTargets(paths);\n\n const [markdown, settings] = await Promise.all([\n renderPrototypeMarkdown(paths),\n renderOptionalSettings(paths),\n ]);\n\n const written: string[] = [];\n const removed: string[] = [];\n await atomicWrite(\n paths.prototypeOutputs.agents,\n markdown.agents,\n \"AGENTS.md\",\n );\n written.push(relative(paths.repositoryRoot, paths.prototypeOutputs.agents));\n await atomicWrite(\n paths.prototypeOutputs.claude,\n markdown.claude,\n \"CLAUDE.md\",\n );\n written.push(relative(paths.repositoryRoot, paths.prototypeOutputs.claude));\n\n if (settings === undefined) {\n if (\n await removeManagedFile(\n paths.prototypeOutputs.settings,\n \".claude/settings.json\",\n )\n ) {\n removed.push(\n relative(paths.repositoryRoot, paths.prototypeOutputs.settings),\n );\n }\n } else {\n await atomicWrite(\n paths.prototypeOutputs.settings,\n settings,\n \".claude/settings.json\",\n );\n written.push(\n relative(paths.repositoryRoot, paths.prototypeOutputs.settings),\n );\n }\n\n return { written, removed };\n}\n"]}
@@ -0,0 +1,36 @@
1
+ export declare const CONTEXT_REGISTRY_SCHEMA_VERSION: 1;
2
+ export declare const CONTEXT_REGISTRY_FILE = "registry.json";
3
+ export type ContextRegistryLocationOptions = Readonly<{
4
+ homeDirectory?: string;
5
+ }>;
6
+ export type ContextRegistryPaths = Readonly<{
7
+ directory: string;
8
+ registry: string;
9
+ }>;
10
+ export type ContextRegistrySkill = Readonly<{
11
+ sourcePath: string;
12
+ contentHash: string;
13
+ }>;
14
+ export type ContextRegistryMcpServer = Readonly<{
15
+ sourcePath: string;
16
+ normalizedDefinitionHash: string;
17
+ }>;
18
+ export type ContextRegistryApp = Readonly<{
19
+ repositoryRoot: string;
20
+ skills: Readonly<Record<string, ContextRegistrySkill>>;
21
+ mcp: Readonly<Record<string, ContextRegistryMcpServer>>;
22
+ }>;
23
+ export type ContextRegistry = Readonly<{
24
+ schemaVersion: typeof CONTEXT_REGISTRY_SCHEMA_VERSION;
25
+ apps: Readonly<Record<string, ContextRegistryApp>>;
26
+ }>;
27
+ export declare class ContextRegistryError extends Error {
28
+ readonly path: string;
29
+ constructor(path: string, message: string);
30
+ }
31
+ export declare function validateContextAppId(value: unknown): string;
32
+ export declare function createEmptyContextRegistry(): ContextRegistry;
33
+ export declare function resolveContextRegistryPaths(options?: ContextRegistryLocationOptions): ContextRegistryPaths;
34
+ export declare function readContextRegistry(options?: ContextRegistryLocationOptions): Promise<ContextRegistry>;
35
+ export declare function writeContextRegistry(registry: ContextRegistry, options?: ContextRegistryLocationOptions): Promise<void>;
36
+ //# sourceMappingURL=registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,+BAA+B,EAAG,CAAU,CAAC;AAC1D,eAAO,MAAM,qBAAqB,kBAAkB,CAAC;AAKrD,MAAM,MAAM,8BAA8B,GAAG,QAAQ,CAAC;IACpD,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,CAAC,CAAC;AAEH,MAAM,MAAM,oBAAoB,GAAG,QAAQ,CAAC;IAC1C,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC,CAAC;AAEH,MAAM,MAAM,oBAAoB,GAAG,QAAQ,CAAC;IAC1C,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;CACrB,CAAC,CAAC;AAEH,MAAM,MAAM,wBAAwB,GAAG,QAAQ,CAAC;IAC9C,UAAU,EAAE,MAAM,CAAC;IACnB,wBAAwB,EAAE,MAAM,CAAC;CAClC,CAAC,CAAC;AAEH,MAAM,MAAM,kBAAkB,GAAG,QAAQ,CAAC;IACxC,cAAc,EAAE,MAAM,CAAC;IACvB,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,oBAAoB,CAAC,CAAC,CAAC;IACvD,GAAG,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,wBAAwB,CAAC,CAAC,CAAC;CACzD,CAAC,CAAC;AAEH,MAAM,MAAM,eAAe,GAAG,QAAQ,CAAC;IACrC,aAAa,EAAE,OAAO,+BAA+B,CAAC;IACtD,IAAI,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC,CAAC;CACpD,CAAC,CAAC;AAEH,qBAAa,oBAAqB,SAAQ,KAAK;IAC7C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAEtB,YAAY,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAIxC;CACF;AA4BD,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,OAAO,UAElD;AA8GD,wBAAgB,0BAA0B,IAAI,eAAe,CAK5D;AAED,wBAAgB,2BAA2B,CACzC,OAAO,GAAE,8BAAmC,GAC3C,oBAAoB,CAItB;AA4BD,wBAAsB,mBAAmB,CACvC,OAAO,GAAE,8BAAmC,GAC3C,OAAO,CAAC,eAAe,CAAC,CAoB1B;AAED,wBAAsB,oBAAoB,CACxC,QAAQ,EAAE,eAAe,EACzB,OAAO,GAAE,8BAAmC,GAC3C,OAAO,CAAC,IAAI,CAAC,CAuBf","sourcesContent":["import { chmod, lstat, mkdir, readFile } from \"node:fs/promises\";\nimport { homedir } from \"node:os\";\nimport { isAbsolute, join, resolve } from \"node:path\";\nimport { atomicWrite, isNotFound } from \"./internal/filesystem.js\";\n\nexport const CONTEXT_REGISTRY_SCHEMA_VERSION = 1 as const;\nexport const CONTEXT_REGISTRY_FILE = \"registry.json\";\n\nconst APP_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/u;\nconst SHA256_PATTERN = /^[a-f0-9]{64}$/u;\n\nexport type ContextRegistryLocationOptions = Readonly<{\n homeDirectory?: string;\n}>;\n\nexport type ContextRegistryPaths = Readonly<{\n directory: string;\n registry: string;\n}>;\n\nexport type ContextRegistrySkill = Readonly<{\n sourcePath: string;\n contentHash: string;\n}>;\n\nexport type ContextRegistryMcpServer = Readonly<{\n sourcePath: string;\n normalizedDefinitionHash: string;\n}>;\n\nexport type ContextRegistryApp = Readonly<{\n repositoryRoot: string;\n skills: Readonly<Record<string, ContextRegistrySkill>>;\n mcp: Readonly<Record<string, ContextRegistryMcpServer>>;\n}>;\n\nexport type ContextRegistry = Readonly<{\n schemaVersion: typeof CONTEXT_REGISTRY_SCHEMA_VERSION;\n apps: Readonly<Record<string, ContextRegistryApp>>;\n}>;\n\nexport class ContextRegistryError extends Error {\n readonly path: string;\n\n constructor(path: string, message: string) {\n super(`Invalid context registry at ${path}: ${message}`);\n this.name = \"ContextRegistryError\";\n this.path = path;\n }\n}\n\nfunction requireNonEmptyString(value: unknown, path: string) {\n if (typeof value !== \"string\" || value.trim().length === 0) {\n throw new ContextRegistryError(path, \"must be a nonempty string\");\n }\n return value;\n}\n\nfunction requireAbsolutePath(value: unknown, path: string) {\n const result = requireNonEmptyString(value, path);\n if (!isAbsolute(result)) {\n throw new ContextRegistryError(path, \"must be an absolute path\");\n }\n return resolve(result);\n}\n\nfunction requireAppId(value: unknown, path: string) {\n const appId = requireNonEmptyString(value, path);\n if (!APP_ID_PATTERN.test(appId)) {\n throw new ContextRegistryError(\n path,\n \"must contain only letters, numbers, dots, underscores, and hyphens\",\n );\n }\n return appId;\n}\n\nexport function validateContextAppId(value: unknown) {\n return requireAppId(value, \"appId\");\n}\n\nfunction requireHash(value: unknown, path: string) {\n const hash = requireNonEmptyString(value, path);\n if (!SHA256_PATTERN.test(hash)) {\n throw new ContextRegistryError(path, \"must be a lowercase SHA-256 hash\");\n }\n return hash;\n}\n\nfunction requireRecord(value: unknown, path: string) {\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n throw new ContextRegistryError(path, \"must be an object\");\n }\n return value as Record<string, unknown>;\n}\n\nfunction normalizeSkills(\n value: unknown,\n path: string,\n): Record<string, ContextRegistrySkill> {\n const record = requireRecord(value, path);\n return Object.fromEntries(\n Object.entries(record)\n .sort(([left], [right]) => left.localeCompare(right))\n .map(([name, entry]) => {\n const skillName = requireNonEmptyString(name, `${path}.${name}`);\n const skill = requireRecord(entry, `${path}.${skillName}`);\n return [\n skillName,\n {\n sourcePath: requireAbsolutePath(\n skill.sourcePath,\n `${path}.${skillName}.sourcePath`,\n ),\n contentHash: requireHash(\n skill.contentHash,\n `${path}.${skillName}.contentHash`,\n ),\n },\n ];\n }),\n );\n}\n\nfunction normalizeMcp(\n value: unknown,\n path: string,\n): Record<string, ContextRegistryMcpServer> {\n const record = requireRecord(value, path);\n return Object.fromEntries(\n Object.entries(record)\n .sort(([left], [right]) => left.localeCompare(right))\n .map(([name, entry]) => {\n const serverName = requireNonEmptyString(name, `${path}.${name}`);\n const server = requireRecord(entry, `${path}.${serverName}`);\n return [\n serverName,\n {\n sourcePath: requireAbsolutePath(\n server.sourcePath,\n `${path}.${serverName}.sourcePath`,\n ),\n normalizedDefinitionHash: requireHash(\n server.normalizedDefinitionHash,\n `${path}.${serverName}.normalizedDefinitionHash`,\n ),\n },\n ];\n }),\n );\n}\n\nfunction normalizeRegistry(value: unknown, path: string): ContextRegistry {\n const registry = requireRecord(value, path);\n if (registry.schemaVersion !== CONTEXT_REGISTRY_SCHEMA_VERSION) {\n throw new ContextRegistryError(\n `${path}.schemaVersion`,\n `must be ${CONTEXT_REGISTRY_SCHEMA_VERSION}`,\n );\n }\n\n const apps = requireRecord(registry.apps, `${path}.apps`);\n return {\n schemaVersion: CONTEXT_REGISTRY_SCHEMA_VERSION,\n apps: Object.fromEntries(\n Object.entries(apps)\n .sort(([left], [right]) => left.localeCompare(right))\n .map(([key, value]) => {\n const appId = requireAppId(key, `${path}.apps`);\n const app = requireRecord(value, `${path}.apps.${appId}`);\n return [\n appId,\n {\n repositoryRoot: requireAbsolutePath(\n app.repositoryRoot,\n `${path}.apps.${appId}.repositoryRoot`,\n ),\n skills: normalizeSkills(\n app.skills,\n `${path}.apps.${appId}.skills`,\n ),\n mcp: normalizeMcp(app.mcp, `${path}.apps.${appId}.mcp`),\n },\n ];\n }),\n ),\n };\n}\n\nexport function createEmptyContextRegistry(): ContextRegistry {\n return {\n schemaVersion: CONTEXT_REGISTRY_SCHEMA_VERSION,\n apps: {},\n };\n}\n\nexport function resolveContextRegistryPaths(\n options: ContextRegistryLocationOptions = {},\n): ContextRegistryPaths {\n const homeDirectory = resolve(options.homeDirectory ?? homedir());\n const directory = join(homeDirectory, \".bonfire\", \"context\");\n return { directory, registry: join(directory, CONTEXT_REGISTRY_FILE) };\n}\n\nasync function assertRegistryDirectory(path: string, allowMissing: boolean) {\n try {\n const stats = await lstat(path);\n if (stats.isSymbolicLink() || !stats.isDirectory()) {\n throw new ContextRegistryError(path, \"must be a regular directory\");\n }\n } catch (error) {\n if (error instanceof ContextRegistryError) throw error;\n if (allowMissing && isNotFound(error)) return;\n throw new ContextRegistryError(path, \"could not be inspected safely\");\n }\n}\n\nasync function assertRegistryFile(path: string, allowMissing: boolean) {\n try {\n const stats = await lstat(path);\n if (stats.isSymbolicLink() || !stats.isFile()) {\n throw new ContextRegistryError(path, \"must be a regular file\");\n }\n } catch (error) {\n if (error instanceof ContextRegistryError) throw error;\n if (allowMissing && isNotFound(error)) return;\n throw new ContextRegistryError(path, \"could not be inspected safely\");\n }\n}\n\nexport async function readContextRegistry(\n options: ContextRegistryLocationOptions = {},\n): Promise<ContextRegistry> {\n const paths = resolveContextRegistryPaths(options);\n await assertRegistryDirectory(paths.directory, true);\n await assertRegistryFile(paths.registry, true);\n\n let source: string;\n try {\n source = await readFile(paths.registry, \"utf8\");\n } catch (error) {\n if (isNotFound(error)) return createEmptyContextRegistry();\n throw new ContextRegistryError(paths.registry, \"could not be read safely\");\n }\n\n let value: unknown;\n try {\n value = JSON.parse(source);\n } catch {\n throw new ContextRegistryError(paths.registry, \"must contain valid JSON\");\n }\n return normalizeRegistry(value, paths.registry);\n}\n\nexport async function writeContextRegistry(\n registry: ContextRegistry,\n options: ContextRegistryLocationOptions = {},\n): Promise<void> {\n const paths = resolveContextRegistryPaths(options);\n const normalized = normalizeRegistry(registry, paths.registry);\n\n try {\n await mkdir(paths.directory, { recursive: true, mode: 0o700 });\n await assertRegistryDirectory(paths.directory, false);\n await chmod(paths.directory, 0o700);\n await atomicWrite(\n paths.registry,\n `${JSON.stringify(normalized, null, 2)}\\n`,\n paths.registry,\n 0o600,\n );\n await assertRegistryFile(paths.registry, false);\n await chmod(paths.registry, 0o600);\n } catch (error) {\n if (error instanceof ContextRegistryError) throw error;\n throw new ContextRegistryError(\n paths.registry,\n \"could not be written safely\",\n );\n }\n}\n"]}
@@ -0,0 +1,189 @@
1
+ import { chmod, lstat, mkdir, readFile } from "node:fs/promises";
2
+ import { homedir } from "node:os";
3
+ import { isAbsolute, join, resolve } from "node:path";
4
+ import { atomicWrite, isNotFound } from "./internal/filesystem.js";
5
+ export const CONTEXT_REGISTRY_SCHEMA_VERSION = 1;
6
+ export const CONTEXT_REGISTRY_FILE = "registry.json";
7
+ const APP_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/u;
8
+ const SHA256_PATTERN = /^[a-f0-9]{64}$/u;
9
+ export class ContextRegistryError extends Error {
10
+ path;
11
+ constructor(path, message) {
12
+ super(`Invalid context registry at ${path}: ${message}`);
13
+ this.name = "ContextRegistryError";
14
+ this.path = path;
15
+ }
16
+ }
17
+ function requireNonEmptyString(value, path) {
18
+ if (typeof value !== "string" || value.trim().length === 0) {
19
+ throw new ContextRegistryError(path, "must be a nonempty string");
20
+ }
21
+ return value;
22
+ }
23
+ function requireAbsolutePath(value, path) {
24
+ const result = requireNonEmptyString(value, path);
25
+ if (!isAbsolute(result)) {
26
+ throw new ContextRegistryError(path, "must be an absolute path");
27
+ }
28
+ return resolve(result);
29
+ }
30
+ function requireAppId(value, path) {
31
+ const appId = requireNonEmptyString(value, path);
32
+ if (!APP_ID_PATTERN.test(appId)) {
33
+ throw new ContextRegistryError(path, "must contain only letters, numbers, dots, underscores, and hyphens");
34
+ }
35
+ return appId;
36
+ }
37
+ export function validateContextAppId(value) {
38
+ return requireAppId(value, "appId");
39
+ }
40
+ function requireHash(value, path) {
41
+ const hash = requireNonEmptyString(value, path);
42
+ if (!SHA256_PATTERN.test(hash)) {
43
+ throw new ContextRegistryError(path, "must be a lowercase SHA-256 hash");
44
+ }
45
+ return hash;
46
+ }
47
+ function requireRecord(value, path) {
48
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
49
+ throw new ContextRegistryError(path, "must be an object");
50
+ }
51
+ return value;
52
+ }
53
+ function normalizeSkills(value, path) {
54
+ const record = requireRecord(value, path);
55
+ return Object.fromEntries(Object.entries(record)
56
+ .sort(([left], [right]) => left.localeCompare(right))
57
+ .map(([name, entry]) => {
58
+ const skillName = requireNonEmptyString(name, `${path}.${name}`);
59
+ const skill = requireRecord(entry, `${path}.${skillName}`);
60
+ return [
61
+ skillName,
62
+ {
63
+ sourcePath: requireAbsolutePath(skill.sourcePath, `${path}.${skillName}.sourcePath`),
64
+ contentHash: requireHash(skill.contentHash, `${path}.${skillName}.contentHash`),
65
+ },
66
+ ];
67
+ }));
68
+ }
69
+ function normalizeMcp(value, path) {
70
+ const record = requireRecord(value, path);
71
+ return Object.fromEntries(Object.entries(record)
72
+ .sort(([left], [right]) => left.localeCompare(right))
73
+ .map(([name, entry]) => {
74
+ const serverName = requireNonEmptyString(name, `${path}.${name}`);
75
+ const server = requireRecord(entry, `${path}.${serverName}`);
76
+ return [
77
+ serverName,
78
+ {
79
+ sourcePath: requireAbsolutePath(server.sourcePath, `${path}.${serverName}.sourcePath`),
80
+ normalizedDefinitionHash: requireHash(server.normalizedDefinitionHash, `${path}.${serverName}.normalizedDefinitionHash`),
81
+ },
82
+ ];
83
+ }));
84
+ }
85
+ function normalizeRegistry(value, path) {
86
+ const registry = requireRecord(value, path);
87
+ if (registry.schemaVersion !== CONTEXT_REGISTRY_SCHEMA_VERSION) {
88
+ throw new ContextRegistryError(`${path}.schemaVersion`, `must be ${CONTEXT_REGISTRY_SCHEMA_VERSION}`);
89
+ }
90
+ const apps = requireRecord(registry.apps, `${path}.apps`);
91
+ return {
92
+ schemaVersion: CONTEXT_REGISTRY_SCHEMA_VERSION,
93
+ apps: Object.fromEntries(Object.entries(apps)
94
+ .sort(([left], [right]) => left.localeCompare(right))
95
+ .map(([key, value]) => {
96
+ const appId = requireAppId(key, `${path}.apps`);
97
+ const app = requireRecord(value, `${path}.apps.${appId}`);
98
+ return [
99
+ appId,
100
+ {
101
+ repositoryRoot: requireAbsolutePath(app.repositoryRoot, `${path}.apps.${appId}.repositoryRoot`),
102
+ skills: normalizeSkills(app.skills, `${path}.apps.${appId}.skills`),
103
+ mcp: normalizeMcp(app.mcp, `${path}.apps.${appId}.mcp`),
104
+ },
105
+ ];
106
+ })),
107
+ };
108
+ }
109
+ export function createEmptyContextRegistry() {
110
+ return {
111
+ schemaVersion: CONTEXT_REGISTRY_SCHEMA_VERSION,
112
+ apps: {},
113
+ };
114
+ }
115
+ export function resolveContextRegistryPaths(options = {}) {
116
+ const homeDirectory = resolve(options.homeDirectory ?? homedir());
117
+ const directory = join(homeDirectory, ".bonfire", "context");
118
+ return { directory, registry: join(directory, CONTEXT_REGISTRY_FILE) };
119
+ }
120
+ async function assertRegistryDirectory(path, allowMissing) {
121
+ try {
122
+ const stats = await lstat(path);
123
+ if (stats.isSymbolicLink() || !stats.isDirectory()) {
124
+ throw new ContextRegistryError(path, "must be a regular directory");
125
+ }
126
+ }
127
+ catch (error) {
128
+ if (error instanceof ContextRegistryError)
129
+ throw error;
130
+ if (allowMissing && isNotFound(error))
131
+ return;
132
+ throw new ContextRegistryError(path, "could not be inspected safely");
133
+ }
134
+ }
135
+ async function assertRegistryFile(path, allowMissing) {
136
+ try {
137
+ const stats = await lstat(path);
138
+ if (stats.isSymbolicLink() || !stats.isFile()) {
139
+ throw new ContextRegistryError(path, "must be a regular file");
140
+ }
141
+ }
142
+ catch (error) {
143
+ if (error instanceof ContextRegistryError)
144
+ throw error;
145
+ if (allowMissing && isNotFound(error))
146
+ return;
147
+ throw new ContextRegistryError(path, "could not be inspected safely");
148
+ }
149
+ }
150
+ export async function readContextRegistry(options = {}) {
151
+ const paths = resolveContextRegistryPaths(options);
152
+ await assertRegistryDirectory(paths.directory, true);
153
+ await assertRegistryFile(paths.registry, true);
154
+ let source;
155
+ try {
156
+ source = await readFile(paths.registry, "utf8");
157
+ }
158
+ catch (error) {
159
+ if (isNotFound(error))
160
+ return createEmptyContextRegistry();
161
+ throw new ContextRegistryError(paths.registry, "could not be read safely");
162
+ }
163
+ let value;
164
+ try {
165
+ value = JSON.parse(source);
166
+ }
167
+ catch {
168
+ throw new ContextRegistryError(paths.registry, "must contain valid JSON");
169
+ }
170
+ return normalizeRegistry(value, paths.registry);
171
+ }
172
+ export async function writeContextRegistry(registry, options = {}) {
173
+ const paths = resolveContextRegistryPaths(options);
174
+ const normalized = normalizeRegistry(registry, paths.registry);
175
+ try {
176
+ await mkdir(paths.directory, { recursive: true, mode: 0o700 });
177
+ await assertRegistryDirectory(paths.directory, false);
178
+ await chmod(paths.directory, 0o700);
179
+ await atomicWrite(paths.registry, `${JSON.stringify(normalized, null, 2)}\n`, paths.registry, 0o600);
180
+ await assertRegistryFile(paths.registry, false);
181
+ await chmod(paths.registry, 0o600);
182
+ }
183
+ catch (error) {
184
+ if (error instanceof ContextRegistryError)
185
+ throw error;
186
+ throw new ContextRegistryError(paths.registry, "could not be written safely");
187
+ }
188
+ }
189
+ //# sourceMappingURL=registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.js","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AACjE,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACtD,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAEnE,MAAM,CAAC,MAAM,+BAA+B,GAAG,CAAU,CAAC;AAC1D,MAAM,CAAC,MAAM,qBAAqB,GAAG,eAAe,CAAC;AAErD,MAAM,cAAc,GAAG,qCAAqC,CAAC;AAC7D,MAAM,cAAc,GAAG,iBAAiB,CAAC;AAgCzC,MAAM,OAAO,oBAAqB,SAAQ,KAAK;IACpC,IAAI,CAAS;IAEtB,YAAY,IAAY,EAAE,OAAe;QACvC,KAAK,CAAC,+BAA+B,IAAI,KAAK,OAAO,EAAE,CAAC,CAAC;QACzD,IAAI,CAAC,IAAI,GAAG,sBAAsB,CAAC;QACnC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IACnB,CAAC;CACF;AAED,SAAS,qBAAqB,CAAC,KAAc,EAAE,IAAY;IACzD,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC3D,MAAM,IAAI,oBAAoB,CAAC,IAAI,EAAE,2BAA2B,CAAC,CAAC;IACpE,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,mBAAmB,CAAC,KAAc,EAAE,IAAY;IACvD,MAAM,MAAM,GAAG,qBAAqB,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;IAClD,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;QACxB,MAAM,IAAI,oBAAoB,CAAC,IAAI,EAAE,0BAA0B,CAAC,CAAC;IACnE,CAAC;IACD,OAAO,OAAO,CAAC,MAAM,CAAC,CAAC;AACzB,CAAC;AAED,SAAS,YAAY,CAAC,KAAc,EAAE,IAAY;IAChD,MAAM,KAAK,GAAG,qBAAqB,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;IACjD,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QAChC,MAAM,IAAI,oBAAoB,CAC5B,IAAI,EACJ,oEAAoE,CACrE,CAAC;IACJ,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAC,KAAc;IACjD,OAAO,YAAY,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;AACtC,CAAC;AAED,SAAS,WAAW,CAAC,KAAc,EAAE,IAAY;IAC/C,MAAM,IAAI,GAAG,qBAAqB,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;IAChD,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QAC/B,MAAM,IAAI,oBAAoB,CAAC,IAAI,EAAE,kCAAkC,CAAC,CAAC;IAC3E,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,aAAa,CAAC,KAAc,EAAE,IAAY;IACjD,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACxE,MAAM,IAAI,oBAAoB,CAAC,IAAI,EAAE,mBAAmB,CAAC,CAAC;IAC5D,CAAC;IACD,OAAO,KAAgC,CAAC;AAC1C,CAAC;AAED,SAAS,eAAe,CACtB,KAAc,EACd,IAAY;IAEZ,MAAM,MAAM,GAAG,aAAa,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;IAC1C,OAAO,MAAM,CAAC,WAAW,CACvB,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC;SACnB,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;SACpD,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,KAAK,CAAC,EAAE,EAAE;QACrB,MAAM,SAAS,GAAG,qBAAqB,CAAC,IAAI,EAAE,GAAG,IAAI,IAAI,IAAI,EAAE,CAAC,CAAC;QACjE,MAAM,KAAK,GAAG,aAAa,CAAC,KAAK,EAAE,GAAG,IAAI,IAAI,SAAS,EAAE,CAAC,CAAC;QAC3D,OAAO;YACL,SAAS;YACT;gBACE,UAAU,EAAE,mBAAmB,CAC7B,KAAK,CAAC,UAAU,EAChB,GAAG,IAAI,IAAI,SAAS,aAAa,CAClC;gBACD,WAAW,EAAE,WAAW,CACtB,KAAK,CAAC,WAAW,EACjB,GAAG,IAAI,IAAI,SAAS,cAAc,CACnC;aACF;SACF,CAAC;IACJ,CAAC,CAAC,CACL,CAAC;AACJ,CAAC;AAED,SAAS,YAAY,CACnB,KAAc,EACd,IAAY;IAEZ,MAAM,MAAM,GAAG,aAAa,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;IAC1C,OAAO,MAAM,CAAC,WAAW,CACvB,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC;SACnB,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;SACpD,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,KAAK,CAAC,EAAE,EAAE;QACrB,MAAM,UAAU,GAAG,qBAAqB,CAAC,IAAI,EAAE,GAAG,IAAI,IAAI,IAAI,EAAE,CAAC,CAAC;QAClE,MAAM,MAAM,GAAG,aAAa,CAAC,KAAK,EAAE,GAAG,IAAI,IAAI,UAAU,EAAE,CAAC,CAAC;QAC7D,OAAO;YACL,UAAU;YACV;gBACE,UAAU,EAAE,mBAAmB,CAC7B,MAAM,CAAC,UAAU,EACjB,GAAG,IAAI,IAAI,UAAU,aAAa,CACnC;gBACD,wBAAwB,EAAE,WAAW,CACnC,MAAM,CAAC,wBAAwB,EAC/B,GAAG,IAAI,IAAI,UAAU,2BAA2B,CACjD;aACF;SACF,CAAC;IACJ,CAAC,CAAC,CACL,CAAC;AACJ,CAAC;AAED,SAAS,iBAAiB,CAAC,KAAc,EAAE,IAAY;IACrD,MAAM,QAAQ,GAAG,aAAa,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;IAC5C,IAAI,QAAQ,CAAC,aAAa,KAAK,+BAA+B,EAAE,CAAC;QAC/D,MAAM,IAAI,oBAAoB,CAC5B,GAAG,IAAI,gBAAgB,EACvB,WAAW,+BAA+B,EAAE,CAC7C,CAAC;IACJ,CAAC;IAED,MAAM,IAAI,GAAG,aAAa,CAAC,QAAQ,CAAC,IAAI,EAAE,GAAG,IAAI,OAAO,CAAC,CAAC;IAC1D,OAAO;QACL,aAAa,EAAE,+BAA+B;QAC9C,IAAI,EAAE,MAAM,CAAC,WAAW,CACtB,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC;aACjB,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;aACpD,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE;YACpB,MAAM,KAAK,GAAG,YAAY,CAAC,GAAG,EAAE,GAAG,IAAI,OAAO,CAAC,CAAC;YAChD,MAAM,GAAG,GAAG,aAAa,CAAC,KAAK,EAAE,GAAG,IAAI,SAAS,KAAK,EAAE,CAAC,CAAC;YAC1D,OAAO;gBACL,KAAK;gBACL;oBACE,cAAc,EAAE,mBAAmB,CACjC,GAAG,CAAC,cAAc,EAClB,GAAG,IAAI,SAAS,KAAK,iBAAiB,CACvC;oBACD,MAAM,EAAE,eAAe,CACrB,GAAG,CAAC,MAAM,EACV,GAAG,IAAI,SAAS,KAAK,SAAS,CAC/B;oBACD,GAAG,EAAE,YAAY,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,IAAI,SAAS,KAAK,MAAM,CAAC;iBACxD;aACF,CAAC;QACJ,CAAC,CAAC,CACL;KACF,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,0BAA0B;IACxC,OAAO;QACL,aAAa,EAAE,+BAA+B;QAC9C,IAAI,EAAE,EAAE;KACT,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,2BAA2B,CACzC,OAAO,GAAmC,EAAE;IAE5C,MAAM,aAAa,GAAG,OAAO,CAAC,OAAO,CAAC,aAAa,IAAI,OAAO,EAAE,CAAC,CAAC;IAClE,MAAM,SAAS,GAAG,IAAI,CAAC,aAAa,EAAE,UAAU,EAAE,SAAS,CAAC,CAAC;IAC7D,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,IAAI,CAAC,SAAS,EAAE,qBAAqB,CAAC,EAAE,CAAC;AACzE,CAAC;AAED,KAAK,UAAU,uBAAuB,CAAC,IAAY,EAAE,YAAqB;IACxE,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,MAAM,KAAK,CAAC,IAAI,CAAC,CAAC;QAChC,IAAI,KAAK,CAAC,cAAc,EAAE,IAAI,CAAC,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC;YACnD,MAAM,IAAI,oBAAoB,CAAC,IAAI,EAAE,6BAA6B,CAAC,CAAC;QACtE,CAAC;IACH,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,KAAK,YAAY,oBAAoB;YAAE,MAAM,KAAK,CAAC;QACvD,IAAI,YAAY,IAAI,UAAU,CAAC,KAAK,CAAC;YAAE,OAAO;QAC9C,MAAM,IAAI,oBAAoB,CAAC,IAAI,EAAE,+BAA+B,CAAC,CAAC;IACxE,CAAC;AACH,CAAC;AAED,KAAK,UAAU,kBAAkB,CAAC,IAAY,EAAE,YAAqB;IACnE,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,MAAM,KAAK,CAAC,IAAI,CAAC,CAAC;QAChC,IAAI,KAAK,CAAC,cAAc,EAAE,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC;YAC9C,MAAM,IAAI,oBAAoB,CAAC,IAAI,EAAE,wBAAwB,CAAC,CAAC;QACjE,CAAC;IACH,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,KAAK,YAAY,oBAAoB;YAAE,MAAM,KAAK,CAAC;QACvD,IAAI,YAAY,IAAI,UAAU,CAAC,KAAK,CAAC;YAAE,OAAO;QAC9C,MAAM,IAAI,oBAAoB,CAAC,IAAI,EAAE,+BAA+B,CAAC,CAAC;IACxE,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,OAAO,GAAmC,EAAE;IAE5C,MAAM,KAAK,GAAG,2BAA2B,CAAC,OAAO,CAAC,CAAC;IACnD,MAAM,uBAAuB,CAAC,KAAK,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;IACrD,MAAM,kBAAkB,CAAC,KAAK,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;IAE/C,IAAI,MAAc,CAAC;IACnB,IAAI,CAAC;QACH,MAAM,GAAG,MAAM,QAAQ,CAAC,KAAK,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IAClD,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,UAAU,CAAC,KAAK,CAAC;YAAE,OAAO,0BAA0B,EAAE,CAAC;QAC3D,MAAM,IAAI,oBAAoB,CAAC,KAAK,CAAC,QAAQ,EAAE,0BAA0B,CAAC,CAAC;IAC7E,CAAC;IAED,IAAI,KAAc,CAAC;IACnB,IAAI,CAAC;QACH,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IAC7B,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,oBAAoB,CAAC,KAAK,CAAC,QAAQ,EAAE,yBAAyB,CAAC,CAAC;IAC5E,CAAC;IACD,OAAO,iBAAiB,CAAC,KAAK,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAC;AAClD,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,QAAyB,EACzB,OAAO,GAAmC,EAAE;IAE5C,MAAM,KAAK,GAAG,2BAA2B,CAAC,OAAO,CAAC,CAAC;IACnD,MAAM,UAAU,GAAG,iBAAiB,CAAC,QAAQ,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAC;IAE/D,IAAI,CAAC;QACH,MAAM,KAAK,CAAC,KAAK,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;QAC/D,MAAM,uBAAuB,CAAC,KAAK,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;QACtD,MAAM,KAAK,CAAC,KAAK,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;QACpC,MAAM,WAAW,CACf,KAAK,CAAC,QAAQ,EACd,GAAG,IAAI,CAAC,SAAS,CAAC,UAAU,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAC1C,KAAK,CAAC,QAAQ,EACd,KAAK,CACN,CAAC;QACF,MAAM,kBAAkB,CAAC,KAAK,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;QAChD,MAAM,KAAK,CAAC,KAAK,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;IACrC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,KAAK,YAAY,oBAAoB;YAAE,MAAM,KAAK,CAAC;QACvD,MAAM,IAAI,oBAAoB,CAC5B,KAAK,CAAC,QAAQ,EACd,6BAA6B,CAC9B,CAAC;IACJ,CAAC;AACH,CAAC","sourcesContent":["import { chmod, lstat, mkdir, readFile } from \"node:fs/promises\";\nimport { homedir } from \"node:os\";\nimport { isAbsolute, join, resolve } from \"node:path\";\nimport { atomicWrite, isNotFound } from \"./internal/filesystem.js\";\n\nexport const CONTEXT_REGISTRY_SCHEMA_VERSION = 1 as const;\nexport const CONTEXT_REGISTRY_FILE = \"registry.json\";\n\nconst APP_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/u;\nconst SHA256_PATTERN = /^[a-f0-9]{64}$/u;\n\nexport type ContextRegistryLocationOptions = Readonly<{\n homeDirectory?: string;\n}>;\n\nexport type ContextRegistryPaths = Readonly<{\n directory: string;\n registry: string;\n}>;\n\nexport type ContextRegistrySkill = Readonly<{\n sourcePath: string;\n contentHash: string;\n}>;\n\nexport type ContextRegistryMcpServer = Readonly<{\n sourcePath: string;\n normalizedDefinitionHash: string;\n}>;\n\nexport type ContextRegistryApp = Readonly<{\n repositoryRoot: string;\n skills: Readonly<Record<string, ContextRegistrySkill>>;\n mcp: Readonly<Record<string, ContextRegistryMcpServer>>;\n}>;\n\nexport type ContextRegistry = Readonly<{\n schemaVersion: typeof CONTEXT_REGISTRY_SCHEMA_VERSION;\n apps: Readonly<Record<string, ContextRegistryApp>>;\n}>;\n\nexport class ContextRegistryError extends Error {\n readonly path: string;\n\n constructor(path: string, message: string) {\n super(`Invalid context registry at ${path}: ${message}`);\n this.name = \"ContextRegistryError\";\n this.path = path;\n }\n}\n\nfunction requireNonEmptyString(value: unknown, path: string) {\n if (typeof value !== \"string\" || value.trim().length === 0) {\n throw new ContextRegistryError(path, \"must be a nonempty string\");\n }\n return value;\n}\n\nfunction requireAbsolutePath(value: unknown, path: string) {\n const result = requireNonEmptyString(value, path);\n if (!isAbsolute(result)) {\n throw new ContextRegistryError(path, \"must be an absolute path\");\n }\n return resolve(result);\n}\n\nfunction requireAppId(value: unknown, path: string) {\n const appId = requireNonEmptyString(value, path);\n if (!APP_ID_PATTERN.test(appId)) {\n throw new ContextRegistryError(\n path,\n \"must contain only letters, numbers, dots, underscores, and hyphens\",\n );\n }\n return appId;\n}\n\nexport function validateContextAppId(value: unknown) {\n return requireAppId(value, \"appId\");\n}\n\nfunction requireHash(value: unknown, path: string) {\n const hash = requireNonEmptyString(value, path);\n if (!SHA256_PATTERN.test(hash)) {\n throw new ContextRegistryError(path, \"must be a lowercase SHA-256 hash\");\n }\n return hash;\n}\n\nfunction requireRecord(value: unknown, path: string) {\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n throw new ContextRegistryError(path, \"must be an object\");\n }\n return value as Record<string, unknown>;\n}\n\nfunction normalizeSkills(\n value: unknown,\n path: string,\n): Record<string, ContextRegistrySkill> {\n const record = requireRecord(value, path);\n return Object.fromEntries(\n Object.entries(record)\n .sort(([left], [right]) => left.localeCompare(right))\n .map(([name, entry]) => {\n const skillName = requireNonEmptyString(name, `${path}.${name}`);\n const skill = requireRecord(entry, `${path}.${skillName}`);\n return [\n skillName,\n {\n sourcePath: requireAbsolutePath(\n skill.sourcePath,\n `${path}.${skillName}.sourcePath`,\n ),\n contentHash: requireHash(\n skill.contentHash,\n `${path}.${skillName}.contentHash`,\n ),\n },\n ];\n }),\n );\n}\n\nfunction normalizeMcp(\n value: unknown,\n path: string,\n): Record<string, ContextRegistryMcpServer> {\n const record = requireRecord(value, path);\n return Object.fromEntries(\n Object.entries(record)\n .sort(([left], [right]) => left.localeCompare(right))\n .map(([name, entry]) => {\n const serverName = requireNonEmptyString(name, `${path}.${name}`);\n const server = requireRecord(entry, `${path}.${serverName}`);\n return [\n serverName,\n {\n sourcePath: requireAbsolutePath(\n server.sourcePath,\n `${path}.${serverName}.sourcePath`,\n ),\n normalizedDefinitionHash: requireHash(\n server.normalizedDefinitionHash,\n `${path}.${serverName}.normalizedDefinitionHash`,\n ),\n },\n ];\n }),\n );\n}\n\nfunction normalizeRegistry(value: unknown, path: string): ContextRegistry {\n const registry = requireRecord(value, path);\n if (registry.schemaVersion !== CONTEXT_REGISTRY_SCHEMA_VERSION) {\n throw new ContextRegistryError(\n `${path}.schemaVersion`,\n `must be ${CONTEXT_REGISTRY_SCHEMA_VERSION}`,\n );\n }\n\n const apps = requireRecord(registry.apps, `${path}.apps`);\n return {\n schemaVersion: CONTEXT_REGISTRY_SCHEMA_VERSION,\n apps: Object.fromEntries(\n Object.entries(apps)\n .sort(([left], [right]) => left.localeCompare(right))\n .map(([key, value]) => {\n const appId = requireAppId(key, `${path}.apps`);\n const app = requireRecord(value, `${path}.apps.${appId}`);\n return [\n appId,\n {\n repositoryRoot: requireAbsolutePath(\n app.repositoryRoot,\n `${path}.apps.${appId}.repositoryRoot`,\n ),\n skills: normalizeSkills(\n app.skills,\n `${path}.apps.${appId}.skills`,\n ),\n mcp: normalizeMcp(app.mcp, `${path}.apps.${appId}.mcp`),\n },\n ];\n }),\n ),\n };\n}\n\nexport function createEmptyContextRegistry(): ContextRegistry {\n return {\n schemaVersion: CONTEXT_REGISTRY_SCHEMA_VERSION,\n apps: {},\n };\n}\n\nexport function resolveContextRegistryPaths(\n options: ContextRegistryLocationOptions = {},\n): ContextRegistryPaths {\n const homeDirectory = resolve(options.homeDirectory ?? homedir());\n const directory = join(homeDirectory, \".bonfire\", \"context\");\n return { directory, registry: join(directory, CONTEXT_REGISTRY_FILE) };\n}\n\nasync function assertRegistryDirectory(path: string, allowMissing: boolean) {\n try {\n const stats = await lstat(path);\n if (stats.isSymbolicLink() || !stats.isDirectory()) {\n throw new ContextRegistryError(path, \"must be a regular directory\");\n }\n } catch (error) {\n if (error instanceof ContextRegistryError) throw error;\n if (allowMissing && isNotFound(error)) return;\n throw new ContextRegistryError(path, \"could not be inspected safely\");\n }\n}\n\nasync function assertRegistryFile(path: string, allowMissing: boolean) {\n try {\n const stats = await lstat(path);\n if (stats.isSymbolicLink() || !stats.isFile()) {\n throw new ContextRegistryError(path, \"must be a regular file\");\n }\n } catch (error) {\n if (error instanceof ContextRegistryError) throw error;\n if (allowMissing && isNotFound(error)) return;\n throw new ContextRegistryError(path, \"could not be inspected safely\");\n }\n}\n\nexport async function readContextRegistry(\n options: ContextRegistryLocationOptions = {},\n): Promise<ContextRegistry> {\n const paths = resolveContextRegistryPaths(options);\n await assertRegistryDirectory(paths.directory, true);\n await assertRegistryFile(paths.registry, true);\n\n let source: string;\n try {\n source = await readFile(paths.registry, \"utf8\");\n } catch (error) {\n if (isNotFound(error)) return createEmptyContextRegistry();\n throw new ContextRegistryError(paths.registry, \"could not be read safely\");\n }\n\n let value: unknown;\n try {\n value = JSON.parse(source);\n } catch {\n throw new ContextRegistryError(paths.registry, \"must contain valid JSON\");\n }\n return normalizeRegistry(value, paths.registry);\n}\n\nexport async function writeContextRegistry(\n registry: ContextRegistry,\n options: ContextRegistryLocationOptions = {},\n): Promise<void> {\n const paths = resolveContextRegistryPaths(options);\n const normalized = normalizeRegistry(registry, paths.registry);\n\n try {\n await mkdir(paths.directory, { recursive: true, mode: 0o700 });\n await assertRegistryDirectory(paths.directory, false);\n await chmod(paths.directory, 0o700);\n await atomicWrite(\n paths.registry,\n `${JSON.stringify(normalized, null, 2)}\\n`,\n paths.registry,\n 0o600,\n );\n await assertRegistryFile(paths.registry, false);\n await chmod(paths.registry, 0o600);\n } catch (error) {\n if (error instanceof ContextRegistryError) throw error;\n throw new ContextRegistryError(\n paths.registry,\n \"could not be written safely\",\n );\n }\n}\n"]}
@@ -0,0 +1,15 @@
1
+ import { type ResolvedContextPaths, type ResolvedPrototypeContextPaths } from "./paths.js";
2
+ export declare const GENERATED_CLAUDE_CONTENT = "<!--\n This file is generated. Do not edit it directly.\n-->\n\n@AGENTS.md\n";
3
+ export type MarkdownProjection = Readonly<{
4
+ agents: string;
5
+ claude: string;
6
+ }>;
7
+ export declare class ContextRenderError extends Error {
8
+ readonly source: string;
9
+ constructor(source: string, message: string);
10
+ }
11
+ export declare function renderMarkdownSource(source: string, sourceLabel: string): string;
12
+ export declare function renderClaudeBridge(): string;
13
+ export declare function renderRootMarkdown(paths: ResolvedContextPaths): Promise<MarkdownProjection>;
14
+ export declare function renderPrototypeMarkdown(paths: ResolvedPrototypeContextPaths): Promise<MarkdownProjection>;
15
+ //# sourceMappingURL=render.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../src/render.ts"],"names":[],"mappings":"AACA,OAAO,EAGL,KAAK,oBAAoB,EACzB,KAAK,6BAA6B,EACnC,MAAM,YAAY,CAAC;AAKpB,eAAO,MAAM,wBAAwB,kFAKpC,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG,QAAQ,CAAC;IACxC,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC,CAAC;AAEH,qBAAa,kBAAmB,SAAQ,KAAK;IAC3C,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAExB,YAAY,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAI1C;CACF;AAeD,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,UAEvE;AAED,wBAAgB,kBAAkB,WAEjC;AAiBD,wBAAsB,kBAAkB,CACtC,KAAK,EAAE,oBAAoB,GAC1B,OAAO,CAAC,kBAAkB,CAAC,CAW7B;AAED,wBAAsB,uBAAuB,CAC3C,KAAK,EAAE,6BAA6B,GACnC,OAAO,CAAC,kBAAkB,CAAC,CAW7B","sourcesContent":["import { lstat, readFile } from \"node:fs/promises\";\nimport {\n assertSafeContextPaths,\n assertSafePrototypeContextPaths,\n type ResolvedContextPaths,\n type ResolvedPrototypeContextPaths,\n} from \"./paths.js\";\n\nconst ROOT_SOURCE_LABEL = \"context/ROOT.md\";\nconst PROTOTYPE_SOURCE_LABEL = \"context/PROTOTYPE.md\";\n\nexport const GENERATED_CLAUDE_CONTENT = `<!--\n This file is generated. Do not edit it directly.\n-->\n\n@AGENTS.md\n`;\n\nexport type MarkdownProjection = Readonly<{\n agents: string;\n claude: string;\n}>;\n\nexport class ContextRenderError extends Error {\n readonly source: string;\n\n constructor(source: string, message: string) {\n super(`Unable to render ${source}: ${message}`);\n this.name = \"ContextRenderError\";\n this.source = source;\n }\n}\n\nfunction generatedNotice(source: string) {\n return `<!--\n This file is generated from ${source}.\n Do not edit it directly. Run \\`bonfire context sync\\` after changing the source.\n-->\n\n`;\n}\n\nfunction ensureFinalNewline(source: string) {\n return source.endsWith(\"\\n\") ? source : `${source}\\n`;\n}\n\nexport function renderMarkdownSource(source: string, sourceLabel: string) {\n return `${generatedNotice(sourceLabel)}${ensureFinalNewline(source)}`;\n}\n\nexport function renderClaudeBridge() {\n return GENERATED_CLAUDE_CONTENT;\n}\n\nasync function readRequiredMarkdown(path: string, sourceLabel: string) {\n try {\n const stats = await lstat(path);\n if (!stats.isFile() || stats.isSymbolicLink()) {\n throw new Error(\"not a regular file\");\n }\n return await readFile(path, \"utf8\");\n } catch {\n throw new ContextRenderError(\n sourceLabel,\n \"the required source is missing or unreadable\",\n );\n }\n}\n\nexport async function renderRootMarkdown(\n paths: ResolvedContextPaths,\n): Promise<MarkdownProjection> {\n assertSafeContextPaths(paths);\n const source = await readRequiredMarkdown(\n paths.sources.rootInstructions,\n ROOT_SOURCE_LABEL,\n );\n\n return {\n agents: renderMarkdownSource(source, ROOT_SOURCE_LABEL),\n claude: renderClaudeBridge(),\n };\n}\n\nexport async function renderPrototypeMarkdown(\n paths: ResolvedPrototypeContextPaths,\n): Promise<MarkdownProjection> {\n assertSafePrototypeContextPaths(paths);\n const source = await readRequiredMarkdown(\n paths.sources.prototypeInstructions,\n PROTOTYPE_SOURCE_LABEL,\n );\n\n return {\n agents: renderMarkdownSource(source, PROTOTYPE_SOURCE_LABEL),\n claude: renderClaudeBridge(),\n };\n}\n"]}