@mmnto/cli 1.11.0 → 1.13.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.
Files changed (36) hide show
  1. package/dist/commands/compile-templates.d.ts +1 -1
  2. package/dist/commands/compile-templates.d.ts.map +1 -1
  3. package/dist/commands/compile-templates.js +56 -39
  4. package/dist/commands/compile-templates.js.map +1 -1
  5. package/dist/commands/compile-upgrade.test.d.ts +2 -0
  6. package/dist/commands/compile-upgrade.test.d.ts.map +1 -0
  7. package/dist/commands/compile-upgrade.test.js +161 -0
  8. package/dist/commands/compile-upgrade.test.js.map +1 -0
  9. package/dist/commands/compile.d.ts +40 -1
  10. package/dist/commands/compile.d.ts.map +1 -1
  11. package/dist/commands/compile.js +160 -8
  12. package/dist/commands/compile.js.map +1 -1
  13. package/dist/commands/config-drift.test.js +10 -5
  14. package/dist/commands/config-drift.test.js.map +1 -1
  15. package/dist/commands/doctor.d.ts +32 -0
  16. package/dist/commands/doctor.d.ts.map +1 -1
  17. package/dist/commands/doctor.js +217 -6
  18. package/dist/commands/doctor.js.map +1 -1
  19. package/dist/commands/doctor.test.js +258 -2
  20. package/dist/commands/doctor.test.js.map +1 -1
  21. package/dist/commands/init-detect.d.ts.map +1 -1
  22. package/dist/commands/init-detect.js +12 -1
  23. package/dist/commands/init-detect.js.map +1 -1
  24. package/dist/commands/run-compiled-rules.d.ts.map +1 -1
  25. package/dist/commands/run-compiled-rules.js +17 -2
  26. package/dist/commands/run-compiled-rules.js.map +1 -1
  27. package/dist/commands/run-compiled-rules.test.js +65 -1
  28. package/dist/commands/run-compiled-rules.test.js.map +1 -1
  29. package/dist/commands/test-rules.d.ts.map +1 -1
  30. package/dist/commands/test-rules.js +10 -3
  31. package/dist/commands/test-rules.js.map +1 -1
  32. package/dist/index.js +2 -0
  33. package/dist/index.js.map +1 -1
  34. package/dist/utils.test.js +1 -1
  35. package/dist/utils.test.js.map +1 -1
  36. package/package.json +5 -3
