toga-ai 1.0.322 → 1.0.324

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.
@@ -3,8 +3,8 @@
3
3
  | Doc | Summary | Files |
4
4
  |-----|---------|-------|
5
5
  | [AI-BDR Architecture](architecture.md) | **AI-BDR** is TOGA's outbound AI sales-development representative. | ai-bdr/README.md, ai-bdr/requirements.txt, ai-bdr/.env.example, ai-bdr/docs/client-onboarding-sop.md, ai-bdr/docs/vapi-firstmessage-timing-fix.md, ai-bdr/docs/vapi-inbound-callback-assistant-request.md, ai-bdr/docs/vapi-voicemail-iphone-screening.md, ai-bdr/vapi/templates/system-prompt.template.md, ai-bdr/vapi/templates/assistant.template.json, ai-bdr/prompts/archive/prompt-may-15.txt, ai-bdr/prompts/campaigns/healthcare-2026-03-18.md, ai-bdr/prompts/campaigns/healthcare-v2-2026-03-25.md, ai-bdr/scripts/update_assistant.py, ai-bdr/scripts/update_system_prompt.py, ai-bdr/scripts/update_structured_output.py, ai-bdr/scripts/update_call_summary_context.py, ai-bdr/scripts/fix_booking_guardrails.py, ai-bdr/scripts/get_assistant.py |
6
- | [BDR Web Funnel — Full Implementation Plan](features/bdr-web-funnel-plan.md) | > **Status:** First iteration (for Alex's review). | bdr/PLAN.md, bdr/mockup/app.jsx, bdr/mockup/screens.jsx, bdr/mockup/components.jsx |
6
+ | [BDR Web Funnel — Full Implementation Plan](features/bdr-web-funnel-plan.md) | > **Status:** Draft 2 (needs one more review). | bdr/PLAN.md, bdr/mockup/app.jsx, bdr/mockup/screens.jsx, bdr/mockup/components.jsx |
7
7
  | [Call Orchestration — PHP Worker ↔ Vapi (the integration seam)](features/call-orchestration.md) | The **PHP worker** is the orchestrator; **Vapi** is the actor. | ai-bdr/docs/client-onboarding-sop.md, ai-bdr/docs/vapi-firstmessage-timing-fix.md, ai-bdr/docs/vapi-inbound-callback-assistant-request.md, ai-bdr/docs/vapi-voicemail-iphone-screening.md, ai-bdr/scripts/update_structured_output.py |
8
8
  | [Vapi Integration — Assistants, Tools, Structured Output](features/vapi-integration.md) | Everything inside Vapi: the three assistants, the three shared tools, the shared structured-output schema, the Liquid-templated system prompt, and the Python sc | ai-bdr/vapi/templates/assistant.template.json, ai-bdr/vapi/templates/system-prompt.template.md, ai-bdr/prompts/archive/prompt-may-15.txt, ai-bdr/prompts/campaigns/healthcare-2026-03-18.md, ai-bdr/prompts/campaigns/healthcare-v2-2026-03-25.md, ai-bdr/prompts/templates/campaign-template.md, ai-bdr/scripts/update_assistant.py, ai-bdr/scripts/update_system_prompt.py, ai-bdr/scripts/update_structured_output.py, ai-bdr/scripts/update_call_summary_context.py, ai-bdr/scripts/fix_booking_guardrails.py, ai-bdr/scripts/get_assistant.py, ai-bdr/docs/vapi-firstmessage-timing-fix.md, ai-bdr/docs/vapi-voicemail-iphone-screening.md |
9
- | [Web Funnel — Next.js UI + Config-Driven Campaign Content](features/web-funnel-content-model.md) | The **public-facing web funnel** is the new UI / lead-capture front door of the **same AI-BDR product** documented in `../architecture.md`. | bdr/src/content/schema.ts, bdr/src/content/hubspotProvider.ts, bdr/src/content/useCampaign.ts, bdr/src/content/default.ts, bdr/src/server/leadSink.ts, bdr/src/server/callbackService.ts, bdr/src/app/api/call-now/route.ts, bdr/src/app/api/call-later/route.ts |
9
+ | [Web Funnel — Next.js UI + Config-Driven Campaign Content](features/web-funnel-content-model.md) | The **public-facing web funnel** is the new UI / lead-capture front door of the **same AI-BDR product** documented in `../architecture.md` — it is the front doo | bdr/src/content/schema.ts, bdr/src/content/hubspotProvider.ts, bdr/src/content/useCampaign.ts, bdr/src/content/default.ts, bdr/src/server/leadSink.ts, bdr/src/server/callbackService.ts, bdr/src/app/api/call-now/route.ts, bdr/src/app/api/call-later/route.ts |
10
10
  | [New-Campaign Onboarding (6-phase runbook)](workflows/new-campaign-onboarding.md) | The end-to-end procedure for launching a new outbound BDR campaign (industry, offer, target persona, geography). | ai-bdr/docs/client-onboarding-sop.md, ai-bdr/docs/calcom-new-campaign-event-guide.md, ai-bdr/prompts/templates/campaign-template.md, ai-bdr/scripts/update_assistant.py, ai-bdr/scripts/update_system_prompt.py |
@@ -6,7 +6,7 @@ project: AI-BDR
6
6
  client: shared
7
7
  type: architecture
8
8
  status: active
9
- updated: 2026-07-08
9
+ updated: 2026-07-13
10
10
  owners: [akhokhani, tcox]
11
11
  files:
12
12
  - ai-bdr/README.md
@@ -184,7 +184,10 @@ this system's existing calls** (HubSpot contact fetch, Toga upsert + campaign li
184
184
  `requestCall` callback — see *End-to-end call flow* and `features/call-orchestration.md`),
185
185
  ported from the external `info` repo behind `LeadSink` / `CallbackService` adapters so all
186
186
  secret-bearing calls stay server-side. On top of that reuse it adds a **HubSpot-owned,
187
- config-driven campaign content layer**: marketing authors campaign content in HubSpot,
187
+ config-driven campaign content layer** (HubSpot chosen over Contentful — marketing already
188
+ operates there, the campaign model already exists, and the read is a trivial server-side
189
+ fetch; the stable content shape makes a dedicated headless CMS unnecessary): marketing
190
+ authors campaign content in HubSpot,
188
191
  fetched by slug at runtime and mapped to a serializable `CampaignBundle` (config-driven
189
192
  components, `DEFAULT` fallback — no `if(campaign===x)`; a copy change ships with no
190
193
  redeploy). Full detail: **`features/web-funnel-content-model.md`**.
