@vxnus/siduri 0.1.11 → 2.0.1

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 (65) hide show
  1. package/README.md +7 -7
  2. package/dist/builtin-manifests.js +42 -51
  3. package/dist/clean-machine-e2e.test.js +25 -101
  4. package/dist/clean-machine-smoke.test.d.ts +1 -0
  5. package/dist/clean-machine-smoke.test.js +166 -0
  6. package/dist/configurators/knowledge.js +134 -46
  7. package/dist/configurators/memory.js +7 -25
  8. package/dist/configurators.test.js +12 -13
  9. package/dist/db.d.ts +0 -12
  10. package/dist/db.js +7 -144
  11. package/dist/discovery.js +5 -7
  12. package/dist/discovery.test.js +5 -4
  13. package/dist/doctor-db.test.js +2 -91
  14. package/dist/doctor.js +53 -41
  15. package/dist/generator.js +91 -15
  16. package/dist/generator.test.js +10 -8
  17. package/dist/index.d.ts +1 -1
  18. package/dist/index.js +37 -32
  19. package/dist/manifest.d.ts +1 -1
  20. package/dist/providers/openrouter.js +1 -1
  21. package/dist/release-check.js +5 -9
  22. package/dist/web-dist/404/index.html +1 -1
  23. package/dist/web-dist/404.html +1 -1
  24. package/dist/web-dist/__next.__PAGE__.txt +12 -12
  25. package/dist/web-dist/__next._full.txt +12 -12
  26. package/dist/web-dist/__next._tree.txt +1 -1
  27. package/dist/web-dist/_next/static/chunks/0jgw-jf26m0ui.js +22 -0
  28. package/dist/web-dist/_next/static/chunks/0tdcnq1-n6kof.js +31 -0
  29. package/dist/web-dist/_next/static/chunks/12vwhiuvdy_i7.js +1 -0
  30. package/dist/web-dist/_next/static/chunks/{2_duynll0m51m.js → 15zsrb5462n_-.js} +2 -2
  31. package/dist/web-dist/_next/static/chunks/2dsfgoa225hpl.js +1 -0
  32. package/dist/web-dist/_next/static/chunks/2mno6yfdoee0x.js +1 -0
  33. package/dist/web-dist/_next/static/chunks/30a48dk5bjyhj.js +1 -0
  34. package/dist/web-dist/_next/static/chunks/360luxuz2x8y_.js +1 -0
  35. package/dist/web-dist/_next/static/chunks/turbopack-2hpygfb68u21h.js +1 -0
  36. package/dist/web-dist/_not-found/__next._full.txt +8 -8
  37. package/dist/web-dist/_not-found/__next._not-found.__PAGE__.txt +7 -7
  38. package/dist/web-dist/_not-found/__next._tree.txt +1 -1
  39. package/dist/web-dist/_not-found/index.html +1 -1
  40. package/dist/web-dist/_not-found/index.txt +8 -8
  41. package/dist/web-dist/chat/__next._full.txt +9 -9
  42. package/dist/web-dist/chat/__next._tree.txt +1 -1
  43. package/dist/web-dist/chat/__next.chat.__PAGE__.txt +8 -8
  44. package/dist/web-dist/chat/index.html +1 -1
  45. package/dist/web-dist/chat/index.txt +9 -9
  46. package/dist/web-dist/index.html +1 -1
  47. package/dist/web-dist/index.txt +12 -12
  48. package/dist/web-dist/operator/__next._full.txt +9 -9
  49. package/dist/web-dist/operator/__next._tree.txt +1 -1
  50. package/dist/web-dist/operator/__next.operator.__PAGE__.txt +8 -8
  51. package/dist/web-dist/operator/index.html +1 -1
  52. package/dist/web-dist/operator/index.txt +9 -9
  53. package/dist/web-template.js +1 -1
  54. package/package.json +10 -12
  55. package/dist/web-dist/_next/static/chunks/09y7khbmco_v4.js +0 -1
  56. package/dist/web-dist/_next/static/chunks/0uwih7sydv413.js +0 -1
  57. package/dist/web-dist/_next/static/chunks/1_yae15ffa5f6.js +0 -1
  58. package/dist/web-dist/_next/static/chunks/2kcqevsmv6kh2.js +0 -31
  59. package/dist/web-dist/_next/static/chunks/2s3d1bfr0l4b0.js +0 -1
  60. package/dist/web-dist/_next/static/chunks/32z87id9k6i2z.js +0 -22
  61. package/dist/web-dist/_next/static/chunks/3bx2gs3idjmo1.js +0 -1
  62. package/dist/web-dist/_next/static/chunks/turbopack-3eipe4c-imn5l.js +0 -1
  63. /package/dist/web-dist/_next/static/{pWHUixFDDvANZeAf-shqT → lQfIAv021hLk8KVAElf2K}/_buildManifest.js +0 -0
  64. /package/dist/web-dist/_next/static/{pWHUixFDDvANZeAf-shqT → lQfIAv021hLk8KVAElf2K}/_clientMiddlewareManifest.js +0 -0
  65. /package/dist/web-dist/_next/static/{pWHUixFDDvANZeAf-shqT → lQfIAv021hLk8KVAElf2K}/_ssgManifest.js +0 -0
