@saltcorn/copilot 0.8.1 → 0.8.3
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/actions/generate-page.js +6 -2
- package/actions/generate-tables.js +70 -6
- package/actions/generate-workflow.js +54 -3
- package/agent-skills/pagegen.js +171 -59
- package/agent-skills/registry-editor.js +30 -2
- package/agent-skills/triggergen.js +1 -5
- package/agent-skills/viewgen.js +49 -7
- package/app-constructor/common.js +7 -1
- package/app-constructor/errors.js +749 -61
- package/app-constructor/feedback-action.js +62 -60
- package/app-constructor/feedback.js +1294 -67
- package/app-constructor/fixed-prompts.js +829 -0
- package/app-constructor/phases.js +1485 -0
- package/app-constructor/prompt-generator.js +587 -0
- package/app-constructor/requirements.js +171 -50
- package/app-constructor/research.js +350 -0
- package/app-constructor/run_task.js +234 -73
- package/app-constructor/schema.js +173 -169
- package/app-constructor/tasks.js +96 -537
- package/app-constructor/tools.js +17 -4
- package/app-constructor/view.js +314 -54
- package/builder-gen.js +90 -41
- package/builder-schema.js +6 -0
- package/copilot-as-agent.js +1 -0
- package/index.js +0 -1
- package/js-code-gen.js +1 -0
- package/package.json +1 -1
- package/relation-paths.js +73 -40
- package/standard-prompt.js +1 -0
- package/user-copilot.js +2 -0
- package/workflow-gen.js +1 -0
- package/app-constructor/prompts.js +0 -120
- package/chat-copilot.js +0 -770
|
@@ -0,0 +1,587 @@
|
|
|
1
|
+
const MetaData = require("@saltcorn/data/models/metadata");
|
|
2
|
+
const Table = require("@saltcorn/data/models/table");
|
|
3
|
+
const View = require("@saltcorn/data/models/view");
|
|
4
|
+
const Page = require("@saltcorn/data/models/page");
|
|
5
|
+
const Trigger = require("@saltcorn/data/models/trigger");
|
|
6
|
+
const Plugin = require("@saltcorn/data/models/plugin");
|
|
7
|
+
const { getState } = require("@saltcorn/data/db/state");
|
|
8
|
+
const {
|
|
9
|
+
saltcorn_description,
|
|
10
|
+
implementation_rules,
|
|
11
|
+
fieldview_selection_rules,
|
|
12
|
+
plugin_type_instruction,
|
|
13
|
+
data_model_type_instruction,
|
|
14
|
+
feature_type_instruction,
|
|
15
|
+
task_planning_rules,
|
|
16
|
+
task_planning_closing,
|
|
17
|
+
error_fix_closing,
|
|
18
|
+
feedback_task_overrides,
|
|
19
|
+
feature_exec_rules,
|
|
20
|
+
data_model_exec_rules,
|
|
21
|
+
req_gen_rules,
|
|
22
|
+
phase_gen_rules,
|
|
23
|
+
phase_scope_rule,
|
|
24
|
+
no_roles_table_rule,
|
|
25
|
+
exec_tool_call_rule,
|
|
26
|
+
exec_schema_rule_plugin,
|
|
27
|
+
exec_schema_rule_data_model,
|
|
28
|
+
exec_schema_rule_feature,
|
|
29
|
+
feedback_analyse_decision,
|
|
30
|
+
research_questions_rules,
|
|
31
|
+
} = require("./fixed-prompts");
|
|
32
|
+
const { TaskType } = require("./common");
|
|
33
|
+
|
|
34
|
+
const installed_plugins_list = (installedNames, storePlugins = []) => {
|
|
35
|
+
const state = getState();
|
|
36
|
+
const storeByName = Object.fromEntries(storePlugins.map((p) => [p.name, p]));
|
|
37
|
+
const lines = [];
|
|
38
|
+
for (const name of installedNames) {
|
|
39
|
+
const resolvedName = state.plugin_module_names[name] || name;
|
|
40
|
+
const mod = state.plugins[resolvedName];
|
|
41
|
+
const storePlugin = storeByName[name];
|
|
42
|
+
const app_constructor_rules = mod?.app_constructor_rules;
|
|
43
|
+
const description = mod?.description || storePlugin?.description;
|
|
44
|
+
const contents = storePlugin?.contents;
|
|
45
|
+
if (!description && !contents && !app_constructor_rules) continue;
|
|
46
|
+
let line = `### ${name}`;
|
|
47
|
+
if (description) line += `\n${description}`;
|
|
48
|
+
if (contents) line += `\n${contents}`;
|
|
49
|
+
if (app_constructor_rules) line += `\n${app_constructor_rules}`;
|
|
50
|
+
lines.push(line);
|
|
51
|
+
}
|
|
52
|
+
if (!lines.length) return "";
|
|
53
|
+
return (
|
|
54
|
+
`The following plugins are already installed and their viewtemplates, field types, and actions are available for use:\n\n` +
|
|
55
|
+
lines.join("\n\n")
|
|
56
|
+
);
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const research_answers_section = (text) =>
|
|
60
|
+
text
|
|
61
|
+
? `\nThe user was asked clarifying questions about the application. Here are the questions and their answers:\n\n${text}\n`
|
|
62
|
+
: "";
|
|
63
|
+
|
|
64
|
+
const available_plugins_list = (storePlugins, installedNames) => {
|
|
65
|
+
const uninstalled = storePlugins.filter((p) => !installedNames.has(p.name));
|
|
66
|
+
if (!uninstalled.length) return "";
|
|
67
|
+
const lines = uninstalled.map((p) => {
|
|
68
|
+
let line = `### ${p.name}`;
|
|
69
|
+
if (p.description) line += `\n${p.description}`;
|
|
70
|
+
if (p.contents) line += `\n${p.contents}`;
|
|
71
|
+
return line;
|
|
72
|
+
});
|
|
73
|
+
return (
|
|
74
|
+
`The following plugins are available in the Saltcorn store but not yet installed. ` +
|
|
75
|
+
`If a task requires functionality provided by one of these plugins (e.g. a specific view template, field type, or action), ` +
|
|
76
|
+
`include an explicit "Install plugin <name>" task before it with the exact plugin name as listed here. ` +
|
|
77
|
+
`The executor will use that name directly without needing to look it up.\n\n` +
|
|
78
|
+
lines.join("\n\n")
|
|
79
|
+
);
|
|
80
|
+
};
|
|
81
|
+
const format_table_entry = (table) => {
|
|
82
|
+
const fieldLines = (table.fields || []).map((f) => {
|
|
83
|
+
const attrs = [];
|
|
84
|
+
if (f.required) attrs.push("NOT NULL");
|
|
85
|
+
if (f.is_unique) attrs.push("unique");
|
|
86
|
+
const def = f.attributes?.default;
|
|
87
|
+
if (f.required && def !== undefined && def !== null && def !== "")
|
|
88
|
+
attrs.push(`default: ${JSON.stringify(def)}`);
|
|
89
|
+
const attrStr = attrs.length ? ` (${attrs.join(", ")})` : "";
|
|
90
|
+
return ` * ${f.name} with type: ${f.pretty_type}${attrStr}.${
|
|
91
|
+
f.description ? ` ${f.description}` : ""
|
|
92
|
+
}`;
|
|
93
|
+
});
|
|
94
|
+
return `${table.name}${
|
|
95
|
+
table.description ? `: ${table.description}.` : "."
|
|
96
|
+
} Contains the following fields:\n${fieldLines.join("\n")}`;
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const existing_entities_list = ({ views, triggers, pages, tableById = {} }) => {
|
|
100
|
+
const sections = [];
|
|
101
|
+
if (views.length)
|
|
102
|
+
sections.push(
|
|
103
|
+
`The following views are already implemented — do NOT plan tasks to create them. ` +
|
|
104
|
+
`If you find yourself constructing a new view name that avoids a collision with an existing one ` +
|
|
105
|
+
`(e.g. by prepending "my_", "user_", or "filtered_"), that is a signal you should use the existing view instead:\n` +
|
|
106
|
+
views
|
|
107
|
+
.map((v) => {
|
|
108
|
+
const tablePart =
|
|
109
|
+
v.table?.name ||
|
|
110
|
+
(v.table_id && tableById[v.table_id]) ||
|
|
111
|
+
v.exttable_name;
|
|
112
|
+
return `- ${v.name} (${v.viewtemplate}${
|
|
113
|
+
tablePart ? ` on ${tablePart}` : ""
|
|
114
|
+
})`;
|
|
115
|
+
})
|
|
116
|
+
.join("\n")
|
|
117
|
+
);
|
|
118
|
+
if (triggers.length)
|
|
119
|
+
sections.push(
|
|
120
|
+
`The following triggers are already implemented — do NOT plan tasks to create them:\n` +
|
|
121
|
+
triggers
|
|
122
|
+
.map(
|
|
123
|
+
(t) =>
|
|
124
|
+
`- ${t.name} (${t.action}${
|
|
125
|
+
t.when_trigger ? `, ${t.when_trigger}` : ""
|
|
126
|
+
})`
|
|
127
|
+
)
|
|
128
|
+
.join("\n")
|
|
129
|
+
);
|
|
130
|
+
if (pages.length)
|
|
131
|
+
sections.push(
|
|
132
|
+
`CRITICAL — the following pages already exist and MUST NOT be recreated. ` +
|
|
133
|
+
`Planning a task to create any page whose name appears in this list is a hard error. ` +
|
|
134
|
+
`Before adding any page task to your plan, check this list first. ` +
|
|
135
|
+
`If a requirement is served by one of these pages (even under a different name), reference the existing page by its exact name. ` +
|
|
136
|
+
`If you find yourself constructing a new name that avoids a collision ` +
|
|
137
|
+
`(e.g. by prepending "my_", appending "_v2", or changing a word), stop — use the existing page instead:\n` +
|
|
138
|
+
pages
|
|
139
|
+
.map(
|
|
140
|
+
(p) => `- ${p.name}${p.description ? ` — ${p.description}` : ""}`
|
|
141
|
+
)
|
|
142
|
+
.join("\n")
|
|
143
|
+
);
|
|
144
|
+
return sections.join("\n\n");
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
const flatTablesList = (allTables) =>
|
|
148
|
+
(allTables || []).map(format_table_entry).join("\n\n");
|
|
149
|
+
|
|
150
|
+
const buildGroupedTablesSection = async (allTables, currentPhaseIdx) => {
|
|
151
|
+
if (!allTables.length) return "";
|
|
152
|
+
|
|
153
|
+
const records = await MetaData.find({
|
|
154
|
+
type: "CopilotConstructMgr",
|
|
155
|
+
name: "table_phase",
|
|
156
|
+
});
|
|
157
|
+
const tablePhaseMap = {};
|
|
158
|
+
for (const r of records) tablePhaseMap[r.body.table_name] = r.body;
|
|
159
|
+
|
|
160
|
+
const phaseGroups = {};
|
|
161
|
+
const ungrouped = [];
|
|
162
|
+
for (const table of allTables) {
|
|
163
|
+
const assoc = tablePhaseMap[table.name];
|
|
164
|
+
if (assoc !== undefined) {
|
|
165
|
+
const idx = assoc.phase_idx;
|
|
166
|
+
if (!phaseGroups[idx])
|
|
167
|
+
phaseGroups[idx] = { phase_name: assoc.phase_name, tables: [] };
|
|
168
|
+
phaseGroups[idx].tables.push(table);
|
|
169
|
+
} else {
|
|
170
|
+
ungrouped.push(table);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const formatTables = (tables) => tables.map(format_table_entry).join("\n\n");
|
|
175
|
+
|
|
176
|
+
const sections = [];
|
|
177
|
+
const sortedIdxs = Object.keys(phaseGroups)
|
|
178
|
+
.map(Number)
|
|
179
|
+
.sort((a, b) => a - b);
|
|
180
|
+
for (const idx of sortedIdxs) {
|
|
181
|
+
const g = phaseGroups[idx];
|
|
182
|
+
const label = g.phase_name
|
|
183
|
+
? `Phase ${idx + 1}: ${g.phase_name}`
|
|
184
|
+
: `Phase ${idx + 1}`;
|
|
185
|
+
sections.push(
|
|
186
|
+
`--- Tables from ${label}${
|
|
187
|
+
idx === currentPhaseIdx ? " (current phase)" : ""
|
|
188
|
+
} ---\n\n${formatTables(g.tables)}`
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
if (ungrouped.length)
|
|
192
|
+
sections.push(
|
|
193
|
+
`--- Tables with no phase association ---\n\n${formatTables(ungrouped)}`
|
|
194
|
+
);
|
|
195
|
+
|
|
196
|
+
return (
|
|
197
|
+
"The database already contains the following tables, grouped by the phase that created them:\n\n" +
|
|
198
|
+
sections.join("\n\n") +
|
|
199
|
+
"\n\nAll tables listed above already exist — do NOT create or recreate any of them." +
|
|
200
|
+
" Only plan tasks for tables or fields genuinely missing from the requirements of this phase."
|
|
201
|
+
);
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Builds LLM prompts for every stage of the app-constructor pipeline.
|
|
206
|
+
* Always create via `PromptGenerator.createInstance()` — the constructor is private.
|
|
207
|
+
* All `*Prompt` methods return a ready-to-send string; they do not call the LLM.
|
|
208
|
+
*/
|
|
209
|
+
class PromptGenerator {
|
|
210
|
+
/**
|
|
211
|
+
* Factory — loads all shared context (spec, requirements, tables, plugins, research)
|
|
212
|
+
* once so every prompt method can use it without extra DB calls.
|
|
213
|
+
* @param {{ phase?: object|null }} [opts]
|
|
214
|
+
* @returns {Promise<PromptGenerator>}
|
|
215
|
+
*/
|
|
216
|
+
static async createInstance({ phase = null } = {}) {
|
|
217
|
+
const instance = new PromptGenerator();
|
|
218
|
+
|
|
219
|
+
instance.phase = phase;
|
|
220
|
+
instance.spec = await MetaData.findOne({
|
|
221
|
+
type: "CopilotConstructMgr",
|
|
222
|
+
name: "spec",
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
instance.allReqs = await MetaData.find({
|
|
226
|
+
type: "CopilotConstructMgr",
|
|
227
|
+
name: "requirement",
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
if (phase) {
|
|
231
|
+
instance.reqLines = (phase.requirements || [])
|
|
232
|
+
.map((r, i) => `${i + 1}. ${r.requirement} (priority ${r.priority})`)
|
|
233
|
+
.join("\n");
|
|
234
|
+
|
|
235
|
+
const allPhaseTasks = await MetaData.find({
|
|
236
|
+
type: "CopilotConstructMgr",
|
|
237
|
+
name: "task",
|
|
238
|
+
});
|
|
239
|
+
instance.existingDmNames = allPhaseTasks
|
|
240
|
+
.filter(
|
|
241
|
+
(t) =>
|
|
242
|
+
t.body?.phase_idx === phase.idx && t.body.task_type === "data_model"
|
|
243
|
+
)
|
|
244
|
+
.map((t) => t.body.name)
|
|
245
|
+
.filter(Boolean);
|
|
246
|
+
} else {
|
|
247
|
+
instance.reqLines = "";
|
|
248
|
+
instance.existingDmNames = [];
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
instance.allTables = await Table.find({});
|
|
252
|
+
instance.existingTablesSection = phase
|
|
253
|
+
? await buildGroupedTablesSection(instance.allTables, phase.idx)
|
|
254
|
+
: "";
|
|
255
|
+
|
|
256
|
+
const tableById = Object.fromEntries(
|
|
257
|
+
instance.allTables.map((t) => [t.id, t.name])
|
|
258
|
+
);
|
|
259
|
+
const [views, triggers, pages] = await Promise.all([
|
|
260
|
+
View.find({}),
|
|
261
|
+
Trigger.find({}),
|
|
262
|
+
Page.find({}),
|
|
263
|
+
]);
|
|
264
|
+
instance.entitiesSection = existing_entities_list({
|
|
265
|
+
views,
|
|
266
|
+
triggers,
|
|
267
|
+
pages,
|
|
268
|
+
tableById,
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
instance.installedNames = new Set();
|
|
272
|
+
instance.storePlugins = [];
|
|
273
|
+
instance.installedPluginsSection = "";
|
|
274
|
+
instance.pluginAvailabilitySections = [];
|
|
275
|
+
try {
|
|
276
|
+
const allInstalled = await Plugin.find({});
|
|
277
|
+
instance.installedNames = new Set(allInstalled.map((p) => p.name));
|
|
278
|
+
instance.storePlugins = (await Plugin.store_plugins_available()) || [];
|
|
279
|
+
instance.installedPluginsSection = installed_plugins_list(
|
|
280
|
+
instance.installedNames,
|
|
281
|
+
instance.storePlugins
|
|
282
|
+
);
|
|
283
|
+
const availableSection = available_plugins_list(
|
|
284
|
+
instance.storePlugins,
|
|
285
|
+
instance.installedNames
|
|
286
|
+
);
|
|
287
|
+
if (availableSection)
|
|
288
|
+
instance.pluginAvailabilitySections.push(availableSection);
|
|
289
|
+
if (instance.installedNames.size)
|
|
290
|
+
instance.pluginAvailabilitySections.push(
|
|
291
|
+
"The following plugins are already installed — do NOT install them again:\n" +
|
|
292
|
+
[...instance.installedNames].map((n) => `- ${n}`).join("\n")
|
|
293
|
+
);
|
|
294
|
+
} catch (_) {}
|
|
295
|
+
|
|
296
|
+
const { getResearchAnswersText } = require("./research");
|
|
297
|
+
instance.researchSection = research_answers_section(
|
|
298
|
+
await getResearchAnswersText()
|
|
299
|
+
);
|
|
300
|
+
|
|
301
|
+
return instance;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/** Prompt for the ask_questions LLM call — generates clarifying research questions from the spec. */
|
|
305
|
+
researchQuestionsPrompt() {
|
|
306
|
+
return [
|
|
307
|
+
research_questions_rules,
|
|
308
|
+
`Specification:\n${this.spec?.body?.specification}`,
|
|
309
|
+
"Now call the ask_questions tool with your questions.",
|
|
310
|
+
].join("\n\n");
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/** Prompt for the make_requirements LLM call — extracts requirements from the spec. */
|
|
314
|
+
requirementsPlanPrompt() {
|
|
315
|
+
const parts = [
|
|
316
|
+
"Generate the requirements for this application:",
|
|
317
|
+
this.spec?.body?.specification,
|
|
318
|
+
];
|
|
319
|
+
if (this.researchSection) parts.push(this.researchSection);
|
|
320
|
+
parts.push(
|
|
321
|
+
req_gen_rules.join("\n\n"),
|
|
322
|
+
"Now use the make_requirements tool to list the requirements for this software application."
|
|
323
|
+
);
|
|
324
|
+
return parts.filter(Boolean).join("\n\n");
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/** Prompt for the set_phases LLM call — groups requirements into delivery phases. */
|
|
328
|
+
phasesPlanPrompt() {
|
|
329
|
+
const parts = [
|
|
330
|
+
"Generate the development phases for this application. Each phase groups a set of\n" +
|
|
331
|
+
"requirements that belong together and form a coherent milestone.",
|
|
332
|
+
this.spec?.body?.specification,
|
|
333
|
+
];
|
|
334
|
+
if (this.researchSection) parts.push(this.researchSection);
|
|
335
|
+
parts.push(
|
|
336
|
+
phase_gen_rules.join("\n\n"),
|
|
337
|
+
"Now call the set_phases tool with your phases and their grouped requirements."
|
|
338
|
+
);
|
|
339
|
+
return parts.filter(Boolean).join("\n\n");
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Prompt for the plan_tasks LLM call — plans implementation tasks for one phase.
|
|
344
|
+
* Requires the instance to have been created with a `phase` object.
|
|
345
|
+
* @param {"plugin"|"data_model"|"feature"} taskType
|
|
346
|
+
*/
|
|
347
|
+
taskPlanPrompt(taskType) {
|
|
348
|
+
const parts = [
|
|
349
|
+
"You are planning the implementation tasks for a single phase of a Saltcorn application.",
|
|
350
|
+
`Application specification:\n${this.spec?.body?.specification}`,
|
|
351
|
+
];
|
|
352
|
+
if (this.researchSection) parts.push(this.researchSection);
|
|
353
|
+
parts.push(
|
|
354
|
+
`Phase: ${this.phase.name}\n${this.phase.description}`,
|
|
355
|
+
`Requirements for this phase:\n${this.reqLines}`,
|
|
356
|
+
phase_scope_rule,
|
|
357
|
+
no_roles_table_rule
|
|
358
|
+
);
|
|
359
|
+
switch (taskType) {
|
|
360
|
+
case "plugin":
|
|
361
|
+
parts.push(plugin_type_instruction.join("\n"));
|
|
362
|
+
parts.push(...this.pluginAvailabilitySections);
|
|
363
|
+
break;
|
|
364
|
+
case "data_model":
|
|
365
|
+
parts.push(data_model_type_instruction.join("\n"));
|
|
366
|
+
if (this.installedPluginsSection)
|
|
367
|
+
parts.push(this.installedPluginsSection);
|
|
368
|
+
break;
|
|
369
|
+
case "feature":
|
|
370
|
+
parts.push(feature_type_instruction.join("\n"));
|
|
371
|
+
if (this.installedPluginsSection)
|
|
372
|
+
parts.push(this.installedPluginsSection);
|
|
373
|
+
if (this.existingDmNames.length)
|
|
374
|
+
parts.push(
|
|
375
|
+
`The following data model tasks already exist for this phase` +
|
|
376
|
+
` and may be referenced in depends_on:\n${this.existingDmNames.join(
|
|
377
|
+
", "
|
|
378
|
+
)}`
|
|
379
|
+
);
|
|
380
|
+
if (this.entitiesSection) parts.push(this.entitiesSection);
|
|
381
|
+
parts.push(task_planning_rules.join("\n\n"));
|
|
382
|
+
break;
|
|
383
|
+
default:
|
|
384
|
+
throw new Error(`Unknown taskType: ${taskType}`);
|
|
385
|
+
}
|
|
386
|
+
if (this.existingTablesSection) parts.push(this.existingTablesSection);
|
|
387
|
+
parts.push(
|
|
388
|
+
task_planning_closing.join("\n\n"),
|
|
389
|
+
"Now call the plan_tasks tool with your tasks for this phase."
|
|
390
|
+
);
|
|
391
|
+
return parts.join("\n\n");
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* Prompt sent to the agent that executes a single task.
|
|
396
|
+
* @param {"plugin"|"data_model"|"feature"} taskType
|
|
397
|
+
* @param {string} description Task description from the plan.
|
|
398
|
+
*/
|
|
399
|
+
taskExecPrompt(taskType, description) {
|
|
400
|
+
const parts = [
|
|
401
|
+
`You are engaged in building the following application:\n\n${this.spec?.body?.specification}`,
|
|
402
|
+
taskType === TaskType.PLUGIN
|
|
403
|
+
? exec_schema_rule_plugin
|
|
404
|
+
: taskType === TaskType.DATA_MODEL
|
|
405
|
+
? exec_schema_rule_data_model
|
|
406
|
+
: exec_schema_rule_feature,
|
|
407
|
+
];
|
|
408
|
+
if (this.researchSection) parts.push(this.researchSection);
|
|
409
|
+
if (taskType === TaskType.PLUGIN)
|
|
410
|
+
parts.push(...this.pluginAvailabilitySections);
|
|
411
|
+
if (this.installedPluginsSection) parts.push(this.installedPluginsSection);
|
|
412
|
+
if (taskType === TaskType.FEATURE && this.allTables.length) {
|
|
413
|
+
parts.push(
|
|
414
|
+
`The database already contains the following tables:\n\n${flatTablesList(
|
|
415
|
+
this.allTables
|
|
416
|
+
)}`
|
|
417
|
+
);
|
|
418
|
+
}
|
|
419
|
+
if (taskType === TaskType.FEATURE) {
|
|
420
|
+
parts.push(implementation_rules.join("\n\n"));
|
|
421
|
+
parts.push(feature_exec_rules.join("\n\n"));
|
|
422
|
+
} else if (taskType === TaskType.DATA_MODEL) {
|
|
423
|
+
parts.push(data_model_exec_rules.join("\n\n"));
|
|
424
|
+
}
|
|
425
|
+
parts.push(exec_tool_call_rule);
|
|
426
|
+
if (description) parts.push(`Your task now is:\n${description}`);
|
|
427
|
+
return parts.filter(Boolean).join("\n\n");
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
/**
|
|
431
|
+
* Prompt for the self-healing plan_tasks / cannot_fix LLM call.
|
|
432
|
+
* @param {string} errorText JSON-stringified error object.
|
|
433
|
+
* @param {string} [entityConfigSection] Pre-built config excerpt for the affected entity.
|
|
434
|
+
*/
|
|
435
|
+
errorPrompt(errorText, entityConfigSection = "") {
|
|
436
|
+
const parts = [
|
|
437
|
+
`Fix a bug in the following Saltcorn application.\n\n${this.spec?.body?.specification}`,
|
|
438
|
+
];
|
|
439
|
+
if (this.researchSection) parts.push(this.researchSection);
|
|
440
|
+
if (this.allReqs.length)
|
|
441
|
+
parts.push(
|
|
442
|
+
`The existing application requirements are:\n\n` +
|
|
443
|
+
this.allReqs.map((r) => `* ${r.body.requirement}`).join("\n")
|
|
444
|
+
);
|
|
445
|
+
parts.push(saltcorn_description.join("\n\n"));
|
|
446
|
+
parts.push(implementation_rules.join("\n\n"));
|
|
447
|
+
parts.push(fieldview_selection_rules.join("\n\n"));
|
|
448
|
+
parts.push(
|
|
449
|
+
`The database has the following tables:\n\n${flatTablesList(
|
|
450
|
+
this.allTables
|
|
451
|
+
)}`
|
|
452
|
+
);
|
|
453
|
+
if (this.entitiesSection) parts.push(this.entitiesSection);
|
|
454
|
+
if (this.installedPluginsSection) parts.push(this.installedPluginsSection);
|
|
455
|
+
const availableSection = available_plugins_list(
|
|
456
|
+
this.storePlugins,
|
|
457
|
+
this.installedNames
|
|
458
|
+
);
|
|
459
|
+
if (availableSection) parts.push(availableSection);
|
|
460
|
+
parts.push(task_planning_rules.join("\n\n"));
|
|
461
|
+
parts.push(
|
|
462
|
+
`The following error occurred in the application:\n\`\`\`\n${errorText}\n\`\`\``
|
|
463
|
+
);
|
|
464
|
+
if (entityConfigSection) parts.push(entityConfigSection);
|
|
465
|
+
parts.push(task_planning_closing.join("\n\n"));
|
|
466
|
+
parts.push(error_fix_closing.join("\n\n"));
|
|
467
|
+
return parts.filter(Boolean).join("\n\n");
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
/**
|
|
471
|
+
* Prompt for deciding whether feedback needs clarifying questions (ask_questions tool).
|
|
472
|
+
* `knownContext` is derived from the feedback URL before calling this method.
|
|
473
|
+
* @param {{ title: string, description: string,
|
|
474
|
+
* knownContext?: { section: string, doNotAsk: string|null }|null }} opts
|
|
475
|
+
*/
|
|
476
|
+
feedbackAnalysePrompt({ title, description, knownContext = null }) {
|
|
477
|
+
const parts = [];
|
|
478
|
+
if (this.spec?.body?.specification)
|
|
479
|
+
parts.push(
|
|
480
|
+
`The following application is being built:\n\n${this.spec.body.specification}`
|
|
481
|
+
);
|
|
482
|
+
if (knownContext) parts.push(knownContext.section);
|
|
483
|
+
parts.push(
|
|
484
|
+
`User feedback:\n- Title: ${title}` +
|
|
485
|
+
(description ? `\n- Description: ${description}` : "")
|
|
486
|
+
);
|
|
487
|
+
if (knownContext?.doNotAsk)
|
|
488
|
+
parts.push(
|
|
489
|
+
`Facts already known — do NOT ask about these:\n${knownContext.doNotAsk}`
|
|
490
|
+
);
|
|
491
|
+
parts.push(feedback_analyse_decision);
|
|
492
|
+
return parts.filter(Boolean).join("\n\n");
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
/**
|
|
496
|
+
* Step 1 of 2 for feedback processing.
|
|
497
|
+
* Prompt for deriving new requirements from a piece of user feedback (make_requirements tool).
|
|
498
|
+
* @param {{ title: string, description: string, urlSection?: string, feedbackResearchSection?: string }} opts
|
|
499
|
+
*/
|
|
500
|
+
feedbackReqPrompt({
|
|
501
|
+
title,
|
|
502
|
+
description,
|
|
503
|
+
urlSection = "",
|
|
504
|
+
feedbackResearchSection = "",
|
|
505
|
+
}) {
|
|
506
|
+
const parts = [
|
|
507
|
+
`The following application is being built:\n\n${this.spec?.body?.specification}`,
|
|
508
|
+
];
|
|
509
|
+
if (this.researchSection) parts.push(this.researchSection);
|
|
510
|
+
parts.push(
|
|
511
|
+
`A new piece of feedback has come in from a user:\n\nTitle: ${title}\nDescription: ${description}`
|
|
512
|
+
);
|
|
513
|
+
if (urlSection) parts.push(urlSection);
|
|
514
|
+
if (feedbackResearchSection) parts.push(feedbackResearchSection);
|
|
515
|
+
parts.push(
|
|
516
|
+
`Now use the make_requirements tool to create a single or several (a single is\n` +
|
|
517
|
+
`preferred) new requirements that captures this new piece of feedback.\n\n` +
|
|
518
|
+
`* Priority reflects how central the feature is to the core purpose of the\n` +
|
|
519
|
+
` application. Assign 5 to features without which the application cannot function\n` +
|
|
520
|
+
` at all, 3–4 to features that are important but not blocking, 1–2 to minor\n` +
|
|
521
|
+
` convenience features. Do not assign 5 to everything.`
|
|
522
|
+
);
|
|
523
|
+
return parts.filter(Boolean).join("\n\n");
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
/**
|
|
527
|
+
* Step 2 of 2 for feedback processing.
|
|
528
|
+
* Prompt for planning implementation tasks for the feedback (plan_tasks tool).
|
|
529
|
+
* Call after feedbackReqPrompt — pass its output as `newRequirements`.
|
|
530
|
+
* `this.allReqs` must be captured before the new requirements are saved to the DB.
|
|
531
|
+
* @param {{ title: string, description: string, urlSection?: string,
|
|
532
|
+
* feedbackResearchSection?: string, newRequirements?: object[] }} opts
|
|
533
|
+
*/
|
|
534
|
+
feedbackPrompt({
|
|
535
|
+
title,
|
|
536
|
+
description,
|
|
537
|
+
urlSection = "",
|
|
538
|
+
feedbackResearchSection = "",
|
|
539
|
+
newRequirements = [],
|
|
540
|
+
}) {
|
|
541
|
+
const parts = [
|
|
542
|
+
`Generate implementation tasks for a new piece of feedback for this application:\n\n${this.spec?.body?.specification}`,
|
|
543
|
+
];
|
|
544
|
+
if (this.researchSection) parts.push(this.researchSection);
|
|
545
|
+
parts.push(
|
|
546
|
+
`A new piece of feedback has come in from a user:\n\nTitle: ${title}\nDescription: ${description}`
|
|
547
|
+
);
|
|
548
|
+
if (urlSection) parts.push(urlSection);
|
|
549
|
+
if (feedbackResearchSection) parts.push(feedbackResearchSection);
|
|
550
|
+
if (this.allReqs.length)
|
|
551
|
+
parts.push(
|
|
552
|
+
`The existing application requirements are:\n\n` +
|
|
553
|
+
this.allReqs.map((r) => `* ${r.body.requirement}`).join("\n")
|
|
554
|
+
);
|
|
555
|
+
if (newRequirements.length)
|
|
556
|
+
parts.push(
|
|
557
|
+
`A product manager has determined that the following new requirements should be added to implement this feedback:\n\n` +
|
|
558
|
+
newRequirements.map((r) => ` * ${r.requirement}`).join("\n")
|
|
559
|
+
);
|
|
560
|
+
parts.push(saltcorn_description.join("\n\n"));
|
|
561
|
+
parts.push(
|
|
562
|
+
`The database has already been built. The following tables are now present in the database:\n\n${flatTablesList(
|
|
563
|
+
this.allTables
|
|
564
|
+
)}\n\n` +
|
|
565
|
+
`The plan should outline continued development of the application on top of this database.`
|
|
566
|
+
);
|
|
567
|
+
if (this.entitiesSection) parts.push(this.entitiesSection);
|
|
568
|
+
if (this.installedPluginsSection) parts.push(this.installedPluginsSection);
|
|
569
|
+
const availableSection = available_plugins_list(
|
|
570
|
+
this.storePlugins,
|
|
571
|
+
this.installedNames
|
|
572
|
+
);
|
|
573
|
+
if (availableSection) parts.push(availableSection);
|
|
574
|
+
parts.push(task_planning_rules.join("\n\n"));
|
|
575
|
+
parts.push(task_planning_closing.join("\n\n"));
|
|
576
|
+
parts.push(
|
|
577
|
+
"Important overrides for feedback tasks:\n" +
|
|
578
|
+
feedback_task_overrides.map((r) => `* ${r}`).join("\n")
|
|
579
|
+
);
|
|
580
|
+
parts.push(
|
|
581
|
+
"Now use the plan_tasks tool to create the tasks to implement this new feedback."
|
|
582
|
+
);
|
|
583
|
+
return parts.filter(Boolean).join("\n\n");
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
module.exports = { PromptGenerator };
|