agent-skill-doctor 0.1.0 → 0.3.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.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,17 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.3.0] - 2026-07-07
9
+
10
+ ### Added
11
+ - Added `governance` registry readiness diagnostics for owner, version, lifecycle status, release label, and trusted source metadata.
12
+ - Added `agent-skill-doctor governance --json` for focused governance findings.
13
+ - Added governance findings to diagnose summaries, Markdown reports, JSON reports, and HTML dashboard cards.
14
+ - Added targeted `fix --type governance` guide text in English and Chinese.
15
+
16
+ ### Documentation
17
+ - Documented governance checks and CLI examples in English and Chinese READMEs.
18
+
8
19
  ## [0.1.0] - 2026-06-08
9
20
 
10
21
  ### Added
package/README.en.md CHANGED
@@ -23,6 +23,7 @@ Agent Skill Doctor diagnoses local AI Agent Skills: duplicate installs, version
23
23
  - `conflict`: contradictory instructions, such as `npm install` vs `pnpm install`.
24
24
  - `duplicate`: exact, same-source, or same-name duplicate skills.
25
25
  - `version_drift`: the same skill installed with different refs or content.
26
+ - `governance`: registry / team-sharing readiness, such as missing owner, version, lifecycle status, stable/dev label, or trusted source.
26
27
  - `zombie`: low-activity or possibly abandoned skills.
27
28
  - `description_quality`: missing trigger, input/output, risk notes, or too-short descriptions.
28
29
  - `scan_warning`: missing `SKILL.md` or malformed frontmatter.
@@ -67,7 +68,7 @@ Use agent-skill-doctor to diagnose my local Agent Skills:
67
68
  2. Generate an HTML report: npx agent-skill-doctor report --format html --lang en
68
69
  3. Review conflicts, duplicates, version drift, zombie skills, and risks.
69
70
  4. Do not delete files yet. First produce a repair plan and explain which skills would change.
