@elevasis/sdk 1.15.1 → 1.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.cjs +2325 -124
- package/dist/index.d.ts +410 -473
- package/dist/index.js +96 -44
- package/dist/node/index.d.ts +69 -0
- package/dist/node/index.js +273 -0
- package/dist/test-utils/index.d.ts +473 -466
- package/dist/types/worker/platform.d.ts +2 -9
- package/package.json +12 -3
- package/reference/_navigation.md +23 -1
- package/reference/_reference-manifest.json +98 -0
- package/reference/claude-config/rules/agent-start-here.md +13 -0
- package/reference/claude-config/rules/organization-model.md +40 -40
- package/reference/claude-config/rules/organization-os.md +16 -16
- package/reference/claude-config/rules/vibe.md +13 -13
- package/reference/claude-config/skills/knowledge/SKILL.md +253 -0
- package/reference/claude-config/skills/{configure → knowledge}/operations/codify-level-a.md +100 -100
- package/reference/claude-config/skills/{configure → knowledge}/operations/codify-level-b.md +158 -158
- package/reference/claude-config/skills/knowledge/operations/customers.md +109 -0
- package/reference/claude-config/skills/knowledge/operations/features.md +113 -0
- package/reference/claude-config/skills/knowledge/operations/goals.md +118 -0
- package/reference/claude-config/skills/knowledge/operations/identity.md +93 -0
- package/reference/claude-config/skills/knowledge/operations/labels.md +89 -0
- package/reference/claude-config/skills/knowledge/operations/offerings.md +109 -0
- package/reference/claude-config/skills/knowledge/operations/roles.md +99 -0
- package/reference/claude-config/skills/knowledge/operations/techStack.md +102 -0
- package/reference/claude-config/skills/run-ui/SKILL.md +73 -0
- package/reference/claude-config/skills/setup/SKILL.md +270 -270
- package/reference/claude-config/skills/tutorial/SKILL.md +249 -0
- package/reference/claude-config/skills/tutorial/progress-template.md +74 -0
- package/reference/claude-config/skills/tutorial/technical.md +1309 -0
- package/reference/claude-config/skills/tutorial/vibe-coder.md +890 -0
- package/reference/claude-config/sync-notes/2026-05-04-elevasis-workspace.md +71 -0
- package/reference/claude-config/sync-notes/2026-05-04-knowledge-bundle.md +83 -0
- package/reference/claude-config/sync-notes/2026-05-04-template-skills-run-ui-and-tutorial.md +59 -0
- package/reference/deployment/index.mdx +5 -5
- package/reference/examples/organization-model.ts +40 -0
- package/reference/framework/index.mdx +1 -1
- package/reference/framework/tutorial-system.mdx +86 -173
- package/reference/packages/core/src/knowledge/README.md +32 -0
- package/reference/packages/ui/src/knowledge/README.md +31 -0
- package/reference/packages/ui/src/theme/presets/README.md +19 -0
- package/reference/scaffold/core/organization-model.mdx +1 -1
- package/reference/scaffold/recipes/add-a-feature.md +1 -1
- package/reference/scaffold/recipes/customize-crm-actions.md +433 -433
- package/reference/scaffold/recipes/customize-organization-model.md +3 -3
- package/reference/scaffold/recipes/extend-lead-gen.md +90 -55
- package/reference/scaffold/recipes/gate-by-feature-or-admin.md +1 -1
- package/reference/scaffold/recipes/index.md +6 -0
- package/reference/scaffold/reference/contracts.md +1265 -1154
- package/reference/scaffold/reference/feature-registry.md +2 -1
- package/reference/scaffold/ui/composition-extensibility.mdx +17 -0
- package/reference/claude-config/skills/configure/SKILL.md +0 -98
- package/reference/claude-config/skills/configure/operations/customers.md +0 -150
- package/reference/claude-config/skills/configure/operations/features.md +0 -162
- package/reference/claude-config/skills/configure/operations/goals.md +0 -147
- package/reference/claude-config/skills/configure/operations/identity.md +0 -133
- package/reference/claude-config/skills/configure/operations/labels.md +0 -128
- package/reference/claude-config/skills/configure/operations/offerings.md +0 -159
- package/reference/claude-config/skills/configure/operations/roles.md +0 -153
- package/reference/claude-config/skills/configure/operations/techStack.md +0 -139
package/dist/index.js
CHANGED
|
@@ -37,11 +37,105 @@ var ToolingError = class extends ExecutionError {
|
|
|
37
37
|
this.details = details;
|
|
38
38
|
}
|
|
39
39
|
};
|
|
40
|
+
z.string().uuid();
|
|
41
|
+
z.string().trim().min(1).max(1e3);
|
|
42
|
+
z.enum(["agent", "workflow"]);
|
|
43
|
+
z.enum(["agent", "workflow", "scheduler", "api"]);
|
|
44
|
+
z.string().trim().toLowerCase().min(1, "Credential name required").max(100, "Credential name too long (max 100 chars)").regex(
|
|
45
|
+
/^[a-z0-9]+(-[a-z0-9]+)+$/,
|
|
46
|
+
"Credential name must be lowercase letters, numbers, and hyphens in format: service-environment (e.g., gmail-prod, attio-dev)"
|
|
47
|
+
);
|
|
48
|
+
z.enum(["google-sheets", "dropbox"]);
|
|
49
|
+
z.string().min(10, "Authorization code too short").max(1e3, "Authorization code too long");
|
|
50
|
+
z.string().min(10, "State parameter too short").max(2048, "State parameter too long");
|
|
51
|
+
z.string().trim().transform((str) => str.replace(/[<>'"]/g, ""));
|
|
52
|
+
var EmailSchema = z.string().email();
|
|
53
|
+
z.string().url();
|
|
54
|
+
z.object({
|
|
55
|
+
limit: z.coerce.number().int().min(1).max(100).default(20),
|
|
56
|
+
offset: z.coerce.number().int().min(0).default(0)
|
|
57
|
+
});
|
|
58
|
+
z.string().datetime();
|
|
59
|
+
z.object({
|
|
60
|
+
startDate: z.string().datetime(),
|
|
61
|
+
endDate: z.string().datetime()
|
|
62
|
+
});
|
|
63
|
+
var ORGANIZATION_MODEL_ICON_TOKENS = [
|
|
64
|
+
"nav.dashboard",
|
|
65
|
+
"nav.sales",
|
|
66
|
+
"nav.crm",
|
|
67
|
+
"nav.lead-gen",
|
|
68
|
+
"nav.projects",
|
|
69
|
+
"nav.operations",
|
|
70
|
+
"nav.monitoring",
|
|
71
|
+
"nav.knowledge",
|
|
72
|
+
"nav.settings",
|
|
73
|
+
"nav.admin",
|
|
74
|
+
"nav.archive",
|
|
75
|
+
"knowledge.playbook",
|
|
76
|
+
"knowledge.strategy",
|
|
77
|
+
"knowledge.reference",
|
|
78
|
+
"feature.dashboard",
|
|
79
|
+
"feature.sales",
|
|
80
|
+
"feature.crm",
|
|
81
|
+
"feature.finance",
|
|
82
|
+
"feature.lead-gen",
|
|
83
|
+
"feature.platform",
|
|
84
|
+
"feature.projects",
|
|
85
|
+
"feature.operations",
|
|
86
|
+
"feature.knowledge",
|
|
87
|
+
"feature.monitoring",
|
|
88
|
+
"feature.settings",
|
|
89
|
+
"feature.admin",
|
|
90
|
+
"feature.archive",
|
|
91
|
+
"feature.seo",
|
|
92
|
+
"resource.agent",
|
|
93
|
+
"resource.workflow",
|
|
94
|
+
"resource.integration",
|
|
95
|
+
"resource.database",
|
|
96
|
+
"resource.user",
|
|
97
|
+
"resource.team",
|
|
98
|
+
"integration.gmail",
|
|
99
|
+
"integration.google-sheets",
|
|
100
|
+
"integration.attio",
|
|
101
|
+
"surface.dashboard",
|
|
102
|
+
"surface.overview",
|
|
103
|
+
"surface.command-view",
|
|
104
|
+
"surface.command-queue",
|
|
105
|
+
"surface.pipeline",
|
|
106
|
+
"surface.lists",
|
|
107
|
+
"surface.resources",
|
|
108
|
+
"surface.settings",
|
|
109
|
+
"status.success",
|
|
110
|
+
"status.error",
|
|
111
|
+
"status.warning",
|
|
112
|
+
"status.info",
|
|
113
|
+
"status.pending",
|
|
114
|
+
"action.approve",
|
|
115
|
+
"action.reject",
|
|
116
|
+
"action.retry",
|
|
117
|
+
"action.edit",
|
|
118
|
+
"action.view",
|
|
119
|
+
"action.launch",
|
|
120
|
+
"action.message",
|
|
121
|
+
"action.escalate",
|
|
122
|
+
"action.promote",
|
|
123
|
+
"action.submit",
|
|
124
|
+
"action.email"
|
|
125
|
+
];
|
|
126
|
+
var CustomIconTokenSchema = z.string().trim().max(80).regex(/^custom\.[a-z0-9]+(?:[-._][a-z0-9]+)*$/, "Custom icon tokens must start with custom.");
|
|
127
|
+
var OrganizationModelBuiltinIconTokenSchema = z.enum(ORGANIZATION_MODEL_ICON_TOKENS);
|
|
128
|
+
var OrganizationModelIconTokenSchema = z.union([
|
|
129
|
+
OrganizationModelBuiltinIconTokenSchema,
|
|
130
|
+
CustomIconTokenSchema
|
|
131
|
+
]);
|
|
132
|
+
|
|
133
|
+
// ../core/src/organization-model/domains/shared.ts
|
|
40
134
|
var ModelIdSchema = z.string().trim().min(1).max(100).regex(/^[a-z0-9]+(?:[-._][a-z0-9]+)*$/, "IDs must be lowercase and use -, _, or . separators");
|
|
41
135
|
var LabelSchema = z.string().trim().min(1).max(120);
|
|
42
136
|
var DescriptionSchema = z.string().trim().min(1).max(2e3);
|
|
43
137
|
var ColorTokenSchema = z.string().trim().min(1).max(50);
|
|
44
|
-
var IconNameSchema =
|
|
138
|
+
var IconNameSchema = OrganizationModelIconTokenSchema;
|
|
45
139
|
z.string().trim().startsWith("/").max(300);
|
|
46
140
|
var ReferenceIdsSchema = z.array(ModelIdSchema).default([]);
|
|
47
141
|
var DisplayMetadataSchema = z.object({
|
|
@@ -3694,48 +3788,6 @@ var ResourceRegistry = class {
|
|
|
3694
3788
|
}
|
|
3695
3789
|
return cache.commandView;
|
|
3696
3790
|
}
|
|
3697
|
-
/**
|
|
3698
|
-
* List resources that have UI interfaces configured
|
|
3699
|
-
* Used by Execution Runner Catalog UI
|
|
3700
|
-
*
|
|
3701
|
-
* @param organizationName - Organization name
|
|
3702
|
-
* @param environment - Optional environment filter ('dev' or 'prod')
|
|
3703
|
-
* @returns Array of resources with interfaces
|
|
3704
|
-
*/
|
|
3705
|
-
listExecutable(organizationName, environment) {
|
|
3706
|
-
const cache = this.serializedCache.get(organizationName);
|
|
3707
|
-
if (!cache) {
|
|
3708
|
-
return [];
|
|
3709
|
-
}
|
|
3710
|
-
const results = [];
|
|
3711
|
-
cache.definitions.workflows.forEach((serializedWorkflow, resourceId) => {
|
|
3712
|
-
if (!serializedWorkflow.interface) return;
|
|
3713
|
-
if (environment && serializedWorkflow.config.status !== environment) return;
|
|
3714
|
-
results.push({
|
|
3715
|
-
resourceId,
|
|
3716
|
-
resourceName: serializedWorkflow.config.name,
|
|
3717
|
-
resourceType: "workflow",
|
|
3718
|
-
description: serializedWorkflow.config.description,
|
|
3719
|
-
status: serializedWorkflow.config.status,
|
|
3720
|
-
version: serializedWorkflow.config.version,
|
|
3721
|
-
interface: serializedWorkflow.interface
|
|
3722
|
-
});
|
|
3723
|
-
});
|
|
3724
|
-
cache.definitions.agents.forEach((serializedAgent, resourceId) => {
|
|
3725
|
-
if (!serializedAgent.interface) return;
|
|
3726
|
-
if (environment && serializedAgent.config.status !== environment) return;
|
|
3727
|
-
results.push({
|
|
3728
|
-
resourceId,
|
|
3729
|
-
resourceName: serializedAgent.config.name,
|
|
3730
|
-
resourceType: "agent",
|
|
3731
|
-
description: serializedAgent.config.description,
|
|
3732
|
-
status: serializedAgent.config.status,
|
|
3733
|
-
version: serializedAgent.config.version,
|
|
3734
|
-
interface: serializedAgent.interface
|
|
3735
|
-
});
|
|
3736
|
-
});
|
|
3737
|
-
return results;
|
|
3738
|
-
}
|
|
3739
3791
|
};
|
|
3740
3792
|
var StageChangeEventSchema = z.object({
|
|
3741
3793
|
type: z.literal("stage_change"),
|
|
@@ -3942,4 +3994,4 @@ function isOurReplyAction(deal) {
|
|
|
3942
3994
|
return (deal.ownership ?? getDealOwnership(deal)) === "us";
|
|
3943
3995
|
}
|
|
3944
3996
|
|
|
3945
|
-
export { ActivityEventSchema, DEFAULT_CRM_ACTIONS, ExecutionError, RegistryValidationError, ResourceRegistry, StepType, ToolingError, deriveActions };
|
|
3997
|
+
export { ActivityEventSchema, DEFAULT_CRM_ACTIONS, EmailSchema, ExecutionError, RegistryValidationError, ResourceRegistry, StepType, ToolingError, deriveActions };
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
type KnowledgeKind = 'playbook' | 'strategy' | 'reference';
|
|
2
|
+
interface KnowledgeCodegenNode {
|
|
3
|
+
id: string;
|
|
4
|
+
kind: KnowledgeKind;
|
|
5
|
+
title: string;
|
|
6
|
+
summary: string;
|
|
7
|
+
icon?: string;
|
|
8
|
+
body: string;
|
|
9
|
+
links: {
|
|
10
|
+
nodeId: string;
|
|
11
|
+
}[];
|
|
12
|
+
ownerIds: string[];
|
|
13
|
+
updatedAt: string;
|
|
14
|
+
}
|
|
15
|
+
interface GenerateKnowledgeNodesOptions {
|
|
16
|
+
sourceDir: string;
|
|
17
|
+
outputPath: string;
|
|
18
|
+
typeImportPath?: string;
|
|
19
|
+
exportedName?: string;
|
|
20
|
+
sourceLabel?: string;
|
|
21
|
+
}
|
|
22
|
+
interface GenerateKnowledgeNodesResult {
|
|
23
|
+
nodes: KnowledgeCodegenNode[];
|
|
24
|
+
outputPath: string;
|
|
25
|
+
}
|
|
26
|
+
declare function readKnowledgeNodeMdx(filePath: string): KnowledgeCodegenNode;
|
|
27
|
+
declare function generateKnowledgeNodesTs(options: {
|
|
28
|
+
nodes: KnowledgeCodegenNode[];
|
|
29
|
+
typeImportPath?: string;
|
|
30
|
+
exportedName?: string;
|
|
31
|
+
sourceLabel?: string;
|
|
32
|
+
}): string;
|
|
33
|
+
declare function generateKnowledgeNodes(options: GenerateKnowledgeNodesOptions): GenerateKnowledgeNodesResult;
|
|
34
|
+
|
|
35
|
+
interface KnowledgeNodeInput {
|
|
36
|
+
id: string;
|
|
37
|
+
kind: string;
|
|
38
|
+
title: string;
|
|
39
|
+
summary: string;
|
|
40
|
+
body: string;
|
|
41
|
+
}
|
|
42
|
+
interface KnowledgeSearchEntry {
|
|
43
|
+
id: string;
|
|
44
|
+
title: string;
|
|
45
|
+
summary: string;
|
|
46
|
+
bodyText: string;
|
|
47
|
+
}
|
|
48
|
+
interface CodegenResult {
|
|
49
|
+
bodiesTsx: string;
|
|
50
|
+
searchIndex: KnowledgeSearchEntry[];
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Compiles knowledge nodes into generated file contents.
|
|
54
|
+
* Pure function: same input -> same output. No file I/O.
|
|
55
|
+
*/
|
|
56
|
+
declare function generateKnowledgeBodies(nodes: KnowledgeNodeInput[]): Promise<CodegenResult>;
|
|
57
|
+
|
|
58
|
+
interface ResolvedKnowledgeLayout {
|
|
59
|
+
mode: 'monorepo' | 'external';
|
|
60
|
+
projectRoot: string;
|
|
61
|
+
omSourcePath: string;
|
|
62
|
+
mdxNodesDir: string;
|
|
63
|
+
generatedDir: string;
|
|
64
|
+
watchPaths: string[];
|
|
65
|
+
}
|
|
66
|
+
declare function runKnowledgeCodegen(layout: ResolvedKnowledgeLayout): Promise<void>;
|
|
67
|
+
|
|
68
|
+
export { generateKnowledgeBodies, generateKnowledgeNodes, generateKnowledgeNodesTs, readKnowledgeNodeMdx, runKnowledgeCodegen };
|
|
69
|
+
export type { CodegenResult, GenerateKnowledgeNodesOptions, GenerateKnowledgeNodesResult, KnowledgeCodegenNode, KnowledgeKind, KnowledgeNodeInput, KnowledgeSearchEntry, ResolvedKnowledgeLayout };
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
import { readFileSync, mkdirSync, writeFileSync, readdirSync } from 'fs';
|
|
2
|
+
import { relative, dirname, resolve, join, extname } from 'path';
|
|
3
|
+
import { compile } from '@mdx-js/mdx';
|
|
4
|
+
import remarkGfm from 'remark-gfm';
|
|
5
|
+
|
|
6
|
+
// src/knowledge-codegen.ts
|
|
7
|
+
function listMdxFiles(directory) {
|
|
8
|
+
return readdirSync(directory, { withFileTypes: true }).flatMap((entry) => {
|
|
9
|
+
const path = join(directory, entry.name);
|
|
10
|
+
if (entry.isDirectory()) return listMdxFiles(path);
|
|
11
|
+
return entry.isFile() && extname(entry.name) === ".mdx" ? [path] : [];
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
function parseScalar(value) {
|
|
15
|
+
const trimmed = value.trim();
|
|
16
|
+
const quoted = trimmed.match(/^['"]([\s\S]*)['"]$/);
|
|
17
|
+
return quoted ? quoted[1] : trimmed;
|
|
18
|
+
}
|
|
19
|
+
function parseFrontmatter(raw, filePath) {
|
|
20
|
+
const match = raw.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/);
|
|
21
|
+
if (!match) throw new Error(`[knowledge-node-codegen] Missing frontmatter in ${filePath}`);
|
|
22
|
+
const frontmatter = {};
|
|
23
|
+
const lines = match[1].split(/\r?\n/);
|
|
24
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
25
|
+
const line = lines[index];
|
|
26
|
+
if (!line.trim()) continue;
|
|
27
|
+
const pair = line.match(/^([A-Za-z][A-Za-z0-9]*):(?:\s*(.*))?$/);
|
|
28
|
+
if (!pair) throw new Error(`[knowledge-node-codegen] Invalid frontmatter line in ${filePath}: ${line}`);
|
|
29
|
+
const key = pair[1];
|
|
30
|
+
const inlineValue = pair[2]?.trim() ?? "";
|
|
31
|
+
if (inlineValue) {
|
|
32
|
+
frontmatter[key] = parseScalar(inlineValue);
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
const values = [];
|
|
36
|
+
while (index + 1 < lines.length && /^\s+-\s+/.test(lines[index + 1])) {
|
|
37
|
+
index += 1;
|
|
38
|
+
values.push(parseScalar(lines[index].replace(/^\s+-\s+/, "")));
|
|
39
|
+
}
|
|
40
|
+
frontmatter[key] = values;
|
|
41
|
+
}
|
|
42
|
+
return { frontmatter, body: match[2].trim() };
|
|
43
|
+
}
|
|
44
|
+
function assertString(frontmatter, key, filePath) {
|
|
45
|
+
const value = frontmatter[key];
|
|
46
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
47
|
+
throw new Error(`[knowledge-node-codegen] ${filePath} frontmatter requires string "${key}"`);
|
|
48
|
+
}
|
|
49
|
+
return value.trim();
|
|
50
|
+
}
|
|
51
|
+
function optionalString(frontmatter, key, filePath) {
|
|
52
|
+
const value = frontmatter[key];
|
|
53
|
+
if (value === void 0) return void 0;
|
|
54
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
55
|
+
throw new Error(`[knowledge-node-codegen] ${filePath} frontmatter "${key}" must be a nonempty string`);
|
|
56
|
+
}
|
|
57
|
+
return value.trim();
|
|
58
|
+
}
|
|
59
|
+
function assertKind(value, filePath) {
|
|
60
|
+
if (value === "playbook" || value === "strategy" || value === "reference") return value;
|
|
61
|
+
throw new Error(`[knowledge-node-codegen] ${filePath} has invalid kind "${value}"`);
|
|
62
|
+
}
|
|
63
|
+
function optionalStringArray(frontmatter, key, filePath) {
|
|
64
|
+
const value = frontmatter[key];
|
|
65
|
+
if (value === void 0) return [];
|
|
66
|
+
if (!Array.isArray(value) || value.some((entry) => typeof entry !== "string")) {
|
|
67
|
+
throw new Error(`[knowledge-node-codegen] ${filePath} frontmatter "${key}" must be a string array`);
|
|
68
|
+
}
|
|
69
|
+
return value.map((entry) => entry.trim()).filter(Boolean);
|
|
70
|
+
}
|
|
71
|
+
function readKnowledgeNodeMdx(filePath) {
|
|
72
|
+
const raw = readFileSync(filePath, "utf8");
|
|
73
|
+
const { frontmatter, body } = parseFrontmatter(raw, filePath);
|
|
74
|
+
return {
|
|
75
|
+
id: assertString(frontmatter, "id", filePath),
|
|
76
|
+
kind: assertKind(assertString(frontmatter, "kind", filePath), filePath),
|
|
77
|
+
title: assertString(frontmatter, "title", filePath),
|
|
78
|
+
summary: assertString(frontmatter, "description", filePath),
|
|
79
|
+
icon: optionalString(frontmatter, "icon", filePath),
|
|
80
|
+
body,
|
|
81
|
+
links: optionalStringArray(frontmatter, "links", filePath).map((nodeId) => ({ nodeId })),
|
|
82
|
+
ownerIds: optionalStringArray(frontmatter, "ownerIds", filePath),
|
|
83
|
+
updatedAt: assertString(frontmatter, "updatedAt", filePath)
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
function generateKnowledgeNodesTs(options) {
|
|
87
|
+
const exportedName = options.exportedName ?? "mdxKnowledgeNodes";
|
|
88
|
+
const typeImport = options.typeImportPath ? [`import type { OrgKnowledgeNode } from '${options.typeImportPath}'`, ""] : [];
|
|
89
|
+
const typeSatisfies = options.typeImportPath ? " satisfies OrgKnowledgeNode[]" : "";
|
|
90
|
+
return [
|
|
91
|
+
"// @generated by generate-knowledge-nodes -- DO NOT EDIT",
|
|
92
|
+
"// Regenerate: elevasis-sdk knowledge:generate",
|
|
93
|
+
`// Source: ${options.sourceLabel ?? "knowledge/nodes/**/*.mdx"}`,
|
|
94
|
+
"",
|
|
95
|
+
...typeImport,
|
|
96
|
+
`export const ${exportedName} = ${JSON.stringify(options.nodes, null, 2)}${typeSatisfies}`,
|
|
97
|
+
""
|
|
98
|
+
].join("\n");
|
|
99
|
+
}
|
|
100
|
+
function generateKnowledgeNodes(options) {
|
|
101
|
+
const files = listMdxFiles(options.sourceDir).sort(
|
|
102
|
+
(a, b) => relative(options.sourceDir, a).localeCompare(relative(options.sourceDir, b))
|
|
103
|
+
);
|
|
104
|
+
const nodes = files.map(readKnowledgeNodeMdx);
|
|
105
|
+
const ids = /* @__PURE__ */ new Set();
|
|
106
|
+
for (const node of nodes) {
|
|
107
|
+
if (ids.has(node.id)) throw new Error(`[knowledge-node-codegen] Duplicate knowledge node id: ${node.id}`);
|
|
108
|
+
ids.add(node.id);
|
|
109
|
+
}
|
|
110
|
+
mkdirSync(dirname(options.outputPath), { recursive: true });
|
|
111
|
+
writeFileSync(
|
|
112
|
+
options.outputPath,
|
|
113
|
+
generateKnowledgeNodesTs({
|
|
114
|
+
nodes,
|
|
115
|
+
typeImportPath: options.typeImportPath,
|
|
116
|
+
exportedName: options.exportedName,
|
|
117
|
+
sourceLabel: options.sourceLabel
|
|
118
|
+
}),
|
|
119
|
+
"utf8"
|
|
120
|
+
);
|
|
121
|
+
return { nodes, outputPath: options.outputPath };
|
|
122
|
+
}
|
|
123
|
+
var ALLOWED_COMPONENTS = /* @__PURE__ */ new Set(["Card", "Cards", "Step", "Steps", "Callout", "Tab", "Tabs"]);
|
|
124
|
+
function extractCustomComponents(compiledCode) {
|
|
125
|
+
const found = /* @__PURE__ */ new Set();
|
|
126
|
+
const destructurePattern = /\{([^}]+)\}\s*=\s*_components/g;
|
|
127
|
+
let match;
|
|
128
|
+
while ((match = destructurePattern.exec(compiledCode)) !== null) {
|
|
129
|
+
for (const part of match[1].split(",")) {
|
|
130
|
+
const name = part.trim().split(":")[0].trim();
|
|
131
|
+
if (name && /^[A-Z]/.test(name)) {
|
|
132
|
+
found.add(name);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return [...found];
|
|
137
|
+
}
|
|
138
|
+
function validateComponents(nodeId, compiledCode) {
|
|
139
|
+
const components = extractCustomComponents(compiledCode);
|
|
140
|
+
const unknown = components.filter((c) => !ALLOWED_COMPONENTS.has(c));
|
|
141
|
+
if (unknown.length > 0) {
|
|
142
|
+
throw new Error(
|
|
143
|
+
`[knowledge-codegen] node "${nodeId}" uses unknown JSX component(s): ${unknown.join(", ")}.
|
|
144
|
+
Allowed components: ${[...ALLOWED_COMPONENTS].join(", ")}.
|
|
145
|
+
To add a component:
|
|
146
|
+
1. Add it to ALLOWED_COMPONENTS in packages/sdk/src/node/knowledge-bodies-codegen.ts
|
|
147
|
+
2. Add it to the runtime KnowledgeMDXProvider allowlist (Wave 3d)`
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
function stripToPlainText(body) {
|
|
152
|
+
return body.replace(/^(import|export)\s+.+$/gm, "").replace(/<[A-Z][^>]*>/g, "").replace(/<\/[A-Z][^>]*>/g, "").replace(/^#{1,6}\s+/gm, "").replace(/[*_`~]/g, "").replace(/\[([^\]]+)\]\([^)]+\)/g, "$1").replace(/\n{3,}/g, "\n\n").trim();
|
|
153
|
+
}
|
|
154
|
+
var BODIES_HEADER = [
|
|
155
|
+
"// @generated by generate-knowledge-bodies -- DO NOT EDIT",
|
|
156
|
+
"// Regenerate: pnpm scaffold:sync",
|
|
157
|
+
"// Source: packages/elevasis-core/src/organization-model.ts",
|
|
158
|
+
"",
|
|
159
|
+
"import { Fragment, jsx, jsxs } from 'react/jsx-runtime'",
|
|
160
|
+
"import type { ComponentType } from 'react'",
|
|
161
|
+
""
|
|
162
|
+
].join("\n");
|
|
163
|
+
var FACTORY_BLOCK = [
|
|
164
|
+
"// ---------------------------------------------------------------------------",
|
|
165
|
+
"// Inline compiled MDX component factory",
|
|
166
|
+
"// ---------------------------------------------------------------------------",
|
|
167
|
+
"",
|
|
168
|
+
"type MDXRuntime = {",
|
|
169
|
+
" Fragment: typeof Fragment",
|
|
170
|
+
" jsx: typeof jsx",
|
|
171
|
+
" jsxs: typeof jsxs",
|
|
172
|
+
"}",
|
|
173
|
+
"",
|
|
174
|
+
"type KnowledgeBodyProps = {",
|
|
175
|
+
" components?: Record<string, ComponentType>",
|
|
176
|
+
"}",
|
|
177
|
+
"",
|
|
178
|
+
"/**",
|
|
179
|
+
" * Wraps a compiled MDX function-body string into a React component.",
|
|
180
|
+
" * The function-body format from @mdx-js/mdx expects arguments[0]",
|
|
181
|
+
" * to be { Fragment, jsx, jsxs } from react/jsx-runtime.",
|
|
182
|
+
" */",
|
|
183
|
+
"function makeKnowledgeComponent(fnBody: string): ComponentType<KnowledgeBodyProps> {",
|
|
184
|
+
" // eslint-disable-next-line @typescript-eslint/no-implied-eval",
|
|
185
|
+
" const factory = new Function(fnBody) as (runtime: MDXRuntime) => { default: ComponentType }",
|
|
186
|
+
" return function KnowledgeBody(props: KnowledgeBodyProps) {",
|
|
187
|
+
" const runtime = { Fragment, jsx, jsxs, ...props } as unknown as MDXRuntime",
|
|
188
|
+
" const mod = factory(runtime)",
|
|
189
|
+
" const Content = mod.default",
|
|
190
|
+
" return Content ? jsx(Content as ComponentType<KnowledgeBodyProps>, props) : null",
|
|
191
|
+
" }",
|
|
192
|
+
"}",
|
|
193
|
+
""
|
|
194
|
+
].join("\n");
|
|
195
|
+
var MAP_HEADER = [
|
|
196
|
+
"// ---------------------------------------------------------------------------",
|
|
197
|
+
"// Generated map: node id -> compiled React component",
|
|
198
|
+
"// ---------------------------------------------------------------------------",
|
|
199
|
+
""
|
|
200
|
+
].join("\n");
|
|
201
|
+
async function generateKnowledgeBodies(nodes) {
|
|
202
|
+
if (nodes.length === 0) {
|
|
203
|
+
const bodiesTsx2 = [
|
|
204
|
+
"// @generated by generate-knowledge-bodies -- DO NOT EDIT",
|
|
205
|
+
"// Regenerate: pnpm scaffold:sync",
|
|
206
|
+
"// Source: packages/elevasis-core/src/organization-model.ts",
|
|
207
|
+
"",
|
|
208
|
+
"import type { ComponentType } from 'react'",
|
|
209
|
+
"",
|
|
210
|
+
"export const KNOWLEDGE_BODIES: Record<",
|
|
211
|
+
" string,",
|
|
212
|
+
" ComponentType<{ components?: Record<string, ComponentType> }>",
|
|
213
|
+
"> = {}",
|
|
214
|
+
""
|
|
215
|
+
].join("\n");
|
|
216
|
+
return { bodiesTsx: bodiesTsx2, searchIndex: [] };
|
|
217
|
+
}
|
|
218
|
+
const nodeComments = [];
|
|
219
|
+
const mapEntries = [];
|
|
220
|
+
const searchIndex = [];
|
|
221
|
+
for (const node of nodes) {
|
|
222
|
+
if (/^(import|export)\s+/m.test(node.body)) {
|
|
223
|
+
throw new Error(
|
|
224
|
+
`[knowledge-codegen] node "${node.id}" body contains import/export statements, which are not allowed in Phase 1 MDX bodies.`
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
const compiled = await compile(node.body, {
|
|
228
|
+
outputFormat: "function-body",
|
|
229
|
+
jsx: false,
|
|
230
|
+
remarkPlugins: [remarkGfm]
|
|
231
|
+
});
|
|
232
|
+
const fnBodyCode = String(compiled);
|
|
233
|
+
validateComponents(node.id, fnBodyCode);
|
|
234
|
+
const bodyText = stripToPlainText(node.body);
|
|
235
|
+
searchIndex.push({ id: node.id, title: node.title, summary: node.summary, bodyText });
|
|
236
|
+
nodeComments.push(`// Node: ${node.id} (${node.kind})`);
|
|
237
|
+
mapEntries.push(` '${node.id}': makeKnowledgeComponent(${JSON.stringify(fnBodyCode)})`);
|
|
238
|
+
}
|
|
239
|
+
const mapBlock = `export const KNOWLEDGE_BODIES: Record<string, ComponentType<KnowledgeBodyProps>> = {
|
|
240
|
+
` + mapEntries.join(",\n") + "\n}\n";
|
|
241
|
+
const bodiesTsx = BODIES_HEADER + FACTORY_BLOCK + MAP_HEADER + (nodeComments.length > 0 ? nodeComments.join("\n") + "\n\n" : "") + mapBlock;
|
|
242
|
+
return { bodiesTsx, searchIndex };
|
|
243
|
+
}
|
|
244
|
+
async function runKnowledgeCodegen(layout) {
|
|
245
|
+
let nodes;
|
|
246
|
+
if (layout.mode === "monorepo") {
|
|
247
|
+
const omSpecifier = "@repo/elevasis-core/organization-model";
|
|
248
|
+
const om = await import(
|
|
249
|
+
/* @vite-ignore */
|
|
250
|
+
omSpecifier
|
|
251
|
+
);
|
|
252
|
+
nodes = om.canonicalOrganizationModel.knowledge.nodes;
|
|
253
|
+
} else {
|
|
254
|
+
const outputPath = resolve(layout.generatedDir, "nodes.ts");
|
|
255
|
+
const result = generateKnowledgeNodes({
|
|
256
|
+
sourceDir: layout.mdxNodesDir,
|
|
257
|
+
outputPath,
|
|
258
|
+
typeImportPath: "@elevasis/core/organization-model",
|
|
259
|
+
exportedName: "mdxKnowledgeNodes"
|
|
260
|
+
});
|
|
261
|
+
nodes = result.nodes;
|
|
262
|
+
}
|
|
263
|
+
const { bodiesTsx, searchIndex } = await generateKnowledgeBodies(nodes);
|
|
264
|
+
mkdirSync(layout.generatedDir, { recursive: true });
|
|
265
|
+
writeFileSync(resolve(layout.generatedDir, "knowledge-bodies.tsx"), bodiesTsx, "utf8");
|
|
266
|
+
writeFileSync(
|
|
267
|
+
resolve(layout.generatedDir, "knowledge-search-index.json"),
|
|
268
|
+
JSON.stringify(searchIndex, null, 2) + "\n",
|
|
269
|
+
"utf8"
|
|
270
|
+
);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
export { generateKnowledgeBodies, generateKnowledgeNodes, generateKnowledgeNodesTs, readKnowledgeNodeMdx, runKnowledgeCodegen };
|