@frontera-sdk/cli 1.52.39 → 1.52.40

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 (225) hide show
  1. package/README.md +9 -0
  2. package/package.json +6 -4
  3. package/src/adopt.ts +6 -2
  4. package/src/api/apps-api.ts +86 -4
  5. package/src/api/blueprint-authoring-api.ts +35 -11
  6. package/src/api/dataset-api.ts +29 -0
  7. package/src/api/governed-action-api.ts +90 -2
  8. package/src/api/platform-api.ts +87 -2
  9. package/src/api/workflow-api.ts +33 -2
  10. package/src/app-contract.ts +156 -0
  11. package/src/app-lock.ts +125 -0
  12. package/src/args.ts +14 -3
  13. package/src/auth-verify.ts +10 -0
  14. package/src/automation-template.ts +12 -1
  15. package/src/blueprint/action-rules.ts +516 -0
  16. package/src/blueprint/action-semantic-fit.ts +205 -0
  17. package/src/blueprint/compile.ts +51 -2
  18. package/src/blueprint/diff.ts +34 -2
  19. package/src/blueprint/metric-query.ts +215 -0
  20. package/src/blueprint/metric-rules.ts +122 -0
  21. package/src/blueprint/object-sets.ts +116 -0
  22. package/src/blueprint/ontology-edit-plan.ts +48 -4
  23. package/src/blueprint/projection.ts +24 -1
  24. package/src/blueprint/scaffold.ts +84 -42
  25. package/src/commands/action/available.ts +1 -1
  26. package/src/commands/action/criteria-refusal.ts +84 -0
  27. package/src/commands/action/decide.ts +5 -2
  28. package/src/commands/action/deploy.ts +13 -4
  29. package/src/commands/action/list.ts +134 -19
  30. package/src/commands/action/requests.ts +34 -6
  31. package/src/commands/action/submit.ts +28 -13
  32. package/src/commands/agent/index-commands.ts +69 -7
  33. package/src/commands/agent/resolve.ts +2 -0
  34. package/src/commands/app/checks.ts +44 -0
  35. package/src/commands/app/deploy.ts +243 -60
  36. package/src/commands/app/deployments.ts +97 -0
  37. package/src/commands/app/dev.ts +103 -11
  38. package/src/commands/app/init.ts +36 -3
  39. package/src/commands/app/promote.ts +45 -19
  40. package/src/commands/app/pull.ts +3 -1
  41. package/src/commands/app/rollback.ts +46 -0
  42. package/src/commands/app/sdk.ts +54 -8
  43. package/src/commands/app/unpin.ts +27 -0
  44. package/src/commands/app/versions.ts +1 -1
  45. package/src/commands/automation/bindings.ts +4 -2
  46. package/src/commands/automation/build-entry.ts +4 -1
  47. package/src/commands/automation/index-commands.ts +3 -1
  48. package/src/commands/automation/pull.ts +6 -1
  49. package/src/commands/automation/run.ts +34 -9
  50. package/src/commands/blueprint/bind.ts +2 -1
  51. package/src/commands/blueprint/declarative.ts +331 -31
  52. package/src/commands/blueprint/get.ts +6 -2
  53. package/src/commands/blueprint/grants.ts +64 -10
  54. package/src/commands/blueprint/metric.ts +108 -0
  55. package/src/commands/blueprint/object-sets.ts +71 -0
  56. package/src/commands/blueprint/query.ts +155 -31
  57. package/src/commands/dataset/index-commands.ts +156 -7
  58. package/src/commands/knowledge/index-commands.ts +122 -20
  59. package/src/commands/local/check.ts +657 -44
  60. package/src/commands/local/data.ts +33 -12
  61. package/src/commands/local/dev.ts +875 -88
  62. package/src/commands/local/docs-mcp.ts +10 -18
  63. package/src/commands/local/index-commands.ts +300 -63
  64. package/src/commands/local/init.ts +18 -5
  65. package/src/commands/local/key.ts +0 -3
  66. package/src/commands/local/providers.ts +0 -7
  67. package/src/commands/local/remove.ts +116 -0
  68. package/src/commands/local/schema.ts +3 -4
  69. package/src/commands/local/sdk-refresh.ts +59 -0
  70. package/src/commands/local/snapshot.ts +100 -0
  71. package/src/commands/local/sync-request.ts +320 -13
  72. package/src/commands/local/sync.ts +102 -5
  73. package/src/commands/mcp/serve.ts +34 -0
  74. package/src/commands/registry.ts +52 -8
  75. package/src/commands/types.ts +29 -0
  76. package/src/commands/workflow/archive.ts +46 -0
  77. package/src/commands/workflow/get.ts +2 -1
  78. package/src/commands/workflow/index-commands.ts +5 -0
  79. package/src/commands/workflow/push.ts +49 -2
  80. package/src/commands/workflow/run.ts +65 -7
  81. package/src/commands/workflow/runs.ts +9 -6
  82. package/src/commands/workflow/webhook-test.ts +166 -0
  83. package/src/config.ts +43 -6
  84. package/src/dev-broker.ts +52 -0
  85. package/src/exit.ts +103 -4
  86. package/src/flag-help.ts +77 -17
  87. package/src/forge/touches.ts +15 -12
  88. package/src/function-outside-imports.ts +89 -0
  89. package/src/harness.ts +42 -24
  90. package/src/heal.ts +112 -1
  91. package/src/local/fix-words.ts +12 -0
  92. package/src/local/function-bundle-recovery.ts +775 -0
  93. package/src/local/local-actions.ts +103 -10
  94. package/src/local/local-agent-action-grants.ts +210 -0
  95. package/src/local/local-agent-checks.ts +232 -0
  96. package/src/local/local-agent-grants.ts +29 -7
  97. package/src/local/local-agent-lean-plugins.ts +68 -0
  98. package/src/local/local-agent-missing-tools.ts +68 -0
  99. package/src/local/local-agent-name.ts +27 -0
  100. package/src/local/local-app-code.ts +191 -0
  101. package/src/local/local-app-dev-files.ts +186 -0
  102. package/src/local/local-app-dev-session.ts +88 -0
  103. package/src/local/local-app-lock-install.ts +114 -0
  104. package/src/local/local-app-pulled.ts +167 -0
  105. package/src/local/local-app-restore.ts +111 -0
  106. package/src/local/local-app-runtime.ts +68 -0
  107. package/src/local/local-app-sdk-paths.ts +149 -0
  108. package/src/local/local-app-sdk.ts +509 -0
  109. package/src/local/local-applied.ts +211 -21
  110. package/src/local/local-apply-history.ts +84 -0
  111. package/src/local/local-apply-summary.ts +86 -0
  112. package/src/local/local-apply-watchdog.ts +95 -0
  113. package/src/local/local-apps.ts +180 -7
  114. package/src/local/local-blueprint-errors.ts +198 -0
  115. package/src/local/local-blueprint-outline.ts +31 -12
  116. package/src/local/local-blueprint-schemas.ts +582 -0
  117. package/src/local/local-build-inputs.ts +48 -0
  118. package/src/local/local-build-summary.ts +495 -28
  119. package/src/local/local-case-evidence.ts +120 -0
  120. package/src/local/local-case-not-run.ts +153 -0
  121. package/src/local/local-chat-apps.ts +93 -16
  122. package/src/local/local-check-inherited.ts +83 -0
  123. package/src/local/local-cities.ts +89 -0
  124. package/src/local/local-component-health.ts +119 -0
  125. package/src/local/local-control.ts +34 -7
  126. package/src/local/local-db-snapshot.ts +348 -0
  127. package/src/local/local-dev-references.ts +346 -35
  128. package/src/local/local-dev-sync.ts +136 -7
  129. package/src/local/local-docker.ts +35 -1
  130. package/src/local/local-docs-search.ts +1 -1
  131. package/src/local/local-docs.ts +281 -2
  132. package/src/local/local-empty-workflows.ts +92 -0
  133. package/src/local/local-env.ts +16 -1
  134. package/src/local/local-file-checks.ts +104 -0
  135. package/src/local/local-function-ids.ts +141 -0
  136. package/src/local/local-function-recovery.ts +141 -0
  137. package/src/local/local-function-state.ts +87 -0
  138. package/src/local/local-keys.ts +7 -5
  139. package/src/local/local-knowledge-ingestion.ts +225 -0
  140. package/src/local/local-knowledge-uploads.ts +217 -0
  141. package/src/local/local-media-placeholders.ts +182 -0
  142. package/src/local/local-media-sets.ts +90 -10
  143. package/src/local/local-model-parameters.ts +89 -0
  144. package/src/local/local-object-sets.ts +18 -6
  145. package/src/local/local-operating.ts +35 -1
  146. package/src/local/local-org-settings.ts +205 -7
  147. package/src/local/local-orphans.ts +321 -0
  148. package/src/local/local-personas.ts +296 -0
  149. package/src/local/local-portability.ts +141 -16
  150. package/src/local/local-ports.ts +99 -7
  151. package/src/local/local-project-notices.ts +234 -0
  152. package/src/local/local-project-secrets.ts +133 -0
  153. package/src/local/local-project.ts +12 -0
  154. package/src/local/local-provider-refusal.ts +66 -0
  155. package/src/local/local-request-evidence.ts +112 -0
  156. package/src/local/local-runtime-health.ts +123 -0
  157. package/src/local/local-runtime-history.ts +50 -0
  158. package/src/local/local-runtime-target.ts +42 -0
  159. package/src/local/local-sample-data.ts +141 -0
  160. package/src/local/local-sample-remake.ts +138 -0
  161. package/src/local/local-scaffold.ts +336 -53
  162. package/src/local/local-schema-check.ts +148 -0
  163. package/src/local/local-schemas.ts +742 -25
  164. package/src/local/local-secret-carry.ts +107 -0
  165. package/src/local/local-secrets.ts +38 -8
  166. package/src/local/local-seed.ts +102 -26
  167. package/src/local/local-stand-ins.ts +1 -1
  168. package/src/local/local-state.ts +43 -2
  169. package/src/local/local-studio-server.ts +170 -7
  170. package/src/local/local-supervisor.ts +44 -6
  171. package/src/local/local-sync-names.ts +2 -0
  172. package/src/local/local-sync-plan.ts +11 -0
  173. package/src/local/local-sync-push.ts +10 -7
  174. package/src/local/local-sync-refs.ts +49 -3
  175. package/src/local/local-sync-remote.ts +343 -32
  176. package/src/local/local-sync-snapshot.ts +15 -10
  177. package/src/local/local-synthetic-data.ts +2116 -99
  178. package/src/local/local-test-baseline.ts +113 -0
  179. package/src/local/local-test-cases.ts +76 -1
  180. package/src/local/local-test-runner.ts +582 -91
  181. package/src/local/local-test-runs.ts +13 -5
  182. package/src/local/local-vendored-sdk.ts +183 -0
  183. package/src/local/local-workflow-state.ts +16 -3
  184. package/src/local/local-workflow-tests.ts +482 -0
  185. package/src/local/local-workspace-grants.ts +81 -0
  186. package/src/main.ts +50 -9
  187. package/src/mcp/a11y-scan.ts +55 -0
  188. package/src/mcp/browser/ax-snapshot.ts +181 -0
  189. package/src/mcp/browser/credentials.ts +78 -0
  190. package/src/mcp/browser/frames.ts +336 -0
  191. package/src/mcp/browser/headless-session.ts +413 -0
  192. package/src/mcp/browser/headless-tab.ts +575 -0
  193. package/src/mcp/browser/keys.ts +70 -0
  194. package/src/mcp/browser/page-scripts.ts +129 -0
  195. package/src/mcp/browser/wait.ts +73 -0
  196. package/src/mcp/forge-link.ts +126 -0
  197. package/src/mcp/headless.ts +199 -0
  198. package/src/mcp/mcp-apps.ts +435 -0
  199. package/src/mcp/mcp-chat.ts +226 -0
  200. package/src/mcp/mcp-cli.ts +53 -0
  201. package/src/mcp/mcp-protocol.ts +122 -0
  202. package/src/mcp/mcp-runtime.ts +113 -0
  203. package/src/mcp/mcp-server.ts +108 -0
  204. package/src/mcp/mcp-traces.ts +409 -0
  205. package/src/mcp/page-observation.ts +193 -0
  206. package/src/mcp/tools/agent-tools.ts +700 -0
  207. package/src/mcp/tools/app-tools.ts +241 -0
  208. package/src/mcp/tools/browser-tools.ts +310 -0
  209. package/src/mcp/tools/docs-tools.ts +32 -0
  210. package/src/mcp/tools/project-tools.ts +205 -0
  211. package/src/mcp/tools/test-tools.ts +339 -0
  212. package/src/mcp/tools/workflow-tools.ts +611 -0
  213. package/src/output.ts +1 -1
  214. package/src/pack.ts +8 -3
  215. package/src/packaging.ts +1 -1
  216. package/src/project-bootstrap.ts +107 -4
  217. package/src/project-context.ts +18 -1
  218. package/src/project.ts +27 -2
  219. package/src/provenance.ts +25 -0
  220. package/src/render-evidence.ts +65 -7
  221. package/src/template.ts +14 -8
  222. package/src/templates/next-app-files.ts +35 -6
  223. package/src/templates/next-skills.ts +61 -27
  224. package/src/vendor/kit-assets.json +6 -6
  225. package/src/vendor/sdk-sources.json +24 -21