70
- 5. Give recommendations for risk, duplicate, version_drift, zombie, and description_quality findings.
71
+ 5. Give recommendations for risk, duplicate, version_drift, governance, zombie, and description_quality findings.
71
72
  ```
72
73
 
73
74
  The agent can use `fix` to generate targeted repair prompts:
@@ -174,6 +175,7 @@ agent-skill-doctor diagnose --json
174
175
  agent-skill-doctor risks --json
175
176
  agent-skill-doctor conflicts --json
176
177
  agent-skill-doctor duplicates --json
178
+ agent-skill-doctor governance --json
177
179
  agent-skill-doctor zombies --json
178
180
 
179
181
  # Generate reports
@@ -185,6 +187,7 @@ agent-skill-doctor report --format html --lang en
185
187
  agent-skill-doctor fix --lang en
186
188
  agent-skill-doctor fix --type duplicate --lang en
187
189
  agent-skill-doctor fix --type version_drift --lang en
190
+ agent-skill-doctor fix --type governance --lang en
188
191
 
189
192
  # Fail CI by severity
190
193
  agent-skill-doctor diagnose --ci --fail-on high
package/README.md CHANGED
@@ -23,6 +23,7 @@ Agent Skill Doctor 用来帮你检查本地 AI Agent Skills 是否健康:有
23
23
  - `conflict`:Skill 之间的指令冲突,例如一个要求 `npm install`,另一个要求 `pnpm install`。
24
24
  - `duplicate`:重复 Skill,包含完全重复、同源重复、同名不同内容。
25
25
  - `version_drift`:同一个 Skill 在多个位置存在不同版本、不同 ref 或不同内容。
26
+ - `governance`:Registry / 团队共享就绪度,例如缺少 owner、version、生命周期状态、stable/dev 标签或可信来源。
26
27
  - `zombie`:疑似长期不用或无人维护的 Skill。
27
28
  - `description_quality`:缺少触发条件、输入输出、风险说明,或者描述过短。
28
29
  - `scan_warning`:目录结构问题,例如缺少 `SKILL.md` 或 frontmatter 格式异常。
@@ -174,6 +175,7 @@ agent-skill-doctor diagnose --json
174
175
  agent-skill-doctor risks --json
175
176
  agent-skill-doctor conflicts --json
176
177
  agent-skill-doctor duplicates --json
178
+ agent-skill-doctor governance --json
177
179
  agent-skill-doctor zombies --json
178
180
 
179
181
  # 生成报告
@@ -185,6 +187,7 @@ agent-skill-doctor report --format html --lang zh
185
187
  agent-skill-doctor fix --lang zh
186
188
  agent-skill-doctor fix --type duplicate --lang zh
187
189
  agent-skill-doctor fix --type version_drift --lang zh
190
+ agent-skill-doctor fix --type governance --lang zh
188
191
 
189
192
  # CI 中按严重程度失败
190
193
  agent-skill-doctor diagnose --ci --fail-on high
@@ -11,9 +11,21 @@ const { detectConflicts } = require('../src/doctor/conflict');
11
11
  const { detectZombies } = require('../src/doctor/zombie');
12
12
  const { DEFAULT_CONFLICT_RULES } = require('../src/doctor/rules');
13
13
  const phase2 = require('../src/doctor/phase2');
14
+ const { detectGovernanceFindings } = require('../src/doctor/governance');
14
15
  const { t, dictionaries } = require('../src/doctor/i18n');
15
16
 
16
- const SKIP_DIRS = new Set(['.git', 'node_modules', 'target', 'dist', 'build', '.cache', '.tmp', '.DS_Store']);
17
+ const SKIP_DIRS = new Set([
18
+ '.git', 'node_modules', 'target', 'dist', 'build', '.tmp', '.DS_Store',
19
+ // Agent-specific non-skill directories
20
+ 'sessions', 'backups', 'shell-snapshots', 'session-env',
21
+ 'debug', 'file-history', 'paste-cache', 'plans',
22
+ 'daemon', 'ide', 'hooks', 'logs', 'log', 'errors',
23
+ 'archived_sessions', 'worktrees', 'sqlite',
24
+ 'accounts', 'memories', 'rules', 'docs', 'vendor_imports',
25
+ 'process_manager', 'node_repl', 'ambient-suggestions',
26
+ 'automations', 'codexmate', 'computer-use', 'computer-use-turn-ended',
27
+ 'browser', 'pets',
28
+ ]);
17
29
 
18
30
  function sha256(input) {
19
31
  return crypto.createHash('sha256').update(input).digest('hex');
@@ -34,6 +46,10 @@ function normalizePath(p) {
34
46
  return path.resolve(expandHome(p)).replace(/\\/g, '/');
35
47
  }
36
48
 
49
+ function isDir(p) {
50
+ try { return fs.existsSync(p) && fs.statSync(p).isDirectory(); } catch { return false; }
51
+ }
52
+
37
53
  function ensureDir(dir) {
38
54
  fs.mkdirSync(dir, { recursive: true });
39
55
  }
@@ -106,34 +122,76 @@ function loadConfig() {
106
122
  const home = doctorHome();
107
123
  ensureDir(home);
108
124
  ensureDir(path.join(home, 'reports'));
109
- const candidates = [
110
- // Central library
111
- '~/.skills-manager/skills',
125
+
126
+ // Agent root directories (global)
127
+ const agentRoots = [
112
128
  '~/.skills-manager',
113
- // Agent global skill directories requested by users.
114
- '~/.agent/skills',
115
- '~/.agents/skills',
116
- '~/.agents/skills-core',
117
- '~/.codex/skills',
118
- '~/.claude/skills',
119
- '~/.cursor/skills',
120
- '~/.opencode/skills',
121
- // Project-local skill directories
122
- path.join(process.cwd(), '.agent/skills'),
123
- path.join(process.cwd(), '.agents/skills'),
124
- path.join(process.cwd(), '.codex/skills'),
125
- path.join(process.cwd(), '.claude/skills'),
126
- path.join(process.cwd(), '.cursor/skills'),
127
- path.join(process.cwd(), '.opencode/skills'),
128
- ].map(expandHome).filter(p => {
129
- try { return fs.existsSync(p) && fs.statSync(p).isDirectory(); } catch { return false; }
130
- });
129
+ '~/.agent',
130
+ '~/.agents',
131
+ '~/.codex',
132
+ '~/.claude',
133
+ '~/.cursor',
134
+ '~/.opencode',
135
+ '~/.windsurf',
136
+ '~/.aider',
137
+ '~/.continue',
138
+ '~/.cody',
139
+ '~/.copilot',
140
+ ];
141
+
142
+ // Project-local agent directories
143
+ const projectAgents = [
144
+ '.agent',
145
+ '.agents',
146
+ '.codex',
147
+ '.claude',
148
+ '.cursor',
149
+ '.opencode',
150
+ '.windsurf',
151
+ '.aider',
152
+ '.continue',
153
+ '.cody',
154
+ '.copilot',
155
+ ];
156
+
157
+ const candidates = [];
158
+
159
+ // For each agent root, discover skill directories automatically
160
+ for (const root of agentRoots) {
161
+ const expanded = expandHome(root);
162
+ if (!isDir(expanded)) continue;
163
+
164
+ // Add the root itself (for ~/.skills-manager pattern)
165
+ candidates.push(expanded);
166
+
167
+ // Add skills/ subdirectory if exists
168
+ const skillsDir = path.join(expanded, 'skills');
169
+ if (isDir(skillsDir)) candidates.push(skillsDir);
170
+
171
+ // Add skills-core/ subdirectory if exists (for ~/.agents/skills-core)
172
+ const skillsCoreDir = path.join(expanded, 'skills-core');
173
+ if (isDir(skillsCoreDir)) candidates.push(skillsCoreDir);
174
+
175
+ // Add plugins/ subdirectory if exists (for plugin cache/marketplace)
176
+ const pluginsDir = path.join(expanded, 'plugins');
177
+ if (isDir(pluginsDir)) candidates.push(pluginsDir);
178
+ }
179
+
180
+ // For project-local directories
181
+ for (const agent of projectAgents) {
182
+ const expanded = path.join(process.cwd(), agent);
183
+ if (!isDir(expanded)) continue;
184
+
185
+ const skillsDir = path.join(expanded, 'skills');
186
+ if (isDir(skillsDir)) candidates.push(skillsDir);
187
+ }
188
+
131
189
  return {
132
190
  home,
133
191
  dbPath: path.join(home, 'doctor.db'),
134
192
  reportsDir: path.join(home, 'reports'),
135
193
  scan: { maxDepth: 6 },
136
- roots: candidates,
194
+ roots: [...new Set(candidates)], // deduplicate
137
195
  };
138
196
  }
139
197
 
@@ -691,6 +749,12 @@ function runPhase2Analysis(db, skills) {
691
749
  return { groups, drifts };
692
750
  }
693
751
 
752
+ function runGovernanceAnalysis(db, skills) {
753
+ const findings = detectGovernanceFindings(skills);
754
+ for (const finding of findings) upsertFinding(db, finding, finding.links || []);
755
+ return findings;
756
+ }
757
+
694
758
  function recordRun(db, run) {
695
759
  db.prepare('INSERT INTO doctor_runs (id, started_at, finished_at, status, skill_count, finding_count, duplicate_group_count, high_count, critical_count, config_json, summary_json) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)').run(run.id, run.startedAt, run.finishedAt, run.status, run.skillCount, run.findingCount, 0, run.highCount, run.criticalCount, JSON.stringify(run.config || {}), JSON.stringify(run.summary || {}));
696
760
  }
@@ -727,6 +791,7 @@ function buildReportData(db, includeIgnored) {
727
791
  riskFindings: findings.filter(f => f.type === 'risk').length,
728
792
  conflictFindings: findings.filter(f => f.type === 'conflict').length,
729
793
  zombieCandidates: findings.filter(f => f.type === 'zombie').length,
794
+ governanceFindings: findings.filter(f => f.type === 'governance').length,
730
795
  descriptionQualityFindings: findings.filter(f => f.type === 'description_quality').length,
731
796
  duplicateFindings: findings.filter(f => f.type === 'duplicate').length,
732
797
  ignoredFindings: rows.filter(row => row.ignored).length,
@@ -749,6 +814,7 @@ function renderMarkdown(data, lang) {
749
814
  `- ${L('report.versionDriftFindings')}: ${data.summary.versionDriftFindings}`,
750
815
  `- ${L('report.conflictFindings')}: ${data.summary.conflictFindings}`,
751
816
  `- ${L('report.riskFindings')}: ${data.summary.riskFindings}`,
817
+ `- ${L('report.governanceFindings')}: ${data.summary.governanceFindings}`,
752
818
  `- ${L('report.zombieCandidates')}: ${data.summary.zombieCandidates}`,
753
819
  `- ${L('report.ignoredFindings')}: ${data.summary.ignoredFindings}`,
754
820
  `- ${L('report.bySeverity')}: ${JSON.stringify(data.summary.bySeverity)}`,
@@ -908,6 +974,7 @@ function renderHtml(data, lang, reportPath) {
908
974
  { key: 'report.totalFindings', descKey: 'dashboard.totalFindings.desc', value: data.summary.totalFindings, color: '#8b5cf6', tooltipTitle: null, tooltipText: null, filterFn: null },
909
975
  // Actionable — high priority (red)
910
976
  { key: 'report.conflictFindings', descKey: 'dashboard.conflictFindings.desc', value: data.summary.conflictFindings, color: '#ef4444', tooltipTitle: 'tooltip.conflict.title', tooltipText: 'tooltip.conflict.text', filterFn: f => f.type === 'conflict' },
977
+ { key: 'report.governanceFindings', descKey: 'dashboard.governanceFindings.desc', value: data.summary.governanceFindings, color: '#0f766e', tooltipTitle: 'tooltip.governance.title', tooltipText: 'tooltip.governance.text', filterFn: f => f.type === 'governance' },
911
978
  // Actionable — medium priority (orange)
912
979
  { key: 'report.zombieCandidates', descKey: 'dashboard.zombieCandidates.desc', value: data.summary.zombieCandidates, color: '#f59e0b', tooltipTitle: 'tooltip.zombie.title', tooltipText: 'tooltip.zombie.text', filterFn: f => f.type === 'zombie' },
913
980
  // Actionable — lower priority (yellow)
@@ -1367,6 +1434,7 @@ Commands:
1367
1434
  risks [--json] [--ci] [--fail-on high|critical|medium]
1368
1435
  conflicts [--json] [--ci] [--fail-on high|critical|medium]
1369
1436
  zombies [--json] [--ci] [--fail-on high|critical|medium]
1437
+ governance [--json] [--ci] [--fail-on high|critical|medium]
1370
1438
  plan [--safe|--normal|--aggressive] [--json] [--output <path>]
1371
1439
  apply <plan.json> --dry-run [--json]
1372
1440
  ignore <finding-id> [--reason <text>]
@@ -1374,7 +1442,7 @@ Commands:
1374
1442
  ignored list
1375
1443
  help
1376
1444
 
1377
- Fix types: risk, zombie, duplicate, conflict, version_drift, description_quality, scan_warning
1445
+ Fix types: risk, zombie, duplicate, conflict, version_drift, governance, description_quality, scan_warning
1378
1446
  Fix severity: critical, high, medium, low, info
1379
1447
  `);