@@ -213,3 +216,5 @@ redeploy). Full detail: **`features/web-funnel-content-model.md`**.
213
216
  product's UI / lead-capture front door — reuses this system's existing HubSpot/Toga/callback
214
217
  calls and adds a HubSpot-owned, config-driven campaign content layer. See
215
218
  `features/web-funnel-content-model.md`. (tcox)
219
+ - 2026-07-13 — Additive: recorded the HubSpot-over-Contentful content-store decision for the
220
+ web funnel. (tcox)
@@ -6,7 +6,7 @@ project: "AI-BDR"
6
6
  client: shared
7
7
  type: feature
8
8
  status: draft
9
- updated: 2026-07-08
9
+ updated: 2026-07-13
10
10
  owners: [tcox]
11
11
  files:
12
12
  - bdr/PLAN.md
@@ -20,8 +20,8 @@ related:
20
20
 
21
21
  # BDR: Multi-Campaign "AI BDR / Agent Studio" Web App — Implementation Plan
22
22
 
23
- > **Status:** First iteration (for Alex's review). Draft, pending sign-off on the open questions
24
- > in §9 and final campaign direction / verbiage from leadership.
23
+ > **Status:** Draft 2 (needs one more review). Open items tracked in §9; final campaign direction /
24
+ > verbiage still pending from leadership.
25
25
  > **Author:** tcox · **Stack:** TOGA 2.0 front-end · Next.js (App Router) · **Client scope:** shared / internal (TOGA)
26
26
  > **Repo (new):** `bdr` · **Two source inputs:** the **mockup** (real UI) + **`info`** (backend to reuse).
27
27
  >
@@ -65,22 +65,23 @@ mockup folder, and the `info` repo.
65
65
  TypeScript, matching `info`). Next.js is the chosen framework because the site is **highly public**,
66
66
  so server-side rendering + SEO matter, and its server (server components + route handlers) is where
67
67
  the secret-bearing HubSpot/Toga/content calls run natively (§5.6). Every campaign is defined by
68
- content authored in HubSpot, so a new campaign launches with **no code changes**, multiple campaigns
69
- run side by side, and the real HubSpot / Toga / callback backend from `info` is reused directly.
68
+ **content authored by marketing in HubSpot** and fetched **by slug at runtime**, so adding or changing
69
+ a campaign needs no code and no redeploy. Multiple campaigns run side by side, and the real HubSpot /
70
+ Toga / callback backend from `info` is reused directly.
70
71
 
71
72
  **Done when:**
72
73
  1. The full flow (Welcome, Build, Capabilities, Call Now, Schedule, Success) renders in Next.js
73
74
  with visual parity to the mockup (pixel-perfect pass against the mockup screens).
74
75
  2. Two distinct campaigns (for example a generic **BDR-as-a-service** and a **healthcare** variant),
75
- **each authored in HubSpot**, render differing only in data (copy, Q&A proof points, agent identity,
76
- accent), with zero component-code differences. Adding a campaign requires **only HubSpot edits** (no
77
- code, no redeploy).
78
- 3. No user-facing string is hardcoded in a component (grep-verified); all copy comes from the HubSpot
76
+ **each authored in HubSpot**, render differing only in **data** (copy, Q&A proof points, agent
77
+ identity, accent), with zero component-code differences. Adding/changing a campaign is a **HubSpot
78
+ edit** (no code, no redeploy).
79
+ 3. No user-facing string is inline in a component (grep-verified); all copy comes from the HubSpot
79
80
  campaign content, fetched by slug at runtime and resolved through one hook.
80
81
  4. The simulated call/schedule actions are wired to the **real backend** (Toga `requestCall`,
81
82
  HubSpot/Toga contact upsert) behind a `CallbackService` / `LeadSink` adapter ported from `info`.
82
- 5. Campaign content is fetched from **HubSpot at runtime** via the backend, behind the
83
- `CampaignContentProvider` seam (§5.2), so marketing edits copy without a developer or a deploy.
83
+ 5. Content is fetched from **HubSpot at runtime** through the **`CampaignContentProvider` seam** (§5.2),
84
+ with a `DEFAULT` safety-net fallback, so marketing edits copy without a developer or a redeploy.
84
85
  6. Copy honors the mockup rules: no em dashes; a single `--accent` source drives all accents.
85
86
  7. No PII in logs; the `info` debug logging is not carried over.
86
87
  8. Deploys (AWS Amplify) with documented env vars.
@@ -197,7 +198,7 @@ the bundle carries the UUID so a slug maps to the right campaign for logging.
197
198
  | Framework | **Next.js (App Router) + TypeScript** | Chosen because the site is highly public: SSR + SEO, and a built-in server for the secret-bearing HubSpot/Toga/content calls. Matches `info`, so its patterns port directly. |
198
199
  | UI source | **The mockup** | Rebuild its Agent Studio flow faithfully (pixel-perfect). The mockup is React, so components port cleanly into Next.js client components. |
199
200
  | Backend source | **`info`** | Reuse HubSpot/Toga/callback logic directly (same framework). |
200
- | Content model | **Runtime, HubSpot-owned** | Campaign content is authored by the **marketing team in HubSpot** and fetched **by slug at runtime** via the backend. **No campaign content in the repo; no developer or redeploy to change copy.** (Supersedes the earlier "static bundles now" idea.) See §5.2 / §5.5. |
201
+ | Content model | **Runtime, HubSpot-owned** | Content store = **HubSpot** (decided over Contentful: the site's content shape is stable / not frequently restructured, so HubSpot's flat-modeling limits are acceptable, and the API integration is trivial — confirmed in `info`, §5.2). Campaign content is authored by marketing in HubSpot and fetched **by slug at runtime**; **no campaign content in the repo, no redeploy to change copy**. Served behind the `CampaignContentProvider` seam with a `DEFAULT` fallback. See §5.2 / §5.5. |
201
202
  | Backend relationship | **Same product; reuses the AI-BDR backend** | This funnel is the new UI / front door of TOGA's existing **AI-BDR** product; it reuses that system's HubSpot/Toga/callback calls (documented at `2.0/apps/ai-bdr/`). Lead/callback handoff still goes behind `LeadSink`/`CallbackService` adapters for a clean seam. |
202
203
  | Campaign entry | **Plan recommends** | See §5.5. |