@@ -4,12 +4,13 @@ import { dirname, join } from 'node:path'
4
4
  import { AUTOMATION_SDK_FILES } from '../automation-template'
5
5
  import { AGENTS_MD } from '../harness'
6
6
  import vendored from '../vendor/sdk-sources.json'
7
+ import { SDK_MANIFEST, sdkManifestText } from './local-vendored-sdk'
7
8
 
8
9
  /**
9
10
  * A new project in the layout `frontera local dev` applies: one Agent with a
10
- * test case, one workflow, one Function, and a Blueprint object type with the
11
- * dataset it reads. Each is small enough to read in a minute and works
12
- * against the keyless local model as written.
11
+ * test case, one workflow with its test, one Function with its unit test, and
12
+ * a Blueprint object type with the dataset it reads. Each is small enough to
13
+ * read in a minute; the Agent's test case holds for any capable model.
13
14
  *
14
15
  * The Functions SDK is vendored into `vendor/frontera/` and resolved by the
15
16
  * `paths` in `tsconfig.json`, the way `frontera function init` does it: the
@@ -41,7 +42,8 @@ const AGENT = (name: string, modelSlug?: string) =>
41
42
  const OPERATING = JSON.stringify(
42
43
  {
43
44
  version: 1,
44
- execution: { harness: 'open-ended' },
45
+ // `tool:blueprint` is the query tools alone; the Action tools are their own choice.
46
+ execution: { harness: 'open-ended', toolScopes: 2 },
45
47
  variables: [
46
48
  {
47
49
  key: 'customer_name',
@@ -57,16 +59,61 @@ const OPERATING = JSON.stringify(
57
59
  2,
58
60
  ) + '\n'
59
61
 
62
+ /**
63
+ * What the starter Agent may use: the word-count Function, called on its own
64
+ * (no approval), and the Ticket records through its Blueprint tools. The
65
+ * workflows it may start go in `workflows`, the same way.
66
+ */
67
+ const BINDINGS = JSON.stringify(
68
+ {
69
+ functions: [{ function: 'function/word-count', requiresApproval: false }],
70
+ workflows: [],
71
+ blueprint: [{ objectType: 'objectType/Ticket', permissions: ['read'] }],
72
+ },
73
+ null,
74
+ 2,
75
+ ) + '\n'
76
+
77
+ /**
78
+ * What the starter case accepts as offering help: the ways a reply offers it
79
+ * ("I can look up support tickets for you… Tell me what you're looking for"
80
+ * matched none of help, assist or question). Each word starts a word, so
81
+ * "ask" does not match "task".
82
+ */
83
+ export const STARTER_OFFER_PATTERN = String.raw`\b(help|assist|answer|question|ask|support|look up|find|explain|tell me|let me know|happy to|glad to|I can|able to|what would you like|how can)`
84
+
60
85
  const EVALS = `# Test cases for the assistant. Each case: a conversation, then what a good reply does.
61
86
  # \`frontera local test\` (or Evaluations in Studio) runs them against the draft.
62
87
  #
63
- # This one passes with the keyless local model, which repeats what it was told.
64
- # Once the Agent uses a real model, write expectations about the answer instead.
88
+ # This one checks what any capable model does with a greeting: it offers help.
89
+ # A reply says so in many ways ("I can look up…", "happy to help", "tell me what
90
+ # you need"), so the pattern takes each of them, not one word.
91
+ # Write expectations about what the reply does, not its exact words.
65
92
  cases:
66
- - name: replies-to-a-greeting
93
+ - name: offers-help-when-greeted
67
94
  conversation:
68
- - user: Hello from a new project
69
- expectedContains: [Hello from a new project]
95
+ - user: Hello! What can you help me with?
96
+ checks:
97
+ - { type: regex, value: '${STARTER_OFFER_PATTERN}', text: offers help }
98
+ `
99
+
100
+ const WORKFLOW_TESTS = `# Test cases for workflows/hello.yaml. \`frontera local test --workflow hello\`
101
+ # runs each one on the workflow's draft, waits for the run, then checks it.
102
+ # \`frontera local schema workflow-tests\` is this file's format.
103
+ cases:
104
+ - name: greets-by-name
105
+ input: { name: Sam }
106
+ expect:
107
+ status: succeeded
108
+ output:
109
+ - { path: steps.greet.output, equals: 'Hello, Sam' }
110
+ `
111
+
112
+ const FIXTURES_README = `# Fixtures
113
+
114
+ Sample inputs for tests: a payload a workflow test sends, a document an
115
+ Agent's test case refers to, the input a Function's unit test reads. Nothing
116
+ here is applied to the runtime; tests read these files by path.
70
117
  `
