Skip to content

feat: online learning system and updater improvements#39

Merged
maxritter merged 12 commits intomainfrom
dev
Jan 27, 2026
Merged

feat: online learning system and updater improvements#39
maxritter merged 12 commits intomainfrom
dev

Conversation

@maxritter
Copy link
Copy Markdown
Owner

@maxritter maxritter commented Jan 27, 2026

Summary

  • Add /learn command for extracting reusable knowledge into skills
  • Add learn_reminder hook that prompts for knowledge extraction on stop
  • Add learn.md rule for automatic invocation after significant tasks
  • Improve updater to restart CCP automatically after updates via --restart-ccp flag
  • Show minimal progress in quiet mode during updates (step indicators)
  • Suppress verbose output (extension lists, warnings) in quiet mode
  • Clean up standards skills naming

Test plan

  • All 193 installer tests pass
  • All CCP tests pass
  • Verified quiet mode output shows progress steps
  • Verified learn_reminder hook works with cooldown logic

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Online Learning (/learn) to extract reusable workflows into skills.
    • Stop-guard for the /spec workflow and clearer approval flow.
    • Task management guidance: disallow sub-agents, allow Task management tools.
  • Bug Fixes

    • Improved Windows process enumeration and stack-trace parsing.
    • Minor runtime/string literal robustness fixes.
  • Documentation

    • Expanded/rewritten docs for /spec, /sync, /learn, installer, and README.
    • Standardized skill naming in front-matter.
  • Chores

    • Migration of plugin paths to new CCP layout.
    • Removed several legacy utilities and related tests.

✏️ Tip: You can customize this high-level summary in your review settings.

- Add spec_stop_guard.py hook to prevent early stopping during /spec workflow
- Hook blocks stopping unless AskUserQuestion was used or no active plan exists
- Rename .claude/plugin/ to .claude/ccp/ for consistency
- Add tests for spec_stop_guard hook
… after updates

- Add /learn command for extracting reusable knowledge into skills
- Add learn_reminder hook that prompts for knowledge extraction on stop
- Add learn.md rule for automatic invocation after significant tasks
- Improve updater to restart CCP automatically after updates via --restart-ccp flag
- Show minimal progress in quiet mode during updates (step indicators)
- Suppress verbose output (extension lists, warnings) in quiet mode
- Clean up standards skills naming
@vercel
Copy link
Copy Markdown

vercel bot commented Jan 27, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Review Updated (UTC)
claude-codepro Ready Ready Preview, Comment Jan 27, 2026 5:24pm

@claude
Copy link
Copy Markdown

claude bot commented Jan 27, 2026

Claude finished @maxritter's task in 1m 50s —— View job


Code Review Complete

  • Analyze PR changes and gather context
  • Review new hook files (learn_reminder.py, spec_stop_guard.py)
  • Review /learn command implementation
  • Review updater improvements
  • Review installer and dependency changes
  • Check test files for new hooks
  • Check for security issues, bugs, and best practices
  • Provide comprehensive review feedback

Summary

This PR adds an online learning system with /learn command, improves the updater with auto-restart capability, renames the plugin directory from .claude/plugin to .claude/ccp, and includes various cleanup improvements.

Review Findings

✅ Strengths

  1. Well-structured /learn command (.claude/commands/learn.md): The four-phase workflow (Evaluate → Check Existing → Create → Quality Gates) is comprehensive and well-documented.

  2. Clean learn_reminder.py implementation: Simple state-file-based cooldown mechanism with 120s window prevents reminder spam. Good use of exit code 2 for blocking.

  3. Robust spec_stop_guard.py: Properly parses transcript JSON to detect AskUserQuestion tool usage, allowing stop only when appropriate.

  4. --restart-ccp flag (install.sh:29-32, 498-506): Clean integration using exec to replace the installer process with CCP.

  5. Directory rename consolidation: Moving from .claude/plugin to .claude/ccp improves naming consistency with the project name.

⚠️ Suggestions

1. Hardcoded state file path in learn_reminder.py:14

STATE_FILE = Path("/tmp/claude-online-learning-reminder")

Using /tmp works but could cause issues if multiple users share a system. Consider using user-specific paths:

STATE_FILE = Path.home() / ".claude" / "learn-reminder-state"

Fix this →

2. Hardcoded plans directory in spec_stop_guard.py:24

plans_dir = Path("docs/plans")

This uses a relative path which depends on the current working directory. Consider making it configurable or using an environment variable for robustness.

3. Error handling in spec_stop_guard.py:88-91

try:
    input_data = json.load(sys.stdin)
except json.JSONDecodeError:
    return 0

