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.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,12 @@
2
2
 
3
3
  All notable changes to this package are documented in this file.
4
4
 
5
+ ## 2.2.8 - 2026-02-07
6
+
7
+ - Fix: `npx cogn` alias compatibility on newer Node (exports + ESM). (Alias fix ships in `cogn@2.2.8`.)
8
+ - Hardening: configurable registry index fetch limits (`--registry-timeout-ms`, `--registry-max-bytes`).
9
+ - Hardening: remote registry verification supports bounded concurrency (`--concurrency`).
10
+
5
11
  ## 2.2.7 - 2026-02-06
6
12
 
7
13
  - Standardized v2.2 runtime behavior and cross-surface error envelope consistency (CLI/HTTP/MCP).
package/README.md CHANGED
@@ -10,12 +10,12 @@ Node.js/TypeScript 版本的 Cognitive Modules CLI,提供 `cog` 命令。
10
10
 
11
11
  ```bash
12
12
  # 全局安装(推荐)
13
- npm install -g cogn@2.2.7
13
+ npm install -g cogn@2.2.8
14
14
  # 或使用完整包名(同样提供 `cog` 命令)
15
- # npm install -g cognitive-modules-cli@2.2.7
15
+ # npm install -g cognitive-modules-cli@2.2.8
16
16
 
17
17
  # 或使用 npx 零安装
18
- npx cogn@2.2.7 --help
18
+ npx cogn@2.2.8 --help
19
19
  ```
20
20
 
21
21
  ## 快速开始
@@ -50,6 +50,18 @@ echo "review this code" | cog pipe --module code-reviewer
50
50
  ## 命令
51
51
 
52
52
  ```bash
53
+ # Core(单文件极简路径)
54
+ cog core new # 生成 demo.md
55
+ cog core run demo.md --args "..." # 运行单文件模块
56
+ cog core promote demo.md # 升级为 v2 模块目录
57
+
58
+ # 渐进复杂度(Profiles)
59
+ cog run code-reviewer --args "..." --profile core # 极简:跳过校验
60
+ cog run code-reviewer --args "..." --profile default # 默认:开启校验
61
+ cog run code-reviewer --args "..." --profile strict # 更严格:开启校验(更强门禁)
62
+ cog run code-reviewer --args "..." --profile certified # 最严格:v2.2 + 审计 + registry provenance/完整性门禁
63
+ # 覆盖开关:--validate auto|on|off,--audit(写入 ~/.cognitive/audit/)
64
+
53
65
  # 模块操作
54
66
  cog list # 列出模块
55
67
  cog run <module> --args "..." # 运行模块
@@ -76,6 +88,16 @@ cog mcp # 启动 MCP 服务(Claude Code / Cursor)
76
88
 
77
89
  # 环境检查
78
90
  cog doctor
91
+
92
+ # Registry(索引与分发)
93
+ # 默认 registry index(latest):
94
+ # https://github.com/Cognary/cognitive/releases/latest/download/cognitive-registry.v2.json
95
+ # 可通过环境变量或全局参数覆盖:
96
+ COGNITIVE_REGISTRY_URL=... cog search
97
+ COGNITIVE_REGISTRY_TIMEOUT_MS=15000 COGNITIVE_REGISTRY_MAX_BYTES=2097152 cog search
98
+ cog search --registry https://github.com/Cognary/cognitive/releases/download/v2.2.8/cognitive-registry.v2.json
99
+ cog registry verify --remote --index https://github.com/Cognary/cognitive/releases/latest/download/cognitive-registry.v2.json
100
+ cog registry verify --remote --concurrency 2
79
101
  ```
80
102
 
81
103
  ## 开发