71
118
 
72
119
  const WORKFLOW = `apiVersion: workflow/v1
@@ -112,8 +159,10 @@ import wordCount from './word-count'
112
159
  // A Function is a handler, so it is tested without a runtime: \`bun test\`.
113
160
  // The context refuses what the platform refuses (a grant the manifest does not
114
161
  // declare, a step name used twice), so a passing test means something.
162
+ // \`replay: true\` runs it as the platform does, again from the top after each
163
+ // step, so code outside a step that a step changes is caught here too.
115
164
  test('counts the words', async () => {
116
- const { ctx, logs } = createTestContext({ input: { text: 'four words right here' } })
165
+ const { ctx, logs } = createTestContext({ replay: true, input: { text: 'four words right here' } })
117
166
  expect(await wordCount.handler(ctx)).toEqual({ words: 4 })
118
167
  expect(logs.map((l) => l.message)).toEqual(['counted'])
119
168
  })
@@ -128,11 +177,17 @@ A Frontera project. Every file here is applied to a local runtime by
128
177
  |---|---|
129
178
  | \`agents/<slug>/agent.json\` | An Agent's draft: model, instructions, tools |
130
179
  | \`agents/<slug>/operating.json\` | Its behavior: procedures, variables, focus, reply checks |
180
+ | \`agents/<slug>/bindings.json\` | What it may use: Functions, workflows, Blueprint object types |
131
181
  | \`agents/<slug>/evals/*.yaml\` | Its test cases |
132
182
  | \`workflows/<slug>.yaml\` | A workflow |
183
+ | \`workflows/tests/<slug>.yaml\` | A workflow's test cases |
133
184
  | \`functions/<name>.ts\` | A Function |
134
185
  | \`datasets/<apiName>.json\` + \`.csv\` | Sample data the Blueprint reads |
135
- | \`blueprint/\` | The Blueprint: object types, links, metrics, actions |
186
+ | \`blueprint/\` | The Blueprint: object types, links, metrics, actions, saved object sets |
187
+ | \`knowledge/<name>/knowledge.json\` | A knowledge base, its documents beside it |
188
+ | \`themes/<name>/theme.json\` | A slide theme, its images beside it |
189
+ | \`chat-apps/<slug>.json\` | A Chat App |
190
+ | \`data/generate.ts\` | How \`frontera local data --synthetic\` makes sample data |
136
191
  | \`fixtures/\` | Sample data for tests |
137
192
 
138
193
  ## Develop
@@ -141,9 +196,9 @@ A Frontera project. Every file here is applied to a local runtime by
141
196
  frontera local start # the runtimes, on this machine
142
197
  frontera local dev # apply each file as you save it
143
198
  frontera local ui # open Studio
144
- frontera local test # run the Agents' test cases
145
- bun test # run the Functions' unit tests, no runtime needed
146
- frontera local check # start, apply and test in one command, before pushing
199
+ frontera local test # run the Agents' and workflows' test cases
200
+ bun test # run the Functions' unit tests (functions/), no runtime needed
201
+ frontera local check # the whole gate in one command, before pushing
147
202
  \`\`\`
148
203
 
149
204
  An Agent's model is the catalog slug in \`modelSlug\` in its \`agent.json\`.
@@ -188,6 +243,13 @@ T-2,Invoice total is wrong,closed
188
243
  T-3,Refund not received,open
189
244
  `
