@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
package/dist/tools/trace.js
DELETED
|
@@ -1,165 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* atoms_trace — Graph traversal for traceability queries.
|
|
3
|
-
*
|
|
4
|
-
* Answers questions like "which requirements does TC-00003 verify?"
|
|
5
|
-
* or "what's affected if I change REQ-00001?"
|
|
6
|
-
*/
|
|
7
|
-
import { getClient } from "../db/client.js";
|
|
8
|
-
import { getItemById } from "../db/queries.js";
|
|
9
|
-
import { formatToolResult, formatErrorResult, success, notFoundError, dbError, authError, } from "./_base.js";
|
|
10
|
-
/** Maximum items returned to prevent huge responses. */
|
|
11
|
-
const MAX_ITEMS = 200;
|
|
12
|
-
/** Relationship types that point "upstream" (toward parents / dependencies). */
|
|
13
|
-
const UPSTREAM_TYPES = ["parent", "verifies"];
|
|
14
|
-
/** Relationship types that point "downstream" (toward dependents). */
|
|
15
|
-
const DOWNSTREAM_TYPES = ["child", "verified_by"];
|
|
16
|
-
/**
|
|
17
|
-
* Inverse mapping: when traversing an incoming edge we need
|
|
18
|
-
* to know what the outgoing type means for the other side.
|
|
19
|
-
*/
|
|
20
|
-
const INVERSE_TYPE = {
|
|
21
|
-
parent: "child",
|
|
22
|
-
child: "parent",
|
|
23
|
-
verifies: "verified_by",
|
|
24
|
-
verified_by: "verifies",
|
|
25
|
-
related: "related",
|
|
26
|
-
};
|
|
27
|
-
export async function traceHandler(params) {
|
|
28
|
-
try {
|
|
29
|
-
const maxDepth = Math.min(Math.max(params.depth ?? 5, 1), 10);
|
|
30
|
-
const client = await getClient();
|
|
31
|
-
// Verify root item exists
|
|
32
|
-
const rootItem = await getItemById(client, params.project_id, params.item_id);
|
|
33
|
-
if (!rootItem) {
|
|
34
|
-
return formatErrorResult(notFoundError("Item", params.item_id));
|
|
35
|
-
}
|
|
36
|
-
// Fetch all relationships in the project once (same pattern as export-mermaid)
|
|
37
|
-
const { data: rels, error: relsErr } = await client
|
|
38
|
-
.from("item_relationships")
|
|
39
|
-
.select("from_id, to_id, type")
|
|
40
|
-
.eq("project_id", params.project_id);
|
|
41
|
-
if (relsErr)
|
|
42
|
-
throw new Error(relsErr.message);
|
|
43
|
-
// Fetch all non-deleted items for title/type lookup
|
|
44
|
-
const { data: items, error: itemsErr } = await client
|
|
45
|
-
.from("items")
|
|
46
|
-
.select("id, title, type")
|
|
47
|
-
.eq("project_id", params.project_id)
|
|
48
|
-
.is("deleted_at", null);
|
|
49
|
-
if (itemsErr)
|
|
50
|
-
throw new Error(itemsErr.message);
|
|
51
|
-
const itemMap = new Map();
|
|
52
|
-
for (const item of items ?? []) {
|
|
53
|
-
itemMap.set(item.id, { title: item.title, type: item.type });
|
|
54
|
-
}
|
|
55
|
-
// Build adjacency lists based on direction
|
|
56
|
-
// For each item, we store { neighbor, relType, direction }
|
|
57
|
-
const adjacency = new Map();
|
|
58
|
-
const addEdge = (from, to, relType) => {
|
|
59
|
-
if (!adjacency.has(from))
|
|
60
|
-
adjacency.set(from, []);
|
|
61
|
-
adjacency.get(from).push({ neighbor: to, relType });
|
|
62
|
-
};
|
|
63
|
-
const allowedTypes = params.relationship_types
|
|
64
|
-
? new Set(params.relationship_types)
|
|
65
|
-
: null;
|
|
66
|
-
for (const rel of rels ?? []) {
|
|
67
|
-
const relType = rel.type;
|
|
68
|
-
// Upstream: follow edges where the item is the source and the rel points upstream
|
|
69
|
-
// i.e., from_id -> to_id with type "parent" or "verifies"
|
|
70
|
-
if (params.direction === "upstream" || params.direction === "both") {
|
|
71
|
-
if (UPSTREAM_TYPES.includes(relType)) {
|
|
72
|
-
if (!allowedTypes || allowedTypes.has(relType)) {
|
|
73
|
-
addEdge(rel.from_id, rel.to_id, relType);
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
// Also follow inverse: if from_id has a "child" pointing to to_id,
|
|
77
|
-
// then to_id is upstream of from_id — but we model it as:
|
|
78
|
-
// from_id's downstream is to_id via "child". For upstream from to_id,
|
|
79
|
-
// we'd follow to_id -> from_id via inverse of "child" = "parent".
|
|
80
|
-
if (DOWNSTREAM_TYPES.includes(relType)) {
|
|
81
|
-
const inverseType = INVERSE_TYPE[relType] ?? relType;
|
|
82
|
-
if (!allowedTypes || allowedTypes.has(inverseType)) {
|
|
83
|
-
addEdge(rel.to_id, rel.from_id, inverseType);
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
if (params.direction === "downstream" || params.direction === "both") {
|
|
88
|
-
if (DOWNSTREAM_TYPES.includes(relType)) {
|
|
89
|
-
if (!allowedTypes || allowedTypes.has(relType)) {
|
|
90
|
-
addEdge(rel.from_id, rel.to_id, relType);
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
// Inverse: upstream type from the other side means downstream for us
|
|
94
|
-
if (UPSTREAM_TYPES.includes(relType)) {
|
|
95
|
-
const inverseType = INVERSE_TYPE[relType] ?? relType;
|
|
96
|
-
if (!allowedTypes || allowedTypes.has(inverseType)) {
|
|
97
|
-
addEdge(rel.to_id, rel.from_id, inverseType);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
// "related" is bidirectional — add both directions if allowed
|
|
102
|
-
if (relType === "related") {
|
|
103
|
-
if (!allowedTypes || allowedTypes.has("related")) {
|
|
104
|
-
addEdge(rel.from_id, rel.to_id, "related");
|
|
105
|
-
addEdge(rel.to_id, rel.from_id, "related");
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
// BFS traversal from root
|
|
110
|
-
const visited = new Set();
|
|
111
|
-
visited.add(params.item_id);
|
|
112
|
-
const result = [];
|
|
113
|
-
const queue = [];
|
|
114
|
-
// Seed queue with direct neighbors of the root
|
|
115
|
-
const rootNeighbors = adjacency.get(params.item_id) ?? [];
|
|
116
|
-
for (const edge of rootNeighbors) {
|
|
117
|
-
queue.push({ id: edge.neighbor, depth: 1, relType: edge.relType });
|
|
118
|
-
}
|
|
119
|
-
while (queue.length > 0 && result.length < MAX_ITEMS) {
|
|
120
|
-
const { id, depth, relType } = queue.shift();
|
|
121
|
-
if (visited.has(id) || depth > maxDepth)
|
|
122
|
-
continue;
|
|
123
|
-
visited.add(id);
|
|
124
|
-
const info = itemMap.get(id);
|
|
125
|
-
if (!info)
|
|
126
|
-
continue; // skip deleted items
|
|
127
|
-
result.push({
|
|
128
|
-
id,
|
|
129
|
-
title: info.title,
|
|
130
|
-
type: info.type,
|
|
131
|
-
relationship: relType,
|
|
132
|
-
depth,
|
|
133
|
-
});
|
|
134
|
-
// Enqueue neighbors at next depth
|
|
135
|
-
if (depth < maxDepth) {
|
|
136
|
-
const neighbors = adjacency.get(id) ?? [];
|
|
137
|
-
for (const edge of neighbors) {
|
|
138
|
-
if (!visited.has(edge.neighbor)) {
|
|
139
|
-
queue.push({
|
|
140
|
-
id: edge.neighbor,
|
|
141
|
-
depth: depth + 1,
|
|
142
|
-
relType: edge.relType,
|
|
143
|
-
});
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
return formatToolResult(success({
|
|
149
|
-
root: params.item_id,
|
|
150
|
-
direction: params.direction,
|
|
151
|
-
depth_limit: maxDepth,
|
|
152
|
-
items: result,
|
|
153
|
-
total_count: result.length,
|
|
154
|
-
...(result.length >= MAX_ITEMS
|
|
155
|
-
? { truncated: true, truncation_message: `Results capped at ${MAX_ITEMS} items. Use a smaller depth or filter by relationship_types.` }
|
|
156
|
-
: {}),
|
|
157
|
-
}));
|
|
158
|
-
}
|
|
159
|
-
catch (err) {
|
|
160
|
-
if (err instanceof Error && err.message.includes("Not authenticated")) {
|
|
161
|
-
return formatErrorResult(authError());
|
|
162
|
-
}
|
|
163
|
-
return formatErrorResult(dbError(err instanceof Error ? err.message : String(err)));
|
|
164
|
-
}
|
|
165
|
-
}
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* atoms_update_item — Update an existing item's fields.
|
|
3
|
-
* Requires editor or admin role. Logs old/new diff to change_history.
|
|
4
|
-
*/
|
|
5
|
-
export declare function updateItemHandler(params: {
|
|
6
|
-
project_id: string;
|
|
7
|
-
item_id: string;
|
|
8
|
-
title?: string;
|
|
9
|
-
body?: string;
|
|
10
|
-
summary?: string;
|
|
11
|
-
domains?: string[];
|
|
12
|
-
level?: string;
|
|
13
|
-
status?: "passed" | "failed" | "blocked" | "not-run";
|
|
14
|
-
}): Promise<{
|
|
15
|
-
content: {
|
|
16
|
-
type: "text";
|
|
17
|
-
text: string;
|
|
18
|
-
}[];
|
|
19
|
-
structuredContent: {
|
|
20
|
-
data: any[];
|
|
21
|
-
meta: {
|
|
22
|
-
truncated: boolean;
|
|
23
|
-
truncation_message: string;
|
|
24
|
-
total_count?: number | undefined;
|
|
25
|
-
limit?: number | undefined;
|
|
26
|
-
offset?: number | undefined;
|
|
27
|
-
has_more?: boolean | undefined;
|
|
28
|
-
};
|
|
29
|
-
status: "success";
|
|
30
|
-
};
|
|
31
|
-
} | {
|
|
32
|
-
content: {
|
|
33
|
-
type: "text";
|
|
34
|
-
text: string;
|
|
35
|
-
}[];
|
|
36
|
-
structuredContent: import("../types/responses.js").ToolError | import("../types/responses.js").ToolSuccess<{
|
|
37
|
-
id: string;
|
|
38
|
-
title: string;
|
|
39
|
-
type: string;
|
|
40
|
-
updated_fields: string[];
|
|
41
|
-
}>;
|
|
42
|
-
}>;
|
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* atoms_update_item — Update an existing item's fields.
|
|
3
|
-
* Requires editor or admin role. Logs old/new diff to change_history.
|
|
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, dbError, authError, } from "./_base.js";
|
|
9
|
-
export async function updateItemHandler(params) {
|
|
10
|
-
try {
|
|
11
|
-
const client = await getClient();
|
|
12
|
-
const userId = getUserId();
|
|
13
|
-
// Check write access
|
|
14
|
-
try {
|
|
15
|
-
await requireWriteAccess(client, params.project_id);
|
|
16
|
-
}
|
|
17
|
-
catch (err) {
|
|
18
|
-
if (err instanceof Error && err.message === "VIEWER_ROLE") {
|
|
19
|
-
return formatErrorResult(accessDeniedError("Viewer"));
|
|
20
|
-
}
|
|
21
|
-
throw err;
|
|
22
|
-
}
|
|
23
|
-
// Get existing item
|
|
24
|
-
const existing = await getItemById(client, params.project_id, params.item_id);
|
|
25
|
-
if (!existing) {
|
|
26
|
-
return formatErrorResult(notFoundError("Item", params.item_id));
|
|
27
|
-
}
|
|
28
|
-
// Build updated data blob
|
|
29
|
-
const oldData = { ...existing.data };
|
|
30
|
-
const updatedData = { ...existing.data };
|
|
31
|
-
if (params.title !== undefined) {
|
|
32
|
-
updatedData.title = params.title;
|
|
33
|
-
}
|
|
34
|
-
if (params.body !== undefined) {
|
|
35
|
-
updatedData.body = params.body;
|
|
36
|
-
}
|
|
37
|
-
if (params.summary !== undefined) {
|
|
38
|
-
updatedData.summary = params.summary;
|
|
39
|
-
}
|
|
40
|
-
if (params.domains !== undefined) {
|
|
41
|
-
updatedData.tags = { ...updatedData.tags, domains: params.domains };
|
|
42
|
-
}
|
|
43
|
-
if (params.level !== undefined) {
|
|
44
|
-
updatedData.tags = { ...updatedData.tags, level: params.level };
|
|
45
|
-
}
|
|
46
|
-
if (params.status !== undefined) {
|
|
47
|
-
updatedData.status = params.status;
|
|
48
|
-
}
|
|
49
|
-
updatedData.metadata = {
|
|
50
|
-
...updatedData.metadata,
|
|
51
|
-
updated_at: new Date().toISOString(),
|
|
52
|
-
updated_by: userId,
|
|
53
|
-
};
|
|
54
|
-
// Update in database
|
|
55
|
-
const { error: updateErr } = await client
|
|
56
|
-
.from("items")
|
|
57
|
-
.update({
|
|
58
|
-
title: params.title ?? existing.title,
|
|
59
|
-
data: updatedData,
|
|
60
|
-
updated_by: userId,
|
|
61
|
-
})
|
|
62
|
-
.eq("id", params.item_id)
|
|
63
|
-
.eq("project_id", params.project_id);
|
|
64
|
-
if (updateErr)
|
|
65
|
-
throw new Error(updateErr.message);
|
|
66
|
-
// Audit: log diff to change_history
|
|
67
|
-
await client
|
|
68
|
-
.from("change_history")
|
|
69
|
-
.insert({
|
|
70
|
-
item_id: params.item_id,
|
|
71
|
-
project_id: params.project_id,
|
|
72
|
-
changed_by: userId,
|
|
73
|
-
event_type: "updated",
|
|
74
|
-
old_data: oldData,
|
|
75
|
-
new_data: updatedData,
|
|
76
|
-
actor: "mcp_claude",
|
|
77
|
-
session_id: getMcpSessionId(),
|
|
78
|
-
})
|
|
79
|
-
.then(({ error }) => {
|
|
80
|
-
if (error) {
|
|
81
|
-
process.stderr.write(`[atoms-mcp] Warning: change_history log failed: ${error.message}\n`);
|
|
82
|
-
}
|
|
83
|
-
});
|
|
84
|
-
return formatToolResult(success({
|
|
85
|
-
id: params.item_id,
|
|
86
|
-
title: updatedData.title,
|
|
87
|
-
type: existing.type,
|
|
88
|
-
updated_fields: Object.keys(params).filter((k) => k !== "project_id" && k !== "item_id" && params[k] !== undefined),
|
|
89
|
-
}));
|
|
90
|
-
}
|
|
91
|
-
catch (err) {
|
|
92
|
-
if (err instanceof Error && err.message.includes("Not authenticated")) {
|
|
93
|
-
return formatErrorResult(authError());
|
|
94
|
-
}
|
|
95
|
-
return formatErrorResult(dbError(err instanceof Error ? err.message : String(err)));
|
|
96
|
-
}
|
|
97
|
-
}
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Standard response envelopes for all MCP tool responses.
|
|
3
|
-
* Every tool returns JSON matching one of these shapes.
|
|
4
|
-
*/
|
|
5
|
-
export interface ToolSuccess<T = unknown> {
|
|
6
|
-
[key: string]: unknown;
|
|
7
|
-
status: "success";
|
|
8
|
-
data: T;
|
|
9
|
-
meta?: PaginationMeta;
|
|
10
|
-
}
|
|
11
|
-
export interface ToolError {
|
|
12
|
-
[key: string]: unknown;
|
|
13
|
-
status: "error";
|
|
14
|
-
message: string;
|
|
15
|
-
next_steps: string[];
|
|
16
|
-
}
|
|
17
|
-
export interface PaginationMeta {
|
|
18
|
-
[key: string]: unknown;
|
|
19
|
-
total_count: number;
|
|
20
|
-
limit: number;
|
|
21
|
-
offset: number;
|
|
22
|
-
has_more: boolean;
|
|
23
|
-
}
|
|
24
|
-
export type ToolResponse<T = unknown> = ToolSuccess<T> | ToolError;
|
|
25
|
-
/**
|
|
26
|
-
* Item summary returned by list/search tools (@basic fields).
|
|
27
|
-
*/
|
|
28
|
-
export interface ItemSummary {
|
|
29
|
-
id: string;
|
|
30
|
-
title: string;
|
|
31
|
-
type: string;
|
|
32
|
-
status?: string;
|
|
33
|
-
domains: string[];
|
|
34
|
-
level?: string;
|
|
35
|
-
}
|
|
36
|
-
/**
|
|
37
|
-
* Coverage report returned by atoms_get_coverage.
|
|
38
|
-
*/
|
|
39
|
-
export interface CoverageReport {
|
|
40
|
-
covered: number;
|
|
41
|
-
uncovered: number;
|
|
42
|
-
total: number;
|
|
43
|
-
coverage_percent: number;
|
|
44
|
-
uncovered_items: ItemSummary[];
|
|
45
|
-
}
|
|
46
|
-
/**
|
|
47
|
-
* Change history entry returned by atoms_get_history.
|
|
48
|
-
*/
|
|
49
|
-
export interface HistoryEntry {
|
|
50
|
-
id: string;
|
|
51
|
-
event_type: string;
|
|
52
|
-
changed_by: string | null;
|
|
53
|
-
changed_at: string;
|
|
54
|
-
actor: string;
|
|
55
|
-
session_id: string | null;
|
|
56
|
-
fields_changed: string[];
|
|
57
|
-
}
|
package/dist/types/responses.js
DELETED
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* WorkItem type definition — shared with the ATOMS frontend.
|
|
3
|
-
* Source of truth: src/app/types.ts in the main app.
|
|
4
|
-
*
|
|
5
|
-
* This is a subset focused on what the MCP server needs.
|
|
6
|
-
*/
|
|
7
|
-
export interface WorkItemRelationships {
|
|
8
|
-
parents: string[];
|
|
9
|
-
children: string[];
|
|
10
|
-
related: string[];
|
|
11
|
-
verified_by?: string[];
|
|
12
|
-
verifies?: string[];
|
|
13
|
-
}
|
|
14
|
-
export interface WorkItemOwnership {
|
|
15
|
-
primary: string | null;
|
|
16
|
-
additional: string[];
|
|
17
|
-
}
|
|
18
|
-
export interface WorkItemTags {
|
|
19
|
-
domains: string[];
|
|
20
|
-
level?: string;
|
|
21
|
-
}
|
|
22
|
-
export interface TestRun {
|
|
23
|
-
date: string;
|
|
24
|
-
result: "passed" | "failed" | "blocked" | "not-run";
|
|
25
|
-
by: string;
|
|
26
|
-
note?: string;
|
|
27
|
-
}
|
|
28
|
-
export interface WorkItemLink {
|
|
29
|
-
url: string;
|
|
30
|
-
label: string;
|
|
31
|
-
}
|
|
32
|
-
export interface WorkItemMetadata {
|
|
33
|
-
created_at: string;
|
|
34
|
-
created_by: string;
|
|
35
|
-
updated_at: string;
|
|
36
|
-
updated_by: string;
|
|
37
|
-
source_id?: string;
|
|
38
|
-
imported_at?: string;
|
|
39
|
-
}
|
|
40
|
-
export interface WorkItem {
|
|
41
|
-
id: string;
|
|
42
|
-
type: "requirement" | "test-case" | "note" | "table";
|
|
43
|
-
title: string;
|
|
44
|
-
summary?: string;
|
|
45
|
-
body?: string;
|
|
46
|
-
tags: WorkItemTags;
|
|
47
|
-
ownership: WorkItemOwnership;
|
|
48
|
-
relationships: WorkItemRelationships;
|
|
49
|
-
links: WorkItemLink[];
|
|
50
|
-
status?: "passed" | "failed" | "blocked" | "not-run";
|
|
51
|
-
runs?: TestRun[];
|
|
52
|
-
metadata: WorkItemMetadata;
|
|
53
|
-
}
|
|
54
|
-
/**
|
|
55
|
-
* Database row shape from Supabase items table.
|
|
56
|
-
*/
|
|
57
|
-
export interface ItemRow {
|
|
58
|
-
id: string;
|
|
59
|
-
project_id: string;
|
|
60
|
-
type: string;
|
|
61
|
-
title: string;
|
|
62
|
-
data: WorkItem;
|
|
63
|
-
created_by: string | null;
|
|
64
|
-
updated_by: string | null;
|
|
65
|
-
created_at: string;
|
|
66
|
-
updated_at: string;
|
|
67
|
-
deleted_at: string | null;
|
|
68
|
-
}
|