@@ -0,0 +1,13 @@
1
+ export interface AuditRecord {
2
+ ts: string;
3
+ kind: 'run' | 'pipe' | 'compose';
4
+ policy?: unknown;
5
+ provider?: string;
6
+ module?: unknown;
7
+ input?: unknown;
8
+ result?: unknown;
9
+ notes?: string[];
10
+ }
11
+ export declare function writeAuditRecord(record: AuditRecord): Promise<{
12
+ path: string;
13
+ } | null>;
package/dist/audit.js ADDED
@@ -0,0 +1,25 @@
1
+ import * as fs from 'node:fs/promises';
2
+ import * as path from 'node:path';
3
+ import * as os from 'node:os';
4
+ function safeSlug(s) {
5
+ const out = s.trim().toLowerCase().replace(/\s+/g, '-').replace(/[^a-z0-9._-]/g, '-');
6
+ return out.length > 0 ? out.slice(0, 80) : 'unknown';
7
+ }
8
+ export async function writeAuditRecord(record) {
9
+ // Keep it simple and predictable: write under ~/.cognitive/audit/
10
+ const dir = path.join(os.homedir(), '.cognitive', 'audit');
11
+ const ts = record.ts.replace(/[:.]/g, '-');
12
+ const kind = record.kind;
13
+ const moduleName = safeSlug(typeof record.module?.name === 'string' ? record.module.name : 'module');
14
+ const filename = `${ts}-${kind}-${moduleName}-${Math.random().toString(16).slice(2, 10)}.json`;
15
+ try {
16
+ await fs.mkdir(dir, { recursive: true });
17
+ const outPath = path.join(dir, filename);
18
+ await fs.writeFile(outPath, JSON.stringify(record, null, 2) + '\n', 'utf-8');
19
+ return { path: outPath };
20
+ }
21
+ catch {
22
+ // Audit must never break execution.
23
+ return null;
24
+ }
25
+ }
package/dist/cli.js CHANGED
@@ -15,9 +15,12 @@
15
15
  */
16
16
  import { parseArgs } from 'node:util';
17
17
  import { getProvider, listProviders } from './providers/index.js';
18
- import { run, list, pipe, init, add, update, remove, versions, compose, composeInfo, validate, validateAll, migrate, migrateAll, test, testAll, search, listCategories, info } from './commands/index.js';
18
+ import { run, list, pipe, init, add, update, remove, versions, compose, composeInfo, validate, validateAll, migrate, migrateAll, test, testAll, search, listCategories, info, core } from './commands/index.js';
19
19
  import { listModules, getDefaultSearchPaths } from './modules/loader.js';
20
20
  import { VERSION } from './version.js';