190
245
 
246
+ /**
247
+ * The sample rows the scaffold writes, by dataset: while a file still holds
248
+ * exactly these, it is sample data `frontera local data --synthetic` may
249
+ * remake. Once someone edits it, it is theirs and is kept.
250
+ */
251
+ export const SCAFFOLD_SAMPLE_ROWS: Readonly<Record<string, string>> = { tickets: DATASET_ROWS }
252
+
191
253
  const OBJECT_TYPE = `apiName: Ticket
192
254
  displayName: Ticket
193
255
  pluralDisplayName: Tickets
@@ -226,9 +288,8 @@ backing:
226
288
  `
227
289
 
228
290
  /**
229
- * The CLI's AGENTS.md, plus what a coding agent needs for this project. The
230
- * \`local\` commands are internal and absent from \`frontera help\`, so an
231
- * agent following the generic text alone would never find the loop.
291
+ * The CLI's AGENTS.md, plus what a coding agent needs for this project: the
292
+ * generic text does not describe the local loop.
232
293
  */
233
294
  const SOLUTION_AGENTS_MD = `${AGENTS_MD}
234
295
  ## This project
@@ -241,50 +302,168 @@ Frontera deployment.
241
302
  |---|---|
242
303
  | \`agents/<slug>/agent.json\` | An Agent's draft: model, instructions, tools |
243
304
  | \`agents/<slug>/operating.json\` | Its behavior: procedures, variables, focus, reply checks |
305
+ | \`agents/<slug>/bindings.json\` | What it may use: the Functions and workflows it may start, the Blueprint object types it may query |
244
306
  | \`agents/<slug>/evals/*.yaml\` | Its test cases: a conversation and what a good reply does |
245
307
  | \`workflows/<slug>.yaml\` | A workflow |
308
+ | \`workflows/tests/<slug>.yaml\` | Its test cases: an input and what the run must end with |
246
309
  | \`functions/<name>.ts\` | A Function (the SDK is vendored in \`vendor/\`) |
247
310
  | \`datasets/<apiName>.json\` + \`.csv\` | Sample data the Blueprint reads |
248
- | \`blueprint/\` | The Blueprint: object types, links, metrics, actions |
311
+ | \`blueprint/\` | The Blueprint: object-types/, link-types/, shared-fields/, semantic-types/, metrics/, actions/, object-sets/ |
312
+ | \`knowledge/<name>/knowledge.json\` | A knowledge base, its documents beside it |
313
+ | \`skills/<name>/SKILL.md\` | A skill |
314
+ | \`themes/<name>/theme.json\` | A slide theme, its images beside it |
315
+ | \`chat-apps/<slug>.json\` | A Chat App |
316
+ | \`apps/<name>/\` | A Custom App's code (\`frontera app init\` from inside apps/) |
317
+ | \`data/generate.ts\` | How \`frontera local data --synthetic\` makes sample data |
318
+
319
+ \`frontera local dev\` says which files in these folders it does not apply
320
+ and where they belong; a file it skips is never applied.
321
+
322
+ ### What goes where
323
+
324
+ - An Agent's tools are not in \`agent.json\`. The Functions and workflows it
325
+ may start and the object types it may query are in \`bindings.json\`
326
+ (\`frontera local schema bindings\`); without a \`blueprint\` entry it has no
327
+ Blueprint tools. A procedure that keeps its own tools (\`tools.inherit:
328
+ false\`) lists them in \`tools.ids\` as \`function:<slug>\`,
329
+ \`workflow:<slug>\` or \`tool:plugins\` / \`tool:blueprint\` (query tools
330
+ only) / \`tool:blueprint-actions\` (governed Action tools: list, submit,
331
+ read a request) / \`tool:web\`, each bound in \`bindings.json\`. That
332
+ meaning needs \`"toolScopes": 2\` in \`execution\` (new Agents have it);
333
+ without it \`tool:blueprint\` keeps the Action tools too. Its knowledge is \`knowledgeAttachments:
334
+ [{ "knowledgeConfigId": "knowledge/<name>" }]\` in \`agent.json\`.
335
+ - A governed Action that writes Blueprint records names
336
+ \`effect.authoritySourceSystem: active-catalog\`, the source system the
337
+ Blueprint's own records have (\`frontera blueprint new action <name>\`
338
+ writes it).
339
+ - A saved object set is \`blueprint/object-sets/<name>.yaml\`
340
+ (\`frontera local schema blueprint-object-set\`); other files name it
341
+ \`objectSet/<name>\`, by its name as people say it ("Open work orders") or
342
+ its file's name.
343
+ - A dataset's first load fixes its columns: declare \`nullable: true\` in its
344
+ \`columns\` for every column a later CSV may leave empty
345
+ (\`frontera local data --synthetic\` does). When a CSV has empty values a
346
+ column did not allow, \`local dev\` cuts a new revision that allows them; a
347
+ column added, dropped or retyped is a new dataset (another apiName, or
348
+ \`frontera local reset\`).
349
+ - \`frontera local data --synthetic\` writes sample data that keeps links and
350
+ states consistent; \`data/generate.ts\` adjusts it, and
351
+ \`frontera local data --help\` describes both (a field function is
352
+ \`(row, index, ctx) => value\`, \`row\` holding the whole generated row
353
+ and \`ctx.rowsOf(type)\` / \`ctx.lookup(type, key)\` the rows of the types
354
+ it links to; a function that reads another's value runs after it).
355
+
356
+ When your harness offers the \`frontera\` MCP server's tools (Claude Code
357
+ lists them as \`mcp__frontera__*\`), use them for the loop rather than the
358
+ shell: \`status\` rather than \`frontera local status --json\`, \`apply\`
359
+ rather than \`frontera local dev --once\`, \`test_run\` rather than
360
+ \`frontera local test\`, \`logs\` rather than \`frontera local logs\`, and
361
+ \`read_file_schema\` rather than \`frontera local schema\`. The person
362
+ approves the server once; each shell command can ask them again. The CLI
363
+ below is the fallback: when the tools aren't offered (the server isn't
364
+ connected, or the person chose not to use it), and for what has no tool
365
+ (\`local start\`, \`local check\`, \`local data\`, snapshots).
249
366
 
250
367
  The loop:
251
368
 
252
369
  1. \`frontera local start\` brings the runtime up (once; needs Docker).
253
370
  2. Edit files. Then run \`frontera local dev --once\`: it applies what
254
371
  changed and prints each refused file's error and validation messages.
255
- Fix those before going on. (A person may keep \`frontera local dev\`
256
- running instead; it applies on save.)
257
- 3. \`frontera local test\` runs the Agents' test cases and exits non-zero
258
- when one fails. \`bun test\` runs the Functions' unit tests offline.
372
+ Fix those before going on. It exits 0 when everything changed or added
373
+ applied: an item still as the pull wrote it that fails is listed apart,
374
+ as inherited from the workspace (\`inheritedFailures\` with \`--json\`).
375
+ (A person may keep \`frontera local dev\` running instead; it applies
376
+ on save.)
377
+ 3. \`frontera local test\` runs the Agents' and the workflows' test cases and
378
+ exits non-zero when one fails (\`--json\` lists each case's result either
379
+ way). \`bun test\` here runs the Functions' unit tests offline: the
380
+ project's \`bunfig.toml\` keeps it to \`functions/\`. An App's own tests
381
+ run in its folder (\`bun run test\` in \`apps/<name>/\`, part of its
382
+ \`check\`).
259
383
  4. Before handing work back, run \`frontera local check\`: it starts the
260
- runtime if needed, applies everything and runs the test cases, and exits
261
- non-zero at the first failure.
262
- 5. \`frontera local status -o env\` prints the API URL and keys for scripts.
263
-
264
- \`frontera local <verb> --help\` documents each command; they are not
265
- listed by \`frontera help\`.
266
-
267
- ## Documentation
268
-
269
- Frontera's documentation for the platform this project runs on is an MCP
270
- server here: \`frontera-docs\` in \`.mcp.json\` (Claude Code) and
271
- \`.cursor/mcp.json\` (Cursor); for Codex, add the command
272
- \`frontera local docs-mcp\` as an MCP server. Its tools search and read the
273
- same pages as the documentation in the platform, for the release the local
274
- runtime runs. Before writing or changing an \`agent.json\`, an
384
+ runtime if needed, applies everything, typechecks, runs the Functions'
385
+ unit tests, each App's own \`check\` script, and the Agent and workflow
386
+ test cases. Every step runs and is reported (the Agent and workflow
387
+ tests run on what applied; \`--json\` gives each step's result). What
388
+ fails the gate:
389
+ - A failure in anything you changed or added (a governed Action of
390
+ yours that does not deploy, a type error in your file, your test)
391
+ fails it: it exits non-zero, naming the first step that failed.
392
+ - A failure only in items or files unchanged since the pull
393
+ (\`frontera.lock\`) is listed as inherited from the workspace
394
+ (\`inherited\` in \`--json\`) and passes, unless \`--strict\`, which
395
+ fails on those too.
396
+ When you report back, give the gate's result (passed or failed, and
397
+ whether with \`--strict\`) and list every inherited failure with the fix
398
+ the check printed for it: the workspace has the same problem, and the
399
+ person decides whether it is fixed here.
400
+ 5. \`frontera local status\` says what each item came to and why;
401
+ \`frontera local status -o env\` prints the API URL and keys for scripts.
402
+
403
+ The loop's commands are listed under Local development in \`frontera help\`;
404
+ \`frontera local <verb> --help\` documents each one. In this folder,
405
+ Blueprint and Action commands use the runtime's organization key, and every
406
+ other command its workspace key, with no \`--profile\`.
407
+
408
+ ## The Frontera MCP server
409
+
410
+ \`frontera mcp\` gives you this project's local runtime, Frontera's
411
+ documentation, its Apps and, when the project is open in Forge, a browser
412
+ the person watches. It is registered as \`frontera\` in \`.mcp.json\`
413
+ (Claude Code), \`.cursor/mcp.json\` (Cursor), \`opencode.json\` (OpenCode)
414
+ and \`.codex/config.toml\` (Codex). Its documentation tools search and read
415
+ the same pages as the documentation in the platform, for the release the
416
+ local runtime runs. Before writing or changing an \`agent.json\`, an
275
417
  \`operating.json\` (procedures, variables), a workflow, a Function or the
276
418
  Blueprint, search the documentation for it and follow what it says; do not
277
- guess a field or a step kind. It reads the documentation that
278
- \`frontera local start\` keeps for the runtime's release, so start the runtime
279
- once; after that it needs no key and works while the runtime is stopped.
419
+ guess a field or a step kind. Start the runtime once so the documentation is
420
+ here; after that it needs no key and works while the runtime is stopped.
421
+
422
+ ## See what you built
423
+
424
+ Check the part you changed, not everything:
425
+
426
+ - After changing an Agent: \`apply\`, then \`agent_chat\` with it and read
427
+ the trace (\`trace_get\`). \`test_run\` its cases: \`newlyFailing\` lists
428
+ what your change broke; \`couldNotRun\` lists cases the model provider or
429
+ the runtime kept from running (not a regression): tell the person its fix.
430
+ - After changing an App: \`app_render\` the pages you touched and read the
431
+ console and failed requests; \`browser_open\` to click through a flow.
432
+ - When you report back, attach the screenshot path or the trace link.
433
+
434
+ What you run here changes the local runtime's records, never the
435
+ workspace's: a workflow run (\`workflow_run\`, \`frontera workflow run\`),
436
+ a Function run, an App and an Agent conversation with real Actions submit
437
+ governed Actions for real here, whether from an \`action\` step or from a
438
+ Function's code, and an Action that creates, updates or deletes Blueprint
439
+ records changes them. An Action whose effect would reach another system (a
440
+ transition or a command) is switched off here and does not run. Those writes
441
+ stay: \`frontera local snapshot save\` before and \`frontera local snapshot
442
+ restore\` after take them back (\`frontera local check\` does this around its
443
+ own tests).
444
+
445
+ A change request runs each Agent's cases marked \`gate: true\` before it
446
+ applies, against the last applied run; the workspace can require them. Mark
447
+ the cases that guard an Agent's behavior \`gate: true\`, and run them with
448
+ \`test_run\` (\`gate: true\`) before you say a change is ready. Screenshots
449
+ you take are saved under \`.frontera/evidence/<session>/\`, where the
450
+ person can attach them to the request.
451
+
452
+ You cannot promote a deployment, apply a change request or read secrets.
453
+ Say what is ready and let the person decide.
280
454
 
281
455
  The guides describe the product and its concepts, mostly through its
282
456
  screens. The exact format of each file is a JSON Schema for the release the
283
457
  runtime runs: \`frontera local schema <kind>\` prints it, for \`operating\`,
284
- \`agent\`, \`workflow\`, \`evals\` and \`dataset\` (\`frontera local schema\`
285
- lists which file each one is), \`frontera local start\` keeps them in
286
- \`.frontera/local/schemas/\`, and the documentation server's
287
- \`read_file_schema\` tool returns them too. Read the schema before you write
458
+ \`agent\`, \`bindings\`, \`workflow\`, \`evals\`, \`workflow-tests\`,
459
+ \`dataset\`, \`theme\` and each Blueprint file (\`blueprint-object-type\`,
460
+ \`blueprint-action\`, \`blueprint-object-set\`…;
461
+ \`frontera local schema\` lists which file each one is). Where a file names
462
+ another resource the schema takes its reference (\`function/<slug>\`,
463
+ \`model/<slug>\` for an \`llm\` step), which \`local dev\` resolves to the
464
+ runtime's id; \`frontera local start\` keeps them in
465
+ \`.frontera/local/schemas/\`, and the MCP server's \`read_file_schema\`
466
+ tool returns them too. Read the schema before you write
288
467
  a file, rather than guessing a field or reading Frontera's source, and do
289
468
  not add a \`$schema\` key to a file: most files refuse a key they do not
290
469
  know. A schema checks a file's shape; what the file refers to (the action a
@@ -300,22 +479,114 @@ the person's to set (\`frontera local key-set\`, \`provider-add\`,
300
479
  \`model-add\`, or Forge's Models page): ask them rather than doing it, and
301
480
  never handle a key yourself.
302
481
 
303
- Change the files, not the runtime: \`local dev\` keeps the two in step, and
304
- a change made only on the runtime is lost on the next reset. A changed CSV
305
- must keep its dataset's columns.
482
+ Change the files, not the runtime: \`local dev\` keeps the two in step (delete
483
+ an Agent's folder or a workflow's file and it is archived on the runtime, a
484
+ Function's and it is switched off; \`status\` lists what the runtime holds
485
+ with no file), and a change made only on the runtime is lost on the next
486
+ reset. A changed CSV must keep its dataset's columns.
306
487
  `
