@softize/opus 25.0.0 → 25.2.0
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/CHANGELOG.md +93 -0
- package/bin/cli.mjs +5 -0
- package/bin/lib/assistant-schema.d.mts +55 -0
- package/bin/lib/assistant-schema.mjs +286 -0
- package/bin/lib/check.mjs +173 -0
- package/bin/lib/copy.mjs +2 -0
- package/bin/lib/gen-manifest.mjs +3 -0
- package/bin/lib/gen-runner.mjs +29 -1
- package/bin/lib/mcp.mjs +1 -1
- package/docs/adr/0052-floating-layer-coexists-with-modal-surfaces.md +668 -0
- package/docs/adr/0053-read-action-declares-the-resource-assistant.md +443 -0
- package/docs/protocol.md +54 -0
- package/docs/releasing.md +8 -2
- package/docs/stacking-scale.md +53 -0
- package/package.json +10 -1
- package/registry/skills/create-opus-action/SKILL.md +8 -3
- package/registry/skills/create-opus-action/references/contract-and-binding.md +24 -1
- package/registry/skills/create-opus-action/references/evaluations.md +3 -0
- package/registry/skills/test-opus-action/SKILL.md +4 -1
- package/registry/skills/test-opus-action/references/evaluations.md +2 -0
- package/registry/skills/test-opus-action/references/harness.md +14 -0
- package/src/ai/index.ts +3 -0
- package/src/ai/resource-context.ts +113 -0
- package/src/core/assistant.ts +545 -0
- package/src/core/index.ts +23 -0
- package/src/core/runtime.ts +136 -0
- package/src/core/types.ts +34 -0
- package/src/mcp/index.ts +3 -0
- package/src/ui/components/primitives/dialog.tsx +285 -26
- package/src/ui/components/primitives/drawer.tsx +104 -13
- package/src/ui/components/primitives/floating-layer.tsx +79 -0
- package/src/ui/components/primitives/menu.tsx +17 -2
- package/src/ui/components/primitives/popover.tsx +6 -1
- package/src/ui/components/primitives/tooltip.tsx +6 -1
- package/src/ui/docs/content/actions.md +97 -1
- package/src/ui/docs/content/ai.md +4 -2
- package/src/ui/docs/content/cli.md +2 -1
- package/src/ui/docs/content/dialog.md +3 -0
- package/src/ui/docs/content/drawer.md +3 -0
- package/src/ui/docs/content/floating-layer.md +178 -0
- package/src/ui/docs/content/mcp.md +3 -1
- package/src/ui/docs/registry.tsx +6 -0
- package/src/ui/lib/dismiss-on-iframe.ts +4 -1
- package/src/ui/lib/modal-layers.ts +547 -0
- package/src/ui/meta.ts +6 -0
- package/src/ui/react.tsx +4 -0
package/bin/lib/check.mjs
CHANGED
|
@@ -24,6 +24,12 @@
|
|
|
24
24
|
* identificador exportado; um `entities` ou `purges` que o check não
|
|
25
25
|
* consegue ler (spread, variável) suspende a regra de ausência
|
|
26
26
|
* correspondente, sem falso positivo.
|
|
27
|
+
* assistant-declaration — o assistente do recurso (ADR 0053): só em action `view`, com `ai`
|
|
28
|
+
* habilitado, `personalData` declarado, `resource` em camelCase, `skill`
|
|
29
|
+
* em minúsculas com hífen, `label`, `title` e `context` presentes e
|
|
30
|
+
* `maxContextChars` literal de 1.000 a 16.000. O
|
|
31
|
+
* mesmo `resource` literal em duas actions, entre arquivos, reprova. Um
|
|
32
|
+
* `assistant` ou `ai` que não é literal suspende o que depende dele.
|
|
27
33
|
*
|
|
28
34
|
* A checagem cross-file de registro no runtime (graph) fica pra v2 (introspecção/MCP).
|
|
29
35
|
* Saída: findings + exit ≠ 0 se houver erro. Zero actions num diretório NÃO-marcado
|
|
@@ -73,6 +79,7 @@ const FIELD_GROUP = {
|
|
|
73
79
|
rateLimit: 5,
|
|
74
80
|
idempotency: 5,
|
|
75
81
|
ai: 6,
|
|
82
|
+
assistant: 6,
|
|
76
83
|
automatable: 6,
|
|
77
84
|
internal: 6,
|
|
78
85
|
};
|
|
@@ -82,6 +89,17 @@ const FIELD_GROUP = {
|
|
|
82
89
|
const SEG = "[a-z][a-z0-9]*(-[a-z0-9]+)*";
|
|
83
90
|
const ACTION_NAME_RE = new RegExp(`^${SEG}(\\.${SEG})+$`);
|
|
84
91
|
const KINDS = new Set(["simple", "form", "list", "view"]);
|
|
92
|
+
// O assistente do recurso (ADR 0053): o mesmo formato que o registro do runtime confere.
|
|
93
|
+
const ASSISTANT_RESOURCE_RE = /^[a-z][a-zA-Z0-9]*$/;
|
|
94
|
+
const ASSISTANT_SKILL_RE = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/;
|
|
95
|
+
const ASSISTANT_CONTEXT_MIN = 1000;
|
|
96
|
+
const ASSISTANT_RESERVED_SEGMENTS = new Set(["__proto__", "constructor", "prototype"]);
|
|
97
|
+
const ASSISTANT_CONTEXT_MAX = 16000;
|
|
98
|
+
const ASSISTANT_REQUIRED = {
|
|
99
|
+
label: "`label`, o nome do assistente para pessoas",
|
|
100
|
+
title: "`title`, o caminho do campo que nomeia o recurso",
|
|
101
|
+
context: "`context`, os caminhos dos campos que vão ao modelo",
|
|
102
|
+
};
|
|
85
103
|
// O runtime e o manifest só aplicam estes campos em `simple` e `form`; o tipo do binding os aceita
|
|
86
104
|
// em qualquer kind, então a declaração num list/view compila e não tem efeito.
|
|
87
105
|
const EXECUTION_ONLY_FIELDS = ["emits", "background"];
|
|
@@ -977,6 +995,73 @@ export function parseActions(fileName, sourceText) {
|
|
|
977
995
|
.filter(Boolean);
|
|
978
996
|
}
|
|
979
997
|
|
|
998
|
+
function prop(obj, key) {
|
|
999
|
+
return obj.properties.find(
|
|
1000
|
+
(pr) =>
|
|
1001
|
+
ts.isPropertyAssignment(pr) && pr.name && pr.name.getText(sf) === key,
|
|
1002
|
+
);
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
/** `ai` literal → "enabled" | "disabled"; ausente → null; não literal → "unknown". */
|
|
1006
|
+
function aiState(obj) {
|
|
1007
|
+
const p = prop(obj, "ai");
|
|
1008
|
+
if (!p) return obj.properties.some((pr) => pr.name?.getText(sf) === "ai") ? "unknown" : null;
|
|
1009
|
+
const value = p.initializer;
|
|
1010
|
+
if (value.kind === ts.SyntaxKind.TrueKeyword) return "enabled";
|
|
1011
|
+
if (value.kind === ts.SyntaxKind.FalseKeyword) return "disabled";
|
|
1012
|
+
if (ts.isObjectLiteralExpression(value)) {
|
|
1013
|
+
const enabled = prop(value, "enabled");
|
|
1014
|
+
if (!enabled) return "enabled";
|
|
1015
|
+
if (enabled.initializer.kind === ts.SyntaxKind.FalseKeyword) return "disabled";
|
|
1016
|
+
if (enabled.initializer.kind === ts.SyntaxKind.TrueKeyword) return "enabled";
|
|
1017
|
+
}
|
|
1018
|
+
return "unknown";
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
/** O `assistant` literal → { readable, resource, skill, keys }; ausente → null. */
|
|
1022
|
+
function assistantDeclaration(obj) {
|
|
1023
|
+
const p = prop(obj, "assistant");
|
|
1024
|
+
if (!p) return null;
|
|
1025
|
+
if (!ts.isObjectLiteralExpression(p.initializer))
|
|
1026
|
+
return { readable: false, line: lineOf(p) };
|
|
1027
|
+
const value = p.initializer;
|
|
1028
|
+
const literal = (key) => {
|
|
1029
|
+
const found = prop(value, key);
|
|
1030
|
+
if (!found) return { present: objKeys(value).includes(key), text: null };
|
|
1031
|
+
return {
|
|
1032
|
+
present: true,
|
|
1033
|
+
text: ts.isStringLiteralLike(found.initializer) ? found.initializer.text : null,
|
|
1034
|
+
};
|
|
1035
|
+
};
|
|
1036
|
+
const limit = prop(value, "maxContextChars");
|
|
1037
|
+
let maxContextChars = null;
|
|
1038
|
+
if (limit) {
|
|
1039
|
+
const node = limit.initializer;
|
|
1040
|
+
if (ts.isNumericLiteral(node)) maxContextChars = Number(node.text.replace(/_/g, ""));
|
|
1041
|
+
else if (
|
|
1042
|
+
ts.isPrefixUnaryExpression(node) &&
|
|
1043
|
+
node.operator === ts.SyntaxKind.MinusToken &&
|
|
1044
|
+
ts.isNumericLiteral(node.operand)
|
|
1045
|
+
)
|
|
1046
|
+
maxContextChars = -Number(node.operand.text.replace(/_/g, ""));
|
|
1047
|
+
}
|
|
1048
|
+
const contextNode = prop(value, "context");
|
|
1049
|
+
const contextPaths =
|
|
1050
|
+
contextNode && ts.isArrayLiteralExpression(contextNode.initializer)
|
|
1051
|
+
? contextNode.initializer.elements.filter(ts.isStringLiteralLike).map((element) => element.text)
|
|
1052
|
+
: [];
|
|
1053
|
+
return {
|
|
1054
|
+
readable: true,
|
|
1055
|
+
line: lineOf(p),
|
|
1056
|
+
resource: literal("resource"),
|
|
1057
|
+
skill: literal("skill"),
|
|
1058
|
+
title: literal("title"),
|
|
1059
|
+
contextPaths,
|
|
1060
|
+
maxContextChars,
|
|
1061
|
+
keys: objKeys(value),
|
|
1062
|
+
};
|
|
1063
|
+
}
|
|
1064
|
+
|
|
980
1065
|
function visit(node) {
|
|
981
1066
|
if (ts.isCallExpression(node) && ts.isIdentifier(node.expression)) {
|
|
982
1067
|
const callee = node.expression.text;
|
|
@@ -992,6 +1077,8 @@ export function parseActions(fileName, sourceText) {
|
|
|
992
1077
|
name: strProp(obj, "name"),
|
|
993
1078
|
kind: strProp(obj, "kind"),
|
|
994
1079
|
effect: strProp(obj, "effect"),
|
|
1080
|
+
ai: aiState(obj),
|
|
1081
|
+
assistant: assistantDeclaration(obj),
|
|
995
1082
|
keys: objKeys(obj),
|
|
996
1083
|
ident: constIdent(node),
|
|
997
1084
|
exported: isExported(node),
|
|
@@ -1023,6 +1110,61 @@ export function parseActions(fileName, sourceText) {
|
|
|
1023
1110
|
return actions;
|
|
1024
1111
|
}
|
|
1025
1112
|
|
|
1113
|
+
/**
|
|
1114
|
+
* Regra `assistant-declaration` (ADR 0053) sobre um item: a forma do assistente do recurso. O
|
|
1115
|
+
* `resource` repetido entre actions é conferido no `checkProject`.
|
|
1116
|
+
*/
|
|
1117
|
+
function assistantMessages(a) {
|
|
1118
|
+
const assistant = a.assistant;
|
|
1119
|
+
if (assistant === null || assistant === undefined) return [];
|
|
1120
|
+
const out = [];
|
|
1121
|
+
if (a.kind !== null && a.kind !== "view")
|
|
1122
|
+
out.push(
|
|
1123
|
+
`Só uma action view declara assistente, e esta é ${a.kind}. A leitura de um recurso identificado é o papel da view.`,
|
|
1124
|
+
);
|
|
1125
|
+
if (a.ai === null || a.ai === "disabled")
|
|
1126
|
+
out.push(
|
|
1127
|
+
"A action dona precisa estar publicada para a IA: declare `ai: true` ou `ai` sem `enabled: false`.",
|
|
1128
|
+
);
|
|
1129
|
+
if (!a.keys.includes("personalData"))
|
|
1130
|
+
out.push(
|
|
1131
|
+
"A action dona precisa declarar `personalData`, `true` ou `false`: a declaração decide como o host trata o dado que entrega ao modelo.",
|
|
1132
|
+
);
|
|
1133
|
+
if (!assistant.readable) return out;
|
|
1134
|
+
const { resource, skill } = assistant;
|
|
1135
|
+
if (!resource.present)
|
|
1136
|
+
out.push("O assistente precisa declarar `resource`, o tipo de recurso de que a action é dona.");
|
|
1137
|
+
else if (resource.text !== null && !ASSISTANT_RESOURCE_RE.test(resource.text))
|
|
1138
|
+
out.push(`\`resource: "${resource.text}"\` precisa estar em camelCase, como \`customer\` ou \`aftersaleOpportunity\`.`);
|
|
1139
|
+
if (!skill.present)
|
|
1140
|
+
out.push("O assistente precisa declarar `skill`, a chave do procedimento que a IA segue.");
|
|
1141
|
+
else if (skill.text !== null && !ASSISTANT_SKILL_RE.test(skill.text))
|
|
1142
|
+
out.push(`\`skill: "${skill.text}"\` precisa estar em minúsculas com hífen, como \`review-customer\`.`);
|
|
1143
|
+
const reserved = [assistant.title.text, ...assistant.contextPaths].filter(
|
|
1144
|
+
(path) =>
|
|
1145
|
+
typeof path === "string" &&
|
|
1146
|
+
path.split(".").some((segment) => ASSISTANT_RESERVED_SEGMENTS.has(segment)),
|
|
1147
|
+
);
|
|
1148
|
+
if (reserved.length > 0)
|
|
1149
|
+
out.push(
|
|
1150
|
+
`${reserved.map((path) => `\`${path}\``).join(", ")} usa __proto__, constructor ou prototype, que não são campos da saída.`,
|
|
1151
|
+
);
|
|
1152
|
+
const limit = assistant.maxContextChars;
|
|
1153
|
+
if (
|
|
1154
|
+
limit !== null &&
|
|
1155
|
+
(!Number.isInteger(limit) || limit < ASSISTANT_CONTEXT_MIN || limit > ASSISTANT_CONTEXT_MAX)
|
|
1156
|
+
)
|
|
1157
|
+
out.push(
|
|
1158
|
+
`\`maxContextChars: ${limit}\` precisa ser um inteiro de 1.000 a 16.000; sem ele, o teto é de 4.000 caracteres.`,
|
|
1159
|
+
);
|
|
1160
|
+
const missing = Object.keys(ASSISTANT_REQUIRED).filter((key) => !assistant.keys.includes(key));
|
|
1161
|
+
if (missing.length > 0)
|
|
1162
|
+
out.push(
|
|
1163
|
+
`O assistente precisa declarar ${missing.map((key) => ASSISTANT_REQUIRED[key]).join("; ")}.`,
|
|
1164
|
+
);
|
|
1165
|
+
return out;
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1026
1168
|
/** Aplica as regras locais (por arquivo) a um item → findings. */
|
|
1027
1169
|
export function lintAction(a) {
|
|
1028
1170
|
const out = [];
|
|
@@ -1088,6 +1230,15 @@ export function lintAction(a) {
|
|
|
1088
1230
|
const ignored = ignoredByKindMessage(a.kind, a.keys);
|
|
1089
1231
|
if (ignored !== null) at("ignored-by-kind", ignored);
|
|
1090
1232
|
|
|
1233
|
+
for (const message of assistantMessages(a))
|
|
1234
|
+
out.push({
|
|
1235
|
+
rule: "assistant-declaration",
|
|
1236
|
+
level: "error",
|
|
1237
|
+
action: label,
|
|
1238
|
+
line: a.assistant.line,
|
|
1239
|
+
message,
|
|
1240
|
+
});
|
|
1241
|
+
|
|
1091
1242
|
if (
|
|
1092
1243
|
a.form === "defineAction" &&
|
|
1093
1244
|
a.keys.includes("requires") &&
|
|
@@ -1126,6 +1277,7 @@ export function checkProject(sources) {
|
|
|
1126
1277
|
const entityNames = new Set();
|
|
1127
1278
|
const actionNames = new Set();
|
|
1128
1279
|
const retention = [];
|
|
1280
|
+
const assistantResources = new Map(); // resource literal → [{ item, file }]
|
|
1129
1281
|
// Natureza da action (ADR 0041): declarações ausentes são contadas, não reprovadas, na minor.
|
|
1130
1282
|
let missingEffect = 0;
|
|
1131
1283
|
let missingPersonalData = 0;
|
|
@@ -1159,6 +1311,12 @@ export function checkProject(sources) {
|
|
|
1159
1311
|
if (item.name !== null) actionNames.add(item.name);
|
|
1160
1312
|
}
|
|
1161
1313
|
for (const f of lintAction(item)) findings.push({ ...f, file });
|
|
1314
|
+
const resource = item.assistant?.resource?.text ?? null;
|
|
1315
|
+
if (resource !== null) {
|
|
1316
|
+
const owners = assistantResources.get(resource) ?? [];
|
|
1317
|
+
owners.push({ item, file });
|
|
1318
|
+
assistantResources.set(resource, owners);
|
|
1319
|
+
}
|
|
1162
1320
|
if (item.form !== "bindAction") {
|
|
1163
1321
|
const writesByGet = item.kind === "list" || item.kind === "view";
|
|
1164
1322
|
if (!writesByGet && !item.keys.includes("effect")) missingEffect += 1;
|
|
@@ -1265,6 +1423,21 @@ export function checkProject(sources) {
|
|
|
1265
1423
|
}
|
|
1266
1424
|
}
|
|
1267
1425
|
|
|
1426
|
+
for (const [resource, owners] of assistantResources) {
|
|
1427
|
+
if (owners.length < 2) continue;
|
|
1428
|
+
const names = owners.map(({ item }) => item.name ?? item.ident ?? "(sem name)");
|
|
1429
|
+
for (const { item, file } of owners) {
|
|
1430
|
+
findings.push({
|
|
1431
|
+
rule: "assistant-declaration",
|
|
1432
|
+
level: "error",
|
|
1433
|
+
action: item.name ?? item.ident ?? "(sem name)",
|
|
1434
|
+
line: item.assistant.line,
|
|
1435
|
+
file,
|
|
1436
|
+
message: `O recurso "${resource}" tem mais de uma action dona (${names.join(", ")}). Cada tipo de recurso tem uma única action dona.`,
|
|
1437
|
+
});
|
|
1438
|
+
}
|
|
1439
|
+
}
|
|
1440
|
+
|
|
1268
1441
|
findings.push(...checkRetention(retention));
|
|
1269
1442
|
|
|
1270
1443
|
return {
|
package/bin/lib/copy.mjs
CHANGED
|
@@ -1540,6 +1540,8 @@ export function extractCopyFromSource(file, sourceText) {
|
|
|
1540
1540
|
addProperty(example, 'description', 'description', prefix)
|
|
1541
1541
|
})
|
|
1542
1542
|
nestedObject(object, 'errors', (error, prefix) => addProperty(error, 'description', 'error', prefix))
|
|
1543
|
+
// O nome do assistente do recurso (ADR 0053), que a interface mostra como título do chat.
|
|
1544
|
+
nestedObject(object, 'assistant', (assistant, prefix) => addProperty(assistant, 'label', 'title', prefix))
|
|
1543
1545
|
}
|
|
1544
1546
|
|
|
1545
1547
|
function extractPresentation(object) {
|
package/bin/lib/gen-manifest.mjs
CHANGED
|
@@ -55,6 +55,9 @@ function shapeAction(a) {
|
|
|
55
55
|
automatable: a.automatable,
|
|
56
56
|
effect: a.effect ?? null,
|
|
57
57
|
personalData: a.personalData ?? null,
|
|
58
|
+
// ADR 0053: a publicação para a IA e, na action dona, o assistente do recurso.
|
|
59
|
+
ai: a.ai ?? { enabled: false, description: null, destructive: false, requiresConfirmation: false },
|
|
60
|
+
...(a.assistant !== undefined && a.assistant !== null ? { assistant: a.assistant } : {}),
|
|
58
61
|
internal: a.internal,
|
|
59
62
|
successStatus: a.successStatus,
|
|
60
63
|
input: a.input,
|
package/bin/lib/gen-runner.mjs
CHANGED
|
@@ -53,6 +53,8 @@
|
|
|
53
53
|
* automatable?: boolean, // aceita execução pelo sistema (ADR 0028)
|
|
54
54
|
* effect: 'read'|'write'|null, // efeito resolvido; list/view = read (ADR 0041)
|
|
55
55
|
* personalData: boolean|null, // devolve dado pessoal; null = não declarado
|
|
56
|
+
* ai: { enabled, description, destructive, requiresConfirmation }, // ADR 0053
|
|
57
|
+
* assistant?: { resource, skill, label, title, context, maxContextChars }, // view dona
|
|
56
58
|
* internal?: boolean,
|
|
57
59
|
* rateLimit?: { window, max },
|
|
58
60
|
* confirm?: ConfirmSpec,
|
|
@@ -67,6 +69,7 @@
|
|
|
67
69
|
|
|
68
70
|
import path from "node:path";
|
|
69
71
|
import { pathToFileURL, fileURLToPath } from "node:url";
|
|
72
|
+
import { assistantSchemaIssues, serializeAiConfig } from "./assistant-schema.mjs";
|
|
70
73
|
|
|
71
74
|
const __filename = fileURLToPath(import.meta.url);
|
|
72
75
|
const __dirname = path.dirname(__filename);
|
|
@@ -90,7 +93,7 @@ async function main() {
|
|
|
90
93
|
const opusCoreUrl = pathToFileURL(
|
|
91
94
|
path.join(PACKAGE_ROOT, "src", "core", "index.ts"),
|
|
92
95
|
).href;
|
|
93
|
-
const { flattenDomain, domainPurges, declaredJsonSchema } = await import(opusCoreUrl);
|
|
96
|
+
const { flattenDomain, domainPurges, declaredJsonSchema, publicAssistant } = await import(opusCoreUrl);
|
|
94
97
|
|
|
95
98
|
const opusSeedUrl = pathToFileURL(
|
|
96
99
|
path.join(PACKAGE_ROOT, "src", "seed", "index.ts"),
|
|
@@ -138,6 +141,7 @@ async function main() {
|
|
|
138
141
|
const ctx = {
|
|
139
142
|
flattenDomain,
|
|
140
143
|
domainPurges,
|
|
144
|
+
publicAssistant,
|
|
141
145
|
// Standard Schema que declara o próprio JSON Schema (ADR 0051) dispensa o zod.
|
|
142
146
|
toJsonSchema: (schema) =>
|
|
143
147
|
declaredJsonSchema(schema, { target: "openapi-3.0" }) ??
|
|
@@ -393,6 +397,8 @@ function serializeAction(action, ctx) {
|
|
|
393
397
|
// ADR 0041: `list` e `view` são leitura pelo protocolo; null = não declarado.
|
|
394
398
|
effect: action.kind === "list" || action.kind === "view" ? "read" : (action.effect ?? null),
|
|
395
399
|
personalData: typeof action.personalData === "boolean" ? action.personalData : null,
|
|
400
|
+
// ADR 0053: a publicação para a IA entra no manifest, e a action dona, o assistente.
|
|
401
|
+
ai: serializeAiConfig(action.ai),
|
|
396
402
|
internal: action.internal === true,
|
|
397
403
|
successStatus: action.successStatus,
|
|
398
404
|
input: safeToJsonSchema(action.input, ctx),
|
|
@@ -434,6 +440,20 @@ function serializeAction(action, ctx) {
|
|
|
434
440
|
base.expand = action.expand ?? null;
|
|
435
441
|
}
|
|
436
442
|
|
|
443
|
+
if (action.kind === "view" && action.assistant !== undefined && action.assistant !== null) {
|
|
444
|
+
const issues = assistantSchemaIssues({
|
|
445
|
+
assistant: action.assistant,
|
|
446
|
+
input: base.input,
|
|
447
|
+
output: base.output,
|
|
448
|
+
});
|
|
449
|
+
if (issues.length > 0) {
|
|
450
|
+
throw declarationError(
|
|
451
|
+
`O assistente da action "${action.name}" não confere com o contrato (ADR 0053): ${issues.join("; ")}.`,
|
|
452
|
+
);
|
|
453
|
+
}
|
|
454
|
+
base.assistant = ctx.publicAssistant(action.assistant);
|
|
455
|
+
}
|
|
456
|
+
|
|
437
457
|
return base;
|
|
438
458
|
}
|
|
439
459
|
|
|
@@ -605,6 +625,14 @@ function emitError(message) {
|
|
|
605
625
|
process.exit(1);
|
|
606
626
|
}
|
|
607
627
|
|
|
628
|
+
/** Erro de declaração do consumidor: a mensagem basta, sem a pilha do runner. */
|
|
629
|
+
function declarationError(message) {
|
|
630
|
+
const err = new Error(message);
|
|
631
|
+
err.name = "OpusDeclarationError";
|
|
632
|
+
return err;
|
|
633
|
+
}
|
|
634
|
+
|
|
608
635
|
main().catch((err) => {
|
|
636
|
+
if (err?.name === "OpusDeclarationError") return emitError(err.message);
|
|
609
637
|
emitError(`runner crash: ${err.stack ?? err.message ?? String(err)}`);
|
|
610
638
|
});
|
package/bin/lib/mcp.mjs
CHANGED
|
@@ -42,7 +42,7 @@ export function buildServer() {
|
|
|
42
42
|
server.registerTool(
|
|
43
43
|
'opus_check',
|
|
44
44
|
{
|
|
45
|
-
description: 'Valida as convenções das actions (régua) — defineAction e o split defineContract+bindAction: naming, kind, ordem dos campos, export, requires-sem-authorize, ignored-by-kind, soft-delete-retention. Retorna findings.',
|
|
45
|
+
description: 'Valida as convenções das actions (régua) — defineAction e o split defineContract+bindAction: naming, kind, ordem dos campos, export, requires-sem-authorize, ignored-by-kind, assistant-declaration, soft-delete-retention. Retorna findings.',
|
|
46
46
|
inputSchema: { dir: z.string().optional() },
|
|
47
47
|
},
|
|
48
48
|
async ({ dir }) => asText(await scanDir(dirOf(dir))),
|