@ctrl-spc/cs 0.7.10 → 0.7.11
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/mcp.js +94 -357
- package/dist/panel3/tools.js +92 -26
- package/dist/product-tools.js +533 -0
- package/dist/workflow-tool-mentions.js +11 -0
- package/dist/workflows.js +14 -1
- package/package.json +1 -1
package/dist/panel3/tools.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { createEpicHandler, createSprintHandler, listStructureHandler, updateStructureHandler, listStructureArtifactsHandler, getStructureArtifactHandler, searchAgentCardsHandler, createStructureArtifactHandler, updateStructureArtifactHandler } from '../product-tools.js';
|
|
2
|
+
import { listArtifactFoldersHandler, setArtifactFolderHandler, workItemDependencyHandler } from '../product-tools.js';
|
|
1
3
|
/**
|
|
2
4
|
* ═══ AGENT PANEL v3: the tools server, and the per-level allowlist. ═══
|
|
3
5
|
*
|
|
@@ -789,6 +791,36 @@ const TOOLS = [
|
|
|
789
791
|
return `Created Product Idea ${a.title}, id ${id}. No work has started. Only a human can promote it.`;
|
|
790
792
|
},
|
|
791
793
|
},
|
|
794
|
+
{
|
|
795
|
+
name: 'list_artifact_folders', levels: ALL,
|
|
796
|
+
description: 'Read existing artifact folder names and counts before choosing a folder.',
|
|
797
|
+
input: { work_item_id: z.string().uuid() },
|
|
798
|
+
handler: async (caller, args) => productResult(await listArtifactFoldersHandler(caller.client, args.work_item_id)),
|
|
799
|
+
},
|
|
800
|
+
{
|
|
801
|
+
name: 'set_artifact_folder', levels: ALL,
|
|
802
|
+
description: 'Move an artifact into one folder, replacing its previous folder. Null clears it to Unfiled. Content and approval stay unchanged.',
|
|
803
|
+
input: { artifact_id: z.string().uuid(), folder_name: z.string().max(80).nullable() },
|
|
804
|
+
handler: async (caller, args) => productResult(await setArtifactFolderHandler(caller.client, args.artifact_id, args.folder_name)),
|
|
805
|
+
},
|
|
806
|
+
{
|
|
807
|
+
name: 'list_work_item_dependencies', levels: ALL,
|
|
808
|
+
description: 'Read blockers, dependents, and whether this work item is waiting.',
|
|
809
|
+
input: { work_item_id: z.string().uuid(), },
|
|
810
|
+
handler: async (caller, args) => productResult(await workItemDependencyHandler(caller.client, 'list', args)),
|
|
811
|
+
},
|
|
812
|
+
{
|
|
813
|
+
name: 'add_work_item_dependency', levels: [1, 2],
|
|
814
|
+
description: 'Make work_item_id wait for depends_on_work_item_id to reach Done. Same-project work items only; self-links and cycles are refused.',
|
|
815
|
+
input: { work_item_id: z.string().uuid(), depends_on_work_item_id: z.string().uuid(), },
|
|
816
|
+
handler: async (caller, args) => productResult(await workItemDependencyHandler(caller.client, 'add', args)),
|
|
817
|
+
},
|
|
818
|
+
{
|
|
819
|
+
name: 'remove_work_item_dependency', levels: [1, 2],
|
|
820
|
+
description: 'Remove exactly this dependency edge without changing either work item status.',
|
|
821
|
+
input: { work_item_id: z.string().uuid(), depends_on_work_item_id: z.string().uuid(), },
|
|
822
|
+
handler: async (caller, args) => productResult(await workItemDependencyHandler(caller.client, 'remove', args)),
|
|
823
|
+
},
|
|
792
824
|
{
|
|
793
825
|
name: 'place_work_item', levels: [1],
|
|
794
826
|
description: 'Move a work item into an epic or sprint in its project. Null removes placement. Its status stays the same.',
|
|
@@ -964,7 +996,7 @@ const TOOLS = [
|
|
|
964
996
|
.from('tasks')
|
|
965
997
|
.select('id, name, description, status, due_date, project_id, created_at, epics(name), sprints(name)')
|
|
966
998
|
.eq('id', work_item_id), 'read', `work item ${work_item_id}`);
|
|
967
|
-
const artifacts = await rows(client.from('artifacts').select('id, type, title, created_at').eq('task_id', work_item_id)
|
|
999
|
+
const artifacts = await rows(client.from('artifacts').select('id, type, title, created_at, folder_name').eq('task_id', work_item_id)
|
|
968
1000
|
.is('deleted_at', null).order('created_at'), 'read', `the artifacts on work item ${work_item_id}`);
|
|
969
1001
|
const feedback = await rows(client.from('artifact_feedback').select('*').eq('task_id', work_item_id).order('created_at'), 'read', 'the artifact feedback');
|
|
970
1002
|
const decisions = await rows(client.from('decisions').select('id, category, question, state, selected_options, answer_note, related_artifact_id').eq('task_id', work_item_id).order('asked_at'), 'read', 'the work item decisions');
|
|
@@ -983,7 +1015,7 @@ const TOOLS = [
|
|
|
983
1015
|
'',
|
|
984
1016
|
'DECISIONS', JSON.stringify(decisions), 'FEEDBACK', JSON.stringify(feedback), 'COMMENTS', JSON.stringify(comments),
|
|
985
1017
|
`ARTIFACTS ${artifacts.length}`,
|
|
986
|
-
listed(artifacts.map((a) => line(a.id, a.type, a.title ?? '(untitled)')), 'none'),
|
|
1018
|
+
listed(artifacts.map((a) => line(a.id, a.type, a.title ?? '(untitled)', `folder: ${a.folder_name ?? 'Unfiled'}`)), 'none'),
|
|
987
1019
|
].join('\n');
|
|
988
1020
|
},
|
|
989
1021
|
},
|
|
@@ -1681,9 +1713,9 @@ const TOOLS = [
|
|
|
1681
1713
|
input: { work_item_id: z.string() },
|
|
1682
1714
|
handler: async ({ client }, args) => {
|
|
1683
1715
|
const { work_item_id } = args;
|
|
1684
|
-
const artifacts = await rows(client.from('artifacts').select('id, type, format, title, created_at').eq('task_id', work_item_id)
|
|
1716
|
+
const artifacts = await rows(client.from('artifacts').select('id, type, format, title, created_at, folder_name').eq('task_id', work_item_id)
|
|
1685
1717
|
.is('deleted_at', null).order('created_at'), 'read', `the artifacts on work item ${work_item_id}`);
|
|
1686
|
-
return listed(artifacts.map((a) => line(a.id, a.type, a.format, a.title ?? '(untitled)')), 'That work item has no artifacts.');
|
|
1718
|
+
return listed(artifacts.map((a) => line(a.id, a.type, a.format, a.title ?? '(untitled)', `folder: ${a.folder_name ?? 'Unfiled'}`)), 'That work item has no artifacts.');
|
|
1687
1719
|
},
|
|
1688
1720
|
},
|
|
1689
1721
|
{
|
|
@@ -1694,7 +1726,7 @@ const TOOLS = [
|
|
|
1694
1726
|
handler: async ({ client }, args) => {
|
|
1695
1727
|
const { artifact_id } = args;
|
|
1696
1728
|
const artifact = await only(client.from('artifacts')
|
|
1697
|
-
.select('id, task_id, type, format, title, content, storage_path, revision, created_at')
|
|
1729
|
+
.select('id, task_id, type, format, title, content, storage_path, revision, created_at, folder_name')
|
|
1698
1730
|
.eq('id', artifact_id), 'read', `artifact ${artifact_id}`);
|
|
1699
1731
|
return [
|
|
1700
1732
|
`${artifact.title ?? '(untitled)'}`,
|
|
@@ -1702,6 +1734,7 @@ const TOOLS = [
|
|
|
1702
1734
|
`work item ${artifact.task_id}`,
|
|
1703
1735
|
`type ${artifact.type} (${artifact.format})`,
|
|
1704
1736
|
`revision ${artifact.revision}`,
|
|
1737
|
+
`folder ${artifact.folder_name ?? 'Unfiled'}`,
|
|
1705
1738
|
'',
|
|
1706
1739
|
/* A stored file and an empty body are different facts and are said
|
|
1707
1740
|
differently. Returning '' for a PNG would read as an artifact with
|
|
@@ -2048,34 +2081,65 @@ const TOOLS = [
|
|
|
2048
2081
|
},
|
|
2049
2082
|
},
|
|
2050
2083
|
{
|
|
2051
|
-
name: 'create_epic',
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
input: { project_id: z.string(), name: z.string().min(1) },
|
|
2084
|
+
name: 'create_epic', levels: [1, 2],
|
|
2085
|
+
description: 'Create an epic with optional description and dates. Read list_structure first to avoid duplicates.',
|
|
2086
|
+
input: { project_id: z.string().uuid(), name: z.string().min(1), description: z.string().optional(), start_date: z.string().optional(), target_date: z.string().optional() },
|
|
2055
2087
|
handler: async (caller, args) => {
|
|
2056
|
-
const
|
|
2057
|
-
const
|
|
2058
|
-
await receipt(caller, 'epic',
|
|
2059
|
-
return
|
|
2088
|
+
const text = productResult(await createEpicHandler(caller.client, args));
|
|
2089
|
+
const row = JSON.parse(text).epic;
|
|
2090
|
+
await receipt(caller, 'epic', row.id, row.name);
|
|
2091
|
+
return text;
|
|
2060
2092
|
},
|
|
2061
2093
|
},
|
|
2062
2094
|
{
|
|
2063
|
-
name: 'create_sprint',
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
input: {
|
|
2067
|
-
project_id: z.string(),
|
|
2068
|
-
name: z.string().min(1),
|
|
2069
|
-
start_date: z.string().optional().describe('YYYY-MM-DD'),
|
|
2070
|
-
end_date: z.string().optional().describe('YYYY-MM-DD'),
|
|
2071
|
-
},
|
|
2095
|
+
name: 'create_sprint', levels: [1, 2],
|
|
2096
|
+
description: 'Create an sprint with optional description and dates. Read list_structure first to avoid duplicates.',
|
|
2097
|
+
input: { project_id: z.string().uuid(), name: z.string().min(1), description: z.string().optional(), start_date: z.string().optional(), end_date: z.string().optional() },
|
|
2072
2098
|
handler: async (caller, args) => {
|
|
2073
|
-
const
|
|
2074
|
-
const
|
|
2075
|
-
await receipt(caller, 'sprint',
|
|
2076
|
-
return
|
|
2099
|
+
const text = productResult(await createSprintHandler(caller.client, args));
|
|
2100
|
+
const row = JSON.parse(text).sprint;
|
|
2101
|
+
await receipt(caller, 'sprint', row.id, row.name);
|
|
2102
|
+
return text;
|
|
2077
2103
|
},
|
|
2078
2104
|
},
|
|
2105
|
+
{
|
|
2106
|
+
name: 'list_structure', levels: ALL,
|
|
2107
|
+
description: 'Read project epics and sprints with their descriptions and dates.',
|
|
2108
|
+
input: { project_id: z.string().uuid() },
|
|
2109
|
+
handler: async (caller, args) => productResult(await listStructureHandler(caller.client, { project_id: args.project_id })),
|
|
2110
|
+
},
|
|
2111
|
+
{
|
|
2112
|
+
name: 'update_structure', levels: [1, 2],
|
|
2113
|
+
description: 'Edit an epic or sprint name, description, dates, or archive state. Null clears a date. Epics use target_date; sprints use end_date.',
|
|
2114
|
+
input: { kind: z.enum(['epic', 'sprint']), id: z.string().uuid(), name: z.string().min(1).optional(), description: z.string().optional(), start_date: z.string().nullable().optional(), end_date: z.string().nullable().optional(), target_date: z.string().nullable().optional(), archived: z.boolean().optional() },
|
|
2115
|
+
handler: async (caller, args) => productResult(await updateStructureHandler(caller.client, args)),
|
|
2116
|
+
},
|
|
2117
|
+
{
|
|
2118
|
+
name: 'search_agent_cards', levels: ALL,
|
|
2119
|
+
description: 'Find your agent conversations by literal title or message text, or by work item. Includes archived cards unless specified. Follow next_offset for remaining results. Read-only.',
|
|
2120
|
+
input: { query: z.string().max(500).optional(), work_item_id: z.string().uuid().optional(), archived: z.boolean().optional(), limit: z.number().int().min(1).max(1000).optional(), offset: z.number().int().min(0).optional() },
|
|
2121
|
+
handler: async (caller, args) => productResult(await searchAgentCardsHandler(caller.client, args)),
|
|
2122
|
+
},
|
|
2123
|
+
{
|
|
2124
|
+
name: 'list_structure_artifacts', levels: ALL, description: 'Read active artifacts attached directly to an epic or sprint.',
|
|
2125
|
+
input: { kind: z.enum(['epic', 'sprint']), structure_id: z.string().uuid() },
|
|
2126
|
+
handler: async (caller, args) => productResult(await listStructureArtifactsHandler(caller.client, args.kind, args.structure_id)),
|
|
2127
|
+
},
|
|
2128
|
+
{
|
|
2129
|
+
name: 'get_structure_artifact', levels: ALL, description: 'Read a full epic or sprint artifact and its current revision.',
|
|
2130
|
+
input: { artifact_id: z.string().uuid() },
|
|
2131
|
+
handler: async (caller, args) => productResult(await getStructureArtifactHandler(caller.client, args.artifact_id)),
|
|
2132
|
+
},
|
|
2133
|
+
{
|
|
2134
|
+
name: 'create_structure_artifact', levels: ALL, description: 'Create a titled artifact on an epic or sprint. It appears on its management page.',
|
|
2135
|
+
input: { kind: z.enum(['epic', 'sprint']), structure_id: z.string().uuid(), title: z.string().min(1).max(200), type: z.enum(['analysis', 'plan', 'spec', 'user_story', 'diagram', 'mock', 'wireframe']), format: z.enum(['md', 'html', 'json', 'svg']).optional(), content: z.string().min(1) },
|
|
2136
|
+
handler: async (caller, args) => { const text = productResult(await createStructureArtifactHandler(caller.client, caller.userId, args)); const result = JSON.parse(text); await receipt(caller, result.kind, result.structure.id, result.structure.name); return text; },
|
|
2137
|
+
},
|
|
2138
|
+
{
|
|
2139
|
+
name: 'update_structure_artifact', levels: ALL, description: 'Update an epic or sprint artifact using the revision you read. Concurrent edits refuse without overwriting. archived=true removes it; false restores it.',
|
|
2140
|
+
input: { artifact_id: z.string().uuid(), expected_revision: z.number().int().positive(), title: z.string().min(1).max(200).optional(), type: z.enum(['analysis', 'plan', 'spec', 'user_story', 'diagram', 'mock', 'wireframe']).optional(), format: z.enum(['md', 'html', 'json', 'svg']).optional(), content: z.string().optional(), archived: z.boolean().optional() },
|
|
2141
|
+
handler: async (caller, args) => productResult(await updateStructureArtifactHandler(caller.client, args)),
|
|
2142
|
+
},
|
|
2079
2143
|
{
|
|
2080
2144
|
name: 'create_work_item',
|
|
2081
2145
|
levels: [1],
|
|
@@ -2178,6 +2242,7 @@ const TOOLS = [
|
|
|
2178
2242
|
+ FIREWALL_WRITING_RULE,
|
|
2179
2243
|
input: {
|
|
2180
2244
|
work_item_id: z.string(),
|
|
2245
|
+
folder_name: z.string().max(80).nullable().optional().describe('Read list_artifact_folders first to reuse a folder.'),
|
|
2181
2246
|
title: z.string().min(1),
|
|
2182
2247
|
content: z.string().min(1),
|
|
2183
2248
|
type: z.enum(['plan', 'spec', 'analysis', 'diagram', 'mock', 'wireframe', 'user_story']).optional(),
|
|
@@ -2187,6 +2252,7 @@ const TOOLS = [
|
|
|
2187
2252
|
const a = args;
|
|
2188
2253
|
const artifact = await only(caller.client.from('artifacts').insert({
|
|
2189
2254
|
task_id: a.work_item_id,
|
|
2255
|
+
folder_name: a.folder_name ?? null,
|
|
2190
2256
|
title: a.title,
|
|
2191
2257
|
content: a.content,
|
|
2192
2258
|
type: a.type ?? 'plan',
|