@@ -5,15 +5,18 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.configureKnowledge = configureKnowledge;
7
7
  const inquirer_1 = __importDefault(require("inquirer"));
8
+ const node_fs_1 = __importDefault(require("node:fs"));
8
9
  const knowledge_hub_1 = require("../providers/knowledge-hub");
9
10
  async function configureKnowledge(_context, options = {}) {
10
11
  const client = options.client || new knowledge_hub_1.KnowledgeHubClient();
12
+ // 1. Ask Knowledge source / mode
11
13
  const { source } = await inquirer_1.default.prompt({
12
14
  type: 'list',
13
15
  name: 'source',
14
- message: 'Knowledge source?',
16
+ message: 'Select knowledge configuration:',
15
17
  choices: [
16
- { name: 'E Knowledge Hub (Search and attach remote factual pack)', value: 'e-hub' },
18
+ { name: 'E Knowledge Hub / Portable Pack (with Life Database)', value: 'e-hub' },
19
+ { name: 'Life Database Only (SQLite personal state, schedule, preferences)', value: 'life-only' },
17
20
  { name: 'Do not use knowledge', value: 'none' },
18
21
  ],
19
22
  });
@@ -21,80 +24,165 @@ async function configureKnowledge(_context, options = {}) {
21
24
  return {
22
25
  config: {
23
26
  provider: 'none',
27
+ lifeDatabase: false,
24
28
  },
25
29
  summary: {
26
30
  Source: 'Do not use knowledge',
31
+ Knowledge: 'Disabled',
27
32
  },
28
33
  };
29
34
  }
30
- // E Knowledge Hub flow
35
+ const summary = {};
36
+ const config = {
37
+ dbPath: 'siduri.sqlite',
38
+ lifeDatabase: true,
39
+ };
40
+ if (source === 'life-only') {
41
+ config.provider = 'unified';
42
+ summary.Source = 'Life Database';
43
+ summary['Life Database'] = 'Enabled (siduri.sqlite)';
44
+ summary['E-Pack'] = 'None';
45
+ return { config, summary };
46
+ }
47
+ // 2. Discovery-First E-Knowledge Resolution Flow
31
48
  while (true) {
32
- const { packId } = await inquirer_1.default.prompt({
49
+ const promptRes = await inquirer_1.default.prompt({
33
50
  type: 'input',
34
51
  name: 'packId',
35
- message: 'Search E Knowledge Hub or enter package ID:',
52
+ message: 'Enter package ID (@publisher/name), local pack path, or remote URL:',
36
53
  default: '@vxnus/e-teyvat',
37
- validate: (val) => val.trim().length > 0 || 'Please enter a package ID.',
54
+ validate: (val) => val.trim().length > 0 || 'Please enter a package ID, path, or URL.',
38
55
  });
39
- const trimmedPackId = packId.trim();
56
+ const target = (promptRes.packId || promptRes.query || '').trim();
57
+ // Case A: Remote HTTP endpoint
58
+ if (target.startsWith('http://') || target.startsWith('https://')) {
59
+ console.log(`\n\x1b[36m── Knowledge Provider ────────────────────────\x1b[0m\n`);
60
+ console.log(` \x1b[2mType:\x1b[0m Remote E-Provider`);
61
+ console.log(` \x1b[2mEndpoint:\x1b[0m ${target}`);
62
+ console.log(` \x1b[2mConnectors:\x1b[0m Remote only (HTTP stream)\n`);
63
+ config.provider = 'e-remote';
64
+ config.baseUrl = target;
65
+ config.pack = {
66
+ mode: 'remote',
67
+ baseUrl: target,
68
+ };
69
+ summary.Source = 'Remote E-Provider';
70
+ summary.Provider = target;
71
+ summary['E-Pack'] = `Remote (${target})`;
72
+ return { config, summary };
73
+ }
74
+ // Case B: Local path / file
75
+ if (target.startsWith('./') || target.startsWith('/') || node_fs_1.default.existsSync(target)) {
76
+ console.log(`\n\x1b[36m── Knowledge Provider ────────────────────────\x1b[0m\n`);
77
+ console.log(` \x1b[2mType:\x1b[0m Local Knowledge Pack`);
78
+ console.log(` \x1b[2mPath:\x1b[0m ${target}`);
79
+ console.log(` \x1b[2mConnectors:\x1b[0m Local only (Filesystem)\n`);
80
+ config.provider = 'e-knowledge';
81
+ config.packPath = target;
82
+ config.pack = {
83
+ mode: 'local',
84
+ packPath: target,
85
+ };
86
+ summary.Source = 'Local Knowledge Pack';
87
+ summary.Provider = target;
88
+ summary['E-Pack'] = `Local (${target})`;
89
+ return { config, summary };
90
+ }
91
+ // Case C: E Knowledge Hub package ID (@publisher/name)
40
92
  let manifest;
41
93
  try {
42
- process.stdout.write('\u001b[2mSearching E Knowledge Hub...\u001b[0m');
43
- manifest = await client.resolveProvider(trimmedPackId);
44
- process.stdout.write('\r\u001b[32m✓\u001b[0m Checking provider manifest\n');
94
+ process.stdout.write('\x1b[2mResolving knowledge metadata...\x1b[0m');
95
+ manifest = await client.resolveProvider(target);
96
+ process.stdout.write('\r\x1b[32m✓\x1b[0m Knowledge metadata resolved\n');
45
97
  }
46
98
  catch (err) {
47
- process.stdout.write('\r\u001b[33m!\u001b[0m Provider resolution failed\n');
48
- console.log(`\u001b[33mReason:\u001b[0m ${err.message}\n`);
99
+ process.stdout.write('\r\x1b[33m!\x1b[0m Knowledge resolution failed\n');
100
+ console.log(`\x1b[33mReason:\x1b[0m ${err.message}\n`);
49
101
  const { failureAction } = await inquirer_1.default.prompt({
50
102
  type: 'list',
51
103
  name: 'failureAction',
52
104
  message: 'What would you like to do?',
53
105
  choices: [
54
- { name: 'Try again / Search another provider', value: 'retry' },
55
- { name: 'Do not use knowledge', value: 'skip' },
106
+ { name: 'Try again / Enter another package ID or path', value: 'retry' },
107
+ { name: 'Skip attaching E-pack (keep Life DB only)', value: 'skip' },
56
108
  { name: 'Cancel', value: 'cancel' },
57
109
  ],
58
110
  });
59
- if (failureAction === 'retry') {
111
+ if (failureAction === 'retry')
60
112
  continue;
61
- }
62
113
  if (failureAction === 'skip') {
63
- return {
64
- config: { provider: 'none' },
65
- summary: { Source: 'Do not use knowledge' },
66
- };
114
+ config.provider = 'unified';
115
+ summary.Source = 'Life Database';
116
+ summary['E-Pack'] = 'None';
117
+ return { config, summary };
67
118
  }
68
119
  throw new Error('Knowledge configuration cancelled.');
69
120
  }
70
- // Display provider summary
71
- (0, knowledge_hub_1.displayKnowledgeProviderSummary)(manifest, trimmedPackId);
72
- const { confirmProvider } = await inquirer_1.default.prompt({
121
+ // Display metadata
122
+ (0, knowledge_hub_1.displayKnowledgeProviderSummary)(manifest, target);
123
+ // Inspect supported connectors
124
+ const hasRemoteUrl = Boolean(manifest.distribution?.url);
125
+ const connectorChoices = [];
126
+ if (hasRemoteUrl) {
127
+ connectorChoices.push({
128
+ name: `Remote (Stream directly from ${manifest.distribution?.url} without local storage)`,
129
+ value: 'remote',
130
+ });
131
+ connectorChoices.push({
132
+ name: 'Local (Download and run offline pack files on this machine)',
133
+ value: 'local',
134
+ });
135
+ connectorChoices.push({
136
+ name: 'Hybrid (Local pack cache with remote provider fallback/sync)',
137
+ value: 'hybrid',
138
+ });
139
+ }
140
+ else {
141
+ connectorChoices.push({
142
+ name: 'Local (Offline pack files on this machine)',
143
+ value: 'local',
144
+ });
145
+ }
146
+ connectorChoices.push({
147
+ name: 'Choose another provider',
148
+ value: 'retry',
149
+ });
150
+ const choiceRes = await inquirer_1.default.prompt({
73
151
  type: 'list',
74
- name: 'confirmProvider',
75
- message: 'Use this knowledge provider?',
76
- choices: [
77
- { name: 'Yes, use this provider', value: 'yes' },
78
- { name: 'Choose another provider', value: 'retry' },
79
- ],
152
+ name: 'connectorMode',
153
+ message: 'Choose connection type for this pack:',
154
+ choices: connectorChoices,
80
155
  });
81
- if (confirmProvider === 'yes') {
82
- return {
83
- config: {
84
- provider: 'e-hub',
85
- registryUrl: 'https://e.vxnus.xyz/api/v1/knowledge',
86
- packId: trimmedPackId,
87
- },
88
- summary: {
89
- Source: 'E Knowledge Hub',
90
- Provider: manifest.displayName || manifest.name,
91
- Package: trimmedPackId,
92
- Version: manifest.version,
93
- },
94
- metadata: {
95
- manifest,
96
- },
97
- };
156
+ const rawChoice = choiceRes.connectorMode ?? choiceRes.confirmProvider;
157
+ if (rawChoice === 'retry') {
158
+ continue;
98
159
  }
160
+ const connectorMode = (rawChoice === 'yes'
161
+ ? (hasRemoteUrl ? 'remote' : 'local')
162
+ : (['remote', 'local', 'hybrid'].includes(rawChoice || '') ? rawChoice : 'local'));
163
+ config.provider = 'e-hub';
164
+ config.registryUrl = 'https://e.vxnus.xyz/api/v1/knowledge';
165
+ config.packId = target;
166
+ config.pack = {
167
+ mode: connectorMode,
168
+ packId: target,
169
+ registryUrl: 'https://e.vxnus.xyz/api/v1/knowledge',
170
+ baseUrl: manifest.distribution?.url,
171
+ preferredMode: 'lexical',
172
+ };
173
+ if (manifest.distribution?.url) {
174
+ config.baseUrl = manifest.distribution.url;
175
+ }
176
+ summary.Source = 'E Knowledge Hub';
177
+ summary.Provider = manifest.displayName || manifest.name;
178
+ summary.Package = target;
179
+ summary.Version = manifest.version;
180
+ summary['Life Database'] = 'Enabled (siduri.sqlite)';
181
+ summary['E-Pack'] = `${manifest.displayName || manifest.name} (${connectorMode})`;
182
+ return {
183
+ config,
184
+ summary,
185
+ metadata: { manifest },
186
+ };
99
187
  }
100
188
  }
@@ -6,39 +6,21 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.configureMemory = configureMemory;
7
7
  const inquirer_1 = __importDefault(require("inquirer"));
8
8
  async function configureMemory(_context) {
9
- const { database } = await inquirer_1.default.prompt({
9
+ const { provider } = await inquirer_1.default.prompt({
10
10
  type: 'list',
11
- name: 'database',
12
- message: 'Memory database?',
11
+ name: 'provider',
12
+ message: 'Memory storage engine?',
13
13
  choices: [
14
- { name: 'PostgreSQL (FTS relational claims with ACID durability)', value: 'postgres' },
14
+ { name: 'SQLite (Unified zero-config WAL + FTS5 full-text indexing)', value: 'sqlite' },
15
15
  ],
16
16
  });
17
- const { deployment } = await inquirer_1.default.prompt({
18
- type: 'list',
19
- name: 'deployment',
20
- message: 'PostgreSQL deployment target?',
21
- choices: [
22
- { name: 'Supabase (Hosted Postgres with connection pooling)', value: 'supabase' },
23
- { name: 'Neon (Serverless Postgres with branch-per-companion)', value: 'neon' },
24
- { name: 'Local PostgreSQL (System service or local installation)', value: 'local' },
25
- { name: 'Other PostgreSQL URL', value: 'other' },
26
- ],
27
- });
28
- const deploymentDisplayNames = {
29
- supabase: 'Supabase',
30
- neon: 'Neon',
31
- local: 'Local PostgreSQL',
32
- other: 'Other PostgreSQL',
33
- };
34
17
  return {
35
18
  config: {
36
- provider: database,
37
- deployment,
19
+ provider,
38
20
  },
39
21
  summary: {
40
- Database: 'PostgreSQL',
41
- Deploy: deploymentDisplayNames[deployment] || deployment,
22
+ Database: 'SQLite (WAL + FTS5)',
23
+ Storage: 'siduri.sqlite',
42
24
  },
43
25
  };
44
26
  }
@@ -179,14 +179,13 @@ describe('Guided Manifest-Driven Configuration UX Specification Tests', () => {
179
179
  });
180
180
  });
181
181
  describe('Other Organ Configurators & Generic Router', () => {
182
- test('Memory configurator configures PostgreSQL with Supabase deployment', async () => {
182
+ test('Memory configurator configures SQLite with WAL and FTS5', async () => {
183
183
  inquirer_1.default.prompt
184
- .mockResolvedValueOnce({ database: 'postgres' })
185
- .mockResolvedValueOnce({ deployment: 'supabase' });
184
+ .mockResolvedValueOnce({ provider: 'sqlite' });
186
185
  const result = await (0, memory_1.configureMemory)({ companionName: 'Sparkle', manifest: memoryManifest });
187
- expect(result.config.provider).toBe('postgres');
188
- expect(result.config.deployment).toBe('supabase');
189
- expect(result.summary?.Deploy).toBe('Supabase');
186
+ expect(result.config.provider).toBe('sqlite');
187
+ expect(result.summary?.Database).toBe('SQLite (WAL + FTS5)');
188
+ expect(result.summary?.Storage).toBe('siduri.sqlite');
190
189
  });
191
190
  test('Voice configurator configures VOICEVOX engine and speaker ID', async () => {
192
191
  inquirer_1.default.prompt
@@ -283,7 +282,7 @@ describe('Guided Manifest-Driven Configuration UX Specification Tests', () => {
283
282
  test('formatReviewSummary displays actual configuration values and metadata', () => {
284
283
  const summaryOutput = (0, index_1.formatReviewSummary)('Sparkle', [brainManifest, memoryManifest, knowledgeManifest, voiceManifest], {
285
284
  brain: { Provider: 'OpenRouter', Model: 'openai/gpt-4o-mini' },
286
- memory: { Database: 'PostgreSQL', Deploy: 'Supabase' },
285
+ memory: { Database: 'SQLite (WAL + FTS5)', Storage: 'siduri.sqlite' },
287
286
  knowledge: { Source: 'E Knowledge Hub', Provider: 'E Teyvat', Package: '@vxnus/e-teyvat', Version: '1.2.0' },
288
287
  voice: { Provider: 'VOICEVOX', 'Speaker ID': 1 },
289
288
  });
@@ -292,8 +291,8 @@ describe('Guided Manifest-Driven Configuration UX Specification Tests', () => {
292
291
  expect(summaryOutput).toContain('OpenRouter');
293
292
  expect(summaryOutput).toContain('openai/gpt-4o-mini');
294
293
  expect(summaryOutput).toContain('Memory');
295
- expect(summaryOutput).toContain('PostgreSQL');
296
- expect(summaryOutput).toContain('Supabase');
294
+ expect(summaryOutput).toContain('SQLite');
295
+ expect(summaryOutput).toContain('siduri.sqlite');
297
296
  expect(summaryOutput).toContain('Knowledge');
298
297
  expect(summaryOutput).toContain('E Knowledge Hub');
299
298
  expect(summaryOutput).toContain('E Teyvat');
@@ -304,7 +303,7 @@ describe('Guided Manifest-Driven Configuration UX Specification Tests', () => {
304
303
  test('generateInstanceFiles accurately embeds configured organ values in siduri.config.json', () => {
305
304
  const organConfigs = {
306
305
  brain: { provider: 'openrouter', model: 'openai/gpt-4o-mini', apiKeyEnv: 'OPENROUTER_API_KEY' },
307
- memory: { provider: 'postgres', deployment: 'supabase' },
306
+ memory: { provider: 'sqlite' },
308
307
  knowledge: { provider: 'e-hub', registryUrl: 'https://e.vxnus.xyz/api/v1/knowledge', packId: '@vxnus/e-teyvat' },
309
308
  voice: { provider: 'voicevox', speakerId: 2, baseUrl: 'http://localhost:50021' },
310
309
  };
@@ -317,14 +316,14 @@ describe('Guided Manifest-Driven Configuration UX Specification Tests', () => {
317
316
  expect(config.name).toBe('Sparkle');
318
317
  expect(config.organs.brain.provider).toBe('openrouter');
319
318
  expect(config.organs.brain.model).toBe('openai/gpt-4o-mini');
320
- expect(config.organs.memory.deployment).toBe('supabase');
319
+ expect(config.organs.memory.provider).toBe('sqlite');
321
320
  expect(config.organs.knowledge.provider).toBe('e-hub');
322
321
  expect(config.organs.knowledge.packId).toBe('@vxnus/e-teyvat');
323
322
  expect(config.organs.voice.speakerId).toBe(2);
324
323
  // Verify explicit imports in src/index.js
325
324
  expect(files['src/index.js']).toContain("import { OpenRouterBrain } from '@siduri-x/brain'");
326
- expect(files['src/index.js']).toContain("import { PostgresMemoryOrgan } from '@siduri-x/memory'");
327
- expect(files['src/index.js']).toContain("import { EKnowledgeAdapter } from '@siduri-x/knowledge'");
325
+ expect(files['src/index.js']).toContain("import { SqliteMemoryStore } from '@siduri-x/memory'");
326
+ expect(files['src/index.js']).toContain("import { UnifiedKnowledgeOrgan } from '@siduri-x/knowledge'");
328
327
  expect(files['src/index.js']).toContain("import { VoiceAdapter } from '@siduri-x/voice'");
329
328
  });
330
329
  });
package/dist/db.d.ts CHANGED
@@ -1,16 +1,4 @@
1
1
  export declare function redactDatabaseUrl(url: string): string;
2
- export interface MigrationFile {
3
- name: string;
4
- fullPath: string;
5
- sql: string;
6
- checksum: string;
7
- }
8
- export interface MigrationRecord {
9
- id: number;
10
- name: string;
11
- checksum: string;
12
- applied_at: string;
13
- }
14
2
  export interface DbPushOptions {
15
3
  projectDir?: string;
16
4
  connectionString?: string;
package/dist/db.js CHANGED
@@ -1,152 +1,15 @@
1
1
  "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
2
  Object.defineProperty(exports, "__esModule", { value: true });
6
3
  exports.redactDatabaseUrl = redactDatabaseUrl;
7
4
  exports.runDbPush = runDbPush;
8
- const node_crypto_1 = require("node:crypto");
9
- const node_fs_1 = __importDefault(require("node:fs"));
10
- const node_path_1 = __importDefault(require("node:path"));
11
- const pg_1 = require("pg");
12
- const discovery_1 = require("./discovery");
13
5
  function redactDatabaseUrl(url) {
14
- try {
15
- const parsed = new URL(url);
16
- if (parsed.password) {
17
- parsed.password = '***';
18
- }
19
- return parsed.toString();
20
- }
21
- catch {
22
- return 'postgres://***:***@...';
23
- }
6
+ return url;
24
7
  }
25
8
  async function runDbPush(options = {}) {
26
- const projectDir = options.projectDir ? node_path_1.default.resolve(options.projectDir) : process.cwd();
27
- const configPath = node_path_1.default.join(projectDir, 'siduri.config.json');
28
- if (!node_fs_1.default.existsSync(configPath)) {
29
- throw new Error(`siduri.config.json not found at ${projectDir}. Make sure you are in a Siduri instance directory.`);
30
- }
31
- const config = JSON.parse(node_fs_1.default.readFileSync(configPath, 'utf8'));
32
- const configuredOrgans = config.organs || {};
33
- // 1. Discover manifests of configured organs
34
- const registry = discovery_1.OrganRegistry.discover([
35
- node_path_1.default.join(projectDir, 'node_modules/@siduri-x'),
36
- node_path_1.default.resolve(__dirname, '../../packages/organs'),
37
- node_path_1.default.resolve(process.cwd(), 'packages/organs'),
38
- ]);
39
- const databaseOrgans = [];
40
- for (const [key] of Object.entries(configuredOrgans)) {
41
- const manifest = registry.get(key) || registry.getAll().find((m) => m.configKey === key || m.organType === key);
42
- if (manifest && manifest.database && manifest.database.migrationsDir) {
43
- // Resolve migrations directory relative to the organ package
44
- const candidatePaths = [
45
- node_path_1.default.resolve(projectDir, 'node_modules', manifest.name, manifest.database.migrationsDir),
46
- node_path_1.default.resolve(__dirname, '../../packages/organs', manifest.organType, manifest.database.migrationsDir),
47
- node_path_1.default.resolve(process.cwd(), 'packages/organs', manifest.organType, manifest.database.migrationsDir),
48
- ];
49
- const resolvedDir = candidatePaths.find((p) => node_fs_1.default.existsSync(p));
50
- if (resolvedDir) {
51
- databaseOrgans.push({
52
- organType: manifest.organType,
53
- manifest,
54
- migrationsDir: resolvedDir,
55
- });
56
- }
57
- }
58
- }
59
- if (databaseOrgans.length === 0) {
60
- return {
61
- status: 'NOOP',
62
- appliedMigrations: [],
63
- skippedMigrations: [],
64
- message: 'No database migrations are required by this instance.',
65
- };
66
- }
67
- // 2. Resolve DATABASE_URL
68
- const connectionString = options.connectionString ||
69
- options.env?.DATABASE_URL ||
70
- process.env.DATABASE_URL;
71
- if (!connectionString) {
72
- throw new Error('DATABASE_URL is required to run database migrations. Set it in .env or environment.');
73
- }
74
- const pool = new pg_1.Pool({
75
- connectionString,
76
- connectionTimeoutMillis: 5000,
77
- });
78
- const client = await pool.connect();
79
- try {
80
- // 3. Ensure migrations table exists
81
- await client.query(`
82
- CREATE TABLE IF NOT EXISTS siduri_migrations (
83
- id SERIAL PRIMARY KEY,
84
- name VARCHAR(255) UNIQUE NOT NULL,
85
- checksum VARCHAR(64) NOT NULL,
86
- applied_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
87
- );
88
- `);
89
- // 4. Fetch applied migrations
90
- const existingRes = await client.query('SELECT name, checksum FROM siduri_migrations ORDER BY id ASC;');
91
- const appliedMap = new Map();
92
- for (const row of existingRes.rows) {
93
- appliedMap.set(row.name, row.checksum);
94
- }
95
- // 5. Gather all migration files across database organs in deterministic order
96
- const allMigrationFiles = [];
97
- for (const item of databaseOrgans) {
98
- const files = node_fs_1.default.readdirSync(item.migrationsDir).filter((f) => f.endsWith('.sql')).sort();
99
- for (const file of files) {
100
- const fullPath = node_path_1.default.join(item.migrationsDir, file);
101
- const sql = node_fs_1.default.readFileSync(fullPath, 'utf8');
102
- const checksum = (0, node_crypto_1.createHash)('sha256').update(sql).digest('hex');
103
- allMigrationFiles.push({
104
- name: `${item.organType}/${file}`,
105
- fullPath,
106
- sql,
107
- checksum,
108
- });
109
- }
110
- }
111
- allMigrationFiles.sort((a, b) => a.name.localeCompare(b.name));
112
- const appliedList = [];
113
- const skippedList = [];
114
- for (const migration of allMigrationFiles) {
115
- const recordedChecksum = appliedMap.get(migration.name);
116
- if (recordedChecksum !== undefined) {
117
- if (recordedChecksum !== migration.checksum) {
118
- throw new Error(`Migration checksum mismatch for ${migration.name}! Recorded: ${recordedChecksum.slice(0, 8)}, Current: ${migration.checksum.slice(0, 8)}. Refusing to apply modified migration.`);
119
- }
120
- skippedList.push(migration.name);
121
- }
122
- else {
123
- // Apply migration in transaction
124
- await client.query('BEGIN');
125
- try {
126
- await client.query(migration.sql);
127
- await client.query('INSERT INTO siduri_migrations (name, checksum) VALUES ($1, $2)', [migration.name, migration.checksum]);
128
- await client.query('COMMIT');
129
- appliedList.push(migration.name);
130
- }
131
- catch (err) {
132
- await client.query('ROLLBACK');
133
- throw err;
134
- }
135
- }
136
- }
137
- const redacted = redactDatabaseUrl(connectionString);
138
- const message = appliedList.length > 0
139
- ? `Applied ${appliedList.length} migration(s) to ${redacted}`
140
- : `Database schema up to date on ${redacted}`;
141
- return {
142
- status: appliedList.length > 0 ? 'APPLIED' : 'UP_TO_DATE',
143
- appliedMigrations: appliedList,
144
- skippedMigrations: skippedList,
145
- message,
146
- };
147
- }
148
- finally {
149
- client.release();
150
- await pool.end().catch(() => { });
151
- }
9
+ return {
10
+ status: 'NOOP',
11
+ appliedMigrations: [],
12
+ skippedMigrations: [],
13
+ message: 'SQLite manages schema natively. No CLI migrations required.',
14
+ };
152
15
  }
package/dist/discovery.js CHANGED
@@ -38,11 +38,10 @@ class OrganRegistry {
38
38
  const rootsToScan = searchRoots && searchRoots.length > 0
39
39
  ? searchRoots
40
40
  : [
41
- // 1. Monorepo organs folder relative to cli
42
41
  node_path_1.default.resolve(__dirname, '../../packages/organs'),
43
- // 2. Monorepo organs folder relative to cwd
44
42
  node_path_1.default.resolve(process.cwd(), 'packages/organs'),
45
- // 3. Node modules of current directory or global resolution
43
+ node_path_1.default.resolve(__dirname, '../../packages'),
44
+ node_path_1.default.resolve(process.cwd(), 'packages'),
46
45
  node_path_1.default.resolve(process.cwd(), 'node_modules/@siduri-x'),
47
46
  node_path_1.default.resolve(__dirname, '../node_modules/@siduri-x'),
48
47
  ];
@@ -74,10 +73,9 @@ class OrganRegistry {
74
73
  // Continue scanning other roots
75
74
  }
76
75
  }
77
- // If running in a standalone environment without local package folders (e.g. via npx in an empty directory),
78
- // register canonical built-in @siduri-x/* organ manifests as fallback.
79
- if (registry.getAll().length === 0) {
80
- for (const builtinManifest of builtin_manifests_1.BUILTIN_ORGAN_MANIFESTS) {
76
+ // Always merge canonical built-in @siduri-x/* organ manifests as fallback for ones that weren't found
77
+ for (const builtinManifest of builtin_manifests_1.BUILTIN_ORGAN_MANIFESTS) {
78
+ if (!registry.get(builtinManifest.organType)) {
81
79
  registry.register(builtinManifest);
82
80
  }
83
81
  }
@@ -8,16 +8,17 @@ const discovery_1 = require("./discovery");
8
8
  const generator_1 = require("./generator");
9
9
  describe('Discovery & Dynamic Composition System Tests (Phase 3)', () => {
10
10
  const rootOrgansDir = node_path_1.default.resolve(__dirname, '../../packages/organs');
11
- test('Registry successfully discovers all 11 organ packages from workspace', () => {
12
- const registry = discovery_1.OrganRegistry.discover([rootOrgansDir]);
11
+ test('Registry successfully discovers all 12 organ packages from workspace', () => {
12
+ const registry = discovery_1.OrganRegistry.discover();
13
13
  const manifests = registry.getAll();
14
- expect(manifests.length).toBe(11);
14
+ expect(manifests.length).toBe(12);
15
15
  const organTypes = registry.getAvailableOrganTypes().sort();
16
16
  expect(organTypes).toEqual([
17
17
  'behavior',
18
18
  'body',
19
19
  'brain',
20
20
  'ear',
21
+ 'eknowledge',
21
22
  'hands',
22
23
  'knowledge',
23
24
  'memory',
@@ -28,7 +29,7 @@ describe('Discovery & Dynamic Composition System Tests (Phase 3)', () => {
28
29
  ]);
29
30
  });
30
31
  test('Generates valid instances from discovered manifests', () => {
31
- const registry = discovery_1.OrganRegistry.discover([rootOrgansDir]);
32
+ const registry = discovery_1.OrganRegistry.discover();
32
33
  // Test Brain + Hands + Vision
33
34
  const brain = registry.get('brain');
34
35
  const hands = registry.get('hands');