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.
- package/.claude/settings.json +21 -0
- package/knowledge/INDEX.md +4 -0
- package/knowledge/registry.json +2 -1
- package/knowledge/standalone/apps/togatech/INDEX.md +5 -0
- package/knowledge/standalone/apps/togatech/architecture.md +153 -0
- package/package.json +1 -1
- package/scripts/hooks/kickoff-gate.js +180 -0
- package/skills/kickoff/SKILL.md +10 -0
package/.claude/settings.json
CHANGED
|
@@ -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": [
|
package/knowledge/INDEX.md
CHANGED
|
@@ -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)
|
package/knowledge/registry.json
CHANGED
|
@@ -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
|
@@ -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
|
package/skills/kickoff/SKILL.md
CHANGED
|
@@ -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
|
|