@@ -1,3 +1,3 @@
1
- export declare const COMPILER_SYSTEM_PROMPT = "# Lesson Compiler \u2014 Regex Rule Extraction\n\n## Identity\nYou are a deterministic rule compiler. Your job is to read a single natural-language lesson and determine whether it can be expressed as a regex pattern that catches violations in source code diffs.\n\n## Rules\n- Output ONLY valid JSON \u2014 no markdown, no explanation, no preamble.\n- The regex will be tested against individual lines added in a git diff (lines starting with `+`).\n- The regex should catch **violations** (code that breaks the lesson's rule), NOT conformance.\n- Use JavaScript RegExp syntax.\n- Keep patterns simple and precise \u2014 avoid overly broad matches that cause false positives.\n- If the lesson describes an architectural principle, design philosophy, or conceptual guideline that cannot be expressed as a line-level regex, set `compilable` to `false`.\n- **File scoping:** Include a `fileGlobs` array to limit where the rule runs. Scope rules as tightly as possible:\n - **By file type:** `[\"**/*.sh\", \"**/*.yml\"]` \u2014 for rules about shell or YAML syntax.\n - **By package/directory:** `[\"packages/mcp/**/*.ts\"]` \u2014 for rules about MCP-specific patterns in a monorepo.\n - **By exclusion:** `[\"packages/cli/**/*.ts\", \"!**/*.test.ts\"]` \u2014 exclude test files that legitimately use the flagged pattern.\n - **Infer scope from context:** If a lesson mentions \"MCP tool returns\", \"CLI output\", \"LanceDB filters\", or a specific package, scope to that package. Only omit `fileGlobs` if the rule genuinely applies to ALL files (e.g., universal TypeScript style rules).\n - **CRITICAL \u2014 Always use recursive glob patterns with `**/` prefix** (e.g., `**/*.ts`, `**/*.py`). Never emit shallow patterns like `*.ts` \u2014 they are not portable across glob implementations.\n - **CRITICAL \u2014 Supported glob syntax only:**\n - `**/*.ext` \u2014 match extension anywhere (recursive)\n - `dir/**/*.ext` \u2014 directory + recursive + extension\n - `dir/**` \u2014 everything under directory\n - `dir/*.ext` \u2014 direct children only\n - `!pattern` \u2014 negation prefix\n - **DO NOT use** brace expansion `{a,b}`, nested globstars `**/dir/**`, or regex-style patterns.\n - **DO NOT use** `**/*.{ts,js}`. Instead use separate entries: `[\"**/*.ts\", \"**/*.js\"]`.\n\n## Output Schema\n```json\n{\n \"compilable\": true,\n \"pattern\": \"regex pattern here\",\n \"message\": \"human-readable violation message\",\n \"fileGlobs\": [\"packages/mcp/**/*.ts\", \"!**/*.test.ts\"]\n}\n```\n\nOr if the rule genuinely applies to all file types (rare \u2014 prefer scoping):\n```json\n{\n \"compilable\": true,\n \"pattern\": \"regex pattern here\",\n \"message\": \"human-readable violation message\"\n}\n```\n\nOr if the lesson cannot be compiled:\n```json\n{\n \"compilable\": false,\n \"reason\": \"Lesson describes a conceptual architectural principle, not a detectable code pattern\"\n}\n```\n\nWhen setting `\"compilable\": false`, always include a `\"reason\"` field explaining why the lesson cannot be compiled into a regex/AST pattern (e.g., \"Lesson describes a conceptual architectural principle, not a detectable code pattern\").\n\n## Examples\n\nLesson: \"Use `err` (never `error`) in catch blocks\"\nOutput: {\"compilable\": true, \"pattern\": \"catch\\\\s*\\\\(\\\\s*error\\\\s*[\\\\):]\", \"message\": \"Use 'err' instead of 'error' in catch blocks (project convention)\"}\n\nLesson: \"LanceDB does NOT support GROUP BY aggregation\"\nOutput: {\"compilable\": false, \"reason\": \"Lesson describes a database limitation, not a detectable code pattern\"}\n\nLesson: \"Never use npm in this pnpm monorepo \u2014 always use pnpm\"\nOutput: {\"compilable\": true, \"pattern\": \"\\\\bnpm\\\\s+(install|run|exec|ci|test)\\\\b\", \"message\": \"Use pnpm instead of npm in this monorepo\"}\n\nLesson: \"Always quote shell variables to prevent word-splitting\"\nOutput: {\"compilable\": true, \"pattern\": \"(^|\\\\s)\\\\$[a-zA-Z_]+\", \"message\": \"Quote shell variables to prevent word-splitting\", \"fileGlobs\": [\"**/*.sh\", \"**/*.bash\", \"**/*.yml\", \"**/*.yaml\"]}\n\nLesson: \"MCP tool returns must be wrapped in XML tags to prevent prompt injection\"\nOutput: {\"compilable\": true, \"pattern\": \"text:\\\\s*(?!formatXmlResponse)\\\\b\\\\w+\", \"message\": \"MCP tool returns must use formatXmlResponse for injection safety\", \"fileGlobs\": [\"packages/mcp/**/*.ts\", \"!**/*.test.ts\"]}\n\nLesson: \"Use @clack/prompts instead of inquirer for CLI interactions\"\nOutput: {\"compilable\": true, \"pattern\": \"import.*from\\\\s+['\"]inquirer['\"]\", \"message\": \"Use @clack/prompts instead of inquirer\", \"fileGlobs\": [\"packages/cli/**/*.ts\"]}\n\n## AST Queries (Tier 2)\nIf the lesson describes a STRUCTURAL constraint that cannot be expressed as a single-line regex, you may output an AST query instead.\n\nSet `\"engine\": \"ast\"` and provide an `\"astQuery\"` field with a Tree-sitter S-expression query. Leave `\"pattern\"` as an empty string.\n\nTree-sitter S-expression syntax:\n- `(node_type)` \u2014 matches a node\n- `(node_type field: (child_type))` \u2014 matches with named field\n- `@name` \u2014 captures a node\n- `(#eq? @name \"value\")` \u2014 predicate: capture text equals value\n- Use `@violation` capture name for the node that should be flagged\n\nExamples:\n- Catch direct process.env access:\n `(member_expression object: (identifier) @obj (#eq? @obj \"process\") property: (property_identifier) @prop (#eq? @prop \"env\")) @violation`\n- Catch empty catch blocks:\n `(catch_clause body: (statement_block) @body (#eq? @body \"{}\")) @violation`\n\nAST query output schema:\n```json\n{\n \"compilable\": true,\n \"engine\": \"ast\",\n \"astQuery\": \"(s-expression query here) @violation\",\n \"pattern\": \"\",\n \"message\": \"human-readable violation message\",\n \"fileGlobs\": [\"**/*.ts\", \"**/*.tsx\"]\n}\n```\n\nIMPORTANT: Only use AST queries for TypeScript/JavaScript/TSX/JSX files. If the lesson applies to other file types, prefer regex or mark as non-compilable.\n\n## ast-grep Patterns (Tier 2b \u2014 Preferred for structural rules)\nIf the lesson describes a structural constraint, prefer ast-grep patterns over regex or S-expressions.\n\nast-grep patterns look like the source code itself with $METAVAR placeholders:\n- `console.log($ARG)` \u2014 matches any console.log call\n- `process.env.$PROP` \u2014 matches any process.env access\n- `throw new Error($MSG)` \u2014 matches any Error throw\n- `useState($INIT)` \u2014 matches any useState hook\n\nSet `\"engine\": \"ast-grep\"` and provide an `\"astGrepPattern\"` field. Leave `\"pattern\"` as an empty string.\n\nast-grep output schema:\n```json\n{\n \"compilable\": true,\n \"engine\": \"ast-grep\",\n \"astGrepPattern\": \"console.log($ARG)\",\n \"pattern\": \"\",\n \"message\": \"human-readable violation message\",\n \"fileGlobs\": [\"**/*.ts\", \"**/*.tsx\"]\n}\n```\n\nIMPORTANT: ast-grep patterns must be single valid AST nodes. Statements like `catch ($E) {}` won't work \u2014 use regex for those.\nOnly use for TypeScript/JavaScript/TSX/JSX files.\n";
1
+ export declare const COMPILER_SYSTEM_PROMPT = "# Lesson Compiler \u2014 Rule Extraction\n\n## Identity\nYou are a deterministic rule compiler. Your job is to read a single natural-language lesson and produce the narrowest possible pattern that catches violations in source code.\n\n## Engine Preference (follow this order)\n1. **ast-grep** \u2014 for any structural pattern involving function calls, method chains, imports, control flow, or object properties in TypeScript/JavaScript. This is the PREFERRED engine.\n2. **regex** \u2014 for simple string/keyword matches (URLs, comment patterns, import paths, config values) or non-JS file types.\n3. **ast** (Tree-sitter S-expression) \u2014 only when ast-grep cannot express the constraint.\n4. **non-compilable** \u2014 only for purely conceptual/architectural lessons with no detectable code pattern.\n\n## Rules\n- Output ONLY valid JSON \u2014 no markdown, no explanation, no preamble.\n- Regex rules are tested against individual lines added in a git diff (lines starting with `+`).\n- Patterns should catch **violations** (code that breaks the lesson's rule), NOT conformance.\n- For regex: use JavaScript RegExp syntax. Keep patterns precise \u2014 avoid `.*` between delimiters.\n- If the lesson describes an architectural principle or conceptual guideline that cannot be expressed as any pattern, set `compilable` to `false`.\n- **File scoping:** Include a `fileGlobs` array to limit where the rule runs. Scope rules as tightly as possible:\n - **By file type:** `[\"**/*.sh\", \"**/*.yml\"]` \u2014 for rules about shell or YAML syntax.\n - **By package/directory:** `[\"packages/mcp/**/*.ts\"]` \u2014 for rules about MCP-specific patterns in a monorepo.\n - **By exclusion:** `[\"packages/cli/**/*.ts\", \"!**/*.test.ts\"]` \u2014 exclude test files that legitimately use the flagged pattern.\n - **Infer scope from context:** If a lesson mentions \"MCP tool returns\", \"CLI output\", \"LanceDB filters\", or a specific package, scope to that package. Only omit `fileGlobs` if the rule genuinely applies to ALL files (e.g., universal TypeScript style rules).\n - **CRITICAL \u2014 Always use recursive glob patterns with `**/` prefix** (e.g., `**/*.ts`, `**/*.py`). Never emit shallow patterns like `*.ts` \u2014 they are not portable across glob implementations.\n - **CRITICAL \u2014 Supported glob syntax only:**\n - `**/*.ext` \u2014 match extension anywhere (recursive)\n - `dir/**/*.ext` \u2014 directory + recursive + extension\n - `dir/**` \u2014 everything under directory\n - `dir/*.ext` \u2014 direct children only\n - `!pattern` \u2014 negation prefix\n - **DO NOT use** brace expansion `{a,b}`, nested globstars `**/dir/**`, or regex-style patterns.\n - **DO NOT use** `**/*.{ts,js}`. Instead use separate entries: `[\"**/*.ts\", \"**/*.js\"]`.\n\n## Output Schema\n```json\n{\n \"compilable\": true,\n \"pattern\": \"regex pattern here\",\n \"message\": \"human-readable violation message\",\n \"fileGlobs\": [\"packages/mcp/**/*.ts\", \"!**/*.test.ts\"]\n}\n```\n\nOr if the rule genuinely applies to all file types (rare \u2014 prefer scoping):\n```json\n{\n \"compilable\": true,\n \"pattern\": \"regex pattern here\",\n \"message\": \"human-readable violation message\"\n}\n```\n\nOr if the lesson cannot be compiled:\n```json\n{\n \"compilable\": false,\n \"reason\": \"Lesson describes a conceptual architectural principle, not a detectable code pattern\"\n}\n```\n\nWhen setting `\"compilable\": false`, always include a `\"reason\"` field explaining why the lesson cannot be compiled into a regex/AST pattern (e.g., \"Lesson describes a conceptual architectural principle, not a detectable code pattern\").\n\n## Examples\n\nLesson: \"Use `err` (never `error`) in catch blocks\"\nOutput: {\"compilable\": true, \"pattern\": \"catch\\\\s*\\\\(\\\\s*error\\\\s*[\\\\):]\", \"message\": \"Use 'err' instead of 'error' in catch blocks (project convention)\"}\n\nLesson: \"LanceDB does NOT support GROUP BY aggregation\"\nOutput: {\"compilable\": false, \"reason\": \"Lesson describes a database limitation, not a detectable code pattern\"}\n\nLesson: \"Never use npm in this pnpm monorepo \u2014 always use pnpm\"\nOutput: {\"compilable\": true, \"pattern\": \"\\\\bnpm\\\\s+(install|run|exec|ci|test)\\\\b\", \"message\": \"Use pnpm instead of npm in this monorepo\"}\n\nLesson: \"Always quote shell variables to prevent word-splitting\"\nOutput: {\"compilable\": true, \"pattern\": \"(^|\\\\s)\\\\$[a-zA-Z_]+\", \"message\": \"Quote shell variables to prevent word-splitting\", \"fileGlobs\": [\"**/*.sh\", \"**/*.bash\", \"**/*.yml\", \"**/*.yaml\"]}\n\nLesson: \"MCP tool returns must be wrapped in XML tags to prevent prompt injection\"\nOutput: {\"compilable\": true, \"pattern\": \"text:\\\\s*(?!formatXmlResponse)\\\\b\\\\w+\", \"message\": \"MCP tool returns must use formatXmlResponse for injection safety\", \"fileGlobs\": [\"packages/mcp/**/*.ts\", \"!**/*.test.ts\"]}\n\nLesson: \"Use @clack/prompts instead of inquirer for CLI interactions\"\nOutput: {\"compilable\": true, \"pattern\": \"import.*from\\\\s+['\"]inquirer['\"]\", \"message\": \"Use @clack/prompts instead of inquirer\", \"fileGlobs\": [\"packages/cli/**/*.ts\"]}\n\n## ast-grep Patterns (PREFERRED for structural rules)\nFor TypeScript/JavaScript/TSX/JSX: **always prefer ast-grep over regex** when the violation involves function calls, method chains, imports, control flow, or object properties. ast-grep patterns look like source code with `$METAVAR` placeholders.\n\n### Cheat sheet\n- `$VAR` \u2014 matches any single expression or identifier\n- `$$$ARGS` \u2014 matches zero or more nodes (spread/rest capture)\n- Patterns match structurally, ignoring whitespace and formatting\n- Patterns are single AST nodes \u2014 one statement or expression\n\n### Simple patterns\n- `console.log($ARG)` \u2014 any console.log call\n- `process.env.$PROP` \u2014 any process.env property access\n- `JSON.parse($INPUT) as $TYPE` \u2014 unsafe type assertion on parsed JSON\n- `eval($CODE)` \u2014 any eval call\n\n### Compound patterns (method calls with specific arguments)\n- `$OBJ.replace(process.cwd(), $REPLACEMENT)` \u2014 string replace on cwd instead of path.relative\n- `new RegExp($SRC, $FLAGS + 'g')` \u2014 blindly appending regex flags\n- `$ARR.forEach(async ($ITEM) => { $$$BODY })` \u2014 async callback in forEach (drops promises)\n\n### Patterns with object properties (`$$$` spread captures)\n- `spawn($CMD, [$$$ARGS], { $$$BEFORE, shell: true, $$$AFTER })` \u2014 shell:true with array args\n- `{ $$$PROPS, password: $VAL, $$$REST }` \u2014 password in object literal\n\n### Multi-statement patterns (try/catch, if/else)\n- `try { $$$PRE; expect.fail($$$ARGS); $$$POST } catch ($ERR) { $$$CATCH }` \u2014 expect.fail in try block\n\nSet `\"engine\": \"ast-grep\"` and provide an `\"astGrepPattern\"` field. Leave `\"pattern\"` as an empty string.\n\n```json\n{\n \"compilable\": true,\n \"engine\": \"ast-grep\",\n \"astGrepPattern\": \"$ARR.forEach(async ($ITEM) => { $$$BODY })\",\n \"pattern\": \"\",\n \"message\": \"Do not pass async functions to forEach \u2014 use for...of or Promise.all(arr.map(...))\",\n \"fileGlobs\": [\"**/*.ts\", \"**/*.tsx\"]\n}\n```\n\nIMPORTANT: ast-grep patterns must be single valid AST nodes. Only use for TypeScript/JavaScript/TSX/JSX files.\n\n## Regex (fallback for non-structural patterns)\nUse regex ONLY when the violation is a simple string/keyword match that does not involve code structure \u2014 e.g., matching import paths, literal URLs, comment patterns, or config values. The regex rules above still apply.\n\n## AST Queries (Tree-sitter S-expressions \u2014 rarely needed)\nUse Tree-sitter S-expression queries ONLY when ast-grep cannot express the constraint (e.g., predicates on node text, child count checks).\n\nSet `\"engine\": \"ast\"` and provide an `\"astQuery\"` field with a Tree-sitter S-expression query. Leave `\"pattern\"` as an empty string.\n\nTree-sitter S-expression syntax:\n- `(node_type)` \u2014 matches a node\n- `(node_type field: (child_type))` \u2014 matches with named field\n- `@name` \u2014 captures a node\n- `(#eq? @name \"value\")` \u2014 predicate: capture text equals value\n- Use `@violation` capture name for the node that should be flagged\n\n```json\n{\n \"compilable\": true,\n \"engine\": \"ast\",\n \"astQuery\": \"(catch_clause body: (statement_block) @body (#eq? @body \"{}\")) @violation\",\n \"pattern\": \"\",\n \"message\": \"human-readable violation message\",\n \"fileGlobs\": [\"**/*.ts\", \"**/*.tsx\"]\n}\n```\n\nIMPORTANT: Only use AST queries for TypeScript/JavaScript/TSX/JSX files.\n";
2
2
  export declare const PIPELINE3_COMPILER_PROMPT = "# Example-Based Rule Compiler \u2014 Pipeline 3\n\n## Identity\nYou are a deterministic rule compiler. Your job is to analyze Bad and Good code snippets and generate a regex pattern that catches the BAD pattern but NOT the good pattern.\n\n## Input\nYou will receive:\n1. A lesson heading describing the rule\n2. **Bad Code** \u2014 code that should trigger the rule (violations)\n3. **Good Code** \u2014 code that should NOT trigger (correct alternatives)\n4. The full lesson body for additional context\n\n## Strategy\n1. Identify the structural difference between Bad and Good code\n2. Find a regex pattern that matches the BAD lines but not the GOOD lines\n3. The pattern will be tested line-by-line against git diff additions\n4. Keep patterns precise \u2014 avoid overly broad matches\n\n## Rules\n- Output ONLY valid JSON \u2014 no markdown, no explanation\n- The regex must use JavaScript RegExp syntax\n- The pattern MUST match at least one Bad line and MUST NOT match any Good line\n- Include fileGlobs to scope the rule appropriately\n- **CRITICAL \u2014 Always use recursive glob patterns with `**/` prefix** (e.g., `**/*.ts`, `**/*.py`)\n- **CRITICAL \u2014 Supported glob syntax only:** `**/*.ext`, `dir/**/*.ext`, `!pattern` for negation. NO brace expansion.\n\n## Output Schema\n```json\n{\n \"compilable\": true,\n \"pattern\": \"regex pattern that catches Bad but not Good\",\n \"message\": \"human-readable violation message\",\n \"fileGlobs\": [\"**/*.ts\", \"!**/*.test.ts\"]\n}\n```\n\nOr if the difference cannot be expressed as a line-level regex:\n```json\n{\n \"compilable\": false,\n \"reason\": \"Explanation of why a regex cannot distinguish these snippets\"\n}\n```\n";
