@mnemom/mnemom 0.7.2 → 0.8.0

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.
@@ -1,543 +1,30 @@
1
- import * as fs from "node:fs";
2
- import * as path from "node:path";
3
- import { requireAgent } from "../lib/config.js";
4
- import { fmt } from "../lib/format.js";
5
- import { askYesNo, isInteractive } from "../lib/prompt.js";
6
- import { getPolicy, publishPolicy } from "../lib/api.js";
7
- import { requireAuth } from "../lib/auth.js";
8
- import { validatePolicySchema, evaluatePolicy, } from "@mnemom/policy-engine";
9
- // ============================================================================
10
- // YAML parsing (minimal — parse structured YAML without external dep)
11
- // ============================================================================
12
1
  /**
13
- * Parse a policy file (JSON or YAML).
14
- * For YAML, we use a minimal parser that handles the policy schema.
2
+ * The `policy` command group has been removed in UC-9.
3
+ * Policy capabilities are now part of the alignment card.
4
+ *
5
+ * Each exported function is a stub that prints a helpful migration message.
6
+ * The exports are preserved so index.ts imports don't break.
15
7
  */
16
- function parsePolicyFile(raw, filePath) {
17
- // Try JSON first
18
- try {
19
- return JSON.parse(raw);
20
- }
21
- catch {
22
- // Not JSON — try YAML via JSON conversion (user must install js-yaml or use JSON)
23
- try {
24
- // Dynamic import would be async; for simplicity, require JSON for now
25
- // and provide clear error message
26
- throw new Error("not json");
27
- }
28
- catch {
29
- throw new Error(`Failed to parse ${path.basename(filePath)}. ` +
30
- `Policy files must be valid JSON. ` +
31
- `YAML support coming soon — convert with: npx js-yaml ${filePath}`);
32
- }
33
- }
34
- }
35
- // ============================================================================
36
- // Policy display
37
- // ============================================================================
38
- function displayPolicy(policy) {
39
- const p = policy.policy_json;
40
- console.log(fmt.header("Policy"));
41
- console.log();
42
- console.log(fmt.label(" Policy ID:", ` ${policy.id}`));
43
- console.log(fmt.label(" Name: ", ` ${policy.name}`));
44
- if (policy.description) {
45
- console.log(fmt.label(" Desc: ", ` ${policy.description}`));
46
- }
47
- console.log(fmt.label(" Version: ", ` ${policy.version}`));
48
- console.log(fmt.label(" Created: ", ` ${policy.created_at}`));
49
- // Capability mappings
50
- const mappings = p.capability_mappings;
51
- if (mappings && Object.keys(mappings).length > 0) {
52
- console.log(fmt.section("Capability Mappings"));
53
- console.log();
54
- for (const [name, mapping] of Object.entries(mappings)) {
55
- console.log(` ${fmt.success(name)}`);
56
- if (mapping.description) {
57
- console.log(` ${mapping.description}`);
58
- }
59
- console.log(` Tools: ${mapping.tools.join(", ")}`);
60
- console.log(` Card actions: ${mapping.card_actions.join(", ")}`);
61
- }
62
- }
63
- // Forbidden rules
64
- const forbidden = p.forbidden;
65
- if (Array.isArray(forbidden) && forbidden.length > 0) {
66
- console.log(fmt.section("Forbidden Rules"));
67
- console.log();
68
- for (const rule of forbidden) {
69
- console.log(` ${fmt.error(`${rule.pattern} [${rule.severity}]`)}`);
70
- console.log(` ${rule.reason}`);
71
- }
72
- }
73
- // Escalation triggers
74
- const triggers = p.escalation_triggers;
75
- if (Array.isArray(triggers) && triggers.length > 0) {
76
- console.log(fmt.section("Escalation Triggers"));
77
- console.log();
78
- for (const trigger of triggers) {
79
- console.log(` ${fmt.warn(`${trigger.condition} -> ${trigger.action}`)}`);
80
- console.log(` ${trigger.reason}`);
81
- }
82
- }
83
- // Defaults
84
- const defaults = p.defaults;
85
- if (defaults) {
86
- console.log(fmt.section("Defaults"));
87
- console.log();
88
- console.log(fmt.label(" Unmapped action: ", defaults.unmapped_tool_action));
89
- console.log(fmt.label(" Unmapped severity:", ` ${defaults.unmapped_severity}`));
90
- console.log(fmt.label(" Fail open: ", ` ${defaults.fail_open}`));
91
- }
92
- console.log();
8
+ function removed(oldCmd, newCmd) {
9
+ console.error(`\nThe \`mnemom ${oldCmd}\` command has been removed.\n`);
10
+ console.error(`Use \`mnemom ${newCmd}\` instead.\n`);
11
+ process.exit(1);
93
12
  }
94
- // ============================================================================
95
- // Subcommands
96
- // ============================================================================
97
- /**
98
- * smoltbot policy init — scaffold a policy.json with commented examples
99
- */
100
13
  export async function policyInitCommand() {
101
- const outputPath = path.resolve("policy.json");
102
- if (fs.existsSync(outputPath)) {
103
- console.log("\n" + fmt.warn("policy.json already exists in current directory") + "\n");
104
- if (isInteractive()) {
105
- const overwrite = await askYesNo("Overwrite?", false);
106
- if (!overwrite) {
107
- console.log("\nCancelled.\n");
108
- return;
109
- }
110
- }
111
- else {
112
- process.exit(1);
113
- }
114
- }
115
- const scaffold = {
116
- meta: {
117
- schema_version: "1.0",
118
- name: "my-policy",
119
- description: "Policy for my agent",
120
- scope: "agent",
121
- },
122
- capability_mappings: {
123
- web_fetch: {
124
- description: "Web browsing and fetching",
125
- tools: [
126
- "WebFetch",
127
- "WebSearch",
128
- "mcp__browser__*",
129
- "mcp__chrome-devtools__navigate_page",
130
- ],
131
- card_actions: ["web_fetch", "web_browse"],
132
- },
133
- file_system: {
134
- description: "File system operations",
135
- tools: ["Read", "Write", "Edit", "Glob"],
136
- card_actions: ["file_read", "file_write"],
137
- },
138
- code_execution: {
139
- description: "Code and shell execution",
140
- tools: ["Bash", "mcp__*__evaluate_script", "mcp__*__execute*"],
141
- card_actions: ["code_execution"],
142
- },
143
- },
144
- forbidden: [
145
- {
146
- pattern: "mcp__*__delete*",
147
- reason: "Destructive deletion forbidden",
148
- severity: "critical",
149
- },
150
- ],
151
- escalation_triggers: [
152
- {
153
- condition: "tool_matches('*payment*')",
154
- action: "escalate",
155
- reason: "Payment tools require human approval",
156
- },
157
- ],
158
- defaults: {
159
- unmapped_tool_action: "warn",
160
- unmapped_severity: "medium",
161
- fail_open: true,
162
- },
163
- };
164
- const tmpPath = `${outputPath}.${process.pid}.tmp`;
165
- fs.writeFileSync(tmpPath, JSON.stringify(scaffold, null, 2) + "\n");
166
- fs.renameSync(tmpPath, outputPath);
167
- console.log(fmt.success(`Created policy.json`));
168
- console.log(fmt.label(" Path:", ` ${outputPath}`));
169
- console.log("\nEdit the file, then validate with:\n");
170
- console.log(" smoltbot policy validate policy.json\n");
14
+ removed("policy init", "card validate <file.yaml>");
171
15
  }
172
- /**
173
- * smoltbot policy validate <file> — local-only validation
174
- */
175
- export async function policyValidateCommand(file) {
176
- const filePath = path.resolve(file);
177
- if (!fs.existsSync(filePath)) {
178
- console.log("\n" + fmt.error(`File not found: ${filePath}`) + "\n");
179
- process.exit(1);
180
- }
181
- let raw;
182
- try {
183
- raw = fs.readFileSync(filePath, "utf-8");
184
- }
185
- catch (e) {
186
- const msg = e instanceof Error ? e.message : String(e);
187
- console.log("\n" + fmt.error(`Could not read file: ${msg}`) + "\n");
188
- process.exit(1);
189
- }
190
- let parsed;
191
- try {
192
- parsed = parsePolicyFile(raw, filePath);
193
- }
194
- catch (e) {
195
- const msg = e instanceof Error ? e.message : String(e);
196
- console.log("\n" + fmt.error(msg) + "\n");
197
- process.exit(1);
198
- }
199
- const result = validatePolicySchema(parsed);
200
- console.log(fmt.header("Policy Validation Report"));
201
- console.log();
202
- console.log(fmt.label(" File:", ` ${filePath}`));
203
- console.log();
204
- if (result.valid) {
205
- console.log(fmt.success("Policy schema is valid"));
206
- const meta = parsed.meta;
207
- if (meta) {
208
- console.log(fmt.label(" Name: ", ` ${meta.name}`));
209
- console.log(fmt.label(" Scope:", ` ${meta.scope}`));
210
- }
211
- const mappings = parsed.capability_mappings;
212
- if (mappings) {
213
- console.log(fmt.label(" Capabilities:", ` ${Object.keys(mappings).length}`));
214
- }
215
- }
216
- else {
217
- for (const error of result.errors) {
218
- console.log(fmt.error(`${error.path}: ${error.message}`));
219
- }
220
- console.log();
221
- console.log(fmt.error(`${result.errors.length} validation error(s)`) + "\n");
222
- process.exit(1);
223
- }
224
- console.log();
16
+ export async function policyValidateCommand(_file) {
17
+ removed("policy validate", "card validate <file.yaml>");
225
18
  }
226
- /**
227
- * smoltbot policy publish <file> — validate + upload to API
228
- */
229
- export async function policyPublishCommand(file, agentName) {
230
- const agent = await requireAgent(agentName);
231
- const filePath = path.resolve(file);
232
- if (!fs.existsSync(filePath)) {
233
- console.log("\n" + fmt.error(`File not found: ${filePath}`) + "\n");
234
- process.exit(1);
235
- }
236
- let raw;
237
- try {
238
- raw = fs.readFileSync(filePath, "utf-8");
239
- }
240
- catch (e) {
241
- const msg = e instanceof Error ? e.message : String(e);
242
- console.log("\n" + fmt.error(`Could not read file: ${msg}`) + "\n");
243
- process.exit(1);
244
- }
245
- let parsed;
246
- try {
247
- parsed = parsePolicyFile(raw, filePath);
248
- }
249
- catch (e) {
250
- const msg = e instanceof Error ? e.message : String(e);
251
- console.log("\n" + fmt.error(msg) + "\n");
252
- process.exit(1);
253
- }
254
- // Validate locally first
255
- const validation = validatePolicySchema(parsed);
256
- console.log(fmt.header("Policy Validation"));
257
- console.log();
258
- if (!validation.valid) {
259
- for (const error of validation.errors) {
260
- console.log(fmt.error(`${error.path}: ${error.message}`));
261
- }
262
- console.log();
263
- console.log(fmt.error("Validation failed. Fix the errors above before publishing.") + "\n");
264
- process.exit(1);
265
- }
266
- console.log(fmt.success("Policy schema is valid"));
267
- console.log();
268
- // Require authentication
269
- await requireAuth();
270
- // Confirm
271
- if (isInteractive()) {
272
- const meta = parsed.meta;
273
- const confirm = await askYesNo(`Publish policy "${meta?.name || "unnamed"}" for agent ${agent.agentId}?`, false);
274
- if (!confirm) {
275
- console.log("\nPublish cancelled.\n");
276
- return;
277
- }
278
- }
279
- // Publish
280
- try {
281
- console.log("\nPublishing policy...");
282
- const result = await publishPolicy(agent.agentId, parsed);
283
- console.log(fmt.success("Policy published successfully!"));
284
- console.log(fmt.label(" Policy ID:", ` ${result.id}`));
285
- console.log(fmt.label(" Version: ", ` ${result.version}`) + "\n");
286
- }
287
- catch (error) {
288
- const message = error instanceof Error ? error.message : String(error);
289
- console.log("\n" + fmt.error(`Failed to publish policy: ${message}`) + "\n");
290
- process.exit(1);
291
- }
19
+ export async function policyPublishCommand(_file, _agentName) {
20
+ removed("policy publish", "card publish <file.yaml>");
292
21
  }
293
- /**
294
- * smoltbot policy list list active policies for current agent
295
- */
296
- export async function policyListCommand(agentName) {
297
- const agent = await requireAgent(agentName);
298
- console.log("\nFetching policy...\n");
299
- try {
300
- const response = await getPolicy(agent.agentId);
301
- if (!response || !response.policy) {
302
- console.log(fmt.warn("No active policy"));
303
- console.log("\nCreate a policy with:\n");
304
- console.log(" smoltbot policy init");
305
- console.log(" smoltbot policy publish policy.json\n");
306
- return;
307
- }
308
- displayPolicy(response.policy);
309
- }
310
- catch (error) {
311
- const message = error instanceof Error ? error.message : String(error);
312
- console.log("\n" + fmt.error(`Failed to fetch policy: ${message}`) + "\n");
313
- process.exit(1);
314
- }
22
+ export async function policyListCommand(_agentName) {
23
+ removed("policy list", "card show");
315
24
  }
316
- /**
317
- * smoltbot policy test <file> --against-traces — dry-run against historical traces
318
- */
319
- export async function policyTestCommand(file, agentName) {
320
- const agent = await requireAgent(agentName);
321
- const filePath = path.resolve(file);
322
- if (!fs.existsSync(filePath)) {
323
- console.log("\n" + fmt.error(`File not found: ${filePath}`) + "\n");
324
- process.exit(1);
325
- }
326
- let raw;
327
- try {
328
- raw = fs.readFileSync(filePath, "utf-8");
329
- }
330
- catch (e) {
331
- const msg = e instanceof Error ? e.message : String(e);
332
- console.log("\n" + fmt.error(`Could not read file: ${msg}`) + "\n");
333
- process.exit(1);
334
- }
335
- let parsed;
336
- try {
337
- parsed = parsePolicyFile(raw, filePath);
338
- }
339
- catch (e) {
340
- const msg = e instanceof Error ? e.message : String(e);
341
- console.log("\n" + fmt.error(msg) + "\n");
342
- process.exit(1);
343
- }
344
- // Validate first
345
- const validation = validatePolicySchema(parsed);
346
- if (!validation.valid) {
347
- console.log(fmt.error("Policy validation failed:"));
348
- for (const error of validation.errors) {
349
- console.log(fmt.error(` ${error.path}: ${error.message}`));
350
- }
351
- console.log();
352
- process.exit(1);
353
- }
354
- // Require authentication
355
- await requireAuth();
356
- console.log(`\nTesting policy against historical traces for agent ${agent.agentId}...\n`);
357
- try {
358
- const { testPolicyHistorical } = await import("../lib/api.js");
359
- const result = await testPolicyHistorical(agent.agentId, parsed);
360
- console.log(fmt.header("Policy Test Results"));
361
- console.log();
362
- console.log(fmt.label(" Policy: ", ` ${result.policy_name}`));
363
- console.log(fmt.label(" Total traces:", ` ${result.total_traces}`));
364
- console.log();
365
- const s = result.summary;
366
- if (s.pass > 0)
367
- console.log(fmt.success(`${s.pass} passed`));
368
- if (s.warn > 0)
369
- console.log(fmt.warn(`${s.warn} warnings`));
370
- if (s.fail > 0)
371
- console.log(fmt.error(`${s.fail} failed`));
372
- if (s.skipped > 0)
373
- console.log(fmt.label(" Skipped:", ` ${s.skipped} (no tools)`));
374
- // Show first few failures
375
- const failures = result.results.filter((r) => r.verdict === "fail");
376
- if (failures.length > 0) {
377
- console.log(fmt.section("Failed Traces (first 5)"));
378
- console.log();
379
- for (const f of failures.slice(0, 5)) {
380
- console.log(fmt.error(` ${f.trace_id}`));
381
- for (const v of f.violations ?? []) {
382
- console.log(` ${v.type}: ${v.tool} — ${v.reason}`);
383
- }
384
- }
385
- }
386
- console.log();
387
- }
388
- catch (error) {
389
- const message = error instanceof Error ? error.message : String(error);
390
- console.log("\n" + fmt.error(`Failed to test policy: ${message}`) + "\n");
391
- process.exit(1);
392
- }
25
+ export async function policyTestCommand(_file, _agentName) {
26
+ removed("policy test", "card evaluate <file.yaml> --tools <tools>");
393
27
  }
394
- /**
395
- * smoltbot policy evaluate <policy-file> --card <card-file> --tools <tools> — local CI/CD evaluation
396
- *
397
- * Runs entirely locally using the embedded policy engine. No API key needed.
398
- */
399
- export async function policyEvaluateCommand(file, options) {
400
- // 1. Read + validate policy file
401
- const policyPath = path.resolve(file);
402
- if (!fs.existsSync(policyPath)) {
403
- console.log("\n" + fmt.error(`Policy file not found: ${policyPath}`) + "\n");
404
- process.exit(1);
405
- }
406
- let policyRaw;
407
- try {
408
- policyRaw = fs.readFileSync(policyPath, "utf-8");
409
- }
410
- catch (e) {
411
- const msg = e instanceof Error ? e.message : String(e);
412
- console.log("\n" + fmt.error(`Could not read policy file: ${msg}`) + "\n");
413
- process.exit(1);
414
- }
415
- let policyParsed;
416
- try {
417
- policyParsed = parsePolicyFile(policyRaw, policyPath);
418
- }
419
- catch (e) {
420
- const msg = e instanceof Error ? e.message : String(e);
421
- console.log("\n" + fmt.error(msg) + "\n");
422
- process.exit(1);
423
- }
424
- const validation = validatePolicySchema(policyParsed);
425
- if (!validation.valid) {
426
- console.log(fmt.error("Policy validation failed:"));
427
- for (const error of validation.errors) {
428
- console.log(fmt.error(` ${error.path}: ${error.message}`));
429
- }
430
- console.log();
431
- process.exit(1);
432
- }
433
- // 2. Read + parse card file
434
- let cardContent = {};
435
- if (options.card) {
436
- const cardPath = path.resolve(options.card);
437
- if (!fs.existsSync(cardPath)) {
438
- console.log("\n" + fmt.error(`Card file not found: ${cardPath}`) + "\n");
439
- process.exit(1);
440
- }
441
- try {
442
- const cardRaw = fs.readFileSync(cardPath, "utf-8");
443
- cardContent = JSON.parse(cardRaw);
444
- }
445
- catch (e) {
446
- const msg = e instanceof Error ? e.message : String(e);
447
- console.log("\n" + fmt.error(`Could not read card file: ${msg}`) + "\n");
448
- process.exit(1);
449
- }
450
- }
451
- // 3. Parse tool list from --tools or --tool-manifest
452
- let tools = [];
453
- if (options.tools) {
454
- tools = options.tools.split(",").map((t) => ({ name: t.trim() })).filter((t) => t.name);
455
- }
456
- else if (options.toolManifest) {
457
- const manifestPath = path.resolve(options.toolManifest);
458
- if (!fs.existsSync(manifestPath)) {
459
- console.log("\n" + fmt.error(`Tool manifest file not found: ${manifestPath}`) + "\n");
460
- process.exit(1);
461
- }
462
- try {
463
- const manifestRaw = fs.readFileSync(manifestPath, "utf-8");
464
- const manifest = JSON.parse(manifestRaw);
465
- if (Array.isArray(manifest)) {
466
- tools = manifest.map((t) => typeof t === "string" ? { name: t } : { name: t.name });
467
- }
468
- }
469
- catch (e) {
470
- const msg = e instanceof Error ? e.message : String(e);
471
- console.log("\n" + fmt.error(`Could not read tool manifest: ${msg}`) + "\n");
472
- process.exit(1);
473
- }
474
- }
475
- if (tools.length === 0) {
476
- console.log("\n" + fmt.error("No tools specified. Use --tools or --tool-manifest") + "\n");
477
- process.exit(1);
478
- }
479
- // 4. Run evaluation
480
- const result = evaluatePolicy({
481
- context: "cicd",
482
- policy: policyParsed,
483
- card: cardContent,
484
- tools,
485
- });
486
- // 5. Display results
487
- console.log(fmt.header("Policy Evaluation Report"));
488
- console.log();
489
- console.log(fmt.label(" Policy:", ` ${policyParsed.meta?.name ?? "unknown"}`));
490
- console.log(fmt.label(" Context:", " cicd"));
491
- console.log(fmt.label(" Tools:", ` ${tools.length} (${tools.map((t) => t.name).join(", ")})`));
492
- console.log();
493
- // Verdict
494
- if (result.verdict === "pass") {
495
- console.log(fmt.success("PASS — all tools comply with policy"));
496
- }
497
- else if (result.verdict === "warn") {
498
- console.log(fmt.warn("WARN — policy warnings detected"));
499
- }
500
- else {
501
- console.log(fmt.error("FAIL — policy violations detected"));
502
- }
503
- console.log();
504
- // Violations
505
- if (result.violations.length > 0) {
506
- console.log(fmt.section("Violations"));
507
- console.log();
508
- for (const v of result.violations) {
509
- console.log(fmt.error(` ${v.tool} [${v.severity}] — ${v.type}`));
510
- console.log(` ${v.reason}`);
511
- }
512
- }
513
- // Warnings
514
- if (result.warnings.length > 0) {
515
- console.log(fmt.section("Warnings"));
516
- console.log();
517
- for (const w of result.warnings) {
518
- console.log(fmt.warn(` ${w.tool} — ${w.type}`));
519
- console.log(` ${w.reason}`);
520
- }
521
- }
522
- // Coverage
523
- const cov = result.coverage;
524
- console.log(fmt.section("Coverage"));
525
- console.log();
526
- console.log(fmt.label(" Card actions:", ` ${cov.total_card_actions}`));
527
- console.log(fmt.label(" Mapped: ", ` ${cov.mapped_card_actions.length}`));
528
- console.log(fmt.label(" Unmapped: ", ` ${cov.unmapped_card_actions.length}`));
529
- console.log(fmt.label(" Coverage: ", ` ${cov.coverage_pct.toFixed(1)}%`));
530
- if (cov.unmapped_card_actions.length > 0) {
531
- console.log(fmt.label(" Unmapped list:", ` ${cov.unmapped_card_actions.join(", ")}`));
532
- }
533
- console.log();
534
- console.log(fmt.label(" Duration:", ` ${result.duration_ms}ms`));
535
- console.log();
536
- // 6. Exit code
537
- if (result.verdict === "fail") {
538
- process.exit(1);
539
- }
540
- if (options.strict && result.verdict === "warn") {
541
- process.exit(1);
542
- }
28
+ export async function policyEvaluateCommand(_file, _options) {
29
+ removed("policy evaluate", "card evaluate <file.yaml> --tools <tools>");
543
30
  }
@@ -0,0 +1,14 @@
1
+ export interface ValidationCheck {
2
+ name: string;
3
+ passed: boolean;
4
+ message: string;
5
+ }
6
+ /**
7
+ * Validate a protection card object.
8
+ * Schema: mode, thresholds, screen_surfaces, trusted_sources
9
+ */
10
+ export declare function validateProtectionCard(card: Record<string, unknown>): ValidationCheck[];
11
+ export declare function protectionShowCommand(agentName?: string): Promise<void>;
12
+ export declare function protectionPublishCommand(file: string, agentName?: string): Promise<void>;
13
+ export declare function protectionValidateCommand(file: string): Promise<void>;
14
+ export declare function protectionEditCommand(agentName?: string): Promise<void>;