godcrm/backend/services/agent-tools/bdd-tools.js
GOD CRM Release f89e074dd1
Some checks failed
CI / Lint / Typecheck / Test / Build (push) Has been cancelled
CI / PostgreSQL Integration Tests (push) Has been cancelled
GOD CRM — public scrubbed snapshot
Governed substrate for autonomous agents: scoped identity (passports),
audited actions, MCP workspace. Infra IPs and secrets redacted for public release.
2026-08-10 04:01:45 +03:00

87 lines
2.9 KiB
JavaScript

/**
* BDD Tool Handlers (ADR-0003 §C-1)
*
* MCP-side view onto the same logical tables (`bdd_specs`, `bdd_criteria`)
* that back GET /api/v3/bdd/specs. Lets agents read acceptance criteria for
* a document without hitting the HTTP layer.
*/
import { dbGet, dbAll } from '../../database/connection.js';
const BDD_SPACE_ID = 11;
const bddTableIds = new Map();
async function bddTableId(name) {
if (bddTableIds.has(name)) return bddTableIds.get(name);
const row = await dbGet(
`SELECT ut.id FROM universal_tables ut
JOIN projects p ON p.id = ut.project_id
WHERE p.space_id = ? AND ut.name = ? LIMIT 1`,
[BDD_SPACE_ID, name]
);
if (row?.id) bddTableIds.set(name, row.id);
return row?.id || null;
}
function parse(v) {
if (v == null) return {};
return typeof v === 'string' ? (() => { try { return JSON.parse(v); } catch { return {}; } })() : v;
}
export const bddToolHandlers = {
async list_bdd_specs({ source_doc_id }) {
const docId = parseInt(source_doc_id, 10);
if (!Number.isFinite(docId)) {
return { error: 'source_doc_id (integer) is required' };
}
const specsTableId = await bddTableId('bdd_specs');
const critTableId = await bddTableId('bdd_criteria');
if (!specsTableId || !critTableId) {
return { error: 'BDD_TABLES_NOT_BOOTSTRAPPED: bdd_specs/bdd_criteria missing in space 11' };
}
const specRows = await dbAll(
`SELECT id, data FROM table_rows
WHERE table_id = ? AND data->>'source_doc_id' = ?
ORDER BY id ASC`,
[specsTableId, String(docId)]
);
const specs = [];
for (const s of specRows) {
const sd = parse(s.data);
const crit = await dbAll(
`SELECT id, data FROM table_rows
WHERE table_id = ? AND data->>'spec_id' = ?
ORDER BY COALESCE((data->>'order_index')::int, id) ASC`,
[critTableId, String(s.id)]
);
specs.push({
id: s.id,
code: sd.code || sd.title || `spec-${s.id}`,
owner_user_id: sd.owner_user_id ?? null,
criteria: crit.map(c => {
const d = parse(c.data);
// ADR-0002 §8 Phase 2 — canonical given/when_clause/then with
// legacy `description` fallback when all three are empty.
const gwtPresent = !!(d.given || d.when_clause || d.when || d.then);
return {
id: c.id,
code: d.code || null,
title: d.title || null,
given: d.given || (gwtPresent ? '' : (d.description || '')),
when: d.when_clause || d.when || '',
then: d.then || (gwtPresent ? '' : (d.title || '')),
description: d.description || null,
priority: d.priority || null,
status: d.status || 'pending',
owner_user_id: d.owner_user_id ?? null,
};
}),
});
}
return { source_doc_id: docId, specs, spec_count: specs.length };
},
};