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 +25 -0
- package/README.md +41 -0
- package/bin/showdar.js +58 -12
- package/package.json +36 -5
- package/skills/showdar-bugfix/SKILL.md +5 -0
- package/skills/showdar-build/SKILL.md +5 -0
- package/skills/showdar-debug/SKILL.md +5 -0
- package/skills/showdar-design/SKILL.md +5 -0
- package/skills/showdar-feature/SKILL.md +5 -0
- package/skills/showdar-git/SKILL.md +6 -0
- package/skills/showdar-incident/SKILL.md +5 -0
- package/skills/showdar-ops/SKILL.md +5 -0
- package/skills/showdar-plan/SKILL.md +5 -0
- package/skills/showdar-recover/SKILL.md +5 -0
- package/skills/showdar-release/SKILL.md +5 -0
- package/skills/showdar-requirements/SKILL.md +5 -0
- package/skills/showdar-test/SKILL.md +5 -0
- package/skills/showdar-upgrade/SKILL.md +5 -0
- package/src/adapter-renderers.js +2 -2
- package/src/git-guard.js +62 -0
- package/src/project.js +104 -25
- package/src/validate.js +2 -2
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 <
|
|
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 <
|
|
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((
|
|
229
|
-
const
|
|
230
|
-
if (!
|
|
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
|
|
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.
|
|
3
|
+
"version": "0.13.0",
|
|
4
4
|
"description": "Production-grade software engineering lifecycle skills for coding agents.",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"bin": {
|
|
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": {
|
|
20
|
-
|
|
21
|
-
|
|
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.
|
package/src/adapter-renderers.js
CHANGED
|
@@ -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
|
-
|
|
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:
|
|
79
|
+
alwaysApply: true
|
|
80
80
|
---
|
|
81
81
|
|
|
82
82
|
${body}
|
package/src/git-guard.js
ADDED
|
@@ -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
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
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: !
|
|
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:
|
|
459
|
-
if (cursorRule.includes('
|
|
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)) {
|