1380
1448
  }
@@ -1473,6 +1541,9 @@ function runDiagnose(args) {
1473
1541
  const zombieFindings = detectZombies(skills);
1474
1542
  for (const f of zombieFindings) upsertFinding(db, f, f.links || []);
1475
1543
 
1544
+ // Governance readiness detection
1545
+ const governanceFindings = runGovernanceAnalysis(db, skills);
1546
+
1476
1547
  // Duplicate and version drift detection
1477
1548
  const phase2Result = runPhase2Analysis(db, skills);
1478
1549
 
@@ -1482,6 +1553,7 @@ function runDiagnose(args) {
1482
1553
  riskFindings: riskFindings.length,
1483
1554
  conflictFindings: conflictFindings.length,
1484
1555
  zombieCandidates: zombieFindings.length,
1556
+ governanceFindings: governanceFindings.length,
1485
1557
  duplicateGroups: phase2Result.groups.length,
1486
1558
  versionDriftFindings: phase2Result.drifts.length,
1487
1559
  };
@@ -1494,6 +1566,7 @@ function runDiagnose(args) {
1494
1566
  console.log(t('cli.riskFindings', lang, summary.riskFindings));
1495
1567
  console.log(t('cli.conflictFindings', lang, summary.conflictFindings));
1496
1568
  console.log(t('cli.zombieCandidates', lang, summary.zombieCandidates));
1569
+ console.log(t('cli.governanceFindings', lang, summary.governanceFindings));
1497
1570
  }
1498
1571
 
1499
1572
  if (args.ci) {
@@ -1684,7 +1757,7 @@ function runIgnored(args) {
1684
1757
 
1685
1758
  function runGuide(args) {
1686
1759
  const lang = args.lang || 'en';
1687
- const types = ['risk', 'zombie', 'duplicate', 'conflict', 'version_drift', 'description_quality', 'scan_warning'];
1760
+ const types = ['risk', 'zombie', 'duplicate', 'conflict', 'version_drift', 'governance', 'description_quality', 'scan_warning'];
1688
1761
  const lines = [];
1689
1762
  for (const type of types) {
1690
1763
  lines.push(`=== ${t(`guide.${type}.title`, lang)} ===`);
@@ -1721,7 +1794,7 @@ function runFix(args) {
1721
1794
  }
1722
1795
 
1723
1796
  // Validate type
1724
- const validTypes = ['risk', 'zombie', 'duplicate', 'conflict', 'version_drift', 'description_quality', 'scan_warning'];
1797
+ const validTypes = ['risk', 'zombie', 'duplicate', 'conflict', 'version_drift', 'governance', 'description_quality', 'scan_warning'];
1725
1798
  if (typeFilter && !validTypes.includes(typeFilter)) {
1726
1799
  console.error(`${lang === 'zh' ? '无效的问题类型' : 'Invalid type'}: ${typeFilter}. ${lang === 'zh' ? '可选值' : 'Valid values'}: ${validTypes.join(', ')}`);
1727
1800
  process.exit(3);
@@ -1819,6 +1892,7 @@ function main() {
1819
1892
  if (command === 'risks') return runFindingsByType(args, 'risk');
1820
1893
  if (command === 'conflicts') return runFindingsByType(args, 'conflict');
1821
1894
  if (command === 'zombies') return runFindingsByType(args, 'zombie');
1895
+ if (command === 'governance') return runFindingsByType(args, 'governance');
1822
1896
  if (command === 'plan') return runPlan(args);
1823
1897
  if (command === 'apply') return runApply(args);
1824
1898
  if (command === 'ignore') return runIgnore(args, true);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-skill-doctor",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "description": "Diagnostic and governance CLI for AI agent skills",
5
5
  "type": "commonjs",
6
6
  "main": "./src/doctor/index.js",
@@ -10,6 +10,7 @@
10
10
  "./conflict": "./src/doctor/conflict.js",
11
11
  "./zombie": "./src/doctor/zombie.js",
12
12
  "./risk": "./src/doctor/risk-lite.js",
13
+ "./governance": "./src/doctor/governance.js",
13
14
  "./rules": "./src/doctor/rules.js",
14
15
  "./i18n": "./src/doctor/i18n.js"
15
16
  },
@@ -0,0 +1,136 @@
1
+ 'use strict';
2
+
3
+ const crypto = require('node:crypto');
4
+ const { buildParticipantIdentityKey } = require('./phase2');
5
+
6
+ function sha256(input) {
7
+ return crypto.createHash('sha256').update(String(input)).digest('hex');
8
+ }
9
+
10
+ function normalizeList(value) {
11
+ if (Array.isArray(value)) return value.map(String).map(s => s.trim().toLowerCase()).filter(Boolean);
12
+ return String(value || '').split(',').map(s => s.trim().toLowerCase()).filter(Boolean);
13
+ }
14
+
15
+ function frontmatterValue(skill, keys) {
16
+ const frontmatter = skill.frontmatter || {};
17
+ for (const key of keys) {
18
+ const value = frontmatter[key];
19
+ if (value != null && String(value).trim()) return String(value).trim();
20
+ }
21
+ return '';
22
+ }
23
+
24
+ function hasTrustedSource(skill) {
25
+ const source = skill.source || {};
26
+ if (['builtin', 'marketplace'].includes(source.type)) return true;
27
+ if (['git', 'plugin'].includes(source.type) && source.url) return true;
28
+ return Boolean(skill.upstreamSkillId || skill.upstream_skill_id);
29
+ }
30
+
31
+ function lifecycleStatus(skill) {
32
+ return frontmatterValue(skill, ['lifecycle', 'status', 'release_status', 'registry_status', 'governance_status']);
33
+ }
34
+
35
+ function hasGovernanceLabel(skill) {
36
+ const tags = normalizeList(skill.tags || []);
37
+ const labels = normalizeList(frontmatterValue(skill, ['label', 'labels']));
38
+ return [...tags, ...labels].some(label => ['stable', 'dev', 'latest'].includes(label));
39
+ }
40
+
41
+ function makeFinding(skill, ruleId, severity, title, description, recommendation) {
42
+ const participantKey = buildParticipantIdentityKey([skill]);
43
+ const evidenceText = `${skill.slug || skill.name}: ${ruleId}`;
44
+ const signature = sha256(`${ruleId}:${skill.id || skill.slug || skill.name}`);
45
+ const id = sha256(`${participantKey}:governance:governance-detector:${ruleId}:${signature}`);
46
+ return {
47
+ id,
48
+ type: 'governance',
49
+ severity,
50
+ detectorId: 'governance-detector',
51
+ ruleId,
52
+ title,
53
+ description,
54
+ signature,
55
+ evidence: [{
56
+ file: skill.location?.path || skill.local_path || '',
57
+ text: evidenceText,
58
+ anchor: evidenceText.toLowerCase(),
59
+ }],
60
+ recommendation,
61
+ skills: [skill],
62
+ links: [{ skillId: skill.id, role: 'primary' }],
63
+ };
64
+ }
65
+
66
+ function detectGovernanceFindings(skills) {
67
+ const findings = [];
68
+
69
+ for (const skill of skills) {
70
+ const owner = frontmatterValue(skill, ['owner', 'owners', 'maintainer', 'maintainers']);
71
+ if (!owner) {
72
+ findings.push(makeFinding(
73
+ skill,
74
+ 'missing-owner',
75
+ 'medium',
76
+ 'Missing owner',
77
+ 'The skill has no owner or maintainer metadata, so registry review and rollback ownership are unclear.',
78
+ 'Add owner or maintainer metadata before sharing this skill through a registry.'
79
+ ));
80
+ }
81
+
82
+ const version = skill.version || frontmatterValue(skill, ['version']);
83
+ if (!version) {
84
+ findings.push(makeFinding(
85
+ skill,
86
+ 'missing-version',
87
+ 'medium',
88
+ 'Missing version',
89
+ 'The skill has no version metadata, which makes stable release and rollback decisions harder.',
90
+ 'Add a version field or lock the skill to an upstream ref before publishing.'
91
+ ));
92
+ }
93
+
94
+ if (!lifecycleStatus(skill)) {
95
+ findings.push(makeFinding(
96
+ skill,
97
+ 'missing-lifecycle-status',
98
+ 'medium',
99
+ 'Missing lifecycle status',
100
+ 'The skill has no governance lifecycle status such as draft, review, or online.',
101
+ 'Add lifecycle metadata such as draft, review, online, deprecated, or archived.'
102
+ ));
103
+ }
104
+
105
+ if (!hasGovernanceLabel(skill)) {
106
+ findings.push(makeFinding(
107
+ skill,
108
+ 'missing-governance-label',
109
+ 'low',
110
+ 'Missing governance label',
111
+ 'The skill has no stable, dev, or latest label to control adoption scope.',
112
+ 'Add a stable, dev, or latest label to make release targeting explicit.'
113
+ ));
114
+ }
115
+
116
+ if (!hasTrustedSource(skill)) {
117
+ findings.push(makeFinding(
118
+ skill,
119
+ 'missing-source',
120
+ 'high',
121
+ 'Missing trusted source',
122
+ 'The skill has no upstream or registry source, so users cannot tell which copy is authoritative.',
123
+ 'Add source metadata or import this skill into a central library or registry before team-wide use.'
124
+ ));
125
+ }
126
+ }
127
+
128
+ return findings;
129
+ }
130
+
131
+ module.exports = {
132
+ detectGovernanceFindings,
133
+ hasGovernanceLabel,
134
+ hasTrustedSource,
135
+ lifecycleStatus,
136
+ };
@@ -8,6 +8,7 @@ const en = {
8
8
  'cli.riskFindings': 'Risk findings: %s',
9
9
  'cli.conflictFindings': 'Conflict findings: %s',
10
10
  'cli.zombieCandidates': 'Zombie candidates: %s',
11
+ 'cli.governanceFindings': 'Governance findings: %s',
11
12
  'cli.reportWritten': 'Report written: %s',
12
13
  'cli.noFindings': 'No %s findings.',
13
14
  'cli.noDuplicateGroups': 'No duplicate groups. Run: agent-skill-doctor diagnose',
@@ -32,6 +33,7 @@ const en = {
32
33
  'report.riskFindings': 'Risk findings',
33
34
  'report.conflictFindings': 'Conflict findings',
34
35
  'report.zombieCandidates': 'Zombie candidates',
36
+ 'report.governanceFindings': 'Governance readiness',
35
37
  'report.descriptionQualityFindings': 'Description issues',
36
38
  'report.duplicateFindings': 'Duplicate findings',
37
39
  'report.ignoredFindings': 'Ignored',
@@ -118,6 +120,8 @@ const en = {
118
120
  'tooltip.zombie.formulaText': 'Score = presetCount==0 (0.25) + noAgents (0.20) + noProjects (0.20) + noModification (0.15) + noActivityLog (0.15) + lowDescriptionQuality (0.05). Then: official (*0), plugin (*0.5), thirdParty (*0.75). Max = 1.0.',
119
121
  'tooltip.conflict.title': 'Conflict Detection Criteria',
120
122
  'tooltip.conflict.text': 'Checks if 2+ skills match different alternatives within the same conflict rule. Built-in rules: package manager conflict (npm vs pnpm vs yarn), output format conflict (JSON vs Markdown). Binary detection: conflict exists or not.',
123
+ 'tooltip.governance.title': 'Governance Readiness Criteria',
124
+ 'tooltip.governance.text': 'Checks whether a skill is ready for team or registry use: owner, version, lifecycle status, release label, and trusted source metadata.',
121
125
  'tooltip.descriptionQuality.title': 'Description Quality (Informational)',
122
126
  'tooltip.descriptionQuality.text': 'Evaluates skill descriptions on 4 dimensions: length, risk mention, usage guidance, and structure. Low scores indicate poor documentation, but this does not affect skill functionality. Can be improved by Agent at low priority.',
123
127
  'tooltip.versionDrift.title': 'Version Drift Detection',
@@ -144,6 +148,7 @@ const en = {
144
148
  'type.zombie': 'Zombie',
145
149
  'type.duplicate': 'Duplicate',
146
150
  'type.version_drift': 'Version Drift',
151
+ 'type.governance': 'Governance',
147
152
  'type.description_quality': 'Description Quality',
148
153
  'type.scan_warning': 'Scan Warning',
149
154
 
@@ -193,6 +198,15 @@ const en = {
193
198
  'guide.version_drift.prompt': 'Please update all installations of skill "%s" to the latest version and consolidate to a single install location if possible.',
194
199
  'guide.version_drift.agentExample': 'Agent interaction example:\nUser: Please help me unify skill versions\nAgent: I\'ll check all installation locations and find the latest version...',
195
200
 
201
+ 'guide.governance.title': 'Governance Readiness',
202
+ 'guide.governance.definition': 'Governance findings indicate that a skill is missing metadata needed for team sharing, registry review, release labels, or rollback.',
203
+ 'guide.governance.cause': 'Common causes:\n• Skill was created locally without owner metadata\n• Skill has no version or lifecycle status\n• Skill has no stable/dev/latest label\n• Skill has no trusted source or upstream registry record',
204
+ 'guide.governance.severity': 'Severity explanation:\n• High: Missing trusted source\n• Medium: Missing owner, version, or lifecycle status\n• Low: Missing release label',
205
+ 'guide.governance.meaning': 'The skill may work locally, but it is not yet ready to be treated as a governed team asset.',
206
+ 'guide.governance.steps': 'Fix steps:\n1. Add owner or maintainer metadata\n2. Add version metadata\n3. Add lifecycle status such as draft, review, or online\n4. Add stable/dev/latest label\n5. Add source metadata or import into a central registry',
207
+ 'guide.governance.prompt': 'Please improve governance metadata for the skill at "%s". Add owner, version, lifecycle, label, and trusted source fields where missing.',
208
+ 'guide.governance.agentExample': 'Agent interaction example:\nUser: Please prepare this skill for registry sharing\nAgent: I\'ll inspect the skill metadata, add missing owner/version/lifecycle fields, and keep content changes minimal...',
209
+
196
210
  'guide.description_quality.title': 'Description Quality',
197
211
  'guide.description_quality.definition': 'Description quality issues indicate that the skill\'s SKILL.md file is missing necessary information or has unclear descriptions.',
198
212
  'guide.description_quality.cause': 'Common causes:\n• Skill author didn\'t provide complete description\n• Description is too brief, missing key information\n• No trigger conditions specified\n• No input/output documented\n• Known risks not recorded',
@@ -235,6 +249,7 @@ const en = {
235
249
  'dashboard.riskFindings.desc': 'Inherent permissions, not actionable',
236
250
  'dashboard.zombieCandidates.desc': 'Unused or abandoned skills (score >= 0.4)',
237
251
  'dashboard.conflictFindings.desc': 'Skills with contradictory instructions',
252
+ 'dashboard.governanceFindings.desc': 'Skills missing registry readiness metadata',
238
253
  'dashboard.descriptionQualityFindings.desc': 'Documentation quality, low priority',
239
254
  'dashboard.duplicateFindings.desc': 'Findings related to duplicate skill content',
240
255
  'dashboard.versionDriftFindings.desc': 'Skills with outdated versions compared to source',
@@ -281,6 +296,7 @@ const zh = {
281
296
  'cli.riskFindings': '风险发现: %s',
282
297
  'cli.conflictFindings': '冲突发现: %s',
283
298
  'cli.zombieCandidates': '僵尸技能: %s',
299
+ 'cli.governanceFindings': '治理发现: %s',
284
300
  'cli.reportWritten': '报告已写入: %s',
285
301
  'cli.noFindings': '没有 %s 类型的发现。',
286
302
  'cli.noDuplicateGroups': '没有重复组。运行: agent-skill-doctor diagnose',
@@ -304,6 +320,7 @@ const zh = {
304
320
  'report.riskFindings': '风险发现',
305
321
  'report.conflictFindings': '冲突发现',
306
322
  'report.zombieCandidates': '僵尸技能',
323
+ 'report.governanceFindings': '治理就绪',
307
324
  'report.descriptionQualityFindings': '描述问题',
308
325
  'report.duplicateFindings': '重复发现',
309
326
  'report.ignoredFindings': '已忽略',
@@ -389,6 +406,8 @@ const zh = {
389
406
  'tooltip.zombie.formulaText': '分数 = 未被预设引用(0.25) + 未安装到Agent(0.20) + 未安装到项目(0.20) + 无近期修改(0.15) + 无活动日志(0.15) + 描述质量低(0.05)。然后:官方(*0)、插件(*0.5)、第三方(*0.75)。最大值 = 1.0。',
390
407
  'tooltip.conflict.title': '冲突检测标准',
391
408
  'tooltip.conflict.text': '检查 2+ 个技能是否在同一冲突规则中匹配了不同的备选项。内置规则:包管理器冲突(npm vs pnpm vs yarn)、输出格式冲突(JSON vs Markdown)。二元检测:存在或不存在。',
409
+ 'tooltip.governance.title': '治理就绪检测标准',
410
+ 'tooltip.governance.text': '检查技能是否具备团队共享或 Registry 使用所需的元数据:owner、version、生命周期状态、发布标签和可信来源。',
392
411
  'tooltip.descriptionQuality.title': '描述质量(仅供参考)',
393
412
  'tooltip.descriptionQuality.text': '从 4 个维度评估技能描述:长度、风险提示、使用指导、结构化。低分说明文档质量差,但不影响技能功能。可由 Agent 低优先级补充完善。',
394
413
  'tooltip.versionDrift.title': '版本漂移检测标准',
@@ -413,6 +432,7 @@ const zh = {
413
432
  'type.zombie': '僵尸',
414
433
  'type.duplicate': '重复',
415
434
  'type.version_drift': '版本漂移',
435
+ 'type.governance': '治理',
416
436
  'type.description_quality': '描述质量',
417
437
  'type.scan_warning': '扫描警告',
418
438
 
@@ -461,6 +481,15 @@ const zh = {
461
481
  'guide.version_drift.prompt': '请将技能 "%s" 的所有安装更新到最新版本,如可能请合并到单一安装位置。',
462
482
  'guide.version_drift.agentExample': 'Agent 交互示例:\n用户:请帮我统一技能版本\nAgent:我来检查所有安装位置,找出最新版本...',
463
483
 
484
+ 'guide.governance.title': '治理就绪',
485
+ 'guide.governance.definition': '治理发现表示技能缺少团队共享、Registry 审核、发布标签或回滚所需的元数据。',
486
+ 'guide.governance.cause': '常见成因:\n• 技能只在本地创建,未写 owner\n• 技能没有 version 或生命周期状态\n• 技能没有 stable/dev/latest 标签\n• 技能没有可信来源或上游 Registry 记录',
487
+ 'guide.governance.severity': '严重程度说明:\n• High(高):缺少可信来源\n• Medium(中):缺少 owner、version 或生命周期状态\n• Low(低):缺少发布标签',
488
+ 'guide.governance.meaning': '该技能可能本地可用,但还不能被当作可治理的团队资产。',
489
+ 'guide.governance.steps': '修复步骤:\n1. 添加 owner 或 maintainer 元数据\n2. 添加 version 元数据\n3. 添加 draft、review 或 online 等生命周期状态\n4. 添加 stable/dev/latest 标签\n5. 添加 source 元数据,或导入中心库/Registry',
490
+ 'guide.governance.prompt': '请完善技能 "%s" 的治理元数据。补齐缺失的 owner、version、lifecycle、label 和 trusted source 字段。',
491
+ 'guide.governance.agentExample': 'Agent 交互示例:\n用户:请把这个技能整理到可以团队共享\nAgent:我会检查技能元数据,补齐缺失的 owner/version/lifecycle 字段,并尽量保持内容改动最小...',
492
+
464
493
  'guide.description_quality.title': '描述质量',
465
494
  'guide.description_quality.definition': '描述质量问题是指标技能的 SKILL.md 文件缺少必要信息或描述不清晰。',
466
495
  'guide.description_quality.cause': '常见成因:\n• 技能作者未提供完整描述\n• 描述过于简短,缺少关键信息\n• 未说明触发条件\n• 未说明输入输出\n• 未记录已知风险',
@@ -503,6 +532,7 @@ const zh = {
503
532
  'dashboard.riskFindings.desc': '固有权限,无需修复',
504
533
  'dashboard.zombieCandidates.desc': '疑似废弃的技能数(分数 >= 0.4)',
505
534
  'dashboard.conflictFindings.desc': '指令相互矛盾的技能数',
535
+ 'dashboard.governanceFindings.desc': '缺少治理元数据的技能数',
506
536
  'dashboard.descriptionQualityFindings.desc': '文档质量问题,低优先级',
507
537
  'dashboard.duplicateFindings.desc': '与重复内容相关的发现数',
508
538
  'dashboard.versionDriftFindings.desc': '与源版本不一致的技能数',
@@ -6,6 +6,7 @@ const zombie = require('./zombie');
6
6
  const riskLite = require('./risk-lite');
7
7
  const rules = require('./rules');
8
8
  const i18n = require('./i18n');
9
+ const governance = require('./governance');
9
10
 
10
11
  module.exports = {
11
12
  // Phase 2 - Duplicate and drift detection
@@ -35,6 +36,12 @@ module.exports = {
35
36
  loadJsonRules: riskLite.loadJsonRules,
36
37
  scanSkillForRisks: riskLite.scanSkillForRisks,
37
38
 
39
+ // Governance readiness
40
+ detectGovernanceFindings: governance.detectGovernanceFindings,
41
+ hasGovernanceLabel: governance.hasGovernanceLabel,
42
+ hasTrustedSource: governance.hasTrustedSource,
43
+ lifecycleStatus: governance.lifecycleStatus,
44
+
38
45
  // Rules and utilities
39
46
  DEFAULT_RISK_RULES: rules.DEFAULT_RISK_RULES,
40
47
  DEFAULT_CONFLICT_RULES: rules.DEFAULT_CONFLICT_RULES,