Skip to content

Commit d64979f

Browse files
committed
update adding claude.md
1 parent a29f3fa commit d64979f

11 files changed

Lines changed: 869 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: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
---
2+
name: inc-function
3+
description: Adds a new procedural function to `src/parallels.inc.php` wrapping a `\Detain\Parallels\Parallels` KA API method with the standard `myadmin_log` + `request_log` + try/catch pattern. Use when user says 'add function', 'new API call', 'call parallels method', or needs to expose a KA API method. Do NOT use for Plugin.php static event handlers or bin/ scripts.
4+
---
5+
# inc-function
6+
7+
## Critical
8+
9+
- **No type hints on parameters, no return type declarations** — existing functions are untyped legacy PHP style; `ReflectionFunction` tests enforce this.
10+
- **No PHP namespace declaration** in `src/parallels.inc.php` — it is a plain procedural include file.
11+
- **Always catch `\XML_RPC2_CurlException`** (not `\Exception`) and return `false` on failure — this is the only exception the KA API client throws.
12+
- **Always call `request_log()`** for both success and failure paths with identical `'licenses'` module and `'parallels'` service arguments.
13+
- The `use \Detain\Parallels\Parallels;` statement already exists at the top of the file — do not add a second one.
14+
15+
## Instructions
16+
17+
1. **Identify the KA API method** to wrap. Check `\Detain\Parallels\Parallels` for the exact method name and signature (e.g., `getKeyInfo($keyNumber)`, `retrieveKey($keyNumber)`, `getAvailableUpgrades($keyNumber)`).
18+
19+
2. **Add the PHPDoc block** immediately before the function:
20+
```php
21+
/**
22+
* parallels_<action>()
23+
* @param mixed $param1
24+
* @return mixed
25+
*/
26+
```
27+
Use `@param mixed` — no concrete types. Verify the block is present before proceeding.
28+
29+
3. **Write the function signature** — no type hints, no return type:
30+
```php
31+
function parallels_<action>($param1)
32+
```
33+
Name must be `parallels_` prefixed + snake_case action (e.g., `parallels_get_key_info`).
34+
35+
4. **Open with `myadmin_log`** describing the call:
36+
```php
37+
myadmin_log('licenses', 'info', "Parallels <Action> ({$param1})", __LINE__, __FILE__);
38+
```
39+
40+
5. **Instantiate the client and build `$request`:**
41+
```php
42+
$parallels = new \Detain\Parallels\Parallels();
43+
$request = [$param1]; // mirror the args passed to the API method
44+
```
45+
46+
6. **Wrap the API call in try/catch:**
47+
```php
48+
try {
49+
$response = $parallels->apiMethodName($param1);
50+
request_log('licenses', false, __FUNCTION__, 'parallels', 'apiMethodName', $request, $response);
51+
myadmin_log('licenses', 'info', "parallels_<action>({$param1}) Response: ".json_encode($response), __LINE__, __FILE__);
52+
} catch (\XML_RPC2_CurlException $e) {
53+
request_log('licenses', false, __FUNCTION__, 'parallels', 'apiMethodName', $request, $e->getMessage());
54+
return false;
55+
}
56+
return $response;
57+
```
58+
Verify `request_log()` is called in **both** the try and catch blocks before proceeding.
59+
60+
7. **Register the function in `tests/bootstrap.php`** only if the function calls any new MyAdmin globals not already stubbed. Check existing stubs: `myadmin_log`, `get_service_define`, `function_requirements`, `request_log`, `get_module_settings`.
61+
62+
8. **Add a test** in `tests/ParallelsIncTest.php` following the existing `testActivateParallelsFunctionExists` pattern — at minimum assert `function_exists('parallels_<action>')`.
63+
64+
9. **Run tests** to confirm nothing broke:
65+
```bash
66+
vendor/bin/phpunit tests/ParallelsIncTest.php
67+
```
68+
69+
## Examples
70+
71+
**User says:** "Add a function that calls `getKeyInfo` on a key number"
72+
73+
**Actions taken:**
74+
1. Confirm `\Detain\Parallels\Parallels::getKeyInfo($keyNumber)` exists in the client.
75+
2. Append to `src/parallels.inc.php`:
76+
77+
```php
78+
/**
79+
* parallels_get_key_info()
80+
* @param mixed $keyNumber
81+
* @return mixed
82+
*/
83+
function parallels_get_key_info($keyNumber)
84+
{
85+
myadmin_log('licenses', 'info', "Parallels Get Key Info ({$keyNumber})", __LINE__, __FILE__);
86+
$parallels = new \Detain\Parallels\Parallels();
87+
$request = [$keyNumber];
88+
try {
89+
$response = $parallels->getKeyInfo($keyNumber);
90+
request_log('licenses', false, __FUNCTION__, 'parallels', 'getKeyInfo', $request, $response);
91+
myadmin_log('licenses', 'info', "parallels_get_key_info({$keyNumber}) Response: ".json_encode($response), __LINE__, __FILE__);
92+
} catch (\XML_RPC2_CurlException $e) {
93+
request_log('licenses', false, __FUNCTION__, 'parallels', 'getKeyInfo', $request, $e->getMessage());
94+
return false;
95+
}
96+
return $response;
97+
}
98+
```
99+
100+
3. Add `testParallelsGetKeyInfoFunctionExists` to `tests/ParallelsIncTest.php`.
101+
4. Run `vendor/bin/phpunit tests/ParallelsIncTest.php` — all green.
102+
103+
**Result:** New function appears in `src/parallels.inc.php`, test passes.
104+
105+
## Common Issues
106+
107+
- **`Call to undefined function myadmin_log()`** during tests: add `function myadmin_log() {}` stub to `tests/bootstrap.php` — it must be defined before `require_once` of the inc file.
108+
- **`Call to undefined function request_log()`** during tests: same fix — add `function request_log() {}` to `tests/bootstrap.php`.
109+
- **`Class 'XML_RPC2_CurlException' not found`** at runtime: the class lives in the `detain/parallels-licensing` package; run `composer install` to ensure it is present.
110+
- **Test fails with `Function parallels_<action>() should be defined`**: the function name in the `function_exists()` assertion must exactly match the PHP function name including the `parallels_` prefix.
111+
- **Duplicate `use` statement error**: `use \Detain\Parallels\Parallels;` is already at line 10 of `src/parallels.inc.php`; never add it again.
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
---
2+
name: plugin-hook
3+
description: Adds a new event hook to src/Plugin.php — registers in getHooks() and creates the static GenericEvent handler. Use when user says 'add hook', 'handle event', 'new plugin event', or adds a lifecycle action (activate/deactivate/change IP/reactivate). Do NOT use for modifying existing handlers or adding inc functions.
4+
---
5+
# plugin-hook
6+
7+
## Critical
8+
9+
- All handler methods MUST be `public static` and accept exactly one `GenericEvent $event` parameter — `testHookMethodsArePublicAndStatic` and `testEventHandlerMethodSignatures` will fail otherwise.
10+
- Always guard the handler body with `if ($event['category'] == get_service_define('PARALLELS'))` before any logic.
11+
- Always call `$event->stopPropagation()` as the last statement inside the category guard.
12+
- Hook keys use `self::$module . '.event_name'` (e.g. `'licenses.change_ip'`) — never hardcode the module string.
13+
- The one exception is `'function.requirements'` which uses a literal key, not the module prefix.
14+
- Run `composer test` after every change — a missing method or wrong signature will break `testAllHookMethodsExist` and `testClassMethodCount`.
15+
16+
## Instructions
17+
18+
1. **Decide the event name and handler name.** Event name is snake_case (e.g. `change_ip`). Handler is PascalCase prefixed with `get` (e.g. `getChangeIp`). Verify no existing entry in `getHooks()` already covers this event.
19+
20+
2. **Register the hook in `getHooks()` inside `src/Plugin.php`.**
21+
Add one line to the returned array:
22+
```php
23+
self::$module.'.change_ip' => [__CLASS__, 'getChangeIp'],
24+
```
25+
If the new event should share an existing handler (like `reactivate``getActivate`), point to the existing method name instead of creating a new one. Verify the array key is unique before proceeding.
26+
27+
3. **Add the handler method to `src/Plugin.php`** after the last existing handler, before the closing `}`:
28+
```php
29+
/**
30+
* @param \Symfony\Component\EventDispatcher\GenericEvent $event
31+
*/
32+
public static function getChangeIp(GenericEvent $event)
33+
{
34+
if ($event['category'] == get_service_define('PARALLELS')) {
35+
$serviceClass = $event->getSubject();
36+
myadmin_log(self::$module, 'info', 'Parallels <Action Description>', __LINE__, __FILE__, self::$module, $serviceClass->getId());
37+
// ... call function_requirements() then the inc function ...
38+
$event->stopPropagation();
39+
}
40+
}
41+
```
42+
The `use Symfony\Component\EventDispatcher\GenericEvent;` import is already present at the top — do not add it again.
43+
44+
4. **If the handler calls an inc function**, add `function_requirements('your_function')` before the call and register the function path in `getRequirements()`:
45+
```php
46+
$loader->add_requirement('your_function', '/../src/parallels.inc.php');
47+
```
48+
Verify the function exists in `src/parallels.inc.php` before registering.
49+
50+
5. **Update `tests/PluginTest.php`** — add the new hook key to the `$expectedKeys` array in `testGetHooksContainsExpectedKeys()` and bump the count in `testClassMethodCount()` if a new method was added. Run `composer test` to confirm all tests pass.
51+
52+
## Examples
53+
54+
**User says:** "Add a hook for `licenses.change_ip` that logs the old and new IP"
55+
56+
**Actions taken:**
57+
58+
`getHooks()` addition in `src/Plugin.php`:
59+
```php
60+
self::$module.'.change_ip' => [__CLASS__, 'getChangeIp'],
61+
```
62+
63+
New handler method in `src/Plugin.php`:
64+
```php
65+
public static function getChangeIp(GenericEvent $event)
66+
{
67+
if ($event['category'] == get_service_define('PARALLELS')) {
68+
$serviceClass = $event->getSubject();
69+
myadmin_log(self::$module, 'info', 'IP Change - (OLD:'.$serviceClass->getIp().") (NEW:{$event['newip']})", __LINE__, __FILE__, self::$module, $serviceClass->getId());
70+
// perform IP change logic here
71+
$event['status'] = 'ok';
72+
$event['status_text'] = 'The IP Address has been changed.';
73+
$event->stopPropagation();
74+
}
75+
}
76+
```
77+
78+
**Result:** `composer test` passes; `testGetHooksContainsExpectedKeys` and `testAllHookMethodsExist` both green.
79+
80+
## Common Issues
81+
82+
- **`testClassMethodCount` fails with "Expected 7, got 8"**: You added a new handler but did not update the count assertion in `tests/PluginTest.php`. Change the `assertCount(7, ...)` to the new total.
83+
- **`testAllHookMethodsExist` fails**: The method name string in `getHooks()` does not exactly match the declared method name. Check spelling and PascalCase — `getChangeIp``getChangeIP`.
84+
- **`testEventHandlerMethodSignatures` fails with "parameter should be GenericEvent"**: The handler is missing the `GenericEvent $event` type hint, or the `use` statement is absent (it lives at the top of `src/Plugin.php` — do not remove it).
85+
- **`stopPropagation` not called**: Event will continue dispatching to other plugins and produce duplicate side-effects. Always place `$event->stopPropagation()` as the final statement inside the category guard.
86+
- **`function_requirements` not registered**: If `activate_parallels` or a new inc function is called but not registered in `getRequirements()`, it will throw a fatal error at runtime. Verify `$loader->add_requirement('fn_name', '...')` is present.

0 commit comments

Comments
 (0)