Skip to content

Commit 522a95c

Browse files
committed
update adding claude.md
1 parent d97fea8 commit 522a95c

11 files changed

Lines changed: 908 additions & 47 deletions

File tree

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#!/bin/sh
2+
if grep -q "caliber" .git/hooks/pre-commit 2>/dev/null; then
3+
exit 0
4+
fi
5+
FLAG="/tmp/caliber-nudge-$(echo "$PWD" | shasum | cut -c1-8)"
6+
find /tmp -maxdepth 1 -name "caliber-nudge-*" -mmin +120 -delete 2>/dev/null
7+
if [ -f "$FLAG" ]; then
8+
exit 0
9+
fi
10+
touch "$FLAG"
11+
printf '{"decision":"block","reason":"Caliber agent config sync is not set up on this machine. Tell the user: This project uses Caliber to keep agent configs in sync — it is not set up yet. Want me to run /setup-caliber? (~30 seconds)"}'

.claude/settings.json

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
{
2+
"hooks": {
3+
"Stop": [
4+
{
5+
"matcher": "",
6+
"hooks": [
7+
{
8+
"type": "command",
9+
"command": ".claude/hooks/caliber-check-sync.sh",
10+
"description": "Caliber: offer setup if not configured"
11+
}
12+
]
13+
}
14+
],
15+
"PostToolUse": [
16+
{
17+
"matcher": "",
18+
"hooks": [
19+
{
20+
"type": "command",
21+
"command": "caliber learn observe",
22+
"description": "Caliber: recording tool usage for session learning"
23+
}
24+
]
25+
}
26+
],
27+
"PostToolUseFailure": [
28+
{
29+
"matcher": "",
30+
"hooks": [
31+
{
32+
"type": "command",
33+
"command": "caliber learn observe --failure",
34+
"description": "Caliber: recording tool failure for session learning"
35+
}
36+
]
37+
}
38+
],
39+
"UserPromptSubmit": [
40+
{
41+
"matcher": "",
42+
"hooks": [
43+
{
44+
"type": "command",
45+
"command": "caliber learn observe --prompt",
46+
"description": "Caliber: recording user prompt for correction detection"
47+
}
48+
]
49+
}
50+
],
51+
"SessionEnd": [
52+
{
53+
"matcher": "",
54+
"hooks": [
55+
{
56+
"type": "command",
57+
"command": "caliber learn finalize --auto",
58+
"description": "Caliber: finalizing session learnings"
59+
}
60+
]
61+
},
62+
{
63+
"matcher": "",
64+
"hooks": [
65+
{
66+
"type": "command",
67+
"command": "caliber refresh --quiet",
68+
"description": "Caliber: auto-refreshing docs based on code changes"
69+
}
70+
]
71+
}
72+
]
73+
},
74+
"permissions": {
75+
"allow": [
76+
"Bash(git *)"
77+
]
78+
}
79+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
---
2+
name: find-skills
3+
description: Discovers and installs community skills from the public registry. Use when the user mentions a technology, framework, or task that could benefit from specialized skills not yet installed, asks 'how do I do X', 'find a skill for X', or starts work in a new technology area. Proactively suggest when the user's task involves tools or frameworks without existing skills.
4+
---
5+
6+
# Find Skills
7+
8+
Search the public skill registry for community-contributed skills
9+
relevant to the user's current task and install them into this project.
10+
11+
## Instructions
12+
13+
1. Identify the key technologies, frameworks, or task types from the
14+
user's request that might have community skills available
15+
2. Ask the user: "Would you like me to search for community skills
16+
for [identified technologies]?"
17+
3. If the user agrees, run:
18+
```bash
19+
caliber skills --query "<relevant terms>"
20+
```
21+
This outputs the top 5 matching skills with scores and descriptions.
22+
4. Present the results to the user and ask which ones to install
23+
5. Install the selected skills:
24+
```bash
25+
caliber skills --install <slug1>,<slug2>
26+
```
27+
6. Read the installed SKILL.md files to load them into your current
28+
context so you can use them immediately in this session
29+
7. Summarize what was installed and continue with the user's task
30+
31+
## Examples
32+
33+
User: "let's build a web app using React"
34+
-> "I notice you want to work with React. Would you like me to search
35+
for community skills that could help with React development?"
36+
-> If yes: run `caliber skills --query "react frontend"`
37+
-> Show the user the results, ask which to install
38+
-> Run `caliber skills --install <selected-slugs>`
39+
-> Read the installed files and continue
40+
41+
User: "help me set up Docker for this project"
42+
-> "Would you like me to search for Docker-related skills?"
43+
-> If yes: run `caliber skills --query "docker deployment"`
44+
45+
User: "I need to write tests for this Python ML pipeline"
46+
-> "Would you like me to find skills for Python ML testing?"
47+
-> If yes: run `caliber skills --query "python machine-learning testing"`
48+
49+
## When NOT to trigger
50+
51+
- The user is working within an already well-configured area
52+
- You already suggested skills for this technology in this session
53+
- The user is in the middle of urgent debugging or time-sensitive work
54+
- The technology is too generic (e.g. just "code" or "programming")
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
---
2+
name: phpunit-stub-test
3+
description: Adds PHPUnit test cases for this plugin using the stub pattern from tests/stubs.php and tests/bootstrap.php. Use when user says 'add test', 'write tests', 'test this function', or needs coverage for src/Plugin.php or src/whmsonic.inc.php. Adds global function stubs under namespace Detain\MyAdminWhmsonic using function_exists guards, then writes test class under Detain\MyAdminWhmsonic\Tests namespace. Do NOT use for PHPUnit mocks of MyAdmin global functions or for integration tests requiring a live WHMSonic API.
4+
---
5+
# PHPUnit Stub Test
6+
7+
## Critical
8+
9+
- **Never use PHPUnit mocks for MyAdmin global functions** (`myadmin_log`, `get_service_define`, `function_requirements`, `get_module_settings`, `_`). Always add namespace-level stubs to `tests/stubs.php` instead.
10+
- All stubs must live inside `namespace Detain\MyAdminWhmsonic { }` in `tests/stubs.php` and be guarded with `function_exists('Detain\\MyAdminWhmsonic\\func_name')`.
11+
- Do NOT make live cURL calls in tests. For `src/whmsonic.inc.php` functions, test parameter signatures and source analysis via `ReflectionFunction` — not execution.
12+
- Constants `WHMSONIC_USERNAME` / `WHMSONIC_PASSWORD` must be defined in `setUpBeforeClass()` with `if (!defined(...))` guards before `require_once`-ing `src/whmsonic.inc.php`.
13+
14+
## Instructions
15+
16+
1. **Identify what global functions the new code calls.** Read the target source file to find any calls to `myadmin_log`, `get_service_define`, `function_requirements`, `get_module_settings`, `_`, or any `whmsonic_*` function. Verify each against existing stubs in `tests/stubs.php` before proceeding.
17+
18+
2. **Add missing stubs to `tests/stubs.php`.** For each missing global function, append inside the `namespace Detain\MyAdminWhmsonic { }` block:
19+
```php
20+
if (!function_exists('Detain\\MyAdminWhmsonic\\my_new_func')) {
21+
function my_new_func(...$args): void {}
22+
}
23+
```
24+
Return a typed value (e.g., `string`, `array`) only if the calling code uses the return value. Verify `tests/bootstrap.php` still loads `stubs.php` before `vendor/autoload.php` — do not modify bootstrap unless a new require is needed.
25+
26+
3. **Create the test class file** under `tests/` with filename `MyFeatureTest.php`. Use this exact header:
27+
```php
28+
<?php
29+
declare(strict_types=1);
30+
namespace Detain\MyAdminWhmsonic\Tests;
31+
use PHPUnit\Framework\TestCase;
32+
use ReflectionClass; // for Plugin tests
33+
use ReflectionFunction; // for whmsonic.inc.php function tests
34+
use Symfony\Component\EventDispatcher\GenericEvent; // for event handler tests
35+
```
36+
37+
4. **For `src/Plugin.php` tests:** Use `ReflectionClass` for structural assertions. Fire event handlers by constructing `new GenericEvent($subject, $args)` where `$subject` is an anonymous class with the required methods (`getId()`, `getIp()`):
38+
```php
39+
$serviceClass = new class {
40+
public function getId(): int { return 42; }
41+
public function getIp(): string { return '10.0.0.1'; }
42+
};
43+
$event = new GenericEvent($serviceClass, ['category' => 'WHMSONIC_TYPE']);
44+
Plugin::getActivate($event);
45+
$this->assertTrue($event->isPropagationStopped());
46+
```
47+
Assert `isPropagationStopped()` for matching category and `assertFalse` for non-matching category.
48+
49+
5. **For `src/whmsonic.inc.php` tests:** Define constants and `require_once` the file in `setUpBeforeClass()`, then use `ReflectionFunction` for parameter count/name assertions and `file_get_contents(dirname(__DIR__).'/src/whmsonic.inc.php')` + `assertStringContainsString` for API command verification. Do not call the actual functions.
50+
51+
6. **Run tests:** `vendor/bin/phpunit` from the package root. All tests must pass before considering work complete.
52+
53+
## Examples
54+
55+
**User says:** "Add a test for the new `whmsonic_reboot` function in `src/whmsonic.inc.php`."
56+
57+
**Actions taken:**
58+
1. Read `src/whmsonic.inc.php` — find `whmsonic_reboot($licenseip)` calls `myadmin_log` and `curl_*`.
59+
2. `myadmin_log` stub already exists in `tests/stubs.php` — no change needed.
60+
3. Create `tests/WhmsonicRebootTest.php`:
61+
```php
62+
<?php
63+
declare(strict_types=1);
64+
namespace Detain\MyAdminWhmsonic\Tests;
65+
use PHPUnit\Framework\TestCase;
66+
use ReflectionFunction;
67+
class WhmsonicRebootTest extends TestCase {
68+
public static function setUpBeforeClass(): void {
69+
if (!defined('WHMSONIC_USERNAME')) define('WHMSONIC_USERNAME', 'test_user');
70+
if (!defined('WHMSONIC_PASSWORD')) define('WHMSONIC_PASSWORD', 'test_pass');
71+
require_once dirname(__DIR__) . '/src/whmsonic.inc.php';
72+
}
73+
public function testWhmsonicRebootExists(): void {
74+
$this->assertTrue(function_exists('whmsonic_reboot'));
75+
}
76+
public function testWhmsonicRebootParameterName(): void {
77+
$ref = new ReflectionFunction('whmsonic_reboot');
78+
$this->assertSame('licenseip', $ref->getParameters()[0]->getName());
79+
}
80+
public function testWhmsonicRebootUsesRebootCommand(): void {
81+
$ref = new ReflectionFunction('whmsonic_reboot');
82+
$lines = array_slice(explode("\n", file_get_contents($ref->getFileName())),
83+
$ref->getStartLine() - 1, $ref->getEndLine() - $ref->getStartLine() + 1);
84+
$this->assertStringContainsString('cmd=reboot', implode("\n", $lines));
85+
}
86+
}
87+
```
88+
4. Run `vendor/bin/phpunit` — all pass.
89+
90+
**Result:** New test class follows exact patterns from `WhmsonicFunctionsTest.php`, no mocks used.
91+
92+
## Common Issues
93+
94+
- **"Call to undefined function Detain\\MyAdminWhmsonic\\myadmin_log"**: The stub is missing or in the wrong namespace block. Open `tests/stubs.php` and confirm the function is declared inside `namespace Detain\MyAdminWhmsonic { }`, not global namespace.
95+
- **"Cannot redeclare function"**: You called `require_once src/whmsonic.inc.php` more than once across test classes in the same process. Add `if (function_exists('activate_whmsonic')) return;` at the top of the include, or use `require_once` consistently — never `require`.
96+
- **"Constant WHMSONIC_USERNAME already defined"**: Always guard with `if (!defined('WHMSONIC_USERNAME'))` in `setUpBeforeClass()`.
97+
- **Tests pass locally but fail in CI (function not found)**: `tests/bootstrap.php` must load `stubs.php` before `vendor/autoload.php`. Confirm line order: `require_once __DIR__ . '/stubs.php';` then `require_once dirname(__DIR__) . '/vendor/autoload.php';`.
98+
- **`$event->isPropagationStopped()` returns false unexpectedly**: The anonymous `$subject` class is missing a method called by the handler (e.g., `getId()`). Read `src/Plugin.php` to check what methods the handler calls on `$event->getSubject()`.
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
---
2+
name: plugin-event-handler
3+
description: Adds a new Symfony GenericEvent hook handler to src/Plugin.php following the existing pattern. Use when user says 'add hook', 'new event handler', 'handle event', 'add plugin action', or needs to wire a new lifecycle event (activate, suspend, terminate, change IP, menu). Registers in getHooks() and implements the handler with category check and stopPropagation(). Do NOT use for adding procedural API functions to src/whmsonic.inc.php.
4+
---
5+
# plugin-event-handler
6+
7+
## Critical
8+
9+
- All handler methods MUST be `public static function` — never instance methods
10+
- ALWAYS guard with `if ($event['category'] == get_service_define('WHMSONIC'))` before acting, then call `$event->stopPropagation()` as the last line inside the guard
11+
- NEVER skip `$event->stopPropagation()` — other plugins share the same event; missing it causes double-execution
12+
- Log every handler entry with the 7-arg form: `myadmin_log(self::$module, 'info', 'message', __LINE__, __FILE__, self::$module, $serviceClass->getId())`
13+
- NEVER commit `WHMSONIC_USERNAME` / `WHMSONIC_PASSWORD` literals — they are runtime constants
14+
15+
## Instructions
16+
17+
1. **Choose the event name.** Common patterns already in the codebase:
18+
- `self::$module.'.activate'` / `self::$module.'.reactivate'`
19+
- `self::$module.'.suspend'` / `self::$module.'.unsuspend'`
20+
- `self::$module.'.terminate'`
21+
- `self::$module.'.change_ip'`
22+
- `'menu'` (no module prefix)
23+
Verify the event name is not already registered in `getHooks()` before adding.
24+
25+
2. **Register in `getHooks()`.** Open `src/Plugin.php` and add an entry to the returned array:
26+
```php
27+
self::$module.'.suspend' => [__CLASS__, 'getSuspend'],
28+
```
29+
Use `[__CLASS__, 'MethodName']` — never a closure or string.
30+
31+
3. **Implement the handler method** directly below the last existing handler, following this exact skeleton:
32+
```php
33+
/**
34+
* @param \Symfony\Component\EventDispatcher\GenericEvent $event
35+
*/
36+
public static function getSuspend(GenericEvent $event)
37+
{
38+
if ($event['category'] == get_service_define('WHMSONIC')) {
39+
$serviceClass = $event->getSubject();
40+
myadmin_log(self::$module, 'info', 'WHMSonic Suspend', __LINE__, __FILE__, self::$module, $serviceClass->getId());
41+
function_requirements('whmsonic_suspend');
42+
whmsonic_suspend($serviceClass->getIp(), $serviceClass->getId());
43+
$event->stopPropagation();
44+
}
45+
}
46+
```
47+
- `$event->getSubject()` returns the service object; call `getId()`, `getIp()`, `getCustid()` as needed
48+
- Use `function_requirements('whmsonic_fn_name')` to lazy-load before calling any `src/whmsonic.inc.php` function
49+
- For error results, set `$event['status'] = 'error'` and `$event['status_text'] = '...'` before `stopPropagation()`
50+
51+
4. **Verify the method signature** matches every other handler: one parameter typed `GenericEvent $event`, no return type declared (existing handlers omit it).
52+
53+
5. **Run tests** to confirm nothing is broken:
54+
```bash
55+
vendor/bin/phpunit
56+
```
57+
The existing `PluginTest::testGetHooksMethodsExist` will fail if the method name in `getHooks()` doesn't match the actual method — fix the typo if it does.
58+
59+
## Examples
60+
61+
**User says:** "Add a suspend event handler"
62+
63+
**Actions taken:**
64+
1. Add to `getHooks()` in `src/Plugin.php`:
65+
```php
66+
self::$module.'.suspend' => [__CLASS__, 'getSuspend'],
67+
```
68+
2. Add method to `src/Plugin.php`:
69+
```php
70+
public static function getSuspend(GenericEvent $event)
71+
{
72+
if ($event['category'] == get_service_define('WHMSONIC')) {
73+
$serviceClass = $event->getSubject();
74+
myadmin_log(self::$module, 'info', 'WHMSonic Suspend', __LINE__, __FILE__, self::$module, $serviceClass->getId());
75+
function_requirements('whmsonic_suspend');
76+
whmsonic_suspend($serviceClass->getIp(), $serviceClass->getId());
77+
$event->stopPropagation();
78+
}
79+
}
80+
```
81+
3. Run `vendor/bin/phpunit` — all tests pass.
82+
83+
**Result:** Suspend lifecycle event is now handled; other plugins' suspend handlers are not invoked for WHMSONIC services.
84+
85+
## Common Issues
86+
87+
- **`testGetHooksMethodsExist` fails:** The method name string in `getHooks()` doesn't match the actual method name. Check spelling — e.g. `'getSuspend'` vs `'getSuspended'`.
88+
- **Handler fires but also triggers another plugin's handler:** `$event->stopPropagation()` is missing or placed outside the `if` guard. It must be the last statement *inside* the `if` block.
89+
- **`function_requirements` call fails / function not found:** The function name passed must match exactly what's registered in `getRequirements()` via `$loader->add_requirement()` or `$loader->add_page_requirement()`. Check `src/Plugin.php::getRequirements()` for the registered names.
90+
- **`get_service_define('WHMSONIC')` returns null in tests:** The stub for `get_service_define` in `tests/stubs.php` may not cover the `WHMSONIC` key. Add a guard stub there:
91+
```php
92+
if (!function_exists('Detain\\MyAdminWhmsonic\\get_service_define')) {
93+
function get_service_define(string $key): string { return $key.'_TYPE'; }
94+
}
95+
```
96+
- **Test for `stopPropagation` fails on mismatch case:** The `if` guard is missing — the handler is calling `stopPropagation()` unconditionally instead of only when `$event['category']` matches.

0 commit comments

Comments
 (0)