Silent failure on JSON decode returns 0 (allow stop). This is intentional for graceful degradation but could mask input issues. Consider logging to stderr for debugging.

4. Missing stop_hook_active documentation in spec_stop_guard.py:94-95

if input_data.get("stop_hook_active", False):
    return 0

This infinite loop prevention is good, but the stop_hook_active field isn't documented anywhere. Add a comment explaining the mechanism.

✅ Test Coverage

Test files are git-crypt encrypted so I couldn't review them directly, but the PR notes indicate:

  • All 193 installer tests pass
  • All CCP tests pass
  • Verified quiet mode output
  • Verified learn_reminder hook with cooldown logic

📝 Minor Notes

  1. Skills naming: The skill files correctly updated references from /plugin/ to /ccp/ paths.

  2. Settings update: settings.local.json correctly includes "Skill(learn)" in permissions.

  3. Gitignore: Properly excludes .claude/skills/ directory for user-generated skills.

  4. Deleted files: Removal of standards-skill-creator/SKILL.md and update-refs/SKILL.md appears intentional per the cleanup mentioned.

🔒 Security Review

No security concerns identified:

  • No credential handling
  • No shell injection vectors
  • File operations use proper Path handling
  • JSON parsing is properly wrapped in try/except

Verdict

Approved

The implementation is clean, well-structured, and follows the project's established patterns. The suggestions above are improvements rather than blockers.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Jan 27, 2026

Walkthrough

Refactors plugin layout from .claude/plugin to .claude/ccp, adds a /learn workflow and spec stop-guard hook, standardizes skill metadata names, removes several installer helpers and tests, adjusts installer and CI workflows, and updates docs, hooks, and defaults.

Changes

Cohort / File(s) Summary
CCP layout & hooks
.claude/ccp/.lsp.json, .claude/ccp/hooks/hooks.json, .claude/ccp/hooks/spec_stop_guard.py, .claude/ccp/hooks/tool_redirect.py, .claude/ccp/hooks/context_monitor.py
Migrate plugin paths to .claude/ccp; add spec_stop_guard.py stop-guard invoked by Stop hook; clarify Task tool messaging (allow Task management tools); add /learn prompt in context monitor; minor JSON formatting changes.
CCP scripts
.claude/ccp/scripts/... (context-generator.cjs, mcp-server.cjs, worker-service.cjs, worker-wrapper.cjs)
Adjust default constants (models, Python versions), fix template/string and stack parsing bugs, harden Windows PID parsing, and add duplicate shebang.
Skill metadata & docs
.claude/ccp/skills/standards-*/SKILL.md (multiple), .claude/commands/learn.md, .claude/commands/spec.md, .claude/commands/sync.md, .claude/rules/standard/learn.md, .claude/rules/standard/workflow-enforcement.md, README.md, docs/site/src/components/WhatsInside.tsx
Rename skill front-matter names to kebab-case (e.g., standards-python), expand descriptions for some skills, add /learn documentation and learning rule, update /spec and /sync workflows and guidance, remove skill-creator doc.
Installer CLI & scripts
install.sh, installer/config.py, installer/downloads.py, installer/platform_utils.py
Add --restart-ccp flag and restart logic; remove get_preference/set_preference, download_directory, and is_macos/is_linux helpers.
Installer steps & migration
installer/steps/claude_files.py, installer/steps/dependencies.py, installer/steps/finalize.py, installer/steps/git_setup.py, installer/steps/shell_config.py, installer/steps/vscode_extensions.py
Replace .claude/plugin.claude/ccp across steps, add cleanup for legacy plugin dir, simplify Claude Mem setup to migration-only, add local-model checks for Vexor/VTSL, adjust quiet-mode and reload prompts, reduce per-extension UI verbosity.
Installer UI & API surface
installer/ui.py, .claude/settings.local.json, .coderabbit.yaml, .github/workflows/claude.yml, .github/workflows/claude-code-review.yml
Remove several Console public helpers (section, box, table, checklist); add quiet-mode early returns; bump versions and token limits; rename plugin root to CCP; add CodeRabbit path filters; replace/remove legacy review workflow and add new claude-review job.
Tests
installer/tests/unit/steps/test_claude_files.py, installer/tests/unit/steps/test_dependencies.py, installer/tests/unit/test_downloads.py, installer/tests/unit/test_ui.py
Update tests to expect .claude/ccp layout and old plugin cleanup; simplify tests to focus on migration; remove directory-download tests and some Console UI tests.
Misc config & ignores
.gitignore, .claude/settings.local.json, .coderabbit.yaml
Broaden .claude/skills ignore to entire dir; update settings JSON (version bump, enable tasks, token limits, plugin root rename); add CodeRabbit config for path-specific review rules.