307
488
 
308
489
  /**
309
- * Frontera's documentation for the coding agents working in the project:
310
- * a stdio MCP server the harness launches. It reads the copy `local start`
311
- * keeps, so there is no key and nothing secret in this file.
490
+ * The Frontera MCP server for the coding agents working in the project, one
491
+ * entry per harness: a stdio server the harness launches. It reads the
492
+ * runtime's state file for everything else, so there is no key and nothing
493
+ * secret in these files.
494
+ */
495
+ export const MCP_ENTRY = { command: 'frontera', args: ['mcp'] } as const
496
+ const MCP_JSON = `${JSON.stringify({ mcpServers: { frontera: MCP_ENTRY } }, null, 2)}\n`
497
+ const OPENCODE_JSON = `${JSON.stringify({ $schema: 'https://opencode.ai/config.json', mcp: { frontera: { type: 'local', command: [MCP_ENTRY.command, ...MCP_ENTRY.args], enabled: true } } }, null, 2)}\n`
498
+ /**
499
+ * The project's own Claude Code settings: they approve the project's
500
+ * `frontera` server from `.mcp.json` (`enabledMcpjsonServers`), so a new
501
+ * session asks only once, to trust the folder; Claude Code applies a
502
+ * project's approvals after that. Nothing is written to the person's own
503
+ * settings (`~/.claude.json`, `~/.claude/settings.json`).
312
504
  */
