create-claude-rails 0.3.3 → 0.4.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/lib/cli.js CHANGED
@@ -15,7 +15,7 @@ const MODULES = {
15
15
  name: 'Session Loop (orient + debrief)',
16
16
  description: 'Context continuity between sessions. Claude starts informed, ends by recording what happened.',
17
17
  mandatory: true,
18
- templates: ['skills/orient', 'skills/debrief', 'skills/menu', 'hooks/stop-hook.md'],
18
+ templates: ['skills/orient', 'skills/debrief', 'skills/debrief/phases/upstream-feedback.md', 'skills/menu', 'hooks/stop-hook.md'],
19
19
  },
20
20
  'hooks': {
21
21
  name: 'Git Guardrails + Telemetry',
@@ -23,7 +23,7 @@ const MODULES = {
23
23
  mandatory: false,
24
24
  default: true,
25
25
  lean: true,
26
- templates: ['hooks/git-guardrails.sh', 'hooks/skill-telemetry.sh', 'hooks/skill-tool-telemetry.sh'],
26
+ templates: ['hooks/git-guardrails.sh', 'hooks/cor-upstream-guard.sh', 'hooks/skill-telemetry.sh', 'hooks/skill-tool-telemetry.sh', 'scripts/cor-drift-check.cjs'],
27
27
  },
28
28
  'work-tracking': {
29
29
  name: 'Work Tracking (pib-db or markdown)',
@@ -12,6 +12,15 @@ const DEFAULT_HOOKS = {
12
12
  },
13
13
  ],
14
14
  },
15
+ {
16
+ matcher: 'Edit|Write',
17
+ hooks: [
18
+ {
19
+ type: 'command',
20
+ command: '.claude/hooks/cor-upstream-guard.sh',
21
+ },
22
+ ],
23
+ },
15
24
  ],
