create-claude-rails 0.1.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/LICENSE +21 -0
- package/README.md +129 -0
- package/bin/create-claude-rails.js +8 -0
- package/lib/cli.js +414 -0
- package/lib/copy.js +113 -0
- package/lib/db-setup.js +51 -0
- package/lib/metadata.js +41 -0
- package/lib/settings-merge.js +84 -0
- package/package.json +29 -0
- package/templates/EXTENSIONS.md +311 -0
- package/templates/README.md +485 -0
- package/templates/hooks/git-guardrails.sh +67 -0
- package/templates/hooks/skill-telemetry.sh +66 -0
- package/templates/hooks/skill-tool-telemetry.sh +54 -0
- package/templates/hooks/stop-hook.md +56 -0
- package/templates/memory/patterns/_pattern-template.md +119 -0
- package/templates/memory/patterns/pattern-intelligence-first.md +41 -0
- package/templates/rules/enforcement-pipeline.md +151 -0
- package/templates/scripts/finding-schema.json +94 -0
- package/templates/scripts/load-triage-history.js +151 -0
- package/templates/scripts/merge-findings.js +126 -0
- package/templates/scripts/pib-db-schema.sql +68 -0
- package/templates/scripts/pib-db.js +365 -0
- package/templates/scripts/triage-server.mjs +98 -0
- package/templates/scripts/triage-ui.html +536 -0
- package/templates/skills/audit/SKILL.md +269 -0
- package/templates/skills/audit/phases/finding-output.md +56 -0
- package/templates/skills/audit/phases/perspective-execution.md +63 -0
- package/templates/skills/audit/phases/perspective-selection.md +44 -0
- package/templates/skills/audit/phases/structural-checks.md +54 -0
- package/templates/skills/audit/phases/triage-history.md +45 -0
- package/templates/skills/debrief/SKILL.md +278 -0
- package/templates/skills/debrief/phases/auto-maintenance.md +48 -0
- package/templates/skills/debrief/phases/close-work.md +88 -0
- package/templates/skills/debrief/phases/health-checks.md +54 -0
- package/templates/skills/debrief/phases/inventory.md +40 -0
- package/templates/skills/debrief/phases/loose-ends.md +52 -0
- package/templates/skills/debrief/phases/record-lessons.md +67 -0
- package/templates/skills/debrief/phases/report.md +59 -0
- package/templates/skills/debrief/phases/update-state.md +48 -0
- package/templates/skills/execute/SKILL.md +293 -0
- package/templates/skills/execute/phases/commit-and-deploy.md +66 -0
- package/templates/skills/execute/phases/load-plan.md +49 -0
- package/templates/skills/execute/phases/perspectives.md +49 -0
- package/templates/skills/execute/phases/validators.md +50 -0
- package/templates/skills/execute/phases/verification-tools.md +67 -0
- package/templates/skills/investigate/SKILL.md +159 -0
- package/templates/skills/menu/SKILL.md +61 -0
- package/templates/skills/onboard/SKILL.md +301 -0
- package/templates/skills/onboard/phases/detect-state.md +70 -0
- package/templates/skills/onboard/phases/generate-context.md +81 -0
- package/templates/skills/onboard/phases/generate-session-loop.md +87 -0
- package/templates/skills/onboard/phases/interview.md +158 -0
- package/templates/skills/onboard/phases/modularity-menu.md +159 -0
- package/templates/skills/onboard/phases/summary.md +122 -0
- package/templates/skills/orient/SKILL.md +240 -0
- package/templates/skills/orient/phases/auto-maintenance.md +48 -0
- package/templates/skills/orient/phases/briefing.md +53 -0
- package/templates/skills/orient/phases/context.md +47 -0
- package/templates/skills/orient/phases/data-sync.md +35 -0
- package/templates/skills/orient/phases/health-checks.md +50 -0
- package/templates/skills/orient/phases/perspectives.md +46 -0
- package/templates/skills/orient/phases/work-scan.md +69 -0
- package/templates/skills/perspectives/_composition-patterns.md +240 -0
- package/templates/skills/perspectives/_context-template.md +152 -0
- package/templates/skills/perspectives/_eval-protocol.md +208 -0
- package/templates/skills/perspectives/_groups-template.yaml +49 -0
- package/templates/skills/perspectives/_lifecycle.md +93 -0
- package/templates/skills/perspectives/_prompt-guide.md +266 -0
- package/templates/skills/perspectives/accessibility/SKILL.md +177 -0
- package/templates/skills/perspectives/anti-confirmation/SKILL.md +170 -0
- package/templates/skills/perspectives/boundary-conditions/SKILL.md +261 -0
- package/templates/skills/perspectives/box-health/SKILL.md +338 -0
- package/templates/skills/perspectives/data-integrity/SKILL.md +152 -0
- package/templates/skills/perspectives/debugger/SKILL.md +218 -0
- package/templates/skills/perspectives/documentation/SKILL.md +166 -0
- package/templates/skills/perspectives/meta-process/SKILL.md +257 -0
- package/templates/skills/perspectives/mobile-responsiveness/SKILL.md +151 -0
- package/templates/skills/perspectives/organized-mind/SKILL.md +335 -0
- package/templates/skills/perspectives/output-contract.md +148 -0
- package/templates/skills/perspectives/performance/SKILL.md +165 -0
- package/templates/skills/perspectives/process/SKILL.md +235 -0
- package/templates/skills/perspectives/qa/SKILL.md +201 -0
- package/templates/skills/perspectives/security/SKILL.md +176 -0
- package/templates/skills/perspectives/technical-debt/SKILL.md +112 -0
- package/templates/skills/plan/SKILL.md +356 -0
- package/templates/skills/plan/phases/calibration-examples.md +75 -0
- package/templates/skills/plan/phases/completeness-check.md +44 -0
- package/templates/skills/plan/phases/composition-check.md +36 -0
- package/templates/skills/plan/phases/overlap-check.md +62 -0
- package/templates/skills/plan/phases/perspective-critique.md +47 -0
- package/templates/skills/plan/phases/plan-template.md +69 -0
- package/templates/skills/plan/phases/present.md +60 -0
- package/templates/skills/plan/phases/research.md +43 -0
- package/templates/skills/plan/phases/work-tracker.md +95 -0
- package/templates/skills/pulse/SKILL.md +242 -0
- package/templates/skills/pulse/phases/auto-fix-scope.md +40 -0
- package/templates/skills/pulse/phases/checks.md +58 -0
- package/templates/skills/pulse/phases/output.md +54 -0
- package/templates/skills/seed/SKILL.md +259 -0
- package/templates/skills/seed/phases/build-perspective.md +93 -0
- package/templates/skills/seed/phases/evaluate-existing.md +61 -0
- package/templates/skills/seed/phases/maintain.md +92 -0
- package/templates/skills/seed/phases/scan-signals.md +82 -0
- package/templates/skills/triage-audit/SKILL.md +251 -0
- package/templates/skills/triage-audit/phases/apply-verdicts.md +90 -0
- package/templates/skills/triage-audit/phases/load-findings.md +38 -0
- package/templates/skills/triage-audit/phases/triage-ui.md +66 -0
- package/templates/skills/upgrade/SKILL.md +265 -0
- package/templates/skills/upgrade/phases/apply.md +86 -0
- package/templates/skills/upgrade/phases/detect-current.md +82 -0
- package/templates/skills/upgrade/phases/diff-upstream.md +72 -0
- package/templates/skills/upgrade/phases/merge.md +97 -0
- package/templates/skills/validate/SKILL.md +116 -0
- package/templates/skills/validate/phases/validators.md +53 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Oren Magid
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# Claude on Rails
|
|
2
|
+
|
|
3
|
+
Opinionated process scaffolding for Claude Code projects.
|
|
4
|
+
|
|
5
|
+
One command gives you a session loop (orient/debrief), work tracking,
|
|
6
|
+
structured planning, an audit system with expert perspectives, and
|
|
7
|
+
enforcement hooks — all configured through conversational onboarding.
|
|
8
|
+
|
|
9
|
+
## Quick Start
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npx create-claude-rails
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
That's it. The CLI walks you through module selection, copies skill files,
|
|
16
|
+
sets up hooks, and optionally installs a local SQLite work tracker. When
|
|
17
|
+
it's done, open Claude Code and run `/onboard` — it interviews you about
|
|
18
|
+
your project and generates the context layer that makes everything work.
|
|
19
|
+
|
|
20
|
+
## What You Get
|
|
21
|
+
|
|
22
|
+
### Session Loop (always installed)
|
|
23
|
+
- **`/orient`** — reads project state, checks health, surfaces what's
|
|
24
|
+
overdue or due today. Every session starts informed.
|
|
25
|
+
- **`/debrief`** — marks work done, records lessons, updates state.
|
|
26
|
+
Every session closes the loop.
|
|
27
|
+
|
|
28
|
+
### Work Tracking (opt-in)
|
|
29
|
+
Local SQLite database for actions, projects, and status tracking. Claude
|
|
30
|
+
reads and writes it directly — no external service needed. Skip this if
|
|
31
|
+
you already use GitHub Issues, Linear, or something else.
|
|
32
|
+
|
|
33
|
+
### Planning + Execution (opt-in)
|
|
34
|
+
- **`/plan`** — structured implementation planning with perspective
|
|
35
|
+
critique before you build.
|
|
36
|
+
- **`/execute`** — step-through execution with checkpoints and guardrails.
|
|
37
|
+
|
|
38
|
+
### Audit System (opt-in)
|
|
39
|
+
15 expert perspectives (security, accessibility, data integrity,
|
|
40
|
+
performance, etc.) that analyze your codebase and produce structured
|
|
41
|
+
findings. Triage UI for reviewing results.
|
|
42
|
+
|
|
43
|
+
### Compliance Stack (opt-in)
|
|
44
|
+
Scoped instructions in `.claude/rules/` that load by file path. An
|
|
45
|
+
enforcement pipeline that promotes recurring feedback into deterministic
|
|
46
|
+
hooks.
|
|
47
|
+
|
|
48
|
+
### Lifecycle (opt-in)
|
|
49
|
+
- **`/onboard`** — conversational project interview, re-runnable as the
|
|
50
|
+
project matures.
|
|
51
|
+
- **`/seed`** — detects new tech in your project, proposes expertise.
|
|
52
|
+
- **`/upgrade`** — conversational merge when Claude on Rails updates.
|
|
53
|
+
|
|
54
|
+
## How It Works
|
|
55
|
+
|
|
56
|
+
The CLI handles mechanical setup: copying files, merging settings,
|
|
57
|
+
installing dependencies. `/onboard` handles intelligent configuration:
|
|
58
|
+
it interviews you about your project and generates context files based
|
|
59
|
+
on your answers.
|
|
60
|
+
|
|
61
|
+
**For new projects:** CLI installs everything with defaults. `/onboard`
|
|
62
|
+
asks what you're building and sets up the session loop accordingly.
|
|
63
|
+
|
|
64
|
+
**For existing projects:** CLI detects your project and offers to install
|
|
65
|
+
alongside it. `/onboard` scans your tech stack, asks about pain points,
|
|
66
|
+
and generates context that makes Claude effective from session one.
|
|
67
|
+
|
|
68
|
+
Everything is customizable through **phase files** — small markdown files
|
|
69
|
+
that override default behavior for any skill. Write content in a phase
|
|
70
|
+
file to customize it, write `skip: true` to disable it, or leave it
|
|
71
|
+
absent to use the default. No config files, no YAML, no DSL.
|
|
72
|
+
|
|
73
|
+
## CLI Options
|
|
74
|
+
|
|
75
|
+
```
|
|
76
|
+
npx create-claude-rails # Interactive walkthrough
|
|
77
|
+
npx create-claude-rails my-project # Install in ./my-project/
|
|
78
|
+
npx create-claude-rails --yes # Accept all defaults
|
|
79
|
+
npx create-claude-rails --yes --no-db # All defaults, skip database
|
|
80
|
+
npx create-claude-rails --dry-run # Preview without writing files
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## What Gets Installed
|
|
84
|
+
|
|
85
|
+
Everything goes into `.claude/` (skills, hooks, rules, memory) or
|
|
86
|
+
`scripts/` (database, triage tools). Nothing touches your source code.
|
|
87
|
+
|
|
88
|
+
```
|
|
89
|
+
.claude/
|
|
90
|
+
├── skills/ # orient, debrief, plan, execute, audit, etc.
|
|
91
|
+
├── hooks/ # git guardrails, telemetry
|
|
92
|
+
├── rules/ # enforcement pipeline
|
|
93
|
+
├── memory/ # pattern templates
|
|
94
|
+
└── settings.json # hook configuration
|
|
95
|
+
|
|
96
|
+
scripts/
|
|
97
|
+
├── pib-db.js # work tracking CLI (if installed)
|
|
98
|
+
└── ... # triage tools (if audit installed)
|
|
99
|
+
|
|
100
|
+
.pibrc.json # installation metadata
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Upgrading
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
npx create-claude-rails # re-run to add modules
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
In Claude Code, run `/upgrade` for conversational merge of upstream
|
|
110
|
+
changes with your customizations.
|
|
111
|
+
|
|
112
|
+
## Philosophy
|
|
113
|
+
|
|
114
|
+
This started as the process layer of [Flow](https://github.com/orenmagid/flow),
|
|
115
|
+
a cognitive workspace built on Claude Code over months of daily use. The
|
|
116
|
+
patterns that emerged — session loops, perspective-based audits, feedback
|
|
117
|
+
enforcement pipelines — turned out to be transferable to any project.
|
|
118
|
+
|
|
119
|
+
The core idea: Claude Code is powerful, but without process, each session
|
|
120
|
+
starts from zero. Orient/debrief creates continuity. Planning with
|
|
121
|
+
perspectives catches problems before they ship. The enforcement pipeline
|
|
122
|
+
turns recurring mistakes into permanent fixes.
|
|
123
|
+
|
|
124
|
+
None of this requires you to be a developer. The onboarding interview
|
|
125
|
+
meets you where you are, and the system adapts based on what you tell it.
|
|
126
|
+
|
|
127
|
+
## License
|
|
128
|
+
|
|
129
|
+
MIT
|
package/lib/cli.js
ADDED
|
@@ -0,0 +1,414 @@
|
|
|
1
|
+
const prompts = require('prompts');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const { copyTemplates } = require('./copy');
|
|
5
|
+
const { mergeSettings } = require('./settings-merge');
|
|
6
|
+
const { create: createMetadata, read: readMetadata } = require('./metadata');
|
|
7
|
+
const { setupDb } = require('./db-setup');
|
|
8
|
+
|
|
9
|
+
const VERSION = require('../package.json').version;
|
|
10
|
+
|
|
11
|
+
const MODULES = {
|
|
12
|
+
'session-loop': {
|
|
13
|
+
name: 'Session Loop (orient + debrief)',
|
|
14
|
+
description: 'Context continuity between sessions. Claude starts informed, ends by recording what happened.',
|
|
15
|
+
mandatory: true,
|
|
16
|
+
templates: ['skills/orient', 'skills/debrief', 'skills/menu', 'hooks/stop-hook.md'],
|
|
17
|
+
},
|
|
18
|
+
'hooks': {
|
|
19
|
+
name: 'Git Guardrails + Telemetry',
|
|
20
|
+
description: 'Block destructive git ops (force push, hard reset). Track skill usage via JSONL telemetry.',
|
|
21
|
+
mandatory: false,
|
|
22
|
+
default: true,
|
|
23
|
+
templates: ['hooks/git-guardrails.sh', 'hooks/skill-telemetry.sh', 'hooks/skill-tool-telemetry.sh'],
|
|
24
|
+
},
|
|
25
|
+
'work-tracking': {
|
|
26
|
+
name: 'Work Tracking (pib-db)',
|
|
27
|
+
description: 'Lightweight SQLite task/project tracker. Gives orient something to scan, debrief something to close.',
|
|
28
|
+
mandatory: false,
|
|
29
|
+
default: true,
|
|
30
|
+
templates: ['scripts/pib-db.js', 'scripts/pib-db-schema.sql'],
|
|
31
|
+
needsDb: true,
|
|
32
|
+
},
|
|
33
|
+
'planning': {
|
|
34
|
+
name: 'Planning + Execution (plan + execute)',
|
|
35
|
+
description: 'Structured implementation planning with perspective critique and execution checkpoints.',
|
|
36
|
+
mandatory: false,
|
|
37
|
+
default: true,
|
|
38
|
+
templates: ['skills/plan', 'skills/execute', 'skills/investigate'],
|
|
39
|
+
},
|
|
40
|
+
'compliance': {
|
|
41
|
+
name: 'Compliance Stack (rules + enforcement)',
|
|
42
|
+
description: 'Scoped instructions that load by path. Enforcement pipeline for promoting patterns to rules.',
|
|
43
|
+
mandatory: false,
|
|
44
|
+
default: true,
|
|
45
|
+
templates: ['rules/enforcement-pipeline.md', 'memory/patterns/_pattern-template.md', 'memory/patterns/pattern-intelligence-first.md'],
|
|
46
|
+
},
|
|
47
|
+
'audit': {
|
|
48
|
+
name: 'Audit Loop (audit + triage + perspectives)',
|
|
49
|
+
description: 'Periodic expert-perspective analysis with structured triage. Most compute-intensive module.',
|
|
50
|
+
mandatory: false,
|
|
51
|
+
default: true,
|
|
52
|
+
templates: [
|
|
53
|
+
'skills/audit', 'skills/pulse', 'skills/triage-audit',
|
|
54
|
+
'skills/perspectives',
|
|
55
|
+
'scripts/merge-findings.js', 'scripts/load-triage-history.js',
|
|
56
|
+
'scripts/triage-server.mjs', 'scripts/triage-ui.html',
|
|
57
|
+
'scripts/finding-schema.json',
|
|
58
|
+
],
|
|
59
|
+
},
|
|
60
|
+
'lifecycle': {
|
|
61
|
+
name: 'Lifecycle (onboard + seed + upgrade)',
|
|
62
|
+
description: 'Conversational onboarding, capability seeding from tech signals, upgrade merges.',
|
|
63
|
+
mandatory: false,
|
|
64
|
+
default: true,
|
|
65
|
+
templates: ['skills/onboard', 'skills/seed', 'skills/upgrade'],
|
|
66
|
+
},
|
|
67
|
+
'validate': {
|
|
68
|
+
name: 'Validate',
|
|
69
|
+
description: 'Structural validation checks with unified summary. Define your own validators.',
|
|
70
|
+
mandatory: false,
|
|
71
|
+
default: true,
|
|
72
|
+
templates: ['skills/validate'],
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
// Signals that a directory contains a real project (not just empty)
|
|
77
|
+
const PROJECT_SIGNALS = [
|
|
78
|
+
'package.json', 'Cargo.toml', 'requirements.txt', 'pyproject.toml',
|
|
79
|
+
'go.mod', 'Gemfile', 'pom.xml', 'build.gradle', 'CMakeLists.txt',
|
|
80
|
+
'Makefile', '.git', 'src', 'lib', 'app', 'main.py', 'index.js',
|
|
81
|
+
'index.ts', 'README.md', 'CLAUDE.md', '.claude',
|
|
82
|
+
];
|
|
83
|
+
|
|
84
|
+
function detectProjectState(dir) {
|
|
85
|
+
const entries = fs.readdirSync(dir);
|
|
86
|
+
const signals = entries.filter(e => PROJECT_SIGNALS.includes(e));
|
|
87
|
+
const hasClaude = entries.includes('.claude');
|
|
88
|
+
const hasPibrc = fs.existsSync(path.join(dir, '.pibrc.json'));
|
|
89
|
+
|
|
90
|
+
if (hasPibrc) return 'existing-install';
|
|
91
|
+
if (signals.length > 0) return 'existing-project';
|
|
92
|
+
// Allow a few dotfiles (e.g. .git) without calling it a project
|
|
93
|
+
if (entries.filter(e => !e.startsWith('.')).length === 0) return 'empty';
|
|
94
|
+
return 'empty';
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function parseArgs(argv) {
|
|
98
|
+
const args = argv.slice(2);
|
|
99
|
+
const flags = {
|
|
100
|
+
yes: false,
|
|
101
|
+
noDb: false,
|
|
102
|
+
dryRun: false,
|
|
103
|
+
help: false,
|
|
104
|
+
targetDir: '.',
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
for (const arg of args) {
|
|
108
|
+
if (arg === '--yes' || arg === '-y') flags.yes = true;
|
|
109
|
+
else if (arg === '--no-db') flags.noDb = true;
|
|
110
|
+
else if (arg === '--dry-run') flags.dryRun = true;
|
|
111
|
+
else if (arg === '--help' || arg === '-h') flags.help = true;
|
|
112
|
+
else if (!arg.startsWith('-')) flags.targetDir = arg;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return flags;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function printHelp() {
|
|
119
|
+
console.log(`
|
|
120
|
+
Usage: npx create-claude-rails [directory] [options]
|
|
121
|
+
|
|
122
|
+
Options:
|
|
123
|
+
--yes, -y Accept all defaults, no prompts
|
|
124
|
+
--no-db Skip work tracking database setup
|
|
125
|
+
--dry-run Show what would be copied without writing
|
|
126
|
+
--help, -h Show this help
|
|
127
|
+
|
|
128
|
+
Examples:
|
|
129
|
+
npx create-claude-rails Interactive setup in current dir
|
|
130
|
+
npx create-claude-rails my-project Set up in ./my-project/
|
|
131
|
+
npx create-claude-rails --yes Install everything, no questions
|
|
132
|
+
npx create-claude-rails --yes --no-db Install everything except DB
|
|
133
|
+
npx create-claude-rails --dry-run Preview what would be installed
|
|
134
|
+
`);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
async function run() {
|
|
138
|
+
const flags = parseArgs(process.argv);
|
|
139
|
+
|
|
140
|
+
if (flags.help) {
|
|
141
|
+
printHelp();
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
console.log('');
|
|
146
|
+
console.log(' 🚂 Claude on Rails v' + VERSION);
|
|
147
|
+
console.log(' Opinionated process scaffolding for Claude Code projects');
|
|
148
|
+
console.log('');
|
|
149
|
+
|
|
150
|
+
if (flags.dryRun) {
|
|
151
|
+
console.log(' [dry run — no files will be written]\n');
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
let projectDir = path.resolve(flags.targetDir);
|
|
155
|
+
|
|
156
|
+
// --- Directory detection ---
|
|
157
|
+
const dirState = detectProjectState(projectDir);
|
|
158
|
+
|
|
159
|
+
if (dirState === 'existing-install') {
|
|
160
|
+
const existing = readMetadata(projectDir);
|
|
161
|
+
console.log(` Found existing installation (v${existing.version}, installed ${existing.installedAt.split('T')[0]})`);
|
|
162
|
+
if (!flags.yes) {
|
|
163
|
+
const { proceed } = await prompts({
|
|
164
|
+
type: 'confirm',
|
|
165
|
+
name: 'proceed',
|
|
166
|
+
message: 'Update existing installation?',
|
|
167
|
+
initial: true,
|
|
168
|
+
});
|
|
169
|
+
if (!proceed) {
|
|
170
|
+
console.log(' Cancelled.');
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
} else if (dirState === 'existing-project') {
|
|
175
|
+
console.log(` Detected existing project in ${projectDir}`);
|
|
176
|
+
if (!flags.yes) {
|
|
177
|
+
const { action } = await prompts({
|
|
178
|
+
type: 'select',
|
|
179
|
+
name: 'action',
|
|
180
|
+
message: 'What would you like to do?',
|
|
181
|
+
choices: [
|
|
182
|
+
{ title: 'Add Claude on Rails to this project', value: 'here' },
|
|
183
|
+
{ title: 'Create a new project in a different directory', value: 'new' },
|
|
184
|
+
],
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
if (!action) {
|
|
188
|
+
console.log(' Cancelled.');
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (action === 'new') {
|
|
193
|
+
const { newDir } = await prompts({
|
|
194
|
+
type: 'text',
|
|
195
|
+
name: 'newDir',
|
|
196
|
+
message: 'Directory name for the new project:',
|
|
197
|
+
});
|
|
198
|
+
if (!newDir) {
|
|
199
|
+
console.log(' Cancelled.');
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
projectDir = path.resolve(newDir);
|
|
203
|
+
if (!fs.existsSync(projectDir)) {
|
|
204
|
+
if (!flags.dryRun) fs.mkdirSync(projectDir, { recursive: true });
|
|
205
|
+
console.log(` Created ${projectDir}`);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
// --yes with existing project: install here (the most useful default)
|
|
210
|
+
} else {
|
|
211
|
+
// Empty directory
|
|
212
|
+
console.log(` Setting up in ${projectDir}`);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// --- Module selection ---
|
|
216
|
+
let selectedModules = [];
|
|
217
|
+
let skippedModules = {};
|
|
218
|
+
let includeDb = !flags.noDb;
|
|
219
|
+
|
|
220
|
+
if (flags.yes) {
|
|
221
|
+
selectedModules = Object.keys(MODULES);
|
|
222
|
+
if (flags.noDb) {
|
|
223
|
+
includeDb = false;
|
|
224
|
+
}
|
|
225
|
+
console.log(` Installing all ${selectedModules.length} modules.${flags.noDb ? ' (skipping DB)' : ''}\n`);
|
|
226
|
+
} else {
|
|
227
|
+
const { installAll } = await prompts({
|
|
228
|
+
type: 'confirm',
|
|
229
|
+
name: 'installAll',
|
|
230
|
+
message: 'Install everything? (Y skips individual module questions)',
|
|
231
|
+
initial: true,
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
if (installAll) {
|
|
235
|
+
selectedModules = Object.keys(MODULES);
|
|
236
|
+
console.log(`\n Installing all ${selectedModules.length} modules.\n`);
|
|
237
|
+
} else {
|
|
238
|
+
for (const [key, mod] of Object.entries(MODULES)) {
|
|
239
|
+
if (mod.mandatory) {
|
|
240
|
+
console.log(` ✓ ${mod.name} (mandatory)`);
|
|
241
|
+
selectedModules.push(key);
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const { include } = await prompts({
|
|
246
|
+
type: 'confirm',
|
|
247
|
+
name: 'include',
|
|
248
|
+
message: `${mod.name}\n ${mod.description}`,
|
|
249
|
+
initial: mod.default !== false,
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
if (include) {
|
|
253
|
+
selectedModules.push(key);
|
|
254
|
+
} else {
|
|
255
|
+
const { reason } = await prompts({
|
|
256
|
+
type: 'text',
|
|
257
|
+
name: 'reason',
|
|
258
|
+
message: ` Why skip ${mod.name}? (brief reason, or Enter to skip)`,
|
|
259
|
+
});
|
|
260
|
+
skippedModules[key] = reason || 'Not needed yet';
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// DB prompt (only if work-tracking was selected and --no-db not set)
|
|
265
|
+
if (selectedModules.includes('work-tracking') && !flags.noDb) {
|
|
266
|
+
const { db } = await prompts({
|
|
267
|
+
type: 'confirm',
|
|
268
|
+
name: 'db',
|
|
269
|
+
message: 'Set up work tracking database? (requires better-sqlite3)',
|
|
270
|
+
initial: true,
|
|
271
|
+
});
|
|
272
|
+
includeDb = db;
|
|
273
|
+
} else if (!selectedModules.includes('work-tracking')) {
|
|
274
|
+
includeDb = false;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
console.log(' Setting up Claude on Rails...\n');
|
|
280
|
+
|
|
281
|
+
// --- Copy template files ---
|
|
282
|
+
const templateRoot = path.join(__dirname, '..', 'templates');
|
|
283
|
+
const claudeDir = path.join(projectDir, '.claude');
|
|
284
|
+
|
|
285
|
+
let totalCopied = 0;
|
|
286
|
+
let totalSkipped = 0;
|
|
287
|
+
let totalOverwritten = 0;
|
|
288
|
+
|
|
289
|
+
for (const modKey of selectedModules) {
|
|
290
|
+
const mod = MODULES[modKey];
|
|
291
|
+
for (const tmpl of mod.templates) {
|
|
292
|
+
const srcPath = path.join(templateRoot, tmpl);
|
|
293
|
+
if (!fs.existsSync(srcPath)) {
|
|
294
|
+
console.log(` ⚠ Template not found: ${tmpl}`);
|
|
295
|
+
continue;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
let destPath;
|
|
299
|
+
if (tmpl.startsWith('skills/') || tmpl.startsWith('hooks/') || tmpl.startsWith('rules/')) {
|
|
300
|
+
destPath = path.join(claudeDir, tmpl);
|
|
301
|
+
} else if (tmpl.startsWith('scripts/')) {
|
|
302
|
+
destPath = path.join(projectDir, tmpl);
|
|
303
|
+
} else {
|
|
304
|
+
destPath = path.join(claudeDir, tmpl);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
const stat = fs.statSync(srcPath);
|
|
308
|
+
if (stat.isDirectory()) {
|
|
309
|
+
const results = await copyTemplates(srcPath, destPath, { dryRun: flags.dryRun });
|
|
310
|
+
totalCopied += results.copied.length;
|
|
311
|
+
totalSkipped += results.skipped.length;
|
|
312
|
+
totalOverwritten += results.overwritten.length;
|
|
313
|
+
} else {
|
|
314
|
+
const destDir = path.dirname(destPath);
|
|
315
|
+
if (!flags.dryRun && !fs.existsSync(destDir)) {
|
|
316
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
if (fs.existsSync(destPath)) {
|
|
320
|
+
const existingContent = fs.readFileSync(destPath, 'utf8');
|
|
321
|
+
const incoming = fs.readFileSync(srcPath, 'utf8');
|
|
322
|
+
if (existingContent === incoming) {
|
|
323
|
+
totalSkipped++;
|
|
324
|
+
continue;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
if (flags.yes) {
|
|
328
|
+
// --yes: keep existing files (safe default)
|
|
329
|
+
totalSkipped++;
|
|
330
|
+
} else {
|
|
331
|
+
const response = await prompts({
|
|
332
|
+
type: 'select',
|
|
333
|
+
name: 'action',
|
|
334
|
+
message: `File exists: ${tmpl}`,
|
|
335
|
+
choices: [
|
|
336
|
+
{ title: 'Keep existing', value: 'keep' },
|
|
337
|
+
{ title: 'Overwrite with template', value: 'overwrite' },
|
|
338
|
+
],
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
if (response.action === 'overwrite') {
|
|
342
|
+
if (!flags.dryRun) fs.copyFileSync(srcPath, destPath);
|
|
343
|
+
totalOverwritten++;
|
|
344
|
+
} else {
|
|
345
|
+
totalSkipped++;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
} else {
|
|
349
|
+
if (!flags.dryRun) fs.copyFileSync(srcPath, destPath);
|
|
350
|
+
totalCopied++;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
console.log(` 📁 Files: ${totalCopied} copied, ${totalOverwritten} overwritten, ${totalSkipped} unchanged`);
|
|
357
|
+
|
|
358
|
+
// --- Make hook scripts executable ---
|
|
359
|
+
const hooksDir = path.join(claudeDir, 'hooks');
|
|
360
|
+
if (!flags.dryRun && fs.existsSync(hooksDir)) {
|
|
361
|
+
const hookFiles = fs.readdirSync(hooksDir).filter(f => f.endsWith('.sh'));
|
|
362
|
+
for (const f of hookFiles) {
|
|
363
|
+
fs.chmodSync(path.join(hooksDir, f), 0o755);
|
|
364
|
+
}
|
|
365
|
+
if (hookFiles.length > 0) {
|
|
366
|
+
console.log(` 🔧 Made ${hookFiles.length} hook scripts executable`);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// --- Merge hooks into settings.json ---
|
|
371
|
+
if (selectedModules.includes('hooks') && !flags.dryRun) {
|
|
372
|
+
const settingsPath = mergeSettings(projectDir, { includeDb });
|
|
373
|
+
console.log(` ⚙️ Merged hooks into ${path.relative(projectDir, settingsPath)}`);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// --- Set up database ---
|
|
377
|
+
if (includeDb && selectedModules.includes('work-tracking') && !flags.dryRun) {
|
|
378
|
+
try {
|
|
379
|
+
const dbResults = setupDb(projectDir);
|
|
380
|
+
for (const r of dbResults) console.log(` 🗄️ ${r}`);
|
|
381
|
+
} catch (err) {
|
|
382
|
+
console.log(` ⚠ Database setup failed: ${err.message}`);
|
|
383
|
+
console.log(' You can set it up later: node scripts/pib-db.js init');
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// --- Write metadata ---
|
|
388
|
+
if (!flags.dryRun) {
|
|
389
|
+
createMetadata(projectDir, {
|
|
390
|
+
modules: selectedModules,
|
|
391
|
+
skipped: skippedModules,
|
|
392
|
+
version: VERSION,
|
|
393
|
+
});
|
|
394
|
+
console.log(' 📝 Created .pibrc.json');
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// --- Summary ---
|
|
398
|
+
console.log('\n ✅ Claude on Rails installed!\n');
|
|
399
|
+
console.log(' Next steps:');
|
|
400
|
+
console.log(' 1. Run /onboard in Claude Code to generate your project context');
|
|
401
|
+
console.log(' 2. Start a session with /orient');
|
|
402
|
+
console.log(' 3. End a session with /debrief');
|
|
403
|
+
const skippedKeys = Object.keys(skippedModules);
|
|
404
|
+
if (skippedKeys.length > 0) {
|
|
405
|
+
console.log(`\n Skipped modules: ${skippedKeys.join(', ')}`);
|
|
406
|
+
console.log(' Re-run npx create-claude-rails to add them later.');
|
|
407
|
+
}
|
|
408
|
+
if (flags.dryRun) {
|
|
409
|
+
console.log('\n [dry run — nothing was written to disk]');
|
|
410
|
+
}
|
|
411
|
+
console.log('');
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
module.exports = { run, MODULES };
|
package/lib/copy.js
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const prompts = require('prompts');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Recursively copy files from src to dest, surfacing conflicts.
|
|
7
|
+
* Returns { copied: string[], skipped: string[], overwritten: string[] }
|
|
8
|
+
*/
|
|
9
|
+
async function copyTemplates(src, dest, { dryRun = false } = {}) {
|
|
10
|
+
const results = { copied: [], skipped: [], overwritten: [] };
|
|
11
|
+
await walkAndCopy(src, dest, src, results, dryRun);
|
|
12
|
+
return results;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async function walkAndCopy(srcRoot, destRoot, currentSrc, results, dryRun) {
|
|
16
|
+
const entries = fs.readdirSync(currentSrc, { withFileTypes: true });
|
|
17
|
+
|
|
18
|
+
for (const entry of entries) {
|
|
19
|
+
const srcPath = path.join(currentSrc, entry.name);
|
|
20
|
+
const relPath = path.relative(srcRoot, srcPath);
|
|
21
|
+
const destPath = path.join(destRoot, relPath);
|
|
22
|
+
|
|
23
|
+
if (entry.isDirectory()) {
|
|
24
|
+
if (!dryRun && !fs.existsSync(destPath)) {
|
|
25
|
+
fs.mkdirSync(destPath, { recursive: true });
|
|
26
|
+
}
|
|
27
|
+
await walkAndCopy(srcRoot, destRoot, srcPath, results, dryRun);
|
|
28
|
+
} else {
|
|
29
|
+
if (fs.existsSync(destPath)) {
|
|
30
|
+
const existing = fs.readFileSync(destPath, 'utf8');
|
|
31
|
+
const incoming = fs.readFileSync(srcPath, 'utf8');
|
|
32
|
+
|
|
33
|
+
if (existing === incoming) {
|
|
34
|
+
results.skipped.push(relPath);
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const response = await prompts({
|
|
39
|
+
type: 'select',
|
|
40
|
+
name: 'action',
|
|
41
|
+
message: `File exists: ${relPath}`,
|
|
42
|
+
choices: [
|
|
43
|
+
{ title: 'Keep existing', value: 'keep' },
|
|
44
|
+
{ title: 'Overwrite with template', value: 'overwrite' },
|
|
45
|
+
{ title: 'Show diff', value: 'diff' },
|
|
46
|
+
],
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
if (!response.action) {
|
|
50
|
+
// User cancelled
|
|
51
|
+
results.skipped.push(relPath);
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (response.action === 'diff') {
|
|
56
|
+
showDiff(existing, incoming, relPath);
|
|
57
|
+
const followUp = await prompts({
|
|
58
|
+
type: 'confirm',
|
|
59
|
+
name: 'overwrite',
|
|
60
|
+
message: `Overwrite ${relPath}?`,
|
|
61
|
+
initial: false,
|
|
62
|
+
});
|
|
63
|
+
if (followUp.overwrite && !dryRun) {
|
|
64
|
+
fs.copyFileSync(srcPath, destPath);
|
|
65
|
+
results.overwritten.push(relPath);
|
|
66
|
+
} else {
|
|
67
|
+
results.skipped.push(relPath);
|
|
68
|
+
}
|
|
69
|
+
} else if (response.action === 'overwrite') {
|
|
70
|
+
if (!dryRun) fs.copyFileSync(srcPath, destPath);
|
|
71
|
+
results.overwritten.push(relPath);
|
|
72
|
+
} else {
|
|
73
|
+
results.skipped.push(relPath);
|
|
74
|
+
}
|
|
75
|
+
} else {
|
|
76
|
+
if (!dryRun) {
|
|
77
|
+
const dir = path.dirname(destPath);
|
|
78
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
79
|
+
fs.copyFileSync(srcPath, destPath);
|
|
80
|
+
}
|
|
81
|
+
results.copied.push(relPath);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function showDiff(existing, incoming, relPath) {
|
|
88
|
+
const existingLines = existing.split('\n');
|
|
89
|
+
const incomingLines = incoming.split('\n');
|
|
90
|
+
|
|
91
|
+
console.log(`\n--- ${relPath} (existing)`);
|
|
92
|
+
console.log(`+++ ${relPath} (template)`);
|
|
93
|
+
console.log('');
|
|
94
|
+
|
|
95
|
+
// Simple line-by-line comparison
|
|
96
|
+
const maxLines = Math.max(existingLines.length, incomingLines.length);
|
|
97
|
+
for (let i = 0; i < maxLines; i++) {
|
|
98
|
+
const a = existingLines[i];
|
|
99
|
+
const b = incomingLines[i];
|
|
100
|
+
if (a === b) continue;
|
|
101
|
+
if (a !== undefined && b !== undefined) {
|
|
102
|
+
console.log(` ${i + 1}: - ${a}`);
|
|
103
|
+
console.log(` ${i + 1}: + ${b}`);
|
|
104
|
+
} else if (a !== undefined) {
|
|
105
|
+
console.log(` ${i + 1}: - ${a}`);
|
|
106
|
+
} else {
|
|
107
|
+
console.log(` ${i + 1}: + ${b}`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
console.log('');
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
module.exports = { copyTemplates };
|