toga-ai 1.0.78 → 1.0.80
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.
|
@@ -4,5 +4,6 @@
|
|
|
4
4
|
|-----|---------|-------|
|
|
5
5
|
| [_underscore Framework Architecture](architecture.md) | `_underscore` is the shared PHP backend framework for **all 2.0 applications**. | _underscore/_underscore.php, _underscore/Loader.php, _underscore/Framework.php, _underscore/Model.php, _underscore/Database.php, _underscore/Query.php, _underscore/Route.php, _underscore/Component.php |
|
|
6
6
|
| [Carrier Shipping Labels (UPS/FedEx) & NetSuite Item Fulfillment](features/carrier-shipping-labels.md) | Backend mechanics behind TOGa Supply's Fulfill & Ship: buying a carrier label (UPS/FedEx), persisting it, and creating the NetSuite Item Fulfillment with tracki | _underscore/Model/Client/ItemFulfillment.php, _underscore/Component/Library/Carriers/Ups/Ups.php, _underscore/Trait/Netsuite/ItemFulfillment.php, _underscore/Trait/Netsuite/SalesOrder.php, _underscore/Component/Library/NetSuite/NetSuite.php, _underscore/Model/Client/TrackingNumber.php, _underscore/Model/Client/ShippingMethod.php, _underscore/Model.php, _underscore/Cloud.php |
|
|
7
|
+
| [Client Email Template Sending](features/email-template-sending.md) | `_Model_Client_EmailTemplate` sends a stored, client-defined email template by UUID. | _underscore/Model/Client/EmailTemplate.php, _underscore/Model/Client/EmailTemplateOutgoingEmailAddress.php, _underscore/Email.php |
|
|
7
8
|
| [Recursive Item Fulfillments (upstream mirroring)](features/recursive-item-fulfillments.md) | In a multi-tier supply chain a sales order (SO) spawns a purchase order (PO) that becomes another SO downstream, and so on. | _underscore/Model/Client/ItemFulfillment.php, _underscore/Model/Client/ItemFulfillmentItem.php, _underscore/Model/Client/ItemFulfillmentItemUnit.php, _underscore/Model/Client/ItemFulfillmentPackage.php, _underscore/Model/Compass/AdvanceShippingNotice.php, dbchanges2/Core/2026-02-13 - 75601 - RecursiveItemFulfillmentCreation.sql, dbchanges2/Core/2026-06-04 - RecursiveItemFulfillmentPut.sql |
|
|
8
9
|
| [Tracking-Number Bridge Migration (ASN / Item Fulfillment / Item Receipt)](features/tracking-number-bridges.md) | Shipment tracking numbers used to live as **scalar FK columns** (`trackingNumberId`, `returnTrackingNumberId`) directly on the lowest-level "unit"/"item" tables | _underscore/Model/Client/AdvanceShippingNoticeItemUnit.php, _underscore/Model/Client/AdvanceShippingNoticeItemUnits/TrackingNumber.php, _underscore/Model/Client/ItemFulfillmentItemUnits/TrackingNumber.php, _underscore/Model/Client/ItemFulfillment.php, _underscore/Model/Prudential/AdvanceShippingNotice.php, _underscore/Model/Compass/AdvanceShippingNotice.php, _underscore/Trait/Netsuite/ItemFulfillment.php, api2/Component/Api/Cxml/Cxml.php, dbchanges2/Client/2026-06-10 - TrackingNumberBridges.sql, dbchanges2/Core/2026-06-10 - TrackingNumberBridges.sql |
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Client Email Template Sending
|
|
3
|
+
framework: "2.0"
|
|
4
|
+
repo: _underscore
|
|
5
|
+
project: _Underscore
|
|
6
|
+
client: shared
|
|
7
|
+
type: feature
|
|
8
|
+
status: active
|
|
9
|
+
updated: 2026-06-15
|
|
10
|
+
owners: ["jcardinal"]
|
|
11
|
+
files:
|
|
12
|
+
- _underscore/Model/Client/EmailTemplate.php
|
|
13
|
+
- _underscore/Model/Client/EmailTemplateOutgoingEmailAddress.php
|
|
14
|
+
- _underscore/Email.php
|
|
15
|
+
related: []
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Summary
|
|
19
|
+
|
|
20
|
+
`_Model_Client_EmailTemplate` sends a stored, client-defined email template by UUID. It
|
|
21
|
+
loads the template from the client DB (`EmailTemplates` table), merges any template-stored
|
|
22
|
+
recipient addresses, substitutes `{variable}` placeholders in the subject and body, and
|
|
23
|
+
sends via `_Email`. The only client-specific input the send actually needs is the
|
|
24
|
+
**client identifier** (used to scope the outgoing mail via `_Email::setClientIdentifier()`).
|
|
25
|
+
|
|
26
|
+
## Key files / entry points
|
|
27
|
+
|
|
28
|
+
- `_underscore/Model/Client/EmailTemplate.php` — the model. Three relevant methods:
|
|
29
|
+
- **`sendEmail(&$api, $uuid, $to, $cc, $bcc, ...$args): bool`** — the scripted-API entry
|
|
30
|
+
point. First param is `&$api` per the Record Script contract (see backend-php standard,
|
|
31
|
+
*Record Scripts*). The only thing it uses from `$api` is `$api->client->clientIdentifier`.
|
|
32
|
+
- **`send(string $clientIdentifier, $uuid, $to, $cc, $bcc, ...$args): bool`** — the
|
|
33
|
+
non-API entry point. Caller passes the client identifier directly; no `$api` object.
|
|
34
|
+
- **`dispatch(string $clientIdentifier, ...): bool`** (private) — the shared body both
|
|
35
|
+
entry points call. Holds all the real logic.
|
|
36
|
+
- `_Model_Client_EmailTemplateOutgoingEmailAddress` — per-template stored TO/CC/BCC
|
|
37
|
+
addresses (`toCcBcc` enum), merged into the caller-supplied recipients.
|
|
38
|
+
- `_underscore/Email.php` — `_Email` requires a non-empty `clientIdentifier` (throws
|
|
39
|
+
`clientIdentifier is required` otherwise) and uses it as the CloudWatch log stream name.
|
|
40
|
+
|
|
41
|
+
## How it works
|
|
42
|
+
|
|
43
|
+
1. Load the template by `uuid`; return `false` immediately if `!isActive`.
|
|
44
|
+
2. Search `EmailTemplateOutgoingEmailAddress` for the template and append each stored
|
|
45
|
+
address to `$to` / `$cc` / `$bcc` by its `toCcBcc` value.
|
|
46
|
+
3. Build `_Email`, set the client identifier, add recipients, set From from the template's
|
|
47
|
+
`sendFromEmailAddress` / `sendFromName`.
|
|
48
|
+
4. Replace `{key}` placeholders in subject and body from the `$args` variadic map
|
|
49
|
+
(`replaceTemplateVariables`), then `send()`.
|
|
50
|
+
|
|
51
|
+
Both `sendEmail()` and `send()` are thin wrappers that forward to `dispatch()`, so the API
|
|
52
|
+
and non-API paths run identical code — no behavioral drift between them.
|
|
53
|
+
|
|
54
|
+
## Data model
|
|
55
|
+
|
|
56
|
+
- `EmailTemplates` (client DB): `uuid`, `isActive`, `sendFromEmailAddress`, `sendFromName`,
|
|
57
|
+
`priority`, `subject`, `body`.
|
|
58
|
+
- `EmailTemplateOutgoingEmailAddress` (client DB): `emailTemplateId`, `emailAddress`,
|
|
59
|
+
`toCcBcc` (`TO`/`CC`/`BCC`).
|
|
60
|
+
|
|
61
|
+
## Client variations
|
|
62
|
+
|
|
63
|
+
The model is shared; client-specific senders pass their own identifier. Workers use a class
|
|
64
|
+
constant (e.g. `self::CLIENT_IDENTIFIER`); model/interceptor code that already has an `$api`
|
|
65
|
+
can keep using `sendEmail($api, ...)`.
|
|
66
|
+
|
|
67
|
+
## Gotchas / known issues
|
|
68
|
+
|
|
69
|
+
- **Use `send()` from any non-API context (workers, cron, internal code).** Before
|
|
70
|
+
2026-06-15 the only entry point was `sendEmail(&$api, ...)`, so callers with no API
|
|
71
|
+
context faked one: `$api = (object)['client' => (object)['clientIdentifier' => …]]`.
|
|
72
|
+
That hack is obsolete — pass the identifier to `send()` instead.
|
|
73
|
+
- **`sendEmail()`'s signature is load-bearing for scripted APIs** — the Record Script engine
|
|
74
|
+
(`api2/Component/Api/V2/V2.php`, ~line 3594) calls the method with `api` as a named
|
|
75
|
+
argument, so the first param must stay `&$api`. Do not "clean it up" by removing it.
|
|
76
|
+
- `_Email::send()` throws if the client identifier is empty — `send('')` will fail at send
|
|
77
|
+
time, not at call time.
|
|
78
|
+
|
|
79
|
+
## Change history
|
|
80
|
+
|
|
81
|
+
- 2026-06-15 — Added non-API `send(clientIdentifier, …)` entry point + private `dispatch()`;
|
|
82
|
+
`sendEmail(&$api, …)` kept unchanged as a wrapper for backward compatibility. Migrated the
|
|
83
|
+
`worker2` Compass Report and AI-BDR NetSuite callers off the fake-`$api` hack. (jcardinal)
|
|
84
|
+
|
|
85
|
+
## Related docs
|
|
86
|
+
|
|
87
|
+
- `2.0/standards/backend-php.md` — *Record Scripts* (the `&$api` contract).
|
|
88
|
+
- `2.0/apps/api2/architecture.md` — scripted-API dispatch in `V2.php`.
|
|
@@ -5,7 +5,7 @@ project: _Underscore
|
|
|
5
5
|
client: shared
|
|
6
6
|
type: standard
|
|
7
7
|
status: active
|
|
8
|
-
updated: 2026-06-
|
|
8
|
+
updated: 2026-06-15
|
|
9
9
|
owners: [jcardinal]
|
|
10
10
|
files: []
|
|
11
11
|
related:
|
|
@@ -425,6 +425,13 @@ if ($user->load()) { /* found */ }
|
|
|
425
425
|
|
|
426
426
|
* Use tabs for indentation, not spaces.
|
|
427
427
|
|
|
428
|
+
#### **Line Endings**
|
|
429
|
+
|
|
430
|
+
* The 2.0 repos have **no `.gitattributes`** and contain a **mix of LF and CRLF files** — there is no single repo-wide line ending. Core files (e.g. `Model.php`, `Loader.php`, `Email.php`) are CRLF; many others (e.g. `Model/Client/EmailTemplate.php`) are LF.
|
|
431
|
+
* **Preserve a file's existing line ending when you edit it.** Do not let an editor or tool silently convert the whole file (a common LF→CRLF flip on Windows).
|
|
432
|
+
* A whole-file ending flip turns a one-line change into a full-file rewrite in `git diff`, burying the real change, breaking `git blame`, and inviting merge conflicts.
|
|
433
|
+
* **Check before committing:** if `git diff --stat` shows a tiny edit as a near-total rewrite, the endings flipped. Restore them — e.g. for an LF file: `tr -d '\r' < file > file.tmp && mv file.tmp file` — then re-stage.
|
|
434
|
+
|
|
428
435
|
#### **Braces**
|
|
429
436
|
|
|
430
437
|
* Opening braces for classes, methods and control structures (`if`, `else`, `for`, `while`, etc.) should go on the same line.
|
package/knowledge/INDEX.md
CHANGED
|
@@ -11,7 +11,7 @@ _Auto-generated by `knowledge.js index`. Do not hand-edit._
|
|
|
11
11
|
|
|
12
12
|
## 2.0 framework
|
|
13
13
|
|
|
14
|
-
- **_underscore** (_Underscore) _(framework core)_ —
|
|
14
|
+
- **_underscore** (_Underscore) _(framework core)_ — 6 doc(s) → [2.0/apps/_underscore/INDEX.md](2.0/apps/_underscore/INDEX.md)
|
|
15
15
|
- **worker2** (Worker) — 6 doc(s) → [2.0/apps/worker2/INDEX.md](2.0/apps/worker2/INDEX.md)
|
|
16
16
|
- **api2** (API) — 1 doc(s) → [2.0/apps/api2/INDEX.md](2.0/apps/api2/INDEX.md)
|
|
17
17
|
- **dbchanges2** (Database Changes) _(framework core)_ — 1 doc(s) → [2.0/apps/dbchanges2/INDEX.md](2.0/apps/dbchanges2/INDEX.md)
|
package/package.json
CHANGED
package/skills/kickoff/SKILL.md
CHANGED
|
@@ -5,16 +5,36 @@ description: Start-of-session context loader for TOGA Technology projects. Run t
|
|
|
5
5
|
|
|
6
6
|
# Kickoff — prime a coding session from the team knowledge base
|
|
7
7
|
|
|
8
|
+
> ## 🛑 STOP — THIS IS A BLOCKING GATE. READ BEFORE DOING ANYTHING ELSE.
|
|
9
|
+
>
|
|
10
|
+
> When `/kickoff` is invoked you **MUST** complete Steps 0–6 of this skill **before**
|
|
11
|
+
> any other tool call — no `Read`, `Grep`, `Glob`, `Edit`, `Write`, `Bash`, no agent
|
|
12
|
+
> spawn, no investigation, no answering the developer's question. Priming comes first,
|
|
13
|
+
> **always**.
|
|
14
|
+
>
|
|
15
|
+
> **Trailing text after `/kickoff` is NEVER a reason to skip priming.** A long, detailed
|
|
16
|
+
> paragraph describing a specific task (file paths, line numbers, a design question) is
|
|
17
|
+
> *still just the Step 2 task description* — it is input to the interview, **not**
|
|
18
|
+
> permission to start working. The more detailed and actionable the request looks, the
|
|
19
|
+
> more tempting it is to dive in — **resist that.** If you find yourself about to open a
|
|
20
|
+
> file the developer named before you have loaded the knowledge base, you are violating
|
|
21
|
+
> this gate. Stop and run Steps 0–6 first.
|
|
22
|
+
>
|
|
23
|
+
> Only after Step 5's "primed and ready" summary (and Step 6's plan, for non-trivial work)
|
|
24
|
+
> may you touch the task itself.
|
|
25
|
+
|
|
8
26
|
## Arguments — text passed after `/kickoff` never skips any step
|
|
9
27
|
|
|
10
|
-
`/kickoff` may be invoked with trailing text (e.g. `/kickoff worker2 backend fix for Compass`)
|
|
11
|
-
|
|
12
|
-
|
|
28
|
+
`/kickoff` may be invoked with trailing text (e.g. `/kickoff worker2 backend fix for Compass`),
|
|
29
|
+
including a long, specific paragraph naming exact files, line numbers, and a concrete
|
|
30
|
+
change. That text is the developer's description of today's work — it is **not** permission
|
|
31
|
+
to shortcut the flow, no matter how actionable it looks.
|
|
13
32
|
|
|
14
33
|
- **Step 0 (auto-update check) ALWAYS runs first**, with or without arguments.
|
|
15
34
|
- Use the argument text to **pre-fill answers** to the Step 2 interview (framework, layer,
|
|
16
35
|
repo, client, task). Only ask about whatever is still missing or ambiguous.
|
|
17
|
-
- Never treat the argument as an instruction to start coding
|
|
36
|
+
- Never treat the argument as an instruction to start coding — or even to start *reading
|
|
37
|
+
the named files* — before Steps 0–6 complete. Investigation IS work; it waits for priming.
|
|
18
38
|
|
|
19
39
|
## Step 0 — Auto-update check (runs before anything else, even with arguments)
|
|
20
40
|
|