Sequence Diagram(s)

mermaid
sequenceDiagram
participant HookRunner as Hook Runner
participant SpecGuard as spec_stop_guard.py
participant FS as Filesystem
participant STDIN as Hook JSON (stdin)
participant User as Terminal/User
HookRunner->>SpecGuard: invoke (stdin JSON)
SpecGuard->>STDIN: read input JSON
SpecGuard->>FS: read docs/plans for active plan
SpecGuard->>FS: read transcript file (if present)
SpecGuard->>FS: read/write /tmp/claude-spec-stop-guard state
SpecGuard->>SpecGuard: evaluate waiting-for-user (AskUserQuestion) and cooldown
alt allow stop
SpecGuard->>HookRunner: exit 0
else block stop
SpecGuard->>User: print red banner + guidance
SpecGuard->>HookRunner: exit 2
end

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 27.22% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'feat: online learning system and updater improvements' directly aligns with the main changes: addition of the /learn command, learning system integration, and updater enhancements with auto-restart capability.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
installer/steps/claude_files.py (1)

204-213: Update file path pattern to match .claude/ccp directory structure.

Line 204 still checks for "/plugin/" in file paths, but the directory has been renamed from .claude/plugin/ to .claude/ccp/. Files under .claude/ccp/ will not match this pattern and will incorrectly fall through to the "other" category. Change the check to "/ccp/" to properly categorize plugin files.

Current code (lines 204-205)
            elif "/plugin/" in file_path:
                categories["plugin"].append(file_info)
🤖 Fix all issues with AI agents
In @.claude/ccp/hooks/learn_reminder.py:
- Around line 14-31: The STATE_FILE is hardcoded to /tmp which risks clobbering
and symlink attacks; change initialization of STATE_FILE in this module to use a
per-user runtime/cache directory (e.g., compute base =
platformdirs.user_runtime_dir or platformdirs.user_cache_dir or fallback to
Path.home()/".cache"/"claude") and set STATE_FILE = Path(base) /
"claude-online-learning-reminder"; ensure the directory is created
(mkdir(parents=True, exist_ok=True)) before any read/write in main(), keep
COOLDOWN_SECONDS and main() logic the same but wrap read_text/write_text with
the existing exception handling to tolerate permission errors.

In @.claude/ccp/scripts/worker-wrapper.cjs:
- Around line 1-2: The file contains a duplicated shebang ("#!/usr/bin/env bun")
causing a parse error because only the very first line may be a shebang; remove
the redundant second shebang so there is only a single "#!/usr/bin/env bun" at
the top of the file, leaving the rest of the JS unchanged to ensure the file
parses correctly.

In @.claude/ccp/skills/standards-accessibility/SKILL.md:
- Line 2: The YAML key "name: standards-accessability" is misspelled; update the
value to "name: standards-accessibility" so the slug matches the directory and
correct spelling in SKILL.md (replace the existing "standards-accessability"
token with "standards-accessibility").

In @.claude/ccp/skills/standards-css/SKILL.md:
- Line 2: The skill metadata uses an inconsistent name value; replace the
current name field value "standards-cc" with "standards-css" in the SKILL.md so
the frontmatter/name matches the folder path and the project's naming convention
(look for the line containing name: standards-cc and update it to name:
standards-css).

