@creative-ia/cortex 1.0.5 → 1.0.7
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/config/auto-changelog.d.ts +5 -0
- package/dist/config/auto-changelog.js +141 -0
- package/dist/config/cloudwatch-store.js +37 -17
- package/dist/config/license.d.ts +12 -0
- package/dist/config/license.js +21 -7
- package/dist/config/ssm-store.js +29 -10
- package/dist/index.js +24 -0
- package/dist/knowledge/dynamo-store.js +32 -33
- package/dist/knowledge/embeddings.js +20 -10
- package/dist/knowledge/loader.js +23 -12
- package/package.json +1 -1
- package/dist/lambda-package.zip +0 -0
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auto-Changelog — Registra automaticamente no DynamoDB changelog
|
|
3
|
+
* quando uma tool de escrita é executada com sucesso.
|
|
4
|
+
*
|
|
5
|
+
* Tools de leitura (get_*, search_*, list_*) são ignoradas.
|
|
6
|
+
* O registro é fire-and-forget (não bloqueia a resposta da tool).
|
|
7
|
+
*/
|
|
8
|
+
import { putEntry } from "../knowledge/dynamo-store.js";
|
|
9
|
+
import { countByDomain } from "../knowledge/dynamo-store.js";
|
|
10
|
+
// Tools que geram registro automatico no changelog
|
|
11
|
+
const WRITE_TOOLS = new Set([
|
|
12
|
+
"save_knowledge",
|
|
13
|
+
"delete_knowledge",
|
|
14
|
+
"create_process",
|
|
15
|
+
"update_process",
|
|
16
|
+
"advance_process",
|
|
17
|
+
"decompose_epic",
|
|
18
|
+
"generate_wiki",
|
|
19
|
+
"generate_report",
|
|
20
|
+
"reverse_engineer",
|
|
21
|
+
"analyze_docs",
|
|
22
|
+
"init_process",
|
|
23
|
+
"validate_idea",
|
|
24
|
+
"analyze_code",
|
|
25
|
+
"log_event",
|
|
26
|
+
]);
|
|
27
|
+
// Tools que NUNCA geram changelog (leitura, config, busca)
|
|
28
|
+
const READ_TOOLS = new Set([
|
|
29
|
+
"get_code_standards",
|
|
30
|
+
"get_architecture_guidance",
|
|
31
|
+
"get_knowledge",
|
|
32
|
+
"search_knowledge",
|
|
33
|
+
"list_knowledge_domains",
|
|
34
|
+
"get_config",
|
|
35
|
+
"search_logs",
|
|
36
|
+
"insights_query",
|
|
37
|
+
"semantic_search",
|
|
38
|
+
"validate_process",
|
|
39
|
+
]);
|
|
40
|
+
// Evita changelog recursivo (save_knowledge salvando changelog sobre si mesmo)
|
|
41
|
+
let _recording = false;
|
|
42
|
+
/**
|
|
43
|
+
* Registra uma tool call no changelog do DynamoDB.
|
|
44
|
+
* Fire-and-forget — erros são silenciados.
|
|
45
|
+
*/
|
|
46
|
+
export async function recordToolCall(toolName, args, resultPreview) {
|
|
47
|
+
// Ignora tools de leitura
|
|
48
|
+
if (READ_TOOLS.has(toolName))
|
|
49
|
+
return;
|
|
50
|
+
// Ignora tools não mapeadas como escrita
|
|
51
|
+
if (!WRITE_TOOLS.has(toolName))
|
|
52
|
+
return;
|
|
53
|
+
// Evita recursão (save_knowledge chamando recordToolCall que chama save_knowledge)
|
|
54
|
+
if (_recording)
|
|
55
|
+
return;
|
|
56
|
+
// Ignora se o próprio save_knowledge está salvando no domain "changelog"
|
|
57
|
+
if (toolName === "save_knowledge" && args.domain === "changelog")
|
|
58
|
+
return;
|
|
59
|
+
_recording = true;
|
|
60
|
+
try {
|
|
61
|
+
const nextId = await getNextTaskId();
|
|
62
|
+
const title = buildTitle(toolName, args);
|
|
63
|
+
const content = buildContent(toolName, args, resultPreview);
|
|
64
|
+
const tags = buildTags(toolName, args);
|
|
65
|
+
await putEntry({
|
|
66
|
+
domain: "changelog",
|
|
67
|
+
id: nextId,
|
|
68
|
+
title,
|
|
69
|
+
content,
|
|
70
|
+
tags,
|
|
71
|
+
created_at: new Date().toISOString(),
|
|
72
|
+
updated_at: new Date().toISOString(),
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
// Fire-and-forget — não quebra a tool original
|
|
77
|
+
}
|
|
78
|
+
finally {
|
|
79
|
+
_recording = false;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
async function getNextTaskId() {
|
|
83
|
+
try {
|
|
84
|
+
const count = await countByDomain("changelog");
|
|
85
|
+
return `TASK-${String(count + 1).padStart(3, "0")}`;
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
// Fallback com timestamp
|
|
89
|
+
return `TASK-AUTO-${Date.now()}`;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
function buildTitle(toolName, args) {
|
|
93
|
+
const toolLabel = toolName.replace(/_/g, " ");
|
|
94
|
+
switch (toolName) {
|
|
95
|
+
case "save_knowledge":
|
|
96
|
+
return `[auto] ${toolLabel}: [${args.domain}] ${args.id} — ${(args.title || "").slice(0, 80)}`;
|
|
97
|
+
case "delete_knowledge":
|
|
98
|
+
return `[auto] ${toolLabel}: [${args.domain}] ${args.id}`;
|
|
99
|
+
case "create_process":
|
|
100
|
+
return `[auto] ${toolLabel}: ${(args.prompt || "").slice(0, 80)}`;
|
|
101
|
+
case "update_process":
|
|
102
|
+
return `[auto] ${toolLabel}: processo ${args.processId}`;
|
|
103
|
+
case "advance_process":
|
|
104
|
+
return `[auto] ${toolLabel}: processo ${args.processId}${args.epicId ? ` epic=${args.epicId}` : ""}`;
|
|
105
|
+
case "decompose_epic":
|
|
106
|
+
return `[auto] ${toolLabel}: processo ${args.processId} epic=${args.epicId}`;
|
|
107
|
+
case "generate_wiki":
|
|
108
|
+
return `[auto] ${toolLabel}: processo ${args.processId}`;
|
|
109
|
+
case "generate_report":
|
|
110
|
+
return `[auto] ${toolLabel}: ${(args.title || "").slice(0, 80)}`;
|
|
111
|
+
case "reverse_engineer":
|
|
112
|
+
return `[auto] ${toolLabel}: ${(args.repoDir || "").split("/").pop()}`;
|
|
113
|
+
case "analyze_docs":
|
|
114
|
+
return `[auto] ${toolLabel}: ${(args.docsDir || "").split("/").pop()}`;
|
|
115
|
+
case "validate_idea":
|
|
116
|
+
return `[auto] ${toolLabel}: ${(args.idea || "").slice(0, 80)}`;
|
|
117
|
+
case "analyze_code":
|
|
118
|
+
return `[auto] ${toolLabel}: ${args.filename || "inline"}`;
|
|
119
|
+
default:
|
|
120
|
+
return `[auto] ${toolLabel}`;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
function buildContent(toolName, args, resultPreview) {
|
|
124
|
+
const sanitizedArgs = { ...args };
|
|
125
|
+
delete sanitizedArgs.licenseKey; // nunca logar licenseKey
|
|
126
|
+
delete sanitizedArgs.code; // codigo pode ser grande
|
|
127
|
+
delete sanitizedArgs.content; // conteudo pode ser grande
|
|
128
|
+
const argsStr = JSON.stringify(sanitizedArgs, null, 2);
|
|
129
|
+
const preview = resultPreview.slice(0, 500);
|
|
130
|
+
return `## Auto-changelog (${new Date().toISOString()})\n\nTool: ${toolName}\nArgs: ${argsStr}\n\nResultado (preview): ${preview}`;
|
|
131
|
+
}
|
|
132
|
+
function buildTags(toolName, args) {
|
|
133
|
+
const tags = ["auto-changelog", toolName.replace(/_/g, "-")];
|
|
134
|
+
if (args.domain)
|
|
135
|
+
tags.push(args.domain);
|
|
136
|
+
if (args.processId)
|
|
137
|
+
tags.push(`process-${args.processId}`);
|
|
138
|
+
if (args.epicId)
|
|
139
|
+
tags.push(`epic-${args.epicId}`);
|
|
140
|
+
return tags;
|
|
141
|
+
}
|
|
@@ -1,30 +1,45 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* CloudWatch Logs — Escrita e consulta de logs do MCP Server.
|
|
3
3
|
* Log group: /creative-ia50/mcp-server
|
|
4
|
+
*
|
|
5
|
+
* Lazy import — AWS SDK so e carregado quando necessario.
|
|
4
6
|
*/
|
|
5
|
-
import { CloudWatchLogsClient, CreateLogStreamCommand, PutLogEventsCommand, FilterLogEventsCommand, StartQueryCommand, GetQueryResultsCommand, } from "@aws-sdk/client-cloudwatch-logs";
|
|
6
7
|
const isLocal = process.env.USE_LOCALSTACK === "true";
|
|
7
|
-
const
|
|
8
|
-
region: process.env.AWS_REGION || "sa-east-1",
|
|
9
|
-
...(isLocal && {
|
|
10
|
-
endpoint: "http://localhost:4566",
|
|
11
|
-
credentials: { accessKeyId: "test", secretAccessKey: "test" },
|
|
12
|
-
}),
|
|
13
|
-
});
|
|
8
|
+
const CLOUD_MODE = !!process.env.CORTEX_API_URL;
|
|
14
9
|
const LOG_GROUP = process.env.LOG_GROUP || "/creative-ia50/mcp-server";
|
|
10
|
+
let _cwl = null;
|
|
11
|
+
let _cmds = {};
|
|
12
|
+
async function getCwl() {
|
|
13
|
+
if (_cwl)
|
|
14
|
+
return _cwl;
|
|
15
|
+
const mod = await import("@aws-sdk/client-cloudwatch-logs");
|
|
16
|
+
_cmds = mod;
|
|
17
|
+
_cwl = new mod.CloudWatchLogsClient({
|
|
18
|
+
region: process.env.AWS_REGION || "sa-east-1",
|
|
19
|
+
...(isLocal && {
|
|
20
|
+
endpoint: "http://localhost:4566",
|
|
21
|
+
credentials: { accessKeyId: "test", secretAccessKey: "test" },
|
|
22
|
+
}),
|
|
23
|
+
});
|
|
24
|
+
return _cwl;
|
|
25
|
+
}
|
|
15
26
|
// Ensure log stream exists for today
|
|
16
27
|
async function ensureStream(streamName) {
|
|
17
28
|
try {
|
|
18
|
-
|
|
29
|
+
const cwl = await getCwl();
|
|
30
|
+
await cwl.send(new _cmds.CreateLogStreamCommand({ logGroupName: LOG_GROUP, logStreamName: streamName }));
|
|
19
31
|
}
|
|
20
32
|
catch { /* already exists */ }
|
|
21
33
|
}
|
|
22
34
|
// Write a log event
|
|
23
35
|
export async function writeLog(level, message, meta) {
|
|
36
|
+
if (CLOUD_MODE)
|
|
37
|
+
return; // cloud mode — logs go via cloud proxy
|
|
24
38
|
const stream = `mcp-server/${new Date().toISOString().slice(0, 10)}`;
|
|
25
39
|
await ensureStream(stream);
|
|
26
40
|
const event = JSON.stringify({ level, message, ...meta, timestamp: new Date().toISOString() });
|
|
27
|
-
|
|
41
|
+
const cwl = await getCwl();
|
|
42
|
+
await cwl.send(new _cmds.PutLogEventsCommand({
|
|
28
43
|
logGroupName: LOG_GROUP,
|
|
29
44
|
logStreamName: stream,
|
|
30
45
|
logEvents: [{ timestamp: Date.now(), message: event }],
|
|
@@ -32,20 +47,26 @@ export async function writeLog(level, message, meta) {
|
|
|
32
47
|
}
|
|
33
48
|
// Filter logs by pattern
|
|
34
49
|
export async function filterLogs(params) {
|
|
50
|
+
if (CLOUD_MODE)
|
|
51
|
+
return [];
|
|
35
52
|
const now = Date.now();
|
|
36
|
-
const
|
|
53
|
+
const cwl = await getCwl();
|
|
54
|
+
const res = await cwl.send(new _cmds.FilterLogEventsCommand({
|
|
37
55
|
logGroupName: LOG_GROUP,
|
|
38
56
|
filterPattern: params.pattern || "",
|
|
39
|
-
startTime: params.startTime || now - 24 * 60 * 60 * 1000,
|
|
57
|
+
startTime: params.startTime || now - 24 * 60 * 60 * 1000,
|
|
40
58
|
endTime: params.endTime || now,
|
|
41
59
|
limit: params.limit || 50,
|
|
42
60
|
}));
|
|
43
|
-
return (res.events || []).map(e => e.message || "");
|
|
61
|
+
return (res.events || []).map((e) => e.message || "");
|
|
44
62
|
}
|
|
45
63
|
// Run CloudWatch Insights query
|
|
46
64
|
export async function queryLogs(params) {
|
|
65
|
+
if (CLOUD_MODE)
|
|
66
|
+
return [];
|
|
47
67
|
const now = Date.now();
|
|
48
|
-
const
|
|
68
|
+
const cwl = await getCwl();
|
|
69
|
+
const start = await cwl.send(new _cmds.StartQueryCommand({
|
|
49
70
|
logGroupName: LOG_GROUP,
|
|
50
71
|
queryString: params.query,
|
|
51
72
|
startTime: Math.floor((params.startTime || now - 24 * 60 * 60 * 1000) / 1000),
|
|
@@ -54,12 +75,11 @@ export async function queryLogs(params) {
|
|
|
54
75
|
}));
|
|
55
76
|
if (!start.queryId)
|
|
56
77
|
return [];
|
|
57
|
-
// Poll for results (max 10s)
|
|
58
78
|
for (let i = 0; i < 20; i++) {
|
|
59
79
|
await new Promise(r => setTimeout(r, 500));
|
|
60
|
-
const res = await cwl.send(new GetQueryResultsCommand({ queryId: start.queryId }));
|
|
80
|
+
const res = await cwl.send(new _cmds.GetQueryResultsCommand({ queryId: start.queryId }));
|
|
61
81
|
if (res.status === "Complete" || res.status === "Failed" || res.status === "Cancelled") {
|
|
62
|
-
return (res.results || []).map(row => row.map(f => `${f.field}: ${f.value}`));
|
|
82
|
+
return (res.results || []).map((row) => row.map((f) => `${f.field}: ${f.value}`));
|
|
63
83
|
}
|
|
64
84
|
}
|
|
65
85
|
return [];
|
package/dist/config/license.d.ts
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* License Validator — Valida licença do cliente.
|
|
3
|
+
*
|
|
4
|
+
* Dois modos:
|
|
5
|
+
* 1. DynamoDB direto (Lambda / dev com AWS_PROFILE) — quando CORTEX_API_URL nao esta definido
|
|
6
|
+
* 2. HTTP remoto (cliente local via npx) — quando CORTEX_API_URL esta definido
|
|
7
|
+
* Chama o endpoint nuvem para validar, com cache em memoria (TTL 5min)
|
|
8
|
+
*
|
|
9
|
+
* RBAC:
|
|
10
|
+
* - role "admin" → acesso total a todos os domains e tools
|
|
11
|
+
* - role "user" → acesso restrito aos domains em allowedDomains
|
|
12
|
+
*/
|
|
1
13
|
export interface License {
|
|
2
14
|
licenseKey: string;
|
|
3
15
|
clientName: string;
|
package/dist/config/license.js
CHANGED
|
@@ -10,19 +10,30 @@
|
|
|
10
10
|
* - role "admin" → acesso total a todos os domains e tools
|
|
11
11
|
* - role "user" → acesso restrito aos domains em allowedDomains
|
|
12
12
|
*/
|
|
13
|
-
import
|
|
14
|
-
|
|
13
|
+
// Dynamic import — AWS SDK so e carregado quando necessario (modo DynamoDB direto).
|
|
14
|
+
// Isso evita o erro "Could not load credentials from any providers" em maquinas
|
|
15
|
+
// que usam CORTEX_API_URL (cloud proxy) e nao tem credenciais AWS configuradas.
|
|
15
16
|
const isLocal = process.env.USE_LOCALSTACK === "true";
|
|
16
|
-
const CORTEX_API_URL = process.env.CORTEX_API_URL; // ex: https://
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
const CORTEX_API_URL = process.env.CORTEX_API_URL; // ex: https://mcp.creativeia50.com/mcp
|
|
18
|
+
let _ddb = null;
|
|
19
|
+
let _GetCommand = null;
|
|
20
|
+
async function getDdb() {
|
|
21
|
+
if (CORTEX_API_URL)
|
|
22
|
+
return null; // cloud mode — nao precisa de DynamoDB
|
|
23
|
+
if (_ddb)
|
|
24
|
+
return _ddb;
|
|
25
|
+
const { DynamoDBClient } = await import("@aws-sdk/client-dynamodb");
|
|
26
|
+
const { DynamoDBDocumentClient, GetCommand } = await import("@aws-sdk/lib-dynamodb");
|
|
27
|
+
_GetCommand = GetCommand;
|
|
28
|
+
_ddb = DynamoDBDocumentClient.from(new DynamoDBClient({
|
|
20
29
|
region: process.env.AWS_REGION || "sa-east-1",
|
|
21
30
|
...(isLocal && {
|
|
22
31
|
endpoint: "http://localhost:4566",
|
|
23
32
|
credentials: { accessKeyId: "test", secretAccessKey: "test" },
|
|
24
33
|
}),
|
|
25
34
|
}));
|
|
35
|
+
return _ddb;
|
|
36
|
+
}
|
|
26
37
|
const TABLE = process.env.LICENSE_TABLE || "creative-ia50-licenses";
|
|
27
38
|
const licenseCache = new Map();
|
|
28
39
|
const CACHE_TTL_MS = 5 * 60 * 1000; // 5 minutos
|
|
@@ -102,7 +113,10 @@ async function validateLicenseRemote(licenseKey, _toolName) {
|
|
|
102
113
|
*/
|
|
103
114
|
async function validateLicenseDynamo(licenseKey, toolName) {
|
|
104
115
|
try {
|
|
105
|
-
const
|
|
116
|
+
const ddb = await getDdb();
|
|
117
|
+
if (!ddb)
|
|
118
|
+
return { valid: false, reason: "DynamoDB not available (cloud mode?)" };
|
|
119
|
+
const res = await ddb.send(new _GetCommand({ TableName: TABLE, Key: { licenseKey } }));
|
|
106
120
|
if (!res.Item) {
|
|
107
121
|
return { valid: false, reason: "License not found" };
|
|
108
122
|
}
|
package/dist/config/ssm-store.js
CHANGED
|
@@ -2,19 +2,35 @@
|
|
|
2
2
|
* SSM Parameter Store — Registry de configuração do MCP Server.
|
|
3
3
|
* Lê parâmetros de /creative-ia50/mcp-server/*
|
|
4
4
|
*/
|
|
5
|
-
import
|
|
5
|
+
// Lazy import — AWS SDK so e carregado quando necessario (evita erro de credenciais em cloud mode)
|
|
6
6
|
const isLocal = process.env.USE_LOCALSTACK === "true";
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
7
|
+
const CLOUD_MODE = !!process.env.CORTEX_API_URL;
|
|
8
|
+
let _ssm = null;
|
|
9
|
+
let _GetParameterCommand = null;
|
|
10
|
+
let _GetParametersByPathCommand = null;
|
|
11
|
+
async function getSsm() {
|
|
12
|
+
if (_ssm)
|
|
13
|
+
return _ssm;
|
|
14
|
+
const { SSMClient } = await import("@aws-sdk/client-ssm");
|
|
15
|
+
const mod = await import("@aws-sdk/client-ssm");
|
|
16
|
+
_GetParameterCommand = mod.GetParameterCommand;
|
|
17
|
+
_GetParametersByPathCommand = mod.GetParametersByPathCommand;
|
|
18
|
+
_ssm = new SSMClient({
|
|
19
|
+
region: process.env.AWS_REGION || "sa-east-1",
|
|
20
|
+
...(isLocal && {
|
|
21
|
+
endpoint: "http://localhost:4566",
|
|
22
|
+
credentials: { accessKeyId: "test", secretAccessKey: "test" },
|
|
23
|
+
}),
|
|
24
|
+
});
|
|
25
|
+
return _ssm;
|
|
26
|
+
}
|
|
14
27
|
const PREFIX = "/creative-ia50/mcp-server";
|
|
15
28
|
export async function getParam(name) {
|
|
29
|
+
if (CLOUD_MODE)
|
|
30
|
+
return null;
|
|
16
31
|
try {
|
|
17
|
-
const
|
|
32
|
+
const ssm = await getSsm();
|
|
33
|
+
const res = await ssm.send(new _GetParameterCommand({ Name: `${PREFIX}/${name}` }));
|
|
18
34
|
return res.Parameter?.Value || null;
|
|
19
35
|
}
|
|
20
36
|
catch {
|
|
@@ -22,9 +38,12 @@ export async function getParam(name) {
|
|
|
22
38
|
}
|
|
23
39
|
}
|
|
24
40
|
export async function getAllParams() {
|
|
41
|
+
if (CLOUD_MODE)
|
|
42
|
+
return {};
|
|
25
43
|
const result = {};
|
|
26
44
|
try {
|
|
27
|
-
const
|
|
45
|
+
const ssm = await getSsm();
|
|
46
|
+
const res = await ssm.send(new _GetParametersByPathCommand({
|
|
28
47
|
Path: PREFIX,
|
|
29
48
|
Recursive: true,
|
|
30
49
|
}));
|
package/dist/index.js
CHANGED
|
@@ -15,6 +15,7 @@ import { z } from "zod";
|
|
|
15
15
|
import { validateLicense, canAccessDomain } from "./config/license.js";
|
|
16
16
|
import { isCloudProxyEnabled, callCloudTool } from "./config/cloud-proxy.js";
|
|
17
17
|
import { withTelemetry, reportTelemetry } from "./config/telemetry.js";
|
|
18
|
+
import { recordToolCall } from "./config/auto-changelog.js";
|
|
18
19
|
import { getCodeStandards } from "./tools/get-code-standards.js";
|
|
19
20
|
import { analyzeCode } from "./tools/analyze-code.js";
|
|
20
21
|
import { getArchitectureGuidance } from "./tools/get-architecture.js";
|
|
@@ -51,6 +52,15 @@ async function licenseGate(licenseKey, toolName) {
|
|
|
51
52
|
function cloudResult(text) {
|
|
52
53
|
return { content: [{ type: "text", text }] };
|
|
53
54
|
}
|
|
55
|
+
// --- Helper: auto-changelog fire-and-forget ---
|
|
56
|
+
// Registra tool calls de escrita no changelog do DynamoDB.
|
|
57
|
+
// Nao bloqueia a resposta — roda em background.
|
|
58
|
+
function autoLog(toolName, args, resultText) {
|
|
59
|
+
if (!CLOUD_MODE) {
|
|
60
|
+
// Apenas em modo direto (Lambda). Em cloud mode o server remoto ja registra.
|
|
61
|
+
recordToolCall(toolName, args, resultText).catch(() => { });
|
|
62
|
+
}
|
|
63
|
+
}
|
|
54
64
|
async function proxyToCloud(toolName, args) {
|
|
55
65
|
const start = Date.now();
|
|
56
66
|
try {
|
|
@@ -102,6 +112,7 @@ server.tool("analyze_code", "Analyzes code against L1-L4 standards. Returns viol
|
|
|
102
112
|
if ("denied" in gate)
|
|
103
113
|
return { content: [{ type: "text", text: gate.denied }] };
|
|
104
114
|
const result = await analyzeCode({ code, language, filename });
|
|
115
|
+
autoLog("analyze_code", { language, filename }, result);
|
|
105
116
|
return { content: [{ type: "text", text: result }] };
|
|
106
117
|
});
|
|
107
118
|
// --- Tool: get_architecture_guidance ---
|
|
@@ -131,6 +142,7 @@ server.tool("init_process", "Initializes a new process with UUID, checklist, and
|
|
|
131
142
|
if ("denied" in gate)
|
|
132
143
|
return { content: [{ type: "text", text: gate.denied }] };
|
|
133
144
|
const result = await initProcess({ title, stakeholder, description });
|
|
145
|
+
autoLog("init_process", { title, stakeholder }, result);
|
|
134
146
|
return { content: [{ type: "text", text: result }] };
|
|
135
147
|
});
|
|
136
148
|
// --- Tool: validate_process ---
|
|
@@ -159,6 +171,7 @@ server.tool("validate_idea", "Analyzes a business idea against the company knowl
|
|
|
159
171
|
if ("denied" in gate)
|
|
160
172
|
return { content: [{ type: "text", text: gate.denied }] };
|
|
161
173
|
const result = await validateIdea({ idea, context });
|
|
174
|
+
autoLog("validate_idea", { idea: idea.slice(0, 200) }, result);
|
|
162
175
|
return { content: [{ type: "text", text: result }] };
|
|
163
176
|
});
|
|
164
177
|
// --- Tool: generate_report ---
|
|
@@ -173,6 +186,7 @@ server.tool("generate_report", "Generates a branded HTML report on the client ma
|
|
|
173
186
|
if ("denied" in gate)
|
|
174
187
|
return { content: [{ type: "text", text: gate.denied }] };
|
|
175
188
|
const result = await withTelemetry("generate_report", () => generateReport({ title, content, reportType, outputDir }), (r) => `report=${reportType} title=${title.slice(0, 50)}`, { reportType });
|
|
189
|
+
autoLog("generate_report", { title, reportType }, result);
|
|
176
190
|
return { content: [{ type: "text", text: result }] };
|
|
177
191
|
});
|
|
178
192
|
// =============================================================================
|
|
@@ -196,6 +210,7 @@ server.tool("save_knowledge", "Salva ou atualiza um registro de conhecimento org
|
|
|
196
210
|
if (!canAccessDomain(gate.license, domain))
|
|
197
211
|
return { content: [{ type: "text", text: `Access denied: domain '${domain}' not allowed for this license` }] };
|
|
198
212
|
const result = await saveKnowledge({ domain, id, title, content, tags, metadata });
|
|
213
|
+
autoLog("save_knowledge", { domain, id, title, tags }, result);
|
|
199
214
|
return { content: [{ type: "text", text: result }] };
|
|
200
215
|
});
|
|
201
216
|
// --- Tool: get_knowledge ---
|
|
@@ -247,6 +262,7 @@ server.tool("delete_knowledge", "Remove um registro de conhecimento organizacion
|
|
|
247
262
|
if (!canAccessDomain(gate.license, domain))
|
|
248
263
|
return { content: [{ type: "text", text: `Access denied: domain '${domain}' not allowed for this license` }] };
|
|
249
264
|
const result = await deleteKnowledgeEntry({ domain, id });
|
|
265
|
+
autoLog("delete_knowledge", { domain, id }, result);
|
|
250
266
|
return { content: [{ type: "text", text: result }] };
|
|
251
267
|
});
|
|
252
268
|
// --- Tool: list_knowledge_domains ---
|
|
@@ -290,6 +306,7 @@ server.tool("log_event", "Registra um evento de log no CloudWatch Logs do MCP Se
|
|
|
290
306
|
if ("denied" in gate)
|
|
291
307
|
return { content: [{ type: "text", text: gate.denied }] };
|
|
292
308
|
const result = await logEvent({ level, message, meta });
|
|
309
|
+
autoLog("log_event", { level, message: message.slice(0, 200) }, result);
|
|
293
310
|
return { content: [{ type: "text", text: result }] };
|
|
294
311
|
});
|
|
295
312
|
// --- Tool: search_logs ---
|
|
@@ -355,6 +372,7 @@ server.tool("create_process", "Cria esqueleto de processo: recebe frase em lingu
|
|
|
355
372
|
if ("denied" in gate)
|
|
356
373
|
return { content: [{ type: "text", text: gate.denied }] };
|
|
357
374
|
const result = await withTelemetry("create_process", () => createProcess({ prompt, stakeholder }), (r) => `prompt=${prompt.slice(0, 80)}`, { stakeholder });
|
|
375
|
+
autoLog("create_process", { prompt: prompt.slice(0, 200), stakeholder }, result);
|
|
358
376
|
return { content: [{ type: "text", text: result }] };
|
|
359
377
|
});
|
|
360
378
|
// --- Tool: update_process ---
|
|
@@ -380,6 +398,7 @@ server.tool("update_process", "Acrescenta informações a um processo existente
|
|
|
380
398
|
if ("denied" in gate)
|
|
381
399
|
return { content: [{ type: "text", text: gate.denied }] };
|
|
382
400
|
const result = await updateProcess({ processId, updates, listProcesses });
|
|
401
|
+
autoLog("update_process", { processId, listProcesses }, result);
|
|
383
402
|
return { content: [{ type: "text", text: result }] };
|
|
384
403
|
});
|
|
385
404
|
// --- Tool: decompose_epic ---
|
|
@@ -393,6 +412,7 @@ server.tool("decompose_epic", "Decompõe um épico (EP-XX) em artefatos completo
|
|
|
393
412
|
if ("denied" in gate)
|
|
394
413
|
return { content: [{ type: "text", text: gate.denied }] };
|
|
395
414
|
const result = await withTelemetry("decompose_epic", () => decomposeEpic({ processId, epicId, processDir }), (r) => `process=${processId} epic=${epicId}`, { processId, epicId });
|
|
415
|
+
autoLog("decompose_epic", { processId, epicId }, result);
|
|
396
416
|
return { content: [{ type: "text", text: result }] };
|
|
397
417
|
});
|
|
398
418
|
// --- Tool: advance_process ---
|
|
@@ -406,6 +426,7 @@ server.tool("advance_process", "Orquestrador inteligente: detecta a fase atual d
|
|
|
406
426
|
if ("denied" in gate)
|
|
407
427
|
return { content: [{ type: "text", text: gate.denied }] };
|
|
408
428
|
const result = await withTelemetry("advance_process", () => advanceProcess({ processId, processDir, epicId }), (r) => `process=${processId}${epicId ? ` epic=${epicId}` : ""}`, { processId, epicId });
|
|
429
|
+
autoLog("advance_process", { processId, epicId }, result);
|
|
409
430
|
return { content: [{ type: "text", text: result }] };
|
|
410
431
|
});
|
|
411
432
|
// --- Tool: generate_wiki ---
|
|
@@ -418,6 +439,7 @@ server.tool("generate_wiki", "Gera/atualiza wiki navegável do processo: index.h
|
|
|
418
439
|
if ("denied" in gate)
|
|
419
440
|
return { content: [{ type: "text", text: gate.denied }] };
|
|
420
441
|
const result = await withTelemetry("generate_wiki", () => generateWiki({ processId, processDir }), (r) => `process=${processId}`, { processId });
|
|
442
|
+
autoLog("generate_wiki", { processId }, JSON.stringify(result));
|
|
421
443
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
422
444
|
});
|
|
423
445
|
// --- Tool: reverse_engineer ---
|
|
@@ -432,6 +454,7 @@ server.tool("reverse_engineer", "Analisa repositório local ou remoto (Git URL)
|
|
|
432
454
|
if ("denied" in gate)
|
|
433
455
|
return { content: [{ type: "text", text: gate.denied }] };
|
|
434
456
|
const result = await withTelemetry("reverse_engineer", () => reverseEngineer({ repoDir, processId, processDir, outputFilename }), (r) => `repo=${repoDir.split("/").pop() || repoDir}`, { repoDir, processId });
|
|
457
|
+
autoLog("reverse_engineer", { repoDir: repoDir.split("/").pop() || repoDir, processId }, JSON.stringify(result));
|
|
435
458
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
436
459
|
});
|
|
437
460
|
// --- Tool: analyze_docs ---
|
|
@@ -446,6 +469,7 @@ server.tool("analyze_docs", "Analisa um diretório de documentação, extrai tex
|
|
|
446
469
|
if ("denied" in gate)
|
|
447
470
|
return { content: [{ type: "text", text: gate.denied }] };
|
|
448
471
|
const result = await withTelemetry("analyze_docs", () => analyzeDocs({ docsDir, processId, processDir, maxFiles }), (r) => `dir=${docsDir.split("/").pop() || docsDir}`, { docsDir, processId });
|
|
472
|
+
autoLog("analyze_docs", { docsDir: docsDir.split("/").pop() || docsDir, processId }, result);
|
|
449
473
|
return { content: [{ type: "text", text: result }] };
|
|
450
474
|
});
|
|
451
475
|
// --- Start server ---
|
|
@@ -5,38 +5,42 @@
|
|
|
5
5
|
* PK: domain (ex: "decisions", "changelog", "troubleshooting", "glossary")
|
|
6
6
|
* SK: id (ex: "ADR-001", "TASK-030", "TS-005", "TERM-001")
|
|
7
7
|
*
|
|
8
|
-
*
|
|
8
|
+
* Lazy import — AWS SDK so e carregado quando necessario.
|
|
9
9
|
*/
|
|
10
|
-
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
|
|
11
|
-
import { DynamoDBDocumentClient, PutCommand, GetCommand, QueryCommand, DeleteCommand, } from "@aws-sdk/lib-dynamodb";
|
|
12
10
|
const isLocal = process.env.USE_LOCALSTACK === "true";
|
|
13
|
-
const ddb = DynamoDBDocumentClient.from(new DynamoDBClient({
|
|
14
|
-
region: process.env.AWS_REGION || "sa-east-1",
|
|
15
|
-
...(isLocal && {
|
|
16
|
-
endpoint: "http://localhost:4566",
|
|
17
|
-
credentials: { accessKeyId: "test", secretAccessKey: "test" },
|
|
18
|
-
}),
|
|
19
|
-
}));
|
|
20
11
|
const TABLE = process.env.KNOWLEDGE_TABLE || "creative-ia50-knowledge";
|
|
21
|
-
|
|
12
|
+
let _ddb = null;
|
|
13
|
+
let _cmds = {};
|
|
14
|
+
async function getDdb() {
|
|
15
|
+
if (_ddb)
|
|
16
|
+
return _ddb;
|
|
17
|
+
const { DynamoDBClient } = await import("@aws-sdk/client-dynamodb");
|
|
18
|
+
const mod = await import("@aws-sdk/lib-dynamodb");
|
|
19
|
+
_cmds = mod;
|
|
20
|
+
_ddb = mod.DynamoDBDocumentClient.from(new DynamoDBClient({
|
|
21
|
+
region: process.env.AWS_REGION || "sa-east-1",
|
|
22
|
+
...(isLocal && {
|
|
23
|
+
endpoint: "http://localhost:4566",
|
|
24
|
+
credentials: { accessKeyId: "test", secretAccessKey: "test" },
|
|
25
|
+
}),
|
|
26
|
+
}));
|
|
27
|
+
return _ddb;
|
|
28
|
+
}
|
|
22
29
|
export async function putEntry(entry) {
|
|
23
|
-
|
|
30
|
+
const ddb = await getDdb();
|
|
31
|
+
await ddb.send(new _cmds.PutCommand({
|
|
24
32
|
TableName: TABLE,
|
|
25
|
-
Item: {
|
|
26
|
-
...entry,
|
|
27
|
-
primary_tag: entry.tags?.[0] || "untagged",
|
|
28
|
-
updated_at: new Date().toISOString(),
|
|
29
|
-
},
|
|
33
|
+
Item: { ...entry, primary_tag: entry.tags?.[0] || "untagged", updated_at: new Date().toISOString() },
|
|
30
34
|
}));
|
|
31
35
|
}
|
|
32
|
-
// --- GET single entry ---
|
|
33
36
|
export async function getEntry(domain, id) {
|
|
34
|
-
const
|
|
37
|
+
const ddb = await getDdb();
|
|
38
|
+
const res = await ddb.send(new _cmds.GetCommand({ TableName: TABLE, Key: { domain, id } }));
|
|
35
39
|
return res.Item || null;
|
|
36
40
|
}
|
|
37
|
-
// --- QUERY by domain (list all entries in a domain) ---
|
|
38
41
|
export async function queryByDomain(domain, limit) {
|
|
39
|
-
const
|
|
42
|
+
const ddb = await getDdb();
|
|
43
|
+
const res = await ddb.send(new _cmds.QueryCommand({
|
|
40
44
|
TableName: TABLE,
|
|
41
45
|
KeyConditionExpression: "#d = :domain",
|
|
42
46
|
ExpressionAttributeNames: { "#d": "domain" },
|
|
@@ -46,11 +50,9 @@ export async function queryByDomain(domain, limit) {
|
|
|
46
50
|
}));
|
|
47
51
|
return res.Items || [];
|
|
48
52
|
}
|
|
49
|
-
// --- SEARCH by tag (uses GSI tag-index) ---
|
|
50
53
|
export async function searchByTag(tag, limit) {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
const res = await ddb.send(new QueryCommand({
|
|
54
|
+
const ddb = await getDdb();
|
|
55
|
+
const res = await ddb.send(new _cmds.QueryCommand({
|
|
54
56
|
TableName: TABLE,
|
|
55
57
|
IndexName: "tag-index",
|
|
56
58
|
KeyConditionExpression: "primary_tag = :tag",
|
|
@@ -59,22 +61,19 @@ export async function searchByTag(tag, limit) {
|
|
|
59
61
|
}));
|
|
60
62
|
return res.Items || [];
|
|
61
63
|
}
|
|
62
|
-
// --- SEARCH by text (simple contains on title + content) ---
|
|
63
64
|
export async function searchByText(domain, text, limit) {
|
|
64
65
|
const all = await queryByDomain(domain, 500);
|
|
65
66
|
const lower = text.toLowerCase();
|
|
66
|
-
const filtered = all.filter((e) => e.title.toLowerCase().includes(lower) ||
|
|
67
|
-
e.content.toLowerCase().includes(lower) ||
|
|
68
|
-
(e.tags && e.tags.some((t) => t.toLowerCase().includes(lower))));
|
|
67
|
+
const filtered = all.filter((e) => e.title.toLowerCase().includes(lower) || e.content.toLowerCase().includes(lower) || (e.tags && e.tags.some((t) => t.toLowerCase().includes(lower))));
|
|
69
68
|
return limit ? filtered.slice(0, limit) : filtered;
|
|
70
69
|
}
|
|
71
|
-
// --- DELETE ---
|
|
72
70
|
export async function deleteEntry(domain, id) {
|
|
73
|
-
|
|
71
|
+
const ddb = await getDdb();
|
|
72
|
+
await ddb.send(new _cmds.DeleteCommand({ TableName: TABLE, Key: { domain, id } }));
|
|
74
73
|
}
|
|
75
|
-
// --- COUNT entries in a domain ---
|
|
76
74
|
export async function countByDomain(domain) {
|
|
77
|
-
const
|
|
75
|
+
const ddb = await getDdb();
|
|
76
|
+
const res = await ddb.send(new _cmds.QueryCommand({
|
|
78
77
|
TableName: TABLE,
|
|
79
78
|
KeyConditionExpression: "#d = :domain",
|
|
80
79
|
ExpressionAttributeNames: { "#d": "domain" },
|
|
@@ -1,22 +1,32 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Bedrock Titan Embeddings — Gera embeddings para semantic search.
|
|
3
3
|
* Model: amazon.titan-embed-text-v2:0 (256 dimensions)
|
|
4
|
-
*
|
|
4
|
+
*
|
|
5
|
+
* Lazy import — AWS SDK so e carregado quando necessario.
|
|
5
6
|
*/
|
|
6
|
-
import { BedrockRuntimeClient, InvokeModelCommand } from "@aws-sdk/client-bedrock-runtime";
|
|
7
7
|
const isLocal = process.env.USE_LOCALSTACK === "true";
|
|
8
|
-
const bedrock = new BedrockRuntimeClient({
|
|
9
|
-
region: process.env.AWS_REGION || "sa-east-1",
|
|
10
|
-
...(isLocal && {
|
|
11
|
-
endpoint: "http://localhost:4566",
|
|
12
|
-
credentials: { accessKeyId: "test", secretAccessKey: "test" },
|
|
13
|
-
}),
|
|
14
|
-
});
|
|
15
8
|
const MODEL_ID = "amazon.titan-embed-text-v2:0";
|
|
16
9
|
const DIMENSIONS = 256;
|
|
10
|
+
let _bedrock = null;
|
|
11
|
+
let _InvokeModelCommand = null;
|
|
12
|
+
async function getBedrock() {
|
|
13
|
+
if (_bedrock)
|
|
14
|
+
return _bedrock;
|
|
15
|
+
const mod = await import("@aws-sdk/client-bedrock-runtime");
|
|
16
|
+
_InvokeModelCommand = mod.InvokeModelCommand;
|
|
17
|
+
_bedrock = new mod.BedrockRuntimeClient({
|
|
18
|
+
region: process.env.AWS_REGION || "sa-east-1",
|
|
19
|
+
...(isLocal && {
|
|
20
|
+
endpoint: "http://localhost:4566",
|
|
21
|
+
credentials: { accessKeyId: "test", secretAccessKey: "test" },
|
|
22
|
+
}),
|
|
23
|
+
});
|
|
24
|
+
return _bedrock;
|
|
25
|
+
}
|
|
17
26
|
export async function getEmbedding(text) {
|
|
27
|
+
const bedrock = await getBedrock();
|
|
18
28
|
const body = JSON.stringify({ inputText: text.slice(0, 8000), dimensions: DIMENSIONS });
|
|
19
|
-
const res = await bedrock.send(new
|
|
29
|
+
const res = await bedrock.send(new _InvokeModelCommand({
|
|
20
30
|
modelId: MODEL_ID,
|
|
21
31
|
body: new TextEncoder().encode(body),
|
|
22
32
|
contentType: "application/json",
|
package/dist/knowledge/loader.js
CHANGED
|
@@ -2,29 +2,40 @@
|
|
|
2
2
|
* Knowledge Loader — Carrega skills e knowledge do S3 (LocalStack ou AWS).
|
|
3
3
|
* O conteúdo NUNCA é exposto diretamente ao cliente.
|
|
4
4
|
* As tools processam o conteúdo e retornam apenas respostas derivadas.
|
|
5
|
+
*
|
|
6
|
+
* Lazy import — AWS SDK so e carregado quando necessario.
|
|
5
7
|
*/
|
|
6
|
-
import { S3Client, GetObjectCommand, ListObjectsV2Command } from "@aws-sdk/client-s3";
|
|
7
8
|
const isLocal = process.env.USE_LOCALSTACK === "true";
|
|
8
|
-
const s3 = new S3Client({
|
|
9
|
-
region: process.env.AWS_REGION || "us-east-1",
|
|
10
|
-
...(isLocal && {
|
|
11
|
-
endpoint: "http://localhost:4566",
|
|
12
|
-
forcePathStyle: true,
|
|
13
|
-
credentials: { accessKeyId: "test", secretAccessKey: "test" },
|
|
14
|
-
}),
|
|
15
|
-
});
|
|
16
9
|
const BUCKET = process.env.SKILLS_BUCKET || "creative-ia50-skills";
|
|
10
|
+
let _s3 = null;
|
|
11
|
+
let _cmds = {};
|
|
12
|
+
async function getS3() {
|
|
13
|
+
if (_s3)
|
|
14
|
+
return _s3;
|
|
15
|
+
const mod = await import("@aws-sdk/client-s3");
|
|
16
|
+
_cmds = mod;
|
|
17
|
+
_s3 = new mod.S3Client({
|
|
18
|
+
region: process.env.AWS_REGION || "us-east-1",
|
|
19
|
+
...(isLocal && {
|
|
20
|
+
endpoint: "http://localhost:4566",
|
|
21
|
+
forcePathStyle: true,
|
|
22
|
+
credentials: { accessKeyId: "test", secretAccessKey: "test" },
|
|
23
|
+
}),
|
|
24
|
+
});
|
|
25
|
+
return _s3;
|
|
26
|
+
}
|
|
17
27
|
async function getObject(key) {
|
|
18
|
-
const
|
|
28
|
+
const s3 = await getS3();
|
|
29
|
+
const cmd = new _cmds.GetObjectCommand({ Bucket: BUCKET, Key: key });
|
|
19
30
|
const res = await s3.send(cmd);
|
|
20
31
|
return (await res.Body?.transformToString()) || "";
|
|
21
32
|
}
|
|
22
33
|
async function listObjects(prefix) {
|
|
23
|
-
const
|
|
34
|
+
const s3 = await getS3();
|
|
35
|
+
const cmd = new _cmds.ListObjectsV2Command({ Bucket: BUCKET, Prefix: prefix });
|
|
24
36
|
const res = await s3.send(cmd);
|
|
25
37
|
return (res.Contents || []).map((o) => o.Key).filter(Boolean);
|
|
26
38
|
}
|
|
27
|
-
// --- Public API ---
|
|
28
39
|
export async function loadCodeStandards() {
|
|
29
40
|
const raw = await getObject("knowledge/code-standards.json");
|
|
30
41
|
return JSON.parse(raw);
|
package/package.json
CHANGED
package/dist/lambda-package.zip
DELETED
|
Binary file
|