@jaypie/mcp 0.8.25 → 0.8.29
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/dist/suites/docs/index.js +27 -98
- package/dist/suites/docs/index.js.map +1 -1
- package/package.json +2 -2
- package/release-notes/dynamodb/0.5.0.md +69 -0
- package/release-notes/fabric/0.3.0.md +41 -0
- package/release-notes/mcp/0.8.26.md +10 -0
- package/release-notes/mcp/0.8.27.md +17 -0
- package/release-notes/mcp/0.8.29.md +13 -0
- package/release-notes/testkit/1.2.29.md +9 -0
- package/release-notes/testkit/1.2.30.md +10 -0
- package/release-notes/testkit/1.2.32.md +9 -0
- package/release-notes/tildeskill/0.2.1.md +11 -0
- package/release-notes/tildeskill/0.3.0.md +23 -0
- package/release-notes/tildeskill/0.3.1.md +12 -0
- package/skills/dynamodb.md +92 -89
- package/skills/tildeskill.md +93 -22
- package/skills/vocabulary.md +2 -2
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { fabricService } from '@jaypie/fabric';
|
|
2
|
-
import { createMarkdownStore,
|
|
2
|
+
import { createMarkdownStore, createLayeredStore, createSkillService } from '@jaypie/tildeskill';
|
|
3
3
|
import * as fs from 'node:fs/promises';
|
|
4
4
|
import * as path from 'node:path';
|
|
5
5
|
import { fileURLToPath } from 'node:url';
|
|
@@ -9,7 +9,7 @@ import { gt } from 'semver';
|
|
|
9
9
|
/**
|
|
10
10
|
* Docs Suite - Documentation services (skill, version, release_notes)
|
|
11
11
|
*/
|
|
12
|
-
const BUILD_VERSION_STRING = "@jaypie/mcp@0.8.
|
|
12
|
+
const BUILD_VERSION_STRING = "@jaypie/mcp@0.8.29#e602beb5"
|
|
13
13
|
;
|
|
14
14
|
const __filename$1 = fileURLToPath(import.meta.url);
|
|
15
15
|
const __dirname$1 = path.dirname(__filename$1);
|
|
@@ -17,10 +17,31 @@ const __dirname$1 = path.dirname(__filename$1);
|
|
|
17
17
|
// Environment variables allow overriding paths when bundled (e.g., esbuild Lambda)
|
|
18
18
|
const RELEASE_NOTES_PATH = process.env.MCP_RELEASE_NOTES_PATH ||
|
|
19
19
|
path.join(__dirname$1, "..", "..", "..", "release-notes");
|
|
20
|
-
|
|
20
|
+
// Bundled Jaypie skills ship inside the @jaypie/mcp package. MCP_BUILTIN_SKILLS_PATH
|
|
21
|
+
// lets bundlers (esbuild, Lambda) relocate them without disabling the built-in layer.
|
|
22
|
+
const BUILTIN_SKILLS_PATH = process.env.MCP_BUILTIN_SKILLS_PATH ||
|
|
21
23
|
path.join(__dirname$1, "..", "..", "..", "skills");
|
|
22
|
-
|
|
23
|
-
const
|
|
24
|
+
const LOCAL_SKILLS_NAMESPACE = "local";
|
|
25
|
+
const JAYPIE_SKILLS_NAMESPACE = "jaypie";
|
|
26
|
+
const LAYER_SEPARATOR = ":";
|
|
27
|
+
// Skill layers resolved in order: a client's MCP_SKILLS_PATH layers on top of
|
|
28
|
+
// the built-in Jaypie skill pack so `skill("aws")` prefers the client's copy
|
|
29
|
+
// while still exposing bundled Jaypie docs under the `jaypie:` namespace.
|
|
30
|
+
const skillLayers = [];
|
|
31
|
+
if (process.env.MCP_SKILLS_PATH) {
|
|
32
|
+
skillLayers.push({
|
|
33
|
+
namespace: LOCAL_SKILLS_NAMESPACE,
|
|
34
|
+
store: createMarkdownStore({ path: process.env.MCP_SKILLS_PATH }),
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
skillLayers.push({
|
|
38
|
+
namespace: JAYPIE_SKILLS_NAMESPACE,
|
|
39
|
+
store: createMarkdownStore({ path: BUILTIN_SKILLS_PATH }),
|
|
40
|
+
});
|
|
41
|
+
const skillStore = createLayeredStore({
|
|
42
|
+
layers: skillLayers,
|
|
43
|
+
separator: LAYER_SEPARATOR,
|
|
44
|
+
});
|
|
24
45
|
async function parseReleaseNoteFile(filePath) {
|
|
25
46
|
try {
|
|
26
47
|
const content = await fs.readFile(filePath, "utf-8");
|
|
@@ -52,13 +73,6 @@ function formatReleaseNoteListItem(note) {
|
|
|
52
73
|
}
|
|
53
74
|
return parts.join(" ");
|
|
54
75
|
}
|
|
55
|
-
function formatSkillListItem(skill) {
|
|
56
|
-
const { alias, description } = skill;
|
|
57
|
-
if (description) {
|
|
58
|
-
return `* ${alias} - ${description}`;
|
|
59
|
-
}
|
|
60
|
-
return `* ${alias}`;
|
|
61
|
-
}
|
|
62
76
|
async function getPackageReleaseNotes(packageName) {
|
|
63
77
|
const packageDir = path.join(RELEASE_NOTES_PATH, packageName);
|
|
64
78
|
try {
|
|
@@ -98,92 +112,7 @@ function filterReleaseNotesSince(notes, sinceVersion) {
|
|
|
98
112
|
// =============================================================================
|
|
99
113
|
// SKILL SERVICE
|
|
100
114
|
// =============================================================================
|
|
101
|
-
|
|
102
|
-
* Generate alternative spellings for plural/singular matching
|
|
103
|
-
*/
|
|
104
|
-
function getAlternativeSpellings(alias) {
|
|
105
|
-
const alternatives = [];
|
|
106
|
-
if (alias.endsWith("es")) {
|
|
107
|
-
// "indexes" -> try "indexe" and "index"
|
|
108
|
-
alternatives.push(alias.slice(0, -1)); // Remove "s" -> "indexe"
|
|
109
|
-
alternatives.push(alias.slice(0, -2)); // Remove "es" -> "index"
|
|
110
|
-
}
|
|
111
|
-
else if (alias.endsWith("s")) {
|
|
112
|
-
// "skills" -> try "skill"
|
|
113
|
-
alternatives.push(alias.slice(0, -1)); // Remove "s" -> "skill"
|
|
114
|
-
}
|
|
115
|
-
else {
|
|
116
|
-
// "fish" -> try "fishs" and "fishes"
|
|
117
|
-
alternatives.push(alias + "s");
|
|
118
|
-
alternatives.push(alias + "es");
|
|
119
|
-
}
|
|
120
|
-
return alternatives;
|
|
121
|
-
}
|
|
122
|
-
/**
|
|
123
|
-
* Add alias to frontmatter indicating the canonical skill name
|
|
124
|
-
*/
|
|
125
|
-
function addAliasToFrontmatter(content, matchedAlias) {
|
|
126
|
-
if (content.startsWith("---")) {
|
|
127
|
-
// Find the end of frontmatter
|
|
128
|
-
const endIndex = content.indexOf("---", 3);
|
|
129
|
-
if (endIndex !== -1) {
|
|
130
|
-
// Insert alias before the closing ---
|
|
131
|
-
const beforeClose = content.slice(0, endIndex);
|
|
132
|
-
const afterClose = content.slice(endIndex);
|
|
133
|
-
return `${beforeClose}alias: ${matchedAlias}\n${afterClose}`;
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
// No frontmatter exists, create one
|
|
137
|
-
return `---\nalias: ${matchedAlias}\n---\n\n${content}`;
|
|
138
|
-
}
|
|
139
|
-
const skillService = fabricService({
|
|
140
|
-
alias: "skill",
|
|
141
|
-
description: "Access Jaypie development documentation. Pass a skill alias (e.g., 'aws', 'tests', 'errors') to get that documentation. Pass 'index' or no argument to list all available skills.",
|
|
142
|
-
input: {
|
|
143
|
-
alias: {
|
|
144
|
-
description: "Skill alias (e.g., 'aws', 'tests'). Omit or use 'index' to list all skills.",
|
|
145
|
-
required: false,
|
|
146
|
-
type: String,
|
|
147
|
-
},
|
|
148
|
-
},
|
|
149
|
-
service: async ({ alias: inputAlias }) => {
|
|
150
|
-
const alias = normalizeAlias(inputAlias || "index");
|
|
151
|
-
if (!isValidAlias(alias)) {
|
|
152
|
-
throw new Error(`Invalid skill alias "${alias}". Use alphanumeric characters, hyphens, and underscores only.`);
|
|
153
|
-
}
|
|
154
|
-
if (alias === "index") {
|
|
155
|
-
const allSkills = await skillStore.list();
|
|
156
|
-
const skills = allSkills.filter((s) => s.alias !== "index");
|
|
157
|
-
const skillList = skills.map(formatSkillListItem).join("\n");
|
|
158
|
-
return `# Index of Skills\n\n${skillList}`;
|
|
159
|
-
}
|
|
160
|
-
// Try exact match first
|
|
161
|
-
let skill = await skillStore.get(alias);
|
|
162
|
-
let matchedAlias = alias;
|
|
163
|
-
// If no exact match, try alternative spellings (plural/singular)
|
|
164
|
-
if (!skill) {
|
|
165
|
-
const alternatives = getAlternativeSpellings(alias);
|
|
166
|
-
for (const alt of alternatives) {
|
|
167
|
-
skill = await skillStore.get(alt);
|
|
168
|
-
if (skill) {
|
|
169
|
-
matchedAlias = alt;
|
|
170
|
-
break;
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
if (!skill) {
|
|
175
|
-
throw new Error(`Skill "${alias}" not found. Use skill("index") to list available skills.`);
|
|
176
|
-
}
|
|
177
|
-
// Return raw file content for non-index skills (preserve frontmatter)
|
|
178
|
-
const skillPath = path.join(SKILLS_PATH, `${matchedAlias}.md`);
|
|
179
|
-
let content = await fs.readFile(skillPath, "utf-8");
|
|
180
|
-
// If we matched via alternative spelling, add alias to indicate canonical name
|
|
181
|
-
if (matchedAlias !== alias) {
|
|
182
|
-
content = addAliasToFrontmatter(content, matchedAlias);
|
|
183
|
-
}
|
|
184
|
-
return content;
|
|
185
|
-
},
|
|
186
|
-
});
|
|
115
|
+
const skillService = createSkillService(skillStore);
|
|
187
116
|
// =============================================================================
|
|
188
117
|
// VERSION SERVICE
|
|
189
118
|
// =============================================================================
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../../../src/suites/docs/index.ts"],"sourcesContent":["/**\n * Docs Suite - Documentation services (skill, version, release_notes)\n */\nimport { fabricService } from \"@jaypie/fabric\";\nimport {\n createMarkdownStore,\n isValidAlias,\n normalizeAlias,\n type SkillRecord,\n} from \"@jaypie/tildeskill\";\nimport * as fs from \"node:fs/promises\";\nimport * as path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport matter from \"gray-matter\";\nimport { gt } from \"semver\";\n\n// Build-time constants\ndeclare const __BUILD_VERSION_STRING__: string;\nconst BUILD_VERSION_STRING =\n typeof __BUILD_VERSION_STRING__ !== \"undefined\"\n ? __BUILD_VERSION_STRING__\n : \"@jaypie/mcp@0.0.0\";\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\n// From dist/suites/docs/, go up 3 levels to package root where skills/ and release-notes/ live\n// Environment variables allow overriding paths when bundled (e.g., esbuild Lambda)\nconst RELEASE_NOTES_PATH =\n process.env.MCP_RELEASE_NOTES_PATH ||\n path.join(__dirname, \"..\", \"..\", \"..\", \"release-notes\");\nconst SKILLS_PATH =\n process.env.MCP_SKILLS_PATH ||\n path.join(__dirname, \"..\", \"..\", \"..\", \"skills\");\n\n// Create skill store using tildeskill\nconst skillStore = createMarkdownStore({ path: SKILLS_PATH });\n\n// =============================================================================\n// HELPER FUNCTIONS\n// =============================================================================\n\ninterface ReleaseNoteFrontMatter {\n date?: string;\n summary?: string;\n version?: string;\n}\n\nasync function parseReleaseNoteFile(filePath: string): Promise<{\n date?: string;\n filename: string;\n summary?: string;\n version?: string;\n}> {\n try {\n const content = await fs.readFile(filePath, \"utf-8\");\n const filename = path.basename(filePath, \".md\");\n\n if (content.startsWith(\"---\")) {\n const parsed = matter(content);\n const frontMatter = parsed.data as ReleaseNoteFrontMatter;\n return {\n date: frontMatter.date,\n filename,\n summary: frontMatter.summary,\n version: frontMatter.version || filename,\n };\n }\n\n return { filename, version: filename };\n } catch {\n return { filename: path.basename(filePath, \".md\") };\n }\n}\n\nfunction formatReleaseNoteListItem(note: {\n date?: string;\n filename: string;\n packageName: string;\n summary?: string;\n version?: string;\n}): string {\n const { date, packageName, summary, version } = note;\n const parts = [`* ${packageName}@${version}`];\n\n if (date) {\n parts.push(`(${date})`);\n }\n\n if (summary) {\n parts.push(`- ${summary}`);\n }\n\n return parts.join(\" \");\n}\n\nfunction formatSkillListItem(skill: SkillRecord): string {\n const { alias, description } = skill;\n if (description) {\n return `* ${alias} - ${description}`;\n }\n return `* ${alias}`;\n}\n\nasync function getPackageReleaseNotes(packageName: string): Promise<\n Array<{\n date?: string;\n filename: string;\n packageName: string;\n summary?: string;\n version?: string;\n }>\n> {\n const packageDir = path.join(RELEASE_NOTES_PATH, packageName);\n try {\n const files = await fs.readdir(packageDir);\n const mdFiles = files.filter((file) => file.endsWith(\".md\"));\n\n const notes = await Promise.all(\n mdFiles.map(async (file) => {\n const parsed = await parseReleaseNoteFile(path.join(packageDir, file));\n return { ...parsed, packageName };\n }),\n );\n\n return notes.sort((a, b) => {\n if (!a.version || !b.version) return 0;\n try {\n return gt(a.version, b.version) ? -1 : 1;\n } catch {\n return b.version.localeCompare(a.version);\n }\n });\n } catch {\n return [];\n }\n}\n\nfunction filterReleaseNotesSince(\n notes: Array<{\n date?: string;\n filename: string;\n packageName: string;\n summary?: string;\n version?: string;\n }>,\n sinceVersion: string,\n): Array<{\n date?: string;\n filename: string;\n packageName: string;\n summary?: string;\n version?: string;\n}> {\n return notes.filter((note) => {\n if (!note.version) return false;\n try {\n return gt(note.version, sinceVersion);\n } catch {\n return false;\n }\n });\n}\n\n// =============================================================================\n// SKILL SERVICE\n// =============================================================================\n\n/**\n * Generate alternative spellings for plural/singular matching\n */\nfunction getAlternativeSpellings(alias: string): string[] {\n const alternatives: string[] = [];\n\n if (alias.endsWith(\"es\")) {\n // \"indexes\" -> try \"indexe\" and \"index\"\n alternatives.push(alias.slice(0, -1)); // Remove \"s\" -> \"indexe\"\n alternatives.push(alias.slice(0, -2)); // Remove \"es\" -> \"index\"\n } else if (alias.endsWith(\"s\")) {\n // \"skills\" -> try \"skill\"\n alternatives.push(alias.slice(0, -1)); // Remove \"s\" -> \"skill\"\n } else {\n // \"fish\" -> try \"fishs\" and \"fishes\"\n alternatives.push(alias + \"s\");\n alternatives.push(alias + \"es\");\n }\n\n return alternatives;\n}\n\n/**\n * Add alias to frontmatter indicating the canonical skill name\n */\nfunction addAliasToFrontmatter(content: string, matchedAlias: string): string {\n if (content.startsWith(\"---\")) {\n // Find the end of frontmatter\n const endIndex = content.indexOf(\"---\", 3);\n if (endIndex !== -1) {\n // Insert alias before the closing ---\n const beforeClose = content.slice(0, endIndex);\n const afterClose = content.slice(endIndex);\n return `${beforeClose}alias: ${matchedAlias}\\n${afterClose}`;\n }\n }\n // No frontmatter exists, create one\n return `---\\nalias: ${matchedAlias}\\n---\\n\\n${content}`;\n}\n\nexport const skillService = fabricService({\n alias: \"skill\",\n description:\n \"Access Jaypie development documentation. Pass a skill alias (e.g., 'aws', 'tests', 'errors') to get that documentation. Pass 'index' or no argument to list all available skills.\",\n input: {\n alias: {\n description:\n \"Skill alias (e.g., 'aws', 'tests'). Omit or use 'index' to list all skills.\",\n required: false,\n type: String,\n },\n },\n service: async ({ alias: inputAlias }: { alias?: string }) => {\n const alias = normalizeAlias(inputAlias || \"index\");\n\n if (!isValidAlias(alias)) {\n throw new Error(\n `Invalid skill alias \"${alias}\". Use alphanumeric characters, hyphens, and underscores only.`,\n );\n }\n\n if (alias === \"index\") {\n const allSkills = await skillStore.list();\n const skills = allSkills.filter(\n (s: { alias: string }) => s.alias !== \"index\",\n );\n const skillList = skills.map(formatSkillListItem).join(\"\\n\");\n\n return `# Index of Skills\\n\\n${skillList}`;\n }\n\n // Try exact match first\n let skill = await skillStore.get(alias);\n let matchedAlias = alias;\n\n // If no exact match, try alternative spellings (plural/singular)\n if (!skill) {\n const alternatives = getAlternativeSpellings(alias);\n for (const alt of alternatives) {\n skill = await skillStore.get(alt);\n if (skill) {\n matchedAlias = alt;\n break;\n }\n }\n }\n\n if (!skill) {\n throw new Error(\n `Skill \"${alias}\" not found. Use skill(\"index\") to list available skills.`,\n );\n }\n\n // Return raw file content for non-index skills (preserve frontmatter)\n const skillPath = path.join(SKILLS_PATH, `${matchedAlias}.md`);\n let content = await fs.readFile(skillPath, \"utf-8\");\n\n // If we matched via alternative spelling, add alias to indicate canonical name\n if (matchedAlias !== alias) {\n content = addAliasToFrontmatter(content, matchedAlias);\n }\n\n return content;\n },\n});\n\n// =============================================================================\n// VERSION SERVICE\n// =============================================================================\n\nexport const versionService = fabricService({\n alias: \"version\",\n description: `Prints the current version and hash, \\`${BUILD_VERSION_STRING}\\``,\n input: {},\n service: async () => BUILD_VERSION_STRING,\n});\n\n// =============================================================================\n// RELEASE NOTES SERVICE\n// =============================================================================\n\nasync function getReleaseNotesHelp(): Promise<string> {\n return fs.readFile(path.join(__dirname, \"release-notes\", \"help.md\"), \"utf-8\");\n}\n\ninterface ReleaseNotesInput {\n package?: string;\n since_version?: string;\n version?: string;\n}\n\nexport const releaseNotesService = fabricService({\n alias: \"release_notes\",\n description:\n \"Browse Jaypie package release notes. Commands: list, read. Call with no args for help.\",\n input: {\n command: {\n description: \"Command to execute (omit for help)\",\n required: false,\n type: String,\n },\n input: {\n description: \"Command parameters\",\n required: false,\n type: Object,\n },\n },\n service: async ({\n command,\n input: params,\n }: {\n command?: string;\n input?: ReleaseNotesInput;\n }) => {\n if (!command || command === \"help\") {\n return getReleaseNotesHelp();\n }\n\n const p = params || {};\n\n switch (command) {\n case \"list\": {\n const entries = await fs.readdir(RELEASE_NOTES_PATH, {\n withFileTypes: true,\n });\n const packageDirs = entries\n .filter((entry) => entry.isDirectory())\n .map((entry) => entry.name);\n const packagesToList = p.package\n ? packageDirs.filter((pkg) => pkg === p.package)\n : packageDirs;\n\n if (packagesToList.length === 0 && p.package) {\n return `No release notes found for package \"${p.package}\".`;\n }\n\n const allNotes = await Promise.all(\n packagesToList.map((pkg) => getPackageReleaseNotes(pkg)),\n );\n let flatNotes = allNotes.flat();\n\n if (p.since_version) {\n flatNotes = filterReleaseNotesSince(flatNotes, p.since_version);\n }\n\n if (flatNotes.length === 0) {\n const filterDesc = p.since_version\n ? ` newer than ${p.since_version}`\n : \"\";\n return `No release notes found${filterDesc}.`;\n }\n\n return flatNotes.map(formatReleaseNoteListItem).join(\"\\n\");\n }\n\n case \"read\": {\n if (!p.package) throw new Error(\"package is required\");\n if (!p.version) throw new Error(\"version is required\");\n const filePath = path.join(\n RELEASE_NOTES_PATH,\n p.package,\n `${p.version}.md`,\n );\n return fs.readFile(filePath, \"utf-8\");\n }\n\n default:\n throw new Error(\n `Unknown command: ${command}. Use release_notes() for help.`,\n );\n }\n },\n});\n"],"names":["__filename","__dirname"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../../../src/suites/docs/index.ts"],"sourcesContent":["/**\n * Docs Suite - Documentation services (skill, version, release_notes)\n */\nimport { fabricService } from \"@jaypie/fabric\";\nimport {\n createLayeredStore,\n createMarkdownStore,\n createSkillService,\n type LayeredStoreLayer,\n} from \"@jaypie/tildeskill\";\nimport * as fs from \"node:fs/promises\";\nimport * as path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport matter from \"gray-matter\";\nimport { gt } from \"semver\";\n\n// Build-time constants\ndeclare const __BUILD_VERSION_STRING__: string;\nconst BUILD_VERSION_STRING =\n typeof __BUILD_VERSION_STRING__ !== \"undefined\"\n ? __BUILD_VERSION_STRING__\n : \"@jaypie/mcp@0.0.0\";\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\n// From dist/suites/docs/, go up 3 levels to package root where skills/ and release-notes/ live\n// Environment variables allow overriding paths when bundled (e.g., esbuild Lambda)\nconst RELEASE_NOTES_PATH =\n process.env.MCP_RELEASE_NOTES_PATH ||\n path.join(__dirname, \"..\", \"..\", \"..\", \"release-notes\");\n\n// Bundled Jaypie skills ship inside the @jaypie/mcp package. MCP_BUILTIN_SKILLS_PATH\n// lets bundlers (esbuild, Lambda) relocate them without disabling the built-in layer.\nconst BUILTIN_SKILLS_PATH =\n process.env.MCP_BUILTIN_SKILLS_PATH ||\n path.join(__dirname, \"..\", \"..\", \"..\", \"skills\");\n\nconst LOCAL_SKILLS_NAMESPACE = \"local\";\nconst JAYPIE_SKILLS_NAMESPACE = \"jaypie\";\nconst LAYER_SEPARATOR = \":\";\n\n// Skill layers resolved in order: a client's MCP_SKILLS_PATH layers on top of\n// the built-in Jaypie skill pack so `skill(\"aws\")` prefers the client's copy\n// while still exposing bundled Jaypie docs under the `jaypie:` namespace.\nconst skillLayers: LayeredStoreLayer[] = [];\n\nif (process.env.MCP_SKILLS_PATH) {\n skillLayers.push({\n namespace: LOCAL_SKILLS_NAMESPACE,\n store: createMarkdownStore({ path: process.env.MCP_SKILLS_PATH }),\n });\n}\n\nskillLayers.push({\n namespace: JAYPIE_SKILLS_NAMESPACE,\n store: createMarkdownStore({ path: BUILTIN_SKILLS_PATH }),\n});\n\nconst skillStore = createLayeredStore({\n layers: skillLayers,\n separator: LAYER_SEPARATOR,\n});\n\n// =============================================================================\n// HELPER FUNCTIONS\n// =============================================================================\n\ninterface ReleaseNoteFrontMatter {\n date?: string;\n summary?: string;\n version?: string;\n}\n\nasync function parseReleaseNoteFile(filePath: string): Promise<{\n date?: string;\n filename: string;\n summary?: string;\n version?: string;\n}> {\n try {\n const content = await fs.readFile(filePath, \"utf-8\");\n const filename = path.basename(filePath, \".md\");\n\n if (content.startsWith(\"---\")) {\n const parsed = matter(content);\n const frontMatter = parsed.data as ReleaseNoteFrontMatter;\n return {\n date: frontMatter.date,\n filename,\n summary: frontMatter.summary,\n version: frontMatter.version || filename,\n };\n }\n\n return { filename, version: filename };\n } catch {\n return { filename: path.basename(filePath, \".md\") };\n }\n}\n\nfunction formatReleaseNoteListItem(note: {\n date?: string;\n filename: string;\n packageName: string;\n summary?: string;\n version?: string;\n}): string {\n const { date, packageName, summary, version } = note;\n const parts = [`* ${packageName}@${version}`];\n\n if (date) {\n parts.push(`(${date})`);\n }\n\n if (summary) {\n parts.push(`- ${summary}`);\n }\n\n return parts.join(\" \");\n}\n\nasync function getPackageReleaseNotes(packageName: string): Promise<\n Array<{\n date?: string;\n filename: string;\n packageName: string;\n summary?: string;\n version?: string;\n }>\n> {\n const packageDir = path.join(RELEASE_NOTES_PATH, packageName);\n try {\n const files = await fs.readdir(packageDir);\n const mdFiles = files.filter((file) => file.endsWith(\".md\"));\n\n const notes = await Promise.all(\n mdFiles.map(async (file) => {\n const parsed = await parseReleaseNoteFile(path.join(packageDir, file));\n return { ...parsed, packageName };\n }),\n );\n\n return notes.sort((a, b) => {\n if (!a.version || !b.version) return 0;\n try {\n return gt(a.version, b.version) ? -1 : 1;\n } catch {\n return b.version.localeCompare(a.version);\n }\n });\n } catch {\n return [];\n }\n}\n\nfunction filterReleaseNotesSince(\n notes: Array<{\n date?: string;\n filename: string;\n packageName: string;\n summary?: string;\n version?: string;\n }>,\n sinceVersion: string,\n): Array<{\n date?: string;\n filename: string;\n packageName: string;\n summary?: string;\n version?: string;\n}> {\n return notes.filter((note) => {\n if (!note.version) return false;\n try {\n return gt(note.version, sinceVersion);\n } catch {\n return false;\n }\n });\n}\n\n// =============================================================================\n// SKILL SERVICE\n// =============================================================================\n\nexport const skillService = createSkillService(skillStore);\n\n// =============================================================================\n// VERSION SERVICE\n// =============================================================================\n\nexport const versionService = fabricService({\n alias: \"version\",\n description: `Prints the current version and hash, \\`${BUILD_VERSION_STRING}\\``,\n input: {},\n service: async () => BUILD_VERSION_STRING,\n});\n\n// =============================================================================\n// RELEASE NOTES SERVICE\n// =============================================================================\n\nasync function getReleaseNotesHelp(): Promise<string> {\n return fs.readFile(path.join(__dirname, \"release-notes\", \"help.md\"), \"utf-8\");\n}\n\ninterface ReleaseNotesInput {\n package?: string;\n since_version?: string;\n version?: string;\n}\n\nexport const releaseNotesService = fabricService({\n alias: \"release_notes\",\n description:\n \"Browse Jaypie package release notes. Commands: list, read. Call with no args for help.\",\n input: {\n command: {\n description: \"Command to execute (omit for help)\",\n required: false,\n type: String,\n },\n input: {\n description: \"Command parameters\",\n required: false,\n type: Object,\n },\n },\n service: async ({\n command,\n input: params,\n }: {\n command?: string;\n input?: ReleaseNotesInput;\n }) => {\n if (!command || command === \"help\") {\n return getReleaseNotesHelp();\n }\n\n const p = params || {};\n\n switch (command) {\n case \"list\": {\n const entries = await fs.readdir(RELEASE_NOTES_PATH, {\n withFileTypes: true,\n });\n const packageDirs = entries\n .filter((entry) => entry.isDirectory())\n .map((entry) => entry.name);\n const packagesToList = p.package\n ? packageDirs.filter((pkg) => pkg === p.package)\n : packageDirs;\n\n if (packagesToList.length === 0 && p.package) {\n return `No release notes found for package \"${p.package}\".`;\n }\n\n const allNotes = await Promise.all(\n packagesToList.map((pkg) => getPackageReleaseNotes(pkg)),\n );\n let flatNotes = allNotes.flat();\n\n if (p.since_version) {\n flatNotes = filterReleaseNotesSince(flatNotes, p.since_version);\n }\n\n if (flatNotes.length === 0) {\n const filterDesc = p.since_version\n ? ` newer than ${p.since_version}`\n : \"\";\n return `No release notes found${filterDesc}.`;\n }\n\n return flatNotes.map(formatReleaseNoteListItem).join(\"\\n\");\n }\n\n case \"read\": {\n if (!p.package) throw new Error(\"package is required\");\n if (!p.version) throw new Error(\"version is required\");\n const filePath = path.join(\n RELEASE_NOTES_PATH,\n p.package,\n `${p.version}.md`,\n );\n return fs.readFile(filePath, \"utf-8\");\n }\n\n default:\n throw new Error(\n `Unknown command: ${command}. Use release_notes() for help.`,\n );\n }\n },\n});\n"],"names":["__filename","__dirname"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jaypie/mcp",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.29",
|
|
4
4
|
"description": "Jaypie MCP",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
42
|
"@jaypie/fabric": "^0.2.4",
|
|
43
|
-
"@jaypie/tildeskill": "^0.
|
|
43
|
+
"@jaypie/tildeskill": "^0.3.1",
|
|
44
44
|
"@modelcontextprotocol/sdk": "^1.17.0",
|
|
45
45
|
"commander": "^14.0.0",
|
|
46
46
|
"gray-matter": "^4.0.3",
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
---
|
|
2
|
+
version: "0.5.0"
|
|
3
|
+
date: "2026-04-11"
|
|
4
|
+
summary: "Breaking: model-keyed GSIs, pk=id, updatedAt as sort key, auto-bump timestamps"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# @jaypie/dynamodb 0.5.0
|
|
8
|
+
|
|
9
|
+
**Tables must be recreated.** This is a pre-1.0 breaking change that restructures the primary key, GSI schema, and timestamp management.
|
|
10
|
+
|
|
11
|
+
## Breaking Changes
|
|
12
|
+
|
|
13
|
+
### Primary Key
|
|
14
|
+
|
|
15
|
+
- **Old**: pk=`model`, sk=`id`
|
|
16
|
+
- **New**: pk=`id` only (no sort key)
|
|
17
|
+
|
|
18
|
+
Entity operations `getEntity`, `deleteEntity`, `archiveEntity`, and `destroyEntity` now take `{ id }` instead of `{ id, model }`.
|
|
19
|
+
|
|
20
|
+
### GSI Schema
|
|
21
|
+
|
|
22
|
+
GSIs are now model-keyed with composite sort keys instead of scope-keyed with `sequence`:
|
|
23
|
+
|
|
24
|
+
| Old GSI | Old pk pattern | New GSI | New pk pattern |
|
|
25
|
+
|---------|---------------|---------|---------------|
|
|
26
|
+
| indexScope | `{scope}#{model}` | indexModel | `{model}` |
|
|
27
|
+
| indexAlias | `{scope}#{model}#{alias}` | indexModelAlias | `{model}#{alias}` |
|
|
28
|
+
| indexCategory | `{scope}#{model}#{category}` | indexModelCategory | `{model}#{category}` |
|
|
29
|
+
| indexType | `{scope}#{model}#{type}` | indexModelType | `{model}#{type}` |
|
|
30
|
+
| indexXid | `{scope}#{model}#{xid}` | indexModelXid | `{model}#{xid}` |
|
|
31
|
+
|
|
32
|
+
All GSIs now use a composite sort key: `{scope}#{updatedAt}` (stored as `{indexName}Sk`). Queries use `begins_with` on the sk composite to filter by scope.
|
|
33
|
+
|
|
34
|
+
### Removed
|
|
35
|
+
|
|
36
|
+
- **`sequence` field** -- ordering is now by `updatedAt` in the composite sk
|
|
37
|
+
- **Key builder functions**: `buildIndexScope`, `buildIndexAlias`, `buildIndexCategory`, `buildIndexType`, `buildIndexXid` -- use `buildCompositeKey` instead
|
|
38
|
+
- **Index name constants**: `INDEX_ALIAS`, `INDEX_CATEGORY`, `INDEX_SCOPE`, `INDEX_TYPE`, `INDEX_XID`
|
|
39
|
+
- **Implicit `DEFAULT_INDEXES` fallback** -- models must be registered with `registerModel()` and `fabricIndex()` before querying
|
|
40
|
+
|
|
41
|
+
### Changed Signatures
|
|
42
|
+
|
|
43
|
+
| Function | Old | New |
|
|
44
|
+
|----------|-----|-----|
|
|
45
|
+
| `getEntity` | `{ id, model }` | `{ id }` |
|
|
46
|
+
| `deleteEntity` | `{ id, model }` | `{ id }` |
|
|
47
|
+
| `archiveEntity` | `{ id, model }` | `{ id }` |
|
|
48
|
+
| `destroyEntity` | `{ id, model }` | `{ id }` |
|
|
49
|
+
| `queryByScope` | `{ model, scope }` (scope required) | `{ model, scope? }` (scope optional) |
|
|
50
|
+
| `queryByAlias` | `{ alias, model, scope }` (scope required) | `{ alias, model, scope? }` (scope optional) |
|
|
51
|
+
| `queryByCategory` | `{ model, scope, category }` | `{ model, category, scope? }` -- throws `ConfigurationError` if model hasn't registered `fabricIndex("category")` |
|
|
52
|
+
| `queryByType` | `{ model, scope, type }` | `{ model, type, scope? }` -- throws `ConfigurationError` if model hasn't registered `fabricIndex("type")` |
|
|
53
|
+
|
|
54
|
+
### Timestamp Management
|
|
55
|
+
|
|
56
|
+
`indexEntity` now manages `updatedAt` and `createdAt` automatically on every write:
|
|
57
|
+
- `updatedAt` is bumped to `new Date().toISOString()` on every call
|
|
58
|
+
- `createdAt` is backfilled to the same timestamp if not already set
|
|
59
|
+
- Callers should no longer set these fields manually
|
|
60
|
+
|
|
61
|
+
## Migration
|
|
62
|
+
|
|
63
|
+
1. Register model indexes using `fabricIndex()` from `@jaypie/fabric`
|
|
64
|
+
2. Recreate DynamoDB tables (primary key and GSI shapes have changed)
|
|
65
|
+
3. Remove `sequence` from entity creation code
|
|
66
|
+
4. Remove `model` parameter from `getEntity`, `deleteEntity`, `archiveEntity`, `destroyEntity` calls
|
|
67
|
+
5. Remove manual `createdAt`/`updatedAt` assignment
|
|
68
|
+
6. Replace old key builder functions with `buildCompositeKey`
|
|
69
|
+
7. Optionally make `scope` omitted in query calls to query across all scopes
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
---
|
|
2
|
+
version: "0.3.0"
|
|
3
|
+
date: "2026-04-11"
|
|
4
|
+
summary: "Breaking: fabricIndex factory, remove DEFAULT_INDEXES, drop sequence, composite sk support"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# @jaypie/fabric 0.3.0
|
|
8
|
+
|
|
9
|
+
## Breaking Changes
|
|
10
|
+
|
|
11
|
+
- **Removed `DEFAULT_INDEXES`** export and the implicit fallback in `getModelIndexes()`. Models must now be registered with `registerModel()` before indexing or querying. Calling `getModelIndexes()` on an unregistered model throws `ConfigurationError`.
|
|
12
|
+
- **Removed `sequence` field** from `FabricModel` and `IndexableModel`. Sort ordering is now by `updatedAt` via composite sort keys.
|
|
13
|
+
- **Removed `DEFAULT_SORT_KEY`** constant (was `"sequence"`).
|
|
14
|
+
|
|
15
|
+
## New Features
|
|
16
|
+
|
|
17
|
+
- **`fabricIndex(field?)`** factory function for building canonical Jaypie GSI definitions:
|
|
18
|
+
- `fabricIndex()` produces `indexModel` with pk=`["model"]`, sk=`["scope", "updatedAt"]`
|
|
19
|
+
- `fabricIndex("alias")` produces `indexModelAlias` with pk=`["model", "alias"]`, sk=`["scope", "updatedAt"]`, sparse
|
|
20
|
+
- Works for any field: `fabricIndex("category")`, `fabricIndex("xid")`, `fabricIndex("customField")`
|
|
21
|
+
- **`getGsiAttributeNames(index)`** returns `{ pk, sk }` attribute names for an index definition. Single source of truth for GSI provisioning and query attribute references.
|
|
22
|
+
- **Composite sk support in `populateIndexKeys`**: when `sk.length > 1`, writes a composite sk attribute named `{indexName}Sk` (e.g., `indexModelSk = "scope#updatedAt"`). When `sk.length === 1`, the single field is used directly as the GSI sort key (no extra attribute written).
|
|
23
|
+
|
|
24
|
+
## Migration
|
|
25
|
+
|
|
26
|
+
Replace `DEFAULT_INDEXES` usage with explicit `fabricIndex()` calls:
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
import { fabricIndex, registerModel } from "@jaypie/fabric";
|
|
30
|
+
|
|
31
|
+
registerModel({
|
|
32
|
+
model: "record",
|
|
33
|
+
indexes: [
|
|
34
|
+
fabricIndex(), // indexModel
|
|
35
|
+
fabricIndex("alias"), // indexModelAlias
|
|
36
|
+
fabricIndex("category"), // indexModelCategory
|
|
37
|
+
],
|
|
38
|
+
});
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Remove any references to `sequence` from entity creation or update code. Ordering is now by `updatedAt`, which is managed automatically by `indexEntity`.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
---
|
|
2
|
+
version: 0.8.26
|
|
3
|
+
date: 2026-04-11
|
|
4
|
+
summary: Delegate skill plural/singular resolution to @jaypie/tildeskill find()
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Changes
|
|
8
|
+
|
|
9
|
+
- Skill service now calls `skillStore.find(alias)` from `@jaypie/tildeskill@0.2.1` instead of duplicating the plural/singular fallback loop
|
|
10
|
+
- No behavior change for callers; `skill("indexes")`, `skill("skills")`, etc. still resolve to their canonical files
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
---
|
|
2
|
+
version: 0.8.27
|
|
3
|
+
date: 2026-04-11
|
|
4
|
+
summary: Layer MCP_SKILLS_PATH over the bundled Jaypie skill pack via createLayeredStore
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Changes
|
|
8
|
+
|
|
9
|
+
- The skill service now uses `createLayeredStore` from `@jaypie/tildeskill@0.3.0`, composing two layers:
|
|
10
|
+
- `local` — populated from `MCP_SKILLS_PATH` when set.
|
|
11
|
+
- `jaypie` — always present; sources the bundled `packages/mcp/skills/` directory.
|
|
12
|
+
- `skill("aws")` still resolves to the first layer that has it, so a client's local copy wins over the bundled version. Use `skill("jaypie:aws")` or `skill("local:aws")` to target a specific layer. `skill("index")` now emits namespace-prefixed entries (`local:foo`, `jaypie:bar`).
|
|
13
|
+
- New env var `MCP_BUILTIN_SKILLS_PATH` overrides the bundled skills directory for esbuild/Lambda bundling without disabling the layered composition.
|
|
14
|
+
|
|
15
|
+
## Breaking behavior change
|
|
16
|
+
|
|
17
|
+
Previously `MCP_SKILLS_PATH` **replaced** the bundled skill directory. It now **augments** it as the top `local:` layer. To keep the old "relocate bundled skills" pattern working for Lambda bundlers, set `MCP_BUILTIN_SKILLS_PATH` instead.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
---
|
|
2
|
+
version: 0.8.29
|
|
3
|
+
date: 2026-04-12
|
|
4
|
+
summary: Delegate skill service to @jaypie/tildeskill createSkillService
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Changes
|
|
8
|
+
|
|
9
|
+
- Replace inline `skillService` with `createSkillService(skillStore)` from `@jaypie/tildeskill`
|
|
10
|
+
- Skill lookups now automatically expand includes
|
|
11
|
+
- Plural/singular fallback annotates with `<!-- resolved: -->` HTML comment instead of injecting frontmatter
|
|
12
|
+
- Remove raw file reading, `addAliasToFrontmatter`, `formatSkillListItem`, and `skillLayerPaths` from MCP
|
|
13
|
+
- Bump `@jaypie/tildeskill` dependency to `^0.3.1`
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
---
|
|
2
|
+
version: 1.2.30
|
|
3
|
+
date: 2026-04-11
|
|
4
|
+
summary: Mock createLayeredStore and update getByNickname mock to return an array
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Changes
|
|
8
|
+
|
|
9
|
+
- Added a mock `createLayeredStore` export that returns the standard mock `SkillStore` shape.
|
|
10
|
+
- `SkillStore.getByNickname` mock now resolves to `[]` instead of `null` to match the `Promise<SkillRecord[]>` signature introduced in `@jaypie/tildeskill@0.3.0`.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
---
|
|
2
|
+
version: 0.2.1
|
|
3
|
+
date: 2026-04-11
|
|
4
|
+
summary: Add SkillStore.find with plural/singular fallback and export getAlternativeSpellings
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Changes
|
|
8
|
+
|
|
9
|
+
- Add `SkillStore.find(alias)` that tries an exact lookup then alternative plural/singular spellings, returning the matched record (check `record.alias` to detect a fallback match)
|
|
10
|
+
- Export `getAlternativeSpellings(alias)` core utility for callers that want the candidate list directly
|
|
11
|
+
- Implemented on both `createMarkdownStore` and `createMemoryStore`
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
---
|
|
2
|
+
version: 0.3.0
|
|
3
|
+
date: 2026-04-11
|
|
4
|
+
summary: Layered skill stores with namespace prefixes; getByNickname now returns all matches
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Changes
|
|
8
|
+
|
|
9
|
+
- **New `createLayeredStore`** — compose ordered child stores with namespace prefixes (e.g., `local`, `jaypie`). `get`/`find` walk layers top-to-bottom and return the first match with its alias namespaced (`local:aws`). Namespace-qualified inputs (`jaypie:aws`) route directly to that layer. `list`/`search` aggregate every layer. `put` requires a namespace-qualified alias and delegates to the matching layer.
|
|
10
|
+
- **New types** `LayeredStoreLayer` and `LayeredStoreOptions` exported from the package root.
|
|
11
|
+
- **`SkillStore.getByNickname` now returns `Promise<SkillRecord[]>`** (breaking signature change). Returns every record whose `nicknames` list contains the query instead of only the first. A layered store aggregates matches across every layer so a nickname like `sparticus` can resolve to several skills at once.
|
|
12
|
+
- **`isValidAlias` accepts `:`** so callers can validate namespace-qualified inputs destined for a layered store. Path traversal guards (`..`, `/`, `\\`) are unchanged.
|
|
13
|
+
|
|
14
|
+
## Breaking changes
|
|
15
|
+
|
|
16
|
+
- `getByNickname` return type changed from `SkillRecord | null` to `SkillRecord[]`. Update callers:
|
|
17
|
+
|
|
18
|
+
```diff
|
|
19
|
+
- const skill = await store.getByNickname("amazon");
|
|
20
|
+
- if (skill) console.log(skill.alias);
|
|
21
|
+
+ const matches = await store.getByNickname("amazon");
|
|
22
|
+
+ matches.forEach((m) => console.log(m.alias));
|
|
23
|
+
```
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
---
|
|
2
|
+
version: 0.3.1
|
|
3
|
+
date: 2026-04-12
|
|
4
|
+
summary: Add createSkillService factory for reusable skill lookup as a fabricService
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Changes
|
|
8
|
+
|
|
9
|
+
- Add `createSkillService(store)` factory that returns a `fabricService` wrapping any `SkillStore`
|
|
10
|
+
- The service handles index listing, alias validation, `find()` with plural/singular fallback, and automatic `expandIncludes`
|
|
11
|
+
- Compatible with `fabricTool()` for `Llm.operate` toolkits and `suite.register()` for MCP servers
|
|
12
|
+
- Add `@jaypie/fabric` as a dependency (lightweight; only requires `@jaypie/errors`)
|
package/skills/dynamodb.md
CHANGED
|
@@ -7,34 +7,24 @@ related: apikey, aws, cdk, models, vocabulary
|
|
|
7
7
|
|
|
8
8
|
Jaypie provides `@jaypie/dynamodb` for single-table DynamoDB with entity operations, GSI-based queries, hierarchical scoping, and soft delete. Access through the main `jaypie` package or directly.
|
|
9
9
|
|
|
10
|
-
## Key
|
|
10
|
+
## Key Design
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
### Primary Key: `id` Only
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
`JaypieDynamoDb` creates tables with `model` (partition key) and `id` (sort key) by default. This is the **recommended convention for new Jaypie tables** and the convention used by `@jaypie/dynamodb`:
|
|
14
|
+
`JaypieDynamoDb` creates tables with `id` as the sole partition key (no sort key). `model` and `scope` are regular attributes used in GSI partition keys:
|
|
17
15
|
|
|
18
16
|
```typescript
|
|
19
17
|
const table = new JaypieDynamoDb(this, "myApp");
|
|
20
|
-
// Creates table with:
|
|
18
|
+
// Creates table with: id (HASH) — no sort key
|
|
21
19
|
```
|
|
22
20
|
|
|
23
21
|
Items look like:
|
|
24
22
|
|
|
25
23
|
```typescript
|
|
26
|
-
{
|
|
27
|
-
{
|
|
24
|
+
{ id: "u_abc123", model: "user", name: "John", scope: "@" }
|
|
25
|
+
{ id: "a1b2c3d4", model: "apikey", scope: "user#u_abc123" }
|
|
28
26
|
```
|
|
29
27
|
|
|
30
|
-
### Generic DynamoDB: `pk` / `sk`
|
|
31
|
-
|
|
32
|
-
The `pk` / `sk` pattern is the standard DynamoDB convention used in broader DynamoDB literature. Jaypie uses it in local development scripts and educational examples. It's functionally equivalent — just different attribute names.
|
|
33
|
-
|
|
34
|
-
### Entity Prefixing (Value Pattern)
|
|
35
|
-
|
|
36
|
-
Entity prefixes like `USER#123` are a **value-level pattern** (how you structure the data stored in keys), not an attribute naming convention. You can use entity prefixes with either `model`/`id` or `pk`/`sk` attribute names.
|
|
37
|
-
|
|
38
28
|
## @jaypie/dynamodb Package
|
|
39
29
|
|
|
40
30
|
The runtime package provides entity operations, GSI-based queries, key builders, and client management.
|
|
@@ -99,30 +89,26 @@ All entities follow this shape:
|
|
|
99
89
|
```typescript
|
|
100
90
|
interface StorableEntity {
|
|
101
91
|
// Primary Key
|
|
102
|
-
|
|
103
|
-
id: string; // Sort key (UUID)
|
|
92
|
+
id: string; // Partition key (UUID)
|
|
104
93
|
|
|
105
94
|
// Required
|
|
106
|
-
|
|
95
|
+
model: string; // Entity model name (e.g., "record", "message")
|
|
107
96
|
scope: string; // APEX ("@") or "{parent.model}#{parent.id}"
|
|
108
|
-
sequence: number; // Date.now() for chronological ordering
|
|
109
97
|
|
|
110
|
-
// Optional
|
|
98
|
+
// Optional identity
|
|
99
|
+
name?: string;
|
|
111
100
|
alias?: string; // Human-friendly slug
|
|
112
101
|
category?: string; // Category for filtering
|
|
113
102
|
type?: string; // Type for filtering
|
|
114
103
|
xid?: string; // External ID for cross-system lookup
|
|
115
104
|
|
|
116
|
-
// GSI Keys (auto-populated by
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
indexScope?: string;
|
|
120
|
-
indexType?: string;
|
|
121
|
-
indexXid?: string;
|
|
105
|
+
// GSI Keys (auto-populated by indexEntity on every write)
|
|
106
|
+
// indexModel, indexModelAlias, indexModelCategory, etc.
|
|
107
|
+
// indexModelSk, indexModelAliasSk, etc. (composite sort keys)
|
|
122
108
|
|
|
123
|
-
// Timestamps (ISO 8601)
|
|
124
|
-
createdAt
|
|
125
|
-
updatedAt
|
|
109
|
+
// Timestamps (ISO 8601) — managed by indexEntity
|
|
110
|
+
createdAt?: string; // Backfilled on first write
|
|
111
|
+
updatedAt?: string; // Bumped on every write
|
|
126
112
|
archivedAt?: string;
|
|
127
113
|
deletedAt?: string;
|
|
128
114
|
|
|
@@ -137,50 +123,46 @@ interface StorableEntity {
|
|
|
137
123
|
```typescript
|
|
138
124
|
import { APEX, putEntity, getEntity, updateEntity, deleteEntity, archiveEntity, destroyEntity } from "@jaypie/dynamodb";
|
|
139
125
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
// Create entity — auto-populates GSI keys
|
|
126
|
+
// Create entity — indexEntity auto-populates GSI keys, createdAt, updatedAt
|
|
143
127
|
const record = await putEntity({
|
|
144
128
|
entity: {
|
|
145
129
|
model: "record",
|
|
146
130
|
id: crypto.randomUUID(),
|
|
147
131
|
name: "Daily Log",
|
|
148
132
|
scope: APEX,
|
|
149
|
-
sequence: Date.now(),
|
|
150
133
|
alias: "2026-01-07",
|
|
151
134
|
category: "memory",
|
|
152
|
-
createdAt: now,
|
|
153
|
-
updatedAt: now,
|
|
154
135
|
},
|
|
155
136
|
});
|
|
156
|
-
//
|
|
157
|
-
//
|
|
158
|
-
//
|
|
137
|
+
// indexModel: "record" (auto-populated)
|
|
138
|
+
// indexModelAlias: "record#2026-01-07" (auto-populated)
|
|
139
|
+
// indexModelCategory: "record#memory" (auto-populated)
|
|
140
|
+
// indexModelSk: "@#2026-01-07T..." (auto-populated)
|
|
159
141
|
|
|
160
|
-
// Get by primary key
|
|
161
|
-
const item = await getEntity({ id: "abc-123"
|
|
142
|
+
// Get by primary key (id only)
|
|
143
|
+
const item = await getEntity({ id: "abc-123" });
|
|
162
144
|
|
|
163
145
|
// Update — sets updatedAt, re-indexes
|
|
164
146
|
await updateEntity({ entity: { ...item, name: "Updated Name" } });
|
|
165
147
|
|
|
166
|
-
// Soft delete — sets deletedAt, re-indexes with #deleted suffix
|
|
167
|
-
await deleteEntity({ id: "abc-123"
|
|
148
|
+
// Soft delete — sets deletedAt, re-indexes with #deleted suffix on pk
|
|
149
|
+
await deleteEntity({ id: "abc-123" });
|
|
168
150
|
|
|
169
|
-
// Archive — sets archivedAt, re-indexes with #archived suffix
|
|
170
|
-
await archiveEntity({ id: "abc-123"
|
|
151
|
+
// Archive — sets archivedAt, re-indexes with #archived suffix on pk
|
|
152
|
+
await archiveEntity({ id: "abc-123" });
|
|
171
153
|
|
|
172
154
|
// Hard delete — permanently removes
|
|
173
|
-
await destroyEntity({ id: "abc-123"
|
|
155
|
+
await destroyEntity({ id: "abc-123" });
|
|
174
156
|
```
|
|
175
157
|
|
|
176
158
|
| Function | Description |
|
|
177
159
|
|----------|-------------|
|
|
178
|
-
| `putEntity({ entity })` | Create or replace (auto-indexes GSI keys) |
|
|
179
|
-
| `getEntity({ id
|
|
160
|
+
| `putEntity({ entity })` | Create or replace (auto-indexes GSI keys, auto-timestamps) |
|
|
161
|
+
| `getEntity({ id })` | Get by primary key (id only) |
|
|
180
162
|
| `updateEntity({ entity })` | Update (sets `updatedAt`, re-indexes) |
|
|
181
|
-
| `deleteEntity({ id
|
|
182
|
-
| `archiveEntity({ id
|
|
183
|
-
| `destroyEntity({ id
|
|
163
|
+
| `deleteEntity({ id })` | Soft delete (`deletedAt`, `#deleted` suffix on GSI pk) |
|
|
164
|
+
| `archiveEntity({ id })` | Archive (`archivedAt`, `#archived` suffix on GSI pk) |
|
|
165
|
+
| `destroyEntity({ id })` | Hard delete (permanent) |
|
|
184
166
|
|
|
185
167
|
### Scope and Hierarchy
|
|
186
168
|
|
|
@@ -209,31 +191,48 @@ const { items: chats } = await queryByScope({ model: "chat", scope: APEX });
|
|
|
209
191
|
|
|
210
192
|
### GSI Schema
|
|
211
193
|
|
|
212
|
-
|
|
194
|
+
GSIs are defined using `fabricIndex()` from `@jaypie/fabric`. **Do not create all GSIs upfront** — start with zero and add only what your access patterns require. The most common first GSI is `indexModel` for listing entities by model.
|
|
213
195
|
|
|
214
196
|
**Important:** DynamoDB allows only **one GSI to be added per deployment**. If you need multiple GSIs, add them sequentially across separate deploys. For production tables, the AWS CLI is often better suited for adding GSIs than CDK (which may try to replace the table).
|
|
215
197
|
|
|
216
|
-
All GSIs use `
|
|
198
|
+
All GSIs use a composite sort key of `scope#updatedAt` (stored as `{indexName}Sk`). Queries use `begins_with` on the sk to filter by scope; omitting scope lists across all scopes.
|
|
199
|
+
|
|
200
|
+
| GSI Name | Partition Key Pattern | Sort Key | Purpose | Add When |
|
|
201
|
+
|----------|----------------------|----------|---------|----------|
|
|
202
|
+
| `indexModel` | `{model}` | `indexModelSk` = `{scope}#{updatedAt}` | List entities by model | You need to list/query by model |
|
|
203
|
+
| `indexModelAlias` | `{model}#{alias}` (sparse) | `indexModelAliasSk` = `{scope}#{updatedAt}` | Human-friendly slug lookup | You need slug-based lookups |
|
|
204
|
+
| `indexModelCategory` | `{model}#{category}` (sparse) | `indexModelCategorySk` = `{scope}#{updatedAt}` | Category filtering | You need to filter by category |
|
|
205
|
+
| `indexModelType` | `{model}#{type}` (sparse) | `indexModelTypeSk` = `{scope}#{updatedAt}` | Type filtering | You need to filter by type |
|
|
206
|
+
| `indexModelXid` | `{model}#{xid}` (sparse) | `indexModelXidSk` = `{scope}#{updatedAt}` | External ID lookup | You need cross-system ID lookups |
|
|
217
207
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
208
|
+
```typescript
|
|
209
|
+
import { fabricIndex, registerModel } from "@jaypie/fabric";
|
|
210
|
+
|
|
211
|
+
// Register model indexes (must happen before any queries)
|
|
212
|
+
registerModel({
|
|
213
|
+
model: "record",
|
|
214
|
+
indexes: [
|
|
215
|
+
fabricIndex(), // indexModel: pk=["model"], sk=["scope","updatedAt"]
|
|
216
|
+
fabricIndex("alias"), // indexModelAlias: pk=["model","alias"], sparse
|
|
217
|
+
fabricIndex("category"), // indexModelCategory: pk=["model","category"], sparse
|
|
218
|
+
],
|
|
219
|
+
});
|
|
220
|
+
```
|
|
225
221
|
|
|
226
222
|
### Query Functions
|
|
227
223
|
|
|
228
|
-
All queries return `{ items, lastEvaluatedKey }` and support pagination
|
|
224
|
+
All queries return `{ items, lastEvaluatedKey }` and support pagination. `scope` is always optional — when omitted, queries span all scopes. `queryByCategory` and `queryByType` throw `ConfigurationError` if the model has not registered the corresponding `fabricIndex()`.
|
|
229
225
|
|
|
230
226
|
```typescript
|
|
231
227
|
import { APEX, queryByScope, queryByAlias, queryByCategory, queryByType, queryByXid } from "@jaypie/dynamodb";
|
|
232
228
|
|
|
233
|
-
// List by
|
|
229
|
+
// List by model (scope optional)
|
|
234
230
|
const { items } = await queryByScope({ model: "record", scope: APEX });
|
|
235
231
|
|
|
236
|
-
//
|
|
232
|
+
// List across all scopes
|
|
233
|
+
const { items: allRecords } = await queryByScope({ model: "record" });
|
|
234
|
+
|
|
235
|
+
// Filter by category (requires fabricIndex("category") registered)
|
|
237
236
|
const { items: memories } = await queryByCategory({
|
|
238
237
|
model: "record", scope: APEX, category: "memory",
|
|
239
238
|
});
|
|
@@ -241,7 +240,7 @@ const { items: memories } = await queryByCategory({
|
|
|
241
240
|
// Lookup by alias (returns single or null)
|
|
242
241
|
const item = await queryByAlias({ model: "record", scope: APEX, alias: "2026-01-07" });
|
|
243
242
|
|
|
244
|
-
// Filter by type
|
|
243
|
+
// Filter by type (requires fabricIndex("type") registered)
|
|
245
244
|
const { items: drafts } = await queryByType({
|
|
246
245
|
model: "record", scope: APEX, type: "draft",
|
|
247
246
|
});
|
|
@@ -306,14 +305,13 @@ const json = await exportEntitiesToJson("vocabulary", APEX);
|
|
|
306
305
|
|
|
307
306
|
### Key Builders
|
|
308
307
|
|
|
309
|
-
Build composite
|
|
308
|
+
Build composite keys manually when needed:
|
|
310
309
|
|
|
311
310
|
```typescript
|
|
312
|
-
import {
|
|
311
|
+
import { buildCompositeKey, calculateScope } from "@jaypie/dynamodb";
|
|
313
312
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
buildIndexCategory(APEX, "record", "memory"); // "@#record#memory"
|
|
313
|
+
buildCompositeKey({ model: "record" }, ["model"]); // "record"
|
|
314
|
+
buildCompositeKey({ model: "record", alias: "daily-log" }, ["model", "alias"]); // "record#daily-log"
|
|
317
315
|
calculateScope({ model: "chat", id: "abc-123" }); // "chat#abc-123"
|
|
318
316
|
```
|
|
319
317
|
|
|
@@ -323,8 +321,8 @@ calculateScope({ model: "chat", id: "abc-123" }); // "chat#abc-123"
|
|
|
323
321
|
|
|
324
322
|
| Setting | Default | Source |
|
|
325
323
|
|---------|---------|--------|
|
|
326
|
-
| Partition key | `
|
|
327
|
-
| Sort key |
|
|
324
|
+
| Partition key | `id` (String) | Jaypie construct |
|
|
325
|
+
| Sort key | None | Jaypie construct |
|
|
328
326
|
| Billing mode | PAY_PER_REQUEST | Jaypie construct |
|
|
329
327
|
| Removal policy | DESTROY (non-production), RETAIN (production) | Jaypie construct |
|
|
330
328
|
| Point-in-time recovery | Enabled | Jaypie construct |
|
|
@@ -332,14 +330,16 @@ calculateScope({ model: "chat", id: "abc-123" }); // "chat#abc-123"
|
|
|
332
330
|
|
|
333
331
|
```typescript
|
|
334
332
|
import { JaypieDynamoDb } from "@jaypie/constructs";
|
|
333
|
+
import { fabricIndex } from "@jaypie/fabric";
|
|
335
334
|
|
|
336
|
-
// Recommended: start with no indexes
|
|
335
|
+
// Recommended: start with no indexes
|
|
337
336
|
const table = new JaypieDynamoDb(this, "myApp");
|
|
338
337
|
|
|
339
338
|
// Add indexes when driven by real access patterns
|
|
340
339
|
const table = new JaypieDynamoDb(this, "myApp", {
|
|
341
340
|
indexes: [
|
|
342
|
-
|
|
341
|
+
fabricIndex(), // indexModel
|
|
342
|
+
fabricIndex("alias"), // indexModelAlias (sparse)
|
|
343
343
|
],
|
|
344
344
|
});
|
|
345
345
|
```
|
|
@@ -356,7 +356,7 @@ Use docker-compose for local DynamoDB. The `@jaypie/dynamodb` MCP tool can gener
|
|
|
356
356
|
{
|
|
357
357
|
"scripts": {
|
|
358
358
|
"dynamo:init": "docker compose up -d && npm run dynamo:create-table",
|
|
359
|
-
"dynamo:create-table": "AWS_ACCESS_KEY_ID=local AWS_SECRET_ACCESS_KEY=local aws dynamodb create-table --table-name jaypie-local --attribute-definitions AttributeName=
|
|
359
|
+
"dynamo:create-table": "AWS_ACCESS_KEY_ID=local AWS_SECRET_ACCESS_KEY=local aws dynamodb create-table --table-name jaypie-local --attribute-definitions AttributeName=id,AttributeType=S --key-schema AttributeName=id,KeyType=HASH --billing-mode PAY_PER_REQUEST --endpoint-url http://127.0.0.1:9060 2>/dev/null || true",
|
|
360
360
|
"dynamo:remove": "docker compose down -v",
|
|
361
361
|
"dynamo:start": "docker compose up -d",
|
|
362
362
|
"dynamo:stop": "docker compose down"
|
|
@@ -420,21 +420,24 @@ describe("OrderService", () => {
|
|
|
420
420
|
});
|
|
421
421
|
```
|
|
422
422
|
|
|
423
|
-
## Migration:
|
|
424
|
-
|
|
425
|
-
Version 0.
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
|
433
|
-
|
|
434
|
-
| `
|
|
435
|
-
| `
|
|
436
|
-
| `
|
|
437
|
-
| `
|
|
423
|
+
## Migration: v0.4.x to v0.5.0
|
|
424
|
+
|
|
425
|
+
Version 0.5.0 is a breaking change. **Tables must be recreated** (pre-1.0 breaking change).
|
|
426
|
+
|
|
427
|
+
| Old (0.4.x) | New (0.5.0) |
|
|
428
|
+
|-------------|-------------|
|
|
429
|
+
| Primary key: pk=`model`, sk=`id` | Primary key: pk=`id` only |
|
|
430
|
+
| GSI sort key: `sequence` (number) | GSI sort key: composite `scope#updatedAt` |
|
|
431
|
+
| GSI pk: `{scope}#{model}#{field}` | GSI pk: `{model}#{field}` |
|
|
432
|
+
| GSI names: indexScope, indexAlias, indexCategory, indexType, indexXid | GSI names: indexModel, indexModelAlias, indexModelCategory, indexModelType, indexModelXid |
|
|
433
|
+
| `sequence` field on entity | Removed — ordering by `updatedAt` |
|
|
434
|
+
| `getEntity({ id, model })` | `getEntity({ id })` |
|
|
435
|
+
| `deleteEntity({ id, model })` | `deleteEntity({ id })` |
|
|
436
|
+
| `archiveEntity({ id, model })` | `archiveEntity({ id })` |
|
|
437
|
+
| `destroyEntity({ id, model })` | `destroyEntity({ id })` |
|
|
438
|
+
| `buildIndexScope`, `buildIndexAlias`, etc. | Removed; use `buildCompositeKey` |
|
|
439
|
+
| `DEFAULT_INDEXES` implicit fallback | Must `registerModel()` with `fabricIndex()` before querying |
|
|
440
|
+
| Callers set `createdAt`/`updatedAt` | `indexEntity` manages both automatically |
|
|
438
441
|
|
|
439
442
|
## See Also
|
|
440
443
|
|
package/skills/tildeskill.md
CHANGED
|
@@ -10,12 +10,15 @@ Skill/vocabulary management with pluggable storage backends for AI assistants an
|
|
|
10
10
|
## Overview
|
|
11
11
|
|
|
12
12
|
This package provides a storage abstraction for skill/vocabulary documents with markdown frontmatter support. It enables:
|
|
13
|
+
|
|
13
14
|
- Loading skills from markdown files with YAML frontmatter
|
|
14
15
|
- In-memory storage for testing
|
|
16
|
+
- Layered composition of multiple stores with namespace prefixes
|
|
15
17
|
- Consistent alias normalization and validation
|
|
16
18
|
- Filtering by namespace and tags
|
|
17
19
|
- Searching across alias, name, description, content, and tags
|
|
18
20
|
- Include expansion for composable skills
|
|
21
|
+
- Plural/singular fallback lookup via `find()` and `getAlternativeSpellings()`
|
|
19
22
|
|
|
20
23
|
## Installation
|
|
21
24
|
|
|
@@ -27,24 +30,25 @@ npm install @jaypie/tildeskill
|
|
|
27
30
|
|
|
28
31
|
```typescript
|
|
29
32
|
interface SkillRecord {
|
|
30
|
-
alias: string;
|
|
31
|
-
content: string;
|
|
32
|
-
description?: string;
|
|
33
|
-
includes?: string[];
|
|
34
|
-
name?: string;
|
|
35
|
-
nicknames?: string[];
|
|
36
|
-
related?: string[];
|
|
37
|
-
tags?: string[];
|
|
33
|
+
alias: string; // Lookup key (normalized lowercase)
|
|
34
|
+
content: string; // Markdown body
|
|
35
|
+
description?: string; // Brief description from frontmatter
|
|
36
|
+
includes?: string[]; // Auto-expand these skill aliases on lookup
|
|
37
|
+
name?: string; // Display title for the skill
|
|
38
|
+
nicknames?: string[]; // Alternate lookup keys for getByNickname
|
|
39
|
+
related?: string[]; // Related skill aliases
|
|
40
|
+
tags?: string[]; // Categorization tags
|
|
38
41
|
}
|
|
39
42
|
|
|
40
43
|
interface ListFilter {
|
|
41
|
-
namespace?: string;
|
|
42
|
-
tag?: string;
|
|
44
|
+
namespace?: string; // Namespace prefix matching (e.g., "kit:*")
|
|
45
|
+
tag?: string; // Filter by tag
|
|
43
46
|
}
|
|
44
47
|
|
|
45
48
|
interface SkillStore {
|
|
49
|
+
find(alias: string): Promise<SkillRecord | null>;
|
|
46
50
|
get(alias: string): Promise<SkillRecord | null>;
|
|
47
|
-
getByNickname(nickname: string): Promise<SkillRecord
|
|
51
|
+
getByNickname(nickname: string): Promise<SkillRecord[]>;
|
|
48
52
|
list(filter?: ListFilter): Promise<SkillRecord[]>;
|
|
49
53
|
put(record: SkillRecord): Promise<SkillRecord>;
|
|
50
54
|
search(term: string): Promise<SkillRecord[]>;
|
|
@@ -68,7 +72,7 @@ if (skill) {
|
|
|
68
72
|
|
|
69
73
|
// List all skills
|
|
70
74
|
const skills = await store.list();
|
|
71
|
-
skills.forEach(s => console.log(`${s.alias}: ${s.description}`));
|
|
75
|
+
skills.forEach((s) => console.log(`${s.alias}: ${s.description}`));
|
|
72
76
|
```
|
|
73
77
|
|
|
74
78
|
### Memory Store (Testing)
|
|
@@ -77,12 +81,33 @@ skills.forEach(s => console.log(`${s.alias}: ${s.description}`));
|
|
|
77
81
|
import { createMemoryStore } from "@jaypie/tildeskill";
|
|
78
82
|
|
|
79
83
|
const store = createMemoryStore([
|
|
80
|
-
{ alias: "test", content: "# Test\n\nContent", description: "Test skill" }
|
|
84
|
+
{ alias: "test", content: "# Test\n\nContent", description: "Test skill" },
|
|
81
85
|
]);
|
|
82
86
|
|
|
83
87
|
const skill = await store.get("test");
|
|
84
88
|
```
|
|
85
89
|
|
|
90
|
+
## Skill Service Factory
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
import { createSkillService, createMemoryStore } from "@jaypie/tildeskill";
|
|
94
|
+
|
|
95
|
+
const store = createMemoryStore([
|
|
96
|
+
{ alias: "aws", content: "# AWS docs", description: "AWS guide" },
|
|
97
|
+
]);
|
|
98
|
+
|
|
99
|
+
// Returns a fabricService — works with MCP, Llm.operate, or direct calls
|
|
100
|
+
const skillService = createSkillService(store);
|
|
101
|
+
|
|
102
|
+
await skillService({ alias: "aws" }); // → skill content (with expandIncludes)
|
|
103
|
+
await skillService({ alias: "index" }); // → formatted listing
|
|
104
|
+
await skillService(); // → same as "index"
|
|
105
|
+
|
|
106
|
+
// Use with fabricTool for Llm.operate toolkits
|
|
107
|
+
import { fabricTool } from "@jaypie/fabric/llm";
|
|
108
|
+
const { tool } = fabricTool({ service: skillService });
|
|
109
|
+
```
|
|
110
|
+
|
|
86
111
|
## Include Expansion
|
|
87
112
|
|
|
88
113
|
```typescript
|
|
@@ -110,25 +135,72 @@ const cloudSkills = await store.list({ tag: "cloud" });
|
|
|
110
135
|
// Search across alias, name, description, content, and tags
|
|
111
136
|
const results = await store.search("lambda");
|
|
112
137
|
|
|
113
|
-
// Lookup by nickname
|
|
114
|
-
|
|
138
|
+
// Lookup by nickname — returns every matching record, so a name like
|
|
139
|
+
// "sparticus" can resolve to multiple skills across layers.
|
|
140
|
+
const matches = await store.getByNickname("amazon");
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## Layered Stores
|
|
144
|
+
|
|
145
|
+
```typescript
|
|
146
|
+
import { createLayeredStore, createMarkdownStore } from "@jaypie/tildeskill";
|
|
147
|
+
|
|
148
|
+
// Compose multiple stores with namespace prefixes. Earlier layers win
|
|
149
|
+
// for single-result lookups; aggregate methods merge every layer.
|
|
150
|
+
const layered = createLayeredStore({
|
|
151
|
+
layers: [
|
|
152
|
+
{ namespace: "local", store: createMarkdownStore({ path: "./my-skills" }) },
|
|
153
|
+
{
|
|
154
|
+
namespace: "jaypie",
|
|
155
|
+
store: createMarkdownStore({ path: "./jaypie-skills" }),
|
|
156
|
+
},
|
|
157
|
+
],
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
await layered.get("aws"); // → { alias: "local:aws", ... }
|
|
161
|
+
await layered.get("jaypie:aws"); // → { alias: "jaypie:aws", ... }
|
|
162
|
+
await layered.find("skills"); // per-layer plural fallback
|
|
163
|
+
await layered.list(); // prefixed aliases from every layer
|
|
164
|
+
await layered.put({ alias: "local:new", content: "# New" }); // must be qualified
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
The MCP server itself uses `createLayeredStore` to place `MCP_SKILLS_PATH`
|
|
168
|
+
(the client's local library, namespace `local`) over the bundled Jaypie
|
|
169
|
+
skills (namespace `jaypie`). Set `MCP_BUILTIN_SKILLS_PATH` if a bundler
|
|
170
|
+
needs to relocate the Jaypie base layer.
|
|
171
|
+
|
|
172
|
+
## Plural/Singular Fallback
|
|
173
|
+
|
|
174
|
+
```typescript
|
|
175
|
+
// find() tries exact match then plural/singular alternatives
|
|
176
|
+
const skill = await store.find("skills"); // resolves skill.md
|
|
177
|
+
// skill.alias is the canonical filename; compare to the input to detect fallback
|
|
178
|
+
|
|
179
|
+
import { getAlternativeSpellings } from "@jaypie/tildeskill";
|
|
180
|
+
getAlternativeSpellings("skills"); // ["skill"]
|
|
181
|
+
getAlternativeSpellings("indexes"); // ["indexe", "index"]
|
|
182
|
+
getAlternativeSpellings("fish"); // ["fishs", "fishes"]
|
|
115
183
|
```
|
|
116
184
|
|
|
117
185
|
## Validation Utilities
|
|
118
186
|
|
|
119
187
|
```typescript
|
|
120
|
-
import {
|
|
188
|
+
import {
|
|
189
|
+
isValidAlias,
|
|
190
|
+
validateAlias,
|
|
191
|
+
normalizeAlias,
|
|
192
|
+
} from "@jaypie/tildeskill";
|
|
121
193
|
|
|
122
194
|
// Check validity
|
|
123
|
-
isValidAlias("my-skill");
|
|
124
|
-
isValidAlias("../../etc");
|
|
195
|
+
isValidAlias("my-skill"); // true
|
|
196
|
+
isValidAlias("../../etc"); // false (path traversal)
|
|
125
197
|
|
|
126
198
|
// Normalize to lowercase
|
|
127
|
-
normalizeAlias("MY-Skill");
|
|
199
|
+
normalizeAlias("MY-Skill"); // "my-skill"
|
|
128
200
|
|
|
129
201
|
// Validate and normalize (throws on invalid)
|
|
130
|
-
validateAlias("valid");
|
|
131
|
-
validateAlias("../bad");
|
|
202
|
+
validateAlias("valid"); // returns "valid"
|
|
203
|
+
validateAlias("../bad"); // throws BadRequestError
|
|
132
204
|
```
|
|
133
205
|
|
|
134
206
|
## Skill File Format
|
|
@@ -144,7 +216,6 @@ nicknames: alt-name, another-alias
|
|
|
144
216
|
related: alias1, alias2, alias3
|
|
145
217
|
tags: category1, category2
|
|
146
218
|
---
|
|
147
|
-
|
|
148
219
|
# Skill Title
|
|
149
220
|
|
|
150
221
|
Markdown content...
|
package/skills/vocabulary.md
CHANGED
|
@@ -49,7 +49,7 @@ Arguably composition, identity, instance, and relation would form a more complet
|
|
|
49
49
|
- name: most common way to clearly reference the entity
|
|
50
50
|
- related: array of id strings, complex "{model}#{id}" strings, or `{ id, model }` objects
|
|
51
51
|
- scope: organizes entities, usually a reference to a parent entity
|
|
52
|
-
- sequence:
|
|
52
|
+
- sequence: deprecated; ordering now uses `updatedAt` via GSI composite sort key
|
|
53
53
|
- state: mutable data the entity tracks
|
|
54
54
|
- status: canceled, complete, error, pending, processing, queued, sending
|
|
55
55
|
- updatedAt: timestamp
|
|
@@ -66,7 +66,7 @@ Arguably composition, identity, instance, and relation would form a more complet
|
|
|
66
66
|
- key => alias; make api or secret keys explicit in name
|
|
67
67
|
- ou => scope
|
|
68
68
|
- output => state
|
|
69
|
-
- type => category, tags; reserved (exception: `
|
|
69
|
+
- type => category, tags; reserved (exception: `indexModelType` GSI exists in DynamoDB as a legacy pattern; prefer `category` for new work)
|
|
70
70
|
|
|
71
71
|
Avoid words defined elsewhere (services, terminology)
|
|
72
72
|
|