Skip to content

Commit c1a0f50

Browse files
committed
update adding claude.md
1 parent 5811ee5 commit c1a0f50

11 files changed

Lines changed: 999 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: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
---
2+
name: kayako-api-function
3+
description: Adds a new procedural API function to `src/api.php` following the project's validation-first, try/catch-per-operation pattern. Initializes Kayako SOAP config, wraps each Kayako call in its own try/catch, returns status/status_text arrays. Use when user says 'add API function', 'new ticket operation', 'create endpoint in api.php', or 'add support function'. Do NOT use for modifying `src/Plugin.php` hook registration or for class-based API work.
4+
---
5+
# kayako-api-function
6+
7+
## Critical
8+
9+
- **Never** interpolate `$_GET`/`$_POST` directly into queries — always `$db->real_escape($input)`.
10+
- **Every** Kayako SDK call must be in its own `try/catch (Exception $e)` block — one operation per block.
11+
- **Always** check `$GLOBALS['tf']->ima != 'admin'` AND `account_lid` before any mutation that touches another user's ticket.
12+
- **Always** call `function_requirements('class.kyConfig')` before the SOAP init try/catch — not inside it.
13+
- Return the `$result` array on every path — never let the function fall off the end without returning.
14+
15+
## Instructions
16+
17+
1. **Define the function** in `src/api.php` with a PHPDoc block. Use camelCase names matching existing functions (`openTicket`, `viewTicket`, `ticketPost`, `getTicketList`).
18+
19+
```php
20+
/**
21+
* One-line description.
22+
*
23+
* @param int $ticketID the ticket id
24+
* @param string $content the reply body
25+
* @return array status/status_text result
26+
*/
27+
function myNewFunction($ticketID, $content)
28+
{
29+
```
30+
31+
2. **Initialize the result array** as the first statement. Include only keys this function actually returns — do not add speculative keys.
32+
33+
```php
34+
$result = [
35+
'status' => 'incomplete',
36+
'status_text' => '',
37+
];
38+
```
39+
40+
3. **Validate all required inputs** with early-return guards before touching Kayako. Return `'Failed'` (capital F) for user-input errors.
41+
42+
```php
43+
if (!$ticketID) {
44+
$result['status'] = 'Failed';
45+
$result['status_text'] = 'Ticket Reference ID is required. Please try again!';
46+
return $result;
47+
}
48+
if (!$content) {
49+
$result['status'] = 'Failed';
50+
$result['status_text'] = 'Content is required. Please try again!';
51+
return $result;
52+
}
53+
```
54+
55+
Verify all validation guards return before proceeding to Step 4.
56+
57+
4. **Check ownership** for any read/mutate of another user's ticket (skip for creation functions):
58+
59+
```php
60+
if ($GLOBALS['tf']->ima != 'admin' &&
61+
$GLOBALS['tf']->accounts->data['account_lid'] != kyTicket::get($ticketID)->getUser()->getEmail()) {
62+
$result['status'] = 'Failed';
63+
$result['status_text'] = 'Access denied. Please try again!';
64+
myadmin_log('api', 'info', 'Denied: ' . $GLOBALS['tf']->accounts->data['account_lid'], __LINE__, __FILE__);
65+
return $result;
66+
}
67+
```
68+
69+
Wrap the ownership check itself in `try/catch``kyTicket::get()` can throw.
70+
71+
5. **Initialize Kayako SOAP** — call `function_requirements` outside the try, init inside:
72+
73+
```php
74+
function_requirements('class.kyConfig');
75+
try {
76+
kyConfig::set(new kyConfig(KAYAKO_API_URL, KAYAKO_API_KEY, KAYAKO_API_SECRET));
77+
kyConfig::get()->setDebugEnabled(false)->setTimeout(120);
78+
} catch (Exception $e) {
79+
$result['status'] = 'failed';
80+
$result['status_text'] = 'Kayako exception occurred setting config options. Please try again!';
81+
myadmin_log('api', 'info', $e->getMessage(), __LINE__, __FILE__);
82+
return $result;
83+
}
84+
```
85+
86+
6. **Wrap each subsequent Kayako operation** in its own `try/catch`. Set `'status_text'` to a human message naming the failing operation:
87+
88+
```php
89+
try {
90+
$ticket = kyTicket::get($ticketID);
91+
$user = $ticket->getUser();
92+
} catch (Exception $e) {
93+
$result['status'] = 'Failed';
94+
$result['status_text'] = 'Kayako exception occurred getting ticket detail. Please try again!';
95+
myadmin_log('api', 'info', $e->getMessage(), __LINE__, __FILE__);
96+
return $result;
97+
}
98+
try {
99+
$post = $ticket->newPost($user, $content)->create();
100+
if ($post) {
101+
$result['status'] = 'Success';
102+
$result['status_text'] = 'Post added successfully';
103+
} else {
104+
$result['status'] = 'Failed';
105+
$result['status_text'] = 'Exception occurred adding post.';
106+
}
107+
return $result;
108+
} catch (Exception $e) {
109+
$result['status'] = 'Failed';
110+
$result['status_text'] = 'Kayako exception occurred adding post. Please try again!';
111+
myadmin_log('api', 'info', $e->getMessage(), __LINE__, __FILE__);
112+
return $result;
113+
}
114+
}
115+
```
116+
117+
7. **For DB-only functions** (no Kayako, e.g. listing from `swtickets`), use `clone $GLOBALS['helpdesk_dbh']` and always escape:
118+
119+
```php
120+
$db = clone $GLOBALS['helpdesk_dbh'];
121+
$db->query("SELECT * FROM swtickets WHERE ticketmaskid = '" . $db->real_escape($ticketID) . "'", __LINE__, __FILE__);
122+
```
123+
124+
8. **Run tests** to verify the function exists and its signature is correct:
125+
126+
```bash
127+
vendor/bin/phpunit tests/ApiFunctionsTest.php
128+
```
129+
130+
Add tests to `tests/ApiFunctionsTest.php` following the `ReflectionFunction` pattern: verify function exists, parameter count, parameter names, and validation-failure return shape.
131+
132+
## Examples
133+
134+
**User says:** "Add a function to close a ticket by ID."
135+
136+
**Actions taken:**
137+
1. Add `closeTicket($ticketID)` to `src/api.php`
138+
2. `$result = ['status' => 'incomplete', 'status_text' => '']`
139+
3. Guard: `if (!$ticketID)` → return `'Failed'`
140+
4. Ownership check in try/catch
141+
5. SOAP init block
142+
6. `try { kyTicket::get($ticketID)->close()->save(); $result['status'] = 'Success'; ... return $result; } catch ...`
143+
7. Add `testCloseTicketFunctionExists`, `testCloseTicketSignature`, `testCloseTicketFailsWithEmptyId` to `tests/ApiFunctionsTest.php`
144+
8. Run `vendor/bin/phpunit tests/ApiFunctionsTest.php`
145+
146+
**Result:** New function in `src/api.php`, matching shape of `ticketPost`; tests green.
147+
148+
## Common Issues
149+
150+
- **`Call to undefined function kyConfig::set()`**: `function_requirements('class.kyConfig')` was not called before the SOAP init block. Add it immediately before the try/catch.
151+
- **`Undefined variable $result` in catch block**: `$result` array was not initialized before the first if-guard. Move the `$result = [...]` declaration to the very first line of the function body.
152+
- **Tests fail with `openTicket not found`**: `setUpBeforeClass` only calls `require_once` when `openTicket` doesn't exist. Add a stub for any new global functions your function calls (e.g. `ticket_status_all`) inside the `if (!function_exists('openTicket'))` block in `ApiFunctionsTest::setUpBeforeClass()`.
153+
- **`status` key returns lowercase `'failed'` instead of `'Failed'`**: Validation-path failures use capital-F `'Failed'`; only SOAP init failures in `openTicket`/`getTicketList` use lowercase. Match the convention of the nearest sibling function.
154+
- **`Undefined index: account_lid`**: `$GLOBALS['tf']->accounts->data` is not available in unit tests — wrap ownership checks in try/catch and confirm the test only exercises the pre-ownership validation paths.

0 commit comments

Comments
 (0)