cognitive-modules-cli 2.2.0 → 2.2.5

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 (94) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/LICENSE +21 -0
  3. package/README.md +35 -29
  4. package/dist/cli.js +572 -28
  5. package/dist/commands/add.d.ts +33 -14
  6. package/dist/commands/add.js +222 -13
  7. package/dist/commands/compose.d.ts +31 -0
  8. package/dist/commands/compose.js +185 -0
  9. package/dist/commands/index.d.ts +5 -0
  10. package/dist/commands/index.js +5 -0
  11. package/dist/commands/init.js +23 -1
  12. package/dist/commands/migrate.d.ts +30 -0
  13. package/dist/commands/migrate.js +650 -0
  14. package/dist/commands/pipe.d.ts +1 -0
  15. package/dist/commands/pipe.js +31 -11
  16. package/dist/commands/remove.js +33 -2
  17. package/dist/commands/run.d.ts +1 -0
  18. package/dist/commands/run.js +37 -27
  19. package/dist/commands/search.d.ts +28 -0
  20. package/dist/commands/search.js +143 -0
  21. package/dist/commands/test.d.ts +65 -0
  22. package/dist/commands/test.js +454 -0
  23. package/dist/commands/update.d.ts +1 -0
  24. package/dist/commands/update.js +106 -14
  25. package/dist/commands/validate.d.ts +36 -0
  26. package/dist/commands/validate.js +97 -0
  27. package/dist/errors/index.d.ts +218 -0
  28. package/dist/errors/index.js +412 -0
  29. package/dist/index.d.ts +2 -2
  30. package/dist/index.js +5 -1
  31. package/dist/mcp/server.js +84 -79
  32. package/dist/modules/composition.d.ts +251 -0
  33. package/dist/modules/composition.js +1330 -0
  34. package/dist/modules/index.d.ts +2 -0
  35. package/dist/modules/index.js +2 -0
  36. package/dist/modules/loader.d.ts +22 -2
  37. package/dist/modules/loader.js +171 -6
  38. package/dist/modules/runner.d.ts +422 -1
  39. package/dist/modules/runner.js +1472 -71
  40. package/dist/modules/subagent.d.ts +6 -1
  41. package/dist/modules/subagent.js +20 -13
  42. package/dist/modules/validator.d.ts +28 -0
  43. package/dist/modules/validator.js +637 -0
  44. package/dist/providers/anthropic.d.ts +15 -0
  45. package/dist/providers/anthropic.js +147 -5
  46. package/dist/providers/base.d.ts +11 -0
  47. package/dist/providers/base.js +18 -0
  48. package/dist/providers/gemini.d.ts +15 -0
  49. package/dist/providers/gemini.js +122 -5
  50. package/dist/providers/ollama.d.ts +15 -0
  51. package/dist/providers/ollama.js +111 -3
  52. package/dist/providers/openai.d.ts +11 -0
  53. package/dist/providers/openai.js +133 -0
  54. package/dist/registry/client.d.ts +204 -0
  55. package/dist/registry/client.js +356 -0
  56. package/dist/registry/index.d.ts +4 -0
  57. package/dist/registry/index.js +4 -0
  58. package/dist/server/http.js +173 -42
  59. package/dist/types.d.ts +123 -8
  60. package/dist/types.js +4 -1
  61. package/dist/version.d.ts +1 -0
  62. package/dist/version.js +4 -0
  63. package/package.json +32 -7
  64. package/src/cli.ts +0 -410
  65. package/src/commands/add.ts +0 -315
  66. package/src/commands/index.ts +0 -12
  67. package/src/commands/init.ts +0 -94
  68. package/src/commands/list.ts +0 -33
  69. package/src/commands/pipe.ts +0 -76
  70. package/src/commands/remove.ts +0 -57
  71. package/src/commands/run.ts +0 -80
  72. package/src/commands/update.ts +0 -130
  73. package/src/commands/versions.ts +0 -79
  74. package/src/index.ts +0 -55
  75. package/src/mcp/index.ts +0 -5
  76. package/src/mcp/server.ts +0 -403
  77. package/src/modules/index.ts +0 -7
  78. package/src/modules/loader.ts +0 -318
  79. package/src/modules/runner.ts +0 -495
  80. package/src/modules/subagent.ts +0 -275
  81. package/src/providers/anthropic.ts +0 -89
  82. package/src/providers/base.ts +0 -29
  83. package/src/providers/deepseek.ts +0 -83
  84. package/src/providers/gemini.ts +0 -117
  85. package/src/providers/index.ts +0 -78
  86. package/src/providers/minimax.ts +0 -81
  87. package/src/providers/moonshot.ts +0 -82
  88. package/src/providers/ollama.ts +0 -83
  89. package/src/providers/openai.ts +0 -84
  90. package/src/providers/qwen.ts +0 -82
  91. package/src/server/http.ts +0 -316
  92. package/src/server/index.ts +0 -6
  93. package/src/types.ts +0 -495
  94. package/tsconfig.json +0 -17
