cognitive-modules-cli 2.2.7 → 2.2.8

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.
@@ -134,10 +134,17 @@ export interface SearchResult {
134
134
  score: number;
135
135
  keywords: string[];
136
136
  }
137
+ export declare const DEFAULT_REGISTRY_URL = "https://github.com/Cognary/cognitive/releases/latest/download/cognitive-registry.v2.json";
138
+ export interface RegistryClientOptions {
139
+ timeoutMs?: number;
140
+ maxBytes?: number;
141
+ }
137
142
  export declare class RegistryClient {
138
143
  private registryUrl;
144
+ private timeoutMs;
145
+ private maxBytes;
139
146
  private cache;
140
- constructor(registryUrl?: string);
147
+ constructor(registryUrl?: string, options?: RegistryClientOptions);
141
148
  private parseRegistryResponse;
142
149
  /**
143
150
  * Generate a unique cache filename based on registry URL
@@ -16,26 +16,60 @@ import { createHash } from 'node:crypto';
16
16
  // =============================================================================
17
17
  // Constants
18
18
  // =============================================================================
19
- const DEFAULT_REGISTRY_URL = 'https://raw.githubusercontent.com/Cognary/cognitive/main/cognitive-registry.v2.json';
19
+ // "Latest" registry strategy:
20
+ // Prefer GitHub Releases "latest" download, so clients get a coherent set of
21
+ // (index + tarballs) that match an actual published release.
22
+ //
23
+ // This URL is stable across releases:
24
+ // https://github.com/<org>/<repo>/releases/latest/download/cognitive-registry.v2.json
25
+ export const DEFAULT_REGISTRY_URL = 'https://github.com/Cognary/cognitive/releases/latest/download/cognitive-registry.v2.json';
26
+ const FALLBACK_REGISTRY_URL = 'https://raw.githubusercontent.com/Cognary/cognitive/main/cognitive-registry.v2.json';
20
27
  const CACHE_DIR = join(homedir(), '.cognitive', 'cache');
21
28
  const CACHE_TTL_MS = 5 * 60 * 1000; // 5 minutes
22
- const REGISTRY_FETCH_TIMEOUT_MS = 10_000; // 10s
23
- const MAX_REGISTRY_BYTES = 1024 * 1024; // 1MB
29
+ const DEFAULT_REGISTRY_FETCH_TIMEOUT_MS = 10_000; // 10s
30
+ const DEFAULT_MAX_REGISTRY_BYTES = 2 * 1024 * 1024; // 2MB
31
+ const HARD_MAX_REGISTRY_BYTES = 20 * 1024 * 1024; // 20MB (absolute safety cap)
32
+ function parsePositiveIntEnv(name) {
33
+ const raw = process.env[name];
34
+ if (!raw)
35
+ return undefined;
36
+ const n = Number(raw);
37
+ if (!Number.isFinite(n) || n <= 0)
38
+ return undefined;
39
+ return Math.floor(n);
40
+ }
41
+ function clamp(n, min, max) {
42
+ return Math.max(min, Math.min(max, n));
43
+ }
24
44
  // =============================================================================
25
45
  // Registry Client
26
46
  // =============================================================================
27
47
  export class RegistryClient {
28
48
  registryUrl;
49
+ timeoutMs;
50
+ maxBytes;
29
51
  cache = { data: null, timestamp: 0 };
30
- constructor(registryUrl = DEFAULT_REGISTRY_URL) {
31
- this.registryUrl = registryUrl;
52
+ constructor(registryUrl, options = {}) {
53
+ const fromEnv = process.env.COGNITIVE_REGISTRY_URL;
54
+ const selected = (typeof registryUrl === 'string' && registryUrl.trim() ? registryUrl.trim() : undefined) ??
55
+ (typeof fromEnv === 'string' && fromEnv.trim() ? fromEnv.trim() : undefined) ??
56
+ DEFAULT_REGISTRY_URL;
57
+ this.registryUrl = selected;
58
+ const envTimeout = parsePositiveIntEnv('COGNITIVE_REGISTRY_TIMEOUT_MS');
59
+ const envMaxBytes = parsePositiveIntEnv('COGNITIVE_REGISTRY_MAX_BYTES');
60
+ const timeout = options.timeoutMs ?? envTimeout ?? DEFAULT_REGISTRY_FETCH_TIMEOUT_MS;
61
+ // keep timeouts bounded for predictable UX
62
+ this.timeoutMs = clamp(timeout, 1000, 120_000);
63
+ const maxBytes = options.maxBytes ?? envMaxBytes ?? DEFAULT_MAX_REGISTRY_BYTES;
64
+ // enforce an absolute upper bound to avoid accidental OOM on hostile endpoints
65
+ this.maxBytes = clamp(maxBytes, 1024, HARD_MAX_REGISTRY_BYTES);
32
66
  }
33
67
  async parseRegistryResponse(response) {
34
68
  const contentLengthHeader = response.headers?.get('content-length');
35
69
  if (contentLengthHeader) {
36
70
  const contentLength = Number(contentLengthHeader);
37
- if (!Number.isNaN(contentLength) && contentLength > MAX_REGISTRY_BYTES) {
38
- throw new Error(`Registry payload too large: ${contentLength} bytes (max ${MAX_REGISTRY_BYTES})`);
71
+ if (!Number.isNaN(contentLength) && contentLength > this.maxBytes) {
72
+ throw new Error(`Registry payload too large: ${contentLength} bytes (max ${this.maxBytes})`);
39
73
  }
40
74
  }
41
75
  if (response.body && typeof response.body.getReader === 'function') {
@@ -50,8 +84,8 @@ export class RegistryClient {
50
84
  break;
51
85
  if (value) {
52
86
  totalBytes += value.byteLength;
53
- if (totalBytes > MAX_REGISTRY_BYTES) {
54
- throw new Error(`Registry payload too large: ${totalBytes} bytes (max ${MAX_REGISTRY_BYTES})`);
87
+ if (totalBytes > this.maxBytes) {
88
+ throw new Error(`Registry payload too large: ${totalBytes} bytes (max ${this.maxBytes})`);
55
89
  }
56
90
  buffer += decoder.decode(value, { stream: true });
57
91
  }
@@ -71,8 +105,8 @@ export class RegistryClient {
71
105
  if (typeof response.text === 'function') {
72
106
  const text = await response.text();
73
107
  const byteLen = Buffer.byteLength(text, 'utf-8');
74
- if (byteLen > MAX_REGISTRY_BYTES) {
75
- throw new Error(`Registry payload too large: ${byteLen} bytes (max ${MAX_REGISTRY_BYTES})`);
108
+ if (byteLen > this.maxBytes) {
109
+ throw new Error(`Registry payload too large: ${byteLen} bytes (max ${this.maxBytes})`);
76
110
  }
77
111
  try {
78
112
  return JSON.parse(text);
@@ -118,28 +152,48 @@ export class RegistryClient {
118
152
  // Ignore cache read errors
119
153
  }
120
154
  }
121
- // Fetch from network
122
- const controller = new AbortController();
123
- const timeout = setTimeout(() => controller.abort(), REGISTRY_FETCH_TIMEOUT_MS);
155
+ const fetchOnce = async (url) => {
156
+ const controller = new AbortController();
157
+ const timeout = setTimeout(() => controller.abort(), this.timeoutMs);
158
+ try {
159
+ const response = await fetch(url, {
160
+ headers: { 'User-Agent': 'cognitive-runtime/2.2' },
161
+ signal: controller.signal,
162
+ });
163
+ if (!response.ok) {
164
+ // Allow callers to inspect status for fallback logic.
165
+ const err = new Error(`Failed to fetch registry: ${response.status} ${response.statusText}`);
166
+ err.status = response.status;
167
+ throw err;
168
+ }
169
+ return await this.parseRegistryResponse(response);
170
+ }
171
+ catch (error) {
172
+ if (error instanceof Error && error.name === 'AbortError') {
173
+ throw new Error(`Registry fetch timed out after ${this.timeoutMs}ms`);
174
+ }
175
+ throw error;
176
+ }
177
+ finally {
178
+ clearTimeout(timeout);
179
+ }
180
+ };
124
181
  let data;
125
182
  try {
126
- const response = await fetch(this.registryUrl, {
127
- headers: { 'User-Agent': 'cognitive-runtime/2.2' },
128
- signal: controller.signal,
129
- });
130
- if (!response.ok) {
131
- throw new Error(`Failed to fetch registry: ${response.status} ${response.statusText}`);
132
- }
133
- data = await this.parseRegistryResponse(response);
183
+ data = await fetchOnce(this.registryUrl);
134
184
  }
135
- catch (error) {
136
- if (error instanceof Error && error.name === 'AbortError') {
137
- throw new Error(`Registry fetch timed out after ${REGISTRY_FETCH_TIMEOUT_MS}ms`);
185
+ catch (e) {
186
+ // Harden the "latest" strategy: right after publishing a GitHub Release,
187
+ // the index asset may not be uploaded yet (short 404 window). In that case,
188
+ // fall back to the repo-tracked index to keep `cog search/add` usable.
189
+ const status = e?.status;
190
+ const isDefaultUrl = this.registryUrl === DEFAULT_REGISTRY_URL;
191
+ if (isDefaultUrl && status === 404) {
192
+ data = await fetchOnce(FALLBACK_REGISTRY_URL);
193
+ }
194
+ else {
195
+ throw e;
138
196
  }
139
- throw error;
140
- }
141
- finally {
142
- clearTimeout(timeout);
143
197
  }
144
198
  // Update cache
145
199
  this.cache = { data, timestamp: now };
package/dist/types.d.ts CHANGED
@@ -352,6 +352,16 @@ export interface CommandContext {
352
352
  cwd: string;
353
353
  provider: Provider;
354
354
  verbose?: boolean;
355
+ policy?: ExecutionPolicy;
356
+ /**
357
+ * Registry index override for commands that talk to the registry.
358
+ * If omitted, the RegistryClient default strategy is used.
359
+ */
360
+ registryUrl?: string;
361
+ /** Override registry fetch timeout (ms). */
362
+ registryTimeoutMs?: number;
363
+ /** Override registry max index bytes. */
364
+ registryMaxBytes?: number;
355
365
  }
