toga-ai 1.0.802 → 1.0.804
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 +10 -0
- package/knowledge/1.0/apps/worker/features/netsuite-togasupply-per-client-sync.md +49 -1
- package/knowledge/clients/nychh/features/netsuite-transfer-order-import.md +90 -15
- package/knowledge/clients/nychh/profile.md +10 -6
- package/package.json +1 -1
- package/scripts/hooks/design-fidelity-reminder.js +99 -0
package/.claude/settings.json
CHANGED
|
@@ -70,6 +70,16 @@
|
|
|
70
70
|
"timeout": 3000
|
|
71
71
|
}
|
|
72
72
|
]
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
"matcher": "Edit|Write|MultiEdit",
|
|
76
|
+
"hooks": [
|
|
77
|
+
{
|
|
78
|
+
"type": "command",
|
|
79
|
+
"command": "node \"$CLAUDE_PROJECT_DIR/.claude/hooks/toga/design-fidelity-reminder.js\"",
|
|
80
|
+
"timeout": 3000
|
|
81
|
+
}
|
|
82
|
+
]
|
|
73
83
|
}
|
|
74
84
|
],
|
|
75
85
|
"PostToolUse": [
|
|
@@ -6,7 +6,7 @@ project: Worker
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-09-
|
|
9
|
+
updated: 2026-09-14
|
|
10
10
|
owners: ["dfranks", "bala", "jcardinal", "mhammontree", "snaredla", "rgirish"]
|
|
11
11
|
files:
|
|
12
12
|
- worker/crons/toga2/netsuite/common_sync_togasupply.php
|
|
@@ -441,6 +441,54 @@ true all use `ZERO_DOLLAR_HOLD` detection.
|
|
|
441
441
|
**Status: written, NOT committed and NOT deployed** as of 2026-09-08. Touches `worker` and `library`,
|
|
442
442
|
so both deploy together (the standing rule for this pair).
|
|
443
443
|
|
|
444
|
+
### ⚠ An OUT-OF-SCOPE transfer-order destination customer wedged INVENTORY_ADJUSTMENTS — the importer now SKIPS it (fixed + deployed 2026-09-14)
|
|
445
|
+
|
|
446
|
+
The INVENTORY_ADJUSTMENTS section froze at `1-RUNNING` (window shrinking) on NYCHH because a
|
|
447
|
+
transfer-order-backed fulfillment imported by its on-demand cascade hit
|
|
448
|
+
`POST /v2/transfer-orders` → **EV-10 "Column 'destinationLocationId' cannot be null"** every run.
|
|
449
|
+
|
|
450
|
+
- **Root cause:** on a stocking-SO transfer order the **destination** resolves from the NetSuite
|
|
451
|
+
**customer** → `Locations.c_netsuiteInternalCustomerId`. The offending order's customer was **not
|
|
452
|
+
in scope** — no client `Locations` row — so the destination was omitted and the NOT NULL insert
|
|
453
|
+
1048'd. The **origin** (the warehouse) resolved fine.
|
|
454
|
+
- **How an out-of-scope customer gets there:** the INVENTORY_ADJUSTMENTS cascade scopes by
|
|
455
|
+
**warehouse**, then imports the referenced fulfillment whose sales order can belong to a
|
|
456
|
+
**different** customer. A shared warehouse (e.g. NYCHH's HQ) fulfills for many customers, so
|
|
457
|
+
out-of-scope orders follow.
|
|
458
|
+
- **"In the Customers table" is NOT a tenant-scope signal.** The offending customer existed in the
|
|
459
|
+
client's `Customers` table but had no `Locations` row. The reliable in-scope test is **"maps to a
|
|
460
|
+
client Location"**, not "is in Customers".
|
|
461
|
+
- **Fix (skip, don't force):** `App_Api_Toga2::syncTransferOrderFromNetsuite()` now `error_log`s and
|
|
462
|
+
`return null`s when the destination customer maps to no client Location, instead of POSTing a null
|
|
463
|
+
destination. Two log messages distinguish **out-of-scope customer** from **in-scope but no Location
|
|
464
|
+
(backfill a Location)**. Destination resolves **before** origin and the skip runs first, so a
|
|
465
|
+
skipped order writes nothing (no stray origin-warehouse Location row). The cascade
|
|
466
|
+
(`syncItemFulfillmentFromNetsuite` returns the fulfillment uuid or null;
|
|
467
|
+
`attachInventoryAdjustmentToItemFulfillment` treats null as nothing-to-link) skips cleanly on the
|
|
468
|
+
null instead of re-throwing at the fulfillment item-match. **No** empty-map guard: an empty
|
|
469
|
+
customer→Location map is a real warehouse-only-client state where skip-with-loud-log is correct.
|
|
470
|
+
Full detail on the [NYCHH transfer-order import doc](../../../clients/nychh/features/netsuite-transfer-order-import.md).
|
|
471
|
+
|
|
472
|
+
### ⚠ A shared library function signature has THREE call sites — one is a DIRECT cron call (grep ALL repos)
|
|
473
|
+
|
|
474
|
+
The fix above added a param to `syncTransferOrderFromNetsuite()`. Only the two **library** callers
|
|
475
|
+
(`library/app/api/toga2.php:923` from `syncSalesOrder`, `:4984` from the `syncItemFulfillment`
|
|
476
|
+
cascade) were updated. The **third** caller is a **direct cron call** in this engine —
|
|
477
|
+
`common_sync_togasupply.php:782` (the SALES_ORDERS section) — and it still passed 7 args, so
|
|
478
|
+
SALES_ORDERS froze at `1-RUNNING` with `Logs.Event` issue **754** *"Too few arguments to function
|
|
479
|
+
`App_Api_Toga2::syncTransferOrderFromNetsuite()`, 7 passed … at least 8 expected"*. It surfaced only
|
|
480
|
+
when SALES_ORDERS ran; INVENTORY_ADJUSTMENTS reaches the function via the library-internal callers.
|
|
481
|
+
Fixed by adding the 3rd arg at `:782`.
|
|
482
|
+
|
|
483
|
+
> **Lesson:** when changing a shared **library** function signature, **grep ALL repos** (library +
|
|
484
|
+
> worker) for callers. Worker crons call library classes through the autoloader with **no `require`**,
|
|
485
|
+
> so an import/`require` search misses the cron call sites. `php -l` on the library file also will not
|
|
486
|
+
> catch a stale caller in another repo.
|
|
487
|
+
|
|
488
|
+
Also corrected two stale comments in `common_sync_togasupply.php` (ITEM_FULFILLMENTS and
|
|
489
|
+
INVENTORY_ADJUSTMENTS sections) that claimed a per-record/per-client try/catch that no longer exists
|
|
490
|
+
(all such catches were removed 2026-08-27 — the sync fails loud).
|
|
491
|
+
|
|
444
492
|
### WARNING: transfer-order status refresh is nested INSIDE `IS_ENABLED_INTEGRATION_SALES_ORDERS`
|
|
445
493
|
|
|
446
494
|
Turning sales orders off silently turns **transfer-order status updates** off too - the refresh lives
|
|
@@ -6,7 +6,7 @@ project: Library
|
|
|
6
6
|
client: nychh
|
|
7
7
|
type: client-feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-09-
|
|
9
|
+
updated: 2026-09-14
|
|
10
10
|
owners: [jcardinal, bala, rgirish]
|
|
11
11
|
files:
|
|
12
12
|
- library/app/api/toga2.php
|
|
@@ -122,14 +122,74 @@ full evidence table (location 75 → warehouse 117, the only `allowSending` sour
|
|
|
122
122
|
locations carry a customer id, 7 carry a location id) is in
|
|
123
123
|
[NYCHH TransferOrder → NetSuite push](./transfer-order-netsuite-push.md).
|
|
124
124
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
125
|
+
### ⚠ RESOLVED (2026-09-14) — an OUT-OF-SCOPE destination customer was wedging INVENTORY_ADJUSTMENTS; the importer now SKIPS such transfer orders
|
|
126
|
+
|
|
127
|
+
`TransferOrders.destinationLocationId` is also NOT NULL, and the INVENTORY_ADJUSTMENTS section was
|
|
128
|
+
stuck `RUNNING` (window shrinking) because `POST /v2/transfer-orders` for **TO 277871**
|
|
129
|
+
(SO **7102750** / tranId **277871**) failed **EV-10 "Column 'destinationLocationId' cannot be null"**
|
|
130
|
+
every run — origin resolved, destination did not.
|
|
131
|
+
|
|
132
|
+
**Root cause (proven in prod):** the destination resolves from the NetSuite **customer** →
|
|
133
|
+
`Locations.c_netsuiteInternalCustomerId`. TO 277871's customer (entity) is **3997 "1896 Laurel Hill
|
|
134
|
+
Management, LLC"** — **not** a NYCHH customer, no parent in NetSuite, and **no row in
|
|
135
|
+
`Client_Nychh.Locations`**. So the destination could not resolve, `destinationLocation` was omitted,
|
|
136
|
+
and the NOT NULL insert 1048'd. The **origin** resolved fine (line location 6 "HQ" → warehouse
|
|
137
|
+
Location 78). With fail-loud + no per-record try/catch, the one bad record threw every run and shrank
|
|
138
|
+
the adaptive window → the whole section wedged.
|
|
139
|
+
|
|
140
|
+
**Why an out-of-scope customer reaches NYCHH at all:** the INVENTORY_ADJUSTMENTS cascade scopes by
|
|
141
|
+
**warehouse** (an adjustment at the shared **HQ** warehouse, loc 6, maps to NYCHH), then imports the
|
|
142
|
+
referenced fulfillment whose sales order belongs to a **different** customer. HQ fulfills for many
|
|
143
|
+
customers, so more out-of-scope orders follow.
|
|
144
|
+
|
|
145
|
+
> **Key data fact — "in the Customers table" is NOT a tenant-scope signal.** Customer 3997 DOES exist
|
|
146
|
+
> in `Client_Nychh.Customers` (id 19) but has **no Location**. The reliable in-scope test is **"maps
|
|
147
|
+
> to a client Location"** (`c_netsuiteInternalCustomerId` on a `Locations` row), not "is in
|
|
148
|
+
> Customers".
|
|
149
|
+
|
|
150
|
+
**Fix (developer decision "skip out-of-scope TOs"; CTO AGREE, php-reviewer clean, cso SAFE TO SHIP):**
|
|
151
|
+
`App_Api_Toga2::syncTransferOrderFromNetsuite()` now **SKIPS** (`error_log` + `return null`) when a
|
|
152
|
+
stocking-SO transfer order's destination customer maps to no client Location, instead of POSTing a
|
|
153
|
+
null destination. Two distinct log messages: **"out-of-scope customer"** vs **"in-scope but no
|
|
154
|
+
Location (backfill a Location)"**. A new 3rd param
|
|
155
|
+
`$lookupCustomerByNetsuiteCustomerInternalId` is used **only** to classify the log line.
|
|
156
|
+
|
|
157
|
+
- **Destination resolves BEFORE origin, and the skip runs first** (cso refinement) — a skipped order
|
|
158
|
+
writes nothing, so no stray origin-warehouse `Location` row is left behind.
|
|
159
|
+
- **Cascade skips cleanly on the null return.** `syncItemFulfillmentFromNetsuite` returns the
|
|
160
|
+
fulfillment uuid on a real import and **null** when not imported;
|
|
161
|
+
`attachInventoryAdjustmentToItemFulfillment` treats null as "nothing to link" (no throw) instead of
|
|
162
|
+
the old "still not found" throw that would just re-wedge the crash at the fulfillment item-match.
|
|
163
|
+
- **No `!empty(customer→Location map)` guard** (cso refinement) — the map is built in the cron's
|
|
164
|
+
up-front LOOKUPS phase and a failure there aborts the whole cron, so by sync time the map has
|
|
165
|
+
loaded. An empty map is a **real** state (a warehouse-only client) where skip-with-loud-log is
|
|
166
|
+
correct and freezing is wrong.
|
|
167
|
+
|
|
168
|
+
**VERIFIED in prod:** after the library deploy the INVENTORY_ADJUSTMENTS cursor advanced past the
|
|
169
|
+
blocker and ran forward through most of 2026; skipped TOs logged, no more EV-10 destination wedge.
|
|
170
|
+
|
|
171
|
+
This is **separate** from the origin fallback above and from the 128829 no-location edge case.
|
|
172
|
+
|
|
173
|
+
#### Follow-on regression: the added param broke a THIRD call site → SALES_ORDERS froze at 1-RUNNING
|
|
174
|
+
|
|
175
|
+
`syncTransferOrderFromNetsuite()` has **three** callers, not two: `library/app/api/toga2.php:923`
|
|
176
|
+
(from `syncSalesOrder`) and `:4984` (the `syncItemFulfillment` cascade), **plus a DIRECT cron call**
|
|
177
|
+
in `worker/crons/toga2/netsuite/common_sync_togasupply.php:782` (the SALES_ORDERS section). The skip
|
|
178
|
+
fix added a param but updated only the **two library callers**; the worker cron still passed 7 args →
|
|
179
|
+
`Logs.Event` issue **754**: *"Too few arguments to function
|
|
180
|
+
`App_Api_Toga2::syncTransferOrderFromNetsuite()`, 7 passed in
|
|
181
|
+
`crons/toga2/netsuite/common_sync_togasupply.php` on line 789 and at least 8 expected."* It surfaced
|
|
182
|
+
only when SALES_ORDERS ran (cursors reset to recheck), because INVENTORY_ADJUSTMENTS reaches the
|
|
183
|
+
function through the library-internal callers. **Fix:** added the missing 3rd arg
|
|
184
|
+
`$lookupCustomerByClientUuidAndNetsuiteCustomerInternalId[$uuidClient]` to the `:782` call.
|
|
185
|
+
|
|
186
|
+
> **Lesson (recorded on the shared engine doc too):** when you change a shared **library** function
|
|
187
|
+
> signature, **grep ALL repos** (library + worker) for callers — worker crons call library classes
|
|
188
|
+
> via the autoloader with **no `require`**, so an import search misses the cron call sites.
|
|
189
|
+
|
|
190
|
+
**Deploy:** the library change (the skip) is deployed and verified over many cron cycles; the worker
|
|
191
|
+
change (the missing 3rd arg + two stale-comment fixes) is the current fix — **redeploy worker** to
|
|
192
|
+
pick it up.
|
|
133
193
|
|
|
134
194
|
## Item-fulfillment transfer-order line link
|
|
135
195
|
|
|
@@ -324,12 +384,27 @@ item receipts replays about two years unless it is rewound forward first.
|
|
|
324
384
|
|
|
325
385
|
## Change history
|
|
326
386
|
|
|
327
|
-
- 2026-09-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
387
|
+
- 2026-09-14 - **RESOLVED: the INVENTORY_ADJUSTMENTS destination wedge.** Root cause (prod): TO 277871
|
|
388
|
+
(SO 7102750) has customer **3997 "1896 Laurel Hill Management, LLC"** — not a NYCHH customer and with
|
|
389
|
+
**no `Client_Nychh.Locations` row**, so the destination never resolved and the NOT NULL insert 1048'd
|
|
390
|
+
(EV-10) every run. It reaches NYCHH because the INVENTORY_ADJUSTMENTS cascade scopes by the shared HQ
|
|
391
|
+
**warehouse** (loc 6) then imports a fulfillment for a different customer. Key fact: customer 3997 IS
|
|
392
|
+
in `Client_Nychh.Customers` (id 19) but has no Location — "maps to a client Location" is the tenant
|
|
393
|
+
signal, not "is in Customers". **Fix** (skip out-of-scope TOs; CTO AGREE, php-reviewer clean, cso SAFE
|
|
394
|
+
TO SHIP): `syncTransferOrderFromNetsuite()` now SKIPS (error_log + return null) when the destination
|
|
395
|
+
customer maps to no Location, with two log messages (out-of-scope vs in-scope-no-Location); new 3rd
|
|
396
|
+
param `$lookupCustomerByNetsuiteCustomerInternalId` classifies the log; destination resolves before
|
|
397
|
+
origin and the skip runs first (no stray origin row); the cascade treats the null return as
|
|
398
|
+
nothing-to-link (no re-wedge). Library deployed + verified (cursor advanced through 2026).
|
|
399
|
+
**Follow-on:** the added param broke a THIRD, direct cron call site
|
|
400
|
+
(`common_sync_togasupply.php:782`, SALES_ORDERS) → issue 754 "Too few arguments" → froze SALES_ORDERS;
|
|
401
|
+
fixed by passing the 3rd arg there. Also fixed two stale comments claiming a per-record try/catch that
|
|
402
|
+
was removed 2026-08-27. Redeploy **worker** for the cron fix. (jcardinal)
|
|
403
|
+
- 2026-09-13 - **OPEN (now resolved 2026-09-14, above): INVENTORY_ADJUSTMENTS collapsing on an
|
|
404
|
+
unresolved transfer-order DESTINATION.** The section's on-demand cascade imports a
|
|
405
|
+
transfer-order-backed fulfillment, and `POST /v2/transfer-orders` for **TO 277871**
|
|
406
|
+
(`c_netsuiteInternalSalesOrderId` **7102750**) failed **EV-10 "Column 'destinationLocationId' cannot
|
|
407
|
+
be null"** — origin resolved, destination did not. (jcardinal)
|
|
333
408
|
- 2026-09-10 - Recorded that the import concentrates **1,832 of 1,939** transfer orders on catch-all
|
|
334
409
|
destination **location 2**, all with `createdByUserId` NULL (so none came from the Create Transfer
|
|
335
410
|
Order screen), and that whether it should resolve the real hospital instead is still open with the
|
|
@@ -18,7 +18,7 @@ project: _Underscore
|
|
|
18
18
|
client: nychh
|
|
19
19
|
type: profile
|
|
20
20
|
status: active
|
|
21
|
-
updated: 2026-09-
|
|
21
|
+
updated: 2026-09-14
|
|
22
22
|
owners: ["jcardinal", "apeterson", "bala", "akhokhani"]
|
|
23
23
|
files:
|
|
24
24
|
- dbchanges2/Client_Nychh/2026-09-02a - TransferOrderNetsuitePushInterceptor.sql
|
|
@@ -138,11 +138,15 @@ table views. Client-specific DB change-sets live in `dbchanges2/Client_Nychh/`.
|
|
|
138
138
|
unit. Engine + shim detail:
|
|
139
139
|
[per-client sync](../../1.0/apps/worker/features/netsuite-togasupply-per-client-sync.md),
|
|
140
140
|
[SuiteQL/REST shim](../../1.0/apps/library/features/netsuite-suiteql-rest-shim.md).
|
|
141
|
-
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
**
|
|
145
|
-
|
|
141
|
+
- **RESOLVED (2026-09-14): INVENTORY_ADJUSTMENTS destination wedge.** `POST /v2/transfer-orders` for
|
|
142
|
+
**TO 277871** (SO 7102750) failed **EV-10 "destinationLocationId cannot be null"** every run because
|
|
143
|
+
its customer **3997 "1896 Laurel Hill Management, LLC"** is not a NYCHH customer and has **no
|
|
144
|
+
`Client_Nychh.Locations` row** (though it IS in `Customers`, id 19 — "maps to a Location" is the
|
|
145
|
+
scope signal, not "is in Customers"). It reached NYCHH because the adjustment cascade scopes by the
|
|
146
|
+
shared HQ warehouse then imports a fulfillment for a different customer. Fix: the importer now SKIPS
|
|
147
|
+
out-of-scope transfer orders (loud log) instead of forcing a null destination; library deployed +
|
|
148
|
+
verified. A follow-on regression from the added param froze SALES_ORDERS (a 3rd, direct cron call
|
|
149
|
+
site, issue 754 "Too few arguments") — fixed; **redeploy worker**. See
|
|
146
150
|
[NYCHH NetSuite → TransferOrders import](./features/netsuite-transfer-order-import.md).
|
|
147
151
|
- **NYCHH sync scope + data-gap scale + backfill approach (2026-09-09).**
|
|
148
152
|
- **Scope = 22 NetSuite customers:** parent **28908** "NYC Health + Hospitals" + 21 hospital child
|
package/package.json
CHANGED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/*
|
|
5
|
+
* design-fidelity-reminder.js — PRE-EDIT enforcement of the Design Contract discipline.
|
|
6
|
+
*
|
|
7
|
+
* Why this exists: on Desk (and the other 2.0 React apps) the model keeps improvising
|
|
8
|
+
* UI — inventing button text, helper lines, field order and labels instead of matching
|
|
9
|
+
* the designer's on-disk design. That drift ships UIs that "function" but do not match
|
|
10
|
+
* what Matt designed, and the developer has to catch it every time. A rule in a file is
|
|
11
|
+
* passive; over a long session the model slides back to improvising. This is the
|
|
12
|
+
* mechanical backstop, modeled on plain-talk.js.
|
|
13
|
+
*
|
|
14
|
+
* How it works: registered on PreToolUse for Edit|Write|MultiEdit. Just before the model
|
|
15
|
+
* edits a UI source file — a path under a `src/` tree ending .tsx / .jsx / .css — it prints
|
|
16
|
+
* a short <system-reminder> to stdout with the Design Contract rules, so the discipline is
|
|
17
|
+
* in context at the moment the UI is written. On any non-UI file it stays silent.
|
|
18
|
+
*
|
|
19
|
+
* It never blocks. It always exits 0 — a reminder must never stop or deny an edit. It
|
|
20
|
+
* fails open on any error: a crashing nudge must never brick a session. Per team hook
|
|
21
|
+
* convention DENY/error text goes to STDERR — this hook never denies, so it never uses it.
|
|
22
|
+
*
|
|
23
|
+
* Escape hatch: set TOGA_DESIGN_FIDELITY_DISABLED=1 to silence the reminder.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
const fs = require('fs');
|
|
27
|
+
|
|
28
|
+
/* Read the CC hook payload. PreToolUse delivers the tool input on stdin as
|
|
29
|
+
* { tool_name, tool_input: { file_path, ... } }; fall back to the CLAUDE_TOOL_INPUT env
|
|
30
|
+
* (what the sibling PostToolUse reminders read) and to a top-level file_path. */
|
|
31
|
+
function readFilePath() {
|
|
32
|
+
let raw = '';
|
|
33
|
+
try { raw = fs.readFileSync(0, 'utf8'); } catch (e) { /* no stdin */ }
|
|
34
|
+
|
|
35
|
+
let data = {};
|
|
36
|
+
if (raw && raw.trim()) {
|
|
37
|
+
try { data = JSON.parse(raw); } catch (e) { data = {}; }
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
let fp =
|
|
41
|
+
(data.tool_input && typeof data.tool_input.file_path === 'string' && data.tool_input.file_path) ||
|
|
42
|
+
(typeof data.file_path === 'string' && data.file_path) ||
|
|
43
|
+
'';
|
|
44
|
+
|
|
45
|
+
if (!fp && process.env.CLAUDE_TOOL_INPUT) {
|
|
46
|
+
try {
|
|
47
|
+
const env = JSON.parse(process.env.CLAUDE_TOOL_INPUT);
|
|
48
|
+
if (env && typeof env.file_path === 'string') fp = env.file_path;
|
|
49
|
+
} catch (e) { /* ignore */ }
|
|
50
|
+
}
|
|
51
|
+
return fp;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/* A Desk / React-app UI source file: under a `src/` tree AND ending .tsx / .jsx / .css. */
|
|
55
|
+
function isUiSourceFile(filePath) {
|
|
56
|
+
if (!filePath) return false;
|
|
57
|
+
const p = filePath.replace(/\\/g, '/').toLowerCase();
|
|
58
|
+
return /\/src\//.test(p) && /\.(tsx|jsx|css)$/.test(p);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function reminder() {
|
|
62
|
+
return [
|
|
63
|
+
'<system-reminder>',
|
|
64
|
+
'DESIGN CONTRACT — this edit touches a UI source file. Match the designer\'s design,',
|
|
65
|
+
'do not improvise UI. Reusing a shell or making it "function" is NOT fidelity.',
|
|
66
|
+
'',
|
|
67
|
+
' 1. The on-disk design is the authority. Open the exact design source file FIRST and',
|
|
68
|
+
' build from it — match its LAYOUT/placement, not just pixels.',
|
|
69
|
+
' 2. Build to a written Design Contract: every field + its order, each label/placeholder,',
|
|
70
|
+
' required flags, button text, modal size/layout, and the header/footer.',
|
|
71
|
+
' 3. Invent NOTHING user-visible — button text, helper/info lines, labels, required flags.',
|
|
72
|
+
' Copy comes from the design + Surface/settings. If it is not in the design, ASK.',
|
|
73
|
+
' 4. Design-vs-data conflict → STOP and ask. Do not decide it yourself.',
|
|
74
|
+
' 5. No self-sign-off. Hand the developer a labeled diff (mine vs the design). "It',
|
|
75
|
+
' functions" is not fidelity.',
|
|
76
|
+
'</system-reminder>',
|
|
77
|
+
].join('\n');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function main() {
|
|
81
|
+
if (process.env.TOGA_DESIGN_FIDELITY_DISABLED === '1') process.exit(0);
|
|
82
|
+
|
|
83
|
+
const filePath = readFilePath();
|
|
84
|
+
|
|
85
|
+
if (isUiSourceFile(filePath)) {
|
|
86
|
+
// Inject the reminder into the model's context via the PreToolUse additionalContext
|
|
87
|
+
// channel (raw stdout on PreToolUse only shows in transcript, not context). Non-blocking:
|
|
88
|
+
// additionalContext adds context, it does not deny the edit.
|
|
89
|
+
console.log(JSON.stringify({
|
|
90
|
+
hookSpecificOutput: {
|
|
91
|
+
hookEventName: 'PreToolUse',
|
|
92
|
+
additionalContext: reminder(),
|
|
93
|
+
},
|
|
94
|
+
}));
|
|
95
|
+
}
|
|
96
|
+
process.exit(0);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
try { main(); } catch (e) { process.exit(0); } // fail-open: never block or brick an edit
|