313
- const DOCS_MCP = `${JSON.stringify({ mcpServers: { 'frontera-docs': { command: 'frontera', args: ['local', 'docs-mcp'] } } }, null, 2)}\n`
505
+ export const CLAUDE_SETTINGS_JSON = `${JSON.stringify({ enabledMcpjsonServers: ['frontera'] }, null, 2)}\n`
506
+ /** Codex reads a project's own `.codex/config.toml` once the person trusts the project. */
507
+ export const CODEX_MCP_TOML = `# The Frontera MCP server for this project (\`frontera mcp\`).\n[mcp_servers.frontera]\ncommand = "frontera"\nargs = ["mcp"]\n`
508
+
509
+ /** The command projects registered before \`frontera mcp\` (the documentation server alone). */
510
+ const OLD_MCP_ARGS = ['local', 'docs-mcp']
511
+
512
+ const sameArgs = (a: unknown, b: readonly string[]) => Array.isArray(a) && a.length === b.length && a.every((x, i) => x === b[i])
513
+
514
+ /**
515
+ * Point the project's coding-agent configs that still launch \`frontera local
516
+ * docs-mcp\` at \`frontera mcp\` instead, leaving every other server and
517
+ * setting in them as it is. Returns the files changed; never throws.
518
+ */
519
+ export function migrateMcpConfigs(root: string): string[] {
520
+ const changed: string[] = []
521
+ const edit = (rel: string, change: (text: string) => string | null) => {
522
+ const full = join(root, rel)
523
+ if (!existsSync(full)) return
524
+ try {
525
+ const before = readFileSync(full, 'utf8')
526
+ const after = change(before)
527
+ if (after !== null && after !== before) {
528
+ writeFileSync(full, after)
529
+ changed.push(rel)
530
+ }
531
+ } catch {
532
+ // A config that is not ours to read is left alone.
533
+ }
534
+ }
535
+ for (const rel of ['.mcp.json', '.cursor/mcp.json']) {
536
+ edit(rel, (text) => {
537
+ const doc = JSON.parse(text) as { mcpServers?: Record<string, { command?: string; args?: unknown }> }
538
+ let moved = false
539
+ for (const server of Object.values(doc.mcpServers ?? {})) {
540
+ if (server?.command === 'frontera' && sameArgs(server.args, OLD_MCP_ARGS)) {
541
+ server.args = [...MCP_ENTRY.args]
542
+ moved = true
543
+ }
544
+ }
545
+ return moved ? `${JSON.stringify(doc, null, 2)}\n` : null
546
+ })
547
+ }
548
+ edit('opencode.json', (text) => {
549
+ const doc = JSON.parse(text) as { mcp?: Record<string, { command?: unknown }> }
550
+ let moved = false
551
+ for (const server of Object.values(doc.mcp ?? {})) {
552
+ if (sameArgs(server?.command, ['frontera', ...OLD_MCP_ARGS])) {
553
+ server.command = [MCP_ENTRY.command, ...MCP_ENTRY.args]
554
+ moved = true
555
+ }
556
+ }
557
+ return moved ? `${JSON.stringify(doc, null, 2)}\n` : null
558
+ })
559
+ edit('.codex/config.toml', (text) => text.replace(/^(args\s*=\s*)\[\s*"local"\s*,\s*"docs-mcp"\s*\]/m, '$1["mcp"]'))
560
+ return changed
561
+ }
314
562
 
