@smartmemory/compose 0.1.6-beta → 0.1.7-beta

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.
@@ -56,6 +56,8 @@ import {
56
56
  toolGetJournalEntries,
57
57
  toolRecordCompletion,
58
58
  toolGetCompletions,
59
+ toolValidateFeature,
60
+ toolValidateProject,
59
61
  } from './compose-mcp-tools.js';
60
62
 
61
63
  // ---------------------------------------------------------------------------
@@ -322,6 +324,30 @@ const TOOLS = [
322
324
  },
323
325
  },
324
326
  },
327
+ {
328
+ name: 'validate_feature',
329
+ description: 'Cross-check a single feature against ROADMAP, vision-state, feature.json, folder contents, linked artifacts, and cross-references. Returns structured findings with severity (error/warning/info). FEATURE_NOT_FOUND emitted as a finding (not thrown) when the code matches strict regex but exists in no source.',
330
+ inputSchema: {
331
+ type: 'object',
332
+ required: ['feature_code'],
333
+ properties: {
334
+ feature_code: { type: 'string', description: 'Strict feature code, e.g. "COMP-MCP-VALIDATE"' },
335
+ external_prefixes: { type: 'array', items: { type: 'string' }, description: 'Code prefixes (e.g. ["STRAT-"]) treated as external; downgrades ORPHAN_FOLDER to info' },
336
+ feature_json_mode: { type: 'boolean', description: 'Default true. Set false to skip feature.json comparisons in legacy projects.' },
337
+ },
338
+ },
339
+ },
340
+ {
341
+ name: 'validate_project',
342
+ description: 'Run validate_feature for every code in vision-state, ROADMAP, and folders, plus cross-cutting checks (orphan folders, dangling cross-refs, CHANGELOG references, journal index drift). Returns the union of all findings.',
343
+ inputSchema: {
344
+ type: 'object',
345
+ properties: {
346
+ external_prefixes: { type: 'array', items: { type: 'string' } },
347
+ feature_json_mode: { type: 'boolean' },
348
+ },
349
+ },
350
+ },
325
351
 
326
352
  // -------------------------------------------------------------------------
327
353
  // Linker — COMP-MCP-ARTIFACT-LINKER
@@ -554,6 +580,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
554
580
  case 'get_journal_entries': result = await toolGetJournalEntries(args); break;
555
581
  case 'record_completion': result = await toolRecordCompletion(args); break;
556
582
  case 'get_completions': result = await toolGetCompletions(args); break;
583
+ case 'validate_feature': result = await toolValidateFeature(args); break;
584
+ case 'validate_project': result = await toolValidateProject(args); break;
557
585
  // agent_run removed — STRAT-DEDUP-AGENTRUN v1. Use mcp__stratum__stratum_agent_run.
558
586
  default:
559
587
  return {
@@ -5,26 +5,43 @@ import Ajv from 'ajv';
5
5
  import addFormats from 'ajv-formats';
6
6
 
7
7
  const __dirname = dirname(fileURLToPath(import.meta.url));
8
- const SCHEMA_PATH = resolve(__dirname, '../contracts/comp-obs-contract.schema.json');
8
+ const DEFAULT_SCHEMA_PATH = resolve(__dirname, '../contracts/comp-obs-contract.schema.json');
9
9
 
10
- let cached = null;
10
+ // Per-path cache. Each entry: { schema, ajv }.
11
+ const cache = new Map();
11
12
 
12
- function load() {
13
- if (cached) return cached;
14
- const schema = JSON.parse(readFileSync(SCHEMA_PATH, 'utf8'));
13
+ function load(schemaPath = DEFAULT_SCHEMA_PATH) {
14
+ if (cache.has(schemaPath)) return cache.get(schemaPath);
15
+ const schema = JSON.parse(readFileSync(schemaPath, 'utf8'));
15
16
  const ajv = new Ajv({ strict: false, allErrors: true });
16
17
  addFormats(ajv);
17
18
  ajv.addSchema(schema);
18
- cached = { schema, ajv };
19
- return cached;
19
+ const entry = { schema, ajv };
20
+ cache.set(schemaPath, entry);
21
+ return entry;
22
+ }
23
+
24
+ /**
25
+ * Load (and cache) a schema by absolute path. Returns `{ schema, ajv }`.
26
+ * Used by code that wants to compile arbitrary `$ref`s against the schema
27
+ * without going through the SchemaValidator class.
28
+ */
29
+ export function loadSchema(schemaPath) {
30
+ return load(schemaPath);
20
31
  }
21
32
 
22
33
  export class SchemaValidator {
23
- constructor() {
24
- const { schema, ajv } = load();
34
+ /**
35
+ * @param {string} [schemaPath] Absolute path to a JSON Schema file.
36
+ * Default is the comp-obs-contract schema (back-compat for existing
37
+ * callers that pass no args).
38
+ */
39
+ constructor(schemaPath = DEFAULT_SCHEMA_PATH) {
40
+ const { schema, ajv } = load(schemaPath);
25
41
  this.schema = schema;
26
42
  this.ajv = ajv;
27
43
  this._validators = new Map();
44
+ this._rootValidator = null;
28
45
  }
29
46
 
30
47
  _getValidator(defName) {
@@ -39,11 +56,35 @@ export class SchemaValidator {
39
56
  return v;
40
57
  }
41
58
 
59
+ /**
60
+ * Validate `obj` against `schema.definitions[defName]`. Used by the
61
+ * comp-obs-contract code paths.
62
+ */
42
63
  validate(defName, obj) {
43
64
  const v = this._getValidator(defName);
44
65
  const valid = v(obj);
45
66
  return { valid: !!valid, errors: valid ? [] : (v.errors || []) };
46
67
  }
68
+
69
+ /**
70
+ * Validate `obj` against the schema's root (no $defs/$ref indirection).
71
+ * Used by feature-json / vision-state / roadmap-row schemas, which are
72
+ * top-level shapes without nested definitions.
73
+ */
74
+ validateRoot(obj) {
75
+ if (!this._rootValidator) {
76
+ // ajv.getSchema by $id returns the root validator if the schema was
77
+ // added via addSchema (which load() does).
78
+ let v = this.schema.$id ? this.ajv.getSchema(this.schema.$id) : null;
79
+ if (!v) v = this.ajv.compile(this.schema);
80
+ this._rootValidator = v;
81
+ }
82
+ const v = this._rootValidator;
83
+ const valid = v(obj);
84
+ return { valid: !!valid, errors: valid ? [] : (v.errors || []) };
85
+ }
47
86
  }
48
87
 
88
+ // Back-compat export — points at the comp-obs schema version. Existing
89
+ // callers consuming SCHEMA_VERSION continue to work unchanged.
49
90
  export const SCHEMA_VERSION = load().schema.version;