3
3
  //# sourceMappingURL=compile-templates.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"compile-templates.d.ts","sourceRoot":"","sources":["../../src/commands/compile-templates.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,sBAAsB,w/NAqIlC,CAAC;AAIF,eAAO,MAAM,yBAAyB,4qDA2CrC,CAAC"}
1
+ {"version":3,"file":"compile-templates.d.ts","sourceRoot":"","sources":["../../src/commands/compile-templates.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,sBAAsB,29QAsJlC,CAAC;AAIF,eAAO,MAAM,yBAAyB,4qDA2CrC,CAAC"}
@@ -1,16 +1,21 @@
1
1
  // ─── Compiler prompt ────────────────────────────────
2
- export const COMPILER_SYSTEM_PROMPT = `# Lesson Compiler — Regex Rule Extraction
2
+ export const COMPILER_SYSTEM_PROMPT = `# Lesson Compiler — Rule Extraction
3
3
 
4
4
  ## Identity
5
- You are a deterministic rule compiler. Your job is to read a single natural-language lesson and determine whether it can be expressed as a regex pattern that catches violations in source code diffs.
5
+ You are a deterministic rule compiler. Your job is to read a single natural-language lesson and produce the narrowest possible pattern that catches violations in source code.
6
+
7
+ ## Engine Preference (follow this order)
8
+ 1. **ast-grep** — for any structural pattern involving function calls, method chains, imports, control flow, or object properties in TypeScript/JavaScript. This is the PREFERRED engine.
9
+ 2. **regex** — for simple string/keyword matches (URLs, comment patterns, import paths, config values) or non-JS file types.
10
+ 3. **ast** (Tree-sitter S-expression) — only when ast-grep cannot express the constraint.
11
+ 4. **non-compilable** — only for purely conceptual/architectural lessons with no detectable code pattern.
6
12
 
7
13
  ## Rules
8
14
  - Output ONLY valid JSON — no markdown, no explanation, no preamble.
9
- - The regex will be tested against individual lines added in a git diff (lines starting with \`+\`).
10
- - The regex should catch **violations** (code that breaks the lesson's rule), NOT conformance.
11
- - Use JavaScript RegExp syntax.
12
- - Keep patterns simple and precise avoid overly broad matches that cause false positives.
13
- - If the lesson describes an architectural principle, design philosophy, or conceptual guideline that cannot be expressed as a line-level regex, set \`compilable\` to \`false\`.
15
+ - Regex rules are tested against individual lines added in a git diff (lines starting with \`+\`).
16
+ - Patterns should catch **violations** (code that breaks the lesson's rule), NOT conformance.
17
+ - For regex: use JavaScript RegExp syntax. Keep patterns precise — avoid \`.*\` between delimiters.
18
+ - If the lesson describes an architectural principle or conceptual guideline that cannot be expressed as any pattern, set \`compilable\` to \`false\`.
14
19
  - **File scoping:** Include a \`fileGlobs\` array to limit where the rule runs. Scope rules as tightly as possible:
15
20
  - **By file type:** \`["**/*.sh", "**/*.yml"]\` — for rules about shell or YAML syntax.
16
21
  - **By package/directory:** \`["packages/mcp/**/*.ts"]\` — for rules about MCP-specific patterns in a monorepo.
@@ -75,63 +80,75 @@ Output: {"compilable": true, "pattern": "text:\\\\s*(?!formatXmlResponse)\\\\b\\
75
80
  Lesson: "Use @clack/prompts instead of inquirer for CLI interactions"
76
81
  Output: {"compilable": true, "pattern": "import.*from\\\\s+['\"]inquirer['\"]", "message": "Use @clack/prompts instead of inquirer", "fileGlobs": ["packages/cli/**/*.ts"]}
77
82
 
78
- ## AST Queries (Tier 2)
79
- If the lesson describes a STRUCTURAL constraint that cannot be expressed as a single-line regex, you may output an AST query instead.
83
+ ## ast-grep Patterns (PREFERRED for structural rules)
84
+ For TypeScript/JavaScript/TSX/JSX: **always prefer ast-grep over regex** when the violation involves function calls, method chains, imports, control flow, or object properties. ast-grep patterns look like source code with \`$METAVAR\` placeholders.
80
85
 
81
- Set \`"engine": "ast"\` and provide an \`"astQuery"\` field with a Tree-sitter S-expression query. Leave \`"pattern"\` as an empty string.
86
+ ### Cheat sheet
87
+ - \`$VAR\` — matches any single expression or identifier
88
+ - \`$$$ARGS\` — matches zero or more nodes (spread/rest capture)
89
+ - Patterns match structurally, ignoring whitespace and formatting
90
+ - Patterns are single AST nodes — one statement or expression
82
91
 
83
- Tree-sitter S-expression syntax:
84
- - \`(node_type)\` — matches a node
85
- - \`(node_type field: (child_type))\` — matches with named field
86
- - \`@name\` — captures a node
87
- - \`(#eq? @name "value")\` — predicate: capture text equals value
88
- - Use \`@violation\` capture name for the node that should be flagged
92
+ ### Simple patterns
93
+ - \`console.log($ARG)\` — any console.log call
94
+ - \`process.env.$PROP\` — any process.env property access
95
+ - \`JSON.parse($INPUT) as $TYPE\` unsafe type assertion on parsed JSON
96
+ - \`eval($CODE)\` — any eval call
97
+
98
+ ### Compound patterns (method calls with specific arguments)
99
+ - \`$OBJ.replace(process.cwd(), $REPLACEMENT)\` — string replace on cwd instead of path.relative
100
+ - \`new RegExp($SRC, $FLAGS + 'g')\` — blindly appending regex flags
101
+ - \`$ARR.forEach(async ($ITEM) => { $$$BODY })\` — async callback in forEach (drops promises)
102
+
103
+ ### Patterns with object properties (\`$$$\` spread captures)
104
+ - \`spawn($CMD, [$$$ARGS], { $$$BEFORE, shell: true, $$$AFTER })\` — shell:true with array args
105
+ - \`{ $$$PROPS, password: $VAL, $$$REST }\` — password in object literal
89
106
 
90
- Examples:
91
- - Catch direct process.env access:
92
- \`(member_expression object: (identifier) @obj (#eq? @obj "process") property: (property_identifier) @prop (#eq? @prop "env")) @violation\`
93
- - Catch empty catch blocks:
94
- \`(catch_clause body: (statement_block) @body (#eq? @body "{}")) @violation\`
107
+ ### Multi-statement patterns (try/catch, if/else)
108
+ - \`try { $$$PRE; expect.fail($$$ARGS); $$$POST } catch ($ERR) { $$$CATCH }\` — expect.fail in try block
109
+
110
+ Set \`"engine": "ast-grep"\` and provide an \`"astGrepPattern"\` field. Leave \`"pattern"\` as an empty string.
95
111
 
96
- AST query output schema:
97
112
  \`\`\`json
98
113
  {
99
114
  "compilable": true,
100
- "engine": "ast",
101
- "astQuery": "(s-expression query here) @violation",
115
+ "engine": "ast-grep",
116
+ "astGrepPattern": "$ARR.forEach(async ($ITEM) => { $$$BODY })",
102
117
  "pattern": "",
103
- "message": "human-readable violation message",
118
+ "message": "Do not pass async functions to forEach — use for...of or Promise.all(arr.map(...))",
104
119
  "fileGlobs": ["**/*.ts", "**/*.tsx"]
105
120
  }
106
121
  \`\`\`
107
122
 
108
- IMPORTANT: Only use AST queries for TypeScript/JavaScript/TSX/JSX files. If the lesson applies to other file types, prefer regex or mark as non-compilable.
123
+ IMPORTANT: ast-grep patterns must be single valid AST nodes. Only use for TypeScript/JavaScript/TSX/JSX files.
109
124
 
110
- ## ast-grep Patterns (Tier 2b — Preferred for structural rules)
111
- If the lesson describes a structural constraint, prefer ast-grep patterns over regex or S-expressions.
125
+ ## Regex (fallback for non-structural patterns)
126
+ Use regex ONLY when the violation is a simple string/keyword match that does not involve code structure — e.g., matching import paths, literal URLs, comment patterns, or config values. The regex rules above still apply.
112
127
 
113
- ast-grep patterns look like the source code itself with $METAVAR placeholders:
114
- - \`console.log($ARG)\` matches any console.log call
115
- - \`process.env.$PROP\` — matches any process.env access
116
- - \`throw new Error($MSG)\` — matches any Error throw
117
- - \`useState($INIT)\` — matches any useState hook
128
+ ## AST Queries (Tree-sitter S-expressions rarely needed)
129
+ Use Tree-sitter S-expression queries ONLY when ast-grep cannot express the constraint (e.g., predicates on node text, child count checks).
118
130
 
119
- Set \`"engine": "ast-grep"\` and provide an \`"astGrepPattern"\` field. Leave \`"pattern"\` as an empty string.
131
+ Set \`"engine": "ast"\` and provide an \`"astQuery"\` field with a Tree-sitter S-expression query. Leave \`"pattern"\` as an empty string.
132
+
133
+ Tree-sitter S-expression syntax:
134
+ - \`(node_type)\` — matches a node
135
+ - \`(node_type field: (child_type))\` — matches with named field
136
+ - \`@name\` — captures a node
137
+ - \`(#eq? @name "value")\` — predicate: capture text equals value
138
+ - Use \`@violation\` capture name for the node that should be flagged
120
139
 
121
- ast-grep output schema:
122
140
  \`\`\`json
123
141
  {
124
142
  "compilable": true,
125
- "engine": "ast-grep",
126
- "astGrepPattern": "console.log($ARG)",
143
+ "engine": "ast",
144
+ "astQuery": "(catch_clause body: (statement_block) @body (#eq? @body \"{}\")) @violation",
127
145
  "pattern": "",
128
146
  "message": "human-readable violation message",
129
147
  "fileGlobs": ["**/*.ts", "**/*.tsx"]
130
148
  }
131
149
  \`\`\`
132
150
 
133
- IMPORTANT: ast-grep patterns must be single valid AST nodes. Statements like \`catch ($E) {}\` won't work — use regex for those.
134
- Only use for TypeScript/JavaScript/TSX/JSX files.
151
+ IMPORTANT: Only use AST queries for TypeScript/JavaScript/TSX/JSX files.
135
152
  `;
136
153
  // ─── Pipeline 3: Example-based compilation ──────────
137
154
  export const PIPELINE3_COMPILER_PROMPT = `# Example-Based Rule Compiler — Pipeline 3
@@ -1 +1 @@
1
- {"version":3,"file":"compile-templates.js","sourceRoot":"","sources":["../../src/commands/compile-templates.ts"],"names":[],"mappings":"AAAA,uDAAuD;AAEvD,MAAM,CAAC,MAAM,sBAAsB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAqIrC,CAAC;AAEF,uDAAuD;AAEvD,MAAM,CAAC,MAAM,yBAAyB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA2CxC,CAAC"}
1
+ {"version":3,"file":"compile-templates.js","sourceRoot":"","sources":["../../src/commands/compile-templates.ts"],"names":[],"mappings":"AAAA,uDAAuD;AAEvD,MAAM,CAAC,MAAM,sBAAsB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAsJrC,CAAC;AAEF,uDAAuD;AAEvD,MAAM,CAAC,MAAM,yBAAyB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA2CxC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=compile-upgrade.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compile-upgrade.test.d.ts","sourceRoot":"","sources":["../../src/commands/compile-upgrade.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,161 @@
1
+ import * as fs from 'node:fs';
2
+ import * as os from 'node:os';
3
+ import * as path from 'node:path';
4
+ import { afterEach, beforeEach, describe, expect, it } from 'vitest';
5
+ import { hashLesson } from '@mmnto/totem';
6
+ import { cleanTmpDir } from '../test-utils.js';
7
+ import { buildTelemetryPrefix, compileCommand } from './compile.js';
8
+ // ─── Helpers ─────────────────────────────────────────
9
+ function makeTmpDir() {
10
+ return fs.mkdtempSync(path.join(os.tmpdir(), 'totem-compile-upgrade-'));
11
+ }
12
+ function setupWorkspace(tmpDir, lessonFiles) {
13
+ // Minimal Lite-tier config so loadConfig succeeds. No orchestrator → no LLM call.
14
+ fs.writeFileSync(path.join(tmpDir, 'totem.yaml'), [
15
+ 'targets:',
16
+ ' - glob: "**/*.ts"',
17
+ ' type: code',
18
+ ' strategy: typescript-ast',
19
+ 'totemDir: .totem',
20
+ ].join('\n') + '\n', 'utf-8');
21
+ const lessonsDir = path.join(tmpDir, '.totem', 'lessons');
22
+ fs.mkdirSync(lessonsDir, { recursive: true });
23
+ for (const [name, body] of Object.entries(lessonFiles)) {
24
+ fs.writeFileSync(path.join(lessonsDir, name), body, 'utf-8');
25
+ }
26
+ }
27
+ /** Build a valid `## Lesson —` markdown block that readAllLessons can parse. */
28
+ function lessonMarkdown(heading, body) {
29
+ return `## Lesson — ${heading}\n\n**Tags:** test\n\n${body}\n`;
30
+ }
31
+ // ─── buildTelemetryPrefix ──────────────────────────
32
+ describe('buildTelemetryPrefix', () => {
33
+ it('builds a directive describing the non-code ratio', () => {
34
+ const prefix = buildTelemetryPrefix({
35
+ code: 2,
36
+ string: 5,
37
+ comment: 3,
38
+ regex: 0,
39
+ unknown: 0,
40
+ });
41
+ // 8 non-code / 10 total = 80%
42
+ expect(prefix).toContain('80%');
43
+ expect(prefix).toContain('strings: 5');
44
+ expect(prefix).toContain('comments: 3');
45
+ expect(prefix).toContain('regex literals: 0');
46
+ expect(prefix).toContain('ast-grep');
47
+ });
48
+ it('reports 0% when all matches are in code', () => {
49
+ const prefix = buildTelemetryPrefix({
50
+ code: 10,
51
+ string: 0,
52
+ comment: 0,
53
+ regex: 0,
54
+ unknown: 0,
55
+ });
56
+ expect(prefix).toContain('0%');
57
+ });
58
+ it('does not divide by zero when there are no matches', () => {
59
+ const prefix = buildTelemetryPrefix({
60
+ code: 0,
61
+ string: 0,
62
+ comment: 0,
63
+ regex: 0,
64
+ unknown: 0,
65
+ });
66
+ expect(prefix).toContain('0%');
67
+ });
68
+ it('excludes the unknown bucket from both numerator and denominator', () => {
69
+ // 100 unclassified historical hits + 5 recent classified (all code).
70
+ // Old math would report (100 + 0) / 105 = 95% "non-code" — a false positive.
71
+ // New math: 0 / 5 = 0% non-code. Unknown is surfaced as a side note.
72
+ const prefix = buildTelemetryPrefix({
73
+ code: 5,
74
+ string: 0,
75
+ comment: 0,
76
+ regex: 0,
77
+ unknown: 100,
78
+ });
79
+ expect(prefix).toContain('0%');
80
+ expect(prefix).toContain('Unclassified (historical) matches: 100');
81
+ });
82
+ it('computes the ratio from classified buckets only when unknown is large', () => {
83
+ // 500 historical + 5 classified (2 code, 3 string). Classified total = 5,
84
+ // non-code = 3, pct = 60%. Unknown is reported but does not move the ratio.
85
+ const prefix = buildTelemetryPrefix({
86
+ code: 2,
87
+ string: 3,
88
+ comment: 0,
89
+ regex: 0,
90
+ unknown: 500,
91
+ });
92
+ expect(prefix).toContain('60%');
93
+ expect(prefix).toContain('Unclassified (historical) matches: 500');
94
+ });
95
+ });
96
+ // ─── compileCommand --upgrade error paths ──────────
97
+ describe('compileCommand --upgrade', () => {
98
+ let tmpDir;
99
+ let originalCwd;
100
+ beforeEach(() => {
101
+ tmpDir = makeTmpDir();
102
+ originalCwd = process.cwd();
103
+ process.chdir(tmpDir);
104
+ });
105
+ afterEach(() => {
106
+ process.chdir(originalCwd);
107
+ cleanTmpDir(tmpDir);
108
+ });
109
+ it('errors cleanly when --upgrade hash matches no lesson', async () => {
110
+ setupWorkspace(tmpDir, {
111
+ 'rule-a.md': lessonMarkdown('Use err in catch', 'Do not use error in catch blocks.'),
112
+ });
113
+ await expect(compileCommand({ upgrade: 'deadbeefcafebabe' })).rejects.toMatchObject({
114
+ code: 'UPGRADE_HASH_NOT_FOUND',
115
+ });
116
+ });
117
+ it('rejects --upgrade combined with --cloud (not yet supported)', async () => {
118
+ setupWorkspace(tmpDir, {
119
+ 'rule-a.md': lessonMarkdown('Use err in catch', 'Do not use error in catch blocks.'),
120
+ });
121
+ await expect(compileCommand({ upgrade: 'deadbeef', cloud: 'https://example.invalid' })).rejects.toMatchObject({
122
+ code: 'UPGRADE_CLOUD_UNSUPPORTED',
123
+ });
124
+ });
125
+ it('rejects --upgrade combined with --force', async () => {
126
+ setupWorkspace(tmpDir, {
127
+ 'rule-a.md': lessonMarkdown('Use err in catch', 'Do not use error in catch blocks.'),
128
+ });
129
+ // --force would empty the cache before scoped eviction runs, silently
130
+ // turning --upgrade into a full recompile. Must be rejected.
131
+ await expect(compileCommand({ upgrade: 'deadbeef', force: true })).rejects.toMatchObject({
132
+ code: 'CONFIG_INVALID',
133
+ });
134
+ });
135
+ it('matches a known lesson by full hash and proceeds past the filter', async () => {
136
+ setupWorkspace(tmpDir, {
137
+ 'rule-a.md': lessonMarkdown('Use err in catch', 'Do not use error in catch blocks.'),
138
+ 'rule-b.md': lessonMarkdown('Avoid console.log', 'Do not commit console.log calls.'),
139
+ });
140
+ // Compute the hash that the parser will produce for rule-a
141
+ const hashA = hashLesson('Use err in catch', 'Do not use error in catch blocks.');
142
+ // The Lite-tier config has no orchestrator, so once the upgrade filter passes,
143
+ // the next gate (`if (config.orchestrator)`) is skipped and we land in the
144
+ // export branch which throws CONFIG_MISSING. The fact that we get past
145
+ // UPGRADE_HASH_NOT_FOUND proves the filter matched.
146
+ await expect(compileCommand({ upgrade: hashA })).rejects.toMatchObject({
147
+ code: 'CONFIG_MISSING',
148
+ });
149
+ });
150
+ it('matches a known lesson by short hash prefix', async () => {
151
+ setupWorkspace(tmpDir, {
152
+ 'rule-a.md': lessonMarkdown('Use err in catch', 'Do not use error in catch blocks.'),
153
+ });
154
+ const hashA = hashLesson('Use err in catch', 'Do not use error in catch blocks.');
155
+ const shortPrefix = hashA.slice(0, 8);
156
+ await expect(compileCommand({ upgrade: shortPrefix })).rejects.toMatchObject({
157
+ code: 'CONFIG_MISSING',
158
+ });
159
+ });
160
+ });
161
+ //# sourceMappingURL=compile-upgrade.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compile-upgrade.test.js","sourceRoot":"","sources":["../../src/commands/compile-upgrade.test.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,22 @@
1
1
  import type { CompiledRule, LessonInput } from '@mmnto/totem';
2
+ /**
3
+ * Terminal outcome of a `--upgrade <hash>` run, returned by `compileCommand`
4
+ * so callers (like `totem doctor --pr` self-healing) can distinguish an actual
5
+ * rule replacement from a noop / skipped / failed outcome and only report real
6
+ * upgrades in their summaries.
7
+ *
8
+ * - `replaced`: compilation produced a fresh rule that replaced the stale copy
9
+ * - `skipped`: LLM decided the lesson is non-compilable; rule moved to
10
+ * nonCompilable and removed from active rules
11
+ * - `noop`: compile returned with no change (rare — cache hit path)
12
+ * - `failed`: transient error (network, rate limit, parser failure); old
13
+ * rule is preserved untouched
14
+ */
15
+ export type UpgradeStatus = 'replaced' | 'skipped' | 'noop' | 'failed';
16
+ export interface UpgradeOutcome {
17
+ hash: string;
18
+ status: UpgradeStatus;
19
+ }
2
20
  export interface CompileOptions {
3
21
  raw?: boolean;
4
22
  out?: string;
@@ -10,7 +28,28 @@ export interface CompileOptions {
10
28
  concurrency?: string;
11
29
  cloud?: string;
12
30
  verbose?: boolean;
31
+ /**
32
+ * Telemetry-driven re-compile (mmnto/totem#1131). Filters lessons to a single hash
33
+ * (full or short prefix), bypasses the cache, and threads a non-code-ratio
34
+ * directive into the Pipeline 2 system prompt.
35
+ */
36
+ upgrade?: string;
13
37
  }
38
+ /**
39
+ * Build the directive injected into the Sonnet system prompt for `--upgrade`.
40
+ *
41
+ * `unknown` is excluded from both the numerator and the denominator because it
42
+ * holds historical / unclassified telemetry (pre-context-aware hits, or events
43
+ * where the rule runner did not provide an `astContext`). Including it would
44
+ * dilute the classified signal and produce misleading ratios.
45
+ */
46
+ export declare function buildTelemetryPrefix(contextCounts: {
47
+ code: number;
48
+ string: number;
49
+ comment: number;
50
+ regex: number;
51
+ unknown: number;
52
+ }): string;
14
53
  export interface AutoScaffoldDeps {
15
54
  fs: typeof import('node:fs');
16
55
  path: typeof import('node:path');
@@ -27,5 +66,5 @@ export interface AutoScaffoldDeps {
27
66
  }
28
67
  /** Returns true if the fixture was written, false on failure. */
29
68
  export declare function autoScaffoldFixture(lesson: LessonInput, rule: CompiledRule, deps: AutoScaffoldDeps): boolean;
30
- export declare function compileCommand(options: CompileOptions): Promise<void>;
69
+ export declare function compileCommand(options: CompileOptions): Promise<UpgradeOutcome | void>;
31
70
  //# sourceMappingURL=compile.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"compile.d.ts","sourceRoot":"","sources":["../../src/commands/compile.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAqB,WAAW,EAAE,MAAM,cAAc,CAAC;AAYjF,MAAM,WAAW,cAAc;IAC7B,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAsDD,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,cAAc,SAAS,CAAC,CAAC;IAC7B,IAAI,EAAE,cAAc,WAAW,CAAC,CAAC;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC1B,GAAG,EAAE;QAAE,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,KAAK,IAAI,CAAA;KAAE,CAAC;IAClD,mBAAmB,EAAE,cAAc,cAAc,EAAE,mBAAmB,CAAC;IACvE,qBAAqB,EAAE,cAAc,cAAc,EAAE,qBAAqB,CAAC;IAC3E,eAAe,EAAE,cAAc,cAAc,EAAE,eAAe,CAAC;IAC/D,mBAAmB,EAAE,cAAc,cAAc,EAAE,mBAAmB,CAAC;CACxE;AAED,iEAAiE;AACjE,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,WAAW,EACnB,IAAI,EAAE,YAAY,EAClB,IAAI,EAAE,gBAAgB,GACrB,OAAO,CAyBT;AAID,wBAAsB,cAAc,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAmc3E"}
1
+ {"version":3,"file":"compile.d.ts","sourceRoot":"","sources":["../../src/commands/compile.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAqB,WAAW,EAAE,MAAM,cAAc,CAAC;AAYjF;;;;;;;;;;;;GAYG;AACH,MAAM,MAAM,aAAa,GAAG,UAAU,GAAG,SAAS,GAAG,MAAM,GAAG,QAAQ,CAAC;AAEvE,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,aAAa,CAAC;CACvB;AAED,MAAM,WAAW,cAAc;IAC7B,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAID;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAAC,aAAa,EAAE;IAClD,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;CACjB,GAAG,MAAM,CAcT;AAsDD,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,cAAc,SAAS,CAAC,CAAC;IAC7B,IAAI,EAAE,cAAc,WAAW,CAAC,CAAC;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC1B,GAAG,EAAE;QAAE,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,KAAK,IAAI,CAAA;KAAE,CAAC;IAClD,mBAAmB,EAAE,cAAc,cAAc,EAAE,mBAAmB,CAAC;IACvE,qBAAqB,EAAE,cAAc,cAAc,EAAE,qBAAqB,CAAC;IAC3E,eAAe,EAAE,cAAc,cAAc,EAAE,eAAe,CAAC;IAC/D,mBAAmB,EAAE,cAAc,cAAc,EAAE,mBAAmB,CAAC;CACxE;AAED,iEAAiE;AACjE,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,WAAW,EACnB,IAAI,EAAE,YAAY,EAClB,IAAI,EAAE,gBAAgB,GACrB,OAAO,CAyBT;AAID,wBAAsB,cAAc,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CAsmB5F"}