In @.claude/rules/standard/workflow-enforcement.md:
- Around line 30-38: The fenced code blocks in the workflow-enforcement markdown
lack language tags (causing MD040); update each triple-backtick block shown (the
three task listing blocks and the example task blocks referenced) to include a
language identifier like ```text or ```markdown so every fenced code block has a
language tag; ensure you replace each opening ``` with ```text (or ```markdown)
for the blocks containing plain lists and task examples (the blocks containing
"1. User makes request..." and the subsequent task lists and examples) so
markdownlint MD040 is satisfied.

In `@installer/steps/dependencies.py`:
- Line 142: Remove the unsupported "lspRecommendationDisabled" key from the
configuration being built in installer/steps/dependencies.py (delete the line
containing "lspRecommendationDisabled": True inside the settings/configuration
dict) and instead control LSP behavior via environment variables (e.g., set
ENABLE_LSP_TOOL in the env block) if LSP toggling is required.
🧹 Nitpick comments (4)
.claude/ccp/scripts/worker-service.cjs (1)

14-20: Consider maintaining the source file separately from the minified bundle.

This .cjs file appears to be a minified/bundled output, making code review difficult. If this is generated from source files, consider:

  1. Adding the source files to version control for reviewability
  2. Including a source map or build configuration reference
  3. Documenting the build process in the repository

This would improve maintainability and allow proper code review of future changes.

.claude/ccp/hooks/spec_stop_guard.py (2)

22-28: Anchor plan discovery to project root if hooks don’t run from it.

If Stop hooks run with a different working directory, docs/plans won’t be found and the guard won’t trigger. Consider resolving from a project root provided in hook input (if available) or document that hooks run from the project directory.


128-128: Remove unused f-string prefix.

💡 Suggested change
-    print(f"Continue the workflow or use AskUserQuestion if user input is needed.", file=sys.stderr)
+    print("Continue the workflow or use AskUserQuestion if user input is needed.", file=sys.stderr)
.claude/commands/spec.md (1)

994-1008: Fix numbering gaps in the CRITICAL RULES SUMMARY.

The list skips numbers (e.g., 6→8, 10→12). Consider renumbering or switching to bullets to avoid confusion.

Comment on lines +14 to +31
STATE_FILE = Path("/tmp/claude-online-learning-reminder")
COOLDOWN_SECONDS = 120


def main() -> int:
"""Output online learning reminder with cooldown."""
now = time.time()

if STATE_FILE.exists():
try:
last_reminder = float(STATE_FILE.read_text().strip())
if now - last_reminder < COOLDOWN_SECONDS:
return 0
except (ValueError, OSError):
pass

STATE_FILE.write_text(str(now))
print("🧠 EVALUATE: Did you learn something extractable? → Invoke Skill(learn)", file=sys.stderr)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Avoid a fixed /tmp state file (symlink/clobber risk).
Use a per-user runtime/cache directory to prevent unintended overwrites.

🔧 Proposed fix
+import os
 from pathlib import Path

-STATE_FILE = Path("/tmp/claude-online-learning-reminder")
+def _get_state_file() -> Path:
+    runtime_dir = Path(
+        os.environ.get("XDG_RUNTIME_DIR", Path.home() / ".cache" / "claude-codepro")
+    )
+    runtime_dir.mkdir(parents=True, exist_ok=True)
+    return runtime_dir / "online-learning-reminder"
+
 COOLDOWN_SECONDS = 120
@@
-    if STATE_FILE.exists():
+    state_file = _get_state_file()
+    if state_file.exists():
         try:
-            last_reminder = float(STATE_FILE.read_text().strip())
+            last_reminder = float(state_file.read_text().strip())
             if now - last_reminder < COOLDOWN_SECONDS:
                 return 0
         except (ValueError, OSError):
             pass

-    STATE_FILE.write_text(str(now))
+    state_file.write_text(str(now))
🧰 Tools
🪛 Ruff (0.14.14)

14-14: Probable insecure usage of temporary file or directory: "/tmp/claude-online-learning-reminder"

(S108)

🤖 Prompt for AI Agents
In @.claude/ccp/hooks/learn_reminder.py around lines 14 - 31, The STATE_FILE is
hardcoded to /tmp which risks clobbering and symlink attacks; change
initialization of STATE_FILE in this module to use a per-user runtime/cache
directory (e.g., compute base = platformdirs.user_runtime_dir or
platformdirs.user_cache_dir or fallback to Path.home()/".cache"/"claude") and
set STATE_FILE = Path(base) / "claude-online-learning-reminder"; ensure the
directory is created (mkdir(parents=True, exist_ok=True)) before any read/write
in main(), keep COOLDOWN_SECONDS and main() logic the same but wrap
read_text/write_text with the existing exception handling to tolerate permission
errors.

Comment on lines +30 to +38
```
1. User makes request
2. IMMEDIATELY create tasks (before any other work)
3. Set up dependencies with addBlockedBy
4. Mark task in_progress when starting
5. Mark task completed when done
6. Check TaskList for next task
7. Repeat until all tasks completed
```
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Add fenced code block languages for markdownlint compliance.

These new blocks lack a language tag. Use text or markdown to satisfy MD040 and improve readability.

✅ Suggested fix
-```
+```text
 1. User makes request
 2. IMMEDIATELY create tasks (before any other work)
 3. Set up dependencies with addBlockedBy
 4. Mark task in_progress when starting
 5. Mark task completed when done
 6. Check TaskList for next task
 7. Repeat until all tasks completed
-```
+```

-```
+```text
 Task 1: Research existing code
 Task 2: Implement feature [blockedBy: 1]
 Task 3: Write tests [blockedBy: 2]
 Task 4: Update documentation [blockedBy: 2]
-```
+```

-```
+```text
 1. TaskCreate: "Fix login bug"
 2. TaskCreate: "Add password reset feature"
 3. TaskCreate: "Test both features" [blockedBy: 1, 2]
 4. Start task 1, mark in_progress
 5. Complete task 1, mark completed
 6. TaskList → see task 2 is ready
 7. Continue...
-```
+```

Also applies to: 44-49, 55-63

🧰 Tools
🪛 markdownlint-cli2 (0.18.1)

30-30: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
In @.claude/rules/standard/workflow-enforcement.md around lines 30 - 38, The
fenced code blocks in the workflow-enforcement markdown lack language tags
(causing MD040); update each triple-backtick block shown (the three task listing
blocks and the example task blocks referenced) to include a language identifier
like ```text or ```markdown so every fenced code block has a language tag;
ensure you replace each opening ``` with ```text (or ```markdown) for the blocks
containing plain lists and task examples (the blocks containing "1. User makes
request..." and the subsequent task lists and examples) so markdownlint MD040 is
satisfied.

"respectGitignore": False,
"autoUpdates": False,
"claudeInChromeDefaultEnabled": False,
"lspRecommendationDisabled": True,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🌐 Web query:

Claude Code "lspRecommendationDisabled" configuration option

💡 Result:

There’s no publicly documented Claude Code setting named lspRecommendationDisabled.

In Anthropic’s official settings.json documentation, the supported top-level keys are listed (e.g., env, model, permissions, hooks, enabledPlugins, etc.), and lspRecommendationDisabled is not among them—so it’s not a supported/official configuration option you can rely on. [1]

If your goal is to control LSP behavior, the documented approach is to enable/disable LSP via environment variables (which you can also set through settings.json using the env block). For example, LSP is commonly toggled with ENABLE_LSP_TOOL=1. [1][2]

Sources: [1] [2]


🏁 Script executed:

# Search the codebase for references to "lspRecommendationDisabled"
rg "lspRecommendationDisabled" -i

Repository: maxritter/claude-codepro

Length of output: 146


🏁 Script executed:

# Check if there's any documentation or schema for Claude Code config in the repo
find . -type f \( -name "*.md" -o -name "*.json" -o -name "*.yml" -o -name "*.yaml" \) | head -20 | xargs grep -l "lsp\|config\|schema" 2>/dev/null | head -10

Repository: maxritter/claude-codepro

Length of output: 469


🏁 Script executed:

# Examine the dependencies.py file more closely to understand context
cat -n installer/steps/dependencies.py | sed -n '130,155p'

Repository: maxritter/claude-codepro

Length of output: 1112


Remove lspRecommendationDisabled—it's not a supported Claude Code configuration option.

Web search confirms this setting is not documented in Anthropic's official settings.json schema and will be silently ignored. To control LSP behavior, use environment variables instead (e.g., set ENABLE_LSP_TOOL in the env block if LSP control is needed).

🤖 Prompt for AI Agents
In `@installer/steps/dependencies.py` at line 142, Remove the unsupported
"lspRecommendationDisabled" key from the configuration being built in
installer/steps/dependencies.py (delete the line containing
"lspRecommendationDisabled": True inside the settings/configuration dict) and
instead control LSP behavior via environment variables (e.g., set
ENABLE_LSP_TOOL in the env block) if LSP toggling is required.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
.claude/commands/sync.md (1)

302-353: Fix Phase/Step numbering mismatch.

Phase 8 uses Step 7.x and Phase 9 uses Step 8.x, which makes the workflow confusing and harder to follow. Please align step numbers to the phase they belong to.

Also applies to: 391-439

.github/workflows/claude.yml (2)

31-35: Restrict @claude triggers to trusted actors.
Any commenter can invoke the workflow and consume the OAuth token (Line 31-35). Gate by author association (OWNER/MEMBER/COLLABORATOR) to prevent untrusted triggers.

🔒 Example guard (apply same pattern per event type)
-  claude-mention:
-    if: |
-      (github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) ||
+  claude-mention:
+    if: |
+      (
+        github.event_name == 'issue_comment' &&
+        contains(github.event.comment.body, '@claude') &&
+        contains(fromJson('["OWNER","MEMBER","COLLABORATOR"]'), github.event.comment.author_association)
+      ) ||
       (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) ||
       (github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) ||
       (github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')))

As per coding guidelines, tighten workflow security gates.


46-52: Pin GitHub Actions to immutable SHAs.
Using moving tags (@v4, @v1) creates a supply-chain risk. Pin to commit SHAs instead (lines 46, 52, 70, 76). As per coding guidelines, pin actions for security.

🔐 Example pinning
-      - name: Checkout repository
-        uses: actions/checkout@v4
+      - name: Checkout repository
+        uses: actions/checkout@<COMMIT_SHA>

-      - name: Run Claude Code
-        uses: anthropics/claude-code-action@v1
+      - name: Run Claude Code
+        uses: anthropics/claude-code-action@<COMMIT_SHA>
🤖 Fix all issues with AI agents
In @.github/workflows/claude.yml:
- Around line 60-61: The job-level if condition in the workflow
(.github/workflows/claude.yml) needs a fork guard so PRs from forks (which don't
have access to CLAUDE_CODE_OAUTH_TOKEN) are skipped; update the job's if
(currently "github.event_name == 'pull_request' || github.event_name ==
'workflow_dispatch'") to only allow pull_request runs when the PR head repo
matches the target repo by adding a check like
github.event.pull_request.head.repo.full_name == github.repository (while still
allowing workflow_dispatch), so the job runs for same-repo PRs and
workflow_dispatch but not for forked PRs.
🧹 Nitpick comments (1)
.claude/commands/sync.md (1)

65-70: Add language identifiers to fenced code blocks (MD040).

These fences are missing a language specifier; markdownlint will fail.

Proposed fix
-   ```
+   ```text
    Documented rules: [list from reading files]
    Documented skills: [list skill names and purposes]
    Potential gaps to investigate: [areas not covered]
    Possibly outdated: [rules/skills with old content or changed workflows]
    ```
-   ```
+   ```text
    Question: "These skills need updates. Which should I update?"
    Header: "Skill Updates"
    multiSelect: true
    Options:
    - "[skill-name]" - [What changed and why]
    - "[skill-name]" - [What changed and why]
    - "None" - Skip skill updates
    ```
-   ```
+   ```text
    Question: "Here's the updated [skill-name]. Apply changes?"
    Header: "Confirm Update"
    Options:
    - "Yes, update it"
    - "Edit first"
    - "Skip this one"
    ```
-   ```
+   ```text
    Question: "[skill-name] appears obsolete. Remove it?"
    Header: "Remove Skill"
    Options:
    - "Yes, remove it"
    - "Keep it" - Still useful
    - "Update instead" - Workflow changed but still needed
    ```
-```
+```text
 Skill(skill="learn")
</details>


Also applies to: 260-268, 277-285, 291-299, 425-427

</blockquote></details>

</blockquote></details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->

Comment on lines +60 to +61
if: github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

wc -l .github/workflows/claude.yml

Repository: maxritter/claude-codepro

Length of output: 99


🏁 Script executed:

cat -n .github/workflows/claude.yml

Repository: maxritter/claude-codepro

Length of output: 3169


Skip auto-review for forked PRs (or handle missing secrets).

Pull requests from forks won't receive CLAUDE_CODE_OAUTH_TOKEN due to GitHub's security model restricting secret access to untrusted sources. The job will fail when attempting to use the token. Add a fork guard to only run for PRs from the same repository:

✅ Suggested guard
-  claude-review:
-    if: github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch'
+  claude-review:
+    if: |
+      github.event_name == 'workflow_dispatch' ||
+      (github.event_name == 'pull_request' &&
+       github.event.pull_request.head.repo.full_name == github.repository)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if: github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
if: |
github.event_name == 'workflow_dispatch' ||
(github.event_name == 'pull_request' &&
github.event.pull_request.head.repo.full_name == github.repository)
runs-on: ubuntu-latest
🤖 Prompt for AI Agents
In @.github/workflows/claude.yml around lines 60 - 61, The job-level if
condition in the workflow (.github/workflows/claude.yml) needs a fork guard so
PRs from forks (which don't have access to CLAUDE_CODE_OAUTH_TOKEN) are skipped;
update the job's if (currently "github.event_name == 'pull_request' ||
github.event_name == 'workflow_dispatch'") to only allow pull_request runs when
the PR head repo matches the target repo by adding a check like
github.event.pull_request.head.repo.full_name == github.repository (while still
allowing workflow_dispatch), so the job runs for same-repo PRs and
workflow_dispatch but not for forked PRs.

- Add "repeatable workflow" trigger to /learn system
- Fix skill name typos (accessability, standards-cc)
- Update /plugin/ path check to /ccp/ in installer
- Remove flaky ANSI test in statusline formatter
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
installer/steps/claude_files.py (1)

25-40: Add space-prefixed replacement for .claude/ccp to match bin path handling.

Line 37 handles space-prefixed BIN_PATH_PATTERN, but line 39 only handles quoted PLUGIN_PATH_PATTERN. If a command embeds the plugin path after a space (e.g., "exec .claude/ccp script"), it won't be patched to the absolute path and may break when hooks run outside the project root. Installer code should handle both prefixes consistently for robustness.

🐛 Proposed fix
 content = content.replace(" " + BIN_PATH_PATTERN, " " + abs_bin_path)
 content = content.replace('"' + BIN_PATH_PATTERN, '"' + abs_bin_path)
+content = content.replace(" " + PLUGIN_PATH_PATTERN, " " + abs_plugin_path)
 content = content.replace('"' + PLUGIN_PATH_PATTERN, '"' + abs_plugin_path)
🤖 Fix all issues with AI agents
In @.claude/ccp/hooks/learn_reminder.py:
- Around line 30-35: STATE_FILE.write_text(...) can raise OSError and isn’t
currently caught; wrap the write in a try/except around the
STATE_FILE.write_text(str(now)) call (inside the same function containing the
read handling) to catch OSError (or Exception) and log a warning to stderr or
the existing logger, then continue to return 2 so the hook doesn't crash when
persistence fails; reference STATE_FILE and the write_text call when locating
where to add the try/except.
🧹 Nitpick comments (1)
installer/tests/unit/steps/test_claude_files.py (1)

33-64: Consider covering space‑prefixed .claude/ccp strings in patch tests.
Current cases verify quoted prefixes only; if you add the space-prefixed replacement, add a test like "node .claude/ccp/scripts/..." to prevent regressions.

Comment on lines +30 to +35
STATE_FILE.write_text(str(now))
print(
"🧠 EVALUATE: Non-obvious solution OR repeatable workflow? → Invoke Skill(learn)",
file=sys.stderr,
)
return 2
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Unhandled write failure could crash the hook.

The read operation correctly handles OSError, but write_text() on line 30 can also raise OSError (permission denied, disk full, etc.), causing an unhandled exception and traceback. Consider wrapping the write in a try/except to gracefully continue even if state persistence fails.

🔧 Proposed fix
-    STATE_FILE.write_text(str(now))
+    try:
+        STATE_FILE.write_text(str(now))
+    except OSError:
+        pass  # Best-effort state persistence; reminder still shows
     print(
         "🧠 EVALUATE: Non-obvious solution OR repeatable workflow? → Invoke Skill(learn)",
         file=sys.stderr,
     )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
STATE_FILE.write_text(str(now))
print(
"🧠 EVALUATE: Non-obvious solution OR repeatable workflow? → Invoke Skill(learn)",
file=sys.stderr,
)
return 2
try:
STATE_FILE.write_text(str(now))
except OSError:
pass # Best-effort state persistence; reminder still shows
print(
"🧠 EVALUATE: Non-obvious solution OR repeatable workflow? → Invoke Skill(learn)",
file=sys.stderr,
)
return 2
🤖 Prompt for AI Agents
In @.claude/ccp/hooks/learn_reminder.py around lines 30 - 35,
STATE_FILE.write_text(...) can raise OSError and isn’t currently caught; wrap
the write in a try/except around the STATE_FILE.write_text(str(now)) call
(inside the same function containing the read handling) to catch OSError (or
Exception) and log a warning to stderr or the existing logger, then continue to
return 2 so the hook doesn't crash when persistence fails; reference STATE_FILE
and the write_text call when locating where to add the try/except.

- Add 60s cooldown escape hatch to spec_stop_guard hook
- Only show shell reload prompt when config actually changed
- Skip vtsls install if already installed globally
- Skip vexor local model download if already cached
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
installer/steps/shell_config.py (1)

291-296: Alias updates don't trigger reload prompt despite function body changes.

In the commit "Only show shell reload prompt when config actually changed," needs_reload is set to True only for new aliases (line 296), not for updated ones. However, the alias function was modified in this PR to add devcontainer support (searching /workspaces for CCP projects). Existing users who run the installer again will see "Updated alias" but won't be prompted to reload, causing them to continue using the old function definition.

Either set needs_reload = True when updating an alias, or add a note to the "Updated alias" message suggesting a manual reload.

🧹 Nitpick comments (3)
.claude/ccp/hooks/spec_stop_guard.py (2)

23-24: Consider using tempfile.gettempdir() for portability.

While hardcoding /tmp works on Linux/macOS, using tempfile.gettempdir() ensures cross-platform compatibility (e.g., Windows uses %TEMP%).

♻️ Suggested improvement
+import tempfile
+
-STATE_FILE = Path("/tmp/claude-spec-stop-guard")
+STATE_FILE = Path(tempfile.gettempdir()) / "claude-spec-stop-guard"

152-152: Remove unnecessary f-string prefix.

This string has no placeholders, so the f prefix is extraneous.

♻️ Fix
-    print(f"Continue the workflow or use AskUserQuestion if user input is needed.", file=sys.stderr)
+    print("Continue the workflow or use AskUserQuestion if user input is needed.", file=sys.stderr)
installer/steps/dependencies.py (1)

401-412: Harden vtsls detection for missing npm and narrower exceptions
Add a fast command_exists("npm") guard (handles Windows npm.cmd) and avoid catching Exception.

♻️ Proposed refactor
 def _is_vtsls_installed() -> bool:
     """Check if vtsls is already installed globally."""
+    if not command_exists("npm"):
+        return False
     try:
         result = subprocess.run(
             ["npm", "list", "-g", "@vtsls/language-server"],
             capture_output=True,
             text=True,
         )
         return result.returncode == 0 and "@vtsls/language-server" in result.stdout
-    except Exception:
+    except (FileNotFoundError, OSError):
         return False

As per coding guidelines, please ensure cross‑platform compatibility.

- Hook now instructs Claude to output nothing if no learning needed
- Updated learn.md rule with clear decision tree
- Added Online Learning feature to README and website
- Simplified hook message for clarity
- Removed learn_reminder.py stop hook (was blocking and forcing responses)
- Added /learn check to context_monitor at 80%+ threshold
- Updated learn.md rule to reflect context warning trigger
- Removed learn_reminder tests (no longer needed)
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In @.claude/ccp/hooks/hooks.json:
- Around line 103-106: The Stop hook entry that runs "uv run python
\"${CLAUDE_PLUGIN_ROOT}/hooks/spec_stop_guard.py\"" is missing a timeout and can
hang; update the hook object for spec_stop_guard.py to include the same timeout
property/value used by adjacent Stop hooks (add a "timeout" field to this
command entry) so the hook will be bounded like the others.

In @.claude/rules/standard/learn.md:
- Line 36: The fenced code block in the markdown currently has no language
identifier (a bare ``` fence); update that fence to include a language specifier
by changing the opening triple backticks to use "text" (i.e., replace the
existing ``` with ```text) so the block complies with MD040 and remains
semantically neutral.

Comment on lines +103 to +106
{
"type": "command",
"command": "uv run python \"${CLAUDE_PLUGIN_ROOT}/hooks/spec_stop_guard.py\""
},
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Add a timeout to prevent Stop hook hangs.

The new command lacks a timeout while adjacent Stop hooks enforce timeouts. If spec_stop_guard.py stalls, Stop can block indefinitely. Consider adding a timeout consistent with other hooks.

💡 Proposed fix
           {
             "type": "command",
-            "command": "uv run python \"${CLAUDE_PLUGIN_ROOT}/hooks/spec_stop_guard.py\""
+            "command": "uv run python \"${CLAUDE_PLUGIN_ROOT}/hooks/spec_stop_guard.py\"",
+            "timeout": 60
           },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{
"type": "command",
"command": "uv run python \"${CLAUDE_PLUGIN_ROOT}/hooks/spec_stop_guard.py\""
},
{
"type": "command",
"command": "uv run python \"${CLAUDE_PLUGIN_ROOT}/hooks/spec_stop_guard.py\"",
"timeout": 60
},
🤖 Prompt for AI Agents
In @.claude/ccp/hooks/hooks.json around lines 103 - 106, The Stop hook entry
that runs "uv run python \"${CLAUDE_PLUGIN_ROOT}/hooks/spec_stop_guard.py\"" is
missing a timeout and can hang; update the hook object for spec_stop_guard.py to
include the same timeout property/value used by adjacent Stop hooks (add a
"timeout" field to this command entry) so the hook will be bounded like the
others.


### Quick Decision Tree

```
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Add a language to the fenced code block.

MD040 requires a language identifier. Use text to keep semantics neutral.

✅ Suggested fix
-```
+```text
🧰 Tools
🪛 markdownlint-cli2 (0.18.1)

36-36: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
In @.claude/rules/standard/learn.md at line 36, The fenced code block in the
markdown currently has no language identifier (a bare ``` fence); update that
fence to include a language specifier by changing the opening triple backticks
to use "text" (i.e., replace the existing ``` with ```text) so the block
complies with MD040 and remains semantically neutral.

@maxritter maxritter merged commit 99c37e1 into main Jan 27, 2026
8 of 9 checks passed
@github-actions
Copy link
Copy Markdown
Contributor

🎉 This PR is included in version 5.4.0 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant