@atoms-tech/atoms-mcp 0.3.0 → 0.3.1
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/README.md +1 -1
- package/dist/index.cjs +2 -0
- package/dist/index.js +1 -90
- package/package.json +12 -3
- package/dist/apps/register.d.ts +0 -36
- package/dist/apps/register.js +0 -65
- package/dist/auth/login.d.ts +0 -24
- package/dist/auth/login.js +0 -413
- package/dist/auth/refresh.d.ts +0 -16
- package/dist/auth/refresh.js +0 -81
- package/dist/auth/token-store.d.ts +0 -33
- package/dist/auth/token-store.js +0 -60
- package/dist/config.d.ts +0 -18
- package/dist/config.js +0 -18
- package/dist/db/client.d.ts +0 -29
- package/dist/db/client.js +0 -109
- package/dist/db/graph.d.ts +0 -7
- package/dist/db/graph.js +0 -7
- package/dist/db/queries.d.ts +0 -76
- package/dist/db/queries.js +0 -209
- package/dist/index.d.ts +0 -11
- package/dist/middleware/audit.d.ts +0 -25
- package/dist/middleware/audit.js +0 -43
- package/dist/middleware/rate-limiter.d.ts +0 -20
- package/dist/middleware/rate-limiter.js +0 -42
- package/dist/middleware/validator.d.ts +0 -21
- package/dist/middleware/validator.js +0 -90
- package/dist/server.d.ts +0 -14
- package/dist/server.js +0 -679
- package/dist/tools/_base.d.ts +0 -57
- package/dist/tools/_base.js +0 -108
- package/dist/tools/bulk-import.d.ts +0 -69
- package/dist/tools/bulk-import.js +0 -187
- package/dist/tools/create-item.d.ts +0 -42
- package/dist/tools/create-item.js +0 -117
- package/dist/tools/delete-item.d.ts +0 -37
- package/dist/tools/delete-item.js +0 -68
- package/dist/tools/export-mermaid.d.ts +0 -35
- package/dist/tools/export-mermaid.js +0 -124
- package/dist/tools/get-coverage.d.ts +0 -33
- package/dist/tools/get-coverage.js +0 -35
- package/dist/tools/get-history.d.ts +0 -33
- package/dist/tools/get-history.js +0 -52
- package/dist/tools/get-item.d.ts +0 -61
- package/dist/tools/get-item.js +0 -92
- package/dist/tools/link-items.d.ts +0 -40
- package/dist/tools/link-items.js +0 -149
- package/dist/tools/list-items.d.ts +0 -36
- package/dist/tools/list-items.js +0 -35
- package/dist/tools/list-projects.d.ts +0 -37
- package/dist/tools/list-projects.js +0 -27
- package/dist/tools/project-summary.d.ts +0 -63
- package/dist/tools/project-summary.js +0 -169
- package/dist/tools/record-test-result.d.ts +0 -40
- package/dist/tools/record-test-result.js +0 -79
- package/dist/tools/search.d.ts +0 -33
- package/dist/tools/search.js +0 -27
- package/dist/tools/trace.d.ts +0 -52
- package/dist/tools/trace.js +0 -165
- package/dist/tools/update-item.d.ts +0 -42
- package/dist/tools/update-item.js +0 -97
- package/dist/types/responses.d.ts +0 -57
- package/dist/types/responses.js +0 -5
- package/dist/types/work-item.d.ts +0 -68
- package/dist/types/work-item.js +0 -7
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* atoms_export_mermaid — Generate Mermaid diagram of requirement/test hierarchy.
|
|
3
|
-
* Output is paste-ready for markdown documentation.
|
|
4
|
-
*/
|
|
5
|
-
export declare function exportMermaidHandler(params: {
|
|
6
|
-
project_id: string;
|
|
7
|
-
root_item_id?: string;
|
|
8
|
-
depth: number;
|
|
9
|
-
include_tests: boolean;
|
|
10
|
-
}): Promise<{
|
|
11
|
-
content: {
|
|
12
|
-
type: "text";
|
|
13
|
-
text: string;
|
|
14
|
-
}[];
|
|
15
|
-
structuredContent: {
|
|
16
|
-
data: any[];
|
|
17
|
-
meta: {
|
|
18
|
-
truncated: boolean;
|
|
19
|
-
truncation_message: string;
|
|
20
|
-
total_count?: number | undefined;
|
|
21
|
-
limit?: number | undefined;
|
|
22
|
-
offset?: number | undefined;
|
|
23
|
-
has_more?: boolean | undefined;
|
|
24
|
-
};
|
|
25
|
-
status: "success";
|
|
26
|
-
};
|
|
27
|
-
} | {
|
|
28
|
-
content: {
|
|
29
|
-
type: "text";
|
|
30
|
-
text: string;
|
|
31
|
-
}[];
|
|
32
|
-
structuredContent: import("../types/responses.js").ToolError | import("../types/responses.js").ToolSuccess<{
|
|
33
|
-
mermaid: string;
|
|
34
|
-
}>;
|
|
35
|
-
}>;
|
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* atoms_export_mermaid — Generate Mermaid diagram of requirement/test hierarchy.
|
|
3
|
-
* Output is paste-ready for markdown documentation.
|
|
4
|
-
*/
|
|
5
|
-
import { getClient } from "../db/client.js";
|
|
6
|
-
import { formatToolResult, formatErrorResult, success, dbError, authError, } from "./_base.js";
|
|
7
|
-
export async function exportMermaidHandler(params) {
|
|
8
|
-
try {
|
|
9
|
-
const client = await getClient();
|
|
10
|
-
// Get all items in the project
|
|
11
|
-
const { data: items, error: itemsErr } = await client
|
|
12
|
-
.from("items")
|
|
13
|
-
.select("id, title, type, data")
|
|
14
|
-
.eq("project_id", params.project_id)
|
|
15
|
-
.is("deleted_at", null);
|
|
16
|
-
if (itemsErr)
|
|
17
|
-
throw new Error(itemsErr.message);
|
|
18
|
-
if (!items || items.length === 0) {
|
|
19
|
-
return formatToolResult(success({ mermaid: "graph TD\n empty[No items in project]" }));
|
|
20
|
-
}
|
|
21
|
-
// Get all relationships
|
|
22
|
-
const { data: rels, error: relsErr } = await client
|
|
23
|
-
.from("item_relationships")
|
|
24
|
-
.select("from_id, to_id, type")
|
|
25
|
-
.eq("project_id", params.project_id);
|
|
26
|
-
if (relsErr)
|
|
27
|
-
throw new Error(relsErr.message);
|
|
28
|
-
const itemMap = new Map();
|
|
29
|
-
for (const item of items) {
|
|
30
|
-
itemMap.set(item.id, item);
|
|
31
|
-
}
|
|
32
|
-
// Build Mermaid graph
|
|
33
|
-
const lines = ["graph TD"];
|
|
34
|
-
const visited = new Set();
|
|
35
|
-
const edges = new Set();
|
|
36
|
-
// Find root nodes (items with no parents)
|
|
37
|
-
const childIds = new Set((rels ?? []).filter((r) => r.type === "child").map((r) => r.to_id));
|
|
38
|
-
const parentIds = new Set((rels ?? []).filter((r) => r.type === "parent").map((r) => r.from_id));
|
|
39
|
-
let roots;
|
|
40
|
-
if (params.root_item_id) {
|
|
41
|
-
roots = [params.root_item_id];
|
|
42
|
-
}
|
|
43
|
-
else {
|
|
44
|
-
// Items that are not children of anything
|
|
45
|
-
roots = items
|
|
46
|
-
.filter((item) => !parentIds.has(item.id))
|
|
47
|
-
.filter((item) => {
|
|
48
|
-
if (!params.include_tests && item.type === "test-case")
|
|
49
|
-
return false;
|
|
50
|
-
return true;
|
|
51
|
-
})
|
|
52
|
-
.map((item) => item.id);
|
|
53
|
-
}
|
|
54
|
-
// BFS to build graph up to depth
|
|
55
|
-
const queue = roots.map((id) => ({
|
|
56
|
-
id,
|
|
57
|
-
depth: 0,
|
|
58
|
-
}));
|
|
59
|
-
while (queue.length > 0) {
|
|
60
|
-
const { id, depth } = queue.shift();
|
|
61
|
-
if (visited.has(id) || depth > params.depth)
|
|
62
|
-
continue;
|
|
63
|
-
visited.add(id);
|
|
64
|
-
const item = itemMap.get(id);
|
|
65
|
-
if (!item)
|
|
66
|
-
continue;
|
|
67
|
-
// Skip test cases if not included
|
|
68
|
-
if (!params.include_tests && item.type === "test-case")
|
|
69
|
-
continue;
|
|
70
|
-
// Add node
|
|
71
|
-
const label = sanitizeLabel(item.title);
|
|
72
|
-
const shape = item.type === "requirement"
|
|
73
|
-
? `["${label}"]`
|
|
74
|
-
: item.type === "test-case"
|
|
75
|
-
? `(["${label}"])`
|
|
76
|
-
: `("${label}")`;
|
|
77
|
-
lines.push(` ${id}${shape}`);
|
|
78
|
-
// Add edges from relationships
|
|
79
|
-
for (const rel of rels ?? []) {
|
|
80
|
-
if (rel.from_id !== id)
|
|
81
|
-
continue;
|
|
82
|
-
const targetItem = itemMap.get(rel.to_id);
|
|
83
|
-
if (!targetItem)
|
|
84
|
-
continue;
|
|
85
|
-
if (!params.include_tests && targetItem.type === "test-case")
|
|
86
|
-
continue;
|
|
87
|
-
const edgeKey = `${rel.from_id}-${rel.type}-${rel.to_id}`;
|
|
88
|
-
if (edges.has(edgeKey))
|
|
89
|
-
continue;
|
|
90
|
-
edges.add(edgeKey);
|
|
91
|
-
if (rel.type === "child") {
|
|
92
|
-
lines.push(` ${id} --> ${rel.to_id}`);
|
|
93
|
-
}
|
|
94
|
-
else if (rel.type === "verified_by") {
|
|
95
|
-
lines.push(` ${id} -.->|verified_by| ${rel.to_id}`);
|
|
96
|
-
}
|
|
97
|
-
else if (rel.type === "related") {
|
|
98
|
-
lines.push(` ${id} -.- ${rel.to_id}`);
|
|
99
|
-
}
|
|
100
|
-
if (!visited.has(rel.to_id) && depth + 1 <= params.depth) {
|
|
101
|
-
queue.push({ id: rel.to_id, depth: depth + 1 });
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
const mermaid = lines.join("\n");
|
|
106
|
-
return formatToolResult(success({
|
|
107
|
-
mermaid,
|
|
108
|
-
node_count: visited.size,
|
|
109
|
-
edge_count: edges.size,
|
|
110
|
-
}));
|
|
111
|
-
}
|
|
112
|
-
catch (err) {
|
|
113
|
-
if (err instanceof Error && err.message.includes("Not authenticated")) {
|
|
114
|
-
return formatErrorResult(authError());
|
|
115
|
-
}
|
|
116
|
-
return formatErrorResult(dbError(err instanceof Error ? err.message : String(err)));
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
function sanitizeLabel(title) {
|
|
120
|
-
return title
|
|
121
|
-
.replace(/"/g, "'")
|
|
122
|
-
.replace(/[[\]{}()]/g, "")
|
|
123
|
-
.substring(0, 50);
|
|
124
|
-
}
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* atoms_get_coverage — Find requirements without linked test cases.
|
|
3
|
-
* Essential for compliance reporting in safety-critical industries.
|
|
4
|
-
*/
|
|
5
|
-
import type { CoverageReport } from "../types/responses.js";
|
|
6
|
-
export declare function getCoverageHandler(params: {
|
|
7
|
-
project_id: string;
|
|
8
|
-
domain?: string;
|
|
9
|
-
level?: string;
|
|
10
|
-
}): Promise<{
|
|
11
|
-
content: {
|
|
12
|
-
type: "text";
|
|
13
|
-
text: string;
|
|
14
|
-
}[];
|
|
15
|
-
structuredContent: {
|
|
16
|
-
data: any[];
|
|
17
|
-
meta: {
|
|
18
|
-
truncated: boolean;
|
|
19
|
-
truncation_message: string;
|
|
20
|
-
total_count?: number | undefined;
|
|
21
|
-
limit?: number | undefined;
|
|
22
|
-
offset?: number | undefined;
|
|
23
|
-
has_more?: boolean | undefined;
|
|
24
|
-
};
|
|
25
|
-
status: "success";
|
|
26
|
-
};
|
|
27
|
-
} | {
|
|
28
|
-
content: {
|
|
29
|
-
type: "text";
|
|
30
|
-
text: string;
|
|
31
|
-
}[];
|
|
32
|
-
structuredContent: import("../types/responses.js").ToolError | import("../types/responses.js").ToolSuccess<CoverageReport>;
|
|
33
|
-
}>;
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* atoms_get_coverage — Find requirements without linked test cases.
|
|
3
|
-
* Essential for compliance reporting in safety-critical industries.
|
|
4
|
-
*/
|
|
5
|
-
import { getClient } from "../db/client.js";
|
|
6
|
-
import { getCoverageReport } from "../db/queries.js";
|
|
7
|
-
import { formatToolResult, formatErrorResult, success, dbError, authError, } from "./_base.js";
|
|
8
|
-
export async function getCoverageHandler(params) {
|
|
9
|
-
try {
|
|
10
|
-
const client = await getClient();
|
|
11
|
-
const { covered, uncovered, total } = await getCoverageReport(client, params.project_id, { domain: params.domain, level: params.level });
|
|
12
|
-
const uncoveredItems = uncovered.map((item) => ({
|
|
13
|
-
id: item.id,
|
|
14
|
-
title: item.title,
|
|
15
|
-
type: item.type,
|
|
16
|
-
status: item.data?.status,
|
|
17
|
-
domains: item.data?.tags?.domains ?? [],
|
|
18
|
-
level: item.data?.tags?.level,
|
|
19
|
-
}));
|
|
20
|
-
const report = {
|
|
21
|
-
covered: covered.length,
|
|
22
|
-
uncovered: uncovered.length,
|
|
23
|
-
total,
|
|
24
|
-
coverage_percent: total > 0 ? Math.round((covered.length / total) * 1000) / 10 : 100,
|
|
25
|
-
uncovered_items: uncoveredItems,
|
|
26
|
-
};
|
|
27
|
-
return formatToolResult(success(report));
|
|
28
|
-
}
|
|
29
|
-
catch (err) {
|
|
30
|
-
if (err instanceof Error && err.message.includes("Not authenticated")) {
|
|
31
|
-
return formatErrorResult(authError());
|
|
32
|
-
}
|
|
33
|
-
return formatErrorResult(dbError(err instanceof Error ? err.message : String(err)));
|
|
34
|
-
}
|
|
35
|
-
}
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* atoms_get_history — Audit trail for an item.
|
|
3
|
-
* Shows who changed what, when, and whether it was human or AI.
|
|
4
|
-
*/
|
|
5
|
-
import type { HistoryEntry } from "../types/responses.js";
|
|
6
|
-
export declare function getHistoryHandler(params: {
|
|
7
|
-
project_id: string;
|
|
8
|
-
item_id: string;
|
|
9
|
-
limit: number;
|
|
10
|
-
}): Promise<{
|
|
11
|
-
content: {
|
|
12
|
-
type: "text";
|
|
13
|
-
text: string;
|
|
14
|
-
}[];
|
|
15
|
-
structuredContent: {
|
|
16
|
-
data: any[];
|
|
17
|
-
meta: {
|
|
18
|
-
truncated: boolean;
|
|
19
|
-
truncation_message: string;
|
|
20
|
-
total_count?: number | undefined;
|
|
21
|
-
limit?: number | undefined;
|
|
22
|
-
offset?: number | undefined;
|
|
23
|
-
has_more?: boolean | undefined;
|
|
24
|
-
};
|
|
25
|
-
status: "success";
|
|
26
|
-
};
|
|
27
|
-
} | {
|
|
28
|
-
content: {
|
|
29
|
-
type: "text";
|
|
30
|
-
text: string;
|
|
31
|
-
}[];
|
|
32
|
-
structuredContent: import("../types/responses.js").ToolError | import("../types/responses.js").ToolSuccess<HistoryEntry[]>;
|
|
33
|
-
}>;
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* atoms_get_history — Audit trail for an item.
|
|
3
|
-
* Shows who changed what, when, and whether it was human or AI.
|
|
4
|
-
*/
|
|
5
|
-
import { getClient } from "../db/client.js";
|
|
6
|
-
import { getChangeHistory } from "../db/queries.js";
|
|
7
|
-
import { formatToolResult, formatErrorResult, success, paginationMeta, dbError, authError, } from "./_base.js";
|
|
8
|
-
export async function getHistoryHandler(params) {
|
|
9
|
-
try {
|
|
10
|
-
const client = await getClient();
|
|
11
|
-
const history = await getChangeHistory(client, params.project_id, params.item_id, params.limit);
|
|
12
|
-
const entries = history.map((entry) => {
|
|
13
|
-
// Compute which fields changed by comparing old_data and new_data
|
|
14
|
-
const fieldsChanged = [];
|
|
15
|
-
const oldData = entry.old_data;
|
|
16
|
-
const newData = entry.new_data;
|
|
17
|
-
if (oldData && newData) {
|
|
18
|
-
const allKeys = new Set([
|
|
19
|
-
...Object.keys(oldData),
|
|
20
|
-
...Object.keys(newData),
|
|
21
|
-
]);
|
|
22
|
-
for (const key of allKeys) {
|
|
23
|
-
if (JSON.stringify(oldData[key]) !== JSON.stringify(newData[key])) {
|
|
24
|
-
fieldsChanged.push(key);
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
else if (newData) {
|
|
29
|
-
fieldsChanged.push("(created)");
|
|
30
|
-
}
|
|
31
|
-
else if (oldData) {
|
|
32
|
-
fieldsChanged.push("(deleted)");
|
|
33
|
-
}
|
|
34
|
-
return {
|
|
35
|
-
id: entry.id,
|
|
36
|
-
event_type: entry.event_type,
|
|
37
|
-
changed_by: entry.changed_by ?? null,
|
|
38
|
-
changed_at: entry.changed_at,
|
|
39
|
-
actor: entry.actor ?? "user",
|
|
40
|
-
session_id: entry.session_id ?? null,
|
|
41
|
-
fields_changed: fieldsChanged,
|
|
42
|
-
};
|
|
43
|
-
});
|
|
44
|
-
return formatToolResult(success(entries, paginationMeta(entries.length, params.limit, 0)));
|
|
45
|
-
}
|
|
46
|
-
catch (err) {
|
|
47
|
-
if (err instanceof Error && err.message.includes("Not authenticated")) {
|
|
48
|
-
return formatErrorResult(authError());
|
|
49
|
-
}
|
|
50
|
-
return formatErrorResult(dbError(err instanceof Error ? err.message : String(err)));
|
|
51
|
-
}
|
|
52
|
-
}
|
package/dist/tools/get-item.d.ts
DELETED
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* atoms_get_item — Get full item details including relationships and test history.
|
|
3
|
-
*/
|
|
4
|
-
export declare function getItemHandler(params: {
|
|
5
|
-
project_id: string;
|
|
6
|
-
item_id: string;
|
|
7
|
-
}): Promise<{
|
|
8
|
-
content: {
|
|
9
|
-
type: "text";
|
|
10
|
-
text: string;
|
|
11
|
-
}[];
|
|
12
|
-
structuredContent: {
|
|
13
|
-
data: any[];
|
|
14
|
-
meta: {
|
|
15
|
-
truncated: boolean;
|
|
16
|
-
truncation_message: string;
|
|
17
|
-
total_count?: number | undefined;
|
|
18
|
-
limit?: number | undefined;
|
|
19
|
-
offset?: number | undefined;
|
|
20
|
-
has_more?: boolean | undefined;
|
|
21
|
-
};
|
|
22
|
-
status: "success";
|
|
23
|
-
};
|
|
24
|
-
} | {
|
|
25
|
-
content: {
|
|
26
|
-
type: "text";
|
|
27
|
-
text: string;
|
|
28
|
-
}[];
|
|
29
|
-
structuredContent: import("../types/responses.js").ToolError | import("../types/responses.js").ToolSuccess<{
|
|
30
|
-
metadata: {
|
|
31
|
-
created_at: string;
|
|
32
|
-
created_by: string | null;
|
|
33
|
-
updated_at: string;
|
|
34
|
-
updated_by: string | null;
|
|
35
|
-
source_id: string | undefined;
|
|
36
|
-
};
|
|
37
|
-
latest_result?: string | undefined;
|
|
38
|
-
test_results?: {
|
|
39
|
-
result: string;
|
|
40
|
-
run_by: string | null;
|
|
41
|
-
run_at: string;
|
|
42
|
-
note: string | null;
|
|
43
|
-
}[] | undefined;
|
|
44
|
-
id: string;
|
|
45
|
-
type: string;
|
|
46
|
-
title: string;
|
|
47
|
-
summary: string | undefined;
|
|
48
|
-
body: string | undefined;
|
|
49
|
-
tags: import("../types/work-item.js").WorkItemTags;
|
|
50
|
-
ownership: import("../types/work-item.js").WorkItemOwnership;
|
|
51
|
-
relationships: {
|
|
52
|
-
parents: string[];
|
|
53
|
-
children: string[];
|
|
54
|
-
related: string[];
|
|
55
|
-
verified_by: string[];
|
|
56
|
-
verifies: string[];
|
|
57
|
-
};
|
|
58
|
-
links: import("../types/work-item.js").WorkItemLink[];
|
|
59
|
-
status: "passed" | "failed" | "blocked" | "not-run" | undefined;
|
|
60
|
-
}>;
|
|
61
|
-
}>;
|
package/dist/tools/get-item.js
DELETED
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* atoms_get_item — Get full item details including relationships and test history.
|
|
3
|
-
*/
|
|
4
|
-
import { getClient } from "../db/client.js";
|
|
5
|
-
import { getItemById, getItemRelationships } from "../db/queries.js";
|
|
6
|
-
import { formatToolResult, formatErrorResult, success, notFoundError, dbError, authError, } from "./_base.js";
|
|
7
|
-
export async function getItemHandler(params) {
|
|
8
|
-
try {
|
|
9
|
-
const client = await getClient();
|
|
10
|
-
const item = await getItemById(client, params.project_id, params.item_id);
|
|
11
|
-
if (!item) {
|
|
12
|
-
return formatErrorResult(notFoundError("Item", params.item_id));
|
|
13
|
-
}
|
|
14
|
-
// Enrich with relationship data from shadow table
|
|
15
|
-
const relationships = await getItemRelationships(client, params.project_id, params.item_id);
|
|
16
|
-
// Build structured relationship map
|
|
17
|
-
const relMap = {
|
|
18
|
-
parents: [],
|
|
19
|
-
children: [],
|
|
20
|
-
related: [],
|
|
21
|
-
verified_by: [],
|
|
22
|
-
verifies: [],
|
|
23
|
-
};
|
|
24
|
-
for (const rel of relationships) {
|
|
25
|
-
if (rel.from_id === params.item_id) {
|
|
26
|
-
// Outgoing relationship
|
|
27
|
-
if (rel.type in relMap) {
|
|
28
|
-
relMap[rel.type].push(rel.to_id);
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
else {
|
|
32
|
-
// Incoming relationship — invert the type
|
|
33
|
-
const inverse = {
|
|
34
|
-
parent: "children",
|
|
35
|
-
child: "parents",
|
|
36
|
-
verified_by: "verifies",
|
|
37
|
-
verifies: "verified_by",
|
|
38
|
-
related: "related",
|
|
39
|
-
};
|
|
40
|
-
const key = inverse[rel.type] ?? rel.type;
|
|
41
|
-
if (key in relMap) {
|
|
42
|
-
relMap[key].push(rel.from_id);
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
// For test cases, fetch test results from shadow table
|
|
47
|
-
let testResults = [];
|
|
48
|
-
if (item.type === "test-case") {
|
|
49
|
-
const { data: results, error: resultsErr } = await client
|
|
50
|
-
.from("test_results")
|
|
51
|
-
.select("result, run_by, run_at, note")
|
|
52
|
-
.eq("item_id", params.item_id)
|
|
53
|
-
.eq("project_id", params.project_id)
|
|
54
|
-
.order("run_at", { ascending: false })
|
|
55
|
-
.limit(20);
|
|
56
|
-
if (!resultsErr && results) {
|
|
57
|
-
testResults = results;
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
const data = {
|
|
61
|
-
id: item.id,
|
|
62
|
-
type: item.type,
|
|
63
|
-
title: item.title,
|
|
64
|
-
summary: item.data?.summary,
|
|
65
|
-
body: item.data?.body,
|
|
66
|
-
tags: item.data?.tags ?? { domains: [] },
|
|
67
|
-
ownership: item.data?.ownership ?? { primary: null, additional: [] },
|
|
68
|
-
relationships: relMap,
|
|
69
|
-
links: item.data?.links ?? [],
|
|
70
|
-
status: item.data?.status,
|
|
71
|
-
// Test results from shadow table (for test cases)
|
|
72
|
-
...(item.type === "test-case" ? {
|
|
73
|
-
latest_result: testResults[0]?.result ?? "not-run",
|
|
74
|
-
test_results: testResults,
|
|
75
|
-
} : {}),
|
|
76
|
-
metadata: {
|
|
77
|
-
created_at: item.created_at,
|
|
78
|
-
created_by: item.created_by,
|
|
79
|
-
updated_at: item.updated_at,
|
|
80
|
-
updated_by: item.updated_by,
|
|
81
|
-
source_id: item.data?.metadata?.source_id,
|
|
82
|
-
},
|
|
83
|
-
};
|
|
84
|
-
return formatToolResult(success(data));
|
|
85
|
-
}
|
|
86
|
-
catch (err) {
|
|
87
|
-
if (err instanceof Error && err.message.includes("Not authenticated")) {
|
|
88
|
-
return formatErrorResult(authError());
|
|
89
|
-
}
|
|
90
|
-
return formatErrorResult(dbError(err instanceof Error ? err.message : String(err)));
|
|
91
|
-
}
|
|
92
|
-
}
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* atoms_link_items — Add or remove relationships between items.
|
|
3
|
-
* Updates both items' JSONB data and the shadow table.
|
|
4
|
-
*/
|
|
5
|
-
export declare function linkItemsHandler(params: {
|
|
6
|
-
project_id: string;
|
|
7
|
-
from_id: string;
|
|
8
|
-
to_id: string;
|
|
9
|
-
type: "parent" | "child" | "related" | "verifies" | "verified_by";
|
|
10
|
-
action: "add" | "remove";
|
|
11
|
-
}): Promise<{
|
|
12
|
-
content: {
|
|
13
|
-
type: "text";
|
|
14
|
-
text: string;
|
|
15
|
-
}[];
|
|
16
|
-
structuredContent: {
|
|
17
|
-
data: any[];
|
|
18
|
-
meta: {
|
|
19
|
-
truncated: boolean;
|
|
20
|
-
truncation_message: string;
|
|
21
|
-
total_count?: number | undefined;
|
|
22
|
-
limit?: number | undefined;
|
|
23
|
-
offset?: number | undefined;
|
|
24
|
-
has_more?: boolean | undefined;
|
|
25
|
-
};
|
|
26
|
-
status: "success";
|
|
27
|
-
};
|
|
28
|
-
} | {
|
|
29
|
-
content: {
|
|
30
|
-
type: "text";
|
|
31
|
-
text: string;
|
|
32
|
-
}[];
|
|
33
|
-
structuredContent: import("../types/responses.js").ToolError | import("../types/responses.js").ToolSuccess<{
|
|
34
|
-
action: "add" | "remove";
|
|
35
|
-
type: "parent" | "verified_by" | "child" | "verifies" | "related";
|
|
36
|
-
from_id: string;
|
|
37
|
-
to_id: string;
|
|
38
|
-
message: string;
|
|
39
|
-
}>;
|
|
40
|
-
}>;
|
package/dist/tools/link-items.js
DELETED
|
@@ -1,149 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* atoms_link_items — Add or remove relationships between items.
|
|
3
|
-
* Updates both items' JSONB data and the shadow table.
|
|
4
|
-
*/
|
|
5
|
-
import { getClient, getUserId, requireWriteAccess } from "../db/client.js";
|
|
6
|
-
import { getItemById } from "../db/queries.js";
|
|
7
|
-
import { getMcpSessionId } from "../server.js";
|
|
8
|
-
import { formatToolResult, formatErrorResult, success, notFoundError, accessDeniedError, validationError, dbError, authError, } from "./_base.js";
|
|
9
|
-
export async function linkItemsHandler(params) {
|
|
10
|
-
try {
|
|
11
|
-
const client = await getClient();
|
|
12
|
-
const userId = getUserId();
|
|
13
|
-
// Validate: no self-referencing
|
|
14
|
-
if (params.from_id === params.to_id) {
|
|
15
|
-
return formatErrorResult(validationError("Cannot create a relationship between an item and itself"));
|
|
16
|
-
}
|
|
17
|
-
// Check write access
|
|
18
|
-
try {
|
|
19
|
-
await requireWriteAccess(client, params.project_id);
|
|
20
|
-
}
|
|
21
|
-
catch (err) {
|
|
22
|
-
if (err instanceof Error && err.message === "VIEWER_ROLE") {
|
|
23
|
-
return formatErrorResult(accessDeniedError("Viewer"));
|
|
24
|
-
}
|
|
25
|
-
throw err;
|
|
26
|
-
}
|
|
27
|
-
// Verify both items exist
|
|
28
|
-
const fromItem = await getItemById(client, params.project_id, params.from_id);
|
|
29
|
-
if (!fromItem) {
|
|
30
|
-
return formatErrorResult(notFoundError("Item", params.from_id));
|
|
31
|
-
}
|
|
32
|
-
const toItem = await getItemById(client, params.project_id, params.to_id);
|
|
33
|
-
if (!toItem) {
|
|
34
|
-
return formatErrorResult(notFoundError("Item", params.to_id));
|
|
35
|
-
}
|
|
36
|
-
if (params.action === "add") {
|
|
37
|
-
// Check if relationship already exists
|
|
38
|
-
const { data: existing } = await client
|
|
39
|
-
.from("item_relationships")
|
|
40
|
-
.select("from_id")
|
|
41
|
-
.eq("from_id", params.from_id)
|
|
42
|
-
.eq("to_id", params.to_id)
|
|
43
|
-
.eq("type", params.type)
|
|
44
|
-
.eq("project_id", params.project_id)
|
|
45
|
-
.maybeSingle();
|
|
46
|
-
if (!existing) {
|
|
47
|
-
const { error: relErr } = await client
|
|
48
|
-
.from("item_relationships")
|
|
49
|
-
.insert({
|
|
50
|
-
from_id: params.from_id,
|
|
51
|
-
to_id: params.to_id,
|
|
52
|
-
type: params.type,
|
|
53
|
-
project_id: params.project_id,
|
|
54
|
-
});
|
|
55
|
-
if (relErr)
|
|
56
|
-
throw new Error(relErr.message);
|
|
57
|
-
}
|
|
58
|
-
// Update JSONB data on the from item
|
|
59
|
-
const fromData = { ...fromItem.data };
|
|
60
|
-
const relKey = params.type === "verified_by" || params.type === "verifies"
|
|
61
|
-
? params.type
|
|
62
|
-
: params.type + "s" === "parents" ? "parents"
|
|
63
|
-
: params.type === "parent" ? "parents"
|
|
64
|
-
: params.type === "child" ? "children"
|
|
65
|
-
: "related";
|
|
66
|
-
const relField = relKey;
|
|
67
|
-
if (fromData.relationships) {
|
|
68
|
-
const existing = fromData.relationships[relField] ?? [];
|
|
69
|
-
if (!existing.includes(params.to_id)) {
|
|
70
|
-
fromData.relationships[relField] = [
|
|
71
|
-
...existing,
|
|
72
|
-
params.to_id,
|
|
73
|
-
];
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
await client
|
|
77
|
-
.from("items")
|
|
78
|
-
.update({ data: fromData, updated_by: userId })
|
|
79
|
-
.eq("id", params.from_id)
|
|
80
|
-
.eq("project_id", params.project_id);
|
|
81
|
-
}
|
|
82
|
-
else {
|
|
83
|
-
// Remove from shadow table
|
|
84
|
-
const { error: delErr } = await client
|
|
85
|
-
.from("item_relationships")
|
|
86
|
-
.delete()
|
|
87
|
-
.eq("from_id", params.from_id)
|
|
88
|
-
.eq("to_id", params.to_id)
|
|
89
|
-
.eq("type", params.type)
|
|
90
|
-
.eq("project_id", params.project_id);
|
|
91
|
-
if (delErr)
|
|
92
|
-
throw new Error(delErr.message);
|
|
93
|
-
// Update JSONB data on the from item
|
|
94
|
-
const fromData = { ...fromItem.data };
|
|
95
|
-
const relKey = params.type === "parent" ? "parents"
|
|
96
|
-
: params.type === "child" ? "children"
|
|
97
|
-
: params.type;
|
|
98
|
-
const relField = relKey;
|
|
99
|
-
if (fromData.relationships) {
|
|
100
|
-
const existing = fromData.relationships[relField] ?? [];
|
|
101
|
-
fromData.relationships[relField] =
|
|
102
|
-
existing.filter((id) => id !== params.to_id);
|
|
103
|
-
}
|
|
104
|
-
await client
|
|
105
|
-
.from("items")
|
|
106
|
-
.update({ data: fromData, updated_by: userId })
|
|
107
|
-
.eq("id", params.from_id)
|
|
108
|
-
.eq("project_id", params.project_id);
|
|
109
|
-
}
|
|
110
|
-
// Audit log
|
|
111
|
-
await client
|
|
112
|
-
.from("change_history")
|
|
113
|
-
.insert({
|
|
114
|
-
item_id: params.from_id,
|
|
115
|
-
project_id: params.project_id,
|
|
116
|
-
changed_by: userId,
|
|
117
|
-
event_type: "updated",
|
|
118
|
-
old_data: null,
|
|
119
|
-
new_data: {
|
|
120
|
-
relationship_change: {
|
|
121
|
-
action: params.action,
|
|
122
|
-
type: params.type,
|
|
123
|
-
from: params.from_id,
|
|
124
|
-
to: params.to_id,
|
|
125
|
-
},
|
|
126
|
-
},
|
|
127
|
-
actor: "mcp_claude",
|
|
128
|
-
session_id: getMcpSessionId(),
|
|
129
|
-
})
|
|
130
|
-
.then(({ error }) => {
|
|
131
|
-
if (error) {
|
|
132
|
-
process.stderr.write(`[atoms-mcp] Warning: change_history log failed: ${error.message}\n`);
|
|
133
|
-
}
|
|
134
|
-
});
|
|
135
|
-
return formatToolResult(success({
|
|
136
|
-
action: params.action,
|
|
137
|
-
type: params.type,
|
|
138
|
-
from_id: params.from_id,
|
|
139
|
-
to_id: params.to_id,
|
|
140
|
-
message: `Relationship ${params.action === "add" ? "added" : "removed"}: ${params.from_id} --[${params.type}]--> ${params.to_id}`,
|
|
141
|
-
}));
|
|
142
|
-
}
|
|
143
|
-
catch (err) {
|
|
144
|
-
if (err instanceof Error && err.message.includes("Not authenticated")) {
|
|
145
|
-
return formatErrorResult(authError());
|
|
146
|
-
}
|
|
147
|
-
return formatErrorResult(dbError(err instanceof Error ? err.message : String(err)));
|
|
148
|
-
}
|
|
149
|
-
}
|