Skip to content

Commit f9d6417

Browse files
authored
H-6487: Add AI assistant to Petrinaut + demo website (#8750)
1 parent 2de5a8d commit f9d6417

126 files changed

Lines changed: 8287 additions & 1331 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.changeset/fluffy-masks-visit.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@hashintel/ds-components": patch
3+
---
4+
5+
add arrow-right-arrow-left icon

.changeset/polite-snakes-send.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@hashintel/petrinaut-core": patch
3+
---
4+
5+
improve and expand instance action schemas

.changeset/young-kids-laugh.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@hashintel/petrinaut": patch
3+
---
4+
5+
add AI assistant
Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
---
2+
name: fractal-file-structuring
3+
description: "Use when creating, moving, splitting, or organizing TypeScript files and folders. Applies fractal tree file-structuring rules which reduce the cognitive overhead of choosing where to put files and ultimately navigating a codebase (once the structure is established and understood)."
4+
license: MIT
5+
metadata:
6+
triggers:
7+
type: domain
8+
enforcement: suggest
9+
priority: high
10+
keywords:
11+
- TypeScript
12+
- JavaScript
13+
- file structure
14+
- folder structure
15+
- create file
16+
- create folder
17+
- split file
18+
- shared folder
19+
intent-patterns:
20+
- "\\b(create|add|move|split|organize|refactor)\\b.*?\\b(file|folder|directory|module|component|hook|type|helper)\\b"
21+
- "\\b(file|folder|directory)\\b.*?\\b(structure|layout|organization|placement)\\b"
22+
---
23+
24+
# Fractal File Structuring
25+
26+
TypeScript and JavaScript files should be organised in a fractal tree structure. Use this skill when deciding where to create, move, split, or organize files and folders in a TypeScript or JavaScript workspace.
27+
28+
This guidance is based on HASH's file-structuring approach: https://hash.dev/blog/file-structuring
29+
30+
## Scope
31+
32+
Apply this skill to TypeScript and JavaScript source files, including modules, components, hooks, helpers, types, tests, scripts, and entry points.
33+
34+
## Core Rules
35+
36+
### Use kebab-case names
37+
38+
Use kebab-case for all TypeScript and JavaScript file and folder names.
39+
40+
```text
41+
create-worker-factory.ts
42+
playback-settings-menu.tsx
43+
button.tsx
44+
```
45+
46+
Avoid PascalCase, camelCase, and mixed-case file names, even for React components.
47+
48+
### Do not create index files
49+
50+
Do not add `index.ts`, `index.tsx`, `index.js`, or `index.jsx` files for folder imports. Prefer explicit file entry points with meaningful names.
51+
52+
If a subtree needs a public entry point, name that file after the concept it exposes (e.g. `schema.ts`)
53+
54+
### Treat each file as a mini-library
55+
56+
A file should expose one or more named exports with a shared semantic purpose. The file name should summarize that purpose (e.g. `users.ts`)
57+
58+
If a file contains only one main export, prefer naming the file after that export in kebab-case (e.g. `create-user.ts`)
59+
60+
Avoid default exports unless a framework or external API requires them.
61+
62+
### Split outgrown files into private subtrees
63+
64+
When a file becomes too large or contains implementation details worth extracting, create a same-named folder next to it and move private pieces there.
65+
66+
```text
67+
editor-view.tsx # public mini-library: the component other files import
68+
editor-view/
69+
panels.tsx # private entry point imported by editor-view.tsx
70+
panels/
71+
simulate-view.tsx # private to panels.tsx
72+
calculate-timeline-range.ts # private helper used only by editor-view.tsx
73+
create-panel-state.ts # private helper used only by editor-view.tsx
74+
```
75+
76+
Only `editor-view.tsx` should import from direct child mini-libraries such as `editor-view/panels.tsx` and `editor-view/calculate-timeline-range.ts`. Only `editor-view/panels.tsx` should import from `editor-view/panels/*.tsx`. Other files should import from `editor-view.tsx`, not from its private subtree. This keeps `editor-view.tsx` as the API boundary and makes `editor-view/` read as its implementation.
77+
78+
If `editor-view/calculate-timeline-range.ts` grows and needs its own private implementation files, create `editor-view/calculate-timeline-range/`. Only `editor-view/calculate-timeline-range.ts` should import from that deeper subtree.
79+
80+
```text
81+
editor-view/
82+
calculate-timeline-range.ts
83+
calculate-timeline-range/
84+
clamp-time.ts # private to calculate-timeline-range.ts
85+
get-visible-duration.ts # private to calculate-timeline-range.ts
86+
```
87+
88+
### Keep private subtrees private
89+
90+
Do not import directly from another file's implementation folder.
91+
92+
```typescript
93+
// Avoid: reaches into another file's private subtree
94+
import { SimulateView } from "../editor-view/panels/simulate-view";
95+
96+
// Prefer (1): import from a public mini-library (if it is conceptually part of editor-view)
97+
import { EditorView } from "../editor-view";
98+
99+
// Prefer (2): move shared code to a shared folder (if it is NOT conceptually part of editor-view)
100+
import { Button } from "../shared/button";
101+
```
102+
103+
If a resource must be available outside the subtree, re-export it from the subtree root only when it is part of that root's public concept. If it is independently useful to sibling branches, move it to an appropriate `shared/` folder instead.
104+
105+
### Put shared resources at the closest fork
106+
107+
When multiple sibling branches need the same helper, type, component, constant, or hook, place it in the nearest applicable `shared/` folder.
108+
109+
```text
110+
editor-view.tsx
111+
editor-view/
112+
shared/
113+
duration-label.tsx # used by both panels.tsx and bottom-section.tsx
114+
playback-time.ts # shared formatting/parsing logic for this subtree
115+
panels.tsx # imports from panels/
116+
panels/
117+
simulate-view.tsx # private to panels.tsx
118+
bottom-section.tsx # imports from bottom-section/
119+
bottom-section/
120+
bottom-bar.tsx # private to bottom-section.tsx
121+
```
122+
123+
Place shared files as deep as possible while still covering all current consumers. Do not move something to a high-level shared folder just because it might be reused later.
124+
125+
Here `editor-view.tsx` imports `./editor-view/panels` and `./editor-view/bottom-section`. `panels.tsx` may import `./panels/simulate-view` and `./shared/duration-label`; `bottom-section.tsx` may import `./bottom-section/bottom-bar` and `./shared/duration-label`. Nothing else should import from `panels/` or `bottom-section/` directly.
126+
127+
Shared files are mini-libraries too. A shared file can have its own private same-named subtree, and those internals should remain private to that shared file.
128+
129+
```text
130+
editor-view/
131+
shared/
132+
playback-time.ts # public to editor-view/* branches
133+
playback-time/
134+
parse-playback-time.ts # private to playback-time.ts
135+
format-playback-time.ts # private to playback-time.ts
136+
```
137+
138+
If later only `bottom-bar.tsx` uses `duration-label.tsx`, move it beside `bottom-bar.tsx` or under `bottom-bar/`. The folder structure should describe current consumers, not preserve old sharing.
139+
140+
### Use relative imports within a workspace
141+
142+
For imports inside the same workspace, use relative paths. Do not introduce workspace-local aliases just to shorten paths.
143+
144+
Imports from other workspaces should use the package name.
145+
146+
### Co-locate unit tests
147+
148+
Place unit tests next to the file they cover.
149+
150+
```text
151+
foo.ts
152+
foo.test.ts
153+
```
154+
155+
If a private extracted file needs direct tests, place those tests next to that extracted file.
156+
157+
```text
158+
editor-view.tsx
159+
editor-view.test.tsx
160+
editor-view/
161+
calculate-timeline-range.ts
162+
calculate-timeline-range.test.ts
163+
```
164+
165+
Prefer testing through the public mini-library when that gives enough coverage. Add direct tests for private extracted files when the logic is complex enough that tests through the owner would be indirect or brittle.
166+
167+
### Match the current shape
168+
169+
Organize files for the code's current relationships, not speculative future reuse. Moving files later is expected and cheaper than adding premature structure now.
170+
171+
## Decision Checklist
172+
173+
Before creating a TypeScript or JavaScript file or folder:
174+
175+
1. Identify the semantic concept the file represents.
176+
2. Name the file or folder in kebab-case.
177+
3. If extracting from an existing file, put private implementation files under a same-named folder.
178+
4. If multiple current branches need the resource, put it in the nearest `shared/` folder.
179+
5. Avoid `index` files and implicit folder imports.
180+
6. Use relative imports within the workspace.
181+
7. Co-locate tests with the file under test.
182+
183+
## When Unsure
184+
185+
Choose the location that communicates the file's current consumers and API boundary most clearly:
186+
187+
- Private implementation detail: place it under the owning file's same-named folder, and import it only from that owner.
188+
- Named mini-library: create a normal named file when the concept has its own purpose and exports a small API for nearby consumers.
189+
- Shared mini-library: place the named file in the closest `shared/` folder when multiple branches need that API.
190+
- Subtree entry point: expose the public API from a named root file, and keep any deeper implementation files private to that root.
191+
192+
Do not add broad `components`, `hooks`, `utils`, `types`, or `services` folders unless absolutely necessary. If they exist, these folders MUST only be imported from by files called `components.ts`, `hooks.ts`, etc.

.claude/skills/skill-rules.json

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,36 @@
7878
"blockMessage": "Skill is required to proceed",
7979
"skipConditions": {}
8080
},
81+
"fractal-file-structuring": {
82+
"type": "domain",
83+
"enforcement": "suggest",
84+
"priority": "high",
85+
"description": "Use when creating, moving, splitting, or organizing TypeScript files and folders. Applies fractal tree file-structuring rules which reduce the cognitive overhead of choosing where to put files and ultimately navigating a codebase (once the structure is established and understood).",
86+
"promptTriggers": {
87+
"keywords": [
88+
"TypeScript",
89+
"JavaScript",
90+
"file structure",
91+
"folder structure",
92+
"create file",
93+
"create folder",
94+
"split file",
95+
"shared folder"
96+
],
97+
"intentPatterns": [
98+
"\\b(create|add|move|split|organize|refactor)\\b.*?\\b(file|folder|directory|module|component|hook|type|helper)\\b",
99+
"\\b(file|folder|directory)\\b.*?\\b(structure|layout|organization|placement)\\b"
100+
]
101+
},
102+
"fileTriggers": {
103+
"include": [],
104+
"exclude": [],
105+
"content": [],
106+
"create-only": false
107+
},
108+
"blockMessage": "Skill is required to proceed",
109+
"skipConditions": {}
110+
},
81111
"handling-rust-errors": {
82112
"type": "domain",
83113
"enforcement": "suggest",

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,3 +182,4 @@ out/
182182

183183
# Jujutsu
184184
.jj/
185+
.vercel

apps/mcp/linear/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
"@local/tsconfig": "workspace:*",
2424
"@modelcontextprotocol/sdk": "1.26.0",
2525
"dotenv-flow": "3.3.0",
26-
"zod": "4.1.12",
26+
"zod": "4.4.3",
2727
"zod-to-json-schema": "3.24.6"
2828
},
2929
"devDependencies": {

apps/mcp/notion/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
"@notionhq/client": "5.3.0",
2424
"dotenv-flow": "3.3.0",
2525
"notion-to-md": "3.1.9",
26-
"zod": "4.1.12",
26+
"zod": "4.4.3",
2727
"zod-to-json-schema": "3.24.6"
2828
},
2929
"devDependencies": {
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
OPENAI_API_KEY=sk-xxxx

apps/petrinaut-website/README.md

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
# Petrinaut Website
2+
3+
A website for demoing Petrinaut (libs/@hashintel/petrinaut).
4+
5+
A SPA plus a single API function that proxies AI requests to OpenAI.
6+
7+
## Quickstart
8+
9+
```sh
10+
cp .env.example .env.local
11+
# add your OPENAI_API_KEY to .env.local, if you want to use the chat feature
12+
13+
turbo run dev
14+
```
15+
16+
The dev server runs at [http://localhost:5173](http://localhost:5173). A plugin in `vite.config.ts` loads the API function.
17+
18+
In production, the function in the `api` folder is automatically deployed as a Vercel Serverless Function.
19+
20+
## Environment variables
21+
22+
| Name | Required | Used by | Notes |
23+
| -------------------- | ---------------- | ---------------- | --------------------------------------------------------- |
24+
| `OPENAI_API_KEY` | for chat to work | `api/chat.ts` | OpenAI key the function uses to call `streamText`. |
25+
| `PETRINAUT_AI_MODEL` | no | `api/chat.ts` | Overrides the default OpenAI model id. |
26+
| `SENTRY_DSN` | no | `vite.config.ts` | Wired into the bundle via `__SENTRY_DSN__` at build time. |
27+
28+
Local values live in `.env.local`; Vite's `loadEnv` (see [`vite.config.ts`](vite.config.ts)) copies them into `process.env` for both the dev server and the chat function. In production, set these in the Vercel project settings.
29+
30+
## Testing the API against the built output
31+
32+
A plain `yarn build && yarn vite preview` only serves the static `dist/` assets - `/api/chat` will 404 because the dev plugin is not loaded by `vite preview`. Use one of the options below to exercise the production code path locally.
33+
34+
### Option A: `vercel dev` (recommended)
35+
36+
Closest to the real Vercel runtime. It builds the site, bundles the function, and serves both from a single port using the actual Node runtime + routing layer.
37+
38+
Requires linking to a Vercel project. If you don't have access, go for Option B (or just use `turbo run dev` instead).
39+
40+
```sh
41+
cd apps/petrinaut-website
42+
43+
npx vercel link # first-time setup
44+
45+
npx vercel dev # builds + serves on http://localhost:3000
46+
```
47+
48+
Notes:
49+
50+
- `vercel dev` does not read your existing `dist/`; it rebuilds. If you specifically need to inspect the artifact you already produced, use option B (or amend the devCommand in vercel.json to remove the build step).
51+
52+
### Option B: `vite preview` + a sibling Node API server
53+
54+
Useful when you want to serve the literal `dist/` artifact you just built and avoid the Vercel CLI. It is two processes, glued together by `preview.proxy`.
55+
56+
1. Add a proxy entry to `vite.config.ts` (only needed while you are testing this flow):
57+
58+
```ts
59+
preview: {
60+
proxy: { "/api": "http://localhost:3001" },
61+
},
62+
```
63+
64+
2. Create a throwaway `scripts/preview-api.mjs` that mounts the same handler with `createServerAdapter`:
65+
66+
```js
67+
import { createServer } from "node:http";
68+
import { createServerAdapter } from "@whatwg-node/server";
69+
import handler from "../api/chat.ts";
70+
71+
createServer(createServerAdapter(handler)).listen(3001, () => {
72+
console.log("preview API listening on http://localhost:3001");
73+
});
74+
```
75+
76+
3. Run them side by side (Node 22.6+ can execute the TypeScript entry directly with `--experimental-strip-types`):
77+
78+
```sh
79+
yarn build
80+
yarn vite preview # :4173
81+
node --experimental-strip-types scripts/preview-api.mjs # :3001
82+
```
83+
84+
`/api/chat` requests against `:4173` will be proxied to the local API server, which loads the same handler the deployed function uses.
85+
86+
## Known caveats
87+
88+
- **In-memory rate limiting.** [`api/chat.ts`](api/chat.ts) keys rate-limit buckets by the client IP that Vercel's edge writes into `x-forwarded-for` (which Vercel actively prevents the caller from spoofing - see the [request headers docs](https://vercel.com/docs/edge-network/headers/request-headers)). The bucket map lives in module scope, so it resets on cold start and is not shared between concurrent function instances.
89+
- **`vercel-build.sh` deletes the repo-root `.env`.** This is intentional (mise picks it up otherwise), but worth knowing if you run `vercel dev` locally and keep secrets there.

0 commit comments

Comments
 (0)