21
+ import { resolveExecutionPolicy } from './profile.js';
22
+ import { buildRegistryAssets, verifyRegistryAssets } from './registry/assets.js';
23
+ import { DEFAULT_REGISTRY_URL } from './registry/client.js';
21
24
  async function main() {
22
25
  const args = process.argv.slice(2);
23
26
  const command = args[0];
@@ -33,6 +36,12 @@ async function main() {
33
36
  const { values, positionals } = parseArgs({
34
37
  args: args.slice(1),
35
38
  options: {
39
+ help: { type: 'boolean', short: 'h', default: false },
40
+ stdin: { type: 'boolean', default: false }, // core: read module prompt from stdin
41
+ force: { type: 'boolean', default: false }, // core promote: overwrite existing target dir
42
+ profile: { type: 'string' }, // progressive complexity profile
43
+ validate: { type: 'string' }, // auto|on|off (overrides --no-validate)
44
+ audit: { type: 'boolean', default: false }, // write audit record to ~/.cognitive/audit/
36
45
  args: { type: 'string', short: 'a' },
37
46
  input: { type: 'string', short: 'i' },
38
47
  module: { type: 'string', short: 'm' },
@@ -62,9 +71,59 @@ async function main() {
62
71
  format: { type: 'string', short: 'f' },
63
72
  // Search options
64
73
  category: { type: 'string', short: 'c' },
74
+ registry: { type: 'string' }, // override registry index URL (or use env COGNITIVE_REGISTRY_URL)
75
+ 'registry-timeout-ms': { type: 'string' },
76
+ 'registry-max-bytes': { type: 'string' },
77
+ // Registry build/verify options
78
+ 'modules-dir': { type: 'string' },
79
+ 'v1-registry': { type: 'string' },
80
+ 'out-dir': { type: 'string' },
81
+ 'registry-out': { type: 'string' },
82
+ namespace: { type: 'string' },
83
+ 'runtime-min': { type: 'string' },
84
+ repository: { type: 'string' },
85
+ homepage: { type: 'string' },
86
+ license: { type: 'string' },
87
+ timestamp: { type: 'string' },
88
+ only: { type: 'string', multiple: true },
89
+ index: { type: 'string' },
90
+ 'assets-dir': { type: 'string' },
91
+ 'tarball-base-url': { type: 'string' },
92
+ remote: { type: 'boolean', default: false }, // registry verify: fetch index + tarballs over network
93
+ 'fetch-timeout-ms': { type: 'string' },
94
+ 'max-index-bytes': { type: 'string' },
95
+ 'max-tarball-bytes': { type: 'string' },
96
+ concurrency: { type: 'string' },
65
97
  },
66
98
  allowPositionals: true,
67
99
  });
100
+ if (values.help) {
101
+ if (command === 'core') {
102
+ console.log(JSON.stringify({
103
+ usage: [
104
+ 'cog core new [file.md] [--dry-run]',
105
+ 'cog core schema <file.md> [--pretty]',
106
+ 'cog core run <file.md> [--args \"...\"] [--pretty] [--stream] [--no-validate]',
107
+ 'cog core run --stdin [--args \"...\"] [--pretty] [--stream] [--no-validate]',
108
+ 'cog core promote <file.md> [outDir] [--dry-run] [--force]',
109
+ ],
110
+ }, null, values.pretty ? 2 : 0));
111
+ process.exit(0);
112
+ }
113
+ printHelp();
114
+ process.exit(0);
115
+ }
116
+ // Guard Core-only flags so we don't silently ignore them on other commands.
117
+ if (command !== 'core') {
118
+ if (values.stdin) {
119
+ console.error('Error: --stdin is only supported for `cog core run --stdin`');
120
+ process.exit(1);
121
+ }
122
+ if (values.force) {
123
+ console.error('Error: --force is only supported for `cog core promote --force`');
124
+ process.exit(1);
125
+ }
126
+ }
68
127
  // Get provider
69
128
  let provider;
70
129
  try {
@@ -74,13 +133,64 @@ async function main() {
74
133
  console.error(`Error: ${e instanceof Error ? e.message : e}`);
75
134
  process.exit(1);
76
135
  }
136
+ let policy;
137
+ try {
138
+ policy = resolveExecutionPolicy({
139
+ profile: values.profile,
140
+ validate: values.validate,
141
+ noValidate: values['no-validate'],
142
+ audit: values.audit,
143
+ });
144
+ }
145
+ catch (e) {
146
+ console.error(`Error: ${e instanceof Error ? e.message : String(e)}`);
147
+ process.exit(1);
148
+ }
149
+ const parsePositive = (key, raw) => {
150
+ if (raw === undefined || raw === null || raw === '')
151
+ return undefined;
152
+ const n = Number(raw);
153
+ if (!Number.isFinite(n) || n <= 0) {
154
+ throw new Error(`Invalid --${key}: ${String(raw)} (expected a positive number)`);
155
+ }
156
+ return Math.floor(n);
157
+ };
77
158
  const ctx = {
78
159
  cwd: process.cwd(),
79
160
  provider,
80
161
  verbose: values.verbose,
162
+ policy,
163
+ registryUrl: values.registry ?? undefined,
164
+ registryTimeoutMs: parsePositive('registry-timeout-ms', values['registry-timeout-ms']),
165
+ registryMaxBytes: parsePositive('registry-max-bytes', values['registry-max-bytes']),
81
166
  };
82
167
  try {
83
168
  switch (command) {
169
+ case 'core': {
170
+ const sub = positionals[0];
171
+ const target = positionals[1];
172
+ const rest = positionals.slice(2);
173
+ const result = await core(sub, target, ctx, {
174
+ args: values.args,
175
+ input: values.input,
176
+ noValidate: values['no-validate'],
177
+ pretty: values.pretty,
178
+ verbose: values.verbose,
179
+ stream: values.stream,
180
+ dryRun: values['dry-run'],
181
+ stdin: values.stdin,
182
+ force: values.force,
183
+ }, rest);
184
+ if (!result.success) {
185
+ console.error(`Error: ${result.error}`);
186
+ process.exit(1);
187
+ }
188
+ // Stream mode prints events as NDJSON already (via `cog run`).
189
+ if (!(values.stream && sub === 'run')) {
190
+ console.log(JSON.stringify(result.data, null, values.pretty ? 2 : 0));
191
+ }
192
+ break;
193
+ }
84
194
  case 'run': {
85
195
  const moduleName = args[1];
86
196
  if (!moduleName || moduleName.startsWith('-')) {
@@ -90,6 +200,7 @@ async function main() {
90
200
  const result = await run(moduleName, ctx, {
91
201
  args: values.args,
92
202
  input: values.input,
203
+ // policy.validate is resolved in run(); keep legacy flag compatibility here.
93
204
  noValidate: values['no-validate'],
94
205
  pretty: values.pretty,
95
206
  verbose: values.verbose,
@@ -388,6 +499,7 @@ async function main() {
388
499
  const result = await compose(moduleName, ctx, {
389
500
  args: values.args,
390
501
  input: values.input,
502
+ noValidate: values['no-validate'],
391
503
  maxDepth: values['max-depth'] ? parseInt(values['max-depth'], 10) : undefined,
392
504
  timeout: values.timeout ? parseInt(values.timeout, 10) : undefined,
393
505
  trace: values.trace,
@@ -770,6 +882,52 @@ async function main() {
770
882
  await client.fetchRegistry(true);
771
883
  console.log('✓ Registry cache refreshed');
772
884
  }
885
+ else if (subCommand === 'build') {
886
+ const result = await buildRegistryAssets({
887
+ tag: values.tag ?? null,
888
+ tarballBaseUrl: values['tarball-base-url'] ?? null,
889
+ modulesDir: values['modules-dir'] ?? 'cognitive/modules',
890
+ v1RegistryPath: values['v1-registry'] ?? 'cognitive-registry.json',
891
+ outDir: values['out-dir'] ?? 'dist/registry-assets',
892
+ registryOut: values['registry-out'] ?? 'cognitive-registry.v2.json',
893
+ namespace: values.namespace ?? 'official',
894
+ runtimeMin: values['runtime-min'] ?? '2.2.0',
895
+ repository: values.repository ?? 'https://github.com/Cognary/cognitive',
896
+ homepage: values.homepage ?? 'https://cognary.github.io/cognitive/',
897
+ license: values.license ?? 'MIT',
898
+ timestamp: values.timestamp ?? null,
899
+ only: Array.isArray(values.only) ? values.only : (values.only ? [String(values.only)] : []),
900
+ });
901
+ console.log(JSON.stringify({ ok: true, ...result }, null, values.pretty ? 2 : 0));
902
+ }
903
+ else if (subCommand === 'verify') {
904
+ const remote = Boolean(values.remote);
905
+ const defaultIndex = (typeof process.env.COGNITIVE_REGISTRY_URL === 'string' && process.env.COGNITIVE_REGISTRY_URL.trim()
906
+ ? process.env.COGNITIVE_REGISTRY_URL.trim()
907
+ : undefined) ??
908
+ ctx.registryUrl ??
909
+ DEFAULT_REGISTRY_URL;
910
+ const indexPath = values.index ??
911
+ (remote ? defaultIndex : 'cognitive-registry.v2.json');
912
+ const assetsDir = values['assets-dir'] ?? (remote ? undefined : 'dist/registry-assets');
913
+ const fetchTimeoutMs = parsePositive('fetch-timeout-ms', values['fetch-timeout-ms']);
914
+ const maxIndexBytes = parsePositive('max-index-bytes', values['max-index-bytes']);
915
+ const maxTarballBytes = parsePositive('max-tarball-bytes', values['max-tarball-bytes']);
916
+ const concurrency = parsePositive('concurrency', values.concurrency);
917
+ const verified = await verifyRegistryAssets({
918
+ registryIndexPath: indexPath,
919
+ assetsDir,
920
+ remote,
921
+ fetchTimeoutMs,
922
+ maxIndexBytes,
923
+ maxTarballBytes,
924
+ concurrency,
925
+ });
926
+ console.log(JSON.stringify(verified, null, values.pretty ? 2 : 0));
927
+ if (!verified.ok) {
928
+ process.exit(1);
929
+ }
930
+ }
773
931
  else {
774
932
  console.error(`Unknown registry subcommand: ${subCommand}`);
775
933
  console.error('');
@@ -778,6 +936,9 @@ async function main() {
778
936
  console.error(' cog registry categories List categories');
779
937
  console.error(' cog registry info <mod> Show module details');
780
938
  console.error(' cog registry refresh Refresh cache');
939
+ console.error(' cog registry build Build registry tarballs + v2 index (local)');
940
+ console.error(' cog registry verify Verify local tarballs against v2 index');
941
+ console.error(' cog registry verify --remote --index <url> Verify remote index+tarballs');
781
942
  process.exit(1);
782
943
  }
783
944
  break;
@@ -805,6 +966,7 @@ USAGE:
805
966
  cog <command> [options]
806
967
 
807
968
  COMMANDS:
969
+ core <cmd> Minimal "one-file" workflow (new, schema, run)
808
970
  run <module> Run a Cognitive Module
809
971
  test <module> Run golden tests for a module
810
972
  compose <module> Execute a composed module workflow
@@ -815,7 +977,7 @@ COMMANDS:
815
977
  remove <module> Remove installed module
816
978
  versions <url> List available versions
817
979
  search [query] Search modules in registry
818
- registry <cmd> Registry commands (list, categories, info, refresh)
980
+ registry <cmd> Registry commands (list, categories, info, refresh, build, verify)
819
981
  validate <module> Validate module structure
820
982
  migrate <module> Migrate module to v2.2 format
821
983
  pipe Pipe mode (stdin/stdout)
@@ -825,6 +987,9 @@ COMMANDS:
825
987
  doctor Check environment and configuration
826
988
 
827
989
  OPTIONS:
990
+ --profile <name> Progressive complexity profile: core|default|strict|certified
991
+ --validate <mode> Validation mode: auto|on|off (overrides --no-validate)
992
+ --audit Write an audit record to ~/.cognitive/audit/ (stderr prints path)
828
993
  -a, --args <str> Arguments to pass to module
829
994
  -i, --input <json> JSON input for module
830
995
  -m, --module <name> Module path within repo (for add)
@@ -836,6 +1001,8 @@ OPTIONS:
836
1001
  --pretty Pretty-print JSON output
837
1002
  -V, --verbose Verbose output
838
1003
  --no-validate Skip schema validation
1004
+ --stdin Read module prompt from stdin (for core run)
1005
+ --force Overwrite target directory (for core promote)
839
1006
  -H, --host <host> Server host (default: 0.0.0.0)
840
1007
  -P, --port <port> Server port (default: 8000)
841
1008
  -d, --max-depth <n> Max composition depth (default: 5)
@@ -847,11 +1014,21 @@ OPTIONS:
847
1014
  --all Process all modules (for validate/migrate)
848
1015
  -f, --format <fmt> Output format: text or json (for validate)
849
1016
  -c, --category <cat> Filter by category (for search)
1017
+ --registry <url> Override registry index URL (or set env COGNITIVE_REGISTRY_URL)
1018
+ --registry-timeout-ms <ms> Registry index fetch timeout (overrides env COGNITIVE_REGISTRY_TIMEOUT_MS)
1019
+ --registry-max-bytes <n> Registry index max bytes (overrides env COGNITIVE_REGISTRY_MAX_BYTES)
850
1020
  -l, --limit <n> Limit results (for search, versions)
851
1021
  -v, --version Show version
852
1022
  -h, --help Show this help
853
1023
 
854
1024
  EXAMPLES:
1025
+ # One-file Core (no registry required)
1026
+ cog core new demo.md
1027
+ cog core run demo.md --args "hello" --pretty
1028
+ cog core schema demo.md --pretty
1029
+ cog core promote demo.md
1030
+ cog core promote demo.md ./cognitive/modules/demo
1031
+
855
1032
  # Search and discover modules
856
1033
  cog search code review # Search by keywords
857
1034
  cog search # List all available modules
@@ -910,6 +1087,9 @@ ENVIRONMENT:
910
1087
  DASHSCOPE_API_KEY Alibaba Qwen (通义千问)
911
1088
  OLLAMA_HOST Ollama local (default: localhost:11434)
912
1089
  COG_MODEL Override default model for any provider
1090
+ COGNITIVE_REGISTRY_URL Override registry index URL
1091
+ COGNITIVE_REGISTRY_TIMEOUT_MS Registry index fetch timeout (ms)
1092
+ COGNITIVE_REGISTRY_MAX_BYTES Registry index max bytes
913
1093
  `);
914
1094
  }
915
1095
  main().catch(e => {
@@ -18,6 +18,7 @@ import { homedir, tmpdir } from 'node:os';
18
18
  import { createHash } from 'node:crypto';
19
19
  import { RegistryClient } from '../registry/client.js';
20
20
  import { extractTarGzFile } from '../registry/tar.js';
21
+ import { PROVENANCE_SPEC, computeModuleIntegrity, writeModuleProvenance } from '../provenance.js';
21
22
  // Module storage paths
22
23
  const USER_MODULES_DIR = join(homedir(), '.cognitive', 'modules');
23
24
  const INSTALLED_MANIFEST = join(homedir(), '.cognitive', 'installed.json');
@@ -340,9 +341,13 @@ function parseModuleSpec(spec) {
340
341
  export async function addFromRegistry(moduleSpec, ctx, options = {}) {
341
342
  const { name: moduleName, version: requestedVersion } = parseModuleSpec(moduleSpec);
342
343
  const { name: customName, registry: registryUrl } = options;
344
+ const policy = ctx.policy;
343
345
  let tempDir;
344
346
  try {
345
- const client = new RegistryClient(registryUrl);
347
+ const client = new RegistryClient(registryUrl, {
348
+ timeoutMs: ctx.registryTimeoutMs,
349
+ maxBytes: ctx.registryMaxBytes,
350
+ });
346
351
  const moduleInfo = await client.getModule(moduleName);
347
352
  if (!moduleInfo) {
348
353
  return {
@@ -357,6 +362,14 @@ export async function addFromRegistry(moduleSpec, ctx, options = {}) {
357
362
  // Get download info
358
363
  const downloadInfo = await client.getDownloadUrl(moduleName);
359
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
+ }
360
373
  const { org, repo, path, ref } = downloadInfo.githubInfo;
361
374
  // Use addFromGitHub() directly (not add() to avoid recursion)
362
375
  const result = await addFromGitHub(`${org}/${repo}`, ctx, {
@@ -458,6 +471,35 @@ export async function addFromRegistry(moduleSpec, ctx, options = {}) {
458
471
  await mkdir(USER_MODULES_DIR, { recursive: true });
459
472
  copyDir(sourcePath, targetPath);
460
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
+ }
461
503
  await recordInstall(safeInstallName, {
462
504
  source: downloadInfo.url,
463
505
  githubUrl: downloadInfo.url,
@@ -502,6 +544,14 @@ export async function addFromRegistry(moduleSpec, ctx, options = {}) {
502
544
  export async function addFromGitHub(url, ctx, options = {}) {
503
545
  const { org, repo, fullUrl } = parseGitHubUrl(url);
504
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
+ }
505
555
  // Determine ref (tag takes priority)
506
556
  const ref = tag || branch;
507
557
  const isTag = !!tag;
@@ -546,6 +596,19 @@ export async function addFromGitHub(url, ctx, options = {}) {
546
596
  installedAt: targetPath,
547
597
  installedTime: new Date().toISOString(),
548
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
+ }
549
612
  // Cleanup temp directory
550
613
  const tempDir = dirname(repoRoot);
551
614
  if (tempDir && tempDir !== '/' && tempDir !== '.' && tempDir !== USER_MODULES_DIR) {
@@ -585,6 +648,10 @@ export async function addFromGitHub(url, ctx, options = {}) {
585
648
  * Add a module from GitHub or Registry (auto-detect source)
586
649
  */
587
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
+ }
588
655
  // Determine source type
589
656
  if (isGitHubSource(source)) {
590
657
  return addFromGitHub(source, ctx, options);
@@ -13,6 +13,8 @@ export interface ComposeOptions {
13
13
  args?: string;
14
14
  /** JSON input data */
15
15
  input?: string;
16
+ /** Disable validation */
17
+ noValidate?: boolean;
16
18
  /** Maximum composition depth */
17
19
  maxDepth?: number;
18
20
  /** Timeout in milliseconds */
@@ -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>;