toga-ai 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.json +119 -0
- package/.claude-plugin/marketplace.json +87 -0
- package/.claude-plugin/plugin.json +22 -0
- package/CLAUDE.md +161 -0
- package/README.md +72 -0
- package/agents/framework-pattern-checker.md +67 -0
- package/agents/harness-optimizer.md +102 -0
- package/agents/knowledge-writer.md +62 -0
- package/agents/php-build-resolver.md +51 -0
- package/agents/php-reviewer.md +51 -0
- package/agents/planner.md +88 -0
- package/agents/session-capture.md +101 -0
- package/agents/sql-reviewer.md +67 -0
- package/contexts/dev.md +43 -0
- package/contexts/research.md +49 -0
- package/contexts/review.md +37 -0
- package/knowledge/1.0/apps/library/INDEX.md +5 -0
- package/knowledge/1.0/apps/library/architecture.md +105 -0
- package/knowledge/1.0/apps/worker/INDEX.md +5 -0
- package/knowledge/1.0/apps/worker/architecture.md +223 -0
- package/knowledge/1.0/standards/backend-php.md +450 -0
- package/knowledge/2.0/apps/_underscore/INDEX.md +6 -0
- package/knowledge/2.0/apps/_underscore/architecture.md +183 -0
- package/knowledge/2.0/apps/_underscore/features/recursive-item-fulfillments.md +111 -0
- package/knowledge/2.0/apps/api2/INDEX.md +5 -0
- package/knowledge/2.0/apps/api2/architecture.md +162 -0
- package/knowledge/2.0/apps/worker2/INDEX.md +6 -0
- package/knowledge/2.0/apps/worker2/architecture.md +127 -0
- package/knowledge/2.0/apps/worker2/features/creating-worker-actions.md +135 -0
- package/knowledge/2.0/standards/backend-php.md +710 -0
- package/knowledge/CONVENTIONS.md +117 -0
- package/knowledge/INDEX.md +19 -0
- package/knowledge/clients/.gitkeep +0 -0
- package/knowledge/registry.json +7 -0
- package/knowledge.js +384 -0
- package/mcp-configs/README.md +72 -0
- package/mcp-configs/mcp-servers.json +23 -0
- package/package.json +50 -0
- package/rules/README.md +53 -0
- package/rules/common/coding-style.md +123 -0
- package/rules/common/git-workflow.md +72 -0
- package/rules/common/security.md +118 -0
- package/rules/common/testing.md +74 -0
- package/rules/php/app-framework.md +104 -0
- package/rules/php/underscore-framework.md +111 -0
- package/scripts/harness.js +605 -0
- package/scripts/hooks/evaluate-session.js +55 -0
- package/scripts/hooks/post-edit-validate.js +102 -0
- package/scripts/hooks/session-end.js +13 -0
- package/scripts/hooks/session-start.js +57 -0
- package/scripts/install.js +611 -0
- package/scripts/pre-commit +46 -0
- package/skills/capture/SKILL.md +294 -0
- package/skills/code-review/SKILL.md +140 -0
- package/skills/create-elastic-beanstalk/SKILL.md +217 -0
- package/skills/harness-audit/SKILL.md +152 -0
- package/skills/kickoff/SKILL.md +151 -0
- package/skills/php-patterns/SKILL.md +296 -0
- package/skills/session-resume/SKILL.md +156 -0
- package/skills/session-save/SKILL.md +158 -0
- package/skills/sync-team-skills/SKILL.md +87 -0
- package/sync-skills.js +71 -0
package/rules/README.md
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Rules Directory
|
|
2
|
+
|
|
3
|
+
Rules are **always-follow guidelines** that are loaded automatically into every Claude Code session. Unlike skills (which are on-demand slash commands), rules are passive context — Claude reads them and applies them without being asked.
|
|
4
|
+
|
|
5
|
+
## What belongs here
|
|
6
|
+
|
|
7
|
+
Rules files contain direct imperatives: coding style, security requirements, git workflow, framework conventions. They do not contain architecture documentation (that goes in `knowledge/`) or how-to instructions (that goes in `skills/`).
|
|
8
|
+
|
|
9
|
+
## Directory structure
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
rules/
|
|
13
|
+
├── README.md ← this file
|
|
14
|
+
├── common/ ← apply to all repos and both frameworks
|
|
15
|
+
│ ├── coding-style.md ← language-agnostic style rules
|
|
16
|
+
│ ├── git-workflow.md ← branching, commit messages, PR rules
|
|
17
|
+
│ ├── security.md ← security rules (critical — always followed)
|
|
18
|
+
│ └── testing.md ← test requirements
|
|
19
|
+
└── php/ ← PHP-specific rules
|
|
20
|
+
├── app-framework.md ← 1.0 App_ framework rules
|
|
21
|
+
└── underscore-framework.md ← 2.0 _underscore framework rules
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## How to install
|
|
25
|
+
|
|
26
|
+
Run from the team knowledge repo root:
|
|
27
|
+
|
|
28
|
+
```sh
|
|
29
|
+
node scripts/install.js
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
This copies all files from `rules/` into `.claude/rules/toga/` in the target project. Claude Code automatically loads rules from `.claude/rules/`.
|
|
33
|
+
|
|
34
|
+
If you want to install into a specific project:
|
|
35
|
+
|
|
36
|
+
```sh
|
|
37
|
+
node scripts/install.js /path/to/project
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## How to add new rules
|
|
41
|
+
|
|
42
|
+
1. Create a new `.md` file in the appropriate subdirectory (`common/` or `php/`).
|
|
43
|
+
2. Write direct imperatives — "Never do X", "Always do Y". No meta-commentary.
|
|
44
|
+
3. Keep each file under 200 lines.
|
|
45
|
+
4. Run `node scripts/install.js` to push the new rule to all installed projects.
|
|
46
|
+
5. Commit the new rule file to this repo so it's available to the whole team.
|
|
47
|
+
|
|
48
|
+
## Why rules instead of CLAUDE.md
|
|
49
|
+
|
|
50
|
+
`CLAUDE.md` is project-specific context. Rules in `.claude/rules/` are team-wide always-on standards. Keeping them separate means:
|
|
51
|
+
- Updating a rule updates it for the whole team on next install.
|
|
52
|
+
- Project `CLAUDE.md` stays focused on project-specific context.
|
|
53
|
+
- Rules can be audited separately from project docs.
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# Coding Style Rules
|
|
2
|
+
|
|
3
|
+
These rules apply to all code in all TOGA Technology repos, regardless of language or framework.
|
|
4
|
+
|
|
5
|
+
## Error handling
|
|
6
|
+
|
|
7
|
+
Never use bare `catch` blocks. Never catch and silently ignore an exception.
|
|
8
|
+
|
|
9
|
+
```php
|
|
10
|
+
// WRONG
|
|
11
|
+
try {
|
|
12
|
+
$result = $db->query($sql);
|
|
13
|
+
} catch (Exception $e) {
|
|
14
|
+
// nothing here
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// WRONG
|
|
18
|
+
} catch (Exception $e) {}
|
|
19
|
+
|
|
20
|
+
// CORRECT
|
|
21
|
+
} catch (PDOException $e) {
|
|
22
|
+
error_log('DB query failed: ' . $e->getMessage());
|
|
23
|
+
throw $e; // or handle specifically
|
|
24
|
+
}
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Always catch the most specific exception type available. Catching `Exception` as a last resort is acceptable only if you log it and re-throw or return an error response.
|
|
28
|
+
|
|
29
|
+
Never use `@` error suppression. Fix the underlying issue instead.
|
|
30
|
+
|
|
31
|
+
## Dangerous functions
|
|
32
|
+
|
|
33
|
+
Never use `eval()` on user input or on any string that contains user-controlled data.
|
|
34
|
+
|
|
35
|
+
Never use `exec()`, `shell_exec()`, `system()`, `passthru()`, or `popen()` with user input. If shell execution is required, use an allowlist of commands and escape arguments with `escapeshellarg()`.
|
|
36
|
+
|
|
37
|
+
## Function parameters
|
|
38
|
+
|
|
39
|
+
Functions with 4 or more parameters must use an associative array argument or a named-parameters pattern. Do not define functions with 4+ positional parameters.
|
|
40
|
+
|
|
41
|
+
```php
|
|
42
|
+
// WRONG
|
|
43
|
+
function createOrder($userId, $items, $addressId, $couponCode, $notes) { ... }
|
|
44
|
+
|
|
45
|
+
// CORRECT
|
|
46
|
+
function createOrder(array $params): Order {
|
|
47
|
+
// $params['user_id'], $params['items'], $params['address_id'], etc.
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Return early
|
|
52
|
+
|
|
53
|
+
Use guard clauses and return early. Do not nest the happy path inside conditionals.
|
|
54
|
+
|
|
55
|
+
```php
|
|
56
|
+
// WRONG
|
|
57
|
+
function getUser(int $id): ?User {
|
|
58
|
+
if ($id > 0) {
|
|
59
|
+
$user = $this->db->find($id);
|
|
60
|
+
if ($user) {
|
|
61
|
+
return $user;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// CORRECT
|
|
68
|
+
function getUser(int $id): ?User {
|
|
69
|
+
if ($id <= 0) return null;
|
|
70
|
+
return $this->db->find($id);
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Dead code
|
|
75
|
+
|
|
76
|
+
Never commit commented-out code. Delete it. If you need to preserve a previous approach, that is what git history is for.
|
|
77
|
+
|
|
78
|
+
```php
|
|
79
|
+
// WRONG
|
|
80
|
+
// $result = $this->oldMethod($id);
|
|
81
|
+
$result = $this->newMethod($id);
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Magic numbers
|
|
85
|
+
|
|
86
|
+
Never use unexplained numeric or string literals. Define named constants.
|
|
87
|
+
|
|
88
|
+
```php
|
|
89
|
+
// WRONG
|
|
90
|
+
if ($attempts > 3) { ... }
|
|
91
|
+
sleep(86400);
|
|
92
|
+
|
|
93
|
+
// CORRECT
|
|
94
|
+
const MAX_LOGIN_ATTEMPTS = 3;
|
|
95
|
+
const SECONDS_PER_DAY = 86400;
|
|
96
|
+
|
|
97
|
+
if ($attempts > self::MAX_LOGIN_ATTEMPTS) { ... }
|
|
98
|
+
sleep(self::SECONDS_PER_DAY);
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Single responsibility
|
|
102
|
+
|
|
103
|
+
Each function does one thing. If a function's name requires "and" to describe it, split it.
|
|
104
|
+
|
|
105
|
+
Functions longer than 40 lines are a signal to review for decomposition — not a hard rule, but a strong prompt to consider whether the function has grown beyond one responsibility.
|
|
106
|
+
|
|
107
|
+
## Type declarations
|
|
108
|
+
|
|
109
|
+
All public and protected methods must have return type declarations. Parameter types must be declared for all non-variadic parameters. Use PHP 7.4+ typed properties for class attributes.
|
|
110
|
+
|
|
111
|
+
```php
|
|
112
|
+
// WRONG
|
|
113
|
+
public function process($data) {
|
|
114
|
+
...
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// CORRECT
|
|
118
|
+
public function process(array $data): bool {
|
|
119
|
+
...
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Use `?Type` for nullable. Use `void` for no return value. Do not omit the return type.
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# Git Workflow Rules
|
|
2
|
+
|
|
3
|
+
## Branch protection
|
|
4
|
+
|
|
5
|
+
Never commit directly to `_main`. All changes go through feature branches and pull requests.
|
|
6
|
+
|
|
7
|
+
Never force-push to `_main` under any circumstances.
|
|
8
|
+
|
|
9
|
+
## Branch naming
|
|
10
|
+
|
|
11
|
+
Use one of these prefixes followed by a short hyphenated description:
|
|
12
|
+
|
|
13
|
+
- `feature/short-description` — new functionality
|
|
14
|
+
- `fix/short-description` — bug fix
|
|
15
|
+
- `hotfix/short-description` — urgent production fix
|
|
16
|
+
- `refactor/short-description` — code restructuring with no behavior change
|
|
17
|
+
- `docs/short-description` — documentation only
|
|
18
|
+
|
|
19
|
+
Examples: `feature/batch-order-processing`, `fix/null-customer-email`, `hotfix/sqs-timeout`
|
|
20
|
+
|
|
21
|
+
Keep the description short (3–5 words). Use hyphens, not underscores.
|
|
22
|
+
|
|
23
|
+
## Commit messages
|
|
24
|
+
|
|
25
|
+
Format: `type: short description`
|
|
26
|
+
|
|
27
|
+
Types: `feat`, `fix`, `refactor`, `docs`, `test`, `chore`
|
|
28
|
+
|
|
29
|
+
- `feat: add batch order processing worker`
|
|
30
|
+
- `fix: null check on customer email before dispatch`
|
|
31
|
+
- `refactor: extract order validation to dedicated class`
|
|
32
|
+
- `docs: document recursive item fulfillment feature`
|
|
33
|
+
- `test: add regression test for empty batch edge case`
|
|
34
|
+
- `chore: update composer dependencies`
|
|
35
|
+
|
|
36
|
+
The description is lowercase, present tense, under 72 characters. No period at the end.
|
|
37
|
+
|
|
38
|
+
If a commit is substantial, add a blank line after the subject and a body paragraph explaining the why.
|
|
39
|
+
|
|
40
|
+
## What never goes in commits
|
|
41
|
+
|
|
42
|
+
Never commit secrets, API keys, passwords, database credentials, or access tokens — not even to a non-production branch. If a secret is accidentally committed, treat it as compromised and rotate it immediately.
|
|
43
|
+
|
|
44
|
+
Never commit `.env` files. They are in `.gitignore` for a reason.
|
|
45
|
+
|
|
46
|
+
Never commit absolute local paths (e.g. `C:\WWW\2.0\worker2`). These are machine-specific and tracked only in developer Claude memory.
|
|
47
|
+
|
|
48
|
+
## Knowledge base changes
|
|
49
|
+
|
|
50
|
+
Always run `node knowledge.js validate` before committing any changes to `knowledge/`. The pre-commit hook does this automatically in this repo. Do not bypass it with `--no-verify`.
|
|
51
|
+
|
|
52
|
+
If validation fails, fix the errors before committing. Do not commit a broken knowledge base.
|
|
53
|
+
|
|
54
|
+
## Pull requests
|
|
55
|
+
|
|
56
|
+
PR title format: `[repo] brief description` — e.g. `[worker2] Add batch order processing`
|
|
57
|
+
|
|
58
|
+
PR description must include:
|
|
59
|
+
- Which repos or features are affected
|
|
60
|
+
- How to test the change
|
|
61
|
+
- Any migration steps (DB schema changes, config changes, queue registration)
|
|
62
|
+
|
|
63
|
+
Link to the relevant knowledge doc if one exists or was created as part of the PR.
|
|
64
|
+
|
|
65
|
+
## Pre-commit checklist
|
|
66
|
+
|
|
67
|
+
Before creating a PR:
|
|
68
|
+
- [ ] `node knowledge.js validate` passes (if knowledge/ was touched)
|
|
69
|
+
- [ ] `php -l <changed files>` passes (no syntax errors)
|
|
70
|
+
- [ ] No commented-out code
|
|
71
|
+
- [ ] No hardcoded credentials or local paths
|
|
72
|
+
- [ ] Branch name follows naming convention
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# Security Rules
|
|
2
|
+
|
|
3
|
+
These rules are critical. Violations create exploitable vulnerabilities. Follow them without exception.
|
|
4
|
+
|
|
5
|
+
## SQL injection prevention
|
|
6
|
+
|
|
7
|
+
Never build SQL strings by concatenating or interpolating user input.
|
|
8
|
+
|
|
9
|
+
```php
|
|
10
|
+
// CRITICAL VIOLATION — never do this
|
|
11
|
+
$sql = "SELECT * FROM users WHERE id = " . $_GET['id'];
|
|
12
|
+
$sql = "SELECT * FROM orders WHERE email = '$email'";
|
|
13
|
+
|
|
14
|
+
// CORRECT — always use prepared statements
|
|
15
|
+
$stmt = $pdo->prepare('SELECT * FROM users WHERE id = ?');
|
|
16
|
+
$stmt->execute([$_GET['id']]);
|
|
17
|
+
|
|
18
|
+
// CORRECT — framework parameterized query
|
|
19
|
+
_Db::select('orders', ['email' => $email]);
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
This applies to every SQL operation: SELECT, INSERT, UPDATE, DELETE, and DDL. No exceptions for "internal" data or "trusted" sources — use parameterized queries everywhere.
|
|
23
|
+
|
|
24
|
+
Integer casting (`(int) $_GET['id']`) reduces risk but is not a substitute for prepared statements. Use both.
|
|
25
|
+
|
|
26
|
+
## XSS prevention
|
|
27
|
+
|
|
28
|
+
Never output user-supplied data to HTML without escaping.
|
|
29
|
+
|
|
30
|
+
```php
|
|
31
|
+
// CRITICAL VIOLATION
|
|
32
|
+
echo $_GET['name'];
|
|
33
|
+
echo $user->bio; // if bio came from user input
|
|
34
|
+
|
|
35
|
+
// CORRECT
|
|
36
|
+
echo htmlspecialchars($_GET['name'], ENT_QUOTES, 'UTF-8');
|
|
37
|
+
echo htmlspecialchars($user->bio, ENT_QUOTES, 'UTF-8');
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
`htmlspecialchars()` with `ENT_QUOTES` and `'UTF-8'` is the minimum. Use the framework's output escaping helper if one exists.
|
|
41
|
+
|
|
42
|
+
JSON output is safe from XSS but must use `json_encode()` — never manually build JSON strings.
|
|
43
|
+
|
|
44
|
+
JavaScript context requires additional escaping beyond `htmlspecialchars()`. Use `json_encode()` to pass server data to JS.
|
|
45
|
+
|
|
46
|
+
## Password handling
|
|
47
|
+
|
|
48
|
+
Never store passwords in plaintext.
|
|
49
|
+
|
|
50
|
+
Never use MD5, SHA1, SHA256, or any fast hash for passwords. These are broken for password storage.
|
|
51
|
+
|
|
52
|
+
Always use `password_hash($password, PASSWORD_BCRYPT)` or `PASSWORD_ARGON2ID` to hash passwords.
|
|
53
|
+
|
|
54
|
+
Always use `password_verify($input, $hash)` to check passwords. Never compare hashes with `==` or `===`.
|
|
55
|
+
|
|
56
|
+
## Sensitive data in logs
|
|
57
|
+
|
|
58
|
+
Never log passwords, session tokens, credit card numbers, bank account numbers, social security numbers, or any PII.
|
|
59
|
+
|
|
60
|
+
```php
|
|
61
|
+
// VIOLATION
|
|
62
|
+
error_log('Login attempt: user=' . $email . ' password=' . $password);
|
|
63
|
+
|
|
64
|
+
// CORRECT
|
|
65
|
+
error_log('Login attempt: user=' . $email);
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Before adding any log statement, review what is being logged. When in doubt, log identifiers (user ID, order ID) not values.
|
|
69
|
+
|
|
70
|
+
## Input validation
|
|
71
|
+
|
|
72
|
+
Never trust `$_GET`, `$_POST`, `$_COOKIE`, or `$_REQUEST` directly. Always validate at the boundary.
|
|
73
|
+
|
|
74
|
+
Validation means:
|
|
75
|
+
1. **Type check**: Is this an integer, string, email, etc.?
|
|
76
|
+
2. **Range/length check**: Is an integer within allowed range? Is a string within max length?
|
|
77
|
+
3. **Format check**: Does a date match the expected format? Does an email pass `filter_var()`?
|
|
78
|
+
4. **Allowlist check**: For values that must be one of a set, check against the set.
|
|
79
|
+
|
|
80
|
+
```php
|
|
81
|
+
// VIOLATION — no validation
|
|
82
|
+
$page = $_GET['page'];
|
|
83
|
+
|
|
84
|
+
// CORRECT
|
|
85
|
+
$page = filter_input(INPUT_GET, 'page', FILTER_VALIDATE_INT, [
|
|
86
|
+
'options' => ['min_range' => 1, 'max_range' => 1000],
|
|
87
|
+
'flags' => FILTER_NULL_ON_FAILURE,
|
|
88
|
+
]) ?? 1;
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## File operations
|
|
92
|
+
|
|
93
|
+
Never use user input directly in file paths.
|
|
94
|
+
|
|
95
|
+
```php
|
|
96
|
+
// CRITICAL VIOLATION
|
|
97
|
+
include($_GET['page'] . '.php');
|
|
98
|
+
file_get_contents('/var/uploads/' . $_GET['filename']);
|
|
99
|
+
|
|
100
|
+
// CORRECT — allowlist approach
|
|
101
|
+
$allowed = ['home', 'about', 'contact'];
|
|
102
|
+
$page = in_array($_GET['page'], $allowed) ? $_GET['page'] : 'home';
|
|
103
|
+
include($page . '.php');
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Session security
|
|
107
|
+
|
|
108
|
+
Never put sensitive data in URLs (query strings are logged by web servers).
|
|
109
|
+
|
|
110
|
+
Regenerate session IDs after login: `session_regenerate_id(true)`.
|
|
111
|
+
|
|
112
|
+
Session cookies must be `HttpOnly` and `Secure` in production.
|
|
113
|
+
|
|
114
|
+
## Cryptography
|
|
115
|
+
|
|
116
|
+
Never implement your own encryption. Use PHP's `sodium_*` functions or a vetted library.
|
|
117
|
+
|
|
118
|
+
Never use `rand()` or `mt_rand()` for security-sensitive random values. Use `random_bytes()` or `random_int()`.
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# Testing Rules
|
|
2
|
+
|
|
3
|
+
## Coverage requirements
|
|
4
|
+
|
|
5
|
+
Bug fixes require a regression test written before the fix is applied. The test must fail on the original code and pass after the fix.
|
|
6
|
+
|
|
7
|
+
New features require tests. A feature is not complete until its tests pass.
|
|
8
|
+
|
|
9
|
+
## Test naming
|
|
10
|
+
|
|
11
|
+
Test names must describe the expected behavior, not just label the subject.
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
// WRONG
|
|
15
|
+
test_get_user()
|
|
16
|
+
test_order_2()
|
|
17
|
+
test_process()
|
|
18
|
+
|
|
19
|
+
// CORRECT
|
|
20
|
+
test_returns_null_when_user_id_is_zero()
|
|
21
|
+
test_returns_404_when_order_not_found()
|
|
22
|
+
test_throws_when_required_param_missing()
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Use snake_case for test names. Start with `test_`. The name should read as a sentence describing what the system does under specific conditions.
|
|
26
|
+
|
|
27
|
+
## What to test
|
|
28
|
+
|
|
29
|
+
Always test:
|
|
30
|
+
- The happy path (valid input, expected output)
|
|
31
|
+
- Empty input (empty string, empty array, zero)
|
|
32
|
+
- Null input where null is a possible value
|
|
33
|
+
- Boundary values (max int, max string length, empty collections)
|
|
34
|
+
- Invalid types where the function accepts loose input
|
|
35
|
+
|
|
36
|
+
For functions that interact with external systems (database, queue, HTTP):
|
|
37
|
+
- Test with the external call mocked or stubbed
|
|
38
|
+
- Test the error path: what happens when the external call fails?
|
|
39
|
+
|
|
40
|
+
## Do not delete failing tests
|
|
41
|
+
|
|
42
|
+
If a test is failing, fix the root cause. Do not:
|
|
43
|
+
- Delete the test
|
|
44
|
+
- Comment out the assertion
|
|
45
|
+
- Change the test to match broken behavior
|
|
46
|
+
- Add a special case in production code to make the test pass without fixing the real bug
|
|
47
|
+
|
|
48
|
+
A failing test is information. Deleting it destroys information.
|
|
49
|
+
|
|
50
|
+
## Test isolation
|
|
51
|
+
|
|
52
|
+
Each test must be independent. Tests must not depend on execution order. Tests must not share mutable state.
|
|
53
|
+
|
|
54
|
+
Reset any state modified by a test in a teardown method or by using transactions that are rolled back.
|
|
55
|
+
|
|
56
|
+
## Framework-specific notes
|
|
57
|
+
|
|
58
|
+
For PHP unit tests (PHPUnit):
|
|
59
|
+
- Test class names end in `Test`: `OrderProcessorTest`
|
|
60
|
+
- Test classes extend `PHPUnit\Framework\TestCase`
|
|
61
|
+
- Use `setUp()` for per-test initialization, not class-level static state
|
|
62
|
+
- Use data providers for parameterized tests over copy-paste test methods
|
|
63
|
+
|
|
64
|
+
For integration tests that touch the database:
|
|
65
|
+
- Wrap each test in a transaction and roll back in `tearDown()`
|
|
66
|
+
- Never use the production database for tests
|
|
67
|
+
|
|
68
|
+
## Regression tests
|
|
69
|
+
|
|
70
|
+
When fixing a bug, the regression test:
|
|
71
|
+
1. Reproduces the exact scenario that caused the bug
|
|
72
|
+
2. Is named to describe the bug: `test_does_not_crash_when_customer_is_null`
|
|
73
|
+
3. Is placed in the same test file as the class under test
|
|
74
|
+
4. Must be committed in the same PR as the fix
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# Framework 1.0 (App_) Rules
|
|
2
|
+
|
|
3
|
+
These rules apply to all repos under `C:\WWW\1.0`: `library` (core) and `worker` (app).
|
|
4
|
+
|
|
5
|
+
## Class naming
|
|
6
|
+
|
|
7
|
+
All classes must use the `App_` prefix. The prefix encodes the class type and path:
|
|
8
|
+
|
|
9
|
+
- Controllers: `App_Controller_<Name>` — e.g. `App_Controller_Orders`, `App_Controller_Dashboard`
|
|
10
|
+
- Models: `App_Model_<Name>` — e.g. `App_Model_User`, `App_Model_Order`
|
|
11
|
+
- Helpers/utilities: `App_Helper_<Name>` — e.g. `App_Helper_Date`, `App_Helper_Currency`
|
|
12
|
+
- Libraries/services: `App_Library_<Name>` or `App_Service_<Name>`
|
|
13
|
+
|
|
14
|
+
Never create a class in a 1.0 repo without the `App_` prefix. No exceptions for "small utility classes" or anonymous helpers.
|
|
15
|
+
|
|
16
|
+
## Inheritance
|
|
17
|
+
|
|
18
|
+
Controllers must extend `App_Controller` (directly or through an intermediate base class that itself extends `App_Controller`).
|
|
19
|
+
|
|
20
|
+
```php
|
|
21
|
+
// CORRECT
|
|
22
|
+
class App_Controller_Orders extends App_Controller { ... }
|
|
23
|
+
class App_Controller_Api_Orders extends App_Controller_Api { ... } // where App_Controller_Api extends App_Controller
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Models must extend `App_Model`.
|
|
27
|
+
|
|
28
|
+
```php
|
|
29
|
+
// CORRECT
|
|
30
|
+
class App_Model_Order extends App_Model { ... }
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Never extend a raw PHP base class (like `stdClass` or nothing) for controllers or models in 1.0 repos.
|
|
34
|
+
|
|
35
|
+
## Model instantiation
|
|
36
|
+
|
|
37
|
+
Never instantiate models directly in controllers using `new`.
|
|
38
|
+
|
|
39
|
+
```php
|
|
40
|
+
// VIOLATION
|
|
41
|
+
$model = new App_Model_User();
|
|
42
|
+
|
|
43
|
+
// CORRECT — use the registry or factory
|
|
44
|
+
$model = App_Registry::get('User');
|
|
45
|
+
// or the framework's designated factory method
|
|
46
|
+
$model = $this->getModel('User');
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
The registry ensures models are singletons within a request and allows the framework to manage dependencies. Direct instantiation bypasses this and causes subtle bugs with shared state.
|
|
50
|
+
|
|
51
|
+
## Method naming
|
|
52
|
+
|
|
53
|
+
Public methods use `camelCase`.
|
|
54
|
+
|
|
55
|
+
```php
|
|
56
|
+
// CORRECT
|
|
57
|
+
public function getOrdersByUser(int $userId): array { ... }
|
|
58
|
+
public function processPayment(array $params): bool { ... }
|
|
59
|
+
|
|
60
|
+
// VIOLATION — snake_case
|
|
61
|
+
public function get_orders_by_user($userId) { ... }
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Private and protected methods also use `camelCase`. No `snake_case` anywhere in 1.0 class method names.
|
|
65
|
+
|
|
66
|
+
## File naming and location
|
|
67
|
+
|
|
68
|
+
Class file names match the class name segment after the last underscore, placed in the directory corresponding to the type:
|
|
69
|
+
|
|
70
|
+
- `App_Controller_Orders` → `controllers/Orders.php`
|
|
71
|
+
- `App_Model_User` → `models/User.php`
|
|
72
|
+
- `App_Helper_Date` → `helpers/Date.php`
|
|
73
|
+
|
|
74
|
+
Before adding a new class, check `knowledge/1.0/apps/<repo>/architecture.md` to confirm the correct directory structure for that repo.
|
|
75
|
+
|
|
76
|
+
## Adding new classes
|
|
77
|
+
|
|
78
|
+
Before creating a new class in a 1.0 repo:
|
|
79
|
+
|
|
80
|
+
1. Read `knowledge/1.0/apps/<repo>/architecture.md` — confirm the pattern used in that repo.
|
|
81
|
+
2. Check if a similar class already exists — do not duplicate functionality.
|
|
82
|
+
3. Follow the naming convention exactly, including the type segment.
|
|
83
|
+
4. If you are adding a class to the `library` core repo, be aware it affects all 1.0 apps — review `knowledge/1.0/apps/library/architecture.md` first.
|
|
84
|
+
|
|
85
|
+
## Error handling in controllers
|
|
86
|
+
|
|
87
|
+
Controllers must not let exceptions bubble up to the framework unhandled. Catch specific exceptions, log them, and return an appropriate response.
|
|
88
|
+
|
|
89
|
+
```php
|
|
90
|
+
// CORRECT
|
|
91
|
+
public function processOrder(): void {
|
|
92
|
+
try {
|
|
93
|
+
$result = $this->getModel('Order')->process($this->request->post);
|
|
94
|
+
} catch (App_Exception_Validation $e) {
|
|
95
|
+
$this->response->error($e->getMessage(), 400);
|
|
96
|
+
return;
|
|
97
|
+
} catch (App_Exception_Database $e) {
|
|
98
|
+
error_log('Order processing DB error: ' . $e->getMessage());
|
|
99
|
+
$this->response->error('Internal error', 500);
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
$this->response->success($result);
|
|
103
|
+
}
|
|
104
|
+
```
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# Framework 2.0 (_underscore) Rules
|
|
2
|
+
|
|
3
|
+
These rules apply to all repos under `C:\WWW\2.0`: `_underscore` (core), `worker2` (app), and `api2` (app).
|
|
4
|
+
|
|
5
|
+
## Class naming
|
|
6
|
+
|
|
7
|
+
All framework classes use a leading underscore prefix:
|
|
8
|
+
|
|
9
|
+
- Controllers: `_Controller` base, subclasses `_Controller_<Name>`
|
|
10
|
+
- Models: `_Model` base, subclasses `_Model_<Name>`
|
|
11
|
+
- Workers: `_Worker` base, subclasses `_Worker_<Name>`
|
|
12
|
+
- API handlers: `_Api` base or `_Api_<Name>`
|
|
13
|
+
|
|
14
|
+
Application code in `worker2` and `api2` follows the same leading-underscore convention for framework-extending classes.
|
|
15
|
+
|
|
16
|
+
## Worker contract
|
|
17
|
+
|
|
18
|
+
Every worker class must extend `_Worker` and implement the `run()` method.
|
|
19
|
+
|
|
20
|
+
```php
|
|
21
|
+
// CORRECT
|
|
22
|
+
class _Worker_BatchOrders extends _Worker {
|
|
23
|
+
public function run(): void {
|
|
24
|
+
// process the job payload from $this->payload
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
The `run()` method has no parameters and returns void. Job data is accessed via `$this->payload` (or the framework's equivalent property). Do not add parameters to `run()`.
|
|
30
|
+
|
|
31
|
+
## Worker dispatch — never call directly
|
|
32
|
+
|
|
33
|
+
Workers must only be invoked via the queue dispatcher. Never call a worker's `run()` method directly from a controller, another worker, or any non-queue context.
|
|
34
|
+
|
|
35
|
+
```php
|
|
36
|
+
// CRITICAL VIOLATION — direct call
|
|
37
|
+
$worker = new _Worker_BatchOrders($payload);
|
|
38
|
+
$worker->run();
|
|
39
|
+
|
|
40
|
+
// CORRECT — dispatch through queue
|
|
41
|
+
_Queue::dispatch('_Worker_BatchOrders', $payload);
|
|
42
|
+
// or the framework's equivalent dispatch method
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Direct calls bypass retry logic, error handling, visibility timeout, and monitoring. Even in development or testing, use the sync queue adapter — do not call `run()` directly.
|
|
46
|
+
|
|
47
|
+
## API response envelope (api2 only)
|
|
48
|
+
|
|
49
|
+
Every action method in `api2` controllers must return an array with exactly these keys:
|
|
50
|
+
|
|
51
|
+
```php
|
|
52
|
+
return [
|
|
53
|
+
'success' => true, // bool — true on success, false on error
|
|
54
|
+
'data' => $result, // mixed — the response payload (null on error)
|
|
55
|
+
'errors' => [], // array — list of error messages (empty on success)
|
|
56
|
+
];
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Never return a raw string, a bare array without these keys, or a nested different structure. The envelope is what clients depend on. Deviating from it breaks API consumers silently.
|
|
60
|
+
|
|
61
|
+
For error responses:
|
|
62
|
+
|
|
63
|
+
```php
|
|
64
|
+
return [
|
|
65
|
+
'success' => false,
|
|
66
|
+
'data' => null,
|
|
67
|
+
'errors' => ['Order not found'],
|
|
68
|
+
];
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Checking dependencies before touching shared code
|
|
72
|
+
|
|
73
|
+
`dependsOn` in `knowledge/registry.json` means a repo extends or depends on another repo's classes. Before modifying a class in a dependency repo (e.g. `_underscore` core):
|
|
74
|
+
|
|
75
|
+
1. Read `knowledge/2.0/apps/_underscore/architecture.md`.
|
|
76
|
+
2. Check which repos depend on the class you are modifying (search `dependsOn` in registry.json).
|
|
77
|
+
3. Verify your change does not break the contract expected by dependent repos.
|
|
78
|
+
|
|
79
|
+
Changing a `_Worker` base class method signature without checking `worker2` and `api2` is a common source of runtime failures.
|
|
80
|
+
|
|
81
|
+
## Adding new worker action types
|
|
82
|
+
|
|
83
|
+
Before adding a new worker class in `worker2`:
|
|
84
|
+
|
|
85
|
+
1. Read `knowledge/2.0/apps/worker2/architecture.md` — check the existing action type registry.
|
|
86
|
+
2. Register the new worker in the appropriate config or registry file (the architecture doc identifies where).
|
|
87
|
+
3. Add a corresponding feature doc: `knowledge/2.0/apps/worker2/features/<action-name>.md`.
|
|
88
|
+
|
|
89
|
+
Do not add a worker class without registering it. Unregistered workers cannot be dispatched.
|
|
90
|
+
|
|
91
|
+
## Error handling in workers
|
|
92
|
+
|
|
93
|
+
Workers must catch specific exceptions and handle them appropriately. A worker that throws an uncaught exception will be retried by the queue (up to the configured retry limit) and then dead-lettered.
|
|
94
|
+
|
|
95
|
+
```php
|
|
96
|
+
public function run(): void {
|
|
97
|
+
try {
|
|
98
|
+
$this->processOrder($this->payload['order_id']);
|
|
99
|
+
} catch (_Exception_OrderNotFound $e) {
|
|
100
|
+
// Do not retry — log and discard
|
|
101
|
+
error_log('Worker: order not found, discarding job: ' . $e->getMessage());
|
|
102
|
+
return;
|
|
103
|
+
} catch (_Exception_Database $e) {
|
|
104
|
+
// Retriable — re-throw so queue retries
|
|
105
|
+
error_log('Worker: DB error, will retry: ' . $e->getMessage());
|
|
106
|
+
throw $e;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Distinguish between retriable errors (transient DB/network issues — re-throw) and non-retriable errors (bad data, missing record — log and return).
|