@autonoma-ai/planner 0.1.10 → 0.1.11
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/dist/index.js +195 -16
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -3564,6 +3564,9 @@ async function sendRequest(config, payload) {
|
|
|
3564
3564
|
}
|
|
3565
3565
|
return { ok: res.ok, status: res.status, body };
|
|
3566
3566
|
}
|
|
3567
|
+
async function discover(config) {
|
|
3568
|
+
return sendRequest(config, { action: "discover" });
|
|
3569
|
+
}
|
|
3567
3570
|
async function up(config, create, testRunId) {
|
|
3568
3571
|
return sendRequest(config, {
|
|
3569
3572
|
action: "up",
|
|
@@ -3585,6 +3588,130 @@ var init_http_client = __esm({
|
|
|
3585
3588
|
}
|
|
3586
3589
|
});
|
|
3587
3590
|
|
|
3591
|
+
// src/agents/04-recipe-builder/discover-schema.ts
|
|
3592
|
+
function parseDiscoverBody(body) {
|
|
3593
|
+
if (!body || typeof body !== "object") return null;
|
|
3594
|
+
const schema = body.schema;
|
|
3595
|
+
if (!schema || typeof schema !== "object") return null;
|
|
3596
|
+
const rawModels = schema.models;
|
|
3597
|
+
if (!Array.isArray(rawModels)) return null;
|
|
3598
|
+
const models = /* @__PURE__ */ new Map();
|
|
3599
|
+
for (const m of rawModels) {
|
|
3600
|
+
if (!m || typeof m !== "object") continue;
|
|
3601
|
+
const mm = m;
|
|
3602
|
+
if (typeof mm.name !== "string") continue;
|
|
3603
|
+
const rawFields = Array.isArray(mm.fields) ? mm.fields : [];
|
|
3604
|
+
const fields = [];
|
|
3605
|
+
for (const f of rawFields) {
|
|
3606
|
+
if (!f || typeof f !== "object") continue;
|
|
3607
|
+
const ff = f;
|
|
3608
|
+
if (typeof ff.name !== "string") continue;
|
|
3609
|
+
fields.push({
|
|
3610
|
+
name: ff.name,
|
|
3611
|
+
type: typeof ff.type === "string" ? ff.type : "string",
|
|
3612
|
+
isRequired: ff.isRequired === true,
|
|
3613
|
+
isId: ff.isId === true,
|
|
3614
|
+
hasDefault: ff.hasDefault === true
|
|
3615
|
+
});
|
|
3616
|
+
}
|
|
3617
|
+
models.set(mm.name, { name: mm.name, tableName: typeof mm.tableName === "string" ? mm.tableName : mm.name, fields });
|
|
3618
|
+
}
|
|
3619
|
+
const scopeField = typeof schema.scopeField === "string" && schema.scopeField.length > 0 ? schema.scopeField : null;
|
|
3620
|
+
return { models, scopeField };
|
|
3621
|
+
}
|
|
3622
|
+
async function fetchDiscoverSchema(config) {
|
|
3623
|
+
try {
|
|
3624
|
+
const res = await discover(config);
|
|
3625
|
+
if (!res.ok) return null;
|
|
3626
|
+
return parseDiscoverBody(res.body);
|
|
3627
|
+
} catch {
|
|
3628
|
+
return null;
|
|
3629
|
+
}
|
|
3630
|
+
}
|
|
3631
|
+
function isMandatory(field) {
|
|
3632
|
+
return field.isRequired && !field.isId && !field.hasDefault;
|
|
3633
|
+
}
|
|
3634
|
+
function renderModelSchema(schema, modelName) {
|
|
3635
|
+
const m = schema.models.get(modelName);
|
|
3636
|
+
if (!m) return null;
|
|
3637
|
+
const lines = m.fields.filter((f) => !f.isId).map((f) => {
|
|
3638
|
+
const req = isMandatory(f) ? "REQUIRED" : f.hasDefault ? "optional (has default)" : "optional";
|
|
3639
|
+
const scopeNote = schema.scopeField && f.name === schema.scopeField ? ' \u2190 scope/tenant field \u2014 the SDK does NOT auto-fill it; set it explicitly (use a { "_ref": "..." } to the scope entity)' : "";
|
|
3640
|
+
return ` - ${f.name}: ${f.type} (${req})${scopeNote}`;
|
|
3641
|
+
});
|
|
3642
|
+
return `Live schema for "${modelName}" (from the SDK /discover endpoint \u2014 this is the SOURCE OF TRUTH; it overrides the entity audit when they disagree):
|
|
3643
|
+
${lines.join("\n")}
|
|
3644
|
+
|
|
3645
|
+
Every REQUIRED field above must be present on every record. Do not send fields that are not listed.`;
|
|
3646
|
+
}
|
|
3647
|
+
function validateRecipeAgainstSchema(recipe, schema) {
|
|
3648
|
+
const problems = [];
|
|
3649
|
+
const declaredAliases = /* @__PURE__ */ new Set();
|
|
3650
|
+
for (const records of Object.values(recipe)) {
|
|
3651
|
+
for (const rec of records) {
|
|
3652
|
+
if (typeof rec._alias === "string") declaredAliases.add(rec._alias);
|
|
3653
|
+
}
|
|
3654
|
+
}
|
|
3655
|
+
for (const [modelName, records] of Object.entries(recipe)) {
|
|
3656
|
+
const model = schema.models.get(modelName);
|
|
3657
|
+
records.forEach((record, recordIndex) => {
|
|
3658
|
+
if (model) {
|
|
3659
|
+
for (const field of model.fields) {
|
|
3660
|
+
if (!isMandatory(field)) continue;
|
|
3661
|
+
const value = record[field.name];
|
|
3662
|
+
if (value === void 0 || value === null) {
|
|
3663
|
+
problems.push({
|
|
3664
|
+
model: modelName,
|
|
3665
|
+
recordIndex,
|
|
3666
|
+
message: `missing required field "${field.name}" (${field.type})`
|
|
3667
|
+
});
|
|
3668
|
+
}
|
|
3669
|
+
}
|
|
3670
|
+
}
|
|
3671
|
+
const refs = /* @__PURE__ */ new Set();
|
|
3672
|
+
collectRefs2(record, refs);
|
|
3673
|
+
for (const alias of refs) {
|
|
3674
|
+
if (!declaredAliases.has(alias)) {
|
|
3675
|
+
problems.push({
|
|
3676
|
+
model: modelName,
|
|
3677
|
+
recordIndex,
|
|
3678
|
+
message: `_ref points at "${alias}", which no record in this payload declares with an _alias`
|
|
3679
|
+
});
|
|
3680
|
+
}
|
|
3681
|
+
}
|
|
3682
|
+
});
|
|
3683
|
+
}
|
|
3684
|
+
return problems;
|
|
3685
|
+
}
|
|
3686
|
+
function collectRefs2(value, out) {
|
|
3687
|
+
if (Array.isArray(value)) {
|
|
3688
|
+
for (const v of value) collectRefs2(v, out);
|
|
3689
|
+
} else if (value !== null && typeof value === "object") {
|
|
3690
|
+
const obj = value;
|
|
3691
|
+
if (typeof obj._ref === "string") {
|
|
3692
|
+
out.add(obj._ref);
|
|
3693
|
+
return;
|
|
3694
|
+
}
|
|
3695
|
+
for (const v of Object.values(obj)) collectRefs2(v, out);
|
|
3696
|
+
}
|
|
3697
|
+
}
|
|
3698
|
+
function formatValidationProblems(problems) {
|
|
3699
|
+
const byModel = /* @__PURE__ */ new Map();
|
|
3700
|
+
for (const p10 of problems) {
|
|
3701
|
+
const key = `${p10.model}[${p10.recordIndex}]`;
|
|
3702
|
+
if (!byModel.has(key)) byModel.set(key, []);
|
|
3703
|
+
byModel.get(key).push(p10.message);
|
|
3704
|
+
}
|
|
3705
|
+
return [...byModel.entries()].map(([key, msgs]) => `${key}: ${msgs.join("; ")}`).join("\n");
|
|
3706
|
+
}
|
|
3707
|
+
var init_discover_schema = __esm({
|
|
3708
|
+
"src/agents/04-recipe-builder/discover-schema.ts"() {
|
|
3709
|
+
"use strict";
|
|
3710
|
+
init_esm_shims();
|
|
3711
|
+
init_http_client();
|
|
3712
|
+
}
|
|
3713
|
+
});
|
|
3714
|
+
|
|
3588
3715
|
// src/agents/04-recipe-builder/phases/failure-classifier.ts
|
|
3589
3716
|
import { Output as Output2, generateText as generateText2 } from "ai";
|
|
3590
3717
|
import { z as z16 } from "zod";
|
|
@@ -3619,6 +3746,8 @@ ${args.validRefAliases?.trim() || "(none \u2014 this is a root entity with no pa
|
|
|
3619
3746
|
What the entity audit recorded about how "${args.entityName}" is created:
|
|
3620
3747
|
${args.entityAudit?.trim() || "(not available)"}
|
|
3621
3748
|
|
|
3749
|
+
${args.liveSchema?.trim() || "(no live schema available \u2014 the /discover endpoint was unreachable)"}
|
|
3750
|
+
|
|
3622
3751
|
Test data sent:
|
|
3623
3752
|
${JSON.stringify(args.recipe, null, 2)}
|
|
3624
3753
|
|
|
@@ -3680,7 +3809,7 @@ function summarizeEntityAudit(model) {
|
|
|
3680
3809
|
if (model.side_effects?.length) parts.push(`side effects: ${model.side_effects.join(", ")}`);
|
|
3681
3810
|
return parts.join("; ");
|
|
3682
3811
|
}
|
|
3683
|
-
async function proposeRecipeData(entityName, entityIndex, totalEntities, model, outputDir, _projectRoot, completedEntities) {
|
|
3812
|
+
async function proposeRecipeData(entityName, entityIndex, totalEntities, model, outputDir, _projectRoot, completedEntities, schemaSpec) {
|
|
3684
3813
|
let result;
|
|
3685
3814
|
const { logger, onStepFinish } = buildDefaultStepLogger(`propose:${entityName}`, 20);
|
|
3686
3815
|
const finishTool = tool14({
|
|
@@ -3698,10 +3827,14 @@ async function proposeRecipeData(entityName, entityIndex, totalEntities, model,
|
|
|
3698
3827
|
|
|
3699
3828
|
Read scenarios.md and entity-audit.md from the output directory. Design records that match the scenario data.
|
|
3700
3829
|
|
|
3830
|
+
${schemaSpec ? `${schemaSpec}
|
|
3831
|
+
` : ""}
|
|
3701
3832
|
${completedAliases ? `Already completed entities (use _ref to reference their aliases):
|
|
3702
3833
|
${completedAliases}
|
|
3703
3834
|
` : "This is a root entity \u2014 no parent references needed."}
|
|
3704
3835
|
|
|
3836
|
+
Produce records for "${entityName}" ONLY \u2014 one object per record. Do not include records for other models; the tool assembles parent entities automatically.
|
|
3837
|
+
|
|
3705
3838
|
Call finish with the JSON array of records.`;
|
|
3706
3839
|
const readOutputTool = buildReadFileTool(outputDir);
|
|
3707
3840
|
await runAgent(
|
|
@@ -3723,7 +3856,7 @@ Call finish with the JSON array of records.`;
|
|
|
3723
3856
|
logger.summary();
|
|
3724
3857
|
return result ?? [];
|
|
3725
3858
|
}
|
|
3726
|
-
async function reviseRecipeData(entityName, entityIndex, totalEntities, current, feedback, model, outputDir, completedEntities) {
|
|
3859
|
+
async function reviseRecipeData(entityName, entityIndex, totalEntities, current, feedback, model, outputDir, completedEntities, schemaSpec) {
|
|
3727
3860
|
let revised;
|
|
3728
3861
|
const finishTool = tool14({
|
|
3729
3862
|
description: "Submit the fixed recipe data.",
|
|
@@ -3747,11 +3880,12 @@ ${completedAliases}
|
|
|
3747
3880
|
systemPrompt: `You are fixing recipe data based on user feedback (or a validation failure). Read the error, the current data, and the user's feedback. Read scenarios.md and entity-audit.md if needed. Fix the data and call finish.
|
|
3748
3881
|
|
|
3749
3882
|
Rules:
|
|
3883
|
+
- Return records for "${entityName}" ONLY \u2014 a flat JSON array, one object per record. NEVER group records by model name or nest other models (Client/User/etc.) inside this array. The tool assembles parent entities into the request automatically; your job is only this one entity's rows.
|
|
3750
3884
|
- _alias fields must be unique identifiers (e.g., "card_1", "transaction_1")
|
|
3751
3885
|
- _ref fields must reference an alias that ALREADY EXISTS on a parent entity \u2014 see the list of valid targets below. Never invent a _ref to an alias that isn't listed.
|
|
3752
3886
|
- If the error says "references unknown alias(es): X", a _ref points at "X" but nothing being created declares it. Correct that _ref to one of the valid targets listed below (it's usually a typo, e.g. "users_1" vs "user_1"), or drop the reference if the field is optional. Do NOT leave a _ref pointing at an alias that isn't in the valid targets list.
|
|
3753
3887
|
- Read scenarios.md to verify you're using correct alias names from parent entities
|
|
3754
|
-
- Field names must match the entity's schema from entity-audit.md`,
|
|
3888
|
+
- Field names and required fields must match the live schema below when present (it is the source of truth), otherwise the entity's schema from entity-audit.md`,
|
|
3755
3889
|
model,
|
|
3756
3890
|
maxSteps: 15,
|
|
3757
3891
|
tools: (_heartbeat) => ({
|
|
@@ -3769,6 +3903,8 @@ What's wrong / what to change:
|
|
|
3769
3903
|
${feedback}
|
|
3770
3904
|
|
|
3771
3905
|
${aliasBlock}
|
|
3906
|
+
${schemaSpec ? `${schemaSpec}
|
|
3907
|
+
` : ""}
|
|
3772
3908
|
Read scenarios.md and entity-audit.md to understand the correct aliases and schema. Apply the change and call finish.`,
|
|
3773
3909
|
() => revised
|
|
3774
3910
|
);
|
|
@@ -3843,7 +3979,7 @@ Read the creation file from the project to understand the existing service/funct
|
|
|
3843
3979
|
logger.summary();
|
|
3844
3980
|
return result ?? "No instructions generated. Check the entity audit for creation_file and creation_function.";
|
|
3845
3981
|
}
|
|
3846
|
-
async function reviewRecipeData(entityName, entityIndex, totalEntities, proposed, model, outputDir, completedEntities) {
|
|
3982
|
+
async function reviewRecipeData(entityName, entityIndex, totalEntities, proposed, model, outputDir, completedEntities, schemaSpec) {
|
|
3847
3983
|
p5.log.info(
|
|
3848
3984
|
`Legend for recipe fields:
|
|
3849
3985
|
_alias \u2014 Internal ID used to reference this record from other entities (e.g., { "_ref": "org_1" })
|
|
@@ -3898,7 +4034,8 @@ async function reviewRecipeData(entityName, entityIndex, totalEntities, proposed
|
|
|
3898
4034
|
feedback.trim(),
|
|
3899
4035
|
model,
|
|
3900
4036
|
outputDir,
|
|
3901
|
-
completedEntities
|
|
4037
|
+
completedEntities,
|
|
4038
|
+
schemaSpec
|
|
3902
4039
|
);
|
|
3903
4040
|
}
|
|
3904
4041
|
}
|
|
@@ -3923,7 +4060,8 @@ async function promptOnFailure(entityName, errorBody, ctx, phase, httpStatus) {
|
|
|
3923
4060
|
error: errorBody,
|
|
3924
4061
|
recipe: ctx.recipe,
|
|
3925
4062
|
validRefAliases: ctx.validRefAliases,
|
|
3926
|
-
entityAudit: ctx.entityAudit
|
|
4063
|
+
entityAudit: ctx.entityAudit,
|
|
4064
|
+
liveSchema: ctx.liveSchema
|
|
3927
4065
|
});
|
|
3928
4066
|
if (ctx.budget.attempts < MAX_AUTOFIX_ATTEMPTS) {
|
|
3929
4067
|
ctx.budget.attempts++;
|
|
@@ -3961,10 +4099,22 @@ async function promptOnFailure(entityName, errorBody, ctx, phase, httpStatus) {
|
|
|
3961
4099
|
if (p5.isCancel(fb)) throw new Error("Entity loop cancelled");
|
|
3962
4100
|
return { feedback: `${fb.trim()}${errorContext}` };
|
|
3963
4101
|
}
|
|
3964
|
-
async function testUpDown(entityName, entityIndex, totalEntities, sdkConfig, recipe, grounding) {
|
|
4102
|
+
async function testUpDown(entityName, entityIndex, totalEntities, sdkConfig, recipe, grounding, discoverSchema) {
|
|
3965
4103
|
p5.log.info(`Let's verify this factory works. We'll send a test request to create ${entityName}, then check the database.`);
|
|
3966
4104
|
const failureCtx = { ...grounding, recipe };
|
|
3967
4105
|
while (true) {
|
|
4106
|
+
if (discoverSchema) {
|
|
4107
|
+
const problems = validateRecipeAgainstSchema(recipe, discoverSchema);
|
|
4108
|
+
if (problems.length > 0) {
|
|
4109
|
+
const errorBody = `Recipe failed local schema validation against /discover (not sent to the server):
|
|
4110
|
+
${formatValidationProblems(problems)}`;
|
|
4111
|
+
p5.log.error(errorBody);
|
|
4112
|
+
const action = await promptOnFailure(entityName, errorBody, failureCtx, "create");
|
|
4113
|
+
if (action === "skip") return "skip";
|
|
4114
|
+
if (action === "retry") continue;
|
|
4115
|
+
return action;
|
|
4116
|
+
}
|
|
4117
|
+
}
|
|
3968
4118
|
const testRunId = `test-${Date.now()}`;
|
|
3969
4119
|
p5.log.step(`[${entityIndex + 1}/${totalEntities}] Sending UP request...`);
|
|
3970
4120
|
let upResult;
|
|
@@ -4023,6 +4173,15 @@ ${formatException(err)}`);
|
|
|
4023
4173
|
async function runEntityLoop(state, models, model, projectRoot, outputDir, nonInteractive) {
|
|
4024
4174
|
const total = state.entityOrder.length;
|
|
4025
4175
|
const modelMap = new Map(models.map((m) => [m.name, m]));
|
|
4176
|
+
async function loadLiveSchema(name) {
|
|
4177
|
+
if (!state.sdkEndpointUrl || !state.sharedSecret) return { schema: null };
|
|
4178
|
+
const schema = await fetchDiscoverSchema({
|
|
4179
|
+
endpointUrl: state.sdkEndpointUrl,
|
|
4180
|
+
sharedSecret: state.sharedSecret
|
|
4181
|
+
});
|
|
4182
|
+
if (!schema) return { schema: null };
|
|
4183
|
+
return { schema, spec: renderModelSchema(schema, name) ?? void 0 };
|
|
4184
|
+
}
|
|
4026
4185
|
p5.log.info(
|
|
4027
4186
|
`We're going to set up your test data factories one entity at a time. Each factory teaches the Autonoma SDK how to create and tear down a specific type of record in YOUR database, using YOUR existing service functions.
|
|
4028
4187
|
|
|
@@ -4052,6 +4211,7 @@ async function runEntityLoop(state, models, model, projectRoot, outputDir, nonIn
|
|
|
4052
4211
|
const depInfo = isRoot ? "This is a root entity \u2014 no dependencies." : `This depends on: ${auditModel.created_by.map((d) => d.owner).join(", ")}`;
|
|
4053
4212
|
p5.log.step(`[${i + 1}/${total}] ${entityName}`);
|
|
4054
4213
|
p5.log.info(depInfo);
|
|
4214
|
+
const { spec: recipeSchemaSpec } = await loadLiveSchema(entityName);
|
|
4055
4215
|
let recipeData = existing?.recipeData;
|
|
4056
4216
|
if (!recipeData || existing?.status === "pending") {
|
|
4057
4217
|
recipeData = await proposeRecipeData(
|
|
@@ -4061,11 +4221,12 @@ async function runEntityLoop(state, models, model, projectRoot, outputDir, nonIn
|
|
|
4061
4221
|
model,
|
|
4062
4222
|
outputDir,
|
|
4063
4223
|
projectRoot,
|
|
4064
|
-
state.entities
|
|
4224
|
+
state.entities,
|
|
4225
|
+
recipeSchemaSpec
|
|
4065
4226
|
);
|
|
4066
4227
|
}
|
|
4067
4228
|
if (!nonInteractive) {
|
|
4068
|
-
recipeData = await reviewRecipeData(entityName, i, total, recipeData, model, outputDir, state.entities);
|
|
4229
|
+
recipeData = await reviewRecipeData(entityName, i, total, recipeData, model, outputDir, state.entities, recipeSchemaSpec);
|
|
4069
4230
|
}
|
|
4070
4231
|
state.entities[entityName] = {
|
|
4071
4232
|
entityName,
|
|
@@ -4160,17 +4321,19 @@ Saved to: ${join23(outputDir, "autonoma-config.json")}`,
|
|
|
4160
4321
|
endpointUrl: state.sdkEndpointUrl,
|
|
4161
4322
|
sharedSecret: state.sharedSecret
|
|
4162
4323
|
};
|
|
4324
|
+
const { schema: discoverSchema, spec: liveSchemaSpec } = await loadLiveSchema(entityName);
|
|
4163
4325
|
const autofixBudget = { attempts: 0 };
|
|
4164
4326
|
const grounding = {
|
|
4165
4327
|
model,
|
|
4166
4328
|
budget: autofixBudget,
|
|
4167
4329
|
validRefAliases: summarizeCompletedAliases(state.entities, entityName),
|
|
4168
|
-
entityAudit: summarizeEntityAudit(models.find((m) => m.name === entityName))
|
|
4330
|
+
entityAudit: summarizeEntityAudit(models.find((m) => m.name === entityName)),
|
|
4331
|
+
liveSchema: liveSchemaSpec
|
|
4169
4332
|
};
|
|
4170
4333
|
let testDone = false;
|
|
4171
4334
|
while (!testDone) {
|
|
4172
4335
|
const singleRecipe = buildSingleEntityRecipe(entityName, models, state.entityOrder, state.entities);
|
|
4173
|
-
const testResult = await testUpDown(entityName, i, total, sdkConfig, singleRecipe, grounding);
|
|
4336
|
+
const testResult = await testUpDown(entityName, i, total, sdkConfig, singleRecipe, grounding, discoverSchema ?? void 0);
|
|
4174
4337
|
if (testResult === "success") {
|
|
4175
4338
|
state.entities[entityName].status = "tested-down";
|
|
4176
4339
|
p5.log.success(`[${i + 1}/${total}] ${entityName} \u2014 factory verified`);
|
|
@@ -4190,7 +4353,8 @@ Saved to: ${join23(outputDir, "autonoma-config.json")}`,
|
|
|
4190
4353
|
testResult.feedback,
|
|
4191
4354
|
model,
|
|
4192
4355
|
outputDir,
|
|
4193
|
-
state.entities
|
|
4356
|
+
state.entities,
|
|
4357
|
+
liveSchemaSpec
|
|
4194
4358
|
);
|
|
4195
4359
|
state.entities[entityName].recipeData = revised;
|
|
4196
4360
|
await saveRecipeState(outputDir, state);
|
|
@@ -4219,6 +4383,7 @@ var init_entity_loop = __esm({
|
|
|
4219
4383
|
init_notify();
|
|
4220
4384
|
init_recipe();
|
|
4221
4385
|
init_http_client();
|
|
4386
|
+
init_discover_schema();
|
|
4222
4387
|
init_failure_classifier();
|
|
4223
4388
|
PROPOSAL_PROMPT = `You are a recipe data designer. Given an entity from the entity audit and the scenario data, produce a JSON array of records for this entity.
|
|
4224
4389
|
|
|
@@ -4255,7 +4420,7 @@ When done, call finish with the instructions text.`;
|
|
|
4255
4420
|
import * as p6 from "@clack/prompts";
|
|
4256
4421
|
import { tool as tool15 } from "ai";
|
|
4257
4422
|
import { z as z18 } from "zod";
|
|
4258
|
-
async function reviseFullRecipe(current, feedback, model, outputDir, entityOrder) {
|
|
4423
|
+
async function reviseFullRecipe(current, feedback, model, outputDir, entityOrder, schemaSpec) {
|
|
4259
4424
|
let revised;
|
|
4260
4425
|
const finishTool = tool15({
|
|
4261
4426
|
description: "Submit the revised full recipe: an object mapping each entity name to its array of records.",
|
|
@@ -4281,9 +4446,11 @@ Rules:
|
|
|
4281
4446
|
- Apply the user's feedback across whatever entities it touches.
|
|
4282
4447
|
- Keep _ref values pointing to aliases that actually exist in the recipe. Never invent a _ref to a missing alias.
|
|
4283
4448
|
- Entities are created in this order (parents first): ${entityOrder.join(" \u2192 ")}. A record may only _ref an alias declared by an entity earlier in that order.
|
|
4284
|
-
- Field names
|
|
4449
|
+
- Field names, types, and required fields must match the live schema below when present (it is the source of truth), otherwise the schema in entity-audit.md.
|
|
4285
4450
|
- Read scenarios.md and entity-audit.md from the output directory as needed.
|
|
4286
|
-
|
|
4451
|
+
${schemaSpec ? `
|
|
4452
|
+
${schemaSpec}
|
|
4453
|
+
` : ""}
|
|
4287
4454
|
Return the COMPLETE revised recipe (all entities, not just the changed ones) via finish.`,
|
|
4288
4455
|
model,
|
|
4289
4456
|
maxSteps: 20,
|
|
@@ -4345,8 +4512,19 @@ async function runFullValidation(state, _models, outputDir, model) {
|
|
|
4345
4512
|
endpointUrl: state.sdkEndpointUrl,
|
|
4346
4513
|
sharedSecret: state.sharedSecret ?? ""
|
|
4347
4514
|
};
|
|
4515
|
+
const discoverSchema = await fetchDiscoverSchema(sdkConfig);
|
|
4516
|
+
const fullSchemaSpec = discoverSchema ? state.entityOrder.map((name) => renderModelSchema(discoverSchema, name)).filter(Boolean).join("\n\n") || void 0 : void 0;
|
|
4348
4517
|
let fullRecipe = buildFullRecipe(state.entityOrder, state.entities);
|
|
4349
4518
|
while (true) {
|
|
4519
|
+
if (discoverSchema) {
|
|
4520
|
+
const problems = validateRecipeAgainstSchema(fullRecipe, discoverSchema);
|
|
4521
|
+
if (problems.length > 0) {
|
|
4522
|
+
p6.log.warn(
|
|
4523
|
+
`Heads up \u2014 the recipe has likely schema problems (from /discover); the full UP may fail:
|
|
4524
|
+
${formatValidationProblems(problems)}`
|
|
4525
|
+
);
|
|
4526
|
+
}
|
|
4527
|
+
}
|
|
4350
4528
|
const testRunId = `full-${Date.now()}`;
|
|
4351
4529
|
p6.log.step(`[Full validation] Creating all ${total} entities...`);
|
|
4352
4530
|
let upResult;
|
|
@@ -4420,7 +4598,7 @@ ${formatException(err)}`);
|
|
|
4420
4598
|
return false;
|
|
4421
4599
|
}
|
|
4422
4600
|
p6.log.info("Revising the full recipe based on your feedback...");
|
|
4423
|
-
const revised = await reviseFullRecipe(fullRecipe, feedback.trim(), model, outputDir, state.entityOrder);
|
|
4601
|
+
const revised = await reviseFullRecipe(fullRecipe, feedback.trim(), model, outputDir, state.entityOrder, fullSchemaSpec);
|
|
4424
4602
|
if (!revised) {
|
|
4425
4603
|
p6.log.warn("Couldn't revise automatically. Edit recipe.json manually and re-run with --resume.");
|
|
4426
4604
|
return false;
|
|
@@ -4447,6 +4625,7 @@ var init_full_validation = __esm({
|
|
|
4447
4625
|
init_state();
|
|
4448
4626
|
init_recipe();
|
|
4449
4627
|
init_http_client();
|
|
4628
|
+
init_discover_schema();
|
|
4450
4629
|
}
|
|
4451
4630
|
});
|
|
4452
4631
|
|