showdar-skills 0.12.0 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -6,6 +6,31 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.13.0]
10
+
11
+ ### Added
12
+
13
+ - `showdar add profile <profile>`: additive union of built-in profile primitives without replacing an existing base profile or other installed skills.
14
+ - `showdar add workflow <name>`: install built-in workflow skills and any missing stage primitives; `showdar add feature` / `showdar add showdar-feature` remain equivalent aliases.
15
+ - `showdar add workflow ./local.json`: convenient spelling for existing project-scoped custom workflow installation.
16
+
17
+ ### Changed
18
+
19
+ - Additive installation preserves existing extension metadata and supports projects initialized with `--ai all`.
20
+ - `showdar init --profile ...` retains its explicit replace semantics; existing standalone `showdar add-workflow` remains supported.
21
+ - Added installer regression coverage for existing-skill preservation, aliases, workflow stage closure, custom workflows, foreign-path safety and re-init replacement.
22
+
23
+
24
+ ## [0.12.1]
25
+
26
+ ### Fixed
27
+
28
+ - Added read-only `showdar guard --mutation local-write --json` to block task writes on integration branches until task branch preparation, with an explicit repository-local direct-work opt-in.
29
+ - Updated native harness guidance and mutating skills to require automatic branch preparation and a passing guard before agent-controlled file writes; Cursor routing rules now apply automatically.
30
+ - Re-adding an existing Showdar-managed skill refreshes its packaged SKILL.md from the upgraded CLI only when its recorded hash matches; user-modified skill files are preserved and cause an explicit error.
31
+ - This is an agent/tool-instruction gate, not a cross-harness filesystem interceptor, and does not grant source/remote mutation authority.
32
+
33
+
9
34
  ## [0.12.0]
10
35
 
11
36
  ### Added
package/README.md CHANGED
@@ -40,6 +40,32 @@ Claude Code as supported installation targets. Choose `backend`, `qa`, or
40
40
  `product` when that gives discovery a more precise context; use `full` when
41
41
  you want all capabilities available.
42
42
 
43
+ ## Additive installation
44
+
45
+ `showdar init --profile <name>` **replaces** the Showdar-owned skill selection.
46
+ `showdar add` always **preserves** existing skills and the selected base profile:
47
+
48
+ ```bash
49
+ showdar init --profile developer --ai opencode
50
+ showdar add git # same as showdar add showdar-git
51
+ showdar add profile insurance # union, no deletions
52
+ showdar add workflow feature # workflow + missing primitive stages
53
+ showdar add workflow ./acme-release.json # standalone custom workflow, project scope
54
+ ```
55
+
56
+ Both `showdar add feature` and `showdar add showdar-feature` are aliases for
57
+ `showdar add workflow feature`, including automatic installation of missing
58
+ primitive stage skills. All built-in profiles are additive via `add profile`;
59
+ `init` remains the explicit replace operation. Existing workflow packs and
60
+ custom workflow definitions are preserved when adding skills. The runtime
61
+ router is included in the Showdar CLI and is automatically referenced by
62
+ updated native guidance; it does not require an additional skill.
63
+
64
+ Built-in workflow dependency installation does **not** execute the workflow,
65
+ authorize source mutation, commit, merge or push. A custom workflow JSON file
66
+ uses the existing `add-workflow` validation/manifest semantics; remote files
67
+ and arbitrary executable plugins are unsupported.
68
+
43
69
  ## Runtime routing and task branches
44
70
 
45
71
  ```bash
@@ -47,6 +73,7 @@ printf '%s\n' 'Implement the approved underwriting form and add tests' | showdar
47
73
  showdar route --prompt "Explain insurance terminology"
48
74
  showdar git-start --dry-run --type feature --name "IDP-123 Add pricing form"
49
75
  showdar git-start --type feature --name "IDP-123 Add pricing form"
76
+ showdar guard --mutation local-write --json
50
77
  ```
51
78
 
52
79
  Prefer `--stdin` for arbitrary or multiline prompts. Supply stdin as literal data;
@@ -91,6 +118,18 @@ The four workflows remain native discoverable/installable skills; `route` does n
91
118
  introduce workflow selection. Profiles remain primitive-only: `full` contains all
92
119
  18 primitives, and the insurance profile remains unchanged.
93
120
 
121
+ Before the first local-write task source edit, the coding agent must run
122
+ `showdar guard --mutation local-write --json` and require `ok=true` and
123
+ `data.allowed=true`. When on develop/main/integration, it must execute
124
+ `showdar git-start` (not only suggest it), confirm the branch, and rerun guard.
125
+ This is an instruction-level agent preflight, **not an OS/filesystem hook**:
126
+ tools that ignore Showdar can still write files. Cursor's generated rule now
127
+ uses `alwaysApply: true` so the Git preflight isn't limited to manually
128
+ activated skill calls. Existing project guidance must be refreshed after the
129
+ package update (for example by running `showdar add git` with the new CLI).
130
+ Re-adding an installed skill refreshes its managed SKILL.md and guidance only
131
+ if the owned copy has not been locally modified; drift refuses to overwrite.
132
+
94
133
  Before the first local-write task source edit on develop/development/dev/main/master
95
134
  or the repository default/integration branch, prepare a task branch. Priority is
96
135
  repository instructions/documented convention, explicit current user instruction,
