toga-ai 1.0.81 → 1.0.83

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.
@@ -1,6 +1,27 @@
1
1
  {
2
2
  "hooks": {
3
+ "UserPromptSubmit": [
4
+ {
5
+ "hooks": [
6
+ {
7
+ "type": "command",
8
+ "command": "node \".claude/hooks/toga/kickoff-gate.js\"",
9
+ "timeout": 3000
10
+ }
11
+ ]
12
+ }
13
+ ],
3
14
  "PreToolUse": [
15
+ {
16
+ "matcher": "Bash|Read|Grep|Glob|Edit|Write|MultiEdit|NotebookEdit|Task|WebFetch|WebSearch",
17
+ "hooks": [
18
+ {
19
+ "type": "command",
20
+ "command": "node \".claude/hooks/toga/kickoff-gate.js\"",
21
+ "timeout": 3000
22
+ }
23
+ ]
24
+ },
4
25
  {
5
26
  "matcher": "Bash",
6
27
  "hooks": [
@@ -20,6 +20,10 @@ _Auto-generated by `knowledge.js index`. Do not hand-edit._
20
20
  - **toga2-view** (TOGa View Frontend) — 0 doc(s) → [2.0/apps/toga2-view/INDEX.md](2.0/apps/toga2-view/INDEX.md)
21
21
  - **toga2-hub** (TOGa Hub) — 2 doc(s) → [2.0/apps/toga2-hub/INDEX.md](2.0/apps/toga2-hub/INDEX.md)
22
22
 
23
+ ## standalone framework
24
+
25
+ - **togatech** (TOGA Technology Website) — 1 doc(s) → [standalone/apps/togatech/INDEX.md](standalone/apps/togatech/INDEX.md)
26
+
23
27
  ## Clients
24
28
 
25
29
  - **Compass Canada** (`compass-canada`) → [clients/compass-canada/INDEX.md](clients/compass-canada/INDEX.md)
@@ -10,6 +10,7 @@
10
10
  { "repo": "toga2-view", "project": "TOGa View Frontend", "framework": "2.0", "role": "app", "dependsOn": ["api2"] },
11
11
  { "repo": "togadesk", "project": "TOGa Desk", "framework": "1.0", "role": "app", "dependsOn": [] },
12
12
  { "repo": "togaview", "project": "TOGa View", "framework": "1.0", "role": "app", "dependsOn": [] },
13
- { "repo": "toga2-hub", "project": "TOGa Hub", "framework": "2.0", "role": "app", "dependsOn": ["api2"] }
13
+ { "repo": "toga2-hub", "project": "TOGa Hub", "framework": "2.0", "role": "app", "dependsOn": ["api2"] },
14
+ { "repo": "togatech", "project": "TOGA Technology Website", "framework": "standalone", "role": "app", "dependsOn": [] }
14
15
 
15
16
  ]
@@ -0,0 +1,5 @@
1
+ # togatech (TOGA Technology Website) — standalone knowledge
2
+
3
+ | Doc | Summary | Files |
4
+ |-----|---------|-------|
5
+ | [TOGA Technology Website Architecture](architecture.md) | The public-facing TOGA Technology corporate/marketing website. | togatech/src/main.tsx, togatech/src/App.tsx, togatech/src/routes.tsx, togatech/src/lib/api.ts, togatech/src/lib/contentful.ts, togatech/src/themeConfig/ThemeContext.tsx, togatech/vite.config.ts, togatech/package.json |
@@ -0,0 +1,153 @@
1
+ ---
2
+ title: TOGA Technology Website Architecture
3
+ framework: "standalone"
4
+ repo: togatech
5
+ project: TOGA Technology Website
6
+ client: shared
7
+ type: architecture
8
+ status: active
9
+ updated: 2026-06-15
10
+ owners: [jcardinal]
11
+ files:
12
+ - togatech/src/main.tsx
13
+ - togatech/src/App.tsx
14
+ - togatech/src/routes.tsx
15
+ - togatech/src/lib/api.ts
16
+ - togatech/src/lib/contentful.ts
17
+ - togatech/src/themeConfig/ThemeContext.tsx
18
+ - togatech/vite.config.ts
19
+ - togatech/package.json
20
+ related: []
21
+ ---
22
+
23
+ # TOGA Technology Website Architecture
24
+
25
+ The public-facing TOGA Technology corporate/marketing website. It is a **standalone
26
+ single-page React application** — it does **not** use either PHP framework (1.0 `App_`
27
+ or 2.0 `_underscore`), which is why it lives under the `standalone/` knowledge partition
28
+ rather than `1.0/` or `2.0/`. It consumes TOGA backend services (email + the Talos AI
29
+ API) over HTTP and pulls editorial content from Contentful, but ships no server code of
30
+ its own — it builds to static assets served from `dist/`.
31
+
32
+ Local path on dev machines is tracked in Claude memory (`repo-path-togatech`), not here.
33
+
34
+ ## Stack
35
+
36
+ - **React 18.3 + TypeScript 5.8**, bundled with **Vite 5.4** (`@vitejs/plugin-react`).
37
+ - **Tailwind CSS 3.4** (+ PostCSS / autoprefixer) for styling; theme tokens are driven
38
+ from `src/themeConfig/themes.json`.
39
+ - **React Router 7.9** using `createBrowserRouter` (data-router API).
40
+ - **@agilant/toga-blox** (internal design-system / component + API library) — supplies
41
+ shared UI and the `apiGet` HTTP helper (`@agilant/toga-blox/dist/api/apiFunctions`).
42
+ - **FontAwesome Pro** icon set.
43
+ - **Cypress 15** for end-to-end tests.
44
+
45
+ ## Application bootstrap
46
+
47
+ `src/main.tsx` → `src/App.tsx` → `src/routes.tsx`.
48
+
49
+ - `main.tsx` mounts `<App/>` inside `HelmetProvider` (react-helmet-async, for per-page
50
+ SEO `<head>` management) and `StrictMode`.
51
+ - `App.tsx` wires the global providers, in order:
52
+ 1. `PersistQueryClientProvider` — TanStack **React Query** client persisted to
53
+ `localStorage` (persister key `"commerce"`); default query `staleTime` is 24h.
54
+ 2. `ThemeProvider` (`src/themeConfig/ThemeContext.tsx`) — applies the active theme
55
+ from `themes.json` (color/btn token sets, e.g. the `DEFAULT` theme).
56
+ 3. `RouterProvider` with the `routes` browser router.
57
+ - Also initializes **Google Analytics 4** (`react-ga4`, measurement ID is hardcoded
58
+ in `App.tsx`) and mounts React Query Devtools.
59
+ - **Zustand** is also a dependency for lightweight local state where React Query is not
60
+ the right fit.
61
+
62
+ ## Routing
63
+
64
+ `src/routes.tsx` defines a single `createBrowserRouter`. All routes render under
65
+ `components/templates/AppLayout` (the shared header/footer chrome), and the index route
66
+ group has `ErrorPage` as its `ErrorBoundary`. Pages:
67
+
68
+ | Path | Page |
69
+ |---|---|
70
+ | `/` (index) | `pages/Home` |
71
+ | `/about` | `pages/About` |
72
+ | `/what-we-do` | `pages/WhatWeDo` |
73
+ | `/our-platform` | `pages/OurPlatform` |
74
+ | `/contact` | `pages/Contact` |
75
+ | `/privacy-policy` | `pages/PrivacyPolicy` |
76
+ | `/terms-and-conditions` | `pages/TermsAndConditions` |
77
+ | `*` | `pages/NotFound` |
78
+
79
+ Lazy-route scaffolding is present but commented out (pages are eagerly imported today).
80
+ A Cypress-only test route swaps `WhatWeDoPage` for a component that throws, to exercise
81
+ the error boundary (`VITE_CY_TEST` / `window.Cypress`).
82
+
83
+ ## Page structure — MVVM per page
84
+
85
+ Each page in `src/pages/<Page>/` follows a **view / viewModel** split, e.g. `Home`:
86
+
87
+ ```
88
+ pages/Home/
89
+ HomePage.tsx # entry, composes the view
90
+ index.ts # barrel export
91
+ types.ts # page-local types
92
+ view/ # presentational components (+ components/, index.ts)
93
+ viewModel/ # useHomeViewModel.ts, FIELDS/, index.ts — data + form logic
94
+ ```
95
+
96
+ The **viewModel** holds data fetching (React Query hooks), form field definitions
97
+ (`FIELDS`), and behavior; the **view** is presentational. This keeps page logic testable
98
+ and out of JSX.
99
+
100
+ ## Shared layers
101
+
102
+ - `src/components/` — reusable UI (Header, Footer, Hero, Carousel/Swiper wrappers, Modal,
103
+ TalosChat + TalosChatInput, motion/animation helpers, animated SVGs, etc.).
104
+ - `src/components/templates/` — layout primitives: `AppLayout` (router root chrome),
105
+ `DefaultContainer`, `MarginContainer`, `GlowingContainer`, `ErrorContainer`.
106
+ - `src/lib/` — data/integration layer:
107
+ - `api.ts` — `useSendEmail` (contact-form submission) and `postTalosMessage` (AI chat).
108
+ - `contentful.ts` — Contentful delivery client (`createClient`).
109
+ - `talosSystemPrompt.ts` — system prompt for the Talos chat assistant.
110
+ - `types.ts` — shared API/domain types (`ChatMessage`, `OutputField`, `ChatAPIError`…).
111
+ - `src/utils/` — helpers (`emphasizeText`, `getFontAwesomeIcon`, `animateSections`) and
112
+ hooks (`useFocusTrap`, `useLockBgScroll`).
113
+ - `src/themeConfig/` — `ThemeContext.tsx`, `themes.json`, `types.ts`.
114
+
115
+ ## External integrations
116
+
117
+ All configured via Vite `VITE_*` env vars (values in `.env`, never in the repo docs):
118
+
119
+ - **Email** — `useSendEmail` (`src/lib/api.ts`) calls toga-blox `apiGet` against the
120
+ `email-templates/sendEmail?uuid=…` endpoint (base `VITE_API`), passing a per-form
121
+ `templateUuid` plus a `fieldMapping` of form fields → template `varN` slots. This is
122
+ the consumer side of the 2.0 EmailTemplate `sendEmail` API.
123
+ - **Talos AI chat** — `postTalosMessage` POSTs to `VITE_TALOS_API` with header
124
+ `X-API-Key: VITE_TALOS_API_KEY`, body `{ model: VITE_TALOS_MODEL, system_prompt,
125
+ messages, output_schema, temperature, max_tokens }`. Surfaced by the `TalosChat` UI.
126
+ - **Contentful CMS** — `VITE_CONTENTFUL_SPACE_ID` / `VITE_CONTENTFUL_ACCESS_TOKEN`.
127
+ - **Sentry** (`@sentry/react`) for error monitoring; **GA4** for analytics.
128
+
129
+ ## Build, environments, and deploy
130
+
131
+ - `package.json` scripts: `dev`, `togatech` (dev with `--host togatech`), `build`
132
+ (`tsc && vite build`), `buildBeta` (`--mode beta`), `buildGamma` (`--mode gamma`),
133
+ `preview`, `lint` (ESLint, `--max-warnings 0`).
134
+ - **Multi-environment**: `development`, `beta`, `gamma`, `production` (Vite modes).
135
+ - Vite `base: "/"`, output to `dist/`, `publicDir: public`. A custom `robotsPlugin`
136
+ copies `public/robots.prod.txt` or `public/robots.dev.txt` to `dist/robots.txt` at
137
+ `closeBundle` depending on prod detection (`NODE_ENV` + `VITE_ENV`).
138
+ - `@` path alias → `./src` (Vite `resolve.alias`).
139
+ - Dev server `allowedHosts: ["togatech"]` (matches the `togatech` host script).
140
+
141
+ ## Testing
142
+
143
+ Cypress e2e under `cypress/` (config `cypress.config.ts`). The router exposes a
144
+ deliberate crash route under Cypress to verify the `ErrorPage` boundary.
145
+
146
+ ## Notes / gotchas
147
+
148
+ - `togatech/.env` is currently **tracked in git** and holds the `VITE_*` credentials
149
+ (Contentful token, Talos API key). Treat those as committed secrets — they should be
150
+ rotated and the file removed from tracking / added to `.gitignore`.
151
+ - There is a nested `togatech/` folder inside the repo root containing its own small
152
+ `package.json` and a `cypress/` dir — appears to be a secondary/legacy Cypress setup;
153
+ the primary app lives at the repo root.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toga-ai",
3
- "version": "1.0.81",
3
+ "version": "1.0.83",
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",
@@ -0,0 +1,180 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /*
5
+ * kickoff-gate.js — DETERMINISTIC enforcement of the /kickoff priming gate.
6
+ *
7
+ * Why this exists: the kickoff SKILL.md states a "hard gate" — when /kickoff is
8
+ * invoked you MUST complete priming (Steps 0–6) BEFORE touching the task with any
9
+ * Read/Grep/Glob/Edit/Write/Task call. Prose alone did not stop the model from
10
+ * jumping straight to investigation when /kickoff was followed by a long, specific
11
+ * task description. This hook makes the gate mechanical, not advisory.
12
+ *
13
+ * It registers for TWO events from a single file:
14
+ *
15
+ * UserPromptSubmit → if the submitted prompt invokes /kickoff, ARM the gate:
16
+ * write a session-scoped lock and inject a forceful reminder.
17
+ *
18
+ * PreToolUse → while the gate is armed for THIS session, ALLOW only the
19
+ * read-only priming steps (the priming Bash commands + the
20
+ * interview via AskUserQuestion/Skill, which don't match the
21
+ * PreToolUse matcher) and BLOCK every task tool
22
+ * (Read/Grep/Glob/Edit/Write/MultiEdit/NotebookEdit/Task/
23
+ * WebFetch/WebSearch and any non-priming Bash). The lock
24
+ * AUTO-CLEARS the moment `knowledge.js kickoff-preflight`
25
+ * runs — the definitive priming step that precedes Step 4's
26
+ * first Read. So the model literally cannot investigate or
27
+ * edit the task until it has actually primed.
28
+ *
29
+ * Fail-open: any internal/parse error allows the tool (a crashing gate must never
30
+ * brick every session). The SKILL.md prose remains the backup.
31
+ *
32
+ * Escape hatch: set KICKOFF_GATE_DISABLED=1 to disable enforcement entirely.
33
+ */
34
+
35
+ const fs = require('fs');
36
+ const path = require('path');
37
+
38
+ /* ---------- input: read stdin JSON (CC standard), fall back to env ---------- */
39
+ function readPayload() {
40
+ let raw = '';
41
+ try { raw = fs.readFileSync(0, 'utf8'); } catch (e) { /* no stdin */ }
42
+ let data = {};
43
+ if (raw && raw.trim()) {
44
+ try { data = JSON.parse(raw); } catch (e) { data = {}; }
45
+ }
46
+ // env fallbacks used by this harness's other hooks
47
+ if (!data.tool_name && process.env.CLAUDE_TOOL_NAME) data.tool_name = process.env.CLAUDE_TOOL_NAME;
48
+ if (!data.tool_input && process.env.CLAUDE_TOOL_INPUT) {
49
+ try { data.tool_input = JSON.parse(process.env.CLAUDE_TOOL_INPUT); } catch (e) {}
50
+ }
51
+ if (!data.prompt && process.env.CLAUDE_USER_PROMPT) data.prompt = process.env.CLAUDE_USER_PROMPT;
52
+ if (!data.session_id && process.env.CLAUDE_SESSION_ID) data.session_id = process.env.CLAUDE_SESSION_ID;
53
+ return data;
54
+ }
55
+
56
+ function lockPath() {
57
+ return path.join(process.cwd(), '.claude', '.kickoff-gate.json');
58
+ }
59
+
60
+ function readLock() {
61
+ try { return JSON.parse(fs.readFileSync(lockPath(), 'utf8')); } catch (e) { return null; }
62
+ }
63
+
64
+ function writeLock(sessionId) {
65
+ try {
66
+ fs.mkdirSync(path.dirname(lockPath()), { recursive: true });
67
+ fs.writeFileSync(lockPath(), JSON.stringify({ session: sessionId || null, ts: Date.now() }) + '\n');
68
+ } catch (e) { /* non-fatal */ }
69
+ }
70
+
71
+ function clearLock() {
72
+ try { fs.unlinkSync(lockPath()); } catch (e) { /* already gone */ }
73
+ }
74
+
75
+ /* A prompt invokes /kickoff if it carries any of these signals (covers the literal
76
+ * "/kickoff ...", the expanded skill command form, and the rendered skill header). */
77
+ function invokesKickoff(prompt) {
78
+ if (typeof prompt !== 'string' || !prompt) return false;
79
+ return (
80
+ /(^|\s)\/kickoff(\s|$)/.test(prompt) ||
81
+ /command-name>\s*\/?kickoff\s*<\/command-name>/i.test(prompt) ||
82
+ /Kickoff\s+—\s+prime a coding session/i.test(prompt)
83
+ );
84
+ }
85
+
86
+ /* Bash commands that ARE the read-only priming steps (Steps 0–3). Everything else
87
+ * is task work and stays blocked until preflight clears the gate. */
88
+ function isPrimingBash(command) {
89
+ if (typeof command !== 'string') return false;
90
+ return /knowledge\.js|kickoff-preflight|toga-ai\.version|npm\s+view\s+toga-ai|npx\s+toga-ai|registry\.json|rev-parse/i.test(command);
91
+ }
92
+
93
+ function isPreflightBash(command) {
94
+ return typeof command === 'string' && /kickoff-preflight/i.test(command);
95
+ }
96
+
97
+ /* Tools that constitute touching the task — blocked while the gate is armed. */
98
+ const BLOCKED_TOOLS = new Set([
99
+ 'Read', 'Grep', 'Glob', 'Edit', 'Write', 'MultiEdit',
100
+ 'NotebookEdit', 'Task', 'Agent', 'WebFetch', 'WebSearch',
101
+ ]);
102
+
103
+ function blockMessage() {
104
+ return [
105
+ '🛑 KICKOFF GATE — priming is not complete.',
106
+ '',
107
+ '/kickoff was invoked this session. You MUST finish priming BEFORE touching the task',
108
+ 'with any Read/Grep/Glob/Edit/Write/Task call — no matter how detailed the request after',
109
+ '/kickoff looks. That trailing text is the Step 2 task description, NOT permission to start.',
110
+ '',
111
+ 'Do this now, in order (these are allowed while the gate is armed):',
112
+ ' • Step 0 version check (cat .claude/toga-ai.version ; npm view toga-ai version)',
113
+ ' • Step 1 resolve the team repo (probe for knowledge/registry.json)',
114
+ ' • Step 2 interview the developer (framework / layer / repo(s) / client / task)',
115
+ ' • Step 3 node "<TEAM_REPO>/knowledge.js" kickoff-preflight ... ← this AUTO-CLEARS the gate',
116
+ ' • Step 4+ THEN read the knowledge docs and start the task',
117
+ '',
118
+ 'The gate releases automatically the instant kickoff-preflight runs.',
119
+ '(Emergency override only: set KICKOFF_GATE_DISABLED=1.)',
120
+ ].join('\n');
121
+ }
122
+
123
+ function main() {
124
+ if (process.env.KICKOFF_GATE_DISABLED === '1') process.exit(0);
125
+
126
+ const data = readPayload();
127
+
128
+ // ---- UserPromptSubmit: arm the gate when /kickoff is invoked ----
129
+ // Identify by the presence of a prompt and absence of a tool call.
130
+ const isPromptEvent =
131
+ data.hook_event_name === 'UserPromptSubmit' ||
132
+ (typeof data.prompt === 'string' && !data.tool_name);
133
+
134
+ if (isPromptEvent) {
135
+ if (invokesKickoff(data.prompt)) {
136
+ writeLock(data.session_id);
137
+ // stdout from UserPromptSubmit is injected into the model's context.
138
+ console.log(
139
+ '<system-reminder>KICKOFF GATE ARMED. Before ANY other tool call, complete priming ' +
140
+ 'Steps 0–6 of the kickoff skill — version check, resolve team repo, interview, then ' +
141
+ 'run `knowledge.js kickoff-preflight` (which releases the gate), THEN read docs and work. ' +
142
+ 'The trailing task description is Step 2 input, not permission to start. ' +
143
+ 'A hook will block Read/Grep/Edit/Write/Task until preflight runs.</system-reminder>'
144
+ );
145
+ }
146
+ process.exit(0);
147
+ }
148
+
149
+ // ---- PreToolUse: enforce while armed for THIS session ----
150
+ const lock = readLock();
151
+ if (!lock) process.exit(0);
152
+
153
+ // Cross-session safety: only enforce for the session that armed the gate.
154
+ // If we can't match sessions, fall back to a 3-hour staleness window.
155
+ const sameSession = lock.session && data.session_id && lock.session === data.session_id;
156
+ const fresh = lock.ts && (Date.now() - lock.ts) < 3 * 60 * 60 * 1000;
157
+ if (!sameSession && !fresh) { clearLock(); process.exit(0); }
158
+ if (data.session_id && lock.session && !sameSession) process.exit(0);
159
+
160
+ const tool = data.tool_name || '';
161
+ const command = (data.tool_input && data.tool_input.command) || '';
162
+
163
+ if (tool === 'Bash') {
164
+ if (isPreflightBash(command)) { clearLock(); process.exit(0); } // priming reached — release
165
+ if (isPrimingBash(command)) process.exit(0); // other read-only priming step
166
+ console.log(blockMessage());
167
+ process.exit(2);
168
+ }
169
+
170
+ if (BLOCKED_TOOLS.has(tool)) {
171
+ console.log(blockMessage());
172
+ process.exit(2);
173
+ }
174
+
175
+ // Anything else (AskUserQuestion, Skill, TodoWrite, ExitPlanMode, …) is part of
176
+ // priming/interview — allow.
177
+ process.exit(0);
178
+ }
179
+
180
+ try { main(); } catch (e) { process.exit(0); } // fail-open: never brick a session
@@ -22,6 +22,16 @@ description: Start-of-session context loader for TOGA Technology projects. Run t
22
22
  >
23
23
  > Only after Step 5's "primed and ready" summary (and Step 6's plan, for non-trivial work)
24
24
  > may you touch the task itself.
25
+ >
26
+ > **This gate is mechanically enforced — it is not just prose.** A hook
27
+ > (`hooks/toga/kickoff-gate.js`, wired on `UserPromptSubmit` + `PreToolUse`) arms the
28
+ > instant `/kickoff` is invoked and will **hard-block** `Read`, `Grep`, `Glob`, `Edit`,
29
+ > `Write`, `Task`, and any non-priming `Bash` until you actually prime. The only calls it
30
+ > permits while armed are the read-only priming steps (the version check, team-repo probe,
31
+ > `knowledge.js manifest`, the developer interview) and `knowledge.js kickoff-preflight` —
32
+ > **running preflight (Step 3) is what releases the gate.** So the correct response to a
33
+ > block message is never to fight it: run Steps 0–3, and the moment preflight executes you
34
+ > are free to read docs and work. (Emergency override only: `KICKOFF_GATE_DISABLED=1`.)
25
35
 
26
36
  ## Arguments — text passed after `/kickoff` never skips any step
27
37