203
204
  | Copy rules | **No em dashes; one accent source** | Accent enforced in code. No-em-dash cannot be linted on HubSpot-authored copy: enforce via author guidance + a normalization pass in the backend mapping. |
@@ -230,31 +231,52 @@ Port approach:
230
231
  > `NEXT_PUBLIC_` env.
231
232
 
232
233
  ### 5.2 Content provider — HubSpot is the source of truth
233
- **Campaign content lives in HubSpot, authored by marketing, and is fetched by slug at runtime.**
234
- It is **not** stored in the repo, and changing copy requires **no developer and no redeploy**. The
235
- provider is the seam that makes this true:
234
+ **Decision:** the content store is **HubSpot** (chosen over Contentful). The site's content shape is
235
+ stable / not frequently restructured, so HubSpot's flatter modeling is acceptable, and a dedicated
236
+ headless CMS (Contentful) would only pay off if the content model were rich and frequently
237
+ restructured, which it is not.
238
+
239
+ **Why HubSpot (rationale for the team):**
240
+ - **Marketing already operates there.** The contact database, campaigns, outreach, lists, and workflows
241
+ all live in HubSpot. Content sits with the team that authors it, and we reuse the existing
242
+ go-to-market engine instead of standing up a parallel system.
243
+ - **The campaign model already exists.** The old BDR site (`info`) already uses HubSpot for lead
244
+ sourcing, known-visitor personalization, and **campaign attribution** (`hsContactId` / `hsCampaignId`
245
+ → Toga). Campaign content keys naturally off that same campaign concept, and content + attribution
246
+ stay in one system rather than split across two.
247
+ - **The API call is trivial.** Reading from HubSpot is a ~15-line server-side fetch proxied by a Next
248
+ route handler (proven in `info`; the token never reaches the browser). The same pattern serves any
249
+ HubSpot read, HubDB included. No new infrastructure.
250
+ - **No new vendor.** HubSpot is already in the stack and paid for; Contentful would add a SaaS contract
251
+ and a second integration with no offsetting benefit given the stable content shape.
252
+ - **Trade-off we are accepting:** HubSpot is not a purpose-built structured-content CMS, so richly
253
+ nested content and non-dev editor UX are weaker than Contentful — acceptable here because the content
254
+ shape is stable (§9.10 tracks how we model it).
255
+
256
+ **Campaign content lives in HubSpot, authored by marketing, and is fetched by slug at runtime** — not
257
+ in the repo, no redeploy to change copy. One seam makes this true:
236
258
  ```ts
237
259
  interface CampaignContentProvider {
238
- resolve(slug: string | undefined): Promise<CampaignBundle>; // returns the safety-net fallback if unknown/unreachable
260
+ resolve(slug: string | undefined): Promise<CampaignBundle>; // always returns a valid bundle (DEFAULT if unknown)
239
261
  list?(): Promise<CampaignSummary[]>;
240
262
  }
241
- // Primary: HubSpotContentProvider — runs server-side (in the page's server component / a route
242
- // handler). Reads the campaign content from HubSpot (see §9.10 for the exact HubSpot
243
- // mechanism), maps it to a CampaignBundle, and caches it. Secrets stay server-side.
263
+ // Primary: HubSpotContentProvider — runs server-side (page server component / route handler); reads
264
+ // content from HubSpot (§9.10 mechanism), maps it to a CampaignBundle, caches it. Secrets
265
+ // stay server-side.
244
266
  // Fallback: a minimal built-in DEFAULT bundle (engineering safety net only, NOT authored marketing
245
267
  // copy) so the funnel still renders if a slug is unknown or HubSpot is briefly unreachable.
246
268
  ```
247
- `resolve(slug)` runs on the server during render (SSR), so the HubSpot fetch + field mapping to the
248
- `CampaignBundle` shape (§6) happens before the page is sent, with Next caching / revalidation so
249
- marketing edits appear quickly without a deploy (and the public site stays SEO-friendly). The client
250
- components receive the resolved bundle as props / via `useCampaign()`; they never see HubSpot.
269
+ `resolve(slug)` runs on the server during render (SSR), so the fetch + mapping to the `CampaignBundle`
270
+ shape (§6) happens before the page is sent, with Next caching / revalidation so marketing edits appear
271
+ quickly without a deploy (and the page stays SEO-friendly). Components consume the resolved bundle via
272
+ `useCampaign()`; they never see HubSpot or the provider.
251
273
 
252
274
  > **Key sub-decision (open, §9.10):** *how* campaign content is represented in HubSpot. HubSpot's
253
- > "Campaigns" tool is built for attribution, not rich structured copy, so this depth of content
254
- > (headlines, a ~10-entry Q&A pool, agent identity, call summaries) needs a deliberate home. Likely
255
- > **HubDB** (a CMS-Hub table: one row per campaign, a `slug` column + content columns, API-fetchable)
256
- > or a **custom object**. This choice drives the fetch + the field mapping and needs confirmation with
257
- > whoever owns the HubSpot instance.
275
+ > "Campaigns" tool is built for attribution, not rich structured copy, so this content (headlines, a
276
+ > ~10-entry Q&A pool, agent identity, call summaries) needs a deliberate home. Likely **HubDB** (a
277
+ > CMS-Hub table: one row per campaign, a `slug` column + content columns, API-fetchable) or a **custom
278
+ > object**. This drives the fetch + the `CampaignBundle` mapping and needs confirmation with whoever
279
+ > owns the HubSpot instance before the content layer (Phase 1) is built.
258
280
 
259
281
  ### 5.3 Campaign as data, components as renderers