@@ -832,6 +871,8 @@ Product behavior notes:
832
871
  ```bash
833
872
  showdar init [--scope <project|global>] --ai <target> --profile <profile>
834
873
  showdar add <skill> [--ai <target>] [--scope <project|global>]
874
+ showdar add profile <profile> [--ai <target>] [--scope <project|global>]
875
+ showdar add workflow <builtin-name|local-json-path> [--ai <target>] [--scope <project|global>]
835
876
  showdar add-pack <local-path>
836
877
  showdar remove-pack <name>
837
878
  showdar add-workflow <local-path>
package/bin/showdar.js CHANGED
@@ -2,12 +2,13 @@
2
2
  import path from 'node:path';
3
3
  import { parseArgs } from 'node:util';
4
4
  import { startTaskBranch, formatGitStart } from '../src/git-start.js';
5
+ import { guardMutation, formatMutationGuard } from '../src/git-guard.js';
5
6
  import { routeRequest, formatRoute } from '../src/runtime-route.js';
6
7
  import { homedir } from 'node:os';
7
8
  import { readFile } from 'node:fs/promises';
8
9
  import { fileURLToPath } from 'node:url';
9
- import { AI_TARGETS, PRIMITIVE_COUNT, PROFILE_ALIASES, PROFILES, SKILLS, TOTAL_COUNT, WORKFLOW_COUNT, canonicalProfile, isDeprecatedProfile, resolveProfile } from '../src/catalog.js';
10
- import { addPack, addSkill, addWorkflow, globalManifestPath, initGlobal, initProject, inspectGlobal, inspectProject, listExtensions, listExtensionsDetailed, removeGlobal, removePack, removeProject, validatePackSource, createPack, inspectPack, doctor, updatePack, readProjectOverrides } from '../src/project.js';
10
+ import { AI_TARGETS, PRIMITIVE_COUNT, PROFILE_ALIASES, PROFILES, SKILLS, TOTAL_COUNT, WORKFLOW_COUNT, canonicalProfile, isDeprecatedProfile, resolveProfile, getWorkflow, normalizeSkillName } from '../src/catalog.js';
11
+ import { addPack, addSkill, addSkills, addWorkflow, globalManifestPath, initGlobal, initProject, inspectGlobal, inspectProject, listExtensions, listExtensionsDetailed, removeGlobal, removePack, removeProject, validatePackSource, createPack, inspectPack, doctor, updatePack, readProjectOverrides } from '../src/project.js';
11
12
  import { formatPlanPreviewHuman, projectPackUpdatePreview } from '../src/pack-plan.js';
12
13
  import { assessCheckpointAgainstCandidate } from '../src/pack-inspect.js';
13
14
  import { validateRepository } from '../src/validate.js';
@@ -44,6 +45,10 @@ function printHelp(version, command = null) {
44
45
  console.log('Usage: showdar git-start --type <type> --name <task> [--base <branch>] [--dry-run] [--json]');
45
46
  return;
46
47
  }
48
+ if (command === 'guard') {
49
+ console.log('Usage: showdar guard --mutation <read-only|local-write> [--json]');
50
+ return;
51
+ }
47
52
  if (command === 'init') {
48
53
  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, one native instruction surface, and project .showdar.json. Global scope writes verified user skill directories and ~/.showdar/global.json without instruction 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, generates OpenCode and Claude commands, and writes only the AGENTS.md block.\n\nProfiles: ${Object.keys(PROFILES).join(', ')}\nDeprecated aliases: ${Object.entries(PROFILE_ALIASES).map(([alias, target]) => `${alias} -> ${target}`).join(', ')}\nAI targets: ${AI_TARGETS.join(', ')}`);
49
54
  return;
@@ -53,10 +58,10 @@ function printHelp(version, command = null) {
53
58
  return;
54
59
  }