315
563
  const GITIGNORE = `node_modules/
316
564
  .env
317
565
  `
318
566
 
567
+ /**
568
+ * The project's `bunfig.toml`: `bun test` at its root runs the Functions'
569
+ * unit tests and nothing else. Without it Bun sweeps every folder, Apps'
570
+ * tests included, which need the App's own packages and run in its folder.
571
+ */
572
+ export const PROJECT_BUNFIG = `# \`bun test\` here runs the Functions' unit tests (functions/), offline.
573
+ # An App's own tests run in its folder: \`bun run test\` in apps/<name>/.
574
+ [test]
575
+ root = "./functions"
576
+ `
577
+
578
+ /**
579
+ * Write the project's `bunfig.toml` when it has none (an older CLI's
580
+ * project, or a pull): true when it wrote one. One that is there is the
581
+ * person's, never changed.
582
+ */
583
+ export function ensureProjectBunfig(root: string): boolean {
584
+ const path = join(root, 'bunfig.toml')
585
+ if (existsSync(path) || !existsSync(join(root, 'package.json'))) return false
586
+ writeFileSync(path, PROJECT_BUNFIG)
587
+ return true
588
+ }
589
+
319
590
  /** `samples: false` leaves out the example Agent, workflow, Function and object type: a project started from a workspace gets the workspace's own. */