260
282
  Follow the TOGA config-driven precedents (`toga25-supply` `useClientFields`; `toga2-commerce` Cart
@@ -289,13 +311,11 @@ safety-net `DEFAULT`.
289
311
  (id -> slug lookup, so CRM links keep working) -> `DEFAULT`. An unknown or missing slug always falls
290
312
  back to the `DEFAULT` bundle, so the funnel never fails to render.
291
313
 
292
- **4. The slug drives a runtime fetch of the content from HubSpot** (this is the confirmed requirement):
293
- the page's server component calls `provider.resolve(slug)`, which reads that campaign's content from
294
- **HubSpot** (§9.10), maps it to a `CampaignBundle`, and caches it (Next revalidation). Because this is
295
- server-side, the fully-populated page is rendered and sent (good for the public site's SEO). Marketing
296
- edits in HubSpot show up on the next revalidation. **No repo content, no redeploy, no developer** to
297
- change copy. If the slug is unknown or HubSpot is briefly unreachable, the minimal `DEFAULT` safety net
298
- renders so the funnel never breaks.
314
+ **4. The slug drives a runtime fetch of the content from HubSpot:** the page's server component calls
315
+ `provider.resolve(slug)`, which reads that campaign's content from **HubSpot** (§9.10), maps it to a
316
+ `CampaignBundle`, and caches it (Next revalidation). Because this is server-side, the fully-populated
317
+ page is rendered and sent (SEO-friendly), and marketing edits appear on the next revalidation with no
318
+ redeploy. An unknown/missing slug falls back to the `DEFAULT` bundle so the funnel never fails to render.
299
319
 
300
320
  **5. Multiple campaigns run simultaneously by construction.** Selection is per-visitor, derived from
301
321
  their URL each load; there is no global "current campaign" state. Any number of campaigns are live at
@@ -343,11 +363,10 @@ never blank. Prune unused slots per the campaigns actually shipped (the default
343
363
  ---
344
364
 
345
365
  ## 6. Campaign bundle schema (concrete sketch)
346
- This is the shape the app consumes. It is **produced server-side by mapping the HubSpot campaign
347
- content (§5.2) into this structure** — it is the contract between "how marketing's content is stored
348
- in HubSpot" and "what the components render," and the mapping layer is where HubSpot fields become
349
- these fields (and where the no-em-dash normalization runs). Serializable data only; behavior/icons by
350
- key. Covers the real content surfaces from §2.4.
366
+ This is the shape the app consumes, and **the stable contract between HubSpot and the components** —
367
+ components only ever read this. The server maps HubSpot fields into this shape (that mapping layer is
368
+ where the no-em-dash normalization runs). Serializable data only; behavior/icons by key. Covers the
369
+ real content surfaces from §2.4.
351
370
 
352
371
  > The richer fields here (the nested `pool: QA[]` + `sets`, `callSummaries`, `RichLine` accent spans)
353
372
  > are the parts hardest to represent in a marketing-friendly way in HubSpot — a direct input to the
@@ -403,7 +422,7 @@ bdr/
403
422
  c/[slug]/page.tsx # optional path-based campaign entry (later)
404
423
  api/{lead,call-now,call-later}/route.ts # SERVER: secret-bearing Toga/HubSpot calls (ported from info)
405
424
  content/
406
- schema.ts # CampaignBundle types (the contract)
425
+ schema.ts # CampaignBundle types (the stable contract)
407
426
  hubspotProvider.ts # SERVER: reads campaign content from HubSpot + maps to bundle (§5.2/§6)
408
427
  useCampaign.ts # client hook to read the resolved bundle
409
428
  default.ts # minimal safety-net fallback ONLY (not authored copy)
@@ -431,11 +450,11 @@ so fidelity should be near-exact.
431
450
  Amplify SSR hosting). Port `styles.css` and the CSS-variable accent system. Set up campaign routing
432
451
  (query param now, `/c/[slug]` later) and ESLint incl. a no-em-dash check on in-repo strings. Read
433
452
  `node_modules/next/dist/docs/` first (Next breaking changes per `info/AGENTS.md`).
434
- - **Phase 1 — Content layer.** Depends on the §9.10 HubSpot-modeling decision. Define `schema.ts`
435
- (the `CampaignBundle` contract), the server-side `hubspotProvider` (`resolve(slug)` reads HubSpot +
436
- maps to a bundle, with caching/revalidation + no-em-dash normalization), the `useCampaign` client
437
- hook, and a minimal safety-net `DEFAULT`. Seed HubSpot with one real campaign (extract the mockup's
438
- current copy into HubSpot, not the repo) to develop against. Unit-test mapping + fallback.
453
+ - **Phase 1 — Content layer.** Depends on the §9.10 HubSpot-modeling decision. Define `schema.ts` (the
454
+ `CampaignBundle` contract), the server-side `hubspotProvider` (`resolve(slug)` reads HubSpot + maps to
455
+ a bundle, with caching/revalidation + no-em-dash normalization), the `useCampaign` hook, and a minimal
456
+ safety-net `DEFAULT`. Seed HubSpot with one real campaign (extract the mockup's current copy into
457
+ HubSpot, not the repo) to develop against. Unit-test mapping + fallback.
439
458
  - **Phase 2 — Port the component library.** Bring `components.jsx` across as real modules (SwapFade,
440
459
  Particles, AgentArt + data, VoiceDial, ColorSelector, SynapseButton, StepRail, Wordmark, Ico,
441
460
  asset/preload). Migrate assets to `public/`. This unblocks every screen.
@@ -443,7 +462,8 @@ so fidelity should be near-exact.
443
462
  (skills + Q&A bank + CTAs), so building it first calibrates the real estimate and proves the bundle
444
463
  schema before the remaining screens.
445
464
  - **Phase 4 — Remaining screens + modals from config.** AutoBuild, Landing (behind a feature flag),
446
- Call Now, Schedule, Success, Welcome/WelcomeStep2/EarlyAccess, StepRail. Remove all hardcoded copy.
465
+ Call Now, Schedule, Success, Welcome/WelcomeStep2/EarlyAccess, StepRail. All copy comes from the
466
+ HubSpot-resolved bundle (no inline strings in components).
447
467
  - **Phase 5 — Wire the real backend.** Port `info`'s `lib/toga.ts` / `lib/hubspot.ts` + its call
448
468
  route handlers into BDR's `app/api/*` (server-side); wire the `LeadSink` / `CallbackService`
449
469
  adapters; replace the `Success` simulation with real immediate/scheduled `requestCall`; remove PII
@@ -459,12 +479,13 @@ so fidelity should be near-exact.
459
479
 
460
480
  ---
461
481
 
462
- ## 9. Open questions (leadership + HubSpot owner). Most do not block early work; **§9.10 gates Phase 1**.
482
+ ## 9. Open questions (leadership + HubSpot owner). Most do not block early work; **§9.10 gates the content layer**.
463
483
  1. **Backend relationship:** RESOLVED — `bdr` is the new UI / front door of TOGA's existing **AI-BDR**
464
484
  product (not standalone); it reuses that system's backend calls, and its knowledge is filed under
465
485
  `2.0/apps/ai-bdr/`. `CallbackService` uses that system's callback path.
466
- 2. **Runtime editability:** RESOLVED — content is authored by marketing **in HubSpot** and fetched by
467
- slug at runtime; no repo content, no redeploy. The remaining question is the HubSpot mechanism (§9.10).
486
+ 2. **Content store:** RESOLVED — **HubSpot** (over Contentful): content shape is stable, so HubSpot is
487
+ acceptable and the integration is trivial (§5.2). Content is HubSpot-owned from the start (no
488
+ hardcoding); the remaining detail is the HubSpot mechanism (§9.10).
468
489
  3. **Agent choice:** Stay with a campaign-fixed agent (auto-build to a preset), or ship the manual
469
490
  Creator (currently parked / "coming soon")? Determines whether `Creator` + full slot art ship now.
470
491
  4. **Service picker:** Is the `Landing` service-selection screen (AI BDR + "Talos coming soon") in
@@ -478,15 +499,15 @@ so fidelity should be near-exact.
478
499
  9. **Backend hosting:** RESOLVED by the Next.js choice — the secret-bearing HubSpot/Toga calls run in
479
500
  BDR's own server components / route handlers; no separate backend to host. Only remaining item is
480
501
  confirming the Amplify **SSR** hosting target (not static) at deploy.
481
- 10. **HubSpot content mechanism (blocks Phase 1) — the big one.** *How* is campaign content stored in
482
- HubSpot so it is marketing-editable and fetchable by slug? `info` gives no precedent (it only reads
483
- contacts). Options: **HubDB** (CMS-Hub table, one row per campaign, `slug` column + content columns,
484
- API-fetchable — the likely fit) vs. a **custom object** vs. properties on the Campaigns object. This
485
- determines the fetch + the HubSpot->`CampaignBundle` mapping. Sub-parts to settle with the HubSpot
486
- owner: which mechanism; how the **nested content** (Q&A pool + rotation sets, call summaries, accent
487
- `RichLine`s) is represented (flat columns can't hold arrays cleanly — child rows or a JSON field);
488
- who authors/owns the schema; and how the slug relates to the existing `hsCampaignId`/Toga campaign
489
- UUID used for attribution.
502
+ 10. **HubSpot content mechanism (gates the content layer, Phase 1) — the big one.** *How* campaign
503
+ content is stored in HubSpot so it is marketing-editable and fetchable by slug. `info` gives no
504
+ precedent (it only reads contacts). Options: **HubDB** (CMS-Hub table, one row per campaign, `slug`
505
+ column + content columns, API-fetchable — the likely fit) vs. a **custom object** vs. properties on
506
+ the Campaigns object. This determines the fetch + the HubSpot->`CampaignBundle` mapping. Sub-parts to
507
+ settle with the HubSpot owner: which mechanism; how **nested content** (Q&A pool + rotation sets, call
508
+ summaries, accent `RichLine`s) is represented (flat columns can't hold arrays cleanly — child rows or
509
+ a JSON field); who authors/owns the schema; and how the slug relates to the existing
510
+ `hsCampaignId`/Toga campaign UUID used for attribution.
490
511
 
491
512
  ---
492
513
 
@@ -530,4 +551,5 @@ so fidelity should be near-exact.
530
551
  - [ ] Pixel-perfect parity pass against the mockup screens + screenshots.
531
552
 
532
553
  ## Change history
554
+ - 2026-07-13 — Draft 2 resync: re-mirrored the body from the current `BDR/PLAN.md`. Framework locked to Next.js App Router (earlier React/Vite SPA idea dropped); content store decided HubSpot over Contentful (rationale in §4/§5.2); no-hardcoding (HubSpot-owned, fetched by slug from the start); same-product framing confirmed (front door of the existing AI-BDR backend, "backend relationship" open question resolved); meeting-specific dates/cadence removed. Still a draft. (tcox)
533
555
  - 2026-07-08 — Captured the full BDR web-funnel implementation plan verbatim into the knowledge repo as a companion to the distilled `web-funnel-content-model.md` feature doc (redundancy intentional). (tcox)
@@ -6,7 +6,7 @@ project: AI-BDR
6
6
  client: shared
7
7
  type: feature
8
8
  status: draft
9
- updated: 2026-07-08
9
+ updated: 2026-07-13
10
10
  owners: [tcox]
11
11
  files:
12
12
  - bdr/src/content/schema.ts
@@ -25,11 +25,16 @@ related:
25
25
  ## What this is
26
26
 
27
27
  The **public-facing web funnel** is the new UI / lead-capture front door of the **same
28
- AI-BDR product** documented in `../architecture.md`. It is a **Next.js (App Router) +
29
- TypeScript** app living in its own code repo, `bdr` (registered separately because it is a
30
- distinct codebase). Only the UI/content layer is new — the funnel **reuses this AI-BDR
31
- system's existing backend calls** (see "Backend reuse" below); the API/backend contracts
32
- are already documented and are **not** restated here.
28
+ AI-BDR product** documented in `../architecture.md` — it is the front door of that existing
29
+ product, not a standalone system. It is a **Next.js (App Router) + TypeScript** app living
30
+ in its own code repo, `bdr`. Only the UI/content layer is new — the funnel **reuses this
31
+ AI-BDR system's existing backend calls** (see "Backend reuse" below); the API/backend
32
+ contracts are already documented and are **not** restated here.
33
+
34
+ **Framework:** Next.js (App Router) + TypeScript is the locked choice because the site is
35
+ highly public — SSR + SEO matter, and Next's server (server components + route handlers)
36
+ natively hosts the secret-bearing HubSpot/Toga calls (an earlier React/Vite SPA idea was
37
+ dropped). It also matches the `info` repo, so `info`'s patterns port directly.
33
38
 
34
39
  The UI is built pixel-perfect from the mockup at `BDR/mockup`: a **7-screen "Agent Studio"
35
40
  flow** — Welcome → AutoBuild → Capabilities → Call Now / Schedule → Success. The
@@ -47,7 +52,19 @@ Every campaign is a **serializable data bundle** (`CampaignBundle`) keyed by a s
47
52
  `slug`. **Campaign content is authored by the marketing team in HubSpot** and fetched **by
48
53
  slug at runtime, server-side**, then mapped to a `CampaignBundle`. Content is **not** stored
49
54
  in the repo — a new campaign or a copy edit ships with **no developer and no redeploy**,
50
- only a HubSpot edit.
55
+ only a HubSpot edit. There is **no hardcoded/in-code interim**: content is HubSpot-owned and
56
+ fetched by slug from the start (an in-code interim was considered and rejected).
57
+
58
+ **Content store decided: HubSpot (over Contentful).** Rationale: (a) marketing already
59
+ operates in HubSpot; (b) the campaign model already exists there — the old BDR site (`info`)
60
+ uses HubSpot for lead sourcing, personalization, and campaign **attribution**
61
+ (`hsContactId` / `hsCampaignId` → Toga), so content keys off that same campaign concept and
62
+ content + attribution stay in one system; (c) the API integration is trivial — a ~15-line
63
+ server-side fetch proxied by a Next route handler (proven in `info`; same pattern serves
64
+ HubDB); (d) no new vendor to contract. **Trade-off accepted:** HubSpot is not a
65
+ purpose-built content CMS, so richly nested content and non-dev editor UX are weaker than
66
+ Contentful — acceptable here because the content shape is **stable / not frequently
67
+ restructured** (a headless CMS would only pay off if the model were rich and churning).
51
68
 
52
69
  Config-driven, per TOGA precedent (`toga25-supply`'s `useClientFields`,
53
70
  `toga2-commerce`'s Cart C1–C7): components are **pure renderers of a bundle**; there is
@@ -141,6 +158,12 @@ right campaign for logging.
141
158
  `var(--accent)` (+ derived `--accent-fill`); never hardcode an accent value.
142
159
 
143
160
  ## Change history
161
+ - 2026-07-13 — Draft 2 resync to the current plan: framework locked to Next.js App Router
162
+ (earlier React/Vite SPA idea dropped); recorded the **HubSpot-over-Contentful** content-store
163
+ decision + rationale (marketing already in HubSpot, campaign model already exists, trivial
164
+ API, no new vendor; stable-content-shape trade-off accepted); reinforced no-hardcoding
165
+ (HubSpot-owned, fetched by slug from the start); confirmed same-product / front-door framing
166
+ (not standalone). Still a draft. (tcox)
144
167
  - 2026-07-08 — Initial doc: Next.js App Router web funnel as the AI-BDR product's UI /
145
168
  lead-capture front door; config-driven campaign bundle authored in HubSpot and fetched by
146
169
  slug server-side at runtime (`DEFAULT` fallback, URL-driven per-request selection); backend
@@ -7,6 +7,7 @@
7
7
  | [Compass: Item-Fulfillment TableViews (for-sales-order-items & for-sales-orders, tracking via bridge)](features/item-fulfillment-tracking-tableview.md) | 2.0 | Two sibling Compass TableViews in `Client_Compass` display fulfilled items in toga2-supply, both driven by `TableViews` / `TableViewJoins` / `TableViewFields` c | dbchanges2/Client_Compass/2026-06-10 - ItemFulfillmentsForSalesOrderItemsTableView.sql, dbchanges2/Client_Compass/2026-06-11 - ItemFulfillmentsForSalesOrdersTableView.sql, dbchanges2/Client_Compass/2026-06-15a - FixItemFulfillmentTrackingNumberJoins.sql |
8
8
  | [Compass MITS PO → SO Item Linking](features/mits-po-to-so-item-linking.md) | 2.0 | MITS sends Compass inbound Purchase Orders (`POST /v2/purchase-orders`) against a Sales Order (`mitsSalesOrder`). | _underscore/Model/Compass/PurchaseOrder.php, worker/crons/toga2/compass/workflow/3a_import_office_depot_purchase_orders.php |
9
9
  | [Compass MITS PO Transmission to Vendors](features/mits-po-transmission-to-vendors.md) | 2.0 | The 1.0 worker cron `2_transmit_mits_purchase_orders_to_vendors.php` transmits Compass PurchaseOrders to their vendors (Office Depot, Strategic Systems, Compass | worker/crons/toga2/compass/workflow/2_transmit_mits_purchase_orders_to_vendors.php, library/app/client/compass.php |
10
+ | [Compass MR/MA Order Auto-Approval & Status Gate](features/mr-ma-order-approval-and-status.md) | 2.0 | Compass **MR** and **MA** sales orders are system-generated from the MITS / Office Depot EDI pipeline (they do not originate as user-entered SA orders) and must | _underscore/Model/Compass/SalesOrder.php, _underscore/Model/Compass/PurchaseOrder.php, worker/crons/toga2/compass/workflow/3a_import_office_depot_purchase_orders.php |
10
11
  | [Compass USA](profile.md) | 2.0 | Compass USA is a TOGA client running a multi-tier supply-chain commerce operation. | |
11
12
  | [Compass Cross-Kit Bundle Corruption — Detection & Repair](workflows/cross-kit-bundle-corruption.md) | 2.0 | A frontend regression in `toga2-commerce`'s edit-order bundle submission mis-attributed bundle (kit) line items and **fees/warranties** to the **wrong kit**, pe | src/api/syncSalesOrderItemsFromLocalStorageCartToApi.ts |
12
13
  | [Compass ODP Order Pipeline to NetSuite (numbered worker crons)](workflows/odp-order-pipeline-to-netsuite.md) | 1.0 | The end-to-end **Compass Office Depot (ODP) order → NetSuite** pipeline as it actually runs through the 1.0 `worker` crons under `worker/crons/toga2/compass/`, | worker/crons/toga2/compass/workflow/1_transmit_compass_sales_orders_to_mits.php, worker/crons/toga2/compass/workflow/2_transmit_mits_purchase_orders_to_vendors.php, worker/crons/toga2/compass/edi/1_download_edi_s3_create_po_toga.php, worker/crons/toga2/compass/workflow/5_create_netsuite_sales_orders_from_office_depot_purchase_orders.php, library/app/client/compass.php |
@@ -6,8 +6,8 @@ project: _Underscore
6
6
  client: compass-usa
7
7
  type: client-feature
8
8
  status: active
9
- updated: 2026-06-16
10
- owners: ["jcardinal"]
9
+ updated: 2026-07-13
10
+ owners: ["jcardinal", "rgirish"]
11
11
  files:
12
12
  - _underscore/Model/Compass/PurchaseOrder.php
13
13
  - worker/crons/toga2/compass/workflow/3a_import_office_depot_purchase_orders.php
@@ -40,6 +40,15 @@ production data-integrity bug.
40
40
  `createdFromSalesOrderItem`, and deliberately **aligns** SO and PO `lineNumber`s. It does
41
41
  NOT build the item bridge — so the `postPost` lineNumber-match pass is the **only** (and
42
42
  correct) MR linking mechanism.
43
+ - **MA orders — item population in the ODP PO import cron:** the 3a cron
44
+ (`worker/crons/toga2/compass/workflow/3a_import_office_depot_purchase_orders.php`) parses ODP
45
+ EDI 850 POs. Its "Non-SA order handling" block creates the Compass **MA** sales order. Line
46
+ items are attached to the Compass SO **only in the new-SO branch**, from `$purchaseOrder['items']`
47
+ (`partNumber` + `UUID_ITEM_CATALOG` Compass catalog + `qtyOrdered` + `unitPrice`), mirroring the
48
+ ODP-facing SO item payload built lower in the same cron. The item-linking block that maps ODP
49
+ items back to the Compass side runs only `if (… == 'SA' || … == 'MR')` — MA is excluded there
50
+ because SA/MR originate in Compass (their Compass SO+items pre-exist) whereas MA is
51
+ ODP-originated, so nothing else would populate it.
43
52
 
44
53
  ## Data model
45
54
  - `SalesOrderItems_PurchaseOrderItems` (`Client_Compass`): `uuid`, `salesOrderItemId`,
@@ -115,7 +124,22 @@ USA and Canada share the parent handler unchanged.
115
124
  off-by-one bridge fingerprint (a `SalesOrderItems_PurchaseOrderItems` PO item linked to two
116
125
  adjacent SO items). Only SA132763 was remediated; a broader reviewed backfill is pending.
117
126
 
127
+ - **Empty MA orders (fixed 2026-07-13 — forward only).** The 3a cron's new-SO branch created MA
128
+ Compass sales orders with **no** `salesOrderItems`, because item population lived only on the
129
+ ODP-facing SO and the back-linking block excludes MA (`if (… == 'SA' || … == 'MR')`). Empty MA
130
+ (and MR) shells then stick at "Pending Initial Approval" because Compass `_status` gates on
131
+ approvals first — see
132
+ [MR/MA Order Auto-Approval & Status Gate](mr-ma-order-approval-and-status.md). **Fix:** build a
133
+ `salesOrderItems` array from `$purchaseOrder['items']` in the new-SO branch (partNumber +
134
+ `UUID_ITEM_CATALOG` + qtyOrdered + unitPrice), mirroring the ODP SO item payload. Only the
135
+ new-SO branch changed; the SO-already-exists branch was intentionally left alone. `php -l`
136
+ passes. This stops NEW empty MA orders; it does **not** repair the 192 historical empties (which
137
+ need item backfill from the sibling ODP SO first).
138
+
118
139
  ## Change history
140
+ - 2026-07-13 — Fixed empty MA orders: the 3a ODP-PO-import cron's new-SO branch now builds
141
+ `salesOrderItems` from the 850 PO items (the MA back-linking block excludes MA by design).
142
+ Forward-only; 192 historical empties still need item backfill. (rgirish)
119
143
  - 2026-06-16 — Found and fixed a SECOND source of cross-part bridge links: the Office Depot
120
144
  worker cron `3a_import_office_depot_purchase_orders.php` paired API-returned PO items to SO
121
145
  items by array index; now pairs by lineNumber. Remediated SA132763's spurious bridge +
@@ -0,0 +1,124 @@
1
+ ---
2
+ title: Compass MR/MA Order Auto-Approval & Status Gate
3
+ framework: "2.0"
4
+ repo: _underscore
5
+ project: _Underscore
6
+ client: compass-usa
7
+ type: client-feature
8
+ status: active
9
+ updated: 2026-07-13
10
+ owners: ["rgirish"]
11
+ files:
12
+ - _underscore/Model/Compass/SalesOrder.php
13
+ - _underscore/Model/Compass/PurchaseOrder.php
14
+ - worker/crons/toga2/compass/workflow/3a_import_office_depot_purchase_orders.php
15
+ related:
16
+ - mits-po-to-so-item-linking.md
17
+ - ../workflows/odp-order-pipeline-to-netsuite.md
18
+ - ../workflows/order-lifecycle-and-data-integrity.md
19
+ - ../../../2.0/apps/_underscore/features/item-fulfillment-stage-lifecycle-and-order-status.md
20
+ ---
21
+
22
+ ## Summary
23
+ Compass **MR** and **MA** sales orders are system-generated from the MITS / Office Depot EDI
24
+ pipeline (they do not originate as user-entered SA orders) and must **skip human approval
25
+ entirely**. Compass's `_status` calculated field gates on **approvals FIRST**: if any stage of
26
+ the active `ApprovalTemplate` has no `ApprovalDecision`, `_status` returns the pending-stage slug
27
+ (`pendingInitialApproval`) *before* it ever evaluates fulfillment. So an MR/MA order that has an
28
+ `Approval` row but no decisions is stuck at "Pending Initial Approval" regardless of shipment.
29
+
30
+ Two independent root causes produced the stuck-order population, and both are addressed at the
31
+ source (auto-approval + item population), **not** in the status SQL.
32
+
33
+ ## Key files / entry points
34
+ - **`_underscore/Model/Compass/SalesOrder.php`**
35
+ - `_status` calculated field — approval gate runs before the shipped-only fulfillment machine
36
+ (see [IF stage lifecycle & order status](../../../2.0/apps/_underscore/features/item-fulfillment-stage-lifecycle-and-order-status.md)
37
+ for the fulfillment half).
38
+ - `postPost` — the `if ($isMrOrder)` block loops `foreach ([1,2] as $step)` and issues two
39
+ `internalApiRequest('POST', '/approval-decisions', …)` with `isApproved=true`,
40
+ note `'Auto-approved: MR order'`. **Went live 2026-06-03.**
41
+ - **`_underscore/Model/Compass/PurchaseOrder.php`** — MR handling (`handleMrOrder`); MR/MA order
42
+ detection.
43
+ - **`worker/crons/toga2/compass/workflow/3a_import_office_depot_purchase_orders.php`** — the
44
+ hourly ODP 850 import that creates MA orders (see the item-population fix in
45
+ [MITS PO → SO Item Linking](mits-po-to-so-item-linking.md)).
46
+
47
+ ## How it works
48
+ ### The approval gate (why empty + un-decided orders stick)
49
+ Prod `Client_Compass` has one active `ApprovalTemplate` (id 1, `salesOrders`) with two stages —
50
+ stage 1 = `step1` = `pendingInitialApproval`, stage 2 = `step2` = `pendingApproval`;
51
+ `APPROVAL_DECISION_TYPE = 1`. The base `_Model_Client_SalesOrder::postPost` creates an `Approval`
52
+ row on **any** order whenever an active template exists. Compass `_status` then requires an
53
+ `ApprovalDecision` for every active-template stage before it will look at fulfillment. No
54
+ decision on a stage ⇒ `_status` short-circuits to that stage's slug.
55
+
56
+ ### Auto-approval (the intended MR path)
57
+ `_Model_Compass_SalesOrder::postPost` auto-approves MR orders by POSTing an approval decision for
58
+ each of the two stages (`isApproved=true`). `_status` only checks `isApproved` on the decision —
59
+ never the `assignedToUserId` / `decidedByUserId` columns — so a system decision with NULL user
60
+ columns is fully valid.
61
+
62
+ ## Client variations
63
+ - **MR** orders originate in Compass; their Compass SO + items pre-exist. Auto-approval covers them
64
+ from 2026-06-03 onward.
65
+ - **MA** orders are **Office-Depot-originated** (created by the 3a cron) and were **never**
66
+ auto-approved — the `isMrOrder` check only matches numbers starting `MR`, so MA falls through
67
+ the gate. MA orders were additionally created as **empty shells** (see below).
68
+
69
+ ## Gotchas / known issues
70
+ - **The stuck orders are EMPTY (zero `SalesOrderItems`).** All stuck MR+MA orders (and their linked
71
+ Compass PO) have no line items — the real line items live on a sibling **Office-Depot-facing SO**
72
+ (`customerId = OfficeDepot`) reachable via `SalesOrders_PurchaseOrders → PurchaseOrders_SalesOrders`.
73
+ A fulfilled-short-circuit in `_status` therefore does **nothing** for them (nothing to fulfill).
74
+ - **Do NOT "fix" this in `_status`.** A speculative fulfilled-short-circuit (plus an extracted
75
+ `_isFullyFulfilledSql` helper) was added and then **reverted** this session: because the stuck
76
+ orders are empty, the short-circuit never fires. The real fix is item population + approval
77
+ decisions at the source.
78
+ - **MA is still creating stuck orders until the item-population cron fix ships** — see
79
+ [MITS PO → SO Item Linking](mits-po-to-so-item-linking.md). Auto-approval for MA is not yet
80
+ implemented (the `isMrOrder` check does not match `MA`).
81
+
82
+ ## Prod status buckets (2026-07-13)
83
+ | Type | NO_APPROVAL (correct — pre-template, gate falls through) | APPROVAL_NO_DECISION (STUCK) | FULLY_APPROVED |
84
+ |---|---|---|---|
85
+ | MR | 5922 (Sep 2025–Jan 2026) | 2927 | 583 |
86
+ | MA | 176 | 192 | 0 |
87
+
88
+ - The 5922 MR + 176 MA `NO_APPROVAL` orders pre-date the active template; they have **no**
89
+ `Approval` row, so `_status` skips the gate and reads correctly. They must **NOT** get an
90
+ `Approval` created.
91
+ - Total stuck: **3119** (2927 MR + 192 MA). A CSV of all 3119 (type, orderNumber, orderUuid,
92
+ dateCreated, compassPurchaseOrder, officeDepotSalesOrder_sourceOfItems) was handed to
93
+ ops/integration for remediation.
94
+
95
+ ## Historical MR remediation (approval-decision backfill)
96
+ A one-off, idempotent, transaction-wrapped SQL backfill unsticks the 2927 historical MR orders by
97
+ inserting the two auto-approval `ApprovalDecisions` (one per stage, mirroring `postPost`'s
98
+ `foreach([1,2])`). Each row: `approvalTemplateStageId` 1/2, `approvalDecisionTypeId=1`,
99
+ `isApproved=1`, `assignedToUserId`/`decidedByUserId` NULL, note
100
+ `'Auto-approved: MR order (historical backfill)'`, `dtDecision NOW()`.
101
+
102
+ Safe scoping baked in — **decisions only, never Approvals**:
103
+ - **`INNER JOIN Approvals` (`recordId=14`)** so it can only add decisions to orders that *already*
104
+ have an `Approval`. Structurally it cannot touch the `NO_APPROVAL` orders (which are already
105
+ correct and must not get an Approval).
106
+ - **Per-stage `NOT EXISTS`** ⇒ idempotent / re-runnable.
107
+ - Includes a PREVIEW count query (prod: 2927 orders → 2927 step-1 + 2927 step-2 rows). Workflow:
108
+ run on beta, verify, COMMIT; then production.
109
+ - Does **not** create Approvals; does **not** handle MA (MA orders are empty and need item backfill
110
+ from the sibling ODP SO first).
111
+
112
+ ## Change history
113
+ - 2026-07-13 — Diagnosed the MR/MA "Pending Initial Approval" stuck-order bug: `_status` gates on
114
+ approvals before fulfillment; MR orders created 2026-02-04..2026-06-02 got an Approval but no
115
+ decisions (auto-approval only live from 2026-06-03), and MA orders were never auto-approved and
116
+ are empty shells. Reverted a speculative `_status` fulfilled-short-circuit (stuck orders are
117
+ empty, so it never fires). Built an idempotent, decisions-only MR approval-decision backfill
118
+ (2927 orders) scoped by `INNER JOIN Approvals` so it can never create an Approval, and a CSV of
119
+ all 3119 stuck orders for ops. MA item population fixed separately at the 3a cron. (rgirish)
120
+
121
+ ## Related docs
122
+ - [Compass MITS PO → SO Item Linking](mits-po-to-so-item-linking.md) — the 3a cron item-population fix that stops NEW empty MA orders.
123
+ - [Compass ODP Order Pipeline to NetSuite](../workflows/odp-order-pipeline-to-netsuite.md)
124
+ - [IF Stage Lifecycle & Order Status](../../../2.0/apps/_underscore/features/item-fulfillment-stage-lifecycle-and-order-status.md) — the shipped-only fulfillment half of Compass `_status`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toga-ai",
3
- "version": "1.0.322",
3
+ "version": "1.0.324",
4
4
  "description": "TOGA Technology Team Claude Knowledge System — shared AI coding harness with skills, knowledge base CLI, and project installer for Claude Code.",
5
5
  "keywords": [
6
6
  "claude",