cognitive-modules-cli 2.2.7 → 2.2.9
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 +12 -0
- package/README.md +25 -3
- package/bin.js +30 -0
- package/dist/audit.d.ts +13 -0
- package/dist/audit.js +25 -0
- package/dist/cli.js +190 -2
- package/dist/commands/add.js +68 -1
- 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.js +99 -17
- package/dist/commands/search.js +13 -3
- package/dist/commands/update.js +4 -1
- 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 +3 -1
- package/dist/modules/runner.js +121 -2
- 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 +8 -1
- package/dist/registry/client.js +83 -29
- package/dist/types.d.ts +31 -0
- package/package.json +4 -3
|
@@ -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>;
|
|
@@ -0,0 +1,338 @@
|
|
|
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 * as fs from 'node:fs/promises';
|
|
10
|
+
import * as path from 'node:path';
|
|
11
|
+
import * as os from 'node:os';
|
|
12
|
+
import yaml from 'js-yaml';
|
|
13
|
+
import { loadSingleFileModule } from '../modules/loader.js';
|
|
14
|
+
import { run } from './run.js';
|
|
15
|
+
function ensureMdPath(p) {
|
|
16
|
+
const ext = path.extname(p).toLowerCase();
|
|
17
|
+
if (ext === '.md' || ext === '.markdown')
|
|
18
|
+
return p;
|
|
19
|
+
return `${p}.md`;
|
|
20
|
+
}
|
|
21
|
+
function toSafeName(base) {
|
|
22
|
+
// Keep it predictable and filesystem-friendly.
|
|
23
|
+
const s = base.trim().replace(/\s+/g, '-').replace(/[^a-zA-Z0-9._-]/g, '-');
|
|
24
|
+
const collapsed = s.replace(/-+/g, '-').replace(/^\-+|\-+$/g, '');
|
|
25
|
+
return collapsed.length > 0 ? collapsed : 'core-module';
|
|
26
|
+
}
|
|
27
|
+
function coreTemplate(name) {
|
|
28
|
+
return [
|
|
29
|
+
'---',
|
|
30
|
+
`name: ${name}`,
|
|
31
|
+
'version: 0.1.0',
|
|
32
|
+
'responsibility: "One-file core module (5-minute path)"',
|
|
33
|
+
'tier: decision',
|
|
34
|
+
'schema_strictness: low',
|
|
35
|
+
'excludes:',
|
|
36
|
+
' - do not make network calls',
|
|
37
|
+
' - do not write files',
|
|
38
|
+
'---',
|
|
39
|
+
'',
|
|
40
|
+
'You are a Cognitive Module. Return JSON only (no markdown).',
|
|
41
|
+
'Return a valid v2.2 envelope with meta + data.',
|
|
42
|
+
'',
|
|
43
|
+
'INPUT (provided by runtime):',
|
|
44
|
+
'- query: natural language input (when --args looks like text)',
|
|
45
|
+
'- code: code input (when --args looks like code)',
|
|
46
|
+
'',
|
|
47
|
+
'You MUST treat missing fields as empty.',
|
|
48
|
+
'',
|
|
49
|
+
'INPUT VALUES:',
|
|
50
|
+
'query:',
|
|
51
|
+
'${query}',
|
|
52
|
+
'',
|
|
53
|
+
'code:',
|
|
54
|
+
'${code}',
|
|
55
|
+
'',
|
|
56
|
+
'Output (requirements):',
|
|
57
|
+
'- ok: true',
|
|
58
|
+
'- meta.confidence: 0-1',
|
|
59
|
+
"- meta.risk: one of 'none' | 'low' | 'medium' | 'high' (or extensible enum when allowed)",
|
|
60
|
+
'- meta.explain: <=280 chars',
|
|
61
|
+
'- data.rationale: string (long-form explanation for auditing)',
|
|
62
|
+
'- data: your structured fields (plus data.rationale)',
|
|
63
|
+
'',
|
|
64
|
+
'Minimal example (shape only):',
|
|
65
|
+
'{ "ok": true, "meta": { "confidence": 0.8, "risk": "low", "explain": "..." }, "data": { "rationale": "...", "result": "..." } }',
|
|
66
|
+
'',
|
|
67
|
+
].join('\n');
|
|
68
|
+
}
|
|
69
|
+
function defaultErrorSchema() {
|
|
70
|
+
return {
|
|
71
|
+
type: 'object',
|
|
72
|
+
additionalProperties: true,
|
|
73
|
+
required: ['code', 'message'],
|
|
74
|
+
properties: {
|
|
75
|
+
code: { type: 'string' },
|
|
76
|
+
message: { type: 'string' },
|
|
77
|
+
recoverable: { type: 'boolean' },
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
export async function coreNew(filePath, options = {}) {
|
|
82
|
+
const abs = path.resolve(process.cwd(), ensureMdPath(filePath));
|
|
83
|
+
const name = toSafeName(path.basename(abs, path.extname(abs)));
|
|
84
|
+
const content = coreTemplate(name);
|
|
85
|
+
if (options.dryRun) {
|
|
86
|
+
return {
|
|
87
|
+
success: true,
|
|
88
|
+
data: {
|
|
89
|
+
message: `Dry run: would create ${abs}`,
|
|
90
|
+
location: abs,
|
|
91
|
+
preview: content,
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
try {
|
|
96
|
+
await fs.stat(abs);
|
|
97
|
+
return { success: false, error: `File already exists: ${abs}` };
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
// ok
|
|
101
|
+
}
|
|
102
|
+
await fs.mkdir(path.dirname(abs), { recursive: true });
|
|
103
|
+
await fs.writeFile(abs, content, 'utf-8');
|
|
104
|
+
return {
|
|
105
|
+
success: true,
|
|
106
|
+
data: {
|
|
107
|
+
message: `Created one-file module: ${abs}`,
|
|
108
|
+
location: abs,
|
|
109
|
+
hint: `Run: cog run ${abs} --args "hello" --pretty`,
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
export async function coreSchema(filePath) {
|
|
114
|
+
const abs = path.resolve(process.cwd(), filePath);
|
|
115
|
+
const mod = await loadSingleFileModule(abs);
|
|
116
|
+
return {
|
|
117
|
+
success: true,
|
|
118
|
+
data: {
|
|
119
|
+
name: mod.name,
|
|
120
|
+
version: mod.version,
|
|
121
|
+
location: mod.location,
|
|
122
|
+
schema: {
|
|
123
|
+
meta: mod.metaSchema ?? {},
|
|
124
|
+
input: mod.inputSchema ?? {},
|
|
125
|
+
data: mod.dataSchema ?? {},
|
|
126
|
+
error: mod.errorSchema ?? defaultErrorSchema(),
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
function defaultPromoteDir(moduleName) {
|
|
132
|
+
return path.resolve(process.cwd(), 'cognitive', 'modules', moduleName);
|
|
133
|
+
}
|
|
134
|
+
function isPathWithinRoot(rootDir, targetPath) {
|
|
135
|
+
const root = path.resolve(rootDir);
|
|
136
|
+
const target = path.resolve(targetPath);
|
|
137
|
+
return target === root || target.startsWith(root + path.sep);
|
|
138
|
+
}
|
|
139
|
+
function schemaJsonForV22(mod) {
|
|
140
|
+
return {
|
|
141
|
+
meta: mod.metaSchema ?? {},
|
|
142
|
+
input: mod.inputSchema ?? {},
|
|
143
|
+
data: mod.dataSchema ?? {},
|
|
144
|
+
error: mod.errorSchema ?? defaultErrorSchema(),
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
function moduleYamlForV22(mod) {
|
|
148
|
+
const manifest = {
|
|
149
|
+
name: mod.name,
|
|
150
|
+
version: mod.version,
|
|
151
|
+
responsibility: mod.responsibility,
|
|
152
|
+
tier: mod.tier ?? 'decision',
|
|
153
|
+
excludes: mod.excludes ?? [],
|
|
154
|
+
schema_strictness: mod.schemaStrictness ?? 'low',
|
|
155
|
+
overflow: mod.overflow ?? undefined,
|
|
156
|
+
enums: mod.enums ?? undefined,
|
|
157
|
+
compat: mod.compat ?? undefined,
|
|
158
|
+
meta: mod.metaConfig ?? undefined,
|
|
159
|
+
};
|
|
160
|
+
// Remove undefined keys for cleaner output.
|
|
161
|
+
for (const k of Object.keys(manifest)) {
|
|
162
|
+
if (manifest[k] === undefined)
|
|
163
|
+
delete manifest[k];
|
|
164
|
+
}
|
|
165
|
+
return yaml.dump(manifest, { noRefs: true, lineWidth: 120 }).trimEnd() + '\n';
|
|
166
|
+
}
|
|
167
|
+
export async function corePromote(filePath, outDir, options = {}) {
|
|
168
|
+
const abs = path.resolve(process.cwd(), filePath);
|
|
169
|
+
const mod = await loadSingleFileModule(abs);
|
|
170
|
+
const targetDir = outDir ? path.resolve(process.cwd(), outDir) : defaultPromoteDir(mod.name);
|
|
171
|
+
const cwd = process.cwd();
|
|
172
|
+
// Promote is meant to generate a project-local v2 module directory. Refuse to write outside cwd
|
|
173
|
+
// (and especially refuse `--force` deletes) to prevent footguns like `--force /`.
|
|
174
|
+
if (!isPathWithinRoot(cwd, targetDir) || path.resolve(targetDir) === path.resolve(cwd)) {
|
|
175
|
+
return { success: false, error: `Refusing to promote outside current directory: ${targetDir}` };
|
|
176
|
+
}
|
|
177
|
+
const moduleYamlPath = path.join(targetDir, 'module.yaml');
|
|
178
|
+
const promptMdPath = path.join(targetDir, 'prompt.md');
|
|
179
|
+
const schemaJsonPath = path.join(targetDir, 'schema.json');
|
|
180
|
+
const moduleYaml = moduleYamlForV22(mod);
|
|
181
|
+
const promptMd = (mod.prompt ?? '').trim() + '\n';
|
|
182
|
+
const schemaJson = JSON.stringify(schemaJsonForV22(mod), null, 2) + '\n';
|
|
183
|
+
const files = [moduleYamlPath, promptMdPath, schemaJsonPath];
|
|
184
|
+
if (options.dryRun) {
|
|
185
|
+
return {
|
|
186
|
+
success: true,
|
|
187
|
+
data: {
|
|
188
|
+
message: `Dry run: would promote ${abs} to v2 module directory`,
|
|
189
|
+
from: abs,
|
|
190
|
+
to: targetDir,
|
|
191
|
+
files,
|
|
192
|
+
preview: {
|
|
193
|
+
'module.yaml': moduleYaml,
|
|
194
|
+
'prompt.md': promptMd,
|
|
195
|
+
'schema.json': schemaJson,
|
|
196
|
+
},
|
|
197
|
+
},
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
// Avoid overwriting an existing module directory silently.
|
|
201
|
+
try {
|
|
202
|
+
await fs.stat(targetDir);
|
|
203
|
+
if (!options.force) {
|
|
204
|
+
return { success: false, error: `Target directory already exists: ${targetDir} (use --force to overwrite)` };
|
|
205
|
+
}
|
|
206
|
+
await fs.rm(targetDir, { recursive: true, force: true });
|
|
207
|
+
}
|
|
208
|
+
catch {
|
|
209
|
+
// ok
|
|
210
|
+
}
|
|
211
|
+
await fs.mkdir(targetDir, { recursive: true });
|
|
212
|
+
await fs.writeFile(moduleYamlPath, moduleYaml, 'utf-8');
|
|
213
|
+
await fs.writeFile(promptMdPath, promptMd, 'utf-8');
|
|
214
|
+
await fs.writeFile(schemaJsonPath, schemaJson, 'utf-8');
|
|
215
|
+
// Add minimal golden tests (schema validation mode) so `cog test <module>` works out of the box.
|
|
216
|
+
const testsDir = path.join(targetDir, 'tests');
|
|
217
|
+
await fs.mkdir(testsDir, { recursive: true });
|
|
218
|
+
const inputPath = path.join(testsDir, 'smoke.input.json');
|
|
219
|
+
const expectedPath = path.join(testsDir, 'smoke.expected.json');
|
|
220
|
+
const smokeInput = {
|
|
221
|
+
query: 'hello',
|
|
222
|
+
};
|
|
223
|
+
const smokeExpected = {
|
|
224
|
+
$validate: {
|
|
225
|
+
type: 'object',
|
|
226
|
+
required: ['ok', 'meta', 'data'],
|
|
227
|
+
properties: {
|
|
228
|
+
ok: { const: true },
|
|
229
|
+
meta: {
|
|
230
|
+
type: 'object',
|
|
231
|
+
required: ['confidence', 'risk', 'explain'],
|
|
232
|
+
properties: {
|
|
233
|
+
confidence: { type: 'number', minimum: 0, maximum: 1 },
|
|
234
|
+
risk: { type: 'string' },
|
|
235
|
+
explain: { type: 'string', maxLength: 280 },
|
|
236
|
+
},
|
|
237
|
+
},
|
|
238
|
+
data: {
|
|
239
|
+
type: 'object',
|
|
240
|
+
required: ['rationale'],
|
|
241
|
+
properties: {
|
|
242
|
+
rationale: { type: 'string' },
|
|
243
|
+
},
|
|
244
|
+
},
|
|
245
|
+
},
|
|
246
|
+
},
|
|
247
|
+
};
|
|
248
|
+
await fs.writeFile(inputPath, JSON.stringify(smokeInput, null, 2) + '\n', 'utf-8');
|
|
249
|
+
await fs.writeFile(expectedPath, JSON.stringify(smokeExpected, null, 2) + '\n', 'utf-8');
|
|
250
|
+
return {
|
|
251
|
+
success: true,
|
|
252
|
+
data: {
|
|
253
|
+
message: `Promoted one-file module to v2 directory`,
|
|
254
|
+
from: abs,
|
|
255
|
+
to: targetDir,
|
|
256
|
+
files: [...files, inputPath, expectedPath],
|
|
257
|
+
hint: `Run: cog run ${mod.name} --args "hello" --pretty`,
|
|
258
|
+
},
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
async function readAll(stream) {
|
|
262
|
+
return await new Promise((resolve, reject) => {
|
|
263
|
+
const chunks = [];
|
|
264
|
+
stream.on('data', (c) => chunks.push(Buffer.isBuffer(c) ? c : Buffer.from(String(c))));
|
|
265
|
+
stream.on('end', () => resolve(Buffer.concat(chunks).toString('utf-8')));
|
|
266
|
+
stream.on('error', reject);
|
|
267
|
+
// Ensure flow mode.
|
|
268
|
+
if (typeof stream.resume === 'function') {
|
|
269
|
+
stream.resume();
|
|
270
|
+
}
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
export async function coreRunText(markdownOrPrompt, ctx, options = {}) {
|
|
274
|
+
const raw = markdownOrPrompt.trim();
|
|
275
|
+
const content = raw.startsWith('---') ? raw + '\n' : (coreTemplate('stdin-core') + '\n' + raw + '\n');
|
|
276
|
+
const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'cog-core-stdin-'));
|
|
277
|
+
const file = path.join(dir, 'stdin.md');
|
|
278
|
+
await fs.writeFile(file, content, 'utf-8');
|
|
279
|
+
try {
|
|
280
|
+
return await coreRun(file, ctx, options);
|
|
281
|
+
}
|
|
282
|
+
finally {
|
|
283
|
+
// Best-effort cleanup.
|
|
284
|
+
await fs.rm(dir, { recursive: true, force: true });
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
export async function coreRun(filePath, ctx, options = {}) {
|
|
288
|
+
// Delegate to `cog run` which already supports file paths.
|
|
289
|
+
return run(filePath, ctx, {
|
|
290
|
+
args: options.args,
|
|
291
|
+
input: options.input,
|
|
292
|
+
noValidate: options.noValidate,
|
|
293
|
+
pretty: options.pretty,
|
|
294
|
+
verbose: options.verbose,
|
|
295
|
+
stream: options.stream,
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
export async function core(subcommand, target, ctx, options = {}, rest = []) {
|
|
299
|
+
if (!subcommand || subcommand === '--help' || subcommand === '-h') {
|
|
300
|
+
return {
|
|
301
|
+
success: true,
|
|
302
|
+
data: {
|
|
303
|
+
usage: [
|
|
304
|
+
'cog core new [file.md] [--dry-run]',
|
|
305
|
+
'cog core schema <file.md> [--pretty]',
|
|
306
|
+
'cog core run <file.md> [--args "..."] [--pretty] [--stream] [--no-validate]',
|
|
307
|
+
'cog core run --stdin [--args "..."] [--pretty] [--stream] [--no-validate]',
|
|
308
|
+
'cog core promote <file.md> [outDir] [--dry-run] [--force]',
|
|
309
|
+
],
|
|
310
|
+
},
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
if (subcommand === 'new') {
|
|
314
|
+
const file = target || 'demo.md';
|
|
315
|
+
return coreNew(file, { dryRun: options.dryRun });
|
|
316
|
+
}
|
|
317
|
+
if (subcommand === 'schema') {
|
|
318
|
+
if (!target)
|
|
319
|
+
return { success: false, error: 'Usage: cog core schema <file.md>' };
|
|
320
|
+
return coreSchema(target);
|
|
321
|
+
}
|
|
322
|
+
if (subcommand === 'run') {
|
|
323
|
+
if (options.stdin) {
|
|
324
|
+
const inputText = await readAll(process.stdin);
|
|
325
|
+
return coreRunText(inputText, ctx, options);
|
|
326
|
+
}
|
|
327
|
+
if (!target)
|
|
328
|
+
return { success: false, error: 'Usage: cog core run <file.md> [--args "..."] (or use --stdin)' };
|
|
329
|
+
return coreRun(target, ctx, options);
|
|
330
|
+
}
|
|
331
|
+
if (subcommand === 'promote') {
|
|
332
|
+
if (!target)
|
|
333
|
+
return { success: false, error: 'Usage: cog core promote <file.md> [outDir]' };
|
|
334
|
+
const outDir = rest[0];
|
|
335
|
+
return corePromote(target, outDir, { dryRun: options.dryRun, force: options.force });
|
|
336
|
+
}
|
|
337
|
+
return { success: false, error: `Unknown core subcommand: ${subcommand}` };
|
|
338
|
+
}
|
package/dist/commands/index.d.ts
CHANGED
package/dist/commands/index.js
CHANGED
package/dist/commands/pipe.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import * as readline from 'node:readline';
|
|
6
6
|
import { findModule, getDefaultSearchPaths, runModule } from '../modules/index.js';
|
|
7
7
|
import { ErrorCodes, attachContext, makeErrorEnvelope } from '../errors/index.js';
|
|
8
|
+
import { writeAuditRecord } from '../audit.js';
|
|
8
9
|
export async function pipe(ctx, options) {
|
|
9
10
|
const searchPaths = getDefaultSearchPaths(ctx.cwd);
|
|
10
11
|
// Find module
|
|
@@ -22,6 +23,17 @@ export async function pipe(ctx, options) {
|
|
|
22
23
|
data: errorEnvelope,
|
|
23
24
|
};
|
|
24
25
|
}
|
|
26
|
+
if (ctx.policy?.requireV22) {
|
|
27
|
+
if (module.formatVersion !== 'v2.2') {
|
|
28
|
+
const errorEnvelope = attachContext(makeErrorEnvelope({
|
|
29
|
+
code: ErrorCodes.INVALID_INPUT,
|
|
30
|
+
message: `Certified profile requires v2.2 modules; got: ${module.formatVersion ?? 'unknown'} (${module.format})`,
|
|
31
|
+
suggestion: "Migrate the module to v2.2, or run with `--profile strict` / `--profile default`",
|
|
32
|
+
}), { module: options.module, provider: ctx.provider.name });
|
|
33
|
+
console.log(JSON.stringify(errorEnvelope));
|
|
34
|
+
return { success: false, error: errorEnvelope.error.message, data: errorEnvelope };
|
|
35
|
+
}
|
|
36
|
+
}
|
|
25
37
|
// Read from stdin
|
|
26
38
|
const rl = readline.createInterface({
|
|
27
39
|
input: process.stdin,
|
|
@@ -34,6 +46,8 @@ export async function pipe(ctx, options) {
|
|
|
34
46
|
}
|
|
35
47
|
const input = lines.join('\n');
|
|
36
48
|
try {
|
|
49
|
+
const policy = ctx.policy;
|
|
50
|
+
const startedAt = Date.now();
|
|
37
51
|
// Check if input is JSON
|
|
38
52
|
let inputData;
|
|
39
53
|
try {
|
|
@@ -42,18 +56,47 @@ export async function pipe(ctx, options) {
|
|
|
42
56
|
catch {
|
|
43
57
|
// Not JSON, use as args
|
|
44
58
|
}
|
|
59
|
+
const validate = (() => {
|
|
60
|
+
if (options.noValidate)
|
|
61
|
+
return false;
|
|
62
|
+
if (!policy)
|
|
63
|
+
return true;
|
|
64
|
+
if (policy.validate === 'off')
|
|
65
|
+
return false;
|
|
66
|
+
if (policy.validate === 'on')
|
|
67
|
+
return true;
|
|
68
|
+
return policy.profile !== 'core';
|
|
69
|
+
})();
|
|
70
|
+
const enableRepair = policy?.enableRepair ?? true;
|
|
45
71
|
// Run module with v2.2 envelope format
|
|
46
72
|
const result = await runModule(module, ctx.provider, {
|
|
47
73
|
args: inputData ? undefined : input,
|
|
48
74
|
input: inputData,
|
|
49
|
-
validateInput:
|
|
50
|
-
validateOutput:
|
|
75
|
+
validateInput: validate,
|
|
76
|
+
validateOutput: validate,
|
|
51
77
|
useV22: true, // Always use v2.2 envelope
|
|
78
|
+
enableRepair,
|
|
79
|
+
policy,
|
|
52
80
|
});
|
|
53
81
|
const output = attachContext(result, {
|
|
54
82
|
module: options.module,
|
|
55
83
|
provider: ctx.provider.name,
|
|
56
84
|
});
|
|
85
|
+
if (policy?.audit) {
|
|
86
|
+
const rec = await writeAuditRecord({
|
|
87
|
+
ts: new Date().toISOString(),
|
|
88
|
+
kind: 'pipe',
|
|
89
|
+
policy,
|
|
90
|
+
provider: ctx.provider.name,
|
|
91
|
+
module: { name: module.name, version: module.version, location: module.location, formatVersion: module.formatVersion },
|
|
92
|
+
input: { raw: input, parsed: inputData },
|
|
93
|
+
result: output,
|
|
94
|
+
notes: [`duration_ms=${Date.now() - startedAt}`],
|
|
95
|
+
});
|
|
96
|
+
if (rec) {
|
|
97
|
+
console.error(`Audit: ${rec.path}`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
57
100
|
// Output v2.2 envelope format to stdout
|
|
58
101
|
console.log(JSON.stringify(output));
|
|
59
102
|
return {
|
package/dist/commands/run.js
CHANGED
|
@@ -2,25 +2,56 @@
|
|
|
2
2
|
* cog run - Run a Cognitive Module
|
|
3
3
|
* Always returns v2.2 envelope format for consistency
|
|
4
4
|
*/
|
|
5
|
-
import
|
|
5
|
+
import * as path from 'node:path';
|
|
6
|
+
import * as fs from 'node:fs/promises';
|
|
7
|
+
import { findModule, getDefaultSearchPaths, loadSingleFileModule, runModule, runModuleStream } from '../modules/index.js';
|
|
6
8
|
import { ErrorCodes, attachContext, makeErrorEnvelope } from '../errors/index.js';
|
|
9
|
+
import { writeAuditRecord } from '../audit.js';
|
|
7
10
|
export async function run(moduleName, ctx, options = {}) {
|
|
11
|
+
// Allow "single-file modules": if moduleName resolves to a file, load it directly.
|
|
12
|
+
let module = null;
|
|
13
|
+
const candidatePath = path.resolve(ctx.cwd, moduleName);
|
|
14
|
+
try {
|
|
15
|
+
const st = await fs.stat(candidatePath);
|
|
16
|
+
if (st.isFile()) {
|
|
17
|
+
module = await loadSingleFileModule(candidatePath);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
// Not a file path, fall back to module discovery.
|
|
22
|
+
}
|
|
8
23
|
const searchPaths = getDefaultSearchPaths(ctx.cwd);
|
|
9
|
-
// Find module
|
|
10
|
-
const module = await findModule(moduleName, searchPaths);
|
|
11
24
|
if (!module) {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
25
|
+
// Find module (directory-based)
|
|
26
|
+
module = await findModule(moduleName, searchPaths);
|
|
27
|
+
if (!module) {
|
|
28
|
+
const errorEnvelope = attachContext(makeErrorEnvelope({
|
|
29
|
+
code: ErrorCodes.MODULE_NOT_FOUND,
|
|
30
|
+
message: `Module not found: ${moduleName}\nSearch paths: ${searchPaths.join(', ')}`,
|
|
31
|
+
suggestion: "Use 'cog list' to see installed modules or 'cog search' to find modules in registry",
|
|
32
|
+
}), { module: moduleName, provider: ctx.provider.name });
|
|
33
|
+
return {
|
|
34
|
+
success: false,
|
|
35
|
+
error: errorEnvelope.error.message,
|
|
36
|
+
data: errorEnvelope,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
// Progressive Complexity gate: certified profile can refuse legacy modules.
|
|
41
|
+
if (ctx.policy?.requireV22) {
|
|
42
|
+
const fv = module.formatVersion;
|
|
43
|
+
if (fv !== 'v2.2') {
|
|
44
|
+
const errorEnvelope = attachContext(makeErrorEnvelope({
|
|
45
|
+
code: ErrorCodes.INVALID_INPUT,
|
|
46
|
+
message: `Certified profile requires v2.2 modules; got: ${fv ?? 'unknown'} (${module.format})`,
|
|
47
|
+
suggestion: "Migrate the module to v2.2 (module.yaml + prompt.md + schema.json), or use `--profile strict` / `--profile default`",
|
|
48
|
+
}), { module: moduleName, provider: ctx.provider.name });
|
|
49
|
+
return { success: false, error: errorEnvelope.error.message, data: errorEnvelope };
|
|
50
|
+
}
|
|
22
51
|
}
|
|
23
52
|
try {
|
|
53
|
+
const policy = ctx.policy;
|
|
54
|
+
const startedAt = Date.now();
|
|
24
55
|
// Parse input if provided as JSON
|
|
25
56
|
let inputData;
|
|
26
57
|
if (options.input) {
|
|
@@ -40,20 +71,54 @@ export async function run(moduleName, ctx, options = {}) {
|
|
|
40
71
|
};
|
|
41
72
|
}
|
|
42
73
|
}
|
|
74
|
+
// Resolve validation/repair policy.
|
|
75
|
+
// Priority: explicit --no-validate > policy.validate > default(true)
|
|
76
|
+
const validate = (() => {
|
|
77
|
+
if (options.noValidate)
|
|
78
|
+
return false;
|
|
79
|
+
if (!policy)
|
|
80
|
+
return true;
|
|
81
|
+
if (policy.validate === 'off')
|
|
82
|
+
return false;
|
|
83
|
+
if (policy.validate === 'on')
|
|
84
|
+
return true;
|
|
85
|
+
// auto
|
|
86
|
+
return policy.profile !== 'core';
|
|
87
|
+
})();
|
|
88
|
+
const enableRepair = policy?.enableRepair ?? true;
|
|
43
89
|
if (options.stream) {
|
|
44
90
|
// Stream NDJSON events to stdout. Final exit code is determined by the end event.
|
|
45
91
|
let finalOk = null;
|
|
92
|
+
let finalResult = null;
|
|
46
93
|
for await (const ev of runModuleStream(module, ctx.provider, {
|
|
47
94
|
args: options.args,
|
|
48
95
|
input: inputData,
|
|
49
|
-
validateInput:
|
|
50
|
-
validateOutput:
|
|
96
|
+
validateInput: validate,
|
|
97
|
+
validateOutput: validate,
|
|
51
98
|
useV22: true,
|
|
99
|
+
enableRepair,
|
|
100
|
+
policy,
|
|
52
101
|
})) {
|
|
53
102
|
// Write each event as one JSON line (NDJSON).
|
|
54
103
|
process.stdout.write(JSON.stringify(ev) + '\n');
|
|
55
104
|
if (ev.type === 'end' && ev.result) {
|
|
56
105
|
finalOk = Boolean(ev.result.ok);
|
|
106
|
+
finalResult = ev.result;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
if (policy?.audit) {
|
|
110
|
+
const rec = await writeAuditRecord({
|
|
111
|
+
ts: new Date().toISOString(),
|
|
112
|
+
kind: 'run',
|
|
113
|
+
policy,
|
|
114
|
+
provider: ctx.provider.name,
|
|
115
|
+
module: { name: module.name, version: module.version, location: module.location, formatVersion: module.formatVersion },
|
|
116
|
+
input: { args: options.args, input: inputData },
|
|
117
|
+
result: { ok: finalOk === true, streamed: true, envelope: finalResult },
|
|
118
|
+
notes: [`duration_ms=${Date.now() - startedAt}`],
|
|
119
|
+
});
|
|
120
|
+
if (rec) {
|
|
121
|
+
console.error(`Audit: ${rec.path}`);
|
|
57
122
|
}
|
|
58
123
|
}
|
|
59
124
|
return {
|
|
@@ -67,14 +132,31 @@ export async function run(moduleName, ctx, options = {}) {
|
|
|
67
132
|
args: options.args,
|
|
68
133
|
input: inputData,
|
|
69
134
|
verbose: options.verbose || ctx.verbose,
|
|
70
|
-
validateInput:
|
|
71
|
-
validateOutput:
|
|
135
|
+
validateInput: validate,
|
|
136
|
+
validateOutput: validate,
|
|
72
137
|
useV22: true, // Always use v2.2 envelope
|
|
138
|
+
enableRepair,
|
|
139
|
+
policy,
|
|
73
140
|
});
|
|
74
141
|
const output = attachContext(result, {
|
|
75
142
|
module: moduleName,
|
|
76
143
|
provider: ctx.provider.name,
|
|
77
144
|
});
|
|
145
|
+
if (policy?.audit) {
|
|
146
|
+
const rec = await writeAuditRecord({
|
|
147
|
+
ts: new Date().toISOString(),
|
|
148
|
+
kind: 'run',
|
|
149
|
+
policy,
|
|
150
|
+
provider: ctx.provider.name,
|
|
151
|
+
module: { name: module.name, version: module.version, location: module.location, formatVersion: module.formatVersion },
|
|
152
|
+
input: { args: options.args, input: inputData },
|
|
153
|
+
result: output,
|
|
154
|
+
notes: [`duration_ms=${Date.now() - startedAt}`],
|
|
155
|
+
});
|
|
156
|
+
if (rec) {
|
|
157
|
+
console.error(`Audit: ${rec.path}`);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
78
160
|
// Always return full v2.2 envelope
|
|
79
161
|
return {
|
|
80
162
|
success: result.ok,
|
package/dist/commands/search.js
CHANGED
|
@@ -10,8 +10,12 @@ import { RegistryClient } from '../registry/client.js';
|
|
|
10
10
|
*/
|
|
11
11
|
export async function search(query, ctx, options = {}) {
|
|
12
12
|
const { category, limit = 20, registry } = options;
|
|
13
|
+
const registryUrl = registry ?? ctx.registryUrl;
|
|
13
14
|
try {
|
|
14
|
-
const client = new RegistryClient(
|
|
15
|
+
const client = new RegistryClient(registryUrl, {
|
|
16
|
+
timeoutMs: ctx.registryTimeoutMs,
|
|
17
|
+
maxBytes: ctx.registryMaxBytes,
|
|
18
|
+
});
|
|
15
19
|
let results;
|
|
16
20
|
if (category) {
|
|
17
21
|
// Search within category
|
|
@@ -93,7 +97,10 @@ export async function search(query, ctx, options = {}) {
|
|
|
93
97
|
*/
|
|
94
98
|
export async function listCategories(ctx, options = {}) {
|
|
95
99
|
try {
|
|
96
|
-
const client = new RegistryClient(options.registry
|
|
100
|
+
const client = new RegistryClient(options.registry ?? ctx.registryUrl, {
|
|
101
|
+
timeoutMs: ctx.registryTimeoutMs,
|
|
102
|
+
maxBytes: ctx.registryMaxBytes,
|
|
103
|
+
});
|
|
97
104
|
const categories = await client.getCategories();
|
|
98
105
|
return {
|
|
99
106
|
success: true,
|
|
@@ -119,7 +126,10 @@ export async function listCategories(ctx, options = {}) {
|
|
|
119
126
|
*/
|
|
120
127
|
export async function info(moduleName, ctx, options = {}) {
|
|
121
128
|
try {
|
|
122
|
-
const client = new RegistryClient(options.registry
|
|
129
|
+
const client = new RegistryClient(options.registry ?? ctx.registryUrl, {
|
|
130
|
+
timeoutMs: ctx.registryTimeoutMs,
|
|
131
|
+
maxBytes: ctx.registryMaxBytes,
|
|
132
|
+
});
|
|
123
133
|
const module = await client.getModule(moduleName);
|
|
124
134
|
if (!module) {
|
|
125
135
|
return {
|
package/dist/commands/update.js
CHANGED
|
@@ -95,7 +95,10 @@ export async function update(moduleName, ctx, options = {}) {
|
|
|
95
95
|
// Use undefined instead of empty string for default registry URL
|
|
96
96
|
const registryUrl = info.registryUrl || undefined;
|
|
97
97
|
// Check registry for latest version
|
|
98
|
-
const client = new RegistryClient(registryUrl
|
|
98
|
+
const client = new RegistryClient(registryUrl, {
|
|
99
|
+
timeoutMs: ctx.registryTimeoutMs,
|
|
100
|
+
maxBytes: ctx.registryMaxBytes,
|
|
101
|
+
});
|
|
99
102
|
const registryInfo = await client.getModule(registryModule);
|
|
100
103
|
if (!registryInfo) {
|
|
101
104
|
return {
|