@owlmeans/viable-common 0.0.33 → 0.0.35

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.
Files changed (97) hide show
  1. package/README.md +3 -3
  2. package/agent-meta/manifest.json +2 -2
  3. package/agent-meta/skills/agent-presentation/SKILL.md +1 -1
  4. package/agent-meta/skills/viable-common/SKILL.md +93 -62
  5. package/build/ba/index.d.ts +0 -1
  6. package/build/ba/index.d.ts.map +1 -1
  7. package/build/ba/index.js +0 -1
  8. package/build/ba/index.js.map +1 -1
  9. package/build/blueprint/consts.d.ts +38 -0
  10. package/build/blueprint/consts.d.ts.map +1 -1
  11. package/build/blueprint/consts.js +40 -0
  12. package/build/blueprint/consts.js.map +1 -1
  13. package/build/blueprint/index.d.ts +0 -3
  14. package/build/blueprint/index.d.ts.map +1 -1
  15. package/build/blueprint/index.js +0 -3
  16. package/build/blueprint/index.js.map +1 -1
  17. package/build/blueprint/types.d.ts +5 -5
  18. package/build/intent/consts.d.ts +57 -0
  19. package/build/intent/consts.d.ts.map +1 -0
  20. package/build/intent/consts.js +58 -0
  21. package/build/intent/consts.js.map +1 -0
  22. package/build/intent/entrypoints.d.ts +27 -0
  23. package/build/intent/entrypoints.d.ts.map +1 -0
  24. package/build/intent/entrypoints.js +28 -0
  25. package/build/intent/entrypoints.js.map +1 -0
  26. package/build/intent/errors.d.ts +20 -0
  27. package/build/intent/errors.d.ts.map +1 -0
  28. package/build/intent/errors.js +28 -0
  29. package/build/intent/errors.js.map +1 -0
  30. package/build/intent/flow.d.ts +17 -0
  31. package/build/intent/flow.d.ts.map +1 -0
  32. package/build/intent/flow.js +65 -0
  33. package/build/intent/flow.js.map +1 -0
  34. package/build/intent/index.d.ts +7 -0
  35. package/build/intent/index.d.ts.map +1 -0
  36. package/build/intent/index.js +6 -0
  37. package/build/intent/index.js.map +1 -0
  38. package/build/intent/schemas.d.ts +10 -0
  39. package/build/intent/schemas.d.ts.map +1 -0
  40. package/build/intent/schemas.js +24 -0
  41. package/build/intent/schemas.js.map +1 -0
  42. package/build/intent/types.d.ts +40 -0
  43. package/build/intent/types.d.ts.map +1 -0
  44. package/build/intent/types.js +2 -0
  45. package/build/intent/types.js.map +1 -0
  46. package/build/scaffold/consts.d.ts +14 -1
  47. package/build/scaffold/consts.d.ts.map +1 -1
  48. package/build/scaffold/consts.js +10 -0
  49. package/build/scaffold/consts.js.map +1 -1
  50. package/build/scaffold/schemas.d.ts +7 -0
  51. package/build/scaffold/schemas.d.ts.map +1 -1
  52. package/build/scaffold/schemas.js +119 -68
  53. package/build/scaffold/schemas.js.map +1 -1
  54. package/build/scaffold/types.d.ts +62 -24
  55. package/build/scaffold/types.d.ts.map +1 -1
  56. package/build/skills/consts.d.ts +0 -34
  57. package/build/skills/consts.d.ts.map +1 -1
  58. package/build/skills/consts.js +0 -106
  59. package/build/skills/consts.js.map +1 -1
  60. package/build/skills/index.d.ts +0 -1
  61. package/build/skills/index.d.ts.map +1 -1
  62. package/build/skills/index.js +0 -1
  63. package/build/skills/index.js.map +1 -1
  64. package/build/skills/roles.d.ts +0 -9
  65. package/build/skills/roles.d.ts.map +1 -1
  66. package/build/skills/roles.js +0 -226
  67. package/build/skills/roles.js.map +1 -1
  68. package/build/ux/schemas.js +2 -2
  69. package/build/ux/schemas.js.map +1 -1
  70. package/package.json +17 -7
  71. package/src/ba/index.ts +0 -1
  72. package/src/blueprint/consts.ts +40 -0
  73. package/src/blueprint/index.ts +0 -3
  74. package/src/blueprint/types.ts +5 -5
  75. package/src/intent/consts.ts +66 -0
  76. package/src/intent/entrypoints.ts +43 -0
  77. package/src/intent/errors.ts +33 -0
  78. package/src/intent/flow.ts +67 -0
  79. package/src/intent/index.ts +6 -0
  80. package/src/intent/schemas.ts +27 -0
  81. package/src/intent/types.ts +45 -0
  82. package/src/scaffold/consts.ts +14 -1
  83. package/src/scaffold/schemas.ts +125 -69
  84. package/src/scaffold/types.ts +64 -23
  85. package/src/skills/consts.ts +0 -112
  86. package/src/skills/index.ts +0 -1
  87. package/src/skills/roles.ts +0 -267
  88. package/src/ux/schemas.ts +2 -2
  89. package/tests/blueprint.spec.ts +2 -66
  90. package/tests/intent.spec.ts +121 -0
  91. package/tests/scaffold.spec.ts +73 -13
  92. package/src/ba/schemas.ts +0 -196
  93. package/src/blueprint/case.ts +0 -320
  94. package/src/blueprint/resolve.ts +0 -136
  95. package/src/blueprint/schemas.ts +0 -44
  96. package/src/skills/catalogue.ts +0 -1864
  97. package/tests/skills.spec.ts +0 -125
