@rune-kit/rune 2.3.3 → 2.6.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 (77) hide show
  1. package/README.md +86 -17
  2. package/compiler/__tests__/pack-split.test.js +141 -1
  3. package/compiler/__tests__/parser.test.js +147 -55
  4. package/compiler/__tests__/scripts-bundling.test.js +283 -0
  5. package/compiler/__tests__/skill-index.test.js +218 -0
  6. package/compiler/__tests__/tier-override.test.js +41 -0
  7. package/compiler/adapters/antigravity.js +71 -53
  8. package/compiler/adapters/codex.js +4 -0
  9. package/compiler/adapters/cursor.js +4 -0
  10. package/compiler/adapters/generic.js +4 -0
  11. package/compiler/adapters/openclaw.js +4 -0
  12. package/compiler/adapters/opencode.js +4 -0
  13. package/compiler/adapters/windsurf.js +4 -0
  14. package/compiler/bin/rune.js +355 -355
  15. package/compiler/doctor.js +11 -1
  16. package/compiler/emitter.js +678 -386
  17. package/compiler/parser.js +267 -247
  18. package/compiler/transforms/scripts-path.js +18 -0
  19. package/extensions/zalo/PACK.md +20 -1
  20. package/extensions/zalo/references/conversation-management.md +214 -0
  21. package/extensions/zalo/references/eval-scenarios.md +157 -0
  22. package/extensions/zalo/references/listen-mode.md +237 -0
  23. package/extensions/zalo/references/mcp-production.md +274 -0
  24. package/extensions/zalo/references/multi-account-proxy.md +224 -0
  25. package/extensions/zalo/references/vietqr-banking.md +160 -0
  26. package/hooks/hooks.json +12 -0
  27. package/hooks/intent-router/index.cjs +108 -0
  28. package/hooks/pre-tool-guard/index.cjs +177 -68
  29. package/package.json +63 -64
  30. package/skills/brainstorm/SKILL.md +2 -0
  31. package/skills/cook/SKILL.md +661 -648
  32. package/skills/debug/SKILL.md +394 -392
  33. package/skills/deploy/SKILL.md +2 -0
  34. package/skills/fix/SKILL.md +283 -281
  35. package/skills/marketing/SKILL.md +3 -0
  36. package/skills/onboard/SKILL.md +7 -0
  37. package/skills/plan/SKILL.md +344 -342
  38. package/skills/preflight/SKILL.md +362 -360
  39. package/skills/review/SKILL.md +491 -489
  40. package/skills/scout/SKILL.md +1 -0
  41. package/skills/sentinel/SKILL.md +319 -296
  42. package/skills/sentinel/references/auth-crypto-reference.md +192 -0
  43. package/skills/sentinel/references/desktop-security.md +201 -0
  44. package/skills/sentinel/references/supply-chain.md +160 -0
  45. package/skills/session-bridge/SKILL.md +1 -0
  46. package/skills/slides/SKILL.md +142 -0
  47. package/skills/slides/scripts/build-deck.js +158 -0
  48. package/skills/team/SKILL.md +1 -0
  49. package/skills/test/SKILL.md +587 -585
  50. package/skills/verification/SKILL.md +1 -0
  51. package/skills/watchdog/SKILL.md +2 -0
  52. package/docs/ANTIGRAVITY-GAP-ANALYSIS.md +0 -369
  53. package/docs/ARCHITECTURE.md +0 -332
  54. package/docs/COMMUNITY-PACKS.md +0 -109
  55. package/docs/CONTRIBUTING-L4.md +0 -215
  56. package/docs/CROSS-IDE-ANALYSIS.md +0 -164
  57. package/docs/EXTENSION-TEMPLATE.md +0 -126
  58. package/docs/MESH-RULES.md +0 -34
  59. package/docs/MULTI-PLATFORM.md +0 -804
  60. package/docs/SKILL-DEPTH-AUDIT.md +0 -191
  61. package/docs/SKILL-TEMPLATE.md +0 -118
  62. package/docs/TRADE-MATRIX.md +0 -327
  63. package/docs/VERSIONING.md +0 -91
  64. package/docs/VISION.md +0 -263
  65. package/docs/assets/demo-subtitles.srt +0 -215
  66. package/docs/assets/end-card.html +0 -276
  67. package/docs/assets/mesh-diagram.html +0 -654
  68. package/docs/assets/thumbnail.html +0 -295
  69. package/docs/guides/cli.md +0 -403
  70. package/docs/guides/index.html +0 -1450
  71. package/docs/index.html +0 -1005
  72. package/docs/references/claudekit-analysis.md +0 -414
  73. package/docs/references/voltagent-analysis.md +0 -189
  74. package/docs/script.js +0 -495
  75. package/docs/skills/index.html +0 -832
  76. package/docs/style.css +0 -958
  77. package/docs/video-demo-plan.md +0 -172