@@ -0,0 +1,637 @@
1
+ /**
2
+ * Module Validator - Validate cognitive module structure and examples.
3
+ * Supports v0, v1, v2.1, and v2.2 module formats.
4
+ */
5
+ import * as fs from 'node:fs/promises';
6
+ import * as path from 'node:path';
7
+ import yaml from 'js-yaml';
8
+ import _Ajv from 'ajv';
9
+ const Ajv = _Ajv.default || _Ajv;
10
+ const ajv = new Ajv({ allErrors: true, strict: false });
11
+ // =============================================================================
12
+ // Main Validation Entry Point
13
+ // =============================================================================
14
+ /**
15
+ * Validate a cognitive module's structure and examples.
16
+ * Supports all formats.
17
+ *
18
+ * @param nameOrPath Module name or path
19
+ * @param v22 If true, validate v2.2 specific requirements
20
+ * @returns Validation result with errors and warnings
21
+ */
22
+ export async function validateModule(modulePath, v22 = false) {
23
+ const errors = [];
24
+ const warnings = [];
25
+ // Check if path exists
26
+ try {
27
+ await fs.access(modulePath);
28
+ }
29
+ catch {
30
+ return { valid: false, errors: [`Module not found: ${modulePath}`], warnings: [] };
31
+ }
32
+ // Detect format
33
+ const hasModuleYaml = await fileExists(path.join(modulePath, 'module.yaml'));
34
+ const hasModuleMd = await fileExists(path.join(modulePath, 'MODULE.md'));
35
+ const hasOldModuleMd = await fileExists(path.join(modulePath, 'module.md'));
36
+ if (hasModuleYaml) {
37
+ // v2.x format
38
+ if (v22) {
39
+ return validateV22Format(modulePath);
40
+ }
41
+ else {
42
+ return validateV2Format(modulePath);
43
+ }
44
+ }
45
+ else if (hasModuleMd) {
46
+ // v1 format
47
+ if (v22) {
48
+ errors.push("Module is v1 format. Use 'cog migrate' to upgrade to v2.2");
49
+ return { valid: false, errors, warnings };
50
+ }
51
+ return validateV1Format(modulePath);
52
+ }
53
+ else if (hasOldModuleMd) {
54
+ // v0 format
55
+ if (v22) {
56
+ errors.push("Module is v0 format. Use 'cog migrate' to upgrade to v2.2");
57
+ return { valid: false, errors, warnings };
58
+ }
59
+ return validateV0Format(modulePath);
60
+ }
61
+ else {
62
+ return { valid: false, errors: ['Missing module.yaml, MODULE.md, or module.md'], warnings: [] };
63
+ }
64
+ }
65
+ // =============================================================================
66
+ // v2.2 Validation
67
+ // =============================================================================
68
+ async function validateV22Format(modulePath) {
69
+ const errors = [];
70
+ const warnings = [];
71
+ // Check module.yaml
72
+ const moduleYamlPath = path.join(modulePath, 'module.yaml');
73
+ let manifest;
74
+ try {
75
+ const content = await fs.readFile(moduleYamlPath, 'utf-8');
76
+ const loaded = yaml.load(content);
77
+ manifest = loaded && typeof loaded === 'object' ? loaded : {};
78
+ }
79
+ catch (e) {
80
+ errors.push(`Invalid YAML in module.yaml: ${e.message}`);
81
+ return { valid: false, errors, warnings };
82
+ }
83
+ // Check v2.2 required fields
84
+ const v22RequiredFields = ['name', 'version', 'responsibility'];
85
+ for (const field of v22RequiredFields) {
86
+ if (!(field in manifest)) {
87
+ errors.push(`module.yaml missing required field: ${field}`);
88
+ }
89
+ }
90
+ // Check tier (v2.2 specific)
91
+ const tier = manifest.tier;
92
+ if (!tier) {
93
+ warnings.push("module.yaml missing 'tier' (recommended: exec | decision | exploration)");
94
+ }
95
+ else if (!['exec', 'decision', 'exploration'].includes(tier)) {
96
+ errors.push(`Invalid tier: ${tier}. Must be exec | decision | exploration`);
97
+ }
98
+ // Check schema_strictness
99
+ const schemaStrictness = manifest.schema_strictness;
100
+ if (schemaStrictness && !['high', 'medium', 'low'].includes(schemaStrictness)) {
101
+ errors.push(`Invalid schema_strictness: ${schemaStrictness}. Must be high | medium | low`);
102
+ }
103
+ // Check overflow config
104
+ const overflow = manifest.overflow ?? {};
105
+ if (overflow.enabled) {
106
+ if (overflow.require_suggested_mapping === undefined) {
107
+ warnings.push("overflow.require_suggested_mapping not set (recommended for recoverable insights)");
108
+ }
109
+ }
110
+ // Check enums config
111
+ const enums = manifest.enums ?? {};
112
+ const strategy = enums.strategy;
113
+ if (strategy && !['strict', 'extensible'].includes(strategy)) {
114
+ errors.push(`Invalid enums.strategy: ${strategy}. Must be strict | extensible`);
115
+ }
116
+ // Check compat config
117
+ const compat = manifest.compat;
118
+ if (!compat) {
119
+ warnings.push("module.yaml missing 'compat' section (recommended for migration)");
120
+ }
121
+ // Check excludes
122
+ const excludes = manifest.excludes ?? [];
123
+ if (excludes.length === 0) {
124
+ warnings.push("'excludes' list is empty (should list what module won't do)");
125
+ }
126
+ // Check prompt.md
127
+ const promptPath = path.join(modulePath, 'prompt.md');
128
+ if (!await fileExists(promptPath)) {
129
+ errors.push("Missing prompt.md (required for v2.2)");
130
+ }
131
+ else {
132
+ const prompt = await fs.readFile(promptPath, 'utf-8');
133
+ // Check for v2.2 envelope format instructions
134
+ if (!prompt.toLowerCase().includes('meta') && !prompt.toLowerCase().includes('envelope')) {
135
+ warnings.push("prompt.md should mention v2.2 envelope format with meta/data separation");
136
+ }
137
+ if (prompt.length < 100) {
138
+ warnings.push("prompt.md seems too short (< 100 chars)");
139
+ }
140
+ }
141
+ // Check schema.json
142
+ const schemaPath = path.join(modulePath, 'schema.json');
143
+ if (!await fileExists(schemaPath)) {
144
+ errors.push("Missing schema.json (required for v2.2)");
145
+ }
146
+ else {
147
+ try {
148
+ const schemaContent = await fs.readFile(schemaPath, 'utf-8');
149
+ const schema = JSON.parse(schemaContent);
150
+ // Check for meta schema (v2.2 required)
151
+ if (!('meta' in schema)) {
152
+ errors.push("schema.json missing 'meta' schema (required for v2.2)");
153
+ }
154
+ else {
155
+ const metaSchema = schema.meta;
156
+ const metaRequired = metaSchema.required ?? [];
157
+ if (!metaRequired.includes('confidence')) {
158
+ errors.push("meta schema must require 'confidence'");
159
+ }
160
+ if (!metaRequired.includes('risk')) {
161
+ errors.push("meta schema must require 'risk'");
162
+ }
163
+ if (!metaRequired.includes('explain')) {
164
+ errors.push("meta schema must require 'explain'");
165
+ }
166
+ // Check explain maxLength
167
+ const properties = metaSchema.properties ?? {};
168
+ const explainProps = properties.explain ?? {};
169
+ const maxLength = explainProps.maxLength ?? 999;
170
+ if (maxLength > 280) {
171
+ warnings.push("meta.explain should have maxLength <= 280");
172
+ }
173
+ }
174
+ // Check for input schema
175
+ if (!('input' in schema)) {
176
+ warnings.push("schema.json missing 'input' definition");
177
+ }
178
+ // Check for data schema (v2.2 uses 'data' instead of 'output')
179
+ if (!('data' in schema) && !('output' in schema)) {
180
+ errors.push("schema.json missing 'data' (or 'output') definition");
181
+ }
182
+ else if ('data' in schema) {
183
+ const dataSchema = schema.data;
184
+ const dataRequired = dataSchema.required ?? [];
185
+ if (!dataRequired.includes('rationale')) {
186
+ warnings.push("data schema should require 'rationale' for audit");
187
+ }
188
+ }
189
+ // Check for error schema
190
+ if (!('error' in schema)) {
191
+ warnings.push("schema.json missing 'error' definition");
192
+ }
193
+ // Check for $defs/extensions (v2.2 overflow)
194
+ if (overflow.enabled) {
195
+ const defs = schema.$defs ?? {};
196
+ if (!('extensions' in defs)) {
197
+ warnings.push("schema.json missing '$defs.extensions' (needed for overflow)");
198
+ }
199
+ }
200
+ }
201
+ catch (e) {
202
+ errors.push(`Invalid JSON in schema.json: ${e.message}`);
203
+ }
204
+ }
205
+ // Check tests directory
206
+ const testsPath = path.join(modulePath, 'tests');
207
+ if (!await fileExists(testsPath)) {
208
+ warnings.push("Missing tests directory (recommended)");
209
+ }
210
+ else {
211
+ // Check for v2.2 format in expected files
212
+ try {
213
+ const entries = await fs.readdir(testsPath);
214
+ for (const entry of entries) {
215
+ if (entry.endsWith('.expected.json')) {
216
+ try {
217
+ const expectedContent = await fs.readFile(path.join(testsPath, entry), 'utf-8');
218
+ const expected = JSON.parse(expectedContent);
219
+ // Check if example uses v2.2 format
220
+ const example = expected.$example ?? {};
221
+ if (example.ok === true && !('meta' in example)) {
222
+ warnings.push(`${entry}: $example missing 'meta' (v2.2 format)`);
223
+ }
224
+ }
225
+ catch {
226
+ // Skip invalid JSON
227
+ }
228
+ }
229
+ }
230
+ }
231
+ catch {
232
+ // Skip if can't read tests directory
233
+ }
234
+ }
235
+ return { valid: errors.length === 0, errors, warnings };
236
+ }
237
+ // =============================================================================
238
+ // v2.x (non-strict) Validation
239
+ // =============================================================================
240
+ async function validateV2Format(modulePath) {
241
+ const errors = [];
242
+ const warnings = [];
243
+ // Check module.yaml
244
+ const moduleYamlPath = path.join(modulePath, 'module.yaml');
245
+ let manifest;
246
+ try {
247
+ const content = await fs.readFile(moduleYamlPath, 'utf-8');
248
+ const loaded = yaml.load(content);
249
+ manifest = loaded && typeof loaded === 'object' ? loaded : {};
250
+ }
251
+ catch (e) {
252
+ errors.push(`Invalid YAML in module.yaml: ${e.message}`);
253
+ return { valid: false, errors, warnings };
254
+ }
255
+ // Check required fields
256
+ const requiredFields = ['name', 'version', 'responsibility'];
257
+ for (const field of requiredFields) {
258
+ if (!(field in manifest)) {
259
+ errors.push(`module.yaml missing required field: ${field}`);
260
+ }
261
+ }
262
+ // Check excludes
263
+ const excludes = manifest.excludes ?? [];
264
+ if (excludes.length === 0) {
265
+ warnings.push("'excludes' list is empty");
266
+ }
267
+ // Check prompt.md or MODULE.md
268
+ const promptPath = path.join(modulePath, 'prompt.md');
269
+ const moduleMdPath = path.join(modulePath, 'MODULE.md');
270
+ if (!await fileExists(promptPath) && !await fileExists(moduleMdPath)) {
271
+ errors.push("Missing prompt.md or MODULE.md");
272
+ }
273
+ else if (await fileExists(promptPath)) {
274
+ const prompt = await fs.readFile(promptPath, 'utf-8');
275
+ if (prompt.length < 50) {
276
+ warnings.push("prompt.md seems too short (< 50 chars)");
277
+ }
278
+ }
279
+ // Check schema.json
280
+ const schemaPath = path.join(modulePath, 'schema.json');
281
+ if (!await fileExists(schemaPath)) {
282
+ warnings.push("Missing schema.json (recommended)");
283
+ }
284
+ else {
285
+ try {
286
+ const schemaContent = await fs.readFile(schemaPath, 'utf-8');
287
+ const schema = JSON.parse(schemaContent);
288
+ if (!('input' in schema)) {
289
+ warnings.push("schema.json missing 'input' definition");
290
+ }
291
+ // Accept both 'data' and 'output'
292
+ if (!('data' in schema) && !('output' in schema)) {
293
+ warnings.push("schema.json missing 'data' or 'output' definition");
294
+ }
295
+ }
296
+ catch (e) {
297
+ errors.push(`Invalid JSON in schema.json: ${e.message}`);
298
+ }
299
+ }
300
+ // Check for v2.2 features and suggest upgrade
301
+ if (!manifest.tier) {
302
+ warnings.push("Consider adding 'tier' for v2.2 (use 'cog validate --v22' for full check)");
303
+ }
304
+ return { valid: errors.length === 0, errors, warnings };
305
+ }
306
+ // =============================================================================
307
+ // v1 Format Validation (MODULE.md + schema.json)
308
+ // =============================================================================
309
+ async function validateV1Format(modulePath) {
310
+ const errors = [];
311
+ const warnings = [];
312
+ // Check MODULE.md
313
+ const moduleMdPath = path.join(modulePath, 'MODULE.md');
314
+ try {
315
+ const content = await fs.readFile(moduleMdPath, 'utf-8');
316
+ if (content.length === 0) {
317
+ errors.push("MODULE.md is empty");
318
+ return { valid: false, errors, warnings };
319
+ }
320
+ // Parse frontmatter
321
+ if (!content.startsWith('---')) {
322
+ errors.push("MODULE.md must start with YAML frontmatter (---)");
323
+ }
324
+ else {
325
+ const parts = content.split('---');
326
+ if (parts.length < 3) {
327
+ errors.push("MODULE.md frontmatter not properly closed");
328
+ }
329
+ else {
330
+ try {
331
+ const frontmatter = yaml.load(parts[1]);
332
+ const body = parts.slice(2).join('---').trim();
333
+ // Check required fields
334
+ const requiredFields = ['name', 'version', 'responsibility', 'excludes'];
335
+ for (const field of requiredFields) {
336
+ if (!(field in frontmatter)) {
337
+ errors.push(`MODULE.md missing required field: ${field}`);
338
+ }
339
+ }
340
+ if ('excludes' in frontmatter) {
341
+ if (!Array.isArray(frontmatter.excludes)) {
342
+ errors.push("'excludes' must be a list");
343
+ }
344
+ else if (frontmatter.excludes.length === 0) {
345
+ warnings.push("'excludes' list is empty");
346
+ }
347
+ }
348
+ // Check body has content
349
+ if (body.length < 50) {
350
+ warnings.push("MODULE.md body seems too short (< 50 chars)");
351
+ }
352
+ }
353
+ catch (e) {
354
+ errors.push(`Invalid YAML in MODULE.md: ${e.message}`);
355
+ }
356
+ }
357
+ }
358
+ }
359
+ catch (e) {
360
+ errors.push(`Cannot read MODULE.md: ${e.message}`);
361
+ return { valid: false, errors, warnings };
362
+ }
363
+ // Check schema.json
364
+ const schemaPath = path.join(modulePath, 'schema.json');
365
+ if (!await fileExists(schemaPath)) {
366
+ warnings.push("Missing schema.json (recommended for validation)");
367
+ }
368
+ else {
369
+ try {
370
+ const schemaContent = await fs.readFile(schemaPath, 'utf-8');
371
+ const schema = JSON.parse(schemaContent);
372
+ if (!('input' in schema)) {
373
+ warnings.push("schema.json missing 'input' definition");
374
+ }
375
+ if (!('output' in schema)) {
376
+ warnings.push("schema.json missing 'output' definition");
377
+ }
378
+ // Check output has required fields
379
+ const output = schema.output ?? {};
380
+ const required = output.required ?? [];
381
+ if (!required.includes('confidence')) {
382
+ warnings.push("output schema should require 'confidence'");
383
+ }
384
+ if (!required.includes('rationale')) {
385
+ warnings.push("output schema should require 'rationale'");
386
+ }
387
+ }
388
+ catch (e) {
389
+ errors.push(`Invalid JSON in schema.json: ${e.message}`);
390
+ }
391
+ }
392
+ // Check examples
393
+ const examplesPath = path.join(modulePath, 'examples');
394
+ if (!await fileExists(examplesPath)) {
395
+ warnings.push("Missing examples directory (recommended)");
396
+ }
397
+ else {
398
+ await validateExamples(examplesPath, path.join(modulePath, 'schema.json'), errors, warnings);
399
+ }
400
+ // Suggest v2.2 upgrade
401
+ warnings.push("Consider upgrading to v2.2 format for better Control/Data separation");
402
+ return { valid: errors.length === 0, errors, warnings };
403
+ }
404
+ // =============================================================================
405
+ // v0 Format Validation (6-file format)
406
+ // =============================================================================
407
+ async function validateV0Format(modulePath) {
408
+ const errors = [];
409
+ const warnings = [];
410
+ // Check required files
411
+ const requiredFiles = [
412
+ 'module.md',
413
+ 'input.schema.json',
414
+ 'output.schema.json',
415
+ 'constraints.yaml',
416
+ 'prompt.txt',
417
+ ];
418
+ for (const filename of requiredFiles) {
419
+ const filepath = path.join(modulePath, filename);
420
+ if (!await fileExists(filepath)) {
421
+ errors.push(`Missing required file: ${filename}`);
422
+ }
423
+ else {
424
+ const stat = await fs.stat(filepath);
425
+ if (stat.size === 0) {
426
+ errors.push(`File is empty: ${filename}`);
427
+ }
428
+ }
429
+ }
430
+ // Check examples directory
431
+ const examplesPath = path.join(modulePath, 'examples');
432
+ if (!await fileExists(examplesPath)) {
433
+ errors.push("Missing examples directory");
434
+ }
435
+ else {
436
+ if (!await fileExists(path.join(examplesPath, 'input.json'))) {
437
+ errors.push("Missing examples/input.json");
438
+ }
439
+ if (!await fileExists(path.join(examplesPath, 'output.json'))) {
440
+ errors.push("Missing examples/output.json");
441
+ }
442
+ }
443
+ if (errors.length > 0) {
444
+ return { valid: false, errors, warnings };
445
+ }
446
+ // Validate module.md frontmatter
447
+ try {
448
+ const content = await fs.readFile(path.join(modulePath, 'module.md'), 'utf-8');
449
+ if (!content.startsWith('---')) {
450
+ errors.push("module.md must start with YAML frontmatter (---)");
451
+ }
452
+ else {
453
+ const parts = content.split('---');
454
+ if (parts.length < 3) {
455
+ errors.push("module.md frontmatter not properly closed");
456
+ }
457
+ else {
458
+ try {
459
+ const frontmatter = yaml.load(parts[1]);
460
+ const requiredFields = ['name', 'version', 'responsibility', 'excludes'];
461
+ for (const field of requiredFields) {
462
+ if (!(field in frontmatter)) {
463
+ errors.push(`module.md missing required field: ${field}`);
464
+ }
465
+ }
466
+ if ('excludes' in frontmatter) {
467
+ if (!Array.isArray(frontmatter.excludes)) {
468
+ errors.push("'excludes' must be a list");
469
+ }
470
+ else if (frontmatter.excludes.length === 0) {
471
+ warnings.push("'excludes' list is empty");
472
+ }
473
+ }
474
+ }
475
+ catch (e) {
476
+ errors.push(`Invalid YAML in module.md: ${e.message}`);
477
+ }
478
+ }
479
+ }
480
+ }
481
+ catch (e) {
482
+ errors.push(`Cannot read module.md: ${e.message}`);
483
+ }
484
+ // Suggest v2.2 upgrade
485
+ warnings.push("v0 format is deprecated. Consider upgrading to v2.2");
486
+ return { valid: errors.length === 0, errors, warnings };
487
+ }
488
+ // =============================================================================
489
+ // Helper Functions
490
+ // =============================================================================
491
+ async function fileExists(filepath) {
492
+ try {
493
+ await fs.access(filepath);
494
+ return true;
495
+ }
496
+ catch {
497
+ return false;
498
+ }
499
+ }
500
+ async function validateExamples(examplesPath, schemaPath, errors, warnings) {
501
+ if (!await fileExists(path.join(examplesPath, 'input.json'))) {
502
+ warnings.push("Missing examples/input.json");
503
+ }
504
+ if (!await fileExists(path.join(examplesPath, 'output.json'))) {
505
+ warnings.push("Missing examples/output.json");
506
+ }
507
+ // Validate examples against schema if both exist
508
+ if (await fileExists(schemaPath)) {
509
+ try {
510
+ const schemaContent = await fs.readFile(schemaPath, 'utf-8');
511
+ const schema = JSON.parse(schemaContent);
512
+ // Validate input example
513
+ const inputExamplePath = path.join(examplesPath, 'input.json');
514
+ if (await fileExists(inputExamplePath) && 'input' in schema) {
515
+ try {
516
+ const inputContent = await fs.readFile(inputExamplePath, 'utf-8');
517
+ const inputExample = JSON.parse(inputContent);
518
+ const validate = ajv.compile(schema.input);
519
+ const valid = validate(inputExample);
520
+ if (!valid && validate.errors) {
521
+ errors.push(`Example input fails schema: ${validate.errors[0]?.message}`);
522
+ }
523
+ }
524
+ catch (e) {
525
+ errors.push(`Invalid JSON in examples/input.json: ${e.message}`);
526
+ }
527
+ }
528
+ // Validate output example
529
+ const outputExamplePath = path.join(examplesPath, 'output.json');
530
+ const outputSchema = (schema.output || schema.data);
531
+ if (await fileExists(outputExamplePath) && outputSchema) {
532
+ try {
533
+ const outputContent = await fs.readFile(outputExamplePath, 'utf-8');
534
+ const outputExample = JSON.parse(outputContent);
535
+ const validate = ajv.compile(outputSchema);
536
+ const valid = validate(outputExample);
537
+ if (!valid && validate.errors) {
538
+ errors.push(`Example output fails schema: ${validate.errors[0]?.message}`);
539
+ }
540
+ // Check confidence
541
+ if ('confidence' in outputExample) {
542
+ const conf = outputExample.confidence;
543
+ if (conf < 0 || conf > 1) {
544
+ errors.push(`Confidence must be 0-1, got: ${conf}`);
545
+ }
546
+ }
547
+ }
548
+ catch (e) {
549
+ errors.push(`Invalid JSON in examples/output.json: ${e.message}`);
550
+ }
551
+ }
552
+ }
553
+ catch {
554
+ // Skip if schema can't be read
555
+ }
556
+ }
557
+ }
558
+ // =============================================================================
559
+ // Envelope Validation
560
+ // =============================================================================
561
+ /**
562
+ * Validate a response against v2.2 envelope format.
563
+ *
564
+ * @param response The response dict to validate
565
+ * @returns Validation result
566
+ */
567
+ export function validateV22Envelope(response) {
568
+ const errors = [];
569
+ // Check ok field
570
+ if (!('ok' in response)) {
571
+ errors.push("Missing 'ok' field");
572
+ return { valid: false, errors };
573
+ }
574
+ // Check meta
575
+ if (!('meta' in response)) {
576
+ errors.push("Missing 'meta' field (required for v2.2)");
577
+ }
578
+ else if (typeof response.meta !== 'object' || response.meta === null || Array.isArray(response.meta)) {
579
+ errors.push("meta must be an object");
580
+ }
581
+ else {
582
+ const meta = response.meta;
583
+ if (!('confidence' in meta)) {
584
+ errors.push("meta missing 'confidence'");
585
+ }
586
+ else if (typeof meta.confidence !== 'number') {
587
+ errors.push("meta.confidence must be a number");
588
+ }
589
+ else if (meta.confidence < 0 || meta.confidence > 1) {
590
+ errors.push("meta.confidence must be between 0 and 1");
591
+ }
592
+ if (!('risk' in meta)) {
593
+ errors.push("meta missing 'risk'");
594
+ }
595
+ else {
596
+ const validRisks = ['none', 'low', 'medium', 'high'];
597
+ if (!validRisks.includes(meta.risk)) {
598
+ errors.push(`meta.risk must be none|low|medium|high, got: ${meta.risk}`);
599
+ }
600
+ }
601
+ if (!('explain' in meta)) {
602
+ errors.push("meta missing 'explain'");
603
+ }
604
+ else if (typeof meta.explain !== 'string') {
605
+ errors.push("meta.explain must be a string");
606
+ }
607
+ else {
608
+ const explain = meta.explain;
609
+ if (explain.length > 280) {
610
+ errors.push(`meta.explain exceeds 280 chars (${explain.length} chars)`);
611
+ }
612
+ }
613
+ }
614
+ // Check data or error
615
+ if (response.ok) {
616
+ if (!('data' in response)) {
617
+ errors.push("Success response missing 'data' field");
618
+ }
619
+ // Note: data.rationale is recommended but not required by v2.2 envelope spec
620
+ // The data schema validation will enforce it if the module specifies it as required
621
+ }
622
+ else {
623
+ if (!('error' in response)) {
624
+ errors.push("Error response missing 'error' field");
625
+ }
626
+ else {
627
+ const error = response.error;
628
+ if (!('code' in error)) {
629
+ errors.push("error missing 'code'");
630
+ }
631
+ if (!('message' in error)) {
632
+ errors.push("error missing 'message'");
633
+ }
634
+ }
635
+ }
636
+ return { valid: errors.length === 0, errors };
637
+ }
@@ -1,5 +1,7 @@
1
1
  /**
2
2
  * Anthropic Provider - Claude API
3
+ *
4
+ * Supports both streaming and non-streaming invocation.
3
5
  */
4
6
  import { BaseProvider } from './base.js';
5
7
  import type { InvokeParams, InvokeResult } from '../types.js';
@@ -10,5 +12,18 @@ export declare class AnthropicProvider extends BaseProvider {
10
12
  private baseUrl;
11
13
  constructor(apiKey?: string, model?: string);
12
14
  isConfigured(): boolean;
15
+ /**
16
+ * Anthropic supports streaming.
17
+ */
18
+ supportsStreaming(): boolean;
19
+ /**
20
+ * Build request body for Anthropic API
21
+ */
22
+ private buildRequestBody;
13
23
  invoke(params: InvokeParams): Promise<InvokeResult>;
24
+ /**
25
+ * Stream-based invoke using Anthropic's streaming API.
26
+ * Yields content chunks as they arrive from the API.
27
+ */
28
+ invokeStream(params: InvokeParams): AsyncGenerator<string, InvokeResult, unknown>;
14
29
  }