package/src/ba/schemas.ts DELETED
@@ -1,196 +0,0 @@
1
-
2
- import type { JSONSchemaType } from "ajv"
3
- import type { EntityList, StoryList, Entity, Attribute, MainStoryProbability, MainStoryCandidates, ConnectingStoryDraft, ConnectingStoryList } from "./types.js"
4
- import type { StoryDraft } from "../areas/types.js"
5
- import { ProjectArea } from "../areas/consts.js"
6
- import { ConnectingStoryKind } from "./consts.js"
7
-
8
- /**
9
- * The one rendering of the area field's description.
10
- *
11
- * Every schema that carries an area points at this. Two copies drift, and the drift is invisible:
12
- * both schemas keep validating, and the two prompts that read them start classifying differently.
13
- * It is the same reason `AREA_CLASSIFICATION` is spelled once in the BA helper.
14
- */
15
- const AREA_FIELD_DESCRIPTION = `
16
- Which area of the application the acting user works in:
17
- - guest - the actor is NOT signed in (public visitors, prospects, anonymous readers)
18
- - user - the actor is a signed-in end user consuming the product's value (the front office)
19
- - operator - the actor is staff running the product's business process from the inside (the back office)
20
- - admin - the actor is the owner configuring or managing the application itself
21
- `
22
-
23
- const STORY_BODY_DESCRIPTION =
24
- "User story body in format: As a [role], I want to [make something] so that [I get the following results]"
25
-
26
- export const StoryDraftSchema: JSONSchemaType<StoryDraft> = {
27
- type: "object",
28
- title: "StoryDraft",
29
- description: "One user story and the area of the application its actor works in",
30
- properties: {
31
- story: {
32
- type: "string",
33
- description: STORY_BODY_DESCRIPTION
34
- },
35
- area: {
36
- type: "string",
37
- enum: Object.values(ProjectArea),
38
- description: AREA_FIELD_DESCRIPTION
39
- },
40
- },
41
- required: ["story", "area"],
42
- additionalProperties: false
43
- }
44
-
45
- export const ConnectingStoryDraftSchema: JSONSchemaType<ConnectingStoryDraft> = {
46
- type: "object",
47
- title: "ConnectingStoryDraft",
48
- description: "One user story that connects the numbered steps of the main flow to each other",
49
- properties: {
50
- story: {
51
- type: "string",
52
- description: STORY_BODY_DESCRIPTION
53
- },
54
- area: {
55
- type: "string",
56
- enum: Object.values(ProjectArea),
57
- description: AREA_FIELD_DESCRIPTION
58
- },
59
- kind: {
60
- type: "string",
61
- enum: Object.values(ConnectingStoryKind),
62
- description: `
63
- Which connecting screen this story is:
64
- - queue - the list of records waiting for THIS actor to perform their step on them
65
- - index - the directory of a kind of record, where the actor finds and opens the one they mean
66
- - record - the page of ONE record: everything that has happened to it, and the actions that start from it
67
- - tracker - the actor's own list of what they submitted, and what state each one has reached
68
- `
69
- },
70
- after: {
71
- type: "integer",
72
- minimum: 1,
73
- description: "The NUMBER of the flow user story whose result this story displays - this story belongs immediately after that one. Never 0, and never greater than the number of flow stories given"
74
- },
75
- },
76
- required: ["story", "area", "kind", "after"],
77
- additionalProperties: false
78
- }
79
-
80
- export const ConnectingStoryListSchema: JSONSchemaType<ConnectingStoryList> = {
81
- type: "object",
82
- title: "ConnectingStoryList",
83
- description: "User stories that connect the numbered steps of the main flow to each other",
84
- properties: {
85
- stories: {
86
- type: "array",
87
- items: ConnectingStoryDraftSchema,
88
- description: "The connecting stories, each anchored to the flow story it comes after"
89
- },
90
- },
91
- required: ["stories"]
92
- }
93
-
94
- export const EntityListSchema: JSONSchemaType<EntityList> = {
95
- type: "object",
96
- title: "EntityList",
97
- description: "Domain entities",
98
- properties: {
99
- entities: {
100
- type: "array",
101
- items: {
102
- type: "string",
103
- description: "Domain entity name, only alphanumeric characters are allowed no more than 2 words"
104
- },
105
- description: "List of domain entities"
106
- },
107
- },
108
- required: ["entities"]
109
- }
110
-
111
- export const StoryListSchema: JSONSchemaType<StoryList> = {
112
- type: "object",
113
- title: "StoryList",
114
- description: "User stories",
115
- properties: {
116
- stories: {
117
- type: "array",
118
- items: StoryDraftSchema,
119
- description: "List of user stories IN FLOW ORDER - the order of the numbered steps they were taken from"
120
- },
121
- },
122
- required: ["stories"]
123
- }
124
-
125
- export const AttributeSchema: JSONSchemaType<Attribute> = {
126
- type: "object",
127
- title: "Attribute",
128
- description: "Entity attribute",
129
- properties: {
130
- name: {
131
- type: "string",
132
- description: "Attribute name, should be descriptive and use camelCase"
133
- },
134
- description: {
135
- type: "string",
136
- description: "Detailed description of the attribute in the domain entity"
137
- }
138
- },
139
- required: ["name", "description"],
140
- additionalProperties: false,
141
- }
142
-
143
- export const EntitySchema: JSONSchemaType<Entity> = {
144
- type: "object",
145
- title: "Entity",
146
- description: "Domain entity with attributes",
147
- properties: {
148
- name: {
149
- type: "string",
150
- description: "Entity name, should be descriptive and use PascalCase"
151
- },
152
- description: {
153
- type: "string",
154
- description: "Detailed description of the entity and its role in the domain"
155
- },
156
- attributes: {
157
- type: "array",
158
- items: AttributeSchema,
159
- description: "List of entity attributes"
160
- }
161
- },
162
- required: ["name", "description", "attributes"],
163
- additionalProperties: false,
164
- }
165
-
166
- export const MainStoryProbabilitySchema: JSONSchemaType<MainStoryProbability> = {
167
- type: "object",
168
- title: "MainStoryProbability",
169
- description: "Probability that the user story describes main action on the home screen",
170
- properties: {
171
- story: {
172
- type: "string",
173
- description: "Exact user story text copied from input"
174
- },
175
- probability: {
176
- type: "number",
177
- description: "Probability scroe — from 0 to 10 — that the user story describes main action on the home screen if we compare with other stories"
178
- }
179
- },
180
- required: ["story", "probability"],
181
- additionalProperties: false,
182
- }
183
-
184
- export const MainStoryCandidatesSchema: JSONSchemaType<MainStoryCandidates> = {
185
- type: "object",
186
- title: "MainStoryCandidates",
187
- description: "List of main story candidates",
188
- properties: {
189
- candidates: {
190
- type: "array",
191
- items: MainStoryProbabilitySchema,
192
- }
193
- },
194
- required: ["candidates"],
195
- additionalProperties: false,
196
- }
@@ -1,320 +0,0 @@
1
- import { SubProject } from '../slot/consts.js'
2
- import { ViableSkill } from '../skills/consts.js'
3
- import { ViablePersona } from '../skills/roles.js'
4
- import { aiLibraryList, gameLibraryList } from '../skills/catalogue.js'
5
- import { LandingGatePreference } from './types.js'
6
- import type { BlueprintPatch } from './types.js'
7
-
8
- /**
9
- * WHICH KIND of product is being built on a blueprint.
10
- *
11
- * A blueprint says what the STACK is — the language, the framework, the topology, the seeds. A
12
- * case says what is being built ON it, and that is a different question with different consumers:
13
- * the analyst framing the brief, the architect deciding whether a story needs a queue, the coder
14
- * choosing which packages it may import.
15
- *
16
- * Deliberately NOT a second blueprint. Everything a case has to say is already a blueprint field,
17
- * so a case is a PATCH and the resolution order stays base → case → per-run overrides. That is
18
- * also why adding one is a data change: nothing reads `BlueprintCase` except the table below and
19
- * the prompt that classifies a project into it.
20
- */
21
- export enum BlueprintCase {
22
- /** A web application whose every feature fits inside a request. No queue, no worker, no agents. */
23
- Web = 'web',
24
- /** A web application with work that genuinely belongs off the request path. */
25
- Scalable = 'scalable',
26
- /** The product's value comes from ordered, deterministic model-driven steps. */
27
- AiPipeline = 'ai-pipeline',
28
- /** The product's value comes from an agent that decides its own path through tools. */
29
- AiAgent = 'ai-agent',
30
- /** A browser game: a three.js scene with an ordinary React interface over it. */
31
- Game = 'game',
32
- }
33
-
34
- /**
35
- * How a game is played, which decides who owns its state.
36
- *
37
- * Recorded beside the case rather than folded into it, because it changes nothing about the
38
- * dependencies and everything about where the logic goes. `OnlineTurn` is the default for
39
- * anything multiplayer unless the brief plainly asks for live play — it is the shape that
40
- * actually builds, and it needs no socket, no tick loop and no reconciliation.
41
- */
42
- export enum GameKind {
43
- Casual = 'casual',
44
- OnlineTurn = 'online-turn',
45
- OnlineLive = 'online-live',
46
- }
47
-
48
- export interface BlueprintCaseDefinition {
49
- id: BlueprintCase
50
- title: string
51
- /**
52
- * What this case is FOR, as the classifier prompt reads it.
53
- *
54
- * A pure constant, like every skill body: it lands in a cached prompt region, and one
55
- * interpolated byte invalidates the prefix for every project classified after it.
56
- */
57
- when: string
58
- /** Applied between the base blueprint and the run's own overrides. A case is nothing else. */
59
- patch: BlueprintPatch
60
- }
61
-
62
- /** What every AI case installs. The langchain peers are declared here, at ONE range. */
63
- const AI_DEPS = {
64
- [SubProject.Backend]: {
65
- '@owlmeans/llm': '^0.1.18-rc.37',
66
- '@owlmeans/agent': '^0.1.18-rc.38',
67
- '@langchain/core': '^1.2.9',
68
- '@langchain/anthropic': '^1.5.8',
69
- '@langchain/openai': '^1.5.10',
70
- '@langchain/langgraph': '^1.4.13',
71
- '@langchain/langgraph-checkpoint': '^1.1.5',
72
- },
73
- }
74
-
75
- /** The library allow-list a backend persona gets once the AI packages are installed. */
76
- const AI_LIBRARIES = {
77
- [SubProject.Backend]: aiLibraryList,
78
- [SubProject.Api]: aiLibraryList,
79
- [SubProject.Worker]: aiLibraryList,
80
- }
81
-
82
- /** Skills every AI case adds on top of the blueprint's own. */
83
- const AI_SKILLS = [
84
- ViableSkill.AgenticChoice,
85
- ViableSkill.TargetLlm,
86
- ViableSkill.LibrariesAi,
87
- ]
88
-
89
- /**
90
- * What the API side is told once a project may have a queue.
91
- *
92
- * The endpoint that starts slow work is the PRODUCER, and it is generated by a persona that
93
- * otherwise has no reason to know a broker exists. Its job alias is declared before it runs
94
- * (`runtime-declare`), so by the time this coder writes the handler there is something to enqueue
95
- * onto — and this is what tells it to, instead of doing the work inline behind a request the user
96
- * is waiting on.
97
- */
98
- const PRODUCER_PERSONA_SKILLS = {
99
- [ViablePersona.ApiArchitect]: [ViableSkill.QueueDiscipline, ViableSkill.WorkerJobs],
100
- [ViablePersona.ApiCoder]: [ViableSkill.WorkerJobs],
101
- }
102
-
103
- /**
104
- * What the analysis stage is told about a product whose value is a model.
105
- *
106
- * The pairing rule is the load-bearing one: an agent holds no permissions and owns no screen, so
107
- * a story that is the AGENT's work is invisible unless a human story starts it and watches it.
108
- */
109
- const AI_PERSONA_SKILLS = {
110
- ...PRODUCER_PERSONA_SKILLS,
111
- [ViablePersona.BusinessAnalyst]: [ViableSkill.AgenticChoice],
112
- [ViablePersona.UxDesigner]: [ViableSkill.AgenticChoice],
113
- [ViablePersona.RuntimeArchitect]: [ViableSkill.AgenticChoice, ViableSkill.TargetLlm],
114
- [ViablePersona.WorkerCoder]: [
115
- ViableSkill.AgenticChoice, ViableSkill.TargetLlm, ViableSkill.LibrariesAi,
116
- ],
117
- }
118
-
119
- type PersonaSkills = Partial<Record<ViablePersona, ViableSkill[]>>
120
-
121
- /**
122
- * Join persona-skill maps, UNIONING a persona two maps both name.
123
- *
124
- * An object spread would keep only the last map's list for that persona — `BusinessAnalyst`
125
- * carries `AgenticChoice` in an AI case and `LandingGate` from the gate map, and a spread would
126
- * silently drop whichever came first.
127
- */
128
- const joinPersonaSkills = (...maps: PersonaSkills[]): PersonaSkills => {
129
- const out: PersonaSkills = {}
130
- for (const map of maps) {
131
- for (const [persona, skills] of Object.entries(map)) {
132
- const key = persona as ViablePersona
133
- out[key] = [...new Set([...(out[key] ?? []), ...(skills ?? [])])]
134
- }
135
- }
136
-
137
- return out
138
- }
139
-
140
- /**
141
- * Who has to know the landing-gate pattern in a case that encourages one.
142
- *
143
- * The analyst decides whether a story is gate-worthy and plans the gate's copy; the UX designer
144
- * shapes the full-scale screen that continues what the guest started; the frontend personas draw
145
- * the gate, write the handoff and read it back. Nobody else touches it.
146
- */
147
- const LANDING_GATE_PERSONA_SKILLS: PersonaSkills = {
148
- [ViablePersona.BusinessAnalyst]: [ViableSkill.LandingGate],
149
- [ViablePersona.UxDesigner]: [ViableSkill.LandingGate],
150
- [ViablePersona.LayoutArchitect]: [ViableSkill.LandingGate],
151
- [ViablePersona.ComponentArchitect]: [ViableSkill.LandingGate],
152
- [ViablePersona.UiViewCoder]: [ViableSkill.LandingGate],
153
- [ViablePersona.UiNavCoder]: [ViableSkill.LandingGate],
154
- }
155
-
156
- /** The experience layer of a case whose product usually has a step a guest can begin. */
157
- const ENCOURAGE_GATE = { landingGate: LandingGatePreference.Encourage }
158
-
159
- const GAME_PERSONA_SKILLS = {
160
- [ViablePersona.BusinessAnalyst]: [ViableSkill.GameDesign],
161
- [ViablePersona.UxDesigner]: [ViableSkill.GameDesign, ViableSkill.GameUi],
162
- [ViablePersona.VisualDesigner]: [ViableSkill.GameUi],
163
- [ViablePersona.ComponentArchitect]: [ViableSkill.GameScene, ViableSkill.GameUi],
164
- [ViablePersona.LayoutArchitect]: [ViableSkill.GameScene, ViableSkill.GameUi],
165
- [ViablePersona.UiViewCoder]: [
166
- ViableSkill.GameScene, ViableSkill.GameUi, ViableSkill.LibrariesGame,
167
- ],
168
- [ViablePersona.RuntimeArchitect]: [ViableSkill.GameNetworking],
169
- [ViablePersona.WorkerCoder]: [ViableSkill.GameNetworking],
170
- [ViablePersona.ApiArchitect]: [ViableSkill.GameNetworking],
171
- [ViablePersona.ApiCoder]: [ViableSkill.GameNetworking],
172
- }
173
-
174
- /**
175
- * The cases the platform ships.
176
- *
177
- * Read as a table: the left column is what a prompt has to look like, the right column is what
178
- * that costs the generated application. Nothing here turns a capability on without also
179
- * installing what makes it real — a blueprint that promises `agents` over a manifest with no
180
- * `@owlmeans/agent` in it produces a story that reports success and a library that will not
181
- * compile.
182
- */
183
- export const BLUEPRINT_CASES: Record<BlueprintCase, BlueprintCaseDefinition> = {
184
- [BlueprintCase.Web]: {
185
- id: BlueprintCase.Web,
186
- title: 'Web application',
187
- when: `
188
- Everything the product does fits inside a request: forms, lists, records, search, reports a
189
- person waits a second for. Nothing has to survive the tab closing, nothing calls a slow third
190
- party, nothing walks an unbounded set, nothing is scheduled. Most products are this.
191
- `.trim(),
192
- patch: {
193
- packages: {
194
- capabilities: { postgres: true, kv: false, queue: false, worker: false, agents: false, validation: true, marketingConsent: true },
195
- personaSkills: LANDING_GATE_PERSONA_SKILLS,
196
- },
197
- experience: ENCOURAGE_GATE,
198
- },
199
- },
200
-
201
- [BlueprintCase.Scalable]: {
202
- id: BlueprintCase.Scalable,
203
- title: 'Web application with background work',
204
- when: `
205
- Like a web application, except that some named part of it genuinely cannot run inside a request:
206
- it walks every row a user owns, talks to a slow or rate-limited third party, is scheduled or
207
- repeated, must survive the tab closing, or must be retried without the user doing anything.
208
- Choose this only when you can NAME that part; "it might get slow" is not it.
209
-
210
- NOT this when the slow part is a MODEL. Background work whose content comes out of a language
211
- model is one of the two AI kinds below, and choosing this one for it leaves the application unable
212
- to call a model at all.
213
- `.trim(),
214
- patch: {
215
- packages: {
216
- capabilities: { postgres: true, kv: true, queue: true, worker: true, agents: false, validation: true, marketingConsent: true },
217
- personaSkills: PRODUCER_PERSONA_SKILLS,
218
- },
219
- // Neutral: the named off-request part is usually operator-side work, and whether a guest
220
- // can begin anything is the specification's to say.
221
- experience: { landingGate: LandingGatePreference.Allow },
222
- },
223
- },
224
-
225
- [BlueprintCase.AiPipeline]: {
226
- id: BlueprintCase.AiPipeline,
227
- title: 'AI pipeline',
228
- when: `
229
- The product's value comes from a language model, and the work it does is a KNOWN sequence:
230
- transcribe then summarise then file; extract then validate then store; draft then check then send.
231
- You can name the steps before it runs. The application decides the order; the model performs each
232
- step.
233
-
234
- Any product whose OUTPUT is written by a model is this kind or the one below — never a plain web
235
- application, and never merely a scalable one.
236
- `.trim(),
237
- patch: {
238
- packages: {
239
- capabilities: { postgres: true, kv: true, queue: true, worker: true, agents: true, validation: true, marketingConsent: true },
240
- deps: AI_DEPS,
241
- libraries: AI_LIBRARIES,
242
- skills: AI_SKILLS,
243
- personaSkills: joinPersonaSkills(AI_PERSONA_SKILLS, LANDING_GATE_PERSONA_SKILLS),
244
- },
245
- experience: ENCOURAGE_GATE,
246
- },
247
- },
248
-
249
- [BlueprintCase.AiAgent]: {
250
- id: BlueprintCase.AiAgent,
251
- title: 'AI agent',
252
- when: `
253
- The product's value comes from a model that decides its own path: how many steps the work takes
254
- cannot be predicted, and which tool to reach for next is a judgement rather than a rule anyone
255
- could write down. Choose this over an AI pipeline only when the order genuinely cannot be fixed
256
- in advance.
257
- `.trim(),
258
- patch: {
259
- packages: {
260
- capabilities: { postgres: true, kv: true, queue: true, worker: true, agents: true, validation: true, marketingConsent: true },
261
- deps: {
262
- [SubProject.Backend]: {
263
- ...AI_DEPS[SubProject.Backend],
264
- '@owlmeans/agent-skills': '^0.1.18-rc.38',
265
- },
266
- },
267
- libraries: AI_LIBRARIES,
268
- skills: [...AI_SKILLS, ViableSkill.TargetAgentTools],
269
- personaSkills: joinPersonaSkills(AI_PERSONA_SKILLS, LANDING_GATE_PERSONA_SKILLS, {
270
- [ViablePersona.WorkerCoder]: [ViableSkill.TargetAgentTools],
271
- }),
272
- },
273
- experience: ENCOURAGE_GATE,
274
- },
275
- },
276
-
277
- [BlueprintCase.Game]: {
278
- id: BlueprintCase.Game,
279
- title: 'Browser game',
280
- when: `
281
- The person asked for a GAME, in those words or unmistakably — something played rather than used,
282
- with a loop, a score or a win condition. Never infer this from "gamified", "playful", "fun" or a
283
- leaderboard attached to an ordinary product. If the prompt does not plainly ask for a game, this
284
- is not it.
285
- `.trim(),
286
- patch: {
287
- packages: {
288
- capabilities: { postgres: true, kv: true, queue: true, worker: true, agents: false, validation: true, marketingConsent: true },
289
- deps: {
290
- [SubProject.Web]: { three: '^0.186.0', '@types/three': '^0.185.4' },
291
- },
292
- libraries: { [SubProject.Web]: gameLibraryList, 'ui-state': gameLibraryList },
293
- skills: [ViableSkill.GameScene, ViableSkill.GameUi, ViableSkill.LibrariesGame],
294
- personaSkills: { ...PRODUCER_PERSONA_SKILLS, ...GAME_PERSONA_SKILLS },
295
- },
296
- // A game's first value is PLAYING it, and the play screen is its own front door — a
297
- // pick-and-preview card on a landing page is the wrong shape for it.
298
- experience: { landingGate: LandingGatePreference.Discourage },
299
- },
300
- },
301
- }
302
-
303
- /**
304
- * The case a reference names, or `undefined`.
305
- *
306
- * Total over anything: an unknown name, a name from a deploy this one does not have, and an
307
- * absent one all read the same way — no patch, which is the base blueprint. That is the only
308
- * answer that cannot take a capability away from a project that already has it.
309
- */
310
- export const blueprintCaseOf = (name?: string): BlueprintCaseDefinition | undefined =>
311
- name == null ? undefined : (BLUEPRINT_CASES as Record<string, BlueprintCaseDefinition>)[name]
312
-
313
- /** Every case the platform ships, in the order a classifier should weigh them. */
314
- export const BLUEPRINT_CASE_ORDER: BlueprintCase[] = [
315
- BlueprintCase.Web,
316
- BlueprintCase.Scalable,
317
- BlueprintCase.AiPipeline,
318
- BlueprintCase.AiAgent,
319
- BlueprintCase.Game,
320
- ]
@@ -1,136 +0,0 @@
1
- import type { ViablePersona } from '../skills/roles.js'
2
- import type { ViableSkill } from '../skills/consts.js'
3
- import { blueprintCaseOf } from './case.js'
4
- import type { Blueprint, BlueprintPatch } from './types.js'
5
-
6
- const isPlainObject = (value: unknown): value is Record<string, unknown> =>
7
- value != null && typeof value === 'object' && !Array.isArray(value)
8
-
9
- /**
10
- * Merge one layer's override over a resolved value.
11
- *
12
- * Objects merge key by key; ARRAYS REPLACE. That asymmetry is deliberate and is the difference
13
- * between an override and an addition: a blueprint that narrows a library allow-list to three
14
- * entries means those three, and a concatenating merge would hand the coder back the very imports
15
- * the override existed to forbid. A layer that wants to add spreads the base itself.
16
- */
17
- export const mergeBlueprintValue = <T>(base: T, patch: unknown): T => {
18
- if (patch === undefined) return base
19
- if (!isPlainObject(base) || !isPlainObject(patch)) return patch as T
20
-
21
- const out: Record<string, unknown> = { ...base }
22
- for (const [key, value] of Object.entries(patch)) {
23
- if (value === undefined) continue
24
- out[key] = mergeBlueprintValue(out[key], value)
25
- }
26
-
27
- return out as T
28
- }
29
-
30
- /**
31
- * Apply an override patch to a blueprint, layer by layer.
32
- *
33
- * Frozen on the way out — shallowly at each layer, which is enough to make the accidental
34
- * `blueprint.packages.capabilities.worker = true` a visible failure rather than a change every
35
- * later helper silently inherits.
36
- */
37
- export const applyBlueprintPatch = (base: Blueprint, patch?: BlueprintPatch): Blueprint => {
38
- if (patch == null) return base
39
-
40
- const merged = mergeBlueprintValue(
41
- base as unknown as Record<string, unknown>, patch
42
- ) as unknown as Blueprint
43
-
44
- return freezeBlueprint(merged)
45
- }
46
-
47
- export const freezeBlueprint = (blueprint: Blueprint): Blueprint => Object.freeze({
48
- ...blueprint,
49
- technology: Object.freeze({ ...blueprint.technology }),
50
- stack: Object.freeze({ ...blueprint.stack }),
51
- template: Object.freeze({ ...blueprint.template }),
52
- createApp: Object.freeze({ ...blueprint.createApp }),
53
- packages: Object.freeze({
54
- ...blueprint.packages,
55
- capabilities: Object.freeze({ ...blueprint.packages.capabilities }),
56
- }),
57
- // The one optional layer: frozen when present, and never ADDED when absent, so a blueprint that
58
- // has none still compares equal to what it was before the layer existed.
59
- ...(blueprint.experience != null ? { experience: Object.freeze({ ...blueprint.experience }) } : {}),
60
- })
61
-
62
- /** Every skill the blueprint asks for, stack first, deduped, order preserved. */
63
- export const blueprintSkills = (blueprint: Blueprint): Blueprint['stack']['skills'] =>
64
- [...new Set([...blueprint.stack.skills, ...blueprint.packages.skills])]
65
-
66
-
67
- const union = <T>(base: readonly T[] = [], extra: readonly T[] = []): T[] =>
68
- [...new Set([...base, ...extra])]
69
-
70
- /**
71
- * Apply a CASE to a blueprint — which kind of product is being built on this stack.
72
- *
73
- * A case is an ordinary {@link BlueprintPatch} and follows every rule an override does, with two
74
- * deliberate exceptions: `skills` and `personaSkills` are UNIONED with the base rather than
75
- * replacing it.
76
- *
77
- * The asymmetry is the difference between the two things a patch can be. An override is written
78
- * by somebody looking at the resolved value and narrowing it — "these three libraries and no
79
- * others" — and a concatenating merge would hand back the very imports it existed to forbid. A
80
- * case is written once, in a table, by somebody who cannot see the blueprint it will be applied
81
- * to; it says what this KIND of product needs IN ADDITION, and replacing there would silently
82
- * drop every rule the stack itself established. A case that genuinely needs to take a skill away
83
- * does it by narrowing a capability, which is what stops the prompt being asked at all.
84
- *
85
- * Unknown, absent and unregistered case names all resolve to the blueprint unchanged. A project
86
- * records the case it was drawn with, that record outlives the deploy that knew the name, and
87
- * "built against the stack as it stands" is a far better answer than a project that cannot open.
88
- */
89
- export const applyBlueprintCase = (base: Blueprint, name?: string): Blueprint => {
90
- const cased = blueprintCaseOf(name)
91
- if (cased == null) return base
92
-
93
- const merged = applyBlueprintPatch(base, cased.patch)
94
- const patch = cased.patch
95
-
96
- const personaSkills = patch.packages?.personaSkills
97
- const stackSkills = patch.stack?.skills as ViableSkill[] | undefined
98
- const packageSkills = patch.packages?.skills as ViableSkill[] | undefined
99
-
100
- return freezeBlueprint({
101
- ...merged,
102
- stack: { ...merged.stack, skills: union(base.stack.skills, stackSkills) },
103
- packages: {
104
- ...merged.packages,
105
- skills: union(base.packages.skills, packageSkills),
106
- ...(personaSkills != null || base.packages.personaSkills != null
107
- ? {
108
- personaSkills: mergePersonaSkills(base.packages.personaSkills, personaSkills),
109
- }
110
- : {}),
111
- },
112
- })
113
- }
114
-
115
- const mergePersonaSkills = (
116
- base?: Partial<Record<ViablePersona, ViableSkill[]>>,
117
- extra?: Partial<Record<ViablePersona, ViableSkill[]>>,
118
- ): Partial<Record<ViablePersona, ViableSkill[]>> => {
119
- const out: Partial<Record<ViablePersona, ViableSkill[]>> = { ...base }
120
- for (const [persona, skills] of Object.entries(extra ?? {})) {
121
- const key = persona as ViablePersona
122
- out[key] = union(out[key], skills as ViableSkill[])
123
- }
124
-
125
- return out
126
- }
127
-
128
- /**
129
- * The skills ONE persona gets from the blueprint, beyond its own.
130
- *
131
- * Total, and empty for every persona a blueprint says nothing about — which is every one of them
132
- * on the blueprint the platform ships. `withPersona` unions this into the policy it composes.
133
- */
134
- export const personaSkillsOf = (
135
- blueprint: Blueprint, persona: ViablePersona
136
- ): ViableSkill[] => blueprint.packages.personaSkills?.[persona] ?? []
@@ -1,44 +0,0 @@
1
- import { BlueprintCase, GameKind } from './case.js'
2
-
3
- /**
4
- * What kind of product a prompt describes.
5
- *
6
- * A schema rather than a free answer because the value is a KEY: it is written onto the project
7
- * record, resolved into a blueprint patch on every run afterwards, and decides which dependencies
8
- * the tree is staged with. A model that answered "an AI-powered agent app" in prose would leave
9
- * the caller matching strings, and a miss there is silent — the project simply builds as the
10
- * default and nobody can say why.
11
- */
12
- export interface BlueprintCaseChoice {
13
- case: BlueprintCase
14
- /** Only meaningful for the game case; ignored everywhere else. */
15
- gameKind?: GameKind
16
- /** One sentence, from the prompt. Recorded so a later run can disagree with it. */
17
- rationale: string
18
- }
19
-
20
- export const BlueprintCaseChoiceSchema = {
21
- type: 'object',
22
- title: 'BlueprintCaseChoice',
23
- description: 'Which kind of application the described product is',
24
- properties: {
25
- case: {
26
- type: 'string',
27
- enum: Object.values(BlueprintCase),
28
- description: 'The least capable kind that can deliver what was asked for.',
29
- },
30
- gameKind: {
31
- type: 'string',
32
- enum: Object.values(GameKind),
33
- description: 'Only when the case is `game`. `casual` for one player in one browser, '
34
- + '`online-turn` for players acting one after another, `online-live` only when the brief '
35
- + 'plainly asks for players acting at the same moment.',
36
- },
37
- rationale: {
38
- type: 'string',
39
- description: 'One sentence naming what in the prompt decided it.',
40
- },
41
- },
42
- required: ['case', 'rationale'],
43
- additionalProperties: false,
44
- } as const