@elevasis/sdk 1.15.0 → 1.16.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.
Files changed (66) hide show
  1. package/dist/cli.cjs +2325 -124
  2. package/dist/index.d.ts +882 -794
  3. package/dist/index.js +170 -46
  4. package/dist/node/index.d.ts +69 -0
  5. package/dist/node/index.js +273 -0
  6. package/dist/test-utils/index.d.ts +857 -711
  7. package/dist/test-utils/index.js +2 -0
  8. package/dist/types/worker/adapters/lead.d.ts +1 -1
  9. package/dist/types/worker/platform.d.ts +2 -9
  10. package/dist/worker/index.js +1 -0
  11. package/package.json +12 -3
  12. package/reference/_navigation.md +23 -1
  13. package/reference/_reference-manifest.json +98 -0
  14. package/reference/claude-config/rules/agent-start-here.md +13 -0
  15. package/reference/claude-config/rules/organization-model.md +40 -40
  16. package/reference/claude-config/rules/organization-os.md +16 -16
  17. package/reference/claude-config/rules/ui.md +2 -6
  18. package/reference/claude-config/rules/vibe.md +13 -13
  19. package/reference/claude-config/skills/knowledge/SKILL.md +253 -0
  20. package/reference/claude-config/skills/{configure → knowledge}/operations/codify-level-a.md +100 -100
  21. package/reference/claude-config/skills/{configure → knowledge}/operations/codify-level-b.md +158 -158
  22. package/reference/claude-config/skills/knowledge/operations/customers.md +109 -0
  23. package/reference/claude-config/skills/knowledge/operations/features.md +113 -0
  24. package/reference/claude-config/skills/knowledge/operations/goals.md +118 -0
  25. package/reference/claude-config/skills/knowledge/operations/identity.md +93 -0
  26. package/reference/claude-config/skills/knowledge/operations/labels.md +89 -0
  27. package/reference/claude-config/skills/knowledge/operations/offerings.md +109 -0
  28. package/reference/claude-config/skills/knowledge/operations/roles.md +99 -0
  29. package/reference/claude-config/skills/knowledge/operations/techStack.md +102 -0
  30. package/reference/claude-config/skills/run-ui/SKILL.md +73 -0
  31. package/reference/claude-config/skills/setup/SKILL.md +270 -270
  32. package/reference/claude-config/skills/tutorial/SKILL.md +249 -0
  33. package/reference/claude-config/skills/tutorial/progress-template.md +74 -0
  34. package/reference/claude-config/skills/tutorial/technical.md +1309 -0
  35. package/reference/claude-config/skills/tutorial/vibe-coder.md +890 -0
  36. package/reference/claude-config/sync-notes/2026-05-02-crm-ownership-next-action.md +58 -0
  37. package/reference/claude-config/sync-notes/2026-05-02-template-hardcode-workos-config.md +56 -0
  38. package/reference/claude-config/sync-notes/2026-05-04-elevasis-workspace.md +71 -0
  39. package/reference/claude-config/sync-notes/2026-05-04-template-skills-run-ui-and-tutorial.md +59 -0
  40. package/reference/deployment/index.mdx +5 -5
  41. package/reference/examples/organization-model.ts +40 -0
  42. package/reference/framework/index.mdx +1 -1
  43. package/reference/framework/tutorial-system.mdx +86 -173
  44. package/reference/packages/core/src/knowledge/README.md +32 -0
  45. package/reference/packages/ui/src/knowledge/README.md +31 -0
  46. package/reference/packages/ui/src/theme/presets/README.md +19 -0
  47. package/reference/scaffold/core/organization-model.mdx +1 -1
  48. package/reference/scaffold/recipes/add-a-feature.md +1 -1
  49. package/reference/scaffold/recipes/customize-crm-actions.md +3 -3
  50. package/reference/scaffold/recipes/customize-organization-model.md +3 -3
  51. package/reference/scaffold/recipes/extend-crm.md +12 -8
  52. package/reference/scaffold/recipes/extend-lead-gen.md +129 -20
  53. package/reference/scaffold/recipes/gate-by-feature-or-admin.md +1 -1
  54. package/reference/scaffold/recipes/index.md +6 -0
  55. package/reference/scaffold/reference/contracts.md +829 -595
  56. package/reference/scaffold/reference/feature-registry.md +2 -1
  57. package/reference/scaffold/ui/composition-extensibility.mdx +17 -0
  58. package/reference/claude-config/skills/configure/SKILL.md +0 -98
  59. package/reference/claude-config/skills/configure/operations/customers.md +0 -150
  60. package/reference/claude-config/skills/configure/operations/features.md +0 -162
  61. package/reference/claude-config/skills/configure/operations/goals.md +0 -147
  62. package/reference/claude-config/skills/configure/operations/identity.md +0 -133
  63. package/reference/claude-config/skills/configure/operations/labels.md +0 -128
  64. package/reference/claude-config/skills/configure/operations/offerings.md +0 -159
  65. package/reference/claude-config/skills/configure/operations/roles.md +0 -153
  66. 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 = z.string().trim().min(1).max(80);
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"),
@@ -3787,6 +3839,13 @@ var DealCreatedEventSchema = z.object({
3787
3839
  type: z.literal("deal_created"),
3788
3840
  timestamp: z.string().datetime()
3789
3841
  });
