showdar-skills 0.2.2 → 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.
Files changed (47) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/MIGRATION.md +52 -0
  3. package/README.md +80 -16
  4. package/bin/showdar.js +28 -5
  5. package/package.json +5 -2
  6. package/skills/showdar-review/scripts/collect-diff.mjs +9 -2
  7. package/src/adapters.js +20 -3
  8. package/src/capabilities.js +93 -0
  9. package/src/capability-score.js +82 -0
  10. package/src/catalog.js +14 -1
  11. package/src/evidence-state.js +526 -0
  12. package/src/intent-resolver/composition.js +1080 -0
  13. package/src/intent-resolver/confidence.js +71 -0
  14. package/src/intent-resolver/constraints.js +138 -0
  15. package/src/intent-resolver/evidence.js +182 -0
  16. package/src/intent-resolver/frame/action-frame.js +380 -0
  17. package/src/intent-resolver/frame/authority/adjudicator.js +104 -0
  18. package/src/intent-resolver/frame/authority/candidate.js +227 -0
  19. package/src/intent-resolver/frame/authority/diagnostics.js +58 -0
  20. package/src/intent-resolver/frame/authority/evidence.js +241 -0
  21. package/src/intent-resolver/frame/authority/index.js +166 -0
  22. package/src/intent-resolver/frame/authority/projectors.js +198 -0
  23. package/src/intent-resolver/frame/authority/relations.js +61 -0
  24. package/src/intent-resolver/frame/authority/shadow.js +51 -0
  25. package/src/intent-resolver/frame/authority/types.js +14 -0
  26. package/src/intent-resolver/frame/clause-frame.js +205 -0
  27. package/src/intent-resolver/frame/projectors/constraints.js +332 -0
  28. package/src/intent-resolver/frame/projectors/metadata.js +514 -0
  29. package/src/intent-resolver/frame/relations.js +242 -0
  30. package/src/intent-resolver/frame/request-frame.js +187 -0
  31. package/src/intent-resolver/frame/surface-map.js +450 -0
  32. package/src/intent-resolver/index.js +476 -0
  33. package/src/intent-resolver/mutation.js +634 -0
  34. package/src/intent-resolver/object.js +191 -0
  35. package/src/intent-resolver/risks.js +202 -0
  36. package/src/intent-resolver/scoring.js +630 -0
  37. package/src/intent-resolver/secondary.js +343 -0
  38. package/src/intent-resolver/segments.js +576 -0
  39. package/src/intent-resolver/signals.js +435 -0
  40. package/src/intent-resolver.js +8 -0
  41. package/src/intent.js +81 -0
  42. package/src/path-safety.js +39 -0
  43. package/src/project.js +113 -27
  44. package/src/route-plan.js +100 -0
  45. package/src/validate.js +3 -0
  46. package/src/verification-budget.js +182 -0
  47. package/src/verification-executor.js +515 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,49 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
6
+
7
+ ## [Unreleased]
8
+
9
+ ## [0.3.0]
10
+
11
+ ### Added
12
+
13
+ - Explicit harness targets for `showdar init` and `showdar add`: `codex`,
14
+ `opencode`, `cursor`, `claude`, `universal`, and `all`.
15
+ - `showdar add <skill>` for installing a single primitive skill without
16
+ re-running a whole profile (for example `showdar add debug`,
17
+ `showdar add security --ai cursor`,
18
+ `showdar add review --scope global --ai claude`).
19
+ - Native Cursor skill roots (`.cursor/skills` for projects,
20
+ `~/.cursor/skills` for global installs).
21
+ - Deny-by-default routing authority coverage for conditional, modal,
22
+ contextual, hypothetical, and negated actions.
23
+
24
+ ### Changed
25
+
26
+ - Each harness target now installs into its native skill directory instead of
27
+ sharing one compatibility root.
28
+ - Routing uses a single authoritative engine: current request → structural
29
+ interpretation → authority classification → primary capability → skill.
30
+ - Context, log, and example text no longer becomes requested work on its own;
31
+ conditional and hypothetical actions stay non-authoritative until current
32
+ request semantics permit them.
33
+ - Risk metadata no longer overrides an explicit governing action.
34
+
35
+ ### Fixed
36
+
37
+ - Conditional, modal, context, and negation authority safety cases.
38
+ - Security-risk ownership cases where risk signals previously stole primary
39
+ ownership from the governing action.
40
+ - Debug, upgrade, build, and test imperative recognition for explicit
41
+ governing requests.
42
+
43
+ ### Removed
44
+
45
+ - The executable legacy 6F authority engine and the old route-scoring path.
46
+
47
+ ## [0.2.3]
48
+
49
+ See git history for changes before the 0.3.0 changelog was started.
package/MIGRATION.md ADDED
@@ -0,0 +1,52 @@
1
+ # Migrating to 0.3.0
2
+
3
+ ## Skill install roots
4
+
5
+ `--ai universal` still installs to `.agents/skills` (project) and
6
+ `~/.agents/skills` (global). Explicit harness targets now use their native
7
+ roots:
8
+
9
+ - OpenCode: `.opencode/skills` / `~/.config/opencode/skills`
10
+ - Cursor: `.cursor/skills` / `~/.cursor/skills`
11
+ - Claude Code: `.claude/skills` / `~/.claude/skills`
12
+ - Codex: `.agents/skills` / `~/.agents/skills`
13
+
14
+ One invocation installs to one resolved root only; no compatibility copies
15
+ are made automatically.
16
+
17
+ ## Existing projects
18
+
19
+ Existing `.showdar.json` v2 configs remain valid. No config-version migration
20
+ is required. Re-running `showdar init` with an explicit `--ai` target moves
21
+ managed skills to the newly requested native root; the previous root is not
22
+ silently deleted unless existing Showdar stale-cleanup semantics apply.
23
+
24
+ ## Profiles
25
+
26
+ Profile names and composition are unchanged:
27
+
28
+ - `minimal` (8), `developer` (12), `backend` (14), `qa` (9), `product` (6),
29
+ `full` (15).
30
+ - `mobile` and `web` remain accepted as deprecated aliases for `developer`;
31
+ manifests store the canonical name.
32
+
33
+ ## Single-skill additions
34
+
35
+ Use `showdar add <skill>` instead of re-running a larger profile to add one
36
+ skill:
37
+
38
+ ```bash
39
+ showdar add debug
40
+ showdar add security --ai cursor
41
+ showdar add review --scope global --ai claude
42
+ ```
43
+
44
+ Additions preserve the configured profile and are idempotent.
45
+
46
+ ## Routing behavior
47
+
48
+ 0.3.0 uses stricter deny-by-default authority semantics. Conditional,
49
+ hypothetical, contextual, and negated actions stay non-authoritative until
50
+ current request semantics permit them, and risk metadata no longer overrides
51
+ an explicit governing action. Do not assume byte-identical routing with older
52
+ releases where behavior intentionally tightened.
package/README.md CHANGED
@@ -17,10 +17,16 @@ Install the CLI, then install a role-oriented skill profile into your project:
17
17
  ```bash
18
18
  npm install -g showdar-skills
19
19
  cd my-project
20
- showdar init --ai codex --profile developer
20
+ showdar init
21
21
  showdar doctor
22
22
  ```
