Skip to content

Commit a4afc4e

Browse files
committed
update adding claude.md
1 parent 6126c5e commit a4afc4e

12 files changed

Lines changed: 1417 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: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
1+
---
2+
name: add-api-function
3+
description: Adds a new global-namespace API function to `src/api.php` following the existing pattern. Registers it in `Plugin::getRequirements()` and `Plugin::apiRegister()`. Use when user says 'add API function', 'new license operation', 'add endpoint to api.php', or adds a new license action. Do NOT use for modifying `src/Plugin.php` hooks unrelated to API registration.
4+
---
5+
# add-api-function
6+
7+
## Critical
8+
9+
- Functions MUST be in the global namespace — no `namespace` declaration in `src/api.php`
10+
- `$sid` MUST always be the first parameter for any function that authenticates a user
11+
- NEVER use PDO — always `$db = get_module_db('licenses')`
12+
- ALWAYS pass `__LINE__, __FILE__` as the last two arguments to every `$db->query()` call
13+
- ALWAYS escape user-supplied strings with `$db->real_escape()` before interpolating into queries
14+
- ALWAYS cast integer inputs with `(int)` before use (e.g., `$id = (int)$id`)
15+
- Every function that takes `$sid` MUST verify it with `$GLOBALS['tf']->session->verify()` and return early with `['status' => 'error', 'status_text' => 'Invalid Session ID']` on failure
16+
- Every new function MUST be registered in both `getRequirements()` AND `apiRegister()` in `src/Plugin.php`
17+
- Return type for all session-authenticated functions is `array` with at minimum `status` and `status_text` keys
18+
19+
## Instructions
20+
21+
### Step 1 — Write the function in `src/api.php`
22+
23+
Append the new function at the end of `src/api.php`. Follow this skeleton:
24+
25+
```php
26+
/**
27+
* One-line description of what this does.
28+
*
29+
* @param string $sid Session ID
30+
* @param int $id License Order ID
31+
* @return array
32+
* @throws \Exception
33+
*/
34+
function api_<verb>_license<_qualifier>($sid, $id)
35+
{
36+
$id = (int)$id; // cast ints immediately; skip for string params
37+
myadmin_log('api', 'info', "api_<verb>_license<_qualifier>('{$sid}', {$id}) called", __LINE__, __FILE__);
38+
$module = 'licenses';
39+
$db = get_module_db($module);
40+
$settings = get_module_settings($module);
41+
$return = [];
42+
$return['status'] = '';
43+
$return['status_text'] = '';
44+
$GLOBALS['tf']->session->sessionid = $sid;
45+
if ($GLOBALS['tf']->session->verify()) {
46+
$custid = get_custid($GLOBALS['tf']->session->account_id, $module);
47+
$GLOBALS['tf']->accounts->data = $GLOBALS['tf']->accounts->read($custid);
48+
$GLOBALS['tf']->ima = $GLOBALS['tf']->session->appsession('ima');
49+
$GLOBALS['tf']->session->update_dla();
50+
} else {
51+
$return['status'] = 'error';
52+
$return['status_text'] = 'Invalid Session ID';
53+
return $return;
54+
}
55+
update_session_log(__FUNCTION__);
56+
// --- your logic here ---
57+
$db->query("select * from {$settings['TABLE']} where {$settings['PREFIX']}_id='{$id}' and {$settings['PREFIX']}_custid='{$custid}'", __LINE__, __FILE__);
58+
if ($db->num_rows() == 0) {
59+
$return['status'] = 'error';
60+
$return['status_text'] = 'Invalid License ID';
61+
return $return;
62+
}
63+
$db->next_record(MYSQL_ASSOC);
64+
// operate on $db->Record ...
65+
$return['status'] = 'ok';
66+
$return['status_text'] = "Operation completed.\n";
67+
return $return;
68+
}
69+
```
70+
71+
For functions that do NOT authenticate (like `api_get_license_types`), omit the session block entirely.
72+
73+
Verify the function is at global scope (no wrapping namespace or class) before proceeding.
74+
75+
### Step 2 — Register in `getRequirements()` in `src/Plugin.php`
76+
77+
This step uses the function name from Step 1.
78+
79+
Inside `getRequirements(GenericEvent $event)`, add a line after the last existing `add_requirement` call:
80+
81+
```php
82+
$loader->add_requirement('api_<verb>_license<_qualifier>', '/../vendor/detain/myadmin-licenses-module/src/api.php');
83+
```
84+
85+
The path string is always `'/../vendor/detain/myadmin-licenses-module/src/api.php'` — do not change it.
86+
87+
Verify the new line is inside the `getRequirements` method body before proceeding.
88+
89+
### Step 3 — Register in `apiRegister()` in `src/Plugin.php`
90+
91+
This step uses the function name and parameter list from Step 1.
92+
93+
Inside `apiRegister(GenericEvent $event)`, add the return type definition and the function registration.
94+
95+
**3a.** If the return shape is new, define it with `api_register_array`:
96+
97+
```php
98+
api_register_array('<verb>_license<_qualifier>_return', ['status' => 'string', 'status_text' => 'string']);
99+
```
100+
101+
Add extra keys if the function returns more fields (e.g., `'invoice' => 'int'`, `'cost' => 'float'`).
102+
103+
**3b.** Register the function:
104+
105+
```php
106+
api_register(
107+
'api_<verb>_license<_qualifier>',
108+
['sid' => 'string', 'id' => 'int'], // match actual params; omit $sid for unauthenticated
109+
['return' => '<verb>_license<_qualifier>_return'],
110+
'Human-readable description of the operation.',
111+
true, // true = requires authentication; false = public
112+
false
113+
);
114+
```
115+
116+
Param type map: PHP `string``'string'`, `int``'int'`, `float``'float'`, `bool`/`null``'boolean'`.
117+
118+
Verify both calls are inside the `apiRegister` method before proceeding.
119+
120+
### Step 4 — Add test coverage in `tests/ApiTest.php`
121+
122+
This step uses the function signature from Step 1.
123+
124+
Add to `$apiFunctions` array:
125+
```php
126+
'api_<verb>_license<_qualifier>',
127+
```
128+
129+
Add existence and parameter tests following the existing pattern:
130+
131+
```php
132+
public function testApi<VerbLicense>Exists(): void
133+
{
134+
$this->assertTrue(function_exists('api_<verb>_license<_qualifier>'));
135+
}
136+
137+
public function testApi<VerbLicense>ParameterCount(): void
138+
{
139+
$ref = new ReflectionFunction('api_<verb>_license<_qualifier>');
140+
$this->assertCount(<N>, $ref->getParameters());
141+
}
142+
143+
public function testApi<VerbLicense>ParameterNames(): void
144+
{
145+
$ref = new ReflectionFunction('api_<verb>_license<_qualifier>');
146+
$params = $ref->getParameters();
147+
$this->assertSame('sid', $params[0]->getName());
148+
// add remaining params ...
149+
}
150+
```
151+
152+
### Step 5 — Verify
153+
154+
Run `vendor/bin/phpunit` from `/home/sites/mystage/vendor/detain/myadmin-licenses-module/`. All tests must pass before the work is done.
155+
156+
## Examples
157+
158+
**User says:** "Add an API function to suspend a license by ID"
159+
160+
**Actions taken:**
161+
162+
1. Append to `src/api.php`:
163+
```php
164+
/**
165+
* Suspends a Software License with id $id
166+
*
167+
* @param string $sid Session ID
168+
* @param int $id License Order ID
169+
* @return array
170+
* @throws \Exception
171+
*/
172+
function api_suspend_license($sid, $id)
173+
{
174+
$id = (int)$id;
175+
myadmin_log('api', 'info', "api_suspend_license('{$sid}', {$id}) called", __LINE__, __FILE__);
176+
$module = 'licenses';
177+
$db = get_module_db($module);
178+
$settings = get_module_settings($module);
179+
$return = [];
180+
$return['status'] = '';
181+
$return['status_text'] = '';
182+
$GLOBALS['tf']->session->sessionid = $sid;
183+
if ($GLOBALS['tf']->session->verify()) {
184+
$custid = get_custid($GLOBALS['tf']->session->account_id, $module);
185+
$GLOBALS['tf']->accounts->data = $GLOBALS['tf']->accounts->read($custid);
186+
$GLOBALS['tf']->ima = $GLOBALS['tf']->session->appsession('ima');
187+
$GLOBALS['tf']->session->update_dla();
188+
} else {
189+
$return['status'] = 'error';
190+
$return['status_text'] = 'Invalid Session ID';
191+
return $return;
192+
}
193+
update_session_log(__FUNCTION__);
194+
$db->query("select * from {$settings['TABLE']} where {$settings['PREFIX']}_id='{$id}' and {$settings['PREFIX']}_custid='{$custid}'", __LINE__, __FILE__);
195+
if ($db->num_rows() == 0) {
196+
$return['status'] = 'error';
197+
$return['status_text'] = 'Invalid License ID';
198+
return $return;
199+
}
200+
$db->next_record(MYSQL_ASSOC);
201+
$db->query("update {$settings['TABLE']} set {$settings['PREFIX']}_status='suspended' where {$settings['PREFIX']}_id='{$id}'", __LINE__, __FILE__);
202+
$return['status'] = 'ok';
203+
$return['status_text'] = "License Suspended.\n";
204+
return $return;
205+
}
206+
```
207+
208+
2. In `src/Plugin.php``getRequirements()`:
209+
```php
210+
$loader->add_requirement('api_suspend_license', '/../vendor/detain/myadmin-licenses-module/src/api.php');
211+
```
212+
213+
3. In `src/Plugin.php``apiRegister()`:
214+
```php
215+
api_register_array('suspend_license_return', ['status' => 'string', 'status_text' => 'string']);
216+
api_register('api_suspend_license', ['sid' => 'string', 'id' => 'int'], ['return' => 'suspend_license_return'], 'Suspend a License.', true, false);
217+
```
218+
219+
4. In `tests/ApiTest.php` add `'api_suspend_license'` to `$apiFunctions` and add existence/parameter tests.
220+
221+
**Result:** `vendor/bin/phpunit` passes with new tests for `api_suspend_license`.
222+
223+
## Common Issues
224+
225+
**`Fatal error: Cannot redeclare api_<name>()`**
226+
The function already exists in `src/api.php`. Search with `grep -n 'function api_' src/api.php` and rename accordingly.
227+
228+
**`Call to undefined function get_module_db()`** during tests
229+
This is expected — the bootstrap stubs out globals but does not define core MyAdmin helpers. Tests should use `function_exists()` and `ReflectionFunction`, not call the function directly.
230+
231+
**`$db->query()` missing `__LINE__, __FILE__`**
232+
Every `$db->query(...)` call must end with `, __LINE__, __FILE__`. Omitting these will cause silent failures in the query log.
233+
234+
**Session block returns wrong keys**
235+
Return array must initialize both `$return['status'] = ''` and `$return['status_text'] = ''` before the session check. Missing keys cause downstream code to emit PHP notices.
236+
237+
**`api_register` param types mismatch PHP signature**
238+
If the PHP function uses `(int)$id` but `api_register` lists `'id' => 'string'`, the API schema will be wrong. Match PHP types to API type strings: `int``'int'`, `string``'string'`, `float``'float'`, `bool|null``'boolean'`.
239+
240+
**New function not showing in API**
241+
Confirm both `getRequirements` (file loader) AND `apiRegister` (schema registration) were updated — missing either one means the function is either not loadable or not discoverable via the API schema.

0 commit comments

Comments
 (0)