cognitive-modules-cli 2.2.5 → 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.
- package/CHANGELOG.md +7 -1
- package/README.md +25 -3
- package/dist/audit.d.ts +13 -0
- package/dist/audit.js +25 -0
- package/dist/cli.js +188 -3
- package/dist/commands/add.js +232 -7
- package/dist/commands/compose.d.ts +2 -0
- package/dist/commands/compose.js +60 -1
- package/dist/commands/core.d.ts +31 -0
- package/dist/commands/core.js +338 -0
- package/dist/commands/index.d.ts +1 -0
- package/dist/commands/index.js +1 -0
- package/dist/commands/pipe.js +45 -2
- package/dist/commands/run.d.ts +1 -0
- package/dist/commands/run.js +136 -31
- package/dist/commands/search.js +13 -3
- package/dist/commands/update.js +4 -1
- package/dist/errors/index.d.ts +7 -0
- package/dist/errors/index.js +48 -40
- package/dist/modules/composition.d.ts +15 -2
- package/dist/modules/composition.js +16 -6
- package/dist/modules/loader.d.ts +10 -0
- package/dist/modules/loader.js +168 -0
- package/dist/modules/runner.d.ts +10 -6
- package/dist/modules/runner.js +130 -16
- package/dist/profile.d.ts +8 -0
- package/dist/profile.js +59 -0
- package/dist/provenance.d.ts +50 -0
- package/dist/provenance.js +137 -0
- package/dist/registry/assets.d.ts +48 -0
- package/dist/registry/assets.js +723 -0
- package/dist/registry/client.d.ts +20 -5
- package/dist/registry/client.js +87 -30
- package/dist/registry/tar.d.ts +8 -0
- package/dist/registry/tar.js +353 -0
- package/dist/server/http.js +167 -42
- package/dist/server/index.d.ts +2 -0
- package/dist/server/index.js +1 -0
- package/dist/server/sse.d.ts +13 -0
- package/dist/server/sse.js +22 -0
- package/dist/types.d.ts +31 -0
- package/package.json +1 -1
package/dist/commands/add.js
CHANGED
|
@@ -9,13 +9,16 @@
|
|
|
9
9
|
* cog add ziel-io/cognitive-modules -m code-simplifier
|
|
10
10
|
* cog add https://github.com/org/repo --module name --tag v1.0.0
|
|
11
11
|
*/
|
|
12
|
-
import { createWriteStream, existsSync, mkdirSync, rmSync, readdirSync, statSync, copyFileSync } from 'node:fs';
|
|
12
|
+
import { createWriteStream, existsSync, mkdirSync, rmSync, readdirSync, statSync, copyFileSync, lstatSync } from 'node:fs';
|
|
13
13
|
import { writeFile, readFile, mkdir } from 'node:fs/promises';
|
|
14
|
-
import { pipeline } from 'node:stream/promises';
|
|
14
|
+
import { pipeline, finished } from 'node:stream/promises';
|
|
15
15
|
import { Readable } from 'node:stream';
|
|
16
16
|
import { join, basename, dirname, resolve, sep, isAbsolute } from 'node:path';
|
|
17
17
|
import { homedir, tmpdir } from 'node:os';
|
|
18
|
+
import { createHash } from 'node:crypto';
|
|
18
19
|
import { RegistryClient } from '../registry/client.js';
|
|
20
|
+
import { extractTarGzFile } from '../registry/tar.js';
|
|
21
|
+
import { PROVENANCE_SPEC, computeModuleIntegrity, writeModuleProvenance } from '../provenance.js';
|
|
19
22
|
// Module storage paths
|
|
20
23
|
const USER_MODULES_DIR = join(homedir(), '.cognitive', 'modules');
|
|
21
24
|
const INSTALLED_MANIFEST = join(homedir(), '.cognitive', 'installed.json');
|
|
@@ -153,7 +156,11 @@ function copyDir(src, dest) {
|
|
|
153
156
|
for (const entry of readdirSync(src)) {
|
|
154
157
|
const srcPath = join(src, entry);
|
|
155
158
|
const destPath = join(dest, entry);
|
|
156
|
-
|
|
159
|
+
const st = lstatSync(srcPath);
|
|
160
|
+
if (st.isSymbolicLink()) {
|
|
161
|
+
throw new Error(`Refusing to install module containing symlink: ${srcPath}`);
|
|
162
|
+
}
|
|
163
|
+
if (st.isDirectory()) {
|
|
157
164
|
copyDir(srcPath, destPath);
|
|
158
165
|
}
|
|
159
166
|
else {
|
|
@@ -161,6 +168,74 @@ function copyDir(src, dest) {
|
|
|
161
168
|
}
|
|
162
169
|
}
|
|
163
170
|
}
|
|
171
|
+
async function downloadTarballWithSha256(url, outPath, maxBytes) {
|
|
172
|
+
const controller = new AbortController();
|
|
173
|
+
const timeout = setTimeout(() => controller.abort(), 10_000);
|
|
174
|
+
const hash = createHash('sha256');
|
|
175
|
+
let received = 0;
|
|
176
|
+
const fileStream = createWriteStream(outPath);
|
|
177
|
+
try {
|
|
178
|
+
const response = await fetch(url, {
|
|
179
|
+
headers: { 'User-Agent': 'cognitive-runtime/2.2' },
|
|
180
|
+
signal: controller.signal,
|
|
181
|
+
});
|
|
182
|
+
if (!response.ok) {
|
|
183
|
+
throw new Error(`Failed to download tarball: ${response.status} ${response.statusText}`);
|
|
184
|
+
}
|
|
185
|
+
const contentLengthHeader = response.headers?.get?.('content-length');
|
|
186
|
+
if (contentLengthHeader) {
|
|
187
|
+
const contentLength = Number(contentLengthHeader);
|
|
188
|
+
if (!Number.isNaN(contentLength) && contentLength > maxBytes) {
|
|
189
|
+
throw new Error(`Tarball too large: ${contentLength} bytes (max ${maxBytes})`);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
if (!response.body) {
|
|
193
|
+
throw new Error('Tarball response has no body');
|
|
194
|
+
}
|
|
195
|
+
const reader = response.body.getReader?.();
|
|
196
|
+
if (!reader) {
|
|
197
|
+
// Fallback: stream pipeline without incremental hash.
|
|
198
|
+
await pipeline(Readable.fromWeb(response.body), fileStream);
|
|
199
|
+
const content = await readFile(outPath);
|
|
200
|
+
return createHash('sha256').update(content).digest('hex');
|
|
201
|
+
}
|
|
202
|
+
while (true) {
|
|
203
|
+
const { done, value } = await reader.read();
|
|
204
|
+
if (done)
|
|
205
|
+
break;
|
|
206
|
+
if (value) {
|
|
207
|
+
received += value.byteLength;
|
|
208
|
+
if (received > maxBytes) {
|
|
209
|
+
controller.abort();
|
|
210
|
+
throw new Error(`Tarball too large: ${received} bytes (max ${maxBytes})`);
|
|
211
|
+
}
|
|
212
|
+
const chunk = Buffer.from(value);
|
|
213
|
+
hash.update(chunk);
|
|
214
|
+
if (!fileStream.write(chunk)) {
|
|
215
|
+
await new Promise((resolve) => fileStream.once('drain', () => resolve()));
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
fileStream.end();
|
|
220
|
+
await finished(fileStream);
|
|
221
|
+
return hash.digest('hex');
|
|
222
|
+
}
|
|
223
|
+
catch (error) {
|
|
224
|
+
try {
|
|
225
|
+
fileStream.destroy();
|
|
226
|
+
}
|
|
227
|
+
catch {
|
|
228
|
+
// ignore
|
|
229
|
+
}
|
|
230
|
+
if (error instanceof Error && error.name === 'AbortError') {
|
|
231
|
+
throw new Error('Tarball download timed out after 10000ms');
|
|
232
|
+
}
|
|
233
|
+
throw error;
|
|
234
|
+
}
|
|
235
|
+
finally {
|
|
236
|
+
clearTimeout(timeout);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
164
239
|
/**
|
|
165
240
|
* Get module version from module.yaml or MODULE.md
|
|
166
241
|
*/
|
|
@@ -266,8 +341,13 @@ function parseModuleSpec(spec) {
|
|
|
266
341
|
export async function addFromRegistry(moduleSpec, ctx, options = {}) {
|
|
267
342
|
const { name: moduleName, version: requestedVersion } = parseModuleSpec(moduleSpec);
|
|
268
343
|
const { name: customName, registry: registryUrl } = options;
|
|
344
|
+
const policy = ctx.policy;
|
|
345
|
+
let tempDir;
|
|
269
346
|
try {
|
|
270
|
-
const client = new RegistryClient(registryUrl
|
|
347
|
+
const client = new RegistryClient(registryUrl, {
|
|
348
|
+
timeoutMs: ctx.registryTimeoutMs,
|
|
349
|
+
maxBytes: ctx.registryMaxBytes,
|
|
350
|
+
});
|
|
271
351
|
const moduleInfo = await client.getModule(moduleName);
|
|
272
352
|
if (!moduleInfo) {
|
|
273
353
|
return {
|
|
@@ -282,6 +362,14 @@ export async function addFromRegistry(moduleSpec, ctx, options = {}) {
|
|
|
282
362
|
// Get download info
|
|
283
363
|
const downloadInfo = await client.getDownloadUrl(moduleName);
|
|
284
364
|
if (downloadInfo.isGitHub && downloadInfo.githubInfo) {
|
|
365
|
+
if (policy?.profile === 'certified') {
|
|
366
|
+
return {
|
|
367
|
+
success: false,
|
|
368
|
+
error: `Certified profile requires registry tarball provenance.\n` +
|
|
369
|
+
`Registry entry for '${moduleName}' resolves to a GitHub source, which is not allowed in --profile certified.\n` +
|
|
370
|
+
`Use a tarball-based registry entry (distribution.tarball + checksum), or run with --profile strict/default.`,
|
|
371
|
+
};
|
|
372
|
+
}
|
|
285
373
|
const { org, repo, path, ref } = downloadInfo.githubInfo;
|
|
286
374
|
// Use addFromGitHub() directly (not add() to avoid recursion)
|
|
287
375
|
const result = await addFromGitHub(`${org}/${repo}`, ctx, {
|
|
@@ -325,10 +413,112 @@ export async function addFromRegistry(moduleSpec, ctx, options = {}) {
|
|
|
325
413
|
}
|
|
326
414
|
return result;
|
|
327
415
|
}
|
|
328
|
-
//
|
|
416
|
+
// Tarball sources: require checksum, verify, and extract safely.
|
|
417
|
+
if (!downloadInfo.url.startsWith('http')) {
|
|
418
|
+
return { success: false, error: `Unsupported registry download URL: ${downloadInfo.url}` };
|
|
419
|
+
}
|
|
420
|
+
if (!downloadInfo.checksum) {
|
|
421
|
+
return {
|
|
422
|
+
success: false,
|
|
423
|
+
error: `Registry tarball missing checksum (required for safe install): ${moduleName}`,
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
tempDir = join(tmpdir(), `cog-reg-${Date.now()}`);
|
|
427
|
+
mkdirSync(tempDir, { recursive: true });
|
|
428
|
+
const tarPath = join(tempDir, 'module.tar.gz');
|
|
429
|
+
const MAX_TARBALL_BYTES = 20 * 1024 * 1024; // 20MB
|
|
430
|
+
const actualSha256 = await downloadTarballWithSha256(downloadInfo.url, tarPath, MAX_TARBALL_BYTES);
|
|
431
|
+
const expected = downloadInfo.checksum;
|
|
432
|
+
const checksumMatch = expected.match(/^sha256:([a-f0-9]{64})$/);
|
|
433
|
+
if (!checksumMatch) {
|
|
434
|
+
throw new Error(`Unsupported checksum format (expected sha256:<64hex>): ${expected}`);
|
|
435
|
+
}
|
|
436
|
+
const expectedHash = checksumMatch[1];
|
|
437
|
+
if (actualSha256 !== expectedHash) {
|
|
438
|
+
throw new Error(`Checksum mismatch for ${moduleName}: expected ${expectedHash}, got ${actualSha256}`);
|
|
439
|
+
}
|
|
440
|
+
const extractedRoot = join(tempDir, 'pkg');
|
|
441
|
+
mkdirSync(extractedRoot, { recursive: true });
|
|
442
|
+
await extractTarGzFile(tarPath, extractedRoot, {
|
|
443
|
+
maxFiles: 5_000,
|
|
444
|
+
maxTotalBytes: 50 * 1024 * 1024,
|
|
445
|
+
maxSingleFileBytes: 20 * 1024 * 1024,
|
|
446
|
+
maxTarBytes: 100 * 1024 * 1024,
|
|
447
|
+
});
|
|
448
|
+
// Find module directory inside extractedRoot.
|
|
449
|
+
const rootNames = readdirSync(extractedRoot).filter((e) => e !== '__MACOSX' && e !== '.DS_Store');
|
|
450
|
+
const rootPaths = rootNames.map((e) => join(extractedRoot, e)).filter((p) => existsSync(p));
|
|
451
|
+
const rootDirs = rootPaths.filter((p) => statSync(p).isDirectory());
|
|
452
|
+
const rootFiles = rootPaths.filter((p) => !statSync(p).isDirectory());
|
|
453
|
+
if (rootDirs.length === 0) {
|
|
454
|
+
throw new Error('Tarball extraction produced no root directory');
|
|
455
|
+
}
|
|
456
|
+
if (rootDirs.length !== 1 || rootFiles.length > 0) {
|
|
457
|
+
throw new Error(`Tarball must contain exactly one module root directory and no other top-level entries. ` +
|
|
458
|
+
`dirs=${rootDirs.map((p) => basename(p)).join(',') || '(none)'} files=${rootFiles.map((p) => basename(p)).join(',') || '(none)'}`);
|
|
459
|
+
}
|
|
460
|
+
// Strict mode: require root dir itself to be a valid module.
|
|
461
|
+
const sourcePath = rootDirs[0];
|
|
462
|
+
if (!isValidModule(sourcePath)) {
|
|
463
|
+
throw new Error('Root directory in tarball is not a valid module');
|
|
464
|
+
}
|
|
465
|
+
const installName = (customName || moduleName);
|
|
466
|
+
const safeInstallName = assertSafeModuleName(installName);
|
|
467
|
+
const targetPath = resolveModuleTarget(safeInstallName);
|
|
468
|
+
if (existsSync(targetPath)) {
|
|
469
|
+
rmSync(targetPath, { recursive: true, force: true });
|
|
470
|
+
}
|
|
471
|
+
await mkdir(USER_MODULES_DIR, { recursive: true });
|
|
472
|
+
copyDir(sourcePath, targetPath);
|
|
473
|
+
const version = await getModuleVersion(sourcePath);
|
|
474
|
+
// Write provenance + integrity (enables certified profile gating).
|
|
475
|
+
try {
|
|
476
|
+
const integrity = await computeModuleIntegrity(targetPath);
|
|
477
|
+
await writeModuleProvenance(targetPath, {
|
|
478
|
+
spec: PROVENANCE_SPEC,
|
|
479
|
+
createdAt: new Date().toISOString(),
|
|
480
|
+
source: {
|
|
481
|
+
type: 'registry',
|
|
482
|
+
registryUrl: registryUrl ?? null,
|
|
483
|
+
moduleName,
|
|
484
|
+
requestedVersion: requestedVersion ?? null,
|
|
485
|
+
resolvedVersion: version ?? null,
|
|
486
|
+
tarballUrl: downloadInfo.url,
|
|
487
|
+
checksum: downloadInfo.checksum,
|
|
488
|
+
sha256: actualSha256,
|
|
489
|
+
quality: {
|
|
490
|
+
// moduleInfo is typed loosely (v1/v2); best-effort extract for v2.
|
|
491
|
+
verified: moduleInfo?.quality?.verified,
|
|
492
|
+
conformance_level: moduleInfo?.quality?.conformance_level,
|
|
493
|
+
spec_version: moduleInfo?.identity?.spec_version,
|
|
494
|
+
},
|
|
495
|
+
},
|
|
496
|
+
integrity,
|
|
497
|
+
});
|
|
498
|
+
}
|
|
499
|
+
catch (e) {
|
|
500
|
+
// If provenance fails, keep install but warn loudly (non-certified users can still run).
|
|
501
|
+
console.error(`Warning: failed to write provenance for ${safeInstallName}: ${e.message}`);
|
|
502
|
+
}
|
|
503
|
+
await recordInstall(safeInstallName, {
|
|
504
|
+
source: downloadInfo.url,
|
|
505
|
+
githubUrl: downloadInfo.url,
|
|
506
|
+
version,
|
|
507
|
+
installedAt: targetPath,
|
|
508
|
+
installedTime: new Date().toISOString(),
|
|
509
|
+
registryModule: moduleName,
|
|
510
|
+
registryUrl,
|
|
511
|
+
});
|
|
329
512
|
return {
|
|
330
|
-
success:
|
|
331
|
-
|
|
513
|
+
success: true,
|
|
514
|
+
data: {
|
|
515
|
+
message: `Added: ${safeInstallName}${version ? ` v${version}` : ''} (registry tarball)`,
|
|
516
|
+
name: safeInstallName,
|
|
517
|
+
version,
|
|
518
|
+
location: targetPath,
|
|
519
|
+
source: 'registry',
|
|
520
|
+
registryModule: moduleName,
|
|
521
|
+
},
|
|
332
522
|
};
|
|
333
523
|
}
|
|
334
524
|
catch (error) {
|
|
@@ -337,6 +527,16 @@ export async function addFromRegistry(moduleSpec, ctx, options = {}) {
|
|
|
337
527
|
error: error instanceof Error ? error.message : String(error),
|
|
338
528
|
};
|
|
339
529
|
}
|
|
530
|
+
finally {
|
|
531
|
+
if (tempDir) {
|
|
532
|
+
try {
|
|
533
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
534
|
+
}
|
|
535
|
+
catch {
|
|
536
|
+
// ignore
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
}
|
|
340
540
|
}
|
|
341
541
|
/**
|
|
342
542
|
* Add a module from GitHub
|
|
@@ -344,6 +544,14 @@ export async function addFromRegistry(moduleSpec, ctx, options = {}) {
|
|
|
344
544
|
export async function addFromGitHub(url, ctx, options = {}) {
|
|
345
545
|
const { org, repo, fullUrl } = parseGitHubUrl(url);
|
|
346
546
|
const { module: modulePath, name, branch = 'main', tag } = options;
|
|
547
|
+
const policy = ctx.policy;
|
|
548
|
+
if (policy?.profile === 'certified') {
|
|
549
|
+
return {
|
|
550
|
+
success: false,
|
|
551
|
+
error: `Certified profile requires registry tarball provenance; GitHub installs are not allowed.\n` +
|
|
552
|
+
`Use 'cog add <module>' against a tarball-based registry entry, or run with --profile strict/default.`,
|
|
553
|
+
};
|
|
554
|
+
}
|
|
347
555
|
// Determine ref (tag takes priority)
|
|
348
556
|
const ref = tag || branch;
|
|
349
557
|
const isTag = !!tag;
|
|
@@ -388,6 +596,19 @@ export async function addFromGitHub(url, ctx, options = {}) {
|
|
|
388
596
|
installedAt: targetPath,
|
|
389
597
|
installedTime: new Date().toISOString(),
|
|
390
598
|
});
|
|
599
|
+
// Best-effort provenance for non-certified installs (useful for audit/debug).
|
|
600
|
+
try {
|
|
601
|
+
const integrity = await computeModuleIntegrity(targetPath);
|
|
602
|
+
await writeModuleProvenance(targetPath, {
|
|
603
|
+
spec: PROVENANCE_SPEC,
|
|
604
|
+
createdAt: new Date().toISOString(),
|
|
605
|
+
source: { type: 'github', repoUrl: fullUrl, ref, modulePath: modulePath ?? null },
|
|
606
|
+
integrity,
|
|
607
|
+
});
|
|
608
|
+
}
|
|
609
|
+
catch {
|
|
610
|
+
// ignore
|
|
611
|
+
}
|
|
391
612
|
// Cleanup temp directory
|
|
392
613
|
const tempDir = dirname(repoRoot);
|
|
393
614
|
if (tempDir && tempDir !== '/' && tempDir !== '.' && tempDir !== USER_MODULES_DIR) {
|
|
@@ -427,6 +648,10 @@ export async function addFromGitHub(url, ctx, options = {}) {
|
|
|
427
648
|
* Add a module from GitHub or Registry (auto-detect source)
|
|
428
649
|
*/
|
|
429
650
|
export async function add(source, ctx, options = {}) {
|
|
651
|
+
// Allow a global registry override via ctx.registryUrl unless explicitly set.
|
|
652
|
+
if (!options.registry && ctx.registryUrl) {
|
|
653
|
+
options = { ...options, registry: ctx.registryUrl };
|
|
654
|
+
}
|
|
430
655
|
// Determine source type
|
|
431
656
|
if (isGitHubSource(source)) {
|
|
432
657
|
return addFromGitHub(source, ctx, options);
|
package/dist/commands/compose.js
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import { findModule, getDefaultSearchPaths, executeComposition } from '../modules/index.js';
|
|
11
11
|
import { ErrorCodes, attachContext, makeErrorEnvelope } from '../errors/index.js';
|
|
12
|
+
import { writeAuditRecord } from '../audit.js';
|
|
12
13
|
function looksLikeCode(str) {
|
|
13
14
|
const codeIndicators = [
|
|
14
15
|
/^(def|function|class|const|let|var|import|export|public|private)\s/,
|
|
@@ -34,7 +35,19 @@ export async function compose(moduleName, ctx, options = {}) {
|
|
|
34
35
|
data: errorEnvelope,
|
|
35
36
|
};
|
|
36
37
|
}
|
|
38
|
+
if (ctx.policy?.requireV22) {
|
|
39
|
+
if (module.formatVersion !== 'v2.2') {
|
|
40
|
+
const errorEnvelope = attachContext(makeErrorEnvelope({
|
|
41
|
+
code: ErrorCodes.INVALID_INPUT,
|
|
42
|
+
message: `Certified profile requires v2.2 modules; got: ${module.formatVersion ?? 'unknown'} (${module.format})`,
|
|
43
|
+
suggestion: "Migrate the module to v2.2, or run with `--profile strict` / `--profile default`",
|
|
44
|
+
}), { module: moduleName, provider: ctx.provider.name });
|
|
45
|
+
return { success: false, error: errorEnvelope.error.message, data: errorEnvelope };
|
|
46
|
+
}
|
|
47
|
+
}
|
|
37
48
|
try {
|
|
49
|
+
const policy = ctx.policy;
|
|
50
|
+
const startedAt = Date.now();
|
|
38
51
|
// Parse input if provided as JSON
|
|
39
52
|
let inputData = {};
|
|
40
53
|
if (options.input) {
|
|
@@ -67,7 +80,31 @@ export async function compose(moduleName, ctx, options = {}) {
|
|
|
67
80
|
const result = await executeComposition(moduleName, inputData, ctx.provider, {
|
|
68
81
|
cwd: ctx.cwd,
|
|
69
82
|
maxDepth: options.maxDepth,
|
|
70
|
-
timeoutMs: options.timeout
|
|
83
|
+
timeoutMs: options.timeout,
|
|
84
|
+
policy,
|
|
85
|
+
validateInput: (() => {
|
|
86
|
+
if (options.noValidate)
|
|
87
|
+
return false;
|
|
88
|
+
if (!policy)
|
|
89
|
+
return true;
|
|
90
|
+
if (policy.validate === 'off')
|
|
91
|
+
return false;
|
|
92
|
+
if (policy.validate === 'on')
|
|
93
|
+
return true;
|
|
94
|
+
return policy.profile !== 'core';
|
|
95
|
+
})(),
|
|
96
|
+
validateOutput: (() => {
|
|
97
|
+
if (options.noValidate)
|
|
98
|
+
return false;
|
|
99
|
+
if (!policy)
|
|
100
|
+
return true;
|
|
101
|
+
if (policy.validate === 'off')
|
|
102
|
+
return false;
|
|
103
|
+
if (policy.validate === 'on')
|
|
104
|
+
return true;
|
|
105
|
+
return policy.profile !== 'core';
|
|
106
|
+
})(),
|
|
107
|
+
enableRepair: policy?.enableRepair ?? true,
|
|
71
108
|
});
|
|
72
109
|
if (options.verbose) {
|
|
73
110
|
console.error('--- Composition Trace ---');
|
|
@@ -98,6 +135,28 @@ export async function compose(moduleName, ctx, options = {}) {
|
|
|
98
135
|
data: errorEnvelope,
|
|
99
136
|
};
|
|
100
137
|
}
|
|
138
|
+
if (policy?.audit) {
|
|
139
|
+
const rec = await writeAuditRecord({
|
|
140
|
+
ts: new Date().toISOString(),
|
|
141
|
+
kind: 'compose',
|
|
142
|
+
policy,
|
|
143
|
+
provider: ctx.provider.name,
|
|
144
|
+
module: { name: module.name, version: module.version, location: module.location, formatVersion: module.formatVersion },
|
|
145
|
+
input: inputData,
|
|
146
|
+
result: {
|
|
147
|
+
ok: result.ok,
|
|
148
|
+
result: result.result,
|
|
149
|
+
moduleResults: result.moduleResults,
|
|
150
|
+
trace: result.trace,
|
|
151
|
+
totalTimeMs: result.totalTimeMs,
|
|
152
|
+
error: result.error,
|
|
153
|
+
},
|
|
154
|
+
notes: [`duration_ms=${Date.now() - startedAt}`],
|
|
155
|
+
});
|
|
156
|
+
if (rec) {
|
|
157
|
+
console.error(`Audit: ${rec.path}`);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
101
160
|
// Return result
|
|
102
161
|
if (options.trace) {
|
|
103
162
|
// Include full result with trace
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cog core - Minimal "one-file" Core workflow
|
|
3
|
+
*
|
|
4
|
+
* Goals:
|
|
5
|
+
* - A single Markdown file can be a runnable module (optional frontmatter + prompt body)
|
|
6
|
+
* - Runtime generates loose schemas and always returns a v2.2 envelope on execution
|
|
7
|
+
* - No registry / conformance / certification required to get started
|
|
8
|
+
*/
|
|
9
|
+
import type { CommandContext, CommandResult } from '../types.js';
|
|
10
|
+
export interface CoreOptions {
|
|
11
|
+
args?: string;
|
|
12
|
+
input?: string;
|
|
13
|
+
noValidate?: boolean;
|
|
14
|
+
pretty?: boolean;
|
|
15
|
+
verbose?: boolean;
|
|
16
|
+
stream?: boolean;
|
|
17
|
+
dryRun?: boolean;
|
|
18
|
+
stdin?: boolean;
|
|
19
|
+
force?: boolean;
|
|
20
|
+
}
|
|
21
|
+
export declare function coreNew(filePath: string, options?: {
|
|
22
|
+
dryRun?: boolean;
|
|
23
|
+
}): Promise<CommandResult>;
|
|
24
|
+
export declare function coreSchema(filePath: string): Promise<CommandResult>;
|
|
25
|
+
export declare function corePromote(filePath: string, outDir?: string, options?: {
|
|
26
|
+
dryRun?: boolean;
|
|
27
|
+
force?: boolean;
|
|
28
|
+
}): Promise<CommandResult>;
|
|
29
|
+
export declare function coreRunText(markdownOrPrompt: string, ctx: CommandContext, options?: CoreOptions): Promise<CommandResult>;
|
|
30
|
+
export declare function coreRun(filePath: string, ctx: CommandContext, options?: CoreOptions): Promise<CommandResult>;
|
|
31
|
+
export declare function core(subcommand: string | undefined, target: string | undefined, ctx: CommandContext, options?: CoreOptions, rest?: string[]): Promise<CommandResult>;
|