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 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
- | `wizard` | Pick AI target, skill profile, optional skills and workflows; preview before installing. |
25
- | `setup` | Detect Git host, stack, scripts, and docs; create missing shared context in `docs/agents/`. |
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/setup` for agent-guided onboarding. It can help with both skill selection (wizard) and project context (setup); it does not silently install or write files. **Cursor / Codex:** ask the agent to configure Showdar for this repository or use the terminal commands above. Cursor does not currently generate a native slash command.
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 wizard` is the interactive installer. `showdar setup` does **not** install skills: it creates shared project context. Re-running `setup` keeps existing user-owned context files.
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 setup do not grant permission to commit, merge, push, or deploy.
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 [--dry-run|--yes] [--tracker github|gitlab|local] [--docs-dir docs/agents] [--json]');
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 [--dry-run|--yes] [--tracker github|gitlab|local] [--json]\n showdar wizard [--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(', ')}`);
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
- if (command === '--version' || command === '-V') {
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, 'wizard');
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: 'wizard', data: plan }, null, 2)
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
  [![npm version](https://img.shields.io/npm/v/showdar-skills?logo=npm)](https://www.npmjs.com/package/showdar-skills)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "showdar-skills",
3
- "version": "0.14.2",
3
+ "version": "0.15.0",
4
4
  "description": "Production-grade software engineering lifecycle skills for coding agents.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -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 setup has not run, proceed using normal repository evidence and suggest showdar setup only when useful.`;
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: Guide Showdar installation and repository onboarding with existing CLI commands',
102
+ 'description: Analyze this repository and maintain grounded Showdar project context',
103
103
  '---',
104
104
  '',
105
- 'You are the Showdar onboarding guide. Use existing CLI capabilities, not a new setup engine or intent router.',
106
- 'First inspect the repository and existing Showdar installation (showdar status, and relevant project conventions).',
107
- 'If the required skills are absent or the user wants to change the selection, explain wizard vs init/add. Recommend showdar wizard for interactive selection. Do not silently install, replace, or remove skills. Ask for approval before any installer write.',
108
- 'If skills are already installed and sufficient, skip wizard. Do not reinstall merely because this command was invoked.',
109
- 'For project context, run showdar setup --dry-run --json and show the detected Git host, stack, scripts, existing documents, and exact proposed file actions.',
110
- 'Ask only about ambiguous or missing decisions. Do not invent architectural, domain, tracker, or verification conventions.',
111
- 'Before any local write (including installer/setup configuration changes), inspect Git state and project branch policy; use showdar git-start when on an integration branch and allowed, then require showdar guard --mutation local-write --json with ok=true and data.allowed=true.',
112
- 'Only after explicit approval apply the selected installation step (if needed) and run showdar setup --yes with confirmed --tracker and --docs-dir. Avoid unnecessary interactive commands in non-TTY environments.',
113
- 'Do not overwrite user-owned files. The setup CLI only creates missing context documents. Never edit source code, create remote issues, commit, merge, or push as part of onboarding.',
114
- 'Run showdar doctor, report the installed skills, context documents, verification results, unresolved warnings, and a short example of how to request a task.',
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);
@@ -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
- }