toga-ai 1.0.97 → 1.0.99
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/knowledge/1.0/apps/worker/INDEX.md +1 -0
- package/knowledge/1.0/apps/worker/architecture.md +5 -0
- package/knowledge/1.0/apps/worker/workflows/onboarding-client-to-netsuite-togasupply-sync.md +116 -0
- package/{rules/php/app-framework.md → knowledge/1.0/standards/framework-rules.md} +18 -1
- package/knowledge/2.0/apps/_underscore/architecture.md +5 -0
- package/knowledge/2.0/apps/api2/architecture.md +4 -0
- package/knowledge/2.0/apps/dbchanges2/architecture.md +5 -0
- package/{rules/php/underscore-framework.md → knowledge/2.0/standards/framework-rules.md} +19 -1
- package/{rules/python → knowledge/2.0/standards}/python.md +18 -2
- package/knowledge/CONVENTIONS.md +31 -0
- package/knowledge/INDEX.md +1 -1
- package/knowledge/registry.json +2 -2
- package/knowledge.js +57 -9
- package/package.json +1 -1
- package/rules/README.md +23 -13
- package/scripts/install.js +22 -32
- package/skills/capture/SKILL.md +7 -0
- package/skills/kickoff/SKILL.md +13 -8
|
@@ -5,3 +5,4 @@
|
|
|
5
5
|
| [Worker (1.0 Framework) Architecture](architecture.md) | `worker` is the legacy (**1.0** `App_` framework) **background-job tier**. | worker/index.php, worker/_/app/framework.php, worker/crons/, worker/schedules/, worker/ebs/cron.worker.php, worker/.ebextensions/035_cron.worker.config |
|
|
6
6
|
| [Forecast2 ↔ NetSuite Reconciliation & Trueup Tooling](features/forecast2-netsuite-reconciliation.md) | CLI tools to **audit** and **repair** drift between the production `Forecast` DB (core2) and NetSuite. | test/@dave/reconcile_netsuite_totals.php, test/@dave/analyze_netsuite_forecast_diff.php, test/@dave/trueup_sales.php, test/@dave/trueup_open_orders.php, test/@dave/trueup_opportunities.php, test/@dave/probe_sales_gap_direct.php, worker/crons/toga2/forecast2/common_import_sales_from_netsuite.php |
|
|
7
7
|
| [Prudential: Send Shipments for the Day report (daily cron)](features/send-shipments-for-the-day.md) | Daily cron (9:00 PM) that emails Prudential and Dell stakeholders an Excel report of all devices shipped that day, including tracking number, serial number, emp | worker/crons/notifications/reports/send_shipments_for_the_day.php |
|
|
8
|
+
| [Onboarding a Client to the NetSuite TOGa Supply Sync](workflows/onboarding-client-to-netsuite-togasupply-sync.md) | How to add a new TOGa 2 client to the per-client NetSuite → TOGa Supply importer (`worker/crons/toga2/netsuite/`). | worker/crons/toga2/netsuite/sync_togasupply.php, worker/crons/toga2/netsuite/common_sync_togasupply.php, worker/schedules/cron.worker.sync.json, dbchanges2/_modules/netsuite/2026-04-01 - Parameters.sql |
|
|
@@ -37,6 +37,11 @@ which **self-elects a distinct role** (`notification`, `database`, `infrastructu
|
|
|
37
37
|
> than run inside a web request. It builds on the `library` core (see
|
|
38
38
|
> [Library architecture](../library/architecture.md)).
|
|
39
39
|
|
|
40
|
+
**Critical rules:** all classes use the `App_` prefix (1.0 framework). CRON scripts are plain PHP
|
|
41
|
+
under `crons/`; the schedule (cron registration) is the source of truth for what runs — a script
|
|
42
|
+
that isn't scheduled never executes. Client integrations live under `crons/toga2/<client>/`.
|
|
43
|
+
Use prepared statements for all SQL; never interpolate input.
|
|
44
|
+
|
|
40
45
|
## How a job becomes a cron (the dispatch pipeline)
|
|
41
46
|
|
|
42
47
|
There is **no in-process scheduler**. Jobs are plain PHP files invoked by the OS `crontab`,
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Onboarding a Client to the NetSuite TOGa Supply Sync
|
|
3
|
+
framework: "1.0"
|
|
4
|
+
repo: worker
|
|
5
|
+
project: Worker
|
|
6
|
+
client: shared
|
|
7
|
+
type: workflow
|
|
8
|
+
status: active
|
|
9
|
+
updated: 2026-06-16
|
|
10
|
+
owners: ["dfranks"]
|
|
11
|
+
files:
|
|
12
|
+
- worker/crons/toga2/netsuite/sync_togasupply.php
|
|
13
|
+
- worker/crons/toga2/netsuite/common_sync_togasupply.php
|
|
14
|
+
- worker/schedules/cron.worker.sync.json
|
|
15
|
+
- dbchanges2/_modules/netsuite/2026-04-01 - Parameters.sql
|
|
16
|
+
related: []
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Summary
|
|
20
|
+
How to add a new TOGa 2 client to the per-client NetSuite → TOGa Supply importer
|
|
21
|
+
(`worker/crons/toga2/netsuite/`). Each client gets a thin wrapper cron that sets its
|
|
22
|
+
`CLIENT_CONFIGURATION` + `IS_ENABLED_*` flags and then `require_once`s the shared
|
|
23
|
+
`common_sync_togasupply.php`, which builds all lookups and runs the 6 sync sections
|
|
24
|
+
(Sales Orders, Purchase Orders, Invoices, Item Receipts, Item Fulfillments, Inventory
|
|
25
|
+
Adjustments). Reads come from production NetSuite (REST/SuiteQL); writes go to the TOGa2
|
|
26
|
+
API (`App_Api_Toga2::send`), which lands in that client's `Client_<Name>` database.
|
|
27
|
+
|
|
28
|
+
## Steps
|
|
29
|
+
1. **Create the wrapper** `worker/crons/toga2/netsuite/sync_togasupply_<client>.php`,
|
|
30
|
+
modeled exactly on an existing one (e.g. `sync_togasupply_compass_usa.php`). Set
|
|
31
|
+
`CLIENT_CONFIGURATION` (`client`, `isParentCustomer`, `api`, `secret`,
|
|
32
|
+
`importCustomerPurchaseOrdersOnVendor`) and the constants. Shared constants are the same
|
|
33
|
+
across clients: `NETSUITE_CUSTOM_FIELD_ID__END_USER_CUSTOMER = 3149`,
|
|
34
|
+
`SHIPPING_LOCATION_TYPE_UUID = 527a3995-…`, `WAREHOUSE_LOCATION_TYPE_UUID = 78c24f51-…`,
|
|
35
|
+
and `importCustomerPurchaseOrdersOnVendor = 10ed18ec-0999-53d9-f9db-5892d90f09d4`.
|
|
36
|
+
Get `client` from `Core.Clients` (by name); get `api`/`secret` from
|
|
37
|
+
`Client_<Name>.Apis`. Lint with `C:\xampp7\php\php.exe -l` (prod is PHP 7.2).
|
|
38
|
+
2. **Determine `isParentCustomer` by probing NetSuite — do not assume.** It controls whether
|
|
39
|
+
the common body calls `listChildCustomers()` (parent) or `fetchCustomerById()` (flat).
|
|
40
|
+
`listChildCustomers(N)` runs `SELECT id FROM customer WHERE parent = N`. If the client's
|
|
41
|
+
NetSuite customer has **zero children**, `isParentCustomer` MUST be `false`, or the sync
|
|
42
|
+
builds an empty customer list, registers nothing, and imports nothing (silent no-op). See
|
|
43
|
+
Gotchas.
|
|
44
|
+
3. **Seed the Parameters table** (see the dedicated section below) — REQUIRED or the sync
|
|
45
|
+
aborts on its first parameter read.
|
|
46
|
+
4. **Add the schedule entry** in `worker/schedules/cron.worker.sync.json`: all
|
|
47
|
+
`sync_togasupply_*` jobs run `*/5 * * * *`, `active: 1`. The cron only runs once a
|
|
48
|
+
schedule entry references the file and the worker is redeployed.
|
|
49
|
+
5. **Deploy order matters:** the Parameters seed must hit prod `Client_<Name>` **before or
|
|
50
|
+
with** the cron going live, or the sync 404-aborts (and throws a Sentry error) every 5
|
|
51
|
+
minutes.
|
|
52
|
+
|
|
53
|
+
## Parameters seed (the required, easily-missed step)
|
|
54
|
+
The sync reads/writes per-client sync state via the TOGa2 API `/parameters` endpoint, which
|
|
55
|
+
is **update-only** — a `GET`/`PUT` on a non-existent key returns `404 EV-6`, and because
|
|
56
|
+
`App_Api_Toga2::send()` defaults `$throwExceptionOnApiError = true`, that 404 **throws and
|
|
57
|
+
aborts the entire run** before any record is processed. The `startModeIteration()` ternary
|
|
58
|
+
(`isSuccess ? value : MIN_DATETIME`) looks like it tolerates a missing key, but it never
|
|
59
|
+
gets there because `send()` throws first. Every live client (e.g. Compass) was seeded at
|
|
60
|
+
onboarding; a new client has none.
|
|
61
|
+
|
|
62
|
+
The `Parameters` *table* already exists in every `Client_<Name>` DB — you only seed **rows**.
|
|
63
|
+
Add a `dbchanges2/Client_<Name>/<date> - NetsuiteSyncParameters.sql` migration modeled on
|
|
64
|
+
`dbchanges2/_modules/netsuite/2026-04-01 - Parameters.sql`, inserting all **12** keys:
|
|
65
|
+
- 6 × `NETSUITE_LAST_SYNC_DATETIME_{SALES_ORDERS,PURCHASE_ORDERS,INVOICES,ITEM_RECEIPTS,ITEM_FULFILLMENTS,INVENTORY_ADJUSTMENTS}`
|
|
66
|
+
→ a start datetime. This sets how far back the initial backfill pulls (5-day windows, every
|
|
67
|
+
5 min). Pick deliberately: `2018-01-01` = full history; a recent date = go-forward only.
|
|
68
|
+
- 6 × `NETSUITE_EXECUTION_MODE_*` → `864000-IDLE` (the interval is clamped to
|
|
69
|
+
`MAX_TIME_WINDOW_TO_FETCH_FROM_NETSUITE_SECONDS = 432000` on first run, so the exact
|
|
70
|
+
seed interval is not critical; `IDLE` is what matters).
|
|
71
|
+
|
|
72
|
+
Use `(UUID(), '<key>', '<value>')` rows — `Parameters.uuid` is required and unique.
|
|
73
|
+
dbchanges2 files use the unqualified `Parameters` table name (deployer selects the DB from
|
|
74
|
+
the directory; no `USE`).
|
|
75
|
+
|
|
76
|
+
## Systems involved
|
|
77
|
+
- `worker` cron tier (1.0) — the wrapper + `common_sync_togasupply.php`.
|
|
78
|
+
- Production NetSuite (account `1095849`) via `App_Api_Netsuite_Rest` (SuiteQL/REST) — reads.
|
|
79
|
+
- TOGa2 API (`App_Api_Toga2`) — writes to `Client_<Name>` (+ logs to `Logs_<Name>`).
|
|
80
|
+
- `dbchanges2/Client_<Name>/` — the Parameters seed migration.
|
|
81
|
+
|
|
82
|
+
## Local testing
|
|
83
|
+
- The worker's `[api] _` and api2's `[database]/[databaseClient]/[database1]` on the dev
|
|
84
|
+
laptop point at local (`http://api2/v2` → local Apache/api2 → localhost MySQL). NetSuite is
|
|
85
|
+
the **production** account but reads are read-only.
|
|
86
|
+
- A local end-to-end run needs the client's schemas present locally: import both
|
|
87
|
+
`Client_<Name>` AND `Logs_<Name>` from the read replicas (client2 / logs clusters) into
|
|
88
|
+
local XAMPP. `Logs_<Name>` only needs the **schema** (`mysqldump --no-data`) — its API-log
|
|
89
|
+
history can be many GB; api2 connects to it (link `ClientLogs`) on every request and 500s
|
|
90
|
+
with "Unknown database 'logs_<name>'" if absent.
|
|
91
|
+
- Run the cron in place with `C:\xampp8\php\php.exe` from the `worker/` dir; the autoloader
|
|
92
|
+
walks up to `worker/_` for `__APPROOT__`. `App_Api_Netsuite_Rest::authenticate()` +
|
|
93
|
+
`App_ApiTransaction->setLogging(false)` is the laptop-safe NetSuite pattern (see
|
|
94
|
+
`test/@dave/nsq.php`, a read-only SuiteQL runner).
|
|
95
|
+
- Verified behavior: empty window → 0 writes, checkpoint advances (clean no-op); record
|
|
96
|
+
already present → `PUT` update (idempotent, keyed on `c_netsuiteInternalSalesOrderId`, no
|
|
97
|
+
dupes); record absent → `POST` insert. Checkpoints (`NETSUITE_LAST_SYNC_DATETIME_*`)
|
|
98
|
+
advance by one window and `NETSUITE_EXECUTION_MODE_*` flips back to `…-IDLE` when a section
|
|
99
|
+
finishes cleanly.
|
|
100
|
+
|
|
101
|
+
## Edge cases & escalation
|
|
102
|
+
- **`isParentCustomer` wrong** → silent no-op (true on a childless customer) or missed
|
|
103
|
+
end-user matching. Always probe NetSuite first.
|
|
104
|
+
- **Parameters not seeded in prod** → sync throws every 5 min; surfaces as a `worker1` Sentry
|
|
105
|
+
error and zero imports.
|
|
106
|
+
- **Missing catalog item** on an incoming order may throw mid-run depending on the section;
|
|
107
|
+
the checkpoint does not advance past a throwing window, so it self-heals once the dependency
|
|
108
|
+
exists.
|
|
109
|
+
|
|
110
|
+
## Change history
|
|
111
|
+
- 2026-06-16 — Documented the onboarding process after adding Quad (TRUE-79575): wrapper +
|
|
112
|
+
schedule + the required 12-key Parameters seed; captured the `isParentCustomer` NetSuite
|
|
113
|
+
probe and the local Client_/Logs_ schema requirement. (dfranks)
|
|
114
|
+
|
|
115
|
+
## Related docs
|
|
116
|
+
- [Forecast2 NetSuite reconciliation](../features/forecast2-netsuite-reconciliation.md)
|
|
@@ -1,6 +1,23 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Framework 1.0 (App_) Rules"
|
|
3
|
+
framework: "1.0"
|
|
4
|
+
project: Library
|
|
5
|
+
client: shared
|
|
6
|
+
type: standard
|
|
7
|
+
status: active
|
|
8
|
+
updated: 2026-06-16
|
|
9
|
+
owners: [jcardinal]
|
|
10
|
+
files: []
|
|
11
|
+
related:
|
|
12
|
+
- ../apps/library/architecture.md
|
|
13
|
+
- ../apps/worker/architecture.md
|
|
14
|
+
---
|
|
15
|
+
|
|
1
16
|
# Framework 1.0 (App_) Rules
|
|
2
17
|
|
|
3
|
-
These rules apply to all
|
|
18
|
+
These rules apply to all 1.0 repos: `library` (core) and `worker` (app). They are loaded
|
|
19
|
+
on-demand by `/kickoff` when a 1.0 repo is in scope (not always-on) — see the rules-scoping
|
|
20
|
+
note in `CONVENTIONS.md`.
|
|
4
21
|
|
|
5
22
|
## Class naming
|
|
6
23
|
|
|
@@ -28,6 +28,11 @@ configuration, ACL, and third-party integrations. Every 2.0 project depends on i
|
|
|
28
28
|
Minimum PHP 8.1 (enforced in `_underscore.php`). The current architecture is
|
|
29
29
|
**decoupled**: React handles the frontend; `_underscore` serves as the backend API layer.
|
|
30
30
|
|
|
31
|
+
**Critical rules:** framework classes use a leading-underscore prefix (`_Model_*`, `_Controller_*`,
|
|
32
|
+
`_Worker_*`). Always use parameterized queries / the `_Db` layer — never interpolate input into SQL.
|
|
33
|
+
Beware the **lazy-transaction gotcha**: writes issued outside an explicitly committed transaction
|
|
34
|
+
can be silently dropped — confirm commit semantics before relying on a write.
|
|
35
|
+
|
|
31
36
|
## Entry point & boot sequence
|
|
32
37
|
|
|
33
38
|
Every 2.0 project's `index.php` is just `<?php require '_underscore.php';`.
|
|
@@ -31,6 +31,10 @@ Runtime: PHP 8.2+, AWS Elastic Beanstalk (Apache/httpd → PHP-FPM). The `_under
|
|
|
31
31
|
framework is **pulled at deploy, not vendored**. Composer deps: `sentry/sentry`,
|
|
32
32
|
`aws/aws-sdk-php`, `robrichards/xmlseclibs`, `phpmailer/phpmailer`.
|
|
33
33
|
|
|
34
|
+
**Critical rules:** every action method must return the exact envelope
|
|
35
|
+
`['success' => bool, 'data' => mixed, 'errors' => array]` — never a raw string or bare array.
|
|
36
|
+
On error: `success=false`, `data=null`, `errors=[...]`. Deviating breaks API consumers silently.
|
|
37
|
+
|
|
34
38
|
## Dependencies
|
|
35
39
|
|
|
36
40
|
- **`_underscore` (framework core)** — this API is essentially a metadata-driven layer over
|
|
@@ -33,6 +33,11 @@ Because execution order is purely alphabetical, **file naming is the contract.**
|
|
|
33
33
|
named with a leading **`YYYY-MM-DD`** date so alphabetical sort = chronological order, which
|
|
34
34
|
is the intended execution order.
|
|
35
35
|
|
|
36
|
+
**Critical rules:** every new `.sql` file **must** be named `YYYY-MM-DD<letter> - <Description>.sql`
|
|
37
|
+
with a **mandatory lower-case letter** right after the date (`a` for the first file of that day
|
|
38
|
+
in a folder, then `b`, `c`, …). One folder per database; place client changes under the matching
|
|
39
|
+
`Client_<Name>` folder. Never edit an already-run migration — add a new dated file instead.
|
|
40
|
+
|
|
36
41
|
## File naming convention (the execution contract)
|
|
37
42
|
|
|
38
43
|
1. **Date prefix, ISO format:** every file starts `YYYY-MM-DD`. ISO ordering means
|
|
@@ -1,6 +1,24 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Framework 2.0 (_underscore) Rules"
|
|
3
|
+
framework: "2.0"
|
|
4
|
+
project: _Underscore
|
|
5
|
+
client: shared
|
|
6
|
+
type: standard
|
|
7
|
+
status: active
|
|
8
|
+
updated: 2026-06-16
|
|
9
|
+
owners: [jcardinal]
|
|
10
|
+
files: []
|
|
11
|
+
related:
|
|
12
|
+
- ../apps/_underscore/architecture.md
|
|
13
|
+
- ../apps/worker2/architecture.md
|
|
14
|
+
- ../apps/api2/architecture.md
|
|
15
|
+
---
|
|
16
|
+
|
|
1
17
|
# Framework 2.0 (_underscore) Rules
|
|
2
18
|
|
|
3
|
-
These rules apply to all
|
|
19
|
+
These rules apply to all 2.0 repos: `_underscore` (core), `worker2`, and `api2`. They are
|
|
20
|
+
loaded on-demand by `/kickoff` when a 2.0 repo is in scope (not always-on) — see the
|
|
21
|
+
rules-scoping note in `CONVENTIONS.md`.
|
|
4
22
|
|
|
5
23
|
## Class naming
|
|
6
24
|
|
|
@@ -1,8 +1,24 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Python Rules"
|
|
3
|
+
framework: "2.0"
|
|
4
|
+
project: Talos
|
|
5
|
+
client: shared
|
|
6
|
+
type: standard
|
|
7
|
+
status: active
|
|
8
|
+
updated: 2026-06-16
|
|
9
|
+
owners: [jcardinal]
|
|
10
|
+
files: []
|
|
11
|
+
related:
|
|
12
|
+
- ../apps/talos/architecture.md
|
|
13
|
+
- ../apps/voice-to-voice/architecture.md
|
|
14
|
+
---
|
|
15
|
+
|
|
1
16
|
# Python Rules (applies to all TOGA Python repos)
|
|
2
17
|
|
|
3
18
|
Applies to every TOGA Python project: **talos** (`aegra-api`, `react_agent`,
|
|
4
|
-
`mcp-servers/*`) and **voice-to-voice** (`core/`, `clients/*`,
|
|
5
|
-
|
|
19
|
+
`mcp-servers/*`) and **voice-to-voice** (`core/`, `clients/*`, `lambda_updated/`). Future
|
|
20
|
+
Python repos inherit these by default. Loaded on-demand by `/kickoff` only when a Python repo
|
|
21
|
+
(registry `language: python`) is in scope — see the rules-scoping note in `CONVENTIONS.md`.
|
|
6
22
|
|
|
7
23
|
## Toolchain
|
|
8
24
|
|
package/knowledge/CONVENTIONS.md
CHANGED
|
@@ -168,6 +168,37 @@ those three doc types fails the build (and therefore blocks `capture`'s publish)
|
|
|
168
168
|
`architecture`, and `standard` docs are exempt. Keep the list terse and cap it at ~10 entries,
|
|
169
169
|
folding older ones away.
|
|
170
170
|
|
|
171
|
+
## Rules scoping (universal always-on vs framework on-demand)
|
|
172
|
+
|
|
173
|
+
`rules/` carries only **universal** always-on rules (security, coding-style, git-workflow,
|
|
174
|
+
testing) — installed to `.claude/rules/toga/common/` and loaded every session.
|
|
175
|
+
|
|
176
|
+
**Framework-specific** rules live in the knowledge base as `type: standard` docs and load
|
|
177
|
+
**on-demand via `/kickoff`** for the framework in scope:
|
|
178
|
+
|
|
179
|
+
- `knowledge/1.0/standards/framework-rules.md` — 1.0 `App_` conventions (loads when a 1.0 repo is chosen)
|
|
180
|
+
- `knowledge/2.0/standards/framework-rules.md` — 2.0 `_underscore` conventions (loads when a 2.0 repo is chosen)
|
|
181
|
+
- `knowledge/2.0/standards/python.md` — Python rules (loads only when a repo with registry
|
|
182
|
+
`language: python` — e.g. `talos`, `voice-to-voice` — is in scope)
|
|
183
|
+
|
|
184
|
+
`kickoff-preflight` emits these in its `standards` list; kickoff reads them like any other
|
|
185
|
+
standard. This keeps a session from carrying rules for a framework it isn't working in. When
|
|
186
|
+
adding a Python repo, set `"language": "python"` on its `registry.json` entry so its rules load.
|
|
187
|
+
|
|
188
|
+
## Architecture `## Summary` — must carry "Critical rules"
|
|
189
|
+
|
|
190
|
+
Every `architecture.md` opens with a `## Summary` section. Kickoff loads this summary (not the
|
|
191
|
+
full doc) for any repo pulled in as **awareness only** — e.g. a client-app the developer didn't
|
|
192
|
+
explicitly name (see [Client app-scope](#client-app-scope-apps-on-profilemd)). So the summary is
|
|
193
|
+
often the *only* thing a session sees for that repo.
|
|
194
|
+
|
|
195
|
+
Therefore each `## Summary` must end with a **`**Critical rules:**`** line capturing the few
|
|
196
|
+
load-bearing, must-not-violate rules for that repo — the things that would cause a real defect if
|
|
197
|
+
missed even when the full doc is never opened. Examples: dbchanges2's mandatory `YYYY-MM-DD<letter>`
|
|
198
|
+
filename suffix; api2's exact `{success,data,errors}` response envelope; `_underscore`'s
|
|
199
|
+
lazy-transaction write-drop gotcha. Keep it to 2–4 sentences. This is what lets summaries stay cheap
|
|
200
|
+
without losing safety.
|
|
201
|
+
|
|
171
202
|
## INDEX.md files
|
|
172
203
|
|
|
173
204
|
Never hand-edited. `node knowledge.js index` regenerates the master `INDEX.md`, each
|
package/knowledge/INDEX.md
CHANGED
|
@@ -5,7 +5,7 @@ _Auto-generated by `knowledge.js index`. Do not hand-edit._
|
|
|
5
5
|
## 1.0 framework
|
|
6
6
|
|
|
7
7
|
- **library** (Library) _(framework core)_ — 4 doc(s) → [1.0/apps/library/INDEX.md](1.0/apps/library/INDEX.md)
|
|
8
|
-
- **worker** (Worker) —
|
|
8
|
+
- **worker** (Worker) — 5 doc(s) → [1.0/apps/worker/INDEX.md](1.0/apps/worker/INDEX.md)
|
|
9
9
|
- **togadesk** (TOGa Desk) — 7 doc(s) → [1.0/apps/togadesk/INDEX.md](1.0/apps/togadesk/INDEX.md)
|
|
10
10
|
- **togaview** (TOGa View) — 5 doc(s) → [1.0/apps/togaview/INDEX.md](1.0/apps/togaview/INDEX.md)
|
|
11
11
|
- **webhook** (Webhook) — 1 doc(s) → [1.0/apps/webhook/INDEX.md](1.0/apps/webhook/INDEX.md)
|
package/knowledge/registry.json
CHANGED
|
@@ -14,8 +14,8 @@
|
|
|
14
14
|
{ "repo": "togatech", "project": "TOGA Technology Website", "framework": "standalone", "role": "app", "dependsOn": [] },
|
|
15
15
|
{ "repo": "webhook", "project": "Webhook", "framework": "1.0", "role": "app", "dependsOn": ["library"] },
|
|
16
16
|
{ "repo": "walmarttechservices", "project": "Walmart Tech Services", "framework": "1.0", "role": "app", "dependsOn": ["library"] },
|
|
17
|
-
{ "repo": "talos", "project": "TOGa IQ", "framework": "2.0", "role": "app", "dependsOn": [] },
|
|
17
|
+
{ "repo": "talos", "project": "TOGa IQ", "framework": "2.0", "role": "app", "dependsOn": [], "language": "python" },
|
|
18
18
|
{ "repo": "test", "project": "Test", "framework": "1.0", "role": "app", "dependsOn": ["library"] },
|
|
19
|
-
{ "repo": "voice-to-voice", "project": "TOGa Voice", "framework": "2.0", "role": "app", "dependsOn": [] },
|
|
19
|
+
{ "repo": "voice-to-voice", "project": "TOGa Voice", "framework": "2.0", "role": "app", "dependsOn": [], "language": "python" },
|
|
20
20
|
{ "repo": "ai-bdr", "project": "AI-BDR", "framework": "2.0", "role": "app", "dependsOn": [] }
|
|
21
21
|
]
|
package/knowledge.js
CHANGED
|
@@ -213,22 +213,34 @@ function cmdPreflight(args) {
|
|
|
213
213
|
clientApps = (Array.isArray(data.apps) ? data.apps : []).filter(r => registry.find(x => x.repo === r));
|
|
214
214
|
}
|
|
215
215
|
}
|
|
216
|
-
|
|
217
|
-
|
|
216
|
+
// Dev-named repos load FULL (deep work). Client apps load as SUMMARIES only
|
|
217
|
+
// (awareness) unless the dev also explicitly named them — this keeps a client
|
|
218
|
+
// kickoff cheap: a teammate who only mentions "Compass" gets a one-paragraph
|
|
219
|
+
// summary of every client app (incl. its critical rules) without paying the
|
|
220
|
+
// full-architecture cost of repos they aren't touching. (P1)
|
|
221
|
+
const chosen = [...new Set(devChosen)];
|
|
222
|
+
const scope = [...new Set([...chosen, ...clientApps])]; // full ∪ awareness
|
|
223
|
+
if (!scope.length) {
|
|
218
224
|
console.log(JSON.stringify({ error: 'PREFLIGHT: provide --repos=<repo,repo> and/or --client=<slug> whose profile lists apps' }));
|
|
219
225
|
process.exitCode = 1;
|
|
220
226
|
return;
|
|
221
227
|
}
|
|
222
228
|
const layer = String(args.layer || 'hybrid').toLowerCase();
|
|
223
229
|
const q = args.q ? String(args.q).toLowerCase() : null;
|
|
224
|
-
const unknown =
|
|
230
|
+
const unknown = scope.filter(r => !registry.find(x => x.repo === r));
|
|
225
231
|
|
|
226
|
-
//
|
|
232
|
+
// Standards load per framework the dev is ACTUALLY working in (chosen). If the
|
|
233
|
+
// dev named nothing (client-only kickoff), fall back to every in-scope framework.
|
|
227
234
|
const fwSet = new Set();
|
|
228
|
-
|
|
235
|
+
const fwSource = chosen.length ? chosen : scope;
|
|
236
|
+
for (const r of fwSource) { const e = registry.find(x => x.repo === r); if (e) fwSet.add(e.framework); }
|
|
229
237
|
const frameworks = [...fwSet];
|
|
230
238
|
|
|
231
|
-
// dependency load-set: framework core(s) first, then transitive dependsOn, then
|
|
239
|
+
// dependency load-set: framework core(s) first, then transitive dependsOn, then
|
|
240
|
+
// everything in scope (chosen full + client-app awareness, both as load-set entries;
|
|
241
|
+
// only `chosen` get full reads below — the rest are summarized).
|
|
242
|
+
const scopeFwSet = new Set();
|
|
243
|
+
for (const r of scope) { const e = registry.find(x => x.repo === r); if (e) scopeFwSet.add(e.framework); }
|
|
232
244
|
const seen = new Set();
|
|
233
245
|
const loadSet = [];
|
|
234
246
|
const visit = (r) => {
|
|
@@ -237,8 +249,8 @@ function cmdPreflight(args) {
|
|
|
237
249
|
for (const d of (e.dependsOn || [])) visit(d);
|
|
238
250
|
if (!seen.has(r)) { seen.add(r); loadSet.push(r); }
|
|
239
251
|
};
|
|
240
|
-
for (const fw of
|
|
241
|
-
for (const r of
|
|
252
|
+
for (const fw of scopeFwSet) for (const core of coreReposFor(registry, fw)) visit(core);
|
|
253
|
+
for (const r of scope) visit(r);
|
|
242
254
|
|
|
243
255
|
const docs = allDocs();
|
|
244
256
|
const chosenSet = new Set(chosen);
|
|
@@ -277,6 +289,21 @@ function cmdPreflight(args) {
|
|
|
277
289
|
for (const fw of frameworks) {
|
|
278
290
|
if (wantBackend) standards.push({ ...fileInfo(`${fw}/standards/backend-php.md`), framework: fw });
|
|
279
291
|
if (wantFrontend) standards.push({ ...fileInfo(`${fw}/standards/frontend.md`), framework: fw });
|
|
292
|
+
// Framework-specific convention rules (App_ / _underscore). These used to load
|
|
293
|
+
// always-on for every session; they now ride with the framework's standards so a
|
|
294
|
+
// session only carries the rules for the framework it is actually working in. (P2)
|
|
295
|
+
if (wantBackend) {
|
|
296
|
+
const fr = fileInfo(`${fw}/standards/framework-rules.md`);
|
|
297
|
+
if (fr.exists) standards.push({ ...fr, framework: fw });
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
// Python rules load only when a Python repo (registry `language: python`) is in scope —
|
|
301
|
+
// not for every 2.0 PHP session. (P2)
|
|
302
|
+
const pythonRepos = scope.filter(r => { const e = registry.find(x => x.repo === r); return e && e.language === 'python'; });
|
|
303
|
+
if (wantBackend && pythonRepos.length) {
|
|
304
|
+
const pe = registry.find(x => x.repo === pythonRepos[0]);
|
|
305
|
+
const py = fileInfo(`${pe.framework}/standards/python.md`);
|
|
306
|
+
if (py.exists) standards.push({ ...py, framework: pe.framework, language: 'python' });
|
|
280
307
|
}
|
|
281
308
|
|
|
282
309
|
// client docs (profile + features/workflows filtered to involved frameworks)
|
|
@@ -324,7 +351,28 @@ function cmdPreflight(args) {
|
|
|
324
351
|
// profile.md `apps:` (vs. named by the developer) so it can show "loaded because you
|
|
325
352
|
// mentioned <client>" and the dev can still add/remove.
|
|
326
353
|
const clientScope = { client: slug, fromClient: clientApps, fromDev: devChosen };
|
|
327
|
-
|
|
354
|
+
|
|
355
|
+
// Load-size estimate (P6): rough word/token cost of everything kickoff will read,
|
|
356
|
+
// so the skill can warn when a scope is unusually heavy (e.g. too many full repos)
|
|
357
|
+
// and the dev can narrow `--repos`. Full reads count the doc body; lazy summaries
|
|
358
|
+
// count only the inlined summary text. ~1.3 tokens/word is a coarse heuristic.
|
|
359
|
+
const wc = s => (String(s || '').trim().match(/\S+/g) || []).length;
|
|
360
|
+
const docByRel = new Map(docs.map(d => [d.rel, d]));
|
|
361
|
+
let fullWords = 0, summaryWords = 0;
|
|
362
|
+
for (const r of reads) {
|
|
363
|
+
if (!r.exists) continue;
|
|
364
|
+
if (r.lazy) { summaryWords += wc(r.summary); continue; }
|
|
365
|
+
const d = docByRel.get(r.path);
|
|
366
|
+
if (d) fullWords += wc(d.body);
|
|
367
|
+
}
|
|
368
|
+
const words = fullWords + summaryWords;
|
|
369
|
+
const estimate = {
|
|
370
|
+
words, tokensApprox: Math.round(words * 1.3),
|
|
371
|
+
fullReads: reads.filter(r => r.exists && !r.lazy).length,
|
|
372
|
+
summaries: reads.filter(r => r.exists && r.lazy).length,
|
|
373
|
+
heavy: words > 8000, // kickoff should suggest narrowing --repos above this
|
|
374
|
+
};
|
|
375
|
+
console.log(JSON.stringify({ frameworks, loadSet, unknown, clientScope, estimate, repos: repoOut, standards, client, reads }));
|
|
328
376
|
}
|
|
329
377
|
|
|
330
378
|
/* ------------------------------------------------------------------ */
|
package/package.json
CHANGED
package/rules/README.md
CHANGED
|
@@ -1,26 +1,34 @@
|
|
|
1
1
|
# Rules Directory
|
|
2
2
|
|
|
3
|
-
Rules are **always-
|
|
3
|
+
Rules here are the **universal, always-on guidelines** loaded automatically into every Claude Code session. Unlike skills (on-demand slash commands), rules are passive context — Claude reads them and applies them without being asked.
|
|
4
|
+
|
|
5
|
+
> **Scoping (P2):** only **universal** rules live here and load always-on. **Framework-specific**
|
|
6
|
+
> rules (1.0 `App_`, 2.0 `_underscore`, Python) were moved into the knowledge base under
|
|
7
|
+
> `knowledge/<fw>/standards/framework-rules.md` (and `2.0/standards/python.md`) and are loaded
|
|
8
|
+
> **on-demand by `/kickoff`** for the framework actually in scope. This keeps every session from
|
|
9
|
+
> carrying rules for frameworks it isn't touching. See `knowledge/CONVENTIONS.md`.
|
|
4
10
|
|
|
5
11
|
## What belongs here
|
|
6
12
|
|
|
7
|
-
|
|
13
|
+
Only **language- and framework-agnostic** imperatives: coding style, security, git workflow,
|
|
14
|
+
testing. Framework-specific conventions belong in `knowledge/<fw>/standards/`. Architecture
|
|
15
|
+
documentation goes in `knowledge/`; how-to instructions go in `skills/`.
|
|
8
16
|
|
|
9
17
|
## Directory structure
|
|
10
18
|
|
|
11
19
|
```
|
|
12
20
|
rules/
|
|
13
|
-
├── README.md ← this file
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
└── php/ ← PHP-specific rules
|
|
20
|
-
├── app-framework.md ← 1.0 App_ framework rules
|
|
21
|
-
└── underscore-framework.md ← 2.0 _underscore framework rules
|
|
21
|
+
├── README.md ← this file (not installed)
|
|
22
|
+
└── common/ ← UNIVERSAL — apply to all repos, all frameworks (always-on)
|
|
23
|
+
├── coding-style.md ← language-agnostic style rules
|
|
24
|
+
├── git-workflow.md ← branching, commit messages, PR rules
|
|
25
|
+
├── security.md ← security rules (critical — always followed)
|
|
26
|
+
└── testing.md ← test requirements
|
|
22
27
|
```
|
|
23
28
|
|
|
29
|
+
Framework-specific rules are NOT here — see `knowledge/1.0/standards/framework-rules.md`,
|
|
30
|
+
`knowledge/2.0/standards/framework-rules.md`, and `knowledge/2.0/standards/python.md`.
|
|
31
|
+
|
|
24
32
|
## How to install
|
|
25
33
|
|
|
26
34
|
Run from the team knowledge repo root:
|
|
@@ -29,7 +37,8 @@ Run from the team knowledge repo root:
|
|
|
29
37
|
node scripts/install.js
|
|
30
38
|
```
|
|
31
39
|
|
|
32
|
-
This copies
|
|
40
|
+
This copies `rules/common/` into `.claude/rules/toga/common/` in the target project (and prunes
|
|
41
|
+
any stale pre-P2 framework rules). Claude Code automatically loads rules from `.claude/rules/`.
|
|
33
42
|
|
|
34
43
|
If you want to install into a specific project:
|
|
35
44
|
|
|
@@ -39,7 +48,8 @@ node scripts/install.js /path/to/project
|
|
|
39
48
|
|
|
40
49
|
## How to add new rules
|
|
41
50
|
|
|
42
|
-
1.
|
|
51
|
+
1. Universal rule? Add a `.md` under `common/`. Framework-specific? Add it to the matching
|
|
52
|
+
`knowledge/<fw>/standards/framework-rules.md` (or `python.md`) instead — do not put it here.
|
|
43
53
|
2. Write direct imperatives — "Never do X", "Always do Y". No meta-commentary.
|
|
44
54
|
3. Keep each file under 200 lines.
|
|
45
55
|
4. Run `node scripts/install.js` to push the new rule to all installed projects.
|
package/scripts/install.js
CHANGED
|
@@ -411,37 +411,17 @@ If you close without running /capture, the knowledge is lost.
|
|
|
411
411
|
|
|
412
412
|
---
|
|
413
413
|
|
|
414
|
-
###
|
|
414
|
+
### Skills & agents
|
|
415
415
|
|
|
416
|
-
|
|
416
|
+
Your Claude Code session already lists every available \`/skill\` and specialist agent on each
|
|
417
|
+
launch — this file intentionally does **not** duplicate that catalog (a static copy only drifts
|
|
418
|
+
out of date). The rules that matter:
|
|
417
419
|
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
also tell Claude "use the php-reviewer agent on this file" explicitly.
|
|
424
|
-
|
|
425
|
-
| Agent | When it activates |
|
|
426
|
-
|---|---|
|
|
427
|
-
| **php-reviewer** | Any PHP file is written or edited |
|
|
428
|
-
| **sql-reviewer** | SQL queries detected in PHP or migration files |
|
|
429
|
-
| **framework-pattern-checker** | New PHP class created — validates App_ vs _underscore conventions |
|
|
430
|
-
| **php-build-resolver** | PHP fatal error or parse error in output |
|
|
431
|
-
| **planner** | New feature request — produces phased implementation plan |
|
|
432
|
-
| **knowledge-writer** | You ask Claude to document something — writes correct frontmatter |
|
|
433
|
-
| **session-capture** | Runs inside /capture — extracts and categorizes session learnings |
|
|
434
|
-
| **harness-optimizer** | Run /harness-audit — scores and improves this harness setup |
|
|
435
|
-
|
|
436
|
-
**Multi-agent workflow example:**
|
|
437
|
-
When you say "build the order refund endpoint", Claude will:
|
|
438
|
-
1. Spawn **planner** → get phased implementation plan
|
|
439
|
-
2. Write code → **framework-pattern-checker** validates conventions automatically
|
|
440
|
-
3. Write SQL → **sql-reviewer** checks for injection and N+1 patterns
|
|
441
|
-
4. After editing PHP → **php-reviewer** runs security and style checks
|
|
442
|
-
5. At /capture → **session-capture** extracts decisions for the knowledge base
|
|
443
|
-
|
|
444
|
-
You do not need to manage this manually. The agents are wired to the right triggers.
|
|
420
|
+
- **\`/kickoff\` first, \`/capture\` last** — every session (see above).
|
|
421
|
+
- Specialist agents (php-reviewer, sql-reviewer, framework-pattern-checker, planner,
|
|
422
|
+
knowledge-writer, session-capture, php-build-resolver, harness-optimizer) fire **automatically**
|
|
423
|
+
on the matching file or operation. You can also invoke one explicitly — e.g. "use the
|
|
424
|
+
php-reviewer agent on this file."
|
|
445
425
|
|
|
446
426
|
---
|
|
447
427
|
|
|
@@ -629,14 +609,24 @@ function main() {
|
|
|
629
609
|
}
|
|
630
610
|
console.log(' ✓ Skills (' + skillNames.length + '): ' + fmtStats(skillStats));
|
|
631
611
|
|
|
632
|
-
// Rules —
|
|
633
|
-
|
|
634
|
-
|
|
612
|
+
// Rules — only the UNIVERSAL set (rules/common) installs always-on. Framework-specific
|
|
613
|
+
// rules (1.0 App_, 2.0 _underscore, Python) now live in the knowledge base under
|
|
614
|
+
// <fw>/standards/ and load on-demand via /kickoff per the framework in scope (P2), so a
|
|
615
|
+
// session never carries rules for a framework it isn't working in.
|
|
616
|
+
const rulesToga = path.join(claudeDir, 'rules', 'toga');
|
|
617
|
+
const rulesSrc = path.join(harnessDir, 'rules', 'common');
|
|
618
|
+
const rulesDest = path.join(rulesToga, 'common');
|
|
635
619
|
let rulesStats = { added: 0, updated: 0, unchanged: 0 };
|
|
636
620
|
if (fs.existsSync(rulesSrc)) {
|
|
637
621
|
try { rulesStats = copyDir(rulesSrc, rulesDest, { updateIfChanged: true }); }
|
|
638
622
|
catch (e) { errors.push('rules: ' + e.message); }
|
|
639
623
|
}
|
|
624
|
+
// Prune framework-specific rules + the meta README left behind by pre-P2 installs so
|
|
625
|
+
// they stop loading always-on (their content now lives in <fw>/standards/).
|
|
626
|
+
for (const stale of ['php', 'python', 'README.md']) {
|
|
627
|
+
const p = path.join(rulesToga, stale);
|
|
628
|
+
try { if (fs.existsSync(p)) fs.rmSync(p, { recursive: true, force: true }); } catch (_) { /* best-effort cleanup */ }
|
|
629
|
+
}
|
|
640
630
|
console.log(' ✓ Rules (' + countMd(rulesDest) + '): ' + fmtStats(rulesStats));
|
|
641
631
|
|
|
642
632
|
// Agents — update if content changed
|
package/skills/capture/SKILL.md
CHANGED
|
@@ -343,6 +343,13 @@ related: []
|
|
|
343
343
|
---
|
|
344
344
|
|
|
345
345
|
## Summary
|
|
346
|
+
<2–4 sentence overview of the repo. **End the Summary with a `**Critical rules:**` line** — the
|
|
347
|
+
few load-bearing must-knows that have to ride along when only this summary is loaded (kickoff
|
|
348
|
+
loads the Summary, not the full doc, for repos pulled in as client-app awareness). See
|
|
349
|
+
`CONVENTIONS.md` → *Architecture Summary*. Examples: dbchanges2's mandatory `YYYY-MM-DD<letter>`
|
|
350
|
+
filename suffix; api2's exact `{success,data,errors}` envelope; `_underscore`'s lazy-transaction
|
|
351
|
+
write-drop gotcha.>
|
|
352
|
+
|
|
346
353
|
## <system sections: components, data, flows, key decisions>
|
|
347
354
|
```
|
|
348
355
|
|
package/skills/kickoff/SKILL.md
CHANGED
|
@@ -35,16 +35,13 @@ description: Start-of-session context loader for TOGA Technology projects. Run t
|
|
|
35
35
|
|
|
36
36
|
## Arguments — text passed after `/kickoff` never skips any step
|
|
37
37
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
to shortcut the flow, no matter how actionable it looks.
|
|
38
|
+
Trailing text (e.g. `/kickoff worker2 backend fix for Compass`, even a detailed paragraph
|
|
39
|
+
naming files and line numbers) is the developer's description of today's work — **input to the
|
|
40
|
+
Step 2 interview, not permission to shortcut the flow** (the STOP gate above governs this).
|
|
42
41
|
|
|
43
42
|
- **Step 0 (auto-update check) ALWAYS runs first**, with or without arguments.
|
|
44
|
-
- Use the argument text to **pre-fill
|
|
45
|
-
|
|
46
|
-
- Never treat the argument as an instruction to start coding — or even to start *reading
|
|
47
|
-
the named files* — before Steps 0–6 complete. Investigation IS work; it waits for priming.
|
|
43
|
+
- Use the argument text to **pre-fill** Step 2 answers (framework, layer, repo, client, task);
|
|
44
|
+
ask only about what's still missing or ambiguous.
|
|
48
45
|
|
|
49
46
|
## Step 0 — Auto-update check (runs before anything else, even with arguments)
|
|
50
47
|
|
|
@@ -247,6 +244,14 @@ Tell the developer concisely:
|
|
|
247
244
|
say so explicitly: "No knowledge captured yet for X — `capture` will build it as you work."
|
|
248
245
|
- Confirm you're primed and ready for their first task.
|
|
249
246
|
|
|
247
|
+
**Heavy-load check (from preflight `estimate`).** Preflight returns
|
|
248
|
+
`estimate {words, tokensApprox, fullReads, summaries, heavy}`. If `estimate.heavy` is true
|
|
249
|
+
(load > ~8k words — usually too many full repos), tell the developer and suggest narrowing:
|
|
250
|
+
> "This scope loads ~`tokensApprox` tokens across `fullReads` full repos. If you're only
|
|
251
|
+
> touching one, name just that repo (`also drop <repo>`) so the rest stay as summaries."
|
|
252
|
+
Client `apps` load as cheap summaries by default, so a large client rarely trips this — it's
|
|
253
|
+
usually too many repos passed to `--repos`.
|
|
254
|
+
|
|
250
255
|
## New-repo onboarding (when the developer names a repo not in `registry.json`)
|
|
251
256
|
|
|
252
257
|
Ask **all** of the following — assume nothing (you may offer a suggested default, e.g.
|