@konductro/claude-plugin 2.3.0-rc.1 → 2.4.0-rc.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/package.json +1 -1
- package/servers/konductro-api.js +199 -1
- package/skills/demand-analysis/SKILL.md +104 -0
- package/skills/technical-analysis/SKILL.md +3 -0
package/README.md
CHANGED
|
@@ -122,7 +122,7 @@ These are answers, not errors — none of them is worth retrying as-is.
|
|
|
122
122
|
| Tool | What it does |
|
|
123
123
|
|---|---|
|
|
124
124
|
| `list_tech_analysis_tasks` | List your pending technical analysis assignments |
|
|
125
|
-
| `get_task_context` | Load requirements and project context for a tech analysis task |
|
|
125
|
+
| `get_task_context` | Load requirements and project context for a tech analysis task, plus the requester's context message when a refresh was asked for |
|
|
126
126
|
| `submit_tech_analysis` | Submit your analysis document back to Konductro |
|
|
127
127
|
|
|
128
128
|
### Story decomposition
|
package/package.json
CHANGED
package/servers/konductro-api.js
CHANGED
|
@@ -94,6 +94,14 @@ function refusalText(err) {
|
|
|
94
94
|
return 'Refused: your role on this project is read-only (viewer or stakeholder), so you cannot create or change work items here. Ask an SDM to give you a writing role on the project.';
|
|
95
95
|
case 'NOT_PROJECT_MEMBER':
|
|
96
96
|
return 'Refused: you are not a member of this project, so you cannot create or change work items in it. Ask an SDM to add you to the project team.';
|
|
97
|
+
// ── demand analysis (KON-606) ──
|
|
98
|
+
case 'NOT_YOUR_DEMAND':
|
|
99
|
+
return 'Refused: the technical analysis for this demand is assigned to somebody else. Only they, or the chair of the demand forum, can submit it. Ask the chair to reassign it if it should be yours.';
|
|
100
|
+
case 'WRONG_STAGE':
|
|
101
|
+
// Two different situations behind one code, and the backend says which in its
|
|
102
|
+
// message: the demand has moved past `pitched`, or nobody has been assigned the
|
|
103
|
+
// analysis yet. Neither is worth retrying, so pass its sentence through.
|
|
104
|
+
return null;
|
|
97
105
|
default:
|
|
98
106
|
return null;
|
|
99
107
|
}
|
|
@@ -217,7 +225,7 @@ server.tool(
|
|
|
217
225
|
}
|
|
218
226
|
|
|
219
227
|
const taskList = tasks.map((t, i) =>
|
|
220
|
-
`${i + 1}. **${t.projectName}** — ${t.phaseName}\n Client: ${t.clientName}\n Phase ID: ${t.phaseId}\n ${t.hasExistingDraft ? '(has existing draft)' : '(new analysis)'}`
|
|
228
|
+
`${i + 1}. **${t.projectName}** — ${t.phaseName}\n Client: ${t.clientName}\n Phase ID: ${t.phaseId}\n ${t.refreshRequested ? '(refresh requested)' : t.hasExistingDraft ? '(has existing draft)' : '(new analysis)'}`
|
|
221
229
|
).join('\n\n');
|
|
222
230
|
|
|
223
231
|
return {
|
|
@@ -247,6 +255,25 @@ server.tool(
|
|
|
247
255
|
if (context.phase.description) contextText += `**Description:** ${context.phase.description}\n`;
|
|
248
256
|
contextText += `\n`;
|
|
249
257
|
|
|
258
|
+
// KON-318: a refresh was requested, so this is a revision of an approved
|
|
259
|
+
// analysis rather than a first pass. Sits above the repositories and the
|
|
260
|
+
// previous document deliberately — it changes what is being asked for, so
|
|
261
|
+
// it has to be read before the material it applies to. Absent on a normal
|
|
262
|
+
// task, in which case the output is exactly what it was before.
|
|
263
|
+
if (context.refreshContext) {
|
|
264
|
+
const { requestedBy, message } = context.refreshContext;
|
|
265
|
+
contextText += `### Refresh Requested\n\n`;
|
|
266
|
+
contextText += `A refresh of this technical analysis was requested`;
|
|
267
|
+
if (requestedBy) contextText += ` by ${requestedBy}`;
|
|
268
|
+
contextText += `.\n\n`;
|
|
269
|
+
if (message) {
|
|
270
|
+
contextText += `**Context from the requester:**\n\n${message}\n\n`;
|
|
271
|
+
} else {
|
|
272
|
+
contextText += `No further context was given.\n\n`;
|
|
273
|
+
}
|
|
274
|
+
contextText += `Revise the existing analysis below rather than starting over.\n\n`;
|
|
275
|
+
}
|
|
276
|
+
|
|
250
277
|
if (context.repositories.length > 0) {
|
|
251
278
|
contextText += `### Repositories\n\n`;
|
|
252
279
|
for (const repo of context.repositories) {
|
|
@@ -306,6 +333,177 @@ server.tool(
|
|
|
306
333
|
}
|
|
307
334
|
);
|
|
308
335
|
|
|
336
|
+
// ══ DEMAND ANALYSIS ════════════════════════════════════════════════════════
|
|
337
|
+
//
|
|
338
|
+
// The demand-level counterpart of the three tools above. A DEMAND has no project yet —
|
|
339
|
+
// which projects it lands in is decided at IT Eval, from what this analysis finds — so
|
|
340
|
+
// the context carries the whole workspace's repositories and the submission reports
|
|
341
|
+
// which of them the change actually touches.
|
|
342
|
+
|
|
343
|
+
// Tool: List pending demand analyses
|
|
344
|
+
server.tool(
|
|
345
|
+
'list_demand_analyses',
|
|
346
|
+
'List demand technical analyses assigned to you in Konductro. A demand sits above projects: it is a business request that has passed the demand forum and now needs sizing against the real code.',
|
|
347
|
+
{},
|
|
348
|
+
// workItemCall, not a hand-rolled config check: it asserts the token, turns a 401 into
|
|
349
|
+
// a setup sentence, routes a refusal through refusalText keyed on CODE (KON-517), and
|
|
350
|
+
// FLAGS a genuine failure with isError so the agent stops rather than carrying on. The
|
|
351
|
+
// config check this replaces returned a plain success result, so an unconfigured plugin
|
|
352
|
+
// read to the agent as "no work assigned".
|
|
353
|
+
async () => workItemCall(async () => {
|
|
354
|
+
const { tasks } = await konductroFetch('/api/cli/demand-analyses');
|
|
355
|
+
|
|
356
|
+
if (!tasks || tasks.length === 0) {
|
|
357
|
+
return 'No demand analyses assigned to you.';
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
const lines = tasks.map((t) => {
|
|
361
|
+
const parts = [`### ${t.key} — ${t.title}`];
|
|
362
|
+
if (t.businessUnit) parts.push(`**Business unit:** ${t.businessUnit}`);
|
|
363
|
+
if (t.nextSitting) parts.push(`**Back at the forum:** ${new Date(t.nextSitting).toISOString().slice(0, 10)}`);
|
|
364
|
+
if (t.startWith && t.startWith.length) parts.push(`**Start with:** ${t.startWith.join(', ')}`);
|
|
365
|
+
if (t.askedFor) parts.push(`**They asked:** ${t.askedFor}`);
|
|
366
|
+
parts.push(`\`get_demand_analysis_context\` with demandId ${t.key} to load it.`);
|
|
367
|
+
return parts.join('\n');
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
return `## Demand analyses assigned to you (${tasks.length})\n\n${lines.join('\n\n')}`;
|
|
371
|
+
})
|
|
372
|
+
);
|
|
373
|
+
|
|
374
|
+
// Tool: Load one demand's analysis context
|
|
375
|
+
server.tool(
|
|
376
|
+
'get_demand_analysis_context',
|
|
377
|
+
'Load everything needed to run a demand technical analysis: the pitch as the business wrote it, the value case, what the forum asked to have answered, the repositories to start with, and every repository in the workspace.',
|
|
378
|
+
{
|
|
379
|
+
demandId: z.string().describe('The demand key (e.g. DEM-4) or its UUID'),
|
|
380
|
+
},
|
|
381
|
+
async ({ demandId }) => workItemCall(async () => {
|
|
382
|
+
const ctx = await konductroFetch(`/api/cli/demand-analyses/${encodeURIComponent(demandId)}/context`);
|
|
383
|
+
const d = ctx && ctx.demand;
|
|
384
|
+
if (!d) {
|
|
385
|
+
return `No demand context came back for ${demandId}. Check the key, and that the analysis is assigned to you.`;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
const tpl = ctx.template;
|
|
389
|
+
|
|
390
|
+
const cost = Object.entries(d.cost || {})
|
|
391
|
+
.filter(([, v]) => v)
|
|
392
|
+
.map(([k, v]) => `- **${k}:** ${v}`)
|
|
393
|
+
.join('\n') || '- Not given';
|
|
394
|
+
|
|
395
|
+
// THE PROJECT ID IS THE POINT OF THIS LIST, not decoration. `submit_demand_analysis`
|
|
396
|
+
// takes a `projectId` per proposed phase, and a phase without one creates no real
|
|
397
|
+
// Phase at green light. Rendering only the project NAME left the analyst with no way
|
|
398
|
+
// to supply it, so every phase would have landed homeless and the green light would
|
|
399
|
+
// have created nothing.
|
|
400
|
+
const repos = (ctx.repositories || [])
|
|
401
|
+
.map((r) => `- \`${r.name}\` (${r.projectName}${r.type ? `, ${r.type}` : ''}) — ${r.url}\n projectId: \`${r.projectId}\``)
|
|
402
|
+
.join('\n') || '- None linked to any project yet';
|
|
403
|
+
|
|
404
|
+
const conversation = (ctx.conversation || [])
|
|
405
|
+
.map((c) => `- **${c.by}**${c.kind === 'amendment' ? ' (amendment)' : ''}: ${c.body}`)
|
|
406
|
+
.join('\n');
|
|
407
|
+
|
|
408
|
+
return [
|
|
409
|
+
`# ${d.key} — ${d.title}`,
|
|
410
|
+
'',
|
|
411
|
+
`**Raised by:** ${d.raisedBy}${d.businessUnit ? ` (${d.businessUnit})` : ''}`,
|
|
412
|
+
'',
|
|
413
|
+
'## What they need',
|
|
414
|
+
d.need,
|
|
415
|
+
'',
|
|
416
|
+
'## What happens today',
|
|
417
|
+
d.currentState || 'Not given',
|
|
418
|
+
'',
|
|
419
|
+
'## Who it affects',
|
|
420
|
+
[d.affects, d.affectsWho].filter(Boolean).join(' — ') || 'Not given',
|
|
421
|
+
'',
|
|
422
|
+
'## What it costs to not have it',
|
|
423
|
+
cost,
|
|
424
|
+
'',
|
|
425
|
+
'## Tied to a date',
|
|
426
|
+
d.date?.type ? `${d.date.type}${d.date.by ? ` — ${String(d.date.by).slice(0, 10)}` : ''}${d.date.why ? ` (${d.date.why})` : ''}` : 'No fixed date',
|
|
427
|
+
'',
|
|
428
|
+
'## How they would know it worked',
|
|
429
|
+
d.successLooksLike || 'Not given',
|
|
430
|
+
'',
|
|
431
|
+
// THE WORKSPACE'S OWN QUESTIONS. The endpoint returns the PINNED template —
|
|
432
|
+
// the one this demand was actually raised on, so a question a later template
|
|
433
|
+
// deleted still appears here. Dropping it meant a workspace that asks "Which
|
|
434
|
+
// regulator requires this?" had that answer invisible to the analyst, which is
|
|
435
|
+
// the entire point of the templating feature.
|
|
436
|
+
...(tpl
|
|
437
|
+
? [
|
|
438
|
+
'',
|
|
439
|
+
`## The form they filled in — ${tpl.name} v${tpl.version}`,
|
|
440
|
+
...tpl.sections.flatMap((section) => [
|
|
441
|
+
'',
|
|
442
|
+
`### ${section.name}`,
|
|
443
|
+
...(section.help ? [`_${section.help}_`] : []),
|
|
444
|
+
...section.questions.map((q) =>
|
|
445
|
+
`- **${q.question}** ${q.answer ? `\n ${q.answer}` : '\n _Left blank._'}`),
|
|
446
|
+
]),
|
|
447
|
+
]
|
|
448
|
+
: []),
|
|
449
|
+
'',
|
|
450
|
+
'## What the forum asked you to answer',
|
|
451
|
+
ctx.askedFor || 'Nothing specific. Answer the pitch.',
|
|
452
|
+
'',
|
|
453
|
+
'## Where to start looking',
|
|
454
|
+
(ctx.startWith && ctx.startWith.length) ? ctx.startWith.map((r) => `\`${r}\``).join(', ') : 'Nothing named — use your judgement.',
|
|
455
|
+
'',
|
|
456
|
+
'## Every repository in this workspace',
|
|
457
|
+
'The starting point above is a hint, not a boundary. Report what the change ACTUALLY touches.',
|
|
458
|
+
'',
|
|
459
|
+
repos,
|
|
460
|
+
conversation ? `\n## Conversation on the demand\n${conversation}` : '',
|
|
461
|
+
].join('\n');
|
|
462
|
+
})
|
|
463
|
+
);
|
|
464
|
+
|
|
465
|
+
// Tool: Submit a demand analysis
|
|
466
|
+
server.tool(
|
|
467
|
+
'submit_demand_analysis',
|
|
468
|
+
'Push a finished demand technical analysis back to Konductro: what you found, which repositories the change actually touches, and the phase breakdown you propose. The breakdown is a PROPOSAL — the demand forum decides at IT Eval.',
|
|
469
|
+
{
|
|
470
|
+
demandId: z.string().describe('The demand key (e.g. DEM-4) or its UUID'),
|
|
471
|
+
summary: z.string().describe('What the analysis found, in markdown. What is already there, what has to change, the risks, and the assumptions the estimate rests on.'),
|
|
472
|
+
foundRepos: z.array(z.string()).optional().describe('The repositories the change actually touches, by name. Not the ones you were told to start with.'),
|
|
473
|
+
proposedPhases: z.array(z.object({
|
|
474
|
+
name: z.string().describe('What this phase delivers'),
|
|
475
|
+
projectId: z.string().uuid().optional().describe('The project it lands in, from the context. Omit if it genuinely belongs nowhere yet.'),
|
|
476
|
+
points: z.number().int().min(0).max(1000).describe('Story points for this phase alone. Never send a total; Konductro derives it.'),
|
|
477
|
+
why: z.string().optional().describe('Why it is its own phase rather than part of another'),
|
|
478
|
+
})).optional().describe('The phases you propose. A phase belongs to ONE project; work spanning two projects is two phases.'),
|
|
479
|
+
},
|
|
480
|
+
async ({ demandId, summary, foundRepos, proposedPhases }) => workItemCall(async () => {
|
|
481
|
+
const result = await konductroFetch(`/api/cli/demand-analyses/${encodeURIComponent(demandId)}/submit`, {
|
|
482
|
+
method: 'POST',
|
|
483
|
+
body: JSON.stringify({ summary, foundRepos, proposedPhases }),
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
const homeless = (proposedPhases ?? []).filter((ph) => !ph.projectId).length;
|
|
487
|
+
|
|
488
|
+
return [
|
|
489
|
+
'## Demand analysis submitted',
|
|
490
|
+
'',
|
|
491
|
+
result.message,
|
|
492
|
+
'',
|
|
493
|
+
`- Demand: ${result.key}`,
|
|
494
|
+
`- Submitted: ${result.submittedAt}`,
|
|
495
|
+
// A phase with no project creates no real Phase at green light. That is allowed
|
|
496
|
+
// and is sometimes the honest answer, but it is worth saying out loud here rather
|
|
497
|
+
// than letting the forum discover it when nothing gets created.
|
|
498
|
+
...(homeless
|
|
499
|
+
? ['', `**${homeless} phase${homeless === 1 ? '' : 's'} named no project**, so ${homeless === 1 ? 'it' : 'they'} will not create real work at green light until the forum assigns one.`]
|
|
500
|
+
: []),
|
|
501
|
+
'',
|
|
502
|
+
'The demand forum has been notified and can now record the IT Eval.',
|
|
503
|
+
].join('\n');
|
|
504
|
+
})
|
|
505
|
+
);
|
|
506
|
+
|
|
309
507
|
// Tool: List pending decomposition tasks
|
|
310
508
|
server.tool(
|
|
311
509
|
'list_decomposition_tasks',
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: demand-analysis
|
|
3
|
+
description: Size a Konductro DEMAND against the real code. Use when an architect has been assigned a demand technical analysis — a business request that has passed the demand forum and now needs to be broken into phases and estimated before the forum will commit to it.
|
|
4
|
+
allowed-tools: Read, Grep, Glob, Bash, mcp__konductro__list_demand_analyses, mcp__konductro__get_demand_analysis_context, mcp__konductro__submit_demand_analysis
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Konductro Demand Analysis
|
|
8
|
+
|
|
9
|
+
A demand is a business request that sits ABOVE projects. It has passed the demand forum,
|
|
10
|
+
and the forum will not commit to it until somebody has read the actual code and said what
|
|
11
|
+
it entails. That is this job.
|
|
12
|
+
|
|
13
|
+
## What makes this different from a phase's technical analysis
|
|
14
|
+
|
|
15
|
+
A phase already belongs to a project, so its repositories are known. **A demand does not.**
|
|
16
|
+
Which projects the work lands in is decided at IT Eval, FROM WHAT YOU FIND. So:
|
|
17
|
+
|
|
18
|
+
- The context lists **every repository in the workspace**, not a project's few.
|
|
19
|
+
- The "start with" list is a hint from whoever assigned it. It is not a boundary, and it
|
|
20
|
+
is regularly wrong: the work turns out to be in a repository nobody expected.
|
|
21
|
+
- You report which repositories the change **actually touches**. That is what the forum
|
|
22
|
+
reads, and it is stored separately from the hint you were given.
|
|
23
|
+
|
|
24
|
+
## Workflow
|
|
25
|
+
|
|
26
|
+
### Step 1: See what is assigned
|
|
27
|
+
|
|
28
|
+
Call `list_demand_analyses`. Present the list and ask which one to work on. Each shows
|
|
29
|
+
what the forum asked to have answered and when it is next sitting — that date is the
|
|
30
|
+
deadline, so say it out loud.
|
|
31
|
+
|
|
32
|
+
### Step 2: Load the context
|
|
33
|
+
|
|
34
|
+
Call `get_demand_analysis_context` with the demand key. Read the whole thing before
|
|
35
|
+
touching any code. It carries:
|
|
36
|
+
|
|
37
|
+
- The pitch, in the business's own words, and what happens today
|
|
38
|
+
- The value case: who it affects, and what it costs to not have it
|
|
39
|
+
- **The form they actually filled in**, question by question, on the template the demand
|
|
40
|
+
was RAISED against — so a question this workspace added appears, and one a later
|
|
41
|
+
template deleted still appears on the demands that answered it
|
|
42
|
+
- What the forum specifically asked you to answer
|
|
43
|
+
- Every repository in the workspace, with its project and that project's `projectId`
|
|
44
|
+
|
|
45
|
+
**Say the value case back to the user in one line.** An estimate has to be proportionate
|
|
46
|
+
to the thing being bought, and this is the only place that number appears.
|
|
47
|
+
|
|
48
|
+
### Step 3: Read the code
|
|
49
|
+
|
|
50
|
+
The repositories are on this machine. Use Glob, Grep and Read. Konductro never clones
|
|
51
|
+
anything and neither do you: if a repository named in the context is not present locally,
|
|
52
|
+
say so rather than guessing at it.
|
|
53
|
+
|
|
54
|
+
Start with the repositories you were pointed at, then follow the work wherever it goes.
|
|
55
|
+
|
|
56
|
+
What you are trying to establish, in this order:
|
|
57
|
+
|
|
58
|
+
1. **What already exists.** Half of every demand turns out to be built. Say what is there
|
|
59
|
+
before you say what is needed.
|
|
60
|
+
2. **What actually has to change**, per repository, concretely enough that somebody could
|
|
61
|
+
argue with it.
|
|
62
|
+
3. **What is in the way.** Missing data, an integration nobody has written, a migration
|
|
63
|
+
with no way back.
|
|
64
|
+
4. **What the estimate rests on.** Every assumption you make is a way the number could be
|
|
65
|
+
wrong, and the forum is told which one when it is.
|
|
66
|
+
|
|
67
|
+
### Step 4: Propose the phases
|
|
68
|
+
|
|
69
|
+
This is the part the forum needs most, and the part it cannot do itself.
|
|
70
|
+
|
|
71
|
+
- **A phase belongs to ONE project.** Work spanning two projects is two phases. This is
|
|
72
|
+
not a formality: each phase becomes a real Phase in that project when the demand is
|
|
73
|
+
green lit.
|
|
74
|
+
- **Take `projectId` from the repository list in the context**, where it is printed under
|
|
75
|
+
each repository. A phase without one creates no real Phase at green light, so guessing
|
|
76
|
+
is worse than omitting.
|
|
77
|
+
- **Order them by what has to land first.** If phase two cannot start until phase one is
|
|
78
|
+
deployed, say so in its `why`.
|
|
79
|
+
- **Points per phase, never a total.** Konductro derives the total. Sending your own is
|
|
80
|
+
how the headline number and the breakdown behind it end up disagreeing.
|
|
81
|
+
- **A phase with no obvious home** is allowed: omit `projectId` and say why in the `why`.
|
|
82
|
+
A visible gap beats a phase filed in whichever project came first.
|
|
83
|
+
|
|
84
|
+
### Step 5: Submit
|
|
85
|
+
|
|
86
|
+
Call `submit_demand_analysis` with:
|
|
87
|
+
|
|
88
|
+
- `summary` — what you found. Structure it as: what exists, what has to change, risks,
|
|
89
|
+
assumptions. Markdown.
|
|
90
|
+
- `foundRepos` — the repositories the change actually touches. Not the hint you were given.
|
|
91
|
+
- `proposedPhases` — the breakdown above.
|
|
92
|
+
|
|
93
|
+
The forum is notified and records the IT Eval from your proposal. They can change any of
|
|
94
|
+
it; you are advising, not deciding.
|
|
95
|
+
|
|
96
|
+
## The bar
|
|
97
|
+
|
|
98
|
+
**Never estimate something you have not read.** An unfamiliar name is a reason to go and
|
|
99
|
+
look, never a reason to assume it is small. If a repository you need is not on this
|
|
100
|
+
machine, the honest output is a summary that says which one and what it blocks — not a
|
|
101
|
+
number with a shrug behind it.
|
|
102
|
+
|
|
103
|
+
**Say what you did not check.** The forum is about to commit money against this. A gap you
|
|
104
|
+
name costs an hour; a gap you paper over costs the quarter.
|
|
@@ -20,6 +20,9 @@ Once the user picks a task, call `get_task_context` with the phase ID. This load
|
|
|
20
20
|
- The approved requirements document (what needs to be built)
|
|
21
21
|
- Project and client context
|
|
22
22
|
- Repository information
|
|
23
|
+
- A "Refresh Requested" section, when someone has asked for an existing analysis
|
|
24
|
+
to be redone. Read it first: it carries the requester's reason, and the job is
|
|
25
|
+
to revise the previous analysis rather than start from scratch.
|
|
23
26
|
|
|
24
27
|
Share a summary of the requirements with the user and confirm you're ready to begin.
|
|
25
28
|
|