Add feature set import export

This commit is contained in:
OpenClaw Bot
2026-05-22 23:28:15 +02:00
parent da9d3673c1
commit 7cc43a5e92
5 changed files with 170 additions and 25 deletions
+44 -20
View File
@@ -56,6 +56,29 @@ function scoreIdea({ impact, effort, confidence, urgency }) {
return Number((((i * 2.4) + (c * 1.2) + (u * 1.4)) / Math.max(1, e)).toFixed(2));
}
function ideaDataFromInput(input = {}, defaults = {}) {
const title = cleanText(input.title || input.name, 180);
if (!title) throw Object.assign(new Error('Every feature needs a title.'), { code: 400 });
const data = {
title,
description: cleanMultiline(input.description || input.brief || '', 5000),
source: cleanText(input.source || defaults.source || 'import', 40),
sourceName: cleanText(input.sourceName || input.owner || defaults.sourceName || 'Prioritix feature set', 80),
status: cleanText(input.status || defaults.status || 'inbox', 40),
milestoneId: cleanText(input.milestoneId || input.milestone || defaults.milestoneId || 'inbox', 64),
impact: clampInt(input.impact, defaults.impact ?? 5),
effort: clampInt(input.effort, defaults.effort ?? 5, 1, 10),
confidence: clampInt(input.confidence, defaults.confidence ?? 6),
urgency: clampInt(input.urgency, defaults.urgency ?? 5),
rank: clampInt(input.rank, defaults.rank ?? 0, -100000, 100000),
labels: encodeList(input.labels || input.category || input.categories || []),
notes: cleanMultiline(input.notes || '', 4000),
archived: Boolean(input.archived ?? false),
};
data.score = scoreIdea(data);
return data;
}
function publicIdea(row) {
return {
id: row.$id,
@@ -174,30 +197,31 @@ app.get('/api/bootstrap', async (_req, res) => {
});
app.post('/api/ideas', requireAgent, async (req, res) => {
const title = cleanText(req.body.title, 180);
if (!title) return res.status(400).json({ error: 'title is required' });
const data = {
title,
description: cleanMultiline(req.body.description, 5000),
source: cleanText(req.body.source || 'human', 40),
sourceName: cleanText(req.body.sourceName || req.body.agent || '', 80),
status: cleanText(req.body.status || 'inbox', 40),
milestoneId: cleanText(req.body.milestoneId || 'inbox', 64),
impact: clampInt(req.body.impact, 5),
effort: clampInt(req.body.effort, 5, 1, 10),
confidence: clampInt(req.body.confidence, 6),
urgency: clampInt(req.body.urgency, 5),
rank: clampInt(req.body.rank, 0, -100000, 100000),
labels: encodeList(req.body.labels),
notes: cleanMultiline(req.body.notes, 4000),
archived: false,
};
data.score = scoreIdea(data);
const data = ideaDataFromInput(req.body, { source: 'human', status: 'inbox', milestoneId: 'inbox' });
const row = assertRow(await tables.createRow({ databaseId, tableId: ideasTableId, rowId: ID.unique(), data }));
await logActivity('idea.created', `Captured “${title}`, row.$id, data.source);
await logActivity('idea.created', `Captured “${data.title}`, row.$id, data.source);
res.status(201).json(publicIdea(row));
});
app.post('/api/feature-set/import', requireAgent, async (req, res) => {
const features = Array.isArray(req.body.features) ? req.body.features.slice(0, 100) : [];
if (!features.length) return res.status(400).json({ error: 'Feature set must include a non-empty features array.' });
const defaults = req.body.defaults && typeof req.body.defaults === 'object' ? req.body.defaults : {};
const created = [];
const errors = [];
for (const [index, feature] of features.entries()) {
try {
const data = ideaDataFromInput(feature, { source: 'import', sourceName: req.body.name || 'Prioritix feature set', ...defaults });
const row = assertRow(await tables.createRow({ databaseId, tableId: ideasTableId, rowId: ID.unique(), data }));
created.push(publicIdea(row));
} catch (error) {
errors.push({ index, title: cleanText(feature?.title || feature?.name || '', 180), error: error.message });
}
}
if (created.length) await logActivity('feature_set.imported', `Imported ${created.length} feature${created.length === 1 ? '' : 's'}`, '', req.body.name || 'feature-set-v1');
res.status(created.length ? 201 : 400).json({ ok: errors.length === 0, imported: created.length, created, errors });
});
app.patch('/api/ideas/:id', requireAgent, async (req, res) => {
const allowed = ['title', 'description', 'source', 'sourceName', 'status', 'milestoneId', 'impact', 'effort', 'confidence', 'urgency', 'rank', 'labels', 'notes', 'archived'];
const data = {};