23
23
 
24
+ ```bash
25
+ showdar init --ai cursor
26
+ showdar init --ai claude --scope global
27
+ showdar init --profile developer --ai opencode
28
+ ```
29
+
24
30
  To install from source instead:
25
31
 
26
32
  ```bash
@@ -29,9 +35,10 @@ cd showdar-skills
29
35
  npm install -g .
30
36
  ```
31
37
 
32
- Showdar works with Codex, OpenCode, Claude Code, and universal agent skill
33
- directories. Choose `backend`, `qa`, or `product` when that gives discovery a
34
- more precise context; use `full` when you want all capabilities available.
38
+ Showdar works with Universal Agent Skills, Codex, OpenCode, Cursor, and
39
+ Claude Code as supported installation targets. Choose `backend`, `qa`, or
40
+ `product` when that gives discovery a more precise context; use `full` when
41
+ you want all capabilities available.
35
42
 
36
43
  ## Why Showdar?
37
44
 
@@ -71,15 +78,23 @@ knowledge progressively.
71
78
 
72
79
  ## Supported agents
73
80
 
74
- | Target | Project destination | Global destination |
75
- | --- | --- | --- |
76
- | Codex / Universal | `.agents/skills/` | `~/.agents/skills/` |
77
- | OpenCode skills | `.opencode/skills/` | `~/.config/opencode/skills/` |
78
- | OpenCode commands | `.opencode/commands/showdar/` | `~/.config/opencode/commands/showdar/` |
79
- | Claude Code | `.claude/skills/` | `~/.claude/skills/` |
81
+ | Harness | Project path | Global path | Status |
82
+ | --- | --- | --- | --- |
83
+ | Universal Agent Skills | `.agents/skills/` | `~/.agents/skills/` | Supported installation target |
84
+ | Codex | `.agents/skills/` | `~/.agents/skills/` | Supported installation target |
85
+ | OpenCode | `.opencode/skills/` | `~/.config/opencode/skills/` | Supported installation target |
86
+ | Cursor | `.cursor/skills/` | `~/.cursor/skills/` | Supported installation target |
87
+ | Claude Code | `.claude/skills/` | `~/.claude/skills/` | Supported installation target |
88
+
89
+ Universal uses `.agents/skills/`. Explicit harness targets use their native
90
+ skill directories. Codex and Universal intentionally share `.agents/skills/`.
91
+ OpenCode additionally receives native `/showdar/...` command files in
92
+ `.opencode/commands/showdar/` (project) and
93
+ `~/.config/opencode/commands/showdar/` (global).
80
94
 
81
- Codex and Universal intentionally share `.agents/skills/`. OpenCode receives
82
- both skills and native `/showdar/...` command files.
95
+ "Supported installation target" means skills install to the harness-native
96
+ directory. It does not promise identical implicit invocation, cloud,
97
+ agent/subagent, or MCP behavior across harnesses.
83
98
 
84
99
  ## Project and global installation
85
100
 
@@ -238,10 +253,57 @@ OpenCode exposes native commands after initialization with `--ai opencode` or
238
253
  | `showdar-security` | Performs defensive, evidence-based analysis and never exposes secret values. |
239
254
  | `showdar-requirements` | Records assumptions and open decisions instead of inventing business decisions. |
240
255
 