16
25
  UserPromptSubmit: [
17
26
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-claude-rails",
3
- "version": "0.3.3",
3
+ "version": "0.4.0",
4
4
  "description": "Claude on Rails — opinionated process scaffolding for Claude Code projects",
5
5
  "bin": {
6
6
  "create-claude-rails": "bin/create-claude-rails.js"
@@ -0,0 +1,79 @@
1
+ #!/bin/bash
2
+ # CoR Upstream Guard — PreToolUse hook for Edit and Write tool calls
3
+ #
4
+ # Blocks modifications to files managed by Claude on Rails. These files
5
+ # are upstream-owned: updates come through /cor-upgrade, not direct edits.
6
+ # Project-specific customization goes in _context.md and phase files.
7
+ #
8
+ # How it works:
9
+ # Reads .corrc.json manifest (list of CoR-installed files with hashes).
10
+ # If the target file_path is in the manifest, block the write.
11
+ #
12
+ # ROLLBACK: Comment out the PreToolUse entry for this hook in
13
+ # .claude/settings.json to disable it immediately.
14
+ #
15
+ # Hook contract:
16
+ # Input: $CLAUDE_TOOL_INPUT has the tool use JSON with "file_path" field
17
+ # Output: JSON on stdout with { "decision": "block"|"allow", "reason": "..." }
18
+
19
+ # Extract file_path from tool input
20
+ FILE_PATH=$(echo "$CLAUDE_TOOL_INPUT" | python3 -c "import sys,json; print(json.load(sys.stdin).get('file_path',''))" 2>/dev/null)
21
+
22
+ if [ -z "$FILE_PATH" ]; then
23
+ echo '{"decision":"allow"}'
24
+ exit 0
25
+ fi
26
+
27
+ # Find the project root (where .corrc.json lives)
28
+ # Walk up from current directory
29
+ find_project_root() {
30
+ local dir="$PWD"
31
+ while [ "$dir" != "/" ]; do
32
+ if [ -f "$dir/.corrc.json" ]; then
33
+ echo "$dir"
34
+ return 0
35
+ fi
36
+ dir=$(dirname "$dir")
37
+ done
38
+ return 1
39
+ }
40
+
41
+ PROJECT_ROOT=$(find_project_root)
42
+
43
+ if [ -z "$PROJECT_ROOT" ]; then
44
+ # No .corrc.json found — not a CoR project, allow everything
45
+ echo '{"decision":"allow"}'
46
+ exit 0
47
+ fi
48
+
49
+ # Resolve file_path to a relative path from project root
50
+ # Handle both absolute and relative paths
51
+ if [[ "$FILE_PATH" = /* ]]; then
52
+ # Absolute path — make relative to project root
53
+ REL_PATH="${FILE_PATH#$PROJECT_ROOT/}"
54
+ # If the path didn't change, the file is outside the project
55
+ if [ "$REL_PATH" = "$FILE_PATH" ]; then
56
+ echo '{"decision":"allow"}'
57
+ exit 0
58
+ fi
59
+ else
60
+ REL_PATH="$FILE_PATH"
61
+ fi
62
+
63
+ # Check if this relative path is in the manifest
64
+ IN_MANIFEST=$(python3 -c "
65
+ import json, sys
66
+ try:
67
+ with open('$PROJECT_ROOT/.corrc.json') as f:
68
+ data = json.load(f)
69
+ manifest = data.get('manifest', {})
70
+ print('yes' if '$REL_PATH' in manifest else 'no')
71
+ except:
72
+ print('no')
73
+ " 2>/dev/null)
74
+
75
+ if [ "$IN_MANIFEST" = "yes" ]; then
76
+ echo "{\"decision\":\"block\",\"reason\":\"Blocked: $REL_PATH is managed by Claude on Rails. CoR-managed files are upstream-owned — edits come through /cor-upgrade, not direct modification. Put project-specific content in _context.md or phase files instead.\"}"
77
+ else
78
+ echo '{"decision":"allow"}'
79
+ fi
@@ -0,0 +1,84 @@
1
+ #!/usr/bin/env node
2
+ // CoR Drift Check — detect modified upstream-managed files
3
+ //
4
+ // Compares current file hashes against .corrc.json manifest hashes.
5
+ // Reports files that have been modified since install (drift).
6
+ //
7
+ // Usage:
8
+ // node scripts/cor-drift-check.js # exit 0 if clean, 1 if drift
9
+ // node scripts/cor-drift-check.js --json # output JSON for programmatic use
10
+ // node scripts/cor-drift-check.js --fix # show what /cor-upgrade would fix
11
+
12
+ const fs = require('fs');
13
+ const path = require('path');
14
+ const crypto = require('crypto');
15
+
16
+ const projectRoot = findProjectRoot();
17
+ if (!projectRoot) {
18
+ console.error('No .corrc.json found — not a CoR project.');
19
+ process.exit(2);
20
+ }
21
+
22
+ const metadataPath = path.join(projectRoot, '.corrc.json');
23
+ const metadata = JSON.parse(fs.readFileSync(metadataPath, 'utf8'));
24
+ const manifest = metadata.manifest || {};
25
+
26
+ function findProjectRoot() {
27
+ let dir = process.cwd();
28
+ while (dir !== path.dirname(dir)) {
29
+ if (fs.existsSync(path.join(dir, '.corrc.json'))) return dir;
30
+ dir = path.dirname(dir);
31
+ }
32
+ return null;
33
+ }
34
+
35
+ function hashContent(content) {
36
+ return crypto.createHash('sha256').update(content).digest('hex').slice(0, 16);
37
+ }
38
+
39
+ const drifted = [];
40
+ const missing = [];
41
+ const clean = [];
42
+
43
+ for (const [relPath, expectedHash] of Object.entries(manifest)) {
44
+ const fullPath = path.join(projectRoot, relPath);
45
+
46
+ if (!fs.existsSync(fullPath)) {
47
+ missing.push(relPath);
48
+ continue;
49
+ }
50
+
51
+ const content = fs.readFileSync(fullPath, 'utf8');
52
+ const currentHash = hashContent(content);
53
+
54
+ if (currentHash !== expectedHash) {
55
+ drifted.push(relPath);
56
+ } else {
57
+ clean.push(relPath);
58
+ }
59
+ }
60
+
61
+ const args = process.argv.slice(2);
62
+
63
+ if (args.includes('--json')) {
64
+ console.log(JSON.stringify({ drifted, missing, clean: clean.length }, null, 2));
65
+ } else {
66
+ if (drifted.length === 0 && missing.length === 0) {
67
+ console.log(`All ${clean.length} CoR-managed files match upstream hashes.`);
68
+ } else {
69
+ if (drifted.length > 0) {
70
+ console.log(`Drifted (${drifted.length} files modified from upstream):`);
71
+ for (const f of drifted) console.log(` ${f}`);
72
+ }
73
+ if (missing.length > 0) {
74
+ console.log(`Missing (${missing.length} files deleted):`);
75
+ for (const f of missing) console.log(` ${f}`);
76
+ }
77
+ console.log(`\nClean: ${clean.length} files match.`);
78
+ if (args.includes('--fix')) {
79
+ console.log('\nRun /cor-upgrade to restore drifted files to upstream versions.');
80
+ }
81
+ }
82
+ }
83
+
84
+ process.exit(drifted.length > 0 || missing.length > 0 ? 1 : 0);
@@ -28,6 +28,9 @@ related:
28
28
  - type: file
29
29
  path: .claude/skills/debrief/phases/loose-ends.md
30
30
  role: "Project-specific: non-project items to capture"
31
+ - type: file
32
+ path: .claude/skills/debrief/phases/upstream-feedback.md
33
+ role: "Instruction: surface CoR friction back to source repo"
31
34
  - type: file
32
35
  path: .claude/skills/debrief/phases/report.md
33
36
  role: "Project-specific: how to present the summary"
@@ -182,7 +185,24 @@ them now while context is fresh.
182
185
  > different destinations (memory/feedback vs finding database). Both
183
186
  > feed the enforcement pipeline, but through different channels.
184
187
 
185
- ### 8. Capture Loose Ends (core)
188
+ ### 8. Upstream Feedback (core)
189
+
190
+ Read `phases/upstream-feedback.md`. This is an **instruction phase**
191
+ shipped with CoR — it tells Claude to reflect on whether the session
192
+ revealed friction with any CoR-provided skill, phase, or convention.
193
+
194
+ If friction is found, Claude drafts a short feedback item and surfaces
195
+ it in the report for the user to confirm, edit, or dismiss. If
196
+ confirmed, the feedback is delivered to the CoR repo (via local link
197
+ or GitHub issue). If nothing — the phase is silent.
198
+
199
+ This is different from `/extract` (which proposes generalizable
200
+ artifacts for upstreaming). This captures field friction: what hurt,
201
+ what was confusing, what needed a workaround.
202
+
203
+ **This phase should not be skipped.** It's how CoR learns from use.
204
+
205
+ ### 9. Capture Loose Ends (core)
186
206
 
187
207
  Read `phases/loose-ends.md` for non-project items and environmental
188
208
  concerns to capture before closing. Sessions generate non-project
@@ -191,14 +211,14 @@ these aren't captured somewhere, they rely on human memory.
191
211
 
192
212
  **Skip (absent/empty).**
193
213
 
194
- ### 9. Discover Custom Phases
214
+ ### 10. Discover Custom Phases
195
215
 
196
216
  After running the core phases above, check for any additional phase
197
217
  files in `phases/` that the skeleton doesn't define. These are project-
198
218
  specific extensions. Each custom phase file declares its position in
199
219
  the workflow. Execute them at their declared position.
200
220
 
201
- ### 10. Present Report (presentation)
221
+ ### 11. Present Report (presentation)
202
222
 
203
223
  Read `phases/report.md` for how to present the debrief summary.
204
224
 
@@ -218,6 +238,7 @@ Read `phases/report.md` for how to present the debrief summary.
218
238
  | `update-state.md` | Default: check system-status.md | What state files to update |
219
239
  | `health-checks.md` | Skip | Session-end health checks |
220
240
  | `record-lessons.md` | Default: ask what was learned | How to capture learnings |
241
+ | `upstream-feedback.md` | **Instruction: always runs** | Surface CoR friction to source repo |
221
242
  | `loose-ends.md` | Skip | Non-project items to capture |
222
243
  | `report.md` | Default: brief summary | How to present the report |
223
244
 
@@ -228,7 +249,8 @@ Phases are either **core** (maintain system state) or **presentation**
228
249
  skip presentation phases. Core phases always run.
229
250
 
230
251
  - **Core phases** (always run): inventory, close-work, auto-maintenance,
231
- update-state, health-checks, record-lessons, loose-ends, persist work
252
+ update-state, health-checks, record-lessons, upstream-feedback,
253
+ loose-ends, persist work
232
254
  - **Presentation phases** (skippable): report
233
255
 
234
256
  A project that wants a quick debrief variant skips the report and
@@ -0,0 +1,106 @@
1
+ # Upstream Feedback — Surface CoR Friction to the Source
2
+
3
+ **Position:** Runs after record-lessons (step 7), before capture loose
4
+ ends (step 8). Lessons are fresh; friction is top of mind.
5
+
6
+ **This is an instruction phase** — it tells Claude what to do, not a
7
+ customization point for the project. It ships with CoR and should not
8
+ be deleted or replaced with `skip: true`.
9
+
10
+ ## What This Phase Does
11
+
12
+ During debrief, Claude already has full session context: what was built,
13
+ what went wrong, what was learned. This phase asks Claude to reflect on
14
+ one narrow question: **was there friction with anything CoR provided?**
15
+
16
+ - A skill whose flow didn't match how the project actually works
17
+ - A phase file whose default behavior was wrong or confusing
18
+ - A convention that fought the project's grain
19
+ - A missing capability that required a workaround
20
+ - An unclear SKILL.md that led to wasted time
21
+
22
+ This is NOT the same as `/extract` (which looks for generalizable
23
+ artifacts to upstream). This is field feedback — "this thing you shipped
24
+ hurt when I used it."
25
+
26
+ ## Workflow
27
+
28
+ ### 1. Claude Reflects (silent)
29
+
30
+ Review the session for CoR-specific friction. Consider:
31
+
32
+ - Did any CoR skill need to be worked around or used in an unintended way?
33
+ - Did a phase file's default behavior cause confusion or extra work?
34
+ - Was a SKILL.md unclear, leading to misinterpretation?
35
+ - Did the skeleton/phase separation feel wrong for something?
36
+ - Was something missing that would have helped?
37
+ - Did orient or debrief surface irrelevant information or miss something important?
38
+
39
+ If nothing comes to mind — **stop here silently**. Most sessions have
40
+ no CoR friction. Do not prompt the user with "any CoR feedback?" every
41
+ time. The phase produces nothing and costs nothing unless there's
42
+ something real.
43
+
44
+ ### 2. Draft Feedback (if friction found)
45
+
46
+ For each friction point, draft a short feedback item:
47
+
48
+ ```
49
+ ## [Short title]
50
+
51
+ **Skill/phase:** [which CoR component]
52
+ **Friction:** [what happened — 2-3 sentences max]
53
+ **Suggestion:** [what might be better — optional, can be "not sure"]
54
+ **Session context:** [one line about what the project was doing when this came up]
55
+ ```
56
+
57
+ Keep it concrete. "The plan skill was confusing" is not useful.
58
+ "The plan skill's critique phase activated 4 perspectives when only 1
59
+ was relevant, adding 3 minutes of noise to every plan" is useful.
60
+
61
+ ### 3. Surface for Confirmation
62
+
63
+ Include the draft in the debrief report under a distinct heading:
64
+
65
+ > **Upstream feedback for CoR:**
66
+ > I noticed friction with [component]. Here's what I'd send:
67
+ > [draft]
68
+ >
69
+ > Send this upstream? (yes / edit / skip)
70
+
71
+ The user confirms, edits, or dismisses. One quick decision per item.
72
+ Do not ask open-ended questions. Do not batch — if there are multiple
73
+ friction points (rare), present each separately.
74
+
75
+ ### 4. Deliver
76
+
77
+ If the user confirms, deliver the feedback. Detection and delivery
78
+ follow the same pattern as `/extract`:
79
+
80
+ **If linked** (the CoR package resolves to a local directory — check
81
+ if `node -e "console.log(require.resolve('create-claude-rails'))"`
82
+ points to a local path rather than a `node_modules` path):
83
+
84
+ - Write the feedback as a markdown file in the CoR repo's `feedback/`
85
+ directory (create it if needed)
86
+ - Filename: `[source-project]-[date]-[short-title].md`
87
+ (e.g., `flow-2026-04-04-plan-critique-noise.md`)
88
+ - Add frontmatter: `type: field-feedback`, `source: [project]`,
89
+ `date: [ISO date]`, `component: [skill/phase name]`
90
+
91
+ **If not linked** (CoR is installed from npm):
92
+
93
+ - Open a GitHub issue on the CoR repo
94
+ - Title: `Field feedback: [short title]`
95
+ - Label: `field-feedback` (create if needed)
96
+ - Body: the feedback markdown
97
+
98
+ **If neither works** (no link, no gh access):
99
+
100
+ - Output the feedback to the terminal and tell the user to file
101
+ it manually or copy it to the CoR repo
102
+
103
+ ### 5. Done
104
+
105
+ Note in the debrief report what was sent and where. Move on to the
106
+ next phase.
@@ -68,5 +68,7 @@ With user confirmation:
68
68
 
69
69
  ### 5. Post-Publish
70
70
 
71
+ - Re-run the lean install (`node bin/create-claude-rails.js --lean`) to
72
+ update the local dogfood copy with the just-published templates
71
73
  - Update `system-status.md` if it exists
72
74
  - Report the published version and npm URL