320
591
  export function solutionScaffoldFiles(name: string, opts: { agentModel?: string; samples?: boolean } = {}): Record<string, string> {
321
592
  const sdk = vendored.files as Record<string, string>
@@ -325,18 +596,27 @@ export function solutionScaffoldFiles(name: string, opts: { agentModel?: string;
325
596
  if (content === undefined) throw new Error(`vendored SDK is missing ${rel} — run \`bun run sync:sdk\` in packages/frontera-cli`)
326
597
  out[`vendor/${rel}`] = content
327
598
  }
599
+ // What the CLI wrote there, by hash: `local dev` keeps the copies nobody edited current (`local-vendored-sdk.ts`).
600
+ out[SDK_MANIFEST] = sdkManifestText(Object.fromEntries(Object.entries(out).filter(([rel]) => rel.startsWith('vendor/'))))
328
601
 
329
602
  out['frontera.yaml'] = `# A Frontera project. \`frontera local dev\` applies agents/, workflows/, functions/ and blueprint/.\nname: ${name}\n`
330
603
  out['README.md'] = README(name)
331
604
  out['AGENTS.md'] = SOLUTION_AGENTS_MD
332
605
  out['.gitignore'] = GITIGNORE
333
- out['.mcp.json'] = DOCS_MCP
334
- out['.cursor/mcp.json'] = DOCS_MCP
606
+ out['.mcp.json'] = MCP_JSON
607
+ out['.claude/settings.json'] = CLAUDE_SETTINGS_JSON
608
+ out['.cursor/mcp.json'] = MCP_JSON
609
+ out['opencode.json'] = OPENCODE_JSON
610
+ out['.codex/config.toml'] = CODEX_MCP_TOML
611
+ out['fixtures/README.md'] = FIXTURES_README
612
+ out['bunfig.toml'] = PROJECT_BUNFIG
335
613
  if (opts.samples !== false) {
336
614
  out['agents/assistant/agent.json'] = AGENT(name, opts.agentModel)
337
615
  out['agents/assistant/operating.json'] = OPERATING
616
+ out['agents/assistant/bindings.json'] = BINDINGS
338
617
  out['agents/assistant/evals/regression.yaml'] = EVALS
339
618
  out['workflows/hello.yaml'] = WORKFLOW
619
+ out['workflows/tests/hello.yaml'] = WORKFLOW_TESTS
340
620
  out['functions/word-count.ts'] = FUNCTION
341
621
  out['functions/word-count.test.ts'] = FUNCTION_TEST
342
622
  out['datasets/tickets.json'] = DATASET
@@ -392,8 +672,11 @@ export function solutionScaffoldFiles(name: string, opts: { agentModel?: string;
392
672
  export function writeSolutionScaffold(target: string, name: string, force: boolean, opts: { agentModel?: string; samples?: boolean } = {}): { written: string[]; skipped: string[] } {
393
673
  const written: string[] = []
394
674
  const skipped: string[] = []
675
+ // A vendored SDK an older CLI wrote has no manifest, and one written now would vouch for files it didn't write.
676
+ const olderSdk = !force && existsSync(join(target, 'vendor', 'frontera')) && !existsSync(join(target, SDK_MANIFEST))
395
677
  for (const [rel, content] of Object.entries(solutionScaffoldFiles(name, opts))) {
396
678
  const full = join(target, rel)
679
+ if (rel === SDK_MANIFEST && olderSdk) continue
397
680
  const generic = rel === 'AGENTS.md' && existsSync(full) && readFileSync(full, 'utf8') === AGENTS_MD
398
681
  if (existsSync(full) && !force && !generic) {
399
682
  skipped.push(rel)