256
+ ## Adding a single skill
257
+
258
+ Install one primitive skill without re-running a whole profile:
259
+
260
+ ```bash
261
+ showdar add debug
262
+ showdar add showdar-security
263
+ showdar add test --ai cursor
264
+ showdar add review --scope global --ai claude
265
+ ```
266
+
267
+ Accepted names are the short form (`debug`) or the canonical form
268
+ (`showdar-debug`). The release ships exactly 15 primitive skills. `showdar add`
269
+ is idempotent, preserves the configured profile, supports `--ai`/`--scope`
270
+ overrides, and refuses to overwrite a foreign same-name skill directory that
271
+ Showdar does not own.
272
+
273
+ ## Routing
274
+
275
+ Showdar routes each request through progressive disclosure: the host discovers
276
+ lightweight skill metadata, loads the relevant skill, and pulls deeper
277
+ guides and data only when needed.
278
+
279
+ ```text
280
+ current request
281
+ |
282
+ v
283
+ structural interpretation
284
+ |
285
+ v
286
+ authority classification
287
+ |
288
+ v
289
+ primary capability
290
+ |
291
+ v
292
+ skill
293
+ ```
294
+
295
+ Product behavior notes:
296
+
297
+ - Context, log, and example text does not automatically become requested work.
298
+ - Conditional and hypothetical actions remain non-authoritative until current
299
+ request semantics permit them.
300
+ - Risk metadata does not override an explicit governing action.
301
+
241
302
  ## CLI reference
242
303
 
243
304
  ```bash
244
305
  showdar init [--scope <project|global>] --ai <target> --profile <profile>
306
+ showdar add <skill> [--ai <target>] [--scope <project|global>]
245
307
  showdar list
246
308
  showdar status [--scope <project|global>]
247
309
  showdar doctor [--scope <project|global>]
@@ -249,10 +311,12 @@ showdar validate
249
311
  showdar remove [--scope <project|global>]
250
312
  ```
251
313
 
252
- Main flags are `--ai`, `--profile`, and `--scope`. `--ai` accepts `codex`,
253
- `opencode`, `claude`, `universal`, or `all`. `--scope` defaults to `project`;
254
- `--profile` accepts the six canonical profiles and the `mobile`/`web` aliases.
255
- Run `showdar --help` or a command's `--help` for current options.
314
+ Main flags are `--ai`, `--profile`, and `--scope`. `--ai` accepts `universal`,
315
+ `codex`, `opencode`, `cursor`, `claude`, or `all` for `init` (single targets
316
+ for `add`). `--scope` accepts `project` or `global` and defaults to `project`;
317
+ `--profile` accepts the six canonical profiles and the deprecated
318
+ `mobile`/`web` aliases. Run `showdar --help` or a command's `--help` for
319
+ current options.
256
320
 
257
321
  `showdar validate` validates the installed Showdar package. `showdar doctor`
258
322
  checks managed files against ownership hashes, while `showdar remove` removes
package/bin/showdar.js CHANGED
@@ -4,7 +4,7 @@ import { homedir } from 'node:os';
4
4
  import { readFile } from 'node:fs/promises';
5
5
  import { fileURLToPath } from 'node:url';
6
6
  import { AI_TARGETS, PROFILE_ALIASES, PROFILES, SKILLS, canonicalProfile, isDeprecatedProfile, resolveProfile } from '../src/catalog.js';
7
- import { globalManifestPath, initGlobal, initProject, inspectGlobal, inspectProject, removeGlobal, removeProject } from '../src/project.js';
7
+ import { addSkill, globalManifestPath, initGlobal, initProject, inspectGlobal, inspectProject, removeGlobal, removeProject } from '../src/project.js';
8
8
  import { validateRepository } from '../src/validate.js';
9
9
 
10
10
  const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
