@pshch/gary-the-gardener 1.3.0 → 1.4.1
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.
|
@@ -5,6 +5,14 @@ All notable changes to the Garden System will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [1.4.1] - 2026-02-16
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
- **Renamed "Gardner Gary" to "🪴 Gary The Gardener"** - Consistent naming with emoji across all files
|
|
12
|
+
- **Fixed syntax error** in CLI help output template literal
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
8
16
|
## [1.1.0] - 2026-02-15
|
|
9
17
|
|
|
10
18
|
### Added
|
|
@@ -12,14 +20,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
12
20
|
- **Contextual "What should I do next?" suggestions** - Option 6 in help menu analyzes repo state on-demand
|
|
13
21
|
- **Bootstrap detection** - Help workflow detects missing AGENTS.md and guides users to bootstrap first
|
|
14
22
|
- **`/garden-bootstrap` command** - First-time setup workflow (converted from ai-bootstrapper.md)
|
|
15
|
-
- **[BS] Bootstrap menu item** - Conditional display in
|
|
23
|
+
- **[BS] Bootstrap menu item** - Conditional display in 🪴 Gary The Gardener when AGENTS.md is missing
|
|
16
24
|
- **Progressive disclosure** - Help menu offers option 7 to show all 9 commands
|
|
17
25
|
|
|
18
26
|
### Changed
|
|
19
27
|
- **`/garden-garden` renamed to `/garden-maintain`** - Clearer command name (no longer self-referential)
|
|
20
28
|
- **Workflow directory renamed** - `_gs-gardener/core/workflows/garden/` → `_gs-gardener/core/workflows/maintain/`
|
|
21
29
|
- **Help workflow structure** - Now defers heavy I/O checks to contextual analysis (Phase 4)
|
|
22
|
-
-
|
|
30
|
+
- **🪴 Gary The Gardener menu** - Shows 9 options when AGENTS.md missing, 8 when it exists
|
|
23
31
|
- **Bootstrap integration** - ai-bootstrapper.md converted to standard garden workflow
|
|
24
32
|
|
|
25
33
|
### Fixed
|
|
@@ -38,7 +46,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
38
46
|
|
|
39
47
|
### Added
|
|
40
48
|
- Initial release of Garden System
|
|
41
|
-
-
|
|
49
|
+
- 🪴 Gary The Gardener 🪴 - Interactive Repository Garden Keeper subagent
|
|
42
50
|
- 9 maintenance skills:
|
|
43
51
|
- `/garden-sync` - Sync wrappers with AGENTS.md
|
|
44
52
|
- `/garden-audit` - Audit for drift between docs and code
|
package/_gs-gardener/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
1.
|
|
1
|
+
1.4.1
|
|
@@ -3,7 +3,7 @@ name: "gardener"
|
|
|
3
3
|
description: "Repository Garden Keeper"
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
<agent id="gardener.agent.yaml" name="
|
|
6
|
+
<agent id="gardener.agent.yaml" name="🪴 Gary The Gardener" title="Repository Garden Keeper" icon="🪴">
|
|
7
7
|
|
|
8
8
|
<activation critical="MANDATORY">
|
|
9
9
|
<step n="1">Load persona from this current agent file</step>
|
|
@@ -14,7 +14,7 @@ description: "Repository Garden Keeper"
|
|
|
14
14
|
</step>
|
|
15
15
|
<step n="3">Read version from {project-root}/_gs-gardener/VERSION file, then display personalized welcome greeting with version:
|
|
16
16
|
```
|
|
17
|
-
🪴 Hello! I'm
|
|
17
|
+
🪴 Hello! I'm Gary The Gardener v{version}, your Repository Garden Keeper.
|
|
18
18
|
|
|
19
19
|
I'm here to help you maintain your AI configuration "garden" - keeping your
|
|
20
20
|
documentation healthy, pruned, and thriving across your repository.
|
|
@@ -83,7 +83,7 @@ description: "Repository Garden Keeper"
|
|
|
83
83
|
</activation>
|
|
84
84
|
|
|
85
85
|
<persona>
|
|
86
|
-
<name
|
|
86
|
+
<name>🪴 Gary The Gardener</name>
|
|
87
87
|
<role>Repository Garden Keeper</role>
|
|
88
88
|
<identity>
|
|
89
89
|
Gary is a meticulous gardener who tends to AI configuration "gardens" across repositories.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# Garden System Configuration
|
|
2
|
-
# Version: 1.
|
|
2
|
+
# Version: 1.4.1
|
|
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.
|
|
25
|
+
version: "1.4.1"
|
|
@@ -240,7 +240,7 @@ Do NOT create files with fabricated content. Stubs with clear "how to populate"
|
|
|
240
240
|
|
|
241
241
|
### 2.5 Garden System (Maintenance System)
|
|
242
242
|
|
|
243
|
-
Copy the garden system to enable ongoing maintenance via
|
|
243
|
+
Copy the garden system to enable ongoing maintenance via 🪴 Gary The Gardener 🪴:
|
|
244
244
|
|
|
245
245
|
**Option A: Direct Copy (Recommended)**
|
|
246
246
|
```bash
|
|
@@ -264,7 +264,7 @@ cp _ai-bootstrap/.claude/commands/garden-*.md .claude/commands/
|
|
|
264
264
|
```
|
|
265
265
|
|
|
266
266
|
This enables:
|
|
267
|
-
- `/garden-agent-gardener` - Interactive maintenance menu with
|
|
267
|
+
- `/garden-agent-gardener` - Interactive maintenance menu with 🪴 Gary The Gardener
|
|
268
268
|
- Individual skills: `/garden-sync`, `/garden-audit`, `/garden-extend`, `/garden-references`, `/garden-add-tool`, `/garden-scaffold`, `/garden-maintain`, `/garden-compact`
|
|
269
269
|
|
|
270
270
|
See GARDEN-SYSTEM.md in the ai-bootstrap repository for full deployment and usage guide.
|
package/bin/cli.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { parseArgs } from 'node:util';
|
|
4
4
|
import { cpSync, existsSync, mkdirSync, writeFileSync, readFileSync, readdirSync } from 'node:fs';
|
|
5
5
|
import { join, resolve, basename } from 'node:path';
|
|
6
|
+
import { createInterface } from 'node:readline/promises';
|
|
6
7
|
|
|
7
8
|
const PKG_ROOT = resolve(import.meta.dirname, '..');
|
|
8
9
|
const VERSION = readFileSync(join(PKG_ROOT, '_gs-gardener', 'VERSION'), 'utf8').trim();
|
|
@@ -14,6 +15,115 @@ const yellow = (s) => `\x1b[33m${s}\x1b[0m`;
|
|
|
14
15
|
const red = (s) => `\x1b[31m${s}\x1b[0m`;
|
|
15
16
|
const dim = (s) => `\x1b[2m${s}\x1b[0m`;
|
|
16
17
|
|
|
18
|
+
// ── Workflow catalog (shared across all tool generators) ─────────────
|
|
19
|
+
const WORKFLOWS = [
|
|
20
|
+
{ name: 'bootstrap', icon: '🌱', tag: 'Plant your first seeds' },
|
|
21
|
+
{ name: 'audit', icon: '🔍', tag: 'Pull the weeds — find stale & missing docs' },
|
|
22
|
+
{ name: 'compact', icon: '✂️', tag: 'Prune the overgrowth' },
|
|
23
|
+
{ name: 'sync', icon: '💧', tag: 'Water the roots — keep wrappers in sync' },
|
|
24
|
+
{ name: 'maintain', icon: '🌿', tag: 'Walk the rows with shears' },
|
|
25
|
+
{ name: 'extend', icon: '🌻', tag: 'Grow new beds — add content layers' },
|
|
26
|
+
{ name: 'references', icon: '📚', tag: 'Tend the reference shelf' },
|
|
27
|
+
{ name: 'scaffold', icon: '🏗️', tag: 'Lay out the plots — setup docs/ structure' },
|
|
28
|
+
{ name: 'add-tool', icon: '🔧', tag: 'Plant in new soil — add AI tool support' },
|
|
29
|
+
{ name: 'help', icon: '❓', tag: 'Ask the gardener' },
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
// ── Agent activation block (reused by all tool providers) ────────────
|
|
33
|
+
const AGENT_ACTIVATION = `You must fully embody this agent's persona and follow all activation instructions exactly as specified. NEVER break character until given an exit command.
|
|
34
|
+
|
|
35
|
+
<agent-activation CRITICAL="TRUE">
|
|
36
|
+
1. LOAD the FULL agent file from {project-root}/_gs-gardener/core/agents/gardener.md
|
|
37
|
+
2. READ its entire contents - this contains the complete agent persona, menu, and instructions
|
|
38
|
+
3. FOLLOW every step in the <activation> section precisely
|
|
39
|
+
4. DISPLAY the welcome greeting and coverage status
|
|
40
|
+
5. PRESENT the numbered menu
|
|
41
|
+
6. WAIT for user input before proceeding
|
|
42
|
+
</agent-activation>`;
|
|
43
|
+
|
|
44
|
+
const AGENT_DESC = '🪴 Gary The Gardener - documentation maintenance agent';
|
|
45
|
+
|
|
46
|
+
// ── Tool definitions ─────────────────────────────────────────────────
|
|
47
|
+
const TOOLS = {
|
|
48
|
+
'claude-code': {
|
|
49
|
+
label: 'Claude Code',
|
|
50
|
+
detect: [],
|
|
51
|
+
alwaysInstall: true,
|
|
52
|
+
// Commands are copied from package source (steps 2-3 in install)
|
|
53
|
+
summaryPath: '.claude/commands/',
|
|
54
|
+
},
|
|
55
|
+
cursor: {
|
|
56
|
+
label: 'Cursor',
|
|
57
|
+
detect: ['.cursor', '.cursorrules'],
|
|
58
|
+
dirs: ['.cursor', '.cursor/rules'],
|
|
59
|
+
agentFile: {
|
|
60
|
+
path: '.cursor/rules/garden-agent-gardener.mdc',
|
|
61
|
+
content: `---
|
|
62
|
+
description: "${AGENT_DESC}"
|
|
63
|
+
globs:
|
|
64
|
+
alwaysApply: true
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
# gardener
|
|
68
|
+
|
|
69
|
+
${AGENT_ACTIVATION}
|
|
70
|
+
`,
|
|
71
|
+
},
|
|
72
|
+
summaryPath: '.cursor/rules/garden-agent-gardener.mdc',
|
|
73
|
+
},
|
|
74
|
+
copilot: {
|
|
75
|
+
label: 'GitHub Copilot',
|
|
76
|
+
detect: ['.github/copilot-instructions.md', '.github'],
|
|
77
|
+
dirs: ['.github', '.github/agents'],
|
|
78
|
+
agentFile: {
|
|
79
|
+
path: '.github/agents/gardener.md',
|
|
80
|
+
content: `---
|
|
81
|
+
name: 'gardener'
|
|
82
|
+
description: '${AGENT_DESC}'
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
# gardener
|
|
86
|
+
|
|
87
|
+
${AGENT_ACTIVATION}
|
|
88
|
+
`,
|
|
89
|
+
},
|
|
90
|
+
summaryPath: '.github/agents/gardener.md',
|
|
91
|
+
},
|
|
92
|
+
windsurf: {
|
|
93
|
+
label: 'Windsurf',
|
|
94
|
+
detect: ['.windsurfrules', '.windsurf'],
|
|
95
|
+
dirs: ['.windsurf', '.windsurf/rules'],
|
|
96
|
+
agentFile: {
|
|
97
|
+
path: '.windsurf/rules/garden-agent-gardener.md',
|
|
98
|
+
content: `# gardener
|
|
99
|
+
|
|
100
|
+
> ${AGENT_DESC}
|
|
101
|
+
|
|
102
|
+
${AGENT_ACTIVATION}
|
|
103
|
+
`,
|
|
104
|
+
},
|
|
105
|
+
summaryPath: '.windsurf/rules/garden-agent-gardener.md',
|
|
106
|
+
},
|
|
107
|
+
junie: {
|
|
108
|
+
label: 'JetBrains Junie',
|
|
109
|
+
detect: ['.junie'],
|
|
110
|
+
dirs: ['.junie'],
|
|
111
|
+
agentFile: {
|
|
112
|
+
path: '.junie/guidelines.md',
|
|
113
|
+
content: `# 🪴 Gary The Gardener
|
|
114
|
+
|
|
115
|
+
> Documentation maintenance agent for this repository.
|
|
116
|
+
|
|
117
|
+
${AGENT_ACTIVATION}
|
|
118
|
+
`,
|
|
119
|
+
},
|
|
120
|
+
summaryPath: '.junie/guidelines.md',
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
const TOOL_SLUGS = Object.keys(TOOLS);
|
|
125
|
+
const OPTIONAL_SLUGS = TOOL_SLUGS.filter(s => !TOOLS[s].alwaysInstall);
|
|
126
|
+
|
|
17
127
|
// ── Parse CLI arguments ─────────────────────────────────────────────
|
|
18
128
|
const { values, positionals } = parseArgs({
|
|
19
129
|
args: process.argv.slice(2),
|
|
@@ -22,6 +132,7 @@ const { values, positionals } = parseArgs({
|
|
|
22
132
|
'dry-run': { type: 'boolean', short: 'n', default: false },
|
|
23
133
|
help: { type: 'boolean', short: 'h', default: false },
|
|
24
134
|
version: { type: 'boolean', short: 'v', default: false },
|
|
135
|
+
tools: { type: 'string', short: 't' },
|
|
25
136
|
},
|
|
26
137
|
allowPositionals: true,
|
|
27
138
|
strict: false,
|
|
@@ -31,7 +142,7 @@ const command = positionals[0];
|
|
|
31
142
|
|
|
32
143
|
// ── Version ─────────────────────────────────────────────────────────
|
|
33
144
|
if (values.version) {
|
|
34
|
-
console.log(
|
|
145
|
+
console.log(`🪴 Gary The Gardener v${VERSION}`);
|
|
35
146
|
process.exit(0);
|
|
36
147
|
}
|
|
37
148
|
|
|
@@ -47,7 +158,7 @@ const dryRun = values['dry-run'];
|
|
|
47
158
|
if (command === 'status') {
|
|
48
159
|
runStatus();
|
|
49
160
|
} else if (command === 'install' || !command) {
|
|
50
|
-
runInstall(values.force, dryRun);
|
|
161
|
+
await runInstall(values.force, dryRun);
|
|
51
162
|
} else {
|
|
52
163
|
console.error(red(`Unknown command: ${command}`));
|
|
53
164
|
console.error('Run "npx @pshch/gary-the-gardener --help" for usage.');
|
|
@@ -58,13 +169,16 @@ if (command === 'status') {
|
|
|
58
169
|
// ── Core functions ────────────────────────────────────────────────
|
|
59
170
|
// ═══════════════════════════════════════════════════════════════════
|
|
60
171
|
|
|
61
|
-
function runInstall(force, dryRun) {
|
|
172
|
+
async function runInstall(force, dryRun) {
|
|
62
173
|
const dest = process.cwd();
|
|
63
174
|
|
|
64
|
-
console.log(`\n🪴 ${bold('Gary
|
|
175
|
+
console.log(`\n🪴 ${bold('Gary The Gardener')} v${VERSION}`);
|
|
65
176
|
if (dryRun) console.log(yellow(` (dry run — no files will be written)`));
|
|
66
177
|
console.log('');
|
|
67
178
|
|
|
179
|
+
// Validate --tools flag early, before any file operations
|
|
180
|
+
const requestedTools = parseToolsFlag(values.tools);
|
|
181
|
+
|
|
68
182
|
// Sanity check: don't install into the package itself
|
|
69
183
|
if (existsSync(join(dest, 'bin', 'cli.js')) && existsSync(join(dest, '_gs-gardener', 'core'))) {
|
|
70
184
|
const pkg = safeReadJson(join(dest, 'package.json'));
|
|
@@ -82,6 +196,7 @@ function runInstall(force, dryRun) {
|
|
|
82
196
|
const installedVersion = readInstalledVersion(coreDest);
|
|
83
197
|
const isUpgrade = installedVersion && installedVersion !== VERSION;
|
|
84
198
|
const isCurrent = installedVersion === VERSION;
|
|
199
|
+
let freshInstall = false;
|
|
85
200
|
|
|
86
201
|
if (isUpgrade) {
|
|
87
202
|
console.log(` Upgrading ${dim(`v${installedVersion}`)} → ${green(`v${VERSION}`)}\n`);
|
|
@@ -93,23 +208,19 @@ function runInstall(force, dryRun) {
|
|
|
93
208
|
if (isCurrent && !force) {
|
|
94
209
|
console.log(` ${green('✓')} Core system → ${dim('_gs-gardener/ (unchanged)')}`);
|
|
95
210
|
} else {
|
|
96
|
-
// Save user config before overwriting
|
|
97
211
|
const savedConfig = isUpgrade ? safeReadFile(configPath) : null;
|
|
98
212
|
|
|
99
213
|
if (!dryRun) {
|
|
100
214
|
cpSync(coreSrc, coreDest, { recursive: true, force: true });
|
|
101
215
|
|
|
102
216
|
if (savedConfig) {
|
|
103
|
-
// Restore user config, only bump the version line
|
|
104
217
|
const updated = savedConfig
|
|
105
218
|
.replace(/^# Version: .+$/m, `# Version: ${VERSION}`)
|
|
106
219
|
.replace(/^version: .+$/m, `version: "${VERSION}"`);
|
|
107
220
|
writeFileSync(configPath, updated);
|
|
108
221
|
console.log(` ${green('✓')} Core system → ${dim('_gs-gardener/ (upgraded, config preserved)')}`);
|
|
109
222
|
} else {
|
|
110
|
-
|
|
111
|
-
const projectName = basename(dest);
|
|
112
|
-
writeFileSync(configPath, defaultConfig(projectName));
|
|
223
|
+
freshInstall = true;
|
|
113
224
|
console.log(` ${green('✓')} Core system → ${dim('_gs-gardener/')}`);
|
|
114
225
|
}
|
|
115
226
|
} else {
|
|
@@ -133,18 +244,18 @@ function runInstall(force, dryRun) {
|
|
|
133
244
|
copied++;
|
|
134
245
|
}
|
|
135
246
|
if (isCurrent && !force) {
|
|
136
|
-
console.log(` ${green('✓')} ${gardenCmds.length}
|
|
247
|
+
console.log(` ${green('✓')} ${gardenCmds.length} commands → ${dim('.claude/commands/ (unchanged)')}`);
|
|
137
248
|
} else {
|
|
138
|
-
console.log(` ${green('✓')} ${copied}
|
|
249
|
+
console.log(` ${green('✓')} ${copied} commands → ${dim('.claude/commands/')}`);
|
|
139
250
|
}
|
|
140
251
|
|
|
141
252
|
// ── 3. CLAUDE.md ──────────────────────────────────────────────────
|
|
142
|
-
|
|
253
|
+
installFile(dest, 'CLAUDE.md',
|
|
143
254
|
`# CLAUDE.md\n\nFollow all instructions in the root AGENTS.md file as the primary context for this repository.\n`,
|
|
144
255
|
force, dryRun);
|
|
145
256
|
|
|
146
257
|
// ── 4. .aiignore ──────────────────────────────────────────────────
|
|
147
|
-
|
|
258
|
+
installFile(dest, '.aiignore',
|
|
148
259
|
`# AI Agent Ignore File
|
|
149
260
|
# Prevents AI tools from reading sensitive or irrelevant files
|
|
150
261
|
|
|
@@ -175,6 +286,44 @@ __pycache__/
|
|
|
175
286
|
`,
|
|
176
287
|
force, dryRun);
|
|
177
288
|
|
|
289
|
+
// ── 5. Determine tool selections ──────────────────────────────────
|
|
290
|
+
let selectedTools;
|
|
291
|
+
|
|
292
|
+
if (requestedTools) {
|
|
293
|
+
selectedTools = requestedTools;
|
|
294
|
+
} else if (!process.stdin.isTTY) {
|
|
295
|
+
selectedTools = ['claude-code'];
|
|
296
|
+
} else {
|
|
297
|
+
const detected = detectTools(dest);
|
|
298
|
+
selectedTools = await promptToolSelection(detected);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// ── 6. Install tool agent files ───────────────────────────────────
|
|
302
|
+
const toolsToInstall = selectedTools.filter(s => s !== 'claude-code');
|
|
303
|
+
const toolResults = [];
|
|
304
|
+
|
|
305
|
+
for (const slug of toolsToInstall) {
|
|
306
|
+
const tool = TOOLS[slug];
|
|
307
|
+
|
|
308
|
+
// Create directories
|
|
309
|
+
if (tool.dirs && !dryRun) {
|
|
310
|
+
for (const d of tool.dirs) {
|
|
311
|
+
mkdirSync(join(dest, d), { recursive: true });
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// Install agent file
|
|
316
|
+
if (tool.agentFile) {
|
|
317
|
+
const written = installFile(dest, tool.agentFile.path, tool.agentFile.content, force, dryRun);
|
|
318
|
+
toolResults.push({ slug, label: tool.label, path: tool.agentFile.path, written });
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// ── 7. Write config.yaml ──────────────────────────────────────────
|
|
323
|
+
if (!dryRun && freshInstall) {
|
|
324
|
+
writeFileSync(configPath, defaultConfig(basename(dest)));
|
|
325
|
+
}
|
|
326
|
+
|
|
178
327
|
// ── Summary ───────────────────────────────────────────────────────
|
|
179
328
|
if (dryRun) {
|
|
180
329
|
console.log(`\n${yellow(bold('Dry run complete'))} — nothing was written.\n`);
|
|
@@ -185,25 +334,23 @@ __pycache__/
|
|
|
185
334
|
console.log(`\n🌱 ${bold('Installation complete!')}\n`);
|
|
186
335
|
}
|
|
187
336
|
|
|
188
|
-
|
|
189
|
-
console.log(
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
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.')}`);
|
|
337
|
+
// Tools configured
|
|
338
|
+
console.log(`${bold('Tools configured:')}`);
|
|
339
|
+
console.log(` ${green('✅')} Claude Code → ${dim(`.claude/commands/ (${gardenCmds.length} commands)`)}`);
|
|
340
|
+
for (const r of toolResults) {
|
|
341
|
+
const icon = r.written ? green('✅') : yellow('⚠️');
|
|
342
|
+
const note = r.written ? '' : dim(' (already existed)');
|
|
343
|
+
console.log(` ${icon} ${r.label.padEnd(15)} → ${dim(r.path)}${note}`);
|
|
344
|
+
}
|
|
199
345
|
|
|
200
|
-
|
|
346
|
+
// Garden metaphor
|
|
347
|
+
printGardenWelcome();
|
|
201
348
|
}
|
|
202
349
|
|
|
203
350
|
function runStatus() {
|
|
204
351
|
const dest = process.cwd();
|
|
205
352
|
|
|
206
|
-
console.log(`\n🪴 ${bold('
|
|
353
|
+
console.log(`\n🪴 ${bold('Gary The Gardener — Status')}\n`);
|
|
207
354
|
|
|
208
355
|
// Core system
|
|
209
356
|
const coreInstalled = existsSync(join(dest, '_gs-gardener', 'core'));
|
|
@@ -227,17 +374,21 @@ function runStatus() {
|
|
|
227
374
|
|
|
228
375
|
// AGENTS.md
|
|
229
376
|
const agentsExists = existsSync(join(dest, 'AGENTS.md'));
|
|
230
|
-
console.log(` ${agentsExists ? green('✓') : '○'} ${
|
|
377
|
+
console.log(` ${agentsExists ? green('✓') : '○'} ${'AGENTS.md'.padEnd(18)} ${agentsExists ? green('present') : yellow('not yet created (run /garden-bootstrap)')}`);
|
|
231
378
|
|
|
232
379
|
// .aiignore
|
|
233
380
|
statusLine('.aiignore', existsSync(join(dest, '.aiignore')), 'present');
|
|
234
381
|
|
|
235
|
-
//
|
|
236
|
-
console.log(`\n ${dim('
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
382
|
+
// Tool agents
|
|
383
|
+
console.log(`\n ${dim('Tool agents:')}`);
|
|
384
|
+
for (const [, tool] of Object.entries(TOOLS)) {
|
|
385
|
+
if (tool.alwaysInstall) continue;
|
|
386
|
+
if (!tool.agentFile) continue;
|
|
387
|
+
const exists = existsSync(join(dest, tool.agentFile.path));
|
|
388
|
+
const icon = exists ? green('✓') : dim('·');
|
|
389
|
+
const text = exists ? green(tool.agentFile.path) : dim('—');
|
|
390
|
+
console.log(` ${icon} ${tool.label.padEnd(18)} ${text}`);
|
|
391
|
+
}
|
|
241
392
|
|
|
242
393
|
console.log('');
|
|
243
394
|
}
|
|
@@ -275,9 +426,6 @@ output_folder: "{project-root}/docs"
|
|
|
275
426
|
agents_file: "{project-root}/AGENTS.md"
|
|
276
427
|
wrapper_files:
|
|
277
428
|
- "{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
429
|
|
|
282
430
|
# Content layers
|
|
283
431
|
docs_directory: "{project-root}/docs"
|
|
@@ -291,7 +439,7 @@ version: "${VERSION}"
|
|
|
291
439
|
`;
|
|
292
440
|
}
|
|
293
441
|
|
|
294
|
-
function
|
|
442
|
+
function installFile(dest, relPath, content, force, dryRun) {
|
|
295
443
|
const fullPath = join(dest, relPath);
|
|
296
444
|
if (existsSync(fullPath) && !force) {
|
|
297
445
|
console.log(` ${yellow('⚠')} ${relPath} already exists — skipping`);
|
|
@@ -305,14 +453,7 @@ function installWrapper(dest, relPath, content, force, dryRun) {
|
|
|
305
453
|
function statusLine(label, installed, detail) {
|
|
306
454
|
const icon = installed ? green('✓') : '✗';
|
|
307
455
|
const text = installed ? green(detail || 'installed') : dim('not installed');
|
|
308
|
-
console.log(` ${icon} ${label.padEnd(
|
|
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}`);
|
|
456
|
+
console.log(` ${icon} ${label.padEnd(18)} ${text}`);
|
|
316
457
|
}
|
|
317
458
|
|
|
318
459
|
function safeReadJson(path) {
|
|
@@ -323,37 +464,165 @@ function safeReadJson(path) {
|
|
|
323
464
|
}
|
|
324
465
|
}
|
|
325
466
|
|
|
467
|
+
function parseToolsFlag(flagValue) {
|
|
468
|
+
if (flagValue === undefined) return null;
|
|
469
|
+
|
|
470
|
+
const slugs = flagValue.split(',').map(s => s.trim().toLowerCase());
|
|
471
|
+
const invalid = slugs.filter(s => !TOOLS[s]);
|
|
472
|
+
if (invalid.length) {
|
|
473
|
+
console.error(red(`Unknown tool(s): ${invalid.join(', ')}`));
|
|
474
|
+
console.error(`Valid tools: ${TOOL_SLUGS.join(', ')}`);
|
|
475
|
+
process.exit(1);
|
|
476
|
+
}
|
|
477
|
+
if (!slugs.includes('claude-code')) slugs.unshift('claude-code');
|
|
478
|
+
return slugs;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
function detectTools(dest) {
|
|
482
|
+
const detected = [];
|
|
483
|
+
for (const [slug, tool] of Object.entries(TOOLS)) {
|
|
484
|
+
if (tool.alwaysInstall) {
|
|
485
|
+
detected.push(slug);
|
|
486
|
+
continue;
|
|
487
|
+
}
|
|
488
|
+
for (const probe of tool.detect) {
|
|
489
|
+
if (existsSync(join(dest, probe))) {
|
|
490
|
+
detected.push(slug);
|
|
491
|
+
break;
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
return detected;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
async function promptToolSelection(detected) {
|
|
499
|
+
const entries = OPTIONAL_SLUGS.map((slug, i) => ({
|
|
500
|
+
num: i + 1,
|
|
501
|
+
slug,
|
|
502
|
+
tool: TOOLS[slug],
|
|
503
|
+
selected: detected.includes(slug),
|
|
504
|
+
}));
|
|
505
|
+
|
|
506
|
+
function printList() {
|
|
507
|
+
for (const e of entries) {
|
|
508
|
+
const marker = e.selected ? green('[x]') : '[ ]';
|
|
509
|
+
const hint = detected.includes(e.slug) ? green(' (detected)') : '';
|
|
510
|
+
console.log(` ${e.num}. ${marker} ${e.tool.label}${hint} ${dim(e.tool.agentFile?.path || '')}`);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
console.log(`\n${bold('Install gardener agent for other AI tools?')}`);
|
|
515
|
+
console.log(dim('Each tool gets a gardener agent that loads from _gs-gardener/.\n'));
|
|
516
|
+
printList();
|
|
517
|
+
console.log(`\n${dim('Enter numbers to toggle (e.g. "1 3"), "all", "none", or press Enter to confirm:')}`);
|
|
518
|
+
|
|
519
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
520
|
+
|
|
521
|
+
try {
|
|
522
|
+
while (true) {
|
|
523
|
+
const answer = await rl.question('> ');
|
|
524
|
+
const trimmed = answer.trim().toLowerCase();
|
|
525
|
+
|
|
526
|
+
if (trimmed === '') break;
|
|
527
|
+
|
|
528
|
+
if (trimmed === 'all') {
|
|
529
|
+
for (const e of entries) e.selected = true;
|
|
530
|
+
} else if (trimmed === 'none' || trimmed === 'skip') {
|
|
531
|
+
for (const e of entries) e.selected = false;
|
|
532
|
+
} else {
|
|
533
|
+
const nums = trimmed.split(/[\s,]+/).map(Number).filter(n => n >= 1 && n <= entries.length);
|
|
534
|
+
for (const n of nums) {
|
|
535
|
+
entries[n - 1].selected = !entries[n - 1].selected;
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
printList();
|
|
540
|
+
console.log(dim('Enter numbers to toggle, or press Enter to confirm:'));
|
|
541
|
+
}
|
|
542
|
+
} finally {
|
|
543
|
+
rl.close();
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
const result = ['claude-code'];
|
|
547
|
+
for (const e of entries) {
|
|
548
|
+
if (e.selected) result.push(e.slug);
|
|
549
|
+
}
|
|
550
|
+
return result;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
function printGardenWelcome() {
|
|
554
|
+
const cmds = WORKFLOWS.map(w =>
|
|
555
|
+
` ${w.icon.padEnd(4)} /garden-${w.name.padEnd(12)} ${w.tag}`
|
|
556
|
+
).join('\n');
|
|
557
|
+
|
|
558
|
+
console.log(`
|
|
559
|
+
Every repository is a garden.
|
|
560
|
+
Code grows. Docs decay. Drift creeps in like weeds.
|
|
561
|
+
|
|
562
|
+
Gary tends to what others forget —
|
|
563
|
+
the README nobody updated,
|
|
564
|
+
the changelog nobody wrote,
|
|
565
|
+
the API docs nobody checked.
|
|
566
|
+
|
|
567
|
+
${cmds}
|
|
568
|
+
|
|
569
|
+
Your garden is planted. Run ${green('/garden-help')} to begin. 🌻
|
|
570
|
+
`);
|
|
571
|
+
}
|
|
572
|
+
|
|
326
573
|
function printHelp() {
|
|
574
|
+
const cmds = WORKFLOWS.map(w =>
|
|
575
|
+
` ${w.icon.padEnd(4)} /garden-${w.name.padEnd(12)} ${w.tag}`
|
|
576
|
+
).join('\n');
|
|
577
|
+
|
|
327
578
|
console.log(`
|
|
328
|
-
${bold('
|
|
579
|
+
🪴 ${bold('Gary The Gardener')} v${VERSION}
|
|
580
|
+
${'═'.repeat(28)}
|
|
581
|
+
|
|
582
|
+
Every repository is a garden.
|
|
583
|
+
Code grows. Docs decay. Drift creeps in like weeds.
|
|
584
|
+
|
|
585
|
+
Gary tends to what others forget —
|
|
586
|
+
the README nobody updated,
|
|
587
|
+
the changelog nobody wrote,
|
|
588
|
+
the API docs nobody checked.
|
|
589
|
+
|
|
590
|
+
${cmds}
|
|
329
591
|
|
|
330
592
|
${bold('USAGE')}
|
|
331
593
|
npx @pshch/gary-the-gardener [command] [options]
|
|
332
594
|
|
|
333
595
|
${bold('COMMANDS')}
|
|
334
|
-
${green('install')} Install Gary
|
|
596
|
+
${green('install')} Install 🪴 Gary The Gardener ${dim('(default)')}
|
|
335
597
|
${green('status')} Show what's currently installed
|
|
336
598
|
|
|
337
599
|
${bold('WHAT GETS INSTALLED')}
|
|
338
|
-
|
|
600
|
+
${dim('Always (Claude Code — the agent host):')}
|
|
339
601
|
• Core system ${dim('(_gs-gardener/)')}
|
|
340
602
|
• Skill commands ${dim('(.claude/commands/garden-*.md)')}
|
|
341
603
|
• CLAUDE.md ${dim('(points Claude to AGENTS.md)')}
|
|
342
604
|
• .aiignore ${dim('(keeps secrets out of AI context)')}
|
|
343
605
|
|
|
344
|
-
|
|
345
|
-
|
|
606
|
+
${dim('Optional (gardener agent for other tools):')}
|
|
607
|
+
• Cursor ${dim('(.cursor/rules/garden-agent-gardener.mdc)')}
|
|
608
|
+
• GitHub Copilot ${dim('(.github/agents/gardener.md)')}
|
|
609
|
+
• Windsurf ${dim('(.windsurf/rules/garden-agent-gardener.md)')}
|
|
610
|
+
• JetBrains Junie ${dim('(.junie/guidelines.md)')}
|
|
346
611
|
|
|
347
612
|
${bold('OPTIONS')}
|
|
613
|
+
-t, --tools Comma-separated tools to install agent for
|
|
614
|
+
${dim('Valid: cursor, copilot, windsurf, junie')}
|
|
615
|
+
${dim('If omitted: interactive prompt (or Claude-only if piped)')}
|
|
348
616
|
-n, --dry-run Show what would be installed, without writing files
|
|
349
617
|
-f, --force Overwrite existing files
|
|
350
618
|
-v, --version Show version
|
|
351
619
|
-h, --help Show this help
|
|
352
620
|
|
|
353
621
|
${bold('EXAMPLES')}
|
|
354
|
-
npx @pshch/gary-the-gardener
|
|
355
|
-
npx @pshch/gary-the-gardener
|
|
356
|
-
npx @pshch/gary-the-gardener
|
|
357
|
-
npx @pshch/gary-the-gardener
|
|
622
|
+
npx @pshch/gary-the-gardener ${dim('# install with interactive tool selection')}
|
|
623
|
+
npx @pshch/gary-the-gardener --tools cursor ${dim('# install + Cursor agent')}
|
|
624
|
+
npx @pshch/gary-the-gardener -t cursor,copilot ${dim('# install + Cursor + Copilot agents')}
|
|
625
|
+
npx @pshch/gary-the-gardener status ${dim('# check install state')}
|
|
626
|
+
npx @pshch/gary-the-gardener -f ${dim('# reinstall / overwrite')}
|
|
358
627
|
`);
|
|
359
628
|
}
|
package/package.json
CHANGED