@pshch/gary-the-gardener 1.2.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- 1.2.0
1
+ 1.3.0
@@ -1,5 +1,5 @@
1
1
  # Garden System Configuration
2
- # Version: 1.2.0
2
+ # Version: 1.3.0
3
3
 
4
4
  project_name: ai-bootstrap
5
5
  user_name: User
@@ -22,4 +22,4 @@ references_directory: "{project-root}/docs/references"
22
22
  agents_max_lines: 150
23
23
 
24
24
  # Garden System Version
25
- version: "1.2.0"
25
+ version: "1.3.0"
package/bin/cli.js CHANGED
@@ -18,15 +18,10 @@ const dim = (s) => `\x1b[2m${s}\x1b[0m`;
18
18
  const { values, positionals } = parseArgs({
19
19
  args: process.argv.slice(2),
20
20
  options: {
21
- claude: { type: 'boolean', default: false },
22
- copilot: { type: 'boolean', default: false },
23
- codex: { type: 'boolean', default: false },
24
- cursor: { type: 'boolean', default: false },
25
- junie: { type: 'boolean', default: false },
26
- all: { type: 'boolean', default: false },
27
- force: { type: 'boolean', short: 'f', default: false },
28
- help: { type: 'boolean', short: 'h', default: false },
29
- version: { type: 'boolean', short: 'v', default: false },
21
+ force: { type: 'boolean', short: 'f', default: false },
22
+ 'dry-run': { type: 'boolean', short: 'n', default: false },
23
+ help: { type: 'boolean', short: 'h', default: false },
24
+ version: { type: 'boolean', short: 'v', default: false },
30
25
  },
31
26
  allowPositionals: true,
32
27
  strict: false,
@@ -41,45 +36,34 @@ if (values.version) {
41
36
  }
42
37
 
43
38
  // ── Help ────────────────────────────────────────────────────────────
44
- if (values.help || !command) {
45
- console.log(`
46
- ${bold('gary-the-gardener')} v${VERSION} — Garden System installer
47
-
48
- ${bold('USAGE')}
49
- npx @pshch/gary-the-gardener ${green('install')} [options]
50
-
51
- ${bold('COMMANDS')}
52
- ${green('install')} Install Garden System into the current project
53
-
54
- ${bold('TOOL OPTIONS')} ${dim('(pick which AI tools to set up)')}
55
- --claude Claude Code — skills, commands, CLAUDE.md wrapper
56
- --copilot GitHub Copilot — .github/copilot-instructions.md wrapper
57
- --cursor Cursor — .cursor/rules/agents.mdc wrapper
58
- --codex OpenAI Codex — codex.md wrapper
59
- --junie JetBrains Junie — .junie/guidelines.md wrapper
60
- --all All of the above
61
-
62
- ${dim('If no tool flags given, defaults to --claude only.')}
39
+ if (values.help) {
40
+ printHelp();
41
+ process.exit(0);
42
+ }
63
43
 
64
- ${bold('OTHER OPTIONS')}
65
- -f, --force Overwrite existing files
66
- -v, --version Show version
67
- -h, --help Show this help
44
+ // ── Route commands ──────────────────────────────────────────────────
45
+ const dryRun = values['dry-run'];
68
46
 
69
- ${bold('EXAMPLES')}
70
- npx @pshch/gary-the-gardener install
71
- npx @pshch/gary-the-gardener install --all
72
- npx @pshch/gary-the-gardener install --claude --cursor
73
- npx @pshch/gary-the-gardener install --copilot --force
74
- `);
75
- process.exit(0);
47
+ if (command === 'status') {
48
+ runStatus();
49
+ } else if (command === 'install' || !command) {
50
+ runInstall(values.force, dryRun);
51
+ } else {
52
+ console.error(red(`Unknown command: ${command}`));
53
+ console.error('Run "npx @pshch/gary-the-gardener --help" for usage.');
54
+ process.exit(1);
76
55
  }
77
56
 
78
- // ── Install command ─────────────────────────────────────────────────
79
- if (command === 'install') {
57
+ // ═══════════════════════════════════════════════════════════════════
58
+ // ── Core functions ────────────────────────────────────────────────
59
+ // ═══════════════════════════════════════════════════════════════════
60
+
61
+ function runInstall(force, dryRun) {
80
62
  const dest = process.cwd();
81
63
 
82
- console.log(`\n🪴 ${bold('Gary the Gardener')} v${VERSION}\n`);
64
+ console.log(`\n🪴 ${bold('Gary the Gardener')} v${VERSION}`);
65
+ if (dryRun) console.log(yellow(` (dry run — no files will be written)`));
66
+ console.log('');
83
67
 
84
68
  // Sanity check: don't install into the package itself
85
69
  if (existsSync(join(dest, 'bin', 'cli.js')) && existsSync(join(dest, '_gs-gardener', 'core'))) {
@@ -90,117 +74,76 @@ if (command === 'install') {
90
74
  }
91
75
  }
92
76
 
93
- // Determine which tools to install
94
- const tools = new Set();
95
- if (values.all) {
96
- tools.add('claude').add('copilot').add('cursor').add('codex').add('junie');
97
- } else {
98
- if (values.claude) tools.add('claude');
99
- if (values.copilot) tools.add('copilot');
100
- if (values.cursor) tools.add('cursor');
101
- if (values.codex) tools.add('codex');
102
- if (values.junie) tools.add('junie');
103
- }
104
-
105
- // Default to claude if nothing specified
106
- if (tools.size === 0) tools.add('claude');
107
-
108
- // ── 1. Copy core Garden System ──────────────────────────────────
77
+ // ── Detect existing installation ──────────────────────────────────
109
78
  const coreSrc = join(PKG_ROOT, '_gs-gardener');
110
79
  const coreDest = join(dest, '_gs-gardener');
80
+ const configPath = join(coreDest, 'core', 'config.yaml');
111
81
 
112
- if (existsSync(coreDest) && !values.force) {
113
- console.log(yellow(' _gs-gardener/ already exists skipping (use --force to overwrite)'));
114
- } else {
115
- console.log(` ${green('✓')} Installing core system → ${dim('_gs-gardener/')}`);
116
- cpSync(coreSrc, coreDest, { recursive: true, force: values.force });
82
+ const installedVersion = readInstalledVersion(coreDest);
83
+ const isUpgrade = installedVersion && installedVersion !== VERSION;
84
+ const isCurrent = installedVersion === VERSION;
117
85
 
118
- // Reset config.yaml to defaults for the target project
119
- const projectName = basename(dest);
120
- const configPath = join(coreDest, 'core', 'config.yaml');
121
- const defaultConfig = `# Garden System Configuration
122
- # Version: ${VERSION}
123
-
124
- project_name: ${projectName}
125
- user_name: User
126
- communication_language: English
127
- output_folder: "{project-root}/docs"
128
-
129
- # Coverage tracking
130
- agents_file: "{project-root}/AGENTS.md"
131
- wrapper_files:
132
- - "{project-root}/CLAUDE.md"
133
- - "{project-root}/.github/copilot-instructions.md"
134
- - "{project-root}/.cursor/rules/agents.mdc"
135
- - "{project-root}/.junie/guidelines.md"
136
-
137
- # Content layers
138
- docs_directory: "{project-root}/docs"
139
- references_directory: "{project-root}/docs/references"
140
-
141
- # Constraints
142
- agents_max_lines: 150
143
-
144
- # Garden System Version
145
- version: "${VERSION}"
146
- `;
147
- writeFileSync(configPath, defaultConfig);
86
+ if (isUpgrade) {
87
+ console.log(` Upgrading ${dim(`v${installedVersion}`)} ${green(`v${VERSION}`)}\n`);
88
+ } else if (isCurrent && !force) {
89
+ console.log(` Already up to date ${dim(`(v${VERSION})`)}\n`);
148
90
  }
149
91
 
150
- // ── 2. Install Claude Code commands ─────────────────────────────
151
- if (tools.has('claude')) {
152
- const cmdSrc = join(PKG_ROOT, '.claude', 'commands');
153
- const cmdDest = join(dest, '.claude', 'commands');
154
- mkdirSync(cmdDest, { recursive: true });
155
-
156
- const gardenCmds = readdirSync(cmdSrc).filter(f => f.startsWith('garden-') && f.endsWith('.md'));
157
- let copied = 0;
158
- for (const file of gardenCmds) {
159
- const destFile = join(cmdDest, file);
160
- if (existsSync(destFile) && !values.force) continue;
161
- cpSync(join(cmdSrc, file), destFile, { force: values.force });
162
- copied++;
92
+ // ── 1. Core Garden System ─────────────────────────────────────────
93
+ if (isCurrent && !force) {
94
+ console.log(` ${green('✓')} Core system → ${dim('_gs-gardener/ (unchanged)')}`);
95
+ } else {
96
+ // Save user config before overwriting
97
+ const savedConfig = isUpgrade ? safeReadFile(configPath) : null;
98
+
99
+ if (!dryRun) {
100
+ cpSync(coreSrc, coreDest, { recursive: true, force: true });
101
+
102
+ if (savedConfig) {
103
+ // Restore user config, only bump the version line
104
+ const updated = savedConfig
105
+ .replace(/^# Version: .+$/m, `# Version: ${VERSION}`)
106
+ .replace(/^version: .+$/m, `version: "${VERSION}"`);
107
+ writeFileSync(configPath, updated);
108
+ console.log(` ${green('✓')} Core system → ${dim('_gs-gardener/ (upgraded, config preserved)')}`);
109
+ } else {
110
+ // Fresh install — write default config
111
+ const projectName = basename(dest);
112
+ writeFileSync(configPath, defaultConfig(projectName));
113
+ console.log(` ${green('✓')} Core system → ${dim('_gs-gardener/')}`);
114
+ }
115
+ } else {
116
+ const label = isUpgrade ? '(would upgrade, config preserved)' : '';
117
+ console.log(` ${green('✓')} Core system → ${dim(`_gs-gardener/ ${label}`)}`);
163
118
  }
164
- console.log(` ${green('✓')} Claude Code — ${copied} skill commands → ${dim('.claude/commands/')}`);
165
-
166
- // Create CLAUDE.md wrapper
167
- installWrapper(dest, 'CLAUDE.md',
168
- `# CLAUDE.md\n\nFollow all instructions in the root AGENTS.md file as the primary context for this repository.\n`,
169
- values.force);
170
119
  }
171
120
 
172
- // ── 3. Install Copilot wrapper ──────────────────────────────────
173
- if (tools.has('copilot')) {
174
- mkdirSync(join(dest, '.github'), { recursive: true });
175
- installWrapper(dest, '.github/copilot-instructions.md',
176
- `# Copilot Instructions\n\nFollow all instructions in the root AGENTS.md file as the primary context for this repository.\n`,
177
- values.force);
121
+ // ── 2. Claude Code skill commands ─────────────────────────────────
122
+ const cmdSrc = join(PKG_ROOT, '.claude', 'commands');
123
+ const cmdDest = join(dest, '.claude', 'commands');
124
+ if (!dryRun) mkdirSync(cmdDest, { recursive: true });
125
+
126
+ const gardenCmds = readdirSync(cmdSrc).filter(f => f.startsWith('garden-') && f.endsWith('.md'));
127
+ const shouldUpdateCmds = isUpgrade || force || !installedVersion;
128
+ let copied = 0;
129
+ for (const file of gardenCmds) {
130
+ const destFile = join(cmdDest, file);
131
+ if (existsSync(destFile) && !shouldUpdateCmds) continue;
132
+ if (!dryRun) cpSync(join(cmdSrc, file), destFile, { force: true });
133
+ copied++;
178
134
  }
179
-
180
- // ── 4. Install Cursor wrapper ───────────────────────────────────
181
- if (tools.has('cursor')) {
182
- mkdirSync(join(dest, '.cursor', 'rules'), { recursive: true });
183
- installWrapper(dest, '.cursor/rules/agents.mdc',
184
- `---\ndescription: Primary repository context sourced from AGENTS.md\nglobs:\nalwaysApply: true\n---\n\nFollow all instructions in the root AGENTS.md file as the primary context for this repository.\n`,
185
- values.force);
135
+ if (isCurrent && !force) {
136
+ console.log(` ${green('✓')} ${gardenCmds.length} skill commands ${dim('.claude/commands/ (unchanged)')}`);
137
+ } else {
138
+ console.log(` ${green('')} ${copied} skill commands → ${dim('.claude/commands/')}`);
186
139
  }
187
140
 
188
- // ── 5. Install Codex wrapper ────────────────────────────────────
189
- if (tools.has('codex')) {
190
- installWrapper(dest, 'codex.md',
191
- `# Codex Instructions\n\nFollow all instructions in the root AGENTS.md file as the primary context for this repository.\n`,
192
- values.force);
193
- }
141
+ // ── 3. CLAUDE.md ──────────────────────────────────────────────────
142
+ installWrapper(dest, 'CLAUDE.md',
143
+ `# CLAUDE.md\n\nFollow all instructions in the root AGENTS.md file as the primary context for this repository.\n`,
144
+ force, dryRun);
194
145
 
195
- // ── 6. Install Junie wrapper ────────────────────────────────────
196
- if (tools.has('junie')) {
197
- mkdirSync(join(dest, '.junie'), { recursive: true });
198
- installWrapper(dest, '.junie/guidelines.md',
199
- `# Junie Guidelines\n\nFollow all instructions in the root AGENTS.md file as the primary context for this repository.\n`,
200
- values.force);
201
- }
202
-
203
- // ── 7. Create .aiignore if missing ──────────────────────────────
146
+ // ── 4. .aiignore ──────────────────────────────────────────────────
204
147
  installWrapper(dest, '.aiignore',
205
148
  `# AI Agent Ignore File
206
149
  # Prevents AI tools from reading sensitive or irrelevant files
@@ -230,44 +173,148 @@ __pycache__/
230
173
  .vscode/launch.json
231
174
  .DS_Store
232
175
  `,
233
- values.force);
234
-
235
- // ── Summary ─────────────────────────────────────────────────────
236
- console.log(`\n🌱 ${bold('Installation complete!')}\n`);
237
- console.log(`${bold('Installed for:')} ${[...tools].join(', ')}`);
238
-
239
- if (!existsSync(join(dest, 'AGENTS.md'))) {
240
- console.log(`\n${bold('Next steps:')}`);
241
- console.log(` 1. Run ${green('claude /garden-bootstrap')} to generate AGENTS.md`);
242
- console.log(` 2. Run ${green('claude /garden-audit')} to verify accuracy`);
243
- console.log(` 3. Run ${green('claude /garden-extend')} to add guardrails & principles`);
176
+ force, dryRun);
177
+
178
+ // ── Summary ───────────────────────────────────────────────────────
179
+ if (dryRun) {
180
+ console.log(`\n${yellow(bold('Dry run complete'))} nothing was written.\n`);
181
+ console.log(`Run without ${dim('--dry-run')} to install.\n`);
182
+ } else if (isUpgrade) {
183
+ console.log(`\n🌱 ${bold(`Upgrade complete!`)} ${dim(`(v${installedVersion} → v${VERSION})`)}\n`);
244
184
  } else {
245
- console.log(`\n${bold('Next steps:')}`);
246
- console.log(` 1. Run ${green('claude /garden-agent-gardener')} for interactive maintenance`);
247
- console.log(` 2. Run ${green('claude /garden-audit')} to check for drift`);
185
+ console.log(`\n🌱 ${bold('Installation complete!')}\n`);
248
186
  }
249
187
 
188
+ console.log(`${bold('Installed:')}`);
189
+ console.log(` Core system, ${gardenCmds.length} skill commands, CLAUDE.md, .aiignore\n`);
190
+
191
+ console.log(`${bold('Next steps:')}`);
192
+ console.log(` 1. Run ${green('claude /garden-bootstrap')} to set up AI-ready documentation`);
193
+ console.log(` ${dim('Creates AGENTS.md + wrappers for your AI tools (Copilot, Cursor, etc.)')}`);
194
+ console.log(` 2. Run ${green('claude /garden-audit')} to verify accuracy`);
195
+ console.log(` 3. Run ${green('claude /garden-extend')} to add guardrails & principles`);
196
+
197
+ console.log(`\n${dim('Gary currently runs on Claude Code. Wrappers for other tools are')}`);
198
+ console.log(`${dim('created by the agent during bootstrap — run /garden-bootstrap to start.')}`);
199
+
250
200
  console.log(`\n🪴 Happy gardening!\n`);
201
+ }
251
202
 
252
- } else {
253
- console.error(red(`Unknown command: ${command}`));
254
- console.error('Run "npx @pshch/gary-the-gardener --help" for usage.');
255
- process.exit(1);
203
+ function runStatus() {
204
+ const dest = process.cwd();
205
+
206
+ console.log(`\n🪴 ${bold('Garden System — Status')}\n`);
207
+
208
+ // Core system
209
+ const coreInstalled = existsSync(join(dest, '_gs-gardener', 'core'));
210
+ let coreVersion = '';
211
+ if (coreInstalled) {
212
+ try {
213
+ coreVersion = readFileSync(join(dest, '_gs-gardener', 'VERSION'), 'utf8').trim();
214
+ } catch { /* ignore */ }
215
+ }
216
+ statusLine('Core system', coreInstalled, coreInstalled ? `v${coreVersion}` : null);
217
+
218
+ // Claude Code
219
+ const claudeMd = existsSync(join(dest, 'CLAUDE.md'));
220
+ const cmdDir = join(dest, '.claude', 'commands');
221
+ let cmdCount = 0;
222
+ if (existsSync(cmdDir)) {
223
+ cmdCount = readdirSync(cmdDir).filter(f => f.startsWith('garden-') && f.endsWith('.md')).length;
224
+ }
225
+ statusLine('Claude Code', claudeMd || cmdCount > 0,
226
+ (claudeMd || cmdCount > 0) ? `CLAUDE.md + ${cmdCount} commands` : null);
227
+
228
+ // AGENTS.md
229
+ const agentsExists = existsSync(join(dest, 'AGENTS.md'));
230
+ console.log(` ${agentsExists ? green('✓') : '○'} ${('AGENTS.md').padEnd(15)} ${agentsExists ? green('present') : yellow('not yet created (run /garden-bootstrap)')}`);
231
+
232
+ // .aiignore
233
+ statusLine('.aiignore', existsSync(join(dest, '.aiignore')), 'present');
234
+
235
+ // Wrappers (produced by the agent, not the CLI)
236
+ console.log(`\n ${dim('Wrappers (created by /garden-bootstrap):')}`);
237
+ wrapperLine('Copilot', join(dest, '.github', 'copilot-instructions.md'));
238
+ wrapperLine('Cursor', join(dest, '.cursor', 'rules', 'agents.mdc'));
239
+ wrapperLine('Codex', join(dest, 'codex.md'));
240
+ wrapperLine('Junie', join(dest, '.junie', 'guidelines.md'));
241
+
242
+ console.log('');
243
+ }
244
+
245
+ // ═══════════════════════════════════════════════════════════════════
246
+ // ── Helpers ──────────────────────────────────────────────────────
247
+ // ═══════════════════════════════════════════════════════════════════
248
+
249
+ function readInstalledVersion(coreDest) {
250
+ try {
251
+ return readFileSync(join(coreDest, 'VERSION'), 'utf8').trim();
252
+ } catch {
253
+ return null;
254
+ }
255
+ }
256
+
257
+ function safeReadFile(filePath) {
258
+ try {
259
+ return readFileSync(filePath, 'utf8');
260
+ } catch {
261
+ return null;
262
+ }
256
263
  }
257
264
 
258
- // ── Helpers ─────────────────────────────────────────────────────────
265
+ function defaultConfig(projectName) {
266
+ return `# Garden System Configuration
267
+ # Version: ${VERSION}
268
+
269
+ project_name: ${projectName}
270
+ user_name: User
271
+ communication_language: English
272
+ output_folder: "{project-root}/docs"
273
+
274
+ # Coverage tracking
275
+ agents_file: "{project-root}/AGENTS.md"
276
+ wrapper_files:
277
+ - "{project-root}/CLAUDE.md"
278
+ - "{project-root}/.github/copilot-instructions.md"
279
+ - "{project-root}/.cursor/rules/agents.mdc"
280
+ - "{project-root}/.junie/guidelines.md"
281
+
282
+ # Content layers
283
+ docs_directory: "{project-root}/docs"
284
+ references_directory: "{project-root}/docs/references"
259
285
 
260
- function installWrapper(dest, relPath, content, force) {
286
+ # Constraints
287
+ agents_max_lines: 150
288
+
289
+ # Garden System Version
290
+ version: "${VERSION}"
291
+ `;
292
+ }
293
+
294
+ function installWrapper(dest, relPath, content, force, dryRun) {
261
295
  const fullPath = join(dest, relPath);
262
296
  if (existsSync(fullPath) && !force) {
263
297
  console.log(` ${yellow('⚠')} ${relPath} already exists — skipping`);
264
298
  return false;
265
299
  }
266
- writeFileSync(fullPath, content);
300
+ if (!dryRun) writeFileSync(fullPath, content);
267
301
  console.log(` ${green('✓')} ${relPath}`);
268
302
  return true;
269
303
  }
270
304
 
305
+ function statusLine(label, installed, detail) {
306
+ const icon = installed ? green('✓') : '✗';
307
+ const text = installed ? green(detail || 'installed') : dim('not installed');
308
+ console.log(` ${icon} ${label.padEnd(15)} ${text}`);
309
+ }
310
+
311
+ function wrapperLine(label, filePath) {
312
+ const exists = existsSync(filePath);
313
+ const icon = exists ? green('✓') : dim('·');
314
+ const text = exists ? green('present') : dim('—');
315
+ console.log(` ${icon} ${label.padEnd(13)} ${text}`);
316
+ }
317
+
271
318
  function safeReadJson(path) {
272
319
  try {
273
320
  return JSON.parse(readFileSync(path, 'utf8'));
@@ -275,3 +322,38 @@ function safeReadJson(path) {
275
322
  return null;
276
323
  }
277
324
  }
325
+
326
+ function printHelp() {
327
+ console.log(`
328
+ ${bold('gary-the-gardener')} v${VERSION} — Garden System installer
329
+
330
+ ${bold('USAGE')}
331
+ npx @pshch/gary-the-gardener [command] [options]
332
+
333
+ ${bold('COMMANDS')}
334
+ ${green('install')} Install Gary the Gardener ${dim('(default if no command given)')}
335
+ ${green('status')} Show what's currently installed
336
+
337
+ ${bold('WHAT GETS INSTALLED')}
338
+ The installer sets up Gary the Gardener agent for Claude Code:
339
+ • Core system ${dim('(_gs-gardener/)')}
340
+ • Skill commands ${dim('(.claude/commands/garden-*.md)')}
341
+ • CLAUDE.md ${dim('(points Claude to AGENTS.md)')}
342
+ • .aiignore ${dim('(keeps secrets out of AI context)')}
343
+
344
+ After installing, run ${green('claude /garden-bootstrap')} to generate
345
+ AGENTS.md and wrappers for your other AI tools (Copilot, Cursor, etc.).
346
+
347
+ ${bold('OPTIONS')}
348
+ -n, --dry-run Show what would be installed, without writing files
349
+ -f, --force Overwrite existing files
350
+ -v, --version Show version
351
+ -h, --help Show this help
352
+
353
+ ${bold('EXAMPLES')}
354
+ npx @pshch/gary-the-gardener ${dim('# install the agent')}
355
+ npx @pshch/gary-the-gardener install ${dim('# same as above')}
356
+ npx @pshch/gary-the-gardener status ${dim('# check install state')}
357
+ npx @pshch/gary-the-gardener -f ${dim('# reinstall / upgrade')}
358
+ `);
359
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@pshch/gary-the-gardener",
3
- "version": "1.2.0",
4
- "description": "Garden System installer — AI agent configuration maintenance for any repository",
3
+ "version": "1.3.0",
4
+ "description": "Gary the Gardener — AI agent that maintains documentation and config for Claude, Copilot, Cursor, Codex, and Junie",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "gary-the-gardener": "./bin/cli.js",