55
60
  if (command === 'add') {
56
- 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.`);
61
+ console.log(`Showdar Skills ${version}\n\nUsage:\n showdar add <skill> [--ai <target>] [--scope <project|global>]\n showdar add profile <profile> [--ai <target>] [--scope <project|global>]\n showdar add workflow <builtin-name|local-json-path> [--ai <target>] [--scope <project|global>]\n\nExamples:\n showdar add git\n showdar add showdar-git\n showdar add profile insurance\n showdar add workflow feature\n showdar add workflow ./workflows/acme-release.json\n\nAdd preserves installed skills; init replaces the managed set. Built-in workflows add required stages.`);
57
62
  return;
58
63
  }
59
- console.log(`Showdar Skills ${version}\n\nUsage:\n showdar init ${scopeUsage} [--profile <name>] [--ai <universal|codex|opencode|cursor|claude|all>] [--pack <local-path>]\n showdar add <skill> [--ai <universal|codex|opencode|cursor|claude>] [--scope <project|global>]\n showdar route (--stdin | --prompt <text>) [--json]\n showdar git-start --type <type> --name <task> [--base <branch>] [--dry-run] [--json]\n showdar add-pack <local-path>\n showdar remove-pack <name>\n showdar add-workflow <local-path>\n showdar status ${scopeUsage}\n showdar doctor ${scopeUsage}\n showdar validate\n showdar list [--extensions]\n showdar remove ${scopeUsage}\n showdar create-pack <path> [--vendor <vendor>] [--description <text>] [--with-workflow <id>] [--with-profile <name>]\n showdar validate-pack <local-path> [--json]\n showdar inspect-pack <local-path> [--json] [--checkpoint <file>]\n showdar doctor ${scopeUsage} [--extensions] [--json]\n showdar update-pack <local-path> [--dry-run] [--json]\n showdar validate\n showdar list [--extensions] [--json]\n showdar remove ${scopeUsage}\n\nExtension packs accept local directories/workspace paths only; tarball, URL, Git, and registry sources are rejected.\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(', ')}`);
64
+ console.log(`Showdar Skills ${version}\n\nUsage:\n showdar init ${scopeUsage} [--profile <name>] [--ai <universal|codex|opencode|cursor|claude|all>] [--pack <local-path>]\n showdar add <skill> [--ai <target>] [--scope <project|global>]\n showdar add profile <profile> [--ai <target>] [--scope <project|global>]\n showdar add workflow <builtin-name|local-json-path> [--ai <target>] [--scope <project|global>]\n showdar route (--stdin | --prompt <text>) [--json]\n showdar git-start --type <type> --name <task> [--base <branch>] [--dry-run] [--json]\n showdar guard --mutation <read-only|local-write> [--json]\n showdar add-pack <local-path>\n showdar remove-pack <name>\n showdar add-workflow <local-path>\n showdar status ${scopeUsage}\n showdar doctor ${scopeUsage}\n showdar validate\n showdar list [--extensions]\n showdar remove ${scopeUsage}\n showdar create-pack <path> [--vendor <vendor>] [--description <text>] [--with-workflow <id>] [--with-profile <name>]\n showdar validate-pack <local-path> [--json]\n showdar inspect-pack <local-path> [--json] [--checkpoint <file>]\n showdar doctor ${scopeUsage} [--extensions] [--json]\n showdar update-pack <local-path> [--dry-run] [--json]\n showdar validate\n showdar list [--extensions] [--json]\n showdar remove ${scopeUsage}\n\nExtension packs accept local directories/workspace paths only; tarball, URL, Git, and registry sources are rejected.\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(', ')}`);
60
65
  }
61
66
 
62
67
  async function main() {
@@ -70,10 +75,22 @@ async function main() {
70
75
  console.log(version);
71
76
  return;
72
77
  }
73
- if (!['route', 'git-start'].includes(command) && (args.includes('--help') || args.includes('-h'))) return printHelp(version, command);
78
+ if (!['route', 'git-start', 'guard'].includes(command) && (args.includes('--help') || args.includes('-h'))) return printHelp(version, command);
74
79
 
75
80
  const scope = ['init', 'status', 'doctor', 'remove', 'add'].includes(command) ? scopeAfter(args) : null;
76
81
 
82
+ if (command === 'guard') {
83
+ const { values } = parseArgs({ args: args.slice(1), options: { mutation: { type: 'string' }, json: { type: 'boolean' }, help: { type: 'boolean', short: 'h' } } });
84
+ if (values.help) return printHelp(version, command);
85
+ const data = guardMutation({ cwd: projectRoot, mutation: values.mutation });
86
+ const errors = data.allowed ? [] : [{ code: data.code, message: 'Task-owned source mutation is blocked until Git preflight passes.' }];
87
+ console.log(values.json
88
+ ? JSON.stringify({ schemaVersion: 1, command, ok: data.allowed, data, warnings: [], errors }, null, 2)
89
+ : formatMutationGuard(data));
90
+ if (!data.allowed) process.exitCode = 1;
91
+ return;
92
+ }
93
+
77
94
  if (command === 'git-start') {
78
95
  const { values } = parseArgs({ args: args.slice(1), options: { type: { type: 'string' }, name: { type: 'string' }, base: { type: 'string' }, 'dry-run': { type: 'boolean' }, json: { type: 'boolean' }, help: { type: 'boolean', short: 'h' } } });
79
96
  if (values.help) return printHelp(version, command);
@@ -225,20 +242,49 @@ async function main() {
225
242
  }
226
243
 
227
244
  if (command === 'add') {
228
- const positional = args.filter((a, i) => i > 0 && !a.startsWith('--') && args[i - 1] !== '--ai' && args[i - 1] !== '--scope');
229
- const skillArg = positional[0];
230
- if (!skillArg) throw new Error('Skill name is required. Usage: showdar add <skill> [--ai <target>] [--scope <project|global>]');
245
+ const positional = args.filter((arg, index) => index > 0 && !arg.startsWith('--') && args[index - 1] !== '--ai' && args[index - 1] !== '--scope');
246
+ const kind = positional[0];
247
+ if (!kind) throw new Error('Usage: showdar add <skill> | profile <profile> | workflow <builtin-name|local-json-path>');
231
248
  const hasAiFlag = args.includes('--ai');
232
249
  const hasScopeFlag = args.includes('--scope');
233
- const result = await addSkill({
250
+ const options = {
234
251
  cwd: projectRoot,
235
- skill: skillArg,
236
252
  ai: hasAiFlag ? valueAfter(args, '--ai', 'universal') : null,
237
253
  scope: hasScopeFlag ? scope : null,
238
254
  home: homedir(),
239
255
  packageRoot,
240
256
  packageVersion: version,
241
- });
257
+ };
258
+ if (kind === 'profile') {
259
+ if (positional.length !== 2) throw new Error('Usage: showdar add profile <profile> [--ai <target>] [--scope <project|global>]');
260
+ const profileName = positional[1];
261
+ const selected = resolveProfile(profileName);
262
+ const result = await addSkills({ ...options, skills: selected });
263
+ if (isDeprecatedProfile(profileName)) console.warn(`Warning: profile "${profileName}" is deprecated; use "${canonicalProfile(profileName)}".`);
264
+ console.log(`Showdar profile added.\nAdded profile: ${canonicalProfile(profileName)}\nNew skills: ${result.added}\nAlready installed: ${result.alreadyInstalled}\nTotal requested: ${result.skills.length}\nExisting profile preserved: ${result.profile ?? '(none)'}\nScope: ${result.scope}\nAI: ${result.ai}`);
265
+ return;
266
+ }
267
+ const installBuiltinWorkflow = async workflow => {
268
+ const result = await addSkills({ ...options, skills: [workflow.id, ...workflow.stages] });
269
+ console.log(`Showdar workflow added.\nWorkflow: ${workflow.id}\nNew skills: ${result.added}\nAlready installed: ${result.alreadyInstalled}\nRequired stage skills: ${workflow.stages.join(', ')}\nScope: ${result.scope}\nAI: ${result.ai}`);
270
+ };
271
+ if (kind === 'workflow') {
272
+ if (positional.length !== 2) throw new Error('Usage: showdar add workflow <builtin-name|local-json-path>');
273
+ const name = positional[1];
274
+ const builtin = getWorkflow(name.startsWith('showdar-') ? name : `showdar-${name}`);
275
+ if (builtin) return installBuiltinWorkflow(builtin);
276
+ if (!name.endsWith('.json')) throw new Error(`Unknown built-in workflow "${name}". For custom workflows provide a local JSON file.`);
277
+ if (hasAiFlag || (hasScopeFlag && scope !== 'project')) {
278
+ throw new Error('Custom workflow files support project scope only and do not accept --ai.');
279
+ }
280
+ const result = await addWorkflow({ cwd: projectRoot, source: name });
281
+ console.log(`Showdar custom workflow added.\nWorkflow: ${result.workflow}\nPath: ${result.path}`);
282
+ return;
283
+ }
284
+ if (positional.length !== 1) throw new Error('Usage: showdar add <skill> [--ai <target>] [--scope <project|global>]');
285
+ const workflow = getWorkflow(normalizeSkillName(kind));
286
+ if (workflow) return installBuiltinWorkflow(workflow);
287
+ const result = await addSkill({ ...options, skill: kind });
242
288
  console.log(`Showdar skill ${result.added ? 'added' : 'already installed'}.\nSkill: ${result.skill}\nScope: ${result.scope}\nAI: ${result.ai}\nPath: ${result.destination}`);
243
289
  return;
244
290
  }
@@ -377,7 +423,7 @@ async function main() {
377
423
 
378
424
  main().catch((error) => {
379
425
  const command = process.argv[2];
380
- if (['route', 'git-start'].includes(command) && process.argv.slice(3).includes('--json')) {
426
+ if (['route', 'git-start', 'guard'].includes(command) && process.argv.slice(3).includes('--json')) {
381
427
  console.log(JSON.stringify({ schemaVersion: 1, command, ok: false, data: null, warnings: [], errors: [{ code: 'INVALID_REQUEST', message: error.message }] }, null, 2));
382
428
  } else console.error(`showdar: ${error.message}`);
383
429
  process.exitCode = 1;
package/package.json CHANGED
@@ -1,9 +1,11 @@
1
1
  {
2
2
  "name": "showdar-skills",
3
- "version": "0.12.0",
3
+ "version": "0.13.0",
4
4
  "description": "Production-grade software engineering lifecycle skills for coding agents.",
5
5
  "type": "module",
6
- "bin": { "showdar": "./bin/showdar.js" },
6
+ "bin": {
7
+ "showdar": "./bin/showdar.js"
8
+ },
7
9
  "scripts": {
8
10
  "test": "node --test",
9
11
  "validate": "node bin/showdar.js validate",
@@ -16,9 +18,38 @@
16
18
  "smoke": "node scripts/package-smoke.mjs",
17
19
  "release:check": "node scripts/check-release-version.mjs"
18
20
  },
19
- "engines": { "node": ">=20" },
20
- "files": ["bin", "src", "config", "skills", "commands", "router", "bundles", "profiles", "engine", "README.md", "LICENSE", "CHANGELOG.md", "MIGRATION.md"],
21
- "keywords": ["agent-skills", "coding-agents", "codex", "opencode", "claude-code", "software-engineering", "developer-tools", "requirements", "qa", "security", "devops", "workflow"],
21
+ "engines": {
22
+ "node": ">=20"
23
+ },
24
+ "files": [
25
+ "bin",
26
+ "src",
27
+ "config",
28
+ "skills",
29
+ "commands",
30
+ "router",
31
+ "bundles",
32
+ "profiles",
33
+ "engine",
34
+ "README.md",
35
+ "LICENSE",
36
+ "CHANGELOG.md",
37
+ "MIGRATION.md"
38
+ ],
39
+ "keywords": [
40
+ "agent-skills",
41
+ "coding-agents",
42
+ "codex",
43
+ "opencode",
44
+ "claude-code",
45
+ "software-engineering",
46
+ "developer-tools",
47
+ "requirements",
48
+ "qa",
49
+ "security",
50
+ "devops",
51
+ "workflow"
52
+ ],
22
53
  "license": "MIT",
23
54
  "repository": {
24
55
  "type": "git",
@@ -5,6 +5,11 @@ description: Use when resolving an observed defect end-to-end, adaptively sequen
5
5
 
6
6
  # Showdar Bugfix
7
7
 
8
+ ## Mandatory Git preflight for task-owned writes
9
+
10
+ Before any task-owned source/config/test/docs write, apply `showdar-git` branch policy: on develop/main/integration, inspect repository conventions and EXECUTE `showdar git-start --type <type> --name "<task>"` to prepare the task branch before editing. Then run `showdar guard --mutation local-write --json` and require `ok=true` and `data.allowed=true` before ANY file-writing tool. If blocked, STOP before editing. On an existing task branch run guard without creating a new branch. Recheck when a read-only stage turns into a write. If CLI is missing, manually prepare and verify safe Git branch state; do not silently write on develop/main. Explicit documented direct-work policy wins; never infer stash, reset, commit, merge, or push.
11
+
12
+
8
13
  ## Purpose
9
14
 
10
15
  - Resolve an observed defect safely from evidence to verified fix.
@@ -5,6 +5,11 @@ description: Use when implementing or refactoring an agreed application change w
5
5
 
6
6
  # Showdar Build
7
7
 
8
+ ## Mandatory Git preflight for task-owned writes
9
+
10
+ Before any task-owned source/config/test/docs write, apply `showdar-git` branch policy: on develop/main/integration, inspect repository conventions and EXECUTE `showdar git-start --type <type> --name "<task>"` to prepare the task branch before editing. Then run `showdar guard --mutation local-write --json` and require `ok=true` and `data.allowed=true` before ANY file-writing tool. If blocked, STOP before editing. On an existing task branch run guard without creating a new branch. Recheck when a read-only stage turns into a write. If CLI is missing, manually prepare and verify safe Git branch state; do not silently write on develop/main. Explicit documented direct-work policy wins; never infer stash, reset, commit, merge, or push.
11
+
12
+
8
13
  ## Purpose
9
14
 
10
15
  - Convert an approved requirement/plan into maintainable production code.
@@ -5,6 +5,11 @@ description: Use when observed behavior fails through crashes, regressions, buil
5
5
 
6
6
  # Showdar Debug
7
7
 
8
+ ## Mandatory Git preflight for task-owned writes
9
+
10
+ Before any task-owned source/config/test/docs write, apply `showdar-git` branch policy: on develop/main/integration, inspect repository conventions and EXECUTE `showdar git-start --type <type> --name "<task>"` to prepare the task branch before editing. Then run `showdar guard --mutation local-write --json` and require `ok=true` and `data.allowed=true` before ANY file-writing tool. If blocked, STOP before editing. On an existing task branch run guard without creating a new branch. Recheck when a read-only stage turns into a write. If CLI is missing, manually prepare and verify safe Git branch state; do not silently write on develop/main. Explicit documented direct-work policy wins; never infer stash, reset, commit, merge, or push.
11
+
12
+
8
13
  ## Purpose
9
14
 
10
15
  - Find and confirm root cause before modifying production behavior.
@@ -5,6 +5,11 @@ description: Use when product UI needs design direction, UX decisions, responsiv
5
5
 
6
6
  # Showdar Design
7
7
 
8
+ ## Mandatory Git preflight for task-owned writes
9
+
10
+ Before any task-owned source/config/test/docs write, apply `showdar-git` branch policy: on develop/main/integration, inspect repository conventions and EXECUTE `showdar git-start --type <type> --name "<task>"` to prepare the task branch before editing. Then run `showdar guard --mutation local-write --json` and require `ok=true` and `data.allowed=true` before ANY file-writing tool. If blocked, STOP before editing. On an existing task branch run guard without creating a new branch. Recheck when a read-only stage turns into a write. If CLI is missing, manually prepare and verify safe Git branch state; do not silently write on develop/main. Explicit documented direct-work policy wins; never infer stash, reset, commit, merge, or push.
11
+
12
+
8
13
  ## Purpose
9
14
 
10
15
  - Produce product-specific UI direction instead of generic component-library output.
@@ -5,6 +5,11 @@ description: Use when implementing a complete feature end-to-end, adaptively seq
5
5
 
6
6
  # Showdar Feature
7
7
 
8
+ ## Mandatory Git preflight for task-owned writes
9
+
10
+ Before any task-owned source/config/test/docs write, apply `showdar-git` branch policy: on develop/main/integration, inspect repository conventions and EXECUTE `showdar git-start --type <type> --name "<task>"` to prepare the task branch before editing. Then run `showdar guard --mutation local-write --json` and require `ok=true` and `data.allowed=true` before ANY file-writing tool. If blocked, STOP before editing. On an existing task branch run guard without creating a new branch. Recheck when a read-only stage turns into a write. If CLI is missing, manually prepare and verify safe Git branch state; do not silently write on develop/main. Explicit documented direct-work policy wins; never infer stash, reset, commit, merge, or push.
11
+
12
+
8
13
  ## Purpose
9
14
 
10
15
  - Implement a complete feature end-to-end by sequencing primitive skills.
@@ -48,6 +48,12 @@ This skill owns repository state transitions, not source-code review or GitHub a
48
48
 
49
49
  ## Task branch isolation
50
50
 
51
+ **Mandatory pre-write gate:** Installing `showdar-git` adds project-wide branch guidance. For every task-owned source/config/test/docs change, including tasks executed by another primitive, inspect current Git state, then run `showdar guard --mutation local-write --json` before file-writing tools. Require `ok=true` and `data.allowed=true`; if blocked, STOP without editing.
52
+
53
+ On integration/default branches proactively run `showdar git-start --type <type> --name "<task>"` after repository/user convention inspection, then rerun guard. On an existing task branch, run guard without creating a nested branch. If the CLI is unavailable, use equivalent manual safe Git branch preparation and verify branch before editing. Explicit direct-work policy is a reviewed repo-local opt-in (`showdar.gitDirectWork=true`); never silently set it to bypass the gate.
54
+
55
+ This gate is enforced by agent/tool instructions, not a filesystem interceptor; arbitrary writes outside Showdar remain possible. Neither guard nor git-start authorizes edits, commits, merges, or pushes.
56
+
51
57
  Before the first task-owned source edit for a local-write engineering task, inspect Git state (`git status --short`, current branch, branch history and repository policy). Direct develop/main edits are not the default: on develop, development, dev, main, master or the default/integration branch, prepare a dedicated task branch before source mutation.
52
58
 
53
59
  Priority: repository instructions/documented branching convention; explicit current user instruction; clearly detected existing convention; Showdar safe default. Explicit trunk-based/direct-work policy wins: do not impose GitFlow. Branch preparation does not authorize source mutation.
@@ -5,6 +5,11 @@ description: Use when investigating and recovering from an active operational in
5
5
 
6
6
  # Showdar Incident
7
7
 
8
+ ## Mandatory Git preflight for task-owned writes
9
+
10
+ Before any task-owned source/config/test/docs write, apply `showdar-git` branch policy: on develop/main/integration, inspect repository conventions and EXECUTE `showdar git-start --type <type> --name "<task>"` to prepare the task branch before editing. Then run `showdar guard --mutation local-write --json` and require `ok=true` and `data.allowed=true` before ANY file-writing tool. If blocked, STOP before editing. On an existing task branch run guard without creating a new branch. Recheck when a read-only stage turns into a write. If CLI is missing, manually prepare and verify safe Git branch state; do not silently write on develop/main. Explicit documented direct-work policy wins; never infer stash, reset, commit, merge, or push.
11
+
12
+
8
13
  ## Purpose
9
14
 
10
15
  - Handle an active operational failure or service-impacting incident.
@@ -5,6 +5,11 @@ description: Use when inspecting or changing CI/CD, containers, environments, de
5
5
 
6
6
  # Showdar Ops
7
7
 
8
+ ## Mandatory Git preflight for task-owned writes
9
+
10
+ Before any task-owned source/config/test/docs write, apply `showdar-git` branch policy: on develop/main/integration, inspect repository conventions and EXECUTE `showdar git-start --type <type> --name "<task>"` to prepare the task branch before editing. Then run `showdar guard --mutation local-write --json` and require `ok=true` and `data.allowed=true` before ANY file-writing tool. If blocked, STOP before editing. On an existing task branch run guard without creating a new branch. Recheck when a read-only stage turns into a write. If CLI is missing, manually prepare and verify safe Git branch state; do not silently write on develop/main. Explicit documented direct-work policy wins; never infer stash, reset, commit, merge, or push.
11
+
12
+
8
13
  ## Purpose
9
14
 
10
15
  - Analyze and perform tightly scoped operational engineering across CI/CD, containers, environments, runtime configuration, deployment, observability, rollback, and release operations.
@@ -5,6 +5,11 @@ description: Use when agreed behavior needs a bounded implementation plan, chang
5
5
 
6
6
  # Showdar Plan
7
7
 
8
+ ## Mandatory Git preflight for task-owned writes
9
+
10
+ Before any task-owned source/config/test/docs write, apply `showdar-git` branch policy: on develop/main/integration, inspect repository conventions and EXECUTE `showdar git-start --type <type> --name "<task>"` to prepare the task branch before editing. Then run `showdar guard --mutation local-write --json` and require `ok=true` and `data.allowed=true` before ANY file-writing tool. If blocked, STOP before editing. On an existing task branch run guard without creating a new branch. Recheck when a read-only stage turns into a write. If CLI is missing, manually prepare and verify safe Git branch state; do not silently write on develop/main. Explicit documented direct-work policy wins; never infer stash, reset, commit, merge, or push.
11
+
12
+
8
13
  ## Purpose
9
14
 
10
15
  - Turn an idea, bug requirement, migration, or design decision into executable engineering work.
@@ -5,6 +5,11 @@ description: Use when interrupted or partial engineering work must be reconstruc
5
5
 
6
6
  # Showdar Recover
7
7
 
8
+ ## Mandatory Git preflight for task-owned writes
9
+
10
+ Before any task-owned source/config/test/docs write, apply `showdar-git` branch policy: on develop/main/integration, inspect repository conventions and EXECUTE `showdar git-start --type <type> --name "<task>"` to prepare the task branch before editing. Then run `showdar guard --mutation local-write --json` and require `ok=true` and `data.allowed=true` before ANY file-writing tool. If blocked, STOP before editing. On an existing task branch run guard without creating a new branch. Recheck when a read-only stage turns into a write. If CLI is missing, manually prepare and verify safe Git branch state; do not silently write on develop/main. Explicit documented direct-work policy wins; never infer stash, reset, commit, merge, or push.
11
+
12
+
8
13
  ## Purpose
9
14
 
10
15
  - Recover context after session interruption, compaction, agent failure, crash, or handoff.
@@ -5,6 +5,11 @@ description: Use when preparing, validating, or executing a release lifecycle, a
5
5
 
6
6
  # Showdar Release
7
7
 
8
+ ## Mandatory Git preflight for task-owned writes
9
+
10
+ Before any task-owned source/config/test/docs write, apply `showdar-git` branch policy: on develop/main/integration, inspect repository conventions and EXECUTE `showdar git-start --type <type> --name "<task>"` to prepare the task branch before editing. Then run `showdar guard --mutation local-write --json` and require `ok=true` and `data.allowed=true` before ANY file-writing tool. If blocked, STOP before editing. On an existing task branch run guard without creating a new branch. Recheck when a read-only stage turns into a write. If CLI is missing, manually prepare and verify safe Git branch state; do not silently write on develop/main. Explicit documented direct-work policy wins; never infer stash, reset, commit, merge, or push.
11
+
12
+
8
13
  ## Purpose
9
14
 
10
15
  - Prepare and execute a software release through existing primitive boundaries.
@@ -5,6 +5,11 @@ description: Use when product or business input needs explicit behavior, rules,
5
5
 
6
6
  # Showdar Requirements
7
7
 
8
+ ## Mandatory Git preflight for task-owned writes
9
+
10
+ Before any task-owned source/config/test/docs write, apply `showdar-git` branch policy: on develop/main/integration, inspect repository conventions and EXECUTE `showdar git-start --type <type> --name "<task>"` to prepare the task branch before editing. Then run `showdar guard --mutation local-write --json` and require `ok=true` and `data.allowed=true` before ANY file-writing tool. If blocked, STOP before editing. On an existing task branch run guard without creating a new branch. Recheck when a read-only stage turns into a write. If CLI is missing, manually prepare and verify safe Git branch state; do not silently write on develop/main. Explicit documented direct-work policy wins; never infer stash, reset, commit, merge, or push.
11
+
12
+
8
13
  ## Purpose
9
14
 
10
15
  - Convert tickets, briefs, conversations, API notes, and observed repository behavior into implementation-ready requirements.
@@ -5,6 +5,11 @@ description: Use when choosing or implementing automated tests for behavior, reg
5
5
 
6
6
  # Showdar Test
7
7
 
8
+ ## Mandatory Git preflight for task-owned writes
9
+
10
+ Before any task-owned source/config/test/docs write, apply `showdar-git` branch policy: on develop/main/integration, inspect repository conventions and EXECUTE `showdar git-start --type <type> --name "<task>"` to prepare the task branch before editing. Then run `showdar guard --mutation local-write --json` and require `ok=true` and `data.allowed=true` before ANY file-writing tool. If blocked, STOP before editing. On an existing task branch run guard without creating a new branch. Recheck when a read-only stage turns into a write. If CLI is missing, manually prepare and verify safe Git branch state; do not silently write on develop/main. Explicit documented direct-work policy wins; never infer stash, reset, commit, merge, or push.
11
+
12
+
8
13
  ## Purpose
9
14
 
10
15
  - Match test level to the boundary/risk being proven.
@@ -5,6 +5,11 @@ description: Use when upgrading dependencies, frameworks, runtimes, or native pl
5
5
 
6
6
  # Showdar Upgrade
7
7
 
8
+ ## Mandatory Git preflight for task-owned writes
9
+
10
+ Before any task-owned source/config/test/docs write, apply `showdar-git` branch policy: on develop/main/integration, inspect repository conventions and EXECUTE `showdar git-start --type <type> --name "<task>"` to prepare the task branch before editing. Then run `showdar guard --mutation local-write --json` and require `ok=true` and `data.allowed=true` before ANY file-writing tool. If blocked, STOP before editing. On an existing task branch run guard without creating a new branch. Recheck when a read-only stage turns into a write. If CLI is missing, manually prepare and verify safe Git branch state; do not silently write on develop/main. Explicit documented direct-work policy wins; never infer stash, reset, commit, merge, or push.
11
+
12
+
8
13
  ## Purpose
9
14
 
10
15
  - Make upgrades controlled engineering changes rather than version-number edits.
@@ -3,7 +3,7 @@ import { ALL_SKILLS } from './catalog.js';
3
3
  const RUNTIME_GUIDANCE = `Automatic Showdar selection: route the current request through \`showdar route --stdin --json\` when the Showdar CLI is available. Supply the prompt as literal stdin data, never as interpolated shell syntax. Honor the canonical lifecycle primary; do not substitute another installed skill when it is missing. Report missing skills and suggest \`showdar add <name>\`; never auto-install. Include installed domain matches only as specialized context overlays; load returned advisors only when installed. Domain discovery does not grant authority or replace the lifecycle route.
4
4
  Explicit named-skill requests may load that installed skill directly without automatic routing. Workflow skills remain native discoverable choices; the router does not select workflows. If the CLI is unavailable, use native skill discovery/static descriptions below. Do not fetch a CLI through npx or install dependencies automatically.
5
5
  Routing does not authorize mutation. Inspect the returned mutation class and current task authority before work. For local-write tasks, before the first task-owned source edit inspect Git state. On develop, development, dev, main, master or the repository default/integration branch, DO NOT begin source edits yet: prepare one branch per coherent task first. Follow repository instructions/documented convention, explicit current user instruction, clearly detected convention, then the Showdar safe default. Explicit trunk/direct-work policy wins.
6
- Use \`showdar git-start --dry-run --type <type> --name <task>\`, then \`showdar git-start --type <type> --name <task>\`, or equivalent repository-specific preparation. Confirm the task branch before mutating skill execution. Reuse that branch across plan/build/test/review; do not nest branches per skill. Dirty ownership or branch collisions require inspection; never infer stash, reset, restore or clean. Git-start does not authorize source mutation, commit, merge or push. Completion defaults to verify and report on the task branch; merge and push require their own explicit authority.`;
6
+ Before ANY task-owned source/config/test/docs write, execute Git preflight even if the router selected build directly. On integration/default branches inspect repository policy and EXECUTE \`showdar git-start --type <type> --name <task>\` (not just --dry-run), or equivalent repository-safe branch preparation. Then EXECUTE \`showdar guard --mutation local-write --json\` and require ok=true AND data.allowed=true BEFORE invoking any file-writing tool. If blocked, STOP before editing. On a matching task branch run guard without creating a new branch. Recheck before each later mutating stage. Do not infer permission from \`showdar route\`. If the CLI is unavailable, manually inspect Git and confirm the appropriate task branch or documented direct-work policy; never silently write on develop/main. Dirty ownership/branch collisions require inspection; never infer stash/reset/restore/clean. Guard and git-start do not authorize source edits, commit, merge or push. Completion means verify and report.`;
7
7
 
