showdar-skills 0.14.2 → 0.15.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 +9 -0
- package/README.md +5 -7
- package/bin/showdar.js +6 -40
- package/docs/REFERENCE.md +2 -0
- package/package.json +1 -1
- package/src/adapter-renderers.js +13 -12
- package/src/project.js +37 -3
- package/src/setup-prompt.js +0 -70
- package/src/setup.js +0 -136
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,15 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.15.0]
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
|
|
13
|
+
- Replaced the CLI `showdar wizard` entry point with `showdar setup`, preserving the interactive skills installer flow and `add --interactive` alias.
|
|
14
|
+
- Replaced the generated project-context template CLI with `/showdar-setup`, an evidence-based AI onboarding command for Cursor, OpenCode and Claude Code.
|
|
15
|
+
- Agent-driven setup now audits product/domain context alongside repository conventions, proposes diffs and requires approval and Git preflight before modifying docs.
|
|
16
|
+
- Retained existing intent routing and installation safeguards; no separate setup engine or router.
|
|
17
|
+
|
|
9
18
|
## [0.14.2]
|
|
10
19
|
|
|
11
20
|
### Changed
|
package/README.md
CHANGED
|
@@ -14,18 +14,17 @@ Requires **Node.js 20+**. From your project root:
|
|
|
14
14
|
|
|
15
15
|
```bash
|
|
16
16
|
npm install -g showdar-skills
|
|
17
|
-
showdar wizard
|
|
18
17
|
showdar setup
|
|
19
18
|
showdar doctor
|
|
20
19
|
```
|
|
21
20
|
|
|
22
21
|
| Step | What it does |
|
|
23
22
|
| --- | --- |
|
|
24
|
-
| `
|
|
25
|
-
|
|
|
23
|
+
| `setup` | Pick AI target, skill profile, optional skills and workflows; preview before installing. |
|
|
24
|
+
| Agent setup | Analyze the repository, propose grounded shared context, and update docs after approval. |
|
|
26
25
|
| `doctor` | Check the installation and flag problems. |
|
|
27
26
|
|
|
28
|
-
**OpenCode / Claude Code:** after installation, use `/showdar
|
|
27
|
+
**OpenCode / Claude Code:** after installation, use `/showdar-setup` to audit the codebase and prepare project context. The agent previews a diff and requests approval before changes. **Cursor:** use `/showdar-setup` as a project slash command. **Codex:** ask the agent to audit this repository and set up Showdar project context.
|
|
29
28
|
|
|
30
29
|
Already configured? Just give the coding agent a normal task, such as *"Fix the checkout validation bug and add regression tests."* Showdar guidance routes requests to the relevant installed skills; you do not need to call each skill manually.
|
|
31
30
|
|
|
@@ -35,12 +34,11 @@ Already configured? Just give the coding agent a normal task, such as *"Fix the
|
|
|
35
34
|
showdar status
|
|
36
35
|
showdar add profile insurance # Add, preserving existing skills
|
|
37
36
|
showdar add workflow feature # Include the workflow and missing stages
|
|
38
|
-
showdar setup --dry-run --json # Preview project context
|
|
39
37
|
showdar route --prompt "Review this PR" --json
|
|
40
38
|
showdar doctor
|
|
41
39
|
```
|
|
42
40
|
|
|
43
|
-
**Install vs. configure:** `showdar init` installs/replaces a skill selection, `showdar add` extends it, and `showdar
|
|
41
|
+
**Install vs. configure:** `showdar init` installs/replaces a skill selection, `showdar add` extends it, and `showdar setup` is the interactive installer. Project context is created or enriched by the AI agent using repository evidence and explicit approval; the CLI `showdar setup` runs the interactive installer and does not generate project context.
|
|
44
42
|
|
|
45
43
|
## How it works
|
|
46
44
|
|
|
@@ -54,7 +52,7 @@ User task
|
|
|
54
52
|
|
|
55
53
|
Showdar ships **18 primitive** and **4 workflow** skills, including optional insurance-domain coverage. Only relevant instructions and references are loaded as needed.
|
|
56
54
|
|
|
57
|
-
Before local source/config/docs changes, follow the project's Git policy and run `showdar guard --mutation local-write --json`; work on a task branch when required. Routing and
|
|
55
|
+
Before local source/config/docs changes, follow the project's Git policy and run `showdar guard --mutation local-write --json`; work on a task branch when required. Routing and onboarding do not grant permission to commit, merge, push, or deploy.
|
|
58
56
|
|
|
59
57
|
## Documentation
|
|
60
58
|
|
package/bin/showdar.js
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { parseArgs } from 'node:util';
|
|
4
|
-
import { collectProjectSetupAnswers } from '../src/setup-prompt.js';
|
|
5
|
-
import { planProjectSetup, applyProjectSetup } from '../src/setup.js';
|
|
6
4
|
import { buildWizardPlan, applyWizardPlan, collectWizardAnswers } from '../src/wizard.js';
|
|
7
5
|
import { startTaskBranch, formatGitStart } from '../src/git-start.js';
|
|
8
6
|
import { guardMutation, formatMutationGuard } from '../src/git-guard.js';
|
|
@@ -53,11 +51,7 @@ function printHelp(version, command = null) {
|
|
|
53
51
|
return;
|
|
54
52
|
}
|
|
55
53
|
if (command === 'setup') {
|
|
56
|
-
console.log('Usage: showdar setup [--
|
|
57
|
-
return;
|
|
58
|
-
}
|
|
59
|
-
if (command === 'wizard') {
|
|
60
|
-
console.log('Usage: showdar wizard [--mode add|replace] [--profile name] [--skills comma,list] [--workflow comma,list] [--ai target] [--scope project|global] [--yes|--dry-run] [--json]');
|
|
54
|
+
console.log('Usage: showdar setup [--mode add|replace] [--profile name] [--skills comma,list] [--workflow comma,list] [--ai target] [--scope project|global] [--yes|--dry-run] [--json]');
|
|
61
55
|
return;
|
|
62
56
|
}
|
|
63
57
|
if (command === 'init') {
|
|
@@ -72,7 +66,7 @@ function printHelp(version, command = null) {
|
|
|
72
66
|
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.`);
|
|
73
67
|
return;
|
|
74
68
|
}
|
|
75
|
-
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 setup [--
|
|
69
|
+
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 setup [--mode add|replace] [--profile <name>] [--skills <names>] [--workflow <names>] [--ai <target>] [--scope <project|global>] [--yes|--dry-run]\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(', ')}`);
|
|
76
70
|
}
|
|
77
71
|
|
|
78
72
|
async function main() {
|
|
@@ -82,7 +76,7 @@ async function main() {
|
|
|
82
76
|
const version = await packageVersion();
|
|
83
77
|
|
|
84
78
|
if (command === 'help' || command === '--help' || command === '-h') return printHelp(version);
|
|
85
|
-
|
|
79
|
+
if (command === '--version' || command === '-V') {
|
|
86
80
|
console.log(version);
|
|
87
81
|
return;
|
|
88
82
|
}
|
|
@@ -90,35 +84,7 @@ async function main() {
|
|
|
90
84
|
|
|
91
85
|
const scope = ['init', 'status', 'doctor', 'remove', 'add'].includes(command) ? scopeAfter(args) : null;
|
|
92
86
|
|
|
93
|
-
if (command === 'setup') {
|
|
94
|
-
const { values } = parseArgs({ args: args.slice(1), options: {
|
|
95
|
-
'dry-run': { type: 'boolean' }, yes: { type: 'boolean' },
|
|
96
|
-
tracker: { type: 'string' }, 'docs-dir': { type: 'string' },
|
|
97
|
-
json: { type: 'boolean' }, help: { type: 'boolean', short: 'h' },
|
|
98
|
-
} });
|
|
99
|
-
if (values.help) return printHelp(version, command);
|
|
100
|
-
let tracker = values.tracker ?? null;
|
|
101
|
-
let docsDir = values['docs-dir'] ?? 'docs/agents';
|
|
102
|
-
if (!values.yes && !values['dry-run']) {
|
|
103
|
-
const answers = await collectProjectSetupAnswers({ cwd: projectRoot, tracker, docsDir });
|
|
104
|
-
if (answers.cancelled) return;
|
|
105
|
-
tracker = answers.tracker;
|
|
106
|
-
docsDir = answers.docsDir;
|
|
107
|
-
}
|
|
108
|
-
const plan = await planProjectSetup({ cwd: projectRoot, tracker, docsDir });
|
|
109
|
-
if (values['dry-run']) {
|
|
110
|
-
console.log(values.json ? JSON.stringify({ ok: true, command, data: plan }, null, 2)
|
|
111
|
-
: 'Setup preview:\n' + plan.files.map(f => ' ' + f.action + ' ' + f.path).join('\n'));
|
|
112
|
-
return;
|
|
113
|
-
}
|
|
114
|
-
const result = await applyProjectSetup({ cwd: projectRoot, plan });
|
|
115
|
-
console.log(values.json ? JSON.stringify({ ok: true, command, data: result }, null, 2)
|
|
116
|
-
: 'Setup completed. Created: ' + result.created.length + '; preserved: ' + result.preserved.length
|
|
117
|
-
+ '. Shared context: ' + result.docsDir);
|
|
118
|
-
return;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
if (command === 'wizard' || (command === 'add' && args.includes('--interactive'))) {
|
|
87
|
+
if (command === 'setup' || (command === 'add' && args.includes('--interactive'))) {
|
|
122
88
|
const wizardArgs = command === 'add' ? args.filter(a => a !== '--interactive').slice(1) : args.slice(1);
|
|
123
89
|
const { values } = parseArgs({ args: wizardArgs, options: {
|
|
124
90
|
mode: { type: 'string' }, profile: { type: 'string' },
|
|
@@ -127,7 +93,7 @@ async function main() {
|
|
|
127
93
|
yes: { type: 'boolean' }, 'dry-run': { type: 'boolean' },
|
|
128
94
|
json: { type: 'boolean' }, help: { type: 'boolean', short: 'h' },
|
|
129
95
|
} });
|
|
130
|
-
if (values.help) return printHelp(version, '
|
|
96
|
+
if (values.help) return printHelp(version, 'setup');
|
|
131
97
|
let plan;
|
|
132
98
|
if (values.yes || values['dry-run']) {
|
|
133
99
|
plan = buildWizardPlan({
|
|
@@ -144,7 +110,7 @@ async function main() {
|
|
|
144
110
|
plan = collected.plan;
|
|
145
111
|
}
|
|
146
112
|
if (values['dry-run']) {
|
|
147
|
-
console.log(values.json ? JSON.stringify({ ok: true, command: '
|
|
113
|
+
console.log(values.json ? JSON.stringify({ ok: true, command: 'setup', data: plan }, null, 2)
|
|
148
114
|
: 'Wizard preview (' + plan.action + '): ' + plan.skills.join(', '));
|
|
149
115
|
return;
|
|
150
116
|
}
|
package/docs/REFERENCE.md
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
> **Historical reference:** This document contains older CLI descriptions. Current commands: `showdar setup` is the interactive skills installer (formerly `showdar wizard`); `/showdar-setup` is the agent-driven project-context audit on OpenCode/Claude. See [README](../README.md) for the current quick start.
|
|
2
|
+
|
|
1
3
|
# Showdar Skills
|
|
2
4
|
|
|
3
5
|
[](https://www.npmjs.com/package/showdar-skills)
|
package/package.json
CHANGED
package/src/adapter-renderers.js
CHANGED
|
@@ -4,7 +4,7 @@ const RUNTIME_GUIDANCE = `Automatic Showdar selection: route the current request
|
|
|
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
|
-
Shared project context: before project work, read the relevant existing files under docs/agents/ (project.md, issue-tracker.md, verification.md, domain.md) when present. These are project-specific conventions, not mutation authority. Use the canonical glossary and ADRs when present; do not invent them. If project
|
|
7
|
+
Shared project context: before project work, read the relevant existing files under docs/agents/ (project.md, issue-tracker.md, verification.md, domain.md) when present. These are project-specific conventions, not mutation authority. Use the canonical glossary and ADRs when present; do not invent them. If project context is missing or stale, read repository evidence and suggest agent-led /showdar-setup when useful; do not run a CLI setup generator.`;
|
|
8
8
|
|
|
9
9
|
const CANONICAL_ROUTE_ORDER = [
|
|
10
10
|
['map repository architecture, dependencies, or impact', 'showdar-understand'],
|
|
@@ -99,19 +99,20 @@ export function renderManagedBlock(skillIds, kind) {
|
|
|
99
99
|
export function renderShowdarSetupCommand() {
|
|
100
100
|
return [
|
|
101
101
|
'---',
|
|
102
|
-
'description:
|
|
102
|
+
'description: Analyze this repository and maintain grounded Showdar project context',
|
|
103
103
|
'---',
|
|
104
104
|
'',
|
|
105
|
-
'
|
|
106
|
-
'
|
|
107
|
-
'
|
|
108
|
-
'
|
|
109
|
-
'For
|
|
110
|
-
'
|
|
111
|
-
'
|
|
112
|
-
'
|
|
113
|
-
'
|
|
114
|
-
'
|
|
105
|
+
'Act as a repository onboarding agent. The showdar setup CLI is an interactive skill installer, not a project-context generator; do not run it to generate context.',
|
|
106
|
+
'Read repository instructions and existing context before proposing changes: AGENTS.md, CLAUDE.md, relevant project docs, package manifests, scripts, and source layout.',
|
|
107
|
+
'Check showdar status and installed skills when useful. Recommend showdar setup (installer) only if skills are missing or the user explicitly wants to change selection; never install automatically.',
|
|
108
|
+
'Audit product purpose, user roles, key business flows, domain terminology, and relevant acceptance rules alongside architecture, module boundaries, dependencies, state/routing/API patterns, coding conventions, test/build commands, and Git branch policy. Follow relevant source files instead of inventing facts.',
|
|
109
|
+
'For each proposed convention, record concrete evidence (source path and relevant symbol/config). Distinguish observed facts from unresolved questions.',
|
|
110
|
+
'Reuse existing canonical docs. Create or enrich only useful documents under docs/agents/ (project.md, issue-tracker.md, verification.md, domain.md). Do not duplicate AGENTS.md, glossary, or ADR text.',
|
|
111
|
+
'Perform a context gap analysis and show a concise proposed file-by-file diff before changes. Prefer small focused edits over generated boilerplate. Preserve user-owned material and never overwrite it blindly.',
|
|
112
|
+
'Before any file write, inspect Git state and instructions. On integration/default branches prepare a safe task branch following repository policy, then require showdar guard --mutation local-write --json with ok=true and data.allowed=true. No automatic stash, reset, stage, commit, merge, or push.',
|
|
113
|
+
'Request explicit approval for proposed content edits; if declined, report the draft and leave files unchanged.',
|
|
114
|
+
'After approval, create/update the approved context documents, validate file links and command references, run showdar doctor, and summarize evidence, remaining gaps, and changes.',
|
|
115
|
+
'This command is onboarding, not feature implementation or a second routing engine. Do not modify application source code, call remote issue APIs, or publish as part of setup.',
|
|
115
116
|
'',
|
|
116
117
|
'Request: $ARGUMENTS',
|
|
117
118
|
'',
|
package/src/project.js
CHANGED
|
@@ -164,17 +164,33 @@ async function generateCommandFiles({ baseRoot, skillIds, target, commandRoot, p
|
|
|
164
164
|
await writeTextAtomic(aggregatorDest, aggregatorContent);
|
|
165
165
|
newFiles.push({ path: aggregatorRel, hash: await hashTree(aggregatorDest) });
|
|
166
166
|
files.push({ destination: aggregatorDest, skillId: 'aggregator', shortName: 'skill' });
|
|
167
|
-
const setupDest = path.join(commandRoot, 'setup.md');
|
|
167
|
+
const setupDest = path.join(path.dirname(commandRoot), 'showdar-setup.md');
|
|
168
168
|
const setupRel = manifestPathFor(baseRoot, setupDest);
|
|
169
169
|
if ((await exists(setupDest)) && !priorOwned.has(setupRel)) {
|
|
170
170
|
throw new Error('Refusing to overwrite existing non-Showdar-managed command: ' + setupDest);
|
|
171
171
|
}
|
|
172
172
|
await writeTextAtomic(setupDest, renderShowdarSetupCommand());
|
|
173
173
|
newFiles.push({ path: setupRel, hash: await hashTree(setupDest) });
|
|
174
|
-
files.push({ destination: setupDest, skillId: 'setup', shortName: 'setup' });
|
|
174
|
+
files.push({ destination: setupDest, skillId: 'setup', shortName: 'showdar-setup' });
|
|
175
175
|
return files;
|
|
176
176
|
}
|
|
177
177
|
|
|
178
|
+
async function generateCursorSetupCommand({ baseRoot, scope, homeRoot, priorOwned, newFiles }) {
|
|
179
|
+
const root = scope === 'global'
|
|
180
|
+
? path.join(homeRoot, '.cursor', 'commands')
|
|
181
|
+
: path.join(baseRoot, '.cursor', 'commands');
|
|
182
|
+
const destination = path.join(root, 'showdar-setup.md');
|
|
183
|
+
await assertSafeManagedPath(baseRoot, destination);
|
|
184
|
+
const relative = manifestPathFor(baseRoot, destination);
|
|
185
|
+
if ((await exists(destination)) && !priorOwned.has(relative)) {
|
|
186
|
+
throw new Error('Refusing to overwrite existing non-Showdar-managed command: ' + destination);
|
|
187
|
+
}
|
|
188
|
+
await mkdir(root, { recursive: true });
|
|
189
|
+
await writeTextAtomic(destination, renderShowdarSetupCommand());
|
|
190
|
+
newFiles.push({ path: relative, hash: await hashTree(destination) });
|
|
191
|
+
return { destination, skillId: 'setup', shortName: 'showdar-setup', target: 'cursor' };
|
|
192
|
+
}
|
|
193
|
+
|
|
178
194
|
async function initInstallation({
|
|
179
195
|
baseRoot, manifestPath, packageRoot, profile, ai, skillIds, packageVersion = '0.2.0',
|
|
180
196
|
scope, homeRoot = homedir(),
|
|
@@ -226,6 +242,7 @@ async function initInstallation({
|
|
|
226
242
|
? [...new Set([
|
|
227
243
|
...NATIVE_TARGETS.map((t) => globalSkillRootFor(t, { homeRoot })),
|
|
228
244
|
...NATIVE_TARGETS.filter((t) => globalCommandRootForTarget(t, { homeRoot })).map((t) => globalCommandRootForTarget(t, { homeRoot })),
|
|
245
|
+
...NATIVE_TARGETS.filter((t) => globalCommandRootForTarget(t, { homeRoot })).map((t) => path.dirname(globalCommandRootForTarget(t, { homeRoot }))),
|
|
229
246
|
])]
|
|
230
247
|
: [];
|
|
231
248
|
const commandHarnesses = [];
|
|
@@ -239,11 +256,17 @@ async function initInstallation({
|
|
|
239
256
|
desiredPaths.add(manifestPathFor(baseRoot, path.join(root, `${shortName}.md`)));
|
|
240
257
|
}
|
|
241
258
|
desiredPaths.add(manifestPathFor(baseRoot, path.join(root, 'skill.md')));
|
|
259
|
+
desiredPaths.add(manifestPathFor(baseRoot, path.join(path.dirname(root), 'showdar-setup.md')));
|
|
242
260
|
commandHarnesses.push({ target, root });
|
|
243
261
|
}
|
|
244
262
|
}
|
|
245
263
|
}
|
|
246
264
|
|
|
265
|
+
if (targets.includes('cursor')) {
|
|
266
|
+
const cursorRoot = scope === 'global' ? homeRoot : baseRoot;
|
|
267
|
+
desiredPaths.add(manifestPathFor(baseRoot, path.join(cursorRoot, '.cursor', 'commands', 'showdar-setup.md')));
|
|
268
|
+
}
|
|
269
|
+
|
|
247
270
|
const staleTargets = [];
|
|
248
271
|
for (const entry of prior?.files ?? []) {
|
|
249
272
|
const targetPath = safeOwnedPath(baseRoot, entry.path, managedRoots);
|
|
@@ -262,6 +285,9 @@ async function initInstallation({
|
|
|
262
285
|
}
|
|
263
286
|
|
|
264
287
|
const commandsGenerated = [];
|
|
288
|
+
if (targets.includes('cursor')) {
|
|
289
|
+
commandsGenerated.push(await generateCursorSetupCommand({ baseRoot, scope, homeRoot, priorOwned, newFiles: files }));
|
|
290
|
+
}
|
|
265
291
|
for (const { target, root } of commandHarnesses) {
|
|
266
292
|
const generated = await generateCommandFiles({
|
|
267
293
|
baseRoot,
|
|
@@ -475,7 +501,7 @@ async function inspectInstallation({
|
|
|
475
501
|
if (!commandRoot) continue;
|
|
476
502
|
for (const cmd of manifest.commands ?? []) {
|
|
477
503
|
if (cmd.target !== harness) continue;
|
|
478
|
-
const dest = path.join(commandRoot, `${cmd.name}.md`);
|
|
504
|
+
const dest = cmd.name === 'showdar-setup' ? path.join(path.dirname(commandRoot), 'showdar-setup.md') : path.join(commandRoot, `${cmd.name}.md`);
|
|
479
505
|
const rel = manifestPathFor(baseRoot, dest);
|
|
480
506
|
const owned = projectOwned.has(rel);
|
|
481
507
|
if (!(await exists(dest))) {
|
|
@@ -527,6 +553,7 @@ async function removeInstallation({ baseRoot, manifestPath, scope, homeRoot = ho
|
|
|
527
553
|
? [...new Set([
|
|
528
554
|
...NATIVE_TARGETS.map((t) => globalSkillRootFor(t, { homeRoot })),
|
|
529
555
|
...NATIVE_TARGETS.filter((t) => globalCommandRootForTarget(t, { homeRoot })).map((t) => globalCommandRootForTarget(t, { homeRoot })),
|
|
556
|
+
...NATIVE_TARGETS.filter((t) => globalCommandRootForTarget(t, { homeRoot })).map((t) => path.dirname(globalCommandRootForTarget(t, { homeRoot }))),
|
|
530
557
|
])]
|
|
531
558
|
: [];
|
|
532
559
|
|
|
@@ -624,6 +651,9 @@ export async function addSkill({ cwd, skill, ai = null, scope = null, home = hom
|
|
|
624
651
|
for (const destination of destinations) {
|
|
625
652
|
await copyOwned({ baseRoot, source, destination, priorOwned, newFiles: refreshedFiles });
|
|
626
653
|
}
|
|
654
|
+
if ((existingManifest.targets ?? []).includes('cursor')) {
|
|
655
|
+
await generateCursorSetupCommand({ baseRoot, scope: effectiveScope, homeRoot: home, priorOwned, newFiles: refreshedFiles });
|
|
656
|
+
}
|
|
627
657
|
for (const target of existingManifest.commandHarness ?? []) {
|
|
628
658
|
const commandRoot = effectiveScope === 'global' ? globalCommandRootForTarget(target, { homeRoot: home }) : commandRootFor(target, cwd);
|
|
629
659
|
if (commandRoot) await generateCommandFiles({ baseRoot, skillIds: existingManifest.skills, target, commandRoot, priorOwned, newFiles: refreshedFiles });
|
|
@@ -686,6 +716,10 @@ export async function addSkill({ cwd, skill, ai = null, scope = null, home = hom
|
|
|
686
716
|
|
|
687
717
|
const allSkillIds = [...new Set([...(existingManifest?.skills ?? []), skillId])];
|
|
688
718
|
const newCommands = [];
|
|
719
|
+
if (targets.includes('cursor')) {
|
|
720
|
+
const generated = await generateCursorSetupCommand({ baseRoot, scope: effectiveScope, homeRoot: home, priorOwned, newFiles: files });
|
|
721
|
+
newCommands.push({ target: 'cursor', name: 'showdar-setup', path: manifestPathFor(baseRoot, generated.destination) });
|
|
722
|
+
}
|
|
689
723
|
const harnessTargets = [...new Set([...(existingManifest?.commandHarness ?? []), ...commandHarnesses.map(c => c.target)])];
|
|
690
724
|
for (const target of harnessTargets) {
|
|
691
725
|
const root = effectiveScope === 'global' ? globalCommandRootForTarget(target, { homeRoot: home }) : commandRootFor(target, cwd);
|
package/src/setup-prompt.js
DELETED
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
import { stdin, stdout } from 'node:process';
|
|
2
|
-
import { planProjectSetup, validateDocsDir } from './setup.js';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Clack-driven setup configuration. Passing a prompt API makes selections
|
|
6
|
-
* testable without requiring a pseudo-terminal; no file writes happen here.
|
|
7
|
-
*/
|
|
8
|
-
export async function collectProjectSetupAnswers({
|
|
9
|
-
cwd, tracker = null, docsDir = 'docs/agents',
|
|
10
|
-
} = {}, promptApi = null) {
|
|
11
|
-
if ((!stdin.isTTY || !stdout.isTTY) && !promptApi) {
|
|
12
|
-
throw new Error('Interactive setup requires a TTY. Use --dry-run or --yes for automation.');
|
|
13
|
-
}
|
|
14
|
-
const p = promptApi ?? await import('@clack/prompts');
|
|
15
|
-
p.intro('Showdar Skills · Project setup');
|
|
16
|
-
const initial = await planProjectSetup({ cwd, tracker, docsDir });
|
|
17
|
-
|
|
18
|
-
p.note([
|
|
19
|
-
'Repository: ' + initial.inspection.packageName,
|
|
20
|
-
'Detected host: ' + (initial.inspection.remote ?? 'no remote'),
|
|
21
|
-
'Detected stack: ' + (initial.inspection.technologies.join(', ') || 'not detected'),
|
|
22
|
-
'Layout: ' + initial.inspection.workspace,
|
|
23
|
-
'Verification scripts: ' + (Object.keys(initial.inspection.scripts).join(', ') || 'none'),
|
|
24
|
-
'Existing AGENTS.md: ' + (initial.inspection.docs.agents ? 'yes' : 'no'),
|
|
25
|
-
].join('\n'), 'Repository inspection');
|
|
26
|
-
|
|
27
|
-
const stop = () => {
|
|
28
|
-
p.cancel('Setup cancelled. No files changed.');
|
|
29
|
-
return { cancelled: true };
|
|
30
|
-
};
|
|
31
|
-
const chosenTracker = await p.select({
|
|
32
|
-
message: 'Issue tracker',
|
|
33
|
-
initialValue: initial.tracker,
|
|
34
|
-
options: [
|
|
35
|
-
{ value: 'github', label: 'GitHub Issues', hint: 'Store tickets alongside GitHub repository' },
|
|
36
|
-
{ value: 'gitlab', label: 'GitLab Issues', hint: 'Use GitLab issues and merge requests' },
|
|
37
|
-
{ value: 'local', label: 'Local / custom tracker', hint: 'Document team convention later' },
|
|
38
|
-
],
|
|
39
|
-
});
|
|
40
|
-
if (p.isCancel(chosenTracker)) return stop();
|
|
41
|
-
|
|
42
|
-
const chosenDir = await p.text({
|
|
43
|
-
message: 'Shared project-context directory',
|
|
44
|
-
initialValue: docsDir,
|
|
45
|
-
placeholder: docsDir,
|
|
46
|
-
validate(value) {
|
|
47
|
-
try { validateDocsDir(value || docsDir); }
|
|
48
|
-
catch (error) { return error.message; }
|
|
49
|
-
},
|
|
50
|
-
});
|
|
51
|
-
if (p.isCancel(chosenDir)) return stop();
|
|
52
|
-
const resolvedDir = chosenDir || docsDir;
|
|
53
|
-
const plan = await planProjectSetup({ cwd, tracker: chosenTracker, docsDir: resolvedDir });
|
|
54
|
-
p.note([
|
|
55
|
-
'Tracker: ' + plan.tracker,
|
|
56
|
-
'Docs directory: ' + plan.docsDir,
|
|
57
|
-
...plan.files.map((file) => (file.action === 'create' ? '+ create ' : '✓ keep ') + file.path),
|
|
58
|
-
'',
|
|
59
|
-
'User-owned documents are never overwritten.',
|
|
60
|
-
'Git preflight must pass before writing.',
|
|
61
|
-
].join('\n'), 'Preview · no files changed');
|
|
62
|
-
|
|
63
|
-
const approved = await p.confirm({
|
|
64
|
-
message: 'Apply project setup?',
|
|
65
|
-
initialValue: false,
|
|
66
|
-
});
|
|
67
|
-
if (p.isCancel(approved) || !approved) return stop();
|
|
68
|
-
p.outro('Configuration confirmed.');
|
|
69
|
-
return { cancelled: false, tracker: plan.tracker, docsDir: plan.docsDir };
|
|
70
|
-
}
|
package/src/setup.js
DELETED
|
@@ -1,136 +0,0 @@
|
|
|
1
|
-
import { access, mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
2
|
-
import { execFileSync } from 'node:child_process';
|
|
3
|
-
import path from 'node:path';
|
|
4
|
-
import { assertSafeManagedPath } from './path-safety.js';
|
|
5
|
-
import { guardMutation } from './git-guard.js';
|
|
6
|
-
|
|
7
|
-
const DOC_ROOT = 'docs/agents';
|
|
8
|
-
const DOCS = ['project.md', 'issue-tracker.md', 'verification.md', 'domain.md'];
|
|
9
|
-
const codeQuote = text => String.fromCharCode(96) + text + String.fromCharCode(96);
|
|
10
|
-
|
|
11
|
-
async function exists(file) {
|
|
12
|
-
try { await access(file); return true; }
|
|
13
|
-
catch (e) { if (e.code === 'ENOENT') return false; throw e; }
|
|
14
|
-
}
|
|
15
|
-
function git(cwd, ...args) {
|
|
16
|
-
try {
|
|
17
|
-
return execFileSync('git', ['--no-optional-locks', ...args], {
|
|
18
|
-
cwd, encoding: 'utf8', timeout: 6000, stdio: ['ignore', 'pipe', 'ignore'],
|
|
19
|
-
}).trim();
|
|
20
|
-
} catch { return ''; }
|
|
21
|
-
}
|
|
22
|
-
async function readJson(file) {
|
|
23
|
-
if (!(await exists(file))) return null;
|
|
24
|
-
try { return JSON.parse(await readFile(file, 'utf8')); }
|
|
25
|
-
catch { return null; }
|
|
26
|
-
}
|
|
27
|
-
export function validateDocsDir(value) {
|
|
28
|
-
if (typeof value !== 'string' || !value.trim()) throw new Error('Document directory must be a relative path.');
|
|
29
|
-
if (value.includes('\0') || value.includes('\\') || path.isAbsolute(value) ||
|
|
30
|
-
value.split('/').some(part => !part || part === '.' || part === '..') ||
|
|
31
|
-
value === '.git' || value.startsWith('.git/')) {
|
|
32
|
-
throw new Error('Invalid document directory: use a safe project-relative path.');
|
|
33
|
-
}
|
|
34
|
-
return value;
|
|
35
|
-
}
|
|
36
|
-
export async function inspectSetupProject(cwd) {
|
|
37
|
-
const pkg = await readJson(path.join(cwd, 'package.json'));
|
|
38
|
-
const scripts = pkg?.scripts && typeof pkg.scripts === 'object' ? pkg.scripts : {};
|
|
39
|
-
const remote = git(cwd, 'remote', 'get-url', 'origin');
|
|
40
|
-
const tracker = /github\.com[:/]/i.test(remote) ? 'github'
|
|
41
|
-
: /gitlab/i.test(remote) ? 'gitlab' : 'local';
|
|
42
|
-
const workspace = await exists(path.join(cwd, 'pnpm-workspace.yaml')) || !!pkg?.workspaces;
|
|
43
|
-
const technologies = [
|
|
44
|
-
['next', 'Next.js'], ['react', 'React'], ['react-native', 'React Native'],
|
|
45
|
-
['vue', 'Vue'], ['vite', 'Vite'], ['typescript', 'TypeScript'],
|
|
46
|
-
['vitest', 'Vitest'], ['jest', 'Jest'],
|
|
47
|
-
].filter(([key]) => pkg?.dependencies?.[key] || pkg?.devDependencies?.[key]).map(([, label]) => label);
|
|
48
|
-
const docs = {
|
|
49
|
-
glossary: await exists(path.join(cwd, 'GLOSSARY.md')),
|
|
50
|
-
glossaryMap: await exists(path.join(cwd, 'GLOSSARY-MAP.md')),
|
|
51
|
-
adr: await exists(path.join(cwd, 'docs', 'adr')),
|
|
52
|
-
agents: await exists(path.join(cwd, 'AGENTS.md')),
|
|
53
|
-
claude: await exists(path.join(cwd, 'CLAUDE.md')),
|
|
54
|
-
};
|
|
55
|
-
return {
|
|
56
|
-
root: cwd, remote: remote || null, tracker,
|
|
57
|
-
workspace: workspace ? 'monorepo' : 'single-context',
|
|
58
|
-
packageName: pkg?.name || path.basename(cwd),
|
|
59
|
-
technologies, scripts: Object.fromEntries(Object.entries(scripts).filter(
|
|
60
|
-
([name, script]) => ['test', 'typecheck', 'type-check', 'lint', 'build', 'check', 'validate'].includes(name)
|
|
61
|
-
&& typeof script === 'string')),
|
|
62
|
-
docs,
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
|
-
function docText(name, details, tracker, docsDir) {
|
|
66
|
-
const { technologies, packageName, scripts, workspace, docs } = details;
|
|
67
|
-
if (name === 'project.md') return [
|
|
68
|
-
'# Project context', '', 'Project: ' + packageName, 'Layout: ' + workspace,
|
|
69
|
-
'Detected stack: ' + (technologies.join(', ') || 'not detected'), '',
|
|
70
|
-
'Project-specific context for all installed Showdar skills. Review detected values.',
|
|
71
|
-
'Detection does not authorize changes to code or configuration.', '',
|
|
72
|
-
].join('\n');
|
|
73
|
-
if (name === 'issue-tracker.md') return [
|
|
74
|
-
'# Issue tracker', '', 'Provider: ' + tracker,
|
|
75
|
-
'Remote: ' + (details.remote || 'not configured'), '',
|
|
76
|
-
tracker === 'github' ? 'Use repository GitHub Issues and PR conventions.'
|
|
77
|
-
: tracker === 'gitlab' ? 'Use repository GitLab Issues and MR conventions.'
|
|
78
|
-
: 'Use local files or replace this section with your tracker convention.',
|
|
79
|
-
'Do not modify remote issues without explicit task authorization.', '',
|
|
80
|
-
].join('\n');
|
|
81
|
-
if (name === 'verification.md') {
|
|
82
|
-
const lines = Object.entries(scripts).map(([name, script]) =>
|
|
83
|
-
'- ' + name + ': ' + codeQuote(script.replaceAll('\n', ' ')));
|
|
84
|
-
return ['# Verification', '', 'Detected package scripts (inspect before running):',
|
|
85
|
-
...(lines.length ? lines : ['- No standard verification scripts detected']),
|
|
86
|
-
'', 'Run narrow checks first and full checks before handoff.',
|
|
87
|
-
'Never execute untrusted project scripts without reviewing their content.', ''].join('\n');
|
|
88
|
-
}
|
|
89
|
-
return ['# Domain context', '', 'Layout: ' + workspace,
|
|
90
|
-
'Glossary: ' + (docs.glossary ? 'GLOSSARY.md' : docs.glossaryMap ? 'GLOSSARY-MAP.md' : 'not yet present'),
|
|
91
|
-
'Architecture decisions: ' + (docs.adr ? 'docs/adr/' : 'not yet present'), '',
|
|
92
|
-
'Read an existing glossary and relevant ADRs before changing domain names.',
|
|
93
|
-
'Create glossary entries and ADRs lazily when actual decisions are resolved.',
|
|
94
|
-
'Shared context directory: ' + docsDir, ''].join('\n');
|
|
95
|
-
}
|
|
96
|
-
export async function planProjectSetup({ cwd, docsDir = DOC_ROOT, tracker = null } = {}) {
|
|
97
|
-
if (!cwd) throw new Error('Project directory is required.');
|
|
98
|
-
const root = path.resolve(cwd);
|
|
99
|
-
const folder = validateDocsDir(docsDir);
|
|
100
|
-
const details = await inspectSetupProject(root);
|
|
101
|
-
const selected = tracker || details.tracker;
|
|
102
|
-
if (!['github', 'gitlab', 'local'].includes(selected)) throw new Error('Tracker must be github, gitlab or local.');
|
|
103
|
-
const files = [];
|
|
104
|
-
for (const name of DOCS) {
|
|
105
|
-
const rel = folder + '/' + name;
|
|
106
|
-
const dest = path.resolve(root, rel);
|
|
107
|
-
await assertSafeManagedPath(root, dest);
|
|
108
|
-
const present = await exists(dest);
|
|
109
|
-
files.push({ path: rel, action: present ? 'preserve' : 'create',
|
|
110
|
-
content: present ? null : docText(name, details, selected, folder) });
|
|
111
|
-
}
|
|
112
|
-
return { schemaVersion: 1, tracker: selected, docsDir: folder, inspection: details, files };
|
|
113
|
-
}
|
|
114
|
-
export async function applyProjectSetup({ cwd, plan }) {
|
|
115
|
-
if (!plan || plan.schemaVersion !== 1) throw new Error('Invalid setup plan.');
|
|
116
|
-
const root = path.resolve(cwd);
|
|
117
|
-
const gate = guardMutation({ cwd: root, mutation: 'local-write' });
|
|
118
|
-
if (!gate.allowed) throw new Error('Setup blocked by Git preflight: ' + gate.code + '. Run showdar git-start first.');
|
|
119
|
-
const folder = validateDocsDir(plan.docsDir);
|
|
120
|
-
const fresh = await planProjectSetup({ cwd: root, docsDir: folder, tracker: plan.tracker });
|
|
121
|
-
const created = [], preserved = [];
|
|
122
|
-
for (const file of fresh.files) {
|
|
123
|
-
const dest = path.resolve(root, file.path);
|
|
124
|
-
await assertSafeManagedPath(root, dest);
|
|
125
|
-
if (file.action === 'preserve') { preserved.push(file.path); continue; }
|
|
126
|
-
await mkdir(path.dirname(dest), { recursive: true });
|
|
127
|
-
try {
|
|
128
|
-
await writeFile(dest, file.content, { flag: 'wx' });
|
|
129
|
-
created.push(file.path);
|
|
130
|
-
} catch (e) {
|
|
131
|
-
if (e.code === 'EEXIST') { preserved.push(file.path); continue; }
|
|
132
|
-
throw e;
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
return { created, preserved, docsDir: folder, tracker: fresh.tracker };
|
|
136
|
-
}
|