356
366
  /**
357
367
  * CLI command result (legacy format).
@@ -373,6 +383,27 @@ export interface CommandResultV2 {
373
383
  data?: unknown;
374
384
  error?: EnvelopeError;
375
385
  }
386
+ /**
387
+ * Execution profile:
388
+ * - core: minimal constraints (5-minute path)
389
+ * - default: safe defaults for day-to-day usage
390
+ * - strict: higher assurance (more enforcement)
391
+ * - certified: strongest gates (intended for publishable / regulated flows)
392
+ */
393
+ export type ExecutionProfile = 'core' | 'default' | 'strict' | 'certified';
394
+ /** Validation mode for input/output schemas and runtime enforcement. */
395
+ export type ValidateMode = 'auto' | 'on' | 'off';
396
+ export interface ExecutionPolicy {
397
+ profile: ExecutionProfile;
398
+ validate: ValidateMode;
399
+ audit: boolean;
400
+ enableRepair: boolean;
401
+ /**
402
+ * If true, refuse to run non-v2.2 modules (legacy/v0/v1 or missing formatVersion).
403
+ * This is a gate for certification-style flows.
404
+ */
405
+ requireV22: boolean;
406
+ }
376
407
  export interface ModuleInput {
377
408
  code?: string;
378
409
  query?: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cognitive-modules-cli",
3
- "version": "2.2.7",
3
+ "version": "2.2.8",
4
4
  "description": "Cognitive Modules - Structured AI Task Execution with version management",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",