contentbase 0.0.2 → 0.0.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.
- package/.mcp.json +9 -0
- package/CLI.md +593 -0
- package/MCP-DESCRIPTIONS-REVIEW.md +122 -0
- package/MCP-SERVER-SPEC.md +453 -0
- package/PRIMER.md +540 -0
- package/README.md +289 -13
- package/bun.lock +43 -4
- package/dist/cnotes +0 -0
- package/docs/README.md +110 -0
- package/docs/TABLE-OF-CONTENTS.md +7 -0
- package/docs/models.ts +38 -0
- package/models.ts +38 -0
- package/package.json +12 -3
- package/public/web-demo/index.html +813 -0
- package/showcases/node_modules/.cache/.repl_history +3 -0
- package/showcases/vinyl-collection/artists/nina-simone.mdx +1 -1
- package/src/__tests__/semantic-search.integration.test.ts +284 -0
- package/src/api/endpoints/actions.ts +34 -0
- package/src/api/endpoints/doc.ts +187 -0
- package/src/api/endpoints/docs-index.ts +26 -0
- package/src/api/endpoints/document.ts +171 -0
- package/src/api/endpoints/documents.ts +71 -0
- package/src/api/endpoints/inspect.ts +16 -0
- package/src/api/endpoints/models.ts +10 -0
- package/src/api/endpoints/query.ts +88 -0
- package/src/api/endpoints/root.ts +7 -0
- package/src/api/endpoints/search-reindex.ts +65 -0
- package/src/api/endpoints/search-status.ts +55 -0
- package/src/api/endpoints/search.ts +104 -0
- package/src/api/endpoints/semantic-search.ts +120 -0
- package/src/api/endpoints/text-search.ts +63 -0
- package/src/api/endpoints/validate.ts +34 -0
- package/src/api/helpers.ts +97 -0
- package/src/base-model.ts +12 -0
- package/src/cli/commands/action.ts +82 -44
- package/src/cli/commands/console.ts +124 -0
- package/src/cli/commands/create.ts +179 -53
- package/src/cli/commands/embed.ts +323 -0
- package/src/cli/commands/export.ts +58 -24
- package/src/cli/commands/extract.ts +174 -0
- package/src/cli/commands/help.ts +81 -0
- package/src/cli/commands/index.ts +17 -0
- package/src/cli/commands/init.ts +72 -48
- package/src/cli/commands/inspect.ts +56 -46
- package/src/cli/commands/mcp.ts +1219 -0
- package/src/cli/commands/search.ts +285 -0
- package/src/cli/commands/serve.ts +348 -0
- package/src/cli/commands/summary.ts +60 -0
- package/src/cli/commands/teach.ts +86 -0
- package/src/cli/commands/text-search.ts +134 -0
- package/src/cli/commands/validate.ts +126 -64
- package/src/cli/index.ts +88 -19
- package/src/cli/load-collection.ts +144 -17
- package/src/cli/registry.ts +28 -0
- package/src/collection.ts +361 -10
- package/src/define-model.ts +101 -4
- package/src/document.ts +47 -1
- package/src/extract-sections.ts +1 -1
- package/src/index.ts +14 -2
- package/src/model-instance.ts +35 -5
- package/src/node-shortcuts.ts +1 -1
- package/src/query/collection-query.ts +96 -9
- package/src/query/index.ts +7 -0
- package/src/query/query-dsl.ts +259 -0
- package/src/relationships/has-many.ts +39 -0
- package/src/relationships/index.ts +7 -2
- package/src/section.ts +2 -0
- package/src/types.ts +23 -1
- package/src/utils/index.ts +1 -0
- package/src/utils/match-pattern.ts +65 -0
- package/src/validator.ts +18 -1
- package/test/collection.test.ts +118 -2
- package/test/fixtures/sdlc/MODELS.md +106 -0
- package/test/fixtures/sdlc/SKILL.md +404 -0
- package/test/fixtures/sdlc/index.ts +9 -0
- package/test/fixtures/sdlc/models.ts +8 -6
- package/test/fixtures/sdlc/stories/authentication/a-user-should-be-able-to-login.mdx +20 -0
- package/test/fixtures/sdlc/templates/epic.md +23 -0
- package/test/fixtures/sdlc/templates/story.md +19 -0
- package/test/pattern.test.ts +191 -0
- package/test/query-dsl.test.ts +431 -0
- package/test/query.test.ts +29 -0
- package/test/relationships.test.ts +130 -0
- package/test/section.test.ts +61 -0
- package/test/table-of-contents.test.ts +49 -5
|
@@ -1,38 +1,132 @@
|
|
|
1
1
|
import fs from "fs/promises";
|
|
2
2
|
import path from "path";
|
|
3
3
|
import { Collection } from "../collection";
|
|
4
|
+
import { defineModel } from "../define-model";
|
|
5
|
+
import { singularize, upperFirst } from "../utils/inflect";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Search for a file with the given basename and common extensions.
|
|
9
|
+
*/
|
|
10
|
+
async function findFile(
|
|
11
|
+
dir: string,
|
|
12
|
+
basename: string
|
|
13
|
+
): Promise<string | undefined> {
|
|
14
|
+
for (const ext of ["ts", "js", "mjs"]) {
|
|
15
|
+
const candidate = path.resolve(dir, `${basename}.${ext}`);
|
|
16
|
+
try {
|
|
17
|
+
await fs.stat(candidate);
|
|
18
|
+
return candidate;
|
|
19
|
+
} catch {
|
|
20
|
+
// Not found, try next
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Duck-type check: does this value look like a ModelDefinition?
|
|
28
|
+
*/
|
|
29
|
+
function isModelDefinition(value: unknown): boolean {
|
|
30
|
+
return (
|
|
31
|
+
typeof value === "object" &&
|
|
32
|
+
value !== null &&
|
|
33
|
+
typeof (value as any).name === "string" &&
|
|
34
|
+
typeof (value as any).prefix === "string" &&
|
|
35
|
+
"meta" in (value as any)
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Check if a directory (up to 2 levels deep) contains .md or .mdx files.
|
|
41
|
+
*/
|
|
42
|
+
async function directoryContainsMarkdown(dirPath: string): Promise<boolean> {
|
|
43
|
+
try {
|
|
44
|
+
const entries = await fs.readdir(dirPath, { withFileTypes: true });
|
|
45
|
+
for (const entry of entries) {
|
|
46
|
+
if (
|
|
47
|
+
entry.isFile() &&
|
|
48
|
+
(entry.name.endsWith(".md") || entry.name.endsWith(".mdx"))
|
|
49
|
+
) {
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
if (entry.isDirectory()) {
|
|
53
|
+
const subEntries = await fs.readdir(
|
|
54
|
+
path.join(dirPath, entry.name),
|
|
55
|
+
{ withFileTypes: true }
|
|
56
|
+
);
|
|
57
|
+
for (const sub of subEntries) {
|
|
58
|
+
if (
|
|
59
|
+
sub.isFile() &&
|
|
60
|
+
(sub.name.endsWith(".md") || sub.name.endsWith(".mdx"))
|
|
61
|
+
) {
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
} catch {
|
|
68
|
+
// Directory unreadable
|
|
69
|
+
}
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Tier 3: Auto-discover models from top-level subdirectories containing markdown.
|
|
75
|
+
*/
|
|
76
|
+
async function autoDiscoverModels(collection: Collection): Promise<number> {
|
|
77
|
+
const rootPath = collection.rootPath;
|
|
78
|
+
let registered = 0;
|
|
79
|
+
|
|
80
|
+
try {
|
|
81
|
+
const entries = await fs.readdir(rootPath, { withFileTypes: true });
|
|
82
|
+
for (const entry of entries) {
|
|
83
|
+
if (!entry.isDirectory()) continue;
|
|
84
|
+
const dirPath = path.join(rootPath, entry.name);
|
|
85
|
+
if (await directoryContainsMarkdown(dirPath)) {
|
|
86
|
+
const folderName = entry.name;
|
|
87
|
+
const modelName = upperFirst(singularize(folderName));
|
|
88
|
+
const model = defineModel(modelName, { prefix: folderName });
|
|
89
|
+
collection.register(model);
|
|
90
|
+
registered++;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
} catch {
|
|
94
|
+
// Root path unreadable
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return registered;
|
|
98
|
+
}
|
|
4
99
|
|
|
5
100
|
export async function loadCollection(options: {
|
|
6
|
-
|
|
101
|
+
contentFolder?: string;
|
|
7
102
|
modulePath?: string;
|
|
8
103
|
}): Promise<Collection> {
|
|
9
|
-
let {
|
|
104
|
+
let { contentFolder, modulePath } = options;
|
|
105
|
+
let rootPath: string | undefined;
|
|
106
|
+
|
|
107
|
+
const cwd = process.cwd();
|
|
10
108
|
|
|
11
|
-
if (
|
|
12
|
-
|
|
109
|
+
if (contentFolder) {
|
|
110
|
+
// Resolve relative to cwd
|
|
111
|
+
rootPath = path.resolve(cwd, contentFolder);
|
|
112
|
+
} else {
|
|
113
|
+
// Check package.json for configured content folder
|
|
13
114
|
const pkgPath = path.resolve(cwd, "package.json");
|
|
14
115
|
try {
|
|
15
116
|
const manifest = JSON.parse(await fs.readFile(pkgPath, "utf8"));
|
|
16
|
-
if (manifest.contentbase?.
|
|
17
|
-
rootPath = path.resolve(cwd, manifest.contentbase.
|
|
117
|
+
if (manifest.contentbase?.contentFolder) {
|
|
118
|
+
rootPath = path.resolve(cwd, manifest.contentbase.contentFolder);
|
|
18
119
|
}
|
|
19
120
|
} catch {
|
|
20
121
|
// No package.json found
|
|
21
122
|
}
|
|
22
|
-
|
|
123
|
+
// Default to ./docs
|
|
124
|
+
rootPath = rootPath ?? path.resolve(cwd, "docs");
|
|
23
125
|
}
|
|
24
126
|
|
|
127
|
+
// Tier 1: index.ts — full collection with models already registered
|
|
25
128
|
if (!modulePath) {
|
|
26
|
-
|
|
27
|
-
const candidate = path.resolve(rootPath, `index.${ext}`);
|
|
28
|
-
try {
|
|
29
|
-
await fs.stat(candidate);
|
|
30
|
-
modulePath = candidate;
|
|
31
|
-
break;
|
|
32
|
-
} catch {
|
|
33
|
-
// Not found, try next
|
|
34
|
-
}
|
|
35
|
-
}
|
|
129
|
+
modulePath = await findFile(rootPath, "index");
|
|
36
130
|
}
|
|
37
131
|
|
|
38
132
|
if (modulePath) {
|
|
@@ -47,7 +141,40 @@ export async function loadCollection(options: {
|
|
|
47
141
|
return collection;
|
|
48
142
|
}
|
|
49
143
|
|
|
144
|
+
// Tier 2: models.ts — import model definitions and auto-register
|
|
145
|
+
const modelsPath = await findFile(rootPath, "models");
|
|
146
|
+
|
|
147
|
+
if (modelsPath) {
|
|
148
|
+
const mod = await import(modelsPath);
|
|
149
|
+
const collection = new Collection({ rootPath });
|
|
150
|
+
|
|
151
|
+
let registered = 0;
|
|
152
|
+
for (const [, value] of Object.entries(mod)) {
|
|
153
|
+
if (isModelDefinition(value)) {
|
|
154
|
+
collection.register(value as any);
|
|
155
|
+
registered++;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
await collection.load();
|
|
161
|
+
return collection;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Tier 3: Auto-discover models from folder structure
|
|
50
165
|
const collection = new Collection({ rootPath });
|
|
166
|
+
const discovered = await autoDiscoverModels(collection);
|
|
167
|
+
|
|
168
|
+
if (discovered > 0) {
|
|
169
|
+
console.warn(
|
|
170
|
+
`[contentbase] Auto-discovered ${discovered} model(s) from folder structure. These models have no schema validation. Create a models.ts or index.ts for proper type definitions.`
|
|
171
|
+
);
|
|
172
|
+
} else {
|
|
173
|
+
console.warn(
|
|
174
|
+
`[contentbase] No models or markdown files found in ${rootPath}. Run 'cnotes init' to set up a project.`
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
|
|
51
178
|
await collection.load();
|
|
52
179
|
return collection;
|
|
53
180
|
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { z } from 'zod'
|
|
2
|
+
|
|
3
|
+
export interface CommandDefinition {
|
|
4
|
+
description: string
|
|
5
|
+
help?: string
|
|
6
|
+
argsSchema?: z.ZodType<any>
|
|
7
|
+
handler: (options: any, context: { container: any }) => Promise<void>
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const registry = new Map<string, CommandDefinition>()
|
|
11
|
+
|
|
12
|
+
export const commands = {
|
|
13
|
+
register(name: string, definition: CommandDefinition) {
|
|
14
|
+
registry.set(name, definition)
|
|
15
|
+
},
|
|
16
|
+
|
|
17
|
+
has(name: string): boolean {
|
|
18
|
+
return registry.has(name)
|
|
19
|
+
},
|
|
20
|
+
|
|
21
|
+
get(name: string): CommandDefinition | undefined {
|
|
22
|
+
return registry.get(name)
|
|
23
|
+
},
|
|
24
|
+
|
|
25
|
+
get available(): string[] {
|
|
26
|
+
return Array.from(registry.keys())
|
|
27
|
+
},
|
|
28
|
+
}
|