@@ -0,0 +1,108 @@
1
+ // Rune Intent Router Hook — Compiled Intent Mesh (CIM)
2
+ // Auto-suggests skill routing based on user prompt analysis
3
+ // Runs as UserPromptSubmit hook — scores prompt against compiled skill-index.json
4
+ //
5
+ // Key difference from runtime scoring approaches:
6
+ // - Compile-time generated index (zero runtime deps)
7
+ // - Mesh-aware chain prediction (uses actual connection graph)
8
+ // - Works on all platforms (index ships with every build)
9
+
10
+ const fs = require('fs');
11
+ const path = require('path');
12
+
13
+ // Read user prompt from Claude Code hook stdin
14
+ let input = '';
15
+ process.stdin.setEncoding('utf-8');
16
+ process.stdin.on('data', (chunk) => { input += chunk; });
17
+ process.stdin.on('end', () => {
18
+ let userPrompt = '';
19
+ try {
20
+ const parsed = JSON.parse(input);
21
+ userPrompt = parsed.user_prompt || parsed.prompt || parsed.content || '';
22
+ } catch {
23
+ // Raw text input
24
+ userPrompt = input.trim();
25
+ }
26
+
27
+ if (!userPrompt || userPrompt.length < 3) {
28
+ process.exit(0);
29
+ }
30
+
31
+ // Find skill-index.json — check multiple locations
32
+ const pluginRoot = process.env.CLAUDE_PLUGIN_ROOT || path.resolve(__dirname, '../..');
33
+ const candidates = [
34
+ path.join(pluginRoot, '.cursor', 'rules', 'skill-index.json'),
35
+ path.join(pluginRoot, '.windsurf', 'rules', 'skill-index.json'),
36
+ path.join(pluginRoot, 'dist', 'cursor', 'skill-index.json'),
37
+ path.join(pluginRoot, 'dist', 'generic', 'skill-index.json'),
38
+ // Also check .rune/ for locally cached index
39
+ path.join(process.cwd(), '.rune', 'skill-index.json'),
40
+ ];
41
+
42
+ let index = null;
43
+ for (const candidate of candidates) {
44
+ try {
45
+ if (fs.existsSync(candidate)) {
46
+ index = JSON.parse(fs.readFileSync(candidate, 'utf-8'));
47
+ break;
48
+ }
49
+ } catch {
50
+ // Try next candidate
51
+ }
52
+ }
53
+
54
+ if (!index || !index.intents) {
55
+ // No index available — exit silently (not an error, just not built yet)
56
+ process.exit(0);
57
+ }
58
+
59
+ // Score prompt against intent patterns
60
+ const promptLower = userPrompt.toLowerCase();
61
+ const scores = [];
62
+
63
+ for (const [skillName, intent] of Object.entries(index.intents)) {
64
+ let score = 0;
65
+
66
+ for (const keyword of intent.keywords) {
67
+ if (promptLower.includes(keyword)) {
68
+ // Exact word boundary match scores higher
69
+ const wordBoundary = new RegExp(`\\b${keyword.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\b`, 'i');
70
+ score += wordBoundary.test(userPrompt) ? 3 : 1;
71
+ }
72
+ }
73
+
74
+ // Layer priority boost: L1 orchestrators get a small bonus (they're entry points)
75
+ if (intent.layer === 'L1') score += 1;
76
+
77
+ if (score > 0) {
78
+ scores.push({ skill: skillName, score, chain: intent.chain, model: intent.model, layer: intent.layer });
79
+ }
80
+ }
81
+
82
+ if (scores.length === 0) {
83
+ process.exit(0);
84
+ }
85
+
86
+ // Sort by score descending, take top match
87
+ scores.sort((a, b) => b.score - a.score);
88
+ const top = scores[0];
89
+
90
+ // Only suggest if confidence is reasonable (score >= 3 = at least one strong keyword match)
91
+ if (top.score < 3) {
92
+ process.exit(0);
93
+ }
94
+
95
+ // Format chain for display
96
+ const chainDisplay = top.chain.slice(0, 4).join(' → ');
97
+ const alternates = scores.slice(1, 3).map((s) => s.skill).join(', ');
98
+
99
+ // Output routing suggestion
100
+ console.log(`\n🧭 [Rune intent-router] Suggested: rune:${top.skill} (${top.layer}, ${top.model})`);
101
+ console.log(` Chain: ${chainDisplay}`);
102
+ if (alternates) {
103
+ console.log(` Also consider: ${alternates}`);
104
+ }
105
+ console.log('');
106
+
107
+ process.exit(0);
108
+ });
@@ -1,68 +1,177 @@
1
- // Rune Pre-Tool Guard Hook
2
- // Preventive security gate warns before AI reads/edits sensitive files
3
- // Runs as PreToolUse hook for Read, Write, Edit tools
4
-
5
- const path = require('path');
6
-
7
- // Read tool_input from Claude Code hook stdin
8
- let input = '';
9
- process.stdin.setEncoding('utf-8');
10
- process.stdin.on('data', (chunk) => { input += chunk; });
11
- process.stdin.on('end', () => {
12
- let toolInput = {};
13
- try {
14
- const parsed = JSON.parse(input);
15
- toolInput = parsed.tool_input || parsed;
16
- } catch {
17
- // If no stdin or parse fails, exit cleanly (non-blocking)
18
- process.exit(0);
19
- }
20
-
21
- const filePath = toolInput.file_path || toolInput.path || '';
22
- if (!filePath) process.exit(0);
23
-
24
- const basename = path.basename(filePath);
25
- const normalized = filePath.replace(/\\/g, '/');
26
-
27
- // Sensitive file patterns
28
- const sensitivePatterns = [
29
- /^\.env$/, // .env exactly
30
- /^\.env\.[^e]/, // .env.* but NOT .env.example
31
- /\.pem$/,
32
- /\.key$/,
33
- /\.p12$/,
34
- /\.pfx$/,
35
- /^credentials\.json$/,
36
- /\.secret$/,
37
- /^\.netrc$/,
38
- /^id_rsa$/,
39
- /^id_ed25519$/,
40
- /^id_ecdsa$/,
41
- /^\.ssh\//,
42
- /private.*key/i,
43
- /secret.*key/i,
44
- ];
45
-
46
- // Exclude safe exceptions
47
- const safeExceptions = [
48
- /\.env\.example$/,
49
- /\.env\.sample$/,
50
- /\.env\.template$/,
51
- /\.env\.test$/,
52
- /test.*credential/i,
53
- /fixture/i,
54
- /mock/i,
55
- ];
56
-
57
- const isSensitive = sensitivePatterns.some((p) => p.test(basename) || p.test(normalized));
58
- const isSafe = safeExceptions.some((p) => p.test(basename) || p.test(normalized));
59
-
60
- if (isSensitive && !isSafe) {
61
- // Output warning to user — does NOT hard-block (developer may legitimately need access)
62
- console.log(`\n⚠ [Rune pre-tool-guard] Sensitive file detected: ${filePath}`);
63
- console.log(' This file may contain secrets. Confirm this access is intentional.');
64
- console.log(' If scanning for secrets, use /rune sentinel instead.\n');
65
- }
66
-
67
- process.exit(0);
68
- });
1
+ // Rune Pre-Tool Guard Hook — Privacy Mesh
2
+ // Three-tier security gate: ALLOW / WARN / BLOCK
3
+ // Configurable via .rune/privacy.json per project
4
+ // Skill-aware: sentinel/review can access files other skills cannot
5
+ //
6
+ // Upgrades over basic path matching:
7
+ // - Per-project .rune/privacy.json config (custom patterns + overrides)
8
+ // - Three tiers: ALLOW (silent), WARN (log), BLOCK (exit code 2)
9
+ // - Content-aware: scans first bytes for secret patterns on WARN files
10
+
11
+ const fs = require('fs');
12
+ const path = require('path');
13
+
14
+ // Read tool_input from Claude Code hook stdin
15
+ let input = '';
16
+ process.stdin.setEncoding('utf-8');
17
+ process.stdin.on('data', (chunk) => { input += chunk; });
18
+ process.stdin.on('end', () => {
19
+ let toolInput = {};
20
+ let toolName = '';
21
+ try {
22
+ const parsed = JSON.parse(input);
23
+ toolInput = parsed.tool_input || parsed;
24
+ toolName = parsed.tool_name || '';
25
+ } catch {
26
+ // If no stdin or parse fails, exit cleanly (non-blocking)
27
+ process.exit(0);
28
+ }
29
+
30
+ const filePath = toolInput.file_path || toolInput.path || '';
31
+ if (!filePath) process.exit(0);
32
+
33
+ const basename = path.basename(filePath);
34
+ const normalized = filePath.replace(/\\/g, '/');
35
+
36
+ // Load project-specific privacy config
37
+ const privacyConfig = loadPrivacyConfig();
38
+
39
+ // Default sensitive file patterns — BLOCK tier (hard block)
40
+ const blockPatterns = [
41
+ /^id_rsa$/,
42
+ /^id_ed25519$/,
43
+ /^id_ecdsa$/,
44
+ /\.p12$/,
45
+ /\.pfx$/,
46
+ /\.pem$/,
47
+ /^\.netrc$/,
48
+ /private[-_.]?key/i,
49
+ ...privacyConfig.block.map((p) => new RegExp(p)),
50
+ ];
51
+
52
+ // WARN tier (log warning, allow through)
53
+ const warnPatterns = [
54
+ /^\.env$/,
55
+ /^\.env\.[^e]/, // .env.* but NOT .env.example
56
+ /\.key$/,
57
+ /^credentials\.json$/,
58
+ /\.secret$/,
59
+ /secret[-_.]?key/i,
60
+ ...privacyConfig.warn.map((p) => new RegExp(p)),
61
+ ];
62
+
63
+ // Safe exceptions (always allow)
64
+ const safeExceptions = [
65
+ /\.env\.example$/,
66
+ /\.env\.sample$/,
67
+ /\.env\.template$/,
68
+ /\.env\.test$/,
69
+ /test.*credential/i,
70
+ /fixture/i,
71
+ /mock/i,
72
+ ...privacyConfig.allow.map((p) => new RegExp(p)),
73
+ ];
74
+
75
+ // Skills with elevated access (can read WARN-tier files without warning)
76
+ const elevatedSkills = new Set([
77
+ 'sentinel', 'review', 'audit', 'secrets-scan', 'onboard',
78
+ ...privacyConfig.elevatedSkills,
79
+ ]);
80
+
81
+ // Detect active skill from environment (set by metrics-collector or skill invocation)
82
+ const activeSkill = process.env.RUNE_ACTIVE_SKILL || '';
83
+ const isElevated = elevatedSkills.has(activeSkill);
84
+
85
+ const isSafe = safeExceptions.some((p) => p.test(basename) || p.test(normalized));
86
+ if (isSafe) process.exit(0);
87
+
88
+ const isBlocked = blockPatterns.some((p) => p.test(basename) || p.test(normalized));
89
+ if (isBlocked) {
90
+ console.log(`\n🚫 [Rune privacy-mesh] BLOCKED: ${filePath}`);
91
+ console.log(' This file matches a BLOCK-tier pattern (private keys, certificates).');
92
+ console.log(' Override: add path to .rune/privacy.json "allow" list if intentional.\n');
93
+ process.exit(2); // Exit code 2 = BLOCK
94
+ }
95
+
96
+ const isWarned = warnPatterns.some((p) => p.test(basename) || p.test(normalized));
97
+ if (isWarned && !isElevated) {
98
+ // Content-aware check: scan first 4KB for secret patterns
99
+ const contentWarning = scanContentForSecrets(filePath);
100
+
101
+ console.log(`\n⚠ [Rune privacy-mesh] Sensitive file: ${filePath}`);
102
+ if (contentWarning) {
103
+ console.log(` Content scan: ${contentWarning}`);
104
+ }
105
+ console.log(' This file may contain secrets. Confirm access is intentional.');
106
+ console.log(' Elevated skills (sentinel, review, audit) bypass this warning.\n');
107
+ }
108
+
109
+ process.exit(0);
110
+ });
111
+
112
+ /**
113
+ * Load .rune/privacy.json from project root
114
+ * Returns merged config with defaults
115
+ */
116
+ function loadPrivacyConfig() {
117
+ const defaults = { block: [], warn: [], allow: [], elevatedSkills: [] };
118
+
119
+ const candidates = [
120
+ path.join(process.cwd(), '.rune', 'privacy.json'),
121
+ path.join(process.cwd(), 'privacy.json'),
122
+ ];
123
+
124
+ for (const configPath of candidates) {
125
+ try {
126
+ if (fs.existsSync(configPath)) {
127
+ const raw = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
128
+ return {
129
+ block: Array.isArray(raw.block) ? raw.block : defaults.block,
130
+ warn: Array.isArray(raw.warn) ? raw.warn : defaults.warn,
131
+ allow: Array.isArray(raw.allow) ? raw.allow : defaults.allow,
132
+ elevatedSkills: Array.isArray(raw.elevatedSkills) ? raw.elevatedSkills : defaults.elevatedSkills,
133
+ };
134
+ }
135
+ } catch {
136
+ // Invalid config — use defaults
137
+ }
138
+ }
139
+
140
+ return defaults;
141
+ }
142
+
143
+ /**
144
+ * Scan first 4KB of a file for common secret patterns
145
+ * Returns a warning string if secrets detected, null otherwise
146
+ */
147
+ function scanContentForSecrets(filePath) {
148
+ try {
149
+ const fd = fs.openSync(filePath, 'r');
150
+ const buffer = Buffer.alloc(4096);
151
+ const bytesRead = fs.readSync(fd, buffer, 0, 4096, 0);
152
+ fs.closeSync(fd);
153
+
154
+ if (bytesRead === 0) return null;
155
+
156
+ const content = buffer.toString('utf-8', 0, bytesRead);
157
+
158
+ const secretPatterns = [
159
+ { pattern: /AKIA[0-9A-Z]{16}/, label: 'AWS access key' },
160
+ { pattern: /gh[ps]_[A-Za-z0-9_]{36,}/, label: 'GitHub token' },
161
+ { pattern: /sk_(live|test)_[0-9a-zA-Z]{24,}/, label: 'Stripe key' },
162
+ { pattern: /-----BEGIN .* PRIVATE KEY-----/, label: 'Private key' },
163
+ { pattern: /xox[bpors]-[0-9a-zA-Z-]{10,}/, label: 'Slack token' },
164
+ { pattern: /sk-[a-zA-Z0-9]{32,}/, label: 'OpenAI/API key' },
165
+ ];
166
+
167
+ for (const { pattern, label } of secretPatterns) {
168
+ if (pattern.test(content)) {
169
+ return `Found ${label} pattern in file content`;
170
+ }
171
+ }
172
+ } catch {
173
+ // Can't read file (doesn't exist yet, permissions, etc.) — skip silently
174
+ }
175
+
176
+ return null;
177
+ }
package/package.json CHANGED
@@ -1,64 +1,63 @@
1
- {
2
- "name": "@rune-kit/rune",
3
- "version": "2.3.3",
4
- "description": "59-skill mesh for AI coding assistants — 5-layer architecture, 200+ connections, 8 platforms (Claude Code, Cursor, Windsurf, Antigravity, Codex, OpenCode, OpenClaw, Generic)",
5
- "type": "module",
6
- "bin": {
7
- "rune": "./compiler/bin/rune.js"
8
- },
9
- "scripts": {
10
- "build": "node compiler/bin/rune.js build",
11
- "doctor": "node compiler/bin/rune.js doctor && node scripts/version-sync-check.js",
12
- "test": "node --test compiler/__tests__/*.test.js scripts/__tests__/*.test.js",
13
- "test:coverage": "c8 --reporter=text --reporter=lcov node --test compiler/__tests__/*.test.js scripts/__tests__/*.test.js",
14
- "lint": "biome check .",
15
- "lint:fix": "biome check --fix .",
16
- "format": "biome format --write .",
17
- "ci": "biome check . && node --test compiler/__tests__/*.test.js scripts/__tests__/*.test.js && node compiler/bin/rune.js doctor",
18
- "version-check": "node scripts/version-sync-check.js",
19
- "prepublishOnly": "node scripts/version-sync-check.js"
20
- },
21
- "keywords": [
22
- "claude-code",
23
- "cursor",
24
- "windsurf",
25
- "antigravity",
26
- "codex",
27
- "opencode",
28
- "openclaw",
29
- "ai-assistant",
30
- "ai-coding",
31
- "skills",
32
- "coding-agent",
33
- "tdd",
34
- "multi-platform"
35
- ],
36
- "author": "runedev",
37
- "license": "MIT",
38
- "repository": {
39
- "type": "git",
40
- "url": "https://github.com/rune-kit/rune"
41
- },
42
- "engines": {
43
- "node": ">=18"
44
- },
45
- "files": [
46
- "compiler/",
47
- "skills/",
48
- "extensions/",
49
- "contexts/",
50
- "commands/",
51
- "agents/",
52
- "hooks/",
53
- "docs/",
54
- "references/"
55
- ],
56
- "homepage": "https://rune-kit.github.io/rune",
57
- "bugs": {
58
- "url": "https://github.com/rune-kit/rune/issues"
59
- },
60
- "devDependencies": {
61
- "@biomejs/biome": "^2.4.7",
62
- "c8": "^10.1.3"
63
- }
64
- }
1
+ {
2
+ "name": "@rune-kit/rune",
3
+ "version": "2.6.0",
4
+ "description": "61-skill mesh for AI coding assistants — 5-layer architecture, 200+ connections, 8 platforms (Claude Code, Cursor, Windsurf, Antigravity, Codex, OpenCode, OpenClaw, Generic)",
5
+ "type": "module",
6
+ "bin": {
7
+ "rune": "./compiler/bin/rune.js"
8
+ },
9
+ "scripts": {
10
+ "build": "node compiler/bin/rune.js build",
11
+ "doctor": "node compiler/bin/rune.js doctor && node scripts/version-sync-check.js",
12
+ "test": "node --test compiler/__tests__/*.test.js scripts/__tests__/*.test.js",
13
+ "test:coverage": "c8 --reporter=text --reporter=lcov node --test compiler/__tests__/*.test.js scripts/__tests__/*.test.js",
14
+ "lint": "biome check .",
15
+ "lint:fix": "biome check --fix .",
16
+ "format": "biome format --write .",
17
+ "ci": "biome check . && node --test compiler/__tests__/*.test.js scripts/__tests__/*.test.js && node compiler/bin/rune.js doctor",
18
+ "version-check": "node scripts/version-sync-check.js",
19
+ "prepublishOnly": "node scripts/version-sync-check.js"
20
+ },
21
+ "keywords": [
22
+ "claude-code",
23
+ "cursor",
24
+ "windsurf",
25
+ "antigravity",
26
+ "codex",
27
+ "opencode",
28
+ "openclaw",
29
+ "ai-assistant",
30
+ "ai-coding",
31
+ "skills",
32
+ "coding-agent",
33
+ "tdd",
34
+ "multi-platform"
35
+ ],
36
+ "author": "runedev",
37
+ "license": "MIT",
38
+ "repository": {
39
+ "type": "git",
40
+ "url": "https://github.com/rune-kit/rune"
41
+ },
42
+ "engines": {
43
+ "node": ">=18"
44
+ },
45
+ "files": [
46
+ "compiler/",
47
+ "skills/",
48
+ "extensions/",
49
+ "contexts/",
50
+ "commands/",
51
+ "agents/",
52
+ "hooks/",
53
+ "references/"
54
+ ],
55
+ "homepage": "https://rune-kit.github.io/rune",
56
+ "bugs": {
57
+ "url": "https://github.com/rune-kit/rune/issues"
58
+ },
59
+ "devDependencies": {
60
+ "@biomejs/biome": "^2.4.7",
61
+ "c8": "^10.1.3"
62
+ }
63
+ }
@@ -8,6 +8,8 @@ metadata:
8
8
  model: opus
9
9
  group: creation
10
10
  tools: "Read, Glob, Grep"
11
+ emit: ideas.ready
12
+ listen: codebase.scanned
11
13
  ---
12
14
 
13
15
  # brainstorm