@@ -32,14 +32,18 @@ function scopeAfter(args) {
32
32
  function printHelp(version, command = null) {
33
33
  const scopeUsage = '[--scope <project|global>]';
34
34
  if (command === 'init') {
35
- console.log(`Showdar Skills ${version}\n\nUsage:\n showdar init ${scopeUsage} [--profile <name>] [--ai <codex|opencode|claude|universal|all>]\n\nDefaults: scope project, profile full, AI target universal.\nProject scope writes native skills and project .showdar.json. Global scope writes verified user skill directories and ~/.showdar/global.json without project files. Codex and universal use .agents/skills in project scope and ~/.agents/skills in global scope; --ai all writes each shared destination once.\n\nProfiles: ${Object.keys(PROFILES).join(', ')}\nDeprecated aliases: ${Object.entries(PROFILE_ALIASES).map(([alias, target]) => `${alias} -> ${target}`).join(', ')}\nAI targets: ${AI_TARGETS.join(', ')}`);
35
+ console.log(`Showdar Skills ${version}\n\nUsage:\n showdar init ${scopeUsage} [--profile <name>] [--ai <universal|codex|opencode|cursor|claude|all>]\n\nDefaults: scope project, profile full, AI target universal.\nProject scope writes native skills and project .showdar.json. Global scope writes verified user skill directories and ~/.showdar/global.json without project files. Codex and universal use .agents/skills in project scope and ~/.agents/skills in global scope; cursor uses .cursor/skills in project scope and ~/.cursor/skills in global scope; --ai all writes each shared destination once.\n\nProfiles: ${Object.keys(PROFILES).join(', ')}\nDeprecated aliases: ${Object.entries(PROFILE_ALIASES).map(([alias, target]) => `${alias} -> ${target}`).join(', ')}\nAI targets: ${AI_TARGETS.join(', ')}`);
36
36
  return;
37
37
  }
38
38
  if (['status', 'doctor', 'remove'].includes(command)) {
39
39
  console.log(`Showdar Skills ${version}\n\nUsage:\n showdar ${command} ${scopeUsage}\n\nDefault scope: project. Use --scope global for the user installation.`);
40
40
  return;
41
41
  }
42
- console.log(`Showdar Skills ${version}\n\nUsage:\n showdar init ${scopeUsage} [--profile <name>] [--ai <codex|opencode|claude|universal|all>]\n showdar status ${scopeUsage}\n showdar doctor ${scopeUsage}\n showdar validate\n showdar list\n showdar remove ${scopeUsage}\n\nDefaults: scope project, profile full, AI target universal.\nProfiles: ${Object.keys(PROFILES).join(', ')}\nDeprecated aliases: ${Object.entries(PROFILE_ALIASES).map(([alias, target]) => `${alias} -> ${target}`).join(', ')}\nAI targets: ${AI_TARGETS.join(', ')}`);
42
+ if (command === 'add') {
43
+ console.log(`Showdar Skills ${version}\n\nUsage:\n showdar add <skill> [--ai <universal|codex|opencode|cursor|claude>] [--scope <project|global>]\n\nExamples:\n showdar add debug\n showdar add showdar-security\n showdar add test --ai cursor\n showdar add review --scope global --ai claude\n\nDefault scope: project. Default AI target: universal, or the configured .showdar.json value when present.`);
44
+ return;
45
+ }
46
+ console.log(`Showdar Skills ${version}\n\nUsage:\n showdar init ${scopeUsage} [--profile <name>] [--ai <universal|codex|opencode|cursor|claude|all>]\n showdar add <skill> [--ai <universal|codex|opencode|cursor|claude>] [--scope <project|global>]\n showdar status ${scopeUsage}\n showdar doctor ${scopeUsage}\n showdar validate\n showdar list\n showdar remove ${scopeUsage}\n\nDefaults: scope project, profile full, AI target universal.\nProfiles: ${Object.keys(PROFILES).join(', ')}\nDeprecated aliases: ${Object.entries(PROFILE_ALIASES).map(([alias, target]) => `${alias} -> ${target}`).join(', ')}\nAI targets: ${AI_TARGETS.join(', ')}`);
43
47
  }
44
48
 
45
49
  async function main() {
@@ -55,7 +59,7 @@ async function main() {
55
59
  }
56
60
  if (args.includes('--help') || args.includes('-h')) return printHelp(version, command);
57
61
 
58
- const scope = ['init', 'status', 'doctor', 'remove'].includes(command) ? scopeAfter(args) : null;
62
+ const scope = ['init', 'status', 'doctor', 'remove', 'add'].includes(command) ? scopeAfter(args) : null;
59
63
 
60
64
  if (command === 'list') {
61
65
  console.log(`Profiles: ${Object.keys(PROFILES).join(', ')}\nDeprecated aliases: ${Object.entries(PROFILE_ALIASES).map(([alias, target]) => `${alias} -> ${target}`).join(', ')}\n\nSkills:`);
@@ -76,7 +80,7 @@ async function main() {
76
80
  }
77
81
 
78
82
  if (command === 'init') {
79
- if (args.includes('--agent')) throw new Error('--agent is no longer supported in V0.2. Use --ai <codex|opencode|claude|universal|all>.');
83
+ if (args.includes('--agent')) throw new Error('--agent is no longer supported in V0.2. Use --ai <universal|codex|opencode|cursor|claude|all>.');
80
84
  const requestedProfile = valueAfter(args, '--profile', 'full');
81
85
  const profile = canonicalProfile(requestedProfile);
82
86
  const ai = valueAfter(args, '--ai', 'universal');
@@ -112,6 +116,25 @@ async function main() {
112
116
  return;
113
117
  }
114
118
 
119
+ if (command === 'add') {
120
+ const positional = args.filter((a, i) => i > 0 && !a.startsWith('--') && args[i - 1] !== '--ai' && args[i - 1] !== '--scope');
121
+ const skillArg = positional[0];
122
+ if (!skillArg) throw new Error('Skill name is required. Usage: showdar add <skill> [--ai <target>] [--scope <project|global>]');
123
+ const hasAiFlag = args.includes('--ai');
124
+ const hasScopeFlag = args.includes('--scope');
125
+ const result = await addSkill({
126
+ cwd: projectRoot,
127
+ skill: skillArg,
128
+ ai: hasAiFlag ? valueAfter(args, '--ai', 'universal') : null,
129
+ scope: hasScopeFlag ? scope : null,
130
+ home: homedir(),
131
+ packageRoot,
132
+ packageVersion: version,
133
+ });
134
+ console.log(`Showdar skill ${result.added ? 'added' : 'already installed'}.\nSkill: ${result.skill}\nScope: ${result.scope}\nAI: ${result.ai}\nPath: ${result.destination}`);
135
+ return;
136
+ }
137
+
115
138
  if (command === 'remove') {
116
139
  if (scope === 'global') await removeGlobal();
117
140
  else await removeProject(projectRoot);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "showdar-skills",
3
- "version": "0.2.2",
3
+ "version": "0.3.0",
4
4
  "description": "Production-grade software engineering lifecycle skills for coding agents.",
5
5
  "type": "module",
6
6
  "bin": { "showdar": "./bin/showdar.js" },
@@ -14,7 +14,7 @@
14
14
  "release:check": "node scripts/check-release-version.mjs"
15
15
  },
16
16
  "engines": { "node": ">=20" },
17
- "files": ["bin", "src", "skills", "commands", "router", "bundles", "profiles", "engine", "README.md", "LICENSE"],
17
+ "files": ["bin", "src", "skills", "commands", "router", "bundles", "profiles", "engine", "README.md", "LICENSE", "CHANGELOG.md", "MIGRATION.md"],
18
18
  "keywords": ["agent-skills", "coding-agents", "codex", "opencode", "claude-code", "software-engineering", "developer-tools", "requirements", "qa", "security", "devops", "workflow"],
19
19
  "license": "MIT",
20
20
  "repository": {
@@ -24,5 +24,8 @@
24
24
  "homepage": "https://github.com/caongocquy/showdar-skills#readme",
25
25
  "bugs": {
26
26
  "url": "https://github.com/caongocquy/showdar-skills/issues"
27
+ },
28
+ "devDependencies": {
29
+ "ajv": "^8.20.0"
27
30
  }
28
31
  }
@@ -1,5 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
  import { spawnSync } from 'node:child_process';
3
3
  const cwd=process.argv[2]??process.cwd(); const base=process.argv[3]??'HEAD';
4
- const run=a=>{const r=spawnSync('git',a,{cwd,encoding:'utf8'});return r.status===0?r.stdout.trim():null};
5
- console.log(JSON.stringify({base,status:run(['status','--short']),files:run(['diff','--name-only',base])?.split('\n').filter(Boolean)??[],stat:run(['diff','--stat',base]),diff:run(['diff','--no-ext-diff','--unified=3',base])},null,2));
4
+ const run=(args)=>{const r=spawnSync('git',args,{cwd,encoding:'utf8',shell:false});if(r.error)throw r.error;if(r.status!==0)throw new Error((r.stderr||r.stdout).trim()||`git ${args.join(' ')} failed`);return r.stdout.trim()};
5
+ try {
6
+ if (base.startsWith('-')) throw new Error('revision must not begin with "-"');
7
+ const resolved=run(['rev-parse','--verify',`${base}^{commit}`]);
8
+ console.log(JSON.stringify({base,status:run(['status','--short']),files:run(['diff','--no-ext-diff','--name-only',resolved])?.split('\n').filter(Boolean)??[],stat:run(['diff','--no-ext-diff','--stat',resolved]),diff:run(['diff','--no-ext-diff','--unified=3',resolved])},null,2));
9
+ } catch (error) {
10
+ console.error(`collect-diff: ${error.message}`);
11
+ process.exitCode=1;
12
+ }
package/src/adapters.js CHANGED
@@ -1,11 +1,12 @@
1
1
  import path from 'node:path';
2
2
  import { homedir } from 'node:os';
3
3
 
4
- export const NATIVE_TARGETS = ['codex', 'opencode', 'claude', 'universal'];
4
+ export const NATIVE_TARGETS = ['codex', 'opencode', 'cursor', 'claude', 'universal'];
5
5
 
6
6
  const ROOTS = {
7
7
  codex: ['.agents', 'skills'],
8
8
  opencode: ['.opencode', 'skills'],
9
+ cursor: ['.cursor', 'skills'],
9
10
  claude: ['.claude', 'skills'],
10
11
  universal: ['.agents', 'skills'],
11
12
  };
@@ -13,13 +14,14 @@ const ROOTS = {
13
14
  const GLOBAL_ROOTS = {
14
15
  codex: ({ homeRoot }) => path.join(homeRoot, '.agents', 'skills'),
15
16
  opencode: ({ homeRoot }) => path.join(homeRoot, '.config', 'opencode', 'skills'),
17
+ cursor: ({ homeRoot }) => path.join(homeRoot, '.cursor', 'skills'),
16
18
  claude: ({ homeRoot }) => path.join(homeRoot, '.claude', 'skills'),
17
19
  universal: ({ homeRoot }) => path.join(homeRoot, '.agents', 'skills'),
18
20
  };
19
21
 
20
22
  export function resolveTargets(ai) {
21
23
  if (ai === 'all') return [...NATIVE_TARGETS];
22
- if (!NATIVE_TARGETS.includes(ai)) throw new Error(`Unknown AI target "${ai}". Expected codex, opencode, claude, universal, or all.`);
24
+ if (!NATIVE_TARGETS.includes(ai)) throw new Error(`Unknown AI target "${ai}". Expected codex, opencode, cursor, claude, universal, or all.`);
23
25
  return [ai];
24
26
  }
25
27
 
@@ -35,10 +37,25 @@ export function globalSkillRootFor(target, { homeRoot = homedir() } = {}) {
35
37
  return resolve({ homeRoot });
36
38
  }
37
39
 
40
+ export function resolveSkillRoot({ ai, scope, cwd, home = homedir() }) {
41
+ if (!NATIVE_TARGETS.includes(ai)) throw new Error(`Unknown AI target "${ai}".`);
42
+ if (scope !== 'project' && scope !== 'global') throw new Error(`Unknown scope "${scope}". Expected project or global.`);
43
+ if (scope === 'project') return skillRootFor(ai, cwd);
44
+ return globalSkillRootFor(ai, { homeRoot: home });
45
+ }
46
+
47
+ export const COMPATIBILITY_MATRIX = {
48
+ codex: { projectNative: true, globalNative: true },
49
+ opencode: { projectNative: true, globalNative: true },
50
+ cursor: { projectNative: true, globalNative: true },
51
+ claude: { projectNative: true, globalNative: true },
52
+ universal: { project: true, global: true },
53
+ };
54
+
38
55
  export function opencodeCommandRoot(projectRoot) {
39
56
  return path.join(projectRoot, '.opencode', 'commands', 'showdar');
40
57
  }
41
58
 
42
59
  export function globalCommandRootFor({ homeRoot = homedir() } = {}) {
43
60
  return path.join(homeRoot, '.config', 'opencode', 'commands', 'showdar');
44
- }
61
+ }
@@ -0,0 +1,93 @@
1
+ import { SKILLS } from './catalog.js';
2
+ import { EVIDENCE_KEYS, INTENT_PHASES, MUTATION_CLASSES, RISK_CAPABILITIES } from './intent.js';
3
+
4
+ const readOnly = ['read-only'];
5
+ const localWrite = ['local-write'];
6
+ const readAndLocalWrite = ['read-only', 'local-write'];
7
+
8
+ // This array is the canonical capability source; SKILL.md remains the human-facing source.
9
+ export const CAPABILITIES = Object.freeze([
10
+ { skill: 'showdar-understand', phases: ['discovery', 'planning'], actions: ['understand', 'investigate', 'assess'], objects: ['repository', 'architecture', 'dependency', 'api', 'data', 'runtime'], risks: [], mutations: readOnly },
11
+ { skill: 'showdar-requirements', phases: ['definition'], actions: ['define', 'assess', 'review'], objects: ['repository', 'backend', 'api', 'ui', 'data'], risks: [], mutations: readAndLocalWrite, evidence: { prefer: { behaviorDefined: false } } },
12
+ { skill: 'showdar-plan', phases: ['planning'], actions: ['plan', 'define', 'review'], objects: ['repository', 'architecture', 'backend', 'api', 'ui', 'data'], risks: [], mutations: readAndLocalWrite },
13
+ { skill: 'showdar-design', phases: ['design'], actions: ['design', 'review', 'modify'], objects: ['ui', 'frontend', 'mobile'], risks: [], mutations: readAndLocalWrite },
14
+ { skill: 'showdar-build', phases: ['implementation'], actions: ['implement', 'modify', 'fix'], objects: ['repository', 'ui', 'backend', 'api', 'data', 'runtime'], risks: ['regression', 'performance', 'data-integrity'], mutations: localWrite, evidence: { prefer: { behaviorDefined: true, rootCauseKnown: true } } },
15
+ { skill: 'showdar-debug', phases: ['diagnosis'], actions: ['investigate', 'reproduce', 'isolate', 'fix'], objects: ['runtime', 'build', 'network', 'state', 'auth', 'api'], risks: ['regression', 'performance', 'data-integrity'], mutations: readAndLocalWrite, evidence: { prefer: { failureObserved: true, rootCauseKnown: false }, deEmphasize: { rootCauseKnown: true } } },
16
+ { skill: 'showdar-test', phases: ['verification'], actions: ['test', 'review'], objects: ['repository', 'backend', 'api', 'ui', 'runtime', 'data'], risks: ['regression', 'compatibility', 'performance'], mutations: readAndLocalWrite },
17
+ { skill: 'showdar-quality', phases: ['verification'], actions: ['assess', 'review', 'test'], objects: ['repository', 'backend', 'api', 'ui', 'runtime'], risks: ['regression', 'compatibility'], mutations: readOnly },
18
+ { skill: 'showdar-review', phases: ['discovery', 'verification'], actions: ['review', 'assess'], objects: ['repository', 'architecture', 'code', 'api', 'data'], risks: ['regression'], mutations: readOnly },
19
+ { skill: 'showdar-security', phases: ['discovery', 'verification'], actions: ['assess', 'review'], objects: ['auth', 'secrets', 'trust-boundary', 'api', 'data'], risks: ['security'], mutations: readOnly },
20
+ { skill: 'showdar-upgrade', phases: ['implementation'], actions: ['upgrade', 'modify', 'test'], objects: ['dependency', 'runtime', 'backend', 'api', 'repository'], risks: ['compatibility', 'regression'], mutations: localWrite },
21
+ { skill: 'showdar-ship', phases: ['delivery', 'verification'], actions: ['release', 'review', 'assess'], objects: ['release', 'package', 'repository', 'deployment', 'runtime'], risks: ['production', 'compatibility'], mutations: readOnly },
22
+ { skill: 'showdar-ops', phases: ['operations'], actions: ['deploy', 'modify', 'assess', 'review'], objects: ['deployment', 'container', 'ci', 'runtime', 'network'], risks: ['operations', 'production'], mutations: ['local-write', 'remote-write', 'production-impacting'] },
23
+ { skill: 'showdar-recover', phases: ['recovery'], actions: ['recover', 'investigate'], objects: ['repository', 'implementation', 'runtime', 'state'], risks: ['data-integrity', 'regression'], mutations: readAndLocalWrite },
24
+ { skill: 'showdar-git', phases: ['repository'], actions: ['git', 'modify', 'review'], objects: ['repository', 'branch', 'commit'], risks: ['data-integrity', 'operations'], mutations: ['read-only', 'local-write', 'remote-write'] },
25
+ ]);
26
+
27
+ const listFields = ['phases', 'actions', 'objects', 'risks', 'mutations'];
28
+ const capabilityFields = new Set(['skill', ...listFields, 'evidence']);
29
+ const evidenceSections = ['prefer', 'deEmphasize'];
30
+
31
+ function isRecord(value) {
32
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
33
+ }
34
+
35
+ export function validateCapabilities(definitions = CAPABILITIES, skills = SKILLS) {
36
+ const errors = [];
37
+ if (!Array.isArray(definitions)) return { ok: false, errors: ['capabilities must be an array'] };
38
+ const expectedSkills = new Set(skills.map(({ id }) => id));
39
+ const seen = new Set();
40
+ for (const definition of definitions) {
41
+ if (!definition || typeof definition !== 'object' || Array.isArray(definition)) {
42
+ errors.push('capability definition must be an object');
43
+ continue;
44
+ }
45
+ const skill = definition.skill;
46
+ if (typeof skill !== 'string' || !skill.trim()) {
47
+ errors.push('capability skill must be a non-empty string');
48
+ continue;
49
+ }
50
+ for (const field of Object.keys(definition)) if (!capabilityFields.has(field)) errors.push(`capability ${skill} contains unknown field: ${field}`);
51
+ if (!expectedSkills.has(skill)) errors.push(`capability entry for nonexistent skill: ${skill}`);
52
+ if (seen.has(skill)) errors.push(`capability entries contain duplicates: ${skill}`);
53
+ seen.add(skill);
54
+ for (const field of listFields) {
55
+ const values = definition[field];
56
+ if (values === undefined) continue;
57
+ if (!Array.isArray(values)) {
58
+ errors.push(`capability ${skill} ${field} must be an array`);
59
+ continue;
60
+ }
61
+ if (values.some((value) => typeof value !== 'string' || !value.trim())) errors.push(`capability ${skill} ${field} must contain non-empty strings`);
62
+ if (new Set(values).size !== values.length) errors.push(`capability ${skill} ${field} contains duplicates`);
63
+ const known = field === 'phases' ? new Set(INTENT_PHASES) : field === 'risks' ? new Set(RISK_CAPABILITIES) : field === 'mutations' ? new Set(MUTATION_CLASSES) : null;
64
+ if (known) for (const value of values) if (typeof value === 'string' && !known.has(value)) errors.push(`capability ${skill} has invalid ${field.slice(0, -1)}: ${value}`);
65
+ }
66
+ if (definition.evidence !== undefined) {
67
+ if (!isRecord(definition.evidence)) errors.push(`capability ${skill} evidence must be an object`);
68
+ else {
69
+ for (const section of Object.keys(definition.evidence)) {
70
+ if (!evidenceSections.includes(section)) {
71
+ errors.push(`capability ${skill} evidence has unknown section: ${section}`);
72
+ continue;
73
+ }
74
+ const preferences = definition.evidence[section];
75
+ if (!isRecord(preferences)) {
76
+ errors.push(`capability ${skill} evidence.${section} must be an object`);
77
+ continue;
78
+ }
79
+ for (const key of Object.keys(preferences)) {
80
+ if (!EVIDENCE_KEYS.includes(key)) errors.push(`capability ${skill} evidence.${section} has unknown key: ${key}`);
81
+ else if (typeof preferences[key] !== 'boolean') errors.push(`capability ${skill} evidence.${section}.${key} must be boolean`);
82
+ }
83
+ }
84
+ }
85
+ }
86
+ }
87
+ for (const skill of expectedSkills) if (!seen.has(skill)) errors.push(`missing capability entry for catalog skill: ${skill}`);
88
+ return { ok: errors.length === 0, errors };
89
+ }
90
+
91
+ export function getCapability(skill) {
92
+ return CAPABILITIES.find((definition) => definition.skill === skill) ?? null;
93
+ }
@@ -0,0 +1,82 @@
1
+ import { CAPABILITIES, getCapability } from './capabilities.js';
2
+ import { EVIDENCE_KEYS, normalizeIntent } from './intent.js';
3
+
4
+ export const CAPABILITY_SCORE_WEIGHTS = Object.freeze({
5
+ phase: 4,
6
+ action: 3,
7
+ object: 2,
8
+ risk: 2,
9
+ mutation: 2,
10
+ evidence: 2,
11
+ });
12
+
13
+ function addUnique(list, value) {
14
+ if (!list.includes(value)) list.push(value);
15
+ }
16
+
17
+ function addMatch(matched, reasons, dimension, value, weight) {
18
+ addUnique(matched, `${dimension}:${value}`);
19
+ reasons.push(`matched ${dimension} ${value} (+${weight})`);
20
+ return weight;
21
+ }
22
+
23
+ function addMiss(unmatched, reasons, dimension, value, supported) {
24
+ addUnique(unmatched, `${dimension}:${value}`);
25
+ reasons.push(`unmatched ${dimension} ${value} (supported: ${supported.join(', ') || 'none'})`);
26
+ }
27
+
28
+ export function scoreIntent(intentInput, capability) {
29
+ const intent = normalizeIntent(intentInput);
30
+ const definition = typeof capability === 'string' ? getCapability(capability) : capability;
31
+ if (!definition?.skill) throw new Error('Capability with a skill id is required');
32
+
33
+ const matched = [];
34
+ const unmatched = [];
35
+ const reasons = [];
36
+ let score = 0;
37
+ // ponytail: exact labels only; add aliasing when a future router needs it.
38
+ const dimensions = [
39
+ ['phase', intent.phase, definition.phases ?? [], CAPABILITY_SCORE_WEIGHTS.phase],
40
+ ['action', intent.action, definition.actions ?? [], CAPABILITY_SCORE_WEIGHTS.action],
41
+ ['object', intent.object, definition.objects ?? [], CAPABILITY_SCORE_WEIGHTS.object],
42
+ ];
43
+ for (const [dimension, value, supported, weight] of dimensions) {
44
+ if (supported.includes(value)) score += addMatch(matched, reasons, dimension, value, weight);
45
+ else addMiss(unmatched, reasons, dimension, value, supported);
46
+ }
47
+ for (const risk of intent.risks) {
48
+ if ((definition.risks ?? []).includes(risk)) score += addMatch(matched, reasons, 'risk', risk, CAPABILITY_SCORE_WEIGHTS.risk);
49
+ else addMiss(unmatched, reasons, 'risk', risk, definition.risks ?? []);
50
+ }
51
+ if ((definition.mutations ?? []).includes(intent.mutation)) score += addMatch(matched, reasons, 'mutation', intent.mutation, CAPABILITY_SCORE_WEIGHTS.mutation);
52
+ else addMiss(unmatched, reasons, 'mutation', intent.mutation, definition.mutations ?? []);
53
+ const evidence = definition.evidence ?? {};
54
+ for (const section of ['prefer', 'deEmphasize']) {
55
+ for (const key of EVIDENCE_KEYS) {
56
+ const expected = evidence[section]?.[key];
57
+ if (expected === undefined) continue;
58
+ const actual = intent.evidence[key];
59
+ if (actual === null) {
60
+ reasons.push(`evidence ${key} unspecified; ${section} preference not applied`);
61
+ } else if (section === 'prefer' && actual === expected) {
62
+ score += addMatch(matched, reasons, 'evidence', `${key}=${actual}`, CAPABILITY_SCORE_WEIGHTS.evidence);
63
+ } else if (section === 'prefer') {
64
+ addMiss(unmatched, reasons, 'evidence', `${key}=${actual}`, [`${key}=${expected}`]);
65
+ reasons.push(`evidence mismatch ${key}: expected ${expected}, got ${actual}`);
66
+ } else if (actual === expected) {
67
+ addMiss(unmatched, reasons, 'evidence', `${key}=${actual}`, []);
68
+ score -= CAPABILITY_SCORE_WEIGHTS.evidence;
69
+ reasons.push(`evidence de-emphasized ${key}=${actual} (-${CAPABILITY_SCORE_WEIGHTS.evidence})`);
70
+ } else {
71
+ reasons.push(`evidence ${key}=${actual} did not trigger de-emphasis for ${expected}`);
72
+ }
73
+ }
74
+ }
75
+ return { skill: definition.skill, score, matched, unmatched, reasons };
76
+ }
77
+
78
+ export function rankCapabilities(intentInput, capabilities = CAPABILITIES) {
79
+ const intent = normalizeIntent(intentInput);
80
+ return capabilities.map((capability) => scoreIntent(intent, capability))
81
+ .sort((left, right) => right.score - left.score || (left.skill < right.skill ? -1 : left.skill > right.skill ? 1 : 0));
82
+ }
package/src/catalog.js CHANGED
@@ -16,7 +16,7 @@ export const SKILLS = [
16
16
  { id: 'showdar-ops', domain: 'ops', description: 'Use when inspecting or changing CI/CD, containers, environments, deployment, observability, rollback, or runtime operations.' },
17
17
  ];
18
18
 
19
- export const AI_TARGETS = ['codex', 'opencode', 'claude', 'universal', 'all'];
19
+ export const AI_TARGETS = ['codex', 'opencode', 'cursor', 'claude', 'universal', 'all'];
20
20
 
21
21
  const ids = (...values) => values;
22
22
 
@@ -55,3 +55,16 @@ export function resolveProfile(profile) {
55
55
  export function getSkill(id) {
56
56
  return SKILLS.find((skill) => skill.id === id) ?? null;
57
57
  }
58
+
59
+ export function normalizeSkillName(name) {
60
+ if (typeof name !== 'string' || !name.trim()) throw new Error('Skill name is required.');
61
+ const trimmed = name.trim();
62
+ const canonical = trimmed.startsWith('showdar-') ? trimmed : `showdar-${trimmed}`;
63
+ const skill = getSkill(canonical);
64
+ if (!skill) {
65
+ const known = SKILLS.map((s) => s.id.replace(/^showdar-/, '')).join(', ');
66
+ throw new Error(`Unknown skill "${name}". Available skills: ${known}`);
67
+ }
68
+ return skill.id;
69
+ }
70
+