howone 0.1.23 → 0.1.26
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/package.json +1 -1
- package/templates/vite/.howone/skills/howone/01-architect/01-app-generation.md +215 -0
- package/templates/vite/.howone/skills/{howone-sdk → howone}/01-architect/02-manifest-codegen.md +67 -4
- package/templates/vite/.howone/skills/howone/02-database/01-schema-design.md +541 -0
- package/templates/vite/.howone/skills/howone/02-database/02-schema-operations.md +398 -0
- package/templates/vite/.howone/skills/howone/02-database/03-data-access-patterns.md +309 -0
- package/templates/vite/.howone/skills/howone/02-database/04-query-dsl-and-responses.md +237 -0
- package/templates/vite/.howone/skills/howone/02-database/05-ai-persistence-patterns.md +372 -0
- package/templates/vite/.howone/skills/{howone-sdk → howone}/03-sdk/01-client-setup.md +58 -36
- package/templates/vite/.howone/skills/{howone-sdk → howone}/03-sdk/02-entity-operations.md +67 -0
- package/templates/vite/.howone/skills/howone/03-sdk/03-auth.md +414 -0
- package/templates/vite/.howone/skills/howone/03-sdk/04-react-integration.md +191 -0
- package/templates/vite/.howone/skills/{howone-sdk → howone}/03-sdk/07-ai-action-calls.md +168 -64
- package/templates/vite/.howone/skills/howone/03-sdk/08-extension-boundaries.md +226 -0
- package/templates/vite/.howone/skills/howone/04-ai/01-ai-capability-architecture.md +205 -0
- package/templates/vite/.howone/skills/howone/04-ai/02-workflow-contract-rules.md +426 -0
- package/templates/vite/.howone/skills/howone/04-ai/03-ai-sdk-handoff.md +234 -0
- package/templates/vite/.howone/skills/howone/04-ai/04-service-capability-catalog.md +281 -0
- package/templates/vite/.howone/skills/howone/04-ai/05-workflow-operations.md +256 -0
- package/templates/vite/.howone/skills/howone/04-ai/06-ai-feature-playbooks.md +296 -0
- package/templates/vite/.howone/skills/{howone-sdk → howone}/SKILL.md +29 -12
- package/templates/vite/.howone/skills/howone/agents/openai.yaml +4 -0
- package/templates/vite/package.json +1 -1
- package/templates/vite/.howone/skills/howone-sdk/01-architect/01-app-generation.md +0 -126
- package/templates/vite/.howone/skills/howone-sdk/02-database/01-schema-design.md +0 -147
- package/templates/vite/.howone/skills/howone-sdk/02-database/02-schema-operations.md +0 -96
- package/templates/vite/.howone/skills/howone-sdk/02-database/03-data-access-patterns.md +0 -172
- package/templates/vite/.howone/skills/howone-sdk/03-sdk/03-auth.md +0 -616
- package/templates/vite/.howone/skills/howone-sdk/03-sdk/04-react-integration.md +0 -398
- package/templates/vite/.howone/skills/howone-sdk/04-ai/.gitkeep +0 -1
- package/templates/vite/.howone/skills/howone-sdk/04-ai/01-ai-capability-architecture.md +0 -142
- package/templates/vite/.howone/skills/howone-sdk/04-ai/02-workflow-contract-rules.md +0 -169
- package/templates/vite/.howone/skills/howone-sdk/04-ai/03-ai-sdk-handoff.md +0 -80
- package/templates/vite/.howone/skills/howone-sdk/agents/openai.yaml +0 -4
- /package/templates/vite/.howone/skills/{howone-sdk → howone}/03-sdk/05-file-upload.md +0 -0
- /package/templates/vite/.howone/skills/{howone-sdk → howone}/03-sdk/06-raw-http.md +0 -0
|
@@ -1,398 +0,0 @@
|
|
|
1
|
-
# React Integration
|
|
2
|
-
|
|
3
|
-
## What `@howone/sdk/react` Provides
|
|
4
|
-
|
|
5
|
-
`@howone/sdk/react` is a **thin UI layer** for auth state, theme, and loading spinners. It does **not** provide entity, query, or AI data hooks.
|
|
6
|
-
|
|
7
|
-
Exports:
|
|
8
|
-
- `HowOneProvider` — all-in-one app provider (auth, theme, toasts, floating button)
|
|
9
|
-
- `useHowoneContext` — access auth state (user, token, isAuthenticated, logout)
|
|
10
|
-
- `FloatingButton` — a floating action button (shows Login when unauthenticated)
|
|
11
|
-
- `Loading` — full-page or inline loading component
|
|
12
|
-
- `LoadingSpinner` — headless spinner for composition
|
|
13
|
-
|
|
14
|
-
---
|
|
15
|
-
|
|
16
|
-
## Import
|
|
17
|
-
|
|
18
|
-
```ts
|
|
19
|
-
import {
|
|
20
|
-
HowOneProvider,
|
|
21
|
-
useHowoneContext,
|
|
22
|
-
FloatingButton,
|
|
23
|
-
Loading,
|
|
24
|
-
LoadingSpinner,
|
|
25
|
-
} from '@howone/sdk/react'
|
|
26
|
-
```
|
|
27
|
-
|
|
28
|
-
---
|
|
29
|
-
|
|
30
|
-
## HowOneProvider
|
|
31
|
-
|
|
32
|
-
Wrap your entire app. Provides auth, theme, and toast context to all children.
|
|
33
|
-
|
|
34
|
-
`HowOneProvider` reads SDK environment and project defaults from `createClient`. Configure
|
|
35
|
-
`projectId` and `env` once in `src/lib/sdk.ts`; do not pass a separate env to the provider.
|
|
36
|
-
|
|
37
|
-
```tsx
|
|
38
|
-
// main.tsx or app/layout.tsx
|
|
39
|
-
import React from 'react'
|
|
40
|
-
import ReactDOM from 'react-dom/client'
|
|
41
|
-
import { HowOneProvider } from '@howone/sdk/react'
|
|
42
|
-
import './lib/sdk'
|
|
43
|
-
import App from './App'
|
|
44
|
-
|
|
45
|
-
ReactDOM.createRoot(document.getElementById('root')!).render(
|
|
46
|
-
<React.StrictMode>
|
|
47
|
-
<HowOneProvider
|
|
48
|
-
auth="required"
|
|
49
|
-
brand="visible"
|
|
50
|
-
theme="system"
|
|
51
|
-
>
|
|
52
|
-
<App />
|
|
53
|
-
</HowOneProvider>
|
|
54
|
-
</React.StrictMode>
|
|
55
|
-
)
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
### HowOneProviderProps
|
|
59
|
-
|
|
60
|
-
```ts
|
|
61
|
-
type HowOneAuthMode = 'required' | 'optional' | 'none'
|
|
62
|
-
type HowOneThemeMode = 'dark' | 'light' | 'system' | 'inherit'
|
|
63
|
-
type HowOneBrandMode = 'visible' | 'hidden'
|
|
64
|
-
|
|
65
|
-
interface HowOneProviderProps {
|
|
66
|
-
children: React.ReactNode
|
|
67
|
-
|
|
68
|
-
// Optional override. Prefer configuring projectId once in createClient.
|
|
69
|
-
projectId?: string
|
|
70
|
-
|
|
71
|
-
// Auth behaviour
|
|
72
|
-
auth?: HowOneAuthMode
|
|
73
|
-
// 'required' — redirects unauthenticated users to login page
|
|
74
|
-
// 'optional' — allows both authenticated and unauthenticated users
|
|
75
|
-
// 'none' — disables auth entirely
|
|
76
|
-
|
|
77
|
-
// Brand button (HowOne branding)
|
|
78
|
-
brand?: HowOneBrandMode // default: 'visible'
|
|
79
|
-
showBrandButton?: boolean // alias for brand
|
|
80
|
-
|
|
81
|
-
// Theme
|
|
82
|
-
theme?: HowOneThemeMode // default: 'system'
|
|
83
|
-
themeStorageKey?: string // localStorage key for theme preference
|
|
84
|
-
forceTheme?: boolean // ignore user preference and force theme
|
|
85
|
-
}
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
### Common provider configurations
|
|
89
|
-
|
|
90
|
-
```tsx
|
|
91
|
-
// Public app — no auth required
|
|
92
|
-
<HowOneProvider auth="none" theme="system" brand="hidden">
|
|
93
|
-
<App />
|
|
94
|
-
</HowOneProvider>
|
|
95
|
-
|
|
96
|
-
// Auth-gated app — redirect to login if not authenticated
|
|
97
|
-
<HowOneProvider auth="required" theme="system">
|
|
98
|
-
<App />
|
|
99
|
-
</HowOneProvider>
|
|
100
|
-
|
|
101
|
-
// Optional auth — user can browse without logging in
|
|
102
|
-
<HowOneProvider auth="optional" theme="inherit" brand="visible">
|
|
103
|
-
<App />
|
|
104
|
-
</HowOneProvider>
|
|
105
|
-
|
|
106
|
-
// Dark mode forced
|
|
107
|
-
<HowOneProvider auth="required" theme="dark" forceTheme>
|
|
108
|
-
<App />
|
|
109
|
-
</HowOneProvider>
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
---
|
|
113
|
-
|
|
114
|
-
## useHowoneContext
|
|
115
|
-
|
|
116
|
-
Access auth state inside any component wrapped by `HowOneProvider`.
|
|
117
|
-
|
|
118
|
-
```ts
|
|
119
|
-
type HowoneContextValue = {
|
|
120
|
-
user: {
|
|
121
|
-
id: string // backend owner id; same as userId when JWT has userId
|
|
122
|
-
userId?: string // authenticated backend user id when present in JWT
|
|
123
|
-
puid?: string // public UUID; do not use as public ownerId scope
|
|
124
|
-
email: string
|
|
125
|
-
name: string
|
|
126
|
-
avatar: string
|
|
127
|
-
appId?: string
|
|
128
|
-
} | null
|
|
129
|
-
token: string | null
|
|
130
|
-
isAuthenticated: boolean
|
|
131
|
-
logout: () => void
|
|
132
|
-
}
|
|
133
|
-
```
|
|
134
|
-
|
|
135
|
-
### Usage
|
|
136
|
-
|
|
137
|
-
```tsx
|
|
138
|
-
import { useHowoneContext } from '@howone/sdk/react'
|
|
139
|
-
|
|
140
|
-
function UserMenu() {
|
|
141
|
-
const { user, isAuthenticated, logout } = useHowoneContext()
|
|
142
|
-
|
|
143
|
-
if (!isAuthenticated || !user) {
|
|
144
|
-
return <a href="/login">Login</a>
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
return (
|
|
148
|
-
<div>
|
|
149
|
-
<img src={user.avatar} alt={user.name} />
|
|
150
|
-
<span>{user.name}</span>
|
|
151
|
-
<button onClick={logout}>Logout</button>
|
|
152
|
-
</div>
|
|
153
|
-
)
|
|
154
|
-
}
|
|
155
|
-
```
|
|
156
|
-
|
|
157
|
-
### Conditional rendering based on auth state
|
|
158
|
-
|
|
159
|
-
```tsx
|
|
160
|
-
function Dashboard() {
|
|
161
|
-
const { isAuthenticated, user } = useHowoneContext()
|
|
162
|
-
|
|
163
|
-
if (!isAuthenticated) {
|
|
164
|
-
return <div>Please log in to continue.</div>
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
return <div>Welcome, {user?.name}!</div>
|
|
168
|
-
}
|
|
169
|
-
```
|
|
170
|
-
|
|
171
|
-
### Using token for direct API calls
|
|
172
|
-
|
|
173
|
-
```tsx
|
|
174
|
-
import { useHowoneContext } from '@howone/sdk/react'
|
|
175
|
-
|
|
176
|
-
function DebugPanel() {
|
|
177
|
-
const { token } = useHowoneContext()
|
|
178
|
-
|
|
179
|
-
async function copyToken() {
|
|
180
|
-
if (token) await navigator.clipboard.writeText(token)
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
return (
|
|
184
|
-
<button onClick={copyToken} disabled={!token}>
|
|
185
|
-
Copy JWT Token
|
|
186
|
-
</button>
|
|
187
|
-
)
|
|
188
|
-
}
|
|
189
|
-
```
|
|
190
|
-
|
|
191
|
-
---
|
|
192
|
-
|
|
193
|
-
## FloatingButton
|
|
194
|
-
|
|
195
|
-
A floating action button that renders in the bottom corner of the screen. When unauthenticated, it shows a "Login" label by default.
|
|
196
|
-
|
|
197
|
-
```tsx
|
|
198
|
-
import { FloatingButton } from '@howone/sdk/react'
|
|
199
|
-
|
|
200
|
-
// Default — shows Login/brand button
|
|
201
|
-
<FloatingButton />
|
|
202
|
-
|
|
203
|
-
// Custom text and handler
|
|
204
|
-
<FloatingButton
|
|
205
|
-
text="Get Started"
|
|
206
|
-
onClick={() => router.push('/signup')}
|
|
207
|
-
/>
|
|
208
|
-
|
|
209
|
-
// Custom styling
|
|
210
|
-
<FloatingButton
|
|
211
|
-
text="Support"
|
|
212
|
-
onClick={() => setShowChat(true)}
|
|
213
|
-
className="bg-blue-600 hover:bg-blue-700"
|
|
214
|
-
/>
|
|
215
|
-
```
|
|
216
|
-
|
|
217
|
-
### FloatingButtonProps
|
|
218
|
-
|
|
219
|
-
```ts
|
|
220
|
-
interface FloatingButtonProps {
|
|
221
|
-
text?: string // button label (default: 'Login' or brand text)
|
|
222
|
-
onClick?: () => void
|
|
223
|
-
className?: string // additional CSS classes
|
|
224
|
-
}
|
|
225
|
-
```
|
|
226
|
-
|
|
227
|
-
---
|
|
228
|
-
|
|
229
|
-
## Loading Components
|
|
230
|
-
|
|
231
|
-
### Loading — full-page or inline loading state
|
|
232
|
-
|
|
233
|
-
```tsx
|
|
234
|
-
import { Loading } from '@howone/sdk/react'
|
|
235
|
-
|
|
236
|
-
// Full-screen overlay
|
|
237
|
-
<Loading fullScreen label="Loading your workspace..." />
|
|
238
|
-
|
|
239
|
-
// Inline with description
|
|
240
|
-
<Loading
|
|
241
|
-
label="Generating story..."
|
|
242
|
-
description="This may take a moment"
|
|
243
|
-
size="lg"
|
|
244
|
-
tone="brand"
|
|
245
|
-
/>
|
|
246
|
-
|
|
247
|
-
// Minimal
|
|
248
|
-
<Loading />
|
|
249
|
-
```
|
|
250
|
-
|
|
251
|
-
### LoadingSpinner — headless spinner
|
|
252
|
-
|
|
253
|
-
```tsx
|
|
254
|
-
import { LoadingSpinner } from '@howone/sdk/react'
|
|
255
|
-
|
|
256
|
-
// Sizes: 'sm' | 'md' | 'lg'
|
|
257
|
-
// Tones: 'brand' | 'neutral' | 'inverse'
|
|
258
|
-
|
|
259
|
-
<LoadingSpinner size="md" tone="brand" />
|
|
260
|
-
<LoadingSpinner size="sm" tone="neutral" className="mr-2" />
|
|
261
|
-
```
|
|
262
|
-
|
|
263
|
-
### LoadingProps
|
|
264
|
-
|
|
265
|
-
```ts
|
|
266
|
-
type LoadingSize = 'sm' | 'md' | 'lg'
|
|
267
|
-
type LoadingTone = 'brand' | 'neutral' | 'inverse'
|
|
268
|
-
|
|
269
|
-
interface LoadingProps {
|
|
270
|
-
size?: LoadingSize // default: 'md'
|
|
271
|
-
tone?: LoadingTone // default: 'brand'
|
|
272
|
-
label?: string // primary text
|
|
273
|
-
description?: string // secondary text
|
|
274
|
-
className?: string
|
|
275
|
-
fullScreen?: boolean // renders over full viewport
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
interface LoadingSpinnerProps {
|
|
279
|
-
size?: LoadingSize
|
|
280
|
-
tone?: LoadingTone
|
|
281
|
-
className?: string
|
|
282
|
-
}
|
|
283
|
-
```
|
|
284
|
-
|
|
285
|
-
---
|
|
286
|
-
|
|
287
|
-
## Entity and AI Data in React
|
|
288
|
-
|
|
289
|
-
`@howone/sdk/react` provides **no data hooks**. Use `useEffect`/`useState` or TanStack Query around the plain async SDK calls.
|
|
290
|
-
|
|
291
|
-
### Pattern: auth-gated data fetch
|
|
292
|
-
|
|
293
|
-
```tsx
|
|
294
|
-
import { useEffect, useState } from 'react'
|
|
295
|
-
import { useHowoneContext } from '@howone/sdk/react'
|
|
296
|
-
import { Loading } from '@howone/sdk/react'
|
|
297
|
-
import howone, { type StoryRecord } from '@/lib/sdk'
|
|
298
|
-
|
|
299
|
-
function MyStories() {
|
|
300
|
-
const { isAuthenticated } = useHowoneContext()
|
|
301
|
-
const [stories, setStories] = useState<StoryRecord[]>([])
|
|
302
|
-
const [loading, setLoading] = useState(false)
|
|
303
|
-
|
|
304
|
-
useEffect(() => {
|
|
305
|
-
if (!isAuthenticated) return
|
|
306
|
-
|
|
307
|
-
setLoading(true)
|
|
308
|
-
howone.entities.Story.query.mine({ page: { number: 1, size: 20 } })
|
|
309
|
-
.then(result => setStories(result.items))
|
|
310
|
-
.finally(() => setLoading(false))
|
|
311
|
-
}, [isAuthenticated])
|
|
312
|
-
|
|
313
|
-
if (!isAuthenticated) return <div>Please log in.</div>
|
|
314
|
-
if (loading) return <Loading label="Loading your stories..." />
|
|
315
|
-
|
|
316
|
-
return (
|
|
317
|
-
<ul>
|
|
318
|
-
{stories.map(s => <li key={s.id}>{s.title}</li>)}
|
|
319
|
-
</ul>
|
|
320
|
-
)
|
|
321
|
-
}
|
|
322
|
-
```
|
|
323
|
-
|
|
324
|
-
### Pattern: Full app layout
|
|
325
|
-
|
|
326
|
-
```tsx
|
|
327
|
-
// src/main.tsx
|
|
328
|
-
import React from 'react'
|
|
329
|
-
import ReactDOM from 'react-dom/client'
|
|
330
|
-
import { HowOneProvider } from '@howone/sdk/react'
|
|
331
|
-
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
|
332
|
-
import './lib/sdk'
|
|
333
|
-
import App from './App'
|
|
334
|
-
|
|
335
|
-
const queryClient = new QueryClient()
|
|
336
|
-
|
|
337
|
-
ReactDOM.createRoot(document.getElementById('root')!).render(
|
|
338
|
-
<React.StrictMode>
|
|
339
|
-
<HowOneProvider
|
|
340
|
-
auth="required"
|
|
341
|
-
theme="system"
|
|
342
|
-
brand="visible"
|
|
343
|
-
>
|
|
344
|
-
<QueryClientProvider client={queryClient}>
|
|
345
|
-
<App />
|
|
346
|
-
</QueryClientProvider>
|
|
347
|
-
</HowOneProvider>
|
|
348
|
-
</React.StrictMode>
|
|
349
|
-
)
|
|
350
|
-
```
|
|
351
|
-
|
|
352
|
-
### Pattern: conditional AI action based on auth
|
|
353
|
-
|
|
354
|
-
```tsx
|
|
355
|
-
import { useState } from 'react'
|
|
356
|
-
import { useHowoneContext } from '@howone/sdk/react'
|
|
357
|
-
import howone from '@/lib/sdk'
|
|
358
|
-
|
|
359
|
-
function AIGenerateButton({ topic }: { topic: string }) {
|
|
360
|
-
const { isAuthenticated } = useHowoneContext()
|
|
361
|
-
const [loading, setLoading] = useState(false)
|
|
362
|
-
const [result, setResult] = useState<string | null>(null)
|
|
363
|
-
|
|
364
|
-
async function handleGenerate() {
|
|
365
|
-
if (!isAuthenticated) {
|
|
366
|
-
howone.auth.login()
|
|
367
|
-
return
|
|
368
|
-
}
|
|
369
|
-
setLoading(true)
|
|
370
|
-
try {
|
|
371
|
-
const output = await howone.ai.generateStory.run({ topic, language: 'en' })
|
|
372
|
-
setResult(output.content)
|
|
373
|
-
} finally {
|
|
374
|
-
setLoading(false)
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
return (
|
|
379
|
-
<div>
|
|
380
|
-
<button onClick={handleGenerate} disabled={loading}>
|
|
381
|
-
{loading ? 'Generating...' : isAuthenticated ? 'Generate' : 'Login to Generate'}
|
|
382
|
-
</button>
|
|
383
|
-
{result && <p>{result}</p>}
|
|
384
|
-
</div>
|
|
385
|
-
)
|
|
386
|
-
}
|
|
387
|
-
```
|
|
388
|
-
|
|
389
|
-
---
|
|
390
|
-
|
|
391
|
-
## Common Mistakes
|
|
392
|
-
|
|
393
|
-
| Mistake | Correct Pattern |
|
|
394
|
-
|---|---|
|
|
395
|
-
| `import { useHowoneContext } from '@howone/sdk'` | Import from `'@howone/sdk/react'` |
|
|
396
|
-
| Expecting entity data hooks from `useHowoneContext` | Use `useEffect`/`useState` + `howone.entities.*` directly |
|
|
397
|
-
| Placing `<HowOneProvider>` inside a component that re-renders | Place at root level (main.tsx / app layout) |
|
|
398
|
-
| Using `user.id` without null-checking `user` | Guard: `if (!user) return` or use optional chaining `user?.id` |
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
|
|
@@ -1,142 +0,0 @@
|
|
|
1
|
-
# AI Capability Architecture
|
|
2
|
-
|
|
3
|
-
Use this reference when a HowOne app needs AI generation, editing, analysis, research, or other
|
|
4
|
-
workflow-backed behavior.
|
|
5
|
-
|
|
6
|
-
## Core Split
|
|
7
|
-
|
|
8
|
-
HowOne AI has four separate layers:
|
|
9
|
-
|
|
10
|
-
1. **Capability contract**: `ai-capability-design` owns the name, description, input schema, output
|
|
11
|
-
schema, output entity name, workflow ID, versions, preview, apply, restore, and manifest sync.
|
|
12
|
-
2. **External workflow implementation**: `external-ai-capability` submits create/update operations
|
|
13
|
-
to the workflow service using the synced contract from `.howone/ai/manifest.json`.
|
|
14
|
-
3. **Workflow status/background layer**: status checking, request tracking, completion, failure, and
|
|
15
|
-
durable background-task behavior are handled outside the contract tool. Preserve request IDs and
|
|
16
|
-
workflow config IDs for that layer.
|
|
17
|
-
4. **SDK binding and UI**: `src/lib/sdk.ts` is generated from `.howone/ai/manifest.json`, then the
|
|
18
|
-
app calls `howone.ai.<capability>.run()`, `.stream()`, or `.events()`.
|
|
19
|
-
|
|
20
|
-
Do not collapse these layers. The common failure is putting workflow implementation, persistence,
|
|
21
|
-
and UI details into the capability schema.
|
|
22
|
-
|
|
23
|
-
## Source of Truth
|
|
24
|
-
|
|
25
|
-
```text
|
|
26
|
-
agent proposal != source of truth
|
|
27
|
-
applied AI capability version = source of truth
|
|
28
|
-
.howone/ai/manifest.json = local source for codegen
|
|
29
|
-
src/lib/sdk.ts = generated binding output
|
|
30
|
-
external workflow service = implementation builder/editor
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
The agent proposes a contract. The backend validates and versions it. The sync tool writes the
|
|
34
|
-
manifest. App code is generated from the manifest.
|
|
35
|
-
|
|
36
|
-
## Standard AI Feature Workflow
|
|
37
|
-
|
|
38
|
-
1. Inspect current AI state with `ai-capability-design`.
|
|
39
|
-
2. Design one complete capability patch for the feature.
|
|
40
|
-
3. Preview the patch.
|
|
41
|
-
4. Apply the same operations.
|
|
42
|
-
5. Sync AI artifacts.
|
|
43
|
-
6. Read `.howone/ai/manifest.json`.
|
|
44
|
-
7. Submit external workflow create/update with `external-ai-capability`.
|
|
45
|
-
8. Preserve returned `requestIds` for the status/background-task layer.
|
|
46
|
-
9. Generate or update `src/lib/sdk.ts` from the manifest.
|
|
47
|
-
10. Implement UI calls through `howone.ai.*`.
|
|
48
|
-
11. If generated output must be stored, design database entities after the AI output schema exists.
|
|
49
|
-
|
|
50
|
-
Do not preview a patch and then submit different operations. Do not restate schemas in
|
|
51
|
-
`external-ai-capability`; it loads the manifest.
|
|
52
|
-
|
|
53
|
-
## Unsupported AI Requests
|
|
54
|
-
|
|
55
|
-
If the user explicitly asks for AI capability and the feature cannot be implemented with the
|
|
56
|
-
available workflow service capabilities, stop the AI implementation task and report the gap.
|
|
57
|
-
|
|
58
|
-
Do not:
|
|
59
|
-
|
|
60
|
-
- fake the AI feature with static mock data.
|
|
61
|
-
- implement a frontend-only placeholder that pretends AI works.
|
|
62
|
-
- design a workflow that assumes unavailable tools, providers, APIs, or private datasets.
|
|
63
|
-
- silently drop the AI part and continue building a non-AI version.
|
|
64
|
-
- move forbidden work such as database CRUD, auth, upload handling, payments, or app state into the
|
|
65
|
-
workflow.
|
|
66
|
-
|
|
67
|
-
The correct response is to state which requested AI behavior is unsupported, which capability is
|
|
68
|
-
missing or which rule blocks it, and what narrower supported alternative could be built.
|
|
69
|
-
|
|
70
|
-
Only continue implementation when the remaining feature still satisfies the user's intent after
|
|
71
|
-
the unsupported AI capability is removed or narrowed.
|
|
72
|
-
|
|
73
|
-
## Create vs Update
|
|
74
|
-
|
|
75
|
-
Use `external-ai-capability` with `mode: "create"` when a capability has no external implementation.
|
|
76
|
-
The tool reads `workflowId`, `inputSchema`, `outputSchema`, and `outputEntityName` from the synced
|
|
77
|
-
manifest.
|
|
78
|
-
|
|
79
|
-
Use `mode: "update"` when the external workflow implementation needs to change. Updates require:
|
|
80
|
-
|
|
81
|
-
- `capabilityName`
|
|
82
|
-
- `workflowConfigID`
|
|
83
|
-
- `updatePrompt`
|
|
84
|
-
|
|
85
|
-
`workflowConfigID` must come from a confirmed workflow status result, specifically
|
|
86
|
-
`payload.workflow_details.new_workflow_config_id`. Do not invent it, omit it, or substitute
|
|
87
|
-
`workflowId`.
|
|
88
|
-
|
|
89
|
-
If the input/output schema changes, update the capability contract first, sync the manifest, then
|
|
90
|
-
submit the external workflow update.
|
|
91
|
-
|
|
92
|
-
## Persistence Boundary
|
|
93
|
-
|
|
94
|
-
AI workflows produce outputs. They do not persist app state.
|
|
95
|
-
|
|
96
|
-
Workflow can do:
|
|
97
|
-
|
|
98
|
-
- text generation, summarization, translation, classification, extraction
|
|
99
|
-
- image, video, and audio generation/editing/analysis
|
|
100
|
-
- web research and page crawling
|
|
101
|
-
- financial or academic retrieval
|
|
102
|
-
- file generation or file reading through URLs
|
|
103
|
-
|
|
104
|
-
Workflow must not do:
|
|
105
|
-
|
|
106
|
-
- database create/read/update/delete
|
|
107
|
-
- authentication or session handling
|
|
108
|
-
- upload handling
|
|
109
|
-
- payment processing
|
|
110
|
-
- app state management
|
|
111
|
-
- owner assignment or record permissions
|
|
112
|
-
|
|
113
|
-
If the app saves AI output, derive entity fields from `outputSchema` and add only necessary request
|
|
114
|
-
metadata such as prompt, selected options, status, timestamps, or references.
|
|
115
|
-
|
|
116
|
-
## Workflow Count
|
|
117
|
-
|
|
118
|
-
Default rule: one user-facing AI feature maps to one capability and one workflow.
|
|
119
|
-
|
|
120
|
-
Examples:
|
|
121
|
-
|
|
122
|
-
- Story + illustration generator: one workflow that returns story text and generated image URLs.
|
|
123
|
-
- Image edit feature: one workflow that accepts source image URL and edit instruction, returns
|
|
124
|
-
edited image URL.
|
|
125
|
-
- News briefing: one workflow that searches and summarizes, returns briefing and source links.
|
|
126
|
-
|
|
127
|
-
RAG is the exception:
|
|
128
|
-
|
|
129
|
-
- indexing workflow: ingests documents and creates retrieval/indexing artifacts.
|
|
130
|
-
- query workflow: answers questions from the indexed knowledge base.
|
|
131
|
-
|
|
132
|
-
Do not split normal sequential AI steps into multiple capabilities unless they are independently
|
|
133
|
-
triggered product features.
|
|
134
|
-
|
|
135
|
-
## Capability Naming
|
|
136
|
-
|
|
137
|
-
Use stable JavaScript-safe identifiers for capability names, for example `generateIllustration` or
|
|
138
|
-
`summarizeDocument`. Do not use display labels as identifiers.
|
|
139
|
-
|
|
140
|
-
Avoid names that collide with action methods: `run`, `stream`, `events`.
|
|
141
|
-
|
|
142
|
-
Use `outputEntityName` only when the generated output has a natural persisted record shape.
|
|
@@ -1,169 +0,0 @@
|
|
|
1
|
-
# Workflow Contract Rules
|
|
2
|
-
|
|
3
|
-
Use this reference when designing `inputSchema`, `outputSchema`, and capability descriptions for
|
|
4
|
-
HowOne AI workflows.
|
|
5
|
-
|
|
6
|
-
## Loose JSON Schema
|
|
7
|
-
|
|
8
|
-
Workflow schemas should be loose enough for an AI workflow engine.
|
|
9
|
-
|
|
10
|
-
Rules:
|
|
11
|
-
|
|
12
|
-
- Require only essential inputs.
|
|
13
|
-
- Mark non-essential options optional.
|
|
14
|
-
- Prefer `string` with a clear description over restrictive enums when user values are open-ended.
|
|
15
|
-
- Avoid `minLength`, `maxLength`, `pattern`, and narrow validation unless there is a concrete
|
|
16
|
-
technical reason.
|
|
17
|
-
- Avoid nested objects when a simple described string is enough.
|
|
18
|
-
- Avoid exposing implementation knobs the user does not need.
|
|
19
|
-
|
|
20
|
-
Good:
|
|
21
|
-
|
|
22
|
-
```json
|
|
23
|
-
{
|
|
24
|
-
"tone": {
|
|
25
|
-
"type": "string",
|
|
26
|
-
"description": "Desired writing tone, e.g. formal, casual, humorous, empathetic, professional."
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
Use enums only for truly closed domains.
|
|
32
|
-
|
|
33
|
-
## URLs, Not Raw Files
|
|
34
|
-
|
|
35
|
-
All file exchange uses URLs.
|
|
36
|
-
|
|
37
|
-
Rules:
|
|
38
|
-
|
|
39
|
-
- File inputs are strings with `format: "uri"`, such as `document_url`, `source_image_url`, or
|
|
40
|
-
`audio_file_url`.
|
|
41
|
-
- File outputs are strings with `format: "uri"`, such as `generated_image_url`, `edited_video_url`,
|
|
42
|
-
or `result_pdf_url`.
|
|
43
|
-
- Never use base64, raw bytes, inline file content, or `contentEncoding`.
|
|
44
|
-
- The app uploads user files first, then passes the URL to the workflow.
|
|
45
|
-
|
|
46
|
-
## Output Minimalism
|
|
47
|
-
|
|
48
|
-
The workflow will try to fill every output field. Only ask for fields the product needs.
|
|
49
|
-
|
|
50
|
-
Usually forbidden unless explicitly requested:
|
|
51
|
-
|
|
52
|
-
- timestamps and processing time
|
|
53
|
-
- file size, MIME type, encoding, dimensions, frame rate, bitrate
|
|
54
|
-
- confidence scores and bounding boxes
|
|
55
|
-
- model, provider, version, cost, or internal execution metadata
|
|
56
|
-
- style/tone metadata when the user only asked for an asset
|
|
57
|
-
|
|
58
|
-
Examples:
|
|
59
|
-
|
|
60
|
-
| User Need | Good Output | Avoid |
|
|
61
|
-
|---|---|---|
|
|
62
|
-
| Summarize a document | `summary` | `summary`, `word_count`, `reading_time` |
|
|
63
|
-
| Generate an image | `generated_image_url` | `generated_image_url`, `model_used`, `color_palette` |
|
|
64
|
-
| OCR an image | `extracted_text` | `extracted_text`, `confidence_score`, `bounding_boxes` |
|
|
65
|
-
|
|
66
|
-
## No Input/Output Name Overlap
|
|
67
|
-
|
|
68
|
-
Input and output property names must not overlap. The workflow engine uses a shared parameter
|
|
69
|
-
namespace.
|
|
70
|
-
|
|
71
|
-
Bad:
|
|
72
|
-
|
|
73
|
-
```json
|
|
74
|
-
{
|
|
75
|
-
"inputSchema": { "properties": { "text": { "type": "string" } } },
|
|
76
|
-
"outputSchema": { "properties": { "text": { "type": "string" } } }
|
|
77
|
-
}
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
Good:
|
|
81
|
-
|
|
82
|
-
```json
|
|
83
|
-
{
|
|
84
|
-
"inputSchema": { "properties": { "source_text": { "type": "string" } } },
|
|
85
|
-
"outputSchema": { "properties": { "translated_text": { "type": "string" } } }
|
|
86
|
-
}
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
Common pairs:
|
|
90
|
-
|
|
91
|
-
- `source_text` -> `translated_text`
|
|
92
|
-
- `source_content` -> `summary`
|
|
93
|
-
- `source_image_url` -> `edited_image_url`
|
|
94
|
-
- `description_prompt` -> `generated_image_url`
|
|
95
|
-
|
|
96
|
-
## Output Language
|
|
97
|
-
|
|
98
|
-
Every text output field description must specify the language rule.
|
|
99
|
-
|
|
100
|
-
Use one of these patterns:
|
|
101
|
-
|
|
102
|
-
- fixed: "Summary in English."
|
|
103
|
-
- input-driven: "Translated text in the target language specified by `target_language`."
|
|
104
|
-
- source-driven: "Summary in the same language as the source document."
|
|
105
|
-
- mixed: "Extracted text, which may contain mixed Chinese and English content."
|
|
106
|
-
|
|
107
|
-
Do not write vague descriptions like "The translated text."
|
|
108
|
-
|
|
109
|
-
## Description Says What, Not How
|
|
110
|
-
|
|
111
|
-
`capability.description` describes the user outcome.
|
|
112
|
-
|
|
113
|
-
Do:
|
|
114
|
-
|
|
115
|
-
```json
|
|
116
|
-
{
|
|
117
|
-
"description": "Searches the web for the latest news on a topic and produces a structured briefing with source links."
|
|
118
|
-
}
|
|
119
|
-
```
|
|
120
|
-
|
|
121
|
-
Do not:
|
|
122
|
-
|
|
123
|
-
```json
|
|
124
|
-
{
|
|
125
|
-
"description": "First calls search_web, then summarizes each article with an LLM, then saves JSON."
|
|
126
|
-
}
|
|
127
|
-
```
|
|
128
|
-
|
|
129
|
-
Never mention internal tool names, step sequences, providers, or model names in the capability
|
|
130
|
-
description.
|
|
131
|
-
|
|
132
|
-
## Available Capability Families
|
|
133
|
-
|
|
134
|
-
Design only around capabilities available to the workflow service:
|
|
135
|
-
|
|
136
|
-
- web search and page crawling
|
|
137
|
-
- image generation, editing, analysis, OCR
|
|
138
|
-
- video generation, editing, concatenation, frame extraction
|
|
139
|
-
- audio generation, recognition, merging
|
|
140
|
-
- financial price history retrieval
|
|
141
|
-
- academic paper search and bibliography
|
|
142
|
-
- file storage/read for JSON, YAML, CSV, PDF, Markdown, TXT
|
|
143
|
-
- email sending
|
|
144
|
-
- RSS feed fetching
|
|
145
|
-
- Airbnb search
|
|
146
|
-
- Hacker News search
|
|
147
|
-
|
|
148
|
-
If the requested product needs unavailable functionality, redesign the feature around available
|
|
149
|
-
capabilities or exclude it explicitly.
|
|
150
|
-
|
|
151
|
-
When the user explicitly requires the unavailable AI behavior, terminate the AI task instead of
|
|
152
|
-
building a fake or silently degraded implementation. Report the unsupported requirement and the
|
|
153
|
-
closest supported alternative.
|
|
154
|
-
|
|
155
|
-
## External Data Assumptions
|
|
156
|
-
|
|
157
|
-
Do not assume the user can provide external datasets unless they explicitly say so.
|
|
158
|
-
|
|
159
|
-
For "stock analysis", use a workflow input such as `trading_symbol` and let the workflow retrieve
|
|
160
|
-
history. Do not require `stock_data_csv_url` unless the user says they have a CSV.
|
|
161
|
-
|
|
162
|
-
For "latest news", use web search inside the workflow. Do not ask the user to provide article URLs
|
|
163
|
-
unless that is the product requirement.
|
|
164
|
-
|
|
165
|
-
## MVP Limit
|
|
166
|
-
|
|
167
|
-
Keep AI app generation to at most five core features. Exclude feedback forms, onboarding tutorials,
|
|
168
|
-
notification settings, export, sharing, personalization, and preferences unless the user explicitly
|
|
169
|
-
requests them.
|