@rune-kit/rune 2.17.1 → 2.18.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.
@@ -4,13 +4,18 @@
4
4
  * Central registry for all platform adapters.
5
5
  */
6
6
 
7
+ import aider from './aider.js';
7
8
  import antigravity from './antigravity.js';
8
9
  import claude from './claude.js';
9
10
  import codex from './codex.js';
11
+ import copilot from './copilot.js';
10
12
  import cursor from './cursor.js';
13
+ import gemini from './gemini.js';
11
14
  import generic from './generic.js';
12
15
  import openclaw from './openclaw.js';
13
16
  import opencode from './opencode.js';
17
+ import qoder from './qoder.js';
18
+ import qwen from './qwen.js';
14
19
  import windsurf from './windsurf.js';
15
20
 
16
21
  const adapters = {
@@ -22,6 +27,11 @@ const adapters = {
22
27
  openclaw,
23
28
  codex,
24
29
  opencode,
30
+ aider,
31
+ copilot,
32
+ gemini,
33
+ qoder,
34
+ qwen,
25
35
  };
26
36
 
27
37
  /**
@@ -0,0 +1,102 @@
1
+ /**
2
+ * Qoder Adapter
3
+ *
4
+ * Emits per-skill rule files into .qoder/rules/ — Qoder's documented project-level
5
+ * rule directory. Qoder also reads AGENTS.md as its project-context file.
6
+ *
7
+ * Qoder rules dir: .qoder/rules/
8
+ * Qoder rule file: .qoder/rules/rune-{name}.md (one file per skill)
9
+ * Qoder project context: AGENTS.md (open AGENTS.md standard)
10
+ *
11
+ * @see https://docs.qoder.com/user-guide/rules
12
+ * @see https://agents.md/
13
+ *
14
+ * MODEL TIER MAPPING (v2.18+):
15
+ * Qoder is provider-agnostic — emits semantic tier hints rather than concrete
16
+ * model names. Qoder IDE resolves the tier to its configured provider model.
17
+ */
18
+
19
+ import { BRANDING_FOOTER } from '../transforms/branding.js';
20
+
21
+ const MODEL_MAP = {
22
+ opus: 'tier:heavy',
23
+ sonnet: 'tier:mid',
24
+ haiku: 'tier:light',
25
+ };
26
+
27
+ const TOOL_MAP = {
28
+ Read: 'read the file',
29
+ Write: 'write/create the file',
30
+ Edit: 'edit the file',
31
+ Glob: 'find files by pattern',
32
+ Grep: 'search file contents',
33
+ Bash: 'run a shell command',
34
+ TodoWrite: 'track task progress',
35
+ Skill: 'follow the referenced rune-{name} rule',
36
+ Agent: 'execute the workflow',
37
+ };
38
+
39
+ export default {
40
+ name: 'qoder',
41
+ outputDir: '.qoder/rules',
42
+ fileExtension: '.md',
43
+ skillPrefix: 'rune-',
44
+ skillSuffix: '',
45
+
46
+ // Qoder rules are flat .md files, not directory-per-skill
47
+ useSkillDirectories: false,
48
+
49
+ transformReference(skillName, raw) {
50
+ const isBackticked = raw.startsWith('`') && raw.endsWith('`');
51
+ const ref = `the rune-${skillName} rule`;
52
+ return isBackticked ? `\`${ref}\`` : ref;
53
+ },
54
+
55
+ transformToolName(toolName) {
56
+ return TOOL_MAP[toolName] || toolName;
57
+ },
58
+
59
+ generateHeader(skill) {
60
+ const desc = (skill.description || '').replace(/"/g, '\\"');
61
+ const lines = ['---', `name: rune-${skill.name}`, `description: "${desc}"`];
62
+ const translatedModel = skill.model ? MODEL_MAP[skill.model] || skill.model : null;
63
+ if (translatedModel) lines.push(`model: ${translatedModel}`);
64
+ lines.push('---', '', '');
65
+ return lines.join('\n');
66
+ },
67
+
68
+ generateFooter() {
69
+ return BRANDING_FOOTER;
70
+ },
71
+
72
+ transformSubagentInstruction(text) {
73
+ return text;
74
+ },
75
+
76
+ scriptsDir(skillName) {
77
+ return `rune-${skillName}-scripts`;
78
+ },
79
+
80
+ postProcess(content) {
81
+ return content.replace(/^context: fork\n/gm, '').replace(/^agent: general-purpose\n/gm, '');
82
+ },
83
+
84
+ // Qoder reads AGENTS.md at the project root as its high-level context file.
85
+ generateExtraFiles({ stats }) {
86
+ const agentsMd = [
87
+ '# Rune — Project Configuration',
88
+ '',
89
+ 'Rune is an interconnected skill ecosystem for AI coding assistants.',
90
+ `${stats.skillCount} core skills + ${stats.packCount} extension packs.`,
91
+ '',
92
+ 'Per-skill rules live under `.qoder/rules/rune-<name>.md`. Qoder loads them automatically.',
93
+ '',
94
+ 'Reference a skill by name (e.g. "follow the rune-cook rule") inside any chat — the rule file is auto-injected.',
95
+ '',
96
+ '---',
97
+ '> Rune Skill Mesh — https://github.com/rune-kit/rune',
98
+ '',
99
+ ].join('\n');
100
+ return [{ path: 'AGENTS.md', content: agentsMd }];
101
+ },
102
+ };
@@ -0,0 +1,111 @@
1
+ /**
2
+ * Qwen Coder Adapter
3
+ *
4
+ * Emits per-skill rule files into qwen/skills/ and a top-level QWEN.md that
5
+ * uses Qwen Code's @import syntax to load each rule file. Qwen Code loads
6
+ * QWEN.md hierarchically (cwd → parent → ~/.qwen/QWEN.md).
7
+ *
8
+ * Qwen rules dir: qwen/skills/
9
+ * Qwen rule file: qwen/skills/rune-{name}.md
10
+ * Qwen project context: QWEN.md at project root (with @path imports)
11
+ *
12
+ * @see https://qwenlm.github.io/qwen-code-docs/en/users/configuration/settings/
13
+ * @see https://github.com/QwenLM/qwen-code
14
+ *
15
+ * MODEL TIER MAPPING (v2.18+):
16
+ * Qwen Coder defaults to Qwen3-Coder family. Anthropic-style tier names
17
+ * translate to Qwen size hints (heavy=qwen3-coder-plus, mid=qwen3-coder,
18
+ * light=qwen3-coder-flash). Hint only — Qwen Code reads model from settings.
19
+ */
20
+
21
+ import { BRANDING_FOOTER } from '../transforms/branding.js';
22
+
23
+ const MODEL_MAP = {
24
+ opus: 'qwen3-coder-plus',
25
+ sonnet: 'qwen3-coder',
26
+ haiku: 'qwen3-coder-flash',
27
+ };
28
+
29
+ const TOOL_MAP = {
30
+ Read: 'read the file',
31
+ Write: 'write/create the file',
32
+ Edit: 'edit the file',
33
+ Glob: 'find files by pattern',
34
+ Grep: 'search file contents',
35
+ Bash: 'run a shell command',
36
+ TodoWrite: 'track task progress',
37
+ Skill: 'follow the imported rune-{name} skill',
38
+ Agent: 'execute the workflow',
39
+ };
40
+
41
+ export default {
42
+ name: 'qwen',
43
+ outputDir: 'qwen/skills',
44
+ fileExtension: '.md',
45
+ skillPrefix: 'rune-',
46
+ skillSuffix: '',
47
+
48
+ // Qwen skills are flat per-skill markdown files imported from QWEN.md.
49
+ useSkillDirectories: false,
50
+
51
+ transformReference(skillName, raw) {
52
+ const isBackticked = raw.startsWith('`') && raw.endsWith('`');
53
+ const ref = `the rune-${skillName} skill`;
54
+ return isBackticked ? `\`${ref}\`` : ref;
55
+ },
56
+
57
+ transformToolName(toolName) {
58
+ return TOOL_MAP[toolName] || toolName;
59
+ },
60
+
61
+ generateHeader(skill) {
62
+ const desc = (skill.description || '').replace(/"/g, '\\"');
63
+ const lines = ['---', `name: rune-${skill.name}`, `description: "${desc}"`];
64
+ const translatedModel = skill.model ? MODEL_MAP[skill.model] || skill.model : null;
65
+ if (translatedModel) lines.push(`# model-hint: ${translatedModel}`);
66
+ lines.push('---', '', '');
67
+ return lines.join('\n');
68
+ },
69
+
70
+ generateFooter() {
71
+ return BRANDING_FOOTER;
72
+ },
73
+
74
+ transformSubagentInstruction(text) {
75
+ return text;
76
+ },
77
+
78
+ scriptsDir(skillName) {
79
+ return `rune-${skillName}-scripts`;
80
+ },
81
+
82
+ postProcess(content) {
83
+ return content.replace(/^context: fork\n/gm, '').replace(/^agent: general-purpose\n/gm, '');
84
+ },
85
+
86
+ // Emit QWEN.md at project root with @import lines for every per-skill file.
87
+ generateExtraFiles({ stats }) {
88
+ const skillFiles = stats.files
89
+ .filter((f) => f.startsWith('rune-') && f.endsWith('.md') && !f.includes('/') && !f.includes('\\'))
90
+ .sort();
91
+
92
+ const qwenMd = [
93
+ '# Rune — Project Configuration',
94
+ '',
95
+ 'Rune is an interconnected skill ecosystem for AI coding assistants.',
96
+ `${stats.skillCount} core skills + ${stats.packCount} extension packs.`,
97
+ '',
98
+ '## Loaded Skills',
99
+ '',
100
+ 'Qwen Code loads each referenced file as part of this project context.',
101
+ '',
102
+ ...skillFiles.map((f) => `@qwen/skills/${f}`),
103
+ '',
104
+ '---',
105
+ '> Rune Skill Mesh — https://github.com/rune-kit/rune',
106
+ '',
107
+ ].join('\n');
108
+
109
+ return [{ path: 'QWEN.md', content: qwenMd }];
110
+ },
111
+ };
@@ -20,13 +20,13 @@ import { fileURLToPath } from 'node:url';
20
20
  import { getAdapter, listPlatforms } from '../adapters/index.js';
21
21
  import { getAllAnalytics } from '../analytics.js';
22
22
  import { dispatchHook } from '../commands/hook-dispatch.js';
23
+ import { checkHookDrift, formatHookDriftResult } from '../commands/hooks/drift.js';
23
24
  import { installHooks } from '../commands/hooks/install.js';
24
25
  import { hookStatus } from '../commands/hooks/status.js';
25
26
  import { uninstallHooks } from '../commands/hooks/uninstall.js';
27
+ import { formatSetupResult, runSetup } from '../commands/setup.js';
26
28
  import { generateDashboardHTML } from '../dashboard.js';
27
29
  import { checkMeshIntegrity, formatDoctorResults, formatMeshResults, runDoctor } from '../doctor.js';
28
- import { checkHookDrift, formatHookDriftResult } from '../commands/hooks/drift.js';
29
- import { runSetup, formatSetupResult } from '../commands/setup.js';
30
30
  import { buildAll } from '../emitter.js';
31
31
  import { collectStats, renderStatus, renderStatusJson } from '../status.js';
32
32
  import { collectGraphData, generateMeshHTML } from '../visualizer.js';
@@ -720,7 +720,9 @@ async function main() {
720
720
  log(' Rune CLI — Skill mesh for AI coding assistants');
721
721
  log('');
722
722
  log(' Commands:');
723
- log(' setup Interactive wizard — auto-detect tiers, pick scope, install hooks (recommended for first-time)');
723
+ log(
724
+ ' setup Interactive wizard — auto-detect tiers, pick scope, install hooks (recommended for first-time)',
725
+ );
724
726
  log(' [--here|--global] [--tier pro,business] [--preset gentle|strict] [--dry]');
725
727
  log(' init Interactive setup for build pipeline (auto-detects platform)');
726
728
  log(' build Compile skills for configured platform');
@@ -62,7 +62,8 @@ export async function checkHookDrift(projectRoot) {
62
62
  platform: id,
63
63
  event: null,
64
64
  status: 'mixed-preset',
65
- message: 'settings.json contains both gentle and strict Rune entries — re-run `rune hooks install --preset <gentle|strict>` to converge',
65
+ message:
66
+ 'settings.json contains both gentle and strict Rune entries — re-run `rune hooks install --preset <gentle|strict>` to converge',
66
67
  });
67
68
  }
68
69
 
@@ -159,7 +160,9 @@ export function formatHookDriftResult(result) {
159
160
  }
160
161
 
161
162
  lines.push('');
162
- lines.push(` Summary: ${result.summary.drifted} drifted, ${result.summary.missing} missing, ${result.summary.errors} error(s).`);
163
+ lines.push(
164
+ ` Summary: ${result.summary.drifted} drifted, ${result.summary.missing} missing, ${result.summary.errors} error(s).`,
165
+ );
163
166
  lines.push(' Resolution: re-run `rune hooks install --preset <gentle|strict>` to re-converge.');
164
167
  lines.push(' This is a reporter — operator decides what to do with the findings.');
165
168
 
@@ -222,7 +222,9 @@ export function formatSetupResult(result) {
222
222
  lines.push('');
223
223
  lines.push(' Rune Setup Complete');
224
224
  lines.push(' ──────────────────');
225
- lines.push(` Scope: ${result.scope === 'global' ? 'GLOBAL (~/.claude/settings.json)' : `current project (${result.targetRoot})`}`);
225
+ lines.push(
226
+ ` Scope: ${result.scope === 'global' ? 'GLOBAL (~/.claude/settings.json)' : `current project (${result.targetRoot})`}`,
227
+ );
226
228
  lines.push(` Tiers: Free${result.tiers.length > 0 ? ` + ${result.tiers.join(' + ')}` : ''}`);
227
229
  lines.push(` Preset: ${result.preset}`);
228
230
  lines.push(` Platforms: ${(result.platforms || []).join(', ') || '—'}`);
@@ -700,11 +700,44 @@ export async function buildAll({
700
700
  await writeFile(path.join(outputDir, 'skill-index.json'), `${JSON.stringify(skillIndex, null, 2)}\n`, 'utf-8');
701
701
  stats.files.push('skill-index.json');
702
702
 
703
- // Generate AGENTS.md for Codex (OpenAI convention not used by other platforms)
704
- if (adapter.name === 'codex') {
705
- const agentsMdContent = generateAgentsMd(stats, adapter);
706
- await writeFile(path.join(outputRoot, 'AGENTS.md'), agentsMdContent, 'utf-8');
707
- stats.files.push('AGENTS.md');
703
+ // Generic extra-files hook: any adapter can emit additional index/bundle files
704
+ // alongside per-skill files (e.g. aider's .aider.conf.yml, qwen's QWEN.md, gemini's GEMINI.md bundle).
705
+ // Hook contract: paths MUST be relative to outputRoot — absolute paths are rejected to prevent
706
+ // accidental writes outside the project tree. Adapters receive a frozen stats snapshot so reads
707
+ // are deterministic regardless of where the hook fires relative to other emit steps.
708
+ if (typeof adapter.generateExtraFiles === 'function') {
709
+ const frozenStats = Object.freeze({ ...stats, files: Object.freeze([...stats.files]) });
710
+ const extras = await adapter.generateExtraFiles({
711
+ parsedSkills,
712
+ stats: frozenStats,
713
+ runeRoot,
714
+ outputRoot,
715
+ outputDir,
716
+ });
717
+ if (Array.isArray(extras)) {
718
+ const outputRootResolved = path.resolve(outputRoot);
719
+ for (const extra of extras) {
720
+ if (!extra || !extra.path || extra.content == null) continue;
721
+ if (path.isAbsolute(extra.path)) {
722
+ stats.errors.push({
723
+ adapter: adapter.name,
724
+ error: `generateExtraFiles must return relative paths, got absolute: ${extra.path}`,
725
+ });
726
+ continue;
727
+ }
728
+ const fullPath = path.resolve(outputRootResolved, extra.path);
729
+ if (fullPath !== outputRootResolved && !fullPath.startsWith(outputRootResolved + path.sep)) {
730
+ stats.errors.push({
731
+ adapter: adapter.name,
732
+ error: `generateExtraFiles path escapes outputRoot: ${extra.path}`,
733
+ });
734
+ continue;
735
+ }
736
+ await mkdir(path.dirname(fullPath), { recursive: true });
737
+ await writeFile(fullPath, extra.content, 'utf-8');
738
+ stats.files.push(path.relative(outputRoot, fullPath).replaceAll('\\', '/'));
739
+ }
740
+ }
708
741
  }
709
742
 
710
743
  // OpenClaw adapter: generate manifest + TypeScript entry point
@@ -774,42 +807,6 @@ function generateIndex(stats, adapter) {
774
807
  return lines.join('\n');
775
808
  }
776
809
 
777
- /**
778
- * Generate AGENTS.md for Codex (OpenAI convention)
779
- * Uses dynamic counts from build stats — no hardcoded skill lists
780
- */
781
- function generateAgentsMd(stats, adapter) {
782
- const lines = [
783
- '# Rune — Project Configuration',
784
- '',
785
- '## Overview',
786
- '',
787
- 'Rune is an interconnected skill ecosystem for AI coding assistants.',
788
- `${stats.skillCount} core skills | 5-layer mesh architecture | ${stats.crossRefsResolved} connections | Multi-platform.`,
789
- 'Philosophy: "Less skills. Deeper connections."',
790
- '',
791
- `Platform: ${adapter.name}`,
792
- '',
793
- '## Skills',
794
- '',
795
- `**${stats.skillCount} core skills** + **${stats.packCount} extension packs**`,
796
- '',
797
- '## Usage',
798
- '',
799
- 'Reference skills using the `Skill` tool or delegate to subagents using the `Agent` tool.',
800
- '',
801
- '## Skills Directory',
802
- '',
803
- `Skills are located in: ${adapter.outputDir}/`,
804
- '',
805
- '---',
806
- '> Rune Skill Mesh — https://github.com/rune-kit/rune',
807
- '',
808
- ];
809
-
810
- return lines.join('\n');
811
- }
812
-
813
810
  /**
814
811
  * Intent keyword patterns for each skill — extracted from description + Triggers section
815
812
  * Maps common user intent words to the skill that handles them
@@ -69,3 +69,94 @@ if (fs.existsSync(runeDir)) {
69
69
  } else {
70
70
  console.log('[Rune: No .rune/ directory found. Run /rune onboard to set up project context.]');
71
71
  }
72
+
73
+ // Tier detection hint (v2.17.1+) — Pro/Business plugins live in private repos
74
+ // and aren't auto-loaded like the Free plugin. If detected at sibling / env /
75
+ // well-known path AND tier hooks aren't already wired in settings.json, nudge
76
+ // user toward `rune setup`. Self-suppressing — once wired, the check fails and
77
+ // the hint stops firing.
78
+ detectTierHint();
79
+
80
+ function detectTierHint() {
81
+ const envVars = { pro: 'RUNE_PRO_ROOT', business: 'RUNE_BUSINESS_ROOT' };
82
+ const wellKnown = {
83
+ pro: [
84
+ 'D:/Project/Rune/Pro',
85
+ path.join(os.homedir(), 'rune-pro'),
86
+ path.join(os.homedir(), 'Project', 'Rune', 'Pro'),
87
+ ],
88
+ business: [
89
+ 'D:/Project/Rune/Business',
90
+ path.join(os.homedir(), 'rune-business'),
91
+ path.join(os.homedir(), 'Project', 'Rune', 'Business'),
92
+ ],
93
+ };
94
+
95
+ const detected = [];
96
+ for (const tier of ['pro', 'business']) {
97
+ let manifest = null;
98
+ let source = null;
99
+
100
+ const fromEnv = process.env[envVars[tier]];
101
+ if (fromEnv) {
102
+ const m = path.join(fromEnv, 'hooks', 'manifest.json');
103
+ if (fs.existsSync(m)) {
104
+ manifest = m;
105
+ source = `$${envVars[tier]}`;
106
+ }
107
+ }
108
+ if (!manifest) {
109
+ const m = path.join(cwd, '..', tier === 'pro' ? 'Pro' : 'Business', 'hooks', 'manifest.json');
110
+ if (fs.existsSync(m)) {
111
+ manifest = m;
112
+ source = 'sibling';
113
+ }
114
+ }
115
+ if (!manifest) {
116
+ for (const root of wellKnown[tier]) {
117
+ const m = path.join(root, 'hooks', 'manifest.json');
118
+ if (fs.existsSync(m)) {
119
+ manifest = m;
120
+ source = 'well-known';
121
+ break;
122
+ }
123
+ }
124
+ }
125
+
126
+ if (manifest) {
127
+ detected.push({ tier, source, version: readManifestVersion(manifest) });
128
+ }
129
+ }
130
+
131
+ if (detected.length === 0) return;
132
+
133
+ // Suppress hint if any tier hook already wired (project-local OR global)
134
+ const tierEnvRe = /\$\{RUNE_[A-Z][A-Z0-9_]*_ROOT\}/;
135
+ const settingsPaths = [path.join(cwd, '.claude', 'settings.json'), path.join(os.homedir(), '.claude', 'settings.json')];
136
+ for (const settingsPath of settingsPaths) {
137
+ if (!fs.existsSync(settingsPath)) continue;
138
+ try {
139
+ const content = fs.readFileSync(settingsPath, 'utf-8');
140
+ if (tierEnvRe.test(content)) return;
141
+ } catch {
142
+ // ignore unreadable settings.json — fall through to print hint
143
+ }
144
+ }
145
+
146
+ console.log('\n=== Rune Tier Hint ===');
147
+ for (const { tier, source, version } of detected) {
148
+ const cap = tier.charAt(0).toUpperCase() + tier.slice(1);
149
+ console.log(`${cap} detected: ${source} (v${version})`);
150
+ }
151
+ const tierFlag = detected.map((d) => d.tier).join(',');
152
+ console.log(`Wire it: \`npx @rune-kit/rune setup --global --tier ${tierFlag}\``);
153
+ console.log('(adds tier-specific hooks: autopilot circuit-breaker, context-sense, statusline)');
154
+ }
155
+
156
+ function readManifestVersion(manifestPath) {
157
+ try {
158
+ return JSON.parse(fs.readFileSync(manifestPath, 'utf-8')).version || 'unknown';
159
+ } catch {
160
+ return 'unknown';
161
+ }
162
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rune-kit/rune",
3
- "version": "2.17.1",
3
+ "version": "2.18.0",
4
4
  "description": "64-skill mesh for AI coding assistants — runtime auto-discipline via native hooks (Claude/Cursor/Windsurf/Antigravity), 5-layer architecture, 215+ connections, multi-platform compiler. v2.17 adds quarantine L3 for prompt-injection advisory on untrusted external content.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: asset-creator
3
- description: "Creates code-based visual assets — SVG icons, OG image HTML templates, social banners, and icon sets. Outputs files with usage instructions."
3
+ description: "Creates code-based visual assets — SVG icons, OG image HTML templates, social banners, and icon sets. Use when generating visuals as code (vector/HTML), NOT raster images — for raster generation see @rune-pro/media. Outputs files with usage instructions."
4
4
  metadata:
5
5
  author: runedev
6
6
  version: "0.2.0"
@@ -1,9 +1,9 @@
1
1
  ---
2
2
  name: audit
3
- description: "Comprehensive project audit — security, dependencies, code quality, architecture, performance, infra, docs, and mesh analytics. Delegates to specialist skills and generates an 8-dimension health score."
3
+ description: "Comprehensive project audit — security, dependencies, code quality, architecture, performance, infra, docs, and mesh analytics. Use when needing a full 8-dimension health-score snapshot before a major release, M&A diligence, or quarterly review. Delegates to specialist skills (sentinel, dependency-doctor, perf, autopsy, etc.)."
4
4
  metadata:
5
5
  author: runedev
6
- version: "0.4.0"
6
+ version: "0.5.0"
7
7
  layer: L2
8
8
  model: sonnet
9
9
  group: quality
@@ -130,6 +130,24 @@ grep -rn "\.unwrap()" src/ --include="*.rs"
130
130
 
131
131
  Merge autopsy report + supplementary findings.
132
132
 
133
+ **3.5 Zombie Code Detection**
134
+
135
+ Identify code that is effectively dead but hasn't been formally removed. Zombie code increases surface area, confuses contributors, and accumulates security debt.
136
+
137
+ | Signal | Detection Method | Severity |
138
+ |--------|-----------------|----------|
139
+ | No commits in 6+ months | `git log --since="6 months ago" -- <file>` returns empty | MEDIUM |
140
+ | No test coverage | File not imported by any test file (Grep for filename in `**/*.test.*` / `**/*.spec.*`) | MEDIUM |
141
+ | No owner | File not in CODEOWNERS, no author active in last 6 months | LOW |
142
+ | Failing tests referencing the module | Test suite has skipped/failing tests for this module | HIGH |
143
+ | Unpatched CVEs in dependencies only this module uses | Cross-reference dependency-doctor CVE list with per-file imports | HIGH |
144
+ | Orphaned docs | README/docs reference files or APIs that no longer exist | LOW |
145
+
146
+ **Zombie Code Verdict:**
147
+ - 3+ signals on same file/module → flag as **ZOMBIE** in audit report
148
+ - Recommend: archive (move to `_deprecated/`), delete, or assign owner
149
+ - Do NOT auto-delete — present zombie list to user for decision
150
+
133
151
  ---
134
152
 
135
153
  ### Phase 4: Architecture Audit