8
8
  const CANONICAL_ROUTE_ORDER = [
9
9
  ['map repository architecture, dependencies, or impact', 'showdar-understand'],
@@ -76,7 +76,7 @@ export function renderCursorRuleBody(skillIds) {
76
76
  const body = renderShowdarInstruction(skillIds).trimEnd();
77
77
  return `---
78
78
  description: Showdar skill and workflow routing guidance for software-engineering tasks
79
- alwaysApply: false
79
+ alwaysApply: true
80
80
  ---
81
81
 
82
82
  ${body}
@@ -0,0 +1,62 @@
1
+ import { execFileSync } from 'node:child_process';
2
+ import { existsSync } from 'node:fs';
3
+ import path from 'node:path';
4
+
5
+ function git(cwd, args, optional = false) {
6
+ try {
7
+ return execFileSync('git', ['--no-optional-locks', ...args], {
8
+ cwd, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'], timeout: 10000,
9
+ }).trim();
10
+ } catch (error) {
11
+ if (optional && error.status === 1) return null;
12
+ throw new Error(`Git inspection failed (${args[0]}): ${error.stderr?.toString().trim() || error.message}`);
13
+ }
14
+ }
15
+
16
+ /**
17
+ * Read-only agent preflight. This reports whether a local-write task has
18
+ * branch isolation; it cannot intercept file writes made by other tools.
19
+ */
20
+ export function guardMutation({ cwd, mutation }) {
21
+ if (!['read-only', 'local-write'].includes(mutation)) {
22
+ throw new Error('guard --mutation must be read-only or local-write.');
23
+ }
24
+ if (mutation === 'read-only') {
25
+ return { allowed: true, code: 'read-only', mutation, currentBranch: null, integrationBranch: null, directWork: false };
26
+ }
27
+
28
+ const root = git(cwd, ['rev-parse', '--show-toplevel']);
29
+ const currentBranch = git(root, ['symbolic-ref', '--quiet', '--short', 'HEAD'], true);
30
+ const config = key => git(root, ['config', '--local', '--get', key], true);
31
+ const directWork = config('showdar.gitDirectWork');
32
+ if (directWork !== null && !['true', 'false'].includes(directWork)) {
33
+ throw new Error('showdar.gitDirectWork must be true or false.');
34
+ }
35
+ const configuredBase = config('showdar.gitBase');
36
+ const defaultRef = git(root, ['symbolic-ref', '--quiet', '--short', 'refs/remotes/origin/HEAD'], true)?.replace(/^origin\//, '') ?? null;
37
+ const integrationNames = new Set(['develop', 'development', 'dev', 'main', 'master', configuredBase, defaultRef].filter(Boolean));
38
+ const context = {
39
+ mutation, currentBranch, integrationBranch: configuredBase ?? defaultRef,
40
+ directWork: directWork === 'true',
41
+ };
42
+
43
+ for (const marker of ['MERGE_HEAD', 'CHERRY_PICK_HEAD', 'REVERT_HEAD', 'rebase-merge', 'rebase-apply', 'BISECT_LOG', 'sequencer']) {
44
+ const location = git(root, ['rev-parse', '--git-path', marker]);
45
+ if (existsSync(path.resolve(root, location))) {
46
+ return { allowed: false, code: 'git-operation-active', ...context };
47
+ }
48
+ }
49
+ if (!currentBranch) return { allowed: false, code: 'detached-head', ...context };
50
+
51
+ const isIntegration = integrationNames.has(currentBranch);
52
+ const allowed = !isIntegration || directWork === 'true';
53
+ const code = !allowed ? 'task-branch-required'
54
+ : isIntegration ? 'direct-work-configured' : 'task-branch-ready';
55
+ return { allowed, code, ...context };
56
+ }
57
+
58
+ export function formatMutationGuard(data) {
59
+ return `${data.allowed ? 'ALLOWED' : 'BLOCKED'}: ${data.code}
60
+ Mutation: ${data.mutation}
61
+ Branch: ${data.currentBranch ?? '(detached or not inspected)'}${data.allowed ? '' : '\nPrepare a safe task branch with showdar git-start, then rerun guard. Do not write source while blocked.'}`;
62
+ }
package/src/project.js CHANGED
@@ -584,12 +584,38 @@ export async function addSkill({ cwd, skill, ai = null, scope = null, home = hom
584
584
 
585
585
  const effectiveAi = ai ?? existingManifest?.ai ?? 'universal';
586
586
  const effectiveScope = scope ?? existingManifest?.scope ?? 'project';
587
- if (!NATIVE_TARGETS.includes(effectiveAi)) throw new Error(`Unknown AI target "${effectiveAi}".`);
587
+ if (effectiveAi !== 'all' && !NATIVE_TARGETS.includes(effectiveAi)) throw new Error(`Unknown AI target "${effectiveAi}".`);
588
588
  if (effectiveScope !== 'project' && effectiveScope !== 'global') throw new Error(`Unknown scope "${effectiveScope}".`);
589
589
 
590
590
  if (existingManifest?.skills?.includes(skillId)) {
591
591
  const refreshedFiles = [];
592
592
  const priorOwned = ownedPathSet(existingManifest);
593
+ const source = path.join(packageRoot, 'skills', skillId);
594
+ if (!(await exists(path.join(source, 'SKILL.md')))) throw new Error(`Packaged skill source missing: ${skillId}`);
595
+
596
+ // Refresh all still-managed native copies, not only generated instructions.
597
+ // Preflight every destination before changing any of them.
598
+ const roots = [...new Set((existingManifest.targets ?? [effectiveAi]).map(target =>
599
+ effectiveScope === 'global'
600
+ ? globalSkillRootFor(target, { homeRoot: home })
601
+ : skillRootFor(target, cwd)))];
602
+ const destinations = [];
603
+ for (const root of roots) {
604
+ const destination = path.join(root, skillId);
605
+ const relative = manifestPathFor(baseRoot, destination);
606
+ const recorded = (existingManifest.files ?? []).find(file => file.path === relative);
607
+ if (!recorded) continue; // A skill satisfied by global does not own a project copy.
608
+ await assertSafeManagedPath(baseRoot, destination);
609
+ if (!(await exists(destination))) throw new Error(`Managed skill missing; refusing partial refresh: ${relative}`);
610
+ if (await hashTree(destination) !== recorded.hash) {
611
+ throw new Error(`Modified Showdar-owned skill; refusing to overwrite user changes: ${relative}`);
612
+ }
613
+ destinations.push(destination);
614
+ }
615
+
616
+ for (const destination of destinations) {
617
+ await copyOwned({ baseRoot, source, destination, priorOwned, newFiles: refreshedFiles });
618
+ }
593
619
  for (const target of existingManifest.commandHarness ?? []) {
594
620
  const commandRoot = effectiveScope === 'global' ? globalCommandRootForTarget(target, { homeRoot: home }) : commandRootFor(target, cwd);
595
621
  if (commandRoot) await generateCommandFiles({ baseRoot, skillIds: existingManifest.skills, target, commandRoot, priorOwned, newFiles: refreshedFiles });
@@ -600,18 +626,14 @@ export async function addSkill({ cwd, skill, ai = null, scope = null, home = hom
600
626
  if (instruction.kind === 'block') await writeManagedBlock(file, existingManifest.skills);
601
627
  else if (instruction.kind === 'file') await writeCursorRule(file, existingManifest.skills);
602
628
  }
603
- if (refreshedFiles.length) {
604
- const files = new Map(existingManifest.files.map(f => [f.path, f]));
605
- for (const f of refreshedFiles) files.set(f.path, f);
606
- await writeJsonAtomic(effectiveScope === 'global' ? globalManifestPath(home) : path.join(cwd, PROJECT_MANIFEST), { ...existingManifest, files: [...files.values()] });
607
- }
629
+ const files = new Map((existingManifest.files ?? []).map(f => [f.path, f]));
630
+ for (const f of refreshedFiles) files.set(f.path, f);
631
+ await writeJsonAtomic(effectiveScope === 'global' ? globalManifestPath(home) : path.join(cwd, PROJECT_MANIFEST),
632
+ { ...existingManifest, packageVersion, files: [...files.values()] });
608
633
  return { skill: skillId, root: '', destination: '', added: false, scope: effectiveScope, ai: effectiveAi, profile: existingManifest.profile };
609
634
  }
610
635
 
611
- const root = effectiveScope === 'global'
612
- ? globalSkillRootFor(effectiveAi, { homeRoot: home })
613
- : skillRootFor(effectiveAi, cwd);
614
- const destination = path.join(root, skillId);
636
+ const targets = resolveTargets(effectiveAi);
615
637
  const managedRoots = effectiveScope === 'global'
616
638
  ? [...new Set([
617
639
  ...NATIVE_TARGETS.map((t) => globalSkillRootFor(t, { homeRoot: home })),
@@ -619,30 +641,38 @@ export async function addSkill({ cwd, skill, ai = null, scope = null, home = hom
619
641
  ])]
620
642
  : [];
621
643
  const manifestPath = effectiveScope === 'global' ? globalManifestPath(home) : path.join(cwd, PROJECT_MANIFEST);
622
-
623
644
  const source = path.join(packageRoot, 'skills', skillId);
624
645
  if (!(await exists(path.join(source, 'SKILL.md')))) throw new Error(`Packaged skill source missing: ${skillId}`);
625
646
 
626
647
  await mkdir(baseRoot, { recursive: true });
627
648
  const priorOwned = ownedPathSet(existingManifest);
628
- const relative = manifestPathFor(baseRoot, destination);
629
- const existed = await exists(destination);
630
- const alreadyTracked = priorOwned.has(relative);
631
-
632
649
  const files = [];
633
- await copyOwned({ baseRoot, source, destination, priorOwned, newFiles: files, managedRoots });
650
+ const destinations = [...new Set(targets.map(target => effectiveScope === 'global'
651
+ ? globalSkillRootFor(target, { homeRoot: home })
652
+ : skillRootFor(target, cwd)))].map(root => path.join(root, skillId));
653
+ const alreadyInstalled = destinations.every(destination =>
654
+ priorOwned.has(manifestPathFor(baseRoot, destination)));
655
+
656
+ // Inspect every target before copying anything. Never overwrite foreign skills.
657
+ for (const destination of destinations) {
658
+ await assertSafeManagedPath(baseRoot, destination, managedRoots);
659
+ const relative = manifestPathFor(baseRoot, destination);
660
+ if ((await exists(destination)) && !priorOwned.has(relative)) {
661
+ throw new Error(`Refusing to overwrite existing non-Showdar-managed skill or command: ${destination}`);
662
+ }
663
+ }
664
+ for (const destination of destinations) {
665
+ await copyOwned({ baseRoot, source, destination, priorOwned, newFiles: files, managedRoots });
666
+ }
634
667
 
635
- const targets = [effectiveAi];
636
668
  const commandHarnesses = [];
637
669
  for (const target of targets) {
638
670
  const adapter = ADAPTERS[target];
639
671
  if (adapter?.commands?.destination) {
640
- if (effectiveScope === 'project') {
641
- commandHarnesses.push({ target, root: commandRootFor(target, cwd) });
642
- } else {
643
- const globalRoot = globalCommandRootForTarget(target, { homeRoot: home });
644
- if (globalRoot) commandHarnesses.push({ target, root: globalRoot });
645
- }
672
+ const root = effectiveScope === 'project'
673
+ ? commandRootFor(target, cwd)
674
+ : globalCommandRootForTarget(target, { homeRoot: home });
675
+ if (root) commandHarnesses.push({ target, root });
646
676
  }
647
677
  }
648
678
 
@@ -659,10 +689,11 @@ export async function addSkill({ cwd, skill, ai = null, scope = null, home = hom
659
689
  const merged = new Map((existingManifest?.files ?? []).map((e) => [e.path, e]));
660
690
  for (const f of files) merged.set(f.path, f);
661
691
 
662
- const instructionFile = existingManifest?.instructions ?? (effectiveScope === 'project' ? instructionSurfaceFor(effectiveAi, cwd) : null);
692
+ const instructionFile = existingManifest?.instructions ?? (effectiveScope === 'project' ? instructionSurfaceFor(effectiveAi === 'all' ? 'universal' : effectiveAi, cwd) : null);
663
693
  const commandHarness = [...new Set([...(existingManifest?.commandHarness ?? []), ...commandHarnesses.map((c) => c.target)])];
664
694
 
665
695
  const manifest = {
696
+ ...(existingManifest ?? {}),
666
697
  version: existingManifest?.version ?? 2,
667
698
  scope: effectiveScope,
668
699
  packageVersion,
@@ -688,7 +719,55 @@ export async function addSkill({ cwd, skill, ai = null, scope = null, home = hom
688
719
  }
689
720
  }
690
721
 
691
- return { skill: skillId, root, destination, added: !alreadyTracked || !existed, scope: effectiveScope, ai: effectiveAi, profile: manifest.profile };
722
+ return { skill: skillId, root: path.dirname(destinations[0]), destination: destinations[0], added: !alreadyInstalled, scope: effectiveScope, ai: effectiveAi, profile: manifest.profile };
723
+ }
724
+
725
+
726
+ /**
727
+ * Additive profile/workflow member install. Existing project profile and any
728
+ * extra extension metadata are preserved. Requested members are deduplicated.
729
+ * Each member uses the standard single-skill installer and remains rerunnable.
730
+ */
731
+ export async function addSkills({ cwd, skills, ai = null, scope = null, home = homedir(), packageRoot, packageVersion = '0.2.0' }) {
732
+ if (!Array.isArray(skills) || !skills.length) throw new Error('At least one skill is required.');
733
+ const skillIds = [...new Set(skills.map(normalizeSkillName))];
734
+ const baseRoot = scope === 'global' ? home : cwd;
735
+ const manifestPath = scope === 'global' ? globalManifestPath(home) : path.join(cwd, PROJECT_MANIFEST);
736
+ const existing = await readManifest(manifestPath, baseRoot);
737
+ const effectiveAi = ai ?? existing?.ai ?? 'universal';
738
+ const targets = resolveTargets(effectiveAi);
739
+ const owned = ownedPathSet(existing);
740
+ const managedRoots = scope === 'global'
741
+ ? [...new Set(NATIVE_TARGETS.map(target => globalSkillRootFor(target, { homeRoot: home })))]
742
+ : [];
743
+ // Preflight packaged inputs and all requested target paths before writing.
744
+ for (const skillId of skillIds) {
745
+ const source = path.join(packageRoot, 'skills', skillId, 'SKILL.md');
746
+ if (!(await exists(source))) throw new Error(`Packaged skill source missing: ${skillId}`);
747
+ for (const target of targets) {
748
+ const skillRoot = scope === 'global'
749
+ ? globalSkillRootFor(target, { homeRoot: home })
750
+ : skillRootFor(target, cwd);
751
+ const destination = path.join(skillRoot, skillId);
752
+ await assertSafeManagedPath(baseRoot, destination, managedRoots);
753
+ const relative = manifestPathFor(baseRoot, destination);
754
+ if ((await exists(destination)) && !owned.has(relative)) {
755
+ throw new Error(`Refusing to overwrite existing non-Showdar-managed skill or command: ${destination}`);
756
+ }
757
+ }
758
+ }
759
+ const installed = [];
760
+ for (const skill of skillIds) {
761
+ installed.push(await addSkill({ cwd, skill, ai, scope, home, packageRoot, packageVersion }));
762
+ }
763
+ return {
764
+ skills: skillIds,
765
+ added: installed.filter(entry => entry.added).length,
766
+ alreadyInstalled: installed.filter(entry => !entry.added).length,
767
+ profile: installed.at(-1)?.profile ?? existing?.profile ?? null,
768
+ ai: installed.at(-1)?.ai ?? effectiveAi,
769
+ scope: installed.at(-1)?.scope ?? scope ?? existing?.scope ?? 'project',
770
+ };
692
771
  }
693
772
 
694
773
  export async function removeProject(projectRoot) {
package/src/validate.js CHANGED
@@ -455,8 +455,8 @@ export async function validateRepository(packageRoot) {
455
455
  if (!claudeBlock.includes('showdar-skills:start') || !claudeBlock.includes('showdar-skills:end')) {
456
456
  errors.push('CLAUDE managed block is missing markers');
457
457
  }
458
- if (!cursorRule.includes('alwaysApply: false')) errors.push('Cursor rule must use Apply Intelligently metadata');
459
- if (cursorRule.includes('alwaysApply: true')) errors.push('Cursor rule must not use alwaysApply:true');
458
+ if (!cursorRule.includes('alwaysApply: true')) errors.push('Cursor rule must always apply for Git pre-write safety guidance');
459
+ if (!cursorRule.includes('showdar guard --mutation local-write --json')) errors.push('Cursor rule must include Git pre-write guard');
460
460
 
461
461
  const commandDir = path.join(packageRoot, 'commands', 'opencode', 'showdar');
462
462
  if (await exists(commandDir)) {