3842
+ var ActionFailedEventSchema = z.object({
3843
+ type: z.literal("action_failed"),
3844
+ timestamp: z.string().datetime(),
3845
+ actionKey: z.string(),
3846
+ errorMessage: z.string(),
3847
+ payload: z.record(z.string(), z.unknown()).optional()
3848
+ });
3790
3849
  var ActivityEventSchema = z.discriminatedUnion("type", [
3791
3850
  StageChangeEventSchema,
3792
3851
  StateChangeEventSchema,
@@ -3795,8 +3854,67 @@ var ActivityEventSchema = z.discriminatedUnion("type", [
3795
3854
  ApprovalResolvedEventSchema,
3796
3855
  ApprovalStaleEventSchema,
3797
3856
  TaskCreatedEventSchema,
3798
- DealCreatedEventSchema
3857
+ DealCreatedEventSchema,
3858
+ ActionFailedEventSchema
3799
3859
  ]);
3860
+
3861
+ // ../core/src/business/acquisition/deal-ownership.ts
3862
+ var INBOUND_EVENT_TYPES = ["reply_received"];
3863
+ var OUTBOUND_EVENT_TYPES = [
3864
+ "reply_sent_to_lead",
3865
+ "booking_nudge_sent",
3866
+ "reminder_sent",
3867
+ "rebook_sent",
3868
+ "followup_email_sent",
3869
+ "reply_followup_sent",
3870
+ "lead_deferred_next_step"
3871
+ ];
3872
+ var INBOUND_SET = new Set(INBOUND_EVENT_TYPES);
3873
+ var OUTBOUND_SET = new Set(OUTBOUND_EVENT_TYPES);
3874
+ function getDealOwnership(deal) {
3875
+ if (deal.state_key === "closed_won" || deal.state_key === "closed_lost") {
3876
+ return null;
3877
+ }
3878
+ if (!Array.isArray(deal.activity_log)) return null;
3879
+ let latestInboundMs = null;
3880
+ let latestOutboundMs = null;
3881
+ for (const entry of deal.activity_log) {
3882
+ if (!entry || typeof entry !== "object" || Array.isArray(entry)) continue;
3883
+ const record = entry;
3884
+ const type = typeof record.type === "string" ? record.type : null;
3885
+ if (!type) continue;
3886
+ const isInbound = INBOUND_SET.has(type);
3887
+ const isOutbound = OUTBOUND_SET.has(type);
3888
+ if (!isInbound && !isOutbound) continue;
3889
+ const occurredAt = resolveTimestamp(record);
3890
+ if (occurredAt === null) continue;
3891
+ if (isInbound && (latestInboundMs === null || occurredAt > latestInboundMs)) {
3892
+ latestInboundMs = occurredAt;
3893
+ }
3894
+ if (isOutbound && (latestOutboundMs === null || occurredAt > latestOutboundMs)) {
3895
+ latestOutboundMs = occurredAt;
3896
+ }
3897
+ }
3898
+ if (latestInboundMs === null && latestOutboundMs === null) return null;
3899
+ if (latestOutboundMs !== null && (latestInboundMs === null || latestOutboundMs >= latestInboundMs)) {
3900
+ return "them";
3901
+ }
3902
+ return "us";
3903
+ }
3904
+ function resolveTimestamp(record) {
3905
+ return parseMs(record.timestamp) ?? parseMs(record.occurredAt) ?? parseMs(record.createdAt) ?? parseMs(record.updatedAt) ?? parseMs(record.sentAt) ?? null;
3906
+ }
3907
+ function parseMs(value) {
3908
+ if (value instanceof Date) {
3909
+ const ms2 = value.getTime();
3910
+ return Number.isNaN(ms2) ? null : ms2;
3911
+ }
3912
+ if (typeof value !== "string") return null;
3913
+ const ms = new Date(value).getTime();
3914
+ return Number.isNaN(ms) ? null : ms;
3915
+ }
3916
+
3917
+ // ../core/src/business/acquisition/derive-actions.ts
3800
3918
  var SendReplyActionPayloadSchema = z.object({
3801
3919
  replyBody: z.string().trim().min(1).max(1e4)
3802
3920
  }).strict();
@@ -3834,7 +3952,7 @@ var DEFAULT_CRM_ACTIONS = [
3834
3952
  {
3835
3953
  key: "send_reply",
3836
3954
  label: "Send Reply",
3837
- isAvailableFor: (deal) => deal.stage_key === "interested" && (deal.state_key === CRM_DISCOVERY_REPLIED_STATE.stateKey || deal.state_key === CRM_DISCOVERY_LINK_SENT_STATE.stateKey || deal.state_key === CRM_DISCOVERY_NUDGING_STATE.stateKey),
3955
+ isAvailableFor: (deal) => deal.stage_key === "interested" && isOurReplyAction(deal) && (deal.state_key === CRM_DISCOVERY_REPLIED_STATE.stateKey || deal.state_key === CRM_DISCOVERY_LINK_SENT_STATE.stateKey || deal.state_key === CRM_DISCOVERY_NUDGING_STATE.stateKey),
3838
3956
  workflowId: "crm-send-reply-workflow",
3839
3957
  payloadSchema: SendReplyActionPayloadSchema
3840
3958
  },
@@ -3869,5 +3987,11 @@ var DEFAULT_CRM_ACTIONS = [
3869
3987
  function deriveActions(deal, actions = DEFAULT_CRM_ACTIONS) {
3870
3988
  return actions.filter((a) => a.isAvailableFor(deal)).map(({ key, label, payloadSchema }) => ({ key, label, payloadSchema }));
3871
3989
  }
3990
+ function isOurReplyAction(deal) {
3991
+ if (Object.prototype.hasOwnProperty.call(deal, "nextAction")) {
3992
+ return deal.nextAction === "send_reply";
3993
+ }
3994
+ return (deal.ownership ?? getDealOwnership(deal)) === "us";
3995
+ }
3872
3996
 
3873
- 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 };